Source code for seemps.register.transforms

from __future__ import annotations
import numpy as np
from typing import Optional, Iterable
from ..typing import VectorLike
from ..mpo import MPO


def mpo_weighted_shifts(
    L: int,
    weights: VectorLike,
    shifts: tuple[int, int] | list[int],
    periodic: bool = False,
    base: int = 2,
) -> MPO:
    r"""Return an MPO that implements a linear combination of arithmetic displacements.

    This function creates a matrix product operator that implements

    .. math::
       O |s\rangle \to \sum_i w_i |s + i\rangle

    The operator is very useful to implement finite difference approximations.

    Arguments
    ---------
    L : int
        Number of qubits in the quantum register
    weights : VectorLike
        List or array of weights to apply to each displacement
    shifts : list[int] | tuple[int,int]
        Range of displacements to be applied
    periodic : bool, default = False
        Whether to wrap around integers when shifting.
    base : int, default = 2
        Encoding of the quantum register elements. By default `base=2`
        meaning we work with qubits.

    Returns
    -------
    MPO
        Matrix product operator for `O` above.
    """
    O = mpo_shifts(L, shifts, periodic, base)
    O[L - 1] = np.einsum("aijb,bc->aijc", O[-1], np.reshape(weights, (-1, 1)))
    return O


def mpo_shifts(
    L: int, shifts: tuple[int, int] | list[int], periodic: bool = False, base: int = 2
) -> MPO:
    r"""Return an MPO with a free index, capable of displacing a quantum
    register by a range of integers.

    This function creates matrix product operators that add a specific integer
    to a quantum register encoded in an MPS. The `shifts` can be a list of those
    integers, or a tuple denoting a range, converted as
    `shifts=list(range(*shifts))`.

    The MPO is a bit special, in that the last tensor of the operator, say
    `A[L]` will have a final index with size `M` equal to the number of shifts.

    Arguments
    ---------
    L : int
        Number of qubits in the quantum register
    shifts : list[int] | tuple[int,int]
        Range of displacements to be applied
    periodic : bool, default = False
        Whether to wrap around integers when shifting.
    base : int, default = 2
        Encoding of the quantum register elements. By default `base=2`
        meaning we work with qubits.

    Returns
    -------
    MPO
        Parameterized MPO with an extra bond at the end
    """
    if isinstance(shifts, tuple):
        r = np.arange(shifts[0], shifts[1], dtype=int)
    else:
        r = np.asarray(shifts, dtype=int)
    tensors = []
    bits = np.arange(base).reshape(base, 1)
    for i in reversed(range(L)):
        #
        # The shift r[j] adds to the current bit s[i], producing
        # an integer r[j] + s[i] = 2 * r' + s[i]', with a new
        # value of the bit s[i]' and a carry displacement r' for
        # the new site
        #
        # These are the carry-on values
        newr_matrix = (bits + r) // base
        # These are the indices that the qudit states are mapped to
        news_matrix = (bits + r) % base
        # A vector with all carry-on values
        newr = np.sort(np.unique(newr_matrix.reshape(-1)))
        # and a matrix with the positions of the carry-on values into
        # this vector. This transforms carry-on-values to bond dimension
        # indices
        newr_matrix = np.searchsorted(newr, newr_matrix)
        A = np.zeros((newr.size, base, base, r.size))
        A[newr_matrix, news_matrix, bits, np.arange(r.size)] = 1.0
        tensors.append(A)
        r = newr
    if periodic:
        tensors[-1] = np.sum(A, 0).reshape((1,) + A.shape[1:])
    else:
        ndx = np.nonzero(r == 0)[0]
        if ndx.size:
            tensors[-1] = A[ndx, :, :, :]
        else:
            tensors[-1] = A[[0], :, :, :] * 0.0
    return MPO(list(reversed(tensors)))


[docs] def twoscomplement( L: int, control: int = 0, sites: Optional[Iterable[int]] = None, **kwdargs ) -> MPO: """Return an MPO that performs a two's complement of the selected qubits depending on a 'control' qubit in a register with L qubits. Arguments --------- L -- Real size of register control -- Which qubit (relative to sites) controls the sign. Defaults to the first qubit in 'sites'. sites -- The qubits involved in the MPO. Defaults to range(L). kwdargs -- Arguments for MPO. Parameters ---------- L : int : control : int : (Default value = 0) sites : Optional[Iterable[int]] : (Default value = None) **kwdargs : L: int : control: int : (Default value = 0) sites: Optional[Iterable[int]] : (Default value = None) Returns ------- """ if sites is not None: sites = sorted(sites) out = twoscomplement( len(sites), control=sites.index(control), sites=None, **kwdargs ) return out.extend(L, sites=sites) else: A0 = np.zeros((2, 2, 2, 2)) A0[0, 0, 0, 0] = 1.0 A0[1, 1, 1, 1] = 1.0 A = np.zeros((2, 2, 2, 2)) A[0, 0, 0, 0] = 1.0 A[0, 1, 1, 0] = 1.0 A[1, 1, 0, 1] = 1.0 A[1, 0, 1, 1] = 1.0 data = [A0 if i == control else A for i in range(L)] A = data[0] data[0] = A[[0], :, :, :] + A[[1], :, :, :] A = data[-1] data[-1] = A[:, :, :, [0]] + A[:, :, :, [1]] return MPO(data, **kwdargs)