@@ -51,7 +51,7 @@ function liouville_transform(sys::AbstractODESystem; kwargs...)
5151end 
5252
5353""" 
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...) 
5555
5656Transform the independent variable (e.g. ``t``) of the ODE system `sys` to a dependent variable `iv` (e.g. ``f(t)``). 
5757An 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
6464================= 
6565If `dummies`, derivatives of the new independent variable are expressed through dummy equations; otherwise they are explicitly inserted into the equations. 
6666If `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 . 
6868Any additional keyword arguments `kwargs...` are forwarded to the constructor that rebuilds the system. 
6969
7070Usage before structural simplification 
@@ -89,118 +89,94 @@ julia> unknowns(M′)
8989 y(x) 
9090``` 
9191""" 
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+ 
9396    if  ! iscomplete (sys)
94-         error (" Cannot change independent variable of incomplete system  $(nameof (sys)) " 
97+         error (" System  $(nameof (sys))  is incomplete. Complete it first! " 
9598    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! " 
97100    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)) !" 
99104    end 
100105
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
107125    end 
108126
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!" 
124132    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! " 
129137    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
134139
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², ...
136141    #     Otherwise, replace all these derivatives by their explicit expressions
137142    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
144147    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
159150    end 
160151
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+     ]
162161    function  transform (ex)
163-         verbose &&  println (" Step 0: " 
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: " 
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: " 
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)
183165        end 
184-         verbose &&  println (" Step 3: " 
185-         verbose &&  println ()
186- 
187166        return  ex
188167    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)!
192169    observed =  map (transform, get_observed (sys))
193170    initialization_eqs =  map (transform, get_initialization_eqs (sys))
194171    parameter_dependencies =  map (transform, get_parameter_dependencies (sys))
195172    defaults =  Dict (transform (var) =>  transform (val) for  (var, val) in  get_defaults (sys))
196173    guesses =  Dict (transform (var) =>  transform (val) for  (var, val) in  get_guesses (sys))
197174    assertions =  Dict (transform (condition) =>  msg for  (condition, msg) in  get_assertions (sys))
198-     #  TODO : handle subsystems
199175
200176    #  5) Recreate system with transformed fields
201177    return  typeof (sys)(
202178        eqs, iv2;
203179        observed, initialization_eqs, parameter_dependencies, defaults, guesses, assertions,
204180        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 
206182end 
0 commit comments