Skip to content

Helix Operations

In BES3, a helix is represented by 5 parameters: dr, phi0, kappa, dz, tanl. pybes3 provides a convenient way to parse these parameters and perform pivot transformations with parameters and error matrices.

The implementation depends on vector. It is recommended to review Vector objects and Awkward Arrays of vectors before using helix objects, as they are used to represent helix position, momentum, and pivot point.

Transformation rules

The implementation follows these formulas:

  • Position \([\mathrm{cm}]\):

    • \(x = x_0 + dr \times \cos \varphi_0\)
    • \(y = y_0 + dr \times \sin \varphi_0\)
    • \(z = z_0 + dz\)
  • Momentum \([\mathrm{GeV}/c]\):

    • \(p_t = \frac{1}{\left| \kappa \right|}\)
    • \(\varphi = \varphi_0 + \frac{\pi}{2}\)
    • \(p_x = p_t \times \cos(\varphi)\)
    • \(p_y = p_t \times \sin(\varphi)\)
    • \(p_z = p_t \times \tan\lambda\)
  • Others:

    • \(\mathrm{charge} = \mathrm{sign}(\kappa)\)
    • \(r_{\mathrm{trk}} \ [\mathrm{cm}] = \frac{1000}{2.99792458} \times p_t \ [\mathrm{GeV}/c]\)

The magnetic field in BES3 is assumed to be 1 T.

Create helix

From helix parameters (single track)

For a single track, use pybes3.helix_obj:

import pybes3 as p3

helix = p3.helix_obj(
    params=(dr, phi0, kappa, dz, tanl), # helix parameters
    error=error,                        # error matrix (optional)
    pivot=(x0, y0, z0),                 # initial pivot point (optional)
)

When error is not provided, it defaults to None. When pivot is not provided, it defaults to (0, 0, 0).

Overload of helix_obj

When calling helix_obj, you can pass the helix parameters and initial pivot point in different ways:

You can specify the helix parameters in two ways:

p3.helix_obj(dr, phi0, kappa, dz, tanl, ...)
# or
p3.helix_obj(dr=dr, phi0=phi0, kappa=kappa, dz=dz, tanl=tanl, ...)
# or
p3.helix_obj(params=(dr, phi0, kappa, dz, tanl), ...)

You can specify the initial pivot point in different ways:

# As a tuple
p3.helix_obj(..., pivot=(x0, y0, z0))

# As a Vector3D object
pivot = p3.vector.obj(x=x0, y=y0, z=z0)
p3.helix_obj(..., pivot=pivot)

From helix parameters (array)

Warning

You must import pybes3 before creating a helix array, even if the creation does not directly use any pybes3 functions.

The helix array is implemented with awkward's "behavior" mechanism. You can create a helix array via pybes3.helix_awk, or directly create an awkward array with the Bes3Helix behavior.

A common use case is reading helix parameters from a BES3 file:

import pybes3 as p3
import uproot

# Open a BES3 file and read the helix parameters
dst_evt = uproot.open("test.dst")["Event/TDstEvent"]
mdc_trks = dst_evt["m_mdcTrackCol"].array()

# Extract the helix parameters and error matrix
raw_helix = mdc_trks["m_helix"]
raw_helix_err = mdc_trks["m_err"]

# Create a helix array
helix = p3.helix_awk(raw_helix, raw_helix_err)

Info

This overload of helix_awk follows such convention:

dr = raw_helix[..., 0]
phi0 = raw_helix[..., 1]
kappa = raw_helix[..., 2]
dz = raw_helix[..., 3]
tanl = raw_helix[..., 4]

If you want to specify the helix parameters directly, pass them as keyword arguments:

# Prepare helix parameters arrays
dr = ak.Array(...)
phi0 = ak.Array(...)
kappa = ak.Array(...)
dz = ak.Array(...)
tanl = ak.Array(...)

# Optional, if you have an error matrix
error = ak.Array(...)

# Create a helix array with the parameters
helix = p3.helix_awk(
    dr=dr,
    phi0=phi0,
    kappa=kappa,
    dz=dz,
    tanl=tanl,
    error=error, # Optional, if you have an error matrix
)

Note

Since each track has a 5x5 error matrix, if a track array has shape (n, var), the error matrix should have shape (n, var, 5, 5).

You can also specify the initial pivot point:

helix = p3.helix_awk(
    dr=dr,
    phi0=phi0,
    kappa=kappa,
    dz=dz,
    tanl=tanl,
    error=error,        # Optional, if you have an error matrix
    pivot=(x0, y0, z0)  # Initial pivot point
)

Different types of pivot point

The pivot can be specified in different ways when using helix_awk:

  • As a tuple or Vector3D object

    If all tracks share the same pivot point, you can specify it as a tuple or a Vector3D object:

    # As a tuple
    helix = p3.helix_awk(..., pivot=(x0, y0, z0))
    
    # As a Vector3D object
    pivot = p3.vector.obj(x=x0, y=y0, z=z0)
    helix = p3.helix_awk(..., pivot=pivot)
    
  • As an awkward array

    If you have different pivot points for each track, you can provide a Vector3D awkward array:

    pivot = ak.Array({
        "x": ak.Array(...),
        "y": ak.Array(...),
        "z": ak.Array(...),
    }, with_name="Vector3D")
    
    helix = p3.helix_awk(..., pivot=pivot)
    
Directly create helix array

To create a helix array directly, first import necessary modules:

import pybes3
import awkward as ak

Prepare helix parameters:

dr = ak.Array(...)
phi0 = ak.Array(...)
kappa = ak.Array(...)
dz = ak.Array(...)
tanl = ak.Array(...)

Prepare initial pivot point:

pivot = ak.Array({
    "x": ak.zeros_like(dr),
    "y": ak.zeros_like(dr),
    "z": ak.zeros_like(dr),
}, with_name="Vector3D")

Create helix array (with no error matrix)

helix = ak.Array({
    "dr": dr,
    "phi0": phi0,
    "kappa": kappa,
    "dz": dz,
    "tanl": tanl,
    "pivot": pivot,
}, with_name="Bes3Helix")

Optionally, you can also create an error matrix.

error = ak.Array(...)

helix = ak.Array({
    "dr": dr,
    "phi0": phi0,
    "kappa": kappa,
    "dz": dz,
    "tanl": tanl,
    "error": error,
    "pivot": pivot,
}, with_name="Bes3Helix")

Create helix from physics parameters

You can also create a helix from position, momentum, and charge. Pass these parameters to pybes3.helix_obj or pybes3.helix_awk, and pybes3 will automatically calculate the helix parameters.

To create a helix object, use:

p3.helix_obj(
    position=(x, y, z),     # position of the track
    momentum=(px, py, pz),  # momentum of the track
    charge=charge,          # charge of the track
    error=error,            # error matrix (optional)
    pivot=(x0, y0, z0)      # initial pivot point (optional)
)

where charge should be 1 or -1, and the pivot defaults to (0, 0, 0) if not provided.

To create a helix array, use:

position = ak.Array({
    "x": ak.Array(...),
    "y": ak.Array(...),
    "z": ak.Array(...),
}, with_name="Vector3D")

momentum = ak.Array({
    "px": ak.Array(...),
    "py": ak.Array(...),
    "pz": ak.Array(...),
}, with_name="Vector3D")

charge = ak.Array(...)  # values should be 1 or -1

p3.helix_awk(
    position=position,
    momentum=momentum,
    charge=charge,
    error=error,    # error matrix (optional)
    pivot=pivot     # initial pivot point (optional)
)

where charge should be array of 1 or -1, and the pivot defaults to (0, 0, 0) if not provided.

Create helix from MC truth

When creating a helix from MC truth, set pivot to the MC truth position, then change it to (0, 0, 0) using the change_pivot method:

truth_helix = p3.helix_obj(
    position=(x, y, z),
    momentum=(px, py, pz),
    charge=charge,
    pivot=(x, y, z)  # use the MC truth position as pivot
).change_pivot(0, 0, 0)  # change pivot to (0, 0, 0)

or, for awkward array:

truth_helix = p3.helix_awk(
    position=position,
    momentum=momentum,
    charge=charge,
    pivot=position  # use the MC truth position as pivot
).change_pivot(0, 0, 0)  # change pivot to (0, 0, 0)

Then you can retrieve the helix parameters via the dr, phi0, kappa, dz, tanl properties.

Physics information

Once you have a helix object or awkward array, you can retrieve physical parameters such as position, momentum, charge, and circular radius.

Position

The position is defined as the closest point on the helix to the pivot point:

position = helix.position

This returns a Vector3D object. You can access individual coordinates with:

x = position.x
y = position.y
z = position.z

See Also

See Interface for 3D vectors for more properties.

Momentum

The momentum is defined at the closest point on the helix to the pivot point:

momentum = helix.momentum

This returns a Momentum3D object with many accessible components:

px = momentum.px
py = momentum.py
pz = momentum.pz
pt = momentum.pt  # transverse momentum
p = momentum.p    # total momentum
costheta = momentum.costheta  # cosine of the polar angle
theta = momentum.theta  # polar angle
phi = momentum.phi  # azimuthal angle

See Also

See Interface for 3D momentum for more properties.

Momentum depends on the pivot point

When changing the pivot point, the momentum also changes, because it is evaluated at the closest point to the pivot.

Pivot point

Retrieve the pivot point of the helix:

pivot = helix.pivot

Similar to position, this returns a Vector3D object.

Charge

Retrieve the charge of the track:

charge = helix.charge

Returns 1 or -1, or an awkward array of these values for helix arrays.

Circular radius

The circular radius is the radius of the 2D circle in the XY plane that the track follows:

r = helix.radius

Returns a scalar value or an awkward array, depending on the helix type.

Pivot transformation

Use change_pivot to transform the helix parameters and error matrix to a new pivot point:

helix = p3.helix_obj(...)

# Change pivot point to (3, 4, 5)
helix = helix.change_pivot(3, 4, 5)

or pass a Vector3D object:

pivot = p3.vector.obj(x=3, y=4, z=5)
helix = helix.change_pivot(pivot)

You can change the pivot for each track in an awkward array of helix:

helix = p3.helix_awk(...)
new_pivot = ak.Array({
    "x": ak.Array(...),
    "y": ak.Array(...),
    "z": ak.Array(...),
}, with_name="Vector3D")

helix = helix.change_pivot(new_pivot)

or change the pivot point to a specific point for each track:

# Change pivot point to (3, 4, 5) for each track
helix = helix.change_pivot(3, 4, 5)

Warning

change_pivot returns a new helix object/array; it does not modify the original.

Helix comparison

Compare two helix objects or arrays using the isclose method. If they have different pivot points, the second helix is automatically transformed to the first helix's pivot point before comparison.

helix1 = p3.helix_obj(...)
helix2 = p3.helix_obj(...)

# Compare two helix objects, returns a boolean value
is_close = helix1.isclose(helix2)
helix1 = p3.helix_awk(...)
helix2 = p3.helix_awk(...)

# Compare two helix awkward arrays, returns an awkward array of boolean values
is_close = helix1.isclose(helix2)

The comparison uses numpy.isclose to check whether the helix parameters and error matrix are close enough. You can customize the tolerance:

is_close = helix1.isclose(helix2, rtol=1e-5, atol=1e-8, equal_nan=False)

See numpy.isclose for more details on these parameters.