Skip to content

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
def __init__(self, n_qubits: int, backend: Any, **kwargs):
    # License validation for all embedding classes
    validate_license_for_class(self.__class__)

    self.n_qubits = n_qubits
    self.backend = backend
    self.params = kwargs
    self._circuit_cache = {}

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()

Clear the circuit cache.

Source code in quantum_data_embedding_suite\embeddings\base.py
def clear_cache(self) -> None:
    """Clear the circuit cache."""
    self._circuit_cache.clear()

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
@abstractmethod
def create_circuit(self, x: np.ndarray) -> Any:
    """
    Create a quantum circuit for embedding a single data point.

    Parameters
    ----------
    x : array-like of shape (n_features,)
        Classical data point to embed

    Returns
    -------
    circuit : quantum circuit object
        Backend-specific quantum circuit
    """
    pass

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
def create_circuit_cached(self, x: np.ndarray) -> Any:
    """
    Create circuit with caching support.

    Parameters
    ----------
    x : array-like
        Input data point

    Returns
    -------
    circuit : quantum circuit object
        Backend-specific quantum circuit
    """
    x = self.validate_input(x)
    circuit_hash = self.get_circuit_hash(x)

    if circuit_hash not in self._circuit_cache:
        self._circuit_cache[circuit_hash] = self.create_circuit(x)

    return self._circuit_cache[circuit_hash]

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
def fit(self, X: np.ndarray) -> "BaseEmbedding":
    """
    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
    ----------
    X : array-like of shape (n_samples, n_features)
        Training data

    Returns
    -------
    self : BaseEmbedding
        Returns self for method chaining
    """
    return self

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
def get_circuit_hash(self, x: np.ndarray) -> str:
    """
    Generate a hash for circuit caching.

    Parameters
    ----------
    x : array-like
        Input data point

    Returns
    -------
    hash_str : str
        Hash string for the circuit
    """
    return str(hash(tuple(x.flatten())))

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

Source code in quantum_data_embedding_suite\embeddings\base.py
@abstractmethod
def get_feature_dimension(self) -> int:
    """
    Get the expected dimension of input features.

    Returns
    -------
    n_features : int
        Number of classical features this embedding expects
    """
    pass

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
def get_info(self) -> Dict[str, Any]:
    """
    Get information about the embedding.

    Returns
    -------
    info : dict
        Dictionary containing embedding information
    """
    return {
        "name": self.__class__.__name__,
        "n_qubits": self.n_qubits,
        "n_features": self.get_feature_dimension(),
        "depth": self.depth,
        "n_parameters": self.n_parameters,
        "backend": str(self.backend),
        "params": self.params,
    }

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
def validate_input(self, x: np.ndarray) -> np.ndarray:
    """
    Validate and preprocess input data.

    Parameters
    ----------
    x : array-like
        Input data point

    Returns
    -------
    x : ndarray
        Validated and preprocessed data
    """
    x = np.asarray(x, dtype=np.float64)

    if x.ndim != 1:
        raise ValueError(f"Input must be 1D array, got {x.ndim}D")

    expected_features = self.get_feature_dimension()
    if len(x) != expected_features:
        raise ValueError(
            f"Expected {expected_features} features, got {len(x)}"
        )

    return x

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
def __init__(
    self,
    n_qubits: int,
    backend: Any,
    rotation: str = "Y",
    entangling: str = "linear",
    depth: int = 1,
    **kwargs
):
    super().__init__(n_qubits, backend, **kwargs)

    if rotation not in ["X", "Y", "Z", "XYZ"]:
        raise ValueError(f"Invalid rotation type: {rotation}")

    if entangling not in ["linear", "circular", "full", "none"]:
        raise ValueError(f"Invalid entangling pattern: {entangling}")

    self.rotation = rotation
    self.entangling = entangling
    self._depth = depth
    self._n_parameters = 0  # Data-dependent, no trainable parameters

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
def create_circuit(self, x: np.ndarray) -> Any:
    """
    Create angle embedding circuit for a data point.

    Parameters
    ----------
    x : array-like of shape (n_features,)
        Classical data point to embed

    Returns
    -------
    circuit : quantum circuit object
        Backend-specific quantum circuit with angle encoding
    """
    x = self.validate_input(x)

    if self.backend.name == "qiskit":
        return self._create_qiskit_circuit(x)
    elif self.backend.name == "pennylane":
        return self._create_pennylane_circuit(x)
    else:
        raise ValueError(f"Unsupported backend: {self.backend.name}")

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
def get_feature_dimension(self) -> int:
    """
    Get the expected number of input features.

    For angle embedding, each qubit can encode one feature per layer.

    Returns
    -------
    n_features : int
        Number of features (n_qubits * depth)
    """
    if self.rotation == "XYZ":
        return self.n_qubits * 3 * self._depth
    else:
        return self.n_qubits * self._depth

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
def __init__(
    self,
    n_qubits: int,
    backend: Any,
    normalize: bool = True,
    padding: str = "zero",
    **kwargs
):
    super().__init__(n_qubits, backend, **kwargs)

    if padding not in ["zero", "repeat", "truncate"]:
        raise ValueError(f"Invalid padding method: {padding}")

    self.normalize = normalize
    self.padding = padding
    self._depth = 1  # Amplitude encoding is typically single layer
    self._n_parameters = 0  # Data-dependent, no trainable parameters

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
def create_circuit(self, x: np.ndarray) -> Any:
    """
    Create amplitude embedding circuit for a data point.

    Parameters
    ----------
    x : array-like of shape (n_features,)
        Classical data point to embed as amplitudes

    Returns
    -------
    circuit : quantum circuit object
        Backend-specific quantum circuit with amplitude encoding
    """
    # Validate and prepare amplitudes
    amplitudes = self._prepare_amplitudes(x)

    if self.backend.name == "qiskit":
        return self._create_qiskit_circuit(amplitudes)
    elif self.backend.name == "pennylane":
        return self._create_pennylane_circuit(amplitudes)
    else:
        raise ValueError(f"Unsupported backend: {self.backend.name}")

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
def get_feature_dimension(self) -> int:
    """
    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
    -------
    n_features : int
        Maximum number of features (2^n_qubits)
    """
    return 2 ** self.n_qubits

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
def validate_input(self, x: np.ndarray) -> np.ndarray:
    """
    Validate input for amplitude embedding.

    Parameters
    ----------
    x : array-like
        Input data

    Returns
    -------
    x : ndarray
        Validated input data
    """
    x = np.asarray(x, dtype=np.float64)

    if x.ndim != 1:
        raise ValueError(f"Input must be 1D array, got {x.ndim}D")

    max_features = 2 ** self.n_qubits
    if len(x) > max_features and self.padding == "truncate":
        return x  # Will be truncated in _prepare_amplitudes
    elif len(x) > max_features:
        raise ValueError(
            f"Input size {len(x)} too large for {self.n_qubits} qubits "
            f"(max {max_features}). Use padding='truncate' or reduce input size."
        )

    return x

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
def __init__(
    self,
    n_qubits: int,
    backend: Any,
    depth: int = 3,
    interaction_pattern: str = "all",
    **kwargs
):
    super().__init__(n_qubits, backend, **kwargs)

    if interaction_pattern not in ["all", "linear", "circular"]:
        raise ValueError(f"Invalid interaction pattern: {interaction_pattern}")

    self._depth = depth
    self.interaction_pattern = interaction_pattern
    self._n_parameters = 0  # Data-dependent, no trainable parameters

    # Generate interaction pairs based on pattern
    self.interaction_pairs = self._generate_interaction_pairs()

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
def create_circuit(self, x: np.ndarray) -> Any:
    """
    Create IQP embedding circuit for a data point.

    Parameters
    ----------
    x : array-like of shape (n_features,)
        Classical data point to embed

    Returns
    -------
    circuit : quantum circuit object
        Backend-specific quantum circuit with IQP encoding
    """
    x = self.validate_input(x)

    if self.backend.name == "qiskit":
        return self._create_qiskit_circuit(x)
    elif self.backend.name == "pennylane":
        return self._create_pennylane_circuit(x)
    else:
        raise ValueError(f"Unsupported backend: {self.backend.name}")

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
def get_expressibility_bounds(self) -> tuple:
    """
    Get theoretical expressibility bounds for IQP circuits.

    Returns
    -------
    bounds : tuple
        (lower_bound, upper_bound) for expressibility
    """
    # IQP circuits have known expressibility properties
    # This is based on theoretical analysis of IQP expressiveness
    n_states = 2 ** self.n_qubits

    # Lower bound: uniform distribution
    lower_bound = 0.0

    # Upper bound: depends on circuit depth and connectivity
    max_expressibility = 1.0 - 1.0 / n_states

    # Scaling with depth (heuristic)
    depth_factor = min(1.0, self._depth / self.n_qubits)

    # Scaling with connectivity
    max_pairs = self.n_qubits * (self.n_qubits - 1) // 2
    connectivity_factor = len(self.interaction_pairs) / max_pairs

    upper_bound = max_expressibility * depth_factor * connectivity_factor

    return (lower_bound, upper_bound)

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
def get_feature_dimension(self) -> int:
    """
    Get the expected number of input features.

    IQP embedding uses features for both single-qubit rotations and 
    multi-qubit interactions.

    Returns
    -------
    n_features : int
        Number of features needed
    """
    # Features for single-qubit gates + features for interaction pairs
    single_qubit_features = self.n_qubits * self._depth
    interaction_features = len(self.interaction_pairs) * self._depth
    return single_qubit_features + interaction_features

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
def __init__(
    self,
    n_qubits: int,
    backend: Any,
    depth: int = 3,
    trainable_params: bool = True,
    rotation_gates: List[str] = None,
    entangling: str = "linear",
    **kwargs
):
    super().__init__(n_qubits, backend, **kwargs)

    if rotation_gates is None:
        rotation_gates = ["RX", "RY", "RZ"]

    for gate in rotation_gates:
        if gate not in ["RX", "RY", "RZ"]:
            raise ValueError(f"Invalid rotation gate: {gate}")

    if entangling not in ["linear", "circular", "full", "none"]:
        raise ValueError(f"Invalid entangling pattern: {entangling}")

    self._depth = depth
    self.trainable_params = trainable_params
    self.rotation_gates = rotation_gates
    self.entangling = entangling

    # Calculate number of parameters
    gates_per_qubit_per_layer = len(rotation_gates)
    if trainable_params:
        # Trainable params + data params
        self._n_parameters = (
            depth * n_qubits * gates_per_qubit_per_layer * 2
        )
    else:
        # Only data parameters
        self._n_parameters = 0

    # Initialize trainable parameters randomly
    if trainable_params:
        self.theta = np.random.uniform(
            0, 2 * np.pi, 
            size=(depth, n_qubits, gates_per_qubit_per_layer)
        )
    else:
        self.theta = None

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
def create_circuit(self, x: np.ndarray) -> Any:
    """
    Create data re-uploading circuit for a data point.

    Parameters
    ----------
    x : array-like of shape (n_features,)
        Classical data point to embed

    Returns
    -------
    circuit : quantum circuit object
        Backend-specific quantum circuit with data re-uploading
    """
    x = self.validate_input(x)

    if self.backend.name == "qiskit":
        return self._create_qiskit_circuit(x)
    elif self.backend.name == "pennylane":
        return self._create_pennylane_circuit(x)
    else:
        raise ValueError(f"Unsupported backend: {self.backend.name}")

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
def fit(self, X: np.ndarray) -> "DataReuploadingEmbedding":
    """
    Fit the embedding to training data.

    For data re-uploading, this can optionally optimize the trainable
    parameters using the training data.

    Parameters
    ----------
    X : array-like of shape (n_samples, n_features)
        Training data

    Returns
    -------
    self : DataReuploadingEmbedding
        Returns self for method chaining
    """
    # Basic implementation: just store the data statistics
    # In a more advanced version, you could optimize the trainable
    # parameters using the training data

    X = np.asarray(X)
    self.data_mean_ = np.mean(X, axis=0)
    self.data_std_ = np.std(X, axis=0)

    return self

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
def get_feature_dimension(self) -> int:
    """
    Get the expected number of input features.

    For data re-uploading, the same features are used in each layer.

    Returns
    -------
    n_features : int
        Number of features (same as number of qubits)
    """
    return self.n_qubits

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
def get_parameters(self) -> np.ndarray:
    """
    Get current trainable parameters.

    Returns
    -------
    theta : ndarray
        Current parameter values
    """
    if not self.trainable_params:
        return np.array([])
    return self.theta.copy()

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
def update_parameters(self, new_theta: np.ndarray) -> None:
    """
    Update trainable parameters.

    Parameters
    ----------
    new_theta : array-like
        New parameter values
    """
    if not self.trainable_params:
        raise ValueError("No trainable parameters to update")

    expected_shape = (self._depth, self.n_qubits, len(self.rotation_gates))
    new_theta = np.asarray(new_theta).reshape(expected_shape)
    self.theta = new_theta

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
def __init__(
    self,
    n_qubits: int,
    backend: Any,
    hamiltonian_type: str = "pauli_x",
    evolution_time: float = 1.0,
    trotter_steps: int = 1,
    initial_state: str = "zero",
    custom_operators: List[str] = None,
    **kwargs
):
    super().__init__(n_qubits, backend, **kwargs)

    valid_types = ["pauli_x", "pauli_y", "pauli_z", "ising", "heisenberg", "custom"]
    if hamiltonian_type not in valid_types:
        raise ValueError(f"Invalid hamiltonian_type: {hamiltonian_type}")

    if initial_state not in ["zero", "plus", "random"]:
        raise ValueError(f"Invalid initial_state: {initial_state}")

    self.hamiltonian_type = hamiltonian_type
    self.evolution_time = evolution_time
    self.trotter_steps = trotter_steps
    self.initial_state = initial_state
    self.custom_operators = custom_operators or []

    # Determine circuit depth based on Trotter steps
    self._depth = trotter_steps
    self._n_parameters = 1  # Evolution time parameter

    # Generate Hamiltonian operators
    self.operators = self._generate_operators()

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
def create_circuit(self, x: np.ndarray) -> Any:
    """
    Create Hamiltonian evolution circuit for a data point.

    Parameters
    ----------
    x : array-like of shape (n_features,)
        Classical data point (Hamiltonian coefficients)

    Returns
    -------
    circuit : quantum circuit object
        Backend-specific quantum circuit with Hamiltonian evolution
    """
    x = self.validate_input(x)

    if self.backend.name == "qiskit":
        return self._create_qiskit_circuit(x)
    elif self.backend.name == "pennylane":
        return self._create_pennylane_circuit(x)
    else:
        raise ValueError(f"Unsupported backend: {self.backend.name}")

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
def get_feature_dimension(self) -> int:
    """
    Get the expected number of input features.

    Returns
    -------
    n_features : int
        Number of features (equal to number of Hamiltonian terms)
    """
    return len(self.operators)

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')

Further Reading