odeintfc.red 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. module odeintfc$ % Enhanced ODE solver interface
  2. % F.J.Wright@Maths.QMW.ac.uk, Time-stamp: <30 October 2000>
  3. % Use: odesolve(ode, y, x, conds, options)
  4. % or dsolve(ode, y, x, conds, options) (cf. Maple)
  5. % The first argument must evaluate to an ODE or a list of ODEs.
  6. % Each dependent variable y may be either an identifier or an
  7. % operator, such as y(x), which is replaced by a new identifier
  8. % internally. If y(x) is specified and x is not specified then x is
  9. % extracted from y(x) (cf. Maple). Equations containing operators of
  10. % the form y(x) with different arguments x are trapped, currently as
  11. % an error, until differential-delay equation solving is implemented.
  12. % If a dependent variable (y) does not depend on the independent
  13. % variable (x) then y is automatically declared to depend on x, and a
  14. % warning message to this effect is output. Derivatives are not
  15. % evaluated until this dependence has been enforced. BUT NOTE THAT
  16. % THIS DOES NOT WORK IF THE FIRST ARGUMENT IS AN ASSIGNMENT! This is
  17. % because the assignment is performed BEFORE the ode solver takes
  18. % control. This is something of an inconsistency in the current
  19. % REDUCE algebraic processing model.
  20. % All arguments after the first are optional but the order must be
  21. % preserved. If the first argument is a list of ODEs then y is
  22. % expected to be a list of dependent variables. If x is specified
  23. % then y must also be specified (first). An empty list can be used as
  24. % a place-holder argument. If x and/or y are missing then they are
  25. % parsed out of the ODE.
  26. % Thus, possible argument combinations, each of which may optionally
  27. % be followed by conds, are: ode | ode, y | ode, y, x
  28. % Currently, conditions can be specified only for a single ODE.
  29. % If specified, conds must take the form of an unordered list of
  30. % (unordered lists of) equations with either y, x, or a derivative of
  31. % y on the left. A single list of conditions need not be contained
  32. % within an outer list. Combinations of conditions are allowed.
  33. % Conditions within one (inner) list all relate to the same x value.
  34. % For example:
  35. % Boundary conditions:
  36. % {{y=y0, x=x0}, {y=y1, x=x1}, ...}
  37. % Initial conditions:
  38. % {x=x0, y=y0, df(y,x)=dy0, ...}
  39. % Combined conditions:
  40. % {{y=y0, x=x0}, {df(y,x)=dy1, x=x1}, {df(y,x)=dy2, y=y2, x=x2}, ...}
  41. % Boundary conditions on the values of y at various values of x may
  42. % also be specified by replacing the variables by equations with
  43. % single values or matching lists of values on the right, of the form:
  44. % y = y0, x = x0 | y = {y0, y1, ...}, x = {x0, x2, ...}
  45. % The final argument may be one of the identifiers
  46. % implicit, explicit, laplace, numeric, series
  47. % specifying an option. The options "implicit" and "explicit" set the
  48. % switches odesolve_implicit and odesolve_explicit locally. The other
  49. % options specify solution techniques -- they are not yet implemented.
  50. % TO DO:
  51. % Improved condition code to handle eigenvalue-type BVPs.
  52. % Solve systems of odes, calling crack where appropriate
  53. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  54. % User interface
  55. % ==============
  56. put('odesolve, 'psopfn, 'odesolve!-eval)$
  57. put('dsolve, 'psopfn, 'odesolve!-eval)$ % alternative (cf. Maple)
  58. listargp odesolve, dsolve$ % May have single list arg.
  59. symbolic procedure odesolve!-eval args;
  60. %% Establish a suitable global environment:
  61. (if !*div or !*intstr or !*factor or not !*exp or not !*mcd then
  62. NoInt2Int reval result else NoInt2Int result) where result =
  63. begin scalar !*evallhseqp, !*multiplicities, !*div, !*intstr,
  64. !*exp, !*mcd, !*factor, !*ifactor, !*precise,
  65. !*nopowers, !*algint, !*echo;
  66. %% Turn echo off to stop Win32 PSL REDUCE (only)
  67. %% outputting its trigsimp lap code at the end of
  68. %% odesolve.tst. (Don't ask!)
  69. !*evallhseqp := !*exp := !*mcd := t;
  70. return odesolve!-eval1 args
  71. end$
  72. symbolic procedure odesolve(ode, y, x);
  73. %% Direct symbolic-mode interface equivalent to MAHM's original.
  74. %% Calls odesolve!-eval to ensure correct environment.
  75. odesolve!-eval{ode, y, x}$
  76. global '(ODESolve!-tracing!-synonyms)$
  77. ODESolve!-tracing!-synonyms := '(trode trace tracing)$
  78. symbolic procedure odesolve!-eval1 args;
  79. %% args = (ode &optional y x conds)
  80. %% Parse variables from ode if necessary (like solve),
  81. %% automatically declare y to depend on x if necessary and
  82. %% optionally impose conditions on the general solution.
  83. %% Support for systems of odes partly implemented so far.
  84. ( begin scalar ode, system, y, x, yconds, xconds, conds, soln;
  85. if null args then RedErr
  86. "ODESolve requires at least one argument -- the ODE";
  87. begin scalar df_simpfn, !*uncached; !*uncached := t;
  88. % Turn off simplification of df (in case y does not yet
  89. % depend on x) before evaluating args (which may be lists):
  90. df_simpfn := get('df, 'simpfn);
  91. put('df, 'simpfn, 'simpiden);
  92. args := errorset!*({'revlis, mkquote args}, t);
  93. put('df, 'simpfn, df_simpfn);
  94. if errorp args then error1();
  95. args := car args
  96. end;
  97. ode := car args; args := cdr args;
  98. system := rlistp ode;
  99. %% Find dependent and independent variables:
  100. %% (rlistp is a smacro defined in rlisp.red)
  101. if args then << y := car args;
  102. if rlistp y then
  103. if null cdr y then % empty list - ignore
  104. y := 'empty
  105. else if rlistp cadr y or eqnp cadr y then
  106. y := nil % condition rlist
  107. else if system then
  108. y := makelist % rlist of dependent variables
  109. for each yy in cdr y collect !*a2k yy
  110. else MsgPri("ODESolve: invalid second argument",
  111. y, nil, nil, t)
  112. else if system then TypErr(y, "dependent var list")
  113. else if eqnp y then
  114. if cadr y memq 'output . ODESolve!-tracing!-synonyms then % option
  115. y := nil
  116. else << yconds := caddr y; y := !*a2k cadr y >>
  117. else if not smember(y:=!*a2k y, ode) then
  118. y := nil % option
  119. >>;
  120. if y then args := cdr args;
  121. if args and y then << x := car args;
  122. if rlistp x then
  123. if null cdr x then % empty list - ignore
  124. x := 'empty
  125. else x := nil % condition list
  126. else if eqnp x then
  127. if cadr x memq 'output . ODESolve!-tracing!-synonyms then % option
  128. x := nil
  129. else << xconds := caddr x; x := !*a2k cadr x >>
  130. else if not smember(x:=!*a2k x, ode) then
  131. x := nil % option
  132. >>;
  133. if x then args := cdr args;
  134. if y eq 'empty then y := nil;
  135. if x eq 'empty then x := nil;
  136. %% If x not given and y an operator (list) then extract x:
  137. if null x and y then
  138. if rlistp y then
  139. begin scalar yy; yy := cdr y;
  140. while yy and atom car yy do yy := cdr yy;
  141. if yy and cdar yy then x := cadar yy;
  142. if not idp x then x := nil
  143. end
  144. else if pairp y and cdr y then x := cadr y;
  145. %% Finally, attempt to parse variables from ode if necessary:
  146. if null y or null x then
  147. %%%%% NOTE: ODE ALREADY AEVAL'ED ABOVE
  148. %%%%% so some of the following is now redundant !!!!!
  149. begin scalar df_simpfn, k_list, df_list;
  150. % Turn off simplification of df (in case y does not yet
  151. % depend on x) before evaluating ode, which may be a list:
  152. df_simpfn := get('df, 'simpfn);
  153. put('df, 'simpfn, 'simpiden);
  154. k_list := errorset!*({'get_k_list, mkquote ode}, t);
  155. put('df, 'simpfn, df_simpfn);
  156. if errorp k_list then error1() else k_list := car k_list;
  157. df_list := get_op_knl('df, car k_list);
  158. for each knl in cdr k_list do
  159. df_list := union(df_list, get_op_knl('df, knl));
  160. %% df_list is set of derivatives in ode(s).
  161. if null df_list then RedErr
  162. "No derivatives found -- use solve instead.";
  163. %% df_list = ((df y x ...) ... (df z x ...) ... )
  164. if null y then <<
  165. y := cadar df_list . nil;
  166. for each el in cdr df_list do
  167. if not member(cadr el, y) then y := cadr el . y;
  168. %% y is a list at this point.
  169. if system then
  170. if length ode < length y then
  171. RedErr "ODESolve: under-determined system of ODEs."
  172. else y := makelist y % algebraic list of vars
  173. else
  174. if cdr y then
  175. MsgPri("ODESolve -- too many dependent variables:",
  176. makelist y, nil, nil, t)
  177. else y := car y; % single var
  178. MsgPri("Dependent var(s) assumed to be", y, nil, nil, nil)
  179. >>;
  180. if null x then <<
  181. x := caddar df_list;
  182. MsgPri("Independent var assumed to be", x, nil, nil, nil)
  183. >>;
  184. end;
  185. %% Process the ode (re-simplifying derivatives):
  186. EnsureDependency(y, x);
  187. ode := aeval ode;
  188. %% !*eqn2a is defined in alg.red
  189. if system then
  190. if length ode > 2 then
  191. %% RedErr "Solving a system of ODEs is not yet supported."
  192. %% Skip conditions TEMPORARILY!
  193. return ODESolve!-Depend(
  194. makelist for each o in cdr ode collect !*eqn2a o, y, x, nil)
  195. else
  196. << ode := !*eqn2a cadr ode; y := cadr y >>
  197. else ode := !*eqn2a ode;
  198. %% Process conditions (re-simplifying derivatives):
  199. if args then
  200. if rlistp(conds := aeval car args) then <<
  201. args := cdr args;
  202. conds := if not rlistp cadr conds then conds . nil
  203. else cdr conds
  204. >> else conds := nil;
  205. %% Now conds should be a lisp list of rlists (of equations).
  206. if yconds then
  207. yconds := if rlistp yconds then cdr yconds else yconds . nil;
  208. if xconds then
  209. xconds := if rlistp xconds then cdr xconds else xconds . nil;
  210. %% Concatenate separate x & y conds onto conds list:
  211. while yconds and xconds do <<
  212. conds := {'list, {'equal, x, car xconds},
  213. {'equal, y, car yconds}} . conds;
  214. yconds := cdr yconds; xconds := cdr xconds
  215. >>;
  216. if yconds or xconds then
  217. RedErr "Different condition list lengths";
  218. if conds then
  219. %% Move this into odesolve!-with!-conds?
  220. conds := makelist odesolve!-sort!-conds(conds, y, x);
  221. %% Process remaining control option arguments:
  222. while args do
  223. begin scalar arg; arg := car args; args := cdr args;
  224. if eqnp arg then % equation argument
  225. if cadr arg eq 'output then
  226. args := caddr arg . args
  227. else if cadr arg memq ODESolve!-tracing!-synonyms then
  228. !*trode := caddr arg
  229. else MsgPri("Invalid ODESolve option", arg,
  230. "ignored.", nil, nil)
  231. % keyword argument
  232. else if arg memq
  233. '(implicit explicit expand noint verbose
  234. basis noswap norecurse fast check) then
  235. set(mkid('!*odesolve_, arg), t)
  236. else if arg eq 'algint then on1 'algint
  237. else if arg eq 'full or !*odesolve_full then
  238. !*odesolve_expand := !*odesolve_explicit := t
  239. else if arg memq ODESolve!-tracing!-synonyms then !*trode := t
  240. else if arg memq '(laplace numeric series) then
  241. RedErr{"ODESolve option", arg, "not yet implemented."}
  242. %% Pass remaining args to routine called
  243. else RedErr{"Invalid ODESolve option", arg}
  244. end;
  245. if !*odesolve_verbose then algebraic <<
  246. write "ODE: ", num ode=0;
  247. write "Dependent variable: ", y, "; independent variable: ", x;
  248. write "Conditions: ", symbolic(conds or "none");
  249. >>;
  250. %% Rationalize conflicting options:
  251. %% Conditions override basis
  252. if conds then !*odesolve_basis := nil;
  253. %% %% Basis overrides explicit
  254. %% if !*odesolve_basis then !*odesolve_explicit := nil;
  255. %% Finally, solve the ode!
  256. if not getd 'ODESolve!*0 then % for testing
  257. return {'ODESolve, ode, y, x, conds};
  258. %% soln := if conds then
  259. %% odesolve!-with!-conds(ode, y, x, conds)
  260. %% else odesolve!-depend(ode, y, x);
  261. if null(soln := ODESolve!-Depend(ode, y, x, conds)) then
  262. return algebraic {num ode=0};
  263. %% Done as follows because it may be easier to solve after
  264. %% imposing conditions than before, and it would be necessary to
  265. %% remove root_of's before imposing conditions anyway.
  266. if !*odesolve_explicit and not ODESolve!-basisp soln then
  267. soln := ODESolve!-Make!-Explicit(soln, y, conds);
  268. if !*odesolve_expand then
  269. soln := expand_roots_of_unity soln;
  270. if !*odesolve_check then
  271. ODE!-Soln!-Check(if !*odesolve_noint then NoInt2Int soln else soln,
  272. ode, y, x, conds) where !*noint = t;
  273. return soln
  274. end ) where !*odesolve_implicit = !*odesolve_implicit,
  275. !*odesolve_explicit = !*odesolve_explicit,
  276. !*odesolve_expand = !*odesolve_expand,
  277. !*trode = !*trode,
  278. !*odesolve_noint = !*odesolve_noint,
  279. !*odesolve_verbose = !*odesolve_verbose,
  280. !*odesolve_basis = !*odesolve_basis,
  281. !*odesolve_noswap = !*odesolve_noswap,
  282. !*odesolve_norecurse = !*odesolve_norecurse,
  283. !*odesolve_fast = !*odesolve_fast,
  284. !*odesolve_check = !*odesolve_check$
  285. symbolic procedure Odesolve!-Make!-Explicit(solns, y, conds);
  286. <<
  287. %% SHOULD PROBABLY CHECK THAT Y IS NOT INSIDE AN UNEVALUATED
  288. %% INTEGRAL BEFORE TRYING TO SOLVE FOR IT -- IT SEEMS TO UPSET
  289. %% SOLVE!
  290. solns := for each soln in cdr solns join
  291. if cadr soln eq y then {soln} else <<
  292. %% soln is an implicit solution of ode for y
  293. %% for each s in cdr reval aeval {'solve, soln, y} join
  294. %% %% Make this test optional?
  295. %% if eqcar(caddr s, 'root_of) or eval('and .
  296. %% mapcar(cdr expand_roots_of_unity subeval{s, ode},
  297. %% 'zerop))
  298. %% then {s}
  299. %% else if !*trode then algebraic write "Solution ", s,
  300. %% " discarded -- does not satisfy ODE";
  301. traceode
  302. "Solution before trying to solve for dependent variable is ",
  303. soln;
  304. cdr reval aeval {'solve, soln, y}
  305. >>;
  306. %% It is reasonable to return root_of's here.
  307. %% Solving can produce duplicates, so ...
  308. %% solns := union(solns, nil); % union still necessary?
  309. %% Check that each explicit solution still satisfies any
  310. %% conditions:
  311. if conds then
  312. for each cond in cdr conds do % each cond is an rlist
  313. begin scalar xcond;
  314. xcond := cadr cond;
  315. cond := makelist for each c in cddr cond collect !*eqn2a c;
  316. solns := for each s in solns join
  317. if eqcar(caddr s, 'root_of) or
  318. union(cdr %% trig_simplify
  319. subeval{xcond, subeval{s, cond}}, nil) = {0}
  320. then {s}
  321. else algebraic traceode "Solution ", s,
  322. " discarded -- does not satisfy conditions";
  323. end;
  324. makelist solns
  325. >>$
  326. % Should now be able to use the standard package `trigsimp' instead!
  327. algebraic procedure trig_simplify u;
  328. u where tan_half_angle_rules$
  329. algebraic(tan_half_angle_rules := {
  330. sin(~u) => 2tan(u/2)/(1+tan(u/2)^2),
  331. cos(~u) => (1-tan(u/2)^2)/(1+tan(u/2)^2) })$
  332. %% Cannot include tan rule -- recursive!
  333. symbolic procedure get_k_list ode;
  334. %% Return set of all top-level kernels in ode or rlist of odes.
  335. %% (Do not cache to ensure derivatives are [eventually] evaluated
  336. %% properly!)
  337. begin scalar k_list, !*uncached; !*uncached := t;
  338. %% Do not make an assignment twice:
  339. if eqcar(ode, 'setk) then ode := caddr ode;
  340. if rlistp(ode := reval ode) then <<
  341. k_list := get_k_list1 cadr ode;
  342. for each el in cddr ode do
  343. k_list := union(k_list, get_k_list1 el)
  344. >>
  345. else k_list := get_k_list1 ode;
  346. return k_list
  347. end$
  348. symbolic procedure get_k_list1 ode;
  349. union(kernels numr o, kernels denr o)
  350. where o = simp !*eqn2a ode$
  351. symbolic procedure get_op_knl(op, knl);
  352. %% Return set of all operator kernels within knl with op as car.
  353. if pairp knl then
  354. if car knl eq op then knl . nil
  355. else
  356. ( if op_in_car then union(op_in_car, op_in_cdr) else op_in_cdr )
  357. where op_in_car = get_op_knl(op, car knl),
  358. op_in_cdr = get_op_knl(op, cdr knl)$
  359. symbolic procedure EnsureDependency(y, x);
  360. for each yy in (if rlistp y then cdr y else y . nil) do
  361. if not depends(yy, x) then <<
  362. MsgPri("depend", yy, ",", x, nil);
  363. depend1(yy, x, t)
  364. >>$
  365. symbolic procedure odesolve!-sort!-conds(conds, y, x);
  366. %% conds is a lisp list of rlists of condition equations.
  367. %% Return a canonical condition list.
  368. %% Collect conditions at the same value of x, check them for
  369. %% consistency and sort them by increasing order of derivative.
  370. begin scalar cond_alist;
  371. for each cond in conds do
  372. begin scalar x_cond, y_conds, x_alist;
  373. if not rlistp cond then TypErr(cond, "ode condition");
  374. %% Extract the x condition:
  375. y_conds := for each c in cdr cond join
  376. if not CondEq(c, y, x) then
  377. TypErr(c, "ode condition equation")
  378. else if cadr c eq x then << x_cond := c; nil >>
  379. else c . nil;
  380. if null x_cond then
  381. MsgPri(nil, x, "omitted from ode condition", cond, t);
  382. if null y_conds then
  383. MsgPri(nil, y, "omitted from ode condition", cond, t);
  384. %% Build the new condition alist, with the x condition as key:
  385. if (x_alist := assoc(x_cond, cond_alist)) then
  386. nconc(x_alist, y_conds)
  387. else cond_alist := (x_cond . y_conds) . cond_alist
  388. end;
  389. %% Now cond_alist is a list of lists of equations, each
  390. %% beginning with a unique x condition.
  391. %% Sort the lists and return a list of rlists:
  392. return for each cond in cond_alist collect makelist
  393. if null cddr cond then cond else car cond .
  394. begin scalar sorted, next_sorted, this, next, result;
  395. sorted := sort(cdr cond, 'lessp!-deriv!-ord);
  396. %% sorted is a list of equations.
  397. while sorted and (next_sorted := cdr sorted) do <<
  398. if cadr(this := car sorted) eq
  399. cadr(next := car next_sorted) then
  400. %% Two conds have same lhs, so ...
  401. ( if caddr this neq caddr next then
  402. MsgPri("Inconsistent conditions:",
  403. {'list, this, next}, "at", car cond, t) )
  404. % otherwise ignore second copy
  405. else result := this . result;
  406. sorted := next_sorted
  407. >>;
  408. return reversip(next . result)
  409. end
  410. end$
  411. symbolic procedure CondEq(c, y, x);
  412. %% Return true if c is a valid condition equation for y(x).
  413. %% cf. eqexpr in alg.red
  414. eqexpr c and ( (c := cadr c) eq x or c eq y or
  415. (eqcar(c, 'df) and cadr c eq y and caddr c eq x
  416. %% Is the following test overkill?
  417. and (null cdddr c or fixp cadddr c)) )$
  418. symbolic procedure lessp!-deriv!-ord(a, b);
  419. %% (y=y0) < (df(y,x)=y1) and df(y,x,m)=ym < df(y,x,n)=yn iff m < n
  420. %% But y might be a kernel rather than an identifier!
  421. if atom(a := cadr a) then % a = (y=?)
  422. not atom cadr b % b = (df(y,x,...)=?)
  423. else if atom(b := cadr b) then % b = (y=?)
  424. not atom cadr a % a = (df(y,x,...)=?)
  425. else if not(car a eq 'df) then % a = (y(x)=?)
  426. car b eq 'df % b = (df(y(x),x,...)=?)
  427. else % a = (df(y,x,...)=?), any y
  428. car b eq 'df and % b = (df(y,x,...)=?)
  429. if null(a := cdddr a) then % a = (df(y,x)=?)
  430. cdddr b % b = (df(y,x,n)=?), 1 < n
  431. else % a = (df(y,x,m)=?), m > 1
  432. (b := cdddr b) and
  433. car a < car b$ % b = (df(y,x,n)=?), m < n
  434. %%% THE FOLLOWING PROCEDURE SHOULD PROBABLY INCLUDE THE CODE TO MAKE
  435. %%% SOLUTIONS EXPLICIT BEFORE RESTORING OPERATOR FORMS FOR Y.
  436. symbolic procedure ODESolve!-Depend(ode, y, x, conds);
  437. %% Check variables and dependences before really calling odesolve.
  438. %% If y is an operator kernel then check whether ode is a
  439. %% differential-delay equation, and if not solve ode with y
  440. %% replaced by an identifier.
  441. ( begin scalar xeqt, ylist, sublist;
  442. y := if rlistp ode then cdr y else y . nil;
  443. %% Using `t' as a variable causes trouble when checking
  444. %% dependence of *SQ forms, which may contain `t' as their last
  445. %% element, so...
  446. if x eq t then <<
  447. xeqt := t; x := gensym();
  448. for each yy in y do if idp yy then depend1(yy, x, t);
  449. %% Cannot simply use `sub' on independent variables in
  450. %% derivatives, so...
  451. ode := subst(x, t, reval ode); % reval subst?
  452. if conds then conds := subst(x, t, reval conds); % reval subst?
  453. sublist := (t.x) . sublist
  454. >>;
  455. for each yy in y do
  456. if idp yy and not(yy eq t) then <<
  457. %% Locally and quietly remove any spurious inverse
  458. %% implicit dependence of x on y:
  459. ylist := yy . ylist;
  460. depend1(x, yy, nil) where !*msg = nil;
  461. >> else % replace variable
  462. begin scalar yyy;
  463. yyy := gensym(); depend1(yyy, x, t);
  464. ylist := yyy . ylist;
  465. put(yyy, 'odesolve!-depvar, yy); % for later access
  466. sublist := (yy.yyy) . sublist;
  467. if xeqt then yy := subeval{{'equal,t,x}, yy};
  468. odesolve!-delay!-check(ode, yy);
  469. ode := subeval{{'equal,yy,yyy}, ode};
  470. if conds then conds := subeval{{'equal,yy,yyy}, conds}
  471. end;
  472. ylist := reverse ylist;
  473. ode := if rlistp ode then
  474. ODESolve!-System(cdr ode, ylist, x)
  475. else if conds then
  476. odesolve!-with!-conds(ode, car ylist, x, conds)
  477. else ODESolve!*0(ode, car ylist, x);
  478. if null ode then return;
  479. if sublist then
  480. begin scalar !*NoInt;
  481. %% Substitute into derivatives and integrals
  482. %% (and turn off integration for speed).
  483. ode := reval ode; % necessary?
  484. for each s in sublist do
  485. ode := subst(car s, cdr s, ode);
  486. %% ode := reval ode; % necessary?
  487. end;
  488. return ode
  489. end ) where depl!* = depl!*$
  490. symbolic procedure ODESolve!-System(ode, y, x);
  491. %% TEMPORARY
  492. {'ODESolve!-System, makelist ode, makelist y, x}$
  493. algebraic operator ODESolve!-System$
  494. symbolic procedure odesolve!-delay!-check(ode, y);
  495. %% Check that ode is not a differential-delay equation in y,
  496. %% i.e. check that every occurrence of the operator y = y(x) has
  497. %% the same argument (without any shifts). This could be used as a
  498. %% hook to call an appropriate solver -- if there were one!
  499. begin scalar odelist;
  500. odelist := if rlistp ode then cdr ode else ode . nil;
  501. for each ode in odelist do
  502. ( for each knl in kernels numr simp ode do
  503. for each yy in get_op_knl(y_op, knl) do
  504. if not(yy eq y) then
  505. MsgPri("Arguments of", y_op, "differ --",
  506. "solving delay equations is not implemented.", t) )
  507. where y_op = car y
  508. end$
  509. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  510. % Impose initial/boundary conditions
  511. % ==================================
  512. % A first attempt to impose initial/boundary conditions on the
  513. % solution of a single ode returned by odesolve.
  514. % Solving with conditions provides access to the general solution as
  515. % the value of the global algebraic variable ode_solution.
  516. % If the solution is explicit then the following code could be
  517. % simplified and should be slightly more efficient, but is it worth
  518. % testing for an explicit solution and adding the special case code?
  519. algebraic procedure odesolve!-with!-conds(ode, y, x, conds);
  520. % conds must be a list of ordered lists of the form
  521. % {x=x0, y=y0, df(y,x)=y1, df(y,x,2)=y2, ...}.
  522. % All conditions applied at the same value of x must be collected
  523. % into the same list.
  524. % More generality is allowed only by odesolve!-eval.
  525. % This code could perhaps be more efficient by building a list of
  526. % all required derivatives of the ode solution once and for all?
  527. begin scalar first!!arbconst, arbconsts;
  528. first!!arbconst := !!arbconst + 1;
  529. %% Find the general solution of the ode and assign it to the
  530. %% global algebraic variable ode_solution:
  531. %1.03% ode_solution := odesolve!-depend(ode, y, x);
  532. ode_solution := symbolic ODESolve!*0(ode, y, x);
  533. if not ode_solution then return;
  534. traceode "General solution is ", ode_solution;
  535. traceode "Applying conditions ", conds;
  536. arbconsts :=
  537. for i := first!!arbconst : !!arbconst collect arbconst i;
  538. return for each soln in ode_solution join
  539. odesolve!-with!-conds1(soln, y, x, conds, arbconsts)
  540. end$
  541. algebraic procedure odesolve!-with!-conds1(soln, y, x, conds, arbconsts);
  542. begin scalar arbconsteqns;
  543. %% Impose the conditions (care is needed if the solution is
  544. %% implicit):
  545. arbconsteqns := for each cond in conds join
  546. begin scalar xcond, ycond, dfconds, arbconsteqns;
  547. xcond := first cond; cond := rest cond;
  548. ycond := first cond;
  549. if lhs ycond = y then cond := rest cond else ycond := 0;
  550. %% Now cond contains only conditions on derivatives.
  551. arbconsteqns :=
  552. if ycond then % Impose the condition on y:
  553. {sub(xcond := {xcond, ycond}, soln)}
  554. else {};
  555. dfconds := {};
  556. %% Impose the conditions on df(y, x, n). If the solution
  557. %% is implicit, then in general all lower derivatives will
  558. %% be introduced, so ...
  559. while cond neq {} do
  560. begin scalar dfcond, result;
  561. %% dfcond : next highest derivative
  562. %% result : of substituting for all derivatives
  563. %% dfconds : all derivatives so far including this one
  564. dfcond := first cond; cond := rest cond;
  565. dfconds := dfcond . dfconds;
  566. %% All conditions on derivatives are handled before
  567. %% conditions on x and y to protect against
  568. %% substituting for x or y in df(y,x,...):
  569. result := sub(dfconds, map(y => lhs dfcond, soln));
  570. if not(result freeof df) then % see comment below
  571. RedErr "Cannot apply conditions";
  572. arbconsteqns := sub(xcond, result) . arbconsteqns
  573. end;
  574. return arbconsteqns
  575. end;
  576. %% Solve for the arbitrary constants:
  577. arbconsts := solve(arbconsteqns, arbconsts);
  578. %% ***** SHOULD CHECK THAT THE SOLUTION HAS SUCCEEDED! *****
  579. %% and substitute each distinct arbconst solution set into the
  580. %% general ode solution:
  581. return for each cond in arbconsts collect
  582. if rhs soln = 0 then % implicit solution
  583. num sub(cond, lhs soln) = 0
  584. else
  585. sub(cond, soln)
  586. end$
  587. %% The above df error can happen only if the solution is implicit and
  588. %% a derivative is missing from the sequence, which is unlikely.
  589. %% Should try to recover by computing the value of the missing
  590. %% derivative from the conditions on lower order derivatives, and
  591. %% letting solve eliminate them. Try this later IF it ever proves
  592. %% necessary.
  593. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  594. % Check the solution set
  595. % ======================
  596. % This code checks that the solution satisfies the ODE and that a
  597. % general solution is general.
  598. % A `solution' is either a basis solution:
  599. % {{b1(x), b2(x), ..., bn(x)}, PI(x)}; PI may be omitted if zero
  600. % or a list of component solutions.
  601. algebraic procedure ODE!-Soln!-Check(soln, ode, y, x, conds);
  602. %% SOLN is a LIST of solutions; ODE is a differential EXPRESSION; Y
  603. %% is the dependent variable; X is the independent variable; CONDS
  604. %% is true if conditions were specified.
  605. begin scalar n, !*allowdfint, !*expanddf;
  606. symbolic(!*allowdfint := !*expanddf := t);
  607. ode := num !*eqn2a ode; % returns ode as expression
  608. %% Should compute order `on demand' in ODE!-Comp!-Soln!-Fails.
  609. n := ODE!-Order(ode, y);
  610. if symbolic ODESolve!-basisp soln then <<
  611. %% Basis solution (of linear ODE).
  612. %% Only arises as general solution.
  613. %% Remove contribution from PI if there is one:
  614. if arglength soln = 2 and second soln then
  615. ode := num sub(y = y + second soln, ode);
  616. if length(soln := first soln) neq n then
  617. write "ODESolve warning - ",
  618. "wrong number of functions in basis!";
  619. %% Test each basis function in turn:
  620. for each s in soln do
  621. if (s:=sub(y = s, ode)) and trigsimp s then
  622. write "ODESolve warning - ",
  623. "basis function may not satisfy ODE: ", s
  624. >> else <<
  625. %% List of component solutions.
  626. %% Check generality:
  627. if not conds and ODESolve!-arbconsts soln < n then
  628. write "ODESolve warning - ",
  629. "too few arbitrary constants in general solution!";
  630. for each s in soln do
  631. if ODE!-Comp!-Soln!-Fails(s, ode, y, x, n) then
  632. write "ODESolve warning - ",
  633. "component solution may not satisfy ODE: ", s;
  634. >>
  635. end$
  636. % Each component solution may be
  637. % explicit: y = f(x)
  638. % implicit: f(x,y) = g(x,y); rhs MAY be 0
  639. % unsolved: ode = 0, but CAN THIS CASE ARISE?
  640. % parametric: {y = g(p), x = f(p), p}
  641. algebraic procedure ODE!-Comp!-Soln!-Fails(soln, ode, y, x, n);
  642. %% SOLN is a SINGLE component solution; ODE is a differential
  643. %% EXPRESSION; Y is the dependent variable; X is the independent
  644. %% variable; N is the order of ODE.
  645. if symbolic eqnp soln then % explicit, implicit or unsolved
  646. if lhs soln = y and rhs soln freeof y then
  647. % explicit: y = f(x)
  648. (if (ode := sub(soln, ode)) then trigsimp ode)
  649. else if rhs soln = 0 and lhs soln = ode then
  650. 1 % unsolved: ode = 0
  651. else % implicit: f(x,y) = 0
  652. begin scalar derivs, deriv;
  653. %% Construct in `derivs' a list of successive derivatives of
  654. %% the implicit solution f(x,y) up to the order of the ODE in
  655. %% decreasing order; each expression is linear in the highest
  656. %% derivative.
  657. derivs := {soln := num !*eqn2a soln};
  658. for i := 1 : n do
  659. derivs := (soln:=num df(soln,x)) . derivs;
  660. %% Substitute for each derivative in ODE in turn in
  661. %% decreasing order until the result is zero; if not the
  662. %% solution fails.
  663. while n > 0 and <<
  664. deriv := solve(first derivs, df(y,x,n)); % linear
  665. if deriv = {} then 0 else
  666. ode := num sub(first deriv, ode) >> do <<
  667. n := n - 1;
  668. derivs := rest derivs
  669. >>;
  670. if deriv = {} then <<
  671. write "ODESolve warning - cannot compute ", df(y,x,n);
  672. return 1
  673. >>;
  674. derivs := first derivs;
  675. ode := (ode where derivs => 0); % for tracing
  676. return ode % 0 for good solution
  677. end
  678. else if symbolic(rlistp soln and eqnp cadr soln) then
  679. % parametric: {y = g(p), x = f(p), p}
  680. begin scalar xx, yy, p, dp!/dx, deriv, derivs;
  681. yy := rhs first soln; % Should not depend on ordering!
  682. xx := rhs second soln; % Should not depend on ordering!
  683. p := third soln; % parameter
  684. %% Construct in `derivs' a list of successive derivatives of the
  685. %% parametric solution (yy,xx) up to the order of the ODE in
  686. %% decreasing order.
  687. dp!/dx := 1/df(xx,p);
  688. derivs := {deriv:=yy};
  689. for i := 1 : n do
  690. derivs := (deriv:=dp!/dx*df(deriv,p)) . derivs;
  691. %% Substitute for each derivative in ODE in turn in
  692. %% decreasing order until the result is zero; if not the
  693. %% solution fails.
  694. while n > 0 and (ode := num sub(df(y,x,n)=first derivs, ode)) do <<
  695. n := n - 1;
  696. derivs := rest derivs
  697. >>;
  698. return sub(y=yy, x=xx, ode)
  699. end
  700. else write "ODESolve warning - invalid solution type: ", soln$
  701. %% Code to find the actual number of arbitrary constants in a solution:
  702. fluid '(ODESolve!-arbconst!-args)$
  703. symbolic operator ODESolve!-arbconsts$
  704. symbolic procedure ODESolve!-arbconsts u;
  705. %% Return the number of distinct arbconsts in any sexpr u.
  706. begin scalar ODESolve!-arbconst!-args;
  707. ODESolve!-arbconsts1 u;
  708. return length ODESolve!-arbconst!-args
  709. end$
  710. symbolic procedure ODESolve!-arbconsts1 u;
  711. %% Collect all the indices of arbconsts in u into a set in the
  712. %% fluid variable ODESolve!-arbconst!-args.
  713. if not atom u then
  714. if car u eq 'arbconst then
  715. (if not member(cadr u, ODESolve!-arbconst!-args) then
  716. ODESolve!-arbconst!-args := cadr u . ODESolve!-arbconst!-args)
  717. else
  718. << ODESolve!-arbconsts1 car u; ODESolve!-arbconsts1 cdr u >>$
  719. endmodule$
  720. end$