From 570dd47b7fded29208a25ae7a675b18b4ea6cb47 Mon Sep 17 00:00:00 2001 From: dobbse42 Date: Tue, 8 Oct 2024 09:30:17 +0300 Subject: [PATCH 01/10] added HammingWeightPhasingWithConfigurableAncilla and a temp test file, still debugging. --- .../bloqs/rotations/hamming_weight_phasing.py | 80 +++++++++++++++++++ qualtran/bloqs/rotations/my_HWP_test.py | 18 +++++ 2 files changed, 98 insertions(+) create mode 100644 qualtran/bloqs/rotations/my_HWP_test.py diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index 7f6334fd4f..4881dcd472 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -209,3 +209,83 @@ def _hamming_weight_phasing_via_phase_gradient() -> HammingWeightPhasingViaPhase bloq_cls=HammingWeightPhasingViaPhaseGradient, examples=(_hamming_weight_phasing_via_phase_gradient,), ) + + +@attrs.frozen +class HammingWeightPhasingWithConfigurableAncilla(GateWithRegisters): + r""" + Args: + bitsize: Size of input register to apply 'Z ** exponent' to. + ancillasize: Size of the ancilla register to be used to calculate the hamming weight of 'x'. + exponent: the exponent of 'Z ** exponent' to be applied to each qubit in the input register. + eps: Accuracy of synthesizing the Z rotations. + + Registers: + x: A 'THRU' register of 'bitsize' qubits. + + References: + """ + + bitsize: int + ancillasize: int + exponent: float = 1 + eps: SymbolicFloat = 1e-10 + + @cached_property + def signature(self) -> 'Signature': + return Signature.build_from_dtypes(x=QUInt(self.bitsize)) + + #TODO: + ''' + General strategy: find the max-bitsize number (n bits) we can compute the HW of using our available ancilla, + greedily do this on the first n bits of x, perform the rotations, then the next n bits and perform those + rotations, and so on until we have computed the HW of the entire input. Can express this as repeated calls to + HammingWeightPhasing bloqs on subsets of the input. + ''' + def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'SoquetT') -> Dict[str, 'SoquetT']: + num_iters = self.bitsize // (self.ancillasize + 1) + remainder = self.bitsize - (self.ancillasize + 1) * num_iters + x = bb.split(x) + x_parts = [] + for i in range(num_iters): + x_part = bb.join(x[i*(self.ancillasize+1):(i+1)*(self.ancillasize+1)], dtype=QUInt(self.ancillasize+1)) #maybe off-by-1 + x_part = bb.add(HammingWeightPhasing(bitsize=self.ancillasize+1, exponent=self.exponent, eps=self.eps), x=x_part) + x_part = bb.add(HammingWeightPhasing(bitsize=self.ancillasize+1, exponent=self.exponent, eps=self.eps).adjoint(), x=x_part) + x_parts.extend(bb.split(x_part)) + #remainder: + if remainder > 0: + x_part = bb.join(x[(-1*remainder):], dtype=QUInt(remainder)) + x_part = bb.add(HammingWeightPhasing(bitsize=remainder, exponent=self.exponent, eps=self.eps), x=x_part) + x_part = bb.add(HammingWeightPhasing(bitsize=remainder, exponent=self.exponent, eps=self.eps).adjoint(), x=x_part) + x_parts.extend(bb.split(x_part)) + #print("shape prior to flatten: ", np.shape(x_parts)) + #x_parts.flatten() + ''' x_parts = [ + a + for x_part in x_parts + for a in x_part + ] + ''' + #print("shape after flatten: ", np.shape(x_parts)) + for part in x: + print("next elem: ", part) + x = bb.join(x_parts, dtype=QUInt(self.bitsize.bit_length())) + return {'x': x} + + + def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': + if reg is None: + return Text(f'HWPCA_{self.bitsize}/(Z^{self.exponent})') + return super().wire_symbol(reg, idx) + +#TODO: (after build_composite_bloq) +@bloq_example +def _hamming_weight_phasing_with_configurable_ancilla() -> HammingWeightPhasingWithConfigurableAncilla: + hamming_weight_phasing_with_configurable_ancilla = HammingWeightPhasingWithConfigurableAncilla(4, 2, np.pi / 2.0) + return hamming_weight_phasing_with_configurable_ancilla + + +_HAMMING_WEIGHT_PHASING_WITH_CONFIGURABLE_ANCILLA_DOC = BloqDocSpec( + bloq_cls=HammingWeightPhasingWithConfigurableAncilla, + examples=(_hamming_weight_phasing_with_configurable_ancilla,), +) diff --git a/qualtran/bloqs/rotations/my_HWP_test.py b/qualtran/bloqs/rotations/my_HWP_test.py new file mode 100644 index 0000000000..7709dc47d8 --- /dev/null +++ b/qualtran/bloqs/rotations/my_HWP_test.py @@ -0,0 +1,18 @@ +from hamming_weight_phasing import HammingWeightPhasing, HammingWeightPhasingWithConfigurableAncilla + +import numpy as np +from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register +from qualtran import QBit, QInt, QUInt, QAny +from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma + +orig = HammingWeightPhasing(4, np.pi / 2.0) +print(orig) + +mine = HammingWeightPhasingWithConfigurableAncilla(4, 2, np.pi / 2.0) +print(mine) + + +from qualtran.resource_counting.generalizers import ignore_split_join +hamming_weight_phasing_g, hamming_weight_phasing_sigma = mine.call_graph(max_depth=1, generalizer=ignore_split_join) +show_call_graph(hamming_weight_phasing_g) +show_counts_sigma(hamming_weight_phasing_sigma) From 431bc6d6a9a4a79e2dd6eb562e14f6ca5688fb2b Mon Sep 17 00:00:00 2001 From: dobbse42 Date: Wed, 16 Oct 2024 10:28:00 +0300 Subject: [PATCH 02/10] Finished HammingWeightPhasingWithConfigurableAncilla.build_composite_bloq(), added test_hamming_weight_phasing_with_configurable_ancilla(). --- .../bloqs/rotations/hamming_weight_phasing.py | 10 ++++--- .../rotations/hamming_weight_phasing_test.py | 27 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index 4881dcd472..dce3272219 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -235,7 +235,7 @@ class HammingWeightPhasingWithConfigurableAncilla(GateWithRegisters): def signature(self) -> 'Signature': return Signature.build_from_dtypes(x=QUInt(self.bitsize)) - #TODO: + ''' General strategy: find the max-bitsize number (n bits) we can compute the HW of using our available ancilla, greedily do this on the first n bits of x, perform the rotations, then the next n bits and perform those @@ -243,16 +243,18 @@ def signature(self) -> 'Signature': HammingWeightPhasing bloqs on subsets of the input. ''' def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'SoquetT') -> Dict[str, 'SoquetT']: + self.ancillasize = min(self.ancillasize, self.bitsize-1) # TODO: this is surely the wrong way to do this, but this at least allows tests to be run for now. num_iters = self.bitsize // (self.ancillasize + 1) remainder = self.bitsize - (self.ancillasize + 1) * num_iters x = bb.split(x) x_parts = [] + for i in range(num_iters): x_part = bb.join(x[i*(self.ancillasize+1):(i+1)*(self.ancillasize+1)], dtype=QUInt(self.ancillasize+1)) #maybe off-by-1 x_part = bb.add(HammingWeightPhasing(bitsize=self.ancillasize+1, exponent=self.exponent, eps=self.eps), x=x_part) x_part = bb.add(HammingWeightPhasing(bitsize=self.ancillasize+1, exponent=self.exponent, eps=self.eps).adjoint(), x=x_part) x_parts.extend(bb.split(x_part)) - #remainder: + if remainder > 0: x_part = bb.join(x[(-1*remainder):], dtype=QUInt(remainder)) x_part = bb.add(HammingWeightPhasing(bitsize=remainder, exponent=self.exponent, eps=self.eps), x=x_part) @@ -269,7 +271,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'SoquetT') -> Dict[str, #print("shape after flatten: ", np.shape(x_parts)) for part in x: print("next elem: ", part) - x = bb.join(x_parts, dtype=QUInt(self.bitsize.bit_length())) + x = bb.join(np.array(x_parts), dtype=QUInt(self.bitsize)) return {'x': x} @@ -278,7 +280,7 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - return Text(f'HWPCA_{self.bitsize}/(Z^{self.exponent})') return super().wire_symbol(reg, idx) -#TODO: (after build_composite_bloq) + @bloq_example def _hamming_weight_phasing_with_configurable_ancilla() -> HammingWeightPhasingWithConfigurableAncilla: hamming_weight_phasing_with_configurable_ancilla = HammingWeightPhasingWithConfigurableAncilla(4, 2, np.pi / 2.0) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py index 33dc216268..60cea4458b 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py @@ -23,6 +23,7 @@ from qualtran.bloqs.rotations.hamming_weight_phasing import ( HammingWeightPhasing, HammingWeightPhasingViaPhaseGradient, + HammingWeightPhasingWithConfigurableAncilla, ) from qualtran.bloqs.rotations.phase_gradient import PhaseGradientState from qualtran.cirq_interop.testing import GateHelper @@ -127,3 +128,29 @@ def test_hamming_weight_phasing_via_phase_gradient_t_complexity(n: int, theta: f naive_total_t = naive_hwp_t_complexity.t_incl_rotations(eps=eps / n.bit_length()) assert total_t < naive_total_t + + +@pytest.mark.parametrize('ancillasize', [1, 2, 3, 4, 5, 6, 7]) +@pytest.mark.parametrize('n', [2, 3, 4, 5, 6, 7, 8]) +@pytest.mark.parametrize('theta', [1 / 10, 1 / 5, 1 / 7, np.pi / 2]) +def test_hamming_weight_phasing_with_configurable_ancilla(n: int, ancillasize: int, theta: float): + gate = HammingWeightPhasingWithConfigurableAncilla(n, ancillasize, theta) + qlt_testing.assert_valid_bloq_decomposition(gate) + qlt_testing.assert_equivalent_bloq_counts( + gate, [ignore_split_join, cirq_to_bloqs, generalize_rotation_angle] + ) + + assert gate.t_complexity().rotations == ceil(n / ancillasize+1) * ancillasize.bit_length() # possibly wrong + assert gate.t_complexity().t == 4 * ancillasize * ceil(n / (ancillasize+1)) + # TODO: add an ancilla size assertion + + gh = GateHelper(gate) + sim = cirq.Simulator(dtype=np.complex128) + initial_state = cirq.testing.random_superposition(dim=2**n, random_state=12345) + state_prep = cirq.Circuit(cirq.StatePreparationChannel(initial_state).on(*gh.quregs['x'])) + brute_force_phasing = cirq.Circuit(state_prep, (cirq.Z**theta).on_each(*gh.quregs['x'])) + expected_final_state = sim.simulate(brute_force_phasing).final_state_vector + + hw_phasing = cirq.Circuit(state_prep, HammingWeightPhasingWithConfigurableAncilla(n, ancillasize, theta).on(*gh.quregs['x'])) + hw_final_state = sim.simulate(hw_phasing).final_state_vector + assert np.allclose(expected_final_state, hw_final_state, atol=1e-7) From e484cc01df955522d7c034199061bcfd77d629f7 Mon Sep 17 00:00:00 2001 From: dobbse42 Date: Thu, 17 Oct 2024 11:24:11 +0300 Subject: [PATCH 03/10] Completed implementation of HammingWeightPhasingWithConfigurableAncilla and tests. --- .../bloqs/rotations/hamming_weight_phasing.py | 51 +++++++++++-------- .../rotations/hamming_weight_phasing_test.py | 13 ++--- .../rotations/{my_HWP_test.py => my_HWP.py} | 1 + 3 files changed, 38 insertions(+), 27 deletions(-) rename qualtran/bloqs/rotations/{my_HWP_test.py => my_HWP.py} (99%) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index 1bfbe5e4b4..ab775915ba 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -227,7 +227,7 @@ class HammingWeightPhasingWithConfigurableAncilla(GateWithRegisters): """ bitsize: int - ancillasize: int + ancillasize: int # TODO: verify that ancillasize is always < bitsize-1 exponent: float = 1 eps: SymbolicFloat = 1e-10 @@ -243,34 +243,22 @@ def signature(self) -> 'Signature': HammingWeightPhasing bloqs on subsets of the input. ''' def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'SoquetT') -> Dict[str, 'SoquetT']: - self.ancillasize = min(self.ancillasize, self.bitsize-1) # TODO: this is surely the wrong way to do this, but this at least allows tests to be run for now. num_iters = self.bitsize // (self.ancillasize + 1) - remainder = self.bitsize - (self.ancillasize + 1) * num_iters + remainder = self.bitsize % (self.ancillasize+1) x = bb.split(x) x_parts = [] - for i in range(num_iters): - x_part = bb.join(x[i*(self.ancillasize+1):(i+1)*(self.ancillasize+1)], dtype=QUInt(self.ancillasize+1)) #maybe off-by-1 + x_part = bb.join(x[i*(self.ancillasize+1):(i+1)*(self.ancillasize+1)], dtype=QUInt(self.ancillasize+1)) x_part = bb.add(HammingWeightPhasing(bitsize=self.ancillasize+1, exponent=self.exponent, eps=self.eps), x=x_part) - x_part = bb.add(HammingWeightPhasing(bitsize=self.ancillasize+1, exponent=self.exponent, eps=self.eps).adjoint(), x=x_part) x_parts.extend(bb.split(x_part)) - - if remainder > 0: + if remainder > 1: x_part = bb.join(x[(-1*remainder):], dtype=QUInt(remainder)) x_part = bb.add(HammingWeightPhasing(bitsize=remainder, exponent=self.exponent, eps=self.eps), x=x_part) - x_part = bb.add(HammingWeightPhasing(bitsize=remainder, exponent=self.exponent, eps=self.eps).adjoint(), x=x_part) - x_parts.extend(bb.split(x_part)) - #print("shape prior to flatten: ", np.shape(x_parts)) - #x_parts.flatten() - ''' x_parts = [ - a - for x_part in x_parts - for a in x_part - ] - ''' - #print("shape after flatten: ", np.shape(x_parts)) - for part in x: - print("next elem: ", part) + x_parts.extend(bb.split(x_part)) + if remainder == 1: + x_part = x[-1] + x_part = bb.add(ZPowGate(exponent=self.exponent, eps=self.eps), q=x_part) + x_parts.append(x_part) x = bb.join(np.array(x_parts), dtype=QUInt(self.bitsize)) return {'x': x} @@ -281,6 +269,27 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - return super().wire_symbol(reg, idx) + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + num_iters = self.bitsize // (self.ancillasize + 1) + remainder = self.bitsize - (self.ancillasize + 1) * num_iters + # TODO: Surely there is a better way of doing this + if remainder > 1: + + return { + HammingWeightPhasing(self.ancillasize+1, self.exponent, self.eps): num_iters, + HammingWeightPhasing(remainder, self.exponent, self.eps): bool(remainder), + } + elif remainder: + return { + HammingWeightPhasing(self.ancillasize+1, self.exponent, self.eps): num_iters, + ZPowGate(exponent=self.exponent, eps=self.eps): 1 + } + else: + return { + HammingWeightPhasing(self.ancillasize+1, self.exponent, self.eps): num_iters, + } + + @bloq_example def _hamming_weight_phasing_with_configurable_ancilla() -> HammingWeightPhasingWithConfigurableAncilla: hamming_weight_phasing_with_configurable_ancilla = HammingWeightPhasingWithConfigurableAncilla(4, 2, np.pi / 2.0) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py index 60cea4458b..4b7f55857b 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py @@ -129,9 +129,7 @@ def test_hamming_weight_phasing_via_phase_gradient_t_complexity(n: int, theta: f assert total_t < naive_total_t - -@pytest.mark.parametrize('ancillasize', [1, 2, 3, 4, 5, 6, 7]) -@pytest.mark.parametrize('n', [2, 3, 4, 5, 6, 7, 8]) +@pytest.mark.parametrize('n, ancillasize', [(n, ancillasize) for n in range(3, 9) for ancillasize in range(1, n-1)]) @pytest.mark.parametrize('theta', [1 / 10, 1 / 5, 1 / 7, np.pi / 2]) def test_hamming_weight_phasing_with_configurable_ancilla(n: int, ancillasize: int, theta: float): gate = HammingWeightPhasingWithConfigurableAncilla(n, ancillasize, theta) @@ -140,9 +138,12 @@ def test_hamming_weight_phasing_with_configurable_ancilla(n: int, ancillasize: i gate, [ignore_split_join, cirq_to_bloqs, generalize_rotation_angle] ) - assert gate.t_complexity().rotations == ceil(n / ancillasize+1) * ancillasize.bit_length() # possibly wrong - assert gate.t_complexity().t == 4 * ancillasize * ceil(n / (ancillasize+1)) - # TODO: add an ancilla size assertion + remainder = n % (ancillasize+1) + +# assert gate.t_complexity().rotations == (-(-n // (ancillasize+1))-1) * (ancillasize+1).bit_length() + remainder.bit_length() # exact, fails for remainder = 0. + assert gate.t_complexity().rotations <= (-(-n // (ancillasize+1))) * (ancillasize+1).bit_length() + remainder.bit_length() # upper bound + assert gate.t_complexity().t <= 4 * (ancillasize) * -(-n // (ancillasize+1)) + # TODO: add an assertion that number of ancilla allocated is never > ancillasize. gh = GateHelper(gate) sim = cirq.Simulator(dtype=np.complex128) diff --git a/qualtran/bloqs/rotations/my_HWP_test.py b/qualtran/bloqs/rotations/my_HWP.py similarity index 99% rename from qualtran/bloqs/rotations/my_HWP_test.py rename to qualtran/bloqs/rotations/my_HWP.py index 7709dc47d8..7bc37616a4 100644 --- a/qualtran/bloqs/rotations/my_HWP_test.py +++ b/qualtran/bloqs/rotations/my_HWP.py @@ -1,5 +1,6 @@ from hamming_weight_phasing import HammingWeightPhasing, HammingWeightPhasingWithConfigurableAncilla + import numpy as np from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register from qualtran import QBit, QInt, QUInt, QAny From d344afe4e068d8786fc4d9e8f2cc5a5acb48a90c Mon Sep 17 00:00:00 2001 From: dobbse42 Date: Mon, 2 Dec 2024 10:43:47 +0200 Subject: [PATCH 04/10] removed extraneous test file --- qualtran/bloqs/rotations/my_HWP.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 qualtran/bloqs/rotations/my_HWP.py diff --git a/qualtran/bloqs/rotations/my_HWP.py b/qualtran/bloqs/rotations/my_HWP.py deleted file mode 100644 index 7bc37616a4..0000000000 --- a/qualtran/bloqs/rotations/my_HWP.py +++ /dev/null @@ -1,19 +0,0 @@ -from hamming_weight_phasing import HammingWeightPhasing, HammingWeightPhasingWithConfigurableAncilla - - -import numpy as np -from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register -from qualtran import QBit, QInt, QUInt, QAny -from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma - -orig = HammingWeightPhasing(4, np.pi / 2.0) -print(orig) - -mine = HammingWeightPhasingWithConfigurableAncilla(4, 2, np.pi / 2.0) -print(mine) - - -from qualtran.resource_counting.generalizers import ignore_split_join -hamming_weight_phasing_g, hamming_weight_phasing_sigma = mine.call_graph(max_depth=1, generalizer=ignore_split_join) -show_call_graph(hamming_weight_phasing_g) -show_counts_sigma(hamming_weight_phasing_sigma) From c0601f18c6f268f5f7ac341f0d5aa9de399f1c86 Mon Sep 17 00:00:00 2001 From: Evan Dobbs Date: Wed, 5 Feb 2025 16:54:12 +0200 Subject: [PATCH 05/10] implemented anurudh's counter suggestion in HWPhasing_with_configurable_ancilla.build_call_graph() --- .../bloqs/rotations/hamming_weight_phasing.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index ab775915ba..2977552632 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -272,22 +272,16 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': num_iters = self.bitsize // (self.ancillasize + 1) remainder = self.bitsize - (self.ancillasize + 1) * num_iters - # TODO: Surely there is a better way of doing this - if remainder > 1: - return { - HammingWeightPhasing(self.ancillasize+1, self.exponent, self.eps): num_iters, - HammingWeightPhasing(remainder, self.exponent, self.eps): bool(remainder), - } + counts = Counter[Bloq]() + counts[HammingWeightPhasing(self.ancillasize+1, self.exponent, self.eps)] += num_iters + + if remainder > 1: + counts[HammingWeightPhasing(remainder, self.exponent, self.eps)]: += 1 elif remainder: - return { - HammingWeightPhasing(self.ancillasize+1, self.exponent, self.eps): num_iters, - ZPowGate(exponent=self.exponent, eps=self.eps): 1 - } - else: - return { - HammingWeightPhasing(self.ancillasize+1, self.exponent, self.eps): num_iters, - } + counts[ZPowGate(exponent=self.exponent, eps=self.eps)] += 1 + + return counts @bloq_example From a64283b86bda3ef4d114c3655bf548a6a67a64a6 Mon Sep 17 00:00:00 2001 From: Evan Dobbs Date: Thu, 3 Apr 2025 10:02:42 +0300 Subject: [PATCH 06/10] fixed syntax error on prior commit --- qualtran/bloqs/rotations/hamming_weight_phasing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index 2977552632..9b371a9a1a 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -14,11 +14,12 @@ from functools import cached_property from typing import Dict, Optional, Tuple, TYPE_CHECKING +from collections import Counter import attrs import numpy as np -from qualtran import bloq_example, BloqDocSpec, GateWithRegisters, QFxp, QUInt, Register, Signature +from qualtran import bloq_example, BloqDocSpec, GateWithRegisters, QFxp, QUInt, Register, Signature, Bloq from qualtran.bloqs.arithmetic import HammingWeightCompute from qualtran.bloqs.basic_gates import ZPowGate from qualtran.bloqs.rotations.quantum_variable_rotation import QvrPhaseGradient @@ -277,7 +278,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': counts[HammingWeightPhasing(self.ancillasize+1, self.exponent, self.eps)] += num_iters if remainder > 1: - counts[HammingWeightPhasing(remainder, self.exponent, self.eps)]: += 1 + counts[HammingWeightPhasing(remainder, self.exponent, self.eps)] += 1 elif remainder: counts[ZPowGate(exponent=self.exponent, eps=self.eps)] += 1 From 57c87ea72e03195b95c114bc453387015ebd5c42 Mon Sep 17 00:00:00 2001 From: Evan Dobbs Date: Thu, 3 Apr 2025 11:50:43 +0300 Subject: [PATCH 07/10] Added input validation --- qualtran/bloqs/rotations/hamming_weight_phasing.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index 9b371a9a1a..312a61dabd 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -236,6 +236,9 @@ class HammingWeightPhasingWithConfigurableAncilla(GateWithRegisters): def signature(self) -> 'Signature': return Signature.build_from_dtypes(x=QUInt(self.bitsize)) + def __attrs_post_init__(self): + if self.ancillasize >= self.bitsize - 1: + raise ValueError('ancillasize should be less than bitsize - 1.') ''' General strategy: find the max-bitsize number (n bits) we can compute the HW of using our available ancilla, From f5e0a7d5190364f612fa385d0a9842f315d9e3ce Mon Sep 17 00:00:00 2001 From: Evan Dobbs Date: Thu, 22 May 2025 15:15:11 +0300 Subject: [PATCH 08/10] changed to using QECGatesCost and GateCounts for testing and swapped to checking toffoli-likes rather than total T-count, and updated the superclass of HammingWeightPhasingWithConfigurableAncilla to bloq. --- .../bloqs/rotations/hamming_weight_phasing.py | 2 +- .../rotations/hamming_weight_phasing_test.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index 312a61dabd..0ca7310e00 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -213,7 +213,7 @@ def _hamming_weight_phasing_via_phase_gradient() -> HammingWeightPhasingViaPhase @attrs.frozen -class HammingWeightPhasingWithConfigurableAncilla(GateWithRegisters): +class HammingWeightPhasingWithConfigurableAncilla(Bloq): r""" Args: bitsize: Size of input register to apply 'Z ** exponent' to. diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py index 4b7f55857b..7bdef4b11e 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py @@ -32,6 +32,12 @@ generalize_rotation_angle, ignore_split_join, ) +from qualtran.resource_counting import ( + QECGatesCost, + GateCounts, + get_cost_value, +) + from qualtran.symbolics import SymbolicInt if TYPE_CHECKING: @@ -141,10 +147,14 @@ def test_hamming_weight_phasing_with_configurable_ancilla(n: int, ancillasize: i remainder = n % (ancillasize+1) # assert gate.t_complexity().rotations == (-(-n // (ancillasize+1))-1) * (ancillasize+1).bit_length() + remainder.bit_length() # exact, fails for remainder = 0. - assert gate.t_complexity().rotations <= (-(-n // (ancillasize+1))) * (ancillasize+1).bit_length() + remainder.bit_length() # upper bound - assert gate.t_complexity().t <= 4 * (ancillasize) * -(-n // (ancillasize+1)) - # TODO: add an assertion that number of ancilla allocated is never > ancillasize. - +# Outdated method of doing gatecount tests. Also tests T-count rather than Toffoli count. +# assert gate.t_complexity().rotations <= (-(-n // (ancillasize+1))) * (ancillasize+1).bit_length() + remainder.bit_length() # upper bound +# assert gate.t_complexity().t <= 4 * (ancillasize) * -(-n // (ancillasize+1) + + gc = get_cost_value(gate, QECGatesCost()) + assert gc.rotation <= (-(-n // (ancillasize+1))) * (ancillasize+1).bit_length() + remainder.bit_length() + assert gc.toffoli + gc.and_bloq + gc.cswap <= ancillasize * -(-n // (ancillasize+1)) + gh = GateHelper(gate) sim = cirq.Simulator(dtype=np.complex128) initial_state = cirq.testing.random_superposition(dim=2**n, random_state=12345) From 414a3067b34bb947b27a9d015e398ee5a148ddc1 Mon Sep 17 00:00:00 2001 From: Evan Dobbs Date: Thu, 22 May 2025 15:40:37 +0300 Subject: [PATCH 09/10] Implemented suggested code style changes and more detailed documentation --- .../bloqs/rotations/hamming_weight_phasing.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index 0ca7310e00..ce662c5e54 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -214,7 +214,14 @@ def _hamming_weight_phasing_via_phase_gradient() -> HammingWeightPhasingViaPhase @attrs.frozen class HammingWeightPhasingWithConfigurableAncilla(Bloq): - r""" + r""""Applies $Z^{\text{exponent}}$ to every qubit of an input register of size `bitsize`. + + See docstring of 'HammingWeightPhasing' for more details about how hamming weight phasing works, and the docstring of 'HammingWeightPhasingViaPhaseGradient' for more details about how the rotations are synthesized. + + This is a variant of hamming weight phasing via phase gradient which uses a constant number of ancilla specified by the user. See Appendix A.2 of [1] (pages 19-21) for details. Note that this method has increased T-complexity compared to hamming weight phasing via phase gradient, since the hamming weight of the entire register cannot be calculated at once due to the limited number of ancilla available. Instead, we calculate the hamming weight in parts, performing the full rotation over the course of $\lceil n/(r+1)\rceil$ repetitions. Again, see [1] for a detailed analysis of the resource costs of this technique compared to vanilla hamming weight phasing and compard with hamming weight phasing via phase gradient. Also see the note in the 'HammingWeightPhasingViaPhaseGradient' docstring for information about when these methods are actually practical to use over vannilla hamming weight phasing. + + + Args: bitsize: Size of input register to apply 'Z ** exponent' to. ancillasize: Size of the ancilla register to be used to calculate the hamming weight of 'x'. @@ -225,6 +232,8 @@ class HammingWeightPhasingWithConfigurableAncilla(Bloq): x: A 'THRU' register of 'bitsize' qubits. References: + [Improved Fault-Tolerant Quantum Simulation of Condensed-Phase Correlated Electrons via Trotterization](https://arxiv.org/abs/1902.10673) + Appendix A.2: Hamming weight phasing with limited ancilla """ bitsize: int @@ -240,13 +249,14 @@ def __attrs_post_init__(self): if self.ancillasize >= self.bitsize - 1: raise ValueError('ancillasize should be less than bitsize - 1.') - ''' - General strategy: find the max-bitsize number (n bits) we can compute the HW of using our available ancilla, - greedily do this on the first n bits of x, perform the rotations, then the next n bits and perform those - rotations, and so on until we have computed the HW of the entire input. Can express this as repeated calls to - HammingWeightPhasing bloqs on subsets of the input. - ''' + def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'SoquetT') -> Dict[str, 'SoquetT']: + ''' + General strategy: find the max-bitsize number (n bits) we can compute the HW of using our available ancilla, + greedily do this on the first n bits of x, perform the rotations, then the next n bits and perform those + rotations, and so on until we have computed the HW of the entire input. Can express this as repeated calls to + HammingWeightPhasing bloqs on subsets of the input. + ''' num_iters = self.bitsize // (self.ancillasize + 1) remainder = self.bitsize % (self.ancillasize+1) x = bb.split(x) From a0126a223cb56e794c22b3d255952434cb4a9380 Mon Sep 17 00:00:00 2001 From: Evan Dobbs Date: Thu, 22 May 2025 15:52:21 +0300 Subject: [PATCH 10/10] Separated HammingWeightPhasingWithConfigurableAncilla decomposition/functionality tests from bloq-count tests --- .../bloqs/rotations/hamming_weight_phasing.py | 6 ++--- .../rotations/hamming_weight_phasing_test.py | 27 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing.py b/qualtran/bloqs/rotations/hamming_weight_phasing.py index ce662c5e54..2a6c6528fd 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing.py @@ -236,9 +236,9 @@ class HammingWeightPhasingWithConfigurableAncilla(Bloq): Appendix A.2: Hamming weight phasing with limited ancilla """ - bitsize: int - ancillasize: int # TODO: verify that ancillasize is always < bitsize-1 - exponent: float = 1 + bitsize: SymbolicInt + ancillasize: SymbolicInt # TODO: verify that ancillasize is always < bitsize-1 + exponent: SymbolicFloat = 1 eps: SymbolicFloat = 1e-10 @cached_property diff --git a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py index 7bdef4b11e..7dbeb32b8e 100644 --- a/qualtran/bloqs/rotations/hamming_weight_phasing_test.py +++ b/qualtran/bloqs/rotations/hamming_weight_phasing_test.py @@ -143,17 +143,6 @@ def test_hamming_weight_phasing_with_configurable_ancilla(n: int, ancillasize: i qlt_testing.assert_equivalent_bloq_counts( gate, [ignore_split_join, cirq_to_bloqs, generalize_rotation_angle] ) - - remainder = n % (ancillasize+1) - -# assert gate.t_complexity().rotations == (-(-n // (ancillasize+1))-1) * (ancillasize+1).bit_length() + remainder.bit_length() # exact, fails for remainder = 0. -# Outdated method of doing gatecount tests. Also tests T-count rather than Toffoli count. -# assert gate.t_complexity().rotations <= (-(-n // (ancillasize+1))) * (ancillasize+1).bit_length() + remainder.bit_length() # upper bound -# assert gate.t_complexity().t <= 4 * (ancillasize) * -(-n // (ancillasize+1) - - gc = get_cost_value(gate, QECGatesCost()) - assert gc.rotation <= (-(-n // (ancillasize+1))) * (ancillasize+1).bit_length() + remainder.bit_length() - assert gc.toffoli + gc.and_bloq + gc.cswap <= ancillasize * -(-n // (ancillasize+1)) gh = GateHelper(gate) sim = cirq.Simulator(dtype=np.complex128) @@ -165,3 +154,19 @@ def test_hamming_weight_phasing_with_configurable_ancilla(n: int, ancillasize: i hw_phasing = cirq.Circuit(state_prep, HammingWeightPhasingWithConfigurableAncilla(n, ancillasize, theta).on(*gh.quregs['x'])) hw_final_state = sim.simulate(hw_phasing).final_state_vector assert np.allclose(expected_final_state, hw_final_state, atol=1e-7) + + +@pytest.mark.parametrize('n, ancillasize', [(n, ancillasize) for n in range(3, 9) for ancillasize in range(1, n-1)]) +@pytest.mark.parametrize('theta', [1 / 10, 1 / 5, 1 / 7, np.pi / 2]) +def test_hamming_weight_phasing_with_configurable_ancilla_bloq_counts(n: int, ancillasize: int, theta: float): + gate = HammingWeightPhasingWithConfigurableAncilla(n, ancillasize, theta) + remainder = n % (ancillasize+1) + +# assert gate.t_complexity().rotations == (-(-n // (ancillasize+1))-1) * (ancillasize+1).bit_length() + remainder.bit_length() # exact, fails for remainder = 0. +# Outdated method of doing gatecount tests. Also tests T-count rather than Toffoli count. +# assert gate.t_complexity().rotations <= (-(-n // (ancillasize+1))) * (ancillasize+1).bit_length() + remainder.bit_length() # upper bound +# assert gate.t_complexity().t <= 4 * (ancillasize) * -(-n // (ancillasize+1) + + gc = get_cost_value(gate, QECGatesCost()) + assert gc.rotation <= (-(-n // (ancillasize+1))) * (ancillasize+1).bit_length() + remainder.bit_length() + assert gc.toffoli + gc.and_bloq + gc.cswap <= ancillasize * -(-n // (ancillasize+1))