Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions crates/ppvm-python-native/src/interface_tableau.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,18 @@ macro_rules! create_interface {
self.inner.coefficients.len()
}

/// `⟨ψ|word|ψ⟩` for the multi-qubit Pauli string `word`.
pub fn expectation(&self, word: String) -> f64 {
let w: PauliWord<<$type as Config>::Storage> = word.into();
self.inner.expectation(&w)
}
Comment on lines +87 to +91

/// `Σ_{P matches pattern} ⟨ψ|P|ψ⟩`.
pub fn trace(&self, pattern: String) -> f64 {
let pat: PauliPattern = pattern.into();
self.inner.trace(&pat)
}
Comment on lines +93 to +97

// clifford
pub fn x(&mut self, targets: Vec<usize>) {
self.inner.x_many(targets.as_slice());
Expand Down
2 changes: 2 additions & 0 deletions ppvm-python/src/ppvm/_core.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ class _GeneralizedTableauBase:
def current_measurement_record(self) -> list[int]: ...
def coefficients(self) -> dict[int, complex]: ...
def num_coefficients(self) -> int: ...
def expectation(self, word: str) -> float: ...
def trace(self, pattern: str) -> float: ...
def x(self, targets: Sequence[int]) -> None: ...
def y(self, targets: Sequence[int]) -> None: ...
def z(self, targets: Sequence[int]) -> None: ...
Expand Down
28 changes: 28 additions & 0 deletions ppvm-python/src/ppvm/generalized_tableau.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,34 @@ def num_coefficients(self) -> int:
"""
return self._interface.num_coefficients()

def expectation(self, word: str) -> float:
"""Compute ``⟨ψ|word|ψ⟩`` for a single multi-qubit Pauli string.

Args:
word: A Pauli string such as ``"ZZ"`` or ``"X0Y1"`` (underscores
are ignored).
Comment on lines +214 to +216

Returns:
The real expectation value of ``word`` in the current state.
"""
return self._interface.expectation(word)

def trace(self, pattern: str) -> float:
"""Sum Pauli expectations over every word matching ``pattern``.

Enumerates each ``PauliWord`` accepted by ``pattern`` and returns
the sum of their expectations. Star quantifiers (``X*``) are not
supported — use counted repetition (``Z?{n}``) or positional
anchors instead.

Args:
pattern: A Pauli pattern string (e.g. ``"Z?{2}"`` or ``"Z0Z1"``).

Returns:
The summed expectation value.
"""
return self._interface.trace(pattern)

def u3(self, addr0: int, theta: float, phi: float, lam: float):
"""Apply the U3 gate to the specified qubit.

Expand Down
61 changes: 61 additions & 0 deletions ppvm-python/test/generalized_tableau/test_expectation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import math

import pytest

from ppvm import GeneralizedTableau


def _bell() -> GeneralizedTableau:
tab = GeneralizedTableau(2)
tab.h(0)
tab.cnot(0, 1)
return tab


def test_expectation_z_on_zero_state():
tab = GeneralizedTableau(1)
assert tab.expectation("Z") == pytest.approx(1.0, abs=1e-10)
assert tab.expectation("X") == pytest.approx(0.0, abs=1e-10)
assert tab.expectation("I") == pytest.approx(1.0, abs=1e-10)


def test_expectation_x_on_plus_state():
tab = GeneralizedTableau(1)
tab.h(0)
assert tab.expectation("X") == pytest.approx(1.0, abs=1e-10)
assert tab.expectation("Z") == pytest.approx(0.0, abs=1e-10)


def test_bell_state_pauli_expectations():
tab = _bell()
assert tab.expectation("II") == pytest.approx(1.0, abs=1e-10)
assert tab.expectation("ZZ") == pytest.approx(1.0, abs=1e-10)
assert tab.expectation("XX") == pytest.approx(1.0, abs=1e-10)
assert tab.expectation("YY") == pytest.approx(-1.0, abs=1e-10)
assert tab.expectation("IZ") == pytest.approx(0.0, abs=1e-10)
assert tab.expectation("ZI") == pytest.approx(0.0, abs=1e-10)


def test_ry_rotation_z_expectation_is_cos_theta():
tab = GeneralizedTableau(1)
theta = math.pi / 3
tab.ry(0, theta)
assert tab.expectation("Z") == pytest.approx(math.cos(theta), abs=1e-10)
assert tab.expectation("X") == pytest.approx(math.sin(theta), abs=1e-10)


def test_trace_z_or_identity_pattern_on_bell():
tab = _bell()
# ⟨II⟩ + ⟨IZ⟩ + ⟨ZI⟩ + ⟨ZZ⟩ = 1 + 0 + 0 + 1 = 2
assert tab.trace("Z?{2}") == pytest.approx(2.0, abs=1e-10)


def test_trace_y_or_identity_pattern_on_bell_is_zero():
tab = _bell()
# ⟨II⟩ + ⟨IY⟩ + ⟨YI⟩ + ⟨YY⟩ = 1 + 0 + 0 + (-1) = 0
assert tab.trace("Y?{2}") == pytest.approx(0.0, abs=1e-10)


def test_trace_positional_pattern_matches_single_pauli():
tab = _bell()
assert tab.trace("Z0Z1") == pytest.approx(tab.expectation("ZZ"), abs=1e-10)
Loading