odetop.red 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. module odetop$ % Top level ODESolve routines, exact ODEs, general
  2. % nonlinear ODE simplifications and utilities
  3. % F.J.Wright@maths.qmw.ac.uk, Time-stamp: <11 August 2001>
  4. % TO DO:
  5. % allow for non-trivial denominator in exact ODEs
  6. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  7. % Code to support hooks for extending the functionality of ODESolve
  8. % without needing to edit the main source code. (Based on the hooks
  9. % supported by Emacs.) [Note that in CSL, each hook must be declared
  10. % global (or fluid), even for interactive testing, otherwise boundp
  11. % does not work as expected!]
  12. % To do: Run hooks within an errorset for extra security?
  13. symbolic procedure ODESolve!-Run!-Hook(hook, args);
  14. %% HOOK is the *name* of a hook; ARGS is a *list* of arguments.
  15. %% If HOOK is a function or is bound to a function then apply it to
  16. %% ARGS; if HOOK is bound to a list of functions then apply them in
  17. %% turn to ARGS until one of them returns non-nil and return that
  18. %% value. Otherwise, return nil.
  19. if getd hook then apply(hook, args)
  20. else if boundp hook then <<
  21. hook := eval hook;
  22. if atom hook then
  23. getd hook and apply(hook, args)
  24. else
  25. begin scalar result;
  26. while hook and null(
  27. getd car hook and
  28. (result:=apply(car hook, args))) do
  29. hook := cdr hook;
  30. if hook then return result
  31. end
  32. >>$
  33. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  34. % Code to break ODE simplifier loops
  35. % ==================================
  36. fluid '(odesolve!-interchange!-list!* !*odesolve!-norecurse)$
  37. global '(ODESolve!-Standard!-x ODESolve!-Standard!-y)$
  38. ODESolve!-Standard!-x := gensym()$
  39. ODESolve!-Standard!-y := gensym()$
  40. symbolic procedure ODESolve!-Standardize(ode, y, x);
  41. %% Return the numerator of ode in true prefix form and with
  42. %% standardized variable names. (What about sign, etc.?)
  43. subst(ODESolve!-Standard!-y, y,
  44. subst(ODESolve!-Standard!-x, x, prepf numr simp!* ode))$
  45. symbolic procedure ODESimp!-Interrupt(ode, y, x);
  46. begin scalar std_ode;
  47. ode := num !*eqn2a ode; % Returns ode as expression.
  48. if member(std_ode:=ODESolve!-Standardize(ode, y, x),
  49. odesolve!-interchange!-list!*) then <<
  50. traceode "ODE simplifier loop interrupted! ";
  51. return t
  52. >>;
  53. odesolve!-interchange!-list!* := std_ode .
  54. odesolve!-interchange!-list!*
  55. end$
  56. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  57. % Top-level classification of an ODE, primarily as linear or
  58. % nonlinear.
  59. global '(ODESolve_Before_Hook ODESolve_After_Hook)$
  60. global '(ODESolve_Before_Non_Hook ODESolve_After_Non_Hook)$
  61. algebraic procedure ODESolve!*0(ode, y, x);
  62. %% Top-level general ODE solver. If no derivatives call solve?
  63. %% ***** DO NOT CALL RECURSIVELY *****
  64. symbolic begin scalar !*precise, solution, !*odesolve!-norecurse,
  65. odesolve!-interchange!-list!*, !*odesolve!-solvable!-xy;
  66. %% (odesolve!-interchange!-list!* and !*odesolve!-solvable!-xy
  67. %% are used to prevent infinite loops.)
  68. ode := num !*eqn2a ode; % returns ode as expression
  69. if (solution := or(
  70. ODESolve!-Run!-Hook('ODESolve_Before_Hook, {ode,y,x}),
  71. ODESolve!*1(ode, y, x),
  72. %% Call ODESolve!-Diff once only, not in recursive loop?
  73. %% SHOULD apply only to nonlinear ODEs?
  74. not !*odesolve_fast and ODESolve!-Diff(ode, y, x),
  75. ODESolve!-Run!-Hook('ODESolve_After_Hook, {ode,y,x})))
  76. then return solution;
  77. traceode "ODESolve cannot solve this ODE!"
  78. end$
  79. algebraic procedure ODESolve!*1(ode, y, x);
  80. %% Top-level discrimination between linear and nonlinear ODEs.
  81. %% May be called recursively.
  82. %% (NB: A product of linear factors is NONLINEAR!)
  83. symbolic if !*odesolve!-norecurse then
  84. traceode "ODESolve terminated: no recursion mode!"
  85. else if ODESimp!-Interrupt(ode, y, x) then nil else
  86. <<
  87. !*odesolve!-norecurse := !*odesolve_norecurse;
  88. traceode1 "Entering top-level general recursive solver ...";
  89. if ODE!-Linearp(ode, y) then % linear
  90. ODESolve!-linear(ode, y, x)
  91. else % nonlinear -- turn off basis solution
  92. algebraic begin scalar !*odesolve_basis, ode_factors, solns;
  93. %% Split into algebraic factors (which may lose exactness).
  94. %% For each algebraic factor, check its linearity and call
  95. %% appropriate (linear or nonlinear) main solver.
  96. %% Merge solution sets.
  97. traceode1 "Trying to factorize nonlinear ODE algebraically ...";
  98. ode_factors := factorize ode;
  99. %% { {factor, multiplicity}, ... }
  100. if length ode_factors = 1 and second first ode_factors = 1 then
  101. %% Guaranteed algebraically-irreducible nonlinear ODE ...
  102. return ODESolve!-nonlinear(ode, y, x);
  103. traceode "This is a nonlinear ODE that factorizes algebraically ",
  104. "and each distinct factor ODE will be solved separately ...";
  105. solns := {};
  106. while ode_factors neq {} do
  107. begin scalar fac;
  108. %% Discard repeated factors:
  109. if smember(y, fac := first first ode_factors) then
  110. %% Guaranteed algebraically-irreducible -- may be
  111. %% either algebraic or linear or nonlinear ODE ...
  112. if (fac := ODESolve!*2!*(fac, y, x)) then <<
  113. solns := append(solns, fac);
  114. ode_factors := rest ode_factors
  115. >> else solns := ode_factors := {}
  116. else <<
  117. if depends(fac, x) or depends(fac, y) then
  118. symbolic MsgPri("ODE factor", fac, "ignored", nil, nil);
  119. ode_factors := rest ode_factors
  120. >>;
  121. end;
  122. %% Finally check whether the UNFACTORIZED ode was exact:
  123. return
  124. if solns = {} then Odesolve!-Exact!*(ode, y, x)
  125. else solns
  126. end
  127. >>$
  128. algebraic procedure ODESolve!-FirstOrder(ode, y, x);
  129. %% Solve an ARBITRARY first-order ODE.
  130. %% (Called from various other modules.)
  131. symbolic <<
  132. ode := num !*eqn2a ode;
  133. traceode ode = 0;
  134. %% if ODE!-Linearp(ode, y) % nil <> 0 !!!
  135. %% then ODENon!-Linear1(ode, y, x)
  136. %% else ODESolve!-NonLinear1(ode, y, x)
  137. %% A nonlinear first-order ODE may need the full solver ...
  138. %% but could later arrange to pass the order rather than
  139. %% recompute it.
  140. ODESolve!*1(ode, y, x)
  141. >>$
  142. algebraic procedure ODESolve!*2!*(ode, y, x);
  143. %% Internal discrimination between algebraic or differential factor.
  144. if smember(df, ode) then % ODE
  145. ODESolve!*2(ode, y, x)
  146. else if ode = y then % Common special algebraic case,
  147. {y = 0} % e.g. solving autonomous ODEs.
  148. else solve(ode, y)$ % General algebraic case.
  149. algebraic procedure ODESolve!*2(ode, y, x);
  150. %% Internal discrimination between linear and nonlinear ODEs. Like
  151. %% ODESolve!*1 but does not attempt any algebraic factorization.
  152. symbolic <<
  153. traceode1 "Entering top-level recursive solver ",
  154. "without algebraic factorization ...";
  155. traceode ode=0;
  156. if ODE!-Linearp(ode, y) then % linear
  157. ODESolve!-linear(ode, y, x)
  158. else % nonlinear
  159. ODESolve!-nonlinear(ode, y, x)
  160. >>$
  161. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  162. % The entry point to the non-trivially nonlinear ODE solver
  163. % =========================================================
  164. algebraic procedure ODESolve!-nonlinear(ode, y, x);
  165. %% Attempt to solve an algebraically-irreducible nonlinear ODE.
  166. symbolic %% if ODESimp!-Interrupt(ode, y, x) then nil else
  167. begin scalar ode_order;
  168. ode_order := ODE!-Order(ode, y);
  169. traceode "This is a nonlinear ODE of order ", ode_order, ".";
  170. return or(
  171. ODESolve!-Run!-Hook(
  172. 'ODESolve_Before_Non_Hook, {ode,y,x,ode_order}),
  173. (if ode_order = 1 then
  174. ODESolve!-nonlinear1(ode, y, x)
  175. else
  176. %% ODESolve!-Diff(ode, y, x) or % TEMPORARY
  177. ODESolve!-nonlinearn(ode, y, x)),
  178. ODESolve!-Exact(ode, y, x, ode_order),
  179. not !*odesolve_fast and ODESolve!-Alg!-Solve(ode, y, x),
  180. not !*odesolve_fast and ODESolve!-Interchange(ode, y, x),
  181. ODESolve!-Run!-Hook(
  182. 'ODESolve_After_Non_Hook, {ode,y,x,ode_order}))
  183. end$
  184. symbolic procedure ODESolve!-Interchange(ode, y, x);
  185. %% Interchange x <--> y and try to solve.
  186. %% PROBABLY NOT DESIRABLE FOR LINEAR ODES!
  187. if !*odesolve_noswap then
  188. traceode "ODESolve terminated: no variable swap mode!"
  189. else
  190. ( begin scalar !*precise; % Can cause trouble here
  191. traceode
  192. "Interchanging dependent and independent variables ...";
  193. %% Should fully canonicalize ode before comparison!!!
  194. %% Temporarily, just use reval to at least ensure the same format.
  195. %% Cannot use aeval form because simplified flag gets reset.
  196. %% odesolve!-interchange!-list!* :=
  197. %% %% reval ode . odesolve!-interchange!-list!*;
  198. %% ODESolve!-Standardize(ode, y, x) . odesolve!-interchange!-list!*;
  199. depend1(x, y, t);
  200. algebraic begin scalar rules;
  201. rules := {odesolve!-df(y,x,~n) => 1/odesolve!-df(x,y)*
  202. odesolve!-df(odesolve!-df(y,x,n-1),y) when n > 1,
  203. odesolve!-df(y,x) => 1/odesolve!-df(x,y),
  204. odesolve!-df(y,x,1) => 1/odesolve!-df(x,y)};
  205. ode := sub(df = odesolve!-df, ode);
  206. ode := (ode where rules);
  207. ode := num sub(odesolve!-df = df, ode)
  208. end;
  209. depend1(y, x, nil); % Necessary to avoid dependence loops
  210. %% Now ode is an ode for x as a function of y
  211. traceode ode;
  212. %% %% if member(reval ode, odesolve!-interchange!-list!*) then
  213. %% if member(ODESolve!-Standardize(ode, x, y),
  214. %% odesolve!-interchange!-list!*) then
  215. %% %% Give up -- we have already interchanged variables in this
  216. %% %% ode once!
  217. %% << !*odesolve_failed := t; return algebraic {ode=0} >>;
  218. ode := ODESolve!*1(ode, x, y); % Try again ..
  219. if ode then return
  220. makelist for each soln in cdr ode join
  221. if smember(y, soln) then {soln} else {}
  222. end ) where depl!* = depl!*$
  223. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  224. % Exact equations
  225. % ===============
  226. % Solve an ODE if it is an exact first or second order ODE. Exactness
  227. % might be lost by factorizing an ode, so all exact ode routines are
  228. % gathered together here under one master routine that can be called
  229. % independently of any ODE simplification.
  230. % Replace by one general routine for any order nonlinear ODE?
  231. % The first-order code is based on code by Malcolm MacCallum.
  232. algebraic procedure ODESolve!-Exact!*(ode, y, x);
  233. %% Solve an exact first or second order nonlinear ODE of unknown
  234. %% order.
  235. ODESolve!-Exact(ode, y, x, ODE!-Order(ode, y))$
  236. algebraic procedure ODESolve!-Exact(ode, y, x, ode_order);
  237. %% Solve an exact first or second order nonlinear ODE of known
  238. %% order.
  239. begin scalar c, den_ode, result;
  240. traceode1 "Checking for an exact ode ...";
  241. c := coeff(num ode, df(y,x,ode_order));
  242. den_ode := den ode;
  243. if not depends(den_ode, x) then den_ode := 0;
  244. %% ... meaning den ode has no effect on exactness.
  245. %% if length c neq 2 or depends(c, df(y,x,n)) then return;
  246. %% NB: depends recurses indefinitely if x depends on y, i.e. after
  247. %% interchange at present. But smember nearly suffices anyway!
  248. if length c neq 2 or smember(df(y,x,ode_order), c) then return;
  249. return if ode_order = 1 then
  250. symbolic ODESolve!-Exact!-1(c, den_ode, y, x)
  251. else if ode_order = 2 then
  252. symbolic ODESolve!-Exact!-2(c, den_ode, y, x)
  253. end$
  254. symbolic procedure ODESolve!-Exact!-1(c, den_ode, y, x);
  255. %% Solves the ode if it is an exact (nonlinear) first order ode of
  256. %% the form = N dy/dx + M.
  257. ( algebraic begin scalar M, N;
  258. M := first c; N := second c;
  259. symbolic depend1(y, x, nil); % all derivatives partial
  260. if df(M,y) - df(N,x) and
  261. (not den_ode or df(M:=M/den_ode,y) - df(N:=N/den_ode,x))
  262. then return;
  263. %% traceode "This is an exact first-order ODE.";
  264. traceode "It is exact and is solved by quadrature.";
  265. return {exact1_pde(M, N, y, x) = 0}
  266. end ) where depl!* = depl!*$
  267. algebraic procedure exact1_pde(M, N, y, x);
  268. %% Return phi(x,y) such that df(phi,x) = M(x,y), df(phi,y) =
  269. %% N(x,y), required to integrate first and second order exact odes.
  270. begin scalar int_M; int_M := int(M, x);
  271. %% phi = int_M + f(y)
  272. %% => df(phi,y) = df(int_M,y) + df(f,y) = N
  273. %% => f = int(N - df(int_M,y), y)
  274. return num(int_M + int(N - df(int_M,y), y) + newarbconst())
  275. end$
  276. symbolic procedure ODESolve!-Exact!-2(c, den_ode, y, x);
  277. %% Computes a first integral of ODE if it is an exact (nonlinear)
  278. %% second order ODE of the form f(x,y,y') y'' + g(x,y,y') = 0.
  279. %% *** EXTEND THIS GENERAL CODE TO HIGHER ORDER ??? ***
  280. ( algebraic begin scalar p, f, g, h, first_int, h_x, h_y;
  281. p := gensym();
  282. f := sub(df(y,x) = p, second c);
  283. g := sub(df(y,x) = p, first c);
  284. symbolic depend1(y, x, nil); % all derivatives partial
  285. if ODESolve!-Exact!-2!-test(f, g, p, y, x)
  286. and (not den_ode or
  287. ODESolve!-Exact!-2!-test(f:=f/den_ode, g:=g/den_ode, p, y, x))
  288. then return;
  289. %% ODE is exact
  290. %% traceode "This is an exact second-order ODE for which ",
  291. %% "a first integral can be constructed:";
  292. traceode "It is exact and a first integral can be constructed ...";
  293. h := gensym();
  294. symbolic depend1(h, x, t); symbolic depend1(h, y, t);
  295. first_int := int(f, p) + h;
  296. c := df(first_int,x) + df(first_int,y)*p - g; % = 0
  297. %% Should be linear in p by construction -- equate coeffs:
  298. c := coeff(num c, p);
  299. if length c neq 2 or depends(c, p) then return
  300. traceode "but ODESolve cannot determine the arbitrary function!";
  301. %% MUST be linear in h_x and h_y by construction, so ...
  302. h_x := coeff(first c, df(h,x)); h_x := -first h_x / second h_x;
  303. h_y := coeff(second c, df(h,y)); h_y := -first h_y / second h_y;
  304. h_x := exact1_pde(h_x, h_y, y, x);
  305. symbolic depend1(y, x, t);
  306. first_int := sub(h = h_x, p = df(y,x), first_int);
  307. %% traceode first_int = 0;
  308. first_int := ODESolve!-FirstOrder(first_int, y, x);
  309. return
  310. if first_int then ODESolve!-Simp!-ArbConsts(first_int, y, x)
  311. else traceode "But ODESolve cannot solve it!"
  312. end ) where depl!* = depl!*$
  313. algebraic procedure ODESolve!-Exact!-2!-test(f, g, p, y, x);
  314. if ( (df(f,x,2) + 2p*df(f,x,y) + p^2*df(f,y,2)) -
  315. (df(g,x,p) + p*df(g,y,p) - df(g,y)) or
  316. (df(f,x,p) + p*df(f,y,p) + 2df(f,y)) - df(g,p,2) ) then 1$
  317. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  318. switch odesolve_diff$ % TEMPORARY?
  319. symbolic(!*odesolve_diff := t)$ % TEMPORARY?
  320. fluid '(!*arbvars)$
  321. algebraic procedure ODESolve!-Diff(ode, y, x);
  322. %% If the derivative of ode factorizes then try to solve each
  323. %% factor and return the solutions, otherwise return nil.
  324. %% This is the inverse of detecting an exact ode!
  325. if symbolic !*odesolve_diff then % TEMPORARY?
  326. begin scalar ode_factors, solns;
  327. load_package solve; % to allow overriding !*arbvars := t
  328. traceode1
  329. "Trying to factorize derivative of ODE algebraically ...";
  330. ode_factors := factorize num df(ode, x);
  331. %% { {factor, multiplicity}, ... }
  332. if length ode_factors = 1 and second first ode_factors = 1 then return;
  333. traceode "The derivative of the ODE factorizes algebraically ",
  334. "and each distinct factor ODE will be solved separately ...";
  335. solns := {};
  336. while ode_factors neq {} do
  337. begin scalar fac, deriv_orders, first!!arbconst, arbconsts,
  338. !*arbvars;
  339. fac := first first ode_factors; ode_factors := rest ode_factors;
  340. deriv_orders := get_deriv_orders(fac, y);
  341. %% Check for purely algebraic factor:
  342. if deriv_orders = {} then return; % no y -- ignore
  343. if deriv_orders = {0} then return
  344. for each s in solve(fac, y) do
  345. if sub(s, ode) = 0 then solns := (s = 0) . solns;
  346. first!!arbconst := !!arbconst + 1;
  347. fac := ODESolve!*2(fac, y, x); % to avoid nasty loops
  348. if not fac then return solns := ode_factors := {};
  349. arbconsts :=
  350. for i := first!!arbconst : !!arbconst collect arbconst i;
  351. %% ***** THIS WILL WORK ONLY FOR EXPLICIT SOLUTIONS *****
  352. for each soln in fac do
  353. for each s in solve(sub(soln, ode), arbconsts) do
  354. solns := sub(s, soln) . solns
  355. end;
  356. if solns neq {} then return solns;
  357. traceode "... but cannot solve all factor ODEs.";
  358. end$
  359. algebraic procedure ODESolve!-Alg!-Solve(ode, y, x);
  360. %% Try to solve algebraically for a single derivative and then
  361. %% solve each solution ode directly.
  362. begin scalar deriv, L, R, d, root_odes, solns;
  363. scalar !*fullroots, !*trigform, !*precise;
  364. %% symbolic(!*fullroots := t); % Can be VERY slow!
  365. traceode1
  366. "Trying to solve algebraically for a single derivative ...";
  367. deriv := delete(0, get_deriv_orders(ode, y));
  368. if length deriv neq 1 then return; % not a single deriv
  369. %% Now ode is an expression in df(y,x,ord) involving no other
  370. %% derivatives. Try to solve it algebraically for the
  371. %% derivative.
  372. deriv := df(y, x, first deriv);
  373. if not( smember(deriv, L:=lcof(ode,deriv)) or
  374. smember(deriv, R:=reduct(ode,deriv)) ) then
  375. if (d:=deg(ode,deriv)) = 1 then
  376. return % linear in single deriv
  377. else
  378. root_odes := % single integer power
  379. { num(deriv - (-R/L)^(1/d)*newroot_of_unity(d)) }
  380. %% Expand roots of unity later.
  381. else <<
  382. root_odes := solve(ode, deriv);
  383. if not(length root_odes > 1 or
  384. first root_multiplicities > 1) then return;
  385. %% Eventually, replace above 3 lines with this:
  386. %% root_odes := SolvePM(ode, deriv); % `use `plus_or_minus'
  387. root_odes := for each ode in root_odes collect
  388. num if symbolic eqcar(caddr ode, 'root_of) then
  389. sub(part(rhs ode, 2)=deriv, part(rhs ode, 1))
  390. else lhs ode - rhs ode
  391. >>;
  392. traceode "It can be (partially) solved algebraically ",
  393. "for the single-order derivative ",
  394. "and each `root ODE' will be solved separately ...";
  395. solns := {};
  396. while root_odes neq {} do
  397. begin scalar soln;
  398. if (soln := ODESolve!*2(first root_odes, y, x)) then <<
  399. solns := append(solns, soln);
  400. root_odes := rest root_odes
  401. >> else solns := root_odes := {}
  402. end;
  403. if solns neq {} then return solns
  404. end$
  405. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  406. % Utility procedures
  407. % ==================
  408. % Linearity and order tests, which are best kept separate!
  409. %% NB: smember in ODE!-Linearp should probably be depends!
  410. symbolic operator ODE!-Linearp$
  411. symbolic procedure ODE!-Linearp(ode, y);
  412. %% ODE is assumed to be an expression (not an equation).
  413. %% Returns t if ODE is linear in y, nil otherwise.
  414. %% Assumes on exp, mcd.
  415. ODE!-Lin!-Form!-p(numr simp!* ode, !*a2k y)$
  416. symbolic procedure ODE!-Lin!-Form!-p(sf, y);
  417. %% A standard (polynomial) form `sf' is linear if each of its terms
  418. %% is linear:
  419. domainp sf or
  420. (ODE!-Lin!-Term!-p(lt sf, y) and ODE!-Lin!-Form!-p(red sf, y))$
  421. symbolic procedure ODE!-Lin!-Term!-p(st, y);
  422. %% A standard (polynomial) term `st' is linear if either (a) its
  423. %% leading power is linear and its coefficient is independent of y,
  424. %% or (b) its leading power is independent of y and its coefficient
  425. %% is linear:
  426. begin scalar knl; knl := tvar st;
  427. return if knl eq y or (eqcar(knl, 'df) and cadr knl eq y) then
  428. %% Kernel knl is either y or a derivative of y (df y ...)
  429. tdeg st eq 1 and not depends(tc st, y)
  430. else if not depends(knl, y) then ODE!-Lin!-Form!-p(tc st, y)
  431. end$
  432. symbolic operator ODE!-Order$
  433. symbolic procedure ODE!-Order(u, y);
  434. %% u is initially an ODE, assumed to be an expression (not an
  435. %% equation). Returns its order wrt. y.
  436. if atom u then 0
  437. else if car u eq 'df and cadr u eq y then
  438. %% u = (df y x n) or (df y x)
  439. (if cdddr u then cadddr u else 1)
  440. else
  441. max(ODE!-Order(car u, y), ODE!-Order(cdr u, y))$
  442. symbolic operator get_deriv_orders$
  443. symbolic procedure get_deriv_orders(ode, y);
  444. % %% Return range of orders of derivatives df(y,x,n) in ode as the
  445. % %% algebraic list {min_ord, min_d_ord, max_ord} where min_ord
  446. % %% includes 0, and min_d_ord excludes 0.
  447. %% Return the SET of all orders of derivatives df(y,x,n) in ode as
  448. %% an unsorted algebraic list. Empty if ode freeof y.
  449. begin scalar result;
  450. ode := kernels numr simp!* ode;
  451. if null ode then return makelist nil;
  452. result := get_deriv_ords_knl(car ode, y);
  453. for each knl in cdr ode do
  454. result := union(get_deriv_ords_knl(knl, y), result);
  455. % return {'list, min_ord,
  456. % if zerop min_ord then min!* delete(0, result) else min_ord,
  457. % max!* result} where min_ord = min!* result
  458. return makelist result
  459. end$
  460. symbolic procedure get_deriv_ords_knl(knl, y);
  461. %% Return a list of all orders of derivatives df(y,x,n) in kernel
  462. %% knl, treating y as df(y,x,0).
  463. if atom knl then (if knl eq y then (0 . nil))
  464. else if car knl eq 'df then
  465. (if cadr knl eq y then
  466. (if cdddr knl then cadddr knl else 1) . nil)
  467. else
  468. ( if in_car then union(in_car, in_cdr) else in_cdr )
  469. where in_car = get_deriv_ords_knl(car knl, y),
  470. in_cdr = get_deriv_ords_knl(cdr knl, y)$
  471. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  472. % Support for an n'th root of unity operator
  473. % ==========================================
  474. algebraic operator root_of_unity, plus_or_minus$
  475. fluid '(!*intflag!*)$ % true when in the integrator
  476. % Simplify powers of these operators, but only when not in the
  477. % integrator, which seems to be upset by this:
  478. algebraic let (plus_or_minus(~tag))^2 => 1 when symbolic not !*intflag!*,
  479. (root_of_unity(~n, ~tag))^n => 1 when symbolic not !*intflag!*$
  480. % Should really be more general, e.g.
  481. % (root_of_unity(~n, ~tag))^nn => 1 when fixp(nn/n)
  482. algebraic procedure newroot_of_unity(n);
  483. if n = 0 then RedErr "zeroth roots of unity undefined"
  484. else if numberp n and (n:=abs num n) = 1 then 1
  485. else if n = 2 then
  486. plus_or_minus(newroot_of_unity_tag())
  487. else
  488. root_of_unity(n, newroot_of_unity_tag())$
  489. algebraic procedure newplus_or_minus;
  490. %% Like this for immediate evaluation, especially in symbolic mode:
  491. symbolic {'plus_or_minus, newroot_of_unity_tag()}$
  492. %% fluid '(!!root_of_unity)$ !!root_of_unity := 0$
  493. algebraic procedure newroot_of_unity_tag;
  494. %% symbolic mkid('tag_, !!root_of_unity := add1 !!root_of_unity)$
  495. symbolic mkrootsoftag()$ % defined in module solve/solve1
  496. define expand_plus_or_minus = expand_roots_of_unity$
  497. define expand_root_of_unity = expand_roots_of_unity$
  498. symbolic operator expand_roots_of_unity$
  499. flag('(expand_roots_of_unity), 'noval)$
  500. symbolic procedure expand_roots_of_unity u;
  501. begin scalar !*NoInt; !*NoInt := t; u := aeval u;
  502. return makelist union( % discard repeats
  503. for each uu in (if rlistp u then cdr u else {u}) join
  504. cdr expand_roots_of_unity1 makelist {uu}, nil)
  505. end$
  506. symbolic procedure expand_roots_of_unity1 u; % u is an rlist
  507. ( if r then expand_roots_of_unity1 makelist append(
  508. (if car r eq 'plus_or_minus then
  509. cdr subeval{{'equal, r, -1}, u}
  510. else
  511. begin scalar n, n!-1;
  512. if not fixp(n := numr simp!* cadr r) then
  513. TypErr(n, "root of unity");
  514. n!-1 := sub1 n;
  515. return for m := 1 : n!-1 join
  516. cdr algebraic sub(r = exp(i*2*pi*m/n), u)
  517. end), cdr subeval{{'equal, r, 1}, u} )
  518. else u ) where r = find_root_of_unity cdr u$
  519. symbolic procedure find_root_of_unity u; % u is a list
  520. if atom u then nil
  521. else if car u eq 'plus_or_minus then u
  522. else if car u eq 'root_of_unity and evalnumberp cadr u then u
  523. else find_root_of_unity car u or find_root_of_unity cdr u$
  524. endmodule$
  525. end$