From 83f2fabebce16c3842613dfd2cb0df301def2308 Mon Sep 17 00:00:00 2001 From: Orjan Ameye Date: Mon, 25 May 2026 13:28:01 +0200 Subject: [PATCH] build: support SymbolicUtils 4 / Symbolics 7 * `substitute_all` now recurses into callable variable arguments by passing `filterer = _ -> true` to `SymbolicUtils.substitute`, matching the pre-SU 4 behaviour where `substitute(x(t), t => T)` reached inside `x(t)`. * `is_trig` compares the operation against `cos` / `sin` with `===` so that a callable variable head (e.g. `v1(T)`) no longer triggers symbolic `in`. * `my_isnan(::BasicSymbolic)` unwraps Const-wrapped numbers before calling `isnan`, so `hasnan` on a matrix of `Num(NaN)` returns `true` again. * Drop the now-unused `Symbolics.substitute` import. --- src/QuestBase.jl | 1 - src/Symbolics/Symbolics_utils.jl | 20 +++++++++++++++++++- src/Symbolics/fourier.jl | 5 +++-- src/utils.jl | 6 +++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/QuestBase.jl b/src/QuestBase.jl index 8a80959..db05c1b 100644 --- a/src/QuestBase.jl +++ b/src/QuestBase.jl @@ -27,7 +27,6 @@ using Symbolics: Equation, Differential, arguments, - substitute, term, expand, operation, diff --git a/src/Symbolics/Symbolics_utils.jl b/src/Symbolics/Symbolics_utils.jl index 495265f..09ecefb 100644 --- a/src/Symbolics/Symbolics_utils.jl +++ b/src/Symbolics/Symbolics_utils.jl @@ -63,8 +63,26 @@ Perform substitutions in `rules` on `x`. `Symbolics.substitute_in_deriv`. """ Subtype = Union{Num,Equation,BasicSymbolic} + +# SymbolicUtils 4 / Symbolics 7: substitute() does not recurse into call arguments +# (e.g. it leaves x(t) unchanged when substituting t => T). Walk the expression and +# rewrite matching nodes ourselves. +function _deep_substitute(e::BasicSymbolic, unwrap_rules::Dict) + # SU 4's default filter blocks recursion into the arguments of callable-symbolic + # terms (e.g. `x(t)`). Override with an always-true filter so substitutions like + # `t => T` reach inside `x(t)`. Compound keys still match before recursion, and + # SU does not re-walk the replacement, so self-referential rules don't loop. + return SymbolicUtils.substitute(e, unwrap_rules; filterer = _ -> true) +end +_deep_substitute(x::Num, ur::Dict) = wrap(_deep_substitute(unwrap(x), ur)) +function _deep_substitute(eq::Equation, ur::Dict) + return Equation(_deep_substitute(unwrap(eq.lhs), ur), _deep_substitute(unwrap(eq.rhs), ur)) +end +_deep_substitute(x, ::Dict) = x + function substitute_all(x::Subtype, rules::Dict; include_derivatives=true) - result = substitute(x, rules) + unwrap_rules = Dict(unwrap(k) => unwrap(v) for (k, v) in rules) + result = _deep_substitute(x, unwrap_rules) if include_derivatives result = Symbolics.substitute_in_deriv(result, rules) end diff --git a/src/Symbolics/fourier.jl b/src/Symbolics/fourier.jl index 233baa9..c21f0f5 100644 --- a/src/Symbolics/fourier.jl +++ b/src/Symbolics/fourier.jl @@ -37,8 +37,9 @@ is_trig(f::Num) = is_trig(unwrap(f)) is_trig(f) = false function is_trig(f::BasicSymbolic) f = ispow(f) ? arguments(f)[1] : f - isterm(f) && operation(f) ∈ [cos, sin] && return true - return false + isterm(f) || return false + op = operation(f) + return op === cos || op === sin end """ diff --git a/src/utils.jl b/src/utils.jl index fe063ea..76bfc82 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -37,4 +37,8 @@ end is_identity(A::Matrix{Num}) = (@eqsym A == Matrix{Num}(LinearAlgebra.I, size(A)...)) hasnan(x::Matrix{Num}) = any(my_isnan, unwrap.(x)) my_isnan(x) = isnan(x) -my_isnan(x::BasicSymbolic) = false +function my_isnan(x::BasicSymbolic) + SymbolicUtils.isconst(x) || return false + v = Symbolics.value(x) + return v isa Number && isnan(v) +end