Source code for awkward_zipper.behaviors.vector

"""2D, 3D, and Lorentz vector class mixins

These mixins will eventually be superseded by the `vector <https://github.com/scikit-hep/vector>`__ library,
which will hopefully be feature-compatible. The 2D vector provides cartesian and polar coordinate attributes,
where ``r`` represents the polar distance from the origin. The 3D vector provides cartesian and spherical coordinates,
where ``rho`` represents the 3D distance from the origin and ``r`` is the axial distance from the z axis, so that it can
subclass the 2D vector. The Lorentz vector also subclasses the 3D vector, adding ``t`` as the fourth
cartesian coordinate. Aliases typical of momentum vectors are also provided.

A small example::

    import numpy as np
    import awkward as ak
    from awkward_zipper.behaviors import vector

    n = 1000

    vec = ak.zip(
        {
            "x": np.random.normal(size=n),
            "y": np.random.normal(size=n),
            "z": np.random.normal(size=n),
        },
        with_name="ThreeVector",
        behavior=vector.behavior,
    )

    vec4 = ak.zip(
        {
            "pt": vec.r,
            "eta": -np.log(np.tan(vec.theta/2)),
            "phi": vec.phi,
            "mass": np.full(n, 1.),
        },
        with_name="PtEtaPhiMLorentzVector",
        behavior=vector.behavior,
    )

    assert np.allclose(np.array(vec4.x), np.array(vec.x))
    assert np.allclose(np.array(vec4.y), np.array(vec.y))
    assert np.allclose(np.array(vec4.z), np.array(vec.z))
    assert np.allclose(np.array(abs(2*vec + vec4) / abs(vec)), 3)

"""

import functools
import numbers

import awkward
import numba
import numpy as np
import vector
from vector.backends.awkward import (
    MomentumAwkward2D,
    MomentumAwkward3D,
    MomentumAwkward4D,
)


@numba.vectorize(
    [
        numba.float32(numba.float32, numba.float32, numba.float32, numba.float32),
        numba.float64(numba.float64, numba.float64, numba.float64, numba.float64),
    ]
)
def _mass2_kernel(t, x, y, z):
    return t * t - x * x - y * y - z * z


@numba.vectorize(
    [
        numba.float32(numba.float32, numba.float32),
        numba.float64(numba.float64, numba.float64),
    ]
)
def delta_phi(a, b):
    """Compute difference in angle given two angles a and b

    Returns a value within [-pi, pi)
    """
    return (a - b + np.pi) % (2 * np.pi) - np.pi


@numba.vectorize(
    [
        numba.float32(numba.float32, numba.float32, numba.float32, numba.float32),
        numba.float64(numba.float64, numba.float64, numba.float64, numba.float64),
    ]
)
def delta_r(eta1, phi1, eta2, phi2):
    r"""Distance in (eta,phi) plane given two pairs of (eta,phi)

    :math:`\sqrt{\Delta\eta^2 + \Delta\phi^2}`
    """
    deta = eta1 - eta2
    dphi = delta_phi(phi1, phi2)
    return np.hypot(deta, dphi)


behavior = {}
behavior.update(vector.backends.awkward.behavior)

# Scikit-hep vector maps each coordinate to a single internal slot, so if
# a record carries two aliases from the same group (e.g. both ``x`` and
# ``px``) one is silently ignored. Flag that at validation time.
_X_COMPONENT = frozenset({"x", "px"})
_Y_COMPONENT = frozenset({"y", "py"})
_Z_COMPONENT = frozenset({"z", "pz"})
_AZIMUTHAL_RADIAL = frozenset({"rho", "pt"})
_TEMPORAL = frozenset({"t", "tau", "E", "e", "energy", "M", "m", "mass"})
_AZIMUTHAL_POLAR = frozenset({"rho", "pt", "phi"})
_AZIMUTHAL_CARTESIAN = frozenset({"x", "px", "y", "py"})

_ALIAS_GROUPS = {
    "x-component": _X_COMPONENT,
    "y-component": _Y_COMPONENT,
    "z-component": _Z_COMPONENT,
    "azimuthal radial": _AZIMUTHAL_RADIAL,
    "temporal": _TEMPORAL,
}


@functools.lru_cache(maxsize=4096)
def _coordinate_validation(fields):
    fields = set(fields)
    errors = []
    for label, aliases in _ALIAS_GROUPS.items():
        overlap = fields & aliases
        if len(overlap) > 1:
            errors.append(f"multiple {label} aliases present: {sorted(overlap)}")
    has_xy = bool(fields & _X_COMPONENT) and bool(fields & _Y_COMPONENT)
    has_rhophi = bool(fields & _AZIMUTHAL_RADIAL) and "phi" in fields
    if (has_xy and fields & _AZIMUTHAL_POLAR) or (
        has_rhophi and fields & _AZIMUTHAL_CARTESIAN
    ):
        cartesian = sorted(fields & _AZIMUTHAL_CARTESIAN)
        polar = sorted(fields & _AZIMUTHAL_POLAR)
        errors.append(
            "conflicting azimuthal coordinate representations present: "
            f"cartesian={cartesian}, polar={polar}"
        )

    has_z = bool(fields & _Z_COMPONENT)
    has_theta = "theta" in fields
    has_eta = "eta" in fields
    if sum((has_z, has_theta, has_eta)) > 1:
        present_longitudinal = []
        if has_z:
            present_longitudinal.append(f"z/pz={sorted(fields & _Z_COMPONENT)}")
        if has_theta:
            present_longitudinal.append("theta=['theta']")
        if has_eta:
            present_longitudinal.append("eta=['eta']")
        errors.append(
            "conflicting longitudinal coordinate representations present: "
            + ", ".join(present_longitudinal)
        )
    return (
        tuple(errors),
        has_xy,
        has_rhophi,
        has_z or has_theta or has_eta,
        bool(fields & _TEMPORAL),
    )


# awkward looks up reducer overloads by record name only, so the ones vector
# registers for Momentum{2,3,4}D never reach our collections. Forward those that
# don't rebuild a record. Sum has to go through our own, which keeps the charge
# field and names the result after what it actually built.
_count_reducer = vector.backends.awkward.behavior[awkward.count, "Momentum4D"]
_count_nonzero_reducer = vector.backends.awkward.behavior[
    awkward.count_nonzero, "Momentum4D"
]


[docs] @awkward.mixin_class(behavior) class TwoVector(MomentumAwkward2D): """A cartesian 2-dimensional vector A heavy emphasis towards a momentum vector interpretation is assumed, hence properties like ``px`` and ``py`` are provided in addition to ``x`` and ``y``. This mixin class requires the parent class to provide items ``x`` and ``y``. """ @property def r(self): r"""Distance from origin in XY plane :math:`\sqrt{x^2+y^2}` """ return self.rho @property def r2(self): """Squared ``r``""" return self.rho2
[docs] @awkward.mixin_class_method(np.absolute) def absolute(self): """Returns magnitude of the 2D vector Alias for ``r`` """ return self.r
[docs] @awkward.mixin_class_method(np.negative) def negative(self): """Returns the negative of the vector""" return self.scale(-1)
[docs] def sum(self, axis=-1): """Sum an array of vectors elementwise using ``x`` and ``y`` components""" return awkward.zip( { "x": awkward.sum(self.x, axis=axis), "y": awkward.sum(self.y, axis=axis), }, with_name="TwoVector", behavior=self.behavior, )
@awkward.mixin_class_method(awkward.sum) def _reduce_sum(self, mask_identity): return self.sum(axis=1) @awkward.mixin_class_method(awkward.count) def _reduce_count(self, mask_identity): return _count_reducer(self, mask_identity) @awkward.mixin_class_method(awkward.count_nonzero) def _reduce_count_nonzero(self, mask_identity): return _count_nonzero_reducer(self, mask_identity)
[docs] @awkward.mixin_class_method(np.multiply, {numbers.Number}) def multiply(self, other): """Multiply this vector by a scalar elementwise using ``x`` and ``y`` components""" return self.scale(other)
[docs] @awkward.mixin_class_method(np.divide, {numbers.Number}) def divide(self, other): """Divide this vector by a scalar elementwise using its cartesian components This is realized by using the multiplication functionality""" return self.scale(1 / other)
[docs] def delta_phi(self, other): """Compute difference in angle between two vectors Returns a value within [-pi, pi) """ return self.deltaphi(other)
def __awkward_validation__(self): errors, has_cart, has_polar, has_longitudinal, has_temporal = ( _coordinate_validation(tuple(self.fields)) ) errors = list(errors) if not (has_cart or has_polar): errors.append( "missing azimuthal coordinates: need x/px and y/py, or rho/pt and phi" ) if has_longitudinal or has_temporal: errors.append( "2D vectors cannot include longitudinal or temporal coordinates" ) if errors: raise ValueError(f"{type(self).__name__}: " + "; ".join(errors))
[docs] @awkward.mixin_class(behavior) class PolarTwoVector(TwoVector): """A polar coordinate 2-dimensional vector This mixin class requires the parent class to provide items ``rho`` and ``phi``. Some additional properties are overridden for performance """
[docs] @awkward.mixin_class_method(np.multiply, {numbers.Number}) def multiply(self, other): """Multiply this vector by a scalar elementwise using using ``x`` and ``y`` components In reality, this directly adjusts ``r`` and ``phi`` for performance """ return self.scale(other)
[docs] @awkward.mixin_class_method(np.negative) def negative(self): """Returns the negative of the vector""" return self.scale(-1)
[docs] @awkward.mixin_class(behavior) class ThreeVector(MomentumAwkward3D): """A cartesian 3-dimensional vector A heavy emphasis towards a momentum vector interpretation is assumed. This mixin class requires the parent class to provide items ``x``, ``y``, and ``z``. """ @property def r(self): r"""Distance from origin in XY plane :math:`\sqrt{x^2+y^2}` """ return self.rho @property def r2(self): """Squared ``r``""" return self.rho2
[docs] @awkward.mixin_class_method(np.absolute) def absolute(self): """Returns magnitude of the 3D vector Alias for ``rho`` """ return self.p
[docs] @awkward.mixin_class_method(np.negative) def negative(self): """Returns the negative of the vector""" return self.scale(-1)
[docs] @awkward.mixin_class_method(np.divide, {numbers.Number}) def divide(self, other): """Divide this vector by a scalar elementwise using its cartesian components This is realized by using the multiplication functionality""" return self.scale(1 / other)
[docs] def sum(self, axis=-1): """Sum an array of vectors elementwise using ``x``, ``y``, and ``z`` components""" return awkward.zip( { "x": awkward.sum(self.x, axis=axis), "y": awkward.sum(self.y, axis=axis), "z": awkward.sum(self.z, axis=axis), }, with_name="ThreeVector", behavior=self.behavior, )
@awkward.mixin_class_method(awkward.sum) def _reduce_sum(self, mask_identity): return self.sum(axis=1) @awkward.mixin_class_method(awkward.count) def _reduce_count(self, mask_identity): return _count_reducer(self, mask_identity) @awkward.mixin_class_method(awkward.count_nonzero) def _reduce_count_nonzero(self, mask_identity): return _count_nonzero_reducer(self, mask_identity)
[docs] @awkward.mixin_class_method(np.multiply, {numbers.Number}) def multiply(self, other): """Multiply this vector by a scalar elementwise using ``x``, ``y``, and ``z`` components""" return self.scale(other)
[docs] def delta_phi(self, other): """Compute difference in angle between two vectors Returns a value within [-pi, pi) """ return self.deltaphi(other)
def __awkward_validation__(self): errors, has_cart, has_polar, has_longitudinal, has_temporal = ( _coordinate_validation(tuple(self.fields)) ) errors = list(errors) if not (has_cart or has_polar): errors.append( "missing azimuthal coordinates: need x/px and y/py, or rho/pt and phi" ) if not has_longitudinal: errors.append("missing longitudinal coordinate: need z/pz, theta, or eta") if has_temporal: errors.append("3D vectors cannot include temporal coordinates") if errors: raise ValueError(f"{type(self).__name__}: " + "; ".join(errors))
[docs] @awkward.mixin_class(behavior) class SphericalThreeVector(ThreeVector): """A spherical coordinate 3-dimensional vector This mixin class requires the parent class to provide items ``rho``, ``theta``, and ``phi``. Some additional properties are overridden for performance """ @property def r(self): r"""Distance from origin in XY plane :math:`\sqrt{x^2+y^2} = \rho \sin(\theta)` """ return self.rho
[docs] @awkward.mixin_class_method(np.multiply, {numbers.Number}) def multiply(self, other): """Multiply this vector by a scalar elementwise using ``x``, ``y``, and ``z`` components In reality, this directly adjusts ``r``, ``theta`` and ``phi`` for performance """ return self.scale(other)
[docs] @awkward.mixin_class_method(np.negative) def negative(self): """Returns the negative of the vector""" return self.scale(-1)
def _metric_table_core(a, b, axis, metric, return_combinations): if axis is not None: a, b = awkward.unzip(awkward.cartesian([a, b], axis=axis, nested=True)) mval = metric(a, b) if return_combinations: return mval, (a, b) return mval def _nearest_core(x, y, axis, metric, return_metric, threshold): mval, (a, b) = x.metric_table(y, axis, metric, return_combinations=True) if axis is None: # NotImplementedError: awkward.firsts with axis=-1 axis = y.layout.purelist_depth - 2 mmin = awkward.argmin(mval, axis=axis + 1, keepdims=True) out = awkward.firsts(b[mmin], axis=axis + 1) metric = awkward.firsts(mval[mmin], axis=axis + 1) if threshold is not None: out = awkward.mask(out, metric <= threshold) if return_metric: return out, metric return out
[docs] @awkward.mixin_class(behavior) class LorentzVector(MomentumAwkward4D): """A cartesian Lorentz vector A heavy emphasis towards a momentum vector interpretation is assumed. (+, -, -, -) metric This mixin class requires the parent class to provide items ``x``, ``y``, ``z``, and ``t``. """
[docs] @awkward.mixin_class_method(np.absolute) def absolute(self): """Magnitude of this Lorentz vector Alias for ``mass`` """ return self.mass
[docs] def sum(self, axis=-1): """Sum an array of vectors elementwise using ``x``, ``y``, ``z``, and ``t`` components""" return awkward.zip( { "x": awkward.sum(self.x, axis=axis), "y": awkward.sum(self.y, axis=axis), "z": awkward.sum(self.z, axis=axis), "t": awkward.sum(self.t, axis=axis), }, with_name="LorentzVector", behavior=self.behavior, )
@awkward.mixin_class_method(awkward.sum) def _reduce_sum(self, mask_identity): return self.sum(axis=1) @awkward.mixin_class_method(awkward.count) def _reduce_count(self, mask_identity): return _count_reducer(self, mask_identity) @awkward.mixin_class_method(awkward.count_nonzero) def _reduce_count_nonzero(self, mask_identity): return _count_nonzero_reducer(self, mask_identity)
[docs] @awkward.mixin_class_method(np.multiply, {numbers.Number}) def multiply(self, other): """Multiply this vector by a scalar elementwise using ``x``, ``y``, ``z``, and ``t`` components""" return self.scale(other)
[docs] @awkward.mixin_class_method(np.divide, {numbers.Number}) def divide(self, other): """Divide this vector by a scalar elementwise using its cartesian components This is realized by using the multiplication functionality""" return self.scale(1 / other)
[docs] def delta_r2(self, other): """Squared `delta_r`""" return self.deltaR2(other)
[docs] def delta_r(self, other): r"""Distance between two Lorentz vectors in (eta,phi) plane :math:`\sqrt{\Delta\eta^2 + \Delta\phi^2}` """ return self.deltaR(other)
[docs] def delta_phi(self, other): """Compute difference in angle between two vectors Returns a value within [-pi, pi) """ return self.deltaphi(other)
[docs] @awkward.mixin_class_method(np.negative) def negative(self): """Returns the negative of the vector""" return self.scale(-1)
@property def pvec(self): """The ``x``, ``y`` and ``z`` components as a `ThreeVector`""" return awkward.zip( {"x": self.x, "y": self.y, "z": self.z}, with_name="ThreeVector", behavior=self.behavior, ) @property def boostvec(self): """The ``x``, ``y`` and ``z`` components divided by ``t`` as a `ThreeVector` This can be used for boosting. For cases where ``|t| <= r``, this returns the unit vector. """ return self.to_beta3()
[docs] def metric_table( self, other, axis=1, metric=lambda a, b: a.delta_r(b), return_combinations=False, ): """Return a list of a metric evaluated between this object and another. The two arrays should be broadcast-compatible on all axes other than the specified axis, which will be used to form a cartesian product. If axis=None, broadcast arrays directly. The return shape will be that of ``self`` with a new axis with shape of ``other`` appended at the specified axis depths. Parameters ---------- other : awkward.Array Another array with same shape in all but ``axis`` axis : int, optional The axis to form the cartesian product (default 1). If None, the metric is directly evaluated on the input arrays (i.e. they should broadcast) metric : callable A function of two arguments, returning a scalar. The default metric is `delta_r`. return_combinations : bool If True return the combinations of inputs as well as an unzipped tuple """ return _metric_table_core(self, other, axis, metric, return_combinations)
[docs] def nearest( self, other, axis=1, metric=lambda a, b: a.delta_r(b), return_metric=False, threshold=None, ): """Return nearest object to this one Finds item in ``other`` satisfying ``min(metric(self, other))``. The two arrays should be broadcast-compatible on all axes other than the specified axis, which will be used to form a cartesian product. If axis=None, broadcast arrays directly. The return shape will be that of ``self``. Parameters ---------- other : awkward.Array Another array with same shape in all but ``axis`` axis : int, optional The axis to form the cartesian product (default 1). If None, the metric is directly evaluated on the input arrays (i.e. they should broadcast) metric : callable A function of two arguments, returning a scalar. The default metric is `delta_r`. return_metric : bool, optional If true, return both the closest object and its metric (default false) threshold : Number, optional If set, any objects with ``metric > threshold`` will be masked from the result """ return _nearest_core(self, other, axis, metric, return_metric, threshold)
def __awkward_validation__(self): errors, has_cart, has_polar, has_longitudinal, has_temporal = ( _coordinate_validation(tuple(self.fields)) ) errors = list(errors) if not (has_cart or has_polar): errors.append( "missing azimuthal coordinates: need x/px and y/py, or rho/pt and phi" ) if not has_longitudinal: errors.append("missing longitudinal coordinate: need z/pz, theta, or eta") if not has_temporal: errors.append( "missing temporal coordinate: need t/E/e/energy or tau/M/m/mass" ) if errors: raise ValueError(f"{type(self).__name__}: " + "; ".join(errors))
[docs] @awkward.mixin_class(behavior) class PtEtaPhiMLorentzVector(LorentzVector): """A Lorentz vector using pseudorapidity and mass This mixin class requires the parent class to provide items ``pt``, ``eta``, ``phi``, and ``mass``. Some additional properties are overridden for performance """
[docs] @awkward.mixin_class_method(np.multiply, {numbers.Number}) def multiply(self, other): """Multiply this vector by a scalar elementwise using ``x``, ``y``, ``z``, and ``t`` components For a non-negative scalar this directly adjusts ``pt``, ``eta``, ``phi`` and ``mass`` for performance and returns a `PtEtaPhiMLorentzVector`. Any other multiplier (negative, or an array whose sign is not known ahead of time) returns a cartesian `LorentzVector`, since (pt, eta, phi, mass) cannot represent ``t < 0``. """ if isinstance(other, numbers.Real) and other >= 0: return awkward.zip( { "pt": self.pt * other, "eta": self.eta, "phi": self.phi, "mass": self.mass * other, }, with_name="PtEtaPhiMLorentzVector", behavior=self.behavior, ) return awkward.zip( { "x": self.x * other, "y": self.y * other, "z": self.z * other, "t": self.t * other, }, with_name="LorentzVector", behavior=self.behavior, )
[docs] @awkward.mixin_class_method(np.negative) def negative(self): """Returns the negative of the vector""" return self.multiply(-1)
[docs] @awkward.mixin_class_method(np.divide, {numbers.Number}) def divide(self, other): """Divide this vector by a scalar elementwise using its cartesian components This is realized by using the multiplication functionality""" return self.multiply(1 / other)
[docs] @awkward.mixin_class(behavior) class PtEtaPhiELorentzVector(LorentzVector): """A Lorentz vector using pseudorapidity and energy This mixin class requires the parent class to provide items ``pt``, ``eta``, ``phi``, and ``energy``. Some additional properties are overridden for performance """
[docs] @awkward.mixin_class_method(np.multiply, {numbers.Number}) def multiply(self, other): """Multiply this vector by a scalar elementwise using ``x``, ``y``, ``z``, and ``t`` components In reality, this directly adjusts ``pt``, ``eta``, ``phi`` and ``energy`` for performance """ return awkward.zip( { "pt": self.pt * abs(other), "eta": self.eta * np.sign(other), "phi": self.phi % (2 * np.pi) - (np.pi * (other < 0)), "energy": self.energy * other, }, with_name="PtEtaPhiELorentzVector", behavior=self.behavior, )
[docs] @awkward.mixin_class_method(np.negative) def negative(self): """Returns the negative of the vector""" return awkward.zip( { "pt": self.pt, "eta": -self.eta, "phi": self.phi % (2 * np.pi) - np.pi, "energy": -self.energy, }, with_name="PtEtaPhiELorentzVector", behavior=self.behavior, )
[docs] @awkward.mixin_class_method(np.divide, {numbers.Number}) def divide(self, other): """Divide this vector by a scalar elementwise using its cartesian components This is realized by using the multiplication functionality""" return self.multiply(1 / other)
_binary_dispatch_cls = { "TwoVector": TwoVector, "PolarTwoVector": TwoVector, "ThreeVector": ThreeVector, "SphericalThreeVector": ThreeVector, "LorentzVector": LorentzVector, "PtEtaPhiMLorentzVector": LorentzVector, "PtEtaPhiELorentzVector": LorentzVector, } _rank = [TwoVector, ThreeVector, LorentzVector] for lhs, lhs_to in _binary_dispatch_cls.items(): for rhs, rhs_to in _binary_dispatch_cls.items(): out_to = min(lhs_to, rhs_to, key=_rank.index) behavior[(np.add, lhs, rhs)] = out_to.add behavior[(np.subtract, lhs, rhs)] = out_to.subtract TwoVectorArray.ProjectionClass2D = TwoVectorArray # noqa: F821 TwoVectorArray.ProjectionClass3D = ThreeVectorArray # noqa: F821 TwoVectorArray.ProjectionClass4D = LorentzVectorArray # noqa: F821 TwoVectorArray.MomentumClass = PolarTwoVectorArray # noqa: F821 TwoVectorRecord.ProjectionClass2D = TwoVectorRecord # noqa: F821 TwoVectorRecord.ProjectionClass3D = ThreeVectorRecord # noqa: F821 TwoVectorRecord.ProjectionClass4D = LorentzVectorRecord # noqa: F821 TwoVectorRecord.MomentumClass = PolarTwoVectorRecord # noqa: F821 PolarTwoVectorArray.ProjectionClass2D = PolarTwoVectorArray # noqa: F821 PolarTwoVectorArray.ProjectionClass3D = SphericalThreeVectorArray # noqa: F821 PolarTwoVectorArray.ProjectionClass4D = LorentzVectorArray # noqa: F821 PolarTwoVectorArray.MomentumClass = PolarTwoVectorArray # noqa: F821 PolarTwoVectorRecord.ProjectionClass2D = PolarTwoVectorRecord # noqa: F821 PolarTwoVectorRecord.ProjectionClass3D = SphericalThreeVectorRecord # noqa: F821 PolarTwoVectorRecord.ProjectionClass4D = LorentzVectorRecord # noqa: F821 PolarTwoVectorRecord.MomentumClass = PolarTwoVectorRecord # noqa: F821 ThreeVectorArray.ProjectionClass2D = TwoVectorArray # noqa: F821 ThreeVectorArray.ProjectionClass3D = ThreeVectorArray # noqa: F821 ThreeVectorArray.ProjectionClass4D = LorentzVectorArray # noqa: F821 ThreeVectorArray.MomentumClass = SphericalThreeVectorArray # noqa: F821 ThreeVectorRecord.ProjectionClass2D = TwoVectorRecord # noqa: F821 ThreeVectorRecord.ProjectionClass3D = ThreeVectorRecord # noqa: F821 ThreeVectorRecord.ProjectionClass4D = LorentzVectorRecord # noqa: F821 ThreeVectorRecord.MomentumClass = SphericalThreeVectorRecord # noqa: F821 SphericalThreeVectorArray.ProjectionClass2D = PolarTwoVectorArray # noqa: F821 SphericalThreeVectorArray.ProjectionClass3D = SphericalThreeVectorArray # noqa: F821 SphericalThreeVectorArray.ProjectionClass4D = LorentzVectorArray # noqa: F821 SphericalThreeVectorArray.MomentumClass = SphericalThreeVectorArray # noqa: F821 SphericalThreeVectorRecord.ProjectionClass2D = PolarTwoVectorRecord # noqa: F821 SphericalThreeVectorRecord.ProjectionClass3D = SphericalThreeVectorRecord # noqa: F821 SphericalThreeVectorRecord.ProjectionClass4D = LorentzVectorRecord # noqa: F821 SphericalThreeVectorRecord.MomentumClass = SphericalThreeVectorRecord # noqa: F821 LorentzVectorArray.ProjectionClass2D = TwoVectorArray # noqa: F821 LorentzVectorArray.ProjectionClass3D = ThreeVectorArray # noqa: F821 LorentzVectorArray.ProjectionClass4D = LorentzVectorArray # noqa: F821 LorentzVectorArray.MomentumClass = LorentzVectorArray # noqa: F821 LorentzVectorRecord.ProjectionClass2D = TwoVectorRecord # noqa: F821 LorentzVectorRecord.ProjectionClass3D = ThreeVectorRecord # noqa: F821 LorentzVectorRecord.ProjectionClass4D = LorentzVectorRecord # noqa: F821 LorentzVectorRecord.MomentumClass = LorentzVectorRecord # noqa: F821 PtEtaPhiMLorentzVectorArray.ProjectionClass2D = TwoVectorArray # noqa: F821 PtEtaPhiMLorentzVectorArray.ProjectionClass3D = ThreeVectorArray # noqa: F821 PtEtaPhiMLorentzVectorArray.ProjectionClass4D = LorentzVectorArray # noqa: F821 PtEtaPhiMLorentzVectorArray.MomentumClass = LorentzVectorArray # noqa: F821 PtEtaPhiMLorentzVectorRecord.ProjectionClass2D = TwoVectorRecord # noqa: F821 PtEtaPhiMLorentzVectorRecord.ProjectionClass3D = ThreeVectorRecord # noqa: F821 PtEtaPhiMLorentzVectorRecord.ProjectionClass4D = LorentzVectorRecord # noqa: F821 PtEtaPhiMLorentzVectorRecord.MomentumClass = LorentzVectorRecord # noqa: F821 PtEtaPhiELorentzVectorArray.ProjectionClass2D = TwoVectorArray # noqa: F821 PtEtaPhiELorentzVectorArray.ProjectionClass3D = ThreeVectorArray # noqa: F821 PtEtaPhiELorentzVectorArray.ProjectionClass4D = LorentzVectorArray # noqa: F821 PtEtaPhiELorentzVectorArray.MomentumClass = LorentzVectorArray # noqa: F821 PtEtaPhiELorentzVectorRecord.ProjectionClass2D = TwoVectorRecord # noqa: F821 PtEtaPhiELorentzVectorRecord.ProjectionClass3D = ThreeVectorRecord # noqa: F821 PtEtaPhiELorentzVectorRecord.ProjectionClass4D = LorentzVectorRecord # noqa: F821 PtEtaPhiELorentzVectorRecord.MomentumClass = LorentzVectorRecord # noqa: F821 __all__ = [ "LorentzVector", "PolarTwoVector", "PtEtaPhiELorentzVector", "PtEtaPhiMLorentzVector", "SphericalThreeVector", "ThreeVector", "TwoVector", ]