odenonn.red 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. module odenonn$ % Special form nonlinear ODEs of order > 1
  2. % F.J.Wright@maths.qmw.ac.uk, Time-stamp: <14 August 2001>
  3. % Trivial order reduction.
  4. % Special cases of Lie symmetry, namely
  5. % autonomous, equidimensional and scale invariant equations.
  6. % Simplification of arbitrary constants.
  7. % TO DO:
  8. % avoid computing orders in both reduce and shift
  9. algebraic procedure ODESolve!-nonlinearn(ode, y, x);
  10. %% `symbolic' mode here is ESSENTIAL, otherwise the code generated
  11. %% is as if this were a macro, except then it does not get eval'ed!
  12. symbolic ODENon!-Reduce!-Order(ode, y, x)$
  13. %% The following defines are used to allow easy changes to the calling
  14. %% sequence.
  15. define ODENon!-Reduce!-Order!-Next = ODESolve!-Shift$
  16. %% Shifting currently NEEDED for Zimmer (8) (only)!
  17. define ODESolve!-Shift!-Next = ODESolve!-nonlinearn!*1$
  18. switch odesolve_equidim_y$ % TEMPORARY?
  19. symbolic(!*odesolve_equidim_y := t)$ % TEMPORARY?
  20. algebraic procedure ODESolve!-nonlinearn!*1(ode, y, x);
  21. %% The order here seems to be important in practice:
  22. symbolic or(
  23. ODESolve!-Autonomous(ode, y, x),
  24. ODESolve!-ScaleInv(ode, y, x), % includes equidim in x
  25. !*odesolve_equidim_y and
  26. ODESolve!-Equidim!-y(ode, y, x) )$
  27. algebraic procedure ODENon!-Reduce!-Order(ode, y, x);
  28. %% If ode does not explicitly involve y and low order derivatives
  29. %% then simplify by reducing the effective order (unless there is
  30. %% only one) and then try to solve the reduced ode directly to give
  31. %% a first integral. Applies only to odes of order > 1.
  32. begin scalar deriv_orders, min_order, max_order;
  33. traceode1 "Trying trivial order reduction ...";
  34. deriv_orders := get_deriv_orders(ode, y);
  35. %% Check for purely algebraic factor from some simplification,
  36. %% such as autonomous reduction:
  37. if deriv_orders = {} or deriv_orders = {0} then
  38. return {ode = 0}; % purely algebraic!
  39. %% Avoid reduction to a purely algebraic equation:
  40. if (min_order := min deriv_orders) = 0 or
  41. length deriv_orders = 1 then return
  42. ODENon!-Reduce!-Order!-Next(ode, y, x);
  43. max_order := max deriv_orders;
  44. ode := sub(df = odesolve!-df, ode);
  45. for ord := min_order : max_order do
  46. ode := if ord = 1 then (ode where odesolve!-df(y,x) => y)
  47. else (ode where odesolve!-df(y,x,ord) =>
  48. odesolve!-df(y,x,ord-min_order));
  49. ode := sub(odesolve!-df = df, ode);
  50. traceode "Performing trivial order reduction to give ",
  51. "the order ", max_order - min_order, " nonlinear ODE: ",
  52. ode = 0;
  53. ode := symbolic(
  54. (if max_order - min_order = 1 then % first order
  55. ODESolve!-nonlinear1(ode, y, x)
  56. else
  57. ODENon!-Reduce!-Order!-Next(ode, y, x))
  58. where !*odesolve_explicit = t);
  59. if not ode then <<
  60. traceode "Cannot solve order-reduced ODE!";
  61. return % abandon solution
  62. >>;
  63. %% ode := sub(y = df(y,x,min_order), ode);
  64. traceode "Solution of order-reduced ODE is ", ode;
  65. traceode "Restoring order, ", y => df(y,x,min_order),
  66. ", to give: ", sub(y = df(y,x,min_order), ode),
  67. " and re-solving ...";
  68. ode := for each soln in ode join
  69. %% Each `soln' here is an EQUATION for y that may be
  70. %% implicit.
  71. if lhs soln = y then % explicit
  72. { y = ODESolve!-multi!-int!*(rhs soln, x, min_order) }
  73. else <<
  74. soln := solve(soln, y);
  75. for each s in soln collect
  76. if lhs s = y then % explicit
  77. y = ODESolve!-multi!-int!*(rhs s, x, min_order)
  78. else % implicit
  79. %% leave unsolved for now
  80. sub(y = df(y,x,min_order), s)
  81. >>;
  82. return ODESolve!-Simp!-ArbConsts(ode, y, x)
  83. end$
  84. algebraic procedure ODESolve!-multi!-int!*(y, x, m);
  85. %% Integate y wrt x m times and add arbitrary constants:
  86. ODESolve!-multi!-int(y, x, m) +
  87. %% << >> below is NECESSARY to force immediate evaluation!
  88. for i := 0 : m-1 sum <<newarbconst()>>*x^i$
  89. % Internal wrapper function for ODESolve!-Shift:
  90. algebraic operator odesolve!-sub!*$
  91. algebraic procedure ODESolve!-Shift(ode, y, x);
  92. %% A first attempt at canonicalizing an ODE by shifting the
  93. %% independent variable.
  94. symbolic if not !*odesolve_fast then % heuristic solution
  95. algebraic begin scalar deriv_orders, a, c, d;
  96. traceode1 "Looking for an independent variable shift ...";
  97. deriv_orders := get_deriv_orders(ode, y);
  98. deriv_orders := sort(deriv_orders, >);
  99. %% Try to find a non-trivial "coefficient" polynomial
  100. %% constituent c that is linear in x.
  101. while deriv_orders neq {} and
  102. (c := lcof(ode, df(y,x,first deriv_orders))) freeof x do
  103. deriv_orders := rest deriv_orders;
  104. if deriv_orders = {} then % not shiftable
  105. return ODESolve!-Shift!-Next(ode, y, x);
  106. if (d := deg(c, x)) neq 1 then <<
  107. c := decompose c;
  108. while (c := rest c) neq {} and deg(rhs first c, x) neq 1 do;
  109. %% << null loop body >>
  110. if c neq {} then c := rhs first c;
  111. if deg(c, x) neq 1 then % not shiftable
  112. return ODESolve!-Shift!-Next(ode, y, x)
  113. >>;
  114. %% c = ax + b is a linear component polynomial of the ode
  115. %% coefficients.
  116. if not(c freeof y) or not((c := coeff(c,x)) freeof x) or
  117. first c = 0 then % not shiftable
  118. return ODESolve!-Shift!-Next(ode, y, x);
  119. c := first c / (a := second c);
  120. %% ode is a function of ax + b (= a(x + c)), so ...
  121. ode := sub(x = x - c, ode) / a^d;
  122. %% This will leave sub(..., df(...)) symbolic, so ...
  123. ode := num sub(sub = odesolve!-sub!*, ode);
  124. ode := (ode where odesolve!-sub!*(~a! ,~b! ) => b! );
  125. traceode "This ODE can be simplified by the ",
  126. "independent variable shift ", x => x - c,
  127. " to give: ", ode = 0;
  128. ode := ODESolve!-Shift!-Next(ode, y, x);
  129. if ode then return
  130. for each soln in ode collect
  131. if symbolic rlistp soln then % parametric solution
  132. for each s in soln collect
  133. if symbolic eqcar(s, 'equal) and lhs s = x
  134. then x = rhs s - c else s
  135. else sub(x = x + c, soln)
  136. end$
  137. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  138. % Autonomous, equidimensional and scale-invariant ODEs
  139. % ====================================================
  140. algebraic procedure ODESolve!-Autonomous(ode, y, x);
  141. %% If ODE is autonomous, i.e. x does not appear explicitly, then
  142. %% reduce the order by using y as independent variable and then try
  143. %% to solve the reduced ODE directly. Applies only to ODEs of
  144. %% order > 1. Do not apply to a linear ODE, because it will become
  145. %% nonlinear!
  146. begin scalar ode1, u, soln;
  147. traceode1 "Testing whether ODE is autonomous ...";
  148. ode1 := (ode where df(y,x) => 1, df(y,x,~n) => 1);
  149. if smember(x, ode1) then return; % not autonomous
  150. u := gensym();
  151. symbolic depend1(u,x,t); symbolic depend1(u,y,t);
  152. ode := (ode where df(y,x) => u, df(y,x,~n) => df(u,x,n-1));
  153. ode := (ode where df(u,x,~n) => u*df(df(u,x,n-1),y) when n > 1,
  154. %% above condition n > 1 is NECESSARY!
  155. df(u,x) => u*df(u,y));
  156. symbolic depend1(u,x,nil);
  157. traceode
  158. "This ODE is autonomous -- transforming dependent variable ",
  159. "to derivative to give this ODE of order 1 lower: ", ode = 0;
  160. ode := symbolic(ODESolve!*1(ode, u, y)
  161. where !*odesolve_explicit = t);
  162. if not ode then <<
  163. symbolic depend1(u,y,nil);
  164. traceode "Cannot solve transformed autonomous ODE!";
  165. return
  166. >>;
  167. ode := sub(u = df(y,x), ode);
  168. symbolic depend1(u,y,nil);
  169. traceode "Restoring order to give these first-order ODEs ...";
  170. soln := {};
  171. a: if ode neq {} then
  172. if (u := ODESolve!-FirstOrder(first ode, y, x)) then <<
  173. soln := append(soln, u);
  174. ode := rest ode;
  175. go to a
  176. >> else <<
  177. traceode "Cannot solve one of the first-order ODEs ",
  178. "arising from solution of transformed autonomous ODE!";
  179. return
  180. >>;
  181. return ODESolve!-Simp!-ArbConsts(soln, y, x)
  182. end$
  183. algebraic procedure ODESolve!-ScaleInv(ode, y, x);
  184. %% If ODE is scale invariant, i.e. invariant under x -> a x, y ->
  185. %% a^p y, then transform it to an equidimensional-in-x ODE and try
  186. %% to solve it. If p = 0 then it is already equidimensional-in-x
  187. %% as a special case. Returns a solution or nil if this method
  188. %% does not lead to a solution. PROBABLY NOT USEFUL FOR LINEAR
  189. %% ODES.
  190. begin scalar u, p, ode1, pow, !*allfac;
  191. traceode1 "Testing whether ODE is scale invariant or ",
  192. "equidimensional in the independent variable ", x, " ...";
  193. u := gensym(); p := gensym();
  194. ode1 := (ode where df(y,x,~n) => mkid(u,n)*x^(p-n),
  195. df(y,x) => mkid(u,1)*x^(p-1));
  196. %% mkid's to avoid spurious cancellations.
  197. ode1 := num sub(y = u*x^p, ode1);
  198. %% Try to choose p to make ode1 proportional to some single
  199. %% power of x. Assume ode1 is a sum of terms.
  200. begin scalar part1, n_parts;
  201. part1 := part(ode1, 1);
  202. n_parts := arglength ode1; % must be at least 2 terms
  203. for i := 2 : n_parts do <<
  204. parti := part(ode1, i)/part1;
  205. pow := df(parti, x)*x/parti;
  206. if pow then << pow := solve(pow, p); n_parts := 0 >>
  207. >>;
  208. if n_parts then
  209. %% Scale invariant for ANY p =>
  210. %% equidimensional in both x and y
  211. return pow := {p=0};
  212. ode1 := (ode1 - part1)/part1;
  213. while pow neq {} and % check scale invariance
  214. (symbolic eqcar(caddr cadr pow, 'root_of) or
  215. not(sub(first pow, ode1) freeof x)) do
  216. pow := rest pow
  217. end;
  218. if pow = {} then return; % not scale invariant
  219. if not(p := rhs first pow) then
  220. %% Scale invariant for p=0 =>
  221. %% equidimensional in x ...
  222. return ODESolve!-ScaleInv!-Equidim!-x(ode, y, x);
  223. %% ode is scale invariant (with p neq 0)
  224. symbolic depend1(u, x, t);
  225. ode := sub(y = x^p*u, ode);
  226. traceode "This ODE is scale invariant -- applying ", y => x^p*u,
  227. " to transform to the simpler ODE: ", ode = 0;
  228. ode := ODESolve!-ScaleInv!-Equidim!-x(ode, u, x);
  229. symbolic depend1(u, x, nil);
  230. if ode then return sub(u = y/x^p, ode);
  231. traceode "Cannot solve transformed scale invariant ODE!"
  232. end$
  233. algebraic procedure ODESolve!-ScaleInv!-Equidim!-x(ode, y, x);
  234. %% ODE is equidimensional in x, i.e. invariant under x -> ax, so
  235. %% transform it to an autonomous ODE and try to solve it. (This
  236. %% includes "reduced" Euler equations as a special case. Could
  237. %% ignore terms independent of y in testing equidimensionality; if
  238. %% there are such terms then the simplified ODE will not be
  239. %% autonomous. This generalization includes ALL Euler equations.)
  240. %% Returns a solution or nil if this method does not lead to a
  241. %% solution.
  242. begin scalar tt, exp_tt;
  243. tt := gensym();
  244. %% ode is equidimensional in x; x -> exp(tt):
  245. exp_tt := exp(tt);
  246. symbolic depend1(y,tt,t);
  247. ode := (ode where df(y,x) => df(y,tt)/exp_tt,
  248. df(y,x,~n) => df(df(y,x,n-1),tt)/exp_tt when
  249. numberp n and n > 0); % n > 0 condition is necessary!
  250. ode := num sub(x = exp_tt, ode);
  251. traceode
  252. "This ODE is equidimensional in the independent variable ",
  253. x, " -- applying ", x => exp_tt,
  254. " to transform to the simpler ODE: ",
  255. ode = 0;
  256. symbolic depend1(y,x,nil); % Necessary to avoid dependence loops
  257. %% ode should be autonomous PROVIDED no term independent of y
  258. ode := symbolic ODESolve!-Autonomous(ode, y, tt);
  259. symbolic depend1(y,x,t); %%% ???
  260. symbolic depend1(y,tt,nil);
  261. if ode then return sub(tt = log x, ode);
  262. traceode "Cannot solve transformed equidimensional ODE!"
  263. end$
  264. algebraic procedure ODESolve!-Equidim!-y(ode, y, x);
  265. %% If ODE is equidimensional in y, i.e. invariant under y -> ay,
  266. %% then simplify the ODE and try to solve the result. Returns a
  267. %% solution or nil if this method does not lead to a solution. Do
  268. %% not apply to a linear ODE, which is trivially equidimensional in
  269. %% y, because it will become nonlinear!
  270. begin scalar ode1, u, exp_u;
  271. traceode1 "Testing whether ODE is equidimensional in ",
  272. "the dependent variable ", y, " ...";
  273. u := gensym(); % to avoid spurious cancellations
  274. ode1 := (ode where df(y,x,~n) => y*mkid(u,n),
  275. df(y,x) => y*u);
  276. %% ode1 must be proportional to some single positive integer
  277. %% power of y:
  278. if reduct(ode1, y) or depends(lcof(ode1, y), y) then return;
  279. %% ode is equidimensional in y; y -> exp(u):
  280. exp_u := exp(u);
  281. symbolic depend1(u,x,t);
  282. ode := lcof(num sub(y = exp_u, ode), exp_u);
  283. %% (Lcof above to remove irrelevant factor of a power of y.)
  284. traceode
  285. "This ODE is equidimensional in the dependent variable ",
  286. y, " -- applying ", y => exp_u,
  287. " to transform to the simpler ODE: ",
  288. ode = 0;
  289. %% ode here could be ANY kind of ode. It should be less
  290. %% nonlinear, but I don't think there is any guarantee that it
  291. %% will be linear -- is there? Hence we must call the full
  292. %% general ode solver again:
  293. ode := ODESolve!*1(ode, u, x);
  294. symbolic depend1(u,x,nil);
  295. if not ode then <<
  296. traceode "Cannot solve transformed equidimensional ODE!";
  297. return
  298. >>;
  299. return for each soln in ode collect
  300. if lhs soln = u then y = exp rhs soln % retain explicit soln
  301. else sub(u = log y, ode)
  302. end$
  303. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  304. % Simplification of Arbitrary Constants
  305. % =====================================
  306. algebraic procedure ODESolve!-Simp!-ArbConsts(solns, y, x);
  307. %% To be applied to non-parametric solutions of ODEs containing
  308. %% arbconsts introduced earlier.
  309. for each soln in solns collect
  310. if symbolic rlistp soln then soln else
  311. (ODESolve!-Simp!-ArbConsts1(lhs soln, y, x) =
  312. ODESolve!-Simp!-ArbConsts1(rhs soln, y, x))$
  313. algebraic procedure ODESolve!-Simp!-ArbConsts1(soln, y, x);
  314. %% Simplify arbconst expressions within soln. Messy arbconst
  315. %% expressions can be introduced by the integrator from simple
  316. %% arbconsts, and there would appear to be no way to avoid this
  317. %% other than to remove them after the event.
  318. begin scalar !*precise, ss, acexprns;
  319. if not(ss := ODESolve!-Structr(soln, x, y, 'arbconst))
  320. then return soln;
  321. acexprns := rest ss; ss := first ss;
  322. traceode "Simplifying the arbconst expressions in ", soln,
  323. " by the rewrites ...";
  324. for each s in acexprns do <<
  325. %% s has the form ansj = "expression in arbconst(n)"
  326. %% MAY NEED TO CHECK ONLY 1 ARBCONST?
  327. %% n!* must be a global algebraic variable!
  328. rhs s where arbconst(~n) => (n!* := n);
  329. traceode rhs s, " => ", arbconst(n!*); % to evaluate `rhs'
  330. %% Remove other occurrences of arbconst(n!*):
  331. ss := sub(solve(s, arbconst(n!*)), ss);
  332. %% Finally rename ansj as arbconst(n):
  333. ss := sub(lhs s = arbconst(n!*), ss)
  334. >>;
  335. return ss
  336. end$
  337. symbolic operator ODESolve!-Structr$
  338. %% symbolic procedure ODESolve!-Structr(u, x, y, arbop);
  339. %% %% Return an rlist consisting of an expression involving variables
  340. %% %% ansj representing sub-structures followed by equations of the
  341. %% %% form ansj = sub-structure, where the sub-structures depend
  342. %% %% non-trivially on the arbitrary opertor arbop, essentially in the
  343. %% %% format returned by structr, or nil if this decomposition is not
  344. %% %% possible.
  345. %% begin scalar !*savestructr, !*precise, ss, arbexprns;
  346. %% !*savestructr := t;
  347. %% ss := cdr structr u; % rlistat; ss = (exprn eqns)
  348. %% if null cdr ss then return;
  349. %% %% Ignore "structures" of the form ansj = arbop(i)
  350. %% ss := car ss . for each s in cdr ss join
  351. %% if eqcar(caddr(s:=reval s), arbop)
  352. %% then << arbexprns := s . arbexprns; nil >>
  353. %% else {s};
  354. %% %% by substituting them back into the structure list:
  355. %% if arbexprns then
  356. %% ss := cdr subeval nconc(arbexprns, {makelist ss});
  357. %%
  358. %% %% Get simplifiable arbop expressions:
  359. %% arbexprns := nil;
  360. %% ss := car ss . for each s in cdr ss join
  361. %% begin scalar rhs_s; rhs_s := caddr s;
  362. %% return if smember(arbop, rhs_s) and
  363. %% not(depends(rhs_s, x) or depends(rhs_s, y))
  364. %% then << arbexprns := s . arbexprns; nil >>
  365. %% else {s}
  366. %% end;
  367. %% if null arbexprns then return;
  368. %% %% Rebuild the rest of the stucture as ss:
  369. %% ss := if cdr ss then
  370. %% subeval nconc(cdr ss, {car ss})
  371. %% else car ss;
  372. %% return makelist(ss . arbexprns)
  373. %% end$
  374. symbolic procedure ODESolve!-Structr(u, x, y, arbop);
  375. %% Return an rlist representing U that consists of an expression
  376. %% involving variables `ansj' representing sub-structures followed
  377. %% by equations of the form `ansj = sub-structure', where the
  378. %% sub-structures depend non-trivially on the arbitrary opertor
  379. %% ARBOP and do not depend on X or Y, essentially in the format
  380. %% returned by structr, or nil if this decomposition is not
  381. %% possible.
  382. begin scalar !*savestructr, !*precise, ss, arbexprns;
  383. !*savestructr := t;
  384. ss := cdr structr u; % rlistat; ss = (exprn eqns)
  385. if null cdr ss then return;
  386. %% Ignore trivial structure of the form ansj = arbop(i) by
  387. %% substituting it back into the structure list:
  388. ss := car ss . for each s in cdr ss join
  389. if eqcar(caddr(s:=reval s), arbop)
  390. then << arbexprns := s . arbexprns; nil >>
  391. else {s};
  392. if null cdr ss then return;
  393. if arbexprns then
  394. ss := cdr subeval nconc(arbexprns, {makelist ss});
  395. %% Ignore structure that does not depend on arbop by
  396. %% substituting it back into the structure list:
  397. arbexprns := nil;
  398. ss := car ss . for each s in cdr ss join
  399. if not smember(arbop, s)
  400. then << arbexprns := s . arbexprns; nil >>
  401. else {s};
  402. if null cdr ss then return;
  403. if arbexprns then
  404. ss := cdr subeval nconc(arbexprns, {makelist ss});
  405. %% Ignore all structure that depends on x or y by repeatedly
  406. %% substituting it back into the structure list:
  407. arbexprns := t;
  408. while arbexprns and cdr ss do <<
  409. arbexprns := nil;
  410. ss := car ss . for each s in cdr ss join
  411. if depends(s, x) or depends(s, y)
  412. then << arbexprns := s . arbexprns; nil >>
  413. else {s};
  414. if arbexprns then
  415. ss := cdr subeval nconc(arbexprns, {makelist ss})
  416. >>;
  417. if null cdr ss then return;
  418. return makelist ss
  419. end$
  420. endmodule$
  421. end$