From c74b9f23c60991bee46ce16b39ebfc221d81376b Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Wed, 8 Jul 2026 17:33:53 -0400 Subject: [PATCH 1/2] Handle ForwardDiff duals in initialization remakes Co-Authored-By: Chris Rackauckas --- .../src/systems/nonlinear/initializesystem.jl | 4 ++-- .../src/systems/problem_utils.jl | 19 ++++++++++++++++++- .../test/initializationsystem.jl | 13 +++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/ModelingToolkitBase/src/systems/nonlinear/initializesystem.jl b/lib/ModelingToolkitBase/src/systems/nonlinear/initializesystem.jl index 88ab4b630c..379e733741 100644 --- a/lib/ModelingToolkitBase/src/systems/nonlinear/initializesystem.jl +++ b/lib/ModelingToolkitBase/src/systems/nonlinear/initializesystem.jl @@ -660,7 +660,7 @@ function _remake_initialization_data_impl( u0 = to_varmap(u0, dvs) symbols_to_symbolics!(sys, u0) end - u0map = as_atomic_dict_with_defaults(Dict{SymbolicT, SymbolicT}(u0), COMMON_NOTHING) + u0map = as_atomic_dict_with_defaults(_symbolic_varmap_dict(u0), COMMON_NOTHING) if eltype(p) <: Pair if p isa Union{AbstractArray, Tuple} p = Dict(p) @@ -673,7 +673,7 @@ function _remake_initialization_data_impl( p = to_varmap(p, ps) symbols_to_symbolics!(sys, p) end - pmap = as_atomic_dict_with_defaults(Dict{SymbolicT, SymbolicT}(p), COMMON_NOTHING) + pmap = as_atomic_dict_with_defaults(_symbolic_varmap_dict(p), COMMON_NOTHING) op = merge!(u0map, pmap) guesses = SymmapT() use_scc = true diff --git a/lib/ModelingToolkitBase/src/systems/problem_utils.jl b/lib/ModelingToolkitBase/src/systems/problem_utils.jl index 291e8e2e44..aa6533ef9a 100644 --- a/lib/ModelingToolkitBase/src/systems/problem_utils.jl +++ b/lib/ModelingToolkitBase/src/systems/problem_utils.jl @@ -1876,6 +1876,23 @@ function operating_point_preprocess(sys::AbstractSystem, op; name = "operating_p return op end +_has_forwarddiff_dual(x) = x isa ForwardDiff.Dual +_has_forwarddiff_dual(x::Union{AbstractArray, Tuple}) = any(_has_forwarddiff_dual, x) + +function _symbolic_varmap_dict(varmap) + dict = Dict{SymbolicT, SymbolicT}() + sizehint!(dict, length(varmap)) + for (k, v) in varmap + val = if _has_forwarddiff_dual(v) + BSImpl.Const{VartypeT}(v; unsafe = true) + else + BSImpl.Const{VartypeT}(v) + end + dict[BSImpl.Const{VartypeT}(k)] = val + end + return dict +end + function build_operating_point(sys::AbstractSystem, op; fast_path = false) if !fast_path op = operating_point_preprocess(sys, op) @@ -1884,7 +1901,7 @@ function build_operating_point(sys::AbstractSystem, op; fast_path = false) # and doesn't override them. This is because explicit `nothing` values in `op` # should be considered as overrides for initial conditions in `ics`. map!(x -> @something(x, CommonSentinel()), values(op)) - op = as_atomic_dict_with_defaults(Dict{SymbolicT, SymbolicT}(op), COMMON_NOTHING) + op = as_atomic_dict_with_defaults(_symbolic_varmap_dict(op), COMMON_NOTHING) ics = add_toterms(initial_conditions(sys); replace = is_discrete_system(sys)) left_merge!(op, ics) map!(values(op)) do v diff --git a/lib/ModelingToolkitBase/test/initializationsystem.jl b/lib/ModelingToolkitBase/test/initializationsystem.jl index 103f3f58ae..1d223ef8bc 100644 --- a/lib/ModelingToolkitBase/test/initializationsystem.jl +++ b/lib/ModelingToolkitBase/test/initializationsystem.jl @@ -1109,6 +1109,19 @@ end end end +@testset "ForwardDiff-tagged symbolic constants before initialization remakes" begin + @parameters P + @variables sx(t) + seed_sys = mtkcompile(System([D(sx) ~ P], t, [sx], [P]; name = :seed_sys)) + + function x_at_1(Pval) + prob = ODEProblem(seed_sys, [sx => Pval, seed_sys.P => Pval], (0.0, 1.0)) + return solve(prob, Tsit5())(1.0) + end + + @test ForwardDiff.derivative(Pval -> x_at_1(Pval), 1.0) ≈ [2.0] +end + @testset "`remake` changes initialization problem types" begin @variables _x(..) y(t) z(t) @parameters p q From c78cc995668b25d6f28bc2d1e29aada4139b0230 Mon Sep 17 00:00:00 2001 From: ChrisRackauckas-Claude Date: Fri, 10 Jul 2026 02:55:08 -0400 Subject: [PATCH 2/2] Remove unsafe initialization varmap workaround Co-Authored-By: Chris Rackauckas --- .../src/systems/nonlinear/initializesystem.jl | 4 ++-- .../src/systems/problem_utils.jl | 19 +------------------ 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/lib/ModelingToolkitBase/src/systems/nonlinear/initializesystem.jl b/lib/ModelingToolkitBase/src/systems/nonlinear/initializesystem.jl index 379e733741..88ab4b630c 100644 --- a/lib/ModelingToolkitBase/src/systems/nonlinear/initializesystem.jl +++ b/lib/ModelingToolkitBase/src/systems/nonlinear/initializesystem.jl @@ -660,7 +660,7 @@ function _remake_initialization_data_impl( u0 = to_varmap(u0, dvs) symbols_to_symbolics!(sys, u0) end - u0map = as_atomic_dict_with_defaults(_symbolic_varmap_dict(u0), COMMON_NOTHING) + u0map = as_atomic_dict_with_defaults(Dict{SymbolicT, SymbolicT}(u0), COMMON_NOTHING) if eltype(p) <: Pair if p isa Union{AbstractArray, Tuple} p = Dict(p) @@ -673,7 +673,7 @@ function _remake_initialization_data_impl( p = to_varmap(p, ps) symbols_to_symbolics!(sys, p) end - pmap = as_atomic_dict_with_defaults(_symbolic_varmap_dict(p), COMMON_NOTHING) + pmap = as_atomic_dict_with_defaults(Dict{SymbolicT, SymbolicT}(p), COMMON_NOTHING) op = merge!(u0map, pmap) guesses = SymmapT() use_scc = true diff --git a/lib/ModelingToolkitBase/src/systems/problem_utils.jl b/lib/ModelingToolkitBase/src/systems/problem_utils.jl index aa6533ef9a..291e8e2e44 100644 --- a/lib/ModelingToolkitBase/src/systems/problem_utils.jl +++ b/lib/ModelingToolkitBase/src/systems/problem_utils.jl @@ -1876,23 +1876,6 @@ function operating_point_preprocess(sys::AbstractSystem, op; name = "operating_p return op end -_has_forwarddiff_dual(x) = x isa ForwardDiff.Dual -_has_forwarddiff_dual(x::Union{AbstractArray, Tuple}) = any(_has_forwarddiff_dual, x) - -function _symbolic_varmap_dict(varmap) - dict = Dict{SymbolicT, SymbolicT}() - sizehint!(dict, length(varmap)) - for (k, v) in varmap - val = if _has_forwarddiff_dual(v) - BSImpl.Const{VartypeT}(v; unsafe = true) - else - BSImpl.Const{VartypeT}(v) - end - dict[BSImpl.Const{VartypeT}(k)] = val - end - return dict -end - function build_operating_point(sys::AbstractSystem, op; fast_path = false) if !fast_path op = operating_point_preprocess(sys, op) @@ -1901,7 +1884,7 @@ function build_operating_point(sys::AbstractSystem, op; fast_path = false) # and doesn't override them. This is because explicit `nothing` values in `op` # should be considered as overrides for initial conditions in `ics`. map!(x -> @something(x, CommonSentinel()), values(op)) - op = as_atomic_dict_with_defaults(_symbolic_varmap_dict(op), COMMON_NOTHING) + op = as_atomic_dict_with_defaults(Dict{SymbolicT, SymbolicT}(op), COMMON_NOTHING) ics = add_toterms(initial_conditions(sys); replace = is_discrete_system(sys)) left_merge!(op, ics) map!(values(op)) do v