Coverage for /usr/lib/python3/dist-packages/sympy/functions/special/tensor_functions.py: 37%

114 statements  

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

1from math import prod 

2 

3from sympy.core import S, Integer 

4from sympy.core.function import Function 

5from sympy.core.logic import fuzzy_not 

6from sympy.core.relational import Ne 

7from sympy.core.sorting import default_sort_key 

8from sympy.external.gmpy import SYMPY_INTS 

9from sympy.functions.combinatorial.factorials import factorial 

10from sympy.functions.elementary.piecewise import Piecewise 

11from sympy.utilities.iterables import has_dups 

12 

13############################################################################### 

14###################### Kronecker Delta, Levi-Civita etc. ###################### 

15############################################################################### 

16 

17 

18def Eijk(*args, **kwargs): 

19 """ 

20 Represent the Levi-Civita symbol. 

21 

22 This is a compatibility wrapper to ``LeviCivita()``. 

23 

24 See Also 

25 ======== 

26 

27 LeviCivita 

28 

29 """ 

30 return LeviCivita(*args, **kwargs) 

31 

32 

33def eval_levicivita(*args): 

34 """Evaluate Levi-Civita symbol.""" 

35 n = len(args) 

36 return prod( 

37 prod(args[j] - args[i] for j in range(i + 1, n)) 

38 / factorial(i) for i in range(n)) 

39 # converting factorial(i) to int is slightly faster 

40 

41 

42class LeviCivita(Function): 

43 """ 

44 Represent the Levi-Civita symbol. 

45 

46 Explanation 

47 =========== 

48 

49 For even permutations of indices it returns 1, for odd permutations -1, and 

50 for everything else (a repeated index) it returns 0. 

51 

52 Thus it represents an alternating pseudotensor. 

53 

54 Examples 

55 ======== 

56 

57 >>> from sympy import LeviCivita 

58 >>> from sympy.abc import i, j, k 

59 >>> LeviCivita(1, 2, 3) 

60 1 

61 >>> LeviCivita(1, 3, 2) 

62 -1 

63 >>> LeviCivita(1, 2, 2) 

64 0 

65 >>> LeviCivita(i, j, k) 

66 LeviCivita(i, j, k) 

67 >>> LeviCivita(i, j, i) 

68 0 

69 

70 See Also 

71 ======== 

72 

73 Eijk 

74 

75 """ 

76 

77 is_integer = True 

78 

79 @classmethod 

80 def eval(cls, *args): 

81 if all(isinstance(a, (SYMPY_INTS, Integer)) for a in args): 

82 return eval_levicivita(*args) 

83 if has_dups(args): 

84 return S.Zero 

85 

86 def doit(self, **hints): 

87 return eval_levicivita(*self.args) 

88 

89 

90class KroneckerDelta(Function): 

91 """ 

92 The discrete, or Kronecker, delta function. 

93 

94 Explanation 

95 =========== 

96 

97 A function that takes in two integers $i$ and $j$. It returns $0$ if $i$ 

98 and $j$ are not equal, or it returns $1$ if $i$ and $j$ are equal. 

99 

100 Examples 

101 ======== 

102 

103 An example with integer indices: 

104 

105 >>> from sympy import KroneckerDelta 

106 >>> KroneckerDelta(1, 2) 

107 0 

108 >>> KroneckerDelta(3, 3) 

109 1 

110 

111 Symbolic indices: 

112 

113 >>> from sympy.abc import i, j, k 

114 >>> KroneckerDelta(i, j) 

115 KroneckerDelta(i, j) 

116 >>> KroneckerDelta(i, i) 

117 1 

118 >>> KroneckerDelta(i, i + 1) 

119 0 

120 >>> KroneckerDelta(i, i + 1 + k) 

121 KroneckerDelta(i, i + k + 1) 

122 

123 Parameters 

124 ========== 

125 

126 i : Number, Symbol 

127 The first index of the delta function. 

128 j : Number, Symbol 

129 The second index of the delta function. 

130 

131 See Also 

132 ======== 

133 

134 eval 

135 DiracDelta 

136 

137 References 

138 ========== 

139 

140 .. [1] https://en.wikipedia.org/wiki/Kronecker_delta 

141 

142 """ 

143 

144 is_integer = True 

145 

146 @classmethod 

147 def eval(cls, i, j, delta_range=None): 

148 """ 

149 Evaluates the discrete delta function. 

150 

151 Examples 

152 ======== 

153 

154 >>> from sympy import KroneckerDelta 

155 >>> from sympy.abc import i, j, k 

156 

157 >>> KroneckerDelta(i, j) 

158 KroneckerDelta(i, j) 

159 >>> KroneckerDelta(i, i) 

160 1 

161 >>> KroneckerDelta(i, i + 1) 

162 0 

163 >>> KroneckerDelta(i, i + 1 + k) 

164 KroneckerDelta(i, i + k + 1) 

165 

166 # indirect doctest 

167 

168 """ 

169 

170 if delta_range is not None: 

171 dinf, dsup = delta_range 

172 if (dinf - i > 0) == True: 

173 return S.Zero 

174 if (dinf - j > 0) == True: 

175 return S.Zero 

176 if (dsup - i < 0) == True: 

177 return S.Zero 

178 if (dsup - j < 0) == True: 

179 return S.Zero 

180 

181 diff = i - j 

182 if diff.is_zero: 

183 return S.One 

184 elif fuzzy_not(diff.is_zero): 

185 return S.Zero 

186 

187 if i.assumptions0.get("below_fermi") and \ 

188 j.assumptions0.get("above_fermi"): 

189 return S.Zero 

190 if j.assumptions0.get("below_fermi") and \ 

191 i.assumptions0.get("above_fermi"): 

192 return S.Zero 

193 # to make KroneckerDelta canonical 

194 # following lines will check if inputs are in order 

195 # if not, will return KroneckerDelta with correct order 

196 if i != min(i, j, key=default_sort_key): 

197 if delta_range: 

198 return cls(j, i, delta_range) 

199 else: 

200 return cls(j, i) 

201 

202 @property 

203 def delta_range(self): 

204 if len(self.args) > 2: 

205 return self.args[2] 

206 

207 def _eval_power(self, expt): 

208 if expt.is_positive: 

209 return self 

210 if expt.is_negative and expt is not S.NegativeOne: 

211 return 1/self 

212 

213 @property 

214 def is_above_fermi(self): 

215 """ 

216 True if Delta can be non-zero above fermi. 

217 

218 Examples 

219 ======== 

220 

221 >>> from sympy import KroneckerDelta, Symbol 

222 >>> a = Symbol('a', above_fermi=True) 

223 >>> i = Symbol('i', below_fermi=True) 

224 >>> p = Symbol('p') 

225 >>> q = Symbol('q') 

226 >>> KroneckerDelta(p, a).is_above_fermi 

227 True 

228 >>> KroneckerDelta(p, i).is_above_fermi 

229 False 

230 >>> KroneckerDelta(p, q).is_above_fermi 

231 True 

232 

233 See Also 

234 ======== 

235 

236 is_below_fermi, is_only_below_fermi, is_only_above_fermi 

237 

238 """ 

239 if self.args[0].assumptions0.get("below_fermi"): 

240 return False 

241 if self.args[1].assumptions0.get("below_fermi"): 

242 return False 

243 return True 

244 

245 @property 

246 def is_below_fermi(self): 

247 """ 

248 True if Delta can be non-zero below fermi. 

249 

250 Examples 

251 ======== 

252 

253 >>> from sympy import KroneckerDelta, Symbol 

254 >>> a = Symbol('a', above_fermi=True) 

255 >>> i = Symbol('i', below_fermi=True) 

256 >>> p = Symbol('p') 

257 >>> q = Symbol('q') 

258 >>> KroneckerDelta(p, a).is_below_fermi 

259 False 

260 >>> KroneckerDelta(p, i).is_below_fermi 

261 True 

262 >>> KroneckerDelta(p, q).is_below_fermi 

263 True 

264 

265 See Also 

266 ======== 

267 

268 is_above_fermi, is_only_above_fermi, is_only_below_fermi 

269 

270 """ 

271 if self.args[0].assumptions0.get("above_fermi"): 

272 return False 

273 if self.args[1].assumptions0.get("above_fermi"): 

274 return False 

275 return True 

276 

277 @property 

278 def is_only_above_fermi(self): 

279 """ 

280 True if Delta is restricted to above fermi. 

281 

282 Examples 

283 ======== 

284 

285 >>> from sympy import KroneckerDelta, Symbol 

286 >>> a = Symbol('a', above_fermi=True) 

287 >>> i = Symbol('i', below_fermi=True) 

288 >>> p = Symbol('p') 

289 >>> q = Symbol('q') 

290 >>> KroneckerDelta(p, a).is_only_above_fermi 

291 True 

292 >>> KroneckerDelta(p, q).is_only_above_fermi 

293 False 

294 >>> KroneckerDelta(p, i).is_only_above_fermi 

295 False 

296 

297 See Also 

298 ======== 

299 

300 is_above_fermi, is_below_fermi, is_only_below_fermi 

301 

302 """ 

303 return ( self.args[0].assumptions0.get("above_fermi") 

304 or 

305 self.args[1].assumptions0.get("above_fermi") 

306 ) or False 

307 

308 @property 

309 def is_only_below_fermi(self): 

310 """ 

311 True if Delta is restricted to below fermi. 

312 

313 Examples 

314 ======== 

315 

316 >>> from sympy import KroneckerDelta, Symbol 

317 >>> a = Symbol('a', above_fermi=True) 

318 >>> i = Symbol('i', below_fermi=True) 

319 >>> p = Symbol('p') 

320 >>> q = Symbol('q') 

321 >>> KroneckerDelta(p, i).is_only_below_fermi 

322 True 

323 >>> KroneckerDelta(p, q).is_only_below_fermi 

324 False 

325 >>> KroneckerDelta(p, a).is_only_below_fermi 

326 False 

327 

328 See Also 

329 ======== 

330 

331 is_above_fermi, is_below_fermi, is_only_above_fermi 

332 

333 """ 

334 return ( self.args[0].assumptions0.get("below_fermi") 

335 or 

336 self.args[1].assumptions0.get("below_fermi") 

337 ) or False 

338 

339 @property 

340 def indices_contain_equal_information(self): 

341 """ 

342 Returns True if indices are either both above or below fermi. 

343 

344 Examples 

345 ======== 

346 

347 >>> from sympy import KroneckerDelta, Symbol 

348 >>> a = Symbol('a', above_fermi=True) 

349 >>> i = Symbol('i', below_fermi=True) 

350 >>> p = Symbol('p') 

351 >>> q = Symbol('q') 

352 >>> KroneckerDelta(p, q).indices_contain_equal_information 

353 True 

354 >>> KroneckerDelta(p, q+1).indices_contain_equal_information 

355 True 

356 >>> KroneckerDelta(i, p).indices_contain_equal_information 

357 False 

358 

359 """ 

360 if (self.args[0].assumptions0.get("below_fermi") and 

361 self.args[1].assumptions0.get("below_fermi")): 

362 return True 

363 if (self.args[0].assumptions0.get("above_fermi") 

364 and self.args[1].assumptions0.get("above_fermi")): 

365 return True 

366 

367 # if both indices are general we are True, else false 

368 return self.is_below_fermi and self.is_above_fermi 

369 

370 @property 

371 def preferred_index(self): 

372 """ 

373 Returns the index which is preferred to keep in the final expression. 

374 

375 Explanation 

376 =========== 

377 

378 The preferred index is the index with more information regarding fermi 

379 level. If indices contain the same information, 'a' is preferred before 

380 'b'. 

381 

382 Examples 

383 ======== 

384 

385 >>> from sympy import KroneckerDelta, Symbol 

386 >>> a = Symbol('a', above_fermi=True) 

387 >>> i = Symbol('i', below_fermi=True) 

388 >>> j = Symbol('j', below_fermi=True) 

389 >>> p = Symbol('p') 

390 >>> KroneckerDelta(p, i).preferred_index 

391 i 

392 >>> KroneckerDelta(p, a).preferred_index 

393 a 

394 >>> KroneckerDelta(i, j).preferred_index 

395 i 

396 

397 See Also 

398 ======== 

399 

400 killable_index 

401 

402 """ 

403 if self._get_preferred_index(): 

404 return self.args[1] 

405 else: 

406 return self.args[0] 

407 

408 @property 

409 def killable_index(self): 

410 """ 

411 Returns the index which is preferred to substitute in the final 

412 expression. 

413 

414 Explanation 

415 =========== 

416 

417 The index to substitute is the index with less information regarding 

418 fermi level. If indices contain the same information, 'a' is preferred 

419 before 'b'. 

420 

421 Examples 

422 ======== 

423 

424 >>> from sympy import KroneckerDelta, Symbol 

425 >>> a = Symbol('a', above_fermi=True) 

426 >>> i = Symbol('i', below_fermi=True) 

427 >>> j = Symbol('j', below_fermi=True) 

428 >>> p = Symbol('p') 

429 >>> KroneckerDelta(p, i).killable_index 

430 p 

431 >>> KroneckerDelta(p, a).killable_index 

432 p 

433 >>> KroneckerDelta(i, j).killable_index 

434 j 

435 

436 See Also 

437 ======== 

438 

439 preferred_index 

440 

441 """ 

442 if self._get_preferred_index(): 

443 return self.args[0] 

444 else: 

445 return self.args[1] 

446 

447 def _get_preferred_index(self): 

448 """ 

449 Returns the index which is preferred to keep in the final expression. 

450 

451 The preferred index is the index with more information regarding fermi 

452 level. If indices contain the same information, index 0 is returned. 

453 

454 """ 

455 if not self.is_above_fermi: 

456 if self.args[0].assumptions0.get("below_fermi"): 

457 return 0 

458 else: 

459 return 1 

460 elif not self.is_below_fermi: 

461 if self.args[0].assumptions0.get("above_fermi"): 

462 return 0 

463 else: 

464 return 1 

465 else: 

466 return 0 

467 

468 @property 

469 def indices(self): 

470 return self.args[0:2] 

471 

472 def _eval_rewrite_as_Piecewise(self, *args, **kwargs): 

473 i, j = args 

474 return Piecewise((0, Ne(i, j)), (1, True))