Skip to content

Commit f0ce1ce

Browse files
Merge pull request #143 from JuliaComputing/as/inline-scc-irreds
fix: respect irreducibles in inline linear SCC handling
2 parents 3474c8b + 8dc2522 commit f0ce1ce

2 files changed

Lines changed: 50 additions & 10 deletions

File tree

lib/ModelingToolkitTearing/src/reassemble.jl

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ function generate_system_equations!(state::TearingState, neweqs::Vector{Equation
485485
# algebraic variables.
486486
linsol_result = nothing
487487
if !is_disc && inline_linear_sccs
488-
linsol_result = get_linear_scc_linsol(state, escc, vscc, neweqs, var_eq_matching, total_sub, analytical_linear_scc_limit, simplify)
488+
linsol_result = get_linear_scc_linsol(state, escc, vscc, neweqs, var_eq_matching, full_var_eq_matching, total_sub, analytical_linear_scc_limit, simplify)
489489
end
490490
if linsol_result isa Tuple{SymbolicT, BitVector, BitVector}
491491
linsol, eqs_mask, vars_mask = linsol_result
@@ -536,9 +536,7 @@ function generate_system_equations!(state::TearingState, neweqs::Vector{Equation
536536
# can populate `total_sub` appropriately.
537537
for (i, ieq) in enumerate(escc)
538538
eqs_mask[i] && continue
539-
# Currently, if an equation is filtered out it is matched to a variable.
540-
# This needs to be changed if we update our heuristic.
541-
var = eq_var_matching[ieq]::Int
539+
var = eq_var_matching[ieq]
542540
codegen_equation!(eq_generator, neweqs[ieq], ieq, var; simplify)
543541
end
544542
else
@@ -607,6 +605,7 @@ All equations not used in the linear system must be solved as normal.
607605
function get_linear_scc_linsol(state::TearingState, alg_eqs::Vector{Int},
608606
alg_vars::Vector{Int}, neweqs::Vector{Equation},
609607
var_eq_matching::StateSelection.VarEqMatchingT,
608+
full_var_eq_matching::StateSelection.VarEqMatchingT,
610609
total_sub::MTKBase.ExpandDerivativeDict{SymbolicT, Dict{SymbolicT, SymbolicT}},
611610
analytical_linear_scc_limit::Int,
612611
simplify::Bool; allow_symbolic::Bool = false,
@@ -624,9 +623,27 @@ function get_linear_scc_linsol(state::TearingState, alg_eqs::Vector{Int},
624623
all_torn && return nothing
625624

626625
N = length(alg_eqs)
626+
eqs_mask = trues(N)
627+
vars_mask = trues(N)
628+
629+
eq_to_idx = Dict(Iterators.map(reverse, enumerate(alg_eqs)))
630+
631+
irreds = irreducibles(sys)
632+
eq_var_matching = invview(var_eq_matching)
633+
for (i, iv) in enumerate(alg_vars)
634+
MTKBase.contains_possibly_indexed_element(irreds, fullvars[iv]) || continue
635+
# Irreducible variables are excluded
636+
vars_mask[i] = false
637+
# Along with the equations used for them
638+
ieq = full_var_eq_matching[iv]
639+
eqs_mask[eq_to_idx[ieq]] = false
640+
@assert !(eq_var_matching[ieq] isa Int)
641+
end
642+
627643
vars = fullvars[alg_vars]
628644

629645
for i in eachindex(vars)
646+
vars_mask[i] || continue
630647
ivar = alg_vars[i]
631648
ieq = var_eq_matching[ivar]
632649
ieq isa Int || continue
@@ -643,6 +660,7 @@ function get_linear_scc_linsol(state::TearingState, alg_eqs::Vector{Int},
643660
b = fill(Symbolics.COMMON_ZERO, N)
644661

645662
for (eqidx, ieq) in enumerate(alg_eqs)
663+
eqs_mask[eqidx] || continue
646664
eq = neweqs[ieq]
647665
resid = eq.rhs
648666
# If `ieq` is a differential equation
@@ -656,8 +674,10 @@ function get_linear_scc_linsol(state::TearingState, alg_eqs::Vector{Int},
656674
end
657675

658676
for (varidx, var) in enumerate(vars)
677+
vars_mask[varidx] || continue
659678
lex = MTKBase.get_linear_expander_for!(sys, var, true)
660679
for (eqidx, resid) in enumerate(b)
680+
eqs_mask[eqidx] || continue
661681
Graphs.has_edge(graph, BipartiteEdge(alg_eqs[eqidx], alg_vars[varidx])) || continue
662682
p, q, islinear = lex(resid)
663683
islinear || return nothing
@@ -672,10 +692,11 @@ function get_linear_scc_linsol(state::TearingState, alg_eqs::Vector{Int},
672692

673693
# `-` is important! `b` is on the other side of the equality.
674694
for i in eachindex(b)
695+
eqs_mask[i] || continue
675696
b[i] = -b[i]
676697
end
677698

678-
A, b, eqs_mask, vars_mask = __reduce_linear_system!(A, b, var_eq_matching, alg_eqs, alg_vars)
699+
A, b = __reduce_linear_system!(A, b, var_eq_matching, alg_eqs, alg_vars, eqs_mask, vars_mask)
679700

680701
N = length(b)
681702
A = collect(A)::Matrix{Num}
@@ -770,22 +791,21 @@ function get_linear_scc_linsol(state::TearingState, alg_eqs::Vector{Int},
770791
return (INLINE_LINEAR_SCC_OP(A, b), eqs_mask, vars_mask)
771792
end
772793

773-
function __reduce_linear_system!(A::StateSelection.CLIL.SparseMatrixCLIL{Num, Int}, b::Vector{SymbolicT}, var_eq_matching::StateSelection.VarEqMatchingT, alg_eqs::Vector{Int}, alg_vars::Vector{Int})
794+
function __reduce_linear_system!(A::StateSelection.CLIL.SparseMatrixCLIL{Num, Int}, b::Vector{SymbolicT}, var_eq_matching::StateSelection.VarEqMatchingT, alg_eqs::Vector{Int}, alg_vars::Vector{Int}, eqs_mask::BitVector, vars_mask::BitVector)
774795
N = length(b)
775796
# Identify rows (equations) not worth involving in the linear solve.
776797
#
777798
# The current heuristic is to find all rows with constant coefficients
778799
# which are matched to a variable in `var_eq_matching`.
779-
eqs_mask = trues(N)
780-
vars_mask = trues(N)
781-
new_N = N
800+
new_N = count(eqs_mask)
782801
eq_var_matching = invview(var_eq_matching)
783802
var_to_idx = Dict{Int, Int}(Iterators.map(reverse, enumerate(alg_vars)))
784803
# The `i`th variable in the SCC is eliminated as
785804
# `∑_k aliases[i][k] * fullvars[alg_vars[k]] + constants[i]`
786805
constants = Dict{Int, SymbolicT}()
787806
aliases = Dict{Int, SparseArrays.SparseVector{Num, Int}}()
788807
for (i, coeffs) in enumerate(A.row_vals)
808+
eqs_mask[i] || continue
789809
eq_var_matching[alg_eqs[i]] isa Int || continue
790810

791811
eqs_mask[i] = false
@@ -880,7 +900,7 @@ function __reduce_linear_system!(A::StateSelection.CLIL.SparseMatrixCLIL{Num, In
880900
old_to_new_eq[.!eqs_mask] .= 0
881901
A = StateSelection.get_new_mm(aliases, old_to_new_eq, old_to_new_var, A)
882902

883-
return A, b, eqs_mask, vars_mask
903+
return A, b
884904
end
885905

886906
"""

lib/ModelingToolkitTearing/test/runtests.jl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,3 +513,23 @@ end
513513
@named cl = System(eqs, t)
514514
@test_nowarn TearingState(cl)
515515
end
516+
517+
@testset "`inline_linear_sccs` does not eliminate irreducible variables" begin
518+
@variables x(t) y(t)
519+
@variables e(t) [irreducible = true]
520+
@variables c(t) [irreducible = true] d(t) [irreducible = true]
521+
# `d` is marked as algebraic in a singleton SCC with equation `c ~ d`. Inline linear
522+
# SCCs would otherwise solve this equation for `d`.
523+
@mtkcompile sys = System(
524+
[
525+
D(x) ~ x,
526+
D(c) ~ -c,
527+
e ~ x,
528+
c ~ d,
529+
y ~ c,
530+
], t
531+
) reassemble_alg = MTKTearing.DefaultReassembleAlgorithm(; inline_linear_sccs = true)
532+
533+
@test Set(unknowns(sys)) == Set([e, c, d])
534+
end
535+

0 commit comments

Comments
 (0)