diff --git a/TODO.md b/TODO.md index 3bfbe82f..b67fb52e 100644 --- a/TODO.md +++ b/TODO.md @@ -41,12 +41,12 @@ - [x] Test: add qubit usage check - [x] Parametrize qint tests over bit_size - [x] Allow constant functions -- [ ] Doc: emphatize the compiler flow -- [ ] Doc: properly render documentation -- [ ] Fix structure and typing location +- [x] Doc: emphatize the compiler flow +- [x] Doc: properly render documentation ### Week 4: (16 Oct 23) -- [ ] Test: Optimize compute_result_of_qcircuit in order to increase MAX_Q_SIM +- [ ] Publish doc +- [ ] Fix structure and typing location - [ ] Int arithmetic expressions (+, -, *, /) - [ ] Function call - [ ] Builtin functions: sum(), max(), min(), len() diff --git a/docs/source/howitworks.rst b/docs/source/howitworks.rst index bc3e61f7..081f4598 100644 --- a/docs/source/howitworks.rst +++ b/docs/source/howitworks.rst @@ -5,6 +5,75 @@ In order to translate python code to quantum circuit, qlasskit performs several it starts from the python *AST* (abstract synthax tree) creating *boolean expressions* as intermediate form. Then these boolean expressions are compiled into a *quantum circuit*. +While other existing libraries translate individual operations into quantum circuits and then +combine them, qlasskit creates a single boolean expression for every output qubit of the entire +function. This approach allows for further optimization using boolean properties. + +For instance, let assume we have the following function: + +.. code-block:: python + + def f_comp(n: Qint4) -> bool: + return n > 3 or n == 7 + +If we compile the whole function to a quantum circuit, we obtain the following circuit, created by +this boolean expression `n.2 | n.3 | (n.0 & n.1 & n.2 & ~n.3)`: + +.. code-block:: text + + f_comp + q_0: ───░─────────────────────■───────────────── + ░ │ + q_1: ───░─────────────────────■───────────────── + ░ │ + q_2: ───░──────■──────────────■───────────────── + ░ │ │ + q_3: ───░──────┼────■─────────┼───────────────── + ░ ┌─┴─┐ │ ┌───┐ │ + q_4: ───░────┤ X ├──┼──┤ X ├──┼─────────■─────── + ░ └───┘┌─┴─┐├───┤ │ │ + q_5: ───░─────────┤ X ├┤ X ├──■─────────■─────── + ░ └───┘└───┘┌─┴─┐┌───┐ │ + q_6: ───░───────────────────┤ X ├┤ X ├──■─────── + ░ └───┘└───┘┌─┴─┐┌───┐ + q_7: ───░─────────────────────────────┤ X ├┤ X ├ + ░ └───┘└───┘ + + +While if we decompose the function in 3 operations `n==7`, `n>3`, `a and b`, we obtain something like +the following circuit (qubit uncomputing is disabled to show the real number of gates): + +.. code-block:: text + + = > | + q_0: ─░─────────────■───░───────────────────────────░────────────────────────── + ░ │ ░ ░ + q_1: ─░─────────────■───░───────────────────────────░────────────────────────── + ░ │ ░ ░ + q_2: ─░─────────────■───░───■───────────────────────░────────────────────────── + ░ │ ░ │ ░ + q_3: ─░───■─────────┼───░───┼────■──────────────────░────────────────────────── + ░ ┌─┴─┐┌───┐ │ ░ │ │ ░ + q_4: ─░─┤ X ├┤ X ├──■───░───┼────┼──────────────────░────────────────────────── + ░ └───┘└───┘┌─┴─┐ ░ │ │ ░ + q_5: ─░───────────┤ X ├─░───┼────┼──────────────────░───■────────────────────── + ░ └───┘ ░ │ │ ░ │ + q_6: ─░─────────────────░───┼────┼──────────────────░───┼────────────────────── + ░ ░ ┌─┴─┐ │ ┌───┐ ░ │ + q_7: ─░─────────────────░─┤ X ├──┼──┤ X ├──■────────░───┼────────────────────── + ░ ░ └───┘┌─┴─┐├───┤ │ ░ │ + q_8: ─░─────────────────░──────┤ X ├┤ X ├──■────────░───┼────────────────────── + ░ ░ └───┘└───┘┌─┴─┐┌───┐ ░ │ + q_9: ─░─────────────────░────────────────┤ X ├┤ X ├─░───┼────■───────────────── + ░ ░ └───┘└───┘ ░ ┌─┴─┐ │ ┌───┐ + q_10: ─░─────────────────░───────────────────────────░─┤ X ├──┼──┤ X ├──■─────── + ░ ░ ░ └───┘┌─┴─┐├───┤ │ + q_11: ─░─────────────────░───────────────────────────░──────┤ X ├┤ X ├──■─────── + ░ ░ ░ └───┘└───┘┌─┴─┐┌───┐ + q_12: ─░─────────────────░───────────────────────────░────────────────┤ X ├┤ X ├ + ░ ░ ░ └───┘└───┘ + + AST Traslator ----------------- @@ -12,6 +81,20 @@ Given a python function, the `qlasskit.ast2logic` module walks its synthax tree expressions to boolean expressions. +For instance, the following function: + +.. code-block:: python + + def f(n: Qint4) -> bool: + return n == 3 + +Is translated to this boolean expression: + +.. code-block:: python + + _ret = n.0 & n.1 & ~n.2 & ~n.3 + + Compiler ------------ The boolean expressions are then being fed to the `qlasskit.compiler`` which translates boolean expressions @@ -19,8 +102,31 @@ to invertible circuits, introducing auxiliary qubits. In this step, the compiler auxiliary qubits in order to reduce the number of qubits needed and the circuit footprint. + + Result ------ The result of the compiler is a quantum circuit represented with qlasskit `QCircuit`. This circuit can now be exported to one of the supported framework. + + +The previous example function `f`, is translated to the following quantum circuit: + + +.. code-block:: text + + q_0: ─────────────────■── + │ + q_1: ─────────────────■── + │ + q_2: ──■──────────────┼── + │ │ + q_3: ──┼────■─────────┼── + ┌─┴─┐ │ ┌───┐ │ + q_4: ┤ X ├──┼──┤ X ├──■── + └───┘┌─┴─┐├───┤ │ + q_5: ─────┤ X ├┤ X ├──■── + └───┘└───┘┌─┴─┐ + q_6: ───────────────┤ X ├ + └───┘ diff --git a/docs/source/index.rst b/docs/source/index.rst index b2e19704..8b03c5d6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,6 +5,7 @@ Qlasskit is a Python library that allows quantum developers to write classical a Python and translate them into unitary operators (gates) for use in quantum circuits. + .. :doc:`howitworks` .. How qlasskit internally works diff --git a/examples/ex1.py b/examples/ex1.py index 5584830c..faf6155e 100644 --- a/examples/ex1.py +++ b/examples/ex1.py @@ -1,15 +1,42 @@ from qiskit import QuantumCircuit -from qlasskit import Int4, qlassf +from qlasskit import Qint4, qlassf @qlassf -def f(n: Int4) -> bool: - if n == 3: - return True - else: - return False +def f1(n: Qint4) -> bool: + return n == 7 + +@qlassf +def f2(n: Qint4, b: bool) -> bool: + return n > 3 + +@qlassf +def f3(a: bool, b: bool) -> bool: + return a or b + +@qlassf +def f_comp(n: Qint4) -> bool: + return n > 3 or n == 7 + + +print(f_comp.expressions) +gate = f_comp.gate() +qc = QuantumCircuit(gate.num_qubits) +qc.barrier(label="f_comp") +qc.append(gate, list(range(gate.num_qubits))) +print(qc.decompose().draw('text')) + -qc = QuantumCircuit(f.num_qubits) -qc.append(f.gate(), f.qubits(0)) +gate1 = f1.gate() +gate2 = f2.gate() +gate3 = f3.gate() +qc = QuantumCircuit(max(gate1.num_qubits, gate2.num_qubits) + gate3.num_qubits) +qc.barrier(label="=") +qc.append(gate1, [0, 1, 2, 3, 4, 5]) +qc.barrier(label=">") +qc.append(gate2, [0, 1, 2, 3, 6, 7, 8, 9]) +qc.barrier(label="|") +qc.append(gate3, [5, 9, 10, 11, 12]) +print(qc.decompose().draw('text')) \ No newline at end of file diff --git a/examples/ex4.py b/examples/ex4.py new file mode 100644 index 00000000..4dbf661d --- /dev/null +++ b/examples/ex4.py @@ -0,0 +1,32 @@ +from qiskit import QuantumCircuit + +from qlasskit import Qint4, qlassf + + +@qlassf +def f1(n: Qint4) -> Qint4: + return n + 1 + +@qlassf +def f2(n: Qint4) -> Qint4: + return n + 3 + +@qlassf +def f_comp(n: Qint4) -> Qint4: + return n + 1 + 3 + + +print(f_comp.expressions) +gate = f_comp.gate() +qc = QuantumCircuit(gate.num_qubits) +qc.append(gate, list(range(gate.num_qubits))) +print(qc.decompose().draw('text')) + + + +gate1 = f1.gate() +gate2 = f2.gate() +qc = QuantumCircuit(max(gate1.num_qubits, gate2.num_qubits)) +qc.append(gate1, list(range(gate1.num_qubits))) +qc.append(gate2, list(range(gate2.num_qubits))) +print(qc.decompose().draw('text')) \ No newline at end of file diff --git a/test/test_qlassf_int.py b/test/test_qlassf_int.py index b74d5af4..8b3a793c 100644 --- a/test/test_qlassf_int.py +++ b/test/test_qlassf_int.py @@ -270,6 +270,11 @@ def test_ite_return_qint(self): self.assertEqual(qf.expressions[1][1], ITE(a, Symbol("b.1"), Symbol("c.1"))) compute_and_compare_results(self, qf) + def test_composed_comparators(self): + f = "def f_comp(n: Qint4) -> bool: return n > 3 or n == 7" + qf = qlassf(f, to_compile=COMPILATION_ENABLED) + compute_and_compare_results(self, qf) + # def test(a: Qint2) -> Qint2: # return a + 1 # def test(a: Qint2, b: Qint2) -> Qint2: