# pyerrors.linalg

View Source

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

def matmul(*operands):
View Source

     6def matmul(*operands):
     7    """Matrix multiply all operands.
     8
     9    Parameters
    10    ----------
    11    operands : numpy.ndarray
    12        Arbitrary number of 2d-numpy arrays which can be real or complex
    13        Obs valued.
    14
    15    This implementation is faster compared to standard multiplication via the @ operator.
    16    """
    17    if any(isinstance(o[0, 0], CObs) for o in operands):
    18        extended_operands = []
    19        for op in operands:
    20            tmp = np.vectorize(lambda x: (np.real(x), np.imag(x)))(op)
    21            extended_operands.append(tmp[0])
    22            extended_operands.append(tmp[1])
    23
    24        def multi_dot(operands, part):
    25            stack_r = operands[0]
    26            stack_i = operands[1]
    27            for op_r, op_i in zip(operands[2::2], operands[3::2]):
    28                tmp_r = stack_r @ op_r - stack_i @ op_i
    29                tmp_i = stack_r @ op_i + stack_i @ op_r
    30
    31                stack_r = tmp_r
    32                stack_i = tmp_i
    33
    34            if part == 'Real':
    35                return stack_r
    36            else:
    37                return stack_i
    38
    39        def multi_dot_r(operands):
    40            return multi_dot(operands, 'Real')
    41
    42        def multi_dot_i(operands):
    43            return multi_dot(operands, 'Imag')
    44
    45        Nr = derived_observable(multi_dot_r, extended_operands, array_mode=True)
    46        Ni = derived_observable(multi_dot_i, extended_operands, array_mode=True)
    47
    48        res = np.empty_like(Nr)
    49        for (n, m), entry in np.ndenumerate(Nr):
    50            res[n, m] = CObs(Nr[n, m], Ni[n, m])
    51
    52        return res
    53    else:
    54        def multi_dot(operands):
    55            stack = operands[0]
    56            for op in operands[1:]:
    57                stack = stack @ op
    58            return stack
    59        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):
View Source

     62def jack_matmul(*operands):
     63    """Matrix multiply both operands making use of the jackknife approximation.
     64
     65    Parameters
     66    ----------
     67    operands : numpy.ndarray
     68        Arbitrary number of 2d-numpy arrays which can be real or complex
     69        Obs valued.
     70
     71    For large matrices this is considerably faster compared to matmul.
     72    """
     73
     74    def _exp_to_jack(matrix):
     75        base_matrix = np.empty_like(matrix)
     76        for index, entry in np.ndenumerate(matrix):
     77            base_matrix[index] = entry.export_jackknife()
     78        return base_matrix
     79
     80    def _imp_from_jack(matrix, name, idl):
     81        base_matrix = np.empty_like(matrix)
     82        for index, entry in np.ndenumerate(matrix):
     83            base_matrix[index] = import_jackknife(entry, name, [idl])
     84        return base_matrix
     85
     86    def _exp_to_jack_c(matrix):
     87        base_matrix = np.empty_like(matrix)
     88        for index, entry in np.ndenumerate(matrix):
     89            base_matrix[index] = entry.real.export_jackknife() + 1j * entry.imag.export_jackknife()
     90        return base_matrix
     91
     92    def _imp_from_jack_c(matrix, name, idl):
     93        base_matrix = np.empty_like(matrix)
     94        for index, entry in np.ndenumerate(matrix):
     95            base_matrix[index] = CObs(import_jackknife(entry.real, name, [idl]),
     96                                      import_jackknife(entry.imag, name, [idl]))
     97        return base_matrix
     98
     99    if any(isinstance(o.flat[0], CObs) for o in operands):
    100        name = operands[0].flat[0].real.names[0]
    101        idl = operands[0].flat[0].real.idl[name]
    102
    103        r = _exp_to_jack_c(operands[0])
    104        for op in operands[1:]:
    105            if isinstance(op.flat[0], CObs):
    106                r = r @ _exp_to_jack_c(op)
    107            else:
    108                r = r @ op
    109        return _imp_from_jack_c(r, name, idl)
    110    else:
    111        name = operands[0].flat[0].names[0]
    112        idl = operands[0].flat[0].idl[name]
    113
    114        r = _exp_to_jack(operands[0])
    115        for op in operands[1:]:
    116            if isinstance(op.flat[0], Obs):
    117                r = r @ _exp_to_jack(op)
    118            else:
    119                r = r @ op
    120        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):
View Source

    123def einsum(subscripts, *operands):
    124    """Wrapper for numpy.einsum
    125
    126    Parameters
    127    ----------
    128    subscripts : str
    129        Subscripts for summation (see numpy documentation for details)
    130    operands : numpy.ndarray
    131        Arbitrary number of 2d-numpy arrays which can be real or complex
    132        Obs valued.
    133    """
    134
    135    def _exp_to_jack(matrix):
    136        base_matrix = []
    137        for index, entry in np.ndenumerate(matrix):
    138            base_matrix.append(entry.export_jackknife())
    139        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
    140
    141    def _exp_to_jack_c(matrix):
    142        base_matrix = []
    143        for index, entry in np.ndenumerate(matrix):
    144            base_matrix.append(entry.real.export_jackknife() + 1j * entry.imag.export_jackknife())
    145        return np.asarray(base_matrix).reshape(matrix.shape + base_matrix[0].shape)
    146
    147    def _imp_from_jack(matrix, name, idl):
    148        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
    149        for index in np.ndindex(matrix.shape[:-1]):
    150            base_matrix[index] = import_jackknife(matrix[index], name, [idl])
    151        return base_matrix
    152
    153    def _imp_from_jack_c(matrix, name, idl):
    154        base_matrix = np.empty(shape=matrix.shape[:-1], dtype=object)
    155        for index in np.ndindex(matrix.shape[:-1]):
    156            base_matrix[index] = CObs(import_jackknife(matrix[index].real, name, [idl]),
    157                                      import_jackknife(matrix[index].imag, name, [idl]))
    158        return base_matrix
    159
    160    for op in operands:
    161        if isinstance(op.flat[0], CObs):
    162            name = op.flat[0].real.names[0]
    163            idl = op.flat[0].real.idl[name]
    164            break
    165        elif isinstance(op.flat[0], Obs):
    166            name = op.flat[0].names[0]
    167            idl = op.flat[0].idl[name]
    168            break
    169
    170    conv_operands = []
    171    for op in operands:
    172        if isinstance(op.flat[0], CObs):
    173            conv_operands.append(_exp_to_jack_c(op))
    174        elif isinstance(op.flat[0], Obs):
    175            conv_operands.append(_exp_to_jack(op))
    176        else:
    177            conv_operands.append(op)
    178
    179    tmp_subscripts = ','.join([o + '...' for o in subscripts.split(',')])
    180    extended_subscripts = '->'.join([o + '...' for o in tmp_subscripts.split('->')[:-1]] + [tmp_subscripts.split('->')[-1]])
    181    einsum_path = np.einsum_path(extended_subscripts, *conv_operands, optimize='optimal')[0]
    182    jack_einsum = np.einsum(extended_subscripts, *conv_operands, optimize=einsum_path)
    183
    184    if jack_einsum.dtype == complex:
    185        result = _imp_from_jack_c(jack_einsum, name, idl)
    186    elif jack_einsum.dtype == float:
    187        result = _imp_from_jack(jack_einsum, name, idl)
    188    else:
    189        raise Exception("Result has unexpected datatype")
    190
    191    if result.shape == ():
    192        return result.flat[0]
    193    else:
    194        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):
View Source

    197def inv(x):
    198    """Inverse of Obs or CObs valued matrices."""
    199    return _mat_mat_op(anp.linalg.inv, x)

Inverse of Obs or CObs valued matrices.

def cholesky(x):
View Source

    202def cholesky(x):
    203    """Cholesky decomposition of Obs or CObs valued matrices."""
    204    return _mat_mat_op(anp.linalg.cholesky, x)

Cholesky decomposition of Obs or CObs valued matrices.

def det(x):
View Source

    207def det(x):
    208    """Determinant of Obs valued matrices."""
    209    return _scalar_mat_op(anp.linalg.det, x)

Determinant of Obs valued matrices.

def eigh(obs, **kwargs):
View Source

    259def eigh(obs, **kwargs):
    260    """Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh."""
    261    w = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[0], obs)
    262    v = derived_observable(lambda x, **kwargs: anp.linalg.eigh(x)[1], obs)
    263    return w, v

Computes the eigenvalues and eigenvectors of a given hermitian matrix of Obs according to np.linalg.eigh.

def eig(obs, **kwargs):
View Source

    266def eig(obs, **kwargs):
    267    """Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig."""
    268    w = derived_observable(lambda x, **kwargs: anp.real(anp.linalg.eig(x)[0]), obs)
    269    return w

Computes the eigenvalues of a given matrix of Obs according to np.linalg.eig.

def pinv(obs, **kwargs):
View Source

    272def pinv(obs, **kwargs):
    273    """Computes the Moore-Penrose pseudoinverse of a matrix of Obs."""
    274    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):
View Source

    277def svd(obs, **kwargs):
    278    """Computes the singular value decomposition of a matrix of Obs."""
    279    u = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[0], obs)
    280    s = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[1], obs)
    281    vh = derived_observable(lambda x, **kwargs: anp.linalg.svd(x, full_matrices=False)[2], obs)
    282    return (u, s, vh)

Computes the singular value decomposition of a matrix of Obs.