labscript-suite / labscript

The 𝗹𝗮𝗯𝘀𝗰𝗿𝗶𝗽𝘁 library provides a translation from expressive Python code to low-level hardware instructions.
http://labscriptsuite.org
BSD 2-Clause "Simplified" License
9 stars 51 forks source link

Ramps can over-/undershoot their final value #46

Closed philipstarkey closed 6 years ago

philipstarkey commented 6 years ago

Original report (archived issue) by Jan Werkmann (Bitbucket: PhyNerd, GitHub: PhyNerd).


Today in the lab we ran into the problem where a script would not compile due to the fact that a ramp exceeded the outputs limits. The output had the limits (0, 10) and the initial values was something like 4 and final value of the ramp was 0 the invalid value was something like -4e-14. My guess is that the quantized times might have caused this though I'm not sure.

I'll try to make a minimal working example tomorrow. The script we used in the lab is quite complex and contains devices that are not in mainline labscript.

philipstarkey commented 6 years ago

Original comment by Philip Starkey (Bitbucket: pstarkey, GitHub: philipstarkey).


Hmm, yeah I can see that quatised times could likely be the cause here. Only other thing I can think of is if you're running it on Python 3?

philipstarkey commented 6 years ago

Original comment by Jan Werkmann (Bitbucket: PhyNerd, GitHub: PhyNerd).


Nope python 2 so far. Only my testing system is running python 3. The lab will make the switch to python 3 sometime next month.

philipstarkey commented 6 years ago

Original comment by Chris Billington (Bitbucket: cbillington, GitHub: chrisjbillington).


After labscript generates lists of output values, devices tend to quantise these as well (in their generate_code method) before storing them in the h5 file. I am not sure if any checks are performed after the output values are quantised here, so bad quantisation code could be to blame.

Happy to debug a minimum working example.

philipstarkey commented 6 years ago

Original comment by Jan Werkmann (Bitbucket: PhyNerd, GitHub: PhyNerd).


Here is a minimal example:

#!python

from labscript import *
from labscript_devices.PulseBlaster_SP2_24_100_32k import PulseBlaster_SP2_24_100_32k
from labscript_devices.NI_PCIe_6363 import NI_PCIe_6363

# Connection Table
PulseBlaster_SP2_24_100_32k(   name='pulse_blaster')
ClockLine(  name='A1_clockline', pseudoclock=pulse_blaster.pseudoclock,   connection='flag 0')
NI_PCIe_6363(name='ni_card_0', parent_device=A1_clockline, clock_terminal='ni_pcie_6363_0/PFI0', MAX_name='ni_pcie_6363_0', acquisition_rate=100e3)
AnalogOut(  name='Pushout_Frequency',           parent_device=ni_card_0,                connection='ao2', limits=(0,10), default_value=5.67)
AnalogOut(  name='Pushout_Amplitude',           parent_device=ni_card_0,                connection='ao3', limits=(0,10), default_value=0.0)

start()
t=1.8061896326530613
ramp_samplerate = 16000.0
Pushout_Amplitude.  ramp(t, duration=0.25, initial=5.00, final=0, samplerate=ramp_samplerate)
t += 0.25 + 0.01
stop(t)

This should result in

#!

Traceback (most recent call last):
  File "script.py", line 18, in <module>
  File "/Users/janwerkmann/labscript_suite/labscript/labscript.py", line 2143, in stop
    generate_code()
  File "/Users/janwerkmann/labscript_suite/labscript/labscript.py", line 2045, in generate_code
    device.generate_code(hdf5_file)
  File "/Users/janwerkmann/labscript_suite/labscript_devices/PulseBlaster_No_DDS.py", line 73, in generate_code
    PseudoclockDevice.generate_code(self, hdf5_file)
  File "/Users/janwerkmann/labscript_suite/labscript/labscript.py", line 963, in generate_code
    self.do_checks(outputs)
  File "/Users/janwerkmann/labscript_suite/labscript/labscript.py", line 943, in do_checks
    output.do_checks(self.trigger_times)
  File "/Users/janwerkmann/labscript_suite/labscript/labscript.py", line 1128, in do_checks
    self.add_instruction(instruction['end time'], instruction['function'](instruction['end time']-instruction['initial time']), instruction['units'])
  File "/Users/janwerkmann/labscript_suite/labscript/labscript.py", line 1107, in add_instruction
    raise LabscriptError('You cannot program the value %s (base units) to %s as it falls outside the limits (%d to %d)'%(str(instruction), self.name, self.limits[0], self.limits[1]))
LabscriptError: You cannot program the value -4.4408920985e-15 (base units) to Pushout_Amplitude as it falls outside the limits (0 to 10)
Compilation aborted.
philipstarkey commented 6 years ago

Original comment by Philip Starkey (Bitbucket: pstarkey, GitHub: philipstarkey).


So this is not an issue with the quantised times code. It actually happens before any of the code for quantised times runs. I'm pretty sure it's an issue with the floating point calculation error (to do with the time) inside the ramp function because. Specifically, if you set t=1.806189632 you see the crash, but if you set t=1.80618963 then you don't. All this does is offset the initial parameter in the ramp function (the duration stays the same which is the only argument used to evaluate the value at the end time point (so it's agnostic of the end time point). Similarly, using t=1.8061896327 works fine but t=1.8061896326 does not.

Investigating further, this error is due to the fact that we directly use the duration when parameterising the ramp (aka, when we create the lambda function in labscript.functions.ramp) but then use a calculated duration when evaluating the end point. The solution should be to instead use a duration internally in AnalogQuantity.ramp that is calculated as t + truncation * duration -t. In a terminal, this works for me and successfully returns 0.0 when you evaluate the function at the end point. However, in labscript I still see the crash (and the error is now twice the size it was before)....

Not sure why that is, I'll keep digging!

In the meantime, a quick solution for you would be to just work out why you have 17 digits of precision in your time and quantise that away manually in your script.

philipstarkey commented 6 years ago

Original comment by Philip Starkey (Bitbucket: pstarkey, GitHub: philipstarkey).


Ok, got the solution. This line should have the function key be changed to: 'function': functions.ramp(round(t + truncation * duration, 10) - round(t, 10), initial, final)

Can you test and confirm it fixes the issue on your actual script and doesn't introduce other issues?

philipstarkey commented 6 years ago

Original comment by Jan Werkmann (Bitbucket: PhyNerd, GitHub: PhyNerd).


Yes this fixes it thanks.

philipstarkey commented 6 years ago

Original comment by Chris Billington (Bitbucket: cbillington, GitHub: chrisjbillington).


Fixes issue #46

→ \<\<cset 9c176b7bcae88874f99bd6a6c6d832949bdc0f4f>>

philipstarkey commented 6 years ago

Original comment by Chris Billington (Bitbucket: cbillington, GitHub: chrisjbillington).


fixed bug in last commit (fixing issue #46) where we included truncation in the duration calculation used to determine the duration used as a parameter of the function.

This was incorrect, since the function should be defined over the original duration, but evaluated over the truncated duration.

The fix was also applied to all ramping functions of AnalogQuantity.

→ \<\<cset 78bab8fa5e054cb7540923efaebe78a401b59337>>