Learning Parameterized Quantum Circuits for Quantum Error Correction.
Note - A
requirements.txtfile will soon be added, until then please use the following commands.
Run the appropriate git command to clone this repository.
Linux (Ubuntu):
$ virtualenv .venv
$ source .venv/bin/activateEnsure your virtual environment is activated (you may have to install visrtualenv using sudo update && sudo upgrade && apt install virtualenv)
MacOS:
$ python -m virtualenv .venv
$ source .venv/bin/activateNow, install the following packages.
$ pip install --upgrade pip
$ pip install --upgrade pennylane pennylane-catalyst qiskit[visualization] jax optax torch jupyter pytestWhile in your virtual environment, run the following command:
$ time python scripts/train_tokenize_circuits_mp.py -q 5 -g 20 -k 10 -n 5000 -e 5 -a 20 -t 20 --seed 25 -o nogit/json_data/The output should start with something like this:
Using device: cpu
Final Config after parsing arguments:
{'config': None, 'qubit_range': [5], 'gate_range': [20], 'pqc_blocks': 1, 'gate_blocks': 10, 'figure_output': 'nogit/json_data/', 'epochs': 5, 'num_data': 5000, 'num_test': 20, 'seed': '25', 'gate_dist': None, 'noise_dist': None, 'gpu': False, 'batch': 20, 'force': False, 'redo': False, 'qubits': [5], 'gates': [20], 'device': device(type='cpu')}
Generating atmost 25 set of tokens for Qubits: 5, Gates: 20, Gate Blocks: 10
Config file saved to nogit/json_data/5q_20g_10blk_data/config.json
Starting parallel processing with 7 cores...
After the script has run successfully, you should be able to see something like this:
...
PQC Circuit Fidelity good for seed 22 : 0.977056622505188
Poor PQC Circuit Fidelity for seed 25 : 0.9336086511611938
Poor PQC Circuit Fidelity for seed 24 : 0.94271320104599
7 circuits not saved for the following poor fidelity parameters:
- Qubits: 5, Gates: 20, Seed: 0, PQC Fidelity: 0.933001697063446
- Qubits: 5, Gates: 20, Seed: 5, PQC Fidelity: 0.9121567606925964
...
There should also be json files in a folder nogit/json_data/5q_20g_10blk_data/ - with all the fields populated.
You can also confirm by running the following:
$ PYTHONPATH="${PYTHONPATH}:${PWD}" pytest -vv- checkpoints: Placeholder for saved model or run artifacts (currently empty).
- data: JSON datasets used for experiments.
data/json_data/3q_10g_5blk_data:config.json,0.json,1.json,2.json,3.json,4.json,5.json,poor_fid_params.json.data/json_data/10q_50g_10blk_data:config.json,0.json,4.json,7.json,8.json,9.json,poor_fid_params.json.data/json_data/5q_20g_10blk_data:config.json,poor_fid_params.json, and multiple per-seed results like1.json,2.json,3.json, ...
- plots: Generated figures and results.
Fidelity_3q_10g_seed5.png
- pqcqec: Core Python package for circuits, training, simulation, experiments, utils, and noise (source files only; caches omitted).
pqcqec/__init__.pypqcqec/circuits:__init__.py,generate.py,modify.py,pqc_circuits.pypqcqec/models:__init__.py,pqc_models.pypqcqec/training:__init__.py,jax_loss_functions.py,jax_train_functions.pypqcqec/simulate:__init__.py,simulate.pypqcqec/experiment:__init__.py,pqc_experiment.pypqcqec/utils:__init__.py,args.py,constants.py,jax_utils.pypqcqec/noise:__init__.py,simple_noise.py
- scripts: Utility scripts for training and plotting.
plot_fidelity_experiement.pytrain_tokenize_circuits.pytrain_tokenize_circuits_mp.py
- testnotebooks: Example and exploratory Jupyter notebooks.
jax/JAX_CPU_NewNoiseModel.ipynbtokenizer/NewInputStateGenerator.ipynbtokenizer/RunTokenizedCircuits.ipynbtokenizer/TokenizeQuantumCircuit.ipynb
- nogit: Placeholder for ignored local artifacts (currently empty).
- Root files:
README.md,.gitignore.
This section explains what each module in the pqcqec package does so new users can quickly navigate and extend the code.
pqcqec/__init__.py: Marks the directory as a Python package.
pqcqec/circuits/generate.py: Utilities to synthesize random circuits.generate_random_circuit_qiskit(num_qubits, num_gates, gate_dist): Returns a QiskitQuantumCircuitby sampling gates according togate_dist.generate_random_circuit_pennylane(...): Returns a list of PennyLane operations constructed from sampled gates.generate_random_circuit_list(...): Returns a token list of(gate_name, [wires])without framework objects.generate_random_circuit(..., backend): Convenience wrapper to select backend:'qiskit' | 'pennylane' | 'list'with optionalseedand default uniform gate distribution.
pqcqec/circuits/modify.py: Circuit post-processing helpers.tokenize_qiskit_circuit(circuit): Converts a Qiskit circuit to tokens(gate_name, [wires], [params]).pennylane_state_embedding(input_state, num_qubits): Embeds an arbitrary complex state viaqml.StatePrepacross all wires.
pqcqec/circuits/pqc_circuits.py: Parametrized PQC building blocks.pennylane_PQC_RZRXRZ_unique(num_qubits, params): Applies one layer per qubit of RZ → RX → RZ using a flat parameter vector of length3 * num_qubits.
pqcqec/models/pqc_models.py: Trainable PQC models that interleave learned gates with a target circuit under noise.StateInputModelInterleavedPQCModel:- Initializes with a tokenized uncompensated circuit
circuit_ops,num_qubits, anoise_model(seenoise/simple_noise.py), and layout controlspqc_blocks,gate_blocks. - Builds a PennyLane
QNodethat: (1) embeds an input state, (2) iterates the circuit tokens applying noisy gates, interleaving learned PQC gates (RZ/RX/RZ) after everygate_blocksoperations, and (3) returns the output state. - Parameters shape:
(ceil(num_gates/gate_blocks) * pqc_blocks, num_qubits, 3); trained via JAX/Optax. - Methods:
run_model_batch(batched forward viavmap),draw_mpl(matplotlib circuit drawing),get_circuit_tokens(returns original tokens interleaved with PQC parameter tokens).
- Initializes with a tokenized uncompensated circuit
pqcqec/training/jax_train_functions.py: Training loop utilities using JAX + Optax.train_pqc_model(model, dataloader, optimizer, schedule, main_loss_fn, epochs): Per-epoch loop with a JIT’dupdate_stepcomputing loss, gradients, parameter updates, and per-batch fidelity. Displays running metrics viatqdmand prints epoch summaries.
pqcqec/training/jax_loss_functions.py: Differentiable JAX loss and fidelity functions.jax_pure_state_fidelity(psi, phi): |⟨ψ|φ⟩|² for statevectors (normalized internally).jax_mixed_state_fidelity(rho, sigma): Uhlmann fidelity for density matrices.- Phase-invariant and complex MSE losses:
jax_mse_complex_loss,jax_mse_complex_loss_aligned,jax_l2_loss_ignore_global_phase. - Alternatives:
jax_fidelity_loss,jax_mixed_fidelity_loss,jax_density_trace_loss,jax_hilbert_schmidt_density_loss.
pqcqec/simulate/simulate.py: Data generation and noisy circuit execution.get_input_data(num_qubits, num_vals, seed): Samples random complex vectors and normalizes to valid quantum states.run_circuit_with_noise_model(circuit_ops, input_state, noise_model, num_qubits, device, batched): Builds a PennyLane circuit that embedsinput_state, applies tokenized gates through the providednoise_model, and returns the final state (supports batched vmap execution).
pqcqec/experiment/pqc_experiment.py: End-to-end experiment orchestration.pqc_experiment_runner(...):- Seeds PRNGs; generates train/test data via
simulate.get_input_data. - Instantiates
PennylaneNoisyGatesfromnoise.simple_noise. - Synthesizes a random Qiskit circuit, forms its inverse, composes an uncompensated circuit, and tokenizes it.
- Builds
StateInputModelInterleavedPQCModelwith interleaved learned gates. - Defines a warmup + cosine-restart learning-rate schedule and an Optax optimizer chain.
- Trains the model; evaluates batched fidelities on test data vs. noisy and PQC outputs; returns metrics and final parameters.
- Seeds PRNGs; generates train/test data via
pqcqec/utils/constants.py: Common mappings/constants.- Gate maps for Qiskit/PennyLane (
QISKIT_GATES,PENNYLANE_GATES), arity (QUBITS_FOR_GATES), and placeholders for PQC/model registries.
- Gate maps for Qiskit/PennyLane (
pqcqec/utils/args.py: Flexible CLI/config handling for experiments.- Central
ARG_DEFINITIONSregistry; parser creation; config loading; argument precedence (CLI > config > defaults); normalization helpers for ranges; output directory setup and device selection; returns a validated config dict.
- Central
pqcqec/utils/jax_utils.py: Lightweight dataset/dataloader for JAX arrays.JAXStateDataset(indexable array wrapper) andJAXDataLoader(permutes indices, batches, stacks tensors, supportsdrop_last).
pqcqec/noise/simple_noise.py: Noisy gate model for PennyLane simulations.PennylaneNoisyGates: Applies ideal gates then injects random RX/RZ over-rotations per wire; supportsX/Z/CX/CZ/Hplus parameterizedRX/RZfor PQC layers. Noise magnitudes are configurable; exposesapply_gate(gate_name, wires, angle=None)to route tokens to the correct noisy/parametrized implementation.
Utilities to run experiments, serialize tokenized circuits/parameters, and visualize fidelity.
-
scripts/train_tokenize_circuits.py: Single‑process training + tokenization over seeds.- Purpose: Iterates seeds serially for each
(qubits, gates)configuration, runspqc_experiment_runner, and writes per‑seed outputs when PQC fidelity exceeds 0.95. - Flow: Creates
figure_output/{qubits}q_{gates}g_{gate_blocks}blk_data, writesconfig.json, loads/updatespoor_fid_params.json, loops seeds with redo/force/skip logic, saves{seed}.jsoncontaining tokens, PQC params, and QASM for base/PQC circuits; accumulates poor‑fidelity tuples. - Helpers:
create_circuit_from_ops(tokens → Qiskit circuit),deep_tuple(normalize nested lists to tuples for comparison/storage). - CLI (via
utils/args.py):--qubit_range,--gate_range,--gate_blocks,--pqc_blocks,--epochs,--num_data,--num_test,--batch,--gpu,--gate_dist,--figure_output,--seed,--force,--redo.
- Purpose: Iterates seeds serially for each
-
scripts/train_tokenize_circuits_mp.py: Multiprocessing variant across seeds.- Purpose: Same outputs as the serial script, but dispatches per‑seed runs across a pool (half of CPU cores) using
process_seedworkers. - Key pieces:
process_seed(args)returns status tuples for each seed (success/poor_fidelity/skipped/error) to the main process, which writes{seed}.json, updatespoor_fid_params.json, and collects run stats. - Helpers/CLI: Shares the same helpers and argument set as the serial script; additionally writes
mp_stats.jsonwhen--forceis used.
- Purpose: Same outputs as the serial script, but dispatches per‑seed runs across a pool (half of CPU cores) using
-
scripts/plot_fidelity_experiement.py: Single‑run training and fidelity plot.- Purpose: Runs one configuration using the first entries of
--qubit_rangeand--gate_range, retrieves(fidelity_noisy, fidelity_pqc)frompqc_experiment_runner(return_fidelity=True), and saves a comparison plot. - Output:
figure_output/Fidelity_{qubits}q_{gates}g_seed{seed}.pngwith both series.
- Purpose: Runs one configuration using the first entries of