@@ -421,6 +421,23 @@ function generate_derivative_variables!(
421
421
for (i, idxs) in idxs_to_remove
422
422
deleteat! (var_sccs[i], idxs)
423
423
end
424
+ new_sccs = insert_sccs (var_sccs, sccs_to_insert)
425
+
426
+ if mm != = nothing
427
+ @set! mm. ncols = ndsts (graph)
428
+ end
429
+
430
+ return new_sccs
431
+ end
432
+
433
+ """
434
+ $(TYPEDSIGNATURES)
435
+
436
+ Given a list of SCCs and a list of SCCs to insert at specific indices, insert them and
437
+ return the new SCC vector.
438
+ """
439
+ function insert_sccs (
440
+ var_sccs:: Vector{Vector{Int}} , sccs_to_insert:: Vector{Tuple{Int, Vector{Int}}} )
424
441
# insert the new SCCs, accounting for the fact that we might have multiple entries
425
442
# in `sccs_to_insert` to be inserted at the same index.
426
443
old_idx = 1
@@ -441,10 +458,6 @@ function generate_derivative_variables!(
441
458
end
442
459
443
460
filter! (! isempty, new_sccs)
444
- if mm != = nothing
445
- @set! mm. ncols = ndsts (graph)
446
- end
447
-
448
461
return new_sccs
449
462
end
450
463
@@ -742,7 +755,17 @@ function codegen_equation!(eg::EquationGenerator,
742
755
@unpack fullvars, sys, structure = state
743
756
@unpack solvable_graph, var_to_diff, eq_to_diff, graph = structure
744
757
diff_to_var = invview (var_to_diff)
745
- if is_solvable (eg, ieq, iv) && is_dervar (eg, iv)
758
+
759
+ issolvable = is_solvable (eg, ieq, iv)
760
+ isdervar = issolvable && is_dervar (eg, iv)
761
+ isdisc = is_only_discrete (structure)
762
+ # The variable is derivative variable and the "most differentiated"
763
+ # This is only used for discrete systems, and basically refers to
764
+ # `Shift(t, 1)(x(k))` in `Shift(t, 1)(x(k)) ~ x(k) + x(k-1)`. As illustrated in
765
+ # the docstring for `add_additional_history!`, this is an exception and needs to be
766
+ # treated like a solved equation rather than a differential equation.
767
+ is_highest_diff = iv isa Int && isdervar && var_to_diff[iv] === nothing
768
+ if issolvable && isdervar && (! isdisc || ! is_highest_diff)
746
769
var = fullvars[iv]
747
770
isnothing (D) && throw (UnexpectedDifferentialError (equations (sys)[ieq]))
748
771
order, lv = var_order (iv, diff_to_var)
@@ -762,15 +785,25 @@ function codegen_equation!(eg::EquationGenerator,
762
785
push! (neweqs′, neweq)
763
786
push! (eq_ordering, ieq)
764
787
push! (var_ordering, diff_to_var[iv])
765
- elseif is_solvable (eg, ieq, iv)
788
+ elseif issolvable
766
789
var = fullvars[iv]
767
790
neweq = make_solved_equation (var, eq, total_sub; simplify)
768
791
if neweq != = nothing
792
+ # backshift solved equations to calculate the value of the variable at the
793
+ # current time. This works because we added one additional history element
794
+ # in `add_additional_history!`.
795
+ if isdisc
796
+ neweq = backshift_expr (neweq, idep)
797
+ end
769
798
push! (solved_eqs, neweq)
770
799
push! (solved_vars, iv)
771
800
end
772
801
else
773
802
neweq = make_algebraic_equation (eq, total_sub)
803
+ # For the same reason as solved equations (they are effectively the same)
804
+ if isdisc
805
+ neweq = backshift_expr (neweq, idep)
806
+ end
774
807
push! (neweqs′, neweq)
775
808
push! (eq_ordering, ieq)
776
809
# we push a dummy to `var_ordering` here because `iv` is `unassigned`
@@ -896,9 +929,24 @@ Update the system equations, unknowns, and observables after simplification.
896
929
"""
897
930
function update_simplified_system! (
898
931
state:: TearingState , neweqs, solved_eqs, dummy_sub, var_sccs, extra_unknowns;
899
- cse_hack = true , array_hack = true )
900
- @unpack solvable_graph, var_to_diff, eq_to_diff, graph = state. structure
932
+ cse_hack = true , array_hack = true , D = nothing , iv = nothing )
933
+ @unpack fullvars, structure = state
934
+ @unpack solvable_graph, var_to_diff, eq_to_diff, graph = structure
901
935
diff_to_var = invview (var_to_diff)
936
+ # Since we solved the highest order derivative varible in discrete systems,
937
+ # we make a list of the solved variables and avoid including them in the
938
+ # unknowns.
939
+ solved_vars = Set ()
940
+ if is_only_discrete (structure)
941
+ for eq in solved_eqs
942
+ var = eq. lhs
943
+ if isequal (eq. lhs, eq. rhs)
944
+ var = lower_shift_varname_with_unit (D (eq. lhs), iv)
945
+ end
946
+ push! (solved_vars, var)
947
+ end
948
+ filter! (eq -> ! isequal (eq. lhs, eq. rhs), solved_eqs)
949
+ end
902
950
903
951
ispresent = let var_to_diff = var_to_diff, graph = graph
904
952
i -> (! isempty (𝑑neighbors (graph, i)) ||
@@ -915,9 +963,19 @@ function update_simplified_system!(
915
963
obs = [fast_substitute (observed (sys), obs_sub); solved_eqs]
916
964
917
965
unknown_idxs = filter (
918
- i -> diff_to_var[i] === nothing && ispresent (i), eachindex (state. fullvars))
966
+ i -> diff_to_var[i] === nothing && ispresent (i) && ! (fullvars[i] in solved_vars) , eachindex (state. fullvars))
919
967
unknowns = state. fullvars[unknown_idxs]
920
968
unknowns = [unknowns; extra_unknowns]
969
+ if is_only_discrete (structure)
970
+ # Algebraic variables are shifted forward by one, so we backshift them.
971
+ unknowns = map (enumerate (unknowns)) do (i, var)
972
+ if iscall (var) && operation (var) isa Shift && operation (var). steps == 1
973
+ backshift_expr (var, iv)
974
+ else
975
+ var
976
+ end
977
+ end
978
+ end
921
979
@set! sys. unknowns = unknowns
922
980
923
981
obs = cse_and_array_hacks (
@@ -979,7 +1037,8 @@ differential variables.
979
1037
function tearing_reassemble (state:: TearingState , var_eq_matching:: Matching ,
980
1038
full_var_eq_matching:: Matching , var_sccs:: Vector{Vector{Int}} ; simplify = false , mm, cse_hack = true ,
981
1039
array_hack = true , fully_determined = true )
982
- extra_eqs_vars = get_extra_eqs_vars (state, full_var_eq_matching, fully_determined)
1040
+ extra_eqs_vars = get_extra_eqs_vars (
1041
+ state, var_eq_matching, full_var_eq_matching, fully_determined)
983
1042
neweqs = collect (equations (state))
984
1043
dummy_sub = Dict ()
985
1044
@@ -995,6 +1054,11 @@ function tearing_reassemble(state::TearingState, var_eq_matching::Matching,
995
1054
end
996
1055
997
1056
extra_unknowns = state. fullvars[extra_eqs_vars[2 ]]
1057
+ if is_only_discrete (state. structure)
1058
+ var_sccs = add_additional_history! (
1059
+ state, neweqs, var_eq_matching, full_var_eq_matching, var_sccs; iv, D)
1060
+ end
1061
+
998
1062
# Structural simplification
999
1063
substitute_derivatives_algevars! (state, neweqs, var_eq_matching, dummy_sub; iv, D)
1000
1064
@@ -1010,13 +1074,121 @@ function tearing_reassemble(state::TearingState, var_eq_matching::Matching,
1010
1074
# var_eq_matching and full_var_eq_matching are now invalidated
1011
1075
1012
1076
sys = update_simplified_system! (state, neweqs, solved_eqs, dummy_sub, var_sccs,
1013
- extra_unknowns; cse_hack, array_hack)
1077
+ extra_unknowns; cse_hack, array_hack, iv, D )
1014
1078
1015
1079
@set! state. sys = sys
1016
1080
@set! sys. tearing_state = state
1017
1081
return invalidate_cache! (sys)
1018
1082
end
1019
1083
1084
+ """
1085
+ $(TYPEDSIGNATURES)
1086
+
1087
+ Add one more history equation for discrete systems. For example, if we have
1088
+
1089
+ ```julia
1090
+ Shift(t, 1)(x(k-1)) ~ x(k)
1091
+ Shift(t, 1)(x(k)) ~ x(k) + x(k-1)
1092
+ ```
1093
+
1094
+ This turns it into
1095
+
1096
+ ```julia
1097
+ Shift(t, 1)(x(k-2)) ~ x(k-1)
1098
+ Shift(t, 1)(x(k-1)) ~ x(k)
1099
+ Shift(t, 1)(x(k)) ~ x(k) + x(k-1)
1100
+ ```
1101
+
1102
+ Thus adding an additional unknown as well. Later, the highest derivative equation will
1103
+ be backshifted by one and turned into an observed equation, resulting in:
1104
+
1105
+ ```julia
1106
+ Shift(t, 1)(x(k-2)) ~ x(k-1)
1107
+ Shift(t, 1)(x(k-1)) ~ x(k)
1108
+
1109
+ x(k) ~ x(k-1) + x(k-2)
1110
+ ```
1111
+
1112
+ Where the last equation is the observed equation.
1113
+ """
1114
+ function add_additional_history! (
1115
+ state:: TearingState , neweqs:: Vector , var_eq_matching:: Matching ,
1116
+ full_var_eq_matching:: Matching , var_sccs:: Vector{Vector{Int}} ; iv, D)
1117
+ @unpack fullvars, sys, structure = state
1118
+ @unpack solvable_graph, var_to_diff, eq_to_diff, graph = structure
1119
+ eq_var_matching = invview (var_eq_matching)
1120
+ diff_to_var = invview (var_to_diff)
1121
+ is_discrete = is_only_discrete (structure)
1122
+ digraph = DiCMOBiGraph {false} (graph, var_eq_matching)
1123
+
1124
+ # We need the inverse mapping of `var_sccs` to update it efficiently later.
1125
+ v_to_scc = Vector {NTuple{2, Int}} (undef, ndsts (graph))
1126
+ for (i, scc) in enumerate (var_sccs), (j, v) in enumerate (scc)
1127
+ v_to_scc[v] = (i, j)
1128
+ end
1129
+
1130
+ vars_to_backshift = BitSet ()
1131
+ eqs_to_backshift = BitSet ()
1132
+ # add history for differential variables
1133
+ for ivar in 1 : length (fullvars)
1134
+ ieq = var_eq_matching[ivar]
1135
+ # the variable to backshift is a state variable which is not the
1136
+ # derivative of any other one.
1137
+ ieq isa SelectedState || continue
1138
+ diff_to_var[ivar] === nothing || continue
1139
+ push! (vars_to_backshift, ivar)
1140
+ end
1141
+
1142
+ inserts = Tuple{Int, Vector{Int}}[]
1143
+
1144
+ for var in vars_to_backshift
1145
+ add_backshifted_var! (state, var, iv)
1146
+ # all backshifted vars are differential vars, hence SelectedState
1147
+ push! (var_eq_matching, SelectedState ())
1148
+ push! (full_var_eq_matching, unassigned)
1149
+ # add to the SCCs right before the variable that was backshifted
1150
+ push! (inserts, (v_to_scc[var][1 ], [length (fullvars)]))
1151
+ end
1152
+
1153
+ sort! (inserts, by = first)
1154
+ new_sccs = insert_sccs (var_sccs, inserts)
1155
+ return new_sccs
1156
+ end
1157
+
1158
+ """
1159
+ $(TYPEDSIGNATURES)
1160
+
1161
+ Add the backshifted version of variable `ivar` to the system.
1162
+ """
1163
+ function add_backshifted_var! (state:: TearingState , ivar:: Int , iv)
1164
+ @unpack fullvars, structure = state
1165
+ @unpack var_to_diff, graph, solvable_graph = structure
1166
+
1167
+ var = fullvars[ivar]
1168
+ newvar = simplify_shifts (Shift (iv, - 1 )(var))
1169
+ push! (fullvars, newvar)
1170
+ inewvar = add_vertex! (var_to_diff)
1171
+ add_edge! (var_to_diff, inewvar, ivar)
1172
+ add_vertex! (graph, DST)
1173
+ add_vertex! (solvable_graph, DST)
1174
+ return inewvar
1175
+ end
1176
+
1177
+ """
1178
+ $(TYPEDSIGNATURES)
1179
+
1180
+ Backshift the given expression `ex`.
1181
+ """
1182
+ function backshift_expr (ex, iv)
1183
+ ex isa Symbolic || return ex
1184
+ return descend_lower_shift_varname_with_unit (
1185
+ simplify_shifts (distribute_shift (Shift (iv, - 1 )(ex))), iv)
1186
+ end
1187
+
1188
+ function backshift_expr (ex:: Equation , iv)
1189
+ return backshift_expr (ex. lhs, iv) ~ backshift_expr (ex. rhs, iv)
1190
+ end
1191
+
1020
1192
"""
1021
1193
$(TYPEDSIGNATURES)
1022
1194
@@ -1025,7 +1197,7 @@ respectively. For fully-determined systems, both of these are empty. Overdetermi
1025
1197
have extra equations, and underdetermined systems have extra variables.
1026
1198
"""
1027
1199
function get_extra_eqs_vars (
1028
- state:: TearingState , full_var_eq_matching:: Matching , fully_determined:: Bool )
1200
+ state:: TearingState , var_eq_matching :: Matching , full_var_eq_matching:: Matching , fully_determined:: Bool )
1029
1201
fully_determined && return Int[], Int[]
1030
1202
1031
1203
extra_eqs = Int[]
@@ -1035,6 +1207,10 @@ function get_extra_eqs_vars(
1035
1207
for v in 𝑑vertices (state. structure. graph)
1036
1208
eq = full_var_eq_matching[v]
1037
1209
eq isa Int && continue
1210
+ # Only if the variable is also unmatched in `var_eq_matching`.
1211
+ # Otherwise, `SelectedState` differential variables from order lowering
1212
+ # are also considered "extra"
1213
+ var_eq_matching[v] === unassigned || continue
1038
1214
push! (extra_vars, v)
1039
1215
end
1040
1216
for eq in 𝑠vertices (state. structure. graph)
0 commit comments