Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,5 @@ dmypy.json
data/*
plots/*
*.lock
.vscode/*
.vscode/*
prof/*
118 changes: 107 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,14 @@ T1/T2 thermal relaxation applied after each gate:

**Noise-Aware VQC**: Same architecture, trained with simulated T1/T2 noise using density matrix evolution.

**Quantum Kernel**: Feature-map based classifier using quantum state overlap. No gradient-based training—predicts labels via kernel sum against training set. Useful for comparing encoding methods under varying noise.

## Usage

### Trainable Models (VQC, Noise-Aware QNN)

```bash
# Default run
# Default run (all models, all encodings, all datasets)
python run_experiments.py

# Custom parameters
Expand All @@ -53,15 +57,92 @@ python run_experiments.py --epochs 50 --T1 50 --T2 100
python run_experiments.py --models deep_vqc --encodings angle --datasets moons --plot
```

| Arg | Default | Options |
|-----|---------|---------|
| `--epochs` | 25 | |
| `--T1` | 100 | T1 relaxation (μs) |
| `--T2` | 200 | T2 dephasing (μs) |
| `--models` | all | `deep_vqc`, `noise_aware` |
| `--encodings` | all | `angle`, `amplitude` |
| `--datasets` | all | `real`, `moons` |
| `--plot` | off | Save comparison plots |
### Quantum Kernel Model

```bash
# Run kernel with default noise parameters
python run_experiments.py --kernel

# Run kernel without noise (noiseless simulation)
python run_experiments.py --kernel-noiseless

# Run kernel with noise sweep (tests T1 = 25, 50, 100, 200, 500 μs)
python run_experiments.py --kernel-noise-sweep --plot

# Run kernel for specific encoding/dataset
python run_experiments.py --kernel --encodings angle --datasets moons

# Compare kernel results with plots
python run_experiments.py --kernel --kernel-compare
```

### Combined Runs

```bash
# Run all models including kernel
python run_experiments.py --models deep_vqc noise_aware kernel --plot

# Run specific models with kernel
python run_experiments.py --models kernel noise_aware --encodings angle --datasets moons
```

### Unified Noise Sweep (Accuracy vs T1 Plot)

Run all models (VQC, QNN, Kernel) across varying T1 noise levels and generate comparison plots:

```bash
# Run noise sweep with default T1 values (25, 50, 100, 200, 500 μs)
python run_experiments.py --noise-sweep --plot

# Custom T1 values and epochs
python run_experiments.py --noise-sweep --epochs 10 --T1-values 50 100 200 --plot

# Noise sweep on specific dataset
python run_experiments.py --noise-sweep --datasets moons --epochs 25 --plot

# Custom T2/T1 ratio (default is 2.0)
python run_experiments.py --noise-sweep --T2-ratio 1.5 --plot
```

This generates a plot with **T1 (μs) on x-axis** and **Final Accuracy on y-axis** for all 6 configurations:
- VQC Angle, VQC Amplitude
- QNN Angle, QNN Amplitude
- Kernel Angle, Kernel Amplitude

### Accuracy vs Epoch (Per-Model Lines)

When running noise sweeps with `--plot`, the runner also saves per-epoch accuracy histories and generates accuracy-vs-epoch plots (kernel appears as a flat dashed line):

```bash
# Generate sweep + per-epoch history and plots
python run_experiments.py --noise-sweep --epochs 100 --T1-values 50 100 200 --plot

# Plot from a saved history CSV later (optional)
python plot_existing_history.py --history-file data/noise_sweep_moons_ep100_<timestamp>_history.csv --dataset moons --save
```

Artifacts:
- Data: `data/noise_sweep_<dataset>_ep<epochs>_<timestamp>_history.csv` (columns: model, encoding, T1, epoch, accuracy)
- Plots: saved under `plots/` with filenames like `accuracy_vs_epoch_<dataset>_ep<epochs>_t1<T1>_<timestamp>.png`

## CLI Arguments

| Arg | Default | Options | Description |
|-----|---------|---------|-------------|
| `--epochs` | 25 | int | Training epochs for VQC/QNN |
| `--T1` | 100 | float | T1 relaxation (μs) |
| `--T2` | 200 | float | T2 dephasing (μs) |
| `--models` | all | `deep_vqc`, `noise_aware`, `kernel` | Models to run |
| `--encodings` | all | `angle`, `amplitude` | Encoding methods |
| `--datasets` | all | `real`, `moons` | Datasets to use |
| `--plot` | off | flag | Save comparison plots |
| `--kernel` | off | flag | Run kernel experiments |
| `--kernel-noiseless` | off | flag | Run kernel without noise |
| `--kernel-noise-sweep` | off | flag | Run kernel with varying noise levels |
| `--kernel-compare` | off | flag | Plot kernel comparison charts |
| `--noise-sweep` | off | flag | Run all models with varying T1 values |
| `--T1-values` | 25 50 100 200 500 | floats | T1 values (μs) for noise sweep |
| `--T2-ratio` | 2.0 | float | T2/T1 ratio for noise sweep |

## Datasets

Expand All @@ -78,4 +159,19 @@ python run_experiments.py --models deep_vqc --encodings angle --datasets moons -
pip install -e .
```

Requires Python ≥3.8, PyTorch ≥2.0, NumPy, Matplotlib, scikit-learn.
Requires Python 3.11, PyTorch ≥2.0, NumPy, Matplotlib, scikit-learn.

## Environment Setup

Use `uv` to create a local 3.11 environment (reads `pyproject.toml`):

```bash
uv sync
source .venv/bin/activate
```

Or run without activating:

```bash
uv run python run_experiments.py --help
```
596 changes: 355 additions & 241 deletions demos/circuit_visualization.ipynb

Large diffs are not rendered by default.

30 changes: 25 additions & 5 deletions error_kraus.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import List, Tuple, Dict
import math
import torch
from constants import DEFAULT_GATE_DURATIONS

# ============================================================
# Notes on Realistic Superconducting Transmon Parameters
Expand Down Expand Up @@ -176,7 +177,7 @@ def add_time_based_noise(
num_qubits: int,
T1: float,
T2: float,
gate_durations: Dict[str, float],
gate_durations: Dict[str, float] | None = None,
gate_noise_fraction: float = 1.0,
) -> List[Tuple]:
"""
Expand All @@ -199,10 +200,14 @@ def add_time_based_noise(
Returns:
Extended circuit with T1T2_NOISE operations inserted.
"""
if gate_durations is None:
gate_durations = dict(DEFAULT_GATE_DURATIONS)

gate_noise_fraction = max(0.0, min(1.0, gate_noise_fraction)) # clamp to [0, 1]

accounted_for_time = [0.0] * num_qubits
noisy_circuit: List[Tuple] = []
relax_cache: Dict[float, Tuple[float, float]] = {}

for op in circuit:
name = op[0]
Expand All @@ -220,7 +225,12 @@ def add_time_based_noise(
for q in acted_on:
if accounted_for_time[q] < max_time:
idle = max_time - accounted_for_time[q]
λ1, λ2 = thermal_relaxation_error_rate(T1, T2, idle)
cache_key = (idle, T1, T2)
if cache_key in relax_cache:
λ1, λ2 = relax_cache[cache_key]
else:
λ1, λ2 = thermal_relaxation_error_rate(T1, T2, idle)
relax_cache[cache_key] = (λ1, λ2)
if λ1 > 0.0 or λ2 > 0.0:
noisy_circuit.append(
("T1T2_NOISE", [q], λ1, λ2, idle)
Expand All @@ -231,9 +241,14 @@ def add_time_based_noise(
# This models T1/T2 relaxation that occurs even while a gate is applied
if gate_noise_fraction > 0.0 and time_to_elapse > 0.0:
noise_time = time_to_elapse * gate_noise_fraction
for q in acted_on:
cache_key = (noise_time, T1, T2)
if cache_key in relax_cache:
λ1, λ2 = relax_cache[cache_key]
else:
λ1, λ2 = thermal_relaxation_error_rate(T1, T2, noise_time)
if λ1 > 0.0 or λ2 > 0.0:
relax_cache[cache_key] = (λ1, λ2)
if λ1 > 0.0 or λ2 > 0.0:
for q in acted_on:
noisy_circuit.append(
("T1T2_NOISE", [q], λ1, λ2, noise_time)
)
Expand All @@ -248,7 +263,12 @@ def add_time_based_noise(
for q in range(num_qubits):
if accounted_for_time[q] < end_time:
idle = end_time - accounted_for_time[q]
λ1, λ2 = thermal_relaxation_error_rate(T1, T2, idle)
cache_key = (idle, T1, T2)
if cache_key in relax_cache:
λ1, λ2 = relax_cache[cache_key]
else:
λ1, λ2 = thermal_relaxation_error_rate(T1, T2, idle)
relax_cache[cache_key] = (λ1, λ2)
if λ1 > 0.0 or λ2 > 0.0:
noisy_circuit.append(
("T1T2_NOISE", [q], λ1, λ2, idle)
Expand Down
107 changes: 107 additions & 0 deletions plot_existing_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""
Load and plot per-epoch accuracy history from existing noise sweep data.

Since the existing noise_sweep_moons_ep100_20_0400.csv only has final accuracies,
this script shows you how to regenerate the data with history tracking, or plot
from the new history CSV files when they become available.

Usage:
python plot_existing_history.py --history-file data/noise_sweep_moons_ep100_20_0400_history.csv
"""

import argparse
import csv
import os
from visualizer import plot_accuracy_vs_epoch


def load_history_from_csv(filepath):
"""
Load per-epoch history from CSV file.

Expected format: model, encoding, T1, epoch, accuracy

Returns:
List of result dicts with 'acc_history' populated
"""
# Group by (model, encoding, T1)
grouped_data = {}

with open(filepath, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
key = (row['model'], row['encoding'], float(row['T1']))

if key not in grouped_data:
grouped_data[key] = {
'model_type': row['model'],
'encoding': row['encoding'],
'T1': float(row['T1']),
'acc_history': []
}

grouped_data[key]['acc_history'].append(float(row['accuracy']))

return list(grouped_data.values())


def main():
parser = argparse.ArgumentParser(
description='Plot accuracy vs epoch from history CSV file'
)
parser.add_argument(
'--history-file',
type=str,
help='Path to history CSV file (e.g., data/noise_sweep_moons_ep100_20_0400_history.csv)'
)
parser.add_argument(
'--dataset',
type=str,
default='moons',
help='Dataset name for plot title (default: moons)'
)
parser.add_argument(
'--T1-filter',
type=float,
default=None,
help='Filter by specific T1 value (optional)'
)
parser.add_argument(
'--save',
action='store_true',
help='Save plot to file'
)

args = parser.parse_args()

if not args.history_file:
print("Error: --history-file is required")
print("\nTo generate history data, re-run your experiment:")
print(" python run_experiments.py --noise-sweep --epochs 100 --T1-values 50 100 200 --plot")
print("\nThis will create a *_history.csv file with per-epoch accuracy data.")
return

if not os.path.exists(args.history_file):
print(f"Error: File not found: {args.history_file}")
return

print(f"Loading history from: {args.history_file}")
results = load_history_from_csv(args.history_file)
print(f"Loaded {len(results)} model configurations")

# Infer epochs from data
epochs = max(len(r['acc_history']) for r in results) if results else 0

# Create plot
plot_accuracy_vs_epoch(
results,
args.dataset,
epochs,
save=args.save,
T1_filter=args.T1_filter
)


if __name__ == '__main__':
main()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
name = "compsci648-qml"
version = "0.1.0"
description = "Quantum Machine Learning Project for COMPSCI648"
requires-python = ">=3.8"
requires-python = ">=3.11,<3.12"
dependencies = [
"torch>=2.0.0,<2.3.0",
"numpy>=1.24.0,<2.0.0",
Expand Down
Loading