pyerrors.linalg
1import autograd.numpy as anp # Thinly-wrapped numpy 2import numpy as np 3 4from .obs import CObs, Obs, derived_observable, import_jackknife 5 6 7def matmul(*operands): 8 """Matrix multiply all operands. 9 10 Parameters 11 ---------- 12 operands : numpy.ndarray 13 Arbitrary number of 2d-numpy arrays which can be real or complex 14 Obs valued. 15 16 This implementation is faster compared to standard multiplication via the @ operator. 17 """ 18 if any(isinstance(o[0, 0], CObs) for o in operands): 19 extended_operands = [] 20 for op in operands: 21 tmp = np.vectorize(lambda x: (np.real(x), np.imag(x)))(op) 22 extended_operands.append(tmp[0]) 23 extended_operands.append(tmp[1]) 24 25 def multi_dot(operands, part): 26 stack_r = operands[0] 27 stack_i = operands[1] 28 for op_r, op_i in zip(operands[2::2], operands[3::2], strict=True): 29 tmp_r = stack_r @ op_r - stack_i @ op_i 30 tmp_i = stack_r @ op_i + stack_i @ op_r 31 32 stack_r = tmp_r 33 stack_i = tmp_i 34 35 if part == 'Real': 36 return stack_r 37 else: 38 return stack_i 39 40 def multi_dot_r(operands): 41 return multi_dot(operands, 'Real') 42 43 def multi_dot_i(operands): 44 return multi_dot(operands, 'Imag') 45 46 Nr = derived_observable(multi_dot_r, extended_operands, array_mode=True) 47 Ni = derived_observable(multi_dot_i, extended_operands, array_mode=True) 48 49 res = np.empty_like(Nr) 50 for (n, m), _entry in np.ndenumerate(Nr): 51 res[n, m] = CObs(Nr[n, m], Ni[n, m]) 52 53 return res 54 else: 55 def multi_dot(operands): 56 stack = operands[0] 57 for op in operands[1:]: 58 stack = stack @ op 59 return stack 60 return derived_observable(multi_dot, operands, array_mode=True) 61 62 63def jack_matmul(*operands): 64 """Matrix multiply both operands making use of the jackknife approximation. 65 66 Parameters 67 ---------- 68 operands : numpy.ndarray 69 Arbitrary number of 2d-numpy arrays which can be real or complex 70 Obs valued. 71 72 For large matrices this is considerably faster compared to matmul. 73 """ 74 75 def _exp_to_jack(matrix): 76 base_matrix = np.empty_like(matrix) 77 for index, entry in np.ndenumerate(matrix): 78 base_matrix[index] = entry.export_jackknife() 79 return base_matrix 80 81 def _imp_from_jack(matrix, name, idl): 82 base_matrix = np.empty_like(matrix) 83 for index, entry in np.ndenumerate(matrix): 84 base_matrix[index] = import_jackknife(entry, name, [idl]) 85 return base_matrix 86 87 def _exp_to_jack_c(matrix): 88 base_matrix = np.empty_like(matrix) 89 for index, entry in np.ndenumerate(matrix): 90 base_matrix[index] = entry.real.export_jackknife() + 1j * entry.imag.export_jackknife() 91 return base_matrix 92 93 def _imp_from_jack_c(matrix, name, idl): 94 base_matrix = np.empty_like(matrix) 95 for index, entry in np.ndenumerate(matrix): 96 base_matrix[index] = CObs(import_jackknife(entry.real, name, [idl]), 97 import_jackknife(entry.imag, name, [idl])) 98 return base_matrix 99 100 if any(isinstance(o.flat[0], CObs) for o in operands): 101 name = operands[0].flat[0].real.names[0] 102 idl = operands[0].flat[0].real.idl[name] 103 104 r = _exp_to_jack_c(operands[0]) 105 for op in operands[1:]: 106 if isinstance(op.flat[0], CObs): 107 r = r @ _exp_to_jack_c(op) 108 else: 109 r = r @ op 110 return _imp_from_jack_c(r, name, idl) 111 else: 112 name = operands[0].flat[0].names[0] 113 idl = operands[0].flat[0].idl[name] 114 115 r = _exp_to_jack(operands[0]) 116 for op in operands[1:]: 117 if isinstance(op.flat[0], Obs): 118 r = r @ _exp_to_jack(op) 119 else: 120 r = r @ op 121 return _imp_from_jack(r, name, idl) 122 123 124def einsum(subscripts, *operands): 125 """Wrapper for numpy.einsum 126 127 Parameters 128 ---------- 129 subscripts : str 130 Subscripts for summation (see numpy documentation for details) 131 operands : numpy.ndarray 132 Arbitrary number of 2d-numpy arrays which can be real or complex 133 Obs valued. 134 """ 135 136 def _exp_to_jack(matrix): 137 base_matrix = [] 138 for _index, entry in np.ndenumerate(matrix): 139 base_matrix.append(entry.export_jackknife()) 140 return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape) 141 142 def _exp_to_jack_c(matrix): 143 base_matrix = [] 144 for _index, entry in np.ndenumerate(matrix): 145 base_matrix.append(entry.real.export_jackknife() + 1j * entry.imag.export_jackknife()) 146 return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape) 147 148 def _imp_from_jack(matrix, name, idl): 149 base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object) 150 for index in np.ndindex(matrix.shape[:-1]): 151 base_matrix[index] = import_jackknife(matrix[index], name, [idl]) 152 return base_matrix 153 154 def _imp_from_jack_c(matrix, name, idl): 155 base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object) 156 for index in np.ndindex(matrix.shape[:-1]): 157 base_matrix[index] = CObs(import_jackknife(matrix[index].real, name, [idl]), 158 import_jackknife(matrix[index].imag, name, [idl])) 159 return base_matrix 160 161 for op in operands: 162 if isinstance(op.flat[0], CObs): 163 name = op.flat[0].real.names[0] 164 idl = op.flat[0].real.idl[name] 165 break 166 elif isinstance(op.flat[0], Obs): 167 name = op.flat[0].names[0] 168 idl = op.flat[0].idl[name] 169 break 170 171 conv_operands = [] 172 for op in operands: 173 if isinstance(op.flat[0], CObs): 174 conv_operands.append(_exp_to_jack_c(op)) 175 elif isinstance(op.flat[0], Obs): 176 conv_operands.append(_exp_to_jack(op)) 177 else: 178 conv_operands.append(op) 179 180 tmp_subscripts = ','.join([o + '...' for o in subscripts.split(',')]) 181 extended_subscripts = '->'.join([o + '...' for o in tmp_subscripts.split('->')[:-1]] + [tmp_subscripts.split('->')[-1]]) 182 einsum_path = np.einsum_path(extended_subscripts, *conv_operands, optimize='optimal')[0] 183 jack_einsum = np.einsum(extended_subscripts, *conv_operands, optimize=einsum_path) 184 185 if jack_einsum.dtype == complex: 186 result = _imp_from_jack_c(jack_einsum, name, idl) 187 elif jack_einsum.dtype == float: 188 result = _imp_from_jack(jack_einsum, name, idl) 189 else: 190 raise Exception("Result has unexpected datatype") 191 192 if result.shape == (): 193 return result.flat[0] 194 else: 195 return result 196 197 198def inv(x): 199 """Inverse of Obs or CObs valued matrices.""" 200 return _mat_mat_op(anp.linalg.inv, x) 201 202 203def cholesky(x): 204 """Cholesky decomposition of Obs valued matrices.""" 205 if any(isinstance(o, CObs) for o in x.ravel()): 206 raise NotImplementedError("Cholesky decomposition is not implemented for CObs.") 207 return _mat_mat_op(anp.linalg.cholesky, x) 208 209 210def det(x): 211 """Determinant of Obs valued matrices.""" 212 return _scalar_mat_op(anp.linalg.det, x) 213 214 215def _scalar_mat_op(op, obs, **kwargs): 216 """Computes the matrix to scalar operation op to a given matrix of Obs.""" 217 def _mat(x, **kwargs): 218 dim = int(np.sqrt(len(x))) 219 220 mat = [] 221 for i in range(dim): 222 row = [] 223 for j in range(dim): 224 row.append(x[j + dim * i]) 225 mat.append(row) 226 227 return op(anp.array(mat)) 228 229 if isinstance(obs, np.ndarray): 230 raveled_obs = (1 * (obs.ravel())).tolist() 231 else: 232 raise TypeError('Unproper type of input.') 233 return derived_observable(_mat, raveled_obs, **kwargs) 234 235 236def _mat_mat_op(op, obs, **kwargs): 237 """Computes the matrix to matrix operation op to a given matrix of Obs.""" 238 # Use real representation to calculate matrix operations for complex matrices 239 if any(isinstance(o, CObs) for o in obs.ravel()): 240 A = np.empty_like(obs) 241 B = np.empty_like(obs) 242 for (n, m), entry in np.ndenumerate(obs): 243 if hasattr(entry, 'real') and hasattr(entry, 'imag'): 244 A[n, m] = entry.real 245 B[n, m] = entry.imag 246 else: 247 A[n, m] = entry 248 B[n, m] = 0.0 249 big_matrix = np.block([[A, -B], [B, A]]) 250 op_big_matrix = derived_observable(lambda x, **kwargs: op(x), [big_matrix], array_mode=True)[0] 251 dim = op_big_matrix.shape[0] 252 op_A = op_big_matrix[0: dim // 2, 0: dim // 2] 253 op_B = op_big_matrix[dim // 2:, 0: dim // 2] 254 res = np.empty_like(op_A) 255 for (n, m), _entry in np.ndenumerate(op_A): 256 res[n, m] = CObs(op_A[n, m], op_B[n, m]) 257 return res 258 else: 259 return derived_observable(lambda x, **kwargs: op(x), [obs], array_mode=True)[0] 260 261 262def eigh(obs, **kwargs): 263 """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.""" 264 w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs) 265 v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs) 266 return w, v 267 268 269def eig(obs, **kwargs): 270 """Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig.""" 271 w = derived_observable(lambda x, **kwargs: anp.real(anp.linalg.eig(x)[0]), obs) 272 return w 273 274 275def eigv(obs, **kwargs): 276 """Computes the eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.""" 277 v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs) 278 return v 279 280 281def pinv(obs, **kwargs): 282 """Computes the Moore-Penrose pseudoinverse of a matrix of Obs.""" 283 return derived_observable(lambda x, **kwargs: anp.linalg.pinv(x), obs) 284 285 286def svd(obs, **kwargs): 287 """Computes the singular value decomposition of a matrix of Obs.""" 288 u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs) 289 s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs) 290 vh = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[2], obs) 291 return (u, s, vh)
def
matmul(*operands):
8def matmul(*operands): 9 """Matrix multiply all operands. 10 11 Parameters 12 ---------- 13 operands : numpy.ndarray 14 Arbitrary number of 2d-numpy arrays which can be real or complex 15 Obs valued. 16 17 This implementation is faster compared to standard multiplication via the @ operator. 18 """ 19 if any(isinstance(o[0, 0], CObs) for o in operands): 20 extended_operands = [] 21 for op in operands: 22 tmp = np.vectorize(lambda x: (np.real(x), np.imag(x)))(op) 23 extended_operands.append(tmp[0]) 24 extended_operands.append(tmp[1]) 25 26 def multi_dot(operands, part): 27 stack_r = operands[0] 28 stack_i = operands[1] 29 for op_r, op_i in zip(operands[2::2], operands[3::2], strict=True): 30 tmp_r = stack_r @ op_r - stack_i @ op_i 31 tmp_i = stack_r @ op_i + stack_i @ op_r 32 33 stack_r = tmp_r 34 stack_i = tmp_i 35 36 if part == 'Real': 37 return stack_r 38 else: 39 return stack_i 40 41 def multi_dot_r(operands): 42 return multi_dot(operands, 'Real') 43 44 def multi_dot_i(operands): 45 return multi_dot(operands, 'Imag') 46 47 Nr = derived_observable(multi_dot_r, extended_operands, array_mode=True) 48 Ni = derived_observable(multi_dot_i, extended_operands, array_mode=True) 49 50 res = np.empty_like(Nr) 51 for (n, m), _entry in np.ndenumerate(Nr): 52 res[n, m] = CObs(Nr[n, m], Ni[n, m]) 53 54 return res 55 else: 56 def multi_dot(operands): 57 stack = operands[0] 58 for op in operands[1:]: 59 stack = stack @ op 60 return stack 61 return derived_observable(multi_dot, operands, array_mode=True)
Matrix multiply all operands.
Parameters
- operands (numpy.ndarray): Arbitrary number of 2d-numpy arrays which can be real or complex Obs valued.
- This implementation is faster compared to standard multiplication via the @ operator.
def
jack_matmul(*operands):
64def jack_matmul(*operands): 65 """Matrix multiply both operands making use of the jackknife approximation. 66 67 Parameters 68 ---------- 69 operands : numpy.ndarray 70 Arbitrary number of 2d-numpy arrays which can be real or complex 71 Obs valued. 72 73 For large matrices this is considerably faster compared to matmul. 74 """ 75 76 def _exp_to_jack(matrix): 77 base_matrix = np.empty_like(matrix) 78 for index, entry in np.ndenumerate(matrix): 79 base_matrix[index] = entry.export_jackknife() 80 return base_matrix 81 82 def _imp_from_jack(matrix, name, idl): 83 base_matrix = np.empty_like(matrix) 84 for index, entry in np.ndenumerate(matrix): 85 base_matrix[index] = import_jackknife(entry, name, [idl]) 86 return base_matrix 87 88 def _exp_to_jack_c(matrix): 89 base_matrix = np.empty_like(matrix) 90 for index, entry in np.ndenumerate(matrix): 91 base_matrix[index] = entry.real.export_jackknife() + 1j * entry.imag.export_jackknife() 92 return base_matrix 93 94 def _imp_from_jack_c(matrix, name, idl): 95 base_matrix = np.empty_like(matrix) 96 for index, entry in np.ndenumerate(matrix): 97 base_matrix[index] = CObs(import_jackknife(entry.real, name, [idl]), 98 import_jackknife(entry.imag, name, [idl])) 99 return base_matrix 100 101 if any(isinstance(o.flat[0], CObs) for o in operands): 102 name = operands[0].flat[0].real.names[0] 103 idl = operands[0].flat[0].real.idl[name] 104 105 r = _exp_to_jack_c(operands[0]) 106 for op in operands[1:]: 107 if isinstance(op.flat[0], CObs): 108 r = r @ _exp_to_jack_c(op) 109 else: 110 r = r @ op 111 return _imp_from_jack_c(r, name, idl) 112 else: 113 name = operands[0].flat[0].names[0] 114 idl = operands[0].flat[0].idl[name] 115 116 r = _exp_to_jack(operands[0]) 117 for op in operands[1:]: 118 if isinstance(op.flat[0], Obs): 119 r = r @ _exp_to_jack(op) 120 else: 121 r = r @ op 122 return _imp_from_jack(r, name, idl)
Matrix multiply both operands making use of the jackknife approximation.
Parameters
- operands (numpy.ndarray): Arbitrary number of 2d-numpy arrays which can be real or complex Obs valued.
- For large matrices this is considerably faster compared to matmul.
def
einsum(subscripts, *operands):
125def einsum(subscripts, *operands): 126 """Wrapper for numpy.einsum 127 128 Parameters 129 ---------- 130 subscripts : str 131 Subscripts for summation (see numpy documentation for details) 132 operands : numpy.ndarray 133 Arbitrary number of 2d-numpy arrays which can be real or complex 134 Obs valued. 135 """ 136 137 def _exp_to_jack(matrix): 138 base_matrix = [] 139 for _index, entry in np.ndenumerate(matrix): 140 base_matrix.append(entry.export_jackknife()) 141 return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape) 142 143 def _exp_to_jack_c(matrix): 144 base_matrix = [] 145 for _index, entry in np.ndenumerate(matrix): 146 base_matrix.append(entry.real.export_jackknife() + 1j * entry.imag.export_jackknife()) 147 return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape) 148 149 def _imp_from_jack(matrix, name, idl): 150 base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object) 151 for index in np.ndindex(matrix.shape[:-1]): 152 base_matrix[index] = import_jackknife(matrix[index], name, [idl]) 153 return base_matrix 154 155 def _imp_from_jack_c(matrix, name, idl): 156 base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object) 157 for index in np.ndindex(matrix.shape[:-1]): 158 base_matrix[index] = CObs(import_jackknife(matrix[index].real, name, [idl]), 159 import_jackknife(matrix[index].imag, name, [idl])) 160 return base_matrix 161 162 for op in operands: 163 if isinstance(op.flat[0], CObs): 164 name = op.flat[0].real.names[0] 165 idl = op.flat[0].real.idl[name] 166 break 167 elif isinstance(op.flat[0], Obs): 168 name = op.flat[0].names[0] 169 idl = op.flat[0].idl[name] 170 break 171 172 conv_operands = [] 173 for op in operands: 174 if isinstance(op.flat[0], CObs): 175 conv_operands.append(_exp_to_jack_c(op)) 176 elif isinstance(op.flat[0], Obs): 177 conv_operands.append(_exp_to_jack(op)) 178 else: 179 conv_operands.append(op) 180 181 tmp_subscripts = ','.join([o + '...' for o in subscripts.split(',')]) 182 extended_subscripts = '->'.join([o + '...' for o in tmp_subscripts.split('->')[:-1]] + [tmp_subscripts.split('->')[-1]]) 183 einsum_path = np.einsum_path(extended_subscripts, *conv_operands, optimize='optimal')[0] 184 jack_einsum = np.einsum(extended_subscripts, *conv_operands, optimize=einsum_path) 185 186 if jack_einsum.dtype == complex: 187 result = _imp_from_jack_c(jack_einsum, name, idl) 188 elif jack_einsum.dtype == float: 189 result = _imp_from_jack(jack_einsum, name, idl) 190 else: 191 raise Exception("Result has unexpected datatype") 192 193 if result.shape == (): 194 return result.flat[0] 195 else: 196 return result
Wrapper for numpy.einsum
Parameters
- subscripts (str): Subscripts for summation (see numpy documentation for details)
- operands (numpy.ndarray): Arbitrary number of 2d-numpy arrays which can be real or complex Obs valued.
def
inv(x):
199def inv(x): 200 """Inverse of Obs or CObs valued matrices.""" 201 return _mat_mat_op(anp.linalg.inv, x)
Inverse of Obs or CObs valued matrices.
def
cholesky(x):
204def cholesky(x): 205 """Cholesky decomposition of Obs valued matrices.""" 206 if any(isinstance(o, CObs) for o in x.ravel()): 207 raise NotImplementedError("Cholesky decomposition is not implemented for CObs.") 208 return _mat_mat_op(anp.linalg.cholesky, x)
Cholesky decomposition of Obs valued matrices.
def
det(x):
211def det(x): 212 """Determinant of Obs valued matrices.""" 213 return _scalar_mat_op(anp.linalg.det, x)
Determinant of Obs valued matrices.
def
eigh(obs, **kwargs):
263def eigh(obs, **kwargs): 264 """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.""" 265 w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs) 266 v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs) 267 return w, v
Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.
def
eig(obs, **kwargs):
270def eig(obs, **kwargs): 271 """Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig.""" 272 w = derived_observable(lambda x, **kwargs: anp.real(anp.linalg.eig(x)[0]), obs) 273 return w
Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig.
def
eigv(obs, **kwargs):
276def eigv(obs, **kwargs): 277 """Computes the eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.""" 278 v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs) 279 return v
Computes the eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.
def
pinv(obs, **kwargs):
282def pinv(obs, **kwargs): 283 """Computes the Moore-Penrose pseudoinverse of a matrix of Obs.""" 284 return derived_observable(lambda x, **kwargs: anp.linalg.pinv(x), obs)
Computes the Moore-Penrose pseudoinverse of a matrix of Obs.
def
svd(obs, **kwargs):
287def svd(obs, **kwargs): 288 """Computes the singular value decomposition of a matrix of Obs.""" 289 u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs) 290 s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs) 291 vh = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[2], obs) 292 return (u, s, vh)
Computes the singular value decomposition of a matrix of Obs.