-
Notifications
You must be signed in to change notification settings - Fork 4
RateSystem #117
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
base: main
Are you sure you want to change the base?
RateSystem #117
Changes from 123 commits
e58aeab
c7d44b1
c71f1b4
525c6a1
4e8366c
727740f
3acaddb
cae20d6
d32c1c5
5ee2ed9
444eb72
b8f145a
f8230d7
320fa82
189f62b
bba545b
dcaf8aa
650b6fa
f6d97e3
3febe82
8e0a7b3
e2f006e
74ba1ae
5786d48
6e57725
bdd468e
020b80f
894945b
af0efc4
9e1f68d
d3c9ece
12d4a01
e3d2156
90f4228
1703378
2a5619e
260e18f
64d775c
cdd41f4
cbe4abe
6ab08ec
c47fdd4
5266412
12ab98f
f13cd91
e9f90c8
14a97d6
f8d73ef
cf5afb2
e7f8580
063b84b
1feff1a
4e0ab57
41128cc
2dcffc8
00f0ef2
729ea5f
648c863
b147beb
79715af
eb43377
4cdbd7d
db71cb3
923849d
8634b8d
1c0999d
44f11fb
722cc74
345865f
a6e8029
3c94279
5f1f6bc
ef73286
5a1ec54
7d15398
fe0fc15
c0d6f75
2cf1029
dc1eda6
b947592
745f70c
11986d4
ae01765
c0758bf
25cdd07
3bedbec
38cedd7
15c3a24
4c5b185
dce3efa
59f482b
c0a0a3d
efbab37
e8bcefb
535be1e
2b26698
d5a2a1e
33f2121
55066e3
3be44f6
90e2aa8
14c2558
22e8f1d
295cc13
82ae326
165fa07
c0708cf
176b480
176a9a0
a94a54e
518f530
fb431b6
0287780
684b8e7
a0e00c3
ab9df85
9082622
02ec326
bbd9e3b
1126b78
102743b
e5732b2
fb49e96
12cdc51
914a329
6be39fd
8d4175e
4970657
765252b
cf0012c
54dc3ba
a115f3c
a975369
acc61f6
39f8e7e
22fd45f
2aa37fe
8e6a900
9a3f723
ff71166
00ae0c9
c0e3773
2c48992
1ce22d0
b8bd829
04fad43
566112a
3ef9dfe
179a0a8
8416059
e69cd6c
05b5798
1efa93c
06055ec
f08cef5
adccf47
bcc0922
6dfd5fd
434a84a
e97ebe0
d349078
b6452b5
ab83fee
7412b93
494b130
2fc8e7b
f987816
02a0bcc
5500415
e2766ea
d06dc6d
538df9e
20e5acc
e2ea030
986bc7c
3da25c0
d3a5d98
6156627
75d493b
8527d7f
1e50aaa
5031bb1
9500149
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Studying R-Tipping | ||
|
||
Let us explore a simple prototypical example of how to use the R-tipping functionality of this package. | ||
We start with defining an autonomous deterministic dynamical system (i.e. a `CoupledODEs`). Then, we define a time-dependent forcing protocol called `RateConfig`, describing how the parameters of the previously defined autonomous `CoupledODEs` will be changed over time. For times `t` smaller than the time `t_start`, the system is autonomous, then non-autonomous for `t_start < t < t_start + ramp_t_length` with the paramter ramping given by the `RateConfig` and for `t > t_start + ramp_t_length` the system is autonomous again. This setting is a widely used and convenient for studying R-tipping. | ||
|
||
We first consider the following simple one-dimensional autonomous system with one attractor, given by the ordinary differential equation: | ||
```math | ||
\begin{aligned} | ||
\dot{x} &= (x+p)^2 - 1 | ||
\end{aligned} | ||
``` | ||
The parameter ``p`` shifts the location of the extrema of the drift field. | ||
We implement this system as follows: | ||
|
||
```@example RateSystem | ||
using CriticalTransitions | ||
using CairoMakie | ||
function f(u,p,t) # out-of-place | ||
x = u[1] | ||
dx = (x+p[1])^2 - 1 | ||
return SVector{1}(dx) | ||
end | ||
x0 = [-1.] | ||
auto_sys = CoupledODEs(f,x0,[0.0]); | ||
``` | ||
|
||
## Applying the parameter ramping | ||
|
||
Now, we want to explore a non-autonomous version of the system by applying a parameter ramping. | ||
As discussed, we consider a setting where in the past and in the future the system is autnonomous and in between there is a non-autonomous period `[t_start, t_start+ramp_t_length]` with a time-dependent parameter ramping given by the function ``p(t)``. Choosing different values of the parameter `ramp_t_length` allows us to vary the speed of the parameter ramping. | ||
|
||
We start by defining the function `p(p_parameters, t)`: | ||
```@example RateSystem | ||
function p(p_parameters, t) | ||
p_max = p_parameters[1] | ||
p_ = (p_max/2)*(tanh(p_max*t/2)+1) | ||
return SVector{1}(p_) | ||
end | ||
|
||
p_max = 1.0 | ||
p_parameters = [p_max] # parameter of the function p | ||
``` | ||
|
||
|
||
We plot the function `p(p_parameters, t)` | ||
```@example RateSystem | ||
p_plotvals = [p(p_parameters, t)[1] for t in -10.0:0.1:10.0] | ||
figp = Figure(); axsp = Axis(figp[1,1],xlabel="t",ylabel=L"p") | ||
lines!(axsp,-10.0:0.1:10.0,p_plotvals,linewidth=2,label=L"p(p_{parameters}, t)") | ||
axislegend(axsp,position=:rc,labelsize=10) | ||
figp | ||
``` | ||
Note that this function fulfills | ||
```math | ||
\begin{aligned} | ||
p(t=-10)& =0 \ and \ p(t=10)=1. | ||
\end{aligned} | ||
``` | ||
We recommend this to be fulfilled for any ramping function to use the functionality of this package. | ||
|
||
|
||
|
||
Now, we define a `RateConfig`, which contains all the information to apply the parameter ramping given by | ||
`p(p_parameters,t)` to the `auto_sys` during `[t_start, t_start+ramp_t_length]`: | ||
|
||
```@example RateSystem | ||
t_start = -10 # start time of non-autonomous part | ||
ramp_t_length = 5 # duration of non-autonomous part | ||
dp=3 # strength of the paramter ramping | ||
rc = CriticalTransitions.RateConfig(p, p_parameters, t_start,ramp_t_length,dp) | ||
``` | ||
Note that `dp` is defined as a prefactor of the function `p`. Thus, changing `dp` will change the amount of the parameter ramping. If the user provides a function fulfilling ``p(t=-10) =0 and p(t=10)=1``, setting `dp=10` would result in a parameter ramping from ``p(t=-10) = 0 and p(t=10) = 10``. | ||
|
||
|
||
We set up the system with autonomous past and future and non-autonomous ramping in between: | ||
|
||
```@example RateSystem | ||
t0 = -10.0 # initial time of the system | ||
nonauto_sys = apply_ramping(auto_sys, rc, t0); | ||
``` | ||
|
||
We can compute trajectories of this new system in the familiar way: | ||
```@example RateSystem | ||
T = 50.0 # final simulation time | ||
auto_traj = trajectory(auto_sys, T, x0) | ||
nonauto_traj = trajectory(nonauto_sys, T, x0); | ||
``` | ||
|
||
We plot the two trajectories | ||
raphael-roemer marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
```@example RateSystem | ||
fig = Figure(); axs = Axis(fig[1,1],xlabel="t",ylabel="x") | ||
lines!(axs,t0.+auto_traj[2],auto_traj[1][:,1],linewidth=2,label=L"\text{Autonomous system}") | ||
lines!(axs,nonauto_traj[2],nonauto_traj[1][:,1],linewidth=2,label=L"\text{Nonautonomous system}") | ||
axislegend(axs,position=:rc,labelsize=10) | ||
fig | ||
``` | ||
|
||
----- | ||
Author: Raphael Roemer; Date: 30 Jun 2025 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
# we consider the ODE dxₜ/dt = f(xₜ,p(rt)) | ||
# here p = p(t) ∈ Rᵐ is a function containing all the system parameters | ||
|
||
# We ask the user to define: | ||
# 1) a ContinuousTimeDynamicalSystem that should be investigated and | ||
# 2) a protocol for the time-dependent forcing with the struct RateConfig | ||
|
||
# Then we give back the ContinuousTimeDynamicalSystem with the parameter | ||
# changing according to the rate protocol | ||
""" | ||
reykboerner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
RateConfig | ||
Time-dependent forcing protocol containing the information to apply a parameter shift to an autonomous system. | ||
Fields | ||
============== | ||
- p: monotonic function which describes the time-dependent parametric forcing | ||
|
||
- p_parameters: the vector of parameters which are associated with p | ||
|
||
- t_pstart: the parameter values of the past limit system are given by p(p_parameteters,t_pstart) | ||
- t_pend: the parameter values of the future limit system are given by p(p_parameters,t_pend) | ||
- t_start: the explicit value of time at which the nonautonomous dynamics starts | ||
|
||
- t_ramp_length: the duration of time of nonautonomous dynamics [i.e. within (t_start,t_start+t_ramp_length)] | ||
- dp: the difference in parameter values attained across the ramping | ||
reykboerner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Default values | ||
============== | ||
- t_pstart = -100 | ||
- t_pend = 100 | ||
- p_parameters = [] | ||
- t_start = -t_ramp_length/2 | ||
- dp = 1 | ||
|
||
""" | ||
mutable struct RateConfig | ||
p::Function | ||
p_parameters::Vector | ||
t_pstart::Float64 | ||
t_pend::Float64 | ||
t_start::Float64 | ||
t_ramp_length::Float64 | ||
dp::Float64 | ||
end | ||
|
||
## convenience functions | ||
|
||
RateConfig(p::Function, t_ramp_length::Float64) = RateConfig(p, [], -100.0, 100.0, -t_ramp_length/2, t_ramp_length, 1.0) | ||
RateConfig(p::Function, t_ramp_length::Float64, dp::Float64) = RateConfig(p, [], -100.0, 100.0, -t_ramp_length/2, t_ramp_length, dp) | ||
|
||
RateConfig(p::Function, p_parameters::Vector, t_ramp_length::Float64) = RateConfig(p, p_parameters, -100.0, 100.0, -t_ramp_length/2, t_ramp_length, 1.0) | ||
RateConfig(p::Function, p_parameters::Vector, t_ramp_length::Float64, dp::Float64) = RateConfig(p, p_parameters, -100.0, 100.0, -t_ramp_length/2, t_ramp_length, dp) | ||
|
||
## the following function creates a piecewise constant function with respect to t_pstart and t_pend... | ||
## ...which is written such that p̃(t_pend)-p̃(t_pstart) = dp, for the time-dependent entries (otherwise p̃(t_pend)-p̃(t_pstart) = 0 as p̃(t)≡c) | ||
|
||
function p_piecewise_scaled(p::Function,p_parameters::Vector,t_pstart::Float64,t_pend::Float64,dp::Float64,t::Float64) | ||
if t ≤ t_pstart | ||
return p(p_parameters,t_pstart) | ||
else | ||
np = length(p(p_parameters,t_pstart)) # the number of system parameters | ||
differences = p(p_parameters,t_pend) .- p(p_parameters,t_pstart) | ||
nonautonomous_inds = [ii for ii ∈ 1:np if differences[ii] != 0] | ||
inds = [ii ∈ nonautonomous_inds ? 1/abs(differences[ii]) : 0 for ii ∈ 1:np] # takes value 1/|p(...,t_pend)-p(...,t_start)| when p(...,t_pend)=/=p(...,t_start); zero otherwise | ||
if t < t_pend | ||
return p(p_parameters,t_pstart) .+ dp .* inds .* (p(p_parameters,t) .- p(p_parameters,t_pstart)) | ||
else | ||
return p(p_parameters,t_pstart) .+ dp .* inds .* (p(p_parameters,t_pend) .- p(p_parameters,t_pstart)) | ||
end | ||
end | ||
end | ||
|
||
function modified_drift( | ||
u, | ||
p_parameters, | ||
t, | ||
ds::ContinuousTimeDynamicalSystem, | ||
p::Function, | ||
t_pstart::Float64, | ||
t_pend::Float64, | ||
t_start::Float64, | ||
t_ramp_length::Float64, | ||
dp::Float64; | ||
kwargs..., | ||
) | ||
|
||
q(t) = p_piecewise_scaled(p,p_parameters,t_pstart,t_pend,dp,t) | ||
|
||
time_shift = ((t_pend-t_pstart)/t_ramp_length)*(t-t_start)+t_pstart # such that [t_start,t_start+t_ramp_length] is shifted into [t_pstart,t_pend] | ||
|
||
q̃ = q(time_shift) | ||
|
||
|
||
return dynamic_rule(ds)(u, q̃, t) | ||
|
||
end; | ||
|
||
""" | ||
reykboerner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
apply_ramping(sys::CoupledODEs, rc::RateConfig, t0=0.0; kwargs...) | ||
Applies a time-dependent [`RateConfig`](@def) to a given autonomous deterministic dynamical system | ||
`sys`, turning it into a non-autonomous dynamical system. The returned [`CoupledODEs`](@ref) | ||
has the explicit parameter time-dependence incorporated. | ||
The returned [`CoupledODEs`](@ref) is autonomous from `t_0` to `t_start`, | ||
then non-autnonmous from `t_start` to `t_start+t_ramp_length` with the parameter shift given by the [`RateConfig`](@def), | ||
and again autonomous from `t_start+t_ramp_length` to the end of the simulation: | ||
`t_0` autonomous `t_start` non-autonomous `t_start+t_ramp_length` autonomous `∞` | ||
Computing trajectories of the returned [`CoupledODEs`](@ref) can then be done in the same way as for any other [`CoupledODEs`](@ref). | ||
""" | ||
function apply_ramping(auto_sys::CoupledODEs, rc::RateConfig, t0=0.0; kwargs...) | ||
# we wish to return a continuous time dynamical system with modified drift field | ||
|
||
f(u, p_parameters, t) = modified_drift( | ||
u, p_parameters, t, auto_sys, rc.p, rc.t_pstart, rc.t_pend, rc.t_start, rc.t_ramp_length, rc.dp; kwargs... | ||
) | ||
prob = remake(referrenced_sciml_prob(auto_sys); f, p=rc.p_parameters, tspan=(t0, Inf)) | ||
nonauto_sys = CoupledODEs(prob, auto_sys.diffeq) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good point. For now, I fixed it by only allowing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe that if we use the use multiple dispatch functionality of Julia, we could essentially write two versions of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would require making another function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good, I think! Could you add this? |
||
return nonauto_sys | ||
end | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# we consider the ODE dxₜ/dt = f(xₜ,λ(rt)) | ||
# here λ = λ(t) ∈ Rᵐ is a function containing all the system parameters | ||
|
||
# We ask the user to define: | ||
# 1) a ContinuousTimeDynamicalSystem that should be investigated and | ||
# 2) a protocol for the time-dependent forcing with the struct RateProtocol | ||
|
||
# Then we give back the ContinuousTimeDynamicalSystem with the parameter | ||
# changing according to the rate protocol | ||
""" | ||
RateProtocol | ||
|
||
The RateProtocol contains all the fields (information) that allows to take an ODE of the form | ||
`dxₜ/dt = f(xₜ, λ)` | ||
with `λ ∈ Rᵐ` containing all system parameters, and make it an ODE of the form | ||
`dxₜ/dt = f(xₜ, λ(rt))` | ||
with `λ(t) ∈ Rᵐ` constant before time `time_interval[1]` and also after time `time_interval[2]`. | ||
|
||
RateProtocol contains the following fields: | ||
- `λ::Function`: forcing function of the form `λ(p, t_start; kwargs...)`` | ||
- `p_lambda::Vector`: parameters of the forcing function | ||
- `r::Float64`: rate parameter | ||
- `time_interval::Tuple`: start and end time of protocol | ||
|
||
Default values | ||
============== | ||
|
||
time_interval = (-Inf, Inf) | ||
p_lambda = [] | ||
""" | ||
mutable struct RateProtocol | ||
λ::Function | ||
p_lambda::Vector | ||
r::Float64 | ||
time_interval::Tuple | ||
end | ||
|
||
# convenience functions | ||
|
||
function RateProtocol(λ::Function, p_lambda::Vector, r::Float64) | ||
RateProtocol(λ, p_lambda, r, (-Inf, Inf)) | ||
end | ||
RateProtocol(λ::Function, r::Float64)=RateProtocol(λ, [], r, (-Inf, Inf)) | ||
#RateProtocol(λ::Function,p_lambda::Vector,r::Float64,t_start::Float64)=RateProtocol(λ,p_lambda,r,t_start,Inf) | ||
#RateProtocol(λ::Function,r::Float64,t_start::Float64)=RateProtocol(λ,[],r,t_start,Inf) | ||
|
||
function modified_drift( | ||
u, | ||
p, | ||
t, | ||
ds::ContinuousTimeDynamicalSystem, | ||
λ::Function, | ||
time_interval::Tuple, | ||
r::Float64; | ||
kwargs..., | ||
) | ||
if time_interval[1] > time_interval[2] | ||
error("Please ensure that time_interval[1] ≤ time_interval[2].") | ||
end | ||
|
||
p̃ = if r*t ≤ time_interval[1] | ||
λ(p, time_interval[1]; kwargs...) | ||
elseif time_interval[1] < r*t < time_interval[2] | ||
λ(p, r*t; kwargs...) # the value(s) of λ(rt) | ||
else | ||
λ(p, time_interval[2]; kwargs...) # the value(s) of λ(rt) | ||
end; # the value(s) of λ(rt) | ||
return dynamic_rule(ds)(u, p̃, t) | ||
end; | ||
|
||
""" | ||
apply_ramping(sys::CoupledODEs, rp::RateProtocol, t0=0.0; kwargs...) | ||
|
||
Applies a time-dependent [`RateProtocol`](@def) to a given autonomous deterministic dynamical system `sys`, | ||
returning a non-autonomous dynamical system of type [`CoupledODEs`](@ref). | ||
|
||
The [`RateProtocol`](@def) replaces the parameters of `sys` by the function `λ(rt)` within the | ||
time interval `time_interval`. Thus, the returned [`CoupledODEs`](@ref) has the explicit parameter time-dependence incorporated and is | ||
autonomous from `t_0` to `time_interval[1]`, non-autnonmous from `time_interval[1]` to `time_interval[2]` with the parameter shift given by the [`RateProtocol`](@def), | ||
and autonomous from `time_interval[2]` to the end of the simulation: | ||
|
||
`t_0` autonomous `time_interval[1]` non-autonomous `time_interval[2]` autonomous `∞` | ||
|
||
Computing trajectories of the returned [`CoupledODEs`](@ref) can then be done in the same way as for any other [`CoupledODEs`](@ref). | ||
""" | ||
function apply_ramping(auto_sys::CoupledODEs, rp::RateProtocol, t0=0.0; kwargs...) | ||
# we wish to return a continuous time dynamical system with modified drift field | ||
|
||
f(u, p, t) = modified_drift( | ||
u, p, t, auto_sys, rp.λ, rp.time_interval, rp.r; kwargs... | ||
) | ||
prob = remake(referrenced_sciml_prob(auto_sys); f, p=rp.p_lambda, tspan=(t0, Inf)) | ||
nonauto_sys = CoupledODEs(prob, auto_sys.diffeq) | ||
return nonauto_sys | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
R-tipping isn't defined anywhere until here. Probably best to call this "rate dependent tipping".
On a side-note: there is no educational resources (to my knowledge) that properly teaches R-tipping to a non-PhD. The best attempt is a paper from Ritchie et al 2023, but it is still advanced (research article). I am currently working on one which will come in v2 of my textbook. I will try to upload some notes on GitHub in the meantime to cite here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also find Wieczorek et al. 2023 pretty good and understandable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will introduce R-tipping more high-level in the docs (see https://juliadynamics.github.io/CriticalTransitions.jl/dev/man/CoupledSDEs/)