Repository: scikit-hep/vector (language: python)

Issue:
`q**2` is always positive for vector.MomentumObject4D
### Vector Version

1.3.1

### Python Version

3.11.4

### OS / Environment

Kubuntu 22.04
vector is installed via pip inside a conda environment

### Describe the bug

When calculating `q**2` of a 4-vector that is created from `vector.obj`, the value is always positive, even when it should not be:
```python
>>> vector.obj(E=1, px=1, py=1, pz=1)**2
2.0000000000000004
```
For numpy vector arrays, the behavior is correct and as expected:
```python
>>> vector.array({"E": [1], "px": [1], "py": [1], "pz": [1]})**2
array([-2.])
```
I have not tested other backends (e.g. awkward).

### Any additional but relevant log output

_No response_


Discussion / hints:
If the backends differ, that's definitely a bug (and hard to see how it happened, since the backends use the same compute functions).

The rules for dealing with spacelike and negative-timelike 4-vectors were taken from ROOT: they're supposed to do the same thing as `TLorentzVector` and `Math::LorentzVector`. Checking this example with ROOT:

```c++
root [0] TLorentzVector v(1, 1, 1, 1);
root [1] v*v
(double) -2.0000000
```

Yes, it should be `-2`. The NumPy and Awkward backends get it right, but the object backend (oddly!) gets it wrong:

```python
>>> vector.array({"E": [1], "px": [1], "py": [1], "pz": [1]})**2
array([-2.])

>>> ak.zip({"E": [1], "px": [1], "py": [1], "pz": [1]}, with_name="Momentum4D")**2
<Array [-2] type='1 * int64'>

>>> vector.obj(E=1, px=1, py=1, pz=1)**2
2.0000000000000004
```

Fix patch:
diff --git a/src/vector/backends/object.py b/src/vector/backends/object.py
index ddc44cad..18a202b5 100644
--- a/src/vector/backends/object.py
+++ b/src/vector/backends/object.py
@@ -387,7 +387,7 @@ def __itruediv__(self: SameVectorType, other: float) -> VectorProtocol:
         return _replace_data(self, numpy.true_divide(self, other))  # type: ignore[call-overload]
 
     def __pow__(self, other: float) -> float:
-        return numpy.power(self, other)  # type: ignore[call-overload]
+        return numpy.square(self) if other == 2 else numpy.power(self, other)  # type: ignore[call-overload]
 
     def __matmul__(self, other: VectorProtocol) -> float:
         return numpy.matmul(self, other)  # type: ignore[call-overload]


Test patch:
diff --git a/tests/compute/test_isclose.py b/tests/compute/test_isclose.py
index 62c94495..a101046e 100644
--- a/tests/compute/test_isclose.py
+++ b/tests/compute/test_isclose.py
@@ -62,8 +62,6 @@ def test_spatial_object():
 
     for t1 in "xyz", "xytheta", "xyeta", "rhophiz", "rhophitheta", "rhophieta":
         for t2 in "xyz", "xytheta", "xyeta", "rhophiz", "rhophitheta", "rhophieta":
-            print(t1, t2)
-
             transformed1, transformed2 = (
                 getattr(v1, "to_" + t1)(),
                 getattr(v2, "to_" + t2)(),
diff --git a/tests/test_issues.py b/tests/test_issues.py
index d0be1ce7..c1d509e8 100644
--- a/tests/test_issues.py
+++ b/tests/test_issues.py
@@ -8,6 +8,7 @@
 import os
 import pickle
 
+import numpy as np
 import pytest
 
 import vector
@@ -47,3 +48,16 @@ def repro(generator_like_jet_constituents):
     with open(file_path, "rb") as f:
         a = ak.from_buffers(*pickle.load(f))
     repro(generator_like_jet_constituents=a.constituents)
+
+
+def test_issue_443():
+    ak = pytest.importorskip("awkward")
+    vector.register_awkward()
+
+    assert vector.array({"E": [1], "px": [1], "py": [1], "pz": [1]}) ** 2 == np.array(
+        [-2.0]
+    )
+    assert ak.zip(
+        {"E": [1], "px": [1], "py": [1], "pz": [1]}, with_name="Momentum4D"
+    ) ** 2 == ak.Array([-2])
+    assert vector.obj(E=1, px=1, py=1, pz=1) ** 2 == -2