Vector objects#

First, install and import Vector.

[1]:
import vector

Making a vector#

If you only need a few vectors or performance is not a concern, you can make vectors as Python objects. The basic constructor for that is vector.obj, and the type of vector (2D/3D/4D, coordinate system, geometric or momentum) depends on the pattern of keyword arguments provided.

Below is a 2D, Cartesian, geometric vector:

[2]:
vector.obj(x=1.1, y=2.2)
[2]:
VectorObject2D(x=1.1, y=2.2)

Below is a 3D, Cartesian, momentum vector:

[3]:
vector.obj(px=1.1, py=2.2, pz=3.3)
[3]:
MomentumObject3D(px=1.1, py=2.2, pz=3.3)

Below is a 4D geometric vector that has Cartesian azimuthal components (x and y), the longitudinal component is expressed in pseudorapidity, and the temporal component is expressed using proper time:

[4]:
vector.obj(x=1.1, y=2.2, eta=3.3, tau=4.4)
[4]:
VectorObject4D(x=1.1, y=2.2, eta=3.3, tau=4.4)

The allowed keyword arguments for 2D vectors are:

  • x and y for Cartesian azimuthal coordinates,

  • px (\(p_x\)) and py (\(p_y\)) for momentum,

  • rho (\(\rho\)) and phi (\(\phi\)) for polar azimuthal coordinates,

  • pt (\(p_T\)) and phi (\(\phi\)) for momentum.

For 3D vectors, you need the above and:

  • z for the Cartesian longitudinal coordinate,

  • pz (\(p_z\)) for momentum,

  • theta (\(\theta\)) for the spherical polar angle (from \(0\) to \(\pi\), inclusive),

  • eta (\(\eta\)) for pseudorapidity, which is a kind of spherical polar angle: \(\eta = -\ln \left[ \tan \left( \frac{\theta}{2} \right) \right]\).

For 4D vectors, you need the above and:

  • t for the Cartesian temporal coordinate,

  • e, E, or energy to get four-momentum,

  • tau (\(\tau\)) for the “proper time” (temporal coordinate in the vector’s rest coordinate system),

  • m, M, or mass to get four-momentum.

Since momentum vectors have momentum-synonyms in addition to the geometrical names, any momentum-synonym will make the whole vector a momentum vector. The meanings of the geometric components are illustrated below:

de631c03964f4392b7e72efe69f8ba27

This one constructor, vector.obj, can output a variety of data types. If you want to control the type more explicitly, you can use vector.VectorObject2D, vector.MomentumObject2D, vector.VectorObject3D, vector.MomentumObject3D, vector.VectorObject4D, and vector.MomentumObject4D to construct or check the type explicitly. These classes also have from_* methods to construct vectors from positional arguments, rather than keyword arguments.

Using a vector#

Vector objects have a suite of properties and methods appropriate to their type (2D/3D/4D, geometric or momentum). For example, to compute the cross-product of two vectors, you would use cross:

[5]:
a = vector.obj(x=2, y=3, z=4)
b = vector.obj(x=1, y=0, z=2)

a.cross(b)
[5]:
VectorObject3D(x=6, y=0, z=-3)

or to compute the angle between them, you would use deltaangle:

[6]:
a.deltaangle(b)
[6]:
0.590872750145419

or to compute their sum, you would use +:

[7]:
a + b
[7]:
VectorObject3D(x=3, y=3, z=6)

In this last example, the + operator overloads the add method. Similarly, multiplication between a vector and a scalar number overloads scale, etc. Since they overload standard operators, vectors can be used in Python built-in functions like sum, as long as you provide a start:

[8]:
vs = [vector.obj(x=x, y=x / 10) for x in range(10)]

sum(vs, start=vector.obj(x=0, y=0))
[8]:
VectorObject2D(x=45, y=4.5)

The same applies to abs for the vector’s magnitude, but note that this depends on the number of dimensions:

[9]:
abs(vector.obj(x=3, y=4))  # sqrt(3**2 + 4**2)
[9]:
5.0
[10]:
abs(vector.obj(x=1, y=2, z=2))  # sqrt(1**2 + 2**2 + 2**2)
[10]:
3.0
[11]:
abs(vector.obj(x=3, y=3, z=3, t=6))  # sqrt(6**2 - 3**2 - 3**2 - 3**2)
[11]:
3.0

Equality (equal) and inequality (not_equal) are defined:

[12]:
vector.obj(x=3, y=4) == vector.obj(x=3, y=4)
[12]:
True

But you’ll probably want to use isclose (and possibly specify tolerances):

[13]:
vector.obj(x=3, y=4) == vector.obj(rho=5, phi=0.9272952180016122)
[13]:
False
[14]:
vector.obj(x=3, y=4).isclose(vector.obj(rho=5, phi=0.9272952180016122))
[14]:
True

The full set of properties and methods available to each type of vector (2D/3D/4D, geometric or momentum) is described in

Using coordinate systems#

A vector can be constructed using any combination of coordinate systems and computations will be performed using whatever coordinate system it has. Thus, after creating vectors, you can write code that does not depend on the coordinate system—it becomes a hidden implementation detail.

[15]:
a = vector.obj(x=2, y=1)
b = vector.obj(rho=3, phi=0)

a + b
[15]:
VectorObject2D(x=5.0, y=1.0)

Some of the properties of a vector are coordinates, so you can use Vector to convert coordinates.

[16]:
a.rho, a.phi
[16]:
(2.23606797749979, 0.4636476090008061)

Since the way that you access the original coordinates is the same as the way that you access converted coordinates,

[17]:
a.x, a.y
[17]:
(2, 1)

these conversions are part of the coordinate-abstraction.

For reasons of numerical precision, you might want to open this black box and explicitly change the coordinate system. These methods start with to_*:

[18]:
a.to_rhophi()
[18]:
VectorObject2D(rho=2.23606797749979, phi=0.4636476090008061)
[19]:
b.to_xy()
[19]:
VectorObject2D(x=3.0, y=0.0)

Geometric versus momentum vectors#

Vectors come in two flavors:

  • geometric: only one name for each property or method

  • momentum: same property or method can be accessed with several synonyms (which assume that the vector is a momentum vector).

[20]:
v = vector.obj(x=1, y=2, z=3)
v
[20]:
VectorObject3D(x=1, y=2, z=3)
[21]:
p = vector.obj(px=1, py=2, pz=3)
p
[21]:
MomentumObject3D(px=1, py=2, pz=3)

Calculations are the same in both cases:

[22]:
abs(v)
[22]:
3.7416573867739413
[23]:
abs(p)
[23]:
3.7416573867739413

but there are more ways to express some operations:

[24]:
v.rho
[24]:
2.23606797749979
[25]:
p.rho
[25]:
2.23606797749979
[26]:
p.pt
[26]:
2.23606797749979

The geometric vector satisfies the Zen of Python stipulation that

There should be one– and preferably only one –obvious way to do it.

and code that uses, for example, “pt” to specify “distance from the beamline” is obfuscated code. However, the most common use for these vectors in High Energy Physics (HEP) is to represent the momentum of particles. For that purpose, using “rho” for \(p_T\) is not self-documenting.

Momentum vectors have all of the same properties and methods as geometric vectors as well as momentum synonyms. In some cases, there are multiple momentum synonyms for adherence to different conventions. For example, energy and mass (the temporal component of momentum, as Cartesian and proper time, respectively) have four different spellings:

energy spelling

mass spelling

rationale

t

tau

geometric coordinates; \(\tau\) for proper time is conventional

energy

mass

full names are more self-documenting in the code

e

m

all other coordinates are lower-case single letters (sometimes Greek letters)

E

M

capital E and M (only!) are used in other HEP vector libraries

If any momentum components are used to construct a vector (or if vector.MomentumObject2D, vector.MomentumObject3D, or vector.MomentumObject4D are used explicitly), then the vector is momentum and all synonyms become available.

Numeric data types and numerical error#

Vector does not require any specific numeric data type, such as np.float32 or np.float64, it only requires that vector components are some kind of number, including integers.

[27]:
v = vector.obj(x=1, y=2.2)
[28]:
type(v.x)
[28]:
int
[29]:
type(v.y)
[29]:
float
[30]:
import numpy as np
[31]:
v = vector.obj(x=np.float32(1.1), y=np.float64(2.2))
[32]:
type(v.x)
[32]:
numpy.float32
[33]:
type(v.y)
[33]:
numpy.float64

The same formulas are applied, regardless of the numeric type, so if the numerical error is larger than you expect it to be, check your types (and coordinate systems).

Application to other backends#

Everything stated above about vector objects (except their methods of construction) apply equally to all other backends. Arrays of vectors and symbolic vector expressions in SymPy have the same properties and methods as vector objects, they can hide choice of coordinate system as an abstraction, and the set of synonyms can be minimal for geometric vectors and maximal for momentum vectors. Therefore, it can be convenient to use vector objects as a quick way to debug issues in large arrays. However, note that different backends can use different libraries for computations, and results might differ in numerical error.

In particular, note that SymPy vector expressions have a different sign convention for operations on space-like and negative time-like 4D vectors. For all other backends, Vector’s conventions were chosen to agree with popular HEP libraries, particularly ROOT, but for the SymPy backend, those conventions would insert piecewise if-then branches, which would complicate symbolic expressions.