The comprehensive library for quantum data encodings in machine learning
Documentation | Website | Tutorials | API Reference | PyPI
The Quantum Encoding Atlas is the definitive open-source resource for understanding, comparing, and selecting quantum data encodings for machine learning applications.
- 📊 16 Encoding Methods — Comprehensive implementations of all major quantum data encodings
- 🔀 Multi-Framework Support — Works seamlessly with PennyLane, Qiskit, and Cirq
- 📈 Analysis Tools — Compute expressibility, entanglement capability, and trainability
- 🧪 Benchmarking Framework — Systematic comparison infrastructure, with scikit-learn-compatible estimators
- 🧭 Decision Guide — Evidence-based encoding recommendations, plus training-free screening on your own data
- 🗺️ Empirical Atlas — Query the measured benchmark results (rank, accuracy, expressibility, trainability, …) bundled with the package
- 📉 Concentration Diagnostics — Measure whether an encoding's kernel survives to wider circuits, and what it costs in shots
- 📐 Scaling Sensitivity — Measure what the feature range costs; for entangling maps it moves accuracy more than the encoding does
- 📚 Extensive Documentation — Tutorials, API docs, and theoretical background
pip install encoding-atlasWith optional backends:
# With Qiskit support
pip install encoding-atlas[qiskit]
# With Cirq support
pip install encoding-atlas[cirq]
# With all backends
pip install encoding-atlas[all]
# Development installation
pip install encoding-atlas[dev]from encoding_atlas import IQPEncoding, AngleEncoding
from encoding_atlas.analysis import compute_expressibility
import numpy as np
# Create encodings
iqp = IQPEncoding(n_features=4, reps=2)
angle = AngleEncoding(n_features=4, rotation='Y')
# Generate circuits (PennyLane by default)
X = np.random.randn(10, 4)
circuit = iqp.get_circuit(X[0])
# Analyze properties
print(f"IQP qubits: {iqp.n_qubits}")
print(f"IQP depth: {iqp.depth}")
print(f"IQP expressibility: {compute_expressibility(iqp, n_samples=500):.4f}")
# Get encoding recommendation
from encoding_atlas.guide import recommend_encoding
rec = recommend_encoding(
n_features=4,
n_samples=500,
task='classification',
hardware='simulator'
)
print(f"Recommended: {rec.encoding_name}")
print(f"Reason: {rec.explanation}")recommend_encoding answers from metadata. Once you have data, screen the
encodings against it — no training, about a second for all 16:
from encoding_atlas.benchmark import get_dataset, evaluate_encoding
from encoding_atlas.guide import screen_encodings
X, y = get_dataset("moons", n_samples=200, seed=0)
result = screen_encodings(X, y, seed=0)
for c in result.top(3):
print(f"{c.rank}. {c.name:22s} alignment={c.alignment:+.3f}")
# Candidates come back built and ready to train
for c in result.top(3):
print(c.name, evaluate_encoding(c.encoding, X, y, method="kernel")["mean"])The ranking key is the centered kernel-target alignment, which the benchmark
found tracks kernel accuracy closely (Spearman ρ = 0.91 across encodings and
datasets). Treat the result as a shortlist, not an oracle: on the
benchmark's eight datasets the top-3 shortlist lands within 0.001 of the best
achievable accuracy, while the single top pick (0.960) is no better than simply
always choosing angle (0.958). Screening buys you a 3-encoding shortlist
instead of 16, and adapts when your data doesn't resemble the benchmark's.
Encodings that can't be built at your feature count are reported, not raised:
result.skipped # {'so2_equivariant': 'ValueError: ... requires n_features=2 ...'}Encodings turn numbers into rotation angles, so the range you scale into
changes the kernel's geometry. It is not a minor knob — IQP swings 34
accuracy points across four ranges, and [0, 2π] (a full rotation period,
and the pipeline's own default) is the worst of them for every entangling map:
from encoding_atlas.analysis import recommend_feature_range, scale_to_range
low, high = recommend_feature_range(IQPEncoding(n_features=2, reps=2), X, y, seed=0)
X_scaled = scale_to_range(X, low, high)Search encodings and ranges together by passing feature_ranges= to the screener:
from encoding_atlas.analysis import DEFAULT_FEATURE_RANGES
result = screen_encodings(X, y, seed=0, feature_ranges=DEFAULT_FEATURE_RANGES)
print(result.best().name, result.best().feature_range)The measured sensitivity for all 16 encodings ships with the package, including
how far the benchmark's own expressibility-versus-accuracy correlation moves
with the range — it reverses sign between [0, π/2] (ρ = +0.78) and the
pipeline's [0, 2π] (ρ = −0.54), both significant. See
Feature Scaling.
from encoding_atlas.atlas import scaling_sensitive_encodings
print([(p.name, round(p.accuracy_spread, 2)) for p in scaling_sensitive_encodings()][:3])
# [('iqp', 0.34), ('hamiltonian', 0.27), ('pauli_feature_map', 0.21)]The measured benchmark results for every encoding ship with the package as a queryable, read-only API:
from encoding_atlas.atlas import get_encoding_profile, rank_encodings, pareto_front
# Measured profile of a single encoding
angle = get_encoding_profile("angle")
print(angle.rank, round(angle.metric("kernel_accuracy"), 3)) # 1 0.958
# Rank encodings by any measured metric
print([p.name for p in rank_encodings(by="kernel_accuracy", limit=3)])
# ['angle', 'cyclic_equivariant', 'qaoa']
# Including the benchmark's validated predictor of accuracy
print([p.name for p in rank_encodings(by="kernel_target_alignment", limit=3)])
# ['amplitude', 'angle', 'so2_equivariant']
# The Pareto-optimal set across accuracy, depth, trainability, and noise
print(sorted(p.name for p in pareto_front()))
# ['angle', 'basis', 'higher_order_angle', 'swap_equivariant']Run variational-quantum-classifier and quantum-kernel comparisons with paired cross-validation, classical baselines, and statistical testing:
from encoding_atlas import AngleEncoding, IQPEncoding
from encoding_atlas.benchmark import EncodingBenchmark, evaluate_encoding
# Compare encodings across datasets and methods
bench = EncodingBenchmark(
encodings=[AngleEncoding(n_features=2), IQPEncoding(n_features=2)],
datasets=["moons", "circles"],
methods=("vqc", "kernel"),
n_runs=3,
n_folds=5,
baselines=("svm_rbf",),
seed=0,
)
results = bench.run()
stats = bench.statistical_tests() # Wilcoxon + Holm–Bonferroni + Cliff's delta
# ...or evaluate a single encoding on your own (X, y)
report = evaluate_encoding(AngleEncoding(n_features=2), X, y, method="kernel")
print(report["mean"], report["ci_low"], report["ci_high"])Both binary and multi-class classification are supported — pass multi-class
labels or a built-in multi-class dataset (list_multiclass_datasets(), e.g.
"iris3", "blobs3"); the VQC uses a one-vs-rest ensemble and metrics switch to
macro averaging automatically.
Regression is supported via task="regression", which reports R² and uses
K-fold splits (stratification is undefined for continuous targets):
from encoding_atlas.benchmark import get_regression_dataset
X, y = get_regression_dataset("sine_reg", n_samples=120, seed=0)
report = evaluate_encoding(
AngleEncoding(n_features=2), X, y, task="regression", method="kernel"
)
print(report["score_metric"], report["mean"]) # r2 0.97VQCClassifier, VQCRegressor, QuantumKernelClassifier and
QuantumKernelRegressor follow scikit-learn's estimator contract, so they drop
straight into the tooling you already use:
from sklearn.model_selection import GridSearchCV, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import MinMaxScaler
from encoding_atlas import AngleEncoding, IQPEncoding
from encoding_atlas.benchmark import QuantumKernelClassifier, get_dataset
X, y = get_dataset("moons", n_samples=120, seed=0)
clf = QuantumKernelClassifier(AngleEncoding(n_features=2))
cross_val_score(clf, X, y, cv=5)
# Scaling matters (see below), so tune it in a pipeline
pipe = make_pipeline(MinMaxScaler((0, 3.14 / 2)), clf).fit(X, y)
# The encoding itself is a tunable hyper-parameter
search = GridSearchCV(
clf,
{"encoding": [AngleEncoding(n_features=2), IQPEncoding(n_features=2, reps=2)],
"C": [0.5, 1.0, 2.0]},
cv=5,
).fit(X, y)
print(search.best_params_)Pipeline, cross_validate, learning_curve, VotingClassifier,
CalibratedClassifierCV and anything else that clones an estimator all work.
QuantumKernelClassifier also exposes decision_function, so ROC-AUC and
probability calibration are available.
As in scikit-learn, hyper-parameters are validated at fit time rather than in
the constructor — QuantumKernelClassifier(enc, C=-1) constructs and then
raises when fitted, exactly like SVC(C=-1).
Cheap, training-free kernel-geometry metrics that predict downstream performance — kernel-target alignment, geometric difference, and effective dimension:
from encoding_atlas.analysis import (
compute_kernel_target_alignment,
compute_geometric_difference,
compute_effective_dimension,
)
# How well the encoding's kernel matches the task (predicts accuracy)
kta = compute_kernel_target_alignment(AngleEncoding(n_features=2), X, y)
# How distinct the quantum kernel is from a classical one (advantage diagnostic)
gd = compute_geometric_difference(AngleEncoding(n_features=2), X)
# Effective feature-space dimension the encoding uses (capacity)
d_eff = compute_effective_dimension(AngleEncoding(n_features=2), X)Every other axis describes an encoding at a fixed width. Kernel concentration describes what happens as the width grows — whether the fidelity kernel keeps enough structure to learn from, and what it costs in shots:
from encoding_atlas.analysis import compute_kernel_concentration
angle = compute_kernel_concentration(AngleEncoding(n_features=6), seed=0)
iqp = compute_kernel_concentration(IQPEncoding(n_features=6, reps=2), seed=0)
print(angle.concentration_ratio, angle.is_concentrated) # 9.0 False
print(iqp.concentration_ratio, iqp.is_concentrated) # 1.0 True
print(iqp.shots_per_entry) # 259 shots per kernel entryconcentration_ratio is the kernel's off-diagonal variance divided by the
Haar-random variance at the same width. A value near 1 means the kernel has
collapsed to the identity up to sampling noise — a kernel method cannot
generalize from it at any shot budget. Sweep it across widths to get a decay
rate and an extrapolated horizon:
from encoding_atlas.analysis import estimate_concentration_scaling
scaling = estimate_concentration_scaling(
lambda d: IQPEncoding(n_features=d, reps=2), feature_counts=(2, 4, 6, 8), seed=0
)
print(scaling.decay_rate) # 3.9 — near the maximal (Haar) rate of 4.0
print(scaling.concentration_horizon()) # 2 — already at the floor
print(scaling.shots_per_entry_at(20)) # extrapolated hardware cost at 20 qubitsThe scan for all 16 encodings ships with the package. The four encodings whose kernels reach the Haar floor are exactly the four with expressibility ≈ 0.999, and four of the five worst-ranked in the benchmark — which is why expressibility fails to predict accuracy:
from encoding_atlas.atlas import concentrated_encodings
print(sorted(p.name for p in concentrated_encodings()))
# ['hamiltonian', 'iqp', 'pauli_feature_map', 'zz_feature_map']Pass shots= to get the kernel a real device would return. The
compute-uncompute estimator's all-zeros count is exactly Binomial(shots, K),
so this is statistically identical to running the circuits:
from encoding_atlas.analysis import compute_fidelity_kernel
K = compute_fidelity_kernel(AngleEncoding(n_features=2), X, shots=1000, seed=0)
kta = compute_kernel_target_alignment(
AngleEncoding(n_features=2), X, y, shots=1000, seed=0
)Sampled kernels stay symmetric with a unit diagonal but are no longer PSD —
project with encoding_atlas.benchmark.kernel.ensure_psd before fitting.
Simulate an encoding under a depolarizing noise model and measure the retained state fidelity — entangling encodings degrade far more than non-entangling ones:
from encoding_atlas.analysis import compute_noise_resilience
result = compute_noise_resilience(AngleEncoding(n_features=4), noise_level="medium")
print(result.retained_fidelity, result.fidelity_decay) # e.g. 0.987 0.013Characterize an encoding — including your own custom one — across every axis, and rank it against the 16 built-in encodings:
from encoding_atlas.analysis import profile_encoding, compare_to_atlas
profile = profile_encoding(AngleEncoding(n_features=2), X=X, y=y)
print(profile.metrics) # depth, expressibility, entanglement, trainability,
# noise, kernel-target alignment, effective dimension, ...
cmp = compare_to_atlas(profile, "expressibility")
print(cmp["rank"], cmp["percentile"]) # where it ranks among the atlas encodings| Category | Encodings |
|---|---|
| Amplitude-based | AmplitudeEncoding |
| Angle-based | AngleEncoding (RX/RY/RZ), HigherOrderAngleEncoding |
| Basis | BasisEncoding |
| Entangling | IQPEncoding, ZZFeatureMap, PauliFeatureMap |
| Advanced | DataReuploading, HardwareEfficientEncoding, QAOAEncoding, HamiltonianEncoding |
| Symmetry & Equivariant | SymmetryInspiredFeatureMap, SO2EquivariantFeatureMap, CyclicEquivariantFeatureMap, SwapEquivariantFeatureMap |
| Trainable | TrainableEncoding |
See the full encoding list for details.
If you use this library in your research, please cite:
@software{Mishra2026encoding,
title={Quantum Encoding Atlas: A Comprehensive Library for Quantum Data Encodings},
author={Mishra, Ashutosh},
year={2026},
doi={10.5281/zenodo.18780936},
url={https://doi.org/10.5281/zenodo.18780936},
version={1.0.0}
}We welcome contributions! Please see our Contributing Guide for details.
This project is licensed under the MIT License - see the LICENSE file for details.