From 23a452182fdeb22a78b8e8959df25c8c12e34867 Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Tue, 8 Jun 2021 12:20:40 +0530 Subject: [PATCH 01/11] refactor dep-div-indep hint --- doc/src/modules/solvers/ode.rst | 3 +- sympy/solvers/ode/ode.py | 91 +------------ sympy/solvers/ode/single.py | 218 +++++++++++++++++++++++++++++++- 3 files changed, 224 insertions(+), 88 deletions(-) diff --git a/doc/src/modules/solvers/ode.rst b/doc/src/modules/solvers/ode.rst index 1bc87c03bad8..06844933d754 100644 --- a/doc/src/modules/solvers/ode.rst +++ b/doc/src/modules/solvers/ode.rst @@ -85,7 +85,8 @@ factorable 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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/sympy/solvers/ode/ode.py b/sympy/solvers/ode/ode.py index a66baaf9c367..cfc4a9e92d66 100644 --- a/sympy/solvers/ode/ode.py +++ b/sympy/solvers/ode/ode.py @@ -1057,6 +1057,7 @@ 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',), } for solvercls in solvers: solver = solvercls(ode) @@ -1138,14 +1139,10 @@ class in it. Note that a hint may do this anyway if 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 @@ -2747,7 +2744,7 @@ def ode_1st_homogeneous_coeff_best(eq, func, order, match): 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` + :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` docstrings for more information on these hints. Note that there is no ``ode_1st_homogeneous_coeff_best_Integral`` hint. @@ -2794,82 +2791,6 @@ def ode_1st_homogeneous_coeff_best(eq, func, order, match): 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') @@ -2937,7 +2858,7 @@ def ode_1st_homogeneous_coeff_subs_indep_div_dep(eq, func, order, match): 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`. + :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep`. Examples ======== @@ -2999,7 +2920,7 @@ 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.single.HomogeneousCoeffSubsDepDivIndep` and :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_indep_div_dep`). Symbols can be functions, but every argument of the function must be a @@ -4094,7 +4015,7 @@ def ode_linear_coefficients(eq, func, order, match): ======== :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` + :meth:`sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` Examples ======== @@ -6674,4 +6595,4 @@ 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) diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 1aa13abeb873..8c7e8572407a 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -20,10 +20,11 @@ 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 simplify, separatevars, logcombine from sympy.simplify.radsimp import fraction +from sympy.core.sympify import sympify from sympy.utilities import numbered_symbols - +from sympy.series import Order from sympy.solvers.solvers import solve from sympy.solvers.deutils import ode_order, _preprocess @@ -141,6 +142,96 @@ def is_autonomous(self): syms = self.eq.subs(self.func, u).free_symbols return x not in syms + def homogeneous_order(self, eq, *symbols): + r""" + Returns the order `n` if `g` is homogeneous and ``None`` if it is not + homogeneous. + + Determines if a function is homogeneous and if so of what order. A + function `f(x, y, \cdots)` is homogeneous of order `n` if `f(t x, t y, + \cdots) = t^n f(x, y, \cdots)`. + + If the function is of two variables, `F(x, y)`, then `f` being homogeneous + of any order is equivalent to being able to rewrite `F(x, y)` as `G(x/y)` + 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`). + + 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 + must match those given in the list of symbols. If a declared function + appears with different arguments than given in the list of symbols, + ``None`` is returned. + + Examples + ======== + + >>> from sympy import Function, homogeneous_order, sqrt + >>> from sympy.abc import x, y + >>> f = Function('f') + >>> homogeneous_order(f(x), f(x)) is None + True + >>> homogeneous_order(f(x,y), f(y, x), x, y) is None + True + >>> homogeneous_order(f(x), f(x), x) + 1 + >>> homogeneous_order(x**2*f(x)/sqrt(x**2+f(x)**2), x, f(x)) + 2 + >>> homogeneous_order(x**2+f(x), x, f(x)) is None + True + + """ + + if not symbols: + raise ValueError("homogeneous_order: no symbols were given.") + symset = set(symbols) + eq = sympify(eq) + + # The following are not supported + if eq.has(Order, Derivative): + return None + + # These are all constants + if (eq.is_Number or + eq.is_NumberSymbol or + eq.is_number + ): + return S.Zero + + # Replace all functions with dummy variables + dum = numbered_symbols(prefix='d', cls=Dummy) + newsyms = set() + for i in [j for j in symset if getattr(j, 'is_Function')]: + iargs = set(i.args) + if iargs.difference(symset): + return None + else: + dummyvar = next(dum) + eq = eq.subs(i, dummyvar) + symset.remove(i) + newsyms.add(dummyvar) + symset.update(newsyms) + + if not eq.free_symbols & symset: + return None + + # assuming order of a nested function can only be equal to zero + if isinstance(eq, Function): + return None if self.homogeneous_order( + eq.args[0], *tuple(symset)) != 0 else S.Zero + + # make the replacement of x with x*t and see if t can be factored out + t = Dummy('t', positive=True) # It is sufficient that t > 0 + eqs = separatevars(eq.subs([(i, t*i) for i in symset]), [t], dict=True)[t] + if eqs is S.One: + return S.Zero # there was no term with only t + i, d = eqs.as_independent(t, as_Add=False) + b, e = d.as_base_exp() + if b == t: + return e + # TODO: Add methods that can be used by many ODE solvers: # order # is_linear() @@ -1310,6 +1401,129 @@ 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 + :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 + + """ + 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 = self.ode_problem.homogeneous_order(self.d, x, self.y) + orderb = self.ode_problem.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: 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] + # Avoid circular import: from .ode import dsolve From 7ddf5de3dd8e946ebde819b8c1a0f35a59200aad Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Tue, 8 Jun 2021 16:29:45 +0530 Subject: [PATCH 02/11] refactor indep-div-dep --- doc/src/modules/solvers/ode.rst | 3 +- sympy/solvers/ode/ode.py | 105 +++---------------------- sympy/solvers/ode/single.py | 131 +++++++++++++++++++++++++++++++- 3 files changed, 139 insertions(+), 100 deletions(-) diff --git a/doc/src/modules/solvers/ode.rst b/doc/src/modules/solvers/ode.rst index 06844933d754..1361c58bcce2 100644 --- a/doc/src/modules/solvers/ode.rst +++ b/doc/src/modules/solvers/ode.rst @@ -90,7 +90,8 @@ factorable 1st_homogeneous_coeff_subs_indep_div_dep ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.ode::ode_1st_homogeneous_coeff_subs_indep_div_dep +.. autofunction:: sympy.solvers.ode.single::HomogeneousCoeffSubsIndepDivDep + :members: 1st_linear ^^^^^^^^^^ diff --git a/sympy/solvers/ode/ode.py b/sympy/solvers/ode/ode.py index cfc4a9e92d66..df13c5ec6e85 100644 --- a/sympy/solvers/ode/ode.py +++ b/sympy/solvers/ode/ode.py @@ -1058,6 +1058,7 @@ class in it. Note that a hint may do this anyway if Separable: ('separable',), SeparableReduced: ('separable_reduced',), HomogeneousCoeffSubsDepDivIndep: ('1st_homogeneous_coeff_subs_dep_div_indep',), + HomogeneousCoeffSubsIndepDivDep: ('1st_homogeneous_coeff_subs_indep_div_dep',) } for solvercls in solvers: solver = solvercls(ode) @@ -1134,20 +1135,11 @@ class in it. Note that a hint may do this anyway if ## 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 - u2 = Dummy('u2') - s = "1st_homogeneous_coeff_subs" - s1 = s + "_dep_div_indep" - s2 = s + "_indep_div_dep" - 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 + s = "1st_homogeneous_coeff_subs" + s1 = s + "_dep_div_indep" + s2 = s + "_indep_div_dep" + 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 @@ -2742,7 +2734,7 @@ def ode_1st_homogeneous_coeff_best(eq, func, order, match): 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` + :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` and :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` docstrings for more information on these hints. Note that there is no @@ -2808,85 +2800,6 @@ def ode_1st_homogeneous_coeff_subs_dep_div_indep(eq, func, order, match): 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.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 - - """ x = func.args[0] f = func.func u = Dummy('u') @@ -4014,7 +3927,7 @@ def ode_linear_coefficients(eq, func, order, match): 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.single.HomogeneousCoeffSubsIndepDivDep` :meth:`sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` Examples @@ -6595,4 +6508,4 @@ 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, HomogeneousCoeffSubsDepDivIndep) + SecondNonlinearAutonomousConserved, FirstExact, Liouville, Separable, SeparableReduced, HomogeneousCoeffSubsDepDivIndep, HomogeneousCoeffSubsIndepDivDep) diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 8c7e8572407a..647633c980e2 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -156,8 +156,8 @@ def homogeneous_order(self, 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`). + :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` and + :py:meth:`~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 @@ -1450,7 +1450,7 @@ class HomogeneousCoeffSubsDepDivIndep(SinglePatternODESolver): 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`. + :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`. Examples ======== @@ -1525,5 +1525,130 @@ def _get_general_solution(self, *, simplify: bool = True): 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(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.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 = self.ode_problem.homogeneous_order(self.d, x, self.y) + orderb = self.ode_problem.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] + + # Avoid circular import: from .ode import dsolve From e6c7a2e2bd2f787eaaccbff728cfcdf07e0f509f Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Tue, 8 Jun 2021 18:07:44 +0530 Subject: [PATCH 03/11] refactor homogenous coeff best --- doc/src/modules/solvers/ode.rst | 2 +- sympy/solvers/ode/ode.py | 68 ++++-------------------------- sympy/solvers/ode/single.py | 73 ++++++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 66 deletions(-) diff --git a/doc/src/modules/solvers/ode.rst b/doc/src/modules/solvers/ode.rst index 1361c58bcce2..a805dd45d133 100644 --- a/doc/src/modules/solvers/ode.rst +++ b/doc/src/modules/solvers/ode.rst @@ -81,7 +81,7 @@ factorable 1st_homogeneous_coeff_best ^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.ode::ode_1st_homogeneous_coeff_best +.. autofunction:: sympy.solvers.ode.single::HomogeneousCoeffBest 1st_homogeneous_coeff_subs_dep_div_indep ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/sympy/solvers/ode/ode.py b/sympy/solvers/ode/ode.py index df13c5ec6e85..70b5b60777ba 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. +:py:meth:`~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 @@ -1058,7 +1058,8 @@ class in it. Note that a hint may do this anyway if Separable: ('separable',), SeparableReduced: ('separable_reduced',), HomogeneousCoeffSubsDepDivIndep: ('1st_homogeneous_coeff_subs_dep_div_indep',), - HomogeneousCoeffSubsIndepDivDep: ('1st_homogeneous_coeff_subs_indep_div_dep',) + HomogeneousCoeffSubsIndepDivDep: ('1st_homogeneous_coeff_subs_indep_div_dep',), + HomogeneousCoeffBest: ('1st_homogeneous_coeff_best',), } for solvercls in solvers: solver = solvercls(ode) @@ -1133,14 +1134,6 @@ class in it. Note that a hint may do this anyway if 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) - s = "1st_homogeneous_coeff_subs" - s1 = s + "_dep_div_indep" - s2 = s + "_indep_div_dep" - 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. @@ -2104,13 +2097,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 | / @@ -2726,49 +2719,6 @@ def _handle_Integral(expr, func, hint): 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.single.HomogeneousCoeffSubsIndepDivDep` - and - :py:meth:`~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 - - """ - # 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, @@ -3926,7 +3876,7 @@ def ode_linear_coefficients(eq, func, order, match): See Also ======== - :meth:`sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_best` + :meth:`sympy.solvers.ode.single.HomogeneousCoeffBest` :meth:`sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` :meth:`sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` @@ -6508,4 +6458,4 @@ 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, HomogeneousCoeffSubsDepDivIndep, HomogeneousCoeffSubsIndepDivDep) + SecondNonlinearAutonomousConserved, FirstExact, Liouville, Separable, SeparableReduced, HomogeneousCoeffSubsDepDivIndep, HomogeneousCoeffSubsIndepDivDep, HomogeneousCoeffBest) diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 647633c980e2..8fa936df7cde 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -1564,18 +1564,18 @@ class HomogeneousCoeffSubsIndepDivDep(SinglePatternODESolver): f(x) / | - | -g(u2) - | ---------------- d(u2) - | u2*g(u2) + h(u2) + | -g(u1) + | ---------------- d(u1) + | u1*g(u1) + h(u1) | / f(x) = C1*e - Where `u_2 g(u_2) + h(u_2) \ne 0` and `f(x) \ne 0`. + Where `u_1 g(u_1) + h(u_1) \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.single.HomogeneousCoeffBest` and :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep`. Examples @@ -1650,5 +1650,66 @@ def _get_general_solution(self, *, simplify_flag: bool = True): 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 + :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` + and + :py:meth:`~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: 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 + return min([sol1, sol2], key=lambda x: ode_sol_simplicity(x, fx, + trysolving=not simplify)) + + # Avoid circular import: -from .ode import dsolve +from .ode import dsolve, ode_sol_simplicity From 64bab8cd45b59e6e07a44d525c83cffaa151460c Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Tue, 8 Jun 2021 21:49:49 +0530 Subject: [PATCH 04/11] fix simplification result --- sympy/solvers/ode/single.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 8fa936df7cde..8982167dbc78 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -1707,9 +1707,11 @@ def _get_general_solution(self, *, simplify: bool = True): sol1 = HomogeneousCoeffSubsIndepDivDep._get_general_solution(self) sol2 = HomogeneousCoeffSubsDepDivIndep._get_general_solution(self) fx = self.ode_problem.func - return min([sol1, sol2], key=lambda x: ode_sol_simplicity(x, fx, - trysolving=not simplify)) + if simplify: + 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)) # Avoid circular import: -from .ode import dsolve, ode_sol_simplicity +from .ode import dsolve, ode_sol_simplicity, odesimp From 1f0efcc0c0b73b17767dff21c9c82e46da7f684a Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Wed, 9 Jun 2021 16:03:57 +0530 Subject: [PATCH 05/11] refactor linear_coefficients --- doc/src/modules/solvers/ode.rst | 2 +- sympy/solvers/ode/ode.py | 235 +----------------------- sympy/solvers/ode/single.py | 314 ++++++++++++++++++++------------ 3 files changed, 207 insertions(+), 344 deletions(-) diff --git a/doc/src/modules/solvers/ode.rst b/doc/src/modules/solvers/ode.rst index a805dd45d133..d92c7ba3ae32 100644 --- a/doc/src/modules/solvers/ode.rst +++ b/doc/src/modules/solvers/ode.rst @@ -154,7 +154,7 @@ almost_linear linear_coefficients ^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.ode::ode_linear_coefficients +.. autofunction:: sympy.solvers.ode.single::LinearCoefficients separable_reduced ^^^^^^^^^^^^^^^^^ diff --git a/sympy/solvers/ode/ode.py b/sympy/solvers/ode/ode.py index 70b5b60777ba..25b572cf8598 100644 --- a/sympy/solvers/ode/ode.py +++ b/sympy/solvers/ode/ode.py @@ -1060,6 +1060,7 @@ class in it. Note that a hint may do this anyway if 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) @@ -1116,54 +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]) - - ## 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 @@ -2718,54 +2671,6 @@ def _handle_Integral(expr, func, hint): return sol -def ode_1st_homogeneous_coeff_best(eq, func, order, match): - 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): - 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): - 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? @@ -3772,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.single.HomogeneousCoeffBest` - :meth:`sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` - :meth:`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 - """ - - return ode_1st_homogeneous_coeff_best(eq, func, order, match) - def ode_1st_power_series(eq, func, order, match): r""" @@ -6458,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, HomogeneousCoeffSubsDepDivIndep, HomogeneousCoeffSubsIndepDivDep, HomogeneousCoeffBest) + SecondNonlinearAutonomousConserved, FirstExact, Liouville, Separable, + SeparableReduced, HomogeneousCoeffSubsDepDivIndep, HomogeneousCoeffSubsIndepDivDep, + HomogeneousCoeffBest, LinearCoefficients) diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 8982167dbc78..a90b6e3ea390 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,7 +20,7 @@ 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, logcombine +from sympy.simplify import collect, simplify, separatevars, logcombine from sympy.simplify.radsimp import fraction from sympy.core.sympify import sympify from sympy.utilities import numbered_symbols @@ -142,96 +142,6 @@ def is_autonomous(self): syms = self.eq.subs(self.func, u).free_symbols return x not in syms - def homogeneous_order(self, eq, *symbols): - r""" - Returns the order `n` if `g` is homogeneous and ``None`` if it is not - homogeneous. - - Determines if a function is homogeneous and if so of what order. A - function `f(x, y, \cdots)` is homogeneous of order `n` if `f(t x, t y, - \cdots) = t^n f(x, y, \cdots)`. - - If the function is of two variables, `F(x, y)`, then `f` being homogeneous - of any order is equivalent to being able to rewrite `F(x, y)` as `G(x/y)` - 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.single.HomogeneousCoeffSubsDepDivIndep` and - :py:meth:`~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 - must match those given in the list of symbols. If a declared function - appears with different arguments than given in the list of symbols, - ``None`` is returned. - - Examples - ======== - - >>> from sympy import Function, homogeneous_order, sqrt - >>> from sympy.abc import x, y - >>> f = Function('f') - >>> homogeneous_order(f(x), f(x)) is None - True - >>> homogeneous_order(f(x,y), f(y, x), x, y) is None - True - >>> homogeneous_order(f(x), f(x), x) - 1 - >>> homogeneous_order(x**2*f(x)/sqrt(x**2+f(x)**2), x, f(x)) - 2 - >>> homogeneous_order(x**2+f(x), x, f(x)) is None - True - - """ - - if not symbols: - raise ValueError("homogeneous_order: no symbols were given.") - symset = set(symbols) - eq = sympify(eq) - - # The following are not supported - if eq.has(Order, Derivative): - return None - - # These are all constants - if (eq.is_Number or - eq.is_NumberSymbol or - eq.is_number - ): - return S.Zero - - # Replace all functions with dummy variables - dum = numbered_symbols(prefix='d', cls=Dummy) - newsyms = set() - for i in [j for j in symset if getattr(j, 'is_Function')]: - iargs = set(i.args) - if iargs.difference(symset): - return None - else: - dummyvar = next(dum) - eq = eq.subs(i, dummyvar) - symset.remove(i) - newsyms.add(dummyvar) - symset.update(newsyms) - - if not eq.free_symbols & symset: - return None - - # assuming order of a nested function can only be equal to zero - if isinstance(eq, Function): - return None if self.homogeneous_order( - eq.args[0], *tuple(symset)) != 0 else S.Zero - - # make the replacement of x with x*t and see if t can be factored out - t = Dummy('t', positive=True) # It is sufficient that t > 0 - eqs = separatevars(eq.subs([(i, t*i) for i in symset]), [t], dict=True)[t] - if eqs is S.One: - return S.Zero # there was no term with only t - i, d = eqs.as_independent(t, as_Add=False) - b, e = d.as_base_exp() - if b == t: - return e - # TODO: Add methods that can be used by many ODE solvers: # order # is_linear() @@ -311,13 +221,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) @@ -443,7 +353,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 @@ -585,7 +495,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 @@ -664,7 +574,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 @@ -752,7 +662,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)) @@ -850,7 +760,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 @@ -922,7 +832,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 @@ -998,7 +908,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 @@ -1058,7 +968,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 @@ -1155,7 +1065,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 @@ -1252,7 +1162,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], @@ -1497,8 +1407,8 @@ def _verify(self, fx): x = self.ode_problem.sym self.d = separatevars(self.d.subs(fx, self.y)) self.e = separatevars(self.e.subs(fx, self.y)) - ordera = self.ode_problem.homogeneous_order(self.d, x, self.y) - orderb = self.ode_problem.homogeneous_order(self.e, x, 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: @@ -1514,7 +1424,7 @@ def _get_match_object(self): yarg = 0 return [self.d, self.e, fx, x, self.u, self.u1, self.y, xarg, yarg] - def _get_general_solution(self, *, simplify: bool = True): + 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( @@ -1624,8 +1534,8 @@ def _verify(self, fx): x = self.ode_problem.sym self.d = separatevars(self.d.subs(fx, self.y)) self.e = separatevars(self.e.subs(fx, self.y)) - ordera = self.ode_problem.homogeneous_order(self.d, x, self.y) - orderb = self.ode_problem.homogeneous_order(self.e, x, 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: @@ -1700,18 +1610,198 @@ def _verify(self, fx): return True return False - def _get_general_solution(self, *, simplify: bool = True): + 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: + 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 + ======== + :meth:`sympy.solvers.ode.single.HomogeneousCoeffBest` + :meth:`sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` + :meth:`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.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 _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, ode_sol_simplicity, odesimp +from .ode import dsolve, ode_sol_simplicity, odesimp, homogeneous_order From 065b15b35d2c0fd1f42cf4ef0dddce0ac70bf756 Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Wed, 9 Jun 2021 16:07:14 +0530 Subject: [PATCH 06/11] fix import --- sympy/solvers/ode/single.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index a90b6e3ea390..5942e91e6b58 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -22,9 +22,7 @@ from sympy.polys.polytools import cancel, factor from sympy.simplify import collect, simplify, separatevars, logcombine from sympy.simplify.radsimp import fraction -from sympy.core.sympify import sympify from sympy.utilities import numbered_symbols -from sympy.series import Order from sympy.solvers.solvers import solve from sympy.solvers.deutils import ode_order, _preprocess @@ -1714,7 +1712,7 @@ def _verify(self, fx): return True return False return False - + def _linear_coeff_match(self,expr, func): r""" Helper function to match hint ``linear_coefficients``. From a9f4a3fd263644b0bfc1fff95339abf3fb0c7498 Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Wed, 9 Jun 2021 18:49:18 +0530 Subject: [PATCH 07/11] change test for _linear_coeff_match --- sympy/solvers/ode/single.py | 16 ++++++++++------ sympy/solvers/ode/tests/test_ode.py | 26 ++++++++++++++++---------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 5942e91e6b58..07af182f1b2a 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -1735,16 +1735,20 @@ def _linear_coeff_match(self,expr, func): >>> from sympy import Function >>> from sympy.abc import x - >>> from sympy.solvers.ode.ode import _linear_coeff_match + >>> from sympy.solvers.ode.single import LinearCoefficients >>> 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)) + >>> 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) - >>> _linear_coeff_match( - ... sin((-5*f(x) - 8*x + 6)/(4*f(x) + x - 1)), f(x)) + >>> 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) - >>> _linear_coeff_match(sin(f(x)/x), f(x)) + >>> eq = sin(f(x)/x) + >>> obj = LinearCoefficients(eq) + >>> obj._linear_coeff_match(eq, f(x)) """ f = func.func 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(): From f8e3d8a8003d1c0a174df82accab9829ef7645ea Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Wed, 9 Jun 2021 21:32:35 +0530 Subject: [PATCH 08/11] fix sphinx failure --- sympy/solvers/ode/ode.py | 2 +- sympy/solvers/ode/single.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sympy/solvers/ode/ode.py b/sympy/solvers/ode/ode.py index 25b572cf8598..941230d68366 100644 --- a/sympy/solvers/ode/ode.py +++ b/sympy/solvers/ode/ode.py @@ -2689,7 +2689,7 @@ def homogeneous_order(eq, *symbols): equations whose coefficients are homogeneous of the same order (see the docstrings of :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` and - :py:meth:`~sympy.solvers.ode.ode.ode_1st_homogeneous_coeff_subs_indep_div_dep`). + :py:meth:`~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 diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 07af182f1b2a..948795e78523 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -1357,7 +1357,7 @@ class HomogeneousCoeffSubsDepDivIndep(SinglePatternODESolver): 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.single.HomogeneousCoeffBest` and :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`. Examples From d7061891cb2e5917ad77733d9c47c0913ddb50ea Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Thu, 10 Jun 2021 14:16:59 +0530 Subject: [PATCH 09/11] fix reference through obj --- doc/src/modules/solvers/ode.rst | 2 +- sympy/solvers/ode/ode.py | 6 +++--- sympy/solvers/ode/single.py | 26 +++++++++++++------------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/src/modules/solvers/ode.rst b/doc/src/modules/solvers/ode.rst index d92c7ba3ae32..5690a032a7c3 100644 --- a/doc/src/modules/solvers/ode.rst +++ b/doc/src/modules/solvers/ode.rst @@ -90,7 +90,7 @@ factorable 1st_homogeneous_coeff_subs_indep_div_dep ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.single::HomogeneousCoeffSubsIndepDivDep +.. autoclass:: sympy.solvers.ode.single::HomogeneousCoeffSubsIndepDivDep :members: 1st_linear diff --git a/sympy/solvers/ode/ode.py b/sympy/solvers/ode/ode.py index 941230d68366..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.single.HomogeneousCoeffBest`, 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 @@ -2688,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.single.HomogeneousCoeffSubsDepDivIndep` and - :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`). + :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 diff --git a/sympy/solvers/ode/single.py b/sympy/solvers/ode/single.py index 948795e78523..393002cc4053 100644 --- a/sympy/solvers/ode/single.py +++ b/sympy/solvers/ode/single.py @@ -598,7 +598,7 @@ class AlmostLinear(SinglePatternODESolver): See Also ======== - :meth:`sympy.solvers.ode.single.FirstLinear` + :obj:`sympy.solvers.ode.single.FirstLinear` Examples ======== @@ -679,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 @@ -706,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')) @@ -1206,7 +1206,7 @@ class SeparableReduced(Separable): See Also ======== - :meth:`sympy.solvers.ode.single.Separable` + :obj:`sympy.solvers.ode.single.Separable` Examples ======== @@ -1357,8 +1357,8 @@ class HomogeneousCoeffSubsDepDivIndep(SinglePatternODESolver): 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.single.HomogeneousCoeffBest` and - :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`. + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep`. Examples ======== @@ -1483,8 +1483,8 @@ class HomogeneousCoeffSubsIndepDivDep(SinglePatternODESolver): Where `u_1 g(u_1) + h(u_1) \ne 0` and `f(x) \ne 0`. See also the docstrings of - :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and - :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep`. + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffBest` and + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep`. Examples ======== @@ -1567,9 +1567,9 @@ class HomogeneousCoeffBest(HomogeneousCoeffSubsIndepDivDep, HomogeneousCoeffSubs This is as determined by :py:meth:`~sympy.solvers.ode.ode.ode_sol_simplicity`. See the - :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` + :obj:`~sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` and - :py:meth:`~sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` + :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. @@ -1645,9 +1645,9 @@ class LinearCoefficients(HomogeneousCoeffBest): See Also ======== - :meth:`sympy.solvers.ode.single.HomogeneousCoeffBest` - :meth:`sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` - :meth:`sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` + :obj:`sympy.solvers.ode.single.HomogeneousCoeffBest` + :obj:`sympy.solvers.ode.single.HomogeneousCoeffSubsIndepDivDep` + :obj:`sympy.solvers.ode.single.HomogeneousCoeffSubsDepDivIndep` Examples ======== From 59897a2276e6b05a181575da4d0c1958c5932279 Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Fri, 11 Jun 2021 00:35:54 +0530 Subject: [PATCH 10/11] fix autoclass --- doc/src/modules/solvers/ode.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/src/modules/solvers/ode.rst b/doc/src/modules/solvers/ode.rst index 5690a032a7c3..afd316421900 100644 --- a/doc/src/modules/solvers/ode.rst +++ b/doc/src/modules/solvers/ode.rst @@ -81,7 +81,7 @@ factorable 1st_homogeneous_coeff_best ^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.single::HomogeneousCoeffBest +.. autoclass:: sympy.solvers.ode.single::HomogeneousCoeffBest 1st_homogeneous_coeff_subs_dep_div_indep ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -154,11 +154,11 @@ almost_linear linear_coefficients ^^^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.single::LinearCoefficients +.. autoclass:: sympy.solvers.ode.single::LinearCoefficients separable_reduced ^^^^^^^^^^^^^^^^^ -.. autofunction:: sympy.solvers.ode.single::SeparableReduced +.. autoclass:: sympy.solvers.ode.single::SeparableReduced lie_group ^^^^^^^^^ From e2867b051a88d88a329154d35b6a2a32f126bac5 Mon Sep 17 00:00:00 2001 From: Mohit Balwani Date: Sat, 12 Jun 2021 09:17:53 +0530 Subject: [PATCH 11/11] add members to every class --- doc/src/modules/solvers/ode.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/src/modules/solvers/ode.rst b/doc/src/modules/solvers/ode.rst index afd316421900..ca0f1acdbbdf 100644 --- a/doc/src/modules/solvers/ode.rst +++ b/doc/src/modules/solvers/ode.rst @@ -78,10 +78,12 @@ factorable 1st_exact ^^^^^^^^^ .. autoclass:: sympy.solvers.ode.single.FirstExact + :members: 1st_homogeneous_coeff_best ^^^^^^^^^^^^^^^^^^^^^^^^^^ .. autoclass:: sympy.solvers.ode.single::HomogeneousCoeffBest + :members: 1st_homogeneous_coeff_subs_dep_div_indep ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -155,10 +157,12 @@ almost_linear linear_coefficients ^^^^^^^^^^^^^^^^^^^ .. autoclass:: sympy.solvers.ode.single::LinearCoefficients + :members: separable_reduced ^^^^^^^^^^^^^^^^^ .. autoclass:: sympy.solvers.ode.single::SeparableReduced + :members: lie_group ^^^^^^^^^