diff --git a/qualtran/_infra/Bloqs-Tutorial.ipynb b/qualtran/_infra/Bloqs-Tutorial.ipynb
index f43624aa3..64ed5ee5a 100644
--- a/qualtran/_infra/Bloqs-Tutorial.ipynb
+++ b/qualtran/_infra/Bloqs-Tutorial.ipynb
@@ -7,7 +7,7 @@
"source": [
"# Bloqs Tutorial\n",
"\n",
- "Bloqs lets you represent high-level quantum programs and subroutines as a hierarchical\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` abstract base class."
]
},
@@ -26,6 +26,17 @@
" ..."
]
},
+ {
+ "attachments": {},
+ "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",
@@ -80,13 +91,14 @@
"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. In particular, we declare a name and type information for each\n",
- "quantum \"register\" on which the bloq operates. This property can be thought of as analogous\n",
- "to the function signature in ordinary programming. For example, it is analogous to function\n",
- "declarations in a C header (`*.h`) file.\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",
- "Concretely, we return an ordered collection of `Register` objects, each of which corresponds to a named\n",
- "register."
+ "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."
]
},
{
@@ -108,7 +120,7 @@
"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`\n",
- "container."
+ "container.
The `attrs.frozen` annotation removes some of the boilerplate to write an immutable Python class with a pre-defined set of attributes.
"
]
},
{
@@ -473,7 +485,7 @@
"## 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."
+ "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.
"
]
},
{
@@ -768,6 +780,191 @@
"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": null,
+ "id": "5e35b5bb",
+ "metadata": {},
+ "outputs": [],
+ "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` 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": null,
+ "id": "2b195829",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "reg = Register('ctrl', 1)\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": null,
+ "id": "02d510b9",
+ "metadata": {},
+ "outputs": [],
+ "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', bitsize=1, side=Side.LEFT),\n",
+ " Register('output_only', bitsize=1, 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": null,
+ "id": "ad7b8708",
+ "metadata": {},
+ "outputs": [],
+ "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": null,
+ "id": "b5a764f7",
+ "metadata": {},
+ "outputs": [],
+ "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 reasining 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": null,
+ "id": "f5fd2783",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qualtran.bloqs.factoring 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": null,
+ "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": null,
+ "id": "c2268b3c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "show_bloq(mod_exp.decompose_bloq())"
+ ]
+ },
{
"cell_type": "markdown",
"id": "e74ee2b3",
@@ -778,47 +975,13 @@
"Bloqs support a growing list of protocols that let you annotate a given `Bloq` with more\n",
"definitions or known information. In the following table we summarize the available protocols. Please note that the method you override as a bloq writer is often different than the method you call as a bloq user.\n",
"\n",
- "\n",
- "\n",
- "\n",
- "What | \n",
- "Call this | \n",
- "Override this | \n",
- "Notebook | \n",
- "
\n",
- "\n",
- "\n",
- " \n",
- "Bloq decomposition | \n",
- "decompose_bloq() | \n",
- "build_composite_bloq(...) | \n",
- "composite_bloq.ipynb | \n",
- "
\n",
- "\n",
- "Numerical simulation via quimb tensor networks | \n",
- "tensor_contract() | \n",
- "add_my_tensors(...) | \n",
- " | \n",
- "
\n",
- " \n",
- "Classical simulation | \n",
- "call_classically(**vals) | \n",
- "on_classical_vals(...) | \n",
- "classical_sim.ipynb | \n",
- "
\n",
- " \n",
- "Resource counting | \n",
- "t_complexity() for both | \n",
- " | \n",
- "
\n",
- " \n",
- "Conversion to Cirq | \n",
- "to_cirq_circuit(**quregs) | \n",
- "as_cirq_op(...) | \n",
- "cirq_gate.ipynb | \n",
- "
\n",
- " \n",
- "
"
+ "| What | Call this | Override this | Docs |\n",
+ "|----------------------|-----------------------------|-----------------------------|---------------------------------------------------------|\n",
+ "| Bloq decomposition | `decompose_bloq()` | `build_composite_bloq(...)` | [Composite bloq](/_infra/composite_bloq.ipynb) |\n",
+ "| Numerical simulation | `tensor_contract()` | `add_my_tensors(...)` | |\n",
+ "| Classical simulation | `call_classically(**vals)` | `on_classical_vals(...)` | [Classical simulation](/simulation/classical_sim.ipynb) |\n",
+ "| Resource counting | `bloq_counts()` | `bloq_counts()` | [Bloq counts](/resource_counting/bloq_counts.ipynb) |\n",
+ "| Conversion to Cirq | `to_cirq_circuit(**quregs)` | `as_cirq_op(...)` | [Cirq Interop](/cirq_interop/cirq_interop.ipynb) |"
]
}
],
diff --git a/qualtran/cirq_interop/cirq_interop.ipynb b/qualtran/cirq_interop/cirq_interop.ipynb
index 6b4dcc560..ca96d877e 100644
--- a/qualtran/cirq_interop/cirq_interop.ipynb
+++ b/qualtran/cirq_interop/cirq_interop.ipynb
@@ -5,9 +5,9 @@
"id": "eb6226ee",
"metadata": {},
"source": [
- "# Cirq Conversion\n",
+ "# Cirq Interoperability\n",
"\n",
- "Cirq is a quantum SDK for explicitly addressing physical qubits and scheduling gates. You can consider it analogous to a quantum assembly language. The bloqs infrastructure provides interoperability with Cirq."
+ "Cirq is a quantum SDK for explicitly addressing physical qubits and scheduling gates. You can consider it analogous to a quantum assembly language. Qualtran provides interoperability with Cirq."
]
},
{