@@ -110,20 +110,59 @@ function change_independent_variable(sys::AbstractODESystem, iv, eqs = []; dummi
110110 iv2name = nameof (operation (iv))
111111 iv2, = @independent_variables $ iv2name # e.g. a
112112 D1 = Differential (iv1)
113- D2 = Differential (iv2)
114113
115114 iv1name = nameof (iv1) # e.g. t
116115 iv1func, = @variables $ iv1name (iv2) # e.g. t(a)
117116
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
120118
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
123160
161+ # 3) Define a transformation function that performs the change of variable on any expression/equation
124162 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
127166 vars = Symbolics. get_variables (ex)
128167 for var1 in vars
129168 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
132171 ex = substitute (ex, var1 => var2; fold = false )
133172 end
134173 end
174+ verbose && println (" Step 1: " , ex)
135175
136- # 2) Substitute in dummy variables for dⁿt₂/dt₁ⁿ
137- verbose && println (" 2. " , ex)
176+ # Step 2: expand out all chain rule derivatives
138177 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+
146187 return ex
147188 end
148189
149- eqs = [ get_eqs (sys); eqs]
190+ # 4) Transform all fields
150191 eqs = map (transform, eqs)
151-
192+ observed = map (transform, get_observed (sys))
152193 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
184206end
0 commit comments