Pauli Correlation Encoding (PCE) converter.
This module provides :class:PCEConverter, which encodes the binary
variables of a combinatorial optimization problem into expectation values
of -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:
PCE has no single cost Hamiltonian. The classical cost is a polynomial in the per-variable expectations , so the abstract
get_cost_hamiltoniancontract from the QRAC base would be a misleading promise. Rather than carry a stub, we drop the inheritance entirely.PCE decodes from a list of Pauli expectations, not a measurement :class:
~qamomile.circuit.transpiler.job.SampleResult. The inheriteddecode(SampleResult)does not apply.
The actual enumeration of -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¶
| Function | Description |
|---|---|
binary_sampleset_to_ommx_samples | Convert a BINARY sample set into an OMMX Samples container. |
evaluate_original_instance | Evaluate transformed binary samples against the original problem. |
normalize_problem_input | Normalize a problem into evaluation, transformed, and spin forms. |
| Class | Description |
|---|---|
PCEConverter | Converter for Pauli Correlation Encoding (PCE). |
PCEEncoder | Pauli Correlation Encoding (PCE) encoder. |
SignRounder | Simple sign-based rounding. |
Functions¶
binary_sampleset_to_ommx_samples [source]¶
def binary_sampleset_to_ommx_samples(binary_sampleset: BinarySampleSet) -> ommx.v1.SamplesConvert 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:
| Name | Type | Description |
|---|---|---|
binary_sampleset | BinarySampleSet | A 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.Samples — sum(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:
ValueError— Ifbinary_sampleset.vartypeis notVarType.BINARY. SPIN sample sets must be converted first.ValueError— If the lengths ofbinary_sampleset.samplesandbinary_sampleset.num_occurrencesare inconsistent (and, when present,binary_sampleset.energyas well).
evaluate_original_instance [source]¶
def evaluate_original_instance(
original_instance: ommx.v1.Instance,
transformed_instance: ommx.v1.Instance,
samples: ommx.v1.Samples,
) -> ommx.v1.SampleSetEvaluate 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:
| Name | Type | Description |
|---|---|---|
original_instance | ommx.v1.Instance | Untouched problem copy used for objective and constraint evaluation. |
transformed_instance | ommx.v1.Instance | Post-to_hubo problem copy used to reconstruct original decision-variable values. |
samples | ommx.v1.Samples | Binary 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:
| Name | Type | Description |
|---|---|---|
instance | ommx.v1.Instance | BinaryModel | The 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_weight | float | None | Uniform 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_weights | dict[int, float] | None | Per-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:
TypeError— Ifinstanceis neither an :class:ommx.v1.Instancenor a :class:BinaryModel.ValueError— If penalty options are supplied for a :class:BinaryModel, which has no OMMX constraints to absorb.
Classes¶
PCEConverter [source]¶
class PCEConverterConverter for Pauli Correlation Encoding (PCE).
PCE compresses optimization variables into the expectation
values of -body Pauli correlators on qubits. By
default, is chosen as the smallest integer satisfying
; callers may instead pass an
explicit num_qubits (at least the minimum) to host the encoding
on a larger register. Each variable is associated with a
distinct correlator , and the decoded spin value is
.
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
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,
) -> NoneInitialize 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:
| Name | Type | Description |
|---|---|---|
instance | ommx.v1.Instance | BinaryModel | The combinatorial optimization problem. ommx.v1.Instance inputs are converted via to_qubo(); BinaryModel inputs retain their declared vartype as the target output vartype. |
correlator_order | int | Compression rate — the body (weight) of each Pauli correlator (denoted in the math). Must be a positive integer. |
num_qubits | int | None | Number of qubits to host the encoding. Defaults to None, meaning the converter uses :meth:min_num_qubits — the smallest 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_weight | float | None | Uniform OMMX constraint penalty. None delegates weight selection to OMMX. |
penalty_weights | dict[int, float] | None | Optional per-constraint penalty weights keyed by constraint ID. |
Raises:
TypeError— Ifinstanceis neither anommx.v1.Instancenor a :class:BinaryModel.ValueError— Ifcorrelator_orderis not a positive integer, ifnum_qubitsis below the minimum required for the problem at the givencorrelator_order, or if the problem contains higher-order (HUBO) terms (raised by :class:PCEEncoder).
Attributes¶
correlator_order: int Compression rate (body weight) of the PCE encoding.encoder: PCEEncoder The :class:PCEEncoderinstance built at construction time.instance: ommx.v1.Instance | Nonenum_qubits: int Number of physical qubits used by the PCE encoding.original_instance: ommx.v1.Instance | Noneoriginal_vartype: VarTypepauli_encoding: dict[int, qm_o.Hamiltonian] Mapping from variable index to its Pauli correlator Hamiltonian.spin_model: BinaryModel
Methods¶
decode¶
def decode(self, expectations: list[float]) -> BinarySampleSet | ommx.v1.SampleSetDecode Pauli expectation values into a sample set.
Uses :class:SignRounder to convert each expectation
into a spin value
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:
Built from an :class:
ommx.v1.Instance— returns an :class:ommx.v1.SampleSetevaluated against the stored OMMX instance, so feasibility, original objective, and per-constraint violations are available through OMMX’s own API (.summary,.best_feasible,.feasible,.objectives, ...).Built from a :class:
BinaryModel— returns a single-sample :class:BinarySampleSet(num_occurrences=[1]) labelled with the computed energy, expressed in the original vartype (BINARYorSPIN) of the input problem.
Parameters:
| Name | Type | Description |
|---|---|---|
expectations | list[float] | Expectation values of the encoding Pauli correlators, in variable-index order. Each value is expected to lie in , 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:
ValueError— If the length ofexpectationsdoes not match the number of variables in the stored spin model.
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.objectiveget_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 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) -> intReturn 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:
| Name | Type | Description |
|---|---|---|
num_vars | int | Number of variables to encode. Must be non-negative. |
correlator_order | int | Compression rate ( in the math). Must be a positive integer. |
Returns:
int — The minimum number of qubits required.
Raises:
ValueError— Ifcorrelator_orderis not a positive integer ornum_varsis negative.
Example:
>>> PCEConverter.min_num_qubits(num_vars=10, correlator_order=2)
3PCEEncoder [source]¶
class PCEEncoderPauli Correlation Encoding (PCE) encoder.
Builds the deterministic mapping from each binary variable to a
distinct -body Pauli correlator on qubits. By
default, is the smallest integer satisfying
; 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 qubit
combinations in lexicographic order, then over the
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:
| Name | Type | Description |
|---|---|---|
spin_model | BinaryModel | The problem in SPIN vartype. Must not contain higher-order (HUBO) terms. |
correlator_order | int | Compression rate — the body (weight) of each Pauli correlator (denoted in the math). Must be a positive integer. |
num_qubits | int | None | Number of qubits to host the encoding. Defaults to None, meaning the encoder uses :meth:min_num_qubits — the smallest satisfying the capacity inequality. When given, must be at least that minimum. |
Raises:
ValueError— Ifcorrelator_orderis not a positive integer, ifspin_modelis not in SPIN vartype, ifspin_modelcontains HUBO terms, or ifnum_qubitsis below the minimum required for the problem at the givencorrelator_order.
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 stringConstructor¶
def __init__(
self,
spin_model: BinaryModel,
correlator_order: int,
num_qubits: int | None = None,
) -> NoneAttributes¶
correlator_order: intnum_qubits: int Number of qubits used by the PCE encoding.pauli_encoding: dict[int, qm_o.Hamiltonian] Mapping from variable index to its Pauli correlator Hamiltonian.spin_model: BinaryModel
Methods¶
min_num_qubits¶
@staticmethod
def min_num_qubits(num_vars: int, correlator_order: int) -> intReturn 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 -body correlator
requires at least k qubits (C(n, k) = 0 for n < k).
When num_vars == 0 the method returns k directly.
Parameters:
| Name | Type | Description |
|---|---|---|
num_vars | int | Number of variables to encode. Must be non-negative. |
correlator_order | int | Compression rate (number of non-identity Paulis per correlator; in the math). Must be a positive integer. |
Returns:
int — The minimum number of qubits n (with
int — n >= correlator_order) required to host num_vars
int — distinct -body Pauli correlators.
Raises:
ValueError— Ifcorrelator_orderis not a positive integer ornum_varsis negative.
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)
3SignRounder [source]¶
class SignRounderSimple sign-based rounding.
Rounds Pauli expectation values to spin values using the sign function:
<P_i> >= 0 → s_i = +1
<P_i> < 0 → s_i = -1
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:
| Name | Type | Description |
|---|---|---|
expectations | list[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