create a quantum circuit simulator without any quantum libraries in python

To create a simple quantum circuit simulator without using any quantum libraries in Python, we can represent the state of our qubits using the complex probability amplitudes. Probability amplitudes are complex numbers that represent the probability of the qubit being in a certain state (0 or 1). Then, we can define operations such as Hadamard gates, Pauli gates, and CNOT gates in terms of matrix multiplication.

Here is a simple implementation of a quantum circuit simulator:

main.py
import numpy as np

class QuantumCircuit:
    def __init__(self, num_qubits):
        self.num_qubits = num_qubits
        self.state = np.zeros(2**num_qubits, dtype=np.complex64)
        self.state[0] = 1.0  # initial state is |0>

    def h(self, qubit):
        """Applies a Hadamard gate to the given qubit."""
        H = np.array([[1, 1], [1, -1]]) / np.sqrt(2)
        self.apply_gate(qubit, H)

    def x(self, qubit):
        """Applies a Pauli-X gate to the given qubit."""
        X = np.array([[0, 1], [1, 0]])
        self.apply_gate(qubit, X)

    def cx(self, control, target):
        """Applies a CNOT gate to the given qubits."""
        CNOT = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
        self.apply_two_qubit_gate(control, target, CNOT)

    def apply_gate(self, qubit, gate):
        """Applies a gate to a single qubit."""
        gate_matrix = self.get_gate_matrix(qubit, gate)
        self.state = np.dot(gate_matrix, self.state)

    def apply_two_qubit_gate(self, control, target, gate):
        """Applies a gate to two qubits."""
        gate_matrix = self.get_two_qubit_gate_matrix(control, target, gate)
        self.state = np.dot(gate_matrix, self.state)

    def get_gate_matrix(self, qubit, gate):
        """Returns the matrix for the given single-qubit gate."""
        gate_matrix = np.eye(2, dtype=np.complex64)
        if qubit == 0:
            gate_matrix = np.kron(gate, gate_matrix)
        else:
            gate_matrix = np.kron(gate_matrix, gate)
        return gate_matrix

    def get_two_qubit_gate_matrix(self, control, target, gate):
        """Returns the matrix for the given two-qubit gate."""
        gate_matrix = np.eye(2, dtype=np.complex64)
        gate_matrix = np.kron(gate_matrix, np.eye(2**max(control, target)-2))
        gate_matrix = np.kron(gate_matrix, gate)
        gate_matrix = np.kron(gate_matrix, np.eye(2**self.num_qubits-2**max(control, target)))
        gate_matrix = gate_matrix.reshape(2**self.num_qubits, 2**self.num_qubits)
        return gate_matrix

    def measure(self, qubit):
        """Measures the given qubit in the standard basis."""
        probabilities = np.abs(self.state)**2
        outcome = np.random.choice(2**self.num_qubits, p=probabilities)
        collapse_mask = np.zeros(2**self.num_qubits)
        collapse_mask[outcome] = 1
        collapse_matrix = np.diag(collapse_mask)
        self.state = np.dot(collapse_matrix, self.state)
        return outcome

    def run(self, program):
        """Runs a quantum program on the simulator."""
        for gate in program:
            gate_type, args = gate[0], gate[1:]
            if gate_type == 'h':
                self.h(*args)
            elif gate_type == 'x':
                self.x(*args)
            elif gate_type == 'cx':
                self.cx(*args)
            else:
                raise ValueError(f"Invalid gate type {gate_type}.")
    
    def get_state(self):
        """Returns the current state of the qubits."""
        return self.state

3043 chars
79 lines

We can create a QuantumCircuit object with a given number of qubits, and then apply gates and measurement to the qubits. Here's an example:

main.py
qc = QuantumCircuit(num_qubits=2)
qc.run([('h', 0), ('cx', 0, 1)])
print(qc.get_state())
89 chars
4 lines

This would apply a Hadamard gate to the first qubit, and then a CNOT gate with the first qubit as the control and the second qubit as the target. The program would then print the current state of the qubits.

main.py
[0.70710677+0.j 0.        +0.j 0.        +0.j 0.70710677+0.j]
62 chars
2 lines

This output represents the state |01> with equal probability of measuring 0 or 1 in the standard basis.

gistlibby LogSnag