{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "0206dd5e", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:38.804300Z", "iopub.status.busy": "2024-10-11T06:17:38.803903Z", "iopub.status.idle": "2024-10-11T06:17:38.809624Z", "shell.execute_reply": "2024-10-11T06:17:38.808818Z" }, "nbsphinx": "hidden" }, "outputs": [], "source": [ "# Copyright 2024 Keysight Technologies Inc.\n", "#" ] }, { "cell_type": "raw", "id": "9d4184d6", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Variables in programs\n", "=====================\n", "\n", "Variables are a central part of |QCS| and represent classical inputs and outputs\n", "of quantum programs. Due to the short time scales imposed by quantum decoherence,\n", "these variables are processed on field-programmable gate arrays (FPGAs) and\n", "application-specific integrated circuits (ASICs) that are integrated into the control\n", "hardware. This integration allows for real-time readouts and updates of variables,\n", "and enables decision logic based on measurement outcomes.\n", "\n", "There are three types of scalars that need to be processed in quantum computations:\n", "\n", " #. Integers, e.g., to describe the classical output of a measurement.\n", "\n", " #. Real numbers, e.g., to describe the amplitude, frequency or duration of\n", " waveforms.\n", "\n", " #. Complex numbers, e.g., to describe the integrated result of an analog\n", " measurement.\n", "\n", "Each of these scalar types can be grouped into arrays. We then want to be able to do\n", "the following:\n", "\n", " #. Define the inputs and outputs of programs.\n", "\n", " #. Define variables and use them to define multiple instructions such that whenever\n", " the value of the variable changes, it is changed in all instructions that use it.\n", "\n", " #. Define one or more variables and use their values at runtime to determine which\n", " instruction to execute.\n", "\n", " #. Define classical processing of arrays, slices thereof, and scalars that are to be\n", " performed during the execution of a quantum program.\n", "\n", "To support the above, |QCS| includes the :py:class:`~keysight.qcs.variables.Scalar`\n", "and :py:class:`~keysight.qcs.variables.Array` classes, which encapsulate a variable\n", "``name``, an underlying scalar ``dtype``, and a ``value`` that may be uninitialized.\n", "The ``value`` can be set on initialization and/or changed afterwards. When the\n", "``value`` is set, it will be coerced to the underlying ``dtype`` if possible and raise\n", "an error if it is not possible.\n", "\n", "The default ``dtype`` is ``complex`` because it is the broadest scalar type." ] }, { "cell_type": "code", "execution_count": 2, "id": "d5db11b2", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:38.813269Z", "iopub.status.busy": "2024-10-11T06:17:38.812958Z", "iopub.status.idle": "2024-10-11T06:17:41.938302Z", "shell.execute_reply": "2024-10-11T06:17:41.937399Z" } }, "outputs": [], "source": [ "import keysight.qcs as qcs\n", "\n", "# define a constant waveform and a delay that both have the same uninitialized duration\n", "duration = qcs.Scalar(\"duration\", dtype=float)\n", "\n", "assert duration.name == \"duration\"\n", "assert duration.dtype is float\n", "\n", "const_pulse = qcs.DCWaveform(duration, qcs.ConstantEnvelope(), amplitude=1)\n", "delay = qcs.Delay(duration)\n", "\n", "assert const_pulse.duration.value is None\n", "assert delay.duration.value is None" ] }, { "cell_type": "raw", "id": "d04ca7e9", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Setting the duration updates both ``const_pulse`` and ``delay``:" ] }, { "cell_type": "code", "execution_count": 3, "id": "6691f8b6", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:41.942384Z", "iopub.status.busy": "2024-10-11T06:17:41.941907Z", "iopub.status.idle": "2024-10-11T06:17:41.946698Z", "shell.execute_reply": "2024-10-11T06:17:41.945945Z" } }, "outputs": [], "source": [ "duration.value = 1e-6\n", "assert const_pulse.duration.value == 1e-6\n", "assert delay.duration.value == 1e-6" ] }, { "cell_type": "raw", "id": "29be63e9", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Setting a variable to an invalid type raises a ``TypeError`` and leaves the ``value``\n", "unchanged:" ] }, { "cell_type": "code", "execution_count": 4, "id": "1f48dbe7", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:41.950066Z", "iopub.status.busy": "2024-10-11T06:17:41.949758Z", "iopub.status.idle": "2024-10-11T06:17:41.953608Z", "shell.execute_reply": "2024-10-11T06:17:41.952834Z" } }, "outputs": [], "source": [ "try:\n", " duration.value = 1j\n", "except TypeError:\n", " pass\n", "assert duration.value == 1e-6" ] }, { "cell_type": "raw", "id": "e8dc1a4e", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "We can also create read-only variables that effectively act as constants in an\n", "experiment. Trying to set the ``value`` of a read-only variable raises a\n", "``ValueError`` and leaves it unchanged:" ] }, { "cell_type": "code", "execution_count": 5, "id": "63fbf894", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:41.957210Z", "iopub.status.busy": "2024-10-11T06:17:41.956902Z", "iopub.status.idle": "2024-10-11T06:17:41.965125Z", "shell.execute_reply": "2024-10-11T06:17:41.964346Z" }, "lines_to_next_cell": 2 }, "outputs": [], "source": [ "read_only = qcs.Scalar(\"duration\", value=1, dtype=int, read_only=True)\n", "try:\n", " read_only.value = 2\n", "except ValueError:\n", " pass\n", "assert read_only.value == 1" ] }, { "cell_type": "raw", "id": "a4d36201", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Arrays\n", "------\n", "\n", "An :py:class:`~keysight.qcs.variables.Array` has the same ``name``, ``dtype``, and\n", "``value`` properties as a :py:class:`~keysight.qcs.variables.Scalar`, but adds a\n", "``shape`` property which specifies the number of dimensions and the size of each\n", "dimension. The dimensions of an undefined size can be specified as ``None`` and will\n", "be inferred when the ``value`` is set. The ``shape`` and ``value`` cannot both be\n", "passed as arguments as they are redundant and must be specified as keyword arguments\n", "to avoid ambiguity." ] }, { "cell_type": "code", "execution_count": 6, "id": "6de9e8c3", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:41.968423Z", "iopub.status.busy": "2024-10-11T06:17:41.968116Z", "iopub.status.idle": "2024-10-11T06:17:41.979922Z", "shell.execute_reply": "2024-10-11T06:17:41.979157Z" } }, "outputs": [], "source": [ "# specify an empty 1D array with two rows\n", "array = qcs.Array(\"array\", shape=(2,))\n", "assert array.value is None\n", "\n", "# setting the value will set any unknown sizes\n", "array.value = [1, 2]\n", "assert array.shape == (2,)\n", "assert all(array.value == [1, 2])" ] }, { "cell_type": "raw", "id": "3d8d9f51", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "We can access individual elements or regularly spaced subsets in an\n", ":py:class:`~keysight.qcs.variables.Array` using the same syntax as\n", "`basic NumPy slicing `_. This\n", "slicing syntax extends Python slicing syntax to multi-dimensional arrays.\n", "\n", ".. note::\n", "\n", " We only support basic NumPy slicing because it returns a view of the underlying\n", " data, whereas advanced NumPy slicing creates copies." ] }, { "cell_type": "code", "execution_count": 7, "id": "886bba76", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:41.983539Z", "iopub.status.busy": "2024-10-11T06:17:41.983198Z", "iopub.status.idle": "2024-10-11T06:17:42.062093Z", "shell.execute_reply": "2024-10-11T06:17:42.061189Z" }, "lines_to_next_cell": 2 }, "outputs": [], "source": [ "# Create a 2x3 array with integer values\n", "array = qcs.Array(\"array\", value=[[1, 2], [3, 4], [4, 5]])\n", "\n", "# retrieve the first row\n", "assert (array[0].value == [1, 2]).all()\n", "\n", "# retrieve the first column\n", "assert (array[:, 0].value == [1, 3, 4]).all()" ] }, { "cell_type": "raw", "id": "392e3cb5", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Slices are valid even if the ``value`` of the\n", ":py:class:`~keysight.qcs.variables.Array` being sliced is ``None``. Setting the\n", "``value`` of the :py:class:`~keysight.qcs.variables.Array` will automatically update\n", "the ``value`` of all its slices.\n", "\n", ".. note::\n", "\n", " To avoid potential edge cases, an :py:class:`~keysight.qcs.variables.Array` can only\n", " be sliced if all elements of its ``shape`` are specified." ] }, { "cell_type": "code", "execution_count": 8, "id": "e1726183", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:42.066168Z", "iopub.status.busy": "2024-10-11T06:17:42.065767Z", "iopub.status.idle": "2024-10-11T06:17:42.082958Z", "shell.execute_reply": "2024-10-11T06:17:42.082004Z" } }, "outputs": [], "source": [ "# create an empty array with two rows\n", "array = qcs.Array(\"array\", shape=(2,), dtype=int)\n", "\n", "# store the first value in a slice\n", "arslice = array[0]\n", "assert arslice.value is None\n", "\n", "# populate the array with values\n", "array.value = [0, 1]\n", "\n", "# the slice value is now updated\n", "assert arslice.value == 0" ] }, { "cell_type": "raw", "id": "80dcbd50", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Likewise, setting the ``value`` of a slice will also update the\n", ":py:class:`~keysight.qcs.variables.Array`\\'s ``value``:" ] }, { "cell_type": "code", "execution_count": 9, "id": "73d74c59", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:42.086749Z", "iopub.status.busy": "2024-10-11T06:17:42.086358Z", "iopub.status.idle": "2024-10-11T06:17:42.100172Z", "shell.execute_reply": "2024-10-11T06:17:42.099350Z" } }, "outputs": [], "source": [ "array = qcs.Array(\"array\", value=[[0, 1]])\n", "\n", "# store the first value in a slice\n", "arslice = array[0, 0]\n", "assert arslice.value == 0\n", "\n", "# change the value of the slice\n", "arslice.value = 2\n", "\n", "# the array value is now updated\n", "assert (array.value == [[2, 1]]).all()" ] }, { "cell_type": "raw", "id": "81da6600", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Implicit variables\n", "------------------\n", "\n", "For convenient and consistent interfaces, we allow scalar arguments to be specified by\n", "a number and implicitly cast them to a read-only variable (or constant) with an empty\n", "string as its ``name``, where the ``dtype`` depends on the class.\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "480e26c0", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:42.103818Z", "iopub.status.busy": "2024-10-11T06:17:42.103263Z", "iopub.status.idle": "2024-10-11T06:17:42.107956Z", "shell.execute_reply": "2024-10-11T06:17:42.107021Z" } }, "outputs": [], "source": [ "delay = qcs.Delay(1e-6)\n", "assert delay.duration.value == 1e-6" ] }, { "cell_type": "raw", "id": "5d78f063", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Dependent variables\n", "-------------------\n", "\n", "Dependent variables can be specified as arithmetic expressions of existing variables." ] }, { "cell_type": "code", "execution_count": 11, "id": "7fd8179b", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:42.111347Z", "iopub.status.busy": "2024-10-11T06:17:42.111040Z", "iopub.status.idle": "2024-10-11T06:17:42.134085Z", "shell.execute_reply": "2024-10-11T06:17:42.133274Z" } }, "outputs": [], "source": [ "var = qcs.Scalar(\"var\", dtype=int)\n", "result = 0.5 * var + 1" ] }, { "cell_type": "raw", "id": "b1551db3", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Note that such an expression is not a temporary variable assignment, but rather a\n", "stationary definition. The value of ``result`` is always set using the value of\n", "``var``." ] }, { "cell_type": "code", "execution_count": 12, "id": "183f6ada", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:42.137429Z", "iopub.status.busy": "2024-10-11T06:17:42.137121Z", "iopub.status.idle": "2024-10-11T06:17:42.141558Z", "shell.execute_reply": "2024-10-11T06:17:42.140832Z" } }, "outputs": [], "source": [ "# the value of result is None before var is used\n", "assert result.value is None\n", "\n", "# result updates accordingly with var\n", "var.value = 3\n", "assert result.value == 2.5" ] }, { "cell_type": "raw", "id": "9531fdbd", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Expressions can use any combination of scalars, arrays, and slices. Array operations\n", "are element-wise." ] }, { "cell_type": "code", "execution_count": 13, "id": "f7daeb88", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:42.144586Z", "iopub.status.busy": "2024-10-11T06:17:42.144296Z", "iopub.status.idle": "2024-10-11T06:17:42.179989Z", "shell.execute_reply": "2024-10-11T06:17:42.178919Z" } }, "outputs": [], "source": [ "coeff = qcs.Scalar(\"c\", value=0.5, dtype=float)\n", "res1 = qcs.Array(\"res1\", value=[5, 2], dtype=int)\n", "res2 = qcs.Array(\"res2\", value=[2, 1], dtype=int)\n", "res3 = qcs.Array(\"res3\", value=[0, 1, 7], dtype=int)\n", "\n", "result = 2 * coeff * res1[0] * res2 + res3[:2] / res2 + 5\n", "\n", "assert (result.value == [15, 11]).all()" ] }, { "cell_type": "raw", "id": "1cf9bee0", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Dependent variables are convenient as instruction arguments that depend on a common\n", "variable." ] }, { "cell_type": "code", "execution_count": 14, "id": "9dbb74f4", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:42.183993Z", "iopub.status.busy": "2024-10-11T06:17:42.183649Z", "iopub.status.idle": "2024-10-11T06:17:42.189796Z", "shell.execute_reply": "2024-10-11T06:17:42.189104Z" } }, "outputs": [], "source": [ "# define an amplitude variable that may update over the course of the program\n", "amp = qcs.Scalar(\"amp\", dtype=float)\n", "\n", "# multiple pulses depend on the variable amplitude\n", "pulse1 = qcs.DCWaveform(80e-9, qcs.GaussianEnvelope(), 0.98 * amp)\n", "pulse2 = qcs.DCWaveform(80e-9, qcs.GaussianEnvelope(), 0.90 * amp)" ] }, { "cell_type": "raw", "id": "4de9c6a8", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Hashing\n", "-------\n", "\n", "Python makes heavy use of hashable collections, such as sets and dictionaries, to\n", "enable lookups to be performed with an average-case cost that is independent of the\n", "collection's size. For variables and any classes that contain them as attributes to be\n", "able to be inserted in a hashable collection, they need to define hash and equality\n", "functions. There are two requirements for a hash function:\n", "\n", " #. The hash must not change over the lifetime of an object.\n", "\n", " #. The hash of two equal objects must be the same.\n", "\n", "To satisfy these requirements, we make the hash of a variable *independent* of the\n", "variable's ``value`` so that changing that ``value`` does not affect the hash of\n", "the variable.\n" ] }, { "cell_type": "code", "execution_count": 15, "id": "a076caa5", "metadata": { "execution": { "iopub.execute_input": "2024-10-11T06:17:42.192901Z", "iopub.status.busy": "2024-10-11T06:17:42.192604Z", "iopub.status.idle": "2024-10-11T06:17:42.198879Z", "shell.execute_reply": "2024-10-11T06:17:42.198147Z" } }, "outputs": [], "source": [ "var = qcs.Scalar(\"var\")\n", "initial_hash = hash(var)\n", "var.value = 1e-6\n", "assert hash(var) == initial_hash" ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "nbsphinx,raw_mimetype,-all", "main_language": "python", "notebook_metadata_filter": "-all", "text_representation": { "extension": ".py", "format_name": "percent" } }, "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.9.6" } }, "nbformat": 4, "nbformat_minor": 5 }