Source code for geqo.utils._numpy_.helpers

import numpy as np
from numpy.typing import NDArray
import itertools
from typing import Any
from geqo.utils._base_.helpers import bin2num


[docs] def permutationMatrixQubitsNumPy(perm: list[int]) -> NDArray: """ Return permutation matrix for given qubit permutation. Parameters ---------- perm : list[int] The permutation of qubits in list form. The elements are indexed starting with 0. For instance, [0,2,1] denotes the permutation of 3 qubits, where the last 2 qubits are flipped. Returns ------- NDArray A permutation matrix on the state space of the qubits. The matrix corresponds to the permutation of the qubits. """ permMat = np.zeros((len(perm), len(perm))) for i in range(len(perm)): permMat[perm[i], i] = 1 res = np.zeros((2 ** len(perm), 2 ** len(perm))) for t in itertools.product([0, 1], repeat=len(perm)): v = np.array(t).reshape(len(perm), 1) b = permMat @ v r = [b[i] for i in range(len(perm))] res[bin2num(list(r)), bin2num(list(t))] = 1 return res
def getSingleQubitOperationOnRegister( u: NDArray, numberQubits: int, targets: list[int] ) -> NDArray: """ Apply single-qubit operation to specific qubits in register. This function embeds the given matrix into the matrix corresponding to the whole system. Parameters ---------- u : NDArray The unitary matrix, which is applied to a part of the whole quantum register. numberQubits: int The size of the quantum register. targets: list[int] The list of qubits, on which the operation is applied to. Returns ------- NDArray A matrix on the whole register, which is the embedding of the provided operation. """ targetOrder = [q for q in targets] for q in range(numberQubits): if q not in targetOrder: targetOrder.append(q) perm = permutationMatrixQubitsNumPy( [targetOrder.index(q) for q in range(numberQubits)] ) u2 = np.kron(u, np.eye(2 ** (numberQubits - len(targets)))) return perm.T * u2 * perm def partialTrace( rho: NDArray, qubits: list[int], dropTargets: list[int] ) -> tuple[NDArray, NDArray]: """ Compute the partial trace of a density matrix. The density matrix is reduced to the remaining qubits. The new density matrix is obtained by tracing out the dropped qubit. In general, this leads to mixed states. Parameters ---------- rho: NDArray The density matrix before dropping qubits. qubits: list[int] The list of all qubits of a quantum register. dropTargets: list[int] The list of qubits, which are dropped. Returns ------- tuple[NDArray, NDArray] The first result matrix is the reduced density matrix. The second result matrix is the permutation matrix, which moves the remaining entries of the original density matrix to the front. """ undroppedQubits = [q for q in qubits if q not in dropTargets] targetOrder2 = undroppedQubits + dropTargets perm = permutationMatrixQubitsNumPy([targetOrder2.index(q) for q in qubits]) rho2 = perm * rho * perm.T newNumberQubits = len(qubits) - len(dropTargets) newEntries: dict[tuple[Any, Any], NDArray] = {} for t1 in itertools.product([0, 1], repeat=newNumberQubits): for t2 in itertools.product([0, 1], repeat=newNumberQubits): newIndex1 = bin2num(t1) newIndex2 = bin2num(t2) for t3 in itertools.product([0, 1], repeat=len(qubits) - newNumberQubits): oldIndex1 = bin2num(t1 + t3) oldIndex2 = bin2num(t2 + t3) if (newIndex1, newIndex2) in newEntries: newEntries[(newIndex1, newIndex2)] += rho2[(oldIndex1, oldIndex2)] else: newEntries[(newIndex1, newIndex2)] = rho2[(oldIndex1, oldIndex2)] rhoNew = np.zeros(2**newNumberQubits, 2**newNumberQubits) for n in newEntries: rhoNew[n[0], n[1]] = newEntries[n] return rhoNew, perm