Skip to content

Kernels API

This page documents the quantum kernel classes and functions.

Base Classes

BaseKernel

BaseKernel(embedding, backend)

Bases: ABC

Abstract base class for quantum kernels.

Quantum kernels compute similarity between data points by comparing the quantum states generated by embedding the data.

Parameters:

Name Type Description Default
embedding BaseEmbedding

Quantum embedding to use for feature mapping

required
backend BaseBackend

Backend for quantum circuit execution

required
Source code in quantum_data_embedding_suite\kernels\base.py
def __init__(self, embedding: Any, backend: Any):
    # License validation for all kernel classes
    validate_license_for_class(self.__class__)

    self.embedding = embedding
    self.backend = backend

Functions

add_regularization(K, reg_param=1e-08)

Add regularization to kernel matrix.

Parameters:

Name Type Description Default
K array - like

Kernel matrix

required
reg_param float

Regularization parameter

1e-8

Returns:

Name Type Description
K_reg ndarray

Regularized kernel matrix

Source code in quantum_data_embedding_suite\kernels\base.py
def add_regularization(
    self, 
    K: np.ndarray, 
    reg_param: float = 1e-8
) -> np.ndarray:
    """
    Add regularization to kernel matrix.

    Parameters
    ----------
    K : array-like
        Kernel matrix
    reg_param : float, default=1e-8
        Regularization parameter

    Returns
    -------
    K_reg : ndarray
        Regularized kernel matrix
    """
    K = np.asarray(K)
    return K + reg_param * np.eye(len(K))

compute_effective_dimension(K, threshold=0.95)

Compute effective dimension of the kernel matrix.

The effective dimension is the number of eigenvalues needed to capture a certain fraction of the total variance.

Parameters:

Name Type Description Default
K array - like

Kernel matrix

required
threshold float

Variance threshold (between 0 and 1)

0.95

Returns:

Name Type Description
eff_dim int

Effective dimension

Source code in quantum_data_embedding_suite\kernels\base.py
def compute_effective_dimension(
    self, 
    K: np.ndarray, 
    threshold: float = 0.95
) -> int:
    """
    Compute effective dimension of the kernel matrix.

    The effective dimension is the number of eigenvalues needed
    to capture a certain fraction of the total variance.

    Parameters
    ----------
    K : array-like
        Kernel matrix
    threshold : float, default=0.95
        Variance threshold (between 0 and 1)

    Returns
    -------
    eff_dim : int
        Effective dimension
    """
    try:
        eigenvals = np.linalg.eigvals(K)
        eigenvals = np.real(eigenvals)
        eigenvals = np.sort(eigenvals)[::-1]  # Sort in descending order
        eigenvals = np.maximum(eigenvals, 0)  # Remove negative eigenvalues

        if np.sum(eigenvals) == 0:
            return 0

        cumsum = np.cumsum(eigenvals)
        total_variance = cumsum[-1]

        # Find number of eigenvalues needed for threshold
        normalized_cumsum = cumsum / total_variance
        eff_dim = np.searchsorted(normalized_cumsum, threshold) + 1

        return min(eff_dim, len(eigenvals))
    except np.linalg.LinAlgError:
        return len(K)

compute_kernel_alignment(K1, K2)

Compute kernel alignment between two kernel matrices.

Kernel alignment measures the similarity between two kernels: A(K1, K2) = _F / (||K1||_F * ||K2||_F)

Parameters:

Name Type Description Default
K1 array - like

Kernel matrices to compare

required
K2 array - like

Kernel matrices to compare

required

Returns:

Name Type Description
alignment float

Kernel alignment score between 0 and 1

Source code in quantum_data_embedding_suite\kernels\base.py
def compute_kernel_alignment(
    self, 
    K1: np.ndarray, 
    K2: np.ndarray
) -> float:
    """
    Compute kernel alignment between two kernel matrices.

    Kernel alignment measures the similarity between two kernels:
    A(K1, K2) = <K1, K2>_F / (||K1||_F * ||K2||_F)

    Parameters
    ----------
    K1, K2 : array-like
        Kernel matrices to compare

    Returns
    -------
    alignment : float
        Kernel alignment score between 0 and 1
    """
    K1 = np.asarray(K1)
    K2 = np.asarray(K2)

    if K1.shape != K2.shape:
        raise ValueError("Kernel matrices must have the same shape")

    # Frobenius inner product
    inner_product = np.sum(K1 * K2)

    # Frobenius norms
    norm1 = np.sqrt(np.sum(K1 * K1))
    norm2 = np.sqrt(np.sum(K2 * K2))

    if norm1 == 0 or norm2 == 0:
        return 0.0

    alignment = inner_product / (norm1 * norm2)
    return float(alignment)

compute_kernel_element(x1, x2) abstractmethod

Compute kernel element K(x1, x2) between two data points.

Parameters:

Name Type Description Default
x1 array - like

Data points to compute kernel between

required
x2 array - like

Data points to compute kernel between

required

Returns:

Name Type Description
kernel_value float

Kernel value between x1 and x2

Source code in quantum_data_embedding_suite\kernels\base.py
@abstractmethod
def compute_kernel_element(self, x1: np.ndarray, x2: np.ndarray) -> float:
    """
    Compute kernel element K(x1, x2) between two data points.

    Parameters
    ----------
    x1, x2 : array-like
        Data points to compute kernel between

    Returns
    -------
    kernel_value : float
        Kernel value between x1 and x2
    """
    pass

compute_kernel_matrix(X, Y=None)

Compute kernel matrix between sets of data points.

Parameters:

Name Type Description Default
X array-like of shape (n_samples_X, n_features)

First set of data points

required
Y array-like of shape (n_samples_Y, n_features)

Second set of data points (defaults to X)

None

Returns:

Name Type Description
K ndarray of shape (n_samples_X, n_samples_Y)

Kernel matrix

Source code in quantum_data_embedding_suite\kernels\base.py
def compute_kernel_matrix(
    self, 
    X: np.ndarray, 
    Y: Optional[np.ndarray] = None
) -> np.ndarray:
    """
    Compute kernel matrix between sets of data points.

    Parameters
    ----------
    X : array-like of shape (n_samples_X, n_features)
        First set of data points
    Y : array-like of shape (n_samples_Y, n_features), optional
        Second set of data points (defaults to X)

    Returns
    -------
    K : ndarray of shape (n_samples_X, n_samples_Y)
        Kernel matrix
    """
    X = np.asarray(X)
    if Y is None:
        Y = X
    else:
        Y = np.asarray(Y)

    n_x, n_y = len(X), len(Y)
    K = np.zeros((n_x, n_y))

    for i in range(n_x):
        for j in range(n_y):
            K[i, j] = self.compute_kernel_element(X[i], Y[j])

    return K

compute_kernel_matrix_symmetric(X)

Compute symmetric kernel matrix for a single dataset.

This method can be more efficient than compute_kernel_matrix when computing K(X, X) due to symmetry.

Parameters:

Name Type Description Default
X array-like of shape (n_samples, n_features)

Data points

required

Returns:

Name Type Description
K ndarray of shape (n_samples, n_samples)

Symmetric kernel matrix

Source code in quantum_data_embedding_suite\kernels\base.py
def compute_kernel_matrix_symmetric(self, X: np.ndarray) -> np.ndarray:
    """
    Compute symmetric kernel matrix for a single dataset.

    This method can be more efficient than compute_kernel_matrix
    when computing K(X, X) due to symmetry.

    Parameters
    ----------
    X : array-like of shape (n_samples, n_features)
        Data points

    Returns
    -------
    K : ndarray of shape (n_samples, n_samples)
        Symmetric kernel matrix
    """
    X = np.asarray(X)
    n = len(X)
    K = np.zeros((n, n))

    # Compute upper triangle (including diagonal)
    for i in range(n):
        for j in range(i, n):
            K[i, j] = self.compute_kernel_element(X[i], X[j])
            if i != j:
                K[j, i] = K[i, j]  # Use symmetry

    return K

is_positive_definite(K, tol=1e-08)

Check if kernel matrix is positive definite.

Parameters:

Name Type Description Default
K array - like

Kernel matrix

required
tol float

Tolerance for eigenvalue check

1e-8

Returns:

Name Type Description
is_pd bool

Whether the matrix is positive definite

Source code in quantum_data_embedding_suite\kernels\base.py
def is_positive_definite(self, K: np.ndarray, tol: float = 1e-8) -> bool:
    """
    Check if kernel matrix is positive definite.

    Parameters
    ----------
    K : array-like
        Kernel matrix
    tol : float, default=1e-8
        Tolerance for eigenvalue check

    Returns
    -------
    is_pd : bool
        Whether the matrix is positive definite
    """
    try:
        eigenvals = np.linalg.eigvals(K)
        return np.all(eigenvals > -tol)
    except np.linalg.LinAlgError:
        return False

normalize_kernel(K)

Normalize kernel matrix to have unit diagonal.

Parameters:

Name Type Description Default
K array - like

Kernel matrix

required

Returns:

Name Type Description
K_norm ndarray

Normalized kernel matrix

Source code in quantum_data_embedding_suite\kernels\base.py
def normalize_kernel(self, K: np.ndarray) -> np.ndarray:
    """
    Normalize kernel matrix to have unit diagonal.

    Parameters
    ----------
    K : array-like
        Kernel matrix

    Returns
    -------
    K_norm : ndarray
        Normalized kernel matrix
    """
    K = np.asarray(K)
    diag_sqrt = np.sqrt(np.diag(K))

    # Avoid division by zero
    diag_sqrt = np.where(diag_sqrt == 0, 1.0, diag_sqrt)

    # Normalize: K_norm[i,j] = K[i,j] / sqrt(K[i,i] * K[j,j])
    K_norm = K / np.outer(diag_sqrt, diag_sqrt)

    return K_norm

Kernel Implementations

FidelityKernel

FidelityKernel(embedding, backend, cache_circuits=True)

Bases: BaseKernel

Fidelity-based quantum kernel.

Computes kernel values based on the fidelity (overlap) between quantum states: K(x1, x2) = |⟨ψ(x1)|ψ(x2)⟩|²

This is the most common type of quantum kernel and measures the squared overlap between the quantum states obtained by embedding the classical data points.

Parameters:

Name Type Description Default
embedding BaseEmbedding

Quantum embedding to use for feature mapping

required
backend BaseBackend

Backend for quantum circuit execution

required
cache_circuits bool

Whether to cache quantum circuits for repeated evaluations

True

Examples:

>>> from quantum_data_embedding_suite.embeddings import AngleEmbedding
>>> from quantum_data_embedding_suite.backends import QiskitBackend
>>> backend = QiskitBackend()
>>> embedding = AngleEmbedding(n_qubits=4, backend=backend)
>>> kernel = FidelityKernel(embedding, backend)
>>> fidelity = kernel.compute_kernel_element([0.1, 0.2], [0.3, 0.4])
Source code in quantum_data_embedding_suite\kernels\fidelity.py
def __init__(
    self, 
    embedding: Any, 
    backend: Any,
    cache_circuits: bool = True
):
    super().__init__(embedding, backend)
    self.cache_circuits = cache_circuits
    self._circuit_cache = {} if cache_circuits else None

Functions

analyze_kernel_properties(K)

Analyze properties of the fidelity kernel matrix.

Parameters:

Name Type Description Default
K array - like

Fidelity kernel matrix

required

Returns:

Name Type Description
properties dict

Dictionary containing kernel analysis results

Source code in quantum_data_embedding_suite\kernels\fidelity.py
def analyze_kernel_properties(self, K: np.ndarray) -> dict:
    """
    Analyze properties of the fidelity kernel matrix.

    Parameters
    ----------
    K : array-like
        Fidelity kernel matrix

    Returns
    -------
    properties : dict
        Dictionary containing kernel analysis results
    """
    K = np.asarray(K)

    # Basic properties
    properties = {
        'is_symmetric': np.allclose(K, K.T),
        'is_positive_definite': self.is_positive_definite(K),
        'condition_number': np.linalg.cond(K),
        'effective_dimension': self.compute_effective_dimension(K),
        'mean_off_diagonal': np.mean(K[~np.eye(len(K), dtype=bool)]),
        'std_off_diagonal': np.std(K[~np.eye(len(K), dtype=bool)]),
    }

    # Eigenvalue analysis
    try:
        eigenvals = np.linalg.eigvals(K)
        eigenvals = np.real(eigenvals)
        eigenvals = np.sort(eigenvals)[::-1]

        properties.update({
            'max_eigenvalue': float(eigenvals[0]),
            'min_eigenvalue': float(eigenvals[-1]),
            'eigenvalue_ratio': float(eigenvals[0] / eigenvals[-1]) if eigenvals[-1] > 1e-10 else np.inf,
            'spectral_gap': float(eigenvals[0] - eigenvals[1]) if len(eigenvals) > 1 else 0.0,
        })
    except np.linalg.LinAlgError:
        properties.update({
            'max_eigenvalue': np.nan,
            'min_eigenvalue': np.nan,
            'eigenvalue_ratio': np.nan,
            'spectral_gap': np.nan,
        })

    return properties

clear_cache()

Clear the circuit cache.

Source code in quantum_data_embedding_suite\kernels\fidelity.py
def clear_cache(self) -> None:
    """Clear the circuit cache."""
    if self.cache_circuits and self._circuit_cache is not None:
        self._circuit_cache.clear()

compute_gram_schmidt_fidelities(X, reference_idx=0)

Compute fidelities with respect to a reference state.

This can be useful for analyzing the embedding quality and understanding how the quantum states relate to a reference point.

Parameters:

Name Type Description Default
X array-like of shape (n_samples, n_features)

Data points

required
reference_idx int

Index of the reference data point

0

Returns:

Name Type Description
fidelities ndarray of shape (n_samples,)

Fidelity values with respect to reference

Source code in quantum_data_embedding_suite\kernels\fidelity.py
def compute_gram_schmidt_fidelities(
    self, 
    X: np.ndarray, 
    reference_idx: int = 0
) -> np.ndarray:
    """
    Compute fidelities with respect to a reference state.

    This can be useful for analyzing the embedding quality
    and understanding how the quantum states relate to a
    reference point.

    Parameters
    ----------
    X : array-like of shape (n_samples, n_features)
        Data points
    reference_idx : int, default=0
        Index of the reference data point

    Returns
    -------
    fidelities : ndarray of shape (n_samples,)
        Fidelity values with respect to reference
    """
    X = np.asarray(X)
    n = len(X)

    if reference_idx >= n:
        raise ValueError(f"Reference index {reference_idx} out of range")

    reference_point = X[reference_idx]
    fidelities = np.zeros(n)

    for i in range(n):
        fidelities[i] = self.compute_kernel_element(reference_point, X[i])

    return fidelities

compute_kernel_element(x1, x2)

Compute fidelity kernel element between two data points.

The fidelity kernel is defined as: K(x1, x2) = |⟨ψ(x1)|ψ(x2)⟩|²

where |ψ(xi)⟩ is the quantum state obtained by applying the embedding circuit to data point xi.

Parameters:

Name Type Description Default
x1 array - like

Data points to compute kernel between

required
x2 array - like

Data points to compute kernel between

required

Returns:

Name Type Description
fidelity float

Fidelity kernel value between 0 and 1

Source code in quantum_data_embedding_suite\kernels\fidelity.py
def compute_kernel_element(self, x1: np.ndarray, x2: np.ndarray) -> float:
    """
    Compute fidelity kernel element between two data points.

    The fidelity kernel is defined as:
    K(x1, x2) = |⟨ψ(x1)|ψ(x2)⟩|²

    where |ψ(xi)⟩ is the quantum state obtained by applying
    the embedding circuit to data point xi.

    Parameters
    ----------
    x1, x2 : array-like
        Data points to compute kernel between

    Returns
    -------
    fidelity : float
        Fidelity kernel value between 0 and 1
    """
    x1 = np.asarray(x1, dtype=np.float64)
    x2 = np.asarray(x2, dtype=np.float64)

    # Check if points are identical (optimization)
    if np.allclose(x1, x2):
        return 1.0

    # Get or create circuits
    circuit1 = self._get_circuit(x1)
    circuit2 = self._get_circuit(x2)

    # Compute fidelity using backend
    try:
        fidelity = self.backend.compute_fidelity(circuit1, circuit2)
        return float(fidelity)
    except Exception as e:
        # Fallback: compute fidelity from statevectors
        return self._compute_fidelity_from_statevectors(circuit1, circuit2)

compute_kernel_matrix_optimized(X)

Optimized computation of symmetric kernel matrix.

This method takes advantage of symmetry and diagonal properties to reduce the number of fidelity computations needed.

Parameters:

Name Type Description Default
X array-like of shape (n_samples, n_features)

Data points

required

Returns:

Name Type Description
K ndarray of shape (n_samples, n_samples)

Fidelity kernel matrix

Source code in quantum_data_embedding_suite\kernels\fidelity.py
def compute_kernel_matrix_optimized(self, X: np.ndarray) -> np.ndarray:
    """
    Optimized computation of symmetric kernel matrix.

    This method takes advantage of symmetry and diagonal properties
    to reduce the number of fidelity computations needed.

    Parameters
    ----------
    X : array-like of shape (n_samples, n_features)
        Data points

    Returns
    -------
    K : ndarray of shape (n_samples, n_samples)
        Fidelity kernel matrix
    """
    X = np.asarray(X)
    n = len(X)
    K = np.zeros((n, n))

    # Diagonal elements are always 1 (fidelity with itself)
    np.fill_diagonal(K, 1.0)

    # Compute upper triangle only (use symmetry)
    for i in range(n):
        for j in range(i + 1, n):
            fidelity = self.compute_kernel_element(X[i], X[j])
            K[i, j] = fidelity
            K[j, i] = fidelity  # Symmetry

    return K

get_cache_size()

Get the current size of the circuit cache.

Source code in quantum_data_embedding_suite\kernels\fidelity.py
def get_cache_size(self) -> int:
    """Get the current size of the circuit cache."""
    if self.cache_circuits and self._circuit_cache is not None:
        return len(self._circuit_cache)
    return 0

ProjectedKernel

ProjectedKernel(embedding, backend, observable='Z', qubits=None)

Bases: BaseKernel

Projected quantum kernel that measures specific observables.

Instead of computing full state fidelity, this kernel computes expectation values of specific observables on the quantum states, providing a different notion of similarity.

Parameters:

Name Type Description Default
embedding BaseEmbedding

Quantum embedding to use for feature mapping

required
backend BaseBackend

Backend for quantum circuit execution

required
observable str or object

Observable to measure ('Z', 'X', 'Y', or custom observable)

'Z'
qubits list of int

Qubits to measure (defaults to all qubits)

None
Source code in quantum_data_embedding_suite\kernels\projected.py
def __init__(
    self, 
    embedding: Any, 
    backend: Any,
    observable: str = "Z",
    qubits: Optional[list] = None
):
    super().__init__(embedding, backend)
    self.observable_str = observable
    self.qubits = qubits or list(range(embedding.n_qubits))

    # Create observable
    self.observable = self._create_observable()

Functions

compute_kernel_element(x1, x2)

Compute projected kernel element between two data points.

The projected kernel is defined as: K(x1, x2) = ⟨ψ(x1)|O|ψ(x1)⟩ * ⟨ψ(x2)|O|ψ(x2)⟩

where O is the observable and |ψ(xi)⟩ are the embedded states.

Parameters:

Name Type Description Default
x1 array - like

Data points to compute kernel between

required
x2 array - like

Data points to compute kernel between

required

Returns:

Name Type Description
kernel_value float

Projected kernel value

Source code in quantum_data_embedding_suite\kernels\projected.py
def compute_kernel_element(self, x1: np.ndarray, x2: np.ndarray) -> float:
    """
    Compute projected kernel element between two data points.

    The projected kernel is defined as:
    K(x1, x2) = ⟨ψ(x1)|O|ψ(x1)⟩ * ⟨ψ(x2)|O|ψ(x2)⟩

    where O is the observable and |ψ(xi)⟩ are the embedded states.

    Parameters
    ----------
    x1, x2 : array-like
        Data points to compute kernel between

    Returns
    -------
    kernel_value : float
        Projected kernel value
    """
    x1 = np.asarray(x1, dtype=np.float64)
    x2 = np.asarray(x2, dtype=np.float64)

    # Create circuits
    circuit1 = self.embedding.create_circuit(x1)
    circuit2 = self.embedding.create_circuit(x2)

    # Compute expectation values
    exp1 = self.backend.compute_expectation(circuit1, self.observable)
    exp2 = self.backend.compute_expectation(circuit2, self.observable)

    # Kernel is product of expectation values
    return float(exp1 * exp2)

TrainableKernel

TrainableKernel(embedding, backend, n_params=4, param_bounds=(-np.pi, np.pi))

Bases: BaseKernel

Trainable quantum kernel with learnable parameters.

This kernel includes additional trainable parameters that can be optimized to improve the kernel's performance for specific tasks.

Parameters:

Name Type Description Default
embedding BaseEmbedding

Quantum embedding to use for feature mapping

required
backend BaseBackend

Backend for quantum circuit execution

required
n_params int

Number of trainable parameters

4
param_bounds tuple

Bounds for parameter initialization

(-pi, pi)
Source code in quantum_data_embedding_suite\kernels\trainable.py
def __init__(
    self, 
    embedding: Any, 
    backend: Any,
    n_params: int = 4,
    param_bounds: tuple = (-np.pi, np.pi)
):
    super().__init__(embedding, backend)
    self.n_params = n_params
    self.param_bounds = param_bounds

    # Initialize trainable parameters
    self.theta = np.random.uniform(
        param_bounds[0], param_bounds[1], n_params
    )

Functions

compute_kernel_element(x1, x2)

Compute trainable kernel element between two data points.

Parameters:

Name Type Description Default
x1 array - like

Data points to compute kernel between

required
x2 array - like

Data points to compute kernel between

required

Returns:

Name Type Description
kernel_value float

Trainable kernel value

Source code in quantum_data_embedding_suite\kernels\trainable.py
def compute_kernel_element(self, x1: np.ndarray, x2: np.ndarray) -> float:
    """
    Compute trainable kernel element between two data points.

    Parameters
    ----------
    x1, x2 : array-like
        Data points to compute kernel between

    Returns
    -------
    kernel_value : float
        Trainable kernel value
    """
    x1 = np.asarray(x1, dtype=np.float64)
    x2 = np.asarray(x2, dtype=np.float64)

    # Create base circuits
    circuit1 = self.embedding.create_circuit(x1)
    circuit2 = self.embedding.create_circuit(x2)

    # Add trainable layers
    circuit1_trained = self._add_trainable_layer(circuit1)
    circuit2_trained = self._add_trainable_layer(circuit2)

    # Compute fidelity
    try:
        fidelity = self.backend.compute_fidelity(circuit1_trained, circuit2_trained)
        return float(fidelity)
    except Exception:
        # Fallback to statevector computation
        psi1 = self.backend.get_statevector(circuit1_trained)
        psi2 = self.backend.get_statevector(circuit2_trained)
        overlap = np.abs(np.vdot(psi1, psi2)) ** 2
        return float(overlap)

get_parameters()

Get current trainable parameters.

Returns:

Name Type Description
theta ndarray

Current parameter values

Source code in quantum_data_embedding_suite\kernels\trainable.py
def get_parameters(self) -> np.ndarray:
    """
    Get current trainable parameters.

    Returns
    -------
    theta : ndarray
        Current parameter values
    """
    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\kernels\trainable.py
def update_parameters(self, new_theta: np.ndarray) -> None:
    """
    Update trainable parameters.

    Parameters
    ----------
    new_theta : array-like
        New parameter values
    """
    self.theta = np.asarray(new_theta).flatten()[:self.n_params]

Usage Examples

Basic Kernel Creation

from quantum_data_embedding_suite.kernels import FidelityKernel
from quantum_data_embedding_suite.embeddings import AngleEmbedding
import numpy as np

# Create embedding and kernel
embedding = AngleEmbedding(n_qubits=4)
kernel = FidelityKernel(embedding, backend="qiskit")

# Compute kernel matrix
X = np.random.randn(50, 4)
K = kernel.compute_kernel(X)

print(f"Kernel matrix shape: {K.shape}")
print(f"Kernel is symmetric: {np.allclose(K, K.T)}")
print(f"Diagonal elements (should be ~1): {np.diag(K)[:5]}")

Kernel Comparison

from quantum_data_embedding_suite.kernels import (
    FidelityKernel, ProjectedKernel, TrainableKernel
)
from sklearn.metrics.pairwise import rbf_kernel

# Create different quantum kernels
embedding = AngleEmbedding(n_qubits=4)
kernels = {
    'fidelity': FidelityKernel(embedding),
    'projected': ProjectedKernel(embedding, n_measurements=100),
    'trainable': TrainableKernel(embedding, n_parameters=20)
}

# Compare with classical kernel
X = np.random.randn(30, 4)
K_classical = rbf_kernel(X)

results = {}
for name, kernel in kernels.items():
    K_quantum = kernel.compute_kernel(X)

    # Compute kernel alignment with classical kernel
    alignment = np.sum(K_quantum * K_classical) / (
        np.linalg.norm(K_quantum) * np.linalg.norm(K_classical)
    )

    results[name] = {
        'trace': np.trace(K_quantum),
        'frobenius_norm': np.linalg.norm(K_quantum),
        'classical_alignment': alignment
    }

for name, metrics in results.items():
    print(f"{name.upper()} Kernel:")
    for metric, value in metrics.items():
        print(f"  {metric}: {value:.4f}")
    print()

Custom Kernel Creation

from quantum_data_embedding_suite.kernels import BaseKernel

class CustomKernel(BaseKernel):
    """Custom kernel implementation"""

    def __init__(self, embedding, alpha=1.0, beta=0.5):
        super().__init__(embedding)
        self.alpha = alpha
        self.beta = beta

    def _compute_element(self, x_i, x_j):
        """Compute kernel element K(x_i, x_j)"""
        # Create quantum circuits
        circuit_i = self.embedding.embed(x_i)
        circuit_j = self.embedding.embed(x_j)

        # Compute fidelity
        fidelity = self._compute_fidelity(circuit_i, circuit_j)

        # Apply custom transformation
        kernel_value = self.alpha * fidelity**self.beta

        return kernel_value

    def _compute_fidelity(self, circuit_i, circuit_j):
        """Compute fidelity between two circuits"""
        # Implement fidelity computation
        # This is a simplified version
        from quantum_data_embedding_suite.backends import QiskitBackend
        backend = QiskitBackend(device="statevector_simulator")

        state_i = backend.get_statevector(circuit_i)
        state_j = backend.get_statevector(circuit_j)

        fidelity = np.abs(np.vdot(state_i, state_j))**2
        return fidelity

# Use custom kernel
custom_kernel = CustomKernel(embedding, alpha=2.0, beta=0.8)
K_custom = custom_kernel.compute_kernel(X[:10])  # Small subset for testing

Advanced Usage

Trainable Kernels

from quantum_data_embedding_suite.kernels import TrainableKernel
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split

# Create trainable kernel
trainable_kernel = TrainableKernel(
    embedding=embedding,
    n_parameters=15,
    optimization_method="adam",
    learning_rate=0.01
)

# Prepare data
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# Train the kernel
print("Training kernel...")
trainable_kernel.fit(
    X_train, y_train, 
    epochs=50,
    validation_split=0.2,
    verbose=True
)

# Use trained kernel for classification
K_train = trainable_kernel.compute_kernel(X_train)
K_test = trainable_kernel.compute_kernel(X_test, X_train)

svm = SVC(kernel='precomputed')
svm.fit(K_train, y_train)

train_accuracy = svm.score(K_train, y_train)
test_accuracy = svm.score(K_test, y_test)

print(f"Training accuracy: {train_accuracy:.4f}")
print(f"Test accuracy: {test_accuracy:.4f}")

Kernel Optimization

from quantum_data_embedding_suite.optimization import optimize_kernel

# Define objective function
def objective_function(kernel, X, y):
    """Objective function for kernel optimization"""
    K = kernel.compute_kernel(X)

    # Use cross-validation accuracy as objective
    from sklearn.model_selection import cross_val_score
    svm = SVC(kernel='precomputed')
    scores = cross_val_score(svm, K, y, cv=5)

    return scores.mean()

# Optimize kernel parameters
best_kernel = optimize_kernel(
    kernel_class=TrainableKernel,
    embedding=embedding,
    X_train=X_train,
    y_train=y_train,
    objective_function=objective_function,
    n_trials=50,
    optimization_method="bayesian"
)

print("Optimal kernel parameters found")

Batch Kernel Computation

def compute_kernel_batch(kernel, X, batch_size=100):
    """Compute kernel matrix in batches to manage memory"""
    n_samples = len(X)
    K = np.zeros((n_samples, n_samples))

    for i in range(0, n_samples, batch_size):
        for j in range(0, n_samples, batch_size):
            i_end = min(i + batch_size, n_samples)
            j_end = min(j + batch_size, n_samples)

            X_batch_i = X[i:i_end]
            X_batch_j = X[j:j_end]

            K_batch = kernel.compute_kernel(X_batch_i, X_batch_j)
            K[i:i_end, j:j_end] = K_batch

            print(f"Computed batch ({i}:{i_end}, {j}:{j_end})")

    return K

# Use batch computation for large datasets
large_X = np.random.randn(1000, 4)
K_large = compute_kernel_batch(kernel, large_X, batch_size=200)

Kernel Analysis

Kernel Properties

def analyze_kernel_properties(K):
    """Analyze properties of a kernel matrix"""
    n = K.shape[0]

    # Basic properties
    is_symmetric = np.allclose(K, K.T)
    is_psd = np.all(np.linalg.eigvals(K) >= -1e-10)

    # Eigenvalue analysis
    eigenvals = np.linalg.eigvals(K)
    eigenvals = np.real(eigenvals[eigenvals.argsort()[::-1]])

    # Effective rank
    eigenval_sum = np.sum(eigenvals)
    normalized_eigenvals = eigenvals / eigenval_sum
    effective_rank = 1.0 / np.sum(normalized_eigenvals**2)

    # Condition number
    condition_number = eigenvals[0] / eigenvals[-1] if eigenvals[-1] > 1e-12 else np.inf

    # Kernel alignment with identity
    identity_alignment = np.trace(K) / np.linalg.norm(K) / np.sqrt(n)

    return {
        'is_symmetric': is_symmetric,
        'is_positive_semidefinite': is_psd,
        'rank': np.linalg.matrix_rank(K),
        'effective_rank': effective_rank,
        'condition_number': condition_number,
        'trace': np.trace(K),
        'frobenius_norm': np.linalg.norm(K),
        'largest_eigenvalue': eigenvals[0],
        'smallest_eigenvalue': eigenvals[-1],
        'identity_alignment': identity_alignment,
        'eigenvalue_decay': eigenvals[:min(10, len(eigenvals))]
    }

# Analyze kernel
K = kernel.compute_kernel(X[:50])
properties = analyze_kernel_properties(K)

print("Kernel Properties:")
for prop, value in properties.items():
    if isinstance(value, np.ndarray):
        print(f"{prop}: {value}")
    elif isinstance(value, float):
        print(f"{prop}: {value:.6f}")
    else:
        print(f"{prop}: {value}")

Kernel Visualization

import matplotlib.pyplot as plt
import seaborn as sns

def visualize_kernel(K, labels=None, title="Kernel Matrix"):
    """Visualize kernel matrix"""
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))

    # Kernel matrix heatmap
    sns.heatmap(K, ax=axes[0,0], cmap='viridis', cbar=True)
    axes[0,0].set_title(f"{title} - Heatmap")

    # Eigenvalue spectrum
    eigenvals = np.linalg.eigvals(K)
    eigenvals = np.real(eigenvals[eigenvals.argsort()[::-1]])
    axes[0,1].plot(eigenvals, 'o-')
    axes[0,1].set_title("Eigenvalue Spectrum")
    axes[0,1].set_xlabel("Index")
    axes[0,1].set_ylabel("Eigenvalue")
    axes[0,1].set_yscale('log')

    # Kernel values distribution
    K_upper = K[np.triu_indices_from(K, k=1)]
    axes[1,0].hist(K_upper, bins=30, alpha=0.7)
    axes[1,0].set_title("Off-diagonal Elements Distribution")
    axes[1,0].set_xlabel("Kernel Value")
    axes[1,0].set_ylabel("Frequency")

    # Diagonal elements
    diag_elements = np.diag(K)
    axes[1,1].hist(diag_elements, bins=20, alpha=0.7)
    axes[1,1].set_title("Diagonal Elements Distribution")
    axes[1,1].set_xlabel("Kernel Value")
    axes[1,1].set_ylabel("Frequency")

    plt.tight_layout()
    plt.show()

# Visualize kernel
visualize_kernel(K, title="Quantum Fidelity Kernel")

Performance Optimization

Parallel Kernel Computation

from multiprocessing import Pool
import functools

def compute_kernel_parallel(kernel, X, n_jobs=4):
    """Compute kernel matrix using parallel processing"""
    n_samples = len(X)

    def compute_row(i):
        """Compute one row of the kernel matrix"""
        row = np.zeros(n_samples)
        for j in range(n_samples):
            if j <= i:  # Exploit symmetry
                row[j] = kernel._compute_element(X[i], X[j])
        return i, row

    # Compute upper triangle in parallel
    with Pool(n_jobs) as pool:
        results = pool.map(compute_row, range(n_samples))

    # Construct full matrix
    K = np.zeros((n_samples, n_samples))
    for i, row in results:
        K[i, :] = row

    # Fill lower triangle using symmetry
    K = K + K.T - np.diag(np.diag(K))

    return K

# Use parallel computation
K_parallel = compute_kernel_parallel(kernel, X[:20], n_jobs=2)

Approximate Kernels

class ApproximateKernel:
    """Approximate kernel using random sampling"""

    def __init__(self, base_kernel, n_samples=100):
        self.base_kernel = base_kernel
        self.n_samples = n_samples

    def compute_kernel(self, X, Y=None):
        """Compute approximate kernel using random sampling"""
        if Y is None:
            Y = X

        n_x, n_y = len(X), len(Y)

        # Sample random pairs for approximation
        n_total_pairs = n_x * n_y
        n_sample_pairs = min(self.n_samples, n_total_pairs)

        # Generate random indices
        i_indices = np.random.randint(0, n_x, n_sample_pairs)
        j_indices = np.random.randint(0, n_y, n_sample_pairs)

        # Compute sampled kernel values
        K_approx = np.zeros((n_x, n_y))
        sample_count = np.zeros((n_x, n_y))

        for i, j in zip(i_indices, j_indices):
            k_val = self.base_kernel._compute_element(X[i], Y[j])
            K_approx[i, j] += k_val
            sample_count[i, j] += 1

        # Average multiple samples
        K_approx = np.divide(K_approx, sample_count, 
                           out=np.zeros_like(K_approx), 
                           where=sample_count!=0)

        # Fill missing values with mean
        mean_val = np.mean(K_approx[K_approx > 0])
        K_approx[sample_count == 0] = mean_val

        return K_approx

# Use approximate kernel
approx_kernel = ApproximateKernel(kernel, n_samples=500)
K_approx = approx_kernel.compute_kernel(X[:100])

Integration with Machine Learning

Scikit-learn Integration

from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.pipeline import Pipeline

class QuantumKernelTransformer(BaseEstimator, TransformerMixin):
    """Scikit-learn transformer for quantum kernels"""

    def __init__(self, quantum_kernel):
        self.quantum_kernel = quantum_kernel

    def fit(self, X, y=None):
        """Fit the transformer"""
        self.X_train_ = X.copy()
        return self

    def transform(self, X):
        """Transform data to kernel space"""
        if not hasattr(self, 'X_train_'):
            raise ValueError("Transformer not fitted")

        # Compute kernel between X and training data
        K = self.quantum_kernel.compute_kernel(X, self.X_train_)
        return K

    def fit_transform(self, X, y=None):
        """Fit and transform"""
        self.fit(X, y)
        # For training data, compute full kernel matrix
        K = self.quantum_kernel.compute_kernel(X)
        return K

# Use in scikit-learn pipeline
pipeline = Pipeline([
    ('quantum_kernel', QuantumKernelTransformer(kernel)),
    ('svm', SVC(kernel='precomputed'))
])

# Train pipeline
pipeline.fit(X_train, y_train)

# Predict
predictions = pipeline.predict(X_test)

Custom Kernel for SVM

def create_sklearn_kernel(quantum_kernel):
    """Create sklearn-compatible kernel function"""

    def sklearn_kernel(X, Y=None):
        """Kernel function for sklearn"""
        if Y is None:
            # Training phase - compute full kernel matrix
            return quantum_kernel.compute_kernel(X)
        else:
            # Prediction phase - compute cross-kernel
            return quantum_kernel.compute_kernel(X, Y)

    return sklearn_kernel

# Use with sklearn SVM
from sklearn.svm import SVC

sklearn_kernel_func = create_sklearn_kernel(kernel)
svm = SVC(kernel=sklearn_kernel_func)

# Note: This approach has limitations with sklearn's kernel caching
# It's better to use precomputed kernels for quantum kernels

Error Handling and Debugging

Kernel Validation

def validate_kernel(K, tolerance=1e-10):
    """Validate kernel matrix properties"""
    errors = []
    warnings = []

    # Check if matrix is square
    if K.shape[0] != K.shape[1]:
        errors.append("Kernel matrix is not square")
        return errors, warnings

    n = K.shape[0]

    # Check symmetry
    if not np.allclose(K, K.T, atol=tolerance):
        errors.append("Kernel matrix is not symmetric")

    # Check positive semidefiniteness
    eigenvals = np.linalg.eigvals(K)
    min_eigenval = np.min(np.real(eigenvals))
    if min_eigenval < -tolerance:
        errors.append(f"Kernel matrix is not PSD (min eigenvalue: {min_eigenval})")

    # Check diagonal elements
    diag_elements = np.diag(K)
    if np.any(diag_elements < 0):
        warnings.append("Some diagonal elements are negative")

    if np.any(diag_elements > 1 + tolerance):
        warnings.append("Some diagonal elements are > 1 (unusual for normalized kernels)")

    # Check for NaN or Inf values
    if np.any(np.isnan(K)):
        errors.append("Kernel matrix contains NaN values")

    if np.any(np.isinf(K)):
        errors.append("Kernel matrix contains infinite values")

    # Check condition number
    condition_num = np.linalg.cond(K)
    if condition_num > 1e12:
        warnings.append(f"Kernel matrix is ill-conditioned (cond={condition_num:.2e})")

    return errors, warnings

# Validate kernel matrix
errors, warnings = validate_kernel(K)

if errors:
    print("Kernel validation errors:")
    for error in errors:
        print(f"  - {error}")

if warnings:
    print("Kernel validation warnings:")
    for warning in warnings:
        print(f"  - {warning}")

if not errors and not warnings:
    print("Kernel matrix is valid")

Debugging Tools

def debug_kernel_computation(kernel, X, verbose=True):
    """Debug kernel computation step by step"""
    if verbose:
        print(f"Debugging kernel: {type(kernel).__name__}")
        print(f"Embedding: {type(kernel.embedding).__name__}")
        print(f"Data shape: {X.shape}")

    # Test single element computation
    try:
        if len(X) >= 2:
            k_val = kernel._compute_element(X[0], X[1])
            if verbose:
                print(f"Sample kernel value K(x_0, x_1): {k_val}")

        # Test diagonal element
        k_diag = kernel._compute_element(X[0], X[0])
        if verbose:
            print(f"Diagonal element K(x_0, x_0): {k_diag}")

        # Test small kernel matrix
        if len(X) >= 3:
            K_small = kernel.compute_kernel(X[:3])
            if verbose:
                print(f"Small kernel matrix (3x3):")
                print(K_small)

            # Validate small matrix
            errors, warnings = validate_kernel(K_small)
            if errors:
                print(f"Errors in small kernel: {errors}")
            if warnings:
                print(f"Warnings in small kernel: {warnings}")

    except Exception as e:
        print(f"Error during kernel computation: {e}")
        import traceback
        traceback.print_exc()
        return False

    return True

# Debug kernel
debug_success = debug_kernel_computation(kernel, X)

Best Practices

Kernel Selection Guidelines

def recommend_kernel(data_characteristics, problem_type):
    """Recommend kernel based on data and problem characteristics"""

    n_samples, n_features = data_characteristics['n_samples'], data_characteristics['n_features']
    noise_level = data_characteristics.get('noise_level', 'medium')
    computational_budget = data_characteristics.get('computational_budget', 'medium')

    recommendations = []

    # Data size considerations
    if n_samples < 100:
        recommendations.append("Consider FidelityKernel for small datasets")
    elif n_samples > 1000:
        recommendations.append("Consider ProjectedKernel or approximate methods for large datasets")

    # Noise considerations
    if noise_level == 'high':
        recommendations.append("ProjectedKernel may be more robust to noise")
    else:
        recommendations.append("FidelityKernel provides better accuracy for low-noise data")

    # Computational budget
    if computational_budget == 'low':
        recommendations.append("Use ProjectedKernel with fewer measurements")
    elif computational_budget == 'high':
        recommendations.append("Consider TrainableKernel for optimal performance")

    # Problem type
    if problem_type == 'classification':
        recommendations.append("All kernel types suitable for classification")
    elif problem_type == 'regression':
        recommendations.append("FidelityKernel often works well for regression")

    return recommendations

# Get recommendations
data_chars = {
    'n_samples': 500,
    'n_features': 8,
    'noise_level': 'medium',
    'computational_budget': 'high'
}

recommendations = recommend_kernel(data_chars, 'classification')
print("Kernel recommendations:")
for rec in recommendations:
    print(f"  - {rec}")

Further Reading