Skip to content

Commit 32ad347

Browse files
fdmalonempharrigan
andauthored
Add HWP Trotter bloqs and costs to notebook. (#946)
* Small fixes to hubbard bloqs. * Add cost notebook. * Fix eps type. * Add hamming weight phasing bloqs. * Add costs to notebook. * Add costs to notebook. * Add hwp trotter factory. * Fix up notebook. * Fix formatting. * Fix mypy errors. * Fix serialization. * Use symbolicfloat. * Fix bug. * Add notebook test. * Add missing semicolon. * Fix test. --------- Co-authored-by: Matthew Harrigan <mpharrigan@google.com>
1 parent 7b602c1 commit 32ad347

11 files changed

Lines changed: 336 additions & 40 deletions

qualtran/bloqs/chemistry/trotter/hubbard/hopping.py

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
from functools import cached_property
1616
from typing import Set, TYPE_CHECKING, Union
1717

18-
import sympy
1918
from attrs import frozen
2019

2120
from qualtran import Bloq, bloq_example, BloqDocSpec, QAny, QBit, Register, Signature
2221
from qualtran.bloqs.basic_gates import Rz
2322
from qualtran.bloqs.qft.two_bit_ffft import TwoBitFFFT
23+
from qualtran.bloqs.rotations.hamming_weight_phasing import HammingWeightPhasing
24+
from qualtran.symbolics import SymbolicFloat, SymbolicInt
2425

2526
if TYPE_CHECKING:
2627
from qualtran.resource_counting import BloqCountT, SympySymbolAllocator
@@ -66,8 +67,8 @@ class HoppingPlaquette(Bloq):
6667
page 13 Eq. E4 and E5 (Appendix E)
6768
"""
6869

69-
kappa: Union[float, sympy.Expr]
70-
eps: Union[float, sympy.Expr] = 1e-9
70+
kappa: Union[SymbolicFloat]
71+
eps: Union[SymbolicFloat] = 1e-9
7172

7273
@cached_property
7374
def signature(self) -> Signature:
@@ -109,10 +110,10 @@ class HoppingTile(Bloq):
109110
see Eq. 21 and App E.
110111
"""
111112

112-
length: Union[int, sympy.Expr]
113-
angle: Union[float, sympy.Expr]
113+
length: Union[SymbolicInt]
114+
angle: Union[SymbolicFloat]
114115
tau: float = 1.0
115-
eps: Union[float, sympy.Expr] = 1e-9
116+
eps: Union[SymbolicFloat] = 1e-9
116117
pink: bool = True
117118

118119
def __attrs_post_init__(self):
@@ -134,6 +135,68 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
134135
}
135136

136137

138+
@frozen
139+
class HoppingTileHWP(HoppingTile):
140+
r"""Bloq implementing a "tile" of the one-body hopping unitary using Hamming weight phasing.
141+
142+
Implements the unitary
143+
$$
144+
e^{i H_h^{x}} = \prod_{k\sigma} e^{i t H_h^{x(k,\sigma)}}
145+
$$
146+
for a particular choise of of plaquette hamiltonian $H_h^x$, where $x$ = pink or gold.
147+
148+
Each plaquette Hamiltonian can be split into $L^2/4$ commuting terms. Each
149+
term can be implemented using the 4-qubit HoppingPlaquette above. The
150+
HoppingPlaquette bloq contains 2 arbitrary rotations which are flanked by Clifford operations.
151+
All of the rotations within a HoppingTile have the same angle so we can use
152+
HammingWeightPhaseing to reduce the number of T gates that need to be
153+
synthesized. Accounting for spin there are then $2 \times 2 \times L^2/4$
154+
arbitrary rotations in each Tile, but only $L^2/2$ of them can be applied
155+
at the same time due to the $e^{iXX} e^{iYY}$ circuit not permitting parallel $R_z$ gates.
156+
157+
Unlike in the HoppingTile implementation where we can neatly factor
158+
everything into sub-bloqs, here we would need to apply any clifford and F
159+
gates first in parallel then bulk apply the rotations in parallel using
160+
HammingWeightPhasing and then apply another layer of clifford and F gates.
161+
162+
Args:
163+
length: Lattice side length L.
164+
angle: The prefactor scaling the Hopping hamiltonian in the unitary (`t` above).
165+
This should contain any relevant prefactors including the time step
166+
and any splitting coefficients.
167+
tau: The Hopping hamiltonian parameter. Typically the hubbard model is
168+
defined relative to $\tau$ so it's defaulted to 1.
169+
eps: The precision of the single qubit rotations.
170+
pink: The colour of the plaquette.
171+
172+
Registers:
173+
system: The system register of size 2 `length`.
174+
175+
References:
176+
[Early fault-tolerant simulations of the Hubbard model](
177+
https://arxiv.org/abs/2012.09238) see Eq. 21 and App E.
178+
"""
179+
180+
def short_name(self) -> str:
181+
l = 'p' if self.pink else 'g'
182+
return f'H_h^{l}(HWP)'
183+
184+
def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
185+
# Page 5, text after Eq. 22. There are L^2 / 4 plaquettes of a given colour and x2 for spin.
186+
# Each plaquette contributes 4 TwoBitFFFT gates and two arbitrary rotations.
187+
# We use Hamming weight phasing to apply all 2 * L^2/4 (two for spin
188+
# here) for both of these rotations.
189+
return {
190+
(TwoBitFFFT(0, 1, self.eps), 4 * self.length**2 // 2),
191+
(
192+
HammingWeightPhasing(
193+
2 * self.length**2 // 4, self.tau * self.angle, eps=self.eps
194+
),
195+
2,
196+
),
197+
}
198+
199+
137200
@bloq_example
138201
def _hopping_tile() -> HoppingTile:
139202
length = 8
@@ -162,3 +225,18 @@ def _plaquette() -> HoppingPlaquette:
162225
import_line='from qualtran.bloqs.chemistry.trotter.hubbard.hopping import HoppingPlaquette',
163226
examples=(_plaquette,),
164227
)
228+
229+
230+
@bloq_example
231+
def _hopping_tile_hwp() -> HoppingTileHWP:
232+
length = 8
233+
angle = 0.15
234+
hopping_tile_hwp = HoppingTileHWP(length, angle)
235+
return hopping_tile_hwp
236+
237+
238+
_HOPPING_TILE_HWP_DOC = BloqDocSpec(
239+
bloq_cls=HoppingTileHWP,
240+
import_line='from qualtran.bloqs.chemistry.trotter.hubbard.hopping import HoppingTileHWP',
241+
examples=(_hopping_tile_hwp,),
242+
)

qualtran/bloqs/chemistry/trotter/hubbard/hopping_test.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
from qualtran import Bloq
15-
from qualtran.bloqs.basic_gates import Rz, TGate
16-
from qualtran.bloqs.chemistry.trotter.hubbard.hopping import _hopping_tile, _plaquette
15+
from qualtran.bloqs.basic_gates import Rz, TGate, ZPowGate
16+
from qualtran.bloqs.chemistry.trotter.hubbard.hopping import (
17+
_hopping_tile,
18+
_hopping_tile_hwp,
19+
_plaquette,
20+
)
1721
from qualtran.bloqs.util_bloqs import ArbitraryClifford
1822
from qualtran.resource_counting.generalizers import PHI
1923

@@ -27,8 +31,10 @@ def test_hopping_plaquette(bloq_autotester):
2731

2832

2933
def catch_rotations(bloq) -> Bloq:
30-
if isinstance(bloq, Rz):
31-
if abs(float(bloq.angle)) < 1e-12:
34+
if isinstance(bloq, (Rz, ZPowGate)):
35+
if isinstance(bloq, ZPowGate):
36+
return Rz(angle=PHI)
37+
elif abs(float(bloq.angle)) < 1e-12:
3238
return ArbitraryClifford(1)
3339
else:
3440
return Rz(angle=PHI)
@@ -40,3 +46,13 @@ def test_hopping_tile_t_counts():
4046
_, counts = bloq.call_graph(generalizer=catch_rotations)
4147
assert counts[TGate()] == 8 * bloq.length**2 // 2
4248
assert counts[Rz(PHI)] == 2 * bloq.length**2 // 2
49+
50+
51+
def test_hopping_tile_hwp_t_counts():
52+
bloq = _hopping_tile_hwp()
53+
_, counts = bloq.call_graph(generalizer=catch_rotations)
54+
n_rot_par = bloq.length**2 // 2
55+
assert counts[Rz(PHI)] == 2 * n_rot_par.bit_length()
56+
assert counts[TGate()] == 8 * bloq.length**2 // 2 + 2 * 4 * (
57+
n_rot_par - n_rot_par.bit_count()
58+
)

qualtran/bloqs/chemistry/trotter/hubbard/interaction.py

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
from functools import cached_property
1717
from typing import Set, TYPE_CHECKING, Union
1818

19-
import sympy
2019
from attrs import frozen
2120

2221
from qualtran import Bloq, bloq_example, BloqDocSpec, QAny, Register, Signature
2322
from qualtran.bloqs.basic_gates import Rz
23+
from qualtran.bloqs.rotations.hamming_weight_phasing import HammingWeightPhasing
24+
from qualtran.symbolics import SymbolicFloat, SymbolicInt
2425

2526
if TYPE_CHECKING:
2627
from qualtran.resource_counting import BloqCountT, SympySymbolAllocator
@@ -52,10 +53,10 @@ class Interaction(Bloq):
5253
Eq. 6 page 2 and page 13 paragraph 1.
5354
"""
5455

55-
length: Union[int, sympy.Expr]
56-
angle: Union[float, sympy.Expr]
57-
hubb_u: Union[float, sympy.Expr]
58-
eps: Union[float, sympy.Expr] = 1e-9
56+
length: Union[SymbolicInt]
57+
angle: Union[SymbolicFloat]
58+
hubb_u: Union[SymbolicFloat]
59+
eps: Union[SymbolicFloat] = 1e-9
5960

6061
@cached_property
6162
def signature(self) -> Signature:
@@ -66,6 +67,50 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
6667
return {(Rz(angle=self.angle * self.hubb_u, eps=self.eps), self.length**2)}
6768

6869

70+
@frozen
71+
class InteractionHWP(Bloq):
72+
r"""Bloq implementing the hubbard U part of the hamiltonian using Hamming weight phasing.
73+
74+
Specifically:
75+
$$
76+
U_I = e^{i t H_I}
77+
$$
78+
which can be implemented using equal angle single-qubit Z rotations.
79+
80+
Each interaction term can be implemented using a e^{iZZ} gate, which
81+
decomposes into a single Rz gate flanked by cliffords. There are L^2
82+
equal angle rotations in total all of which may be applied in parallel using HWP.
83+
84+
Args:
85+
length: Lattice length L.
86+
angle: The rotation angle for unitary.
87+
hubb_u: The hubbard U parameter.
88+
eps: The precision for single qubit rotations.
89+
90+
Registers:
91+
system: The system register of size 2 `length`.
92+
93+
References:
94+
[Early fault-tolerant simulations of the Hubbard model](
95+
https://arxiv.org/abs/2012.09238) Eq. page 13 paragraph 1, and page
96+
14 paragraph 3 right column. The apply 2 batches of $L^2/2$ rotations.
97+
"""
98+
99+
length: Union[SymbolicInt]
100+
angle: Union[SymbolicFloat]
101+
hubb_u: Union[SymbolicFloat]
102+
eps: Union[SymbolicFloat] = 1e-9
103+
104+
@cached_property
105+
def signature(self) -> Signature:
106+
return Signature([Register('system', QAny(self.length), shape=(2,))])
107+
108+
def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
109+
return {
110+
(HammingWeightPhasing(self.length**2 // 2, self.angle * self.hubb_u, eps=self.eps), 2)
111+
}
112+
113+
69114
@bloq_example
70115
def _interaction() -> Interaction:
71116
length = 8
@@ -80,3 +125,19 @@ def _interaction() -> Interaction:
80125
import_line='from qualtran.bloqs.chemistry.trotter.hubbard.interaction import Interaction',
81126
examples=(_interaction,),
82127
)
128+
129+
130+
@bloq_example
131+
def _interaction_hwp() -> InteractionHWP:
132+
length = 8
133+
angle = 0.5
134+
hubb_u = 4.0
135+
interaction = InteractionHWP(length, angle, hubb_u)
136+
return interaction
137+
138+
139+
_INTERACTION_HWP_DOC = BloqDocSpec(
140+
bloq_cls=InteractionHWP,
141+
import_line='from qualtran.bloqs.chemistry.trotter.hubbard.interaction import InteractionHWP',
142+
examples=(_interaction_hwp,),
143+
)

qualtran/bloqs/chemistry/trotter/hubbard/interaction_test.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,30 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
from qualtran.bloqs.chemistry.trotter.hubbard.interaction import _interaction
14+
from qualtran.bloqs.basic_gates import Rz, TGate
15+
from qualtran.bloqs.chemistry.trotter.hubbard.hopping_test import catch_rotations
16+
from qualtran.bloqs.chemistry.trotter.hubbard.interaction import _interaction, _interaction_hwp
17+
from qualtran.resource_counting.generalizers import PHI
1518

1619

1720
def test_hopping_tile(bloq_autotester):
1821
bloq_autotester(_interaction)
22+
23+
24+
def test_interaction_hwp(bloq_autotester):
25+
bloq_autotester(_interaction_hwp)
26+
27+
28+
def test_interaction_hwp_bloq_counts():
29+
bloq = _interaction_hwp()
30+
_, counts = bloq.call_graph(generalizer=catch_rotations)
31+
n_rot_par = bloq.length**2 // 2
32+
assert counts[Rz(PHI)] == 2 * n_rot_par.bit_length()
33+
assert counts[TGate()] == 2 * 4 * (n_rot_par - n_rot_par.bit_count())
34+
35+
36+
def test_interaction_bloq_counts():
37+
bloq = _interaction()
38+
_, counts = bloq.call_graph(generalizer=catch_rotations)
39+
n_rot = bloq.length**2
40+
assert counts[Rz(PHI)] == n_rot

qualtran/bloqs/chemistry/trotter/hubbard/qpe_cost_optimization.ipynb

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,59 @@
446446
"print(rf\"T_{{opt}} = {t_opt:4.3e}\")"
447447
]
448448
},
449+
{
450+
"cell_type": "markdown",
451+
"metadata": {},
452+
"source": [
453+
"### Using Hamming Weight Phasing "
454+
]
455+
},
456+
{
457+
"cell_type": "markdown",
458+
"metadata": {},
459+
"source": [
460+
"We can compare this cost to that found using Hamming weight phasing for the equal angle rotations. "
461+
]
462+
},
463+
{
464+
"cell_type": "code",
465+
"execution_count": null,
466+
"metadata": {},
467+
"outputs": [],
468+
"source": [
469+
"from qualtran.bloqs.chemistry.trotter.hubbard.trotter_step import build_plaq_hwp_unitary_second_order_suzuki\n",
470+
"trotter_step_hwp = build_plaq_hwp_unitary_second_order_suzuki(length, hubb_u, timestep, eps=1e-10)\n",
471+
"n_t_hwp, n_rot_hwp = t_and_rot_counts_from_sigma(trotter_step_hwp.call_graph(generalizer=catch_rotations)[1])\n",
472+
"print(f\"N_T(HWP) = {n_t_hwp} vs {(3*length**2 // 2)*8}\")\n",
473+
"print(f\"N_rot(HWP) = {n_rot_hwp} vs {(3 * length**2 + 2*length**2)}\")\n",
474+
"delta_ht_opt, delta_ts_opt, delta_pe_opt, t_opt = minimize_linesearch(n_rot_hwp, n_t_hwp, xi_bound, prod_ord)\n",
475+
"print(rf\"T_{{OPT}}(HWP) = {t_opt:4.3e}\")\n",
476+
"# The reference counts Toffolis as 2 T gates, we count them as 4.\n",
477+
"print(rf\"Reference value = {1.7e6 + 4 * 1.8e5:4.3e}\")"
478+
]
479+
},
480+
{
481+
"cell_type": "markdown",
482+
"metadata": {},
483+
"source": [
484+
"Our value is slightly higher as we included all terms in the Trotter step. The reference only counts one layer of interaction gates. Let's correct for that."
485+
]
486+
},
487+
{
488+
"cell_type": "code",
489+
"execution_count": null,
490+
"metadata": {},
491+
"outputs": [],
492+
"source": [
493+
"trotter_step_hwp = build_plaq_hwp_unitary_second_order_suzuki(length, hubb_u, timestep, eps=1e-10, strip_layer=True)\n",
494+
"n_t_hwp, n_rot_hwp = t_and_rot_counts_from_sigma(trotter_step_hwp.call_graph(generalizer=catch_rotations)[1])\n",
495+
"print(f\"N_T(HWP) = {n_t_hwp}\")\n",
496+
"print(f\"N_rot(HWP) = {n_rot_hwp}\")\n",
497+
"delta_ht_opt, delta_ts_opt, delta_pe_opt, t_opt = minimize_linesearch(n_rot_hwp, n_t_hwp, xi_bound, prod_ord)\n",
498+
"print(rf\"T_{{OPT}}(HWP) = {t_opt:4.3e}\")\n",
499+
"print(rf\"Reference value = {1.7e6 + 4 * 1.8e5:4.3e}\")"
500+
]
501+
},
449502
{
450503
"cell_type": "markdown",
451504
"metadata": {},

0 commit comments

Comments
 (0)