Skip to content

Commit 734795e

Browse files
committed
Add VQC and quantum-kernel benchmarking framework
Implement EncodingBenchmark and evaluate_encoding to compare encodings on classification tasks with variational quantum classifiers and quantum-kernel SVMs, paired stratified cross-validation, classical baselines, and statistical testing (Wilcoxon + Holm-Bonferroni + Cliff's delta), replacing the previously stubbed runner.
1 parent 7a538f8 commit 734795e

15 files changed

Lines changed: 1998 additions & 37 deletions

File tree

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,33 @@ print(sorted(p.name for p in pareto_front()))
113113
# ['angle', 'basis', 'higher_order_angle', 'swap_equivariant']
114114
```
115115

116+
### Benchmark encodings on your own data
117+
118+
Run variational-quantum-classifier and quantum-kernel comparisons with paired
119+
cross-validation, classical baselines, and statistical testing:
120+
121+
```python
122+
from encoding_atlas import AngleEncoding, IQPEncoding
123+
from encoding_atlas.benchmark import EncodingBenchmark, evaluate_encoding
124+
125+
# Compare encodings across datasets and methods
126+
bench = EncodingBenchmark(
127+
encodings=[AngleEncoding(n_features=2), IQPEncoding(n_features=2)],
128+
datasets=["moons", "circles"],
129+
methods=("vqc", "kernel"),
130+
n_runs=3,
131+
n_folds=5,
132+
baselines=("svm_rbf",),
133+
seed=0,
134+
)
135+
results = bench.run()
136+
stats = bench.statistical_tests() # Wilcoxon + Holm–Bonferroni + Cliff's delta
137+
138+
# ...or evaluate a single encoding on your own (X, y)
139+
report = evaluate_encoding(AngleEncoding(n_features=2), X, y, method="kernel")
140+
print(report["mean"], report["ci_low"], report["ci_high"])
141+
```
142+
116143
## Supported Encodings
117144

118145
| Category | Encodings |

docs/api/index.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,56 @@ All encodings inherit from `BaseEncoding` and share a unified interface.
129129

130130
---
131131

132+
## Benchmark Module
133+
134+
Evaluate encodings on classification tasks with variational quantum classifiers
135+
and quantum-kernel SVMs, paired stratified cross-validation, classical
136+
baselines, and statistical comparison (Wilcoxon + Holm–Bonferroni + Cliff's
137+
delta).
138+
139+
::: encoding_atlas.benchmark.EncodingBenchmark
140+
options:
141+
show_root_heading: true
142+
members:
143+
- run
144+
- statistical_tests
145+
- plot_comparison
146+
- save_results
147+
148+
::: encoding_atlas.benchmark.evaluate_encoding
149+
options:
150+
show_root_heading: true
151+
152+
::: encoding_atlas.benchmark.VQCClassifier
153+
options:
154+
show_root_heading: true
155+
members:
156+
- fit
157+
- predict
158+
- score
159+
160+
::: encoding_atlas.benchmark.QuantumKernelClassifier
161+
options:
162+
show_root_heading: true
163+
members:
164+
- fit
165+
- predict
166+
- score
167+
168+
::: encoding_atlas.benchmark.compute_kernel_matrix
169+
options:
170+
show_root_heading: true
171+
172+
::: encoding_atlas.benchmark.kernel_target_alignment
173+
options:
174+
show_root_heading: true
175+
176+
::: encoding_atlas.benchmark.compare_encodings_corrected
177+
options:
178+
show_root_heading: true
179+
180+
---
181+
132182
## Atlas Module
133183

134184
The empirical benchmark results — measured circuit resources, simulability,
Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,69 @@
1-
"""Benchmarking framework for encoding comparison."""
1+
"""Benchmarking framework for encoding comparison.
22
3+
Evaluate quantum encodings on classification tasks with variational quantum
4+
classifiers and quantum-kernel SVMs, paired stratified cross-validation,
5+
classical baselines, and statistical comparison.
6+
7+
>>> from encoding_atlas import AngleEncoding
8+
>>> from encoding_atlas.benchmark import EncodingBenchmark
9+
>>> bench = EncodingBenchmark(
10+
... [AngleEncoding(n_features=2)], ["moons"],
11+
... methods=("kernel",), n_runs=1, n_folds=3, seed=0,
12+
... )
13+
>>> results = bench.run() # doctest: +SKIP
14+
"""
15+
16+
from encoding_atlas.benchmark.baselines import (
17+
CLASSICAL_BASELINE_NAMES,
18+
get_classical_baseline,
19+
run_baseline_single_fold,
20+
)
321
from encoding_atlas.benchmark.datasets import get_dataset, list_datasets
22+
from encoding_atlas.benchmark.kernel import (
23+
QuantumKernelClassifier,
24+
centered_kernel_target_alignment,
25+
compute_kernel_matrix,
26+
ensure_psd,
27+
kernel_target_alignment,
28+
run_kernel_single_fold,
29+
)
430
from encoding_atlas.benchmark.metrics import compute_metrics
5-
from encoding_atlas.benchmark.runner import EncodingBenchmark
31+
from encoding_atlas.benchmark.runner import EncodingBenchmark, evaluate_encoding
32+
from encoding_atlas.benchmark.statistical import (
33+
cliffs_delta,
34+
compare_encodings,
35+
compare_encodings_corrected,
36+
holm_bonferroni,
37+
wilcoxon_test,
38+
)
39+
from encoding_atlas.benchmark.vqc import VQCClassifier, run_vqc_single_fold
640

741
__all__ = [
42+
# Orchestration
43+
"EncodingBenchmark",
44+
"evaluate_encoding",
45+
# Datasets & metrics
846
"get_dataset",
947
"list_datasets",
10-
"EncodingBenchmark",
1148
"compute_metrics",
49+
# Classifiers
50+
"VQCClassifier",
51+
"QuantumKernelClassifier",
52+
"run_vqc_single_fold",
53+
"run_kernel_single_fold",
54+
# Quantum kernel utilities
55+
"compute_kernel_matrix",
56+
"kernel_target_alignment",
57+
"centered_kernel_target_alignment",
58+
"ensure_psd",
59+
# Classical baselines
60+
"get_classical_baseline",
61+
"run_baseline_single_fold",
62+
"CLASSICAL_BASELINE_NAMES",
63+
# Statistics
64+
"wilcoxon_test",
65+
"compare_encodings",
66+
"compare_encodings_corrected",
67+
"cliffs_delta",
68+
"holm_bonferroni",
1269
]
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""Classical machine-learning baselines for benchmark calibration.
2+
3+
Provides standard scikit-learn classifiers (SVM-RBF, random forest, 2-layer
4+
MLP) so quantum encoding results can be contextualised against classical
5+
reference accuracy on the same train/test splits.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import logging
11+
from typing import Any
12+
13+
import numpy as np
14+
from numpy.typing import NDArray
15+
16+
logger = logging.getLogger(__name__)
17+
18+
# Names accepted by :func:`get_classical_baseline`.
19+
CLASSICAL_BASELINE_NAMES: list[str] = ["svm_rbf", "random_forest", "mlp_2layer"]
20+
21+
22+
def get_classical_baseline(name: str, seed: int) -> Any:
23+
"""Return a fresh scikit-learn classifier for the named baseline.
24+
25+
Parameters
26+
----------
27+
name : {"svm_rbf", "random_forest", "mlp_2layer"}
28+
Baseline identifier.
29+
seed : int
30+
Random seed for reproducibility.
31+
32+
Raises
33+
------
34+
ValueError
35+
If ``name`` is not a known baseline.
36+
"""
37+
from sklearn.ensemble import RandomForestClassifier
38+
from sklearn.neural_network import MLPClassifier
39+
from sklearn.svm import SVC
40+
41+
builders = {
42+
"svm_rbf": lambda: SVC(kernel="rbf", random_state=seed, probability=True),
43+
"random_forest": lambda: RandomForestClassifier(
44+
n_estimators=100, random_state=seed
45+
),
46+
"mlp_2layer": lambda: MLPClassifier(
47+
hidden_layer_sizes=(32, 16), max_iter=200, random_state=seed
48+
),
49+
}
50+
if name not in builders:
51+
raise ValueError(
52+
f"Unknown baseline: {name}. Available: {CLASSICAL_BASELINE_NAMES}"
53+
)
54+
return builders[name]()
55+
56+
57+
def run_baseline_single_fold(
58+
name: str,
59+
X_train: NDArray[np.floating[Any]],
60+
X_test: NDArray[np.floating[Any]],
61+
y_train: NDArray[np.intp],
62+
y_test: NDArray[np.intp],
63+
*,
64+
seed: int = 42,
65+
) -> dict[str, Any]:
66+
"""Train and evaluate a classical baseline on one train/test split.
67+
68+
Returns a dict with ``test_accuracy``, ``precision``, ``recall``, ``f1``,
69+
and ``status``. Failures are reported as ``status="failed"``.
70+
"""
71+
from sklearn.metrics import f1_score, precision_score, recall_score
72+
73+
try:
74+
clf = get_classical_baseline(name, seed=seed)
75+
clf.fit(X_train, y_train)
76+
y_pred = clf.predict(X_test)
77+
return {
78+
"test_accuracy": float(np.mean(y_pred == y_test)),
79+
"precision": float(precision_score(y_test, y_pred, zero_division=0)),
80+
"recall": float(recall_score(y_test, y_pred, zero_division=0)),
81+
"f1": float(f1_score(y_test, y_pred, zero_division=0)),
82+
"status": "success",
83+
}
84+
except Exception as exc: # noqa: BLE001 - report and continue the sweep
85+
logger.error("Baseline %s fold failed: %s", name, exc)
86+
return {
87+
"test_accuracy": 0.0,
88+
"precision": 0.0,
89+
"recall": 0.0,
90+
"f1": 0.0,
91+
"status": "failed",
92+
"error": str(exc),
93+
}

0 commit comments

Comments
 (0)