@@ -208,12 +208,19 @@ mutable struct TearingState{T <: AbstractSystem} <: AbstractTearingState{T}
208
208
structure:: SystemStructure
209
209
extra_eqs:: Vector
210
210
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}
211
217
end
212
218
213
219
TransformationState (sys:: AbstractSystem ) = TearingState (sys)
214
220
function system_subset (ts:: TearingState , ieqs:: Vector{Int} )
215
221
eqs = equations (ts)
216
222
@set! ts. sys. eqs = eqs[ieqs]
223
+ @set! ts. original_eqs = ts. original_eqs[ieqs]
217
224
@set! ts. structure = system_subset (ts. structure, ieqs)
218
225
ts
219
226
end
@@ -266,6 +273,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
266
273
iv = length (ivs) == 1 ? ivs[1 ] : nothing
267
274
# scalarize array equations, without scalarizing arguments to registered functions
268
275
eqs = flatten_equations (copy (equations (sys)))
276
+ original_eqs = copy (eqs)
269
277
neqs = length (eqs)
270
278
dervaridxs = OrderedSet {Int} ()
271
279
var2idx = Dict {Any, Int} ()
@@ -378,6 +386,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
378
386
end
379
387
end
380
388
eqs = eqs[eqs_to_retain]
389
+ original_eqs = original_eqs[eqs_to_retain]
381
390
neqs = length (eqs)
382
391
symbolic_incidence = symbolic_incidence[eqs_to_retain]
383
392
@@ -386,6 +395,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
386
395
# depending on order due to NP-completeness of tearing.
387
396
sortidxs = Base. sortperm (eqs, by = string)
388
397
eqs = eqs[sortidxs]
398
+ original_eqs = original_eqs[sortidxs]
389
399
symbolic_incidence = symbolic_incidence[sortidxs]
390
400
end
391
401
@@ -475,13 +485,116 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
475
485
ts = TearingState (sys, fullvars,
476
486
SystemStructure (complete (var_to_diff), complete (eq_to_diff),
477
487
complete (graph), nothing , var_types, sys isa AbstractDiscreteSystem),
478
- Any[], param_derivative_map)
488
+ Any[], param_derivative_map, original_eqs, Equation[] )
479
489
if sys isa DiscreteSystem
480
490
ts = shift_discrete_system (ts)
481
491
end
482
492
return ts
483
493
end
484
494
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
+
485
598
function lower_order_var (dervar, t)
486
599
if isdifferential (dervar)
487
600
diffvar = arguments (dervar)[1 ]
@@ -739,6 +852,7 @@ function _structural_simplify!(state::TearingState, io; simplify = false,
739
852
else
740
853
input_idxs = 0 : - 1 # Empty range
741
854
end
855
+ trivial_tearing! (state)
742
856
sys, mm = ModelingToolkit. alias_elimination! (state; kwargs... )
743
857
if check_consistency
744
858
fully_determined = ModelingToolkit. check_consistency (
0 commit comments