Repository: scipy/scipy

Issue:
BUG: interpolate.interpn fails with read only input
### Describe your issue.

Using a slightly modified version of the example from the documentation
```python
import numpy
from scipy.interpolate import interpn


def value_func_3d(x, y, z):
    return 2 * x + 3 * y - z


x = numpy.linspace(0, 4, 5)
y = numpy.linspace(0, 5, 6)
z = numpy.linspace(0, 6, 7)
points = (x, y, z)
values = value_func_3d(*numpy.meshgrid(*points, indexing='ij'))
point = numpy.array([2.21, 3.12, 1.15])
print(interpn(points, values, point))
for ax in points:
    ax.flags.writeable = False
print(interpn(points, values, point))
```

Fails with
```bash
...
    return find_indices(self.grid, xi)
  File "_rgi_cython.pyx", line 95, in scipy.interpolate._rgi_cython.find_indices
  File "stringsource", line 660, in View.MemoryView.memoryview_cwrapper
  File "stringsource", line 350, in View.MemoryView.memoryview.__cinit__
ValueError: buffer source array is read-only
```

A more minimal example is:
```python
import numpy
from scipy.interpolate import _rgi


grid = (numpy.array([0, 1], dtype='f8'), )
xi = numpy.array([[0.5, 0.5], ])
grid[0].flags.writeable = False
print(_rgi.find_indices(grid, xi))
```

This may be related to https://github.com/scipy/scipy/issues/17172 and may have been introduced with the cython code in #17291

Adding a const to this line appear to fix the problem locally: https://github.com/scipy/scipy/blob/7b858b70c16c5559c2155a6bf2c39b32e45fbb78/scipy/interpolate/_rgi_cython.pyx#L79

### Reproducing Code Example

```python
import numpy
from scipy.interpolate import _rgi


grid = (numpy.array([0, 1], dtype='f8'), )
xi = numpy.array([[0.5, 0.5], ])
grid[0].flags.writeable = False
print(_rgi.find_indices(grid, xi))
```


### Error message

```shell
File "_rgi_cython.pyx", line 95, in scipy.interpolate._rgi_cython.find_indices
  File "stringsource", line 660, in View.MemoryView.memoryview_cwrapper
  File "stringsource", line 350, in View.MemoryView.memoryview.__cinit__
ValueError: buffer source array is read-only
```


### SciPy/NumPy/Python version information

1.11.0.dev0+0.057cf5c 1.24.1 sys.version_info(major=3, minor=10, micro=6, releaselevel='final', serial=0)


Discussion:

Resolving patch:
diff --git a/scipy/interpolate/_rgi.py b/scipy/interpolate/_rgi.py
--- a/scipy/interpolate/_rgi.py
+++ b/scipy/interpolate/_rgi.py
@@ -23,11 +23,12 @@ def _check_points(points):
                 # input is descending, so make it ascending
                 descending_dimensions.append(i)
                 p = np.flip(p)
-                p = np.ascontiguousarray(p)
             else:
                 raise ValueError(
                     "The points in dimension %d must be strictly "
                     "ascending or descending" % i)
+        # see https://github.com/scipy/scipy/issues/17716
+        p = np.ascontiguousarray(p)
         grid.append(p)
     return tuple(grid), tuple(descending_dimensions)
 
@@ -330,7 +331,10 @@ def __call__(self, xi, method=None):
         if method == "linear":
             indices, norm_distances = self._find_indices(xi.T)
             if (ndim == 2 and hasattr(self.values, 'dtype') and
-                    self.values.ndim == 2):
+                    self.values.ndim == 2 and self.values.flags.writeable and
+                    self.values.dtype.byteorder == '='):
+                # until cython supports const fused types, the fast path
+                # cannot support non-writeable values
                 # a fast path
                 out = np.empty(indices.shape[1], dtype=self.values.dtype)
                 result = evaluate_linear_2d(self.values,


Test patch:
diff --git a/scipy/interpolate/tests/test_rgi.py b/scipy/interpolate/tests/test_rgi.py
--- a/scipy/interpolate/tests/test_rgi.py
+++ b/scipy/interpolate/tests/test_rgi.py
@@ -939,3 +939,58 @@ def test_invalid_xi_dimensions(self):
                "RegularGridInterpolator has dimension 1")
         with assert_raises(ValueError, match=msg):
             interpn(points, values, xi)
+
+    def test_readonly_grid(self):
+        # https://github.com/scipy/scipy/issues/17716
+        x = np.linspace(0, 4, 5)
+        y = np.linspace(0, 5, 6)
+        z = np.linspace(0, 6, 7)
+        points = (x, y, z)
+        values = np.ones((5, 6, 7))
+        point = np.array([2.21, 3.12, 1.15])
+        for d in points:
+            d.flags.writeable = False
+        values.flags.writeable = False
+        point.flags.writeable = False
+        interpn(points, values, point)
+        RegularGridInterpolator(points, values)(point)
+
+    def test_2d_readonly_grid(self):
+        # https://github.com/scipy/scipy/issues/17716
+        # test special 2d case
+        x = np.linspace(0, 4, 5)
+        y = np.linspace(0, 5, 6)
+        points = (x, y)
+        values = np.ones((5, 6))
+        point = np.array([2.21, 3.12])
+        for d in points:
+            d.flags.writeable = False
+        values.flags.writeable = False
+        point.flags.writeable = False
+        interpn(points, values, point)
+        RegularGridInterpolator(points, values)(point)
+
+    def test_non_c_contiguous_grid(self):
+        # https://github.com/scipy/scipy/issues/17716
+        x = np.linspace(0, 4, 5)
+        x = np.vstack((x, np.empty_like(x))).T.copy()[:, 0]
+        assert not x.flags.c_contiguous
+        y = np.linspace(0, 5, 6)
+        z = np.linspace(0, 6, 7)
+        points = (x, y, z)
+        values = np.ones((5, 6, 7))
+        point = np.array([2.21, 3.12, 1.15])
+        interpn(points, values, point)
+        RegularGridInterpolator(points, values)(point)
+
+    @pytest.mark.parametrize("dtype", ['>f8', '<f8'])
+    def test_endianness(self, dtype):
+        # https://github.com/scipy/scipy/issues/17716
+        # test special 2d case
+        x = np.linspace(0, 4, 5, dtype=dtype)
+        y = np.linspace(0, 5, 6, dtype=dtype)
+        points = (x, y)
+        values = np.ones((5, 6), dtype=dtype)
+        point = np.array([2.21, 3.12], dtype=dtype)
+        interpn(points, values, point)
+        RegularGridInterpolator(points, values)(point)