Skip to content

fix: [AI] consider variable derivatives when inserting dummy derivative variables into SCCs#67

Merged
AayushSabharwal merged 1 commit intomainfrom
as/fix-dummy-der-scc-order
Apr 8, 2026
Merged

fix: [AI] consider variable derivatives when inserting dummy derivative variables into SCCs#67
AayushSabharwal merged 1 commit intomainfrom
as/fix-dummy-der-scc-order

Conversation

@AayushSabharwal
Copy link
Copy Markdown
Member

Bug: Raw Differential Term in Observed Equation for 2nd-order Kinematic Constraint

Summary

When structurally simplifying a system that contains a 2nd-order kinematic constraint
relating a SelectedState variable x to a non-SelectedState variable y, the observed
equation for default_toterm(D(D(y))) can retain a raw D(D(x)) on its RHS instead of
being fully resolved to algebraic state expressions.

Minimal Reproducer

Consider a system with:

  • A 2nd-order ODE in SelectedState variable x (e.g., dynamics equation solved for D(D(x)))
  • A 2nd-order kinematic constraint: D(D(y)) ~ D(D(x)) + D(D(z)) where y and z are
    non-SelectedState (solved from constraints)

The observed equation for y_tt = diff2term(D(D(y))) will incorrectly contain D(D(x))
in its RHS instead of the fully resolved algebraic expression.

# After structural simplification:
idx = findfirst(isequal(y_tt), observables(ssys))
bad_eq = observed(ssys)[idx]
@test !SymbolicUtils.query(isequal(D(D(x))), bad_eq.rhs)
# Test FAILS: D(D(x)) is present

Variable roles (post-Pantelides, post-state-selection)

Variable Role
x SelectedState
D(x) SelectedState
D(D(x)) Solved from dynamics equation
y Solved from 0th-order constraint
D(y) Solved from 1st-order constraint
D(D(y)) Solved from 2nd-order constraint

The 2nd-order kinematic constraint produces an observed equation for
y_tt = diff2term(D(D(y))).

Root cause

Step 1 — substitute_derivatives_algevars!

Because y is non-SelectedState, the function renames:

  • fullvars[D(y)_idx] = y_t
  • fullvars[D(D(y))_idx] = y_tt
  • Substitutes these terms into all incident equations.
  • Calls diff_to_var[D(D(y))_idx] = nothing, severing the diff-chain primal link.

Because x is SelectedState, the function does not touch it. D(D(x))
remains as the symbolic Differential(t,2)(x) in the 2nd-order constraint equation.

Step 2 — generate_derivative_variables!

Because D(x)_idx is SelectedState, the function creates the dummy variable x_t:

  • add_dd_variable! pushes x_t into fullvars and sets:
    var_to_diff[x_t_idx] = D(D(x))_idx
  • DiffGraph.setindex! overwrites
    diff_to_primal[D(D(x))_idx] from D(x)_idxx_t_idx.
  • Adds the equation 0 ~ D(x) - x_t as dummy_eq.
  • Updates var_eq_matching[D(x)_idx] = dummy_eq.

The BLT (var_sccs) is updated: the original SelectedState SCC {D(x)_idx} at
position i becomes {x_t_idx}, and a new singleton {D(x)_idx} is
inserted at position i (line 248: sccs_to_insert[k] = (i, [dv])).

Step 3 — BLT ordering conflict

generate_system_equations! processes SCCs in BLT order and populates total_sub
(a DerivativeDict) incrementally.

The DiCMO graph gives these ordering constraints:

  • dynamics SCC {D(D(x))_idx} → must precede → constraint SCC {D(D(y))_idx}
    (constraint equation is incident on D(D(x))).
  • x dummy singleton {D(x)_idx}no ordering constraint relative to
    the constraint SCC, because the constraint equation is incident on D(D(x))_idx,
    not D(x)_idx.

When the dynamics equation has no velocity-dependent terms (D(x) is not an
incidence variable of the dynamics equation), the original SelectedState SCC
{D(x)_idx} has no forced position in the BLT and can appear after both the
dynamics SCC and the constraint SCC.

After generate_derivative_variables!, the inserted singleton {D(x)_idx} inherits
that same late position. The actual processing order becomes:

  1. Dynamics SCC {D(D(x))_idx}:
    processes the inline linear SCC path, sets total_sub[D(x_t)] = rhs_dynamics.
  2. Constraint SCC {D(D(y))_idx}:
    diff_to_var[D(D(y))_idx] = nothing (cleared in step 1), so
    make_solved_equation(y_tt, constraint2, total_sub) is called.
    • rhs = D(D(x)) - z_tt (raw from neweqs)
    • fixpoint_sub(D(D(x)), total_sub, MTKBase.Shift):
      • DerivativeDict resolves Differential(t,2)(x) by looking up D(x).
      • D(x) is not yet in total_sub → lookup fails → D(D(x)) stays.
    • Observable stored: y_tt ~ D(D(x)) - z_ttBUG
  3. x dummy singleton {D(x)_idx}:
    sets total_sub[D(x)] = x_ttoo late.

Fix

File: lib/ModelingToolkitTearing/src/reassemble.jl
Function: generate_derivative_variables!, ~line 248

The singleton {dv} must be inserted before the SCC containing
var_to_diff[dv] (the dynamics SCC), not merely at the position of the original
SelectedState SCC.

# Before (buggy):
sccs_to_insert[k] = (i, [dv])

# After (fixed):
ddv = var_to_diff[dv]
i_insert = if ddv isa Int && ddv <= length(v_to_scc)
    min(i, v_to_scc[ddv][1])
else
    i
end
sccs_to_insert[k] = (i_insert, [dv])

v_to_scc[ddv][1] is the BLT position of the SCC containing D(D(x))_idx.
Using min(i, i_ddv) as the insertion point guarantees the dummy singleton runs
before the dynamics SCC regardless of where the original SelectedState SCC was placed.
Since the constraint SCC depends on dynamics (BLT edge), it runs after both, so
total_sub[D(x)] = x_t is available when make_solved_equation is called.

When i ≤ i_ddv (the normal case where the SelectedState SCC was already before
dynamics), min(i, i_ddv) = i and behavior is unchanged.

…ve variables into SCCs

Co-authored-by: Claude <noreply@anthropic.com>
@AayushSabharwal
Copy link
Copy Markdown
Member Author

The above description is anonymized from the MWE and is generated by Claude

@AayushSabharwal AayushSabharwal marked this pull request as draft April 7, 2026 12:27
@AayushSabharwal AayushSabharwal marked this pull request as ready for review April 8, 2026 09:04
@AayushSabharwal AayushSabharwal merged commit 472ffa5 into main Apr 8, 2026
11 of 23 checks passed
@AayushSabharwal AayushSabharwal deleted the as/fix-dummy-der-scc-order branch April 8, 2026 09:04
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.

1 participant