"int64 indices" error in fit_predict function even with 32-bit integer

### Describe the bug

I'm trying to apply spectral clustering on a sparse adjacency matrix of a surface mesh. Although the matrix's entries are using 32-bit integer indices, the `fit_predict` function gives me the following error: 
```
ValueError: Only sparse matrices with 32-bit integer indices are accepted. Got int64 indices. Please do report a minimal reproducer on scikit-learn issue tracker so that support for your use-case can be studied by maintainers.
```

### Steps/Code to Reproduce

```python
import trimesh
import pyvista as pv 
import numpy as np
import networkx as nx
from sklearn.cluster import SpectralClustering 

# Get a sample file
bunny = pv.examples.download_bunny_coarse()
vertices = np.array(bunny.points) # (num_vertex, 3)
faces = bunny.faces.reshape(-1,4)[:, 1:] # trinangular faces, (num_face, 3)

# Create a mesh object in trimesh
mesh = trimesh.Trimesh(vertices=vertices, faces=faces)

def mesh_to_graph(mesh):
    G = nx.Graph()
    # Add nodes
    for i, vertex in enumerate(mesh.vertices):
        G.add_node(i, pos=vertex)
    # Add edges
    for face in mesh.faces:
        for i in range(3):
            G.add_edge(face[i], face[(i + 1) % 3])
    return G

def spectral_clustering_mesh(mesh, n_clusters=6):
    # Convert mesh to a graph
    G = mesh_to_graph(mesh)
    # Compute the graph adjacency matrix as a scipy sparse array 
    adj_matrix_sparse = nx.to_scipy_sparse_array(G, dtype=np.int32, format='csr')
    # Perform spectral clustering 
    sc = SpectralClustering(n_clusters = n_clusters,
                            affinity='precomputed',
                            assign_labels='kmeans')
    labels_groups = sc.fit_predict(adj_matrix_sparse) # <--- Causes the error! 
    return labels_groups

# Run the clustering 
labels_groups = spectral_clustering_mesh(mesh, 6)

# Use the labels groups to color-code the mesh
pl = pv.Plotter()
pl.add_mesh(mesh, scalars=labels_groups, colormap=cm.Spectral_r)
pl.show()
```

### Expected Results

If successful, the code should find 6 clusters of facets and color-code the Stanford bunny accordingly. 

### Actual Results

```pytb
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[1], [line 39](vscode-notebook-cell:?execution_count=1&line=39)
         labels_groups = sc.fit_predict(adj_matrix_sparse)
         return labels_groups
---> labels_groups = spectral_clustering_mesh(mesh, 6)

Cell In[1], [line 36](vscode-notebook-cell:?execution_count=1&line=36)
     # Perform spectral clustering 
     sc = SpectralClustering(n_clusters = n_clusters,
                             affinity='precomputed',
                             assign_labels='kmeans')
---> labels_groups = sc.fit_predict(adj_matrix_sparse)
     return labels_groups

File /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/cluster/_spectral.py:792, in SpectralClustering.fit_predict(self, X, y)
     def fit_predict(self, X, y=None):
         """Perform spectral clustering on `X` and return cluster labels.
     
         Parameters
   (
             Cluster labels.
         """
-->      return super().fit_predict(X, y)

File /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/base.py:900, in ClusterMixin.fit_predict(self, X, y, **kwargs)
    """
    Perform clustering on `X` and returns cluster labels.
    
   (
        Cluster labels.
    """
    # non-optimized default implementation; override when a better
    # method is possible for a given clustering algorithm
--> self.fit(X, **kwargs)
    return self.labels_

File /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/base.py:1473, in _fit_context.<locals>.decorator.<locals>.wrapper(estimator, *args, **kwargs)
        estimator._validate_params()
    with config_context(
        skip_parameter_validation=(
            prefer_skip_nested_validation or global_skip_validation
        )
    ):
->      return fit_method(estimator, *args, **kwargs)

File /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/cluster/_spectral.py:744, in SpectralClustering.fit(self, X, y)
     n_components = (
         self.n_clusters if self.n_components is None else self.n_components
     )
     # We now obtain the real valued solution matrix to the
     # relaxed Ncut problem, solving the eigenvalue problem
     # L_sym x = lambda x  and recovering u = D^-1/2 x.
     # The first eigenvector is constant only for fully connected graphs
     # and should be kept for spectral clustering (drop_first = False)
     # See spectral_embedding documentation.
-->  maps = _spectral_embedding(
         self.affinity_matrix_,
         n_components=n_components,
         eigen_solver=self.eigen_solver,
         random_state=random_state,
         eigen_tol=self.eigen_tol,
         drop_first=False,
     )
     if self.verbose:
         print(f"Computing label assignment using {self.assign_labels}")

File /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/manifold/_spectral_embedding.py:369, in _spectral_embedding(adjacency, n_components, eigen_solver, random_state, eigen_tol, norm_laplacian, drop_first)
     laplacian *= -1
     v0 = _init_arpack_v0(laplacian.shape[0], random_state)
-->  laplacian = check_array(
         laplacian, accept_sparse="csr", accept_large_sparse=False
     )
     _, diffusion_map = eigsh(
         laplacian, k=n_components, sigma=1.0, which="LM", tol=tol, v0=v0
     )
     embedding = diffusion_map.T[n_components::-1]

File /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/utils/validation.py:971, in check_array(array, accept_sparse, accept_large_sparse, dtype, order, copy, force_writeable, force_all_finite, ensure_2d, allow_nd, ensure_min_samples, ensure_min_features, estimator, input_name)
     if sp.issparse(array):
         _ensure_no_complex_data(array)
-->      array = _ensure_sparse_format(
             array,
             accept_sparse=accept_sparse,
             dtype=dtype,
             copy=copy,
             force_all_finite=force_all_finite,
             accept_large_sparse=accept_large_sparse,
             estimator_name=estimator_name,
             input_name=input_name,
         )
         if ensure_2d and array.ndim < 2:
             raise ValueError(
                 f"Expected 2D input, got input with shape {array.shape}.\n"
                 "Reshape your data either using array.reshape(-1, 1) if "
                 "your data has a single feature or array.reshape(1, -1) "
                 "if it contains a single sample."
             )

File /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/utils/validation.py:591, in _ensure_sparse_format(sparse_container, accept_sparse, dtype, copy, force_all_finite, accept_large_sparse, estimator_name, input_name)
         accept_sparse = [accept_sparse]
     # Indices dtype validation
-->  _check_large_sparse(sparse_container, accept_large_sparse)
     if accept_sparse is False:
         padded_input = " for " + input_name if input_name else ""

File /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/utils/validation.py:1145, in _check_large_sparse(X, accept_large_sparse)
    indices_datatype = getattr(X, key).dtype
    if indices_datatype not in supported_indices:
->      raise ValueError(
            "Only sparse matrices with 32-bit integer indices are accepted."
            f" Got {indices_datatype} indices. Please do report a minimal"
            " reproducer on scikit-learn issue tracker so that support for"
            " your use-case can be studied by maintainers. See:"
            " https://scikit-learn.org/dev/developers/minimal_reproducer.html"
        )

ValueError: Only sparse matrices with 32-bit integer indices are accepted. Got int64 indices. Please do report a minimal reproducer on scikit-learn issue tracker so that support for your use-case can be studied by maintainers. See: https://scikit-learn.org/dev/developers/minimal_reproducer.html
```

### Versions

```shell
System:
    python: 3.10.14 (main, May  6 2024, 14:42:37) [Clang 14.0.6 ]
executable: /opt/anaconda3/envs/env_shadow/bin/python
   machine: macOS-14.6.1-arm64-arm-64bit

Python dependencies:
      sklearn: 1.5.1
          pip: 24.2
   setuptools: 60.10.0
        numpy: 1.26.4
        scipy: 1.14.1
       Cython: 0.29.37
       pandas: 2.2.2
   matplotlib: 3.9.2
       joblib: 1.4.2
threadpoolctl: 3.5.0

Built with OpenMP: True

threadpoolctl info:
       user_api: blas
   internal_api: openblas
    num_threads: 8
         prefix: libopenblas
       filepath: /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/numpy/.dylibs/libopenblas64_.0.dylib
        version: 0.3.23.dev
threading_layer: pthreads
   architecture: armv8

       user_api: openmp
   internal_api: openmp
    num_threads: 8
         prefix: libomp
       filepath: /opt/anaconda3/envs/env_shadow/lib/python3.10/site-packages/sklearn/.dylibs/libomp.dylib
        version: None
```

COMMENT:
@dkobayas-cyber the issue is not the `dtype` but the indices `dtype`, a work-around for your snippet is to convert the indices:
```py
adj_matrix_sparse.indices = adj_matrix_sparse.indices.astype(np.int32, casting='same_kind')
adj_matrix_sparse.indptr = adj_matrix_sparse.indptr.astype(np.int32, casting='same_kind')
```

More context:
- scipy historically had only int32 indices and still uses int32 indices by default unless `arr.nnz > 2**31`, i.e. more than ~`2.1e9` non-zero entries. See https://github.com/scipy/scipy/issues/16774 for more details.
- some of our Cython code only deals with int32 indices, in some cases adding the functionality to support int64 indices is probably a significant amount of work
- we would need to see some motivation why this work would be useful e.g. some context with your use case and a few details about why int64 indices are needed. I guess this is the goal of the error message but it is probably not explicit enough.
- I haven't tried to understand in @dkobayas-cyber's snippet who is responsible for creating int64 indices although there is no clear reason why, since there is only 3000 non-zero entries in the sparse matrix ...

Labels: Needs Info