From f302b2237c8e75ff8b6d9cbf77d49983ab2afaea Mon Sep 17 00:00:00 2001 From: "Davide Gessa (dakk)" Date: Tue, 16 Jan 2024 11:36:10 +0100 Subject: [PATCH] qutip exporter and qasm2 support --- .github/workflows/docs.yaml | 2 +- TODO.md | 61 +------------------------ docs/source/example_big_circuit.ipynb | 1 + docs/source/example_grover_sudoku.ipynb | 16 ++++--- docs/source/exporter.ipynb | 55 ++++++++++++++++------ qlasskit/qcircuit/__init__.py | 2 +- qlasskit/qcircuit/exporter_qasm.py | 48 ++++++++++++++++++- qlasskit/qcircuit/exporter_qutip.py | 27 +++++++++++ qlasskit/qcircuit/qcircuit.py | 7 ++- test/test_qcircuit_exporters.py | 30 +++++++++++- tox.ini | 2 + tox_no_tweedledum.ini | 2 + 12 files changed, 170 insertions(+), 83 deletions(-) create mode 100644 qlasskit/qcircuit/exporter_qutip.py diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 25951add..66ef4398 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -16,7 +16,7 @@ jobs: - name: Install dependencies run: | pip install sphinx sphinx_rtd_theme sphinx_rtd_dark_mode myst_nb - pip install sympy qiskit-terra qiskit-aer matplotlib pylatexenc pennylane cirq + pip install sympy qiskit-terra qiskit-aer matplotlib pylatexenc pennylane cirq qutip qutip_qip python setup.py install - name: Sphinx build run: | diff --git a/TODO.md b/TODO.md index 7fd17e67..ee1ad170 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,5 @@ -# Roadmap +# Todo list -> And keep an eye to the roadmap. It likes to change - -## Month 1 - -### Week 1: (25 Sept 23) - [x] POC - [x] Test suite setup - [x] Integrate tox with linters, unit-tests, typecheck, coverage @@ -14,8 +9,6 @@ - [x] Ast2logic: assign - [x] Dummy compiler: compile ite - [x] Ast2logic: tuple - -### Week 2: (2 Oct 23) - [x] Split ast2logic into a directory - [x] Ast2logic: write a type inference function - [x] Ast2logic: fix type inference on assign @@ -28,14 +21,10 @@ - [x] OpenQASM3 exporter - [x] Int: comparison - eq, noteq - [x] Int: comparison - lt, gt, gte, lte - -#### Typechecker branch - [x] Translate_expr should returns ttype*expr - [x] Args should also hold the original type - [x] Transform Env to a class holding also the original types - [x] Typecheck all the expressions - -### Week 3: (9 Oct 23) - [x] Test circuit and boolexp using the python code as reference - [x] Qubit garbage uncomputing and recycling - [x] Test: add qubit usage check @@ -45,8 +34,6 @@ - [x] Doc: properly render documentation - [x] Builtin debug functions: print() - [x] Fix code structure and typing location - -### Week 4: (16 Oct 23) - [x] Extensible type system - [x] Builtin functions: max(), min(), len() - [x] Function call (to builtin) @@ -54,10 +41,6 @@ - [x] Qtype: bitwise not - [x] Qtype: shift right / left - [x] Int: subtraction - -## Month 2: - -### Week 1: (23 Oct 23) - [x] Symbol reassign and augassign - [x] Remove unneccessary expressions - [x] Remove quantum circuit identities @@ -70,9 +53,6 @@ - [x] Grover algorithm - [x] Tuple-tuple comparison - [x] Multi var assign - -### Week 2: (30 Oct 23) - - [x] Integrate qrack on test suite - [x] Test / support on multiple py version >= 3.8 - [x] Fixed size list @@ -80,30 +60,16 @@ - [x] Slideshow for UF midterm - [x] Builtin functions: sum(), all(), any() - [x] Bool optimizers test - -### Week 3: (6 Nov 23) - - [x] Ast2logic: if-then-else statement - [x] Midterm call - [x] Bool optimization refactoring - -### Week 4: (13 Nov 23) - - [x] Cirq exporter - [x] Qint multiplier - [x] Allow quantum gates inside sympy expressions - [x] CNotSim dummy simulator for circuit testing - -## Month 3: - -### Week 1: (20 Nov 23) - - [x] Improve exporting utilities - [x] Publish doc on github - [x] Documentation - -### Week 2: (27 Nov 23) - - [x] Qmatrix - [x] Hash function preimage attack notebook - [x] Move all examples to doc @@ -112,37 +78,18 @@ - [x] Improve documentation - [x] First stable release - [x] Use cases - -### Week 3: (4 Dec 23) - - [x] Int arithmetic: mod - [x] Simon example - [x] Deutsch-Jozsa example - [x] Improve performance on big circuits - -### Week 4: (11 Dec 23) - - [x] Minor fixes and new release - -## Month 4: - -### Week 1: (18 Dec 23) - [x] Pennylane exporter - [x] Regression test for qubit number and gates - [x] Sympy exporter - [x] QFT and IQFT - - -### Week 2: (25 Dec 23) - - [x] Separate tests in directories - [x] Circuit decompilation - -### Week 3: (1 Jan 24) - -### Week 4: (8 Jan 24) - - +- [x] QuTip Support ## Future features @@ -167,10 +114,6 @@ - [ ] Parameter bind -### Framwork support - -- [ ] QuTip - ### Tools - [ ] py2qasm tool diff --git a/docs/source/example_big_circuit.ipynb b/docs/source/example_big_circuit.ipynb index ee235e22..6de70138 100644 --- a/docs/source/example_big_circuit.ipynb +++ b/docs/source/example_big_circuit.ipynb @@ -24,6 +24,7 @@ "source": [ "from qlasskit import Qint8, Qlist, boolopt, qlassfa\n", "\n", + "\n", "@qlassfa(bool_optimizer=boolopt.fastOptimizer)\n", "def test(a_list: Qlist[Qint8, 64]) -> Qint8:\n", " h_val = Qint8(0)\n", diff --git a/docs/source/example_grover_sudoku.ipynb b/docs/source/example_grover_sudoku.ipynb index d29a793f..834fb95a 100644 --- a/docs/source/example_grover_sudoku.ipynb +++ b/docs/source/example_grover_sudoku.ipynb @@ -21,6 +21,7 @@ "from qlasskit import qlassf, Qmatrix\n", "from qlasskit.algorithms import Grover\n", "\n", + "\n", "@qlassf\n", "def sudoku_check(m: Qmatrix[bool, 2, 2]) -> bool:\n", " constr = m[0][0]\n", @@ -30,6 +31,7 @@ " sub3 = m[0][1] ^ m[1][1]\n", " return sub0 and sub1 and sub2 and sub3 and constr\n", "\n", + "\n", "q_algo = Grover(sudoku_check)" ] }, @@ -97,22 +99,24 @@ "source": [ "from qlasskit import Qint2, Qint4\n", "\n", + "\n", "@qlassf\n", "def sudoku_check(m: Qmatrix[Qint2, 4, 4]) -> bool:\n", " res = True\n", - " \n", + "\n", " # Constraints\n", " res = (m[0][2] == 3) and (m[0][0] == 1)\n", - " \n", + "\n", " # Check every row and column\n", " for i in range(4):\n", - " c = (Qint4(0) + m[i][0] + m[i][1] + m[i][2] + m[i][3]) == 6 \n", - " r = (Qint4(0) + m[0][i] + m[1][i] + m[2][i] + m[3][i]) == 6 \n", + " c = (Qint4(0) + m[i][0] + m[i][1] + m[i][2] + m[i][3]) == 6\n", + " r = (Qint4(0) + m[0][i] + m[1][i] + m[2][i] + m[3][i]) == 6\n", " res = res and c and r\n", - " \n", + "\n", " return res\n", "\n", - "#q_algo = Grover(sudoku_check)\n", + "\n", + "# q_algo = Grover(sudoku_check)\n", "print(sudoku_check.circuit())" ] } diff --git a/docs/source/exporter.ipynb b/docs/source/exporter.ipynb index 87301c8c..f3606e16 100644 --- a/docs/source/exporter.ipynb +++ b/docs/source/exporter.ipynb @@ -16,7 +16,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -47,7 +47,7 @@ "
" ] }, - "execution_count": 13, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -75,13 +75,14 @@ "text": [ "OPENQASM 3.0;\n", "\n", - "gate hello_world a b.0 b.1 anc_0 _ret.0 _ret.1 {\n", + "gate hello_world a b.0 b.1 _ret.0 _ret.1 {\n", "\tcx a _ret.0\n", "\tcx b.0 _ret.0\n", - "\tcx b.1 anc_0\n", - "\tccx a b.0 anc_0\n", + "\tcx b.1 _ret.1\n", + "\tccx a b.0 _ret.1\n", "}\n", "\n", + "hello_world q[0],q[1],q[2],q[3],q[4];\n", "\n" ] } @@ -100,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -128,7 +129,7 @@ "4: ───hello_world───" ] }, - "execution_count": 15, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -149,7 +150,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -158,7 +159,7 @@ "" ] }, - "execution_count": 16, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -179,7 +180,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -191,7 +192,7 @@ "C((0,1),X(4))*CNOT(2,4)*CNOT(1,3)*CNOT(0,3)*|00000>" ] }, - "execution_count": 17, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -200,6 +201,34 @@ "qc = hello_world.export(\"sympy\")\n", "qc" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Qutip" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Gate(hello_world, targets=[0, 1, 2, 3, 4], controls=None, classical controls=None, control_value=None, classical_control_value=None)]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qc = hello_world.export(\"qutip\")\n", + "qc.gates" + ] } ], "metadata": { diff --git a/qlasskit/qcircuit/__init__.py b/qlasskit/qcircuit/__init__.py index 57f79b70..86430a04 100644 --- a/qlasskit/qcircuit/__init__.py +++ b/qlasskit/qcircuit/__init__.py @@ -15,7 +15,7 @@ from typing import Literal, get_args # noqa: F401 -SupportedFramework = Literal["qiskit", "sympy", "cirq", "qasm", "pennylane"] +SupportedFramework = Literal["qiskit", "sympy", "cirq", "qasm", "pennylane", "qutip"] SupportedFrameworks = list(get_args(SupportedFramework)) from . import gates # noqa: F401, E402 diff --git a/qlasskit/qcircuit/exporter_qasm.py b/qlasskit/qcircuit/exporter_qasm.py index 36600930..f736e013 100644 --- a/qlasskit/qcircuit/exporter_qasm.py +++ b/qlasskit/qcircuit/exporter_qasm.py @@ -19,7 +19,10 @@ class QasmExporter(QCircuitExporter): - def export(self, _selfqc, mode: Literal["circuit", "gate"]): + def __init__(self, version=3): + self.version = version + + def export_v3(self, _selfqc, mode: Literal["circuit", "gate"]): gate_qasm = f"gate {_selfqc.name} " gate_qasm += " ".join(_selfqc.qubit_map.keys()) gate_qasm += " {\n" @@ -39,5 +42,48 @@ def export(self, _selfqc, mode: Literal["circuit", "gate"]): qasm = "OPENQASM 3.0;\n\n" qasm += gate_qasm + qasm += ( + _selfqc.name + + " " + + ",".join(map(lambda c: f"q[{c}]", range(_selfqc.num_qubits))) + + ";\n" + ) return qasm + + def export_v2(self, _selfqc, mode: Literal["circuit", "gate"]): + gate_qasm = f"gate {_selfqc.name} " + gate_qasm += " ".join(_selfqc.qubit_map.keys()) + gate_qasm += " {\n" + for g, ws, p in _selfqc.gates: + if issubclass(g.__class__, gates.NopGate): + continue + + qbs = list(map(lambda gq: _selfqc.get_key_by_index(gq), ws)) + 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": + return gate_qasm + + qasm = "OPENQASM 2.0;\n\n" + qasm += 'include "qelib1.inc";\n\n' + qasm += "qreg q[" + str(_selfqc.num_qubits) + "];\n" + qasm += gate_qasm + qasm += ( + _selfqc.name + + " " + + ",".join(map(lambda c: f"q[{c}]", range(_selfqc.num_qubits))) + + ";\n" + ) + + return qasm + + def export(self, _selfqc, mode: Literal["circuit", "gate"]): + if self.version == 3: + return self.export_v3(_selfqc, mode) + else: + return self.export_v2(_selfqc, mode) diff --git a/qlasskit/qcircuit/exporter_qutip.py b/qlasskit/qcircuit/exporter_qutip.py new file mode 100644 index 00000000..80345f5b --- /dev/null +++ b/qlasskit/qcircuit/exporter_qutip.py @@ -0,0 +1,27 @@ +# Copyright 2023 Davide Gessa + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# 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. + +from typing import Literal + +from .exporter import QCircuitExporter +from .exporter_qasm import QasmExporter + + +class QutipExporter(QCircuitExporter): + def export(self, _selfqc, mode: Literal["circuit", "gate"]): + from qutip_qip.qasm import read_qasm + + qasm = QasmExporter(version=2).export(_selfqc, mode) + qc = read_qasm(qasm, mode="qiskit", version="2.0", strmode=True) + return qc diff --git a/qlasskit/qcircuit/qcircuit.py b/qlasskit/qcircuit/qcircuit.py index 3ed2ebd1..7f1a5e67 100644 --- a/qlasskit/qcircuit/qcircuit.py +++ b/qlasskit/qcircuit/qcircuit.py @@ -340,7 +340,8 @@ def export( mode (Literal["circuit", "gate"], optional): The export mode, which can be "circuit" or "gate". Defaults to "circuit". framework (SupportedFramework, optional): The target framework for export, - either "qiskit", "sympy", "cirq", "qasm", "pennylane". Defaults to "qiskit". + either "qiskit", "sympy", "cirq", "qasm", "pennylane", "qutip". + Defaults to "qiskit". Returns: Any: The exported circuit or gate representation in the specified framework. @@ -366,6 +367,10 @@ def export( from .exporter_cirq import CirqExporter return CirqExporter().export(self, mode) + elif framework == "qutip": + from .exporter_qutip import QutipExporter + + return QutipExporter().export(self, mode) elif framework == "pennylane": from .exporter_pennylane import PennyLaneExporter diff --git a/test/test_qcircuit_exporters.py b/test/test_qcircuit_exporters.py index d9f65e4a..e2ba33d4 100644 --- a/test/test_qcircuit_exporters.py +++ b/test/test_qcircuit_exporters.py @@ -17,6 +17,7 @@ import numpy as np import pennylane as qml from parameterized import parameterized_class +from qutip import basis, tensor from sympy.physics.quantum.qapply import qapply from sympy.physics.quantum.qubit import Qubit, measure_all @@ -81,7 +82,10 @@ def test_export_qasm_gate(self): def test_export_qasm_circuit(self): qasm_c = self.qc.export("circuit", "qasm") - self.assertEqual(qasm_c, f"OPENQASM 3.0;\n\n{self.result}") + + call = "qc " + call += ",".join([f"q[{x}]" for x in range(self.qc.num_qubits)]) + self.assertEqual(qasm_c, f"OPENQASM 3.0;\n\n{self.result}{call};\n") @parameterized_class( @@ -199,3 +203,27 @@ def test_export_pennylane_circuit(self): for a, b in zip(r[0], self.result): self.assertAlmostEqual(a, b) + + +@parameterized_class( + ("qc", "result"), + [ + (cx_circuit(), [0, 0, 0, 1]), + (ccx_circuit(), [0, 0, 0, 0, 0, 0, 0, 1]), + (bell_circuit(), [0.70710678, 0, 0, 0.70710678]), + (qft_circuit(), [1, 0, 0, 0, 0, 0, 0, 0]), + ], +) +class TestQCircuitExportQutip(unittest.TestCase): + def test_export_qutip_circuit(self): + qc = self.qc.export("circuit", "qutip") + zero_state = tensor(*[basis(2, 0) for i in range(self.qc.num_qubits)]) + + result = qc.run_statistics(state=zero_state) + states = result.get_final_states() + probabilities = result.get_probabilities() + + self.assertEqual(probabilities, [1]) + + for i in zip(states[0].data.toarray(), self.result): + self.assertAlmostEqual(float(i[0][0]), i[1]) diff --git a/tox.ini b/tox.ini index 82a360bd..764e7dd1 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,8 @@ deps = qiskit qiskit-aer cirq + qutip + qutip_qip pyqrack qiskit-qrack-provider pandas diff --git a/tox_no_tweedledum.ini b/tox_no_tweedledum.ini index 547e4e1c..78c2927a 100644 --- a/tox_no_tweedledum.ini +++ b/tox_no_tweedledum.ini @@ -15,6 +15,8 @@ deps = qiskit qiskit-aer cirq + qutip + qutip_qip pyqrack qiskit-qrack-provider pandas