{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Creating QCoDeS instrument drivers" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# most of the drivers only need a couple of these... moved all up here for clarity below\n", "from time import sleep, time\n", "import numpy as np\n", "import ctypes # only for DLL-based instrument\n", "\n", "import qcodes as qc\n", "from qcodes import (Instrument, VisaInstrument,\n", " ManualParameter, MultiParameter,\n", " validators as vals)\n", "from qcodes.instrument.channel import InstrumentChannel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Base Classes\n", "\n", "There are 3 available:\n", "- `VisaInstrument` - for most instruments that communicate over a text channel (ethernet, GPIB, serial, USB...) that do not have a custom DLL or other driver to manage low-level commands.\n", "- `IPInstrument` - a deprecated driver just for ethernet connections. Do not use this; use `VisaInstrument` instead.\n", "- `Instrument` - superclass of both `VisaInstrument` and `IPInstrument`, use this if you do not communicate over a text channel, for example:\n", " - PCI cards with their own DLLs\n", " - Instruments with only manual controls.\n", " \n", "If possible, please use a `VisaInstrument`, as this allows for the creation of a simulated instrument. (See the `Creating Simulated PyVISA Instruments` notebook) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Parameters and Channels\n", "\n", "Broadly speaking, a QCoDeS instrument driver is nothing but an object that holds a connection handle to the physical instrument and has some sub-objects that represent the state of the physical instrument. These sub-objects are the `Parameters`. Writing a driver basically boils down to adding a ton of `Parameters`.\n", "\n", "### What's a Parameter?\n", "\n", "A parameter represents a single value of a single feature of an instrument, e.g. the frequency of a function generator, the mode of a multimeter (resistance, current, or voltage), or the input impedance of an oscilloscope channel. Each `Parameter` can have the following attributes:\n", "\n", " * `name`, the name used internally by QCoDeS, e.g. 'input_impedance'\n", " * `label`, the label to use for plotting this parameter\n", " * `unit`, the physical unit. ALWAYS use SI units if a unit is applicable\n", " * `set_cmd`, the command to set the parameter. Either a SCPI string with a single '{}', or a function taking one argument (see examples below)\n", " * `get_cmd`, the command to get the parameter. Follows the same scheme as `set_cmd`\n", " * `vals`, a validator (from `qcodes.utils.validators`) to reject invalid values before they are sent to the instrument. Since there is no standard for how an instrument responds to an out-of-bound value (e.g. a 10 kHz function generator receiving 12e9 for its frequency), meaning that the user can expect anything from silent failure to the instrument breaking or suddenly outputting random noise, it is MUCH better to catch invalid values in software. Therefore, please provide a validator if at all possible.\n", " * `val_mapping`, a dictionary mapping human-readable values like 'High Impedance' to the instrument's internal representation like '372'. Not always needed. If supplied, a validator is automatically constructed.\n", " * `get_parser`, a parser of the raw return value. Since all VISA instruments return strings, but users usually want numbers, `int` and `float` are popular `get_parsers`\n", " * `docstring` A short string describing the function of the parameter\n", " \n", "Golden rule: if a `Parameter` is settable, it must always accept its own output as input.\n", "\n", "In most cases you will probably be adding parameters via the `add_parameter` method on the instrument class as shown in the example below. \n", "\n", "### Functions\n", "\n", "Similar to parameters QCoDeS instruments implement the concept of functions that can be added to the instrument via `add_function`. They are meant to implement simple actions on the instrument such as resetting it. However, the functions do not add any value over normal python methods in the driver Class and we are planning to eventually remove them from QCoDeS. **We therefore encourage any driver developer to not use function in any new driver**.\n", "\n", "### What's a Channel, then?\n", "\n", "A `Channel` is a submodule holding `Parameter`s. It sometimes makes sense to group `Parameter`s, for instance when an oscilloscope has four identical input channels. (see Keithley example below)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Logging\n", "Every QCoDeS module should have its own logger that is named with the name of the module. So to create a logger put a line at the top of the module like this:\n", "```\n", "log = logging.getLogger(__name__)\n", "```\n", "Use this logger only to log messages that are not originating from an `Instrument` instance. For messages from within an instrument instance use the `log` member of the `Instrument` class, e.g\n", "```\n", "self.log.info(f\"Could not connect at {address}\")\n", "```\n", "This way the instrument name will be prepended to the log message and the log messages can be filtered according to the instrument they originate from. See the example notebook of the logger module for more info ([offline](logging/logging_example.ipynb),[online](https://nbviewer.jupyter.org/github/QCoDeS/Qcodes/tree/master/docs/examples/logging/logging_example.ipynb)).\n", "\n", "When creating a nested `Instrument`, like e.g. something like the `InstrumentChannel` class, that has a `_parent` property, make sure that this property gets set before calling the `super().__init__` method, so that the full name of the instrument gets resolved correctly for the logging.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## VisaInstrument: Simple example\n", "The Weinschel 8320 driver is about as basic a driver as you can get. It only defines one parameter, \"attenuation\". All the comments here are my additions to describe what's happening." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "class Weinschel_8320(VisaInstrument):\n", " \"\"\"\n", " QCoDeS driver for the stepped attenuator\n", " Weinschel is formerly known as Aeroflex/Weinschel\n", " \"\"\"\n", "\n", " # all instrument constructors should accept **kwargs and pass them on to\n", " # super().__init__\n", " def __init__(self, name, address, **kwargs):\n", " # supplying the terminator means you don't need to remove it from every response\n", " super().__init__(name, address, terminator='\\r', **kwargs)\n", "\n", " self.add_parameter('attenuation', unit='dB',\n", " # the value you set will be inserted in this command with\n", " # regular python string substitution. This instrument wants\n", " # an integer zero-padded to 2 digits. For robustness, don't\n", " # assume you'll get an integer input though - try to allow\n", " # floats (as opposed to {:0=2d})\n", " set_cmd='ATTN ALL {:02.0f}',\n", " get_cmd='ATTN? 1',\n", " # setting any attenuation other than 0, 2, ... 60 will error.\n", " vals=vals.Enum(*np.arange(0, 60.1, 2).tolist()),\n", " # the return value of get() is a string, but we want to\n", " # turn it into a (float) number\n", " get_parser=float)\n", "\n", " # it's a good idea to call connect_message at the end of your constructor.\n", " # this calls the 'IDN' parameter that the base Instrument class creates for\n", " # every instrument (you can override the `get_idn` method if it doesn't work\n", " # in the standard VISA form for your instrument) which serves two purposes:\n", " # 1) verifies that you are connected to the instrument\n", " # 2) gets the ID info so it will be included with metadata snapshots later.\n", " self.connect_message()\n", "\n", "# instantiating and using this instrument (commented out because I can't actually do it!)\n", "#\n", "# from qcodes.instrument_drivers.weinschel.Weinschel_8320 import Weinschel_8320\n", "# weinschel = Weinschel_8320('w8320_1', 'TCPIP0::172.20.2.212::inst0::INSTR')\n", "# weinschel.attenuation(40)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## VisaInstrument: a more involved example\n", "\n", "The Keithley 2600 sourcemeter driver uses two channels. The actual driver is quite long, so here we show an abridged version that has:\n", "\n", "- A class defining a `Channel`. All the `Parameter`s of the `Channel` go here. \n", "- A nifty way to look up the model number, allowing it to be a driver for many different Keithley models\n", "\n", "\n" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "class KeithleyChannel(InstrumentChannel):\n", " \"\"\"\n", " Class to hold the two Keithley channels, i.e.\n", " SMUA and SMUB.\n", " \"\"\"\n", "\n", " def __init__(self, parent: Instrument, name: str, channel: str) -> None:\n", " \"\"\"\n", " Args:\n", " parent: The Instrument instance to which the channel is\n", " to be attached.\n", " name: The 'colloquial' name of the channel\n", " channel: The name used by the Keithley, i.e. either\n", " 'smua' or 'smub'\n", " \"\"\"\n", "\n", " if channel not in ['smua', 'smub']:\n", " raise ValueError('channel must be either \"smub\" or \"smua\"')\n", "\n", " super().__init__(parent, name)\n", " self.model = self._parent.model\n", " vranges = self._parent._vranges\n", " iranges = self._parent._iranges\n", "\n", " self.add_parameter('volt',\n", " get_cmd='{}.measure.v()'.format(channel),\n", " get_parser=float,\n", " set_cmd='{}.source.levelv={}'.format(channel,\n", " '{:.12f}'),\n", " # note that the set_cmd is either the following format string\n", " #'smua.source.levelv={:.12f}' or 'smub.source.levelv={:.12f}' \n", " # depending on the value of `channel`\n", " label='Voltage',\n", " unit='V')\n", "\n", " self.add_parameter('curr',\n", " get_cmd='{}.measure.i()'.format(channel),\n", " get_parser=float,\n", " set_cmd='{}.source.leveli={}'.format(channel,\n", " '{:.12f}'),\n", " label='Current',\n", " unit='A')\n", "\n", " self.add_parameter('mode',\n", " get_cmd='{}.source.func'.format(channel),\n", " get_parser=float,\n", " set_cmd='{}.source.func={}'.format(channel, '{:d}'),\n", " val_mapping={'current': 0, 'voltage': 1},\n", " docstring='Selects the output source.')\n", "\n", " self.add_parameter('output',\n", " get_cmd='{}.source.output'.format(channel),\n", " get_parser=float,\n", " set_cmd='{}.source.output={}'.format(channel,\n", " '{:d}'),\n", " val_mapping={'on': 1, 'off': 0})\n", "\n", " self.add_parameter('nplc',\n", " label='Number of power line cycles',\n", " set_cmd='{}.measure.nplc={}'.format(channel,\n", " '{:.4f}'),\n", " get_cmd='{}.measure.nplc'.format(channel),\n", " get_parser=float,\n", " vals=vals.Numbers(0.001, 25))\n", " \n", "\n", " self.channel = channel\n", " \n", " \n", "class Keithley_2600(VisaInstrument):\n", " \"\"\"\n", " This is the qcodes driver for the Keithley_2600 Source-Meter series,\n", " tested with Keithley_2614B\n", " \"\"\"\n", " def __init__(self, name: str, address: str, **kwargs) -> None:\n", " \"\"\"\n", " Args:\n", " name: Name to use internally in QCoDeS\n", " address: VISA ressource address\n", " \"\"\"\n", " super().__init__(name, address, terminator='\\n', **kwargs)\n", "\n", " model = self.ask('localnode.model')\n", "\n", " knownmodels = ['2601B', '2602B', '2604B', '2611B', '2612B',\n", " '2614B', '2635B', '2636B']\n", " if model not in knownmodels:\n", " kmstring = ('{}, '*(len(knownmodels)-1)).format(*knownmodels[:-1])\n", " kmstring += 'and {}.'.format(knownmodels[-1])\n", " raise ValueError('Unknown model. Known model are: ' + kmstring)\n", " \n", " # Add the channel to the instrument\n", " for ch in ['a', 'b']:\n", " ch_name = 'smu{}'.format(ch)\n", " channel = KeithleyChannel(self, ch_name, ch_name)\n", " self.add_submodule(ch_name, channel)\n", "\n", " # display parameter\n", " # Parameters NOT specific to a channel still belong on the Instrument object\n", " # In this case, the Parameter controls the text on the display\n", " self.add_parameter('display_settext',\n", " set_cmd=self._display_settext,\n", " vals=vals.Strings())\n", "\n", " self.connect_message()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## VisaInstruments: Simulating the instrument\n", "\n", "As mentioned above, drivers subclassing `VisaInstrument` have the nice property that they may be connected to a simulated version of the physical instrument. See the `Creating Simulated PyVISA Instruments` notebook for more information. If you are writing a `VisaInstrument` driver, please consider spending 20 minutes to also add a simulated instrument and a test." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## DLL-based instruments\n", "The Alazar cards use their own DLL. C interfaces tend to need a lot of boilerplate, so I'm not going to include it all. The key is: use `Instrument` directly, load the DLL, and have parameters interact with it." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "class AlazarTech_ATS(Instrument):\n", " dll_path = 'C:\\\\WINDOWS\\\\System32\\\\ATSApi'\n", " def __init__(self, name, system_id=1, board_id=1, dll_path=None, **kwargs):\n", " super().__init__(name, **kwargs)\n", " \n", " # connect to the DLL\n", " self._ATS_dll = ctypes.cdll.LoadLibrary(dll_path or self.dll_path)\n", "\n", " self._handle = self._ATS_dll.AlazarGetBoardBySystemID(system_id,\n", " board_id)\n", " if not self._handle:\n", " raise Exception('AlazarTech_ATS not found at '\n", " 'system {}, board {}'.format(system_id, board_id))\n", "\n", " self.buffer_list = []\n", " \n", " # the Alazar driver includes its own parameter class to hold values\n", " # until later config is called, and warn if you try to read a value\n", " # that hasn't been sent to config.\n", " self.add_parameter(name='clock_source',\n", " parameter_class=AlazarParameter,\n", " label='Clock Source',\n", " unit=None,\n", " value='INTERNAL_CLOCK',\n", " byte_to_value_dict={1: 'INTERNAL_CLOCK',\n", " 4: 'SLOW_EXTERNAL_CLOCK',\n", " 5: 'EXTERNAL_CLOCK_AC',\n", " 7: 'EXTERNAL_CLOCK_10_MHz_REF'})\n", " \n", " # etc..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manual instruments\n", "A totally manual instrument (like the ithaco 1211) will contain only `ManualParameter`s. Some instruments may have a mix of manual and standard parameters. Here we also define a new `CurrentParameter` class that uses the ithaco parameters to convert a measured voltage to a current. When subclassing a parameter class (`Parameter`, `MultiParameter`, ...), the functions for setting and getting should be called `get_raw` and `set_raw`, respectively." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "class CurrentParameter(MultiParameter):\n", " \"\"\"\n", " Current measurement via an Ithaco preamp and a measured voltage.\n", "\n", " To be used when you feed a current into the Ithaco, send the Ithaco's\n", " output voltage to a lockin or other voltage amplifier, and you have\n", " the voltage reading from that amplifier as a qcodes parameter.\n", "\n", " ``CurrentParameter.get()`` returns ``(voltage_raw, current)``\n", "\n", " Args:\n", " measured_param (Parameter): a gettable parameter returning the\n", " voltage read from the Ithaco output.\n", "\n", " c_amp_ins (Ithaco_1211): an Ithaco instance where you manually\n", " maintain the present settings of the real Ithaco amp.\n", "\n", " name (str): the name of the current output. Default 'curr'.\n", " Also used as the name of the whole parameter.\n", " \"\"\"\n", " def __init__(self, measured_param, c_amp_ins, name='curr'):\n", " p_name = measured_param.name\n", "\n", " p_label = getattr(measured_param, 'label', None)\n", " p_unit = getattr(measured_param, 'units', None)\n", "\n", " super().__init__(name=name, names=(p_name+'_raw', name),\n", " shapes=((), ()),\n", " labels=(p_label, 'Current'),\n", " units=(p_unit, 'A'))\n", "\n", " self._measured_param = measured_param\n", " self._instrument = c_amp_ins\n", "\n", " def get_raw(self):\n", " volt = self._measured_param.get()\n", " current = (self._instrument.sens.get() *\n", " self._instrument.sens_factor.get()) * volt\n", "\n", " if self._instrument.invert.get():\n", " current *= -1\n", "\n", " value = (volt, current)\n", " self._save_val(value)\n", " return value\n", "\n", "\n", "class Ithaco_1211(Instrument):\n", " \"\"\"\n", " This is the qcodes driver for the Ithaco 1211 Current-preamplifier.\n", "\n", " This is a virtual driver only and will not talk to your instrument.\n", " \"\"\"\n", " def __init__(self, name, **kwargs):\n", " super().__init__(name, **kwargs)\n", "\n", " # ManualParameter has an \"initial_value\" kwarg, but if you use this\n", " # you must be careful to check that it's correct before relying on it.\n", " # if you don't set initial_value, it will start out as None.\n", " self.add_parameter('sens',\n", " parameter_class=ManualParameter,\n", " initial_value=1e-8,\n", " label='Sensitivity',\n", " units='A/V',\n", " vals=vals.Enum(1e-11, 1e-10, 1e-09, 1e-08, 1e-07,\n", " 1e-06, 1e-05, 1e-4, 1e-3))\n", "\n", " self.add_parameter('invert',\n", " parameter_class=ManualParameter,\n", " initial_value=True,\n", " label='Inverted output',\n", " vals=vals.Bool())\n", "\n", " self.add_parameter('sens_factor',\n", " parameter_class=ManualParameter,\n", " initial_value=1,\n", " label='Sensitivity factor',\n", " units=None,\n", " vals=vals.Enum(0.1, 1, 10))\n", "\n", " self.add_parameter('suppression',\n", " parameter_class=ManualParameter,\n", " initial_value=1e-7,\n", " label='Suppression',\n", " units='A',\n", " vals=vals.Enum(1e-10, 1e-09, 1e-08, 1e-07, 1e-06,\n", " 1e-05, 1e-4, 1e-3))\n", "\n", " self.add_parameter('risetime',\n", " parameter_class=ManualParameter,\n", " initial_value=0.3,\n", " label='Rise Time',\n", " units='msec',\n", " vals=vals.Enum(0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30,\n", " 100, 300, 1000))\n", "\n", " def get_idn(self):\n", " return {'vendor': 'Ithaco (DL Instruments)', 'model': '1211',\n", " 'serial': None, 'firmware': None}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Custom Parameter classes\n", "\n", "When you call:\n", "```\n", "self.add_parameter(name, **kwargs)\n", "```\n", "you create a `Parameter`. But with the `parameter_class` kwarg you can invoke any class you want:\n", "```\n", "self.add_parameter(name, parameter_class=OtherClass, **kwargs)\n", "```\n", "\n", "- `Parameter` handles most common instrument settings and measurements.\n", " - Accepts get and/or set commands as either strings for the instrument's `ask` and `write` methods, or functions/methods. The set and get commands may also be set to `False` and `None`. `False` corresponds to \"no get/set method available\" (example: the reading of a voltmeter is not settable, so we set `set_cmd=False`). `None` corresponds to a manually updated parameter (example: an instrument with no remote interface).\n", " - Has options for translating between instrument codes and more meaningful data values\n", " - Supports software-controlled ramping\n", "- Any other parameter class may be used in `add_parameter`, if it accepts `name` and `instrument` as constructor kwargs. Generally these should subclasses of `Parameter`, `ParameterWithSetpoints`, `ArrayParameter`, or `MultiParameter`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`ParameterWithSetpoints` is specifically designed to handle the situations where the instrument returns an array of data with assosiated setpoints. An example of how to use it can be found in the notebook [Simple Example of ParameterWithSetpoints](writing_drivers/Simple-Example-of-ParameterWithSetpoints.ipynb)\n", "\n", "`ArrayParameter` is an older alternative that does the same thing. However, it is significantly less flexible and much harder to use correct but used in a significant number of drivers. **It is not recommended for any new driver.**\n", "\n", "`MultiParameter` is designed to for the situation where multiple different types of data is captured from the same instrument command." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### On/Off parameters\n", "\n", "Frequently, an instrument has parameters which can be expressed in terms of \"something is on or off\". Moreover, usually it is not easy to translate the lingo of the instrument to something that can have simply the value of `True` or `False` (which are typical in software). Even further, it may be difficult to find consensus between users on a convention: is it `on`/`off`, or `ON`/`OFF`, or python `True`/`False`, or `1`/`0`, or else?\n", "\n", "This case becomes even more complex if the instrument's API (say, corresponding VISA command) uses unexpected values for such a parameter, for example, turning an output \"on\" corresponds to a VISA command `DEV:CH:BLOCK 0` which means \"set blocking of the channel to 0 where 0 has the meaning of the boolean value False, and alltogether this command actually enables the output on this channel\".\n", "\n", "This results in inconsistency among instrument drivers where for some instrument, say, a `display` parameter has 'on'/'off' values for input, while for a different instrument a similar `display` parameter has `'ON'`/`'OFF'` values or `1`/`0`.\n", "\n", "Note that this particular example of a `display` parameter is trivial because the ambiguity and inconsistency for \"this kind\" of parameters can be solved by having the name of the parameter be `display_enabled` and the allowed input values to be python `bool` `True`/`False`.\n", "\n", "Anyway, when defining parameters where the solution does not come trivially, please, consider setting `val_mapping` of a parameter to the output of `create_on_off_val_mapping(on_val=<>, off_val=<>)` function from `qcodes.utils.helpers` package. The function takes care of creating a `val_mapping` dictionary that maps given instrument-side values of `on_val` and `off_val` to `True`/`False`, `'ON'`/`'OFF'`, `'on'`/`'off'`, and other commonly used ones. Note that when getting a value of such a parameter, the user will not get `'ON'` or `'off'` or `'oFF'` - instead, `True`/`False` will be returned." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dynamically adding and removing parameters\n", "\n", "Sometimes when conditions change (for example, the mode of operation of the instrument is changed from current to voltage measurement) you want different parameters to be available.\n", "\n", "To delete existing parameters:\n", "```\n", "del self.parameters[name_to_delete]\n", "```\n", "And to add more, do the same thing as you did initially:\n", "```\n", "self.add_parameter(new_name, **kwargs)\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Organization\n", "\n", "Your drivers do not need to be part of QCoDeS in order to use them with QCoDeS, but we strongly encourage you to contribute them to the project. That way we prevent duplication of effort, and you will likely get help making the driver better, with more features and better code.\n", "\n", "Make one driver per module, inside a directory named for the company (or institution), within the `instrument_drivers` directory, following the convention:\n", "\n", "`instrument_drivers..._`\n", "- example: `instrument_drivers.AlazarTech.ATS9870.AlazarTech_ATS9870`\n", "\n", "Although the class name can be just the model if it is globally unambiguous. For example:\n", "- example: `instrument_drivers.stanford_research.SR560.SR560`\n", "\n", "And note that due to mergers, some drivers may not be in the folder you expect:\n", "- example: `instrument_drivers.tektronix.Keithley_2600.Keithley_2600_Channels`" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true }, "source": [ "## Documentation\n", "\n", "A driver should be documented in the following ways. \n", "\n", "* All methods of the driver class should be documented including the arguments and return type of the function. QCoDeS docstrings uses the [Google style](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)\n", "* Parameters should have a meaningful docstring if the usage of the parameter is not obvious.\n", "* An IPython notebook that documents the usage of the instrument should be added to `docs/example/driver_examples/Qcodes example with .ipynb` Note that we execute notebooks by default as part of the docs build. That is usually not possible for instrument examples so we want to disable the execution. This can be done as described [here](https://nbsphinx.readthedocs.io/en/latest/never-execute.html) editing the notebooks metadata accessible via `Edit/Edit Notebook Metadata` from the notebook interface.\n", "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.6.8" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 1 }