@@ -51,7 +51,7 @@ function liouville_transform(sys::AbstractODESystem; kwargs...)
51
51
end
52
52
53
53
"""
54
- function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, simplify = true, verbose = false, kwargs...)
54
+ function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummies = false, simplify = true, fold = false, kwargs...)
55
55
56
56
Transform the independent variable (e.g. ``t``) of the ODE system `sys` to a dependent variable `iv` (e.g. ``f(t)``).
57
57
An equation in `sys` must define the rate of change of the new independent variable (e.g. ``df(t)/dt``).
@@ -64,7 +64,7 @@ Keyword arguments
64
64
=================
65
65
If `dummies`, derivatives of the new independent variable are expressed through dummy equations; otherwise they are explicitly inserted into the equations.
66
66
If `simplify`, these dummy expressions are simplified and often give a tidier transformation.
67
- If `verbose `, the function prints intermediate transformations of equations to aid debugging .
67
+ If `fold `, internal substitutions will evaluate numerical expressions .
68
68
Any additional keyword arguments `kwargs...` are forwarded to the constructor that rebuilds the system.
69
69
70
70
Usage before structural simplification
@@ -89,118 +89,94 @@ julia> unknowns(M′)
89
89
y(x)
90
90
```
91
91
"""
92
- function change_independent_variable (sys:: AbstractODESystem , iv, eqs = []; dummies = false , simplify = true , verbose = false , kwargs... )
92
+ function change_independent_variable (sys:: AbstractODESystem , iv, eqs = []; dummies = false , simplify = true , fold = false , kwargs... )
93
+ iv2_of_iv1 = unwrap (iv) # e.g. u(t)
94
+ iv1 = get_iv (sys) # e.g. t
95
+
93
96
if ! iscomplete (sys)
94
- error (" Cannot change independent variable of incomplete system $(nameof (sys)) " )
97
+ error (" System $(nameof (sys)) is incomplete. Complete it first! " )
95
98
elseif isscheduled (sys)
96
- error (" Cannot change independent variable of structurally simplified system $(nameof (sys)) " )
99
+ error (" System $(nameof (sys)) is structurally simplified. Change independent variable before structural simplification! " )
97
100
elseif ! isempty (get_systems (sys))
98
- error (" Cannot change independent variable of hierarchical system $(nameof (sys)) . Flatten it first." ) # TODO : implement
101
+ error (" System $(nameof (sys)) is hierarchical. Flatten it first!" ) # TODO : implement and allow?
102
+ elseif ! iscall (iv2_of_iv1) || ! isequal (only (arguments (iv2_of_iv1)), iv1)
103
+ error (" Variable $iv is not a function of the independent variable $iv1 of the system $(nameof (sys)) !" )
99
104
end
100
105
101
- iv = unwrap (iv)
102
- iv1 = get_iv (sys) # e.g. t
103
- if ! iscall (iv) || ! isequal (only (arguments (iv)), iv1)
104
- error (" New independent variable $iv is not a function of the independent variable $iv1 of the system $(nameof (sys)) " )
105
- elseif ! isautonomous (sys) && isempty (findall (eq -> isequal (eq. lhs, iv1), eqs))
106
- error (" System $(nameof (sys)) is autonomous in $iv1 . An equation of the form $iv1 ~ F($iv ) must be provided." )
106
+ iv1name = nameof (iv1) # e.g. :t
107
+ iv2name = nameof (operation (iv2_of_iv1)) # e.g. :u
108
+ iv2, = @independent_variables $ iv2name # e.g. u
109
+ iv1_of_iv2, = @variables $ iv1name (iv2) # inverse in case sys is autonomous; e.g. t(u)
110
+ D1 = Differential (iv1) # e.g. d/d(t)
111
+ D2 = Differential (iv2_of_iv1) # e.g. d/d(u(t))
112
+
113
+ # 1) Utility that performs the chain rule on an expression, e.g. (d/dt)(f(t)) -> (d/dt)(f(u(t))) -> df(u(t))/du(t) * du(t)/dt
114
+ function chain_rule (ex)
115
+ vars = get_variables (ex)
116
+ for var_of_iv1 in vars # loop through e.g. f(t)
117
+ if iscall (var_of_iv1) && ! isequal (var_of_iv1, iv2_of_iv1) # handle e.g. f(t) -> f(u(t)), but not u(t) -> u(u(t))
118
+ varname = nameof (operation (var_of_iv1)) # e.g. :f
119
+ var_of_iv2, = @variables $ varname (iv2_of_iv1) # e.g. f(u(t))
120
+ ex = substitute (ex, var_of_iv1 => var_of_iv2; fold) # e.g. f(t) -> f(u(t))
121
+ end
122
+ end
123
+ ex = expand_derivatives (ex, simplify) # expand chain rule, e.g. (d/dt)(f(u(t)))) -> df(u(t))/du(t) * du(t)/dt
124
+ return ex
107
125
end
108
126
109
- iv2func = iv # e.g. a(t)
110
- iv2name = nameof (operation (iv))
111
- iv2, = @independent_variables $ iv2name # e.g. a
112
- D1 = Differential (iv1)
113
-
114
- iv1name = nameof (iv1) # e.g. t
115
- iv1func, = @variables $ iv1name (iv2) # e.g. t(a)
116
-
117
- eqs = [get_eqs (sys); eqs] # copies system equations to avoid modifying original system
118
-
119
- # 1) Find and compute all necessary expressions for e.g. df/dt, d²f/dt², ...
120
- # 1.1) Find the 1st order derivative of the new independent variable (e.g. da(t)/dt = ...), ...
121
- div2_div1_idxs = findall (eq -> isequal (eq. lhs, D1 (iv2func)), eqs) # index of e.g. da/dt = ...
122
- if length (div2_div1_idxs) != 1
123
- error (" Exactly one equation for $D1 ($iv2func ) was not specified." )
127
+ # 2) Find e.g. du/dt in equations, then calculate e.g. d²u/dt², ...
128
+ eqs = [get_eqs (sys); eqs] # all equations (system-defined + user-provided) we may use
129
+ idxs = findall (eq -> isequal (eq. lhs, D1 (iv2_of_iv1)), eqs)
130
+ if length (idxs) != 1
131
+ error (" Exactly one equation for $D1 ($iv2_of_iv1 ) was not specified!" )
124
132
end
125
- div2_div1_eq = popat! (eqs, only (div2_div1_idxs )) # get and remove e.g. df /dt = ... (may be added back later)
126
- div2_div1 = div2_div1_eq . rhs
127
- if isequal (div2_div1 , 0 )
128
- error (" Cannot change independent variable from $iv1 to $iv2 with singular transformation $div2_div1_eq . " )
133
+ div2_of_iv1_eq = popat! (eqs, only (idxs )) # get and remove e.g. du /dt = ... (may be added back later as a dummy )
134
+ div2_of_iv1 = chain_rule (div2_of_iv1_eq . rhs)
135
+ if isequal (div2_of_iv1 , 0 ) # e.g. du/dt ~ 0
136
+ error (" Independent variable transformation $(div2_of_iv1_eq) is singular! " )
129
137
end
130
- # 1.2) ... then compute the 2nd order derivative of the new independent variable
131
- div1_div2 = 1 / div2_div1 # TODO : URL reference for clarity
132
- ddiv2_ddiv1 = expand_derivatives (- Differential (iv2func)(div1_div2) / div1_div2^ 3 , simplify) # e.g. https://math.stackexchange.com/questions/249253/second-derivative-of-the-inverse-function # TODO : higher orders # TODO : pass simplify here
133
- # 1.3) # TODO : handle higher orders (3+) derivatives ...
138
+ ddiv2_of_iv1 = chain_rule (D1 (div2_of_iv1)) # TODO : implement higher orders (order >= 3) derivatives with a loop
134
139
135
- # 2 ) If requested, insert extra dummy equations for e.g. df /dt, d²f /dt², ...
140
+ # 3 ) If requested, insert extra dummy equations for e.g. du /dt, d²u /dt², ...
136
141
# Otherwise, replace all these derivatives by their explicit expressions
137
142
if dummies
138
- div2name = Symbol (iv2name, :_t ) # TODO : not always t
139
- div2, = @variables $ div2name (iv2) # e.g. a_t(a)
140
- ddiv2name = Symbol (iv2name, :_tt ) # TODO : not always t
141
- ddiv2, = @variables $ ddiv2name (iv2) # e.g. a_tt(a)
142
- eqs = [eqs; [div2 ~ div2_div1, ddiv2 ~ ddiv2_ddiv1]] # add dummy equations
143
- derivsubs = [D1 (D1 (iv2func)) => ddiv2, D1 (iv2func) => div2] # order is crucial!
143
+ div2name = Symbol (iv2name, :_ , iv1name) # e.g. :u_t # TODO : customize
144
+ ddiv2name = Symbol (div2name, iv1name) # e.g. :u_tt # TODO : customize
145
+ div2, ddiv2 = @variables $ div2name (iv2) $ ddiv2name (iv2) # e.g. u_t(u), u_tt(u)
146
+ eqs = [eqs; [div2 ~ div2_of_iv1, ddiv2 ~ ddiv2_of_iv1]] # add dummy equations
144
147
else
145
- derivsubs = [D1 (D1 (iv2func)) => ddiv2_ddiv1, D1 (iv2func) => div2_div1] # order is crucial!
146
- end
147
- derivsubs = [derivsubs; [iv2func => iv2, iv1 => iv1func]]
148
-
149
- if verbose
150
- # Explain what we just did
151
- println (" Order 1 (found): $div2_div1_eq " )
152
- println (" Order 2 (computed): $(D1 (div2_div1_eq. lhs) ~ ddiv2_ddiv1) " )
153
- println ()
154
- println (" Substitutions will be made in this order:" )
155
- for (n, sub) in enumerate (derivsubs)
156
- println (" $n : $(sub[1 ]) => $(sub[2 ]) " )
157
- end
158
- println ()
148
+ div2 = div2_of_iv1
149
+ ddiv2 = ddiv2_of_iv1
159
150
end
160
151
161
- # 3) Define a transformation function that performs the change of variable on any expression/equation
152
+ # 4) Transform everything from old to new independent variable, e.g. t -> u.
153
+ # Substitution order matters! Must begin with highest order to get D(D(u(t))) -> u_tt(u).
154
+ # If we had started with the lowest order, we would get D(D(u(t))) -> D(u_t(u)) -> 0!
155
+ iv1_to_iv2_subs = [ # a vector ensures substitution order
156
+ D1 (D1 (iv2_of_iv1)) => ddiv2 # order 2, e.g. D(D(u(t))) -> u_tt(u)
157
+ D1 (iv2_of_iv1) => div2 # order 1, e.g. D(u(t)) -> u_t(u)
158
+ iv2_of_iv1 => iv2 # order 0, e.g. u(t) -> u
159
+ iv1 => iv1_of_iv2 # in case sys was autonomous, e.g. t -> t(u)
160
+ ]
162
161
function transform (ex)
163
- verbose && println (" Step 0: " , ex)
164
-
165
- # Step 1: substitute f(t₁) => f(t₂(t₁)) in all variables in the expression
166
- vars = Symbolics. get_variables (ex)
167
- for var1 in vars
168
- if Symbolics. iscall (var1) && ! isequal (var1, iv2func) # && isequal(only(arguments(var1)), iv1) # skip e.g. constants
169
- name = nameof (operation (var1))
170
- var2, = @variables $ name (iv2func)
171
- ex = substitute (ex, var1 => var2; fold = false )
172
- end
173
- end
174
- verbose && println (" Step 1: " , ex)
175
-
176
- # Step 2: expand out all chain rule derivatives
177
- ex = expand_derivatives (ex) # expand out with chain rule to get d(iv2)/d(iv1)
178
- verbose && println (" Step 2: " , ex)
179
-
180
- # Step 3: substitute d²f/dt², df/dt, ... (to dummy variables or explicit expressions, depending on dummies)
181
- for sub in derivsubs
182
- ex = substitute (ex, sub)
162
+ ex = chain_rule (ex)
163
+ for sub in iv1_to_iv2_subs
164
+ ex = substitute (ex, sub; fold)
183
165
end
184
- verbose && println (" Step 3: " , ex)
185
- verbose && println ()
186
-
187
166
return ex
188
167
end
189
-
190
- # 4) Transform all fields
191
- eqs = map (transform, eqs)
168
+ eqs = map (transform, eqs) # we derived and added equations to eqs; they are not in get_eqs(sys)!
192
169
observed = map (transform, get_observed (sys))
193
170
initialization_eqs = map (transform, get_initialization_eqs (sys))
194
171
parameter_dependencies = map (transform, get_parameter_dependencies (sys))
195
172
defaults = Dict (transform (var) => transform (val) for (var, val) in get_defaults (sys))
196
173
guesses = Dict (transform (var) => transform (val) for (var, val) in get_guesses (sys))
197
174
assertions = Dict (transform (condition) => msg for (condition, msg) in get_assertions (sys))
198
- # TODO : handle subsystems
199
175
200
176
# 5) Recreate system with transformed fields
201
177
return typeof (sys)(
202
178
eqs, iv2;
203
179
observed, initialization_eqs, parameter_dependencies, defaults, guesses, assertions,
204
180
name = nameof (sys), description = description (sys), kwargs...
205
- ) |> complete # original system had to be complete
181
+ ) |> complete # input system must be complete, so complete the output system
206
182
end
0 commit comments