diff --git a/doc/src/modules/solvers/ode.rst b/doc/src/modules/solvers/ode.rst index 1bc87c03bad8..ca0f1acdbbdf 100644 --- a/doc/src/modules/solvers/ode.rst +++ b/doc/src/modules/solvers/ode.rst @@ -78,18 +78,22 @@ factorable 1st_exact ^^^^^^^^^ .. autoclass:: sympy.solvers.ode.single.FirstExact + :members: 1st_homogeneous_coeff_best ^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.ode::ode_1st_homogeneous_coeff_best +.. autoclass:: sympy.solvers.ode.single::HomogeneousCoeffBest + :members: 1st_homogeneous_coeff_subs_dep_div_indep ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.ode::ode_1st_homogeneous_coeff_subs_dep_div_indep +.. autoclass:: sympy.solvers.ode.single::HomogeneousCoeffSubsDepDivIndep + :members: 1st_homogeneous_coeff_subs_indep_div_dep ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.ode::ode_1st_homogeneous_coeff_subs_indep_div_dep +.. autoclass:: sympy.solvers.ode.single::HomogeneousCoeffSubsIndepDivDep + :members: 1st_linear ^^^^^^^^^^ @@ -152,11 +156,13 @@ almost_linear linear_coefficients ^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.ode::ode_linear_coefficients +.. autoclass:: sympy.solvers.ode.single::LinearCoefficients + :members: separable_reduced ^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.single::SeparableReduced +.. autoclass:: sympy.solvers.ode.single::SeparableReduced + :members: lie_group ^^^^^^^^^ diff --git a/sympy/solvers/ode/ode.py b/sympy/solvers/ode/ode.py index a66baaf9c367..ac6efceabf49 100644 --- a/sympy/solvers/ode/ode.py +++ b/sympy/solvers/ode/ode.py @@ -109,7 +109,7 @@ for each one, as well as a ``_best`` hint. Your ``ode__best()`` function should choose the best using min with ``ode_sol_simplicity`` as the key argument. See -:py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_best`, for example. +:obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest`, for example. The function that uses your method will be called ``ode_()``, so the hint must only use characters that are allowed in a Python function name (alphanumeric characters and the underscore '``_``' character). Include a @@ -1057,6 +1057,10 @@ class in it. Note that a hint may do this anyway if Liouville: ('Liouville',), Separable: ('separable',), SeparableReduced: ('separable_reduced',), + HomogeneousCoeffSubsDepDivIndep: ('1st_homogeneous_coeff_subs_dep_div_indep',), + HomogeneousCoeffSubsIndepDivDep: ('1st_homogeneous_coeff_subs_indep_div_dep',), + HomogeneousCoeffBest: ('1st_homogeneous_coeff_best',), + LinearCoefficients: ('linear_coefficients',), } for solvercls in solvers: solver = solvercls(ode) @@ -1113,75 +1117,6 @@ class in it. Note that a hint may do this anyway if # method matching_hints["lie_group"] = r3 - # This match is used for several cases below; we now collect on - # f(x) so the matching works. - r = collect(reduced_eq, df, exact=True).match(d + e*df) - - if r: - # Using r[d] and r[e] without any modification for hints - # linear-coefficients and separable-reduced. - num, den = r[d], r[e] # ODE = d/e + df - r['d'] = d - r['e'] = e - r['y'] = y - r[d] = num.subs(f(x), y) - r[e] = den.subs(f(x), y) - - ## Separable Case: y' == P(y)*Q(x) - r[d] = separatevars(r[d]) - r[e] = separatevars(r[e]) - - ## First order equation with homogeneous coefficients: - # dy/dx == F(y/x) or dy/dx == F(x/y) - ordera = homogeneous_order(r[d], x, y) - if ordera is not None: - orderb = homogeneous_order(r[e], x, y) - if ordera == orderb: - # u1=y/x and u2=x/y - u1 = Dummy('u1') - u2 = Dummy('u2') - s = "1st_homogeneous_coeff_subs" - s1 = s + "_dep_div_indep" - s2 = s + "_indep_div_dep" - if simplify((r[d] + u1*r[e]).subs({x: 1, y: u1})) != 0: - matching_hints[s1] = r - matching_hints[s1 + "_Integral"] = r - if simplify((r[e] + u2*r[d]).subs({x: u2, y: 1})) != 0: - matching_hints[s2] = r - matching_hints[s2 + "_Integral"] = r - if s1 in matching_hints and s2 in matching_hints: - matching_hints["1st_homogeneous_coeff_best"] = r - - ## Linear coefficients of the form - # y'+ F((a*x + b*y + c)/(a'*x + b'y + c')) = 0 - # that can be reduced to homogeneous form. - F = num/den - params = _linear_coeff_match(F, func) - if params: - xarg, yarg = params - u = Dummy('u') - t = Dummy('t') - # Dummy substitution for df and f(x). - dummy_eq = reduced_eq.subs(((df, t), (f(x), u))) - reps = ((x, x + xarg), (u, u + yarg), (t, df), (u, f(x))) - dummy_eq = simplify(dummy_eq.subs(reps)) - # get the re-cast values for e and d - r2 = collect(expand(dummy_eq), [df, f(x)]).match(e*df + d) - if r2: - orderd = homogeneous_order(r2[d], x, f(x)) - if orderd is not None: - ordere = homogeneous_order(r2[e], x, f(x)) - if orderd == ordere: - # Match arguments are passed in such a way that it - # is coherent with the already existing homogeneous - # functions. - r2[d] = r2[d].subs(f(x), y) - r2[e] = r2[e].subs(f(x), y) - r2.update({'xarg': xarg, 'yarg': yarg, - 'd': d, 'e': e, 'y': y}) - matching_hints["linear_coefficients"] = r2 - matching_hints["linear_coefficients_Integral"] = r2 - elif order == 2: # Homogeneous second order differential equation of the form # a3*f(x).diff(x, 2) + b3*f(x).diff(x) + c3 @@ -2115,13 +2050,13 @@ def odesimp(ode, eq, func, hint): / | | / 1 \ - | -|u2 + -------| + | -|u1 + -------| | | /1 \| | | sin|--|| - | \ \u2// - log(f(x)) = log(C1) + | ---------------- d(u2) + | \ \u1// + log(f(x)) = log(C1) + | ---------------- d(u1) | 2 - | u2 + | u1 | / @@ -2736,252 +2671,6 @@ def _handle_Integral(expr, func, hint): return sol -def ode_1st_homogeneous_coeff_best(eq, func, order, match): - r""" - Returns the best solution to an ODE from the two hints - ``1st_homogeneous_coeff_subs_dep_div_indep`` and - ``1st_homogeneous_coeff_subs_indep_div_dep``. - - This is as determined by :py:meth:`~sympy.solvers.ode.ode.ode_sol_simplicity`. - - See the - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_indep_div_dep` - and - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_dep_div_indep` - docstrings for more information on these hints. Note that there is no - ``ode_1st_homogeneous_coeff_best_Integral`` hint. - - Examples - ======== - - >>> from sympy import Function, dsolve, pprint - >>> from sympy.abc import x - >>> f = Function('f') - >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), - ... hint='1st_homogeneous_coeff_best', simplify=False)) - / 2 \ - | 3*x | - log|----- + 1| - | 2 | - \f (x) / - log(f(x)) = log(C1) - -------------- - 3 - - References - ========== - - - https://en.wikipedia.org/wiki/Homogeneous_differential_equation - - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", - Dover 1963, pp. 59 - - # indirect doctest - - """ - # There are two substitutions that solve the equation, u1=y/x and u2=x/y - # They produce different integrals, so try them both and see which - # one is easier. - sol1 = ode_1st_homogeneous_coeff_subs_indep_div_dep(eq, - func, order, match) - sol2 = ode_1st_homogeneous_coeff_subs_dep_div_indep(eq, - func, order, match) - simplify = match.get('simplify', True) - if simplify: - # why is odesimp called here? Should it be at the usual spot? - sol1 = odesimp(eq, sol1, func, "1st_homogeneous_coeff_subs_indep_div_dep") - sol2 = odesimp(eq, sol2, func, "1st_homogeneous_coeff_subs_dep_div_indep") - return min([sol1, sol2], key=lambda x: ode_sol_simplicity(x, func, - trysolving=not simplify)) - - -def ode_1st_homogeneous_coeff_subs_dep_div_indep(eq, func, order, match): - r""" - Solves a 1st order differential equation with homogeneous coefficients - using the substitution `u_1 = \frac{\text{}}{\text{}}`. - - This is a differential equation - - .. math:: P(x, y) + Q(x, y) dy/dx = 0 - - such that `P` and `Q` are homogeneous and of the same order. A function - `F(x, y)` is homogeneous of order `n` if `F(x t, y t) = t^n F(x, y)`. - Equivalently, `F(x, y)` can be rewritten as `G(y/x)` or `H(x/y)`. See - also the docstring of :py:meth:`~sympy.solvers.ode.homogeneous_order`. - - If the coefficients `P` and `Q` in the differential equation above are - homogeneous functions of the same order, then it can be shown that the - substitution `y = u_1 x` (i.e. `u_1 = y/x`) will turn the differential - equation into an equation separable in the variables `x` and `u`. If - `h(u_1)` is the function that results from making the substitution `u_1 = - f(x)/x` on `P(x, f(x))` and `g(u_2)` is the function that results from the - substitution on `Q(x, f(x))` in the differential equation `P(x, f(x)) + - Q(x, f(x)) f'(x) = 0`, then the general solution is:: - - >>> from sympy import Function, dsolve, pprint - >>> from sympy.abc import x - >>> f, g, h = map(Function, ['f', 'g', 'h']) - >>> genform = g(f(x)/x) + h(f(x)/x)*f(x).diff(x) - >>> pprint(genform) - /f(x)\ /f(x)\ d - g|----| + h|----|*--(f(x)) - \ x / \ x / dx - >>> pprint(dsolve(genform, f(x), - ... hint='1st_homogeneous_coeff_subs_dep_div_indep_Integral')) - f(x) - ---- - x - / - | - | -h(u1) - log(x) = C1 + | ---------------- d(u1) - | u1*h(u1) + g(u1) - | - / - - Where `u_1 h(u_1) + g(u_1) \ne 0` and `x \ne 0`. - - See also the docstrings of - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_best` and - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_indep_div_dep`. - - Examples - ======== - - >>> from sympy import Function, dsolve - >>> from sympy.abc import x - >>> f = Function('f') - >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), - ... hint='1st_homogeneous_coeff_subs_dep_div_indep', simplify=False)) - / 3 \ - |3*f(x) f (x)| - log|------ + -----| - | x 3 | - \ x / - log(x) = log(C1) - ------------------- - 3 - - References - ========== - - - https://en.wikipedia.org/wiki/Homogeneous_differential_equation - - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", - Dover 1963, pp. 59 - - # indirect doctest - - """ - x = func.args[0] - f = func.func - u = Dummy('u') - u1 = Dummy('u1') # u1 == f(x)/x - r = match # d+e*diff(f(x),x) - C1 = get_numbered_constants(eq, num=1) - xarg = match.get('xarg', 0) - yarg = match.get('yarg', 0) - int = Integral( - (-r[r['e']]/(r[r['d']] + u1*r[r['e']])).subs({x: 1, r['y']: u1}), - (u1, None, f(x)/x)) - sol = logcombine(Eq(log(x), int + log(C1)), force=True) - sol = sol.subs(f(x), u).subs(((u, u - yarg), (x, x - xarg), (u, f(x)))) - return sol - - -def ode_1st_homogeneous_coeff_subs_indep_div_dep(eq, func, order, match): - r""" - Solves a 1st order differential equation with homogeneous coefficients - using the substitution `u_2 = \frac{\text{}}{\text{}}`. - - This is a differential equation - - .. math:: P(x, y) + Q(x, y) dy/dx = 0 - - such that `P` and `Q` are homogeneous and of the same order. A function - `F(x, y)` is homogeneous of order `n` if `F(x t, y t) = t^n F(x, y)`. - Equivalently, `F(x, y)` can be rewritten as `G(y/x)` or `H(x/y)`. See - also the docstring of :py:meth:`~sympy.solvers.ode.homogeneous_order`. - - If the coefficients `P` and `Q` in the differential equation above are - homogeneous functions of the same order, then it can be shown that the - substitution `x = u_2 y` (i.e. `u_2 = x/y`) will turn the differential - equation into an equation separable in the variables `y` and `u_2`. If - `h(u_2)` is the function that results from making the substitution `u_2 = - x/f(x)` on `P(x, f(x))` and `g(u_2)` is the function that results from the - substitution on `Q(x, f(x))` in the differential equation `P(x, f(x)) + - Q(x, f(x)) f'(x) = 0`, then the general solution is: - - >>> from sympy import Function, dsolve, pprint - >>> from sympy.abc import x - >>> f, g, h = map(Function, ['f', 'g', 'h']) - >>> genform = g(x/f(x)) + h(x/f(x))*f(x).diff(x) - >>> pprint(genform) - / x \ / x \ d - g|----| + h|----|*--(f(x)) - \f(x)/ \f(x)/ dx - >>> pprint(dsolve(genform, f(x), - ... hint='1st_homogeneous_coeff_subs_indep_div_dep_Integral')) - x - ---- - f(x) - / - | - | -g(u2) - | ---------------- d(u2) - | u2*g(u2) + h(u2) - | - / - - f(x) = C1*e - - Where `u_2 g(u_2) + h(u_2) \ne 0` and `f(x) \ne 0`. - - See also the docstrings of - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_best` and - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_dep_div_indep`. - - Examples - ======== - - >>> from sympy import Function, pprint, dsolve - >>> from sympy.abc import x - >>> f = Function('f') - >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), - ... hint='1st_homogeneous_coeff_subs_indep_div_dep', - ... simplify=False)) - / 2 \ - | 3*x | - log|----- + 1| - | 2 | - \f (x) / - log(f(x)) = log(C1) - -------------- - 3 - - References - ========== - - - https://en.wikipedia.org/wiki/Homogeneous_differential_equation - - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", - Dover 1963, pp. 59 - - # indirect doctest - - """ - x = func.args[0] - f = func.func - u = Dummy('u') - u2 = Dummy('u2') # u2 == x/f(x) - r = match # d+e*diff(f(x),x) - C1 = get_numbered_constants(eq, num=1) - xarg = match.get('xarg', 0) # If xarg present take xarg, else zero - yarg = match.get('yarg', 0) # If yarg present take yarg, else zero - int = Integral( - simplify( - (-r[r['d']]/(r[r['e']] + u2*r[r['d']])).subs({x: u2, r['y']: 1})), - (u2, None, x/f(x))) - sol = logcombine(Eq(log(f(x)), int + log(C1)), force=True) - sol = sol.subs(f(x), u).subs(((u, u - yarg), (x, x - xarg), (u, f(x)))) - return sol - # XXX: Should this function maybe go somewhere else? @@ -2999,8 +2688,8 @@ def homogeneous_order(eq, *symbols): or `H(y/x)`. This fact is used to solve 1st order ordinary differential equations whose coefficients are homogeneous of the same order (see the docstrings of - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_dep_div_indep` and - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_indep_div_dep`). + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`). Symbols can be functions, but every argument of the function must be a symbol, and the arguments of the function that appear in the expression @@ -3988,140 +3677,6 @@ def ode_nth_linear_euler_eq_nonhomogeneous_variation_of_parameters(eq, func, ord sol = _solve_variation_of_parameters(eq, func, order, match) return Eq(f(x), r['sol'].rhs + (sol.rhs - r['sol'].rhs)*r[ode_order(eq, f(x))]) -def _linear_coeff_match(expr, func): - r""" - Helper function to match hint ``linear_coefficients``. - - Matches the expression to the form `(a_1 x + b_1 f(x) + c_1)/(a_2 x + b_2 - f(x) + c_2)` where the following conditions hold: - - 1. `a_1`, `b_1`, `c_1`, `a_2`, `b_2`, `c_2` are Rationals; - 2. `c_1` or `c_2` are not equal to zero; - 3. `a_2 b_1 - a_1 b_2` is not equal to zero. - - Return ``xarg``, ``yarg`` where - - 1. ``xarg`` = `(b_2 c_1 - b_1 c_2)/(a_2 b_1 - a_1 b_2)` - 2. ``yarg`` = `(a_1 c_2 - a_2 c_1)/(a_2 b_1 - a_1 b_2)` - - - Examples - ======== - - >>> from sympy import Function - >>> from sympy.abc import x - >>> from sympy.solvers.ode.ode import _linear_coeff_match - >>> from sympy.functions.elementary.trigonometric import sin - >>> f = Function('f') - >>> _linear_coeff_match(( - ... (-25*f(x) - 8*x + 62)/(4*f(x) + 11*x - 11)), f(x)) - (1/9, 22/9) - >>> _linear_coeff_match( - ... sin((-5*f(x) - 8*x + 6)/(4*f(x) + x - 1)), f(x)) - (19/27, 2/27) - >>> _linear_coeff_match(sin(f(x)/x), f(x)) - - """ - f = func.func - x = func.args[0] - def abc(eq): - r''' - Internal function of _linear_coeff_match - that returns Rationals a, b, c - if eq is a*x + b*f(x) + c, else None. - ''' - eq = _mexpand(eq) - c = eq.as_independent(x, f(x), as_Add=True)[0] - if not c.is_Rational: - return - a = eq.coeff(x) - if not a.is_Rational: - return - b = eq.coeff(f(x)) - if not b.is_Rational: - return - if eq == a*x + b*f(x) + c: - return a, b, c - - def match(arg): - r''' - Internal function of _linear_coeff_match that returns Rationals a1, - b1, c1, a2, b2, c2 and a2*b1 - a1*b2 of the expression (a1*x + b1*f(x) - + c1)/(a2*x + b2*f(x) + c2) if one of c1 or c2 and a2*b1 - a1*b2 is - non-zero, else None. - ''' - n, d = arg.together().as_numer_denom() - m = abc(n) - if m is not None: - a1, b1, c1 = m - m = abc(d) - if m is not None: - a2, b2, c2 = m - d = a2*b1 - a1*b2 - if (c1 or c2) and d: - return a1, b1, c1, a2, b2, c2, d - - m = [fi.args[0] for fi in expr.atoms(Function) if fi.func != f and - len(fi.args) == 1 and not fi.args[0].is_Function] or {expr} - m1 = match(m.pop()) - if m1 and all(match(mi) == m1 for mi in m): - a1, b1, c1, a2, b2, c2, denom = m1 - return (b2*c1 - b1*c2)/denom, (a1*c2 - a2*c1)/denom - -def ode_linear_coefficients(eq, func, order, match): - r""" - Solves a differential equation with linear coefficients. - - The general form of a differential equation with linear coefficients is - - .. math:: y' + F\left(\!\frac{a_1 x + b_1 y + c_1}{a_2 x + b_2 y + - c_2}\!\right) = 0\text{,} - - where `a_1`, `b_1`, `c_1`, `a_2`, `b_2`, `c_2` are constants and `a_1 b_2 - - a_2 b_1 \ne 0`. - - This can be solved by substituting: - - .. math:: x = x' + \frac{b_2 c_1 - b_1 c_2}{a_2 b_1 - a_1 b_2} - - y = y' + \frac{a_1 c_2 - a_2 c_1}{a_2 b_1 - a_1 - b_2}\text{.} - - This substitution reduces the equation to a homogeneous differential - equation. - - See Also - ======== - :meth:`sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_best` - :meth:`sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_indep_div_dep` - :meth:`sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_dep_div_indep` - - Examples - ======== - - >>> from sympy import Function, pprint - >>> from sympy.solvers.ode.ode import dsolve - >>> from sympy.abc import x - >>> f = Function('f') - >>> df = f(x).diff(x) - >>> eq = (x + f(x) + 1)*df + (f(x) - 6*x + 1) - >>> dsolve(eq, hint='linear_coefficients') - [Eq(f(x), -x - sqrt(C1 + 7*x**2) - 1), Eq(f(x), -x + sqrt(C1 + 7*x**2) - 1)] - >>> pprint(dsolve(eq, hint='linear_coefficients')) - ___________ ___________ - / 2 / 2 - [f(x) = -x - \/ C1 + 7*x - 1, f(x) = -x + \/ C1 + 7*x - 1] - - - References - ========== - - - Joel Moses, "Symbolic Integration - The Stormy Decade", Communications - of the ACM, Volume 14, Number 8, August 1971, pp. 558 - """ - - return ode_1st_homogeneous_coeff_best(eq, func, order, match) - def ode_1st_power_series(eq, func, order, match): r""" @@ -6674,4 +6229,6 @@ def _nonlinear_3eq_order1_type5(x, y, z, t, eq): #This import is written at the bottom to avoid circular imports. from .single import (NthAlgebraic, Factorable, FirstLinear, AlmostLinear, Bernoulli, SingleODEProblem, SingleODESolver, RiccatiSpecial, - SecondNonlinearAutonomousConserved, FirstExact, Liouville, Separable, SeparableReduced) + SecondNonlinearAutonomousConserved, FirstExact, Liouville, Separable, + SeparableReduced, HomogeneousCoeffSubsDepDivIndep, HomogeneousCoeffSubsIndepDivDep, + HomogeneousCoeffBest, LinearCoefficients) diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 1aa13abeb873..393002cc4053 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -12,7 +12,7 @@ from sympy.core import Add, S, Pow from sympy.core.exprtools import factor_terms from sympy.core.expr import Expr -from sympy.core.function import AppliedUndef, Derivative, Function, expand, Subs +from sympy.core.function import AppliedUndef, Derivative, Function, expand, Subs, _mexpand from sympy.core.numbers import Float, zoo from sympy.core.relational import Equality, Eq from sympy.core.symbol import Symbol, Dummy, Wild @@ -20,10 +20,9 @@ from sympy.functions import exp, sqrt, tan, log from sympy.integrals import Integral from sympy.polys.polytools import cancel, factor -from sympy.simplify import simplify, separatevars +from sympy.simplify import collect, simplify, separatevars, logcombine from sympy.simplify.radsimp import fraction from sympy.utilities import numbered_symbols - from sympy.solvers.solvers import solve from sympy.solvers.deutils import ode_order, _preprocess @@ -220,13 +219,13 @@ def get_general_solution(self, *, simplify: bool = True) -> List[Equality]: if not self.matches(): msg = "%s solver can not solve:\n%s" raise ODEMatchError(msg % (self.hint, self.ode_problem.eq)) - return self._get_general_solution() + return self._get_general_solution(simplify_flag=simplify) def _matches(self) -> bool: msg = "Subclasses of SingleODESolver should implement matches." raise NotImplementedError(msg) - def _get_general_solution(self, *, simplify: bool = True) -> List[Equality]: + def _get_general_solution(self, *, simplify_flag: bool = True) -> List[Equality]: msg = "Subclasses of SingleODESolver should implement get_general_solution." raise NotImplementedError(msg) @@ -352,7 +351,7 @@ def unreplace(eq, var): self.solutions = solns return len(solns) != 0 - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): return self.solutions # This needs to produce an invertible function but the inverse depends @@ -494,7 +493,7 @@ def _verify(self, fx) -> bool: self._wilds_match[Q] = n.subs(y, fx) return True - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): m, n = self.wilds_match() fx = self.ode_problem.func x = self.ode_problem.sym @@ -573,7 +572,7 @@ def _equation(self, fx, x, order): P, Q = self.wilds() return fx.diff(x) + P*fx - Q - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): P, Q = self.wilds_match() fx = self.ode_problem.func x = self.ode_problem.sym @@ -599,7 +598,7 @@ class AlmostLinear(SinglePatternODESolver): See Also ======== - :meth:`sympy.solvers.ode.single.FirstLinear` + :obj:`sympy.solvers.ode.single.FirstLinear` Examples ======== @@ -661,7 +660,7 @@ def _verify(self, fx): return False - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): x = self.ode_problem.sym (C1,) = self.ode_problem.get_numbered_constants(num=1) gensol = Eq(self.ly, ((C1 + Integral((self.cx/self.ax)*exp(Integral(self.bx/self.ax, x)),x)) @@ -680,7 +679,7 @@ class Bernoulli(SinglePatternODESolver): The substitution `w = 1/y^{1-n}` will transform an equation of this form into one that is linear (see the docstring of - :py:meth:`~sympy.solvers.ode.single.FirstLinear`). The general solution is:: + :obj:`~sympy.solvers.ode.single.FirstLinear`). The general solution is:: >>> from sympy import Function, dsolve, Eq, pprint >>> from sympy.abc import x, n @@ -707,7 +706,7 @@ class Bernoulli(SinglePatternODESolver): Note that the equation is separable when `n = 1` (see the docstring of - :py:meth:`~sympy.solvers.ode.single.Separable`). + :obj:`~sympy.solvers.ode.single.Separable`). >>> pprint(dsolve(Eq(f(x).diff(x) + P(x)*f(x), Q(x)*f(x)), f(x), ... hint='separable_Integral')) @@ -759,7 +758,7 @@ def _equation(self, fx, x, order): P, Q, n = self.wilds() return fx.diff(x) + P*fx - Q*fx**n - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): P, Q, n = self.wilds_match() fx = self.ode_problem.func x = self.ode_problem.sym @@ -831,7 +830,7 @@ def _matches(self): return False - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): func = self.ode_problem.func.func x = self.ode_problem.sym eqns = self.eqs @@ -907,7 +906,7 @@ def _equation(self, fx, x, order): a, b, c, d = self.wilds() return a*fx.diff(x) + b*fx**2 + c*fx/x + d/x**2 - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): a, b, c, d = self.wilds_match() fx = self.ode_problem.func x = self.ode_problem.sym @@ -967,7 +966,7 @@ def _equation(self, fx, x, order): def _verify(self, fx): return self.ode_problem.is_autonomous - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): g = self.wilds_match()[0] fx = self.ode_problem.func x = self.ode_problem.sym @@ -1064,7 +1063,7 @@ def _verify(self, fx): return False return True - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): d, e, k = self.wilds_match() fx = self.ode_problem.func x = self.ode_problem.sym @@ -1161,7 +1160,7 @@ def _get_match_object(self): x = self.ode_problem.sym return self.m1, self.m2, x, fx - def _get_general_solution(self, *, simplify: bool = True): + def _get_general_solution(self, *, simplify_flag: bool = True): m1, m2, x, fx = self._get_match_object() (C1, ) = self.ode_problem.get_numbered_constants(num=1) int = Integral(m2['coeff']*m2[self.y]/m1[self.y], @@ -1207,7 +1206,7 @@ class SeparableReduced(Separable): See Also ======== - :meth:`sympy.solvers.ode.single.Separable` + :obj:`sympy.solvers.ode.single.Separable` Examples ======== @@ -1310,6 +1309,501 @@ def _get_match_object(self): m2 = {self.y: ycoeff, x: 1, 'coeff': 1} return m1, m2, x, x**self.r2['power']*fx +class HomogeneousCoeffSubsDepDivIndep(SinglePatternODESolver): + r""" + Solves a 1st order differential equation with homogeneous coefficients + using the substitution `u_1 = \frac{\text{}}{\text{}}`. + + This is a differential equation + + .. math:: P(x, y) + Q(x, y) dy/dx = 0 + + such that `P` and `Q` are homogeneous and of the same order. A function + `F(x, y)` is homogeneous of order `n` if `F(x t, y t) = t^n F(x, y)`. + Equivalently, `F(x, y)` can be rewritten as `G(y/x)` or `H(x/y)`. See + also the docstring of :py:meth:`~sympy.solvers.ode.homogeneous_order`. + + If the coefficients `P` and `Q` in the differential equation above are + homogeneous functions of the same order, then it can be shown that the + substitution `y = u_1 x` (i.e. `u_1 = y/x`) will turn the differential + equation into an equation separable in the variables `x` and `u`. If + `h(u_1)` is the function that results from making the substitution `u_1 = + f(x)/x` on `P(x, f(x))` and `g(u_2)` is the function that results from the + substitution on `Q(x, f(x))` in the differential equation `P(x, f(x)) + + Q(x, f(x)) f'(x) = 0`, then the general solution is:: + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f, g, h = map(Function, ['f', 'g', 'h']) + >>> genform = g(f(x)/x) + h(f(x)/x)*f(x).diff(x) + >>> pprint(genform) + /f(x)\ /f(x)\ d + g|----| + h|----|*--(f(x)) + \ x / \ x / dx + >>> pprint(dsolve(genform, f(x), + ... hint='1st_homogeneous_coeff_subs_dep_div_indep_Integral')) + f(x) + ---- + x + / + | + | -h(u1) + log(x) = C1 + | ---------------- d(u1) + | u1*h(u1) + g(u1) + | + / + + Where `u_1 h(u_1) + g(u_1) \ne 0` and `x \ne 0`. + + See also the docstrings of + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`. + + Examples + ======== + + >>> from sympy import Function, dsolve + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), + ... hint='1st_homogeneous_coeff_subs_dep_div_indep', simplify=False)) + / 3 \ + |3*f(x) f (x)| + log|------ + -----| + | x 3 | + \ x / + log(x) = log(C1) - ------------------- + 3 + + References + ========== + + - https://en.wikipedia.org/wiki/Homogeneous_differential_equation + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 59 + + # indirect doctest + + """ + hint = "1st_homogeneous_coeff_subs_dep_div_indep" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) + e = Wild('e', exclude=[f(x).diff(x)]) + return d, e + + def _equation(self, fx, x, order): + d, e = self.wilds() + return d + e*fx.diff(x) + + def _verify(self, fx): + self.d, self.e = self.wilds_match() + self.y = Dummy('y') + x = self.ode_problem.sym + self.d = separatevars(self.d.subs(fx, self.y)) + self.e = separatevars(self.e.subs(fx, self.y)) + ordera = homogeneous_order(self.d, x, self.y) + orderb = homogeneous_order(self.e, x, self.y) + if ordera == orderb and ordera is not None: + self.u = Dummy('u') + if simplify((self.d + self.u*self.e).subs({x: 1, self.y: self.u})) != 0: + return True + return False + return False + + def _get_match_object(self): + fx = self.ode_problem.func + x = self.ode_problem.sym + self.u1 = Dummy('u1') + xarg = 0 + yarg = 0 + return [self.d, self.e, fx, x, self.u, self.u1, self.y, xarg, yarg] + + def _get_general_solution(self, *, simplify_flag: bool = True): + d, e, fx, x, u, u1, y, xarg, yarg = self._get_match_object() + (C1, ) = self.ode_problem.get_numbered_constants(num=1) + int = Integral( + (-e/(d + u1*e)).subs({x: 1, y: u1}), + (u1, None, fx/x)) + sol = logcombine(Eq(log(x), int + log(C1)), force=True) + gen_sol = sol.subs(fx, u).subs(((u, u - yarg), (x, x - xarg), (u, fx))) + return [gen_sol] + + +class HomogeneousCoeffSubsIndepDivDep(SinglePatternODESolver): + r""" + Solves a 1st order differential equation with homogeneous coefficients + using the substitution `u_2 = \frac{\text{}}{\text{}}`. + + This is a differential equation + + .. math:: P(x, y) + Q(x, y) dy/dx = 0 + + such that `P` and `Q` are homogeneous and of the same order. A function + `F(x, y)` is homogeneous of order `n` if `F(x t, y t) = t^n F(x, y)`. + Equivalently, `F(x, y)` can be rewritten as `G(y/x)` or `H(x/y)`. See + also the docstring of :py:meth:`~sympy.solvers.ode.homogeneous_order`. + + If the coefficients `P` and `Q` in the differential equation above are + homogeneous functions of the same order, then it can be shown that the + substitution `x = u_2 y` (i.e. `u_2 = x/y`) will turn the differential + equation into an equation separable in the variables `y` and `u_2`. If + `h(u_2)` is the function that results from making the substitution `u_2 = + x/f(x)` on `P(x, f(x))` and `g(u_2)` is the function that results from the + substitution on `Q(x, f(x))` in the differential equation `P(x, f(x)) + + Q(x, f(x)) f'(x) = 0`, then the general solution is: + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f, g, h = map(Function, ['f', 'g', 'h']) + >>> genform = g(x/f(x)) + h(x/f(x))*f(x).diff(x) + >>> pprint(genform) + / x \ / x \ d + g|----| + h|----|*--(f(x)) + \f(x)/ \f(x)/ dx + >>> pprint(dsolve(genform, f(x), + ... hint='1st_homogeneous_coeff_subs_indep_div_dep_Integral')) + x + ---- + f(x) + / + | + | -g(u1) + | ---------------- d(u1) + | u1*g(u1) + h(u1) + | + / + + f(x) = C1*e + + Where `u_1 g(u_1) + h(u_1) \ne 0` and `f(x) \ne 0`. + + See also the docstrings of + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep`. + + Examples + ======== + + >>> from sympy import Function, pprint, dsolve + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), + ... hint='1st_homogeneous_coeff_subs_indep_div_dep', + ... simplify=False)) + / 2 \ + | 3*x | + log|----- + 1| + | 2 | + \f (x) / + log(f(x)) = log(C1) - -------------- + 3 + + References + ========== + + - https://en.wikipedia.org/wiki/Homogeneous_differential_equation + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 59 + + # indirect doctest + + """ + hint = "1st_homogeneous_coeff_subs_indep_div_dep" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) + e = Wild('e', exclude=[f(x).diff(x)]) + return d, e + + def _equation(self, fx, x, order): + d, e = self.wilds() + return d + e*fx.diff(x) + + def _verify(self, fx): + self.d, self.e = self.wilds_match() + self.y = Dummy('y') + x = self.ode_problem.sym + self.d = separatevars(self.d.subs(fx, self.y)) + self.e = separatevars(self.e.subs(fx, self.y)) + ordera = homogeneous_order(self.d, x, self.y) + orderb = homogeneous_order(self.e, x, self.y) + if ordera == orderb and ordera is not None: + self.u = Dummy('u') + if simplify((self.e + self.u*self.d).subs({x: self.u, self.y: 1})) != 0: + return True + return False + return False + + def _get_match_object(self): + fx = self.ode_problem.func + x = self.ode_problem.sym + self.u1 = Dummy('u1') + xarg = 0 + yarg = 0 + return [self.d, self.e, fx, x, self.u, self.u1, self.y, xarg, yarg] + + def _get_general_solution(self, *, simplify_flag: bool = True): + d, e, fx, x, u, u1, y, xarg, yarg = self._get_match_object() + (C1, ) = self.ode_problem.get_numbered_constants(num=1) + int = Integral(simplify((-d/(e + u1*d)).subs({x: u1, y: 1})),(u1, None, x/fx)) + sol = logcombine(Eq(log(fx), int + log(C1)), force=True) + gen_sol = sol.subs(fx, u).subs(((u, u - yarg), (x, x - xarg), (u, fx))) + return [gen_sol] + + +class HomogeneousCoeffBest(HomogeneousCoeffSubsIndepDivDep, HomogeneousCoeffSubsDepDivIndep): + r""" + Returns the best solution to an ODE from the two hints + ``1st_homogeneous_coeff_subs_dep_div_indep`` and + ``1st_homogeneous_coeff_subs_indep_div_dep``. + + This is as determined by :py:meth:`~sympy.solvers.ode.ode.ode_sol_simplicity`. + + See the + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` + and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` + docstrings for more information on these hints. Note that there is no + ``ode_1st_homogeneous_coeff_best_Integral`` hint. + + Examples + ======== + + >>> from sympy import Function, dsolve, pprint + >>> from sympy.abc import x + >>> f = Function('f') + >>> pprint(dsolve(2*x*f(x) + (x**2 + f(x)**2)*f(x).diff(x), f(x), + ... hint='1st_homogeneous_coeff_best', simplify=False)) + / 2 \ + | 3*x | + log|----- + 1| + | 2 | + \f (x) / + log(f(x)) = log(C1) - -------------- + 3 + + References + ========== + + - https://en.wikipedia.org/wiki/Homogeneous_differential_equation + - M. Tenenbaum & H. Pollard, "Ordinary Differential Equations", + Dover 1963, pp. 59 + + # indirect doctest + + """ + hint = "1st_homogeneous_coeff_best" + has_integral = False + order = [1] + + def _verify(self, fx): + if HomogeneousCoeffSubsIndepDivDep._verify(self, fx) and HomogeneousCoeffSubsDepDivIndep._verify(self, fx): + return True + return False + + def _get_general_solution(self, *, simplify_flag: bool = True): + # There are two substitutions that solve the equation, u1=y/x and u2=x/y + # # They produce different integrals, so try them both and see which + # # one is easier + sol1 = HomogeneousCoeffSubsIndepDivDep._get_general_solution(self) + sol2 = HomogeneousCoeffSubsDepDivIndep._get_general_solution(self) + fx = self.ode_problem.func + if simplify_flag: + sol1 = odesimp(self.ode_problem.eq, *sol1, fx, "1st_homogeneous_coeff_subs_indep_div_dep") + sol2 = odesimp(self.ode_problem.eq, *sol2, fx, "1st_homogeneous_coeff_subs_dep_div_indep") + return min([sol1, sol2], key=lambda x: ode_sol_simplicity(x, fx, trysolving=not simplify)) + + +class LinearCoefficients(HomogeneousCoeffBest): + r""" + Solves a differential equation with linear coefficients. + + The general form of a differential equation with linear coefficients is + + .. math:: y' + F\left(\!\frac{a_1 x + b_1 y + c_1}{a_2 x + b_2 y + + c_2}\!\right) = 0\text{,} + + where `a_1`, `b_1`, `c_1`, `a_2`, `b_2`, `c_2` are constants and `a_1 b_2 + - a_2 b_1 \ne 0`. + + This can be solved by substituting: + + .. math:: x = x' + \frac{b_2 c_1 - b_1 c_2}{a_2 b_1 - a_1 b_2} + + y = y' + \frac{a_1 c_2 - a_2 c_1}{a_2 b_1 - a_1 + b_2}\text{.} + + This substitution reduces the equation to a homogeneous differential + equation. + + See Also + ======== + :obj:`sympy.solvers.ode.single.HomogeneousCoeffBest` + :obj:`sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` + :obj:`sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` + + Examples + ======== + + >>> from sympy import Function, pprint + >>> from sympy.solvers.ode.ode import dsolve + >>> from sympy.abc import x + >>> f = Function('f') + >>> df = f(x).diff(x) + >>> eq = (x + f(x) + 1)*df + (f(x) - 6*x + 1) + >>> dsolve(eq, hint='linear_coefficients') + [Eq(f(x), -x - sqrt(C1 + 7*x**2) - 1), Eq(f(x), -x + sqrt(C1 + 7*x**2) - 1)] + >>> pprint(dsolve(eq, hint='linear_coefficients')) + ___________ ___________ + / 2 / 2 + [f(x) = -x - \/ C1 + 7*x - 1, f(x) = -x + \/ C1 + 7*x - 1] + + + References + ========== + + - Joel Moses, "Symbolic Integration - The Stormy Decade", Communications + of the ACM, Volume 14, Number 8, August 1971, pp. 558 + """ + hint = "linear_coefficients" + has_integral = True + order = [1] + + def _wilds(self, f, x, order): + d = Wild('d', exclude=[f(x).diff(x), f(x).diff(x, 2)]) + e = Wild('e', exclude=[f(x).diff(x)]) + return d, e + + def _equation(self, fx, x, order): + d, e = self.wilds() + return d + e*fx.diff(x) + + def _verify(self, fx): + self.d, self.e = self.wilds_match() + a, b = self.wilds() + F = self.d/self.e + x = self.ode_problem.sym + params = self._linear_coeff_match(F, fx) + if params: + self.xarg, self.yarg = params + u = Dummy('u') + t = Dummy('t') + self.y = Dummy('y') + # Dummy substitution for df and f(x). + dummy_eq = self.ode_problem.eq.subs(((fx.diff(x), t), (fx, u))) + reps = ((x, x + self.xarg), (u, u + self.yarg), (t, fx.diff(x)), (u, fx)) + dummy_eq = simplify(dummy_eq.subs(reps)) + # get the re-cast values for e and d + r2 = collect(expand(dummy_eq), [fx.diff(x), fx]).match(a*fx.diff(x) + b) + if r2: + self.d, self.e = r2[b], r2[a] + orderd = homogeneous_order(self.d, x, fx) + ordere = homogeneous_order(self.e, x, fx) + if orderd == ordere and orderd is not None: + self.d = self.d.subs(fx, self.y) + self.e = self.e.subs(fx, self.y) + return True + return False + return False + + def _linear_coeff_match(self,expr, func): + r""" + Helper function to match hint ``linear_coefficients``. + + Matches the expression to the form `(a_1 x + b_1 f(x) + c_1)/(a_2 x + b_2 + f(x) + c_2)` where the following conditions hold: + + 1. `a_1`, `b_1`, `c_1`, `a_2`, `b_2`, `c_2` are Rationals; + 2. `c_1` or `c_2` are not equal to zero; + 3. `a_2 b_1 - a_1 b_2` is not equal to zero. + + Return ``xarg``, ``yarg`` where + + 1. ``xarg`` = `(b_2 c_1 - b_1 c_2)/(a_2 b_1 - a_1 b_2)` + 2. ``yarg`` = `(a_1 c_2 - a_2 c_1)/(a_2 b_1 - a_1 b_2)` + + + Examples + ======== + + >>> from sympy import Function + >>> from sympy.abc import x + >>> from sympy.solvers.ode.single import LinearCoefficients + >>> from sympy.functions.elementary.trigonometric import sin + >>> f = Function('f') + >>> eq = (-25*f(x) - 8*x + 62)/(4*f(x) + 11*x - 11) + >>> obj = LinearCoefficients(eq) + >>> obj._linear_coeff_match(eq, f(x)) + (1/9, 22/9) + >>> eq = sin((-5*f(x) - 8*x + 6)/(4*f(x) + x - 1)) + >>> obj = LinearCoefficients(eq) + >>> obj._linear_coeff_match(eq, f(x)) + (19/27, 2/27) + >>> eq = sin(f(x)/x) + >>> obj = LinearCoefficients(eq) + >>> obj._linear_coeff_match(eq, f(x)) + + """ + f = func.func + x = func.args[0] + def abc(eq): + r''' + Internal function of _linear_coeff_match + that returns Rationals a, b, c + if eq is a*x + b*f(x) + c, else None. + ''' + eq = _mexpand(eq) + c = eq.as_independent(x, f(x), as_Add=True)[0] + if not c.is_Rational: + return + a = eq.coeff(x) + if not a.is_Rational: + return + b = eq.coeff(f(x)) + if not b.is_Rational: + return + if eq == a*x + b*f(x) + c: + return a, b, c + + def match(arg): + r''' + Internal function of _linear_coeff_match that returns Rationals a1, + b1, c1, a2, b2, c2 and a2*b1 - a1*b2 of the expression (a1*x + b1*f(x) + + c1)/(a2*x + b2*f(x) + c2) if one of c1 or c2 and a2*b1 - a1*b2 is + non-zero, else None. + ''' + n, d = arg.together().as_numer_denom() + m = abc(n) + if m is not None: + a1, b1, c1 = m + m = abc(d) + if m is not None: + a2, b2, c2 = m + d = a2*b1 - a1*b2 + if (c1 or c2) and d: + return a1, b1, c1, a2, b2, c2, d + + m = [fi.args[0] for fi in expr.atoms(Function) if fi.func != f and + len(fi.args) == 1 and not fi.args[0].is_Function] or {expr} + m1 = match(m.pop()) + if m1 and all(match(mi) == m1 for mi in m): + a1, b1, c1, a2, b2, c2, denom = m1 + return (b2*c1 - b1*c2)/denom, (a1*c2 - a2*c1)/denom + + def _get_match_object(self): + fx = self.ode_problem.func + x = self.ode_problem.sym + self.u1 = Dummy('u1') + u = Dummy('u') + return [self.d, self.e, fx, x, u, self.u1, self.y, self.xarg, self.yarg] + # Avoid circular import: -from .ode import dsolve +from .ode import dsolve, ode_sol_simplicity, odesimp, homogeneous_order diff --git a/sympy/solvers/ode/tests/test_ode.py b/sympy/solvers/ode/tests/test_ode.py index 5e1935e254ab..9bc3f9ae3db4 100644 --- a/sympy/solvers/ode/tests/test_ode.py +++ b/sympy/solvers/ode/tests/test_ode.py @@ -7,11 +7,10 @@ homogeneous_order, dsolve) from sympy.solvers.ode.subscheck import checkodesol -from sympy.solvers.ode.ode import (_linear_coeff_match, - _undetermined_coefficients_match, classify_sysode, +from sympy.solvers.ode.ode import (_undetermined_coefficients_match, classify_sysode, constant_renumber, constantsimp, get_numbered_constants, solve_ics) - +from sympy.solvers.ode.single import LinearCoefficients from sympy.solvers.deutils import ode_order from sympy.testing.pytest import XFAIL, raises, slow @@ -815,25 +814,32 @@ def test_linear_coeff_match(): n, d = z*(2*x + 3*f(x) + 5), z*(7*x + 9*f(x) + 11) rat = n/d eq1 = sin(rat) + cos(rat.expand()) + obj1 = LinearCoefficients(eq1) eq2 = rat + obj2 = LinearCoefficients(eq2) eq3 = log(sin(rat)) + obj3 = LinearCoefficients(eq3) ans = (4, Rational(-13, 3)) - assert _linear_coeff_match(eq1, f(x)) == ans - assert _linear_coeff_match(eq2, f(x)) == ans - assert _linear_coeff_match(eq3, f(x)) == ans + assert obj1._linear_coeff_match(eq1, f(x)) == ans + assert obj2._linear_coeff_match(eq2, f(x)) == ans + assert obj3._linear_coeff_match(eq3, f(x)) == ans # no c eq4 = (3*x)/f(x) + obj4 = LinearCoefficients(eq4) # not x and f(x) eq5 = (3*x + 2)/x + obj5 = LinearCoefficients(eq5) # denom will be zero eq6 = (3*x + 2*f(x) + 1)/(3*x + 2*f(x) + 5) + obj6 = LinearCoefficients(eq6) # not rational coefficient eq7 = (3*x + 2*f(x) + sqrt(2))/(3*x + 2*f(x) + 5) - assert _linear_coeff_match(eq4, f(x)) is None - assert _linear_coeff_match(eq5, f(x)) is None - assert _linear_coeff_match(eq6, f(x)) is None - assert _linear_coeff_match(eq7, f(x)) is None + obj7 = LinearCoefficients(eq7) + assert obj4._linear_coeff_match(eq4, f(x)) is None + assert obj5._linear_coeff_match(eq5, f(x)) is None + assert obj6._linear_coeff_match(eq6, f(x)) is None + assert obj7._linear_coeff_match(eq7, f(x)) is None def test_constantsimp_take_problem():