Skip to content

Commit 5a77f43

Browse files
committed
explicit case
1 parent bf3cd33 commit 5a77f43

File tree

3 files changed

+104
-91
lines changed

3 files changed

+104
-91
lines changed

src/structural_transformation/symbolics_tearing.jl

Lines changed: 97 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,32 @@ where `:=` denotes assignment.
333333
334334
As a final note, in all the above cases where we need to introduce new
335335
variables and equations, don't add them when they already exist.
336+
337+
###### DISCRETE SYSTEMS #######
338+
339+
Documenting the differences to structural simplification for discrete systems:
340+
In discrete systems the lowest-order term is x_k-i, instead of x(t).
341+
342+
The orders will also be off by one. The reason this is is that the dynamics of
343+
the system should be given in terms of Shift(t, 1)(x(t), x(t-1), ...). But
344+
having the observables be indexed by the next time step is not so nice. So we
345+
handle the shifts in the renaming, rather than explicitly.
346+
347+
The substitution should look like the following:
348+
x(t) -> Shift(t, 1)(x(t))
349+
Shift(t, -1)(x(t)) -> x(t)
350+
Shift(t, -2)(x(t)) -> x_{t-1}(t)
351+
Shift(t, -3)(x(t)) -> x_{t-2}(t)
352+
and so on...
353+
354+
In the implicit discrete case this shouldn't happen. The simplification should
355+
look like a NonlinearSystem.
356+
357+
For discrete systems Shift(t, 2)(x(t)) is not equivalent to Shift(t, 1)(Shift(t,1)(x(t))
358+
This is different from the continuous case where D(D(x)) can be substituted for
359+
by iteratively substituting x_t ~ D(x), then x_tt ~ D(x_t). For this reason the
360+
total_sub dict is updated at the time that the renamed variables are written,
361+
inside the loop where new variables are generated.
336362
"""
337363
function generate_derivative_variables!(ts::TearingState, neweqs, total_sub, var_eq_matching, var_order;
338364
is_discrete = false, mm = nothing)
@@ -342,26 +368,41 @@ function generate_derivative_variables!(ts::TearingState, neweqs, total_sub, var
342368
eq_var_matching = invview(var_eq_matching)
343369
diff_to_var = invview(var_to_diff)
344370
iv = ModelingToolkit.has_iv(sys) ? ModelingToolkit.get_iv(sys) : nothing
345-
lower_name = is_discrete ? lower_name_withshift : lower_name_with_unit
371+
lower_name = is_discrete ? lower_varname_withshift : lower_varname_with_unit
372+
373+
# index v gets mapped to the lowest shift and the index of the unshifted variable
374+
if is_discrete
375+
idx_to_lowest_shift = Dict{Int, Tuple{Int, Int}}(var => (0,0) for var in 1:length(fullvars))
376+
var_to_unshiftedidx = Dict{Any, Int}(var => findfirst(x -> isequal(x, var), fullvars) for var in keys(lowest_shift))
377+
378+
for (i,var) in enumerate(fullvars)
379+
key = (operation(var) isa Shift) ? only(arguments(var)) : var
380+
idx_to_lowest_shift[i] = (get(lowest_shift, key, 0), get(var_to_unshiftedidx, key, i))
381+
end
382+
end
346383

347384
linear_eqs = mm === nothing ? Dict{Int, Int}() :
348385
Dict(reverse(en) for en in enumerate(mm.nzrows))
349386

387+
# v is the index of the current variable, x = fullvars[v]
388+
# dv is the index of the derivative dx = D(x), x_t is the substituted variable
389+
#
390+
# For ODESystems: lv is the index of the lowest-order variable (x(t))
391+
# For DiscreteSystems:
392+
# - lv is the index of the lowest-order variable (Shift(t, k)(x(t)))
393+
# - uv is the index of the highest-order variable (x(t))
350394
for v in 1:length(var_to_diff)
351-
is_highest_discrete = begin
352-
var = fullvars[v]
353-
op = operation(var)
354-
if (!is_discrete || op isa Shift)
355-
false
356-
elseif !haskey(lowest_shift, var)
357-
false
358-
else
359-
low = lowest_shift[var]
360-
idx = findfirst(x -> isequal(x, Shift(iv, low)(var)), fullvars)
361-
true
395+
dv = var_to_diff[v]
396+
if is_discrete
397+
x = fullvars[v]
398+
op = operation(x)
399+
(low, uv) = idx_to_lowest_shift[v]
400+
401+
# If v is unshifted (i.e. x(t)), then substitute the lowest-shift variable
402+
if !(op isa Shift) && (low != 0)
403+
dv = findfirst(_x -> isequal(_x, Shift(iv, low)(x)), fullvars)
362404
end
363405
end
364-
dv = is_highest_discrete ? idx : var_to_diff[v]
365406
dv isa Int || continue
366407

367408
solved = var_eq_matching[dv] isa Int
@@ -388,25 +429,37 @@ function generate_derivative_variables!(ts::TearingState, neweqs, total_sub, var
388429

389430
dx = fullvars[dv]
390431
# add `x_t`
391-
println()
392-
@show order, lv = var_order(dv)
393-
x_t = lower_name(fullvars[lv], iv, order)
394-
@show fullvars[v]
395-
@show fullvars[dv]
396-
@show fullvars[lv]
397-
@show dx, x_t
432+
order, lv = var_order(dv)
433+
x_t = is_discrete ? lower_name(fullvars[lv], iv, -low-order-1; unshifted = fullvars[uv]) :
434+
lower_name(fullvars[lv], iv, order)
398435
push!(fullvars, simplify_shifts(x_t))
399436
v_t = length(fullvars)
400437
v_t_idx = add_vertex!(var_to_diff)
401438
add_vertex!(graph, DST)
402439
# TODO: do we care about solvable_graph? We don't use them after
403440
# `dummy_derivative_graph`.
404441
add_vertex!(solvable_graph, DST)
405-
# var_eq_matching is a bit odd.
406-
# length(var_eq_matching) == length(invview(var_eq_matching))
407442
push!(var_eq_matching, unassigned)
408443
@assert v_t_idx == ndsts(graph) == ndsts(solvable_graph) == length(fullvars) ==
409444
length(var_eq_matching)
445+
446+
# Add the substitutions to total_sub directly.
447+
is_discrete && begin
448+
idx_to_lowest_shift[v_t] = idx_to_lowest_shift[dv]
449+
@show dx
450+
if operation(dx) isa Shift
451+
total_sub[dx] = x_t
452+
for e in 𝑑neighbors(graph, dv)
453+
add_edge!(graph, e, v_t)
454+
rem_edge!(graph, e, dv)
455+
end
456+
@show graph
457+
!(operation(x) isa Shift) && begin
458+
var_to_diff[v_t] = var_to_diff[dv]
459+
continue
460+
end
461+
end
462+
end
410463
# add `D(x) - x_t ~ 0`
411464
push!(neweqs, 0 ~ x_t - dx)
412465
add_vertex!(graph, SRC)
@@ -417,16 +470,10 @@ function generate_derivative_variables!(ts::TearingState, neweqs, total_sub, var
417470
add_edge!(solvable_graph, dummy_eq, dv)
418471
@assert nsrcs(graph) == nsrcs(solvable_graph) == dummy_eq
419472
@label FOUND_DUMMY_EQ
420-
is_discrete && begin
421-
idx_to_lowest_shift[v_t] = idx_to_lowest_shift[dv]
422-
operation(dx) isa Shift && (total_sub[dx] = x_t)
423-
order == 1 && (total_sub[x_t] = fullvars[var_to_diff[dv]])
424-
end
425473
var_to_diff[v_t] = var_to_diff[dv]
426474
var_eq_matching[dv] = unassigned
427475
eq_var_matching[dummy_eq] = dv
428476
end
429-
@show total_sub
430477
end
431478

432479
"""
@@ -442,7 +489,7 @@ such that the mass matrix is:
442489
443490
Update the state to account for the new ordering and equations.
444491
"""
445-
function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, var_eq_matching, var_order)
492+
function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, var_eq_matching, var_order; simplify = false)
446493
@unpack fullvars, sys, structure = state
447494
@unpack solvable_graph, var_to_diff, eq_to_diff, graph, lowest_shift = structure
448495
eq_var_matching = invview(var_eq_matching)
@@ -467,7 +514,7 @@ function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, v
467514
var -> diff_to_var[var] !== nothing
468515
end
469516

470-
### Extract partition information
517+
# Extract partition information
471518
is_solvable = let solvable_graph = solvable_graph
472519
(eq, iv) -> eq isa Int && iv isa Int && BipartiteEdge(eq, iv) in solvable_graph
473520
end
@@ -485,9 +532,12 @@ function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, v
485532
eqs = Iterators.reverse(toporder)
486533
idep = ModelingToolkit.has_iv(sys) ? ModelingToolkit.get_iv(sys) : nothing
487534

488-
# Generate differential equations in terms of the new variables.
535+
@show eq_var_matching
536+
@show fullvars
537+
@show neweqs
538+
539+
# Equation ieq is solved for the RHS of iv
489540
for ieq in eqs
490-
println()
491541
iv = eq_var_matching[ieq]
492542
if is_solvable(ieq, iv)
493543
# We don't solve differential equations, but we will need to try to
@@ -497,23 +547,14 @@ function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, v
497547
isnothing(D) &&
498548
error("Differential found in a non-differential system. Likely this is a bug in the construction of an initialization system. Please report this issue with a reproducible example. Offending equation: $(equations(sys)[ieq])")
499549
order, lv = var_order(iv)
500-
@show fullvars[iv]
501-
@show (order, lv)
502-
dx = D(simplify_shifts(lower_name(
503-
fullvars[lv], idep, order - 1)))
504-
@show dx
550+
dx = D(fullvars[lv])
505551
eq = dx ~ simplify_shifts(Symbolics.fixpoint_sub(
506552
Symbolics.symbolic_linear_solve(neweqs[ieq],
507553
fullvars[iv]),
508554
total_sub; operator = ModelingToolkit.Shift))
509-
@show total_sub
510-
@show eq
511555
for e in 𝑑neighbors(graph, iv)
512-
e == ieq && continue
513-
for v in 𝑠neighbors(graph, e)
514-
add_edge!(graph, e, v)
515-
end
516556
rem_edge!(graph, e, iv)
557+
add_edge!(graph, e, lv)
517558
end
518559
push!(diff_eqs, eq)
519560
total_sub[simplify_shifts(eq.lhs)] = eq.rhs
@@ -594,38 +635,10 @@ function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, v
594635
new_eq_to_diff[v′] = d′ > 0 ? d′ : nothing
595636
end
596637

597-
fullvars[invvarsperm], new_var_to_diff, new_eq_to_diff, neweqs, subeqs
638+
fullvars[invvarsperm], new_var_to_diff, new_eq_to_diff, neweqs, subeqs, graph
598639
end
599640

600-
# Documenting the differences to structural simplification for discrete systems:
601-
# In discrete systems the lowest-order term is x_k-i, instead of x(t). In order
602-
# to substitute dummy variables for x_k-1, x_k-2, ... instead you need to reverse
603-
# the order. So for discrete systems `var_order` is defined a little differently.
604-
#
605-
# The orders will also be off by one. The reason this is is that the dynamics of
606-
# the system should be given in terms of Shift(t, 1)(x(t), x(t-1), ...). But
607-
# having the observables be indexed by the next time step is not so nice. So we
608-
# handle the shifts in the renaming, rather than explicitly.
609-
#
610-
# The substitution should look like the following:
611-
# x(t) -> Shift(t, 1)(x(t))
612-
# x(k-1) -> x(t)
613-
# x(k-2) -> x_{t-1}(t)
614-
# x(k-3) -> x_{t-2}(t)
615-
# and so on...
616-
#
617-
# In the implicit discrete case this shouldn't happen. The simplification should
618-
# look like a NonlinearSystem.
619-
#
620-
# For discrete systems Shift(t, 2)(x(t)) is not equivalent to Shift(t, 1)(Shift(t,1)(x(t))
621-
# This is different from the continuous case where D(D(x)) can be substituted for
622-
# by iteratively substituting x_t ~ D(x), then x_tt ~ D(x_t). For this reason the
623-
# total_sub dict is updated at the time that the renamed variables are written,
624-
# inside the loop where new variables are generated.
625-
626-
627641
# Terminology and Definition:
628-
#
629642
# A general DAE is in the form of `F(u'(t), u(t), p, t) == 0`. We can
630643
# characterize variables in `u(t)` into two classes: differential variables
631644
# (denoted `v(t)`) and algebraic variables (denoted `z(t)`). Differential
@@ -654,21 +667,13 @@ function tearing_reassemble(state::TearingState, var_eq_matching,
654667
dummy_sub = Dict()
655668
is_discrete = is_only_discrete(state.structure)
656669

657-
if is_discrete
658-
idx_to_lowest_shift = Dict{Int, Int}(var => 0 for var in 1:length(fullvars))
659-
for (i,var) in enumerate(fullvars)
660-
key = (operation(var) isa Shift) ? only(arguments(var)) : var
661-
idx_to_lowest_shift[i] = get(lowest_shift, key, 0)
662-
end
663-
end
664670
var_order = let diff_to_var = diff_to_var
665671
dv -> begin
666672
order = 0
667673
while (dv′ = diff_to_var[dv]) !== nothing
668674
order += 1
669675
dv = dv′
670676
end
671-
is_discrete && (order = -idx_to_lowest_shift[dv] - order)
672677
order, dv
673678
end
674679
end
@@ -677,7 +682,7 @@ function tearing_reassemble(state::TearingState, var_eq_matching,
677682
substitute_dummy_derivatives!(state, neweqs, dummy_sub, var_eq_matching)
678683
generate_derivative_variables!(state, neweqs, total_sub, var_eq_matching, var_order;
679684
is_discrete, mm)
680-
new_fullvars, new_var_to_diff, new_eq_to_diff, neweqs, subeqs = solve_and_generate_equations!(state, neweqs, total_sub, var_eq_matching, var_order)
685+
new_fullvars, new_var_to_diff, new_eq_to_diff, neweqs, subeqs, graph = solve_and_generate_equations!(state, neweqs, total_sub, var_eq_matching, var_order; simplify)
681686

682687
# Update system
683688
var_to_diff = new_var_to_diff
@@ -693,16 +698,25 @@ function tearing_reassemble(state::TearingState, var_eq_matching,
693698
i -> (!isempty(𝑑neighbors(graph, i)) ||
694699
(var_to_diff[i] !== nothing && !isempty(𝑑neighbors(graph, var_to_diff[i]))))
695700
end
701+
@show graph
702+
println()
703+
println("Shift test...")
704+
@show neweqs
705+
@show fullvars
706+
@show 𝑑neighbors(graph, 5)
696707

697708
sys = state.sys
698-
699-
@show dummy_sub
700709
obs_sub = dummy_sub
701710
for eq in neweqs
702711
isdiffeq(eq) || continue
703712
obs_sub[eq.lhs] = eq.rhs
704713
end
714+
is_discrete && for eq in subeqs
715+
obs_sub[eq.rhs] = eq.lhs
716+
end
717+
705718
@show obs_sub
719+
@show observed(sys)
706720
# TODO: compute the dependency correctly so that we don't have to do this
707721
obs = [fast_substitute(observed(sys), obs_sub); subeqs]
708722

src/structural_transformation/utils.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,10 +449,9 @@ end
449449
### Misc
450450
###
451451

452-
function lower_varname_withshift(var, iv, order)
453-
order == 0 && return var
454-
#order == -1 && return Shift(iv, 1)(var)
455-
ds = "$iv-$(order-1)"
452+
function lower_varname_withshift(var, iv, backshift; unshifted = nothing)
453+
backshift == 0 && return unshifted
454+
ds = "$iv-$backshift"
456455
d_separator = 'ˍ'
457456

458457
if ModelingToolkit.isoperator(var, ModelingToolkit.Shift)

src/systems/systems.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ function structural_simplify(
4141
end
4242
if newsys isa DiscreteSystem &&
4343
any(eq -> symbolic_type(eq.lhs) == NotSymbolic(), equations(newsys))
44-
error("""
45-
Encountered algebraic equations when simplifying discrete system. This is \
46-
not yet supported.
47-
""")
44+
# error("""
45+
# Encountered algebraic equations when simplifying discrete system. This is \
46+
# not yet supported.
47+
# """)
4848
end
4949
for pass in additional_passes
5050
newsys = pass(newsys)

0 commit comments

Comments
 (0)