From 5662eca837ea3b0b21ef489520f7b1cb2fcbd90e Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 16 Feb 2026 18:33:40 +0530 Subject: [PATCH 1/4] feat: implement `setindex!` for `EquationsView` --- lib/ModelingToolkitTearing/src/tearingstate.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/ModelingToolkitTearing/src/tearingstate.jl b/lib/ModelingToolkitTearing/src/tearingstate.jl index 198f533..745e9c2 100644 --- a/lib/ModelingToolkitTearing/src/tearingstate.jl +++ b/lib/ModelingToolkitTearing/src/tearingstate.jl @@ -86,6 +86,15 @@ function Base.getindex(ev::EquationsView, i::Integer) end return eqs[i] end +function Base.setindex!(ev::EquationsView, v::Equation, i::Integer) + eqs = equations(ev.ts.sys) + if i > length(eqs) + ev.ts.extra_eqs[i - length(eqs)] = v + else + eqs[i] = v + end + return ev +end function Base.push!(ev::EquationsView, eq) push!(ev.ts.extra_eqs, eq) end From feb33544a77e0b5ce8de4d67d27b862d9f1488a2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 16 Feb 2026 23:39:38 +0530 Subject: [PATCH 2/4] build: bump Symbolics compat --- lib/ModelingToolkitTearing/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ModelingToolkitTearing/Project.toml b/lib/ModelingToolkitTearing/Project.toml index 5e9c460..da4389e 100644 --- a/lib/ModelingToolkitTearing/Project.toml +++ b/lib/ModelingToolkitTearing/Project.toml @@ -38,7 +38,7 @@ SparseArrays = "1" StateSelection = "1.3" SymbolicIndexingInterface = "0.3" SymbolicUtils = "4.3" -Symbolics = "7.1" +Symbolics = "7.15.1" julia = "1.10" [extras] From 54e1a7ea4bdc8341f18418248fe8c22a35445745 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 16 Feb 2026 18:34:53 +0530 Subject: [PATCH 3/4] fix: automatically remove false incidences, structurally and symbolically 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. --- .../src/stateselection_interface.jl | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/lib/ModelingToolkitTearing/src/stateselection_interface.jl b/lib/ModelingToolkitTearing/src/stateselection_interface.jl index d75f25a..a740176 100644 --- a/lib/ModelingToolkitTearing/src/stateselection_interface.jl +++ b/lib/ModelingToolkitTearing/src/stateselection_interface.jl @@ -13,6 +13,7 @@ function StateSelection.eq_derivative!(ts::TearingState, ieq::Int; kwargs...) eq_diff = StateSelection.eq_derivative_graph!(s, ieq) sys = ts.sys + eqs = equations(ts) eq = equations(ts)[ieq] eq = Symbolics.COMMON_ZERO ~ substitute( Symbolics.derivative( @@ -30,20 +31,34 @@ function StateSelection.eq_derivative!(ts::TearingState, ieq::Int; kwargs...) """) end - push!(equations(ts), eq) + push!(eqs, eq) # Analyze the new equation and update the graph/solvable_graph # First, copy the previous incidence and add the derivative terms. # That's a superset of all possible occurrences. find_solvables! will # remove those that doesn't actually occur. - eq_diff = length(equations(ts)) + @assert length(equations(ts)) == eq_diff for var in đť‘ neighbors(s.graph, ieq) add_edge!(s.graph, eq_diff, var) add_edge!(s.graph, eq_diff, s.var_to_diff[var]) end - s.solvable_graph === nothing || - StateSelection.find_eq_solvables!( - ts, eq_diff; may_be_zero = true, allow_symbolic = false, kwargs...) + # `symbolically_rm_singular = false` because a lot of the removed + # variables aren't actually symbolically present in the system. This + # will thus cause a lot of unnecessary calls to `Symbolics.linear_expansion`. + # We already ran `search_variables!`, so we can identify the ones that + # really need to be removed here. + to_rm = Int[] + s.solvable_graph === nothing || StateSelection.find_eq_solvables!( + ts, eq_diff, to_rm; may_be_zero = true, allow_symbolic = false, + symbolically_rm_singular = false, kwargs... + ) + + for i in to_rm + ts.fullvars[i] in vs || continue + a, b, islin = Symbolics.linear_expansion(eqs[eq_diff].rhs, ts.fullvars[i]) + @assert islin && SU._iszero(a) + eqs[eq_diff] = Symbolics.COMMON_ZERO ~ b + end return eq_diff end @@ -201,9 +216,13 @@ function _check_allow_symbolic_parameter( end function StateSelection.find_eq_solvables!(state::TearingState, ieq, to_rm = Int[], coeffs = nothing; - may_be_zero = false, + # this used to be `false`, but I can't find a place where this is called + # that doesn't want to remove false incidences, and it fixes several bugs. + # So now this defaults to `true`. + may_be_zero = true, allow_symbolic = false, allow_parameter = true, conservative = false, + symbolically_rm_singular = true, kwargs...) (; fullvars) = state (; graph, solvable_graph) = state.structure @@ -224,8 +243,7 @@ function StateSelection.find_eq_solvables!(state::TearingState, ieq, to_rm = Int for j in đť‘ neighbors(graph, ieq) var = fullvars[j] MTKBase.isirreducible(var) && (all_int_vars = false; continue) - a, b, islinear = Symbolics.linear_expansion(term, var) - + a, b, islinear = Symbolics.LinearExpander(var; strict = true)(term) islinear || (all_int_vars = false; continue) if !SU.isconst(a) all_int_vars = false @@ -261,6 +279,11 @@ function StateSelection.find_eq_solvables!(state::TearingState, ieq, to_rm = Int end for j in to_rm rem_edge!(graph, ieq, j) + symbolically_rm_singular || continue + eq = equations(state)[ieq] + a, b, islin = Symbolics.LinearExpander(fullvars[j]; strict = true)(eq.rhs) + SU._iszero(a) && islin || continue + equations(state)[ieq] = eq.lhs ~ b end all_int_vars, term end From 40f6aa3444dbd92ae57a3bad1da10b2bc933d38a Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Mon, 16 Feb 2026 23:41:02 +0530 Subject: [PATCH 4/4] test: add test for automatically removing false incidences --- lib/ModelingToolkitTearing/test/runtests.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/ModelingToolkitTearing/test/runtests.jl b/lib/ModelingToolkitTearing/test/runtests.jl index 4003f51..ced3c6f 100644 --- a/lib/ModelingToolkitTearing/test/runtests.jl +++ b/lib/ModelingToolkitTearing/test/runtests.jl @@ -2,7 +2,11 @@ using Test using ModelingToolkitTearing using ModelingToolkit using Symbolics +using BipartiteGraphs +using Graphs +import StateSelection using ModelingToolkit: t_nounits as t, D_nounits as D +import SymbolicUtils as SU @testset "`InferredDiscrete` validation" begin k = ShiftIndex() @@ -23,3 +27,18 @@ end @test issetequal(unknowns(sys), [x, y]) @test isempty(observables(sys)) end + +# https://github.com/SciML/ModelingToolkit.jl/issues/4282 +@testset "`find_eq_solvables!` symbolically removes singular incidences" begin + @variables x(t) y(t) + @named sys = System([D(x) ~ 2x, y + 2(x + t) - t * (2x / t - 1) ~ 0], t) + ts = TearingState(sys; sort_eqs = false) + @test SU.query(isequal(x), equations(ts)[2].rhs) + a, b, islin = Symbolics.linear_expansion(equations(ts)[2], x) + @test SU._iszero(a) && islin + x_idx = findfirst(isequal(x), ts.fullvars)::Int + @test Graphs.has_edge(ts.structure.graph, BipartiteEdge(2, x_idx)) + StateSelection.find_solvables!(ts) + @test !Graphs.has_edge(ts.structure.graph, BipartiteEdge(2, x_idx)) + @test !SU.query(isequal(x), equations(ts)[2].rhs) +end