feat(tableau): peek observable expectation — string observables + loss handling#187
Closed
rafaelha wants to merge 1 commit into
Closed
feat(tableau): peek observable expectation — string observables + loss handling#187rafaelha wants to merge 1 commit into
rafaelha wants to merge 1 commit into
Conversation
…ss handling
Add a non-destructive observable-expectation peek to GeneralizedTableau,
end to end:
- crates/ppvm-tableau/src/observable.rs: parse Pauli-observable strings —
Stim-style sparse products ("X0*X3*Z5*Y7", optional leading +/-) and
dense "IXYZ…" / "_"-for-identity forms — with typed errors for
malformed, out-of-range, or repeated-qubit observables.
- GeneralizedTableau::pauli_expectation((qubit, Pauli) factors + sign):
returns None when the observable's support touches a lost qubit,
otherwise delegates to the existing expectation() machinery (#172).
- GeneralizedTableau::peek_observable_expectation(&str): string front-end.
- Python: peek_observable_expectation returning an ExpectationResult
(frozen dataclass with a LOST sentinel and __float__), plus stubs.
Rebased over #172/#176, which added the core expectation/trace machinery
independently: this commit now reuses their compute_decomposition_word
and expectation() instead of carrying its own decomposition path, and
contributes the string parsing, loss semantics, and Python result type
on top.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a non-destructive “peek” API for Pauli observable expectations on GeneralizedTableau, including a Rust-side observable string parser (dense and sparse Stim-style syntax), loss-aware semantics (None/LOST when support touches a lost qubit), and Python bindings with an ExpectationResult wrapper plus tests.
Changes:
- Add
crates/ppvm-tableau::observableto parse signed dense/sparse Pauli observable strings into typed factors. - Add
GeneralizedTableau::{pauli_expectation, peek_observable_expectation}(Rust) andGeneralizedTableau.peek_observable_expectation()+ExpectationResult(Python), including loss handling. - Add Rust + Python test coverage for parsing, correctness vs known examples, loss behavior, and non-disturbance.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| ppvm-python/test/generalized_tableau/test_peek_expectation.py | Adds Python tests for peek expectation behavior, parsing forms, loss semantics, and Stim cross-validation. |
| ppvm-python/src/ppvm/generalized_tableau.py | Adds ExpectationResult dataclass and GeneralizedTableau.peek_observable_expectation() wrapper. |
| ppvm-python/src/ppvm/_core.pyi | Extends native interface typing to include peek_observable_expectation. |
| ppvm-python/src/ppvm/init.py | Re-exports ExpectationResult from the public Python package. |
| crates/ppvm-tableau/src/observable.rs | New Rust module implementing dense/sparse observable parsing + tests. |
| crates/ppvm-tableau/src/measure.rs | Adds Rust-side expectation peek APIs and related tests. |
| crates/ppvm-tableau/src/lib.rs | Registers the new observable module and re-exports ObservableParseError in the prelude. |
| crates/ppvm-tableau/src/data.rs | Makes PhasedPauliWordNoHash available to the crate for reuse by peek/expectation code. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+35
to
+56
| impl std::fmt::Display for ObservableParseError { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| match self { | ||
| ObservableParseError::BadToken(t) => { | ||
| write!(f, "malformed Pauli factor {t:?}; expected e.g. \"X0\"") | ||
| } | ||
| ObservableParseError::QubitOutOfRange { qubit, n_qubits } => { | ||
| write!( | ||
| f, | ||
| "qubit {qubit} out of range for a {n_qubits}-qubit system" | ||
| ) | ||
| } | ||
| ObservableParseError::RepeatedQubit(q) => { | ||
| write!(f, "qubit {q} appears in more than one factor") | ||
| } | ||
| ObservableParseError::DenseLengthMismatch { got, n_qubits } => write!( | ||
| f, | ||
| "dense observable has length {got}, expected {n_qubits} (one Pauli per qubit)" | ||
| ), | ||
| } | ||
| } | ||
| } |
Comment on lines
+141
to
+146
| if rest.chars().count() != n_qubits { | ||
| return Err(ObservableParseError::DenseLengthMismatch { | ||
| got: rest.chars().count(), | ||
| n_qubits, | ||
| }); | ||
| } |
Comment on lines
+606
to
+618
| pub fn pauli_expectation(&self, factors: &[(usize, Pauli)], negate: bool) -> Option<f64> { | ||
| for &(qubit, pauli) in factors { | ||
| if pauli != Pauli::I && self.is_lost[qubit] { | ||
| return None; | ||
| } | ||
| } | ||
| let mut word = PhasedPauliWordNoHash::<T::Storage, T::BuildHasher>::new(self.n_qubits()); | ||
| for &(qubit, pauli) in factors { | ||
| word.set(qubit, pauli); | ||
| } | ||
| let value = self.expectation(&word.word); | ||
| Some(if negate { -value } else { value }) | ||
| } |
|
Collaborator
Author
|
This is actually handled sufficiently well by the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
Adds a non-destructive observable-expectation peek to
GeneralizedTableau, layered on top of the expectation machinery from #172/#176:crates/ppvm-tableau/src/observable.rs(new): parsing of Pauli-observable strings. Two notations, each with an optional leading+/-sign:MPP-style products:"X0*X3*Z5*Y7","-Z0*Y1";"IXYZ…"of lengthn_qubits, with_accepted as an alias forIso StimPauliStringtext ("+__X_Y_Z") parses directly.Malformed, out-of-range, or repeated-qubit observables produce a typed
ObservableParseError.GeneralizedTableau::pauli_expectation(factors, negate): returnsNonewhen any qubit in the observable's support has been lost; otherwise builds the word and delegates to the existingexpectation()(feat(tableau): add Pauli expectation/trace and reset_all #172). No state is disturbed.GeneralizedTableau::peek_observable_expectation(&str): string front-end combining the two.Python:
GeneralizedTableau.peek_observable_expectation(observable)returns anExpectationResult— a frozen dataclass with aLOSTsentinel,is_lost, and__float__(which raises onLOST). Complements the plainexpectation()/trace()from Add expectation and trace to tableau python bindings #176 with sparse/signed syntax, input validation (ValueError), and loss-aware semantics.Relation to #172 / #176
This work originally carried its own multi-qubit decomposition path; it has been rebased to reuse
compute_decomposition_wordandexpectation()from #172 instead (the onlydata.rschange left is making thePhasedPauliWordNoHashaliaspub(crate)). What it adds on top is the observable-string grammar, lost-qubit handling, and the Python result type.Testing
cargo test -p ppvm-tableau— all green (includes new parser tests inobservable.rsand peek tests inmeasure.rs, alongside the existing feat(tableau): add Pauli expectation/trace and reset_all #172 expectation tests);cargo clippyclean,cargo fmtclean.pytest test/generalized_tableau/— 170 passed (native module rebuilt), includingtest_peek_expectation.pyand Add expectation and trace to tableau python bindings #176'stest_expectation.pyside by side.🤖 Generated with Claude Code