Skip to content

Commit 49fe6b8

Browse files
fdmalonempharrigan
andauthored
Add walk operator example for sparse chem ham. (#882)
* Add walk operator example for sparse chem ham. * Add missing module. * Add QPE example for sparse chem. * Use random.normal not random.random for fake integrals. * Refactor walk operator. * Fix format. * Fix QPE example and add notebook. * Fix lint. * Add to which bloq to skip for serialization test. * Add more detail to call graph. * Tidy up prepare. * Format. * WIP. * [skip ci] WIP. * Fix errors. Working cost. Stash. Fix errors. * Fix pylint. * Fix notebooks. * Remove commented out code. * Use attrs. * Remove duplicate file. * Use new control mixin. --------- Co-authored-by: Matthew Harrigan <mpharrigan@google.com>
1 parent 433a288 commit 49fe6b8

15 files changed

Lines changed: 589 additions & 154 deletions

dev_tools/autogenerate-bloqs-notebooks-v2.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import qualtran.bloqs.chemistry.pbc.first_quantization.select_uv
6565
import qualtran.bloqs.chemistry.sf.single_factorization
6666
import qualtran.bloqs.chemistry.sparse.prepare
67+
import qualtran.bloqs.chemistry.sparse.walk_operator
6768
import qualtran.bloqs.chemistry.thc.prepare
6869
import qualtran.bloqs.chemistry.trotter.grid_ham.inverse_sqrt
6970
import qualtran.bloqs.chemistry.trotter.grid_ham.qvr
@@ -427,6 +428,11 @@
427428
module=qualtran.bloqs.qubitization_walk_operator,
428429
bloq_specs=[qualtran.bloqs.qubitization_walk_operator._QUBITIZATION_WALK_DOC],
429430
),
431+
NotebookSpecV2(
432+
title='Qubitization Phase Estimation',
433+
module=qualtran.bloqs.phase_estimation.qubitization_qpe,
434+
bloq_specs=[qualtran.bloqs.phase_estimation.qubitization_qpe._QUBITIZATION_QPE_DOC],
435+
),
430436
]
431437

432438
# --------------------------------------------------------------------------

docs/bloqs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ Bloqs Library
7777
phase_estimation/lp_resource_state.ipynb
7878
phase_estimation/text_book_qpe.ipynb
7979
qubitization_walk_operator.ipynb
80+
phase_estimation/qubitization_qpe.ipynb
8081

8182
.. toctree::
8283
:maxdepth: 2

qualtran/bloqs/chemistry/sparse/prepare.py

Lines changed: 58 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,24 @@
1717
from functools import cached_property
1818
from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING
1919

20+
import attrs
2021
import numpy as np
21-
from attrs import evolve, field, frozen
2222
from numpy.typing import NDArray
2323

2424
from qualtran import (
2525
bloq_example,
2626
BloqBuilder,
2727
BloqDocSpec,
2828
BoundedQUInt,
29+
CtrlSpec,
2930
QAny,
3031
QBit,
3132
Register,
3233
Soquet,
3334
SoquetT,
3435
)
3536
from qualtran.bloqs.arithmetic.comparison import LessThanEqual
36-
from qualtran.bloqs.basic_gates import CSwap, Hadamard, Toffoli
37+
from qualtran.bloqs.basic_gates import CSwap, Hadamard
3738
from qualtran.bloqs.basic_gates.on_each import OnEach
3839
from qualtran.bloqs.basic_gates.z_basis import ZGate
3940
from qualtran.bloqs.data_loading.select_swap_qrom import find_optimal_log_block_size, SelectSwapQROM
@@ -112,7 +113,7 @@ def _add(p_indx: int, q_indx: int, r_indx: int, s_indx: int):
112113
return np.concatenate((tpq_indx, pqrs_indx_np)), np.concatenate((tpq_sparse, eris_eight_np))
113114

114115

115-
@frozen
116+
@attrs.frozen
116117
class PrepareSparse(PrepareOracle):
117118
r"""Prepare oracle for the sparse chemistry Hamiltonian
118119
@@ -163,16 +164,17 @@ class PrepareSparse(PrepareOracle):
163164
[Even More Efficient Quantum Computations of Chemistry Through Tensor
164165
hypercontraction](https://arxiv.org/abs/2011.03494) Eq. A11.
165166
"""
167+
166168
num_spin_orb: int
167169
num_non_zero: int
168170
num_bits_state_prep: int
169-
alt_pqrs: Tuple[Tuple[int, ...], ...] = field(repr=False)
170-
alt_theta: Tuple[int, ...] = field(repr=False)
171-
alt_one_body: Tuple[int, ...] = field(repr=False)
172-
ind_pqrs: Tuple[Tuple[int, ...], ...] = field(repr=False)
173-
theta: Tuple[int, ...] = field(repr=False)
174-
one_body: Tuple[int, ...] = field(repr=False)
175-
keep: Tuple[int, ...] = field(repr=False)
171+
alt_pqrs: Tuple[Tuple[int, ...], ...] = attrs.field(repr=False)
172+
alt_theta: Tuple[int, ...] = attrs.field(repr=False)
173+
alt_one_body: Tuple[int, ...] = attrs.field(repr=False)
174+
ind_pqrs: Tuple[Tuple[int, ...], ...] = attrs.field(repr=False)
175+
theta: Tuple[int, ...] = attrs.field(repr=False)
176+
one_body: Tuple[int, ...] = attrs.field(repr=False)
177+
keep: Tuple[int, ...] = attrs.field(repr=False)
176178
num_bits_rot_aa: int = 8
177179
is_adjoint: bool = False
178180
qroam_block_size: Optional[int] = None
@@ -226,9 +228,6 @@ def selection_registers(self) -> Tuple[Register, ...]:
226228
Register("flag_1b", BoundedQUInt(1)),
227229
)
228230

229-
def adjoint(self) -> 'Bloq':
230-
return evolve(self, is_adjoint=not self.is_adjoint)
231-
232231
@cached_property
233232
def junk_registers(self) -> Tuple[Register, ...]:
234233
return (
@@ -241,6 +240,9 @@ def junk_registers(self) -> Tuple[Register, ...]:
241240
Register("alt_flag_1b", QBit()),
242241
)
243242

243+
def adjoint(self) -> 'Bloq':
244+
return attrs.evolve(self, is_adjoint=not self.is_adjoint)
245+
244246
@classmethod
245247
def from_hamiltonian_coeffs(
246248
cls,
@@ -281,7 +283,7 @@ def from_hamiltonian_coeffs(
281283
tpq_prime, eris, drop_element_thresh=drop_element_thresh
282284
)
283285
num_non_zero = len(integrals)
284-
alt, keep, mu = preprocess_lcu_coefficients_for_reversible_sampling(
286+
alt, keep, _ = preprocess_lcu_coefficients_for_reversible_sampling(
285287
np.abs(integrals), epsilon=2**-num_bits_state_prep / num_non_zero
286288
)
287289
theta = (1 - np.sign(integrals)) // 2
@@ -305,31 +307,7 @@ def from_hamiltonian_coeffs(
305307
qroam_block_size=qroam_block_size,
306308
)
307309

308-
def build_composite_bloq(
309-
self,
310-
bb: 'BloqBuilder',
311-
d: 'SoquetT',
312-
p: 'SoquetT',
313-
q: 'SoquetT',
314-
r: 'SoquetT',
315-
s: 'SoquetT',
316-
sigma: 'SoquetT',
317-
alpha: 'SoquetT',
318-
beta: 'SoquetT',
319-
rot_aa: 'SoquetT',
320-
swap_pq: 'SoquetT',
321-
swap_rs: 'SoquetT',
322-
swap_pqrs: 'SoquetT',
323-
flag_1b: 'SoquetT',
324-
alt_pqrs: NDArray[Soquet], # type: ignore[type-var]
325-
theta: NDArray[Soquet], # type: ignore[type-var]
326-
keep: 'SoquetT',
327-
less_than: 'SoquetT',
328-
alt_flag_1b: 'SoquetT',
329-
) -> Dict[str, 'SoquetT']:
330-
# 1. Prepare \sum_d |d\rangle
331-
d = bb.add(PrepareUniformSuperposition(self.num_non_zero), target=d)
332-
# 2. QROM the ind_d alt_d values
310+
def build_qrom_bloq(self) -> 'Bloq':
333311
n_n = (self.num_spin_orb // 2 - 1).bit_length()
334312
target_bitsizes = (
335313
(n_n,) * 4 + (1,) * 2 + (n_n,) * 4 + (1,) * 2 + (self.num_bits_state_prep,)
@@ -355,6 +333,34 @@ def build_composite_bloq(
355333
target_bitsizes=target_bitsizes,
356334
block_size=block_size,
357335
)
336+
return qrom
337+
338+
def build_composite_bloq(
339+
self,
340+
bb: 'BloqBuilder',
341+
d: 'SoquetT',
342+
p: 'SoquetT',
343+
q: 'SoquetT',
344+
r: 'SoquetT',
345+
s: 'SoquetT',
346+
sigma: 'SoquetT',
347+
alpha: 'SoquetT',
348+
beta: 'SoquetT',
349+
rot_aa: 'SoquetT',
350+
swap_pq: 'SoquetT',
351+
swap_rs: 'SoquetT',
352+
swap_pqrs: 'SoquetT',
353+
flag_1b: 'SoquetT',
354+
alt_pqrs: NDArray[Soquet], # type: ignore[type-var]
355+
theta: NDArray[Soquet], # type: ignore[type-var]
356+
keep: 'SoquetT',
357+
less_than: 'SoquetT',
358+
alt_flag_1b: 'SoquetT',
359+
) -> Dict[str, 'SoquetT']:
360+
# 1. Prepare \sum_d |d\rangle
361+
d = bb.add(PrepareUniformSuperposition(self.num_non_zero), target=d)
362+
# 2. QROM the ind_d alt_d values
363+
qrom = self.build_qrom_bloq()
358364
(
359365
d,
360366
p,
@@ -392,8 +398,8 @@ def build_composite_bloq(
392398
sigma = bb.add(OnEach(self.num_bits_state_prep, Hadamard()), q=sigma)
393399
keep, sigma, less_than = bb.add(lte_bloq, x=keep, y=sigma, target=less_than)
394400
less_than, theta[1] = bb.add(ZGate().controlled(), ctrl=less_than, q=theta[1])
395-
# TODO: This should be off control
396-
less_than, theta[0] = bb.add(ZGate().controlled(), ctrl=less_than, q=theta[0])
401+
ctrl_spec = CtrlSpec(QBit(), 0b0)
402+
less_than, theta[0] = bb.add(ZGate().controlled(ctrl_spec), ctrl=less_than, q=theta[0])
397403
# swap the ind and alt_pqrs values
398404
# TODO: These swaps are inverted at zero Toffoli cost in the reference.
399405
# The method is to copy all values being swapped before they are swapped. Then
@@ -402,14 +408,13 @@ def build_composite_bloq(
402408
# controlled-phase operations, where the control is the control qubit
403409
# for the controlled swaps, and the targets are the copies of the
404410
# registers.
411+
n_n = (self.num_spin_orb // 2 - 1).bit_length()
405412
less_than, alt_pqrs[0], p = bb.add(CSwap(n_n), ctrl=less_than, x=alt_pqrs[0], y=p)
406413
less_than, alt_pqrs[1], q = bb.add(CSwap(n_n), ctrl=less_than, x=alt_pqrs[1], y=q)
407414
less_than, alt_pqrs[2], r = bb.add(CSwap(n_n), ctrl=less_than, x=alt_pqrs[2], y=r)
408415
less_than, alt_pqrs[3], s = bb.add(CSwap(n_n), ctrl=less_than, x=alt_pqrs[3], y=s)
409416
# swap the 1b/2b alt values
410-
# less_than, flag_1b, alt_flag_1b = bb.add(CSwap(1), ctrl=less_than, x=flag_1b, y=alt_flag_1b)
411-
# invert the comparator
412-
keep, sigma, less_than = bb.add(lte_bloq, x=keep, y=sigma, target=less_than)
417+
less_than, flag_1b, alt_flag_1b = bb.add(CSwap(1), ctrl=less_than, x=flag_1b, y=alt_flag_1b)
413418
# prepare |+> states for symmetry swaps
414419
swap_pq = bb.add(Hadamard(), q=swap_pq)
415420
swap_rs = bb.add(Hadamard(), q=swap_rs)
@@ -441,48 +446,23 @@ def build_composite_bloq(
441446
}
442447

443448
def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
444-
num_bits_spat = (self.num_spin_orb // 2 - 1).bit_length()
445-
if self.qroam_block_size is None:
446-
target_bitsizes = (
447-
(num_bits_spat,) * 4
448-
+ (1,) * 2
449-
+ (num_bits_spat,) * 4
450-
+ (1,) * 2
451-
+ (self.num_bits_state_prep,)
452-
)
453-
block_size = 2 ** find_optimal_log_block_size(self.num_non_zero, sum(target_bitsizes))
454-
else:
455-
block_size = self.qroam_block_size
456-
if self.is_adjoint:
457-
num_toff_qrom = int(np.ceil(self.num_non_zero / block_size)) + block_size # A15
458-
else:
459-
output_size = self.num_bits_state_prep + 8 * num_bits_spat + 4
460-
num_toff_qrom = int(np.ceil(self.num_non_zero / block_size)) + output_size * (
461-
block_size - 1
462-
) # A14
463-
qrom_cost = (Toffoli(), num_toff_qrom)
464-
if self.is_adjoint:
465-
return {(PrepareUniformSuperposition(self.num_non_zero), 1), qrom_cost}
466-
swap_cost_state_prep = (CSwap(num_bits_spat), 4 + 4) # 2. pg 39
467-
ineq_cost_state_prep = (Toffoli(), (self.num_bits_state_prep + 1)) # 2. pg 39
468-
469449
return {
470450
(PrepareUniformSuperposition(self.num_non_zero), 1),
471-
qrom_cost,
472-
swap_cost_state_prep,
473-
ineq_cost_state_prep,
451+
(self.build_qrom_bloq(), 1),
452+
(OnEach(self.num_bits_state_prep, Hadamard()), 1),
453+
(Hadamard(), 3),
454+
(CSwap(1), 1),
455+
(CSwap((self.num_spin_orb // 2 - 1).bit_length()), 4 + 4),
456+
(LessThanEqual(self.num_bits_state_prep, self.num_bits_state_prep), 1),
474457
}
475458

476459

477460
@bloq_example
478461
def _prep_sparse() -> PrepareSparse:
462+
from qualtran.bloqs.chemistry.sparse.prepare_test import build_random_test_integrals
463+
479464
num_spin_orb = 4
480-
tpq = np.random.random((num_spin_orb // 2, num_spin_orb // 2))
481-
tpq = 0.5 * (tpq + tpq.T)
482-
eris = np.random.random((num_spin_orb // 2,) * 4)
483-
eris += np.transpose(eris, (0, 1, 3, 2))
484-
eris += np.transpose(eris, (1, 0, 2, 3))
485-
eris += np.transpose(eris, (2, 3, 0, 1))
465+
tpq, eris = build_random_test_integrals(num_spin_orb // 2)
486466
prep_sparse = PrepareSparse.from_hamiltonian_coeffs(num_spin_orb, tpq, eris)
487467
return prep_sparse
488468

qualtran/bloqs/chemistry/sparse/prepare_test.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,32 @@ def test_decompose_bloq_counts():
5858
prep = _prep_sparse()
5959
cost_decomp = prep.decompose_bloq().call_graph()[1][TGate()]
6060
cost_call = prep.call_graph()[1][TGate()]
61-
assert cost_decomp != cost_call
61+
assert cost_decomp == cost_call
6262

6363

64-
@pytest.mark.parametrize('sparsity', [0.0, 1e-2])
65-
@pytest.mark.parametrize('nb', [4, 5, 6, 7])
66-
def test_get_sparse_inputs_from_integrals(nb, sparsity):
67-
tpq = np.random.random((nb, nb))
64+
def build_random_test_integrals(nb: int):
65+
"""Build random one- and two-electron integrals of the correct symmetry.
66+
67+
Args:
68+
nb: The number of spatial orbitals.
69+
70+
Returns:
71+
tpq: The one-body matrix elements.
72+
eris: Chemist ERIs (pq|rs).
73+
"""
74+
tpq = np.random.normal(size=(nb, nb))
6875
tpq = 0.5 * (tpq + tpq.T)
6976
eris = np.random.normal(scale=4, size=(nb,) * 4)
7077
eris += np.transpose(eris, (0, 1, 3, 2))
7178
eris += np.transpose(eris, (1, 0, 2, 3))
7279
eris += np.transpose(eris, (2, 3, 0, 1))
80+
return tpq, eris
81+
82+
83+
@pytest.mark.parametrize('sparsity', [0.0, 1e-2])
84+
@pytest.mark.parametrize('nb', [4, 5, 6, 7])
85+
def test_get_sparse_inputs_from_integrals(nb, sparsity):
86+
tpq, eris = build_random_test_integrals(nb)
7387
pqrs_indx, eris_eight = get_sparse_inputs_from_integrals(
7488
tpq, eris, drop_element_thresh=sparsity
7589
)

0 commit comments

Comments
 (0)