Skip to content

Commit ec6bb8b

Browse files
authored
Start of Givens rotation LCU primitives (#866)
* Start of Givens rotation LCU primitives * added counting tests * fix typo * complex givens counts * add license at header * WIP part of fixes format and doc strings and bloq_examples * format * parameterized testing * format doc string * Correct Chem Rotation Toffoli costs * format and linting changes
1 parent 41c9cc5 commit ec6bb8b

3 files changed

Lines changed: 387 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2023 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
# Copyright 2023 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
r"""The Givens rotation Bloqs help count costs for similarity transforming
15+
fermionic ladder operators to produce linear combinations of fermionic ladder operators.
16+
17+
Following notation from Reference [1] we note that a single
18+
ladder operator can be similarity transformed by a basis rotation to produce a linear
19+
combination of ladder operators
20+
$$
21+
U(Q)a_{q}U(Q)^{\dagger} = \sum_{p}Q_{pq}^{*}a_{p} = \overrightarrow{a}_{q}\\
22+
U(Q)a_{q}^{\dagger}U(Q)^{\dagger} = \sum_{p}Q_{pq}a_{p}^{\dagger} =
23+
\overrightarrow{a}_{q}^{\dagger}
24+
$$
25+
Each vector of operators can be implemented by a $N$ (size of basis) Givens rotation unitaries as
26+
$$
27+
V_{\overrightarrow{Q}_{q}} a_{0} V_{\overrightarrow{Q}_{q}}^{\dagger} =
28+
\overrightarrow{a}_{q} \\
29+
V_{\overrightarrow{Q}_{q}} a_{0}^{\dagger} V_{\overrightarrow{Q}_{q}}^{\dagger} =
30+
\overrightarrow{a}_{q}^{\dagger}
31+
$$
32+
where
33+
$$
34+
V_{\overrightarrow{Q}_{q}} = V_{n-1,n-2}(0, \phi_{n-1}) V_{n-2, n-3}(\theta_{n-2}, \phi_{n-2})
35+
V_{n-3,n-4}(\theta_{n-2}, \phi_{n-2})...V_{2, 1}(\theta_{1}, \phi_{1})
36+
V_{1, 0}(\theta_{0}, \phi_{0})
37+
$$
38+
with each $V_{ij}(\theta, \phi) = \mathrm{RZ}_{j}(\pi)\mathrm{R}_{ij}(\theta)$.
39+
and $1$ Rz rotation for real valued $\overrightarrow{Q}$.
40+
41+
42+
References:
43+
1. Vera von Burg, Guang Hao Low, Thomas H ̈aner, Damian S. Steiger, Markus Reiher,
44+
Martin Roetteler, and Matthias Troyer, “Quantum computing enhanced computational catalysis,”
45+
Phys. Rev. Res. 3, 033055 (2021).
46+
47+
"""
48+
from functools import cached_property
49+
from typing import Dict
50+
51+
from attrs import frozen
52+
53+
from qualtran import Bloq, bloq_example, BloqBuilder, BloqDocSpec, QBit, QFxp, Signature, SoquetT
54+
from qualtran.bloqs.basic_gates import CNOT, Hadamard, SGate, Toffoli, XGate
55+
from qualtran.bloqs.rotations.phase_gradient import AddIntoPhaseGrad
56+
57+
58+
class RzAddIntoPhaseGradient(AddIntoPhaseGrad):
59+
r"""Temporary controlled adder to give the right complexity for Rz rotations by
60+
phase gradient state addition.
61+
62+
References:
63+
[Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](
64+
https://arxiv.org/abs/2007.07391).
65+
Section II-C: Oracles for phasing by cost function. Appendix A: Addition for controlled
66+
rotations
67+
"""
68+
69+
def bloq_counts(self):
70+
return {Toffoli(): self.x_bitsize - 2}
71+
72+
73+
@frozen
74+
class RealGivensRotationByPhaseGradient(Bloq):
75+
r"""Givens rotation corresponding to a 2-fermion mode transformation generated by
76+
77+
$$
78+
e^{\theta (a_{i}^{\dagger}a_{j} - a_{j}^{\dagger}a_{i})} = e^{i \theta (YX + XY) / 2}
79+
$$
80+
81+
corresponding to the circuit
82+
83+
i: ───X───X───S^-1───X───Rz(theta)───X───X───@───────X───S^-1───
84+
│ │ │ │ │
85+
j: ───S───@───H──────@───Rz(theta)───@───────X───H───@──────────
86+
87+
The rotation is performed by addition into a phase state and the fractional binary for
88+
$\theta$ is stored in an additional register.
89+
90+
The Toffoli cost for this block comes from the cost of two rotations by addition into
91+
the phase gradient state which which is $2(b_{\mathrm{grad}}-2)$ where $b_{\mathrm{grad}}$
92+
is the size of the phasegradient register.
93+
94+
Args:
95+
phasegrad_bitsize int: size of phase gradient which is also the size of the register
96+
representing the binary fraction of the rotation angle
97+
Registers:
98+
target_i: 1st-qubit QBit type register
99+
target_j: 2nd-qubit Qbit type register
100+
rom_data: QFxp data representing fractional binary for real part of rotation
101+
phase_gradient: QFxp data type representing the phase gradient register
102+
103+
References:
104+
[Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization](
105+
https://arxiv.org/abs/2007.07391).
106+
Section II-C: Oracles for phasing by cost function. Appendix A: Addition for controlled
107+
rotations
108+
"""
109+
phasegrad_bitsize: int
110+
111+
@cached_property
112+
def signature(self) -> Signature:
113+
return Signature.build_from_dtypes(
114+
target_i=QBit(),
115+
target_j=QBit(),
116+
rom_data=QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize, signed=False),
117+
phase_gradient=QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize, signed=False),
118+
)
119+
120+
def build_composite_bloq(
121+
self,
122+
bb: BloqBuilder,
123+
target_i: SoquetT,
124+
target_j: SoquetT,
125+
rom_data: SoquetT,
126+
phase_gradient: SoquetT,
127+
) -> Dict[str, SoquetT]:
128+
# set up rz-rotation via phase-gradient state
129+
add_into_phasegrad_gate = RzAddIntoPhaseGradient(
130+
x_bitsize=self.phasegrad_bitsize,
131+
phase_bitsize=self.phasegrad_bitsize,
132+
right_shift=0,
133+
sign=1,
134+
controlled=1,
135+
)
136+
137+
# clifford block
138+
target_i = bb.add(XGate(), q=target_i)
139+
target_j = bb.add(SGate(), q=target_j)
140+
target_j, target_i = bb.add(CNOT(), ctrl=target_j, target=target_i)
141+
target_j = bb.add(Hadamard(), q=target_j)
142+
target_i = bb.add(SGate(is_adjoint=True), q=target_i)
143+
target_j, target_i = bb.add(CNOT(), ctrl=target_j, target=target_i)
144+
145+
# parallel rz (Can probably be improved with single out of place adder into a single ancilla
146+
target_i, rom_data, phase_gradient = bb.add(
147+
add_into_phasegrad_gate, x=rom_data, phase_grad=phase_gradient, ctrl=target_i
148+
)
149+
target_j, rom_data, phase_gradient = bb.add(
150+
add_into_phasegrad_gate, x=rom_data, phase_grad=phase_gradient, ctrl=target_j
151+
)
152+
153+
# clifford block
154+
target_j, target_i = bb.add(CNOT(), ctrl=target_j, target=target_i)
155+
target_i = bb.add(XGate(), q=target_i)
156+
target_i, target_j = bb.add(CNOT(), ctrl=target_i, target=target_j)
157+
target_j = bb.add(Hadamard(), q=target_j)
158+
target_j, target_i = bb.add(CNOT(), ctrl=target_j, target=target_i)
159+
target_i = bb.add(SGate(), q=target_i)
160+
161+
return {
162+
'target_i': target_i,
163+
'target_j': target_j,
164+
'rom_data': rom_data,
165+
'phase_gradient': phase_gradient,
166+
}
167+
168+
169+
@bloq_example
170+
def _real_givens() -> RealGivensRotationByPhaseGradient:
171+
r_givens = RealGivensRotationByPhaseGradient(phasegrad_bitsize=4)
172+
return r_givens
173+
174+
175+
_REAL_GIVENS_DOC = BloqDocSpec(
176+
bloq_cls=RealGivensRotationByPhaseGradient,
177+
import_line='from qualtran.bloqs.chemistry.quad_fermion.givens_bloq import RealGivensRotationByPhaseGradient',
178+
examples=(_real_givens,),
179+
)
180+
181+
182+
@frozen
183+
class ComplexGivensRotationByPhaseGradient(Bloq):
184+
r"""Complex Givens rotation corresponding to a 2-fermion mode transformation generated by
185+
186+
$$
187+
e^{i \phi n_{j}}e^{\theta (a_{i}^{\dagger}a_{j} - a_{j}^{\dagger}a_{i})} = e^{i \phi Z_{j}/2}e^{i \theta (YX + XY) / 2}
188+
$$
189+
190+
corresponding to the circuit
191+
192+
i: ───X───X───S^-1───X───Rz(theta)───X───X───@───────X──S^-1─────
193+
│ │ │ │ │
194+
j: ───S───@───H──────@───Rz(theta)───@───────X───H───@──Rz(phi)──
195+
196+
The rotation is performed by addition into a phase state and the fractional binary for
197+
$\theta$ is stored in an additional register.
198+
199+
Args:
200+
phasegrad_bitsize int: size of phase gradient which is also the size of the register
201+
representing the binary fraction of the rotation angles
202+
Registers:
203+
target_i: 1st-qubit QBit type register
204+
target_j: 2nd-qubit Qbit type register
205+
real_rom_data: QFxp data representing fractional binary for real part of rotation
206+
cplx_rom_data: QFxp data representing fractional binary for imag part of rotation
207+
phase_gradient: QFxp data type representing the phase gradient register
208+
"""
209+
210+
phasegrad_bitsize: int
211+
212+
@cached_property
213+
def signature(self) -> Signature:
214+
return Signature.build_from_dtypes(
215+
target_i=QBit(),
216+
target_j=QBit(),
217+
real_rom_data=QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize, signed=False),
218+
cplx_rom_data=QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize, signed=False),
219+
phase_gradient=QFxp(self.phasegrad_bitsize, self.phasegrad_bitsize, signed=False),
220+
)
221+
222+
def build_composite_bloq(
223+
self,
224+
bb: BloqBuilder,
225+
target_i: SoquetT,
226+
target_j: SoquetT,
227+
real_rom_data: SoquetT,
228+
cplx_rom_data: SoquetT,
229+
phase_gradient: SoquetT,
230+
) -> Dict[str, SoquetT]:
231+
real_givens_gate = RealGivensRotationByPhaseGradient(
232+
phasegrad_bitsize=self.phasegrad_bitsize
233+
)
234+
235+
# real-valued Givens rotation
236+
target_i, target_j, real_rom_data, phase_gradient = bb.add(
237+
real_givens_gate,
238+
target_i=target_i,
239+
target_j=target_j,
240+
rom_data=real_rom_data,
241+
phase_gradient=phase_gradient,
242+
)
243+
244+
# set up rz-rotation on j-bit by phase-gradient state
245+
add_into_phasegrad_gate = RzAddIntoPhaseGradient(
246+
x_bitsize=self.phasegrad_bitsize,
247+
phase_bitsize=self.phasegrad_bitsize,
248+
right_shift=0,
249+
sign=1,
250+
controlled=1,
251+
)
252+
target_j, cplx_rom_data, phase_gradient = bb.add(
253+
add_into_phasegrad_gate, x=cplx_rom_data, phase_grad=phase_gradient, ctrl=target_j
254+
)
255+
return {
256+
'target_i': target_i,
257+
'target_j': target_j,
258+
'real_rom_data': real_rom_data,
259+
'cplx_rom_data': cplx_rom_data,
260+
'phase_gradient': phase_gradient,
261+
}
262+
263+
264+
@bloq_example
265+
def _cplx_givens() -> ComplexGivensRotationByPhaseGradient:
266+
c_givens = ComplexGivensRotationByPhaseGradient(phasegrad_bitsize=4)
267+
return c_givens
268+
269+
270+
_CPLX_GIVENS_DOC = BloqDocSpec(
271+
bloq_cls=ComplexGivensRotationByPhaseGradient,
272+
import_line='from qualtran.bloqs.chemistry.quad_fermion.givens_bloq import ComplexGivensRotationByPhaseGradient',
273+
examples=(_cplx_givens,),
274+
)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2023 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
import cirq
15+
import numpy as np
16+
import openfermion as of
17+
import pytest
18+
from openfermion.circuits.gates import Ryxxy
19+
from scipy.linalg import expm
20+
21+
from qualtran.bloqs.basic_gates import CNOT, Hadamard, SGate, Toffoli, XGate
22+
from qualtran.bloqs.chemistry.quad_fermion.givens_bloq import (
23+
ComplexGivensRotationByPhaseGradient,
24+
RealGivensRotationByPhaseGradient,
25+
RzAddIntoPhaseGradient,
26+
)
27+
28+
29+
def test_circuit_decomposition_givens():
30+
"""
31+
confirm Figure 9 of [Quantum 4, 296 (2020)](https://quantum-journal.org/papers/q-2020-07-16-296/pdf/)
32+
corresponds to Givens rotation in OpenFermion
33+
"""
34+
np.set_printoptions(linewidth=500)
35+
36+
def circuit_construction(eta):
37+
qubits = cirq.LineQubit.range(2)
38+
circuit = cirq.Circuit()
39+
circuit.append(cirq.X.on(qubits[0]))
40+
circuit.append(cirq.S.on(qubits[1]))
41+
42+
circuit.append(cirq.CNOT(qubits[1], qubits[0]))
43+
circuit.append(cirq.H.on(qubits[1]))
44+
circuit.append(cirq.inverse(cirq.S.on(qubits[0])))
45+
46+
circuit.append(cirq.CNOT(qubits[1], qubits[0]))
47+
circuit.append(cirq.rz(eta).on(qubits[0]))
48+
circuit.append(cirq.rz(eta).on(qubits[1]))
49+
circuit.append(cirq.CNOT(qubits[1], qubits[0]))
50+
51+
circuit.append(cirq.X.on(qubits[0]))
52+
circuit.append(cirq.CNOT(qubits[0], qubits[1]))
53+
circuit.append(cirq.H.on(qubits[1]))
54+
circuit.append(cirq.CNOT(qubits[1], qubits[0]))
55+
circuit.append(cirq.inverse(cirq.S.on(qubits[0])))
56+
return circuit
57+
58+
for _ in range(10):
59+
theta = 2 * np.pi / np.random.randn()
60+
ryxxy = cirq.unitary(Ryxxy(theta))
61+
i, j = 0, 1
62+
theta_fop = theta * (
63+
of.FermionOperator(((i, 1), (j, 0))) - of.FermionOperator(((j, 1), (i, 0)))
64+
)
65+
fUtheta = expm(of.get_sparse_operator(of.jordan_wigner(theta_fop), n_qubits=2).todense())
66+
assert np.allclose(fUtheta, ryxxy)
67+
circuit = circuit_construction(theta)
68+
test_unitary = cirq.unitary(circuit)
69+
assert np.isclose(4, abs(np.trace(test_unitary.conj().T @ fUtheta)))
70+
71+
72+
@pytest.mark.parametrize("x_bitsize", [4, 5, 6, 7])
73+
def test_count_t_cliffords(x_bitsize: int):
74+
add_into_phasegrad_gate = RzAddIntoPhaseGradient(
75+
x_bitsize=x_bitsize, phase_bitsize=x_bitsize, right_shift=0, sign=1, controlled=1
76+
)
77+
bloq_counts = add_into_phasegrad_gate.bloq_counts()
78+
# produces Toffoli costs given in chemistry papers
79+
assert bloq_counts[Toffoli()] == (x_bitsize - 2)
80+
81+
gate = RealGivensRotationByPhaseGradient(phasegrad_bitsize=x_bitsize)
82+
gate_counts = gate.bloq_counts()
83+
assert gate_counts[CNOT()] == 5
84+
assert gate_counts[Hadamard()] == 2
85+
assert gate_counts[SGate(is_adjoint=False)] == 2
86+
assert gate_counts[SGate(is_adjoint=True)] == 1
87+
assert gate_counts[XGate()] == 2
88+
assert gate_counts[add_into_phasegrad_gate] == 2
89+
90+
91+
@pytest.mark.parametrize("x_bitsize", [4, 5, 6, 7])
92+
def test_complex_givens_costs(x_bitsize: int):
93+
add_into_phasegrad_gate = RzAddIntoPhaseGradient(
94+
x_bitsize=x_bitsize, phase_bitsize=x_bitsize, right_shift=0, sign=1, controlled=1
95+
)
96+
real_givens_gate = RealGivensRotationByPhaseGradient(phasegrad_bitsize=x_bitsize)
97+
gate = ComplexGivensRotationByPhaseGradient(phasegrad_bitsize=x_bitsize)
98+
gate_counts = gate.bloq_counts()
99+
assert gate_counts[add_into_phasegrad_gate] == 1
100+
assert gate_counts[real_givens_gate] == 1

0 commit comments

Comments
 (0)