{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "6e781953", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:26.486180Z", "iopub.status.busy": "2025-06-13T13:23:26.485408Z", "iopub.status.idle": "2025-06-13T13:23:26.490796Z", "shell.execute_reply": "2025-06-13T13:23:26.489859Z" }, "nbsphinx": "hidden" }, "outputs": [], "source": [ "# Copyright 2025 Keysight Technologies Inc.\n", "#" ] }, { "cell_type": "raw", "id": "260880b6", "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": "1fd6c7bd", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:26.493951Z", "iopub.status.busy": "2025-06-13T13:23:26.493670Z", "iopub.status.idle": "2025-06-13T13:23:30.380254Z", "shell.execute_reply": "2025-06-13T13:23:30.379298Z" } }, "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": "33d214e0", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "\n", "Setting the duration updates both ``const_pulse`` and ``delay``:" ] }, { "cell_type": "code", "execution_count": 3, "id": "c9d2b492", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.384842Z", "iopub.status.busy": "2025-06-13T13:23:30.384384Z", "iopub.status.idle": "2025-06-13T13:23:30.389228Z", "shell.execute_reply": "2025-06-13T13:23:30.388529Z" } }, "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": "e61e5259", "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": "ba19f2c2", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.392434Z", "iopub.status.busy": "2025-06-13T13:23:30.392133Z", "iopub.status.idle": "2025-06-13T13:23:30.395943Z", "shell.execute_reply": "2025-06-13T13:23:30.395265Z" } }, "outputs": [], "source": [ "try:\n", " duration.value = 1j\n", "except TypeError:\n", " pass\n", "assert duration.value == 1e-6" ] }, { "cell_type": "raw", "id": "cdefb2e0", "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": "82f91ca3", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.398943Z", "iopub.status.busy": "2025-06-13T13:23:30.398667Z", "iopub.status.idle": "2025-06-13T13:23:30.406138Z", "shell.execute_reply": "2025-06-13T13:23:30.405446Z" }, "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": "ddd5421a", "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": "e43ace6a", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.409467Z", "iopub.status.busy": "2025-06-13T13:23:30.409187Z", "iopub.status.idle": "2025-06-13T13:23:30.425736Z", "shell.execute_reply": "2025-06-13T13:23:30.424891Z" } }, "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": "580b9f5d", "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": "3a0268ae", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.429533Z", "iopub.status.busy": "2025-06-13T13:23:30.429202Z", "iopub.status.idle": "2025-06-13T13:23:30.489076Z", "shell.execute_reply": "2025-06-13T13:23:30.488290Z" }, "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": "b52074dc", "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": "5242d6f0", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.492646Z", "iopub.status.busy": "2025-06-13T13:23:30.492225Z", "iopub.status.idle": "2025-06-13T13:23:30.505127Z", "shell.execute_reply": "2025-06-13T13:23:30.504399Z" } }, "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": "30f583f1", "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": "6faace0a", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.508406Z", "iopub.status.busy": "2025-06-13T13:23:30.508062Z", "iopub.status.idle": "2025-06-13T13:23:30.520463Z", "shell.execute_reply": "2025-06-13T13:23:30.519713Z" } }, "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": "ae31e2c7", "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": "7796788f", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.523716Z", "iopub.status.busy": "2025-06-13T13:23:30.523415Z", "iopub.status.idle": "2025-06-13T13:23:30.527669Z", "shell.execute_reply": "2025-06-13T13:23:30.527023Z" } }, "outputs": [], "source": [ "delay = qcs.Delay(1e-6)\n", "assert delay.duration.value == 1e-6" ] }, { "cell_type": "raw", "id": "630f7973", "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": "cbb77262", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.530722Z", "iopub.status.busy": "2025-06-13T13:23:30.530444Z", "iopub.status.idle": "2025-06-13T13:23:30.549646Z", "shell.execute_reply": "2025-06-13T13:23:30.548882Z" } }, "outputs": [], "source": [ "var = qcs.Scalar(\"var\", dtype=int)\n", "result = 0.5 * var + 1" ] }, { "cell_type": "raw", "id": "88780671", "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": "d9ca0368", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.552948Z", "iopub.status.busy": "2025-06-13T13:23:30.552654Z", "iopub.status.idle": "2025-06-13T13:23:30.556993Z", "shell.execute_reply": "2025-06-13T13:23:30.556313Z" } }, "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": "a5d76b7c", "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": "d02220cc", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.559950Z", "iopub.status.busy": "2025-06-13T13:23:30.559666Z", "iopub.status.idle": "2025-06-13T13:23:30.610569Z", "shell.execute_reply": "2025-06-13T13:23:30.609740Z" } }, "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": "81ff2794", "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": "2649e1a5", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.614089Z", "iopub.status.busy": "2025-06-13T13:23:30.613731Z", "iopub.status.idle": "2025-06-13T13:23:30.620276Z", "shell.execute_reply": "2025-06-13T13:23:30.619541Z" } }, "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": "ae6c1162", "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": "c350210a", "metadata": { "execution": { "iopub.execute_input": "2025-06-13T13:23:30.623391Z", "iopub.status.busy": "2025-06-13T13:23:30.623095Z", "iopub.status.idle": "2025-06-13T13:23:30.628997Z", "shell.execute_reply": "2025-06-13T13:23:30.628092Z" } }, "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": "raw_mimetype,nbsphinx,-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.10.15" } }, "nbformat": 4, "nbformat_minor": 5 }