Skip to content

Commit eb2b839

Browse files
authored
Correct version of #203 (#204)
* allow using dict in set state * improve docstring of set _parametrs, allow pair * add tests * oh lord yes every single test passes * hell yeall all tests pass, all features are implemented * add changelog and version * make ds broadcastable * finish up everything! * better docstring
1 parent 1106184 commit eb2b839

14 files changed

+162
-45
lines changed

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# v3.8.0
2+
3+
Even more amazing level of integration with ModelingToolkit.jl!
4+
5+
- Now it is possible to partially set specific state variables by
6+
providing a dictionary or vector of pairs that map indices to variables in `set_state!` or `reinit!`.
7+
- Partial setting of states via providing a dictionary also works in `reinit!`.
8+
- Partial setting of states also works when initializing a `ParallelDynamicalSystem`
9+
by providing a vector of dictionaries or vector of pairs.
10+
- It was already possible to provide a dictionary of parameter indices to values
11+
in `set_parameters!`, now it was made more clear in the docstring.
12+
- bugfix where `reinit!` or `set_state!` of continuous time out of place parallel dynamical systems was not working correctly.
13+
- Dynamical systems are now broadacastable, e.g., you can do `observe_state.(ds, (a, b, c))`.
14+
- Fixed a bug where `set_state!(ds, val, index)` only worked for in-place systems.
15+
16+
117
# v3.7.0
218

319
Symbolic indices used in `observe_state` can now also be used in `trajectory`.

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DynamicalSystemsBase"
22
uuid = "6e36e845-645a-534a-86f2-f5d4aa5a06b4"
33
repo = "https://github.com/JuliaDynamics/DynamicalSystemsBase.jl.git"
4-
version = "3.7.1"
4+
version = "3.8.0"
55

66
[deps]
77
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"

src/core/dynamicalsystem_interface.jl

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ unless when developing new algorithm implementations that use dynamical systems.
105105
"""
106106
abstract type DynamicalSystem end
107107

108+
# Make it broadcastable:
109+
Base.broadcastable(ds::DynamicalSystem) = Ref(ds)
110+
108111
# We utilize nature of time for dispatch; continuous time dispatches to `integ`
109112
# and dispatches for `::DEIntegrator` are defined in `CoupledODEs` file.
110113
"""
@@ -326,27 +329,35 @@ StateSpaceSets.dimension(ds::DynamicalSystem) = length(current_state(ds))
326329
# API - altering status of the system
327330
###########################################################################################
328331
"""
329-
set_state!(ds::DynamicalSystem, u::AbstractArray)
332+
set_state!(ds::DynamicalSystem, u::AbstractArray{Real})
330333
331334
Set the state of `ds` to `u`, which must match dimensionality with that of `ds`.
332335
Also ensure that the change is notified to whatever integration protocol is used.
333336
"""
334337
set_state!(ds, u) = errormsg(ds)
335338

336339
"""
337-
set_state!(ds::DynamicalSystem, value::Real, index) → u
340+
set_state!(ds::DynamicalSystem, value::Real, i) → u
338341
339-
Set the `i`th variable of `ds` to `value`. The `index` can be an integer or
342+
Set the `i`th variable of `ds` to `value`. The index `i` can be an integer or
340343
a symbolic-like index for systems that reference a ModelingToolkit.jl model.
341-
342-
Calling instead `set_state!(u, value, index, ds)` will modify the given
343-
state `u` and return it, leaving `ds` unaltered.
344+
For example:
345+
```julia
346+
i = :x # or `1` or `only(@variables(x))`
347+
set_state!(ds, 0.5, i)
348+
```
344349
345350
**Warning:** this function should not be used with derivative dynamical systems
346351
such as Poincare/stroboscopic/projected dynamical systems.
352+
Use the method below to manipulate an array and give that to `set_state!`.
353+
354+
355+
set_state!(u::AbstractArray, value, index, ds::DynamicalSystem)
356+
357+
Modify the given state `u` and leave `ds` untouched.
347358
"""
348359
function set_state!(ds::DynamicalSystem, value::Real, i)
349-
u = current_state(ds)
360+
u = Array(current_state(ds)) # ensure it works for out of place as well!
350361
u = set_state!(u, value, i, ds)
351362
set_state!(ds, u)
352363
end
@@ -365,6 +376,27 @@ function set_state!(u::AbstractArray, value::Real, i, ds::DynamicalSystem)
365376
return u
366377
end
367378

379+
"""
380+
set_state!(ds::DynamicalSystem, mapping::Dict)
381+
382+
Convenience version of `set_state!` that iteratively calls `set_state!(ds, val, i)`
383+
for all index-value pairs `(i, val)` in `mapping`. This allows you to
384+
partially set only some state variables.
385+
"""
386+
function set_state!(ds::DynamicalSystem, mapping::Dict)
387+
# ensure we use a mutable vector, so same code works for in-place problems
388+
# (SymbolicIndexingInterface only works with mutable objects)
389+
um = Array(copy(current_state(ds)))
390+
set_state!(um, mapping, ds)
391+
set_state!(ds, um)
392+
end
393+
function set_state!(um::Array{<:Real}, mapping::Dict, ds::DynamicalSystem)
394+
for (i, value) in pairs(mapping)
395+
set_state!(um, value, i, ds)
396+
end
397+
return um
398+
end
399+
368400
"""
369401
set_parameter!(ds::DynamicalSystem, index, value [, p])
370402
@@ -403,14 +435,18 @@ end
403435
"""
404436
set_parameters!(ds::DynamicalSystem, p = initial_parameters(ds))
405437
406-
Set the parameter values in the [`current_parameters`](@ref)`(ds)` to match `p`.
407-
This is done as an in-place overwrite by looping over the keys of `p`.
408-
Hence the keys of `p` must be a subset of the keys of [`current_parameters`](@ref)`(ds)`.
438+
Set the parameter values in the [`current_parameters`](@ref)`(ds)` to match those in `p`.
439+
This is done as an in-place overwrite by looping over the keys of `p`
440+
hence `p` can be an arbitrary container mapping parameter indices to values
441+
(such as a `Vector{Real}`, `Vector{Pair}`, or `Dict`).
442+
443+
The keys of `p` must be valid keys that can be given to [`set_parameter!`](@ref).
409444
"""
410445
function set_parameters!(ds::DynamicalSystem, p = initial_parameters(ds))
411446
cp = current_parameters(ds)
412447
p === cp && return
413-
for (index, value) in pairs(p)
448+
iter = p isa Vector ? pairs(p) : p # allows using vector, dict, or vector{pair}.
449+
for (index, value) in iter
414450
_set_parameter!(ds, index, value, cp)
415451
end
416452
return
@@ -442,15 +478,28 @@ SciMLBase.step!(ds::DynamicalSystem, args...) = errormsg(ds)
442478
443479
Reset the status of `ds`, so that it is as if it has be just initialized
444480
with initial state `u`. Practically every function of the ecosystem that evolves
445-
`ds` first calls this function on it. Besides the new initial state `u`, you
481+
`ds` first calls this function on it. Besides the new state `u`, you
446482
can also configure the keywords `t0 = initial_time(ds)` and `p = current_parameters(ds)`.
447483
448-
Note the default settings: the state and time are the initial,
449-
but the parameters are the current.
484+
reinit!(ds::DynamicalSystem, u::Dict; kwargs...) → ds
450485
451-
The special method `reinit!(ds, ::Nothing; kwargs...)` is also available,
452-
which does nothing and leaves the system as is. This is so that downstream functions
486+
If `u` is a `Dict` (for partially setting specific state variables in [`set_state`](@ref)),
487+
then the alterations in `u` are still done in the state given by the keyword
488+
`reference_state = copy(initial_state(ds))`.
489+
490+
reinit!(ds, ::Nothing; kwargs...)
491+
492+
This method does nothing and leaves the system as is. This is so that downstream functions
453493
that call `reinit!` can still be used without resetting the system but rather
454494
continuing from its exact current state.
455495
"""
456-
SciMLBase.reinit!(ds::DynamicalSystem, args...; kwargs...) = errormsg(ds)
496+
function SciMLBase.reinit!(ds::DynamicalSystem, mapping::Dict;
497+
reference_state = copy(initial_state(ds)), kwargs...)
498+
um = Array(reference_state)
499+
set_state!(um, mapping, ds)
500+
reinit!(ds, um; kwargs...)
501+
end
502+
503+
SciMLBase.reinit!(ds::DynamicalSystem, ::Nothing; kw...) = ds
504+
# all extensions of `reinit!` in concrete implementaitons
505+
# should only implement `reinit!(ds, ::AbstractArray)` method.

src/core_systems/arbitrary_steppable.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ the following adjustments:
3232
- The keyword `isdeterministic` should be set properly, as it decides whether
3333
downstream algorithms should error or not.
3434
"""
35-
struct ArbitrarySteppable{M, F, R, ES, EP, U, P, S} <: DiscreteTimeDynamicalSystem
35+
struct ArbitrarySteppable{U, M, F, R, ES, EP, P, S} <: DiscreteTimeDynamicalSystem
3636
model::M
3737
step::F
3838
reinit::R
@@ -77,10 +77,9 @@ function SciMLBase.step!(ds::ArbitrarySteppable, n::Int = 1, stop::Bool = true)
7777
return ds
7878
end
7979

80-
function SciMLBase.reinit!(ds::ArbitrarySteppable, u = initial_state(ds);
80+
function SciMLBase.reinit!(ds::ArbitrarySteppable{U}, u::U = initial_state(ds);
8181
p = current_parameters(ds), t0 = 0, # t0 is not used but required for downstream.
82-
)
83-
isnothing(u) && return
82+
) where {U}
8483
ds.reinit(ds.model, u, p)
8584
ds.t[] = 0
8685
return ds

src/core_systems/continuous_time_ode.jl

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,14 @@ for f in (:initial_state, :current_parameters, :dynamic_rule,
136136
end
137137

138138
SciMLBase.isinplace(::CoupledODEs{IIP}) where {IIP} = IIP
139-
set_state!(ds::CoupledODEs, u::AbstractArray) = set_state!(ds.integ, u)
139+
set_state!(ds::CoupledODEs, u::AbstractArray) = (set_state!(ds.integ, u); ds)
140140

141141
# so that `ds` is printed
142142
SciMLBase.step!(ds::CoupledODEs, args...) = (step!(ds.integ, args...); ds)
143143

144-
function SciMLBase.reinit!(ds::ContinuousTimeDynamicalSystem, u = initial_state(ds);
144+
function SciMLBase.reinit!(ds::ContinuousTimeDynamicalSystem, u::AbstractArray = initial_state(ds);
145145
p = current_parameters(ds), t0 = initial_time(ds)
146146
)
147-
isnothing(u) && return
148147
set_parameters!(ds, p)
149148
reinit!(ds.integ, u; reset_dt = true, t0)
150149
return ds
@@ -174,7 +173,7 @@ end
174173
function set_state!(integ::DEIntegrator, u)
175174
if integ.u isa Array{<:Real}
176175
integ.u .= u
177-
elseif integ.u isa Union{SVector, SMatrix}
176+
elseif integ.u isa StateSpaceSets.StaticArraysCore.SArray{<:Real}
178177
integ.u = u
179178
else
180179
integ.u = recursivecopy(u)

src/core_systems/discrete_time_map.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ isdeterministic(::DIM) = true
7373
function set_state!(ds::DeterministicIteratedMap{IIP}, u) where {IIP}
7474
ds.u = recursivecopy(u)
7575
ds.dummy = recursivecopy(u)
76-
return
76+
return ds
7777
end
7878

7979
###########################################################################################
@@ -115,10 +115,9 @@ end
115115
###########################################################################################
116116
# Alterations
117117
###########################################################################################
118-
function reinit!(ds::DIM, u = initial_state(ds);
118+
function reinit!(ds::DIM, u::AbstractArray = initial_state(ds);
119119
p = current_parameters(ds), t0 = initial_time(ds)
120120
)
121-
isnothing(u) && return
122121
set_state!(ds, u)
123122
ds.t = t0
124123
set_parameters!(ds, p)

src/derived_systems/parallel_systems.jl

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ This struct follows the [`DynamicalSystem`](@ref) interface with the following a
2323
- [`current_states`](@ref) and [`initial_states`](@ref) can be used to get
2424
all parallel states.
2525
- [`reinit!`](@ref) takes in a vector of states (like `states`) for `u`.
26+
27+
28+
ParallelDynamicalSystem(ds::DynamicalSystem, states::Vector{<:Dict})
29+
30+
For a dynamical system referring a MTK model, one can specify states
31+
as a vector of dictionaries to alter the current state of `ds`
32+
as in [`set_state!`](@ref).
2633
"""
2734
abstract type ParallelDynamicalSystem <: DynamicalSystem end
2835

@@ -45,7 +52,7 @@ struct ParallelDynamicalSystemAnalytic{D, M} <: ParallelDynamicalSystem
4552
prob # reference original MTK problem
4653
end
4754

48-
function ParallelDynamicalSystem(ds::CoreDynamicalSystem, states)
55+
function ParallelDynamicalSystem(ds::CoreDynamicalSystem, states::Vector{<:AbstractArray{<:Real}})
4956
f, st = parallel_rule(ds, states)
5057
if ds isa DeterministicIteratedMap
5158
pds = DeterministicIteratedMap(f, st, current_parameters(ds); t0 = initial_time(ds))
@@ -60,6 +67,13 @@ function ParallelDynamicalSystem(ds::CoreDynamicalSystem, states)
6067
return ParallelDynamicalSystemAnalytic{typeof(pds), M}(pds, dynamic_rule(ds), prob)
6168
end
6269

70+
function ParallelDynamicalSystem(ds::CoreDynamicalSystem, mappings::Vector{<:Dict})
71+
# convert to vector of arrays:
72+
u = Array(current_state(ds))
73+
states = [set_state!(copy(u), mapping, ds) for mapping in mappings]
74+
return ParallelDynamicalSystem(ds, states)
75+
end
76+
6377
# Out of place: everywhere the same
6478
function parallel_rule(ds::CoreDynamicalSystem{false}, states)
6579
f = dynamic_rule(ds)
@@ -74,7 +88,7 @@ function parallel_rule(ds::CoreDynamicalSystem{false}, states)
7488
return parallel_f, st
7589
end
7690

77-
# In place: for where `Vector{Vector}` is possible
91+
# In place: for where `AbstractVector{Vector}` is possible
7892
function parallel_rule(ds::DeterministicIteratedMap{true}, states)
7993
f = dynamic_rule(ds)
8094
S = typeof(correct_state(Val{true}(), first(states)))
@@ -88,7 +102,7 @@ function parallel_rule(ds::DeterministicIteratedMap{true}, states)
88102
return parallel_f, st
89103
end
90104

91-
# In place, uses matrix with each column a parallel state
105+
# In place, but ODEs use matrix with each column a parallel state
92106
function parallel_rule(ds::CoupledODEs{true}, states)
93107
st = Matrix(hcat(states...))
94108
f = dynamic_rule(ds)
@@ -136,6 +150,7 @@ initial_states(pdsa::ParallelDynamicalSystemAnalytic) = initial_state(pdsa.ds)
136150
function set_state!(pdsa::ParallelDynamicalSystemAnalytic, u::AbstractArray, i::Int = 1)
137151
current_states(pdsa)[i] = u
138152
set_state!(pdsa.ds, current_states(pdsa))
153+
return pdsa
139154
end
140155

141156
# States IO for matrix state
@@ -208,6 +223,6 @@ function set_state!(pdtds::PDTDS, u, i::Int = 1)
208223
end
209224
end
210225

211-
function SciMLBase.reinit!(pdtds::PDTDS, states = initial_states(pdtds); kwargs...)
226+
function SciMLBase.reinit!(pdtds::PDTDS, states::AbstractVector = initial_states(pdtds); kwargs...)
212227
for (ds, s) in zip(pdtds.systems, states); reinit!(ds, s; kwargs...); end
213228
end

src/derived_systems/poincare/hyperplane.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
export PlaneCrossing, poincaresos
22

3-
# TODO: Remove keyword `save_idxs`!
4-
53
####################################################################################################
64
# Hyperplane definitions
75
####################################################################################################

src/derived_systems/poincare/poincaremap.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ export PoincareMap, current_crossing_time
66

77
const ROOTS_ALG = Roots.A42()
88

9+
# TODO: Remove keyword `save_idxs`!
10+
911
###########################################################################################
1012
# Type definition
1113
###########################################################################################
@@ -164,7 +166,7 @@ function SciMLBase.step!(pmap::PoincareMap)
164166
end
165167
SciMLBase.step!(pmap::PoincareMap, n::Int, s = true) = (for _ 1:n; step!(pmap); end; pmap)
166168

167-
function SciMLBase.reinit!(pmap::PoincareMap, u0 = initial_state(pmap);
169+
function SciMLBase.reinit!(pmap::PoincareMap, u0::AbstractArray = initial_state(pmap);
168170
t0 = initial_time(pmap), p = current_parameters(pmap)
169171
)
170172
isnothing(u0) && return pmap
@@ -184,7 +186,7 @@ end
184186
function set_state!(pmap::PoincareMap, u)
185187
set_state!(pmap.ds, u)
186188
step!(pmap) # always step once to reach the PSOS
187-
return
189+
return pmap
188190
end
189191

190192
function _recreate_state_from_poincare_plane(pmap::PoincareMap, u0)

src/derived_systems/projected_system.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function SciMLBase.step!(prods::ProjectedDynamicalSystem, args...)
113113
return prods
114114
end
115115

116-
function SciMLBase.reinit!(prods::ProjectedDynamicalSystem{P, D, <:AbstractVector}, y = initial_state(prods); kwargs...) where {P, D}
116+
function SciMLBase.reinit!(prods::ProjectedDynamicalSystem{P, D, <:AbstractVector}, y::AbstractArray = initial_state(prods); kwargs...) where {P, D}
117117
isnothing(y) && return(prods)
118118
u = prods.u
119119
u[prods.projection] .= y
@@ -122,7 +122,7 @@ function SciMLBase.reinit!(prods::ProjectedDynamicalSystem{P, D, <:AbstractVecto
122122
return prods
123123
end
124124

125-
function SciMLBase.reinit!(prods::ProjectedDynamicalSystem{P, D, <:Function}, y = initial_state(prods); kwargs...) where {P, D}
125+
function SciMLBase.reinit!(prods::ProjectedDynamicalSystem{P, D, <:Function}, y::AbstractArray = initial_state(prods); kwargs...) where {P, D}
126126
isnothing(y) || reinit!(prods.ds, prods.complete_state(y); kwargs...)
127127
return prods
128128
end

0 commit comments

Comments
 (0)