API Reference¶
Complete programmatic interface for the Quantum Encoding Atlas. All public classes and functions are documented below with their signatures, parameters, and return types.
Encodings¶
All encodings inherit from BaseEncoding and share a unified interface.
Base Class¶
BaseEncoding
¶
Bases: ABC
Abstract base class for all quantum data encodings.
All encoding implementations must inherit from this class and implement the required abstract methods.
Parameters¶
n_features : int Number of classical features to encode. **kwargs : Any Additional encoding-specific parameters.
Examples¶
from encoding_atlas import AngleEncoding encoding = AngleEncoding(n_features=4, rotation='Y') encoding.n_qubits 4
Notes¶
This class uses slots for memory efficiency. Subclasses should also define slots listing their own instance attributes to maintain this optimization. If a subclass does not define slots, it will have a dict and the memory benefit is partially lost.
Source code in src/encoding_atlas/core/base.py
properties
property
¶
properties: EncodingProperties
Compute and return encoding properties.
This property uses thread-safe lazy initialization with double-checked locking to ensure safe access in multi-threaded environments while minimizing lock contention for subsequent accesses.
Returns¶
EncodingProperties Computed properties of this encoding.
Notes¶
Thread Safety: The first access in a multi-threaded context will acquire a lock to ensure only one thread computes the properties. Subsequent accesses bypass the lock entirely for optimal performance.
The double-checked locking pattern
- First check without lock (fast path for initialized case)
- Acquire lock
- Second check inside lock (handles race condition)
- Compute if still None
get_circuit
¶
Generate quantum circuit for the given data.
This is a template method that validates input and delegates to the
encoding-specific :meth:_get_circuit_from_validated. Subclasses
should normally override :meth:_get_circuit_from_validated rather
than this method.
If circuit caching has been enabled via :meth:enable_cache, the
result for repeated calls with byte-identical inputs is returned
from cache.
Parameters¶
x : ArrayLike Input data of shape (n_features,) or (1, n_features) for a single sample. If 2D with a single row, the row is unwrapped before dispatch. backend : {'pennylane', 'qiskit', 'cirq'}, default='pennylane' Target quantum computing framework.
Returns¶
CircuitType Circuit in the specified backend's format.
Raises¶
ValueError If input shape doesn't match n_features, contains NaN/Inf values, or backend is not recognized.
Source code in src/encoding_atlas/core/base.py
Properties¶
EncodingProperties
dataclass
¶
EncodingProperties(n_qubits: int, depth: int, gate_count: int, single_qubit_gates: int, two_qubit_gates: int, parameter_count: int, is_entangling: bool, simulability: Literal['simulable', 'conditionally_simulable', 'not_simulable'], expressibility: Optional[float] = None, entanglement_capability: Optional[float] = None, trainability_estimate: Optional[float] = None, noise_resilience_estimate: Optional[float] = None, notes: str = '')
Properties computed for a quantum encoding.
Attributes¶
n_qubits : int Number of qubits required. depth : int Circuit depth (number of time steps). gate_count : int Total number of gates. single_qubit_gates : int Number of single-qubit gates. two_qubit_gates : int Number of two-qubit gates. parameter_count : int Number of data-dependent parameters. is_entangling : bool Whether the encoding creates entanglement. simulability : str Classical simulability status.
__post_init__
¶
Validate properties after initialization.
Source code in src/encoding_atlas/core/properties.py
to_dict
¶
Convert properties to dictionary.
Source code in src/encoding_atlas/core/properties.py
Encoding Classes¶
Angle Encoding¶
AngleEncoding
¶
Bases: BaseEncoding
Angle encoding using single-qubit rotation gates.
AngleEncoding maps classical features directly to rotation angles of single-qubit gates. Each feature is encoded on a dedicated qubit using a rotation gate around a specified axis (X, Y, or Z).
This encoding creates product states (no entanglement), making it: - Classically simulable (efficient classical simulation possible) - Free from barren plateaus (good trainability) - Hardware-efficient (only single-qubit gates)
The circuit structure for each repetition is:
|0⟩ ─ Rₐ(x₀) ─
|0⟩ ─ Rₐ(x₁) ─
|0⟩ ─ Rₐ(x₂) ─
...
where Rₐ is the rotation gate for axis a ∈ {X, Y, Z}.
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. Each feature requires one qubit, so this also determines the number of qubits in the circuit. rotation : {"X", "Y", "Z"}, default="Y" The axis of rotation to use for encoding:
- "X": Uses RX gates, encoding features in the YZ plane
- "Y": Uses RY gates (default), encoding features in the XZ plane
- "Z": Uses RZ gates, encoding features as phases
The Y rotation is commonly used as it creates real-valued amplitudes
when starting from |0⟩, and can represent any point on the Bloch
sphere's XZ great circle.
reps : int, default=1
Number of times to repeat the encoding layer. Higher values increase
the effective rotation angle by a factor of reps. Must be at least 1.
With reps > 1, the effective rotation for feature xᵢ becomes:
Rₐ(xᵢ)^reps = Rₐ(reps · xᵢ)
Attributes¶
rotation : str The rotation axis used ("X", "Y", or "Z"). reps : int Number of encoding layer repetitions. n_features : int Number of classical features (inherited from BaseEncoding). n_qubits : int Number of qubits, equal to n_features.
Examples¶
Create a basic angle encoding with default Y rotation:
from encoding_atlas import AngleEncoding import numpy as np enc = AngleEncoding(n_features=4) enc.n_qubits 4 enc.rotation 'Y'
Generate a PennyLane circuit:
x = np.array([0.1, 0.2, 0.3, 0.4]) circuit = enc.get_circuit(x, backend='pennylane') callable(circuit) True
Use X rotation for different encoding behavior:
enc_x = AngleEncoding(n_features=4, rotation='X') qiskit_circuit = enc_x.get_circuit(x, backend='qiskit') qiskit_circuit.num_qubits 4
Use multiple repetitions to increase rotation range:
enc_reps = AngleEncoding(n_features=2, reps=3) enc_reps.depth 3 enc_reps.properties.gate_count 6
Access encoding properties:
enc = AngleEncoding(n_features=4) props = enc.properties props.is_entangling False props.simulability 'simulable'
References¶
.. [1] Schuld, M., & Petruccione, F. (2018). "Supervised Learning with Quantum Computers." Springer. .. [2] LaRose, R., & Coyle, B. (2020). "Robust data encodings for quantum classifiers." Physical Review A, 102(3), 032420.
See Also¶
AmplitudeEncoding : Encodes features in state amplitudes (logarithmic qubits). IQPEncoding : Adds entanglement via ZZ interactions. DataReuploading : Re-uploads data multiple times with trainable layers. PauliFeatureMap : Configurable Pauli rotations with entanglement.
Notes¶
Expressivity: Angle encoding creates product states, which limits its expressivity compared to entangling encodings. However, this simplicity is advantageous for trainability and classical simulation.
Rotation Axis Choice:
- RY is most common as it creates real amplitudes from |0⟩
- RX creates complex amplitudes with imaginary components
- RZ only adds phases to |0⟩ (no population change from |0⟩)
Scaling Considerations: The rotation gates have period 2π (for RX, RY) or 4π (for full Bloch sphere coverage). Input features should be scaled appropriately to utilize the full encoding range.
Comparison with Other Encodings:
+-------------------+----------+--------+------------+---------------+ | Encoding | Qubits | Depth | Entangling | Simulability | +===================+==========+========+============+===============+ | AngleEncoding | n | reps | No | Simulable | +-------------------+----------+--------+------------+---------------+ | BasisEncoding | n | 1 | No | Simulable | +-------------------+----------+--------+------------+---------------+ | AmplitudeEncoding | log₂(n) | O(2^n) | Yes | Not simulable | +-------------------+----------+--------+------------+---------------+ | IQPEncoding | n | O(n²) | Yes | Not simulable | +-------------------+----------+--------+------------+---------------+ | ZZFeatureMap | n | O(n²) | Yes | Not simulable | +-------------------+----------+--------+------------+---------------+
Initialize the angle encoding.
Parameters¶
n_features : int Number of classical features to encode. rotation : {"X", "Y", "Z"}, default="Y" Rotation axis for the encoding gates. reps : int, default=1 Number of encoding layer repetitions.
Raises¶
ValueError If rotation is not one of "X", "Y", "Z". ValueError If reps is less than 1. ValueError If n_features is less than 1 (raised by parent class).
Amplitude Encoding¶
AmplitudeEncoding
¶
Bases: BaseEncoding
Amplitude encoding maps classical features to quantum state amplitudes.
AmplitudeEncoding provides exponential data compression by encoding n classical features into the amplitudes of a log₂(n)-qubit quantum state. This is one of the most powerful quantum data encodings in terms of compression, but comes with significant circuit depth requirements.
The encoding prepares a quantum state where each computational basis state |i⟩ has amplitude proportional to the i-th feature value:
|0⟩ → (1/‖x‖) Σᵢ xᵢ|i⟩
The circuit structure requires state preparation gates:
|0⟩⊗ⁿ ─── [State Preparation Unitary U] ─── |ψ(x)⟩
where U|0⟩⊗ⁿ = |ψ(x)⟩ and the state preparation uses O(2^n) gates for n qubits in the general case.
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. The number of qubits is ⌈log₂(n_features)⌉, with zero-padding if n_features is not a power of 2. normalize : bool, default=True Whether to normalize input data to create a valid quantum state. If True (default), input vectors are divided by their L2 norm. If False, the user must ensure inputs are already normalized.
.. warning::
Setting normalize=False with unnormalized data may cause
undefined behavior or errors in quantum backends.
Attributes¶
normalize : bool Whether automatic normalization is enabled. n_features : int Number of classical features (inherited from BaseEncoding). n_qubits : int Number of qubits: max(1, ⌈log₂(n_features)⌉). depth : int Circuit depth: O(2^n_qubits) for general state preparation.
Examples¶
Create a basic amplitude encoding for 4 features (requires 2 qubits):
from encoding_atlas import AmplitudeEncoding import numpy as np enc = AmplitudeEncoding(n_features=4) enc.n_qubits 2 enc.n_features 4
Encode a feature vector (automatically normalized):
x = np.array([1.0, 2.0, 3.0, 4.0]) circuit = enc.get_circuit(x, backend='pennylane') callable(circuit) True
Single feature requires 1 qubit (padded to 2 amplitudes):
enc_single = AmplitudeEncoding(n_features=1) enc_single.n_qubits 1
Generate circuits for different backends:
qiskit_circuit = enc.get_circuit(x, backend='qiskit') qiskit_circuit.num_qubits 2
cirq_circuit = enc.get_circuit(x, backend='cirq') len(cirq_circuit.all_qubits()) 2
Non-power-of-two features are zero-padded:
enc_5 = AmplitudeEncoding(n_features=5) enc_5.n_qubits # ceil(log2(5)) = 3, so 8 amplitudes 3
Batch processing multiple samples:
X = np.random.randn(10, 4) circuits = enc.get_circuits(X, backend='pennylane') len(circuits) 10
Access encoding properties:
props = enc.properties props.is_entangling True props.simulability 'not_simulable'
References¶
.. [1] Schuld, M., & Petruccione, F. (2018). "Supervised Learning with Quantum Computers." Springer. .. [2] Mottonen, M., et al. (2004). "Transformation of quantum states using uniformly controlled rotations."
See Also¶
AngleEncoding : Linear scaling (n features = n qubits), no entanglement. BasisEncoding : Encodes discrete data in computational basis states. IQPEncoding : Adds entanglement with quadratic feature interactions. DataReuploading : Re-encodes data multiple times with trainable layers.
Notes¶
Compression vs. Circuit Depth Tradeoff: While amplitude encoding provides exponential compression (n features in log₂(n) qubits), it requires O(2^n) gates for state preparation. This means the encoding circuit depth scales linearly with the number of features, not logarithmically.
Qubit Calculation Edge Case: For n_features=1, the formula log₂(1) = 0 would yield 0 qubits. The implementation enforces a minimum of 1 qubit, padding the single feature to a 2-element state vector [x, 0] → |0⟩ after normalization.
Normalization Behavior: - If normalize=True: Input vector x is divided by ‖x‖ before encoding - If normalize=False: Input is used as-is (must be pre-normalized) - Zero-norm vectors will cause errors (division by zero)
Backend-Specific Implementation: - PennyLane: Uses qml.AmplitudeEmbedding for optimal performance - Qiskit: Uses QuantumCircuit.initialize() with automatic decomposition - Cirq: Uses a custom unitary gate constructed via Gram-Schmidt
Qubit Ordering Conventions: Quantum computing frameworks differ in how they map statevector indices to qubit labels. This library adopts the MSB (most significant bit) convention used by PennyLane and Cirq, where qubit 0 is the leftmost (most significant) bit.
-
PennyLane — MSB natively.
qml.state()returns a statevector where index i encodes the computational basis state with qubit 0 as the most significant bit. No conversion needed. -
Cirq — MSB natively.
Simulator.simulate()returns a statevector withLineQubit(0)as the most significant bit. No conversion needed. -
Qiskit — LSB natively.
Statevector(circuit).datareturns amplitudes where qubit 0 is the least significant bit._to_qiskit()compensates for this by bit-reversing the amplitude indices before callinginitialize(), and the analysis module's_reverse_qubit_order()reverses the statevector when reading results back. The net effect is transparent MSB semantics for the caller.
Example: For 4 features (2 qubits), amplitude vector [a₀, a₁, a₂, a₃] maps to quantum state: a₀|00⟩ + a₁|01⟩ + a₂|10⟩ + a₃|11⟩
In the MSB convention used by all backends after conversion:
- Index 0 (binary 00) → qubit 0 = 0, qubit 1 = 0
- Index 1 (binary 01) → qubit 0 = 0, qubit 1 = 1
- Index 2 (binary 10) → qubit 0 = 1, qubit 1 = 0
- Index 3 (binary 11) → qubit 0 = 1, qubit 1 = 1
This consistent ordering ensures that circuits generated by different backends produce equivalent quantum states, enabling reliable cross- backend testing and backend-agnostic algorithm development.
Hardware Considerations: Due to the deep circuit depth, amplitude encoding may accumulate significant errors on NISQ hardware. Consider using shallower encodings (AngleEncoding, IQPEncoding) for near-term applications.
Initialize the amplitude encoding.
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. normalize : bool, default=True Whether to automatically normalize input vectors.
Raises¶
ValueError If n_features is not a positive integer (raised by parent class). TypeError If normalize is not a boolean.
Examples¶
enc = AmplitudeEncoding(n_features=8) enc.n_qubits 3
enc = AmplitudeEncoding(n_features=4, normalize=False) enc.normalize False
Basis Encoding¶
BasisEncoding
¶
Bases: BaseEncoding
Basis encoding for binary/discrete data into computational basis states.
BasisEncoding provides the simplest quantum data encoding by mapping binary classical data directly to computational basis states. Each qubit represents one classical bit, with X gates applied to encode 1s.
This encoding creates pure computational basis states (no superposition), making it:
- Trivially simulable: Classical computers can efficiently track basis states
- Hardware efficient: Only single-qubit X gates, no entanglement
- Deterministic: Measurement always yields the encoded bit string
- Minimal depth: Circuit depth of 1 regardless of feature count
The circuit structure is:
|0⟩ ─── X^{x₀} ─── |x₀⟩
|0⟩ ─── X^{x₁} ─── |x₁⟩
|0⟩ ─── X^{x₂} ─── |x₂⟩
...
where X^{xᵢ} means "apply X gate if xᵢ = 1, otherwise identity".
Parameters¶
n_features : int Number of binary features to encode. Must be a positive integer. Each feature requires one qubit, so this also determines the number of qubits in the circuit. threshold : float, default=0.5 Binarization threshold for continuous inputs. Values strictly greater than this threshold are mapped to 1 (X gate applied), while values less than or equal to the threshold are mapped to 0 (no gate).
Common threshold choices:
- 0.5: Standard for data normalized to [0, 1]
- 0.0: Treat negative values as 0, positive as 1
- Custom: Match your data's natural decision boundary
Attributes¶
n_features : int Number of binary features (inherited from BaseEncoding). n_qubits : int Number of qubits, equal to n_features. depth : int Circuit depth, always 1 for basis encoding. threshold : float Binarization threshold. Values > threshold become 1.
Examples¶
Create a basic basis encoding for 4 binary features:
from encoding_atlas import BasisEncoding import numpy as np enc = BasisEncoding(n_features=4) enc.n_qubits 4 enc.depth 1 enc.threshold 0.5
Encode binary data directly:
x_binary = np.array([1, 0, 1, 1]) circuit = enc.get_circuit(x_binary, backend='pennylane') callable(circuit) True
Continuous data is automatically binarized at threshold 0.5:
x_continuous = np.array([0.8, 0.2, 0.6, 0.9]) # -> [1, 0, 1, 1] circuit = enc.get_circuit(x_continuous, backend='pennylane')
Custom threshold for data with different scales:
enc_custom = BasisEncoding(n_features=4, threshold=0.0) enc_custom.threshold 0.0
Now: positive values -> 1, non-positive values -> 0¶
x_signed = np.array([-0.5, 0.5, -0.1, 0.1]) # -> [0, 1, 0, 1] circuit = enc_custom.get_circuit(x_signed, backend='pennylane')
Generate circuits for different backends:
x = np.array([1, 0, 1, 0]) qiskit_circuit = enc.get_circuit(x, backend='qiskit') qiskit_circuit.num_qubits 4
cirq_circuit = enc.get_circuit(x, backend='cirq') len(cirq_circuit.all_qubits()) 4
Batch processing multiple samples:
X = np.array([[1, 0, 0, 1], [0, 1, 1, 0], [1, 1, 1, 1]]) circuits = enc.get_circuits(X, backend='pennylane') len(circuits) 3
Access encoding properties:
props = enc.properties props.is_entangling False props.simulability 'simulable' props.depth 1
Analyze actual resource usage for specific data:
enc = BasisEncoding(n_features=4) enc.properties.gate_count # Theoretical worst-case 4 enc.actual_gate_count([1, 0, 0, 0]) # Actual for sparse data 1 enc.binarize([0.8, 0.2, 0.6, 0.4]) # Inspect binarization array([1, 0, 1, 0])
References¶
.. [1] Nielsen, M. A., & Chuang, I. L. (2010). "Quantum Computation and Quantum Information." Cambridge University Press. .. [2] Schuld, M., & Petruccione, F. (2018). "Supervised Learning with Quantum Computers." Springer.
See Also¶
AngleEncoding : Encodes continuous features as rotation angles. AmplitudeEncoding : Encodes features in state amplitudes (logarithmic qubits). IQPEncoding : Adds entanglement via ZZ interactions for richer expressivity.
Notes¶
Binarization Behavior: This implementation automatically binarizes continuous inputs using the configured threshold (default 0.5):
- x > threshold → 1 (X gate applied)
- x ≤ threshold → 0 (no gate applied)
For pre-binarized data (0s and 1s), this threshold has no effect. The threshold can be customized at construction time to match your data's natural decision boundary.
Comparison with Other Encodings:
+------------------+----------+-------+------------+---------------+ | Encoding | Qubits | Depth | Entangling | Data Type | +==================+==========+=======+============+===============+ | BasisEncoding | n | 1 | No | Binary | +------------------+----------+-------+------------+---------------+ | AngleEncoding | n | 1 | No | Continuous | +------------------+----------+-------+------------+---------------+ | AmplitudeEncoding| log₂(n) | O(2ⁿ) | Yes | Continuous | +------------------+----------+-------+------------+---------------+ | IQPEncoding | n | O(n) | Yes | Continuous | +------------------+----------+-------+------------+---------------+
When to Use Basis Encoding:
- Your data is naturally binary or categorical
- You need a simple baseline encoding for comparison
- Circuit depth must be minimal (NISQ hardware)
- You're implementing algorithms that work with marked basis states (Grover's search, QAOA for combinatorial problems)
When NOT to Use Basis Encoding:
- Your data is continuous and pattern-rich (use AngleEncoding or IQP)
- You need amplitude-based information processing
- Your algorithm requires superposition or entanglement from the encoding
Cirq Backend Note:
Cirq's circuit.all_qubits() only returns qubits that have operations.
For sparse inputs (many zeros), this may return fewer qubits than expected.
Always use encoding.n_qubits to determine the actual qubit count
required for your circuit, not circuit.all_qubits().
Understanding Gate Counts (Important):
BasisEncoding provides two ways to analyze gate counts:
-
Theoretical (worst-case):
properties.gate_countreturnsn_features, assuming all features binarize to 1. This is computed once at construction time and cached. Use this for: -
Capacity planning and resource allocation
- Worst-case guarantees
- Comparing encoding algorithms theoretically
-
Documentation and specifications
-
Actual (data-dependent):
actual_gate_count(x)returns the exact count of X gates for specific input data. This equals the number of 1s after binarization. Use this for: -
Accurate hardware resource estimation
- Benchmarking with real datasets
- Cost analysis for sparse data
- Debugging unexpected behavior
Example showing the difference:
enc = BasisEncoding(n_features=100) enc.properties.gate_count # Theoretical: always 100 100 sparse_data = np.zeros(100) sparse_data[0] = 1 # Only one feature is "on" enc.actual_gate_count(sparse_data) # Actual: only 1 gate 1
For sparse binary data, the actual gate count can be significantly lower than the theoretical maximum, which matters for:
- Hardware execution time (fewer gates = faster circuits)
- Error rates (fewer gates = less decoherence)
- Classical simulation cost (sparser circuits = easier to simulate)
Initialize the basis encoding.
Parameters¶
n_features : int Number of binary features to encode. Each feature requires one qubit, so this determines both the input dimension and the number of qubits in the circuit. threshold : float, default=0.5 Binarization threshold for continuous inputs. Values strictly greater than this threshold are mapped to 1 (X gate applied), while values less than or equal are mapped to 0 (no gate).
Must be a finite real number. Common choices:
- 0.5 for data normalized to [0, 1]
- 0.0 for signed data (negative -> 0, positive -> 1)
Raises¶
ValueError If n_features is not a positive integer (raised by parent class). TypeError If threshold is not a numeric type (int or float). ValueError If threshold is NaN or infinite.
Examples¶
enc = BasisEncoding(n_features=4) enc.n_qubits 4 enc.depth 1 enc.threshold 0.5
enc = BasisEncoding(n_features=8) enc.properties.gate_count # Worst case: all X gates 8
enc = BasisEncoding(n_features=4, threshold=0.0) enc.threshold 0.0
IQP Encoding¶
IQPEncoding
¶
IQPEncoding(n_features: int, reps: int = _DEFAULT_REPS, entanglement: Literal['full', 'linear', 'circular'] = _DEFAULT_ENTANGLEMENT)
Bases: BaseEncoding
IQP encoding using Hadamard gates and ZZ interactions.
IQPEncoding implements Instantaneous Quantum Polynomial circuits for quantum machine learning. It creates highly entangled quantum states by combining Hadamard gates, single-qubit Z rotations, and two-qubit ZZ interactions parameterized by classical input features.
This encoding is notable for its provable classical hardness: simulating IQP circuits is believed to be intractable for classical computers under standard complexity-theoretic assumptions, making it a candidate for demonstrating quantum advantage in machine learning.
The circuit structure for each repetition is:
|0⟩ ─ H ─ RZ(2x₀) ─╭─────╮─╭─────╮─
|0⟩ ─ H ─ RZ(2x₁) ─│ ZZ │─│ │─
|0⟩ ─ H ─ RZ(2x₂) ─╰─────╯─│ ZZ │─
... ╰─────╯
where ZZ(xᵢxⱼ) interactions are applied according to the entanglement topology (full, linear, or circular connectivity).
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. Each feature requires one qubit, so this also determines the number of qubits in the circuit. reps : int, default=2 Number of times to repeat the encoding layers. Higher values create deeper circuits with stronger entanglement but may face trainability issues. Must be at least 1. entanglement : {"full", "linear", "circular"}, default="full" Topology of ZZ interactions between qubits:
- "full": All-to-all connectivity. Every pair (i, j) with i < j has a
ZZ interaction. Creates n(n-1)/2 entangling gates per layer.
Maximum expressivity but highest gate count.
- "linear": Nearest-neighbor connectivity. Only pairs (i, i+1) have
ZZ interactions. Creates n-1 entangling gates per layer.
Hardware-friendly for linear qubit topologies.
- "circular": Nearest-neighbor with periodic boundary. Like linear,
but adds a ZZ interaction between qubits n-1 and 0.
Creates n entangling gates per layer.
Attributes¶
reps : int Number of encoding layer repetitions. entanglement : str The entanglement topology ("full", "linear", or "circular"). n_features : int Number of classical features (inherited from BaseEncoding). n_qubits : int Number of qubits, equal to n_features.
Examples¶
Create a basic IQP encoding with default settings:
from encoding_atlas import IQPEncoding import numpy as np enc = IQPEncoding(n_features=4) enc.n_qubits 4 enc.entanglement 'full'
Generate a PennyLane circuit:
x = np.array([0.1, 0.2, 0.3, 0.4]) circuit = enc.get_circuit(x, backend='pennylane') callable(circuit) True
Use linear entanglement for hardware-friendly circuits:
enc_linear = IQPEncoding(n_features=4, entanglement='linear') props = enc_linear.properties props.two_qubit_gates < enc.properties.two_qubit_gates True
Generate Qiskit and Cirq circuits:
qiskit_circuit = enc.get_circuit(x, backend='qiskit') qiskit_circuit.num_qubits 4 cirq_circuit = enc.get_circuit(x, backend='cirq') len(cirq_circuit.all_qubits()) 4
Access encoding properties:
props = enc.properties props.is_entangling True props.simulability 'not_simulable'
References¶
.. [1] Havlíček, V., et al. (2019). "Supervised learning with quantum-enhanced feature spaces." Nature, 567(7747), 209-212. .. [2] Bremner, M. J., et al. (2016). "Average-case complexity versus approximate simulation of commuting quantum computations." Physical Review Letters, 117(8), 080501. .. [3] McClean, J. R., et al. (2018). "Barren plateaus in quantum neural network training landscapes." Nature Communications, 9, 4812.
See Also¶
ZZFeatureMap : Similar encoding with different phase conventions. AngleEncoding : Simpler encoding without entanglement. PauliFeatureMap : Generalized Pauli-based feature map. DataReuploading : Re-uploads data with trainable intermediate layers.
Warnings¶
Full Entanglement with Many Features: When using entanglement='full'
with more than 12 features, a warning is issued because the circuit
complexity may exceed practical limits for NISQ devices. The number of
two-qubit gates scales as O(n²), which can lead to:
- Excessive circuit depth and gate errors
- Hardware connectivity constraints (all-to-all required)
- Potential trainability issues (barren plateaus)
Consider using entanglement='linear' or entanglement='circular'
for large feature counts.
Notes¶
Classical Hardness: The output distribution of IQP circuits is provably hard to sample from classically under the assumption that the polynomial hierarchy does not collapse [2]_. This provides theoretical motivation for potential quantum advantage in machine learning.
Feature Interactions: The ZZ(xᵢxⱼ) gates encode products of features, allowing the quantum kernel to capture pairwise correlations. This is analogous to polynomial feature expansion in classical machine learning, but executed in superposition.
Entanglement Topology Trade-offs:
+------------+------------------+---------------+------------------------+ | Topology | Gate Scaling | Connectivity | Best For | +============+==================+===============+========================+ | full | O(n²) | All-to-all | Max expressivity | +------------+------------------+---------------+------------------------+ | linear | O(n) | Nearest-only | NISQ devices | +------------+------------------+---------------+------------------------+ | circular | O(n) | Ring | Periodic problems | +------------+------------------+---------------+------------------------+
Trainability: Deeper circuits (higher reps) with full entanglement may exhibit barren plateaus, where gradients become exponentially small [3]_. Mitigation strategies include:
- Using fewer repetitions (reps=1 or 2)
- Using sparser entanglement (linear or circular)
- Layer-wise training approaches
- Careful parameter initialization
Gate Decomposition: The ZZ interaction is implemented using the
standard decomposition: ZZ(θ) = CNOT · RZ(2θ) · CNOT. This requires
2 CNOT gates per interaction, which dominates the circuit's two-qubit
gate count.
Initialize the IQP encoding.
Parameters¶
n_features : int Number of classical features to encode. reps : int, default=2 Number of encoding layer repetitions. entanglement : {"full", "linear", "circular"}, default="full" Topology of ZZ interactions between qubits.
Raises¶
ValueError If reps is less than 1. ValueError If entanglement is not one of "full", "linear", "circular". ValueError If n_features is less than 1 (raised by parent class).
Warns¶
UserWarning If using full entanglement with more than 12 features, as the circuit complexity may exceed practical NISQ device limits.
ZZ Feature Map¶
ZZFeatureMap
¶
ZZFeatureMap(n_features: int, reps: int = _DEFAULT_REPS, entanglement: Literal['full', 'linear', 'circular'] = _DEFAULT_ENTANGLEMENT)
Bases: BaseEncoding
ZZ Feature Map encoding following Qiskit conventions.
ZZFeatureMap implements the second-order Pauli-Z expansion feature map commonly used in quantum kernel methods and QSVM. It creates entangled quantum states by applying Hadamard gates, phase rotations on single qubits, and ZZ entangling interactions between pairs of qubits.
This encoding follows the Qiskit convention for ZZFeatureMap, using the phase formula 2(π - xᵢ)(π - xⱼ) for two-qubit interactions, which creates a different kernel geometry compared to the standard IQP encoding.
The circuit structure for each repetition is:
|0⟩ ─ H ─ P(2x₀) ─╭──────────────╮─╭──────────────╮─
|0⟩ ─ H ─ P(2x₁) ─│ ZZ(φ₀₁) │─│ │─
|0⟩ ─ H ─ P(2x₂) ─╰──────────────╯─│ ZZ(φ₁₂) │─
... ╰──────────────╯
where φᵢⱼ = 2(π - xᵢ)(π - xⱼ) and ZZ interactions are applied according to the specified entanglement topology.
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. Each feature requires one qubit, so this also determines the number of qubits in the circuit. reps : int, default=2 Number of times to repeat the encoding layers. Higher values create deeper circuits that may capture more complex feature relationships. Must be at least 1. entanglement : {"full", "linear", "circular"}, default="full" Topology of ZZ interactions between qubits:
- "full": All-to-all connectivity. Every pair (i, j) with i < j has a
ZZ interaction. Creates n(n-1)/2 entangling gates per layer.
- "linear": Nearest-neighbor connectivity. Only pairs (i, i+1) have
ZZ interactions. Creates n-1 entangling gates per layer.
- "circular": Nearest-neighbor with periodic boundary. Like linear,
but adds a ZZ interaction between qubits n-1 and 0 when n > 2.
Attributes¶
reps : int Number of encoding layer repetitions. entanglement : str The entanglement topology ("full", "linear", or "circular"). n_features : int Number of classical features (inherited from BaseEncoding). n_qubits : int Number of qubits, equal to n_features.
Use Cases¶
Quantum Kernel Methods (QSVM): ZZFeatureMap is the canonical choice for quantum kernel estimation in supervised learning. The (π-x) phase convention creates kernels that can separate data classes that are not linearly separable in the original feature space.
Quantum Neural Networks: Use as a feature embedding layer before variational ansatz circuits. The entanglement creates correlations that parameterized layers can learn to exploit.
Benchmarking: Standard circuit for comparing quantum machine learning approaches across different platforms and simulators.
Research: Studying the effect of different phase conventions on kernel geometry and quantum advantage in machine learning tasks.
Limitations¶
Scalability: Full entanglement creates O(n²) ZZ pairs, leading to
deep circuits for large feature counts. For n > 10 features, consider
using entanglement='linear' or entanglement='circular'.
Hardware Connectivity: Full entanglement requires all-to-all qubit connectivity, which is not available on most near-term quantum hardware. SWAP gates will be inserted during transpilation, increasing circuit depth.
Noise Sensitivity: Deep circuits with many two-qubit gates are more susceptible to decoherence and gate errors on NISQ devices.
Classical Simulation: Unlike product-state encodings (e.g., AngleEncoding), ZZFeatureMap creates entangled states that cannot be efficiently simulated classically for large qubit counts.
Resource Analysis¶
The circuit resources scale as follows per repetition:
Full entanglement (entanglement='full'):
- ZZ pairs: n(n-1)/2
- CNOT gates: n(n-1) per rep
- Example: n=10 → 45 pairs, 90 CNOTs per rep
Linear entanglement (entanglement='linear'):
- ZZ pairs: n-1
- CNOT gates: 2(n-1) per rep
- Example: n=10 → 9 pairs, 18 CNOTs per rep
Circular entanglement (entanglement='circular'):
- ZZ pairs: n (for n > 2)
- CNOT gates: 2n per rep
- Example: n=10 → 10 pairs, 20 CNOTs per rep
Use gate_count_breakdown() for detailed gate counts, or
get_entanglement_pairs() to inspect the connectivity.
Entanglement Topology Trade-offs¶
+------------+------------------+----------------+------------------+ | Topology | Connectivity | ZZ Pairs | Hardware | +============+==================+================+==================+ | full | All-to-all | n(n-1)/2 | Requires SWAP | | | (maximum) | (quadratic) | gates on most HW | +------------+------------------+----------------+------------------+ | linear | Nearest-neighbor | n-1 | Native on most | | | (chain) | (linear) | superconducting | +------------+------------------+----------------+------------------+ | circular | Nearest-neighbor | n (for n > 2) | Native on ring | | | + wrap-around | (linear) | topologies | +------------+------------------+----------------+------------------+
Recommendation: Use entanglement='linear' for NISQ hardware
experiments to minimize SWAP overhead. Use entanglement='full'
for simulation studies or when maximum expressivity is needed.
Examples¶
Create a basic ZZ Feature Map with default settings:
from encoding_atlas import ZZFeatureMap import numpy as np enc = ZZFeatureMap(n_features=4) enc.n_qubits 4 enc.entanglement 'full'
Generate a PennyLane circuit:
x = np.array([0.5, 1.0, 1.5, 2.0]) circuit = enc.get_circuit(x, backend='pennylane') callable(circuit) True
Use linear entanglement for hardware-friendly circuits:
enc_linear = ZZFeatureMap(n_features=4, entanglement='linear') enc_linear.properties.two_qubit_gates 6
Generate Qiskit circuit (compatible with Qiskit's ZZFeatureMap):
qiskit_circuit = enc.get_circuit(x, backend='qiskit') qiskit_circuit.num_qubits 4
Batch processing with parallel execution:
X = np.random.randn(100, 4) circuits = enc.get_circuits(X, backend='pennylane', parallel=True) len(circuits) 100
Inspect entanglement connectivity:
enc.get_entanglement_pairs() [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
Get detailed gate count breakdown:
breakdown = enc.gate_count_breakdown() breakdown['cnot'] 24 breakdown['total'] 52
Access encoding properties:
props = enc.properties props.is_entangling True props.simulability 'not_simulable'
References¶
.. [1] Havlíček, V., et al. (2019). "Supervised learning with quantum-enhanced feature spaces." Nature, 567(7747), 209-212. .. [2] Qiskit Development Team. "ZZFeatureMap documentation."
See Also¶
IQPEncoding : Similar IQP-style encoding with direct xᵢxⱼ interactions. PauliFeatureMap : Generalized Pauli-based feature map. AngleEncoding : Simpler encoding without entanglement. DataReuploading : Re-uploads data with trainable intermediate layers.
Notes¶
Phase Convention: The (π - x) convention in ZZ interactions means:
- Features at x = π have zero interaction strength
- Features at x = 0 or x = 2π have maximum interaction
- This creates a kernel with different geometry than standard IQP
Comparison with IQPEncoding:
- ZZFeatureMap uses P(2x) phase gates; IQP uses RZ(2x)
- ZZFeatureMap uses (π-xᵢ)(π-xⱼ) for ZZ; IQP uses xᵢxⱼ
- Both create classically hard-to-simulate circuits
- Choice depends on data distribution and kernel requirements
Qiskit Compatibility: This implementation follows Qiskit conventions and produces equivalent circuits to qiskit.circuit.library.ZZFeatureMap.
Thread Safety: This class is thread-safe for circuit generation.
Multiple threads can safely call get_circuit() or get_circuits()
concurrently on the same encoding instance. The encoding object is not
modified during circuit generation, and input validation creates
defensive copies to prevent data races.
Circular Entanglement Edge Case: For n_features=2, circular entanglement produces the same connectivity as linear (only one pair). The wrap-around edge (n-1, 0) is only added when n > 2 to avoid duplicate pairs.
Initialize the ZZ Feature Map encoding.
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. Each feature requires one qubit, so this also determines the number of qubits in the circuit. reps : int, default=2 Number of encoding layer repetitions. Must be at least 1. entanglement : {"full", "linear", "circular"}, default="full" Topology of ZZ interactions between qubits.
Raises¶
ValueError If reps is not a positive integer. ValueError If entanglement is not one of "full", "linear", "circular". ValueError If n_features is less than 1 (raised by parent class).
Examples¶
enc = ZZFeatureMap(n_features=4) enc.n_qubits 4 enc.reps 2 enc.entanglement 'full'
enc = ZZFeatureMap(n_features=8, reps=3, entanglement='linear') len(enc.get_entanglement_pairs()) 7
Pauli Feature Map¶
PauliFeatureMap
¶
PauliFeatureMap(n_features: int, reps: int = _DEFAULT_REPS, paulis: list[str] | None = None, entanglement: Literal['full', 'linear', 'circular'] = _DEFAULT_ENTANGLEMENT)
Bases: BaseEncoding
Configurable Pauli feature map encoding for quantum machine learning.
PauliFeatureMap encodes classical data into quantum states using a configurable set of Pauli rotation gates. This provides flexibility to tailor the encoding to specific problem structures.
The circuit structure for each repetition consists of:
1. A Hadamard layer creating uniform superposition
2. Pauli rotation layers as specified by the paulis parameter
For single-qubit Paulis (e.g., "Z", "Y", "X"), rotations R_P(2φ(x_i)) are applied where φ is a feature mapping function.
For two-qubit Paulis (e.g., "ZZ", "YY", "XX"), entangling rotations exp(-i φ(x_i, x_j) P⊗P) are applied using the specified entanglement pattern.
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. reps : int, default=2 Number of repetitions (layers) of the feature map circuit. Higher values increase expressibility but also circuit depth. Must be at least 1. paulis : list[str] or None, default=None List of Pauli strings specifying which rotations to apply. Valid single-qubit terms: "X", "Y", "Z" Valid two-qubit terms: "XX", "YY", "ZZ", "XY", "XZ", "YZ" If None, defaults to ["Z", "ZZ"] (equivalent to ZZFeatureMap). entanglement : {"full", "linear", "circular"}, default="full" Pattern for two-qubit interactions: - "full": All pairs (i, j) where i < j - "linear": Consecutive pairs (i, i+1) - "circular": Linear plus (n-1, 0)
Attributes¶
reps : int Number of circuit repetitions. paulis : list[str] List of Pauli terms used in the encoding. entanglement : str Entanglement pattern for two-qubit gates.
Examples¶
Create a basic Pauli feature map with default settings:
from encoding_atlas import PauliFeatureMap import numpy as np enc = PauliFeatureMap(n_features=4) enc.n_qubits 4 enc.paulis ['Z', 'ZZ']
Create a custom feature map with Y and YY rotations:
enc = PauliFeatureMap(n_features=4, paulis=["Y", "YY"], reps=3) x = np.array([0.1, 0.2, 0.3, 0.4]) circuit = enc.get_circuit(x, backend='pennylane')
Use linear entanglement for hardware-friendly circuits:
enc_full = PauliFeatureMap(n_features=8, entanglement="full") enc_linear = PauliFeatureMap(n_features=8, entanglement="linear") enc_linear.properties.two_qubit_gates < enc_full.properties.two_qubit_gates True
Batch processing with parallel execution:
X = np.random.randn(100, 4) circuits = enc.get_circuits(X, backend='pennylane', parallel=True) len(circuits) 100
Inspect entanglement connectivity:
enc = PauliFeatureMap(n_features=4) enc.get_entanglement_pairs() [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
Get detailed gate count breakdown:
breakdown = enc.gate_count_breakdown() breakdown['cnot'] 24 breakdown['total'] 52
References¶
.. [1] Havlíček, V., et al. (2019). "Supervised learning with quantum- enhanced feature spaces." Nature, 567(7747), 209-212.
See Also¶
ZZFeatureMap : Specialized version with fixed Z and ZZ paulis. IQPEncoding : Similar encoding with IQP-style structure. AngleEncoding : Simpler encoding without entanglement.
Warnings¶
Full Entanglement with Many Features: When using entanglement='full'
with more than 12 features, a warning is issued because the circuit
complexity may exceed practical limits for NISQ devices. The number of
two-qubit gates scales as O(n²), which can lead to:
- Excessive circuit depth and gate errors
- Hardware connectivity constraints (all-to-all required)
- Potential trainability issues (barren plateaus)
Consider using entanglement='linear' or entanglement='circular'
for large feature counts.
Notes¶
Feature Interactions: The two-qubit Pauli gates encode products of features using the (π - xᵢ)(π - xⱼ) convention, allowing the quantum kernel to capture pairwise correlations.
Entanglement Topology Trade-offs:
+------------+------------------+---------------+------------------------+ | Topology | Gate Scaling | Connectivity | Best For | +============+==================+===============+========================+ | full | O(n²) | All-to-all | Max expressivity | +------------+------------------+---------------+------------------------+ | linear | O(n) | Nearest-only | NISQ devices | +------------+------------------+---------------+------------------------+ | circular | O(n) | Ring | Periodic problems | +------------+------------------+---------------+------------------------+
Pauli Term Trade-offs:
+-------+-------------------+------------------------------------------+ | Pauli | Gate Overhead | Notes | +=======+===================+==========================================+ | Z | Minimal | Native on most hardware | +-------+-------------------+------------------------------------------+ | X | Low (1 H gate) | Requires basis change | +-------+-------------------+------------------------------------------+ | Y | Medium (H + S) | Requires two basis change gates | +-------+-------------------+------------------------------------------+ | ZZ | 2 CNOTs | Standard for IQP/ZZFeatureMap | +-------+-------------------+------------------------------------------+ | XX | 2 CNOTs + 4 H | Basis change adds 4 single-qubit gates | +-------+-------------------+------------------------------------------+ | YY | 2 CNOTs + 8 gates | Most overhead due to Y-basis changes | +-------+-------------------+------------------------------------------+
Trainability: Deeper circuits (higher reps) with full entanglement and many Pauli terms may exhibit barren plateaus, where gradients become exponentially small. Mitigation strategies include:
- Using fewer repetitions (reps=1 or 2)
- Using sparser entanglement (linear or circular)
- Selecting fewer Pauli terms
- Layer-wise training approaches
Thread Safety: This class is thread-safe for circuit generation.
Multiple threads can safely call get_circuit() or get_circuits()
concurrently on the same encoding instance.
Initialize the Pauli feature map encoding.
Parameters¶
n_features : int Number of classical features to encode. reps : int, default=2 Number of repetitions of the feature map circuit. paulis : list[str] or None, default=None List of Pauli strings. Defaults to ["Z", "ZZ"]. entanglement : {"full", "linear", "circular"}, default="full" Entanglement pattern for two-qubit gates.
Raises¶
ValueError If any parameter is invalid. TypeError If paulis is not a list or None.
Warns¶
UserWarning If using full entanglement with more than 12 features, as the circuit complexity may exceed practical NISQ device limits.
Data Re-uploading¶
DataReuploading
¶
Bases: BaseEncoding
Data re-uploading quantum feature map with high expressivity.
DataReuploading implements a quantum encoding strategy where classical data is encoded multiple times throughout the circuit, interleaved with entangling layers. This multi-layer approach creates quantum states with rich Fourier spectra, enabling expressive quantum kernels and serving as a foundation for variational quantum classifiers.
The key insight is that re-uploading data at multiple circuit depths increases the number of accessible Fourier frequencies, similar to how depth increases expressivity in classical neural networks.
The circuit structure repeats L times:
|0⟩ ─ RY(x₀) ─╭────╮─ RY(x₀) ─╭────╮─ ... ─
|0⟩ ─ RY(x₁) ─│CNOT│─ RY(x₁) ─│CNOT│─ ... ─
|0⟩ ─ RY(x₂) ─│ │─ RY(x₂) ─│ │─ ... ─
... ╰────╯ ╰────╯
Each layer consists of data encoding (RY rotations) followed by entanglement (CNOT ladder), repeated n_layers times.
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. If n_features > n_qubits, features are cyclically mapped to qubits. n_layers : int, default=3 Number of re-uploading layers. Higher values increase the number of accessible Fourier frequencies but also circuit depth. Must be at least 1. More layers provide richer feature representations. n_qubits : int, optional Number of qubits in the circuit. If not specified, defaults to n_features. Can be set lower than n_features to use fewer qubits with cyclic feature mapping.
Attributes¶
n_layers : int Number of re-uploading layers. n_features : int Number of classical features (inherited from BaseEncoding). n_qubits : int Number of qubits in the circuit.
Examples¶
Create a basic data re-uploading encoding:
from encoding_atlas import DataReuploading import numpy as np enc = DataReuploading(n_features=4) enc.n_qubits 4 enc.n_layers 3
Generate a PennyLane circuit:
x = np.array([0.1, 0.2, 0.3, 0.4]) circuit = enc.get_circuit(x, backend='pennylane') callable(circuit) True
Use fewer qubits than features (cyclic mapping):
enc_compact = DataReuploading(n_features=8, n_qubits=4) enc_compact.n_qubits 4
Increase layers for higher expressivity:
enc_deep = DataReuploading(n_features=4, n_layers=10) enc_deep.depth 30
Generate circuits for different backends:
qiskit_circuit = enc.get_circuit(x, backend='qiskit') qiskit_circuit.num_qubits 4
Access encoding properties:
props = enc.properties props.is_entangling True
References¶
.. [1] Pérez-Salinas, A., et al. (2020). "Data re-uploading for a universal quantum classifier." Quantum, 4, 226. .. [2] Schuld, M., et al. (2021). "Effect of data encoding on the expressive power of variational quantum machine learning models."
See Also¶
AngleEncoding : Single-layer encoding without re-uploading. IQPEncoding : Entangling encoding without data re-uploading. HardwareEfficientEncoding : Similar structure optimized for hardware. PauliFeatureMap : Alternative multi-layer encoding approach.
Notes¶
Feature Map vs. Universal Approximation: This class implements a fixed feature map that provides high expressivity through repeated data encoding. For full universal function approximation as described in [1]_, trainable parameters must be added between data uploads. Use this encoding as the feature map layer in a variational circuit, then add trainable rotations to achieve universal approximation capability.
Fourier Expressivity: With L encoding layers, the quantum state can represent functions with Fourier frequencies up to ±L. More layers enable the representation of higher-frequency components. The specific Fourier coefficients are determined by the fixed circuit structure.
Trainability Considerations: While data re-uploading helps with expressivity, very deep circuits may still face trainability challenges. The interleaved structure generally provides better gradient flow than purely entangling circuits.
Feature Mapping: When n_features > n_qubits, feature xᵢ is encoded on qubit (i mod n_qubits). This allows compact representations but may create interference between features mapped to the same qubit.
Gate Set Choice: This implementation uses RY rotations for data encoding and a CNOT ladder for entanglement. The original single-qubit formulation in [1]_ uses general SU(2) (U3) rotations with three parameters per gate, and the multi-qubit extension uses CZ gates with varying entanglement topologies. The RY + CNOT variant used here is a common simplification adopted by many QML frameworks; it is less expressive per gate but sufficient for most feature map applications. The Fourier frequency analysis from [2]_ applies regardless of the specific rotation axis.
Comparison with Classical Deep Learning: Data re-uploading is analogous to residual connections in deep neural networks, where information about the input is preserved and refined through layers.
Initialize the data re-uploading encoding.
Parameters¶
n_features : int Number of classical features to encode. n_layers : int, default=3 Number of re-uploading layers for expressivity. n_qubits : int, optional Number of qubits. Defaults to n_features if not specified.
Raises¶
ValueError If n_layers is less than 1. ValueError If n_qubits is less than 1 (when specified). ValueError If n_features is less than 1 (raised by parent class).
Warns¶
UserWarning If n_layers exceeds the deep circuit threshold (default 10), as very deep circuits may face trainability challenges due to barren plateaus.
Examples¶
enc = DataReuploading(n_features=4) enc.n_qubits 4 enc.n_layers 3
enc = DataReuploading(n_features=4, n_layers=5, n_qubits=2) enc.n_qubits 2 enc.n_layers 5
Hardware Efficient Encoding¶
HardwareEfficientEncoding
¶
HardwareEfficientEncoding(n_features: int, reps: int = 2, rotation: Literal['X', 'Y', 'Z'] = 'Y', entanglement: Literal['linear', 'circular', 'full'] = 'linear')
Bases: BaseEncoding
Hardware-efficient encoding optimized for NISQ devices.
HardwareEfficientEncoding implements a quantum data encoding that uses only native gates and respects physical qubit connectivity constraints. This makes it particularly suitable for near-term quantum devices where gate errors and limited connectivity are significant concerns.
The encoding uses simple alternating layers of single-qubit rotations (parameterized by input features) and entangling CNOT gates following a linear or circular topology.
The circuit structure for each repetition is:
|0⟩ ─ Rₐ(x₀) ─╭────╮─ Rₐ(x₀) ─╭────╮─
|0⟩ ─ Rₐ(x₁) ─│CNOT│─ Rₐ(x₁) ─│CNOT│─
|0⟩ ─ Rₐ(x₂) ─│ │─ Rₐ(x₂) ─│ │─
... ╰────╯ ╰────╯
where Rₐ is the chosen rotation gate (RX, RY, or RZ) and CNOT gates connect neighboring qubits according to the entanglement topology.
Parameters¶
n_features : int Number of classical features to encode. Must be a positive integer. Each feature requires one qubit, so this also determines the number of qubits in the circuit. reps : int, default=2 Number of times to repeat the encoding layers. Higher values increase circuit expressivity but also depth and accumulated errors. Must be at least 1. rotation : {"X", "Y", "Z"}, default="Y" Axis of rotation for single-qubit gates:
- "X": Uses RX gates, commonly native on superconducting qubits
- "Y": Uses RY gates (default), creates real-valued amplitudes
- "Z": Uses RZ gates, often "virtual" gates with zero error
The optimal choice depends on the target hardware's native gate set.
entanglement : {"linear", "circular", "full"}, default="linear" Topology of CNOT entangling gates:
- "linear": Nearest-neighbor connectivity. CNOT gates connect pairs
(0,1), (1,2), ..., (n-2, n-1). Creates n-1 entangling gates per layer.
Matches linear qubit arrays common in superconducting devices.
- "circular": Linear connectivity plus periodic boundary. Adds a CNOT
between qubits n-1 and 0. Creates n entangling gates per layer.
Matches circular/ring qubit topologies.
- "full": All-to-all connectivity. CNOT gates connect every pair of
qubits. Creates n(n-1)/2 entangling gates per layer (O(n²) scaling).
Matches ion trap devices (IonQ, Quantinuum) with global connectivity.
Attributes¶
reps : int Number of encoding layer repetitions. rotation : str The rotation axis ("X", "Y", or "Z"). entanglement : str The entanglement topology ("linear", "circular", or "full"). n_features : int Number of classical features (inherited from BaseEncoding). n_qubits : int Number of qubits, equal to n_features.
Examples¶
Create a basic hardware-efficient encoding:
from encoding_atlas import HardwareEfficientEncoding import numpy as np enc = HardwareEfficientEncoding(n_features=4) enc.n_qubits 4 enc.rotation 'Y'
Generate a PennyLane circuit:
x = np.array([0.1, 0.2, 0.3, 0.4]) circuit = enc.get_circuit(x, backend='pennylane') callable(circuit) True
Use RZ rotations (often "virtual" gates with zero error):
enc_rz = HardwareEfficientEncoding(n_features=4, rotation='Z') enc_rz.rotation 'Z'
Use circular entanglement for ring topologies:
enc_circ = HardwareEfficientEncoding(n_features=4, entanglement='circular') enc_circ.properties.two_qubit_gates 8
Generate circuits for different backends:
qiskit_circuit = enc.get_circuit(x, backend='qiskit') qiskit_circuit.num_qubits 4 cirq_circuit = enc.get_circuit(x, backend='cirq') len(cirq_circuit.all_qubits()) 4
Access encoding properties:
props = enc.properties props.is_entangling True props.trainability_estimate 0.8
References¶
.. [1] Kandala, A., et al. (2017). "Hardware-efficient variational quantum eigensolver for small molecules and quantum magnets." Nature. .. [2] Cerezo, M., et al. (2021). "Variational quantum algorithms." Nature Reviews Physics.
See Also¶
AngleEncoding : Simpler encoding without entanglement. DataReuploading : Re-uploads data for universal approximation. IQPEncoding : Higher expressivity with ZZ interactions. ZZFeatureMap : Qiskit-style feature map encoding.
Notes¶
Hardware Native Gates: Most superconducting quantum computers support RX, RY, RZ rotations and CNOT (or CZ) as native gates. Ion trap devices typically support different gate sets. Choose the rotation type that matches your target hardware.
Virtual RZ Gates: On many superconducting devices, RZ gates are implemented virtually (by updating the phase tracking) with essentially zero error. Using rotation='Z' can significantly reduce circuit errors.
Depth Considerations: Each repetition adds one rotation layer and one entanglement layer. For n qubits with linear entanglement: - Total single-qubit gates: reps × n - Total two-qubit gates: reps × (n-1) - Circuit depth: approximately 2 × reps
Expressivity vs. Trainability: Hardware-efficient encodings typically have good trainability (gradients don't vanish as quickly as random circuits) but may have lower expressivity than IQP-style encodings. This trade-off often favors hardware-efficient designs on NISQ devices.
Initialize the hardware-efficient encoding.
Parameters¶
n_features : int Number of classical features to encode. reps : int, default=2 Number of encoding layer repetitions. rotation : {"X", "Y", "Z"}, default="Y" Rotation axis for single-qubit gates. entanglement : {"linear", "circular", "full"}, default="linear" Topology for entangling CNOT gates.
Raises¶
ValueError If reps is less than 1. ValueError If rotation is not one of "X", "Y", "Z". ValueError If entanglement is not one of "linear", "circular", "full". ValueError If n_features is less than 1 (raised by parent class).
Analysis Module¶
analysis
¶
Analysis tools for quantum encoding properties.
This module provides quantitative analysis capabilities for quantum encodings, including resource counting, simulability analysis, expressibility computation, entanglement capability measurement, and trainability estimation.
Analysis Functions¶
The main analysis functions are:
- :func:
count_resources: Count computational resources (gates, depth, qubits) - :func:
get_resource_summary: Quick resource summary from encoding properties - :func:
get_gate_breakdown: Detailed gate-by-gate breakdown - :func:
compare_resources: Compare resources across multiple encodings - :func:
estimate_execution_time: Estimate circuit execution time - :func:
check_simulability: Determine if classical simulation is efficient - :func:
get_simulability_reason: Get a concise simulability explanation - :func:
is_clifford_circuit: Check if encoding uses only Clifford gates - :func:
is_matchgate_circuit: Check if encoding uses only matchgate operations - :func:
estimate_entanglement_bound: Estimate upper bound on entanglement entropy - :func:
compute_expressibility: Measure Hilbert space coverage - :func:
compute_entanglement_capability: Measure entanglement generation - :func:
estimate_trainability: Detect barren plateau risk - :func:
compute_kernel_concentration: Measure fidelity-kernel concentration - :func:
estimate_concentration_scaling: Track concentration across widths - :func:
summarize_kernel_concentration: Concentration from a given kernel - :func:
scan_feature_ranges: Measure sensitivity to feature scaling - :func:
recommend_feature_range: Pick the range that maximises alignment
Utility Functions¶
Lower-level utilities for custom analysis:
- :func:
simulate_encoding_statevector: Simulate circuit to get statevector - :func:
compute_fidelity: Compute fidelity between pure states - :func:
compute_purity: Compute purity of a density matrix - :func:
partial_trace_single_qubit: Compute reduced density matrix
Examples¶
Basic usage:
from encoding_atlas import AngleEncoding, IQPEncoding from encoding_atlas.analysis import count_resources, check_simulability
Compare resources between encodings¶
enc_simple = AngleEncoding(n_features=4) enc_complex = IQPEncoding(n_features=4, reps=2)
res_simple = count_resources(enc_simple) res_complex = count_resources(enc_complex)
print(f"Simple encoding: {res_simple['gate_count']} gates") print(f"Complex encoding: {res_complex['gate_count']} gates")
Advanced analysis:
from encoding_atlas.analysis import simulate_encoding_statevector import numpy as np
enc = AngleEncoding(n_features=2) x = np.array([0.5, 1.0]) state = simulate_encoding_statevector(enc, x) print(f"State dimension: {len(state)}")
See Also¶
encoding_atlas.core.properties : Encoding property dataclasses. encoding_atlas.core.exceptions : Analysis-related exceptions.
ConcentrationResult
dataclass
¶
ConcentrationResult(n_qubits: int, n_samples: int, offdiagonal_mean: float, offdiagonal_variance: float, offdiagonal_std: float, offdiagonal_min: float, offdiagonal_max: float, haar_mean: float, haar_variance: float, concentration_ratio: float, mean_ratio: float, identity_distance: float, is_concentrated: bool, threshold: float, shots_per_entry: float)
Fidelity-kernel concentration measured at one circuit width.
Attributes¶
n_qubits : int
Circuit width the kernel was measured at.
n_samples : int
Number of inputs used; statistics are over the
n_samples (n_samples - 1) / 2 off-diagonal pairs.
offdiagonal_mean : float
Mean off-diagonal kernel entry, in [0, 1].
offdiagonal_variance, offdiagonal_std : float
Spread of the off-diagonal entries — the signal a kernel method
actually uses. Its vanishing is concentration.
offdiagonal_min, offdiagonal_max : float
Extremes of the off-diagonal entries.
haar_mean, haar_variance : float
Haar-random reference moments at this width, from
:func:haar_kernel_moments.
concentration_ratio : float
offdiagonal_variance / haar_variance — the order parameter.
Near 1 means the kernel's spread has reached the Haar floor and
carries no usable geometry; larger means structure remains. Directly
comparable across qubit counts.
mean_ratio : float
offdiagonal_mean / haar_mean. Reported for completeness only:
it is 1 for any ensemble of independent random single-qubit states
as well as for Haar, so it cannot distinguish the two regimes. Do not
use it as a concentration measure.
identity_distance : float
Root-mean-square off-diagonal entry, i.e.
||K - I||_F / sqrt(n (n - 1)). Zero means K is exactly I.
is_concentrated : bool
Whether concentration_ratio is below :attr:threshold.
threshold : float
The concentration_ratio cut used for :attr:is_concentrated.
shots_per_entry : float
Shots per kernel entry for the compute-uncompute estimator's standard
error to fall below half the observed spread —
4 K_bar (1 - K_bar) / Var. inf if the entries have no spread.
A noiseless lower bound on hardware cost.
shots_for_dataset
¶
Total shots to estimate a full n_samples x n_samples kernel.
The kernel is symmetric with a deterministic unit diagonal, so only
n (n - 1) / 2 entries need measuring.
Parameters¶
n_samples : int Dataset size. Must be a positive integer.
Returns¶
float
shots_per_entry * n (n - 1) / 2; inf if the per-entry cost
is unbounded.
Raises¶
ValueError
If n_samples is not a positive integer.
EncodingCharacterization
dataclass
¶
EncodingCharacterization(encoding_name: str, n_qubits: int, n_features: int, is_simulable: bool | None, metrics: Mapping[str, Any], notes: Mapping[str, str])
A computed, multi-axis profile of a single encoding.
Attributes¶
encoding_name : str
The encoding's class name.
n_qubits, n_features : int
Circuit width and input dimensionality.
is_simulable : bool or None
Whether the encoding is classically efficiently simulable.
metrics : Mapping[str, float | None]
Measured metrics. Always includes depth, expressibility,
entanglement_capability, trainability_estimate,
noise_retained_fidelity and the concentration axis
(kernel_concentration_ratio, kernel_offdiagonal_mean,
kernel_shots_per_entry, kernel_is_concentrated); includes
kernel_target_alignment, geometric_difference and
effective_dimension when a dataset was supplied. A value is
None if that axis could not be computed (see notes).
depth/expressibility/entanglement_capability/
trainability_estimate share the atlas's definitions;
noise_retained_fidelity is the fidelity-based noise metric (distinct
from the atlas's noise_resilience trade-off column).
kernel_is_concentrated is a bool, not a float.
notes : Mapping[str, str]
Per-axis failure reasons for any metric that is None.
metric
¶
Return a metric value, or default if absent or uncomputable.
EntanglementResult
¶
Bases: TypedDict
Result dictionary from entanglement capability computation.
Attributes¶
entanglement_capability : float
Average entanglement measure in [0, 1]. Higher values indicate
the encoding creates more entangled states on average.
entanglement_samples : NDArray[np.floating]
Array of entanglement measure values for each sampled input.
Shape: (n_samples,). The measure type depends on the measure
parameter used (Meyer-Wallach or Scott).
std_error : float
Standard error of the mean, computed as std / sqrt(n_samples).
Useful for assessing the reliability of the estimate.
n_samples : int
Number of random samples used in the computation.
per_qubit_entanglement : NDArray[np.floating]
Average linear entropy contribution from each qubit.
Shape: (n_qubits,). Useful for identifying which qubits
are most entangled. Note: Only meaningful for Meyer-Wallach
measure; returns zeros for Scott measure.
measure : str
The entanglement measure used: "meyer_wallach" or "scott".
scott_k : int | None
The k value used for Scott measure, or None if Meyer-Wallach was used.
entanglement_ci_lower : float
Lower bound of the percentile bootstrap confidence interval on
entanglement_capability at confidence_level. Computed by
resampling entanglement_samples with replacement.
entanglement_ci_upper : float
Upper bound of the bootstrap CI on entanglement_capability.
confidence_level : float
Two-sided confidence level used for the CI bounds above
(default 0.95 → 95% CIs).
sampling : {'uniform', 'sobol'}
Sampling strategy that produced the per-sample inputs. See
:func:compute_entanglement_capability for details.
ExpressibilityResult
¶
Bases: TypedDict
Result dictionary from expressibility computation.
This TypedDict defines the structure of the result returned by
:func:compute_expressibility when return_distributions=True.
Attributes¶
expressibility : float
Normalized expressibility score in [0, 1]. Higher values indicate
the encoding's fidelity distribution is closer to Haar-random,
meaning it can explore more of the Hilbert space.
kl_divergence : float
Raw Kullback-Leibler divergence from the encoding's fidelity
distribution to the Haar distribution. Lower values indicate
higher expressibility.
fidelity_distribution : NDArray[np.floating]
Histogram of sampled fidelities (probability for each bin).
Shape: (n_bins,). Values sum to approximately 1.
haar_distribution : NDArray[np.floating]
Theoretical Haar-random fidelity distribution evaluated at
the same bin centers. Shape: (n_bins,).
bin_edges : NDArray[np.floating]
Edges of the histogram bins. Shape: (n_bins + 1,).
Range is [0, 1] for fidelity values.
n_samples : int
Number of fidelity samples used in the computation.
n_bins : int
Number of histogram bins used.
convergence_estimate : float
Bootstrap estimate of the standard error of the KL divergence.
Smaller values indicate more reliable results.
mean_fidelity : float
Mean of the sampled fidelities. Useful for understanding
the typical overlap between encoded states.
std_fidelity : float
Standard deviation of the sampled fidelities.
expressibility_ci_lower : float
Lower bound of the percentile bootstrap confidence interval on
expressibility at confidence_level. Computed by
resampling the per-sample fidelity array and re-running the
full histogram → KL → score pipeline on each resample.
expressibility_ci_upper : float
Upper bound of the percentile bootstrap CI on expressibility.
mean_fidelity_ci_lower : float
Lower bound of the bootstrap CI on mean_fidelity.
mean_fidelity_ci_upper : float
Upper bound of the bootstrap CI on mean_fidelity.
confidence_level : float
Two-sided confidence level used for the CI bounds above
(default 0.95 → 95% CIs).
sampling : {'uniform', 'sobol'}
Sampling strategy that produced the fidelity pairs. See
:func:compute_expressibility for details.
Examples¶
result = compute_expressibility(enc, return_distributions=True, seed=42) print(f"Expressibility: {result['expressibility']:.4f}") print(f"KL Divergence: {result['kl_divergence']:.4f}") print(f"Mean Fidelity: {result['mean_fidelity']:.4f}") lo, hi = result['expressibility_ci_lower'], result['expressibility_ci_upper'] print(f"95% CI: [{lo:.4f}, {hi:.4f}]")
FeatureRangeResult
dataclass
¶
FeatureRangeResult(low: float, high: float, width: float, alignment: float, concentration_ratio: float, offdiagonal_mean: float, offdiagonal_variance: float)
An encoding measured at one feature-scaling range.
Attributes¶
low, high : float
The range the features were scaled into.
width : float
high - low, as a convenience for plotting against period coverage.
alignment : float
Kernel-target alignment at this range, in [-1, 1]. Higher predicts
better downstream accuracy.
concentration_ratio : float
Off-diagonal kernel variance over the Haar variance. Near 1 and with
offdiagonal_mean near 1/2**n_qubits means the kernel has
collapsed onto the Haar floor. Not monotone in the range width on its
own — see the module docstring.
offdiagonal_mean, offdiagonal_variance : float
Raw moments of the kernel's off-diagonal entries. The mean is the
cleaner read on how close the kernel is to the floor: it decreases
towards 1/2**n_qubits as the range widens.
FeatureRangeScan
dataclass
¶
FeatureRangeScan(encoding_name: str, n_qubits: int, n_features: int, n_samples_used: int, n_samples_supplied: int, centered: bool, results: tuple[FeatureRangeResult, ...])
How an encoding's kernel geometry responds to feature scaling.
Attributes¶
encoding_name : str Class name of the scanned encoding. n_qubits, n_features : int Circuit width and input dimensionality. n_samples_used, n_samples_supplied : int Rows used after sub-sampling, and rows supplied. centered : bool Whether the centred alignment variant was used. results : tuple[FeatureRangeResult, ...] One measurement per range, in the order scanned.
alignment_spread
property
¶
Highest minus lowest alignment across the scanned ranges.
A large spread means the scaling choice matters as much as, or more than, the choice of encoding — so it should be reported, not assumed.
best
property
¶
best: FeatureRangeResult
The range with the highest alignment.
Ties break towards the narrower range, which is the more conservative choice: it keeps the kernel further from the concentration floor.
Raises¶
RuntimeError If the scan produced no measurements.
at
¶
at(low: float, high: float) -> FeatureRangeResult | None
Return the measurement for a given range, or None.
NoiseResilienceResult
dataclass
¶
NoiseResilienceResult(retained_fidelity: float, fidelity_decay: float, noise_level: str, single_qubit_error: float, two_qubit_error: float, n_samples: int, std_fidelity: float, min_fidelity: float, max_fidelity: float)
Result of a noise-resilience analysis.
Attributes¶
retained_fidelity : float
Mean fidelity between the ideal and noisy states over the sampled
inputs, in [0, 1] (1 = perfectly resilient).
fidelity_decay : float
1 - retained_fidelity: the fraction of state fidelity lost to noise.
noise_level : str
The preset level used, or "custom" for explicit noise_params.
single_qubit_error, two_qubit_error : float
The depolarizing probabilities applied after one- and two-qubit gates.
n_samples : int
Number of random inputs averaged over.
std_fidelity : float
Standard deviation of the per-input fidelities.
min_fidelity, max_fidelity : float
Extremes of the per-input fidelities.
ScalingResult
dataclass
¶
ScalingResult(encoding_name: str, n_qubits: tuple[int, ...], feature_counts: tuple[int, ...], results: tuple[ConcentrationResult, ...], decay_rate: float, mean_decay_rate: float, haar_normalized_slope: float, r_squared: float, skipped: dict[int, str])
How an encoding's kernel concentration scales with circuit width.
Attributes¶
encoding_name : str
Class name of the encodings the factory produced.
n_qubits : tuple[int, ...]
Circuit widths successfully measured, ascending.
feature_counts : tuple[int, ...]
Feature counts that produced those widths, in the same order.
results : tuple[ConcentrationResult, ...]
Per-width measurements, in the same order.
decay_rate : float
Per-qubit factor by which the off-diagonal variance shrinks, from
a least-squares fit of log(variance) against n_qubits. 1.0
means no decay. The Haar floor itself decays at 4.0, so a rate
near 4 means the encoding is scrambling as fast as it possibly can.
nan if fewer than two widths were measurable.
mean_decay_rate : float
The same quantity for the off-diagonal mean. The Haar floor decays at
2.0. nan if unfittable.
haar_normalized_slope : float
Slope of log(concentration_ratio) against n_qubits. Negative
means the kernel is collapsing towards the Haar floor as the circuit
widens; non-negative means it holds structure or pulls away. This is
the honest discriminator — a large decay_rate alone proves nothing,
because the floor is falling too.
r_squared : float
Coefficient of determination of the log(variance) fit, in
[0, 1]. Well below ~0.9 means the log-linear model — and any
extrapolation from it — should not be trusted. nan for fewer than
two points.
skipped : dict[int, str]
Feature counts that could not be measured, mapped to the reason.
concentration_ratios
property
¶
The measured concentration_ratio at each width.
offdiagonal_means
property
¶
The measured mean off-diagonal entry at each width.
offdiagonal_variances
property
¶
The measured off-diagonal variance at each width.
concentration_horizon
¶
Smallest qubit count at which the kernel reaches the Haar floor.
Resolution order:
- If the measured widths show a sustained crossing — some width
from which every wider measured point is also below
threshold— the smallest such width is returned. No extrapolation involved. The crossing must be sustained: a single dip below the threshold at the narrowest width, followed by growth away from the floor, is not a horizon. - Otherwise, if :attr:
haar_normalized_slopeis negative, the fit is extrapolated from the widest measured point to the first integer width whose predicted ratio falls belowthreshold. - Otherwise
None: the encoding shows no sign of approaching the floor over the measured range.
Parameters¶
threshold : float, default=:data:CONCENTRATION_THRESHOLD
concentration_ratio defining the floor. Must be positive.
Returns¶
int or None
The horizon, or None if the measurement does not support one.
Raises¶
ValueError
If threshold is not positive.
Warnings¶
Case 2 is an extrapolation from a fitted log-linear model. Check
:attr:r_squared before relying on it.
shots_per_entry_at
¶
Extrapolate the per-entry shot requirement to a wider circuit.
Propagates the mean and the variance forward with their own fitted
decay rates and re-evaluates 4 K_bar (1 - K_bar) / Var. Using both
fits matters: the relative spread of the entries is not preserved as
the circuit widens, so scaling the standard deviation with the mean
would be wrong by an exponential factor.
Parameters¶
n_qubits : int Target circuit width. Must be a positive integer. Widths at or below the measured range return the nearest measurement instead of an extrapolation.
Returns¶
float
Extrapolated shots per kernel entry, or nan if no fit exists.
Raises¶
ValueError
If n_qubits is not a positive integer.
Warnings¶
Beyond the measured range this is an extrapolation, not a measurement.
Check :attr:r_squared.
SimulabilityResult
¶
Bases: TypedDict
Result of simulability analysis.
Attributes¶
is_simulable : bool True if the encoding circuit can be efficiently simulated classically. simulability_class : str One of "simulable", "conditionally_simulable", "not_simulable". reason : str Human-readable explanation of the classification. details : dict[str, Any] Additional analysis details including:
- is_entangling: Whether circuit creates entanglement
- is_clifford: Whether circuit uses only Clifford gates
- is_matchgate: Whether circuit uses only matchgate operations
- entanglement_pattern: Description of entanglement structure
- two_qubit_gate_count: Number of two-qubit gates
- n_qubits: Number of qubits
- declared_simulability: Simulability from encoding properties
list[str]
Suggestions for simulation approaches.
TrainabilityResult
¶
Bases: TypedDict
Result of trainability estimation.
This TypedDict defines the structure of the detailed result returned
by :func:estimate_trainability when return_details=True.
Attributes¶
trainability_estimate : float Overall trainability score in the range [0, 1], where higher values indicate better trainability. This score is derived from the gradient variance using a logistic transformation. gradient_variance : float Average variance of parameter gradients computed over successful samples only. This is the primary metric for barren plateau detection. Values near zero indicate a barren plateau. barren_plateau_risk : {"low", "medium", "high"} Categorical assessment of barren plateau risk based on gradient variance and circuit size:
- "low": Variance is healthy, training should proceed normally
- "medium": Variance is borderline, training may be slow
- "high": Variance is very low, likely barren plateau
float
Estimated effective parameter dimension (HEURISTIC METRIC), computed
as the count of parameters with variance exceeding 1% of the mean
variance. This indicates how many parameters contribute meaningfully
to the optimization landscape. Note: This is not a standard metric
in barren plateau literature; it is provided for diagnostic purposes.
For rigorous analysis, examine per_parameter_variance directly.
n_samples : int
Total number of random parameter samples requested for the estimate.
n_successful_samples : int
Number of samples where gradient computation succeeded. This is the
actual sample size used for variance computation. Equal to
n_samples - n_failed_samples.
per_parameter_variance : NDArray[np.floating]
Array of variance values for each individual parameter, computed
over successful samples only. This can reveal if specific parameters
are more trainable than others.
n_failed_samples : int
Number of samples where gradient computation failed. High values
may indicate issues with the encoding or simulation backend.
Failed samples are excluded from variance computation to ensure
unbiased statistical estimates.
trainability_ci_lower : float
Lower bound of the percentile bootstrap CI on
trainability_estimate at confidence_level. Computed by
resampling the per-sample gradient matrix and re-running the
variance → trainability mapping on each resample.
trainability_ci_upper : float
Upper bound of the bootstrap CI on trainability_estimate.
gradient_variance_ci_lower : float
Lower bound of the bootstrap CI on gradient_variance.
gradient_variance_ci_upper : float
Upper bound of the bootstrap CI on gradient_variance.
confidence_level : float
Two-sided confidence level used for the CI bounds above
(default 0.95 → 95% CIs).
sampling : {'uniform', 'sobol'}
Sampling strategy that produced the per-sample inputs. See
:func:estimate_trainability for details.
atlas_comparable_metrics
¶
Return the metric keys that can be compared against the atlas.
centered_kernel_target_alignment
¶
centered_kernel_target_alignment(K: NDArray[floating[Any]], y: NDArray[integer[Any] | floating[Any]]) -> float
Centred kernel-target alignment in [-1, 1] (Cortes et al., 2012).
Centring removes the bias the uncentred score suffers for fidelity kernels
(whose diagonal is always 1), giving a more faithful measure of task
alignment. Returns 0.0 for degenerate inputs or n < 2.
This is the variant the benchmark uses and the one to screen encodings
with. It is already invariant to affine relabelling of y; labels are
normalised through :func:_signed_labels so both variants agree on what a
two-class split means.
check_simulability
¶
check_simulability(encoding: BaseEncoding, detailed: bool = True) -> SimulabilityResult
Check whether an encoding circuit is classically simulable.
Analyzes the encoding's circuit structure to determine if it can be efficiently simulated on a classical computer. This analysis considers entanglement structure, gate set, and circuit topology.
Parameters¶
encoding : BaseEncoding The encoding instance to analyze. detailed : bool, default=True If True, include detailed analysis in the result. If False, return minimal result with just classification.
Returns¶
SimulabilityResult Dictionary containing:
- ``is_simulable`` : bool
True if efficiently classically simulable.
- ``simulability_class`` : str
One of "simulable", "conditionally_simulable", "not_simulable".
- ``reason`` : str
Human-readable explanation of the classification.
- ``details`` : dict
Additional analysis details (if detailed=True):
- is_entangling: Whether circuit creates entanglement
- is_clifford: Whether circuit uses only Clifford gates
- entanglement_pattern: Description of entanglement structure
- two_qubit_gate_count: Number of two-qubit gates
- n_qubits: Number of qubits
- declared_simulability: Simulability from encoding properties
- ``recommendations`` : list[str]
Suggestions for simulation approaches.
Raises¶
AnalysisError If encoding is not a valid BaseEncoding instance or analysis fails.
Examples¶
Check a non-entangling encoding:
from encoding_atlas import AngleEncoding enc = AngleEncoding(n_features=4) result = check_simulability(enc) print(result['simulability_class']) simulable print(result['reason']) Encoding produces only product states (no entanglement)
Check an entangling encoding:
from encoding_atlas import IQPEncoding enc = IQPEncoding(n_features=4, reps=2) result = check_simulability(enc) print(result['simulability_class']) not_simulable print(result['recommendations']) ['Use statevector simulation for small instances (< 20 qubits)', ...]
Quick check without details:
result = check_simulability(enc, detailed=False) if result['is_simulable']: ... print("Can use classical simulation")
Notes¶
Simulability Classes:
-
simulable: Circuit can always be efficiently simulated. Examples: Clifford circuits, product state circuits. -
conditionally_simulable: Circuit may be simulable depending on input data, circuit parameters, or specific structure. Examples: Circuits with linear/circular entanglement topology. -
not_simulable: Circuit is believed to be hard to simulate classically. Examples: IQP circuits, random circuits with high entanglement.
Limitations:
This analysis is based on known theoretical results and heuristics. It may be conservative (classifying some simulable circuits as not_simulable) but aims not to falsely claim simulability.
See Also¶
is_clifford_circuit : Check if circuit uses only Clifford gates. estimate_entanglement_bound : Estimate entanglement entropy bound. count_resources : Get detailed gate counts.
compare_resources
¶
compare_resources(encodings: list[BaseEncoding], metrics: list[str] | None = None, *, include_names: bool = True) -> dict[str, list[Any]]
Compare resources across multiple encodings.
Provides side-by-side comparison of resource metrics for a list of encodings. Useful for encoding selection and benchmarking.
Parameters¶
encodings : list[BaseEncoding] List of encodings to compare. Must all be valid BaseEncoding instances. metrics : list[str], optional Specific metrics to compare. If None, compares all standard metrics. Available metrics:
- ``"n_qubits"``: Number of qubits
- ``"depth"``: Circuit depth
- ``"gate_count"``: Total gates
- ``"single_qubit_gates"``: Single-qubit gate count
- ``"two_qubit_gates"``: Two-qubit gate count
- ``"parameter_count"``: Parameterized gate count
- ``"two_qubit_ratio"``: Ratio of two-qubit gates
- ``"gates_per_qubit"``: Average gates per qubit
bool, default=True
If True, include encoding names in the result.
Returns¶
dict[str, list[Any]]
Dictionary mapping metric names to lists of values
(one per encoding). If include_names=True, includes
an "encoding_name" key with encoding class names.
Raises¶
AnalysisError If any encoding is invalid. ValueError If encodings list is empty.
Examples¶
from encoding_atlas import AngleEncoding, IQPEncoding from encoding_atlas.analysis import compare_resources
encodings = [ ... AngleEncoding(n_features=4), ... IQPEncoding(n_features=4, reps=1), ... IQPEncoding(n_features=4, reps=2), ... ] comparison = compare_resources(encodings) print(comparison['encoding_name']) ['AngleEncoding', 'IQPEncoding', 'IQPEncoding'] print(comparison['gate_count']) [4, 26, 52]
Compare specific metrics:
comparison = compare_resources( ... encodings, ... metrics=['gate_count', 'two_qubit_gates'], ... ) print(comparison.keys()) dict_keys(['encoding_name', 'gate_count', 'two_qubit_gates'])
Notes¶
For data-dependent encodings, this function returns worst-case
(maximum) values from encoding properties. Use :func:count_resources
with specific input data for actual counts.
See Also¶
count_resources : Get resources for a single encoding. get_resource_summary : Quick summary for a single encoding.
compare_to_atlas
¶
compare_to_atlas(characterization: EncodingCharacterization, metric: str) -> dict[str, Any]
Rank a profiled encoding against the 16 built-in encodings on metric.
Only axes that share the atlas's definition are supported (see
:func:atlas_comparable_metrics): depth (lower is better),
expressibility, entanglement_capability and
trainability_estimate (higher is better).
Parameters¶
characterization : EncodingCharacterization
A profile produced by :func:profile_encoding.
metric : str
The axis to compare on.
Returns¶
dict
{"metric", "value", "rank", "n_atlas", "percentile", "beats",
"atlas_min", "atlas_median", "atlas_max", "higher_is_better"} where
rank is 1-based (1 = better than every atlas encoding) and
percentile is the fraction of atlas encodings the value beats.
Raises¶
ValueError
If metric is not atlas-comparable or was not computed.
compute_all_parameter_gradients
¶
compute_all_parameter_gradients(encoding: BaseEncoding, x: NDArray[floating[Any]], observable: Literal['computational', 'global_z', 'local_z', 'pauli_z'] = 'computational', backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> FloatArray
Compute gradients for all parameters using the parameter-shift rule.
Builds the full (2 * n_params, n_features) matrix of shifted
parameter vectors, dispatches a single
:func:simulate_encoding_statevectors_batch call, and evaluates all
expectation values with one vectorised observable computation.
Compared to the previous implementation — a Python for loop over
parameters, each calling :func:compute_parameter_gradient and
therefore each invoking the backend simulator twice independently —
this version still performs the same 2 * n_params statevector
simulations (the parameter-shift rule fundamentally requires them)
but amortises the Python and observable-evaluation overhead into a
single pass. The numerical result is identical to the per-call
version (verified by test_batched_matches_per_call_gradient).
Parameters¶
encoding : BaseEncoding
The encoding instance.
x : NDArray[np.floating]
Input data vector of shape (n_features,).
observable : {"computational", "global_z", "local_z", "pauli_z"}, default="computational"
Observable to measure. See :func:compute_parameter_gradient
for the mathematical definitions. "pauli_z" is a deprecated
alias for "global_z" and emits a DeprecationWarning.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Simulation backend.
Returns¶
FloatArray
Array of gradients of shape (n_features,). The element at
index i is ∂⟨O⟩/∂xᵢ evaluated at the input point x.
Raises¶
ValidationError
If x is not a 1-D array.
ValueError
If observable is not one of the recognised options.
SimulationError
If the underlying batched simulation fails.
Examples¶
from encoding_atlas import AngleEncoding import numpy as np
enc = AngleEncoding(n_features=3) x = np.array([0.5, 1.0, 1.5]) gradients = compute_all_parameter_gradients(enc, x) print(f"Gradients shape: {gradients.shape}") Gradients shape: (3,)
compute_effective_dimension
¶
compute_effective_dimension(encoding: BaseEncoding, X: NDArray[floating[Any]], *, regularization: float = 1.0, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane', shots: int | None = None, seed: int | None = None) -> float
Feature-space effective dimension of an encoding's fidelity kernel.
Larger values indicate the encoding spreads data across more effective
feature-space directions (higher capacity). See
:func:kernel_effective_dimension for the definition.
Parameters¶
encoding : BaseEncoding
Encoding under test.
X : ndarray, shape (n_samples, n_features)
Input data.
regularization : float, default=1.0
Ridge parameter of the effective-dimension sum.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector backend.
shots : int or None, default=None
Measure on a finite-shot kernel estimate instead of the exact one.
Shot noise raises the apparent dimension by spreading the eigenvalue
spectrum, so finite-shot values overstate capacity.
seed : int or None, default=None
Seed for the shot sampling. Ignored when shots is None.
compute_entanglement_capability
¶
compute_entanglement_capability(encoding: BaseEncoding, n_samples: int = ..., input_range: tuple[float, float] = ..., seed: int | None = ..., backend: Literal['pennylane', 'qiskit', 'cirq'] = ..., measure: Literal['meyer_wallach', 'scott'] = ..., scott_k: int | None = ..., return_details: Literal[False] = ..., verbose: bool = ..., parallel: ParallelArg = ..., max_workers: int | None = ..., sampling: SamplingMethod = ..., confidence_level: float = ..., n_bootstrap_ci: int = ...) -> float
compute_entanglement_capability(encoding: BaseEncoding, n_samples: int = ..., input_range: tuple[float, float] = ..., seed: int | None = ..., backend: Literal['pennylane', 'qiskit', 'cirq'] = ..., measure: Literal['meyer_wallach', 'scott'] = ..., scott_k: int | None = ..., return_details: Literal[True] = ..., verbose: bool = ..., parallel: ParallelArg = ..., max_workers: int | None = ..., sampling: SamplingMethod = ..., confidence_level: float = ..., n_bootstrap_ci: int = ...) -> EntanglementResult
compute_entanglement_capability(encoding: BaseEncoding, n_samples: int = _DEFAULT_N_SAMPLES, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane', measure: Literal['meyer_wallach', 'scott'] = 'meyer_wallach', scott_k: int | None = None, return_details: bool = False, verbose: bool = False, parallel: ParallelArg = False, max_workers: int | None = None, sampling: SamplingMethod = 'uniform', confidence_level: float = _DEFAULT_CONFIDENCE_LEVEL, n_bootstrap_ci: int = _DEFAULT_N_BOOTSTRAP_CI) -> Union[float, EntanglementResult]
Compute the entanglement capability of a quantum encoding.
Entanglement capability quantifies the average amount of entanglement produced by the encoding across random inputs. Higher values indicate the encoding creates more entangled states on average.
Parameters¶
encoding : BaseEncoding
The encoding instance to analyze. Must implement get_circuit().
n_samples : int, default=1000
Number of random inputs to sample for averaging. Higher values
give more accurate estimates but increase computation time.
Must be at least 10.
input_range : tuple[float, float], default=(0, 2*pi)
Range for sampling random input values. Features are sampled
uniformly from this range.
seed : int, optional
Random seed for reproducibility. If None, uses system entropy.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Backend for circuit simulation.
measure : {"meyer_wallach", "scott"}, default="meyer_wallach"
Entanglement measure to use:
- ``"meyer_wallach"``: Average single-qubit purity deficit.
Faster and most commonly used. Equivalent to Scott measure with k=1.
- ``"scott"``: Scott measure with configurable k-body subsystems.
More detailed but slower. Use ``scott_k`` to specify subsystem size.
int, optional
Subsystem size for Scott measure. Only used when measure="scott".
Must satisfy 1 <= scott_k <= n_qubits - 1.
- If None (default): Automatically selects
min(2, n_qubits - 1). For 2-qubit systems this falls back to k=1 (Meyer-Wallach). - If specified: Uses the given k value, raising an error if invalid.
Ignored when measure="meyer_wallach".
bool, default=False
If True, return full result dictionary with statistics. If False, return only the entanglement capability score.
verbose : bool, default=False If True, log progress during computation. parallel : bool or {'thread', 'process'}, default=False Parallel-dispatch mode for the per-sample simulation + entanglement-measure computation.
- ``False`` (default) — sequential, no executor overhead.
- ``True`` or ``'thread'`` — :class:`ThreadPoolExecutor`.
- ``'process'`` — :class:`ProcessPoolExecutor` with the encoding
pickled once per worker. Workers exchange only float / NumPy
arrays, so process-pool parallelism works with **all** three
backends here (unlike ``BaseEncoding.get_circuits`` where
PennyLane's local-closure qfuncs prevent process-pool use).
Output is numerically identical across all modes for a fixed
``seed`` — the RNG is fully consumed in the main process before
any work is dispatched.
max_workers : int or None, default=None
Maximum number of workers when parallel is enabled.
sampling : {'uniform', 'sobol'}, default='uniform'
Strategy for drawing the random per-sample inputs:
- ``'uniform'`` (default, unchanged): pseudo-random i.i.d.
uniform draws.
- ``'sobol'``: Sobol' low-discrepancy quasi-random sequence,
seeded from the same ``seed`` argument. Typically gives the
same mean accuracy with **30-50% fewer samples**. For best
statistical properties choose ``n_samples`` as a power of
two.
confidence_level : float, default=0.95
Two-sided confidence level for the bootstrap CI reported in
the result dict (only used when return_details=True).
Must lie strictly in (0, 1).
n_bootstrap_ci : int, default=200
Number of bootstrap resamples used to estimate the
entanglement_capability percentile CI (only when
return_details=True). 200 stabilizes the percentile
endpoints to ~1% jitter at negligible cost.
Returns¶
float or EntanglementResult
If return_details=False: Entanglement capability in [0, 1].
If ``return_details=True``: Dictionary containing:
- ``entanglement_capability``: float in [0, 1]
- ``entanglement_samples``: per-sample entanglement values
- ``std_error``: standard error of mean
- ``n_samples``: number of samples used
- ``per_qubit_entanglement``: average entanglement per qubit
- ``measure``: the measure used ("meyer_wallach" or "scott")
- ``scott_k``: the k value used (None for Meyer-Wallach)
Raises¶
InsufficientSamplesError
If n_samples < 10.
SimulationError
If circuit simulation fails.
ValueError
If encoding has < 2 qubits (entanglement requires multiple qubits).
ValidationError
If encoding is invalid or input parameters are malformed.
Warns¶
UserWarning
If n_samples < 100, results may be unreliable.
Examples¶
Basic usage:
from encoding_atlas import IQPEncoding enc = IQPEncoding(n_features=4, reps=2, entanglement="full") ent = compute_entanglement_capability(enc, seed=42) print(f"Entanglement: {ent:.4f}")
Compare different entanglement patterns:
enc_linear = IQPEncoding(n_features=4, reps=2, entanglement="linear") enc_full = IQPEncoding(n_features=4, reps=2, entanglement="full") print(f"Linear: {compute_entanglement_capability(enc_linear, seed=42):.4f}") print(f"Full: {compute_entanglement_capability(enc_full, seed=42):.4f}")
Get detailed statistics:
result = compute_entanglement_capability( ... enc, n_samples=500, seed=42, return_details=True ... ) print(f"Mean: {result['entanglement_capability']:.4f}") print(f"Std Error: {result['std_error']:.4f}") print(f"Per-qubit: {result['per_qubit_entanglement']}")
Notes¶
For non-entangling encodings (e.g., AngleEncoding), the entanglement capability will be exactly 0.0, as all generated states are product states.
The computation time scales linearly with n_samples and exponentially
with the number of qubits (due to statevector simulation).
See Also¶
compute_meyer_wallach : Compute MW measure for a single state. compute_scott_measure : Compute Scott measure for a single state. compute_expressibility : Related metric for Hilbert space coverage.
compute_expressibility
¶
compute_expressibility(encoding: BaseEncoding, n_samples: int = ..., n_bins: int | None = ..., input_range: tuple[float, float] = ..., seed: int | None = ..., backend: Literal['pennylane', 'qiskit', 'cirq'] = ..., return_distributions: Literal[False] = ..., verbose: bool = ..., parallel: ParallelArg = ..., max_workers: int | None = ..., sampling: SamplingMethod = ..., confidence_level: float = ..., n_bootstrap_ci: int = ...) -> float
compute_expressibility(encoding: BaseEncoding, n_samples: int = ..., n_bins: int | None = ..., input_range: tuple[float, float] = ..., seed: int | None = ..., backend: Literal['pennylane', 'qiskit', 'cirq'] = ..., return_distributions: Literal[True] = ..., verbose: bool = ..., parallel: ParallelArg = ..., max_workers: int | None = ..., sampling: SamplingMethod = ..., confidence_level: float = ..., n_bootstrap_ci: int = ...) -> ExpressibilityResult
compute_expressibility(encoding: BaseEncoding, n_samples: int = _DEFAULT_N_SAMPLES, n_bins: int | None = None, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane', return_distributions: bool = False, verbose: bool = False, parallel: ParallelArg = False, max_workers: int | None = None, sampling: SamplingMethod = 'uniform', confidence_level: float = _DEFAULT_CONFIDENCE_LEVEL, n_bootstrap_ci: int = _DEFAULT_N_BOOTSTRAP_CI) -> float | ExpressibilityResult
Compute the expressibility of a quantum encoding.
Expressibility quantifies how well the encoding can explore the Hilbert space compared to Haar-random states. Higher expressibility (closer to 1.0) indicates the encoding can generate a more diverse set of quantum states, which is generally desirable for variational quantum algorithms.
The computation works by:
- Sampling random input pairs and computing their quantum states
- Computing fidelities between state pairs
- Building a histogram of fidelities
- Comparing to the theoretical Haar-random distribution via KL divergence
- Normalizing to a [0, 1] score
Parameters¶
encoding : BaseEncoding
The encoding instance to analyze. Must have n_features and
n_qubits attributes and implement get_circuit().
n_samples : int, default=5000
Number of random input pairs to sample. Higher values give more
accurate results but increase computation time. A minimum of 1000
samples is recommended for reliable results.
n_bins : int or None, default=None
Number of bins for the fidelity histogram. If None (default),
automatically set to min(75, n_samples) so that small sample
counts don't require manually tuning the bin count. If provided
explicitly, must satisfy 10 ≤ n_bins ≤ n_samples.
Rule of thumb: n_bins ≈ √n_samples.
input_range : tuple[float, float], default=(0, 2π)
Range for sampling random input values. The default covers the
full rotation range for typical quantum gates.
seed : int, optional
Random seed for reproducibility. If None, results will vary
between runs. Always specify a seed for reproducible analysis.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Backend to use for circuit simulation.
- ``"pennylane"``: Uses PennyLane's default.qubit simulator (recommended)
- ``"qiskit"``: Uses Qiskit's Statevector class
- ``"cirq"``: Uses Cirq's Simulator for statevector simulation
return_distributions : bool, default=False If True, return full result dictionary including fidelity distributions, bin edges, and statistics. If False, return only the expressibility score (float). verbose : bool, default=False If True, log progress information during computation. parallel : bool or {'thread', 'process'}, default=False Parallel-dispatch mode for the per-sample fidelity computation. Each sample requires two independent statevector simulations, making the loop embarrassingly parallel.
- ``False`` (default) — sequential, no executor overhead.
- ``True`` or ``'thread'`` — :class:`ThreadPoolExecutor`. Useful
when the backend simulator releases the GIL during numerical
work (NumPy operations typically do).
- ``'process'`` — :class:`ProcessPoolExecutor` for true CPU
parallelism. The encoding is pickled once per worker. Best
for large ``n_samples`` and CPU-bound backends. Has
process-startup overhead, so only worthwhile for batches of
roughly 100+ samples. Workers exchange only NumPy arrays /
floats, so process-pool parallelism works with **all** three
backends here (unlike ``BaseEncoding.get_circuits`` where
PennyLane's local-closure qfuncs prevent process-pool use).
Output is numerically identical across all modes for a fixed
``seed``: the RNG is fully consumed in the main process before
any work is dispatched to workers.
max_workers : int or None, default=None
Maximum number of workers when parallel is enabled.
None defers to the executor's default (typically based on
CPU count).
sampling : {'uniform', 'sobol'}, default='uniform'
Strategy for drawing the random input pairs (X1, X2):
- ``'uniform'`` (default, unchanged behavior): pseudo-random
i.i.d. samples via :meth:`numpy.random.Generator.uniform`.
- ``'sobol'``: Sobol' low-discrepancy quasi-random sequence
via :class:`scipy.stats.qmc.Sobol`. Covers the hypercube
much more evenly than i.i.d. uniform draws and typically
reaches the same KL accuracy with **30-50% fewer samples**.
For best statistical properties round ``n_samples`` up to
the nearest power of two.
Sobol is seeded from the same ``seed`` argument so a given
``(seed, sampling)`` pair is fully reproducible.
confidence_level : float, default=0.95
Two-sided confidence level for the bootstrap CIs reported in
the result dict (only used when return_distributions=True).
Must lie strictly in (0, 1). Common choices: 0.90, 0.95,
0.99.
n_bootstrap_ci : int, default=200
Number of bootstrap resamples used to estimate the
expressibility and mean_fidelity percentile CIs
(only when return_distributions=True). 200 stabilizes
the percentile endpoints to ~1% jitter; raise to ~1000 for
publication-grade precision.
Returns¶
float or ExpressibilityResult
If return_distributions=False: Expressibility score in [0, 1].
Higher values indicate more expressive encodings.
If ``return_distributions=True``: Dictionary containing:
- **expressibility**: float in [0, 1], higher = more expressive
- **kl_divergence**: raw KL divergence value
- **fidelity_distribution**: sampled fidelity histogram
- **haar_distribution**: theoretical Haar distribution
- **bin_edges**: histogram bin edges
- **n_samples**: number of samples used
- **n_bins**: number of histogram bins
- **convergence_estimate**: bootstrap standard error estimate
- **mean_fidelity**: mean of sampled fidelities
- **std_fidelity**: standard deviation of sampled fidelities
Raises¶
InsufficientSamplesError
If n_samples < 10 (too few for meaningful statistics).
SimulationError
If circuit simulation fails for the given backend.
AnalysisError
If encoding is invalid or computation fails.
ValueError
If explicit n_bins < 10 or n_bins > n_samples, or if
input_range[0] >= input_range[1].
Warns¶
UserWarning
If n_samples < 100 (results may be unreliable).
UserWarning
If encoding has > 10 qubits (computation may be slow).
Examples¶
Basic usage with default parameters:
from encoding_atlas import AngleEncoding enc = AngleEncoding(n_features=4) expr = compute_expressibility(enc, seed=42) print(f"Expressibility: {expr:.4f}") Expressibility: ...
Get full result with distributions:
result = compute_expressibility(enc, return_distributions=True, seed=42) print(f"KL Divergence: {result['kl_divergence']:.4f}") print(f"Samples used: {result['n_samples']}")
Compare two encodings:
from encoding_atlas import IQPEncoding enc1 = AngleEncoding(n_features=4) # Non-entangling enc2 = IQPEncoding(n_features=4, reps=2) # Entangling expr1 = compute_expressibility(enc1, n_samples=1000, seed=42) expr2 = compute_expressibility(enc2, n_samples=1000, seed=42) print(f"AngleEncoding: {expr1:.4f}") print(f"IQPEncoding: {expr2:.4f}")
IQP typically shows higher expressibility due to entanglement¶
Notes¶
Computational Complexity
The complexity is O(n_samples × simulation_cost), where simulation_cost scales as O(2^n_qubits). For encodings with more than 10 qubits, consider reducing n_samples or using approximate methods.
Interpretation
- Expressibility ≈ 0: Encoding produces states in a narrow region of Hilbert space (e.g., product states only)
- Expressibility ≈ 1: Encoding produces states distributed like Haar-random states (explores full Hilbert space)
Normalization Formula
The expressibility score is normalized from KL divergence using::
expr = 1.0 - min(1.0, kl_divergence / MAX_KL)
This maps KL divergence to [0, 1] where higher values indicate distributions closer to Haar-random (more expressive).
Statistical Reliability
The convergence_estimate in the result dictionary provides a
bootstrap estimate of the standard error. Values < 0.05 generally
indicate reliable results.
See Also¶
compute_fidelity_distribution : Compute fidelity distribution only. compute_haar_distribution : Compute theoretical Haar distribution. compute_entanglement_capability : Related metric for entanglement.
References¶
.. [1] Sim, S., Johnson, P. D., & Aspuru-Guzik, A. (2019). "Expressibility and entangling capability of parameterized quantum circuits for hybrid quantum-classical algorithms." Advanced Quantum Technologies, 2(12), 1900070.
compute_fidelity
¶
Compute the fidelity between two pure quantum states.
The fidelity between two pure states is defined as:
F(|ψ⟩, |φ⟩) = |⟨ψ|φ⟩|²
This measures how "close" two quantum states are, with F = 1 for identical states (up to global phase) and F = 0 for orthogonal states.
Parameters¶
state1 : StatevectorType
First statevector.
state2 : StatevectorType
Second statevector. Must have the same dimension as state1.
Returns¶
float
Fidelity value in the range [0, 1].
Raises¶
ValueError If states have different dimensions. ValidationError If states are invalid.
Examples¶
Identical states have fidelity 1:
import numpy as np state = np.array([1, 0, 0, 0], dtype=complex) fidelity = compute_fidelity(state, state) print(f"Fidelity: {fidelity}") Fidelity: 1.0
Orthogonal states have fidelity 0:
state1 = np.array([1, 0], dtype=complex) # |0⟩ state2 = np.array([0, 1], dtype=complex) # |1⟩ fidelity = compute_fidelity(state1, state2) print(f"Fidelity: {fidelity}") Fidelity: 0.0
States differing by a global phase have fidelity 1:
state1 = np.array([1, 0], dtype=complex) state2 = np.array([1j, 0], dtype=complex) # |0⟩ with phase fidelity = compute_fidelity(state1, state2) print(f"Fidelity: {fidelity:.6f}") Fidelity: 1.000000
Notes¶
The implementation uses :func:numpy.vdot which conjugates the first
argument, computing ⟨state1|state2⟩ = Σᵢ state1ᵢ* · state2ᵢ.
For mixed states (density matrices), use the more general formula: F(ρ, σ) = (Tr√(√ρ σ √ρ))²
See Also¶
compute_purity : Compute purity of a density matrix.
compute_fidelity_distribution
¶
compute_fidelity_distribution(encoding: BaseEncoding, n_samples: int = _DEFAULT_N_SAMPLES, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane', parallel: ParallelArg = False, max_workers: int | None = None, sampling: SamplingMethod = 'uniform') -> NDArray[floating[Any]]
Compute the fidelity distribution for an encoding.
Samples random input pairs and computes the fidelity between their corresponding quantum states. This is useful for analyzing the encoding's behavior without computing the full expressibility.
Parameters¶
encoding : BaseEncoding The encoding to analyze. n_samples : int, default=5000 Number of random input pairs to sample. input_range : tuple[float, float], default=(0, 2π) Range for random input sampling. seed : int, optional Random seed for reproducibility. backend : {"pennylane", "qiskit", "cirq"}, default="pennylane" Backend for circuit simulation.
- ``"pennylane"``: Uses PennyLane's default.qubit simulator (recommended)
- ``"qiskit"``: Uses Qiskit's Statevector class
- ``"cirq"``: Uses Cirq's Simulator for statevector simulation
parallel : bool or {'thread', 'process'}, default=False
Parallel-dispatch mode for the per-sample fidelity computation.
See :func:compute_expressibility for the full discussion.
max_workers : int or None, default=None
Maximum number of workers when parallel is enabled.
sampling : {'uniform', 'sobol'}, default='uniform'
Strategy for drawing the random input pairs. See
:func:compute_expressibility for the full discussion.
Returns¶
NDArray[np.floating]
Array of fidelity values, shape (n_samples,), each in [0, 1].
Raises¶
InsufficientSamplesError
If n_samples < 10.
SimulationError
If circuit simulation fails.
AnalysisError
If encoding is invalid.
Examples¶
from encoding_atlas import IQPEncoding enc = IQPEncoding(n_features=4, reps=1) fidelities = compute_fidelity_distribution(enc, n_samples=1000, seed=42) print(f"Mean fidelity: {fidelities.mean():.4f}") print(f"Std fidelity: {fidelities.std():.4f}")
Notes¶
The fidelity F = |⟨ψ₁|ψ₂⟩|² measures the overlap between two quantum states. F = 1 means identical states (up to global phase), F = 0 means orthogonal states.
For Haar-random states in high dimensions, most pairs have F ≈ 0 because random high-dimensional vectors are typically nearly orthogonal.
See Also¶
compute_expressibility : Full expressibility computation. compute_haar_distribution : Theoretical Haar distribution.
compute_fidelity_kernel
¶
compute_fidelity_kernel(encoding: BaseEncoding, X: NDArray[floating[Any]], *, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane', shots: int | None = None, seed: int | None = None) -> NDArray[floating[Any]]
Compute the fidelity (overlap) kernel matrix for X.
K[i, j] = |<phi(x_i)|phi(x_j)>|^2 where |phi(x)> = U(x)|0> is the
encoded state. With the default shots=None the matrix is exact:
symmetric, PSD, unit diagonal, entries in [0, 1].
Parameters¶
encoding : BaseEncoding
Encoding used for state preparation. encoding.n_features must equal
X.shape[1].
X : ndarray, shape (n_samples, n_features)
Input data.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector simulation backend.
shots : int or None, default=None
When given, return the finite-shot compute-uncompute estimate from
shots measurements per entry instead of the exact kernel (see
:func:sample_shot_kernel). The result stays symmetric with a unit
diagonal but is no longer guaranteed PSD.
seed : int or None, default=None
Seed for the shot sampling. Ignored when shots is None.
Returns¶
ndarray, shape (n_samples, n_samples) The fidelity kernel matrix.
Raises¶
ValueError
If shots is given but is not a positive integer.
compute_geometric_difference
¶
compute_geometric_difference(encoding: BaseEncoding, X: NDArray[floating[Any]], *, classical_kernel: Literal['rbf', 'linear'] = 'rbf', gamma: float | None = None, regularization: float = 1e-06, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane', shots: int | None = None, seed: int | None = None) -> float
Geometric difference g(K_classical || K_quantum) for an encoding.
A large value means the encoding's fidelity kernel is geometrically distinct from the classical reference kernel — a necessary (not sufficient) condition for a quantum advantage over that classical kernel.
Parameters¶
encoding : BaseEncoding
Encoding under test.
X : ndarray, shape (n_samples, n_features)
Input data.
classical_kernel : {"rbf", "linear"}, default="rbf"
Classical reference kernel.
gamma : float or None, default=None
RBF bandwidth (None -> 1 / n_features, scikit-learn default).
regularization : float, default=1e-6
Ridge for the classical-kernel inversion.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector backend.
shots : int or None, default=None
Measure against a finite-shot kernel estimate instead of the exact
one. Shot noise inflates g — noise is a direction no classical
kernel reproduces — so a large value under finite shots is not by
itself evidence of an advantage.
seed : int or None, default=None
Seed for the shot sampling. Ignored when shots is None.
compute_gradient_variance
¶
compute_gradient_variance(encoding: BaseEncoding, n_samples: int = _DEFAULT_N_SAMPLES, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, observable: Literal['computational', 'pauli_z', 'global_z'] = 'computational', seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> float
Compute the average variance of parameter gradients.
This is the core metric used for trainability estimation. It measures how much the gradients vary across random parameter initializations. Low variance indicates a flat optimization landscape (barren plateau).
This function is a convenience wrapper that calls :func:estimate_trainability
and extracts only the gradient variance.
Parameters¶
encoding : BaseEncoding The encoding to analyze. n_samples : int, default=500 Number of random parameter samples. input_range : tuple[float, float], default=(0, 2π) Range for sampling random parameters. observable : {"computational", "pauli_z", "global_z"}, default="computational" Observable for gradient computation. seed : int, optional Random seed for reproducibility. backend : {"pennylane", "qiskit", "cirq"}, default="pennylane" Backend for gradient computation.
Returns¶
float Average gradient variance across parameters. Values close to zero indicate a barren plateau.
Examples¶
from encoding_atlas import AngleEncoding enc = AngleEncoding(n_features=4) variance = compute_gradient_variance(enc, n_samples=100, seed=42) print(f"Gradient variance: {variance:.2e}")
See Also¶
estimate_trainability : Full trainability analysis with risk assessment. detect_barren_plateau : Categorize variance into risk levels.
compute_haar_distribution
¶
compute_haar_distribution(n_qubits: int, fidelity_values: NDArray[floating[Any]]) -> NDArray[floating[Any]]
Compute the Haar-random fidelity distribution.
For a d-dimensional Hilbert space (d = 2^n_qubits), the probability density of fidelities between Haar-random states is:
P_Haar(F) = (d - 1)(1 - F)^(d - 2)
This is derived from the fact that Haar-random states are uniformly distributed on the complex unit sphere.
Parameters¶
n_qubits : int Number of qubits (determines Hilbert space dimension d = 2^n_qubits). fidelity_values : NDArray[np.floating] Array of fidelity values at which to evaluate the distribution. Should be in [0, 1].
Returns¶
NDArray[np.floating] Probability density values at each fidelity point, normalized to sum to 1 (assuming fidelity_values are evenly spaced bin centers).
Raises¶
ValueError
If n_qubits < 1.
Examples¶
import numpy as np fidelities = np.linspace(0, 1, 100) P_haar = compute_haar_distribution(n_qubits=4, fidelity_values=fidelities) print(f"P_Haar shape: {P_haar.shape}") print(f"P_Haar sum: {P_haar.sum():.4f}") # Should be close to 1
Notes¶
Physical Interpretation
The Haar distribution represents what we would observe if we picked two states uniformly at random from the Hilbert space. For large d:
- Most pairs have very low fidelity (nearly orthogonal)
- High fidelity pairs are exponentially rare
- The distribution is concentrated near F = 0
Numerical Considerations
For large d (many qubits), the term (1-F)^(d-2) can cause underflow. We use log-space computation for d > 100 to maintain numerical stability.
Single Qubit Special Case
For d = 2 (single qubit), P_Haar(F) = 1 (uniform distribution), because the Bloch sphere has special geometry where all fidelities are equally likely.
References¶
.. [1] Życzkowski, K., & Sommers, H.-J. (2001). "Induced measures in the space of mixed quantum states." Journal of Physics A, 34(35), 7111.
compute_kernel_concentration
¶
compute_kernel_concentration(encoding: BaseEncoding, X: NDArray[floating[Any]] | None = None, *, n_samples: int = _DEFAULT_N_SAMPLES, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, sampling: SamplingMethod = 'uniform', threshold: float = CONCENTRATION_THRESHOLD, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> ConcentrationResult
Measure how concentrated an encoding's fidelity kernel is.
Builds the fidelity kernel on X (or on random inputs when X is not
supplied) and reduces it to the concentration order parameter, the spread
of its off-diagonal entries, and the shot budget that spread implies.
A concentration_ratio near 1 means the kernel's off-diagonal variance
has collapsed to the Haar floor: every pair of distinct inputs is equally
dissimilar, the matrix is the identity up to sampling noise, and a kernel
method built on it cannot generalize regardless of shot budget or noise
level. Values well above 1 mean the encoding still separates inputs.
Parameters¶
encoding : BaseEncoding
Encoding to analyse.
X : ndarray of shape (n_samples, n_features), optional
Inputs to build the kernel from. When None (default), inputs are
drawn from input_range, making this a data-free axis comparable
across encodings — the same convention as expressibility and
entanglement capability.
n_samples : int, default=40
Number of random inputs when X is not supplied. Must be >= 2.
Ignored when X is given.
input_range : (float, float), default=(0, 2*pi)
Range for randomly sampled inputs. Concentration depends on the input
distribution as well as the circuit; narrowing this range (as feature
scaling does) generally reduces it. Ignored when X is given.
sampling : {"uniform", "sobol"}, default="uniform"
Random-input strategy. Ignored when X is given.
threshold : float, default=2.0
concentration_ratio below which is_concentrated is set.
seed : int or None, default=None
Seed for input sampling. Ignored when X is given.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector simulation backend.
Returns¶
ConcentrationResult The measured concentration statistics.
Raises¶
ValueError
If n_samples, input_range, threshold or sampling is
invalid, or if X is not a 2D array with at least two rows.
Warns¶
UserWarning If the encoding is wide enough that the statevector batch becomes memory-hungry (more than 14 qubits).
Examples¶
An entangling feature map sits at the Haar floor; a non-entangling one does not:
from encoding_atlas import AngleEncoding, IQPEncoding angle = compute_kernel_concentration( ... AngleEncoding(n_features=6), n_samples=30, seed=0 ... ) iqp = compute_kernel_concentration( ... IQPEncoding(n_features=6, reps=2), n_samples=30, seed=0 ... ) angle.concentration_ratio > iqp.concentration_ratio True
See Also¶
estimate_concentration_scaling : The same measurement across widths. encoding_atlas.analysis.generalization.compute_fidelity_kernel : The kernel.
compute_kernel_target_alignment
¶
compute_kernel_target_alignment(encoding: BaseEncoding, X: NDArray[floating[Any]], y: NDArray[integer[Any] | floating[Any]], *, centered: bool = True, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane', shots: int | None = None, seed: int | None = None) -> float
Kernel-target alignment of an encoding's fidelity kernel on (X, y).
Higher alignment predicts stronger quantum-kernel classification accuracy. Uses the centred variant by default.
Parameters¶
encoding : BaseEncoding
Encoding under test.
X : ndarray, shape (n_samples, n_features)
Input data.
y : ndarray, shape (n_samples,)
Binary labels.
centered : bool, default=True
Use the centred alignment (Cortes et al., 2012).
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector backend.
shots : int or None, default=None
Measure the alignment on a finite-shot kernel estimate instead of the
exact one. Alignment aggregates over all n(n-1)/2 entries, so it
tolerates shot noise far better than the kernel matrix itself does.
seed : int or None, default=None
Seed for the shot sampling. Ignored when shots is None.
compute_linear_entropy
¶
Compute the linear entropy of a density matrix.
The linear entropy is a measure of mixedness defined as:
S_L(ρ) = 1 - Tr(ρ²) = 1 - γ(ρ)
where γ(ρ) is the purity.
Properties
- For a pure state: S_L = 0
- For a maximally mixed state of dimension d: S_L = 1 - 1/d
- In general: 0 ≤ S_L ≤ 1 - 1/d
Parameters¶
density_matrix : DensityMatrixType Density matrix (must be square).
Returns¶
float
Linear entropy value in the range [0, 1 - 1/d].
Examples¶
Pure state has zero linear entropy:
import numpy as np rho_pure = np.array([[1, 0], [0, 0]], dtype=complex) entropy = compute_linear_entropy(rho_pure) print(f"Linear entropy: {entropy}") Linear entropy: 0.0
Maximally mixed state has maximum linear entropy:
rho_mixed = np.array([[0.5, 0], [0, 0.5]], dtype=complex) entropy = compute_linear_entropy(rho_mixed) print(f"Linear entropy: {entropy}") Linear entropy: 0.5
See Also¶
compute_purity : Compute purity (1 - linear entropy). compute_von_neumann_entropy : Compute von Neumann entropy.
compute_meyer_wallach
¶
Compute the Meyer-Wallach entanglement measure for a pure state.
The Meyer-Wallach measure Q(|psi>) quantifies global entanglement as the average linear entropy across all single-qubit reduced states:
Q(|psi>) = (2/n) * sum_i (1 - Tr(rho_i^2))
where rho_i is the reduced density matrix of qubit i.
Parameters¶
statevector : NDArray[np.complexfloating]
Pure state vector, shape (2^n_qubits,).
n_qubits : int
Number of qubits in the system.
Returns¶
float Meyer-Wallach measure in [0, 1].
- 0 = completely separable (product state)
- 1 = maximally entangled
Raises¶
ValueError
If statevector length doesn't match 2^n_qubits.
ValidationError
If statevector is invalid (NaN, zero norm, etc.).
Examples¶
GHZ state: (|000> + |111>) / sqrt(2) has MW = 1:
import numpy as np ghz = np.zeros(8, dtype=complex) ghz[0] = ghz[7] = 1.0 / np.sqrt(2) mw = compute_meyer_wallach(ghz, n_qubits=3) print(f"GHZ Meyer-Wallach: {mw:.4f}") GHZ Meyer-Wallach: 1.0000
Product state |000> has MW = 0:
product = np.zeros(8, dtype=complex) product[0] = 1.0 mw = compute_meyer_wallach(product, n_qubits=3) print(f"Product Meyer-Wallach: {mw:.4f}") Product Meyer-Wallach: 0.0000
Bell state (|00> + |11>) / sqrt(2) has MW = 1:
bell = np.zeros(4, dtype=complex) bell[0] = bell[3] = 1.0 / np.sqrt(2) mw = compute_meyer_wallach(bell, n_qubits=2) print(f"Bell Meyer-Wallach: {mw:.4f}") Bell Meyer-Wallach: 1.0000
Notes¶
The Meyer-Wallach measure is invariant under local unitary operations. It reaches its maximum value of 1 for maximally entangled states like the GHZ state (|00...0> + |11...1>) / sqrt(2).
See Also¶
compute_meyer_wallach_with_breakdown : Also returns per-qubit contributions. compute_scott_measure : Generalization to k-body reduced states.
compute_meyer_wallach_with_breakdown
¶
compute_meyer_wallach_with_breakdown(statevector: StatevectorType, n_qubits: int) -> tuple[float, FloatArray]
Compute Meyer-Wallach measure with per-qubit breakdown.
This function returns both the total Meyer-Wallach measure and the contribution from each qubit, which is useful for understanding which qubits are most entangled.
Parameters¶
statevector : NDArray[np.complexfloating]
Pure state vector, shape (2^n_qubits,).
n_qubits : int
Number of qubits in the system.
Returns¶
mw_value : float
Total Meyer-Wallach measure in [0, 1].
per_qubit_entropy : NDArray[np.floating]
Linear entropy (1 - purity) for each qubit's reduced state.
Shape: (n_qubits,).
Raises¶
ValueError
If statevector length doesn't match 2^n_qubits, or if
n_qubits < 1.
ValidationError
If statevector is invalid.
Examples¶
import numpy as np
W state: (|001> + |010> + |100>) / sqrt(3)¶
w_state = np.zeros(8, dtype=complex) w_state[1] = w_state[2] = w_state[4] = 1.0 / np.sqrt(3) mw, per_qubit = compute_meyer_wallach_with_breakdown(w_state, n_qubits=3) print(f"Total MW: {mw:.4f}") print(f"Per-qubit entropies: {per_qubit}")
Notes¶
The per-qubit breakdown is useful for:
- Identifying which qubits contribute most to entanglement
- Detecting asymmetric entanglement structures
- Debugging encoding circuits
For the GHZ state, all qubits have equal entropy (= 0.5). For the W state, all qubits also have equal entropy (= ⅔).
compute_noise_resilience
¶
compute_noise_resilience(encoding: BaseEncoding, *, noise_level: str = 'medium', noise_params: dict[str, float] | None = None, n_samples: int = 25, seed: int | None = None, feature_range: tuple[float, float] = _DEFAULT_RANGE) -> NoiseResilienceResult
Measure an encoding's resilience to depolarizing gate noise.
For n_samples random inputs, simulates the ideal state |phi(x)> and
the noisy density matrix rho(x) and averages the fidelity
F(x) = <phi(x)| rho(x) |phi(x)>. Higher retained fidelity means the
encoding degrades less under noise.
Parameters¶
encoding : BaseEncoding
Encoding to analyze (at most 12 qubits; density-matrix memory is
O(4^n)).
noise_level : {"low", "medium", "high"}, default="medium"
Preset depolarizing error rates (ignored if noise_params is given).
noise_params : dict or None, default=None
Explicit {"single_qubit": p1, "two_qubit": p2} error probabilities,
overriding noise_level.
n_samples : int, default=25
Number of random inputs to average over. Depolarizing fidelity is
nearly input-independent, so modest values are accurate.
seed : int or None, default=None
Seed for input sampling (reproducibility).
feature_range : tuple, default=(0, 2*pi)
Range for uniformly sampled input features.
Returns¶
NoiseResilienceResult Retained fidelity, fidelity decay, and per-sample statistics.
Raises¶
ValueError
If n_samples < 1, the encoding exceeds the qubit cap, or the noise
specification is invalid.
compute_parameter_gradient
¶
compute_parameter_gradient(encoding: BaseEncoding, x: NDArray[floating[Any]], param_index: int, observable: Literal['computational', 'global_z', 'local_z', 'pauli_z'] = 'computational', backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> float
Compute gradient of expectation value with respect to one parameter.
Uses the parameter-shift rule for quantum gradients:
∂⟨O⟩/∂θ = (⟨O⟩_{θ+π/2} - ⟨O⟩_{θ-π/2}) / 2
This is exact for gates of the form U(θ) = exp(-iθG/2) where G² = I, which includes all standard rotation gates (RX, RY, RZ).
Parameters¶
encoding : BaseEncoding The encoding instance. x : NDArray[np.floating] Input data vector. param_index : int Index of the parameter to differentiate with respect to. observable : {"computational", "global_z", "local_z", "pauli_z"}, default="computational" Observable to measure:
- ``"computational"``: Probability of |0...0⟩ state, i.e., ⟨0...0|ρ|0...0⟩
- ``"global_z"``: Expectation value of Z⊗Z⊗...⊗Z (tensor product of Z on
all qubits). Eigenvalue is (-1)^(number of 1s in bitstring).
- ``"local_z"``: Expectation value of Z on first qubit only, ⟨Z₀⟩.
Computed as P(0 on qubit 0) - P(1 on qubit 0) = 2·P(0) - 1.
- ``"pauli_z"``: **Deprecated alias for "global_z"**. For clarity,
use ``"global_z"`` for the global Z-string observable or ``"local_z"``
for the single-qubit Z observable.
{"pennylane", "qiskit", "cirq"}, default="pennylane"
Simulation backend.
Returns¶
float Gradient of the expectation value.
Raises¶
ValidationError If param_index is out of range. ValueError If observable is not a recognized option.
Examples¶
from encoding_atlas import AngleEncoding import numpy as np
enc = AngleEncoding(n_features=2) x = np.array([0.5, 1.0]) grad = compute_parameter_gradient(enc, x, param_index=0) print(f"Gradient: {grad:.6f}")
Notes¶
The parameter-shift rule requires two circuit evaluations per gradient
component. For full gradient vectors, use :func:compute_all_parameter_gradients.
Observable Semantics
The distinction between "global_z" and "local_z" is important:
"global_z"measures the parity of all qubits simultaneously. The expectation value is ⟨Z⊗Z⊗...⊗Z⟩ where each Z acts on a different qubit. This captures global correlations in the encoded state."local_z"measures only the first qubit (MSB in computational basis ordering), ignoring the state of other qubits. This is useful for local sensitivity analysis.
See Also¶
compute_all_parameter_gradients : Compute gradients for all parameters.
compute_purity
¶
Compute the purity of a density matrix.
The purity of a quantum state ρ is defined as:
γ(ρ) = Tr(ρ²)
Properties
- For a pure state: γ = 1
- For a maximally mixed state of dimension d: γ = 1/d
- In general: 1/d ≤ γ ≤ 1
Parameters¶
density_matrix : DensityMatrixType Density matrix (must be square). Can be any valid quantum state, including mixed states.
Returns¶
float
Purity value in the range [1/d, 1] where d is the dimension.
Raises¶
ValidationError If the density matrix is not square or contains invalid values.
Examples¶
Pure state |0⟩ has purity 1:
import numpy as np rho_pure = np.array([[1, 0], [0, 0]], dtype=complex) purity = compute_purity(rho_pure) print(f"Purity: {purity}") Purity: 1.0
Maximally mixed state has purity 1/d:
rho_mixed = np.array([[0.5, 0], [0, 0.5]], dtype=complex) purity = compute_purity(rho_mixed) print(f"Purity: {purity}") Purity: 0.5
Notes¶
The purity is related to the linear entropy by: S_L(ρ) = 1 - γ(ρ)
And to the participation ratio by
PR = 1/γ(ρ)
which measures the "effective dimension" of the state.
See Also¶
compute_linear_entropy : Compute linear entropy from purity. compute_fidelity : Compute fidelity between pure states.
compute_scott_measure
¶
Compute the Scott entanglement measure.
The Scott measure generalizes Meyer-Wallach to k-body reduced states, averaging the linear entropy over all k-qubit subsystems.
Parameters¶
statevector : NDArray[np.complexfloating]
Pure state vector.
n_qubits : int
Number of qubits.
k : int, default=2
Size of subsystems to consider. Must satisfy
1 <= k <= n_qubits - 1.
- k=1: Equivalent to Meyer-Wallach measure.
- k=2: Pairwise entanglement (default).
- Higher k: Captures higher-order entanglement structure.
The measure is well-defined for any proper subsystem size.
Note that ``Q_k`` and ``Q_{n-k}`` probe complementary subsystems
and therefore carry related (but not identical) information.
Returns¶
float Scott measure in [0, 1].
Raises¶
ValueError
If k < 1, k >= n_qubits, or other inputs are invalid.
ValidationError
If statevector is invalid.
Examples¶
import numpy as np
GHZ state on 3 qubits¶
ghz = np.zeros(8, dtype=complex) ghz[0] = ghz[7] = 1.0 / np.sqrt(2) scott_1 = compute_scott_measure(ghz, n_qubits=3, k=1) scott_2 = compute_scott_measure(ghz, n_qubits=3, k=2) print(f"Scott k=1: {scott_1:.4f}") # Same as Meyer-Wallach Scott k=1: 1.0000 print(f"Scott k=2: {scott_2:.4f}") Scott k=2: 0.6667
Notes¶
The Scott measure averages the normalized linear entropy of all
:math:inom{n}{k} reduced density matrices of size k. The
mathematical definition is valid for any 1 <= k <= n - 1, where
n is the number of qubits [1]_.
Computational cost scales with :math:inom{n}{k}, the number of
k-qubit subsystems. For large n, choosing k close to 1 or
close to n - 1 is cheaper than choosing k near n / 2.
References¶
.. [1] Scott, A. J. (2004). "Multipartite entanglement, quantum-error-correcting codes, and entangling power of quantum evolutions." Physical Review A, 69(5), 052330.
compute_von_neumann_entropy
¶
Compute the von Neumann entropy of a density matrix.
The von Neumann entropy is the quantum analog of Shannon entropy:
S(ρ) = -Tr(ρ log₂ ρ) = -Σᵢ λᵢ log₂(λᵢ)
where λᵢ are the eigenvalues of ρ.
Properties
- For a pure state: S = 0
- For a maximally mixed state of dimension d: S = log₂(d)
- In general: 0 ≤ S ≤ log₂(d)
Parameters¶
density_matrix : DensityMatrixType Density matrix (must be square).
Returns¶
float Von Neumann entropy in bits (log base 2).
Examples¶
Pure state has zero entropy:
import numpy as np rho_pure = np.array([[1, 0], [0, 0]], dtype=complex) entropy = compute_von_neumann_entropy(rho_pure) print(f"von Neumann entropy: {entropy:.6f}") von Neumann entropy: 0.000000
Maximally mixed state has maximum entropy:
rho_mixed = np.array([[0.5, 0], [0, 0.5]], dtype=complex) entropy = compute_von_neumann_entropy(rho_mixed) print(f"von Neumann entropy: {entropy:.6f}") von Neumann entropy: 1.000000
Notes¶
The computation uses the eigenvalue decomposition of the density matrix. Eigenvalues that are zero or negative (due to numerical errors) are filtered out to avoid log(0) issues.
See Also¶
compute_linear_entropy : Compute linear entropy (simpler approximation). compute_purity : Compute state purity.
count_resources
¶
count_resources(encoding: BaseEncoding, x: NDArray[floating[Any]] | None = None, *, detailed: Literal[False] = False) -> ResourceCountSummary
count_resources(encoding: BaseEncoding, x: NDArray[floating[Any]] | None = None, *, detailed: Literal[True]) -> DetailedGateBreakdown
count_resources(encoding: BaseEncoding, x: NDArray[floating[Any]] | None = None, *, detailed: bool = False) -> ResourceCountSummary | DetailedGateBreakdown
Count the computational resources required by an encoding.
Analyzes the encoding circuit structure to determine gate counts, circuit depth, and other resource metrics. This analysis is performed analytically (without simulation) for most encodings.
Parameters¶
encoding : BaseEncoding
The encoding instance to analyze. Must be a valid encoding that
inherits from :class:BaseEncoding.
x : NDArray[np.floating], optional
Input data vector. Required only for data-dependent encodings
(e.g., BasisEncoding) where gate count depends on input values.
For most encodings, this can be omitted.
detailed : bool, default=False
If True, return detailed gate-by-gate breakdown.
If False, return summary with totals and derived metrics.
Returns¶
ResourceCountSummary or DetailedGateBreakdown
If detailed=False (default): Dictionary with summary metrics including:
- ``n_qubits``: Number of qubits
- ``depth``: Circuit depth
- ``gate_count``: Total gates
- ``single_qubit_gates``: Single-qubit gate count
- ``two_qubit_gates``: Two-qubit gate count
- ``parameter_count``: Number of parameterized gates
- ``two_qubit_ratio``: Fraction of gates that are two-qubit
- ``gates_per_qubit``: Average gates per qubit
- ``encoding_name``: Name of the encoding class
- ``is_data_dependent``: Whether counts depend on input data
If ``detailed=True``: Dictionary with per-gate-type counts including:
- ``rx``, ``ry``, ``rz``: Rotation gate counts
- ``h``: Hadamard count
- ``x``, ``y``, ``z``: Pauli gate counts
- ``cnot``, ``cz``, ``swap``: Two-qubit gate counts
- ``total_single_qubit``, ``total_two_qubit``, ``total``
- ``encoding_name``: Name of the encoding class
Raises¶
AnalysisError If encoding is not a valid BaseEncoding instance or if resource counting fails. ValidationError If x is required but not provided (for data-dependent encodings), or if x has invalid shape or values.
Examples¶
Basic resource counting:
from encoding_atlas import AngleEncoding, IQPEncoding from encoding_atlas.analysis import count_resources
enc = AngleEncoding(n_features=4) res = count_resources(enc) print(f"Qubits: {res['n_qubits']}, Depth: {res['depth']}") Qubits: 4, Depth: 1
Comparing two encodings:
enc_simple = AngleEncoding(n_features=4) enc_complex = IQPEncoding(n_features=4, reps=2)
res_simple = count_resources(enc_simple) res_complex = count_resources(enc_complex)
print(f"Simple: {res_simple['gate_count']} gates") print(f"Complex: {res_complex['gate_count']} gates")
Detailed breakdown:
breakdown = count_resources(enc_complex, detailed=True) print(f"CNOT gates: {breakdown['cnot']}") print(f"RZ gates: {breakdown['rz']}")
Data-dependent encoding:
from encoding_atlas import BasisEncoding import numpy as np
enc = BasisEncoding(n_features=4) x = np.array([0.1, 0.9, 0.3, 0.8]) # Will be binarized res = count_resources(enc, x=x) print(f"X gates (depends on input): {res['gate_count']}")
Notes¶
For most encodings, resource counts are computed analytically from encoding parameters (n_features, reps, entanglement pattern, etc.). This is fast and deterministic.
For data-dependent encodings like BasisEncoding, the gate count depends on the specific input values (e.g., number of 1s in binary input determines number of X gates).
See Also¶
get_resource_summary : Get summary from encoding properties. get_gate_breakdown : Get detailed gate-by-gate breakdown. compare_resources : Compare resources between multiple encodings. check_simulability : Check if encoding is classically simulable.
create_rng
¶
Create a numpy random number generator.
Parameters¶
seed : int, optional Random seed for reproducibility. If None, uses system entropy.
Returns¶
Generator NumPy random number generator instance.
Examples¶
rng = create_rng(42) values = rng.random(5)
Same seed produces same values¶
rng2 = create_rng(42) values2 = rng2.random(5) np.allclose(values, values2) True
detect_barren_plateau
¶
detect_barren_plateau(gradient_variance: float, n_qubits: int, n_params: int) -> Literal['low', 'medium', 'high']
Detect barren plateau risk based on gradient variance.
Compares observed gradient variance to theoretical scaling for barren plateaus (exponential decay with qubit count) and returns a categorical risk assessment.
The thresholds are adjusted based on system size to account for the natural scaling of gradient variance with qubit count.
Parameters¶
gradient_variance : float Observed gradient variance from gradient sampling. Must be non-negative. n_qubits : int Number of qubits in the circuit. Used to adjust thresholds based on expected scaling. n_params : int Number of parameters in the circuit. Currently used for logging but may be used for more sophisticated detection in future versions.
Returns¶
{"low", "medium", "high"} Barren plateau risk level:
- "low": Variance is healthy, gradient-based training should work
- "medium": Variance is borderline, may need careful tuning
- "high": Variance is very low, likely barren plateau
Notes¶
Risk Threshold Calibration:
The risk thresholds are calibrated based on literature values:
- McClean et al. (2018) showed variance scales as O(½ⁿ) for random circuits with global cost functions
- Cerezo et al. (2021) showed local cost functions have better scaling: O(1/n) for certain circuit architectures
The thresholds are adjusted as follows:
- Base high-risk threshold: 1e-6
- Base medium-risk threshold: 1e-3
- Size adjustment: thresholds scale with 2^(-n) to account for natural variance decay
Practical Interpretation:
- "low" risk: Proceed with standard optimization
- "medium" risk: Consider using:
- Adaptive learning rates
- Better parameter initialization (e.g., identity-initialized)
- Noise injection during training
- "high" risk: Fundamental changes needed:
- Reduce circuit depth
- Use local cost functions
- Try hardware-efficient ansatze
- Consider layer-wise training
Examples¶
risk = detect_barren_plateau( ... gradient_variance=1e-2, ... n_qubits=4, ... n_params=16 ... ) print(f"Risk level: {risk}") Risk level: low
risk = detect_barren_plateau( ... gradient_variance=1e-8, ... n_qubits=10, ... n_params=40 ... ) print(f"Risk level: {risk}") Risk level: high
See Also¶
estimate_trainability : Complete trainability analysis. compute_gradient_variance : Compute gradient variance.
estimate_concentration_scaling
¶
estimate_concentration_scaling(factory: Callable[[int], BaseEncoding], *, feature_counts: tuple[int, ...] = _DEFAULT_FEATURE_COUNTS, n_samples: int = _DEFAULT_N_SAMPLES, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, sampling: SamplingMethod = 'uniform', threshold: float = CONCENTRATION_THRESHOLD, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> ScalingResult
Measure kernel concentration across a range of circuit widths.
Calls factory(n_features) for each requested feature count, measures
the concentration at each resulting width, and fits a log-linear decay
model. Feature counts the factory rejects (an encoding requiring exactly
two features, or an even count) are recorded in
:attr:ScalingResult.skipped rather than aborting the sweep.
The fit is against the encoding's qubit count, not its feature count,
because concentration is a statement about Hilbert-space dimension. The
distinction matters for amplitude encoding, where n_qubits =
ceil(log2(n_features)).
Parameters¶
factory : callable
n_features -> BaseEncoding, e.g.
lambda d: IQPEncoding(n_features=d, reps=2).
feature_counts : tuple[int, ...], default=(2, 4, 6, 8)
Feature counts to sweep. Must be non-empty and all positive.
n_samples : int, default=40
Random inputs per width. Must be >= 2.
input_range : (float, float), default=(0, 2*pi)
Range for randomly sampled inputs.
sampling : {"uniform", "sobol"}, default="uniform"
Random-input strategy.
threshold : float, default=2.0
concentration_ratio defining the Haar floor.
seed : int or None, default=None
Base seed. Width i uses seed + i, so widths are independent
draws yet the whole sweep is reproducible.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector simulation backend.
Returns¶
ScalingResult Per-width measurements plus the fitted decay rates, Haar-normalized slope, and fit quality.
Raises¶
ValueError
If feature_counts is empty or contains non-positive values, or if
any shared argument is invalid.
RuntimeError
If the factory failed for every requested feature count; the
per-count reasons are included in the message.
Examples¶
from encoding_atlas import IQPEncoding scaling = estimate_concentration_scaling( ... lambda d: IQPEncoding(n_features=d, reps=2), ... feature_counts=(2, 4), ... n_samples=12, ... seed=0, ... ) len(scaling.results) 2
See Also¶
compute_kernel_concentration : Single-width measurement.
estimate_entanglement_bound
¶
estimate_entanglement_bound(encoding: BaseEncoding, n_samples: int = 100, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> float
Estimate upper bound on entanglement entropy.
Samples random inputs and estimates the maximum entanglement entropy across the middle bipartition. Low entanglement suggests the circuit may be simulable using tensor network methods.
The entanglement entropy is computed as the von Neumann entropy of the reduced density matrix for one half of the system:
S = -Tr(ρ_A log₂ ρ_A)
where ρ_A is obtained by tracing out the other half.
Parameters¶
encoding : BaseEncoding The encoding to analyze. n_samples : int, default=100 Number of random inputs to sample. More samples give more reliable estimates but take longer. seed : int, optional Random seed for reproducibility. backend : {"pennylane", "qiskit", "cirq"}, default="pennylane" Backend to use for circuit simulation.
- ``"pennylane"``: Uses PennyLane's default.qubit simulator (recommended)
- ``"qiskit"``: Uses Qiskit's Statevector class
- ``"cirq"``: Uses Cirq's Simulator for statevector simulation
Returns¶
float Estimated maximum entanglement entropy (in bits). For n qubits, the maximum possible value is n/2 bits (achieved by maximally entangled states).
Raises¶
AnalysisError If encoding is not valid or simulation fails. ValueError If an unknown backend is specified.
Warnings¶
UserWarning If the encoding has more than 25 qubits, a warning is issued about memory usage for simulation.
Notes¶
This is a statistical estimate based on random sampling. The true maximum may be higher for adversarial inputs not sampled.
For non-entangling encodings, this will return 0.0.
For tensor network simulability, entanglement entropy bounded by O(log n) suggests efficient MPS simulation is possible.
Examples¶
from encoding_atlas import AngleEncoding, IQPEncoding
Non-entangling encoding has zero entanglement¶
enc = AngleEncoding(n_features=4) entropy = estimate_entanglement_bound(enc, n_samples=50, seed=42) entropy < 0.01 # Essentially zero True
Entangling encoding has non-zero entanglement¶
enc = IQPEncoding(n_features=4, reps=2) entropy = estimate_entanglement_bound(enc, n_samples=50, seed=42) entropy > 0.1 True
Using Cirq backend¶
entropy_cirq = estimate_entanglement_bound(enc, n_samples=50, seed=42, backend="cirq") entropy_cirq > 0.1 True
See Also¶
check_simulability : Full simulability analysis. compute_entanglement_capability : Related entanglement measure.
estimate_execution_time
¶
estimate_execution_time(encoding: BaseEncoding, *, single_qubit_gate_time_us: float = _DEFAULT_SINGLE_QUBIT_GATE_TIME_US, two_qubit_gate_time_us: float = _DEFAULT_TWO_QUBIT_GATE_TIME_US, measurement_time_us: float = _DEFAULT_MEASUREMENT_TIME_US, include_measurement: bool = True, parallelization_factor: float = 0.5) -> dict[str, float]
Estimate execution time for an encoding circuit.
Provides a rough estimate of circuit execution time based on gate counts and typical gate times. This is useful for:
- Comparing relative execution times between encodings
- Planning batch processing
- Evaluating hardware constraints
Parameters¶
encoding : BaseEncoding The encoding to analyze. single_qubit_gate_time_us : float, default=0.02 Time for single-qubit gates in microseconds. Default is 20 ns, typical for superconducting qubits. two_qubit_gate_time_us : float, default=0.2 Time for two-qubit gates in microseconds. Default is 200 ns, typical for superconducting qubits. measurement_time_us : float, default=1.0 Time for measurement in microseconds. include_measurement : bool, default=True Whether to include measurement time in the estimate. parallelization_factor : float, default=0.5 Fraction of gates assumed to run in parallel. 0 = fully serial, 1 = fully parallel. Default of 0.5 assumes moderate parallelism.
Returns¶
dict[str, float] Dictionary containing:
- ``serial_time_us``: Time assuming no parallelization
- ``estimated_time_us``: Time with parallelization factor
- ``single_qubit_time_us``: Time for single-qubit gates only
- ``two_qubit_time_us``: Time for two-qubit gates only
- ``measurement_time_us``: Measurement time (if included)
- ``parallelization_factor``: The factor used
Examples¶
from encoding_atlas import IQPEncoding from encoding_atlas.analysis import estimate_execution_time
enc = IQPEncoding(n_features=4, reps=2) times = estimate_execution_time(enc) print(f"Estimated time: {times['estimated_time_us']:.2f} μs")
With custom gate times (trapped ions):
times = estimate_execution_time( ... enc, ... single_qubit_gate_time_us=1.0, # 1 μs ... two_qubit_gate_time_us=100.0, # 100 μs ... )
Notes¶
This is a rough estimate based on gate counts and does not account for:
- Circuit topology and routing overhead
- Hardware-specific gate implementations
- Error correction overhead
- Classical control latency
For accurate timing, run the circuit on actual hardware or use a detailed hardware simulator.
The parallelization factor accounts for the fact that independent gates can run in parallel. A factor of 0.5 means the effective time is 50% of the fully serial time.
estimate_trainability
¶
estimate_trainability(encoding: BaseEncoding, n_samples: int = ..., input_range: tuple[float, float] = ..., observable: Literal['computational', 'pauli_z', 'global_z'] = ..., seed: int | None = ..., backend: Literal['pennylane', 'qiskit', 'cirq'] = ..., return_details: Literal[False] = ..., verbose: bool = ..., parallel: ParallelArg = ..., max_workers: int | None = ..., sampling: SamplingMethod = ..., confidence_level: float = ..., n_bootstrap_ci: int = ...) -> float
estimate_trainability(encoding: BaseEncoding, n_samples: int = ..., input_range: tuple[float, float] = ..., observable: Literal['computational', 'pauli_z', 'global_z'] = ..., seed: int | None = ..., backend: Literal['pennylane', 'qiskit', 'cirq'] = ..., return_details: Literal[True] = ..., verbose: bool = ..., parallel: ParallelArg = ..., max_workers: int | None = ..., sampling: SamplingMethod = ..., confidence_level: float = ..., n_bootstrap_ci: int = ...) -> TrainabilityResult
estimate_trainability(encoding: BaseEncoding, n_samples: int = _DEFAULT_N_SAMPLES, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, observable: Literal['computational', 'pauli_z', 'global_z'] = 'computational', seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane', return_details: bool = False, verbose: bool = False, parallel: ParallelArg = False, max_workers: int | None = None, sampling: SamplingMethod = 'uniform', confidence_level: float = _DEFAULT_CONFIDENCE_LEVEL, n_bootstrap_ci: int = _DEFAULT_N_BOOTSTRAP_CI) -> float | TrainabilityResult
Estimate the trainability of a quantum encoding.
Trainability is estimated by analyzing the variance of gradients with respect to encoding parameters. Low variance (especially exponentially decaying with system size) indicates barren plateaus and poor trainability.
This function samples random parameter initializations, computes gradients using the parameter-shift rule, and analyzes the variance of these gradients to detect barren plateaus.
Parameters¶
encoding : BaseEncoding
The encoding instance to analyze. Must be a valid encoding that
implements the get_circuit() method.
n_samples : int, default=500
Number of random parameter initializations to sample. Higher
values give more accurate estimates but take longer to compute.
Minimum value is 10, recommended minimum is 50.
input_range : tuple[float, float], default=(0, 2π)
Range for sampling random input/parameter values as (min, max).
The default range covers full rotation angles for quantum gates.
observable : {"computational", "pauli_z", "global_z"}, default="computational"
Observable to use for gradient computation:
- ``"computational"``: Probability of |0...0⟩ state (local)
- ``"pauli_z"``: Expectation value of Z on first qubit (local)
- ``"global_z"``: Expectation value of Z⊗Z⊗...⊗Z (global)
Local observables are less prone to false positives for barren
plateau detection and are recommended for most use cases.
seed : int, optional Random seed for reproducibility. If None, uses system entropy. For reproducible results, always specify a seed. backend : {"pennylane", "qiskit", "cirq"}, default="pennylane" Backend for circuit simulation and gradient computation:
- ``"pennylane"``: Uses PennyLane's default.qubit simulator
- ``"qiskit"``: Uses Qiskit's Statevector simulator
- ``"cirq"``: Uses Cirq's Simulator for statevector simulation
return_details : bool, default=False
If True, return full result dictionary with detailed statistics.
If False, return only the trainability estimate as a float.
verbose : bool, default=False
If True, log progress information during computation.
parallel : bool or {'thread', 'process'}, default=False
Parallel-dispatch mode for the per-sample gradient computation.
Trainability is the most expensive of the three analyses
(2 * n_features simulations per sample for the
parameter-shift rule), so parallelism delivers the largest
wall-clock speedup here.
- ``False`` (default) — sequential, no executor overhead.
- ``True`` or ``'thread'`` — :class:`ThreadPoolExecutor`.
- ``'process'`` — :class:`ProcessPoolExecutor` with the encoding
pickled once per worker. Workers exchange only NumPy float
arrays (gradient vectors), so process-pool parallelism works
with **all** three backends here.
Output is numerically identical across all modes for a fixed
``seed`` — the RNG is fully consumed in the main process before
any work is dispatched. Failed samples are still counted (so the
``failure_fraction`` check produces the same result as
sequential), and successful gradients are stored contiguously
from index 0 just like the sequential version.
max_workers : int or None, default=None
Maximum number of workers when parallel is enabled.
sampling : {'uniform', 'sobol'}, default='uniform'
Strategy for drawing the random per-sample inputs:
- ``'uniform'`` (default, unchanged): pseudo-random i.i.d.
uniform draws.
- ``'sobol'``: Sobol' low-discrepancy quasi-random sequence,
seeded from the same ``seed`` argument. Typically gives the
same variance accuracy with **30-50% fewer samples**. For
best statistical properties choose ``n_samples`` as a power
of two.
confidence_level : float, default=0.95
Two-sided confidence level for the bootstrap CIs on
trainability_estimate and gradient_variance reported
in the result dict (only used when return_details=True).
Must lie strictly in (0, 1).
n_bootstrap_ci : int, default=200
Number of bootstrap resamples used to estimate the
percentile CIs (only when return_details=True). 200
stabilizes the percentile endpoints to ~1% jitter at
negligible cost.
Returns¶
float or TrainabilityResult
If return_details=False: Trainability estimate in [0, 1],
where higher values indicate better trainability.
If ``return_details=True``: Dictionary containing:
- ``trainability_estimate``: float in [0, 1]
- ``gradient_variance``: variance computed over successful samples only
- ``barren_plateau_risk``: "low", "medium", or "high"
- ``effective_dimension``: estimated effective parameter dimension
- ``n_samples``: total number of samples requested
- ``n_successful_samples``: samples used for variance computation
- ``per_parameter_variance``: variance for each parameter
- ``n_failed_samples``: number of failed gradient computations
Raises¶
InsufficientSamplesError
If n_samples < 10.
SimulationError
If too many gradient computations fail (> 20% of samples).
AnalysisError
If the encoding is invalid or cannot be analyzed.
Warnings¶
UserWarning
If n_samples < 50, a warning is issued about potential
statistical unreliability.
Notes¶
Interpretation:
The trainability score should be interpreted as a relative measure for comparing encodings, not an absolute guarantee. A score of 0.8 means the encoding appears more trainable than one with score 0.5, but actual training success depends on many other factors.
Barren Plateau Risk Levels:
- "low" (variance ≥ 1e-3): Training should proceed normally
- "medium" (1e-6 ≤ variance < 1e-3): May need careful tuning
- "high" (variance < 1e-6): Likely barren plateau, consider:
- Using fewer qubits
- Shallower circuits
- Local cost functions
- Parameter initialization strategies
Computational Cost:
Each sample requires computing gradients for all parameters, which involves 2 × n_parameters circuit evaluations (parameter-shift rule). Total circuit evaluations = 2 × n_parameters × n_samples.
Failed Sample Handling:
Gradient computation may fail for certain parameter configurations
due to numerical instabilities or backend issues. Failed samples are
completely excluded from variance computation to ensure unbiased
statistical estimates. If more than 20% of samples fail, a
:exc:SimulationError is raised. The number of successful samples
used is reported in n_successful_samples when return_details=True.
Best Practices:
- Use at least 100 samples for reliable estimates
- Compare encodings using the same seed for fair comparison
- Use local observables ("computational" or "pauli_z") unless specifically studying global cost functions
- For publication, report both trainability score and gradient variance with confidence intervals
Examples¶
Basic usage:
from encoding_atlas import AngleEncoding enc = AngleEncoding(n_features=4) train = estimate_trainability(enc, seed=42) print(f"Trainability: {train:.4f}")
Detailed analysis:
result = estimate_trainability( ... enc, n_samples=200, seed=42, return_details=True ... ) print(f"Variance: {result['gradient_variance']:.2e}") print(f"Risk level: {result['barren_plateau_risk']}")
Comparing encodings:
from encoding_atlas import IQPEncoding enc1 = AngleEncoding(n_features=4) enc2 = IQPEncoding(n_features=4, reps=2) t1 = estimate_trainability(enc1, seed=42) t2 = estimate_trainability(enc2, seed=42) print(f"Angle: {t1:.4f}, IQP: {t2:.4f}")
See Also¶
compute_gradient_variance : Compute only gradient variance. detect_barren_plateau : Detect barren plateau from variance.
generate_random_parameters
¶
generate_random_parameters(encoding_or_n_features: BaseEncoding | int, n_samples: int = 1, param_min: float = _DEFAULT_PARAM_MIN, param_max: float = _DEFAULT_PARAM_MAX, seed: int | None = None) -> FloatArray
Generate random parameter vectors for encoding analysis.
Parameters¶
encoding_or_n_features : BaseEncoding or int
Either an encoding instance (n_features is extracted
automatically) or an integer specifying the number of features
directly.
n_samples : int, default=1
Number of parameter vectors to generate.
param_min : float, default=0.0
Minimum parameter value.
param_max : float, default=2π
Maximum parameter value.
seed : int, optional
Random seed for reproducibility.
Returns¶
FloatArray
Random parameters of shape (n_samples, n_features) if
n_samples > 1, or (n_features,) if n_samples == 1.
Examples¶
Generate parameters from an encoding:
from encoding_atlas import AngleEncoding enc = AngleEncoding(n_features=4) params = generate_random_parameters(enc, n_samples=10, seed=42) print(params.shape) (10, 4)
Generate parameters by specifying n_features directly:
params = generate_random_parameters(4, seed=42) print(params.shape) (4,)
With custom range:
params = generate_random_parameters( ... 4, n_samples=10, param_min=-np.pi, param_max=np.pi ... )
geometric_difference
¶
geometric_difference(K1: NDArray[floating[Any]], K2: NDArray[floating[Any]], *, regularization: float = 1e-06) -> float
Asymmetric geometric difference g(K1 || K2) (Huang et al., 2021).
g(K1 || K2) = sqrt( || sqrt(K2) (K1 + reg I)^-1 sqrt(K2) ||_inf ) where
||.||_inf is the spectral norm and both kernels are trace-normalised to
n. It quantifies how well a model expressed in K1's space can
reproduce K2's geometry: g is large when K2 explores directions
K1 cannot. g(K, K) = 1 (in the reg -> 0 limit).
Parameters¶
K1, K2 : ndarray, shape (n, n)
Kernel matrices (symmetric PSD). K1 is the reference (inverted);
for a quantum-advantage diagnostic use K1 = classical and
K2 = quantum.
regularization : float, default=1e-6
Ridge added to K1 before inversion for numerical stability.
get_gate_breakdown
¶
get_gate_breakdown(encoding: BaseEncoding, x: NDArray[floating[Any]] | None = None) -> DetailedGateBreakdown
Get detailed gate-by-gate breakdown for an encoding.
This is a convenience function that calls :func:count_resources
with detailed=True.
Parameters¶
encoding : BaseEncoding The encoding to analyze. x : NDArray[np.floating], optional Input data for data-dependent encodings. Required for encodings like BasisEncoding where gate count depends on input values.
Returns¶
DetailedGateBreakdown Dictionary with counts for each gate type.
Raises¶
AnalysisError If encoding is invalid. ValidationError If x is required but not provided or has invalid values.
Examples¶
from encoding_atlas import IQPEncoding from encoding_atlas.analysis import get_gate_breakdown
enc = IQPEncoding(n_features=4, reps=2) breakdown = get_gate_breakdown(enc) print(f"CNOT gates: {breakdown['cnot']}") print(f"Hadamard gates: {breakdown['h']}") print(f"Total: {breakdown['total']}")
See Also¶
count_resources : Full resource counting with summary option. get_resource_summary : Quick summary from cached properties.
get_resource_summary
¶
get_resource_summary(encoding: BaseEncoding) -> ResourceCountSummary
Get a quick resource summary from encoding properties.
This is a lightweight alternative to :func:count_resources that
uses cached encoding properties when available. It always returns
theoretical (worst-case) values, even for data-dependent encodings.
Parameters¶
encoding : BaseEncoding The encoding to summarize.
Returns¶
ResourceCountSummary Dictionary with resource metrics. See Warnings section for details on which fields contain approximations.
Raises¶
AnalysisError If encoding is not a valid BaseEncoding instance.
Warnings¶
Approximated Fields: The following fields are approximations based on aggregate property values, not actual gate-by-gate circuit analysis:
cnot_count: Approximated astwo_qubit_gates. This assumes all two-qubit gates are CNOTs, which may overcount if the encoding uses CZ or SWAP gates instead.cz_count: Always returns 0 (requires detailed circuit analysis).t_gate_count: Always returns 0 (requires detailed circuit analysis).hadamard_count: Always returns 0 (requires detailed circuit analysis).rotation_gates: Approximated assingle_qubit_gates. This assumes all single-qubit gates are rotation gates (RX, RY, RZ), which may overcount if the encoding uses non-rotation gates like H, X, Y, Z, S, T.
For accurate gate-type counts, use :func:count_resources with
detailed=True, which performs actual gate-by-gate analysis via
the encoding's gate_count_breakdown() method when available.
Examples¶
from encoding_atlas import IQPEncoding from encoding_atlas.analysis import get_resource_summary
enc = IQPEncoding(n_features=4, reps=2) summary = get_resource_summary(enc) print(f"Qubits: {summary['n_qubits']}, Depth: {summary['depth']}")
For accurate gate counts, prefer count_resources with detailed=True:
from encoding_atlas.analysis import count_resources detailed = count_resources(enc, detailed=True) print(f"Actual Hadamard gates: {detailed['h']}")
Notes¶
This function uses the properties attribute of the encoding,
which is typically cached after first access. This makes it very
fast for repeated calls.
For data-dependent encodings, this returns worst-case (maximum)
gate counts. Use :func:count_resources with input data for
actual counts.
See Also¶
count_resources : Full resource counting with detailed options. get_gate_breakdown : Get detailed gate-by-gate breakdown.
get_simulability_reason
¶
get_simulability_reason(encoding: BaseEncoding) -> str
Get a concise explanation of why an encoding is or isn't simulable.
This is a convenience function that provides a quick summary without
the full details returned by :func:check_simulability.
Parameters¶
encoding : BaseEncoding The encoding to analyze.
Returns¶
str Human-readable explanation of simulability status.
Raises¶
AnalysisError If encoding is not a valid BaseEncoding instance.
Examples¶
from encoding_atlas import AngleEncoding, IQPEncoding enc = AngleEncoding(n_features=4) print(get_simulability_reason(enc)) Simulable: Encoding produces only product states (no entanglement)
enc = IQPEncoding(n_features=4, reps=2) reason = get_simulability_reason(enc) print(reason.startswith("Not simulable:")) True
See Also¶
check_simulability : Get full simulability analysis with details.
haar_kernel_moments
¶
Return the Haar-random (mean, variance) of the fidelity kernel.
For two independent Haar-random pure states in dimension d = 2**n_qubits
the overlap |<psi|phi>|^2 follows Beta(1, d - 1), giving
mean = 1 / d , variance = (d - 1) / (d^2 (d + 1)) .
The variance is the floor an encoding's kernel decays towards; it is the
reference for :attr:ConcentrationResult.concentration_ratio.
Parameters¶
n_qubits : int Circuit width. Must be a positive integer.
Returns¶
(float, float) The Haar mean and variance of a single off-diagonal kernel entry.
Raises¶
ValueError
If n_qubits is not a positive integer.
Examples¶
mean, variance = haar_kernel_moments(4) round(mean, 6) 0.0625 round(variance, 8) 0.00344669
is_clifford_circuit
¶
is_clifford_circuit(encoding: BaseEncoding) -> bool
Check if an encoding uses only Clifford gates.
Clifford circuits can be efficiently simulated using the stabilizer formalism, as proven by the Gottesman-Knill theorem. The Clifford gate set includes: H, S, CNOT, CZ, and Pauli gates (X, Y, Z).
Parameters¶
encoding : BaseEncoding The encoding to check.
Returns¶
bool True if the circuit is believed to use only Clifford gates.
Raises¶
AnalysisError If encoding is not a valid BaseEncoding instance.
Notes¶
This is a conservative check based on encoding properties and known gate sets. Some encodings may be classified as non-Clifford even if specific parameter choices yield Clifford circuits.
For example, RZ(π/2) is equivalent to S (a Clifford gate), but general RZ(θ) rotations are non-Clifford.
Examples¶
from encoding_atlas import AngleEncoding, BasisEncoding
AngleEncoding uses parameterized rotations (non-Clifford in general)¶
enc = AngleEncoding(n_features=4) is_clifford_circuit(enc) False
See Also¶
check_simulability : Full simulability analysis.
is_matchgate_circuit
¶
is_matchgate_circuit(encoding: BaseEncoding) -> bool
Check if an encoding uses only matchgate operations.
Matchgate circuits with nearest-neighbor connectivity on a line topology can be efficiently simulated classically in polynomial time. This is based on the fact that matchgates preserve fermionic parity and can be mapped to free-fermion systems.
Parameters¶
encoding : BaseEncoding The encoding to check.
Returns¶
bool True if the circuit is believed to use only matchgate operations with appropriate (nearest-neighbor) topology.
Raises¶
AnalysisError If encoding is not a valid BaseEncoding instance.
Notes¶
What are Matchgates?
Matchgates are a class of two-qubit gates that act on the computational basis states in a specific way that preserves particle number parity. Common matchgates include:
- iSWAP: Swaps |01⟩ ↔ |10⟩ with a phase
- fSWAP (fermionic SWAP): Used in fermionic simulations
- Givens rotations: Parameterized rotations in the {|01⟩, |10⟩} subspace
Simulability Conditions
Matchgate circuits are classically simulable when:
- All two-qubit gates are matchgates
- Qubits are arranged in a line topology
- Two-qubit gates act only on nearest neighbors
Limitations
This check is heuristic and may be conservative. It identifies known matchgate-based encodings by name and checks for linear entanglement patterns. Some matchgate circuits may not be detected if they don't follow recognized naming conventions.
References¶
.. [1] Jozsa, R., & Miyake, A. (2008). "Matchgates and classical simulation of quantum circuits." Proc. R. Soc. A, 464(2100), 3089-3106.
.. [2] Terhal, B. M., & DiVincenzo, D. P. (2002). "Classical simulation of noninteracting-fermion quantum circuits." Physical Review A, 65(3).
Examples¶
from encoding_atlas import AngleEncoding enc = AngleEncoding(n_features=4) is_matchgate_circuit(enc) False
See Also¶
is_clifford_circuit : Check for Clifford-only circuits. check_simulability : Full simulability analysis.
kernel_effective_dimension
¶
kernel_effective_dimension(K: NDArray[floating[Any]], *, regularization: float = 1.0, normalize: bool = True) -> float
Effective degrees of freedom of a kernel's eigenspectrum.
d_eff(lambda) = sum_i lambda_i / (lambda_i + lambda) over the eigenvalues
lambda_i of K (Zhang, 2005). It is a smooth "soft rank" measuring how
many feature-space directions the kernel effectively uses, in [0, n].
Parameters¶
K : ndarray, shape (n, n)
Kernel matrix (symmetric PSD).
regularization : float, default=1.0
Ridge lambda. With normalize=True the mean eigenvalue is 1, so
lambda = 1 weights each direction by its relative strength.
normalize : bool, default=True
Trace-normalise K to n (mean eigenvalue 1) before the sum, making
the value comparable across encodings and dataset sizes.
kernel_target_alignment
¶
kernel_target_alignment(K: NDArray[floating[Any]], y: NDArray[integer[Any] | floating[Any]]) -> float
Uncentred kernel-target alignment in [-1, 1] (Cristianini, 2002).
A(K, y) = <K, yy^T>_F / (||K||_F ||yy^T||_F) with labels mapped to
{-1, +1}. Returns 0.0 for degenerate kernels or single-class labels.
Two-class labels are mapped by partition, so the score does not depend on
whether the classes are spelled {0, 1}, {1, 2} or {-1, +1}.
Unlike :func:centered_kernel_target_alignment, the uncentred score is
not invariant to that choice on its own, which is why the mapping is
normalised here.
partial_trace_single_qubit
¶
partial_trace_single_qubit(statevector: StatevectorType, n_qubits: int, keep_qubit: int) -> DensityMatrixType
Compute the partial trace, keeping only a single qubit.
This function traces out all qubits except the specified one, returning the reduced density matrix for that qubit.
Parameters¶
statevector : StatevectorType
Full system statevector of shape (2^n_qubits,).
n_qubits : int
Total number of qubits in the system.
keep_qubit : int
Index of the qubit to keep (0-indexed). Qubit 0 is the most
significant bit in the computational basis ordering.
Returns¶
DensityMatrixType
Reduced density matrix for the kept qubit, shape (2, 2).
This is a valid density matrix: Hermitian, positive semidefinite,
with trace 1.
Raises¶
ValueError
If keep_qubit is out of range [0, n_qubits-1].
ValidationError
If the statevector is invalid.
Examples¶
For a separable state |00⟩, each qubit's reduced state is |0⟩:
import numpy as np state_00 = np.array([1, 0, 0, 0], dtype=complex) # |00⟩ rho_0 = partial_trace_single_qubit(state_00, n_qubits=2, keep_qubit=0) print(rho_0) [[1.+0.j 0.+0.j] [0.+0.j 0.+0.j]]
For a Bell state (|00⟩ + |11⟩)/√2, each qubit is maximally mixed:
bell = np.array([1, 0, 0, 1], dtype=complex) / np.sqrt(2) rho_0 = partial_trace_single_qubit(bell, n_qubits=2, keep_qubit=0) print(np.round(rho_0, 3)) [[0.5+0.j 0. +0.j] [0. +0.j 0.5+0.j]]
Notes¶
The convention used is that qubit 0 is the most significant bit. For a 2-qubit system:
- Index 0: |00⟩ (qubit 0 = 0, qubit 1 = 0)
- Index 1: |01⟩ (qubit 0 = 0, qubit 1 = 1)
- Index 2: |10⟩ (qubit 0 = 1, qubit 1 = 0)
- Index 3: |11⟩ (qubit 0 = 1, qubit 1 = 1)
See Also¶
partial_trace_subsystem : Trace out arbitrary qubits. compute_purity : Compute purity of a density matrix.
partial_trace_subsystem
¶
partial_trace_subsystem(statevector: StatevectorType, n_qubits: int, keep_qubits: Sequence[int]) -> DensityMatrixType
Compute partial trace, keeping a specified subsystem.
This function traces out all qubits not in the keep list, returning the reduced density matrix for the kept subsystem.
Parameters¶
statevector : StatevectorType
Full system statevector of shape (2^n_qubits,).
n_qubits : int
Total number of qubits in the system.
keep_qubits : Sequence[int]
Indices of qubits to keep (0-indexed). Must be a non-empty
sequence of unique integers in range [0, n_qubits-1].
Returns¶
DensityMatrixType
Reduced density matrix for the kept subsystem, shape
(2^len(keep_qubits), 2^len(keep_qubits)).
Raises¶
ValueError
If keep_qubits is empty, contains duplicates, or has
out-of-range indices.
ValidationError
If the statevector is invalid.
Examples¶
Example 1: Keep the first two qubits of a 3-qubit product state
import numpy as np
|000⟩ state¶
state = np.zeros(8, dtype=complex) state[0] = 1.0 rho_01 = partial_trace_subsystem(state, n_qubits=3, keep_qubits=[0, 1]) print(rho_01.shape) (4, 4)
Result is |00⟩⟨00| (pure state)¶
print(np.round(rho_01, 3)) [[1.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.+0.j] [0.+0.j 0.+0.j 0.+0.j 0.+0.j]]
Example 2: GHZ state partial trace (keeping first qubit)
For the GHZ state (|000⟩ + |111⟩)/√2, tracing out qubits 1 and 2 leaves qubit 0 in a maximally mixed state:
ghz = np.zeros(8, dtype=complex) ghz[0] = 1.0 / np.sqrt(2) # |000⟩ ghz[7] = 1.0 / np.sqrt(2) # |111⟩ rho_0 = partial_trace_subsystem(ghz, n_qubits=3, keep_qubits=[0])
Single qubit is maximally mixed (I/2)¶
print(np.round(rho_0, 3)) [[0.5+0.j 0. +0.j] [0. +0.j 0.5+0.j]]
Example 3: Non-contiguous qubit selection
Keep qubits 0 and 2, tracing out qubit 1:
|000⟩ state¶
state = np.zeros(8, dtype=complex) state[0] = 1.0 rho_02 = partial_trace_subsystem(state, n_qubits=3, keep_qubits=[0, 2]) print(rho_02.shape) (4, 4)
Result is |00⟩⟨00| for qubits 0 and 2¶
print(np.round(np.diag(rho_02).real, 3)) [1. 0. 0. 0.]
Example 4: W state partial trace
The W state (|001⟩ + |010⟩ + |100⟩)/√3 has different entanglement structure than GHZ. Tracing out one qubit leaves a mixed state:
w_state = np.zeros(8, dtype=complex) w_state[1] = 1.0 / np.sqrt(3) # |001⟩ w_state[2] = 1.0 / np.sqrt(3) # |010⟩ w_state[4] = 1.0 / np.sqrt(3) # |100⟩ rho_01 = partial_trace_subsystem(w_state, n_qubits=3, keep_qubits=[0, 1])
Trace = 1, but not maximally mixed¶
print(f"Trace: {np.trace(rho_01).real:.3f}") Trace: 1.000
Notes¶
The ordering of qubits in the output follows the order specified
in keep_qubits. For example, if keep_qubits=[1, 0], the
first index of the output density matrix corresponds to qubit 1.
Qubit Indexing Convention
Qubit 0 is the most significant bit in the computational basis:
- For 3 qubits: |q0 q1 q2⟩
- Index 0 = |000⟩, Index 1 = |001⟩, Index 4 = |100⟩, Index 7 = |111⟩
Use Cases
- Computing entanglement entropy of subsystems
- Analyzing local properties of entangled states
- Verifying separability of quantum states
profile_encoding
¶
profile_encoding(encoding: BaseEncoding, *, X: NDArray[floating[Any]] | None = None, y: NDArray[integer[Any]] | None = None, expressibility_samples: int = 500, entanglement_samples: int = 500, trainability_samples: int = 100, noise_level: str = 'medium', noise_samples: int = 20, include_noise: bool = True, concentration_samples: int = 30, seed: int | None = None) -> EncodingCharacterization
Characterise an encoding across every analysis axis.
Parameters¶
encoding : BaseEncoding
The encoding to profile (may be any custom encoding, not just a
built-in one).
X : ndarray or None, default=None
Optional feature matrix (n_features must match the encoding). When
given, the kernel-geometry axes are computed.
y : ndarray or None, default=None
Optional binary labels; required for kernel-target alignment.
expressibility_samples, entanglement_samples, trainability_samples : int
Sample counts for the corresponding stochastic estimators.
noise_level : {"low", "medium", "high"}, default="medium"
Noise preset for the noise-resilience axis.
noise_samples : int, default=20
Inputs averaged for the noise-resilience axis.
include_noise : bool, default=True
Whether to compute the (density-matrix) noise axis.
concentration_samples : int, default=30
Inputs used to build the kernel for the concentration axis. When X
is supplied, X is used instead and this is ignored.
seed : int or None, default=None
Base random seed for all stochastic axes.
Returns¶
EncodingCharacterization
The computed multi-axis profile. Axes that fail are recorded as None
with a note, so the call always returns a profile for a valid encoding.
recommend_feature_range
¶
recommend_feature_range(encoding: BaseEncoding, X: NDArray[floating[Any]], y: NDArray[integer[Any] | floating[Any]], *, ranges: Sequence[tuple[float, float]] = DEFAULT_FEATURE_RANGES, max_samples: int = _DEFAULT_MAX_SAMPLES, centered: bool = True, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> tuple[float, float]
Return the feature range that maximises alignment for encoding.
A thin wrapper over :func:scan_feature_ranges for the common case of
"just tell me what to scale into". Ties break towards the narrower range.
Parameters¶
encoding, X, y, ranges, max_samples, centered, seed, backend
As for :func:scan_feature_ranges.
Returns¶
(float, float)
The recommended (low, high). Feed it to :func:scale_to_range.
Examples¶
from encoding_atlas import IQPEncoding from encoding_atlas.benchmark import get_dataset X, y = get_dataset("moons", n_samples=40, seed=0) low, high = recommend_feature_range( ... IQPEncoding(n_features=2, reps=2), X, y, seed=0 ... ) low == 0.0 True
sample_shot_kernel
¶
sample_shot_kernel(K: NDArray[floating[Any]], shots: int, *, seed: int | None = None) -> NDArray[floating[Any]]
Return the finite-shot estimate of an exact fidelity kernel.
Models the compute-uncompute estimator used on hardware: for each pair
(i, j) the circuit U(x_j)^dagger U(x_i)|0> is measured shots
times and the fraction of all-zeros outcomes is taken as K[i, j].
Because the all-zeros probability is K[i, j], that count is exactly
Binomial(shots, K[i, j]) — so drawing from the binomial is
statistically identical to simulating the circuits, at a fraction of the
cost.
The estimate is unbiased entrywise and symmetric, and its diagonal is
exactly 1 (the compute-uncompute circuit for x_i = x_i returns
all-zeros with certainty). It is not guaranteed to be positive
semidefinite: independent noise on each entry routinely pushes the
smallest eigenvalues negative. Project with
:func:encoding_atlas.benchmark.kernel.ensure_psd before handing it to a
precomputed-kernel estimator.
Parameters¶
K : ndarray, shape (n, n)
Exact kernel with entries in [0, 1], e.g. from
:func:compute_fidelity_kernel.
shots : int
Measurements per entry. Must be a positive integer.
seed : int or None, default=None
Seed for the binomial draws.
Returns¶
ndarray, shape (n, n)
Symmetric finite-shot estimate with unit diagonal. Entries are
multiples of 1 / shots.
Raises¶
ValueError
If shots is not a positive integer, or K is not a square 2D
matrix with entries in [0, 1].
Examples¶
import numpy as np K = np.array([[1.0, 0.5], [0.5, 1.0]]) K_hat = sample_shot_kernel(K, shots=1000, seed=0) bool(K_hat[0, 0] == 1.0 and K_hat[0, 1] == K_hat[1, 0]) True
Notes¶
The variance of each off-diagonal estimate is K (1 - K) / shots. When
the kernel has concentrated — every off-diagonal entry near 2**-n —
that noise swamps the signal unless shots grows with the Hilbert-space
dimension; see
:func:encoding_atlas.analysis.concentration.compute_kernel_concentration
for the resulting budget.
scale_to_range
¶
scale_to_range(X: NDArray[floating[Any]], low: float, high: float, *, reference: NDArray[floating[Any]] | None = None) -> NDArray[float64]
Min-max scale each feature of X into [low, high].
Parameters¶
X : ndarray of shape (n_samples, n_features)
Data to transform.
low, high : float
Target interval. Must satisfy low < high and both be finite.
reference : ndarray of shape (m, n_features), optional
Fit the per-feature minimum and span on this array instead of on
X. Pass the training fold here to transform a test fold without
leaking its extremes into the scaler — transformed values may then
fall outside [low, high], which is correct and matches
:class:sklearn.preprocessing.MinMaxScaler. Defaults to X.
Returns¶
ndarray of shape (n_samples, n_features)
The scaled data. Constant features map to low.
Raises¶
ValueError
If X is not 2D, the range is invalid or non-finite, or
reference has a different feature count.
Examples¶
import numpy as np X = np.array([[0.0, 10.0], [1.0, 20.0]]) np.round(scale_to_range(X, 0.0, np.pi), 4) array([[0. , 0. ], [3.1416, 3.1416]])
scan_feature_ranges
¶
scan_feature_ranges(encoding: BaseEncoding, X: NDArray[floating[Any]], y: NDArray[integer[Any] | floating[Any]], *, ranges: Sequence[tuple[float, float]] = DEFAULT_FEATURE_RANGES, max_samples: int = _DEFAULT_MAX_SAMPLES, centered: bool = True, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> FeatureRangeScan
Measure an encoding's kernel geometry across feature-scaling ranges.
For each candidate range the data is min-max scaled into it, the fidelity kernel is built, and both the kernel-target alignment and the kernel concentration are recorded. No training is involved.
Use it to choose a range (see :func:recommend_feature_range) and to see
how much the choice matters — :attr:FeatureRangeScan.alignment_spread
is often comparable to the spread across encodings.
Parameters¶
encoding : BaseEncoding
Encoding to scan. Built once and reused across ranges.
X : ndarray of shape (n_samples, n_features)
Unscaled feature matrix. Whatever scaling it already carries is
overwritten, so pass raw features.
y : ndarray of shape (n_samples,)
Two-class labels. Any two distinct values work.
ranges : sequence of (float, float), default=:data:DEFAULT_FEATURE_RANGES
Candidate ranges. Each must satisfy low < high.
max_samples : int, default=200
Sub-sample above this many rows. Must be at least 2.
centered : bool, default=True
Use the centred alignment (Cortes et al., 2012).
seed : int or None, default=None
Seed for sub-sampling.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector simulation backend.
Returns¶
FeatureRangeScan One measurement per range, plus the best range and the spread.
Raises¶
ValueError
If X/y are malformed or mismatched, y is not two-class,
max_samples is invalid, or any range is invalid.
Examples¶
from encoding_atlas import IQPEncoding from encoding_atlas.benchmark import get_dataset X, y = get_dataset("moons", n_samples=40, seed=0) scan = scan_feature_ranges(IQPEncoding(n_features=2, reps=2), X, y, seed=0) len(scan.results) 4
See Also¶
recommend_feature_range : Just the best range. encoding_atlas.analysis.compute_kernel_concentration : The concentration axis.
simulate_encoding_statevector
¶
simulate_encoding_statevector(encoding: BaseEncoding, x: NDArray[floating[Any]], backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> StatevectorType
Simulate an encoding circuit and return the resulting statevector.
This function executes a quantum circuit defined by the encoding with the given input data and returns the final quantum state as a complex vector in the computational basis.
Parameters¶
encoding : BaseEncoding
The encoding instance to simulate.
x : NDArray[np.floating]
Input data vector of shape (n_features,). The values are used
as parameters for the encoding circuit.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
The quantum simulation backend to use.
- ``"pennylane"``: Uses PennyLane's default.qubit simulator
- ``"qiskit"``: Uses Qiskit's Statevector class
- ``"cirq"``: Uses Cirq's Simulator for statevector simulation
Returns¶
StatevectorType
Complex statevector of shape (2^n_qubits,) representing the
final quantum state in the computational basis. The state is
normalized (sum of |amplitude|² = 1).
Raises¶
SimulationError If the simulation fails due to backend errors, missing dependencies, or invalid circuit structure. ValidationError If the encoding or input data is invalid. ValueError If an unknown backend is specified.
Warnings¶
UserWarning
If the number of qubits exceeds _SIMULATION_QUBIT_WARNING_THRESHOLD
(15 by default), a warning is issued about memory usage.
Examples¶
Simulate an angle encoding circuit:
from encoding_atlas import AngleEncoding import numpy as np
enc = AngleEncoding(n_features=3) x = np.array([0.1, 0.2, 0.3]) state = simulate_encoding_statevector(enc, x) print(f"State shape: {state.shape}") State shape: (8,) print(f"State norm: {np.linalg.norm(state):.6f}") State norm: 1.000000
Using Qiskit backend:
state_qiskit = simulate_encoding_statevector(enc, x, backend="qiskit")
Results should be equivalent (up to global phase)¶
fidelity = np.abs(np.vdot(state, state_qiskit))**2 print(f"Cross-backend fidelity: {fidelity:.6f}") Cross-backend fidelity: 1.000000
Notes¶
The statevector is ordered in the computational basis with the standard convention where qubit 0 is the most significant bit:
- Index 0 corresponds to |00...0⟩
- Index 1 corresponds to |00...1⟩
- Index 2^n - 1 corresponds to |11...1⟩
Memory Requirements
Memory grows exponentially with qubit count:
- Statevector: 2^n × 16 bytes (e.g., 15 qubits → 0.5 MB, 20 qubits → 16 MB)
- Density matrix (for analysis): 2^(2n) × 16 bytes (e.g., 15 qubits → 16 GB)
Analysis operations like entanglement capability create full density matrices, so their practical limit is around 13-15 qubits. For larger systems, consider tensor network methods or sampling-based approaches.
See Also¶
simulate_encoding_statevectors_batch : Simulate multiple inputs at once. compute_fidelity : Compute fidelity between two statevectors.
simulate_encoding_statevectors_batch
¶
simulate_encoding_statevectors_batch(encoding: BaseEncoding, X: NDArray[floating[Any]], backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> NDArray[complexfloating[Any, Any]]
Simulate encoding circuits for multiple input vectors.
This is a convenience function that applies :func:simulate_encoding_statevector
to each row of a 2D input array and returns a pre-allocated 2D array of
statevectors.
Parameters¶
encoding : BaseEncoding
The encoding instance to simulate.
X : NDArray[np.floating]
Input data array of shape (n_samples, n_features).
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
The quantum simulation backend to use.
Returns¶
NDArray[np.complexfloating], shape (n_samples, 2**n_qubits)
2D array of statevectors, one row per input sample.
Raises¶
SimulationError If simulation fails for any input. ValidationError If input shape is incorrect.
Examples¶
from encoding_atlas import AngleEncoding import numpy as np
enc = AngleEncoding(n_features=2) X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]]) states = simulate_encoding_statevectors_batch(enc, X) print(f"Number of states: {len(states)}") Number of states: 3
simulate_noisy_density_matrix
¶
simulate_noisy_density_matrix(encoding: BaseEncoding, x: NDArray[floating[Any]], *, single_qubit_error: float, two_qubit_error: float) -> NDArray[complexfloating[Any, Any]]
Simulate the encoding under depolarizing noise; return the density matrix.
A depolarizing channel is inserted after each gate: single_qubit_error
after one-qubit gates, and two_qubit_error on each qubit of two-qubit
(or larger) gates. Uses PennyLane's default.mixed device.
Parameters¶
encoding : BaseEncoding
Encoding to simulate.
x : ndarray, shape (n_features,)
Input to encode.
single_qubit_error, two_qubit_error : float
Depolarizing probabilities in [0, 1].
Returns¶
ndarray, shape (2**n_qubits, 2**n_qubits) The noisy density matrix.
summarize_kernel_concentration
¶
summarize_kernel_concentration(K: NDArray[floating[Any]], n_qubits: int, *, threshold: float = CONCENTRATION_THRESHOLD) -> ConcentrationResult
Reduce an already-computed fidelity kernel to concentration statistics.
Split out from :func:compute_kernel_concentration so a caller that
already holds the kernel — screening several encodings, for instance —
gets the concentration axis without paying for a second simulation, while
both paths keep a single definition of the statistics.
Parameters¶
K : ndarray of shape (n, n)
Fidelity kernel with entries in [0, 1]. Must have at least two
rows so an off-diagonal pair exists.
n_qubits : int
Circuit width the kernel was produced at; sets the Haar reference.
threshold : float, default=2.0
concentration_ratio below which is_concentrated is set.
Returns¶
ConcentrationResult
The same statistics :func:compute_kernel_concentration returns.
Raises¶
ValueError
If K is not a square 2D matrix of size >= 2, n_qubits is not a
positive integer, or threshold is not positive.
validate_binary_labels
¶
Validate that y is a 1D, finite, exactly-two-class label vector.
Kernel-target alignment is a two-class quantity, so anything else — a single class, a multi-class vector, or a continuous regression target — would be scored meaninglessly rather than rejected. Callers that rank or select encodings by alignment should gate on this first.
Parameters¶
y : ndarray of shape (n_samples,)
Candidate label vector. The two classes may be any two distinct
values; see :func:_signed_labels for how they are mapped.
Returns¶
ndarray of shape (n_samples,), dtype float64 The labels as float64, unchanged apart from the cast.
Raises¶
ValueError
If y is not 1D, contains non-finite values, or does not have
exactly two distinct values.
Examples¶
import numpy as np validate_binary_labels(np.array([0, 1, 1, 0])).dtype dtype('float64')
validate_encoding_for_analysis
¶
validate_encoding_for_analysis(encoding: BaseEncoding) -> None
Validate that an encoding is suitable for analysis operations.
This function performs comprehensive validation to ensure the encoding can be safely used in analysis functions. It checks the encoding type, configuration, and derived properties.
Parameters¶
encoding : BaseEncoding The encoding instance to validate.
Raises¶
AnalysisError If the encoding is not a valid BaseEncoding instance. ValidationError If the encoding has invalid configuration (e.g., zero features or qubits).
Examples¶
from encoding_atlas import AngleEncoding enc = AngleEncoding(n_features=4) validate_encoding_for_analysis(enc) # No exception raised
validate_encoding_for_analysis("not an encoding") Traceback (most recent call last): ... AnalysisError: Expected BaseEncoding instance, got str
Notes¶
This function is called internally by analysis functions. You generally don't need to call it directly unless implementing custom analysis.
validate_statevector
¶
validate_statevector(statevector: NDArray[Any], expected_qubits: int | None = None, check_normalization: bool = True, tolerance: float = 1e-10) -> StatevectorType
Validate and normalize a statevector.
Parameters¶
statevector : NDArray The statevector to validate. expected_qubits : int, optional If provided, verify the statevector has dimension 2^expected_qubits. check_normalization : bool, default=True If True, verify the statevector is normalized and renormalize if needed. tolerance : float, default=1e-10 Tolerance for normalization check.
Returns¶
StatevectorType Validated (and possibly renormalized) statevector as complex128.
Raises¶
ValidationError If the statevector has invalid shape or values. NumericalInstabilityError If the statevector has near-zero norm.
Examples¶
import numpy as np state = np.array([1, 0, 0, 0], dtype=complex) validated = validate_statevector(state, expected_qubits=2) validated.dtype dtype('complex128')
Kernel-Concentration Diagnostics¶
How an encoding's fidelity kernel approaches the Haar floor as the circuit widens — the axis that says whether a fixed-width result transfers. See Kernel Concentration for the theory.
compute_kernel_concentration
¶
compute_kernel_concentration(encoding: BaseEncoding, X: NDArray[floating[Any]] | None = None, *, n_samples: int = _DEFAULT_N_SAMPLES, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, sampling: SamplingMethod = 'uniform', threshold: float = CONCENTRATION_THRESHOLD, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> ConcentrationResult
Measure how concentrated an encoding's fidelity kernel is.
Builds the fidelity kernel on X (or on random inputs when X is not
supplied) and reduces it to the concentration order parameter, the spread
of its off-diagonal entries, and the shot budget that spread implies.
A concentration_ratio near 1 means the kernel's off-diagonal variance
has collapsed to the Haar floor: every pair of distinct inputs is equally
dissimilar, the matrix is the identity up to sampling noise, and a kernel
method built on it cannot generalize regardless of shot budget or noise
level. Values well above 1 mean the encoding still separates inputs.
Parameters¶
encoding : BaseEncoding
Encoding to analyse.
X : ndarray of shape (n_samples, n_features), optional
Inputs to build the kernel from. When None (default), inputs are
drawn from input_range, making this a data-free axis comparable
across encodings — the same convention as expressibility and
entanglement capability.
n_samples : int, default=40
Number of random inputs when X is not supplied. Must be >= 2.
Ignored when X is given.
input_range : (float, float), default=(0, 2*pi)
Range for randomly sampled inputs. Concentration depends on the input
distribution as well as the circuit; narrowing this range (as feature
scaling does) generally reduces it. Ignored when X is given.
sampling : {"uniform", "sobol"}, default="uniform"
Random-input strategy. Ignored when X is given.
threshold : float, default=2.0
concentration_ratio below which is_concentrated is set.
seed : int or None, default=None
Seed for input sampling. Ignored when X is given.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector simulation backend.
Returns¶
ConcentrationResult The measured concentration statistics.
Raises¶
ValueError
If n_samples, input_range, threshold or sampling is
invalid, or if X is not a 2D array with at least two rows.
Warns¶
UserWarning If the encoding is wide enough that the statevector batch becomes memory-hungry (more than 14 qubits).
Examples¶
An entangling feature map sits at the Haar floor; a non-entangling one does not:
from encoding_atlas import AngleEncoding, IQPEncoding angle = compute_kernel_concentration( ... AngleEncoding(n_features=6), n_samples=30, seed=0 ... ) iqp = compute_kernel_concentration( ... IQPEncoding(n_features=6, reps=2), n_samples=30, seed=0 ... ) angle.concentration_ratio > iqp.concentration_ratio True
See Also¶
estimate_concentration_scaling : The same measurement across widths. encoding_atlas.analysis.generalization.compute_fidelity_kernel : The kernel.
Source code in src/encoding_atlas/analysis/concentration.py
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 | |
estimate_concentration_scaling
¶
estimate_concentration_scaling(factory: Callable[[int], BaseEncoding], *, feature_counts: tuple[int, ...] = _DEFAULT_FEATURE_COUNTS, n_samples: int = _DEFAULT_N_SAMPLES, input_range: tuple[float, float] = _DEFAULT_INPUT_RANGE, sampling: SamplingMethod = 'uniform', threshold: float = CONCENTRATION_THRESHOLD, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> ScalingResult
Measure kernel concentration across a range of circuit widths.
Calls factory(n_features) for each requested feature count, measures
the concentration at each resulting width, and fits a log-linear decay
model. Feature counts the factory rejects (an encoding requiring exactly
two features, or an even count) are recorded in
:attr:ScalingResult.skipped rather than aborting the sweep.
The fit is against the encoding's qubit count, not its feature count,
because concentration is a statement about Hilbert-space dimension. The
distinction matters for amplitude encoding, where n_qubits =
ceil(log2(n_features)).
Parameters¶
factory : callable
n_features -> BaseEncoding, e.g.
lambda d: IQPEncoding(n_features=d, reps=2).
feature_counts : tuple[int, ...], default=(2, 4, 6, 8)
Feature counts to sweep. Must be non-empty and all positive.
n_samples : int, default=40
Random inputs per width. Must be >= 2.
input_range : (float, float), default=(0, 2*pi)
Range for randomly sampled inputs.
sampling : {"uniform", "sobol"}, default="uniform"
Random-input strategy.
threshold : float, default=2.0
concentration_ratio defining the Haar floor.
seed : int or None, default=None
Base seed. Width i uses seed + i, so widths are independent
draws yet the whole sweep is reproducible.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector simulation backend.
Returns¶
ScalingResult Per-width measurements plus the fitted decay rates, Haar-normalized slope, and fit quality.
Raises¶
ValueError
If feature_counts is empty or contains non-positive values, or if
any shared argument is invalid.
RuntimeError
If the factory failed for every requested feature count; the
per-count reasons are included in the message.
Examples¶
from encoding_atlas import IQPEncoding scaling = estimate_concentration_scaling( ... lambda d: IQPEncoding(n_features=d, reps=2), ... feature_counts=(2, 4), ... n_samples=12, ... seed=0, ... ) len(scaling.results) 2
See Also¶
compute_kernel_concentration : Single-width measurement.
Source code in src/encoding_atlas/analysis/concentration.py
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 | |
haar_kernel_moments
¶
Return the Haar-random (mean, variance) of the fidelity kernel.
For two independent Haar-random pure states in dimension d = 2**n_qubits
the overlap |<psi|phi>|^2 follows Beta(1, d - 1), giving
mean = 1 / d , variance = (d - 1) / (d^2 (d + 1)) .
The variance is the floor an encoding's kernel decays towards; it is the
reference for :attr:ConcentrationResult.concentration_ratio.
Parameters¶
n_qubits : int Circuit width. Must be a positive integer.
Returns¶
(float, float) The Haar mean and variance of a single off-diagonal kernel entry.
Raises¶
ValueError
If n_qubits is not a positive integer.
Examples¶
mean, variance = haar_kernel_moments(4) round(mean, 6) 0.0625 round(variance, 8) 0.00344669
Source code in src/encoding_atlas/analysis/concentration.py
ConcentrationResult
dataclass
¶
ConcentrationResult(n_qubits: int, n_samples: int, offdiagonal_mean: float, offdiagonal_variance: float, offdiagonal_std: float, offdiagonal_min: float, offdiagonal_max: float, haar_mean: float, haar_variance: float, concentration_ratio: float, mean_ratio: float, identity_distance: float, is_concentrated: bool, threshold: float, shots_per_entry: float)
Fidelity-kernel concentration measured at one circuit width.
Attributes¶
n_qubits : int
Circuit width the kernel was measured at.
n_samples : int
Number of inputs used; statistics are over the
n_samples (n_samples - 1) / 2 off-diagonal pairs.
offdiagonal_mean : float
Mean off-diagonal kernel entry, in [0, 1].
offdiagonal_variance, offdiagonal_std : float
Spread of the off-diagonal entries — the signal a kernel method
actually uses. Its vanishing is concentration.
offdiagonal_min, offdiagonal_max : float
Extremes of the off-diagonal entries.
haar_mean, haar_variance : float
Haar-random reference moments at this width, from
:func:haar_kernel_moments.
concentration_ratio : float
offdiagonal_variance / haar_variance — the order parameter.
Near 1 means the kernel's spread has reached the Haar floor and
carries no usable geometry; larger means structure remains. Directly
comparable across qubit counts.
mean_ratio : float
offdiagonal_mean / haar_mean. Reported for completeness only:
it is 1 for any ensemble of independent random single-qubit states
as well as for Haar, so it cannot distinguish the two regimes. Do not
use it as a concentration measure.
identity_distance : float
Root-mean-square off-diagonal entry, i.e.
||K - I||_F / sqrt(n (n - 1)). Zero means K is exactly I.
is_concentrated : bool
Whether concentration_ratio is below :attr:threshold.
threshold : float
The concentration_ratio cut used for :attr:is_concentrated.
shots_per_entry : float
Shots per kernel entry for the compute-uncompute estimator's standard
error to fall below half the observed spread —
4 K_bar (1 - K_bar) / Var. inf if the entries have no spread.
A noiseless lower bound on hardware cost.
shots_for_dataset
¶
Total shots to estimate a full n_samples x n_samples kernel.
The kernel is symmetric with a deterministic unit diagonal, so only
n (n - 1) / 2 entries need measuring.
Parameters¶
n_samples : int Dataset size. Must be a positive integer.
Returns¶
float
shots_per_entry * n (n - 1) / 2; inf if the per-entry cost
is unbounded.
Raises¶
ValueError
If n_samples is not a positive integer.
Source code in src/encoding_atlas/analysis/concentration.py
ScalingResult
dataclass
¶
ScalingResult(encoding_name: str, n_qubits: tuple[int, ...], feature_counts: tuple[int, ...], results: tuple[ConcentrationResult, ...], decay_rate: float, mean_decay_rate: float, haar_normalized_slope: float, r_squared: float, skipped: dict[int, str])
How an encoding's kernel concentration scales with circuit width.
Attributes¶
encoding_name : str
Class name of the encodings the factory produced.
n_qubits : tuple[int, ...]
Circuit widths successfully measured, ascending.
feature_counts : tuple[int, ...]
Feature counts that produced those widths, in the same order.
results : tuple[ConcentrationResult, ...]
Per-width measurements, in the same order.
decay_rate : float
Per-qubit factor by which the off-diagonal variance shrinks, from
a least-squares fit of log(variance) against n_qubits. 1.0
means no decay. The Haar floor itself decays at 4.0, so a rate
near 4 means the encoding is scrambling as fast as it possibly can.
nan if fewer than two widths were measurable.
mean_decay_rate : float
The same quantity for the off-diagonal mean. The Haar floor decays at
2.0. nan if unfittable.
haar_normalized_slope : float
Slope of log(concentration_ratio) against n_qubits. Negative
means the kernel is collapsing towards the Haar floor as the circuit
widens; non-negative means it holds structure or pulls away. This is
the honest discriminator — a large decay_rate alone proves nothing,
because the floor is falling too.
r_squared : float
Coefficient of determination of the log(variance) fit, in
[0, 1]. Well below ~0.9 means the log-linear model — and any
extrapolation from it — should not be trusted. nan for fewer than
two points.
skipped : dict[int, str]
Feature counts that could not be measured, mapped to the reason.
concentration_ratios
property
¶
The measured concentration_ratio at each width.
offdiagonal_variances
property
¶
The measured off-diagonal variance at each width.
offdiagonal_means
property
¶
The measured mean off-diagonal entry at each width.
concentration_horizon
¶
Smallest qubit count at which the kernel reaches the Haar floor.
Resolution order:
- If the measured widths show a sustained crossing — some width
from which every wider measured point is also below
threshold— the smallest such width is returned. No extrapolation involved. The crossing must be sustained: a single dip below the threshold at the narrowest width, followed by growth away from the floor, is not a horizon. - Otherwise, if :attr:
haar_normalized_slopeis negative, the fit is extrapolated from the widest measured point to the first integer width whose predicted ratio falls belowthreshold. - Otherwise
None: the encoding shows no sign of approaching the floor over the measured range.
Parameters¶
threshold : float, default=:data:CONCENTRATION_THRESHOLD
concentration_ratio defining the floor. Must be positive.
Returns¶
int or None
The horizon, or None if the measurement does not support one.
Raises¶
ValueError
If threshold is not positive.
Warnings¶
Case 2 is an extrapolation from a fitted log-linear model. Check
:attr:r_squared before relying on it.
Source code in src/encoding_atlas/analysis/concentration.py
shots_per_entry_at
¶
Extrapolate the per-entry shot requirement to a wider circuit.
Propagates the mean and the variance forward with their own fitted
decay rates and re-evaluates 4 K_bar (1 - K_bar) / Var. Using both
fits matters: the relative spread of the entries is not preserved as
the circuit widens, so scaling the standard deviation with the mean
would be wrong by an exponential factor.
Parameters¶
n_qubits : int Target circuit width. Must be a positive integer. Widths at or below the measured range return the nearest measurement instead of an extrapolation.
Returns¶
float
Extrapolated shots per kernel entry, or nan if no fit exists.
Raises¶
ValueError
If n_qubits is not a positive integer.
Warnings¶
Beyond the measured range this is an extrapolation, not a measurement.
Check :attr:r_squared.
Source code in src/encoding_atlas/analysis/concentration.py
Feature-Scaling Sensitivity¶
How much the range features are scaled into changes an encoding's kernel geometry — for several encodings, more than the choice of encoding does. See Feature Scaling.
scan_feature_ranges
¶
scan_feature_ranges(encoding: BaseEncoding, X: NDArray[floating[Any]], y: NDArray[integer[Any] | floating[Any]], *, ranges: Sequence[tuple[float, float]] = DEFAULT_FEATURE_RANGES, max_samples: int = _DEFAULT_MAX_SAMPLES, centered: bool = True, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> FeatureRangeScan
Measure an encoding's kernel geometry across feature-scaling ranges.
For each candidate range the data is min-max scaled into it, the fidelity kernel is built, and both the kernel-target alignment and the kernel concentration are recorded. No training is involved.
Use it to choose a range (see :func:recommend_feature_range) and to see
how much the choice matters — :attr:FeatureRangeScan.alignment_spread
is often comparable to the spread across encodings.
Parameters¶
encoding : BaseEncoding
Encoding to scan. Built once and reused across ranges.
X : ndarray of shape (n_samples, n_features)
Unscaled feature matrix. Whatever scaling it already carries is
overwritten, so pass raw features.
y : ndarray of shape (n_samples,)
Two-class labels. Any two distinct values work.
ranges : sequence of (float, float), default=:data:DEFAULT_FEATURE_RANGES
Candidate ranges. Each must satisfy low < high.
max_samples : int, default=200
Sub-sample above this many rows. Must be at least 2.
centered : bool, default=True
Use the centred alignment (Cortes et al., 2012).
seed : int or None, default=None
Seed for sub-sampling.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector simulation backend.
Returns¶
FeatureRangeScan One measurement per range, plus the best range and the spread.
Raises¶
ValueError
If X/y are malformed or mismatched, y is not two-class,
max_samples is invalid, or any range is invalid.
Examples¶
from encoding_atlas import IQPEncoding from encoding_atlas.benchmark import get_dataset X, y = get_dataset("moons", n_samples=40, seed=0) scan = scan_feature_ranges(IQPEncoding(n_features=2, reps=2), X, y, seed=0) len(scan.results) 4
See Also¶
recommend_feature_range : Just the best range. encoding_atlas.analysis.compute_kernel_concentration : The concentration axis.
Source code in src/encoding_atlas/analysis/scaling.py
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 | |
recommend_feature_range
¶
recommend_feature_range(encoding: BaseEncoding, X: NDArray[floating[Any]], y: NDArray[integer[Any] | floating[Any]], *, ranges: Sequence[tuple[float, float]] = DEFAULT_FEATURE_RANGES, max_samples: int = _DEFAULT_MAX_SAMPLES, centered: bool = True, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> tuple[float, float]
Return the feature range that maximises alignment for encoding.
A thin wrapper over :func:scan_feature_ranges for the common case of
"just tell me what to scale into". Ties break towards the narrower range.
Parameters¶
encoding, X, y, ranges, max_samples, centered, seed, backend
As for :func:scan_feature_ranges.
Returns¶
(float, float)
The recommended (low, high). Feed it to :func:scale_to_range.
Examples¶
from encoding_atlas import IQPEncoding from encoding_atlas.benchmark import get_dataset X, y = get_dataset("moons", n_samples=40, seed=0) low, high = recommend_feature_range( ... IQPEncoding(n_features=2, reps=2), X, y, seed=0 ... ) low == 0.0 True
Source code in src/encoding_atlas/analysis/scaling.py
scale_to_range
¶
scale_to_range(X: NDArray[floating[Any]], low: float, high: float, *, reference: NDArray[floating[Any]] | None = None) -> NDArray[float64]
Min-max scale each feature of X into [low, high].
Parameters¶
X : ndarray of shape (n_samples, n_features)
Data to transform.
low, high : float
Target interval. Must satisfy low < high and both be finite.
reference : ndarray of shape (m, n_features), optional
Fit the per-feature minimum and span on this array instead of on
X. Pass the training fold here to transform a test fold without
leaking its extremes into the scaler — transformed values may then
fall outside [low, high], which is correct and matches
:class:sklearn.preprocessing.MinMaxScaler. Defaults to X.
Returns¶
ndarray of shape (n_samples, n_features)
The scaled data. Constant features map to low.
Raises¶
ValueError
If X is not 2D, the range is invalid or non-finite, or
reference has a different feature count.
Examples¶
import numpy as np X = np.array([[0.0, 10.0], [1.0, 20.0]]) np.round(scale_to_range(X, 0.0, np.pi), 4) array([[0. , 0. ], [3.1416, 3.1416]])
Source code in src/encoding_atlas/analysis/scaling.py
FeatureRangeScan
dataclass
¶
FeatureRangeScan(encoding_name: str, n_qubits: int, n_features: int, n_samples_used: int, n_samples_supplied: int, centered: bool, results: tuple[FeatureRangeResult, ...])
How an encoding's kernel geometry responds to feature scaling.
Attributes¶
encoding_name : str Class name of the scanned encoding. n_qubits, n_features : int Circuit width and input dimensionality. n_samples_used, n_samples_supplied : int Rows used after sub-sampling, and rows supplied. centered : bool Whether the centred alignment variant was used. results : tuple[FeatureRangeResult, ...] One measurement per range, in the order scanned.
best
property
¶
best: FeatureRangeResult
The range with the highest alignment.
Ties break towards the narrower range, which is the more conservative choice: it keeps the kernel further from the concentration floor.
Raises¶
RuntimeError If the scan produced no measurements.
alignment_spread
property
¶
Highest minus lowest alignment across the scanned ranges.
A large spread means the scaling choice matters as much as, or more than, the choice of encoding — so it should be reported, not assumed.
at
¶
at(low: float, high: float) -> FeatureRangeResult | None
Return the measurement for a given range, or None.
Source code in src/encoding_atlas/analysis/scaling.py
FeatureRangeResult
dataclass
¶
FeatureRangeResult(low: float, high: float, width: float, alignment: float, concentration_ratio: float, offdiagonal_mean: float, offdiagonal_variance: float)
An encoding measured at one feature-scaling range.
Attributes¶
low, high : float
The range the features were scaled into.
width : float
high - low, as a convenience for plotting against period coverage.
alignment : float
Kernel-target alignment at this range, in [-1, 1]. Higher predicts
better downstream accuracy.
concentration_ratio : float
Off-diagonal kernel variance over the Haar variance. Near 1 and with
offdiagonal_mean near 1/2**n_qubits means the kernel has
collapsed onto the Haar floor. Not monotone in the range width on its
own — see the module docstring.
offdiagonal_mean, offdiagonal_variance : float
Raw moments of the kernel's off-diagonal entries. The mean is the
cleaner read on how close the kernel is to the floor: it decreases
towards 1/2**n_qubits as the range widens.
Finite-Shot Kernel Estimation¶
sample_shot_kernel
¶
sample_shot_kernel(K: NDArray[floating[Any]], shots: int, *, seed: int | None = None) -> NDArray[floating[Any]]
Return the finite-shot estimate of an exact fidelity kernel.
Models the compute-uncompute estimator used on hardware: for each pair
(i, j) the circuit U(x_j)^dagger U(x_i)|0> is measured shots
times and the fraction of all-zeros outcomes is taken as K[i, j].
Because the all-zeros probability is K[i, j], that count is exactly
Binomial(shots, K[i, j]) — so drawing from the binomial is
statistically identical to simulating the circuits, at a fraction of the
cost.
The estimate is unbiased entrywise and symmetric, and its diagonal is
exactly 1 (the compute-uncompute circuit for x_i = x_i returns
all-zeros with certainty). It is not guaranteed to be positive
semidefinite: independent noise on each entry routinely pushes the
smallest eigenvalues negative. Project with
:func:encoding_atlas.benchmark.kernel.ensure_psd before handing it to a
precomputed-kernel estimator.
Parameters¶
K : ndarray, shape (n, n)
Exact kernel with entries in [0, 1], e.g. from
:func:compute_fidelity_kernel.
shots : int
Measurements per entry. Must be a positive integer.
seed : int or None, default=None
Seed for the binomial draws.
Returns¶
ndarray, shape (n, n)
Symmetric finite-shot estimate with unit diagonal. Entries are
multiples of 1 / shots.
Raises¶
ValueError
If shots is not a positive integer, or K is not a square 2D
matrix with entries in [0, 1].
Examples¶
import numpy as np K = np.array([[1.0, 0.5], [0.5, 1.0]]) K_hat = sample_shot_kernel(K, shots=1000, seed=0) bool(K_hat[0, 0] == 1.0 and K_hat[0, 1] == K_hat[1, 0]) True
Notes¶
The variance of each off-diagonal estimate is K (1 - K) / shots. When
the kernel has concentrated — every off-diagonal entry near 2**-n —
that noise swamps the signal unless shots grows with the Hilbert-space
dimension; see
:func:encoding_atlas.analysis.concentration.compute_kernel_concentration
for the resulting budget.
Source code in src/encoding_atlas/analysis/generalization.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | |
summarize_kernel_concentration
¶
summarize_kernel_concentration(K: NDArray[floating[Any]], n_qubits: int, *, threshold: float = CONCENTRATION_THRESHOLD) -> ConcentrationResult
Reduce an already-computed fidelity kernel to concentration statistics.
Split out from :func:compute_kernel_concentration so a caller that
already holds the kernel — screening several encodings, for instance —
gets the concentration axis without paying for a second simulation, while
both paths keep a single definition of the statistics.
Parameters¶
K : ndarray of shape (n, n)
Fidelity kernel with entries in [0, 1]. Must have at least two
rows so an off-diagonal pair exists.
n_qubits : int
Circuit width the kernel was produced at; sets the Haar reference.
threshold : float, default=2.0
concentration_ratio below which is_concentrated is set.
Returns¶
ConcentrationResult
The same statistics :func:compute_kernel_concentration returns.
Raises¶
ValueError
If K is not a square 2D matrix of size >= 2, n_qubits is not a
positive integer, or threshold is not positive.
Source code in src/encoding_atlas/analysis/concentration.py
Guide Module¶
recommend_encoding
¶
recommend_encoding(n_features: int, n_samples: int = 500, task: Literal['classification', 'regression'] = 'classification', hardware: str = 'simulator', priority: Literal['accuracy', 'trainability', 'speed', 'noise_resilience'] = 'accuracy', *, data_type: Literal['continuous', 'binary', 'discrete'] = 'continuous', symmetry: Literal['rotation', 'cyclic', 'permutation_pairs', 'general'] | None = None, trainable: bool = False, problem_structure: Literal['combinatorial', 'physics_simulation', 'time_series'] | None = None, feature_interactions: Literal['polynomial', 'custom_pauli'] | None = None) -> Recommendation
Recommend an encoding based on problem characteristics.
The recommendation is produced in two phases:
- Hard filter — encodings whose structural preconditions are not satisfied (data type, feature count, symmetry, trainability) are eliminated.
- Soft scoring — remaining candidates are scored on priority match, problem structure, task type, hardware suitability, and feature count. The highest-scoring encoding becomes the primary recommendation.
Parameters¶
n_features : int
Number of input features (must be >= 1).
n_samples : int
Number of training samples (default 500, must be >= 1).
task : {"classification", "regression"}
Machine learning task type. Classification boosts kernel-method
encodings; regression boosts universal-approximation encodings.
hardware : str
Target hardware ("simulator", "ibm", "ionq", etc.).
priority : {"accuracy", "trainability", "speed", "noise_resilience"}
Optimisation priority.
data_type : {"continuous", "binary", "discrete"}
Nature of the input features.
symmetry : {"rotation", "cyclic", "permutation_pairs", "general"} | None
Known symmetry in the data. None means no known symmetry.
trainable : bool
Whether the encoding should have learnable parameters.
problem_structure : {"combinatorial", "physics_simulation", "time_series"} | None
Domain structure of the problem.
feature_interactions : {"polynomial", "custom_pauli"} | None
Desired feature interaction type.
Returns¶
Recommendation The recommended encoding with explanation, alternatives, and confidence score.
Raises¶
ValueError If any parameter value is outside its valid set.
Source code in src/encoding_atlas/guide/recommender.py
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 | |
Recommendation
dataclass
¶
Recommendation(encoding_name: str, explanation: str, alternatives: list[str], confidence: float, scale_warning: str | None = None)
Encoding recommendation result.
Attributes¶
encoding_name : str
Canonical name of the recommended encoding (matches registry keys).
explanation : str
Human-readable rationale for the recommendation.
alternatives : list[str]
Up to three runner-up encoding names, ranked by score.
confidence : float
Confidence in the recommendation, in [0, 1].
scale_warning : str or None
Set when the recommended encoding's fidelity kernel is measured to sit
at the Haar floor at this feature count — meaning a quantum-kernel
method built on it has no geometry left to learn from, and the shot
cost per kernel entry grows exponentially with circuit width. None
when no such warning applies. Kept out of explanation so callers
that format the rationale are unaffected; see
:mod:encoding_atlas.atlas.concentration for the underlying scan.
get_matching_encodings
¶
get_matching_encodings(requirements: list[str], constraints: list[str] | None = None, *, n_features: int | None = None, data_type: str = 'continuous', symmetry: str | None = None, trainable: bool = False) -> list[str]
Get encodings matching requirements, constraints, and hard filters.
This performs a two-phase check for each encoding:
- Hard filter — eliminates encodings whose structural preconditions are not met (data type, feature count, symmetry, trainability).
- Soft match — among survivors, selects those whose
best_fortags overlap with requirements and whoseavoid_whentags do not overlap with constraints.
Parameters¶
requirements : list[str]
Tags the encoding should be good at (matched against best_for).
constraints : list[str] | None
Tags the encoding should not be associated with (matched against
avoid_when). None means no soft constraints.
n_features : int | None
Number of input features for hard-filter checks.
data_type : str
Data type for hard-filter checks.
symmetry : str | None
Symmetry type for hard-filter checks.
trainable : bool
Trainable flag for hard-filter checks.
Returns¶
list[str] Encoding names that pass both hard and soft filters.
Source code in src/encoding_atlas/guide/rules.py
Data-Driven Screening¶
Rank candidate encodings by kernel-target alignment measured on your own dataset — the training-free predictor of kernel accuracy. See Screening on Your Data.
screen_encodings
¶
screen_encodings(X: NDArray[floating[Any]], y: NDArray[integer[Any] | floating[Any]], *, candidates: Sequence[str] | None = None, max_samples: int = _DEFAULT_MAX_SAMPLES, centered: bool = True, include_concentration: bool = False, feature_ranges: Sequence[tuple[float, float]] | None = None, shots: int | None = None, seed: int | None = None, backend: Literal['pennylane', 'qiskit', 'cirq'] = 'pennylane') -> ScreeningResult
Rank candidate encodings by kernel-target alignment on your data.
Builds each candidate at X.shape[1] features, computes its fidelity
kernel on the data, and scores it by alignment with the labels — no
training. Encodings that cannot be built at this width (SO(2) equivariance
needs exactly two features, the swap-equivariant map an even count) are
reported in :attr:ScreeningResult.skipped rather than raising.
Use the result as a shortlist: train the top few and compare them properly. The top-1 pick alone is not reliably better than the library's metadata recommendation — see the module docstring for the measured numbers.
Parameters¶
X : ndarray of shape (n_samples, n_features)
Feature matrix. Scale it as you would before training; alignment
depends on the input distribution, not just the circuit.
y : ndarray of shape (n_samples,)
Two-class labels. Any two distinct values work — {0, 1},
{1, 2} and {-1, +1} all describe the same split and score the
same. Multi-class and continuous targets are rejected.
candidates : sequence of str, optional
Restrict to these encodings. Defaults to all 16 benchmarked ones, at
the benchmark's own parameters.
max_samples : int, default=200
Sub-sample (stratified, seeded) above this many rows to keep the
screen fast. Must be at least 2.
centered : bool, default=True
Use the centred alignment (Cortes et al., 2012). This is the variant
the benchmark validated; leave it on unless you have a reason not to.
include_concentration : bool, default=False
Also annotate each candidate with its kernel concentration on your
data. Computed from the same kernel, so it costs no extra simulation.
It does not affect the ranking.
feature_ranges : sequence of (float, float), optional
Also sweep the range the features are min-max scaled into, ranking
over (encoding, range) pairs. The range is not a minor knob: for
several encodings it moves accuracy further than the choice of
encoding does, because a full 2*pi sweep drives the kernel onto
its concentration floor. Pass
:data:encoding_atlas.analysis.DEFAULT_FEATURE_RANGES for the usual
sweep. When None (default) the data is used exactly as supplied.
shots : int or None, default=None
Screen on finite-shot kernel estimates instead of exact ones, to see
what a device would report. Alignment aggregates over all pairs and so
tolerates shot noise well.
seed : int or None, default=None
Seed for sub-sampling and shot sampling. Fixes the whole screen.
backend : {"pennylane", "qiskit", "cirq"}, default="pennylane"
Statevector simulation backend.
Returns¶
ScreeningResult Ranked candidates plus the reasons any were skipped.
Raises¶
ValueError
If X/y are malformed or mismatched, y is not two-class,
max_samples is invalid, or candidates names an unknown
encoding.
Examples¶
from encoding_atlas.benchmark import get_dataset from encoding_atlas.guide import screen_encodings X, y = get_dataset("moons", n_samples=60, seed=0) result = screen_encodings(X, y, seed=0) shortlist = result.names(3) len(shortlist) 3
See Also¶
encoding_atlas.guide.recommend_encoding : Metadata-based recommendation. encoding_atlas.analysis.compute_kernel_target_alignment : The score. encoding_atlas.benchmark.evaluate_encoding : Train the shortlist.
Source code in src/encoding_atlas/guide/screening.py
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 | |
ScreeningResult
dataclass
¶
ScreeningResult(candidates: tuple[ScreenedEncoding, ...], skipped: Mapping[str, str], n_samples_used: int, n_samples_supplied: int, n_features: int, centered: bool, shots: int | None, feature_ranges: tuple[tuple[float, float], ...] | None = None)
Ranked outcome of screening candidate encodings on a dataset.
Attributes¶
candidates : tuple[ScreenedEncoding, ...]
Successfully scored encodings, highest alignment first. Ties break on
name so the order is deterministic.
skipped : Mapping[str, str]
Encodings that could not be built or scored at this feature count,
mapped to the reason.
n_samples_used : int
Rows actually used, after any sub-sampling.
n_samples_supplied : int
Rows supplied by the caller.
n_features : int
Feature count screened at.
centered : bool
Whether the centred alignment variant was used.
shots : int or None
Shot budget per kernel entry, or None for exact kernels.
feature_ranges : tuple[tuple[float, float], ...] or None
Ranges swept, or None if the data was used as supplied.
top
¶
top(k: int = _DEFAULT_TOP_K) -> list[ScreenedEncoding]
Return the k highest-aligned candidates.
Defaults to three, the shortlist size the benchmark supports: it
captures nearly all achievable accuracy, whereas the single top pick
is no better than the angle default. See the module docstring.
Parameters¶
k : int, default=3 Shortlist size. Must be a positive integer. Values beyond the number of scored candidates return all of them.
Returns¶
list[ScreenedEncoding]
Raises¶
ValueError
If k is not a positive integer.
Source code in src/encoding_atlas/guide/screening.py
names
¶
Return candidate names in ranked order, optionally the top k.
best
¶
best() -> ScreenedEncoding
Return the highest-aligned candidate.
Raises¶
RuntimeError If no candidate could be scored.
Source code in src/encoding_atlas/guide/screening.py
get
¶
get(name: str) -> ScreenedEncoding | None
Return the scored candidate called name, or None.
Source code in src/encoding_atlas/guide/screening.py
ScreenedEncoding
dataclass
¶
ScreenedEncoding(name: str, display_name: str, encoding: BaseEncoding, params: Mapping[str, Any], n_qubits: int, alignment: float, rank: int, feature_range: tuple[float, float] | None, atlas_rank: int | None, atlas_alignment: float | None, concentration_ratio: float | None, is_concentrated: bool | None)
One candidate encoding, scored on the user's data.
Attributes¶
name : str
Canonical encoding name, e.g. "angle".
display_name : str
Class name of the built encoding, e.g. "AngleEncoding".
encoding : BaseEncoding
The built instance, ready to train with — no need to rebuild it.
params : Mapping[str, Any]
Fixed parameters used, matching the benchmark's configuration.
n_qubits : int
Circuit width at the caller's feature count.
alignment : float
Kernel-target alignment on the supplied data, in [-1, 1]. Centred
by default. This is the ranking key.
rank : int
1-based position in the screen, 1 = highest alignment.
atlas_rank : int or None
The encoding's overall benchmark rank, or None if not benchmarked.
atlas_alignment : float or None
Mean alignment the benchmark measured, for reference. A large gap
against :attr:alignment means your data behaves differently from the
benchmark's, which is exactly when screening pays off.
feature_range : tuple[float, float] or None
The range the features were scaled into for this measurement, when
feature_ranges was supplied. None means the data was used as
given. Candidates are ranked across (encoding, range) pairs, so the
same encoding can appear more than once at different ranges.
concentration_ratio : float or None
Kernel variance relative to the Haar floor on your data, when
include_concentration=True. Near 1 means the kernel carries no
usable geometry at this width.
is_concentrated : bool or None
Whether concentration_ratio fell below the concentration
threshold. None when concentration was not requested.
Benchmark Module¶
Evaluate encodings on classification tasks with variational quantum classifiers and quantum-kernel SVMs, paired stratified cross-validation, classical baselines, and statistical comparison (Wilcoxon + Holm–Bonferroni + Cliff's delta).
All four estimators follow scikit-learn's estimator contract, so they compose
with cross_val_score, GridSearchCV, Pipeline, VotingClassifier and
other meta-estimators. Hyper-parameters are validated at fit time rather than
in the constructor, as scikit-learn requires so that clone and set_params
can rebuild an estimator from get_params().
EncodingBenchmark
¶
EncodingBenchmark(encodings: list[BaseEncoding], datasets: list[str], n_runs: int = 10, seed: int | None = None, *, methods: tuple[str, ...] = ('vqc', 'kernel'), task: str = 'classification', n_folds: int = 5, baselines: tuple[str, ...] = (), custom_datasets: dict[str, tuple[Any, Any]] | None = None, n_samples: int = 200, vqc_layers: int = 2, vqc_epochs: int = 30, vqc_lr: float = 0.05, kernel_C: float = 1.0, kernel_alpha: float = 1.0, scale_range: tuple[float, float] = _DEFAULT_SCALE)
Framework for benchmarking encodings on classification datasets.
Parameters¶
encodings : list[BaseEncoding]
Encodings to benchmark. Each must accept the datasets' feature count.
datasets : list[str]
Built-in dataset names (see
:func:encoding_atlas.benchmark.list_datasets).
n_runs : int, default=10
Independent repetitions per configuration (each a different CV split).
seed : int or None, default=None
Base random seed (None -> 0).
methods : tuple, default=("vqc", "kernel")
Quantum methods to evaluate; subset of ("vqc", "kernel").
task : {"classification", "regression"}, default="classification"
Learning task. Regression draws from the regression dataset registry,
uses K-fold splits, and reports R^2.
n_folds : int, default=5
CV folds per run.
baselines : tuple, default=()
Classical baseline names for calibration — classifier names (e.g.
("svm_rbf",)) for classification, regressor names (e.g.
("svr_rbf",)) for regression.
custom_datasets : dict or None, default=None
Optional mapping name -> (X, y) of user-provided datasets, evaluated
alongside the named datasets.
n_samples : int, default=200
Sample count requested from the built-in dataset generators.
vqc_layers, vqc_epochs, vqc_lr, kernel_C, scale_range
Method hyper-parameters (see :func:evaluate_encoding).
Notes¶
Cost scales as len(encodings) x len(datasets) x len(methods) x n_runs x
n_folds fold evaluations (VQC folds train a circuit and are the expensive
part). Start small.
Source code in src/encoding_atlas/benchmark/runner.py
run
¶
Run the full benchmark and return a structured results dictionary.
Returns¶
dict
{"config", "encodings", "datasets", "results", "baselines"}.
results[method][label][dataset] holds the accuracy summary;
mismatched (encoding, dataset) feature counts are recorded with
status="skipped".
Source code in src/encoding_atlas/benchmark/runner.py
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 | |
statistical_tests
¶
Pairwise encoding comparison per (method, dataset).
Runs :func:compare_encodings_corrected (Wilcoxon + Holm-Bonferroni +
Cliff's delta) on the paired per-fold scores. Requires :meth:run to
have been called first.
Returns¶
dict
{f"{method}/{dataset}": {<corrected comparison>}}.
Source code in src/encoding_atlas/benchmark/runner.py
plot_comparison
¶
Return a matplotlib bar chart of mean accuracy per encoding/dataset.
Parameters¶
method : str or None Which method to plot. Defaults to the first configured method.
Raises¶
RuntimeError
If :meth:run has not been called.
ImportError
If matplotlib is not installed.
Source code in src/encoding_atlas/benchmark/runner.py
save_results
¶
Write the benchmark results to path as JSON.
Raises¶
RuntimeError
If :meth:run has not been called.
Source code in src/encoding_atlas/benchmark/runner.py
evaluate_encoding
¶
evaluate_encoding(encoding: Any, X: NDArray[floating[Any]], y: NDArray[intp], *, method: str = 'kernel', task: str = 'classification', n_runs: int = 1, n_folds: int = 5, seed: int = 42, scale: bool = True, scale_range: tuple[float, float] = _DEFAULT_SCALE, vqc_layers: int = 2, vqc_epochs: int = 30, vqc_lr: float = 0.05, kernel_C: float = 1.0, kernel_alpha: float = 1.0) -> dict[str, Any]
Evaluate one encoding on a single dataset via cross-validation.
This is the entry point for benchmarking on custom data: pass any feature
matrix X with either class labels or continuous targets y.
Parameters¶
encoding : BaseEncoding
Encoding whose n_features must equal X.shape[1].
X, y : ndarray
Feature matrix and targets — class labels for task="classification"
or continuous values for task="regression".
method : {"vqc", "kernel"}, default="kernel"
Quantum model family.
task : {"classification", "regression"}, default="classification"
Learning task. Classification reports accuracy and uses stratified
folds; regression reports R^2 and uses plain K-fold.
n_runs : int, default=1
Independent repetitions, each with a different CV split seed.
n_folds : int, default=5
CV folds per run.
seed : int, default=42
Base random seed.
scale : bool, default=True
Min-max scale features into scale_range before encoding.
scale_range : tuple, default=(0, 2*pi)
Target range for feature scaling.
vqc_layers, vqc_epochs, vqc_lr : int, int, float
VQC ansatz depth, training epochs, and learning rate.
kernel_C : float, default=1.0
SVM regularisation (classification kernel method).
kernel_alpha : float, default=1.0
Ridge regularisation (regression kernel method).
Returns¶
dict
{"method", "task", "score_metric", "scores", "n_failed", ...summary}
where the summary keys are
mean/std/ci_low/ci_high/n_scores. The score is
accuracy for classification and R^2 for regression.
Raises¶
ValueError
If method/task is invalid or the encoding's feature count does
not match X.
Source code in src/encoding_atlas/benchmark/runner.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | |
VQCClassifier
¶
VQCClassifier(encoding: Any = None, n_var_layers: int = 2, lr: float = 0.05, epochs: int = 30, seed: int | None = None)
Bases: ClassifierMixin, BaseEstimator
Variational quantum classifier with a configurable encoding.
Architecture::
|0> -- Encoding(x) -- [RY(theta) + CNOT chain] x L -- <Z_0> -- class
Parameters¶
encoding : BaseEncoding
The quantum encoding to benchmark. Must expose n_qubits and a
get_circuit(x, backend="pennylane") method.
n_var_layers : int, default=2
Number of variational layers after the encoding.
lr : float, default=0.05
Adam learning rate.
epochs : int, default=30
Number of training epochs.
seed : int or None, default=None
Random seed for parameter initialisation and shuffling.
Follows scikit-learn's estimator contract, so it composes with
cross_val_score, GridSearchCV, Pipeline and other
meta-estimators. Hyper-parameters are validated at fit time rather than
in the constructor; see :mod:encoding_atlas.benchmark._estimator.
Attributes¶
params_ : ndarray
Trained variational parameters of shape (n_var_layers, n_qubits).
Set by fit; absent before it.
loss_history_ : list[float]
Mean training loss per epoch.
status_ : str
"success" or "diverged". Set by fit.
classes_ : ndarray
Class labels seen during fit.
n_features_in_ : int
Feature count seen during fit.
Examples¶
from sklearn.model_selection import GridSearchCV from encoding_atlas import AngleEncoding from encoding_atlas.benchmark import get_dataset X, y = get_dataset("moons", n_samples=24, seed=0) search = GridSearchCV( ... VQCClassifier(AngleEncoding(n_features=2), epochs=2), ... {"n_var_layers": [1, 2]}, ... cv=2, ... ).fit(X, y) search.best_params_["n_var_layers"] in (1, 2) True
Source code in src/encoding_atlas/benchmark/vqc.py
fit
¶
fit(X: NDArray[floating[Any]], y: NDArray[intp]) -> VQCClassifier
Train on X and labels y.
Two-class problems (labels in {0, 1}) train a single VQC that reads
out <Z_0>. Problems with more than two classes train a one-vs-rest
ensemble of binary VQCs.
Source code in src/encoding_atlas/benchmark/vqc.py
predict
¶
Return predicted labels (in {0, 1} for binary tasks).
Source code in src/encoding_atlas/benchmark/vqc.py
QuantumKernelClassifier
¶
Bases: ClassifierMixin, BaseEstimator
Fidelity-kernel SVM classifier for a fixed encoding.
Computes the quantum kernel for the training data, enforces PSD, and fits a
precomputed-kernel :class:sklearn.svm.SVC. Prediction uses the test/train
cross kernel.
Follows scikit-learn's estimator contract, so it composes with
cross_val_score, GridSearchCV, Pipeline and other
meta-estimators. As there, hyper-parameters are validated at fit time
rather than in the constructor; see
:mod:encoding_atlas.benchmark._estimator.
Parameters¶
encoding : BaseEncoding Encoding used for state preparation. Fixes the accepted feature count. C : float, default=1.0 SVM regularisation strength. Must be positive. seed : int or None, default=None Seed forwarded to the SVM for reproducibility.
Attributes¶
classes_ : ndarray
Class labels seen during fit.
n_features_in_ : int
Feature count seen during fit.
kernel_regularized_ : bool
Whether the training kernel needed PSD projection.
Examples¶
from sklearn.model_selection import cross_val_score from encoding_atlas import AngleEncoding from encoding_atlas.benchmark import get_dataset X, y = get_dataset("moons", n_samples=30, seed=0) clf = QuantumKernelClassifier(AngleEncoding(n_features=2)) scores = cross_val_score(clf, X, y, cv=3) len(scores) 3
Source code in src/encoding_atlas/benchmark/kernel.py
fit
¶
fit(X: NDArray[floating[Any]], y: NDArray[intp]) -> QuantumKernelClassifier
Fit the precomputed-kernel SVM on (X, y).
Raises¶
ValueError
If C is not positive, encoding is unset, or X/y are
malformed or mismatched.
Source code in src/encoding_atlas/benchmark/kernel.py
predict
¶
Predict labels for X using the test/train cross kernel.
Source code in src/encoding_atlas/benchmark/kernel.py
decision_function
¶
Signed distance to the separating hyperplane in feature space.
Exposed so the classifier works with threshold-based metrics such as ROC-AUC, and with probability calibration.
Source code in src/encoding_atlas/benchmark/kernel.py
VQCRegressor
¶
VQCRegressor(encoding: Any = None, n_var_layers: int = 2, lr: float = 0.05, epochs: int = 30, seed: int | None = None)
Bases: RegressorMixin, BaseEstimator
Variational quantum regressor with a configurable encoding.
Uses the same circuit as :class:VQCClassifier but reads <Z_0> as a
continuous prediction and trains with mean-squared error. Targets are
min-max scaled into [-1, 1] (the range of <Z_0>) using training
statistics only, and predictions are mapped back to the original scale.
Parameters¶
encoding : BaseEncoding The quantum encoding to benchmark. n_var_layers : int, default=2 Number of variational layers after the encoding. lr : float, default=0.05 Adam learning rate. epochs : int, default=30 Number of training epochs. seed : int or None, default=None Random seed for parameter initialisation and shuffling.
Attributes¶
params_ : ndarray
Trained variational parameters. Set by fit; absent before it.
loss_history_ : list[float]
Mean training loss per epoch.
status_ : str
"success" or "diverged". Set by fit.
n_features_in_ : int
Feature count seen during fit.
Source code in src/encoding_atlas/benchmark/vqc.py
fit
¶
fit(X: NDArray[floating[Any]], y: NDArray[floating[Any]]) -> VQCRegressor
Train on X and continuous targets y using MSE loss.
Source code in src/encoding_atlas/benchmark/vqc.py
predict
¶
Predict continuous targets on the original target scale.
Source code in src/encoding_atlas/benchmark/vqc.py
QuantumKernelRegressor
¶
Bases: RegressorMixin, BaseEstimator
Fidelity-kernel ridge regressor for a fixed encoding.
Computes the quantum kernel for the training data, enforces PSD, and fits a
precomputed-kernel :class:sklearn.kernel_ridge.KernelRidge. Prediction
uses the test/train cross kernel.
Follows scikit-learn's estimator contract; score returns R^2 through
:class:sklearn.base.RegressorMixin.
Parameters¶
encoding : BaseEncoding Encoding used for state preparation. alpha : float, default=1.0 Ridge regularisation strength. Must be positive.
Attributes¶
n_features_in_ : int
Feature count seen during fit.
kernel_regularized_ : bool
Whether the training kernel needed PSD projection.
Source code in src/encoding_atlas/benchmark/kernel.py
fit
¶
fit(X: NDArray[floating[Any]], y: NDArray[floating[Any]]) -> QuantumKernelRegressor
Fit the precomputed-kernel ridge regressor on (X, y).
Raises¶
ValueError
If alpha is not positive, encoding is unset, or X/y
are malformed or mismatched.
Source code in src/encoding_atlas/benchmark/kernel.py
predict
¶
Predict continuous targets using the test/train cross kernel.
Source code in src/encoding_atlas/benchmark/kernel.py
compute_kernel_matrix
¶
compute_kernel_matrix(encoding: BaseEncoding, X: NDArray[floating[Any]], *, backend: str = 'pennylane', return_states: bool = False) -> Any
Compute the symmetric n x n fidelity kernel matrix for X.
Statevectors are simulated once per sample and reused for all pairwise
fidelities. When return_states=True a (K, states) tuple is returned
so the states can be reused for the cross kernel.
Source code in src/encoding_atlas/benchmark/kernel.py
compare_encodings_corrected
¶
compare_encodings_corrected(results: dict[str, list[float]], *, alpha: float = 0.05) -> dict[str, Any]
Full pairwise comparison: Wilcoxon + Holm-Bonferroni + Cliff's delta.
Parameters¶
results : dict
Mapping of encoding name -> list of paired per-fold/run scores. All
lists must have equal length (paired across the same CV splits).
alpha : float, default=0.05
Family-wise significance level used to flag significant pairs.
Returns¶
dict
{"n_comparisons": int, "alpha": float, "pairs": [...]} where each
pair record holds the two encodings, raw and corrected p-values,
Wilcoxon statistic, Cliff's delta with magnitude, the mean-score
difference, and a significant boolean (corrected p < alpha).
Source code in src/encoding_atlas/benchmark/statistical.py
Atlas Module¶
The empirical benchmark results — measured circuit resources, simulability, expressibility, entanglement, trainability, noise resilience, and downstream VQC / quantum-kernel accuracy for all 16 encodings — bundled with the package as a queryable, read-only API.
get_encoding_profile
¶
get_encoding_profile(name: str) -> EncodingProfile
Return the measured profile for a single encoding.
Parameters¶
name : str
Encoding identifier. Canonical names ("qaoa"), dataset aliases
("qaoa_encoding"), and class display names ("QAOAEncoding") are
all accepted, case-insensitively.
Returns¶
EncodingProfile The encoding's empirical profile.
Raises¶
KeyError
If name does not correspond to any benchmarked encoding.
Source code in src/encoding_atlas/atlas/profiles.py
rank_encodings
¶
rank_encodings(by: str = _SCORE_KEY, *, ascending: bool | None = None, limit: int | None = None) -> list[EncodingProfile]
Rank encodings by a measured metric or by the composite score.
Parameters¶
by : str
Ranking key: "score", "rank", or any scalar metric from
:func:list_metrics (e.g. "kernel_accuracy", "depth").
ascending : bool or None
Sort direction. If None (default), a sensible direction is chosen
per key: ascending for "rank" and "depth" (lower is better),
descending otherwise (higher is better).
limit : int or None
If given, return at most this many profiles.
Returns¶
list[EncodingProfile]
Profiles sorted as requested. Encodings whose value for by is
undefined (None) are omitted from metric rankings.
Raises¶
ValueError
If by is not a recognised ranking key, or limit is negative.
Source code in src/encoding_atlas/atlas/profiles.py
pareto_front
¶
pareto_front() -> list[EncodingProfile]
Return the Pareto-optimal encoding profiles, ordered by rank.
These are the encodings not dominated on the benchmark objectives (accuracy, inverse depth, trainability, noise resilience).
Source code in src/encoding_atlas/atlas/profiles.py
hypothesis_verdicts
¶
Return the pre-registered hypothesis outcomes (H1–H7).
Each verdict carries its verdict ("supported" / "refuted" /
"inconclusive"), confidence, a plain-language evidence summary,
and the supporting test_statistic. A deep copy is returned so callers
cannot mutate the cached dataset.
Source code in src/encoding_atlas/atlas/profiles.py
atlas_metadata
¶
Return provenance and summary metadata for the bundled atlas.
Returns¶
dict
Schema version, encoding count, benchmark objective names, Pareto-front
size, per-stage success counts, and a human-readable source string.
Source code in src/encoding_atlas/atlas/profiles.py
EncodingProfile
dataclass
¶
EncodingProfile(name: str, display_name: str, family: str, rank: int, score: float, is_pareto: bool, is_simulable: bool, metrics: Mapping[str, Any])
Measured empirical profile of a single encoding.
Attributes¶
name : str
Canonical (registry-primary) encoding name, e.g. "qaoa".
display_name : str
Human-friendly class name, e.g. "QAOAEncoding".
family : str
Taxonomic family the encoding belongs to.
rank : int
Overall benchmark rank (1 = best) by the composite trade-off score.
score : float
Composite trade-off score in [0, 1] (higher is better).
is_pareto : bool
Whether the encoding lies on the Pareto front across the benchmark
objectives (accuracy, inverse depth, trainability, noise resilience).
is_simulable : bool
Whether the encoding is classically efficiently simulable.
metrics : Mapping[str, Any]
Read-only mapping of measured metrics. Scalar keys are listed by
:func:list_metrics; vqc_ci and kernel_ci hold 95% confidence
intervals as [low, high] pairs. Some entries may be None when a
metric is not defined for that encoding (e.g. expressibility for
basis/amplitude state-preparation circuits).
``kernel_target_alignment`` is the mean *centered* alignment over the
benchmark's (configuration, dataset) pairs — the training-free quantity
that predicts kernel accuracy. To measure it on your own data instead,
see :func:`encoding_atlas.guide.screen_encodings`.
metric
¶
Return a measured metric value, or default if absent/undefined.
Parameters¶
key : str
Metric name (see :func:list_metrics).
default : Any
Value to return if the metric is missing or recorded as None.
Source code in src/encoding_atlas/atlas/profiles.py
Kernel-Concentration Scan¶
The companion dataset recording how each encoding's fidelity kernel approaches the Haar floor across 2–8 qubits. The accuracy numbers above were measured at 2–4 qubits, so this is the axis that says whether they transfer.
get_concentration_profile
¶
get_concentration_profile(name: str) -> ConcentrationProfile
Return the measured concentration profile for a single encoding.
Parameters¶
name : str
Encoding identifier. Canonical names ("qaoa"), dataset aliases
("qaoa_encoding"), and class display names ("QAOAEncoding")
are all accepted, case-insensitively.
Returns¶
ConcentrationProfile The encoding's measured concentration scaling.
Raises¶
KeyError
If name does not correspond to any scanned encoding.
Examples¶
profile = get_concentration_profile("iqp") profile.horizon 2
Source code in src/encoding_atlas/atlas/concentration.py
list_concentration_profiles
¶
list_concentration_profiles() -> list[ConcentrationProfile]
Return all concentration profiles, ordered by canonical name.
concentrated_encodings
¶
concentrated_encodings(*, threshold: float = _DEFAULT_THRESHOLD) -> list[ConcentrationProfile]
Return the encodings whose kernel reaches the Haar floor.
These are the encodings for which a fidelity-kernel method stops working as the circuit widens, regardless of shot budget or noise level.
Parameters¶
threshold : float, default=2.0
concentration_ratio defining the floor. An encoding qualifies when
its widest measured point falls below this. Must be positive.
Returns¶
list[ConcentrationProfile] Matching profiles, ordered by canonical name.
Raises¶
ValueError
If threshold is not positive.
Examples¶
sorted(p.name for p in concentrated_encodings()) ['hamiltonian', 'iqp', 'pauli_feature_map', 'zz_feature_map']
Source code in src/encoding_atlas/atlas/concentration.py
concentration_metadata
¶
Return provenance and the measurement protocol for the scan.
Returns¶
dict
Schema version, encoding count, the full measurement protocol
(feature counts, sample count, input range, sampling strategy,
threshold, seed, backend), and a human-readable source string.
Source code in src/encoding_atlas/atlas/concentration.py
ConcentrationProfile
dataclass
¶
ConcentrationProfile(name: str, display_name: str, params: Mapping[str, Any], points: tuple[ConcentrationPoint, ...], decay_rate: float | None, mean_decay_rate: float | None, haar_normalized_slope: float | None, r_squared: float | None, horizon: int | None, skipped: Mapping[str, str])
Measured kernel-concentration scaling for a single encoding.
Attributes¶
name : str
Canonical (registry-primary) encoding name, e.g. "qaoa".
display_name : str
Human-friendly class name, e.g. "QAOAEncoding".
params : Mapping[str, Any]
Fixed circuit parameters the scan used (matching the benchmark's
kernel stage), e.g. {"reps": 2}.
points : tuple[ConcentrationPoint, ...]
Per-width measurements, ordered by circuit width.
decay_rate : float or None
Per-qubit factor by which the off-diagonal variance shrinks. The
Haar floor itself falls at 4.0, so a rate near 4 means the encoding
scrambles as fast as it can. None if unfittable (too few widths).
mean_decay_rate : float or None
The same for the off-diagonal mean; the Haar floor falls at 2.0.
haar_normalized_slope : float or None
Slope of log(concentration_ratio) against qubit count. Negative
means the kernel is collapsing towards the floor as the circuit
widens; positive means it is pulling away.
r_squared : float or None
Quality of the log-linear variance fit, in [0, 1].
horizon : int or None
Narrowest measured width from which the encoding stays at the Haar
floor, or an extrapolation when the trend points there. None means
the encoding shows no sign of reaching the floor over 2-8 qubits.
skipped : Mapping[str, str]
Feature counts the scan could not measure (an encoding requiring
exactly two features, or an even count), mapped to the reason.
at_features
¶
at_features(n_features: int) -> ConcentrationPoint
Return the measured point closest to n_features.
The scan measured a fixed grid of feature counts, so this snaps to the nearest measured configuration rather than interpolating. Ties break towards the wider circuit, which is the conservative direction for a concentration question.
Parameters¶
n_features : int Feature count of interest. Must be a positive integer.
Returns¶
ConcentrationPoint The nearest measured point.
Raises¶
ValueError
If n_features is not a positive integer.
RuntimeError
If this profile has no measured points at all.
Source code in src/encoding_atlas/atlas/concentration.py
is_concentrated_at
¶
Whether the kernel is at the Haar floor at roughly n_features.
Uses the nearest measured point (see :meth:at_features).
Parameters¶
n_features : int
Feature count of interest.
threshold : float, default=2.0
concentration_ratio defining the floor. Must be positive.
Returns¶
bool
True when the nearest measured concentration_ratio falls
below threshold.
Raises¶
ValueError
If threshold is not positive, or n_features is invalid.
Source code in src/encoding_atlas/atlas/concentration.py
ConcentrationPoint
dataclass
¶
ConcentrationPoint(n_features: int, n_qubits: int, concentration_ratio: float, mean_ratio: float, offdiagonal_mean: float, offdiagonal_variance: float, shots_per_entry: float, is_concentrated: bool)
One measured (width, concentration) point for an encoding.
Attributes¶
n_features : int
Feature count the encoding was built with.
n_qubits : int
Resulting circuit width. Equal to n_features for most encodings;
ceil(log2(n_features)) for amplitude encoding.
concentration_ratio : float
Off-diagonal kernel variance divided by the Haar variance at this
width. Near 1 means the kernel is at the floor.
mean_ratio : float
Off-diagonal kernel mean divided by the Haar mean. Reported for
completeness only — it is 1 for any ensemble of independent random
single-qubit states as well as for Haar, so it cannot discriminate.
offdiagonal_mean, offdiagonal_variance : float
The raw measured moments of the off-diagonal entries.
shots_per_entry : float
Shots per kernel entry needed for the compute-uncompute estimator's
standard error to fall below half the measured spread. A noiseless
lower bound on hardware cost.
is_concentrated : bool
Whether concentration_ratio was below the scan's threshold.
Feature-Scaling Sensitivity Scan¶
What the feature range costs each encoding, and how far the study's own expressibility-accuracy correlation moves with that choice.
get_scaling_profile
¶
get_scaling_profile(name: str) -> ScalingProfile
Return the measured scaling sensitivity for a single encoding.
Parameters¶
name : str Encoding identifier. Canonical names, dataset aliases, and class display names are all accepted, case-insensitively.
Returns¶
ScalingProfile
Raises¶
KeyError
If name does not correspond to any scanned encoding.
Examples¶
profile = get_scaling_profile("iqp") profile.accuracy_spread > 0.2 True
Source code in src/encoding_atlas/atlas/scaling.py
list_scaling_profiles
¶
list_scaling_profiles() -> list[ScalingProfile]
scaling_sensitive_encodings
¶
scaling_sensitive_encodings(*, threshold: float = 0.15) -> list[ScalingProfile]
Return encodings whose accuracy moves more than threshold with scaling.
These are the encodings for which the feature range is not a detail: report it, and tune it before drawing conclusions.
Parameters¶
threshold : float, default=0.15 Minimum accuracy spread across the scanned ranges. Must be non-negative.
Returns¶
list[ScalingProfile] Matching profiles, most sensitive first.
Raises¶
ValueError
If threshold is negative.
Source code in src/encoding_atlas/atlas/scaling.py
expressibility_accuracy_correlation
¶
Return the expressibility-accuracy correlation at each scaling range.
This is the scan's scientific payload: hypothesis H1 evaluated as a function of the preprocessing choice it was originally measured under.
Returns¶
list[dict]
One entry per range, ordered as scanned, each with low, high,
spearman_rho / p_value / n_encodings over every measurable
encoding, the same three over the _atlas_subset (the encodings the
atlas reports an expressibility for — the set the published analysis
used), and is_published_range. A deep copy is returned, so callers
cannot mutate the cached dataset.
Source code in src/encoding_atlas/atlas/scaling.py
scaling_metadata
¶
Return provenance and the measurement protocol for the scan.
Returns¶
dict
Schema version, encoding count, the full protocol (ranges, datasets,
sample and fold counts, seed, backend, and the pipeline's own
published_range), and a human-readable source string.
Source code in src/encoding_atlas/atlas/scaling.py
ScalingProfile
dataclass
¶
ScalingProfile(name: str, display_name: str, params: Mapping[str, Any], points: tuple[ScalingPoint, ...], best_range: tuple[float, float], accuracy_spread: float)
Measured feature-scaling sensitivity for a single encoding.
Attributes¶
name : str Canonical (registry-primary) encoding name. display_name : str Human-friendly class name. params : Mapping[str, Any] Fixed circuit parameters the scan used. points : tuple[ScalingPoint, ...] Per-range measurements, ordered by increasing range width. best_range : tuple[float, float] The range that maximised alignment. accuracy_spread : float Highest minus lowest mean accuracy across ranges. For several encodings this exceeds the spread between encodings, which is the point: scaling is not a minor knob.
published
property
¶
published: ScalingPoint | None
The measurement at the pipeline's own [0, 2*pi] range.
at_range
¶
at_range(low: float, high: float) -> ScalingPoint | None
Return the measurement for an exact range, or None.
ScalingPoint
dataclass
¶
ScalingPoint(low: float, high: float, width: float, mean_alignment: float, mean_accuracy: float, mean_concentration_ratio: float, expressibility: float | None, n_datasets: int)
One encoding measured at one feature-scaling range.
Attributes¶
low, high : float
The range the features were scaled into.
width : float
high - low; 2*pi is one full rotation period.
mean_alignment : float
Mean centred kernel-target alignment across the benchmark datasets.
mean_accuracy : float
Mean cross-validated quantum-kernel accuracy across those datasets.
mean_concentration_ratio : float
Mean kernel variance relative to the Haar floor. Falling towards 1 as
the range widens is the mechanism behind the accuracy loss.
expressibility : float or None
Expressibility re-measured with inputs drawn from this same range, so
both axes of the correlation describe one regime.
n_datasets : int
Datasets contributing to these means.
Exceptions¶
exceptions
¶
Custom exceptions for encoding_atlas.
This module defines the exception hierarchy for the encoding_atlas package.
All custom exceptions inherit from :class:EncodingError, which itself
inherits from Python's built-in :class:Exception.
Exception Hierarchy¶
::
Exception
└── EncodingError (base for all encoding_atlas exceptions)
├── ValidationError (input validation failures)
├── BackendError (quantum backend issues)
├── RegistryError (encoding registry issues)
└── AnalysisError (analysis operation failures)
├── SimulationError (circuit simulation failures)
├── ConvergenceError (iterative computation failures)
├── NumericalInstabilityError (numerical issues)
└── InsufficientSamplesError (sampling issues)
Examples¶
Catching all encoding-related exceptions:
try: ... result = some_encoding_operation() ... except EncodingError as e: ... print(f"Encoding operation failed: {e}")
Catching specific analysis exceptions:
from encoding_atlas.core.exceptions import SimulationError try: ... statevector = simulate_encoding(encoding, x) ... except SimulationError as e: ... print(f"Simulation failed: {e}")
AnalysisError
¶
Bases: EncodingError
Base exception for analysis operations.
This exception class serves as the base for all analysis-related
errors in the encoding_atlas.analysis module. It inherits from
:class:EncodingError to maintain the package's exception hierarchy.
Use this exception for general analysis failures that don't fit into more specific categories. For specific failure modes, use the specialized subclasses.
Parameters¶
message : str Human-readable description of the error. details : dict, optional Additional context about the error (e.g., parameter values, intermediate results).
Attributes¶
details : dict Additional context about the error, if provided.
Examples¶
raise AnalysisError("Analysis failed for encoding") Traceback (most recent call last): ... AnalysisError: Analysis failed for encoding
With additional details:
raise AnalysisError( ... "Invalid encoding for analysis", ... details={"encoding_type": "CustomEncoding", "n_qubits": 0} ... )
BackendError
¶
Bases: EncodingError
Raised when quantum backend operations fail.
This exception indicates a failure in the underlying quantum computing framework (PennyLane, Qiskit, or Cirq). Common causes include missing backend installations, invalid circuit operations, or backend-specific limitations.
Examples¶
raise BackendError("Qiskit backend not available") Traceback (most recent call last): ... BackendError: Qiskit backend not available
ConvergenceError
¶
ConvergenceError(message: str, iterations: int | None = None, target_tolerance: float | None = None, achieved_tolerance: float | None = None, details: dict | None = None)
Bases: AnalysisError
Raised when iterative computation fails to converge.
This exception indicates that an iterative algorithm did not reach the desired convergence criteria within the allowed iterations or time. Common causes include:
- Insufficient sample count
- Poor initial conditions
- Numerically unstable problem
- Convergence criteria too strict
Parameters¶
message : str Human-readable description of the convergence failure. iterations : int, optional Number of iterations attempted before failure. target_tolerance : float, optional The convergence tolerance that was not achieved. achieved_tolerance : float, optional The best tolerance achieved before stopping. details : dict, optional Additional context about the failure.
Attributes¶
iterations : int or None Number of iterations attempted. target_tolerance : float or None The target convergence tolerance. achieved_tolerance : float or None The best tolerance achieved. details : dict Additional context about the failure.
Examples¶
raise ConvergenceError( ... "Expressibility estimation did not converge", ... iterations=10000, ... target_tolerance=1e-4, ... achieved_tolerance=1e-2 ... )
EncodingError
¶
Bases: Exception
Base exception for all encoding_atlas errors.
This is the root exception class for the encoding_atlas package. All custom exceptions in this package inherit from this class, allowing users to catch all encoding-related errors with a single except clause.
Parameters¶
message : str Human-readable description of the error. *args : tuple Additional positional arguments passed to Exception.
Examples¶
raise EncodingError("Something went wrong") Traceback (most recent call last): ... EncodingError: Something went wrong
InsufficientSamplesError
¶
InsufficientSamplesError(message: str, requested_samples: int | None = None, minimum_samples: int | None = None, metric: str | None = None, details: dict | None = None)
Bases: AnalysisError
Raised when sample count is too low for reliable results.
This exception indicates that a statistical analysis cannot provide reliable results with the given number of samples. This is distinct from a convergence failure — here, the algorithm recognizes upfront that the sample count is inadequate.
Parameters¶
message : str Human-readable description of the sampling issue. requested_samples : int, optional The number of samples requested by the user. minimum_samples : int, optional The minimum number of samples required for reliable results. metric : str, optional The metric being computed (e.g., "expressibility"). details : dict, optional Additional context about the sampling requirements.
Attributes¶
requested_samples : int or None The number of samples requested. minimum_samples : int or None The minimum required samples. metric : str or None The metric being computed. details : dict Additional context about the failure.
Examples¶
raise InsufficientSamplesError( ... "Too few samples for expressibility estimation", ... requested_samples=10, ... minimum_samples=100, ... metric="expressibility" ... )
Notes¶
When this exception is raised, the recommended action is to
increase the sample count. The minimum_samples attribute
provides guidance on the minimum viable sample count.
NumericalInstabilityError
¶
NumericalInstabilityError(message: str, value: float | None = None, operation: str | None = None, details: dict | None = None)
Bases: AnalysisError
Raised when numerical instability is detected.
This exception indicates that a computation encountered numerical issues that compromise the accuracy or validity of results. Common causes include:
- Division by near-zero values
- Overflow or underflow in intermediate calculations
- Loss of precision in floating-point operations
- Ill-conditioned matrices
Parameters¶
message : str Human-readable description of the numerical issue. value : float, optional The problematic value that triggered the error. operation : str, optional The operation that encountered the issue. details : dict, optional Additional context about the numerical issue.
Attributes¶
value : float or None The problematic value, if applicable. operation : str or None The operation that failed, if specified. details : dict Additional context about the failure.
Examples¶
raise NumericalInstabilityError( ... "Division by near-zero in fidelity computation", ... value=1e-320, ... operation="compute_fidelity" ... )
Notes¶
This exception is distinct from Python's built-in arithmetic exceptions (ZeroDivisionError, OverflowError) because it catches cases where the computation technically succeeds but produces unreliable results due to floating-point limitations.
RegistryError
¶
Bases: EncodingError
Raised when encoding registry operations fail.
This exception indicates a failure in the encoding registration or lookup system. Common causes include duplicate registrations or requests for non-existent encodings.
Examples¶
raise RegistryError("Encoding 'CustomEncoding' not found in registry") Traceback (most recent call last): ... RegistryError: Encoding 'CustomEncoding' not found in registry
SimulationError
¶
Bases: AnalysisError
Raised when quantum circuit simulation fails.
This exception indicates that a quantum circuit could not be successfully simulated. Common causes include:
- Missing backend dependencies (PennyLane, Qiskit not installed)
- Invalid circuit structure
- Memory limitations for large qubit counts
- Backend-specific errors
Parameters¶
message : str Human-readable description of the simulation failure. backend : str, optional The backend that failed (e.g., "pennylane", "qiskit"). details : dict, optional Additional context about the failure.
Attributes¶
backend : str or None The backend that failed, if specified. details : dict Additional context about the failure.
Examples¶
raise SimulationError("PennyLane simulation failed: device error") Traceback (most recent call last): ... SimulationError: PennyLane simulation failed: device error
With backend information:
raise SimulationError( ... "Simulation failed", ... backend="qiskit", ... details={"n_qubits": 20, "error": "memory exceeded"} ... )
ValidationError
¶
Bases: EncodingError
Raised when input validation fails.
This exception indicates that input data does not meet the requirements of an encoding or analysis function. Common causes include incorrect shapes, invalid data types, or out-of-range values.
Examples¶
raise ValidationError("Expected 4 features, got 3") Traceback (most recent call last): ... ValidationError: Expected 4 features, got 3