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.
normalize_problem_inputNormalize a problem input into (stored_instance, original_vartype, spin_model).
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:


normalize_problem_input [source]

def normalize_problem_input(
    instance: ommx.v1.Instance | BinaryModel,
) -> tuple[ommx.v1.Instance | None, VarType, BinaryModel]

Normalize a problem input into (stored_instance, original_vartype, spin_model).

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. The deep copy retains original-constraint metadata internally, so downstream evaluate_samples reports feasibility against the user’s original constraints, and slack bits added by to_hubo are reconstructed back into the original decision variables (e.g., integers rebuilt from log-encoded slack bits) by evaluate_samples automatically.

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.

Returns:

ommx.v1.Instance | None — tuple[ommx.v1.Instance | None, VarType, BinaryModel]: A triple VarType(stored_instance, original_vartype, spin_model). BinaryModel — * stored_instance: the deep-copied Instance for OMMX inputs (post to_hubo), or None for BinaryModel inputs. tuple[ommx.v1.Instance | None, VarType, BinaryModel] — * original_vartype: the declared vartype of the input — used by converters’ decode paths to return results in the caller’s preferred representation. tuple[ommx.v1.Instance | None, VarType, BinaryModel] — * spin_model: the problem rewritten in SPIN form, which is the natural domain for sign rounding and Pauli encoding.

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,
) -> 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.

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