-
Notifications
You must be signed in to change notification settings - Fork 347
V0 #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
V0 #2
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
aa23e00
giant commit to kick things off
alexcjohnson c70e65b
downgrade to pre-py3.5 syntax, but test it'll still work w/ 3.5 code
alexcjohnson 3f03759
now I get better linting too
alexcjohnson 7182286
flesh out storage and sweeps
alexcjohnson 8ef09d5
storage server doesn't need the storage class ahead of time
alexcjohnson dafde25
sweep delay time starts after setters, but before monitor
alexcjohnson 873d368
have sweep look for sweepvals.feedback methods
alexcjohnson 235fcd6
move SweepValues to its own file and break up base and subclasses
alexcjohnson 92ed873
simple adaptive sampling example
alexcjohnson 1d77429
check for running sweeps, allow queueing and halting
alexcjohnson 7337957
ignore ipynb helper files
alexcjohnson fd59ce5
fix some bugs in sweeping
alexcjohnson d8b1291
example notebook
alexcjohnson d8efb2f
revert math.inf and math.nan to python < 3.5
akhmerov c11185d
Create objects.md
alexcjohnson c866552
explode lambdas and closures for Windows multiprocessing
alexcjohnson 7f36c32
if you're reading a SweepStorage, it obviously needs to hold data!
alexcjohnson 36c0dbe
can't create functions or classes in the interpreter with spawn proce…
alexcjohnson File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,9 @@ | |
| __pycache__/ | ||
| *.py[cod] | ||
|
|
||
| # ipython extras | ||
| .ipynb_checkpoints/ | ||
|
|
||
| # C extensions | ||
| *.so | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "@python": 3.3, | ||
| "linters": { | ||
| "pep257": { | ||
| "excludes": ["*/test_*.py"] | ||
| } | ||
| } | ||
| } |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| # Object Hierarchy | ||
|
|
||
| ## Rough linkages: | ||
|
|
||
| In **bold** the containing class creates this object. | ||
| In *italics* the container just holds this object (or class) as a default for derivatives to use. | ||
| Normal text the container includes and uses this object | ||
|
|
||
| - Station | ||
| - BaseInstrument: IPInstrument, VisaInstrument, MockInstrument | ||
| - **Parameter** | ||
| - Validator: Anything, Strings, Numbers, Ints, Enum, MultiType | ||
| - **SweepValues**: SweepFixedValues, AdaptiveSweep | ||
| - Function | ||
| - Validator | ||
| - *SweepStorage class*: MergedCSVStorage, ? | ||
| - **Monitor** | ||
| - MeasurementSet | ||
| - StorageManager, Monitor | ||
| - *SweepStorage class* | ||
| - .sweep | ||
| - SweepValues | ||
| - **SweepStorage** | ||
| - **StorageManager** | ||
| - **StorageServer** | ||
| - SweepStorage | ||
| - Monitor | ||
|
|
||
| ## Station | ||
|
|
||
| A representation of the entire physical setup. | ||
|
|
||
| Lists all the connected `Instrument`s, keeps references to the `StorageManager` and | ||
| `Monitor` objects that are running, and holds various default settings, such as | ||
| the default `MeasurementSet` and the default `SweepStorage` class in use right now. | ||
|
|
||
| ## Instrument | ||
|
|
||
| A representation of one particular piece of hardware. | ||
|
|
||
| Lists all `Parameter`s and `Function`s this hardware is capable of, and handles the | ||
| underlying communication with the hardware. | ||
| `BaseInstrument` sets the common structure but real instruments will generally derive | ||
| from its subclasses, such as `IPInstrument` and `VisaInstrument`. There is also | ||
| `MockInstrument` for making simulated instruments, connected to a `Model` that mimics | ||
| a serialized communication channel with an apparatus. | ||
|
|
||
| ## Parameter | ||
|
|
||
| A representation of one particular state variable. | ||
|
|
||
| Most `Parameter`s are part of an `Instrument`, but you can also create `Parameter`s | ||
| that execute arbitrary functions, for example to combine several gate voltages in a | ||
| diagonal sweep. Parameters can have setters and/or getters (they must define at least | ||
| a setter OR a getter but do not need to define both) | ||
|
|
||
| `Parameter`s can be sliced to create a `SweepFixedValues` object. | ||
|
|
||
| ## Validator | ||
|
|
||
| Defines a set of valid inputs, and tests values against this set | ||
|
|
||
| Subclasses include `Anything`, `Strings`, `Numbers`, `Ints`, `Enum`, and `MultiType`, | ||
| each of which takes various arguments that restrict it further. | ||
|
|
||
| ## SweepValues | ||
|
|
||
| An iterator that provides values to a sweep, along with a setter for those values | ||
| connected to a `Parameter`. | ||
|
|
||
| Mostly the `SweepFixedValues` subclass is used (this is | ||
| flexible enough to iterate over any arbitrary collection of values, not necessarily | ||
| linearly spaced) but other subclasses can execute adaptive sampling techniques, based | ||
| on the `.feedback` method by which the sweep passes measured values back into the | ||
| `SweepValues` object. | ||
|
|
||
| ## Function | ||
|
|
||
| A representation of some action an `Instrument` can perform, which is not connected to | ||
| any particular state variable (eg calibrate, reset, identify) | ||
|
|
||
| ## StorageManager | ||
|
|
||
| The gateway to the separate storage process. | ||
|
|
||
| Sweeps and monitoring routines all pass their data through the `StorageManager` to the | ||
| `StorageServer`, which the `StorageManager` started and is running in a separate process. | ||
| Likewise, plotting & display routines query the `StorageServer` via the `StorageManager` | ||
| to retrieve current data. | ||
|
|
||
| ## StorageServer | ||
|
|
||
| Running in its own process, receives, holds, and returns current sweep and monitor data, | ||
| and writes it to disk (or other storage) | ||
|
|
||
| When a sweep is *not* running, the StorageServer also calls the monitor routine itself. | ||
| But when a sweep *is* running, the sweep calls the monitor so that it can avoid conflicts. | ||
| Also while a sweep is running, there are complementary `SweepStorage` objects in the sweep | ||
| and `StorageServer` processes - they are nearly identical objects, but are configured | ||
| differently. | ||
|
|
||
| ## Monitor | ||
|
|
||
| Measures all system parameters periodically and logs these measurements | ||
|
|
||
| Not yet implemented, so I don't know the details, but the plan is that during idle times | ||
| it measures parameters one-by-one, keeping track of how long it took to measure each one, | ||
| so that during sweeps it can be given a command "measure for no longer than X seconds" and | ||
| it will figure out which parameters it has time to measure, and which of those are most | ||
| important (primarily, I guess, the most important were last measured the longest time ago) | ||
|
|
||
| ## MeasurementSet | ||
|
|
||
| A list of parameters to measure while sweeping, along with the sweep methods. | ||
|
|
||
| Usually created from a `Station` and inheriting the `StorageManager`, `Monitor`, | ||
| and default `SweepStorage` class from it. | ||
|
|
||
| The `.sweep` method starts a sweep, which creates a `SweepStorage` object and, | ||
| if the sweep is to run in the background (the default), this method starts the | ||
| sweep process. | ||
|
|
||
| ## SweepStorage | ||
|
|
||
| Object that holds sweep data and knows how to read from and write to disk or | ||
| other long-term storage, as well as how to communicate with its clone on the | ||
| `StorageServer` if one exists. | ||
|
|
||
| A `SweepStorage` is created when a new sweep is started, and this one clones | ||
| itself on the `StorageServer`. But you can also create a `SweepStorage` from | ||
| a previous sweep, in which case it simply reads in the sweep and holds it | ||
| for plotting or analysis. | ||
|
|
||
| Subclasses (eg `MergedCSVStorage`, later perhaps `AzureStorage` etc) define | ||
| the connection with the particular long-term storage you are using. |
Empty file.
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| import asyncio | ||
|
|
||
| from qcodes.utils.metadata import Metadatable | ||
| from qcodes.utils.sync_async import wait_for_async | ||
| from qcodes.instrument.parameter import Parameter | ||
| from qcodes.instrument.function import Function | ||
|
|
||
|
|
||
| class BaseInstrument(Metadatable): | ||
| def __init__(self, name, **kwargs): | ||
| super().__init__(**kwargs) | ||
| self.functions = {} | ||
| self.parameters = {} | ||
|
|
||
| self.name = str(name) | ||
| # TODO: need a sync/async, multiprocessing-friendly lock | ||
| # should be based on multiprocessing.Lock (or RLock) | ||
| # but with a non-blocking option for async use | ||
| # anyway threading.Lock is unpicklable on Windows | ||
| # self.lock = threading.Lock() | ||
|
|
||
| def add_parameter(self, name, **kwargs): | ||
| ''' | ||
| binds one Parameter to this instrument. | ||
|
|
||
| instrument subclasses can call this repeatedly in their __init__ | ||
| for every real parameter of the instrument. | ||
|
|
||
| In this sense, parameters are the state variables of the instrument, | ||
| anything the user can set and/or get | ||
|
|
||
| `name` is how the Parameter will be stored within instrument.parameters | ||
| and also how you address it using the shortcut methods: | ||
| instrument.set(param_name, value) etc. | ||
|
|
||
| see Parameter for the list of kwargs | ||
| ''' | ||
| if name in self.parameters: | ||
| raise KeyError('Duplicate parameter name {}'.format(name)) | ||
| self.parameters[name] = Parameter(self, name, **kwargs) | ||
|
|
||
| def add_function(self, name, **kwargs): | ||
| ''' | ||
| binds one Function to this instrument. | ||
|
|
||
| instrument subclasses can call this repeatedly in their __init__ | ||
| for every real function of the instrument. | ||
|
|
||
| In this sense, functions are actions of the instrument, that typically | ||
| transcend any one parameter, such as reset, activate, or trigger. | ||
|
|
||
| `name` is how the Function will be stored within instrument.functions | ||
| and also how you address it using the shortcut methods: | ||
| instrument.call(func_name, *args) etc. | ||
|
|
||
| see Function for the list of kwargs | ||
| ''' | ||
| if name in self.functions: | ||
| raise KeyError('Duplicate function name {}'.format(name)) | ||
| self.functions[name] = Function(self, name, **kwargs) | ||
|
|
||
| def snapshot_base(self): | ||
| return { | ||
| 'parameters': dict((name, param.snapshot()) | ||
| for name, param in self.parameters.items()), | ||
| 'functions': dict((name, func.snapshot()) | ||
| for name, func in self.functions.items()) | ||
| } | ||
|
|
||
| ########################################################################## | ||
| # write, read, and ask are the interface to hardware # | ||
| # # | ||
| # at least one (sync or async) of each pair should be overridden by a # | ||
| # subclass. These defaults simply convert between sync and async if only # | ||
| # one is defined, but raise an error if neither is. # | ||
| ########################################################################## | ||
|
|
||
| def write(self, cmd): | ||
| wait_for_async(self.write_async, cmd) | ||
|
|
||
| @asyncio.coroutine | ||
| def write_async(self, cmd): | ||
| # check if the paired function is still from the base class (so we'd | ||
| # have a recursion loop) notice that we only have to do this in one | ||
| # of the pair, because the other will call this one. | ||
| if self.write.__func__ is BaseInstrument.write: | ||
| raise NotImplementedError( | ||
| 'instrument {} has no write method defined'.format(self.name)) | ||
| self.write(cmd) | ||
|
|
||
| def read(self): | ||
| return wait_for_async(self.read_async) | ||
|
|
||
| @asyncio.coroutine | ||
| def read_async(self): | ||
| if self.read.__func__ is BaseInstrument.read: | ||
| raise NotImplementedError( | ||
| 'instrument {} has no read method defined'.format(self.name)) | ||
| return self.read() | ||
|
|
||
| def ask(self, cmd): | ||
| return wait_for_async(self.ask_async, cmd) | ||
|
|
||
| @asyncio.coroutine | ||
| def ask_async(self, cmd): | ||
| if self.ask.__func__ is BaseInstrument.ask: | ||
| raise NotImplementedError( | ||
| 'instrument {} has no ask method defined'.format(self.name)) | ||
| return self.ask(cmd) | ||
|
|
||
| ########################################################################## | ||
| # shortcuts to parameters & setters & getters # | ||
| # # | ||
| # instrument['someparam'] === instrument.parameters['someparam'] # | ||
| # instrument.get('someparam') === instrument['someparam'].get() # | ||
| # etc... # | ||
| ########################################################################## | ||
|
|
||
| def __getitem__(self, key): | ||
| return self.parameters[key] | ||
|
|
||
| def set(self, param_name, value): | ||
| self.parameters[param_name].set(value) | ||
|
|
||
| @asyncio.coroutine | ||
| def set_async(self, param_name, value): | ||
| yield from self.parameters[param_name].set_async(value) | ||
|
|
||
| def get(self, param_name): | ||
| return self.parameters[param_name].get() | ||
|
|
||
| @asyncio.coroutine | ||
| def get_async(self, param_name): | ||
| return (yield from self.parameters[param_name].get_async()) | ||
|
|
||
| def call(self, func_name, *args): | ||
| return self.functions[func_name].call(*args) | ||
|
|
||
| @asyncio.coroutine | ||
| def call_async(self, func_name, *args): | ||
| return (yield from self.functions[func_name].call_async(*args)) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import asyncio | ||
|
|
||
| from qcodes.utils.metadata import Metadatable | ||
| from qcodes.utils.sync_async import syncable_command | ||
| from qcodes.utils.validators import Validator | ||
|
|
||
|
|
||
| class Function(Metadatable): | ||
| def __init__(self, instrument, name, call_cmd=None, async_call_cmd=None, | ||
| parameters=[], parse_function=None, **kwargs): | ||
| ''' | ||
| defines a function (with arbitrary parameters) that this instrument | ||
| can execute. | ||
|
|
||
| instrument: an instrument that handles this function | ||
| name: the local name of this parameter | ||
| call_cmd: command to execute on instrument | ||
| - a string (with positional fields to .format, "{}" or "{0}" etc) | ||
| - a function (with parameter count matching parameters list) | ||
| async_call_cmd: an async function to use for call_async, or for both | ||
| sync and async if call_cmd is missing or None. | ||
| parameters: list of Validator objects, | ||
| one for each parameter to the Function | ||
| parse_function: function to parse the return value of cmd, | ||
| may be a type casting function like int or float. | ||
| If None, will not wait for or read any response | ||
| ''' | ||
| super().__init__(**kwargs) | ||
|
|
||
| self._instrument = instrument | ||
| self.name = name | ||
|
|
||
| self._set_params(parameters) | ||
| self._set_call(call_cmd, async_call_cmd, parse_function) | ||
|
|
||
| def _set_params(self, parameters): | ||
| for param in parameters: | ||
| if not isinstance(param, Validator): | ||
| raise TypeError('all parameters must be Validator objects') | ||
| self._parameters = parameters | ||
| self._param_count = len(parameters) | ||
|
|
||
| def _set_call(self, call_cmd, async_call_cmd, parse_function): | ||
| ask_or_write = self._instrument.write | ||
| ask_or_write_async = self._instrument.write_async | ||
| if isinstance(call_cmd, str) and parse_function: | ||
| ask_or_write = self._instrument.ask | ||
| ask_or_write_async = self._instrument.ask_async | ||
|
|
||
| self._call, self._call_async = syncable_command( | ||
| self._param_count, call_cmd, async_call_cmd, | ||
| ask_or_write, ask_or_write_async, parse_function) | ||
|
|
||
| def validate(self, args): | ||
| if len(args) != self._param_count: | ||
| raise TypeError( | ||
| '{} called with {} parameters but requires {}'.format( | ||
| self.name, len(args), self._param_count)) | ||
|
|
||
| for i in range(self._param_count): | ||
| value = args[i] | ||
| param = self._parameters[i] | ||
| if not param.is_valid(value): | ||
| raise ValueError( | ||
| '{} is not a valid value for parameter {} of {}'.format( | ||
| value, i, self.name)) | ||
|
|
||
| def call(self, *args): | ||
| self.validate(args) | ||
| return self._call(*args) | ||
|
|
||
| @asyncio.coroutine | ||
| def call_async(self, *args): | ||
| self.validate(args) | ||
| return (yield from self._call_async(*args)) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm hoping that async commands will work well for most of the instrument drivers, but that will take some experimentation. Regardless, this set of wrappers will let people define whichever version they can for any given instrument, and sweeps can run either sync or async, making the best of what they find.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I started with the snazzy new python 3.5 syntax but ended up downgrading to 3.3 because anaconda (and my linter) are still going to take a while to play nicely with the new syntax.