-
Notifications
You must be signed in to change notification settings - Fork 315
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added environment.yml file * Added requirements to environment.yml * Added requirements to environment.yml * Works under linux now * 1) added american instrument driver from the main qcodes repo 2) added a mock IP module to mock IP instruments 3) added a debug module to debug the AMI430 instrument * the mock IP instrument now remembers the set point * added test for spherical coordinates. Making test for cylindrical coordinates. Work in progress * Added a module for handy converting between spherical, cartesian and cylindrical coodinates. By keeping a prepresnetation of the vector in all three systems, there is less risk of loss of information so that e.g. the field strength can be ramped up given previous supplied phi and theta. The spherical and cylindrical tests seem to work. * additional work * 1) The driver is adhering to the qcodes specs now 2) Made proper tests * 1) The driver is adhering to the qcodes specs now 2) Made proper tests * 1) Made tests to ensure that the set points correctly reach the instruments 2) Made test to verify that the correct error is raised if we intentionally exceed the field limits * Added a test to verify that ramp downs are performed first * Added the ability to define more complex regions for safe set points. Tests to verify this are also included. * Re-wrote the instrument mocker so we have a cleaner design. * changing how mocking works * Mocking no longer requires sockets and threading. Major rewrite of the mocking mechanism * Mocking no longer requires sockets and threading. Major rewrite of the mocking mechanism * further clean up of the mocking mechanism. * Solved a bug which raised a value error when asking for spherical coordinates. Added tests to verify if we can get spherical coordinates. Also, when getting single field values, return a single float instead of an array of length one * 1) Added tests for the field vector 2) Added test to verify that the correct warnings are raised if we change the default maximum ramp rate 3) Added test to verify that an error is raised if the ramp rate larger then the maximum * Added a notebook showing the results of the testing of the new AMI430 driver * * Processed Jens comments, except changing the doc string format and pep8 conventions. Will do that tomorrow * Processed the rest of Jens comments
- Loading branch information
1 parent
acb3b6e
commit 5acbeeb
Showing
13 changed files
with
1,784 additions
and
472 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
454 changes: 454 additions & 0 deletions
454
docs/examples/driver_examples/Qcodes example with AMI430.ipynb
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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,20 @@ | ||
name: qcodes | ||
channels: | ||
- defaults | ||
- conda-forge | ||
dependencies: | ||
- h5py=2.6.0 | ||
- matplotlib=2.0.2 | ||
- pyqtgraph | ||
- python=3.6 | ||
- jsonschema | ||
- pyvisa | ||
- QtPy=1.2.0 | ||
- sip | ||
- pyqt=5.6.0 | ||
- numpy | ||
- jupyter=1.0.0 | ||
- typing | ||
- hypothesis | ||
- pytest | ||
- pytest-runner |
This file contains 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
This file contains 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
Empty file.
This file contains 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,217 @@ | ||
import re | ||
import time | ||
from datetime import datetime | ||
|
||
|
||
class MockAMI430: | ||
states = { | ||
"RAMPING to target field/current": "1", | ||
"HOLDING at the target field/current": "2", | ||
"PAUSED": "3", | ||
"Ramping in MANUAL UP mode": "4", | ||
"Ramping in MANUAL DOWN mode": "5", | ||
"ZEROING CURRENT (in progress)": "6", | ||
"Quench detected": "7", | ||
"At ZERO current": "8", | ||
"Heating persistent switch": "9", | ||
"Cooling persistent switch": "10" | ||
} | ||
|
||
field_units = { | ||
"tesla": "1", | ||
"kilogauss": "0" | ||
} | ||
|
||
ramp_rate_units = { | ||
"A/s": "0", | ||
"A/min": "1" | ||
} | ||
|
||
quench_state = {False: "0", True: "1"} | ||
|
||
def __init__(self, name): | ||
|
||
self.name = name | ||
self.log_messages = [] | ||
|
||
self._field_mag = 0 | ||
self._field_target = 0 | ||
self._state = MockAMI430.states["HOLDING at the target field/current"] | ||
|
||
self.handlers = { | ||
"RAMP:RATE:UNITS": { | ||
"get": MockAMI430.ramp_rate_units["A/s"], | ||
"set": None | ||
}, | ||
"FIELD:UNITS": { | ||
"get": MockAMI430.field_units["tesla"], | ||
"set": None | ||
}, | ||
"*IDN": { | ||
"get": "v0.1 Mock", | ||
"set": None | ||
}, | ||
"STATE": { | ||
"get": self._getter("_state"), | ||
"set": self._setter("_state") | ||
}, | ||
"FIELD:MAG": { | ||
"get": self._getter("_field_mag"), | ||
"set": None | ||
}, | ||
"QU": { | ||
"get": MockAMI430.quench_state[False], # We are never in a quenching state so always return the | ||
# same value | ||
"set": None | ||
}, | ||
"PERS": { | ||
"get": "0", | ||
"set": None | ||
}, | ||
"PAUSE": { | ||
"get": self._is_paused, | ||
"set": self._do_pause | ||
}, | ||
"CONF:FIELD:TARG": { | ||
"get": None, # To get the field target, send a message "FIELD:TARG?" | ||
"set": self._setter("_field_target") | ||
}, | ||
"FIELD:TARG": { | ||
"get": self._getter("_field_target"), | ||
"set": None | ||
}, | ||
"PS": { | ||
"get": "0", # The heater is off | ||
"set": None | ||
}, | ||
"RAMP": { | ||
"set": self._do_ramp, | ||
"get": None | ||
}, | ||
"RAMP:RATE:CURRENT": { | ||
"get": "0.1000,50.0000", | ||
"set": None | ||
}, | ||
"COIL": { | ||
"get": "1", | ||
"set": None | ||
} | ||
} | ||
|
||
@staticmethod | ||
def message_parser(gs, msg_str, key): | ||
""" | ||
* If gs = "get": | ||
Let suppose key = "RAMP:RATE:UNITS", then if we get msg_str = "RAMP:RATE:UNITS?" then match will be True and | ||
args = None. If msg_str = "RAMP:RATE:UNITS:10?" then match = True and args = "10". On the other hand if | ||
key = "RAMP" then both "RAMP:RATE:UNITS?" and "RAMP:RATE:UNITS:10?" will cause match to be False | ||
* If gs = "set" | ||
If key = "STATE" and msg_str = "STATE 2,1" then match = True and args = "2,1". If key="STATE" and | ||
msg_str = STATE:ELSE 2,1 then match is False. | ||
Consult [1] for a complete description of the AMI430 protocol. | ||
[1] http://www.americanmagnetics.com/support/manuals/mn-4Q06125PS-430.pdf | ||
Parameters: | ||
gs (string): "get", or "set" | ||
msg_str (string): the message string the mock instrument gets. | ||
key (string): one of the keys in self.handlers | ||
Returns: | ||
match (bool): if the key and the msg_str match, then match = True | ||
args (string): if any arguments are present in the message string these will be passed along. This is | ||
always None when match = False | ||
""" | ||
if msg_str == key: # If the message string matches a key exactly we have a match with no arguments | ||
return True, None | ||
|
||
# We use regular expressions to find out if the message string and the key match. We need to replace reserved | ||
# regular expression characters in the key. For instance replace "*IDN" with "\*IDN". | ||
reserved_re_characters = "\^${}[]().*+?|<>-&" | ||
for c in reserved_re_characters: | ||
key = key.replace(c, "\{}".format(c)) | ||
|
||
s = {"get": "(:[^:]*)?\?$", "set": "([^:]+)"}[gs] # Get and set messages use different regular expression | ||
# patterns to determine a match | ||
search_string = "^" + key + s | ||
r = re.search(search_string, msg_str) | ||
match = r is not None | ||
|
||
args = None | ||
if match: | ||
args = r.groups()[0] | ||
if args is not None: | ||
args = args.strip(":") | ||
|
||
return match, args | ||
|
||
def _getter(self, attribute): | ||
return lambda _: getattr(self, attribute) | ||
|
||
def _setter(self, attribute): | ||
return lambda value: setattr(self, attribute, value) | ||
|
||
def _log(self, msg): | ||
|
||
now = datetime.now() | ||
log_line = "[{}] {}: {}".format(now.strftime("%d:%m:%Y-%H:%M:%S.%f"), self.name, msg) | ||
self.log_messages.append(log_line) | ||
|
||
def _handle_messages(self, msg): | ||
""" | ||
Parameters: | ||
msg (string): a message received through the socket communication layer | ||
Returns: | ||
rval (string or None): If the type of message requests a value (a get message) then this value is returned | ||
by this function. A set message will return a None value. | ||
""" | ||
|
||
gs = {True: "get", False: "set"}[msg.endswith("?")] # A "get" message ends with a "?" and will invoke the get | ||
# part of the handler defined in self.handlers. | ||
|
||
rval = None | ||
handler = None | ||
|
||
for key in self.handlers: # Find which handler is suitable to handle the message | ||
match, args = MockAMI430.message_parser(gs, msg, key) | ||
if not match: | ||
continue | ||
|
||
handler = self.handlers[key][gs] | ||
if callable(handler): | ||
rval = handler(args) | ||
else: | ||
rval = handler | ||
|
||
break | ||
|
||
if handler is None: | ||
self._log("Command {} unknown".format(msg)) | ||
|
||
return rval | ||
|
||
def _do_pause(self, _): | ||
self._state = MockAMI430.states["PAUSED"] | ||
|
||
def _is_paused(self): | ||
return self._state == MockAMI430.states["PAUSED"] | ||
|
||
def _do_ramp(self, _): | ||
self._log("Ramping to {}".format(self._field_target)) | ||
self._state = MockAMI430.states["RAMPING to target field/current"] | ||
time.sleep(0.1) # Lets pretend to be ramping for a bit | ||
self._field_mag = self._field_target | ||
self._state = MockAMI430.states["HOLDING at the target field/current"] | ||
|
||
def get_log_messages(self): | ||
return self.log_messages | ||
|
||
def ask(self, msg): | ||
return self._handle_messages(msg) | ||
|
||
def write(self, msg): | ||
self._handle_messages(msg) |
Oops, something went wrong.