@@ -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