Repository: cupy/cupy

Issue:
Issue in cupyx/scipy/ndimage/_spline_prefilter_core.py with min() operation in large arrays for cupyx.scipy.ndimage.map_coordinates
This should be one easy to fix...

I stumbled with this when using `cupyx.scipy.ndimage.map_coordinates`. It worked great for small arrays but for larger ones, it shows a compilation error of the kernel.

In file `_spline_prefilter_core.py`  (and probably others), there are a few `min` operations to find the minimum between `min(n - 1,  {n_boundary}`. The problem is that the `min` operation doesn't have a template to compare between `long long` and `int` . The data type of  `n`  is defined based on the size of the array, and it makes `long long` for large arrays. The issue is that `boundary` is replaced by a low-value int constant (such as 32). Then the compilation of the `min` function fails. A solution is just to add the same datatype cast used for `n`, which is `(idx_t )`. For example, changing in  `_spline_prefilter_core.py`  all `min(..)` operations such as 
```python
def _causal_init_code(mode):
    ...
        c[0] = c[0] + z_n_1 * c[(n - 1) * element_stride];
        for (i = 1; i < min(n - 1,  {n_boundary}); ++i) {{
            c[0] += z_i * (c[i * element_stride] +
                           z_n_1 * c[(n - 1 - i) * element_stride]);
            z_i *= z;
        }}
        c[0] /= 1 - z_n_1 * z_n_1;'''
....
```
to 

```python
def _causal_init_code(mode):
...
        c[0] = c[0] + z_n_1 * c[(n - 1) * element_stride];
        for (i = 1; i < min(n - 1, (idx_t ) {n_boundary}); ++i) {{
            c[0] += z_i * (c[i * element_stride] +
                           z_n_1 * c[(n - 1 - i) * element_stride]);
            z_i *= z;
        }}
        c[0] /= 1 - z_n_1 * z_n_1;'''
....
```
because `n_boundary` will always be positive and small, then the cast should be safe. After changing it, then `cupyx.scipy.ndimage.map_coordinates` works for both small and large arrays.

Python Version               : 3.6.9
CuPy Version                 : 9.2.0
CuPy Platform                : NVIDIA CUDA
NumPy Version                : 1.17.4
SciPy Version                : 1.5.4
Cython Build Version         : 0.29.23
Cython Runtime Version       : 0.29.13
CUDA Root                    : C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2
nvcc PATH                    : C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\bin\nvcc.EXE
CUDA Build Version           : 11020
CUDA Driver Version          : 11040
CUDA Runtime Version         : 11020
cuBLAS Version               : (available)
cuFFT Version                : 10401
cuRAND Version               : 10203
cuSOLVER Version             : (11, 1, 0)
cuSPARSE Version             : (available)
NVRTC Version                : (11, 2)
Thrust Version               : 101000
CUB Build Version            : 101000
Jitify Build Version         : 60e9e72
cuDNN Build Version          : None
cuDNN Version                : None
NCCL Build Version           : None
NCCL Runtime Version         : None
cuTENSOR Version             : None
cuSPARSELt Build Version     : None
Device 0 Name                : NVIDIA RTX A6000
Device 0 Compute Capability  : 86
Device 0 PCI Bus ID          : 0000:65:00.0

- OS                           : Windows-10-10.0.22000-SP0
- CuPy version 9.2.0
  - OS/Platform Windows
  - CUDA version 11.2
  - cuDNN/NCCL version (if applicable)

## Code to reproduce 
```python
import cupy
import cupyx
import cupyx.scipy.ndimage as cndimage

RatioPPW=1.5
gFXDim=(cupy.arange(369)*RatioPPW+12).astype(np.float32)
gFYDim=(cupy.arange(449)*RatioPPW+12).astype(np.float32)
gFZDim=(cupy.arange(425)*RatioPPW+12+15).astype(np.float32)
FRefY,FRefX,FRefZ=cupy.meshgrid(gFYDim,gFXDim,gFZDim)
gPressMapFourier=cupy.ones((577,697,676)) 
gFPressure=cndimage.map_coordinates(gPressMapFourier, cupy.array([FRefX.ravel(),FRefY.ravel(),FRefZ.ravel()]))
```
## Error messages, stack traces, or logs
```

---------------------------------------------------------------------------
NVRTCError                                Traceback (most recent call last)
~\.edm\envs\MORPHEUS36\lib\site-packages\cupy\cuda\compiler.py in compile(self, options, log_stream)
    622                     nvrtc.addAddNameExpression(self.ptr, ker)
--> 623             nvrtc.compileProgram(self.ptr, options)
    624             mapping = None

cupy_backends\cuda\libs\nvrtc.pyx in cupy_backends.cuda.libs.nvrtc.compileProgram()

cupy_backends\cuda\libs\nvrtc.pyx in cupy_backends.cuda.libs.nvrtc.compileProgram()

cupy_backends\cuda\libs\nvrtc.pyx in cupy_backends.cuda.libs.nvrtc.check_status()

NVRTCError: NVRTC_ERROR_COMPILATION (6)

During handling of the above exception, another exception occurred:

CompileException                          Traceback (most recent call last)
<ipython-input-13-4575038dcee6> in <module>
----> 1 SIM.Step8_ResamplingAndExport()
      2 gc.collect()

~\Documents\ITRUSST\CODE\CoreDefsFunctions.py in Step8_ResamplingAndExport(self, bReload, bSkipSaveAndReturnData)
    362             return self._SIM_SETTINGS.ResamplingToFocusConditions3D(TESTNAME=self._TESTNAME,
    363                             bReload=bReload,
--> 364                             bSkipSaveAndReturnData=bSkipSaveAndReturnData)
    365         else:
    366             return self._SIM_SETTINGS.ResamplingToFocusConditions(TESTNAME=self._TESTNAME,

~\Documents\ITRUSST\CODE\CoreDefsFunctions.py in ResamplingToFocusConditions3D(self, TESTNAME, bReload, bSkipSaveAndReturnData, bUseCupyToInterpolate)
   2287             gPressMapFourier=cupy.asarray(PressMapFourier)
   2288             gPhaseMap=cupy.asarray(PhaseMap)
-> 2289             gFPressure=cndimage.map_coordinates(gPressMapFourier, cupy.array([FRefX.ravel(),FRefY.ravel(),FRefZ.ravel()]))
   2290             gFPressure=gFPressure.reshape(FRefX.shape)
   2291             FPressure=gFPressure.get()

~\.edm\envs\MORPHEUS36\lib\site-packages\cupyx\scipy\ndimage\interpolation.py in map_coordinates(input, coordinates, output, order, mode, cval, prefilter)
    300         input = input.astype(cupy.float32)
    301     coordinates = _check_coordinates(coordinates, order)
--> 302     filtered, nprepad = _filter_input(input, prefilter, mode, cval, order)
    303     large_int = max(_prod(input.shape), coordinates.shape[0]) > 1 << 31
    304     kern = _interp_kernels._get_map_kernel(

~\.edm\envs\MORPHEUS36\lib\site-packages\cupyx\scipy\ndimage\interpolation.py in _filter_input(image, prefilter, mode, cval, order)
    243     padded, npad = _prepad_for_spline_filter(image, mode, cval)
    244     float_dtype = cupy.promote_types(image.dtype, cupy.float32)
--> 245     filtered = spline_filter(padded, order, output=float_dtype, mode=mode)
    246     return cupy.ascontiguousarray(filtered), npad
    247

~\.edm\envs\MORPHEUS36\lib\site-packages\cupyx\scipy\ndimage\interpolation.py in spline_filter(input, order, output, mode)
    177     if order not in [0, 1] and input.ndim > 0:
    178         for axis in range(x.ndim):
--> 179             spline_filter1d(x, order, axis, output=temp, mode=mode)
    180             x = temp
    181     if isinstance(output, cupy.ndarray):

~\.edm\envs\MORPHEUS36\lib\site-packages\cupyx\scipy\ndimage\interpolation.py in spline_filter1d(input, order, axis, output, mode)
    141
    142     # apply caual + anti-causal IIR spline filters
--> 143     kern(grid, block, (temp, info))
    144
    145     if isinstance(output, cupy.ndarray) and temp is not output:

cupy\_core\raw.pyx in cupy._core.raw.RawKernel.__call__()

cupy\_core\raw.pyx in cupy._core.raw.RawKernel.kernel.__get__()

cupy\_core\raw.pyx in cupy._core.raw.RawKernel._kernel()

cupy\_util.pyx in cupy._util.memoize.decorator.ret()

cupy\_core\raw.pyx in cupy._core.raw._get_raw_module()

cupy\_core\core.pyx in cupy._core.core.compile_with_cache()

cupy\_core\core.pyx in cupy._core.core.compile_with_cache()

~\.edm\envs\MORPHEUS36\lib\site-packages\cupy\cuda\compiler.py in compile_with_cache(source, options, arch, cache_dir, extra_source, backend, enable_cooperative_groups, name_expressions, log_stream, jitify)
    433             source, options, arch, cache_dir, extra_source, backend,
    434             enable_cooperative_groups, name_expressions, log_stream,
--> 435             cache_in_memory, jitify)
    436
    437

~\.edm\envs\MORPHEUS36\lib\site-packages\cupy\cuda\compiler.py in _compile_with_cache_cuda(source, options, arch, cache_dir, extra_source, backend, enable_cooperative_groups, name_expressions, log_stream, cache_in_memory, jitify)
    509         ptx, mapping = compile_using_nvrtc(
    510             source, options, arch, cu_name, name_expressions,
--> 511             log_stream, cache_in_memory, jitify)
    512         if _is_cudadevrt_needed(options):
    513             # for separate compilation

~\.edm\envs\MORPHEUS36\lib\site-packages\cupy\cuda\compiler.py in compile_using_nvrtc(source, options, arch, filename, name_expressions, log_stream, cache_in_memory, jitify)
    270
    271             return _compile(source, options, cu_path,
--> 272                             name_expressions, log_stream, jitify)
    273     else:
    274         cu_path = '' if not jitify else filename

~\.edm\envs\MORPHEUS36\lib\site-packages\cupy\cuda\compiler.py in _compile(source, options, cu_path, name_expressions, log_stream, jitify)
    253                              name_expressions=name_expressions)
    254         try:
--> 255             ptx, mapping = prog.compile(options, log_stream)
    256         except CompileException as e:
    257             dump = _get_bool_env_variable(

~\.edm\envs\MORPHEUS36\lib\site-packages\cupy\cuda\compiler.py in compile(self, options, log_stream)
    634             log = nvrtc.getProgramLog(self.ptr)
    635             raise CompileException(log, self.src, self.name, options,
--> 636                                    'nvrtc' if not runtime.is_hip else 'hiprtc')
    637
    638

CompileException: C:\Users\SPICHA~1\AppData\Local\Temp\tmpr8sm6w8k\3cb584674357d966d3689a89981bda53_2.cubin.cu(38): error: more than one instance of overloaded function "min" matches the argument list:
            function "min(int, int)"
            function "min(unsigned int, int)"
            function "min(long long, long long)"
            function "min(long long, unsigned long long)"
            argument types are: (long long, int)

1 error detected in the compilation of "C:\Users\SPICHA~1\AppData\Local\Temp\tmpr8sm6w8k\3cb584674357d966d3689a89981bda53_2.cubin.cu".

CompileException: C:\Users\SPICHA~1\AppData\Local\Temp\tmpr8sm6w8k\3cb584674357d966d3689a89981bda53_2.cubin.cu(38): error: more than one instance of overloaded function "min" matches the argument list:
            function "min(int, int)"
            function "min(unsigned int, int)"
            function "min(long long, long long)"
            function "min(long long, unsigned long long)"
            argument types are: (long long, int)
```



Discussion:
I'll make a fixing PR for this soon.

Resolving patch:
diff --git a/cupyx/scipy/ndimage/_spline_prefilter_core.py b/cupyx/scipy/ndimage/_spline_prefilter_core.py
--- a/cupyx/scipy/ndimage/_spline_prefilter_core.py
+++ b/cupyx/scipy/ndimage/_spline_prefilter_core.py
@@ -50,7 +50,7 @@ def _causal_init_code(mode):
         z_n_1 = pow(z, (P)(n - 1));
 
         c[0] = c[0] + z_n_1 * c[(n - 1) * element_stride];
-        for (i = 1; i < min(n - 1, {n_boundary}); ++i) {{
+        for (i = 1; i < min(n - 1, static_cast<idx_t>({n_boundary})); ++i) {{
             c[0] += z_i * (c[i * element_stride] +
                            z_n_1 * c[(n - 1 - i) * element_stride]);
             z_i *= z;
@@ -60,7 +60,7 @@ def _causal_init_code(mode):
         code += '''
         z_i = z;
 
-        for (i = 1; i < min(n, {n_boundary}); ++i) {{
+        for (i = 1; i < min(n, static_cast<idx_t>({n_boundary})); ++i) {{
             c[0] += z_i * c[(n - i) * element_stride];
             z_i *= z;
         }}
@@ -72,7 +72,7 @@ def _causal_init_code(mode):
         c0 = c[0];
 
         c[0] = c[0] + z_n * c[(n - 1) * element_stride];
-        for (i = 1; i < min(n, {n_boundary}); ++i) {{
+        for (i = 1; i < min(n, static_cast<idx_t>({n_boundary})); ++i) {{
             c[0] += z_i * (c[i * element_stride] +
                            z_n * c[(n - 1 - i) * element_stride]);
             z_i *= z;
@@ -100,7 +100,7 @@ def _anticausal_init_code(mode):
         code += '''
         z_i = z;
 
-        for (i = 0; i < min(n - 1, {n_boundary}); ++i) {{
+        for (i = 0; i < min(n - 1, static_cast<idx_t>({n_boundary})); ++i) {{
             c[(n - 1) * element_stride] += z_i * c[i * element_stride];
             z_i *= z;
         }}


Test patch:
diff --git a/tests/cupyx_tests/scipy_tests/ndimage_tests/test_interpolation.py b/tests/cupyx_tests/scipy_tests/ndimage_tests/test_interpolation.py
--- a/tests/cupyx_tests/scipy_tests/ndimage_tests/test_interpolation.py
+++ b/tests/cupyx_tests/scipy_tests/ndimage_tests/test_interpolation.py
@@ -940,6 +940,21 @@ def test_spline_filter1d_output(self, xp, scp, array_order):
         return output
 
 
+# See #5537
+@testing.slow
+@testing.with_requires('scipy')
+class TestSplineFilter1dLargeArray:
+
+    @pytest.mark.parametrize('mode', ['mirror', 'grid-wrap', 'reflect'])
+    @testing.numpy_cupy_allclose(atol=1e-5, rtol=1e-5, scipy_name='scp')
+    def test_spline_filter1d_large_array(self, xp, scp, mode):
+        if mode == 'grid-wrap' and scipy_version < '1.6.0':
+            pytest.skip('testing mode grid-wrap requires scipy >= 1.6.0')
+        x = testing.shaped_random(
+            (2**10, 2**10, 2**10), dtype=numpy.float64, xp=xp)
+        return scp.ndimage.spline_filter1d(x, mode=mode)
+
+
 @testing.parameterize(*testing.product({
     # these 3 modes have analytical spline boundary conditions
     'mode': ['mirror', 'grid-wrap', 'reflect'],