# *****************************************************************************
# 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 -----------------------------------------
# --- 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