Skip to content

Commit 37b9fc7

Browse files
committed
call graph for GQSP
1 parent 3af0315 commit 37b9fc7

File tree

3 files changed

+115
-55
lines changed

3 files changed

+115
-55
lines changed

qualtran/bloqs/generalized_qsp.ipynb

+33-25
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
"import scipy\n",
2020
"from qualtran.drawing import show_bloq\n",
2121
"\n",
22+
"import qualtran\n",
2223
"from qualtran.bloqs.qubitization_walk_operator_test import get_walk_operator_for_1d_Ising_model\n",
2324
"from qualtran.bloqs.qubitization_walk_operator import QubitizationWalkOperator\n",
25+
"from qualtran.cirq_interop import CirqGateAsBloq\n",
2426
"\n",
25-
"from qualtran.bloqs.generalized_qsp import GeneralizedQSP"
27+
"from qualtran.bloqs.generalized_qsp import GeneralizedQSP, HamiltonianSimulationByGQSP\n",
28+
"from qualtran.bloqs.generalized_qsp_test import RandomGate"
2629
]
2730
},
2831
{
@@ -60,18 +63,26 @@
6063
"metadata": {},
6164
"outputs": [],
6265
"source": [
63-
"pU = GeneralizedQSP(U, (0.5, 0.5))\n",
66+
"pU = GeneralizedQSP(U, (0.5, 0.5), (-0.5, 0.5))\n",
6467
"show_bloq(pU.decompose_bloq())"
6568
]
6669
},
70+
{
71+
"cell_type": "markdown",
72+
"id": "935a03f7-5843-4b11-abe6-5eb9048c0ab5",
73+
"metadata": {},
74+
"source": [
75+
"There is also a method that directly computes $Q$ from $P$:"
76+
]
77+
},
6778
{
6879
"cell_type": "code",
6980
"execution_count": null,
7081
"id": "78cd3857297f092b",
7182
"metadata": {},
7283
"outputs": [],
7384
"source": [
74-
"pU = GeneralizedQSP(U, (0.5, 0, 0.5))\n",
85+
"pU = GeneralizedQSP.from_qsp_polynomial(U, (0.5, 0, 0.5))\n",
7586
"show_bloq(pU.decompose_bloq())"
7687
]
7788
},
@@ -93,7 +104,7 @@
93104
"metadata": {},
94105
"outputs": [],
95106
"source": [
96-
"pU = GeneralizedQSP(U, (0.5, 0, 0.5), negative_power=1)\n",
107+
"pU = GeneralizedQSP.from_qsp_polynomial(U, (0.5, 0, 0.5), negative_power=1)\n",
97108
"show_bloq(pU.decompose_bloq())"
98109
]
99110
},
@@ -118,6 +129,8 @@
118129
"\n",
119130
"$$e^{it\\cos\\theta} = \\sum_{n = -\\infty}^{\\infty} i^n J_n(t) (e^{i\\theta})^n$$\n",
120131
"\n",
132+
"where $J_n$ is the $n$-th [Bessel function of the first kind](https://en.wikipedia.org/wiki/Bessel_function#Bessel_functions_of_the_first_kind), which is provided by `scipy.special.jv`.\n",
133+
"\n",
121134
"We can cutoff at $d = O(t + \\log(1/\\epsilon) / \\log\\log(1/\\epsilon))$ to get an $\\epsilon$-approximation (Theorem 7):\n",
122135
"\n",
123136
"$$P[t](z) = \\sum_{n = -d}^d i^n J_n(t) z^n$$\n",
@@ -132,37 +145,32 @@
132145
{
133146
"cell_type": "code",
134147
"execution_count": null,
135-
"id": "721cd78c6d6e60c4",
136-
"metadata": {
137-
"jupyter": {
138-
"outputs_hidden": false
139-
}
140-
},
148+
"id": "c4ccd9c8-92ce-4b53-92bc-722f263a32ad",
149+
"metadata": {},
141150
"outputs": [],
142151
"source": [
143-
"def hamiltonian_simulation_by_gqsp(\n",
144-
" W: QubitizationWalkOperator, t: float, *, precision=1e-5, max_degree=None\n",
145-
") -> GeneralizedQSP:\n",
146-
" degree = t + 3 * np.log(1/precision) / np.log(np.log(1/precision))\n",
147-
" if max_degree is not None:\n",
148-
" degree = min(max_degree, degree)\n",
149-
" degree = int(np.round(degree))\n",
150-
"\n",
151-
" coeff_indices = np.arange(-degree, degree + 1)\n",
152-
" approx_cos = 1j**coeff_indices * scipy.special.jv(coeff_indices, t)\n",
153-
"\n",
154-
" return GeneralizedQSP(W, approx_cos, np.zeros(2*degree + 1), negative_power=degree)"
152+
"W_e_iHt = HamiltonianSimulationByGQSP(U, t=5, alpha=1, precision=1e-3)\n",
153+
"\n",
154+
"def decomp_pred(inst):\n",
155+
" from qualtran.bloqs.select_and_prepare import PrepareOracle, SelectOracle\n",
156+
"\n",
157+
" return any(isinstance(inst.bloq, cls) for cls in [HamiltonianSimulationByGQSP, GeneralizedQSP, SelectOracle, PrepareOracle])\n",
158+
" \n",
159+
"show_bloq(\n",
160+
" W_e_iHt\n",
161+
" .decompose_bloq()\n",
162+
" .flatten(pred=decomp_pred)\n",
163+
")"
155164
]
156165
},
157166
{
158167
"cell_type": "code",
159168
"execution_count": null,
160-
"id": "c4ccd9c8-92ce-4b53-92bc-722f263a32ad",
169+
"id": "c6627cb6-446d-4f76-b727-8ed2f67bc382",
161170
"metadata": {},
162171
"outputs": [],
163172
"source": [
164-
"W_e_iHt = hamiltonian_simulation_by_gqsp(U, 5)\n",
165-
"show_bloq(W_e_iHt.decompose_bloq())"
173+
"W_e_iHt.call_graph()"
166174
]
167175
}
168176
],

qualtran/bloqs/generalized_qsp.py

+76-28
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,26 @@
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 Optional, Sequence, Tuple
15+
from typing import Dict, Sequence, Set, Tuple, TYPE_CHECKING
1616

1717
import cirq
1818
import numpy as np
19+
import scipy
1920
from attrs import field, frozen
2021
from numpy.polynomial import Polynomial
2122
from numpy.typing import NDArray
2223

23-
from qualtran import GateWithRegisters, QBit, Register, Signature, Adjoint
24+
from qualtran import Bloq, GateWithRegisters, QBit, Register, Signature
25+
from qualtran.bloqs.basic_gates import Ry, ZPowGate
26+
from qualtran.bloqs.qubitization_walk_operator import QubitizationWalkOperator
2427

25-
# TODO refactor using Bloq instead of cirq-ft infra
28+
if TYPE_CHECKING:
29+
from qualtran import BloqBuilder, SoquetT
30+
from qualtran.resource_counting import BloqCountT, SympySymbolAllocator
2631

2732

2833
@frozen
29-
class SU2RotationGate(GateWithRegisters):
34+
class SU2RotationGate(Bloq):
3035
theta: float
3136
phi: float
3237
lambd: float
@@ -65,15 +70,12 @@ def rotation_matrix(self):
6570
]
6671
)
6772

68-
def decompose_from_registers(
69-
self, *, context: cirq.DecompositionContext, q: NDArray[cirq.Qid]
70-
) -> cirq.OP_TREE:
71-
qubit = q[0]
72-
73-
yield cirq.Rz(rads=np.pi - self.lambd).on(qubit)
74-
yield cirq.Ry(rads=2 * self.theta).on(qubit)
75-
yield cirq.Rz(rads=-self.phi).on(qubit)
76-
yield cirq.GlobalPhaseGate(np.exp(1j * (np.pi + self.lambd + self.phi) / 2)).on()
73+
def build_composite_bloq(self, bb: 'BloqBuilder', q: 'SoquetT') -> Dict[str, 'SoquetT']:
74+
q = bb.add(ZPowGate(exponent=2, global_shift=0.5), q=q)
75+
q = bb.add(ZPowGate(exponent=1 - self.lambd / np.pi, global_shift=-1), q=q)
76+
q = bb.add(Ry(angle=2 * self.theta), q=q)
77+
q = bb.add(ZPowGate(exponent=-self.phi / np.pi, global_shift=-1), q=q)
78+
return {'q': q}
7779

7880

7981
def qsp_complementary_polynomial(
@@ -261,7 +263,7 @@ class GeneralizedQSP(GateWithRegisters):
261263
to obtain $U^{-k} P(U)$. (Theorem 6)
262264
This gate represents the following unitary:
263265
264-
$$ \begin{bmatrix} U^{-k} P(U) & \cdot \\ \cdot & \cdot \end{bmatrix} $$
266+
$$ \begin{bmatrix} U^{-k} P(U) & \cdot \\ Q(U) & \cdot \end{bmatrix} $$
265267
266268
The polynomial $P$ must satisfy:
267269
$\abs{P(e^{i \theta})}^2 \le 1$ for every $\theta \in \mathbb{R}$.
@@ -280,18 +282,19 @@ class GeneralizedQSP(GateWithRegisters):
280282

281283
U: GateWithRegisters
282284
P: Sequence[complex]
283-
_precomputed_Q: Optional[Sequence[complex]] = None
285+
Q: Sequence[complex]
284286
negative_power: int = field(default=0, kw_only=True)
285287

286288
@cached_property
287289
def signature(self) -> Signature:
288290
return Signature([Register('signal', QBit()), *self.U.signature])
289291

290-
@cached_property
291-
def Q(self):
292-
if self._precomputed_Q is not None:
293-
return self._precomputed_Q
294-
return qsp_complementary_polynomial(self.P)
292+
@staticmethod
293+
def from_qsp_polynomial(
294+
U: GateWithRegisters, P: Sequence[complex], *, negative_power: int = 0
295+
) -> 'GeneralizedQSP':
296+
Q = qsp_complementary_polynomial(P)
297+
return GeneralizedQSP(U, P, Q, negative_power=negative_power)
295298

296299
@cached_property
297300
def _qsp_phases(self) -> Tuple[Sequence[float], Sequence[float], float]:
@@ -309,16 +312,24 @@ def _phi(self) -> Sequence[float]:
309312
def _lambda(self) -> float:
310313
return self._qsp_phases[2]
311314

315+
@cached_property
316+
def signal_rotations(self) -> NDArray[SU2RotationGate]:
317+
return np.array(
318+
[
319+
SU2RotationGate(theta, phi, self._lambda if i == 0 else 0)
320+
for i, (theta, phi) in enumerate(zip(self._theta, self._phi))
321+
]
322+
)
323+
312324
def decompose_from_registers(
313325
self, *, context: cirq.DecompositionContext, signal, **quregs: NDArray[cirq.Qid]
314326
) -> cirq.OP_TREE:
315-
assert len(signal) == 1
316327
signal_qubit = signal[0]
317328

318329
num_inverse_applications = self.negative_power
319330

320-
yield SU2RotationGate(self._theta[0], self._phi[0], self._lambda).on(signal_qubit)
321-
for theta, phi in zip(self._theta[1:], self._phi[1:]):
331+
yield self.signal_rotations[0].on(signal_qubit)
332+
for signal_rotation in self.signal_rotations[1:]:
322333
# TODO debug controlled adjoint bloq serialization
323334
# if num_inverse_applications > 0:
324335
# # apply C-U^\dagger
@@ -327,12 +338,49 @@ def decompose_from_registers(
327338
# else:
328339
# apply C[0]-U
329340
yield self.U.on_registers(**quregs).controlled_by(signal_qubit, control_values=[0])
330-
yield SU2RotationGate(theta, phi, 0).on(signal_qubit)
341+
yield signal_rotation.on(signal_qubit)
331342

332-
while num_inverse_applications > 0:
343+
for _ in range(num_inverse_applications):
333344
yield self.U.adjoint().on_registers(**quregs)
334-
num_inverse_applications -= 1
335345

336346
def __hash__(self):
337-
# TODO hash properly
338-
return hash((self.U, *list(self.P), *list(self.Q), self.negative_power))
347+
return hash((self.U, *self.signal_rotations, self.negative_power))
348+
349+
def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
350+
return {(self.U, max(len(self.P), self.negative_power))} | {
351+
(rotation, 1) for rotation in self.signal_rotations
352+
}
353+
354+
355+
@frozen
356+
class HamiltonianSimulationByGQSP(GateWithRegisters):
357+
WalkOperator: QubitizationWalkOperator
358+
t: float = field(kw_only=True)
359+
alpha: float = field(kw_only=True)
360+
precision: float = field(kw_only=True)
361+
362+
@cached_property
363+
def degree(self) -> int:
364+
log_precision = np.log(1 / self.precision)
365+
degree = self.t * self.alpha + log_precision / np.log(log_precision)
366+
return int(np.ceil(degree))
367+
368+
@cached_property
369+
def gqsp(self) -> GeneralizedQSP:
370+
coeff_indices = np.arange(-self.degree, self.degree + 1)
371+
approx_cos = 1j**coeff_indices * scipy.special.jv(coeff_indices, self.t * self.alpha)
372+
373+
return GeneralizedQSP(
374+
self.WalkOperator, approx_cos, np.zeros(2 * self.degree + 1), negative_power=self.degree
375+
)
376+
377+
@cached_property
378+
def signature(self) -> 'Signature':
379+
return self.gqsp.signature
380+
381+
def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str, 'SoquetT']:
382+
# TODO call prepare oracle
383+
# soqs |= bb.add_d(self.WalkOperator.prepare, selection=soqs['selection'])
384+
soqs = bb.add_d(self.gqsp, **soqs)
385+
# soqs |= bb.add_d(self.WalkOperator.prepare.adjoint(), selection=soqs['selection'])
386+
return soqs

qualtran/bloqs/generalized_qsp_test.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
qsp_phase_factors,
3131
SU2RotationGate,
3232
)
33+
from qualtran.cirq_interop import BloqAsCirqGate
3334

3435

3536
def assert_angles_almost_equal(
@@ -50,7 +51,7 @@ def test_cirq_decompose_SU2_to_single_qubit_pauli_gates():
5051
gate = SU2RotationGate(theta, phi, lambd)
5152

5253
expected = gate.rotation_matrix
53-
actual = cirq.unitary(gate)
54+
actual = cirq.unitary(BloqAsCirqGate(gate))
5455
np.testing.assert_allclose(actual, expected)
5556

5657

@@ -151,7 +152,10 @@ def verify_generalized_qsp(
151152
):
152153
input_unitary = cirq.unitary(U)
153154
N = input_unitary.shape[0]
154-
gqsp_U = GeneralizedQSP(U, P, Q)
155+
if Q is None:
156+
gqsp_U = GeneralizedQSP.from_qsp_polynomial(U, P)
157+
else:
158+
gqsp_U = GeneralizedQSP(U, P, Q)
155159
result_unitary = cirq.unitary(gqsp_U)
156160

157161
expected_top_left = evaluate_polynomial_of_matrix(P, input_unitary)

0 commit comments

Comments
 (0)