Skip to content

matrix_expander

expand_ket(base_ket, reduced_ket, qubits)

Given a base quantum ket on n qubits and a reduced ket on a subset of those qubits, this computes the expanded ket where the reduction qubits and the other qubits are set based on the reduced ket and the base ket, respectively. Roughly equivalent to the pdep assembly instruction (bits deposit).

Parameters:

Name Type Description Default
base_ket int

A quantum ket, represented by its corresponding non-negative integer. By convention, qubit #0 corresponds to the least significant bit.

required
reduced_ket int

A quantum ket, represented by its corresponding non-negative integer. By convention, qubit #0 corresponds to the least significant bit.

required
qubits Iterable[Qubit]

The indices of the qubits to expand from the reduced ket. Order matters.

required

Returns:

Type Description
int

The non-negative integer corresponding to the expanded ket.

Examples:

>>> expand_ket(0b00000, 0b0, [Qubit(5)])   # 0b000000
0
>>> expand_ket(0b00000, 0b1, [Qubit(5)])   # 0b100000
32
>>> expand_ket(0b00111, 0b0, [Qubit(5)])   # 0b000111
7
>>> expand_ket(0b00111, 0b1, [Qubit(5)])   # 0b100111
39
>>> expand_ket(0b0000, 0b000, [Qubit(1), Qubit(2), Qubit(3)])    # 0b0000
0
>>> expand_ket(0b0000, 0b001, [Qubit(1), Qubit(2), Qubit(3)])    # 0b0010
2
>>> expand_ket(0b0000, 0b011, [Qubit(1), Qubit(2), Qubit(3)])    # 0b0110
6
>>> expand_ket(0b0000, 0b101, [Qubit(1), Qubit(2), Qubit(3)])   # 0b1010
10
>>> expand_ket(0b0001, 0b101, [Qubit(1), Qubit(2), Qubit(3)])   # 0b1011
11
Source code in opensquirrel\utils\matrix_expander.py
def expand_ket(base_ket: int, reduced_ket: int, qubits: Iterable[Qubit]) -> int:
    """
    Given a base quantum ket on n qubits and a reduced ket on a subset of those qubits, this computes the expanded ket
    where the reduction qubits and the other qubits are set based on the reduced ket and the base ket, respectively.
    Roughly equivalent to the `pdep` assembly instruction (bits deposit).

    Args:
        base_ket: A quantum ket, represented by its corresponding non-negative integer.
                  By convention, qubit #0 corresponds to the least significant bit.
        reduced_ket: A quantum ket, represented by its corresponding non-negative integer.
                     By convention, qubit #0 corresponds to the least significant bit.
        qubits: The indices of the qubits to expand from the reduced ket. Order matters.

    Returns:
        The non-negative integer corresponding to the expanded ket.

    Examples:
        >>> expand_ket(0b00000, 0b0, [Qubit(5)])   # 0b000000
        0
        >>> expand_ket(0b00000, 0b1, [Qubit(5)])   # 0b100000
        32
        >>> expand_ket(0b00111, 0b0, [Qubit(5)])   # 0b000111
        7
        >>> expand_ket(0b00111, 0b1, [Qubit(5)])   # 0b100111
        39
        >>> expand_ket(0b0000, 0b000, [Qubit(1), Qubit(2), Qubit(3)])    # 0b0000
        0
        >>> expand_ket(0b0000, 0b001, [Qubit(1), Qubit(2), Qubit(3)])    # 0b0010
        2
        >>> expand_ket(0b0000, 0b011, [Qubit(1), Qubit(2), Qubit(3)])    # 0b0110
        6
        >>> expand_ket(0b0000, 0b101, [Qubit(1), Qubit(2), Qubit(3)])   # 0b1010
        10
        >>> expand_ket(0b0001, 0b101, [Qubit(1), Qubit(2), Qubit(3)])   # 0b1011
        11
    """
    expanded_ket = base_ket
    for i, qubit in enumerate(qubits):
        expanded_ket &= ~(1 << qubit.index)  # Erase bit.
        expanded_ket |= ((reduced_ket & (1 << i)) >> i) << qubit.index  # Set bit to value from reduced_ket.

    return expanded_ket

get_matrix(gate, qubit_register_size)

Compute the unitary matrix corresponding to the gate applied to those qubit operands, taken among any number of qubits. This can be used for, e.g., - testing, - permuting the operands of multi-qubit gates, - simulating a circuit (simulation in this way is inefficient for large numbers of qubits).

Parameters:

Name Type Description Default
gate Gate

The gate, including the qubits on which it is operated on.

required
qubit_register_size int

The size of the qubit register.

required

Examples:

>>> X = lambda q: BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
>>> get_matrix(X(Qubit(1)), 2).astype(int)           # X q[1]
array([[0, 0, 1, 0],
       [0, 0, 0, 1],
       [1, 0, 0, 0],
       [0, 1, 0, 0]])
>>> CNOT02 = ControlledGate(Qubit(0), X(Qubit(2)))
>>> get_matrix(CNOT02, 3).astype(int)     # CNOT q[0], q[2]
array([[1, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 1, 0],
       [0, 0, 0, 1, 0, 0, 0, 0]])
>>> get_matrix(ControlledGate(Qubit(1), X(Qubit(2))), 3).astype(int)     # CNOT q[1], q[2]
array([[1, 0, 0, 0, 0, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 1, 0],
       [0, 0, 0, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 1, 0, 0, 0],
       [0, 0, 0, 0, 0, 1, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0, 0]])
Source code in opensquirrel\utils\matrix_expander.py
def get_matrix(gate: Gate, qubit_register_size: int) -> NDArray[np.complex128]:
    """
    Compute the unitary matrix corresponding to the gate applied to those qubit operands, taken among any number of
    qubits. This can be used for, e.g.,
    - testing,
    - permuting the operands of multi-qubit gates,
    - simulating a circuit (simulation in this way is inefficient for large numbers of qubits).

    Args:
        gate: The gate, including the qubits on which it is operated on.
        qubit_register_size: The size of the qubit register.

    Examples:
        >>> X = lambda q: BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=math.pi, phase=math.pi / 2)
        >>> get_matrix(X(Qubit(1)), 2).astype(int)           # X q[1]
        array([[0, 0, 1, 0],
               [0, 0, 0, 1],
               [1, 0, 0, 0],
               [0, 1, 0, 0]])

        >>> CNOT02 = ControlledGate(Qubit(0), X(Qubit(2)))
        >>> get_matrix(CNOT02, 3).astype(int)     # CNOT q[0], q[2]
        array([[1, 0, 0, 0, 0, 0, 0, 0],
               [0, 0, 0, 0, 0, 1, 0, 0],
               [0, 0, 1, 0, 0, 0, 0, 0],
               [0, 0, 0, 0, 0, 0, 0, 1],
               [0, 0, 0, 0, 1, 0, 0, 0],
               [0, 1, 0, 0, 0, 0, 0, 0],
               [0, 0, 0, 0, 0, 0, 1, 0],
               [0, 0, 0, 1, 0, 0, 0, 0]])
        >>> get_matrix(ControlledGate(Qubit(1), X(Qubit(2))), 3).astype(int)     # CNOT q[1], q[2]
        array([[1, 0, 0, 0, 0, 0, 0, 0],
               [0, 1, 0, 0, 0, 0, 0, 0],
               [0, 0, 0, 0, 0, 0, 1, 0],
               [0, 0, 0, 0, 0, 0, 0, 1],
               [0, 0, 0, 0, 1, 0, 0, 0],
               [0, 0, 0, 0, 0, 1, 0, 0],
               [0, 0, 1, 0, 0, 0, 0, 0],
               [0, 0, 0, 1, 0, 0, 0, 0]])
    """
    expander = MatrixExpander(qubit_register_size)
    return cast(NDArray[np.complex128], gate.accept(expander))

get_reduced_ket(ket, qubits)

Given a quantum ket represented by its corresponding base-10 integer, this computes the reduced ket where only the given qubits appear, in order. Roughly equivalent to the pext assembly instruction (bits extraction).

Parameters:

Name Type Description Default
ket int

A quantum ket, represented by its corresponding non-negative integer. By convention, qubit #0 corresponds to the least significant bit.

required
qubits Iterable[Qubit]

The indices of the qubits to extract. Order matters.

required

Returns:

Type Description
int

The non-negative integer corresponding to the reduced ket.

Examples:

>>> get_reduced_ket(1, [Qubit(0)])         # 0b01
1
>>> get_reduced_ket(1111, [Qubit(2)])      # 0b01
1
>>> get_reduced_ket(1111, [Qubit(5)])      # 0b0
0
>>> get_reduced_ket(1111, [Qubit(2), Qubit(5)])   # 0b01
1
>>> get_reduced_ket(101, [Qubit(1), Qubit(0)])    # 0b10
2
>>> get_reduced_ket(101, [Qubit(0), Qubit(1)])    # 0b01
1
Source code in opensquirrel\utils\matrix_expander.py
def get_reduced_ket(ket: int, qubits: Iterable[Qubit]) -> int:
    """
    Given a quantum ket represented by its corresponding base-10 integer, this computes the reduced ket
    where only the given qubits appear, in order.
    Roughly equivalent to the `pext` assembly instruction (bits extraction).

    Args:
        ket: A quantum ket, represented by its corresponding non-negative integer.
             By convention, qubit #0 corresponds to the least significant bit.
        qubits: The indices of the qubits to extract. Order matters.

    Returns:
        The non-negative integer corresponding to the reduced ket.

    Examples:
        >>> get_reduced_ket(1, [Qubit(0)])         # 0b01
        1
        >>> get_reduced_ket(1111, [Qubit(2)])      # 0b01
        1
        >>> get_reduced_ket(1111, [Qubit(5)])      # 0b0
        0
        >>> get_reduced_ket(1111, [Qubit(2), Qubit(5)])   # 0b01
        1
        >>> get_reduced_ket(101, [Qubit(1), Qubit(0)])    # 0b10
        2
        >>> get_reduced_ket(101, [Qubit(0), Qubit(1)])    # 0b01
        1
    """
    reduced_ket = 0
    for i, qubit in enumerate(qubits):
        reduced_ket |= ((ket & (1 << qubit.index)) >> qubit.index) << i

    return reduced_ket