@@ -208,12 +208,19 @@ mutable struct TearingState{T <: AbstractSystem} <: AbstractTearingState{T}
208208 structure:: SystemStructure
209209 extra_eqs:: Vector
210210 param_derivative_map:: Dict{BasicSymbolic, Any}
211+ original_eqs:: Vector{Equation}
212+ """
213+ Additional user-provided observed equations. The variables calculated here
214+ are not used in the rest of the system.
215+ """
216+ additional_observed:: Vector{Equation}
211217end
212218
213219TransformationState (sys:: AbstractSystem ) = TearingState (sys)
214220function system_subset (ts:: TearingState , ieqs:: Vector{Int} )
215221 eqs = equations (ts)
216222 @set! ts. sys. eqs = eqs[ieqs]
223+ @set! ts. original_eqs = ts. original_eqs[ieqs]
217224 @set! ts. structure = system_subset (ts. structure, ieqs)
218225 ts
219226end
@@ -266,6 +273,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
266273 iv = length (ivs) == 1 ? ivs[1 ] : nothing
267274 # scalarize array equations, without scalarizing arguments to registered functions
268275 eqs = flatten_equations (copy (equations (sys)))
276+ original_eqs = copy (eqs)
269277 neqs = length (eqs)
270278 dervaridxs = OrderedSet {Int} ()
271279 var2idx = Dict {Any, Int} ()
@@ -378,6 +386,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
378386 end
379387 end
380388 eqs = eqs[eqs_to_retain]
389+ original_eqs = original_eqs[eqs_to_retain]
381390 neqs = length (eqs)
382391 symbolic_incidence = symbolic_incidence[eqs_to_retain]
383392
@@ -386,6 +395,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
386395 # depending on order due to NP-completeness of tearing.
387396 sortidxs = Base. sortperm (eqs, by = string)
388397 eqs = eqs[sortidxs]
398+ original_eqs = original_eqs[sortidxs]
389399 symbolic_incidence = symbolic_incidence[sortidxs]
390400 end
391401
@@ -475,13 +485,116 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
475485 ts = TearingState (sys, fullvars,
476486 SystemStructure (complete (var_to_diff), complete (eq_to_diff),
477487 complete (graph), nothing , var_types, sys isa AbstractDiscreteSystem),
478- Any[], param_derivative_map)
488+ Any[], param_derivative_map, original_eqs, Equation[] )
479489 if sys isa DiscreteSystem
480490 ts = shift_discrete_system (ts)
481491 end
482492 return ts
483493end
484494
495+ """
496+ $(TYPEDSIGNATURES)
497+
498+ Preemptively identify observed equations in the system and tear them. This happens before
499+ any simplification. The equations torn by this process are ones that are already given in
500+ an explicit form in the system and where the LHS is not present in any other equation of
501+ the system except for other such preempitvely torn equations.
502+ """
503+ function trivial_tearing! (ts:: TearingState )
504+ @assert length (ts. original_eqs) == length (equations (ts))
505+ # equations that can be trivially torn an observed equations
506+ trivial_idxs = BitSet ()
507+ # equations to never check
508+ blacklist = BitSet ()
509+ torn_eqs = Equation[]
510+ # variables that have been matched to trivially torn equations
511+ matched_vars = BitSet ()
512+ # variable to index in fullvars
513+ var_to_idx = Dict {Any, Int} (ts. fullvars .=> eachindex (ts. fullvars))
514+
515+ complete! (ts. structure)
516+ var_to_diff = ts. structure. var_to_diff
517+ graph = ts. structure. graph
518+ while true
519+ # track whether we added an equation to the trivial list this iteration
520+ added_equation = false
521+ for (i, eq) in enumerate (ts. original_eqs)
522+ # don't check already torn equations
523+ i in trivial_idxs && continue
524+ i in blacklist && continue
525+ # ensure it is an observed equation matched to a variable in fullvars
526+ vari = get (var_to_idx, eq. lhs, 0 )
527+ iszero (vari) && continue
528+ # don't tear irreducible variables
529+ if isirreducible (eq. lhs)
530+ push! (blacklist, i)
531+ continue
532+ end
533+ # if a variable was the LHS of two trivial observed equations, we wouldn't have
534+ # included it in the list. Error if somehow it made it through.
535+ @assert ! (vari in matched_vars)
536+ # don't tear differential/shift equations (or differentiated/shifted variables)
537+ var_to_diff[vari] === nothing || continue
538+ invview (var_to_diff)[vari] === nothing || continue
539+ # get the equations that the candidate matched variable is present in, except
540+ # those equations which have already been torn as observed
541+ eqidxs = setdiff (𝑑neighbors (graph, vari), trivial_idxs)
542+ # it should only be present in this equation
543+ length (eqidxs) == 1 || continue
544+ eqi = only (eqidxs)
545+ @assert eqi == i
546+
547+ # for every variable present in this equation, make sure it isn't _only_
548+ # present in trivial equations
549+ isvalid = true
550+ for v in 𝑠neighbors (graph, eqi)
551+ v == vari && continue
552+ v in matched_vars && continue
553+ # `> 1` and not `0` because one entry will be this equation (`eqi`)
554+ isvalid &= count (! in (trivial_idxs), 𝑑neighbors (graph, v)) > 1
555+ isvalid || break
556+ end
557+ isvalid || continue
558+ # skip if the LHS is present in the RHS, since then this isn't explicit
559+ if occursin (eq. lhs, eq. rhs)
560+ push! (blacklist, i)
561+ continue
562+ end
563+
564+ added_equation = true
565+ push! (trivial_idxs, eqi)
566+ push! (torn_eqs, eq)
567+ push! (matched_vars, vari)
568+ end
569+
570+ # if we didn't add an equation this iteration, we won't add one next iteration
571+ added_equation || break
572+ end
573+
574+ deleteat! (var_to_diff. primal_to_diff, matched_vars)
575+ deleteat! (var_to_diff. diff_to_primal, matched_vars)
576+ deleteat! (ts. structure. eq_to_diff. primal_to_diff, trivial_idxs)
577+ deleteat! (ts. structure. eq_to_diff. diff_to_primal, trivial_idxs)
578+ delete_srcs! (ts. structure. graph, trivial_idxs; rm_verts = true )
579+ delete_dsts! (ts. structure. graph, matched_vars; rm_verts = true )
580+ if ts. structure. solvable_graph != = nothing
581+ delete_srcs! (ts. structure. solvable_graph, trivial_idxs; rm_verts = true )
582+ delete_dsts! (ts. structure. solvable_graph, matched_vars; rm_verts = true )
583+ end
584+ if ts. structure. var_types != = nothing
585+ deleteat! (ts. structure. var_types, matched_vars)
586+ end
587+ deleteat! (ts. fullvars, matched_vars)
588+ deleteat! (ts. original_eqs, trivial_idxs)
589+ ts. additional_observed = torn_eqs
590+ sys = ts. sys
591+ eqs = copy (get_eqs (sys))
592+ deleteat! (eqs, trivial_idxs)
593+ @set! sys. eqs = eqs
594+ ts. sys = sys
595+ return ts
596+ end
597+
485598function lower_order_var (dervar, t)
486599 if isdifferential (dervar)
487600 diffvar = arguments (dervar)[1 ]
@@ -739,6 +852,7 @@ function _structural_simplify!(state::TearingState, io; simplify = false,
739852 else
740853 input_idxs = 0 : - 1 # Empty range
741854 end
855+ trivial_tearing! (state)
742856 sys, mm = ModelingToolkit. alias_elimination! (state; kwargs... )
743857 if check_consistency
744858 fully_determined = ModelingToolkit. check_consistency (
0 commit comments