Skip to content

Encoders

Encoders map a normalized feature vector to a parameterized quantum circuit.

All encoders:

  • Accept a 1-D np.ndarray
  • Return an EncodedResult with .parameters and .metadata
  • Are deterministic — same input always produces same output
  • Do not normalize — normalization is the pipeline's job

AngleEncoder

quprep.encode.angle.AngleEncoder(rotation='ry')

Bases: BaseEncoder

Angle encoding using single-qubit rotation gates.

Parameters:

Name Type Description Default
rotation str

Rotation gate to use: 'ry' (default), 'rx', or 'rz'.

'ry'
Source code in quprep/encode/angle.py
def __init__(self, rotation: str = "ry"):
    if rotation not in ("ry", "rx", "rz"):
        raise ValueError(f"rotation must be 'ry', 'rx', or 'rz', got '{rotation}'")
    self.rotation = rotation

Functions

encode(x)

Encode feature vector x as rotation angles.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector. Must be in \([0, \pi]\) for 'ry', or \([-\pi, \pi]\) for 'rx'/'rz'. Use quprep.normalize.auto_normalizer(encoding) for correct scaling.

required

Returns:

Type Description
EncodedResult

parameters = x (rotation angles, one per qubit). metadata includes encoding, rotation, n_qubits, depth.

Source code in quprep/encode/angle.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode feature vector x as rotation angles.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector. Must be in $[0, \pi]$ for 'ry',
        or $[-\pi, \pi]$ for 'rx'/'rz'. Use
        ``quprep.normalize.auto_normalizer(encoding)`` for correct scaling.

    Returns
    -------
    EncodedResult
        ``parameters`` = x (rotation angles, one per qubit).
        ``metadata`` includes ``encoding``, ``rotation``, ``n_qubits``, ``depth``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1:
        raise ValueError(f"Expected 1D input, got shape {x.shape}")
    if len(x) == 0:
        raise ValueError("Input vector must not be empty")
    return EncodedResult(
        parameters=x.copy(),
        metadata={
            "encoding": "angle",
            "rotation": self.rotation,
            "n_qubits": len(x),
            "depth": 1,
        },
    )

AmplitudeEncoder

quprep.encode.amplitude.AmplitudeEncoder(pad=True)

Bases: BaseEncoder

Amplitude encoding.

x must be L2-normalized (\(\|x\|_2 = 1\)). Use quprep.normalize.Scaler('l2').

Parameters:

Name Type Description Default
pad bool

If True, zero-pad x to the next power of two when d is not 2^n. If False, raise ValueError.

True
Source code in quprep/encode/amplitude.py
def __init__(self, pad: bool = True):
    self.pad = pad

Functions

encode(x)

Encode x as amplitude vector.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

L2-normalized feature vector (\(\|x\|_2 = 1\)). Use Scaler('l2'). If d is not a power of two and pad=True, zero-pads and re-normalizes.

required

Returns:

Type Description
EncodedResult

parameters = amplitude vector (length padded to next power of two). metadata includes encoding, n_qubits, padded, original_dim.

Source code in quprep/encode/amplitude.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode x as amplitude vector.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        L2-normalized feature vector ($\|x\|_2 = 1$). Use ``Scaler('l2')``.
        If d is not a power of two and ``pad=True``, zero-pads and re-normalizes.

    Returns
    -------
    EncodedResult
        ``parameters`` = amplitude vector (length padded to next power of two).
        ``metadata`` includes ``encoding``, ``n_qubits``, ``padded``, ``original_dim``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1:
        raise ValueError(f"Expected 1D input, got shape {x.shape}")
    if len(x) == 0:
        raise ValueError("Input vector must not be empty")

    norm = np.linalg.norm(x)
    if not np.isclose(norm, 1.0, atol=1e-6):
        raise ValueError(
            f"AmplitudeEncoder requires L2-normalized input (‖x‖₂ = 1), "
            f"got ‖x‖₂ = {norm:.6f}. Use Scaler('l2') first."
        )

    d = len(x)
    next_pow2 = 1 << (d - 1).bit_length() if d > 1 else 1
    if d != next_pow2:
        if not self.pad:
            raise ValueError(
                f"d={d} is not a power of two. Set pad=True to zero-pad, "
                f"or reduce dimensions to d={next_pow2}."
            )
        padded = np.zeros(next_pow2, dtype=float)
        padded[:d] = x
        padded /= np.linalg.norm(padded)  # re-normalize after padding
        warnings.warn(
            f"AmplitudeEncoder: d={d} is not a power of two — zero-padded to {next_pow2} "
            f"and re-normalized. {next_pow2 - d} zero amplitude(s) added. "
            "Use a dimensionality reducer to avoid this.",
            QuPrepWarning,
            stacklevel=2,
        )
    else:
        padded = x.copy()

    n_qubits = int(np.log2(len(padded)))
    return EncodedResult(
        parameters=padded,
        metadata={
            "encoding": "amplitude",
            "n_qubits": n_qubits,
            "depth": "O(2^n)",
            "padded": d != next_pow2,
            "original_dim": d,
        },
    )

BasisEncoder

quprep.encode.basis.BasisEncoder(threshold=0.5)

Bases: BaseEncoder

Basis (computational state) encoding.

Input x must be binary {0, 1} per feature. Use quprep.normalize.Scaler('binary').

Parameters:

Name Type Description Default
threshold float

Binarization threshold when input is continuous. Default 0.5.

0.5
Source code in quprep/encode/basis.py
def __init__(self, threshold: float = 0.5):
    self.threshold = threshold

Functions

encode(x)

Binarize x and encode as computational basis state.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Feature vector. Values >= threshold → 1; values < threshold → 0.

required

Returns:

Type Description
EncodedResult

parameters = binary vector of 0.0/1.0 values (one per qubit). metadata includes encoding, threshold, n_qubits, depth.

Source code in quprep/encode/basis.py
def encode(self, x: np.ndarray) -> EncodedResult:
    """
    Binarize x and encode as computational basis state.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Feature vector. Values >= threshold → 1; values < threshold → 0.

    Returns
    -------
    EncodedResult
        ``parameters`` = binary vector of 0.0/1.0 values (one per qubit).
        ``metadata`` includes ``encoding``, ``threshold``, ``n_qubits``, ``depth``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1:
        raise ValueError(f"Expected 1D input, got shape {x.shape}")
    if len(x) == 0:
        raise ValueError("Input vector must not be empty")

    bits = (x >= self.threshold).astype(float)
    return EncodedResult(
        parameters=bits,
        metadata={
            "encoding": "basis",
            "threshold": self.threshold,
            "n_qubits": len(bits),
            "depth": 1,
        },
    )

BaseEncoder and EncodedResult

quprep.encode.base.BaseEncoder

Bases: ABC

Abstract interface for quantum encoders.

All encoders must implement encode(), which maps a normalized feature vector \(x \in \mathbb{R}^d\) to a parameterized quantum circuit.

Subclasses should document: - The mathematical mapping x → circuit parameters. - The number of qubits as a function of d. - The circuit depth as a function of d. - NISQ suitability.

Attributes

depth abstractmethod property

Circuit depth (integer or asymptotic expression).

n_qubits abstractmethod property

Number of qubits required. None if data-dependent.

Functions

encode(x) abstractmethod

Encode a single normalized feature vector.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector.

required

Returns:

Type Description
EncodedResult

Circuit parameters and metadata for the encoded sample.

encode_batch(dataset)

Encode all samples in a Dataset.

Parameters:

Name Type Description Default
dataset Dataset

Dataset whose rows are encoded one by one.

required

Returns:

Type Description
list of EncodedResult

One EncodedResult per sample row.

quprep.encode.base.EncodedResult(parameters, circuit_fn=None, metadata=None)

Output of an encoder for a single sample.

Attributes:

Name Type Description
parameters ndarray

Circuit parameter values.

circuit_fn callable or None

A function(framework) → circuit object, filled in by the exporter.

metadata dict

Encoding details (method, n_qubits, depth, etc.).


EntangledAngleEncoder

quprep.encode.entangled_angle.EntangledAngleEncoder(rotation='ry', layers=1, entanglement='linear')

Bases: BaseEncoder

Angle encoding with entangling CNOT layers between rotation rounds.

Each layer applies single-qubit rotations on all qubits followed by a structured CNOT entangling block. Features are re-uploaded in every layer (data re-uploading style).

Parameters:

Name Type Description Default
rotation str

Rotation gate: 'ry' (default), 'rx', or 'rz'.

'ry'
layers int

Number of rotation+entanglement repetitions. Default 1.

1
entanglement str

CNOT topology: 'linear' (default), 'circular', or 'full'.

'linear'
Source code in quprep/encode/entangled_angle.py
def __init__(
    self,
    rotation: str = "ry",
    layers: int = 1,
    entanglement: str = "linear",
):
    if rotation not in ("ry", "rx", "rz"):
        raise ValueError(f"rotation must be 'ry', 'rx', or 'rz', got '{rotation}'")
    if layers < 1:
        raise ValueError(f"layers must be >= 1, got {layers}")
    if entanglement not in _ENTANGLEMENT_PATTERNS:
        raise ValueError(
            f"entanglement must be one of {_ENTANGLEMENT_PATTERNS}, got '{entanglement}'"
        )
    self.rotation = rotation
    self.layers = layers
    self.entanglement = entanglement

Functions

encode(x)

Encode feature vector x as entangled rotation angles.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector. Use auto_normalizer for correct scaling (same as AngleEncoder: minmax_pi for Ry, minmax_pm_pi for Rx/Rz).

required

Returns:

Type Description
EncodedResult

parameters = x (stored once; exporter repeats per layer).

Source code in quprep/encode/entangled_angle.py
def encode(self, x: np.ndarray) -> EncodedResult:
    """
    Encode feature vector x as entangled rotation angles.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector. Use ``auto_normalizer`` for correct
        scaling (same as AngleEncoder: ``minmax_pi`` for Ry, ``minmax_pm_pi``
        for Rx/Rz).

    Returns
    -------
    EncodedResult
        ``parameters`` = x (stored once; exporter repeats per layer).
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1:
        raise ValueError(f"Expected 1D input, got shape {x.shape}")
    if len(x) == 0:
        raise ValueError("Input vector must not be empty")

    d = len(x)
    # Compute CNOT pairs for metadata (so exporters don't need to recompute)
    cnot_pairs = _cnot_pairs(d, self.entanglement)

    # Depth: d rotation gates + len(cnot_pairs) CNOTs per layer
    circuit_depth = (d + len(cnot_pairs)) * self.layers

    return EncodedResult(
        parameters=x.copy(),
        metadata={
            "encoding": "entangled_angle",
            "rotation": self.rotation,
            "n_qubits": d,
            "layers": self.layers,
            "entanglement": self.entanglement,
            "cnot_pairs": cnot_pairs,
            "depth": circuit_depth,
        },
    )

IQPEncoder

quprep.encode.iqp.IQPEncoder(reps=2)

Bases: BaseEncoder

IQP feature map encoding.

Parameters:

Name Type Description Default
reps int

Number of repetitions of the feature map layer. Default 2.

2
Source code in quprep/encode/iqp.py
def __init__(self, reps: int = 2):
    if reps < 1:
        raise ValueError(f"reps must be >= 1, got {reps}.")
    self.reps = reps

Functions

encode(x)

Encode a 1-D feature vector using the IQP feature map.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector in \([-\pi, \pi]\). Use Scaler('minmax_pm_pi').

required

Returns:

Type Description
EncodedResult

parameters = \([x_0, \ldots, x_{d-1}, x_0 x_1, \ldots, x_{d-2} x_{d-1}]\) (d single-qubit angles followed by \(d(d-1)/2\) pairwise products). metadata includes encoding, n_qubits, reps, depth, n_pairs.

Source code in quprep/encode/iqp.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode a 1-D feature vector using the IQP feature map.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector in $[-\pi, \pi]$. Use ``Scaler('minmax_pm_pi')``.

    Returns
    -------
    EncodedResult
        ``parameters`` = $[x_0, \ldots, x_{d-1}, x_0 x_1, \ldots, x_{d-2} x_{d-1}]$
        (d single-qubit angles followed by $d(d-1)/2$ pairwise products).
        ``metadata`` includes ``encoding``, ``n_qubits``, ``reps``, ``depth``, ``n_pairs``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("IQPEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)
    pairs = np.array(
        [x[i] * x[j] for i in range(d) for j in range(i + 1, d)],
        dtype=float,
    )
    parameters = np.concatenate([x, pairs])

    return EncodedResult(
        parameters=parameters,
        metadata={
            "encoding": "iqp",
            "n_qubits": d,
            "reps": self.reps,
            "depth": self.reps * (2 + 3 * d * (d - 1) // 2),
            "n_pairs": len(pairs),
        },
    )

ReUploadEncoder

quprep.encode.reupload.ReUploadEncoder(layers=3, rotation='ry')

Bases: BaseEncoder

Data re-uploading encoder.

Parameters:

Name Type Description Default
layers int

Number of re-upload layers L. Default 3.

3
rotation str

Rotation gate for data encoding: 'ry', 'rx', or 'rz'.

'ry'
Source code in quprep/encode/reupload.py
def __init__(self, layers: int = 3, rotation: str = "ry"):
    if layers < 1:
        raise ValueError(f"layers must be >= 1, got {layers}.")
    if rotation not in _VALID_ROTATIONS:
        raise ValueError(
            f"Invalid rotation '{rotation}'. Choose from {sorted(_VALID_ROTATIONS)}."
        )
    self.layers = layers
    self.rotation = rotation

Functions

encode(x)

Encode a 1-D feature vector using data re-uploading.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector in \([-\pi, \pi]\). Use Scaler('minmax_pm_pi').

required

Returns:

Type Description
EncodedResult

parameters = x stored once (exporter repeats it layers times). metadata includes encoding, n_qubits, layers, rotation, depth.

Source code in quprep/encode/reupload.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode a 1-D feature vector using data re-uploading.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector in $[-\pi, \pi]$. Use ``Scaler('minmax_pm_pi')``.

    Returns
    -------
    EncodedResult
        ``parameters`` = x stored once (exporter repeats it ``layers`` times).
        ``metadata`` includes ``encoding``, ``n_qubits``, ``layers``,
        ``rotation``, ``depth``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("ReUploadEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)

    return EncodedResult(
        parameters=x.copy(),
        metadata={
            "encoding": "reupload",
            "n_qubits": d,
            "layers": self.layers,
            "rotation": self.rotation,
            "depth": d * self.layers,
        },
    )

HamiltonianEncoder

quprep.encode.hamiltonian.HamiltonianEncoder(evolution_time=1.0, trotter_steps=4)

Bases: BaseEncoder

Hamiltonian / time-evolution encoding.

Parameters:

Name Type Description Default
evolution_time float

Evolution time T.

1.0
trotter_steps int

Number of Trotter steps S. More steps → higher fidelity, greater depth.

4
Source code in quprep/encode/hamiltonian.py
def __init__(self, evolution_time: float = 1.0, trotter_steps: int = 4):
    if trotter_steps < 1:
        raise ValueError(f"trotter_steps must be >= 1, got {trotter_steps}.")
    if evolution_time <= 0:
        raise ValueError(f"evolution_time must be > 0, got {evolution_time}.")
    self.evolution_time = evolution_time
    self.trotter_steps = trotter_steps

Functions

encode(x)

Encode a 1-D feature vector using Trotterized Hamiltonian evolution.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Feature vector encoding Hamiltonian coefficients. Use Scaler('zscore').

required

Returns:

Type Description
EncodedResult

parameters = per-step Rz angles \(2 x_i T/S\) (one per qubit). The exporter applies these trotter_steps times per qubit. metadata includes encoding, n_qubits, trotter_steps, evolution_time, depth.

Source code in quprep/encode/hamiltonian.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode a 1-D feature vector using Trotterized Hamiltonian evolution.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Feature vector encoding Hamiltonian coefficients. Use ``Scaler('zscore')``.

    Returns
    -------
    EncodedResult
        ``parameters`` = per-step Rz angles $2 x_i T/S$ (one per qubit).
        The exporter applies these ``trotter_steps`` times per qubit.
        ``metadata`` includes ``encoding``, ``n_qubits``, ``trotter_steps``,
        ``evolution_time``, ``depth``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("HamiltonianEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)
    # Per-step rotation angle: 2 * x_i * T / S
    angles = 2.0 * x * self.evolution_time / self.trotter_steps

    return EncodedResult(
        parameters=angles,
        metadata={
            "encoding": "hamiltonian",
            "n_qubits": d,
            "trotter_steps": self.trotter_steps,
            "evolution_time": self.evolution_time,
            "depth": d * self.trotter_steps,
        },
    )

ZZFeatureMapEncoder

quprep.encode.zz_feature_map.ZZFeatureMapEncoder(reps=2)

Bases: BaseEncoder

ZZ feature map encoder (Qiskit-compatible convention).

Implements the feature map from Havlíček et al. (2019) using the same parameter convention as Qiskit's ZZFeatureMap: single-qubit angles are 2(π − xᵢ) and pairwise angles are 2(π − xᵢ)(π − xⱼ).

Parameters:

Name Type Description Default
reps int

Number of repetitions of the feature map layer. Default 2.

2
Source code in quprep/encode/zz_feature_map.py
def __init__(self, reps: int = 2):
    if reps < 1:
        raise ValueError(f"reps must be >= 1, got {reps}.")
    self.reps = reps

Functions

encode(x)

Encode a 1-D feature vector using the ZZ feature map.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector in \([0, 2\pi]\). Use Scaler('minmax') scaled to [0, 2π].

required

Returns:

Type Description
EncodedResult

parameters = original x (kept for QASM generation). metadata includes encoding, n_qubits, reps, depth, single_angles, pair_angles, pairs.

Source code in quprep/encode/zz_feature_map.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode a 1-D feature vector using the ZZ feature map.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector in $[0, 2\pi]$.
        Use ``Scaler('minmax')`` scaled to ``[0, 2π]``.

    Returns
    -------
    EncodedResult
        ``parameters`` = original ``x`` (kept for QASM generation).
        ``metadata`` includes ``encoding``, ``n_qubits``, ``reps``,
        ``depth``, ``single_angles``, ``pair_angles``, ``pairs``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("ZZFeatureMapEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)
    single_angles = 2.0 * (np.pi - x)

    pairs = [(i, j) for i in range(d) for j in range(i + 1, d)]
    pair_angles = np.array(
        [2.0 * (np.pi - x[i]) * (np.pi - x[j]) for i, j in pairs],
        dtype=float,
    )

    return EncodedResult(
        parameters=x.copy(),
        metadata={
            "encoding": "zz_feature_map",
            "n_qubits": d,
            "reps": self.reps,
            "depth": self.reps * (2 + 3 * d * (d - 1) // 2),
            "single_angles": single_angles.tolist(),
            "pair_angles": pair_angles.tolist(),
            "pairs": pairs,
        },
    )

PauliFeatureMapEncoder

quprep.encode.pauli_feature_map.PauliFeatureMapEncoder(paulis=None, reps=2)

Bases: BaseEncoder

Pauli feature map encoder.

Generalizes the ZZ feature map by allowing arbitrary single- and two-qubit Pauli strings as interaction terms. The default paulis=['Z', 'ZZ'] reproduces the ZZ feature map (with direct x_i parametrization rather than the π − xᵢ convention).

Parameters:

Name Type Description Default
paulis list of str

Pauli strings to include. Single-qubit: 'X', 'Y', 'Z'. Two-qubit: 'ZZ', 'XX', 'YY', 'XZ', etc. Default ['Z', 'ZZ'].

None
reps int

Number of repetitions. Default 2.

2
Source code in quprep/encode/pauli_feature_map.py
def __init__(self, paulis: list[str] | None = None, reps: int = 2):
    if paulis is None:
        paulis = ["Z", "ZZ"]
    for p in paulis:
        if p not in _VALID_PAULIS:
            raise ValueError(
                f"Unknown Pauli '{p}'. Valid options: {sorted(_VALID_PAULIS)}"
            )
    if reps < 1:
        raise ValueError(f"reps must be >= 1, got {reps}.")
    self.paulis = list(paulis)
    self.reps = reps

Functions

encode(x)

Encode a 1-D feature vector using the Pauli feature map.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector in \([-\pi, \pi]\). Use Scaler('minmax_pm_pi').

required

Returns:

Type Description
EncodedResult

parameters = x. metadata includes encoding, n_qubits, paulis, reps, single_terms, pair_terms.

Source code in quprep/encode/pauli_feature_map.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode a 1-D feature vector using the Pauli feature map.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector in $[-\pi, \pi]$.
        Use ``Scaler('minmax_pm_pi')``.

    Returns
    -------
    EncodedResult
        ``parameters`` = ``x``.
        ``metadata`` includes ``encoding``, ``n_qubits``, ``paulis``,
        ``reps``, ``single_terms``, ``pair_terms``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("PauliFeatureMapEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)

    # Build single-qubit terms: {pauli_str: [angle_0, ..., angle_{d-1}]}
    single_terms: dict[str, list[float]] = {}
    for p in self.paulis:
        if len(p) == 1:
            single_terms[p] = (2.0 * x).tolist()

    # Build pairwise terms: {pauli_str: [(i, j, angle), ...]}
    pair_terms: dict[str, list[tuple[int, int, float]]] = {}
    for p in self.paulis:
        if len(p) == 2:
            entries = [
                (i, j, float(2.0 * x[i] * x[j]))
                for i in range(d)
                for j in range(i + 1, d)
            ]
            pair_terms[p] = entries

    return EncodedResult(
        parameters=x.copy(),
        metadata={
            "encoding": "pauli_feature_map",
            "n_qubits": d,
            "paulis": self.paulis,
            "reps": self.reps,
            "depth": d * d * self.reps if pair_terms else d * self.reps,
            "single_terms": single_terms,
            "pair_terms": pair_terms,
        },
    )

RandomFourierEncoder

quprep.encode.random_fourier.RandomFourierEncoder(n_components=8, gamma=1.0, random_state=None)

Bases: BaseEncoder

Random Fourier features encoder.

Projects input features through a random Fourier basis to approximate the RBF kernel, then angle-encodes the result on a fixed number of qubits. Must be fitted before use.

Parameters:

Name Type Description Default
n_components int

Number of random Fourier features (= number of qubits). Default 8.

8
gamma float

RBF kernel bandwidth parameter. Default 1.0.

1.0
random_state int or None

Seed for reproducibility. Default None.

None
Source code in quprep/encode/random_fourier.py
def __init__(
    self,
    n_components: int = 8,
    gamma: float = 1.0,
    random_state: int | None = None,
):
    if n_components < 1:
        raise ValueError(f"n_components must be >= 1, got {n_components}.")
    if gamma <= 0:
        raise ValueError(f"gamma must be > 0, got {gamma}.")
    self.n_components = n_components
    self.gamma = gamma
    self.random_state = random_state
    self._W: np.ndarray | None = None
    self._b: np.ndarray | None = None
    self._z_min: float = 0.0
    self._z_max: float = 0.0

Functions

encode(x)

Project x through random Fourier features and angle-encode.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Feature vector. Must match the dimensionality used in :meth:fit.

required

Returns:

Type Description
EncodedResult

parameters = scaled angles in \([0, \pi]\) of length n_components. metadata includes encoding, n_qubits, n_components, gamma, depth.

Raises:

Type Description
RuntimeError

If :meth:fit has not been called.

Source code in quprep/encode/random_fourier.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Project ``x`` through random Fourier features and angle-encode.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Feature vector. Must match the dimensionality used in :meth:`fit`.

    Returns
    -------
    EncodedResult
        ``parameters`` = scaled angles in $[0, \pi]$ of length ``n_components``.
        ``metadata`` includes ``encoding``, ``n_qubits``, ``n_components``,
        ``gamma``, ``depth``.

    Raises
    ------
    RuntimeError
        If :meth:`fit` has not been called.
    """
    if self._W is None:
        raise RuntimeError(
            "RandomFourierEncoder must be fitted before encoding. Call fit(X) first."
        )
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("RandomFourierEncoder.encode() expects a non-empty 1-D array.")

    z = np.sqrt(2.0 / self.n_components) * np.cos(self._W @ x + self._b)
    # Scale to [0, π] using training-data range so all samples receive the
    # same affine transform, preserving the kernel approximation property.
    z_range = self._z_max - self._z_min
    if z_range > 1e-12:
        angles = (z - self._z_min) / z_range * np.pi
    else:
        angles = np.zeros_like(z)

    return EncodedResult(
        parameters=angles,
        metadata={
            "encoding": "random_fourier",
            "n_qubits": self.n_components,
            "n_components": self.n_components,
            "gamma": self.gamma,
            "depth": 1,
        },
    )

fit(X)

Sample the random projection matrix from the training data shape.

Parameters:

Name Type Description Default
X (ndarray, shape(n_samples, d))

Training data (used only for dimensionality).

required

Returns:

Type Description
self
Source code in quprep/encode/random_fourier.py
def fit(self, X: np.ndarray) -> RandomFourierEncoder:
    """
    Sample the random projection matrix from the training data shape.

    Parameters
    ----------
    X : np.ndarray, shape (n_samples, d)
        Training data (used only for dimensionality).

    Returns
    -------
    self
    """
    X = np.asarray(X, dtype=float)
    if X.ndim == 1:
        X = X.reshape(1, -1)
    d = X.shape[1]
    rng = np.random.default_rng(self.random_state)
    self._W = rng.normal(0, np.sqrt(2.0 * self.gamma), size=(self.n_components, d))
    self._b = rng.uniform(0, 2 * np.pi, size=(self.n_components,))
    # Compute global z range from training data so all samples use the same
    # affine scaling — preserving the RBF kernel approximation property.
    Z = np.sqrt(2.0 / self.n_components) * np.cos(self._W @ X.T + self._b[:, None])
    self._z_min = float(Z.min())
    self._z_max = float(Z.max())
    self._fitted = True
    return self

TensorProductEncoder

quprep.encode.tensor_product.TensorProductEncoder

Bases: BaseEncoder

Tensor product encoding using full Bloch sphere parameterization.

Encodes pairs of features as Ry + Rz rotations on individual qubits with no entanglement. Uses ⌈d/2⌉ qubits for d-dimensional input.

No parameters.

Functions

encode(x)

Encode a 1-D feature vector onto the Bloch sphere.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector. Use Scaler('minmax') scaled to \([0, \pi]\) for \(R_y\) angles (polar) and Scaler('minmax_pm_pi') scaled to \([-\pi, \pi]\) for \(R_z\) angles (azimuthal). In practice a single Scaler('minmax') applied to all features works well as a starting point.

required

Returns:

Type Description
EncodedResult

parameters = flat array of alternating Ry/Rz angles, length 2 * ⌈d/2⌉ (odd inputs are zero-padded for the missing Rz angle). metadata includes encoding, n_qubits, depth, ry_angles, rz_angles.

Source code in quprep/encode/tensor_product.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode a 1-D feature vector onto the Bloch sphere.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector. Use ``Scaler('minmax')`` scaled to
        $[0, \pi]$ for $R_y$ angles (polar) and ``Scaler('minmax_pm_pi')``
        scaled to $[-\pi, \pi]$ for $R_z$ angles (azimuthal).
        In practice a single ``Scaler('minmax')`` applied to all features
        works well as a starting point.

    Returns
    -------
    EncodedResult
        ``parameters`` = flat array of alternating Ry/Rz angles,
        length ``2 * ⌈d/2⌉`` (odd inputs are zero-padded for the
        missing Rz angle).
        ``metadata`` includes ``encoding``, ``n_qubits``, ``depth``,
        ``ry_angles``, ``rz_angles``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("TensorProductEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)
    n_qubits = math.ceil(d / 2)

    # Pad to even length if needed
    if d % 2 == 1:
        x_pad = np.append(x, 0.0)
    else:
        x_pad = x

    ry_angles = x_pad[0::2]  # even indices → Ry
    rz_angles = x_pad[1::2]  # odd  indices → Rz

    # Interleaved: [ry_0, rz_0, ry_1, rz_1, ...]
    parameters = np.empty(2 * n_qubits, dtype=float)
    parameters[0::2] = ry_angles
    parameters[1::2] = rz_angles

    return EncodedResult(
        parameters=parameters,
        metadata={
            "encoding": "tensor_product",
            "n_qubits": n_qubits,
            "depth": 2,
            "ry_angles": ry_angles.tolist(),
            "rz_angles": rz_angles.tolist(),
        },
    )

QAOAProblemEncoder

quprep.encode.qaoa_problem.QAOAProblemEncoder(p=1, gamma=np.pi / 4, beta=np.pi / 8, connectivity='linear')

Bases: BaseEncoder

QAOA-inspired problem encoder.

Encodes a feature vector as a one-layer (or multi-layer) QAOA circuit where the cost Hamiltonian is constructed from the feature values:

  • Local fields: \(h_i = \gamma \cdot x_i\)
  • Couplings: \(J_{ij} = \gamma \cdot x_i x_j\) (linear or full connectivity)

Parameters:

Name Type Description Default
p int

Number of QAOA layers (circuit repetitions). Default 1.

1
gamma float

Scaling factor for the cost unitary angles. Default π/4.

pi / 4
beta float

Mixing angle for the RX mixer. Default π/8.

pi / 8
connectivity ('linear', 'full')

"linear" (default) — only adjacent qubit pairs, NISQ-safe. "full" — all :math:\binom{d}{2} pairs, \(O(d^2)\) depth.

"linear"
Source code in quprep/encode/qaoa_problem.py
def __init__(
    self,
    p: int = 1,
    gamma: float = np.pi / 4,
    beta: float = np.pi / 8,
    connectivity: str = "linear",
):
    if p < 1:
        raise ValueError(f"p must be >= 1, got {p}.")
    if connectivity not in ("linear", "full"):
        raise ValueError(f"connectivity must be 'linear' or 'full', got '{connectivity}'.")
    self.p = p
    self.gamma = float(gamma)
    self.beta = float(beta)
    self.connectivity = connectivity

Functions

encode(x)

Encode a 1-D feature vector as a QAOA problem circuit.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector in \([-\pi, \pi]\). Use Scaler('minmax_pm_pi') for correct scaling.

required

Returns:

Type Description
EncodedResult

parameters = \([\gamma x_0, \ldots, \gamma x_{d-1}, \gamma x_0 x_1, \ldots]\) (local angles then coupling angles). metadata includes encoding, n_qubits, p, gamma, beta, connectivity, depth.

Source code in quprep/encode/qaoa_problem.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode a 1-D feature vector as a QAOA problem circuit.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector in $[-\pi, \pi]$.
        Use ``Scaler('minmax_pm_pi')`` for correct scaling.

    Returns
    -------
    EncodedResult
        ``parameters`` = $[\gamma x_0, \ldots, \gamma x_{d-1},
        \gamma x_0 x_1, \ldots]$ (local angles then coupling angles).
        ``metadata`` includes ``encoding``, ``n_qubits``, ``p``,
        ``gamma``, ``beta``, ``connectivity``, ``depth``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("QAOAProblemEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)

    # Local field angles: γ * x_i (one per qubit)
    local_angles = self.gamma * x

    # Coupling angles: γ * x_i * x_j for connected pairs
    if self.connectivity == "linear":
        pairs = [(i, i + 1) for i in range(d - 1)]
    else:  # full
        pairs = [(i, j) for i in range(d) for j in range(i + 1, d)]

    coupling_angles = np.array(
        [self.gamma * x[i] * x[j] for i, j in pairs],
        dtype=float,
    )

    parameters = np.concatenate([local_angles, coupling_angles])

    # Depth calculation
    if self.connectivity == "linear":
        depth = 1 + self.p * (d + 3 * len(pairs))
    else:
        # Full connectivity serialized: each pair ~3 gates deep
        depth = 1 + (1 + 3 * len(pairs) + 1) * self.p

    return EncodedResult(
        parameters=parameters,
        metadata={
            "encoding": "qaoa_problem",
            "n_qubits": d,
            "p": self.p,
            "gamma": self.gamma,
            "beta": self.beta,
            "connectivity": self.connectivity,
            "depth": depth,
            "n_pairs": len(pairs),
            "pairs": pairs,
            "local_angles": local_angles.tolist(),
            "coupling_angles": coupling_angles.tolist(),
        },
    )

DenseAngleEncoder

quprep.encode.dense_angle.DenseAngleEncoder(first_rotation='ry', second_rotation='rz')

Bases: BaseEncoder

Dense angle encoding — 2 features per qubit via two rotation gates.

Encodes feature pairs as two consecutive single-qubit rotations, using ⌈d/2⌉ qubits for d-dimensional input (half the count of :class:~quprep.encode.angle.AngleEncoder).

Parameters:

Name Type Description Default
first_rotation str

First rotation gate applied to each qubit. One of 'ry', 'rx', 'rz'. Default 'ry'.

'ry'
second_rotation str

Second rotation gate applied to each qubit. One of 'ry', 'rx', 'rz'. Default 'rz'.

'rz'
Source code in quprep/encode/dense_angle.py
def __init__(self, first_rotation: str = "ry", second_rotation: str = "rz"):
    if first_rotation not in _VALID_ROTATIONS:
        raise ValueError(
            f"first_rotation must be one of {sorted(_VALID_ROTATIONS)}, "
            f"got {first_rotation!r}"
        )
    if second_rotation not in _VALID_ROTATIONS:
        raise ValueError(
            f"second_rotation must be one of {sorted(_VALID_ROTATIONS)}, "
            f"got {second_rotation!r}"
        )
    self.first_rotation = first_rotation
    self.second_rotation = second_rotation

Functions

encode(x)

Encode a feature vector using two rotation gates per qubit.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Normalized feature vector. Recommended normalization: Scaler('minmax') to \([0, \pi]\) for Ry/Rx, or Scaler('minmax_pm_pi') to \([-\pi, \pi]\) for Rz.

required

Returns:

Type Description
EncodedResult

parameters is a flat array of interleaved angles [r1_0, r2_0, r1_1, r2_1, ...] of length 2 * ⌈d/2⌉. metadata includes encoding, first_rotation, second_rotation, n_qubits, depth, and n_features.

Source code in quprep/encode/dense_angle.py
def encode(self, x: np.ndarray) -> EncodedResult:
    r"""
    Encode a feature vector using two rotation gates per qubit.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Normalized feature vector. Recommended normalization:
        ``Scaler('minmax')`` to $[0, \pi]$ for Ry/Rx, or
        ``Scaler('minmax_pm_pi')`` to $[-\pi, \pi]$ for Rz.

    Returns
    -------
    EncodedResult
        ``parameters`` is a flat array of interleaved angles
        ``[r1_0, r2_0, r1_1, r2_1, ...]`` of length ``2 * ⌈d/2⌉``.
        ``metadata`` includes ``encoding``, ``first_rotation``,
        ``second_rotation``, ``n_qubits``, ``depth``, and ``n_features``.
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("DenseAngleEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)
    n_qubits = math.ceil(d / 2)

    if d % 2 == 1:
        x = np.append(x, 0.0)

    # Interleaved layout: [r1_0, r2_0, r1_1, r2_1, ...]
    parameters = np.empty(2 * n_qubits, dtype=float)
    parameters[0::2] = x[0::2]  # first-rotation angles
    parameters[1::2] = x[1::2]  # second-rotation angles

    return EncodedResult(
        parameters=parameters,
        metadata={
            "encoding": "dense_angle",
            "first_rotation": self.first_rotation,
            "second_rotation": self.second_rotation,
            "n_qubits": n_qubits,
            "depth": 2,
            "n_features": d,
        },
    )

DiscretizedEncoder

quprep.encode.discretized.DiscretizedEncoder(bits=4, min_val=0.0, max_val=1.0)

Bases: BaseEncoder

Discretized encoding — maps continuous features to binary basis states.

Each feature is quantized into bits binary digits using unsigned fixed-point representation. The resulting binary vector is QUBO-ready and can be passed directly to :func:quprep.qubo.to_qubo.

Parameters:

Name Type Description Default
bits int

Bits per feature. Default 4. Precision = (max_val − min_val) / (2^bits − 1).

4
min_val float

Lower bound of the expected feature range. Default 0.0.

0.0
max_val float

Upper bound of the expected feature range. Default 1.0.

1.0
Source code in quprep/encode/discretized.py
def __init__(self, bits: int = 4, min_val: float = 0.0, max_val: float = 1.0):
    if bits < 1:
        raise ValueError(f"bits must be >= 1, got {bits}")
    if min_val >= max_val:
        raise ValueError(
            f"min_val must be strictly less than max_val, "
            f"got min_val={min_val}, max_val={max_val}"
        )
    self.bits = bits
    self.min_val = float(min_val)
    self.max_val = float(max_val)

Functions

decode(bits_array)

Reconstruct continuous feature values from a binary parameter array.

Parameters:

Name Type Description Default
bits_array (ndarray, shape(d * bits))

Binary array as returned by encode().parameters.

required

Returns:

Type Description
(ndarray, shape(d))

Reconstructed feature values in [min_val, max_val].

Source code in quprep/encode/discretized.py
def decode(self, bits_array: np.ndarray) -> np.ndarray:
    """
    Reconstruct continuous feature values from a binary parameter array.

    Parameters
    ----------
    bits_array : np.ndarray, shape (d * bits,)
        Binary array as returned by ``encode().parameters``.

    Returns
    -------
    np.ndarray, shape (d,)
        Reconstructed feature values in ``[min_val, max_val]``.
    """
    bits_array = np.asarray(bits_array, dtype=float)
    n = len(bits_array)
    if n % self.bits != 0:
        raise ValueError(
            f"bits_array length {n} is not divisible by bits={self.bits}"
        )
    d = n // self.bits
    levels = (1 << self.bits) - 1
    result = np.zeros(d)
    for i in range(d):
        chunk = bits_array[i * self.bits: (i + 1) * self.bits]
        val = int(
            sum(int(b) * (1 << (self.bits - 1 - k)) for k, b in enumerate(chunk))
        )
        result[i] = self.min_val + (self.max_val - self.min_val) * val / levels
    return result

encode(x)

Quantize each feature to bits binary digits and encode as a basis state.

Parameters:

Name Type Description Default
x (ndarray, shape(d))

Feature vector. Values outside [min_val, max_val] are clipped.

required

Returns:

Type Description
EncodedResult

parameters: binary float array of shape (d * bits,), MSB-first per feature. metadata includes encoding, n_qubits, bits, min_val, max_val, precision, and qubo_variables (a dict mapping each feature index to its list of qubit indices).

Source code in quprep/encode/discretized.py
def encode(self, x: np.ndarray) -> EncodedResult:
    """
    Quantize each feature to ``bits`` binary digits and encode as a basis state.

    Parameters
    ----------
    x : np.ndarray, shape (d,)
        Feature vector. Values outside ``[min_val, max_val]`` are clipped.

    Returns
    -------
    EncodedResult
        ``parameters``: binary float array of shape ``(d * bits,)``, MSB-first
        per feature. ``metadata`` includes ``encoding``, ``n_qubits``, ``bits``,
        ``min_val``, ``max_val``, ``precision``, and ``qubo_variables`` (a dict
        mapping each feature index to its list of qubit indices).
    """
    x = np.asarray(x, dtype=float)
    if x.ndim != 1 or len(x) == 0:
        raise ValueError("DiscretizedEncoder.encode() expects a non-empty 1-D array.")

    d = len(x)
    n_qubits = d * self.bits
    levels = (1 << self.bits) - 1  # 2^bits − 1

    x_clipped = np.clip(x, self.min_val, self.max_val)
    normalized = (x_clipped - self.min_val) / (self.max_val - self.min_val)
    integers = np.round(normalized * levels).astype(int)

    bits_array = np.zeros(n_qubits, dtype=float)
    for i, val in enumerate(integers):
        for k in range(self.bits):
            bits_array[i * self.bits + k] = float((val >> (self.bits - 1 - k)) & 1)

    qubo_variables = {
        i: list(range(i * self.bits, (i + 1) * self.bits)) for i in range(d)
    }

    return EncodedResult(
        parameters=bits_array,
        metadata={
            "encoding": "discretized",
            "n_qubits": n_qubits,
            "bits": self.bits,
            "min_val": self.min_val,
            "max_val": self.max_val,
            "precision": (self.max_val - self.min_val) / levels,
            "depth": 1,
            "qubo_variables": qubo_variables,
        },
    )

Circuit parameter inspector

quprep.encode.inspector.inspect_encoding(result)

Inspect an EncodedResult and return structured gate parameters.

Parses the parameters and metadata fields of the result to build a gate-level description of the encoded circuit, suitable for programmatic inspection and debugging.

Parameters:

Name Type Description Default
result EncodedResult

Output of any BaseEncoder.encode() call.

required

Returns:

Type Description
EncodingParams

Structured object with raw angles, per-gate GateParam list, qubit count, depth, and full metadata.

Examples:

>>> from quprep.encode.angle import AngleEncoder
>>> from quprep.encode.inspector import inspect_encoding
>>> import numpy as np
>>> result = AngleEncoder().encode(np.array([0.5, 1.0, 1.5]))
>>> ep = inspect_encoding(result)
>>> ep.n_qubits
3
>>> ep.gates[0]
GateParam(Ry, q0, θ=0.5000)

quprep.encode.inspector.EncodingParams(encoding, n_qubits, depth, angles, gates=list(), metadata=dict()) dataclass

Structured representation of a quantum encoding circuit's parameters.

Attributes:

Name Type Description
encoding str

Encoding type (e.g., 'angle', 'iqp').

n_qubits int

Number of qubits used.

depth int or str or None

Circuit depth.

angles ndarray

Raw rotation angle parameters from the encoder.

gates list[GateParam]

Gate-level description of the circuit.

metadata dict

Full metadata dict from the EncodedResult.

Functions

summary()

Return a human-readable multi-line summary of all gates.

quprep.encode.inspector.GateParam(gate, qubit=None, angle=None, control=None, amplitudes=None) dataclass

A single gate operation with its parameters.