odenon1.red 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. module odenon1$ % Special form nonlinear ODEs of order 1
  2. % F.J.Wright@maths.qmw.ac.uk, Time-stamp: <11 August 2001>
  3. % Original author: Malcolm MacCallum
  4. % Basic layout is to test first for well-known special forms, namely:
  5. % separable
  6. % quasi-separable (separable after linear transformation)
  7. % (algebraically) homogeneous
  8. % quasi-homogeneous (homogeneous after linear transformation)
  9. % Bernoulli
  10. % Riccati
  11. % solvable for x or y, including Clairaut and Lagrange
  12. % exact (FJW: general exact ode now handled elsewhere)
  13. % and at a later stage of development to add more general methods,
  14. % such as Prelle Singer
  15. % Note that all cases of first order ODEs can be considered equivalent
  16. % to searches for integrating factors. (In particular Lie methods do
  17. % not help here.)
  18. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  19. %% algebraic procedure odesolve(ode, y, x);
  20. %% %% Temporary definition for test purposes.
  21. %% begin scalar !*precise, !*odesolve!-solvable!-xy, solution;
  22. %% ode := num !*eqn2a ode; % returns ode as expression
  23. %% if (solution := ODESolve!-NonLinear1(ode, y, x)) then
  24. %% return solution
  25. %% else
  26. %% write "***** ODESolve cannot solve this ODE!"
  27. %% end$
  28. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  29. global '(ODESolve_Before_Non1Grad_Hook ODESolve_After_Non1Grad_Hook)$
  30. algebraic procedure ODESolve!-NonLinear1(ode, y, x);
  31. %% Top-level solver for non-linear first-order ODEs.
  32. begin scalar odecoeffs, gradient, solution, p, ode_p;
  33. traceode1 "Entering top-level non-linear first-order solver ...";
  34. %% traceode "This is a nonlinear ODE of order 1.";
  35. p := gensym();
  36. ode_p := num sub(df(y,x) = p, ode);
  37. %% If ode contains exponentials then the above sub can produce a
  38. %% denominator when there is none in ode!
  39. odecoeffs := coeff(ode_p, p);
  40. if length odecoeffs = 2 and not smember(p, odecoeffs)
  41. then << % first DEGREE ODE
  42. gradient := -first odecoeffs / second odecoeffs;
  43. symbolic if (solution := or(
  44. ODESolve!-Run!-Hook(
  45. 'ODESolve_Before_Non1Grad_Hook, {gradient, y, x}),
  46. ODESolve!-Separable(gradient, y, x),
  47. ODESolve!-QuasiSeparable(gradient, y, x),
  48. ODESolve!-Homogeneous(gradient, y, x),
  49. ODESolve!-QuasiHomog(gradient, y, x),
  50. ODESolve!-Bernoulli(gradient, y, x),
  51. ODESolve!-Riccati(gradient, y, x),
  52. ODESolve!-Run!-Hook(
  53. 'ODESolve_After_Non1Grad_Hook, {gradient, y, x})))
  54. then return solution
  55. >>;
  56. %% If ode degree neq 1 or above solvers fail then ...
  57. %% A first-order ode is "solvable-for-y" if it can be put into
  58. %% the form y = f(x,p) where p = y'. This form includes
  59. %% Clairaut and Lagrange equations as special cases. The
  60. %% Lagrange form is y = xF(y') + G(y'). If F(p) = p then this
  61. %% is a Clairaut equation. It is "solvable-for-x" if it can be
  62. %% put into the form x = f(y,p).
  63. if (solution := ODESolve!-Clairaut(ode, ode_p, p, y, x)) then
  64. return solution;
  65. %% Avoid infinite loops:
  66. symbolic if !*odesolve!-solvable!-xy then return;
  67. symbolic(!*odesolve!-solvable!-xy := t);
  68. %% "Solvable for y" includes Lagrange as a special case:
  69. symbolic return
  70. ODESolve!-Solvable!-y(ode_p, p, y, x) or
  71. ODESolve!-Solvable!-x(ode_p, p, y, x)
  72. end$
  73. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  74. % Support routines
  75. algebraic procedure ODENon!-Linear1(ode, y, x);
  76. %% Solve the linear ODE: dy/dx = gradient(x,y) = P(x)*y + Q(x)
  77. %% Split into 2 procedures by FJW.
  78. begin scalar gradient;
  79. gradient := coeff(num ode, df(y,x)); % {low, high}
  80. gradient := -first gradient/second gradient;
  81. traceode!* "This is a first-order linear ODE solved by ";
  82. return if smember(y, gradient) then
  83. begin scalar P, Q;
  84. traceode "the integrating factor method.";
  85. P := lcof(num gradient,y)/den gradient;
  86. Q := gradient - P*y;
  87. return { y = ODENon!-Linear1PQ(P, Q, x) }
  88. end
  89. else <<
  90. traceode "quadrature.";
  91. % FJW: Optionally turn off final integration:
  92. { y = ODESolve!-Int(gradient, x) + newarbconst() }
  93. >>
  94. end$
  95. algebraic procedure ODENon!-Linear1PQ(P, Q, x);
  96. %% Solve the linear ODE: dy/dx = P(x)*y + Q(x)
  97. %% Called directly by ODESolve!-Bernoulli
  98. begin scalar intfactor, !*combinelogs;
  99. %% intfactor simplifies better if logs in the integral are
  100. %% combined:
  101. symbolic(!*combinelogs := t);
  102. intfactor := exp(int(-P, x));
  103. %% Optionally turn off final integration:
  104. return (newarbconst() + ODESolve!-Int(intfactor*Q,x))/intfactor
  105. end$
  106. %% algebraic procedure unfactorize factorlist;
  107. %% %% Multiply out a factor list of the form
  108. %% %% { {factor, multiplicity}, ... }:
  109. %% for each fac in factorlist product first fac^second fac$
  110. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  111. % Separable ODEs
  112. algebraic procedure ODESolve!-Separable(gradient, y, x);
  113. %% The ODE has the form dy/dx = gradient = F(x,y). If F(x,y) =
  114. %% f(x)g(y) then the ODE is separable, in which case return the
  115. %% solution; otherwise return nil.
  116. begin scalar f, g, !*redefmsg;
  117. traceode1 "Testing for a separable ODE ...";
  118. %% Handle implicit dependence on x (ignoring that via y):
  119. symbolic (<< depend1(y, x, nil);
  120. %% Hack sub to handle implicit dependences:
  121. copyd('ODESolve!-old!-subsublis, 'subsublis);
  122. copyd('subsublis, 'ODESolve!-subsublis);
  123. g := errorset!*(
  124. {'ODESolve!-Separable1, mkquote gradient, mkquote x}, nil);
  125. copyd('subsublis, 'ODESolve!-old!-subsublis);
  126. if errorp g then RedErr {"(in ODESolve!-Separable1)", emsg!*};
  127. g := car g;
  128. if depends(g, x) then g := nil;
  129. >> where depl!* = depl!*);
  130. if not g then return;
  131. %% Then F(alpha,y) = f(alpha)g(y), and F(x,y)/F(alpha,y) =
  132. %% f(x)/f(alpha) is free of y:
  133. if depends(f := gradient/g, y) then return;
  134. traceode "It is separable.";
  135. %% Any separation of F(x,y) will suffice!
  136. %% gradient := int(1/g, y) - int(f, x) + newarbconst();
  137. %% Try to optimize structure of solution to avoid a likely
  138. %% logarithm of a function of y:
  139. gradient := int(1/g, y);
  140. gradient := if part(gradient,0) = log then
  141. part(gradient,1) - newarbconst()*exp(int(f, x))
  142. else gradient - int(f, x) + newarbconst();
  143. return { num gradient = 0 }
  144. end$
  145. algebraic procedure ODESolve!-Separable1(gradient, x);
  146. %% Find a small constant alpha such that F(alpha,y) exists and
  147. %% F(alpha,y) neq 0, and return F(alpha,y), where F = gradient:
  148. begin scalar numer, denom, alpha, d, n;
  149. numer := num gradient;
  150. denom := den gradient;
  151. alpha := 0;
  152. while (d:=sub(x=alpha,denom)) = 0 or
  153. (n:=sub(x=alpha,numer)) = 0 do
  154. alpha := alpha + 1;
  155. return n/d
  156. end$
  157. symbolic procedure ODESolve!-subsublis(u,v);
  158. % NOTE: This definition assumes that with the exception of *SQ and
  159. % domain elements, expressions do not contain dotted pairs.
  160. % This is the standard `subsublis' plus support for one level of
  161. % indirect dependence (cf. freeof), in which case sub converts the
  162. % dependent variable into an independent variable with a different
  163. % but related name.
  164. % u is an alist of substitutions, v is an expression to be
  165. % substituted:
  166. begin scalar x;
  167. return if x := assoc(v,u) then cdr x
  168. % allow for case of sub(sqrt 2=s2,atan sqrt 2).
  169. else if eqcar(v,'sqrt)
  170. and (x := assoc(list('expt,cadr v,'(quotient 1 2)),u))
  171. then cdr x
  172. else if atom v then
  173. if (x := assoc(v,depl!*)) and % FJW
  174. %% Run through variables on which v depends:
  175. << while (x := cdr x) and not assoc(car x,u) do; x >> % FJW
  176. then mkid(v, '!!) else % FJW
  177. v
  178. else if not idp car v
  179. then for each j in v collect ODESolve!-subsublis(u,j)
  180. else if x := get(car v,'subfunc) then apply2(x,u,v)
  181. else if get(car v,'dname) then v
  182. else if car v eq '!*sq then ODESolve!-subsublis(u,prepsq cadr v)
  183. else for each j in v collect ODESolve!-subsublis(u,j)
  184. end$
  185. algebraic procedure ODESolve!-QuasiSeparable(gradient, y, x);
  186. %% The ODE has the form dy/dx = gradient = F(x,y). If F(x,y) =
  187. %% f(y+kx) then the ODE is quasi-separable, in which case return
  188. %% the solution; otherwise return nil.
  189. begin scalar k;
  190. traceode1 "Testing for a quasi-separable ODE ...";
  191. %% F(x,y) = f(y+kx) iff df(F,x)/df(F,y) = k = constant (using
  192. %% PARTIAL derivatives):
  193. k := (df(gradient,x)/df(gradient,y) where df(y,x) => 0);
  194. if depends(k, x) then return; % sufficient since y = y(x)
  195. traceode "It is separable after letting ", y+k*x => y;
  196. %% Setting u = y+kx gives du/dx = dy/dx+k = f(u)+k:
  197. gradient := sub(x=0, gradient) + k;
  198. %% => int(1/(f(u)+k),u) = x + arbconst.
  199. gradient := sub(y=y+k*x, int(1/gradient, y)) - x + newarbconst();
  200. return { num gradient = 0 }
  201. end$
  202. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  203. % Algebraically homogeneous ODEs
  204. algebraic procedure ODESolve!-Homogeneous(gradient, y, x);
  205. %% The ODE has the form dy/dx = gradient = F(x,y). If F(x,y) =
  206. %% f(y/x) then the ODE is algebraically homogeneous.
  207. %% Setting y = vx => v + x dv/dx = F(x,vx) = f(v)
  208. begin scalar v; v := gensym();
  209. traceode1 "Testing for a homogeneous ODE ...";
  210. gradient := sub(y=v*x, gradient);
  211. if depends(gradient, x) then return;
  212. traceode
  213. "It is of algebraically homogeneous type ",
  214. "solved by a change of variables of the form `y = vx'.";
  215. %% Integrate to give int(1/(f(v)-v),v) = int(1/x, x) + arbconst
  216. %% Hence exp int(1/(f(v)-v),v) = arbconst * x
  217. gradient := exp(int(1/(gradient-v), v));
  218. gradient := (gradient where (sqrt ~a)*(sqrt ~b) => sqrt(a*b));
  219. gradient := sub(v=y/x, x/gradient) + newarbconst();
  220. return {num gradient = 0}
  221. end$
  222. %% A quasi-homogeneous first-order nonlinear ODE has the form
  223. %% dy/dx = F(..., ((a1*x + b1*y + c1)/(a2*x + b2*y + c2))^p, ...)
  224. %% where F is an arbitrary composition of functions and p is an
  225. %% arbitrary power. F may have other arguments that do not depend on
  226. %% x or y or that depend in the same way (e.g. F may be a sum or
  227. %% product). The argument of F that depends on x or y will be a
  228. %% quotient of expanded polynomials if p is a positive integer.
  229. %% Otherwise, it will be a power of a quotient (expt (quotient ... )),
  230. %% in which case the `expt' can be treated as part of the composition
  231. %% F, or a quotient of powers (quotient (expt ... ) (expt ... )) which
  232. %% must be treated separately.
  233. algebraic procedure ODESolve!-QuasiHomog(gradient, y, x);
  234. %% The ODE has the form dy/dx = gradient = F(x,y). If F(x,y) =
  235. %% f((a1*x + b1*y + c1)/(a2*x + b2*y + c2)) where the function f
  236. %% may be arbitrary then the ODE is reducible to algebraically
  237. %% homogeneous. Find the first linear-rational sub-expression, and
  238. %% use it to try to make the ODE homogeneous:
  239. begin scalar tmp, n, d, soln;
  240. traceode1 "Testing for a quasi-homogeneous ODE ...";
  241. %% First, find an "argument" that is a rational function, with
  242. %% numerator and denominator that both depend on x.
  243. if not(tmp := symbolic ODESolve!-QuasiHomog1(reval gradient, x))
  244. then return;
  245. n := num tmp; d := den tmp;
  246. %% Now check that numerator and denominator have the same degree
  247. %% in x (and y):
  248. if (tmp := deg(n,x)) neq deg(d,x) then return;
  249. %% If that degree > 1 then extract the squarefree factor:
  250. if tmp = 1 then % => deg(d,x) = 1
  251. %% ( if deg(n,y) neq 1 or deg(d,y) neq 1 then return )
  252. else <<
  253. %% Use partial squarefree factorization to find p'th root of
  254. %% numerator and denominator:
  255. %% If f = poly = (a x + b y + c)^p
  256. %% then f' = a p(a x + b y + c)^(p-1)
  257. %% => gcd(f, f') = (a x + b y + c)^(p-1)
  258. %% => f / gcd(f, f') = a x + b y + c.
  259. n := n / gcd(n, df(n, x)); % must be linear in x and y
  260. %% if deg(n,x) neq 1 or deg(n,y) neq 1 then return;
  261. d := d / gcd(d, df(d, x)); % must be linear in x and y
  262. %% if deg(d,x) neq 1 or deg(d,y) neq 1 then return
  263. >>;
  264. %% Check that numerator and denominator are really LINEAR
  265. %% POLYNOMIALS in x and y (where y depends on x):
  266. if length(tmp := coeff(n, y)) neq 2 or depends(tmp, y) then return;
  267. if depends(second tmp, x) or % coeff of y^1
  268. length(tmp := coeff(first tmp, x)) neq 2 or % coeff of y^0
  269. depends(tmp, x) then return;
  270. if length(tmp := coeff(d, y)) neq 2 or depends(tmp, y) then return;
  271. if depends(second tmp, x) or % coeff of y^1
  272. length(tmp := coeff(first tmp, x)) neq 2 or % coeff of y^0
  273. depends(tmp, x) then return;
  274. %% The degenerate case a1*b2=a2*b1 is now treated as quasi-separable.
  275. traceode "It is quasi-homogeneous if ",
  276. "the result of shifting the origin is homogeneous ...";
  277. soln := first solve({n,d},{x,y});
  278. n := rhs first soln; d := rhs second soln;
  279. gradient := sub(x=x+n, y=y+d, gradient);
  280. %% ODE was quasi-homogeneous iff the new ODE is homogeneous:
  281. if (soln := ODESolve!-Homogeneous(gradient,y,x)) then
  282. return sub(x=x-n, y=y-d, soln);
  283. traceode "... which it is not!"
  284. end$
  285. %%% The calls to `depends' below are inefficient!
  286. symbolic procedure ODESolve!-QuasiHomog1(u, x);
  287. %% Assumes "algebraic" form! Get the first argument of any
  288. %% composition of functions that is a quotient of polynomials or
  289. %% symbolic powers (expt forms) that both depend on `x'.
  290. if atom u then nil % not QH, else operator ...
  291. else if car u eq 'quotient and % quotient, in which
  292. depends(cadr u, x) and % numerator depends on x, and
  293. depends(caddr u, x) then % denominator depends on x.
  294. if eqcar(cadr u, 'expt) then % symbolic powers
  295. ( if eqcar(caddr u, 'expt) and (caddr cadr u eq caddr caddr u)
  296. then {'quotient, cadr cadr u, cadr caddr u} )
  297. else
  298. ( if eqcar(cadr u, 'plus) and eqcar(caddr u, 'plus)
  299. then u ) % polynomials (?)
  300. else % Process first x-dependent argument of operator u:
  301. begin
  302. a: if (u := cdr u) then
  303. if depends(car u, x) then return ODESolve!-QuasiHomog1(car u, x)
  304. else go to a
  305. end$
  306. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  307. % Bernoulli ODEs
  308. symbolic operator ODESolve!-Bernoulli$
  309. symbolic procedure ODESolve!-Bernoulli(rhs, y, x);
  310. %% The ODE has the form df(y,x) = rhs. If rhs has the Bernoulli
  311. %% form P(x)*y + Q(x)*y^n then extract P(x), Q(x), n and return the
  312. %% solution else return nil.
  313. ( begin scalar num_rhs, den_rhs, C1, C2, d, d1, d2, d3, P, Q, n;
  314. traceode1 "Testing for a Bernoulli ODE ...";
  315. %% Degrees will be constructed in true prefix form.
  316. %% Need sum of two terms, both with main var (essentially) y.
  317. depend1(x, y, nil) where !*msg = nil;
  318. << updkorder y; rhs := simp rhs >> where kord!* = kord!*;
  319. num_rhs := numr rhs; den_rhs := denr rhs;
  320. if domainp num_rhs or not(mvar num_rhs eq y) then return;
  321. %% Num may have the form y^d * (y^d1 C1(x) + y^d2 C2(x)) ...
  322. if null red num_rhs then <<
  323. d := ldeg num_rhs; num_rhs := lc num_rhs;
  324. if domainp num_rhs then return
  325. >>;
  326. %% Now num must have the form y^d1 C1(x) + y^d2 C2(x),
  327. %% where d1 > d2 and d2 = 0 is allowed (if d <> 0 or d3 <> 0).
  328. if (C1 := get!!y!^n!*C(num_rhs, y)) then
  329. << d1 := car C1; C1 := cdr C1 >>
  330. else return;
  331. num_rhs := red num_rhs;
  332. %% Allow d2 = 0 => num_rhs freeof y
  333. if not smember(y, num_rhs) then
  334. << d2 := 0; C2 := num_rhs >>
  335. else if red num_rhs then return
  336. else if (C2 := get!!y!^n!*C(num_rhs, y)) then
  337. << d2 := car C2; C2 := cdr C2 >>
  338. else return;
  339. %% Den must have the form C3(x) or y^d3 C3(x).
  340. %% In the latter case, combine the powers of y.
  341. if smember(y, den_rhs) then
  342. if null red den_rhs and
  343. (den_rhs := get!!y!^n!*C(den_rhs, y)) then <<
  344. d3 := car den_rhs; den_rhs := cdr den_rhs;
  345. d1 := {'difference, d1, d3};
  346. d2 := {'difference, d2, d3}
  347. >> else return;
  348. %% Simplify the degrees of y and find which is 1:
  349. if d then << d1 := {'plus, d1, d}; d2 := {'plus, d2, d} >>;
  350. d1 := aeval d1; d2 := aeval d2;
  351. if d1 = 1 then << P := C1; Q := C2; n := d2 >>
  352. else if d2 = 1 then << P := C2; Q := C1; n := d1 >>
  353. else return;
  354. %% A final check that P, Q, n are valid:
  355. if Bernoulli!-depend!-check(P, y) or
  356. Bernoulli!-depend!-check(Q, y) or
  357. Bernoulli!-depend!-check(den_rhs, y) or
  358. Bernoulli!-depend!-check(n, x) then return;
  359. %% ( Last test implies Bernoulli!-depend!-check(n, y). )
  360. %% For testing:
  361. %% return {'list, mk!*sq(P ./ den_rhs), mk!*sq(Q ./ den_rhs), n};
  362. P := mk!*sq(P ./ den_rhs); Q := mk!*sq(Q ./ den_rhs);
  363. return ODESolve!-Bernoulli1(P, Q, y, x, n)
  364. end ) where depl!* = depl!*$
  365. symbolic procedure Bernoulli!-depend!-check(f, xy);
  366. %% f is a standard form, xy is an identifier (kernel).
  367. if numr difff(f, xy) then << % neq 0 (nil)
  368. traceode "It is not of Bernoulli type because ...";
  369. MsgPri(nil, !*f2a f, "depends (possibly implicitly) on",
  370. get(xy, 'odesolve!-depvar) or xy, nil);
  371. %% (y might be gensym -- 'odesolve!-depvar set in odeintfc)
  372. t
  373. >>$
  374. symbolic procedure get!!y!^n!*C(u, y);
  375. %% Assume that u is a standard form representation of y^n * C(x)
  376. %% with y the leading kernel. Return (n . C) or nil
  377. begin scalar n, C;
  378. if mvar u eq y then << % ?? y^n * C(x), n nonnegint
  379. n := ldeg u; C := lc u;
  380. return if not domainp C and smember(y, mvar C) then
  381. ( if (C := get!!y!^n!*C1(C, y)) then
  382. % y^n * y^n1 * C(x), n nonnegint
  383. {'plus, n, car C} . cdr C )
  384. else n . C
  385. >> else % (y^n1)^n * C(x), n nonnegint
  386. return get!!y!^n!*C1(u, y)
  387. end$
  388. symbolic procedure get!!y!^n!*C1(u, y);
  389. % u = (y^n1)^n * C(x), n nonnegint
  390. begin scalar n, C;
  391. n := mvar u;
  392. if not(eqcar(n, 'expt) and cadr n eq y) then return;
  393. n := {'times, caddr n, ldeg u};
  394. C := lc u;
  395. return n . C
  396. end$
  397. algebraic procedure ODESolve!-Bernoulli1(P, Q, y, x, n);
  398. begin scalar !*odesolve_noint; % Force integration?
  399. traceode "It is of Bernoulli type.";
  400. n := 1 - n;
  401. return if symbolic !*odesolve_explicit then
  402. { y = ODENon!-Linear1PQ(n*P, n*Q, x)^(1/n)*
  403. newroot_of_unity(n) } % explicit form
  404. else { y^n = ODENon!-Linear1PQ(n*P, n*Q, x) } % implicit form
  405. end$
  406. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  407. % Riccati ODEs
  408. algebraic procedure ODESolve!-Riccati(rhs, y, x);
  409. %% The ODE has the form df(y,x) = rhs. If rhs has the Riccati form
  410. %% a(x)y^2 + b(x)y + c(x) then transform to a reduced linear
  411. %% second-order ODE and attempt to solve it.
  412. symbolic if not !*odesolve_fast then % heuristic solution
  413. algebraic begin scalar a, b, c, soln, !*ratarg;
  414. traceode1 "Testing for a Riccati ODE ...";
  415. %% rhs may have a denominator that depends on y, so ...
  416. symbolic(!*ratarg := t);
  417. c := coeff(rhs, y);
  418. if length c neq 3 or depends(c, y) then return;
  419. a := third c; b := second c; c := first c;
  420. c := a*c; b := -(df(a,x)/a + b);
  421. traceode "It is of Riccati type ",
  422. "and transforms into the linear second-order ODE: ",
  423. num(df(y,x,2) + b*df(y,x) + c*y) = 0;
  424. soln := {c, b, 1}; % low .. high
  425. soln := ODESolve!-linear!-basis(soln, 0, y, x, 2,
  426. if c then 0 else if b then 1 else 2); % min_order
  427. if not soln then <<
  428. traceode "But ODESolve cannot solve it!";
  429. return
  430. >>;
  431. %% The solution of the linear second-order ode must have the
  432. %% form y = arbconst(1)*y1 + arbconst(2)*y2, in which only the
  433. %% ratio of the arbconst's is relevant here, so ...
  434. soln := first soln;
  435. soln := newarbconst()*first soln + second soln;
  436. return {y = sub(y = soln, -df(y,x)/(a*y))}
  437. %% BEWARE: above minus sign missing in Zwillinger, first edn.
  438. end$
  439. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  440. % ODEs that are "solvable for y or x", including Clairaut and Lagrange
  441. % as special cases.
  442. algebraic procedure ODESolve!-Clairaut(ode, ode_p, p, y, x);
  443. %% Assuming that ode is first order, determine whether it is of
  444. %% Clairaut type f(xy'-y) = g(y'), and if so return its general
  445. %% solution together with a singular solution if one exists.
  446. begin scalar sing_soln;
  447. traceode1 "Testing for a Clairaut ODE ...";
  448. sing_soln := sub(y = x*p, ode_p);
  449. if depends(sing_soln, x) or depends(sing_soln, y) then
  450. return; % Not Clairaut
  451. traceode "It is of Clairaut type.";
  452. %% Look for a singular solution:
  453. sing_soln := solve(df(ode, x), df(y,x));
  454. sing_soln := for each soln in sing_soln join
  455. %% NB: Cannot use algebraic code to check soln because it may
  456. %% contain derivatives that evaluate to 0.
  457. if symbolic eqcar(caddr soln, 'root_of)
  458. then {} else {num sub(soln, ode) = 0};
  459. return (sub(p = newarbconst(), ode_p) = 0) . sing_soln
  460. end$
  461. symbolic operator ODESolve!-Solvable!-y$
  462. symbolic procedure ODESolve!-Solvable!-y(ode_p, p, y, x);
  463. %% ode_p has the form f(x,y,p) where p = y'.
  464. ( algebraic begin scalar c, lagrange, ode_y, ode_x;
  465. traceode1 "Testing for a ""solvable for y"" or Lagrange ODE ...";
  466. %% Is the ODE "solvable for y"?
  467. c := coeff(ode_p, y);
  468. if length c neq 2 or depends(c, y) then return;
  469. ode_y := -first c / second c;
  470. %% ode_y has the form f(x,p) where p = y' and ode is y = ode_y.
  471. %% If f(x,p) = xF(p) + G(p) then ode is a Lagrange equation.
  472. if not depends(den ode_y, x) and
  473. length(c:=coeff(num ode_y, x)) = 2 and
  474. not depends(c, x) then
  475. lagrange := 1; % Lagrange form
  476. symbolic depend1(p, x, t);
  477. ode_x := num(p - df(ode_y, x));
  478. if lagrange then <<
  479. %% ODE is a Lagrange equation, hence ode_x is a LINEAR ODE
  480. %% for x(p) that can be solved EXPLICITLY (using an
  481. %% integrating factor) for a single value of x.
  482. symbolic depend1(x, p, t);
  483. ode_x := num(ode_x where df(p,x) => 1/df(x,p));
  484. symbolic depend1(p, x, nil);
  485. traceode "It is of Lagrange type and reduces to this ",
  486. "subsidiary ODE for x(y'): ",
  487. ode_x = 0;
  488. ode_x := ODENon!-Linear1(ode_x, x, p)
  489. >> else if symbolic !*odesolve_fast then return
  490. traceode "Sub-solver terminated: fast mode, no heuristics!"
  491. else <<
  492. %% ode_x is an arbitrary first-order ODE for p(x), so ...
  493. traceode "It is ""solvable for y"" and reduces ",
  494. "to this subsidiary ODE for y'(x):";
  495. ode_x := ODESolve!-FirstOrder(ode_x, p, x);
  496. if not ode_x then <<
  497. traceode "But ODESolve cannot solve it!";
  498. return
  499. >>
  500. >>;
  501. traceode "The subsidiary solution is ", ode_x,
  502. " and the main ODE can be solved parametrically ",
  503. "in terms of the derivative.";
  504. return if symbolic(!*odesolve_implicit or !*odesolve_explicit) then
  505. %% Try to eliminate p between ode_y and ode_x else fail.
  506. %% Assume that the interface code will try to actually solve
  507. %% this for y:
  508. Odesolve!-Elim!-Param(ode_y, y, ode_x, p, y)
  509. else
  510. %% Return a parametric solution {y(p), x(p), p}:
  511. if lagrange then % soln explicit for x
  512. for each soln in ode_x collect ODESolve!-Simp!-ArbParam
  513. sub(p=newarbparam(), {y = sub(soln, ode_y), soln, p})
  514. else
  515. for each soln in ode_x join <<
  516. %% Make solution as explicit as possible:
  517. soln := solve(soln, x);
  518. for each s in soln collect ODESolve!-Simp!-ArbParam
  519. sub(p=newarbparam(),
  520. if symbolic eqcar(caddr s, 'root_of) then
  521. {y=ode_y, sub(part(rhs s, 2)=x, part(rhs s, 1)), p}
  522. else {y=sub(s, ode_y), s, p})
  523. >>
  524. end ) where depl!* = depl!*$
  525. symbolic operator ODESolve!-Solvable!-x$
  526. symbolic procedure ODESolve!-Solvable!-x(ode_p, p, y, x);
  527. %% ode_p has the form f(x,y,p) where p = y'.
  528. not !*odesolve_fast and % heuristic solution
  529. ( algebraic begin scalar c, ode_x, ode_y;
  530. traceode1 "Testing for a ""solvable for x"" ODE ...";
  531. %% Is the ODE "solvable for x"?
  532. c := coeff(ode_p, x);
  533. if length c neq 2 or depends(c, x) then return;
  534. ode_x := -first c / second c;
  535. %% ode_x has the form f(y,p) where p = y' and ode is x = ode_x.
  536. symbolic depend1(p, y, t);
  537. ode_y := num(1/p - df(ode_x, y));
  538. %% ode_y is an arbitrary first-order ODE for p(y), so ...
  539. traceode "It is ""solvable for x"" and reduces ",
  540. "to this subsidiary ODE for y'(y):";
  541. ode_y := ODESolve!-FirstOrder(ode_y, p, y);
  542. if not ode_y then <<
  543. traceode "But ODESolve cannot solve it! ";
  544. return
  545. >>;
  546. traceode "The subsidiary solution is ", ode_y,
  547. " and the main ODE can be solved parametrically ",
  548. "in terms of the derivative.";
  549. return if symbolic(!*odesolve_implicit or !*odesolve_explicit) then
  550. %% Try to eliminate p between ode_x and ode_y else fail.
  551. %% Assume that the interface code will try to actually solve
  552. %% this for y:
  553. Odesolve!-Elim!-Param(ode_x, x, ode_y, p, y)
  554. else
  555. for each soln in ode_y join <<
  556. %% Return a parametric solution {y(p), x(p), p}:
  557. %% Make solution as explicit as possible:
  558. soln := solve(soln, y);
  559. for each s in soln collect ODESolve!-Simp!-ArbParam
  560. sub(p=newarbparam(),
  561. if symbolic eqcar(caddr s, 'root_of) then
  562. {sub(part(rhs s, 2)=y, part(rhs s, 1)), x=ode_x, p}
  563. else {s, x=sub(s, ode_x), p})
  564. >>
  565. end ) where depl!* = depl!*$
  566. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  567. % The arbitrary parameters in solutions:
  568. algebraic operator arbparam$
  569. algebraic (!!arbparam := 0)$
  570. algebraic procedure newarbparam();
  571. arbparam(!!arbparam:=!!arbparam+1)$
  572. algebraic procedure Odesolve!-Elim!-Param(ode_y, y, ode_x, p, depvar);
  573. %% ode_y is an expression for y and ode_x is an rlist of odesolve
  574. %% solutions for x, both in terms of a parameter p. Return a list
  575. %% of equations corresponding to the equations in ode_x but with p
  576. %% eliminated with ode_y, or nil if this is not possible.
  577. %% depvar is the true dependent variable.
  578. begin scalar soln, result;
  579. if polynomialp(ode_y := num(y - ode_y), p) then <<
  580. result := {};
  581. while ode_x neq {} and
  582. polynomialp(soln := num !*eqn2a first ode_x, p)
  583. do <<
  584. result := resultant(ode_y, soln, p) . result;
  585. ode_x := rest ode_x
  586. >>;
  587. if ode_x = {} then
  588. return Odesolve!-Tidy!-Implicit(result, y)
  589. >>;
  590. ode_y := solve(ode_y, p);
  591. %% solve here may return a one_of construct (zimmer (4) & (19)).
  592. %% I don't see why, but ...
  593. %% (expand_cases not defined until solve loaded.)
  594. ode_y := expand_cases ode_y;
  595. if not smember(root_of, ode_y) then <<
  596. result := for each soln in ode_y join
  597. for each s in ode_x join
  598. if rhs s = 0 then % s is f(x,y) = 0
  599. if (s:=sub(soln, num lhs s)) neq 0 then {num s}
  600. else {}
  601. else {num(sub(soln, rhs s) - x)}; % s is x = f(x,y)
  602. return Odesolve!-Tidy!-Implicit(result, depvar)
  603. >>;
  604. traceode "But cannot eliminate parameter ",
  605. "to make solution explicit."
  606. end$
  607. algebraic procedure Odesolve!-Tidy!-Implicit(solns, depvar);
  608. %% Remove repeated and irrelevant factors from implicit solutions.
  609. for each soln in solns join
  610. for each fac in factorize soln join
  611. if smember(depvar, fac:=first fac) then {fac = 0} else {}$
  612. switch odesolve_simp_arbparam$ % DEFAULT OFF. TEMPORARY?
  613. symbolic operator ODESolve!-Simp!-ArbParam$
  614. symbolic procedure ODESolve!-Simp!-ArbParam u;
  615. %% Simplify arbparam expressions within parametric solution u
  616. %% (cf. ODESolve!-Simp!-ArbConsts)
  617. begin scalar !*precise, x, y, ss_x, ss_y, arbexprns_x, arbexprns_y,
  618. arbexprns, param;
  619. if not(rlistp u and length u = 4) then
  620. TypErr(u, "parametric ODE solution");
  621. if not !*odesolve_simp_arbparam then return u; % TEMPORARY?
  622. x := lhs cadr u; y := lhs caddr u;
  623. if not(ss_x := ODESolve!-Structr(caddr cadr u, x, y, 'arbparam))
  624. then return u;
  625. if not(ss_y := ODESolve!-Structr(caddr caddr u, x, y, 'arbparam))
  626. then return u;
  627. ss_x := cdr ss_x; ss_y := cdr ss_y;
  628. arbexprns_x := for each s in cdr ss_x collect caddr s;
  629. arbexprns_y := for each s in cdr ss_y collect caddr s;
  630. if null(arbexprns := intersection(arbexprns_x, arbexprns_y))
  631. then return u;
  632. arbexprns_x := cdr ss_x; ss_x := car ss_x;
  633. arbexprns_y := cdr ss_y; ss_y := car ss_y;
  634. arbexprns_x := for each s in arbexprns_x join
  635. if member(caddr s, arbexprns) then {s}
  636. else << ss_x := subeval{s, ss_x}; nil >>;
  637. arbexprns_y := for each s in arbexprns_y join
  638. if member(caddr s, arbexprns) then {s}
  639. else << ss_y := subeval{s, ss_y}; nil >>;
  640. traceode "Simplifying the arbparam expressions in ", u,
  641. " by the rewrites ...";
  642. param := cadddr u;
  643. for each s in arbexprns_x do <<
  644. %% s has the form ansj = "expression in arbparam(n)"
  645. traceode rhs s => param;
  646. %% Remove other occurrences of arbparam(n):
  647. ss_x := algebraic sub(solve(s, param), ss_x);
  648. %% Finally rename ansj as arbparam(n):
  649. ss_x := subeval{{'equal, cadr s, param}, ss_x}
  650. >>;
  651. for each s in arbexprns_y do <<
  652. ss_y := algebraic sub(solve(s, param), ss_y);
  653. ss_y := subeval{{'equal, cadr s, param}, ss_y}
  654. >>;
  655. %% Try a further heuristic simplification:
  656. if smember(param, den ss_x) and smember(param, den ss_y) then
  657. begin scalar ss_x1, ss_y1;
  658. ss_x1 := algebraic sub(param = 1/param, ss_x);
  659. if smember(param, den ss_x1) then return;
  660. ss_y1 := algebraic sub(param = 1/param, ss_y);
  661. if smember(param, den ss_y1) then return;
  662. traceode "Simplifying further by the rewrite ",
  663. param => 1/param;
  664. ss_x := ss_x1; ss_y := ss_y1
  665. end;
  666. return makelist {{'equal, x, ss_x}, {'equal, y, ss_y}, param}
  667. end$
  668. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  669. symbolic operator Polynomialp$
  670. %% symbolic procedure polynomialp(pol, var);
  671. %% %% Returns true if numerator of pol is polynomial in var.
  672. %% %% Assumes on exp, mcd.
  673. %% begin scalar kord!*; kord!* := {var};
  674. %% pol := numr simp!* pol;
  675. %% while not domainp pol and mvar pol eq var and
  676. %% (domainp lc pol or not smember(var, mvar lc pol)) do
  677. %% pol := red pol;
  678. %% return not smember(var, pol)
  679. %% end$
  680. symbolic procedure Polynomialp(pol, var);
  681. %% Returns true if numerator of pol is polynomial in var.
  682. %% Assumes on exp, mcd.
  683. Polynomial!-Form!-p(numr simp!* pol, !*a2k var)$
  684. symbolic procedure Polynomial!-Form!-p(sf, y);
  685. %% A standard form `sf' is polynomial if each of its terms is
  686. %% polynomial:
  687. domainp sf or
  688. (Polynomial!-Term!-p(lt sf, y) and Polynomial!-Form!-p(red sf, y))$
  689. symbolic procedure Polynomial!-Term!-p(st, y);
  690. %% A standard term `st' is polynomial if either (a) its
  691. %% leading power is polynomial and its coefficient is free of y, or (b)
  692. %% its leading power is free of y and its coefficient is polynomial:
  693. if tvar st eq y then not smember(y, tc st)
  694. else if not smember(y, tvar st) then Polynomial!-Form!-p(tc st, y)$
  695. endmodule;
  696. end;