Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[open-systems] QCDType, CDType, QDType #1584

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a9d5d7f
notey
mpharrigan Mar 5, 2025
62e9fee
ctrl
mpharrigan Mar 5, 2025
ac2db81
qdtype
mpharrigan Mar 5, 2025
fbca42b
ctrl spec
mpharrigan Mar 5, 2025
4dfe53a
controlled
mpharrigan Mar 5, 2025
0f3b5a1
controlled
mpharrigan Mar 5, 2025
d8bbe49
data types
mpharrigan Mar 5, 2025
a747b51
on classical vals
mpharrigan Mar 5, 2025
497d7ee
dtypes
mpharrigan Mar 5, 2025
9bba4e4
registers
mpharrigan Mar 5, 2025
efe6f0f
registers
mpharrigan Mar 5, 2025
e145433
bloq ctrl spec
mpharrigan Mar 5, 2025
dedc304
cast
mpharrigan Mar 5, 2025
9fc5300
bloq dtypes
mpharrigan Mar 5, 2025
e0f2b41
controlled
mpharrigan Mar 5, 2025
c58116c
bloq dtypes
mpharrigan Mar 5, 2025
535c8ef
ctrl
mpharrigan Mar 5, 2025
9e97cc9
bloq dtypes
mpharrigan Mar 5, 2025
f283249
bloq dtypes
mpharrigan Mar 5, 2025
a26cdab
bloq dtypes
mpharrigan Mar 5, 2025
f9bd77d
bloq dtypes
mpharrigan Mar 5, 2025
a10a331
bloq dtypes
mpharrigan Mar 5, 2025
1c758d4
bloq dtypes
mpharrigan Mar 5, 2025
f57cdcc
drawing
mpharrigan Mar 5, 2025
d0773f1
drawing
mpharrigan Mar 5, 2025
ba39913
annotations
mpharrigan Mar 5, 2025
300b8ba
bits
mpharrigan Mar 5, 2025
bad1f36
bits
mpharrigan Mar 5, 2025
d668e62
init
mpharrigan Mar 5, 2025
e699973
init
mpharrigan Mar 5, 2025
85907da
controlled docs
mpharrigan Mar 5, 2025
96f2cbb
lint
mpharrigan Mar 5, 2025
919bdd9
fixes
mpharrigan Mar 5, 2025
b05d94d
Merge remote-tracking branch 'origin/main' into 2025-03/classical-1
mpharrigan Mar 5, 2025
40ef237
format
mpharrigan Mar 5, 2025
22e3184
autogenerate notebooks
mpharrigan Mar 5, 2025
aabe689
Merge remote-tracking branch 'origin/main' into 2025-03/classical-1
mpharrigan Mar 5, 2025
70bac9a
add notebook to toc
mpharrigan Mar 5, 2025
6e93114
fix musical score serialization
mpharrigan Mar 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/bloq_infra.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ types (``Register``), and algorithms (``CompositeBloq``).

_infra/Bloqs-Tutorial.ipynb
Protocols.ipynb
DataTypes.ipynb
simulation/classical_sim.ipynb
simulation/tensor.ipynb
resource_counting/call_graph.ipynb
Expand Down
14 changes: 8 additions & 6 deletions qualtran/Controlled.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
"source": [
"# Controlled\n",
"\n",
"The controlled protocol lets you request a 'controlled' version of a bloq.\n",
"The controlled protocol lets you request a *controlled* version of a bloq: a bloq that is only active based on the status of another quantum bit or register.\n",
"\n",
"A key feature of any program is the ability to do branching. That is, choose what operations to do based on input. The primitive branching feature in quantum algorithms is the idea of a controlled bloq.\n",
"A key feature of any program is the ability to do branching. That is, choosing what operations to do based on input. Controlled bloqs give the quantum programmer access to branching.\n",
"\n",
"In its simplest form, a control bit is specified as an (additional) input to a bloq and the bloq is active when the control input is in the $|1\\rangle$ state. Otherwise, the bloq's operation is not performed (said another way: the Identity bloq is performed). The control input can be in superposition. "
"In its simplest form, a control bit is specified as an (additional) input to a bloq and the bloq is active when the control input is in the $|1\\rangle$ state. Otherwise, the bloq's operation is not performed. Equivalently, the control bit toggles between the bloq and the `Identity` operation.\n",
"\n",
"Control bits can be in quantum superposition. The result is a wavefunction containaing simultaneous branches where the controlled operation was performed and not performed. "
]
},
{
Expand All @@ -37,11 +39,11 @@
"source": [
"## Interface\n",
"\n",
"The method for accessing the controlled version of a bloq is calling `Bloq.controlled(ctrl_spec)`. `ctrl_spec` is an instance of `CtrlSpec` which specifies how to control the bloq. \n",
"The method for accessing the controlled version of a bloq is calling `Bloq.controlled(ctrl_spec)`. `ctrl_spec` is an instance of `qualtran.CtrlSpec` which specifies the condition under which the bloq is active. \n",
"\n",
"`CtrlSpec` supports additional control specifications:\n",
"`CtrlSpec` supports $|1\\rangle$-state individual control bits, as well as:\n",
" 1. 'negative' controls where the bloq is active if the input is |0>.\n",
" 2. integer-equality controls where a `bitsize`-sized input must match an integer control value.\n",
" 2. integer-equality controls where an input of an arbitrary `QCDType` must match a compile-time control value.\n",
" 3. ndarrays of control values, where the bloq is active if **all** inputs are active.\n",
" 4. Multiple control registers, control values for each of which can be specified\n",
" using 1-3 above.\n",
Expand Down
353 changes: 353 additions & 0 deletions qualtran/DataTypes.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "cc3bc886-66ec-4a5e-8042-ed84c98ae210",
"metadata": {},
"source": [
"# Data Types\n",
"\n",
"## Introduction to quantum data: the qubit\n",
"Qualtran lets you write quantum programs that operate on quantum data. The smallest unit of quantum data is the qubit (\"quantum bit\"). A quantum bit can be in the familiar `0` or `1` states (called computational basis states) or any combination of them, like $|+\\rangle = (|0\\rangle + |1\\rangle)/\\sqrt{2}$. Allocation-like bloqs can allocate a qubit in a particular state. Below, we create a simple program that allocates one quantum vairalbe in the `0` state and one in the `+` state."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "780e26eb-b50a-46fd-aec9-a74d68201c2f",
"metadata": {},
"outputs": [],
"source": [
"from qualtran import BloqBuilder\n",
"from qualtran.bloqs.basic_gates import ZeroState, PlusState\n",
"\n",
"bb = BloqBuilder()\n",
"zero_q = bb.add(ZeroState())\n",
"plus_q = bb.add(PlusState())\n",
"cbloq = bb.finalize(q0=zero_q, q1=plus_q)\n",
"\n",
"from qualtran.drawing import show_bloq\n",
"show_bloq(cbloq)"
]
},
{
"cell_type": "markdown",
"id": "a3ead17f-c2b1-4d7b-8467-deb4691ce4a2",
"metadata": {},
"source": [
"## Quantum variables\n",
"\n",
"When we use `BloqBuilder` to `add` these allocation operations to our program, we are given a handle to the resulting quantum data. These handles are *quantum variables*, which can be provided as inputs to subsequent operations. Quantum variables follow *linear logic*: that is, each quantum variable must be used exactly once. You cannot use the same variable twice (this would violate the *no-cloning theorem*), and you cannot leave a variable unused (this would violate the corresponding *no-deleting theorem*). In the above program, we use the `finalize` method to account for our unused quantum variables—it is presumed that the programmer will handle these piece of data with subsequent bloqs."
]
},
{
"cell_type": "markdown",
"id": "f51e6b35-64a9-442c-b677-b7053791e3f0",
"metadata": {},
"source": [
"## Bloq signatures and `QBit()`\n",
"\n",
"We write quantum programs by composing subroutines encoded as Qualtran *bloqs*. A bloq class inherits from the `qualtran.Bloq` interface, which only has one required property: `signature`. A bloq's signature declares the names and types of quantum data the bloq takes as input and output. You might think of a bloq with nothing other than its signature analogous to declaring (but not defining) a function in a C/C++ header file. \n",
"\n",
"The `Bloq.signature` property method must return a `qualtran.Signature` object, which is itself a list of `Register` objects. Each register is the name and data type of an input/output variable. In quantum computing (as a consequence of the no-deleting theorem), we often have a pattern we term *thru registers* where quantum data is used as input and returned with the same name and data type, so registers default to simultaneous input and output arguments.\n",
"\n",
"Below, we construct a signature consisting of two input-output arguments named 'arg1' and 'arg2'; and we declare that each must be a qubit using the data type specification `qualtran.QBit()`. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9da1ffe1-b214-43aa-82f6-098dffec10eb",
"metadata": {},
"outputs": [],
"source": [
"from qualtran import Signature, Register, QBit\n",
"\n",
"signature = Signature([\n",
" Register('arg1', QBit()),\n",
" Register('arg2', QBit()),\n",
"])\n",
"print(signature.n_qubits())"
]
},
{
"cell_type": "markdown",
"id": "e3f1675d-57af-4712-b694-62cca89440c3",
"metadata": {},
"source": [
"## Quantum data types\n",
"\n",
"Completely analogously to classical computation, collections of individual qubits can be used to encode a variety of data types. For example, `qualtran.QUInt(32)` represents a 32-bit unsigned, quantum integer. These data type objects are used in the definition of signatures to provide type checking for your quantum programs. \n",
"\n",
"In Qualtran, quantum variables of arbitrary type are first-class objects. You can represent a program operating on e.g. 2048-bit registers without having a unique index or an individual Python object for each underlying qubit (like in many NISQ frameworks like Cirq). \n",
"\n",
"We support statically-sized data; and do not support sum or union types. Built-in data types like `QInt`, `QFxp` (fixed-point reals), `QIntOnesComp` (signed integers using ones' complement), and others are available in the top-level `qualtran` namespace. Custom data types can be implemented by inheriting from `QDType`.\n",
"\n",
"Below, we construct a signature consisting of two input-output arguments named 'x' and 'y'; and we declare that each is a 32-bit quantum unsigned integer."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "578c71e2-3b6b-43d1-bb91-88c94ae4f70e",
"metadata": {},
"outputs": [],
"source": [
"from qualtran import QUInt\n",
"\n",
"signature = Signature([\n",
" Register('x', QUInt(32)),\n",
" Register('y', QUInt(32)),\n",
"])\n",
"print(signature.n_qubits())"
]
},
{
"cell_type": "markdown",
"id": "3fc9dd5c-7e05-4b4c-a423-0c58399bc46e",
"metadata": {},
"source": [
"### Quantum data types as bloq parameters\n",
"\n",
"By using compile-time classical attributes of bloqs, we can support *generic programming* where a single bloq class can be used with a variety of quantum data types. Many of the arithmetic operations take the data type as a compile-time classical attribute.\n",
"\n",
"Below, we show that the `Negate` operation can handle a `QUInt` of arbitrary size; and indeed you can read the documentation to figure out that it also supports signed and other types of integers. Note: we can represent programs on large bitsize variables without any performance overhead."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fb259875-b438-4ee8-882c-092bd9711682",
"metadata": {},
"outputs": [],
"source": [
"from qualtran.bloqs.arithmetic import Negate\n",
"\n",
"negate = Negate(dtype=QUInt(2048))\n",
"show_bloq(negate.decompose_bloq())"
]
},
{
"cell_type": "markdown",
"id": "5d9426cd-087a-4d7c-a5cc-8ad95ed4257e",
"metadata": {},
"source": [
"## Splitting\n",
"\n",
"It is great if you can express your algorithm as manipulations of quantum ints, reals, or other *high-level* data types. But, we anticipate that the gateset of a quantum computer will consist of 1-, 2- and 3-qubit operations. At some point, we need to define our operations in terms of their action on individual bits. We can use `Split` and other *bookkeeping* operations to carefully cast the data type of a quantum variable so we can write decompositions down to the architecture-supported gateset.\n",
"\n",
"As an example, we'll consider the `BitwiseNot` used in the previous snippet. We'll take a quantum unsigned integer and just do a *not* (in quantum computing: `XGate`) on each bit."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9b5fdb88-9a4d-4ccc-801f-76769df67214",
"metadata": {},
"outputs": [],
"source": [
"from qualtran.bloqs.basic_gates import XGate\n",
"\n",
"dtype = QUInt(3) # 3-bit integer for demonstration purposes\n",
"\n",
"# We'll use BloqBuilder directly. In the standard library this would\n",
"# be the `build_composite_bloq` method on the `BitwiseNot` bloq class\n",
"bb = BloqBuilder()\n",
"x = bb.add_register_from_dtype('x', dtype)\n",
"\n",
"# First, we split up the bits using the `.split` helper method on BloqBuilder.\n",
"# It returns a numpy array of quantum variables.\n",
"x_bits = bb.split(x)\n",
"\n",
"# Then, we apply the XGate to each bit. Remember that each quantum variable\n",
"# must be used exactly once, so the input bits are consumed by the XGate and\n",
"# we get a new variable back that we store in our `x_bits` array.\n",
"for i in range(len(x_bits)):\n",
" x_bits[i] = bb.add(XGate(), q=x_bits[i])\n",
"\n",
"# For users calling this bloq, we want the fact that we split up all the bits\n",
"# to be an \"implementation detail\"; so we re-join our output bits back into\n",
"# a 3-bit unsigned integer\n",
"x = bb.join(x_bits, dtype=dtype)\n",
"\n",
"# Finish up and draw a diagram\n",
"cbloq = bb.finalize(x=x)\n",
"show_bloq(cbloq)"
]
},
{
"cell_type": "markdown",
"id": "a0c2d228-8f1e-4d7b-938e-4b59430aa27d",
"metadata": {},
"source": [
"## Endianness\n",
"\n",
"The Qualtran data types use a big-endian bit convention. The most significant bit is at index 0."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bbe875d9-7a15-488c-ad19-02cd0487765a",
"metadata": {},
"outputs": [],
"source": [
"QUInt(8).to_bits(x=0x30)"
]
},
{
"cell_type": "markdown",
"id": "08a9d5cd-ef4e-4695-b992-e8c7f777248a",
"metadata": {},
"source": [
"## Casting and QAny\n",
"\n",
"In general, we can cast from one data type to another using the `Cast` bloq. The system will validate that the number of bits between the two data types match, but this operation must still be done with some care.\n",
"\n",
"When type checking is irrelevant, you can use the `QAny(n)` type to represent an arbitrary collection of qubits that doesn't necessarily encode anything. \n",
"\n",
"Below, we allocate individual qubits and then join them into a new quantum variable. Since there's no type information, the resulting variable will have the `QAny(3)` type. We can declare that this should encode a `QUInt(3)` by using a `Cast`. (There's also a `dtype` argument to `bb.join`, which you would probably use in practice)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ecf4057-9ea9-4854-8b66-d5b041a8cc49",
"metadata": {},
"outputs": [],
"source": [
"bb = BloqBuilder()\n",
"\n",
"# Make three |0> qubits\n",
"qs = [bb.add(ZeroState()) for _ in range(3)]\n",
"\n",
"# Join them into one quantum variable. Since\n",
"# we don't specify a type, `x` is `QAny(3)`. \n",
"x = bb.join(qs)\n",
"\n",
"# Maybe we're trying to allocate an unsigned integer.\n",
"from qualtran.bloqs.bookkeeping import Cast\n",
"from qualtran import QAny\n",
"x = bb.add(Cast(inp_dtype=QAny(3), out_dtype=QUInt(3)), reg=x)\n",
"\n",
"cbloq = bb.finalize(x=x)\n",
"show_bloq(cbloq)"
]
},
{
"cell_type": "markdown",
"id": "e6987d5b-1465-47dd-894e-f966cadb868f",
"metadata": {},
"source": [
"## Type checking\n",
"\n",
"When wiring up bloqs, the data types must be compatible. \n",
"\n",
" - When the two data types are the same, they are always compatible\n",
" - All single-qubit data types are compatible\n",
"\n",
"The consistency checking functions accept a severity parameter. If it is set to `STRICT`, then nothing outside of the above two rules are compatible. If it is set to `LOOSE` (the default), the following pairs are also compatible:\n",
"\n",
" - `QAny` is compatible with any other data type if its number of qubits match\n",
" - Integer types are mutually compatible if the number of qubits match\n",
" - An unsigned `QFxp` fixed-point with only an integer part is compatible with integer types."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9f4e4a13-e112-4b7e-95e1-6fee0168d989",
"metadata": {},
"outputs": [],
"source": [
"from qualtran import QDTypeCheckingSeverity, check_dtypes_consistent\n",
"\n",
"print('same ', check_dtypes_consistent(QUInt(3), QUInt(3)))\n",
"print('1bit ', check_dtypes_consistent(QBit(), QAny(1)))\n",
"print('qany ',\n",
" check_dtypes_consistent(QAny(3), QUInt(3)),\n",
" check_dtypes_consistent(QAny(3), QUInt(3), QDTypeCheckingSeverity.STRICT)\n",
")\n",
"from qualtran import QInt\n",
"print('qint ', \n",
" check_dtypes_consistent(QUInt(3), QInt(3)),\n",
" check_dtypes_consistent(QUInt(3), QInt(3), QDTypeCheckingSeverity.STRICT)\n",
")\n",
"print('diff ', check_dtypes_consistent(QAny(3), QAny(4)))"
]
},
{
"cell_type": "markdown",
"id": "a27b83f4-44e4-4b53-a878-ecce6a100675",
"metadata": {},
"source": [
"## `QDType`, `CDType`, and `QCDType`\n",
"\n",
"Quantum variables are essential when authoring quantum programs, but we live in a classical world. Measuring a qubit yields a classical bit, and a program can do classical branching (choosing which quantum operations to execute based on a classical bit). Each data type we've seen so far is a quantum data type and inherits from `QDType`. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06c5c158-555b-48cd-b612-e00d03b82f70",
"metadata": {},
"outputs": [],
"source": [
"from qualtran import QDType\n",
"\n",
"print(\"QBit() is QDType:\", isinstance(QBit(), QDType), \"; num_qubits =\", QBit().num_qubits)\n",
"print(\"QUInt(4) is QDType:\", isinstance(QUInt(4), QDType), \"; num_qubits =\", QUInt(4).num_qubits)"
]
},
{
"cell_type": "markdown",
"id": "c434a68e-b691-4a35-bc81-49b7159728f8",
"metadata": {},
"source": [
"There is a more general base class: `QCDType` that includes both quantum and classical data types. Classical data types inherit from `CDType`"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c983e4cb-ac84-497b-883f-3a71abf6878f",
"metadata": {},
"outputs": [],
"source": [
"from qualtran import QCDType, QDType, CDType, CBit\n",
"\n",
"dtypes = [QBit(), QUInt(4), CBit()]\n",
"\n",
"print(f\"{'dtype':10} {'QCDType?':9s} {'QDType?':9s} {'CDType?':9s}\"\n",
" f\"{'bits':>6s} {'qubits':>6s} {'cbits':>6s}\"\n",
" )\n",
"print(\"-\"*60)\n",
"for dtype in dtypes:\n",
" print(f\"{dtype!s:10} {isinstance(dtype, QCDType)!s:9} {isinstance(dtype, QDType)!s:9} {isinstance(dtype, CDType)!s:9}\"\n",
" f\"{dtype.num_bits:6d} {dtype.num_qubits:6d} {dtype.num_cbits:6d}\"\n",
" )"
]
}
],
"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
}
Loading