odelin.red 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. module odelin$ % Simple linear ODE solver
  2. % F.J.Wright@Maths.QMW.ac.uk, Time-stamp: <14 September 2000>
  3. % Based in part on code by Malcolm MacCallum.
  4. % TO DO:
  5. % Polynomial solutions for polynomial coeffs.
  6. % Check the distinction between finding a solution technique for an
  7. % ODE that then fails internally to solve it (e.g. failing to solve
  8. % an auxiliary equation) and failing to find a solution technique.
  9. % (Should the former be handled by `odefailure'?)
  10. %% Techniques implemented
  11. %% ======================
  12. %% First order (integrating factor)
  13. %% Constant coefficients
  14. %% Euler and shifted Euler
  15. %% Exact
  16. %% Trivial order reduction (dep var and low-order derivs missing)
  17. %% Second-order special function ODEs (module odespcfn)
  18. %% Notes: Overall factors are handled in most cases by making the ODE
  19. %% "monic".
  20. %% Internal representation
  21. %% =======================
  22. %% A linear ode is represented by its list of coefficient functions
  23. %% (odecoeffs), driver term (driver), and dependent (y) and
  24. %% independent (x) variables. The maximum (ode_order) and minimum
  25. %% (min_order) derivative orders are included in the representation
  26. %% for efficiency/convenience. Its solution is represented as a basis
  27. %% for the solution space of the reduced ODE together with a
  28. %% particular integral of the full ODE.
  29. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  30. %% algebraic procedure odesolve(ode, y, x);
  31. %% %% Temporary definition for test purposes.
  32. %% begin scalar !*precise, solution;
  33. %% ode := num !*eqn2a ode; % returns ode as expression
  34. %% if (solution := ODESolve!-linear(ode, y, x)) then
  35. %% return solution
  36. %% else
  37. %% write "***** ODESolve cannot solve this ODE!"
  38. %% end$
  39. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  40. global '(ODESolve_Before_Lin_Hook ODESolve_After_Lin_Hook)$
  41. algebraic procedure ODESolve!-linear(ode, y, x);
  42. %% MAIN LINEAR SOLVER
  43. %% Assumes ODE is an algebraically irreducible "polynomial" expression.
  44. begin scalar reduced_ode, auxvar, auxeqn, odecoeffs,
  45. first_arb, solution, driver;
  46. %% The following decomposition is needed FOR ALL linear ODEs.
  47. %% The DRIVER is the part of the ODE independent of y, such that
  48. %% the ODE can be expressed as REDUCED_ODE = DRIVER.
  49. %% driver := if part(ode, 0) = plus then
  50. %% -select(~u freeof y, ode) else 0;
  51. driver := -sub(y=0, ode);
  52. reduced_ode := ode + driver;
  53. auxvar := symbolic gensym();
  54. %% df(y, x, n) => m^n, where m = auxvar
  55. auxeqn := sub(y=e^(auxvar*x), reduced_ode)/e^(auxvar*x);
  56. odecoeffs := coeff(auxeqn, auxvar); % low .. high
  57. traceode "This is a linear ODE of order ", high_pow, ".";
  58. first_arb := !!arbconst + 1;
  59. symbolic if not(solution := ODESolve!-linear!-basis
  60. (odecoeffs, driver, y, x, high_pow, low_pow) or
  61. (not !*odesolve_fast and
  62. %% Add a switch to control access to this thread?
  63. %% It is currently necessary for Zimmer (8).
  64. << traceode
  65. "But ODESolve cannot solve it using linear techniques, so ...";
  66. %% NB: This will probably produce a NONLINEAR ODE!
  67. %% But, in desperation, try it anyway ...
  68. (ODESolve!-Interchange(ode, y, x) where !*odesolve_basis = nil)
  69. >>)) then return;
  70. %% Return solution as BASIS or LINEAR COMBINATION, assuming a
  71. %% SINGLE solution since the ODE is linear:
  72. return if symbolic !*odesolve_basis then % return basis
  73. if (part(solution, 1, 0) = equal) then
  74. if lhs first solution = y and % solution is explicit
  75. (auxeqn := ODESolve!-LinComb2Basis
  76. (rhs first solution, first_arb, !!arbconst)) then auxeqn
  77. else << write
  78. "***** Cannot convert nonlinear combination solution to basis!";
  79. solution >>
  80. else solution
  81. else % return linear combination
  82. if part(solution, 1, 0) = list then
  83. {y = ODESolve!-Basis2LinComb solution}
  84. else solution
  85. end$
  86. algebraic procedure ODESolve!-linear!-basis
  87. (odecoeffs, driver, y, x, ode_order, min_order);
  88. %% Always returns the solution in basis format.
  89. %% Called by ODESolve!-Riccati in odenon1.
  90. symbolic if ode_order = 1 then
  91. ODESolve!-linear1(odecoeffs, driver, x)
  92. else or(
  93. ODESolve!-Run!-Hook(
  94. 'ODESolve_Before_Lin_Hook,
  95. {odecoeffs,driver,y,x,ode_order,min_order}),
  96. ODESolve!-linearn
  97. (odecoeffs, driver, y, x, ode_order, min_order, nil),
  98. ODESolve!-Run!-Hook(
  99. 'ODESolve_After_Lin_Hook,
  100. {odecoeffs,driver,y,x,ode_order,min_order}))$
  101. algebraic procedure ODESolve!-linear!-basis!-recursive
  102. (odecoeffs, driver, y, x, ode_order, min_order);
  103. %% Always returns the solution in basis format.
  104. %% Internal linear solver called recursively.
  105. symbolic if ode_order = 1 then
  106. ODESolve!-linear1(odecoeffs, driver, x)
  107. else
  108. ODESolve!-linearn
  109. (odecoeffs, driver, y, x, ode_order, min_order, t)$
  110. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  111. % Solve a linear first-order ODE by using an integrating factor.
  112. % Based on procedure linear1 from module ode1ord by Malcolm MacCallum.
  113. algebraic procedure ODESolve!-linear1(odecoeffs, driver, x);
  114. %% Solve the linear ODE reduced_ode = driver, where
  115. %% reduced_ode = A(x)*(dy/dx + P(x)*y), driver = A(x)*Q(x).
  116. %% Uses Odesolve!-Int to optionally turn off final integration.
  117. begin scalar A, P, Q;
  118. A := second odecoeffs;
  119. P := first odecoeffs/A;
  120. Q := driver/A;
  121. return if P then % dy/dx + P(x)*y = Q(x)
  122. begin scalar intfactor, !*combinelogs;
  123. traceode "It is solved by the integrating factor method.";
  124. %% intfactor simplifies better if logs are combined:
  125. symbolic(!*combinelogs := t);
  126. P := (P where tan(~x) => sin(x)/cos(x));
  127. intfactor := exp(int(P, x));
  128. return if Q then
  129. { {1/intfactor}, Odesolve!-Int(intfactor*Q,x)/intfactor }
  130. else { {1/intfactor} }
  131. end
  132. else << % dy/dx = Q(x)
  133. traceode "It is solved by quadrature.";
  134. if Q then {{1}, Odesolve!-Int(Q, x)} else {{1}}
  135. >>
  136. end$
  137. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  138. % Try to solve a linear ODE of order > 1.
  139. % Based on procedure linearn from module linearn by Malcolm MacCallum.
  140. % If the first integral of an exact ODE has constant coefficients, is
  141. % of Euler type or has trivally reducible order then so does the
  142. % original ODE. Also, trivial order reduction preserves constant
  143. % coefficients or Euler type, and the reduced ODE is not further
  144. % reducible. Hence, when the linear ODE solver is called recursively
  145. % to solve a first integral of an exact ODE or a trivially order-
  146. % reduced ODE, the argument `recursive' is used to avoid checking
  147. % again whether it has constant coefficients, is of Euler type or has
  148. % trivially reducible order.
  149. algebraic procedure ODESolve!-linearn
  150. (odecoeffs, driver, y, x, ode_order, min_order, recursive);
  151. %% Solve the linear ODE: reduced_ode = driver.
  152. begin scalar lcoeff, odecoeffs1, driver1, solution;
  153. %% Make the ODE "monic" as assumed by some solvers:
  154. %% (Note that this makes algebraic factorization largely
  155. %% irrelevant!)
  156. if (lcoeff := part(odecoeffs, ode_order+1)) = 1 then <<
  157. odecoeffs1 := odecoeffs;
  158. driver1 := driver
  159. >> else <<
  160. odecoeffs1 := for each c in odecoeffs collect c/lcoeff; % low .. high
  161. %% Could discard last element of odecoeffs1 because it must be 1!
  162. driver1 := driver/lcoeff
  163. >>;
  164. if recursive then goto a;
  165. %% Test for constant coefficients:
  166. if odecoeffs1 freeof x then
  167. return ODESolve!-LCC(odecoeffs1, driver1, x, ode_order);
  168. traceode "It has non-constant coefficients.";
  169. %% Test for Euler form:
  170. if (solution :=
  171. ODESolve!-Euler(odecoeffs1, driver1, x, ode_order))
  172. then return solution;
  173. %% Test for trivial order reduction. The result cannot have
  174. %% constant coeffs or Euler form, but it could be first order or
  175. %% exact or ...
  176. if min_order neq 0 and ode_order neq min_order and
  177. %% else would reduce to purely algebraic equation
  178. (solution := ODELin!-Reduce!-Order
  179. (odecoeffs, driver, y, x, ode_order, min_order))
  180. then return solution;
  181. a: %% Non-trivial solution techniques for recursive calls ...
  182. %% Test for exact form - try monic then original form:
  183. if (solution :=
  184. ODELin!-Exact(odecoeffs1, driver1, y, x, ode_order))
  185. then return solution;
  186. if lcoeff neq 1 and (solution :=
  187. ODELin!-Exact(odecoeffs, driver, y, x, ode_order))
  188. then return solution;
  189. %% Add other methods here ...
  190. %% Null return implies failure.
  191. %% FINALLY, test for a second-order special-function equation:
  192. if ode_order = 2 and
  193. (solution := ODESolve!-Specfn(odecoeffs1, driver1, x))
  194. then return solution
  195. end$
  196. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  197. % Convert between basis and linear combination formats
  198. % Solution basis output format: { {B1, B2, ...}, PI }
  199. % where {Bi} is a basis for the reduced ODE and PI is a particular
  200. % intergral for the full ODE, which may be absent.
  201. % This corresponds to the linear-combination output format
  202. % y = ( for each B in {B1, B2, ...} sum newarbconst()*B ) + PI.
  203. % This agrees with Maple, e.g. Maple V Release 5 gives
  204. % > dsolve(diff(y(x),x) + y(x) = x, output=basis);
  205. % [[exp(-x)], -1 + x]
  206. % > dsolve(diff(y(x),x) + y(x) = x);
  207. % y(x) = x - 1 + exp(-x) _C1
  208. algebraic procedure ODESolve!-Basis2LinComb solution;
  209. %% Convert basis { {B1, B2, ...}, PI } to linear combination:
  210. begin scalar lincomb;
  211. lincomb := for each B in first solution sum <<newarbconst()>>*B;
  212. %% << >> above is NECESSARY to force immediate evaluation!
  213. if length solution > 1 then
  214. lincomb := lincomb + second solution;
  215. return lincomb
  216. end$
  217. algebraic procedure ODESolve!-LinComb2Basis
  218. (lincomb, first_arb, last_arb);
  219. %% Convert linear combination to basis { {B1, B2, ...}, PI }:
  220. ODESolve!-LinComb2Basis1({}, lincomb, first_arb, last_arb)$
  221. algebraic procedure ODESolve!-LinComb2Basis1
  222. (basis, lincomb, first_arb, last_arb);
  223. %% `basis' is a LIST of independent reduced_ode solutions.
  224. %% Algorithm is to recursively move components from lincomb to
  225. %% basis.
  226. begin scalar coeffs, C; C := arbconst last_arb;
  227. coeffs := coeff(lincomb, C);
  228. if high_pow > 1 or smember(C, coeffs) then
  229. return % cannot convert
  230. else if high_pow = 1 then <<
  231. basis := second coeffs . basis;
  232. lincomb := first coeffs
  233. >>;
  234. %% else independent of this arbconst
  235. return if first_arb >= last_arb then
  236. { basis, lincomb }
  237. else
  238. ODESolve!-LinComb2Basis1
  239. (basis, lincomb, first_arb, last_arb-1)
  240. end$
  241. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  242. % Solve a linear, constant-coefficient ODE.
  243. % Based on code by Malcolm MacCallum.
  244. % There are (at least) 4 ways to get the particular integral (P.I.)
  245. % for a given driving term on the right of the equation:
  246. % 1. The method of undetermined coefficients: this is similar to the
  247. % integrator in that for a given driving term one has to find the
  248. % functional form of the P.I. and then solve for the numerical
  249. % coefficients in it. Making it really general is as big a task as
  250. % rewriting the integrator.
  251. % 2. The method of variation of parameters: this expands the P.I. as a
  252. % sum of functions of `x' times the linearly independent solutions in
  253. % the complementary function (C.F.).
  254. % 3. Factorise the linear operator (done anyway for the C.F.) and then
  255. % apply for each root `m' the operation
  256. % ans := exp(m*x) * int(ans * exp(-m*x))
  257. % This is a form of the "D-operator method". N.B. Some `m' are
  258. % complex.
  259. % 4. Use Laplace transforms (and some kind of table lookup for the
  260. % inverse transforms).
  261. % The current implementation first tries to use the "D-operator
  262. % method", but as soon as any integral fails to evaluate it switches
  263. % to "variation of parameters".
  264. algebraic procedure ODESolve!-LCC(odecoeffs, driver, x, ode_order);
  265. % Returns a solution basis or nil (if it fails).
  266. begin scalar auxvar, auxeqn, i, auxroots, solutions, PI;
  267. traceode "It has constant coefficients.";
  268. %% TEMPORARY HACK -- REBUILD AUXEQN:
  269. auxvar := symbolic gensym(); i := -1;
  270. auxeqn := for each c in odecoeffs sum c*auxvar^(i:=i+1);
  271. % First we solve for the auxiliary roots:
  272. auxroots := solve(auxeqn, auxvar);
  273. % and check the solution carefully:
  274. if ode_order neq
  275. (for each multi in multiplicities!* sum multi) then return
  276. traceode "But insufficient roots of auxiliary equation!";
  277. solutions := auxroots;
  278. a: if lhs first solutions neq auxvar then return
  279. traceode "But auxiliary equation could not be solved!";
  280. if (solutions := rest solutions) neq {} then goto a;
  281. % Now we find the complementary solution:
  282. solutions := ODESolve!-LCC!-CompSoln(auxroots, x);
  283. % Next the particular integral:
  284. if driver = 0 then return { solutions };
  285. if not (PI := ODESolve!-LCC!-PI(auxroots, driver, x)) then
  286. %% (Cannot use `or' as an algebraic operator!)
  287. PI := ODESolve!-PI(solutions, driver, x);
  288. return { solutions, PI }
  289. end$
  290. algebraic procedure ODESolve!-LCC!-CompSoln(auxroots, x);
  291. %% Construct the complimentary solution (functions) from the roots
  292. %% of the auxiliary equation for a linear ODE with constant
  293. %% coefficients. Pairs of complex conjugate roots are converted to
  294. %% real trigonometric form up to the minimum of their
  295. %% multiplicities (regardless of complex switch and parameters).
  296. %% `auxroots' is a list of equations with a temporary variable on
  297. %% the left and an auxilliary root on the right. The root
  298. %% multiplicities are stored as a list in the global variable
  299. %% `multiplicities!*'. `x' is the independent variable.
  300. begin scalar multilist, crootlist, ans, multi, imroot, exppart;
  301. %% crootlist will be a list of lists of the form
  302. %% {unpaired_complex_root, multiplicity}.
  303. multilist := multiplicities!*;
  304. crootlist := {}; ans := {};
  305. for each root in auxroots do <<
  306. root := rhs root;
  307. multi := first multilist; multilist := rest multilist;
  308. % Test for complex roots:
  309. imroot := impart!* root;
  310. if imroot = 0 then <<
  311. exppart := exp(root*x);
  312. for j := 1 : multi do ans := (x**(j-1)*exppart) . ans
  313. >> else
  314. begin scalar conjroot, conjmulti;
  315. %% Cannot assume anything about the order of the roots in
  316. %% auxroots, so build a list of the complex roots found to
  317. %% avoid using complex conjugate pairs twice.
  318. conjroot := conj!* root; conjmulti := 0;
  319. %% Essentially do assoc followed by delete if found:
  320. crootlist := for each root in crootlist join
  321. if first root = conjroot then <<
  322. conjmulti := second root; {}
  323. >> else {root};
  324. if conjmulti then % conjugate pair found:
  325. begin scalar minmulti;
  326. exppart := exp(repart!* root*x);
  327. minmulti := min(multi, conjmulti);
  328. imroot := abs imroot; % to avoid spurious minus sign
  329. imroot := (imroot where abs ~x => x); % to avoid spurious abs!
  330. for j := 1 : minmulti do
  331. ans := (x**(j-1)*cos(imroot*x)*exppart) .
  332. (x**(j-1)*sin(imroot*x)*exppart) . ans;
  333. if multi neq conjmulti then <<
  334. %% Skip this unlikely case if possible
  335. minmulti := minmulti + 1;
  336. exppart := exp(root*x);
  337. for j := minmulti : multi do
  338. ans := (x**(j-1)*exppart) . ans;
  339. exppart := exp(conjroot*x);
  340. for j := minmulti : conjmulti do
  341. ans := (x**(j-1)*exppart) . ans
  342. >>
  343. end
  344. else crootlist := {root, multi} . crootlist
  345. end
  346. >>;
  347. %% Finally include unpaired complex roots:
  348. for each root in crootlist do <<
  349. exppart := exp(first root*x);
  350. multi := second root;
  351. for j := 1 : multi do ans := (x**(j-1)*exppart) . ans
  352. >>;
  353. return ans
  354. end$
  355. % The following procedures process complex-valued expressions with
  356. % regard only to their EXPLICIT complexity, i.e. assuming that all
  357. % symbolic quantities are pure real. They need to work with the
  358. % complex switch both on and off, which is slightly tricky!
  359. algebraic(vars!-are!-real := {repart ~x => x, impart ~x => 0})$
  360. algebraic procedure repart!* u;
  361. << u := repart u; u where vars!-are!-real >>$
  362. algebraic procedure impart!* u;
  363. << u := impart u; u where vars!-are!-real >>$
  364. algebraic procedure conj!* u;
  365. << u := conj u; u where vars!-are!-real >>$
  366. algebraic procedure ODESolve!-LCC!-PI(auxroots, driver, x);
  367. % Try to construct a particular integral using the `D-operator
  368. % method'. Factorise the linear operator (done anyway for the
  369. % C.F.) and then apply for each root m the operation
  370. % ans := exp(m*x) * int(ans * exp(-m*x));
  371. % N.B. Some m may be complex.
  372. % See e.g. Stephenson, section 21.8, p 410.
  373. % Returns nil if any integral cannot be evaluated.
  374. begin scalar exp_mx, multiplicities, multi;
  375. traceode
  376. "Constructing particular integral using `D-operator method'.";
  377. multiplicities := multiplicities!*;
  378. while driver and auxroots neq {} do <<
  379. exp_mx := exp((rhs first auxroots)*x);
  380. driver := driver/exp_mx;
  381. multi := first multiplicities;
  382. while driver and multi >= 1 do <<
  383. driver := int(driver, x);
  384. if driver freeof int then multi := multi - 1 else driver := 0
  385. >>;
  386. driver := exp_mx*driver;
  387. auxroots := rest auxroots;
  388. multiplicities := rest multiplicities
  389. >>;
  390. if driver = 0 then
  391. traceode "But cannot evaluate the integrals, so ..."
  392. else return driver
  393. end$
  394. algebraic procedure ODESolve!-PI(solutions, R, x);
  395. % Given a "monic" forced linear nth-order ODE
  396. % y^(n) + a_(n-1)(x)y^(n-1) + ... + a_1(x)y = R(x)
  397. % and a set of n linearly independent solutions {yi(x)} to the
  398. % unforced ODE, construct a particular solution of the forced ODE
  399. % in the form of a (single) integral representation by the method
  400. % of variation of parameters.
  401. begin scalar n;
  402. traceode
  403. "Constructing particular integral using `variation of parameters'.";
  404. return
  405. if (n := length solutions) = 2 then
  406. begin scalar y1, y2, W;
  407. y1 := first solutions; y2 := second solutions;
  408. %% The Wronskian, kept separate to facilitate tracing:
  409. W := trigsimp(y1*df(y2, x) - y2*df(y1, x));
  410. traceode "The Wronskian is ", W;
  411. R := R/W;
  412. return -ode!-int(y2*R, x)*y1 + ode!-int(y1*R, x)*y2
  413. end
  414. else
  415. begin scalar Wmat, ys, W, i;
  416. %% Construct the (square) Wronskian matrix of the solutions:
  417. Wmat := {ys := solutions};
  418. for i := 2 : n do
  419. Wmat := (ys := for each y in ys collect df(y,x)) . Wmat;
  420. load_package matrix; % to define mat
  421. Wmat := list2mat reverse Wmat;
  422. %% The Wronskian (determinant), kept separate for tracing:
  423. W := trigsimp det Wmat;
  424. traceode "The Wronskian is ", W;
  425. R := R/W; i := 0;
  426. return
  427. for each y in solutions sum
  428. ode!-int(cofactor(Wmat, n, i:=i+1)*R, x) * y
  429. end
  430. end$
  431. % This facility should be in the standard matrix package!
  432. symbolic operator list2mat$
  433. symbolic procedure list2mat M;
  434. % Input: (list (list A B ...) (list C D ...) ...)
  435. % Output: (mat (A B ...) (C D ...) ...)
  436. 'mat . for each row in cdr M collect cdr row$
  437. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  438. % Special cases of non-constant coefficients:
  439. algebraic procedure ODESolve!-Euler(odecoeffs, driver, x, ode_order);
  440. %% Solve a (MONIC) ODE having (essentially) the form
  441. %% reduced_ode = x^n df(y,x,n) + ... + a_{n-1} x df(y,x) + a_n y = driver
  442. %% odecoeffs = {a_n, a_{n-1} x, ..., a_0 x^n} / (a_0 x^n)
  443. %% = {a_n/(a_0 x^n), a_{n-1}/(a_0 x^{n-1}), ..., a_1/(a_0 x), 1}
  444. begin scalar tmp, shift, i, c, solution;
  445. odecoeffs := reverse odecoeffs; % high .. low
  446. %% Check for possible Euler or "shifted Euler" form.
  447. %% Find second non-zero ode coeff:
  448. tmp := rest odecoeffs; i := 1;
  449. while first tmp = 0 do << tmp := rest tmp; i := i+1 >>;
  450. tmp := first tmp; % second non-zero ode coeff
  451. tmp := den tmp; % ax^i or a(x+b)^i
  452. tmp := reverse coeff(tmp, x); % high .. low
  453. if high_pow neq i then return; % not Euler
  454. if second tmp then << % "shifted Euler"
  455. shift := second tmp/(i*first tmp); % b
  456. driver := sub(x=x-shift, driver) % x -> x-b
  457. >>;
  458. tmp := {first odecoeffs}; i := 0;
  459. odecoeffs := rest odecoeffs;
  460. a: if odecoeffs neq {} then <<
  461. c := first odecoeffs * (x+shift)^(i:=i+1);
  462. if not(c freeof x) then return; % not Euler
  463. tmp := c . tmp;
  464. odecoeffs := rest odecoeffs;
  465. go to a
  466. >>;
  467. odecoeffs := tmp;
  468. traceode "It is of the homogeneous (Euler) type ",
  469. if shift then "(with shifted coefficients) " else "",
  470. "and is reducible to a simpler ODE ...";
  471. i := -2;
  472. tmp := for each c in odecoeffs sum <<
  473. i := i + 1;
  474. c * for j := 0 : i product (x-j)
  475. >>;
  476. odecoeffs := coeff(tmp, x); % TEMPORARY HACK!
  477. driver := sub(x=e^x, driver*x^ode_order);
  478. solution := ODESolve!-LCC(odecoeffs, driver, x, ode_order);
  479. solution := sub(x=log x, solution);
  480. if shift then solution := sub(x=x+shift, solution);
  481. return solution
  482. end$
  483. algebraic procedure ODELin!-Exact(P_list, driver, y, x, n);
  484. %% Computes a (linear) first integral if ODE is an exact linear
  485. %% n'th order ODE P_n(x) df(y,x,n) + ... + P_0(x) y = R(x).
  486. begin scalar P_0, C, Q_list, Q, const, soln, PI;
  487. P_0 := first P_list;
  488. P_list := reverse rest P_list; % P_n, ..., P_1
  489. %% ODE is exact if C = df(P_n,x,n) - df(P_{n-1},x,{n-1}) + ...
  490. %% + (-1)^{n-1} df(P_1,x) + (-1)^n P_0 = 0.
  491. for each P in P_list do C := P - df(C,x);
  492. C := P_0 - df(C,x); % C = 0 if exact
  493. if C then return;
  494. Q_list := {};
  495. for each P in P_list do
  496. Q_list := (Q := P - df(Q,x)) . Q_list; % Q_0, ..., Q_{n-1}
  497. driver := int(driver, x) + (const := symbolic gensym());
  498. %% The first integral is the LINEAR (n-1)'th order ODE
  499. %% Q_{n-1}(x) df(y,x,n) + ... + Q_0(x) y = int(R(x),x).
  500. traceode "It is exact, and the following linear ODE of order ",
  501. n-1, " is a first integral:";
  502. if symbolic !*trode then <<
  503. C := y;
  504. soln := first Q_list*y +
  505. ( for each Q in rest Q_list sum Q*(C := df(C,x)) );
  506. write soln = driver
  507. >>;
  508. %% Recurse on the order:
  509. C := Q_list;
  510. %% First-integral ODE must have min order 0, since input ODE was
  511. %% already order-reduced.
  512. soln := ODESolve!-linear!-basis!-recursive
  513. (Q_list, driver, y, x, n-1, 0);
  514. PI := second soln; % MUST exist since driver neq 0
  515. PI := coeff(PI, const); % { real PI, extra basis fn }
  516. return if high_pow = 1 then
  517. if first PI then { second PI . first soln, first PI }
  518. else { second PI . first soln }
  519. else <<
  520. %% This error should now be redundant!
  521. write "*** Internal error in ODELin!-Exact:",
  522. " cannot separate basis functions! ";
  523. write "(Probably caused by `noint' option.)";
  524. soln
  525. >>
  526. end$
  527. algebraic procedure ODELin!-Reduce!-Order
  528. (odecoeffs, driver, y, x, ode_order, min_order);
  529. %% If ODE does not explicitly involve y (and perhaps low order
  530. %% derivatives) then simplify by reducing the effective order
  531. %% (unless there is only one) and try to solve the reduced ODE to
  532. %% give a first integral. Applies only to ODEs of order > 1.
  533. begin scalar solution, PI;
  534. ode_order := ode_order - min_order;
  535. for ord := 1 : min_order do odecoeffs := rest odecoeffs;
  536. traceode "Performing trivial order reduction to give the order ",
  537. ode_order, " linear ODE with coefficients (low -- high): ",
  538. odecoeffs;
  539. solution := ODESolve!-linear!-basis!-recursive
  540. (odecoeffs, driver, y, x, ode_order, 0);
  541. if not solution then <<
  542. traceode "But ODESolve cannot solve the reduced ODE! ";
  543. return
  544. >>;
  545. traceode "Solution of order-reduced ODE is ", solution;
  546. traceode "Restoring order, ", y => df(y,x,min_order),
  547. ", to give: ", df(y,x,min_order) = solution,
  548. " and re-solving ...";
  549. %% = lin comb of fns of x, so just integrate min_order times:
  550. if length solution > 1 then % PI = particular integral
  551. PI := second solution;
  552. solution := append(
  553. for each c in first solution collect
  554. ODESolve!-multi!-int(c, x, min_order),
  555. %% and add min_order extra basis functions:
  556. for i := min_order-1 step -1 until 0 collect x^i );
  557. return if PI then
  558. { solution, ODESolve!-multi!-int(PI, x, min_order) }
  559. else
  560. { solution }
  561. end$
  562. endmodule$
  563. end$