Coverage for /usr/lib/python3/dist-packages/sympy/solvers/ode/lie_group.py: 5%
588 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1r"""
2This module contains the implementation of the internal helper functions for the lie_group hint for
3dsolve. These helper functions apply different heuristics on the given equation
4and return the solution. These functions are used by :py:meth:`sympy.solvers.ode.single.LieGroup`
6References
7=========
9- `abaco1_simple`, `function_sum` and `chi` are referenced from E.S Cheb-Terrab, L.G.S Duarte
10and L.A,C.P da Mota, Computer Algebra Solving of First Order ODEs Using
11Symmetry Methods, pp. 7 - pp. 8
13- `abaco1_product`, `abaco2_similar`, `abaco2_unique_unknown`, `linear` and `abaco2_unique_general`
14are referenced from E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
15ODE Patterns, pp. 7 - pp. 12
17- `bivariate` from Lie Groups and Differential Equations pp. 327 - pp. 329
19"""
20from itertools import islice
22from sympy.core import Add, S, Mul, Pow
23from sympy.core.exprtools import factor_terms
24from sympy.core.function import Function, AppliedUndef, expand
25from sympy.core.relational import Equality, Eq
26from sympy.core.symbol import Symbol, Wild, Dummy, symbols
27from sympy.functions import exp, log
28from sympy.integrals.integrals import integrate
29from sympy.polys import Poly
30from sympy.polys.polytools import cancel, div
31from sympy.simplify import (collect, powsimp, # type: ignore
32 separatevars, simplify)
33from sympy.solvers import solve
34from sympy.solvers.pde import pdsolve
36from sympy.utilities import numbered_symbols
37from sympy.solvers.deutils import _preprocess, ode_order
38from .ode import checkinfsol
41lie_heuristics = (
42 "abaco1_simple",
43 "abaco1_product",
44 "abaco2_similar",
45 "abaco2_unique_unknown",
46 "abaco2_unique_general",
47 "linear",
48 "function_sum",
49 "bivariate",
50 "chi"
51 )
54def _ode_lie_group_try_heuristic(eq, heuristic, func, match, inf):
56 xi = Function("xi")
57 eta = Function("eta")
58 f = func.func
59 x = func.args[0]
60 y = match['y']
61 h = match['h']
62 tempsol = []
63 if not inf:
64 try:
65 inf = infinitesimals(eq, hint=heuristic, func=func, order=1, match=match)
66 except ValueError:
67 return None
68 for infsim in inf:
69 xiinf = (infsim[xi(x, func)]).subs(func, y)
70 etainf = (infsim[eta(x, func)]).subs(func, y)
71 # This condition creates recursion while using pdsolve.
72 # Since the first step while solving a PDE of form
73 # a*(f(x, y).diff(x)) + b*(f(x, y).diff(y)) + c = 0
74 # is to solve the ODE dy/dx = b/a
75 if simplify(etainf/xiinf) == h:
76 continue
77 rpde = f(x, y).diff(x)*xiinf + f(x, y).diff(y)*etainf
78 r = pdsolve(rpde, func=f(x, y)).rhs
79 s = pdsolve(rpde - 1, func=f(x, y)).rhs
80 newcoord = [_lie_group_remove(coord) for coord in [r, s]]
81 r = Dummy("r")
82 s = Dummy("s")
83 C1 = Symbol("C1")
84 rcoord = newcoord[0]
85 scoord = newcoord[-1]
86 try:
87 sol = solve([r - rcoord, s - scoord], x, y, dict=True)
88 if sol == []:
89 continue
90 except NotImplementedError:
91 continue
92 else:
93 sol = sol[0]
94 xsub = sol[x]
95 ysub = sol[y]
96 num = simplify(scoord.diff(x) + scoord.diff(y)*h)
97 denom = simplify(rcoord.diff(x) + rcoord.diff(y)*h)
98 if num and denom:
99 diffeq = simplify((num/denom).subs([(x, xsub), (y, ysub)]))
100 sep = separatevars(diffeq, symbols=[r, s], dict=True)
101 if sep:
102 # Trying to separate, r and s coordinates
103 deq = integrate((1/sep[s]), s) + C1 - integrate(sep['coeff']*sep[r], r)
104 # Substituting and reverting back to original coordinates
105 deq = deq.subs([(r, rcoord), (s, scoord)])
106 try:
107 sdeq = solve(deq, y)
108 except NotImplementedError:
109 tempsol.append(deq)
110 else:
111 return [Eq(f(x), sol) for sol in sdeq]
114 elif denom: # (ds/dr) is zero which means s is constant
115 return [Eq(f(x), solve(scoord - C1, y)[0])]
117 elif num: # (dr/ds) is zero which means r is constant
118 return [Eq(f(x), solve(rcoord - C1, y)[0])]
120 # If nothing works, return solution as it is, without solving for y
121 if tempsol:
122 return [Eq(sol.subs(y, f(x)), 0) for sol in tempsol]
123 return None
126def _ode_lie_group( s, func, order, match):
128 heuristics = lie_heuristics
129 inf = {}
130 f = func.func
131 x = func.args[0]
132 df = func.diff(x)
133 xi = Function("xi")
134 eta = Function("eta")
135 xis = match['xi']
136 etas = match['eta']
137 y = match.pop('y', None)
138 if y:
139 h = -simplify(match[match['d']]/match[match['e']])
140 y = y
141 else:
142 y = Dummy("y")
143 h = s.subs(func, y)
145 if xis is not None and etas is not None:
146 inf = [{xi(x, f(x)): S(xis), eta(x, f(x)): S(etas)}]
148 if checkinfsol(Eq(df, s), inf, func=f(x), order=1)[0][0]:
149 heuristics = ["user_defined"] + list(heuristics)
151 match = {'h': h, 'y': y}
153 # This is done so that if any heuristic raises a ValueError
154 # another heuristic can be used.
155 sol = None
156 for heuristic in heuristics:
157 sol = _ode_lie_group_try_heuristic(Eq(df, s), heuristic, func, match, inf)
158 if sol:
159 return sol
160 return sol
163def infinitesimals(eq, func=None, order=None, hint='default', match=None):
164 r"""
165 The infinitesimal functions of an ordinary differential equation, `\xi(x,y)`
166 and `\eta(x,y)`, are the infinitesimals of the Lie group of point transformations
167 for which the differential equation is invariant. So, the ODE `y'=f(x,y)`
168 would admit a Lie group `x^*=X(x,y;\varepsilon)=x+\varepsilon\xi(x,y)`,
169 `y^*=Y(x,y;\varepsilon)=y+\varepsilon\eta(x,y)` such that `(y^*)'=f(x^*, y^*)`.
170 A change of coordinates, to `r(x,y)` and `s(x,y)`, can be performed so this Lie group
171 becomes the translation group, `r^*=r` and `s^*=s+\varepsilon`.
172 They are tangents to the coordinate curves of the new system.
174 Consider the transformation `(x, y) \to (X, Y)` such that the
175 differential equation remains invariant. `\xi` and `\eta` are the tangents to
176 the transformed coordinates `X` and `Y`, at `\varepsilon=0`.
178 .. math:: \left(\frac{\partial X(x,y;\varepsilon)}{\partial\varepsilon
179 }\right)|_{\varepsilon=0} = \xi,
180 \left(\frac{\partial Y(x,y;\varepsilon)}{\partial\varepsilon
181 }\right)|_{\varepsilon=0} = \eta,
183 The infinitesimals can be found by solving the following PDE:
185 >>> from sympy import Function, Eq, pprint
186 >>> from sympy.abc import x, y
187 >>> xi, eta, h = map(Function, ['xi', 'eta', 'h'])
188 >>> h = h(x, y) # dy/dx = h
189 >>> eta = eta(x, y)
190 >>> xi = xi(x, y)
191 >>> genform = Eq(eta.diff(x) + (eta.diff(y) - xi.diff(x))*h
192 ... - (xi.diff(y))*h**2 - xi*(h.diff(x)) - eta*(h.diff(y)), 0)
193 >>> pprint(genform)
194 /d d \ d 2 d
195 |--(eta(x, y)) - --(xi(x, y))|*h(x, y) - eta(x, y)*--(h(x, y)) - h (x, y)*--(x
196 \dy dx / dy dy
197 <BLANKLINE>
198 d d
199 i(x, y)) - xi(x, y)*--(h(x, y)) + --(eta(x, y)) = 0
200 dx dx
202 Solving the above mentioned PDE is not trivial, and can be solved only by
203 making intelligent assumptions for `\xi` and `\eta` (heuristics). Once an
204 infinitesimal is found, the attempt to find more heuristics stops. This is done to
205 optimise the speed of solving the differential equation. If a list of all the
206 infinitesimals is needed, ``hint`` should be flagged as ``all``, which gives
207 the complete list of infinitesimals. If the infinitesimals for a particular
208 heuristic needs to be found, it can be passed as a flag to ``hint``.
210 Examples
211 ========
213 >>> from sympy import Function
214 >>> from sympy.solvers.ode.lie_group import infinitesimals
215 >>> from sympy.abc import x
216 >>> f = Function('f')
217 >>> eq = f(x).diff(x) - x**2*f(x)
218 >>> infinitesimals(eq)
219 [{eta(x, f(x)): exp(x**3/3), xi(x, f(x)): 0}]
221 References
222 ==========
224 - Solving differential equations by Symmetry Groups,
225 John Starrett, pp. 1 - pp. 14
227 """
229 if isinstance(eq, Equality):
230 eq = eq.lhs - eq.rhs
231 if not func:
232 eq, func = _preprocess(eq)
233 variables = func.args
234 if len(variables) != 1:
235 raise ValueError("ODE's have only one independent variable")
236 else:
237 x = variables[0]
238 if not order:
239 order = ode_order(eq, func)
240 if order != 1:
241 raise NotImplementedError("Infinitesimals for only "
242 "first order ODE's have been implemented")
243 else:
244 df = func.diff(x)
245 # Matching differential equation of the form a*df + b
246 a = Wild('a', exclude = [df])
247 b = Wild('b', exclude = [df])
248 if match: # Used by lie_group hint
249 h = match['h']
250 y = match['y']
251 else:
252 match = collect(expand(eq), df).match(a*df + b)
253 if match:
254 h = -simplify(match[b]/match[a])
255 else:
256 try:
257 sol = solve(eq, df)
258 except NotImplementedError:
259 raise NotImplementedError("Infinitesimals for the "
260 "first order ODE could not be found")
261 else:
262 h = sol[0] # Find infinitesimals for one solution
263 y = Dummy("y")
264 h = h.subs(func, y)
266 u = Dummy("u")
267 hx = h.diff(x)
268 hy = h.diff(y)
269 hinv = ((1/h).subs([(x, u), (y, x)])).subs(u, y) # Inverse ODE
270 match = {'h': h, 'func': func, 'hx': hx, 'hy': hy, 'y': y, 'hinv': hinv}
271 if hint == 'all':
272 xieta = []
273 for heuristic in lie_heuristics:
274 function = globals()['lie_heuristic_' + heuristic]
275 inflist = function(match, comp=True)
276 if inflist:
277 xieta.extend([inf for inf in inflist if inf not in xieta])
278 if xieta:
279 return xieta
280 else:
281 raise NotImplementedError("Infinitesimals could not be found for "
282 "the given ODE")
284 elif hint == 'default':
285 for heuristic in lie_heuristics:
286 function = globals()['lie_heuristic_' + heuristic]
287 xieta = function(match, comp=False)
288 if xieta:
289 return xieta
291 raise NotImplementedError("Infinitesimals could not be found for"
292 " the given ODE")
294 elif hint not in lie_heuristics:
295 raise ValueError("Heuristic not recognized: " + hint)
297 else:
298 function = globals()['lie_heuristic_' + hint]
299 xieta = function(match, comp=True)
300 if xieta:
301 return xieta
302 else:
303 raise ValueError("Infinitesimals could not be found using the"
304 " given heuristic")
307def lie_heuristic_abaco1_simple(match, comp=False):
308 r"""
309 The first heuristic uses the following four sets of
310 assumptions on `\xi` and `\eta`
312 .. math:: \xi = 0, \eta = f(x)
314 .. math:: \xi = 0, \eta = f(y)
316 .. math:: \xi = f(x), \eta = 0
318 .. math:: \xi = f(y), \eta = 0
320 The success of this heuristic is determined by algebraic factorisation.
321 For the first assumption `\xi = 0` and `\eta` to be a function of `x`, the PDE
323 .. math:: \frac{\partial \eta}{\partial x} + (\frac{\partial \eta}{\partial y}
324 - \frac{\partial \xi}{\partial x})*h
325 - \frac{\partial \xi}{\partial y}*h^{2}
326 - \xi*\frac{\partial h}{\partial x} - \eta*\frac{\partial h}{\partial y} = 0
328 reduces to `f'(x) - f\frac{\partial h}{\partial y} = 0`
329 If `\frac{\partial h}{\partial y}` is a function of `x`, then this can usually
330 be integrated easily. A similar idea is applied to the other 3 assumptions as well.
333 References
334 ==========
336 - E.S Cheb-Terrab, L.G.S Duarte and L.A,C.P da Mota, Computer Algebra
337 Solving of First Order ODEs Using Symmetry Methods, pp. 8
340 """
342 xieta = []
343 y = match['y']
344 h = match['h']
345 func = match['func']
346 x = func.args[0]
347 hx = match['hx']
348 hy = match['hy']
349 xi = Function('xi')(x, func)
350 eta = Function('eta')(x, func)
352 hysym = hy.free_symbols
353 if y not in hysym:
354 try:
355 fx = exp(integrate(hy, x))
356 except NotImplementedError:
357 pass
358 else:
359 inf = {xi: S.Zero, eta: fx}
360 if not comp:
361 return [inf]
362 if comp and inf not in xieta:
363 xieta.append(inf)
365 factor = hy/h
366 facsym = factor.free_symbols
367 if x not in facsym:
368 try:
369 fy = exp(integrate(factor, y))
370 except NotImplementedError:
371 pass
372 else:
373 inf = {xi: S.Zero, eta: fy.subs(y, func)}
374 if not comp:
375 return [inf]
376 if comp and inf not in xieta:
377 xieta.append(inf)
379 factor = -hx/h
380 facsym = factor.free_symbols
381 if y not in facsym:
382 try:
383 fx = exp(integrate(factor, x))
384 except NotImplementedError:
385 pass
386 else:
387 inf = {xi: fx, eta: S.Zero}
388 if not comp:
389 return [inf]
390 if comp and inf not in xieta:
391 xieta.append(inf)
393 factor = -hx/(h**2)
394 facsym = factor.free_symbols
395 if x not in facsym:
396 try:
397 fy = exp(integrate(factor, y))
398 except NotImplementedError:
399 pass
400 else:
401 inf = {xi: fy.subs(y, func), eta: S.Zero}
402 if not comp:
403 return [inf]
404 if comp and inf not in xieta:
405 xieta.append(inf)
407 if xieta:
408 return xieta
410def lie_heuristic_abaco1_product(match, comp=False):
411 r"""
412 The second heuristic uses the following two assumptions on `\xi` and `\eta`
414 .. math:: \eta = 0, \xi = f(x)*g(y)
416 .. math:: \eta = f(x)*g(y), \xi = 0
418 The first assumption of this heuristic holds good if
419 `\frac{1}{h^{2}}\frac{\partial^2}{\partial x \partial y}\log(h)` is
420 separable in `x` and `y`, then the separated factors containing `x`
421 is `f(x)`, and `g(y)` is obtained by
423 .. math:: e^{\int f\frac{\partial}{\partial x}\left(\frac{1}{f*h}\right)\,dy}
425 provided `f\frac{\partial}{\partial x}\left(\frac{1}{f*h}\right)` is a function
426 of `y` only.
428 The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as
429 `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first assumption
430 satisfies. After obtaining `f(x)` and `g(y)`, the coordinates are again
431 interchanged, to get `\eta` as `f(x)*g(y)`
434 References
435 ==========
436 - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
437 ODE Patterns, pp. 7 - pp. 8
439 """
441 xieta = []
442 y = match['y']
443 h = match['h']
444 hinv = match['hinv']
445 func = match['func']
446 x = func.args[0]
447 xi = Function('xi')(x, func)
448 eta = Function('eta')(x, func)
451 inf = separatevars(((log(h).diff(y)).diff(x))/h**2, dict=True, symbols=[x, y])
452 if inf and inf['coeff']:
453 fx = inf[x]
454 gy = simplify(fx*((1/(fx*h)).diff(x)))
455 gysyms = gy.free_symbols
456 if x not in gysyms:
457 gy = exp(integrate(gy, y))
458 inf = {eta: S.Zero, xi: (fx*gy).subs(y, func)}
459 if not comp:
460 return [inf]
461 if comp and inf not in xieta:
462 xieta.append(inf)
464 u1 = Dummy("u1")
465 inf = separatevars(((log(hinv).diff(y)).diff(x))/hinv**2, dict=True, symbols=[x, y])
466 if inf and inf['coeff']:
467 fx = inf[x]
468 gy = simplify(fx*((1/(fx*hinv)).diff(x)))
469 gysyms = gy.free_symbols
470 if x not in gysyms:
471 gy = exp(integrate(gy, y))
472 etaval = fx*gy
473 etaval = (etaval.subs([(x, u1), (y, x)])).subs(u1, y)
474 inf = {eta: etaval.subs(y, func), xi: S.Zero}
475 if not comp:
476 return [inf]
477 if comp and inf not in xieta:
478 xieta.append(inf)
480 if xieta:
481 return xieta
483def lie_heuristic_bivariate(match, comp=False):
484 r"""
485 The third heuristic assumes the infinitesimals `\xi` and `\eta`
486 to be bi-variate polynomials in `x` and `y`. The assumption made here
487 for the logic below is that `h` is a rational function in `x` and `y`
488 though that may not be necessary for the infinitesimals to be
489 bivariate polynomials. The coefficients of the infinitesimals
490 are found out by substituting them in the PDE and grouping similar terms
491 that are polynomials and since they form a linear system, solve and check
492 for non trivial solutions. The degree of the assumed bivariates
493 are increased till a certain maximum value.
495 References
496 ==========
497 - Lie Groups and Differential Equations
498 pp. 327 - pp. 329
500 """
502 h = match['h']
503 hx = match['hx']
504 hy = match['hy']
505 func = match['func']
506 x = func.args[0]
507 y = match['y']
508 xi = Function('xi')(x, func)
509 eta = Function('eta')(x, func)
511 if h.is_rational_function():
512 # The maximum degree that the infinitesimals can take is
513 # calculated by this technique.
514 etax, etay, etad, xix, xiy, xid = symbols("etax etay etad xix xiy xid")
515 ipde = etax + (etay - xix)*h - xiy*h**2 - xid*hx - etad*hy
516 num, denom = cancel(ipde).as_numer_denom()
517 deg = Poly(num, x, y).total_degree()
518 deta = Function('deta')(x, y)
519 dxi = Function('dxi')(x, y)
520 ipde = (deta.diff(x) + (deta.diff(y) - dxi.diff(x))*h - (dxi.diff(y))*h**2
521 - dxi*hx - deta*hy)
522 xieq = Symbol("xi0")
523 etaeq = Symbol("eta0")
525 for i in range(deg + 1):
526 if i:
527 xieq += Add(*[
528 Symbol("xi_" + str(power) + "_" + str(i - power))*x**power*y**(i - power)
529 for power in range(i + 1)])
530 etaeq += Add(*[
531 Symbol("eta_" + str(power) + "_" + str(i - power))*x**power*y**(i - power)
532 for power in range(i + 1)])
533 pden, denom = (ipde.subs({dxi: xieq, deta: etaeq}).doit()).as_numer_denom()
534 pden = expand(pden)
536 # If the individual terms are monomials, the coefficients
537 # are grouped
538 if pden.is_polynomial(x, y) and pden.is_Add:
539 polyy = Poly(pden, x, y).as_dict()
540 if polyy:
541 symset = xieq.free_symbols.union(etaeq.free_symbols) - {x, y}
542 soldict = solve(polyy.values(), *symset)
543 if isinstance(soldict, list):
544 soldict = soldict[0]
545 if any(soldict.values()):
546 xired = xieq.subs(soldict)
547 etared = etaeq.subs(soldict)
548 # Scaling is done by substituting one for the parameters
549 # This can be any number except zero.
550 dict_ = {sym: 1 for sym in symset}
551 inf = {eta: etared.subs(dict_).subs(y, func),
552 xi: xired.subs(dict_).subs(y, func)}
553 return [inf]
555def lie_heuristic_chi(match, comp=False):
556 r"""
557 The aim of the fourth heuristic is to find the function `\chi(x, y)`
558 that satisfies the PDE `\frac{d\chi}{dx} + h\frac{d\chi}{dx}
559 - \frac{\partial h}{\partial y}\chi = 0`.
561 This assumes `\chi` to be a bivariate polynomial in `x` and `y`. By intuition,
562 `h` should be a rational function in `x` and `y`. The method used here is
563 to substitute a general binomial for `\chi` up to a certain maximum degree
564 is reached. The coefficients of the polynomials, are calculated by by collecting
565 terms of the same order in `x` and `y`.
567 After finding `\chi`, the next step is to use `\eta = \xi*h + \chi`, to
568 determine `\xi` and `\eta`. This can be done by dividing `\chi` by `h`
569 which would give `-\xi` as the quotient and `\eta` as the remainder.
572 References
573 ==========
574 - E.S Cheb-Terrab, L.G.S Duarte and L.A,C.P da Mota, Computer Algebra
575 Solving of First Order ODEs Using Symmetry Methods, pp. 8
577 """
579 h = match['h']
580 hy = match['hy']
581 func = match['func']
582 x = func.args[0]
583 y = match['y']
584 xi = Function('xi')(x, func)
585 eta = Function('eta')(x, func)
587 if h.is_rational_function():
588 schi, schix, schiy = symbols("schi, schix, schiy")
589 cpde = schix + h*schiy - hy*schi
590 num, denom = cancel(cpde).as_numer_denom()
591 deg = Poly(num, x, y).total_degree()
593 chi = Function('chi')(x, y)
594 chix = chi.diff(x)
595 chiy = chi.diff(y)
596 cpde = chix + h*chiy - hy*chi
597 chieq = Symbol("chi")
598 for i in range(1, deg + 1):
599 chieq += Add(*[
600 Symbol("chi_" + str(power) + "_" + str(i - power))*x**power*y**(i - power)
601 for power in range(i + 1)])
602 cnum, cden = cancel(cpde.subs({chi : chieq}).doit()).as_numer_denom()
603 cnum = expand(cnum)
604 if cnum.is_polynomial(x, y) and cnum.is_Add:
605 cpoly = Poly(cnum, x, y).as_dict()
606 if cpoly:
607 solsyms = chieq.free_symbols - {x, y}
608 soldict = solve(cpoly.values(), *solsyms)
609 if isinstance(soldict, list):
610 soldict = soldict[0]
611 if any(soldict.values()):
612 chieq = chieq.subs(soldict)
613 dict_ = {sym: 1 for sym in solsyms}
614 chieq = chieq.subs(dict_)
615 # After finding chi, the main aim is to find out
616 # eta, xi by the equation eta = xi*h + chi
617 # One method to set xi, would be rearranging it to
618 # (eta/h) - xi = (chi/h). This would mean dividing
619 # chi by h would give -xi as the quotient and eta
620 # as the remainder. Thanks to Sean Vig for suggesting
621 # this method.
622 xic, etac = div(chieq, h)
623 inf = {eta: etac.subs(y, func), xi: -xic.subs(y, func)}
624 return [inf]
626def lie_heuristic_function_sum(match, comp=False):
627 r"""
628 This heuristic uses the following two assumptions on `\xi` and `\eta`
630 .. math:: \eta = 0, \xi = f(x) + g(y)
632 .. math:: \eta = f(x) + g(y), \xi = 0
634 The first assumption of this heuristic holds good if
636 .. math:: \frac{\partial}{\partial y}[(h\frac{\partial^{2}}{
637 \partial x^{2}}(h^{-1}))^{-1}]
639 is separable in `x` and `y`,
641 1. The separated factors containing `y` is `\frac{\partial g}{\partial y}`.
642 From this `g(y)` can be determined.
643 2. The separated factors containing `x` is `f''(x)`.
644 3. `h\frac{\partial^{2}}{\partial x^{2}}(h^{-1})` equals
645 `\frac{f''(x)}{f(x) + g(y)}`. From this `f(x)` can be determined.
647 The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as
648 `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first
649 assumption satisfies. After obtaining `f(x)` and `g(y)`, the coordinates
650 are again interchanged, to get `\eta` as `f(x) + g(y)`.
652 For both assumptions, the constant factors are separated among `g(y)`
653 and `f''(x)`, such that `f''(x)` obtained from 3] is the same as that
654 obtained from 2]. If not possible, then this heuristic fails.
657 References
658 ==========
659 - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
660 ODE Patterns, pp. 7 - pp. 8
662 """
664 xieta = []
665 h = match['h']
666 func = match['func']
667 hinv = match['hinv']
668 x = func.args[0]
669 y = match['y']
670 xi = Function('xi')(x, func)
671 eta = Function('eta')(x, func)
673 for odefac in [h, hinv]:
674 factor = odefac*((1/odefac).diff(x, 2))
675 sep = separatevars((1/factor).diff(y), dict=True, symbols=[x, y])
676 if sep and sep['coeff'] and sep[x].has(x) and sep[y].has(y):
677 k = Dummy("k")
678 try:
679 gy = k*integrate(sep[y], y)
680 except NotImplementedError:
681 pass
682 else:
683 fdd = 1/(k*sep[x]*sep['coeff'])
684 fx = simplify(fdd/factor - gy)
685 check = simplify(fx.diff(x, 2) - fdd)
686 if fx:
687 if not check:
688 fx = fx.subs(k, 1)
689 gy = (gy/k)
690 else:
691 sol = solve(check, k)
692 if sol:
693 sol = sol[0]
694 fx = fx.subs(k, sol)
695 gy = (gy/k)*sol
696 else:
697 continue
698 if odefac == hinv: # Inverse ODE
699 fx = fx.subs(x, y)
700 gy = gy.subs(y, x)
701 etaval = factor_terms(fx + gy)
702 if etaval.is_Mul:
703 etaval = Mul(*[arg for arg in etaval.args if arg.has(x, y)])
704 if odefac == hinv: # Inverse ODE
705 inf = {eta: etaval.subs(y, func), xi : S.Zero}
706 else:
707 inf = {xi: etaval.subs(y, func), eta : S.Zero}
708 if not comp:
709 return [inf]
710 else:
711 xieta.append(inf)
713 if xieta:
714 return xieta
716def lie_heuristic_abaco2_similar(match, comp=False):
717 r"""
718 This heuristic uses the following two assumptions on `\xi` and `\eta`
720 .. math:: \eta = g(x), \xi = f(x)
722 .. math:: \eta = f(y), \xi = g(y)
724 For the first assumption,
726 1. First `\frac{\frac{\partial h}{\partial y}}{\frac{\partial^{2} h}{
727 \partial yy}}` is calculated. Let us say this value is A
729 2. If this is constant, then `h` is matched to the form `A(x) + B(x)e^{
730 \frac{y}{C}}` then, `\frac{e^{\int \frac{A(x)}{C} \,dx}}{B(x)}` gives `f(x)`
731 and `A(x)*f(x)` gives `g(x)`
733 3. Otherwise `\frac{\frac{\partial A}{\partial X}}{\frac{\partial A}{
734 \partial Y}} = \gamma` is calculated. If
736 a] `\gamma` is a function of `x` alone
738 b] `\frac{\gamma\frac{\partial h}{\partial y} - \gamma'(x) - \frac{
739 \partial h}{\partial x}}{h + \gamma} = G` is a function of `x` alone.
740 then, `e^{\int G \,dx}` gives `f(x)` and `-\gamma*f(x)` gives `g(x)`
742 The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as
743 `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first assumption
744 satisfies. After obtaining `f(x)` and `g(x)`, the coordinates are again
745 interchanged, to get `\xi` as `f(x^*)` and `\eta` as `g(y^*)`
747 References
748 ==========
749 - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
750 ODE Patterns, pp. 10 - pp. 12
752 """
754 h = match['h']
755 hx = match['hx']
756 hy = match['hy']
757 func = match['func']
758 hinv = match['hinv']
759 x = func.args[0]
760 y = match['y']
761 xi = Function('xi')(x, func)
762 eta = Function('eta')(x, func)
764 factor = cancel(h.diff(y)/h.diff(y, 2))
765 factorx = factor.diff(x)
766 factory = factor.diff(y)
767 if not factor.has(x) and not factor.has(y):
768 A = Wild('A', exclude=[y])
769 B = Wild('B', exclude=[y])
770 C = Wild('C', exclude=[x, y])
771 match = h.match(A + B*exp(y/C))
772 try:
773 tau = exp(-integrate(match[A]/match[C]), x)/match[B]
774 except NotImplementedError:
775 pass
776 else:
777 gx = match[A]*tau
778 return [{xi: tau, eta: gx}]
780 else:
781 gamma = cancel(factorx/factory)
782 if not gamma.has(y):
783 tauint = cancel((gamma*hy - gamma.diff(x) - hx)/(h + gamma))
784 if not tauint.has(y):
785 try:
786 tau = exp(integrate(tauint, x))
787 except NotImplementedError:
788 pass
789 else:
790 gx = -tau*gamma
791 return [{xi: tau, eta: gx}]
793 factor = cancel(hinv.diff(y)/hinv.diff(y, 2))
794 factorx = factor.diff(x)
795 factory = factor.diff(y)
796 if not factor.has(x) and not factor.has(y):
797 A = Wild('A', exclude=[y])
798 B = Wild('B', exclude=[y])
799 C = Wild('C', exclude=[x, y])
800 match = h.match(A + B*exp(y/C))
801 try:
802 tau = exp(-integrate(match[A]/match[C]), x)/match[B]
803 except NotImplementedError:
804 pass
805 else:
806 gx = match[A]*tau
807 return [{eta: tau.subs(x, func), xi: gx.subs(x, func)}]
809 else:
810 gamma = cancel(factorx/factory)
811 if not gamma.has(y):
812 tauint = cancel((gamma*hinv.diff(y) - gamma.diff(x) - hinv.diff(x))/(
813 hinv + gamma))
814 if not tauint.has(y):
815 try:
816 tau = exp(integrate(tauint, x))
817 except NotImplementedError:
818 pass
819 else:
820 gx = -tau*gamma
821 return [{eta: tau.subs(x, func), xi: gx.subs(x, func)}]
824def lie_heuristic_abaco2_unique_unknown(match, comp=False):
825 r"""
826 This heuristic assumes the presence of unknown functions or known functions
827 with non-integer powers.
829 1. A list of all functions and non-integer powers containing x and y
830 2. Loop over each element `f` in the list, find `\frac{\frac{\partial f}{\partial x}}{
831 \frac{\partial f}{\partial x}} = R`
833 If it is separable in `x` and `y`, let `X` be the factors containing `x`. Then
835 a] Check if `\xi = X` and `\eta = -\frac{X}{R}` satisfy the PDE. If yes, then return
836 `\xi` and `\eta`
837 b] Check if `\xi = \frac{-R}{X}` and `\eta = -\frac{1}{X}` satisfy the PDE.
838 If yes, then return `\xi` and `\eta`
840 If not, then check if
842 a] :math:`\xi = -R,\eta = 1`
844 b] :math:`\xi = 1, \eta = -\frac{1}{R}`
846 are solutions.
848 References
849 ==========
850 - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
851 ODE Patterns, pp. 10 - pp. 12
853 """
855 h = match['h']
856 hx = match['hx']
857 hy = match['hy']
858 func = match['func']
859 x = func.args[0]
860 y = match['y']
861 xi = Function('xi')(x, func)
862 eta = Function('eta')(x, func)
864 funclist = []
865 for atom in h.atoms(Pow):
866 base, exp = atom.as_base_exp()
867 if base.has(x) and base.has(y):
868 if not exp.is_Integer:
869 funclist.append(atom)
871 for function in h.atoms(AppliedUndef):
872 syms = function.free_symbols
873 if x in syms and y in syms:
874 funclist.append(function)
876 for f in funclist:
877 frac = cancel(f.diff(y)/f.diff(x))
878 sep = separatevars(frac, dict=True, symbols=[x, y])
879 if sep and sep['coeff']:
880 xitry1 = sep[x]
881 etatry1 = -1/(sep[y]*sep['coeff'])
882 pde1 = etatry1.diff(y)*h - xitry1.diff(x)*h - xitry1*hx - etatry1*hy
883 if not simplify(pde1):
884 return [{xi: xitry1, eta: etatry1.subs(y, func)}]
885 xitry2 = 1/etatry1
886 etatry2 = 1/xitry1
887 pde2 = etatry2.diff(x) - (xitry2.diff(y))*h**2 - xitry2*hx - etatry2*hy
888 if not simplify(expand(pde2)):
889 return [{xi: xitry2.subs(y, func), eta: etatry2}]
891 else:
892 etatry = -1/frac
893 pde = etatry.diff(x) + etatry.diff(y)*h - hx - etatry*hy
894 if not simplify(pde):
895 return [{xi: S.One, eta: etatry.subs(y, func)}]
896 xitry = -frac
897 pde = -xitry.diff(x)*h -xitry.diff(y)*h**2 - xitry*hx -hy
898 if not simplify(expand(pde)):
899 return [{xi: xitry.subs(y, func), eta: S.One}]
902def lie_heuristic_abaco2_unique_general(match, comp=False):
903 r"""
904 This heuristic finds if infinitesimals of the form `\eta = f(x)`, `\xi = g(y)`
905 without making any assumptions on `h`.
907 The complete sequence of steps is given in the paper mentioned below.
909 References
910 ==========
911 - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
912 ODE Patterns, pp. 10 - pp. 12
914 """
915 hx = match['hx']
916 hy = match['hy']
917 func = match['func']
918 x = func.args[0]
919 y = match['y']
920 xi = Function('xi')(x, func)
921 eta = Function('eta')(x, func)
923 A = hx.diff(y)
924 B = hy.diff(y) + hy**2
925 C = hx.diff(x) - hx**2
927 if not (A and B and C):
928 return
930 Ax = A.diff(x)
931 Ay = A.diff(y)
932 Axy = Ax.diff(y)
933 Axx = Ax.diff(x)
934 Ayy = Ay.diff(y)
935 D = simplify(2*Axy + hx*Ay - Ax*hy + (hx*hy + 2*A)*A)*A - 3*Ax*Ay
936 if not D:
937 E1 = simplify(3*Ax**2 + ((hx**2 + 2*C)*A - 2*Axx)*A)
938 if E1:
939 E2 = simplify((2*Ayy + (2*B - hy**2)*A)*A - 3*Ay**2)
940 if not E2:
941 E3 = simplify(
942 E1*((28*Ax + 4*hx*A)*A**3 - E1*(hy*A + Ay)) - E1.diff(x)*8*A**4)
943 if not E3:
944 etaval = cancel((4*A**3*(Ax - hx*A) + E1*(hy*A - Ay))/(S(2)*A*E1))
945 if x not in etaval:
946 try:
947 etaval = exp(integrate(etaval, y))
948 except NotImplementedError:
949 pass
950 else:
951 xival = -4*A**3*etaval/E1
952 if y not in xival:
953 return [{xi: xival, eta: etaval.subs(y, func)}]
955 else:
956 E1 = simplify((2*Ayy + (2*B - hy**2)*A)*A - 3*Ay**2)
957 if E1:
958 E2 = simplify(
959 4*A**3*D - D**2 + E1*((2*Axx - (hx**2 + 2*C)*A)*A - 3*Ax**2))
960 if not E2:
961 E3 = simplify(
962 -(A*D)*E1.diff(y) + ((E1.diff(x) - hy*D)*A + 3*Ay*D +
963 (A*hx - 3*Ax)*E1)*E1)
964 if not E3:
965 etaval = cancel(((A*hx - Ax)*E1 - (Ay + A*hy)*D)/(S(2)*A*D))
966 if x not in etaval:
967 try:
968 etaval = exp(integrate(etaval, y))
969 except NotImplementedError:
970 pass
971 else:
972 xival = -E1*etaval/D
973 if y not in xival:
974 return [{xi: xival, eta: etaval.subs(y, func)}]
977def lie_heuristic_linear(match, comp=False):
978 r"""
979 This heuristic assumes
981 1. `\xi = ax + by + c` and
982 2. `\eta = fx + gy + h`
984 After substituting the following assumptions in the determining PDE, it
985 reduces to
987 .. math:: f + (g - a)h - bh^{2} - (ax + by + c)\frac{\partial h}{\partial x}
988 - (fx + gy + c)\frac{\partial h}{\partial y}
990 Solving the reduced PDE obtained, using the method of characteristics, becomes
991 impractical. The method followed is grouping similar terms and solving the system
992 of linear equations obtained. The difference between the bivariate heuristic is that
993 `h` need not be a rational function in this case.
995 References
996 ==========
997 - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
998 ODE Patterns, pp. 10 - pp. 12
1000 """
1001 h = match['h']
1002 hx = match['hx']
1003 hy = match['hy']
1004 func = match['func']
1005 x = func.args[0]
1006 y = match['y']
1007 xi = Function('xi')(x, func)
1008 eta = Function('eta')(x, func)
1010 coeffdict = {}
1011 symbols = numbered_symbols("c", cls=Dummy)
1012 symlist = [next(symbols) for _ in islice(symbols, 6)]
1013 C0, C1, C2, C3, C4, C5 = symlist
1014 pde = C3 + (C4 - C0)*h - (C0*x + C1*y + C2)*hx - (C3*x + C4*y + C5)*hy - C1*h**2
1015 pde, denom = pde.as_numer_denom()
1016 pde = powsimp(expand(pde))
1017 if pde.is_Add:
1018 terms = pde.args
1019 for term in terms:
1020 if term.is_Mul:
1021 rem = Mul(*[m for m in term.args if not m.has(x, y)])
1022 xypart = term/rem
1023 if xypart not in coeffdict:
1024 coeffdict[xypart] = rem
1025 else:
1026 coeffdict[xypart] += rem
1027 else:
1028 if term not in coeffdict:
1029 coeffdict[term] = S.One
1030 else:
1031 coeffdict[term] += S.One
1033 sollist = coeffdict.values()
1034 soldict = solve(sollist, symlist)
1035 if soldict:
1036 if isinstance(soldict, list):
1037 soldict = soldict[0]
1038 subval = soldict.values()
1039 if any(t for t in subval):
1040 onedict = dict(zip(symlist, [1]*6))
1041 xival = C0*x + C1*func + C2
1042 etaval = C3*x + C4*func + C5
1043 xival = xival.subs(soldict)
1044 etaval = etaval.subs(soldict)
1045 xival = xival.subs(onedict)
1046 etaval = etaval.subs(onedict)
1047 return [{xi: xival, eta: etaval}]
1050def _lie_group_remove(coords):
1051 r"""
1052 This function is strictly meant for internal use by the Lie group ODE solving
1053 method. It replaces arbitrary functions returned by pdsolve as follows:
1055 1] If coords is an arbitrary function, then its argument is returned.
1056 2] An arbitrary function in an Add object is replaced by zero.
1057 3] An arbitrary function in a Mul object is replaced by one.
1058 4] If there is no arbitrary function coords is returned unchanged.
1060 Examples
1061 ========
1063 >>> from sympy.solvers.ode.lie_group import _lie_group_remove
1064 >>> from sympy import Function
1065 >>> from sympy.abc import x, y
1066 >>> F = Function("F")
1067 >>> eq = x**2*y
1068 >>> _lie_group_remove(eq)
1069 x**2*y
1070 >>> eq = F(x**2*y)
1071 >>> _lie_group_remove(eq)
1072 x**2*y
1073 >>> eq = x*y**2 + F(x**3)
1074 >>> _lie_group_remove(eq)
1075 x*y**2
1076 >>> eq = (F(x**3) + y)*x**4
1077 >>> _lie_group_remove(eq)
1078 x**4*y
1080 """
1081 if isinstance(coords, AppliedUndef):
1082 return coords.args[0]
1083 elif coords.is_Add:
1084 subfunc = coords.atoms(AppliedUndef)
1085 if subfunc:
1086 for func in subfunc:
1087 coords = coords.subs(func, 0)
1088 return coords
1089 elif coords.is_Pow:
1090 base, expr = coords.as_base_exp()
1091 base = _lie_group_remove(base)
1092 expr = _lie_group_remove(expr)
1093 return base**expr
1094 elif coords.is_Mul:
1095 mulargs = []
1096 coordargs = coords.args
1097 for arg in coordargs:
1098 if not isinstance(coords, AppliedUndef):
1099 mulargs.append(_lie_group_remove(arg))
1100 return Mul(*mulargs)
1101 return coords