QAOAEncoding - Complete Feature Demonstration¶
A comprehensive guide to every feature of QAOAEncoding in the encoding-atlas library.
This notebook demonstrates the full capabilities of the QAOA-style quantum data encoding, covering:
- Installation & Setup - Getting started with the library
- Core Concepts - What QAOA encoding is and why it matters
- Instantiation & Configuration - All 10 constructor parameters
- Circuit Generation - PennyLane, Qiskit, and Cirq backends
- Batch Processing - Sequential and parallel circuit generation
- Properties & Resource Analysis - Gate counts, depth, simulability
- Entanglement Topologies - Linear, circular, full, none
- Feature Mapping - Linear vs quadratic mappings
- Data Angle Computation - Understanding rotation angles
- Serialization -
to_dict,from_dict, pickle support - Copy & Variation - Creating encoding variants
- Equality & Hashing - Comparison and set/dict usage
- Protocol Support - ResourceAnalyzable, EntanglementQueryable
- Analysis Tools - Expressibility, entanglement, trainability, simulability
- Visualization & Comparison - Comparing QAOA with other encodings
- Edge Cases & Validation - Input validation, warnings, error handling
- Advanced Usage - Logging, thread safety, hardware considerations
- End-to-End Workflow - Complete ML pipeline example
Mathematical Background:
The QAOA-style encoding creates quantum states through alternating layers inspired by the Quantum Approximate Optimization Algorithm:
$$|\psi(x)\rangle = \prod_{p=1}^{\text{reps}} U_M(\beta) \, U_C(\gamma, x) \, |+\rangle^{\otimes n}$$
where:
- $U_C(\gamma, x) = \prod_i R_{\text{data}}(\gamma \cdot x_i)$ applies data-dependent rotations
- $U_M(\beta) = U_{\text{entangle}} \cdot \prod_i R_{\text{mixer}}(\beta)$ applies mixing operations
- $|+\rangle^{\otimes n}$ is the uniform superposition state (when
include_initial_h=True)
References:
- Farhi, E., Goldstone, J., & Gutmann, S. (2014). "A Quantum Approximate Optimization Algorithm." arXiv:1411.4028.
- Hadfield, S., et al. (2019). "From the Quantum Approximate Optimization Algorithm to a Quantum Alternating Operator Ansatz."
- Lloyd, S., et al. (2020). "Quantum embeddings for machine learning." arXiv:2001.03622.
1. Installation & Setup¶
# Install from PyPI
# pip install encoding-atlas
# For specific backend support:
# pip install encoding-atlas[qiskit]
# pip install encoding-atlas[cirq]
# pip install encoding-atlas[all] # All backends
import numpy as np
import warnings
warnings.filterwarnings('ignore') # Clean output for the guide
import encoding_atlas
print(f"encoding-atlas version: {encoding_atlas.__version__}")
encoding-atlas version: 0.2.0
# Check available backends
from encoding_atlas.backends._detection import (
get_available_backends,
is_pennylane_available,
is_qiskit_available,
is_cirq_available,
)
print(f"Available backends: {get_available_backends()}")
print(f" PennyLane: {is_pennylane_available()}")
print(f" Qiskit: {is_qiskit_available()}")
print(f" Cirq: {is_cirq_available()}")
Available backends: ['pennylane', 'qiskit', 'cirq'] PennyLane: True Qiskit: True Cirq: True
2. Core Concepts¶
What is QAOA Encoding?¶
QAOA Encoding adapts the structure of the Quantum Approximate Optimization Algorithm for classical data encoding into quantum states. Instead of optimizing a cost Hamiltonian, it uses data-dependent rotations to create rich quantum feature maps.
Circuit structure per repetition:
- Data encoding layer (cost layer): $R_{\text{data}}(\gamma \cdot x_i)$ on each qubit
- Mixer layer: $R_{\text{mixer}}(\beta)$ rotations + entangling gates
Why use QAOA encoding?
- Problems with graph or combinatorial structure
- Variational quantum classifiers
- Quantum kernel methods requiring QAOA-like feature maps
- Highly configurable (10 parameters) for fine-tuning
from encoding_atlas import QAOAEncoding
# Quick start - create a QAOA encoding with defaults
enc = QAOAEncoding(n_features=4)
print(f"Encoding: {enc}")
print(f" n_features: {enc.n_features}")
print(f" n_qubits: {enc.n_qubits}")
print(f" depth: {enc.depth}")
print(f" reps: {enc.reps}")
Encoding: QAOAEncoding(n_features=4, reps=2, data_rotation='Z', mixer_rotation='X', entanglement='linear', entangling_gate='cz', gamma=1.0, beta=1.0, include_initial_h=True, feature_map='linear') n_features: 4 n_qubits: 4 depth: 9 reps: 2
3. Instantiation - All 10 Constructor Parameters¶
QAOAEncoding has 10 constructor parameters, each controlling a different aspect of the encoding.
Let's explore every single one.
3.1 n_features (required) - Number of classical features¶
Maps directly to the number of qubits: n_qubits = n_features.
# n_features determines qubit count
for n in [1, 2, 4, 8]:
enc = QAOAEncoding(n_features=n)
print(f"n_features={n} -> n_qubits={enc.n_qubits}, depth={enc.depth}")
n_features=1 -> n_qubits=1, depth=5 n_features=2 -> n_qubits=2, depth=7 n_features=4 -> n_qubits=4, depth=9 n_features=8 -> n_qubits=8, depth=9
3.2 reps (default=2) - Number of QAOA-style repetitions¶
Controls the depth and expressibility of the encoding. Analogous to 'p' in QAOA literature.
# Effect of reps on depth and gate count
for r in [1, 2, 3, 5, 10]:
enc = QAOAEncoding(n_features=4, reps=r)
breakdown = enc.gate_count_breakdown()
print(f"reps={r:2d} -> depth={enc.depth:3d}, total_gates={breakdown['total']:3d}, "
f"data_rot={breakdown['data_rotation']:2d}, mixer_rot={breakdown['mixer_rotation']:2d}, "
f"entangling={breakdown['entangling']:2d}")
reps= 1 -> depth= 5, total_gates= 15, data_rot= 4, mixer_rot= 4, entangling= 3 reps= 2 -> depth= 9, total_gates= 26, data_rot= 8, mixer_rot= 8, entangling= 6 reps= 3 -> depth= 13, total_gates= 37, data_rot=12, mixer_rot=12, entangling= 9 reps= 5 -> depth= 21, total_gates= 59, data_rot=20, mixer_rot=20, entangling=15 reps=10 -> depth= 41, total_gates=114, data_rot=40, mixer_rot=40, entangling=30
3.3 data_rotation (default='Z') - Rotation axis for data encoding¶
Controls which Pauli rotation gate encodes the data: RX, RY, or RZ.
# All three rotation axes
for rot in ["X", "Y", "Z"]:
enc = QAOAEncoding(n_features=3, data_rotation=rot)
print(f"data_rotation='{rot}' -> Uses R{rot} gates for data encoding")
# Verify via properties
props = enc.properties
print(f" Notes: {props.notes}")
data_rotation='X' -> Uses RX gates for data encoding Notes: QAOA-style p=2, data:X mixer:X, entanglement:linear, gate:cz data_rotation='Y' -> Uses RY gates for data encoding Notes: QAOA-style p=2, data:Y mixer:X, entanglement:linear, gate:cz data_rotation='Z' -> Uses RZ gates for data encoding Notes: QAOA-style p=2, data:Z mixer:X, entanglement:linear, gate:cz
3.4 mixer_rotation (default='X') - Rotation axis for mixer layer¶
Controls the mixer Hamiltonian approximation. Standard QAOA uses RX (transverse field mixer).
# All mixer rotation combinations
for mixer in ["X", "Y", "Z"]:
enc = QAOAEncoding(n_features=3, data_rotation="Z", mixer_rotation=mixer)
print(f"mixer_rotation='{mixer}' -> Notes: {enc.properties.notes}")
mixer_rotation='X' -> Notes: QAOA-style p=2, data:Z mixer:X, entanglement:linear, gate:cz mixer_rotation='Y' -> Notes: QAOA-style p=2, data:Z mixer:Y, entanglement:linear, gate:cz mixer_rotation='Z' -> Notes: QAOA-style p=2, data:Z mixer:Z, entanglement:linear, gate:cz
3.5 entanglement (default='linear') - Entanglement topology¶
Four options controlling how qubits interact:
'linear': Nearest-neighbor pairs (i, i+1) - hardware-friendly'circular': Linear + wrap-around (n-1, 0) - periodic boundary'full': All pairs (i, j) where i < j - maximum entanglement'none': No entanglement - product state encoding
# All entanglement patterns with their qubit pairs
for ent in ["none", "linear", "circular", "full"]:
enc = QAOAEncoding(n_features=4, entanglement=ent)
pairs = enc.get_entanglement_pairs()
print(f"entanglement='{ent:8s}' -> {len(pairs)} pairs: {pairs}")
print(f" is_entangling={enc.properties.is_entangling}, "
f"depth={enc.depth}, "
f"simulability='{enc.properties.simulability}'")
entanglement='none ' -> 0 pairs: [] is_entangling=False, depth=5, simulability='simulable' entanglement='linear ' -> 3 pairs: [(0, 1), (1, 2), (2, 3)] is_entangling=True, depth=9, simulability='not_simulable' entanglement='circular' -> 4 pairs: [(0, 1), (1, 2), (2, 3), (3, 0)] is_entangling=True, depth=9, simulability='not_simulable' entanglement='full ' -> 6 pairs: [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)] is_entangling=True, depth=11, simulability='not_simulable'
3.6 entangling_gate (default='cz') - Type of entangling gate¶
Three options for the two-qubit entangling operation:
'cx': CNOT gates'cz': Controlled-Z gates (symmetric, hardware-native on many platforms)'rzz': RZZ gates with angle beta (parameterized entanglement)
# All entangling gate types
for gate in ["cx", "cz", "rzz"]:
enc = QAOAEncoding(n_features=4, entangling_gate=gate, entanglement="linear")
breakdown = enc.gate_count_breakdown()
print(f"entangling_gate='{gate}' -> "
f"entangling_gates={breakdown['entangling']}, "
f"total={breakdown['total']}")
entangling_gate='cx' -> entangling_gates=6, total=26 entangling_gate='cz' -> entangling_gates=6, total=26 entangling_gate='rzz' -> entangling_gates=6, total=26
3.7 gamma (default=1.0) - Scaling factor for data encoding¶
Controls the rotation angle: angle = gamma * x_i (linear) or gamma * x_i^2 (quadratic).
# Effect of gamma on data angles
x = np.array([0.5, 1.0, 1.5, 2.0])
for gamma in [0.5, 1.0, 2.0, np.pi]:
enc = QAOAEncoding(n_features=4, gamma=gamma)
angles = enc.compute_data_angles(x)
print(f"gamma={gamma:.4f} -> angles={np.round(angles, 4)}")
gamma=0.5000 -> angles=[0.25 0.5 0.75 1. ] gamma=1.0000 -> angles=[0.5 1. 1.5 2. ] gamma=2.0000 -> angles=[1. 2. 3. 4.] gamma=3.1416 -> angles=[1.5708 3.1416 4.7124 6.2832]
3.8 beta (default=1.0) - Scaling factor for mixer rotations¶
Controls the mixer rotation angle. For RZZ gates, also controls the entangling interaction strength.
# Effect of beta on circuit behavior
for beta in [0.5, 1.0, np.pi/2, np.pi]:
enc = QAOAEncoding(n_features=4, beta=beta)
info = enc.get_layer_info()
print(f"beta={beta:.4f} -> beta in layer_info: {info['beta']:.4f}")
beta=0.5000 -> beta in layer_info: 0.5000 beta=1.0000 -> beta in layer_info: 1.0000 beta=1.5708 -> beta in layer_info: 1.5708 beta=3.1416 -> beta in layer_info: 3.1416
3.9 include_initial_h (default=True) - Initial Hadamard layer¶
Controls whether Hadamard gates create uniform superposition at the start.
True: Start from |+>^n (standard QAOA)False: Start from |0>^n
# With and without initial Hadamard
for h in [True, False]:
enc = QAOAEncoding(n_features=4, include_initial_h=h)
breakdown = enc.gate_count_breakdown()
print(f"include_initial_h={str(h):5s} -> "
f"hadamard={breakdown['hadamard']}, "
f"depth={enc.depth}, "
f"total_gates={breakdown['total']}")
include_initial_h=True -> hadamard=4, depth=9, total_gates=26 include_initial_h=False -> hadamard=0, depth=8, total_gates=22
3.10 feature_map (default='linear') - Feature mapping type¶
Controls how features map to rotation angles:
'linear': phi(x_i) = gamma * x_i (direct mapping)'quadratic': phi(x_i) = gamma * x_i^2 (nonlinear mapping)
# Linear vs quadratic feature mapping
x = np.array([0.5, 1.0, 1.5, 2.0])
for fm in ["linear", "quadratic"]:
enc = QAOAEncoding(n_features=4, gamma=1.0, feature_map=fm)
angles = enc.compute_data_angles(x)
print(f"feature_map='{fm:10s}' -> angles={np.round(angles, 4)}")
# Verify: linear -> gamma*x, quadratic -> gamma*x^2
if fm == "linear":
expected = 1.0 * x
else:
expected = 1.0 * x**2
assert np.allclose(angles, expected), "Mismatch!"
print(f" Verified: {'gamma*x' if fm == 'linear' else 'gamma*x^2'} = {np.round(expected, 4)}")
feature_map='linear ' -> angles=[0.5 1. 1.5 2. ] Verified: gamma*x = [0.5 1. 1.5 2. ] feature_map='quadratic ' -> angles=[0.25 1. 2.25 4. ] Verified: gamma*x^2 = [0.25 1. 2.25 4. ]
Full Parameter Showcase¶
Creating an encoding with every parameter explicitly specified:
# Every parameter specified
enc_full = QAOAEncoding(
n_features=4,
reps=3,
data_rotation="Y",
mixer_rotation="X",
entanglement="circular",
entangling_gate="cz",
gamma=1.5,
beta=0.8,
include_initial_h=True,
feature_map="quadratic",
)
print(f"Full config: {enc_full}")
print(f"\nAll attributes:")
print(f" n_features: {enc_full.n_features}")
print(f" n_qubits: {enc_full.n_qubits}")
print(f" reps: {enc_full.reps}")
print(f" data_rotation: {enc_full.data_rotation}")
print(f" mixer_rotation: {enc_full.mixer_rotation}")
print(f" entanglement: {enc_full.entanglement}")
print(f" entangling_gate: {enc_full.entangling_gate}")
print(f" gamma: {enc_full.gamma}")
print(f" beta: {enc_full.beta}")
print(f" include_initial_h: {enc_full.include_initial_h}")
print(f" feature_map: {enc_full.feature_map}")
print(f" depth: {enc_full.depth}")
Full config: QAOAEncoding(n_features=4, reps=3, data_rotation='Y', mixer_rotation='X', entanglement='circular', entangling_gate='cz', gamma=1.5, beta=0.8, include_initial_h=True, feature_map='quadratic') All attributes: n_features: 4 n_qubits: 4 reps: 3 data_rotation: Y mixer_rotation: X entanglement: circular entangling_gate: cz gamma: 1.5 beta: 0.8 include_initial_h: True feature_map: quadratic depth: 13
4. Circuit Generation - All Three Backends¶
QAOAEncoding supports three quantum computing frameworks: PennyLane, Qiskit, and Cirq.
4.1 PennyLane Backend¶
Returns a callable function that applies gates when invoked within a QNode context.
enc = QAOAEncoding(n_features=3, reps=2, entanglement="linear")
x = np.array([0.5, 1.0, 1.5])
# PennyLane circuit (returns a callable)
circuit_fn = enc.get_circuit(x, backend="pennylane")
print(f"PennyLane circuit type: {type(circuit_fn).__name__}")
print(f"Callable: {callable(circuit_fn)}")
# Use within a PennyLane QNode to get statevector
try:
import pennylane as qml
dev = qml.device("default.qubit", wires=enc.n_qubits)
@qml.qnode(dev)
def run_circuit():
circuit_fn()
return qml.state()
state = run_circuit()
print(f"\nStatevector dimension: {len(state)}")
print(f"State norm: {np.linalg.norm(state):.6f}")
print(f"First 4 amplitudes: {np.round(state[:4], 6)}")
except ImportError:
print("PennyLane not available - skipping execution")
PennyLane circuit type: function Callable: True Statevector dimension: 8 State norm: 1.000000 First 4 amplitudes: [-0.676483+0.329765j 0.309629+0.192165j 0.148004+0.163709j 0.023212+0.231589j]
4.2 Qiskit Backend¶
Returns a QuantumCircuit object with barriers between repetitions for clarity.
enc = QAOAEncoding(n_features=3, reps=2, entanglement="linear")
x = np.array([0.5, 1.0, 1.5])
try:
qc = enc.get_circuit(x, backend="qiskit")
print(f"Qiskit circuit type: {type(qc).__name__}")
print(f"Circuit depth: {qc.depth()}")
print(f"Circuit width: {qc.num_qubits}")
print(f"Gate count: {qc.size()}")
print(f"\nCircuit diagram:")
print(qc.draw(output='text'))
except ImportError:
print("Qiskit not available - skipping")
Qiskit circuit type: QuantumCircuit
Circuit depth: 9
Circuit width: 3
Gate count: 19
Circuit diagram:
┌───┐┌─────────┐┌───────┐ ░ ┌─────────┐┌───────┐
q_0: ┤ H ├┤ Rz(0.5) ├┤ Rx(1) ├─■─────░─┤ Rz(0.5) ├┤ Rx(1) ├─■────
├───┤└┬───────┬┘├───────┤ │ ░ └┬───────┬┘├───────┤ │
q_1: ┤ H ├─┤ Rz(1) ├─┤ Rx(1) ├─■──■──░──┤ Rz(1) ├─┤ Rx(1) ├─■──■─
├───┤┌┴───────┴┐├───────┤ │ ░ ┌┴───────┴┐├───────┤ │
q_2: ┤ H ├┤ Rz(1.5) ├┤ Rx(1) ├────■──░─┤ Rz(1.5) ├┤ Rx(1) ├────■─
└───┘└─────────┘└───────┘ ░ └─────────┘└───────┘
4.3 Cirq Backend¶
Returns a cirq.Circuit with optimized Moment structure for minimal depth.
CZ and RZZ gates are parallelized into Moments; CNOT gates are applied sequentially.
enc = QAOAEncoding(n_features=3, reps=2, entanglement="linear")
x = np.array([0.5, 1.0, 1.5])
try:
import cirq
circuit = enc.get_circuit(x, backend="cirq")
print(f"Cirq circuit type: {type(circuit).__name__}")
print(f"Number of moments: {len(circuit)}")
print(f"\nCircuit diagram:")
print(circuit)
except ImportError:
print("Cirq not available - skipping")
Cirq circuit type: Circuit
Number of moments: 9
Circuit diagram:
0: ───H───Rz(0.159π)───Rx(0.318π)───@───────Rz(0.159π)───Rx(0.318π)───@───────
│ │
1: ───H───Rz(0.318π)───Rx(0.318π)───@───@───Rz(0.318π)───Rx(0.318π)───@───@───
│ │
2: ───H───Rz(0.477π)───Rx(0.318π)───────@───Rz(0.477π)───Rx(0.318π)───────@───
4.4 Cross-Backend Consistency¶
All backends produce the same quantum state for the same input.
from encoding_atlas.analysis import simulate_encoding_statevector, compute_fidelity
enc = QAOAEncoding(n_features=3, reps=2, entanglement="linear", entangling_gate="cz")
x = np.array([0.5, 1.0, 1.5])
# Simulate statevector (uses PennyLane internally)
state = simulate_encoding_statevector(enc, x)
print(f"Statevector dimension: {len(state)}")
print(f"State norm: {np.linalg.norm(state):.10f}")
print(f"Number of qubits: {int(np.log2(len(state)))}")
# Verify it's a valid quantum state
assert abs(np.linalg.norm(state) - 1.0) < 1e-10, "Not a valid quantum state!"
print("\nValid quantum state confirmed (norm = 1)")
Statevector dimension: 8 State norm: 1.0000000000 Number of qubits: 3 Valid quantum state confirmed (norm = 1)
5. Batch Circuit Generation¶
Generate circuits for multiple data samples at once, with optional parallel processing.
5.1 Sequential Batch Processing¶
enc = QAOAEncoding(n_features=4, reps=2)
X = np.random.rand(10, 4) * np.pi # 10 samples, 4 features
# Sequential processing (default)
circuits = enc.get_circuits(X, backend="pennylane")
print(f"Generated {len(circuits)} circuits (sequential)")
print(f"Each circuit is callable: {callable(circuits[0])}")
Generated 10 circuits (sequential) Each circuit is callable: True
5.2 Parallel Batch Processing¶
Use parallel=True for large batches. Thread-safe because circuit generation is stateless.
import time
enc = QAOAEncoding(n_features=4, reps=2)
X_large = np.random.rand(200, 4) * np.pi
# Sequential timing
try:
t0 = time.perf_counter()
circuits_seq = enc.get_circuits(X_large, backend="qiskit")
t_seq = time.perf_counter() - t0
# Parallel timing
t0 = time.perf_counter()
circuits_par = enc.get_circuits(X_large, backend="qiskit", parallel=True)
t_par = time.perf_counter() - t0
print(f"Sequential: {len(circuits_seq)} circuits in {t_seq:.3f}s")
print(f"Parallel: {len(circuits_par)} circuits in {t_par:.3f}s")
print(f"Speedup: {t_seq/t_par:.2f}x")
except ImportError:
# Fall back to pennylane
t0 = time.perf_counter()
circuits_seq = enc.get_circuits(X_large, backend="pennylane")
t_seq = time.perf_counter() - t0
t0 = time.perf_counter()
circuits_par = enc.get_circuits(X_large, backend="pennylane", parallel=True)
t_par = time.perf_counter() - t0
print(f"Sequential: {len(circuits_seq)} circuits in {t_seq:.3f}s")
print(f"Parallel: {len(circuits_par)} circuits in {t_par:.3f}s")
Sequential: 200 circuits in 0.104s Parallel: 200 circuits in 0.139s Speedup: 0.74x
5.3 Custom Worker Count¶
import os
enc = QAOAEncoding(n_features=4, reps=2)
X = np.random.rand(50, 4) * np.pi
# Specify max_workers for parallel processing
circuits = enc.get_circuits(
X, backend="pennylane", parallel=True, max_workers=os.cpu_count()
)
print(f"Generated {len(circuits)} circuits with max_workers={os.cpu_count()}")
# Single sample as 1D array
x_single = np.array([0.5, 1.0, 1.5, 2.0])
circuits_single = enc.get_circuits(x_single, backend="pennylane")
print(f"Single sample -> {len(circuits_single)} circuit(s)")
Generated 50 circuits with max_workers=12 Single sample -> 1 circuit(s)
enc = QAOAEncoding(n_features=4, reps=2, entanglement="full")
props = enc.properties
print("EncodingProperties (frozen dataclass):")
print(f" n_qubits: {props.n_qubits}")
print(f" depth: {props.depth}")
print(f" gate_count: {props.gate_count}")
print(f" single_qubit_gates: {props.single_qubit_gates}")
print(f" two_qubit_gates: {props.two_qubit_gates}")
print(f" parameter_count: {props.parameter_count}")
print(f" is_entangling: {props.is_entangling}")
print(f" simulability: {props.simulability}")
print(f" trainability_estimate:{props.trainability_estimate}")
print(f" notes: {props.notes}")
# Properties as dictionary
props_dict = props.to_dict()
print(f"\nProperties dict keys: {list(props_dict.keys())}")
EncodingProperties (frozen dataclass): n_qubits: 4 depth: 11 gate_count: 32 single_qubit_gates: 20 two_qubit_gates: 12 parameter_count: 0 is_entangling: True simulability: not_simulable trainability_estimate:0.6610817970188937 notes: QAOA-style p=2, data:Z mixer:X, entanglement:full, gate:cz Properties dict keys: ['n_qubits', 'depth', 'gate_count', 'single_qubit_gates', 'two_qubit_gates', 'parameter_count', 'is_entangling', 'simulability', 'expressibility', 'entanglement_capability', 'trainability_estimate', 'noise_resilience_estimate', 'notes']
6.2 Gate Count Breakdown (GateCountBreakdown TypedDict)¶
enc = QAOAEncoding(n_features=4, reps=2, entanglement="linear")
breakdown = enc.gate_count_breakdown()
print("GateCountBreakdown:")
print(f" hadamard: {breakdown['hadamard']}")
print(f" data_rotation: {breakdown['data_rotation']}")
print(f" mixer_rotation: {breakdown['mixer_rotation']}")
print(f" entangling: {breakdown['entangling']}")
print(f" total_single_qubit: {breakdown['total_single_qubit']}")
print(f" total_two_qubit: {breakdown['total_two_qubit']}")
print(f" total: {breakdown['total']}")
# Verify consistency
assert breakdown['total_single_qubit'] == breakdown['hadamard'] + breakdown['data_rotation'] + breakdown['mixer_rotation']
assert breakdown['total_two_qubit'] == breakdown['entangling']
assert breakdown['total'] == breakdown['total_single_qubit'] + breakdown['total_two_qubit']
print("\nAll consistency checks passed!")
GateCountBreakdown: hadamard: 4 data_rotation: 8 mixer_rotation: 8 entangling: 6 total_single_qubit: 20 total_two_qubit: 6 total: 26 All consistency checks passed!
6.3 Layer Information¶
enc = QAOAEncoding(n_features=4, reps=2, entanglement="circular", entangling_gate="rzz")
info = enc.get_layer_info()
print("Layer Information:")
for key, value in info.items():
print(f" {key}: {value}")
Layer Information:
n_qubits: 4
reps: 2
layers_per_rep: {'data_encoding': '4 RZ gates', 'mixer_rotation': '4 RX gates', 'entanglement': '4 RZZ gates'}
entanglement_pairs: [(0, 1), (1, 2), (2, 3), (3, 0)]
total_data_gates: 8
total_mixer_gates: 8
total_entangling_gates: 8
include_initial_h: True
gamma: 1.0
beta: 1.0
6.4 Comprehensive Resource Summary¶
enc = QAOAEncoding(n_features=4, reps=2, entanglement="full", entangling_gate="cz")
summary = enc.resource_summary()
print("Resource Summary:")
print(f" Circuit Structure:")
print(f" n_qubits: {summary['n_qubits']}")
print(f" n_features: {summary['n_features']}")
print(f" depth: {summary['depth']}")
print(f" reps: {summary['reps']}")
print(f"\n Entanglement:")
print(f" topology: {summary['entanglement']}")
print(f" n_pairs: {summary['n_entanglement_pairs']}")
print(f" gate: {summary['entangling_gate']}")
print(f" pairs: {summary['entanglement_pairs']}")
print(f"\n Gate Counts:")
for k, v in summary['gate_counts'].items():
print(f" {k}: {v}")
print(f"\n Characteristics:")
print(f" is_entangling: {summary['is_entangling']}")
print(f" simulability: {summary['simulability']}")
print(f" trainability_estimate:{summary['trainability_estimate']:.4f}")
print(f"\n Hardware Requirements:")
for k, v in summary['hardware_requirements'].items():
print(f" {k}: {v}")
Resource Summary:
Circuit Structure:
n_qubits: 4
n_features: 4
depth: 11
reps: 2
Entanglement:
topology: full
n_pairs: 6
gate: cz
pairs: [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
Gate Counts:
hadamard: 4
data_rotation: 8
mixer_rotation: 8
entangling: 12
total_single_qubit: 20
total_two_qubit: 12
total: 32
Characteristics:
is_entangling: True
simulability: not_simulable
trainability_estimate:0.6611
Hardware Requirements:
connectivity: all-to-all
native_gates: ['H', 'RZ', 'RX', 'CZ']
min_qubits: 4
6.5 Depth Analysis - Theoretical vs Actual¶
enc = QAOAEncoding(n_features=4, reps=2, entanglement="linear")
# Theoretical depth (fast, no circuit generation)
theoretical = enc.depth
print(f"Theoretical depth: {theoretical}")
# Actual backend-specific depth
try:
qiskit_depth = enc.get_depth(backend="qiskit")
print(f"Qiskit depth: {qiskit_depth}")
except ImportError:
print("Qiskit not available")
try:
cirq_depth = enc.get_depth(backend="cirq")
print(f"Cirq depth: {cirq_depth}")
except ImportError:
print("Cirq not available")
pennylane_depth = enc.get_depth(backend="pennylane")
print(f"PennyLane depth: {pennylane_depth} (returns theoretical)")
Theoretical depth: 9 Qiskit depth: 11 Cirq depth: 9 PennyLane depth: 9 (returns theoretical)
6.6 n_data_parameters Property¶
# Number of data-dependent rotation angles per input sample
for reps in [1, 2, 3, 5]:
enc = QAOAEncoding(n_features=4, reps=reps)
print(f"n_features=4, reps={reps} -> n_data_parameters={enc.n_data_parameters} "
f"(= n_qubits * reps = {enc.n_qubits} * {reps})")
n_features=4, reps=1 -> n_data_parameters=4 (= n_qubits * reps = 4 * 1) n_features=4, reps=2 -> n_data_parameters=8 (= n_qubits * reps = 4 * 2) n_features=4, reps=3 -> n_data_parameters=12 (= n_qubits * reps = 4 * 3) n_features=4, reps=5 -> n_data_parameters=20 (= n_qubits * reps = 4 * 5)
7. Entanglement Topologies - Deep Dive¶
The entanglement pattern has a major impact on encoding expressibility, circuit depth, and hardware requirements.
# Comprehensive comparison of all entanglement patterns
print(f"{'Topology':<10} {'Pairs':>5} {'Depth':>6} {'Ent.Gates':>10} {'Total':>6} {'Simulable':<15} {'Connectivity'}")
print("-" * 80)
for ent in ["none", "linear", "circular", "full"]:
enc = QAOAEncoding(n_features=6, reps=2, entanglement=ent)
bd = enc.gate_count_breakdown()
summary = enc.resource_summary()
connectivity = summary['hardware_requirements']['connectivity']
print(f"{ent:<10} {len(enc.get_entanglement_pairs()):>5} {enc.depth:>6} "
f"{bd['entangling']:>10} {bd['total']:>6} {enc.properties.simulability:<15} {connectivity}")
Topology Pairs Depth Ent.Gates Total Simulable Connectivity -------------------------------------------------------------------------------- none 0 5 0 30 simulable none required linear 5 9 10 40 not_simulable linear (nearest-neighbor) circular 6 9 12 42 not_simulable ring full 15 15 30 60 not_simulable all-to-all
7.1 Depth Scaling by Entanglement Pattern¶
The depth of the entangling layer depends on the pattern and uses graph-theoretic optimal scheduling:
- Linear: min(2, n-1) layers (even/odd pair parallelization)
- Circular: 2 for even n, 3 for odd n (cycle graph edge coloring)
- Full: n-1 for even n, n for odd n (complete graph chromatic index, Vizing's theorem)
# Depth scaling for different n_features
print(f"{'n_features':<12} {'none':>6} {'linear':>8} {'circular':>10} {'full':>6}")
print("-" * 50)
for n in [2, 3, 4, 5, 6, 8, 10]:
depths = {}
for ent in ["none", "linear", "circular", "full"]:
enc = QAOAEncoding(n_features=n, reps=1, entanglement=ent)
depths[ent] = enc.depth
print(f"{n:<12} {depths['none']:>6} {depths['linear']:>8} {depths['circular']:>10} {depths['full']:>6}")
n_features none linear circular full -------------------------------------------------- 2 3 4 5 4 3 3 5 6 6 4 3 5 5 6 5 3 5 6 8 6 3 5 5 8 8 3 5 5 10 10 3 5 5 12
7.2 Entanglement Pairs Visualization¶
# Visualize entanglement pairs for each topology
for ent in ["linear", "circular", "full"]:
enc = QAOAEncoding(n_features=5, entanglement=ent)
pairs = enc.get_entanglement_pairs()
print(f"\n{ent.upper()} entanglement (n=5):")
print(f" Pairs: {pairs}")
# Connectivity per qubit
for q in range(5):
neighbors = [p for p in pairs if q in p]
connected_to = [p[1] if p[0] == q else p[0] for p in neighbors]
print(f" Qubit {q} connects to: {connected_to} ({len(connected_to)} connections)")
LINEAR entanglement (n=5): Pairs: [(0, 1), (1, 2), (2, 3), (3, 4)] Qubit 0 connects to: [1] (1 connections) Qubit 1 connects to: [0, 2] (2 connections) Qubit 2 connects to: [1, 3] (2 connections) Qubit 3 connects to: [2, 4] (2 connections) Qubit 4 connects to: [3] (1 connections) CIRCULAR entanglement (n=5): Pairs: [(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)] Qubit 0 connects to: [1, 4] (2 connections) Qubit 1 connects to: [0, 2] (2 connections) Qubit 2 connects to: [1, 3] (2 connections) Qubit 3 connects to: [2, 4] (2 connections) Qubit 4 connects to: [3, 0] (2 connections) FULL entanglement (n=5): Pairs: [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)] Qubit 0 connects to: [1, 2, 3, 4] (4 connections) Qubit 1 connects to: [0, 2, 3, 4] (4 connections) Qubit 2 connects to: [0, 1, 3, 4] (4 connections) Qubit 3 connects to: [0, 1, 2, 4] (4 connections) Qubit 4 connects to: [0, 1, 2, 3] (4 connections)
8. Feature Mapping - Linear vs Quadratic¶
x = np.array([0.0, 0.5, 1.0, 1.5, 2.0])
# Linear: angle = gamma * x
enc_lin = QAOAEncoding(n_features=5, gamma=2.0, feature_map="linear")
angles_lin = enc_lin.compute_data_angles(x)
# Quadratic: angle = gamma * x^2
enc_quad = QAOAEncoding(n_features=5, gamma=2.0, feature_map="quadratic")
angles_quad = enc_quad.compute_data_angles(x)
print("Input x: ", np.round(x, 4))
print("Linear (2*x): ", np.round(angles_lin, 4))
print("Quadratic (2*x²):", np.round(angles_quad, 4))
# Verify formulas
assert np.allclose(angles_lin, 2.0 * x)
assert np.allclose(angles_quad, 2.0 * x**2)
print("\nFormulas verified!")
Input x: [0. 0.5 1. 1.5 2. ] Linear (2*x): [0. 1. 2. 3. 4.] Quadratic (2*x²): [0. 0.5 2. 4.5 8. ] Formulas verified!
9. Data Angle Computation¶
The compute_data_angles() method reveals the rotation angles used in the data encoding layer.
# Understanding how gamma and feature_map interact
x = np.array([0.1, 0.2, 0.3, 0.4])
configs = [
{"gamma": 1.0, "feature_map": "linear"},
{"gamma": 2.0, "feature_map": "linear"},
{"gamma": np.pi, "feature_map": "linear"},
{"gamma": 1.0, "feature_map": "quadratic"},
{"gamma": np.pi, "feature_map": "quadratic"},
]
for cfg in configs:
enc = QAOAEncoding(n_features=4, **cfg)
angles = enc.compute_data_angles(x)
print(f"gamma={cfg['gamma']:.4f}, feature_map='{cfg['feature_map']}' -> {np.round(angles, 6)}")
gamma=1.0000, feature_map='linear' -> [0.1 0.2 0.3 0.4] gamma=2.0000, feature_map='linear' -> [0.2 0.4 0.6 0.8] gamma=3.1416, feature_map='linear' -> [0.314159 0.628319 0.942478 1.256637] gamma=1.0000, feature_map='quadratic' -> [0.01 0.04 0.09 0.16] gamma=3.1416, feature_map='quadratic' -> [0.031416 0.125664 0.282743 0.502655]
10. Serialization - to_dict, from_dict, Pickle¶
10.1 Dictionary Serialization (JSON-compatible)¶
import json
enc = QAOAEncoding(n_features=4, reps=3, gamma=2.0, entangling_gate="rzz",
entanglement="circular", feature_map="quadratic")
# Serialize to dict
config = enc.to_dict()
print("Serialized config:")
print(json.dumps(config, indent=2))
# Reconstruct from dict
enc_restored = QAOAEncoding.from_dict(config)
print(f"\nRestored: {enc_restored}")
print(f"Equal to original: {enc == enc_restored}")
# Save to / load from JSON file
import tempfile, os
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump(config, f, indent=2)
tmp_path = f.name
with open(tmp_path, 'r') as f:
loaded_config = json.load(f)
enc_from_file = QAOAEncoding.from_dict(loaded_config)
print(f"Loaded from file: {enc_from_file}")
print(f"Equal to original: {enc == enc_from_file}")
os.unlink(tmp_path)
Serialized config:
{
"class": "QAOAEncoding",
"n_features": 4,
"reps": 3,
"data_rotation": "Z",
"mixer_rotation": "X",
"entanglement": "circular",
"entangling_gate": "rzz",
"gamma": 2.0,
"beta": 1.0,
"include_initial_h": true,
"feature_map": "quadratic"
}
Restored: QAOAEncoding(n_features=4, reps=3, data_rotation='Z', mixer_rotation='X', entanglement='circular', entangling_gate='rzz', gamma=2.0, beta=1.0, include_initial_h=True, feature_map='quadratic')
Equal to original: True
Loaded from file: QAOAEncoding(n_features=4, reps=3, data_rotation='Z', mixer_rotation='X', entanglement='circular', entangling_gate='rzz', gamma=2.0, beta=1.0, include_initial_h=True, feature_map='quadratic')
Equal to original: True
10.2 Pickle Serialization¶
All encodings support pickling, including cached properties. Thread locks are excluded from serialization and recreated on unpickling.
import pickle
enc = QAOAEncoding(n_features=4, reps=2, entanglement="full")
# Force property computation before pickling
_ = enc.properties
print(f"Properties cached before pickle: {enc.properties.gate_count} gates")
# Pickle roundtrip
data = pickle.dumps(enc)
enc_unpickled = pickle.loads(data)
print(f"\nPickle size: {len(data)} bytes")
print(f"Unpickled: {enc_unpickled}")
print(f"Equal: {enc == enc_unpickled}")
print(f"Properties preserved: {enc_unpickled.properties.gate_count} gates")
# Verify the unpickled encoding works
x = np.array([0.5, 1.0, 1.5, 2.0])
circuit = enc_unpickled.get_circuit(x, backend="pennylane")
print(f"Circuit from unpickled encoding: callable={callable(circuit)}")
Properties cached before pickle: 32 gates Pickle size: 814 bytes Unpickled: QAOAEncoding(n_features=4, reps=2, data_rotation='Z', mixer_rotation='X', entanglement='full', entangling_gate='cz', gamma=1.0, beta=1.0, include_initial_h=True, feature_map='linear') Equal: True Properties preserved: 32 gates Circuit from unpickled encoding: callable=True
# Original encoding
enc = QAOAEncoding(n_features=4, reps=2, gamma=1.5, entanglement="linear")
# Create variations
enc_deeper = enc.copy(reps=5)
enc_full = enc.copy(entanglement="full", entangling_gate="cx")
enc_quadratic = enc.copy(feature_map="quadratic", gamma=2.0)
print(f"Original: reps={enc.reps}, entanglement='{enc.entanglement}', gamma={enc.gamma}")
print(f"Deeper: reps={enc_deeper.reps}, entanglement='{enc_deeper.entanglement}', gamma={enc_deeper.gamma}")
print(f"Full: reps={enc_full.reps}, entanglement='{enc_full.entanglement}', gate='{enc_full.entangling_gate}'")
print(f"Quadratic: feature_map='{enc_quadratic.feature_map}', gamma={enc_quadratic.gamma}")
# Original is unchanged
print(f"\nOriginal unchanged: reps={enc.reps}, gamma={enc.gamma}, feature_map='{enc.feature_map}'")
Original: reps=2, entanglement='linear', gamma=1.5 Deeper: reps=5, entanglement='linear', gamma=1.5 Full: reps=2, entanglement='full', gate='cx' Quadratic: feature_map='quadratic', gamma=2.0 Original unchanged: reps=2, gamma=1.5, feature_map='linear'
12. Equality & Hashing¶
QAOAEncoding supports equality comparison and hashing, enabling use in sets and as dict keys.
# Equality
enc1 = QAOAEncoding(n_features=4, reps=2, gamma=1.5)
enc2 = QAOAEncoding(n_features=4, reps=2, gamma=1.5)
enc3 = QAOAEncoding(n_features=4, reps=3, gamma=1.5)
print(f"Same config equal: {enc1 == enc2}")
print(f"Different config equal: {enc1 == enc3}")
print(f"Not equal to non-QAOA: {enc1 == 'not an encoding'}")
# Hashing - use in sets and dicts
encoding_set = {enc1, enc2, enc3}
print(f"\nSet of 3 encodings (2 equal): {len(encoding_set)} unique")
encoding_dict = {enc1: "config_A", enc3: "config_B"}
print(f"Dict lookup: {encoding_dict[enc2]}") # enc2 == enc1, same hash
# Hash consistency
print(f"\nhash(enc1) == hash(enc2): {hash(enc1) == hash(enc2)}")
Same config equal: True Different config equal: False Not equal to non-QAOA: False Set of 3 encodings (2 equal): 2 unique Dict lookup: config_A hash(enc1) == hash(enc2): True
13. Capability Protocols¶
QAOAEncoding implements two capability protocols from the library's Layered Contract Architecture.
from encoding_atlas import (
ResourceAnalyzable,
EntanglementQueryable,
DataDependentResourceAnalyzable,
DataTransformable,
)
enc = QAOAEncoding(n_features=4, reps=2, entanglement="linear")
# Protocol checks
print("Protocol support:")
print(f" ResourceAnalyzable: {isinstance(enc, ResourceAnalyzable)}")
print(f" EntanglementQueryable: {isinstance(enc, EntanglementQueryable)}")
print(f" DataDependentResourceAnalyzable: {isinstance(enc, DataDependentResourceAnalyzable)}")
print(f" DataTransformable: {isinstance(enc, DataTransformable)}")
# Type guard functions
from encoding_atlas.core.protocols import (
is_resource_analyzable,
is_entanglement_queryable,
)
print(f"\nType guards:")
print(f" is_resource_analyzable: {is_resource_analyzable(enc)}")
print(f" is_entanglement_queryable: {is_entanglement_queryable(enc)}")
Protocol support: ResourceAnalyzable: True EntanglementQueryable: True DataDependentResourceAnalyzable: False DataTransformable: False Type guards: is_resource_analyzable: True is_entanglement_queryable: True
# Using protocols for safe capability-based dispatch
from encoding_atlas import AngleEncoding, IQPEncoding
encodings = [
QAOAEncoding(n_features=4, reps=2),
AngleEncoding(n_features=4),
IQPEncoding(n_features=4, reps=2),
]
for enc in encodings:
name = enc.__class__.__name__
if isinstance(enc, ResourceAnalyzable):
summary = enc.resource_summary()
gates = summary['gate_counts']['total'] if 'gate_counts' in summary else 'N/A'
print(f"{name}: ResourceAnalyzable -> total_gates={gates}")
if isinstance(enc, EntanglementQueryable):
pairs = enc.get_entanglement_pairs()
print(f" EntanglementQueryable -> {len(pairs)} pairs")
else:
print(f" Not EntanglementQueryable")
QAOAEncoding: ResourceAnalyzable -> total_gates=26 EntanglementQueryable -> 3 pairs AngleEncoding: ResourceAnalyzable -> total_gates=4 Not EntanglementQueryable IQPEncoding: ResourceAnalyzable -> total_gates=52 EntanglementQueryable -> 6 pairs
14.1 Resource Counting (via analysis module)¶
from encoding_atlas.analysis import (
count_resources,
get_resource_summary,
get_gate_breakdown,
compare_resources,
estimate_execution_time,
)
enc = QAOAEncoding(n_features=4, reps=2, entanglement="full")
# Count resources
resources = count_resources(enc)
print("count_resources:")
for k, v in resources.items():
print(f" {k}: {v}")
count_resources: n_qubits: 4 depth: 11 gate_count: 32 single_qubit_gates: 20 two_qubit_gates: 12 parameter_count: 0 cnot_count: 0 cz_count: 0 t_gate_count: 0 hadamard_count: 4 rotation_gates: 0 two_qubit_ratio: 0.375 gates_per_qubit: 8.0 encoding_name: QAOAEncoding is_data_dependent: False
# Quick resource summary from properties
enc = QAOAEncoding(n_features=4, reps=2)
summary = get_resource_summary(enc)
print(f"Resource summary: {summary}")
# Gate breakdown
breakdown = get_gate_breakdown(enc)
print(f"\nGate breakdown: {breakdown}")
Resource summary: {'n_qubits': 4, 'depth': 9, 'gate_count': 26, 'single_qubit_gates': 20, 'two_qubit_gates': 6, 'parameter_count': 0, 'cnot_count': 6, 'cz_count': 0, 't_gate_count': 0, 'hadamard_count': 0, 'rotation_gates': 20, 'two_qubit_ratio': 0.23076923076923078, 'gates_per_qubit': 6.5, 'encoding_name': 'QAOAEncoding', 'is_data_dependent': False}
Gate breakdown: {'rx': 0, 'ry': 0, 'rz': 0, 'h': 4, 'x': 0, 'y': 0, 'z': 0, 's': 0, 't': 0, 'cnot': 0, 'cx': 0, 'cz': 0, 'swap': 0, 'total_single_qubit': 20, 'total_two_qubit': 6, 'total': 26, 'encoding_name': 'QAOAEncoding'}
# Compare resources across encodings
from encoding_atlas import AngleEncoding, IQPEncoding, HardwareEfficientEncoding
comparison = compare_resources([
QAOAEncoding(n_features=4, reps=2),
IQPEncoding(n_features=4, reps=2),
AngleEncoding(n_features=4, reps=2),
HardwareEfficientEncoding(n_features=4, reps=2),
])
# compare_resources returns dict[str, list] - one list per metric
print("Resource Comparison:")
names = comparison.get('encoding_name', [''] * 4)
print(f"{'Encoding':<30} {'Qubits':>7} {'Depth':>7} {'Gates':>7} {'2Q Gates':>9}")
print("-" * 65)
for i in range(len(names)):
print(f"{names[i]:<30} {comparison['n_qubits'][i]:>7} {comparison['depth'][i]:>7} "
f"{comparison['gate_count'][i]:>7} {comparison['two_qubit_gates'][i]:>9}")
Resource Comparison: Encoding Qubits Depth Gates 2Q Gates ----------------------------------------------------------------- QAOAEncoding 4 9 26 6 IQPEncoding 4 6 52 24 AngleEncoding 4 2 8 0 HardwareEfficientEncoding 4 4 14 6
# Estimate execution time on quantum hardware
enc = QAOAEncoding(n_features=4, reps=2, entanglement="linear")
# Default: superconducting qubit timings
timing = estimate_execution_time(enc)
print(f"Estimated execution time: {timing}")
Estimated execution time: {'serial_time_us': 2.6, 'estimated_time_us': 2.8, 'single_qubit_time_us': 0.4, 'two_qubit_time_us': 1.2000000000000002, 'measurement_time_us': 1.0, 'parallelization_factor': 0.5}
14.2 Simulability Analysis¶
from encoding_atlas.analysis import (
check_simulability,
get_simulability_reason,
is_clifford_circuit,
is_matchgate_circuit,
estimate_entanglement_bound,
)
# Compare simulability across configurations
configs = [
("No entanglement", {"entanglement": "none"}),
("Linear CZ", {"entanglement": "linear", "entangling_gate": "cz"}),
("Full CZ", {"entanglement": "full", "entangling_gate": "cz"}),
("Full CNOT", {"entanglement": "full", "entangling_gate": "cx"}),
]
for name, kwargs in configs:
enc = QAOAEncoding(n_features=4, reps=2, **kwargs)
sim = check_simulability(enc)
reason = get_simulability_reason(enc)
cliff = is_clifford_circuit(enc)
match = is_matchgate_circuit(enc)
print(f"{name:20s} -> simulable={sim['is_simulable']}, clifford={cliff}, matchgate={match}")
print(f" Reason: {reason}")
No entanglement -> simulable=True, clifford=True, matchgate=False Reason: Simulable: Encoding produces only product states (no entanglement) Linear CZ -> simulable=False, clifford=False, matchgate=False Reason: Not simulable: Linear entanglement structure may allow tensor network simulation if entanglement entropy is bounded Full CZ -> simulable=False, clifford=False, matchgate=False Reason: Not simulable: High entanglement circuit with 12 two-qubit gates and non-Clifford operations Full CNOT -> simulable=False, clifford=False, matchgate=False Reason: Not simulable: High entanglement circuit with 12 two-qubit gates and non-Clifford operations
# Detailed simulability result
enc = QAOAEncoding(n_features=4, reps=2, entanglement="full")
result = check_simulability(enc, detailed=True)
print("Detailed simulability result:")
for k, v in result.items():
print(f" {k}: {v}")
Detailed simulability result:
is_simulable: False
simulability_class: not_simulable
reason: High entanglement circuit with 12 two-qubit gates and non-Clifford operations
details: {'is_entangling': True, 'is_clifford': False, 'is_matchgate': False, 'entanglement_pattern': 'full', 'two_qubit_gate_count': 12, 'n_qubits': 4, 'n_features': 4, 'declared_simulability': 'not_simulable', 'encoding_name': 'QAOAEncoding', 'has_non_clifford_gates': False, 'has_t_gates': False, 'has_parameterized_rotations': False}
recommendations: ['Statevector simulation feasible (4 qubits, ~256 bytes memory)', 'Brute-force statevector simulation is feasible at this circuit size (4 qubits, ~256 bytes memory)', 'Use statevector simulation for instances with < 20 qubits', 'Consider tensor network methods for structured entanglement', 'May require quantum hardware for large instances']
# Entanglement bound estimate
enc = QAOAEncoding(n_features=4, reps=2, entanglement="full")
bound = estimate_entanglement_bound(enc)
print(f"Entanglement bound: {bound}")
Entanglement bound: 1.5671247705854983
14.3 Expressibility Analysis¶
Measures how well the encoding covers the Hilbert space by comparing its fidelity distribution to the Haar random distribution.
from encoding_atlas.analysis import (
compute_expressibility,
compute_fidelity_distribution,
compute_haar_distribution,
)
# Compare expressibility across entanglement patterns
print(f"{'Entanglement':<12} {'Expressibility':>15} {'Mean Fidelity':>15}")
print("-" * 45)
for ent in ["none", "linear", "circular", "full"]:
enc = QAOAEncoding(n_features=3, reps=2, entanglement=ent)
result = compute_expressibility(enc, n_samples=300, seed=42, return_distributions=True)
print(f"{ent:<12} {result['expressibility']:>15.6f} {result['mean_fidelity']:>15.6f}")
Entanglement Expressibility Mean Fidelity --------------------------------------------- none 0.980053 0.142557 linear 0.980053 0.142557 circular 0.980053 0.142557 full 0.980053 0.142557
# Detailed expressibility result
enc = QAOAEncoding(n_features=3, reps=2, entanglement="full")
result = compute_expressibility(enc, n_samples=500, seed=42, return_distributions=True)
print(f"Expressibility (KL divergence): {result['expressibility']:.6f}")
print(f"Mean fidelity: {result['mean_fidelity']:.6f}")
print(f"Std fidelity: {result['std_fidelity']:.6f}")
print(f"Number of fidelity samples: {len(result['fidelity_distribution'])}")
Expressibility (KL divergence): 0.988094 Mean fidelity: 0.147891 Std fidelity: 0.152221 Number of fidelity samples: 75
14.4 Entanglement Capability¶
Measures the encoding's ability to generate entanglement using Meyer-Wallach and Scott measures.
from encoding_atlas.analysis import (
compute_entanglement_capability,
compute_meyer_wallach,
compute_scott_measure,
)
# Compare entanglement capability
print(f"{'Entanglement':<12} {'Capability':>12}")
print("-" * 28)
for ent in ["none", "linear", "circular", "full"]:
enc = QAOAEncoding(n_features=3, reps=2, entanglement=ent)
result = compute_entanglement_capability(enc, n_samples=200, seed=42)
print(f"{ent:<12} {result:>12.6f}")
Entanglement Capability ---------------------------- none 0.000000 linear 0.640633 circular 0.689538 full 0.689538
# Low-level: compute Meyer-Wallach directly on a statevector
from encoding_atlas.analysis import simulate_encoding_statevector
enc = QAOAEncoding(n_features=3, reps=2, entanglement="full")
x = np.array([0.5, 1.0, 1.5])
state = simulate_encoding_statevector(enc, x)
mw = compute_meyer_wallach(state, n_qubits=3)
print(f"Meyer-Wallach for specific input: {mw:.6f}")
Meyer-Wallach for specific input: 0.508694
14.5 Trainability Analysis (Barren Plateau Detection)¶
from encoding_atlas.analysis import (
estimate_trainability,
compute_gradient_variance,
detect_barren_plateau,
)
# Compare trainability across configurations
print(f"{'Config':<35} {'Trainability':>13} {'Grad Variance':>14} {'BP Risk':>10}")
print("-" * 75)
configs = [
("reps=1, linear", {"reps": 1, "entanglement": "linear"}),
("reps=2, linear", {"reps": 2, "entanglement": "linear"}),
("reps=2, full", {"reps": 2, "entanglement": "full"}),
("reps=5, full", {"reps": 5, "entanglement": "full"}),
]
for name, kwargs in configs:
enc = QAOAEncoding(n_features=3, **kwargs)
result = estimate_trainability(enc, n_samples=100, seed=42, return_details=True)
bp = detect_barren_plateau(result['gradient_variance'], enc.n_qubits, enc.properties.gate_count)
print(f"{name:<35} {result['trainability_estimate']:>13.6f} {result['gradient_variance']:>14.8f} {bp:>10}")
Config Trainability Grad Variance BP Risk --------------------------------------------------------------------------- reps=1, linear 0.127423 0.01091513 low reps=2, linear 0.060935 0.00429002 low reps=2, full 0.061542 0.00434249 low reps=5, full 0.119617 0.01004547 low
14.6 Simulation Utilities¶
from encoding_atlas.analysis import (
simulate_encoding_statevector,
simulate_encoding_statevectors_batch,
compute_fidelity,
compute_purity,
partial_trace_single_qubit,
compute_von_neumann_entropy,
compute_linear_entropy,
generate_random_parameters,
)
enc = QAOAEncoding(n_features=3, reps=2, entanglement="linear")
# Single statevector simulation
x = np.array([0.5, 1.0, 1.5])
state = simulate_encoding_statevector(enc, x)
print(f"State dimension: {len(state)}, norm: {np.linalg.norm(state):.10f}")
# Batch simulation
X = np.random.rand(5, 3) * np.pi
states = simulate_encoding_statevectors_batch(enc, X)
print(f"\nBatch: {len(states)} states of dim {len(states[0])}")
# Fidelity between states
f_same = compute_fidelity(states[0], states[0])
f_diff = compute_fidelity(states[0], states[1])
print(f"\nFidelity (same state): {f_same:.6f}")
print(f"Fidelity (diff states): {f_diff:.6f}")
State dimension: 8, norm: 1.0000000000 Batch: 5 states of dim 8 Fidelity (same state): 1.000000 Fidelity (diff states): 0.079034
# Partial trace and entropy
enc = QAOAEncoding(n_features=3, reps=2, entanglement="full")
x = np.array([0.5, 1.0, 1.5])
state = simulate_encoding_statevector(enc, x)
# Reduced density matrix for qubit 0
rho_0 = partial_trace_single_qubit(state, n_qubits=3, keep_qubit=0)
print(f"Reduced density matrix (qubit 0):")
print(np.round(rho_0, 4))
# Purity and entropy
purity = compute_purity(rho_0)
vn_entropy = compute_von_neumann_entropy(rho_0)
lin_entropy = compute_linear_entropy(rho_0)
print(f"\nPurity: {purity:.6f}")
print(f"Von Neumann entropy: {vn_entropy:.6f}")
print(f"Linear entropy: {lin_entropy:.6f}")
print(f"\nNote: purity=1 -> pure state (no entanglement with rest)")
print(f" purity<1 -> mixed state (entangled with rest)")
Reduced density matrix (qubit 0):
[[0.771 +0.j 0.1436+0.1843j]
[0.1436-0.1843j 0.229 -0.j ]]
Purity: 0.756110
Von Neumann entropy: 0.589848
Linear entropy: 0.243890
Note: purity=1 -> pure state (no entanglement with rest)
purity<1 -> mixed state (entangled with rest)
# Random parameter generation
params = generate_random_parameters(4, n_samples=5, seed=42)
print(f"Random parameters shape: {params.shape}")
print(f"Range: [{params.min():.4f}, {params.max():.4f}]")
Random parameters shape: (5, 4) Range: [0.4010, 6.1300]
15. Visualization & Comparison¶
from encoding_atlas.visualization import compare_encodings
# Text comparison
text_output = compare_encodings(
["qaoa", "iqp", "zz_feature_map", "hardware_efficient"],
n_features=4,
output="text"
)
print(text_output)
┌────────────────────────────────────────────────────────────────────────────┐ │ ENCODING COMPARISON (n_features=4) │ ├────────────────────────────────────────────────────────────────────────────┤ │ │ │ QUBITS CIRCUIT DEPTH │ │ ────── ───────────── │ │ qaoa ███████████████ 4 qaoa ██████ │ │ iqp ███████████████ 4 iqp ████ │ │ zz_feature_map ███████████████ 4 zz_feature_map █████████████│ │ hardware_efficient ███████████████ 4 hardware_efficient ██ │ │ │ │ GATE COUNT TWO-QUBIT GATES │ │ ────────── ─────────────── │ │ qaoa ███████ 26 qaoa ███ │ │ iqp ███████████████ 52 iqp █████████████│ │ zz_feature_map ███████████████ 52 zz_feature_map █████████████│ │ hardware_efficient ████ 14 hardware_efficient ███ │ │ │ │ PROPERTIES │ │ ────────── │ │ Encoding Entangling Simulability Trainability │ │ ─────────────────────────────────────────────────────────────────────── │ │ qaoa ✓ Yes Not Simulable ██████ 0.8 │ │ iqp ✓ Yes Not Simulable █████ 0.7 │ │ zz_feature_map ✓ Yes Not Simulable █████ 0.6 │ │ hardware_efficient ✓ Yes Not Simulable ██████ 0.8 │ │ │ └────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────┐ │ ENCODING COMPARISON (n_features=4) │ ├────────────────────────────────────────────────────────────────────────────┤ │ │ │ QUBITS CIRCUIT DEPTH │ │ ────── ───────────── │ │ qaoa ███████████████ 4 qaoa ██████ │ │ iqp ███████████████ 4 iqp ████ │ │ zz_feature_map ███████████████ 4 zz_feature_map █████████████│ │ hardware_efficient ███████████████ 4 hardware_efficient ██ │ │ │ │ GATE COUNT TWO-QUBIT GATES │ │ ────────── ─────────────── │ │ qaoa ███████ 26 qaoa ███ │ │ iqp ███████████████ 52 iqp █████████████│ │ zz_feature_map ███████████████ 52 zz_feature_map █████████████│ │ hardware_efficient ████ 14 hardware_efficient ███ │ │ │ │ PROPERTIES │ │ ────────── │ │ Encoding Entangling Simulability Trainability │ │ ─────────────────────────────────────────────────────────────────────── │ │ qaoa ✓ Yes Not Simulable ██████ 0.8 │ │ iqp ✓ Yes Not Simulable █████ 0.7 │ │ zz_feature_map ✓ Yes Not Simulable █████ 0.6 │ │ hardware_efficient ✓ Yes Not Simulable ██████ 0.8 │ │ │ └────────────────────────────────────────────────────────────────────────────┘
# Compare QAOA variants
text_output = compare_encodings(
["qaoa", "qaoa", "qaoa"],
n_features=4,
configs={
"qaoa": {"reps": 2, "entanglement": "linear"},
},
output="text"
)
print(text_output)
┌────────────────────────────────────────────────────────────────────────────┐ │ ENCODING COMPARISON (n_features=4) │ ├────────────────────────────────────────────────────────────────────────────┤ │ │ │ QUBITS CIRCUIT DEPTH │ │ ────── ───────────── │ │ qaoa ███████████████ 4 qaoa ███████████████ 9 │ │ qaoa ███████████████ 4 qaoa ███████████████ 9 │ │ qaoa ███████████████ 4 qaoa ███████████████ 9 │ │ │ │ GATE COUNT TWO-QUBIT GATES │ │ ────────── ─────────────── │ │ qaoa ███████████████ 26 qaoa ███████████████ 6 │ │ qaoa ███████████████ 26 qaoa ███████████████ 6 │ │ qaoa ███████████████ 26 qaoa ███████████████ 6 │ │ │ │ PROPERTIES │ │ ────────── │ │ Encoding Entangling Simulability Trainability │ │ ───────────────────────────────────────────────────────── │ │ qaoa ✓ Yes Not Simulable ██████ 0.8 │ │ qaoa ✓ Yes Not Simulable ██████ 0.8 │ │ qaoa ✓ Yes Not Simulable ██████ 0.8 │ │ │ └────────────────────────────────────────────────────────────────────────────┘ ┌────────────────────────────────────────────────────────────────────────────┐ │ ENCODING COMPARISON (n_features=4) │ ├────────────────────────────────────────────────────────────────────────────┤ │ │ │ QUBITS CIRCUIT DEPTH │ │ ────── ───────────── │ │ qaoa ███████████████ 4 qaoa ███████████████ 9 │ │ qaoa ███████████████ 4 qaoa ███████████████ 9 │ │ qaoa ███████████████ 4 qaoa ███████████████ 9 │ │ │ │ GATE COUNT TWO-QUBIT GATES │ │ ────────── ─────────────── │ │ qaoa ███████████████ 26 qaoa ███████████████ 6 │ │ qaoa ███████████████ 26 qaoa ███████████████ 6 │ │ qaoa ███████████████ 26 qaoa ███████████████ 6 │ │ │ │ PROPERTIES │ │ ────────── │ │ Encoding Entangling Simulability Trainability │ │ ───────────────────────────────────────────────────────── │ │ qaoa ✓ Yes Not Simulable ██████ 0.8 │ │ qaoa ✓ Yes Not Simulable ██████ 0.8 │ │ qaoa ✓ Yes Not Simulable ██████ 0.8 │ │ │ └────────────────────────────────────────────────────────────────────────────┘
16. Registry Access - Factory Pattern¶
from encoding_atlas import list_encodings, get_encoding
# List all registered encodings
all_encodings = list_encodings()
print(f"All {len(all_encodings)} registered encodings:")
for name in all_encodings:
print(f" - {name}")
# Create QAOA via registry
enc = get_encoding("qaoa", n_features=4, reps=3, entanglement="full")
print(f"\nCreated via registry: {enc}")
All 26 registered encodings: - amplitude - angle - angle_ry - basis - covariant - covariant_feature_map - cyclic_equivariant - cyclic_equivariant_feature_map - data_reuploading - hamiltonian - hamiltonian_encoding - hardware_efficient - higher_order_angle - iqp - pauli_feature_map - qaoa - qaoa_encoding - so2_equivariant - so2_equivariant_feature_map - swap_equivariant - swap_equivariant_feature_map - symmetry_inspired - symmetry_inspired_feature_map - trainable - trainable_encoding - zz_feature_map Created via registry: QAOAEncoding(n_features=4, reps=3, data_rotation='Z', mixer_rotation='X', entanglement='full', entangling_gate='cz', gamma=1.0, beta=1.0, include_initial_h=True, feature_map='linear')
17. Edge Cases & Input Validation¶
The library provides thorough input validation with helpful error messages.
17.1 Constructor Validation¶
# Invalid n_features
for invalid_n in [0, -1]:
try:
QAOAEncoding(n_features=invalid_n)
except ValueError as e:
print(f"n_features={invalid_n}: {e}")
# Non-integer n_features
try:
QAOAEncoding(n_features=3.5)
except TypeError as e:
print(f"n_features=3.5: {e}")
# Invalid reps
try:
QAOAEncoding(n_features=4, reps=0)
except ValueError as e:
print(f"reps=0: {e}")
try:
QAOAEncoding(n_features=4, reps="two")
except TypeError as e:
print(f"reps='two': {e}")
n_features=0: n_features must be at least 1, got 0 n_features=-1: n_features must be at least 1, got -1 n_features=3.5: n_features must be an integer, got float reps=0: reps must be at least 1, got 0 reps='two': reps must be an integer, got str
# Invalid rotation axes
try:
QAOAEncoding(n_features=4, data_rotation="W")
except ValueError as e:
print(f"data_rotation='W': {e}")
try:
QAOAEncoding(n_features=4, mixer_rotation=123)
except TypeError as e:
print(f"mixer_rotation=123: {e}")
# Invalid entanglement
try:
QAOAEncoding(n_features=4, entanglement="star")
except ValueError as e:
print(f"entanglement='star': {e}")
# Invalid entangling_gate
try:
QAOAEncoding(n_features=4, entangling_gate="swap")
except ValueError as e:
print(f"entangling_gate='swap': {e}")
data_rotation='W': data_rotation must be one of ['X', 'Y', 'Z'], got 'W' mixer_rotation=123: mixer_rotation must be a string, got int entanglement='star': entanglement must be one of ['circular', 'full', 'linear', 'none'], got 'star' entangling_gate='swap': entangling_gate must be one of ['cx', 'cz', 'rzz'], got 'swap'
# Invalid gamma/beta
try:
QAOAEncoding(n_features=4, gamma=float('inf'))
except ValueError as e:
print(f"gamma=inf: {e}")
try:
QAOAEncoding(n_features=4, beta=float('nan'))
except ValueError as e:
print(f"beta=nan: {e}")
try:
QAOAEncoding(n_features=4, gamma="1.0")
except TypeError as e:
print(f"gamma='1.0': {e}")
# Invalid include_initial_h
try:
QAOAEncoding(n_features=4, include_initial_h=1)
except TypeError as e:
print(f"include_initial_h=1: {e}")
# Invalid feature_map
try:
QAOAEncoding(n_features=4, feature_map="cubic")
except ValueError as e:
print(f"feature_map='cubic': {e}")
gamma=inf: gamma must be finite, got inf beta=nan: beta must be finite, got nan gamma='1.0': gamma must be a number, got str include_initial_h=1: include_initial_h must be a bool, got int feature_map='cubic': feature_map must be one of ['linear', 'quadratic'], got 'cubic'
17.2 Input Validation for Circuit Generation¶
enc = QAOAEncoding(n_features=3)
# Wrong number of features
try:
enc.get_circuit(np.array([1.0, 2.0]), backend="pennylane")
except ValueError as e:
print(f"Wrong features: {e}")
# NaN input
try:
enc.get_circuit(np.array([1.0, float('nan'), 3.0]), backend="pennylane")
except ValueError as e:
print(f"NaN input: {e}")
# Infinity input
try:
enc.get_circuit(np.array([1.0, float('inf'), 3.0]), backend="pennylane")
except ValueError as e:
print(f"Inf input: {e}")
# String input
try:
enc.get_circuit(["0.5", "1.0", "1.5"], backend="pennylane")
except TypeError as e:
print(f"String input: {e}")
# Complex input
try:
enc.get_circuit(np.array([1+2j, 3+0j, 0+1j]), backend="pennylane")
except TypeError as e:
print(f"Complex input: {e}")
# 3D input
try:
enc.get_circuit(np.ones((2, 3, 1)), backend="pennylane")
except ValueError as e:
print(f"3D input: {e}")
Wrong features: Expected 3 features, got 2 NaN input: Input contains NaN or infinite values Inf input: Input contains NaN or infinite values String input: Input contains string values. Expected numeric data, got str. Convert strings to floats before encoding. Complex input: Input contains complex values (dtype: complex128). Complex numbers are not supported. Use real-valued data only. 3D input: Input must be 1D or 2D array, got 3D
17.3 Edge Case: Single Feature¶
# n_features=1 (single qubit, no entanglement possible)
enc = QAOAEncoding(n_features=1, reps=2, entanglement="linear")
print(f"Single feature: n_qubits={enc.n_qubits}, depth={enc.depth}")
print(f"Entanglement pairs: {enc.get_entanglement_pairs()}")
print(f"Is entangling: {enc.properties.is_entangling}")
print(f"Simulability: {enc.properties.simulability}")
x = np.array([1.5])
circuit = enc.get_circuit(x, backend="pennylane")
print(f"\nCircuit callable: {callable(circuit)}")
# Verify it produces a valid statevector
state = simulate_encoding_statevector(enc, x)
print(f"State: {np.round(state, 6)}")
print(f"Norm: {np.linalg.norm(state):.10f}")
Single feature: n_qubits=1, depth=5 Entanglement pairs: [] Is entangling: False Simulability: simulable Circuit callable: True State: [ 0.172754-0.861765j -0.420765+0.224665j] Norm: 1.0000000000
17.4 Edge Case: Single Repetition¶
enc = QAOAEncoding(n_features=4, reps=1, entanglement="full")
print(f"reps=1: depth={enc.depth}, total_gates={enc.gate_count_breakdown()['total']}")
# Compare with more reps
for r in [1, 2, 3]:
enc = QAOAEncoding(n_features=4, reps=r)
bd = enc.gate_count_breakdown()
print(f"reps={r}: H={bd['hadamard']}, data={bd['data_rotation']}, "
f"mixer={bd['mixer_rotation']}, ent={bd['entangling']}")
reps=1: depth=6, total_gates=18 reps=1: H=4, data=4, mixer=4, ent=3 reps=2: H=4, data=8, mixer=8, ent=6 reps=3: H=4, data=12, mixer=12, ent=9
17.5 Edge Case: Two Features (Minimal Entanglement)¶
# n_features=2 with different entanglement patterns
for ent in ["none", "linear", "circular", "full"]:
enc = QAOAEncoding(n_features=2, reps=2, entanglement=ent)
pairs = enc.get_entanglement_pairs()
print(f"n=2, {ent:8s}: pairs={pairs}, depth={enc.depth}")
n=2, none : pairs=[], depth=5 n=2, linear : pairs=[(0, 1)], depth=7 n=2, circular: pairs=[(0, 1)], depth=9 n=2, full : pairs=[(0, 1)], depth=7
17.6 Warning: Deep Circuits (reps > 10)¶
# Deep circuit warning (reps > 10 triggers barren plateau warning)
import warnings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
enc = QAOAEncoding(n_features=4, reps=15)
if w:
print(f"Warning issued for reps=15:")
for warning in w:
print(f" {warning.category.__name__}: {warning.message}")
else:
print("No warning (unexpected)")
Warning issued for reps=15: UserWarning: reps=15 creates a deep circuit that may suffer from barren plateaus, making training difficult. Consider reps <= 10 for practical trainability. See: McClean et al. (2018) 'Barren plateaus in quantum neural network training landscapes'.
17.7 Warning: Full Entanglement with Many Features¶
# Full entanglement warning (n_features >= 12)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
enc = QAOAEncoding(n_features=12, entanglement="full", reps=2)
if w:
print(f"Warning for n_features=12, entanglement='full':")
for warning in w:
print(f" {warning.message}")
else:
print("No warning")
Warning for n_features=12, entanglement='full': Full entanglement with n_features=12 creates 66 entangling gate pairs per layer (132 total with reps=2). This may exceed NISQ device capabilities. Consider 'linear' or 'circular' entanglement for better hardware compatibility and reduced circuit depth.
17.8 Warning: Large Input Values¶
# Input range warning (rotation angles > 2pi)
enc = QAOAEncoding(n_features=3, gamma=10.0)
x = np.array([5.0, 10.0, 15.0]) # gamma*x will be 50, 100, 150
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
circuit = enc.get_circuit(x, backend="pennylane")
if w:
print(f"Warning for large inputs:")
for warning in w:
print(f" {warning.message}")
Warning for large inputs:
Input values produce rotation angles outside recommended range. 3/3 features exceed |angle| > 2π (max angle: 150.00 rad ≈ 23.9 full rotations). This may cause precision loss due to angle wrapping. Consider normalizing inputs to [-2π, 2π] range. To suppress this warning, normalize your data or use warnings.filterwarnings('ignore', category=UserWarning).
17.9 Case Insensitivity¶
# Parameters are case-insensitive and normalized
enc_lower = QAOAEncoding(n_features=4, data_rotation="x", mixer_rotation="y", entanglement="LINEAR")
enc_upper = QAOAEncoding(n_features=4, data_rotation="X", mixer_rotation="Y", entanglement="linear")
print(f"Lowercase input -> data_rotation='{enc_lower.data_rotation}', mixer='{enc_lower.mixer_rotation}'")
print(f"Uppercase input -> data_rotation='{enc_upper.data_rotation}', mixer='{enc_upper.mixer_rotation}'")
print(f"Equal: {enc_lower == enc_upper}")
Lowercase input -> data_rotation='X', mixer='Y' Uppercase input -> data_rotation='X', mixer='Y' Equal: True
17.10 from_dict Error Handling¶
# Wrong class name in from_dict
try:
QAOAEncoding.from_dict({"class": "IQPEncoding", "n_features": 4})
except ValueError as e:
print(f"Wrong class: {e}")
# from_dict with missing 'class' key (defaults to 'QAOAEncoding')
enc = QAOAEncoding.from_dict({"n_features": 4, "reps": 3})
print(f"No 'class' key -> {enc}")
Wrong class: Config specifies class 'IQPEncoding', expected 'QAOAEncoding'. Use the appropriate class's from_dict method. No 'class' key -> QAOAEncoding(n_features=4, reps=3, data_rotation='Z', mixer_rotation='X', entanglement='linear', entangling_gate='cz', gamma=1.0, beta=1.0, include_initial_h=True, feature_map='linear')
17.11 Invalid Backend¶
enc = QAOAEncoding(n_features=3)
x = np.array([0.5, 1.0, 1.5])
try:
enc.get_circuit(x, backend="tensorflow")
except ValueError as e:
print(f"Invalid backend: {e}")
try:
enc.get_depth(backend="tensorflow")
except ValueError as e:
print(f"Invalid backend for depth: {e}")
Invalid backend: Unknown backend 'tensorflow'. Supported backends: 'pennylane', 'qiskit', 'cirq' Invalid backend for depth: Unknown backend 'tensorflow'. Supported backends: 'pennylane', 'qiskit', 'cirq'
17.12 Numpy Integer Types¶
# numpy integer types are accepted
enc = QAOAEncoding(n_features=np.int32(4), reps=np.int64(2))
print(f"numpy ints: n_features={enc.n_features}, reps={enc.reps}")
# numpy float types for gamma/beta
enc = QAOAEncoding(n_features=4, gamma=np.float32(1.5), beta=np.float64(0.8))
print(f"numpy floats: gamma={enc.gamma}, beta={enc.beta}")
numpy ints: n_features=4, reps=2 numpy floats: gamma=1.5, beta=0.8
18. Advanced Usage¶
18.1 Debug Logging¶
import logging
# Enable debug logging for QAOA encoding
logger = logging.getLogger('encoding_atlas.encodings.qaoa_encoding')
logger.setLevel(logging.DEBUG)
# Add handler to see output
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
# Now operations will produce debug output
enc = QAOAEncoding(n_features=3, reps=1, entanglement="linear")
x = np.array([0.5, 1.0, 1.5])
_ = enc.get_circuit(x, backend="pennylane")
# Clean up
logger.removeHandler(handler)
logger.setLevel(logging.WARNING)
encoding_atlas.encodings.qaoa_encoding - DEBUG - QAOAEncoding initialized: n_features=3, n_qubits=3, reps=1, data_rotation=Z, mixer_rotation=X, entanglement=linear, entangling_gate=cz, gamma=1.0000, beta=1.0000, include_initial_h=True, feature_map=linear, n_entanglement_pairs=2 encoding_atlas.encodings.qaoa_encoding - DEBUG - Generating circuit for backend=pennylane, input_shape=(3,), input_range=[0.5000, 1.5000]
18.2 Thread Safety¶
import threading
enc = QAOAEncoding(n_features=4, reps=2)
results = {}
errors = []
def worker(thread_id, x):
try:
# Properties are thread-safe (double-checked locking)
props = enc.properties
# Circuit generation is thread-safe (stateless)
circuit = enc.get_circuit(x, backend="pennylane")
results[thread_id] = (props.gate_count, callable(circuit))
except Exception as e:
errors.append((thread_id, str(e)))
# Launch concurrent threads
threads = []
for i in range(10):
x = np.random.rand(4) * np.pi
t = threading.Thread(target=worker, args=(i, x))
threads.append(t)
t.start()
for t in threads:
t.join()
print(f"Threads completed: {len(results)}")
print(f"Errors: {len(errors)}")
print(f"All got same gate count: {len(set(r[0] for r in results.values())) == 1}")
print(f"All circuits callable: {all(r[1] for r in results.values())}")
Threads completed: 10 Errors: 0 All got same gate count: True All circuits callable: True
18.3 Config Access (Read-Only)¶
enc = QAOAEncoding(n_features=4, reps=3, entangling_gate="rzz", gamma=2.0)
# Config returns a COPY (read-only pattern)
config = enc.config
print(f"Config: {config}")
# Modifying the copy doesn't affect the encoding
config['reps'] = 99
print(f"\nAfter modifying copy:")
print(f" config['reps'] = {config['reps']}")
print(f" enc.reps = {enc.reps} (unchanged)")
Config: {'reps': 3, 'data_rotation': 'Z', 'mixer_rotation': 'X', 'entanglement': 'linear', 'entangling_gate': 'rzz', 'gamma': 2.0, 'beta': 1.0, 'include_initial_h': True, 'feature_map': 'linear'}
After modifying copy:
config['reps'] = 99
enc.reps = 3 (unchanged)
18.4 Repr and Str¶
enc = QAOAEncoding(
n_features=4, reps=3, data_rotation="Y", mixer_rotation="Z",
entanglement="full", entangling_gate="rzz", gamma=2.0, beta=0.5,
include_initial_h=False, feature_map="quadratic"
)
print(f"repr: {repr(enc)}")
print(f"str: {str(enc)}")
repr: QAOAEncoding(n_features=4, reps=3, data_rotation='Y', mixer_rotation='Z', entanglement='full', entangling_gate='rzz', gamma=2.0, beta=0.5, include_initial_h=False, feature_map='quadratic') str: QAOAEncoding(n_features=4, reps=3, data_rotation='Y', mixer_rotation='Z', entanglement='full', entangling_gate='rzz', gamma=2.0, beta=0.5, include_initial_h=False, feature_map='quadratic')
from encoding_atlas.core.exceptions import (
EncodingError,
ValidationError,
BackendError,
RegistryError,
AnalysisError,
SimulationError,
ConvergenceError,
NumericalInstabilityError,
InsufficientSamplesError,
)
# Hierarchy demonstration
print("Exception hierarchy:")
print(" EncodingError (base)")
print(" ├── ValidationError")
print(" ├── BackendError")
print(" ├── RegistryError")
print(" └── AnalysisError")
print(" ├── SimulationError")
print(" ├── ConvergenceError")
print(" ├── NumericalInstabilityError")
print(" └── InsufficientSamplesError")
# Catching all encoding-related exceptions
enc = QAOAEncoding(n_features=3)
try:
enc.get_circuit(np.array([1.0, 2.0]), backend="pennylane") # Wrong size
except EncodingError as e:
print(f"\nCaught EncodingError: {type(e).__name__}: {e}")
except ValueError as e:
print(f"\nCaught ValueError: {e}")
Exception hierarchy:
EncodingError (base)
├── ValidationError
├── BackendError
├── RegistryError
└── AnalysisError
├── SimulationError
├── ConvergenceError
├── NumericalInstabilityError
└── InsufficientSamplesError
Caught ValueError: Expected 3 features, got 2
20. Encoding Recommendation Guide¶
from encoding_atlas.guide import recommend_encoding
from encoding_atlas import get_encoding
# Get recommendation for different scenarios
scenarios = [
{"n_features": 4, "n_samples": 500, "priority": "accuracy"},
{"n_features": 4, "n_samples": 500, "priority": "speed"},
{"n_features": 8, "n_samples": 1000, "priority": "accuracy"},
]
for scenario in scenarios:
rec = recommend_encoding(**scenario)
print(f"Scenario {scenario}:")
print(f" Recommended: {rec.encoding_name}")
print(f" Reason: {rec.explanation}")
print()
Scenario {'n_features': 4, 'n_samples': 500, 'priority': 'accuracy'}:
Recommended: iqp
Reason: IQP encoding creates highly entangled states with provable classical simulation hardness, well-suited for kernel methods
Scenario {'n_features': 4, 'n_samples': 500, 'priority': 'speed'}:
Recommended: angle
Reason: Angle encoding provides O(1) depth with simple rotations, ideal for speed
Scenario {'n_features': 8, 'n_samples': 1000, 'priority': 'accuracy'}:
Recommended: zz_feature_map
Reason: ZZ Feature Map provides standard pairwise feature interactions via (pi-x_i)(pi-x_j) phase encoding for kernel methods
21. Benchmark Datasets¶
from encoding_atlas.benchmark import list_datasets, get_dataset
print(f"Available datasets: {list_datasets()}")
for name in list_datasets():
X, y = get_dataset(name, n_samples=100, seed=42)
print(f"\n{name}:")
print(f" X: shape={X.shape}, dtype={X.dtype}, range=[{X.min():.3f}, {X.max():.3f}]")
print(f" y: shape={y.shape}, classes={sorted(set(y))}")
Available datasets: ['iris', 'moons', 'circles', 'linear', 'xor'] iris: X: shape=(100, 2), dtype=float64, range=[2.000, 7.000] y: shape=(100,), classes=[np.int64(0), np.int64(1)] moons: X: shape=(100, 2), dtype=float64, range=[-1.175, 2.270] y: shape=(100,), classes=[np.int64(0), np.int64(1)] circles: X: shape=(100, 2), dtype=float64, range=[-1.167, 1.158] y: shape=(100,), classes=[np.int64(0), np.int64(1)] linear: X: shape=(100, 2), dtype=float64, range=[-2.132, 2.914] y: shape=(100,), classes=[np.int64(0), np.int64(1)] xor: X: shape=(100, 2), dtype=float64, range=[-2.132, 2.914] y: shape=(100,), classes=[np.int64(0), np.int64(1)]
# Step 1: Define the encoding
enc = QAOAEncoding(
n_features=4,
reps=2,
data_rotation="Z",
mixer_rotation="X",
entanglement="circular",
entangling_gate="cz",
gamma=1.0,
beta=np.pi/4,
include_initial_h=True,
feature_map="linear",
)
print("Step 1: Encoding created")
print(f" {enc}")
print(f" n_qubits={enc.n_qubits}, depth={enc.depth}")
# Step 2: Analyze resources
summary = enc.resource_summary()
print(f"\nStep 2: Resource Analysis")
print(f" Total gates: {summary['gate_counts']['total']}")
print(f" Two-qubit gates: {summary['gate_counts']['total_two_qubit']}")
print(f" Entanglement pairs: {summary['n_entanglement_pairs']}")
# Step 3: Check properties
props = enc.properties
print(f"\nStep 3: Properties")
print(f" Entangling: {props.is_entangling}")
print(f" Simulability: {props.simulability}")
print(f" Trainability: {props.trainability_estimate:.4f}")
Step 1: Encoding created QAOAEncoding(n_features=4, reps=2, data_rotation='Z', mixer_rotation='X', entanglement='circular', entangling_gate='cz', gamma=1.0, beta=0.7853981633974483, include_initial_h=True, feature_map='linear') n_qubits=4, depth=9 Step 2: Resource Analysis Total gates: 28 Two-qubit gates: 8 Entanglement pairs: 4 Step 3: Properties Entangling: True Simulability: not_simulable Trainability: 0.7378
# Step 4: Generate circuits for data
np.random.seed(42)
X_train = np.random.rand(20, 4) * np.pi
circuits = enc.get_circuits(X_train, backend="pennylane")
print(f"Step 4: Generated {len(circuits)} circuits")
# Step 5: Simulate statevectors
from encoding_atlas.analysis import (
simulate_encoding_statevectors_batch,
compute_fidelity,
)
states = simulate_encoding_statevectors_batch(enc, X_train[:5])
print(f"\nStep 5: Simulated {len(states)} statevectors")
# Step 6: Analyze pairwise fidelities (kernel matrix)
n_samples = 5
kernel = np.zeros((n_samples, n_samples))
for i in range(n_samples):
for j in range(n_samples):
kernel[i, j] = compute_fidelity(states[i], states[j])
print(f"\nStep 6: Quantum kernel matrix ({n_samples}x{n_samples})")
print(np.round(kernel, 4))
# Verify diagonal is 1 (self-fidelity)
assert np.allclose(np.diag(kernel), 1.0)
print("\nDiagonal = 1.0 (self-fidelity) confirmed!")
Step 4: Generated 20 circuits Step 5: Simulated 5 statevectors Step 6: Quantum kernel matrix (5x5) [[1. 0.1181 0.0594 0.013 0.0555] [0.1181 1. 0.0662 0.1963 0.0261] [0.0594 0.0662 1. 0.097 0.0381] [0.013 0.1963 0.097 1. 0.0707] [0.0555 0.0261 0.0381 0.0707 1. ]] Diagonal = 1.0 (self-fidelity) confirmed!
# Step 7: Analysis suite
from encoding_atlas.analysis import (
check_simulability,
compute_expressibility,
compute_entanglement_capability,
estimate_trainability,
)
print("Step 7: Full Analysis Suite")
print("=" * 50)
sim = check_simulability(enc)
print(f"\nSimulability: {sim['is_simulable']} ({sim.get('reason', 'N/A')})")
expr = compute_expressibility(enc, n_samples=300, seed=42)
print(f"Expressibility: {expr:.6f}")
ent_score = compute_entanglement_capability(enc, n_samples=200, seed=42)
print(f"Entanglement (MW): {ent_score:.6f}")
train = estimate_trainability(enc, n_samples=100, seed=42)
print(f"Trainability: {train:.6f}")
# Step 8: Serialize for reproducibility
config = enc.to_dict()
print(f"\nStep 8: Configuration saved")
print(f" {config}")
Step 7: Full Analysis Suite
==================================================
Simulability: False (Circular entanglement structure may allow tensor network simulation if entanglement entropy is bounded)
Expressibility: 0.975072
Entanglement (MW): 0.662323
Trainability: 0.022267
Step 8: Configuration saved
{'class': 'QAOAEncoding', 'n_features': 4, 'reps': 2, 'data_rotation': 'Z', 'mixer_rotation': 'X', 'entanglement': 'circular', 'entangling_gate': 'cz', 'gamma': 1.0, 'beta': 0.7853981633974483, 'include_initial_h': True, 'feature_map': 'linear'}
Summary¶
This notebook covered the complete feature set of QAOAEncoding in the encoding-atlas library:
| Feature | Description |
|---|---|
| 10 Constructor Parameters | n_features, reps, data_rotation, mixer_rotation, entanglement, entangling_gate, gamma, beta, include_initial_h, feature_map |
| 3 Backend Support | PennyLane (callable), Qiskit (QuantumCircuit), Cirq (Circuit with Moments) |
| Batch Processing | Sequential and parallel (ThreadPoolExecutor) circuit generation |
| Properties | n_qubits, depth, gate_count, simulability, trainability_estimate |
| Gate Count Breakdown | GateCountBreakdown TypedDict with 7 fields |
| Layer Info | Detailed per-layer gate information |
| Resource Summary | Comprehensive resource + hardware requirements dict |
| 4 Entanglement Patterns | none, linear, circular, full (with optimal depth via graph theory) |
| 3 Entangling Gates | cx, cz, rzz |
| 2 Feature Maps | linear (gammax), quadratic (gammax^2) |
| Data Angle Computation | compute_data_angles() for debugging |
| Depth Analysis | Theoretical (property) + backend-specific (get_depth) |
| Serialization | to_dict/from_dict (JSON), pickle with lock handling |
| Copy with Overrides | copy(**kwargs) for creating variations |
| Equality & Hashing | == comparison, set/dict support |
| 2 Protocols | ResourceAnalyzable, EntanglementQueryable |
| Analysis Tools | Expressibility, entanglement, trainability, simulability, resources |
| Simulation Utilities | Statevector, fidelity, partial trace, entropy |
| Visualization | compare_encodings() for side-by-side comparison |
| Registry | Factory pattern via get_encoding("qaoa", ...) |
| Recommendation | Rule-based encoding recommendation guide |
| Benchmarks | Standard benchmark datasets |
| Input Validation | Shape, type, range, NaN/Inf, complex, string checks |
| Warnings | Deep circuits, full entanglement scaling, input range |
| Logging | Per-module debug logging support |
| Thread Safety | Double-checked locking, stateless circuit generation |
For more information, see the documentation.