{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Getting Started with OpenQASM on Braket" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Use Braket SDK Cost Tracking to estimate the cost to run this example\n", "from braket.tracking import Tracker\n", "t = Tracker().start()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "OpenQASM is a popular human-readable and hardware-agnostic quantum circuit description language. It is open-source and has been actively maintained by a [Technical Steering Committee](https://medium.com/qiskit/introducing-a-technical-steering-committee-for-openqasm3-f9db808108e1) formed by IBM, Amazon, Microsoft and the University of Innsbruck. Amazon Braket now supports OpenQASM 3.0 as an *Intermediate Representation* (IR) in addition to the in-house *JSON-Based AWS Quantum Circuit Description* ([JAQCD](https://github.com/aws/amazon-braket-schemas-python/tree/main/src/braket/ir/jaqcd)). In this notebook, we demonstrate how to submit OpenQASM tasks to various devices on Braket and introduce some OpenQASM features available on Braket." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create and submit an OpenQASM task\n", "\n", "Submitting a quantum task with OpenQASM is just as simple as using JAQCD. You can use the Amazon Braket Python SDK, Boto3, or the AWS CLI to submit OpenQASM 3.0 tasks to an Amazon Braket device. We will go over each method in this section.\n", "\n", "\n", "### A Bell state\n", "\n", "We will start with by preparing a [Bell state](https://en.wikipedia.org/wiki/Bell_state) in OpenQASM:\n" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "bell_qasm = \"\"\"\n", "OPENQASM 3;\n", "\n", "qubit[2] q;\n", "bit[2] c;\n", "\n", "h q[0];\n", "cnot q[0], q[1];\n", "\n", "c = measure q;\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compare this to the same Bell state written in JAQCD:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"braketSchemaHeader\": {\n", " \"name\": \"braket.ir.jaqcd.program\",\n", " \"version\": \"1\"\n", " },\n", " \"instructions\": [\n", " {\n", " \"target\": 0,\n", " \"type\": \"h\"\n", " },\n", " {\n", " \"control\": 0,\n", " \"target\": 1,\n", " \"type\": \"cnot\"\n", " }\n", " ],\n", " \"results\": null,\n", " \"basis_rotation_instructions\": null\n", "}\n" ] } ], "source": [ "from braket.ir.jaqcd import CNot, H, Program\n", "\n", "program = Program(instructions=[H(target=0), CNot(control=0, target=1)])\n", "print(program.json(indent=2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Immediately, we can see a difference: In OpenQASM, users define their own qubit registers, and thus the syntax is closer to what quantum algorithm researchers are used to; on the other hand, in JAQCD, qubits are indexed by integers and the convention is closer to that of hardware providers. Also, JAQCD has result types and basis rotation instructions embedded in the language while OpenQASM doesn't support them inherently (but later we will show how to use the `pragma` syntax to support them in OpenQASM)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "### Use the Python SDK to create OpenQASM 3.0 tasks\n", "\n", "Most Braket users might want to use the Braket Python SDK to submit OpenQASM tasks. To submit our Bell state program in the Python SDK, we first choose the quantum device that we want to run our program on. In this example, we will use the SV1 state-vector simulator for demonstration." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import boto3\n", "import json\n", "from braket.aws import AwsDevice\n", "sv1 = AwsDevice(\"arn:aws:braket:::device/quantum-simulator/amazon/sv1\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To submit the OpenQASM task, we initialize an `OpenQASMProgram` object using the Bell state program text string `bell_qasm` we defined above and send it to the SV1 simulator." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": false }, "outputs": [], "source": [ "from braket.ir.openqasm import Program as OpenQASMProgram\n", "\n", "bell_program = OpenQASMProgram(source=bell_qasm)\n", "bell_task = sv1.run(\n", " bell_program, \n", " shots=100, \n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit OpenQASM 3.0 programs using the AWS Command Line Interface\n", "\n", "Alternatively, if you like the command line experience or you are not a Python user, you can also choose to use the [AWS Command Line Interface (CLI)](https://aws.amazon.com/cli/) to submit our Bell state program. Before doing that we have to make sure we have [AWS CLI installed](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html). The following code saves the `bell_qasm` string to a file named `bell.qasm`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "with open(\"bell.qasm\", \"w\") as f:\n", " f.write(bell_qasm)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then we can use the command below to submit the task via AWS CLI. Remember to replace the placeholder \\\"amazon-braket-my-bucket\\\" with your own bucket name.\n", " \n", " aws braket create-quantum-task \\\n", " --region \"us-west-1\" \\\n", " --device-arn \"arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3\" \\\n", " --shots 100 \\\n", " --action '{\n", " \"braketSchemaHeader\": {\n", " \"name\": \"braket.ir.openqasm.program\", \n", " \"version\": \"1\"\n", " },\n", " \"source\": $(cat bell.qasm)\n", " }'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Figure out what OpenQASM features are supported on each device\n", "\n", "Different devices on Braket support different subsets of OpenQASM features. To see what are the supported OpenQASM features on each device, we can simply check the device capability for OpenQASM actions. As an example, we can take a look at the `action` field in the device capability of SV1 simulator:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['ccnot',\n", " 'cnot',\n", " 'cphaseshift',\n", " 'cphaseshift00',\n", " 'cphaseshift01',\n", " 'cphaseshift10',\n", " 'cswap',\n", " 'cy',\n", " 'cz',\n", " 'ecr',\n", " 'h',\n", " 'i',\n", " 'iswap',\n", " 'pswap',\n", " 'phaseshift',\n", " 'rx',\n", " 'ry',\n", " 'rz',\n", " 's',\n", " 'si',\n", " 'swap',\n", " 't',\n", " 'ti',\n", " 'v',\n", " 'vi',\n", " 'x',\n", " 'xx',\n", " 'xy',\n", " 'y',\n", " 'yy',\n", " 'z',\n", " 'zz',\n", " 'gpi',\n", " 'gpi2',\n", " 'ms']" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# supportedOperations\n", "sv1.properties.action['braket.ir.openqasm.program'].supportedOperations" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['braket_unitary_matrix',\n", " 'braket_basis_rotation',\n", " 'braket_result_type_sample',\n", " 'braket_result_type_expectation',\n", " 'braket_result_type_variance',\n", " 'braket_result_type_probability',\n", " 'braket_result_type_amplitude',\n", " 'braket_result_type_adjoint_gradient']" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# supportedPragmas\n", "sv1.properties.action['braket.ir.openqasm.program'].supportedPragmas" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['braket_result_type_state_vector',\n", " 'braket_result_type_density_matrix',\n", " 'braket_noise_amplitude_damping',\n", " 'braket_noise_bit_flip',\n", " 'braket_noise_depolarizing',\n", " 'braket_noise_kraus',\n", " 'braket_noise_pauli_channel',\n", " 'braket_noise_generalized_amplitude_damping',\n", " 'braket_noise_phase_flip',\n", " 'braket_noise_phase_damping',\n", " 'braket_noise_two_qubit_dephasing',\n", " 'braket_noise_two_qubit_depolarizing']" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# forbiddenPragmas\n", "sv1.properties.action['braket.ir.openqasm.program'].forbiddenPragmas" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The SV1 OpenQASM `action` field lists supported/forbidden OpenQASM features on the device, including `supportedPragmas`, `forbiddenPragmas`, `maximumQubitArrays`, `maximumClassicalArrays`, `requiresAllQubitsMeasurement`, `supportedResultTypes`, etc. The names are self-evident, but readers are encouraged to visit the [Amazon Braket developer guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-using.html) for full information of what these fields mean." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# OpenQASM features on Braket\n", "\n", "Braket supports many useful OpenQASM features, either through the OpenQASM program syntax or Braket-specific pragmas. We will walk through some of these features in this section.\n", "\n", "## Simulating Noise with OpenQASM\n", "\n", "With the fully on-demand, high-performance, density-matrix simulator [DM1](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html#braket-simulator-dm1), you can easily investigate the effects of realistic noise on your quantum programs. Now, we show how to use OpenQASM programs to leverage the circuit-level noise simulation capability of DM1.\n", "\n", "To simulate noise, we have to be able to specify different noise channels. Although syntax for noise channels is not available in the OpenQASM language, Braket uses the `pragma` statement to extend OpenQASM for defining noise channels. Here is an example of an OpenQASM program that prepares a noisy 3-qubit [GHZ state](https://en.wikipedia.org/wiki/Greenberger%E2%80%93Horne%E2%80%93Zeilinger_state):" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "noisy_ghz3_program = \"\"\"\n", "// noisy_ghz3.qasm\n", "// Prepare a 3 noisy qubit GHZ state\n", "OPENQASM 3;\n", "\n", "qubit[3] q;\n", "bit[3] c;\n", "\n", "h q[0];\n", "#pragma braket noise depolarizing(0.1) q[0]\n", "cnot q[0], q[1];\n", "#pragma braket noise depolarizing(0.1) q[0]\n", "#pragma braket noise depolarizing(0.1) q[1]\n", "cnot q[1], q[2];\n", "#pragma braket noise depolarizing(0.1) q[0]\n", "#pragma braket noise depolarizing(0.1) q[1]\n", "\n", "c = measure q;\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the example above, we inserted the depolarizing noise channel with probability of 0.1 after each gate in the circuit. The `noisy_ghz3_program` is equivalent to the following program in the Braket SDK:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Circuit('instructions': [Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)])), Instruction('operator': Depolarizing(0.1), 'target': QubitSet([Qubit(0)])), Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), Qubit(1)])), Instruction('operator': Depolarizing(0.1), 'target': QubitSet([Qubit(0)])), Instruction('operator': Depolarizing(0.1), 'target': QubitSet([Qubit(1)])), Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(1), Qubit(2)])), Instruction('operator': Depolarizing(0.1), 'target': QubitSet([Qubit(1)])), Instruction('operator': Depolarizing(0.1), 'target': QubitSet([Qubit(2)]))])" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from braket.circuits import Circuit, Observable, Gate, Noise, FreeParameter\n", "\n", "noisy_ghz3_circ = Circuit().h(0).cnot(0, 1).cnot(1, 2)\n", "noise = Noise.Depolarizing(probability=0.1)\n", "noisy_ghz3_circ.apply_gate_noise(noise)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To see if `noisy_ghz3_program` and `noisy_ghz3_circ` are indeed the same, we can run both circuits and compare the results:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [], "source": [ "dm1 = AwsDevice(\"arn:aws:braket:::device/quantum-simulator/amazon/dm1\")\n", "\n", "noisy_ghz3_circ_task = dm1.run(noisy_ghz3_circ, shots = 10)\n", "noisy_ghz3_program_task = dm1.run(OpenQASMProgram(source=noisy_ghz3_program), shots = 10)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "sdk measurement results: Counter({'000': 6, '011': 2, '111': 1, '100': 1})\n", "openqasm measurement results: Counter({'100': 4, '111': 4, '000': 2})\n" ] } ], "source": [ "sdk_result = noisy_ghz3_circ_task.result()\n", "openqasm_result = noisy_ghz3_program_task.result()\n", "sdk_measurement = sdk_result.measurement_counts\n", "openqasm_measurement = openqasm_result.measurement_counts\n", "print('sdk measurement results:', sdk_measurement)\n", "print('openqasm measurement results:', openqasm_measurement)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As expected, the measurement counts of the two are very close.\n", "\n", "In addition to depolarizing noises, we can simulate more complicated noise types with Braket, e.g., `pauli_channel`, `amplitude_damping`, etc. Check the [Amazon Braket developer guide](https://docs.aws.amazon.com/braket/latest/developerguide/braket-using.html) for a complete list of noise channels supported on Braket. Here we give another example of general noise channels defined by the Kraus representation." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": [ "noisy_program_with_kraus_operators = \"\"\"\n", "// noisy_program_with_kraus_operators\n", "OPENQASM 3;\n", "\n", "qubit[2] q;\n", "bit[2] c;\n", "\n", "h q[0];\n", "#pragma braket noise kraus([[0.9486833, 0], [0, 0.9486833]], [[0, 0.31622777], [0.31622777, 0]]) q[0]\n", "cnot q[1], q[2];\n", "\n", "c = measure q;\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We inserted a single qubit noise channel defined by two 2x2 complex Kraus operators in the example above on qubit `q[0]`. Braket will validate if the Kraus operators indeed form a Completely-Positive and Trace-Preserving (CPTP) map." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submitting parametrized tasks with OpenQASM\n", "\n", "The on-demand [SV1 simulator](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html#braket-simulator-sv1) and [DM1 simulator](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html#braket-simulator-dm1) support submitting OpenQASM programs with free parameters. You can set the value of the parameter when you submit the task, like so:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "parameter_qasm = \"\"\"\n", "OPENQASM 3.0;\n", "input float alpha;\n", "\n", "bit[2] b;\n", "qubit[2] q;\n", "\n", "h q[0];\n", "h q[1];\n", "rx(alpha) q[0];\n", "rx(alpha) q[1];\n", "b[0] = measure q[0];\n", "b[1] = measure q[1];\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `input float alpha` line indicates that we have an input parameter of type `float` named `alpha`. We can specify a value for `alpha` when we submit the task using the optional `inputs` argument to `run`. `input` should be a `dict` of `string`-`float` pairs." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Counter({'11': 4, '00': 4, '10': 2})\n" ] } ], "source": [ "input_dict = {'alpha': 0.1}\n", "param_sv1_task = sv1.run(OpenQASMProgram(source=parameter_qasm), shots = 10, inputs=input_dict)\n", "param_sv1_result = param_sv1_task.result()\n", "print(param_sv1_result.measurement_counts)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, we can specify values for parametrized noise operations:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "parameter_noise_qasm = \"\"\"\n", "OPENQASM 3.0;\n", "input float beta;\n", "input float alpha;\n", "bit[2] b;\n", "qubit[2] q;\n", "h q[0];\n", "h q[1];\n", "rx(alpha) q[0];\n", "rx(alpha) q[1];\n", "#pragma braket noise bit_flip(beta) q[0]\n", "b[0] = measure q[0];\n", "b[1] = measure q[1];\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see there are now two `input` lines, one for each free parameter." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Counter({'11': 4, '01': 3, '00': 2, '10': 1})\n" ] } ], "source": [ "noise_input_dict = {'alpha': 0.1, 'beta': 0.2}\n", "param_dm1_task = dm1.run(OpenQASMProgram(source=parameter_qasm), shots = 10, inputs=noise_input_dict)\n", "param_dm1_result = param_dm1_task.result()\n", "print(param_dm1_result.measurement_counts)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simulating arbitrary unitaries with OpenQASM\n", "\n", "The on-demand [SV1 simulator](https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html#braket-simulator-sv1) allows us to simulate arbitrary unitary gates in a circuit. With OpenQASM, we can use the `unitary` pramga to insert these arbitrary unitary gates: " ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "program_with_unitary = \"\"\"\n", "// noisy_program_with_kraus_operators\n", "OPENQASM 3;\n", "\n", "qubit q;\n", "bit c;\n", "\n", "#pragma braket unitary([[0, -1im], [1im, 0]]) q\n", "c = measure q;\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `1im` in the `unitary` pragma is the OpenQASM notation of the imaginary number $i$, thus, we were simply using the pragma to perform a Pauli Y gate. We can check it by submitting the above program to SV1." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Counter({'1': 10})\n" ] } ], "source": [ "unitary_task = sv1.run(OpenQASMProgram(source=program_with_unitary), shots = 10)\n", "unitary_result = unitary_task.result()\n", "print(unitary_result.measurement_counts)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As expected, the Pauli Y gate flipped the initial 0 state to the 1 state." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Qubit Rewiring with OpenQASM\n", "\n", "Amazon Braket supports the [physical qubit notation within OpenQASM](https://openqasm.com/language/types.html#physical-qubits) on Rigetti devices. When using physical qubits, you have to ensure that the qubits are indeed connected on the selected device. Alternatively, if qubit registers are used instead, the `PARTIAL` rewiring strategy is enabled by default on Rigetti devices. The following example shows how to use physical qubit notation in an OpenQASM program:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [], "source": [ "ghz_program_with_physical_qubits = \"\"\"\n", "// Prepare a GHZ state\n", "OPENQASM 3;\n", "\n", "bit[3] ro;\n", "h $0;\n", "cnot $0, $1;\n", "cnot $1, $2;\n", "ro[0] = measure $0;\n", "ro[1] = measure $1;\n", "ro[2] = measure $2;\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can run the above program on the Rigetti Aspen-M-3 device," ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Measured qubits: [0, 1, 2]\n" ] } ], "source": [ "# choose the quantum device\n", "aspen_m = AwsDevice(\"arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3\")\n", "\n", "ghz_program_with_physical_qubits_task = aspen_m.run(OpenQASMProgram(source=ghz_program_with_physical_qubits), shots = 10)\n", "measured_qubits = ghz_program_with_physical_qubits_task.result().measured_qubits\n", "print(\"Measured qubits:\", measured_qubits)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, physical qubits 0, 1 and 2 are indeed being used and measured." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "