跳转至

CircuitMatrix

QuICT.core.utils.get_gates_order_by_depth

get_gates_order_by_depth(gates: list) -> list

Order the gates of circuit by its depth layer

Returns:

  • list

    List[List[BasicGate]]: The list of gates which at same layers in circuit.

Source code in QuICT/core/utils/circuit_matrix.py
def get_gates_order_by_depth(gates: list) -> list:
    """ Order the gates of circuit by its depth layer

    Returns:
        List[List[BasicGate]]: The list of gates which at same layers in circuit.
    """
    gate_by_depth = [[gates[0]]]          # List[list], gates for each depth level.
    # List[set], gates' qubits for each depth level.
    gate_args_by_depth = [set(gates[0].cargs + gates[0].targs)]
    for gate in gates[1:]:
        gate_arg = set(gate.cargs + gate.targs)
        for i in range(len(gate_args_by_depth) - 1, -1, -1):
            if gate_arg & gate_args_by_depth[i]:
                if i == len(gate_args_by_depth) - 1:
                    gate_by_depth.append([gate])
                    gate_args_by_depth.append(gate_arg)
                else:
                    gate_by_depth[i + 1].append(gate)
                    gate_args_by_depth[i + 1] = gate_arg | gate_args_by_depth[i + 1]
                break
            else:
                if i == 0:
                    gate_by_depth[i].append(gate)
                    gate_args_by_depth[i] = gate_arg | gate_args_by_depth[i]

    return gate_by_depth

QuICT.core.utils.circuit_matrix.MatrixGroup

MatrixGroup(matrix, args, blocked_args: set = set([]))

Create a group of Matrix with qubit indexes.

Source code in QuICT/core/utils/circuit_matrix.py
def __init__(self, matrix, args, blocked_args: set = set([])):
    self.args = set(args)
    self.value = [(matrix, args)]
    self.block_args = blocked_args

QuICT.core.utils.CircuitMatrix

CircuitMatrix(device: str = 'CPU', precision: str = 'double')

Generate Circuit's Matrix.

Parameters:

  • device (str, default: 'CPU' ) –

    The device type, one of [CPU, GPU]. Defaults to "CPU".

  • precision (str, default: 'double' ) –

    The precision for matrix, one of [single, double]. Defaults to "double".

Source code in QuICT/core/utils/circuit_matrix.py
def __init__(self, device: str = "CPU", precision: str = "double"):
    """
    Args:
        device (str, optional): The device type, one of [CPU, GPU]. Defaults to "CPU".
        precision (str, optional): The precision for matrix, one of [single, double]. Defaults to "double".
    """
    self._device = device
    self._precision = precision
    self._dtype = np.complex128 if precision == "double" else np.complex64

    if device == "CPU":
        self._computer = CPUCalculator
        self._array_helper = np
    else:
        import cupy as cp
        import QuICT.ops.linalg.gpu_calculator as GPUCalculator

        self._computer = GPUCalculator
        self._array_helper = cp

get_unitary_matrix

get_unitary_matrix(gates: list, qubits_num: int) -> np.ndarray

Parameters:

  • gates (List[BasicGate]) –

    The list of Quantum Gates in the Circuit.

  • qubits_num (int) –

    The number of qubits

Returns:

  • ndarray

    np.ndarray: The unitary matrix of the Quantum Circuit.

Source code in QuICT/core/utils/circuit_matrix.py
def get_unitary_matrix(self, gates: list, qubits_num: int) -> np.ndarray:
    """
    Args:
        gates (List[BasicGate]): The list of Quantum Gates in the Circuit.
        qubits_num (int): The number of qubits

    Returns:
        np.ndarray: The unitary matrix of the Quantum Circuit.
    """
    if len(gates) == 0:
        return self._array_helper.identity(1 << qubits_num, dtype=self._dtype)

    # Order gates by depth
    gates_order_by_depth = get_gates_order_by_depth(gates)

    # Get MatrixGroups by its qubit args and depth
    matrix_groups = [[]]          # List[List[MatrixGroup]]
    for layer_gates in gates_order_by_depth:
        for gate in layer_gates:
            if gate.controls + gate.targets >= 3:
                raise TypeError(
                    "CircuitMatrix.get_unitary_matrix.gates", "1 or 2-qubits gates", gate.controls + gate.targets
                )

            if gate.is_special():
                continue

            args = gate.cargs + gate.targs
            if gate.type == GateType.kraus:
                matrix = gate.matrix
            else:
                matrix = gate.get_matrix(self._precision) if self._device == "CPU" else \
                    self._array_helper.array(gate.get_matrix(self._precision))

            if len(args) == 2 and args[0] > args[1]:
                args.sort()
                self._computer.MatrixPermutation(
                    matrix, self._array_helper.array([1, 0]), True
                )

            is_intersect, is_blocked_layer = self._find_related_MatrixGroup(matrix_groups, args)
            if not is_intersect:
                new_mg = MatrixGroup(matrix, args)
                matrix_groups[is_blocked_layer].append(new_mg)
            else:
                if len(is_intersect) == 2:
                    related_mg0, related_mg1 = is_intersect[0], is_intersect[1]
                    if related_mg0.position == related_mg1.position:
                        matrix_groups[related_mg0.layer][related_mg0.position].append(matrix, args)
                    else:
                        blocked_args = matrix_groups[related_mg0.layer][related_mg0.position].args | \
                            matrix_groups[related_mg1.layer][related_mg1.position].args
                        new_mg = MatrixGroup(matrix, args, blocked_args)
                        if related_mg0.layer == len(matrix_groups) - 1:
                            matrix_groups.append([new_mg])
                        else:
                            matrix_groups[related_mg0.layer + 1].append(new_mg)
                else:
                    related_mg = is_intersect[0]
                    if len(args) == 1:
                        matrix_groups[related_mg.layer][related_mg.position].append(matrix, args)
                    else:
                        new_mg = MatrixGroup(matrix, args)
                        if related_mg.layer == len(matrix_groups) - 1:
                            matrix_groups.append([new_mg])
                        else:
                            matrix_groups[related_mg.layer + 1].append(new_mg)

    # Combined the matries in each MatrixGroup
    combined_matries = []
    for layer in matrix_groups:
        for mg in layer:
            combined_matries.append(self._combined_gates(mg.value))

    # Combined all matries from the combined MatrixGroup
    circuit_matrix, circuit_matrix_args = self._combined_gates(combined_matries)
    # Permutation the circuit matrix with currect qubits' order
    args_baseline = list(range(qubits_num))
    if circuit_matrix_args != args_baseline:
        circuit_matrix = self._tensor_unitary(circuit_matrix, circuit_matrix_args, args_baseline)

    return circuit_matrix

get_unitary_matrix_non_expand

get_unitary_matrix_non_expand(gates: list, qubits_num: int) -> np.ndarray

Calculate the total unitary for gates in the gate list without expanding each gate to the system size of the total qubit number. Args: gates (List[BasicGate]): The list of Quantum Gates in the Circuit. qubits_num (int): The number of qubits

Returns:

  • ndarray

    np.ndarray: The unitary matrix of the Quantum Circuit.

Source code in QuICT/core/utils/circuit_matrix.py
def get_unitary_matrix_non_expand(self, gates: list, qubits_num: int) -> np.ndarray:
    """ Calculate the total unitary for gates in the gate list without expanding each gate to
    the system size of the total qubit number.
    Args:
        gates (List[BasicGate]): The list of Quantum Gates in the Circuit.
        qubits_num (int): The number of qubits

    Returns:
        np.ndarray: The unitary matrix of the Quantum Circuit.
    """
    circuit_matrix = self._array_helper.identity(1 << qubits_num, dtype=self._dtype)
    if len(gates) == 0:
        return circuit_matrix

    for gate in gates:
        if gate.type in [GateType.barrier, GateType.reset, GateType.measure]:
            continue

        ct_args = gate.cargs + gate.targs
        ct_args_rect = (-np.array(ct_args) + (qubits_num - 1)).tolist()[::-1]
        self._computer.matrix_dot_matrix(circuit_matrix, gate.matrix, target_args=ct_args_rect)

    return circuit_matrix

merge_gates

merge_gates(u1, u1_args, u2, u2_args)

Combined two Quantum Gate togather.

Parameters:

  • u1 (ndarray) –

    The unitary matrix of Gate A.

  • u1_args (list) –

    The qubit indexes of Gate A.

  • u2 (ndarray) –

    The unitary matrix of Gate B.

  • u2_args (list) –

    The qubit indexes of Gate B.

Returns:

  • Tuple ( (ndarray, list) ) –

    The combined unitary matrix and its qubits indexes.

Source code in QuICT/core/utils/circuit_matrix.py
def merge_gates(self, u1, u1_args, u2, u2_args):
    """ Combined two Quantum Gate togather.

    Args:
        u1 (np.ndarray): The unitary matrix of Gate A.
        u1_args (list): The qubit indexes of Gate A.
        u2 (np.ndarray): The unitary matrix of Gate B.
        u2_args (list): The qubit indexes of Gate B.

    Returns:
        Tuple(np.ndarray, list): The combined unitary matrix and its qubits indexes.
    """
    u1_args_set, u2_args_set = set(u1_args), set(u2_args)
    insection_args = u1_args_set & u2_args_set
    if len(insection_args) == 0:
        return self._computer.tensor(u1, u2), u1_args + u2_args

    if u1_args_set == u2_args_set:
        args_idx = [u1_args.index(u2_arg) for u2_arg in u2_args]
        self._computer.MatrixPermutation(
            u2,
            self._array_helper.array(args_idx),
            True
        )

    union_args = u1_args + [i for i in u2_args if i not in u1_args] if len(u1_args) >= len(u2_args) else \
        u2_args + [i for i in u1_args if i not in u2_args]
    if len(union_args) != len(u1_args_set):
        u1 = self._tensor_unitary(u1, u1_args, union_args)

    if len(union_args) != len(u2_args_set):
        u2 = self._tensor_unitary(u2, u2_args, union_args)

    return self._computer.dot(u2, u1), union_args