@@ -110,20 +110,59 @@ function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummi
110
110
iv2name = nameof (operation (iv))
111
111
iv2, = @independent_variables $ iv2name # e.g. a
112
112
D1 = Differential (iv1)
113
- D2 = Differential (iv2)
114
113
115
114
iv1name = nameof (iv1) # e.g. t
116
115
iv1func, = @variables $ iv1name (iv2) # e.g. t(a)
117
116
118
- div2name = Symbol (iv2name, :_t )
119
- div2, = @variables $ div2name (iv2) # e.g. a_t(a)
117
+ eqs = [get_eqs (sys); eqs] # copies system equations to avoid modifying original system
120
118
121
- ddiv2name = Symbol (iv2name, :_tt )
122
- ddiv2, = @variables $ ddiv2name (iv2) # e.g. a_tt(a)
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." )
124
+ 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 ." )
129
+ 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 ...
134
+
135
+ # 2) If requested, insert extra dummy equations for e.g. df/dt, d²f/dt², ...
136
+ # Otherwise, replace all these derivatives by their explicit expressions
137
+ 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!
144
+ 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 ()
159
+ end
123
160
161
+ # 3) Define a transformation function that performs the change of variable on any expression/equation
124
162
function transform (ex)
125
- # 1) Substitute f(t₁) => f(t₂(t₁)) in all variables
126
- verbose && println (" 1. " , ex)
163
+ verbose && println (" Step 0: " , ex)
164
+
165
+ # Step 1: substitute f(t₁) => f(t₂(t₁)) in all variables in the expression
127
166
vars = Symbolics. get_variables (ex)
128
167
for var1 in vars
129
168
if Symbolics. iscall (var1) && ! isequal (var1, iv2func) # && isequal(only(arguments(var1)), iv1) # skip e.g. constants
@@ -132,53 +171,36 @@ function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummi
132
171
ex = substitute (ex, var1 => var2; fold = false )
133
172
end
134
173
end
174
+ verbose && println (" Step 1: " , ex)
135
175
136
- # 2) Substitute in dummy variables for dⁿt₂/dt₁ⁿ
137
- verbose && println (" 2. " , ex)
176
+ # Step 2: expand out all chain rule derivatives
138
177
ex = expand_derivatives (ex) # expand out with chain rule to get d(iv2)/d(iv1)
139
- verbose && println (" 3. " , ex)
140
- ex = substitute (ex, D1 (D1 (iv2func)) => ddiv2) # order 2 # TODO : more orders
141
- ex = substitute (ex, D1 (iv2func) => div2) # order 1; e.g. D(a(t)) => a_t(t)
142
- ex = substitute (ex, iv2func => iv2) # order 0; make iv2 independent
143
- verbose && println (" 4. " , ex)
144
- ex = substitute (ex, iv1 => iv1func) # if autonomous
145
- verbose && println (" 5. " , ex)
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)
183
+ end
184
+ verbose && println (" Step 3: " , ex)
185
+ verbose && println ()
186
+
146
187
return ex
147
188
end
148
189
149
- eqs = [ get_eqs (sys); eqs]
190
+ # 4) Transform all fields
150
191
eqs = map (transform, eqs)
151
-
192
+ observed = map (transform, get_observed (sys))
152
193
initialization_eqs = map (transform, get_initialization_eqs (sys))
153
-
154
- div2_div1_idxs = findall (eq -> isequal (eq. lhs, div2), eqs)
155
- if length (div2_div1_idxs) == 0
156
- error (" No equation for $D1 ($iv2func ) was specified." )
157
- elseif length (div2_div1_idxs) >= 2
158
- error (" Multiple equations for $D1 ($iv2func ) were specified." )
159
- end
160
- div2_div1 = eqs[only (div2_div1_idxs)]. rhs
161
- if isequal (div2_div1, 0 )
162
- error (" Cannot change independent variable from $iv1 to $iv2 with singular transformation $(div2 ~ div2_div1) ." )
163
- end
164
-
165
- # 3) Add equations for dummy variables
166
- div1_div2 = 1 / div2_div1
167
- ddiv1_ddiv2 = expand_derivatives (- D2 (div1_div2) / div1_div2^ 3 )
168
- if simplify
169
- ddiv1_ddiv2 = Symbolics. simplify (ddiv1_ddiv2)
170
- end
171
- push! (eqs, ddiv2 ~ ddiv1_ddiv2) # e.g. https://math.stackexchange.com/questions/249253/second-derivative-of-the-inverse-function # TODO : higher orders
172
-
173
- # 4) If requested, instead remove and insert dummy equations
174
- if ! dummies # TODO : make dummies = false work with more fields!
175
- dummyidxs = findall (eq -> isequal (eq. lhs, div2) || isequal (eq. lhs, ddiv2), eqs)
176
- dummyeqs = splice! (eqs, dummyidxs) # return and remove dummy equations
177
- dummysubs = Dict (eq. lhs => eq. rhs for eq in dummyeqs)
178
- eqs = substitute .(eqs, Ref (dummysubs)) # don't iterate over dummysubs
179
- end
180
-
181
- # 5) Recreate system with new equations
182
- sys2 = typeof (sys)(eqs, iv2; initialization_eqs, name = nameof (sys), description = description (sys), kwargs... )
183
- return sys2
194
+ parameter_dependencies = map (transform, get_parameter_dependencies (sys))
195
+ defaults = Dict (transform (var) => transform (val) for (var, val) in get_defaults (sys))
196
+ guesses = Dict (transform (var) => transform (val) for (var, val) in get_guesses (sys))
197
+ assertions = Dict (transform (condition) => msg for (condition, msg) in get_assertions (sys))
198
+ # TODO : handle subsystems
199
+
200
+ # 5) Recreate system with transformed fields
201
+ return typeof (sys)(
202
+ eqs, iv2;
203
+ observed, initialization_eqs, parameter_dependencies, defaults, guesses, assertions,
204
+ name = nameof (sys), description = description (sys), kwargs...
205
+ ) |> complete # original system had to be complete
184
206
end
0 commit comments