Coverage for /usr/lib/python3/dist-packages/sympy/solvers/deutils.py: 8%
90 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Utility functions for classifying and solving
2ordinary and partial differential equations.
4Contains
5========
6_preprocess
7ode_order
8_desolve
10"""
11from sympy.core import Pow
12from sympy.core.function import Derivative, AppliedUndef
13from sympy.core.relational import Equality
14from sympy.core.symbol import Wild
16def _preprocess(expr, func=None, hint='_Integral'):
17 """Prepare expr for solving by making sure that differentiation
18 is done so that only func remains in unevaluated derivatives and
19 (if hint does not end with _Integral) that doit is applied to all
20 other derivatives. If hint is None, do not do any differentiation.
21 (Currently this may cause some simple differential equations to
22 fail.)
24 In case func is None, an attempt will be made to autodetect the
25 function to be solved for.
27 >>> from sympy.solvers.deutils import _preprocess
28 >>> from sympy import Derivative, Function
29 >>> from sympy.abc import x, y, z
30 >>> f, g = map(Function, 'fg')
32 If f(x)**p == 0 and p>0 then we can solve for f(x)=0
33 >>> _preprocess((f(x).diff(x)-4)**5, f(x))
34 (Derivative(f(x), x) - 4, f(x))
36 Apply doit to derivatives that contain more than the function
37 of interest:
39 >>> _preprocess(Derivative(f(x) + x, x))
40 (Derivative(f(x), x) + 1, f(x))
42 Do others if the differentiation variable(s) intersect with those
43 of the function of interest or contain the function of interest:
45 >>> _preprocess(Derivative(g(x), y, z), f(y))
46 (0, f(y))
47 >>> _preprocess(Derivative(f(y), z), f(y))
48 (0, f(y))
50 Do others if the hint does not end in '_Integral' (the default
51 assumes that it does):
53 >>> _preprocess(Derivative(g(x), y), f(x))
54 (Derivative(g(x), y), f(x))
55 >>> _preprocess(Derivative(f(x), y), f(x), hint='')
56 (0, f(x))
58 Do not do any derivatives if hint is None:
60 >>> eq = Derivative(f(x) + 1, x) + Derivative(f(x), y)
61 >>> _preprocess(eq, f(x), hint=None)
62 (Derivative(f(x) + 1, x) + Derivative(f(x), y), f(x))
64 If it's not clear what the function of interest is, it must be given:
66 >>> eq = Derivative(f(x) + g(x), x)
67 >>> _preprocess(eq, g(x))
68 (Derivative(f(x), x) + Derivative(g(x), x), g(x))
69 >>> try: _preprocess(eq)
70 ... except ValueError: print("A ValueError was raised.")
71 A ValueError was raised.
73 """
74 if isinstance(expr, Pow):
75 # if f(x)**p=0 then f(x)=0 (p>0)
76 if (expr.exp).is_positive:
77 expr = expr.base
78 derivs = expr.atoms(Derivative)
79 if not func:
80 funcs = set().union(*[d.atoms(AppliedUndef) for d in derivs])
81 if len(funcs) != 1:
82 raise ValueError('The function cannot be '
83 'automatically detected for %s.' % expr)
84 func = funcs.pop()
85 fvars = set(func.args)
86 if hint is None:
87 return expr, func
88 reps = [(d, d.doit()) for d in derivs if not hint.endswith('_Integral') or
89 d.has(func) or set(d.variables) & fvars]
90 eq = expr.subs(reps)
91 return eq, func
94def ode_order(expr, func):
95 """
96 Returns the order of a given differential
97 equation with respect to func.
99 This function is implemented recursively.
101 Examples
102 ========
104 >>> from sympy import Function
105 >>> from sympy.solvers.deutils import ode_order
106 >>> from sympy.abc import x
107 >>> f, g = map(Function, ['f', 'g'])
108 >>> ode_order(f(x).diff(x, 2) + f(x).diff(x)**2 +
109 ... f(x).diff(x), f(x))
110 2
111 >>> ode_order(f(x).diff(x, 2) + g(x).diff(x, 3), f(x))
112 2
113 >>> ode_order(f(x).diff(x, 2) + g(x).diff(x, 3), g(x))
114 3
116 """
117 a = Wild('a', exclude=[func])
118 if expr.match(a):
119 return 0
121 if isinstance(expr, Derivative):
122 if expr.args[0] == func:
123 return len(expr.variables)
124 else:
125 args = expr.args[0].args
126 rv = len(expr.variables)
127 if args:
128 rv += max(ode_order(_, func) for _ in args)
129 return rv
130 else:
131 return max(ode_order(_, func) for _ in expr.args) if expr.args else 0
134def _desolve(eq, func=None, hint="default", ics=None, simplify=True, *, prep=True, **kwargs):
135 """This is a helper function to dsolve and pdsolve in the ode
136 and pde modules.
138 If the hint provided to the function is "default", then a dict with
139 the following keys are returned
141 'func' - It provides the function for which the differential equation
142 has to be solved. This is useful when the expression has
143 more than one function in it.
145 'default' - The default key as returned by classifier functions in ode
146 and pde.py
148 'hint' - The hint given by the user for which the differential equation
149 is to be solved. If the hint given by the user is 'default',
150 then the value of 'hint' and 'default' is the same.
152 'order' - The order of the function as returned by ode_order
154 'match' - It returns the match as given by the classifier functions, for
155 the default hint.
157 If the hint provided to the function is not "default" and is not in
158 ('all', 'all_Integral', 'best'), then a dict with the above mentioned keys
159 is returned along with the keys which are returned when dict in
160 classify_ode or classify_pde is set True
162 If the hint given is in ('all', 'all_Integral', 'best'), then this function
163 returns a nested dict, with the keys, being the set of classified hints
164 returned by classifier functions, and the values being the dict of form
165 as mentioned above.
167 Key 'eq' is a common key to all the above mentioned hints which returns an
168 expression if eq given by user is an Equality.
170 See Also
171 ========
172 classify_ode(ode.py)
173 classify_pde(pde.py)
174 """
175 if isinstance(eq, Equality):
176 eq = eq.lhs - eq.rhs
178 # preprocess the equation and find func if not given
179 if prep or func is None:
180 eq, func = _preprocess(eq, func)
181 prep = False
183 # type is an argument passed by the solve functions in ode and pde.py
184 # that identifies whether the function caller is an ordinary
185 # or partial differential equation. Accordingly corresponding
186 # changes are made in the function.
187 type = kwargs.get('type', None)
188 xi = kwargs.get('xi')
189 eta = kwargs.get('eta')
190 x0 = kwargs.get('x0', 0)
191 terms = kwargs.get('n')
193 if type == 'ode':
194 from sympy.solvers.ode import classify_ode, allhints
195 classifier = classify_ode
196 string = 'ODE '
197 dummy = ''
199 elif type == 'pde':
200 from sympy.solvers.pde import classify_pde, allhints
201 classifier = classify_pde
202 string = 'PDE '
203 dummy = 'p'
205 # Magic that should only be used internally. Prevents classify_ode from
206 # being called more than it needs to be by passing its results through
207 # recursive calls.
208 if kwargs.get('classify', True):
209 hints = classifier(eq, func, dict=True, ics=ics, xi=xi, eta=eta,
210 n=terms, x0=x0, hint=hint, prep=prep)
212 else:
213 # Here is what all this means:
214 #
215 # hint: The hint method given to _desolve() by the user.
216 # hints: The dictionary of hints that match the DE, along with other
217 # information (including the internal pass-through magic).
218 # default: The default hint to return, the first hint from allhints
219 # that matches the hint; obtained from classify_ode().
220 # match: Dictionary containing the match dictionary for each hint
221 # (the parts of the DE for solving). When going through the
222 # hints in "all", this holds the match string for the current
223 # hint.
224 # order: The order of the DE, as determined by ode_order().
225 hints = kwargs.get('hint',
226 {'default': hint,
227 hint: kwargs['match'],
228 'order': kwargs['order']})
229 if not hints['default']:
230 # classify_ode will set hints['default'] to None if no hints match.
231 if hint not in allhints and hint != 'default':
232 raise ValueError("Hint not recognized: " + hint)
233 elif hint not in hints['ordered_hints'] and hint != 'default':
234 raise ValueError(string + str(eq) + " does not match hint " + hint)
235 # If dsolve can't solve the purely algebraic equation then dsolve will raise
236 # ValueError
237 elif hints['order'] == 0:
238 raise ValueError(
239 str(eq) + " is not a solvable differential equation in " + str(func))
240 else:
241 raise NotImplementedError(dummy + "solve" + ": Cannot solve " + str(eq))
242 if hint == 'default':
243 return _desolve(eq, func, ics=ics, hint=hints['default'], simplify=simplify,
244 prep=prep, x0=x0, classify=False, order=hints['order'],
245 match=hints[hints['default']], xi=xi, eta=eta, n=terms, type=type)
246 elif hint in ('all', 'all_Integral', 'best'):
247 retdict = {}
248 gethints = set(hints) - {'order', 'default', 'ordered_hints'}
249 if hint == 'all_Integral':
250 for i in hints:
251 if i.endswith('_Integral'):
252 gethints.remove(i[:-len('_Integral')])
253 # special cases
254 for k in ["1st_homogeneous_coeff_best", "1st_power_series",
255 "lie_group", "2nd_power_series_ordinary", "2nd_power_series_regular"]:
256 if k in gethints:
257 gethints.remove(k)
258 for i in gethints:
259 sol = _desolve(eq, func, ics=ics, hint=i, x0=x0, simplify=simplify, prep=prep,
260 classify=False, n=terms, order=hints['order'], match=hints[i], type=type)
261 retdict[i] = sol
262 retdict['all'] = True
263 retdict['eq'] = eq
264 return retdict
265 elif hint not in allhints: # and hint not in ('default', 'ordered_hints'):
266 raise ValueError("Hint not recognized: " + hint)
267 elif hint not in hints:
268 raise ValueError(string + str(eq) + " does not match hint " + hint)
269 else:
270 # Key added to identify the hint needed to solve the equation
271 hints['hint'] = hint
272 hints.update({'func': func, 'eq': eq})
273 return hints