{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Station\n", "\n", "In this tutorial, the following topics shall be covered:\n", "\n", "- What is a station\n", "- How to create it, and work with it\n", "- Snapshot of a station\n", "- Configuring station using a YAML configuration file" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Useful imports:\n", "from qcodes.instrument_drivers.mock_instruments import DummyInstrument\n", "from qcodes.parameters import Parameter\n", "from qcodes.station import Station" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is a Station?\n", "\n", "Experimental setups are generally large as they consist of many instruments. Each instrument in a given setup tends to be quite complex due to the fact that each comprises a variety of adjustable parameters and other stateful parts. As a result, it deems useful to have a bucket in which the necessary information concerning the instruments that are going to be used in a particular experiment can be conveniently stored, and accessed.\n", "\n", "The concept of a station, in essence, is a programmatical representation of such a bucket. Instruments, parameters, and other \"components\" can be added to a station. The user gets a station instance that can be referred to in order to access those \"components\".\n", "\n", "A station can be configured from a text file which simplifies the initialisation of the instruments. In particular, in this tutorial, we shall provide an example configuration of a station by using a YAML file.\n", "\n", "A special use case of a station in an experiment would be the capturing of the state of an experimental setup, known as a snapshot. We shall devote a subsection for the concept of a snapshot. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## How to create a Station and work with it?\n", "\n", "Let us, first, create a dummy parameter, and a dummy instrument which we shall use throughout the tutorial." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# A dummy self-standing parameter\n", "p = Parameter('p', label='Parameter P', unit='kg', set_cmd=None, get_cmd=None)\n", "p.set(123)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# A dummy instrument with three parameters\n", "instr = DummyInstrument('instr', gates=['input', 'output', 'gain'])\n", "instr.gain(42)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Creating a Station" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We create a ``Station`` object and add previously defined parameter and instrument as its components as follows: " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'instr'" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "station = Station()\n", "\n", "station.add_component(p)\n", "station.add_component(instr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is straightforward to verify if the station contains our parameter and instrument:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'p': ,\n", " 'instr': }" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Now station contains both `p` and `instr`\n", "station.components" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that it is also possible to add components to a station via arguments of its constructor:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "station = Station(p, instr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Accessing Station components" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that the components have been added to the station, it is possible to access them as its attributes (by using the \"dot\" notation). With this feature, users can use tab-completion to find the instrument in the station they'd like to access." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "# Let's confirm that station's `p` is\n", "# actually the `p` parameter defined above\n", "assert station.p is p" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "It is also possible to access a component or a sub component of a instrument directly via its name. For example, if we want to access the parameter ``input`` of the instrument ``instr``, we can do the following:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "instr_input\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "input_name = station.instr.input.full_name\n", "print(input_name)\n", "param = station.get_component(input_name)\n", "param is instr.input" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Removing components from a Station" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Removing components from a station should be done with `remove_component` method - the name of the component that is to be removed should be passed as the argument of the method:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "station.remove_component('p')" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'instr': }" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Now station contains only `instr`\n", "station.components" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Default Station\n", "\n", "The `Station` class is designed in such a way that it always contains a reference to a `default` station object (the `Station.default` attribute). The constructor of the station object has a `default` keyword argument that allows to specify whether the resulting instance shall be stored as a default station, or not.\n", "\n", "This feature is a convenience. Other objects which consume an instance of `Station` as an argument (for example, `Measurement`) can now implement a logic to resort to `Station.default` in case a `Station` instance was not explicitly given to them." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Snapshot of a Station\n", "\n", "The station has a `snapshot` method that allows to create a collective, single snapshot of all the instruments, parameters, and submodules that have been added to it. It would be very time-consuming for the user to manually go through every instrument and parameter, and collect the snapshot data.\n", "\n", "For example, the `Measurement` object accepts a station argument exactly for the purpose of storing a snapshot of the whole experimental setup next to the measured data.\n", "\n", "Read more about snapshots in general, how to work with them, station's snapshot in particular, and more -- in [this notebook](DataSet/Working with snapshots.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Configuring the Station by using a YAML configuration file\n", "\n", "The instantiation of the instruments, setting up the proper initial values of the corresponding parameters and similar pre-specifications of a measurement constitutes the initialization portion of the code. In general, this portion can be quite long and tedious to maintain. For example, consider a case in which a certain instrument is no longer needed. In this case a common practice is commenting out the lines of initialization script which are related to the said instrument, and re-run the initialization script. The latter may easily cause a bloaded code with possible repetitions. In another case, we may want to share initialization scripts among collaborators and fail to do so as it is difficult due to the fact that even similar experiments may require different instantiations.\n", "\n", "These (and more) concerns are to be solved by a YAML configuration file of the `Station` object (formerly known as the `StationConfigurator`).\n", "\n", "The YAML configuration file allows one to statically and uniformly specify settings of all the instruments (and their parameters) that the measurement setup (the \"physical\" station) consists of, and load them with those settings on demand. The `Station` object implements convenient methods for this procedure.\n", "\n", "The YAML configuration file, if used, is stored in the station as a attribute with the name `config`, and is thus included in the snapshot of the whole station.\n", "\n", "Note that we are not obliged to use the YAML configuration file to set up a `Station` (see, for example, the section \"How to create a Station and work with it\" of this tutorial).\n", "\n", "In what follows, we shall discuss:\n", "\n", "- The structure of the YAML configuration file\n", "- `Station`s methods related to working with the YAML configuration\n", "- Entries in QCoDeS configuration that are related to `Station`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example of YAML Station configuration" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, we provide an example YAML station configuration file. All the fields within the configuration file are explained via inline comments that should not be disregarded. A careful inspection of the comments by the reader is strongly recomended for a clear understanding.\n", "\n", "In particular, here, we would like to underline the difference between `parameters` and `add_parameters` sections. In this example file for the `QDac` instrument, we define a `Bx` parameter as a new, additional parameter. The `Bx` parameter will have the properties such as `limits`, `scale`, etc. __different__ from its \"source\" parameter `ch02.v` that it controls. Specifically, this means that when setting `Bx` to `2.0`:\n", "\n", "1. the value of `2.0` is being validated against the limits of `Bx` (`0.0, 3.0`),\n", "2. then the raw (\"scaled\") value of `130.468` (`= 2.0 * 65.234`) is passed to the `ch02.v` parameter,\n", "3. then that value of `130.468` is validated against the limits of `ch02.v` (`0.0, 1.5e+3`),\n", "4. then the raw (\"scaled\") value of `1.30468` (`= 130.468 * 0.01`) is finally passed to the physical instrument.\n", "\n", "We also note that in the exponential represantations of numbers, it is required to provide `+` and `-` signs after `e`, e.g., we write `7.8334e+5` and `2.5e-23`. Refer to YAML file format specification for more information." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```yaml\n", "# Example YAML Station configuration file\n", "#\n", "# This file gets snapshotted and can be read back from the JSON \n", "# snapshot for every experiment run.\n", "#\n", "# All fields are optional unless explicitly mentioned otherwise.\n", "#\n", "# As in all YAML files a one-line notation can also be used\n", "# instead of nesting notation.\n", "#\n", "# The file starts with a list of loadable instruments instances,\n", "# i.e. there can be two entries for two instruments of the same\n", "# type if you want to specify two different use cases \n", "# e.g. \"dmm1-readout\" and \"dmm1-calibration\".\n", "#\n", "instruments:\n", " # Each instrument is specified by its name.\n", " # This name is what is looked up by the `load_instrument`\n", " # method of `Station`.\n", " # Simulated instruments can also be specified here, just put\n", " # the path to the simulation .yaml file as the value of the\n", " # \"init\"->\"visalib\" field (see below for an example of the\n", " # \"init\" section as well as an example of specifying \n", " # a simulated instrument).\n", " qdac:\n", " # Full import path to the python class of the instrument\n", " # driver\n", " type: qcodes.instrument_drivers.QDev.QDevQDac\n", " # Visa address of the instrument.\n", " # Note that this field can also be specified in the\n", " # \"init\" section (see below) but the address specified \n", " # here will overrule the address from the \"init\" section.\n", " # Essentially, specifying address here allows avoiding\n", " # the \"init\" section completely when address is the only\n", " # necessary argument that the instrument driver needs.\n", " # For obvious reasons, this field is required for VISA\n", " # instruments.\n", " address: ASRL4::INSTR\n", " # If an instrument with this name is already instantiated,\n", " # and this field is true, then the existing instrument \n", " # instance will be closed before instantiating this new one.\n", " # If this field is false, or left out, closing will not\n", " # happen.\n", " enable_forced_reconnect: true\n", " #\n", " # The \"init\" section specifies constant arguments that are\n", " # to be passed to the __init__ function of the instrument.\n", " # Note that it is the instrument's driver class that defines\n", " # the allowed arguments, for example, here \"update_currents\"\n", " # is an argument that is specific to \"QDac\" driver.\n", " init:\n", " terminator: \\n\n", " update_currents: false\n", " #\n", " # Setting up properties of parameters that already exist on\n", " # the instrument.\n", " parameters:\n", " # Each parameter is specified by its name from the\n", " # instrument driver class.\n", " # Note that \"dot: notation can be used to specify \n", " # parameters in (sub)channels and submodules.\n", " ch01.v:\n", " # If an alias is specified, the parameter becomes \n", " # accessible under another name, so that you can write\n", " # `qdac.cutter_gate(0.2)` instead of `qdac.ch01.v(0.2)`.\n", " # Note that the parameter instance does not get copied,\n", " # so that `(qdac.ch01.v is qdac.cutter_gate) == True`.\n", " alias: cutter_gate\n", " # Set new label.\n", " label: Cutter Gate Voltage\n", " # Set new unit.\n", " unit: mV\n", " # Set new scale.\n", " scale: 0.001\n", " # Set new post_delay.\n", " post_delay: 0\n", " # Set new inter_delay.\n", " inter_delay: 0.01\n", " # Set new step.\n", " step: 1e-4\n", " # If this field is given, and contains an array of two \n", " # numbers like here, then the parameter\n", " # gets a new `Numbers` validator with these values as\n", " # lower and upper limits, respectively (in this case, it\n", " # is `Numbers(-0.1, 0.1)`).\n", " limits: [-0.1, 0.1]\n", " # Set the parameter to this given initial value upon\n", " # instrument initialization.\n", " # Note that if the current value on the physical\n", " # instrument is different, the parameter will be ramped\n", " # with the delays and step specified in this file.\n", " initial_value: 0.01\n", " # In case this values equals to true, upon loading this\n", " # instrument from this configuration this parameter will\n", " # be appended to the list of parameters that are \n", " # displayed in QCoDeS `Monitor`.\n", " monitor: true\n", " # As in all YAML files a one-line notation can also be \n", " # used, here is an example.\n", " ch02.v: {scale: 0.01, limits: [0.0, 1.5e+3] , label: my label}\n", " ch04.v: {alias: Q1lplg1, monitor: true}\n", " #\n", " # This section allows to add new parameters to the\n", " # instrument instance which are based on existing parameters\n", " # of the instrument. This functionality is based on the use\n", " # of the `DelegateParameter` class.\n", " add_parameters:\n", " # For example, here we define a parameter that represents\n", " # magnetic field control. Setting and getting this \n", " # parameter will actually set/get a specific DAC channel.\n", " # So this new magnetic field parameter is playing a role\n", " # of a convenient proxy - it is much more convenient to \n", " # perform a measurement where \"Bx\" is changed in tesla as\n", " # opposed to where some channel of some DAC is changed in\n", " # volts and one has to clutter the measurement code with\n", " # the mess of conversion factors and more.\n", " # Every new parameter definition starts with a name of\n", " # the new parameter.\n", " Bx:\n", " # This field specifies the parameter which \"getter\" and \n", " # \"setter\" will be used when calling `get`/`set` on this\n", " # new parameter.\n", " # Required field.\n", " source: ch02.v\n", " # Set the label. Otherwise, the one of the source parameter\n", " # will be used.\n", " label: Magnetic Field X-Component\n", " # Set the unit. Otherwise, the one of the source parameter\n", " # will be used.\n", " unit: T\n", " # Other fields have the same purpose and behavior as for\n", " # the entries in the `add_parameter` section.\n", " scale: 65.243\n", " inter_delay: 0.001\n", " post_delay: 0.05\n", " step: 0.001\n", " limits: [0.0, 3.0]\n", " initial_value: 0.0\n", " # For the sake of example, we decided not to monitor this\n", " # parameter in QCoDeS `Monitor`.\n", " #monitor: true\n", " #\n", " # More example instruments, just for the sake of example.\n", " # Note that configuring simulated instruments also works,\n", " # see the use of 'visalib' argument field below\n", " dmm1:\n", " type: qcodes.instrument_drivers.agilent.Agilent34401A\n", " enable_forced_reconnect: true\n", " address: GPIB::1::65535::INSTR\n", " init:\n", " visalib: 'Agilent_34400A.yaml@sim'\n", " parameters:\n", " volt: {monitor: true}\n", " mock_dac:\n", " type: qcodes.instrument_drivers.mock_instruments.DummyInstrument\n", " enable_forced_reconnect: true\n", " init:\n", " # To pass an list of items use {}.\n", " gates: {\"ch1\", \"ch2\"}\n", " add_parameters:\n", " Bx: {source: ch1, label: Bx, unit: T,\n", " scale: 28, limits: [-1, 1], monitor: true}\n", " mock_dac2:\n", " type: qcodes.instrument_drivers.mock_instruments.DummyInstrument\n", " enable_forced_reconnect: true\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### QCoDeS configuration entries related to Station" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "QCoDeS configuration contains entries that are related to the `Station` and its YAML configuration file. Refer to [the description of the 'station' section in QCoDeS config](http://microsoft.github.io/Qcodes/user/configuration.html?highlight=station#default-config) for more specific information." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Using Station with YAML configuration files" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this section, we shall briefly describe the usage of `Station` with YAML configuration files. For more details, we refer to the docstrings of the methods of the `Station` class.\n", "\n", "A `Station` can be created with or without one or more YAML configuration files. With YAML configuration files, `Station` can be created by passing the file names (or file names with absolute path) to `Station`'s constructor. File names and location resolution also takes into account related entries in the `'station'` section of the QCoDeS configuration. We refer to corresponding documentation for more information.\n", "\n", "```python\n", "station = Station(config_file='qutech_station_25.yaml')\n", "```\n", "\n", "or\n", "\n", "```python\n", "station = Station(config_file=['example_1.yaml', 'example_2.yaml'])\n", "```\n", "\n", "Alternatively, `load_config_file` or `load_config_files` methods can be called on an already instantiated Station to load the configuration files.\n", "\n", "* `load_config_file` method can be used for loading 1 configuration file.\n", "* `load_config_files` method can be used for loading 1 or more configuration files. If multiple configuration files are provided, this method first merges them into one temporary file and then loads that into the station. Please note that, the merged file is temporary and not saved on the disk.\n", "\n", "```python\n", "station = Station()\n", "station.load_config_file=r'Q:\\\\station_data\\\\qutech_station_25.yaml'\n", "```\n", "\n", "or\n", "\n", "```python\n", "station = Station()\n", "station.load_config_files('example_1.yaml', 'example_2.yaml', 'example_3.yaml')\n", "```\n", "\n", "In case the configuration is already available as a YAML string, then that configuration can be loaded using `Station`'s `load_config` method. We refer to it's docstring and signature for more information.\n", "\n", "Once the YAML configuration is loaded, the `load_instrument` method of the `Station` can be used to instantiate a particular instrument that is described in the YAML configuration. Calling this method not only will return an instance of the instantiated instrument, but will also add it to the station object.\n", "\n", "For example, to instantiate the `qdac` instrument from the YAML configuration example from the previous section, it is sufficient to execute the following:\n", "```python\n", "loaded_qdac = station.load_instrument('qdac')\n", "```\n", "\n", "Note the `load_instrument`'s `revive_instance` argument, as well as `enable_force_reconnect` setting from the YAML configuration - these define what to do in case an instrument with the given name has already been instantiated in this python session.\n", "\n", "There is a more convenient way to load the instruments. Upon load of the YAML configuration, convenient `load_` methods are being generated on the `Station` object. Users can make use of tab-completion in their development environments to list what instruments can be loaded by the station object from the loaded YAML configuration. For example, loading the QDac instrument can, alternatively, be done via:\n", "```python\n", "conveniently_loaded_qdac = station.load_qdac()\n", "```\n", "\n", "We note that instruments are instantiated only when `load_*` methods are called. This means that loading a YAML configuration does NOT automatically instantiate anything.\n", "\n", "For the instruments that are loaded with the `load_*` methods, it is recommended to use `Station`'s `close_and_remove_instrument` method for closing and removing those from the station." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Setting up Visual Studio Code to work with station files\n", "VSCode can be conveniently used to work with station files. The [YAML extension by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml), allows to use JSON schemas to validate and auto-complete YAML files.\n", "\n", "\n", "![vscode](assets/vscode_integration.gif \"Auto-validation and -completion in VSCode\")\n", "\n", "\n", "**Quick setup steps**\n", "\n", "- install [YAML extension by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml)\n", "- run `qcodes.utils.installation.register_station_schema_with_vscode()`\n", "- make sure your station file has the extension `.station.yaml`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Setup details**\n", "\n", "To associate the qcodes station schema with `*.station.yaml` files you can either run" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "import os\n", "import sys\n", "\n", "from qcodes.utils.installation import register_station_schema_with_vscode\n", "\n", "# if statement for CI only\n", "if sys.platform == 'win32' and os.path.exists(os.path.expandvars(os.path.join('%APPDATA%', 'Code', 'User', 'settings.json'))):\n", " register_station_schema_with_vscode()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "or manually add the following to your vscode user/workspace settings:\n", "```\n", "\"yaml.schemas\": {\n", " \"file:///path/to/home/schemas/station.schema.json\": \"*.station.yaml\"\n", " }\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To get autocompletion for instruments from other packages run\n", "\n", "```python\n", "import qcodes\n", "import qcodes_contrib_drivers\n", "import zhinst\n", "qcodes.station.update_config_schema(qcodes_contrib_drivers, zhinst)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "where `qcodes_contrib_drivers` and `zhinst` are example packages that are available in the python environment where qcodes is running from.\n", "\n", "**Beware** that the generated schema file is not python-environment aware. To circumvent that, you can manually generate a schema file for each environment and register them as a workspace setting in vscode." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'C:\\\\Users\\\\jenielse\\\\AppData\\\\Roaming\\\\Code\\\\User\\\\settings.json'" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "os.path.expandvars(os.path.join('%APPDATA%', 'Code', 'User', 'settings.json'))" ] } ], "metadata": { "file_extension": ".py", "kernelspec": { "display_name": "qcodespip311", "language": "python", "name": "python3" }, "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.12.1" }, "mimetype": "text/x-python", "name": "python", "npconvert_exporter": "python", "pygments_lexer": "ipython3", "version": 3, "vscode": { "interpreter": { "hash": "7ac3e91929df5618782934af11c3fa566d637713ed5d04bf73eff1f535fb8e06" } }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }