1 /* -*- Mode: MACSYMA; Package: MAXIMA -*- */
3 /* (c) Copyright 1981 Massachusetts Institute of Technology */
5 /* The Ordinary Differential Equation Solver.
6 This package consists primarily of a set of routines taken from Moses'
7 thesis and Boyce & DiPrima for solving O.D.E.s of 1st and 2nd order.
8 The top-level routines are ODE2, IC1, IC2, and BC2. */
11 declare_translated(boundtest,noteqn,nlxy,nly,nlx,xcc2,bessel2,euler2,
12 pttest,exact2,cc2,genhom,solvebernoulli,solvehom,integfactor,exact,
13 separable,solvelnr,solve1,linear2,reduce,hom2,pr2,varp,desimp,failure,
16 ode2(eq,yold,x):=block([derivsubst:false,ynew],
17 subst(yold,ynew,ode2a(subst(ynew,yold,eq),ynew,x)))$
19 ode2a(eq,y,x):=block([de,a1,a2,a3,a4,%q%],
20 intfactor: false, method: 'none,
21 if freeof('diff(y,x,2),eq)
22 then if ftest(ode1a(eq,y,x)) then return(%q%) else return(false),
23 if derivdegree(de: desimp(lhs(eq)-rhs(eq)),y,x) # 2
24 then return(failure(msg1,eq)),
25 a1: coeff(de,'diff(y,x,2)),
26 a2: coeff(de,'diff(y,x)),
28 a4: expand(de - a1*'diff(y,x,2) - a2*'diff(y,x) - a3*y),
29 if pr2(a1) and pr2(a2) and pr2(a3) and pr2(a4) and
31 then if a4=0 then return(%q%) else return(varp(%q%,-a4/a1)),
32 if ftest(reduce(de)) then return(%q%) else return(false))$
34 ode1a(eq,y,x):=block([de,des], /* f, g, %q% */
35 if derivdegree(de: expand(lhs(eq)-rhs(eq)),y,x) # 1
36 then return(failure(msg1,eq)),
37 if linear2(de,'diff(y,x)) = false then return(failure(msg2,eq)),
39 de: solve1(des,'diff(y,x)),
40 if ftest(solvelnr(de)) then return(%q%),
41 if ftest(separable(de)) then return(%q%),
42 if ftest(integfactor(%g%,%f%)) then return(exact(%q%*%g%,%q%*%f%)),
43 /* linear2 binds %f% and %g% */
44 if linear2(des,'diff(y,x)) = false then return(failure(msg2,eq)),
45 if ftest(integfactor(%g%,%f%)) then return(exact(%q%*%g%,%q%*%f%)),
46 if ftest(solvehom(de)) then return(%q%),
47 if ftest(solvebernoulli(de)) then return(%q%),
48 if ftest(genhom(de)) then return(%q%) else return(false))$
50 desimp(eq):=block([inflag:true],
52 if atom(eq) or not(inpart(eq,0)="*") then return(expand(eq)),
53 eq: map(lambda([u], if freeof(nounify('diff),u) then 1 else u), eq),
56 pr2(%f%):=freeof(y,'diff(y,x),'diff(y,x,2),%f%)$
58 ftest(call):=is((%q%: call) # false)$
60 solve1(eq,y):=block([programmode:true],first(solve(eq,y)))$
62 /* linear2 tests for the form fx+%g% */
64 linear2(expr,x):=block([],
66 if not(freeof(x,%f%)) then return(false),
67 %g%: ratsimp(expr - %f%*x),
68 return(freeof(x,%g%)))$
70 /* variables used to denote constants: %C, %K1, %K2.
71 METHOD denotes the method of solution.
72 INTFACTOR denotes the integrating factor.
73 ODEINDEX denotes the index for Bernoulli's method or for the genhom method.
74 YP denotes the particular solution for the variation of parameters technique. */
76 /* B&DiP, pp. 13-14 */
78 solvelnr(eq):=block([%f%,%g%,w,%c],
79 if linear2(rhs(eq),y) = false then return(false),
80 w: %e^(integrate(%f%,x)),
82 return(y=w*(integrate(%g%/w,x)+%c)))$
84 /* B&DiP, pp. 29-34 */
86 separable(eq):=block([xpart:[],ypart:[],flag:false,inflag:true,%c],
88 if atom(eq) or not(inpart(eq,0)="*") then eq: [eq],
90 if freeof(x,u) then ypart: cons(u,ypart) else
91 if freeof(y,u) then xpart: cons(u,xpart) else return(flag: true),
92 if flag = true then return(false),
93 if xpart = [] then xpart: 1 else xpart: apply("*",xpart),
94 if ypart = [] then ypart: 1 else ypart: apply("*",ypart),
96 return(ratsimp(integrate(1/ypart,y)) = ratsimp(integrate(xpart,x)) + %c))$
98 /* B&DiP, pp. 34-41 */
100 integfactor(m,n):=block([b1,b2,dmdx,dmdy,dndx,dndy,dd,%e_to_numlog:true],
101 dmdy: ratsimp(diff(m,y)), dndx: ratsimp(diff(n,x)),
102 if (dd: dmdy-dndx) = 0 then return(1),
103 dmdx: ratsimp(diff(m,x)), dndy: ratsimp(diff(n,y)),
104 if dmdx-dndy=0 and dmdy+dndx=0 then return(1/(m^2 + n^2)),
105 if freeof(y, (b1: ratsimp(dd/n))) then return(%e^(integrate(b1,x))),
106 if freeof(x, (b2: ratsimp(dd/m)))
107 then return(%e^(integrate(-b2,y))) else return(false))$
109 exact(m,n):=block([a,ynew,%c],
110 intfactor: subst(yold,ynew,%q%),
111 a: integrate(ratsimp(m),x),
113 return(ratsimp(a + integrate(ratsimp(n-diff(a,y)),y)) = %c))$
115 /* B&DiP, pp. 43-44 */
117 solvehom(eq):=block([qq,a1,a2,%c],
118 a1: ratsimp(subst(x*qq,y,rhs(eq))),
119 if not(freeof(x,a1)) then return(false),
120 a2: ratsimp(subst(y/x,qq,integrate(1/(a1-qq),qq))),
121 method: 'homogeneous,
122 return(%c*x = %e^a2))$
124 /* B&DiP, p. 21, problem 15 */
126 solvebernoulli(eq):=block([a1,a2,n,%c],
127 a1: coeff(eq: expand(rhs(eq)),y,1),
128 if not(freeof(y,a1)) then return(false),
129 n: hipow(ratsimp(eq-a1*y),y),
131 if not(freeof(y,a2)) or not(freeof(x,y,n)) or n=0
132 or not(eq = expand(a1*y + a2*y^n))
135 method: 'bernoulli, odeindex: n,
136 return(y = %e^a1 * ((1-n)*integrate(a2*%e^((n-1)*a1),x) + %c) ^ (1/(1-n))))$
138 /* Generalized homogeneous equation: y' = y/x * H(yx^n)
139 Reference: Moses' thesis. */
141 genhom(eq):=block([%g%,u,n,a1,a2,a3,%c],
143 n: ratsimp(x*diff(%g%,x)/(y*diff(%g%,y))),
144 if not(freeof(x,y,n)) then return(false),
145 a1: ratsimp(subst(u/x^n,y,%g%)),
146 a2: integrate(1/(u*(n+a1)),u),
147 if not(freeof(nounify('integrate),a2)) then return(false),
148 a3: ratsimp(subst(y*x^n,u,a2)),
149 method: 'genhom, odeindex: n,
150 return(x = %c*%e^a3))$
152 /* Chain of solution methods for second order linear homogeneous equations */
154 hom2(a1,a2,a3):=block([ap,aq,pt],
155 ap: a2/a1, aq: a3/a1,
156 if ftest(cc2(ap,aq,y,x)) then return(%q%),
157 if ftest(exact2(a1,a2,a3)) then return(%q%),
158 if (pt: pttest(ap)) = false then go(end),
159 if ftest(euler2(ap,aq)) then return(%q%),
160 if ftest(bessel2(ap,aq)) then return(%q%),
162 if ftest(xcc2(ap,aq)) then return(%q%) else return(false))$
164 /* B&DiP, pp. 106-112 */
166 cc2(%f%,%g%,y,x):=block([a,sign,radexpand:'all,alpha,zero,pos,ynew,%k1,%k2],
167 if not(freeof(x,y,%f%) and freeof(x,y,%g%)) then return(false),
170 if freeof(%i,a) then sign: asksign(a)
171 else (radexpand: true, sign: 'pnz),
172 if sign = zero then return(y = %e^(-%f%*x/2) * (%k1 + %k2*x)),
174 return(y = %k1*%e^((-%f%+sqrt(a))*x/2) + %k2*%e^((-%f%-sqrt(a))*x/2)),
175 a: -a, alpha: x*sqrt(a)/2,
176 if exponentialize = false then
177 return(y = %e^(-%f%*x/2) * (%k1*sin(alpha) + %k2*cos(alpha))),
178 return(y = %e^(-%f%*x/2) * (%k1*exp(%i*alpha) + %k2*exp(-%i*alpha))))$
180 /* B&DiP, pp. 98-99, problem 17 */
182 exact2(a1,a2,a3):=block([b1,%k1,%k2],
183 if ratsimp(diff(a1,x,2) - diff(a2,x) + a3) = 0
184 then b1: %e^(-integrate(ratsimp((a2 - diff(a1,x))/a1), x))
187 return(y = %k1*b1*integrate(1/(a1*b1),x) + %k2*b1))$
189 /* B&DiP, pp. 113-114, problem 16 */
191 xcc2(ap,aq):=block([d,b1,z,radexpand:'all],
192 if aq=0 then return(false),
193 d: ratsimp((diff(aq,x) + 2*ap*aq)/(2*aq^(3/2))),
194 if freeof(x,y,d) then b1: cc2(d,1,y,z) else return(false),
195 method: 'xformtoconstcoeff,
196 return(subst(integrate(sqrt(aq),x),z,b1)))$
198 /* B&DiP, pp. 124-127 */
200 varp(soln,%g%):=block([y1,y2,y3,y4,wr,heuristic:false,%k1,%k2],
201 y1: ratsimp(subst([%k1=1,%k2=0],rhs(soln))),
202 y2: ratsimp(subst([%k1=0,%k2=1],rhs(soln))),
203 wr: y1*diff(y2,x) - y2*diff(y1,x),
204 if wr=0 then return(false),
205 if method='constcoeff and not(freeof('sin,wr)) and not(freeof('cos,wr))
206 then (heuristic: true, wr: ratsimp(trigreduce(wr))),
207 y3: ratsimp(y1*%g%/wr),
208 y4: ratsimp(y2*%g%/wr),
209 yp: ratsimp(y2*integrate(y3,x) - y1*integrate(y4,x)),
210 if heuristic=true then yp: ratsimp(trigreduce(yp)),
211 method: 'variationofparameters,
212 return(y = rhs(soln) + yp))$
214 /* Methods to reduce second-order equations free of x or y */
216 reduce(eq):=block([b1,qq],
217 b1: subst(['diff(y,x,2)=qq, 'diff(y,x)=qq], eq),
218 if freeof(y,b1) then return(nlx(eq)),
219 if freeof(x,b1) then return(nly(eq)) else return(false))$
221 /* B&DiP, p. 89, problem 1 */
223 nlx(eq):=block([de,b,a1,v,%k1,%c],
224 de: subst(['diff(y,x,2)='diff(v,x), 'diff(y,x)=v], eq),
225 if (b: ode1a(de,v,x)) = false then return(false),
226 a1: subst([v='diff(y,x),%c=%k1], b),
227 if ftest(nlxy(a1,'diff(y,x)))
228 then (method: 'freeofy, return(%q%)) else return(false))$
230 /* B&DiP, p. 89, problem 2 */
232 nly(eq):=block([de,b,a1,yz,v,%c,%k1],
233 de: subst(['diff(y,x,2)=v*'diff(v,yz), 'diff(y,x)=v, y=yz], eq),
234 if (b: ode1a(de,v,yz)) = false then return(false),
235 a1: subst([v='diff(y,x),yz=y,%c=%k1], b),
236 if ftest(nlxy(a1,'diff(y,x)))
237 then (method: 'freeofx, return(%q%)) else return(false))$
239 nlxy(eq,de):=block([programmode:true,eq1,%k2,%c],
241 eq1: maplist(lambda([zz], if ftest(ode1a(zz,y,x))
242 then subst(%k2,%c,%q%) else false),
244 if length(eq1)=1 then return(first(eq1)) else return(eq1))$
246 /* This is a start on a series of programs to recognize and
247 solve certain special classes of differential equations.
248 In particular, to start with, is the Euler, or equidimensional,
249 equation: x^2*y'' + axy' + by = 0. Actually, the form we
250 will investigate is: y'' + ay'/x + by/x^2 = 0.
251 PTTEST analyzes the y' term for a coefficient of the form
252 a/(x-pt), since we must assume that the equation may be
255 pttest(a):=block([a1,a2,a3],
256 if (a1: ratsimp(a)) = 0 then return(false),
258 if (a2: coeff(a1,x,1)) = 0 then return(false),
259 if not(freeof(x,a2)) then return(false),
261 if not(a1 = a2*x + a3) then return(false) else return(-a3/a2))$
263 euler2(a,b):=block([dc,rp,ip,alpha,beta,sign,radexpand:false,%k1,%k2,pos,zero],
264 if not(freeof(x,y,beta: ratsimp(b*(x-pt)^2))) then return(false),
265 method: 'euler, alpha: a*(x-pt),
266 dc: ratsimp((alpha-1)^2 - 4*beta),
267 rp: ratsimp(-(alpha-1)/2),
269 if sign = zero then return(y = (x-pt)^rp * (%k1 + %k2*log(x-pt))),
271 (ip: sqrt(dc)/2, return(y = %k1*(x-pt)^(rp+ip) + %k2*(x-pt)^(rp-ip))),
272 dc: -dc, ip: sqrt(dc)/2,
273 return(y = (x-pt)^rp * (%k1*sin(ip*log(x-pt)) + %k2*cos(ip*log(x-pt)))))$
275 bessel2(a,b):=block([nu,b1,intp,radexpand:'all,%k1,%k2],
276 if not(freeof(x,y,b1: ratsimp((1-b)*(x-pt)^2))) then return(false),
277 if ratsimp(a*(x-pt)) # 1 then return(false),
278 nu: sqrt(b1), method: 'bessel,
279 if nu = 1/2 then return(y = (%k1*sin(x-pt) + %k2*cos(x-pt))/sqrt(x-pt)),
281 if intp = 'yes then return(y = %k1*bessel_j(nu,x-pt) + %k2*bessel_y(nu,x-pt)),
282 return(y = %k1*bessel_j(nu,x-pt) + %k2*bessel_j(-nu,x-pt)))$
286 (noteqn(xc), noteqn(yc), boundtest('%c,%c),
287 ratsimp(subst(['%c=rhs(solve1(at(soln,[xc,yc]),%c))],soln))))$
289 bc2(soln,xa,ya,xb,yb):=
290 block([programmode:true,backsubst:true,singsolve:true,temp,%k1,%k2],
291 noteqn(xa), noteqn(ya), noteqn(xb), noteqn(yb),
292 boundtest('%k1,%k1), boundtest('%k2,%k2),
293 temp: maplist(lambda([zz], subst(zz,soln)),
294 solve([subst([xa,ya],soln),
295 subst([xb,yb],soln)],
297 if length(temp)=1 then return(first(temp)) else return(temp))$
299 ic2(soln,xa,ya,dya):=
300 block([programmode:true,backsubst:true,singsolve:true,temp,%k2,%k1,yas],
301 noteqn(xa), noteqn(ya), noteqn(dya),
302 boundtest('%k1,%k1), boundtest('%k2,%k2),
304 /* We need a dependency for the algorithm. Add it, if it is not present */
305 if diff(yas,lhs(xa)) = 0 then depends(yas,lhs(xa)),
307 /* A list of solutions. Map over the solutions. */
308 temp: map('lhs,soln)-map('rhs, soln)
310 temp: lhs(soln) - rhs(soln),
311 temp: maplist(lambda([zz], subst(zz,soln)),
312 solve(flatten([subst([xa,ya], soln),
313 subst([dya,xa,ya], diff(soln,lhs(xa)))]),
315 if length(temp)=1 then return(first(temp)) else return(temp))$
317 noteqn(x):=if atom(x) or not inpart(x,0)="="
318 then (disp(x), disp("not an equation"), error())$
321 if x#y then (disp(x), disp("must not be bound"), error())$
324 block([ynew], (if not status(feature,"ode")
325 then (ldisp(subst(yold,ynew,eq)), disp(msg)),
328 msg1: "not a proper differential equation"$
329 msg2: "first order equation not linear in y'"$