Repository: scipy/scipy

Issue:
BUG: n-d interpolation parameter provided to geometric_slerp
This was first reported by @BvB93 during addition of type hints to the related code.

In short, [`geometric_slerp()`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.geometric_slerp.html) was intended to be a bit like `np.linspace` on the surface of an n-dimensional circle/sphere, interpolating between start and end points on that surface. However, it somehow slipped through that we allow arbitrary dimensions for the interpolation points/array, and this leads to weird/inconsistent/incorrect results.

Examples are below, where the "intended" 1-dimensional case produces the correct output--two points with 1 in the middle of the unit circle between the start/end. Notice the confusing discrepancy between the degenerate path output shapes vs. non-degenerate. While the non-degenerate outputs respect the input shape for `ndim > 1`, their numerical results are incorrect--the `x` coordinate isn't reflecting the movement along the unit circle to the halfway point between `[0, 1]` and `[1, 0]`.

What I'd like to do is raise an exception if `np.asarray(t).ndim > 1` and just move on, but as Bas points out we do have *de facto* support (no error raised) for arbitrary dimensions whether I like it or not, and I don't want my own view on it to exempt it from backwards compatibility concerns. That said, I wonder if we can basically say that this is a clear bug so it trumps backward compatibility?

```python
import numpy as np
import scipy
from scipy.spatial import geometric_slerp

print("scipy.__version__:", scipy.__version__)

arr1 = np.array([0, 1])
arr2 = np.array([1, 0])

for t in [[0, 0.5],
          [[0, 0.5]],
          [[[[[[[[[0, 0.5]]]]]]]]]]:
    print("t dims:", np.asarray(t).ndim)
    path = geometric_slerp(start=arr1,
                           end=arr2,
                           t=t)
    print("path:\n", path)
    degenerate_path = geometric_slerp(start=arr1,
                                      end=arr1,
                                      t=t)
    print("degenerate_path:\n", degenerate_path)
```

```
scipy.__version__: 1.8.0.dev0+1472.130a1e6
t dims: 1
path:
 [[0.         1.        ]
 [0.70710678 0.70710678]]
degenerate_path:
 [[0. 1.]
 [0. 1.]]
t dims: 2
path:
 [[[0.         0.70710678]]]
degenerate_path:
 [[0. 1.]
 [0. 1.]]
t dims: 9
path:
 [[[[[[[[[[0.         0.70710678]]]]]]]]]]
degenerate_path:
 [[0. 1.]
 [0. 1.]]

```


Discussion:

Resolving patch:
diff --git a/scipy/spatial/_geometric_slerp.py b/scipy/spatial/_geometric_slerp.py
--- a/scipy/spatial/_geometric_slerp.py
+++ b/scipy/spatial/_geometric_slerp.py
@@ -52,8 +52,8 @@ def geometric_slerp(
     end : (n_dimensions, ) array-like
         Single n-dimensional input coordinate in a 1-D array-like
         object. `n` must be greater than 1.
-    t: float or (n_points,) array-like
-        A float or array-like of doubles representing interpolation
+    t: float or (n_points,) 1D array-like
+        A float or 1D array-like of doubles representing interpolation
         parameters, with values required in the inclusive interval
         between 0 and 1. A common approach is to generate the array
         with ``np.linspace(0, 1, n_pts)`` for linearly spaced points.
@@ -176,6 +176,11 @@ def geometric_slerp(
 
     start = np.asarray(start, dtype=np.float64)
     end = np.asarray(end, dtype=np.float64)
+    t = np.asarray(t)
+
+    if t.ndim > 1:
+        raise ValueError("The interpolation parameter "
+                         "value must be one dimensional.")
 
     if start.ndim != 1 or end.ndim != 1:
         raise ValueError("Start and end coordinates "
@@ -191,7 +196,7 @@ def geometric_slerp(
                          "space")
 
     if np.array_equal(start, end):
-        return np.linspace(start, start, np.asarray(t).size)
+        return np.linspace(start, start, t.size)
 
     # for points that violate equation for n-sphere
     for coord in [start, end]:


Test patch:
diff --git a/scipy/spatial/tests/test_slerp.py b/scipy/spatial/tests/test_slerp.py
--- a/scipy/spatial/tests/test_slerp.py
+++ b/scipy/spatial/tests/test_slerp.py
@@ -363,15 +363,21 @@ def test_scalar_t(self):
         np.linspace(0, 1, 5),
     ])
     def test_degenerate_input(self, start, t):
-        shape = (t.size,) + start.shape
-        expected = np.full(shape, start)
+        if np.asarray(t).ndim > 1:
+            with pytest.raises(ValueError):
+                geometric_slerp(start=start, end=start, t=t)
+        else:
 
-        actual = geometric_slerp(start=start, end=start, t=t)
-        assert_allclose(actual, expected)
+            shape = (t.size,) + start.shape
+            expected = np.full(shape, start)
+
+            actual = geometric_slerp(start=start, end=start, t=t)
+            assert_allclose(actual, expected)
 
-        # Check that degenerate and non-degenerate inputs yield the same size
-        non_degenerate = geometric_slerp(start=start, end=start[::-1], t=t)
-        assert actual.size == non_degenerate.size
+            # Check that degenerate and non-degenerate
+            # inputs yield the same size
+            non_degenerate = geometric_slerp(start=start, end=start[::-1], t=t)
+            assert actual.size == non_degenerate.size
 
     @pytest.mark.parametrize('k', np.logspace(-10, -1, 10))
     def test_numerical_stability_pi(self, k):
@@ -391,3 +397,22 @@ def test_numerical_stability_pi(self, k):
             norms = np.linalg.norm(result, axis=1)
             error = np.max(np.abs(norms - 1))
             assert error < 4e-15
+
+    @pytest.mark.parametrize('t', [
+     [[0, 0.5]],
+     [[[[[[[[[0, 0.5]]]]]]]]],
+    ])
+    def test_interpolation_param_ndim(self, t):
+        # regression test for gh-14465
+        arr1 = np.array([0, 1])
+        arr2 = np.array([1, 0])
+
+        with pytest.raises(ValueError):
+            geometric_slerp(start=arr1,
+                            end=arr2,
+                            t=t)
+
+        with pytest.raises(ValueError):
+            geometric_slerp(start=arr1,
+                            end=arr1,
+                            t=t)