Background / Motivation
graphqomb currently provides two measurement-basis representations:
PlannerMeasBasis(plane, angle: float)
AxisMeasBasis(axis, sign) (exact Pauli ±X/±Y/±Z)
Internally, most code treats bases as planner-like (uses plane/angle), and Pauli detection depends on floating-point closeness checks (e.g. is_close_angle, is_clifford_angle). This reduces the main advantage of AxisMeasBasis: representing Pauli measurements exactly without numerical tolerance issues.
Notable examples:
- Float-based Pauli detection:
determine_pauli_axis in graphqomb/common.py
- Local Clifford update is numeric and always returns
PlannerMeasBasis: update_lc_basis in graphqomb/euler.py
- GraphState Clifford application uses
update_lc_basis: apply_local_clifford in graphqomb/graphstate.py
- Downstream components rely on Pauli detection (
stim_compiler, pauli_simplification, PauliFrame caching)
Goal
When a measurement basis is (or becomes) a Pauli basis, represent and preserve it as AxisMeasBasis end-to-end, avoiding float-based angle comparisons in that regime.
Proposal
-
Add an explicit “Pauli basis query” API to MeasBasis
- e.g.
pauli_basis() -> AxisMeasBasis | None
AxisMeasBasis.pauli_basis() returns self
PlannerMeasBasis.pauli_basis() returns an AxisMeasBasis only if it is Pauli (tolerance only at this boundary is OK)
-
Prefer discrete Pauli info in consumers
- Update
determine_pauli_axis to short-circuit for AxisMeasBasis (or to use basis.pauli_basis() first)
- Update call sites to rely on this path where possible (
stim_compiler, feedforward, visualizer, PauliFrame caching)
-
Implement an algebraic update path for Pauli bases under Local Cliffords
- Extend
update_lc_basis(lc, basis) so that if basis is Pauli (AxisMeasBasis), it returns an AxisMeasBasis computed by a discrete Clifford action table (permutation/sign on {X,Y,Z})
- Keep existing numeric fallback for non-Pauli bases
- (Optional follow-up) represent
LocalClifford itself in a discrete form (24-element group id or angles mod π/2) to eliminate float drift entirely for Clifford-only workflows
-
“Snap to Axis” normalization at key boundaries
- After numeric updates that yield a Pauli basis, convert to
AxisMeasBasis and store that instead of PlannerMeasBasis
- Apply this in
GraphState.apply_local_clifford and Local Clifford expansion paths (_expand_input_local_cliffords, _expand_output_local_cliffords) when created angles correspond to Pauli axes
Acceptance Criteria
determine_pauli_axis(AxisMeasBasis(...)) returns the correct axis without float comparisons.
update_lc_basis(LocalClifford, AxisMeasBasis) returns AxisMeasBasis and matches existing numeric behavior for all Pauli bases and all local Cliffords.
- Pauli measurements do not “drift” into
PlannerMeasBasis after repeated Clifford updates/expansions.
stim_compile does not fail due to missed Pauli detection caused by float error.
- Tests cover:
- Local Clifford action on Pauli bases (all 24 Cliffords × 6 Pauli eigenbases, or equivalent coverage)
- Regression for “snap to axis” behavior across GraphState transformations
Notes / Risks
- Clarify the semantics of
AxisMeasBasis.sign vs flip() and downstream classical post-processing, to ensure normalization does not change meaning.
- Any tolerance use should be confined to recognizing a
PlannerMeasBasis as Pauli; once converted, operations should remain discrete.
Background / Motivation
graphqombcurrently provides two measurement-basis representations:PlannerMeasBasis(plane, angle: float)AxisMeasBasis(axis, sign)(exact Pauli ±X/±Y/±Z)Internally, most code treats bases as planner-like (uses
plane/angle), and Pauli detection depends on floating-point closeness checks (e.g.is_close_angle,is_clifford_angle). This reduces the main advantage ofAxisMeasBasis: representing Pauli measurements exactly without numerical tolerance issues.Notable examples:
determine_pauli_axisingraphqomb/common.pyPlannerMeasBasis:update_lc_basisingraphqomb/euler.pyupdate_lc_basis:apply_local_cliffordingraphqomb/graphstate.pystim_compiler,pauli_simplification,PauliFramecaching)Goal
When a measurement basis is (or becomes) a Pauli basis, represent and preserve it as
AxisMeasBasisend-to-end, avoiding float-based angle comparisons in that regime.Proposal
Add an explicit “Pauli basis query” API to
MeasBasispauli_basis() -> AxisMeasBasis | NoneAxisMeasBasis.pauli_basis()returnsselfPlannerMeasBasis.pauli_basis()returns anAxisMeasBasisonly if it is Pauli (tolerance only at this boundary is OK)Prefer discrete Pauli info in consumers
determine_pauli_axisto short-circuit forAxisMeasBasis(or to usebasis.pauli_basis()first)stim_compiler,feedforward,visualizer,PauliFramecaching)Implement an algebraic update path for Pauli bases under Local Cliffords
update_lc_basis(lc, basis)so that ifbasisis Pauli (AxisMeasBasis), it returns anAxisMeasBasiscomputed by a discrete Clifford action table (permutation/sign on {X,Y,Z})LocalClifforditself in a discrete form (24-element group id or angles mod π/2) to eliminate float drift entirely for Clifford-only workflows“Snap to Axis” normalization at key boundaries
AxisMeasBasisand store that instead ofPlannerMeasBasisGraphState.apply_local_cliffordand Local Clifford expansion paths (_expand_input_local_cliffords,_expand_output_local_cliffords) when created angles correspond to Pauli axesAcceptance Criteria
determine_pauli_axis(AxisMeasBasis(...))returns the correct axis without float comparisons.update_lc_basis(LocalClifford, AxisMeasBasis)returnsAxisMeasBasisand matches existing numeric behavior for all Pauli bases and all local Cliffords.PlannerMeasBasisafter repeated Clifford updates/expansions.stim_compiledoes not fail due to missed Pauli detection caused by float error.Notes / Risks
AxisMeasBasis.signvsflip()and downstream classical post-processing, to ensure normalization does not change meaning.PlannerMeasBasisas Pauli; once converted, operations should remain discrete.