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

1"""Utility functions for classifying and solving 

2ordinary and partial differential equations. 

3 

4Contains 

5======== 

6_preprocess 

7ode_order 

8_desolve 

9 

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 

15 

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.) 

23 

24 In case func is None, an attempt will be made to autodetect the 

25 function to be solved for. 

26 

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') 

31 

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)) 

35 

36 Apply doit to derivatives that contain more than the function 

37 of interest: 

38 

39 >>> _preprocess(Derivative(f(x) + x, x)) 

40 (Derivative(f(x), x) + 1, f(x)) 

41 

42 Do others if the differentiation variable(s) intersect with those 

43 of the function of interest or contain the function of interest: 

44 

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)) 

49 

50 Do others if the hint does not end in '_Integral' (the default 

51 assumes that it does): 

52 

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)) 

57 

58 Do not do any derivatives if hint is None: 

59 

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)) 

63 

64 If it's not clear what the function of interest is, it must be given: 

65 

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. 

72 

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 

92 

93 

94def ode_order(expr, func): 

95 """ 

96 Returns the order of a given differential 

97 equation with respect to func. 

98 

99 This function is implemented recursively. 

100 

101 Examples 

102 ======== 

103 

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 

115 

116 """ 

117 a = Wild('a', exclude=[func]) 

118 if expr.match(a): 

119 return 0 

120 

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 

132 

133 

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. 

137 

138 If the hint provided to the function is "default", then a dict with 

139 the following keys are returned 

140 

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. 

144 

145 'default' - The default key as returned by classifier functions in ode 

146 and pde.py 

147 

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. 

151 

152 'order' - The order of the function as returned by ode_order 

153 

154 'match' - It returns the match as given by the classifier functions, for 

155 the default hint. 

156 

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 

161 

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. 

166 

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. 

169 

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 

177 

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 

182 

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') 

192 

193 if type == 'ode': 

194 from sympy.solvers.ode import classify_ode, allhints 

195 classifier = classify_ode 

196 string = 'ODE ' 

197 dummy = '' 

198 

199 elif type == 'pde': 

200 from sympy.solvers.pde import classify_pde, allhints 

201 classifier = classify_pde 

202 string = 'PDE ' 

203 dummy = 'p' 

204 

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) 

211 

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