QVM v0.4.0
[GitHub]
Docs/Exact Statevector Simulation

🧮 Exact Statevector Simulation

The Statevector Simulator, implemented in simulator.py, is the exact execution engine of the QVM. It represents the quantum state as a complex vector and applies unitary matrices to evolve the state.


💾 Mathematical State Representation

For an NN-qubit system, the statevector is a 1D1\text{D} complex NumPy array of size 2N2^N (dtype=complex128): ∣ψ⟩=∑x=02N−1αx∣x⟩=α0∣0…00⟩+α1∣0…01⟩+⋯+α2N−1∣1…11⟩|\psi\rangle = \sum_{x=0}^{2^N-1} \alpha_x |x\rangle = \alpha_0|0\dots00\rangle + \alpha_1|0\dots01\rangle + \dots + \alpha_{2^N-1}|1\dots11\rangle

Under the Born Rule, the amplitudes must satisfy the normalization condition: ∑x=02N−1∣αx∣2=1\sum_{x=0}^{2^N-1} |\alpha_x|^2 = 1

Little-Endian Qubit Ordering

QVM uses the Little-Endian convention:

  • Qubit 00 is the Least Significant Bit (LSB) (rightmost in bitstrings).
  • State index 11 (binary …001\dots001) corresponds to state ∣0…01⟩|0\dots01\rangle (where Qubit 00 is 11, and all others are 00).
  • In tensor products, Qubit 00 is the fastest-changing index (rightmost in Kronecker products).

⚡ Vectorized Gate Application

Applying a gate to target qubits requires evolving the statevector ∣ψ⟩→U∣ψ⟩|\psi\rangle \to U |\psi\rangle. To simulate this efficiently in Python, the simulator uses NumPy vectorization instead of loops.

1. Single-Qubit Gates (Tensor Product)

For a single-qubit gate UU acting on target qubit kk, the global operator UglobalU_{\text{global}} is: Uglobal=I⊗(N−1−k)⊗U⊗I⊗kU_{\text{global}} = I^{\otimes (N-1-k)} \otimes U \otimes I^{\otimes k}

The simulator builds this 2N×2N2^N \times 2^N matrix using np.kron and multiplies it by the statevector:

python
def _apply_single_qubit_gate(self, state, gate, target, n):
    op_list = [self.I] * n
    op_list[n - 1 - target] = gate  # Reverse index for little-endian order
    full_op = op_list[0]
    for i in range(1, n):
        full_op = np.kron(full_op, op_list[i])
    return full_op @ state

2. Multi-Qubit Gates (Vectorized Index Permutation)

Creating a 2N×2N2^N \times 2^N matrix for multi-qubit gates (like CNOT, CZ, SWAP) becomes memory intensive as NN grows. To avoid this, QVM uses vectorized index permutation to update the statevector directly.

A. CNOT Gate (Control cc, Target tt)

A CNOT gate flips the target qubit tt if the control qubit cc is 11.

  • Permutation Rule: For all indices where the bit at position cc is 11, swap the amplitude with the index where the bit at position tt is flipped.
python
def _apply_cnot_gate(self, state, ctrl, target, n):
    indices = np.arange(2**n)
    # 1. Find indices where control bit is 1
    mask = (indices >> ctrl) & 1 == 1
    # 2. Compute partner indices by flipping target bit
    perm = indices.copy()
    perm[mask] = indices[mask] ^ (1 << target)
    # 3. Swap amplitudes via index slicing
    return state[perm]

B. SWAP Gate (Qubits q1,q2q_1, q_2)

A SWAP gate swaps the states of qubits q1q_1 and q2q_2.

  • Permutation Rule: For all indices where the bit at position q1q_1 differs from the bit at position q2q_2, swap their amplitudes.
python
def _apply_swap_gate(self, state, q1, q2, n):
    indices = np.arange(2**n)
    # Find indices where bit_q1 != bit_q2
    diff = ((indices >> q1) & 1) != ((indices >> q2) & 1)
    perm = indices.copy()
    # Flip both bits to find swap partner
    perm[diff] = indices[diff] ^ ((1 << q1) | (1 << q2))
    return state[perm]

C. CZ Gate (Control cc, Target tt)

A Controlled-Z gate applies a phase flip (multiplies by −1-1) only if both qubits are in the ∣1⟩|1\rangle state.

  • Permutation Rule: Identify indices where both bits at positions cc and tt are 11, and multiply those amplitudes by −1-1.
python
def _apply_cz_gate(self, state, ctrl, target, n):
    indices = np.arange(2**n)
    mask = ((indices >> ctrl) & 1 == 1) & ((indices >> target) & 1 == 1)
    result = state.copy()
    result[mask] = -result[mask]
    return result

📉 Projective Measurement and State Collapse

Mid-circuit measurement causes the quantum state to collapse. When measuring a subset of qubits QQ:

  1. Calculate Probabilities: Computes the probability of each outcome on the measured qubits by summing the squared magnitudes of the matching statevector entries: P(outcome)=∑x∈matching∣αx∣2P(\text{outcome}) = \sum_{x \in \text{matching}} |\alpha_x|^2
  2. Stochastic Choice: Samples an outcome based on these probabilities.
  3. State Projection: Set amplitudes of non-matching states to 00: αx→0∀x∉matching\alpha_x \to 0 \quad \forall x \notin \text{matching}
  4. Renormalization: Renormalizes the remaining statevector to preserve probability conservation: ∣ψcollapsed⟩=∣ψprojected⟩∥∣ψprojected⟩∥|\psi_{\text{collapsed}}\rangle = \frac{|\psi_{\text{projected}}\rangle}{\| |\psi_{\text{projected}}\rangle \|}
python
# Project and collapse slice
collapsed = np.zeros_like(statevector)
collapsed[mask] = statevector[mask]
collapsed = collapsed / np.linalg.norm(collapsed)

🖥️ Classical Register Operations (classical_op)

The simulator contains a classical register execution engine. When it encounters a classical_op instruction, it evaluates classical bitwise expressions:

OperatorActionImplementation
=Direct copymem[target] = val
&Bitwise ANDmem[target] = val1 & val2
|Bitwise ORmem[target] = val1 | val2
^Bitwise XORmem[target] = val1 ^ val2
~Bitwise NOTmem[target] = ~val1 & 1 (keeps it single bit)

This classical logic engine allows QVM to run hybrid quantum-classical algorithms, where future gate decisions are based on classical registers updated by mid-circuit measurements.