Skip to content

Commit

Permalink
qft, iqft and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dakk committed Dec 22, 2023
1 parent baad12f commit 472b0ae
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 20 deletions.
8 changes: 8 additions & 0 deletions qlasskit/qcircuit/exporter_cirq.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import math
from typing import Literal

from . import gates
Expand Down Expand Up @@ -54,6 +55,13 @@ def _decompose_(self, qubits):
)
yield gg(list(map(lambda wx: qubits[w], w)))

elif isinstance(g, gates.Swap):
yield cirq.SWAP(qubits[w[0]], qubits[w[1]])

elif isinstance(g, gates.CP):
cphase_gate = cirq.CZPowGate(exponent=p / math.pi)
yield cphase_gate(qubits[w[0]], qubits[w[1]])

elif hasattr(cirq, g_name):
yield getattr(cirq, g_name)(
*list(map(lambda x: qubits[x], w))
Expand Down
8 changes: 7 additions & 1 deletion qlasskit/qcircuit/exporter_pennylane.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ def export(self, _selfqc, mode: Literal["circuit", "gate"]): # noqa: C901
elif isinstance(g, gates.MCtrl) and isinstance(g.gate, gates.Z):
ops.append(qml.CZ(wires=w))

elif isinstance(g, gates.Swap):
ops.append(qml.SWAP(wires=w))

elif isinstance(g, gates.CP):
ops.append(qml.CPhase(p, wires=w))

elif isinstance(g, gates.Barrier):
pass

Expand All @@ -62,6 +68,6 @@ def export(self, _selfqc, mode: Literal["circuit", "gate"]): # noqa: C901
ops.append(getattr(qml, g_name)(wires=w))

else:
raise Exception(f"not handled {g}")
raise Exception(f"Gate not handled for pennylane exporter: {g_name}")

return qml.tape.QuantumTape(ops)
5 changes: 4 additions & 1 deletion qlasskit/qcircuit/exporter_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ def export(self, _selfqc, mode: Literal["circuit", "gate"]):
continue

qbs = list(map(lambda gq: _selfqc.get_key_by_index(gq), ws))
gate_qasm += f'\t{g.__name__.lower()} {" ".join(qbs)}\n'
if p:
gate_qasm += f'\t{g.__name__.lower()}({p:.2f}) {" ".join(qbs)}\n'
else:
gate_qasm += f'\t{g.__name__.lower()} {" ".join(qbs)}\n'
gate_qasm += "}\n\n"

if mode == "gate":
Expand Down
7 changes: 5 additions & 2 deletions qlasskit/qcircuit/exporter_qiskit.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ def export(self, _selfqc, mode: Literal["circuit", "gate"]): # noqa: C901
pass

elif hasattr(qc, g_name):
getattr(qc, g_name)(*w)
if p:
getattr(qc, g_name)(p, *w)
else:
getattr(qc, g_name)(*w)

else:
raise Exception(f"not handled {g}")
raise Exception(f"Gate not handled for qiskit exporter: {g_name}")

if mode == "gate":
qc.remove_final_measurements()
Expand Down
18 changes: 13 additions & 5 deletions qlasskit/qcircuit/exporter_sympy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@

from typing import Literal

from sympy.physics.quantum.gate import CNOT, CGate, H, X, XGate
from sympy.physics.quantum.gate import CNOT, SWAP, CGate, H, X, XGate
from sympy.physics.quantum.qubit import Qubit

from . import gates
from .exporter import QCircuitExporter

# def cp(theta, q0, q1):
# return CGate((q0,), Z(q1)**(theta))


def mcx(w):
return CGate(tuple(w[0:-1]), XGate(w[-1]))
Expand All @@ -30,29 +33,34 @@ def toffoli(q0, q1, q2):


class SympyExporter(QCircuitExporter):
def export(self, _selfqc, mode: Literal["circuit", "gate"]):
qstate = Qubit("0" * _selfqc.num_qubits) if mode == 'circuit' else None
def export(self, _selfqc, mode: Literal["circuit", "gate"]): # noqa: C901
qstate = Qubit("0" * _selfqc.num_qubits) if mode == "circuit" else None

for g, w, p in _selfqc.gates:
g_name = g.__class__.__name__
ga = None
if isinstance(g, gates.X):
ga = X(w[0])
elif isinstance(g, gates.H):
ga = H(w[0])
elif isinstance(g, gates.CX):
ga = CNOT(w[0], w[1])
# elif isinstance(g, gates.CP):
# ga = cp(p, w[0], w[1])
elif isinstance(g, gates.Swap):
ga = SWAP(w[0], w[1])
elif isinstance(g, gates.CCX) or isinstance(g, gates.MCX):
ga = mcx(w)
elif isinstance(g, gates.Barrier) and mode != "gate":
pass
elif isinstance(g, gates.NopGate):
pass
else:
raise Exception("not handled")
raise Exception(f"Gate not handled for sympy exporter: {g_name}")

if ga and qstate:
qstate = ga * qstate
elif ga:
qstate = ga

return qstate
15 changes: 15 additions & 0 deletions qlasskit/qcircuit/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,26 @@ def __init__(self):
super().__init__("Z")


class P(QGate):
def __init__(self):
super().__init__("P")


class Swap(QGate):
def __init__(self):
super().__init__("SWAP", 2)


class CX(QControlledGate):
def __init__(self):
super().__init__(X(), 1)


class CP(QControlledGate):
def __init__(self):
super().__init__(P(), 1)


class CCX(QControlledGate):
def __init__(self):
super().__init__(X(), 2)
Expand Down
51 changes: 49 additions & 2 deletions qlasskit/qcircuit/qcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@

# http://www.apache.org/licenses/LICENSE-2.0

import copy

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
import math
from typing import Any, List, Literal, Tuple, Union

from sympy import Symbol
Expand Down Expand Up @@ -227,6 +228,52 @@ def mcx(self, wl: List[int], target):
wl = list(map(lambda w: self[w], wl))
self.append(gates.MCX(len(wl)), wl + [target])

def swap(self, w1, w2):
w1, w2 = self[w1], self[w2]
self.append(gates.Swap(), [w1, w2])

def cp(self, phase, w1, w2):
"""CP gate"""
w1, w2 = self[w1], self[w2]
self.append(gates.CP(), [w1, w2], phase)

def qft(self, wl: List[int]):
"""Apply the quantum fourier transform"""
n = len(wl)
wl = list(map(lambda w: self[w], wl))

for i in range(n):
# Apply the Hadamard gate on the qubit at index i
self.h(wl[i])

# Apply the controlled phase rotation gates
for j in range(i + 1, n):
phase = 2 * math.pi / (2 ** (j - i + 1))
self.cp(phase, wl[j], wl[i])

# Swap the qubits to get them in the correct order
for i in range(n // 2):
self.swap(wl[i], wl[n - i - 1])

def iqft(self, wl: List[int]):
"""Apply the inverse quantum fourier transform"""
n = len(wl)
wl = list(map(lambda w: self[w], wl))

# Swap the qubits to get them in the reverse order for IQFT
for i in range(n // 2):
self.swap(wl[i], wl[n - i - 1])

# Apply the IQFT
for i in reversed(range(n)):
# Apply the controlled phase rotation gates in reverse order
for j in reversed(range(i + 1, n)):
angle = -2 * math.pi / (2 ** (j - i + 1))
self.cp(angle, wl[j], wl[i])

# Apply the Hadamard gate on the qubit at index i
self.h(wl[i])

def export(
self,
mode: Literal["circuit", "gate"] = "circuit",
Expand Down
35 changes: 30 additions & 5 deletions test/test_qcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import ast
import unittest

from sympy import Symbol
from sympy.physics.quantum.qapply import qapply
from sympy.physics.quantum.qubit import Qubit, measure_all

from qlasskit.qcircuit import QCircuit, QCircuitEnhanced, gates

Expand All @@ -32,8 +29,13 @@ def test_base(self):

def test_base_mapping(self):
qc = QCircuit()
a, b, c = qc.add_qubit("a"), qc.add_qubit("b"), qc.add_qubit(Symbol("c"))
a, b, c = (
qc.add_qubit("a"),
qc.add_qubit("b"),
qc.add_qubit(Symbol("c")),
)
qc.ccx("a", Symbol("b"), c)
qc.ccx(a, b, c)
self.assertEqual(qc.num_qubits, 3)
self.assertTrue(isinstance(qc.gates[0][0], gates.CCX))
self.assertEqual(qc.gates[0][1:], ([0, 1, 2], None))
Expand All @@ -60,7 +62,7 @@ def test_augassign_othercirc(self):

def test_get_key_by_index(self):
qc = QCircuit()
a, b = qc.add_qubit("a"), qc.add_qubit("b")
a, b = qc.add_qubit("a"), qc.add_qubit("b") # noqa: F841
self.assertRaises(Exception, lambda qc: qc.get_key_by_index(3), qc)
self.assertEqual(qc.get_key_by_index(0), "a")

Expand Down Expand Up @@ -126,3 +128,26 @@ def test_uncompute_all(self):
qc.uncompute(a)
qc.uncompute_all([r])
# qc.draw()


class TestQCircuitQFT(unittest.TestCase):
def test_qft(self):
qc = QCircuit(3)
qc.qft([0, 1, 2])
self.assertEqual(qc.num_gates, 7)

qc.iqft([0, 1, 2])
self.assertEqual(qc.num_gates, 14)

for i in range(int(qc.num_gates / 2)):
a = qc.gates[i]
b = qc.gates[qc.num_gates - i - 1]

self.assertEqual(a[0].__name__, b[0].__name__)
self.assertEqual(a[1], b[1])

if a[2]:
self.assertEqual(a[2], -b[2])

counts = qiskit_measure_and_count(qc.export("circuit", "qiskit"), shots=1024)
self.assertEqual(counts, {"000": 1024})
32 changes: 28 additions & 4 deletions test/test_qcircuit_exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,16 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import ast
import unittest

import cirq
import numpy as np
import pennylane as qml
from parameterized import parameterized_class
from sympy import Symbol
from sympy.physics.quantum.qapply import qapply
from sympy.physics.quantum.qubit import Qubit, measure_all

from qlasskit.qcircuit import QCircuit, QCircuitEnhanced, gates
from qlasskit.qcircuit import QCircuit

from .utils import qiskit_measure_and_count

Expand Down Expand Up @@ -53,12 +50,28 @@ def bell_circuit():
return qc


def qft_circuit():
qc = QCircuit()
a, b, c = qc.add_qubit("a"), qc.add_qubit("b"), qc.add_qubit("c")
qc.qft([a, b, c])
qc.iqft([a, b, c])
return qc


@parameterized_class(
("qc", "result"),
[
(cx_circuit(), "gate qc q0 q1 {\n\tx q0\n\tcx q0 q1\n}\n\n"),
(ccx_circuit(), "gate qc a b c {\n\tx a\n\tx b\n\tccx a b c\n}\n\n"),
(bell_circuit(), "gate qc a b {\n\th a\n\tcx a b\n}\n\n"),
(
qft_circuit(),
(
"gate qc a b c {\n\th a\n\tcp(1.57) b a\n\tcp(0.79) c a\n\th b\n"
"\tcp(1.57) c b\n\th c\n\tswap a c\n\tswap a c\n\th c\n"
"\tcp(-1.57) c b\n\th b\n\tcp(-0.79) c a\n\tcp(-1.57) b a\n\th a\n}\n\n"
),
),
],
)
class TestQCircuitExportQASM(unittest.TestCase):
Expand All @@ -77,6 +90,7 @@ def test_export_qasm_circuit(self):
(cx_circuit(), [(Qubit("11"), 1)]),
(ccx_circuit(), [(Qubit("111"), 1)]),
(bell_circuit(), [(Qubit("00"), 1 / 2), (Qubit("11"), 1 / 2)]),
# (qft_circuit(), [(Qubit("000"), 1)]),
],
)
class TestQCircuitExportSympy(unittest.TestCase):
Expand All @@ -98,6 +112,7 @@ def test_export_sympy_gate(self):
[
(cx_circuit(), {"11": 1}),
(ccx_circuit(), {"111": 1}),
(qft_circuit(), {"000": 1}),
],
)
class TestQCircuitExportQiskit(unittest.TestCase):
Expand Down Expand Up @@ -125,6 +140,14 @@ def test_export_qiskit(self):
"q(2)": np.array([[[1]]], dtype=np.int8),
},
),
(
qft_circuit(),
{
"q(0)": np.array([[[0]]], dtype=np.int8),
"q(1)": np.array([[[0]]], dtype=np.int8),
"q(2)": np.array([[[0]]], dtype=np.int8),
},
),
],
)
class TestQCircuitExportCirq(unittest.TestCase):
Expand Down Expand Up @@ -161,6 +184,7 @@ def test_export_cirq_gate(self):
(cx_circuit(), [0, 0, 0, 1]),
(ccx_circuit(), [0, 0, 0, 0, 0, 0, 0, 1]),
(bell_circuit(), [0.5, 0, 0, 0.5]),
(qft_circuit(), [1, 0, 0, 0, 0, 0, 0, 0]),
],
)
class TestQCircuitExportPennylane(unittest.TestCase):
Expand Down

0 comments on commit 472b0ae

Please sign in to comment.