Skip to content

[v10] refactor: use ImplicitDiscreteSystem to implement affects in callback systems #3452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 123 commits into
base: v10
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
c4fa355
chore!: bump MAJOR version
AayushSabharwal Apr 14, 2025
d2e0821
ci: run workflows on PR to v10 branch
AayushSabharwal Apr 14, 2025
eb2e1fb
docs: bump MTK compat
AayushSabharwal Apr 14, 2025
6eba594
TEMP COMMIT: use branch of MTKStdlib
AayushSabharwal Apr 14, 2025
b976833
feat: make `@named` always wrap arguments in `ParentScope`
AayushSabharwal Apr 3, 2025
50451d2
test: test `@named` always wrapping in `ParentScope`
AayushSabharwal Apr 14, 2025
e0844d7
refactor: remove `DelayParentScope`
AayushSabharwal Apr 14, 2025
086bab6
refactor: remove `time_varying_as_func`
AayushSabharwal Apr 14, 2025
607c0a5
test: update tests with removed `time_varying_as_func`
AayushSabharwal Apr 14, 2025
cfc6cd7
refactor: remove input_idxs output
vyudu Apr 18, 2025
d3e8841
refactor: require simplify system for linearization
vyudu Apr 21, 2025
ea858ca
use mtkbuild
vyudu Apr 21, 2025
8cbbb16
fix: fix linearization tests
vyudu Apr 22, 2025
1f4243d
fix: simplify if not simplified
vyudu Apr 22, 2025
c424570
revert rename
vyudu Apr 22, 2025
6e3216e
revert more renames
vyudu Apr 22, 2025
22cdc69
correct tests
vyudu Apr 22, 2025
1956ca0
fix: require simplification again
vyudu Apr 22, 2025
5feffd8
reset test file
vyudu Apr 23, 2025
e6eab84
revert src/linearization
vyudu Apr 23, 2025
4e25363
reset doc file
vyudu Apr 23, 2025
f4723b8
revert rename
vyudu Apr 23, 2025
81fc5b5
test: test updates
vyudu Apr 24, 2025
a86e553
fix input output tests
vyudu Apr 24, 2025
e31b6a7
more test fixes
vyudu Apr 25, 2025
f1b7ceb
fix: fix sort_eqs and check distrubances in markio
vyudu Apr 28, 2025
47b7dd8
format
vyudu May 2, 2025
0cf319c
init
vyudu Mar 1, 2025
37f050e
refactor: refactor affect codegen
vyudu Mar 3, 2025
9795eeb
feat: correct affect system generation
vyudu Mar 5, 2025
811a42a
use Pre in the affect definition
vyudu Mar 10, 2025
60d5369
refactor: correct condition generation in
vyudu Mar 11, 2025
0e9215f
some tests working
vyudu Mar 12, 2025
66afd66
fix: modify constructor for SDESystem and JUmpSystem
vyudu Mar 12, 2025
627fbe0
test: make more tests pass
vyudu Mar 12, 2025
f3fe987
test: fix namespacing
vyudu Mar 12, 2025
e6abcd4
fix: fix JumpSystem and don't use is_diff_equation
vyudu Mar 12, 2025
1e15e74
typo: add )
vyudu Mar 12, 2025
c462dfc
typo: algeeqs
vyudu Mar 12, 2025
cdf3a6d
fix
vyudu Mar 14, 2025
72421e7
more test fixes
vyudu Mar 17, 2025
a57df9d
refactor: make iv, algeeqs kwargs
vyudu Mar 18, 2025
e10e5e4
fix NoInit() error
vyudu Mar 18, 2025
095b6b6
fix: fix initialization and finalization affects
vyudu Mar 20, 2025
4247831
uncomment tests
vyudu Mar 20, 2025
ed98038
fix: most tests passing
vyudu Mar 20, 2025
f1b6535
feat: add optimization for explicit affects
vyudu Mar 22, 2025
b7a4502
fix: fix FMI tests and parameter dependency tests
vyudu Mar 22, 2025
0d08ea1
more test fixes
vyudu Mar 22, 2025
4f9dfea
fix: more tests passing
vyudu Mar 24, 2025
ee65a30
fix: more test fixes
vyudu Mar 24, 2025
0e8b48d
fix: use is_diff_equation instead of isdiffeq when finding algeeqs
vyudu Mar 24, 2025
b595375
feat: specify discrete_parameters
vyudu Mar 25, 2025
b9669c1
up
vyudu Mar 26, 2025
8625744
feat: add discrete_parameters
vyudu Mar 26, 2025
990eb7b
fix: use is_diff_equation with flatten_equations
vyudu Mar 26, 2025
479e147
remove show
vyudu Mar 26, 2025
60a6a53
format
vyudu Mar 26, 2025
94d172c
fix: fix typos and to_term differentials in affect equations
vyudu Mar 27, 2025
9e11381
fix: add events to SDESystem after structural simplification
vyudu Mar 27, 2025
831a8d7
fix: add reinitalizealg back
vyudu Mar 28, 2025
deb4e31
fix: use discrete_parameters in tests
vyudu Mar 31, 2025
9b356db
fix: fix collect_var
vyudu Mar 31, 2025
661e1f6
format
vyudu Mar 31, 2025
4d1e8b7
docs: add documentation for the symbolic affect changes
vyudu Apr 1, 2025
00ccca4
revert index cache
vyudu Apr 1, 2025
7b4058b
fix: use discrete_parameters in SII test
vyudu Apr 1, 2025
8d0454c
fix: fix model parsing for events
vyudu Apr 1, 2025
3c036b0
docs: document the discrete_parameters
vyudu Apr 1, 2025
7295caa
format
vyudu Apr 1, 2025
9e12c83
fix: remove the plural constructors
vyudu Apr 1, 2025
84903a5
fix: fix model parsing error
vyudu Apr 2, 2025
0c09dd0
fix: add continuous_events back
vyudu Apr 2, 2025
50e9368
fix: allow Arr in tovar
vyudu Apr 2, 2025
45ec229
fix: allow Arr in tovar
vyudu Apr 2, 2025
57dd747
fix JumpSystem
vyudu Apr 2, 2025
c6e7fdb
fix: unwrap s in tovar
vyudu Apr 2, 2025
692c089
up
vyudu Apr 2, 2025
f5f5a88
fix: fix several tests
vyudu Apr 2, 2025
d95aa83
docs: fix doc discrete_events example
vyudu Apr 2, 2025
0b344eb
docs: fix doc example
vyudu Apr 2, 2025
4e8c752
docs: fix more doc examples
vyudu Apr 2, 2025
710fb4a
allow symbolic in Discrete condition
vyudu Apr 2, 2025
19e4368
require Bool
vyudu Apr 2, 2025
b34c766
more docs fixes
vyudu Apr 3, 2025
f682cac
update NewsMD
vyudu Apr 21, 2025
79278a2
fix: fix sfmi bugs and
vyudu Apr 22, 2025
04b86db
fix remaining tests
vyudu Apr 23, 2025
589e20e
format
vyudu Apr 23, 2025
29cdece
fix: improve performance of implicit_affect
vyudu Apr 29, 2025
b726810
fix: fix implicit_affect
vyudu Apr 29, 2025
f556dc5
format
vyudu Apr 29, 2025
44cf0fd
fix: improve performance of implicit affect
vyudu Apr 30, 2025
9c7c597
fix: drop alg_eqs for JumpSystem
vyudu Apr 30, 2025
d2ec633
fix: fix jumpsystem test
vyudu Apr 30, 2025
15dc5e2
format
vyudu Apr 30, 2025
115b8db
fix: fix SII issue in the implicit affect
vyudu May 1, 2025
26537d1
cleanup
vyudu May 1, 2025
5409f2b
fix: typo and setsym error
vyudu May 1, 2025
9ed02ce
rebase callback
vyudu May 5, 2025
985b8ae
chore!: bump MAJOR version
AayushSabharwal Apr 14, 2025
5af08a4
ci: run workflows on PR to v10 branch
AayushSabharwal Apr 14, 2025
0fbd4ee
docs: bump MTK compat
AayushSabharwal Apr 14, 2025
2f4112e
TEMP COMMIT: use branch of MTKStdlib
AayushSabharwal Apr 14, 2025
138c568
feat: make `@named` always wrap arguments in `ParentScope`
AayushSabharwal Apr 3, 2025
f603e8c
test: test `@named` always wrapping in `ParentScope`
AayushSabharwal Apr 14, 2025
c13fe32
refactor: remove `DelayParentScope`
AayushSabharwal Apr 14, 2025
b6b35af
refactor: remove `time_varying_as_func`
AayushSabharwal Apr 14, 2025
faa5bbf
test: update tests with removed `time_varying_as_func`
AayushSabharwal Apr 14, 2025
26292ff
refactor: remove input_idxs output
vyudu Apr 18, 2025
21b231a
refactor: use new `structural_simplify` in linearization
vyudu Apr 21, 2025
1d2a604
fix: fix linearization tests
vyudu Apr 22, 2025
c124625
test: test updates
vyudu Apr 24, 2025
70e25e8
fix input output tests
vyudu Apr 24, 2025
48ae093
more test fixes
vyudu Apr 25, 2025
0234315
fix: fix sort_eqs and check distrubances in markio
vyudu Apr 28, 2025
90aba86
test: fix input output test
AayushSabharwal May 7, 2025
0d7943d
test: fix usage of old `structural_simplify` io syntax
AayushSabharwal May 7, 2025
bdbf29a
Update src/parameters.jl
vyudu May 7, 2025
d6c2666
Update src/systems/callbacks.jl
vyudu May 7, 2025
bc8c760
Merge branch 'v10' into callback
vyudu May 7, 2025
ca507df
fix compile condition error
vyudu May 8, 2025
426e6bb
disable cse
vyudu May 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/Documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- master
- v10
tags: '*'
pull_request:

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/FormatCheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- 'master'
- v10
tags: '*'
pull_request:

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
- 'release-'
- v10
paths-ignore:
- 'docs/**'
push:
Expand Down
19 changes: 19 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# ModelingToolkit v10 Release Notes

### Callbacks

Callback semantics have changed.

- There is a new `Pre` operator that is used to specify which values are before the callback.
For example, the affect `A ~ A + 1` should now be written as `A ~ Pre(A) + 1`. This is
**required** to be specified - `A ~ A + 1` will now be interpreted as an equation to be
satisfied after the callback (and will thus error since it is unsatisfiable).

- All parameters that are changed by a callback must be declared as discrete parameters to
the callback constructor, using the `discrete_parameters` keyword argument.

```julia
event = SymbolicDiscreteCallback(
[t == 1] => [p ~ Pre(p) + 1], discrete_parameters = [p])
```

# ModelingToolkit v9 Release Notes

### Upgrade guide
Expand Down
13 changes: 11 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ModelingToolkit"
uuid = "961ee093-0014-501f-94e3-6117800e7a78"
authors = ["Yingbo Ma <[email protected]>", "Chris Rackauckas <[email protected]> and contributors"]
version = "9.78.0"
version = "10.0.0"

[deps]
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
Expand Down Expand Up @@ -30,6 +30,7 @@ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e"
FunctionWrappersWrappers = "77dc65aa-8811-40c2-897b-53d922fa7daf"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
ImplicitDiscreteSolve = "3263718b-31ed-49cf-8a0f-35a466e8af96"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
JumpProcesses = "ccbc3e58-028d-4f4c-8cd5-9ae44345cda5"
Expand All @@ -42,6 +43,7 @@ NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Expand Down Expand Up @@ -113,6 +115,7 @@ ForwardDiff = "0.10.3"
FunctionWrappers = "1.1"
FunctionWrappersWrappers = "0.1"
Graphs = "1.5.2"
ImplicitDiscreteSolve = "0.1.2"
InfiniteOpt = "0.5"
InteractiveUtils = "1"
JuliaFormatter = "1.0.47, 2"
Expand Down Expand Up @@ -174,6 +177,7 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
OptimizationBase = "bca83a33-5cc9-4baa-983d-23429ab6bcbb"
OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1"
OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
Expand All @@ -193,5 +197,10 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[sources]
ModelingToolkitStandardLibrary = { url = "https://github.com/SciML/ModelingToolkitStandardLibrary.jl/", rev = "mtk-v10" }
OptimizationBase = { url = "https://github.com/AayushSabharwal/OptimizationBase.jl", rev = "as/mtk-v10" }
OptimizationMOI = { url = "https://github.com/AayushSabharwal/Optimization.jl", subdir = "lib/OptimizationMOI", rev = "as/mtk-v10" }

[targets]
test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging"]
test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging", "OptimizationBase"]
8 changes: 8 additions & 0 deletions TestEnv/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[deps]
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
2 changes: 1 addition & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Documenter = "1"
DynamicQuantities = "^0.11.2, 0.12, 1"
FMI = "0.14"
FMIZoo = "1"
ModelingToolkit = "8.33, 9"
ModelingToolkit = "10"
ModelingToolkitStandardLibrary = "2.19"
NonlinearSolve = "3, 4"
Optim = "1.7"
Expand Down
20 changes: 6 additions & 14 deletions docs/src/basics/Composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,42 +135,34 @@ sys.y = u * 1.1
In a hierarchical system, variables of the subsystem get namespaced by the name of the system they are in. This prevents naming clashes, but also enforces that every unknown and parameter is local to the subsystem it is used in. In some cases it might be desirable to have variables and parameters that are shared between subsystems, or even global. This can be accomplished as follows.

```julia
@parameters a b c d e f
@parameters a b c d

# a is a local variable
b = ParentScope(b) # b is a variable that belongs to one level up in the hierarchy
c = ParentScope(ParentScope(c)) # ParentScope can be nested
d = DelayParentScope(d) # skips one level before applying ParentScope
e = DelayParentScope(e, 2) # second argument allows skipping N levels
f = GlobalScope(f)
d = GlobalScope(d)

p = [a, b, c, d, e, f]
p = [a, b, c, d]

level0 = ODESystem(Equation[], t, [], p; name = :level0)
level1 = ODESystem(Equation[], t, [], []; name = :level1) ∘ level0
parameters(level1)
#level0₊a
#b
#c
#level0₊d
#level0₊e
#f
#d
level2 = ODESystem(Equation[], t, [], []; name = :level2) ∘ level1
parameters(level2)
#level1₊level0₊a
#level1₊b
#c
#level0₊d
#level1₊level0₊e
#f
#d
level3 = ODESystem(Equation[], t, [], []; name = :level3) ∘ level2
parameters(level3)
#level2₊level1₊level0₊a
#level2₊level1₊b
#level2₊c
#level2₊level0₊d
#level1₊level0₊e
#f
#d
```

## Structural Simplify
Expand Down
108 changes: 87 additions & 21 deletions docs/src/basics/Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,67 @@ the event occurs). These can both be specified symbolically, but a more [general
functional affect](@ref func_affects) representation is also allowed, as described
below.

## Symbolic Callback Semantics

In callbacks, there is a distinction between values of the unknowns and parameters
*before* the callback, and the desired values *after* the callback. In MTK, this
is provided by the `Pre` operator. For example, if we would like to add 1 to an
unknown `x` in a callback, the equation would look like the following:

```julia
x ~ Pre(x) + 1
```

Non `Pre`-d values will be interpreted as values *after* the callback. As such,
writing

```julia
x ~ x + 1
```

will be interpreted as an algebraic equation to be satisfied after the callback.
Since this equation obviously cannot be satisfied, an error will result.

Callbacks must maintain the consistency of DAEs, meaning that they must satisfy
all the algebraic equations of the system after their update. However, the affect
equations often do not fully specify which unknowns/parameters should be modified
to maintain consistency. To make this clear, MTK uses the following rules:

1. All unknowns are treated as modifiable by the callback. In order to enforce that an unknown `x` remains the same, one can add `x ~ Pre(x)` to the affect equations.
2. All parameters are treated as un-modifiable, *unless* they are declared as `discrete_parameters` to the callback. In order to be a discrete parameter, the parameter must be time-dependent (the terminology *discretes* here means [discrete variables](@ref save_discretes)).

For example, consider the following system.

```julia
@variables x(t) y(t)
@parameters p(t)
@mtkbuild sys = ODESystem([x * y ~ p, D(x) ~ 0], t)
event = [t == 1] => [x ~ Pre(x) + 1]
```

By default what will happen is that `x` will increase by 1, `p` will remain constant,
and `y` will change in order to compensate the increase in `x`. But what if we
wanted to keep `y` constant and change `p` instead? We could use the callback
constructor as follows:

```julia
event = SymbolicDiscreteCallback(
[t == 1] => [x ~ Pre(x) + 1, y ~ Pre(y)], discrete_parameters = [p])
```

This way, we enforce that `y` will remain the same, and `p` will change.

!!! warning

Symbolic affects come with the guarantee that the state after the callback
will be consistent. However, when using [general functional affects](@ref func_affects)
or [imperative affects](@ref imp_affects) one must be more careful. In
particular, one can pass in `reinitializealg` as a keyword arg to the
callback constructor to re-initialize the system. This will default to
`SciMLBase.NoInit()` in the case of symbolic affects and `SciMLBase.CheckInit()`
in the case of functional affects. This keyword should *not* be provided
if the affect is purely symbolic.

## Continuous Events

The basic purely symbolic continuous event interface to encode *one* continuous
Expand Down Expand Up @@ -91,7 +152,7 @@ like this
@variables x(t)=1 v(t)=0

root_eqs = [x ~ 0] # the event happens at the ground x(t) = 0
affect = [v ~ -v] # the effect is that the velocity changes sign
affect = [v ~ -Pre(v)] # the effect is that the velocity changes sign

@mtkbuild ball = ODESystem([D(x) ~ v
D(v) ~ -9.8], t; continuous_events = root_eqs => affect) # equation => affect
Expand All @@ -110,8 +171,8 @@ Multiple events? No problem! This example models a bouncing ball in 2D that is e
```@example events
@variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=2

continuous_events = [[x ~ 0] => [vx ~ -vx]
[y ~ -1.5, y ~ 1.5] => [vy ~ -vy]]
continuous_events = [[x ~ 0] => [vx ~ -Pre(vx)]
[y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]]

@mtkbuild ball = ODESystem(
[
Expand Down Expand Up @@ -204,7 +265,7 @@ bb_sol = solve(bb_prob, Tsit5())
plot(bb_sol)
```

## Discrete events support
## Discrete Events

In addition to continuous events, discrete events are also supported. The
general interface to represent a collection of discrete events is
Expand All @@ -227,13 +288,13 @@ Suppose we have a population of `N(t)` cells that can grow and die, and at time
`t1` we want to inject `M` more cells into the population. We can model this by

```@example events
@parameters M tinject α
@parameters M tinject α(t)
@variables N(t)
Dₜ = Differential(t)
eqs = [Dₜ(N) ~ α - N]

# at time tinject we inject M cells
injection = (t == tinject) => [N ~ N + M]
injection = (t == tinject) => [N ~ Pre(N) + M]

u0 = [N => 0.0]
tspan = (0.0, 20.0)
Expand All @@ -255,7 +316,7 @@ its steady-state value (which is 100). We can encode this by modifying the event
to

```@example events
injection = ((t == tinject) & (N < 50)) => [N ~ N + M]
injection = ((t == tinject) & (N < 50)) => [N ~ Pre(N) + M]

@mtkbuild osys = ODESystem(eqs, t, [N], [M, tinject, α]; discrete_events = injection)
oprob = ODEProblem(osys, u0, tspan, p)
Expand All @@ -269,16 +330,18 @@ event time, the event condition now returns false. Here we used logical and,
cannot be used within symbolic expressions.

Let's now also add a drug at time `tkill` that turns off production of new
cells, modeled by setting `α = 0.0`
cells, modeled by setting `α = 0.0`. Since this is a parameter we must explicitly
set it as `discrete_parameters`.

```@example events
@parameters tkill

# we reset the first event to just occur at tinject
injection = (t == tinject) => [N ~ N + M]
injection = (t == tinject) => [N ~ Pre(N) + M]

# at time tkill we turn off production of cells
killing = (t == tkill) => [α ~ 0.0]
killing = ModelingToolkit.SymbolicDiscreteCallback(
(t == tkill) => [α ~ 0.0]; discrete_parameters = α, iv = t)

tspan = (0.0, 30.0)
p = [α => 100.0, tinject => 10.0, M => 50, tkill => 20.0]
Expand All @@ -298,16 +361,17 @@ A preset-time event is triggered at specific set times, which can be
passed in a vector like

```julia
discrete_events = [[1.0, 4.0] => [v ~ -v]]
discrete_events = [[1.0, 4.0] => [v ~ -Pre(v)]]
```

This will change the sign of `v` *only* at `t = 1.0` and `t = 4.0`.

As such, our last example with treatment and killing could instead be modeled by

```@example events
injection = [10.0] => [N ~ N + M]
killing = [20.0] => [α ~ 0.0]
injection = [10.0] => [N ~ Pre(N) + M]
killing = ModelingToolkit.SymbolicDiscreteCallback(
[20.0] => [α ~ 0.0], discrete_parameters = α, iv = t)

p = [α => 100.0, M => 50]
@mtkbuild osys = ODESystem(eqs, t, [N], [α, M];
Expand All @@ -325,7 +389,7 @@ specify a periodic interval, pass the interval as the condition for the event.
For example,

```julia
discrete_events = [1.0 => [v ~ -v]]
discrete_events = [1.0 => [v ~ -Pre(v)]]
```

will change the sign of `v` at `t = 1.0`, `2.0`, ...
Expand All @@ -334,10 +398,10 @@ Finally, we note that to specify an event at precisely one time, say 2.0 below,
one must still use a vector

```julia
discrete_events = [[2.0] => [v ~ -v]]
discrete_events = [[2.0] => [v ~ -Pre(v)]]
```

## Saving discrete values
## [Saving discrete values](@id save_discretes)

Time-dependent parameters which are updated in callbacks are termed as discrete variables.
ModelingToolkit enables automatically saving the timeseries of these discrete variables,
Expand All @@ -348,8 +412,10 @@ example:
@variables x(t)
@parameters c(t)

ev = ModelingToolkit.SymbolicDiscreteCallback(
1.0 => [c ~ Pre(c) + 1], discrete_parameters = c, iv = t)
@mtkbuild sys = ODESystem(
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]])
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [ev])

prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0])
sol = solve(prob, Tsit5())
Expand All @@ -362,15 +428,15 @@ The solution object can also be interpolated with the discrete variables
sol([1.0, 2.0], idxs = [c, c * cos(x)])
```

Note that only time-dependent parameters will be saved. If we repeat the above example with
this change:
Note that only time-dependent parameters that are explicitly passed as `discrete_parameters`
will be saved. If we repeat the above example with `c` not a `discrete_parameter`:

```@example events
@variables x(t)
@parameters c
@parameters c(t)

@mtkbuild sys = ODESystem(
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]])
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ Pre(c) + 1]])

prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0])
sol = solve(prob, Tsit5())
Expand Down
Loading