From afc514809be3d2b6ac4491345d82e4522d78eb8b Mon Sep 17 00:00:00 2001 From: ermalrrapaj Date: Wed, 31 Jan 2024 22:33:02 -0800 Subject: [PATCH] added qft tutorial --- 3_quantum_fourier_transform.ipynb | 496 ++++++++++++++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 3_quantum_fourier_transform.ipynb diff --git a/3_quantum_fourier_transform.ipynb b/3_quantum_fourier_transform.ipynb new file mode 100644 index 0000000..37aa00d --- /dev/null +++ b/3_quantum_fourier_transform.ipynb @@ -0,0 +1,496 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "52e9205f", + "metadata": {}, + "source": [ + "# Quantum Fourier Transform with BQSKit\n", + "---\n", + "![bqskit](https://bqskit.lbl.gov/wp-content/uploads/sites/6/2021/02/BQSKit_header-1.png)\n", + "\n", + "The Berkeley Quantum Synthesis Toolkit (BQSKit) is a powerful and portable quantum compiler framework. This tutorial explores how to use BQSKit to compile the quantum phase estimation algorithm. \n", + "\n", + "To **install** BQSKit and other required packages, follow the instructions in the [README](https://github.com/BQSKit/bqskit-tutorial/blob/main/README.md)." + ] + }, + { + "cell_type": "markdown", + "id": "b266d12d", + "metadata": {}, + "source": [ + "## Fourier transform for wavefunction amplitude \n", + "\n", + "\n", + "The Fourier transform occurs in many different areas ranging from signal processing, data compression, complexity theory, to high energy physics. It is an integral transform that converts a function into a form that describes the frequencies present in the original function. The output of the transform is a complex-valued function of frequency. The Fourier transform is analogous to decomposing the sound of a musical chord into the intensities of its constituent pitches. The mathematical expression for the transform is,\n", + "\n", + "$$\n", + "\\widehat{f}(\\xi) = \\int_{-\\infty}^{\\infty} f(x)\\ e^{-i 2\\pi \\xi x}\\,dx.\n", + "$$\n", + "\n", + "The inverse Fourier transform is,\n", + "\n", + "$$f(x) = \\int_{-\\infty}^{\\infty} \\widehat f(\\xi)\\ e^{i 2 \\pi \\xi x}\\,d\\xi.$$\n", + "\n", + "\n", + "The discrete Fourier transform acts on a vector $(x_0, ..., x_{N-1})$ and maps it to the vector $(y_0, ..., y_{N-1})$ according to the formula\n", + "\n", + "\n", + "$$y_k = \\frac{1}{\\sqrt{N}}\\sum_{j=0}^{N-1}x_j\\omega_N^{jk}$$\n", + "\n", + "\n", + "where $\\omega_N^{jk} = e^{2\\pi i \\frac{jk}{N}}$. The quantum Fourier transform (QFT) is the quantum implementation of the discrete Fourier transform over the amplitudes of a wavefunction $|X\\rangle = \\sum_k x_k | k \\rangle$, to the wavefunction $|Y\\rangle = \\sum_k y_k | k \\rangle$, based on the same formula.\n", + "This can also be stated as unitary transformation, \n", + "$U_{QFT}=\\frac{1}{\\sqrt{N}}\\sum_{j=0}^{N-1}\\sum_{k=0}^{N-1}\\omega^{jk}_{N}\\vert k \\rangle \\langle j\\vert$.\n", + "\n", + "Thus,\n", + "\n", + "$$\\begin{equation}\n", + "\\begin{split}\n", + "U_{QFT} |X\\rangle =& \\frac{1}{\\sqrt{N}}\\sum_{j=0}^{N-1}\\sum_{k=0}^{N-1}\\omega^{jk}_{N}|k \\rangle \\langle j| \\\\\n", + "=& \\frac{1}{\\sqrt{N}} \\sum_{j=0}^{N-1} 2^{2\\pi i (\\sum_{k=1}^n x_k/2^n)} |x_1...x_n \\rangle \\\\\n", + "=&\\frac{1}{2^n}\\bigotimes_{k=1}^n \\left(| 0 \\rangle + e^{2\\pi i \\frac{x}{2^k}} | 1\\rangle \\right)\\\\\n", + "=& \\frac{1}{\\sqrt{N}}\n", + "\\left(\\vert0\\rangle + e^{\\frac{2\\pi i}{2}x} \\vert1\\rangle\\right) \n", + "\\otimes\n", + "\\left(\\vert0\\rangle + e^{\\frac{2\\pi i}{2^2}x} \\vert1\\rangle\\right) \n", + "\\otimes \n", + "\\ldots\n", + "\\otimes\n", + "\\left(\\vert0\\rangle + e^{\\frac{2\\pi i}{2^{n-1}}x} \\vert1\\rangle\\right) \n", + "\\otimes\n", + "\\left(\\vert0\\rangle + e^{\\frac{2\\pi i}{2^n}x} \\vert1\\rangle\\right) \n", + "\\end{split}\n", + "\\end{equation}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "1e3b3185", + "metadata": {}, + "source": [ + "### Single qubit QFT \n", + "\n", + "Consider how the QFT operator as defined above acts on a single qubit state $\\vert\\psi\\rangle = \\alpha \\vert 0 \\rangle + \\beta \\vert 1 \\rangle$. In this case, $x_0 = \\alpha$, $x_1 = \\beta$, and $n = 1$. Then,\n", + "\n", + "\n", + "\n", + "$$y_0 = \\frac{1}{\\sqrt{2}}\\left( \\alpha \\exp\\left(2\\pi i\\frac{0\\times0}{2}\\right) + \\beta \\exp\\left(2\\pi i\\frac{1\\times0}{2}\\right) \\right) = \\frac{1}{\\sqrt{2}}\\left(\\alpha + \\beta\\right),$$\n", + "\n", + "\n", + "\n", + "and\n", + "\n", + "\n", + "\n", + "$$y_1 = \\frac{1}{\\sqrt{2}}\\left( \\alpha \\exp\\left(2\\pi i\\frac{0\\times1}{2}\\right) + \\beta \\exp\\left(2\\pi i\\frac{1\\times1}{2}\\right) \\right) = \\frac{1}{\\sqrt{2}}\\left(\\alpha - \\beta\\right).$$\n", + "\n", + "\n", + "\n", + "The final resulting state is \n", + "\n", + "\n", + "\n", + "$$\n", + "\\begin{equation}\n", + "\\begin{split}\n", + "U_{QFT}\\vert\\psi\\rangle =& \\frac{1}{\\sqrt{2}}(\\alpha + \\beta) \\vert 0 \\rangle + \\frac{1}{\\sqrt{2}}(\\alpha - \\beta) \\vert 1 \\rangle\\\\\n", + "=& \\frac{1}{\\sqrt{2}}\\begin{bmatrix} 1 & 1 \\\\ 1 & -1 \\end{bmatrix} \\vert\\psi\\rangle \\\\\n", + "=& H \\vert\\psi\\rangle\n", + "\\end{split}\n", + "\\end{equation}\n", + "$$\n", + "\n", + "\n", + "\n", + "This operation is exactly the result of applying the Hadamard operator $H$ on the qubit." + ] + }, + { + "cell_type": "markdown", + "id": "d972f0ee", + "metadata": {}, + "source": [ + "## Multiple qubit circuit implementation \n", + "\n", + "Two are the main gates used for the circuit. The first one is a single-qubit Hadamard gate $H$, and the second is a twoo-qubit controlled rotation $CROT_k$ given in block-diagonal form as \n", + "\n", + "$$CR_k = \\left[\\begin{matrix}\n", + "I&0\\\\\n", + "0&U_k\\\\\n", + "\\end{matrix}\\right]$$\n", + "\n", + "where \n", + "\n", + "$$U_k = \\left[\\begin{matrix}\n", + "1&0\\\\\n", + "0&\\exp\\left(\\frac{2\\pi i}{2^k}\\right)\\\\\n", + "\\end{matrix}\\right]$$\n", + "\n", + "The action of $CR_k$ on a two-qubit state is given by\n", + "\n", + "\n", + "\n", + "$$CR_k\\vert 0x_j\\rangle = \\vert 0x_j\\rangle, \\ CR_k\\vert 1x_j\\rangle = \\exp\\left( \\frac{2\\pi i}{2^k}x_j \\right)\\vert 1x_j\\rangle$$\n", + "\n", + "\n", + "\n", + "We start with an n-qubit input state $\\vert x_1x_2\\ldots x_n\\rangle$.\n", + "\n", + "
    \n", + "
  1. After the first Hadamard gate on qubit 1, the state is transformed from the input state to \n", + "\n", + "$$\n", + "H_1\\vert x_1x_2\\ldots x_n\\rangle = \n", + "\\frac{1}{\\sqrt{2}}\n", + "\\left[\\vert0\\rangle + \\exp\\left(\\frac{2\\pi i}{2}x_1\\right)\\vert1\\rangle\\right]\n", + "\\otimes\n", + "\\vert x_2x_3\\ldots x_n\\rangle\n", + "$$\n", + "\n", + "
  2. After the $U_2$ gate on qubit 1 controlled by qubit 2, the state is transformed to\n", + "\n", + "$$\n", + "\\frac{1}{\\sqrt{2}}\n", + "\\left[\\vert0\\rangle + \\exp\\left(\\frac{2\\pi i}{2^2}x_2 + \\frac{2\\pi i}{2}x_1\\right)\\vert1\\rangle\\right]\n", + "\\otimes\n", + "\\vert x_2x_3\\ldots x_n\\rangle\n", + "$$\n", + "\n", + "
  3. Repeating the process up to qubit $n$, the state becomes\n", + "\n", + "$$\n", + "\\frac{1}{\\sqrt{2}}\n", + "\\left[\\vert0\\rangle + \n", + "\\exp\\left(\n", + "\\frac{2\\pi i}{2^n}x_n + \n", + "\\frac{2\\pi i}{2^{n-1}}x_{n-1} + \n", + "\\ldots + \n", + "\\frac{2\\pi i}{2^2}x_2 + \n", + "\\frac{2\\pi i}{2}x_1\n", + "\\right)\n", + "\\vert1\\rangle\\right]\n", + "\\otimes\n", + "\\vert x_2x_3\\ldots x_n\\rangle\n", + "$$\n", + "\n", + "Noting that \n", + "\n", + "$$\n", + "x = 2^{n-1}x_1 + 2^{n-2}x_2 + \\ldots + 2^1x_{n-1} + 2^0x_n\n", + "$$\n", + "\n", + "we can write the above state as \n", + "\n", + "$$\n", + "\\frac{1}{\\sqrt{2}}\n", + "\\left[\\vert0\\rangle + \n", + "\\exp\\left(\n", + "\\frac{2\\pi i}{2^n}x \n", + "\\right)\n", + "\\vert1\\rangle\\right]\n", + "\\otimes\n", + "\\vert x_2x_3\\ldots x_n\\rangle\n", + "$$\n", + "\n", + "
  4. After the application of a similar sequence of gates for qubits $2\\ldots n$, we find the final state to be:\n", + "\n", + "$$\n", + "\\frac{1}{\\sqrt{2}}\n", + "\\left[\\vert0\\rangle + \n", + "\\exp\\left(\n", + "\\frac{2\\pi i}{2^n}x \n", + "\\right)\n", + "\\vert1\\rangle\\right]\n", + "\\otimes\n", + "\\frac{1}{\\sqrt{2}}\n", + "\\left[\\vert0\\rangle + \n", + "\\exp\\left(\n", + "\\frac{2\\pi i}{2^{n-1}}x \n", + "\\right)\n", + "\\vert1\\rangle\\right]\n", + "\\otimes\n", + "\\ldots\n", + "\\otimes\n", + "\\frac{1}{\\sqrt{2}}\n", + "\\left[\\vert0\\rangle + \n", + "\\exp\\left(\n", + "\\frac{2\\pi i}{2^{2}}x \n", + "\\right)\n", + "\\vert1\\rangle\\right]\n", + "\\otimes\n", + "\\frac{1}{\\sqrt{2}}\n", + "\\left[\\vert0\\rangle + \n", + "\\exp\\left(\n", + "\\frac{2\\pi i}{2^{1}}x \n", + "\\right)\n", + "\\vert1\\rangle\\right]\n", + "$$\n", + "\n", + "This is $|Y\\rangle$ the order of the qubits is reversed in the output state. To obtain the right order, we perform swap gates. \n", + " \n", + " \n", + "TODO: include comments about numeric precision! \n", + " \n", + "The QFT function is written as follows, \n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "78f4c754", + "metadata": {}, + "outputs": [], + "source": [ + "from bqskit import Circuit\n", + "from bqskit.ir.gates import XGate, HGate, CPGate, SwapGate\n", + "import numpy as np\n", + "\n", + "# build the QFT function \n", + "def QFT(qcirc,n,initial=0,inverse=False,swap=True,nmax=16): # add option for final swaps, check that precision option as it should\n", + " \"\"\"\n", + " Args:\n", + " \n", + " qcirc: quantum circuit instance in BQSkit\n", + " n: the number of consecutive qubits QFT will act on\n", + " initial: the index of the first qubit QFT will act on\n", + " inverse: whether to apply QFT or QFT^(-1)\n", + " swap: whether to apply swap gates to revert the qubit order back to the original, \n", + " by default the order is reversered\n", + " nmax: the maximal value of n allowed for computations as precision is limited\n", + " Raises:\n", + " Warning: if n>nmax\n", + " \n", + " Returns: \n", + " No return, all gates required for QFT are applied on qicrc and the circuit is modified accordingly \n", + " \"\"\"\n", + " assert n <= qcirc.num_qudits\n", + " if n>nmax:\n", + " print(\" n = \"+str(n)+\" is higher than \"+str(nmax)+\", no QFT will be performed!\")\n", + " \n", + " if not inverse:\n", + " # repeated H and CR_k applications\n", + " for i in range(n-1+initial,-1+initial,-1):\n", + " qcirc.append_gate(HGate(),i)\n", + " p=0\n", + " for j in range(initial,i):\n", + " qcirc.append_gate(CPGate(),[i-j-1,i],[np.pi/(2**p)]) # apply control phases with the power of 2^distance\n", + " p+=1\n", + " \n", + " if swap:\n", + " # swap gates to fix the order of the qubits\n", + " for i in range(initial,initial+int(n//2)):\n", + " qcirc.append_gate(SwapGate(),[i,n-i-1+initial]) # swap the first with last etc until the middle of the circuit\n", + " else: # the inverse circuit is also reveresed in order of gate applications \n", + " if swap:\n", + " # swap gates to fix the order of the qubits\n", + " for i in range(initial,initial+int(n//2)):\n", + " qcirc.append_gate(SwapGate(),[i,n-i-1+initial]) # swap the first with last etc until the middle of the circuit \n", + " \n", + " # repeated H and CR_k applications with revesed order and sign for the phases\n", + " for i in range(initial,n+initial):\n", + " p=i\n", + " for j in range(initial,i):\n", + " p-=1\n", + " qcirc.append_gate(CPGate(),[j,i],[-np.pi/(2**p)]) # apply control phases with the power of 2^distance\n", + " qcirc.append_gate(HGate(),i)" + ] + }, + { + "cell_type": "markdown", + "id": "87694c69", + "metadata": {}, + "source": [ + "## Example: 4 qubit QFT\n", + "\n", + "We start with $\\vert \\Psi \\rangle = \\vert 0101 \\rangle$." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "2fcff6f7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Circuit(4)\n", + "\t[None, XGate@(1,), None, XGate@(3,)]\n", + "\t[None, None, None, HGate@(3,)]\n", + "\t[None, None, CPGate([3.141592653589793])@(2, 3), CPGate([3.141592653589793])@(2, 3)]\n", + "\t[None, CPGate([1.5707963267948966])@(1, 3), HGate@(2,), CPGate([1.5707963267948966])@(1, 3)]\n", + "\t[CPGate([0.7853981633974483])@(0, 3), CPGate([3.141592653589793])@(1, 2), CPGate([3.141592653589793])@(1, 2), CPGate([0.7853981633974483])@(0, 3)]\n", + "\t[CPGate([1.5707963267948966])@(0, 2), HGate@(1,), CPGate([1.5707963267948966])@(0, 2), None]\n", + "\t[CPGate([3.141592653589793])@(0, 1), CPGate([3.141592653589793])@(0, 1), None, None]\n", + "\t[HGate@(0,), None, None, None]" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# initialize the circuit\n", + "qcirc=Circuit(4)\n", + "\n", + "# prepare the initial state\n", + "qcirc.append_gate(XGate(),1)\n", + "qcirc.append_gate(XGate(),3)\n", + "\n", + "# perform qft\n", + "QFT(qcirc,4,inverse=False,swap=False,nmax=16)\n", + "qcirc" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "00a28108", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: divide by zero encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: divide by zero encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: invalid value encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: invalid value encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: divide by zero encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: invalid value encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: divide by zero encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: invalid value encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: divide by zero encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n", + "/opt/homebrew/anaconda3/envs/bqskit/lib/python3.11/site-packages/numpy/linalg/linalg.py:2180: RuntimeWarning: invalid value encountered in det\n", + " r = _umath_linalg.det(a, signature=signature)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Gate Counts: {U1qGate: 14, ZZGate: 9, RZGate: 6}\n", + "Compiled Circuit Statistics\n", + "Gate Counts: {U1qGate: 14, ZZGate: 9, RZGate: 6}\n", + "Logical Connectivity: CouplingGraph({(0, 1), (1, 2), (0, 3), (2, 3), (0, 2), (1, 3)})\n" + ] + } + ], + "source": [ + "from bqskit import compile\n", + "\n", + "# Building Rigetti's native gate set\n", + "from bqskit.ir.gates import U1qGate, RZGate, ZZGate\n", + "gate_set = {RZGate(), ZZGate(), U1qGate()} \n", + "\n", + "# Build a MachineModel with this gate set\n", + "# and the same number of qubits as the circuit\n", + "from bqskit import MachineModel\n", + "model = MachineModel(qcirc.num_qudits, gate_set=gate_set)\n", + "\n", + "# # Compile the circuit with optimization level 3\n", + "out_circuit = compile(qcirc, model=model, optimization_level=3)\n", + "print(\"Gate Counts:\", out_circuit.gate_counts)\n", + "\n", + "# Print new statistics\n", + "print(\"Compiled Circuit Statistics\")\n", + "print(\"Gate Counts:\", out_circuit.gate_counts)\n", + "print(\"Logical Connectivity:\", out_circuit.coupling_graph)" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "689e002e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Circuit(4)\n", + "\t[None, None, U1qGate([1.570796344593084, 1.5707963064008534])@(2,), U1qGate([7.853981626304372, 20.420352238330118])@(3,)]\n", + "\t[None, ZZGate@(1, 2), ZZGate@(1, 2), None]\n", + "\t[None, None, U1qGate([4.7123889789500755, 3.1415926515049093])@(2,), None]\n", + "\t[None, None, ZZGate@(2, 3), ZZGate@(2, 3)]\n", + "\t[None, None, RZGate([-1.8583570386147872])@(2,), U1qGate([1.5707963225517572, 33.629362028085346])@(3,)]\n", + "\t[None, ZZGate@(1, 3), U1qGate([2.278219306461859, 4.42482823139524])@(2,), ZZGate@(1, 3)]\n", + "\t[None, U1qGate([3.141592658324123, 4.302517087376481])@(1,), None, U1qGate([0.7853981417237897, 2.213435475525513])@(3,)]\n", + "\t[None, ZZGate@(1, 3), None, ZZGate@(1, 3)]\n", + "\t[None, U1qGate([4.712388959617491, 6.248839684147036])@(1,), None, U1qGate([5.202667749675468, 3.7842317795539957])@(3,)]\n", + "\t[ZZGate@(0, 3), None, None, ZZGate@(0, 3)]\n", + "\t[None, None, None, U1qGate([0.39269911899764576, 2.2134353628165813])@(3,)]\n", + "\t[ZZGate@(0, 3), None, None, ZZGate@(0, 3)]\n", + "\t[ZZGate@(0, 2), None, ZZGate@(0, 2), RZGate([0.973336552354656])@(3,)]\n", + "\t[RZGate([2.3003452119821857])@(0,), None, U1qGate([2.356194496440999, 5.995624599900657])@(2,), U1qGate([1.5707963104991498, 6.328364605698556])@(3,)]\n", + "\t[ZZGate@(0, 2), None, ZZGate@(0, 2), None]\n", + "\t[ZZGate@(0, 1), ZZGate@(0, 1), RZGate([0.3655358664915287])@(2,), None]\n", + "\t[RZGate([3.590140992928264])@(0,), RZGate([3.1759382299330516])@(1,), U1qGate([1.5707963304352888, 6.361160460953048])@(2,), None]\n", + "\t[U1qGate([4.712388973679297, 4.712388973944584])@(0,), None, None, None]" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "out_circuit" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "788ed8c2", + "metadata": {}, + "outputs": [], + "source": [ + "# Save circuit to qasm file\n", + "out_circuit.save('QFT_0101.qasm')" + ] + }, + { + "cell_type": "markdown", + "id": "a14f90c7", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "1. M. Nielsen and I. Chuang, Quantum Computation and Quantum Information, Cambridge Series on Information and the Natural Sciences (Cambridge University Press, Cambridge, 2000)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}