Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Changelog

All notable changes to QuestBase.jl will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2026-03-10

### Breaking

- Upgraded dependency bounds to SymbolicUtils.jl v4 and Symbolics.jl v7. Downstream packages must be compatible with these versions.
- Dropped support for SymbolicUtils.jl v3 and Symbolics.jl v6.

### Changed

- Replaced all uses of SymbolicUtils internals (`@compactified`, direct field access like `.base`, `.exp`, `.num`, `.den`, `.f`, `.arguments`, `.coeff`, `.name`) with public API (`isadd`, `ismul`, `isdiv`, `ispow`, `isterm`, `issym`, `operation`, `arguments`, `nameof`, `unwrap_const`).
- Replaced `Symbolics._toexpr` with the public `Symbolics.tosymbol` in `var_name`.
- Replaced `Symbolics.isterm`/`Symbolics.issym`/`Symbolics.is_derivative` with imports from SymbolicUtils/Symbolics rather than qualified calls.
- Removed dependency on `SymbolicUtils.Unityper` (removed in SymbolicUtils v4).
- Removed use of `frac_maketerm` in `add_div` (deleted in SymbolicUtils v4).
- Adapted `substitute_all` to handle Symbolics v7's change where `substitute` no longer recurses into `Differential` arguments; added `include_derivatives` keyword.
- Adapted `_parameters` in `HarmonicEquation` to handle `Symbolics.get_variables` returning `Set{BasicSymbolic}` instead of `Vector{Num}`, and to filter out derivative terms now returned by `get_variables` in v7.
- Adapted `rearrange!` in `DifferentialEquation` for v7's `get_variables` treating derivatives as variables.
- Adapted `trig_to_exp` to work at the `BasicSymbolic` level to avoid `Complex{Num}` issues with v7's `exp(im * Num)`.
- Adapted `exp_to_trig` to handle `Const`-wrapped zero arguments (`exp(Const(0)) = 1`).
- Adapted `_has_negative_coefficient` to unwrap `Const`-wrapped numeric coefficients in SymbolicUtils v4.
- Adapted `power_of`, `simplify_exp_products_mul`, and `trig_to_exp` to use `unwrap_const` for `Const`-wrapped numeric values in expression arguments.

### Performance

Benchmarks show significant improvements from SymbolicUtils v4 / Symbolics v7:

- **Fourier pipeline**: 3--5x faster (`fourier_cos_term`, `fourier_sin_term`, `trig_reduce`, `trig_to_exp`, `exp_to_trig`)
- **Power operations**: 3--4x faster (`max_power`, `drop_powers` with multiple variables)
- **Exponential simplification**: ~2x faster (`expand_exp_power`, `simplify_exp_products`)
- **Harmonic checks**: ~5x faster (`is_harmonic`)
- **Construction**: ~1.5x faster (`DifferentialEquation`)
- **Substitution**: 1.3--3x faster (`substitute_all`)
- Minor regressions on sub-millisecond operations (`expand_fraction`, `_apply_termwise` on `Div`, `drop_powers` with single variable)

### Added

- Benchmark suite in `benchmark/` for tracking performance across versions.
8 changes: 4 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "QuestBase"
uuid = "7e80f742-43d6-403d-a9ea-981410111d43"
authors = ["Orjan Ameye <orjan.ameye@hotmail.com>", "Jan Kosata <kosataj@phys.ethz.ch>", "Javier del Pino <jdelpino@phys.ethz.ch>"]
version = "0.3.4"
version = "0.4.0"

[deps]
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Expand All @@ -12,16 +12,16 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"

[compat]
DocStringExtensions = "0.9.4"
SymbolicUtils = "3.25"
Symbolics = "6.34"
SymbolicUtils = "4"
Symbolics = "7"
julia = "1.10"
Random = "1.10"
LinearAlgebra = "1.10"
Test = "1.10"
OrderedCollections = "1.8"
Aqua = "0.8.11"
ExplicitImports = "1.11"
JET = "0.9.18, 0.10.0"
JET = "0.9.18, 0.10.0, 0.11"
CheckConcreteStructs = "0.1.0"

[extras]
Expand Down
5 changes: 5 additions & 0 deletions benchmark/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[deps]
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
QuestBase = "7e80f742-43d6-403d-a9ea-981410111d43"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
1 change: 1 addition & 0 deletions benchmark/baseline.json

Large diffs are not rendered by default.

220 changes: 220 additions & 0 deletions benchmark/benchmarks.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
using BenchmarkTools
using QuestBase
using QuestBase:
expand_all,
expand_fraction,
_apply_termwise,
simplify_complex,
get_independent,
get_all_terms,
expand_exp_power,
simplify_exp_products,
trig_to_exp,
exp_to_trig,
trig_reduce,
fourier_cos_term,
fourier_sin_term,
drop_powers,
max_power,
power_of,
substitute_all,
is_harmonic,
is_function,
count_derivatives,
add_div,
DifferentialEquation,
HarmonicVariable,
HarmonicEquation,
add_harmonic!,
rearrange_standard!,
d,
var_name
using Symbolics: Symbolics, @variables, unwrap, expand, Num, Equation, Differential
using SymbolicUtils: BasicSymbolic

const SUITE = BenchmarkGroup()

# --- Setup variables ---
@variables t x(t) y(t) ω0 ω F k a b c f θ
D = Differential(t)

# ==========================================================================
# Symbolic utilities
# ==========================================================================
SUITE["symbolic_utils"] = BenchmarkGroup()

expr_nested = (a + b * c)^2 + (a - c) / b
expr_complex = a + b * cos(f * t) + c * sin(f * t)

SUITE["symbolic_utils"]["expand_all_nested"] = @benchmarkable expand_all($expr_nested)
SUITE["symbolic_utils"]["expand_all_trig"] = @benchmarkable expand_all($expr_complex)

SUITE["symbolic_utils"]["expand_fraction"] = @benchmarkable expand_fraction(
$(unwrap((a^2 + b * c + a * b^2 + c^3) / (a + b)))
)

SUITE["symbolic_utils"]["_apply_termwise_add"] = @benchmarkable _apply_termwise(
simplify_complex, $(unwrap(a^2 + b * c + a * b^2 + c^3 + a * b * c))
)
SUITE["symbolic_utils"]["_apply_termwise_mul"] = @benchmarkable _apply_termwise(
simplify_complex, $(unwrap(a * b * c * (a + b) * (b + c)))
)
SUITE["symbolic_utils"]["_apply_termwise_div"] = @benchmarkable _apply_termwise(
simplify_complex, $(unwrap((a^2 + b * c + c^3) / (a * b + c^2)))
)

SUITE["symbolic_utils"]["simplify_complex_add"] = @benchmarkable simplify_complex(
$(unwrap(Complex{Num}(a^2 + b * c, 0 * a)))
)
SUITE["symbolic_utils"]["simplify_complex_real"] = @benchmarkable simplify_complex(
$(Complex{Num}(a^2 + b * c, 0 * a))
)

SUITE["symbolic_utils"]["get_independent_simple"] = @benchmarkable get_independent(
$(a^2 + b * c + a * b), $t
)
SUITE["symbolic_utils"]["get_independent_trig"] = @benchmarkable get_independent(
$(cos(f * t)^2 + a * sin(f * t) + b), $t
)

SUITE["symbolic_utils"]["get_all_terms_nested"] = @benchmarkable get_all_terms($expr_nested)

SUITE["symbolic_utils"]["is_function_true"] = @benchmarkable is_function(
$(cos(f * t)^2 + a), $t
)
SUITE["symbolic_utils"]["is_function_false"] = @benchmarkable is_function(
$(a^2 + b * c), $t
)

SUITE["symbolic_utils"]["count_derivatives_0"] = @benchmarkable count_derivatives($x)
SUITE["symbolic_utils"]["count_derivatives_2"] = @benchmarkable count_derivatives(
$(d(d(x, t), t))
)

SUITE["symbolic_utils"]["var_name"] = @benchmarkable var_name($x)

# ==========================================================================
# Exponentials
# ==========================================================================
SUITE["exponentials"] = BenchmarkGroup()

SUITE["exponentials"]["expand_exp_power"] = @benchmarkable expand_exp_power(
$(unwrap(exp(a)^3 + exp(b)^2 + a * exp(c)^4))
)
SUITE["exponentials"]["simplify_exp_products"] = @benchmarkable simplify_exp_products(
$(unwrap(exp(3a) * exp(4b) + exp(a) * exp(c)))
)

# ==========================================================================
# Fourier
# ==========================================================================
SUITE["fourier"] = BenchmarkGroup()

trig_expr_sq = cos(f * t)^2
trig_expr_product =
(a * sin(f * t) + b * cos(f * t)) *
(a * sin(2 * f * t) + b * cos(2 * f * t)) *
(a * sin(3 * f * t) + b * cos(3 * f * t))
trig_expr_hard = (a + b * cos(f * t + θ)^2)^3 * sin(f * t)

SUITE["fourier"]["trig_to_exp_sq"] = @benchmarkable trig_to_exp($trig_expr_sq)
SUITE["fourier"]["trig_to_exp_product"] = @benchmarkable trig_to_exp($trig_expr_product)

exp_sum = unwrap(exp(im * a) + exp(-im * a) + exp(im * (a + b)))
SUITE["fourier"]["exp_to_trig_sum"] = @benchmarkable exp_to_trig($exp_sum)

SUITE["fourier"]["add_div"] = @benchmarkable add_div($(a / (b + c) + b / (a + c)))

SUITE["fourier"]["trig_reduce_simple"] = @benchmarkable trig_reduce($trig_expr_sq)
SUITE["fourier"]["trig_reduce_product"] = @benchmarkable trig_reduce($trig_expr_product)

SUITE["fourier"]["fourier_cos_term_simple"] = @benchmarkable fourier_cos_term(
$(cos(f * t)), $f, $t
)
SUITE["fourier"]["fourier_cos_term_sq"] = @benchmarkable fourier_cos_term(
$trig_expr_sq, $(2f), $t
)
SUITE["fourier"]["fourier_sin_term_simple"] = @benchmarkable fourier_sin_term(
$(sin(f * t)), $f, $t
)
SUITE["fourier"]["fourier_cos_term_product"] = @benchmarkable fourier_cos_term(
$trig_expr_product, $(2f), $t
)
SUITE["fourier"]["fourier_cos_term_hard"] = @benchmarkable fourier_cos_term(
$trig_expr_hard, $f, $t
)

# ==========================================================================
# Powers
# ==========================================================================
SUITE["powers"] = BenchmarkGroup()

SUITE["powers"]["max_power_simple"] = @benchmarkable max_power($(a^2 + b), $a)
SUITE["powers"]["max_power_nested"] = @benchmarkable max_power($(a * ((a + b)^4)^2 + a), $a)
SUITE["powers"]["power_of_pow"] = @benchmarkable power_of($(unwrap(a^3)), $(unwrap(a)))
SUITE["powers"]["drop_powers_single_var"] = @benchmarkable drop_powers(
$((a + b)^3 + a^2 * b + a * b^2), $a, 2
)
SUITE["powers"]["drop_powers_multi"] = @benchmarkable drop_powers($((a + b)^2), [$a, $b], 1)
SUITE["powers"]["drop_powers_high"] = @benchmarkable drop_powers(
$((a + b)^3 + (a + b)^5), [$a, $b], 4
)

# ==========================================================================
# Substitution
# ==========================================================================
SUITE["substitution"] = BenchmarkGroup()

@variables d_var e_var f_var g_var h_var
rules_medium = Dict(a => b, c => d_var, e_var => f_var)
expr_sub = a + b + c + d_var + e_var + f_var

SUITE["substitution"]["substitute_all_1rule"] = @benchmarkable substitute_all(
$(a + b), $(a => b)
)
SUITE["substitution"]["substitute_all_3rules"] = @benchmarkable substitute_all(
$expr_sub, $rules_medium
)
SUITE["substitution"]["substitute_all_vector"] = @benchmarkable substitute_all(
[$a, $c, $e_var], $rules_medium
)
SUITE["substitution"]["substitute_all_no_deriv"] = @benchmarkable substitute_all(
$(a^2 + b * c), $(Dict(a => b)); include_derivatives=false
)
SUITE["substitution"]["substitute_all_with_deriv"] = @benchmarkable substitute_all(
$(D(x) + a * x), $(Dict(x => 2x, a => b))
)

# ==========================================================================
# Construction and rearrangement
# ==========================================================================
SUITE["construction"] = BenchmarkGroup()

SUITE["construction"]["DifferentialEquation_single"] = @benchmarkable DifferentialEquation(
d($x, $t, 2) + $ω0^2 * $x ~ $F * cos($ω * $t), $x
)
SUITE["construction"]["DifferentialEquation_coupled"] = @benchmarkable DifferentialEquation(
[d($x, $t, 2) + $ω0^2 * $x - $k * $y, d($y, $t, 2) + $ω0^2 * $y - $k * $x] .~
[$F * cos($ω * $t), 0],
[$x, $y],
)

# Rearrangement benchmark
diff_eq_for_rearrange = DifferentialEquation(
[d(x, t, 2) + ω0^2 * x - k * y ~ F * cos(ω * t), d(y, t, 2) + ω0^2 * y - k * x ~ 0],
[x, y],
)
SUITE["construction"]["rearrange_standard"] = @benchmarkable rearrange_standard!(eom) setup = (
eom = deepcopy($diff_eq_for_rearrange)
)

# ==========================================================================
# Harmonic checks
# ==========================================================================
SUITE["harmonic"] = BenchmarkGroup()

SUITE["harmonic"]["is_harmonic_true"] = @benchmarkable is_harmonic($(cos(f * t)), $t)
SUITE["harmonic"]["is_harmonic_false"] = @benchmarkable is_harmonic($(cos(f * t^2 + a)), $t)
SUITE["harmonic"]["is_harmonic_complex"] = @benchmarkable is_harmonic(
$(a * cos(f * t) + b * sin(2f * t) + c), $t
)
6 changes: 5 additions & 1 deletion docs/pages.jl
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
pages = ["Home" => "index.md", "API" => "API.md"]
pages = [
"Home" => "index.md",
"API" => "API.md",
"Developer" => ["Symbolic Rewriting" => "dev/symbolic_rewriting.md"],
]
Loading
Loading