From 4ae8410d7ab55f6b5503e5ae8566e22a6455fd78 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 20 May 2026 14:57:56 -0700 Subject: [PATCH 1/4] syntax --- qualtran/__init__.py | 4 +- qualtran/_infra/bloq.py | 9 +- qualtran/_infra/quantum_graph.py | 5 + qualtran/_infra/registers.py | 8 +- .../bloq-builder-gate-helpers.ipynb | 251 ++++++++ .../bloqify-syntactic-sugar.ipynb | 603 ++++++++++++++++++ qualtran/bloqs/arithmetic/addition.py | 13 +- qualtran/bloqs/arithmetic/bitwise.py | 9 +- qualtran/bloqs/arithmetic/negate.py | 16 +- qualtran/bloqs/arithmetic/negate_test.py | 42 +- qualtran/drawing/_show_funcs.py | 73 ++- qualtran/serialization/data_types.py | 2 +- qualtran/testing.py | 12 +- 13 files changed, 998 insertions(+), 49 deletions(-) create mode 100644 qualtran/bloqify_syntax/bloq-builder-gate-helpers.ipynb create mode 100644 qualtran/bloqify_syntax/bloqify-syntactic-sugar.ipynb diff --git a/qualtran/__init__.py b/qualtran/__init__.py index b41f8e6e71..981add8377 100644 --- a/qualtran/__init__.py +++ b/qualtran/__init__.py @@ -85,7 +85,7 @@ # Internal imports: none # External: # - numpy: multiplying bitsizes, making cirq quregs -from ._infra.registers import Register, Signature, Side +from ._infra.registers import Register, Signature, Side, qsig # Internal imports: none # External imports: none @@ -105,6 +105,8 @@ from ._infra.bloq_example import BloqExample, bloq_example, BloqDocSpec +from .drawing._show_funcs import show_bloq + from .bloqify_syntax import bloqify # -------------------------------------------------------------------------------------------------- diff --git a/qualtran/_infra/bloq.py b/qualtran/_infra/bloq.py index 08519c2cf9..a0389af06a 100644 --- a/qualtran/_infra/bloq.py +++ b/qualtran/_infra/bloq.py @@ -65,7 +65,9 @@ def _decompose_from_build_composite_bloq(bloq: 'Bloq') -> 'CompositeBloq': from qualtran import BloqBuilder - bb, initial_soqs = BloqBuilder.from_signature(bloq.signature, add_registers_allowed=False) + bb, initial_soqs = BloqBuilder.from_signature( + bloq.signature, add_registers_allowed=False, bloq_key=bloq.__class__.__name__ + ) out_soqs = bloq.build_composite_bloq(bb=bb, **initial_soqs) if not isinstance(out_soqs, dict): raise ValueError( @@ -692,6 +694,11 @@ def wire_symbol( return directional_text_box(text=pretty_str, side=reg.side) + def draw(self, type: str = 'graph'): + from qualtran.drawing import show_bloq + + return show_bloq(self, type=type) + def __str__(self): return self.__class__.__name__ diff --git a/qualtran/_infra/quantum_graph.py b/qualtran/_infra/quantum_graph.py index 84e69c357c..11bb0e543a 100644 --- a/qualtran/_infra/quantum_graph.py +++ b/qualtran/_infra/quantum_graph.py @@ -174,6 +174,11 @@ def reg(self) -> 'Register': def __hash__(self): raise TypeError("QVar objects during bloq building are *not* hashable.") + def __str__(self) -> str: + if self.ssa_name is not None: + return self.ssa_name + return str(self.soquet) + def __getitem__(self, item): if self._split_components is None: self._split_components = self.bb.split(self) diff --git a/qualtran/_infra/registers.py b/qualtran/_infra/registers.py index e25146afcf..6222b11f6d 100644 --- a/qualtran/_infra/registers.py +++ b/qualtran/_infra/registers.py @@ -113,7 +113,9 @@ def shape_symbolic(self) -> Tuple[SymbolicInt, ...]: @property def shape(self) -> Tuple[int, ...]: if is_symbolic(*self._shape): - raise ValueError(f"{self} is symbolic. Cannot get real-valued shape.") + raise ValueError( + f"{self.name}'s shape {self._shape} is symbolic. Cannot get real-valued shape." + ) return cast(Tuple[int, ...], self._shape) @property @@ -452,3 +454,7 @@ def __hash__(self): def __eq__(self, other) -> bool: return self._registers == other._registers + + +def qsig(*args, **kwargs) -> Signature: + return Signature.build(*args, **kwargs) diff --git a/qualtran/bloqify_syntax/bloq-builder-gate-helpers.ipynb b/qualtran/bloqify_syntax/bloq-builder-gate-helpers.ipynb new file mode 100644 index 0000000000..f36cc4cf2d --- /dev/null +++ b/qualtran/bloqify_syntax/bloq-builder-gate-helpers.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5b8c9ba4-3032-4984-8a34-750a7ccac823", + "metadata": {}, + "source": [ + "# BloqBuilder Gate Helper Methods\n", + "\n", + "For convenience, `qualtran.BloqBuilder` has methods for adding (\"calling\") common basic gates to your program. Each method maps 1-to-1 to a bloq class, particularly the `.qcall(...)` class method. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "551dfef2-3cb0-4db1-bc03-57e97bb2c609", + "metadata": {}, + "outputs": [], + "source": [ + "# Top-level qualtran imports\n", + "import qualtran as qlt\n", + "import qualtran.dtype as qdt" + ] + }, + { + "cell_type": "markdown", + "id": "6877a5ac-3fc9-4f5f-8738-1afaf689a6fb", + "metadata": {}, + "source": [ + "### Single qubit gates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77f77416-0a1a-478f-9ab2-b5c2b90cb9fb", + "metadata": {}, + "outputs": [], + "source": [ + "bb = qlt.BloqBuilder()\n", + "q = bb.in_register('q', qdt.QBit())\n", + "\n", + "q = bb.X(q)\n", + "q = bb.Y(q)\n", + "q = bb.Z(q)\n", + "q = bb.H(q)\n", + "q = bb.T(q)\n", + "q = bb.T(q, is_adjoint=True)\n", + "q = bb.S(q)\n", + "q = bb.S(q, is_adjoint=True)\n", + "q = bb.identity(q)\n", + "\n", + "qlt.show_bloq(bb.finalize(q=q))" + ] + }, + { + "cell_type": "markdown", + "id": "bf7ffb5a-d0fb-499d-acd0-f5008a56dc75", + "metadata": {}, + "source": [ + "### Two qubit gates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d295b5d-a2cd-47bd-92b6-6afb6ad72563", + "metadata": {}, + "outputs": [], + "source": [ + "bb = qlt.BloqBuilder()\n", + "q1 = bb.in_register('q1', qdt.QBit())\n", + "q2 = bb.in_register('q2', qdt.QBit())\n", + "\n", + "q1, q2 = bb.CNOT(q1, q2)\n", + "q1, q2 = bb.CZ(q1, q2)\n", + "q1, q2 = bb.CY(q1, q2)\n", + "q1, q2 = bb.CH(q1, q2)\n", + "q1, q2 = bb.swap(q1, q2)\n", + "[q1, q2], anc = bb.And([q1, q2])\n", + "[q1, q2] = bb.UnAnd([q1, q2], anc)\n", + "\n", + "qlt.show_bloq(bb.finalize(q1=q1, q2=q2))" + ] + }, + { + "cell_type": "markdown", + "id": "71f7f4e1-5f30-43ce-ad77-51c405cc5692", + "metadata": {}, + "source": [ + "### Three qubit gates" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2773c4fd-a65b-4ccb-9005-247db189e81e", + "metadata": {}, + "outputs": [], + "source": [ + "bb = qlt.BloqBuilder()\n", + "q1 = bb.in_register('q1', qdt.QBit())\n", + "q2 = bb.in_register('q2', qdt.QBit())\n", + "q3 = bb.in_register('q3', qdt.QBit())\n", + "\n", + "q1, q2, q3 = bb.Toffoli(q1, q2, q3)\n", + "q1, q2, q3 = bb.cswap(q1, q2, q3)\n", + "\n", + "qlt.show_bloq(bb.finalize(q1=q1, q2=q2, q3=q3))" + ] + }, + { + "cell_type": "markdown", + "id": "aec6346b-5241-4850-940f-7aa15c7a5cea", + "metadata": {}, + "source": [ + "### Rotations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "309bf03a-e83c-476a-9778-0149b9d7a481", + "metadata": {}, + "outputs": [], + "source": [ + "bb = qlt.BloqBuilder()\n", + "q1 = bb.in_register('q1', qdt.QBit())\n", + "q2 = bb.in_register('q2', qdt.QBit())\n", + "\n", + "q1 = bb.rx(q1, 0.1)\n", + "q1 = bb.ry(q1, 0.2)\n", + "q1 = bb.rz(q1, 0.3)\n", + "q1, q2 = bb.cry(q1, q2, 0.4)\n", + "q1, q2 = bb.crz(q1, q2, 0.5)\n", + "q1 = bb.su2_rotation(q1, theta=0.1, phi=0.2, lambd=0.3)\n", + "q1 = bb.z_pow(q1, exponent=0.6)\n", + "q1, q2 = bb.cz_pow(q1, q2, exponent=0.7)\n", + "q1 = bb.x_pow(q1, exponent=0.8)\n", + "q1 = bb.y_pow(q1, exponent=0.9)\n", + "\n", + "qlt.show_bloq(bb.finalize(q1=q1, q2=q2))" + ] + }, + { + "cell_type": "markdown", + "id": "6eb498c3-474d-444f-9b7c-118c1c7ffb19", + "metadata": {}, + "source": [ + "### Measurement and discard" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96d6094f-9bc5-49ce-8862-db4b657ec1b0", + "metadata": {}, + "outputs": [], + "source": [ + "bb = qlt.BloqBuilder()\n", + "q1 = bb.in_register('q1', qdt.QBit())\n", + "q2 = bb.in_register('q2', qdt.QBit())\n", + "\n", + "c1 = bb.measure_x(q1)\n", + "c2 = bb.measure_z(q2)\n", + "\n", + "bb.discard(c1)\n", + "bb.discard(c2)\n", + "\n", + "q3 = bb.alloc_qbit()\n", + "bb.discard_q(q3)\n", + "\n", + "qlt.show_bloq(bb.finalize())" + ] + }, + { + "cell_type": "markdown", + "id": "7b45cbe0-3cba-4fd5-8978-87b7caddf456", + "metadata": {}, + "source": [ + "### Allocations" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0709526a-d696-41ee-b52c-cf7a580891bd", + "metadata": {}, + "outputs": [], + "source": [ + "bb = qlt.BloqBuilder()\n", + "\n", + "q = bb.alloc_qbit(0)\n", + "bb.free_qbit(q, 0)\n", + "\n", + "x = bb.alloc_qint(55, bitsize=8)\n", + "bb.free(x)\n", + "\n", + "y = bb.allocate(3)\n", + "bb.free(y)\n", + "\n", + "qlt.show_bloq(bb.finalize())" + ] + }, + { + "cell_type": "markdown", + "id": "45faf54e-cf0b-458e-9073-f6938b888de9", + "metadata": {}, + "source": [ + "### Bookkeeping" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32a49f87-a497-45c7-8e2a-9aac55d28f97", + "metadata": {}, + "outputs": [], + "source": [ + "bb = qlt.BloqBuilder()\n", + "x = bb.in_register('x', qdt.QAny(3))\n", + "\n", + "xs = bb.split(x)\n", + "x = bb.join(xs)\n", + "\n", + "qlt.show_bloq(bb.finalize(x=x))" + ] + } + ], + "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.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqify_syntax/bloqify-syntactic-sugar.ipynb b/qualtran/bloqify_syntax/bloqify-syntactic-sugar.ipynb new file mode 100644 index 0000000000..ccd3052782 --- /dev/null +++ b/qualtran/bloqify_syntax/bloqify-syntactic-sugar.ipynb @@ -0,0 +1,603 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e5db9a68-440c-4ccf-8ed4-bf4ec0e92901", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "# `bloqify` Syntactic Sugar\n", + "\n", + "This notebook shows how we can use syntactic sugar, in particular the `bloqify` decorator to quickly compose bloqs in Qualtran." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d9acdc43-3340-4c50-80e1-d8f87d4774f9", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "fragment" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Top-level qualtran imports\n", + "import qualtran as qlt\n", + "import qualtran.dtype as qdt" + ] + }, + { + "cell_type": "markdown", + "id": "576bf4ed-5cd6-4797-bab6-3d3b6176a730", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "## Example: Negate\n", + "\n", + "We'll use the overloaded operators on quantum variables as an introductory example of how to define a quantum subroutine using syntactic sugar.\n", + "\n", + " - We define an ordinary function that takes in the quantum variable `x`. This function can take arbitrary classical compile-time parameters as well.\n", + " - The `bloqify` decorator lets us record calls to this function as quantum subroutine calls.\n", + " - The function takes a bloq-builder as its first argument. This is required. It's usually helpful to pass along if you need to allocate new quantum variables. Here, it's unused.\n", + " - The `~` operator performs a bitwise-not via `qualtran.bloqs.arithmetic.BitwiseNot`.\n", + " - The `+=` operator with a compile-time classical right-hand argument performs a quantum-classical addition via `qualtran.bloqs.arithmetic.AddK`.\n", + " - A bloqify function returns a string-keyed dictionary. The keys set the output register names." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "043ac529-cbe8-47d5-a130-0165bb607cc7", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "@qlt.bloqify\n", + "def negate(bb, x):\n", + " x = ~x\n", + " x += 1\n", + " return {'x': x}" + ] + }, + { + "cell_type": "markdown", + "id": "d2268e34-a72e-4cc9-9154-0a956831365b", + "metadata": {}, + "source": [ + "### Using our `negate` function\n", + "\n", + "You can \"call\" a `bloqify` function from another quantum function definition: a caller `bloqify` function; a `build_composite_bloq` method override; or a raw `BloqBuilder` construction (as below). When you \"call\" the bloqify function, the system will record it as a quantum subroutine (analogous to a call to `bb.add(bloq, ...)`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "741fa43c-c896-42bb-be6c-8913730f781f", + "metadata": {}, + "outputs": [], + "source": [ + "# Set up a simple, outer, \"calling\" function definition\n", + "bb = qlt.BloqBuilder()\n", + "x = bb.alloc_qint(bitsize=8, k=5)\n", + "\n", + "# Perform a quantum call to our bloqify function (!)\n", + "x = negate(bb, x)\n", + "\n", + "# Finish our outer function definition\n", + "program = bb.finalize(x_out=x)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "577e8f83-d73d-4f12-b5d4-1949dc2fa0d5", + "metadata": {}, + "outputs": [], + "source": [ + "program.draw()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a8ea916-9423-4796-9c49-05c48fe4ab2e", + "metadata": {}, + "outputs": [], + "source": [ + "program.call_classically()" + ] + }, + { + "cell_type": "markdown", + "id": "b9d4c246-8d93-42fa-814b-771b084994db", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "### Instantiating `negate`.\n", + "\n", + "In (ordinary) Qualtran, we're used to instantiating bloqs as Python objects. If you'd like a bloq object from a `bloqify` function, you must provide additional information, namely the quantum signature specifying the input and output quantum data types.\n", + "\n", + "- `qlt.Bloq` objects declare their quantum `qlt.Signature` a priori. `bloqify` functions infer their signatures when they're \"called\". To instantiate a `qlt.Bloq` object from a `bloqify` function, you must explicitly pass a signature to `bloqify_func.make(...)`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6114ff2e-5d58-440f-ad7b-5f3d4664910e", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "negate_bloq = negate.make(qlt.qsig(x=qdt.QInt(8)))\n", + "negate_bloq.draw(type='musical_score')\n", + "\n", + "negate_bloq = negate.make(qlt.qsig(x=qdt.QInt(64)))\n", + "negate_bloq.draw(type='musical_score')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a35b0de-eb88-423e-b1c3-0fb307c607e1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "print('x_out =', negate_bloq.call_classically(x=5))" + ] + }, + { + "cell_type": "markdown", + "id": "d22a8d7b-5c79-442d-9176-9fee298da18a", + "metadata": {}, + "source": [ + "### Dumping `negate` to L1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d81043af-ba97-4d72-8087-2243b85b8cbd", + "metadata": {}, + "outputs": [], + "source": [ + "negate.print_l1(qlt.qsig(x=qdt.QInt(8)))" + ] + }, + { + "cell_type": "markdown", + "id": "7e1f8e22-bab1-45c1-b346-d1e14f8dc637", + "metadata": {}, + "source": [ + "## Example: Making a Bell state\n", + "\n", + "As a second example, we'll create a simple program that allocates a bell state by allocating two qubits and performing the appropriate 1- and 2-qubit operations.\n", + "\n", + " - Again, we use the `bloqify` decorator so we can call this from other quantum functions.\n", + " - It takes a `bb: BloqBuidler` as its only argument since this function allocates a new state rather than operating on any existing quantum variables.\n", + " - We use methods on `BloqBuilder` to do common atomic operations like `bb.H(q)` to call the Hadamard gate on quantum variable `q`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84799744-2158-49d2-9a04-a5e0a5ebdc45", + "metadata": {}, + "outputs": [], + "source": [ + "@qlt.bloqify\n", + "def bell(bb):\n", + " q0 = bb.alloc_qbit()\n", + " q0 = bb.H(q0)\n", + "\n", + " q1 = bb.alloc_qbit()\n", + " q0, q1 = bb.CNOT(q0, q1)\n", + " return {'q0': q0, 'q1': q1}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b023fe08-d4c1-4533-93e5-1845177a4741", + "metadata": {}, + "outputs": [], + "source": [ + "bell.draw(type='musical_score')" + ] + }, + { + "cell_type": "markdown", + "id": "cd048f64-e943-40ec-a56d-526c215222f4", + "metadata": {}, + "source": [ + "### Instantiating `bell`\n", + "\n", + "To recover a Python `qlt.Bloq` object, we again call `bloqify_func.make(...)`. Since this function does not take any quantum inputs or classical compile-time parameters, we can call `make` with no arguments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e932f55-b7bd-4923-aadf-8209da13e08f", + "metadata": {}, + "outputs": [], + "source": [ + "bell_bloq = bell.make()\n", + "print(repr(bell_bloq))" + ] + }, + { + "cell_type": "markdown", + "id": "e4eed96e-d589-47e5-8af7-478d8d4b3e7b", + "metadata": {}, + "source": [ + "When we have the bloq object, we can use any of the Qualtran analysis routines that operates on bloqs. Here, we perform a numerical simulation (tensor contraction) to get the state vector representation of the simple circuit. It is indeed a Bell state." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5320f882-7dc1-4fa9-ad40-72aa783150f0", + "metadata": {}, + "outputs": [], + "source": [ + "bell.make().tensor_contract()" + ] + }, + { + "cell_type": "markdown", + "id": "7a991bba-3a5f-45db-a2e4-3ddb6c604c16", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "## Building `negate` from the ground up\n", + "\n", + "When we used the operators `~` and `+=`, we used bloqs from the standard library (namely `BitwiseNot` and `AddK`) to build our program. For illustrative purposes, we'll implement more of the callees of a `negate` subroutine from a simpler instruction set. We'll write our routines in terms:\n", + "\n", + " - atomic bloqs (`CNOT`, `X`, ...)\n", + " - `qualtran.bloqs.arithmetic.Add` quantum-quantum adder\n" + ] + }, + { + "cell_type": "markdown", + "id": "9fad1e15-d4ac-460c-bc27-b38cca68720b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "### `xor_k`\n", + "\n", + "This bloq does $x \\oplus k$ for quantum $x$ and classical $k$. Specifically: it flips bits in `x` according to the bit-encoding of the classical compile-time integer `k`.\n", + "\n", + " - We use `x[:]` to split e.g. a quantum integer into its individual qubits.\n", + " - We use `x.dtype` to write generic code without boilerplate. Here, we use `x.dtype` to turn the classical integer `k` into bits.\n", + " - We use the `~q` operator on individual qubits (i.e. `QVar` with `dtype=QBit()`). The operator knows to use `qualtran.bloqs.basic_gates.XGate`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d9bcf54-1fd4-4934-9aba-6e1c8042830f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "@qlt.bloqify\n", + "def xor_k(bb: 'qlt.BloqBuilder', x: 'qlt.QVar', k: int):\n", + " \"\"\"XOR the classical value `k` into the `x` register.\"\"\"\n", + " xs = x[:]\n", + " for i, bit in enumerate(x.dtype.to_bits(k)):\n", + " if bit == 1:\n", + " xs[i] = ~xs[i]\n", + "\n", + " return {'x': bb.join(xs, dtype=x.dtype)}" + ] + }, + { + "cell_type": "markdown", + "id": "0f529d44-d0e2-49f8-9742-6f3c42b7ee3f", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "### `bitwise_not`\n", + "\n", + "This bloq does $X$ to each qubit.\n", + "\n", + " - We implicitly split the qubits. The `x` qvar will call split once, and subsequent index access will give you the results of the split operation.\n", + " - We could have used `~` again, but library functions can also be provided by little wrapper functions accessible on `bb`, here: `bb.X`. These just delegate to `XGate.qcall(...)`, which we'll discuss later; but they look nicer." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b0c76e2a-f431-46c1-9619-6f016f62f499", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "@qlt.bloqify\n", + "def bitwise_not(bb: 'qlt.BloqBuilder', x: 'qlt.QVar'):\n", + " \"\"\"NOT (X gate) on each bit encoding `x`\"\"\"\n", + " outs = []\n", + " for i in range(len(x)):\n", + " out = bb.X(x[i])\n", + " outs.append(out)\n", + "\n", + " return {'x': bb.join(outs)}" + ] + }, + { + "cell_type": "markdown", + "id": "dd9508b4-abbb-4076-aeb5-12b846f57dd4", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "### `add_k`\n", + "\n", + "This bloq adds a constant by loading it into a register with clifford operations and performing a quantum-quantum addition.\n", + "\n", + " - We use our previously-defined `xor_k` function. You can see that you call it like normal.\n", + " - We're using `Add` from the standard library. Bloqs can learn a new classmethod `qcall` which takes both quantum variables and classical compile-time parameters to instantiate a bloq object and add it to the program. Here, `Add.qcall` knows how to deduce the two data types from the quantum variables; and it can extract the `bb` object from within the quantum variables." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "135e3b16-1566-4022-bc77-11b73f745400", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "@qlt.bloqify\n", + "def add_k(bb: 'qlt.BloqBuilder', x: 'qlt.QVar', k: int):\n", + " \"\"\"A classical `k` into quantum `x`.\"\"\"\n", + " from qualtran.bloqs.arithmetic import Add\n", + "\n", + " # load `k`\n", + " qk = bb.allocate(dtype=x.dtype)\n", + " qk = xor_k(bb, x=qk, k=k)\n", + "\n", + " # Add\n", + " qk, x = Add.qcall(a=qk, b=x)\n", + "\n", + " # unload `k`\n", + " qk = xor_k(bb, x=qk, k=k)\n", + " bb.free(qk)\n", + "\n", + " return {'x': x}" + ] + }, + { + "cell_type": "markdown", + "id": "5c08a86c-ca9d-4b39-b1de-40ada075df8b", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "### Negate\n", + "\n", + "We put things together in the same way, calling our functions we defined above." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d668c1b-0ae4-4bdf-b39b-92933b576b25", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "fragment" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "@qlt.bloqify\n", + "def negate(bb: 'qlt.BloqBuilder', x: 'qlt.QVar'):\n", + " x = bitwise_not(bb, x=x)\n", + " x = add_k(bb, x=x, k=1)\n", + " return {'x': x}" + ] + }, + { + "cell_type": "markdown", + "id": "9e935995-8d89-44b8-a7dc-132db9e466f1", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "### Instantiating\n", + "\n", + "This is the same as before. To get a bloq object, we need to give the overall signature, while all subsequent callees deduce data types from the quantum variables that get passed from callers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27c49439-b839-4149-ae71-5e4a8082e947", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "fragment" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "bloq = negate.make(qlt.Signature.build(x=qdt.QInt(4)))\n", + "qlt.show_bloq(bloq)" + ] + }, + { + "cell_type": "markdown", + "id": "929d560e-e4f1-4cfe-a866-2a36ec643d2c", + "metadata": {}, + "source": [ + "### Complete L1 QLT Intermediate Representation (IR)\n", + "\n", + "We can compile our entire program to the QLT intermediate representation (IR) to view the full, static program." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c3d877dd-efcc-4d05-afcc-ed9f056b51ea", + "metadata": {}, + "outputs": [], + "source": [ + "def is_in_isa(bloq: qlt.Bloq):\n", + " from qualtran.bloqs.bookkeeping._bookkeeping_bloq import _BookkeepingBloq\n", + " from qualtran.bloqs.basic_gates import XGate\n", + " from qualtran.bloqs.arithmetic import Add\n", + "\n", + " return isinstance(bloq, (_BookkeepingBloq, XGate, Add))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98decbc5-c5f4-4e4d-86e2-3a8592c0bb45", + "metadata": {}, + "outputs": [], + "source": [ + "from qualtran.l1 import dump_l1\n", + "print(dump_l1(bloq, force_extern_pred=is_in_isa))" + ] + }, + { + "cell_type": "markdown", + "id": "2711c67d-5799-40f5-ae46-10cb34fb8eaa", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "slide" + }, + "tags": [] + }, + "source": [ + "### Call graph\n", + "\n", + "We can inspect the call graph of bloqs defined via `@bloqify`.\n", + "\n", + "Whereas the built-in `AddK` bloq can override its callgraph, declare its tensors, and be queried for its compile-time classical parameters; these bloq objects are all named instantiations of the `CompositeBloq` container class, which can be limiting. To promote\n", + "an L3 `@bloq` function to its own `Bloq` class, I'll show that somewhere else." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c39c1bc-c2fe-444f-ac79-c86233956592", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "subslide" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_call_graph\n", + "show_call_graph(bloq, agg_gate_counts='t_and_ccz')" + ] + } + ], + "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.13.9" + }, + "toc": { + "base_numbering": 0 + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 5a7bb55768..36831cd770 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -46,7 +46,7 @@ from qualtran.bloqs.mcmt.and_bloq import And from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv from qualtran.cirq_interop import decompose_from_cirq_style_method -from qualtran.drawing import directional_text_box, Text +from qualtran.drawing import directional_text_box, Text, TextBox from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import add_ints from qualtran.symbolics import is_symbolic, SymbolicInt @@ -430,7 +430,7 @@ class AddK(Bloq): def __attrs_post_init__(self): if not isinstance(self.dtype, (QInt, QUInt, QMontgomeryUInt)): raise NotImplementedError( - "Only QInt, QUInt and QMontgomeryUInt types are supported for composite addition." + f"Only QInt, QUInt and QMontgomeryUInt types are supported for composite addition, not {self.dtype}" ) @cached_property @@ -493,6 +493,15 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return counts + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return Text('') + if reg.name == 'x': + return TextBox(f'+={self.k}') + raise ValueError(f"Unknown register {reg}") + def __str__(self): return f'AddK(k={self.k})' diff --git a/qualtran/bloqs/arithmetic/bitwise.py b/qualtran/bloqs/arithmetic/bitwise.py index 7708e45167..8353dd8d0e 100644 --- a/qualtran/bloqs/arithmetic/bitwise.py +++ b/qualtran/bloqs/arithmetic/bitwise.py @@ -35,7 +35,7 @@ SoquetT, ) from qualtran.bloqs.basic_gates import CNOT, OnEach, XGate -from qualtran.drawing import TextBox, WireSymbol +from qualtran.drawing import Text, TextBox, WireSymbol from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.symbolics import is_symbolic, SymbolicInt @@ -108,7 +108,7 @@ def wire_symbol( self, reg: Optional['Register'], idx: tuple[int, ...] = tuple() ) -> 'WireSymbol': if reg is None: - return TextBox("") + return Text("") return TextBox(f"⊕{self.k}") @@ -171,6 +171,9 @@ def build_composite_bloq(self, bb: BloqBuilder, x: Soquet, y: Soquet) -> dict[st def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return {CNOT(): self.dtype.num_qubits} + def adjoint(self) -> 'Xor': + return self + def on_classical_vals( self, x: 'ClassicalValT', y: 'ClassicalValT' ) -> dict[str, 'ClassicalValT']: @@ -234,7 +237,7 @@ def wire_symbol( self, reg: Optional['Register'], idx: tuple[int, ...] = tuple() ) -> 'WireSymbol': if reg is None: - return TextBox("") + return Text("") return TextBox("~x") diff --git a/qualtran/bloqs/arithmetic/negate.py b/qualtran/bloqs/arithmetic/negate.py index 0d42dcb3f1..c0007e432e 100644 --- a/qualtran/bloqs/arithmetic/negate.py +++ b/qualtran/bloqs/arithmetic/negate.py @@ -12,11 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import TYPE_CHECKING, Union +from typing import Mapping, TYPE_CHECKING, Union from attrs import frozen -from qualtran import Bloq, bloq_example, BloqDocSpec, QInt, QMontgomeryUInt, QUInt, Signature +from qualtran import ( + Bloq, + bloq_example, + BloqDocSpec, + QInt, + QMontgomeryUInt, + QUInt, + Signature, +) from qualtran.bloqs.arithmetic import AddK from qualtran.bloqs.arithmetic.bitwise import BitwiseNot @@ -28,10 +36,12 @@ class Negate(Bloq): """Compute the two's complement negation for a integer/fixed-point value. - This bloq is equivalent to the "Unary minus" [1] C++ operator. + This bloq is similar to the "Unary minus" [1] C++ operator. - For a signed `x`, the output is `-x`. - For an unsigned `x`, the output is `2^n - x` (where `n` is the bitsize). + On overflow, we use the overflow behavior of the implementation. + This is computed by the bit-fiddling trick `-x = ~x + 1`, as follows: 1. Flip all the bits (i.e. `x := ~x`) 2. Add 1 to the value (interpreted as an unsigned integer), ignoring diff --git a/qualtran/bloqs/arithmetic/negate_test.py b/qualtran/bloqs/arithmetic/negate_test.py index b6ee06978a..c5fb3e9b52 100644 --- a/qualtran/bloqs/arithmetic/negate_test.py +++ b/qualtran/bloqs/arithmetic/negate_test.py @@ -22,23 +22,35 @@ def test_examples(bloq_autotester): bloq_autotester(_negate_symb) -@pytest.mark.parametrize("bitsize", [1, 2, 3, 4, 8]) +@pytest.mark.parametrize("bitsize", [1, 3, 8]) def test_negate_classical_sim(bitsize: int): - # TODO use QInt once classical sim is fixed. - # classical sim currently only supports unsigned. - def _uint_to_int(val: int) -> int: - return QInt(bitsize).from_bits(QUInt(bitsize).to_bits(val)) + dtype = QInt(bitsize) + bloq = Negate(dtype) + cbloq = bloq.decompose_bloq() - dtype = QUInt(bitsize) + if bitsize == 3: + assert list(dtype.get_classical_domain()) == [-4, -3, -2, -1, 0, 1, 2, 3] - bloq = Negate(dtype) - for x_unsigned in dtype.get_classical_domain(): - (neg_x_unsigned,) = bloq.call_classically(x=x_unsigned) - x = _uint_to_int(x_unsigned) - neg_x = _uint_to_int(int(neg_x_unsigned)) - if x == -(2 ** (bitsize - 1)): + for x_in in dtype.get_classical_domain(): + (x_out,) = cbloq.call_classically(x=x_in) + + if x_in == -(2 ** (bitsize - 1)): # twos complement negate(-2**(n - 1)) == -2**(n - 1) - assert neg_x == x + assert x_out == x_in + else: + assert x_out == -x_in + + +@pytest.mark.parametrize("bitsize", [1, 3, 8]) +def test_negate_unsigned_classical_sim(bitsize: int): + dtype = QUInt(bitsize) + bloq = Negate(dtype) + cbloq = bloq.decompose_bloq() + + for x_in in dtype.get_classical_domain(): + (x_out,) = cbloq.call_classically(x=x_in) + + if x_in == 0: + assert x_out == 0 else: - # all other values - assert neg_x == -x + assert x_out == 2**bitsize - x_in diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index 93b1a6b6cd..ffa9574b05 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -14,20 +14,12 @@ """Convenience functions for showing rich displays in Jupyter notebook.""" +# TODO: move to _infra and export to top level import os from typing import Dict, Optional, overload, Sequence, TYPE_CHECKING, Union -import IPython.display -import ipywidgets - from qualtran import Bloq -from .bloq_counts_graph import format_counts_sigma, GraphvizCallGraph -from .flame_graph import get_flame_graph_svg_data -from .graphviz import PrettyGraphDrawer, TypedGraphDrawer -from .musical_score import draw_musical_score, get_musical_score_data -from .qpic_diagram import qpic_diagram_for_bloq - if TYPE_CHECKING: import networkx as nx import sympy @@ -44,24 +36,29 @@ def show_bloq(bloq: 'Bloq', type: str = 'graph'): # pylint: disable=redefined-b then latex diagrams are drawn using `qpic`, which should be installed already and is invoked via a subprocess.run() call. Otherwise, draw a musical score diagram. """ - if type.lower() == 'graph': - IPython.display.display(PrettyGraphDrawer(bloq).get_svg()) - elif type.lower() == 'dtype': - IPython.display.display(TypedGraphDrawer(bloq).get_svg()) - elif type.lower() == 'musical_score': - fig, ax = draw_musical_score(get_musical_score_data(bloq)) - fig.show() - elif type.lower() == 'latex': + type = type.lower() + if type == 'graph': + show_bloq_graph(bloq) + elif type == 'dtype': + show_bloq_dtype_graph(bloq) + elif type == 'musical_score' or type == 'stave': + show_bloq_musical_score(bloq) + elif type == 'latex': show_bloq_via_qpic(bloq) else: raise ValueError( f"Unknown `show_bloq` type: {type}." - "Allowed types are [graph, dtype, musical_score, latex]" + "Allowed types are [graph, dtype, musical_score, stave, latex]" ) def show_bloqs(bloqs: Sequence['Bloq'], labels: Optional[Sequence[Optional[str]]] = None): """Display multiple bloqs side-by-side in IPython.""" + import IPython.display + import ipywidgets + + from .graphviz import PrettyGraphDrawer + n = len(bloqs) if labels is not None: assert len(labels) == n, 'Must provide exactly as many labels as bloqs' @@ -114,6 +111,10 @@ def show_call_graph( approach is used where each type of gate is counted individually. """ + import IPython.display + + from .bloq_counts_graph import GraphvizCallGraph + if isinstance(item, Bloq): IPython.display.display( GraphvizCallGraph.from_bloq( @@ -126,19 +127,55 @@ def show_call_graph( def show_counts_sigma(sigma: Dict['Bloq', Union[int, 'sympy.Expr']]): """Display nicely formatted bloq counts sums `sigma`.""" + import IPython.display + + from .bloq_counts_graph import format_counts_sigma + IPython.display.display(IPython.display.Markdown(format_counts_sigma(sigma))) def show_flame_graph(*bloqs: 'Bloq', **kwargs): """Display hiearchical decomposition and T-complexity costs as a Flame Graph.""" + from .flame_graph import get_flame_graph_svg_data + svg_data = get_flame_graph_svg_data(*bloqs, **kwargs) + import IPython.display + IPython.display.display(IPython.display.SVG(svg_data)) +def show_bloq_graph(bloq: 'Bloq'): + import IPython.display + + from .graphviz import PrettyGraphDrawer + + IPython.display.display(PrettyGraphDrawer(bloq).get_svg()) + + +def show_bloq_dtype_graph(bloq): + import IPython.display + + from .graphviz import TypedGraphDrawer + + IPython.display.display(TypedGraphDrawer(bloq).get_svg()) + + +def show_bloq_musical_score(bloq: 'Bloq'): + import matplotlib.pyplot as plt + + from .musical_score import draw_musical_score, get_musical_score_data + + fig, ax = draw_musical_score(get_musical_score_data(bloq)) + plt.show() + + def show_bloq_via_qpic(bloq: 'Bloq', width: int = 1000, height: int = 400): """Display latex diagram for bloq by invoking `qpic`. Assumes qpic is already installed.""" + from .qpic_diagram import qpic_diagram_for_bloq + output_file_path = qpic_diagram_for_bloq(bloq, output_type='png') + import IPython.display from IPython.display import Image IPython.display.display(Image(output_file_path, width=width, height=height)) diff --git a/qualtran/serialization/data_types.py b/qualtran/serialization/data_types.py index 9f7aebe00f..03bc21f1aa 100644 --- a/qualtran/serialization/data_types.py +++ b/qualtran/serialization/data_types.py @@ -31,7 +31,7 @@ def data_type_to_proto(data: QCDType) -> data_types_pb2.QDataType: if isinstance(data, QBit): return data_types_pb2.QDataType(qbit=data_types_pb2.QBit()) - bitsize = int_or_sympy_to_proto(data.bitsize) # type: ignore[attr-defined] + bitsize = int_or_sympy_to_proto(data.num_bits) if isinstance(data, QAny): return data_types_pb2.QDataType(qany=data_types_pb2.QAny(bitsize=bitsize)) elif isinstance(data, QInt): diff --git a/qualtran/testing.py b/qualtran/testing.py index 253f7a1c16..04dadf96dc 100644 --- a/qualtran/testing.py +++ b/qualtran/testing.py @@ -122,10 +122,14 @@ def assert_connections_compatible(cbloq: CompositeBloq): lr = cxn.left.reg rr = cxn.right.reg - if not is_symbolic(lr.dtype.num_qubits) and lr.dtype.num_qubits <= 0: - raise BloqError(f"{cxn} has an invalid number of qubits: {lr.dtype}") - if not is_symbolic(rr.dtype.num_qubits) and rr.dtype.num_qubits <= 0: - raise BloqError(f"{cxn} has an invalid number of qubits: {rr.dtype}") + if not is_symbolic(lr.dtype.num_qubits) and lr.dtype.num_bits <= 0: + raise BloqError( + f"{cxn} has an invalid number of bits: {lr.dtype} with {lr.dtype.num_bits}" + ) + if not is_symbolic(rr.dtype.num_qubits) and rr.dtype.num_bits <= 0: + raise BloqError( + f"{cxn} has an invalid number of bits: {rr.dtype} with {rr.dtype.num_bits}" + ) if not check_dtypes_consistent(lr.dtype, rr.dtype): raise BloqError(f"{cxn}'s QDTypes are incompatible: {lr.dtype} -> {rr.dtype}") From 512ef644aec0a3e732d439703f78a0fc77ff68fa Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 20 May 2026 15:12:37 -0700 Subject: [PATCH 2/4] fixes --- qualtran/_infra/bloq.py | 4 ++-- qualtran/bloqify_syntax/_infra_test.py | 11 +++++++++++ qualtran/bloqs/arithmetic/negate.py | 12 ++---------- qualtran/drawing/_show_funcs.py | 1 - 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/qualtran/_infra/bloq.py b/qualtran/_infra/bloq.py index a0389af06a..7a46a750f6 100644 --- a/qualtran/_infra/bloq.py +++ b/qualtran/_infra/bloq.py @@ -694,7 +694,7 @@ def wire_symbol( return directional_text_box(text=pretty_str, side=reg.side) - def draw(self, type: str = 'graph'): + def draw(self, type: str = 'graph'): # pylint: disable=redefined-builtin from qualtran.drawing import show_bloq return show_bloq(self, type=type) @@ -710,7 +710,7 @@ def _pkg_(cls) -> str: def _class_name_in_pkg_(cls) -> str: """The bloq class's name with its package. - The Qualtran standard library contains a heirarchy of packages under + The Qualtran standard library contains a hierarchy of packages under `qualtran.bloqs.*`. Each bloq class is defined in a module (i.e. the "*.py" file) and re-exported one level up. """ diff --git a/qualtran/bloqify_syntax/_infra_test.py b/qualtran/bloqify_syntax/_infra_test.py index 4e4a0ea6a1..603651469f 100644 --- a/qualtran/bloqify_syntax/_infra_test.py +++ b/qualtran/bloqify_syntax/_infra_test.py @@ -17,6 +17,7 @@ import pytest import qualtran as qlt +import qualtran.testing as qlt_testing from qualtran import BloqBuilder, QVar @@ -113,3 +114,13 @@ def test_bloqify_missing_bb(): @qlt.bloqify # type: ignore[arg-type] def no_bb_func(x: 'QVar'): return {'x': x} + + +@pytest.mark.notebook +def test_bloqify_notebook(): + qlt_testing.execute_notebook('bloqify-syntactic-sugar') + + +@pytest.mark.notebook +def test_bb_helper_notebook(): + qlt_testing.execute_notebook('bloq-builder-gate-helpers') diff --git a/qualtran/bloqs/arithmetic/negate.py b/qualtran/bloqs/arithmetic/negate.py index c0007e432e..5c7ebcbc49 100644 --- a/qualtran/bloqs/arithmetic/negate.py +++ b/qualtran/bloqs/arithmetic/negate.py @@ -12,19 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. from functools import cached_property -from typing import Mapping, TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Union from attrs import frozen -from qualtran import ( - Bloq, - bloq_example, - BloqDocSpec, - QInt, - QMontgomeryUInt, - QUInt, - Signature, -) +from qualtran import Bloq, bloq_example, BloqDocSpec, QInt, QMontgomeryUInt, QUInt, Signature from qualtran.bloqs.arithmetic import AddK from qualtran.bloqs.arithmetic.bitwise import BitwiseNot diff --git a/qualtran/drawing/_show_funcs.py b/qualtran/drawing/_show_funcs.py index ffa9574b05..7f72bb975a 100644 --- a/qualtran/drawing/_show_funcs.py +++ b/qualtran/drawing/_show_funcs.py @@ -14,7 +14,6 @@ """Convenience functions for showing rich displays in Jupyter notebook.""" -# TODO: move to _infra and export to top level import os from typing import Dict, Optional, overload, Sequence, TYPE_CHECKING, Union From 841ef891fc89e7ffdfb256aff1216aa37143accd Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 20 May 2026 15:14:40 -0700 Subject: [PATCH 3/4] docs --- docs/bloq_infra.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/bloq_infra.rst b/docs/bloq_infra.rst index b149d00032..c711a4d5a5 100644 --- a/docs/bloq_infra.rst +++ b/docs/bloq_infra.rst @@ -45,6 +45,8 @@ types (``Register``), and algorithms (``CompositeBloq``). :maxdepth: 1 :caption: Advanced Topics: + bloqify_syntax/bloqify-syntactic-sugar.ipynb + bloqify_syntax/bloq-builder-gate-helpers.ipynb _infra/Bloqs-Tutorial.ipynb _infra/composite_bloq.ipynb cirq_interop/cirq_interop.ipynb From de279a329910c64677cd0e580930eab37ae50b6f Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 20 May 2026 15:27:31 -0700 Subject: [PATCH 4/4] fixes --- qualtran/bloqs/arithmetic/negate.ipynb | 4 +++- qualtran/serialization/bloq_test.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/qualtran/bloqs/arithmetic/negate.ipynb b/qualtran/bloqs/arithmetic/negate.ipynb index 832bdbaf66..0c6f5ee38e 100644 --- a/qualtran/bloqs/arithmetic/negate.ipynb +++ b/qualtran/bloqs/arithmetic/negate.ipynb @@ -38,10 +38,12 @@ "## `Negate`\n", "Compute the two's complement negation for a integer/fixed-point value.\n", "\n", - "This bloq is equivalent to the \"Unary minus\" [1] C++ operator.\n", + "This bloq is similar to the \"Unary minus\" [1] C++ operator.\n", "- For a signed `x`, the output is `-x`.\n", "- For an unsigned `x`, the output is `2^n - x` (where `n` is the bitsize).\n", "\n", + "On overflow, we use the overflow behavior of the implementation.\n", + "\n", "This is computed by the bit-fiddling trick `-x = ~x + 1`, as follows:\n", "1. Flip all the bits (i.e. `x := ~x`)\n", "2. Add 1 to the value (interpreted as an unsigned integer), ignoring\n", diff --git a/qualtran/serialization/bloq_test.py b/qualtran/serialization/bloq_test.py index d423a5bca7..fd13e1d412 100644 --- a/qualtran/serialization/bloq_test.py +++ b/qualtran/serialization/bloq_test.py @@ -79,6 +79,7 @@ def test_cbloq_to_proto_two_cnot(): resolver_dict.add_to_resolver_dict(TestTwoCNOT) cbloq = TestTwoCNOT().decompose_bloq() + cbloq = attrs.evolve(cbloq, bloq_key=None) proto_lib = bloq_serialization.bloqs_to_proto(cbloq) assert len(proto_lib.table) == 2 # TestTwoCNOT and TestCNOT # First one is always the CompositeBloq. @@ -133,6 +134,7 @@ def test_cbloq_to_proto_test_two_cswap(): cswap_proto = bloq_serialization.bloqs_to_proto(TestCSwap(100)).table[0].bloq assert TestCSwap(100).t_complexity().t == 7 * 100 cbloq = TestTwoCSwap(100).decompose_bloq() + cbloq = attrs.evolve(cbloq, bloq_key=None) proto_lib = bloq_serialization.bloqs_to_proto(cbloq, max_depth=100) assert len(proto_lib.table) == 2 assert proto_lib.table[1].bloq == cswap_proto @@ -177,6 +179,7 @@ def test_meta_bloq_to_proto(): sub_bloq_one = TestTwoCSwap(20) sub_bloq_two = TestTwoCSwap(20).decompose_bloq() + sub_bloq_two = attrs.evolve(sub_bloq_two, bloq_key=None) bloq = TestMetaBloq(sub_bloq_one, sub_bloq_two) depth_0_lib = bloq_serialization.bloqs_to_proto(bloq, max_depth=0)