← projects

Quantum circuit simulator

I wrote a quantum circuit simulator in NumPy without any quantum library, and its final states match Qiskit to within 1e-9 on every standard gate and on 30 random circuits.

Python, React, quantum computing

source code

What it is

I built a quantum circuit simulator from scratch in Python with NumPy, and it doesn't use Qiskit or any other quantum library. A FastAPI backend runs the simulation. A React front end lets you build a circuit by drag and drop, and it shows each qubit on a 3D Bloch sphere, which pictures the state of one qubit.

The most useful part is how I checked it. A test suite runs every standard gate and 30 random circuits on both my simulator and Qiskit, and the final states must agree to within 1e-9.

The backend also runs Grover's search, the quantum Fourier transform, phase estimation, VQE for the H₂ molecule, QAOA, and three error correcting codes. VQE and QAOA tune a circuit with a classical optimizer. VQE finds the lowest energy of a molecule, and QAOA solves graph problems such as MaxCut.

How it works

The state of n qubits is a vector of 2^n complex amplitudes. When a circuit has noise, the simulator uses a density matrix instead. A density matrix is a 2^n by 2^n matrix, and it can also describe a mix of states with given probabilities.

Gates are applied by tensor contraction. The code reshapes the vector into a tensor with one axis per qubit, and it multiplies the gate into only the axes the gate touches. Each gate then costs O(2^n) instead of O(4^n). The density matrix code reuses the same kernel on its rows and its columns.

When all measurements come at the end, the executor simulates the gates once and draws every shot from the final distribution. A WebSocket lets the front end step through a circuit one gate at a time.

Decisions

  • I used the state vector as the main representation, because it is exact and works for any gate. Tensor network and stabilizer methods scale better, but only for restricted kinds of circuits.
  • I use a density matrix only when there is noise. Mixed states need it, but it squares the memory cost.
  • I fixed one qubit ordering for the whole engine, and the gate kernel enforces it. An earlier version hid a mismatch with a table that reordered qubits by gate name, and I removed that table.
  • apply_gate returns a new state instead of changing the old one. Snapshots for step by step execution are then simple, and bugs from shared state go away.

What was hard

  • Speed. The first version built a full 2^n by 2^n matrix for every gate, and it filled gates on more than one qubit entry by entry in a Python loop. I rewrote gate application as a tensor contraction in commit 8bdd5aa.
  • Grover's search broke above 3 qubits. The old multi-controlled Z gate was a simplified chain of Toffoli gates, and I replaced it with a recursive decomposition that needs no extra helper qubits.
  • The executor ignored the X and Y measurement bases. It now rotates into the Z basis, measures, and rotates back.
  • The map of entanglement between pairs of qubits gave the same value for every pair that shared a first qubit. I changed it to the concurrence of each pair, which is a standard measure of how entangled two qubits are.

Results

  • Agreement with Qiskit. tests/test_qiskit_validation.py applies each standard gate on every qubit placement, and it runs 30 random circuits of 2 to 5 qubits with a depth of 20. My states must reach a fidelity above 1 minus 1e-9 with Qiskit's Statevector, where fidelity measures how close two quantum states are. The suite runs in CI on Python 3.10, 3.11 and 3.12.
  • Speed. My design notes record that one layer of gates on 10 qubits went from about 19 seconds to a few milliseconds after the rewrite.
  • Grover's search. The tests require a success probability above 0.9 for 2 to 5 qubits, within 1e-8 of the value given by the formula.

What I would do next

My design notes list the known limits. The practical size is about 14 qubits, and the hard limit is 20. The server also only accepts requests from local development addresses, so a public deployment needs its own configuration.

← projets

Simulateur de circuits quantiques

J'ai écrit un simulateur de circuits quantiques en NumPy sans bibliothèque quantique, et ses états finaux correspondent à ceux de Qiskit à 1e-9 près pour chaque porte standard et 30 circuits aléatoires.

Python, React, informatique quantique

code source

Ce que c'est

J'ai construit un simulateur de circuits quantiques de zéro en Python avec NumPy, et il n'utilise ni Qiskit ni aucune autre bibliothèque quantique. Un backend FastAPI fait la simulation. Une interface React permet de construire un circuit par glisser-déposer, et elle montre chaque qubit sur une sphère de Bloch en 3D, qui représente l'état d'un qubit.

La partie la plus utile est la façon dont je l'ai vérifié. Une suite de tests exécute chaque porte standard et 30 circuits aléatoires sur mon simulateur et sur Qiskit, et les états finaux doivent concorder à 1e-9 près.

Le backend exécute aussi la recherche de Grover, la transformée de Fourier quantique, l'estimation de phase, VQE pour la molécule H₂, QAOA et trois codes correcteurs d'erreurs. VQE et QAOA ajustent un circuit avec un optimiseur classique. VQE trouve l'énergie la plus basse d'une molécule, et QAOA résout des problèmes de graphes comme MaxCut.

Comment ça marche

L'état de n qubits est un vecteur de 2^n amplitudes complexes. Quand un circuit a du bruit, le simulateur utilise plutôt une matrice densité. Une matrice densité est une matrice de 2^n par 2^n, et elle peut aussi décrire un mélange d'états avec des probabilités données.

Les portes sont appliquées par contraction tensorielle. Le code transforme le vecteur en tenseur avec un axe par qubit, et il multiplie la porte seulement dans les axes qu'elle touche. Chaque porte coûte alors O(2^n) au lieu de O(4^n). Le code des matrices densité réutilise le même noyau sur ses lignes et ses colonnes.

Quand toutes les mesures sont à la fin, l'exécuteur simule les portes une seule fois et tire chaque mesure de la distribution finale. Un WebSocket permet à l'interface d'avancer dans un circuit une porte à la fois.

Décisions

  • J'ai pris le vecteur d'état comme représentation principale, parce qu'il est exact et fonctionne pour toutes les portes. Les réseaux de tenseurs et les méthodes de stabilisateurs passent mieux à l'échelle, mais seulement pour certains types de circuits.
  • J'utilise une matrice densité seulement quand il y a du bruit. Les états mixtes en ont besoin, mais elle met au carré le coût en mémoire.
  • J'ai fixé un seul ordre des qubits pour tout le moteur, et le noyau des portes l'impose. Une version antérieure cachait une incohérence avec une table qui réordonnait les qubits selon le nom de la porte, et j'ai retiré cette table.
  • apply_gate renvoie un nouvel état au lieu de modifier l'ancien. Les instantanés pour l'exécution pas à pas sont alors simples, et les bogues dus à l'état partagé disparaissent.

Ce qui était difficile

  • La vitesse. La première version construisait une matrice complète de 2^n par 2^n pour chaque porte, et elle remplissait les portes sur plusieurs qubits case par case dans une boucle Python. J'ai réécrit l'application des portes en contraction tensorielle dans le commit 8bdd5aa.
  • La recherche de Grover ne marchait plus au-delà de 3 qubits. L'ancienne porte Z multi-contrôlée était une chaîne simplifiée de portes de Toffoli, et je l'ai remplacée par une décomposition récursive qui n'a pas besoin de qubits auxiliaires.
  • L'exécuteur ignorait les bases de mesure X et Y. Il tourne maintenant vers la base Z, mesure, puis revient.
  • La carte de l'intrication entre paires de qubits donnait la même valeur pour toutes les paires qui partageaient un premier qubit. Je l'ai remplacée par la concurrence de chaque paire, une mesure standard de l'intrication de deux qubits.

Résultats

  • Concordance avec Qiskit. tests/test_qiskit_validation.py applique chaque porte standard sur chaque placement de qubits, et exécute 30 circuits aléatoires de 2 à 5 qubits d'une profondeur de 20. Mes états doivent atteindre une fidélité supérieure à 1 moins 1e-9 avec le Statevector de Qiskit, la fidélité mesurant à quel point deux états quantiques sont proches. La suite tourne en intégration continue sur Python 3.10, 3.11 et 3.12.
  • Vitesse. Mes notes de conception indiquent qu'une couche de portes sur 10 qubits est passée d'environ 19 secondes à quelques millisecondes après la réécriture.
  • Recherche de Grover. Les tests exigent une probabilité de succès supérieure à 0,9 pour 2 à 5 qubits, à 1e-8 près de la valeur donnée par la formule.

La suite

Mes notes de conception listent les limites connues. La taille pratique est d'environ 14 qubits, et la limite stricte est de 20. Le serveur n'accepte aussi que les requêtes venant d'adresses de développement locales, donc un déploiement public a besoin de sa propre configuration.