Embeddings API
This page documents the quantum embedding classes and functions.
Base Classes
BaseEmbedding
BaseEmbedding(n_qubits, backend, **kwargs)
Bases: ABC
Abstract base class for quantum data embeddings.
All embedding implementations should inherit from this class and implement the required abstract methods.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_qubits
|
int
|
Number of qubits in the embedding circuit |
required |
backend
|
object
|
Backend instance for quantum circuit execution |
required |
Source code in quantum_data_embedding_suite\embeddings\base.py
Attributes
depth
property
Get the depth of the embedding circuit.
Returns:
Name | Type | Description |
---|---|---|
depth |
int
|
Circuit depth (number of layers) |
n_parameters
property
Get the number of trainable parameters in the embedding.
Returns:
Name | Type | Description |
---|---|---|
n_params |
int
|
Number of trainable parameters |
Functions
clear_cache()
create_circuit(x)
abstractmethod
Create a quantum circuit for embedding a single data point.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array-like of shape (n_features,)
|
Classical data point to embed |
required |
Returns:
Name | Type | Description |
---|---|---|
circuit |
quantum circuit object
|
Backend-specific quantum circuit |
Source code in quantum_data_embedding_suite\embeddings\base.py
create_circuit_cached(x)
Create circuit with caching support.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array - like
|
Input data point |
required |
Returns:
Name | Type | Description |
---|---|---|
circuit |
quantum circuit object
|
Backend-specific quantum circuit |
Source code in quantum_data_embedding_suite\embeddings\base.py
fit(X)
Fit the embedding to training data (if needed).
Some embeddings may need to fit parameters based on the data distribution. Default implementation does nothing.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
X
|
array-like of shape (n_samples, n_features)
|
Training data |
required |
Returns:
Name | Type | Description |
---|---|---|
self |
BaseEmbedding
|
Returns self for method chaining |
Source code in quantum_data_embedding_suite\embeddings\base.py
get_circuit_hash(x)
Generate a hash for circuit caching.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array - like
|
Input data point |
required |
Returns:
Name | Type | Description |
---|---|---|
hash_str |
str
|
Hash string for the circuit |
Source code in quantum_data_embedding_suite\embeddings\base.py
get_feature_dimension()
abstractmethod
Get the expected dimension of input features.
Returns:
Name | Type | Description |
---|---|---|
n_features |
int
|
Number of classical features this embedding expects |
get_info()
Get information about the embedding.
Returns:
Name | Type | Description |
---|---|---|
info |
dict
|
Dictionary containing embedding information |
Source code in quantum_data_embedding_suite\embeddings\base.py
validate_input(x)
Validate and preprocess input data.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array - like
|
Input data point |
required |
Returns:
Name | Type | Description |
---|---|---|
x |
ndarray
|
Validated and preprocessed data |
Source code in quantum_data_embedding_suite\embeddings\base.py
Embedding Implementations
AngleEmbedding
AngleEmbedding(n_qubits, backend, rotation='Y', entangling='linear', depth=1, **kwargs)
Bases: BaseEmbedding
Angle embedding encodes classical data as rotation angles in quantum circuits.
Features are encoded as parameters to rotation gates (RX, RY, RZ). This is one of the most common and straightforward embedding methods.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_qubits
|
int
|
Number of qubits in the circuit |
required |
backend
|
object
|
Backend instance for quantum circuit execution |
required |
rotation
|
str
|
Type of rotation gate ('X', 'Y', 'Z', or 'XYZ' for all) |
'Y'
|
entangling
|
str
|
Entangling pattern ('linear', 'circular', 'full', 'none') |
'linear'
|
depth
|
int
|
Number of encoding layers |
1
|
Examples:
>>> from quantum_data_embedding_suite.backends import QiskitBackend
>>> backend = QiskitBackend()
>>> embedding = AngleEmbedding(n_qubits=4, backend=backend)
>>> circuit = embedding.create_circuit([0.1, 0.2, 0.3, 0.4])
Source code in quantum_data_embedding_suite\embeddings\angle.py
Functions
create_circuit(x)
Create angle embedding circuit for a data point.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array-like of shape (n_features,)
|
Classical data point to embed |
required |
Returns:
Name | Type | Description |
---|---|---|
circuit |
quantum circuit object
|
Backend-specific quantum circuit with angle encoding |
Source code in quantum_data_embedding_suite\embeddings\angle.py
get_feature_dimension()
Get the expected number of input features.
For angle embedding, each qubit can encode one feature per layer.
Returns:
Name | Type | Description |
---|---|---|
n_features |
int
|
Number of features (n_qubits * depth) |
Source code in quantum_data_embedding_suite\embeddings\angle.py
AmplitudeEmbedding
AmplitudeEmbedding(n_qubits, backend, normalize=True, padding='zero', **kwargs)
Bases: BaseEmbedding
Amplitude embedding encodes classical data into quantum state amplitudes.
Classical data is directly encoded as the amplitudes of a quantum state. This provides an exponential advantage in data density but requires normalization and specific data sizes (powers of 2).
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_qubits
|
int
|
Number of qubits in the circuit |
required |
backend
|
object
|
Backend instance for quantum circuit execution |
required |
normalize
|
bool
|
Whether to normalize the input data to unit norm |
True
|
padding
|
str
|
How to handle data that doesn't fit 2^n_qubits ('zero', 'repeat', 'truncate') |
'zero'
|
Examples:
>>> from quantum_data_embedding_suite.backends import QiskitBackend
>>> backend = QiskitBackend()
>>> embedding = AmplitudeEmbedding(n_qubits=3, backend=backend)
>>> circuit = embedding.create_circuit([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8])
Source code in quantum_data_embedding_suite\embeddings\amplitude.py
Functions
create_circuit(x)
Create amplitude embedding circuit for a data point.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array-like of shape (n_features,)
|
Classical data point to embed as amplitudes |
required |
Returns:
Name | Type | Description |
---|---|---|
circuit |
quantum circuit object
|
Backend-specific quantum circuit with amplitude encoding |
Source code in quantum_data_embedding_suite\embeddings\amplitude.py
get_feature_dimension()
Get the expected number of input features.
For amplitude embedding, the number of features should be 2^n_qubits for optimal encoding, but can be flexible with padding.
Returns:
Name | Type | Description |
---|---|---|
n_features |
int
|
Maximum number of features (2^n_qubits) |
Source code in quantum_data_embedding_suite\embeddings\amplitude.py
validate_input(x)
Validate input for amplitude embedding.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array - like
|
Input data |
required |
Returns:
Name | Type | Description |
---|---|---|
x |
ndarray
|
Validated input data |
Source code in quantum_data_embedding_suite\embeddings\amplitude.py
IQPEmbedding
IQPEmbedding(n_qubits, backend, depth=3, interaction_pattern='all', **kwargs)
Bases: BaseEmbedding
IQP embedding uses diagonal unitaries to create expressive quantum feature maps.
IQP circuits consist of layers of Hadamard gates followed by diagonal gates with multi-qubit interactions. These circuits are known to be hard to simulate classically while remaining shallow, making them excellent for NISQ devices.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_qubits
|
int
|
Number of qubits in the circuit |
required |
backend
|
object
|
Backend instance for quantum circuit execution |
required |
depth
|
int
|
Number of IQP layers |
3
|
interaction_pattern
|
str
|
Pattern of qubit interactions ('all', 'linear', 'circular') |
'all'
|
Examples:
>>> from quantum_data_embedding_suite.backends import QiskitBackend
>>> backend = QiskitBackend()
>>> embedding = IQPEmbedding(n_qubits=4, backend=backend, depth=3)
>>> circuit = embedding.create_circuit([0.1, 0.2, 0.3, 0.4, 0.5, 0.6])
Source code in quantum_data_embedding_suite\embeddings\iqp.py
Functions
create_circuit(x)
Create IQP embedding circuit for a data point.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array-like of shape (n_features,)
|
Classical data point to embed |
required |
Returns:
Name | Type | Description |
---|---|---|
circuit |
quantum circuit object
|
Backend-specific quantum circuit with IQP encoding |
Source code in quantum_data_embedding_suite\embeddings\iqp.py
get_expressibility_bounds()
Get theoretical expressibility bounds for IQP circuits.
Returns:
Name | Type | Description |
---|---|---|
bounds |
tuple
|
(lower_bound, upper_bound) for expressibility |
Source code in quantum_data_embedding_suite\embeddings\iqp.py
get_feature_dimension()
Get the expected number of input features.
IQP embedding uses features for both single-qubit rotations and multi-qubit interactions.
Returns:
Name | Type | Description |
---|---|---|
n_features |
int
|
Number of features needed |
Source code in quantum_data_embedding_suite\embeddings\iqp.py
DataReuploadingEmbedding
DataReuploadingEmbedding(n_qubits, backend, depth=3, trainable_params=True, rotation_gates=None, entangling='linear', **kwargs)
Bases: BaseEmbedding
Data re-uploading embedding repeatedly encodes the same data across multiple layers.
This technique allows quantum circuits to achieve universal approximation properties by encoding data multiple times with trainable parameters interspersed between encoding layers.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_qubits
|
int
|
Number of qubits in the circuit |
required |
backend
|
object
|
Backend instance for quantum circuit execution |
required |
depth
|
int
|
Number of re-uploading layers |
3
|
trainable_params
|
bool
|
Whether to include trainable parameters between layers |
True
|
rotation_gates
|
list
|
Types of rotation gates to use |
['RX', 'RY', 'RZ']
|
entangling
|
str
|
Entangling pattern between layers |
'linear'
|
Examples:
>>> from quantum_data_embedding_suite.backends import QiskitBackend
>>> backend = QiskitBackend()
>>> embedding = DataReuploadingEmbedding(n_qubits=4, backend=backend, depth=3)
>>> circuit = embedding.create_circuit([0.1, 0.2, 0.3, 0.4])
Source code in quantum_data_embedding_suite\embeddings\data_reuploading.py
Functions
create_circuit(x)
Create data re-uploading circuit for a data point.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array-like of shape (n_features,)
|
Classical data point to embed |
required |
Returns:
Name | Type | Description |
---|---|---|
circuit |
quantum circuit object
|
Backend-specific quantum circuit with data re-uploading |
Source code in quantum_data_embedding_suite\embeddings\data_reuploading.py
fit(X)
Fit the embedding to training data.
For data re-uploading, this can optionally optimize the trainable parameters using the training data.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
X
|
array-like of shape (n_samples, n_features)
|
Training data |
required |
Returns:
Name | Type | Description |
---|---|---|
self |
DataReuploadingEmbedding
|
Returns self for method chaining |
Source code in quantum_data_embedding_suite\embeddings\data_reuploading.py
get_feature_dimension()
Get the expected number of input features.
For data re-uploading, the same features are used in each layer.
Returns:
Name | Type | Description |
---|---|---|
n_features |
int
|
Number of features (same as number of qubits) |
Source code in quantum_data_embedding_suite\embeddings\data_reuploading.py
get_parameters()
Get current trainable parameters.
Returns:
Name | Type | Description |
---|---|---|
theta |
ndarray
|
Current parameter values |
Source code in quantum_data_embedding_suite\embeddings\data_reuploading.py
update_parameters(new_theta)
Update trainable parameters.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
new_theta
|
array - like
|
New parameter values |
required |
Source code in quantum_data_embedding_suite\embeddings\data_reuploading.py
HamiltonianEmbedding
HamiltonianEmbedding(n_qubits, backend, hamiltonian_type='pauli_x', evolution_time=1.0, trotter_steps=1, initial_state='zero', custom_operators=None, **kwargs)
Bases: BaseEmbedding
Hamiltonian embedding encodes data through time evolution under a data-dependent Hamiltonian.
This physics-inspired approach uses the Hamiltonian H = Σ x_i * H_i where x_i are the classical features and H_i are Pauli operators. The quantum state is evolved using exp(-i * H * t) where t is an evolution time parameter.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
n_qubits
|
int
|
Number of qubits in the circuit |
required |
backend
|
object
|
Backend instance for quantum circuit execution |
required |
hamiltonian_type
|
str
|
Type of Hamiltonian ('pauli_x', 'pauli_y', 'pauli_z', 'ising', 'custom') |
'pauli_x'
|
evolution_time
|
float
|
Time parameter for Hamiltonian evolution |
1.0
|
trotter_steps
|
int
|
Number of Trotter steps for time evolution approximation |
1
|
initial_state
|
str
|
Initial quantum state ('zero', 'plus', 'random') |
'zero'
|
Examples:
>>> from quantum_data_embedding_suite.backends import QiskitBackend
>>> backend = QiskitBackend()
>>> embedding = HamiltonianEmbedding(n_qubits=4, backend=backend, hamiltonian_type='ising')
>>> circuit = embedding.create_circuit([0.1, 0.2, 0.3, 0.4])
Source code in quantum_data_embedding_suite\embeddings\hamiltonian.py
Functions
create_circuit(x)
Create Hamiltonian evolution circuit for a data point.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
x
|
array-like of shape (n_features,)
|
Classical data point (Hamiltonian coefficients) |
required |
Returns:
Name | Type | Description |
---|---|---|
circuit |
quantum circuit object
|
Backend-specific quantum circuit with Hamiltonian evolution |
Source code in quantum_data_embedding_suite\embeddings\hamiltonian.py
get_feature_dimension()
Get the expected number of input features.
Returns:
Name | Type | Description |
---|---|---|
n_features |
int
|
Number of features (equal to number of Hamiltonian terms) |
Source code in quantum_data_embedding_suite\embeddings\hamiltonian.py
Usage Examples
Basic Embedding Creation
from quantum_data_embedding_suite.embeddings import AngleEmbedding
import numpy as np
# Create an angle embedding
embedding = AngleEmbedding(
n_qubits=4,
rotation_axis='Y',
entangling_layers=1
)
# Embed a data point
data_point = np.array([0.1, 0.5, -0.3, 0.8])
circuit = embedding.embed(data_point)
# Get embedding information
info = embedding.get_info()
print(f"Circuit depth: {info['depth']}")
print(f"Number of parameters: {info['n_parameters']}")
Custom Embedding Creation
from quantum_data_embedding_suite.embeddings import BaseEmbedding
from qiskit import QuantumCircuit
class CustomEmbedding(BaseEmbedding):
def __init__(self, n_qubits, custom_param=1.0):
super().__init__(n_qubits)
self.custom_param = custom_param
def _build_circuit(self, data_point):
circuit = QuantumCircuit(self.n_qubits)
# Custom embedding logic
for i, x in enumerate(data_point):
circuit.ry(x * self.custom_param, i)
# Add entanglement
for i in range(self.n_qubits - 1):
circuit.cnot(i, i + 1)
return circuit
def get_info(self):
info = super().get_info()
info['custom_param'] = self.custom_param
return info
# Use custom embedding
custom_embedding = CustomEmbedding(n_qubits=4, custom_param=2.0)
circuit = custom_embedding.embed(data_point)
Embedding Comparison
from quantum_data_embedding_suite.embeddings import (
AngleEmbedding, AmplitudeEmbedding, IQPEmbedding
)
# Create different embeddings
embeddings = {
'angle': AngleEmbedding(n_qubits=4),
'amplitude': AmplitudeEmbedding(n_qubits=4),
'iqp': IQPEmbedding(n_qubits=4, depth=2)
}
# Compare circuit properties
data_point = np.random.randn(4)
for name, embedding in embeddings.items():
circuit = embedding.embed(data_point)
info = embedding.get_info()
print(f"{name.upper()} Embedding:")
print(f" Circuit depth: {circuit.depth()}")
print(f" Gate count: {circuit.size()}")
print(f" Parameters: {info.get('n_parameters', 0)}")
print()
Advanced Usage
Parameterized Embeddings
from quantum_data_embedding_suite.embeddings import DataReuploadingEmbedding
# Create trainable embedding
embedding = DataReuploadingEmbedding(
n_qubits=4,
n_layers=3,
rotation_gates=['RY', 'RZ'],
trainable=True
)
# Set parameters
parameters = np.random.randn(embedding.n_parameters)
embedding.set_parameters(parameters)
# Get current parameters
current_params = embedding.get_parameters()
print(f"Number of parameters: {len(current_params)}")
Embedding Optimization
from quantum_data_embedding_suite.optimization import optimize_embedding_parameters
# Optimize embedding parameters
best_params = optimize_embedding_parameters(
embedding=embedding,
X_train=X_train,
y_train=y_train,
objective='classification_accuracy',
n_trials=100
)
# Apply optimized parameters
embedding.set_parameters(best_params)
Batch Processing
# Process multiple data points
data_points = np.random.randn(100, 4)
# Method 1: Loop through data points
circuits = []
for data_point in data_points:
circuit = embedding.embed(data_point)
circuits.append(circuit)
# Method 2: Use batch method (if available)
if hasattr(embedding, 'embed_batch'):
circuits = embedding.embed_batch(data_points)
Integration with Backends
Using Different Backends
from quantum_data_embedding_suite.backends import QiskitBackend, PennyLaneBackend
# Qiskit backend
qiskit_backend = QiskitBackend(device='aer_simulator')
circuit = embedding.embed(data_point)
result = qiskit_backend.execute_circuit(circuit)
# PennyLane backend
pennylane_backend = PennyLaneBackend(device='default.qubit')
# Convert circuit for PennyLane
pennylane_circuit = pennylane_backend.convert_circuit(circuit)
Hardware Compatibility
# Check hardware compatibility
from quantum_data_embedding_suite.utils import check_hardware_compatibility
compatibility = check_hardware_compatibility(
embedding=embedding,
hardware_specs={
'n_qubits': 127,
'connectivity': 'heavy_hex',
'gate_fidelity': 0.999
}
)
print(f"Compatibility score: {compatibility['score']}")
if compatibility['compatible']:
print("Embedding is compatible with the hardware")
else:
print(f"Issues: {compatibility['issues']}")
Error Handling
Common Error Patterns
from quantum_data_embedding_suite.embeddings import AngleEmbedding
from quantum_data_embedding_suite.exceptions import EmbeddingError
try:
# Create embedding
embedding = AngleEmbedding(n_qubits=4)
# This will raise an error if data dimension doesn't match
wrong_size_data = np.array([1, 2, 3]) # Only 3 features, need 4
circuit = embedding.embed(wrong_size_data)
except EmbeddingError as e:
print(f"Embedding error: {e}")
# Handle by padding or truncating data
if len(wrong_size_data) < embedding.n_qubits:
# Pad with zeros
padded_data = np.pad(wrong_size_data,
(0, embedding.n_qubits - len(wrong_size_data)))
circuit = embedding.embed(padded_data)
else:
# Truncate
truncated_data = wrong_size_data[:embedding.n_qubits]
circuit = embedding.embed(truncated_data)
Validation and Debugging
# Validate embedding before use
def validate_embedding(embedding, data_sample):
"""Validate embedding with sample data"""
try:
# Test embedding
circuit = embedding.embed(data_sample)
# Check circuit properties
if circuit.depth() > 100:
print("Warning: Circuit depth is very high")
if circuit.size() > 1000:
print("Warning: Circuit has many gates")
# Test with backend
from quantum_data_embedding_suite.backends import QiskitBackend
backend = QiskitBackend(device='statevector_simulator')
result = backend.execute_circuit(circuit)
print("Embedding validation successful")
return True
except Exception as e:
print(f"Embedding validation failed: {e}")
return False
# Use validation
data_sample = np.random.randn(4)
is_valid = validate_embedding(embedding, data_sample)
Performance Considerations
Memory Usage
# Monitor memory usage for large embeddings
import psutil
import gc
def monitor_memory_usage(embedding, data_points):
"""Monitor memory usage during embedding"""
process = psutil.Process()
initial_memory = process.memory_info().rss / 1024 / 1024 # MB
circuits = []
for i, data_point in enumerate(data_points):
circuit = embedding.embed(data_point)
circuits.append(circuit)
if i % 100 == 0:
current_memory = process.memory_info().rss / 1024 / 1024
print(f"Processed {i} points, Memory: {current_memory:.1f} MB")
# Clean up if memory usage is too high
if current_memory > initial_memory + 1000: # 1GB increase
gc.collect()
return circuits
Caching
from functools import lru_cache
import hashlib
class CachedEmbedding:
"""Wrapper for caching embedding results"""
def __init__(self, embedding, cache_size=128):
self.embedding = embedding
self.cache_size = cache_size
@lru_cache(maxsize=None)
def _cached_embed(self, data_hash):
"""Cache embedding results by data hash"""
# This is a simplified version - in practice, you'd need
# to store the actual data and retrieve it for embedding
pass
def embed(self, data_point):
# Create hash of data point
data_hash = hashlib.md5(data_point.tobytes()).hexdigest()
# Check cache or compute
if hasattr(self, '_cache') and data_hash in self._cache:
return self._cache[data_hash]
circuit = self.embedding.embed(data_point)
# Store in cache
if not hasattr(self, '_cache'):
self._cache = {}
if len(self._cache) < self.cache_size:
self._cache[data_hash] = circuit
return circuit
# Use cached embedding
cached_embedding = CachedEmbedding(embedding, cache_size=256)
Best Practices
Embedding Selection Guidelines
def select_embedding(data_characteristics):
"""Select appropriate embedding based on data characteristics"""
n_samples, n_features = data_characteristics['shape']
data_type = data_characteristics['type'] # 'continuous', 'binary', 'categorical'
# Guidelines for embedding selection
if n_features <= 4:
return AngleEmbedding(n_qubits=n_features)
elif n_features <= 16 and data_type == 'continuous':
return AmplitudeEmbedding(n_qubits=int(np.ceil(np.log2(n_features))))
elif data_characteristics.get('complex_interactions', False):
n_qubits = min(6, int(np.ceil(np.log2(n_features))))
return IQPEmbedding(n_qubits=n_qubits, depth=2)
else:
# Default to angle embedding with feature selection
n_qubits = min(8, n_features)
return AngleEmbedding(n_qubits=n_qubits)
# Example usage
data_chars = {
'shape': (1000, 10),
'type': 'continuous',
'complex_interactions': True
}
recommended_embedding = select_embedding(data_chars)
Parameter Initialization
def initialize_embedding_parameters(embedding, method='random'):
"""Initialize embedding parameters using different strategies"""
if not hasattr(embedding, 'set_parameters'):
return # Not a parameterized embedding
n_params = embedding.n_parameters
if method == 'random':
# Random initialization
params = np.random.uniform(-np.pi, np.pi, n_params)
elif method == 'xavier':
# Xavier initialization
fan_in = n_params
limit = np.sqrt(6.0 / fan_in)
params = np.random.uniform(-limit, limit, n_params)
elif method == 'zeros':
# Zero initialization
params = np.zeros(n_params)
elif method == 'small_random':
# Small random values
params = np.random.normal(0, 0.1, n_params)
embedding.set_parameters(params)
return params
# Initialize parameters
params = initialize_embedding_parameters(embedding, method='xavier')