Skip to content

Commit 511b776

Browse files
authored
[open-systems] QCDType, CDType, QDType (#1584)
* notey * ctrl * qdtype * ctrl spec * controlled * controlled * data types * on classical vals * dtypes * registers * registers * bloq ctrl spec * cast * bloq dtypes * controlled * bloq dtypes * ctrl * bloq dtypes * bloq dtypes * bloq dtypes * bloq dtypes * bloq dtypes * bloq dtypes * drawing * drawing * annotations * bits * bits * init * init * controlled docs * lint * fixes * format * autogenerate notebooks * add notebook to toc * fix musical score serialization * Signature.n_xbits
1 parent 4a4d7af commit 511b776

35 files changed

Lines changed: 1043 additions & 267 deletions

docs/bloq_infra.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ types (``Register``), and algorithms (``CompositeBloq``).
1313

1414
_infra/Bloqs-Tutorial.ipynb
1515
Protocols.ipynb
16+
DataTypes.ipynb
1617
simulation/classical_sim.ipynb
1718
simulation/tensor.ipynb
1819
resource_counting/call_graph.ipynb

qualtran/Controlled.ipynb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77
"source": [
88
"# Controlled\n",
99
"\n",
10-
"The controlled protocol lets you request a 'controlled' version of a bloq.\n",
10+
"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",
1111
"\n",
12-
"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",
12+
"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",
1313
"\n",
14-
"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. "
14+
"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",
15+
"\n",
16+
"Control bits can be in quantum superposition. The result is a wavefunction containaing simultaneous branches where the controlled operation was performed and not performed. "
1517
]
1618
},
1719
{
@@ -37,11 +39,11 @@
3739
"source": [
3840
"## Interface\n",
3941
"\n",
40-
"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",
42+
"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",
4143
"\n",
42-
"`CtrlSpec` supports additional control specifications:\n",
44+
"`CtrlSpec` supports $|1\\rangle$-state individual control bits, as well as:\n",
4345
" 1. 'negative' controls where the bloq is active if the input is |0>.\n",
44-
" 2. integer-equality controls where a `bitsize`-sized input must match an integer control value.\n",
46+
" 2. integer-equality controls where an input of an arbitrary `QCDType` must match a compile-time control value.\n",
4547
" 3. ndarrays of control values, where the bloq is active if **all** inputs are active.\n",
4648
" 4. Multiple control registers, control values for each of which can be specified\n",
4749
" using 1-3 above.\n",

qualtran/DataTypes.ipynb

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "cc3bc886-66ec-4a5e-8042-ed84c98ae210",
6+
"metadata": {},
7+
"source": [
8+
"# Data Types\n",
9+
"\n",
10+
"## Introduction to quantum data: the qubit\n",
11+
"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."
12+
]
13+
},
14+
{
15+
"cell_type": "code",
16+
"execution_count": null,
17+
"id": "780e26eb-b50a-46fd-aec9-a74d68201c2f",
18+
"metadata": {},
19+
"outputs": [],
20+
"source": [
21+
"from qualtran import BloqBuilder\n",
22+
"from qualtran.bloqs.basic_gates import ZeroState, PlusState\n",
23+
"\n",
24+
"bb = BloqBuilder()\n",
25+
"zero_q = bb.add(ZeroState())\n",
26+
"plus_q = bb.add(PlusState())\n",
27+
"cbloq = bb.finalize(q0=zero_q, q1=plus_q)\n",
28+
"\n",
29+
"from qualtran.drawing import show_bloq\n",
30+
"show_bloq(cbloq)"
31+
]
32+
},
33+
{
34+
"cell_type": "markdown",
35+
"id": "a3ead17f-c2b1-4d7b-8467-deb4691ce4a2",
36+
"metadata": {},
37+
"source": [
38+
"## Quantum variables\n",
39+
"\n",
40+
"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."
41+
]
42+
},
43+
{
44+
"cell_type": "markdown",
45+
"id": "f51e6b35-64a9-442c-b677-b7053791e3f0",
46+
"metadata": {},
47+
"source": [
48+
"## Bloq signatures and `QBit()`\n",
49+
"\n",
50+
"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",
51+
"\n",
52+
"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",
53+
"\n",
54+
"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()`. "
55+
]
56+
},
57+
{
58+
"cell_type": "code",
59+
"execution_count": null,
60+
"id": "9da1ffe1-b214-43aa-82f6-098dffec10eb",
61+
"metadata": {},
62+
"outputs": [],
63+
"source": [
64+
"from qualtran import Signature, Register, QBit\n",
65+
"\n",
66+
"signature = Signature([\n",
67+
" Register('arg1', QBit()),\n",
68+
" Register('arg2', QBit()),\n",
69+
"])\n",
70+
"print(signature.n_qubits())"
71+
]
72+
},
73+
{
74+
"cell_type": "markdown",
75+
"id": "e3f1675d-57af-4712-b694-62cca89440c3",
76+
"metadata": {},
77+
"source": [
78+
"## Quantum data types\n",
79+
"\n",
80+
"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",
81+
"\n",
82+
"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",
83+
"\n",
84+
"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",
85+
"\n",
86+
"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."
87+
]
88+
},
89+
{
90+
"cell_type": "code",
91+
"execution_count": null,
92+
"id": "578c71e2-3b6b-43d1-bb91-88c94ae4f70e",
93+
"metadata": {},
94+
"outputs": [],
95+
"source": [
96+
"from qualtran import QUInt\n",
97+
"\n",
98+
"signature = Signature([\n",
99+
" Register('x', QUInt(32)),\n",
100+
" Register('y', QUInt(32)),\n",
101+
"])\n",
102+
"print(signature.n_qubits())"
103+
]
104+
},
105+
{
106+
"cell_type": "markdown",
107+
"id": "3fc9dd5c-7e05-4b4c-a423-0c58399bc46e",
108+
"metadata": {},
109+
"source": [
110+
"### Quantum data types as bloq parameters\n",
111+
"\n",
112+
"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",
113+
"\n",
114+
"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."
115+
]
116+
},
117+
{
118+
"cell_type": "code",
119+
"execution_count": null,
120+
"id": "fb259875-b438-4ee8-882c-092bd9711682",
121+
"metadata": {},
122+
"outputs": [],
123+
"source": [
124+
"from qualtran.bloqs.arithmetic import Negate\n",
125+
"\n",
126+
"negate = Negate(dtype=QUInt(2048))\n",
127+
"show_bloq(negate.decompose_bloq())"
128+
]
129+
},
130+
{
131+
"cell_type": "markdown",
132+
"id": "5d9426cd-087a-4d7c-a5cc-8ad95ed4257e",
133+
"metadata": {},
134+
"source": [
135+
"## Splitting\n",
136+
"\n",
137+
"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",
138+
"\n",
139+
"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."
140+
]
141+
},
142+
{
143+
"cell_type": "code",
144+
"execution_count": null,
145+
"id": "9b5fdb88-9a4d-4ccc-801f-76769df67214",
146+
"metadata": {},
147+
"outputs": [],
148+
"source": [
149+
"from qualtran.bloqs.basic_gates import XGate\n",
150+
"\n",
151+
"dtype = QUInt(3) # 3-bit integer for demonstration purposes\n",
152+
"\n",
153+
"# We'll use BloqBuilder directly. In the standard library this would\n",
154+
"# be the `build_composite_bloq` method on the `BitwiseNot` bloq class\n",
155+
"bb = BloqBuilder()\n",
156+
"x = bb.add_register_from_dtype('x', dtype)\n",
157+
"\n",
158+
"# First, we split up the bits using the `.split` helper method on BloqBuilder.\n",
159+
"# It returns a numpy array of quantum variables.\n",
160+
"x_bits = bb.split(x)\n",
161+
"\n",
162+
"# Then, we apply the XGate to each bit. Remember that each quantum variable\n",
163+
"# must be used exactly once, so the input bits are consumed by the XGate and\n",
164+
"# we get a new variable back that we store in our `x_bits` array.\n",
165+
"for i in range(len(x_bits)):\n",
166+
" x_bits[i] = bb.add(XGate(), q=x_bits[i])\n",
167+
"\n",
168+
"# For users calling this bloq, we want the fact that we split up all the bits\n",
169+
"# to be an \"implementation detail\"; so we re-join our output bits back into\n",
170+
"# a 3-bit unsigned integer\n",
171+
"x = bb.join(x_bits, dtype=dtype)\n",
172+
"\n",
173+
"# Finish up and draw a diagram\n",
174+
"cbloq = bb.finalize(x=x)\n",
175+
"show_bloq(cbloq)"
176+
]
177+
},
178+
{
179+
"cell_type": "markdown",
180+
"id": "a0c2d228-8f1e-4d7b-938e-4b59430aa27d",
181+
"metadata": {},
182+
"source": [
183+
"## Endianness\n",
184+
"\n",
185+
"The Qualtran data types use a big-endian bit convention. The most significant bit is at index 0."
186+
]
187+
},
188+
{
189+
"cell_type": "code",
190+
"execution_count": null,
191+
"id": "bbe875d9-7a15-488c-ad19-02cd0487765a",
192+
"metadata": {},
193+
"outputs": [],
194+
"source": [
195+
"QUInt(8).to_bits(x=0x30)"
196+
]
197+
},
198+
{
199+
"cell_type": "markdown",
200+
"id": "08a9d5cd-ef4e-4695-b992-e8c7f777248a",
201+
"metadata": {},
202+
"source": [
203+
"## Casting and QAny\n",
204+
"\n",
205+
"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",
206+
"\n",
207+
"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",
208+
"\n",
209+
"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)."
210+
]
211+
},
212+
{
213+
"cell_type": "code",
214+
"execution_count": null,
215+
"id": "3ecf4057-9ea9-4854-8b66-d5b041a8cc49",
216+
"metadata": {},
217+
"outputs": [],
218+
"source": [
219+
"bb = BloqBuilder()\n",
220+
"\n",
221+
"# Make three |0> qubits\n",
222+
"qs = [bb.add(ZeroState()) for _ in range(3)]\n",
223+
"\n",
224+
"# Join them into one quantum variable. Since\n",
225+
"# we don't specify a type, `x` is `QAny(3)`. \n",
226+
"x = bb.join(qs)\n",
227+
"\n",
228+
"# Maybe we're trying to allocate an unsigned integer.\n",
229+
"from qualtran.bloqs.bookkeeping import Cast\n",
230+
"from qualtran import QAny\n",
231+
"x = bb.add(Cast(inp_dtype=QAny(3), out_dtype=QUInt(3)), reg=x)\n",
232+
"\n",
233+
"cbloq = bb.finalize(x=x)\n",
234+
"show_bloq(cbloq)"
235+
]
236+
},
237+
{
238+
"cell_type": "markdown",
239+
"id": "e6987d5b-1465-47dd-894e-f966cadb868f",
240+
"metadata": {},
241+
"source": [
242+
"## Type checking\n",
243+
"\n",
244+
"When wiring up bloqs, the data types must be compatible. \n",
245+
"\n",
246+
" - When the two data types are the same, they are always compatible\n",
247+
" - All single-qubit data types are compatible\n",
248+
"\n",
249+
"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",
250+
"\n",
251+
" - `QAny` is compatible with any other data type if its number of qubits match\n",
252+
" - Integer types are mutually compatible if the number of qubits match\n",
253+
" - An unsigned `QFxp` fixed-point with only an integer part is compatible with integer types."
254+
]
255+
},
256+
{
257+
"cell_type": "code",
258+
"execution_count": null,
259+
"id": "9f4e4a13-e112-4b7e-95e1-6fee0168d989",
260+
"metadata": {},
261+
"outputs": [],
262+
"source": [
263+
"from qualtran import QDTypeCheckingSeverity, check_dtypes_consistent\n",
264+
"\n",
265+
"print('same ', check_dtypes_consistent(QUInt(3), QUInt(3)))\n",
266+
"print('1bit ', check_dtypes_consistent(QBit(), QAny(1)))\n",
267+
"print('qany ',\n",
268+
" check_dtypes_consistent(QAny(3), QUInt(3)),\n",
269+
" check_dtypes_consistent(QAny(3), QUInt(3), QDTypeCheckingSeverity.STRICT)\n",
270+
")\n",
271+
"from qualtran import QInt\n",
272+
"print('qint ', \n",
273+
" check_dtypes_consistent(QUInt(3), QInt(3)),\n",
274+
" check_dtypes_consistent(QUInt(3), QInt(3), QDTypeCheckingSeverity.STRICT)\n",
275+
")\n",
276+
"print('diff ', check_dtypes_consistent(QAny(3), QAny(4)))"
277+
]
278+
},
279+
{
280+
"cell_type": "markdown",
281+
"id": "a27b83f4-44e4-4b53-a878-ecce6a100675",
282+
"metadata": {},
283+
"source": [
284+
"## `QDType`, `CDType`, and `QCDType`\n",
285+
"\n",
286+
"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`. "
287+
]
288+
},
289+
{
290+
"cell_type": "code",
291+
"execution_count": null,
292+
"id": "06c5c158-555b-48cd-b612-e00d03b82f70",
293+
"metadata": {},
294+
"outputs": [],
295+
"source": [
296+
"from qualtran import QDType\n",
297+
"\n",
298+
"print(\"QBit() is QDType:\", isinstance(QBit(), QDType), \"; num_qubits =\", QBit().num_qubits)\n",
299+
"print(\"QUInt(4) is QDType:\", isinstance(QUInt(4), QDType), \"; num_qubits =\", QUInt(4).num_qubits)"
300+
]
301+
},
302+
{
303+
"cell_type": "markdown",
304+
"id": "c434a68e-b691-4a35-bc81-49b7159728f8",
305+
"metadata": {},
306+
"source": [
307+
"There is a more general base class: `QCDType` that includes both quantum and classical data types. Classical data types inherit from `CDType`"
308+
]
309+
},
310+
{
311+
"cell_type": "code",
312+
"execution_count": null,
313+
"id": "c983e4cb-ac84-497b-883f-3a71abf6878f",
314+
"metadata": {},
315+
"outputs": [],
316+
"source": [
317+
"from qualtran import QCDType, QDType, CDType, CBit\n",
318+
"\n",
319+
"dtypes = [QBit(), QUInt(4), CBit()]\n",
320+
"\n",
321+
"print(f\"{'dtype':10} {'QCDType?':9s} {'QDType?':9s} {'CDType?':9s}\"\n",
322+
" f\"{'bits':>6s} {'qubits':>6s} {'cbits':>6s}\"\n",
323+
" )\n",
324+
"print(\"-\"*60)\n",
325+
"for dtype in dtypes:\n",
326+
" print(f\"{dtype!s:10} {isinstance(dtype, QCDType)!s:9} {isinstance(dtype, QDType)!s:9} {isinstance(dtype, CDType)!s:9}\"\n",
327+
" f\"{dtype.num_bits:6d} {dtype.num_qubits:6d} {dtype.num_cbits:6d}\"\n",
328+
" )"
329+
]
330+
}
331+
],
332+
"metadata": {
333+
"kernelspec": {
334+
"display_name": "Python 3 (ipykernel)",
335+
"language": "python",
336+
"name": "python3"
337+
},
338+
"language_info": {
339+
"codemirror_mode": {
340+
"name": "ipython",
341+
"version": 3
342+
},
343+
"file_extension": ".py",
344+
"mimetype": "text/x-python",
345+
"name": "python",
346+
"nbconvert_exporter": "python",
347+
"pygments_lexer": "ipython3",
348+
"version": "3.11.8"
349+
}
350+
},
351+
"nbformat": 4,
352+
"nbformat_minor": 5
353+
}

0 commit comments

Comments
 (0)