Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to boost to the center of mass? #134

Closed
cverstege opened this issue Aug 12, 2021 · 6 comments · Fixed by #135
Closed

How to boost to the center of mass? #134

cverstege opened this issue Aug 12, 2021 · 6 comments · Fixed by #135

Comments

@cverstege
Copy link

cverstege commented Aug 12, 2021

I would expect the following code to return a 4Vec with values close to 0 for px, py and pz and the rest mass as energy, but it does not:

import vector
particle = vector.obj(px=0.012665269027980897, py=-0.7570775769417907, pz=1.5215944275747002, E=5.37456054969824)
print(particle.boost(-particle))

Output: vector.obj(px=-0.026700724532754565, py=1.5960592536320295, pz=-3.207801869150135, E=-6.231808367987319)

Am I missing something obvious here?

This exact same code produces a vector of the particle in the rest frame with root.

Edit: particle.boost(particle) and particle.boost_p4(particle) with either sign don't work either.

@cverstege
Copy link
Author

It seems to be a bug with 4vecs defined with energy, as defining a 4vec with mass works perfectly fine, as does calculating beta:

import vector
particle = vector.obj(px=0.012665269027980897, py=-0.7570775769417907, pz=1.5215944275747002, E=5.37456054969824)
m_particle = vector.obj(m=particle.m, px=particle.px, py=particle.py, pz=particle.pz)
print(particle.boost(-particle))  # does not work
print(m_particle.boost(-m_particle))  # works
print(particle.boost_beta3(-particle.to_beta3()))  # works

Output:

vector.obj(px=-0.026700724532754565, py=1.5960592536320295, pz=-3.207801869150135, E=-6.231808367987319)
vector.obj(px=-1.734723475976807e-18, py=1.1102230246251565e-16, pz=0.0, mass=5.098757165604051)
vector.obj(px=3.469446951953614e-18, py=-1.1102230246251565e-16, pz=0.0, E=5.098757165604052)

@cverstege cverstege changed the title Problem when boosting particle Bug when boosting 4vec initialized with Energy instead of mass Aug 12, 2021
@cverstege
Copy link
Author

cverstege commented Aug 12, 2021

Btw: -particle and -m_particle does not only invert the momentum but also energy/mass. I don't know if this is expected behaviour.
In Physics -particle often means a parity transformation, which should only affect the momentum, not the energy. When doing particle1 - particle2 this might not be the case...

In either way, this behavior should be properly documented.

Edit:
Different behaviors when calculating beta of inverted particles:

import vector
particle = vector.obj(px=0.012665269027980897, py=-0.7570775769417907, pz=1.5215944275747002, E=5.37456054969824)
beta1 = -(particle.to_beta3())
beta2 = (-particle).to_beta3()
print(particle.boost_beta3(beta1))  # rest frame
print(particle.boost_beta3(beta2))  # same as particle.boost(particle), not rest frame
import vector
particle = vector.obj(px=0.012665269027980897, py=-0.7570775769417907, pz=1.5215944275747002, E=5.37456054969824)
m_particle = vector.obj(m=particle.m, px=particle.px, py=particle.py, pz=particle.pz)
beta1 = -(m_particle.to_beta3())
beta2 = (-m_particle).to_beta3()
print(m_particle.boost_beta3(beta1))  # rest frame, same as m_particle.boost(-m_particle)
print(m_particle.boost_beta3(beta2))  # momentum is nan

jpivarski added a commit that referenced this issue Aug 12, 2021
* Add boostCM_of to clarify #134, supported by scale*D and neg*D.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Refactor to satisfy MyPy.

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
@jpivarski
Copy link
Member

There are some convention-setting choices to make here, such as whether -p4 should negate all components or only the spatial components, and in all of these cases, we have followed what ROOT does. One could argue about whether they're the "right" conventions or the "wrong" conventions, but at least they should be familiar to users with previous experience with ROOT, and should help avoid an impedance mismatch when working with both Vector and ROOT.

So, on the point of negating a Lorentz vector, ROOT negates all components, regardless of whether the temporal component is energy-like (t) or mass-like (tau). In the following examples, note that ROOT treats timelike and spacelike vectors differently, that 4**2 > 1**2 + 2**2 + 3**2, and that ROOT uses negative mass for spacelike vectors defined by mass (instead of, say, complex numbers).

>>> import ROOT
>>>
>>> timelike_t = ROOT.Math.PxPyPzEVector(1, 2, 3, 4)
>>> print(timelike_t)
(1,2,3,4)
>>> print(-timelike_t)
(-1,-2,-3,-4)
>>>
>>> timelike_m = ROOT.Math.PxPyPzMVector(1, 2, 3, 4)
>>> print(timelike_m)
(1,2,3,4)
>>> print(-timelike_m)
(-1,-2,-3,-4)
>>>
>>> spacelike_t = ROOT.Math.PxPyPzEVector(1, 2, 3, 0.5)
>>> print(spacelike_t)
(1,2,3,0.5)
>>> print(-spacelike_t)
(-1,-2,-3,-0.5)
>>> 
>>> spacelike_m = ROOT.Math.PxPyPzMVector(1, 2, 3, -0.5)
>>> print(spacelike_m)
(1,2,3,-0.5)
>>> print(-spacelike_m)
(-1,-2,-3,0.5)

Now, for boosting, v.boost(v) boosts v in the direction of v, which takes it twice as far from its center of mass frame, rather than to it. As you have discovered, you want to boost in the opposite direction, and to do that, you need to negate only the spatial components of the booster. There hadn't been a way to do that, so #135 adds the appropriate methods and properties.

It also adds a convenience function for boosting to the center-of-mass (CM), boostCM_of:

>>> import vector
>>> v = vector.obj(px=1, py=2, pz=3, E=4)
>>> 
>>> v.boostCM_of(v)
vector.obj(px=4.440892098500626e-16, py=8.881784197001252e-16, pz=1.7763568394002505e-15, E=1.414213562373094)
>>> 
>>> v.boostCM_of(v.to_beta3())
vector.obj(px=0.0, py=0.0, pz=0.0, E=1.414213562373094)

To do this same thing, ROOT only has the velocity-based approach (the one with beta3).

>>> timelike_t.BoostToCM()
<cppyy.gbl.ROOT.Math.DisplacementVector3D<ROOT::Math::Cartesian3D<double>,ROOT::Math::DefaultCoordinateSystemTag> object at 0x56403a345640>
>>> timelike_t.BoostToCM().X()
-0.25
>>> timelike_t.BoostToCM().Y()
-0.5
>>> timelike_t.BoostToCM().Z()
-0.75
>>> print(ROOT.Math.VectorUtil.boost(timelike_t, timelike_t.BoostToCM()))
(0,0,0,1.41421)
>>> timelike_t.M()
1.4142135623730951

The direct equivalent of this in Vector is v.boostCM_of(v.to_beta3()), which is v.boost(-v.to_beta3()) (shown above). We didn't adopt the name "BoostToCM" because it really means "get a velocity vector that, if you boost to it, would boost you to the center-of-mass frame." Using "Boost" as a verb in "BoostToCM" is really misleading. We're adopting conventions from ROOT, but not the exact naming if the names are misleading, so that's why we don't have a boostToCM. The to_beta3 method literally derives the velocity of a four-momentum, without negating its sign, so it's the opposite of the direction you need to boost to the center-of-mass.

However, boosting to the center-of-mass of something is the most important use-case for boosting. Sure, there's v.boost(-v.to_beta3()), but (a) that takes some thought to figure out, as you did, and (b) we lose the ability to boost using four-momenta alone (without adding a neg3D property, which #135 also does). By adding boostCM_of(booster) for four-momentum and velocity booster, there's a well-named, straightforward way to handle this very common use-case.

As for the distinction between the four-momentum (p4) and velocity (beta3) methods, as you can see they have different numerical precision:

>>> v.boostCM_of(v)
vector.obj(px=4.440892098500626e-16, py=8.881784197001252e-16, pz=1.7763568394002505e-15, E=1.414213562373094)
>>> v.boostCM_of(v.to_beta3())
vector.obj(px=0.0, py=0.0, pz=0.0, E=1.414213562373094)

Notwithstanding this example, boosting with a four-momentum should usually have better numerical precision than the velocity method. Converting a four-momentum into a velocity squashes the infinite range of high momenta/energies onto numbers between -1 and 1, then expands it back out again when it's applied to the vector(s) you want to boost. Boosting by a four-momentum is algebraically equivalent to boosting by a velocity, but expressed in a way that avoids large multiplications and divisions for ultra-high-energy boosts. That's why Vector provides both, and I'm a little surprised that I could only find velocity-boosting in ROOT. (Maybe I just couldn't find it.) So, if you're boosting to a frame given by a four-momentum, it would generally be better to do it with the four-momentum itself, rather than first converting to a velocity, but if you're given a velocity, you can do that too. (Also, the velocity method allows for a more direct cross-check with ROOT.)

@jpivarski jpivarski changed the title Bug when boosting 4vec initialized with Energy instead of mass How to boost to the center of mass? Aug 12, 2021
@jpivarski
Copy link
Member

You also figured out (and edited away) the fact that TLorentzVector worked this way: you had to say particle.Boost(-particle.BoostVector()) with an explicit minus sign. TLorentzVector was deprecated in favor of ROOT.Math.GenVector about 15 years ago, but it's still widely used.

TLorentzVector's BoostVector is directly equivalent to Vector4D's to_beta3. TLorentzVector's Boost method is directly equivalent to Vector4D's boost method (except that TLorentzVector's acts in-place, while Vector4D's returns the result).

>>> tlv = ROOT.TLorentzVector(1, 2, 3, 4)
>>> tlv.X(), tlv.Y(), tlv.Z(), tlv.T()
(1.0, 2.0, 3.0, 4.0)
>>> beta3 = tlv.BoostVector()
>>> beta3.X(), beta3.Y(), beta3.Z()
(0.25, 0.5, 0.75)
>>> tlv.Boost(tlv.BoostVector())
>>> tlv.X(), tlv.Y(), tlv.Z(), tlv.T()
(5.65685424949238, 11.31370849898476, 16.97056274847714, 21.213203435596423)
>>> 
>>> tlv = ROOT.TLorentzVector(1, 2, 3, 4)   # have to reset the TLorentzVector
>>> tlv.X(), tlv.Y(), tlv.Z(), tlv.T()
(1.0, 2.0, 3.0, 4.0)
>>> tlv.Boost(-beta3)
>>> tlv.X(), tlv.Y(), tlv.Z(), tlv.T()
(0.0, 0.0, 0.0, 1.414213562373095)
>>> 
>>> v = vector.obj(px=1, py=2, pz=3, E=4)
>>> v
vector.obj(px=1, py=2, pz=3, E=4)
>>> beta3 = v.to_beta3()
>>> beta3
vector.obj(px=0.25, py=0.5, pz=0.75)
>>> v.boost(beta3)
vector.obj(px=5.65685424949238, py=11.31370849898476, pz=16.97056274847714, E=21.213203435596427)
>>> 
>>> v.boost(-beta3)
vector.obj(px=0.0, py=0.0, pz=0.0, E=1.414213562373094)

@cverstege
Copy link
Author

Thank you very much for the new features and in depth explanation. I learned something new about ROOT here.
I did open the issue without fully understanding it, hence the edits. The ROOT code in the beginning was missing the boost vector, that's why i removed it. I should/will take some more time when creating issues and avoiding edits, so it's easier to follow along.

The new methods are very useful and I think it's now clear what v.boost() and v.boostCM_of() do and what's different. Before I naively expected v.boost() to be able to boost to the CM using a 4D vector, because it also accepts 4D vectors and not just 3D vectors.
That -v negates all components makes sense mathematically and is not an issue anymore because the neg3D property was introduced.

I'm currently part of a group which moves study exercises in particle physics partially using PyROOT to a new vectorized and unified approach using the vector package. I think this will be a nice real world use case for the vector package and testing it's features.
If we stumble upon some more questions/uncertainties/bugs myself or someone else might open a new issue.

One last question: why does v.boost(-v) boost a 4d vector to the center of mass, when creating a 4d vector with mass instead of energy? Is it because of a square somewhere in the calculations?

@jpivarski
Copy link
Member

That sounds like a good project. I'm looking forward to it!

As for why it works when defining the vector with mass, these vectors have weird rules about interpreting negative masses as spacelike vectors. Is it only effectively negating the spatial components, within approximation? It might be getting that effect by accident, since the temporal part might be getting clipped when it's mass instead of energy, and that would make the negation a larger effect in the spatial components.

Oh, and I didn't mean to imply that you shouldn't have asked without having the full picture. That's why you ask, after all. I was piecing together what had happened, coming from another project without having thought about this recently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants