Source code for dpnp.scipy.sparse._csr

# *****************************************************************************
# Copyright (c) 2026, Intel Corporation
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# - Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
# - Neither the name of the copyright holder nor the names of its contributors
#   may be used to endorse or promote products derived from this software
#   without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
# *****************************************************************************

"""CSR matrix backed by dpnp/USM arrays.

Minimal implementation supporting the operations exercised by
dpnp.scipy.sparse.linalg solvers (cg, gmres, minres) and
LinearOperator. Construction from dense arrays or raw CSR
components; ``dot`` is routed through oneMKL ``sparse::gemv``.

SpMV fast path
--------------
On first ``.dot(x)`` with a 1-D ``x`` of a supported dtype, the
instance lazily allocates an oneMKL ``matrix_handle`` via
``_sparse_gemv_init`` (which itself runs ``set_csr_data`` plus
``optimize_gemv`` -- the expensive sparsity-analysis phase). The
handle is cached on the instance and reused for every subsequent
matvec; ``__del__`` releases it. This matches the cupyx behaviour
where ``csr_matrix.dot`` calls cuSPARSE SpMV directly without
densification, and lets the iterative solvers in
``dpnp.scipy.sparse.linalg`` reuse the same handle through
``_make_fast_matvec`` without rebuilding it.

An all-zero matrix (``nnz == 0``) has no handle: oneMKL
``set_csr_data`` rejects ``nnz == 0``. ``_ensure_spmv_handle`` returns
``None`` in that case and ``dot`` short-circuits to a zero result, so
every caller -- ``dot`` and the solver fast-path alike -- stays on a
handle-free path.
"""

import sys

import dpctl.utils as _dpu
import numpy as _np

import dpnp as _dpnp

# pylint: disable-next=no-name-in-module
import dpnp.backend.extensions.sparse._sparse_impl as _si
import dpnp.tensor as _dpt
from dpnp.exceptions import ExecutionPlacementError

from .._lib._sparse import SparseABC, issparse

# Two short blocks intentionally mirror code in
# dpnp/scipy/sparse/linalg/_iterative.py: the cached-SpMV invocation
# and the __del__ shutdown-safe release pattern. Both are tightly
# coupled to oneMKL's contract; extracting a shared helper would add
# indirection without reducing real duplication.
# pylint: disable=duplicate-code

# Value dtypes the oneMKL sparse::gemv dispatch table registers
# (see dpnp/backend/extensions/sparse/types_matrix.hpp). ``dot`` raises
# for anything outside this set.
_SPMV_VALUE_DTYPES = frozenset("fdFD")
# Index dtypes oneMKL accepts (int32, int64). Matches the second
# dimension of SparseGemvInitTypePairSupportFactory.
_SPMV_INDEX_DTYPES = frozenset("ilq")


def _isshape(arg):
    """True if arg is a length-2 tuple of non-negative integers."""
    if not (isinstance(arg, tuple) and len(arg) == 2):
        return False
    try:
        return all(int(v) == v and int(v) >= 0 for v in arg)
    except (TypeError, ValueError):
        return False


# pylint: disable=invalid-name,too-many-instance-attributes
# The instance-attribute count exceeds the default
# pylint cap because the lazily-built oneMKL handle adds four cache
# fields (handle, val_type_id, si, exec_q) on top of the CSR triple +
# shape; all are required.
[docs] class csr_matrix(SparseABC): # pylint: disable=too-many-public-methods """Compressed Sparse Row matrix on a SYCL device. Attributes ---------- data : {dpnp.ndarray, usm_ndarray} 1-D array of nonzero values, shape (nnz,). indices : {dpnp.ndarray, usm_ndarray} 1-D array of column indices, shape (nnz,). indptr : {dpnp.ndarray, usm_ndarray} 1-D array of row pointers, shape (M+1,). shape : tuple of int Matrix dimensions ``(M, N)``. dtype : dpnp dtype Data type of the stored values. nnz : int Number of stored values, including explicit zeros. has_sorted_indices : bool Whether column indices are sorted within each row. format : str Always 'csr'. ndim : int Always 2. Notes ----- Construction: * ``csr_matrix(D)`` -- from a 2-D array (``dpnp.ndarray`` or ``usm_ndarray``). * ``csr_matrix((M, N), [dtype=...])`` -- an empty (all-zero) matrix of shape ``(M, N)``; ``dtype`` defaults to the default floating-point type of the device on which the matrix is allocated. * ``csr_matrix((data, indices, indptr), [shape=(M, N)])`` -- from raw CSR component arrays (1-D, on the same SYCL queue). ``shape`` is inferred from the index arrays when omitted. Components are stored as given; indices are sorted lazily (see ``sort_indices``) when required by the SpMV path. * ``csr_matrix(other_csr)`` -- copy of another csr_matrix. Duplicate column indices within a row are not supported (unlike scipy, which sums them); each column must appear at most once per row. This matches the CSR produced by dense construction and the solvers, which never generate duplicates. Supported operations: construction, ``dot`` (matvec) via cached oneMKL SpMV, ``toarray``, ``copy``. This is a solver-support subset of the scipy/cupy CSR API; arithmetic, indexing, reductions, transpose, format conversion and element-wise math are not implemented (the most common such methods raise ``NotImplementedError``). Convert with ``toarray()`` and use dpnp for those. """ format = "csr" ndim = 2 def __init__( self, arg1, shape=None, dtype=None, copy=False, *, device=None, usm_type=None, sycl_queue=None, ): # Lazy SpMV handle state. Assigned BEFORE the dispatch below so # that __del__ never sees a partially-constructed object (it can # be invoked if any of the _init_* helpers raise). self._spmv_handle = None self._spmv_val_type_id = -1 self._spmv_si = None self._spmv_exec_q = None self._has_sorted_indices = None self._checked_format = False self._data = None self._indices = None self._indptr = None self._shape = None if issparse(arg1): self._init_from_components( (arg1.data, arg1.indices, arg1.indptr), arg1.shape, dtype=dtype if dtype is not None else arg1.dtype, copy=True, device=device, usm_type=usm_type, sycl_queue=sycl_queue, ) elif _dpnp.is_supported_array_type(arg1): self._init_from_dense( arg1, dtype=dtype, device=device, usm_type=usm_type, sycl_queue=sycl_queue, ) elif isinstance(arg1, tuple) and len(arg1) == 2 and _isshape(arg1): self._init_empty( arg1, dtype=dtype, device=device, usm_type=usm_type, sycl_queue=sycl_queue, ) elif isinstance(arg1, tuple) and len(arg1) == 3: self._init_from_components( arg1, shape, dtype=dtype, copy=copy, device=device, usm_type=usm_type, sycl_queue=sycl_queue, ) else: raise TypeError( f"csr_matrix: cannot construct from {type(arg1).__name__}; " "supported forms are a 2-D array (dpnp.ndarray or " "usm_ndarray), another csr_matrix, a (data, indices, indptr) " "tuple, or a shape tuple (M, N) for an empty matrix." ) def _init_empty( self, shape, dtype=None, device=None, usm_type=None, sycl_queue=None ): nrows, ncols = int(shape[0]), int(shape[1]) if dtype is None: dtype = _dpnp.default_float_type( device=device, sycl_queue=sycl_queue ) common = { "device": device, "usm_type": usm_type, "sycl_queue": sycl_queue, } self._data = _dpnp.empty(0, dtype=dtype, **common) idx_dtype = _dpnp.int64 self._indices = _dpnp.empty(0, dtype=idx_dtype, **common) self._indptr = _dpnp.zeros(nrows + 1, dtype=idx_dtype, **common) self._shape = (nrows, ncols) self._has_sorted_indices = True def _init_from_components( self, arrays, shape, dtype=None, copy=False, device=None, usm_type=None, sycl_queue=None, ): data, indices, indptr = arrays _dpnp.check_supported_arrays_type(data, indices, indptr) # Normalize to dpnp.ndarray; moved/copied to the requested # placement if given, otherwise the input placement is kept. data = _dpnp.asarray( data, device=device, usm_type=usm_type, sycl_queue=sycl_queue ) indices = _dpnp.asarray( indices, device=device, usm_type=usm_type, sycl_queue=sycl_queue ) indptr = _dpnp.asarray( indptr, device=device, usm_type=usm_type, sycl_queue=sycl_queue ) if data.ndim != 1 or indices.ndim != 1 or indptr.ndim != 1: raise ValueError( "csr_matrix: data, indices, and indptr must be 1-D" ) if data.shape[0] != indices.shape[0]: raise ValueError( f"csr_matrix: data length {data.shape[0]} != " f"indices length {indices.shape[0]}" ) # Infer number of rows from indptr when shape is omitted; number # of columns is max(indices)+1 (matching scipy/cupy). if shape is None: nrows = int(indptr.shape[0]) - 1 ncols = int(indices.max()) + 1 if indices.shape[0] > 0 else 0 else: nrows, ncols = int(shape[0]), int(shape[1]) if indptr.shape[0] != nrows + 1: raise ValueError( f"csr_matrix: indptr length {indptr.shape[0]} != " f"nrows+1 ({nrows + 1})" ) q = _dpt.get_execution_queue( (data.sycl_queue, indices.sycl_queue, indptr.sycl_queue) ) if q is None: raise ExecutionPlacementError( "csr_matrix: data, indices, and indptr must be allocated on " "the same SYCL queue" ) idx_char = _np.dtype(indices.dtype).char if idx_char not in ("i", "l", "q"): raise TypeError( f"csr_matrix: indices dtype must be int32 or int64, " f"got {indices.dtype}" ) if _np.dtype(indptr.dtype).char != idx_char: raise TypeError( f"csr_matrix: indptr dtype ({indptr.dtype}) must match " f"indices dtype ({indices.dtype})" ) if dtype is not None and _np.dtype(dtype) != _np.dtype(data.dtype): data = data.astype(dtype, copy=True) elif copy: data = data.copy() if copy: indices = indices.copy() indptr = indptr.copy() # oneMKL reads each component as a bare unit-stride pointer, so a # non-contiguous input (a slice or strided view) would be misread # element-for-element -- and because the pointers are baked into # the cached handle, every later matvec would be wrong. Pack here # rather than at use, so the stored arrays are always valid CSR. # ascontiguousarray is a no-op for the usual contiguous input. data = _dpnp.ascontiguousarray(data) indices = _dpnp.ascontiguousarray(indices) indptr = _dpnp.ascontiguousarray(indptr) # Store components verbatim (matching scipy): the caller's column # order is preserved and copy=False aliasing is honoured. Sorting # is deferred to sort_indices(), invoked lazily by the SpMV path. self._data = data self._indices = indices self._indptr = indptr self._shape = (nrows, ncols) self._has_sorted_indices = None @property def has_sorted_indices(self): """Whether column indices are sorted per row (scipy-compatible). The result is cached; an unknown state triggers a one-time check. """ if self._has_sorted_indices is None: self._has_sorted_indices = self._check_sorted() return self._has_sorted_indices def _check_sorted(self): idx = self._indices if idx.shape[0] == 0: return True # Row lengths feed dpnp.repeat below, which rejects a negative # count with an opaque "'repeats' elements must be positive"; # validate first so a malformed indptr names the real problem. self.check_format() # Sorted iff no adjacent pair within the same row is decreasing. q = idx.sycl_queue nrows = self._shape[0] row_lengths = self._indptr[1:] - self._indptr[:-1] row_ids = _dpnp.repeat( _dpnp.arange(nrows, dtype=self._indptr.dtype, sycl_queue=q), row_lengths, ) same_row = row_ids[1:] == row_ids[:-1] decreasing = idx[1:] < idx[:-1] return not bool(_dpnp.any(same_row & decreasing))
[docs] def sort_indices(self): """Sort column indices within each row, in place (scipy-compatible). SpMV backends require sorted CSR; this is a no-op once the indices are known sorted. """ if self.has_sorted_indices: return indices = self._indices nnz = indices.shape[0] if nnz == 0: self._has_sorted_indices = True return nrows = self._shape[0] row_lengths = self._indptr[1:] - self._indptr[:-1] row_ids = _dpnp.repeat( _dpnp.arange( nrows, dtype=indices.dtype, usm_type=indices.usm_type, sycl_queue=indices.sycl_queue, ), row_lengths, ) # Lexsort by (row, col) via two stable passes. order = _dpnp.argsort(indices, kind="stable") order = order[_dpnp.argsort(row_ids[order], kind="stable")] self._data = self._data[order] self._indices = self._indices[order] self._has_sorted_indices = True
def _init_from_dense( self, dense, dtype=None, device=None, usm_type=None, sycl_queue=None ): # Normalize to dpnp.ndarray; moved/copied to the requested # placement if given, otherwise the input placement is kept. dense = _dpnp.asarray( dense, device=device, usm_type=usm_type, sycl_queue=sycl_queue ) if dense.ndim != 2: raise ValueError( f"csr_matrix: dense input must be 2-D, got {dense.ndim}-D" ) if dtype is not None: dense = dense.astype(dtype, copy=False) nrows, ncols = dense.shape rows, cols = _dpnp.nonzero(dense) nnz = int(rows.shape[0]) if nnz == 0: self._data = _dpnp.empty_like(dense, shape=0) self._indices = _dpnp.empty_like(dense, shape=0, dtype=_dpnp.int64) self._indptr = _dpnp.zeros_like( dense, shape=nrows + 1, dtype=_dpnp.int64 ) self._shape = (nrows, ncols) self._has_sorted_indices = True return values = dense[rows, cols] idx_dtype = _dpnp.int64 row_counts = _dpnp.bincount(rows.astype(idx_dtype), minlength=nrows) indptr = _dpnp.empty_like(dense, shape=nrows + 1, dtype=idx_dtype) indptr[0] = 0 indptr[1:] = _dpnp.cumsum(row_counts) self._data = values self._indices = cols.astype(idx_dtype) self._indptr = indptr self._shape = (nrows, ncols) # dpnp.nonzero yields row-major order, columns ascending per row. self._has_sorted_indices = True # --- read-only properties ------------------------------------------ @property def data(self): """Non-zero values, one per stored entry (read-only). Read-only because the oneMKL SpMV handle caches raw pointers into this array (see module docstring); reassigning it would leave the cached handle pointing at stale or freed USM memory without any signal that it needs to be rebuilt. Use :meth:`copy` or construct a new ``csr_matrix`` to change the stored values. """ return self._data @property def indices(self): """Column index of each stored entry (read-only). Read-only for the same reason as :attr:`data`: it feeds the cached oneMKL handle by raw pointer. """ return self._indices @property def indptr(self): """Row-start offsets into :attr:`data` / :attr:`indices` (read-only). Read-only for the same reason as :attr:`data`: it feeds the cached oneMKL handle by raw pointer. """ return self._indptr @property def shape(self): """Tuple of matrix dimensions ``(M, N)``.""" return self._shape @property def dtype(self): """Data type of stored values.""" return self._data.dtype @property def nnz(self): """Number of stored nonzero entries.""" return int(self._data.shape[0]) @property def size(self): """Alias for ``nnz`` (number of stored entries).""" return self.nnz @property # pylint: disable-next=invalid-name def T(self): """Transpose (not implemented).""" raise NotImplementedError("csr_matrix.T is not implemented.") # --- structural validation -----------------------------------------
[docs] def check_format(self): """Validate the CSR structure, raising ``ValueError`` if invalid. oneMKL trusts ``indptr`` / ``indices`` and indexes the value array with them directly, so malformed input is an out-of-bounds device read rather than a clean error. The checks mirror :meth:`scipy.sparse.csr_matrix.check_format`: * ``indptr[0] == 0`` and ``indptr`` non-decreasing, * ``indptr[-1] <= nnz`` (a larger value reads past ``data``), * every column index within ``[0, N)``. Called once automatically before the oneMKL handle is built (see ``_ensure_spmv_handle``); the result is cached, so repeated matvecs on the same matrix re-validate nothing. Calling it directly is only needed to validate a matrix ahead of its first matvec. Raises ------ ValueError If the CSR structure is malformed. """ if self._checked_format: return nrows, ncols = self._shape nnz = int(self._data.shape[0]) # One device->host transfer for the three indptr facts, instead # of three separate syncs. if self._indptr.shape[0] != nrows + 1: raise ValueError( f"csr_matrix: indptr length {self._indptr.shape[0]} != " f"nrows+1 ({nrows + 1})" ) indptr_ok = bool( _dpnp.all(self._indptr[1:] >= self._indptr[:-1]) & (self._indptr[0] == 0) & (self._indptr[-1] <= nnz) ) if not indptr_ok: first = int(self._indptr[0]) last = int(self._indptr[-1]) if first != 0: raise ValueError( f"csr_matrix: indptr[0] must be 0, got {first}" ) if last > nnz: raise ValueError( f"csr_matrix: indptr[-1] ({last}) exceeds the number " f"of stored values ({nnz}); the index arrays would " "read past the end of data." ) raise ValueError( "csr_matrix: indptr must be non-decreasing (row lengths " "cannot be negative)." ) if nnz > 0: # Bounds-check the column indices in a single reduction. in_range = bool( _dpnp.all((self._indices >= 0) & (self._indices < ncols)) ) if not in_range: lo = int(_dpnp.min(self._indices)) hi = int(_dpnp.max(self._indices)) raise ValueError( f"csr_matrix: column indices must lie in [0, {ncols}), " f"got range [{lo}, {hi}]." ) self._checked_format = True
# --- SpMV fast-path internals -------------------------------------- def _spmv_supported(self): """True iff value and index dtypes are in the oneMKL dispatch table.""" return ( _np.dtype(self._data.dtype).char in _SPMV_VALUE_DTYPES and _np.dtype(self._indices.dtype).char in _SPMV_INDEX_DTYPES ) def _ensure_spmv_handle(self): """Lazily build the cached oneMKL matrix_handle for forward SpMV. Returns the ``(si, handle, val_type_id, exec_q)`` quadruple so callers can drive ``_sparse_gemv_compute`` directly. Returns ``None`` if the value/index dtype combination is not in the oneMKL dispatch table, or if the matrix has nnz == 0 (oneMKL ``set_csr_data`` rejects nnz == 0; callers fall back to a zero matvec instead). """ if self._spmv_handle is not None: return ( self._spmv_si, self._spmv_handle, self._spmv_val_type_id, self._spmv_exec_q, ) if self._data.shape[0] == 0: return None if not self._spmv_supported(): return None # Validate before the structure reaches oneMKL, which would # otherwise index out of bounds on malformed input. Cached, so # this costs one sync per matrix, not one per matvec. self.check_format() self.sort_indices() exec_q = self._data.sycl_queue _manager = _dpu.SequentialOrderManager[exec_q] # pylint: disable-next=protected-access handle, val_type_id, ev = _si._sparse_gemv_init( exec_q, 0, # trans=N (forward) _dpnp.get_usm_ndarray(self._indptr), _dpnp.get_usm_ndarray(self._indices), _dpnp.get_usm_ndarray(self._data), int(self._shape[0]), int(self._shape[1]), int(self._data.shape[0]), _manager.submitted_events, ) _manager.add_event_pair(ev, ev) self._spmv_si = _si self._spmv_handle = handle self._spmv_val_type_id = val_type_id self._spmv_exec_q = exec_q return (_si, handle, val_type_id, exec_q) # --- public API: matvec via cached oneMKL handle -------------------
[docs] def dot(self, x): """Compute ``A @ x`` for a 1-D `x`. Dispatches to oneMKL ``sparse::gemv`` via a cached matrix handle (built lazily on the first call and reused afterwards), matching the cupyx ``csr_matrix.dot`` behaviour. Raises for an unsupported value/index dtype (no dense fallback); 2-D `x` (batched SpMM) is not implemented. """ if not _dpnp.is_supported_array_type(x): raise TypeError( f"csr_matrix.dot: expected a dpnp or usm_ndarray, " f"got {type(x).__name__}" ) if x.ndim != 1: raise NotImplementedError( f"csr_matrix.dot: only 1-D x is supported, got {x.ndim}-D" ) nrows, ncols = self._shape if x.shape[0] != ncols: raise ValueError( f"csr_matrix.dot: x length {x.shape[0]} does not match " f"number of columns {ncols}" ) if x.dtype != self._data.dtype: raise TypeError( f"csr_matrix.dot: x dtype {x.dtype} does not match matrix " f"dtype {self._data.dtype}" ) # nnz == 0: A @ x == 0. oneMKL set_csr_data rejects nnz == 0. if self._data.shape[0] == 0: return _dpnp.zeros_like(self._data, shape=nrows) # oneMKL reads x as a bare unit-stride pointer, so a strided view # (e.g. a column of a C-contiguous 2-D array) must be packed # first. ascontiguousarray is a no-op when x is already unit # stride, so the common path pays nothing. if not x.flags.c_contiguous: x = _dpnp.ascontiguousarray(x) handle_info = self._ensure_spmv_handle() if handle_info is None: raise TypeError( f"csr_matrix.dot: unsupported dtype combination " f"(value={self._data.dtype}, index={self._indices.dtype}); " "supported: {float32, float64, complex64, complex128} x " "{int32, int64}." ) _si, handle, val_type_id, exec_q = handle_info y = _dpnp.empty_like(self._data, shape=nrows) _manager = _dpu.SequentialOrderManager[exec_q] # pylint: disable-next=protected-access ht_ev, comp_ev = _si._sparse_gemv_compute( exec_q, handle, val_type_id, 0, # trans=N 1.0, # alpha _dpnp.get_usm_ndarray(x), 0.0, # beta _dpnp.get_usm_ndarray(y), nrows, ncols, _manager.submitted_events, ) _manager.add_event_pair(ht_ev, comp_ev) return y
def __matmul__(self, x): return self.dot(x) def __del__(self): handle = getattr(self, "_spmv_handle", None) if handle is None: return self._spmv_handle = None if sys.is_finalizing(): # OS reclaims the handle at process exit; the queue/module # state needed to release it may already be gone. return try: exec_q = self._spmv_exec_q _manager = _dpu.SequentialOrderManager[exec_q] release_ev = _si._sparse_gemv_release( exec_q, handle, _manager.submitted_events ) _manager.add_event_pair(release_ev, release_ev) release_ev.wait() except Exception: # pylint: disable=broad-exception-caught pass
[docs] def toarray(self): """Convert to a dense dpnp 2-D array.""" nrows = self._shape[0] q = self._data.sycl_queue dense = _dpnp.zeros_like(self._data, shape=self._shape) if self.nnz == 0: return dense # Malformed indices would scatter out of bounds below (or, for # an over-long indptr, silently drop entries), so validate on # this path too. Cached, and shared with the SpMV path. self.check_format() row_lengths = self._indptr[1:] - self._indptr[:-1] rows = _dpnp.repeat( _dpnp.arange(nrows, dtype=self._indices.dtype, sycl_queue=q), row_lengths, ) dense[rows, self._indices] = self._data return dense
[docs] def copy(self): """Return a deep copy of this matrix.""" return csr_matrix(self)
def __repr__(self): return ( f"<{self._shape[0]}x{self._shape[1]} csr_matrix " f"of dtype {self.dtype} with {self.nnz} stored elements>" ) # --- unsupported scipy/cupy CSR operations ------------------------- # This container implements only the subset needed by the # dpnp.scipy.sparse.linalg solvers (construction, matvec via ``dot``, # ``toarray``). The most commonly expected scipy/cupy methods below # raise a clear error; convert with # ``toarray()`` and use dpnp for anything else. @staticmethod def _unsupported(name): raise NotImplementedError( f"csr_matrix.{name} is not implemented; this container " "supports construction, dot (matvec) and toarray only. " "Use toarray() and operate with dpnp for other operations." ) # Unsupported-op stubs: each just raises via _unsupported(); the # signatures mirror scipy for a clear error, so args are intentionally # unused and docstrings would be pure noise. # pylint: disable=missing-function-docstring,unused-argument def __getitem__(self, key): self._unsupported("__getitem__") def __setitem__(self, key, value): self._unsupported("__setitem__") def __add__(self, other): self._unsupported("__add__") def __sub__(self, other): self._unsupported("__sub__") def __mul__(self, other): self._unsupported("__mul__")
[docs] def transpose(self, axes=None, copy=False): self._unsupported("transpose")
[docs] def conj(self, copy=True): self._unsupported("conj")
[docs] def conjugate(self, copy=True): self._unsupported("conjugate")
[docs] def sum(self, axis=None, dtype=None, out=None): self._unsupported("sum")
[docs] def tocsc(self, copy=False): self._unsupported("tocsc")
[docs] def tocoo(self, copy=False): self._unsupported("tocoo")
[docs] def todok(self, copy=False): self._unsupported("todok")
# pylint: enable=missing-function-docstring,unused-argument