@@ -333,6 +333,32 @@ where `:=` denotes assignment.
333
333
334
334
As a final note, in all the above cases where we need to introduce new
335
335
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.
336
362
"""
337
363
function generate_derivative_variables! (ts:: TearingState , neweqs, total_sub, var_eq_matching, var_order;
338
364
is_discrete = false , mm = nothing )
@@ -342,26 +368,41 @@ function generate_derivative_variables!(ts::TearingState, neweqs, total_sub, var
342
368
eq_var_matching = invview (var_eq_matching)
343
369
diff_to_var = invview (var_to_diff)
344
370
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
346
383
347
384
linear_eqs = mm === nothing ? Dict {Int, Int} () :
348
385
Dict (reverse (en) for en in enumerate (mm. nzrows))
349
386
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))
350
394
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)
362
404
end
363
405
end
364
- dv = is_highest_discrete ? idx : var_to_diff[v]
365
406
dv isa Int || continue
366
407
367
408
solved = var_eq_matching[dv] isa Int
@@ -388,25 +429,37 @@ function generate_derivative_variables!(ts::TearingState, neweqs, total_sub, var
388
429
389
430
dx = fullvars[dv]
390
431
# 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)
398
435
push! (fullvars, simplify_shifts (x_t))
399
436
v_t = length (fullvars)
400
437
v_t_idx = add_vertex! (var_to_diff)
401
438
add_vertex! (graph, DST)
402
439
# TODO : do we care about solvable_graph? We don't use them after
403
440
# `dummy_derivative_graph`.
404
441
add_vertex! (solvable_graph, DST)
405
- # var_eq_matching is a bit odd.
406
- # length(var_eq_matching) == length(invview(var_eq_matching))
407
442
push! (var_eq_matching, unassigned)
408
443
@assert v_t_idx == ndsts (graph) == ndsts (solvable_graph) == length (fullvars) ==
409
444
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
410
463
# add `D(x) - x_t ~ 0`
411
464
push! (neweqs, 0 ~ x_t - dx)
412
465
add_vertex! (graph, SRC)
@@ -417,16 +470,10 @@ function generate_derivative_variables!(ts::TearingState, neweqs, total_sub, var
417
470
add_edge! (solvable_graph, dummy_eq, dv)
418
471
@assert nsrcs (graph) == nsrcs (solvable_graph) == dummy_eq
419
472
@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
425
473
var_to_diff[v_t] = var_to_diff[dv]
426
474
var_eq_matching[dv] = unassigned
427
475
eq_var_matching[dummy_eq] = dv
428
476
end
429
- @show total_sub
430
477
end
431
478
432
479
"""
@@ -442,7 +489,7 @@ such that the mass matrix is:
442
489
443
490
Update the state to account for the new ordering and equations.
444
491
"""
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 )
446
493
@unpack fullvars, sys, structure = state
447
494
@unpack solvable_graph, var_to_diff, eq_to_diff, graph, lowest_shift = structure
448
495
eq_var_matching = invview (var_eq_matching)
@@ -467,7 +514,7 @@ function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, v
467
514
var -> diff_to_var[var] != = nothing
468
515
end
469
516
470
- # ## Extract partition information
517
+ # Extract partition information
471
518
is_solvable = let solvable_graph = solvable_graph
472
519
(eq, iv) -> eq isa Int && iv isa Int && BipartiteEdge (eq, iv) in solvable_graph
473
520
end
@@ -485,9 +532,12 @@ function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, v
485
532
eqs = Iterators. reverse (toporder)
486
533
idep = ModelingToolkit. has_iv (sys) ? ModelingToolkit. get_iv (sys) : nothing
487
534
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
489
540
for ieq in eqs
490
- println ()
491
541
iv = eq_var_matching[ieq]
492
542
if is_solvable (ieq, iv)
493
543
# 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
497
547
isnothing (D) &&
498
548
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]) " )
499
549
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])
505
551
eq = dx ~ simplify_shifts (Symbolics. fixpoint_sub (
506
552
Symbolics. symbolic_linear_solve (neweqs[ieq],
507
553
fullvars[iv]),
508
554
total_sub; operator = ModelingToolkit. Shift))
509
- @show total_sub
510
- @show eq
511
555
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
516
556
rem_edge! (graph, e, iv)
557
+ add_edge! (graph, e, lv)
517
558
end
518
559
push! (diff_eqs, eq)
519
560
total_sub[simplify_shifts (eq. lhs)] = eq. rhs
@@ -594,38 +635,10 @@ function solve_and_generate_equations!(state::TearingState, neweqs, total_sub, v
594
635
new_eq_to_diff[v′] = d′ > 0 ? d′ : nothing
595
636
end
596
637
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
598
639
end
599
640
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
-
627
641
# Terminology and Definition:
628
- #
629
642
# A general DAE is in the form of `F(u'(t), u(t), p, t) == 0`. We can
630
643
# characterize variables in `u(t)` into two classes: differential variables
631
644
# (denoted `v(t)`) and algebraic variables (denoted `z(t)`). Differential
@@ -654,21 +667,13 @@ function tearing_reassemble(state::TearingState, var_eq_matching,
654
667
dummy_sub = Dict ()
655
668
is_discrete = is_only_discrete (state. structure)
656
669
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
664
670
var_order = let diff_to_var = diff_to_var
665
671
dv -> begin
666
672
order = 0
667
673
while (dv′ = diff_to_var[dv]) != = nothing
668
674
order += 1
669
675
dv = dv′
670
676
end
671
- is_discrete && (order = - idx_to_lowest_shift[dv] - order)
672
677
order, dv
673
678
end
674
679
end
@@ -677,7 +682,7 @@ function tearing_reassemble(state::TearingState, var_eq_matching,
677
682
substitute_dummy_derivatives! (state, neweqs, dummy_sub, var_eq_matching)
678
683
generate_derivative_variables! (state, neweqs, total_sub, var_eq_matching, var_order;
679
684
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 )
681
686
682
687
# Update system
683
688
var_to_diff = new_var_to_diff
@@ -693,16 +698,25 @@ function tearing_reassemble(state::TearingState, var_eq_matching,
693
698
i -> (! isempty (𝑑neighbors (graph, i)) ||
694
699
(var_to_diff[i] != = nothing && ! isempty (𝑑neighbors (graph, var_to_diff[i]))))
695
700
end
701
+ @show graph
702
+ println ()
703
+ println (" Shift test..." )
704
+ @show neweqs
705
+ @show fullvars
706
+ @show 𝑑neighbors (graph, 5 )
696
707
697
708
sys = state. sys
698
-
699
- @show dummy_sub
700
709
obs_sub = dummy_sub
701
710
for eq in neweqs
702
711
isdiffeq (eq) || continue
703
712
obs_sub[eq. lhs] = eq. rhs
704
713
end
714
+ is_discrete && for eq in subeqs
715
+ obs_sub[eq. rhs] = eq. lhs
716
+ end
717
+
705
718
@show obs_sub
719
+ @show observed (sys)
706
720
# TODO : compute the dependency correctly so that we don't have to do this
707
721
obs = [fast_substitute (observed (sys), obs_sub); subeqs]
708
722
0 commit comments