Encoding Guide¶
Quantum encoding maps classical feature vectors into quantum states. Choosing the right encoding affects qubit count, circuit depth, NISQ suitability, and expressivity.
Available encodings¶
Angle encoding¶
Maps each feature to a single-qubit rotation gate.
where \(R_G\) is Ry (default), Rx, or Rz.
| Property | Value |
|---|---|
| Qubits | n = d (one per feature) |
| Depth | O(1) |
| NISQ-safe | ✅ Excellent |
| Best for | Most QML tasks — default recommendation |
import quprep as qd
enc = qd.AngleEncoder(rotation="ry") # or "rx", "rz"
result = enc.encode(x) # x must be in [0, π] for ry
print(result.parameters) # rotation angles
print(result.metadata) # {"n_qubits": 4, "depth": 1, ...}
Normalization: Use Scaler("minmax_pi") for Ry, Scaler("minmax_pm_pi") for Rx/Rz. The pipeline applies this automatically.
Amplitude encoding¶
Embeds the entire vector as quantum state amplitudes.
Requires \(\|x\|_2 = 1\). If \(d\) is not a power of two, pads with zeros and re-normalizes.
| Property | Value |
|---|---|
| Qubits | \(n = \lceil \log_2(d) \rceil\) |
| Depth | \(O(2^n)\) — exponential |
| NISQ-safe | ❌ Poor — deep circuit |
| Best for | Qubit-limited scenarios, high expressivity |
import quprep as qd
import numpy as np
# Normalize first (pipeline does this automatically)
x = np.array([1.0, 2.0, 3.0, 4.0])
x = x / np.linalg.norm(x)
enc = qd.AmplitudeEncoder(pad=True) # pad=False raises if d is not power of two
result = enc.encode(x)
print(result.metadata["n_qubits"]) # 2 (log2(4))
print(result.metadata["padded"]) # False
Normalization: Requires L2 normalization. Use Scaler("l2") or let the pipeline handle it automatically.
Not NISQ-safe
Amplitude encoding requires exponential-depth state preparation circuits. Avoid on current hardware unless qubit count is the primary constraint.
References
Möttönen, Vartiainen, Bergholm, Salomaa (2005). Transformation of quantum states using uniformly controlled rotations. Quantum Information & Computation, 5(6), 467–473. doi:10.26421/QIC5.6-5
Basis encoding¶
Maps binary features to computational basis states via X gates.
| Property | Value |
|---|---|
| Qubits | n = d |
| Depth | O(1) — X gates only |
| NISQ-safe | ✅ Excellent |
| Best for | Binary data, QAOA, combinatorial optimization |
import quprep as qd
enc = qd.BasisEncoder(threshold=0.5) # values >= 0.5 → |1⟩, else → |0⟩
result = enc.encode(x)
print(result.parameters) # binary {0.0, 1.0}
Normalization: Binarized at 0.5 by default. Use Scaler("binary") or set a custom threshold on the encoder.
IQP encoding¶
Havlíček et al. 2019 feature map. Applies Hadamards, then single-qubit Rz and pairwise ZZ interactions, repeated reps times.
| Property | Value |
|---|---|
| Qubits | n = d |
| Depth | \(O(d^2 \cdot \text{reps})\) |
| NISQ-safe | ⚠️ Medium — \(d^2\) two-qubit gates |
| Best for | Kernel methods, quantum advantage arguments |
Normalization: minmax_pm_pi (\([-\pi, \pi]\)). Applied automatically.
References
Havlíček et al. (2019). Supervised learning with quantum-enhanced feature spaces. Nature, 567, 209–212. doi:10.1038/s41586-019-0980-2
Entangled angle encoding¶
Alternates rotation layers (Ry/Rx/Rz per qubit) with CNOT entangling layers, repeated layers times. Three entanglement topologies: linear, circular, full.
| Property | Value |
|---|---|
| Qubits | n = d |
| Depth | (d + CNOTs) × layers |
| NISQ-safe | ✅ Good — controlled depth |
| Best for | Feature correlations, expressivity beyond angle encoding |
import quprep as qd
enc = qd.EntangledAngleEncoder(rotation="ry", layers=2, entanglement="circular")
result = enc.encode(x)
print(result.metadata["cnot_pairs"]) # [(0,1), (1,2), (2,0)] for circular
Data re-uploading¶
Pérez-Salinas et al. 2020. Applies the same rotation layer layers times, interleaved with trainable parameters. Proven universal approximator with enough layers.
| Property | Value |
|---|---|
| Qubits | n = d |
| Depth | d × layers |
| NISQ-safe | ✅ Good |
| Best for | High-expressivity QNNs |
import quprep as qd
enc = qd.ReUploadEncoder(layers=3, rotation="ry")
result = enc.encode(x) # x in [−π, π]
References
Pérez-Salinas et al. (2020). Data re-uploading for a universal quantum classifier. Quantum, 4, 226. doi:10.22331/q-2020-02-06-226
Hamiltonian encoding¶
Trotterized time evolution under a single-qubit Z Hamiltonian \(H(x) = \sum_i x_i Z_i\). Applies Rz gates over trotter_steps steps.
| Property | Value |
|---|---|
| Qubits | n = d |
| Depth | d × trotter_steps |
| NISQ-safe | ⚠️ Medium — grows with steps |
| Best for | Physics simulation, VQE |
import quprep as qd
enc = qd.HamiltonianEncoder(evolution_time=1.0, trotter_steps=4)
result = enc.encode(x)
Normalization: zscore. Applied automatically.
ZZ feature map¶
Havlíček et al. 2019 ZZ feature map (Qiskit convention). Applies a Hadamard layer, single-qubit Rz gates, and pairwise ZZ interactions, repeated reps times.
| Property | Value |
|---|---|
| Qubits | n = d |
| Depth | \(O(d^2 \cdot \text{reps})\) |
| NISQ-safe | ⚠️ Medium — \(d^2\) two-qubit gates |
| Best for | Kernel methods, QSVMs, quantum advantage |
from quprep.encode.zz_feature_map import ZZFeatureMapEncoder
enc = ZZFeatureMapEncoder(reps=2)
result = enc.encode(x) # x in [0, π]
print(result.metadata["single_angles"]) # [2(π−x₀), 2(π−x₁), ...]
print(result.metadata["pair_angles"]) # pairwise ZZ angles
print(result.metadata["pairs"]) # [(0,1), (0,2), (1,2), ...]
Normalization: minmax_pi (\([0, \pi]\)). Applied automatically.
Qiskit compatibility
Produces the same circuit structure as Qiskit's ZZFeatureMap. Output circuits can be directly exported to Qiskit, QASM, Braket, Q#, or IQM.
Pauli feature map¶
Generalized feature map using configurable Pauli string interactions. Extends ZZ feature map to arbitrary single-qubit and pairwise Pauli operators.
| Property | Value |
|---|---|
| Qubits | n = d |
| Depth | \(O(d^2 \cdot \text{reps})\) with pair terms, \(O(d \cdot \text{reps})\) without |
| NISQ-safe | ⚠️ Medium — depends on Pauli strings chosen |
| Best for | Expressive kernel circuits, custom feature maps |
from quprep.encode.pauli_feature_map import PauliFeatureMapEncoder
# Z single-qubit + ZZ pairwise (equivalent to ZZFeatureMap)
enc = PauliFeatureMapEncoder(paulis=["Z", "ZZ"], reps=2)
# Higher expressivity with mixed Paulis
enc = PauliFeatureMapEncoder(paulis=["Z", "X", "ZZ", "XZ"], reps=1)
result = enc.encode(x)
print(result.metadata["single_terms"]) # {"Z": [...], "X": [...]}
print(result.metadata["pair_terms"]) # {"ZZ": [(i,j,angle), ...]}
Valid single Paulis: X, Y, Z
Valid pair Paulis: XX, YY, ZZ, XZ, ZX, XY, YX, YZ, ZY
Random Fourier features¶
Approximates the RBF (Gaussian) kernel using Bochner's theorem. Samples random Fourier frequencies from \(\mathcal{N}(0, 2\gamma)\) and encodes the cosine projection as rotation angles.
| Property | Value |
|---|---|
| Qubits | n = n_components (fixed, regardless of input dim) |
| Depth | O(1) — single Ry layer |
| NISQ-safe | ✅ Excellent |
| Best for | Kernel approximation, dimensionality expansion |
import numpy as np
from quprep.encode.random_fourier import RandomFourierEncoder
enc = RandomFourierEncoder(n_components=8, gamma=1.0, random_state=42)
# Must call fit() first — samples the random projection matrix
enc.fit(X_train)
result = enc.encode(x) # always n_components qubits
Requires fit()
RandomFourierEncoder must be fitted on training data before encoding. Calling encode() without fit() raises RuntimeError.
References
Rahimi & Recht (2007). Random Features for Large-Scale Kernel Machines. Advances in Neural Information Processing Systems (NeurIPS), 20. proceedings
Tensor product encoding¶
Encodes two features per qubit using a full Bloch sphere rotation (Ry + Rz). Requires \(\lceil d/2 \rceil\) qubits. No entanglement — purely single-qubit.
| Property | Value |
|---|---|
| Qubits | \(n = \lceil d/2 \rceil\) |
| Depth | O(1) |
| NISQ-safe | ✅ Excellent |
| Best for | Qubit-efficient encoding, full Bloch sphere expressivity |
from quprep.encode.tensor_product import TensorProductEncoder
enc = TensorProductEncoder()
result = enc.encode(x) # x in [0, π]; odd-length inputs zero-padded
print(result.metadata["ry_angles"]) # [θ₀, θ₁, ...]
print(result.metadata["rz_angles"]) # [φ₀, φ₁, ...]
print(result.metadata["n_qubits"]) # ceil(d/2)
Normalization: minmax_pi (\([0, \pi]\)). Applied automatically.
Dense angle encoding¶
Two rotation gates per qubit (Ry + Rz by default), halving the qubit count
compared to AngleEncoder. No entanglement — depth 2.
| Property | Value |
|---|---|
| Qubits | \(n = \lceil d/2 \rceil\) |
| Depth | 2 |
| NISQ-safe | ✅ Excellent |
| Best for | Qubit-limited scenarios; paired features on a single qubit |
import quprep as qd
enc = qd.DenseAngleEncoder() # default: Ry + Rz
enc = qd.DenseAngleEncoder(first_rotation="rx",
second_rotation="rz") # configurable pair
result = enc.encode(x) # x in [0, π]
print(result.metadata["n_qubits"]) # ceil(d/2)
print(result.metadata["depth"]) # 2
Normalization: minmax_pi (\([0, \pi]\)). Applied automatically.
Discretized encoding¶
Quantizes each continuous feature to bits binary digits (fixed-point), then
encodes the result as a computational basis state. Total qubits = d × bits.
The output binary vector is QUBO-compatible and can be passed directly to
quprep.qubo.to_qubo().
| Property | Value |
|---|---|
| Qubits | \(n = d \times \text{bits}\) |
| Depth | 1 (X gates only) |
| NISQ-safe | ✅ Excellent |
| Best for | QUBO/Ising pipelines; continuous-to-binary relaxations; QAOA warm-start |
import quprep as qd
import numpy as np
enc = qd.DiscretizedEncoder(bits=4, min_val=0.0, max_val=1.0)
result = enc.encode(x)
print(result.metadata["n_qubits"]) # d * 4
print(result.metadata["precision"]) # (max - min) / 15
print(result.metadata["qubo_variables"]) # {0: [0,1,2,3], 1: [4,5,6,7], ...}
# Roundtrip reconstruction
x_hat = enc.decode(result.parameters)
QUBO integration
metadata["qubo_variables"] maps each feature index to its qubit indices.
Use this to build QUBO coefficient matrices aligned with the binary variables.
QAOA problem encoding (v0.6.0)¶
Encodes a feature vector as a QAOA-inspired circuit where features become the cost Hamiltonian parameters. Each feature \(x_i\) sets a local field \(h_i = \gamma x_i\), and adjacent-pair products \(x_i x_{i+1}\) set coupling angles. One layer applies \(H^{\otimes d}\), cost unitaries (RZ + CNOT-RZ-CNOT), and a mixer (RX).
| Property | Value |
|---|---|
| Qubits | \(n = d\) |
| Depth | O(p) linear; O(d·p) full |
| NISQ-safe | ✅ Yes (linear, p=1) |
| Best for | QAOA warm-starting, problem-inspired feature maps, NISQ kernel methods |
from quprep.encode.qaoa_problem import QAOAProblemEncoder
enc = QAOAProblemEncoder(p=1, connectivity="linear")
result = enc.encode(x) # x in [-π, π]
print(result.metadata["local_angles"]) # γ·xᵢ per qubit
print(result.metadata["coupling_angles"]) # γ·xᵢxⱼ per pair
print(result.metadata["depth"]) # 1 + p·(d + 3·(d−1)) for linear connectivity
Normalization: minmax_pm_pi (\([-\pi, \pi]\)). Applied automatically.
Choosing an encoding¶
Not sure? Use quprep.recommend():
Or follow this decision tree:
Is your data binary?
└─ Yes → Basis encoding (QAOA, combinatorial)
Need QUBO variables from continuous data?
└─ Yes → Discretized encoding (continuous → binary, QUBO-ready)
└─ No → Is qubit count the main constraint?
└─ Yes (log₂ d qubits) → Amplitude encoding (deep)
└─ Yes (⌈d/2⌉ qubits) → Dense angle or Tensor product
└─ No → What is your task?
classification/regression → Angle or IQP
kernel methods → IQP or ZZ feature map
high-expressivity QNN → Data re-uploading
physics simulation/VQE → Hamiltonian
feature correlations → Entangled angle
QAOA warm-start / problem → QAOA problem
Inspecting encoded circuits¶
inspect_encoding gives a structured Python view of rotation angles and gate parameters after encoding — without parsing QASM strings:
import numpy as np
import quprep as qd
x = np.array([0.5, 1.0, 1.5, 2.0])
enc = qd.AngleEncoder(rotation="ry")
result = enc.encode(x)
params = qd.inspect_encoding(result)
print(params.n_qubits) # 4
print(params.encoding) # "angle"
for g in params.gates:
print(g.gate, g.qubit, g.angle)
# Ry 0 0.5
# Ry 1 1.0
# ...
EncodingParams.gates is a list of GateParam objects with fields gate (str), qubit (int), angle (float or None), control (int or None for entangled pairs), and amplitudes (ndarray or None for amplitude encoding). Works for all encoders; angle is None for non-rotation gates.
Auto-normalization¶
The pipeline selects the correct normalization automatically:
import quprep as qd
# No manual normalization needed — pipeline handles it
pipeline = qd.Pipeline(encoder=qd.AngleEncoder(rotation="ry"))
result = pipeline.fit_transform(data)
# data was automatically scaled to [0, π] before encoding
To override: