@@ -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
@@ -276,6 +283,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
276
283
iv = length (ivs) == 1 ? ivs[1 ] : nothing
277
284
# flatten array equations
278
285
eqs = flatten_equations (equations (sys))
286
+ original_eqs = copy (eqs)
279
287
neqs = length (eqs)
280
288
param_derivative_map = Dict {BasicSymbolic, Any} ()
281
289
# * Scalarize unknowns
@@ -320,6 +328,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
320
328
varsbuf = Set ()
321
329
eqs_to_retain = trues (length (eqs))
322
330
for (i, eq) in enumerate (eqs)
331
+ _eq = eq
323
332
if iscall (eq. lhs) && (op = operation (eq. lhs)) isa Differential &&
324
333
isequal (op. x, iv) && is_time_dependent_parameter (only (arguments (eq. lhs)), ps, iv)
325
334
# parameter derivatives are opted out by specifying `D(p) ~ missing`, but
@@ -415,6 +424,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
415
424
end
416
425
end
417
426
eqs = eqs[eqs_to_retain]
427
+ original_eqs = original_eqs[eqs_to_retain]
418
428
neqs = length (eqs)
419
429
symbolic_incidence = symbolic_incidence[eqs_to_retain]
420
430
@@ -423,6 +433,7 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
423
433
# depending on order due to NP-completeness of tearing.
424
434
sortidxs = Base. sortperm (eqs, by = string)
425
435
eqs = eqs[sortidxs]
436
+ original_eqs = original_eqs[sortidxs]
426
437
symbolic_incidence = symbolic_incidence[sortidxs]
427
438
end
428
439
@@ -516,11 +527,114 @@ function TearingState(sys; quick_cancel = false, check = true, sort_eqs = true)
516
527
ts = TearingState (sys, fullvars,
517
528
SystemStructure (complete (var_to_diff), complete (eq_to_diff),
518
529
complete (graph), nothing , var_types, false ),
519
- Any[], param_derivative_map)
530
+ Any[], param_derivative_map, original_eqs, Equation[] )
520
531
521
532
return ts
522
533
end
523
534
535
+ """
536
+ $(TYPEDSIGNATURES)
537
+
538
+ Preemptively identify observed equations in the system and tear them. This happens before
539
+ any simplification. The equations torn by this process are ones that are already given in
540
+ an explicit form in the system and where the LHS is not present in any other equation of
541
+ the system except for other such preempitvely torn equations.
542
+ """
543
+ function trivial_tearing! (ts:: TearingState )
544
+ @assert length (ts. original_eqs) == length (equations (ts))
545
+ # equations that can be trivially torn an observed equations
546
+ trivial_idxs = BitSet ()
547
+ # equations to never check
548
+ blacklist = BitSet ()
549
+ torn_eqs = Equation[]
550
+ # variables that have been matched to trivially torn equations
551
+ matched_vars = BitSet ()
552
+ # variable to index in fullvars
553
+ var_to_idx = Dict {Any, Int} (ts. fullvars .=> eachindex (ts. fullvars))
554
+
555
+ complete! (ts. structure)
556
+ var_to_diff = ts. structure. var_to_diff
557
+ graph = ts. structure. graph
558
+ while true
559
+ # track whether we added an equation to the trivial list this iteration
560
+ added_equation = false
561
+ for (i, eq) in enumerate (ts. original_eqs)
562
+ # don't check already torn equations
563
+ i in trivial_idxs && continue
564
+ i in blacklist && continue
565
+ # ensure it is an observed equation matched to a variable in fullvars
566
+ vari = get (var_to_idx, eq. lhs, 0 )
567
+ iszero (vari) && continue
568
+ # don't tear irreducible variables
569
+ if isirreducible (eq. lhs)
570
+ push! (blacklist, i)
571
+ continue
572
+ end
573
+ # if a variable was the LHS of two trivial observed equations, we wouldn't have
574
+ # included it in the list. Error if somehow it made it through.
575
+ @assert ! (vari in matched_vars)
576
+ # don't tear differential/shift equations (or differentiated/shifted variables)
577
+ var_to_diff[vari] === nothing || continue
578
+ invview (var_to_diff)[vari] === nothing || continue
579
+ # get the equations that the candidate matched variable is present in, except
580
+ # those equations which have already been torn as observed
581
+ eqidxs = setdiff (𝑑neighbors (graph, vari), trivial_idxs)
582
+ # it should only be present in this equation
583
+ length (eqidxs) == 1 || continue
584
+ eqi = only (eqidxs)
585
+ @assert eqi == i
586
+
587
+ # for every variable present in this equation, make sure it isn't _only_
588
+ # present in trivial equations
589
+ isvalid = true
590
+ for v in 𝑠neighbors (graph, eqi)
591
+ v == vari && continue
592
+ v in matched_vars && continue
593
+ # `> 1` and not `0` because one entry will be this equation (`eqi`)
594
+ isvalid &= count (! in (trivial_idxs), 𝑑neighbors (graph, v)) > 1
595
+ isvalid || break
596
+ end
597
+ isvalid || continue
598
+ # skip if the LHS is present in the RHS, since then this isn't explicit
599
+ if occursin (eq. lhs, eq. rhs)
600
+ push! (blacklist, i)
601
+ continue
602
+ end
603
+
604
+ added_equation = true
605
+ push! (trivial_idxs, eqi)
606
+ push! (torn_eqs, eq)
607
+ push! (matched_vars, vari)
608
+ end
609
+
610
+ # if we didn't add an equation this iteration, we won't add one next iteration
611
+ added_equation || break
612
+ end
613
+
614
+ deleteat! (var_to_diff. primal_to_diff, matched_vars)
615
+ deleteat! (var_to_diff. diff_to_primal, matched_vars)
616
+ deleteat! (ts. structure. eq_to_diff. primal_to_diff, trivial_idxs)
617
+ deleteat! (ts. structure. eq_to_diff. diff_to_primal, trivial_idxs)
618
+ delete_srcs! (ts. structure. graph, trivial_idxs; rm_verts = true )
619
+ delete_dsts! (ts. structure. graph, matched_vars; rm_verts = true )
620
+ if ts. structure. solvable_graph != = nothing
621
+ delete_srcs! (ts. structure. solvable_graph, trivial_idxs; rm_verts = true )
622
+ delete_dsts! (ts. structure. solvable_graph, matched_vars; rm_verts = true )
623
+ end
624
+ if ts. structure. var_types != = nothing
625
+ deleteat! (ts. structure. var_types, matched_vars)
626
+ end
627
+ deleteat! (ts. fullvars, matched_vars)
628
+ deleteat! (ts. original_eqs, trivial_idxs)
629
+ ts. additional_observed = torn_eqs
630
+ sys = ts. sys
631
+ eqs = copy (get_eqs (sys))
632
+ deleteat! (eqs, trivial_idxs)
633
+ @set! sys. eqs = eqs
634
+ ts. sys = sys
635
+ return ts
636
+ end
637
+
524
638
function lower_order_var (dervar, t)
525
639
if isdifferential (dervar)
526
640
diffvar = arguments (dervar)[1 ]
@@ -753,6 +867,7 @@ function _mtkcompile!(state::TearingState; simplify = false,
753
867
ModelingToolkit. markio! (state, orig_inputs, inputs, outputs, disturbance_inputs)
754
868
state = ModelingToolkit. inputs_to_parameters! (state, [inputs; disturbance_inputs])
755
869
end
870
+ trivial_tearing! (state)
756
871
sys, mm = ModelingToolkit. alias_elimination! (state; fully_determined, kwargs... )
757
872
if check_consistency
758
873
fully_determined = ModelingToolkit. check_consistency (
0 commit comments