Experiment Control Software
Experiment and control software
Sequence generator
The Sequence Generator repository is for generating AWG sequences that are synchronized with the Pritel OAC fast mode locked laser. This is needed for carving the mode locked laser signal with intensity modulators. This toolkit was useful for my PPM project as well as a concurrent high rate QKD project with slightly different requirements.
The most important feature of this codebase is the ability to determine compatible AWG sample rates and sequence lengths for a given laser repetition rate, while imposing certain requirements, like that the AWG sequence length must be a multiple of 128 samples. The script supports situations where a small integer number of laser pulses does not match in time with an integer number of AWG samples. The main requirement is that the time for the full AWG sequence to run must be an integer multiple of the laser repetition period, so that the AWG sequence can be repeated indefinitely without drifting out of sync with the laser.
This analysis is performed in the functions determine_ppm_properties
and determine_
-regular_properties
for the PPM and QKD applications, respectively.
Time-walk correction
A repository of tools for performing time-walk calibration and correction on timetag data files. It is set up to use the binary format .ttbin
from swabian timetaggers, but can be easily adapted to other formats.
Bias controll user interface
A web-based user interfaces for controlling isolated voltage sources used for SNSPD biasing. Is is an improvement over a previous web interface which is based on the svelte frontend framework and should be much easier to maintain and extend than the previous web interface.
Entanglement analysis repositories
This master repository contains submodules for all the repositories used for analyzing data from the high-rate entanglement distribution system. It includes a script for automatically populating each repository with the original raw data stored or figshare.
Entanglement control software
Based on an example file shipped with the swabian timetagger, this leverages the swabian python api for collecting entanglement data for the high-rate entanglement distribution system. It incorporates an innovative method for defining long running measurements based on a construct called Actions
. Actions
are objects that perform some operation at one stage in the script’s main event loop (e.g., change an interferometer voltage to a specific value), or over multiple event loop iterations (e.g., integrate coincidences). Actions may also contain and run other actions, so that successively more complex measurements can be built from simple reusable components. This is done through an evaluate
method that all Action
methods share. The base class for all actions is located in measurements/measurement
-_management.py
. When a complex measurement is finished, a highly nested construct of actions may export its internal data to a .json
file. The high-rate experiment analysis repositories start with loading and analyzing these .json
files.
More information about the Action
framework:
Action-framework
The time tagger software I use has a main event loop and runs a series of python functions multiple times per second to pull in new data and display it to the graphs on screen. I've found it non-trivial to add long-term complex behavior inside this software construct. The program can be written to do different operations on different even loop cycles (for example, starting an integration, ending an integration or changing the interferometer voltage) with a series of complicated if-statements. But this approach quickly becomes untenable with many deeply nested if-statements and program state that is hard to reason about. Therefore, I developed a system of tasks or Actions
that may be added to a task queue and 'consumed' by the main even loop. Each Action
is an object that exposes an .evaluate()
method that is called inside the program's main loop. Action objects can contain other action objects, so that the .evaluate()
method of the parent action calls the .evaluate()
method on a list of child actions it contains or manages.
A bare-bones base action class
certain details of the implementation have been removed for clarity
class Action:
def __init__(self):
self.event_list = []
self.init_time = -1
self.results = []
self.save = False
def add_action(self, object):
self.event_list.append(object)
def evaluate(self, current_time, counts, **kwargs):
if self.pass_state:
return {"state": "passed"}
response = self.event_list[0].evaluate(current_time, counts)
if (response["state"] == "finished"):
response.pop("state", None)
self.results.append(response)
self.event_list.pop(0)
# if all internal actions are finished, finish this action
if len(self.event_list) == 0:
self.final_state = {
"state": "finished",
"name": self.__class__.__name__,
"results": self.flatten(self.results),
}
# if the current internal action is finished,
# recursively call the next
self.evaluate(current_time, counts)
return {"state": "waiting", "results": response}
Actions can be in multiple states depending on if the event loop has not yet reached them, is currently evaluating them ('waiting' or 'integrating'), or has finished them ('finished'). Actions signal that they are 'finished' by returning a dictionary including {"state": "finished"}
along with other results from the completion of the action.
An Integrate
operation, that inherits from Action
class Integrate(Action):
def __init__(self, int_time):
super().__init__()
self.int_time = int_time
self.counts = 0
def evaluate(self, current_time, counts, **kwargs):
if self.init_time == -1:
self.init_time = time.time()
return {"state": "integrating"}
self.counts = self.counts + counts # add counts
if (current_time - self.init_time) > self.int_time:
self.delta_time = current_time - self.init_time
self.final_state = {
"state": "finished",
"name": self.__class__.__name__,
"counts": self.counts,
"delta_time": self.delta_time,
}
return self.final_state
return {"state": "integrating"}
The exact time that new data is acquired is determined by the timing of the main event loop. So the Integrate
action cannot integrate for an exact amount of time. But it can integrate for the necessary number of event loops and extract an accurate measure of photons or coincidences per second based on the elapsed time.
Wait()
or SetVoltage()
actions can be defined in a similar way, so that the behavior of a more complex action containing other actions can be specified like this
voltage_and_integrate = Action()
voltage_and_integrate.add_action(SetVoltage(voltage_value))
voltage_and_integrate.add_action(Wait(wait_time))
voltage_and_integrate.add_action(Integrate(integration_time))
The course scan, and coordinate search methods I use to find the entanglement visibility are implemented in this action framework. There are a couple special action types. DependentAction
takes data returned from the finishing first internal action and passes it to the next internal action. ConcurrentAction
passes data between internal actions that is returned from the internal .evaluate()
methods upon each event loop cycle. This is used to move data from the measurement actions to the graph action.
This is the code that defines the operations for finding entanglement visibility:
tracker = Action()
self.event_loop_action = tracker
scan_and_find_extremes = DependentAction("coarse_scan")
scan_and_find_extremes.add_action(Scan(params["int_fast"], self.VSource))
minimum_and_maximum = DistributeData("coarse_scan")
minimum = Extremum(
"min", 2, 1, 0.02, self.VSource, 0, "coarse_scan", fine_grain_mode=True)
minimum.enable_save(save_name="minimum_data.json")
minimum_and_maximum.add_action(minimum)
maximum = Extremum(
"max", 1, 4, 0.2, self.VSource, 0, "coarse_scan", fine_grain_mode=True)
maximum.enable_save(save_name="maximum_data.json")
minimum_and_maximum.add_action(maximum)
scan_and_find_extremes.add_action(minimum_and_maximum)
tracker.add_action(SetVoltage(0, self.VSource, 2))
tracker.add_action(Wait(10))
tracker.add_action(
ConcurrentAction(scan_and_find_extremes,
GraphUpdate(self.clockAxis)))
tracker.enable_save(save_name=params["save_name"])
Dark count rate through filters simulation
Based on the known lens f-number, aperture size, detector size, and filter transmissions for the dark count minimization project, the rate of dark counts due to transmitted blackbody photons can be estimated. With this simulation we determined that 4 thick custom short-pass filters, and one bandpass filter were ideal for our experiment.