-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Export to quantify_scheduler's Schedule format
- Add ZYZ and ABC decompositions - Add exporter - Dependency on quantify_scheduler only when the Python version is compatible - Test with mocks
- Loading branch information
Pablo Le Hénaff
committed
Feb 9, 2024
1 parent
00821a8
commit 9829dac
Showing
16 changed files
with
4,193 additions
and
322 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
from opensquirrel.default_gates import cnot, ry, rz | ||
from opensquirrel.identity_filter import filter_out_identities | ||
from opensquirrel.replacer import Decomposer | ||
from opensquirrel.squirrel_ir import BlochSphereRotation, ControlledGate, Float, Gate | ||
from opensquirrel.zyz_decomposer import ZYZDecomposer, theta123 | ||
|
||
|
||
class CNOTDecomposer(Decomposer): | ||
""" | ||
Decomposes 2-qubit controlled unitary gates to CNOT + rz/ry. | ||
Applying single-qubit gate fusion after this pass might be beneficial. | ||
""" | ||
|
||
@staticmethod | ||
def decompose(g: Gate) -> [Gate]: | ||
if not isinstance(g, ControlledGate): | ||
# Do nothing. | ||
# Decomposing MatrixGate is more mathematically involved. | ||
return [g] | ||
|
||
if not isinstance(g.target_gate, BlochSphereRotation): | ||
# Do nothing. | ||
# ControlledGate's with 2+ control qubits are ignored. | ||
return [g] | ||
|
||
# Perform ZYZ decomposition on the target gate. | ||
# This gives us an ABC decomposition (U = AXBXC, ABC = I) of the target gate. | ||
theta0, theta1, theta2 = theta123(g.target_gate.angle, g.target_gate.axis) | ||
|
||
target_qubit = g.target_gate.qubit | ||
|
||
A = [ry(q=target_qubit, theta=Float(theta1 / 2)), rz(q=target_qubit, theta=Float(theta2))] | ||
|
||
B = [ | ||
rz(q=target_qubit, theta=Float(-(theta0 + theta2) / 2)), | ||
ry(q=target_qubit, theta=Float(-theta1 / 2)), | ||
] | ||
|
||
C = [ | ||
rz(q=target_qubit, theta=Float((theta0 - theta2) / 2)), | ||
] | ||
|
||
return filter_out_identities( | ||
C | ||
+ [cnot(control=g.control_qubit, target=target_qubit)] | ||
+ B | ||
+ [cnot(control=g.control_qubit, target=target_qubit)] | ||
+ A | ||
+ [rz(q=g.control_qubit, theta=Float(g.target_gate.phase))] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import math | ||
|
||
from opensquirrel.common import ATOL | ||
from opensquirrel.default_gates import x, z | ||
from opensquirrel.squirrel_ir import ( | ||
BlochSphereRotation, | ||
ControlledGate, | ||
MatrixGate, | ||
Qubit, | ||
SquirrelIR, | ||
SquirrelIRVisitor, | ||
) | ||
|
||
try: | ||
import quantify_scheduler | ||
import quantify_scheduler.operations.gate_library as quantify_scheduler_gates | ||
except Exception as e: | ||
pass | ||
|
||
|
||
class _ScheduleCreator(SquirrelIRVisitor): | ||
def _get_qubit_string(self, q: Qubit) -> str: | ||
return f"{self.qubit_register_name}[{q.index}]" | ||
|
||
def __init__(self, qubit_register_name: str): | ||
self.qubit_register_name = qubit_register_name | ||
self.schedule = quantify_scheduler.Schedule(f"Exported OpenSquirrel circuit") | ||
|
||
def visit_bloch_sphere_rotation(self, g: BlochSphereRotation): | ||
if abs(g.axis[2]) < ATOL: | ||
# Rxy rotation. | ||
theta: float = g.angle | ||
phi: float = math.atan2(g.axis[1], g.axis[0]) | ||
self.schedule.add(quantify_scheduler_gates.Rxy(theta=theta, phi=phi, qubit=self._get_qubit_string(g.qubit))) | ||
return | ||
|
||
if abs(g.axis[0]) < ATOL and abs(g.axis[1]) < ATOL: | ||
# Rz rotation. | ||
self.schedule.add(quantify_scheduler_gates.Rz(theta=g.angle, qubit=self._get_qubit_string(g.qubit))) | ||
return | ||
|
||
raise Exception( | ||
"Cannot export circuit: it contains unsupported gates - decompose them to the " | ||
"Quantify-scheduler gate set first" | ||
) | ||
|
||
def visit_matrix_gate(self, g: MatrixGate): | ||
raise Exception("Unimplemented: would require a complicated mathematical decomposition...") | ||
|
||
def visit_controlled_gate(self, g: ControlledGate): | ||
if not isinstance(g.target_gate, BlochSphereRotation): | ||
raise Exception( | ||
"Cannot export circuit: it contains unsupported gates - decompose them to the " | ||
"Quantify-scheduler gate set first" | ||
) | ||
|
||
if g.target_gate == x(g.target_gate.qubit): | ||
self.schedule.add( | ||
quantify_scheduler_gates.CNOT( | ||
qC=self._get_qubit_string(g.control_qubit), qT=self._get_qubit_string(g.target_gate.qubit) | ||
) | ||
) | ||
return | ||
|
||
if g.target_gate == z(g.target_gate.qubit): | ||
self.schedule.add( | ||
quantify_scheduler_gates.CZ( | ||
qC=self._get_qubit_string(g.control_qubit), qT=self._get_qubit_string(g.target_gate.qubit) | ||
) | ||
) | ||
return | ||
|
||
raise Exception( | ||
"Cannot export circuit: it contains unsupported gates - decompose them to the " | ||
"Quantify-scheduler gate set first" | ||
) | ||
|
||
|
||
def export(squirrel_ir: SquirrelIR): | ||
if "quantify_scheduler" not in globals(): | ||
|
||
class QuantifySchedulerNotInstalled: | ||
def __getattr__(self, attr_name): | ||
raise Exception("quantify-scheduler is not installed, or cannot be installed on your system") | ||
|
||
global quantify_scheduler | ||
quantify_scheduler = QuantifySchedulerNotInstalled() | ||
global quantify_scheduler_gates | ||
quantify_scheduler_gates = QuantifySchedulerNotInstalled() | ||
|
||
schedule_creator = _ScheduleCreator(squirrel_ir.qubit_register_name) | ||
squirrel_ir.accept(schedule_creator) | ||
return schedule_creator.schedule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from enum import Enum | ||
|
||
|
||
class ExportFormat(Enum): | ||
QUANTIFY_SCHEDULER = 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from opensquirrel.squirrel_ir import Gate | ||
|
||
|
||
def filter_out_identities(l: [Gate]): | ||
return [g for g in l if not g.is_identity()] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import opensquirrel.mock_quantify_scheduler.operations as operations | ||
|
||
|
||
class Schedule: | ||
def __init__(self, name): | ||
self.name = name | ||
self.gates = [] | ||
|
||
def add(self, g): | ||
self.gates.append(g) | ||
|
||
def __repr__(self): | ||
return f"Schedule(name={self.name}, gates={self.gates})" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import opensquirrel.mock_quantify_scheduler.operations.gate_library |
14 changes: 14 additions & 0 deletions
14
opensquirrel/mock_quantify_scheduler/operations/gate_library.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
def Rxy(theta: float, phi: float, qubit: str): | ||
return f"Rxy(theta={theta}, phi={phi}, qubit={qubit})" | ||
|
||
|
||
def Rz(theta: float, qubit: str): | ||
return f"Rz({theta}, qubit={qubit})" | ||
|
||
|
||
def CNOT(qC: str, qT: str): | ||
return f"Rz(qC={qC}, qT={qT})" | ||
|
||
|
||
def CZ(qC: str, qT: str): | ||
return f"CZ(qC={qC}, qT={qT})" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import math | ||
from typing import Tuple | ||
|
||
from opensquirrel.common import ATOL | ||
from opensquirrel.default_gates import ry, rz | ||
from opensquirrel.identity_filter import filter_out_identities | ||
from opensquirrel.replacer import Decomposer | ||
from opensquirrel.squirrel_ir import BlochSphereRotation, Float, Gate | ||
|
||
|
||
def theta123(alpha: float, axis: Tuple[float, float, float]): | ||
""" | ||
Gives the angles used in the Z-Y-Z decomposition of the Bloch sphere rotation | ||
caracterized by a rotation around `axis` of angle `alpha`. | ||
Parameters: | ||
alpha: angle of the Bloch sphere rotation | ||
axis: _normalized_ axis of the Bloch sphere rotation | ||
Returns: | ||
a triple (theta1, theta2, theta3) corresponding to the decomposition of the | ||
arbitrary Bloch sphere rotation into U = rz(theta3) ry(theta2) rz(theta1) | ||
""" | ||
|
||
nx, ny, nz = axis | ||
|
||
assert abs(nx**2 + ny**2 + nz**2 - 1) < ATOL, "Axis needs to be normalized" | ||
|
||
assert -math.pi + ATOL < alpha <= math.pi + ATOL, "Angle needs to be normalized" | ||
|
||
if abs(alpha - math.pi) < ATOL: | ||
# alpha == pi, math.tan(alpha / 2) is not defined. | ||
p = math.pi | ||
theta2 = 2 * math.acos(nz) | ||
|
||
if abs(nz - 1) < ATOL or abs(nz + 1) < ATOL: | ||
m = p # This can be anything, but setting m = p means theta3 == 0, which is better for gate count. | ||
else: | ||
m = 2 * math.acos(ny / math.sqrt(1 - nz**2)) | ||
|
||
else: | ||
p = 2 * math.atan2(nz * math.sin(alpha / 2), math.cos(alpha / 2)) | ||
theta2 = 2 * math.acos(math.cos(alpha / 2) * math.sqrt(1 + (nz * math.tan(alpha / 2)) ** 2)) | ||
theta2 = math.copysign(theta2, alpha) | ||
|
||
if abs(math.sin(theta2 / 2)) < ATOL: | ||
m = p # This can be anything, but setting m = p means theta3 == 0, which is better for gate count. | ||
else: | ||
m = 2 * math.acos(ny * math.sin(alpha / 2) / math.sin(theta2 / 2)) | ||
|
||
theta1 = (p + m) / 2 | ||
|
||
theta3 = p - theta1 | ||
|
||
return theta1, theta2, theta3 | ||
|
||
|
||
class ZYZDecomposer(Decomposer): | ||
@staticmethod | ||
def decompose(g: Gate) -> [Gate]: | ||
if not isinstance(g, BlochSphereRotation): | ||
# Only decompose single-qubit gates. | ||
return [g] | ||
|
||
theta1, theta2, theta3 = theta123(g.angle, g.axis) | ||
|
||
z1 = rz(g.qubit, Float(theta1)) | ||
y = ry(g.qubit, Float(theta2)) | ||
z2 = rz(g.qubit, Float(theta3)) | ||
|
||
# Note: written like this, the decomposition doesn't preserve the global phase, which is fine | ||
# since the global phase is a physically irrelevant artifact of the mathematical | ||
# model we use to describe the quantum system. | ||
|
||
# Should we want to preserve it, we would need to use a raw BlochSphereRotation, which would then | ||
# be an anonymous gate in the resulting decomposed circuit: | ||
# z2 = BlochSphereRotation(qubit=g.qubit, angle=theta3, axis=(0, 0, 1), phase = g.phase) | ||
|
||
return filter_out_identities([z1, y, z2]) |
Oops, something went wrong.