Source code for experiment_prototype.scan_classes.averaging_periods

#!/usr/bin/python

"""
    averaging_periods
    ~~~~~~~~~~~~~~~~~
    This is the module containing the AveragingPeriod class. The AveragingPeriod class 
    contains the ScanClassBase members, as well as clrfrqflag (to be implemented), 
    intn (number of integrations to run), or intt(max time for integrations), 
    and it contains sequences of class Sequence.

    :copyright: 2018 SuperDARN Canada
    :author: Marci Detwiller
"""

""" Scans are made up of AveragingPeriods, these are typically a 3sec time of
the same pulse sequence pointing in one direction.  AveragingPeriods are made
up of Sequences, typically the same sequence run ave. 20-30 times after a clear
frequency search.  Sequences are made up of pulse_time lists, which give
timing, slice, and pulsenumber. CPObject provides channels, pulseshift (if
any), freq, pulse length, beamdir, and wavetype.
"""

import sys
import operator

from experiment_prototype.scan_classes.sequences import Sequence
from experiment_prototype.scan_classes.scan_class_base import ScanClassBase
from experiment_prototype.experiment_exception import ExperimentException


[docs]class AveragingPeriod(ScanClassBase): """ Set up the AveragingPeriods. An averagingperiod contains sequences and integrates one or multiple pulse sequences together in a given time frame or in a given number of averages, if that is the preferred limiter. **The unique members of the averagingperiod are (not a member of the scanclassbase):** slice_to_beamorder passed in by the scan that this AveragingPeriod instance is contained in. A dictionary of slice: beam_order for all slices contained in this aveperiod. slice_to_beamdir passed in by the scan that this AveragingPeriod instance is contained in. A dictionary of slice: beamdir(s) for all slices contained in this aveperiod. clrfrqflag Boolean, True if clrfrqsearch should be performed. clrfrqrange The range of frequency to search if clrfrqflag is True. Otherwise empty. intt The priority limitation. The time limit (ms) at which time the aveperiod will end. If None, we will use intn to end the aveperiod (a number of sequences). intn Number of averages (# of times the sequence transmits) to end after for the averagingperiod. sequences The list of sequences included in this aveperiod. This does not indicate how many averages will be transmitted in the aveperiod. If there are multiple sequences in the list, they will be alternated between until the end of the aveperiod. one_pulse_only boolean, True if this averaging period only has one unique set of pulse samples in it. This is true if there is only one sequence in the averaging period, and all pulses after the first pulse in the sequence have the isarepeat key = True. This boolean can be used to speed up the process of sending data to the driver which means we can get more averages in less time. """ def __init__(self, ave_keys, ave_slice_dict, ave_interface, transmit_metadata, slice_to_beamorder_dict, slice_to_beamdir_dict): ScanClassBase.__init__(self, ave_keys, ave_slice_dict, ave_interface, transmit_metadata) self.slice_to_beamorder = slice_to_beamorder_dict self.slice_to_beamdir = slice_to_beamdir_dict # Metadata for an AveragingPeriod: clear frequency search, integration time, number of averages goal self.clrfrqflag = False # there may be multiple slices in this averaging period at different frequencies so # we may have to search multiple ranges. self.clrfrqrange = [] for slice_id in self.slice_ids: if self.slice_dict[slice_id]['clrfrqflag']: self.clrfrqflag = True self.clrfrqrange.append(self.slice_dict[slice_id]['clrfrqrange']) # TODO: SET UP CLEAR FREQUENCY SEARCH CAPABILITY # also note for when setting this up clrfrqranges may overlap self.intt = self.slice_dict[self.slice_ids[0]]['intt'] self.intn = self.slice_dict[self.slice_ids[0]]['intn'] if self.intt is not None: # intt has priority over intn for slice_id in self.slice_ids: if self.slice_dict[slice_id]['intt'] != self.intt: errmsg = """Slice {} and {} are INTEGRATION or PULSE interfaced and do not have the same Averaging Period duration intt""".format(self.slice_ids[0], slice_id) raise ExperimentException(errmsg) elif self.intn is not None: for slice_id in self.slice_ids: if self.slice_dict[slice_id]['intn'] != self.intn: errmsg = """Slice {} and {} are INTEGRATION or PULSE interfaced and do not have the same NAVE goal intn""".format(self.slice_ids[0], slice_id) raise ExperimentException(errmsg) for slice_id in self.slice_ids: if len(self.slice_dict[slice_id]['beam_order']) != len(self.slice_dict[self.slice_ids[0]]['beam_order']): errmsg = """Slice {} and {} are INTEGRATION or PULSE interfaced and do not have the same length of beam_order (number of integration periods) """.format(self.slice_ids[0], slice_id) raise ExperimentException(errmsg) # NOTE: Do not need beam information inside the AveragingPeriod, this is in Scan. # Determine how this averaging period is made by separating out the INTEGRATION interfaced. self.nested_slice_list = self.get_sequence_slice_ids() self.sequences = [] for params in self.prep_for_nested_scan_class(): self.sequences.append(Sequence(*params)) self.one_pulse_only = False
[docs] def get_sequence_slice_ids(self): """ Return the slice_ids that are within the Sequences in this AveragingPeriod instance. Take the interface keys inside this averagingperiod and return a list of lists where each inner list contains the slices that are in a sequence that is inside this averagingperiod. ie. len(nested_slice_list) = # of sequences in this averagingperiod, len(nested_slice_list[0]) = # of slices in the first sequence, etc. :returns: the nested_slice_list which is used when creating the sequences in this averagingperiod. """ integ_combos = [] # Remove INTEGRATION combos as we are trying to separate those. for k, interface_type in self.interface.items(): # TODO make example if interface_type == "PULSE": integ_combos.append(list(k)) combos = self.slice_combos_sorter(integ_combos, self.slice_ids) if __debug__: print("sequences slice id combos: {}".format(combos)) return combos
[docs] def set_beamdirdict(self, beamiter): """ Get a dictionary of 'slice_id' : 'beamdir(s)' for this averaging period. At a given beam iteration, this averagingperiod instance will select the beam directions that it will shift to. :param beamiter: the index into the beam_order list, or the index of an averaging period into the scan :returns: dictionary of slice to beamdir where beamdir is always a list (may be of length one though). Beamdir is azimuth angle. """ slice_to_beamdir_dict = {} try: for slice_id in self.slice_ids: beam_number = self.slice_to_beamorder[slice_id][beamiter] if isinstance(beam_number, int): beamdir = [] beamdir.append(self.slice_to_beamdir[slice_id][beam_number]) else: # is a list beamdir = [self.slice_to_beamdir[slice_id][bmnum] for bmnum in beam_number] slice_to_beamdir_dict[slice_id] = beamdir except IndexError: errmsg = 'Looking for BeamNumber or Beamdir that does not Exist at BeamIter' \ ' {}'.format(beamiter) raise ExperimentException(errmsg) return slice_to_beamdir_dict
[docs] def build_sequences(self, slice_to_beamdir_dict): """ Build a list of sequences to iterate through when transmitting. This includes building all pulses within the sequences, so it then contains all pulse samples data to iterate through when transmitting. If there is only one sequence type in the averaging period, this list will be of length 1. That would mean that that one sequence gets repeated throughout the averagingperiod (intn and intt still apply). :returns: sequence_dict_list, list of lists of pulse dictionaries. """ # Create a pulse dictionary before running through the # averaging period. sequence_dict_list = [] # a list of sequence data. # a sequence data is a list of pulses in the sequence in order. # a pulse data is a dictionary of the required data for the pulse. for sequence in self.sequences: # create pulse dictionary. sequence_dict = sequence.build_pulse_transmit_data(slice_to_beamdir_dict) # Just alternating sequences sequence_dict_list.append(sequence_dict) if len(sequence_dict_list) == 1: # only one sequence in the averaging period for pulse_num, pulse_dict in enumerate(sequence_dict_list[0]): if pulse_num == 0: continue elif pulse_dict['isarepeat'] == False: break # there is another unique pulse in the sequence. else: # no break self.one_pulse_only = True # there is only one unique pulse in the sequence. return sequence_dict_list