From 852a875d38a1858003e51c08a9c5900707846b2f Mon Sep 17 00:00:00 2001 From: nadavyoran <74191524+nadavyoran@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:44:39 +0200 Subject: [PATCH] Shor in library corrected - post-processing --- .../shor/doubly_controlled_modular_adder.qmod | 43 +- ...olled_modular_adder.synthesis_options.json | 45 +- .../shor/shor_modular_exponentiation.ipynb | 570 ++++++++++++------ .../shor/shor_modular_exponentiation.qmod | 63 +- ...ular_exponentiation.synthesis_options.json | 42 +- 5 files changed, 535 insertions(+), 228 deletions(-) diff --git a/algorithms/algebraic/shor/doubly_controlled_modular_adder.qmod b/algorithms/algebraic/shor/doubly_controlled_modular_adder.qmod index 2b7f9ccf3..fcb372482 100644 --- a/algorithms/algebraic/shor/doubly_controlled_modular_adder.qmod +++ b/algorithms/algebraic/shor/doubly_controlled_modular_adder.qmod @@ -33,41 +33,44 @@ qfunc check_msb(ref: int, x: qbit[], aux: qbit) { } } -qfunc ccmod_add(N: int, a: int, phi_b: qbit[], c1: qbit, c2: qbit, aux: qbit) { +qfunc ccmod_add(N: int, a: int, phi_b: qbit[], c1: qbit, c2: qbit) { ctrl: qbit[]; - {c1, c2} -> ctrl; - control (ctrl) { - phase_lad(a, phi_b); - } - invert { - phase_lad(N, phi_b); - } - check_msb(1, phi_b, aux); - control (aux) { - phase_lad(N, phi_b); - } + aux: qbit; within { + allocate(1, aux); + {c1, c2} -> ctrl; + } apply { + control (ctrl) { + phase_lad(a, phi_b); + } invert { - control (ctrl) { - phase_lad(a, phi_b); + phase_lad(N, phi_b); + } + check_msb(1, phi_b, aux); + control (aux) { + phase_lad(N, phi_b); + } + within { + invert { + control (ctrl) { + phase_lad(a, phi_b); + } } + } apply { + check_msb(0, phi_b, aux); } - } apply { - check_msb(0, phi_b, aux); } - ctrl -> {c1, c2}; } -qfunc main(output b: qnum, output ctrl: qbit[2], output aux: qbit) { +qfunc main(output b: qnum, output ctrl: qbit[2]) { allocate(5, b); allocate(2, ctrl); - allocate(1, aux); b ^= 8; X(ctrl[0]); X(ctrl[1]); within { qft(b); } apply { - ccmod_add(15, 9, b, ctrl[0], ctrl[1], aux); + ccmod_add(15, 9, b, ctrl[0], ctrl[1]); } } diff --git a/algorithms/algebraic/shor/doubly_controlled_modular_adder.synthesis_options.json b/algorithms/algebraic/shor/doubly_controlled_modular_adder.synthesis_options.json index 0967ef424..90687a564 100644 --- a/algorithms/algebraic/shor/doubly_controlled_modular_adder.synthesis_options.json +++ b/algorithms/algebraic/shor/doubly_controlled_modular_adder.synthesis_options.json @@ -1 +1,44 @@ -{} +{ + "constraints": { + "max_gate_count": {}, + "optimization_parameter": "no_opt" + }, + "preferences": { + "machine_precision": 8, + "custom_hardware_settings": { + "basis_gates": [ + "u1", + "h", + "sxdg", + "z", + "y", + "u2", + "r", + "p", + "sx", + "s", + "t", + "cy", + "tdg", + "x", + "rx", + "rz", + "cx", + "cz", + "ry", + "sdg", + "id", + "u" + ], + "is_symmetric_connectivity": true + }, + "debug_mode": true, + "synthesize_all_separately": false, + "optimization_level": 3, + "output_format": ["qasm"], + "pretty_qasm": true, + "transpilation_option": "auto optimize", + "timeout_seconds": 300, + "random_seed": 3976221924 + } +} diff --git a/algorithms/algebraic/shor/shor_modular_exponentiation.ipynb b/algorithms/algebraic/shor/shor_modular_exponentiation.ipynb index 1b85a470b..2514583b1 100644 --- a/algorithms/algebraic/shor/shor_modular_exponentiation.ipynb +++ b/algorithms/algebraic/shor/shor_modular_exponentiation.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "d6f83f81-aceb-42e3-ad75-f5978c201cdc", + "id": "0", "metadata": {}, "source": [ "# Shor's Algorithm\n" @@ -10,16 +10,18 @@ }, { "cell_type": "markdown", - "id": "83a55311-9ce2-4262-a4fd-de01d8c51420", + "id": "1", "metadata": {}, "source": [ - "Integer factorization [[1](#IntegerFactor)] is a famous problem in number theory: given a composite number $N$, find its prime factors. The importance of the problem stems from there being no known efficient (polynomial-time, in the number of bits needed to represent $N$) classical algorithm, and much of modern-day cryptography relies on this fact. In 1994, Peter Shor came up with an efficient _quantum_ algorithm for the problem [[2](#Shor94)]; providing, arguably, the most important evidence for an exponential advantage of quantum computing over classical computing.\n", + "## Introduction\n", "\n", - "### Full Algorithm\n", + "Integer factorization [[1](#IntegerFactor)] is a famous problem in number theory: given a number $N$ which is composite, find its prime factors. The importance of the problem stems from the fact that no efficient (polynomial-time, in the number of bits needed to represent $N$) classical algorithm is known for it to this day, and much of modern day cryptography relies on this fact. In 1994, Peter Shor came up with an efficient _quantum_ algorithm for the problem [[2](#Shor94)] - providing, arguably, the most important evidence for an exponential advantage of quantum computing over classical computing.\n", "\n", - "Shor's algorithm consists of a classical part and a quantum subroutine. The steps of the algorithm for factoring an input number $N$, summarized from [[3](#ShorSteps)]:\n", + "### The full Algorithm\n", "\n", - "1. Pick a random number $1 < a < N$ that is co-prime with $N$. Co-primality can be checked by computing the GCD (greatest common divisor) of $a$ and $N$. If it is 1, then we have a co-prime $a$; otherwise, we have a non-trivial factor of $N$ and we are done.\n", + "Shor's algorithm consists of a classical part and a quantum subroutine. The steps of the algorithm for factoring an input number $N$, summarized from [[3](#ShorSteps)], are as follows:\n", + "\n", + "1. Pick a random number $1 < a < N$ that is co-prime with $N$. Co-primality can be checked by computing the GCD (greatest common divisor) of $a$ and $N$ - if it is 1 then we have found a co-prime $a$, otherwise we have found a non-trivial factor of $N$ and we are done.\n", "2. Find the period $r$ of the following function, using the quantum period finding algorithm (described in [[3](#ShorSteps)]): $$f(x) = a^x\\hspace{-8pt} \\mod \\hspace{-4pt} N$$\n", "3. If $r$ is odd or $a^{r/2} = -1 \\mod N$, return to step 1 (this event can be shown to happen with probability at most $1/2$).\n", "4. Otherwise, $\\gcd(a^{r/2} \\pm 1, N)$ are both factors of $N$, and computing one of them yields the required result.\n" @@ -28,14 +30,14 @@ { "attachments": {}, "cell_type": "markdown", - "id": "610dbdf9-62ac-4588-933b-733e2ed03f92", + "id": "2", "metadata": {}, "source": [ - "### Quantum Period Finding\n", + "### Quantum period finding\n", "\n", - "The quantum part of Shor's algorithm—the period finding—is simply quantum phase estimation (QPE) applied to the unitary $U$, which computes multiplication by $a$ modulo $N$: $U|y\\rangle =|ya \\hspace{-6pt}\\mod \\hspace{-4pt} N\\rangle$[[4](#NielsenChuang)]. This unitary is applied to the input state $|y=1\\rangle$, which is an equal superposition of $r$ eigenstates of $U$ ($r$ being the period we wish to find) with eigenvalues \n", - "$\\exp (2\\pi i s/r)$, $s = 0, ... ,r-1$. The measured output of the QPE circuit is therefore a number that is a good approximation to $s/r$ for some $s < r$. The denominator $r$ can be found with some additional classical postprocessing [[4](#NielsenChuang)]. \n", - "The repeated applications of $U^{2^{j}}$ in the QPE algorithm are realized by multiplying with the corresponding powers $a^{2^{j}}$ modulo $N$. The resulting computation (of the repeated multiplications) is exponentiation modulo $N$. The general scheme of the QPE realizing the quantum order finding is shown in this figure.\n" + "The quantum part of Shor's algorithm - the period finding is simply quantum phase estimation (QPE) applied to the unitary $U$ which computes multiplication by $a$ modulo $N$: $U|y\\rangle =|ya \\hspace{-6pt}\\mod \\hspace{-4pt} N\\rangle$[[4](#NielsenChuang)]. This unitary is applied to the input state $|y=1\\rangle$ which is an equal superposition of $r$ eigenstates of $U$ ($r$ being the period we wish to find) with eigenvalues \n", + "$\\exp (2\\pi i s/r)$, $s = 0, ... ,r-1$. The measured output of the QPE circuit would therefore be a number which is a good approximation to $s/r$ for some $s < r$. The denominator $r$ can be found with some additional classical post-processing [[4](#NielsenChuang)]. \n", + "The repeated applications of $U^{2^{j}}$ in the QPE algorithm are realized by multiplying with the corresponding powers $a^{2^{j}}$ modulo $N$. The resulting computation (of the repeated multiplications) is exponentiation modulo $N$. The general scheme of the QPE realizing the quantum order finding is shown in the following figure. \n" ] }, { @@ -45,7 +47,7 @@ } }, "cell_type": "markdown", - "id": "b8e77e98-583e-4264-aacc-fc051a384771", + "id": "3", "metadata": {}, "source": [ "![image.png](attachment:e9fc2bd9-113e-4a65-b423-277b082e7456.png)" @@ -53,27 +55,27 @@ }, { "cell_type": "markdown", - "id": "fc30e98e-72d2-4f20-ad68-ec8356abf4ba", + "id": "4", "metadata": {}, "source": [ - "The above QPE scheme includes three components: \n", + "The above QPE scheme includes three components \n", "\n", - "* A Hadamard transform applied to a first $t=2n$ qubit register at the begining of the circuit ($t$ is set by the required accuracy and probability of success [[4](#NielsenChuang)])\n", + "* A Hadamard transform applied to a first $t=2n$ qubit register at the begining of the circuit (t is set by the required accuracy and probability of success [[4](#NielsenChuang)])\n", "* An inverse QFT applied to the same register at the end \n", - "* A modular exponentiation applied to the first and a second $n$ qubit register, computing $a^{j}\\hspace{-6pt}\\mod \\hspace{-4pt} N$ for each of the $j$ values in the first register\n", + "* A modular exponentiation applied to the first and a second $n$ qubit register computing $a^{j}\\hspace{-6pt}\\mod \\hspace{-4pt} N$ for each of the values $j$ in the first register\n", "\n", - "Clearly, the most difficult component to implement is the modular exponentiation, which requires additional auxiliary qubits. \n", - "Here, we construct a circuit implementing the above period finding algorithm, receiving classical numbers $N$ and $a$ as input and returning (with a good probability) a measured output, which is a good approximation to the rational number $s/r$ above. \n", - "We start with modular addition, going via modular multiplication, to modular exponentiation and the full period finding circuit. The modular adder used here is a version of the QFT-based addition of Draper [[5](#Draper)] and is similar to the circuit suggested in [[6](#Beauregard)]. More generally, the period finding circuit here is similar to implementation of [[6](#Beauregard)] except for the fact that the modular exponentiation and the Quantum Fourier Transform (QFT) at the end are realized on a full register of $2n$ qubits (not on a single qubit as in [[6](#Beauregard)]) and the circuit therefore includes $4n+2$ qubits. " + "Clearly, the most difficult component to implement is the modular exponentiation which will require additional auxiliary qubits. \n", + "Here we construct a circuit implementing the above period finding algorithm receiving classical numbers $N$ and $a$ as input and returns (with a good probability) a measured output which is a good approximation to the rational number $s/r$ above. \n", + "We start from modular addition, via modular multiplication to modular exponentiation and the full period finding circuit. The modular adder used here is a version of the QFT-based addition of Draper [[5](#Draper)] and is similar to the circuit suggested in [[6](#Beauregard)]. More generally, the period finding circuit here is similar to implementation of [[6](#Beauregard)] except for the fact that the modular exponentiation and the Quantum Fourier Transform (QFT) at the end are realized on a full register of $2n$ qubits (not on a single qubit as in [[6](#Beauregard)]) and the circuit therefore includes $4n+2$ qubits. " ] }, { "cell_type": "markdown", - "id": "10d6a91c-2ce6-446e-b097-a897ca0f2418", + "id": "5", "metadata": {}, "source": [ "## Modular Addition\n", - "The basic building block in the modular exponentiation function is the doubly controlled modular adder in the Fourier space. This circuit relies on adders in the Fourier space, which add a classical number $a$ to a quantum register, and consist solely of single qubit phase gates as shown in the figure [[6](#Beauregard)]:" + "The basic building block in the modular exponentiation function is the doubly controlled modular adder in the Fourier space. This circuit relies on adders in the Fourier space, which add a classical number $a$ to a quantum register, and consist solely of single qubit phase gates as shown in the figure below [[6](#Beauregard)]." ] }, { @@ -83,7 +85,7 @@ } }, "cell_type": "markdown", - "id": "861c7f7d-7cef-4a37-8aa4-25a64f9ec71c", + "id": "6", "metadata": {}, "source": [ "![image.png](attachment:bc7f8672-d419-4bfe-a479-025c1017f057.png)" @@ -91,10 +93,10 @@ }, { "cell_type": "markdown", - "id": "272ad477-a17c-4cc4-b756-b819197d5128", + "id": "7", "metadata": {}, "source": [ - "The modular adder calculates $|\\phi(b)\\rangle \\rightarrow |\\phi(b+a)\\hspace{-8pt}\\mod N\\rangle$ where $|\\phi(b)\\rangle = QFT|b\\rangle$ is the input to the circuit and $a$ and $N$ are classical values hardwired into the circuit (specifically into the phases of the adder and inverse adder circuits). The input $|\\phi(b)\\rangle$ $(b < N)$ is encoded into an $n+1$ qubit register where $n$ is the size of $N$ such that the most significant bit of the register is an overflow bit, which is zero at the input and the output of the circuit. The circuit includes three adder functions and two inverse adders (subtractors) in the Fourier space as shown in the figure [[6](#Beauregard)]. (The thick bar on the right/left denotes an adder/inverse adder.) " + "The modular adder calculates $|\\phi(b)\\rangle \\rightarrow |\\phi(b+a)\\hspace{-8pt}\\mod N\\rangle$ where $|\\phi(b)\\rangle = QFT|b\\rangle$ is the input to the circuit and $a$ and $N$ are classical values hardwired into the circuit (specifically into the phases of the adder and inverse adder circuits). the input $|\\phi(b)\\rangle$ $(b < N)$ is encoded into an $n+1$ qubit register where $n$ in the size of $N$ such that the most significant bit of the register is an overflow bit which is zero at the input and the output of the circuit. The circuit includes three adder functions and two inverse adders (subtractors) in the Fourier space as shown in the figure below [[6](#Beauregard)] (the a thick bar on the right/left side denotes an adder/inverse adder). " ] }, { @@ -104,7 +106,7 @@ } }, "cell_type": "markdown", - "id": "0791bdb9-590b-40a4-9eb4-7953118609f9", + "id": "8", "metadata": {}, "source": [ "\n", @@ -114,25 +116,28 @@ { "attachments": {}, "cell_type": "markdown", - "id": "0e848d93-0ce4-48b2-9911-380b6473e92f", + "id": "9", "metadata": {}, "source": [ - "Dividing the circuit into three subcircuits and assuming that both control qubits are in state $|1\\rangle$, the state evolves as follows:\n", + "Dividing the circuit into 3 subcircuits and assuming that both control qubits are in state $|1\\rangle$ the state evolevs as follows:\n", "\n", - "- In subcircuit A, the value $a$ is added to $b$, that is $|\\phi(b)\\rangle \\rightarrow |\\phi(a+b)\\rangle$. \n", - "- In subcircuit B, $N$ is subtracted from $a+b$ and then added again if $N < a+b$. This is done by checking the MSB of $\\phi(a+b)$, which is in the state $|1\\rangle$ iff $N < a+b$. The value of the MSB is checked by applying inverse QFT to the b-register, copying the state of the MSB to an auxiliary qubit, and conditioning the addition of $N$ on the auxiliary qubit.\n", - "- In subcircuit C, the auxiliary qubit is reset to $|0\\rangle$, disentangling it from the b-register. This is done by first subtracting $a$ from the b-register, checking the MSB, flipping the auxiliary qubit if the MSB is in state $|0\\rangle$, and re-adding $a$ to the register.\n", + "- In subcircuit A the value $a$ is added to $b$, that is: $|\\phi(b)\\rangle \\rightarrow |\\phi(a+b)\\rangle$ \n", + "- In subcircuit B $N$ is subtracted from $a+b$ and then added again if $N < a+b$ – this is done by checking the msb of $\\phi(a+b)$ which will be in the state $|1\\rangle$ iff $N < a+b$. The value of the msb is checked by applying inverse QFT to the b-register and copying the state of the msb to an auxiliary qubit and conditioning the addition of $N$ on the auxiliary qubit.\n", + "- In subcircuit C the auxiliary qubit is reset to $|0\\rangle$, disentangling it from the b-register. this is done by first subtracting $a$ from the b-register checking whether the msb and fliping the auxiliary qubit if the msb is in state $|0\\rangle$ and re-adding $a$ to the register.\n", "\n", - "We define a QFT function without swap gates. In subcircuits B and C above there is no need to introduce swap gates (required for keeping the order of the qubits in a register). We keep track of the MSB (which, after the QFT, is at the LSB position) and apply the CNOT gate to the 'correct' qubit. The order reverses again after the application of the second QFT in each circuit. In other QFT functions (used later in modular multiplication and at the end of the circuit) we keep the swap gates for clarity.\n", + "In a first step we define a QFT function without swap gates. In subcircuits B and C above there is no need to introduce swap gates to (required for keeping the order of the qubits in a register). We simply keep track of the msb (which after the QFT will be at the the lsb position) and apply the CNOT gate to the 'correct' qubit. The order will be reversed again after the application of the second QFT in each circuit. In other QFT functions (used later in modular multiplication and at the end of the circuit) we keep the swap gates for clarity.\n", "\n" ] }, { "cell_type": "code", - "execution_count": 1, - "id": "792f975c-e7f8-4937-a1e8-837c9c3c7436", - "metadata": {}, - "outputs": [], + "id": "10", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:21:43.326698Z", + "start_time": "2025-01-02T13:21:39.188313Z" + } + }, "source": [ "import math\n", "\n", @@ -156,24 +161,29 @@ " count=qbv.len,\n", " iteration=lambda index: my_qft_step(qbv[index : qbv.len]),\n", " )" - ] + ], + "outputs": [], + "execution_count": 1 }, { "cell_type": "markdown", - "id": "28304e01-3f61-47a9-bf39-42ce35016ee1", + "id": "11", "metadata": {}, "source": [ - "The `ccmod_add` function implements the modular adder, which adds the (classical) number $a$ to the b-register modulo $N$ in the QFT space. The function receives $a$ and $N$ as `CInt`s: classical integer parameters. The uncontrolled, controlled, and doubly controlled adders in the QFT space are implemented by the `phase_lad` function. The function that checks the MSB of the b-register and conditionally flips the auxiliary qubit is `check_msb`. Note that at this stage, as we do not use SWAP after the QFT, the MSB is the first qubit." + "The function `ccmod_add` implements the modular adder which adds the (classical) number $a$ to the b-register modulo $N$ in the QFT space. The function receives $a$ and $N$ as `CInt`s: classical integers parameters. The un-controlled, controlled and doubly controlled adders in the QFT space are implemented by the function `phase_lad`. The functions which check the msb of the b-register and conditionally flip the auxiliary qubit is `check_msb`. Notice that at this stage, as we don't use SWAP after the QFT, the msb is the first qubit." ] }, { "cell_type": "code", - "execution_count": 2, - "id": "af7ebdcf-1357-4fca-9dbd-2b9d6994561f", - "metadata": {}, - "outputs": [], + "id": "12", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:21:43.341338Z", + "start_time": "2025-01-02T13:21:43.332404Z" + } + }, "source": [ - "from classiq import *\n", + "from classiq.qmod import QNum, bind, control, within_apply\n", "from classiq.qmod.builtins.classical_functions import qft_const_adder_phase\n", "\n", "\n", @@ -207,27 +217,33 @@ " phi_b: QArray[QBit], # b in fourier basis\n", " c1: QBit,\n", " c2: QBit,\n", - " aux: QBit,\n", ") -> None:\n", " ctrl = QArray(\"ctrl\")\n", - " bind([c1, c2], ctrl)\n", - " control(ctrl, lambda: phase_lad(a, phi_b))\n", - " invert(lambda: phase_lad(N, phi_b))\n", - " check_msb(1, phi_b, aux)\n", - " control(aux, lambda: phase_lad(N, phi_b))\n", + " aux = QBit(\"aux\")\n", + "\n", " within_apply(\n", - " lambda: invert(lambda: control(ctrl, lambda: phase_lad(a, phi_b))),\n", - " lambda: check_msb(0, phi_b, aux),\n", - " )\n", - " bind(ctrl, [c1, c2])" - ] + " lambda: [allocate(1, aux), bind([c1, c2], ctrl)],\n", + " lambda: [\n", + " control(ctrl, lambda: phase_lad(a, phi_b)),\n", + " invert(lambda: phase_lad(N, phi_b)),\n", + " check_msb(1, phi_b, aux),\n", + " control(aux, lambda: phase_lad(N, phi_b)),\n", + " within_apply(\n", + " lambda: invert(lambda: control(ctrl, lambda: phase_lad(a, phi_b))),\n", + " lambda: check_msb(0, phi_b, aux),\n", + " ),\n", + " ],\n", + " )" + ], + "outputs": [], + "execution_count": 2 }, { "cell_type": "markdown", - "id": "ebd310e1-8e12-4114-828a-30db70957e05", + "id": "13", "metadata": {}, "source": [ - "The phases for the QFT-based quantum adder are generated using the built-in `qft_const_adder_phase` function, which implements this logic:\n", + "The phases for the QFT-based quantum adder are generated using the builtin-function `qft_const_adder_phase`, which implements the following logic:\n", "\n", "```python\n", "def qft_const_adder_phase(bit_index: int, value: int, reg_len: int) -> int:\n", @@ -239,35 +255,35 @@ }, { "cell_type": "markdown", - "id": "28e50aa8-43e2-4164-a7c9-d3e6d96a8acc", + "id": "14", "metadata": {}, "source": [ - "To check the modular addition circuit, we create a main function that includes `ccmod_add` between a QFT at the beginning and an inverse QFT at the end. We set the classical input to the modulo number (15) and the classical value to add (9), and set the input state of the b-register by applying X-gates to chosen qubits (here we flipped the fourth qubit so the value of the register is 8)." + "In order to check the modular addition circuit we create a main function which includes the `ccmod_add` between a QFT at the beginning and an inverse QFT at the end. We set the classical input the modulo number (15) and the classical value to add (9), and set the input state of the b-register by applying X-gates to chosen qubits (here we flipped the forth qubit so the value of the register is 8)." ] }, { "cell_type": "code", - "execution_count": 3, - "id": "6af345fa-92a7-481e-bd3e-ccdaa6a2fa99", + "id": "15", "metadata": { - "scrolled": true + "ExecuteTime": { + "end_time": "2025-01-02T13:21:43.717982Z", + "start_time": "2025-01-02T13:21:43.577245Z" + } }, - "outputs": [], "source": [ - "from classiq import *\n", + "from classiq.qmod import QNum\n", "\n", - "modulo_num = 15\n", - "reg_len = math.ceil(math.log(modulo_num, 2)) + 1\n", - "a_num = 9\n", + "modulo_num_1 = 15\n", + "reg_len_1 = math.ceil(math.log(modulo_num_1, 2)) + 1\n", + "a_num_1 = 9\n", "\n", "b_initial_value = 8\n", "\n", "\n", "@qfunc\n", - "def main(b: Output[QNum], ctrl: Output[QArray[2]], aux: Output[QBit]) -> None:\n", - " allocate(reg_len, b)\n", + "def main(b: Output[QNum], ctrl: Output[QArray[2]]) -> None:\n", + " allocate(reg_len_1, b)\n", " allocate(2, ctrl)\n", - " allocate(1, aux)\n", "\n", " # set initial values for the addition\n", " b ^= b_initial_value\n", @@ -276,16 +292,18 @@ "\n", " # perform the addition in fourier basis and then transform back\n", " within_apply(\n", - " lambda: qft(b), lambda: ccmod_add(modulo_num, a_num, b, ctrl[0], ctrl[1], aux)\n", + " lambda: qft(b), lambda: ccmod_add(modulo_num_1, a_num_1, b, ctrl[0], ctrl[1])\n", " )\n", "\n", "\n", "qmod_1 = create_model(main, out_file=\"doubly_controlled_modular_adder\")" - ] + ], + "outputs": [], + "execution_count": 3 }, { "cell_type": "markdown", - "id": "03910079-42de-4cf5-9604-b1ced1d9b510", + "id": "16", "metadata": {}, "source": [ "Once we have created a model of the circuit, we can synthesize it and view the circuit." @@ -293,24 +311,31 @@ }, { "cell_type": "code", - "execution_count": 4, - "id": "4d1ccf9b-eea7-42a3-86c6-f0ea815fb32f", - "metadata": {}, + "id": "17", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:21:51.859892Z", + "start_time": "2025-01-02T13:21:43.734126Z" + } + }, + "source": [ + "qprog_1 = synthesize(qmod_1)\n", + "show(qprog_1)" + ], "outputs": [ { "name": "stdout", "output_type": "stream", - "text": [] + "text": [ + "Opening: https://platform.classiq.io/circuit/2r4hAXDCzeS1lc4zZ2IRMXvpGpY?version=0.64.0\n" + ] } ], - "source": [ - "qprog_1 = synthesize(qmod_1)\n", - "show(qprog_1)" - ] + "execution_count": 4 }, { "cell_type": "markdown", - "id": "20ac31c9-e566-4149-a398-3a843c22538f", + "id": "18", "metadata": {}, "source": [ "We now can execute the synthesized circuit on a simulator (we use the default simulator) and check the outcome." @@ -318,38 +343,43 @@ }, { "cell_type": "code", - "execution_count": 5, - "id": "8ce57936-ca1c-40ed-a6a0-e3505c3e2de9", - "metadata": {}, + "id": "19", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:21:55.715794Z", + "start_time": "2025-01-02T13:21:51.935999Z" + } + }, + "source": [ + "result_1 = execute(qprog_1).result_value()\n", + "print(result_1.parsed_states)" + ], "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'01100010': {'b': 2.0, 'ctrl': [1, 1], 'aux': 0.0}}\n" + "{'1100010': {'b': 2, 'ctrl': [1, 1]}}\n" ] } ], - "source": [ - "result_1 = execute(qprog_1).result_value()\n", - "print(result_1.parsed_states)" - ] + "execution_count": 5 }, { "cell_type": "markdown", - "id": "50bbc2f4-e400-4173-a0b9-e8e9c3a6a96f", + "id": "20", "metadata": {}, "source": [ - "As expected, the value of the b-register is $2=8+9 \\hspace{-4pt}\\mod \\hspace{-3pt}15$." + "As expected the value of the b-register is $2=8+9 \\hspace{-4pt}\\mod \\hspace{-3pt}15$" ] }, { "cell_type": "markdown", - "id": "93a5f429-b81a-40b8-b3d4-d155474f004c", + "id": "21", "metadata": {}, "source": [ - "## Modular Multiplication\n", - "A controlled modular multiplication circuit that receives $b$ (in the b-register) as input and $x$ in additional x-register and outputs $|(b+xa) mod N\\rangle$ comprises repeated application of the doubly controlled modular adder, adding $2^{i}a$ for $i=0,...,n-1$ where one of the controls in each of the modular adders is the suitable qubit of the x-register, as in the following figure [[6](#Beauregard)]." + "## Modular multiplication\n", + "A controlled modular multiplication circuit which receives as input $b$ (in the b-register) and $x$ in additional x-register and outputs $|(b+xa) \\hspace{-8pt}\\mod \\hspace{-4pt} N\\rangle$ is comprised of repeated application of the doubly controlled modular adder adding $2^{i}a$ for $i=0,...,n-1$ where one of the controls in each of the modular adder is the suitable qbit of the x-register as in the following figure [[6](#Beauregard)]." ] }, { @@ -359,7 +389,7 @@ } }, "cell_type": "markdown", - "id": "5d832454-e091-4cc2-af9b-ae54f813d836", + "id": "22", "metadata": {}, "source": [ "![image.png](attachment:612a0423-454e-4a1a-8374-65e694a6e365.png)" @@ -367,18 +397,21 @@ }, { "cell_type": "markdown", - "id": "75099a4a-fe73-462a-8157-9c4bba8c0b9b", + "id": "23", "metadata": {}, "source": [ - "The `cmod_mult` function implements the above circuit." + "The `cmod_mult` function implements the above circuit and." ] }, { "cell_type": "code", - "execution_count": 6, - "id": "1f000418-d89b-4e6f-bb7c-56437e4c645c", - "metadata": {}, - "outputs": [], + "id": "24", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:21:55.747406Z", + "start_time": "2025-01-02T13:21:55.743154Z" + } + }, "source": [ "@qfunc\n", "def cmod_mult(\n", @@ -387,26 +420,27 @@ " b: QArray[QBit],\n", " x: QArray[QBit],\n", " ctrl: QBit,\n", - " aux: QBit,\n", ") -> None:\n", " within_apply(\n", " lambda: qft(b),\n", " lambda: repeat(\n", " count=x.len,\n", " iteration=lambda index: ccmod_add(\n", - " N, (a * (2**index)) % N, b, x[index], ctrl, aux\n", + " N, (a * (2**index)) % N, b, x[index], ctrl\n", " ),\n", " ),\n", " )" - ] + ], + "outputs": [], + "execution_count": 6 }, { "attachments": {}, "cell_type": "markdown", - "id": "846943ee-7d81-444b-bea0-36de82110e70", + "id": "25", "metadata": {}, "source": [ - "The above circuit outputs the state $|(b+xa) \\mod N\\rangle$ in the b-register (assuming the control is in state $|1\\rangle$); however, the required output is $|xa \\mod N\\rangle$. This output can be obtained by conditionally swapping the b and x registers and applying the inverse of the modular multiplication circuit for the $a^{-1} \\mod N$ classical value with input $b=0$, as in the following construction [[6](#Beauregard)].\n", + "The above circuit outputs the state $|(b+xa)\\hspace{-8pt}\\mod \\hspace{-3pt}N\\rangle$ in the b-register (assuming the control is in state $|1\\rangle$) however the required output is $|xa \\mod N\\rangle$. This output can be obtained by conditionally swapping the b and x registers and applying the inverse of the modular multiplication circuit for the $a^{-1} \\mod N$ classical value with input $b=0$, as in the following construction [[6](#Beauregard)].\n", " " ] }, @@ -417,7 +451,7 @@ } }, "cell_type": "markdown", - "id": "d8bd9535-4270-4f53-8842-26e0e227ab12", + "id": "26", "metadata": {}, "source": [ "![image.png](attachment:227d3268-5463-478e-9d65-0699493058ed.png)" @@ -425,21 +459,24 @@ }, { "cell_type": "markdown", - "id": "c63e48de-c219-4ff1-8e6e-0cbc9d44c88c", + "id": "27", "metadata": {}, "source": [ - "After the swap, the x and b registers are in the state $|ax\\hspace{-6pt} \\mod \\hspace{-4pt}N\\rangle|x\\rangle$ and the inverse multiplication function (by $a^{-1}$) sends them to the state \n", - "$|ax\\hspace{-6pt} \\mod\\hspace{-4pt} N\\rangle|x- a^{-1}ax\\rangle = |ax \\mod N\\rangle|0\\rangle$. Thus, the x-register carries the required output while the state of the b-register is $|0\\rangle$ at the output. The `cmod_mult_pair` function implements this circuit using the `c_swap` and `creg_swap` functions, which implement the swap between qubits and registers, respectively. " + "After the swap the x and b registers are in the state $|ax\\hspace{-6pt} \\mod \\hspace{-4pt}N\\rangle|x\\rangle$ and the inverse multiplication function (by $a^{-1}$) will send them to the state \n", + "$|ax\\hspace{-6pt} \\mod\\hspace{-4pt} N\\rangle|x- a^{-1}ax\\rangle = |ax \\hspace{-6pt}\\mod \\hspace{-3pt} N\\rangle|0\\rangle$. Thus, the x-register carries the required output while the state of the b-register is $|0\\rangle$ at the output. The `cmod_mult_pair` function implements this circuit using the `c_swap` and `creg_swap` functions which implement swap between qubits and registers respectively. " ] }, { "cell_type": "code", - "execution_count": 7, - "id": "5601891d-500d-409a-88ac-f8a833520d45", - "metadata": {}, - "outputs": [], + "id": "28", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:21:55.773684Z", + "start_time": "2025-01-02T13:21:55.764517Z" + } + }, "source": [ - "from classiq import *\n", + "from classiq.qmod import SWAP, free\n", "from classiq.qmod.symbolic import min, mod_inverse\n", "\n", "\n", @@ -457,7 +494,6 @@ " a: CInt,\n", " x: QArray[QBit],\n", " ctrl: QBit,\n", - " aux: QBit,\n", ") -> None:\n", " b = QArray(\"b\")\n", " allocate(x.len + 1, b)\n", @@ -468,7 +504,6 @@ " b,\n", " x,\n", " ctrl,\n", - " aux,\n", " )\n", " control(ctrl, lambda: multi_swap(x, b))\n", " invert(\n", @@ -478,77 +513,80 @@ " b,\n", " x,\n", " ctrl,\n", - " aux,\n", " )\n", " )\n", " free(b)" - ] + ], + "outputs": [], + "execution_count": 7 }, { "cell_type": "markdown", - "id": "330131a8-27a6-43d0-b1ba-365b3068a9e4", + "id": "29", "metadata": {}, "source": [ "## Modular Exponentiation\n", - "The above circuit can be applied repeatedly to achieve modular exponentiation. Specifically, taking an $m$ qubit register carrying the value $M$ and applying the `cmod_mult_pair` function $M$ times in sequence, cascading the control over the qubits of m-register, and multiplying by values $a^{2^{0}}, ..., a^{2^{m-1}}$ takes the input state $|M\\rangle_{m}|1\\rangle_{x}|0\\rangle_{b}$ to the output state \n", - "$|M\\rangle_{m}|a^{M}\\mod N\\rangle_{x}|0\\rangle_{b}$ as required. (For clarity, subscripts are added to identify the registers.) The `mod_exp_fuc` function below implements the modular exponentiation and accepts the classical numbers $N$ and $a$.\n" + "The above circuit can be applied repeatedly to achieve modular exponentiation. Specifically, taking an $m$ qubit $power$ register (carrying value $M$) and applying the `cmod_mult_pair` function $m$ times in sequence cascading the control over the qubits of m-register multiplying by values $a^{2^{0}}, ..., a^{2^{m-1}}$ will take the input state $|M\\rangle_{power}|1\\rangle_{x}|0\\rangle_{b}$ to the output state $|M\\rangle_{power}|a^{M}\\hspace{-6pt}\\mod \\hspace{-3pt} N\\rangle_{x}|0\\rangle_{b}$ as required (for clarity subscripts were added to identify the registers). The `mod_exp_fuc` below accepts the classical numbers $N$ and $a$ and implements the modular exponentiation." ] }, { "cell_type": "code", - "execution_count": 8, - "id": "0ce01115-e6f2-4ee1-9353-2a34b86685fe", - "metadata": {}, - "outputs": [], + "id": "30", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:21:55.793038Z", + "start_time": "2025-01-02T13:21:55.789368Z" + } + }, "source": [ "@qfunc\n", "def mod_exp_func(\n", " N: CInt,\n", " a: CInt,\n", " x: QArray[QBit],\n", - " m: QArray[QBit],\n", - " aux: QBit,\n", + " power: QArray[QBit],\n", ") -> None:\n", " repeat(\n", - " count=m.len,\n", + " count=power.len,\n", " iteration=lambda index: cmod_mult_pair(\n", - " N, (a ** (2**index)) % N, x, m[index], aux\n", + " N, (a ** (2**index)) % N, x, power[index]\n", " ),\n", " )" - ] + ], + "outputs": [], + "execution_count": 8 }, { "cell_type": "markdown", - "id": "6b709bb1-15f8-4bdf-a199-3df309e1545c", + "id": "31", "metadata": {}, "source": [ - "## Implementing Quantum Period Finding\n", - "Using the modular exponentiation function, it is straightforward to implement the complete period finding algorithm. We apply the Hadamard transform to the m-register at the beginning of the circuit and an inverse QFT at the end. " + "## Quantum Period Finding\n", + "Using the modular exponentiation function it is straightforward to implement the complete period finding algorithm - one needs to apply the Hadamard transform to the $power$ register at the beginning of the circuit and an inverse QFT at the end. " ] }, { "cell_type": "code", - "execution_count": 9, - "id": "8ef24bb4-b063-42d6-a5ea-ddcd5310ae13", - "metadata": {}, - "outputs": [], + "id": "32", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:21:55.812621Z", + "start_time": "2025-01-02T13:21:55.808751Z" + } + }, "source": [ - "from classiq import *\n", - "\n", - "modulo_num = 6\n", - "reg_len = math.ceil(math.log(modulo_num, 2)) + 1\n", - "a_num = 5\n", + "modulo_num = 21 # The number we wish to factor\n", + "x_len = math.ceil(math.log(modulo_num, 2))\n", + "a_num = 11 # Should be coprime with modulo_num\n", "\n", "\n", "@qfunc\n", "def main(\n", " x: Output[QNum],\n", - " power: Output[QArray[2 * (reg_len - 1)]],\n", - " aux: Output[QBit],\n", + " power: Output[QNum],\n", ") -> None:\n", - " allocate(reg_len - 1, x)\n", - " allocate(2 * (reg_len - 1), power)\n", - " allocate(1, aux)\n", + " allocate(x_len, x)\n", + " allocate(2 * x_len, power)\n", "\n", " hadamard_transform(power)\n", " x ^= 1\n", @@ -558,46 +596,79 @@ " a_num,\n", " x,\n", " power,\n", - " aux,\n", " )\n", " invert(lambda: qft(power))" - ] + ], + "outputs": [], + "execution_count": 9 }, { "cell_type": "code", - "execution_count": 10, - "id": "8e9d0b08-f3f8-4e95-a468-bfeec010d97c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [] + "id": "33", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:23:15.418828Z", + "start_time": "2025-01-02T13:21:55.828586Z" } - ], + }, "source": [ - "constraints = Constraints(max_width=14)\n", + "max_circuit_width = 4 * x_len + 2\n", "qmod_2 = create_model(\n", - " main, constraints=constraints, out_file=\"shor_modular_exponentiation\"\n", + " entry_point=main,\n", + " out_file=\"shor_modular_exponentiation\",\n", + " constraints=Constraints(max_width=max_circuit_width),\n", + " preferences=Preferences(optimization_level=1),\n", ")\n", "\n", "qprog_2 = synthesize(qmod_2)\n", "show(qprog_2)" + ], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Opening: https://platform.classiq.io/circuit/2r4hKyE8Sg14YsW4lN2xISc2fvb?version=0.64.0\n" + ] + } + ], + "execution_count": 10 + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "### Output values for 2048 shots\n", + "Let us first examine the $x$ register which keeps a superposition of values $a^j$ for all possible values of $j$. \n", + "Namely $11^{y}\\hspace{-6pt}\\mod \\hspace{-3pt}N$ for $y=0,...r-1$ where $r$ is the period we wish to obtain. In each line below appears the measured value of the $x$ register with the corresponding number of counts. " ] }, { "cell_type": "code", - "execution_count": 11, - "id": "0aef2760-aa9a-4710-8c60-08333a0a9b0b", - "metadata": {}, + "id": "35", + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-02T13:25:50.730082Z", + "start_time": "2025-01-02T13:23:15.537341Z" + } + }, + "source": [ + "job = execute(qprog_2)\n", + "result_2 = job.result()[0].value\n", + "x_results = result_2.parsed_counts_of_outputs(\"x\")\n", + "x_results" + ], "outputs": [ { "data": { "text/plain": [ - "[{'x': [1, 0, 1], 'power': [0, 0, 0, 0, 0, 1], 'aux': 0.0}: 257,\n", - " {'x': [1, 0, 1], 'power': [0, 0, 0, 0, 0, 0], 'aux': 0.0}: 254,\n", - " {'x': [1, 0, 0], 'power': [0, 0, 0, 0, 0, 1], 'aux': 0.0}: 254,\n", - " {'x': [1, 0, 0], 'power': [0, 0, 0, 0, 0, 0], 'aux': 0.0}: 235]" + "[{'x': 1}: 360,\n", + " {'x': 8}: 354,\n", + " {'x': 4}: 342,\n", + " {'x': 11}: 333,\n", + " {'x': 16}: 332,\n", + " {'x': 2}: 327]" ] }, "execution_count": 11, @@ -605,30 +676,177 @@ "output_type": "execute_result" } ], + "execution_count": 11 + }, + { + "cell_type": "markdown", + "id": "36", + "metadata": {}, + "source": [ + "Since we have a large number of shots and only six values are measured in register $x$: $1,11,16,8,4,2$ (corresponding to $11$ to the power of $0,...,5$ modulo $21$ respectively) we can conclude that $r=6$. Note however that this is not how Shor's algorithm proceeds since the number of required measurements is clearly non-scalable. \n", + "\n", + "#### Finding the period using the continued fraction algoritm\n", + "In order to find $r$ efficiently we need to use the continued fraction algorithm on the measured outcome of the $power$ register (which is the only register we need to measure).\n", + "We can examine a histogram of the measured results (using the filters option on the top right to view the $power$ register outcomes) on the classiq IDE. An image of such a histogram is shown below." + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "