state_priority is ineffective when the prioritized variable's derivative is matched to a differentiated alias equation: singleton SCC forces demotion, yielding singular Cartesian states in a trivial planar arm
Summary
In a planar 2-link serial arm, joint angles/velocities are marked state_priority = 10
(raising to 100 changes nothing) and body Cartesian variables state_priority = 1–2, yet
dummy-derivative selection picks the Cartesian body coordinates as differential states
and leaves one algebraic equation that solves for a joint angle:
0 ~ -(robot₊link1₊r0(t))[2] + robot₊link1₊r[1]*sin(robot₊revolute2₊frame_a₊phi(t)) + robot₊link1₊r[2]*cos(robot₊revolute2₊frame_a₊phi(t))
With r = [1, 0], the Jacobian of this equation w.r.t. its unknown is cos(phi) —
singular at phi = π/2, which is exactly the target configuration of the example's
point-to-point motion. Every solver dies with dt → eps (retcode = Unstable) as the arm
approaches the target; better-tuned controllers fail more reliably because they approach
the singular configuration more exactly. With joint coordinates as states (minimal
coordinates, what Dymola selects for this model) the system is a causal ODE with no
algebraic equation and no singularity.
What was ruled out
Traced with MTK v11.26.8 (ModelingToolkitTearing v1.13.5) / StateSelection v1.9.3:
- Metadata propagation is correct. On the flattened system:
revolute*.phi/w
priority 10, body.r/v priority 1, body.phi/w priority 2 (array element metadata
resolves through getindex correctly).
- Alias elimination is correct. After
eliminate_perfect_aliases! +
alias_elimination!, the prioritized variables survive with correct entries in
structure.state_priorities (pick_alias_target respects priority; the joint angle
sits in a 3-variable relation frame_a.phi + phi ~ frame_b.phi and is not merged).
- The Jacobian/
col_order path is not exercised. A separate latent issue was found in
dummy_derivative_graph! (src/partial_state_selection.jl): when state_priority is
given, vars is priority-sorted but a previously-computed integer Jacobian J is not
column-permuted, so vars[col_order[i]] indexes mismatched orders. In this model,
however, the kinematic-loop SCC has a trigonometric (non-integer) Jacobian, so jac
returns nothing and the matching path runs (verified by instrumenting the jac
closure: (neqs, nvars, integer) = (11, 13, false)). The matching path respects the
sort within an SCC.
Root cause
dummy_derivative_graph! consults state_priority only within each SCC of
find_var_sccs(graph, var_eq_matching), and the SCC partition comes from the
priority-blind Pantelides matching. Dumping the partition (via log = Val(true) /
DummyDerivativeSummary) for this model:
-
The kinematic-loop SCC (13 candidates, 11 differentiated equations) contains only
frame/body variables (priorities 0–2). 11 are demoted; the survivors are necessarily
body Cartesian coordinates.
-
Each joint-coordinate derivative sits in its own singleton SCC, matched to a
twice-differentiated alias/connection equation, e.g.
SCC #152: D²(robot₊revolute1₊phi) matched to 0 ~ D²(robot₊revolute1₊phi) - D²(robot₊revolute2₊frame_a₊phi)
SCC #153: D²(robot₊revolute2₊phi) matched to 0 ~ -D²(robot₊revolute1₊phi) - D²(robot₊revolute2₊phi) + D²(robot₊body₊phi)
SCC #156: D(robot₊revolute2₊w) matched to 0 ~ -D(robot₊revolute2₊w) + D²(robot₊revolute2₊phi)
SCC #159: D(robot₊revolute1₊w) matched to 0 ~ D²(robot₊revolute1₊phi) - D(robot₊revolute1₊w)
A singleton SCC with one differentiated equation demotes its only candidate
unconditionally — priority is never compared against anything. This is why raising the
priority from 10 to 100 has zero effect.
The matching is not unique: matching 0 ~ D²(rev1.phi) - D²(rev2.frame_a.phi) to
D²(rev2.frame_a.phi) instead would pull rev1.phi into the loop SCC, where the priority
sort would (correctly) keep it as a state and demote the Cartesian candidates. The outcome
is thus legal w.r.t. the dummy-derivative algorithm for some matching, but the
state_priority feature is silently ineffective for the very common pattern where the
prioritized variable is connected to a kinematic loop through a rigid alias chain
(joint → flange → frame connections — i.e., essentially every multibody joint).
Reproduction
Model: TwoJointPlanarRobotPID / TwoJointPlanarArm on the twodof branch of
JuliaComputing/MultibodyComponents.jl (world → revolute1 → link1 → revolute2 → link2 →
body; Revolute.phi/w have state_priority = 10 by default, Body.r/v 1 and
Body.phi/w 2).
using ModelingToolkit, MultibodyComponents
using MultibodyComponents: multibody
@named model = MultibodyComponents.PlanarMechanics.examples.TwoJointPlanarRobotPID()
ssys = multibody(model) # mtkcompile with multibody-suitable options
unknowns(ssys) # body.phi, body.w, body.r[2], body.v[2] differential;
# revolute2.frame_a.phi algebraic (singular at phi = π/2)
Instrumentation used to localize the issue (priority traces, DummyDerivativeSummary
dump, matched-equation dump per SCC) can be provided.
Workarounds attempted
statePriority = 100 on the joints: no effect (mechanism above).
- First-order filter between the subsystems whose coupling was initially suspected:
no effect on state selection (the selection is a property of the arm kinematics alone).
- Only effective mitigation so far: use
FBDF and avoid resting exactly at the singular
configuration — clearly not a fix.
Possible directions
- Make the matching/SCC condensation priority-aware: when a differentiated 2-variable
alias equation can be matched to either end, prefer matching it to the lower-priority
variable so high-priority variables remain selectable.
- Post-pass: for singleton SCCs created by differentiated alias equations, allow swapping
the demoted variable with its alias partner when the partner has lower priority.
- At minimum, document/warn that
state_priority cannot influence selection in this
situation (it currently fails silently).
Related (checked, distinct): #96 (priority-aware
algebraic tearing), SciML/ModelingToolkit.jl#3093 (alias-aware priority propagation,
pre-StateSelection-split prototype), StateSelection.jl#74/#75 (merged, present in v1.9.3).
The unpermuted-Jacobian/col_order mismatch described above is a separate latent bug and
can be filed separately if useful.
state_priorityis ineffective when the prioritized variable's derivative is matched to a differentiated alias equation: singleton SCC forces demotion, yielding singular Cartesian states in a trivial planar armSummary
In a planar 2-link serial arm, joint angles/velocities are marked
state_priority = 10(raising to 100 changes nothing) and body Cartesian variables
state_priority = 1–2, yetdummy-derivative selection picks the Cartesian body coordinates as differential states
and leaves one algebraic equation that solves for a joint angle:
With
r = [1, 0], the Jacobian of this equation w.r.t. its unknown iscos(phi)—singular at phi = π/2, which is exactly the target configuration of the example's
point-to-point motion. Every solver dies with dt → eps (
retcode = Unstable) as the armapproaches the target; better-tuned controllers fail more reliably because they approach
the singular configuration more exactly. With joint coordinates as states (minimal
coordinates, what Dymola selects for this model) the system is a causal ODE with no
algebraic equation and no singularity.
What was ruled out
Traced with MTK v11.26.8 (ModelingToolkitTearing v1.13.5) / StateSelection v1.9.3:
revolute*.phi/wpriority 10,
body.r/vpriority 1,body.phi/wpriority 2 (array element metadataresolves through
getindexcorrectly).eliminate_perfect_aliases!+alias_elimination!, the prioritized variables survive with correct entries instructure.state_priorities(pick_alias_targetrespects priority; the joint anglesits in a 3-variable relation
frame_a.phi + phi ~ frame_b.phiand is not merged).col_orderpath is not exercised. A separate latent issue was found indummy_derivative_graph!(src/partial_state_selection.jl): whenstate_priorityisgiven,
varsis priority-sorted but a previously-computed integer JacobianJis notcolumn-permuted, so
vars[col_order[i]]indexes mismatched orders. In this model,however, the kinematic-loop SCC has a trigonometric (non-integer) Jacobian, so
jacreturns
nothingand the matching path runs (verified by instrumenting thejacclosure:
(neqs, nvars, integer) = (11, 13, false)). The matching path respects thesort within an SCC.
Root cause
dummy_derivative_graph!consultsstate_priorityonly within each SCC offind_var_sccs(graph, var_eq_matching), and the SCC partition comes from thepriority-blind Pantelides matching. Dumping the partition (via
log = Val(true)/DummyDerivativeSummary) for this model:The kinematic-loop SCC (13 candidates, 11 differentiated equations) contains only
frame/body variables (priorities 0–2). 11 are demoted; the survivors are necessarily
body Cartesian coordinates.
Each joint-coordinate derivative sits in its own singleton SCC, matched to a
twice-differentiated alias/connection equation, e.g.
A singleton SCC with one differentiated equation demotes its only candidate
unconditionally — priority is never compared against anything. This is why raising the
priority from 10 to 100 has zero effect.
The matching is not unique: matching
0 ~ D²(rev1.phi) - D²(rev2.frame_a.phi)toD²(rev2.frame_a.phi)instead would pullrev1.phiinto the loop SCC, where the prioritysort would (correctly) keep it as a state and demote the Cartesian candidates. The outcome
is thus legal w.r.t. the dummy-derivative algorithm for some matching, but the
state_priorityfeature is silently ineffective for the very common pattern where theprioritized variable is connected to a kinematic loop through a rigid alias chain
(joint → flange → frame connections — i.e., essentially every multibody joint).
Reproduction
Model:
TwoJointPlanarRobotPID/TwoJointPlanarArmon thetwodofbranch ofJuliaComputing/MultibodyComponents.jl (world → revolute1 → link1 → revolute2 → link2 →
body;
Revolute.phi/whavestate_priority = 10by default,Body.r/v1 andBody.phi/w2).Instrumentation used to localize the issue (priority traces,
DummyDerivativeSummarydump, matched-equation dump per SCC) can be provided.
Workarounds attempted
statePriority = 100on the joints: no effect (mechanism above).no effect on state selection (the selection is a property of the arm kinematics alone).
FBDFand avoid resting exactly at the singularconfiguration — clearly not a fix.
Possible directions
alias equation can be matched to either end, prefer matching it to the lower-priority
variable so high-priority variables remain selectable.
the demoted variable with its alias partner when the partner has lower priority.
state_prioritycannot influence selection in thissituation (it currently fails silently).
Related (checked, distinct): #96 (priority-aware
algebraic tearing), SciML/ModelingToolkit.jl#3093 (alias-aware priority propagation,
pre-StateSelection-split prototype), StateSelection.jl#74/#75 (merged, present in v1.9.3).
The unpermuted-Jacobian/
col_ordermismatch described above is a separate latent bug andcan be filed separately if useful.