{ "cells": [ { "cell_type": "markdown", "id": "89844513", "metadata": {}, "source": [ "# Basic Example" ] }, { "cell_type": "markdown", "id": "8332150b", "metadata": {}, "source": [ "The following contains the essential building blocks to design your own experiment. Mock experiments made using these building blocks can be found in the Demo Notebooks section. \n", "\n", "If you downloaded the pyscan directory from git, you can even run the demo_notebooks yourself in Jupyter." ] }, { "cell_type": "markdown", "id": "15d723e0-e7dc-4e43-91e8-b5e71aa2ab2d", "metadata": {}, "source": [ "## 1. Import libraries\n", "\n", "First you need to import libraries. As a convention, we `import pyscan as ps` in all of our demo notebooks.\n", "\n", "The output tells you which drivers were not imported due to specific requirements not being met. This is not a problem unless you are trying to use one of those instruments. If your instrument doesn't show up on this list, that means it was imported successfully." ] }, { "cell_type": "code", "execution_count": 1, "id": "0dddab17-f2cb-4f73-bacc-343c308ed0bb", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Could not load Keysight SD1\n", "Could not load Keysight SD1\n", "pylablib not found, AttocubeANC350 not loaded\n", "Basler Camera software not found, BaserCamera not loaded\n", "Helios Camera not installed\n", "msl not installed, Thorlabs BSC203 driver not loaded\n", "seabreeze module not found, Ocean Optics not imported\n", "Failed to load spinapi library.\n", "spinapi is not installed, PulseBlaster driver not loaded.\n", "Thorlabs Kinesis not found, ThorlabsBSC203 not loaded\n", "Thorlabs Kinesis not found, ThorlabsBPC303 not loaded\n", "Thorlabs Kinesis not found, ThorlabsMFF101 not loaded\n" ] } ], "source": [ "import pyscan as ps" ] }, { "cell_type": "markdown", "id": "31eb3ef9-1ccc-4e77-b0e0-e563395478bb", "metadata": {}, "source": [ "## 2. Setup devices\n", "\n", "Next, create an instance of `ps.ItemAttribute` in a variable called `devices`. This is where you will store instances of driver classes which connect to your instruments. \n", "\n", "Remember, an `ItemAttribute` class is just a class which has methods that mimic a dictionary. You can name your devices whatever you like." ] }, { "cell_type": "code", "execution_count": 2, "id": "6b3f60e7-d2db-46bd-886f-762029d2ce1c", "metadata": { "tags": [] }, "outputs": [], "source": [ "devices = ps.ItemAttribute()" ] }, { "cell_type": "markdown", "id": "e187dbf7-2ae8-480f-bebd-585c5bd0374c", "metadata": {}, "source": [ "The first device we will add is a dummy driver called `TestVoltage`. It has basic functionality to show you what a driver class does without actually requiring you to connect to an instrument.\n", "\n", "The TestVoltage instance will allow you to set the `voltage` property as well as query it." ] }, { "cell_type": "code", "execution_count": 3, "id": "83580337-a51f-425a-bf7b-b7dec1fc1f8b", "metadata": { "tags": [] }, "outputs": [], "source": [ "devices.voltagesource = ps.TestVoltage()" ] }, { "cell_type": "markdown", "id": "d2e95cd6-5897-41f9-8490-bae6aa2cf030", "metadata": { "tags": [] }, "source": [ "### 2.1. Required parameters for certain drivers" ] }, { "cell_type": "markdown", "id": "fe7e74ff-45dd-4348-8e50-5ea0d82ceb62", "metadata": {}, "source": [ "Some driver classes require parameters such as serial number or the VISA or GPIB address. Check the docs for that driver to find out.\n", "\n", "If you're not sure what the GPIB address is, you can get a list of connected instruments using the pyvisa library." ] }, { "cell_type": "markdown", "id": "258302ca-261f-473e-9470-4571e79958ba", "metadata": {}, "source": [ "### 2.2. See what devices have already been setup" ] }, { "cell_type": "code", "execution_count": 4, "id": "84667402-2c41-400e-a6b0-c9154efcf55e", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "dict_items([('voltagesource', )])" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "devices.items()" ] }, { "cell_type": "markdown", "id": "8fd08199-7aa5-40e8-a69c-8eb128cde71d", "metadata": { "tags": [] }, "source": [ "### 2.3. Test the device to ensure that it is working\n", "\n", "It's always good practice to both write to the instrument and query it to ensure your connection to the instrument is successful and working as expected.\n", "\n", "In the case of `TestVoltage`, we can read in the documentation that it has a property called `voltage`, so let's test that." ] }, { "cell_type": "code", "execution_count": 5, "id": "c2b6462a-88dd-42a7-aba2-d36b7c2ea193", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "0.0" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "devices.voltagesource.voltage" ] }, { "cell_type": "code", "execution_count": 6, "id": "efe8db8e-d047-4aa2-b502-4a246feb2e42", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "5.0" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "devices.voltagesource.voltage = 5\n", "devices.voltagesource.voltage" ] }, { "cell_type": "markdown", "id": "25e1d3c9-9797-4d45-ab25-1c9d8409f73c", "metadata": {}, "source": [ "Looks good!" ] }, { "cell_type": "markdown", "id": "9d17dd2f-eb3d-427a-beea-168ce96dbd0d", "metadata": {}, "source": [ "## 3. Define a measure function" ] }, { "cell_type": "markdown", "id": "5facedfc-c7b3-4c9a-8a40-f295312f2100", "metadata": {}, "source": [ "A `measure_function` is a required attribute of a `RunInfo` instance, which in turn is a required parameter when you create an instance of `Experiment`. \n", "\n", "This `measure_function` is run after every iteration of scans, which define the independent variables of your experiment.\n", "\n", "The `measure_function` is a custom function you create, and its only requirements are that:\n", "\n", "1. It takes an `Experiment` object as its only parameter\n", "2. It returns an `ItemAttribute` containing data attributes (unlimited in number and named anything you like) which represent a single observation.\n", "\n", "Note that `Experiment` saves its `runinfo` and `devices` parameters as attributes upon initialization, thus these can also be accessed from within the measure function.\n", "\n", "A very simple measure_function is defined below:" ] }, { "cell_type": "code", "execution_count": 7, "id": "83542021-078a-4949-b061-b8152c0c4834", "metadata": { "tags": [] }, "outputs": [], "source": [ "def get_voltage(expt):\n", " devices = expt.devices\n", " runinfo = expt.runinfo\n", " \n", " # setup a new ItemAttribute instance in which to store the collected data\n", " data = ps.ItemAttribute()\n", " \n", " # collect a measurement and store it in the data object\n", " data.voltage = devices.voltagesource.voltage\n", " \n", " return data" ] }, { "cell_type": "markdown", "id": "5f0b8372-bf0e-4f26-ace6-51d6afb7026f", "metadata": {}, "source": [ "## 4. Setup a RunInfo instance" ] }, { "cell_type": "markdown", "id": "16e46072-7ba6-4d1a-b74f-ca86bba66734", "metadata": {}, "source": [ " Next, we will setup a `RunInfo` isntance and define scans." ] }, { "cell_type": "code", "execution_count": 8, "id": "dba2a801-2842-49f3-8495-27b06dac42bd", "metadata": { "tags": [] }, "outputs": [], "source": [ "runinfo = ps.RunInfo()" ] }, { "cell_type": "markdown", "id": "3522f662-6c2f-4ad9-9b95-060aebd1cbe5", "metadata": {}, "source": [ "A `RunInfo` instance contains a number of default attributes, but here we will focus on the essentials. You must define the `measure_function` as well as any scans you want, each representing independent variables. You may define between 1 and 4 scans, labelled as `scan0`, `scan1`, `scan2`, and `scan3`.\n", "\n", "Just for education, let's see what attributes exist inside a runinfo:" ] }, { "cell_type": "code", "execution_count": 9, "id": "93fc6ba0-a4c7-40a4-ab89-053a561b75ad", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "dict_keys(['scan0', 'scan1', 'scan2', 'scan3', 'static', 'measured', 'measure_function', 'trigger_function', 'initial_pause', 'average_d', 'verbose'])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "runinfo.keys()" ] }, { "cell_type": "markdown", "id": "2299fa93-d29f-45a5-bea0-dde7ae61583d", "metadata": {}, "source": [ "### 4.1. Setup the measure_function" ] }, { "cell_type": "markdown", "id": "af24e0cc-5e4c-49f3-a357-ef9f7bbb5a72", "metadata": {}, "source": [ "Now let's set the measure function to the `get_voltage` function we already defined. **Do not** put parentheses after this function, since we do not want to call the function - rather, we are saving the funciton object itself inside the runinfo." ] }, { "cell_type": "code", "execution_count": 10, "id": "3c2cf5a0-1b95-4eb4-b1dc-e4b203131709", "metadata": { "tags": [] }, "outputs": [], "source": [ "runinfo.measure_function = get_voltage" ] }, { "cell_type": "markdown", "id": "6717e898-c8ba-4bbe-a1a7-468f1f6951c5", "metadata": {}, "source": [ "### 4.2. Setup the scans" ] }, { "cell_type": "markdown", "id": "aa40beff-2d5c-458f-ba54-d85591d8d51a", "metadata": {}, "source": [ "The simplest type of scan is a `PropertyScan`. It takes three arguments: \n", "\n", "**PropertyScan**(input_dict, prop, dt=0)\n", "\n", "where `input_dict` is a dictionary containing key-value pairs representing \"device name strings and arrays of values representing the new prop values you want to set for each device.\" The most common and simplest scenario is to change a single device within a `PropertyScan`, thus the `input_dict` will contain only one key-value pair.\n", "\n", "The `prop` is the name of the property on the device that will be iterated through - in this case, we will use `voltage`. The available properties for a particular device are only known by reading the docs for that driver class.\n", "\n", "The `dt` is the delay time in seconds after one iteration of the scan, and before the measure_function is called. If unset, it defaults to 0s. Sometimes experiments will operate more optimally with a longer dt, for example, a stage may take a certain fraction of a second to reach its destination after you set its position." ] }, { "cell_type": "code", "execution_count": 11, "id": "a8a35e6f-58c6-446e-b389-e3d2b4b39d89", "metadata": { "tags": [] }, "outputs": [], "source": [ "runinfo.scan0 = ps.PropertyScan({'voltagesource': [0,1,2,3,4,5]}, 'voltage', dt=0.01)" ] }, { "cell_type": "markdown", "id": "f8c387cb-b058-451d-8133-db585221d12f", "metadata": {}, "source": [ "You may also use the built-in `drange(start, step, stop)` function to create that same array. " ] }, { "cell_type": "code", "execution_count": 12, "id": "2da44a35-5159-4d6a-9731-18c80cd464c6", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "array([0., 1., 2., 3., 4., 5.])" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ps.drange(0, 1, 5)" ] }, { "cell_type": "markdown", "id": "8f6703eb-1414-44df-9fdb-b1d21852faf7", "metadata": {}, "source": [ "The exact value of the `stop` parameter is always included, even if the steps don't fit perfectly into the range." ] }, { "cell_type": "code", "execution_count": 13, "id": "4ef26173-0d2a-488e-a463-b4d33f3fa732", "metadata": { "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[0.0, 2.0, 4.0, 5]" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "ps.drange(0, 2, 5)" ] }, { "cell_type": "markdown", "id": "54139581-40b5-4d1c-b928-8c76e50580d1", "metadata": {}, "source": [ "Thus an alternate way that `scan0` could have been defined is:" ] }, { "cell_type": "code", "execution_count": 14, "id": "57f0c5ac-604c-404e-9822-f7ce06de13cd", "metadata": { "tags": [] }, "outputs": [], "source": [ "runinfo.scan0 = ps.PropertyScan({'voltagesource': ps.drange(0, 1, 5)}, 'voltage', dt=1)" ] }, { "cell_type": "markdown", "id": "2bf98886-1ccc-490d-a3f3-d00c4e4cf38f", "metadata": {}, "source": [ "We've set the dt to be unreasonably large (1s) just so that we will be able to watch its progress in the live_plot for demonstration purposes." ] }, { "cell_type": "markdown", "id": "75c7ca91-821c-4152-bda7-88d73c488670", "metadata": {}, "source": [ "## 5. Setup & run Experiment" ] }, { "cell_type": "markdown", "id": "4da3f7d5-5c9e-494d-8446-a83df99f545c", "metadata": {}, "source": [ "Setting up the `Experiment` is now simple. While there are a few types of experiments that all inherit from the `AbstractExperiment` class such as `SparseExperiment`, which does not collect data for every single point defined by the scans, by far the class that will be used for most purposes is the `Experiment` class.\n", "\n", "To setup an Experiment, simply input the `runinfo` and `devices` objects which we previously defined." ] }, { "cell_type": "code", "execution_count": 15, "id": "cca0ffe3-0ce4-4839-8969-7837c07ea9bd", "metadata": { "tags": [] }, "outputs": [], "source": [ "expt = ps.Experiment(runinfo, devices)" ] }, { "cell_type": "markdown", "id": "4059c4de-3b15-4008-9c3b-774509ab94ad", "metadata": {}, "source": [ "Now we run the experiment.\n", "`Experiment` has two run methods: `run()` and `start_thread()`. `start_thread()` calls `run()` in a separate thread, so it is non-blocking. We will use `start_thread()` as that enables us to also use live plotting." ] }, { "cell_type": "code", "execution_count": 16, "id": "cf087058-f870-4331-a01c-2bc82499c0df", "metadata": { "tags": [] }, "outputs": [], "source": [ "expt.start_thread()" ] }, { "cell_type": "code", "execution_count": 17, "id": "e441950e-d2a8-485d-a0f2-ee87d9daf639", "metadata": { "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Stopping Experiment\n" ] } ], "source": [ "expt.stop()" ] }, { "cell_type": "code", "execution_count": null, "id": "1bfbf563-4bb8-446b-ad8e-8643589505b4", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "pyscan", "language": "python", "name": "pyscan" }, "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.11.4" } }, "nbformat": 4, "nbformat_minor": 5 }