Coverage for /usr/lib/python3/dist-packages/sympy/concrete/expr_with_intlimits.py: 17%

76 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1from sympy.concrete.expr_with_limits import ExprWithLimits 

2from sympy.core.singleton import S 

3from sympy.core.relational import Eq 

4 

5class ReorderError(NotImplementedError): 

6 """ 

7 Exception raised when trying to reorder dependent limits. 

8 """ 

9 def __init__(self, expr, msg): 

10 super().__init__( 

11 "%s could not be reordered: %s." % (expr, msg)) 

12 

13class ExprWithIntLimits(ExprWithLimits): 

14 """ 

15 Superclass for Product and Sum. 

16 

17 See Also 

18 ======== 

19 

20 sympy.concrete.expr_with_limits.ExprWithLimits 

21 sympy.concrete.products.Product 

22 sympy.concrete.summations.Sum 

23 """ 

24 __slots__ = () 

25 

26 def change_index(self, var, trafo, newvar=None): 

27 r""" 

28 Change index of a Sum or Product. 

29 

30 Perform a linear transformation `x \mapsto a x + b` on the index variable 

31 `x`. For `a` the only values allowed are `\pm 1`. A new variable to be used 

32 after the change of index can also be specified. 

33 

34 Explanation 

35 =========== 

36 

37 ``change_index(expr, var, trafo, newvar=None)`` where ``var`` specifies the 

38 index variable `x` to transform. The transformation ``trafo`` must be linear 

39 and given in terms of ``var``. If the optional argument ``newvar`` is 

40 provided then ``var`` gets replaced by ``newvar`` in the final expression. 

41 

42 Examples 

43 ======== 

44 

45 >>> from sympy import Sum, Product, simplify 

46 >>> from sympy.abc import x, y, a, b, c, d, u, v, i, j, k, l 

47 

48 >>> S = Sum(x, (x, a, b)) 

49 >>> S.doit() 

50 -a**2/2 + a/2 + b**2/2 + b/2 

51 

52 >>> Sn = S.change_index(x, x + 1, y) 

53 >>> Sn 

54 Sum(y - 1, (y, a + 1, b + 1)) 

55 >>> Sn.doit() 

56 -a**2/2 + a/2 + b**2/2 + b/2 

57 

58 >>> Sn = S.change_index(x, -x, y) 

59 >>> Sn 

60 Sum(-y, (y, -b, -a)) 

61 >>> Sn.doit() 

62 -a**2/2 + a/2 + b**2/2 + b/2 

63 

64 >>> Sn = S.change_index(x, x+u) 

65 >>> Sn 

66 Sum(-u + x, (x, a + u, b + u)) 

67 >>> Sn.doit() 

68 -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u 

69 >>> simplify(Sn.doit()) 

70 -a**2/2 + a/2 + b**2/2 + b/2 

71 

72 >>> Sn = S.change_index(x, -x - u, y) 

73 >>> Sn 

74 Sum(-u - y, (y, -b - u, -a - u)) 

75 >>> Sn.doit() 

76 -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u 

77 >>> simplify(Sn.doit()) 

78 -a**2/2 + a/2 + b**2/2 + b/2 

79 

80 >>> P = Product(i*j**2, (i, a, b), (j, c, d)) 

81 >>> P 

82 Product(i*j**2, (i, a, b), (j, c, d)) 

83 >>> P2 = P.change_index(i, i+3, k) 

84 >>> P2 

85 Product(j**2*(k - 3), (k, a + 3, b + 3), (j, c, d)) 

86 >>> P3 = P2.change_index(j, -j, l) 

87 >>> P3 

88 Product(l**2*(k - 3), (k, a + 3, b + 3), (l, -d, -c)) 

89 

90 When dealing with symbols only, we can make a 

91 general linear transformation: 

92 

93 >>> Sn = S.change_index(x, u*x+v, y) 

94 >>> Sn 

95 Sum((-v + y)/u, (y, b*u + v, a*u + v)) 

96 >>> Sn.doit() 

97 -v*(a*u - b*u + 1)/u + (a**2*u**2/2 + a*u*v + a*u/2 - b**2*u**2/2 - b*u*v + b*u/2 + v)/u 

98 >>> simplify(Sn.doit()) 

99 a**2*u/2 + a/2 - b**2*u/2 + b/2 

100 

101 However, the last result can be inconsistent with usual 

102 summation where the index increment is always 1. This is 

103 obvious as we get back the original value only for ``u`` 

104 equal +1 or -1. 

105 

106 See Also 

107 ======== 

108 

109 sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index, 

110 reorder_limit, 

111 sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder, 

112 sympy.concrete.summations.Sum.reverse_order, 

113 sympy.concrete.products.Product.reverse_order 

114 """ 

115 if newvar is None: 

116 newvar = var 

117 

118 limits = [] 

119 for limit in self.limits: 

120 if limit[0] == var: 

121 p = trafo.as_poly(var) 

122 if p.degree() != 1: 

123 raise ValueError("Index transformation is not linear") 

124 alpha = p.coeff_monomial(var) 

125 beta = p.coeff_monomial(S.One) 

126 if alpha.is_number: 

127 if alpha == S.One: 

128 limits.append((newvar, alpha*limit[1] + beta, alpha*limit[2] + beta)) 

129 elif alpha == S.NegativeOne: 

130 limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta)) 

131 else: 

132 raise ValueError("Linear transformation results in non-linear summation stepsize") 

133 else: 

134 # Note that the case of alpha being symbolic can give issues if alpha < 0. 

135 limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta)) 

136 else: 

137 limits.append(limit) 

138 

139 function = self.function.subs(var, (var - beta)/alpha) 

140 function = function.subs(var, newvar) 

141 

142 return self.func(function, *limits) 

143 

144 

145 def index(expr, x): 

146 """ 

147 Return the index of a dummy variable in the list of limits. 

148 

149 Explanation 

150 =========== 

151 

152 ``index(expr, x)`` returns the index of the dummy variable ``x`` in the 

153 limits of ``expr``. Note that we start counting with 0 at the inner-most 

154 limits tuple. 

155 

156 Examples 

157 ======== 

158 

159 >>> from sympy.abc import x, y, a, b, c, d 

160 >>> from sympy import Sum, Product 

161 >>> Sum(x*y, (x, a, b), (y, c, d)).index(x) 

162 0 

163 >>> Sum(x*y, (x, a, b), (y, c, d)).index(y) 

164 1 

165 >>> Product(x*y, (x, a, b), (y, c, d)).index(x) 

166 0 

167 >>> Product(x*y, (x, a, b), (y, c, d)).index(y) 

168 1 

169 

170 See Also 

171 ======== 

172 

173 reorder_limit, reorder, sympy.concrete.summations.Sum.reverse_order, 

174 sympy.concrete.products.Product.reverse_order 

175 """ 

176 variables = [limit[0] for limit in expr.limits] 

177 

178 if variables.count(x) != 1: 

179 raise ValueError(expr, "Number of instances of variable not equal to one") 

180 else: 

181 return variables.index(x) 

182 

183 def reorder(expr, *arg): 

184 """ 

185 Reorder limits in a expression containing a Sum or a Product. 

186 

187 Explanation 

188 =========== 

189 

190 ``expr.reorder(*arg)`` reorders the limits in the expression ``expr`` 

191 according to the list of tuples given by ``arg``. These tuples can 

192 contain numerical indices or index variable names or involve both. 

193 

194 Examples 

195 ======== 

196 

197 >>> from sympy import Sum, Product 

198 >>> from sympy.abc import x, y, z, a, b, c, d, e, f 

199 

200 >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((x, y)) 

201 Sum(x*y, (y, c, d), (x, a, b)) 

202 

203 >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder((x, y), (x, z), (y, z)) 

204 Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b)) 

205 

206 >>> P = Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)) 

207 >>> P.reorder((x, y), (x, z), (y, z)) 

208 Product(x*y*z, (z, e, f), (y, c, d), (x, a, b)) 

209 

210 We can also select the index variables by counting them, starting 

211 with the inner-most one: 

212 

213 >>> Sum(x**2, (x, a, b), (x, c, d)).reorder((0, 1)) 

214 Sum(x**2, (x, c, d), (x, a, b)) 

215 

216 And of course we can mix both schemes: 

217 

218 >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, x)) 

219 Sum(x*y, (y, c, d), (x, a, b)) 

220 >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, 0)) 

221 Sum(x*y, (y, c, d), (x, a, b)) 

222 

223 See Also 

224 ======== 

225 

226 reorder_limit, index, sympy.concrete.summations.Sum.reverse_order, 

227 sympy.concrete.products.Product.reverse_order 

228 """ 

229 new_expr = expr 

230 

231 for r in arg: 

232 if len(r) != 2: 

233 raise ValueError(r, "Invalid number of arguments") 

234 

235 index1 = r[0] 

236 index2 = r[1] 

237 

238 if not isinstance(r[0], int): 

239 index1 = expr.index(r[0]) 

240 if not isinstance(r[1], int): 

241 index2 = expr.index(r[1]) 

242 

243 new_expr = new_expr.reorder_limit(index1, index2) 

244 

245 return new_expr 

246 

247 

248 def reorder_limit(expr, x, y): 

249 """ 

250 Interchange two limit tuples of a Sum or Product expression. 

251 

252 Explanation 

253 =========== 

254 

255 ``expr.reorder_limit(x, y)`` interchanges two limit tuples. The 

256 arguments ``x`` and ``y`` are integers corresponding to the index 

257 variables of the two limits which are to be interchanged. The 

258 expression ``expr`` has to be either a Sum or a Product. 

259 

260 Examples 

261 ======== 

262 

263 >>> from sympy.abc import x, y, z, a, b, c, d, e, f 

264 >>> from sympy import Sum, Product 

265 

266 >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2) 

267 Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b)) 

268 >>> Sum(x**2, (x, a, b), (x, c, d)).reorder_limit(1, 0) 

269 Sum(x**2, (x, c, d), (x, a, b)) 

270 

271 >>> Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2) 

272 Product(x*y*z, (z, e, f), (y, c, d), (x, a, b)) 

273 

274 See Also 

275 ======== 

276 

277 index, reorder, sympy.concrete.summations.Sum.reverse_order, 

278 sympy.concrete.products.Product.reverse_order 

279 """ 

280 var = {limit[0] for limit in expr.limits} 

281 limit_x = expr.limits[x] 

282 limit_y = expr.limits[y] 

283 

284 if (len(set(limit_x[1].free_symbols).intersection(var)) == 0 and 

285 len(set(limit_x[2].free_symbols).intersection(var)) == 0 and 

286 len(set(limit_y[1].free_symbols).intersection(var)) == 0 and 

287 len(set(limit_y[2].free_symbols).intersection(var)) == 0): 

288 

289 limits = [] 

290 for i, limit in enumerate(expr.limits): 

291 if i == x: 

292 limits.append(limit_y) 

293 elif i == y: 

294 limits.append(limit_x) 

295 else: 

296 limits.append(limit) 

297 

298 return type(expr)(expr.function, *limits) 

299 else: 

300 raise ReorderError(expr, "could not interchange the two limits specified") 

301 

302 @property 

303 def has_empty_sequence(self): 

304 """ 

305 Returns True if the Sum or Product is computed for an empty sequence. 

306 

307 Examples 

308 ======== 

309 

310 >>> from sympy import Sum, Product, Symbol 

311 >>> m = Symbol('m') 

312 >>> Sum(m, (m, 1, 0)).has_empty_sequence 

313 True 

314 

315 >>> Sum(m, (m, 1, 1)).has_empty_sequence 

316 False 

317 

318 >>> M = Symbol('M', integer=True, positive=True) 

319 >>> Product(m, (m, 1, M)).has_empty_sequence 

320 False 

321 

322 >>> Product(m, (m, 2, M)).has_empty_sequence 

323 

324 >>> Product(m, (m, M + 1, M)).has_empty_sequence 

325 True 

326 

327 >>> N = Symbol('N', integer=True, positive=True) 

328 >>> Sum(m, (m, N, M)).has_empty_sequence 

329 

330 >>> N = Symbol('N', integer=True, negative=True) 

331 >>> Sum(m, (m, N, M)).has_empty_sequence 

332 False 

333 

334 See Also 

335 ======== 

336 

337 has_reversed_limits 

338 has_finite_limits 

339 

340 """ 

341 ret_None = False 

342 for lim in self.limits: 

343 dif = lim[1] - lim[2] 

344 eq = Eq(dif, 1) 

345 if eq == True: 

346 return True 

347 elif eq == False: 

348 continue 

349 else: 

350 ret_None = True 

351 

352 if ret_None: 

353 return None 

354 return False