diff --git a/docs/devices.ipynb b/docs/devices.ipynb new file mode 100644 index 00000000000..ca4828290c7 --- /dev/null +++ b/docs/devices.ipynb @@ -0,0 +1,478 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "DkA0Fobtb9dM" + }, + "source": [ + "##### Copyright 2022 The Cirq Developers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "cellView": "form", + "id": "tUshu7YfcAAW" + }, + "outputs": [], + "source": [ + "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", + "# you may not use this file except in compliance with the License.\n", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", + "# See the License for the specific language governing permissions and\n", + "# limitations under the License." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "igOQCrBOcF5d" + }, + "source": [ + "# Devices" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LHRAvc9TcHOH" + }, + "source": [ + "\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + " View on QuantumAI\n", + " \n", + " Run in Google Colab\n", + " \n", + " View source on GitHub\n", + " \n", + " Download notebook\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bd9529db1c0b" + }, + "outputs": [], + "source": [ + "try:\n", + " import cirq\n", + "except ImportError:\n", + " print(\"installing cirq...\")\n", + " !pip install --quiet cirq\n", + " print(\"installed cirq.\")\n", + " import cirq " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "x6s7QnYKAdo7" + }, + "source": [ + "## 1. Validation basics\n", + "\n", + "When you are looking to run an algorithm on a real quantum computer (not a simulated one), there are often many additional constraints placed on the circuits you would like to run. Qubit connectivity, algorithm layout and types of gates used in the circuit all become much more important. Cirq uses the abtract class `Device` to represent constraints of an actual quantum processor, an example implementation of a device can be seen in the `cirq_google.Sycamore` class:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "BFcKF8Xp-A2S" + }, + "outputs": [], + "source": [ + "import cirq_google\n", + "import networkx as nx\n", + "my_device = cirq_google.Sycamore\n", + "my_device" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "bN7O0XCLAH7G" + }, + "source": [ + "All devices are capable of validing moments, operations and circuits with the `validate_***` method to verify if they would work on that device or not. You can check if the following operations work on the Sycamore device:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "53z2ucqZAFz7" + }, + "outputs": [], + "source": [ + "op1 = cirq.X(cirq.GridQubit(7 ,7))\n", + "\n", + "try:\n", + " my_device.validate_operation(op1)\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jkgV8lhEB7L5" + }, + "source": [ + "Above you used a qubit that wasn't on the device and was invalid. Most validate operations also take into account things like supported gates and connectivity as well:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VM3kDY6QCRPY" + }, + "outputs": [], + "source": [ + "q1, q2, q3 = cirq.GridQubit(7, 4), cirq.GridQubit(7, 5), cirq.GridQubit(7, 6)\n", + "op1 = cirq.H(q1)\n", + "op2 = cirq_google.SYC(q1, q3)\n", + "\n", + "try:\n", + " my_device.validate_operation(op1)\n", + "except Exception as e:\n", + " print(e)\n", + "\n", + "try:\n", + " my_device.validate_operation(op2)\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "EcTgQg5xCxzG" + }, + "source": [ + "These validation operations can also be used with full circuits:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "EiolyMKLC262" + }, + "outputs": [], + "source": [ + "my_circuit = cirq.Circuit(\n", + " cirq.PhasedXPowGate(phase_exponent=0.3)(q1),\n", + " cirq.PhasedXPowGate(phase_exponent=0.3)(q2),\n", + " cirq_google.SYC(q1, q2),\n", + " cirq_google.SYC(q2, q3),\n", + ")\n", + "my_device.validate_circuit(my_circuit)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-sNjRTCKDb2D" + }, + "source": [ + "`my_circuit` satisfies all the device constraints and could be run on a Sycamore device." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BrxB0eCUDvlI" + }, + "source": [ + "## 2. Metadata features\n", + "\n", + "Some devices will also expose additional information via the `metadata` property. Metadata is usually exposed via the an instance (or subclass instance) of the `cirq.DeviceMetadata` class. You can look at the metadata information of the Sycamore device with:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "clsvdcXzEOIV" + }, + "outputs": [], + "source": [ + "metadata = my_device.metadata\n", + "metadata" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "glMmeLevElt-" + }, + "source": [ + "The Sycamore device is a 2d grid device that exposes a `cirq.GridDeviceMetadata` with a uniform set of gates across all the qubits as well as a planar nearest neighbor connectivity graph. You can explore the properties below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9Zm_1IsFEiHT" + }, + "outputs": [], + "source": [ + "type(metadata)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "LvaYZjANb7_n" + }, + "outputs": [], + "source": [ + "issubclass(type(metadata), cirq.DeviceMetadata)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "358DyXIsGdPE" + }, + "source": [ + "Some properties that are common to all devices and belong to the `cirq.DeviceMetadata` class include the set of qubits on the device (`qubit_set`) and the connectivity graph of those qubits (`nx_graph`)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "BeW0hnFPGkyB" + }, + "outputs": [], + "source": [ + "# Properties common to all `cirq.DeviceMetadata`\n", + "qubit_set = metadata.qubit_set\n", + "nx_graph = metadata.nx_graph\n", + "print(qubit_set)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "oM6dpucFGoCK" + }, + "source": [ + "Some properties are unique to the subclass. For `cirq.GridDeviceMetadata` you can explore the `gateset` (supported gates across all qubits) and the gate_durations (Dictionary of times that it takes to execute these gates on the device) fields:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "JbwrPD_AETiX" + }, + "outputs": [], + "source": [ + "# Properties unique to `cirq.GridDeviceMetadata`\n", + "gateset = metadata.gateset\n", + "durations = metadata.gate_durations\n", + "\n", + "print(gateset)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fo6ZTYN4HBVL" + }, + "source": [ + "These metadata features can be useful when designing/building algorithms around certain device information in order to tailor them for that device." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RvC4xz_OHT3s" + }, + "source": [ + "## 3. The `cirq.Device` interface\n", + "\n", + "For advanced users (such as vendors) it is also possible to implement your own Device with its own unique constraints and metadata information. Below you can implement our own fictituous device:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "cA7AdVWJCpRr" + }, + "outputs": [], + "source": [ + "class MyDevice(cirq.Device):\n", + " \"\"\"Five qubits on a line, supporting X/Y/Z and CZ between neighbors.\"\"\"\n", + "\n", + " def __init__(self):\n", + " self._qubits = set(cirq.LineQubit.range(5))\n", + " self._supported_gates = cirq.Gateset(\n", + " cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZPowGate\n", + " )\n", + "\n", + " def validate_operation(self, operation):\n", + " \"\"\"Check to make sure `operation` is valid.\n", + "\n", + " `operation` must be on qubits found on the device\n", + " and if it is a two qubit gate the qubits must be adjacent\n", + "\n", + " Raises:\n", + " ValueError: if operation acts on qubits not found on the device.\n", + " ValueError: if two qubit gates have non-local interactions.\n", + " ValueError: if the operation is not in the supported gates.\n", + " \"\"\"\n", + " if any(x not in self._qubits for x in operation.qubits):\n", + " raise ValueError(\"Using qubits not found on device.\")\n", + "\n", + " if len(operation.qubits) == 2:\n", + " p, q = operation.qubits\n", + " if not p.is_adjacent(q):\n", + " raise ValueError('Non-local interaction: {}'.format(repr(operation)))\n", + "\n", + " if operation not in self._supported_gates:\n", + " raise ValueError(\"Unsupported operation type.\")\n", + " \n", + " def validate_circuit(self, circuit):\n", + " \"\"\"Check to make sure `circuit` is valid.\n", + "\n", + " Calls validate_operation on all operations as well as imposing\n", + " a global limit on the total number of CZ gates.\n", + "\n", + " Raises:\n", + " ValueError: if `validate_operation` raises for any operation in the\n", + " circuit.\n", + " ValueError: if there are more than 10 CZ gates in the entire circuit.\n", + " \"\"\"\n", + " super().validate_circuit(circuit) # calls our `validate_operation`\n", + " cz_count = sum(1 for mom in circuit for op in mom if len(op.qubits) == 2)\n", + " if cz_count >= 10:\n", + " raise ValueError(\"Too many total CZs\")\n", + "\n", + " @property\n", + " def metadata(self):\n", + " \"\"\"MyDevice GridDeviceMetadata.\"\"\"\n", + " # Since `MyDevice` is planar it is a good idea to subclass the\n", + " # GridDeviceMetadata class to communicate additional device information to\n", + " # the user.\n", + " return cirq.GridDeviceMetadata(\n", + " qubit_pairs=[(p, q) for p in self._qubits for q in self._qubits if p.is_adjacent(q)],\n", + " gateset=self._supported_gates\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "9oRUQuGGI7MJ" + }, + "source": [ + "and use it to validate circuits:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "uFKsR1qLe24M" + }, + "outputs": [], + "source": [ + "my_custom_device = MyDevice()\n", + "\n", + "my_circuit = cirq.Circuit(\n", + " cirq.X(cirq.LineQubit(0)),\n", + " cirq.X(cirq.LineQubit(2)),\n", + " cirq.X(cirq.LineQubit(4)),\n", + " cirq.CZ(*cirq.LineQubit.range(2))\n", + ")\n", + "too_many_czs = cirq.Circuit(\n", + " cirq.CZ(*cirq.LineQubit.range(2)) for _ in range(11)\n", + ")\n", + "\n", + "# Valid for my_custom_device.\n", + "my_custom_device.validate_circuit(my_circuit)\n", + "\n", + "\n", + "# too_many_czs operation(s) are fine locally.\n", + "for moment in too_many_czs:\n", + " for op in moment:\n", + " my_custom_device.validate_operation(op)\n", + "\n", + "# But the device has global constraints which the circuit does not meet:\n", + "try:\n", + " my_custom_device.validate_circuit(too_many_czs)\n", + "except Exception as e:\n", + " print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eG7JY2IVZGEQ" + }, + "source": [ + "By default calling `validate_circuit` calls `validate_moment` on all the moments which calls `validate_operation` on all the operations. Depending on the scoping of constraints the device has, certain less local constraints might be better placed in `validate_moment` and certain global constraints might belong in `validate_circuit`. In addition to this you can also add metadata options to your device. You can define a metadata subclass of `cirq.DeviceMetadata` or you can use an inbuilt metadata class like `cirq.GridDeviceMetadata`:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "bbTat2DMf1VE" + }, + "outputs": [], + "source": [ + "my_metadata = my_custom_device.metadata\n", + "\n", + "# Display device graph:\n", + "nx.draw(my_metadata.nx_graph)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OZxolGY3I82H" + }, + "source": [ + "Success! You have used Devices in Cirq to validate circuits for compatability when running as well as explored metadata information on the device and implemented your own boilerplate device." + ] + } + ], + "metadata": { + "colab": { + "collapsed_sections": [], + "name": "devices.ipynb", + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/docs/devices.md b/docs/devices.md deleted file mode 100644 index 14d3bb8d130..00000000000 --- a/docs/devices.md +++ /dev/null @@ -1,63 +0,0 @@ -# Devices - -``Device`` is an abstract concept in Cirq, that can represent constraints of an actual quantum processor. -This page describes this abstract concept. - -If you are looking for ways of running quantum algorithms, take a look at - - [Simulation](simulation.ipynb), that is available on any computer - - Quantum processors, that are provided by different Quantum Service Providers: - - [Google Quantum Computing Service](tutorials/google/start.ipynb) - - [Alpine Quantum Technologies](tutorials/aqt/getting_started.ipynb) - - [Pasqal](tutorials/pasqal/getting_started.ipynb) - - [IonQ](tutorials/ionq/getting_started.ipynb) - -## The `cirq.Device` class - -The ``Device`` class is an abstract class which encapsulates constraints -(or lack thereof) that come when running a circuit on actual hardware. -For instance, most hardware only allows certain gates to be enacted -on qubits. Or, as another example, some gates may be constrained to not -be able to run at the same time as neighboring gates. Further the -``Device`` class knows more about the scheduling of ``Operations``. - -Here for example is a ``Device`` made up of 10 qubits on a line: -```python -import cirq -from cirq.devices import GridQubit -class Xmon10Device(cirq.Device): - - def __init__(self): - self.qubits = [GridQubit(i, 0) for i in range(10)] - - def validate_operation(self, operation): - if not isinstance(operation, cirq.GateOperation): - raise ValueError('{!r} is not a supported operation'.format(operation)) - if not isinstance(operation.gate, (cirq.CZPowGate, - cirq.XPowGate, - cirq.PhasedXPowGate, - cirq.YPowGate)): - raise ValueError('{!r} is not a supported gate'.format(operation.gate)) - if len(operation.qubits) == 2: - p, q = operation.qubits - if not p.is_adjacent(q): - raise ValueError('Non-local interaction: {}'.format(repr(operation))) - - - def validate_circuit(self, circuit): - for moment in circuit: - for operation in moment.operations: - self.validate_operation(operation) -``` -This device, for example, knows that two qubit gates between -next-nearest-neighbors is not valid: -```python -device = Xmon10Device() -circuit = cirq.Circuit() -circuit.append([cirq.CZ(device.qubits[0], device.qubits[2])]) -try: - device.validate_circuit(circuit) -except ValueError as e: - print(e) -# prints something like -# ValueError: Non-local interaction: Operation(cirq.CZ, (GridQubit(0, 0), GridQubit(2, 0))) -```