Skip to content

Commit e6d6e01

Browse files
authored
QubitizationWalkOperator add attribute: sum of LCU coefficients (#890)
* `QubitizationWalkOperator` attribute: `sum_of_lcu_coefficients` defaults to `None` * add property to `PrepareOracle` instead * ising: return true LCU coeffs * fix symbolic ham sim example * walk op docstrings * fix docstring nits
1 parent 480f863 commit e6d6e01

14 files changed

Lines changed: 100 additions & 56 deletions

qualtran/bloqs/chemistry/ising/hamiltonian.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,5 +90,4 @@ def get_1d_ising_lcu_coeffs(
9090
spins = cirq.LineQubit.range(n_spins)
9191
ham = get_1d_ising_hamiltonian(spins, j_zz_strength, gamma_x_strength)
9292
coeffs = np.array([term.coefficient.real for term in ham])
93-
lcu_coeffs = coeffs / np.sum(abs(coeffs))
94-
return lcu_coeffs
93+
return coeffs

qualtran/bloqs/for_testing/qubitization_walk_test.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
from functools import cached_property
15-
from typing import Tuple
15+
from typing import Optional, Tuple
1616

1717
import attrs
1818
import cirq
1919
import scipy
20-
from numpy._typing import NDArray
20+
from numpy.typing import NDArray
2121

2222
from qualtran import Signature
2323
from qualtran.bloqs.multiplexers.select_pauli_lcu import SelectPauliLCU
2424
from qualtran.bloqs.qubitization_walk_operator import QubitizationWalkOperator
2525
from qualtran.bloqs.select_and_prepare import PrepareOracle
2626
from qualtran.bloqs.state_preparation import PrepareUniformSuperposition
27+
from qualtran.resource_counting.symbolic_counting_utils import SymbolicFloat
2728

2829

2930
@attrs.frozen
@@ -32,6 +33,7 @@ class PrepareUniformSuperpositionTest(PrepareOracle):
3233
cvs: Tuple[int, ...] = attrs.field(
3334
converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=()
3435
)
36+
qlambda: Optional[float] = None
3537

3638
@cached_property
3739
def selection_registers(self) -> Signature:
@@ -41,6 +43,10 @@ def selection_registers(self) -> Signature:
4143
def junk_registers(self) -> Signature:
4244
return Signature.build()
4345

46+
@cached_property
47+
def l1_norm_of_coeffs(self) -> Optional['SymbolicFloat']:
48+
return self.qlambda
49+
4450
def decompose_from_registers(
4551
self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] # type: ignore[type-var]
4652
) -> cirq.OP_TREE:
@@ -57,7 +63,7 @@ def get_uniform_pauli_qubitized_walk(target_bitsize: int):
5763
ham_dps = [ps.dense(q) for ps in ham]
5864

5965
assert scipy.linalg.ishermitian(ham.matrix())
60-
prepare = PrepareUniformSuperpositionTest(len(ham_coeff))
66+
prepare = PrepareUniformSuperpositionTest(len(ham_coeff), qlambda=sum(ham_coeff))
6167
select = SelectPauliLCU(
6268
(len(ham_coeff) - 1).bit_length(), select_unitaries=ham_dps, target_bitsize=target_bitsize
6369
)

qualtran/bloqs/for_testing/random_select_and_prepare.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ def __attrs_post_init__(self):
5656
def selection_registers(self) -> tuple[Register, ...]:
5757
return (Register('selection', BoundedQUInt(bitsize=self.U.bitsize)),)
5858

59+
@property
60+
def l1_norm_of_coeffs(self) -> float:
61+
return 1.0
62+
5963
@classmethod
6064
def random(cls, bitsize: int, *, random_state: np.random.RandomState):
6165
"""Generate a random unitary s.t. the first column has all real amplitudes"""

qualtran/bloqs/hamiltonian_simulation/hamiltonian_simulation_by_gqsp.ipynb

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@
8585
"#### Parameters\n",
8686
" - `walk_operator`: qubitization walk operator of $H$ constructed from SELECT and PREPARE oracles.\n",
8787
" - `t`: time to simulate the Hamiltonian, i.e. $e^{-iHt}$\n",
88-
" - `alpha`: the $1$-norm of the coefficients of the unitaries comprising the Hamiltonian $H$.\n",
8988
" - `precision`: the precision $\\epsilon$ to approximate $e^{it\\cos\\theta}$ to a polynomial.\n"
9089
]
9190
},
@@ -123,9 +122,7 @@
123122
"from qualtran.bloqs.hubbard_model import get_walk_operator_for_hubbard_model\n",
124123
"\n",
125124
"walk_op = get_walk_operator_for_hubbard_model(2, 2, 1, 1)\n",
126-
"hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(\n",
127-
" walk_op, t=5, alpha=1, precision=1e-7\n",
128-
")"
125+
"hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=5, precision=1e-7)"
129126
]
130127
},
131128
{
@@ -142,10 +139,9 @@
142139
"from qualtran.bloqs.hubbard_model import get_walk_operator_for_hubbard_model\n",
143140
"\n",
144141
"walk_op = get_walk_operator_for_hubbard_model(2, 2, 1, 1)\n",
145-
"t, alpha, inv_eps = sympy.symbols(\"t alpha N\")\n",
146-
"symbolic_hamsim_by_gqsp = HamiltonianSimulationByGQSP(\n",
147-
" walk_op, t=t, alpha=alpha, precision=1 / inv_eps\n",
148-
")"
142+
"\n",
143+
"t, inv_eps = sympy.symbols(\"t N\")\n",
144+
"symbolic_hamsim_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=t, precision=1 / inv_eps)"
149145
]
150146
},
151147
{
@@ -191,8 +187,8 @@
191187
},
192188
"outputs": [],
193189
"source": [
194-
"from qualtran.resource_counting.generalizers import generalize_rotation_angle, ignore_split_join, ignore_alloc_free\n",
195-
"hubbard_time_evolution_by_gqsp_g, hubbard_time_evolution_by_gqsp_sigma = hubbard_time_evolution_by_gqsp.call_graph(generalizer=[ignore_split_join, ignore_alloc_free, generalize_rotation_angle])\n",
190+
"from qualtran.resource_counting.generalizers import ignore_split_join\n",
191+
"hubbard_time_evolution_by_gqsp_g, hubbard_time_evolution_by_gqsp_sigma = hubbard_time_evolution_by_gqsp.call_graph(max_depth=1, generalizer=ignore_split_join)\n",
196192
"show_call_graph(hubbard_time_evolution_by_gqsp_g)\n",
197193
"show_counts_sigma(hubbard_time_evolution_by_gqsp_sigma)"
198194
]

qualtran/bloqs/hamiltonian_simulation/hamiltonian_simulation_by_gqsp.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,26 @@ class HamiltonianSimulationByGQSP(GateWithRegisters):
8888
Args:
8989
walk_operator: qubitization walk operator of $H$ constructed from SELECT and PREPARE oracles.
9090
t: time to simulate the Hamiltonian, i.e. $e^{-iHt}$
91-
alpha: the $1$-norm of the coefficients of the unitaries comprising the Hamiltonian $H$.
9291
precision: the precision $\epsilon$ to approximate $e^{it\cos\theta}$ to a polynomial.
9392
"""
9493

9594
walk_operator: QubitizationWalkOperator
9695
t: SymbolicFloat = field(kw_only=True)
97-
alpha: SymbolicFloat = field(kw_only=True)
9896
precision: SymbolicFloat = field(kw_only=True)
9997

98+
def __attrs_post_init__(self):
99+
if self.walk_operator.sum_of_lcu_coefficients is None:
100+
raise ValueError(
101+
f"Missing attribute `sum_of_ham_coeffs` for {self.walk_operator}, cannot implement Hamiltonian Simulation"
102+
)
103+
100104
def is_symbolic(self):
101105
return is_symbolic(self.t, self.alpha, self.precision)
102106

107+
@property
108+
def alpha(self):
109+
return self.walk_operator.sum_of_lcu_coefficients
110+
103111
@cached_property
104112
def degree(self) -> SymbolicInt:
105113
r"""degree of the polynomial to approximate the function e^{it\cos(\theta)}"""
@@ -179,9 +187,7 @@ def _hubbard_time_evolution_by_gqsp() -> HamiltonianSimulationByGQSP:
179187
from qualtran.bloqs.hubbard_model import get_walk_operator_for_hubbard_model
180188

181189
walk_op = get_walk_operator_for_hubbard_model(2, 2, 1, 1)
182-
hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(
183-
walk_op, t=5, alpha=1, precision=1e-7
184-
)
190+
hubbard_time_evolution_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=5, precision=1e-7)
185191
return hubbard_time_evolution_by_gqsp
186192

187193

@@ -192,10 +198,9 @@ def _symbolic_hamsim_by_gqsp() -> HamiltonianSimulationByGQSP:
192198
from qualtran.bloqs.hubbard_model import get_walk_operator_for_hubbard_model
193199

194200
walk_op = get_walk_operator_for_hubbard_model(2, 2, 1, 1)
195-
t, alpha, inv_eps = sympy.symbols("t alpha N")
196-
symbolic_hamsim_by_gqsp = HamiltonianSimulationByGQSP(
197-
walk_op, t=t, alpha=alpha, precision=1 / inv_eps
198-
)
201+
202+
t, inv_eps = sympy.symbols("t N")
203+
symbolic_hamsim_by_gqsp = HamiltonianSimulationByGQSP(walk_op, t=t, precision=1 / inv_eps)
199204
return symbolic_hamsim_by_gqsp
200205

201206

qualtran/bloqs/hamiltonian_simulation/hamiltonian_simulation_by_gqsp_test.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
check_polynomial_pair_on_random_points_on_unit_circle,
2626
verify_generalized_qsp,
2727
)
28-
from qualtran.bloqs.qubitization_walk_operator import _walk_op, QubitizationWalkOperator
28+
from qualtran.bloqs.qubitization_walk_operator import QubitizationWalkOperator
2929

3030
from .hamiltonian_simulation_by_gqsp import (
3131
_hubbard_time_evolution_by_gqsp,
@@ -52,10 +52,11 @@ def test_generalized_qsp_with_exp_cos_approx_on_random_unitaries(
5252
bitsize: int, t: float, precision: float
5353
):
5454
random_state = np.random.RandomState(42)
55+
W, _ = random_qubitization_walk_operator(1, 1, random_state=random_state)
5556

5657
for _ in range(5):
5758
U = MatrixGate.random(bitsize, random_state=random_state)
58-
gqsp = HamiltonianSimulationByGQSP(_walk_op, t=t, alpha=1, precision=precision).gqsp
59+
gqsp = HamiltonianSimulationByGQSP(W, t=t, precision=precision).gqsp
5960
P, Q = gqsp.P, gqsp.Q
6061

6162
check_polynomial_pair_on_random_points_on_unit_circle(
@@ -65,16 +66,11 @@ def test_generalized_qsp_with_exp_cos_approx_on_random_unitaries(
6566

6667

6768
def verify_hamiltonian_simulation_by_gqsp(
68-
W: QubitizationWalkOperator,
69-
H: NDArray[np.complex_],
70-
*,
71-
t: float,
72-
alpha: float,
73-
precision: float,
69+
W: QubitizationWalkOperator, H: NDArray[np.complex_], *, t: float, precision: float
7470
):
7571
N = H.shape[0]
7672

77-
W_e_iHt = HamiltonianSimulationByGQSP(W, t=t, alpha=alpha, precision=precision)
73+
W_e_iHt = HamiltonianSimulationByGQSP(W, t=t, precision=precision)
7874
result_unitary = cirq.unitary(W_e_iHt)
7975

8076
expected_top_left = scipy.linalg.expm(-1j * H * t)
@@ -96,4 +92,4 @@ def test_hamiltonian_simulation_by_gqsp(
9692
W, H = random_qubitization_walk_operator(
9793
select_bitsize, target_bitsize, random_state=random_state
9894
)
99-
verify_hamiltonian_simulation_by_gqsp(W, H.matrix(), t=t, alpha=1, precision=precision)
95+
verify_hamiltonian_simulation_by_gqsp(W, H.matrix(), t=t, precision=precision)

qualtran/bloqs/hubbard_model.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
See the documentation for `PrepareHubbard` and `SelectHubbard` for details.
4848
"""
4949
from functools import cached_property
50-
from typing import Collection, Optional, Sequence, Tuple, Union
50+
from typing import Collection, Optional, Sequence, Tuple, TYPE_CHECKING, Union
5151

5252
import attrs
5353
import cirq
@@ -67,6 +67,9 @@
6767
PrepareUniformSuperposition,
6868
)
6969

70+
if TYPE_CHECKING:
71+
from qualtran.resource_counting.symbolic_counting_utils import SymbolicFloat
72+
7073

7174
@attrs.frozen
7275
class SelectHubbard(SelectOracle):
@@ -309,6 +312,13 @@ def selection_registers(self) -> Tuple[Register, ...]:
309312
def junk_registers(self) -> Tuple[Register, ...]:
310313
return (Register('temp', QAny(2)),)
311314

315+
@cached_property
316+
def l1_norm_of_coeffs(self) -> 'SymbolicFloat':
317+
# https://arxiv.org/abs/1805.03662v2 equation 60
318+
N = self.x_dim * self.y_dim * 2
319+
qlambda = 2 * N * self.t + (N * self.mu) // 2
320+
return qlambda
321+
312322
@cached_property
313323
def signature(self) -> Signature:
314324
return Signature([*self.selection_registers, *self.junk_registers])
@@ -321,8 +331,7 @@ def decompose_from_registers(
321331
temp = quregs['temp']
322332

323333
N = self.x_dim * self.y_dim * 2
324-
qlambda = 2 * N * self.t + (N * self.mu) // 2
325-
yield cirq.Ry(rads=2 * np.arccos(np.sqrt(self.t * N / qlambda))).on(*V)
334+
yield cirq.Ry(rads=2 * np.arccos(np.sqrt(self.t * N / self.l1_norm_of_coeffs))).on(*V)
326335
yield cirq.Ry(rads=2 * np.arccos(np.sqrt(1 / 5))).on(*U).controlled_by(*V)
327336
yield PrepareUniformSuperposition(self.x_dim).on_registers(controls=[], target=p_x)
328337
yield PrepareUniformSuperposition(self.y_dim).on_registers(controls=[], target=p_y)
@@ -352,4 +361,5 @@ def get_walk_operator_for_hubbard_model(
352361
) -> 'QubitizationWalkOperator':
353362
select = SelectHubbard(x_dim, y_dim)
354363
prepare = PrepareHubbard(x_dim, y_dim, t, mu)
364+
355365
return QubitizationWalkOperator(select=select, prepare=prepare)

qualtran/bloqs/multiplexers/select_pauli_lcu_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ def test_select_application_to_eigenstates():
172172
# right now we only handle positive interaction term values
173173
ham = get_1d_ising_hamiltonian(target, 1, 1)
174174
dense_pauli_string_hamiltonian = [tt.dense(target) for tt in ham]
175+
qubitization_lambda = sum(xx.coefficient.real for xx in dense_pauli_string_hamiltonian)
175176
# built select with unary iteration gate
176177
bloq = SelectPauliLCU(
177178
selection_bitsize=selection_bitsize,
@@ -187,11 +188,10 @@ def test_select_application_to_eigenstates():
187188
all_qubits = select_circuit.all_qubits()
188189

189190
coeffs = get_1d_ising_lcu_coeffs(num_sites, 1, 1)
190-
prep_circuit = _fake_prepare(np.sqrt(coeffs), selection)
191+
prep_circuit = _fake_prepare(np.sqrt(coeffs / qubitization_lambda), selection)
191192
turn_on_control = cirq.Circuit(cirq.X.on(control))
192193

193194
ising_eigs, ising_wfns = np.linalg.eigh(ham.matrix())
194-
qubitization_lambda = sum(xx.coefficient.real for xx in dense_pauli_string_hamiltonian)
195195
for iw_idx, ie in enumerate(ising_eigs):
196196
eigenstate_prep = cirq.Circuit()
197197
eigenstate_prep.append(

qualtran/bloqs/qubitization_walk_operator.ipynb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,16 @@
3838
"## `QubitizationWalkOperator`\n",
3939
"Constructs a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE.\n",
4040
"\n",
41+
"For a Hamiltonian $H = \\sum_l w_l H_l$ (s.t. $w_l > 0$ and $H_l$ are unitaries),\n",
4142
"Constructs a Szegedy quantum walk operator $W = R_{L} . SELECT$, which is a product of\n",
4243
"two reflections $R_{L} = (2|L><L| - I)$ and $SELECT=\\sum_{l}|l><l|H_{l}$.\n",
4344
"\n",
4445
"The action of $W$ partitions the Hilbert space into a direct sum of two-dimensional irreducible\n",
4546
"vector spaces. For an arbitrary eigenstate $|k>$ of $H$ with eigenvalue $E_k$, $|\\ell>|k>$ and\n",
4647
"an orthogonal state $\\phi_{k}$ span the irreducible two-dimensional space that $|\\ell>|k>$ is\n",
4748
"in under the action of $W$. In this space, $W$ implements a Pauli-Y rotation by an angle of\n",
48-
"$-2arccos(E_{k} / \\lambda)$ s.t. $W = e^{i arccos(E_k / \\lambda) Y}$.\n",
49+
"$-2arccos(E_{k} / \\lambda)$ s.t. $W = e^{i arccos(E_k / \\lambda) Y}$,\n",
50+
"where $\\lambda = \\sum_l w_l$.\n",
4951
"\n",
5052
"Thus, the walk operator $W$ encodes the spectrum of $H$ as a function of eigenphases of $W$\n",
5153
"s.t. $spectrum(H) = \\lambda cos(arg(spectrum(W)))$ where $arg(e^{i\\phi}) = \\phi$.\n",

qualtran/bloqs/qubitization_walk_operator.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,16 @@
3535
class QubitizationWalkOperator(GateWithRegisters):
3636
r"""Constructs a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE.
3737
38+
For a Hamiltonian $H = \sum_l w_l H_l$ (s.t. $w_l > 0$ and $H_l$ are unitaries),
3839
Constructs a Szegedy quantum walk operator $W = R_{L} . SELECT$, which is a product of
3940
two reflections $R_{L} = (2|L><L| - I)$ and $SELECT=\sum_{l}|l><l|H_{l}$.
4041
4142
The action of $W$ partitions the Hilbert space into a direct sum of two-dimensional irreducible
4243
vector spaces. For an arbitrary eigenstate $|k>$ of $H$ with eigenvalue $E_k$, $|\ell>|k>$ and
4344
an orthogonal state $\phi_{k}$ span the irreducible two-dimensional space that $|\ell>|k>$ is
4445
in under the action of $W$. In this space, $W$ implements a Pauli-Y rotation by an angle of
45-
$-2arccos(E_{k} / \lambda)$ s.t. $W = e^{i arccos(E_k / \lambda) Y}$.
46+
$-2arccos(E_{k} / \lambda)$ s.t. $W = e^{i arccos(E_k / \lambda) Y}$,
47+
where $\lambda = \sum_l w_l$.
4648
4749
Thus, the walk operator $W$ encodes the spectrum of $H$ as a function of eigenphases of $W$
4850
s.t. $spectrum(H) = \lambda cos(arg(spectrum(W)))$ where $arg(e^{i\phi}) = \phi$.
@@ -92,6 +94,11 @@ def signature(self) -> Signature:
9294
def reflect(self) -> ReflectionUsingPrepare:
9395
return ReflectionUsingPrepare(self.prepare, control_val=self.control_val, global_phase=-1)
9496

97+
@cached_property
98+
def sum_of_lcu_coefficients(self) -> Optional[float]:
99+
r"""value of $\lambda$, i.e. sum of absolute values of coefficients $w_l$."""
100+
return self.prepare.l1_norm_of_coeffs
101+
95102
def decompose_from_registers(
96103
self,
97104
context: cirq.DecompositionContext,
@@ -132,17 +139,13 @@ def controlled(
132139
):
133140
c_select = self.select.controlled(control_values=control_values)
134141
assert isinstance(c_select, SelectOracle)
135-
return QubitizationWalkOperator(
136-
c_select, self.prepare, control_val=control_values[0], power=self.power
137-
)
142+
return attrs.evolve(self, select=c_select, control_val=control_values[0])
138143
raise NotImplementedError(
139144
f'Cannot create a controlled version of {self} with control_values={control_values}.'
140145
)
141146

142147
def with_power(self, new_power: int) -> 'QubitizationWalkOperator':
143-
return QubitizationWalkOperator(
144-
self.select, self.prepare, control_val=self.control_val, power=new_power
145-
)
148+
return attrs.evolve(self, power=new_power)
146149

147150
def __pow__(self, power: int):
148151
return self.with_power(self.power * power)

0 commit comments

Comments
 (0)