{ "cells": [ { "cell_type": "markdown", "id": "9640a40f", "metadata": {}, "source": [ "# Bloqs Tutorial\n", "\n", "Qualtran lets you represent high-level quantum programs and subroutines as a hierarchical\n", "collection of Python objects. The main interface is the [`Bloq`](/reference/qualtran/Bloq.md) abstract base class." ] }, { "cell_type": "code", "execution_count": 1, "id": "37c9b7ce", "metadata": {}, "outputs": [], "source": [ "import abc\n", "from typing import *\n", "\n", "\n", "class Bloq(metaclass=abc.ABCMeta):\n", " ..." ] }, { "cell_type": "markdown", "id": "d4169ffa", "metadata": {}, "source": [ "We use a graph-like container to wire up collections of bloqs to define new bloqs.\n", "\n", "By the end of this tutorial, you should understand how to declare bloqs, wire them up, use named registers, use high-bitsize registers, use 'bookkeeping' operations to split and join wires, represent allocations as operations in the graph, and use linear logic to prevent violations of the no cloning theorem." ] }, { "cell_type": "markdown", "id": "7a3d76e5", "metadata": {}, "source": [ "## Basics\n", "\n", "There are two important flavors of implementations of the [`Bloq`](/reference/qualtran/Bloq.md) interface. The first flavor\n", "consists of bloqs implemented by you, the user-developer to express quantum operations of\n", "interest. For example:" ] }, { "cell_type": "code", "execution_count": 2, "id": "4aaf9451", "metadata": {}, "outputs": [], "source": [ "class ShorsAlgorithm(Bloq):\n", " ..." ] }, { "cell_type": "markdown", "id": "9b504f60", "metadata": {}, "source": [ "The other important [`Bloq`](/reference/qualtran/Bloq.md) subclass is [`CompositeBloq`](/reference/qualtran/CompositeBloq.md), which is a container type for a\n", "collection of sub-bloqs. We'll investigate this class more later. First, let's define a\n", "bloq for a simple quantum operation: the controlled-not (CNOT)." ] }, { "cell_type": "code", "execution_count": 3, "id": "114b1560", "metadata": {}, "outputs": [], "source": [ "class CNOT(Bloq):\n", " ..." ] }, { "cell_type": "markdown", "id": "c632807b", "metadata": {}, "source": [ "There is only one mandatory method we must implement to have a well-formed [`Bloq`](/reference/qualtran/Bloq.md). There\n", "are many other methods we can optionally implement to encode more information about the\n", "bloq, which we will add as we go along.\n", "\n", "The mandatory method is the `Bloq.signature` property. This declares what the inputs and\n", "outputs are for our bloq, and is a list of registers. A register has a name and quantum-type\n", "information. By default, a register declares both an input and an output allowing quantum\n", "data to pass through it, like the \"control\" register below. We call these `THRU` registers.\n", "\n", "The `Bloq.signature` property can be thought of as analogous\n", "to the function signature in ordinary programming. You can think of a bloq\n", "with just this property implemented like a function\n", "declarations in a C header (`*.h`) file." ] }, { "cell_type": "code", "execution_count": 4, "id": "22d8749d", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Register(name='control', dtype=QBit(), _shape=(), side=)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qualtran import Register, QBit\n", "\n", "Register('control', QBit())" ] }, { "cell_type": "markdown", "id": "11e163ca", "metadata": {}, "source": [ "The above declares a register named \"control\" with a size of 1. We'll return this as well\n", "as a register for the \"target\" input/output of the CNOT bloq wrapped in the [`Signature`](/reference/qualtran/Signature.md)\n", "container.
The `attrs.frozen` annotation removes some of the boilerplate to write an immutable Python class with a pre-defined set of attributes.
" ] }, { "cell_type": "code", "execution_count": 5, "id": "34a159c4", "metadata": {}, "outputs": [], "source": [ "import attrs\n", "from qualtran import Bloq, Signature\n", "\n", "@attrs.frozen\n", "class CNOT(Bloq):\n", " @property\n", " def signature(self):\n", " return Signature([\n", " Register('control', QBit()),\n", " Register('target', QBit()),\n", " ])" ] }, { "cell_type": "markdown", "id": "8533a7f5", "metadata": {}, "source": [ "We now have a well-formed bloq. We can instantiate it and visualize it." ] }, { "cell_type": "code", "execution_count": 6, "id": "5cec9aff", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "control_G3\n", "control\n", "\n", "\n", "\n", "CNOT\n", "\n", "CNOT\n", "\n", "control\n", "\n", "target\n", "\n", "\n", "\n", "control_G3:e->CNOT:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "target_G0\n", "target\n", "\n", "\n", "\n", "target_G0:e->CNOT:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "control_G5\n", "control\n", "\n", "\n", "\n", "CNOT:e->control_G5:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "target_G4\n", "target\n", "\n", "\n", "\n", "CNOT:e->target_G4:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from qualtran.drawing import show_bloq\n", "\n", "cnot = CNOT()\n", "show_bloq(cnot)" ] }, { "cell_type": "markdown", "id": "da9baea0", "metadata": {}, "source": [ "This is the absolute minimum amount of information needed to define a Bloq: a name (i.e.\n", "the class name) and a `signature` property.
If you're familiar with Cirq, you can consider\n", "a `cirq.Gate` to be analogous to a [`Bloq`](/reference/qualtran/Bloq.md) with one register named \"qubits\" of size `n`. In\n", "fact, `CirqGateAsBloq` lets you wrap any Cirq gate in\n", "this way.
" ] }, { "cell_type": "markdown", "id": "9b36a23d", "metadata": {}, "source": [ "## Decomposing Bloqs\n", "\n", "As you can probably guess, such a simple block-box model of an operation has limited utility.\n", "We now turn our attention to a second bloq: SWAP -- which we will define in terms of its\n", "decomposition into three CNOTs." ] }, { "cell_type": "code", "execution_count": 7, "id": "c2bf35bf", "metadata": {}, "outputs": [], "source": [ "class SwapTwoBits(Bloq):\n", " ..." ] }, { "cell_type": "markdown", "id": "1bdf983c", "metadata": {}, "source": [ "As before, we must define the function signature by naming and sizing the registers on\n", "which it operates. We'll implement a bloq that swaps two (qu)bits. We'll name the arguments\n", "`x` and `y`, but you have some creative freedom with these choices.
We've been filling in the `name` and `bitsize`\n", "attributes for our registers. The other two attributes are for more advanced usage and\n", "will be covered later. In simple cases, we could use the convenience method\n", "`Signature.build(x=1, y=1)` for the same object below.
" ] }, { "cell_type": "code", "execution_count": 8, "id": "9e548b5f", "metadata": {}, "outputs": [], "source": [ "class SwapTwoBits(Bloq):\n", " @property\n", " def signature(self):\n", " return Signature([\n", " Register('x', QBit()),\n", " Register('y', QBit()),\n", " ])" ] }, { "cell_type": "markdown", "id": "54bf8781", "metadata": {}, "source": [ "Now, for the moment you've been waiting for. We can define the implementation of SWAP in terms\n", "of sub-operations. `Bloq.decompose_bloq()` will decompose a bloq into its component parts.\n", "The return type of this operation is [`CompositeBloq`](/reference/qualtran/CompositeBloq.md) -- our bloq container type which itself\n", "follows the [`Bloq`](/reference/qualtran/Bloq.md) interface.\n", "\n", "Instead of overriding `decompose_bloq()` directly, we override `build_composite_bloq`, which\n", "makes it easier for you, the user-developer, to write decompositions." ] }, { "cell_type": "code", "execution_count": 9, "id": "305ad1ed", "metadata": {}, "outputs": [], "source": [ "from qualtran import BloqBuilder\n", "\n", "class SwapTwoBits(Bloq):\n", " ...\n", "\n", " def build_composite_bloq(self, bb: BloqBuilder, *, x, y):\n", " ..." ] }, { "cell_type": "markdown", "id": "dfdfc199", "metadata": {}, "source": [ "The bloqs infrastructure will pass in keyword arguments for each of the input registers,\n", "here `x` and `y`. I stress that these names must match the names of the registers declared\n", "in the `.signatures` property. The infrastructure also passes in a [`BloqBuilder`](/reference/qualtran/BloqBuilder.md)\n", "which is what you will use to add suboperations to the composite bloq storing your\n", "decomposition.\n", "\n", "We use `bb.add(...)` to add sub-operations. For our swap operation, we will need to call\n", "`add` three times for each of the CNOTs. The signature is: `bb.add(bloq, **bloq_args)` where\n", "the first argument is an instantiation of the bloq we want to add, and then keyword arguments\n", "providing the input quantum variables. This call will return quantum variables representing\n", "the outputs of the operation that are suitable for using as inputs to subsequent operations.\n", "\n", "The method returns a dictionary mapping (output) register names to the final quantum variables." ] }, { "cell_type": "code", "execution_count": 10, "id": "1b2af153", "metadata": {}, "outputs": [], "source": [ "class SwapTwoBits(Bloq):\n", " @property\n", " def signature(self):\n", " return Signature([\n", " Register('x', QBit()),\n", " Register('y', QBit()),\n", " ])\n", "\n", " def build_composite_bloq(self, bb: BloqBuilder, *, x, y):\n", " x, y = bb.add(CNOT(), control=x, target=y)\n", " y, x = bb.add(CNOT(), control=y, target=x)\n", " x, y = bb.add(CNOT(), control=x, target=y)\n", " return {'x': x, 'y': y}" ] }, { "cell_type": "markdown", "id": "9ade0bc3", "metadata": {}, "source": [ "Note that each CNOT operation takes two arguments named \"control\" and \"target\" and returns\n", "two quantum variables which are ordered according to the ordering of the registers\n", "in `CNOT.signature` (so in this case: control, target).\n", "\n", "Let's see what this looks like." ] }, { "cell_type": "code", "execution_count": 11, "id": "676dddd5", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "x_G5\n", "x\n", "\n", "\n", "\n", "SwapTwoBits\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "x_G5:e->SwapTwoBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G2\n", "y\n", "\n", "\n", "\n", "y_G2:e->SwapTwoBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G3\n", "x\n", "\n", "\n", "\n", "SwapTwoBits:e->x_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G0\n", "y\n", "\n", "\n", "\n", "SwapTwoBits:e->y_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "swap = SwapTwoBits()\n", "show_bloq(swap)" ] }, { "cell_type": "markdown", "id": "87172163", "metadata": {}, "source": [ "Wait! This is still just a two-bit black box! I thought we defined the bloq by its decomposition.\n", "The Bloq object always represents the atomic operation and [`CompositeBloq`](/reference/qualtran/CompositeBloq.md) always represents\n", "a collection of sub-operations. We have to explicitly request the decomposition if that's\n", "what we want to visualize." ] }, { "cell_type": "code", "execution_count": 12, "id": "f08bb50b", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "x_G12\n", "x\n", "\n", "\n", "\n", "CNOT\n", "\n", "CNOT\n", "\n", "control\n", "\n", "target\n", "\n", "\n", "\n", "x_G12:e->CNOT:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G9\n", "y\n", "\n", "\n", "\n", "y_G9:e->CNOT:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CNOT_G0\n", "\n", "CNOT\n", "\n", "control\n", "\n", "target\n", "\n", "\n", "\n", "CNOT:e->CNOT_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CNOT:e->CNOT_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CNOT_G3\n", "\n", "CNOT\n", "\n", "control\n", "\n", "target\n", "\n", "\n", "\n", "CNOT_G0:e->CNOT_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CNOT_G0:e->CNOT_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x\n", "x\n", "\n", "\n", "\n", "CNOT_G3:e->x:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y\n", "y\n", "\n", "\n", "\n", "CNOT_G3:e->y:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_bloq(swap.decompose_bloq())" ] }, { "cell_type": "markdown", "id": "5cf1a3d8", "metadata": {}, "source": [ "### Using [`BloqBuilder`](/reference/qualtran/BloqBuilder.md) directly\n", "\n", "You can build a standalone [`CompositeBloq`](/reference/qualtran/CompositeBloq.md) (i.e. not as part of the decomposition of\n", "another bloq) as well. This can come in handy for testing or prototyping. Simply\n", "instantiate a [`BloqBuilder`](/reference/qualtran/BloqBuilder.md). You need to manually\n", "manage your registers with `bb.add_register(...)` and you must finish your building session\n", "by calling `bb.finalize(...)` to freeze your composite-bloq-under-construction into an\n", "immutable [`CompositeBloq`](/reference/qualtran/CompositeBloq.md)." ] }, { "cell_type": "code", "execution_count": 13, "id": "d23ab186", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "x_G12\n", "x\n", "\n", "\n", "\n", "CNOT\n", "\n", "CNOT\n", "\n", "control\n", "\n", "target\n", "\n", "\n", "\n", "x_G12:e->CNOT:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G9\n", "y\n", "\n", "\n", "\n", "y_G9:e->CNOT:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CNOT_G0\n", "\n", "CNOT\n", "\n", "control\n", "\n", "target\n", "\n", "\n", "\n", "CNOT:e->CNOT_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CNOT:e->CNOT_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CNOT_G3\n", "\n", "CNOT\n", "\n", "control\n", "\n", "target\n", "\n", "\n", "\n", "CNOT_G0:e->CNOT_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CNOT_G0:e->CNOT_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x\n", "x\n", "\n", "\n", "\n", "CNOT_G3:e->x:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y\n", "y\n", "\n", "\n", "\n", "CNOT_G3:e->y:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "bb = BloqBuilder()\n", "x = bb.add_register('x', 1)\n", "y = bb.add_register('y', 1)\n", "x, y = bb.add(CNOT(), control=x, target=y)\n", "y, x = bb.add(CNOT(), control=y, target=x)\n", "x, y = bb.add(CNOT(), control=x, target=y)\n", "cbloq = bb.finalize(x=x, y=y)\n", "show_bloq(cbloq)" ] }, { "cell_type": "markdown", "id": "a63a9531", "metadata": {}, "source": [ "## Quantum variables and [`Soquet`](/reference/qualtran/Soquet.md)s.\n", "\n", "What are the types of `x` and `y`? They represent quantum variables used to \"wire up\" sub\n", "operations by providing them as inputs and receiving them as outputs during calls to `bb.add`.
If you're familiar with Cirq, you might think\n", "that they are equivalent to `cirq.Qubit`s. Whereas a cirq.Circuit has a fixed pool of qubits\n", "on which many operations act, these quantum variables follow different rules — read on!
\n", "\n", "The rules of quantum mechanics makes these quantum variables behave very differently than\n", "normal variables. The most salient rules are the no-cloning theorem and its dual, the\n", "[no-deleting theorem](https://en.wikipedia.org/wiki/No-deleting_theorem). In the parlance\n", "of programming language research, our variables follow \"linear logic\". A linear variable\n", "must be used once and only once.\n", "\n", "The following snippets show improper use of our quantum variables. Luckily, the bloq builder will\n", "raise an error if the rules of quantum mechanics are not followed!" ] }, { "cell_type": "code", "execution_count": 14, "id": "85da3eb7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Can't use a variable as both control and target!\n", "LeftDangle.x is not an available Soquet for `CNOT.target`.\n" ] } ], "source": [ "from qualtran import BloqError\n", "\n", "bb = BloqBuilder()\n", "x = bb.add_register('x', 1)\n", "y = bb.add_register('y', 1)\n", "\n", "try:\n", " _ = bb.add(CNOT(), control=x, target=x)\n", "except BloqError as e:\n", " print(\"Can't use a variable as both control and target!\")\n", " print(e)" ] }, { "cell_type": "code", "execution_count": 15, "id": "cfe23c3e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "`x` and `y` were consumed by the first call to `add`.\n", "Returned quantum variables are *new, immutable* variables that you use\n", "in subsequent operations\n", "LeftDangle.x is not an available Soquet for `CNOT.control`.\n" ] } ], "source": [ "bb = BloqBuilder()\n", "x = bb.add_register('x', 1)\n", "y = bb.add_register('y', 1)\n", "x2, y2 = bb.add(CNOT(), control=x, target=y)\n", "\n", "try:\n", " x3, y3 = bb.add(CNOT(), control=x, target=y)\n", "except BloqError as e:\n", " print(\"`x` and `y` were consumed by the first call to `add`.\")\n", " print(\"Returned quantum variables are *new, immutable* variables that you use\")\n", " print(\"in subsequent operations\")\n", " print(e)" ] }, { "cell_type": "code", "execution_count": 16, "id": "3b07af73", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Any unused variables must be 'passed on' to `finalize` to be outputs.\n", "Finalizing requires a Soquet named `y`.\n" ] } ], "source": [ "bb = BloqBuilder()\n", "x = bb.add_register('x', 1)\n", "y = bb.add_register('y', 1)\n", "\n", "# The following line turns on the additional checks needed to\n", "# raise an exception in this case:\n", "bb.add_register_allowed = False\n", "\n", "x2, y2 = bb.add(CNOT(), control=x, target=y)\n", "x3, y3 = bb.add(CNOT(), control=x2, target=y2)\n", "\n", "try:\n", " bb.finalize(x=x3)\n", "except BloqError as e:\n", " print(\"Any unused variables must be 'passed on' to `finalize` to be outputs.\")\n", " print(e)" ] }, { "cell_type": "markdown", "id": "432b4c75", "metadata": {}, "source": [ "The actual Python type of these objects is [`Soquet`](/reference/qualtran/Soquet.md), which you will see in type annotations,\n", "but you should never instantiate a [`Soquet`](/reference/qualtran/Soquet.md) directly, nor should you use or set its attributes.\n", "Soquets should be constructed and managed by [`BloqBuilder`](/reference/qualtran/BloqBuilder.md) and other infrastructure.
Another opaque, infrastructural class that\n", " you may see but should not be manipulating directly is [`BloqInstance`](/reference/qualtran/BloqInstance.md). This simple wrapper\n", "lets us distinguish between two *instances* of e.g. a CNOT bloq. Usually we want value\n", "equality semantics between bloqs.
" ] }, { "cell_type": "markdown", "id": "4f44f287", "metadata": {}, "source": [ "## Larger registers\n", "\n", "Our two bloqs have still been operating at the level of individual bits. We now consider\n", "a general swap between two `n`-sized registers.
The `n: int` line means our class has one attribute named `n` of type `int`. The attrs annotation will automatically generate an `__init__` function.
" ] }, { "cell_type": "code", "execution_count": 17, "id": "145c65b6", "metadata": {}, "outputs": [], "source": [ "from qualtran import QAny\n", "\n", "@attrs.frozen\n", "class Swap(Bloq):\n", " n: int\n", "\n", " @property\n", " def signature(self):\n", " return Signature([\n", " Register('x', QAny(bitsize=self.n)),\n", " Register('y', QAny(bitsize=self.n)),\n", " ])" ] }, { "cell_type": "markdown", "id": "1513500c", "metadata": {}, "source": [ "Note that our bloq now has an attrs attribute `n` that lets us configure the exact parameters\n", "of the bloq without defining a new class. This is analogous to a template parameter in C++,\n", "for example. Bloqs should be immutable and hashable." ] }, { "cell_type": "code", "execution_count": 18, "id": "4bcc3e93", "metadata": {}, "outputs": [], "source": [ "assert Swap(5) == Swap(5)\n", "assert Swap(5) != Swap(6)" ] }, { "cell_type": "code", "execution_count": 19, "id": "2ebf0d70", "metadata": {}, "outputs": [], "source": [ "from qualtran import SoquetT\n", "\n", "@attrs.frozen\n", "class Swap(Bloq):\n", " n: int\n", "\n", " @property\n", " def signature(self):\n", " return Signature.build(x=self.n, y=self.n)\n", "\n", " def build_composite_bloq(\n", " self, bb: BloqBuilder, *, x: SoquetT, y: SoquetT\n", " ) -> Dict[str, SoquetT]:\n", " # THIS WON'T ACTUALLY WORK! Read on.\n", " for i in range(self.n):\n", " x[i], y[i] = bb.add(SwapTwoBits(), x=x[i], y=y[i])\n", " return {'x': x, 'y': y}" ] }, { "cell_type": "markdown", "id": "3db1bfa8", "metadata": {}, "source": [ "For our first attempt, we will straightforwardly decompose our swap between two n-bit registers\n", "into n swaps over each bit in the two registers. There's a note that this won't actually work.\n", "Let's see what happens." ] }, { "cell_type": "code", "execution_count": 20, "id": "b08436af", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "x_G4\n", "x\n", "\n", "\n", "\n", "Swap\n", "\n", "Swap\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "x_G4:e->Swap:w\n", "\n", "\n", "5\n", "\n", "\n", "\n", "y_G2\n", "y\n", "\n", "\n", "\n", "y_G2:e->Swap:w\n", "\n", "\n", "5\n", "\n", "\n", "\n", "x_G3\n", "x\n", "\n", "\n", "\n", "Swap:e->x_G3:w\n", "\n", "\n", "5\n", "\n", "\n", "\n", "y_G0\n", "y\n", "\n", "\n", "\n", "Swap:e->y_G0:w\n", "\n", "\n", "5\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_bloq(Swap(n=5))" ] }, { "cell_type": "markdown", "id": "fdc991f3", "metadata": {}, "source": [ "That looks fine... In fact: you can see a useful property of bloqs. Instead of representing\n", "each qubit as its own quantum variable, the size of our register is just a property annotated\n", "on the graph. We can make it arbitrarily large with no performance penalty" ] }, { "cell_type": "code", "execution_count": 21, "id": "fba876e0", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "x_G4\n", "x\n", "\n", "\n", "\n", "Swap\n", "\n", "Swap\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "x_G4:e->Swap:w\n", "\n", "\n", "10000\n", "\n", "\n", "\n", "y_G5\n", "y\n", "\n", "\n", "\n", "y_G5:e->Swap:w\n", "\n", "\n", "10000\n", "\n", "\n", "\n", "x_G0\n", "x\n", "\n", "\n", "\n", "Swap:e->x_G0:w\n", "\n", "\n", "10000\n", "\n", "\n", "\n", "y_G2\n", "y\n", "\n", "\n", "\n", "Swap:e->y_G2:w\n", "\n", "\n", "10000\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_bloq(Swap(n=10_000))" ] }, { "cell_type": "markdown", "id": "ec5150d2", "metadata": {}, "source": [ "The problem occurs when we decompose our bloq." ] }, { "cell_type": "code", "execution_count": 22, "id": "48648ac4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Traceback (most recent call last):\n", " File \"/tmp/ipykernel_835779/2679143270.py\", line 4, in \n", " cbloq = Swap(n=5).decompose_bloq()\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/usr/local/google/home/mpharrigan/qualtran/qualtran/qualtran/_infra/bloq.py\", line 163, in decompose_bloq\n", " return _decompose_from_build_composite_bloq(self)\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/usr/local/google/home/mpharrigan/qualtran/qualtran/qualtran/_infra/bloq.py\", line 71, in _decompose_from_build_composite_bloq\n", " out_soqs = bloq.build_composite_bloq(bb=bb, **initial_soqs)\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", " File \"/tmp/ipykernel_835779/1590169855.py\", line 16, in build_composite_bloq\n", " x[i], y[i] = bb.add(SwapTwoBits(), x=x[i], y=y[i])\n", " ~^^^\n", "TypeError: 'Soquet' object is not subscriptable\n", "\n" ] } ], "source": [ "import traceback\n", "\n", "try:\n", " cbloq = Swap(n=5).decompose_bloq()\n", "except TypeError as e:\n", " print(traceback.format_exc())" ] }, { "cell_type": "markdown", "id": "d029bf65", "metadata": {}, "source": [ "Can you figure out what's happening? The very advantage alluded to above has come back\n", "to bite us! If we have one object representing an n-bit register, we can't index into it\n", "to do bit-twiddling in our decomposition. We'll take a second look at [`Register`](/reference/qualtran/Register.md) to see\n", "if we can modify our signature declaration to make this work.\n", "\n", "[`Register`](/reference/qualtran/Register.md) can represent an n-dimensional array of quantum bits. For example, I can\n", "declare a 3x3 matrix of 32-bit quantum variables:" ] }, { "cell_type": "code", "execution_count": 23, "id": "c98fa648", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "total bits: 288\n" ] } ], "source": [ "arr_reg = Register('arr', QAny(32), shape=(3, 3))\n", "print('total bits:', arr_reg.total_bits())" ] }, { "cell_type": "markdown", "id": "91d649ca", "metadata": {}, "source": [ "
`shape` is like `np.ndarray.shape`.
\n", "\n", "In computing, we can think of all data as an ndarray of bits or qubits, but -- analogous to\n", "classical data types -- it's preferable to treat a certain number of bits (or qubits) as\n", "our atomic datatype. For example, in C, an array of `int32 x[10];` does not let you index\n", "into individual bits like `x[3][31]`. Unlike in C, you are not limited by machine word size\n", "for atomic type sizes, which is why above we could define a register of `bitsize=5`.\n", "\n", "In the `SwapTwoBits` example everything was `bitsize=1` and we could write our decomposition\n", "without slicing into the registers. Let's write a version of `Swap` that uses an array\n", "of `bitsize=1` values." ] }, { "cell_type": "code", "execution_count": 24, "id": "7095d363", "metadata": {}, "outputs": [], "source": [ "@attrs.frozen\n", "class SwapManyBits(Bloq):\n", " n: int\n", "\n", " @property\n", " def signature(self):\n", " # Not ideal; read on.\n", " return Signature([\n", " Register('x', QBit(), shape=(self.n,)),\n", " Register('y', QBit(), shape=(self.n,)),\n", " ])\n", "\n", " def build_composite_bloq(\n", " self, bb: BloqBuilder, *, x: SoquetT, y: SoquetT\n", " ) -> Dict[str, SoquetT]:\n", " for i in range(self.n):\n", " x[i], y[i] = bb.add(SwapTwoBits(), x=x[i], y=y[i])\n", " return {'x': x, 'y': y}" ] }, { "cell_type": "markdown", "id": "6a5649f3", "metadata": {}, "source": [ "Now since we've moved our `n` dimension of our inputs into the `shape` part of\n", "the register declaration, slicing should work:" ] }, { "cell_type": "code", "execution_count": 25, "id": "6db17a87", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "x_G30\n", "x[0]\n", "\n", "\n", "\n", "SwapTwoBits_G0\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "x_G30:e->SwapTwoBits_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G29\n", "x[1]\n", "\n", "\n", "\n", "SwapTwoBits_G6\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "x_G29:e->SwapTwoBits_G6:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G32\n", "x[2]\n", "\n", "\n", "\n", "SwapTwoBits\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "x_G32:e->SwapTwoBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G13\n", "x[3]\n", "\n", "\n", "\n", "SwapTwoBits_G3\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "x_G13:e->SwapTwoBits_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G26\n", "y[0]\n", "\n", "\n", "\n", "y_G26:e->SwapTwoBits_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G24\n", "y[1]\n", "\n", "\n", "\n", "y_G24:e->SwapTwoBits_G6:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G31\n", "y[2]\n", "\n", "\n", "\n", "y_G31:e->SwapTwoBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G10\n", "y[3]\n", "\n", "\n", "\n", "y_G10:e->SwapTwoBits_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G15\n", "x[2]\n", "\n", "\n", "\n", "SwapTwoBits:e->x_G15:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G20\n", "y[2]\n", "\n", "\n", "\n", "SwapTwoBits:e->y_G20:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G12\n", "x[0]\n", "\n", "\n", "\n", "SwapTwoBits_G0:e->x_G12:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G19\n", "y[0]\n", "\n", "\n", "\n", "SwapTwoBits_G0:e->y_G19:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G22\n", "x[3]\n", "\n", "\n", "\n", "SwapTwoBits_G3:e->x_G22:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G28\n", "y[3]\n", "\n", "\n", "\n", "SwapTwoBits_G3:e->y_G28:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G11\n", "x[1]\n", "\n", "\n", "\n", "SwapTwoBits_G6:e->x_G11:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G14\n", "y[1]\n", "\n", "\n", "\n", "SwapTwoBits_G6:e->y_G14:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "cbloq = SwapManyBits(n=4).decompose_bloq()\n", "show_bloq(cbloq)" ] }, { "cell_type": "markdown", "id": "41a1cb9a", "metadata": {}, "source": [ "The problem is now we (once again) have a Python object constructed for each bit:" ] }, { "cell_type": "code", "execution_count": 26, "id": "a301b582", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "x_G20\n", "x[0]\n", "\n", "\n", "\n", "SwapManyBits\n", "\n", "SwapManyBits\n", "\n", "x[0]\n", "\n", "x[1]\n", "\n", "x[2]\n", "\n", "x[3]\n", "\n", "y[0]\n", "\n", "y[1]\n", "\n", "y[2]\n", "\n", "y[3]\n", "\n", "\n", "\n", "x_G20:e->SwapManyBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G17\n", "x[1]\n", "\n", "\n", "\n", "x_G17:e->SwapManyBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G23\n", "x[2]\n", "\n", "\n", "\n", "x_G23:e->SwapManyBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G5\n", "x[3]\n", "\n", "\n", "\n", "x_G5:e->SwapManyBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G15\n", "y[0]\n", "\n", "\n", "\n", "y_G15:e->SwapManyBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G12\n", "y[1]\n", "\n", "\n", "\n", "y_G12:e->SwapManyBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G21\n", "y[2]\n", "\n", "\n", "\n", "y_G21:e->SwapManyBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G1\n", "y[3]\n", "\n", "\n", "\n", "y_G1:e->SwapManyBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G3\n", "x[0]\n", "\n", "\n", "\n", "SwapManyBits:e->x_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G2\n", "x[1]\n", "\n", "\n", "\n", "SwapManyBits:e->x_G2:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G7\n", "x[2]\n", "\n", "\n", "\n", "SwapManyBits:e->x_G7:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G10\n", "x[3]\n", "\n", "\n", "\n", "SwapManyBits:e->x_G10:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G8\n", "y[0]\n", "\n", "\n", "\n", "SwapManyBits:e->y_G8:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G6\n", "y[1]\n", "\n", "\n", "\n", "SwapManyBits:e->y_G6:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G9\n", "y[2]\n", "\n", "\n", "\n", "SwapManyBits:e->y_G9:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G16\n", "y[3]\n", "\n", "\n", "\n", "SwapManyBits:e->y_G16:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_bloq(SwapManyBits(n=4))" ] }, { "cell_type": "markdown", "id": "57f16270", "metadata": {}, "source": [ "
Forget about trying to show `n=10_000`.
\n", "\n", "Can we have the best of both worlds? Yes: the general technique is to represent the Bloq\n", "definition in as high-level terms as practical and use `bb.split(...)` and `bb.join(...)`\n", "to break apart registers during decomposition. This way a user can use the Bloq as a black-box\n", "without incurring the performance overhead of representing each bit if they do not care about\n", "the decomposition. If they *are* interested in the decomposition, then the cost will only be\n", "paid when actually doing the decomposition." ] }, { "cell_type": "code", "execution_count": 27, "id": "99f9fd52", "metadata": {}, "outputs": [], "source": [ "@attrs.frozen\n", "class Swap(Bloq):\n", " n: int\n", "\n", " @property\n", " def signature(self):\n", " return Signature.build(x=self.n, y=self.n)\n", "\n", " def build_composite_bloq(\n", " self, bb: BloqBuilder, *, x: SoquetT, y: SoquetT\n", " ) -> Dict[str, SoquetT]:\n", " xs = bb.split(x)\n", " ys = bb.split(y)\n", "\n", " for i in range(self.n):\n", " xs[i], ys[i] = bb.add(SwapTwoBits(), x=xs[i], y=ys[i])\n", " return {\n", " 'x': bb.join(xs),\n", " 'y': bb.join(ys),\n", " }" ] }, { "cell_type": "code", "execution_count": 28, "id": "c5932695", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "x_G48\n", "x\n", "\n", "\n", "\n", "Split\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "x_G48:e->Split:w\n", "\n", "\n", "5\n", "\n", "\n", "\n", "y_G33\n", "y\n", "\n", "\n", "\n", "Split_G10\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "y_G33:e->Split_G10:w\n", "\n", "\n", "5\n", "\n", "\n", "\n", "SwapTwoBits\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "Split:e->SwapTwoBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G0\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "Split:e->SwapTwoBits_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G3\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "Split:e->SwapTwoBits_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G7\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "Split:e->SwapTwoBits_G7:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G12\n", "\n", "SwapTwoBits\n", "\n", "x\n", "\n", "y\n", "\n", "\n", "\n", "Split:e->SwapTwoBits_G12:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "Join\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "SwapTwoBits:e->Join:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "Join_G15\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "SwapTwoBits:e->Join_G15:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G0:e->Join:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G0:e->Join_G15:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G3:e->Join:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G3:e->Join_G15:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G41\n", "x\n", "\n", "\n", "\n", "Join:e->x_G41:w\n", "\n", "\n", "5\n", "\n", "\n", "\n", "SwapTwoBits_G7:e->Join:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G7:e->Join_G15:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "Split_G10:e->SwapTwoBits:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "Split_G10:e->SwapTwoBits_G0:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "Split_G10:e->SwapTwoBits_G3:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "Split_G10:e->SwapTwoBits_G7:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "Split_G10:e->SwapTwoBits_G12:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G12:e->Join:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "SwapTwoBits_G12:e->Join_G15:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "y_G28\n", "y\n", "\n", "\n", "\n", "Join_G15:e->y_G28:w\n", "\n", "\n", "5\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "cbloq = Swap(n=5).decompose_bloq()\n", "show_bloq(cbloq)" ] }, { "cell_type": "markdown", "id": "c20817f9", "metadata": {}, "source": [ "The interleaved wires can get a little confusing. An alternative method of visualization via the familiar \"musical score\" diagram is also available" ] }, { "cell_type": "code", "execution_count": 29, "id": "5e35b5bb", "metadata": {}, "outputs": [ { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAA3cAAAMWCAYAAABSm3/pAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAoRxJREFUeJzs3XlcVGX///H3ALKOiDtqLre5i2uouYMbuaSWmt4uuYGWmVbq3dfM9rvFqLRyqTv3yEpTUzIzTQjFMhfSXO8ssxSNVBQQZZvfH/6YW0L2GQYOr+fjwePhnDnnOp85DnPNm+s655gsFotFAAAAAIBSzcnRBQAAAAAAio5wBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAFjFxcXp4YcfVp06deTm5iZfX18FBQVp9+7dxV7LihUrZDKZcv05ffq0zfZXr149a7vOzs6qWbOmJk6cqMuXL9tsHwAAAPZkslgsFkcXAaBk6Natm1JSUvTKK6+ofv36unDhgnbs2KHmzZtr4MCBxVpLcnKyrly5Yn18//33y8/PTy+88IJ1WdWqVeXs7GyT/dWrV08TJ05USEiI0tPTdfLkSU2aNEl33323Vq9ebZN9AAAA2BMjdwAkSfHx8YqKitJrr72mwMBA1a1bV+3bt9fs2bM1cOBAzZw5UwMGDLCuP3/+fJlMJm3dutW6rEGDBvrggw8kST/88IN69+6tKlWqqEKFCurevbsOHDiQZZ8mk0mLFy9W37595eHhofr162vdunWSJA8PD/n6+lp/XF1d5enpKV9fX61bt069evWyBruNGzfKZDJpyZIl1rZ79eqlp59+2vp48eLFuvPOO+Xq6qrGjRvfNrCVL19evr6+qlWrlgIDAzV27NhsNQMAAJRUhDsAkiSz2Syz2ayNGzfqxo0b2Z7v3r27du3apfT0dElSZGSkqlSpooiICEnS2bNnderUKQUEBEiSEhISNHbsWO3atUvfffedGjZsqH79+ikhISFLu3PnztWQIUP0448/atSoURoxYoSOHTuWa63du3fX0aNHFRcXd9taUlNTtWfPHmstGzZs0PTp0zVjxgz99NNPmjx5ssaPH6+dO3fmuI+zZ89q8+bN6tChQ16HDgAAoERgWiYAq88++0whISFKTk5W27Zt1b17d40YMUItW7ZUfHy8KleurO+//1533XWXqlSpolmzZmnjxo367rvvFBYWpieffFJ//PHHbdvOyMiQj4+PPvroI+sIoMlk0kMPPaTFixdb17v77rvVtm1bLVq0KMv2AQEBat26tebPny+LxaKqVatqyZIlGjp0qNq0aaPhw4drwYIFio2N1e7duxUYGKj4+Hh5enqqc+fOat68ud5//31rew888ICSkpL0xRdfSLo5LTM2NlblypVTenq6rl+/rg4dOmjr1q3y8fGx8ZEGAACwPUbuAFgNGTJE586d06ZNm3TPPfcoIiJCbdu21YoVK+Tj46NWrVopIiJChw8flqurqyZNmqSDBw8qMTFRkZGR6t69u7WtCxcuKCQkRA0bNlSFChXk7e2txMREnTlzJss+O3bsmO1xXiN3JpNJ3bp1U0REhOLj43X06FFNmTJFN27c0PHjxxUZGal27drJ09NTknTs2DF17tw5SxudO3fOtp9Zs2YpJiZGhw4d0o4dOyRJ/fv3t45WAgAAlGSEOwBZuLu7q3fv3po7d66io6M1btw4Pfvss5Jujp5FRERYg1ylSpXUtGlT7dq1K1u4Gzt2rGJiYrRgwQJFR0crJiZGlStXVkpKik3qzKwlKipKbdq0kbe3tzXw/b2W/KpSpYoaNGighg0bqkePHpo/f76io6Nznb4JAABQUhDuAOSqWbNmSkpKkvS/8+527NhhPZ8tICBAa9as0cmTJ63LJGn37t2aNm2a+vXrp+bNm8vNzU1//fVXtva/++67bI+bNm2aZ12Z592tXbs2Sy3bt2/X7t27s9TStGnTbLdz2L17t5o1a5brPjIv2JKcnJxnPQAAAI7m4ugCAJQMFy9e1LBhwzRhwgS1bNlS5cuX1759+zRv3jwNGjRI0s1bJSQkJCg8PFyvvvqqpJuBaujQoapRo4YaNWpkba9hw4ZavXq1/P39dfXqVc2aNUseHh7Z9rt27Vr5+/urS5cuCgsL0969e7V06dI8623ZsqUqVqyojz76SOHh4dZaZs6cKZPJlGUa5qxZs/TAAw+oTZs26tWrlzZv3qz169dr+/btWdpMSEjQ+fPnZbFY9Pvvv+tf//qXqlatqk6dOkm6eZGVnj17atWqVWrfvr0k6cEHH1StWrX0yiuvFORwAwAA2BwjdwAk3bxaZocOHfTWW2+pW7du8vPz09y5cxUSEqJ3331XklSxYkW1aNFCVatWVZMmTSTdDHwZGRnZpkEuXbpUly9fVtu2bTVmzBhNmzZN1apVy7bf559/Xh9//LFatmypVatWac2aNXmOqEk3z7vr2rWrTCaTunTpIulm4PP29pa/v7+8vLys6w4ePFgLFixQaGiomjdvrvfee0/Lly/PMronSc8884xq1KihmjVrasCAAfLy8tK2bdtUuXJlSTevwnnixAldu3bNus2ZM2cUGxubjyMMAABgX1wtE4DDmEwmbdiwQYMHD3Z0KQAAAKUeI3cAAAAAYACEOwAAAAAwAC6oAsBhmBUOAABgO4zcAQAAAIABEO4AAAAAwAAIdwAAAABgAIQ7AAAAADAAwh0AAAAAGADhDgAAAAAMgHAHAAAAAAZAuAMAAAAAAyDcwa5Onz6t1NRUR5dRYly5ckVxcXGOLgMAAJQA58+fV2JioqPLgIEQ7mA3ERERql+/vkaNGkXAkxQfH68+ffrojjvu0NmzZx1dDgAAcKBTp06pdu3a6tu3LwEPNkO4g91cu3ZNFotFa9euLfMBLz4+XkFBQdq7d69SUlLK9LEAAADSjRs3lJaWpl27dhHwYDOEO9jd4sWLtWHDhjIb8DKD3X//+1/NmzfP0eUAAIAS5M0339SPP/5IwINNEO5gd4MGDdK6devKZMC7Ndht375dbdu2dXRJAACgBLn77ru1bds2Ah5sgnCHYlEWAx7BDgAA5AcBD7bi4ugCYBwHDx7Ufffdp+vXr0u6OZdcklxcbr7NMgPe0KFDNWrUKIWFhalcuXIOq9eecgp2ma+3Xbt2cnZ2tsm+3nzzTY0cOdImbTmSxWLRZ599pjVr1ujAgQNKTk7OdX1nZ2fVrFlTAwcOVHBwsGrUqFFMlQIAyqply5bpqaeesklbaWlpkv733SAz4PXp00d9+/bVl19+KbPZbJN9oewwWSwWi6OLgDHMnz9f//rXv/TMM89Yl/n5+Wnw4MFZ1vv88881dOhQ3XfffYYMeLmN2FksFi1dulTnz5+3yb6WLVumu+++Wx999JFN2nMUi8WimTNn6s0335S/v7969Oghs9ksk8mU4zZpaWk6ceKEwsPDVaVKFUVERKhu3brFWDUAoKwZPHiwjh07pjFjxtikvTvuuENjx47N0t9999136tOnj1q1akXAQ4ER7mAz8+fP19y5c5WQkJDnukYNeMU9FbNnz56qXr16qQ93ERERCgwM1IIFCzRt2rQCbXvmzBl17dpVbdu21YYNG+xUIQAAN8Ndenq6Nm/ebNf9EPBQWJxzB4cw4jl4nGNXeOvWrVOdOnX06KOPFnjbOnXqaNq0afryyy/z9YcFAABKOs7BQ2ER7uAwRgp4BLuiOXbsmDp27JjrNMzcdO7cWTdu3NDp06dtWxgAAA5CwENhEO7gUEYIePkJdgEBATKZTDKZTIqJiclXuytWrLBu89hjj9m26BLmxo0b8vDwKPT2mdtmXsQHAIDiZK9+noCHgiLcweFKc8AryIhdSEiIYmNj5efnJ+nmuWL9+/eXp6enqlWrplmzZlmvnCVJw4cPV2xsrDp27Gj31wEAAIrGXv08AQ8FUabD3YABA/T9999LksLDw9WuXTs1btxY9evXV0hIiOLj47Osb7FY9I9//EM9e/a0WQ0//vij+vfvb31sMpnUokULtW7dWq1bt1ZUVJQk6fr167rrrrt05coVm+27JCmNAa+gUzE9PT3l6+srFxcXpaenq3///kpJSVF0dLRWrlypFStWZLnSqIeHh3x9feXq6mrvlwIAAIrInv08AQ/5VWbDXWJioo4dO6b27dtr69atmjx5st5//32dOHFCJ0+elLu7u/r27auMjAzrNjt27JCPj48OHTqkX3/91SZ1zJ49W//3f/+XZVlUVJRiYmIUExOjrl27SpLc3d01ZswYvfHGGzbZb0lUmgJeUc+x27Ztm44ePaoPP/xQrVu3Vt++ffXiiy9q4cKFSklJsVPVpc+qVatUuXLlbNMtBw8ebLPLUAMAYGv26OcJeMgPQ4e7EydO6I477tAvv/wiSQoNDdU999yjjIwMffnllwoKCpLJZNJLL72kOXPmqE2bNpJu3nT7jTfe0O+//66vv/7a2t7SpUsVEhKikSNHatmyZdblERER8vPz05QpU9SqVSs1b95c+/btkyRNnTpVL7/8cpaaateurbS0NJ05c0ZHjhyxBri8jBgxQv/5z39k5LtXlIaAZ4uLp+zZs0ctWrRQ9erVrcuCgoJ09epVHTlyxJbllmrDhg1Tenq6Nm3aZF32559/6osvvtCECRMcWBkAADmzVz9PwENeXBxdgD01btxYr7/+uh544AGFhoZq4cKF2rt3r5ycnLRhwwaNGzdOknTgwAG98847WbZ1dXXVXXfdpZiYGAUFBenSpUvaunWrFi9ebJ1D/fzzz8vJ6WY+Pn78uJYuXapFixZpyZIlmjNnjr766is9+uijCgoK0pNPPilnZ2ctWrRIkyZNkouLiyIjI9WuXbtsdffs2VNpaWnq2bOnXnzxRXl5eUmSfH195eHhoSNHjljnc/9dUlKSDY9gwdhqxCkz4A0dOlSjRo0qUffBs9VVMc+fP5/lA1+S9XFBb3Celpbm0P93W7h1hPxWHh4eGjlypJYvX65hw4ZJkj788EPVqVNHAQEB2dZPTk4u9ccCAFBypaen52s9W/bzf5cZ8Pr06aO+fftyHzxkYehwJ0n//Oc/tXPnTgUFBWnHjh2qWrWqUlNTrfOf85J5Fb6wsDD17dtXPj4+8vHxUfXq1fXVV1+pb9++kqQGDRqoQ4cOkqSOHTsqNDRU0s2A2axZM33++ecKCgrSmjVrdPjwYUnSH3/8ke0X/7ffflOdOnWUlJSkhx56SLNmzdKiRYusz/v6+uqPP/7IMdw5+pe7KFc8vNWtAW/OnDmaN2+eTdotqtGjR+vkyZPasWNHibjdQXp6utauXau1a9c6upQicXFxUePGjW/7XEhIiNq1a6ezZ8+qVq1aWrFihcaNG3fb2yZ069bN3qUCAMq4AQMGOLqELAFvwoQJ+vTTTx1dEkoIQ0/LlG6Oavz000+qVKmSzp49K0n65ptv1LlzZ+toUNu2bbVnz54s26WkpGj//v3q1KmTpJtTMr/55hvVq1dP9erV06+//qqlS5da13d3d7f+29nZOcvVkKZPn653331XH374oXr37m0NdJ6enrp+/XqW/dapU0eS5OXlpSlTplgvqJLp+vXrNgtQJd3FixeVnp5uHbksCby8vHTjxg1dvXq1SO34+vrqwoULWZZlPvb19S1S20bTpk0btWrVSqtWrdL+/ft15MgR66g7AAAlUXH08/Hx8UpJSSlR35PgeIYfufu///s/NW7cWKtWrVJgYKDuuusubdy4Uffdd591naeeekrBwcHq1KmTWrdurbS0NM2YMUMBAQHy9/fX/v37FRcXp3PnzlmnYcbHx6t27dqKi4vLs4Y+ffro8ccf10svvZTlLystW7bMMuJy+fJlubm5ydPTUxkZGfrkk0+s5wFKN0dpTp06pRYtWuS4L0fOvV64cKFefPFFm7S1bNkyBQcHa/LkyZo7d65N2rSFFStWaNCgQerXr5+2bNly26mB+dGxY0f9+9//1p9//qlq1apJkr7++mt5e3urWbNm+W7H2dlZw4YN0/LlywtVR0nRu3fvXJ8PDg7W/PnzdfbsWfXq1Uu1a9e+7XrffvttiRhRBQAY04gRI/K1nq36+Zxs3bpVgwcPVp8+fbRkyZIitwfjMHS4Cw8P19atW7V37155enrqzTff1LBhw3TlyhW9/vrr1vX69eunJUuWKDg4WFevXlVsbKwGDRqkVatWSbo5ajdixAhrsJMkHx8f9e7dW6tXr87zy6TJZNLEiRP10UcfZbmXSZcuXfTHH3/o0qVLqlSpko4fP67JkyfLZDIpLS1Nbdu21YIFC6zr79q1S+3atVOlSpVy3Jcj/3pjq0v23xrsFi5cmOW4O5qHh4c+//zzIge8Pn36qFmzZhozZozmzZun8+fP6+mnn9YjjzwiNze3ArXl4uJS6v9ql9f/8ciRIzVz5kz95z//sf5e3o6Hh0epPxYAgJLL2dk5X+fd2bKf/7tbg93atWuL3B6MxdDhbsCAAVnmRQ8bNky1a9fWSy+9lO3ctIEDB2rgwIGSbl79ctSoUdZz6m495+1W69evt/47JibG+m8/Pz+dPn06y7o7d+7UY489lmVZuXLl9Mgjj2jp0qWaNWuWOnbsqEOHDuX4ehYvXqwnn3wyt5dc6pXkYJfJFgHP2dlZ4eHhevjhh9WxY0d5eXlp7NixeuGFF+xTdClXoUIFDRkyRF988YUGDx7s6HIAAMiVvfp5gh3yYuhwdzt33323wsPDc10nICDAen5eUe3bt08jRoxQs2bNNHLkyGzPT58+Pcu5ezm5fv26unfvnuf0tdKsNAS7TLYIeHXr1tWWLVvsU6ABnT17VqNGjaIjAwCUCrbu5wl2yI+S++3ZIPz9/fXzzz9r06ZNcnZ2zva8q6urHn744TzbcXd3z9d6pVVpCnaZMgNely5d1K9fP0VEROS6/qJFi2Q2m61XS81LWFiYzGZztovqlDWXL1/Whg0bFBERoUceecTR5QAAcFv27OcJdsivMjdyh5KnNAa7TPkdwQsLC1NycrKk/10RNS8DBw603l7Dx8fHViWXWBaL5bbL27Rpo8uXL+u1117L8XYJOW0LAEBxsGc/T7BDQRDu4FClOdhlyk/Aq1WrVoHbLV++vMqXL2+jKks2d3f3HG8+/vfzV28nc9uycpsQAEDJYq9+nmCHgip936RhGEYIdpkKOkUTWbVs2VJRUVH5ugLZ7URERMjLy0v169e3cWUAADgGwQ6FUXq/TaNUM1Kwy0TAK7zhw4frwoULev755ws8xfLo0aN6++23de+99zJyBwAwBIIdCotpmbCp69eva9q0adbHLVq0UEhISJZ1jBjsMuU1RTM9PV2vvfaazp8/b5P9nThxQtWrV7dJW47UoUMHvfLKK5o9e7ZWrlypwMBAlS9fXiaTKcdtUlNTdeLECUVGRqpZs2Z6++23i7FiAEBZ9dNPP2X5rlMUd9xxh2bOnJnluxDBDkVhsnAlAtjIiRMn9OCDD+r69euSpKtXr+r06dOKjY2Vr6+vJGMHu1slJydr0KBB2rVrV5aAt3PnTvXo0UNNmjSx3vQ9IyNDJ06ckCQ1bty4wMfk5ZdfVv/+/W1av6N8++23+vjjj3Xw4EFdu3ZNUs7Hx9nZWTVr1tTAgQP1wAMPlImLzgAAHOuzzz4r8L3qcurHrl+/rpMnT+q7776zXliFYIeiItzBbrZs2aL+/fvr3LlzqlGjRpkJdpluF/B27NihXr166ddff1W9evUk3bwYiNlsliQlJibKy8vLgVWXPBwfAEBpllM/dvToUTVv3lzR0dHq2LEjwQ42Yexv1ygxMoPdhAkTykSwkzgHDwAA5A/BDrZi/G/YcLjMYGexWPTWW2+ViWCXiYAHAAByQ7CDLZWdb9lwmKefftp6BcSyFOwy3RrwXnrpJUeXAwAASpAXXniBYAebKXvftFFsKlasKHd3d02cONHRpThcZsDr3bu39bgAAICyy9PTU97e3rr33nsJdrAZLqgCu7p8+bJcXFzk7e0tiQtiXL9+XRkZGfL09LQu44IhueP4AABKs9z6scTERJUrV45gB5vhPnewq4oVKyopKcnRZZQYjNgBAIBMmaEPsBWmZQIAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBzjQxYsX9ccffzi6DAAAbOKnn35SWlqao8sAyizCHeBAdevWVY8ePRxdBgAARbZs2TK1bNlSDz30kKNLAcosF0cXAJRl169fV3JyslxcXPhLJwCg1Fq2bJmCg4NlMpl0/fp1R5cDlFmM3AEONnfuXNWtW1cuLi46deqUo8sBAKBAMoPd5MmT1aVLF0eXA5RphDvAwWrUqKGoqCjVrVtXffv21c8//+zokgAAyJdbg93ChQvl5MRXS8CR+A0ESoDMgFe+fHkFBAQQ8AAAJR7BDih5+C0EiklSUpL8/f1Vvnx56096errc3d0l3Qx4O3fulNlsJuABAEq0nIKdu7u7Pv744yx93dy5cx1cLVB2mCwWi8XRRcDYkpKSZDabJUmJiYny8vJycEWO8csvv+jOO+/Uww8/rIYNG0qSPD09FRwcLGdnZ+t6sbGxCgwMVGJioiIiItSgQQNHlVwi8P4BgJIltxG73377TevXr7c+/vTTT5Wenq69e/c6otQSgX4MxYmrZQLFbNiwYQoMDMzx+cwRvMDAQAUEBBDwAAAlRl5TMevWravHH3/c+vj48eM6ePBgcZcJlFlMywRKIKZoAgBKGs6xA0o+fiuBEoqABwAoKQh2QOnAbyZQghHwAACORrADSg9+O4ESjoAHAHAUgh1QupTp39ABAwbo+++/lySFh4erXbt2aty4serXr6+QkBDFx8dnWd9isegf//iHevbsabMafvzxR/Xv39/62GQyqUWLFmrdurVat26tqKgoSdL169d111136cqVKzbbN0oPAh4AoLgR7JAXvkuXPGX2tzQxMVHHjh1T+/bttXXrVk2ePFnvv/++Tpw4oZMnT8rd3V19+/ZVRkaGdZsdO3bIx8dHhw4d0q+//mqTOmbPnq3/+7//y7IsKipKMTExiomJUdeuXSXdvG/MmDFj9MYbb9hkvyh9CHgAgOJCsENe+C5dMhn6NzU0NFSTJk2yPo6Pj1eVKlV06dIlffnllwoKCpLJZNJLL72kOXPmqE2bNpIkFxcXvfHGG/r999/19ddfW7dfunSpQkJCNHLkSC1btsy6PCIiQn5+fpoyZYpatWql5s2ba9++fZKkqVOn6uWXX7aue+LECdWuXVtpaWk6c+aMjhw5Yn3T5WXEiBH6z3/+I25NWHYR8AAA9kawQ6YTJ07ojjvu0C+//CLp5nfre+65RxkZGXyXLqEMfZ+74OBgNWrUSPPmzZOPj4+WL1+uQYMGqVKlStqwYYPGjRsnSTpw4IDeeeedLNu6urrqrrvuUkxMjIKCgnTp0iVt3bpVixcv1pkzZ9S/f389//zz1g+848ePa+nSpVq0aJGWLFmiOXPm6KuvvtKjjz6qoKAgPfnkk3J2dtaiRYs0adIkubi4KDIyUu3atctWd8+ePZWWlqaePXvqxRdftN7s0tfXVx4eHjpy5Ij8/Pxu+5qTkpJseARt49aaSmJ9xeXatWs2aYf74AEA7MUewS4jI6NM9/+l4XtQTjdWb9y4sV5//XU98MADCg0N1cKFC7V37145OTkZ9rt0aWfocOfj46OhQ4dq2bJlevzxx7V48WJ98sknSk1NVXR0tFauXJlnGx4eHpKksLAw9e3bVz4+PvLx8VH16tX11VdfqW/fvpKkBg0aqEOHDpKkjh07KjQ0VNLNX4pmzZrp888/V1BQkNasWaPDhw9Lkv744w9Vr149y/5+++031alTR0lJSXrooYc0a9YsLVq0yPq8r6+v/vjjjxzfkGazuYBHqXj9/fWicDIDXvfu3RUUFKRTp045uiQAQCn3/fffKzg4WCEhITYdsTt06FCJ/35SXErq96DcRrL++c9/aufOnQoKCtKOHTtUtWpVQ3+XLu0MP84+bdo0LVmyRFu3blXVqlXVpk0bffPNN+rcubPKlSsnSWrbtq327NmTZbuUlBTt379fnTp1knRzGPmbb75RvXr1VK9ePf36669aunSpdX13d3frv52dnZWWlmZ9PH36dL377rv68MMP1bt3b+ub0NPTU9evX8+y3zp16ki6+ReUKVOmWE8CzXT9+nXrLwnKtgsXLujixYsltqMAAJQu5cuXl4eHh3755Zds309QdqWlpemnn35SpUqVdPbsWUniu3QJZuiRO0lq0qSJ6tevr0mTJmnevHmSpI0bN+q+++6zrvPUU08pODhYnTp1UuvWrZWWlqYZM2YoICBA/v7+2r9/v+Li4nTu3DnrX7Hi4+NVu3ZtxcXF5VlDnz599Pjjj+ull17Sp59+al3esmVLrV271vr48uXLcnNzk6enpzIyMvTJJ59Y5y5LUnp6uk6dOqUWLVrkuK/ExMT8H5xikpSUZP0lvHDhQo5D/0b366+/5vp/VxAxMTHq2bOn6tevry1bttikTQBA2dasWTN9+eWX6tu3rwYNGqTPP/9cnp6eRW63ZcuWioyMtEGFpVNp/x70f//3f2rcuLFWrVqlwMBA3XXXXYb+Ll3aGT7cSVJISIimTp2qoUOHymKx6KuvvtLrr79ufb5fv35asmSJgoODdfXqVcXGxmrQoEFatWqVpJt/aRgxYkSW6Qk+Pj7q3bu3Vq9erbZt2+a6f5PJpIkTJ+qjjz5Sx44drcu7dOmiP/74Q5cuXVKlSpV0/PhxTZ48WSaTSWlpaWrbtq0WLFhgXX/Xrl1q166dKlWqlOO+SvoHhpeXV4mv0V5s0UFKWYPd119/LR8fH5u0CwBAt27dbB7wnJycymzf/3el7XtQeHi4tm7dqr1798rT01Nvvvmmhg0bpitXrhj2u3SpZykDHnnkEcsLL7xgsVgslj179lj69++f6/o7d+601KxZ07Jlyxab1dC/f3/LqlWrsi2fN2+eZd68eflqY/jw4ZZt27bZrKbikpiYaJFkkWRJTEx0dDkOc+rUKYskyzfffFPoNg4ePGipVKmSxd/f33L58mXbFVeC8f4BgOIXGRlp8fT0tPTq1cuSlJRU6HYmTZpkadeunQ0rK32M1o/xXbpkM/Q5d+fOnVOTJk104MABPfbYY5Kku+++W+Hh4bluFxAQoLNnz1pP8CyKffv2qUGDBnJyctLIkSOzPT99+vR8nWR8/fp1de/eXb179y5yTSidGLEDABSXzBG86OhoDRo0yGZXfEbpx3fpks1ksRj4Rg8oEZKSkqy/dImJiaVqOoIt/fLLL7rzzjv1zTffKDAwsEDbluVgx/sHABzn22+/Vd++fdWpU6dCTdGcPHmyDh48qL1799qpwpKPfgzFydAjd4ARlOVgBwBwLEbwgNKFcAeUYAQ7AICjEfCA0oNwB5RQBDsAQElBwANKB8IdUAIR7AAAJQ0BDyj5ysR97oCSZN68efrwww8l3bz33b///W95e3tbnyfYAQBKqrzug7dnzx598MEH1se7du1ShQoVHFEqUCYR7oBiUrt2bY0fP17Hjx/XlStXJN3sBP38/DR58mRJBDsAQMmXW8B76qmndPjwYTVq1EiSVLlyZWsfB8D+CHdAMSlXrpyWLVuWZZmLy/9+BQl2AIDSIreAd88991hnqAAoXpxzB5QAMTExCggIUL169Qh2AIBS4dZz8AYMGMA5eEAJQLgDHCwz2CUmJmrTpk0EOwBAqdGtWzetX79ekZGRBDygBGBaJuBgS5YskbOzs9LT0wl2AIBSp0uXLsrIyFBkZKQyMjLUsGFDR5cElFmM3AEOVL9+fbVt21bp6emOLgUAgCLJyMiQ2WzWP/7xD0eXApRZjNwBDnT8+HFdunRJVatWdXQpAAAU2alTp1StWjVHlwGUWYzcAQ7k5OQkDw8PR5cBAIBNeHl5OboEoEwj3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwBwAAAAAGQLgDAAAAAAMg3AEAAACAARDuAAAAAMAACHcAAAAAYACEOwAAAAAwAMIdAAAAABgA4Q4AAAAADIBwB5s6dOiQ4uPjHV0GAABwsIyMDEVHR8tisTi6lBLjv//9ry5cuODoMmBghDvYzLZt29ShQwf16NHD0aUAAAAHe/PNN9W5c2c98sgjBLz/r2fPnmratKmjy4CBEe5gE9u2bdOgQYOUlpamtLQ0R5cDAAAcLC0tTU5OTlq8eDEBT5Kzs7OuXr3K9yTYFeEORZYZ7Hr06KGJEyc6uhwAAFBCVKpUSR988EGZDniHDh2Ss7OzWrRoodmzZzu6HBgc4Q5FcmuwW79+vdzc3BxdEgAAKEEmTpxYZgNeTEyMBgwYoBYtWuibb75RxYoVHV0SDM7F0QWg9CLYAQCA/Mic2RMcHCxJWrhwoUwmkyNLsruYmBj17NlT9evX17Zt2wh2KBaM3KFQcgp2Xl5eOnz4sJycnKw/5cuXt25Xvnz5LM/l9VOxYkUdPXrUUS8TxSg9PV2LFi1S165d5eXllef7p1y5cqpbt64effRR/fe//3Vg5QBgHIGBgQXqp3P7nJ49e7a8vLysz5WlEbycgp2Xl5cSEhKyHL9WrVopOTnZwRXDKBi5Q4HlNmL39NNPq379+srIyCjyfm7cuKFp06bp0KFDatasWZHbQ8mVnp6uCRMm6MMPP1Tfvn314osvysvLK9e/6qalpenkyZP69NNP9emnn+qbb75R8+bNi7FqADCeyMhIjRo1Sl27drVJe506dcryuCyM4OU2Yjdx4kS5u7vr+vXrkm6ej7dw4UJduXJFHh4ejioZBmKyGPnPJrC54pyKmZSUJLPZrDVr1mjEiBF224+jZb5OSUpMTMzyV86y4quvvtI999yjDz/8UKNGjSrQtn/99Ze6dOmi+vXra8uWLXaqEADKBicnJ7333nsKCQmx636WLl2q4OBgPfzww4YKeAWdivnFF19owIABio2Nla+vbzFVCSNj5A75xjl2sJd169apQYMGGjlyZIG3rVKliqZOnarHH39c8fHx8vHxsX2BAACbMuIIHufYoSQg3CFfCHawp59//lnt2rUrdMfevn17paWl6bfffiPcAUApYaSAR7BDScEFVZCn/AS7gIAAmUwmmUwmxcTE5KvdFStWWLd57LHHbFs0SpXU1NQi/cEgc9vU1FRblQQAuIW9+nkjXGSFYIeShHCHXBVkxC4kJESxsbHy8/OTJJ05c0b9+/eXp6enqlWrplmzZiktLc26/vDhwxUbG6uOHTva/XUAAICisVc/X5oDHsEOJQ3hDjkq6FRMT09P+fr6ysXFRenp6erfv79SUlIUHR2tlStXasWKFXrmmWes63t4eMjX11eurq72fikAAKCI7NnPl8aAR7CTBgwYoO+//16SFB4ernbt2qlx48aqX7++QkJCFB8fn2V9i8Wif/zjH+rZs6fNavjxxx/Vv39/62OTyaQWLVqodevWat26taKioiRJ169f11133aUrV67YbN8lEeEOt1XUc+y2bdumo0eP6sMPP1Tr1q2tl7dfuHChUlJS7FQ1jCYuLk6+vr56+eWXrcuio6Pl6uqqHTt2OLAyACjb7NHPl6aAR7C7eYXvY8eOqX379tq6dasmT56s999/XydOnNDJkyfl7u6uvn37Zrk91o4dO+Tj46NDhw7p119/tUkds2fP1v/93/9lWRYVFaWYmBjFxMRYb+vh7u6uMWPG6I033rDJfksqwh2yscXFU/bs2aMWLVqoevXq1mVBQUG6evWqjhw5YstyYWBVq1bVsmXL9Nxzz2nfvn1KSEjQmDFjNHXqVJv+1Q8AUDD26udLQ8ArS8EuNDRUkyZNsj6Oj49XlSpVdOnSJX355ZcKCgqSyWTSSy+9pDlz5qhNmzaSJBcXF73xxhv6/fff9fXXX1u3X7p0qUJCQjRy5EgtW7bMujwiIkJ+fn6aMmWKWrVqpebNm2vfvn2SpKlTp2b5I++JEydUu3ZtpaWl6cyZMzpy5Ei+78s4YsQI/ec//ymR7ytb4WqZyMJWV8U8f/58lg98SdbH58+fL1Bb169fV1JSUqHqKA1ufW1Gfp25ye2m9/369VNISIhGjRolf39/eXl56ZVXXrntusnJyWX2GAJAcbJlP/93JfkqmvYKdteuXXNo/5XTPXaDg4PVqFEjzZs3Tz4+Plq+fLkGDRqkSpUqacOGDRo3bpwk6cCBA3rnnXeybOvq6qq77rpLMTExCgoK0qVLl7R161YtXrzYer7m888/Lyenm2NNx48f19KlS7Vo0SItWbJEc+bM0VdffaVHH31UQUFBevLJJ+Xs7KxFixZp0qRJcnFxUWRkpNq1a5et7p49eyotLU09e/bUiy++aH19vr6+8vDw0JEjR6znjhoN4Q5Wf/31lwYNGqRu3bqVqNsdjB8/XuPHj3d0GcXi7x1lWeHi4qLGjRvn+HxoaKj8/Py0du1a7d+/P8f3Zrdu3exVIgCgGE2cOFHp6emaPHmy2rRpY/ebqudX7969Vbt2bZuP2N155502a6swchrJ8vHx0dChQ7Vs2TI9/vjjWrx4sT755BOlpqZaz7XMi4eHhyQpLCxMffv2lY+Pj3x8fFS9enV99dVX6tu3rySpQYMG6tChgySpY8eOCg0NlSQ1btxYzZo10+eff66goCCtWbNGhw8fliT98ccf2b47/fbbb6pTp46SkpL00EMPadasWVq0aJH1eV9fX/3xxx+GDXdMy4SVm5ubatasqZMnTxb5r26+vr66cOFClmWZj319fYvUNsqeU6dO6dy5c8rIyNDp06cdXQ4AlHn27ufT09O1a9cuOTk5qU6dOkVuz1bq16+v33//vUz1RdOmTdOSJUu0detWVa1aVW3atNE333yjzp07q1y5cpKktm3bas+ePVm2S0lJ0f79+9WpUydJN6dkfvPNN6pXr57q1aunX3/9VUuXLrWu7+7ubv23s7NzliuvTp8+Xe+++64+/PBD9e7d2xroPD09df369Sz7zXy/eHl5acqUKdYLqmS6fv26NXAaESN3sCpfvrwiIiIUEBCggIAARUREqG7duoVqq2PHjvr3v/+tP//8U9WqVZMkff311/L29lazZs0K1Nby5cs1bNiwQtVRGiQlJVk/pC5cuJDj1Agj6927d47PpaSkaPTo0Ro+fLgaN26s4OBgHT582Pq+utW3336rtm3b2rNUADC08uXL52s9W/bzf5eenq7x48crLCxMH330kYKCgorUni199dVX6tOnj3r16qXt27dbzzErqlOnTpXY2TtNmjRR/fr1NWnSJM2bN0+StHHjRt13333WdZ566ikFBwerU6dOat26tdLS0jRjxgwFBATI399f+/fvV1xcnM6dO2edhhkfH6/atWsrLi4uzxr69Omjxx9/XC+99JI+/fRT6/KWLVtq7dq11seXL1+Wm5ubPD09lZGRoU8++STL/1F6erpOnTqlFi1aFPm4lFSEO2RRu3ZtmwS8Pn36qFmzZhozZozmzZun8+fP6+mnn9YjjzxS4Ome7u7uZSbweHl5lZnXeqvMD/rbmTNnjq5cuaK3335bZrNZW7Zs0YQJExQeHp5tXQ8PjzJ5/ACguNmyn7/V34Pd8OHDbVh10fn4+Gjbtm02D3ienp4luv8KCQnR1KlTNXToUFksFn311Vd6/fXXrc/369dPS5YsUXBwsK5evarY2FgNGjRIq1atknRz1G7EiBFZ+nsfHx/17t1bq1evzvMPsyaTSRMnTtRHH32U5b6JXbp00R9//KFLly6pUqVKOn78uCZPniyTyaS0tDS1bdtWCxYssK6/a9cutWvXTpUqVbLVoSlxmJaJbDIDnpOTkwICAvTbb78VuA1nZ2eFh4fL2dlZHTt21OjRo/Xggw/qhRdesEPFMKqIiAjNnz9fq1evlre3t5ycnLR69WpFRUVp8eLFji4PAMose/Tz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+fj42K9QAKVaZrj79NNPNWzYMLvuqywFPPp5FCeufoF84+IpgGNs27ZNSUlJeuqppwp8Dp3JZNLs2bOVkpKiL774wk4VAkDBcJEVwD74do58IdgBjvPLL7/IbDarUaNGhdr+jjvuUPXq1fXLL7/YuDIAKDwCHmB7fENHnvIT7AICAmQymWQymRQTE5OvdiMiIqzbDB482LZFAwaSmpoqV1fXIrXh5ubGFycAhWavfp6AB9gW4Q65KsiIXUhIiGJjY+Xn5ydJmjZtmu666y65ubmpdevW2dbv1KmTYmNjufE5AAClgL36eQIeYDuEO+SooFMxPT095evrKxeX/91hY8KECRo+fPht13d1dZWvr688PDxsWjcAALA9e/bzBDzANgh3uC1bnGP39ttv65FHHlH9+vXtUCGAuLg4+fr66uWXX7Yui46Olqurq3bs2OHAygCUBbbu5wl4QNER7pANF08BSoeqVatq2bJleu6557Rv3z4lJCRozJgxmjp1qnr27Ono8gCgwAh4QNG45L0KypKSGOxu3LihpKQkR5dhN7e+NiO/ThReSkpKjs/169dPISEhGjVqlPz9/eXl5aVXXnklx3Z4jwHISUn5fMgMeEOGDNGIESMMfx88wJYId7D65Zdf9OCDD2r48OElJthZLBY99thjeuyxxxxdSrGoXr26o0tACeXt7Z3jc6GhofLz89PatWu1f/9+ubm5ZVsnNTVVr732ml577TV7lgkANnHvvfdq7dq1Gjx4sEJDQzV79mxHlwSUCo7/9o4So0KFCqpTp46+//57nT171tHlAMinU6dO6dy5c8rIyNDp06cdXQ4AFFlGRoY+//xzOTk5qVmzZo4uByg1GLmDVeXKlbVz504FBgYqICBAERERql27tkNrMplMmj9/voKDgx1ahz0lJSVZR+wuXLggLy8vB1eEkuatt97SW2+9ddvnUlJSNHr0aA0fPlyNGzdWcHCwDh8+rGrVqmVZr1y5cnryySc1d+7c4igZQCl09epV1axZ09FlKCMjQ8HBwVq5cqVWrlypQYMGObokoNQg3CGLevXq2Szg/fzzz0pMTNT58+eVnJxsvelps2bNCnRDZjc3tzITeLy8vMrMa0X+5fb7MmfOHF25ckVvv/22zGaztmzZogkTJig8PPy27fD+ApCT9PT0Aq1vq37+Vn8PdqNHjy5UO0BZ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t261LmvQoIE++OADSdIPP/yg3r17q0qVKqpQoYK6d++uAwcOZNmnyWTS4sWL1bdvX3l4eKh+/fpat26dJMnDw0O+vr7WH1dXV3l6esrX11fr1q1Tr1695OzsLEnauHGjTCaTlixZYm27V69eevrpp62PFy9erDvvvFOurq5q3LixVq9ene0YlC9fXr6+vqpVq5YCAwM1duzYbDUDAAqPcAcAQDEwm80ym83auHGjbty4ke357t27a9euXUpPT5ckRUZGqkqVKoqIiJAknT17VqdOnVJAQIAkKSEhQWPHjtWuXbv03XffqWHDhurXr58SEhKytDt37lwNGTJEP/74o0aNGqURI0bo2LFjudbavXt3HT16VHFxcbetJTU1VXv27LHWsmHDBk2fPl0zZszQTz/9pMmTJ2v8+PHauXNnjvs4e/asNm/erA4dOuR16AAA+US4AwCgGLi4uGjFihVauXKlfHx81LlzZz311FM6dOiQJKlr165KSEjQwYMHZbFY9O2332rGjBnWQBUREaFatWqpQYMGkqQePXpo9OjRatKkiZo2bar3339f165dU2RkZJb9Dhs2TMHBwWrUqJFefPFF+fv765133sm1Vj8/P1WqVMnaVkREhGbMmGF9vHfvXqWmpqpTp06SpNDQUI0bN05TpkxRo0aN9MQTT+j+++9XaGholnaffPJJmc1meXh46I477pDJZNKbb75ZtAMLALAi3AEAUEyGDBmic+fOadOmTbrnnnsUERGhtm3basWKFfLx8VGrVq0UERGhw4cPy9XVVZMmTdLBgweVmJioyMhIde/e3drWhQsXFBISooYNG6pChQry9vZWYmKizpw5k2WfHTt2zPY4r5E7k8mkbt26KSIiQvHx8Tp69KimTJmiGzdu6Pjx44qMjFS7du3k6ekpSTp27Jg6d+6cpY3OnTtn28+sWbMUExOjQ4cOaceOHZKk/v37W0crAQBFQ7gDAKAYubu7q3fv3po7d66io6M1btw4Pfvss5KkgIAARUREWINcpUqV1LRpU+3atStbuBs7dqxiYmK0YMECRUdHKyYmRpUrV1ZKSopN6sysJSoqSm3atJG3t7c18P29lvyqUqWKGjRooIYNG6pHjx6aP3++oqOjc52+CQDIP8IdAAAO1KxZMyUlJUn633l3O3bssJ7PFhAQoDVr1ujkyZPWZZK0e/du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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from qualtran.drawing import get_musical_score_data, draw_musical_score\n", "msd = get_musical_score_data(cbloq)\n", "fig, ax = draw_musical_score(msd)\n", "fig.set_figwidth(9)" ] }, { "cell_type": "markdown", "id": "a56b18da", "metadata": {}, "source": [ "## Allocations and de-allocations\n", "\n", "We can encode operations that allocate and/or de-allocate quantum data as well. Each [`Register`](/reference/qualtran/Register.md) has an attribute called `side`. By default, it is set to `THRU` meaning that the quantum data moves through the register and that register is available for use as both an input and an output." ] }, { "cell_type": "code", "execution_count": 30, "id": "2b195829", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "reg = Register('ctrl', QBit())\n", "reg.side" ] }, { "cell_type": "markdown", "id": "4788d0c9", "metadata": {}, "source": [ "### `LEFT` and `RIGHT`\n", "\n", "We can declare registers that are input-only (\"LEFT\") or output-only (\"RIGHT\"). Pure-state quantum evolution is unitary; so using registers like these implies you're encoding a non-unitary operation. For example: bloqs which allocate a new qubit or discard an existing qubit would have asymmetric registers." ] }, { "cell_type": "code", "execution_count": 31, "id": "02d510b9", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "input_only_G1\n", "input_only\n", "\n", "\n", "\n", "ReAlloc\n", "\n", "ReAlloc\n", "\n", "input_only\n", "\n", "\n", "\n", "output_only\n", "\n", "\n", "\n", "input_only_G1:e->ReAlloc:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "output_only_G2\n", "output_only\n", "\n", "\n", "\n", "ReAlloc:e->output_only_G2:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from qualtran import Side\n", "\n", "@attrs.frozen\n", "class ReAlloc(Bloq):\n", " @property\n", " def signature(self):\n", " return Signature([\n", " Register('input_only', QBit(), side=Side.LEFT),\n", " Register('output_only', QBit(), side=Side.RIGHT),\n", " ])\n", " \n", "show_bloq(ReAlloc())" ] }, { "cell_type": "markdown", "id": "1537693c", "metadata": {}, "source": [ "Of course, the signature *only* provides the `side` of the register. It is up to the bloq author to give it functionality by providing a decomposition or annotating it with simulation information. We'll use the `State` and `Effect` one-qubit bloqs provided by the library to explore their behavior under simulation." ] }, { "cell_type": "code", "execution_count": 32, "id": "ad7b8708", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "ZeroState\n", "\n", "|0>\n", "\n", "\n", "q\n", "\n", "\n", "\n", "q_G1\n", "q\n", "\n", "\n", "\n", "ZeroState:e->q_G1:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "array([1.+0.j, 0.+0.j])" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qualtran.bloqs.basic_gates import ZeroState\n", "\n", "# Show a simple allocating bloq and its tensor representation\n", "show_bloq(ZeroState())\n", "ZeroState().tensor_contract()" ] }, { "cell_type": "code", "execution_count": 33, "id": "b5a764f7", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "ZeroEffect\n", "\n", "<0|\n", "\n", "q\n", "\n", "\n", "\n", "\n", "PlusState\n", "\n", "|+>\n", "\n", "\n", "q\n", "\n", "\n", "\n", "PlusState:e->ZeroEffect:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "0.4999999999999999" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from qualtran.bloqs.basic_gates import PlusState, ZeroEffect\n", "\n", "bb = BloqBuilder()\n", "\n", "# Wire up <+|0>\n", "q = bb.add(PlusState())\n", "bb.add(ZeroEffect(), q=q)\n", "\n", "# Show it and find its probability\n", "cbloq = bb.finalize()\n", "show_bloq(cbloq)\n", "cbloq.tensor_contract() ** 2" ] }, { "cell_type": "markdown", "id": "83c0a66d", "metadata": {}, "source": [ "## Algorithms\n", "\n", "We've been looking at small, familiar bloqs to get acquainted with the functionality. Bloqs can represent quantum operations at any level of complexity, but are particularly useful for reasoning about high-level algorithms. For example, Qualtran includes a reference implementation of modular exponentiation (the limiting operation for Shor's factoring)" ] }, { "cell_type": "code", "execution_count": 34, "id": "f5fd2783", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "exponent_G0\n", "exponent\n", "\n", "\n", "\n", "ModExp\n", "\n", "ModExp\n", "\n", "exponent\n", "\n", "\n", "x\n", "\n", "\n", "\n", "exponent_G0:e->ModExp:w\n", "\n", "\n", "3\n", "\n", "\n", "\n", "exponent_G3\n", "exponent\n", "\n", "\n", "\n", "ModExp:e->exponent_G3:w\n", "\n", "\n", "3\n", "\n", "\n", "\n", "x_G1\n", "x\n", "\n", "\n", "\n", "ModExp:e->x_G1:w\n", "\n", "\n", "1024\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from qualtran.bloqs.cryptography.rsa import ModExp\n", "\n", "mod_exp = ModExp(base=8, mod=13*17, exp_bitsize=3, x_bitsize=1024)\n", "show_bloq(mod_exp)" ] }, { "cell_type": "markdown", "id": "e6ff4cfd", "metadata": {}, "source": [ "High-level bloqs should be defined in terms of only-slightly-less high-level bloqs to keep each step of the decomposition understandable. The `ModExp` bloq's decomposition is:" ] }, { "cell_type": "code", "execution_count": 35, "id": "7f5bf452", "metadata": {}, "outputs": [], "source": [ "def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'SoquetT') -> Dict[str, 'SoquetT']:\n", " x = bb.add(IntState(val=1, bitsize=self.x_bitsize))\n", " exponent = bb.split(exponent)\n", "\n", " # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method\n", " base = self.base\n", " for j in range(self.exp_bitsize - 1, 0 - 1, -1):\n", " exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x)\n", " base = base * base % self.mod\n", "\n", " return {'exponent': bb.join(exponent), 'x': x}" ] }, { "cell_type": "markdown", "id": "5a17bad6", "metadata": {}, "source": [ "In addition to decomposing and visualizing, we can use other protocols to query properties of the bloq or test its correctness. " ] }, { "cell_type": "code", "execution_count": 36, "id": "c2268b3c", "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ "\n", "\n", "my_graph\n", "\n", "\n", "\n", "exponent_G20\n", "exponent\n", "\n", "\n", "\n", "Split\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "exponent_G20:e->Split:w\n", "\n", "\n", "3\n", "\n", "\n", "\n", "CModMulK\n", "\n", "CModMulK\n", "\n", "ctrl\n", "\n", "x\n", "\n", "\n", "\n", "Join\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CModMulK:e->Join:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "x_G16\n", "x\n", "\n", "\n", "\n", "CModMulK:e->x_G16:w\n", "\n", "\n", "1024\n", "\n", "\n", "\n", "exponent\n", "exponent\n", "\n", "\n", "\n", "Join:e->exponent:w\n", "\n", "\n", "3\n", "\n", "\n", "\n", "Split:e->CModMulK:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CModMulK_G1\n", "\n", "CModMulK\n", "\n", "ctrl\n", "\n", "x\n", "\n", "\n", "\n", "Split:e->CModMulK_G1:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CModMulK_G4\n", "\n", "CModMulK\n", "\n", "ctrl\n", "\n", "x\n", "\n", "\n", "\n", "Split:e->CModMulK_G4:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CModMulK_G1:e->Join:w\n", "\n", "\n", "1\n", "\n", "\n", "\n", "CModMulK_G1:e->CModMulK_G4:w\n", "\n", "\n", "1024\n", "\n", "\n", "\n", "IntState\n", "\n", "|1>\n", "\n", "\n", "val\n", "\n", "\n", "\n", "IntState:e->CModMulK_G1:w\n", "\n", "\n", "1024\n", "\n", "\n", "\n", "CModMulK_G4:e->CModMulK:w\n", "\n", "\n", "1024\n", "\n", "\n", "\n", "CModMulK_G4:e->Join:w\n", "\n", "\n", "1\n", "\n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_bloq(mod_exp.decompose_bloq())" ] } ], "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.8" } }, "nbformat": 4, "nbformat_minor": 5 }