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. |
normalize_problem_input | Normalize a problem input into (stored_instance, original_vartype, spin_model). |
| 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).
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:
| 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. |
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:
TypeError— Ifinstanceis neither an :class:ommx.v1.Instancenor a :class:BinaryModel.
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,
) -> 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. |
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_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