From dff8d6c8d12d4f5c11bba16149532e679af453de Mon Sep 17 00:00:00 2001 From: Scott Smart <42444435+sesmart@users.noreply.github.com> Date: Wed, 29 Jul 2026 16:54:28 -0400 Subject: [PATCH] added phase treatment and corrected multiplication --- .../quantum_information/pauli_string.py | 27 +++++++------------ src/braket/quantum_information/pauli_sum.py | 3 +-- .../quantum_information/test_pauli_string.py | 4 +-- .../test_pauli_string_sum.py | 8 +++--- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index 89d05970a..2bea43a74 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -30,7 +30,8 @@ } _ID_OBS = I() _PAULI_OBSERVABLES = {_PAULI_X: X(), _PAULI_Y: Y(), _PAULI_Z: Z()} -_SIGN_MAP = {"+": 1, "-": -1} +_SIGN_MAP = {"+": 1, "-": -1, "j": 1j, "J": -1j} +_PHASE_MAP = {v: k for k, v in _SIGN_MAP.items()} class PauliString: @@ -64,10 +65,10 @@ def __init__(self, pauli_string: str | PauliString): raise TypeError(f"Pauli word {pauli_string} must be of type {PauliString} or {str}") @property - def phase(self) -> int: - """int: The phase of the Pauli string. + def phase(self) -> complex: + """complex: The phase of the Pauli string. - Can be one of +/-1 + Can be one of four roots of unity, +1, +1j, -1, and -1j. """ return self._phase @@ -199,15 +200,12 @@ def dot(self, other: PauliString, inplace: bool = False) -> PauliString: if i not in self._nontrivial: pauli_result[i] = other._nontrivial[i] - # ignore complex global phase - out_phase = -1 if (phase_result.real < 0 or phase_result.imag < 0) else 1 - # Bypass __init__ via __new__ to avoid serializing the computed dict # back into a string just to have __init__ parse it again. The fields # below fully define a valid PauliString, so direct assignment is both # faster and avoids an O(qubit_count) dense-string round trip. out_pauli_string = PauliString.__new__(PauliString) - out_pauli_string._phase = out_phase + out_pauli_string._phase = phase_result out_pauli_string._qubit_count = self._qubit_count out_pauli_string._nontrivial = pauli_result @@ -334,11 +332,10 @@ def power(self, n: int, inplace: bool = False) -> PauliString: # field assignment keeps the hot path allocation-light. pauli_other = PauliString.__new__(PauliString) pauli_other._qubit_count = self._qubit_count + pauli_other._phase = self._phase**n if n % 2 == 0: - pauli_other._phase = 1 pauli_other._nontrivial = {} else: - pauli_other._phase = self._phase pauli_other._nontrivial = dict(self._nontrivial) if inplace: @@ -428,14 +425,14 @@ def __repr__(self): factors = ["I"] * self._qubit_count for i, p in self._nontrivial.items(): factors[i] = p - return f"{PauliString._phase_to_str(self._phase)}{''.join(factors)}" + return f"{_PHASE_MAP[self._phase]}{''.join(factors)}" @staticmethod def _split(pauli_word: str) -> tuple[int, str]: index = 0 phase = 1 - if pauli_word[index] in {"+", "-"}: - phase *= int(f"{pauli_word[index]}1") + if pauli_word[index] in _SIGN_MAP: + phase *= _SIGN_MAP[pauli_word[index]] index += 1 unsigned = pauli_word[index:] if not unsigned: @@ -444,10 +441,6 @@ def _split(pauli_word: str) -> tuple[int, str]: raise ValueError(f"{pauli_word} is not a valid Pauli string") return phase, unsigned - @staticmethod - def _phase_to_str(phase: int) -> str: - return "+" if phase > 0 else "-" - def _generate_eigenstate_circuit(self, signs: tuple[int, ...]) -> Circuit: circ = Circuit() for qubit in range(len(signs)): diff --git a/src/braket/quantum_information/pauli_sum.py b/src/braket/quantum_information/pauli_sum.py index 7e0a06f49..c7ca833bb 100644 --- a/src/braket/quantum_information/pauli_sum.py +++ b/src/braket/quantum_information/pauli_sum.py @@ -208,8 +208,7 @@ def _pad_pauli_string(pauli_string: PauliString, qubit_count: int) -> PauliStrin factors = ["I"] * qubit_count for qubit in range(pauli_string.qubit_count): factors[qubit] = "IXYZ"[pauli_string[qubit]] - sign = "-" if pauli_string.phase < 0 else "+" - return PauliString(f"{sign}{''.join(factors)}") + return PauliString("".join(factors)) @staticmethod def _term_from_observable(observable: Observable) -> tuple[numbers.Number, str]: diff --git a/test/unit_tests/braket/quantum_information/test_pauli_string.py b/test/unit_tests/braket/quantum_information/test_pauli_string.py index 61491ed48..7fe95c890 100644 --- a/test/unit_tests/braket/quantum_information/test_pauli_string.py +++ b/test/unit_tests/braket/quantum_information/test_pauli_string.py @@ -149,8 +149,8 @@ def test_eigenstate_invalid_signs(sign): ("XYXZY", "+XYXZY", "IIIII"), ("XYZ", "ZYX", "YIY"), ("YZ", "ZX", "-XY"), - ("-Z", "Y", "X"), - ("Z", "Y", "-X"), + ("-Z", "Y", "jX"), + ("Z", "Y", "JX"), ], ) def test_dot(circ_arg_1, circ_arg_2, circ_res): diff --git a/test/unit_tests/braket/quantum_information/test_pauli_string_sum.py b/test/unit_tests/braket/quantum_information/test_pauli_string_sum.py index e7892fa21..0676e605a 100644 --- a/test/unit_tests/braket/quantum_information/test_pauli_string_sum.py +++ b/test/unit_tests/braket/quantum_information/test_pauli_string_sum.py @@ -35,20 +35,20 @@ def test_addition_subtraction_and_scalar_multiplication(): def test_multiplication_by_pauli_string(): pauli_sum = PauliStringSum([(2.0, "XY"), (3.0, "ZZ")]) - assert (pauli_sum * PauliString("YZ")).to_list() == [(-2.0, "ZX"), (-3.0, "XI")] + assert (pauli_sum * PauliString("YZ")).to_list() == [(-2.0 + 0j, "ZX"), (-3j, "XI")] def test_multiplication_by_pauli_string_pads_mixed_width_terms(): pauli_sum = PauliStringSum([(1.0, "X"), (2.0, "IZ")]) - assert (pauli_sum * PauliString("ZZ")).to_list() == [(-1.0, "YZ"), (2.0, "ZI")] + assert (pauli_sum * PauliString("ZZ")).to_list() == [(-1j, "YZ"), (2.0, "ZI")] def test_left_multiplication_by_pauli_string_preserves_order(): pauli_sum = PauliStringSum([(2.0, "X")]) - assert (pauli_sum * PauliString("Z")).to_list() == [(-2.0, "Y")] - assert (PauliString("Z") * pauli_sum).to_list() == [(2.0, "Y")] + assert (pauli_sum * PauliString("Z")).to_list() == [(-2j, "Y")] + assert (PauliString("Z") * pauli_sum).to_list() == [(2j, "Y")] def test_indexing_and_membership():