Coverage for /usr/lib/python3/dist-packages/sympy/solvers/ode/single.py: 23%

1114 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1# 

2# This is the module for ODE solver classes for single ODEs. 

3# 

4 

5from __future__ import annotations 

6from typing import ClassVar, Iterator 

7 

8from .riccati import match_riccati, solve_riccati 

9from sympy.core import Add, S, Pow, Rational 

10from sympy.core.cache import cached_property 

11from sympy.core.exprtools import factor_terms 

12from sympy.core.expr import Expr 

13from sympy.core.function import AppliedUndef, Derivative, diff, Function, expand, Subs, _mexpand 

14from sympy.core.numbers import zoo 

15from sympy.core.relational import Equality, Eq 

16from sympy.core.symbol import Symbol, Dummy, Wild 

17from sympy.core.mul import Mul 

18from sympy.functions import exp, tan, log, sqrt, besselj, bessely, cbrt, airyai, airybi 

19from sympy.integrals import Integral 

20from sympy.polys import Poly 

21from sympy.polys.polytools import cancel, factor, degree 

22from sympy.simplify import collect, simplify, separatevars, logcombine, posify # type: ignore 

23from sympy.simplify.radsimp import fraction 

24from sympy.utilities import numbered_symbols 

25from sympy.solvers.solvers import solve 

26from sympy.solvers.deutils import ode_order, _preprocess 

27from sympy.polys.matrices.linsolve import _lin_eq2dict 

28from sympy.polys.solvers import PolyNonlinearError 

29from .hypergeometric import equivalence_hypergeometric, match_2nd_2F1_hypergeometric, \ 

30 get_sol_2F1_hypergeometric, match_2nd_hypergeometric 

31from .nonhomogeneous import _get_euler_characteristic_eq_sols, _get_const_characteristic_eq_sols, \ 

32 _solve_undetermined_coefficients, _solve_variation_of_parameters, _test_term, _undetermined_coefficients_match, \ 

33 _get_simplified_sol 

34from .lie_group import _ode_lie_group 

35 

36 

37class ODEMatchError(NotImplementedError): 

38 """Raised if a SingleODESolver is asked to solve an ODE it does not match""" 

39 pass 

40 

41 

42class SingleODEProblem: 

43 """Represents an ordinary differential equation (ODE) 

44 

45 This class is used internally in the by dsolve and related 

46 functions/classes so that properties of an ODE can be computed 

47 efficiently. 

48 

49 Examples 

50 ======== 

51 

52 This class is used internally by dsolve. To instantiate an instance 

53 directly first define an ODE problem: 

54 

55 >>> from sympy import Function, Symbol 

56 >>> x = Symbol('x') 

57 >>> f = Function('f') 

58 >>> eq = f(x).diff(x, 2) 

59 

60 Now you can create a SingleODEProblem instance and query its properties: 

61 

62 >>> from sympy.solvers.ode.single import SingleODEProblem 

63 >>> problem = SingleODEProblem(f(x).diff(x), f(x), x) 

64 >>> problem.eq 

65 Derivative(f(x), x) 

66 >>> problem.func 

67 f(x) 

68 >>> problem.sym 

69 x 

70 """ 

71 

72 # Instance attributes: 

73 eq = None # type: Expr 

74 func = None # type: AppliedUndef 

75 sym = None # type: Symbol 

76 _order = None # type: int 

77 _eq_expanded = None # type: Expr 

78 _eq_preprocessed = None # type: Expr 

79 _eq_high_order_free = None 

80 

81 def __init__(self, eq, func, sym, prep=True, **kwargs): 

82 assert isinstance(eq, Expr) 

83 assert isinstance(func, AppliedUndef) 

84 assert isinstance(sym, Symbol) 

85 assert isinstance(prep, bool) 

86 self.eq = eq 

87 self.func = func 

88 self.sym = sym 

89 self.prep = prep 

90 self.params = kwargs 

91 

92 @cached_property 

93 def order(self) -> int: 

94 return ode_order(self.eq, self.func) 

95 

96 @cached_property 

97 def eq_preprocessed(self) -> Expr: 

98 return self._get_eq_preprocessed() 

99 

100 @cached_property 

101 def eq_high_order_free(self) -> Expr: 

102 a = Wild('a', exclude=[self.func]) 

103 c1 = Wild('c1', exclude=[self.sym]) 

104 # Precondition to try remove f(x) from highest order derivative 

105 reduced_eq = None 

106 if self.eq.is_Add: 

107 deriv_coef = self.eq.coeff(self.func.diff(self.sym, self.order)) 

108 if deriv_coef not in (1, 0): 

109 r = deriv_coef.match(a*self.func**c1) 

110 if r and r[c1]: 

111 den = self.func**r[c1] 

112 reduced_eq = Add(*[arg/den for arg in self.eq.args]) 

113 if not reduced_eq: 

114 reduced_eq = expand(self.eq) 

115 return reduced_eq 

116 

117 @cached_property 

118 def eq_expanded(self) -> Expr: 

119 return expand(self.eq_preprocessed) 

120 

121 def _get_eq_preprocessed(self) -> Expr: 

122 if self.prep: 

123 process_eq, process_func = _preprocess(self.eq, self.func) 

124 if process_func != self.func: 

125 raise ValueError 

126 else: 

127 process_eq = self.eq 

128 return process_eq 

129 

130 def get_numbered_constants(self, num=1, start=1, prefix='C') -> list[Symbol]: 

131 """ 

132 Returns a list of constants that do not occur 

133 in eq already. 

134 """ 

135 ncs = self.iter_numbered_constants(start, prefix) 

136 Cs = [next(ncs) for i in range(num)] 

137 return Cs 

138 

139 def iter_numbered_constants(self, start=1, prefix='C') -> Iterator[Symbol]: 

140 """ 

141 Returns an iterator of constants that do not occur 

142 in eq already. 

143 """ 

144 atom_set = self.eq.free_symbols 

145 func_set = self.eq.atoms(Function) 

146 if func_set: 

147 atom_set |= {Symbol(str(f.func)) for f in func_set} 

148 return numbered_symbols(start=start, prefix=prefix, exclude=atom_set) 

149 

150 @cached_property 

151 def is_autonomous(self): 

152 u = Dummy('u') 

153 x = self.sym 

154 syms = self.eq.subs(self.func, u).free_symbols 

155 return x not in syms 

156 

157 def get_linear_coefficients(self, eq, func, order): 

158 r""" 

159 Matches a differential equation to the linear form: 

160 

161 .. math:: a_n(x) y^{(n)} + \cdots + a_1(x)y' + a_0(x) y + B(x) = 0 

162 

163 Returns a dict of order:coeff terms, where order is the order of the 

164 derivative on each term, and coeff is the coefficient of that derivative. 

165 The key ``-1`` holds the function `B(x)`. Returns ``None`` if the ODE is 

166 not linear. This function assumes that ``func`` has already been checked 

167 to be good. 

168 

169 Examples 

170 ======== 

171 

172 >>> from sympy import Function, cos, sin 

173 >>> from sympy.abc import x 

174 >>> from sympy.solvers.ode.single import SingleODEProblem 

175 >>> f = Function('f') 

176 >>> eq = f(x).diff(x, 3) + 2*f(x).diff(x) + \ 

177 ... x*f(x).diff(x, 2) + cos(x)*f(x).diff(x) + x - f(x) - \ 

178 ... sin(x) 

179 >>> obj = SingleODEProblem(eq, f(x), x) 

180 >>> obj.get_linear_coefficients(eq, f(x), 3) 

181 {-1: x - sin(x), 0: -1, 1: cos(x) + 2, 2: x, 3: 1} 

182 >>> eq = f(x).diff(x, 3) + 2*f(x).diff(x) + \ 

183 ... x*f(x).diff(x, 2) + cos(x)*f(x).diff(x) + x - f(x) - \ 

184 ... sin(f(x)) 

185 >>> obj = SingleODEProblem(eq, f(x), x) 

186 >>> obj.get_linear_coefficients(eq, f(x), 3) == None 

187 True 

188 

189 """ 

190 f = func.func 

191 x = func.args[0] 

192 symset = {Derivative(f(x), x, i) for i in range(order+1)} 

193 try: 

194 rhs, lhs_terms = _lin_eq2dict(eq, symset) 

195 except PolyNonlinearError: 

196 return None 

197 

198 if rhs.has(func) or any(c.has(func) for c in lhs_terms.values()): 

199 return None 

200 terms = {i: lhs_terms.get(f(x).diff(x, i), S.Zero) for i in range(order+1)} 

201 terms[-1] = rhs 

202 return terms 

203 

204 # TODO: Add methods that can be used by many ODE solvers: 

205 # order 

206 # is_linear() 

207 # get_linear_coefficients() 

208 # eq_prepared (the ODE in prepared form) 

209 

210 

211class SingleODESolver: 

212 """ 

213 Base class for Single ODE solvers. 

214 

215 Subclasses should implement the _matches and _get_general_solution 

216 methods. This class is not intended to be instantiated directly but its 

217 subclasses are as part of dsolve. 

218 

219 Examples 

220 ======== 

221 

222 You can use a subclass of SingleODEProblem to solve a particular type of 

223 ODE. We first define a particular ODE problem: 

224 

225 >>> from sympy import Function, Symbol 

226 >>> x = Symbol('x') 

227 >>> f = Function('f') 

228 >>> eq = f(x).diff(x, 2) 

229 

230 Now we solve this problem using the NthAlgebraic solver which is a 

231 subclass of SingleODESolver: 

232 

233 >>> from sympy.solvers.ode.single import NthAlgebraic, SingleODEProblem 

234 >>> problem = SingleODEProblem(eq, f(x), x) 

235 >>> solver = NthAlgebraic(problem) 

236 >>> solver.get_general_solution() 

237 [Eq(f(x), _C*x + _C)] 

238 

239 The normal way to solve an ODE is to use dsolve (which would use 

240 NthAlgebraic and other solvers internally). When using dsolve a number of 

241 other things are done such as evaluating integrals, simplifying the 

242 solution and renumbering the constants: 

243 

244 >>> from sympy import dsolve 

245 >>> dsolve(eq, hint='nth_algebraic') 

246 Eq(f(x), C1 + C2*x) 

247 """ 

248 

249 # Subclasses should store the hint name (the argument to dsolve) in this 

250 # attribute 

251 hint: ClassVar[str] 

252 

253 # Subclasses should define this to indicate if they support an _Integral 

254 # hint. 

255 has_integral: ClassVar[bool] 

256 

257 # The ODE to be solved 

258 ode_problem = None # type: SingleODEProblem 

259 

260 # Cache whether or not the equation has matched the method 

261 _matched: bool | None = None 

262 

263 # Subclasses should store in this attribute the list of order(s) of ODE 

264 # that subclass can solve or leave it to None if not specific to any order 

265 order: list | None = None 

266 

267 def __init__(self, ode_problem): 

268 self.ode_problem = ode_problem 

269 

270 def matches(self) -> bool: 

271 if self.order is not None and self.ode_problem.order not in self.order: 

272 self._matched = False 

273 return self._matched 

274 

275 if self._matched is None: 

276 self._matched = self._matches() 

277 return self._matched 

278 

279 def get_general_solution(self, *, simplify: bool = True) -> list[Equality]: 

280 if not self.matches(): 

281 msg = "%s solver cannot solve:\n%s" 

282 raise ODEMatchError(msg % (self.hint, self.ode_problem.eq)) 

283 return self._get_general_solution(simplify_flag=simplify) 

284 

285 def _matches(self) -> bool: 

286 msg = "Subclasses of SingleODESolver should implement matches." 

287 raise NotImplementedError(msg) 

288 

289 def _get_general_solution(self, *, simplify_flag: bool = True) -> list[Equality]: 

290 msg = "Subclasses of SingleODESolver should implement get_general_solution." 

291 raise NotImplementedError(msg) 

292 

293 

294class SinglePatternODESolver(SingleODESolver): 

295 '''Superclass for ODE solvers based on pattern matching''' 

296 

297 def wilds(self): 

298 prob = self.ode_problem 

299 f = prob.func.func 

300 x = prob.sym 

301 order = prob.order 

302 return self._wilds(f, x, order) 

303 

304 def wilds_match(self): 

305 match = self._wilds_match 

306 return [match.get(w, S.Zero) for w in self.wilds()] 

307 

308 def _matches(self): 

309 eq = self.ode_problem.eq_expanded 

310 f = self.ode_problem.func.func 

311 x = self.ode_problem.sym 

312 order = self.ode_problem.order 

313 df = f(x).diff(x, order) 

314 

315 if order not in [1, 2]: 

316 return False 

317 

318 pattern = self._equation(f(x), x, order) 

319 

320 if not pattern.coeff(df).has(Wild): 

321 eq = expand(eq / eq.coeff(df)) 

322 eq = eq.collect([f(x).diff(x), f(x)], func = cancel) 

323 

324 self._wilds_match = match = eq.match(pattern) 

325 if match is not None: 

326 return self._verify(f(x)) 

327 return False 

328 

329 def _verify(self, fx) -> bool: 

330 return True 

331 

332 def _wilds(self, f, x, order): 

333 msg = "Subclasses of SingleODESolver should implement _wilds" 

334 raise NotImplementedError(msg) 

335 

336 def _equation(self, fx, x, order): 

337 msg = "Subclasses of SingleODESolver should implement _equation" 

338 raise NotImplementedError(msg) 

339 

340 

341class NthAlgebraic(SingleODESolver): 

342 r""" 

343 Solves an `n`\th order ordinary differential equation using algebra and 

344 integrals. 

345 

346 There is no general form for the kind of equation that this can solve. The 

347 the equation is solved algebraically treating differentiation as an 

348 invertible algebraic function. 

349 

350 Examples 

351 ======== 

352 

353 >>> from sympy import Function, dsolve, Eq 

354 >>> from sympy.abc import x 

355 >>> f = Function('f') 

356 >>> eq = Eq(f(x) * (f(x).diff(x)**2 - 1), 0) 

357 >>> dsolve(eq, f(x), hint='nth_algebraic') 

358 [Eq(f(x), 0), Eq(f(x), C1 - x), Eq(f(x), C1 + x)] 

359 

360 Note that this solver can return algebraic solutions that do not have any 

361 integration constants (f(x) = 0 in the above example). 

362 """ 

363 

364 hint = 'nth_algebraic' 

365 has_integral = True # nth_algebraic_Integral hint 

366 

367 def _matches(self): 

368 r""" 

369 Matches any differential equation that nth_algebraic can solve. Uses 

370 `sympy.solve` but teaches it how to integrate derivatives. 

371 

372 This involves calling `sympy.solve` and does most of the work of finding a 

373 solution (apart from evaluating the integrals). 

374 """ 

375 eq = self.ode_problem.eq 

376 func = self.ode_problem.func 

377 var = self.ode_problem.sym 

378 

379 # Derivative that solve can handle: 

380 diffx = self._get_diffx(var) 

381 

382 # Replace derivatives wrt the independent variable with diffx 

383 def replace(eq, var): 

384 def expand_diffx(*args): 

385 differand, diffs = args[0], args[1:] 

386 toreplace = differand 

387 for v, n in diffs: 

388 for _ in range(n): 

389 if v == var: 

390 toreplace = diffx(toreplace) 

391 else: 

392 toreplace = Derivative(toreplace, v) 

393 return toreplace 

394 return eq.replace(Derivative, expand_diffx) 

395 

396 # Restore derivatives in solution afterwards 

397 def unreplace(eq, var): 

398 return eq.replace(diffx, lambda e: Derivative(e, var)) 

399 

400 subs_eqn = replace(eq, var) 

401 try: 

402 # turn off simplification to protect Integrals that have 

403 # _t instead of fx in them and would otherwise factor 

404 # as t_*Integral(1, x) 

405 solns = solve(subs_eqn, func, simplify=False) 

406 except NotImplementedError: 

407 solns = [] 

408 

409 solns = [simplify(unreplace(soln, var)) for soln in solns] 

410 solns = [Equality(func, soln) for soln in solns] 

411 

412 self.solutions = solns 

413 return len(solns) != 0 

414 

415 def _get_general_solution(self, *, simplify_flag: bool = True): 

416 return self.solutions 

417 

418 # This needs to produce an invertible function but the inverse depends 

419 # which variable we are integrating with respect to. Since the class can 

420 # be stored in cached results we need to ensure that we always get the 

421 # same class back for each particular integration variable so we store these 

422 # classes in a global dict: 

423 _diffx_stored: dict[Symbol, type[Function]] = {} 

424 

425 @staticmethod 

426 def _get_diffx(var): 

427 diffcls = NthAlgebraic._diffx_stored.get(var, None) 

428 

429 if diffcls is None: 

430 # A class that behaves like Derivative wrt var but is "invertible". 

431 class diffx(Function): 

432 def inverse(self): 

433 # don't use integrate here because fx has been replaced by _t 

434 # in the equation; integrals will not be correct while solve 

435 # is at work. 

436 return lambda expr: Integral(expr, var) + Dummy('C') 

437 

438 diffcls = NthAlgebraic._diffx_stored.setdefault(var, diffx) 

439 

440 return diffcls 

441 

442 

443class FirstExact(SinglePatternODESolver): 

444 r""" 

445 Solves 1st order exact ordinary differential equations. 

446 

447 A 1st order differential equation is called exact if it is the total 

448 differential of a function. That is, the differential equation 

449 

450 .. math:: P(x, y) \,\partial{}x + Q(x, y) \,\partial{}y = 0 

451 

452 is exact if there is some function `F(x, y)` such that `P(x, y) = 

453 \partial{}F/\partial{}x` and `Q(x, y) = \partial{}F/\partial{}y`. It can 

454 be shown that a necessary and sufficient condition for a first order ODE 

455 to be exact is that `\partial{}P/\partial{}y = \partial{}Q/\partial{}x`. 

456 Then, the solution will be as given below:: 

457 

458 >>> from sympy import Function, Eq, Integral, symbols, pprint 

459 >>> x, y, t, x0, y0, C1= symbols('x,y,t,x0,y0,C1') 

460 >>> P, Q, F= map(Function, ['P', 'Q', 'F']) 

461 >>> pprint(Eq(Eq(F(x, y), Integral(P(t, y), (t, x0, x)) + 

462 ... Integral(Q(x0, t), (t, y0, y))), C1)) 

463 x y 

464 / / 

465 | | 

466 F(x, y) = | P(t, y) dt + | Q(x0, t) dt = C1 

467 | | 

468 / / 

469 x0 y0 

470 

471 Where the first partials of `P` and `Q` exist and are continuous in a 

472 simply connected region. 

473 

474 A note: SymPy currently has no way to represent inert substitution on an 

475 expression, so the hint ``1st_exact_Integral`` will return an integral 

476 with `dy`. This is supposed to represent the function that you are 

477 solving for. 

478 

479 Examples 

480 ======== 

481 

482 >>> from sympy import Function, dsolve, cos, sin 

483 >>> from sympy.abc import x 

484 >>> f = Function('f') 

485 >>> dsolve(cos(f(x)) - (x*sin(f(x)) - f(x)**2)*f(x).diff(x), 

486 ... f(x), hint='1st_exact') 

487 Eq(x*cos(f(x)) + f(x)**3/3, C1) 

488 

489 References 

490 ========== 

491 

492 - https://en.wikipedia.org/wiki/Exact_differential_equation 

493 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

494 Dover 1963, pp. 73 

495 

496 # indirect doctest 

497 

498 """ 

499 hint = "1st_exact" 

500 has_integral = True 

501 order = [1] 

502 

503 def _wilds(self, f, x, order): 

504 P = Wild('P', exclude=[f(x).diff(x)]) 

505 Q = Wild('Q', exclude=[f(x).diff(x)]) 

506 return P, Q 

507 

508 def _equation(self, fx, x, order): 

509 P, Q = self.wilds() 

510 return P + Q*fx.diff(x) 

511 

512 def _verify(self, fx) -> bool: 

513 P, Q = self.wilds() 

514 x = self.ode_problem.sym 

515 y = Dummy('y') 

516 

517 m, n = self.wilds_match() 

518 

519 m = m.subs(fx, y) 

520 n = n.subs(fx, y) 

521 numerator = cancel(m.diff(y) - n.diff(x)) 

522 

523 if numerator.is_zero: 

524 # Is exact 

525 return True 

526 else: 

527 # The following few conditions try to convert a non-exact 

528 # differential equation into an exact one. 

529 # References: 

530 # 1. Differential equations with applications 

531 # and historical notes - George E. Simmons 

532 # 2. https://math.okstate.edu/people/binegar/2233-S99/2233-l12.pdf 

533 

534 factor_n = cancel(numerator/n) 

535 factor_m = cancel(-numerator/m) 

536 if y not in factor_n.free_symbols: 

537 # If (dP/dy - dQ/dx) / Q = f(x) 

538 # then exp(integral(f(x))*equation becomes exact 

539 factor = factor_n 

540 integration_variable = x 

541 elif x not in factor_m.free_symbols: 

542 # If (dP/dy - dQ/dx) / -P = f(y) 

543 # then exp(integral(f(y))*equation becomes exact 

544 factor = factor_m 

545 integration_variable = y 

546 else: 

547 # Couldn't convert to exact 

548 return False 

549 

550 factor = exp(Integral(factor, integration_variable)) 

551 m *= factor 

552 n *= factor 

553 self._wilds_match[P] = m.subs(y, fx) 

554 self._wilds_match[Q] = n.subs(y, fx) 

555 return True 

556 

557 def _get_general_solution(self, *, simplify_flag: bool = True): 

558 m, n = self.wilds_match() 

559 fx = self.ode_problem.func 

560 x = self.ode_problem.sym 

561 (C1,) = self.ode_problem.get_numbered_constants(num=1) 

562 y = Dummy('y') 

563 

564 m = m.subs(fx, y) 

565 n = n.subs(fx, y) 

566 

567 gen_sol = Eq(Subs(Integral(m, x) 

568 + Integral(n - Integral(m, x).diff(y), y), y, fx), C1) 

569 return [gen_sol] 

570 

571 

572class FirstLinear(SinglePatternODESolver): 

573 r""" 

574 Solves 1st order linear differential equations. 

575 

576 These are differential equations of the form 

577 

578 .. math:: dy/dx + P(x) y = Q(x)\text{.} 

579 

580 These kinds of differential equations can be solved in a general way. The 

581 integrating factor `e^{\int P(x) \,dx}` will turn the equation into a 

582 separable equation. The general solution is:: 

583 

584 >>> from sympy import Function, dsolve, Eq, pprint, diff, sin 

585 >>> from sympy.abc import x 

586 >>> f, P, Q = map(Function, ['f', 'P', 'Q']) 

587 >>> genform = Eq(f(x).diff(x) + P(x)*f(x), Q(x)) 

588 >>> pprint(genform) 

589 d 

590 P(x)*f(x) + --(f(x)) = Q(x) 

591 dx 

592 >>> pprint(dsolve(genform, f(x), hint='1st_linear_Integral')) 

593 / / \ 

594 | | | 

595 | | / | / 

596 | | | | | 

597 | | | P(x) dx | - | P(x) dx 

598 | | | | | 

599 | | / | / 

600 f(x) = |C1 + | Q(x)*e dx|*e 

601 | | | 

602 \ / / 

603 

604 

605 Examples 

606 ======== 

607 

608 >>> f = Function('f') 

609 >>> pprint(dsolve(Eq(x*diff(f(x), x) - f(x), x**2*sin(x)), 

610 ... f(x), '1st_linear')) 

611 f(x) = x*(C1 - cos(x)) 

612 

613 References 

614 ========== 

615 

616 - https://en.wikipedia.org/wiki/Linear_differential_equation#First-order_equation_with_variable_coefficients 

617 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

618 Dover 1963, pp. 92 

619 

620 # indirect doctest 

621 

622 """ 

623 hint = '1st_linear' 

624 has_integral = True 

625 order = [1] 

626 

627 def _wilds(self, f, x, order): 

628 P = Wild('P', exclude=[f(x)]) 

629 Q = Wild('Q', exclude=[f(x), f(x).diff(x)]) 

630 return P, Q 

631 

632 def _equation(self, fx, x, order): 

633 P, Q = self.wilds() 

634 return fx.diff(x) + P*fx - Q 

635 

636 def _get_general_solution(self, *, simplify_flag: bool = True): 

637 P, Q = self.wilds_match() 

638 fx = self.ode_problem.func 

639 x = self.ode_problem.sym 

640 (C1,) = self.ode_problem.get_numbered_constants(num=1) 

641 gensol = Eq(fx, ((C1 + Integral(Q*exp(Integral(P, x)), x)) 

642 * exp(-Integral(P, x)))) 

643 return [gensol] 

644 

645 

646class AlmostLinear(SinglePatternODESolver): 

647 r""" 

648 Solves an almost-linear differential equation. 

649 

650 The general form of an almost linear differential equation is 

651 

652 .. math:: a(x) g'(f(x)) f'(x) + b(x) g(f(x)) + c(x) 

653 

654 Here `f(x)` is the function to be solved for (the dependent variable). 

655 The substitution `g(f(x)) = u(x)` leads to a linear differential equation 

656 for `u(x)` of the form `a(x) u' + b(x) u + c(x) = 0`. This can be solved 

657 for `u(x)` by the `first_linear` hint and then `f(x)` is found by solving 

658 `g(f(x)) = u(x)`. 

659 

660 See Also 

661 ======== 

662 :obj:`sympy.solvers.ode.single.FirstLinear` 

663 

664 Examples 

665 ======== 

666 

667 >>> from sympy import dsolve, Function, pprint, sin, cos 

668 >>> from sympy.abc import x 

669 >>> f = Function('f') 

670 >>> d = f(x).diff(x) 

671 >>> eq = x*d + x*f(x) + 1 

672 >>> dsolve(eq, f(x), hint='almost_linear') 

673 Eq(f(x), (C1 - Ei(x))*exp(-x)) 

674 >>> pprint(dsolve(eq, f(x), hint='almost_linear')) 

675 -x 

676 f(x) = (C1 - Ei(x))*e 

677 >>> example = cos(f(x))*f(x).diff(x) + sin(f(x)) + 1 

678 >>> pprint(example) 

679 d 

680 sin(f(x)) + cos(f(x))*--(f(x)) + 1 

681 dx 

682 >>> pprint(dsolve(example, f(x), hint='almost_linear')) 

683 / -x \ / -x \ 

684 [f(x) = pi - asin\C1*e - 1/, f(x) = asin\C1*e - 1/] 

685 

686 

687 References 

688 ========== 

689 

690 - Joel Moses, "Symbolic Integration - The Stormy Decade", Communications 

691 of the ACM, Volume 14, Number 8, August 1971, pp. 558 

692 """ 

693 hint = "almost_linear" 

694 has_integral = True 

695 order = [1] 

696 

697 def _wilds(self, f, x, order): 

698 P = Wild('P', exclude=[f(x).diff(x)]) 

699 Q = Wild('Q', exclude=[f(x).diff(x)]) 

700 return P, Q 

701 

702 def _equation(self, fx, x, order): 

703 P, Q = self.wilds() 

704 return P*fx.diff(x) + Q 

705 

706 def _verify(self, fx): 

707 a, b = self.wilds_match() 

708 c, b = b.as_independent(fx) if b.is_Add else (S.Zero, b) 

709 # a, b and c are the function a(x), b(x) and c(x) respectively. 

710 # c(x) is obtained by separating out b as terms with and without fx i.e, l(y) 

711 # The following conditions checks if the given equation is an almost-linear differential equation using the fact that 

712 # a(x)*(l(y))' / l(y)' is independent of l(y) 

713 

714 if b.diff(fx) != 0 and not simplify(b.diff(fx)/a).has(fx): 

715 self.ly = factor_terms(b).as_independent(fx, as_Add=False)[1] # Gives the term containing fx i.e., l(y) 

716 self.ax = a / self.ly.diff(fx) 

717 self.cx = -c # cx is taken as -c(x) to simplify expression in the solution integral 

718 self.bx = factor_terms(b) / self.ly 

719 return True 

720 

721 return False 

722 

723 def _get_general_solution(self, *, simplify_flag: bool = True): 

724 x = self.ode_problem.sym 

725 (C1,) = self.ode_problem.get_numbered_constants(num=1) 

726 gensol = Eq(self.ly, ((C1 + Integral((self.cx/self.ax)*exp(Integral(self.bx/self.ax, x)), x)) 

727 * exp(-Integral(self.bx/self.ax, x)))) 

728 

729 return [gensol] 

730 

731 

732class Bernoulli(SinglePatternODESolver): 

733 r""" 

734 Solves Bernoulli differential equations. 

735 

736 These are equations of the form 

737 

738 .. math:: dy/dx + P(x) y = Q(x) y^n\text{, }n \ne 1`\text{.} 

739 

740 The substitution `w = 1/y^{1-n}` will transform an equation of this form 

741 into one that is linear (see the docstring of 

742 :obj:`~sympy.solvers.ode.single.FirstLinear`). The general solution is:: 

743 

744 >>> from sympy import Function, dsolve, Eq, pprint 

745 >>> from sympy.abc import x, n 

746 >>> f, P, Q = map(Function, ['f', 'P', 'Q']) 

747 >>> genform = Eq(f(x).diff(x) + P(x)*f(x), Q(x)*f(x)**n) 

748 >>> pprint(genform) 

749 d n 

750 P(x)*f(x) + --(f(x)) = Q(x)*f (x) 

751 dx 

752 >>> pprint(dsolve(genform, f(x), hint='Bernoulli_Integral'), num_columns=110) 

753 -1 

754 ----- 

755 n - 1 

756 // / / \ \ 

757 || | | | | 

758 || | / | / | / | 

759 || | | | | | | | 

760 || | -(n - 1)* | P(x) dx | -(n - 1)* | P(x) dx | (n - 1)* | P(x) dx| 

761 || | | | | | | | 

762 || | / | / | / | 

763 f(x) = ||C1 - n* | Q(x)*e dx + | Q(x)*e dx|*e | 

764 || | | | | 

765 \\ / / / / 

766 

767 

768 Note that the equation is separable when `n = 1` (see the docstring of 

769 :obj:`~sympy.solvers.ode.single.Separable`). 

770 

771 >>> pprint(dsolve(Eq(f(x).diff(x) + P(x)*f(x), Q(x)*f(x)), f(x), 

772 ... hint='separable_Integral')) 

773 f(x) 

774 / 

775 | / 

776 | 1 | 

777 | - dy = C1 + | (-P(x) + Q(x)) dx 

778 | y | 

779 | / 

780 / 

781 

782 

783 Examples 

784 ======== 

785 

786 >>> from sympy import Function, dsolve, Eq, pprint, log 

787 >>> from sympy.abc import x 

788 >>> f = Function('f') 

789 

790 >>> pprint(dsolve(Eq(x*f(x).diff(x) + f(x), log(x)*f(x)**2), 

791 ... f(x), hint='Bernoulli')) 

792 1 

793 f(x) = ----------------- 

794 C1*x + log(x) + 1 

795 

796 References 

797 ========== 

798 

799 - https://en.wikipedia.org/wiki/Bernoulli_differential_equation 

800 

801 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

802 Dover 1963, pp. 95 

803 

804 # indirect doctest 

805 

806 """ 

807 hint = "Bernoulli" 

808 has_integral = True 

809 order = [1] 

810 

811 def _wilds(self, f, x, order): 

812 P = Wild('P', exclude=[f(x)]) 

813 Q = Wild('Q', exclude=[f(x)]) 

814 n = Wild('n', exclude=[x, f(x), f(x).diff(x)]) 

815 return P, Q, n 

816 

817 def _equation(self, fx, x, order): 

818 P, Q, n = self.wilds() 

819 return fx.diff(x) + P*fx - Q*fx**n 

820 

821 def _get_general_solution(self, *, simplify_flag: bool = True): 

822 P, Q, n = self.wilds_match() 

823 fx = self.ode_problem.func 

824 x = self.ode_problem.sym 

825 (C1,) = self.ode_problem.get_numbered_constants(num=1) 

826 if n==1: 

827 gensol = Eq(log(fx), ( 

828 C1 + Integral((-P + Q), x) 

829 )) 

830 else: 

831 gensol = Eq(fx**(1-n), ( 

832 (C1 - (n - 1) * Integral(Q*exp(-n*Integral(P, x)) 

833 * exp(Integral(P, x)), x) 

834 ) * exp(-(1 - n)*Integral(P, x))) 

835 ) 

836 return [gensol] 

837 

838 

839class Factorable(SingleODESolver): 

840 r""" 

841 Solves equations having a solvable factor. 

842 

843 This function is used to solve the equation having factors. Factors may be of type algebraic or ode. It 

844 will try to solve each factor independently. Factors will be solved by calling dsolve. We will return the 

845 list of solutions. 

846 

847 Examples 

848 ======== 

849 

850 >>> from sympy import Function, dsolve, pprint 

851 >>> from sympy.abc import x 

852 >>> f = Function('f') 

853 >>> eq = (f(x)**2-4)*(f(x).diff(x)+f(x)) 

854 >>> pprint(dsolve(eq, f(x))) 

855 -x 

856 [f(x) = 2, f(x) = -2, f(x) = C1*e ] 

857 

858 

859 """ 

860 hint = "factorable" 

861 has_integral = False 

862 

863 def _matches(self): 

864 eq_orig = self.ode_problem.eq 

865 f = self.ode_problem.func.func 

866 x = self.ode_problem.sym 

867 df = f(x).diff(x) 

868 self.eqs = [] 

869 eq = eq_orig.collect(f(x), func = cancel) 

870 eq = fraction(factor(eq))[0] 

871 factors = Mul.make_args(factor(eq)) 

872 roots = [fac.as_base_exp() for fac in factors if len(fac.args)!=0] 

873 if len(roots)>1 or roots[0][1]>1: 

874 for base, expo in roots: 

875 if base.has(f(x)): 

876 self.eqs.append(base) 

877 if len(self.eqs)>0: 

878 return True 

879 roots = solve(eq, df) 

880 if len(roots)>0: 

881 self.eqs = [(df - root) for root in roots] 

882 # Avoid infinite recursion 

883 matches = self.eqs != [eq_orig] 

884 return matches 

885 for i in factors: 

886 if i.has(f(x)): 

887 self.eqs.append(i) 

888 return len(self.eqs)>0 and len(factors)>1 

889 

890 def _get_general_solution(self, *, simplify_flag: bool = True): 

891 func = self.ode_problem.func.func 

892 x = self.ode_problem.sym 

893 eqns = self.eqs 

894 sols = [] 

895 for eq in eqns: 

896 try: 

897 sol = dsolve(eq, func(x)) 

898 except NotImplementedError: 

899 continue 

900 else: 

901 if isinstance(sol, list): 

902 sols.extend(sol) 

903 else: 

904 sols.append(sol) 

905 

906 if sols == []: 

907 raise NotImplementedError("The given ODE " + str(eq) + " cannot be solved by" 

908 + " the factorable group method") 

909 return sols 

910 

911 

912class RiccatiSpecial(SinglePatternODESolver): 

913 r""" 

914 The general Riccati equation has the form 

915 

916 .. math:: dy/dx = f(x) y^2 + g(x) y + h(x)\text{.} 

917 

918 While it does not have a general solution [1], the "special" form, `dy/dx 

919 = a y^2 - b x^c`, does have solutions in many cases [2]. This routine 

920 returns a solution for `a(dy/dx) = b y^2 + c y/x + d/x^2` that is obtained 

921 by using a suitable change of variables to reduce it to the special form 

922 and is valid when neither `a` nor `b` are zero and either `c` or `d` is 

923 zero. 

924 

925 >>> from sympy.abc import x, a, b, c, d 

926 >>> from sympy import dsolve, checkodesol, pprint, Function 

927 >>> f = Function('f') 

928 >>> y = f(x) 

929 >>> genform = a*y.diff(x) - (b*y**2 + c*y/x + d/x**2) 

930 >>> sol = dsolve(genform, y, hint="Riccati_special_minus2") 

931 >>> pprint(sol, wrap_line=False) 

932 / / __________________ \\ 

933 | __________________ | / 2 || 

934 | / 2 | \/ 4*b*d - (a + c) *log(x)|| 

935 -|a + c - \/ 4*b*d - (a + c) *tan|C1 + ----------------------------|| 

936 \ \ 2*a // 

937 f(x) = ------------------------------------------------------------------------ 

938 2*b*x 

939 

940 >>> checkodesol(genform, sol, order=1)[0] 

941 True 

942 

943 References 

944 ========== 

945 

946 - https://www.maplesoft.com/support/help/Maple/view.aspx?path=odeadvisor/Riccati 

947 - https://eqworld.ipmnet.ru/en/solutions/ode/ode0106.pdf - 

948 https://eqworld.ipmnet.ru/en/solutions/ode/ode0123.pdf 

949 """ 

950 hint = "Riccati_special_minus2" 

951 has_integral = False 

952 order = [1] 

953 

954 def _wilds(self, f, x, order): 

955 a = Wild('a', exclude=[x, f(x), f(x).diff(x), 0]) 

956 b = Wild('b', exclude=[x, f(x), f(x).diff(x), 0]) 

957 c = Wild('c', exclude=[x, f(x), f(x).diff(x)]) 

958 d = Wild('d', exclude=[x, f(x), f(x).diff(x)]) 

959 return a, b, c, d 

960 

961 def _equation(self, fx, x, order): 

962 a, b, c, d = self.wilds() 

963 return a*fx.diff(x) + b*fx**2 + c*fx/x + d/x**2 

964 

965 def _get_general_solution(self, *, simplify_flag: bool = True): 

966 a, b, c, d = self.wilds_match() 

967 fx = self.ode_problem.func 

968 x = self.ode_problem.sym 

969 (C1,) = self.ode_problem.get_numbered_constants(num=1) 

970 mu = sqrt(4*d*b - (a - c)**2) 

971 

972 gensol = Eq(fx, (a - c - mu*tan(mu/(2*a)*log(x) + C1))/(2*b*x)) 

973 return [gensol] 

974 

975 

976class RationalRiccati(SinglePatternODESolver): 

977 r""" 

978 Gives general solutions to the first order Riccati differential 

979 equations that have atleast one rational particular solution. 

980 

981 .. math :: y' = b_0(x) + b_1(x) y + b_2(x) y^2 

982 

983 where `b_0`, `b_1` and `b_2` are rational functions of `x` 

984 with `b_2 \ne 0` (`b_2 = 0` would make it a Bernoulli equation). 

985 

986 Examples 

987 ======== 

988 

989 >>> from sympy import Symbol, Function, dsolve, checkodesol 

990 >>> f = Function('f') 

991 >>> x = Symbol('x') 

992 

993 >>> eq = -x**4*f(x)**2 + x**3*f(x).diff(x) + x**2*f(x) + 20 

994 >>> sol = dsolve(eq, hint="1st_rational_riccati") 

995 >>> sol 

996 Eq(f(x), (4*C1 - 5*x**9 - 4)/(x**2*(C1 + x**9 - 1))) 

997 >>> checkodesol(eq, sol) 

998 (True, 0) 

999 

1000 References 

1001 ========== 

1002 

1003 - Riccati ODE: https://en.wikipedia.org/wiki/Riccati_equation 

1004 - N. Thieu Vo - Rational and Algebraic Solutions of First-Order Algebraic ODEs: 

1005 Algorithm 11, pp. 78 - https://www3.risc.jku.at/publications/download/risc_5387/PhDThesisThieu.pdf 

1006 """ 

1007 has_integral = False 

1008 hint = "1st_rational_riccati" 

1009 order = [1] 

1010 

1011 def _wilds(self, f, x, order): 

1012 b0 = Wild('b0', exclude=[f(x), f(x).diff(x)]) 

1013 b1 = Wild('b1', exclude=[f(x), f(x).diff(x)]) 

1014 b2 = Wild('b2', exclude=[f(x), f(x).diff(x)]) 

1015 return (b0, b1, b2) 

1016 

1017 def _equation(self, fx, x, order): 

1018 b0, b1, b2 = self.wilds() 

1019 return fx.diff(x) - b0 - b1*fx - b2*fx**2 

1020 

1021 def _matches(self): 

1022 eq = self.ode_problem.eq_expanded 

1023 f = self.ode_problem.func.func 

1024 x = self.ode_problem.sym 

1025 order = self.ode_problem.order 

1026 

1027 if order != 1: 

1028 return False 

1029 

1030 match, funcs = match_riccati(eq, f, x) 

1031 if not match: 

1032 return False 

1033 _b0, _b1, _b2 = funcs 

1034 b0, b1, b2 = self.wilds() 

1035 self._wilds_match = match = {b0: _b0, b1: _b1, b2: _b2} 

1036 return True 

1037 

1038 def _get_general_solution(self, *, simplify_flag: bool = True): 

1039 # Match the equation 

1040 b0, b1, b2 = self.wilds_match() 

1041 fx = self.ode_problem.func 

1042 x = self.ode_problem.sym 

1043 return solve_riccati(fx, x, b0, b1, b2, gensol=True) 

1044 

1045 

1046class SecondNonlinearAutonomousConserved(SinglePatternODESolver): 

1047 r""" 

1048 Gives solution for the autonomous second order nonlinear 

1049 differential equation of the form 

1050 

1051 .. math :: f''(x) = g(f(x)) 

1052 

1053 The solution for this differential equation can be computed 

1054 by multiplying by `f'(x)` and integrating on both sides, 

1055 converting it into a first order differential equation. 

1056 

1057 Examples 

1058 ======== 

1059 

1060 >>> from sympy import Function, symbols, dsolve 

1061 >>> f, g = symbols('f g', cls=Function) 

1062 >>> x = symbols('x') 

1063 

1064 >>> eq = f(x).diff(x, 2) - g(f(x)) 

1065 >>> dsolve(eq, simplify=False) 

1066 [Eq(Integral(1/sqrt(C1 + 2*Integral(g(_u), _u)), (_u, f(x))), C2 + x), 

1067 Eq(Integral(1/sqrt(C1 + 2*Integral(g(_u), _u)), (_u, f(x))), C2 - x)] 

1068 

1069 >>> from sympy import exp, log 

1070 >>> eq = f(x).diff(x, 2) - exp(f(x)) + log(f(x)) 

1071 >>> dsolve(eq, simplify=False) 

1072 [Eq(Integral(1/sqrt(-2*_u*log(_u) + 2*_u + C1 + 2*exp(_u)), (_u, f(x))), C2 + x), 

1073 Eq(Integral(1/sqrt(-2*_u*log(_u) + 2*_u + C1 + 2*exp(_u)), (_u, f(x))), C2 - x)] 

1074 

1075 References 

1076 ========== 

1077 

1078 - https://eqworld.ipmnet.ru/en/solutions/ode/ode0301.pdf 

1079 """ 

1080 hint = "2nd_nonlinear_autonomous_conserved" 

1081 has_integral = True 

1082 order = [2] 

1083 

1084 def _wilds(self, f, x, order): 

1085 fy = Wild('fy', exclude=[0, f(x).diff(x), f(x).diff(x, 2)]) 

1086 return (fy, ) 

1087 

1088 def _equation(self, fx, x, order): 

1089 fy = self.wilds()[0] 

1090 return fx.diff(x, 2) + fy 

1091 

1092 def _verify(self, fx): 

1093 return self.ode_problem.is_autonomous 

1094 

1095 def _get_general_solution(self, *, simplify_flag: bool = True): 

1096 g = self.wilds_match()[0] 

1097 fx = self.ode_problem.func 

1098 x = self.ode_problem.sym 

1099 u = Dummy('u') 

1100 g = g.subs(fx, u) 

1101 C1, C2 = self.ode_problem.get_numbered_constants(num=2) 

1102 inside = -2*Integral(g, u) + C1 

1103 lhs = Integral(1/sqrt(inside), (u, fx)) 

1104 return [Eq(lhs, C2 + x), Eq(lhs, C2 - x)] 

1105 

1106 

1107class Liouville(SinglePatternODESolver): 

1108 r""" 

1109 Solves 2nd order Liouville differential equations. 

1110 

1111 The general form of a Liouville ODE is 

1112 

1113 .. math:: \frac{d^2 y}{dx^2} + g(y) \left(\! 

1114 \frac{dy}{dx}\!\right)^2 + h(x) 

1115 \frac{dy}{dx}\text{.} 

1116 

1117 The general solution is: 

1118 

1119 >>> from sympy import Function, dsolve, Eq, pprint, diff 

1120 >>> from sympy.abc import x 

1121 >>> f, g, h = map(Function, ['f', 'g', 'h']) 

1122 >>> genform = Eq(diff(f(x),x,x) + g(f(x))*diff(f(x),x)**2 + 

1123 ... h(x)*diff(f(x),x), 0) 

1124 >>> pprint(genform) 

1125 2 2 

1126 /d \ d d 

1127 g(f(x))*|--(f(x))| + h(x)*--(f(x)) + ---(f(x)) = 0 

1128 \dx / dx 2 

1129 dx 

1130 >>> pprint(dsolve(genform, f(x), hint='Liouville_Integral')) 

1131 f(x) 

1132 / / 

1133 | | 

1134 | / | / 

1135 | | | | 

1136 | - | h(x) dx | | g(y) dy 

1137 | | | | 

1138 | / | / 

1139 C1 + C2* | e dx + | e dy = 0 

1140 | | 

1141 / / 

1142 

1143 Examples 

1144 ======== 

1145 

1146 >>> from sympy import Function, dsolve, Eq, pprint 

1147 >>> from sympy.abc import x 

1148 >>> f = Function('f') 

1149 >>> pprint(dsolve(diff(f(x), x, x) + diff(f(x), x)**2/f(x) + 

1150 ... diff(f(x), x)/x, f(x), hint='Liouville')) 

1151 ________________ ________________ 

1152 [f(x) = -\/ C1 + C2*log(x) , f(x) = \/ C1 + C2*log(x) ] 

1153 

1154 References 

1155 ========== 

1156 

1157 - Goldstein and Braun, "Advanced Methods for the Solution of Differential 

1158 Equations", pp. 98 

1159 - https://www.maplesoft.com/support/help/Maple/view.aspx?path=odeadvisor/Liouville 

1160 

1161 # indirect doctest 

1162 

1163 """ 

1164 hint = "Liouville" 

1165 has_integral = True 

1166 order = [2] 

1167 

1168 def _wilds(self, f, x, order): 

1169 d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) 

1170 e = Wild('e', exclude=[f(x).diff(x)]) 

1171 k = Wild('k', exclude=[f(x).diff(x)]) 

1172 return d, e, k 

1173 

1174 def _equation(self, fx, x, order): 

1175 # Liouville ODE in the form 

1176 # f(x).diff(x, 2) + g(f(x))*(f(x).diff(x))**2 + h(x)*f(x).diff(x) 

1177 # See Goldstein and Braun, "Advanced Methods for the Solution of 

1178 # Differential Equations", pg. 98 

1179 d, e, k = self.wilds() 

1180 return d*fx.diff(x, 2) + e*fx.diff(x)**2 + k*fx.diff(x) 

1181 

1182 def _verify(self, fx): 

1183 d, e, k = self.wilds_match() 

1184 self.y = Dummy('y') 

1185 x = self.ode_problem.sym 

1186 self.g = simplify(e/d).subs(fx, self.y) 

1187 self.h = simplify(k/d).subs(fx, self.y) 

1188 if self.y in self.h.free_symbols or x in self.g.free_symbols: 

1189 return False 

1190 return True 

1191 

1192 def _get_general_solution(self, *, simplify_flag: bool = True): 

1193 d, e, k = self.wilds_match() 

1194 fx = self.ode_problem.func 

1195 x = self.ode_problem.sym 

1196 C1, C2 = self.ode_problem.get_numbered_constants(num=2) 

1197 int = Integral(exp(Integral(self.g, self.y)), (self.y, None, fx)) 

1198 gen_sol = Eq(int + C1*Integral(exp(-Integral(self.h, x)), x) + C2, 0) 

1199 

1200 return [gen_sol] 

1201 

1202 

1203class Separable(SinglePatternODESolver): 

1204 r""" 

1205 Solves separable 1st order differential equations. 

1206 

1207 This is any differential equation that can be written as `P(y) 

1208 \tfrac{dy}{dx} = Q(x)`. The solution can then just be found by 

1209 rearranging terms and integrating: `\int P(y) \,dy = \int Q(x) \,dx`. 

1210 This hint uses :py:meth:`sympy.simplify.simplify.separatevars` as its back 

1211 end, so if a separable equation is not caught by this solver, it is most 

1212 likely the fault of that function. 

1213 :py:meth:`~sympy.simplify.simplify.separatevars` is 

1214 smart enough to do most expansion and factoring necessary to convert a 

1215 separable equation `F(x, y)` into the proper form `P(x)\cdot{}Q(y)`. The 

1216 general solution is:: 

1217 

1218 >>> from sympy import Function, dsolve, Eq, pprint 

1219 >>> from sympy.abc import x 

1220 >>> a, b, c, d, f = map(Function, ['a', 'b', 'c', 'd', 'f']) 

1221 >>> genform = Eq(a(x)*b(f(x))*f(x).diff(x), c(x)*d(f(x))) 

1222 >>> pprint(genform) 

1223 d 

1224 a(x)*b(f(x))*--(f(x)) = c(x)*d(f(x)) 

1225 dx 

1226 >>> pprint(dsolve(genform, f(x), hint='separable_Integral')) 

1227 f(x) 

1228 / / 

1229 | | 

1230 | b(y) | c(x) 

1231 | ---- dy = C1 + | ---- dx 

1232 | d(y) | a(x) 

1233 | | 

1234 / / 

1235 

1236 Examples 

1237 ======== 

1238 

1239 >>> from sympy import Function, dsolve, Eq 

1240 >>> from sympy.abc import x 

1241 >>> f = Function('f') 

1242 >>> pprint(dsolve(Eq(f(x)*f(x).diff(x) + x, 3*x*f(x)**2), f(x), 

1243 ... hint='separable', simplify=False)) 

1244 / 2 \ 2 

1245 log\3*f (x) - 1/ x 

1246 ---------------- = C1 + -- 

1247 6 2 

1248 

1249 References 

1250 ========== 

1251 

1252 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

1253 Dover 1963, pp. 52 

1254 

1255 # indirect doctest 

1256 

1257 """ 

1258 hint = "separable" 

1259 has_integral = True 

1260 order = [1] 

1261 

1262 def _wilds(self, f, x, order): 

1263 d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) 

1264 e = Wild('e', exclude=[f(x).diff(x)]) 

1265 return d, e 

1266 

1267 def _equation(self, fx, x, order): 

1268 d, e = self.wilds() 

1269 return d + e*fx.diff(x) 

1270 

1271 def _verify(self, fx): 

1272 d, e = self.wilds_match() 

1273 self.y = Dummy('y') 

1274 x = self.ode_problem.sym 

1275 d = separatevars(d.subs(fx, self.y)) 

1276 e = separatevars(e.subs(fx, self.y)) 

1277 # m1[coeff]*m1[x]*m1[y] + m2[coeff]*m2[x]*m2[y]*y' 

1278 self.m1 = separatevars(d, dict=True, symbols=(x, self.y)) 

1279 self.m2 = separatevars(e, dict=True, symbols=(x, self.y)) 

1280 if self.m1 and self.m2: 

1281 return True 

1282 return False 

1283 

1284 def _get_match_object(self): 

1285 fx = self.ode_problem.func 

1286 x = self.ode_problem.sym 

1287 return self.m1, self.m2, x, fx 

1288 

1289 def _get_general_solution(self, *, simplify_flag: bool = True): 

1290 m1, m2, x, fx = self._get_match_object() 

1291 (C1,) = self.ode_problem.get_numbered_constants(num=1) 

1292 int = Integral(m2['coeff']*m2[self.y]/m1[self.y], 

1293 (self.y, None, fx)) 

1294 gen_sol = Eq(int, Integral(-m1['coeff']*m1[x]/ 

1295 m2[x], x) + C1) 

1296 return [gen_sol] 

1297 

1298 

1299class SeparableReduced(Separable): 

1300 r""" 

1301 Solves a differential equation that can be reduced to the separable form. 

1302 

1303 The general form of this equation is 

1304 

1305 .. math:: y' + (y/x) H(x^n y) = 0\text{}. 

1306 

1307 This can be solved by substituting `u(y) = x^n y`. The equation then 

1308 reduces to the separable form `\frac{u'}{u (\mathrm{power} - H(u))} - 

1309 \frac{1}{x} = 0`. 

1310 

1311 The general solution is: 

1312 

1313 >>> from sympy import Function, dsolve, pprint 

1314 >>> from sympy.abc import x, n 

1315 >>> f, g = map(Function, ['f', 'g']) 

1316 >>> genform = f(x).diff(x) + (f(x)/x)*g(x**n*f(x)) 

1317 >>> pprint(genform) 

1318 / n \ 

1319 d f(x)*g\x *f(x)/ 

1320 --(f(x)) + --------------- 

1321 dx x 

1322 >>> pprint(dsolve(genform, hint='separable_reduced')) 

1323 n 

1324 x *f(x) 

1325 / 

1326 | 

1327 | 1 

1328 | ------------ dy = C1 + log(x) 

1329 | y*(n - g(y)) 

1330 | 

1331 / 

1332 

1333 See Also 

1334 ======== 

1335 :obj:`sympy.solvers.ode.single.Separable` 

1336 

1337 Examples 

1338 ======== 

1339 

1340 >>> from sympy import dsolve, Function, pprint 

1341 >>> from sympy.abc import x 

1342 >>> f = Function('f') 

1343 >>> d = f(x).diff(x) 

1344 >>> eq = (x - x**2*f(x))*d - f(x) 

1345 >>> dsolve(eq, hint='separable_reduced') 

1346 [Eq(f(x), (1 - sqrt(C1*x**2 + 1))/x), Eq(f(x), (sqrt(C1*x**2 + 1) + 1)/x)] 

1347 >>> pprint(dsolve(eq, hint='separable_reduced')) 

1348 ___________ ___________ 

1349 / 2 / 2 

1350 1 - \/ C1*x + 1 \/ C1*x + 1 + 1 

1351 [f(x) = ------------------, f(x) = ------------------] 

1352 x x 

1353 

1354 References 

1355 ========== 

1356 

1357 - Joel Moses, "Symbolic Integration - The Stormy Decade", Communications 

1358 of the ACM, Volume 14, Number 8, August 1971, pp. 558 

1359 """ 

1360 hint = "separable_reduced" 

1361 has_integral = True 

1362 order = [1] 

1363 

1364 def _degree(self, expr, x): 

1365 # Made this function to calculate the degree of 

1366 # x in an expression. If expr will be of form 

1367 # x**p*y, (wheare p can be variables/rationals) then it 

1368 # will return p. 

1369 for val in expr: 

1370 if val.has(x): 

1371 if isinstance(val, Pow) and val.as_base_exp()[0] == x: 

1372 return (val.as_base_exp()[1]) 

1373 elif val == x: 

1374 return (val.as_base_exp()[1]) 

1375 else: 

1376 return self._degree(val.args, x) 

1377 return 0 

1378 

1379 def _powers(self, expr): 

1380 # this function will return all the different relative power of x w.r.t f(x). 

1381 # expr = x**p * f(x)**q then it will return {p/q}. 

1382 pows = set() 

1383 fx = self.ode_problem.func 

1384 x = self.ode_problem.sym 

1385 self.y = Dummy('y') 

1386 if isinstance(expr, Add): 

1387 exprs = expr.atoms(Add) 

1388 elif isinstance(expr, Mul): 

1389 exprs = expr.atoms(Mul) 

1390 elif isinstance(expr, Pow): 

1391 exprs = expr.atoms(Pow) 

1392 else: 

1393 exprs = {expr} 

1394 

1395 for arg in exprs: 

1396 if arg.has(x): 

1397 _, u = arg.as_independent(x, fx) 

1398 pow = self._degree((u.subs(fx, self.y), ), x)/self._degree((u.subs(fx, self.y), ), self.y) 

1399 pows.add(pow) 

1400 return pows 

1401 

1402 def _verify(self, fx): 

1403 num, den = self.wilds_match() 

1404 x = self.ode_problem.sym 

1405 factor = simplify(x/fx*num/den) 

1406 # Try representing factor in terms of x^n*y 

1407 # where n is lowest power of x in factor; 

1408 # first remove terms like sqrt(2)*3 from factor.atoms(Mul) 

1409 num, dem = factor.as_numer_denom() 

1410 num = expand(num) 

1411 dem = expand(dem) 

1412 pows = self._powers(num) 

1413 pows.update(self._powers(dem)) 

1414 pows = list(pows) 

1415 if(len(pows)==1) and pows[0]!=zoo: 

1416 self.t = Dummy('t') 

1417 self.r2 = {'t': self.t} 

1418 num = num.subs(x**pows[0]*fx, self.t) 

1419 dem = dem.subs(x**pows[0]*fx, self.t) 

1420 test = num/dem 

1421 free = test.free_symbols 

1422 if len(free) == 1 and free.pop() == self.t: 

1423 self.r2.update({'power' : pows[0], 'u' : test}) 

1424 return True 

1425 return False 

1426 return False 

1427 

1428 def _get_match_object(self): 

1429 fx = self.ode_problem.func 

1430 x = self.ode_problem.sym 

1431 u = self.r2['u'].subs(self.r2['t'], self.y) 

1432 ycoeff = 1/(self.y*(self.r2['power'] - u)) 

1433 m1 = {self.y: 1, x: -1/x, 'coeff': 1} 

1434 m2 = {self.y: ycoeff, x: 1, 'coeff': 1} 

1435 return m1, m2, x, x**self.r2['power']*fx 

1436 

1437 

1438class HomogeneousCoeffSubsDepDivIndep(SinglePatternODESolver): 

1439 r""" 

1440 Solves a 1st order differential equation with homogeneous coefficients 

1441 using the substitution `u_1 = \frac{\text{<dependent 

1442 variable>}}{\text{<independent variable>}}`. 

1443 

1444 This is a differential equation 

1445 

1446 .. math:: P(x, y) + Q(x, y) dy/dx = 0 

1447 

1448 such that `P` and `Q` are homogeneous and of the same order. A function 

1449 `F(x, y)` is homogeneous of order `n` if `F(x t, y t) = t^n F(x, y)`. 

1450 Equivalently, `F(x, y)` can be rewritten as `G(y/x)` or `H(x/y)`. See 

1451 also the docstring of :py:meth:`~sympy.solvers.ode.homogeneous_order`. 

1452 

1453 If the coefficients `P` and `Q` in the differential equation above are 

1454 homogeneous functions of the same order, then it can be shown that the 

1455 substitution `y = u_1 x` (i.e. `u_1 = y/x`) will turn the differential 

1456 equation into an equation separable in the variables `x` and `u`. If 

1457 `h(u_1)` is the function that results from making the substitution `u_1 = 

1458 f(x)/x` on `P(x, f(x))` and `g(u_2)` is the function that results from the 

1459 substitution on `Q(x, f(x))` in the differential equation `P(x, f(x)) + 

1460 Q(x, f(x)) f'(x) = 0`, then the general solution is:: 

1461 

1462 >>> from sympy import Function, dsolve, pprint 

1463 >>> from sympy.abc import x 

1464 >>> f, g, h = map(Function, ['f', 'g', 'h']) 

1465 >>> genform = g(f(x)/x) + h(f(x)/x)*f(x).diff(x) 

1466 >>> pprint(genform) 

1467 /f(x)\ /f(x)\ d 

1468 g|----| + h|----|*--(f(x)) 

1469 \ x / \ x / dx 

1470 >>> pprint(dsolve(genform, f(x), 

1471 ... hint='1st_homogeneous_coeff_subs_dep_div_indep_Integral')) 

1472 f(x) 

1473 ---- 

1474 x 

1475 / 

1476 | 

1477 | -h(u1) 

1478 log(x) = C1 + | ---------------- d(u1) 

1479 | u1*h(u1) + g(u1) 

1480 | 

1481 / 

1482 

1483 Where `u_1 h(u_1) + g(u_1) \ne 0` and `x \ne 0`. 

1484 

1485 See also the docstrings of 

1486 :obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and 

1487 :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`. 

1488 

1489 Examples 

1490 ======== 

1491 

1492 >>> from sympy import Function, dsolve 

1493 >>> from sympy.abc import x 

1494 >>> f = Function('f') 

1495 >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), 

1496 ... hint='1st_homogeneous_coeff_subs_dep_div_indep', simplify=False)) 

1497 / 3 \ 

1498 |3*f(x) f (x)| 

1499 log|------ + -----| 

1500 | x 3 | 

1501 \ x / 

1502 log(x) = log(C1) - ------------------- 

1503 3 

1504 

1505 References 

1506 ========== 

1507 

1508 - https://en.wikipedia.org/wiki/Homogeneous_differential_equation 

1509 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

1510 Dover 1963, pp. 59 

1511 

1512 # indirect doctest 

1513 

1514 """ 

1515 hint = "1st_homogeneous_coeff_subs_dep_div_indep" 

1516 has_integral = True 

1517 order = [1] 

1518 

1519 def _wilds(self, f, x, order): 

1520 d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) 

1521 e = Wild('e', exclude=[f(x).diff(x)]) 

1522 return d, e 

1523 

1524 def _equation(self, fx, x, order): 

1525 d, e = self.wilds() 

1526 return d + e*fx.diff(x) 

1527 

1528 def _verify(self, fx): 

1529 self.d, self.e = self.wilds_match() 

1530 self.y = Dummy('y') 

1531 x = self.ode_problem.sym 

1532 self.d = separatevars(self.d.subs(fx, self.y)) 

1533 self.e = separatevars(self.e.subs(fx, self.y)) 

1534 ordera = homogeneous_order(self.d, x, self.y) 

1535 orderb = homogeneous_order(self.e, x, self.y) 

1536 if ordera == orderb and ordera is not None: 

1537 self.u = Dummy('u') 

1538 if simplify((self.d + self.u*self.e).subs({x: 1, self.y: self.u})) != 0: 

1539 return True 

1540 return False 

1541 return False 

1542 

1543 def _get_match_object(self): 

1544 fx = self.ode_problem.func 

1545 x = self.ode_problem.sym 

1546 self.u1 = Dummy('u1') 

1547 xarg = 0 

1548 yarg = 0 

1549 return [self.d, self.e, fx, x, self.u, self.u1, self.y, xarg, yarg] 

1550 

1551 def _get_general_solution(self, *, simplify_flag: bool = True): 

1552 d, e, fx, x, u, u1, y, xarg, yarg = self._get_match_object() 

1553 (C1,) = self.ode_problem.get_numbered_constants(num=1) 

1554 int = Integral( 

1555 (-e/(d + u1*e)).subs({x: 1, y: u1}), 

1556 (u1, None, fx/x)) 

1557 sol = logcombine(Eq(log(x), int + log(C1)), force=True) 

1558 gen_sol = sol.subs(fx, u).subs(((u, u - yarg), (x, x - xarg), (u, fx))) 

1559 return [gen_sol] 

1560 

1561 

1562class HomogeneousCoeffSubsIndepDivDep(SinglePatternODESolver): 

1563 r""" 

1564 Solves a 1st order differential equation with homogeneous coefficients 

1565 using the substitution `u_2 = \frac{\text{<independent 

1566 variable>}}{\text{<dependent variable>}}`. 

1567 

1568 This is a differential equation 

1569 

1570 .. math:: P(x, y) + Q(x, y) dy/dx = 0 

1571 

1572 such that `P` and `Q` are homogeneous and of the same order. A function 

1573 `F(x, y)` is homogeneous of order `n` if `F(x t, y t) = t^n F(x, y)`. 

1574 Equivalently, `F(x, y)` can be rewritten as `G(y/x)` or `H(x/y)`. See 

1575 also the docstring of :py:meth:`~sympy.solvers.ode.homogeneous_order`. 

1576 

1577 If the coefficients `P` and `Q` in the differential equation above are 

1578 homogeneous functions of the same order, then it can be shown that the 

1579 substitution `x = u_2 y` (i.e. `u_2 = x/y`) will turn the differential 

1580 equation into an equation separable in the variables `y` and `u_2`. If 

1581 `h(u_2)` is the function that results from making the substitution `u_2 = 

1582 x/f(x)` on `P(x, f(x))` and `g(u_2)` is the function that results from the 

1583 substitution on `Q(x, f(x))` in the differential equation `P(x, f(x)) + 

1584 Q(x, f(x)) f'(x) = 0`, then the general solution is: 

1585 

1586 >>> from sympy import Function, dsolve, pprint 

1587 >>> from sympy.abc import x 

1588 >>> f, g, h = map(Function, ['f', 'g', 'h']) 

1589 >>> genform = g(x/f(x)) + h(x/f(x))*f(x).diff(x) 

1590 >>> pprint(genform) 

1591 / x \ / x \ d 

1592 g|----| + h|----|*--(f(x)) 

1593 \f(x)/ \f(x)/ dx 

1594 >>> pprint(dsolve(genform, f(x), 

1595 ... hint='1st_homogeneous_coeff_subs_indep_div_dep_Integral')) 

1596 x 

1597 ---- 

1598 f(x) 

1599 / 

1600 | 

1601 | -g(u1) 

1602 | ---------------- d(u1) 

1603 | u1*g(u1) + h(u1) 

1604 | 

1605 / 

1606 <BLANKLINE> 

1607 f(x) = C1*e 

1608 

1609 Where `u_1 g(u_1) + h(u_1) \ne 0` and `f(x) \ne 0`. 

1610 

1611 See also the docstrings of 

1612 :obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and 

1613 :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep`. 

1614 

1615 Examples 

1616 ======== 

1617 

1618 >>> from sympy import Function, pprint, dsolve 

1619 >>> from sympy.abc import x 

1620 >>> f = Function('f') 

1621 >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), 

1622 ... hint='1st_homogeneous_coeff_subs_indep_div_dep', 

1623 ... simplify=False)) 

1624 / 2 \ 

1625 | 3*x | 

1626 log|----- + 1| 

1627 | 2 | 

1628 \f (x) / 

1629 log(f(x)) = log(C1) - -------------- 

1630 3 

1631 

1632 References 

1633 ========== 

1634 

1635 - https://en.wikipedia.org/wiki/Homogeneous_differential_equation 

1636 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

1637 Dover 1963, pp. 59 

1638 

1639 # indirect doctest 

1640 

1641 """ 

1642 hint = "1st_homogeneous_coeff_subs_indep_div_dep" 

1643 has_integral = True 

1644 order = [1] 

1645 

1646 def _wilds(self, f, x, order): 

1647 d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) 

1648 e = Wild('e', exclude=[f(x).diff(x)]) 

1649 return d, e 

1650 

1651 def _equation(self, fx, x, order): 

1652 d, e = self.wilds() 

1653 return d + e*fx.diff(x) 

1654 

1655 def _verify(self, fx): 

1656 self.d, self.e = self.wilds_match() 

1657 self.y = Dummy('y') 

1658 x = self.ode_problem.sym 

1659 self.d = separatevars(self.d.subs(fx, self.y)) 

1660 self.e = separatevars(self.e.subs(fx, self.y)) 

1661 ordera = homogeneous_order(self.d, x, self.y) 

1662 orderb = homogeneous_order(self.e, x, self.y) 

1663 if ordera == orderb and ordera is not None: 

1664 self.u = Dummy('u') 

1665 if simplify((self.e + self.u*self.d).subs({x: self.u, self.y: 1})) != 0: 

1666 return True 

1667 return False 

1668 return False 

1669 

1670 def _get_match_object(self): 

1671 fx = self.ode_problem.func 

1672 x = self.ode_problem.sym 

1673 self.u1 = Dummy('u1') 

1674 xarg = 0 

1675 yarg = 0 

1676 return [self.d, self.e, fx, x, self.u, self.u1, self.y, xarg, yarg] 

1677 

1678 def _get_general_solution(self, *, simplify_flag: bool = True): 

1679 d, e, fx, x, u, u1, y, xarg, yarg = self._get_match_object() 

1680 (C1,) = self.ode_problem.get_numbered_constants(num=1) 

1681 int = Integral(simplify((-d/(e + u1*d)).subs({x: u1, y: 1})), (u1, None, x/fx)) # type: ignore 

1682 sol = logcombine(Eq(log(fx), int + log(C1)), force=True) 

1683 gen_sol = sol.subs(fx, u).subs(((u, u - yarg), (x, x - xarg), (u, fx))) 

1684 return [gen_sol] 

1685 

1686 

1687class HomogeneousCoeffBest(HomogeneousCoeffSubsIndepDivDep, HomogeneousCoeffSubsDepDivIndep): 

1688 r""" 

1689 Returns the best solution to an ODE from the two hints 

1690 ``1st_homogeneous_coeff_subs_dep_div_indep`` and 

1691 ``1st_homogeneous_coeff_subs_indep_div_dep``. 

1692 

1693 This is as determined by :py:meth:`~sympy.solvers.ode.ode.ode_sol_simplicity`. 

1694 

1695 See the 

1696 :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` 

1697 and 

1698 :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` 

1699 docstrings for more information on these hints. Note that there is no 

1700 ``ode_1st_homogeneous_coeff_best_Integral`` hint. 

1701 

1702 Examples 

1703 ======== 

1704 

1705 >>> from sympy import Function, dsolve, pprint 

1706 >>> from sympy.abc import x 

1707 >>> f = Function('f') 

1708 >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), 

1709 ... hint='1st_homogeneous_coeff_best', simplify=False)) 

1710 / 2 \ 

1711 | 3*x | 

1712 log|----- + 1| 

1713 | 2 | 

1714 \f (x) / 

1715 log(f(x)) = log(C1) - -------------- 

1716 3 

1717 

1718 References 

1719 ========== 

1720 

1721 - https://en.wikipedia.org/wiki/Homogeneous_differential_equation 

1722 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

1723 Dover 1963, pp. 59 

1724 

1725 # indirect doctest 

1726 

1727 """ 

1728 hint = "1st_homogeneous_coeff_best" 

1729 has_integral = False 

1730 order = [1] 

1731 

1732 def _verify(self, fx): 

1733 if HomogeneousCoeffSubsIndepDivDep._verify(self, fx) and HomogeneousCoeffSubsDepDivIndep._verify(self, fx): 

1734 return True 

1735 return False 

1736 

1737 def _get_general_solution(self, *, simplify_flag: bool = True): 

1738 # There are two substitutions that solve the equation, u1=y/x and u2=x/y 

1739 # # They produce different integrals, so try them both and see which 

1740 # # one is easier 

1741 sol1 = HomogeneousCoeffSubsIndepDivDep._get_general_solution(self) 

1742 sol2 = HomogeneousCoeffSubsDepDivIndep._get_general_solution(self) 

1743 fx = self.ode_problem.func 

1744 if simplify_flag: 

1745 sol1 = odesimp(self.ode_problem.eq, *sol1, fx, "1st_homogeneous_coeff_subs_indep_div_dep") 

1746 sol2 = odesimp(self.ode_problem.eq, *sol2, fx, "1st_homogeneous_coeff_subs_dep_div_indep") 

1747 return min([sol1, sol2], key=lambda x: ode_sol_simplicity(x, fx, trysolving=not simplify)) 

1748 

1749 

1750class LinearCoefficients(HomogeneousCoeffBest): 

1751 r""" 

1752 Solves a differential equation with linear coefficients. 

1753 

1754 The general form of a differential equation with linear coefficients is 

1755 

1756 .. math:: y' + F\left(\!\frac{a_1 x + b_1 y + c_1}{a_2 x + b_2 y + 

1757 c_2}\!\right) = 0\text{,} 

1758 

1759 where `a_1`, `b_1`, `c_1`, `a_2`, `b_2`, `c_2` are constants and `a_1 b_2 

1760 - a_2 b_1 \ne 0`. 

1761 

1762 This can be solved by substituting: 

1763 

1764 .. math:: x = x' + \frac{b_2 c_1 - b_1 c_2}{a_2 b_1 - a_1 b_2} 

1765 

1766 y = y' + \frac{a_1 c_2 - a_2 c_1}{a_2 b_1 - a_1 

1767 b_2}\text{.} 

1768 

1769 This substitution reduces the equation to a homogeneous differential 

1770 equation. 

1771 

1772 See Also 

1773 ======== 

1774 :obj:`sympy.solvers.ode.single.HomogeneousCoeffBest` 

1775 :obj:`sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` 

1776 :obj:`sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` 

1777 

1778 Examples 

1779 ======== 

1780 

1781 >>> from sympy import dsolve, Function, pprint 

1782 >>> from sympy.abc import x 

1783 >>> f = Function('f') 

1784 >>> df = f(x).diff(x) 

1785 >>> eq = (x + f(x) + 1)*df + (f(x) - 6*x + 1) 

1786 >>> dsolve(eq, hint='linear_coefficients') 

1787 [Eq(f(x), -x - sqrt(C1 + 7*x**2) - 1), Eq(f(x), -x + sqrt(C1 + 7*x**2) - 1)] 

1788 >>> pprint(dsolve(eq, hint='linear_coefficients')) 

1789 ___________ ___________ 

1790 / 2 / 2 

1791 [f(x) = -x - \/ C1 + 7*x - 1, f(x) = -x + \/ C1 + 7*x - 1] 

1792 

1793 

1794 References 

1795 ========== 

1796 

1797 - Joel Moses, "Symbolic Integration - The Stormy Decade", Communications 

1798 of the ACM, Volume 14, Number 8, August 1971, pp. 558 

1799 """ 

1800 hint = "linear_coefficients" 

1801 has_integral = True 

1802 order = [1] 

1803 

1804 def _wilds(self, f, x, order): 

1805 d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) 

1806 e = Wild('e', exclude=[f(x).diff(x)]) 

1807 return d, e 

1808 

1809 def _equation(self, fx, x, order): 

1810 d, e = self.wilds() 

1811 return d + e*fx.diff(x) 

1812 

1813 def _verify(self, fx): 

1814 self.d, self.e = self.wilds_match() 

1815 a, b = self.wilds() 

1816 F = self.d/self.e 

1817 x = self.ode_problem.sym 

1818 params = self._linear_coeff_match(F, fx) 

1819 if params: 

1820 self.xarg, self.yarg = params 

1821 u = Dummy('u') 

1822 t = Dummy('t') 

1823 self.y = Dummy('y') 

1824 # Dummy substitution for df and f(x). 

1825 dummy_eq = self.ode_problem.eq.subs(((fx.diff(x), t), (fx, u))) 

1826 reps = ((x, x + self.xarg), (u, u + self.yarg), (t, fx.diff(x)), (u, fx)) 

1827 dummy_eq = simplify(dummy_eq.subs(reps)) 

1828 # get the re-cast values for e and d 

1829 r2 = collect(expand(dummy_eq), [fx.diff(x), fx]).match(a*fx.diff(x) + b) 

1830 if r2: 

1831 self.d, self.e = r2[b], r2[a] 

1832 orderd = homogeneous_order(self.d, x, fx) 

1833 ordere = homogeneous_order(self.e, x, fx) 

1834 if orderd == ordere and orderd is not None: 

1835 self.d = self.d.subs(fx, self.y) 

1836 self.e = self.e.subs(fx, self.y) 

1837 return True 

1838 return False 

1839 return False 

1840 

1841 def _linear_coeff_match(self, expr, func): 

1842 r""" 

1843 Helper function to match hint ``linear_coefficients``. 

1844 

1845 Matches the expression to the form `(a_1 x + b_1 f(x) + c_1)/(a_2 x + b_2 

1846 f(x) + c_2)` where the following conditions hold: 

1847 

1848 1. `a_1`, `b_1`, `c_1`, `a_2`, `b_2`, `c_2` are Rationals; 

1849 2. `c_1` or `c_2` are not equal to zero; 

1850 3. `a_2 b_1 - a_1 b_2` is not equal to zero. 

1851 

1852 Return ``xarg``, ``yarg`` where 

1853 

1854 1. ``xarg`` = `(b_2 c_1 - b_1 c_2)/(a_2 b_1 - a_1 b_2)` 

1855 2. ``yarg`` = `(a_1 c_2 - a_2 c_1)/(a_2 b_1 - a_1 b_2)` 

1856 

1857 

1858 Examples 

1859 ======== 

1860 

1861 >>> from sympy import Function, sin 

1862 >>> from sympy.abc import x 

1863 >>> from sympy.solvers.ode.single import LinearCoefficients 

1864 >>> f = Function('f') 

1865 >>> eq = (-25*f(x) - 8*x + 62)/(4*f(x) + 11*x - 11) 

1866 >>> obj = LinearCoefficients(eq) 

1867 >>> obj._linear_coeff_match(eq, f(x)) 

1868 (1/9, 22/9) 

1869 >>> eq = sin((-5*f(x) - 8*x + 6)/(4*f(x) + x - 1)) 

1870 >>> obj = LinearCoefficients(eq) 

1871 >>> obj._linear_coeff_match(eq, f(x)) 

1872 (19/27, 2/27) 

1873 >>> eq = sin(f(x)/x) 

1874 >>> obj = LinearCoefficients(eq) 

1875 >>> obj._linear_coeff_match(eq, f(x)) 

1876 

1877 """ 

1878 f = func.func 

1879 x = func.args[0] 

1880 def abc(eq): 

1881 r''' 

1882 Internal function of _linear_coeff_match 

1883 that returns Rationals a, b, c 

1884 if eq is a*x + b*f(x) + c, else None. 

1885 ''' 

1886 eq = _mexpand(eq) 

1887 c = eq.as_independent(x, f(x), as_Add=True)[0] 

1888 if not c.is_Rational: 

1889 return 

1890 a = eq.coeff(x) 

1891 if not a.is_Rational: 

1892 return 

1893 b = eq.coeff(f(x)) 

1894 if not b.is_Rational: 

1895 return 

1896 if eq == a*x + b*f(x) + c: 

1897 return a, b, c 

1898 

1899 def match(arg): 

1900 r''' 

1901 Internal function of _linear_coeff_match that returns Rationals a1, 

1902 b1, c1, a2, b2, c2 and a2*b1 - a1*b2 of the expression (a1*x + b1*f(x) 

1903 + c1)/(a2*x + b2*f(x) + c2) if one of c1 or c2 and a2*b1 - a1*b2 is 

1904 non-zero, else None. 

1905 ''' 

1906 n, d = arg.together().as_numer_denom() 

1907 m = abc(n) 

1908 if m is not None: 

1909 a1, b1, c1 = m 

1910 m = abc(d) 

1911 if m is not None: 

1912 a2, b2, c2 = m 

1913 d = a2*b1 - a1*b2 

1914 if (c1 or c2) and d: 

1915 return a1, b1, c1, a2, b2, c2, d 

1916 

1917 m = [fi.args[0] for fi in expr.atoms(Function) if fi.func != f and 

1918 len(fi.args) == 1 and not fi.args[0].is_Function] or {expr} 

1919 m1 = match(m.pop()) 

1920 if m1 and all(match(mi) == m1 for mi in m): 

1921 a1, b1, c1, a2, b2, c2, denom = m1 

1922 return (b2*c1 - b1*c2)/denom, (a1*c2 - a2*c1)/denom 

1923 

1924 def _get_match_object(self): 

1925 fx = self.ode_problem.func 

1926 x = self.ode_problem.sym 

1927 self.u1 = Dummy('u1') 

1928 u = Dummy('u') 

1929 return [self.d, self.e, fx, x, u, self.u1, self.y, self.xarg, self.yarg] 

1930 

1931 

1932class NthOrderReducible(SingleODESolver): 

1933 r""" 

1934 Solves ODEs that only involve derivatives of the dependent variable using 

1935 a substitution of the form `f^n(x) = g(x)`. 

1936 

1937 For example any second order ODE of the form `f''(x) = h(f'(x), x)` can be 

1938 transformed into a pair of 1st order ODEs `g'(x) = h(g(x), x)` and 

1939 `f'(x) = g(x)`. Usually the 1st order ODE for `g` is easier to solve. If 

1940 that gives an explicit solution for `g` then `f` is found simply by 

1941 integration. 

1942 

1943 

1944 Examples 

1945 ======== 

1946 

1947 >>> from sympy import Function, dsolve, Eq 

1948 >>> from sympy.abc import x 

1949 >>> f = Function('f') 

1950 >>> eq = Eq(x*f(x).diff(x)**2 + f(x).diff(x, 2), 0) 

1951 >>> dsolve(eq, f(x), hint='nth_order_reducible') 

1952 ... # doctest: +NORMALIZE_WHITESPACE 

1953 Eq(f(x), C1 - sqrt(-1/C2)*log(-C2*sqrt(-1/C2) + x) + sqrt(-1/C2)*log(C2*sqrt(-1/C2) + x)) 

1954 

1955 """ 

1956 hint = "nth_order_reducible" 

1957 has_integral = False 

1958 

1959 def _matches(self): 

1960 # Any ODE that can be solved with a substitution and 

1961 # repeated integration e.g.: 

1962 # `d^2/dx^2(y) + x*d/dx(y) = constant 

1963 #f'(x) must be finite for this to work 

1964 eq = self.ode_problem.eq_preprocessed 

1965 func = self.ode_problem.func 

1966 x = self.ode_problem.sym 

1967 r""" 

1968 Matches any differential equation that can be rewritten with a smaller 

1969 order. Only derivatives of ``func`` alone, wrt a single variable, 

1970 are considered, and only in them should ``func`` appear. 

1971 """ 

1972 # ODE only handles functions of 1 variable so this affirms that state 

1973 assert len(func.args) == 1 

1974 vc = [d.variable_count[0] for d in eq.atoms(Derivative) 

1975 if d.expr == func and len(d.variable_count) == 1] 

1976 ords = [c for v, c in vc if v == x] 

1977 if len(ords) < 2: 

1978 return False 

1979 self.smallest = min(ords) 

1980 # make sure func does not appear outside of derivatives 

1981 D = Dummy() 

1982 if eq.subs(func.diff(x, self.smallest), D).has(func): 

1983 return False 

1984 return True 

1985 

1986 def _get_general_solution(self, *, simplify_flag: bool = True): 

1987 eq = self.ode_problem.eq 

1988 f = self.ode_problem.func.func 

1989 x = self.ode_problem.sym 

1990 n = self.smallest 

1991 # get a unique function name for g 

1992 names = [a.name for a in eq.atoms(AppliedUndef)] 

1993 while True: 

1994 name = Dummy().name 

1995 if name not in names: 

1996 g = Function(name) 

1997 break 

1998 w = f(x).diff(x, n) 

1999 geq = eq.subs(w, g(x)) 

2000 gsol = dsolve(geq, g(x)) 

2001 

2002 if not isinstance(gsol, list): 

2003 gsol = [gsol] 

2004 

2005 # Might be multiple solutions to the reduced ODE: 

2006 fsol = [] 

2007 for gsoli in gsol: 

2008 fsoli = dsolve(gsoli.subs(g(x), w), f(x)) # or do integration n times 

2009 fsol.append(fsoli) 

2010 

2011 return fsol 

2012 

2013 

2014class SecondHypergeometric(SingleODESolver): 

2015 r""" 

2016 Solves 2nd order linear differential equations. 

2017 

2018 It computes special function solutions which can be expressed using the 

2019 2F1, 1F1 or 0F1 hypergeometric functions. 

2020 

2021 .. math:: y'' + A(x) y' + B(x) y = 0\text{,} 

2022 

2023 where `A` and `B` are rational functions. 

2024 

2025 These kinds of differential equations have solution of non-Liouvillian form. 

2026 

2027 Given linear ODE can be obtained from 2F1 given by 

2028 

2029 .. math:: (x^2 - x) y'' + ((a + b + 1) x - c) y' + b a y = 0\text{,} 

2030 

2031 where {a, b, c} are arbitrary constants. 

2032 

2033 Notes 

2034 ===== 

2035 

2036 The algorithm should find any solution of the form 

2037 

2038 .. math:: y = P(x) _pF_q(..; ..;\frac{\alpha x^k + \beta}{\gamma x^k + \delta})\text{,} 

2039 

2040 where pFq is any of 2F1, 1F1 or 0F1 and `P` is an "arbitrary function". 

2041 Currently only the 2F1 case is implemented in SymPy but the other cases are 

2042 described in the paper and could be implemented in future (contributions 

2043 welcome!). 

2044 

2045 

2046 Examples 

2047 ======== 

2048 

2049 >>> from sympy import Function, dsolve, pprint 

2050 >>> from sympy.abc import x 

2051 >>> f = Function('f') 

2052 >>> eq = (x*x - x)*f(x).diff(x,2) + (5*x - 1)*f(x).diff(x) + 4*f(x) 

2053 >>> pprint(dsolve(eq, f(x), '2nd_hypergeometric')) 

2054 _ 

2055 / / 4 \\ |_ /-1, -1 | \ 

2056 |C1 + C2*|log(x) + -----||* | | | x| 

2057 \ \ x + 1// 2 1 \ 1 | / 

2058 f(x) = -------------------------------------------- 

2059 3 

2060 (x - 1) 

2061 

2062 

2063 References 

2064 ========== 

2065 

2066 - "Non-Liouvillian solutions for second order linear ODEs" by L. Chan, E.S. Cheb-Terrab 

2067 

2068 """ 

2069 hint = "2nd_hypergeometric" 

2070 has_integral = True 

2071 

2072 def _matches(self): 

2073 eq = self.ode_problem.eq_preprocessed 

2074 func = self.ode_problem.func 

2075 r = match_2nd_hypergeometric(eq, func) 

2076 self.match_object = None 

2077 if r: 

2078 A, B = r 

2079 d = equivalence_hypergeometric(A, B, func) 

2080 if d: 

2081 if d['type'] == "2F1": 

2082 self.match_object = match_2nd_2F1_hypergeometric(d['I0'], d['k'], d['sing_point'], func) 

2083 if self.match_object is not None: 

2084 self.match_object.update({'A':A, 'B':B}) 

2085 # We can extend it for 1F1 and 0F1 type also. 

2086 return self.match_object is not None 

2087 

2088 def _get_general_solution(self, *, simplify_flag: bool = True): 

2089 eq = self.ode_problem.eq 

2090 func = self.ode_problem.func 

2091 if self.match_object['type'] == "2F1": 

2092 sol = get_sol_2F1_hypergeometric(eq, func, self.match_object) 

2093 if sol is None: 

2094 raise NotImplementedError("The given ODE " + str(eq) + " cannot be solved by" 

2095 + " the hypergeometric method") 

2096 

2097 return [sol] 

2098 

2099 

2100class NthLinearConstantCoeffHomogeneous(SingleODESolver): 

2101 r""" 

2102 Solves an `n`\th order linear homogeneous differential equation with 

2103 constant coefficients. 

2104 

2105 This is an equation of the form 

2106 

2107 .. math:: a_n f^{(n)}(x) + a_{n-1} f^{(n-1)}(x) + \cdots + a_1 f'(x) 

2108 + a_0 f(x) = 0\text{.} 

2109 

2110 These equations can be solved in a general manner, by taking the roots of 

2111 the characteristic equation `a_n m^n + a_{n-1} m^{n-1} + \cdots + a_1 m + 

2112 a_0 = 0`. The solution will then be the sum of `C_n x^i e^{r x}` terms, 

2113 for each where `C_n` is an arbitrary constant, `r` is a root of the 

2114 characteristic equation and `i` is one of each from 0 to the multiplicity 

2115 of the root - 1 (for example, a root 3 of multiplicity 2 would create the 

2116 terms `C_1 e^{3 x} + C_2 x e^{3 x}`). The exponential is usually expanded 

2117 for complex roots using Euler's equation `e^{I x} = \cos(x) + I \sin(x)`. 

2118 Complex roots always come in conjugate pairs in polynomials with real 

2119 coefficients, so the two roots will be represented (after simplifying the 

2120 constants) as `e^{a x} \left(C_1 \cos(b x) + C_2 \sin(b x)\right)`. 

2121 

2122 If SymPy cannot find exact roots to the characteristic equation, a 

2123 :py:class:`~sympy.polys.rootoftools.ComplexRootOf` instance will be return 

2124 instead. 

2125 

2126 >>> from sympy import Function, dsolve 

2127 >>> from sympy.abc import x 

2128 >>> f = Function('f') 

2129 >>> dsolve(f(x).diff(x, 5) + 10*f(x).diff(x) - 2*f(x), f(x), 

2130 ... hint='nth_linear_constant_coeff_homogeneous') 

2131 ... # doctest: +NORMALIZE_WHITESPACE 

2132 Eq(f(x), C5*exp(x*CRootOf(_x**5 + 10*_x - 2, 0)) 

2133 + (C1*sin(x*im(CRootOf(_x**5 + 10*_x - 2, 1))) 

2134 + C2*cos(x*im(CRootOf(_x**5 + 10*_x - 2, 1))))*exp(x*re(CRootOf(_x**5 + 10*_x - 2, 1))) 

2135 + (C3*sin(x*im(CRootOf(_x**5 + 10*_x - 2, 3))) 

2136 + C4*cos(x*im(CRootOf(_x**5 + 10*_x - 2, 3))))*exp(x*re(CRootOf(_x**5 + 10*_x - 2, 3)))) 

2137 

2138 Note that because this method does not involve integration, there is no 

2139 ``nth_linear_constant_coeff_homogeneous_Integral`` hint. 

2140 

2141 Examples 

2142 ======== 

2143 

2144 >>> from sympy import Function, dsolve, pprint 

2145 >>> from sympy.abc import x 

2146 >>> f = Function('f') 

2147 >>> pprint(dsolve(f(x).diff(x, 4) + 2*f(x).diff(x, 3) - 

2148 ... 2*f(x).diff(x, 2) - 6*f(x).diff(x) + 5*f(x), f(x), 

2149 ... hint='nth_linear_constant_coeff_homogeneous')) 

2150 x -2*x 

2151 f(x) = (C1 + C2*x)*e + (C3*sin(x) + C4*cos(x))*e 

2152 

2153 References 

2154 ========== 

2155 

2156 - https://en.wikipedia.org/wiki/Linear_differential_equation section: 

2157 Nonhomogeneous_equation_with_constant_coefficients 

2158 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

2159 Dover 1963, pp. 211 

2160 

2161 # indirect doctest 

2162 

2163 """ 

2164 hint = "nth_linear_constant_coeff_homogeneous" 

2165 has_integral = False 

2166 

2167 def _matches(self): 

2168 eq = self.ode_problem.eq_high_order_free 

2169 func = self.ode_problem.func 

2170 order = self.ode_problem.order 

2171 x = self.ode_problem.sym 

2172 self.r = self.ode_problem.get_linear_coefficients(eq, func, order) 

2173 if order and self.r and not any(self.r[i].has(x) for i in self.r if i >= 0): 

2174 if not self.r[-1]: 

2175 return True 

2176 else: 

2177 return False 

2178 return False 

2179 

2180 def _get_general_solution(self, *, simplify_flag: bool = True): 

2181 fx = self.ode_problem.func 

2182 order = self.ode_problem.order 

2183 roots, collectterms = _get_const_characteristic_eq_sols(self.r, fx, order) 

2184 # A generator of constants 

2185 constants = self.ode_problem.get_numbered_constants(num=len(roots)) 

2186 gsol = Add(*[i*j for (i, j) in zip(constants, roots)]) 

2187 gsol = Eq(fx, gsol) 

2188 if simplify_flag: 

2189 gsol = _get_simplified_sol([gsol], fx, collectterms) 

2190 

2191 return [gsol] 

2192 

2193 

2194class NthLinearConstantCoeffVariationOfParameters(SingleODESolver): 

2195 r""" 

2196 Solves an `n`\th order linear differential equation with constant 

2197 coefficients using the method of variation of parameters. 

2198 

2199 This method works on any differential equations of the form 

2200 

2201 .. math:: f^{(n)}(x) + a_{n-1} f^{(n-1)}(x) + \cdots + a_1 f'(x) + a_0 

2202 f(x) = P(x)\text{.} 

2203 

2204 This method works by assuming that the particular solution takes the form 

2205 

2206 .. math:: \sum_{x=1}^{n} c_i(x) y_i(x)\text{,} 

2207 

2208 where `y_i` is the `i`\th solution to the homogeneous equation. The 

2209 solution is then solved using Wronskian's and Cramer's Rule. The 

2210 particular solution is given by 

2211 

2212 .. math:: \sum_{x=1}^n \left( \int \frac{W_i(x)}{W(x)} \,dx 

2213 \right) y_i(x) \text{,} 

2214 

2215 where `W(x)` is the Wronskian of the fundamental system (the system of `n` 

2216 linearly independent solutions to the homogeneous equation), and `W_i(x)` 

2217 is the Wronskian of the fundamental system with the `i`\th column replaced 

2218 with `[0, 0, \cdots, 0, P(x)]`. 

2219 

2220 This method is general enough to solve any `n`\th order inhomogeneous 

2221 linear differential equation with constant coefficients, but sometimes 

2222 SymPy cannot simplify the Wronskian well enough to integrate it. If this 

2223 method hangs, try using the 

2224 ``nth_linear_constant_coeff_variation_of_parameters_Integral`` hint and 

2225 simplifying the integrals manually. Also, prefer using 

2226 ``nth_linear_constant_coeff_undetermined_coefficients`` when it 

2227 applies, because it does not use integration, making it faster and more 

2228 reliable. 

2229 

2230 Warning, using simplify=False with 

2231 'nth_linear_constant_coeff_variation_of_parameters' in 

2232 :py:meth:`~sympy.solvers.ode.dsolve` may cause it to hang, because it will 

2233 not attempt to simplify the Wronskian before integrating. It is 

2234 recommended that you only use simplify=False with 

2235 'nth_linear_constant_coeff_variation_of_parameters_Integral' for this 

2236 method, especially if the solution to the homogeneous equation has 

2237 trigonometric functions in it. 

2238 

2239 Examples 

2240 ======== 

2241 

2242 >>> from sympy import Function, dsolve, pprint, exp, log 

2243 >>> from sympy.abc import x 

2244 >>> f = Function('f') 

2245 >>> pprint(dsolve(f(x).diff(x, 3) - 3*f(x).diff(x, 2) + 

2246 ... 3*f(x).diff(x) - f(x) - exp(x)*log(x), f(x), 

2247 ... hint='nth_linear_constant_coeff_variation_of_parameters')) 

2248 / / / x*log(x) 11*x\\\ x 

2249 f(x) = |C1 + x*|C2 + x*|C3 + -------- - ----|||*e 

2250 \ \ \ 6 36 /// 

2251 

2252 References 

2253 ========== 

2254 

2255 - https://en.wikipedia.org/wiki/Variation_of_parameters 

2256 - https://planetmath.org/VariationOfParameters 

2257 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

2258 Dover 1963, pp. 233 

2259 

2260 # indirect doctest 

2261 

2262 """ 

2263 hint = "nth_linear_constant_coeff_variation_of_parameters" 

2264 has_integral = True 

2265 

2266 def _matches(self): 

2267 eq = self.ode_problem.eq_high_order_free 

2268 func = self.ode_problem.func 

2269 order = self.ode_problem.order 

2270 x = self.ode_problem.sym 

2271 self.r = self.ode_problem.get_linear_coefficients(eq, func, order) 

2272 

2273 if order and self.r and not any(self.r[i].has(x) for i in self.r if i >= 0): 

2274 if self.r[-1]: 

2275 return True 

2276 else: 

2277 return False 

2278 return False 

2279 

2280 def _get_general_solution(self, *, simplify_flag: bool = True): 

2281 eq = self.ode_problem.eq_high_order_free 

2282 f = self.ode_problem.func.func 

2283 x = self.ode_problem.sym 

2284 order = self.ode_problem.order 

2285 roots, collectterms = _get_const_characteristic_eq_sols(self.r, f(x), order) 

2286 # A generator of constants 

2287 constants = self.ode_problem.get_numbered_constants(num=len(roots)) 

2288 homogen_sol = Add(*[i*j for (i, j) in zip(constants, roots)]) 

2289 homogen_sol = Eq(f(x), homogen_sol) 

2290 homogen_sol = _solve_variation_of_parameters(eq, f(x), roots, homogen_sol, order, self.r, simplify_flag) 

2291 if simplify_flag: 

2292 homogen_sol = _get_simplified_sol([homogen_sol], f(x), collectterms) 

2293 return [homogen_sol] 

2294 

2295 

2296class NthLinearConstantCoeffUndeterminedCoefficients(SingleODESolver): 

2297 r""" 

2298 Solves an `n`\th order linear differential equation with constant 

2299 coefficients using the method of undetermined coefficients. 

2300 

2301 This method works on differential equations of the form 

2302 

2303 .. math:: a_n f^{(n)}(x) + a_{n-1} f^{(n-1)}(x) + \cdots + a_1 f'(x) 

2304 + a_0 f(x) = P(x)\text{,} 

2305 

2306 where `P(x)` is a function that has a finite number of linearly 

2307 independent derivatives. 

2308 

2309 Functions that fit this requirement are finite sums functions of the form 

2310 `a x^i e^{b x} \sin(c x + d)` or `a x^i e^{b x} \cos(c x + d)`, where `i` 

2311 is a non-negative integer and `a`, `b`, `c`, and `d` are constants. For 

2312 example any polynomial in `x`, functions like `x^2 e^{2 x}`, `x \sin(x)`, 

2313 and `e^x \cos(x)` can all be used. Products of `\sin`'s and `\cos`'s have 

2314 a finite number of derivatives, because they can be expanded into `\sin(a 

2315 x)` and `\cos(b x)` terms. However, SymPy currently cannot do that 

2316 expansion, so you will need to manually rewrite the expression in terms of 

2317 the above to use this method. So, for example, you will need to manually 

2318 convert `\sin^2(x)` into `(1 + \cos(2 x))/2` to properly apply the method 

2319 of undetermined coefficients on it. 

2320 

2321 This method works by creating a trial function from the expression and all 

2322 of its linear independent derivatives and substituting them into the 

2323 original ODE. The coefficients for each term will be a system of linear 

2324 equations, which are be solved for and substituted, giving the solution. 

2325 If any of the trial functions are linearly dependent on the solution to 

2326 the homogeneous equation, they are multiplied by sufficient `x` to make 

2327 them linearly independent. 

2328 

2329 Examples 

2330 ======== 

2331 

2332 >>> from sympy import Function, dsolve, pprint, exp, cos 

2333 >>> from sympy.abc import x 

2334 >>> f = Function('f') 

2335 >>> pprint(dsolve(f(x).diff(x, 2) + 2*f(x).diff(x) + f(x) - 

2336 ... 4*exp(-x)*x**2 + cos(2*x), f(x), 

2337 ... hint='nth_linear_constant_coeff_undetermined_coefficients')) 

2338 / / 3\\ 

2339 | | x || -x 4*sin(2*x) 3*cos(2*x) 

2340 f(x) = |C1 + x*|C2 + --||*e - ---------- + ---------- 

2341 \ \ 3 // 25 25 

2342 

2343 References 

2344 ========== 

2345 

2346 - https://en.wikipedia.org/wiki/Method_of_undetermined_coefficients 

2347 - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", 

2348 Dover 1963, pp. 221 

2349 

2350 # indirect doctest 

2351 

2352 """ 

2353 hint = "nth_linear_constant_coeff_undetermined_coefficients" 

2354 has_integral = False 

2355 

2356 def _matches(self): 

2357 eq = self.ode_problem.eq_high_order_free 

2358 func = self.ode_problem.func 

2359 order = self.ode_problem.order 

2360 x = self.ode_problem.sym 

2361 self.r = self.ode_problem.get_linear_coefficients(eq, func, order) 

2362 does_match = False 

2363 if order and self.r and not any(self.r[i].has(x) for i in self.r if i >= 0): 

2364 if self.r[-1]: 

2365 eq_homogeneous = Add(eq, -self.r[-1]) 

2366 undetcoeff = _undetermined_coefficients_match(self.r[-1], x, func, eq_homogeneous) 

2367 if undetcoeff['test']: 

2368 self.trialset = undetcoeff['trialset'] 

2369 does_match = True 

2370 return does_match 

2371 

2372 def _get_general_solution(self, *, simplify_flag: bool = True): 

2373 eq = self.ode_problem.eq 

2374 f = self.ode_problem.func.func 

2375 x = self.ode_problem.sym 

2376 order = self.ode_problem.order 

2377 roots, collectterms = _get_const_characteristic_eq_sols(self.r, f(x), order) 

2378 # A generator of constants 

2379 constants = self.ode_problem.get_numbered_constants(num=len(roots)) 

2380 homogen_sol = Add(*[i*j for (i, j) in zip(constants, roots)]) 

2381 homogen_sol = Eq(f(x), homogen_sol) 

2382 self.r.update({'list': roots, 'sol': homogen_sol, 'simpliy_flag': simplify_flag}) 

2383 gsol = _solve_undetermined_coefficients(eq, f(x), order, self.r, self.trialset) 

2384 if simplify_flag: 

2385 gsol = _get_simplified_sol([gsol], f(x), collectterms) 

2386 return [gsol] 

2387 

2388 

2389class NthLinearEulerEqHomogeneous(SingleODESolver): 

2390 r""" 

2391 Solves an `n`\th order linear homogeneous variable-coefficient 

2392 Cauchy-Euler equidimensional ordinary differential equation. 

2393 

2394 This is an equation with form `0 = a_0 f(x) + a_1 x f'(x) + a_2 x^2 f''(x) 

2395 \cdots`. 

2396 

2397 These equations can be solved in a general manner, by substituting 

2398 solutions of the form `f(x) = x^r`, and deriving a characteristic equation 

2399 for `r`. When there are repeated roots, we include extra terms of the 

2400 form `C_{r k} \ln^k(x) x^r`, where `C_{r k}` is an arbitrary integration 

2401 constant, `r` is a root of the characteristic equation, and `k` ranges 

2402 over the multiplicity of `r`. In the cases where the roots are complex, 

2403 solutions of the form `C_1 x^a \sin(b \log(x)) + C_2 x^a \cos(b \log(x))` 

2404 are returned, based on expansions with Euler's formula. The general 

2405 solution is the sum of the terms found. If SymPy cannot find exact roots 

2406 to the characteristic equation, a 

2407 :py:obj:`~.ComplexRootOf` instance will be returned 

2408 instead. 

2409 

2410 >>> from sympy import Function, dsolve 

2411 >>> from sympy.abc import x 

2412 >>> f = Function('f') 

2413 >>> dsolve(4*x**2*f(x).diff(x, 2) + f(x), f(x), 

2414 ... hint='nth_linear_euler_eq_homogeneous') 

2415 ... # doctest: +NORMALIZE_WHITESPACE 

2416 Eq(f(x), sqrt(x)*(C1 + C2*log(x))) 

2417 

2418 Note that because this method does not involve integration, there is no 

2419 ``nth_linear_euler_eq_homogeneous_Integral`` hint. 

2420 

2421 The following is for internal use: 

2422 

2423 - ``returns = 'sol'`` returns the solution to the ODE. 

2424 - ``returns = 'list'`` returns a list of linearly independent solutions, 

2425 corresponding to the fundamental solution set, for use with non 

2426 homogeneous solution methods like variation of parameters and 

2427 undetermined coefficients. Note that, though the solutions should be 

2428 linearly independent, this function does not explicitly check that. You 

2429 can do ``assert simplify(wronskian(sollist)) != 0`` to check for linear 

2430 independence. Also, ``assert len(sollist) == order`` will need to pass. 

2431 - ``returns = 'both'``, return a dictionary ``{'sol': <solution to ODE>, 

2432 'list': <list of linearly independent solutions>}``. 

2433 

2434 Examples 

2435 ======== 

2436 

2437 >>> from sympy import Function, dsolve, pprint 

2438 >>> from sympy.abc import x 

2439 >>> f = Function('f') 

2440 >>> eq = f(x).diff(x, 2)*x**2 - 4*f(x).diff(x)*x + 6*f(x) 

2441 >>> pprint(dsolve(eq, f(x), 

2442 ... hint='nth_linear_euler_eq_homogeneous')) 

2443 2 

2444 f(x) = x *(C1 + C2*x) 

2445 

2446 References 

2447 ========== 

2448 

2449 - https://en.wikipedia.org/wiki/Cauchy%E2%80%93Euler_equation 

2450 - C. Bender & S. Orszag, "Advanced Mathematical Methods for Scientists and 

2451 Engineers", Springer 1999, pp. 12 

2452 

2453 # indirect doctest 

2454 

2455 """ 

2456 hint = "nth_linear_euler_eq_homogeneous" 

2457 has_integral = False 

2458 

2459 def _matches(self): 

2460 eq = self.ode_problem.eq_preprocessed 

2461 f = self.ode_problem.func.func 

2462 order = self.ode_problem.order 

2463 x = self.ode_problem.sym 

2464 match = self.ode_problem.get_linear_coefficients(eq, f(x), order) 

2465 self.r = None 

2466 does_match = False 

2467 

2468 if order and match: 

2469 coeff = match[order] 

2470 factor = x**order / coeff 

2471 self.r = {i: factor*match[i] for i in match} 

2472 if self.r and all(_test_term(self.r[i], f(x), i) for i in 

2473 self.r if i >= 0): 

2474 if not self.r[-1]: 

2475 does_match = True 

2476 return does_match 

2477 

2478 def _get_general_solution(self, *, simplify_flag: bool = True): 

2479 fx = self.ode_problem.func 

2480 eq = self.ode_problem.eq 

2481 homogen_sol = _get_euler_characteristic_eq_sols(eq, fx, self.r)[0] 

2482 return [homogen_sol] 

2483 

2484 

2485class NthLinearEulerEqNonhomogeneousVariationOfParameters(SingleODESolver): 

2486 r""" 

2487 Solves an `n`\th order linear non homogeneous Cauchy-Euler equidimensional 

2488 ordinary differential equation using variation of parameters. 

2489 

2490 This is an equation with form `g(x) = a_0 f(x) + a_1 x f'(x) + a_2 x^2 f''(x) 

2491 \cdots`. 

2492 

2493 This method works by assuming that the particular solution takes the form 

2494 

2495 .. math:: \sum_{x=1}^{n} c_i(x) y_i(x) {a_n} {x^n} \text{, } 

2496 

2497 where `y_i` is the `i`\th solution to the homogeneous equation. The 

2498 solution is then solved using Wronskian's and Cramer's Rule. The 

2499 particular solution is given by multiplying eq given below with `a_n x^{n}` 

2500 

2501 .. math:: \sum_{x=1}^n \left( \int \frac{W_i(x)}{W(x)} \, dx 

2502 \right) y_i(x) \text{, } 

2503 

2504 where `W(x)` is the Wronskian of the fundamental system (the system of `n` 

2505 linearly independent solutions to the homogeneous equation), and `W_i(x)` 

2506 is the Wronskian of the fundamental system with the `i`\th column replaced 

2507 with `[0, 0, \cdots, 0, \frac{x^{- n}}{a_n} g{\left(x \right)}]`. 

2508 

2509 This method is general enough to solve any `n`\th order inhomogeneous 

2510 linear differential equation, but sometimes SymPy cannot simplify the 

2511 Wronskian well enough to integrate it. If this method hangs, try using the 

2512 ``nth_linear_constant_coeff_variation_of_parameters_Integral`` hint and 

2513 simplifying the integrals manually. Also, prefer using 

2514 ``nth_linear_constant_coeff_undetermined_coefficients`` when it 

2515 applies, because it does not use integration, making it faster and more 

2516 reliable. 

2517 

2518 Warning, using simplify=False with 

2519 'nth_linear_constant_coeff_variation_of_parameters' in 

2520 :py:meth:`~sympy.solvers.ode.dsolve` may cause it to hang, because it will 

2521 not attempt to simplify the Wronskian before integrating. It is 

2522 recommended that you only use simplify=False with 

2523 'nth_linear_constant_coeff_variation_of_parameters_Integral' for this 

2524 method, especially if the solution to the homogeneous equation has 

2525 trigonometric functions in it. 

2526 

2527 Examples 

2528 ======== 

2529 

2530 >>> from sympy import Function, dsolve, Derivative 

2531 >>> from sympy.abc import x 

2532 >>> f = Function('f') 

2533 >>> eq = x**2*Derivative(f(x), x, x) - 2*x*Derivative(f(x), x) + 2*f(x) - x**4 

2534 >>> dsolve(eq, f(x), 

2535 ... hint='nth_linear_euler_eq_nonhomogeneous_variation_of_parameters').expand() 

2536 Eq(f(x), C1*x + C2*x**2 + x**4/6) 

2537 

2538 """ 

2539 hint = "nth_linear_euler_eq_nonhomogeneous_variation_of_parameters" 

2540 has_integral = True 

2541 

2542 def _matches(self): 

2543 eq = self.ode_problem.eq_preprocessed 

2544 f = self.ode_problem.func.func 

2545 order = self.ode_problem.order 

2546 x = self.ode_problem.sym 

2547 match = self.ode_problem.get_linear_coefficients(eq, f(x), order) 

2548 self.r = None 

2549 does_match = False 

2550 

2551 if order and match: 

2552 coeff = match[order] 

2553 factor = x**order / coeff 

2554 self.r = {i: factor*match[i] for i in match} 

2555 if self.r and all(_test_term(self.r[i], f(x), i) for i in 

2556 self.r if i >= 0): 

2557 if self.r[-1]: 

2558 does_match = True 

2559 

2560 return does_match 

2561 

2562 def _get_general_solution(self, *, simplify_flag: bool = True): 

2563 eq = self.ode_problem.eq 

2564 f = self.ode_problem.func.func 

2565 x = self.ode_problem.sym 

2566 order = self.ode_problem.order 

2567 homogen_sol, roots = _get_euler_characteristic_eq_sols(eq, f(x), self.r) 

2568 self.r[-1] = self.r[-1]/self.r[order] 

2569 sol = _solve_variation_of_parameters(eq, f(x), roots, homogen_sol, order, self.r, simplify_flag) 

2570 

2571 return [Eq(f(x), homogen_sol.rhs + (sol.rhs - homogen_sol.rhs)*self.r[order])] 

2572 

2573 

2574class NthLinearEulerEqNonhomogeneousUndeterminedCoefficients(SingleODESolver): 

2575 r""" 

2576 Solves an `n`\th order linear non homogeneous Cauchy-Euler equidimensional 

2577 ordinary differential equation using undetermined coefficients. 

2578 

2579 This is an equation with form `g(x) = a_0 f(x) + a_1 x f'(x) + a_2 x^2 f''(x) 

2580 \cdots`. 

2581 

2582 These equations can be solved in a general manner, by substituting 

2583 solutions of the form `x = exp(t)`, and deriving a characteristic equation 

2584 of form `g(exp(t)) = b_0 f(t) + b_1 f'(t) + b_2 f''(t) \cdots` which can 

2585 be then solved by nth_linear_constant_coeff_undetermined_coefficients if 

2586 g(exp(t)) has finite number of linearly independent derivatives. 

2587 

2588 Functions that fit this requirement are finite sums functions of the form 

2589 `a x^i e^{b x} \sin(c x + d)` or `a x^i e^{b x} \cos(c x + d)`, where `i` 

2590 is a non-negative integer and `a`, `b`, `c`, and `d` are constants. For 

2591 example any polynomial in `x`, functions like `x^2 e^{2 x}`, `x \sin(x)`, 

2592 and `e^x \cos(x)` can all be used. Products of `\sin`'s and `\cos`'s have 

2593 a finite number of derivatives, because they can be expanded into `\sin(a 

2594 x)` and `\cos(b x)` terms. However, SymPy currently cannot do that 

2595 expansion, so you will need to manually rewrite the expression in terms of 

2596 the above to use this method. So, for example, you will need to manually 

2597 convert `\sin^2(x)` into `(1 + \cos(2 x))/2` to properly apply the method 

2598 of undetermined coefficients on it. 

2599 

2600 After replacement of x by exp(t), this method works by creating a trial function 

2601 from the expression and all of its linear independent derivatives and 

2602 substituting them into the original ODE. The coefficients for each term 

2603 will be a system of linear equations, which are be solved for and 

2604 substituted, giving the solution. If any of the trial functions are linearly 

2605 dependent on the solution to the homogeneous equation, they are multiplied 

2606 by sufficient `x` to make them linearly independent. 

2607 

2608 Examples 

2609 ======== 

2610 

2611 >>> from sympy import dsolve, Function, Derivative, log 

2612 >>> from sympy.abc import x 

2613 >>> f = Function('f') 

2614 >>> eq = x**2*Derivative(f(x), x, x) - 2*x*Derivative(f(x), x) + 2*f(x) - log(x) 

2615 >>> dsolve(eq, f(x), 

2616 ... hint='nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients').expand() 

2617 Eq(f(x), C1*x + C2*x**2 + log(x)/2 + 3/4) 

2618 

2619 """ 

2620 hint = "nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients" 

2621 has_integral = False 

2622 

2623 def _matches(self): 

2624 eq = self.ode_problem.eq_high_order_free 

2625 f = self.ode_problem.func.func 

2626 order = self.ode_problem.order 

2627 x = self.ode_problem.sym 

2628 match = self.ode_problem.get_linear_coefficients(eq, f(x), order) 

2629 self.r = None 

2630 does_match = False 

2631 

2632 if order and match: 

2633 coeff = match[order] 

2634 factor = x**order / coeff 

2635 self.r = {i: factor*match[i] for i in match} 

2636 if self.r and all(_test_term(self.r[i], f(x), i) for i in 

2637 self.r if i >= 0): 

2638 if self.r[-1]: 

2639 e, re = posify(self.r[-1].subs(x, exp(x))) 

2640 undetcoeff = _undetermined_coefficients_match(e.subs(re), x) 

2641 if undetcoeff['test']: 

2642 does_match = True 

2643 return does_match 

2644 

2645 def _get_general_solution(self, *, simplify_flag: bool = True): 

2646 f = self.ode_problem.func.func 

2647 x = self.ode_problem.sym 

2648 chareq, eq, symbol = S.Zero, S.Zero, Dummy('x') 

2649 for i in self.r.keys(): 

2650 if i >= 0: 

2651 chareq += (self.r[i]*diff(x**symbol, x, i)*x**-symbol).expand() 

2652 

2653 for i in range(1, degree(Poly(chareq, symbol))+1): 

2654 eq += chareq.coeff(symbol**i)*diff(f(x), x, i) 

2655 

2656 if chareq.as_coeff_add(symbol)[0]: 

2657 eq += chareq.as_coeff_add(symbol)[0]*f(x) 

2658 e, re = posify(self.r[-1].subs(x, exp(x))) 

2659 eq += e.subs(re) 

2660 

2661 self.const_undet_instance = NthLinearConstantCoeffUndeterminedCoefficients(SingleODEProblem(eq, f(x), x)) 

2662 sol = self.const_undet_instance.get_general_solution(simplify = simplify_flag)[0] 

2663 sol = sol.subs(x, log(x)) 

2664 sol = sol.subs(f(log(x)), f(x)).expand() 

2665 

2666 return [sol] 

2667 

2668 

2669class SecondLinearBessel(SingleODESolver): 

2670 r""" 

2671 Gives solution of the Bessel differential equation 

2672 

2673 .. math :: x^2 \frac{d^2y}{dx^2} + x \frac{dy}{dx} y(x) + (x^2-n^2) y(x) 

2674 

2675 if `n` is integer then the solution is of the form ``Eq(f(x), C0 besselj(n,x) 

2676 + C1 bessely(n,x))`` as both the solutions are linearly independent else if 

2677 `n` is a fraction then the solution is of the form ``Eq(f(x), C0 besselj(n,x) 

2678 + C1 besselj(-n,x))`` which can also transform into ``Eq(f(x), C0 besselj(n,x) 

2679 + C1 bessely(n,x))``. 

2680 

2681 Examples 

2682 ======== 

2683 

2684 >>> from sympy.abc import x 

2685 >>> from sympy import Symbol 

2686 >>> v = Symbol('v', positive=True) 

2687 >>> from sympy import dsolve, Function 

2688 >>> f = Function('f') 

2689 >>> y = f(x) 

2690 >>> genform = x**2*y.diff(x, 2) + x*y.diff(x) + (x**2 - v**2)*y 

2691 >>> dsolve(genform) 

2692 Eq(f(x), C1*besselj(v, x) + C2*bessely(v, x)) 

2693 

2694 References 

2695 ========== 

2696 

2697 https://math24.net/bessel-differential-equation.html 

2698 

2699 """ 

2700 hint = "2nd_linear_bessel" 

2701 has_integral = False 

2702 

2703 def _matches(self): 

2704 eq = self.ode_problem.eq_high_order_free 

2705 f = self.ode_problem.func 

2706 order = self.ode_problem.order 

2707 x = self.ode_problem.sym 

2708 df = f.diff(x) 

2709 a = Wild('a', exclude=[f,df]) 

2710 b = Wild('b', exclude=[x, f,df]) 

2711 a4 = Wild('a4', exclude=[x,f,df]) 

2712 b4 = Wild('b4', exclude=[x,f,df]) 

2713 c4 = Wild('c4', exclude=[x,f,df]) 

2714 d4 = Wild('d4', exclude=[x,f,df]) 

2715 a3 = Wild('a3', exclude=[f, df, f.diff(x, 2)]) 

2716 b3 = Wild('b3', exclude=[f, df, f.diff(x, 2)]) 

2717 c3 = Wild('c3', exclude=[f, df, f.diff(x, 2)]) 

2718 deq = a3*(f.diff(x, 2)) + b3*df + c3*f 

2719 r = collect(eq, 

2720 [f.diff(x, 2), df, f]).match(deq) 

2721 if order == 2 and r: 

2722 if not all(r[key].is_polynomial() for key in r): 

2723 n, d = eq.as_numer_denom() 

2724 eq = expand(n) 

2725 r = collect(eq, 

2726 [f.diff(x, 2), df, f]).match(deq) 

2727 

2728 if r and r[a3] != 0: 

2729 # leading coeff of f(x).diff(x, 2) 

2730 coeff = factor(r[a3]).match(a4*(x-b)**b4) 

2731 

2732 if coeff: 

2733 # if coeff[b4] = 0 means constant coefficient 

2734 if coeff[b4] == 0: 

2735 return False 

2736 point = coeff[b] 

2737 else: 

2738 return False 

2739 

2740 if point: 

2741 r[a3] = simplify(r[a3].subs(x, x+point)) 

2742 r[b3] = simplify(r[b3].subs(x, x+point)) 

2743 r[c3] = simplify(r[c3].subs(x, x+point)) 

2744 

2745 # making a3 in the form of x**2 

2746 r[a3] = cancel(r[a3]/(coeff[a4]*(x)**(-2+coeff[b4]))) 

2747 r[b3] = cancel(r[b3]/(coeff[a4]*(x)**(-2+coeff[b4]))) 

2748 r[c3] = cancel(r[c3]/(coeff[a4]*(x)**(-2+coeff[b4]))) 

2749 # checking if b3 is of form c*(x-b) 

2750 coeff1 = factor(r[b3]).match(a4*(x)) 

2751 if coeff1 is None: 

2752 return False 

2753 # c3 maybe of very complex form so I am simply checking (a - b) form 

2754 # if yes later I will match with the standerd form of bessel in a and b 

2755 # a, b are wild variable defined above. 

2756 _coeff2 = r[c3].match(a - b) 

2757 if _coeff2 is None: 

2758 return False 

2759 # matching with standerd form for c3 

2760 coeff2 = factor(_coeff2[a]).match(c4**2*(x)**(2*a4)) 

2761 if coeff2 is None: 

2762 return False 

2763 

2764 if _coeff2[b] == 0: 

2765 coeff2[d4] = 0 

2766 else: 

2767 coeff2[d4] = factor(_coeff2[b]).match(d4**2)[d4] 

2768 

2769 self.rn = {'n':coeff2[d4], 'a4':coeff2[c4], 'd4':coeff2[a4]} 

2770 self.rn['c4'] = coeff1[a4] 

2771 self.rn['b4'] = point 

2772 return True 

2773 return False 

2774 

2775 def _get_general_solution(self, *, simplify_flag: bool = True): 

2776 f = self.ode_problem.func.func 

2777 x = self.ode_problem.sym 

2778 n = self.rn['n'] 

2779 a4 = self.rn['a4'] 

2780 c4 = self.rn['c4'] 

2781 d4 = self.rn['d4'] 

2782 b4 = self.rn['b4'] 

2783 n = sqrt(n**2 + Rational(1, 4)*(c4 - 1)**2) 

2784 (C1, C2) = self.ode_problem.get_numbered_constants(num=2) 

2785 return [Eq(f(x), ((x**(Rational(1-c4,2)))*(C1*besselj(n/d4,a4*x**d4/d4) 

2786 + C2*bessely(n/d4,a4*x**d4/d4))).subs(x, x-b4))] 

2787 

2788 

2789class SecondLinearAiry(SingleODESolver): 

2790 r""" 

2791 Gives solution of the Airy differential equation 

2792 

2793 .. math :: \frac{d^2y}{dx^2} + (a + b x) y(x) = 0 

2794 

2795 in terms of Airy special functions airyai and airybi. 

2796 

2797 Examples 

2798 ======== 

2799 

2800 >>> from sympy import dsolve, Function 

2801 >>> from sympy.abc import x 

2802 >>> f = Function("f") 

2803 >>> eq = f(x).diff(x, 2) - x*f(x) 

2804 >>> dsolve(eq) 

2805 Eq(f(x), C1*airyai(x) + C2*airybi(x)) 

2806 """ 

2807 hint = "2nd_linear_airy" 

2808 has_integral = False 

2809 

2810 def _matches(self): 

2811 eq = self.ode_problem.eq_high_order_free 

2812 f = self.ode_problem.func 

2813 order = self.ode_problem.order 

2814 x = self.ode_problem.sym 

2815 df = f.diff(x) 

2816 a4 = Wild('a4', exclude=[x,f,df]) 

2817 b4 = Wild('b4', exclude=[x,f,df]) 

2818 match = self.ode_problem.get_linear_coefficients(eq, f, order) 

2819 does_match = False 

2820 if order == 2 and match and match[2] != 0: 

2821 if match[1].is_zero: 

2822 self.rn = cancel(match[0]/match[2]).match(a4+b4*x) 

2823 if self.rn and self.rn[b4] != 0: 

2824 self.rn = {'b':self.rn[a4],'m':self.rn[b4]} 

2825 does_match = True 

2826 return does_match 

2827 

2828 def _get_general_solution(self, *, simplify_flag: bool = True): 

2829 f = self.ode_problem.func.func 

2830 x = self.ode_problem.sym 

2831 (C1, C2) = self.ode_problem.get_numbered_constants(num=2) 

2832 b = self.rn['b'] 

2833 m = self.rn['m'] 

2834 if m.is_positive: 

2835 arg = - b/cbrt(m)**2 - cbrt(m)*x 

2836 elif m.is_negative: 

2837 arg = - b/cbrt(-m)**2 + cbrt(-m)*x 

2838 else: 

2839 arg = - b/cbrt(-m)**2 + cbrt(-m)*x 

2840 

2841 return [Eq(f(x), C1*airyai(arg) + C2*airybi(arg))] 

2842 

2843 

2844class LieGroup(SingleODESolver): 

2845 r""" 

2846 This hint implements the Lie group method of solving first order differential 

2847 equations. The aim is to convert the given differential equation from the 

2848 given coordinate system into another coordinate system where it becomes 

2849 invariant under the one-parameter Lie group of translations. The converted 

2850 ODE can be easily solved by quadrature. It makes use of the 

2851 :py:meth:`sympy.solvers.ode.infinitesimals` function which returns the 

2852 infinitesimals of the transformation. 

2853 

2854 The coordinates `r` and `s` can be found by solving the following Partial 

2855 Differential Equations. 

2856 

2857 .. math :: \xi\frac{\partial r}{\partial x} + \eta\frac{\partial r}{\partial y} 

2858 = 0 

2859 

2860 .. math :: \xi\frac{\partial s}{\partial x} + \eta\frac{\partial s}{\partial y} 

2861 = 1 

2862 

2863 The differential equation becomes separable in the new coordinate system 

2864 

2865 .. math :: \frac{ds}{dr} = \frac{\frac{\partial s}{\partial x} + 

2866 h(x, y)\frac{\partial s}{\partial y}}{ 

2867 \frac{\partial r}{\partial x} + h(x, y)\frac{\partial r}{\partial y}} 

2868 

2869 After finding the solution by integration, it is then converted back to the original 

2870 coordinate system by substituting `r` and `s` in terms of `x` and `y` again. 

2871 

2872 Examples 

2873 ======== 

2874 

2875 >>> from sympy import Function, dsolve, exp, pprint 

2876 >>> from sympy.abc import x 

2877 >>> f = Function('f') 

2878 >>> pprint(dsolve(f(x).diff(x) + 2*x*f(x) - x*exp(-x**2), f(x), 

2879 ... hint='lie_group')) 

2880 / 2\ 2 

2881 | x | -x 

2882 f(x) = |C1 + --|*e 

2883 \ 2 / 

2884 

2885 

2886 References 

2887 ========== 

2888 

2889 - Solving differential equations by Symmetry Groups, 

2890 John Starrett, pp. 1 - pp. 14 

2891 

2892 """ 

2893 hint = "lie_group" 

2894 has_integral = False 

2895 

2896 def _has_additional_params(self): 

2897 return 'xi' in self.ode_problem.params and 'eta' in self.ode_problem.params 

2898 

2899 def _matches(self): 

2900 eq = self.ode_problem.eq 

2901 f = self.ode_problem.func.func 

2902 order = self.ode_problem.order 

2903 x = self.ode_problem.sym 

2904 df = f(x).diff(x) 

2905 y = Dummy('y') 

2906 d = Wild('d', exclude=[df, f(x).diff(x, 2)]) 

2907 e = Wild('e', exclude=[df]) 

2908 does_match = False 

2909 if self._has_additional_params() and order == 1: 

2910 xi = self.ode_problem.params['xi'] 

2911 eta = self.ode_problem.params['eta'] 

2912 self.r3 = {'xi': xi, 'eta': eta} 

2913 r = collect(eq, df, exact=True).match(d + e * df) 

2914 if r: 

2915 r['d'] = d 

2916 r['e'] = e 

2917 r['y'] = y 

2918 r[d] = r[d].subs(f(x), y) 

2919 r[e] = r[e].subs(f(x), y) 

2920 self.r3.update(r) 

2921 does_match = True 

2922 return does_match 

2923 

2924 def _get_general_solution(self, *, simplify_flag: bool = True): 

2925 eq = self.ode_problem.eq 

2926 x = self.ode_problem.sym 

2927 func = self.ode_problem.func 

2928 order = self.ode_problem.order 

2929 df = func.diff(x) 

2930 

2931 try: 

2932 eqsol = solve(eq, df) 

2933 except NotImplementedError: 

2934 eqsol = [] 

2935 

2936 desols = [] 

2937 for s in eqsol: 

2938 sol = _ode_lie_group(s, func, order, match=self.r3) 

2939 if sol: 

2940 desols.extend(sol) 

2941 

2942 if desols == []: 

2943 raise NotImplementedError("The given ODE " + str(eq) + " cannot be solved by" 

2944 + " the lie group method") 

2945 return desols 

2946 

2947 

2948solver_map = { 

2949 'factorable': Factorable, 

2950 'nth_linear_constant_coeff_homogeneous': NthLinearConstantCoeffHomogeneous, 

2951 'nth_linear_euler_eq_homogeneous': NthLinearEulerEqHomogeneous, 

2952 'nth_linear_constant_coeff_undetermined_coefficients': NthLinearConstantCoeffUndeterminedCoefficients, 

2953 'nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients': NthLinearEulerEqNonhomogeneousUndeterminedCoefficients, 

2954 'separable': Separable, 

2955 '1st_exact': FirstExact, 

2956 '1st_linear': FirstLinear, 

2957 'Bernoulli': Bernoulli, 

2958 'Riccati_special_minus2': RiccatiSpecial, 

2959 '1st_rational_riccati': RationalRiccati, 

2960 '1st_homogeneous_coeff_best': HomogeneousCoeffBest, 

2961 '1st_homogeneous_coeff_subs_indep_div_dep': HomogeneousCoeffSubsIndepDivDep, 

2962 '1st_homogeneous_coeff_subs_dep_div_indep': HomogeneousCoeffSubsDepDivIndep, 

2963 'almost_linear': AlmostLinear, 

2964 'linear_coefficients': LinearCoefficients, 

2965 'separable_reduced': SeparableReduced, 

2966 'nth_linear_constant_coeff_variation_of_parameters': NthLinearConstantCoeffVariationOfParameters, 

2967 'nth_linear_euler_eq_nonhomogeneous_variation_of_parameters': NthLinearEulerEqNonhomogeneousVariationOfParameters, 

2968 'Liouville': Liouville, 

2969 '2nd_linear_airy': SecondLinearAiry, 

2970 '2nd_linear_bessel': SecondLinearBessel, 

2971 '2nd_hypergeometric': SecondHypergeometric, 

2972 'nth_order_reducible': NthOrderReducible, 

2973 '2nd_nonlinear_autonomous_conserved': SecondNonlinearAutonomousConserved, 

2974 'nth_algebraic': NthAlgebraic, 

2975 'lie_group': LieGroup, 

2976 } 

2977 

2978# Avoid circular import: 

2979from .ode import dsolve, ode_sol_simplicity, odesimp, homogeneous_order