@@ -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
662672class 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
693712class 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