Skip to content

Fail the measurement when InstrJob timeout #62

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions exopy_hqc_legacy/instruments/drivers/driver_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ class InstrIOError(InstrError):
pass


class InstrTimeoutError(InstrError):
"""Generic error raised when an instrument does not behave as expected
"""
pass


class instrument_property(property):
"""Property allowing to cache the result of a get operation and return it
on the next get. The cache can be cleared.
Expand Down Expand Up @@ -157,11 +163,15 @@ class InstrJob(object):
cancel : Callable, optional
Function to cancel the task.

timeout : Callable, optional
Function called when the task timeouts

"""
def __init__(self, condition_callable, expected_waiting_time, cancel):
def __init__(self, condition_callable, expected_waiting_time, cancel, timeout):
self.condition_callable = condition_callable
self.expected_waiting_time = expected_waiting_time
self.cancel = cancel
self.timeout = timeout
self._start_time = time.time()

def wait_for_completion(self, break_condition_callable=None, timeout=15,
Expand All @@ -185,6 +195,10 @@ def wait_for_completion(self, break_condition_callable=None, timeout=15,
result : bool
Boolean indicating if the wait succeeded of was interrupted.

Raises
------
InstrTimeoutError:
Raised if the operation timeout.
"""
while True:
remaining_time = (self.expected_waiting_time -
Expand All @@ -205,7 +219,13 @@ def wait_for_completion(self, break_condition_callable=None, timeout=15,
if self.condition_callable():
return True
if remaining_time < 0 or break_condition_callable():
return False
if remaining_time < 0:
if self.timeout:
self.timeout()
raise InstrTimeoutError()
else:
return False


def cancel(self, *args, **kwargs):
"""Cancel the long running job.
Expand Down
19 changes: 17 additions & 2 deletions exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_cs4.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class CS4(VisaInstrument):
#: Typical fluctuations at the output of the instrument.
#: We use a class variable since we expect this to be identical for all
#: instruments.
OUTPUT_FLUCTUATIONS = 2e-4
OUTPUT_FLUCTUATIONS = 5e-4

caching_permissions = {'heater_state': True,
'target_field': True,
Expand Down Expand Up @@ -116,20 +116,35 @@ def sweep_to_field(self, value, rate=None):

# Start ramping.
self.target_field = value
# Added a pause and then sweep up due to a buggy behavior of the source
sleep(1)
self.activity = 'Hold'
sleep(1)
self.activity = 'To set point'

# Create job.
span = abs(self.read_output_field() - value)
wait = 60 * span / rate
job = InstrJob(self.is_target_reached, wait, cancel=self.stop_sweep)
job = InstrJob(self.is_target_reached, wait, cancel=self.stop_sweep,
timeout=self.stop_sweep_safe)
return job

@secure_communication()
def stop_sweep(self):
"""Stop the field sweep at the current value.

"""
self.activity = 'Hold'

@secure_communication()
def stop_sweep_safe(self):
"""Stop the field sweep at the current value, and turn of the switch heater.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change in behavior ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was specified in the job after job.cancel() was called (which in turn calls this method), it is more logical to integrate it directly to the method called as cancel()

Copy link
Member

@MatthieuDartiailh MatthieuDartiailh Dec 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, you may stop a sweep but need to run a new one right after in which case automatically closing the switch heater would be a bad idea (this is hypothetical). Also if you wanted to go that way you need to be sure all driver are consistent.


"""
self.activity = 'Hold'
self.heater_state = 'Off'
sleep(self.post_switch_wait)

def check_connection(self):
pass

Expand Down
16 changes: 13 additions & 3 deletions exopy_hqc_legacy/instruments/drivers/visa/cryomagnetics_g4.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@

class C4G(CS4):
"""Driver for the superconducting magnet power supply Cryomagnetics 4G.

The fields are read and given in kG (can't change it in G4)
The display only is in T.
"""

def open_connection(self, **para):
Expand All @@ -40,7 +41,7 @@ def open_connection(self, **para):
self.read_termination = '\n'
# Need to write the lower limit in kG for source 4G
#(LLIM needs to be lower than any ULIM)
self.write('LLIM -70')
self.write('LLIM -70;')

@secure_communication()
def read_output_field(self):
Expand All @@ -62,7 +63,6 @@ def target_field(self):
"""Field that the source will try to reach.

"""
# in T
return float(self.ask('ULIM?').strip(' kG')) / 10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you keep the division the field is not given in kG.


@target_field.setter
Expand All @@ -76,6 +76,16 @@ def target_field(self, target):
# can't reuse CS4 class because a semi-colon is needed
self.write('ULIM {};'.format(target * 10))

@instrument_property
@secure_communication()
def field_sweep_rate(self):
"""Rate at which to ramp the field (T/min).

"""
# converted from A/s to T/min
rate = float(self.ask('RATE? 0'))
return rate * (60 * self.field_current_ratio)

@field_sweep_rate.setter
@secure_communication()
def field_sweep_rate(self, rate):
Expand Down
2 changes: 1 addition & 1 deletion exopy_hqc_legacy/manifest.enaml
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ enamldef HqcLegacyManifest(PluginManifest):
task = 'meas_mag_field_task:MeasMagFieldTask'
view = 'views.meas_mag_field_view:MeasMagFieldView'
instruments = ['exopy_hqc_legacy.Legacy.IPS12010',
'exopy_hqc_legacy.Legacy.CS40',
'exopy_hqc_legacy.Legacy.CS4',
'exopy_hqc_legacy.Legacy.C4G']
metadata = {'loopable': True}
Task:
Expand Down
38 changes: 25 additions & 13 deletions exopy_hqc_legacy/tasks/tasks/instr/apply_mag_field_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from atom.api import (Unicode, Float, Bool, set_default)

from exopy.tasks.api import InstrumentTask, validators
from exopy_hqc_legacy.instruments.drivers.driver_tools import InstrTimeoutError


class ApplyMagFieldTask(InstrumentTask):
Expand Down Expand Up @@ -59,8 +60,9 @@ def perform(self, target_value=None):

driver = self.driver
normal_end = True
if (abs(driver.read_persistent_field() - target_value) >
driver.output_fluctuations):
# Only if driver.heater_state == 'Off' otherwise we wait for
# post_switch_wait no matter what
if driver.heater_state == 'Off':
job = driver.sweep_to_persistent_field()
if job.wait_for_completion(self.check_for_interruption,
timeout=60, refresh_time=1):
Expand All @@ -69,31 +71,41 @@ def perform(self, target_value=None):
else:
return False

if (abs(driver.read_persistent_field() - target_value) >
driver.output_fluctuations):
# set the magnetic field
job = driver.sweep_to_field(target_value, self.rate)
normal_end = job.wait_for_completion(self.check_for_interruption,
timeout=60,
refresh_time=10)

# Always close the switch heater when the ramp was interrupted.
try:
normal_end = job.wait_for_completion(self.check_for_interruption,
timeout=60,
refresh_time=5)
except InstrTimeoutError:
# job.timeout() has been called, which stops the sweep and turn off
# the switch heater
self.write_in_database('field', driver.read_persistent_field())
# fail the measurement
self.root.should_stop.set()
raise ValueError(cleandoc('''Field source did not set the field to
{}'''.format(target_value)))

if not normal_end:
# this happens when a stop signal has been send to the measurement
job.cancel()
driver.heater_state = 'Off'
sleep(self.post_switch_wait)
self.write_in_database('field', driver.read_persistent_field())
return False
return

# turn off heater
# turn off heater if required
if self.auto_stop_heater:
driver.heater_state = 'Off'
sleep(self.post_switch_wait)
job = driver.sweep_to_field(0)
# sweep down to zero at the fast sweep rate
job = driver.sweep_to_field(0, driver.fast_sweep_rate)
job.wait_for_completion(self.check_for_interruption,
timeout=60, refresh_time=1)

self.write_in_database('field', target_value)


class ApplyMagFieldAndDropTask(InstrumentTask):
"""Use a supraconducting magnet to apply a magnetic field. Parallel task.

Expand Down
3 changes: 2 additions & 1 deletion tests/tasks/tasks/instr/instr_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class DummyJob(InstrJob):

"""
def __init__(self):
super(DummyJob, self).__init__(lambda: True, 0.1, lambda: None)
super(DummyJob, self).__init__(lambda: True, 0.1, lambda: None,
lambda: None)


class HelperMeta(type):
Expand Down
4 changes: 3 additions & 1 deletion tests/tasks/tasks/instr/test_apply_mag_field_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ def setup(self):
{'Test1':
{'connections': {'C': {'owner': [],
'output_fluctuations': 1e-6,
'heater_state': []}},
'heater_state': ['On', 'Off'],
'fast_sweep_rate': '1.',
'field_sweep_rate': '1.'}},
'settings': {'S': {'sweep_to_field': [DummyJob(), DummyJob(),
DummyJob()],
'sweep_to_persistent_field': [DummyJob()],
Expand Down