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

1548 statements  

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

1r""" 

2This module contains :py:meth:`~sympy.solvers.ode.dsolve` and different helper 

3functions that it uses. 

4 

5:py:meth:`~sympy.solvers.ode.dsolve` solves ordinary differential equations. 

6See the docstring on the various functions for their uses. Note that partial 

7differential equations support is in ``pde.py``. Note that hint functions 

8have docstrings describing their various methods, but they are intended for 

9internal use. Use ``dsolve(ode, func, hint=hint)`` to solve an ODE using a 

10specific hint. See also the docstring on 

11:py:meth:`~sympy.solvers.ode.dsolve`. 

12 

13**Functions in this module** 

14 

15 These are the user functions in this module: 

16 

17 - :py:meth:`~sympy.solvers.ode.dsolve` - Solves ODEs. 

18 - :py:meth:`~sympy.solvers.ode.classify_ode` - Classifies ODEs into 

19 possible hints for :py:meth:`~sympy.solvers.ode.dsolve`. 

20 - :py:meth:`~sympy.solvers.ode.checkodesol` - Checks if an equation is the 

21 solution to an ODE. 

22 - :py:meth:`~sympy.solvers.ode.homogeneous_order` - Returns the 

23 homogeneous order of an expression. 

24 - :py:meth:`~sympy.solvers.ode.infinitesimals` - Returns the infinitesimals 

25 of the Lie group of point transformations of an ODE, such that it is 

26 invariant. 

27 - :py:meth:`~sympy.solvers.ode.checkinfsol` - Checks if the given infinitesimals 

28 are the actual infinitesimals of a first order ODE. 

29 

30 These are the non-solver helper functions that are for internal use. The 

31 user should use the various options to 

32 :py:meth:`~sympy.solvers.ode.dsolve` to obtain the functionality provided 

33 by these functions: 

34 

35 - :py:meth:`~sympy.solvers.ode.ode.odesimp` - Does all forms of ODE 

36 simplification. 

37 - :py:meth:`~sympy.solvers.ode.ode.ode_sol_simplicity` - A key function for 

38 comparing solutions by simplicity. 

39 - :py:meth:`~sympy.solvers.ode.constantsimp` - Simplifies arbitrary 

40 constants. 

41 - :py:meth:`~sympy.solvers.ode.ode.constant_renumber` - Renumber arbitrary 

42 constants. 

43 - :py:meth:`~sympy.solvers.ode.ode._handle_Integral` - Evaluate unevaluated 

44 Integrals. 

45 

46 See also the docstrings of these functions. 

47 

48**Currently implemented solver methods** 

49 

50The following methods are implemented for solving ordinary differential 

51equations. See the docstrings of the various hint functions for more 

52information on each (run ``help(ode)``): 

53 

54 - 1st order separable differential equations. 

55 - 1st order differential equations whose coefficients or `dx` and `dy` are 

56 functions homogeneous of the same order. 

57 - 1st order exact differential equations. 

58 - 1st order linear differential equations. 

59 - 1st order Bernoulli differential equations. 

60 - Power series solutions for first order differential equations. 

61 - Lie Group method of solving first order differential equations. 

62 - 2nd order Liouville differential equations. 

63 - Power series solutions for second order differential equations 

64 at ordinary and regular singular points. 

65 - `n`\th order differential equation that can be solved with algebraic 

66 rearrangement and integration. 

67 - `n`\th order linear homogeneous differential equation with constant 

68 coefficients. 

69 - `n`\th order linear inhomogeneous differential equation with constant 

70 coefficients using the method of undetermined coefficients. 

71 - `n`\th order linear inhomogeneous differential equation with constant 

72 coefficients using the method of variation of parameters. 

73 

74**Philosophy behind this module** 

75 

76This module is designed to make it easy to add new ODE solving methods without 

77having to mess with the solving code for other methods. The idea is that 

78there is a :py:meth:`~sympy.solvers.ode.classify_ode` function, which takes in 

79an ODE and tells you what hints, if any, will solve the ODE. It does this 

80without attempting to solve the ODE, so it is fast. Each solving method is a 

81hint, and it has its own function, named ``ode_<hint>``. That function takes 

82in the ODE and any match expression gathered by 

83:py:meth:`~sympy.solvers.ode.classify_ode` and returns a solved result. If 

84this result has any integrals in it, the hint function will return an 

85unevaluated :py:class:`~sympy.integrals.integrals.Integral` class. 

86:py:meth:`~sympy.solvers.ode.dsolve`, which is the user wrapper function 

87around all of this, will then call :py:meth:`~sympy.solvers.ode.ode.odesimp` on 

88the result, which, among other things, will attempt to solve the equation for 

89the dependent variable (the function we are solving for), simplify the 

90arbitrary constants in the expression, and evaluate any integrals, if the hint 

91allows it. 

92 

93**How to add new solution methods** 

94 

95If you have an ODE that you want :py:meth:`~sympy.solvers.ode.dsolve` to be 

96able to solve, try to avoid adding special case code here. Instead, try 

97finding a general method that will solve your ODE, as well as others. This 

98way, the :py:mod:`~sympy.solvers.ode` module will become more robust, and 

99unhindered by special case hacks. WolphramAlpha and Maple's 

100DETools[odeadvisor] function are two resources you can use to classify a 

101specific ODE. It is also better for a method to work with an `n`\th order ODE 

102instead of only with specific orders, if possible. 

103 

104To add a new method, there are a few things that you need to do. First, you 

105need a hint name for your method. Try to name your hint so that it is 

106unambiguous with all other methods, including ones that may not be implemented 

107yet. If your method uses integrals, also include a ``hint_Integral`` hint. 

108If there is more than one way to solve ODEs with your method, include a hint 

109for each one, as well as a ``<hint>_best`` hint. Your ``ode_<hint>_best()`` 

110function should choose the best using min with ``ode_sol_simplicity`` as the 

111key argument. See 

112:obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest`, for example. 

113The function that uses your method will be called ``ode_<hint>()``, so the 

114hint must only use characters that are allowed in a Python function name 

115(alphanumeric characters and the underscore '``_``' character). Include a 

116function for every hint, except for ``_Integral`` hints 

117(:py:meth:`~sympy.solvers.ode.dsolve` takes care of those automatically). 

118Hint names should be all lowercase, unless a word is commonly capitalized 

119(such as Integral or Bernoulli). If you have a hint that you do not want to 

120run with ``all_Integral`` that does not have an ``_Integral`` counterpart (such 

121as a best hint that would defeat the purpose of ``all_Integral``), you will 

122need to remove it manually in the :py:meth:`~sympy.solvers.ode.dsolve` code. 

123See also the :py:meth:`~sympy.solvers.ode.classify_ode` docstring for 

124guidelines on writing a hint name. 

125 

126Determine *in general* how the solutions returned by your method compare with 

127other methods that can potentially solve the same ODEs. Then, put your hints 

128in the :py:data:`~sympy.solvers.ode.allhints` tuple in the order that they 

129should be called. The ordering of this tuple determines which hints are 

130default. Note that exceptions are ok, because it is easy for the user to 

131choose individual hints with :py:meth:`~sympy.solvers.ode.dsolve`. In 

132general, ``_Integral`` variants should go at the end of the list, and 

133``_best`` variants should go before the various hints they apply to. For 

134example, the ``undetermined_coefficients`` hint comes before the 

135``variation_of_parameters`` hint because, even though variation of parameters 

136is more general than undetermined coefficients, undetermined coefficients 

137generally returns cleaner results for the ODEs that it can solve than 

138variation of parameters does, and it does not require integration, so it is 

139much faster. 

140 

141Next, you need to have a match expression or a function that matches the type 

142of the ODE, which you should put in :py:meth:`~sympy.solvers.ode.classify_ode` 

143(if the match function is more than just a few lines. It should match the 

144ODE without solving for it as much as possible, so that 

145:py:meth:`~sympy.solvers.ode.classify_ode` remains fast and is not hindered by 

146bugs in solving code. Be sure to consider corner cases. For example, if your 

147solution method involves dividing by something, make sure you exclude the case 

148where that division will be 0. 

149 

150In most cases, the matching of the ODE will also give you the various parts 

151that you need to solve it. You should put that in a dictionary (``.match()`` 

152will do this for you), and add that as ``matching_hints['hint'] = matchdict`` 

153in the relevant part of :py:meth:`~sympy.solvers.ode.classify_ode`. 

154:py:meth:`~sympy.solvers.ode.classify_ode` will then send this to 

155:py:meth:`~sympy.solvers.ode.dsolve`, which will send it to your function as 

156the ``match`` argument. Your function should be named ``ode_<hint>(eq, func, 

157order, match)`. If you need to send more information, put it in the ``match`` 

158dictionary. For example, if you had to substitute in a dummy variable in 

159:py:meth:`~sympy.solvers.ode.classify_ode` to match the ODE, you will need to 

160pass it to your function using the `match` dict to access it. You can access 

161the independent variable using ``func.args[0]``, and the dependent variable 

162(the function you are trying to solve for) as ``func.func``. If, while trying 

163to solve the ODE, you find that you cannot, raise ``NotImplementedError``. 

164:py:meth:`~sympy.solvers.ode.dsolve` will catch this error with the ``all`` 

165meta-hint, rather than causing the whole routine to fail. 

166 

167Add a docstring to your function that describes the method employed. Like 

168with anything else in SymPy, you will need to add a doctest to the docstring, 

169in addition to real tests in ``test_ode.py``. Try to maintain consistency 

170with the other hint functions' docstrings. Add your method to the list at the 

171top of this docstring. Also, add your method to ``ode.rst`` in the 

172``docs/src`` directory, so that the Sphinx docs will pull its docstring into 

173the main SymPy documentation. Be sure to make the Sphinx documentation by 

174running ``make html`` from within the doc directory to verify that the 

175docstring formats correctly. 

176 

177If your solution method involves integrating, use :py:obj:`~.Integral` instead of 

178:py:meth:`~sympy.core.expr.Expr.integrate`. This allows the user to bypass 

179hard/slow integration by using the ``_Integral`` variant of your hint. In 

180most cases, calling :py:meth:`sympy.core.basic.Basic.doit` will integrate your 

181solution. If this is not the case, you will need to write special code in 

182:py:meth:`~sympy.solvers.ode.ode._handle_Integral`. Arbitrary constants should be 

183symbols named ``C1``, ``C2``, and so on. All solution methods should return 

184an equality instance. If you need an arbitrary number of arbitrary constants, 

185you can use ``constants = numbered_symbols(prefix='C', cls=Symbol, start=1)``. 

186If it is possible to solve for the dependent function in a general way, do so. 

187Otherwise, do as best as you can, but do not call solve in your 

188``ode_<hint>()`` function. :py:meth:`~sympy.solvers.ode.ode.odesimp` will attempt 

189to solve the solution for you, so you do not need to do that. Lastly, if your 

190ODE has a common simplification that can be applied to your solutions, you can 

191add a special case in :py:meth:`~sympy.solvers.ode.ode.odesimp` for it. For 

192example, solutions returned from the ``1st_homogeneous_coeff`` hints often 

193have many :obj:`~sympy.functions.elementary.exponential.log` terms, so 

194:py:meth:`~sympy.solvers.ode.ode.odesimp` calls 

195:py:meth:`~sympy.simplify.simplify.logcombine` on them (it also helps to write 

196the arbitrary constant as ``log(C1)`` instead of ``C1`` in this case). Also 

197consider common ways that you can rearrange your solution to have 

198:py:meth:`~sympy.solvers.ode.constantsimp` take better advantage of it. It is 

199better to put simplification in :py:meth:`~sympy.solvers.ode.ode.odesimp` than in 

200your method, because it can then be turned off with the simplify flag in 

201:py:meth:`~sympy.solvers.ode.dsolve`. If you have any extraneous 

202simplification in your function, be sure to only run it using ``if 

203match.get('simplify', True):``, especially if it can be slow or if it can 

204reduce the domain of the solution. 

205 

206Finally, as with every contribution to SymPy, your method will need to be 

207tested. Add a test for each method in ``test_ode.py``. Follow the 

208conventions there, i.e., test the solver using ``dsolve(eq, f(x), 

209hint=your_hint)``, and also test the solution using 

210:py:meth:`~sympy.solvers.ode.checkodesol` (you can put these in a separate 

211tests and skip/XFAIL if it runs too slow/does not work). Be sure to call your 

212hint specifically in :py:meth:`~sympy.solvers.ode.dsolve`, that way the test 

213will not be broken simply by the introduction of another matching hint. If your 

214method works for higher order (>1) ODEs, you will need to run ``sol = 

215constant_renumber(sol, 'C', 1, order)`` for each solution, where ``order`` is 

216the order of the ODE. This is because ``constant_renumber`` renumbers the 

217arbitrary constants by printing order, which is platform dependent. Try to 

218test every corner case of your solver, including a range of orders if it is a 

219`n`\th order solver, but if your solver is slow, such as if it involves hard 

220integration, try to keep the test run time down. 

221 

222Feel free to refactor existing hints to avoid duplicating code or creating 

223inconsistencies. If you can show that your method exactly duplicates an 

224existing method, including in the simplicity and speed of obtaining the 

225solutions, then you can remove the old, less general method. The existing 

226code is tested extensively in ``test_ode.py``, so if anything is broken, one 

227of those tests will surely fail. 

228 

229""" 

230 

231from sympy.core import Add, S, Mul, Pow, oo 

232from sympy.core.containers import Tuple 

233from sympy.core.expr import AtomicExpr, Expr 

234from sympy.core.function import (Function, Derivative, AppliedUndef, diff, 

235 expand, expand_mul, Subs) 

236from sympy.core.multidimensional import vectorize 

237from sympy.core.numbers import nan, zoo, Number 

238from sympy.core.relational import Equality, Eq 

239from sympy.core.sorting import default_sort_key, ordered 

240from sympy.core.symbol import Symbol, Wild, Dummy, symbols 

241from sympy.core.sympify import sympify 

242from sympy.core.traversal import preorder_traversal 

243 

244from sympy.logic.boolalg import (BooleanAtom, BooleanTrue, 

245 BooleanFalse) 

246from sympy.functions import exp, log, sqrt 

247from sympy.functions.combinatorial.factorials import factorial 

248from sympy.integrals.integrals import Integral 

249from sympy.polys import (Poly, terms_gcd, PolynomialError, lcm) 

250from sympy.polys.polytools import cancel 

251from sympy.series import Order 

252from sympy.series.series import series 

253from sympy.simplify import (collect, logcombine, powsimp, # type: ignore 

254 separatevars, simplify, cse) 

255from sympy.simplify.radsimp import collect_const 

256from sympy.solvers import checksol, solve 

257 

258from sympy.utilities import numbered_symbols 

259from sympy.utilities.iterables import uniq, sift, iterable 

260from sympy.solvers.deutils import _preprocess, ode_order, _desolve 

261 

262 

263#: This is a list of hints in the order that they should be preferred by 

264#: :py:meth:`~sympy.solvers.ode.classify_ode`. In general, hints earlier in the 

265#: list should produce simpler solutions than those later in the list (for 

266#: ODEs that fit both). For now, the order of this list is based on empirical 

267#: observations by the developers of SymPy. 

268#: 

269#: The hint used by :py:meth:`~sympy.solvers.ode.dsolve` for a specific ODE 

270#: can be overridden (see the docstring). 

271#: 

272#: In general, ``_Integral`` hints are grouped at the end of the list, unless 

273#: there is a method that returns an unevaluable integral most of the time 

274#: (which go near the end of the list anyway). ``default``, ``all``, 

275#: ``best``, and ``all_Integral`` meta-hints should not be included in this 

276#: list, but ``_best`` and ``_Integral`` hints should be included. 

277allhints = ( 

278 "factorable", 

279 "nth_algebraic", 

280 "separable", 

281 "1st_exact", 

282 "1st_linear", 

283 "Bernoulli", 

284 "1st_rational_riccati", 

285 "Riccati_special_minus2", 

286 "1st_homogeneous_coeff_best", 

287 "1st_homogeneous_coeff_subs_indep_div_dep", 

288 "1st_homogeneous_coeff_subs_dep_div_indep", 

289 "almost_linear", 

290 "linear_coefficients", 

291 "separable_reduced", 

292 "1st_power_series", 

293 "lie_group", 

294 "nth_linear_constant_coeff_homogeneous", 

295 "nth_linear_euler_eq_homogeneous", 

296 "nth_linear_constant_coeff_undetermined_coefficients", 

297 "nth_linear_euler_eq_nonhomogeneous_undetermined_coefficients", 

298 "nth_linear_constant_coeff_variation_of_parameters", 

299 "nth_linear_euler_eq_nonhomogeneous_variation_of_parameters", 

300 "Liouville", 

301 "2nd_linear_airy", 

302 "2nd_linear_bessel", 

303 "2nd_hypergeometric", 

304 "2nd_hypergeometric_Integral", 

305 "nth_order_reducible", 

306 "2nd_power_series_ordinary", 

307 "2nd_power_series_regular", 

308 "nth_algebraic_Integral", 

309 "separable_Integral", 

310 "1st_exact_Integral", 

311 "1st_linear_Integral", 

312 "Bernoulli_Integral", 

313 "1st_homogeneous_coeff_subs_indep_div_dep_Integral", 

314 "1st_homogeneous_coeff_subs_dep_div_indep_Integral", 

315 "almost_linear_Integral", 

316 "linear_coefficients_Integral", 

317 "separable_reduced_Integral", 

318 "nth_linear_constant_coeff_variation_of_parameters_Integral", 

319 "nth_linear_euler_eq_nonhomogeneous_variation_of_parameters_Integral", 

320 "Liouville_Integral", 

321 "2nd_nonlinear_autonomous_conserved", 

322 "2nd_nonlinear_autonomous_conserved_Integral", 

323 ) 

324 

325 

326 

327def get_numbered_constants(eq, num=1, start=1, prefix='C'): 

328 """ 

329 Returns a list of constants that do not occur 

330 in eq already. 

331 """ 

332 

333 ncs = iter_numbered_constants(eq, start, prefix) 

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

335 return (Cs[0] if num == 1 else tuple(Cs)) 

336 

337 

338def iter_numbered_constants(eq, start=1, prefix='C'): 

339 """ 

340 Returns an iterator of constants that do not occur 

341 in eq already. 

342 """ 

343 

344 if isinstance(eq, (Expr, Eq)): 

345 eq = [eq] 

346 elif not iterable(eq): 

347 raise ValueError("Expected Expr or iterable but got %s" % eq) 

348 

349 atom_set = set().union(*[i.free_symbols for i in eq]) 

350 func_set = set().union(*[i.atoms(Function) for i in eq]) 

351 if func_set: 

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

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

354 

355 

356def dsolve(eq, func=None, hint="default", simplify=True, 

357 ics= None, xi=None, eta=None, x0=0, n=6, **kwargs): 

358 r""" 

359 Solves any (supported) kind of ordinary differential equation and 

360 system of ordinary differential equations. 

361 

362 For single ordinary differential equation 

363 ========================================= 

364 

365 It is classified under this when number of equation in ``eq`` is one. 

366 **Usage** 

367 

368 ``dsolve(eq, f(x), hint)`` -> Solve ordinary differential equation 

369 ``eq`` for function ``f(x)``, using method ``hint``. 

370 

371 **Details** 

372 

373 ``eq`` can be any supported ordinary differential equation (see the 

374 :py:mod:`~sympy.solvers.ode` docstring for supported methods). 

375 This can either be an :py:class:`~sympy.core.relational.Equality`, 

376 or an expression, which is assumed to be equal to ``0``. 

377 

378 ``f(x)`` is a function of one variable whose derivatives in that 

379 variable make up the ordinary differential equation ``eq``. In 

380 many cases it is not necessary to provide this; it will be 

381 autodetected (and an error raised if it could not be detected). 

382 

383 ``hint`` is the solving method that you want dsolve to use. Use 

384 ``classify_ode(eq, f(x))`` to get all of the possible hints for an 

385 ODE. The default hint, ``default``, will use whatever hint is 

386 returned first by :py:meth:`~sympy.solvers.ode.classify_ode`. See 

387 Hints below for more options that you can use for hint. 

388 

389 ``simplify`` enables simplification by 

390 :py:meth:`~sympy.solvers.ode.ode.odesimp`. See its docstring for more 

391 information. Turn this off, for example, to disable solving of 

392 solutions for ``func`` or simplification of arbitrary constants. 

393 It will still integrate with this hint. Note that the solution may 

394 contain more arbitrary constants than the order of the ODE with 

395 this option enabled. 

396 

397 ``xi`` and ``eta`` are the infinitesimal functions of an ordinary 

398 differential equation. They are the infinitesimals of the Lie group 

399 of point transformations for which the differential equation is 

400 invariant. The user can specify values for the infinitesimals. If 

401 nothing is specified, ``xi`` and ``eta`` are calculated using 

402 :py:meth:`~sympy.solvers.ode.infinitesimals` with the help of various 

403 heuristics. 

404 

405 ``ics`` is the set of initial/boundary conditions for the differential equation. 

406 It should be given in the form of ``{f(x0): x1, f(x).diff(x).subs(x, x2): 

407 x3}`` and so on. For power series solutions, if no initial 

408 conditions are specified ``f(0)`` is assumed to be ``C0`` and the power 

409 series solution is calculated about 0. 

410 

411 ``x0`` is the point about which the power series solution of a differential 

412 equation is to be evaluated. 

413 

414 ``n`` gives the exponent of the dependent variable up to which the power series 

415 solution of a differential equation is to be evaluated. 

416 

417 **Hints** 

418 

419 Aside from the various solving methods, there are also some meta-hints 

420 that you can pass to :py:meth:`~sympy.solvers.ode.dsolve`: 

421 

422 ``default``: 

423 This uses whatever hint is returned first by 

424 :py:meth:`~sympy.solvers.ode.classify_ode`. This is the 

425 default argument to :py:meth:`~sympy.solvers.ode.dsolve`. 

426 

427 ``all``: 

428 To make :py:meth:`~sympy.solvers.ode.dsolve` apply all 

429 relevant classification hints, use ``dsolve(ODE, func, 

430 hint="all")``. This will return a dictionary of 

431 ``hint:solution`` terms. If a hint causes dsolve to raise the 

432 ``NotImplementedError``, value of that hint's key will be the 

433 exception object raised. The dictionary will also include 

434 some special keys: 

435 

436 - ``order``: The order of the ODE. See also 

437 :py:meth:`~sympy.solvers.deutils.ode_order` in 

438 ``deutils.py``. 

439 - ``best``: The simplest hint; what would be returned by 

440 ``best`` below. 

441 - ``best_hint``: The hint that would produce the solution 

442 given by ``best``. If more than one hint produces the best 

443 solution, the first one in the tuple returned by 

444 :py:meth:`~sympy.solvers.ode.classify_ode` is chosen. 

445 - ``default``: The solution that would be returned by default. 

446 This is the one produced by the hint that appears first in 

447 the tuple returned by 

448 :py:meth:`~sympy.solvers.ode.classify_ode`. 

449 

450 ``all_Integral``: 

451 This is the same as ``all``, except if a hint also has a 

452 corresponding ``_Integral`` hint, it only returns the 

453 ``_Integral`` hint. This is useful if ``all`` causes 

454 :py:meth:`~sympy.solvers.ode.dsolve` to hang because of a 

455 difficult or impossible integral. This meta-hint will also be 

456 much faster than ``all``, because 

457 :py:meth:`~sympy.core.expr.Expr.integrate` is an expensive 

458 routine. 

459 

460 ``best``: 

461 To have :py:meth:`~sympy.solvers.ode.dsolve` try all methods 

462 and return the simplest one. This takes into account whether 

463 the solution is solvable in the function, whether it contains 

464 any Integral classes (i.e. unevaluatable integrals), and 

465 which one is the shortest in size. 

466 

467 See also the :py:meth:`~sympy.solvers.ode.classify_ode` docstring for 

468 more info on hints, and the :py:mod:`~sympy.solvers.ode` docstring for 

469 a list of all supported hints. 

470 

471 **Tips** 

472 

473 - You can declare the derivative of an unknown function this way: 

474 

475 >>> from sympy import Function, Derivative 

476 >>> from sympy.abc import x # x is the independent variable 

477 >>> f = Function("f")(x) # f is a function of x 

478 >>> # f_ will be the derivative of f with respect to x 

479 >>> f_ = Derivative(f, x) 

480 

481 - See ``test_ode.py`` for many tests, which serves also as a set of 

482 examples for how to use :py:meth:`~sympy.solvers.ode.dsolve`. 

483 - :py:meth:`~sympy.solvers.ode.dsolve` always returns an 

484 :py:class:`~sympy.core.relational.Equality` class (except for the 

485 case when the hint is ``all`` or ``all_Integral``). If possible, it 

486 solves the solution explicitly for the function being solved for. 

487 Otherwise, it returns an implicit solution. 

488 - Arbitrary constants are symbols named ``C1``, ``C2``, and so on. 

489 - Because all solutions should be mathematically equivalent, some 

490 hints may return the exact same result for an ODE. Often, though, 

491 two different hints will return the same solution formatted 

492 differently. The two should be equivalent. Also note that sometimes 

493 the values of the arbitrary constants in two different solutions may 

494 not be the same, because one constant may have "absorbed" other 

495 constants into it. 

496 - Do ``help(ode.ode_<hintname>)`` to get help more information on a 

497 specific hint, where ``<hintname>`` is the name of a hint without 

498 ``_Integral``. 

499 

500 For system of ordinary differential equations 

501 ============================================= 

502 

503 **Usage** 

504 ``dsolve(eq, func)`` -> Solve a system of ordinary differential 

505 equations ``eq`` for ``func`` being list of functions including 

506 `x(t)`, `y(t)`, `z(t)` where number of functions in the list depends 

507 upon the number of equations provided in ``eq``. 

508 

509 **Details** 

510 

511 ``eq`` can be any supported system of ordinary differential equations 

512 This can either be an :py:class:`~sympy.core.relational.Equality`, 

513 or an expression, which is assumed to be equal to ``0``. 

514 

515 ``func`` holds ``x(t)`` and ``y(t)`` being functions of one variable which 

516 together with some of their derivatives make up the system of ordinary 

517 differential equation ``eq``. It is not necessary to provide this; it 

518 will be autodetected (and an error raised if it could not be detected). 

519 

520 **Hints** 

521 

522 The hints are formed by parameters returned by classify_sysode, combining 

523 them give hints name used later for forming method name. 

524 

525 Examples 

526 ======== 

527 

528 >>> from sympy import Function, dsolve, Eq, Derivative, sin, cos, symbols 

529 >>> from sympy.abc import x 

530 >>> f = Function('f') 

531 >>> dsolve(Derivative(f(x), x, x) + 9*f(x), f(x)) 

532 Eq(f(x), C1*sin(3*x) + C2*cos(3*x)) 

533 

534 >>> eq = sin(x)*cos(f(x)) + cos(x)*sin(f(x))*f(x).diff(x) 

535 >>> dsolve(eq, hint='1st_exact') 

536 [Eq(f(x), -acos(C1/cos(x)) + 2*pi), Eq(f(x), acos(C1/cos(x)))] 

537 >>> dsolve(eq, hint='almost_linear') 

538 [Eq(f(x), -acos(C1/cos(x)) + 2*pi), Eq(f(x), acos(C1/cos(x)))] 

539 >>> t = symbols('t') 

540 >>> x, y = symbols('x, y', cls=Function) 

541 >>> eq = (Eq(Derivative(x(t),t), 12*t*x(t) + 8*y(t)), Eq(Derivative(y(t),t), 21*x(t) + 7*t*y(t))) 

542 >>> dsolve(eq) 

543 [Eq(x(t), C1*x0(t) + C2*x0(t)*Integral(8*exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0(t)**2, t)), 

544 Eq(y(t), C1*y0(t) + C2*(y0(t)*Integral(8*exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0(t)**2, t) + 

545 exp(Integral(7*t, t))*exp(Integral(12*t, t))/x0(t)))] 

546 >>> eq = (Eq(Derivative(x(t),t),x(t)*y(t)*sin(t)), Eq(Derivative(y(t),t),y(t)**2*sin(t))) 

547 >>> dsolve(eq) 

548 {Eq(x(t), -exp(C1)/(C2*exp(C1) - cos(t))), Eq(y(t), -1/(C1 - cos(t)))} 

549 """ 

550 if iterable(eq): 

551 from sympy.solvers.ode.systems import dsolve_system 

552 

553 # This may have to be changed in future 

554 # when we have weakly and strongly 

555 # connected components. This have to 

556 # changed to show the systems that haven't 

557 # been solved. 

558 try: 

559 sol = dsolve_system(eq, funcs=func, ics=ics, doit=True) 

560 return sol[0] if len(sol) == 1 else sol 

561 except NotImplementedError: 

562 pass 

563 

564 match = classify_sysode(eq, func) 

565 

566 eq = match['eq'] 

567 order = match['order'] 

568 func = match['func'] 

569 t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] 

570 

571 # keep highest order term coefficient positive 

572 for i in range(len(eq)): 

573 for func_ in func: 

574 if isinstance(func_, list): 

575 pass 

576 else: 

577 if eq[i].coeff(diff(func[i],t,ode_order(eq[i], func[i]))).is_negative: 

578 eq[i] = -eq[i] 

579 match['eq'] = eq 

580 if len(set(order.values()))!=1: 

581 raise ValueError("It solves only those systems of equations whose orders are equal") 

582 match['order'] = list(order.values())[0] 

583 def recur_len(l): 

584 return sum(recur_len(item) if isinstance(item,list) else 1 for item in l) 

585 if recur_len(func) != len(eq): 

586 raise ValueError("dsolve() and classify_sysode() work with " 

587 "number of functions being equal to number of equations") 

588 if match['type_of_equation'] is None: 

589 raise NotImplementedError 

590 else: 

591 if match['is_linear'] == True: 

592 solvefunc = globals()['sysode_linear_%(no_of_equation)seq_order%(order)s' % match] 

593 else: 

594 solvefunc = globals()['sysode_nonlinear_%(no_of_equation)seq_order%(order)s' % match] 

595 sols = solvefunc(match) 

596 if ics: 

597 constants = Tuple(*sols).free_symbols - Tuple(*eq).free_symbols 

598 solved_constants = solve_ics(sols, func, constants, ics) 

599 return [sol.subs(solved_constants) for sol in sols] 

600 return sols 

601 else: 

602 given_hint = hint # hint given by the user 

603 

604 # See the docstring of _desolve for more details. 

605 hints = _desolve(eq, func=func, 

606 hint=hint, simplify=True, xi=xi, eta=eta, type='ode', ics=ics, 

607 x0=x0, n=n, **kwargs) 

608 eq = hints.pop('eq', eq) 

609 all_ = hints.pop('all', False) 

610 if all_: 

611 retdict = {} 

612 failed_hints = {} 

613 gethints = classify_ode(eq, dict=True, hint='all') 

614 orderedhints = gethints['ordered_hints'] 

615 for hint in hints: 

616 try: 

617 rv = _helper_simplify(eq, hint, hints[hint], simplify) 

618 except NotImplementedError as detail: 

619 failed_hints[hint] = detail 

620 else: 

621 retdict[hint] = rv 

622 func = hints[hint]['func'] 

623 

624 retdict['best'] = min(list(retdict.values()), key=lambda x: 

625 ode_sol_simplicity(x, func, trysolving=not simplify)) 

626 if given_hint == 'best': 

627 return retdict['best'] 

628 for i in orderedhints: 

629 if retdict['best'] == retdict.get(i, None): 

630 retdict['best_hint'] = i 

631 break 

632 retdict['default'] = gethints['default'] 

633 retdict['order'] = gethints['order'] 

634 retdict.update(failed_hints) 

635 return retdict 

636 

637 else: 

638 # The key 'hint' stores the hint needed to be solved for. 

639 hint = hints['hint'] 

640 return _helper_simplify(eq, hint, hints, simplify, ics=ics) 

641 

642def _helper_simplify(eq, hint, match, simplify=True, ics=None, **kwargs): 

643 r""" 

644 Helper function of dsolve that calls the respective 

645 :py:mod:`~sympy.solvers.ode` functions to solve for the ordinary 

646 differential equations. This minimizes the computation in calling 

647 :py:meth:`~sympy.solvers.deutils._desolve` multiple times. 

648 """ 

649 r = match 

650 func = r['func'] 

651 order = r['order'] 

652 match = r[hint] 

653 

654 if isinstance(match, SingleODESolver): 

655 solvefunc = match 

656 elif hint.endswith('_Integral'): 

657 solvefunc = globals()['ode_' + hint[:-len('_Integral')]] 

658 else: 

659 solvefunc = globals()['ode_' + hint] 

660 

661 free = eq.free_symbols 

662 cons = lambda s: s.free_symbols.difference(free) 

663 

664 if simplify: 

665 # odesimp() will attempt to integrate, if necessary, apply constantsimp(), 

666 # attempt to solve for func, and apply any other hint specific 

667 # simplifications 

668 if isinstance(solvefunc, SingleODESolver): 

669 sols = solvefunc.get_general_solution() 

670 else: 

671 sols = solvefunc(eq, func, order, match) 

672 if iterable(sols): 

673 rv = [odesimp(eq, s, func, hint) for s in sols] 

674 else: 

675 rv = odesimp(eq, sols, func, hint) 

676 else: 

677 # We still want to integrate (you can disable it separately with the hint) 

678 if isinstance(solvefunc, SingleODESolver): 

679 exprs = solvefunc.get_general_solution(simplify=False) 

680 else: 

681 match['simplify'] = False # Some hints can take advantage of this option 

682 exprs = solvefunc(eq, func, order, match) 

683 if isinstance(exprs, list): 

684 rv = [_handle_Integral(expr, func, hint) for expr in exprs] 

685 else: 

686 rv = _handle_Integral(exprs, func, hint) 

687 

688 if isinstance(rv, list): 

689 if simplify: 

690 rv = _remove_redundant_solutions(eq, rv, order, func.args[0]) 

691 if len(rv) == 1: 

692 rv = rv[0] 

693 if ics and 'power_series' not in hint: 

694 if isinstance(rv, (Expr, Eq)): 

695 solved_constants = solve_ics([rv], [r['func']], cons(rv), ics) 

696 rv = rv.subs(solved_constants) 

697 else: 

698 rv1 = [] 

699 for s in rv: 

700 try: 

701 solved_constants = solve_ics([s], [r['func']], cons(s), ics) 

702 except ValueError: 

703 continue 

704 rv1.append(s.subs(solved_constants)) 

705 if len(rv1) == 1: 

706 return rv1[0] 

707 rv = rv1 

708 return rv 

709 

710def solve_ics(sols, funcs, constants, ics): 

711 """ 

712 Solve for the constants given initial conditions 

713 

714 ``sols`` is a list of solutions. 

715 

716 ``funcs`` is a list of functions. 

717 

718 ``constants`` is a list of constants. 

719 

720 ``ics`` is the set of initial/boundary conditions for the differential 

721 equation. It should be given in the form of ``{f(x0): x1, 

722 f(x).diff(x).subs(x, x2): x3}`` and so on. 

723 

724 Returns a dictionary mapping constants to values. 

725 ``solution.subs(constants)`` will replace the constants in ``solution``. 

726 

727 Example 

728 ======= 

729 >>> # From dsolve(f(x).diff(x) - f(x), f(x)) 

730 >>> from sympy import symbols, Eq, exp, Function 

731 >>> from sympy.solvers.ode.ode import solve_ics 

732 >>> f = Function('f') 

733 >>> x, C1 = symbols('x C1') 

734 >>> sols = [Eq(f(x), C1*exp(x))] 

735 >>> funcs = [f(x)] 

736 >>> constants = [C1] 

737 >>> ics = {f(0): 2} 

738 >>> solved_constants = solve_ics(sols, funcs, constants, ics) 

739 >>> solved_constants 

740 {C1: 2} 

741 >>> sols[0].subs(solved_constants) 

742 Eq(f(x), 2*exp(x)) 

743 

744 """ 

745 # Assume ics are of the form f(x0): value or Subs(diff(f(x), x, n), (x, 

746 # x0)): value (currently checked by classify_ode). To solve, replace x 

747 # with x0, f(x0) with value, then solve for constants. For f^(n)(x0), 

748 # differentiate the solution n times, so that f^(n)(x) appears. 

749 x = funcs[0].args[0] 

750 diff_sols = [] 

751 subs_sols = [] 

752 diff_variables = set() 

753 for funcarg, value in ics.items(): 

754 if isinstance(funcarg, AppliedUndef): 

755 x0 = funcarg.args[0] 

756 matching_func = [f for f in funcs if f.func == funcarg.func][0] 

757 S = sols 

758 elif isinstance(funcarg, (Subs, Derivative)): 

759 if isinstance(funcarg, Subs): 

760 # Make sure it stays a subs. Otherwise subs below will produce 

761 # a different looking term. 

762 funcarg = funcarg.doit() 

763 if isinstance(funcarg, Subs): 

764 deriv = funcarg.expr 

765 x0 = funcarg.point[0] 

766 variables = funcarg.expr.variables 

767 matching_func = deriv 

768 elif isinstance(funcarg, Derivative): 

769 deriv = funcarg 

770 x0 = funcarg.variables[0] 

771 variables = (x,)*len(funcarg.variables) 

772 matching_func = deriv.subs(x0, x) 

773 for sol in sols: 

774 if sol.has(deriv.expr.func): 

775 diff_sols.append(Eq(sol.lhs.diff(*variables), sol.rhs.diff(*variables))) 

776 diff_variables.add(variables) 

777 S = diff_sols 

778 else: 

779 raise NotImplementedError("Unrecognized initial condition") 

780 

781 for sol in S: 

782 if sol.has(matching_func): 

783 sol2 = sol 

784 sol2 = sol2.subs(x, x0) 

785 sol2 = sol2.subs(funcarg, value) 

786 # This check is necessary because of issue #15724 

787 if not isinstance(sol2, BooleanAtom) or not subs_sols: 

788 subs_sols = [s for s in subs_sols if not isinstance(s, BooleanAtom)] 

789 subs_sols.append(sol2) 

790 

791 # TODO: Use solveset here 

792 try: 

793 solved_constants = solve(subs_sols, constants, dict=True) 

794 except NotImplementedError: 

795 solved_constants = [] 

796 

797 # XXX: We can't differentiate between the solution not existing because of 

798 # invalid initial conditions, and not existing because solve is not smart 

799 # enough. If we could use solveset, this might be improvable, but for now, 

800 # we use NotImplementedError in this case. 

801 if not solved_constants: 

802 raise ValueError("Couldn't solve for initial conditions") 

803 

804 if solved_constants == True: 

805 raise ValueError("Initial conditions did not produce any solutions for constants. Perhaps they are degenerate.") 

806 

807 if len(solved_constants) > 1: 

808 raise NotImplementedError("Initial conditions produced too many solutions for constants") 

809 

810 return solved_constants[0] 

811 

812def classify_ode(eq, func=None, dict=False, ics=None, *, prep=True, xi=None, eta=None, n=None, **kwargs): 

813 r""" 

814 Returns a tuple of possible :py:meth:`~sympy.solvers.ode.dsolve` 

815 classifications for an ODE. 

816 

817 The tuple is ordered so that first item is the classification that 

818 :py:meth:`~sympy.solvers.ode.dsolve` uses to solve the ODE by default. In 

819 general, classifications at the near the beginning of the list will 

820 produce better solutions faster than those near the end, thought there are 

821 always exceptions. To make :py:meth:`~sympy.solvers.ode.dsolve` use a 

822 different classification, use ``dsolve(ODE, func, 

823 hint=<classification>)``. See also the 

824 :py:meth:`~sympy.solvers.ode.dsolve` docstring for different meta-hints 

825 you can use. 

826 

827 If ``dict`` is true, :py:meth:`~sympy.solvers.ode.classify_ode` will 

828 return a dictionary of ``hint:match`` expression terms. This is intended 

829 for internal use by :py:meth:`~sympy.solvers.ode.dsolve`. Note that 

830 because dictionaries are ordered arbitrarily, this will most likely not be 

831 in the same order as the tuple. 

832 

833 You can get help on different hints by executing 

834 ``help(ode.ode_hintname)``, where ``hintname`` is the name of the hint 

835 without ``_Integral``. 

836 

837 See :py:data:`~sympy.solvers.ode.allhints` or the 

838 :py:mod:`~sympy.solvers.ode` docstring for a list of all supported hints 

839 that can be returned from :py:meth:`~sympy.solvers.ode.classify_ode`. 

840 

841 Notes 

842 ===== 

843 

844 These are remarks on hint names. 

845 

846 ``_Integral`` 

847 

848 If a classification has ``_Integral`` at the end, it will return the 

849 expression with an unevaluated :py:class:`~.Integral` 

850 class in it. Note that a hint may do this anyway if 

851 :py:meth:`~sympy.core.expr.Expr.integrate` cannot do the integral, 

852 though just using an ``_Integral`` will do so much faster. Indeed, an 

853 ``_Integral`` hint will always be faster than its corresponding hint 

854 without ``_Integral`` because 

855 :py:meth:`~sympy.core.expr.Expr.integrate` is an expensive routine. 

856 If :py:meth:`~sympy.solvers.ode.dsolve` hangs, it is probably because 

857 :py:meth:`~sympy.core.expr.Expr.integrate` is hanging on a tough or 

858 impossible integral. Try using an ``_Integral`` hint or 

859 ``all_Integral`` to get it return something. 

860 

861 Note that some hints do not have ``_Integral`` counterparts. This is 

862 because :py:func:`~sympy.integrals.integrals.integrate` is not used in 

863 solving the ODE for those method. For example, `n`\th order linear 

864 homogeneous ODEs with constant coefficients do not require integration 

865 to solve, so there is no 

866 ``nth_linear_homogeneous_constant_coeff_Integrate`` hint. You can 

867 easily evaluate any unevaluated 

868 :py:class:`~sympy.integrals.integrals.Integral`\s in an expression by 

869 doing ``expr.doit()``. 

870 

871 Ordinals 

872 

873 Some hints contain an ordinal such as ``1st_linear``. This is to help 

874 differentiate them from other hints, as well as from other methods 

875 that may not be implemented yet. If a hint has ``nth`` in it, such as 

876 the ``nth_linear`` hints, this means that the method used to applies 

877 to ODEs of any order. 

878 

879 ``indep`` and ``dep`` 

880 

881 Some hints contain the words ``indep`` or ``dep``. These reference 

882 the independent variable and the dependent function, respectively. For 

883 example, if an ODE is in terms of `f(x)`, then ``indep`` will refer to 

884 `x` and ``dep`` will refer to `f`. 

885 

886 ``subs`` 

887 

888 If a hints has the word ``subs`` in it, it means that the ODE is solved 

889 by substituting the expression given after the word ``subs`` for a 

890 single dummy variable. This is usually in terms of ``indep`` and 

891 ``dep`` as above. The substituted expression will be written only in 

892 characters allowed for names of Python objects, meaning operators will 

893 be spelled out. For example, ``indep``/``dep`` will be written as 

894 ``indep_div_dep``. 

895 

896 ``coeff`` 

897 

898 The word ``coeff`` in a hint refers to the coefficients of something 

899 in the ODE, usually of the derivative terms. See the docstring for 

900 the individual methods for more info (``help(ode)``). This is 

901 contrast to ``coefficients``, as in ``undetermined_coefficients``, 

902 which refers to the common name of a method. 

903 

904 ``_best`` 

905 

906 Methods that have more than one fundamental way to solve will have a 

907 hint for each sub-method and a ``_best`` meta-classification. This 

908 will evaluate all hints and return the best, using the same 

909 considerations as the normal ``best`` meta-hint. 

910 

911 

912 Examples 

913 ======== 

914 

915 >>> from sympy import Function, classify_ode, Eq 

916 >>> from sympy.abc import x 

917 >>> f = Function('f') 

918 >>> classify_ode(Eq(f(x).diff(x), 0), f(x)) 

919 ('nth_algebraic', 

920 'separable', 

921 '1st_exact', 

922 '1st_linear', 

923 'Bernoulli', 

924 '1st_homogeneous_coeff_best', 

925 '1st_homogeneous_coeff_subs_indep_div_dep', 

926 '1st_homogeneous_coeff_subs_dep_div_indep', 

927 '1st_power_series', 'lie_group', 'nth_linear_constant_coeff_homogeneous', 

928 'nth_linear_euler_eq_homogeneous', 

929 'nth_algebraic_Integral', 'separable_Integral', '1st_exact_Integral', 

930 '1st_linear_Integral', 'Bernoulli_Integral', 

931 '1st_homogeneous_coeff_subs_indep_div_dep_Integral', 

932 '1st_homogeneous_coeff_subs_dep_div_indep_Integral') 

933 >>> classify_ode(f(x).diff(x, 2) + 3*f(x).diff(x) + 2*f(x) - 4) 

934 ('factorable', 'nth_linear_constant_coeff_undetermined_coefficients', 

935 'nth_linear_constant_coeff_variation_of_parameters', 

936 'nth_linear_constant_coeff_variation_of_parameters_Integral') 

937 

938 """ 

939 ics = sympify(ics) 

940 

941 if func and len(func.args) != 1: 

942 raise ValueError("dsolve() and classify_ode() only " 

943 "work with functions of one variable, not %s" % func) 

944 

945 if isinstance(eq, Equality): 

946 eq = eq.lhs - eq.rhs 

947 

948 # Some methods want the unprocessed equation 

949 eq_orig = eq 

950 

951 if prep or func is None: 

952 eq, func_ = _preprocess(eq, func) 

953 if func is None: 

954 func = func_ 

955 x = func.args[0] 

956 f = func.func 

957 y = Dummy('y') 

958 terms = 5 if n is None else n 

959 

960 order = ode_order(eq, f(x)) 

961 # hint:matchdict or hint:(tuple of matchdicts) 

962 # Also will contain "default":<default hint> and "order":order items. 

963 matching_hints = {"order": order} 

964 

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

966 a = Wild('a', exclude=[f(x)]) 

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

968 e = Wild('e', exclude=[df]) 

969 n = Wild('n', exclude=[x, f(x), df]) 

970 c1 = Wild('c1', exclude=[x]) 

971 a3 = Wild('a3', exclude=[f(x), df, f(x).diff(x, 2)]) 

972 b3 = Wild('b3', exclude=[f(x), df, f(x).diff(x, 2)]) 

973 c3 = Wild('c3', exclude=[f(x), df, f(x).diff(x, 2)]) 

974 boundary = {} # Used to extract initial conditions 

975 C1 = Symbol("C1") 

976 

977 # Preprocessing to get the initial conditions out 

978 if ics is not None: 

979 for funcarg in ics: 

980 # Separating derivatives 

981 if isinstance(funcarg, (Subs, Derivative)): 

982 # f(x).diff(x).subs(x, 0) is a Subs, but f(x).diff(x).subs(x, 

983 # y) is a Derivative 

984 if isinstance(funcarg, Subs): 

985 deriv = funcarg.expr 

986 old = funcarg.variables[0] 

987 new = funcarg.point[0] 

988 elif isinstance(funcarg, Derivative): 

989 deriv = funcarg 

990 # No information on this. Just assume it was x 

991 old = x 

992 new = funcarg.variables[0] 

993 

994 if (isinstance(deriv, Derivative) and isinstance(deriv.args[0], 

995 AppliedUndef) and deriv.args[0].func == f and 

996 len(deriv.args[0].args) == 1 and old == x and not 

997 new.has(x) and all(i == deriv.variables[0] for i in 

998 deriv.variables) and x not in ics[funcarg].free_symbols): 

999 

1000 dorder = ode_order(deriv, x) 

1001 temp = 'f' + str(dorder) 

1002 boundary.update({temp: new, temp + 'val': ics[funcarg]}) 

1003 else: 

1004 raise ValueError("Invalid boundary conditions for Derivatives") 

1005 

1006 

1007 # Separating functions 

1008 elif isinstance(funcarg, AppliedUndef): 

1009 if (funcarg.func == f and len(funcarg.args) == 1 and 

1010 not funcarg.args[0].has(x) and x not in ics[funcarg].free_symbols): 

1011 boundary.update({'f0': funcarg.args[0], 'f0val': ics[funcarg]}) 

1012 else: 

1013 raise ValueError("Invalid boundary conditions for Function") 

1014 

1015 else: 

1016 raise ValueError("Enter boundary conditions of the form ics={f(point): value, f(x).diff(x, order).subs(x, point): value}") 

1017 

1018 ode = SingleODEProblem(eq_orig, func, x, prep=prep, xi=xi, eta=eta) 

1019 user_hint = kwargs.get('hint', 'default') 

1020 # Used when dsolve is called without an explicit hint. 

1021 # We exit early to return the first valid match 

1022 early_exit = (user_hint=='default') 

1023 if user_hint.endswith('_Integral'): 

1024 user_hint = user_hint[:-len('_Integral')] 

1025 user_map = solver_map 

1026 # An explicit hint has been given to dsolve 

1027 # Skip matching code for other hints 

1028 if user_hint not in ['default', 'all', 'all_Integral', 'best'] and user_hint in solver_map: 

1029 user_map = {user_hint: solver_map[user_hint]} 

1030 

1031 for hint in user_map: 

1032 solver = user_map[hint](ode) 

1033 if solver.matches(): 

1034 matching_hints[hint] = solver 

1035 if user_map[hint].has_integral: 

1036 matching_hints[hint + "_Integral"] = solver 

1037 if dict and early_exit: 

1038 matching_hints["default"] = hint 

1039 return matching_hints 

1040 

1041 eq = expand(eq) 

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

1043 reduced_eq = None 

1044 if eq.is_Add: 

1045 deriv_coef = eq.coeff(f(x).diff(x, order)) 

1046 if deriv_coef not in (1, 0): 

1047 r = deriv_coef.match(a*f(x)**c1) 

1048 if r and r[c1]: 

1049 den = f(x)**r[c1] 

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

1051 if not reduced_eq: 

1052 reduced_eq = eq 

1053 

1054 if order == 1: 

1055 

1056 # NON-REDUCED FORM OF EQUATION matches 

1057 r = collect(eq, df, exact=True).match(d + e * df) 

1058 if r: 

1059 r['d'] = d 

1060 r['e'] = e 

1061 r['y'] = y 

1062 r[d] = r[d].subs(f(x), y) 

1063 r[e] = r[e].subs(f(x), y) 

1064 

1065 # FIRST ORDER POWER SERIES WHICH NEEDS INITIAL CONDITIONS 

1066 # TODO: Hint first order series should match only if d/e is analytic. 

1067 # For now, only d/e and (d/e).diff(arg) is checked for existence at 

1068 # at a given point. 

1069 # This is currently done internally in ode_1st_power_series. 

1070 point = boundary.get('f0', 0) 

1071 value = boundary.get('f0val', C1) 

1072 check = cancel(r[d]/r[e]) 

1073 check1 = check.subs({x: point, y: value}) 

1074 if not check1.has(oo) and not check1.has(zoo) and \ 

1075 not check1.has(nan) and not check1.has(-oo): 

1076 check2 = (check1.diff(x)).subs({x: point, y: value}) 

1077 if not check2.has(oo) and not check2.has(zoo) and \ 

1078 not check2.has(nan) and not check2.has(-oo): 

1079 rseries = r.copy() 

1080 rseries.update({'terms': terms, 'f0': point, 'f0val': value}) 

1081 matching_hints["1st_power_series"] = rseries 

1082 

1083 elif order == 2: 

1084 # Homogeneous second order differential equation of the form 

1085 # a3*f(x).diff(x, 2) + b3*f(x).diff(x) + c3 

1086 # It has a definite power series solution at point x0 if, b3/a3 and c3/a3 

1087 # are analytic at x0. 

1088 deq = a3*(f(x).diff(x, 2)) + b3*df + c3*f(x) 

1089 r = collect(reduced_eq, 

1090 [f(x).diff(x, 2), f(x).diff(x), f(x)]).match(deq) 

1091 ordinary = False 

1092 if r: 

1093 if not all(r[key].is_polynomial() for key in r): 

1094 n, d = reduced_eq.as_numer_denom() 

1095 reduced_eq = expand(n) 

1096 r = collect(reduced_eq, 

1097 [f(x).diff(x, 2), f(x).diff(x), f(x)]).match(deq) 

1098 if r and r[a3] != 0: 

1099 p = cancel(r[b3]/r[a3]) # Used below 

1100 q = cancel(r[c3]/r[a3]) # Used below 

1101 point = kwargs.get('x0', 0) 

1102 check = p.subs(x, point) 

1103 if not check.has(oo, nan, zoo, -oo): 

1104 check = q.subs(x, point) 

1105 if not check.has(oo, nan, zoo, -oo): 

1106 ordinary = True 

1107 r.update({'a3': a3, 'b3': b3, 'c3': c3, 'x0': point, 'terms': terms}) 

1108 matching_hints["2nd_power_series_ordinary"] = r 

1109 

1110 # Checking if the differential equation has a regular singular point 

1111 # at x0. It has a regular singular point at x0, if (b3/a3)*(x - x0) 

1112 # and (c3/a3)*((x - x0)**2) are analytic at x0. 

1113 if not ordinary: 

1114 p = cancel((x - point)*p) 

1115 check = p.subs(x, point) 

1116 if not check.has(oo, nan, zoo, -oo): 

1117 q = cancel(((x - point)**2)*q) 

1118 check = q.subs(x, point) 

1119 if not check.has(oo, nan, zoo, -oo): 

1120 coeff_dict = {'p': p, 'q': q, 'x0': point, 'terms': terms} 

1121 matching_hints["2nd_power_series_regular"] = coeff_dict 

1122 

1123 

1124 # Order keys based on allhints. 

1125 retlist = [i for i in allhints if i in matching_hints] 

1126 if dict: 

1127 # Dictionaries are ordered arbitrarily, so make note of which 

1128 # hint would come first for dsolve(). Use an ordered dict in Py 3. 

1129 matching_hints["default"] = retlist[0] if retlist else None 

1130 matching_hints["ordered_hints"] = tuple(retlist) 

1131 return matching_hints 

1132 else: 

1133 return tuple(retlist) 

1134 

1135 

1136def classify_sysode(eq, funcs=None, **kwargs): 

1137 r""" 

1138 Returns a dictionary of parameter names and values that define the system 

1139 of ordinary differential equations in ``eq``. 

1140 The parameters are further used in 

1141 :py:meth:`~sympy.solvers.ode.dsolve` for solving that system. 

1142 

1143 Some parameter names and values are: 

1144 

1145 'is_linear' (boolean), which tells whether the given system is linear. 

1146 Note that "linear" here refers to the operator: terms such as ``x*diff(x,t)`` are 

1147 nonlinear, whereas terms like ``sin(t)*diff(x,t)`` are still linear operators. 

1148 

1149 'func' (list) contains the :py:class:`~sympy.core.function.Function`s that 

1150 appear with a derivative in the ODE, i.e. those that we are trying to solve 

1151 the ODE for. 

1152 

1153 'order' (dict) with the maximum derivative for each element of the 'func' 

1154 parameter. 

1155 

1156 'func_coeff' (dict or Matrix) with the coefficient for each triple ``(equation number, 

1157 function, order)```. The coefficients are those subexpressions that do not 

1158 appear in 'func', and hence can be considered constant for purposes of ODE 

1159 solving. The value of this parameter can also be a Matrix if the system of ODEs are 

1160 linear first order of the form X' = AX where X is the vector of dependent variables. 

1161 Here, this function returns the coefficient matrix A. 

1162 

1163 'eq' (list) with the equations from ``eq``, sympified and transformed into 

1164 expressions (we are solving for these expressions to be zero). 

1165 

1166 'no_of_equations' (int) is the number of equations (same as ``len(eq)``). 

1167 

1168 'type_of_equation' (string) is an internal classification of the type of 

1169 ODE. 

1170 

1171 'is_constant' (boolean), which tells if the system of ODEs is constant coefficient 

1172 or not. This key is temporary addition for now and is in the match dict only when 

1173 the system of ODEs is linear first order constant coefficient homogeneous. So, this 

1174 key's value is True for now if it is available else it does not exist. 

1175 

1176 'is_homogeneous' (boolean), which tells if the system of ODEs is homogeneous. Like the 

1177 key 'is_constant', this key is a temporary addition and it is True since this key value 

1178 is available only when the system is linear first order constant coefficient homogeneous. 

1179 

1180 References 

1181 ========== 

1182 -https://eqworld.ipmnet.ru/en/solutions/sysode/sode-toc1.htm 

1183 -A. D. Polyanin and A. V. Manzhirov, Handbook of Mathematics for Engineers and Scientists 

1184 

1185 Examples 

1186 ======== 

1187 

1188 >>> from sympy import Function, Eq, symbols, diff 

1189 >>> from sympy.solvers.ode.ode import classify_sysode 

1190 >>> from sympy.abc import t 

1191 >>> f, x, y = symbols('f, x, y', cls=Function) 

1192 >>> k, l, m, n = symbols('k, l, m, n', Integer=True) 

1193 >>> x1 = diff(x(t), t) ; y1 = diff(y(t), t) 

1194 >>> x2 = diff(x(t), t, t) ; y2 = diff(y(t), t, t) 

1195 >>> eq = (Eq(x1, 12*x(t) - 6*y(t)), Eq(y1, 11*x(t) + 3*y(t))) 

1196 >>> classify_sysode(eq) 

1197 {'eq': [-12*x(t) + 6*y(t) + Derivative(x(t), t), -11*x(t) - 3*y(t) + Derivative(y(t), t)], 'func': [x(t), y(t)], 

1198 'func_coeff': {(0, x(t), 0): -12, (0, x(t), 1): 1, (0, y(t), 0): 6, (0, y(t), 1): 0, (1, x(t), 0): -11, (1, x(t), 1): 0, (1, y(t), 0): -3, (1, y(t), 1): 1}, 'is_linear': True, 'no_of_equation': 2, 'order': {x(t): 1, y(t): 1}, 'type_of_equation': None} 

1199 >>> eq = (Eq(diff(x(t),t), 5*t*x(t) + t**2*y(t) + 2), Eq(diff(y(t),t), -t**2*x(t) + 5*t*y(t))) 

1200 >>> classify_sysode(eq) 

1201 {'eq': [-t**2*y(t) - 5*t*x(t) + Derivative(x(t), t) - 2, t**2*x(t) - 5*t*y(t) + Derivative(y(t), t)], 

1202 'func': [x(t), y(t)], 'func_coeff': {(0, x(t), 0): -5*t, (0, x(t), 1): 1, (0, y(t), 0): -t**2, (0, y(t), 1): 0, 

1203 (1, x(t), 0): t**2, (1, x(t), 1): 0, (1, y(t), 0): -5*t, (1, y(t), 1): 1}, 'is_linear': True, 'no_of_equation': 2, 

1204 'order': {x(t): 1, y(t): 1}, 'type_of_equation': None} 

1205 

1206 """ 

1207 

1208 # Sympify equations and convert iterables of equations into 

1209 # a list of equations 

1210 def _sympify(eq): 

1211 return list(map(sympify, eq if iterable(eq) else [eq])) 

1212 

1213 eq, funcs = (_sympify(w) for w in [eq, funcs]) 

1214 for i, fi in enumerate(eq): 

1215 if isinstance(fi, Equality): 

1216 eq[i] = fi.lhs - fi.rhs 

1217 

1218 t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] 

1219 matching_hints = {"no_of_equation":i+1} 

1220 matching_hints['eq'] = eq 

1221 if i==0: 

1222 raise ValueError("classify_sysode() works for systems of ODEs. " 

1223 "For scalar ODEs, classify_ode should be used") 

1224 

1225 # find all the functions if not given 

1226 order = {} 

1227 if funcs==[None]: 

1228 funcs = _extract_funcs(eq) 

1229 

1230 funcs = list(set(funcs)) 

1231 if len(funcs) != len(eq): 

1232 raise ValueError("Number of functions given is not equal to the number of equations %s" % funcs) 

1233 

1234 # This logic of list of lists in funcs to 

1235 # be replaced later. 

1236 func_dict = {} 

1237 for func in funcs: 

1238 if not order.get(func, False): 

1239 max_order = 0 

1240 for i, eqs_ in enumerate(eq): 

1241 order_ = ode_order(eqs_,func) 

1242 if max_order < order_: 

1243 max_order = order_ 

1244 eq_no = i 

1245 if eq_no in func_dict: 

1246 func_dict[eq_no] = [func_dict[eq_no], func] 

1247 else: 

1248 func_dict[eq_no] = func 

1249 order[func] = max_order 

1250 

1251 funcs = [func_dict[i] for i in range(len(func_dict))] 

1252 matching_hints['func'] = funcs 

1253 for func in funcs: 

1254 if isinstance(func, list): 

1255 for func_elem in func: 

1256 if len(func_elem.args) != 1: 

1257 raise ValueError("dsolve() and classify_sysode() work with " 

1258 "functions of one variable only, not %s" % func) 

1259 else: 

1260 if func and len(func.args) != 1: 

1261 raise ValueError("dsolve() and classify_sysode() work with " 

1262 "functions of one variable only, not %s" % func) 

1263 

1264 # find the order of all equation in system of odes 

1265 matching_hints["order"] = order 

1266 

1267 # find coefficients of terms f(t), diff(f(t),t) and higher derivatives 

1268 # and similarly for other functions g(t), diff(g(t),t) in all equations. 

1269 # Here j denotes the equation number, funcs[l] denotes the function about 

1270 # which we are talking about and k denotes the order of function funcs[l] 

1271 # whose coefficient we are calculating. 

1272 def linearity_check(eqs, j, func, is_linear_): 

1273 for k in range(order[func] + 1): 

1274 func_coef[j, func, k] = collect(eqs.expand(), [diff(func, t, k)]).coeff(diff(func, t, k)) 

1275 if is_linear_ == True: 

1276 if func_coef[j, func, k] == 0: 

1277 if k == 0: 

1278 coef = eqs.as_independent(func, as_Add=True)[1] 

1279 for xr in range(1, ode_order(eqs,func) + 1): 

1280 coef -= eqs.as_independent(diff(func, t, xr), as_Add=True)[1] 

1281 if coef != 0: 

1282 is_linear_ = False 

1283 else: 

1284 if eqs.as_independent(diff(func, t, k), as_Add=True)[1]: 

1285 is_linear_ = False 

1286 else: 

1287 for func_ in funcs: 

1288 if isinstance(func_, list): 

1289 for elem_func_ in func_: 

1290 dep = func_coef[j, func, k].as_independent(elem_func_, as_Add=True)[1] 

1291 if dep != 0: 

1292 is_linear_ = False 

1293 else: 

1294 dep = func_coef[j, func, k].as_independent(func_, as_Add=True)[1] 

1295 if dep != 0: 

1296 is_linear_ = False 

1297 return is_linear_ 

1298 

1299 func_coef = {} 

1300 is_linear = True 

1301 for j, eqs in enumerate(eq): 

1302 for func in funcs: 

1303 if isinstance(func, list): 

1304 for func_elem in func: 

1305 is_linear = linearity_check(eqs, j, func_elem, is_linear) 

1306 else: 

1307 is_linear = linearity_check(eqs, j, func, is_linear) 

1308 matching_hints['func_coeff'] = func_coef 

1309 matching_hints['is_linear'] = is_linear 

1310 

1311 

1312 if len(set(order.values())) == 1: 

1313 order_eq = list(matching_hints['order'].values())[0] 

1314 if matching_hints['is_linear'] == True: 

1315 if matching_hints['no_of_equation'] == 2: 

1316 if order_eq == 1: 

1317 type_of_equation = check_linear_2eq_order1(eq, funcs, func_coef) 

1318 else: 

1319 type_of_equation = None 

1320 # If the equation does not match up with any of the 

1321 # general case solvers in systems.py and the number 

1322 # of equations is greater than 2, then NotImplementedError 

1323 # should be raised. 

1324 else: 

1325 type_of_equation = None 

1326 

1327 else: 

1328 if matching_hints['no_of_equation'] == 2: 

1329 if order_eq == 1: 

1330 type_of_equation = check_nonlinear_2eq_order1(eq, funcs, func_coef) 

1331 else: 

1332 type_of_equation = None 

1333 elif matching_hints['no_of_equation'] == 3: 

1334 if order_eq == 1: 

1335 type_of_equation = check_nonlinear_3eq_order1(eq, funcs, func_coef) 

1336 else: 

1337 type_of_equation = None 

1338 else: 

1339 type_of_equation = None 

1340 else: 

1341 type_of_equation = None 

1342 

1343 matching_hints['type_of_equation'] = type_of_equation 

1344 

1345 return matching_hints 

1346 

1347 

1348def check_linear_2eq_order1(eq, func, func_coef): 

1349 x = func[0].func 

1350 y = func[1].func 

1351 fc = func_coef 

1352 t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] 

1353 r = {} 

1354 # for equations Eq(a1*diff(x(t),t), b1*x(t) + c1*y(t) + d1) 

1355 # and Eq(a2*diff(y(t),t), b2*x(t) + c2*y(t) + d2) 

1356 r['a1'] = fc[0,x(t),1] ; r['a2'] = fc[1,y(t),1] 

1357 r['b1'] = -fc[0,x(t),0]/fc[0,x(t),1] ; r['b2'] = -fc[1,x(t),0]/fc[1,y(t),1] 

1358 r['c1'] = -fc[0,y(t),0]/fc[0,x(t),1] ; r['c2'] = -fc[1,y(t),0]/fc[1,y(t),1] 

1359 forcing = [S.Zero,S.Zero] 

1360 for i in range(2): 

1361 for j in Add.make_args(eq[i]): 

1362 if not j.has(x(t), y(t)): 

1363 forcing[i] += j 

1364 if not (forcing[0].has(t) or forcing[1].has(t)): 

1365 # We can handle homogeneous case and simple constant forcings 

1366 r['d1'] = forcing[0] 

1367 r['d2'] = forcing[1] 

1368 else: 

1369 # Issue #9244: nonhomogeneous linear systems are not supported 

1370 return None 

1371 

1372 # Conditions to check for type 6 whose equations are Eq(diff(x(t),t), f(t)*x(t) + g(t)*y(t)) and 

1373 # Eq(diff(y(t),t), a*[f(t) + a*h(t)]x(t) + a*[g(t) - h(t)]*y(t)) 

1374 p = 0 

1375 q = 0 

1376 p1 = cancel(r['b2']/(cancel(r['b2']/r['c2']).as_numer_denom()[0])) 

1377 p2 = cancel(r['b1']/(cancel(r['b1']/r['c1']).as_numer_denom()[0])) 

1378 for n, i in enumerate([p1, p2]): 

1379 for j in Mul.make_args(collect_const(i)): 

1380 if not j.has(t): 

1381 q = j 

1382 if q and n==0: 

1383 if ((r['b2']/j - r['b1'])/(r['c1'] - r['c2']/j)) == j: 

1384 p = 1 

1385 elif q and n==1: 

1386 if ((r['b1']/j - r['b2'])/(r['c2'] - r['c1']/j)) == j: 

1387 p = 2 

1388 # End of condition for type 6 

1389 

1390 if r['d1']!=0 or r['d2']!=0: 

1391 return None 

1392 else: 

1393 if not any(r[k].has(t) for k in 'a1 a2 b1 b2 c1 c2'.split()): 

1394 return None 

1395 else: 

1396 r['b1'] = r['b1']/r['a1'] ; r['b2'] = r['b2']/r['a2'] 

1397 r['c1'] = r['c1']/r['a1'] ; r['c2'] = r['c2']/r['a2'] 

1398 if p: 

1399 return "type6" 

1400 else: 

1401 # Equations for type 7 are Eq(diff(x(t),t), f(t)*x(t) + g(t)*y(t)) and Eq(diff(y(t),t), h(t)*x(t) + p(t)*y(t)) 

1402 return "type7" 

1403def check_nonlinear_2eq_order1(eq, func, func_coef): 

1404 t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] 

1405 f = Wild('f') 

1406 g = Wild('g') 

1407 u, v = symbols('u, v', cls=Dummy) 

1408 def check_type(x, y): 

1409 r1 = eq[0].match(t*diff(x(t),t) - x(t) + f) 

1410 r2 = eq[1].match(t*diff(y(t),t) - y(t) + g) 

1411 if not (r1 and r2): 

1412 r1 = eq[0].match(diff(x(t),t) - x(t)/t + f/t) 

1413 r2 = eq[1].match(diff(y(t),t) - y(t)/t + g/t) 

1414 if not (r1 and r2): 

1415 r1 = (-eq[0]).match(t*diff(x(t),t) - x(t) + f) 

1416 r2 = (-eq[1]).match(t*diff(y(t),t) - y(t) + g) 

1417 if not (r1 and r2): 

1418 r1 = (-eq[0]).match(diff(x(t),t) - x(t)/t + f/t) 

1419 r2 = (-eq[1]).match(diff(y(t),t) - y(t)/t + g/t) 

1420 if r1 and r2 and not (r1[f].subs(diff(x(t),t),u).subs(diff(y(t),t),v).has(t) \ 

1421 or r2[g].subs(diff(x(t),t),u).subs(diff(y(t),t),v).has(t)): 

1422 return 'type5' 

1423 else: 

1424 return None 

1425 for func_ in func: 

1426 if isinstance(func_, list): 

1427 x = func[0][0].func 

1428 y = func[0][1].func 

1429 eq_type = check_type(x, y) 

1430 if not eq_type: 

1431 eq_type = check_type(y, x) 

1432 return eq_type 

1433 x = func[0].func 

1434 y = func[1].func 

1435 fc = func_coef 

1436 n = Wild('n', exclude=[x(t),y(t)]) 

1437 f1 = Wild('f1', exclude=[v,t]) 

1438 f2 = Wild('f2', exclude=[v,t]) 

1439 g1 = Wild('g1', exclude=[u,t]) 

1440 g2 = Wild('g2', exclude=[u,t]) 

1441 for i in range(2): 

1442 eqs = 0 

1443 for terms in Add.make_args(eq[i]): 

1444 eqs += terms/fc[i,func[i],1] 

1445 eq[i] = eqs 

1446 r = eq[0].match(diff(x(t),t) - x(t)**n*f) 

1447 if r: 

1448 g = (diff(y(t),t) - eq[1])/r[f] 

1449 if r and not (g.has(x(t)) or g.subs(y(t),v).has(t) or r[f].subs(x(t),u).subs(y(t),v).has(t)): 

1450 return 'type1' 

1451 r = eq[0].match(diff(x(t),t) - exp(n*x(t))*f) 

1452 if r: 

1453 g = (diff(y(t),t) - eq[1])/r[f] 

1454 if r and not (g.has(x(t)) or g.subs(y(t),v).has(t) or r[f].subs(x(t),u).subs(y(t),v).has(t)): 

1455 return 'type2' 

1456 g = Wild('g') 

1457 r1 = eq[0].match(diff(x(t),t) - f) 

1458 r2 = eq[1].match(diff(y(t),t) - g) 

1459 if r1 and r2 and not (r1[f].subs(x(t),u).subs(y(t),v).has(t) or \ 

1460 r2[g].subs(x(t),u).subs(y(t),v).has(t)): 

1461 return 'type3' 

1462 r1 = eq[0].match(diff(x(t),t) - f) 

1463 r2 = eq[1].match(diff(y(t),t) - g) 

1464 num, den = ( 

1465 (r1[f].subs(x(t),u).subs(y(t),v))/ 

1466 (r2[g].subs(x(t),u).subs(y(t),v))).as_numer_denom() 

1467 R1 = num.match(f1*g1) 

1468 R2 = den.match(f2*g2) 

1469 # phi = (r1[f].subs(x(t),u).subs(y(t),v))/num 

1470 if R1 and R2: 

1471 return 'type4' 

1472 return None 

1473 

1474 

1475def check_nonlinear_2eq_order2(eq, func, func_coef): 

1476 return None 

1477 

1478def check_nonlinear_3eq_order1(eq, func, func_coef): 

1479 x = func[0].func 

1480 y = func[1].func 

1481 z = func[2].func 

1482 fc = func_coef 

1483 t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] 

1484 u, v, w = symbols('u, v, w', cls=Dummy) 

1485 a = Wild('a', exclude=[x(t), y(t), z(t), t]) 

1486 b = Wild('b', exclude=[x(t), y(t), z(t), t]) 

1487 c = Wild('c', exclude=[x(t), y(t), z(t), t]) 

1488 f = Wild('f') 

1489 F1 = Wild('F1') 

1490 F2 = Wild('F2') 

1491 F3 = Wild('F3') 

1492 for i in range(3): 

1493 eqs = 0 

1494 for terms in Add.make_args(eq[i]): 

1495 eqs += terms/fc[i,func[i],1] 

1496 eq[i] = eqs 

1497 r1 = eq[0].match(diff(x(t),t) - a*y(t)*z(t)) 

1498 r2 = eq[1].match(diff(y(t),t) - b*z(t)*x(t)) 

1499 r3 = eq[2].match(diff(z(t),t) - c*x(t)*y(t)) 

1500 if r1 and r2 and r3: 

1501 num1, den1 = r1[a].as_numer_denom() 

1502 num2, den2 = r2[b].as_numer_denom() 

1503 num3, den3 = r3[c].as_numer_denom() 

1504 if solve([num1*u-den1*(v-w), num2*v-den2*(w-u), num3*w-den3*(u-v)],[u, v]): 

1505 return 'type1' 

1506 r = eq[0].match(diff(x(t),t) - y(t)*z(t)*f) 

1507 if r: 

1508 r1 = collect_const(r[f]).match(a*f) 

1509 r2 = ((diff(y(t),t) - eq[1])/r1[f]).match(b*z(t)*x(t)) 

1510 r3 = ((diff(z(t),t) - eq[2])/r1[f]).match(c*x(t)*y(t)) 

1511 if r1 and r2 and r3: 

1512 num1, den1 = r1[a].as_numer_denom() 

1513 num2, den2 = r2[b].as_numer_denom() 

1514 num3, den3 = r3[c].as_numer_denom() 

1515 if solve([num1*u-den1*(v-w), num2*v-den2*(w-u), num3*w-den3*(u-v)],[u, v]): 

1516 return 'type2' 

1517 r = eq[0].match(diff(x(t),t) - (F2-F3)) 

1518 if r: 

1519 r1 = collect_const(r[F2]).match(c*F2) 

1520 r1.update(collect_const(r[F3]).match(b*F3)) 

1521 if r1: 

1522 if eq[1].has(r1[F2]) and not eq[1].has(r1[F3]): 

1523 r1[F2], r1[F3] = r1[F3], r1[F2] 

1524 r1[c], r1[b] = -r1[b], -r1[c] 

1525 r2 = eq[1].match(diff(y(t),t) - a*r1[F3] + r1[c]*F1) 

1526 if r2: 

1527 r3 = (eq[2] == diff(z(t),t) - r1[b]*r2[F1] + r2[a]*r1[F2]) 

1528 if r1 and r2 and r3: 

1529 return 'type3' 

1530 r = eq[0].match(diff(x(t),t) - z(t)*F2 + y(t)*F3) 

1531 if r: 

1532 r1 = collect_const(r[F2]).match(c*F2) 

1533 r1.update(collect_const(r[F3]).match(b*F3)) 

1534 if r1: 

1535 if eq[1].has(r1[F2]) and not eq[1].has(r1[F3]): 

1536 r1[F2], r1[F3] = r1[F3], r1[F2] 

1537 r1[c], r1[b] = -r1[b], -r1[c] 

1538 r2 = (diff(y(t),t) - eq[1]).match(a*x(t)*r1[F3] - r1[c]*z(t)*F1) 

1539 if r2: 

1540 r3 = (diff(z(t),t) - eq[2] == r1[b]*y(t)*r2[F1] - r2[a]*x(t)*r1[F2]) 

1541 if r1 and r2 and r3: 

1542 return 'type4' 

1543 r = (diff(x(t),t) - eq[0]).match(x(t)*(F2 - F3)) 

1544 if r: 

1545 r1 = collect_const(r[F2]).match(c*F2) 

1546 r1.update(collect_const(r[F3]).match(b*F3)) 

1547 if r1: 

1548 if eq[1].has(r1[F2]) and not eq[1].has(r1[F3]): 

1549 r1[F2], r1[F3] = r1[F3], r1[F2] 

1550 r1[c], r1[b] = -r1[b], -r1[c] 

1551 r2 = (diff(y(t),t) - eq[1]).match(y(t)*(a*r1[F3] - r1[c]*F1)) 

1552 if r2: 

1553 r3 = (diff(z(t),t) - eq[2] == z(t)*(r1[b]*r2[F1] - r2[a]*r1[F2])) 

1554 if r1 and r2 and r3: 

1555 return 'type5' 

1556 return None 

1557 

1558 

1559def check_nonlinear_3eq_order2(eq, func, func_coef): 

1560 return None 

1561 

1562 

1563@vectorize(0) 

1564def odesimp(ode, eq, func, hint): 

1565 r""" 

1566 Simplifies solutions of ODEs, including trying to solve for ``func`` and 

1567 running :py:meth:`~sympy.solvers.ode.constantsimp`. 

1568 

1569 It may use knowledge of the type of solution that the hint returns to 

1570 apply additional simplifications. 

1571 

1572 It also attempts to integrate any :py:class:`~sympy.integrals.integrals.Integral`\s 

1573 in the expression, if the hint is not an ``_Integral`` hint. 

1574 

1575 This function should have no effect on expressions returned by 

1576 :py:meth:`~sympy.solvers.ode.dsolve`, as 

1577 :py:meth:`~sympy.solvers.ode.dsolve` already calls 

1578 :py:meth:`~sympy.solvers.ode.ode.odesimp`, but the individual hint functions 

1579 do not call :py:meth:`~sympy.solvers.ode.ode.odesimp` (because the 

1580 :py:meth:`~sympy.solvers.ode.dsolve` wrapper does). Therefore, this 

1581 function is designed for mainly internal use. 

1582 

1583 Examples 

1584 ======== 

1585 

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

1587 >>> from sympy.solvers.ode.ode import odesimp 

1588 >>> x, u2, C1= symbols('x,u2,C1') 

1589 >>> f = Function('f') 

1590 

1591 >>> eq = dsolve(x*f(x).diff(x) - f(x) - x*sin(f(x)/x), f(x), 

1592 ... hint='1st_homogeneous_coeff_subs_indep_div_dep_Integral', 

1593 ... simplify=False) 

1594 >>> pprint(eq, wrap_line=False) 

1595 x 

1596 ---- 

1597 f(x) 

1598 / 

1599 | 

1600 | / 1 \ 

1601 | -|u1 + -------| 

1602 | | /1 \| 

1603 | | sin|--|| 

1604 | \ \u1// 

1605 log(f(x)) = log(C1) + | ---------------- d(u1) 

1606 | 2 

1607 | u1 

1608 | 

1609 / 

1610 

1611 >>> pprint(odesimp(eq, f(x), 1, {C1}, 

1612 ... hint='1st_homogeneous_coeff_subs_indep_div_dep' 

1613 ... )) #doctest: +SKIP 

1614 x 

1615 --------- = C1 

1616 /f(x)\ 

1617 tan|----| 

1618 \2*x / 

1619 

1620 """ 

1621 x = func.args[0] 

1622 f = func.func 

1623 C1 = get_numbered_constants(eq, num=1) 

1624 constants = eq.free_symbols - ode.free_symbols 

1625 

1626 # First, integrate if the hint allows it. 

1627 eq = _handle_Integral(eq, func, hint) 

1628 if hint.startswith("nth_linear_euler_eq_nonhomogeneous"): 

1629 eq = simplify(eq) 

1630 if not isinstance(eq, Equality): 

1631 raise TypeError("eq should be an instance of Equality") 

1632 

1633 # Second, clean up the arbitrary constants. 

1634 # Right now, nth linear hints can put as many as 2*order constants in an 

1635 # expression. If that number grows with another hint, the third argument 

1636 # here should be raised accordingly, or constantsimp() rewritten to handle 

1637 # an arbitrary number of constants. 

1638 eq = constantsimp(eq, constants) 

1639 

1640 # Lastly, now that we have cleaned up the expression, try solving for func. 

1641 # When CRootOf is implemented in solve(), we will want to return a CRootOf 

1642 # every time instead of an Equality. 

1643 

1644 # Get the f(x) on the left if possible. 

1645 if eq.rhs == func and not eq.lhs.has(func): 

1646 eq = [Eq(eq.rhs, eq.lhs)] 

1647 

1648 # make sure we are working with lists of solutions in simplified form. 

1649 if eq.lhs == func and not eq.rhs.has(func): 

1650 # The solution is already solved 

1651 eq = [eq] 

1652 

1653 else: 

1654 # The solution is not solved, so try to solve it 

1655 try: 

1656 floats = any(i.is_Float for i in eq.atoms(Number)) 

1657 eqsol = solve(eq, func, force=True, rational=False if floats else None) 

1658 if not eqsol: 

1659 raise NotImplementedError 

1660 except (NotImplementedError, PolynomialError): 

1661 eq = [eq] 

1662 else: 

1663 def _expand(expr): 

1664 numer, denom = expr.as_numer_denom() 

1665 

1666 if denom.is_Add: 

1667 return expr 

1668 else: 

1669 return powsimp(expr.expand(), combine='exp', deep=True) 

1670 

1671 # XXX: the rest of odesimp() expects each ``t`` to be in a 

1672 # specific normal form: rational expression with numerator 

1673 # expanded, but with combined exponential functions (at 

1674 # least in this setup all tests pass). 

1675 eq = [Eq(f(x), _expand(t)) for t in eqsol] 

1676 

1677 # special simplification of the lhs. 

1678 if hint.startswith("1st_homogeneous_coeff"): 

1679 for j, eqi in enumerate(eq): 

1680 newi = logcombine(eqi, force=True) 

1681 if isinstance(newi.lhs, log) and newi.rhs == 0: 

1682 newi = Eq(newi.lhs.args[0]/C1, C1) 

1683 eq[j] = newi 

1684 

1685 # We cleaned up the constants before solving to help the solve engine with 

1686 # a simpler expression, but the solved expression could have introduced 

1687 # things like -C1, so rerun constantsimp() one last time before returning. 

1688 for i, eqi in enumerate(eq): 

1689 eq[i] = constantsimp(eqi, constants) 

1690 eq[i] = constant_renumber(eq[i], ode.free_symbols) 

1691 

1692 # If there is only 1 solution, return it; 

1693 # otherwise return the list of solutions. 

1694 if len(eq) == 1: 

1695 eq = eq[0] 

1696 return eq 

1697 

1698 

1699def ode_sol_simplicity(sol, func, trysolving=True): 

1700 r""" 

1701 Returns an extended integer representing how simple a solution to an ODE 

1702 is. 

1703 

1704 The following things are considered, in order from most simple to least: 

1705 

1706 - ``sol`` is solved for ``func``. 

1707 - ``sol`` is not solved for ``func``, but can be if passed to solve (e.g., 

1708 a solution returned by ``dsolve(ode, func, simplify=False``). 

1709 - If ``sol`` is not solved for ``func``, then base the result on the 

1710 length of ``sol``, as computed by ``len(str(sol))``. 

1711 - If ``sol`` has any unevaluated :py:class:`~sympy.integrals.integrals.Integral`\s, 

1712 this will automatically be considered less simple than any of the above. 

1713 

1714 This function returns an integer such that if solution A is simpler than 

1715 solution B by above metric, then ``ode_sol_simplicity(sola, func) < 

1716 ode_sol_simplicity(solb, func)``. 

1717 

1718 Currently, the following are the numbers returned, but if the heuristic is 

1719 ever improved, this may change. Only the ordering is guaranteed. 

1720 

1721 +----------------------------------------------+-------------------+ 

1722 | Simplicity | Return | 

1723 +==============================================+===================+ 

1724 | ``sol`` solved for ``func`` | ``-2`` | 

1725 +----------------------------------------------+-------------------+ 

1726 | ``sol`` not solved for ``func`` but can be | ``-1`` | 

1727 +----------------------------------------------+-------------------+ 

1728 | ``sol`` is not solved nor solvable for | ``len(str(sol))`` | 

1729 | ``func`` | | 

1730 +----------------------------------------------+-------------------+ 

1731 | ``sol`` contains an | ``oo`` | 

1732 | :obj:`~sympy.integrals.integrals.Integral` | | 

1733 +----------------------------------------------+-------------------+ 

1734 

1735 ``oo`` here means the SymPy infinity, which should compare greater than 

1736 any integer. 

1737 

1738 If you already know :py:meth:`~sympy.solvers.solvers.solve` cannot solve 

1739 ``sol``, you can use ``trysolving=False`` to skip that step, which is the 

1740 only potentially slow step. For example, 

1741 :py:meth:`~sympy.solvers.ode.dsolve` with the ``simplify=False`` flag 

1742 should do this. 

1743 

1744 If ``sol`` is a list of solutions, if the worst solution in the list 

1745 returns ``oo`` it returns that, otherwise it returns ``len(str(sol))``, 

1746 that is, the length of the string representation of the whole list. 

1747 

1748 Examples 

1749 ======== 

1750 

1751 This function is designed to be passed to ``min`` as the key argument, 

1752 such as ``min(listofsolutions, key=lambda i: ode_sol_simplicity(i, 

1753 f(x)))``. 

1754 

1755 >>> from sympy import symbols, Function, Eq, tan, Integral 

1756 >>> from sympy.solvers.ode.ode import ode_sol_simplicity 

1757 >>> x, C1, C2 = symbols('x, C1, C2') 

1758 >>> f = Function('f') 

1759 

1760 >>> ode_sol_simplicity(Eq(f(x), C1*x**2), f(x)) 

1761 -2 

1762 >>> ode_sol_simplicity(Eq(x**2 + f(x), C1), f(x)) 

1763 -1 

1764 >>> ode_sol_simplicity(Eq(f(x), C1*Integral(2*x, x)), f(x)) 

1765 oo 

1766 >>> eq1 = Eq(f(x)/tan(f(x)/(2*x)), C1) 

1767 >>> eq2 = Eq(f(x)/tan(f(x)/(2*x) + f(x)), C2) 

1768 >>> [ode_sol_simplicity(eq, f(x)) for eq in [eq1, eq2]] 

1769 [28, 35] 

1770 >>> min([eq1, eq2], key=lambda i: ode_sol_simplicity(i, f(x))) 

1771 Eq(f(x)/tan(f(x)/(2*x)), C1) 

1772 

1773 """ 

1774 # TODO: if two solutions are solved for f(x), we still want to be 

1775 # able to get the simpler of the two 

1776 

1777 # See the docstring for the coercion rules. We check easier (faster) 

1778 # things here first, to save time. 

1779 

1780 if iterable(sol): 

1781 # See if there are Integrals 

1782 for i in sol: 

1783 if ode_sol_simplicity(i, func, trysolving=trysolving) == oo: 

1784 return oo 

1785 

1786 return len(str(sol)) 

1787 

1788 if sol.has(Integral): 

1789 return oo 

1790 

1791 # Next, try to solve for func. This code will change slightly when CRootOf 

1792 # is implemented in solve(). Probably a CRootOf solution should fall 

1793 # somewhere between a normal solution and an unsolvable expression. 

1794 

1795 # First, see if they are already solved 

1796 if sol.lhs == func and not sol.rhs.has(func) or \ 

1797 sol.rhs == func and not sol.lhs.has(func): 

1798 return -2 

1799 # We are not so lucky, try solving manually 

1800 if trysolving: 

1801 try: 

1802 sols = solve(sol, func) 

1803 if not sols: 

1804 raise NotImplementedError 

1805 except NotImplementedError: 

1806 pass 

1807 else: 

1808 return -1 

1809 

1810 # Finally, a naive computation based on the length of the string version 

1811 # of the expression. This may favor combined fractions because they 

1812 # will not have duplicate denominators, and may slightly favor expressions 

1813 # with fewer additions and subtractions, as those are separated by spaces 

1814 # by the printer. 

1815 

1816 # Additional ideas for simplicity heuristics are welcome, like maybe 

1817 # checking if a equation has a larger domain, or if constantsimp has 

1818 # introduced arbitrary constants numbered higher than the order of a 

1819 # given ODE that sol is a solution of. 

1820 return len(str(sol)) 

1821 

1822 

1823def _extract_funcs(eqs): 

1824 funcs = [] 

1825 for eq in eqs: 

1826 derivs = [node for node in preorder_traversal(eq) if isinstance(node, Derivative)] 

1827 func = [] 

1828 for d in derivs: 

1829 func += list(d.atoms(AppliedUndef)) 

1830 for func_ in func: 

1831 funcs.append(func_) 

1832 funcs = list(uniq(funcs)) 

1833 

1834 return funcs 

1835 

1836 

1837def _get_constant_subexpressions(expr, Cs): 

1838 Cs = set(Cs) 

1839 Ces = [] 

1840 def _recursive_walk(expr): 

1841 expr_syms = expr.free_symbols 

1842 if expr_syms and expr_syms.issubset(Cs): 

1843 Ces.append(expr) 

1844 else: 

1845 if expr.func == exp: 

1846 expr = expr.expand(mul=True) 

1847 if expr.func in (Add, Mul): 

1848 d = sift(expr.args, lambda i : i.free_symbols.issubset(Cs)) 

1849 if len(d[True]) > 1: 

1850 x = expr.func(*d[True]) 

1851 if not x.is_number: 

1852 Ces.append(x) 

1853 elif isinstance(expr, Integral): 

1854 if expr.free_symbols.issubset(Cs) and \ 

1855 all(len(x) == 3 for x in expr.limits): 

1856 Ces.append(expr) 

1857 for i in expr.args: 

1858 _recursive_walk(i) 

1859 return 

1860 _recursive_walk(expr) 

1861 return Ces 

1862 

1863def __remove_linear_redundancies(expr, Cs): 

1864 cnts = {i: expr.count(i) for i in Cs} 

1865 Cs = [i for i in Cs if cnts[i] > 0] 

1866 

1867 def _linear(expr): 

1868 if isinstance(expr, Add): 

1869 xs = [i for i in Cs if expr.count(i)==cnts[i] \ 

1870 and 0 == expr.diff(i, 2)] 

1871 d = {} 

1872 for x in xs: 

1873 y = expr.diff(x) 

1874 if y not in d: 

1875 d[y]=[] 

1876 d[y].append(x) 

1877 for y in d: 

1878 if len(d[y]) > 1: 

1879 d[y].sort(key=str) 

1880 for x in d[y][1:]: 

1881 expr = expr.subs(x, 0) 

1882 return expr 

1883 

1884 def _recursive_walk(expr): 

1885 if len(expr.args) != 0: 

1886 expr = expr.func(*[_recursive_walk(i) for i in expr.args]) 

1887 expr = _linear(expr) 

1888 return expr 

1889 

1890 if isinstance(expr, Equality): 

1891 lhs, rhs = [_recursive_walk(i) for i in expr.args] 

1892 f = lambda i: isinstance(i, Number) or i in Cs 

1893 if isinstance(lhs, Symbol) and lhs in Cs: 

1894 rhs, lhs = lhs, rhs 

1895 if lhs.func in (Add, Symbol) and rhs.func in (Add, Symbol): 

1896 dlhs = sift([lhs] if isinstance(lhs, AtomicExpr) else lhs.args, f) 

1897 drhs = sift([rhs] if isinstance(rhs, AtomicExpr) else rhs.args, f) 

1898 for i in [True, False]: 

1899 for hs in [dlhs, drhs]: 

1900 if i not in hs: 

1901 hs[i] = [0] 

1902 # this calculation can be simplified 

1903 lhs = Add(*dlhs[False]) - Add(*drhs[False]) 

1904 rhs = Add(*drhs[True]) - Add(*dlhs[True]) 

1905 elif lhs.func in (Mul, Symbol) and rhs.func in (Mul, Symbol): 

1906 dlhs = sift([lhs] if isinstance(lhs, AtomicExpr) else lhs.args, f) 

1907 if True in dlhs: 

1908 if False not in dlhs: 

1909 dlhs[False] = [1] 

1910 lhs = Mul(*dlhs[False]) 

1911 rhs = rhs/Mul(*dlhs[True]) 

1912 return Eq(lhs, rhs) 

1913 else: 

1914 return _recursive_walk(expr) 

1915 

1916@vectorize(0) 

1917def constantsimp(expr, constants): 

1918 r""" 

1919 Simplifies an expression with arbitrary constants in it. 

1920 

1921 This function is written specifically to work with 

1922 :py:meth:`~sympy.solvers.ode.dsolve`, and is not intended for general use. 

1923 

1924 Simplification is done by "absorbing" the arbitrary constants into other 

1925 arbitrary constants, numbers, and symbols that they are not independent 

1926 of. 

1927 

1928 The symbols must all have the same name with numbers after it, for 

1929 example, ``C1``, ``C2``, ``C3``. The ``symbolname`` here would be 

1930 '``C``', the ``startnumber`` would be 1, and the ``endnumber`` would be 3. 

1931 If the arbitrary constants are independent of the variable ``x``, then the 

1932 independent symbol would be ``x``. There is no need to specify the 

1933 dependent function, such as ``f(x)``, because it already has the 

1934 independent symbol, ``x``, in it. 

1935 

1936 Because terms are "absorbed" into arbitrary constants and because 

1937 constants are renumbered after simplifying, the arbitrary constants in 

1938 expr are not necessarily equal to the ones of the same name in the 

1939 returned result. 

1940 

1941 If two or more arbitrary constants are added, multiplied, or raised to the 

1942 power of each other, they are first absorbed together into a single 

1943 arbitrary constant. Then the new constant is combined into other terms if 

1944 necessary. 

1945 

1946 Absorption of constants is done with limited assistance: 

1947 

1948 1. terms of :py:class:`~sympy.core.add.Add`\s are collected to try join 

1949 constants so `e^x (C_1 \cos(x) + C_2 \cos(x))` will simplify to `e^x 

1950 C_1 \cos(x)`; 

1951 

1952 2. powers with exponents that are :py:class:`~sympy.core.add.Add`\s are 

1953 expanded so `e^{C_1 + x}` will be simplified to `C_1 e^x`. 

1954 

1955 Use :py:meth:`~sympy.solvers.ode.ode.constant_renumber` to renumber constants 

1956 after simplification or else arbitrary numbers on constants may appear, 

1957 e.g. `C_1 + C_3 x`. 

1958 

1959 In rare cases, a single constant can be "simplified" into two constants. 

1960 Every differential equation solution should have as many arbitrary 

1961 constants as the order of the differential equation. The result here will 

1962 be technically correct, but it may, for example, have `C_1` and `C_2` in 

1963 an expression, when `C_1` is actually equal to `C_2`. Use your discretion 

1964 in such situations, and also take advantage of the ability to use hints in 

1965 :py:meth:`~sympy.solvers.ode.dsolve`. 

1966 

1967 Examples 

1968 ======== 

1969 

1970 >>> from sympy import symbols 

1971 >>> from sympy.solvers.ode.ode import constantsimp 

1972 >>> C1, C2, C3, x, y = symbols('C1, C2, C3, x, y') 

1973 >>> constantsimp(2*C1*x, {C1, C2, C3}) 

1974 C1*x 

1975 >>> constantsimp(C1 + 2 + x, {C1, C2, C3}) 

1976 C1 + x 

1977 >>> constantsimp(C1*C2 + 2 + C2 + C3*x, {C1, C2, C3}) 

1978 C1 + C3*x 

1979 

1980 """ 

1981 # This function works recursively. The idea is that, for Mul, 

1982 # Add, Pow, and Function, if the class has a constant in it, then 

1983 # we can simplify it, which we do by recursing down and 

1984 # simplifying up. Otherwise, we can skip that part of the 

1985 # expression. 

1986 

1987 Cs = constants 

1988 

1989 orig_expr = expr 

1990 

1991 constant_subexprs = _get_constant_subexpressions(expr, Cs) 

1992 for xe in constant_subexprs: 

1993 xes = list(xe.free_symbols) 

1994 if not xes: 

1995 continue 

1996 if all(expr.count(c) == xe.count(c) for c in xes): 

1997 xes.sort(key=str) 

1998 expr = expr.subs(xe, xes[0]) 

1999 

2000 # try to perform common sub-expression elimination of constant terms 

2001 try: 

2002 commons, rexpr = cse(expr) 

2003 commons.reverse() 

2004 rexpr = rexpr[0] 

2005 for s in commons: 

2006 cs = list(s[1].atoms(Symbol)) 

2007 if len(cs) == 1 and cs[0] in Cs and \ 

2008 cs[0] not in rexpr.atoms(Symbol) and \ 

2009 not any(cs[0] in ex for ex in commons if ex != s): 

2010 rexpr = rexpr.subs(s[0], cs[0]) 

2011 else: 

2012 rexpr = rexpr.subs(*s) 

2013 expr = rexpr 

2014 except IndexError: 

2015 pass 

2016 expr = __remove_linear_redundancies(expr, Cs) 

2017 

2018 def _conditional_term_factoring(expr): 

2019 new_expr = terms_gcd(expr, clear=False, deep=True, expand=False) 

2020 

2021 # we do not want to factor exponentials, so handle this separately 

2022 if new_expr.is_Mul: 

2023 infac = False 

2024 asfac = False 

2025 for m in new_expr.args: 

2026 if isinstance(m, exp): 

2027 asfac = True 

2028 elif m.is_Add: 

2029 infac = any(isinstance(fi, exp) for t in m.args 

2030 for fi in Mul.make_args(t)) 

2031 if asfac and infac: 

2032 new_expr = expr 

2033 break 

2034 return new_expr 

2035 

2036 expr = _conditional_term_factoring(expr) 

2037 

2038 # call recursively if more simplification is possible 

2039 if orig_expr != expr: 

2040 return constantsimp(expr, Cs) 

2041 return expr 

2042 

2043 

2044def constant_renumber(expr, variables=None, newconstants=None): 

2045 r""" 

2046 Renumber arbitrary constants in ``expr`` to use the symbol names as given 

2047 in ``newconstants``. In the process, this reorders expression terms in a 

2048 standard way. 

2049 

2050 If ``newconstants`` is not provided then the new constant names will be 

2051 ``C1``, ``C2`` etc. Otherwise ``newconstants`` should be an iterable 

2052 giving the new symbols to use for the constants in order. 

2053 

2054 The ``variables`` argument is a list of non-constant symbols. All other 

2055 free symbols found in ``expr`` are assumed to be constants and will be 

2056 renumbered. If ``variables`` is not given then any numbered symbol 

2057 beginning with ``C`` (e.g. ``C1``) is assumed to be a constant. 

2058 

2059 Symbols are renumbered based on ``.sort_key()``, so they should be 

2060 numbered roughly in the order that they appear in the final, printed 

2061 expression. Note that this ordering is based in part on hashes, so it can 

2062 produce different results on different machines. 

2063 

2064 The structure of this function is very similar to that of 

2065 :py:meth:`~sympy.solvers.ode.constantsimp`. 

2066 

2067 Examples 

2068 ======== 

2069 

2070 >>> from sympy import symbols 

2071 >>> from sympy.solvers.ode.ode import constant_renumber 

2072 >>> x, C1, C2, C3 = symbols('x,C1:4') 

2073 >>> expr = C3 + C2*x + C1*x**2 

2074 >>> expr 

2075 C1*x**2 + C2*x + C3 

2076 >>> constant_renumber(expr) 

2077 C1 + C2*x + C3*x**2 

2078 

2079 The ``variables`` argument specifies which are constants so that the 

2080 other symbols will not be renumbered: 

2081 

2082 >>> constant_renumber(expr, [C1, x]) 

2083 C1*x**2 + C2 + C3*x 

2084 

2085 The ``newconstants`` argument is used to specify what symbols to use when 

2086 replacing the constants: 

2087 

2088 >>> constant_renumber(expr, [x], newconstants=symbols('E1:4')) 

2089 E1 + E2*x + E3*x**2 

2090 

2091 """ 

2092 

2093 # System of expressions 

2094 if isinstance(expr, (set, list, tuple)): 

2095 return type(expr)(constant_renumber(Tuple(*expr), 

2096 variables=variables, newconstants=newconstants)) 

2097 

2098 # Symbols in solution but not ODE are constants 

2099 if variables is not None: 

2100 variables = set(variables) 

2101 free_symbols = expr.free_symbols 

2102 constantsymbols = list(free_symbols - variables) 

2103 # Any Cn is a constant... 

2104 else: 

2105 variables = set() 

2106 isconstant = lambda s: s.startswith('C') and s[1:].isdigit() 

2107 constantsymbols = [sym for sym in expr.free_symbols if isconstant(sym.name)] 

2108 

2109 # Find new constants checking that they aren't already in the ODE 

2110 if newconstants is None: 

2111 iter_constants = numbered_symbols(start=1, prefix='C', exclude=variables) 

2112 else: 

2113 iter_constants = (sym for sym in newconstants if sym not in variables) 

2114 

2115 constants_found = [] 

2116 

2117 # make a mapping to send all constantsymbols to S.One and use 

2118 # that to make sure that term ordering is not dependent on 

2119 # the indexed value of C 

2120 C_1 = [(ci, S.One) for ci in constantsymbols] 

2121 sort_key=lambda arg: default_sort_key(arg.subs(C_1)) 

2122 

2123 def _constant_renumber(expr): 

2124 r""" 

2125 We need to have an internal recursive function 

2126 """ 

2127 

2128 # For system of expressions 

2129 if isinstance(expr, Tuple): 

2130 renumbered = [_constant_renumber(e) for e in expr] 

2131 return Tuple(*renumbered) 

2132 

2133 if isinstance(expr, Equality): 

2134 return Eq( 

2135 _constant_renumber(expr.lhs), 

2136 _constant_renumber(expr.rhs)) 

2137 

2138 if type(expr) not in (Mul, Add, Pow) and not expr.is_Function and \ 

2139 not expr.has(*constantsymbols): 

2140 # Base case, as above. Hope there aren't constants inside 

2141 # of some other class, because they won't be renumbered. 

2142 return expr 

2143 elif expr.is_Piecewise: 

2144 return expr 

2145 elif expr in constantsymbols: 

2146 if expr not in constants_found: 

2147 constants_found.append(expr) 

2148 return expr 

2149 elif expr.is_Function or expr.is_Pow: 

2150 return expr.func( 

2151 *[_constant_renumber(x) for x in expr.args]) 

2152 else: 

2153 sortedargs = list(expr.args) 

2154 sortedargs.sort(key=sort_key) 

2155 return expr.func(*[_constant_renumber(x) for x in sortedargs]) 

2156 expr = _constant_renumber(expr) 

2157 

2158 # Don't renumber symbols present in the ODE. 

2159 constants_found = [c for c in constants_found if c not in variables] 

2160 

2161 # Renumbering happens here 

2162 subs_dict = {var: cons for var, cons in zip(constants_found, iter_constants)} 

2163 expr = expr.subs(subs_dict, simultaneous=True) 

2164 

2165 return expr 

2166 

2167 

2168def _handle_Integral(expr, func, hint): 

2169 r""" 

2170 Converts a solution with Integrals in it into an actual solution. 

2171 

2172 For most hints, this simply runs ``expr.doit()``. 

2173 

2174 """ 

2175 if hint == "nth_linear_constant_coeff_homogeneous": 

2176 sol = expr 

2177 elif not hint.endswith("_Integral"): 

2178 sol = expr.doit() 

2179 else: 

2180 sol = expr 

2181 return sol 

2182 

2183 

2184# XXX: Should this function maybe go somewhere else? 

2185 

2186 

2187def homogeneous_order(eq, *symbols): 

2188 r""" 

2189 Returns the order `n` if `g` is homogeneous and ``None`` if it is not 

2190 homogeneous. 

2191 

2192 Determines if a function is homogeneous and if so of what order. A 

2193 function `f(x, y, \cdots)` is homogeneous of order `n` if `f(t x, t y, 

2194 \cdots) = t^n f(x, y, \cdots)`. 

2195 

2196 If the function is of two variables, `F(x, y)`, then `f` being homogeneous 

2197 of any order is equivalent to being able to rewrite `F(x, y)` as `G(x/y)` 

2198 or `H(y/x)`. This fact is used to solve 1st order ordinary differential 

2199 equations whose coefficients are homogeneous of the same order (see the 

2200 docstrings of 

2201 :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` and 

2202 :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`). 

2203 

2204 Symbols can be functions, but every argument of the function must be a 

2205 symbol, and the arguments of the function that appear in the expression 

2206 must match those given in the list of symbols. If a declared function 

2207 appears with different arguments than given in the list of symbols, 

2208 ``None`` is returned. 

2209 

2210 Examples 

2211 ======== 

2212 

2213 >>> from sympy import Function, homogeneous_order, sqrt 

2214 >>> from sympy.abc import x, y 

2215 >>> f = Function('f') 

2216 >>> homogeneous_order(f(x), f(x)) is None 

2217 True 

2218 >>> homogeneous_order(f(x,y), f(y, x), x, y) is None 

2219 True 

2220 >>> homogeneous_order(f(x), f(x), x) 

2221 1 

2222 >>> homogeneous_order(x**2*f(x)/sqrt(x**2+f(x)**2), x, f(x)) 

2223 2 

2224 >>> homogeneous_order(x**2+f(x), x, f(x)) is None 

2225 True 

2226 

2227 """ 

2228 

2229 if not symbols: 

2230 raise ValueError("homogeneous_order: no symbols were given.") 

2231 symset = set(symbols) 

2232 eq = sympify(eq) 

2233 

2234 # The following are not supported 

2235 if eq.has(Order, Derivative): 

2236 return None 

2237 

2238 # These are all constants 

2239 if (eq.is_Number or 

2240 eq.is_NumberSymbol or 

2241 eq.is_number 

2242 ): 

2243 return S.Zero 

2244 

2245 # Replace all functions with dummy variables 

2246 dum = numbered_symbols(prefix='d', cls=Dummy) 

2247 newsyms = set() 

2248 for i in [j for j in symset if getattr(j, 'is_Function')]: 

2249 iargs = set(i.args) 

2250 if iargs.difference(symset): 

2251 return None 

2252 else: 

2253 dummyvar = next(dum) 

2254 eq = eq.subs(i, dummyvar) 

2255 symset.remove(i) 

2256 newsyms.add(dummyvar) 

2257 symset.update(newsyms) 

2258 

2259 if not eq.free_symbols & symset: 

2260 return None 

2261 

2262 # assuming order of a nested function can only be equal to zero 

2263 if isinstance(eq, Function): 

2264 return None if homogeneous_order( 

2265 eq.args[0], *tuple(symset)) != 0 else S.Zero 

2266 

2267 # make the replacement of x with x*t and see if t can be factored out 

2268 t = Dummy('t', positive=True) # It is sufficient that t > 0 

2269 eqs = separatevars(eq.subs([(i, t*i) for i in symset]), [t], dict=True)[t] 

2270 if eqs is S.One: 

2271 return S.Zero # there was no term with only t 

2272 i, d = eqs.as_independent(t, as_Add=False) 

2273 b, e = d.as_base_exp() 

2274 if b == t: 

2275 return e 

2276 

2277 

2278def ode_2nd_power_series_ordinary(eq, func, order, match): 

2279 r""" 

2280 Gives a power series solution to a second order homogeneous differential 

2281 equation with polynomial coefficients at an ordinary point. A homogeneous 

2282 differential equation is of the form 

2283 

2284 .. math :: P(x)\frac{d^2y}{dx^2} + Q(x)\frac{dy}{dx} + R(x) y(x) = 0 

2285 

2286 For simplicity it is assumed that `P(x)`, `Q(x)` and `R(x)` are polynomials, 

2287 it is sufficient that `\frac{Q(x)}{P(x)}` and `\frac{R(x)}{P(x)}` exists at 

2288 `x_{0}`. A recurrence relation is obtained by substituting `y` as `\sum_{n=0}^\infty a_{n}x^{n}`, 

2289 in the differential equation, and equating the nth term. Using this relation 

2290 various terms can be generated. 

2291 

2292 

2293 Examples 

2294 ======== 

2295 

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

2297 >>> from sympy.abc import x 

2298 >>> f = Function("f") 

2299 >>> eq = f(x).diff(x, 2) + f(x) 

2300 >>> pprint(dsolve(eq, hint='2nd_power_series_ordinary')) 

2301 / 4 2 \ / 2\ 

2302 |x x | | x | / 6\ 

2303 f(x) = C2*|-- - -- + 1| + C1*x*|1 - --| + O\x / 

2304 \24 2 / \ 6 / 

2305 

2306 

2307 References 

2308 ========== 

2309 - https://tutorial.math.lamar.edu/Classes/DE/SeriesSolutions.aspx 

2310 - George E. Simmons, "Differential Equations with Applications and 

2311 Historical Notes", p.p 176 - 184 

2312 

2313 """ 

2314 x = func.args[0] 

2315 f = func.func 

2316 C0, C1 = get_numbered_constants(eq, num=2) 

2317 n = Dummy("n", integer=True) 

2318 s = Wild("s") 

2319 k = Wild("k", exclude=[x]) 

2320 x0 = match['x0'] 

2321 terms = match['terms'] 

2322 p = match[match['a3']] 

2323 q = match[match['b3']] 

2324 r = match[match['c3']] 

2325 seriesdict = {} 

2326 recurr = Function("r") 

2327 

2328 # Generating the recurrence relation which works this way: 

2329 # for the second order term the summation begins at n = 2. The coefficients 

2330 # p is multiplied with an*(n - 1)*(n - 2)*x**n-2 and a substitution is made such that 

2331 # the exponent of x becomes n. 

2332 # For example, if p is x, then the second degree recurrence term is 

2333 # an*(n - 1)*(n - 2)*x**n-1, substituting (n - 1) as n, it transforms to 

2334 # an+1*n*(n - 1)*x**n. 

2335 # A similar process is done with the first order and zeroth order term. 

2336 

2337 coefflist = [(recurr(n), r), (n*recurr(n), q), (n*(n - 1)*recurr(n), p)] 

2338 for index, coeff in enumerate(coefflist): 

2339 if coeff[1]: 

2340 f2 = powsimp(expand((coeff[1]*(x - x0)**(n - index)).subs(x, x + x0))) 

2341 if f2.is_Add: 

2342 addargs = f2.args 

2343 else: 

2344 addargs = [f2] 

2345 for arg in addargs: 

2346 powm = arg.match(s*x**k) 

2347 term = coeff[0]*powm[s] 

2348 if not powm[k].is_Symbol: 

2349 term = term.subs(n, n - powm[k].as_independent(n)[0]) 

2350 startind = powm[k].subs(n, index) 

2351 # Seeing if the startterm can be reduced further. 

2352 # If it vanishes for n lesser than startind, it is 

2353 # equal to summation from n. 

2354 if startind: 

2355 for i in reversed(range(startind)): 

2356 if not term.subs(n, i): 

2357 seriesdict[term] = i 

2358 else: 

2359 seriesdict[term] = i + 1 

2360 break 

2361 else: 

2362 seriesdict[term] = S.Zero 

2363 

2364 # Stripping of terms so that the sum starts with the same number. 

2365 teq = S.Zero 

2366 suminit = seriesdict.values() 

2367 rkeys = seriesdict.keys() 

2368 req = Add(*rkeys) 

2369 if any(suminit): 

2370 maxval = max(suminit) 

2371 for term in seriesdict: 

2372 val = seriesdict[term] 

2373 if val != maxval: 

2374 for i in range(val, maxval): 

2375 teq += term.subs(n, val) 

2376 

2377 finaldict = {} 

2378 if teq: 

2379 fargs = teq.atoms(AppliedUndef) 

2380 if len(fargs) == 1: 

2381 finaldict[fargs.pop()] = 0 

2382 else: 

2383 maxf = max(fargs, key = lambda x: x.args[0]) 

2384 sol = solve(teq, maxf) 

2385 if isinstance(sol, list): 

2386 sol = sol[0] 

2387 finaldict[maxf] = sol 

2388 

2389 # Finding the recurrence relation in terms of the largest term. 

2390 fargs = req.atoms(AppliedUndef) 

2391 maxf = max(fargs, key = lambda x: x.args[0]) 

2392 minf = min(fargs, key = lambda x: x.args[0]) 

2393 if minf.args[0].is_Symbol: 

2394 startiter = 0 

2395 else: 

2396 startiter = -minf.args[0].as_independent(n)[0] 

2397 lhs = maxf 

2398 rhs = solve(req, maxf) 

2399 if isinstance(rhs, list): 

2400 rhs = rhs[0] 

2401 

2402 # Checking how many values are already present 

2403 tcounter = len([t for t in finaldict.values() if t]) 

2404 

2405 for _ in range(tcounter, terms - 3): # Assuming c0 and c1 to be arbitrary 

2406 check = rhs.subs(n, startiter) 

2407 nlhs = lhs.subs(n, startiter) 

2408 nrhs = check.subs(finaldict) 

2409 finaldict[nlhs] = nrhs 

2410 startiter += 1 

2411 

2412 # Post processing 

2413 series = C0 + C1*(x - x0) 

2414 for term in finaldict: 

2415 if finaldict[term]: 

2416 fact = term.args[0] 

2417 series += (finaldict[term].subs([(recurr(0), C0), (recurr(1), C1)])*( 

2418 x - x0)**fact) 

2419 series = collect(expand_mul(series), [C0, C1]) + Order(x**terms) 

2420 return Eq(f(x), series) 

2421 

2422 

2423def ode_2nd_power_series_regular(eq, func, order, match): 

2424 r""" 

2425 Gives a power series solution to a second order homogeneous differential 

2426 equation with polynomial coefficients at a regular point. A second order 

2427 homogeneous differential equation is of the form 

2428 

2429 .. math :: P(x)\frac{d^2y}{dx^2} + Q(x)\frac{dy}{dx} + R(x) y(x) = 0 

2430 

2431 A point is said to regular singular at `x0` if `x - x0\frac{Q(x)}{P(x)}` 

2432 and `(x - x0)^{2}\frac{R(x)}{P(x)}` are analytic at `x0`. For simplicity 

2433 `P(x)`, `Q(x)` and `R(x)` are assumed to be polynomials. The algorithm for 

2434 finding the power series solutions is: 

2435 

2436 1. Try expressing `(x - x0)P(x)` and `((x - x0)^{2})Q(x)` as power series 

2437 solutions about x0. Find `p0` and `q0` which are the constants of the 

2438 power series expansions. 

2439 2. Solve the indicial equation `f(m) = m(m - 1) + m*p0 + q0`, to obtain the 

2440 roots `m1` and `m2` of the indicial equation. 

2441 3. If `m1 - m2` is a non integer there exists two series solutions. If 

2442 `m1 = m2`, there exists only one solution. If `m1 - m2` is an integer, 

2443 then the existence of one solution is confirmed. The other solution may 

2444 or may not exist. 

2445 

2446 The power series solution is of the form `x^{m}\sum_{n=0}^\infty a_{n}x^{n}`. The 

2447 coefficients are determined by the following recurrence relation. 

2448 `a_{n} = -\frac{\sum_{k=0}^{n-1} q_{n-k} + (m + k)p_{n-k}}{f(m + n)}`. For the case 

2449 in which `m1 - m2` is an integer, it can be seen from the recurrence relation 

2450 that for the lower root `m`, when `n` equals the difference of both the 

2451 roots, the denominator becomes zero. So if the numerator is not equal to zero, 

2452 a second series solution exists. 

2453 

2454 

2455 Examples 

2456 ======== 

2457 

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

2459 >>> from sympy.abc import x 

2460 >>> f = Function("f") 

2461 >>> eq = x*(f(x).diff(x, 2)) + 2*(f(x).diff(x)) + x*f(x) 

2462 >>> pprint(dsolve(eq, hint='2nd_power_series_regular')) 

2463 / 6 4 2 \ 

2464 | x x x | 

2465 / 4 2 \ C1*|- --- + -- - -- + 1| 

2466 | x x | \ 720 24 2 / / 6\ 

2467 f(x) = C2*|--- - -- + 1| + ------------------------ + O\x / 

2468 \120 6 / x 

2469 

2470 

2471 References 

2472 ========== 

2473 - George E. Simmons, "Differential Equations with Applications and 

2474 Historical Notes", p.p 176 - 184 

2475 

2476 """ 

2477 x = func.args[0] 

2478 f = func.func 

2479 C0, C1 = get_numbered_constants(eq, num=2) 

2480 m = Dummy("m") # for solving the indicial equation 

2481 x0 = match['x0'] 

2482 terms = match['terms'] 

2483 p = match['p'] 

2484 q = match['q'] 

2485 

2486 # Generating the indicial equation 

2487 indicial = [] 

2488 for term in [p, q]: 

2489 if not term.has(x): 

2490 indicial.append(term) 

2491 else: 

2492 term = series(term, x=x, n=1, x0=x0) 

2493 if isinstance(term, Order): 

2494 indicial.append(S.Zero) 

2495 else: 

2496 for arg in term.args: 

2497 if not arg.has(x): 

2498 indicial.append(arg) 

2499 break 

2500 

2501 p0, q0 = indicial 

2502 sollist = solve(m*(m - 1) + m*p0 + q0, m) 

2503 if sollist and isinstance(sollist, list) and all( 

2504 sol.is_real for sol in sollist): 

2505 serdict1 = {} 

2506 serdict2 = {} 

2507 if len(sollist) == 1: 

2508 # Only one series solution exists in this case. 

2509 m1 = m2 = sollist.pop() 

2510 if terms-m1-1 <= 0: 

2511 return Eq(f(x), Order(terms)) 

2512 serdict1 = _frobenius(terms-m1-1, m1, p0, q0, p, q, x0, x, C0) 

2513 

2514 else: 

2515 m1 = sollist[0] 

2516 m2 = sollist[1] 

2517 if m1 < m2: 

2518 m1, m2 = m2, m1 

2519 # Irrespective of whether m1 - m2 is an integer or not, one 

2520 # Frobenius series solution exists. 

2521 serdict1 = _frobenius(terms-m1-1, m1, p0, q0, p, q, x0, x, C0) 

2522 if not (m1 - m2).is_integer: 

2523 # Second frobenius series solution exists. 

2524 serdict2 = _frobenius(terms-m2-1, m2, p0, q0, p, q, x0, x, C1) 

2525 else: 

2526 # Check if second frobenius series solution exists. 

2527 serdict2 = _frobenius(terms-m2-1, m2, p0, q0, p, q, x0, x, C1, check=m1) 

2528 

2529 if serdict1: 

2530 finalseries1 = C0 

2531 for key in serdict1: 

2532 power = int(key.name[1:]) 

2533 finalseries1 += serdict1[key]*(x - x0)**power 

2534 finalseries1 = (x - x0)**m1*finalseries1 

2535 finalseries2 = S.Zero 

2536 if serdict2: 

2537 for key in serdict2: 

2538 power = int(key.name[1:]) 

2539 finalseries2 += serdict2[key]*(x - x0)**power 

2540 finalseries2 += C1 

2541 finalseries2 = (x - x0)**m2*finalseries2 

2542 return Eq(f(x), collect(finalseries1 + finalseries2, 

2543 [C0, C1]) + Order(x**terms)) 

2544 

2545 

2546def _frobenius(n, m, p0, q0, p, q, x0, x, c, check=None): 

2547 r""" 

2548 Returns a dict with keys as coefficients and values as their values in terms of C0 

2549 """ 

2550 n = int(n) 

2551 # In cases where m1 - m2 is not an integer 

2552 m2 = check 

2553 

2554 d = Dummy("d") 

2555 numsyms = numbered_symbols("C", start=0) 

2556 numsyms = [next(numsyms) for i in range(n + 1)] 

2557 serlist = [] 

2558 for ser in [p, q]: 

2559 # Order term not present 

2560 if ser.is_polynomial(x) and Poly(ser, x).degree() <= n: 

2561 if x0: 

2562 ser = ser.subs(x, x + x0) 

2563 dict_ = Poly(ser, x).as_dict() 

2564 # Order term present 

2565 else: 

2566 tseries = series(ser, x=x0, n=n+1) 

2567 # Removing order 

2568 dict_ = Poly(list(ordered(tseries.args))[: -1], x).as_dict() 

2569 # Fill in with zeros, if coefficients are zero. 

2570 for i in range(n + 1): 

2571 if (i,) not in dict_: 

2572 dict_[(i,)] = S.Zero 

2573 serlist.append(dict_) 

2574 

2575 pseries = serlist[0] 

2576 qseries = serlist[1] 

2577 indicial = d*(d - 1) + d*p0 + q0 

2578 frobdict = {} 

2579 for i in range(1, n + 1): 

2580 num = c*(m*pseries[(i,)] + qseries[(i,)]) 

2581 for j in range(1, i): 

2582 sym = Symbol("C" + str(j)) 

2583 num += frobdict[sym]*((m + j)*pseries[(i - j,)] + qseries[(i - j,)]) 

2584 

2585 # Checking for cases when m1 - m2 is an integer. If num equals zero 

2586 # then a second Frobenius series solution cannot be found. If num is not zero 

2587 # then set constant as zero and proceed. 

2588 if m2 is not None and i == m2 - m: 

2589 if num: 

2590 return False 

2591 else: 

2592 frobdict[numsyms[i]] = S.Zero 

2593 else: 

2594 frobdict[numsyms[i]] = -num/(indicial.subs(d, m+i)) 

2595 

2596 return frobdict 

2597 

2598def _remove_redundant_solutions(eq, solns, order, var): 

2599 r""" 

2600 Remove redundant solutions from the set of solutions. 

2601 

2602 This function is needed because otherwise dsolve can return 

2603 redundant solutions. As an example consider: 

2604 

2605 eq = Eq((f(x).diff(x, 2))*f(x).diff(x), 0) 

2606 

2607 There are two ways to find solutions to eq. The first is to solve f(x).diff(x, 2) = 0 

2608 leading to solution f(x)=C1 + C2*x. The second is to solve the equation f(x).diff(x) = 0 

2609 leading to the solution f(x) = C1. In this particular case we then see 

2610 that the second solution is a special case of the first and we do not 

2611 want to return it. 

2612 

2613 This does not always happen. If we have 

2614 

2615 eq = Eq((f(x)**2-4)*(f(x).diff(x)-4), 0) 

2616 

2617 then we get the algebraic solution f(x) = [-2, 2] and the integral solution 

2618 f(x) = x + C1 and in this case the two solutions are not equivalent wrt 

2619 initial conditions so both should be returned. 

2620 """ 

2621 def is_special_case_of(soln1, soln2): 

2622 return _is_special_case_of(soln1, soln2, eq, order, var) 

2623 

2624 unique_solns = [] 

2625 for soln1 in solns: 

2626 for soln2 in unique_solns[:]: 

2627 if is_special_case_of(soln1, soln2): 

2628 break 

2629 elif is_special_case_of(soln2, soln1): 

2630 unique_solns.remove(soln2) 

2631 else: 

2632 unique_solns.append(soln1) 

2633 

2634 return unique_solns 

2635 

2636def _is_special_case_of(soln1, soln2, eq, order, var): 

2637 r""" 

2638 True if soln1 is found to be a special case of soln2 wrt some value of the 

2639 constants that appear in soln2. False otherwise. 

2640 """ 

2641 # The solutions returned by dsolve may be given explicitly or implicitly. 

2642 # We will equate the sol1=(soln1.rhs - soln1.lhs), sol2=(soln2.rhs - soln2.lhs) 

2643 # of the two solutions. 

2644 # 

2645 # Since this is supposed to hold for all x it also holds for derivatives. 

2646 # For an order n ode we should be able to differentiate 

2647 # each solution n times to get n+1 equations. 

2648 # 

2649 # We then try to solve those n+1 equations for the integrations constants 

2650 # in sol2. If we can find a solution that does not depend on x then it 

2651 # means that some value of the constants in sol1 is a special case of 

2652 # sol2 corresponding to a particular choice of the integration constants. 

2653 

2654 # In case the solution is in implicit form we subtract the sides 

2655 soln1 = soln1.rhs - soln1.lhs 

2656 soln2 = soln2.rhs - soln2.lhs 

2657 

2658 # Work for the series solution 

2659 if soln1.has(Order) and soln2.has(Order): 

2660 if soln1.getO() == soln2.getO(): 

2661 soln1 = soln1.removeO() 

2662 soln2 = soln2.removeO() 

2663 else: 

2664 return False 

2665 elif soln1.has(Order) or soln2.has(Order): 

2666 return False 

2667 

2668 constants1 = soln1.free_symbols.difference(eq.free_symbols) 

2669 constants2 = soln2.free_symbols.difference(eq.free_symbols) 

2670 

2671 constants1_new = get_numbered_constants(Tuple(soln1, soln2), len(constants1)) 

2672 if len(constants1) == 1: 

2673 constants1_new = {constants1_new} 

2674 for c_old, c_new in zip(constants1, constants1_new): 

2675 soln1 = soln1.subs(c_old, c_new) 

2676 

2677 # n equations for sol1 = sol2, sol1'=sol2', ... 

2678 lhs = soln1 

2679 rhs = soln2 

2680 eqns = [Eq(lhs, rhs)] 

2681 for n in range(1, order): 

2682 lhs = lhs.diff(var) 

2683 rhs = rhs.diff(var) 

2684 eq = Eq(lhs, rhs) 

2685 eqns.append(eq) 

2686 

2687 # BooleanTrue/False awkwardly show up for trivial equations 

2688 if any(isinstance(eq, BooleanFalse) for eq in eqns): 

2689 return False 

2690 eqns = [eq for eq in eqns if not isinstance(eq, BooleanTrue)] 

2691 

2692 try: 

2693 constant_solns = solve(eqns, constants2) 

2694 except NotImplementedError: 

2695 return False 

2696 

2697 # Sometimes returns a dict and sometimes a list of dicts 

2698 if isinstance(constant_solns, dict): 

2699 constant_solns = [constant_solns] 

2700 

2701 # after solving the issue 17418, maybe we don't need the following checksol code. 

2702 for constant_soln in constant_solns: 

2703 for eq in eqns: 

2704 eq=eq.rhs-eq.lhs 

2705 if checksol(eq, constant_soln) is not True: 

2706 return False 

2707 

2708 # If any solution gives all constants as expressions that don't depend on 

2709 # x then there exists constants for soln2 that give soln1 

2710 for constant_soln in constant_solns: 

2711 if not any(c.has(var) for c in constant_soln.values()): 

2712 return True 

2713 

2714 return False 

2715 

2716 

2717def ode_1st_power_series(eq, func, order, match): 

2718 r""" 

2719 The power series solution is a method which gives the Taylor series expansion 

2720 to the solution of a differential equation. 

2721 

2722 For a first order differential equation `\frac{dy}{dx} = h(x, y)`, a power 

2723 series solution exists at a point `x = x_{0}` if `h(x, y)` is analytic at `x_{0}`. 

2724 The solution is given by 

2725 

2726 .. math:: y(x) = y(x_{0}) + \sum_{n = 1}^{\infty} \frac{F_{n}(x_{0},b)(x - x_{0})^n}{n!}, 

2727 

2728 where `y(x_{0}) = b` is the value of y at the initial value of `x_{0}`. 

2729 To compute the values of the `F_{n}(x_{0},b)` the following algorithm is 

2730 followed, until the required number of terms are generated. 

2731 

2732 1. `F_1 = h(x_{0}, b)` 

2733 2. `F_{n+1} = \frac{\partial F_{n}}{\partial x} + \frac{\partial F_{n}}{\partial y}F_{1}` 

2734 

2735 Examples 

2736 ======== 

2737 

2738 >>> from sympy import Function, pprint, exp, dsolve 

2739 >>> from sympy.abc import x 

2740 >>> f = Function('f') 

2741 >>> eq = exp(x)*(f(x).diff(x)) - f(x) 

2742 >>> pprint(dsolve(eq, hint='1st_power_series')) 

2743 3 4 5 

2744 C1*x C1*x C1*x / 6\ 

2745 f(x) = C1 + C1*x - ----- + ----- + ----- + O\x / 

2746 6 24 60 

2747 

2748 

2749 References 

2750 ========== 

2751 

2752 - Travis W. Walker, Analytic power series technique for solving first-order 

2753 differential equations, p.p 17, 18 

2754 

2755 """ 

2756 x = func.args[0] 

2757 y = match['y'] 

2758 f = func.func 

2759 h = -match[match['d']]/match[match['e']] 

2760 point = match['f0'] 

2761 value = match['f0val'] 

2762 terms = match['terms'] 

2763 

2764 # First term 

2765 F = h 

2766 if not h: 

2767 return Eq(f(x), value) 

2768 

2769 # Initialization 

2770 series = value 

2771 if terms > 1: 

2772 hc = h.subs({x: point, y: value}) 

2773 if hc.has(oo) or hc.has(nan) or hc.has(zoo): 

2774 # Derivative does not exist, not analytic 

2775 return Eq(f(x), oo) 

2776 elif hc: 

2777 series += hc*(x - point) 

2778 

2779 for factcount in range(2, terms): 

2780 Fnew = F.diff(x) + F.diff(y)*h 

2781 Fnewc = Fnew.subs({x: point, y: value}) 

2782 # Same logic as above 

2783 if Fnewc.has(oo) or Fnewc.has(nan) or Fnewc.has(-oo) or Fnewc.has(zoo): 

2784 return Eq(f(x), oo) 

2785 series += Fnewc*((x - point)**factcount)/factorial(factcount) 

2786 F = Fnew 

2787 series += Order(x**terms) 

2788 return Eq(f(x), series) 

2789 

2790 

2791def checkinfsol(eq, infinitesimals, func=None, order=None): 

2792 r""" 

2793 This function is used to check if the given infinitesimals are the 

2794 actual infinitesimals of the given first order differential equation. 

2795 This method is specific to the Lie Group Solver of ODEs. 

2796 

2797 As of now, it simply checks, by substituting the infinitesimals in the 

2798 partial differential equation. 

2799 

2800 

2801 .. math:: \frac{\partial \eta}{\partial x} + \left(\frac{\partial \eta}{\partial y} 

2802 - \frac{\partial \xi}{\partial x}\right)*h 

2803 - \frac{\partial \xi}{\partial y}*h^{2} 

2804 - \xi\frac{\partial h}{\partial x} - \eta\frac{\partial h}{\partial y} = 0 

2805 

2806 

2807 where `\eta`, and `\xi` are the infinitesimals and `h(x,y) = \frac{dy}{dx}` 

2808 

2809 The infinitesimals should be given in the form of a list of dicts 

2810 ``[{xi(x, y): inf, eta(x, y): inf}]``, corresponding to the 

2811 output of the function infinitesimals. It returns a list 

2812 of values of the form ``[(True/False, sol)]`` where ``sol`` is the value 

2813 obtained after substituting the infinitesimals in the PDE. If it 

2814 is ``True``, then ``sol`` would be 0. 

2815 

2816 """ 

2817 if isinstance(eq, Equality): 

2818 eq = eq.lhs - eq.rhs 

2819 if not func: 

2820 eq, func = _preprocess(eq) 

2821 variables = func.args 

2822 if len(variables) != 1: 

2823 raise ValueError("ODE's have only one independent variable") 

2824 else: 

2825 x = variables[0] 

2826 if not order: 

2827 order = ode_order(eq, func) 

2828 if order != 1: 

2829 raise NotImplementedError("Lie groups solver has been implemented " 

2830 "only for first order differential equations") 

2831 else: 

2832 df = func.diff(x) 

2833 a = Wild('a', exclude = [df]) 

2834 b = Wild('b', exclude = [df]) 

2835 match = collect(expand(eq), df).match(a*df + b) 

2836 

2837 if match: 

2838 h = -simplify(match[b]/match[a]) 

2839 else: 

2840 try: 

2841 sol = solve(eq, df) 

2842 except NotImplementedError: 

2843 raise NotImplementedError("Infinitesimals for the " 

2844 "first order ODE could not be found") 

2845 else: 

2846 h = sol[0] # Find infinitesimals for one solution 

2847 

2848 y = Dummy('y') 

2849 h = h.subs(func, y) 

2850 xi = Function('xi')(x, y) 

2851 eta = Function('eta')(x, y) 

2852 dxi = Function('xi')(x, func) 

2853 deta = Function('eta')(x, func) 

2854 pde = (eta.diff(x) + (eta.diff(y) - xi.diff(x))*h - 

2855 (xi.diff(y))*h**2 - xi*(h.diff(x)) - eta*(h.diff(y))) 

2856 soltup = [] 

2857 for sol in infinitesimals: 

2858 tsol = {xi: S(sol[dxi]).subs(func, y), 

2859 eta: S(sol[deta]).subs(func, y)} 

2860 sol = simplify(pde.subs(tsol).doit()) 

2861 if sol: 

2862 soltup.append((False, sol.subs(y, func))) 

2863 else: 

2864 soltup.append((True, 0)) 

2865 return soltup 

2866 

2867 

2868def sysode_linear_2eq_order1(match_): 

2869 x = match_['func'][0].func 

2870 y = match_['func'][1].func 

2871 func = match_['func'] 

2872 fc = match_['func_coeff'] 

2873 eq = match_['eq'] 

2874 r = {} 

2875 t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] 

2876 for i in range(2): 

2877 eq[i] = Add(*[terms/fc[i,func[i],1] for terms in Add.make_args(eq[i])]) 

2878 

2879 # for equations Eq(a1*diff(x(t),t), a*x(t) + b*y(t) + k1) 

2880 # and Eq(a2*diff(x(t),t), c*x(t) + d*y(t) + k2) 

2881 r['a'] = -fc[0,x(t),0]/fc[0,x(t),1] 

2882 r['c'] = -fc[1,x(t),0]/fc[1,y(t),1] 

2883 r['b'] = -fc[0,y(t),0]/fc[0,x(t),1] 

2884 r['d'] = -fc[1,y(t),0]/fc[1,y(t),1] 

2885 forcing = [S.Zero,S.Zero] 

2886 for i in range(2): 

2887 for j in Add.make_args(eq[i]): 

2888 if not j.has(x(t), y(t)): 

2889 forcing[i] += j 

2890 if not (forcing[0].has(t) or forcing[1].has(t)): 

2891 r['k1'] = forcing[0] 

2892 r['k2'] = forcing[1] 

2893 else: 

2894 raise NotImplementedError("Only homogeneous problems are supported" + 

2895 " (and constant inhomogeneity)") 

2896 

2897 if match_['type_of_equation'] == 'type6': 

2898 sol = _linear_2eq_order1_type6(x, y, t, r, eq) 

2899 if match_['type_of_equation'] == 'type7': 

2900 sol = _linear_2eq_order1_type7(x, y, t, r, eq) 

2901 return sol 

2902 

2903def _linear_2eq_order1_type6(x, y, t, r, eq): 

2904 r""" 

2905 The equations of this type of ode are . 

2906 

2907 .. math:: x' = f(t) x + g(t) y 

2908 

2909 .. math:: y' = a [f(t) + a h(t)] x + a [g(t) - h(t)] y 

2910 

2911 This is solved by first multiplying the first equation by `-a` and adding 

2912 it to the second equation to obtain 

2913 

2914 .. math:: y' - a x' = -a h(t) (y - a x) 

2915 

2916 Setting `U = y - ax` and integrating the equation we arrive at 

2917 

2918 .. math:: y - ax = C_1 e^{-a \int h(t) \,dt} 

2919 

2920 and on substituting the value of y in first equation give rise to first order ODEs. After solving for 

2921 `x`, we can obtain `y` by substituting the value of `x` in second equation. 

2922 

2923 """ 

2924 C1, C2, C3, C4 = get_numbered_constants(eq, num=4) 

2925 p = 0 

2926 q = 0 

2927 p1 = cancel(r['c']/cancel(r['c']/r['d']).as_numer_denom()[0]) 

2928 p2 = cancel(r['a']/cancel(r['a']/r['b']).as_numer_denom()[0]) 

2929 for n, i in enumerate([p1, p2]): 

2930 for j in Mul.make_args(collect_const(i)): 

2931 if not j.has(t): 

2932 q = j 

2933 if q!=0 and n==0: 

2934 if ((r['c']/j - r['a'])/(r['b'] - r['d']/j)) == j: 

2935 p = 1 

2936 s = j 

2937 break 

2938 if q!=0 and n==1: 

2939 if ((r['a']/j - r['c'])/(r['d'] - r['b']/j)) == j: 

2940 p = 2 

2941 s = j 

2942 break 

2943 

2944 if p == 1: 

2945 equ = diff(x(t),t) - r['a']*x(t) - r['b']*(s*x(t) + C1*exp(-s*Integral(r['b'] - r['d']/s, t))) 

2946 hint1 = classify_ode(equ)[1] 

2947 sol1 = dsolve(equ, hint=hint1+'_Integral').rhs 

2948 sol2 = s*sol1 + C1*exp(-s*Integral(r['b'] - r['d']/s, t)) 

2949 elif p ==2: 

2950 equ = diff(y(t),t) - r['c']*y(t) - r['d']*s*y(t) + C1*exp(-s*Integral(r['d'] - r['b']/s, t)) 

2951 hint1 = classify_ode(equ)[1] 

2952 sol2 = dsolve(equ, hint=hint1+'_Integral').rhs 

2953 sol1 = s*sol2 + C1*exp(-s*Integral(r['d'] - r['b']/s, t)) 

2954 return [Eq(x(t), sol1), Eq(y(t), sol2)] 

2955 

2956def _linear_2eq_order1_type7(x, y, t, r, eq): 

2957 r""" 

2958 The equations of this type of ode are . 

2959 

2960 .. math:: x' = f(t) x + g(t) y 

2961 

2962 .. math:: y' = h(t) x + p(t) y 

2963 

2964 Differentiating the first equation and substituting the value of `y` 

2965 from second equation will give a second-order linear equation 

2966 

2967 .. math:: g x'' - (fg + gp + g') x' + (fgp - g^{2} h + f g' - f' g) x = 0 

2968 

2969 This above equation can be easily integrated if following conditions are satisfied. 

2970 

2971 1. `fgp - g^{2} h + f g' - f' g = 0` 

2972 

2973 2. `fgp - g^{2} h + f g' - f' g = ag, fg + gp + g' = bg` 

2974 

2975 If first condition is satisfied then it is solved by current dsolve solver and in second case it becomes 

2976 a constant coefficient differential equation which is also solved by current solver. 

2977 

2978 Otherwise if the above condition fails then, 

2979 a particular solution is assumed as `x = x_0(t)` and `y = y_0(t)` 

2980 Then the general solution is expressed as 

2981 

2982 .. math:: x = C_1 x_0(t) + C_2 x_0(t) \int \frac{g(t) F(t) P(t)}{x_0^{2}(t)} \,dt 

2983 

2984 .. math:: y = C_1 y_0(t) + C_2 [\frac{F(t) P(t)}{x_0(t)} + y_0(t) \int \frac{g(t) F(t) P(t)}{x_0^{2}(t)} \,dt] 

2985 

2986 where C1 and C2 are arbitrary constants and 

2987 

2988 .. math:: F(t) = e^{\int f(t) \,dt}, P(t) = e^{\int p(t) \,dt} 

2989 

2990 """ 

2991 C1, C2, C3, C4 = get_numbered_constants(eq, num=4) 

2992 e1 = r['a']*r['b']*r['c'] - r['b']**2*r['c'] + r['a']*diff(r['b'],t) - diff(r['a'],t)*r['b'] 

2993 e2 = r['a']*r['c']*r['d'] - r['b']*r['c']**2 + diff(r['c'],t)*r['d'] - r['c']*diff(r['d'],t) 

2994 m1 = r['a']*r['b'] + r['b']*r['d'] + diff(r['b'],t) 

2995 m2 = r['a']*r['c'] + r['c']*r['d'] + diff(r['c'],t) 

2996 if e1 == 0: 

2997 sol1 = dsolve(r['b']*diff(x(t),t,t) - m1*diff(x(t),t)).rhs 

2998 sol2 = dsolve(diff(y(t),t) - r['c']*sol1 - r['d']*y(t)).rhs 

2999 elif e2 == 0: 

3000 sol2 = dsolve(r['c']*diff(y(t),t,t) - m2*diff(y(t),t)).rhs 

3001 sol1 = dsolve(diff(x(t),t) - r['a']*x(t) - r['b']*sol2).rhs 

3002 elif not (e1/r['b']).has(t) and not (m1/r['b']).has(t): 

3003 sol1 = dsolve(diff(x(t),t,t) - (m1/r['b'])*diff(x(t),t) - (e1/r['b'])*x(t)).rhs 

3004 sol2 = dsolve(diff(y(t),t) - r['c']*sol1 - r['d']*y(t)).rhs 

3005 elif not (e2/r['c']).has(t) and not (m2/r['c']).has(t): 

3006 sol2 = dsolve(diff(y(t),t,t) - (m2/r['c'])*diff(y(t),t) - (e2/r['c'])*y(t)).rhs 

3007 sol1 = dsolve(diff(x(t),t) - r['a']*x(t) - r['b']*sol2).rhs 

3008 else: 

3009 x0 = Function('x0')(t) # x0 and y0 being particular solutions 

3010 y0 = Function('y0')(t) 

3011 F = exp(Integral(r['a'],t)) 

3012 P = exp(Integral(r['d'],t)) 

3013 sol1 = C1*x0 + C2*x0*Integral(r['b']*F*P/x0**2, t) 

3014 sol2 = C1*y0 + C2*(F*P/x0 + y0*Integral(r['b']*F*P/x0**2, t)) 

3015 return [Eq(x(t), sol1), Eq(y(t), sol2)] 

3016 

3017 

3018def sysode_nonlinear_2eq_order1(match_): 

3019 func = match_['func'] 

3020 eq = match_['eq'] 

3021 fc = match_['func_coeff'] 

3022 t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] 

3023 if match_['type_of_equation'] == 'type5': 

3024 sol = _nonlinear_2eq_order1_type5(func, t, eq) 

3025 return sol 

3026 x = func[0].func 

3027 y = func[1].func 

3028 for i in range(2): 

3029 eqs = 0 

3030 for terms in Add.make_args(eq[i]): 

3031 eqs += terms/fc[i,func[i],1] 

3032 eq[i] = eqs 

3033 if match_['type_of_equation'] == 'type1': 

3034 sol = _nonlinear_2eq_order1_type1(x, y, t, eq) 

3035 elif match_['type_of_equation'] == 'type2': 

3036 sol = _nonlinear_2eq_order1_type2(x, y, t, eq) 

3037 elif match_['type_of_equation'] == 'type3': 

3038 sol = _nonlinear_2eq_order1_type3(x, y, t, eq) 

3039 elif match_['type_of_equation'] == 'type4': 

3040 sol = _nonlinear_2eq_order1_type4(x, y, t, eq) 

3041 return sol 

3042 

3043 

3044def _nonlinear_2eq_order1_type1(x, y, t, eq): 

3045 r""" 

3046 Equations: 

3047 

3048 .. math:: x' = x^n F(x,y) 

3049 

3050 .. math:: y' = g(y) F(x,y) 

3051 

3052 Solution: 

3053 

3054 .. math:: x = \varphi(y), \int \frac{1}{g(y) F(\varphi(y),y)} \,dy = t + C_2 

3055 

3056 where 

3057 

3058 if `n \neq 1` 

3059 

3060 .. math:: \varphi = [C_1 + (1-n) \int \frac{1}{g(y)} \,dy]^{\frac{1}{1-n}} 

3061 

3062 if `n = 1` 

3063 

3064 .. math:: \varphi = C_1 e^{\int \frac{1}{g(y)} \,dy} 

3065 

3066 where `C_1` and `C_2` are arbitrary constants. 

3067 

3068 """ 

3069 C1, C2 = get_numbered_constants(eq, num=2) 

3070 n = Wild('n', exclude=[x(t),y(t)]) 

3071 f = Wild('f') 

3072 u, v = symbols('u, v') 

3073 r = eq[0].match(diff(x(t),t) - x(t)**n*f) 

3074 g = ((diff(y(t),t) - eq[1])/r[f]).subs(y(t),v) 

3075 F = r[f].subs(x(t),u).subs(y(t),v) 

3076 n = r[n] 

3077 if n!=1: 

3078 phi = (C1 + (1-n)*Integral(1/g, v))**(1/(1-n)) 

3079 else: 

3080 phi = C1*exp(Integral(1/g, v)) 

3081 phi = phi.doit() 

3082 sol2 = solve(Integral(1/(g*F.subs(u,phi)), v).doit() - t - C2, v) 

3083 sol = [] 

3084 for sols in sol2: 

3085 sol.append(Eq(x(t),phi.subs(v, sols))) 

3086 sol.append(Eq(y(t), sols)) 

3087 return sol 

3088 

3089def _nonlinear_2eq_order1_type2(x, y, t, eq): 

3090 r""" 

3091 Equations: 

3092 

3093 .. math:: x' = e^{\lambda x} F(x,y) 

3094 

3095 .. math:: y' = g(y) F(x,y) 

3096 

3097 Solution: 

3098 

3099 .. math:: x = \varphi(y), \int \frac{1}{g(y) F(\varphi(y),y)} \,dy = t + C_2 

3100 

3101 where 

3102 

3103 if `\lambda \neq 0` 

3104 

3105 .. math:: \varphi = -\frac{1}{\lambda} log(C_1 - \lambda \int \frac{1}{g(y)} \,dy) 

3106 

3107 if `\lambda = 0` 

3108 

3109 .. math:: \varphi = C_1 + \int \frac{1}{g(y)} \,dy 

3110 

3111 where `C_1` and `C_2` are arbitrary constants. 

3112 

3113 """ 

3114 C1, C2 = get_numbered_constants(eq, num=2) 

3115 n = Wild('n', exclude=[x(t),y(t)]) 

3116 f = Wild('f') 

3117 u, v = symbols('u, v') 

3118 r = eq[0].match(diff(x(t),t) - exp(n*x(t))*f) 

3119 g = ((diff(y(t),t) - eq[1])/r[f]).subs(y(t),v) 

3120 F = r[f].subs(x(t),u).subs(y(t),v) 

3121 n = r[n] 

3122 if n: 

3123 phi = -1/n*log(C1 - n*Integral(1/g, v)) 

3124 else: 

3125 phi = C1 + Integral(1/g, v) 

3126 phi = phi.doit() 

3127 sol2 = solve(Integral(1/(g*F.subs(u,phi)), v).doit() - t - C2, v) 

3128 sol = [] 

3129 for sols in sol2: 

3130 sol.append(Eq(x(t),phi.subs(v, sols))) 

3131 sol.append(Eq(y(t), sols)) 

3132 return sol 

3133 

3134def _nonlinear_2eq_order1_type3(x, y, t, eq): 

3135 r""" 

3136 Autonomous system of general form 

3137 

3138 .. math:: x' = F(x,y) 

3139 

3140 .. math:: y' = G(x,y) 

3141 

3142 Assuming `y = y(x, C_1)` where `C_1` is an arbitrary constant is the general 

3143 solution of the first-order equation 

3144 

3145 .. math:: F(x,y) y'_x = G(x,y) 

3146 

3147 Then the general solution of the original system of equations has the form 

3148 

3149 .. math:: \int \frac{1}{F(x,y(x,C_1))} \,dx = t + C_1 

3150 

3151 """ 

3152 C1, C2, C3, C4 = get_numbered_constants(eq, num=4) 

3153 v = Function('v') 

3154 u = Symbol('u') 

3155 f = Wild('f') 

3156 g = Wild('g') 

3157 r1 = eq[0].match(diff(x(t),t) - f) 

3158 r2 = eq[1].match(diff(y(t),t) - g) 

3159 F = r1[f].subs(x(t), u).subs(y(t), v(u)) 

3160 G = r2[g].subs(x(t), u).subs(y(t), v(u)) 

3161 sol2r = dsolve(Eq(diff(v(u), u), G/F)) 

3162 if isinstance(sol2r, Equality): 

3163 sol2r = [sol2r] 

3164 for sol2s in sol2r: 

3165 sol1 = solve(Integral(1/F.subs(v(u), sol2s.rhs), u).doit() - t - C2, u) 

3166 sol = [] 

3167 for sols in sol1: 

3168 sol.append(Eq(x(t), sols)) 

3169 sol.append(Eq(y(t), (sol2s.rhs).subs(u, sols))) 

3170 return sol 

3171 

3172def _nonlinear_2eq_order1_type4(x, y, t, eq): 

3173 r""" 

3174 Equation: 

3175 

3176 .. math:: x' = f_1(x) g_1(y) \phi(x,y,t) 

3177 

3178 .. math:: y' = f_2(x) g_2(y) \phi(x,y,t) 

3179 

3180 First integral: 

3181 

3182 .. math:: \int \frac{f_2(x)}{f_1(x)} \,dx - \int \frac{g_1(y)}{g_2(y)} \,dy = C 

3183 

3184 where `C` is an arbitrary constant. 

3185 

3186 On solving the first integral for `x` (resp., `y` ) and on substituting the 

3187 resulting expression into either equation of the original solution, one 

3188 arrives at a first-order equation for determining `y` (resp., `x` ). 

3189 

3190 """ 

3191 C1, C2 = get_numbered_constants(eq, num=2) 

3192 u, v = symbols('u, v') 

3193 U, V = symbols('U, V', cls=Function) 

3194 f = Wild('f') 

3195 g = Wild('g') 

3196 f1 = Wild('f1', exclude=[v,t]) 

3197 f2 = Wild('f2', exclude=[v,t]) 

3198 g1 = Wild('g1', exclude=[u,t]) 

3199 g2 = Wild('g2', exclude=[u,t]) 

3200 r1 = eq[0].match(diff(x(t),t) - f) 

3201 r2 = eq[1].match(diff(y(t),t) - g) 

3202 num, den = ( 

3203 (r1[f].subs(x(t),u).subs(y(t),v))/ 

3204 (r2[g].subs(x(t),u).subs(y(t),v))).as_numer_denom() 

3205 R1 = num.match(f1*g1) 

3206 R2 = den.match(f2*g2) 

3207 phi = (r1[f].subs(x(t),u).subs(y(t),v))/num 

3208 F1 = R1[f1]; F2 = R2[f2] 

3209 G1 = R1[g1]; G2 = R2[g2] 

3210 sol1r = solve(Integral(F2/F1, u).doit() - Integral(G1/G2,v).doit() - C1, u) 

3211 sol2r = solve(Integral(F2/F1, u).doit() - Integral(G1/G2,v).doit() - C1, v) 

3212 sol = [] 

3213 for sols in sol1r: 

3214 sol.append(Eq(y(t), dsolve(diff(V(t),t) - F2.subs(u,sols).subs(v,V(t))*G2.subs(v,V(t))*phi.subs(u,sols).subs(v,V(t))).rhs)) 

3215 for sols in sol2r: 

3216 sol.append(Eq(x(t), dsolve(diff(U(t),t) - F1.subs(u,U(t))*G1.subs(v,sols).subs(u,U(t))*phi.subs(v,sols).subs(u,U(t))).rhs)) 

3217 return set(sol) 

3218 

3219def _nonlinear_2eq_order1_type5(func, t, eq): 

3220 r""" 

3221 Clairaut system of ODEs 

3222 

3223 .. math:: x = t x' + F(x',y') 

3224 

3225 .. math:: y = t y' + G(x',y') 

3226 

3227 The following are solutions of the system 

3228 

3229 `(i)` straight lines: 

3230 

3231 .. math:: x = C_1 t + F(C_1, C_2), y = C_2 t + G(C_1, C_2) 

3232 

3233 where `C_1` and `C_2` are arbitrary constants; 

3234 

3235 `(ii)` envelopes of the above lines; 

3236 

3237 `(iii)` continuously differentiable lines made up from segments of the lines 

3238 `(i)` and `(ii)`. 

3239 

3240 """ 

3241 C1, C2 = get_numbered_constants(eq, num=2) 

3242 f = Wild('f') 

3243 g = Wild('g') 

3244 def check_type(x, y): 

3245 r1 = eq[0].match(t*diff(x(t),t) - x(t) + f) 

3246 r2 = eq[1].match(t*diff(y(t),t) - y(t) + g) 

3247 if not (r1 and r2): 

3248 r1 = eq[0].match(diff(x(t),t) - x(t)/t + f/t) 

3249 r2 = eq[1].match(diff(y(t),t) - y(t)/t + g/t) 

3250 if not (r1 and r2): 

3251 r1 = (-eq[0]).match(t*diff(x(t),t) - x(t) + f) 

3252 r2 = (-eq[1]).match(t*diff(y(t),t) - y(t) + g) 

3253 if not (r1 and r2): 

3254 r1 = (-eq[0]).match(diff(x(t),t) - x(t)/t + f/t) 

3255 r2 = (-eq[1]).match(diff(y(t),t) - y(t)/t + g/t) 

3256 return [r1, r2] 

3257 for func_ in func: 

3258 if isinstance(func_, list): 

3259 x = func[0][0].func 

3260 y = func[0][1].func 

3261 [r1, r2] = check_type(x, y) 

3262 if not (r1 and r2): 

3263 [r1, r2] = check_type(y, x) 

3264 x, y = y, x 

3265 x1 = diff(x(t),t); y1 = diff(y(t),t) 

3266 return {Eq(x(t), C1*t + r1[f].subs(x1,C1).subs(y1,C2)), Eq(y(t), C2*t + r2[g].subs(x1,C1).subs(y1,C2))} 

3267 

3268def sysode_nonlinear_3eq_order1(match_): 

3269 x = match_['func'][0].func 

3270 y = match_['func'][1].func 

3271 z = match_['func'][2].func 

3272 eq = match_['eq'] 

3273 t = list(list(eq[0].atoms(Derivative))[0].atoms(Symbol))[0] 

3274 if match_['type_of_equation'] == 'type1': 

3275 sol = _nonlinear_3eq_order1_type1(x, y, z, t, eq) 

3276 if match_['type_of_equation'] == 'type2': 

3277 sol = _nonlinear_3eq_order1_type2(x, y, z, t, eq) 

3278 if match_['type_of_equation'] == 'type3': 

3279 sol = _nonlinear_3eq_order1_type3(x, y, z, t, eq) 

3280 if match_['type_of_equation'] == 'type4': 

3281 sol = _nonlinear_3eq_order1_type4(x, y, z, t, eq) 

3282 if match_['type_of_equation'] == 'type5': 

3283 sol = _nonlinear_3eq_order1_type5(x, y, z, t, eq) 

3284 return sol 

3285 

3286def _nonlinear_3eq_order1_type1(x, y, z, t, eq): 

3287 r""" 

3288 Equations: 

3289 

3290 .. math:: a x' = (b - c) y z, \enspace b y' = (c - a) z x, \enspace c z' = (a - b) x y 

3291 

3292 First Integrals: 

3293 

3294 .. math:: a x^{2} + b y^{2} + c z^{2} = C_1 

3295 

3296 .. math:: a^{2} x^{2} + b^{2} y^{2} + c^{2} z^{2} = C_2 

3297 

3298 where `C_1` and `C_2` are arbitrary constants. On solving the integrals for `y` and 

3299 `z` and on substituting the resulting expressions into the first equation of the 

3300 system, we arrives at a separable first-order equation on `x`. Similarly doing that 

3301 for other two equations, we will arrive at first order equation on `y` and `z` too. 

3302 

3303 References 

3304 ========== 

3305 -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0401.pdf 

3306 

3307 """ 

3308 C1, C2 = get_numbered_constants(eq, num=2) 

3309 u, v, w = symbols('u, v, w') 

3310 p = Wild('p', exclude=[x(t), y(t), z(t), t]) 

3311 q = Wild('q', exclude=[x(t), y(t), z(t), t]) 

3312 s = Wild('s', exclude=[x(t), y(t), z(t), t]) 

3313 r = (diff(x(t),t) - eq[0]).match(p*y(t)*z(t)) 

3314 r.update((diff(y(t),t) - eq[1]).match(q*z(t)*x(t))) 

3315 r.update((diff(z(t),t) - eq[2]).match(s*x(t)*y(t))) 

3316 n1, d1 = r[p].as_numer_denom() 

3317 n2, d2 = r[q].as_numer_denom() 

3318 n3, d3 = r[s].as_numer_denom() 

3319 val = solve([n1*u-d1*v+d1*w, d2*u+n2*v-d2*w, d3*u-d3*v-n3*w],[u,v]) 

3320 vals = [val[v], val[u]] 

3321 c = lcm(vals[0].as_numer_denom()[1], vals[1].as_numer_denom()[1]) 

3322 b = vals[0].subs(w, c) 

3323 a = vals[1].subs(w, c) 

3324 y_x = sqrt(((c*C1-C2) - a*(c-a)*x(t)**2)/(b*(c-b))) 

3325 z_x = sqrt(((b*C1-C2) - a*(b-a)*x(t)**2)/(c*(b-c))) 

3326 z_y = sqrt(((a*C1-C2) - b*(a-b)*y(t)**2)/(c*(a-c))) 

3327 x_y = sqrt(((c*C1-C2) - b*(c-b)*y(t)**2)/(a*(c-a))) 

3328 x_z = sqrt(((b*C1-C2) - c*(b-c)*z(t)**2)/(a*(b-a))) 

3329 y_z = sqrt(((a*C1-C2) - c*(a-c)*z(t)**2)/(b*(a-b))) 

3330 sol1 = dsolve(a*diff(x(t),t) - (b-c)*y_x*z_x) 

3331 sol2 = dsolve(b*diff(y(t),t) - (c-a)*z_y*x_y) 

3332 sol3 = dsolve(c*diff(z(t),t) - (a-b)*x_z*y_z) 

3333 return [sol1, sol2, sol3] 

3334 

3335 

3336def _nonlinear_3eq_order1_type2(x, y, z, t, eq): 

3337 r""" 

3338 Equations: 

3339 

3340 .. math:: a x' = (b - c) y z f(x, y, z, t) 

3341 

3342 .. math:: b y' = (c - a) z x f(x, y, z, t) 

3343 

3344 .. math:: c z' = (a - b) x y f(x, y, z, t) 

3345 

3346 First Integrals: 

3347 

3348 .. math:: a x^{2} + b y^{2} + c z^{2} = C_1 

3349 

3350 .. math:: a^{2} x^{2} + b^{2} y^{2} + c^{2} z^{2} = C_2 

3351 

3352 where `C_1` and `C_2` are arbitrary constants. On solving the integrals for `y` and 

3353 `z` and on substituting the resulting expressions into the first equation of the 

3354 system, we arrives at a first-order differential equations on `x`. Similarly doing 

3355 that for other two equations we will arrive at first order equation on `y` and `z`. 

3356 

3357 References 

3358 ========== 

3359 -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0402.pdf 

3360 

3361 """ 

3362 C1, C2 = get_numbered_constants(eq, num=2) 

3363 u, v, w = symbols('u, v, w') 

3364 p = Wild('p', exclude=[x(t), y(t), z(t), t]) 

3365 q = Wild('q', exclude=[x(t), y(t), z(t), t]) 

3366 s = Wild('s', exclude=[x(t), y(t), z(t), t]) 

3367 f = Wild('f') 

3368 r1 = (diff(x(t),t) - eq[0]).match(y(t)*z(t)*f) 

3369 r = collect_const(r1[f]).match(p*f) 

3370 r.update(((diff(y(t),t) - eq[1])/r[f]).match(q*z(t)*x(t))) 

3371 r.update(((diff(z(t),t) - eq[2])/r[f]).match(s*x(t)*y(t))) 

3372 n1, d1 = r[p].as_numer_denom() 

3373 n2, d2 = r[q].as_numer_denom() 

3374 n3, d3 = r[s].as_numer_denom() 

3375 val = solve([n1*u-d1*v+d1*w, d2*u+n2*v-d2*w, -d3*u+d3*v+n3*w],[u,v]) 

3376 vals = [val[v], val[u]] 

3377 c = lcm(vals[0].as_numer_denom()[1], vals[1].as_numer_denom()[1]) 

3378 a = vals[0].subs(w, c) 

3379 b = vals[1].subs(w, c) 

3380 y_x = sqrt(((c*C1-C2) - a*(c-a)*x(t)**2)/(b*(c-b))) 

3381 z_x = sqrt(((b*C1-C2) - a*(b-a)*x(t)**2)/(c*(b-c))) 

3382 z_y = sqrt(((a*C1-C2) - b*(a-b)*y(t)**2)/(c*(a-c))) 

3383 x_y = sqrt(((c*C1-C2) - b*(c-b)*y(t)**2)/(a*(c-a))) 

3384 x_z = sqrt(((b*C1-C2) - c*(b-c)*z(t)**2)/(a*(b-a))) 

3385 y_z = sqrt(((a*C1-C2) - c*(a-c)*z(t)**2)/(b*(a-b))) 

3386 sol1 = dsolve(a*diff(x(t),t) - (b-c)*y_x*z_x*r[f]) 

3387 sol2 = dsolve(b*diff(y(t),t) - (c-a)*z_y*x_y*r[f]) 

3388 sol3 = dsolve(c*diff(z(t),t) - (a-b)*x_z*y_z*r[f]) 

3389 return [sol1, sol2, sol3] 

3390 

3391def _nonlinear_3eq_order1_type3(x, y, z, t, eq): 

3392 r""" 

3393 Equations: 

3394 

3395 .. math:: x' = c F_2 - b F_3, \enspace y' = a F_3 - c F_1, \enspace z' = b F_1 - a F_2 

3396 

3397 where `F_n = F_n(x, y, z, t)`. 

3398 

3399 1. First Integral: 

3400 

3401 .. math:: a x + b y + c z = C_1, 

3402 

3403 where C is an arbitrary constant. 

3404 

3405 2. If we assume function `F_n` to be independent of `t`,i.e, `F_n` = `F_n (x, y, z)` 

3406 Then, on eliminating `t` and `z` from the first two equation of the system, one 

3407 arrives at the first-order equation 

3408 

3409 .. math:: \frac{dy}{dx} = \frac{a F_3 (x, y, z) - c F_1 (x, y, z)}{c F_2 (x, y, z) - 

3410 b F_3 (x, y, z)} 

3411 

3412 where `z = \frac{1}{c} (C_1 - a x - b y)` 

3413 

3414 References 

3415 ========== 

3416 -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0404.pdf 

3417 

3418 """ 

3419 C1 = get_numbered_constants(eq, num=1) 

3420 u, v, w = symbols('u, v, w') 

3421 fu, fv, fw = symbols('u, v, w', cls=Function) 

3422 p = Wild('p', exclude=[x(t), y(t), z(t), t]) 

3423 q = Wild('q', exclude=[x(t), y(t), z(t), t]) 

3424 s = Wild('s', exclude=[x(t), y(t), z(t), t]) 

3425 F1, F2, F3 = symbols('F1, F2, F3', cls=Wild) 

3426 r1 = (diff(x(t), t) - eq[0]).match(F2-F3) 

3427 r = collect_const(r1[F2]).match(s*F2) 

3428 r.update(collect_const(r1[F3]).match(q*F3)) 

3429 if eq[1].has(r[F2]) and not eq[1].has(r[F3]): 

3430 r[F2], r[F3] = r[F3], r[F2] 

3431 r[s], r[q] = -r[q], -r[s] 

3432 r.update((diff(y(t), t) - eq[1]).match(p*r[F3] - r[s]*F1)) 

3433 a = r[p]; b = r[q]; c = r[s] 

3434 F1 = r[F1].subs(x(t), u).subs(y(t),v).subs(z(t), w) 

3435 F2 = r[F2].subs(x(t), u).subs(y(t),v).subs(z(t), w) 

3436 F3 = r[F3].subs(x(t), u).subs(y(t),v).subs(z(t), w) 

3437 z_xy = (C1-a*u-b*v)/c 

3438 y_zx = (C1-a*u-c*w)/b 

3439 x_yz = (C1-b*v-c*w)/a 

3440 y_x = dsolve(diff(fv(u),u) - ((a*F3-c*F1)/(c*F2-b*F3)).subs(w,z_xy).subs(v,fv(u))).rhs 

3441 z_x = dsolve(diff(fw(u),u) - ((b*F1-a*F2)/(c*F2-b*F3)).subs(v,y_zx).subs(w,fw(u))).rhs 

3442 z_y = dsolve(diff(fw(v),v) - ((b*F1-a*F2)/(a*F3-c*F1)).subs(u,x_yz).subs(w,fw(v))).rhs 

3443 x_y = dsolve(diff(fu(v),v) - ((c*F2-b*F3)/(a*F3-c*F1)).subs(w,z_xy).subs(u,fu(v))).rhs 

3444 y_z = dsolve(diff(fv(w),w) - ((a*F3-c*F1)/(b*F1-a*F2)).subs(u,x_yz).subs(v,fv(w))).rhs 

3445 x_z = dsolve(diff(fu(w),w) - ((c*F2-b*F3)/(b*F1-a*F2)).subs(v,y_zx).subs(u,fu(w))).rhs 

3446 sol1 = dsolve(diff(fu(t),t) - (c*F2 - b*F3).subs(v,y_x).subs(w,z_x).subs(u,fu(t))).rhs 

3447 sol2 = dsolve(diff(fv(t),t) - (a*F3 - c*F1).subs(u,x_y).subs(w,z_y).subs(v,fv(t))).rhs 

3448 sol3 = dsolve(diff(fw(t),t) - (b*F1 - a*F2).subs(u,x_z).subs(v,y_z).subs(w,fw(t))).rhs 

3449 return [sol1, sol2, sol3] 

3450 

3451def _nonlinear_3eq_order1_type4(x, y, z, t, eq): 

3452 r""" 

3453 Equations: 

3454 

3455 .. math:: x' = c z F_2 - b y F_3, \enspace y' = a x F_3 - c z F_1, \enspace z' = b y F_1 - a x F_2 

3456 

3457 where `F_n = F_n (x, y, z, t)` 

3458 

3459 1. First integral: 

3460 

3461 .. math:: a x^{2} + b y^{2} + c z^{2} = C_1 

3462 

3463 where `C` is an arbitrary constant. 

3464 

3465 2. Assuming the function `F_n` is independent of `t`: `F_n = F_n (x, y, z)`. Then on 

3466 eliminating `t` and `z` from the first two equations of the system, one arrives at 

3467 the first-order equation 

3468 

3469 .. math:: \frac{dy}{dx} = \frac{a x F_3 (x, y, z) - c z F_1 (x, y, z)} 

3470 {c z F_2 (x, y, z) - b y F_3 (x, y, z)} 

3471 

3472 where `z = \pm \sqrt{\frac{1}{c} (C_1 - a x^{2} - b y^{2})}` 

3473 

3474 References 

3475 ========== 

3476 -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0405.pdf 

3477 

3478 """ 

3479 C1 = get_numbered_constants(eq, num=1) 

3480 u, v, w = symbols('u, v, w') 

3481 p = Wild('p', exclude=[x(t), y(t), z(t), t]) 

3482 q = Wild('q', exclude=[x(t), y(t), z(t), t]) 

3483 s = Wild('s', exclude=[x(t), y(t), z(t), t]) 

3484 F1, F2, F3 = symbols('F1, F2, F3', cls=Wild) 

3485 r1 = eq[0].match(diff(x(t),t) - z(t)*F2 + y(t)*F3) 

3486 r = collect_const(r1[F2]).match(s*F2) 

3487 r.update(collect_const(r1[F3]).match(q*F3)) 

3488 if eq[1].has(r[F2]) and not eq[1].has(r[F3]): 

3489 r[F2], r[F3] = r[F3], r[F2] 

3490 r[s], r[q] = -r[q], -r[s] 

3491 r.update((diff(y(t),t) - eq[1]).match(p*x(t)*r[F3] - r[s]*z(t)*F1)) 

3492 a = r[p]; b = r[q]; c = r[s] 

3493 F1 = r[F1].subs(x(t),u).subs(y(t),v).subs(z(t),w) 

3494 F2 = r[F2].subs(x(t),u).subs(y(t),v).subs(z(t),w) 

3495 F3 = r[F3].subs(x(t),u).subs(y(t),v).subs(z(t),w) 

3496 x_yz = sqrt((C1 - b*v**2 - c*w**2)/a) 

3497 y_zx = sqrt((C1 - c*w**2 - a*u**2)/b) 

3498 z_xy = sqrt((C1 - a*u**2 - b*v**2)/c) 

3499 y_x = dsolve(diff(v(u),u) - ((a*u*F3-c*w*F1)/(c*w*F2-b*v*F3)).subs(w,z_xy).subs(v,v(u))).rhs 

3500 z_x = dsolve(diff(w(u),u) - ((b*v*F1-a*u*F2)/(c*w*F2-b*v*F3)).subs(v,y_zx).subs(w,w(u))).rhs 

3501 z_y = dsolve(diff(w(v),v) - ((b*v*F1-a*u*F2)/(a*u*F3-c*w*F1)).subs(u,x_yz).subs(w,w(v))).rhs 

3502 x_y = dsolve(diff(u(v),v) - ((c*w*F2-b*v*F3)/(a*u*F3-c*w*F1)).subs(w,z_xy).subs(u,u(v))).rhs 

3503 y_z = dsolve(diff(v(w),w) - ((a*u*F3-c*w*F1)/(b*v*F1-a*u*F2)).subs(u,x_yz).subs(v,v(w))).rhs 

3504 x_z = dsolve(diff(u(w),w) - ((c*w*F2-b*v*F3)/(b*v*F1-a*u*F2)).subs(v,y_zx).subs(u,u(w))).rhs 

3505 sol1 = dsolve(diff(u(t),t) - (c*w*F2 - b*v*F3).subs(v,y_x).subs(w,z_x).subs(u,u(t))).rhs 

3506 sol2 = dsolve(diff(v(t),t) - (a*u*F3 - c*w*F1).subs(u,x_y).subs(w,z_y).subs(v,v(t))).rhs 

3507 sol3 = dsolve(diff(w(t),t) - (b*v*F1 - a*u*F2).subs(u,x_z).subs(v,y_z).subs(w,w(t))).rhs 

3508 return [sol1, sol2, sol3] 

3509 

3510def _nonlinear_3eq_order1_type5(x, y, z, t, eq): 

3511 r""" 

3512 .. math:: x' = x (c F_2 - b F_3), \enspace y' = y (a F_3 - c F_1), \enspace z' = z (b F_1 - a F_2) 

3513 

3514 where `F_n = F_n (x, y, z, t)` and are arbitrary functions. 

3515 

3516 First Integral: 

3517 

3518 .. math:: \left|x\right|^{a} \left|y\right|^{b} \left|z\right|^{c} = C_1 

3519 

3520 where `C` is an arbitrary constant. If the function `F_n` is independent of `t`, 

3521 then, by eliminating `t` and `z` from the first two equations of the system, one 

3522 arrives at a first-order equation. 

3523 

3524 References 

3525 ========== 

3526 -https://eqworld.ipmnet.ru/en/solutions/sysode/sode0406.pdf 

3527 

3528 """ 

3529 C1 = get_numbered_constants(eq, num=1) 

3530 u, v, w = symbols('u, v, w') 

3531 fu, fv, fw = symbols('u, v, w', cls=Function) 

3532 p = Wild('p', exclude=[x(t), y(t), z(t), t]) 

3533 q = Wild('q', exclude=[x(t), y(t), z(t), t]) 

3534 s = Wild('s', exclude=[x(t), y(t), z(t), t]) 

3535 F1, F2, F3 = symbols('F1, F2, F3', cls=Wild) 

3536 r1 = eq[0].match(diff(x(t), t) - x(t)*F2 + x(t)*F3) 

3537 r = collect_const(r1[F2]).match(s*F2) 

3538 r.update(collect_const(r1[F3]).match(q*F3)) 

3539 if eq[1].has(r[F2]) and not eq[1].has(r[F3]): 

3540 r[F2], r[F3] = r[F3], r[F2] 

3541 r[s], r[q] = -r[q], -r[s] 

3542 r.update((diff(y(t), t) - eq[1]).match(y(t)*(p*r[F3] - r[s]*F1))) 

3543 a = r[p]; b = r[q]; c = r[s] 

3544 F1 = r[F1].subs(x(t), u).subs(y(t), v).subs(z(t), w) 

3545 F2 = r[F2].subs(x(t), u).subs(y(t), v).subs(z(t), w) 

3546 F3 = r[F3].subs(x(t), u).subs(y(t), v).subs(z(t), w) 

3547 x_yz = (C1*v**-b*w**-c)**-a 

3548 y_zx = (C1*w**-c*u**-a)**-b 

3549 z_xy = (C1*u**-a*v**-b)**-c 

3550 y_x = dsolve(diff(fv(u), u) - ((v*(a*F3 - c*F1))/(u*(c*F2 - b*F3))).subs(w, z_xy).subs(v, fv(u))).rhs 

3551 z_x = dsolve(diff(fw(u), u) - ((w*(b*F1 - a*F2))/(u*(c*F2 - b*F3))).subs(v, y_zx).subs(w, fw(u))).rhs 

3552 z_y = dsolve(diff(fw(v), v) - ((w*(b*F1 - a*F2))/(v*(a*F3 - c*F1))).subs(u, x_yz).subs(w, fw(v))).rhs 

3553 x_y = dsolve(diff(fu(v), v) - ((u*(c*F2 - b*F3))/(v*(a*F3 - c*F1))).subs(w, z_xy).subs(u, fu(v))).rhs 

3554 y_z = dsolve(diff(fv(w), w) - ((v*(a*F3 - c*F1))/(w*(b*F1 - a*F2))).subs(u, x_yz).subs(v, fv(w))).rhs 

3555 x_z = dsolve(diff(fu(w), w) - ((u*(c*F2 - b*F3))/(w*(b*F1 - a*F2))).subs(v, y_zx).subs(u, fu(w))).rhs 

3556 sol1 = dsolve(diff(fu(t), t) - (u*(c*F2 - b*F3)).subs(v, y_x).subs(w, z_x).subs(u, fu(t))).rhs 

3557 sol2 = dsolve(diff(fv(t), t) - (v*(a*F3 - c*F1)).subs(u, x_y).subs(w, z_y).subs(v, fv(t))).rhs 

3558 sol3 = dsolve(diff(fw(t), t) - (w*(b*F1 - a*F2)).subs(u, x_z).subs(v, y_z).subs(w, fw(t))).rhs 

3559 return [sol1, sol2, sol3] 

3560 

3561 

3562#This import is written at the bottom to avoid circular imports. 

3563from .single import SingleODEProblem, SingleODESolver, solver_map