Skip to content

Commit 706fc12

Browse files
committed
Merge branch 'main' into public
2 parents 53f4c67 + e8dcac6 commit 706fc12

19 files changed

+557
-145
lines changed

.github/workflows/cd_pypi.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
- name: Install dependencies
2020
run: |
2121
python -m pip install --upgrade pip
22-
pip install setuptools wheel twine
22+
pip install setuptools wheel importlib-metadata==7.2.1 twine==4.0.2
2323
2424
- name: Build
2525
run: |

CHANGELOG.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Added
1212

13+
### Changed
14+
15+
### Fixed
16+
17+
### Deprecated
18+
19+
20+
## [0.5.0] - 2025-04-24
21+
22+
### Added
23+
1324
- Support for arbitrary Gram determinants, e.g. for a box gram: `Δ_12|3|4|5`
25+
- Support for spinor strings with two open indices, e.g.: `|1+2|3|4|`. The first open index is assumed to be a lower alpha.
26+
- Raising and lowering of spinor indices does works in the presence of additional spin indices.
27+
- `Particles.cluster` accepts a new keyword arguement `massive_fermions`, which allows to specify the states of the fermions. E.g. `massive_fermions=((3, 'u', all), (4, 'd', all))` results in tensor output with open indices IJ, or `massive_fermions=((3, 'u', 1), (4, 'd', 1))` picks the scalar I=1, J=1 component.
28+
- Support for bold numbers representing spinors of massive particles. If a bold number appears where e.g. an integer or rational number should be, the evaluation will fail.
1429

1530
### Changed
1631

32+
- Support for Particles string evaluation containing sqrt (in arbitrary field insteald of only with `mpmath`).
33+
- Particles compute and eval may return a tensor if additional spin indices are present.
34+
1735
### Fixed
1836

37+
- Missleading `Particles.masses` and `Particle.mass` no longer exist (they used to return squared masses). Use `Particles.ms`, `Particles.m2s`, `Particle.m`, `Particle.m2` instead.
1938
- Sphinx fails if autodoc fails, instead of quitely raising a warning.
20-
21-
### Deprecated
39+
- Fixed some spinor string regex parsing.
2240

2341

2442
## [0.4.5] - 2025-01-31
@@ -70,8 +88,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7088

7189
- Fixed issue with `Particles.variety` not correctly recognizing when an hardcoded limit failed with p-adics.
7290

73-
### Deprecated
74-
7591

7692
## [0.4.1] - 2024-01-08
7793

@@ -113,6 +129,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
113129

114130
- Fixed issue where manipulations (such as via arithmetic operations) of `Particles` would lose track of the underlying field.
115131

132+
### Deprecated
133+
134+
- Import of Field from lips is deprecated. Use [syngular](https://github.com/GDeLaurentis/syngular).
135+
116136

117137
## [0.3.1] - 2023-01-16
118138

@@ -131,7 +151,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
131151

132152

133153
[unreleased]: https://github.com/GDeLaurentis/lips/compare/v0.4.5...HEAD
134-
[0.4.4]: https://github.com/GDeLaurentis/lips/compare/v0.4.4...v0.4.5
154+
[0.4.5]: https://github.com/GDeLaurentis/lips/compare/v0.4.4...v0.4.5
135155
[0.4.4]: https://github.com/GDeLaurentis/lips/compare/v0.4.3...v0.4.4
136156
[0.4.3]: https://github.com/GDeLaurentis/lips/compare/v0.4.2...v0.4.3
137157
[0.4.2]: https://github.com/GDeLaurentis/lips/compare/v0.4.1...v0.4.2

lips/hardcoded_limits/particles_set.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def _set(self, temp_string, temp_value, fix_mom=True, mode=1):
2929
if mom_cons is False:
3030
raise myException("Setting: {} to {}. Momentum conservation is not satisfied: ", temp_string, temp_value, max(map(abs, flatten(self.total_mom))))
3131
elif on_shell is False:
32-
raise myException("Setting: {} to {}. On shellness is not satisfied: ", temp_string, temp_value, max(map(abs, flatten(self.masses))))
32+
raise myException("Setting: {} to {}. On shellness is not satisfied: ", temp_string, temp_value, max(map(abs, flatten(self.m2s))))
3333
if not abs_diff <= self.field.tollerance:
3434
raise myException("Failed to set {} to {}. Instead got {}. Absolute difference {}.".format(
3535
temp_string, temp_value, self.compute(temp_string), abs_diff))

lips/hardcoded_limits/particles_set_pair.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def _set_pair(self, t_s1, t_v1, t_s2, t_v2):
3434
if mom_cons is False:
3535
raise myException("Momentum conservation is not satisfied: ", max(map(abs, flatten(self.total_mom))))
3636
elif on_shell is False:
37-
raise myException("On shellness is not satisfied: ", max(map(abs, flatten(self.masses))))
37+
raise myException("On shellness is not satisfied: ", max(map(abs, flatten(self.m2s))))
3838
elif not all([abs_diff1 <= self.field.tollerance, abs_diff2 <= self.field.tollerance]):
3939
raise myException("Failed to set {} to {} and {} to {}. Instead got {} and {}. Absoute differences: {} and {}.".format(
4040
t_s1, abs(t_v1), t_s2, abs(t_v2), abs(self.compute(t_s1)), abs(self.compute(t_s2)), abs_diff1, abs_diff2))

lips/particle.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def __getitem__(self, key):
109109
return self.four_mom[key]
110110

111111
def __hash__(self):
112-
if abs(self.mass) <= self.field.tollerance:
112+
if abs(self.m2) <= self.field.tollerance:
113113
return hash(tuple([tuple(self.r_sp_d.flatten()), tuple(self.l_sp_d.flatten())]))
114114
else:
115115
return hash(tuple(self.r2_sp.flatten()))
@@ -349,9 +349,14 @@ def randomise_padic(self):
349349
def angles_for_squares(self):
350350
"""Flips left and right spinors."""
351351
if self.l_sp_d is not None and self.r_sp_d is not None: # massive scalars do not have these defined
352-
l_sp_d_shape, r_sp_d_shape = self.l_sp_d.shape, self.r_sp_d.shape
353-
self._l_sp_d, self._r_sp_d = self._r_sp_d, self._l_sp_d
354-
self._l_sp_d.shape, self._r_sp_d.shape = l_sp_d_shape, r_sp_d_shape
352+
self._l_sp_d, self._r_sp_d = self._r_sp_d.T, self._l_sp_d.T
353+
if hasattr(self, 'spin_index'):
354+
if self.spin_index == 'u':
355+
self._l_sp_d *= -1
356+
elif self.spin_index == 'd':
357+
self._r_sp_d *= -1
358+
else:
359+
raise Exception("Spin index not understood.")
355360
self._sps_d_to_sps_u()
356361
self._r2_sp = self._r2_sp.T
357362
self._r2_sp_b = self._r2_sp_b.T
@@ -441,25 +446,19 @@ def _r1_sp_to_r2_sp_b(self):
441446
self._l_sp_d.shape = (1, 2)
442447
self._r2_sp_b = numpy.dot(self.r_sp_d, self.l_sp_d)
443448

444-
def _r_sp_d_to_r_sp_u(self):
445-
self._r_sp_u = numpy.dot(LeviCivita, self.r_sp_d)
446-
self._r_sp_u.shape = (1, 2) # row vector
449+
def _r_sp_d_to_r_sp_u(self): # λ^α = ϵ^αβ λ_β
450+
"""⟨a| = λ^α = ϵ^αβ λ_β = ϵ |a⟩ or ⟨a^I| = λ^Iα = ϵ^αβ λ_β^I = ϵ |a^I⟩"""
451+
self._r_sp_u = (LeviCivita @ self.r_sp_d).T
447452

448453
def _l_sp_d_to_l_sp_u(self):
449-
self._l_sp_d.shape = (2, 1) # temporary column vector
450-
self._l_sp_u = numpy.dot(LeviCivita, self.l_sp_d)
451-
self._l_sp_d.shape = (1, 2) # back to row vector
452-
self._l_sp_u.shape = (2, 1) # column vector
454+
"""|a] = λ^α˙ = ϵ^α˙β˙ λ_β˙ = λ_β˙ ϵ^β˙α˙ = [a| ϵ or |a^I] = λ^α˙I = ϵ^α˙β˙ λ^I_β˙ = λ^I_β˙ ϵ^β˙α˙ = [a^I|"""
455+
self._l_sp_u = (self.l_sp_d @ LeviCivita.T).T
453456

454457
def _r_sp_u_to_r_sp_d(self):
455-
self._r_sp_u.shape = (2, 1) # temporary column vector
456-
self._r_sp_d = numpy.dot(numpy.transpose(LeviCivita), self.r_sp_u)
457-
self._r_sp_u.shape = (1, 2) # back to row vector
458-
self._r_sp_d.shape = (2, 1) # column vector
458+
self._r_sp_d = (self.r_sp_u @ LeviCivita).T
459459

460460
def _l_sp_u_to_l_sp_d(self):
461-
self._l_sp_d = numpy.dot(numpy.transpose(LeviCivita), self.l_sp_u)
462-
self._l_sp_d.shape = (1, 2) # row vector
461+
self._l_sp_d = (LeviCivita.T @ self.l_sp_u).T
463462

464463
def _sps_u_to_sps_d(self):
465464
self._l_sp_u_to_l_sp_d()
@@ -509,6 +508,9 @@ def lsq(self):
509508
return lsq
510509

511510
@property
512-
def mass(self):
513-
# Technically this is the mass squared - might want to rename
511+
def m2(self):
514512
return self.lsq()
513+
514+
@property
515+
def m(self):
516+
return self.field.sqrt(self.lsq())

lips/particles.py

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,24 @@
1919
from sympy import NotInvertible
2020

2121
from syngular import Field
22-
from pyadic import PAdic
22+
from pyadic import PAdic, ModP
2323

24-
from .tools import MinkowskiMetric, flatten, subs_dict, pNB, myException, indexing_decorator, pAu, pAd, pSu, pSd, pMVar
24+
from .tools import MinkowskiMetric, flatten, subs_dict, pNB, myException, indexing_decorator, pAu, pAd, pSu, pSd, pMVar, LeviCivita
2525
from .particle import Particle
2626
from .particles_compute import Particles_Compute
2727
from .particles_eval import Particles_Eval
2828
from .hardcoded_limits.particles_set import Particles_Set
2929
from .hardcoded_limits.particles_set_pair import Particles_SetPair
3030
from .algebraic_geometry.particles_singular_variety import Particles_SingularVariety
3131
from .particles_variety import Particles_Variety
32+
from .particles_slices import Particles_Slices
3233

3334

3435
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
3536

3637

37-
class Particles(Particles_Compute, Particles_Eval, Particles_Set, Particles_SetPair, Particles_SingularVariety, Particles_Variety, list):
38+
class Particles(Particles_Compute, Particles_Eval, Particles_Set, Particles_SetPair, Particles_SingularVariety,
39+
Particles_Variety, Particles_Slices, list):
3840
"""Describes the kinematics of n particles. Base one list of Particle objects."""
3941

4042
# MAGIC METHODS
@@ -101,9 +103,14 @@ def total_mom(self):
101103
return sum([oParticle.r2_sp for oParticle in self])
102104

103105
@property
104-
def masses(self):
106+
def m2s(self):
107+
"""Masses squared of all particles in phase space."""
108+
return [oParticle.m2 for oParticle in self]
109+
110+
@property
111+
def ms(self):
105112
"""Masses of all particles in phase space."""
106-
return [oParticle.mass for oParticle in self]
113+
return [oParticle.m for oParticle in self]
107114

108115
@property
109116
def internal_masses_dict(self):
@@ -152,14 +159,35 @@ def copy(self):
152159
from .symmetries import identity
153160
return self.image(identity(len(self)))
154161

155-
def cluster(self, llIntegers):
156-
"""Returns clustered particle objects according to lists of lists of integers (e.g. corners of one loop diagram)."""
162+
def cluster(self, llIntegers, massive_fermions=None):
163+
"""Returns clustered particle objects according to lists of lists of integers (e.g. corners of one loop diagram).
164+
Massive legs are by default massive scalars.
165+
Massive fermions can be specificed as e.g.: massive_fermions=((3, 'u', all), (4, 'd', all)))
166+
"""
157167
drule1 = dict(zip(["s" + "".join(map(str, entry)) for entry in llIntegers], [f"s{i}" for i in range(1, len(llIntegers) + 1)]))
158168
drule2 = dict(zip(["s_" + "".join(map(str, entry)) for entry in llIntegers], [f"s_{i}" for i in range(1, len(llIntegers) + 1)]))
159169
clustered_internal_masses = {key: (subs_dict(val, drule1 | drule2) if isinstance(val, str) else val)
160170
for key, val in self.internal_masses_dict.items()}
161-
return Particles([sum([self[i] for i in corner_as_integers]) for corner_as_integers in llIntegers],
162-
field=self.field, fix_mom_cons=False, internal_masses=clustered_internal_masses)
171+
selfClustered = Particles([sum([self[i] for i in corner_as_integers]) for corner_as_integers in llIntegers],
172+
field=self.field, fix_mom_cons=False, internal_masses=clustered_internal_masses)
173+
if massive_fermions is not None:
174+
for leg, index_position, index_value in massive_fermions:
175+
assert len(llIntegers[leg - 1]) == 2
176+
a, b = llIntegers[leg - 1]
177+
selfClustered[leg]._r_sp_d = numpy.block([self(f"|{a}⟩"), self(f"|{b}⟩")]) # |bold leg> = |leg^I> = (lambda_leg)_alpha^I
178+
selfClustered[leg]._l_sp_d = numpy.block([[self(f"[{a}|")], [self(f"[{b}|")]]) # [bold leg| = [leg_I| = (tilde-lambda_leg)_I_alpha
179+
if index_position == "u":
180+
selfClustered[leg]._l_sp_d = -LeviCivita @ selfClustered[leg]._l_sp_d # [bold leg| = [leg^I||
181+
elif index_position == "d":
182+
selfClustered[leg]._r_sp_d = selfClustered[leg]._r_sp_d @ -LeviCivita # |bold leg> = |leg_I>
183+
else:
184+
raise Exception("Massive fermion spin index position must be either 'u' or 'd'.")
185+
selfClustered[leg].spin_index = index_position
186+
if isinstance(index_value, int):
187+
selfClustered[leg]._r_sp_d = selfClustered[leg]._r_sp_d[:, index_value - 1:index_value]
188+
selfClustered[leg]._l_sp_d = selfClustered[leg]._l_sp_d[index_value - 1:index_value, :]
189+
selfClustered[leg]._sps_d_to_sps_u()
190+
return selfClustered
163191

164192
def make_analytical_d(self, indepVars=None, symbols=('a', 'b', 'c', 'd')):
165193
""" """
@@ -202,6 +230,44 @@ def analytical_subs_d(self):
202230
subs_dict.update({lc[i]: iParticle.l_sp_d[0, 0], ld[i]: iParticle.l_sp_d[0, 1]})
203231
return subs_dict
204232

233+
def subs(self, myDict):
234+
"""For all rank-1 spinor components, substitutes symbols with values from myDict."""
235+
for oP in self:
236+
if isinstance(oP.r_sp_d[0, 0], (sympy.Add, sympy.Mul, sympy.Symbol)):
237+
oP.r_sp_d[0, 0] = oP.r_sp_d[0, 0].subs(myDict)
238+
if isinstance(oP.r_sp_d[0, 0], sympy.Integer) and self.field.name == "finite field":
239+
oP.r_sp_d[0, 0] = ModP(oP.r_sp_d[0, 0], self.field.characteristic)
240+
elif isinstance(oP.r_sp_d[0, 0], sympy.Integer) and self.field.name == "padic":
241+
oP.r_sp_d[0, 0] = PAdic(oP.r_sp_d[0, 0], self.field.characteristic, self.field.digits)
242+
else:
243+
oP.r_sp_d[0, 0] = sympy.poly(oP.r_sp_d[0, 0], modulus=self.field.characteristic ** self.field.digits).as_expr()
244+
if isinstance(oP.r_sp_d[1, 0], (sympy.Add, sympy.Mul, sympy.Symbol)):
245+
oP.r_sp_d[1, 0] = oP.r_sp_d[1, 0].subs(myDict)
246+
if isinstance(oP.r_sp_d[1, 0], sympy.Integer) and self.field.name == "finite field":
247+
oP.r_sp_d[1, 0] = ModP(oP.r_sp_d[1, 0], self.field.characteristic)
248+
elif isinstance(oP.r_sp_d[1, 0], sympy.Integer) and self.field.name == "padic":
249+
oP.r_sp_d[1, 0] = PAdic(oP.r_sp_d[1, 0], self.field.characteristic, self.field.digits)
250+
else:
251+
oP.r_sp_d[1, 0] = sympy.poly(oP.r_sp_d[1, 0], modulus=self.field.characteristic ** self.field.digits).as_expr()
252+
oP.r_sp_d = oP.r_sp_d # trigger setter
253+
if isinstance(oP.l_sp_d[0, 0], (sympy.Add, sympy.Mul, sympy.Symbol)):
254+
oP.l_sp_d[0, 0] = oP.l_sp_d[0, 0].subs(myDict)
255+
if isinstance(oP.l_sp_d[0, 0], sympy.Integer) and self.field.name == "finite field":
256+
oP.l_sp_d[0, 0] = ModP(oP.l_sp_d[0, 0], self.field.characteristic)
257+
elif isinstance(oP.l_sp_d[0, 0], sympy.Integer) and self.field.name == "padic":
258+
oP.l_sp_d[0, 0] = PAdic(oP.l_sp_d[0, 0], self.field.characteristic, self.field.digits)
259+
else:
260+
oP.l_sp_d[0, 0] = sympy.poly(oP.l_sp_d[0, 0], modulus=self.field.characteristic ** self.field.digits).as_expr()
261+
if isinstance(oP.l_sp_d[0, 1], (sympy.Add, sympy.Mul, sympy.Symbol)):
262+
oP.l_sp_d[0, 1] = oP.l_sp_d[0, 1].subs(myDict)
263+
if isinstance(oP.l_sp_d[0, 1], sympy.Integer) and self.field.name == "finite field":
264+
oP.l_sp_d[0, 1] = ModP(oP.l_sp_d[0, 1], self.field.characteristic)
265+
elif isinstance(oP.l_sp_d[0, 1], sympy.Integer) and self.field.name == "padic":
266+
oP.l_sp_d[0, 1] = PAdic(oP.l_sp_d[0, 1], self.field.characteristic, self.field.digits)
267+
else:
268+
oP.l_sp_d[0, 1] = sympy.poly(oP.l_sp_d[0, 1], modulus=self.field.characteristic ** self.field.digits).as_expr()
269+
oP.l_sp_d = oP.l_sp_d # trigger setter
270+
205271
def fix_mom_cons(self, A=0, B=0, real_momenta=False, axis=1): # using real momenta changes both |⟩ and |] of A & B
206272
"""Fixes momentum conservation using particles A and B."""
207273
if A == 0 and B == 0: # defaults to random particles to fix mom cons
@@ -234,7 +300,7 @@ def momentum_conservation_check(self, silent=True):
234300

235301
def onshell_relation_check(self, silent=True):
236302
"""Returns true if all on-shell relations are satisfied."""
237-
onshell_violation = max(map(abs, flatten(self.masses)))
303+
onshell_violation = max(map(abs, flatten(self.m2s)))
238304
if silent is False:
239305
print("The largest on shell violation is {}".format(float(onshell_violation) if type(onshell_violation) is mpmath.mpf else onshell_violation))
240306
if onshell_violation > self.field.tollerance:

0 commit comments

Comments
 (0)