Coverage for /usr/lib/python3/dist-packages/scipy/sparse/linalg/_interface.py: 27%
379 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Abstract linear algebra library.
3This module defines a class hierarchy that implements a kind of "lazy"
4matrix representation, called the ``LinearOperator``. It can be used to do
5linear algebra with extremely large sparse or structured matrices, without
6representing those explicitly in memory. Such matrices can be added,
7multiplied, transposed, etc.
9As a motivating example, suppose you want have a matrix where almost all of
10the elements have the value one. The standard sparse matrix representation
11skips the storage of zeros, but not ones. By contrast, a LinearOperator is
12able to represent such matrices efficiently. First, we need a compact way to
13represent an all-ones matrix::
15 >>> import numpy as np
16 >>> class Ones(LinearOperator):
17 ... def __init__(self, shape):
18 ... super().__init__(dtype=None, shape=shape)
19 ... def _matvec(self, x):
20 ... return np.repeat(x.sum(), self.shape[0])
22Instances of this class emulate ``np.ones(shape)``, but using a constant
23amount of storage, independent of ``shape``. The ``_matvec`` method specifies
24how this linear operator multiplies with (operates on) a vector. We can now
25add this operator to a sparse matrix that stores only offsets from one::
27 >>> from scipy.sparse import csr_matrix
28 >>> offsets = csr_matrix([[1, 0, 2], [0, -1, 0], [0, 0, 3]])
29 >>> A = aslinearoperator(offsets) + Ones(offsets.shape)
30 >>> A.dot([1, 2, 3])
31 array([13, 4, 15])
33The result is the same as that given by its dense, explicitly-stored
34counterpart::
36 >>> (np.ones(A.shape, A.dtype) + offsets.toarray()).dot([1, 2, 3])
37 array([13, 4, 15])
39Several algorithms in the ``scipy.sparse`` library are able to operate on
40``LinearOperator`` instances.
41"""
43import warnings
45import numpy as np
47from scipy.sparse import issparse
48from scipy.sparse._sputils import isshape, isintlike, asmatrix, is_pydata_spmatrix
50__all__ = ['LinearOperator', 'aslinearoperator']
53class LinearOperator:
54 """Common interface for performing matrix vector products
56 Many iterative methods (e.g. cg, gmres) do not need to know the
57 individual entries of a matrix to solve a linear system A*x=b.
58 Such solvers only require the computation of matrix vector
59 products, A*v where v is a dense vector. This class serves as
60 an abstract interface between iterative solvers and matrix-like
61 objects.
63 To construct a concrete LinearOperator, either pass appropriate
64 callables to the constructor of this class, or subclass it.
66 A subclass must implement either one of the methods ``_matvec``
67 and ``_matmat``, and the attributes/properties ``shape`` (pair of
68 integers) and ``dtype`` (may be None). It may call the ``__init__``
69 on this class to have these attributes validated. Implementing
70 ``_matvec`` automatically implements ``_matmat`` (using a naive
71 algorithm) and vice-versa.
73 Optionally, a subclass may implement ``_rmatvec`` or ``_adjoint``
74 to implement the Hermitian adjoint (conjugate transpose). As with
75 ``_matvec`` and ``_matmat``, implementing either ``_rmatvec`` or
76 ``_adjoint`` implements the other automatically. Implementing
77 ``_adjoint`` is preferable; ``_rmatvec`` is mostly there for
78 backwards compatibility.
80 Parameters
81 ----------
82 shape : tuple
83 Matrix dimensions (M, N).
84 matvec : callable f(v)
85 Returns returns A * v.
86 rmatvec : callable f(v)
87 Returns A^H * v, where A^H is the conjugate transpose of A.
88 matmat : callable f(V)
89 Returns A * V, where V is a dense matrix with dimensions (N, K).
90 dtype : dtype
91 Data type of the matrix.
92 rmatmat : callable f(V)
93 Returns A^H * V, where V is a dense matrix with dimensions (M, K).
95 Attributes
96 ----------
97 args : tuple
98 For linear operators describing products etc. of other linear
99 operators, the operands of the binary operation.
100 ndim : int
101 Number of dimensions (this is always 2)
103 See Also
104 --------
105 aslinearoperator : Construct LinearOperators
107 Notes
108 -----
109 The user-defined matvec() function must properly handle the case
110 where v has shape (N,) as well as the (N,1) case. The shape of
111 the return type is handled internally by LinearOperator.
113 LinearOperator instances can also be multiplied, added with each
114 other and exponentiated, all lazily: the result of these operations
115 is always a new, composite LinearOperator, that defers linear
116 operations to the original operators and combines the results.
118 More details regarding how to subclass a LinearOperator and several
119 examples of concrete LinearOperator instances can be found in the
120 external project `PyLops <https://pylops.readthedocs.io>`_.
123 Examples
124 --------
125 >>> import numpy as np
126 >>> from scipy.sparse.linalg import LinearOperator
127 >>> def mv(v):
128 ... return np.array([2*v[0], 3*v[1]])
129 ...
130 >>> A = LinearOperator((2,2), matvec=mv)
131 >>> A
132 <2x2 _CustomLinearOperator with dtype=float64>
133 >>> A.matvec(np.ones(2))
134 array([ 2., 3.])
135 >>> A * np.ones(2)
136 array([ 2., 3.])
138 """
140 ndim = 2
141 # Necessary for right matmul with numpy arrays.
142 __array_ufunc__ = None
144 def __new__(cls, *args, **kwargs):
145 if cls is LinearOperator:
146 # Operate as _CustomLinearOperator factory.
147 return super().__new__(_CustomLinearOperator)
148 else:
149 obj = super().__new__(cls)
151 if (type(obj)._matvec == LinearOperator._matvec
152 and type(obj)._matmat == LinearOperator._matmat):
153 warnings.warn("LinearOperator subclass should implement"
154 " at least one of _matvec and _matmat.",
155 category=RuntimeWarning, stacklevel=2)
157 return obj
159 def __init__(self, dtype, shape):
160 """Initialize this LinearOperator.
162 To be called by subclasses. ``dtype`` may be None; ``shape`` should
163 be convertible to a length-2 tuple.
164 """
165 if dtype is not None:
166 dtype = np.dtype(dtype)
168 shape = tuple(shape)
169 if not isshape(shape):
170 raise ValueError(f"invalid shape {shape!r} (must be 2-d)")
172 self.dtype = dtype
173 self.shape = shape
175 def _init_dtype(self):
176 """Called from subclasses at the end of the __init__ routine.
177 """
178 if self.dtype is None:
179 v = np.zeros(self.shape[-1])
180 self.dtype = np.asarray(self.matvec(v)).dtype
182 def _matmat(self, X):
183 """Default matrix-matrix multiplication handler.
185 Falls back on the user-defined _matvec method, so defining that will
186 define matrix multiplication (though in a very suboptimal way).
187 """
189 return np.hstack([self.matvec(col.reshape(-1,1)) for col in X.T])
191 def _matvec(self, x):
192 """Default matrix-vector multiplication handler.
194 If self is a linear operator of shape (M, N), then this method will
195 be called on a shape (N,) or (N, 1) ndarray, and should return a
196 shape (M,) or (M, 1) ndarray.
198 This default implementation falls back on _matmat, so defining that
199 will define matrix-vector multiplication as well.
200 """
201 return self.matmat(x.reshape(-1, 1))
203 def matvec(self, x):
204 """Matrix-vector multiplication.
206 Performs the operation y=A*x where A is an MxN linear
207 operator and x is a column vector or 1-d array.
209 Parameters
210 ----------
211 x : {matrix, ndarray}
212 An array with shape (N,) or (N,1).
214 Returns
215 -------
216 y : {matrix, ndarray}
217 A matrix or ndarray with shape (M,) or (M,1) depending
218 on the type and shape of the x argument.
220 Notes
221 -----
222 This matvec wraps the user-specified matvec routine or overridden
223 _matvec method to ensure that y has the correct shape and type.
225 """
227 x = np.asanyarray(x)
229 M,N = self.shape
231 if x.shape != (N,) and x.shape != (N,1):
232 raise ValueError('dimension mismatch')
234 y = self._matvec(x)
236 if isinstance(x, np.matrix):
237 y = asmatrix(y)
238 else:
239 y = np.asarray(y)
241 if x.ndim == 1:
242 y = y.reshape(M)
243 elif x.ndim == 2:
244 y = y.reshape(M,1)
245 else:
246 raise ValueError('invalid shape returned by user-defined matvec()')
248 return y
250 def rmatvec(self, x):
251 """Adjoint matrix-vector multiplication.
253 Performs the operation y = A^H * x where A is an MxN linear
254 operator and x is a column vector or 1-d array.
256 Parameters
257 ----------
258 x : {matrix, ndarray}
259 An array with shape (M,) or (M,1).
261 Returns
262 -------
263 y : {matrix, ndarray}
264 A matrix or ndarray with shape (N,) or (N,1) depending
265 on the type and shape of the x argument.
267 Notes
268 -----
269 This rmatvec wraps the user-specified rmatvec routine or overridden
270 _rmatvec method to ensure that y has the correct shape and type.
272 """
274 x = np.asanyarray(x)
276 M,N = self.shape
278 if x.shape != (M,) and x.shape != (M,1):
279 raise ValueError('dimension mismatch')
281 y = self._rmatvec(x)
283 if isinstance(x, np.matrix):
284 y = asmatrix(y)
285 else:
286 y = np.asarray(y)
288 if x.ndim == 1:
289 y = y.reshape(N)
290 elif x.ndim == 2:
291 y = y.reshape(N,1)
292 else:
293 raise ValueError('invalid shape returned by user-defined rmatvec()')
295 return y
297 def _rmatvec(self, x):
298 """Default implementation of _rmatvec; defers to adjoint."""
299 if type(self)._adjoint == LinearOperator._adjoint:
300 # _adjoint not overridden, prevent infinite recursion
301 raise NotImplementedError
302 else:
303 return self.H.matvec(x)
305 def matmat(self, X):
306 """Matrix-matrix multiplication.
308 Performs the operation y=A*X where A is an MxN linear
309 operator and X dense N*K matrix or ndarray.
311 Parameters
312 ----------
313 X : {matrix, ndarray}
314 An array with shape (N,K).
316 Returns
317 -------
318 Y : {matrix, ndarray}
319 A matrix or ndarray with shape (M,K) depending on
320 the type of the X argument.
322 Notes
323 -----
324 This matmat wraps any user-specified matmat routine or overridden
325 _matmat method to ensure that y has the correct type.
327 """
328 if not (issparse(X) or is_pydata_spmatrix(X)):
329 X = np.asanyarray(X)
331 if X.ndim != 2:
332 raise ValueError(f'expected 2-d ndarray or matrix, not {X.ndim}-d')
334 if X.shape[0] != self.shape[1]:
335 raise ValueError(f'dimension mismatch: {self.shape}, {X.shape}')
337 try:
338 Y = self._matmat(X)
339 except Exception as e:
340 if issparse(X) or is_pydata_spmatrix(X):
341 raise TypeError(
342 "Unable to multiply a LinearOperator with a sparse matrix."
343 " Wrap the matrix in aslinearoperator first."
344 ) from e
345 raise
347 if isinstance(Y, np.matrix):
348 Y = asmatrix(Y)
350 return Y
352 def rmatmat(self, X):
353 """Adjoint matrix-matrix multiplication.
355 Performs the operation y = A^H * x where A is an MxN linear
356 operator and x is a column vector or 1-d array, or 2-d array.
357 The default implementation defers to the adjoint.
359 Parameters
360 ----------
361 X : {matrix, ndarray}
362 A matrix or 2D array.
364 Returns
365 -------
366 Y : {matrix, ndarray}
367 A matrix or 2D array depending on the type of the input.
369 Notes
370 -----
371 This rmatmat wraps the user-specified rmatmat routine.
373 """
374 if not (issparse(X) or is_pydata_spmatrix(X)):
375 X = np.asanyarray(X)
377 if X.ndim != 2:
378 raise ValueError('expected 2-d ndarray or matrix, not %d-d'
379 % X.ndim)
381 if X.shape[0] != self.shape[0]:
382 raise ValueError(f'dimension mismatch: {self.shape}, {X.shape}')
384 try:
385 Y = self._rmatmat(X)
386 except Exception as e:
387 if issparse(X) or is_pydata_spmatrix(X):
388 raise TypeError(
389 "Unable to multiply a LinearOperator with a sparse matrix."
390 " Wrap the matrix in aslinearoperator() first."
391 ) from e
392 raise
394 if isinstance(Y, np.matrix):
395 Y = asmatrix(Y)
396 return Y
398 def _rmatmat(self, X):
399 """Default implementation of _rmatmat defers to rmatvec or adjoint."""
400 if type(self)._adjoint == LinearOperator._adjoint:
401 return np.hstack([self.rmatvec(col.reshape(-1, 1)) for col in X.T])
402 else:
403 return self.H.matmat(X)
405 def __call__(self, x):
406 return self*x
408 def __mul__(self, x):
409 return self.dot(x)
411 def __truediv__(self, other):
412 if not np.isscalar(other):
413 raise ValueError("Can only divide a linear operator by a scalar.")
415 return _ScaledLinearOperator(self, 1.0/other)
417 def dot(self, x):
418 """Matrix-matrix or matrix-vector multiplication.
420 Parameters
421 ----------
422 x : array_like
423 1-d or 2-d array, representing a vector or matrix.
425 Returns
426 -------
427 Ax : array
428 1-d or 2-d array (depending on the shape of x) that represents
429 the result of applying this linear operator on x.
431 """
432 if isinstance(x, LinearOperator):
433 return _ProductLinearOperator(self, x)
434 elif np.isscalar(x):
435 return _ScaledLinearOperator(self, x)
436 else:
437 if not issparse(x) and not is_pydata_spmatrix(x):
438 # Sparse matrices shouldn't be converted to numpy arrays.
439 x = np.asarray(x)
441 if x.ndim == 1 or x.ndim == 2 and x.shape[1] == 1:
442 return self.matvec(x)
443 elif x.ndim == 2:
444 return self.matmat(x)
445 else:
446 raise ValueError('expected 1-d or 2-d array or matrix, got %r'
447 % x)
449 def __matmul__(self, other):
450 if np.isscalar(other):
451 raise ValueError("Scalar operands are not allowed, "
452 "use '*' instead")
453 return self.__mul__(other)
455 def __rmatmul__(self, other):
456 if np.isscalar(other):
457 raise ValueError("Scalar operands are not allowed, "
458 "use '*' instead")
459 return self.__rmul__(other)
461 def __rmul__(self, x):
462 if np.isscalar(x):
463 return _ScaledLinearOperator(self, x)
464 else:
465 return self._rdot(x)
467 def _rdot(self, x):
468 """Matrix-matrix or matrix-vector multiplication from the right.
470 Parameters
471 ----------
472 x : array_like
473 1-d or 2-d array, representing a vector or matrix.
475 Returns
476 -------
477 xA : array
478 1-d or 2-d array (depending on the shape of x) that represents
479 the result of applying this linear operator on x from the right.
481 Notes
482 -----
483 This is copied from dot to implement right multiplication.
484 """
485 if isinstance(x, LinearOperator):
486 return _ProductLinearOperator(x, self)
487 elif np.isscalar(x):
488 return _ScaledLinearOperator(self, x)
489 else:
490 if not issparse(x) and not is_pydata_spmatrix(x):
491 # Sparse matrices shouldn't be converted to numpy arrays.
492 x = np.asarray(x)
494 # We use transpose instead of rmatvec/rmatmat to avoid
495 # unnecessary complex conjugation if possible.
496 if x.ndim == 1 or x.ndim == 2 and x.shape[0] == 1:
497 return self.T.matvec(x.T).T
498 elif x.ndim == 2:
499 return self.T.matmat(x.T).T
500 else:
501 raise ValueError('expected 1-d or 2-d array or matrix, got %r'
502 % x)
504 def __pow__(self, p):
505 if np.isscalar(p):
506 return _PowerLinearOperator(self, p)
507 else:
508 return NotImplemented
510 def __add__(self, x):
511 if isinstance(x, LinearOperator):
512 return _SumLinearOperator(self, x)
513 else:
514 return NotImplemented
516 def __neg__(self):
517 return _ScaledLinearOperator(self, -1)
519 def __sub__(self, x):
520 return self.__add__(-x)
522 def __repr__(self):
523 M,N = self.shape
524 if self.dtype is None:
525 dt = 'unspecified dtype'
526 else:
527 dt = 'dtype=' + str(self.dtype)
529 return '<%dx%d %s with %s>' % (M, N, self.__class__.__name__, dt)
531 def adjoint(self):
532 """Hermitian adjoint.
534 Returns the Hermitian adjoint of self, aka the Hermitian
535 conjugate or Hermitian transpose. For a complex matrix, the
536 Hermitian adjoint is equal to the conjugate transpose.
538 Can be abbreviated self.H instead of self.adjoint().
540 Returns
541 -------
542 A_H : LinearOperator
543 Hermitian adjoint of self.
544 """
545 return self._adjoint()
547 H = property(adjoint)
549 def transpose(self):
550 """Transpose this linear operator.
552 Returns a LinearOperator that represents the transpose of this one.
553 Can be abbreviated self.T instead of self.transpose().
554 """
555 return self._transpose()
557 T = property(transpose)
559 def _adjoint(self):
560 """Default implementation of _adjoint; defers to rmatvec."""
561 return _AdjointLinearOperator(self)
563 def _transpose(self):
564 """ Default implementation of _transpose; defers to rmatvec + conj"""
565 return _TransposedLinearOperator(self)
568class _CustomLinearOperator(LinearOperator):
569 """Linear operator defined in terms of user-specified operations."""
571 def __init__(self, shape, matvec, rmatvec=None, matmat=None,
572 dtype=None, rmatmat=None):
573 super().__init__(dtype, shape)
575 self.args = ()
577 self.__matvec_impl = matvec
578 self.__rmatvec_impl = rmatvec
579 self.__rmatmat_impl = rmatmat
580 self.__matmat_impl = matmat
582 self._init_dtype()
584 def _matmat(self, X):
585 if self.__matmat_impl is not None:
586 return self.__matmat_impl(X)
587 else:
588 return super()._matmat(X)
590 def _matvec(self, x):
591 return self.__matvec_impl(x)
593 def _rmatvec(self, x):
594 func = self.__rmatvec_impl
595 if func is None:
596 raise NotImplementedError("rmatvec is not defined")
597 return self.__rmatvec_impl(x)
599 def _rmatmat(self, X):
600 if self.__rmatmat_impl is not None:
601 return self.__rmatmat_impl(X)
602 else:
603 return super()._rmatmat(X)
605 def _adjoint(self):
606 return _CustomLinearOperator(shape=(self.shape[1], self.shape[0]),
607 matvec=self.__rmatvec_impl,
608 rmatvec=self.__matvec_impl,
609 matmat=self.__rmatmat_impl,
610 rmatmat=self.__matmat_impl,
611 dtype=self.dtype)
614class _AdjointLinearOperator(LinearOperator):
615 """Adjoint of arbitrary Linear Operator"""
617 def __init__(self, A):
618 shape = (A.shape[1], A.shape[0])
619 super().__init__(dtype=A.dtype, shape=shape)
620 self.A = A
621 self.args = (A,)
623 def _matvec(self, x):
624 return self.A._rmatvec(x)
626 def _rmatvec(self, x):
627 return self.A._matvec(x)
629 def _matmat(self, x):
630 return self.A._rmatmat(x)
632 def _rmatmat(self, x):
633 return self.A._matmat(x)
635class _TransposedLinearOperator(LinearOperator):
636 """Transposition of arbitrary Linear Operator"""
638 def __init__(self, A):
639 shape = (A.shape[1], A.shape[0])
640 super().__init__(dtype=A.dtype, shape=shape)
641 self.A = A
642 self.args = (A,)
644 def _matvec(self, x):
645 # NB. np.conj works also on sparse matrices
646 return np.conj(self.A._rmatvec(np.conj(x)))
648 def _rmatvec(self, x):
649 return np.conj(self.A._matvec(np.conj(x)))
651 def _matmat(self, x):
652 # NB. np.conj works also on sparse matrices
653 return np.conj(self.A._rmatmat(np.conj(x)))
655 def _rmatmat(self, x):
656 return np.conj(self.A._matmat(np.conj(x)))
658def _get_dtype(operators, dtypes=None):
659 if dtypes is None:
660 dtypes = []
661 for obj in operators:
662 if obj is not None and hasattr(obj, 'dtype'):
663 dtypes.append(obj.dtype)
664 return np.result_type(*dtypes)
667class _SumLinearOperator(LinearOperator):
668 def __init__(self, A, B):
669 if not isinstance(A, LinearOperator) or \
670 not isinstance(B, LinearOperator):
671 raise ValueError('both operands have to be a LinearOperator')
672 if A.shape != B.shape:
673 raise ValueError(f'cannot add {A} and {B}: shape mismatch')
674 self.args = (A, B)
675 super().__init__(_get_dtype([A, B]), A.shape)
677 def _matvec(self, x):
678 return self.args[0].matvec(x) + self.args[1].matvec(x)
680 def _rmatvec(self, x):
681 return self.args[0].rmatvec(x) + self.args[1].rmatvec(x)
683 def _rmatmat(self, x):
684 return self.args[0].rmatmat(x) + self.args[1].rmatmat(x)
686 def _matmat(self, x):
687 return self.args[0].matmat(x) + self.args[1].matmat(x)
689 def _adjoint(self):
690 A, B = self.args
691 return A.H + B.H
694class _ProductLinearOperator(LinearOperator):
695 def __init__(self, A, B):
696 if not isinstance(A, LinearOperator) or \
697 not isinstance(B, LinearOperator):
698 raise ValueError('both operands have to be a LinearOperator')
699 if A.shape[1] != B.shape[0]:
700 raise ValueError(f'cannot multiply {A} and {B}: shape mismatch')
701 super().__init__(_get_dtype([A, B]),
702 (A.shape[0], B.shape[1]))
703 self.args = (A, B)
705 def _matvec(self, x):
706 return self.args[0].matvec(self.args[1].matvec(x))
708 def _rmatvec(self, x):
709 return self.args[1].rmatvec(self.args[0].rmatvec(x))
711 def _rmatmat(self, x):
712 return self.args[1].rmatmat(self.args[0].rmatmat(x))
714 def _matmat(self, x):
715 return self.args[0].matmat(self.args[1].matmat(x))
717 def _adjoint(self):
718 A, B = self.args
719 return B.H * A.H
722class _ScaledLinearOperator(LinearOperator):
723 def __init__(self, A, alpha):
724 if not isinstance(A, LinearOperator):
725 raise ValueError('LinearOperator expected as A')
726 if not np.isscalar(alpha):
727 raise ValueError('scalar expected as alpha')
728 if isinstance(A, _ScaledLinearOperator):
729 A, alpha_original = A.args
730 # Avoid in-place multiplication so that we don't accidentally mutate
731 # the original prefactor.
732 alpha = alpha * alpha_original
734 dtype = _get_dtype([A], [type(alpha)])
735 super().__init__(dtype, A.shape)
736 self.args = (A, alpha)
738 def _matvec(self, x):
739 return self.args[1] * self.args[0].matvec(x)
741 def _rmatvec(self, x):
742 return np.conj(self.args[1]) * self.args[0].rmatvec(x)
744 def _rmatmat(self, x):
745 return np.conj(self.args[1]) * self.args[0].rmatmat(x)
747 def _matmat(self, x):
748 return self.args[1] * self.args[0].matmat(x)
750 def _adjoint(self):
751 A, alpha = self.args
752 return A.H * np.conj(alpha)
755class _PowerLinearOperator(LinearOperator):
756 def __init__(self, A, p):
757 if not isinstance(A, LinearOperator):
758 raise ValueError('LinearOperator expected as A')
759 if A.shape[0] != A.shape[1]:
760 raise ValueError('square LinearOperator expected, got %r' % A)
761 if not isintlike(p) or p < 0:
762 raise ValueError('non-negative integer expected as p')
764 super().__init__(_get_dtype([A]), A.shape)
765 self.args = (A, p)
767 def _power(self, fun, x):
768 res = np.array(x, copy=True)
769 for i in range(self.args[1]):
770 res = fun(res)
771 return res
773 def _matvec(self, x):
774 return self._power(self.args[0].matvec, x)
776 def _rmatvec(self, x):
777 return self._power(self.args[0].rmatvec, x)
779 def _rmatmat(self, x):
780 return self._power(self.args[0].rmatmat, x)
782 def _matmat(self, x):
783 return self._power(self.args[0].matmat, x)
785 def _adjoint(self):
786 A, p = self.args
787 return A.H ** p
790class MatrixLinearOperator(LinearOperator):
791 def __init__(self, A):
792 super().__init__(A.dtype, A.shape)
793 self.A = A
794 self.__adj = None
795 self.args = (A,)
797 def _matmat(self, X):
798 return self.A.dot(X)
800 def _adjoint(self):
801 if self.__adj is None:
802 self.__adj = _AdjointMatrixOperator(self)
803 return self.__adj
805class _AdjointMatrixOperator(MatrixLinearOperator):
806 def __init__(self, adjoint):
807 self.A = adjoint.A.T.conj()
808 self.__adjoint = adjoint
809 self.args = (adjoint,)
810 self.shape = adjoint.shape[1], adjoint.shape[0]
812 @property
813 def dtype(self):
814 return self.__adjoint.dtype
816 def _adjoint(self):
817 return self.__adjoint
820class IdentityOperator(LinearOperator):
821 def __init__(self, shape, dtype=None):
822 super().__init__(dtype, shape)
824 def _matvec(self, x):
825 return x
827 def _rmatvec(self, x):
828 return x
830 def _rmatmat(self, x):
831 return x
833 def _matmat(self, x):
834 return x
836 def _adjoint(self):
837 return self
840def aslinearoperator(A):
841 """Return A as a LinearOperator.
843 'A' may be any of the following types:
844 - ndarray
845 - matrix
846 - sparse matrix (e.g. csr_matrix, lil_matrix, etc.)
847 - LinearOperator
848 - An object with .shape and .matvec attributes
850 See the LinearOperator documentation for additional information.
852 Notes
853 -----
854 If 'A' has no .dtype attribute, the data type is determined by calling
855 :func:`LinearOperator.matvec()` - set the .dtype attribute to prevent this
856 call upon the linear operator creation.
858 Examples
859 --------
860 >>> import numpy as np
861 >>> from scipy.sparse.linalg import aslinearoperator
862 >>> M = np.array([[1,2,3],[4,5,6]], dtype=np.int32)
863 >>> aslinearoperator(M)
864 <2x3 MatrixLinearOperator with dtype=int32>
865 """
866 if isinstance(A, LinearOperator):
867 return A
869 elif isinstance(A, np.ndarray) or isinstance(A, np.matrix):
870 if A.ndim > 2:
871 raise ValueError('array must have ndim <= 2')
872 A = np.atleast_2d(np.asarray(A))
873 return MatrixLinearOperator(A)
875 elif issparse(A) or is_pydata_spmatrix(A):
876 return MatrixLinearOperator(A)
878 else:
879 if hasattr(A, 'shape') and hasattr(A, 'matvec'):
880 rmatvec = None
881 rmatmat = None
882 dtype = None
884 if hasattr(A, 'rmatvec'):
885 rmatvec = A.rmatvec
886 if hasattr(A, 'rmatmat'):
887 rmatmat = A.rmatmat
888 if hasattr(A, 'dtype'):
889 dtype = A.dtype
890 return LinearOperator(A.shape, A.matvec, rmatvec=rmatvec,
891 rmatmat=rmatmat, dtype=dtype)
893 else:
894 raise TypeError('type not understood')