vector icon indicating copy to clipboard operation
vector copied to clipboard

`VectorNumpy3D`'s `azimuthal` and `longitudinal` properties throw an error (similar for `VectorNumpy4D`)

Open Saransh-cpp opened this issue 2 years ago • 0 comments

Reproducible example

import vector

vec = vector.array(
    [
        (1.1, 2.1, 3.1),
        (1.2, 2.2, 3.2),
        (1.3, 2.3, 3.3),
        (1.4, 2.4, 4.4),
        (1.5, 2.5, 5.5)
    ], dtype=[("x", float), ("y", float), ("z", float)]
)
print(vec.azimuthal)

Similarly for -

  • vec.longitudinal
  • vec.azimuthal for a 4D NumPy vector (VectorNumpy4D)
  • vec.longitudinal for a 4D NumPy vector (VectorNumpy4D)
  • vec.temporal for a 4D NumPy vector (VectorNumpy4D)

Error

>>> vec.azimuthal
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\numpy\core\arrayprint.py", line 1488, in _array_repr_implementation
    lst = array2string(arr, max_line_width, precision, suppress_small,
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\numpy\core\arrayprint.py", line 736, in array2string
    return _array2string(a, options, separator, prefix)
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\numpy\core\arrayprint.py", line 513, in wrapper
    return f(self, *args, **kwargs)
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\numpy\core\arrayprint.py", line 546, in _array2string
    lst = _formatArray(a, format_function, options['linewidth'],
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\numpy\core\arrayprint.py", line 889, in _formatArray
    return recurser(index=(),
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\numpy\core\arrayprint.py", line 853, in recurser
    word = recurser(index + (-1,), next_hanging_indent, next_width)
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\numpy\core\arrayprint.py", line 799, in recurser
    return format_function(a[index])
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\vector\_backends\numpy_.py", line 218, in __getitem__
    return _getitem(self, where, self.__class__._IS_MOMENTUM)  # type: ignore[arg-type]
  File "D:\extras\IRIS-HEP\.env\lib\site-packages\vector\_backends\numpy_.py", line 143, in _getitem
    return array.ObjectClass(*out.view(numpy.ndarray))  # type: ignore[misc, return-value]
TypeError: <lambda>() takes 3 positional arguments but 4 were given

Cause

Going through the stack trace I looked into the function _getitem, which was throwing an error related to the number of arguments. After adding some debug statements, I discovered that the if-else statements written to initialize azimuthal, longitudinal, and temporal are never executed when running the example.

This is due to the fact that the type of the passed array argument is AzimuthalNumpyXY (in the case of calling vec.azimuthal on a structured array having "x" and "y" as the datatypes). As array belongs to AzimuthalNumpyXY, it never has an attribute named _azimuthal_type, rather it has attributes named x and y.

The main intention here was to pass in a VectorNumpy2D, VectorNumpy3D, or VectorNumpy4D object but I can't really figure out why the __getitem method in the class GetItem is picking up AzimuthalNumpyXY.

I think the case of calling azimuthal on a VectorNumpy2D object works coincidentally, through the last else condition.

_getitem

def _getitem(
    array: typing.Union["VectorNumpy2D", "VectorNumpy3D", "VectorNumpy4D"],
    where: typing.Any,
    is_momentum: bool,
) -> typing.Union[float, FloatArray]:
    if isinstance(where, str):
        if is_momentum:
            where = _repr_momentum_to_generic.get(where, where)
        return array.view(numpy.ndarray)[where]
    else:
        out = numpy.ndarray.__getitem__(array, where)
        if not isinstance(out, numpy.void):
            return out

        azimuthal, longitudinal, temporal = None, None, None
        if hasattr(array, "_azimuthal_type"):
            azimuthal = array._azimuthal_type.ObjectClass(
                *(out[x] for x in _coordinate_class_to_names[_aztype(array)])
            )
        if hasattr(array, "_longitudinal_type"):
            longitudinal = array._longitudinal_type.ObjectClass(  # type: ignore[union-attr]
                *(out[x] for x in _coordinate_class_to_names[_ltype(array)])  # type: ignore[arg-type]
            )
        if hasattr(array, "_temporal_type"):
            temporal = array._temporal_type.ObjectClass(  # type: ignore[union-attr]
                *(out[x] for x in _coordinate_class_to_names[_ttype(array)])  # type: ignore[arg-type]
            )
        if temporal is not None:
            return array.ObjectClass(azimuthal, longitudinal, temporal)  # type: ignore[call-arg, arg-type, return-value]
        elif longitudinal is not None:
            return array.ObjectClass(azimuthal, longitudinal)  # type: ignore[call-arg, arg-type, return-value]
        elif azimuthal is not None:
            return array.ObjectClass(azimuthal)  # type: ignore[call-arg, return-value]
        else:
            return array.ObjectClass(*out.view(numpy.ndarray))  # type: ignore[misc, return-value]

A possible fix

I tried fixing this by modifying the function but ended up breaking some tests -

Modified function

def _getitem(
    array: typing.Union["VectorNumpy2D", "VectorNumpy3D", "VectorNumpy4D"],
    where: typing.Any,
    is_momentum: bool,
) -> typing.Union[float, FloatArray]:
    if isinstance(where, str):
        if is_momentum:
            where = _repr_momentum_to_generic.get(where, where)
        return array.view(numpy.ndarray)[where]
    else:
        out = numpy.ndarray.__getitem__(array, where)
        if not isinstance(out, numpy.void):
            return out
        azimuthal, longitudinal, temporal = None, None, None

        if (hasattr(array, "x") and hasattr(array, "y")):
            azimuthal = vector._backends.object_.AzimuthalObjectXY(*(out[x] for x in _coordinate_class_to_names[AzimuthalXY]))
        elif (hasattr(array, "rho") and hasattr(array, "phi")):
            azimuthal = vector._backends.object_.AzimuthalObjectRhoPhi(*(out[x] for x in _coordinate_class_to_names[AzimuthalRhoPhi]))
        
        if hasattr(array, "z"):
            longitudinal = vector._backends.object_.LongitudinalObjectZ(*(out[x] for x in _coordinate_class_to_names[LongitudinalZ]))  # type: ignore[union-attr]
        elif hasattr(array, "eta"):
            longitudinal = vector._backends.object_.LongitudinalObjectEta(*(out[x] for x in _coordinate_class_to_names[LongitudinalEta]))  # type: ignore[union-attr]
        elif hasattr(array, "theta"):
            longitudinal = vector._backends.object_.LongitudinalObjectTheta(*(out[x] for x in _coordinate_class_to_names[LongitudinalTheta]))  # type: ignore[union-attr]

        if hasattr(array, "t"):
            temporal = vector._backends.object_.TemporalObjectT(*(out[x] for x in _coordinate_class_to_names[TemporalT]))  # type: ignore[union-attr]
        elif hasattr(array, "tau"):
            temporal = vector._backends.object_.TemporalObjectTau(*(out[x] for x in _coordinate_class_to_names[TemporalTau]))  # type: ignore[union-attr]

        if temporal is not None:
            return temporal  # type: ignore[call-arg, arg-type, return-value]
        elif longitudinal is not None:
            return longitudinal  # type: ignore[call-arg, arg-type, return-value]
        elif azimuthal is not None:
            return azimuthal  # type: ignore[call-arg, return-value]
        else:
            return array.ObjectClass(*out.view(numpy.ndarray))  # type: ignore[misc, return-value]

This works somewhat fine. An example -

import vector

vec = vector.array(
    [
        (1.1, 2.1, 3.1),
        (1.2, 2.2, 3.2),
        (1.3, 2.3, 3.3),
        (1.4, 2.4, 4.4),
        (1.5, 2.5, 5.5)
    ], dtype=[("x", float), ("y", float), ("z", float)]
)
print(vec.azimuthal)

# output
# AzimuthalNumpyXY([(1.1, 2.1), (1.2, 2.2), (1.3, 2.3), (1.4, 2.4),
#                 (1.5, 2.5)],
#                 dtype=[('x', '<f8'), ('y', '<f8'), ('z', '<f8')])

print(vec.longitudinal)
# output
# LongitudinalNumpyZ([(3.1,), (3.2,), (3.3,), (4.4,), (5.5,)],
#                    dtype=[('x', '<f8'), ('y', '<f8'), ('z', '<f8')])

Broken tests

These are most probably failing as I did a lot of hard coding inside _getitem -

=============================================================================================== FAILURES =============================================================================================== 
__________________________________________________________________________________________ test_spatial_numpy __________________________________________________________________________________________ 

    def test_spatial_numpy():
        v1 = vector._backends.numpy_.VectorNumpy3D(
            [(0.1, 0.2, 0.3)],
            dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
        )
        v2 = vector._backends.numpy_.VectorNumpy3D(
            [(0.4, 0.5, 0.6)],
            dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
        )
        out = v1.cross(v2)
        assert isinstance(out, vector._backends.numpy_.VectorNumpy3D)
        assert out.dtype.names == ("x", "y", "z")
>       assert (out[0].x, out[0].y, out[0].z) == pytest.approx((-0.03, 0.06, -0.03))
E       AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'

tests\compute\spatial\test_cross.py:55: AttributeError
__________________________________________________________________________________________ test_lorentz_numpy __________________________________________________________________________________________

    def test_lorentz_numpy():
        v1 = vector._backends.numpy_.VectorNumpy4D(
            [(0.1, 0.2, 0.3, 99)],
            dtype=[
                ("x", numpy.float64),
                ("y", numpy.float64),
                ("z", numpy.float64),
                ("t", numpy.float64),
            ],
        )
        v2 = vector._backends.numpy_.VectorNumpy4D(
            [(0.4, 0.5, 0.6, 99)],
            dtype=[
                ("x", numpy.float64),
                ("y", numpy.float64),
                ("z", numpy.float64),
                ("t", numpy.float64),
            ],
        )
        out = v1.cross(v2)
        assert isinstance(out, vector._backends.numpy_.VectorNumpy3D)
        assert out.dtype.names == ("x", "y", "z")
        assert out.tolist() == pytest.approx([(-0.03, 0.06, -0.030000000000000013)])

        for t1 in (
            "xyzt",
            "xythetat",
            "xyetat",
            "rhophizt",
            "rhophithetat",
            "rhophietat",
            "xyztau",
            "xythetatau",
            "xyetatau",
            "rhophiztau",
            "rhophithetatau",
            "rhophietatau",
        ):
            for t2 in (
                "xyzt",
                "xythetat",
                "xyetat",
                "rhophizt",
                "rhophithetat",
                "rhophietat",
                "xyztau",
                "xythetatau",
                "xyetatau",
                "rhophiztau",
                "rhophithetatau",
                "rhophietatau",
            ):
                transformed1, transformed2 = (
                    getattr(v1, "to_" + t1)(),
                    getattr(v2, "to_" + t2)(),
                )
                out = transformed1.cross(transformed2)
                assert isinstance(out, vector._backends.numpy_.VectorNumpy3D)
                assert out.dtype.names == ("x", "y", "z")
>               assert (out[0].x, out[0].y, out[0].z) == pytest.approx((-0.03, 0.06, -0.03))
E               AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'

tests\compute\spatial\test_cross.py:186: AttributeError
__________________________________________________________________________________________ test_spatial_numpy __________________________________________________________________________________________ 

    def test_spatial_numpy():
        vec = vector._backends.numpy_.VectorNumpy3D(
            [(0.1, 0.2, 0.3)],
            dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
        )
        out = vec.rotateX(0.25)
        assert isinstance(out.azimuthal, vector._methods.AzimuthalXY)
        assert isinstance(out.longitudinal, vector._methods.LongitudinalZ)
>       assert out[0].x == pytest.approx(0.1)
E       AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'

tests\compute\spatial\test_rotateX.py:43: AttributeError
__________________________________________________________________________________________ test_lorentz_numpy __________________________________________________________________________________________

    def test_lorentz_numpy():
        vec = vector._backends.numpy_.VectorNumpy4D(
            [(0.1, 0.2, 0.3, 99)],
            dtype=[
                ("x", numpy.float64),
                ("y", numpy.float64),
                ("z", numpy.float64),
                ("t", numpy.float64),
            ],
        )
        out = vec.rotateX(0.25)
        assert isinstance(out.azimuthal, vector._methods.AzimuthalXY)
        assert isinstance(out.longitudinal, vector._methods.LongitudinalZ)
>       assert out[0].x == pytest.approx(0.1)
E       AttributeError: 'TemporalObjectT' object has no attribute 'x'

tests\compute\spatial\test_rotateX.py:106: AttributeError
__________________________________________________________________________________________ test_spatial_numpy __________________________________________________________________________________________ 

    def test_spatial_numpy():
        vec = vector._backends.numpy_.VectorNumpy3D(
            [(0.1, 0.2, 0.3)],
            dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
        )
        out = vec.rotateY(0.25)
        assert isinstance(out.azimuthal, vector._methods.AzimuthalXY)
        assert isinstance(out.longitudinal, vector._methods.LongitudinalZ)
>       assert out[0].x == pytest.approx(0.17111242994742137)
E       AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'

tests\compute\spatial\test_rotateY.py:43: AttributeError
__________________________________________________________________________________________ test_lorentz_numpy __________________________________________________________________________________________

    def test_lorentz_numpy():
        vec = vector._backends.numpy_.VectorNumpy4D(
            [(0.1, 0.2, 0.3, 99)],
            dtype=[
                ("x", numpy.float64),
                ("y", numpy.float64),
                ("z", numpy.float64),
                ("t", numpy.float64),
            ],
        )
        out = vec.rotateY(0.25)
        assert isinstance(out.azimuthal, vector._methods.AzimuthalXY)
        assert isinstance(out.longitudinal, vector._methods.LongitudinalZ)
>       assert out[0].x == pytest.approx(0.17111242994742137)
E       AttributeError: 'TemporalObjectT' object has no attribute 'x'

tests\compute\spatial\test_rotateY.py:106: AttributeError
__________________________________________________________________________________________ test_spatial_numpy __________________________________________________________________________________________ 

    def test_spatial_numpy():
        axis = vector._backends.numpy_.VectorNumpy3D(
            [(0.1, 0.2, 0.3)],
            dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
        )
        vec = vector._backends.numpy_.VectorNumpy3D(
            [(0.4, 0.5, 0.6)],
            dtype=[("x", numpy.float64), ("y", numpy.float64), ("z", numpy.float64)],
        )
        out = vec.rotate_axis(axis, 0.25)
        assert isinstance(out, vector._backends.numpy_.VectorNumpy3D)
        assert out.dtype.names == ("x", "y", "z")
>       assert out[0].x == pytest.approx(0.37483425404335763)
E       AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'

tests\compute\spatial\test_rotate_axis.py:59: AttributeError
__________________________________________________________________________________________ test_lorentz_numpy __________________________________________________________________________________________ 

    def test_lorentz_numpy():
        axis = vector._backends.numpy_.VectorNumpy4D(
            [(0.1, 0.2, 0.3, 99)],
            dtype=[
                ("x", numpy.float64),
                ("y", numpy.float64),
                ("z", numpy.float64),
                ("t", numpy.float64),
            ],
        )
        vec = vector._backends.numpy_.VectorNumpy4D(
            [(0.4, 0.5, 0.6, 99)],
            dtype=[
                ("x", numpy.float64),
                ("y", numpy.float64),
                ("z", numpy.float64),
                ("t", numpy.float64),
            ],
        )
        out = vec.rotate_axis(axis, 0.25)
        assert isinstance(out, vector._backends.numpy_.VectorNumpy4D)
        assert out.dtype.names == ("x", "y", "z", "t")
>       assert out[0].x == pytest.approx(0.37483425404335763)
E       AttributeError: 'TemporalObjectT' object has no attribute 'x'

tests\compute\spatial\test_rotate_axis.py:163: AttributeError
=========================================================================================== warnings summary =========================================================================================== 
c:\users\saransh\saransh_softwares\python_3.9\lib\site-packages\pyreadline\py3k_compat.py:8
  c:\users\saransh\saransh_softwares\python_3.9\lib\site-packages\pyreadline\py3k_compat.py:8: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
    return isinstance(x, collections.Callable)

-- Docs: https://docs.pytest.org/en/stable/warnings.html
======================================================================================= short test summary info ======================================================================================== 
SKIPPED [2404] tests\test_compute_features.py:99: Unsupported Python version 3.9 (canonic 3.9.0beta5)
FAILED tests/compute/spatial/test_cross.py::test_spatial_numpy - AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'
FAILED tests/compute/spatial/test_cross.py::test_lorentz_numpy - AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'
FAILED tests/compute/spatial/test_rotateX.py::test_spatial_numpy - AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'
FAILED tests/compute/spatial/test_rotateX.py::test_lorentz_numpy - AttributeError: 'TemporalObjectT' object has no attribute 'x'
FAILED tests/compute/spatial/test_rotateY.py::test_spatial_numpy - AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'
FAILED tests/compute/spatial/test_rotateY.py::test_lorentz_numpy - AttributeError: 'TemporalObjectT' object has no attribute 'x'
FAILED tests/compute/spatial/test_rotate_axis.py::test_spatial_numpy - AttributeError: 'LongitudinalObjectZ' object has no attribute 'x'
FAILED tests/compute/spatial/test_rotate_axis.py::test_lorentz_numpy - AttributeError: 'TemporalObjectT' object has no attribute 'x'

Saransh-cpp avatar May 25 '22 18:05 Saransh-cpp