123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101 |
- r"""
- This module contains the implementation of the internal helper functions for the lie_group hint for
- dsolve. These helper functions apply different heuristics on the given equation
- and return the solution. These functions are used by :py:meth:`sympy.solvers.ode.single.LieGroup`
- References
- =========
- - `abaco1_simple`, `function_sum` and `chi` are referenced from E.S Cheb-Terrab, L.G.S Duarte
- and L.A,C.P da Mota, Computer Algebra Solving of First Order ODEs Using
- Symmetry Methods, pp. 7 - pp. 8
- - `abaco1_product`, `abaco2_similar`, `abaco2_unique_unknown`, `linear` and `abaco2_unique_general`
- are referenced from E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
- ODE Patterns, pp. 7 - pp. 12
- - `bivariate` from Lie Groups and Differential Equations pp. 327 - pp. 329
- """
- from itertools import islice
- from sympy.core import Add, S, Mul, Pow
- from sympy.core.exprtools import factor_terms
- from sympy.core.function import Function, AppliedUndef, expand
- from sympy.core.relational import Equality, Eq
- from sympy.core.symbol import Symbol, Wild, Dummy, symbols
- from sympy.functions import exp, log
- from sympy.integrals.integrals import integrate
- from sympy.polys import Poly
- from sympy.polys.polytools import cancel, div
- from sympy.simplify import (collect, powsimp, # type: ignore
- separatevars, simplify)
- from sympy.solvers import solve
- from sympy.solvers.pde import pdsolve
- from sympy.utilities import numbered_symbols
- from sympy.solvers.deutils import _preprocess, ode_order
- from .ode import checkinfsol
- lie_heuristics = (
- "abaco1_simple",
- "abaco1_product",
- "abaco2_similar",
- "abaco2_unique_unknown",
- "abaco2_unique_general",
- "linear",
- "function_sum",
- "bivariate",
- "chi"
- )
- def _ode_lie_group_try_heuristic(eq, heuristic, func, match, inf):
- xi = Function("xi")
- eta = Function("eta")
- f = func.func
- x = func.args[0]
- y = match['y']
- h = match['h']
- tempsol = []
- if not inf:
- try:
- inf = infinitesimals(eq, hint=heuristic, func=func, order=1, match=match)
- except ValueError:
- return None
- for infsim in inf:
- xiinf = (infsim[xi(x, func)]).subs(func, y)
- etainf = (infsim[eta(x, func)]).subs(func, y)
- # This condition creates recursion while using pdsolve.
- # Since the first step while solving a PDE of form
- # a*(f(x, y).diff(x)) + b*(f(x, y).diff(y)) + c = 0
- # is to solve the ODE dy/dx = b/a
- if simplify(etainf/xiinf) == h:
- continue
- rpde = f(x, y).diff(x)*xiinf + f(x, y).diff(y)*etainf
- r = pdsolve(rpde, func=f(x, y)).rhs
- s = pdsolve(rpde - 1, func=f(x, y)).rhs
- newcoord = [_lie_group_remove(coord) for coord in [r, s]]
- r = Dummy("r")
- s = Dummy("s")
- C1 = Symbol("C1")
- rcoord = newcoord[0]
- scoord = newcoord[-1]
- try:
- sol = solve([r - rcoord, s - scoord], x, y, dict=True)
- if sol == []:
- continue
- except NotImplementedError:
- continue
- else:
- sol = sol[0]
- xsub = sol[x]
- ysub = sol[y]
- num = simplify(scoord.diff(x) + scoord.diff(y)*h)
- denom = simplify(rcoord.diff(x) + rcoord.diff(y)*h)
- if num and denom:
- diffeq = simplify((num/denom).subs([(x, xsub), (y, ysub)]))
- sep = separatevars(diffeq, symbols=[r, s], dict=True)
- if sep:
- # Trying to separate, r and s coordinates
- deq = integrate((1/sep[s]), s) + C1 - integrate(sep['coeff']*sep[r], r)
- # Substituting and reverting back to original coordinates
- deq = deq.subs([(r, rcoord), (s, scoord)])
- try:
- sdeq = solve(deq, y)
- except NotImplementedError:
- tempsol.append(deq)
- else:
- return [Eq(f(x), sol) for sol in sdeq]
- elif denom: # (ds/dr) is zero which means s is constant
- return [Eq(f(x), solve(scoord - C1, y)[0])]
- elif num: # (dr/ds) is zero which means r is constant
- return [Eq(f(x), solve(rcoord - C1, y)[0])]
- # If nothing works, return solution as it is, without solving for y
- if tempsol:
- return [Eq(sol.subs(y, f(x)), 0) for sol in tempsol]
- return None
- def _ode_lie_group( s, func, order, match):
- heuristics = lie_heuristics
- inf = {}
- f = func.func
- x = func.args[0]
- df = func.diff(x)
- xi = Function("xi")
- eta = Function("eta")
- xis = match['xi']
- etas = match['eta']
- y = match.pop('y', None)
- if y:
- h = -simplify(match[match['d']]/match[match['e']])
- y = y
- else:
- y = Dummy("y")
- h = s.subs(func, y)
- if xis is not None and etas is not None:
- inf = [{xi(x, f(x)): S(xis), eta(x, f(x)): S(etas)}]
- if checkinfsol(Eq(df, s), inf, func=f(x), order=1)[0][0]:
- heuristics = ["user_defined"] + list(heuristics)
- match = {'h': h, 'y': y}
- # This is done so that if any heuristic raises a ValueError
- # another heuristic can be used.
- sol = None
- for heuristic in heuristics:
- sol = _ode_lie_group_try_heuristic(Eq(df, s), heuristic, func, match, inf)
- if sol:
- return sol
- return sol
- def infinitesimals(eq, func=None, order=None, hint='default', match=None):
- r"""
- The infinitesimal functions of an ordinary differential equation, `\xi(x,y)`
- and `\eta(x,y)`, are the infinitesimals of the Lie group of point transformations
- for which the differential equation is invariant. So, the ODE `y'=f(x,y)`
- would admit a Lie group `x^*=X(x,y;\varepsilon)=x+\varepsilon\xi(x,y)`,
- `y^*=Y(x,y;\varepsilon)=y+\varepsilon\eta(x,y)` such that `(y^*)'=f(x^*, y^*)`.
- A change of coordinates, to `r(x,y)` and `s(x,y)`, can be performed so this Lie group
- becomes the translation group, `r^*=r` and `s^*=s+\varepsilon`.
- They are tangents to the coordinate curves of the new system.
- Consider the transformation `(x, y) \to (X, Y)` such that the
- differential equation remains invariant. `\xi` and `\eta` are the tangents to
- the transformed coordinates `X` and `Y`, at `\varepsilon=0`.
- .. math:: \left(\frac{\partial X(x,y;\varepsilon)}{\partial\varepsilon
- }\right)|_{\varepsilon=0} = \xi,
- \left(\frac{\partial Y(x,y;\varepsilon)}{\partial\varepsilon
- }\right)|_{\varepsilon=0} = \eta,
- The infinitesimals can be found by solving the following PDE:
- >>> from sympy import Function, Eq, pprint
- >>> from sympy.abc import x, y
- >>> xi, eta, h = map(Function, ['xi', 'eta', 'h'])
- >>> h = h(x, y) # dy/dx = h
- >>> eta = eta(x, y)
- >>> xi = xi(x, y)
- >>> genform = Eq(eta.diff(x) + (eta.diff(y) - xi.diff(x))*h
- ... - (xi.diff(y))*h**2 - xi*(h.diff(x)) - eta*(h.diff(y)), 0)
- >>> pprint(genform)
- /d d \ d 2 d
- |--(eta(x, y)) - --(xi(x, y))|*h(x, y) - eta(x, y)*--(h(x, y)) - h (x, y)*--(x
- \dy dx / dy dy
- <BLANKLINE>
- d d
- i(x, y)) - xi(x, y)*--(h(x, y)) + --(eta(x, y)) = 0
- dx dx
- Solving the above mentioned PDE is not trivial, and can be solved only by
- making intelligent assumptions for `\xi` and `\eta` (heuristics). Once an
- infinitesimal is found, the attempt to find more heuristics stops. This is done to
- optimise the speed of solving the differential equation. If a list of all the
- infinitesimals is needed, ``hint`` should be flagged as ``all``, which gives
- the complete list of infinitesimals. If the infinitesimals for a particular
- heuristic needs to be found, it can be passed as a flag to ``hint``.
- Examples
- ========
- >>> from sympy import Function
- >>> from sympy.solvers.ode.lie_group import infinitesimals
- >>> from sympy.abc import x
- >>> f = Function('f')
- >>> eq = f(x).diff(x) - x**2*f(x)
- >>> infinitesimals(eq)
- [{eta(x, f(x)): exp(x**3/3), xi(x, f(x)): 0}]
- References
- ==========
- - Solving differential equations by Symmetry Groups,
- John Starrett, pp. 1 - pp. 14
- """
- if isinstance(eq, Equality):
- eq = eq.lhs - eq.rhs
- if not func:
- eq, func = _preprocess(eq)
- variables = func.args
- if len(variables) != 1:
- raise ValueError("ODE's have only one independent variable")
- else:
- x = variables[0]
- if not order:
- order = ode_order(eq, func)
- if order != 1:
- raise NotImplementedError("Infinitesimals for only "
- "first order ODE's have been implemented")
- else:
- df = func.diff(x)
- # Matching differential equation of the form a*df + b
- a = Wild('a', exclude = [df])
- b = Wild('b', exclude = [df])
- if match: # Used by lie_group hint
- h = match['h']
- y = match['y']
- else:
- match = collect(expand(eq), df).match(a*df + b)
- if match:
- h = -simplify(match[b]/match[a])
- else:
- try:
- sol = solve(eq, df)
- except NotImplementedError:
- raise NotImplementedError("Infinitesimals for the "
- "first order ODE could not be found")
- else:
- h = sol[0] # Find infinitesimals for one solution
- y = Dummy("y")
- h = h.subs(func, y)
- u = Dummy("u")
- hx = h.diff(x)
- hy = h.diff(y)
- hinv = ((1/h).subs([(x, u), (y, x)])).subs(u, y) # Inverse ODE
- match = {'h': h, 'func': func, 'hx': hx, 'hy': hy, 'y': y, 'hinv': hinv}
- if hint == 'all':
- xieta = []
- for heuristic in lie_heuristics:
- function = globals()['lie_heuristic_' + heuristic]
- inflist = function(match, comp=True)
- if inflist:
- xieta.extend([inf for inf in inflist if inf not in xieta])
- if xieta:
- return xieta
- else:
- raise NotImplementedError("Infinitesimals could not be found for "
- "the given ODE")
- elif hint == 'default':
- for heuristic in lie_heuristics:
- function = globals()['lie_heuristic_' + heuristic]
- xieta = function(match, comp=False)
- if xieta:
- return xieta
- raise NotImplementedError("Infinitesimals could not be found for"
- " the given ODE")
- elif hint not in lie_heuristics:
- raise ValueError("Heuristic not recognized: " + hint)
- else:
- function = globals()['lie_heuristic_' + hint]
- xieta = function(match, comp=True)
- if xieta:
- return xieta
- else:
- raise ValueError("Infinitesimals could not be found using the"
- " given heuristic")
- def lie_heuristic_abaco1_simple(match, comp=False):
- r"""
- The first heuristic uses the following four sets of
- assumptions on `\xi` and `\eta`
- .. math:: \xi = 0, \eta = f(x)
- .. math:: \xi = 0, \eta = f(y)
- .. math:: \xi = f(x), \eta = 0
- .. math:: \xi = f(y), \eta = 0
- The success of this heuristic is determined by algebraic factorisation.
- For the first assumption `\xi = 0` and `\eta` to be a function of `x`, the PDE
- .. math:: \frac{\partial \eta}{\partial x} + (\frac{\partial \eta}{\partial y}
- - \frac{\partial \xi}{\partial x})*h
- - \frac{\partial \xi}{\partial y}*h^{2}
- - \xi*\frac{\partial h}{\partial x} - \eta*\frac{\partial h}{\partial y} = 0
- reduces to `f'(x) - f\frac{\partial h}{\partial y} = 0`
- If `\frac{\partial h}{\partial y}` is a function of `x`, then this can usually
- be integrated easily. A similar idea is applied to the other 3 assumptions as well.
- References
- ==========
- - E.S Cheb-Terrab, L.G.S Duarte and L.A,C.P da Mota, Computer Algebra
- Solving of First Order ODEs Using Symmetry Methods, pp. 8
- """
- xieta = []
- y = match['y']
- h = match['h']
- func = match['func']
- x = func.args[0]
- hx = match['hx']
- hy = match['hy']
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- hysym = hy.free_symbols
- if y not in hysym:
- try:
- fx = exp(integrate(hy, x))
- except NotImplementedError:
- pass
- else:
- inf = {xi: S.Zero, eta: fx}
- if not comp:
- return [inf]
- if comp and inf not in xieta:
- xieta.append(inf)
- factor = hy/h
- facsym = factor.free_symbols
- if x not in facsym:
- try:
- fy = exp(integrate(factor, y))
- except NotImplementedError:
- pass
- else:
- inf = {xi: S.Zero, eta: fy.subs(y, func)}
- if not comp:
- return [inf]
- if comp and inf not in xieta:
- xieta.append(inf)
- factor = -hx/h
- facsym = factor.free_symbols
- if y not in facsym:
- try:
- fx = exp(integrate(factor, x))
- except NotImplementedError:
- pass
- else:
- inf = {xi: fx, eta: S.Zero}
- if not comp:
- return [inf]
- if comp and inf not in xieta:
- xieta.append(inf)
- factor = -hx/(h**2)
- facsym = factor.free_symbols
- if x not in facsym:
- try:
- fy = exp(integrate(factor, y))
- except NotImplementedError:
- pass
- else:
- inf = {xi: fy.subs(y, func), eta: S.Zero}
- if not comp:
- return [inf]
- if comp and inf not in xieta:
- xieta.append(inf)
- if xieta:
- return xieta
- def lie_heuristic_abaco1_product(match, comp=False):
- r"""
- The second heuristic uses the following two assumptions on `\xi` and `\eta`
- .. math:: \eta = 0, \xi = f(x)*g(y)
- .. math:: \eta = f(x)*g(y), \xi = 0
- The first assumption of this heuristic holds good if
- `\frac{1}{h^{2}}\frac{\partial^2}{\partial x \partial y}\log(h)` is
- separable in `x` and `y`, then the separated factors containing `x`
- is `f(x)`, and `g(y)` is obtained by
- .. math:: e^{\int f\frac{\partial}{\partial x}\left(\frac{1}{f*h}\right)\,dy}
- provided `f\frac{\partial}{\partial x}\left(\frac{1}{f*h}\right)` is a function
- of `y` only.
- The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as
- `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first assumption
- satisfies. After obtaining `f(x)` and `g(y)`, the coordinates are again
- interchanged, to get `\eta` as `f(x)*g(y)`
- References
- ==========
- - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
- ODE Patterns, pp. 7 - pp. 8
- """
- xieta = []
- y = match['y']
- h = match['h']
- hinv = match['hinv']
- func = match['func']
- x = func.args[0]
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- inf = separatevars(((log(h).diff(y)).diff(x))/h**2, dict=True, symbols=[x, y])
- if inf and inf['coeff']:
- fx = inf[x]
- gy = simplify(fx*((1/(fx*h)).diff(x)))
- gysyms = gy.free_symbols
- if x not in gysyms:
- gy = exp(integrate(gy, y))
- inf = {eta: S.Zero, xi: (fx*gy).subs(y, func)}
- if not comp:
- return [inf]
- if comp and inf not in xieta:
- xieta.append(inf)
- u1 = Dummy("u1")
- inf = separatevars(((log(hinv).diff(y)).diff(x))/hinv**2, dict=True, symbols=[x, y])
- if inf and inf['coeff']:
- fx = inf[x]
- gy = simplify(fx*((1/(fx*hinv)).diff(x)))
- gysyms = gy.free_symbols
- if x not in gysyms:
- gy = exp(integrate(gy, y))
- etaval = fx*gy
- etaval = (etaval.subs([(x, u1), (y, x)])).subs(u1, y)
- inf = {eta: etaval.subs(y, func), xi: S.Zero}
- if not comp:
- return [inf]
- if comp and inf not in xieta:
- xieta.append(inf)
- if xieta:
- return xieta
- def lie_heuristic_bivariate(match, comp=False):
- r"""
- The third heuristic assumes the infinitesimals `\xi` and `\eta`
- to be bi-variate polynomials in `x` and `y`. The assumption made here
- for the logic below is that `h` is a rational function in `x` and `y`
- though that may not be necessary for the infinitesimals to be
- bivariate polynomials. The coefficients of the infinitesimals
- are found out by substituting them in the PDE and grouping similar terms
- that are polynomials and since they form a linear system, solve and check
- for non trivial solutions. The degree of the assumed bivariates
- are increased till a certain maximum value.
- References
- ==========
- - Lie Groups and Differential Equations
- pp. 327 - pp. 329
- """
- h = match['h']
- hx = match['hx']
- hy = match['hy']
- func = match['func']
- x = func.args[0]
- y = match['y']
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- if h.is_rational_function():
- # The maximum degree that the infinitesimals can take is
- # calculated by this technique.
- etax, etay, etad, xix, xiy, xid = symbols("etax etay etad xix xiy xid")
- ipde = etax + (etay - xix)*h - xiy*h**2 - xid*hx - etad*hy
- num, denom = cancel(ipde).as_numer_denom()
- deg = Poly(num, x, y).total_degree()
- deta = Function('deta')(x, y)
- dxi = Function('dxi')(x, y)
- ipde = (deta.diff(x) + (deta.diff(y) - dxi.diff(x))*h - (dxi.diff(y))*h**2
- - dxi*hx - deta*hy)
- xieq = Symbol("xi0")
- etaeq = Symbol("eta0")
- for i in range(deg + 1):
- if i:
- xieq += Add(*[
- Symbol("xi_" + str(power) + "_" + str(i - power))*x**power*y**(i - power)
- for power in range(i + 1)])
- etaeq += Add(*[
- Symbol("eta_" + str(power) + "_" + str(i - power))*x**power*y**(i - power)
- for power in range(i + 1)])
- pden, denom = (ipde.subs({dxi: xieq, deta: etaeq}).doit()).as_numer_denom()
- pden = expand(pden)
- # If the individual terms are monomials, the coefficients
- # are grouped
- if pden.is_polynomial(x, y) and pden.is_Add:
- polyy = Poly(pden, x, y).as_dict()
- if polyy:
- symset = xieq.free_symbols.union(etaeq.free_symbols) - {x, y}
- soldict = solve(polyy.values(), *symset)
- if isinstance(soldict, list):
- soldict = soldict[0]
- if any(soldict.values()):
- xired = xieq.subs(soldict)
- etared = etaeq.subs(soldict)
- # Scaling is done by substituting one for the parameters
- # This can be any number except zero.
- dict_ = {sym: 1 for sym in symset}
- inf = {eta: etared.subs(dict_).subs(y, func),
- xi: xired.subs(dict_).subs(y, func)}
- return [inf]
- def lie_heuristic_chi(match, comp=False):
- r"""
- The aim of the fourth heuristic is to find the function `\chi(x, y)`
- that satisfies the PDE `\frac{d\chi}{dx} + h\frac{d\chi}{dx}
- - \frac{\partial h}{\partial y}\chi = 0`.
- This assumes `\chi` to be a bivariate polynomial in `x` and `y`. By intuition,
- `h` should be a rational function in `x` and `y`. The method used here is
- to substitute a general binomial for `\chi` up to a certain maximum degree
- is reached. The coefficients of the polynomials, are calculated by by collecting
- terms of the same order in `x` and `y`.
- After finding `\chi`, the next step is to use `\eta = \xi*h + \chi`, to
- determine `\xi` and `\eta`. This can be done by dividing `\chi` by `h`
- which would give `-\xi` as the quotient and `\eta` as the remainder.
- References
- ==========
- - E.S Cheb-Terrab, L.G.S Duarte and L.A,C.P da Mota, Computer Algebra
- Solving of First Order ODEs Using Symmetry Methods, pp. 8
- """
- h = match['h']
- hy = match['hy']
- func = match['func']
- x = func.args[0]
- y = match['y']
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- if h.is_rational_function():
- schi, schix, schiy = symbols("schi, schix, schiy")
- cpde = schix + h*schiy - hy*schi
- num, denom = cancel(cpde).as_numer_denom()
- deg = Poly(num, x, y).total_degree()
- chi = Function('chi')(x, y)
- chix = chi.diff(x)
- chiy = chi.diff(y)
- cpde = chix + h*chiy - hy*chi
- chieq = Symbol("chi")
- for i in range(1, deg + 1):
- chieq += Add(*[
- Symbol("chi_" + str(power) + "_" + str(i - power))*x**power*y**(i - power)
- for power in range(i + 1)])
- cnum, cden = cancel(cpde.subs({chi : chieq}).doit()).as_numer_denom()
- cnum = expand(cnum)
- if cnum.is_polynomial(x, y) and cnum.is_Add:
- cpoly = Poly(cnum, x, y).as_dict()
- if cpoly:
- solsyms = chieq.free_symbols - {x, y}
- soldict = solve(cpoly.values(), *solsyms)
- if isinstance(soldict, list):
- soldict = soldict[0]
- if any(soldict.values()):
- chieq = chieq.subs(soldict)
- dict_ = {sym: 1 for sym in solsyms}
- chieq = chieq.subs(dict_)
- # After finding chi, the main aim is to find out
- # eta, xi by the equation eta = xi*h + chi
- # One method to set xi, would be rearranging it to
- # (eta/h) - xi = (chi/h). This would mean dividing
- # chi by h would give -xi as the quotient and eta
- # as the remainder. Thanks to Sean Vig for suggesting
- # this method.
- xic, etac = div(chieq, h)
- inf = {eta: etac.subs(y, func), xi: -xic.subs(y, func)}
- return [inf]
- def lie_heuristic_function_sum(match, comp=False):
- r"""
- This heuristic uses the following two assumptions on `\xi` and `\eta`
- .. math:: \eta = 0, \xi = f(x) + g(y)
- .. math:: \eta = f(x) + g(y), \xi = 0
- The first assumption of this heuristic holds good if
- .. math:: \frac{\partial}{\partial y}[(h\frac{\partial^{2}}{
- \partial x^{2}}(h^{-1}))^{-1}]
- is separable in `x` and `y`,
- 1. The separated factors containing `y` is `\frac{\partial g}{\partial y}`.
- From this `g(y)` can be determined.
- 2. The separated factors containing `x` is `f''(x)`.
- 3. `h\frac{\partial^{2}}{\partial x^{2}}(h^{-1})` equals
- `\frac{f''(x)}{f(x) + g(y)}`. From this `f(x)` can be determined.
- The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as
- `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first
- assumption satisfies. After obtaining `f(x)` and `g(y)`, the coordinates
- are again interchanged, to get `\eta` as `f(x) + g(y)`.
- For both assumptions, the constant factors are separated among `g(y)`
- and `f''(x)`, such that `f''(x)` obtained from 3] is the same as that
- obtained from 2]. If not possible, then this heuristic fails.
- References
- ==========
- - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
- ODE Patterns, pp. 7 - pp. 8
- """
- xieta = []
- h = match['h']
- func = match['func']
- hinv = match['hinv']
- x = func.args[0]
- y = match['y']
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- for odefac in [h, hinv]:
- factor = odefac*((1/odefac).diff(x, 2))
- sep = separatevars((1/factor).diff(y), dict=True, symbols=[x, y])
- if sep and sep['coeff'] and sep[x].has(x) and sep[y].has(y):
- k = Dummy("k")
- try:
- gy = k*integrate(sep[y], y)
- except NotImplementedError:
- pass
- else:
- fdd = 1/(k*sep[x]*sep['coeff'])
- fx = simplify(fdd/factor - gy)
- check = simplify(fx.diff(x, 2) - fdd)
- if fx:
- if not check:
- fx = fx.subs(k, 1)
- gy = (gy/k)
- else:
- sol = solve(check, k)
- if sol:
- sol = sol[0]
- fx = fx.subs(k, sol)
- gy = (gy/k)*sol
- else:
- continue
- if odefac == hinv: # Inverse ODE
- fx = fx.subs(x, y)
- gy = gy.subs(y, x)
- etaval = factor_terms(fx + gy)
- if etaval.is_Mul:
- etaval = Mul(*[arg for arg in etaval.args if arg.has(x, y)])
- if odefac == hinv: # Inverse ODE
- inf = {eta: etaval.subs(y, func), xi : S.Zero}
- else:
- inf = {xi: etaval.subs(y, func), eta : S.Zero}
- if not comp:
- return [inf]
- else:
- xieta.append(inf)
- if xieta:
- return xieta
- def lie_heuristic_abaco2_similar(match, comp=False):
- r"""
- This heuristic uses the following two assumptions on `\xi` and `\eta`
- .. math:: \eta = g(x), \xi = f(x)
- .. math:: \eta = f(y), \xi = g(y)
- For the first assumption,
- 1. First `\frac{\frac{\partial h}{\partial y}}{\frac{\partial^{2} h}{
- \partial yy}}` is calculated. Let us say this value is A
- 2. If this is constant, then `h` is matched to the form `A(x) + B(x)e^{
- \frac{y}{C}}` then, `\frac{e^{\int \frac{A(x)}{C} \,dx}}{B(x)}` gives `f(x)`
- and `A(x)*f(x)` gives `g(x)`
- 3. Otherwise `\frac{\frac{\partial A}{\partial X}}{\frac{\partial A}{
- \partial Y}} = \gamma` is calculated. If
- a] `\gamma` is a function of `x` alone
- b] `\frac{\gamma\frac{\partial h}{\partial y} - \gamma'(x) - \frac{
- \partial h}{\partial x}}{h + \gamma} = G` is a function of `x` alone.
- then, `e^{\int G \,dx}` gives `f(x)` and `-\gamma*f(x)` gives `g(x)`
- The second assumption holds good if `\frac{dy}{dx} = h(x, y)` is rewritten as
- `\frac{dy}{dx} = \frac{1}{h(y, x)}` and the same properties of the first assumption
- satisfies. After obtaining `f(x)` and `g(x)`, the coordinates are again
- interchanged, to get `\xi` as `f(x^*)` and `\eta` as `g(y^*)`
- References
- ==========
- - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
- ODE Patterns, pp. 10 - pp. 12
- """
- h = match['h']
- hx = match['hx']
- hy = match['hy']
- func = match['func']
- hinv = match['hinv']
- x = func.args[0]
- y = match['y']
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- factor = cancel(h.diff(y)/h.diff(y, 2))
- factorx = factor.diff(x)
- factory = factor.diff(y)
- if not factor.has(x) and not factor.has(y):
- A = Wild('A', exclude=[y])
- B = Wild('B', exclude=[y])
- C = Wild('C', exclude=[x, y])
- match = h.match(A + B*exp(y/C))
- try:
- tau = exp(-integrate(match[A]/match[C]), x)/match[B]
- except NotImplementedError:
- pass
- else:
- gx = match[A]*tau
- return [{xi: tau, eta: gx}]
- else:
- gamma = cancel(factorx/factory)
- if not gamma.has(y):
- tauint = cancel((gamma*hy - gamma.diff(x) - hx)/(h + gamma))
- if not tauint.has(y):
- try:
- tau = exp(integrate(tauint, x))
- except NotImplementedError:
- pass
- else:
- gx = -tau*gamma
- return [{xi: tau, eta: gx}]
- factor = cancel(hinv.diff(y)/hinv.diff(y, 2))
- factorx = factor.diff(x)
- factory = factor.diff(y)
- if not factor.has(x) and not factor.has(y):
- A = Wild('A', exclude=[y])
- B = Wild('B', exclude=[y])
- C = Wild('C', exclude=[x, y])
- match = h.match(A + B*exp(y/C))
- try:
- tau = exp(-integrate(match[A]/match[C]), x)/match[B]
- except NotImplementedError:
- pass
- else:
- gx = match[A]*tau
- return [{eta: tau.subs(x, func), xi: gx.subs(x, func)}]
- else:
- gamma = cancel(factorx/factory)
- if not gamma.has(y):
- tauint = cancel((gamma*hinv.diff(y) - gamma.diff(x) - hinv.diff(x))/(
- hinv + gamma))
- if not tauint.has(y):
- try:
- tau = exp(integrate(tauint, x))
- except NotImplementedError:
- pass
- else:
- gx = -tau*gamma
- return [{eta: tau.subs(x, func), xi: gx.subs(x, func)}]
- def lie_heuristic_abaco2_unique_unknown(match, comp=False):
- r"""
- This heuristic assumes the presence of unknown functions or known functions
- with non-integer powers.
- 1. A list of all functions and non-integer powers containing x and y
- 2. Loop over each element `f` in the list, find `\frac{\frac{\partial f}{\partial x}}{
- \frac{\partial f}{\partial x}} = R`
- If it is separable in `x` and `y`, let `X` be the factors containing `x`. Then
- a] Check if `\xi = X` and `\eta = -\frac{X}{R}` satisfy the PDE. If yes, then return
- `\xi` and `\eta`
- b] Check if `\xi = \frac{-R}{X}` and `\eta = -\frac{1}{X}` satisfy the PDE.
- If yes, then return `\xi` and `\eta`
- If not, then check if
- a] :math:`\xi = -R,\eta = 1`
- b] :math:`\xi = 1, \eta = -\frac{1}{R}`
- are solutions.
- References
- ==========
- - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
- ODE Patterns, pp. 10 - pp. 12
- """
- h = match['h']
- hx = match['hx']
- hy = match['hy']
- func = match['func']
- x = func.args[0]
- y = match['y']
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- funclist = []
- for atom in h.atoms(Pow):
- base, exp = atom.as_base_exp()
- if base.has(x) and base.has(y):
- if not exp.is_Integer:
- funclist.append(atom)
- for function in h.atoms(AppliedUndef):
- syms = function.free_symbols
- if x in syms and y in syms:
- funclist.append(function)
- for f in funclist:
- frac = cancel(f.diff(y)/f.diff(x))
- sep = separatevars(frac, dict=True, symbols=[x, y])
- if sep and sep['coeff']:
- xitry1 = sep[x]
- etatry1 = -1/(sep[y]*sep['coeff'])
- pde1 = etatry1.diff(y)*h - xitry1.diff(x)*h - xitry1*hx - etatry1*hy
- if not simplify(pde1):
- return [{xi: xitry1, eta: etatry1.subs(y, func)}]
- xitry2 = 1/etatry1
- etatry2 = 1/xitry1
- pde2 = etatry2.diff(x) - (xitry2.diff(y))*h**2 - xitry2*hx - etatry2*hy
- if not simplify(expand(pde2)):
- return [{xi: xitry2.subs(y, func), eta: etatry2}]
- else:
- etatry = -1/frac
- pde = etatry.diff(x) + etatry.diff(y)*h - hx - etatry*hy
- if not simplify(pde):
- return [{xi: S.One, eta: etatry.subs(y, func)}]
- xitry = -frac
- pde = -xitry.diff(x)*h -xitry.diff(y)*h**2 - xitry*hx -hy
- if not simplify(expand(pde)):
- return [{xi: xitry.subs(y, func), eta: S.One}]
- def lie_heuristic_abaco2_unique_general(match, comp=False):
- r"""
- This heuristic finds if infinitesimals of the form `\eta = f(x)`, `\xi = g(y)`
- without making any assumptions on `h`.
- The complete sequence of steps is given in the paper mentioned below.
- References
- ==========
- - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
- ODE Patterns, pp. 10 - pp. 12
- """
- hx = match['hx']
- hy = match['hy']
- func = match['func']
- x = func.args[0]
- y = match['y']
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- A = hx.diff(y)
- B = hy.diff(y) + hy**2
- C = hx.diff(x) - hx**2
- if not (A and B and C):
- return
- Ax = A.diff(x)
- Ay = A.diff(y)
- Axy = Ax.diff(y)
- Axx = Ax.diff(x)
- Ayy = Ay.diff(y)
- D = simplify(2*Axy + hx*Ay - Ax*hy + (hx*hy + 2*A)*A)*A - 3*Ax*Ay
- if not D:
- E1 = simplify(3*Ax**2 + ((hx**2 + 2*C)*A - 2*Axx)*A)
- if E1:
- E2 = simplify((2*Ayy + (2*B - hy**2)*A)*A - 3*Ay**2)
- if not E2:
- E3 = simplify(
- E1*((28*Ax + 4*hx*A)*A**3 - E1*(hy*A + Ay)) - E1.diff(x)*8*A**4)
- if not E3:
- etaval = cancel((4*A**3*(Ax - hx*A) + E1*(hy*A - Ay))/(S(2)*A*E1))
- if x not in etaval:
- try:
- etaval = exp(integrate(etaval, y))
- except NotImplementedError:
- pass
- else:
- xival = -4*A**3*etaval/E1
- if y not in xival:
- return [{xi: xival, eta: etaval.subs(y, func)}]
- else:
- E1 = simplify((2*Ayy + (2*B - hy**2)*A)*A - 3*Ay**2)
- if E1:
- E2 = simplify(
- 4*A**3*D - D**2 + E1*((2*Axx - (hx**2 + 2*C)*A)*A - 3*Ax**2))
- if not E2:
- E3 = simplify(
- -(A*D)*E1.diff(y) + ((E1.diff(x) - hy*D)*A + 3*Ay*D +
- (A*hx - 3*Ax)*E1)*E1)
- if not E3:
- etaval = cancel(((A*hx - Ax)*E1 - (Ay + A*hy)*D)/(S(2)*A*D))
- if x not in etaval:
- try:
- etaval = exp(integrate(etaval, y))
- except NotImplementedError:
- pass
- else:
- xival = -E1*etaval/D
- if y not in xival:
- return [{xi: xival, eta: etaval.subs(y, func)}]
- def lie_heuristic_linear(match, comp=False):
- r"""
- This heuristic assumes
- 1. `\xi = ax + by + c` and
- 2. `\eta = fx + gy + h`
- After substituting the following assumptions in the determining PDE, it
- reduces to
- .. math:: f + (g - a)h - bh^{2} - (ax + by + c)\frac{\partial h}{\partial x}
- - (fx + gy + c)\frac{\partial h}{\partial y}
- Solving the reduced PDE obtained, using the method of characteristics, becomes
- impractical. The method followed is grouping similar terms and solving the system
- of linear equations obtained. The difference between the bivariate heuristic is that
- `h` need not be a rational function in this case.
- References
- ==========
- - E.S. Cheb-Terrab, A.D. Roche, Symmetries and First Order
- ODE Patterns, pp. 10 - pp. 12
- """
- h = match['h']
- hx = match['hx']
- hy = match['hy']
- func = match['func']
- x = func.args[0]
- y = match['y']
- xi = Function('xi')(x, func)
- eta = Function('eta')(x, func)
- coeffdict = {}
- symbols = numbered_symbols("c", cls=Dummy)
- symlist = [next(symbols) for _ in islice(symbols, 6)]
- C0, C1, C2, C3, C4, C5 = symlist
- pde = C3 + (C4 - C0)*h - (C0*x + C1*y + C2)*hx - (C3*x + C4*y + C5)*hy - C1*h**2
- pde, denom = pde.as_numer_denom()
- pde = powsimp(expand(pde))
- if pde.is_Add:
- terms = pde.args
- for term in terms:
- if term.is_Mul:
- rem = Mul(*[m for m in term.args if not m.has(x, y)])
- xypart = term/rem
- if xypart not in coeffdict:
- coeffdict[xypart] = rem
- else:
- coeffdict[xypart] += rem
- else:
- if term not in coeffdict:
- coeffdict[term] = S.One
- else:
- coeffdict[term] += S.One
- sollist = coeffdict.values()
- soldict = solve(sollist, symlist)
- if soldict:
- if isinstance(soldict, list):
- soldict = soldict[0]
- subval = soldict.values()
- if any(t for t in subval):
- onedict = dict(zip(symlist, [1]*6))
- xival = C0*x + C1*func + C2
- etaval = C3*x + C4*func + C5
- xival = xival.subs(soldict)
- etaval = etaval.subs(soldict)
- xival = xival.subs(onedict)
- etaval = etaval.subs(onedict)
- return [{xi: xival, eta: etaval}]
- def _lie_group_remove(coords):
- r"""
- This function is strictly meant for internal use by the Lie group ODE solving
- method. It replaces arbitrary functions returned by pdsolve as follows:
- 1] If coords is an arbitrary function, then its argument is returned.
- 2] An arbitrary function in an Add object is replaced by zero.
- 3] An arbitrary function in a Mul object is replaced by one.
- 4] If there is no arbitrary function coords is returned unchanged.
- Examples
- ========
- >>> from sympy.solvers.ode.lie_group import _lie_group_remove
- >>> from sympy import Function
- >>> from sympy.abc import x, y
- >>> F = Function("F")
- >>> eq = x**2*y
- >>> _lie_group_remove(eq)
- x**2*y
- >>> eq = F(x**2*y)
- >>> _lie_group_remove(eq)
- x**2*y
- >>> eq = x*y**2 + F(x**3)
- >>> _lie_group_remove(eq)
- x*y**2
- >>> eq = (F(x**3) + y)*x**4
- >>> _lie_group_remove(eq)
- x**4*y
- """
- if isinstance(coords, AppliedUndef):
- return coords.args[0]
- elif coords.is_Add:
- subfunc = coords.atoms(AppliedUndef)
- if subfunc:
- for func in subfunc:
- coords = coords.subs(func, 0)
- return coords
- elif coords.is_Pow:
- base, expr = coords.as_base_exp()
- base = _lie_group_remove(base)
- expr = _lie_group_remove(expr)
- return base**expr
- elif coords.is_Mul:
- mulargs = []
- coordargs = coords.args
- for arg in coordargs:
- if not isinstance(coords, AppliedUndef):
- mulargs.append(_lie_group_remove(arg))
- return Mul(*mulargs)
- return coords
|