Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/sparse/linalg/_interface.py: 27%
379 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-23 06:43 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-23 06:43 +0000
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 >>> from scipy.sparse.linalg._interface import LinearOperator
17 >>> class Ones(LinearOperator):
18 ... def __init__(self, shape):
19 ... super().__init__(dtype=None, shape=shape)
20 ... def _matvec(self, x):
21 ... return np.repeat(x.sum(), self.shape[0])
23Instances of this class emulate ``np.ones(shape)``, but using a constant
24amount of storage, independent of ``shape``. The ``_matvec`` method specifies
25how this linear operator multiplies with (operates on) a vector. We can now
26add this operator to a sparse matrix that stores only offsets from one::
28 >>> from scipy.sparse.linalg._interface import aslinearoperator
29 >>> from scipy.sparse import csr_matrix
30 >>> offsets = csr_matrix([[1, 0, 2], [0, -1, 0], [0, 0, 3]])
31 >>> A = aslinearoperator(offsets) + Ones(offsets.shape)
32 >>> A.dot([1, 2, 3])
33 array([13, 4, 15])
35The result is the same as that given by its dense, explicitly-stored
36counterpart::
38 >>> (np.ones(A.shape, A.dtype) + offsets.toarray()).dot([1, 2, 3])
39 array([13, 4, 15])
41Several algorithms in the ``scipy.sparse`` library are able to operate on
42``LinearOperator`` instances.
43"""
45import warnings
47import numpy as np
49from scipy.sparse import issparse
50from scipy.sparse._sputils import isshape, isintlike, asmatrix, is_pydata_spmatrix
52__all__ = ['LinearOperator', 'aslinearoperator']
55class LinearOperator:
56 """Common interface for performing matrix vector products
58 Many iterative methods (e.g. cg, gmres) do not need to know the
59 individual entries of a matrix to solve a linear system A*x=b.
60 Such solvers only require the computation of matrix vector
61 products, A*v where v is a dense vector. This class serves as
62 an abstract interface between iterative solvers and matrix-like
63 objects.
65 To construct a concrete LinearOperator, either pass appropriate
66 callables to the constructor of this class, or subclass it.
68 A subclass must implement either one of the methods ``_matvec``
69 and ``_matmat``, and the attributes/properties ``shape`` (pair of
70 integers) and ``dtype`` (may be None). It may call the ``__init__``
71 on this class to have these attributes validated. Implementing
72 ``_matvec`` automatically implements ``_matmat`` (using a naive
73 algorithm) and vice-versa.
75 Optionally, a subclass may implement ``_rmatvec`` or ``_adjoint``
76 to implement the Hermitian adjoint (conjugate transpose). As with
77 ``_matvec`` and ``_matmat``, implementing either ``_rmatvec`` or
78 ``_adjoint`` implements the other automatically. Implementing
79 ``_adjoint`` is preferable; ``_rmatvec`` is mostly there for
80 backwards compatibility.
82 Parameters
83 ----------
84 shape : tuple
85 Matrix dimensions (M, N).
86 matvec : callable f(v)
87 Returns returns A * v.
88 rmatvec : callable f(v)
89 Returns A^H * v, where A^H is the conjugate transpose of A.
90 matmat : callable f(V)
91 Returns A * V, where V is a dense matrix with dimensions (N, K).
92 dtype : dtype
93 Data type of the matrix.
94 rmatmat : callable f(V)
95 Returns A^H * V, where V is a dense matrix with dimensions (M, K).
97 Attributes
98 ----------
99 args : tuple
100 For linear operators describing products etc. of other linear
101 operators, the operands of the binary operation.
102 ndim : int
103 Number of dimensions (this is always 2)
105 See Also
106 --------
107 aslinearoperator : Construct LinearOperators
109 Notes
110 -----
111 The user-defined matvec() function must properly handle the case
112 where v has shape (N,) as well as the (N,1) case. The shape of
113 the return type is handled internally by LinearOperator.
115 LinearOperator instances can also be multiplied, added with each
116 other and exponentiated, all lazily: the result of these operations
117 is always a new, composite LinearOperator, that defers linear
118 operations to the original operators and combines the results.
120 More details regarding how to subclass a LinearOperator and several
121 examples of concrete LinearOperator instances can be found in the
122 external project `PyLops <https://pylops.readthedocs.io>`_.
125 Examples
126 --------
127 >>> import numpy as np
128 >>> from scipy.sparse.linalg import LinearOperator
129 >>> def mv(v):
130 ... return np.array([2*v[0], 3*v[1]])
131 ...
132 >>> A = LinearOperator((2,2), matvec=mv)
133 >>> A
134 <2x2 _CustomLinearOperator with dtype=float64>
135 >>> A.matvec(np.ones(2))
136 array([ 2., 3.])
137 >>> A * np.ones(2)
138 array([ 2., 3.])
140 """
142 ndim = 2
143 # Necessary for right matmul with numpy arrays.
144 __array_ufunc__ = None
146 def __new__(cls, *args, **kwargs):
147 if cls is LinearOperator:
148 # Operate as _CustomLinearOperator factory.
149 return super().__new__(_CustomLinearOperator)
150 else:
151 obj = super().__new__(cls)
153 if (type(obj)._matvec == LinearOperator._matvec
154 and type(obj)._matmat == LinearOperator._matmat):
155 warnings.warn("LinearOperator subclass should implement"
156 " at least one of _matvec and _matmat.",
157 category=RuntimeWarning, stacklevel=2)
159 return obj
161 def __init__(self, dtype, shape):
162 """Initialize this LinearOperator.
164 To be called by subclasses. ``dtype`` may be None; ``shape`` should
165 be convertible to a length-2 tuple.
166 """
167 if dtype is not None:
168 dtype = np.dtype(dtype)
170 shape = tuple(shape)
171 if not isshape(shape):
172 raise ValueError(f"invalid shape {shape!r} (must be 2-d)")
174 self.dtype = dtype
175 self.shape = shape
177 def _init_dtype(self):
178 """Called from subclasses at the end of the __init__ routine.
179 """
180 if self.dtype is None:
181 v = np.zeros(self.shape[-1])
182 self.dtype = np.asarray(self.matvec(v)).dtype
184 def _matmat(self, X):
185 """Default matrix-matrix multiplication handler.
187 Falls back on the user-defined _matvec method, so defining that will
188 define matrix multiplication (though in a very suboptimal way).
189 """
191 return np.hstack([self.matvec(col.reshape(-1,1)) for col in X.T])
193 def _matvec(self, x):
194 """Default matrix-vector multiplication handler.
196 If self is a linear operator of shape (M, N), then this method will
197 be called on a shape (N,) or (N, 1) ndarray, and should return a
198 shape (M,) or (M, 1) ndarray.
200 This default implementation falls back on _matmat, so defining that
201 will define matrix-vector multiplication as well.
202 """
203 return self.matmat(x.reshape(-1, 1))
205 def matvec(self, x):
206 """Matrix-vector multiplication.
208 Performs the operation y=A*x where A is an MxN linear
209 operator and x is a column vector or 1-d array.
211 Parameters
212 ----------
213 x : {matrix, ndarray}
214 An array with shape (N,) or (N,1).
216 Returns
217 -------
218 y : {matrix, ndarray}
219 A matrix or ndarray with shape (M,) or (M,1) depending
220 on the type and shape of the x argument.
222 Notes
223 -----
224 This matvec wraps the user-specified matvec routine or overridden
225 _matvec method to ensure that y has the correct shape and type.
227 """
229 x = np.asanyarray(x)
231 M,N = self.shape
233 if x.shape != (N,) and x.shape != (N,1):
234 raise ValueError('dimension mismatch')
236 y = self._matvec(x)
238 if isinstance(x, np.matrix):
239 y = asmatrix(y)
240 else:
241 y = np.asarray(y)
243 if x.ndim == 1:
244 y = y.reshape(M)
245 elif x.ndim == 2:
246 y = y.reshape(M,1)
247 else:
248 raise ValueError('invalid shape returned by user-defined matvec()')
250 return y
252 def rmatvec(self, x):
253 """Adjoint matrix-vector multiplication.
255 Performs the operation y = A^H * x where A is an MxN linear
256 operator and x is a column vector or 1-d array.
258 Parameters
259 ----------
260 x : {matrix, ndarray}
261 An array with shape (M,) or (M,1).
263 Returns
264 -------
265 y : {matrix, ndarray}
266 A matrix or ndarray with shape (N,) or (N,1) depending
267 on the type and shape of the x argument.
269 Notes
270 -----
271 This rmatvec wraps the user-specified rmatvec routine or overridden
272 _rmatvec method to ensure that y has the correct shape and type.
274 """
276 x = np.asanyarray(x)
278 M,N = self.shape
280 if x.shape != (M,) and x.shape != (M,1):
281 raise ValueError('dimension mismatch')
283 y = self._rmatvec(x)
285 if isinstance(x, np.matrix):
286 y = asmatrix(y)
287 else:
288 y = np.asarray(y)
290 if x.ndim == 1:
291 y = y.reshape(N)
292 elif x.ndim == 2:
293 y = y.reshape(N,1)
294 else:
295 raise ValueError('invalid shape returned by user-defined rmatvec()')
297 return y
299 def _rmatvec(self, x):
300 """Default implementation of _rmatvec; defers to adjoint."""
301 if type(self)._adjoint == LinearOperator._adjoint:
302 # _adjoint not overridden, prevent infinite recursion
303 raise NotImplementedError
304 else:
305 return self.H.matvec(x)
307 def matmat(self, X):
308 """Matrix-matrix multiplication.
310 Performs the operation y=A*X where A is an MxN linear
311 operator and X dense N*K matrix or ndarray.
313 Parameters
314 ----------
315 X : {matrix, ndarray}
316 An array with shape (N,K).
318 Returns
319 -------
320 Y : {matrix, ndarray}
321 A matrix or ndarray with shape (M,K) depending on
322 the type of the X argument.
324 Notes
325 -----
326 This matmat wraps any user-specified matmat routine or overridden
327 _matmat method to ensure that y has the correct type.
329 """
330 if not (issparse(X) or is_pydata_spmatrix(X)):
331 X = np.asanyarray(X)
333 if X.ndim != 2:
334 raise ValueError(f'expected 2-d ndarray or matrix, not {X.ndim}-d')
336 if X.shape[0] != self.shape[1]:
337 raise ValueError(f'dimension mismatch: {self.shape}, {X.shape}')
339 try:
340 Y = self._matmat(X)
341 except Exception as e:
342 if issparse(X) or is_pydata_spmatrix(X):
343 raise TypeError(
344 "Unable to multiply a LinearOperator with a sparse matrix."
345 " Wrap the matrix in aslinearoperator first."
346 ) from e
347 raise
349 if isinstance(Y, np.matrix):
350 Y = asmatrix(Y)
352 return Y
354 def rmatmat(self, X):
355 """Adjoint matrix-matrix multiplication.
357 Performs the operation y = A^H * x where A is an MxN linear
358 operator and x is a column vector or 1-d array, or 2-d array.
359 The default implementation defers to the adjoint.
361 Parameters
362 ----------
363 X : {matrix, ndarray}
364 A matrix or 2D array.
366 Returns
367 -------
368 Y : {matrix, ndarray}
369 A matrix or 2D array depending on the type of the input.
371 Notes
372 -----
373 This rmatmat wraps the user-specified rmatmat routine.
375 """
376 if not (issparse(X) or is_pydata_spmatrix(X)):
377 X = np.asanyarray(X)
379 if X.ndim != 2:
380 raise ValueError('expected 2-d ndarray or matrix, not %d-d'
381 % X.ndim)
383 if X.shape[0] != self.shape[0]:
384 raise ValueError(f'dimension mismatch: {self.shape}, {X.shape}')
386 try:
387 Y = self._rmatmat(X)
388 except Exception as e:
389 if issparse(X) or is_pydata_spmatrix(X):
390 raise TypeError(
391 "Unable to multiply a LinearOperator with a sparse matrix."
392 " Wrap the matrix in aslinearoperator() first."
393 ) from e
394 raise
396 if isinstance(Y, np.matrix):
397 Y = asmatrix(Y)
398 return Y
400 def _rmatmat(self, X):
401 """Default implementation of _rmatmat defers to rmatvec or adjoint."""
402 if type(self)._adjoint == LinearOperator._adjoint:
403 return np.hstack([self.rmatvec(col.reshape(-1, 1)) for col in X.T])
404 else:
405 return self.H.matmat(X)
407 def __call__(self, x):
408 return self*x
410 def __mul__(self, x):
411 return self.dot(x)
413 def __truediv__(self, other):
414 if not np.isscalar(other):
415 raise ValueError("Can only divide a linear operator by a scalar.")
417 return _ScaledLinearOperator(self, 1.0/other)
419 def dot(self, x):
420 """Matrix-matrix or matrix-vector multiplication.
422 Parameters
423 ----------
424 x : array_like
425 1-d or 2-d array, representing a vector or matrix.
427 Returns
428 -------
429 Ax : array
430 1-d or 2-d array (depending on the shape of x) that represents
431 the result of applying this linear operator on x.
433 """
434 if isinstance(x, LinearOperator):
435 return _ProductLinearOperator(self, x)
436 elif np.isscalar(x):
437 return _ScaledLinearOperator(self, x)
438 else:
439 if not issparse(x) and not is_pydata_spmatrix(x):
440 # Sparse matrices shouldn't be converted to numpy arrays.
441 x = np.asarray(x)
443 if x.ndim == 1 or x.ndim == 2 and x.shape[1] == 1:
444 return self.matvec(x)
445 elif x.ndim == 2:
446 return self.matmat(x)
447 else:
448 raise ValueError('expected 1-d or 2-d array or matrix, got %r'
449 % x)
451 def __matmul__(self, other):
452 if np.isscalar(other):
453 raise ValueError("Scalar operands are not allowed, "
454 "use '*' instead")
455 return self.__mul__(other)
457 def __rmatmul__(self, other):
458 if np.isscalar(other):
459 raise ValueError("Scalar operands are not allowed, "
460 "use '*' instead")
461 return self.__rmul__(other)
463 def __rmul__(self, x):
464 if np.isscalar(x):
465 return _ScaledLinearOperator(self, x)
466 else:
467 return self._rdot(x)
469 def _rdot(self, x):
470 """Matrix-matrix or matrix-vector multiplication from the right.
472 Parameters
473 ----------
474 x : array_like
475 1-d or 2-d array, representing a vector or matrix.
477 Returns
478 -------
479 xA : array
480 1-d or 2-d array (depending on the shape of x) that represents
481 the result of applying this linear operator on x from the right.
483 Notes
484 -----
485 This is copied from dot to implement right multiplication.
486 """
487 if isinstance(x, LinearOperator):
488 return _ProductLinearOperator(x, self)
489 elif np.isscalar(x):
490 return _ScaledLinearOperator(self, x)
491 else:
492 if not issparse(x) and not is_pydata_spmatrix(x):
493 # Sparse matrices shouldn't be converted to numpy arrays.
494 x = np.asarray(x)
496 # We use transpose instead of rmatvec/rmatmat to avoid
497 # unnecessary complex conjugation if possible.
498 if x.ndim == 1 or x.ndim == 2 and x.shape[0] == 1:
499 return self.T.matvec(x.T).T
500 elif x.ndim == 2:
501 return self.T.matmat(x.T).T
502 else:
503 raise ValueError('expected 1-d or 2-d array or matrix, got %r'
504 % x)
506 def __pow__(self, p):
507 if np.isscalar(p):
508 return _PowerLinearOperator(self, p)
509 else:
510 return NotImplemented
512 def __add__(self, x):
513 if isinstance(x, LinearOperator):
514 return _SumLinearOperator(self, x)
515 else:
516 return NotImplemented
518 def __neg__(self):
519 return _ScaledLinearOperator(self, -1)
521 def __sub__(self, x):
522 return self.__add__(-x)
524 def __repr__(self):
525 M,N = self.shape
526 if self.dtype is None:
527 dt = 'unspecified dtype'
528 else:
529 dt = 'dtype=' + str(self.dtype)
531 return '<%dx%d %s with %s>' % (M, N, self.__class__.__name__, dt)
533 def adjoint(self):
534 """Hermitian adjoint.
536 Returns the Hermitian adjoint of self, aka the Hermitian
537 conjugate or Hermitian transpose. For a complex matrix, the
538 Hermitian adjoint is equal to the conjugate transpose.
540 Can be abbreviated self.H instead of self.adjoint().
542 Returns
543 -------
544 A_H : LinearOperator
545 Hermitian adjoint of self.
546 """
547 return self._adjoint()
549 H = property(adjoint)
551 def transpose(self):
552 """Transpose this linear operator.
554 Returns a LinearOperator that represents the transpose of this one.
555 Can be abbreviated self.T instead of self.transpose().
556 """
557 return self._transpose()
559 T = property(transpose)
561 def _adjoint(self):
562 """Default implementation of _adjoint; defers to rmatvec."""
563 return _AdjointLinearOperator(self)
565 def _transpose(self):
566 """ Default implementation of _transpose; defers to rmatvec + conj"""
567 return _TransposedLinearOperator(self)
570class _CustomLinearOperator(LinearOperator):
571 """Linear operator defined in terms of user-specified operations."""
573 def __init__(self, shape, matvec, rmatvec=None, matmat=None,
574 dtype=None, rmatmat=None):
575 super().__init__(dtype, shape)
577 self.args = ()
579 self.__matvec_impl = matvec
580 self.__rmatvec_impl = rmatvec
581 self.__rmatmat_impl = rmatmat
582 self.__matmat_impl = matmat
584 self._init_dtype()
586 def _matmat(self, X):
587 if self.__matmat_impl is not None:
588 return self.__matmat_impl(X)
589 else:
590 return super()._matmat(X)
592 def _matvec(self, x):
593 return self.__matvec_impl(x)
595 def _rmatvec(self, x):
596 func = self.__rmatvec_impl
597 if func is None:
598 raise NotImplementedError("rmatvec is not defined")
599 return self.__rmatvec_impl(x)
601 def _rmatmat(self, X):
602 if self.__rmatmat_impl is not None:
603 return self.__rmatmat_impl(X)
604 else:
605 return super()._rmatmat(X)
607 def _adjoint(self):
608 return _CustomLinearOperator(shape=(self.shape[1], self.shape[0]),
609 matvec=self.__rmatvec_impl,
610 rmatvec=self.__matvec_impl,
611 matmat=self.__rmatmat_impl,
612 rmatmat=self.__matmat_impl,
613 dtype=self.dtype)
616class _AdjointLinearOperator(LinearOperator):
617 """Adjoint of arbitrary Linear Operator"""
619 def __init__(self, A):
620 shape = (A.shape[1], A.shape[0])
621 super().__init__(dtype=A.dtype, shape=shape)
622 self.A = A
623 self.args = (A,)
625 def _matvec(self, x):
626 return self.A._rmatvec(x)
628 def _rmatvec(self, x):
629 return self.A._matvec(x)
631 def _matmat(self, x):
632 return self.A._rmatmat(x)
634 def _rmatmat(self, x):
635 return self.A._matmat(x)
637class _TransposedLinearOperator(LinearOperator):
638 """Transposition of arbitrary Linear Operator"""
640 def __init__(self, A):
641 shape = (A.shape[1], A.shape[0])
642 super().__init__(dtype=A.dtype, shape=shape)
643 self.A = A
644 self.args = (A,)
646 def _matvec(self, x):
647 # NB. np.conj works also on sparse matrices
648 return np.conj(self.A._rmatvec(np.conj(x)))
650 def _rmatvec(self, x):
651 return np.conj(self.A._matvec(np.conj(x)))
653 def _matmat(self, x):
654 # NB. np.conj works also on sparse matrices
655 return np.conj(self.A._rmatmat(np.conj(x)))
657 def _rmatmat(self, x):
658 return np.conj(self.A._matmat(np.conj(x)))
660def _get_dtype(operators, dtypes=None):
661 if dtypes is None:
662 dtypes = []
663 for obj in operators:
664 if obj is not None and hasattr(obj, 'dtype'):
665 dtypes.append(obj.dtype)
666 return np.result_type(*dtypes)
669class _SumLinearOperator(LinearOperator):
670 def __init__(self, A, B):
671 if not isinstance(A, LinearOperator) or \
672 not isinstance(B, LinearOperator):
673 raise ValueError('both operands have to be a LinearOperator')
674 if A.shape != B.shape:
675 raise ValueError(f'cannot add {A} and {B}: shape mismatch')
676 self.args = (A, B)
677 super().__init__(_get_dtype([A, B]), A.shape)
679 def _matvec(self, x):
680 return self.args[0].matvec(x) + self.args[1].matvec(x)
682 def _rmatvec(self, x):
683 return self.args[0].rmatvec(x) + self.args[1].rmatvec(x)
685 def _rmatmat(self, x):
686 return self.args[0].rmatmat(x) + self.args[1].rmatmat(x)
688 def _matmat(self, x):
689 return self.args[0].matmat(x) + self.args[1].matmat(x)
691 def _adjoint(self):
692 A, B = self.args
693 return A.H + B.H
696class _ProductLinearOperator(LinearOperator):
697 def __init__(self, A, B):
698 if not isinstance(A, LinearOperator) or \
699 not isinstance(B, LinearOperator):
700 raise ValueError('both operands have to be a LinearOperator')
701 if A.shape[1] != B.shape[0]:
702 raise ValueError(f'cannot multiply {A} and {B}: shape mismatch')
703 super().__init__(_get_dtype([A, B]),
704 (A.shape[0], B.shape[1]))
705 self.args = (A, B)
707 def _matvec(self, x):
708 return self.args[0].matvec(self.args[1].matvec(x))
710 def _rmatvec(self, x):
711 return self.args[1].rmatvec(self.args[0].rmatvec(x))
713 def _rmatmat(self, x):
714 return self.args[1].rmatmat(self.args[0].rmatmat(x))
716 def _matmat(self, x):
717 return self.args[0].matmat(self.args[1].matmat(x))
719 def _adjoint(self):
720 A, B = self.args
721 return B.H * A.H
724class _ScaledLinearOperator(LinearOperator):
725 def __init__(self, A, alpha):
726 if not isinstance(A, LinearOperator):
727 raise ValueError('LinearOperator expected as A')
728 if not np.isscalar(alpha):
729 raise ValueError('scalar expected as alpha')
730 if isinstance(A, _ScaledLinearOperator):
731 A, alpha_original = A.args
732 # Avoid in-place multiplication so that we don't accidentally mutate
733 # the original prefactor.
734 alpha = alpha * alpha_original
736 dtype = _get_dtype([A], [type(alpha)])
737 super().__init__(dtype, A.shape)
738 self.args = (A, alpha)
740 def _matvec(self, x):
741 return self.args[1] * self.args[0].matvec(x)
743 def _rmatvec(self, x):
744 return np.conj(self.args[1]) * self.args[0].rmatvec(x)
746 def _rmatmat(self, x):
747 return np.conj(self.args[1]) * self.args[0].rmatmat(x)
749 def _matmat(self, x):
750 return self.args[1] * self.args[0].matmat(x)
752 def _adjoint(self):
753 A, alpha = self.args
754 return A.H * np.conj(alpha)
757class _PowerLinearOperator(LinearOperator):
758 def __init__(self, A, p):
759 if not isinstance(A, LinearOperator):
760 raise ValueError('LinearOperator expected as A')
761 if A.shape[0] != A.shape[1]:
762 raise ValueError('square LinearOperator expected, got %r' % A)
763 if not isintlike(p) or p < 0:
764 raise ValueError('non-negative integer expected as p')
766 super().__init__(_get_dtype([A]), A.shape)
767 self.args = (A, p)
769 def _power(self, fun, x):
770 res = np.array(x, copy=True)
771 for i in range(self.args[1]):
772 res = fun(res)
773 return res
775 def _matvec(self, x):
776 return self._power(self.args[0].matvec, x)
778 def _rmatvec(self, x):
779 return self._power(self.args[0].rmatvec, x)
781 def _rmatmat(self, x):
782 return self._power(self.args[0].rmatmat, x)
784 def _matmat(self, x):
785 return self._power(self.args[0].matmat, x)
787 def _adjoint(self):
788 A, p = self.args
789 return A.H ** p
792class MatrixLinearOperator(LinearOperator):
793 def __init__(self, A):
794 super().__init__(A.dtype, A.shape)
795 self.A = A
796 self.__adj = None
797 self.args = (A,)
799 def _matmat(self, X):
800 return self.A.dot(X)
802 def _adjoint(self):
803 if self.__adj is None:
804 self.__adj = _AdjointMatrixOperator(self)
805 return self.__adj
807class _AdjointMatrixOperator(MatrixLinearOperator):
808 def __init__(self, adjoint):
809 self.A = adjoint.A.T.conj()
810 self.__adjoint = adjoint
811 self.args = (adjoint,)
812 self.shape = adjoint.shape[1], adjoint.shape[0]
814 @property
815 def dtype(self):
816 return self.__adjoint.dtype
818 def _adjoint(self):
819 return self.__adjoint
822class IdentityOperator(LinearOperator):
823 def __init__(self, shape, dtype=None):
824 super().__init__(dtype, shape)
826 def _matvec(self, x):
827 return x
829 def _rmatvec(self, x):
830 return x
832 def _rmatmat(self, x):
833 return x
835 def _matmat(self, x):
836 return x
838 def _adjoint(self):
839 return self
842def aslinearoperator(A):
843 """Return A as a LinearOperator.
845 'A' may be any of the following types:
846 - ndarray
847 - matrix
848 - sparse matrix (e.g. csr_matrix, lil_matrix, etc.)
849 - LinearOperator
850 - An object with .shape and .matvec attributes
852 See the LinearOperator documentation for additional information.
854 Notes
855 -----
856 If 'A' has no .dtype attribute, the data type is determined by calling
857 :func:`LinearOperator.matvec()` - set the .dtype attribute to prevent this
858 call upon the linear operator creation.
860 Examples
861 --------
862 >>> import numpy as np
863 >>> from scipy.sparse.linalg import aslinearoperator
864 >>> M = np.array([[1,2,3],[4,5,6]], dtype=np.int32)
865 >>> aslinearoperator(M)
866 <2x3 MatrixLinearOperator with dtype=int32>
867 """
868 if isinstance(A, LinearOperator):
869 return A
871 elif isinstance(A, np.ndarray) or isinstance(A, np.matrix):
872 if A.ndim > 2:
873 raise ValueError('array must have ndim <= 2')
874 A = np.atleast_2d(np.asarray(A))
875 return MatrixLinearOperator(A)
877 elif issparse(A) or is_pydata_spmatrix(A):
878 return MatrixLinearOperator(A)
880 else:
881 if hasattr(A, 'shape') and hasattr(A, 'matvec'):
882 rmatvec = None
883 rmatmat = None
884 dtype = None
886 if hasattr(A, 'rmatvec'):
887 rmatvec = A.rmatvec
888 if hasattr(A, 'rmatmat'):
889 rmatmat = A.rmatmat
890 if hasattr(A, 'dtype'):
891 dtype = A.dtype
892 return LinearOperator(A.shape, A.matvec, rmatvec=rmatvec,
893 rmatmat=rmatmat, dtype=dtype)
895 else:
896 raise TypeError('type not understood')