@@ -333,6 +333,32 @@ where `:=` denotes assignment.
333333
334334As a final note, in all the above cases where we need to introduce new
335335variables 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"""
337363function 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
430477end
431478
432479"""
@@ -442,7 +489,7 @@ such that the mass matrix is:
442489
443490Update 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
598639end
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
0 commit comments