跳转至

CPUCalculator

QuICT.ops.linalg.cpu_calculator

MatrixPermutation

MatrixPermutation(A: ndarray, mapping: ndarray, changeInput: bool = False) -> np.ndarray

permute A with mapping, inplace

Parameters:

  • A(np.array<np.complex>)

    the matrix A

  • mapping(np.ndarray<int>)

    the qubit mapping

  • changeInput(bool)

    whether changes in A

Source code in QuICT/ops/linalg/cpu_calculator.py
@njit(parallel=True, nogil=True)
def MatrixPermutation(A: np.ndarray, mapping: np.ndarray, changeInput: bool = False) -> np.ndarray:
    """ permute A with mapping, inplace

    Args:
        A(np.array<np.complex>): the matrix A
        mapping(np.ndarray<int>): the qubit mapping
        changeInput(bool): whether changes in A
    """
    if not A.shape[0] == (1 << mapping.shape[0]):
        raise IndexError("Indices do not match!")

    idx_mapping = mapping_augment(mapping)

    # Do NOT perform parallel operations over row permutations!
    # They are just too spare in memory. Elements in the same column
    # are distributed with a gap as matrix row length.
    perm_mat = A[idx_mapping, :]
    for i in prange(idx_mapping.shape[0]):
        perm_mat[i] = perm_mat[i][idx_mapping]

    if changeInput:
        A[:, :] = perm_mat

    return perm_mat

MatrixTensorI

MatrixTensorI(A, n, m)

Applying the Matrix Tensor operator to matrix A

\[ A' = I^n \otimes A \otimes I^m \]

Parameters:

  • A(np.array<np.complex>)

    the matrix A

  • n(int)

    the index of indentity

  • m(int)

    the index of indentity

Returns:

  • np.array: the tensor result I^n ⊗ A ⊗ I^m

Source code in QuICT/ops/linalg/cpu_calculator.py
@njit(parallel=True, nogil=True)
def MatrixTensorI(A, n, m):
    r""" Applying the Matrix Tensor operator to matrix A

    $$ A' = I^n \otimes A \otimes I^m $$

    Args:
        A(np.array<np.complex>): the matrix A
        n(int): the index of indentity
        m(int): the index of indentity

    Returns:
        np.array<np.complex>: the tensor result I^n ⊗ A ⊗ I^m
    """
    i_m = np.identity(m)
    row_a, col_a = A.shape
    MatrixTensor = np.zeros((n * m * row_a, n * m * col_a), dtype=A.dtype)

    for i in prange(row_a):
        for j in prange(col_a):
            temp_M = A[i, j] * i_m
            for k in range(n):
                start_row_idx = k * m * row_a + i * m
                start_col_idx = k * m * col_a + j * m
                MatrixTensor[start_row_idx:start_row_idx + m, start_col_idx:start_col_idx + m] = temp_M

    return MatrixTensor

VectorPermutation

VectorPermutation(A: ndarray, mapping: ndarray, changeInput: bool = False)

permutaion A with mapping, changeInput

Parameters:

  • A(np.array<np.complex>)

    the matrix A

  • mapping(np.ndarray<int>)

    the qubit mapping

  • changeInput(bool)

    whether changes in A

Returns: np.array: the result of Permutation

Source code in QuICT/ops/linalg/cpu_calculator.py
@njit()
def VectorPermutation(A: np.ndarray, mapping: np.ndarray, changeInput: bool = False):
    """ permutaion A with mapping, changeInput

    Args:
        A(np.array<np.complex>): the matrix A
        mapping(np.ndarray<int>): the qubit mapping
        changeInput(bool): whether changes in A
    Returns:
        np.array<np.complex>: the result of Permutation
    """
    if not A.shape[0] == 1 << mapping.shape[0]:
        raise IndexError("Indices do not match!")

    unchanged_mapping = True
    for i in range(mapping.size):
        if mapping[i] != i:
            unchanged_mapping = False
            break

    if unchanged_mapping:
        return A

    switched_idx = mapping_augment(mapping)

    if changeInput:
        A[:] = A[switched_idx]

    return A[switched_idx]

array_combination

array_combination(A: ndarray, qubits: int, block_qubits: int)

Applying the array combination of A and block qubits

Parameters:

  • A(np.array<np.complex>)

    the state vector A

  • qubits(int)

    the qubit number of A

  • block_qubits(int)

    the block qubit number

Returns:

  • np.array: [1 << qubits - block_qubits]

Source code in QuICT/ops/linalg/cpu_calculator.py
@njit()
def array_combination(A: np.ndarray, qubits: int, block_qubits: int):
    """ Applying the array combination of A and block qubits

    Args:
        A(np.array<np.complex>): the state vector A
        qubits(int): the qubit number of A
        block_qubits(int): the block qubit number

    Returns:
        np.array<np.complex>: [1 << qubits - block_qubits]
    """
    task_number = 1 << (qubits - block_qubits)
    block_dim = 1 << block_qubits
    out_array = np.empty(task_number, dtype=A.dtype)

    for i in prange(task_number):
        s_idx = i * block_dim
        out_array[i] = np.abs(A[s_idx]) * np.abs(A[s_idx])
        for j in range(1, block_dim):
            out_array[i] += np.abs(A[s_idx + j]) * np.abs(A[s_idx + j])

    return out_array

dot

dot(A: ndarray, B: ndarray)

Applying the dot operator between A and B

Parameters:

  • A(np.array<np.complex>)

    the matrix A

  • B(np.array<np.complex>)

    the matrix B

Returns:

  • np.array: A * B

Source code in QuICT/ops/linalg/cpu_calculator.py
@njit()
def dot(A: np.ndarray, B: np.ndarray):
    """ Applying the dot operator between A and B

    Args:
        A(np.array<np.complex>): the matrix A
        B(np.array<np.complex>): the matrix B

    Returns:
        np.array<np.complex>: A * B
    """
    return np.dot(A, B)

matrix_dot_matrix

matrix_dot_matrix(matrix_u: ndarray, matrix_g: ndarray, control_args: ndarray = None, target_args: ndarray = None)

Dot the quantum gate's matrix and qubits'state vector, depending on the target qubits of gate.

Parameters:

  • matrix_u (ndarray) –

    The 2D numpy array, represent the unitary matrix / density matrix

  • matrix_g (ndarray) –

    The 2D numpy array, represent the quantum gate's matrix

  • control_args (ndarray, default: None ) –

    The control qubits of quantum gate

  • target_args (ndarray, default: None ) –

    The target qubits of quantum gate

Raises:

  • TypeError

    matrix and vector should be complex and with same precision

Returns:

  • np.ndarray: updated state vector

Source code in QuICT/ops/linalg/cpu_calculator.py
def matrix_dot_matrix(
    matrix_u: np.ndarray,
    matrix_g: np.ndarray,
    control_args: np.ndarray = None,
    target_args: np.ndarray = None
):
    """ Dot the quantum gate's matrix and qubits'state vector, depending on the target qubits of gate.

    Args:
        matrix_u (np.ndarray): The 2D numpy array, represent the unitary matrix / density matrix
        matrix_g (np.ndarray): The 2D numpy array, represent the quantum gate's matrix
        control_args (np.ndarray): The control qubits of quantum gate
        target_args (np.ndarray): The target qubits of quantum gate

    Raises:
        TypeError: matrix and vector should be complex and with same precision

    Returns:
        np.ndarray: updated state vector
    """
    # Step 1: Calculate mat_bit and vec_bit
    mat_bit = int(np.log2(matrix_u.shape[0]))
    if control_args is None and target_args is None:
        assert mat_bit == int(np.log2(matrix_g.shape[0])), \
            "matrix dot matrix should have same qubits number, if not assigned gate_args."
        np.dot(matrix_g, matrix_u, out=matrix_u)
        return

    # Step 2: Get fixed index of vector by control indexes
    based_idx = 0
    if control_args is not None:
        for carg_idx in control_args:
            based_idx += 1 << carg_idx

    arg_len = 1 << len(target_args)
    indexes = np.repeat(based_idx, arg_len)
    for idx in range(1, arg_len):
        for tidx in range(len(target_args)):
            if idx & (1 << tidx):
                indexes[idx] += 1 << target_args[tidx]

    gate_args = np.append(control_args, target_args) if control_args is not None else target_args
    sorted_args = gate_args.copy()
    sorted_args = np.sort(sorted_args)

    # Step 4: normal matrix * matrix
    _matrix_dot_matrix(matrix_u, mat_bit, matrix_g, len(gate_args), indexes, sorted_args)

multiply

multiply(A: ndarray, B: ndarray)

Applying the multiply operator between A and B

Parameters:

  • A(np.array<np.complex>)

    the matrix A

  • B(np.array<np.complex>)

    the matrix B

Returns:

  • np.array: A x B

Source code in QuICT/ops/linalg/cpu_calculator.py
@njit()
def multiply(A: np.ndarray, B: np.ndarray):
    """ Applying the multiply operator between A and B

    Args:
        A(np.array<np.complex>): the matrix A
        B(np.array<np.complex>): the matrix B

    Returns:
        np.array<np.complex>: A x B
    """
    return np.multiply(A, B)

tensor

tensor(A: ndarray, B: ndarray)

Applying the tensor operator between A and B.

Parameters:

  • A(np.array<np.complex>)

    the matrix A

  • B(np.array<np.complex>)

    the matrix B

Returns:

  • np.array: the tensor result A ⊗ B

Source code in QuICT/ops/linalg/cpu_calculator.py
@njit(parallel=True, nogil=True)
def tensor(A: np.ndarray, B: np.ndarray):
    """ Applying the tensor operator between A and B.

    Args:
        A(np.array<np.complex>): the matrix A
        B(np.array<np.complex>): the matrix B

    Returns:
        np.array<np.complex>: the tensor result A ⊗ B
    """
    row_a, col_a = A.shape
    row_b, col_b = B.shape
    tensor_data = np.empty((row_a * row_b, col_a * col_b), dtype=A.dtype)

    for r in prange(row_a):
        for c in prange(col_a):
            tensor_data[r * row_b:(r + 1) * row_b, c * col_b:(c + 1) * col_b] = A[r, c] * B

    return tensor_data