Skip to content

Fix TypeError from nothing-holes in MTKParameters substitution (#4607)#4617

Open
baggepinnen wants to merge 4 commits into
masterfrom
fix-4607-nothing-holes-mtkparameters
Open

Fix TypeError from nothing-holes in MTKParameters substitution (#4607)#4617
baggepinnen wants to merge 4 commits into
masterfrom
fix-4607-nothing-holes-mtkparameters

Conversation

@baggepinnen

Copy link
Copy Markdown
Contributor

Fixes #4607.

Root cause

The issue's stacktrace (promote_symtype failing a ::DataType typeassert with Union{Nothing, Real}) is not caused by a variable with that symtype. Partially specifying an array variable (e.g. pinning a single component of a 3-vector connector flow) leaves COMMON_NOTHING = Const(nothing) holes for the remaining elements in the operating point (as_atomic_dict_with_defaults / write_possibly_indexed_array!); build_operating_point only filters values that are entirely nothing. When another operating-point entry's value expression references such a hole element, MTKParameters — which substituted against the raw operating point — folded Const(nothing) into arithmetic: promote_symtype(*, Real, Nothing)promote_type = Union{Nothing, Real} → typeassert failure (or MethodError: *(::Int, ::Nothing) when all co-arguments are constant).

This only fires on the fully-determined initialization path because that is the one that builds an SCCNonlinearProblem for the initialization system, whose process_SciMLProblemMTKParameters(initsys, op) evaluates all parameters (including Initials) against an op containing the partially-pinned arrays. The underdetermined sibling path instead hits #4237.

Changes

  1. lib/ModelingToolkitBase/src/systems/parameter_buffer.jl: substitute through AtomicArrayDictSubstitutionWrapper (which exists precisely to keep holes symbolic, and is already used by every other substitution site against operating points — problem_utils.jl:348, problem_utils.jl:1599, sccnonlinearproblem.jl:564). Values of Initial parameters that consequently cannot be fully evaluated refer to variables not fixed by the operating point and are treated (elementwise) as unfixed, generalizing the existing val == args[1] → false special case; this matches how timevaring_initsys_process_op! turns non-constant pins into initialization equations rather than Initial values. Non-Initial parameters retain the informative Could not evaluate value of parameter error — which now also replaces the cryptic TypeError for genuinely missing values.

  2. lib/ModelingToolkitBase/src/utils.jl: memoize the _check_operator_variables traversal. With the crash fixed, the repro proceeded into per-SCC generate_rhs and spun indefinitely (100% CPU, flat memory) in this check, which recursed into shared subexpressions repeatedly — exponential on the DAG-shaped expressions of large torn initialization systems.

  3. src/problems/sccnonlinearproblem.jl: the MissingGuessValue.HashedRandom() branch (previously dead code, now reachable) called stable_eachindex on a plain Vector{Int} and referenced an undefined variable var. Fixed mirroring the equivalent branch in problem_utils.jl.

Validation

  • New regression testset in lib/ModelingToolkitBase/test/mtkparameters.jl (the two crash modes now produce the informative error; unresolvable Initial values build; values referencing specified elements still fold).
  • mtkparameters.jl, initial_values.jl, initializationsystem.jl (ModelingToolkitBase) and scc_nonlinear_problem.jl (ModelingToolkit) test files pass locally.
  • The original FullCar repro (Dyad MultibodyComponents#suspension, private — not CI-able): pinning the chassis state (r_0, v_0, phi, phid) plus the four redundant chassis reaction torques (excited_suspension_*.chassis_frame.tau[2] => 0) previously threw the exact TypeError; with this PR the ODEProblem builds successfully. solve subsequently hits a SingularException from the chassis static indeterminacy — that is the separate, pre-existing Unhelpful initialization error #4237 and out of scope here.

🤖 Generated with Claude Code

Fredrik Bagge Carlson and others added 4 commits June 11, 2026 12:09
Partially specifying an array variable (e.g. pinning only one element)
leaves `COMMON_NOTHING` holes for the remaining elements in the
operating point. `MTKParameters` substituted against the raw operating
point, so expressions referencing such hole elements had
`Const(nothing)` folded into arithmetic, throwing
`TypeError: in typeassert, expected DataType, got Type{Union{Nothing, Real}}`
from `promote_symtype` (or `MethodError: *(::Int, ::Nothing)` when all
other arguments are constant). Substitute through
`AtomicArrayDictSubstitutionWrapper` instead, like all other
substitution sites against operating points, so holes are left
symbolic.

Values of `Initial` parameters that consequently cannot be fully
evaluated refer to variables not fixed by the operating point; treat
them (elementwise) as unfixed instead of erroring, generalizing the
existing `val == args[1]` special case. Non-`Initial` parameters retain
the informative "Could not evaluate value of parameter" error.

Fixes #4607

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The recursive traversal visited shared subexpressions repeatedly,
making `check_operator_variables` (called per-equation from
`generate_rhs`) exponential in the depth of DAG-shaped expressions
with heavy sharing. Large torn initialization systems (e.g. multibody
models) spun indefinitely here. Track visited subexpressions in a set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The branch called `stable_eachindex` on a plain `Vector{Int}` (no such
method) and referenced an undefined variable `var`. Compute the hashed
guess per missing unknown, mirroring the semantics of the equivalent
branch in `problem_utils.jl`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (Julia vlts)

Time benchmarks
master 6e724ba... master / 6e724ba...
ODEProblem 14 ± 2.3 ms 11.6 ± 1.7 ms 1.21 ± 0.27
init 0.086 ± 0.031 ms 0.0809 ± 0.032 ms 1.06 ± 0.57
large_parameter_init/ODEProblem 0.0354 ± 0.0043 s 0.0365 ± 0.0058 s 0.967 ± 0.19
large_parameter_init/init 0.0908 ± 0.019 ms 0.096 ± 0.027 ms 0.945 ± 0.33
mtkcompile 15.1 ± 3 ms 13.2 ± 1.7 ms 1.15 ± 0.27
sparse_analytical_jacobian/ODEProblem 0.0404 ± 0.0038 s 0.0415 ± 0.0046 s 0.973 ± 0.14
sparse_analytical_jacobian/f_iip 1.89 ± 0.56 μs 1.47 ± 0.49 μs 1.28 ± 0.57
sparse_analytical_jacobian/f_oop 0.37 ± 0.015 ms 0.364 ± 0.014 ms 1.02 ± 0.055
time_to_load 6.9 ± 0.21 s 6.22 ± 0.32 s 1.11 ± 0.065
Memory benchmarks
master 6e724ba... master / 6e724ba...
ODEProblem 0.0733 M allocs: 3.14 MB 0.0734 M allocs: 3.14 MB 1
init 0.384 k allocs: 0.065 MB 0.384 k allocs: 0.065 MB 1
large_parameter_init/ODEProblem 0.298 M allocs: 10.3 MB 0.301 M allocs: 10.4 MB 0.987
large_parameter_init/init 0.551 k allocs: 0.166 MB 0.551 k allocs: 0.166 MB 1
mtkcompile 0.093 M allocs: 3.94 MB 0.093 M allocs: 3.94 MB 1
sparse_analytical_jacobian/ODEProblem 0.261 M allocs: 9.23 MB 0.261 M allocs: 9.23 MB 1
sparse_analytical_jacobian/f_iip 0 allocs: 0 B 0 allocs: 0 B
sparse_analytical_jacobian/f_oop 0.634 k allocs: 19.6 kB 0.634 k allocs: 19.6 kB 1
time_to_load 0.153 k allocs: 14.6 kB 0.153 k allocs: 14.6 kB 1

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (Julia v1)

Time benchmarks
master 6e724ba... master / 6e724ba...
ODEProblem 10.2 ± 1.1 ms 9.89 ± 0.61 ms 1.04 ± 0.12
init 0.053 ± 0.008 ms 0.0519 ± 0.0076 ms 1.02 ± 0.21
large_parameter_init/ODEProblem 0.0318 ± 0.0052 s 0.0322 ± 0.0045 s 0.988 ± 0.21
large_parameter_init/init 0.0647 ± 0.0042 ms 0.0655 ± 0.004 ms 0.987 ± 0.089
mtkcompile 10.6 ± 1.2 ms 9.88 ± 0.61 ms 1.07 ± 0.14
sparse_analytical_jacobian/ODEProblem 0.0355 ± 0.0032 s 0.0332 ± 0.0032 s 1.07 ± 0.14
sparse_analytical_jacobian/f_iip 0.1 ± 0.01 μs 0.09 ± 0.01 μs 1.11 ± 0.17
sparse_analytical_jacobian/f_oop 0.12 ± 0.0046 ms 0.122 ± 0.0051 ms 0.989 ± 0.056
time_to_load 5.91 ± 0.019 s 6.04 ± 0.038 s 0.978 ± 0.0069
Memory benchmarks
master 6e724ba... master / 6e724ba...
ODEProblem 0.0439 M allocs: 2.32 MB 0.0439 M allocs: 2.32 MB 0.999
init 0.345 k allocs: 0.0447 MB 0.345 k allocs: 0.0447 MB 1
large_parameter_init/ODEProblem 0.262 M allocs: 9.91 MB 0.265 M allocs: 10.1 MB 0.977
large_parameter_init/init 0.685 k allocs: 0.144 MB 0.685 k allocs: 0.144 MB 1
mtkcompile 0.0672 M allocs: 2.85 MB 0.0672 M allocs: 2.84 MB 1
sparse_analytical_jacobian/ODEProblem 0.201 M allocs: 7.34 MB 0.201 M allocs: 7.34 MB 1
sparse_analytical_jacobian/f_iip 0 allocs: 0 B 0 allocs: 0 B
sparse_analytical_jacobian/f_oop 0.848 k allocs: 27 kB 0.848 k allocs: 27 kB 1
time_to_load 0.145 k allocs: 11 kB 0.145 k allocs: 11 kB 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeError (promote_symtype on Union{Nothing,Real}) in MTKParameters when initialization is fully determined

1 participant