Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

qamomile.optimization.pce

Pauli Correlation Encoding (PCE) converter.

This module provides :class:PCEConverter, which encodes the binary variables of a combinatorial optimization problem into expectation values of kk-body Pauli correlators acting on a small number of qubits.

PCE mirrors the QRAC family’s API shape — num_qubits, get_encoded_pauli_list, encoder, and a sign-rounding decoder — but :class:PCEConverter does not inherit from :class:~qamomile.optimization.qrao.base_converter.QRACConverterBase (or :class:~qamomile.optimization.converter.MathematicalProblemConverter). The reason is structural:

The actual enumeration of kk-body Pauli correlators lives on the standalone :class:PCEEncoder, mirroring how QRAC factors its encoders out of the converter so users can construct and inspect the encoding without going through the converter.

Overview

FunctionDescription
binary_sampleset_to_ommx_samplesConvert a BINARY sample set into an OMMX Samples container.
evaluate_original_instanceEvaluate transformed binary samples against the original problem.
normalize_problem_inputNormalize a problem into evaluation, transformed, and spin forms.
ClassDescription
PCEConverterConverter for Pauli Correlation Encoding (PCE).
PCEEncoderPauli Correlation Encoding (PCE) encoder.
SignRounderSimple sign-based rounding.

Functions

binary_sampleset_to_ommx_samples [source]

def binary_sampleset_to_ommx_samples(binary_sampleset: BinarySampleSet) -> ommx.v1.Samples

Convert a BINARY sample set into an OMMX Samples container.

Each unique sample state is appended once with a list of sample IDs of length num_occurrences, so OMMX-side aggregation reflects the original shot counts without duplicating the underlying state. States with num_occurrences == 0 are skipped.

This is the canonical bridge from Qamomile’s :class:BinarySampleSet to OMMX’s :class:ommx.v1.Samples. It is the helper that :meth:MathematicalProblemConverter.decode and :meth:~qamomile.optimization.pce.PCEConverter.decode use to feed samples into :meth:ommx.v1.Instance.evaluate_samples for feasibility and original-objective evaluation.

Parameters:

NameTypeDescription
binary_samplesetBinarySampleSetA sample set with vartype=VarType.BINARY. SPIN sample sets must be converted to BINARY first because OMMX expects 0/1 decision-variable values.

Returns:

ommx.v1.Samples — ommx.v1.Samples: An OMMX Samples object with ommx.v1.Samplessum(num_occurrences) sample IDs, where IDs sharing the same ommx.v1.Samples — state are grouped together. The returned object is empty when the ommx.v1.Samples — input has no samples.

Raises:


evaluate_original_instance [source]

def evaluate_original_instance(
    original_instance: ommx.v1.Instance,
    transformed_instance: ommx.v1.Instance,
    samples: ommx.v1.Samples,
) -> ommx.v1.SampleSet

Evaluate transformed binary samples against the original problem.

The transformed instance first reconstructs substituted integer and slack variables. Only the caller’s original decision-variable IDs are then copied into a new sample container and evaluated against the untouched original instance. This keeps feasibility and objective values free of penalty terms while retaining OMMX’s binary-substitution semantics.

Parameters:

NameTypeDescription
original_instanceommx.v1.InstanceUntouched problem copy used for objective and constraint evaluation.
transformed_instanceommx.v1.InstancePost-to_hubo problem copy used to reconstruct original decision-variable values.
samplesommx.v1.SamplesBinary samples in the transformed problem’s variable space.

Returns:

ommx.v1.SampleSet — ommx.v1.SampleSet: Samples evaluated against the original objective ommx.v1.SampleSet — and constraints.


normalize_problem_input [source]

def normalize_problem_input(
    instance: ommx.v1.Instance | BinaryModel,
    *,
    uniform_penalty_weight: float | None = None,
    penalty_weights: dict[int, float] | None = None,
) -> tuple[ommx.v1.Instance | None, ommx.v1.Instance | None, VarType, BinaryModel]

Normalize a problem into evaluation, transformed, and spin forms.

Shared canonical entry point used by every converter that consumes a combinatorial optimization problem expressed either as an OMMX :class:ommx.v1.Instance or a Qamomile :class:BinaryModel. The :class:MathematicalProblemConverter base class and :class:~qamomile.optimization.pce.PCEConverter (which deliberately does not inherit from that base) both delegate to this helper so the OMMX deep-copy / to_hubo semantics live in exactly one place.

For OMMX inputs, the instance is deep-copied via a bytes round-trip before to_hubo is called: to_hubo mutates the instance it is called on (it appends slack decision variables for non-binary vars and absorbs constraints into the objective via the penalty method). Mutating the caller’s instance silently is surprising; copying first leaves the caller’s view untouched. Separate original and transformed copies let downstream decoding reconstruct substituted variables with the transformed instance, then evaluate the untouched objective and constraints with the original instance.

to_hubo is used instead of to_qubo so that both purely quadratic (QUBO) and higher-order (HUBO) instances are handled uniformly. For quadratic-only instances the two paths produce equivalent optimization problems (same coefficients and vartype).

Parameters:

NameTypeDescription
instanceommx.v1.Instance | BinaryModelThe combinatorial optimization problem. ommx.v1.Instance inputs are deep-copied and converted via to_hubo(); BinaryModel inputs retain their declared vartype as the target output vartype.
uniform_penalty_weightfloat | NoneUniform weight passed to :meth:ommx.v1.Instance.to_hubo. None delegates weight selection to OMMX. Ignored for :class:BinaryModel inputs only when left at its default.
penalty_weightsdict[int, float] | NonePer-constraint penalty weights passed to :meth:ommx.v1.Instance.to_hubo. Keys are constraint IDs. Defaults to no explicit overrides.

Returns:

ommx.v1.Instance | None — tuple[ommx.v1.Instance | None, ommx.v1.Instance | None, VarType, ommx.v1.Instance | None — BinaryModel]: A tuple (original_instance, transformed_instance, `VarType` — original_vartype, spin_model). The two instances are None for a BinaryModel — class:BinaryModel input. The original copy is reserved for tuple[ommx.v1.Instance | None, ommx.v1.Instance | None, VarType, BinaryModel] — unpenalized result evaluation; the transformed copy owns the binary tuple[ommx.v1.Instance | None, ommx.v1.Instance | None, VarType, BinaryModel] — substitutions and penalty metadata needed to reconstruct samples.

Raises:

Classes

PCEConverter [source]

class PCEConverter

Converter for Pauli Correlation Encoding (PCE).

PCE compresses NN optimization variables into the expectation values of kk-body Pauli correlators on nn qubits. By default, nn is chosen as the smallest integer satisfying (nk)3kN\binom{n}{k} \cdot 3^k \ge N; callers may instead pass an explicit num_qubits (at least the minimum) to host the encoding on a larger register. Each variable ii is associated with a distinct correlator PiP_i, and the decoded spin value is si=sgnPis_i = \operatorname{sgn}\langle P_i \rangle.

PCE does not prescribe a specific ansatz — users build their own variational circuit and transpile it directly with their backend’s :class:~qamomile.circuit.transpiler.transpiler.Transpiler (this converter does not wrap that step). The classical cost that the outer optimizer should minimize is

C(P)=ihiPi+i<jJijPiPj+C(\langle P \rangle) = \sum_i h_i \langle P_i \rangle + \sum_{i<j} J_{ij} \langle P_i \rangle \langle P_j \rangle + \ldots

evaluated from the per-variable expectation values produced by the user’s ansatz. Use :meth:get_encoded_pauli_list to obtain the observables to feed into the backend’s estimator.

The encoding is built once at construction (parametrized by correlator_order) and cached on the encoder. To re-encode with a different correlator_order, construct a new :class:PCEConverter.

Although the public API mirrors :class:~qamomile.optimization.qrao.base_converter.QRACConverterBase (num_qubits, encoder, get_encoded_pauli_list), this class deliberately does not inherit from it. PCE has no single cost Hamiltonian, so the QRAC base’s abstract get_cost_hamiltonian contract would be a misleading promise. PCE also decodes from expectation values rather than a sample set, which is incompatible with MathematicalProblemConverter.decode.

The polymorphic return type of :meth:decode, however, is aligned with the rest of the converter family: an OMMX-backed converter returns an :class:ommx.v1.SampleSet (so feasibility, original objective, and per-constraint diagnostics are available through OMMX’s own API), while a :class:BinaryModel-backed converter returns a :class:BinarySampleSet in the model’s original vartype.

Example:

>>> converter = PCEConverter(instance, correlator_order=2)
>>> observables = converter.get_encoded_pauli_list()
>>> # Transpile the user's ansatz directly with the backend
>>> # transpiler (PCEConverter does not wrap this step):
>>> executable = transpiler.transpile(
...     my_ansatz,
...     bindings={"P": observables[0]},
...     parameters=["thetas"],
... )
>>> # ... evaluate <P_i> for each i via executable ...
>>> sampleset = converter.decode([0.7, -0.2, 0.9, -0.4])

Constructor

def __init__(
    self,
    instance: ommx.v1.Instance | BinaryModel,
    correlator_order: int,
    num_qubits: int | None = None,
    *,
    uniform_penalty_weight: float | None = None,
    penalty_weights: dict[int, float] | None = None,
) -> None

Initialize the converter from an OMMX instance or BinaryModel.

The problem is internally converted to SPIN vartype, while the original vartype is remembered so that :meth:decode can return results in the user’s preferred representation. The PCE encoding is built immediately so :attr:num_qubits and :meth:get_encoded_pauli_list are usable right after construction.

Parameters:

NameTypeDescription
instanceommx.v1.Instance | BinaryModelThe combinatorial optimization problem. ommx.v1.Instance inputs are converted via to_qubo(); BinaryModel inputs retain their declared vartype as the target output vartype.
correlator_orderintCompression rate — the body (weight) of each Pauli correlator (denoted kk in the math). Must be a positive integer.
num_qubitsint | NoneNumber of qubits to host the encoding. Defaults to None, meaning the converter uses :meth:min_num_qubits — the smallest nn large enough to host every variable’s correlator. When given, must be at least that minimum; supplying a larger value is supported for callers that want to match a specific hardware register width.
uniform_penalty_weightfloat | NoneUniform OMMX constraint penalty. None delegates weight selection to OMMX.
penalty_weightsdict[int, float] | NoneOptional per-constraint penalty weights keyed by constraint ID.

Raises:

Attributes

Methods

decode
def decode(self, expectations: list[float]) -> BinarySampleSet | ommx.v1.SampleSet

Decode Pauli expectation values into a sample set.

Uses :class:SignRounder to convert each expectation Pi\langle P_i \rangle into a spin value si{+1,1}s_i \in \{+1, -1\} and computes the energy of the resulting assignment from the stored SPIN model. The return type tracks the input that built this converter — matching the polymorphic behaviour of :meth:MathematicalProblemConverter.decode:

Parameters:

NameTypeDescription
expectationslist[float]Expectation values of the encoding Pauli correlators, in variable-index order. Each value is expected to lie in [1,1][-1, 1], though the sign rounder does not enforce that range — only the sign is consulted.

Returns:

BinarySampleSet | ommx.v1.SampleSet — BinarySampleSet | ommx.v1.SampleSet: see method description.

Raises:

Example:

>>> # A BinaryModel-backed converter with 3 SPIN variables.
>>> sampleset = converter.decode([0.4, -0.1, 0.8])
>>> sampleset.samples
[{0: 1, 1: -1, 2: 1}]
>>> # An OMMX-backed converter returns an ommx SampleSet.
>>> sample_set = ommx_converter.decode([0.4, -0.1, 0.8])
>>> sample_set.best_feasible.objective
get_encoded_pauli_list
def get_encoded_pauli_list(self) -> list[qm_o.Hamiltonian]

Return the per-variable Pauli correlator observables.

Returns the encoding as a list indexed by variable, suitable for passing to a backend estimator to obtain Pi\langle P_i \rangle for each variable.

Returns:

list[qm_o.Hamiltonian] — list[qm_o.Hamiltonian]: One Hamiltonian per variable, in list[qm_o.Hamiltonian] — variable-index order. Each Hamiltonian contains a single Pauli list[qm_o.Hamiltonian] — string with coefficient 1.0 on self.num_qubits qubits.

min_num_qubits
@staticmethod
def min_num_qubits(num_vars: int, correlator_order: int) -> int

Return the smallest n with C(n, k) * 3**k >= num_vars.

Here k denotes correlator_order. Thin forwarder to :meth:PCEEncoder.min_num_qubits so callers can size a problem before constructing the converter.

Parameters:

NameTypeDescription
num_varsintNumber of variables to encode. Must be non-negative.
correlator_orderintCompression rate (kk in the math). Must be a positive integer.

Returns:

int — The minimum number of qubits required.

Raises:

Example:

>>> PCEConverter.min_num_qubits(num_vars=10, correlator_order=2)
3

PCEEncoder [source]

class PCEEncoder

Pauli Correlation Encoding (PCE) encoder.

Builds the deterministic mapping from each binary variable to a distinct kk-body Pauli correlator on nn qubits. By default, nn is the smallest integer satisfying (nk)3knum_vars\binom{n}{k} \cdot 3^k \ge \text{num\_vars}; callers may instead pass an explicit num_qubits to use a larger register (e.g., to match a hardware topology), provided it is at least the computed minimum.

The enumeration first iterates over all (nk)\binom{n}{k} qubit combinations in lexicographic order, then over the 3k3^k assignments of X, Y, Z on those qubits. The first spin_model.num_bits correlators are assigned to variables in index order; the remaining correlators are unused. Because the lexicographic enumeration depends on num_qubits, the per-variable correlator assignment may differ when num_qubits is set above the minimum.

Parameters:

NameTypeDescription
spin_modelBinaryModelThe problem in SPIN vartype. Must not contain higher-order (HUBO) terms.
correlator_orderintCompression rate — the body (weight) of each Pauli correlator (denoted kk in the math). Must be a positive integer.
num_qubitsint | NoneNumber of qubits to host the encoding. Defaults to None, meaning the encoder uses :meth:min_num_qubits — the smallest nn satisfying the capacity inequality. When given, must be at least that minimum.

Raises:

Example:

>>> from qamomile.optimization.binary_model import BinaryModel, VarType
>>> spin = BinaryModel(...).change_vartype(VarType.SPIN)
>>> encoder = PCEEncoder(spin, correlator_order=2)
>>> encoder.num_qubits
4
>>> # Override to use a larger register:
>>> wide_encoder = PCEEncoder(spin, correlator_order=2, num_qubits=6)
>>> wide_encoder.num_qubits
6
>>> encoder.pauli_encoding[0]  # Hamiltonian with one Pauli string

Constructor

def __init__(
    self,
    spin_model: BinaryModel,
    correlator_order: int,
    num_qubits: int | None = None,
) -> None

Attributes

Methods

min_num_qubits
@staticmethod
def min_num_qubits(num_vars: int, correlator_order: int) -> int

Return the smallest n >= k with C(n, k) * 3**k >= num_vars.

Here k denotes the correlator_order argument. The result is always at least k because a kk-body correlator requires at least k qubits (C(n, k) = 0 for n < k). When num_vars == 0 the method returns k directly.

Parameters:

NameTypeDescription
num_varsintNumber of variables to encode. Must be non-negative.
correlator_orderintCompression rate (number of non-identity Paulis per correlator; kk in the math). Must be a positive integer.

Returns:

int — The minimum number of qubits n (with intn >= correlator_order) required to host num_vars int — distinct kk-body Pauli correlators.

Raises:

Example:

>>> PCEEncoder.min_num_qubits(num_vars=10, correlator_order=2)
3
>>> # Lower bound: result is always >= correlator_order
>>> PCEEncoder.min_num_qubits(num_vars=0, correlator_order=3)
3

SignRounder [source]

class SignRounder

Simple sign-based rounding.

Rounds Pauli expectation values to spin values using the sign function:

Example:

>>> rounder = SignRounder()
>>> expectations = [0.8, -0.3, 0.1]  # Pauli expectation values
>>> spins = rounder.round(expectations)
>>> print(spins)  # [1, -1, 1]

Methods

round
def round(self, expectations: list[float]) -> list[int]

Round Pauli expectations to spin values.

Parameters:

NameTypeDescription
expectationslist[float]List of Pauli expectation values for each variable. Each value should be in range [-1, 1].

Returns:

list[int] — List of spin values (+1 or -1) for each variable