def _solve_P_Q(U, V, structure=None): """ A helper function for expm_2009. Parameters ---------- U : ndarray Pade numerator. V : ndarray Pade denominator. structure : str, optional A string describing the structure of both matrices `U` and `V`. Only `upper_triangular` is currently supported. Notes ----- The `structure` argument is inspired by similar args for theano and cvxopt functions. """ P = U + V Q = -U + V if isspmatrix(U): return spsolve(Q, P) elif structure is None: return solve(Q, P) elif structure == UPPER_TRIANGULAR: return solve_triangular(Q, P) else: raise ValueError('unsupported matrix structure: ' + str(structure))
def _count_nonzero(A): # A compatibility function which should eventually disappear. #XXX There should be a better way to do this when A is sparse # in the traditional sense. if isspmatrix(A): return np.sum(A.toarray() != 0) else: return np.sum(A != 0)
def _is_upper_triangular(A): # This function could possibly be of wider interest. if isspmatrix(A): lower_part = scipy.sparse.tril(A, -1) # Check structural upper triangularity, # then coincidental upper triangularity if needed. return lower_part.nnz == 0 or lower_part.count_nonzero() == 0 else: return not np.tril(A, -1).any()
def _solve_P_Q(U, V): """ A helper function for expm_2009. """ P = U + V Q = -U + V if isspmatrix(U): R = spsolve(Q, P) else: R = solve(Q, P) return R
def _smart_matrix_product(A, B, alpha=None, structure=None): """ A matrix product that knows about sparse and structured matrices. Parameters ---------- A : 2d ndarray First matrix. B : 2d ndarray Second matrix. alpha : float The matrix product will be scaled by this constant. structure : str, optional A string describing the structure of both matrices `A` and `B`. Only `upper_triangular` is currently supported. Returns ------- M : 2d ndarray Matrix product of A and B. """ if len(A.shape) != 2: raise ValueError('expected A to be a rectangular matrix') if len(B.shape) != 2: raise ValueError('expected B to be a rectangular matrix') f = None if structure == UPPER_TRIANGULAR: if (not isspmatrix(A) and not isspmatrix(B) and not is_pydata_spmatrix(A) and not is_pydata_spmatrix(B)): f, = scipy.linalg.get_blas_funcs(('trmm', ), (A, B)) if f is not None: if alpha is None: alpha = 1. out = f(alpha, A, B) else: if alpha is None: out = A.dot(B) else: out = alpha * A.dot(B) return out
def _is_upper_triangular(A): # This function could possibly be of wider interest. if isspmatrix(A): lower_part = scipy.sparse.tril(A, -1) if lower_part.nnz == 0: # structural upper triangularity return True else: # coincidental upper triangularity return _count_nonzero(lower_part) == 0 else: return _count_nonzero(np.tril(A, -1)) == 0
def _smart_matrix_product(A, B, alpha=None, structure=None): """ A matrix product that knows about sparse and structured matrices. Parameters ---------- A : 2d ndarray First matrix. B : 2d ndarray Second matrix. alpha : float The matrix product will be scaled by this constant. structure : str, optional A string describing the structure of both matrices `A` and `B`. Only `upper_triangular` is currently supported. Returns ------- M : 2d ndarray Matrix product of A and B. """ if len(A.shape) != 2: raise ValueError('expected A to be a rectangular matrix') if len(B.shape) != 2: raise ValueError('expected B to be a rectangular matrix') f = None if structure == UPPER_TRIANGULAR: if not isspmatrix(A) and not isspmatrix(B): f, = scipy.linalg.get_blas_funcs(('trmm',), (A, B)) if f is not None: if alpha is None: alpha = 1. out = f(alpha, A, B) else: if alpha is None: out = A.dot(B) else: out = alpha * A.dot(B) return out
def _is_upper_triangular(A): # This function could possibly be of wider interest. if isspmatrix(A): lower_part = scipy.sparse.tril(A, -1) # Check structural upper triangularity, # then coincidental upper triangularity if needed. return lower_part.nnz == 0 or lower_part.count_nonzero() == 0 elif is_pydata_spmatrix(A): import sparse lower_part = sparse.tril(A, -1) return lower_part.nnz == 0 else: return not np.tril(A, -1).any()
def __sub__(self,other): # First check if argument is a scalar if isscalarlike(other): if other == 0: return self.copy() else: # Now we would add this scalar to every element. raise NotImplementedError('adding a nonzero scalar to a ' 'sparse matrix is not supported') elif isspmatrix(other): if (other.shape != self.shape): raise ValueError("inconsistent shapes") return self._binopt(other,'_minus_') elif isdense(other): # Convert this matrix to a dense matrix and subtract them return self.todense() - other else: return NotImplemented
def __setitem__(self, index, x): # Process arrays from IndexMixin i, j = self._unpack_index(index) i, j = self._index_to_arrays(i, j) if isspmatrix(x): x = x.toarray() # Make x and i into the same shape x = np.asarray(x, dtype=self.dtype) x, _ = np.broadcast_arrays(x, i) if x.shape != i.shape: raise ValueError("shape mismatch in assignment") if np.size(x) == 0: return i, j = self._swap((i.ravel(), j.ravel())) self._set_many(i, j, x.ravel())
def __eq__(self, other): # Scalar other. if isscalarlike(other): if np.isnan(other): return self.__class__(self.shape, dtype=np.bool_) if other == 0: warn( "Comparing a sparse matrix with 0 using == is inefficient" ", try using != instead.", SparseEfficiencyWarning, stacklevel=3) all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) inv = self._scalar_binopt(other, operator.ne) return all_true - inv else: return self._scalar_binopt(other, operator.eq) # Dense other. elif isdense(other): return self.todense() == other # Pydata sparse other. elif is_pydata_spmatrix(other): return NotImplemented # Sparse other. elif isspmatrix(other): warn( "Comparing sparse matrices using == is inefficient, try using" " != instead.", SparseEfficiencyWarning, stacklevel=3) # TODO sparse broadcasting if self.shape != other.shape: return False elif self.format != other.format: other = other.asformat(self.format) res = self._binopt(other, '_ne_') all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) return all_true - res else: return False
def __init__(self, matrix, impl, exponent=2.0, dist_using_inner=False, **kwargs): """Initialize a new instance. Parameters ---------- matrix : ``scipy.sparse.spmatrix`` or `array-like`, 2-dim. Square weighting matrix of the inner product impl : `str` Specifier for the implementation backend exponent : positive `float`, optional Exponent of the norm. For values other than 2.0, the inner product is not defined. If ``matrix`` is a sparse matrix, only 1.0, 2.0 and ``inf`` are allowed. dist_using_inner : `bool`, optional Calculate `dist` using the following formula:: ||x - y||^2 = ||x||^2 + ||y||^2 - 2 * Re <x, y> This avoids the creation of new arrays and is thus faster for large arrays. On the downside, it will not evaluate to exactly zero for equal (but not identical) ``x`` and ``y``. This option can only be used if ``exponent`` is 2.0. precomp_mat_pow : `bool`, optional If `True`, precompute the matrix power ``W ** (1/p)`` during initialization. This has no effect if ``exponent`` is 1.0, 2.0 or ``inf``. Default: `False` cache_mat_pow : `bool`, optional If `True`, cache the matrix power ``W ** (1/p)``. This can happen either during initialization or in the first call to ``norm`` or ``dist``, resp. This has no effect if ``exponent`` is 1.0, 2.0 or ``inf``. Default: `True` cache_mat_decomp : `bool`, optional If `True`, cache the eigenbasis decomposition of the matrix. This can happen either during initialization or in the first call to ``norm`` or ``dist``, resp. This has no effect if ``exponent`` is 1.0, 2.0 or ``inf``. Default: `False` Notes ----- The matrix power ``W ** (1/p)`` is computed by eigenbasis decomposition:: eigval, eigvec = scipy.linalg.eigh(matrix) mat_pow = (eigval ** p * eigvec).dot(eigvec.conj().T) Depending on the matrix size, this can be rather expensive. """ precomp_mat_pow = kwargs.pop('precomp_mat_pow', False) self._cache_mat_pow = bool(kwargs.pop('cache_mat_pow', True)) self._cache_mat_decomp = bool(kwargs.pop('cache_mat_decomp', False)) super().__init__(impl=impl, exponent=exponent, dist_using_inner=dist_using_inner) # Check and set matrix if isspmatrix(matrix): self._matrix = matrix else: self._matrix = np.asarray(matrix) if self._matrix.dtype == object: raise ValueError('invalid matrix {}'.format(matrix)) elif self._matrix.ndim != 2: raise ValueError('matrix {} is {}-dimensional instead of ' '2-dimensional' ''.format(matrix, self._matrix.ndim)) if self._matrix.shape[0] != self._matrix.shape[1]: raise ValueError('matrix has shape {}, expected a square matrix' ''.format(self._matrix.shape)) if (self.matrix_issparse and self.exponent not in (1.0, 2.0, float('inf'))): raise NotImplementedError('sparse matrices only supported for ' 'exponent 1.0, 2.0 or `inf`') # Compute the power and decomposition if desired self._eigval = self._eigvec = None if self.exponent in (1.0, float('inf')): self._mat_pow = self.matrix elif precomp_mat_pow and self.exponent != 2.0: eigval, eigvec = self.matrix_decomp() if self._cache_mat_decomp: self._eigval, self._eigvec = eigval, eigvec eigval_pow = eigval**(1.0 / self.exponent) else: eigval_pow = eigval eigval_pow **= 1.0 / self.exponent self._mat_pow = (eigval_pow * eigvec).dot(eigvec.conj().T) else: self._mat_pow = None
def matrix_issparse(self): """Whether the representing matrix is sparse or not.""" return isspmatrix(self.matrix)
def cs_graph_components(x): """ Determine connected components of a graph stored as a compressed sparse row or column matrix. For speed reasons, the symmetry of the matrix x is not checked. A nonzero at index `(i, j)` means that node `i` is connected to node `j` by an edge. The number of rows/columns of the matrix thus corresponds to the number of nodes in the graph. Parameters ----------- x : array_like or sparse matrix, 2 dimensions The adjacency matrix of the graph. Only the upper triangular part is used. Returns -------- n_comp : int The number of connected components. label : ndarray (ints, 1 dimension): The label array of each connected component (-2 is used to indicate empty rows in the matrix: 0 everywhere, including diagonal). This array has the length of the number of nodes, i.e. one label for each node of the graph. Nodes having the same label belong to the same connected component. Notes ------ The matrix is assumed to be symmetric and the upper triangular part of the matrix is used. The matrix is converted to a CSR matrix unless it is already a CSR. Examples -------- >>> from scipy.sparse.csgraph import connected_components >>> D = np.eye(4) >>> D[0,1] = D[1,0] = 1 >>> cs_graph_components(D) (3, array([0, 0, 1, 2])) >>> from scipy.sparse import dok_matrix >>> cs_graph_components(dok_matrix(D)) (3, array([0, 0, 1, 2])) """ try: shape = x.shape except AttributeError: raise ValueError(_msg0) if not ((len(x.shape) == 2) and (x.shape[0] == x.shape[1])): raise ValueError(_msg1 % x.shape) if isspmatrix(x): x = x.tocsr() else: x = csr_matrix(x) label = np.empty((shape[0],), dtype=x.indptr.dtype) n_comp = _cs_graph_components(shape[0], x.indptr, x.indices, label) return n_comp, label
def __init__(self, arg1, shape=None, dtype=None, copy=False): _data_matrix.__init__(self) if isspmatrix(arg1): if arg1.format == self.format and copy: arg1 = arg1.copy() else: arg1 = arg1.asformat(self.format) self._set_self(arg1) elif isinstance(arg1, tuple): if isshape(arg1): # It's a tuple of matrix dimensions (M, N) # create empty matrix self._shape = check_shape(arg1) M, N = self.shape # Select index dtype large enough to pass array and # scalar parameters to sparsetools idx_dtype = get_index_dtype(maxval=max(M, N)) self.data = np.zeros(0, getdtype(dtype, default=float)) self.indices = np.zeros(0, idx_dtype) self.indptr = np.zeros(self._swap((M, N))[0] + 1, dtype=idx_dtype) else: if len(arg1) == 2: # (data, ij) format from scipy.sparse.coo import coo_matrix other = self.__class__(coo_matrix(arg1, shape=shape)) self._set_self(other) elif len(arg1) == 3: # (data, indices, indptr) format (data, indices, indptr) = arg1 # Select index dtype large enough to pass array and # scalar parameters to sparsetools maxval = None if shape is not None: maxval = max(shape) idx_dtype = get_index_dtype((indices, indptr), maxval=maxval, check_contents=True) self.indices = np.array(indices, copy=copy, dtype=idx_dtype) self.indptr = np.array(indptr, copy=copy, dtype=idx_dtype) self.data = np.array(data, copy=copy, dtype=dtype) else: raise ValueError("unrecognized {}_matrix " "constructor usage".format(self.format)) else: # must be dense try: arg1 = np.asarray(arg1) except Exception: raise ValueError("unrecognized {}_matrix constructor usage" "".format(self.format)) from scipy.sparse.coo import coo_matrix self._set_self(self.__class__(coo_matrix(arg1, dtype=dtype))) # Read matrix dimensions given, if any if shape is not None: self._shape = check_shape(shape) else: if self.shape is None: # shape not already set, try to infer dimensions try: major_dim = len(self.indptr) - 1 minor_dim = self.indices.max() + 1 except Exception: raise ValueError('unable to infer matrix dimensions') else: self._shape = check_shape( self._swap((major_dim, minor_dim))) if dtype is not None: self.data = self.data.astype(dtype, copy=False) self.check_format(full_check=False)
def _expm(A, use_exact_onenorm): # Core of expm, separated to allow testing exact and approximate # algorithms. # Avoid indiscriminate asarray() to allow sparse or other strange arrays. if isinstance(A, (list, tuple)): A = np.asarray(A) if len(A.shape) != 2 or A.shape[0] != A.shape[1]: raise ValueError('expected a square matrix') # Trivial case if A.shape == (1, 1): out = [[np.exp(A[0, 0])]] # Avoid indiscriminate casting to ndarray to # allow for sparse or other strange arrays if isspmatrix(A): return A.__class__(out) return np.array(out) # Detect upper triangularity. structure = UPPER_TRIANGULAR if _is_upper_triangular(A) else None if use_exact_onenorm == "auto": # Hardcode a matrix order threshold for exact vs. estimated one-norms. use_exact_onenorm = A.shape[0] < 200 # Track functions of A to help compute the matrix exponential. h = _ExpmPadeHelper(A, structure=structure, use_exact_onenorm=use_exact_onenorm) # Try Pade order 3. eta_1 = max(h.d4_loose, h.d6_loose) if eta_1 < 1.495585217958292e-002 and _ell(h.A, 3) == 0: U, V = h.pade3() return _solve_P_Q(U, V, structure=structure) # Try Pade order 5. eta_2 = max(h.d4_tight, h.d6_loose) if eta_2 < 2.539398330063230e-001 and _ell(h.A, 5) == 0: U, V = h.pade5() return _solve_P_Q(U, V, structure=structure) # Try Pade orders 7 and 9. eta_3 = max(h.d6_tight, h.d8_loose) if eta_3 < 9.504178996162932e-001 and _ell(h.A, 7) == 0: U, V = h.pade7() return _solve_P_Q(U, V, structure=structure) if eta_3 < 2.097847961257068e+000 and _ell(h.A, 9) == 0: U, V = h.pade9() return _solve_P_Q(U, V, structure=structure) # Use Pade order 13. eta_4 = max(h.d8_loose, h.d10_loose) eta_5 = min(eta_3, eta_4) theta_13 = 4.25 # Choose smallest s>=0 such that 2**(-s) eta_5 <= theta_13 if eta_5 == 0: # Nilpotent special case s = 0 else: s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) s = s + _ell(2**-s * h.A, 13) U, V = h.pade13_scaled(s) X = _solve_P_Q(U, V, structure=structure) if structure == UPPER_TRIANGULAR: # Invoke Code Fragment 2.1. X = _fragment_2_1(X, h.A, s) else: # X = r_13(A)^(2^s) by repeated squaring. for i in range(s): X = X.dot(X) return X
def multiply(self, other): """Point-wise multiplication by another matrix, vector, or scalar. """ # print("multiply") # Scalar multiplication. if isscalarlike(other): return self._mul_scalar(other) # Sparse matrix or vector. if isspmatrix(other): if self.shape == other.shape: other = self.__class__(other) return self._binopt(other, '_elmul_') # Single element. elif other.shape == (1, 1): return self._mul_scalar(other.toarray()[0, 0]) elif self.shape == (1, 1): return other._mul_scalar(self.toarray()[0, 0]) # A row times a column. elif self.shape[1] == 1 and other.shape[0] == 1: return self._mul_sparse_matrix(other.tocsc()) elif self.shape[0] == 1 and other.shape[1] == 1: return other._mul_sparse_matrix(self.tocsc()) # Row vector times matrix. other is a row. elif other.shape[0] == 1 and self.shape[1] == other.shape[1]: other = dia_matrix((other.toarray().ravel(), [0]), shape=(other.shape[1], other.shape[1])) return self._mul_sparse_matrix(other) # self is a row. elif self.shape[0] == 1 and self.shape[1] == other.shape[1]: copy = dia_matrix((self.toarray().ravel(), [0]), shape=(self.shape[1], self.shape[1])) return other._mul_sparse_matrix(copy) # Column vector times matrix. other is a column. elif other.shape[1] == 1 and self.shape[0] == other.shape[0]: other = dia_matrix((other.toarray().ravel(), [0]), shape=(other.shape[0], other.shape[0])) return other._mul_sparse_matrix(self) # self is a column. elif self.shape[1] == 1 and self.shape[0] == other.shape[0]: copy = dia_matrix((self.toarray().ravel(), [0]), shape=(self.shape[0], self.shape[0])) return copy._mul_sparse_matrix(other) else: raise ValueError("inconsistent shapes") # Assume other is a dense matrix/array, which produces a single-item # object array if other isn't convertible to ndarray. other = np.atleast_2d(other) if other.ndim != 2: return np.multiply(self.toarray(), other) # Single element / wrapped object. if other.size == 1: return self._mul_scalar(other.flat[0]) # Fast case for trivial sparse matrix. elif self.shape == (1, 1): return np.multiply(self.toarray()[0, 0], other) from scipy.sparse.coo import coo_matrix ret = self.tocoo() # Matching shapes. if self.shape == other.shape: data = np.multiply(ret.data, other[ret.row, ret.col]) # Sparse row vector times... elif self.shape[0] == 1: if other.shape[1] == 1: # Dense column vector. data = np.multiply(ret.data, other) elif other.shape[1] == self.shape[1]: # Dense matrix. data = np.multiply(ret.data, other[:, ret.col]) else: raise ValueError("inconsistent shapes") row = np.repeat(np.arange(other.shape[0]), len(ret.row)) col = np.tile(ret.col, other.shape[0]) return coo_matrix((data.view(np.ndarray).ravel(), (row, col)), shape=(other.shape[0], self.shape[1]), copy=False) # Sparse column vector times... elif self.shape[1] == 1: if other.shape[0] == 1: # Dense row vector. data = np.multiply(ret.data[:, None], other) elif other.shape[0] == self.shape[0]: # Dense matrix. data = np.multiply(ret.data[:, None], other[ret.row]) else: raise ValueError("inconsistent shapes") row = np.repeat(ret.row, other.shape[1]) col = np.tile(np.arange(other.shape[1]), len(ret.col)) return coo_matrix((data.view(np.ndarray).ravel(), (row, col)), shape=(self.shape[0], other.shape[1]), copy=False) # Sparse matrix times dense row vector. elif other.shape[0] == 1 and self.shape[1] == other.shape[1]: data = np.multiply(ret.data, other[:, ret.col].ravel()) # Sparse matrix times dense column vector. elif other.shape[1] == 1 and self.shape[0] == other.shape[0]: data = np.multiply(ret.data, other[ret.row].ravel()) else: raise ValueError("inconsistent shapes") ret.data = data.view(np.ndarray).ravel() return ret
def cs_graph_components(x): """ Determine connected components of a graph stored as a compressed sparse row or column matrix. For speed reasons, the symmetry of the matrix x is not checked. A nonzero at index `(i, j)` means that node `i` is connected to node `j` by an edge. The number of rows/columns of the matrix thus corresponds to the number of nodes in the graph. Parameters ----------- x : array_like or sparse matrix, 2 dimensions The adjacency matrix of the graph. Only the upper triangular part is used. Returns -------- n_comp : int The number of connected components. label : ndarray (ints, 1 dimension): The label array of each connected component (-2 is used to indicate empty rows in the matrix: 0 everywhere, including diagonal). This array has the length of the number of nodes, i.e. one label for each node of the graph. Nodes having the same label belong to the same connected component. Notes ------ The matrix is assumed to be symmetric and the upper triangular part of the matrix is used. The matrix is converted to a CSR matrix unless it is already a CSR. Examples -------- >>> from scipy.sparse.csgraph import connected_components >>> D = np.eye(4) >>> D[0,1] = D[1,0] = 1 >>> cs_graph_components(D) (3, array([0, 0, 1, 2])) >>> from scipy.sparse import dok_matrix >>> cs_graph_components(dok_matrix(D)) (3, array([0, 0, 1, 2])) """ try: shape = x.shape except AttributeError: raise ValueError(_msg0) if not ((len(x.shape) == 2) and (x.shape[0] == x.shape[1])): raise ValueError(_msg1 % x.shape) if isspmatrix(x): x = x.tocsr() else: x = csr_matrix(x) label = np.empty((shape[0], ), dtype=x.indptr.dtype) n_comp = _cs_graph_components(shape[0], x.indptr, x.indices, label) return n_comp, label
def expm(A, t=None ): """Compute the matrix exponential - exp(t*A) - using Padé approximation. Parameters ---------- A : array or sparse matrix, shape(M,M) 2D Array or Matrix (sparse or dense) to be exponentiated t : array, shape(1) Exponent multiplier. Default=1. Returns ------- exp(A*t) : array, shape(M,M) Matrix exponential of A References ---------- Roger B. Sidje, "EXPOKIT: A Software Package for Computing Matrix Exponentials", ACM Trans. Math. Softw. 24(1), 130-156 (1998). http://www.maths.uq.edu.au/expokit/ """ # dgpadm does an internal check for square matrices, # so assume ldh == m # constant input variables if hasattr( A, 'shape' ): m = A.shape[0] #ldh = A.shape[1] dtype = A.dtype else: m = len(A) #ldh = len(A[0]) dtype = type( A[0][0] ) #if t is None: # t = np.array([1.])[0] # output integers needed to get result: iexph = np.array([0]) ns = np.array([0]) iflag = np.array([0]) if isspmatrix(A): # Sparse matrix routines. matvec = lambda v : A.dot(v) # Please check usage of LinearOperator:- Av = LinearOperator( (m, m), matvec=matvec) itrace = np.array([0]) tol = np.array([1e-7]) v = np.ones(m, dtype=dtype) anorm = np.linalg.norm(A.todense(), np.inf) if dtype in ('float64', 'float32', float,): # See src/expokit.f for documentation w, wsp = dgexpv(v, tol, anorm, Av.matvec, itrace, iflag, t=t) elif dtype in ('complex128', 'complex64', complex): w, wsp = zgexpv(v * 1 + 1j, tol, anorm, Av.matvec, itrace, iflag, t=t) #print('\nA {0}: {1}'.format(A.shape, type(A))) #print('w {0}: {1}'.format(w.shape, type(w))) return np.reshape(w, (m,m), order='F') #return wsp[m*(m+1)+m+(m+2)**2:] #return np.reshape(wsp[m*(m+1)+m+(m+2)**2:], (m,m), order='F') else: iexph = np.array([0]) ns = np.array([0]) if dtype in ('float64', 'float32', float,): wsp = dgpadm(A, iexph, ns, iflag, t) elif dtype in ('complex128', 'complex64', complex): wsp = zgpadm(A, iexph, ns, iflag, t=t) if iflag[0] != 0: raise IOError("Expokit error ({0}) in routine {1}".format(iflag[0])) return np.reshape(wsp[iexph[0]-1 : iexph[0] + m * m - 1], (m,m), order='F')
def _expm(A, use_exact_onenorm): # Core of expm, separated to allow testing exact and approximate # algorithms. # Avoid indiscriminate asarray() to allow sparse or other strange arrays. if isinstance(A, (list, tuple)): A = np.asarray(A) if len(A.shape) != 2 or A.shape[0] != A.shape[1]: raise ValueError('expected a square matrix') # Trivial case if A.shape == (1, 1): out = [[np.exp(A[0, 0])]] # Avoid indiscriminate casting to ndarray to # allow for sparse or other strange arrays if isspmatrix(A): return A.__class__(out) return np.array(out) # Detect upper triangularity. structure = UPPER_TRIANGULAR if _is_upper_triangular(A) else None if use_exact_onenorm == "auto": # Hardcode a matrix order threshold for exact vs. estimated one-norms. use_exact_onenorm = A.shape[0] < 200 # Track functions of A to help compute the matrix exponential. h = _ExpmPadeHelper( A, structure=structure, use_exact_onenorm=use_exact_onenorm) # Try Pade order 3. eta_1 = max(h.d4_loose, h.d6_loose) if eta_1 < 1.495585217958292e-002 and _ell(h.A, 3) == 0: U, V = h.pade3() return _solve_P_Q(U, V, structure=structure) # Try Pade order 5. eta_2 = max(h.d4_tight, h.d6_loose) if eta_2 < 2.539398330063230e-001 and _ell(h.A, 5) == 0: U, V = h.pade5() return _solve_P_Q(U, V, structure=structure) # Try Pade orders 7 and 9. eta_3 = max(h.d6_tight, h.d8_loose) if eta_3 < 9.504178996162932e-001 and _ell(h.A, 7) == 0: U, V = h.pade7() return _solve_P_Q(U, V, structure=structure) if eta_3 < 2.097847961257068e+000 and _ell(h.A, 9) == 0: U, V = h.pade9() return _solve_P_Q(U, V, structure=structure) # Use Pade order 13. eta_4 = max(h.d8_loose, h.d10_loose) eta_5 = min(eta_3, eta_4) theta_13 = 4.25 # Choose smallest s>=0 such that 2**(-s) eta_5 <= theta_13 if eta_5 == 0: # Nilpotent special case s = 0 else: s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) s = s + _ell(2**-s * h.A, 13) U, V = h.pade13_scaled(s) X = _solve_P_Q(U, V, structure=structure) if structure == UPPER_TRIANGULAR: # Invoke Code Fragment 2.1. X = _fragment_2_1(X, h.A, s) else: # X = r_13(A)^(2^s) by repeated squaring. for i in range(s): X = X.dot(X) return X
def expm(A): """ Compute the matrix exponential using Pade approximation. .. versionadded:: 0.12.0 Parameters ---------- A : (M,M) array or sparse matrix 2D Array or Matrix (sparse or dense) to be exponentiated Returns ------- expA : (M,M) ndarray Matrix exponential of `A` References ---------- N. J. Higham, "The Scaling and Squaring Method for the Matrix Exponential Revisited", SIAM. J. Matrix Anal. & Appl. 26, 1179 (2005). """ n_squarings = 0 Aissparse = isspmatrix(A) if Aissparse: A_L1 = max(abs(A).sum(axis=0).flat) ident = speye(A.shape[0], A.shape[1], dtype=A.dtype, format=A.format) else: A = asarray(A) A_L1 = norm(A, 1) ident = eye(A.shape[0], A.shape[1], dtype=A.dtype) if A.dtype == 'float64' or A.dtype == 'complex128': if A_L1 < 1.495585217958292e-002: U, V = _pade3(A, ident) elif A_L1 < 2.539398330063230e-001: U, V = _pade5(A, ident) elif A_L1 < 9.504178996162932e-001: U, V = _pade7(A, ident) elif A_L1 < 2.097847961257068e+000: U, V = _pade9(A, ident) else: maxnorm = 5.371920351148152 n_squarings = max(0, int(ceil(log2(A_L1 / maxnorm)))) A = A / 2**n_squarings U, V = _pade13(A, ident) elif A.dtype == 'float32' or A.dtype == 'complex64': if A_L1 < 4.258730016922831e-001: U, V = _pade3(A, ident) elif A_L1 < 1.880152677804762e+000: U, V = _pade5(A, ident) else: maxnorm = 3.925724783138660 n_squarings = max(0, int(ceil(log2(A_L1 / maxnorm)))) A = A / 2**n_squarings U, V = _pade7(A, ident) else: raise ValueError("invalid type: " + str(A.dtype)) P = U + V # p_m(A) : numerator Q = -U + V # q_m(A) : denominator if Aissparse: from scipy.sparse.linalg import spsolve R = spsolve(Q, P) else: R = solve(Q, P) # squaring step to undo scaling for i in range(n_squarings): R = R.dot(R) return R
def __init__(self, matrix, impl, exponent=2.0, dist_using_inner=False, **kwargs): """Initialize a new instance. Parameters ---------- matrix : ``scipy.sparse.spmatrix`` or `array-like`, 2-dim. Square weighting matrix of the inner product impl : `str` Specifier for the implementation backend exponent : positive `float`, optional Exponent of the norm. For values other than 2.0, the inner product is not defined. If ``matrix`` is a sparse matrix, only 1.0, 2.0 and ``inf`` are allowed. dist_using_inner : `bool`, optional Calculate `dist` using the following formula:: ||x - y||^2 = ||x||^2 + ||y||^2 - 2 * Re <x, y> This avoids the creation of new arrays and is thus faster for large arrays. On the downside, it will not evaluate to exactly zero for equal (but not identical) ``x`` and ``y``. This option can only be used if ``exponent`` is 2.0. precomp_mat_pow : `bool`, optional If `True`, precompute the matrix power ``W ** (1/p)`` during initialization. This has no effect if ``exponent`` is 1.0, 2.0 or ``inf``. Default: `False` cache_mat_pow : `bool`, optional If `True`, cache the matrix power ``W ** (1/p)``. This can happen either during initialization or in the first call to ``norm`` or ``dist``, resp. This has no effect if ``exponent`` is 1.0, 2.0 or ``inf``. Default: `True` cache_mat_decomp : `bool`, optional If `True`, cache the eigenbasis decomposition of the matrix. This can happen either during initialization or in the first call to ``norm`` or ``dist``, resp. This has no effect if ``exponent`` is 1.0, 2.0 or ``inf``. Default: `False` Notes ----- The matrix power ``W ** (1/p)`` is computed by eigenbasis decomposition:: eigval, eigvec = scipy.linalg.eigh(matrix) mat_pow = (eigval ** p * eigvec).dot(eigvec.conj().T) Depending on the matrix size, this can be rather expensive. """ precomp_mat_pow = kwargs.pop('precomp_mat_pow', False) self._cache_mat_pow = bool(kwargs.pop('cache_mat_pow', True)) self._cache_mat_decomp = bool(kwargs.pop('cache_mat_decomp', False)) super().__init__(impl=impl, exponent=exponent, dist_using_inner=dist_using_inner) # Check and set matrix if isspmatrix(matrix): self._matrix = matrix else: self._matrix = np.asarray(matrix) if self._matrix.dtype == object: raise ValueError('invalid matrix {}.'.format(matrix)) elif self._matrix.ndim != 2: raise ValueError('matrix {} is {}-dimensional instead of ' '2-dimensional.' ''.format(matrix, self._matrix.ndim)) if self._matrix.shape[0] != self._matrix.shape[1]: raise ValueError('matrix has shape {}, expected a square matrix.' ''.format(self._matrix.shape)) if (self.matrix_issparse and self.exponent not in (1.0, 2.0, float('inf'))): raise NotImplementedError('sparse matrices only supported for ' 'exponent 1.0, 2.0 or `inf`.') # Compute the power and decomposition if desired self._eigval = self._eigvec = None if self.exponent in (1.0, float('inf')): self._mat_pow = self.matrix elif precomp_mat_pow and self.exponent != 2.0: eigval, eigvec = self.matrix_decomp() if self._cache_mat_decomp: self._eigval, self._eigvec = eigval, eigvec eigval_pow = eigval ** (1.0 / self.exponent) else: eigval_pow = eigval eigval_pow **= 1.0 / self.exponent self._mat_pow = (eigval_pow * eigvec).dot(eigvec.conj().T) else: self._mat_pow = None
def expm(A): """Compute the matrix exponential using Pade approximation. .. versionadded:: 0.12.0 Parameters ---------- A : array or sparse matrix, shape(M,M) 2D Array or Matrix (sparse or dense) to be exponentiated Returns ------- expA : array, shape(M,M) Matrix exponential of A References ---------- N. J. Higham, "The Scaling and Squaring Method for the Matrix Exponential Revisited", SIAM. J. Matrix Anal. & Appl. 26, 1179 (2005). """ n_squarings = 0 Aissparse = isspmatrix(A) if Aissparse: A_L1 = max(abs(A).sum(axis=0).flat) ident = speye(A.shape[0], A.shape[1], dtype=A.dtype, format=A.format) else: A = asarray(A) A_L1 = norm(A,1) ident = eye(A.shape[0], A.shape[1], dtype=A.dtype) if A.dtype == 'float64' or A.dtype == 'complex128': if A_L1 < 1.495585217958292e-002: U,V = _pade3(A, ident) elif A_L1 < 2.539398330063230e-001: U,V = _pade5(A, ident) elif A_L1 < 9.504178996162932e-001: U,V = _pade7(A, ident) elif A_L1 < 2.097847961257068e+000: U,V = _pade9(A, ident) else: maxnorm = 5.371920351148152 n_squarings = max(0, int(ceil(log2(A_L1 / maxnorm)))) A = A / 2**n_squarings U,V = _pade13(A, ident) elif A.dtype == 'float32' or A.dtype == 'complex64': if A_L1 < 4.258730016922831e-001: U,V = _pade3(A, ident) elif A_L1 < 1.880152677804762e+000: U,V = _pade5(A, ident) else: maxnorm = 3.925724783138660 n_squarings = max(0, int(ceil(log2(A_L1 / maxnorm)))) A = A / 2**n_squarings U,V = _pade7(A, ident) else: raise ValueError("invalid type: "+str(A.dtype)) P = U + V # p_m(A) : numerator Q = -U + V # q_m(A) : denominator if Aissparse: from scipy.sparse.linalg import spsolve R = spsolve(Q, P) else: R = solve(Q,P) # squaring step to undo scaling for i in range(n_squarings): R = R.dot(R) return R
def __init__(self, arg1, shape=None, dtype=None, copy=False): _data_matrix.__init__(self) if isspmatrix(arg1): if arg1.format == self.format and copy: arg1 = arg1.copy() else: arg1 = arg1.asformat(self.format) self._set_self(arg1) elif isinstance(arg1, tuple): if isshape(arg1): # It's a tuple of matrix dimensions (M, N) # create empty matrix self.shape = arg1 # spmatrix checks for errors here M, N = self.shape idx_dtype = get_index_dtype(maxval=self._swap((M,N))[1]) self.data = da.zeros(0, getdtype(dtype, default=float)) self.indices = da.zeros(0, idx_dtype) self.indptr = da.zeros(self._swap((M,N))[0] + 1, dtype=idx_dtype) else: if len(arg1) == 2: # (data, ij) format from .coo import coo_matrix other = self.__class__(coo_matrix(arg1, shape=shape)) self._set_self(other) elif len(arg1) == 3: # (data, indices, indptr) format (data, indices, indptr) = arg1 idx_dtype = get_index_dtype((indices, indptr), check_contents=True) chunks = (10,) self.indices = da.from_array(indices, chunks=chunks) self.indptr = da.from_array(indptr, chunks=chunks) self.data = da.from_array(data, chunks=chunks) else: raise ValueError("unrecognized %s_matrix constructor usage" % self.format) else: # must be dense try: arg1 = np.asarray(arg1) except: raise ValueError("unrecognized %s_matrix constructor usage" % self.format) from scipy.sparse.coo import coo_matrix self._set_self(self.__class__(coo_matrix(arg1, dtype=dtype))) # Read matrix dimensions given, if any if shape is not None: self.shape = shape # spmatrix will check for errors else: if self.shape is None: # shape not already set, try to infer dimensions try: major_dim = len(self.indptr) - 1 minor_dim = self.indices.max() + 1 except: raise ValueError('unable to infer matrix dimensions') else: self.shape = self._swap((major_dim,minor_dim)) if dtype is not None: self.data = np.asarray(self.data, dtype=dtype) self.check_format(full_check=False)
def __init__(self, arg1, shape=None, dtype=None, copy=False): _data_matrix.__init__(self) self.chunks = (10, 1) if isinstance(arg1, tuple): if isshape(arg1): M, N = arg1 self.shape = (M, N) idx_dtype = get_index_dtype(maxval=max(M, N)) self.row = np.array([], dtype=idx_dtype) self.col = np.array([], dtype=idx_dtype) self.data = np.array([], getdtype(dtype, default=float)) self.has_canonical_format = True else: try: obj, (row, col) = arg1 except (TypeError, ValueError): raise TypeError('invalid input format') if shape is None: if len(row) == 0 or len(col) == 0: raise ValueError('cannot infer dimensions from zero ' 'sized index arrays') M = np.max(row) + 1 N = np.max(col) + 1 self.shape = (M, N) else: # Use 2 steps to ensure shape has length 2. M, N = shape self.shape = (M, N) idx_dtype = get_index_dtype(maxval=max(self.shape)) if isinstance(row, da.core.Array): self.row = row else: self.row = da.from_array(row, chunks=self.chunks) if isinstance(col, da.core.Array): self.col = col else: self.col = da.from_array(col, chunks=self.chunks) if isinstance(obj, da.core.Array): self.data = obj else: self.data = da.from_array(obj, chunks=self.chunks) self.has_canonical_format = False else: if isspmatrix(arg1): if isspmatrix_coo(arg1) and copy: self.row = arg1.row.copy() self.col = arg1.col.copy() self.data = arg1.data.copy() self.shape = arg1.shape else: coo = arg1.tocoo() self.row = coo.row self.col = coo.col self.data = coo.data self.shape = coo.shape self.has_canonical_format = False else: #dense argument M = np.atleast_2d(np.asarray(arg1)) if M.ndim != 2: raise TypeError('expected dimension <= 2 array or matrix') else: self.shape = M.shape self.row, self.col = M.nonzero() self.data = M[self.row, self.col] self.has_canonical_format = True if dtype is not None: self.data = self.data.astype(dtype) self._check()