Skip to content

fix: automatically remove false structural and symbolic incidences#34

Merged
AayushSabharwal merged 4 commits intomainfrom
as/eqdiff-singular-var
Feb 24, 2026
Merged

fix: automatically remove false structural and symbolic incidences#34
AayushSabharwal merged 4 commits intomainfrom
as/eqdiff-singular-var

Conversation

@AayushSabharwal
Copy link
Member

@AayushSabharwal AayushSabharwal commented Feb 16, 2026

Close SciML/ModelingToolkit.jl#4282

Some equations in the system can contain false incidence information for some variables, such as in the test where x is symbolically present in the equation but is easily eliminated. This causes some "internal error" warnings, as well as bad simplification. If the equation is differentiated, the problem worsens. Now, find_eq_solvables! has may_be_zero = true by default. It will always remove false incidence information, and update the symbolic equations accordingly too. Inadvertently, this fixes the below issue

Close SciML/ModelingToolkit.jl#3897

since we need to use the new strict flag in linear_expansion (via LinearExpander). Even more incidentally, I think part of the reason this flag wasn't on by default is because linear_expansion would falsely claim that elements of arrays aren't present in an expression that contains an indexed registered array function involving the array. See the test case in https://github.com/JuliaSymbolics/Symbolics.jl/blob/master/test/linear_solver.jl#L168, which this PR depends on. Now that that is fixed, we won't falsely assume that a variable isn't present and thus won't incorrectly remove the incidence. As a result, MTK won't spam warnings in such cases. This shouldn't (and doesn't, for the cases I tried) affect simplification. Since the warnings were ignored anyway, this is a strict improvement. As a result, we can

Close SciML/ModelingToolkit.jl#3003

@AayushSabharwal
Copy link
Member Author

A clarifying description:

The initial problem is that (ignoring ifelse) an equation can symbolically contain a variable, but for linear_expansion or expand to eliminate that variable. This leads to false incidence where the TearingState constructor will say that eq[i] is incident on x, but when building the solvable_graph MTK will complain “hey you told me it contains this variable but I don’t see it.” This code path has a flag to remove false incidences, but it is almost always disabled. The flag also doesn’t update the symbolic information, only structural. This is relevant because if pantelides decides to differentiate eq[i], then eq[i]' will have a false incidence on D(x). Adding a differentiated equation is one of the cases where the aforementioned flag is enabled, which means that MTK will remove the structural incidence of eq[i]' on D(x), but not symbolic. Tearing then doesn’t handle this correctly, since D(x) should be substituted for x_t but isn’t.

I tried to fix this by enabling the flag by default. There’s no place where that function is called that doesn’t want to remove false incidences. I also made it symbolically remove false incidences. However, this leads to two issues:
linear_expansion(ifelse(x < 0, 1, 2), x) will by default return (0, ifelse(x < 0, 1, 2), true) since that expression is linear everywhere except a measure zero set. This is good enough for many cases, and required in some places in MTK. However, the solvable_graph construction will say “hey, this equation is incident on x, linear_expansion says it is linear (affine, really) in x, but the coefficient of x is zero? FALSE INCIDENCE.” With my changes, it would then remove the structural incidence on x. This is bad.
Given variables y[1:3, 1:3] and z[1:3], a registered function like foo(y, z) = y * z and an equation containing foo(y, z)[1], then linear_expansion(foo(y, z)[1], z[1]) returns (0, foo(y, z)[1], true) which is blatantly incorrect. Remember, foo is registered so Symbolics can’t look inside and see this is affine. It should be considered as strictly non-affine. This then means that any equation containing foo(y, z)[1] would be considered to have false incidence on z[i]. My change would then remove this.

We were previously “guarded” from these bugs by the flag being disabled, and resultant warnings being spammed. So,
To fix the first problem, I added a strict flag to linear_expansion which makes linear_expansion(ifelse(x < 0, 1, 2), x) return (0, 0, false). MTK wants to use affine-ness as a proxy for solvability, so this is correct since the expression cannot be analytically solved for x.
The second problem is a plain bug. I just fixed the bug in linear_expansion, and now we don’t get incorrect results.
This then means that false incidence information can be removed both structurally and symbolically as soon as we find it. It does not change simplification for ifelse(x < 0, …, …) or the array case. We get the same results we did before, just without the mess of warnings that were ignored anyway. This also does not mean we can handle ifelse conditions properly. That is still potentially variable index and requires something like dynamic state selection. The only way this PR affects simplification is when simplification gave incorrect and unusable results anyway, as in the issue it originally solves.

…ally

The differentiated equation could symbolically contain a variable but
be singular in it. This would then be removed by `find_eq_solvables!`
but be mishandled in tearing.
@AayushSabharwal AayushSabharwal merged commit 305a6be into main Feb 24, 2026
13 of 23 checks passed
@AayushSabharwal AayushSabharwal deleted the as/eqdiff-singular-var branch February 24, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant