Skip to content

Commit efa479a

Browse files
authored
Merge pull request #88 from philipstarkey/philipstarkey/issue51
Introduce a new attribute for `IntermediateDevice` defining the minimum clock high time
2 parents de6fb95 + a9888d5 commit efa479a

File tree

1 file changed

+122
-61
lines changed

1 file changed

+122
-61
lines changed

labscript/labscript.py

+122-61
Original file line numberDiff line numberDiff line change
@@ -658,11 +658,22 @@ def __init__(self, name, parent_device, **kwargs):
658658
raise LabscriptError('Error instantiating device %s. The parent (%s) must be an instance of ClockLine.'%(name, parent_device_name))
659659
Device.__init__(self, name, parent_device, 'internal', **kwargs) # This 'internal' should perhaps be more descriptive?
660660

661+
@property
662+
def minimum_clock_high_time(self):
663+
if getattr(self, "clock_limit", None) is None:
664+
return 0
665+
666+
# Convert clock limit to minimum pulse period and then divide by 2 to
667+
# get minimum half period. This is the fastest assuming the minimum high
668+
# time corresponds to half the fastest clock pulse supported.
669+
return 1/self.clock_limit/2
670+
661671

662672
class ClockLine(Device):
663673
description = 'Generic ClockLine'
664674
allowed_children = [IntermediateDevice]
665675
_clock_limit = None
676+
_minimum_clock_high_time = 0
666677

667678
@set_passed_properties(property_names = {})
668679
def __init__(self, name, pseudoclock, connection, ramping_allowed = True, **kwargs):
@@ -675,6 +686,10 @@ def add_device(self, device):
675686
Device.add_device(self, device)
676687
if getattr(device, 'clock_limit', None) is not None and (self._clock_limit is None or device.clock_limit < self.clock_limit):
677688
self._clock_limit = device.clock_limit
689+
if getattr(device, 'minimum_clock_high_time', None) is not None:
690+
self._minimum_clock_high_time = max(
691+
device.minimum_clock_high_time, self._minimum_clock_high_time
692+
)
678693

679694
# define a property to make sure no children overwrite this value themselves
680695
# The calculation of maximum clock_limit should be done by the add_device method above
@@ -689,6 +704,10 @@ def clock_limit(self):
689704
return self.parent_device.clock_limit
690705
return self._clock_limit
691706

707+
@property
708+
def minimum_clock_high_time(self):
709+
return self._minimum_clock_high_time
710+
692711

693712
class Pseudoclock(Device):
694713
"""Parent class of all pseudoclocks.
@@ -757,113 +776,147 @@ def collect_change_times(self, all_outputs, outputs_by_clockline):
757776
change_times[clock_line].extend(output_change_times)
758777
all_change_times.extend(output_change_times)
759778
ramps_by_clockline[clock_line].extend(output.get_ramp_times())
760-
761-
# print 'initial_change_times for %s: %s'%(clock_line.name,change_times[clock_line])
762-
779+
763780
# Change to a set and back to get rid of duplicates:
764781
if not all_change_times:
765782
all_change_times.append(0)
766783
all_change_times.append(self.parent_device.stop_time)
767-
# include trigger times in change_times, so that pseudoclocks always have an instruction immediately following a wait:
784+
# include trigger times in change_times, so that pseudoclocks always
785+
# have an instruction immediately following a wait:
768786
all_change_times.extend(self.parent_device.trigger_times)
769-
770-
####################################################################################################
771-
# Find out whether any other clockline has a change time during a ramp on another clockline. #
772-
# If it does, we need to let the ramping clockline know it needs to break it's loop at that time #
773-
####################################################################################################
787+
788+
########################################################################
789+
# Find out whether any other clockline has a change time during a ramp #
790+
# on another clockline. If it does, we need to let the ramping #
791+
# clockline know it needs to break it's loop at that time #
792+
########################################################################
774793
# convert all_change_times to a numpy array
775794
all_change_times_numpy = array(all_change_times)
776-
795+
777796
# quantise the all change times to the pseudoclock clock resolution
778-
# all_change_times_numpy = (all_change_times_numpy/self.clock_resolution).round()*self.clock_resolution
779-
all_change_times_numpy = self.quantise_to_pseudoclock(all_change_times_numpy)
780-
797+
all_change_times_numpy = self.quantise_to_pseudoclock(
798+
all_change_times_numpy
799+
)
800+
781801
# Loop through each clockline
782-
# print ramps_by_clockline
783802
for clock_line, ramps in ramps_by_clockline.items():
784803
# for each clockline, loop through the ramps on that clockline
785804
for ramp_start_time, ramp_end_time in ramps:
786-
# for each ramp, check to see if there is a change time in all_change_times which intersects
787-
# with the ramp. If there is, add a change time into this clockline at that point
788-
indices = np.where((ramp_start_time < all_change_times_numpy) & (all_change_times_numpy < ramp_end_time))
805+
# for each ramp, check to see if there is a change time in
806+
# all_change_times which intersects with the ramp. If there is,
807+
# add a change time into this clockline at that point
808+
indices = np.where(
809+
(ramp_start_time < all_change_times_numpy) &
810+
(all_change_times_numpy < ramp_end_time)
811+
)
789812
for idx in indices[0]:
790813
change_times[clock_line].append(all_change_times_numpy[idx])
791-
814+
792815
# Get rid of duplicates:
793816
all_change_times = list(set(all_change_times_numpy))
794817
all_change_times.sort()
795-
818+
796819
# Check that the pseudoclock can handle updates this fast
797820
for i, t in enumerate(all_change_times[:-1]):
798821
dt = all_change_times[i+1] - t
799822
if dt < 1.0/self.clock_limit:
800-
raise LabscriptError('Commands have been issued to devices attached to %s at t= %s s and %s s. '%(self.name, str(t),str(all_change_times[i+1])) +
801-
'This Pseudoclock cannot support update delays shorter than %s sec.'%(str(1.0/self.clock_limit)))
823+
raise LabscriptError(
824+
"Commands have been issued to devices attached to "
825+
f"{self.name} at t={t} and t={all_change_times[i+1]}. "
826+
"This Pseudoclock cannot support update delays shorter "
827+
f"than {1.0/self.clock_limit} seconds."
828+
)
802829

803-
####################################################################################################
804-
# For each clockline, make sure we have a change time for triggers, stop_time, t=0 and #
805-
# check that no change tiems are too close together #
806-
####################################################################################################
830+
########################################################################
831+
# For each clockline, make sure we have a change time for triggers, #
832+
# stop_time, t=0 and check that no change times are too close together #
833+
########################################################################
807834
for clock_line, change_time_list in change_times.items():
808-
# include trigger times in change_times, so that pseudoclocks always have an instruction immediately following a wait:
835+
# include trigger times in change_times, so that pseudoclocks always
836+
# have an instruction immediately following a wait:
809837
change_time_list.extend(self.parent_device.trigger_times)
810-
838+
811839
# If the device has no children, we still need it to have a
812840
# single instruction. So we'll add 0 as a change time:
813841
if not change_time_list:
814842
change_time_list.append(0)
815-
843+
816844
# quantise the all change times to the pseudoclock clock resolution
817-
# change_time_list = (array(change_time_list)/self.clock_resolution).round()*self.clock_resolution
818845
change_time_list = self.quantise_to_pseudoclock(change_time_list)
819-
846+
820847
# Get rid of duplicates if trigger times were already in the list:
821848
change_time_list = list(set(change_time_list))
822849
change_time_list.sort()
823850

851+
# Also add the stop time as as change time. First check that it
852+
# isn't too close to the time of the last instruction:
853+
if not self.parent_device.stop_time in change_time_list:
854+
dt = self.parent_device.stop_time - change_time_list[-1]
855+
if abs(dt) < 1.0/clock_line.clock_limit:
856+
raise LabscriptError(
857+
"The stop time of the experiment is "
858+
f"t={self.parent_device.stop_time}, but the last "
859+
f"instruction for a device attached to {self.name} is "
860+
f"at t={change_time_list[-1]}. One or more connected "
861+
"devices cannot support update delays shorter than "
862+
f"{1.0/clock_line.clock_limit} seconds. Please set the "
863+
"stop_time a bit later."
864+
)
865+
866+
change_time_list.append(self.parent_device.stop_time)
867+
868+
# Sort change times so self.stop_time will be in the middle
869+
# somewhere if it is prior to the last actual instruction.
870+
# Whilst this means the user has set stop_time in error, not
871+
# catching the error here allows it to be caught later by the
872+
# specific device that has more instructions after
873+
# self.stop_time. Thus we provide the user with sligtly more
874+
# detailed error info.
875+
change_time_list.sort()
876+
824877
# index to keep track of in all_change_times
825878
j = 0
826879
# Check that no two instructions are too close together:
827880
for i, t in enumerate(change_time_list[:-1]):
828881
dt = change_time_list[i+1] - t
829882
if dt < 1.0/clock_line.clock_limit:
830-
raise LabscriptError('Commands have been issued to devices attached to clockline %s at t= %s s and %s s. '%(clock_line.name, str(t),str(change_time_list[i+1])) +
831-
'One or more connected devices on ClockLine %s cannot support update delays shorter than %s sec.'%(clock_line.name, str(1.0/clock_line.clock_limit)))
832-
883+
raise LabscriptError(
884+
"Commands have been issued to devices attached to "
885+
f"clockline {clock_line.name} at t={t} and "
886+
f"t={change_time_list[i+1]}. One or more connected "
887+
f"devices on ClockLine {clock_line.name} cannot "
888+
"support update delays shorter than "
889+
f"{1.0/clock_line.clock_limit} seconds"
890+
)
891+
833892
all_change_times_len = len(all_change_times)
834893
# increment j until we reach the current time
835894
while all_change_times[j] < t and j < all_change_times_len-1:
836895
j += 1
837896
# j should now index all_change_times at "t"
838-
# Check that the next all change_time is not too close (and thus would force this clock tick to be faster than the clock_limit)
897+
# Check that the next all change_time is not too close (and thus
898+
# would force this clock tick to be faster than the minimum
899+
# clock high time)
839900
dt = all_change_times[j+1] - t
840-
if dt < 1.0/clock_line.clock_limit:
841-
raise LabscriptError('Commands have been issued to devices attached to %s at t= %s s and %s s. '%(self.name, str(t),str(all_change_times[j+1])) +
842-
'One or more connected devices on ClockLine %s cannot support update delays shorter than %s sec.'%(clock_line.name, str(1.0/clock_line.clock_limit)))
843-
844-
# Also add the stop time as as change time. First check that it isn't too close to the time of the last instruction:
845-
if not self.parent_device.stop_time in change_time_list:
846-
dt = self.parent_device.stop_time - change_time_list[-1]
847-
if abs(dt) < 1.0/clock_line.clock_limit:
848-
raise LabscriptError('The stop time of the experiment is t= %s s, but the last instruction for a device attached to %s is at t= %s s. '%( str(self.stop_time), self.name, str(change_time_list[-1])) +
849-
'One or more connected devices cannot support update delays shorter than %s sec. Please set the stop_time a bit later.'%str(1.0/clock_line.clock_limit))
850-
851-
change_time_list.append(self.parent_device.stop_time)
852-
853-
# Sort change times so self.stop_time will be in the middle
854-
# somewhere if it is prior to the last actual instruction. Whilst
855-
# this means the user has set stop_time in error, not catching
856-
# the error here allows it to be caught later by the specific
857-
# device that has more instructions after self.stop_time. Thus
858-
# we provide the user with sligtly more detailed error info.
859-
change_time_list.sort()
860-
861-
# because we made the list into a set and back to a list, it is now a different object
862-
# so modifying it won't update the list in the dictionary.
863-
# So store the updated list in the dictionary
901+
if dt < (2 * clock_line.minimum_clock_high_time):
902+
raise LabscriptError(
903+
"Commands have been issued to devices attached to "
904+
f"{self.name} at t={t} and t={all_change_times[j+1]}. "
905+
"One or more connected devices on ClockLine "
906+
f"{clock_line.name} cannot support clock ticks with a "
907+
"digital high time shorter than "
908+
f"{clock_line.minimum_clock_high_time} which is more "
909+
"than half the available time between the event at "
910+
f"t={t} on ClockLine {clock_line.name} and the next "
911+
"event on another ClockLine."
912+
)
913+
914+
# because we made the list into a set and back to a list, it is now
915+
# a different object so modifying it won't update the list in the
916+
# dictionary. So store the updated list in the dictionary
864917
change_times[clock_line] = change_time_list
865918
return all_change_times, change_times
866-
919+
867920
def expand_change_times(self, all_change_times, change_times, outputs_by_clockline):
868921
"""For each time interval delimited by change_times, constructs
869922
an array of times at which the clock for this device needs to
@@ -2412,7 +2465,11 @@ def go_high(self):
24122465
self.add_instruction(0,1)
24132466
self._static_value = 1
24142467
else:
2415-
raise LabscriptError('%s %s has already been set to %s. It cannot also be set to %s.'%(self.description, self.name, self.instruction_to_string[self._static_value], self.instruction_to_string[value]))
2468+
raise LabscriptError(
2469+
f"{self.description} {self.name} has already been set to "
2470+
f"{self.instruction_to_string(self._static_value)}. It cannot "
2471+
"also be set to 1."
2472+
)
24162473

24172474
def go_low(self):
24182475
"""Command a static low output.
@@ -2424,7 +2481,11 @@ def go_low(self):
24242481
self.add_instruction(0,0)
24252482
self._static_value = 0
24262483
else:
2427-
raise LabscriptError('%s %s has already been set to %s. It cannot also be set to %s.'%(self.description, self.name, self.instruction_to_string[self._static_value], self.instruction_to_string[value]))
2484+
raise LabscriptError(
2485+
f"{self.description} {self.name} has already been set to "
2486+
f"{self.instruction_to_string(self._static_value)}. It cannot "
2487+
"also be set to 0."
2488+
)
24282489

24292490
def get_change_times(self):
24302491
"""Enforces no change times.

0 commit comments

Comments
 (0)