diff --git a/qlasskit/qcircuit/exporter_cirq.py b/qlasskit/qcircuit/exporter_cirq.py index 4bcb6a67..b3073b22 100644 --- a/qlasskit/qcircuit/exporter_cirq.py +++ b/qlasskit/qcircuit/exporter_cirq.py @@ -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 @@ -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)) diff --git a/qlasskit/qcircuit/exporter_pennylane.py b/qlasskit/qcircuit/exporter_pennylane.py index 043061f6..fbb28888 100644 --- a/qlasskit/qcircuit/exporter_pennylane.py +++ b/qlasskit/qcircuit/exporter_pennylane.py @@ -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 @@ -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) diff --git a/qlasskit/qcircuit/exporter_qasm.py b/qlasskit/qcircuit/exporter_qasm.py index d68ae8d2..15cdd9b6 100644 --- a/qlasskit/qcircuit/exporter_qasm.py +++ b/qlasskit/qcircuit/exporter_qasm.py @@ -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": diff --git a/qlasskit/qcircuit/exporter_qiskit.py b/qlasskit/qcircuit/exporter_qiskit.py index 0477abeb..a5f44b3f 100644 --- a/qlasskit/qcircuit/exporter_qiskit.py +++ b/qlasskit/qcircuit/exporter_qiskit.py @@ -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() diff --git a/qlasskit/qcircuit/exporter_sympy.py b/qlasskit/qcircuit/exporter_sympy.py index 205bf0a7..636018fc 100644 --- a/qlasskit/qcircuit/exporter_sympy.py +++ b/qlasskit/qcircuit/exporter_sympy.py @@ -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])) @@ -30,10 +33,11 @@ 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]) @@ -41,6 +45,10 @@ def export(self, _selfqc, mode: Literal["circuit", "gate"]): 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": @@ -48,11 +56,11 @@ def export(self, _selfqc, mode: Literal["circuit", "gate"]): 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 diff --git a/qlasskit/qcircuit/gates.py b/qlasskit/qcircuit/gates.py index bc2dcd13..3eba89f2 100644 --- a/qlasskit/qcircuit/gates.py +++ b/qlasskit/qcircuit/gates.py @@ -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) diff --git a/qlasskit/qcircuit/qcircuit.py b/qlasskit/qcircuit/qcircuit.py index 9c80f507..cdc0fdaf 100644 --- a/qlasskit/qcircuit/qcircuit.py +++ b/qlasskit/qcircuit/qcircuit.py @@ -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 @@ -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", diff --git a/test/test_qcircuit.py b/test/test_qcircuit.py index 236008d3..c32d0659 100644 --- a/test/test_qcircuit.py +++ b/test/test_qcircuit.py @@ -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 @@ -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)) @@ -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") @@ -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}) diff --git a/test/test_qcircuit_exporters.py b/test/test_qcircuit_exporters.py index e0533440..d9f65e4a 100644 --- a/test/test_qcircuit_exporters.py +++ b/test/test_qcircuit_exporters.py @@ -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 @@ -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): @@ -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): @@ -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): @@ -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): @@ -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):