Experiments

This is where you would create your experiment that you would like to run on the radar. The following are a couple of examples of current SuperDARN experiments, and a brief discussion of the update() method which will be implemented at a later date.

normalscan

Normalscan is a very common experiment for SuperDARN. It does not update itself, so no update() method is necessary. It only has a single slice, as there is only one frequency, pulse_len, beam_order, etc. Since there is only one slice there is no need for an interface dictionary.

normalsound.py
 1#!/usr/bin/python
 2
 3"""
 4    normalscan
 5    ~~~~~~~~~~
 6    Standard radar operating experiment. Transmits a single frequency signal.
 7
 8    :copyright: 2023 SuperDARN Canada
 9"""
10
11import borealis_experiments.superdarn_common_fields as scf
12from experiment_prototype.experiment_prototype import ExperimentPrototype
13
14
15class Normalscan(ExperimentPrototype):
16
17    def __init__(self, **kwargs):
18        """
19        kwargs:
20
21        freq: int
22
23        """
24        cpid = 151
25        super().__init__(cpid)
26
27        if scf.IS_FORWARD_RADAR:
28            beams_to_use = scf.STD_16_FORWARD_BEAM_ORDER
29        else:
30            beams_to_use = scf.STD_16_REVERSE_BEAM_ORDER
31
32        if scf.options.site_id in ["cly", "rkn", "inv"]:
33            num_ranges = scf.POLARDARN_NUM_RANGES
34        if scf.options.site_id in ["sas", "pgr", "lab"]:
35            num_ranges = scf.STD_NUM_RANGES
36
37        # default frequency set here
38        freq = scf.COMMON_MODE_FREQ_1
39
40        if kwargs:
41            if 'freq' in kwargs.keys():
42                freq = kwargs['freq']
43
44        print('Frequency set to {}'.format(freq))   # TODO: Log
45
46        self.add_slice({  # slice_id = 0, there is only one slice.
47            "pulse_sequence": scf.SEQUENCE_7P,
48            "tau_spacing": scf.TAU_SPACING_7P,
49            "pulse_len": scf.PULSE_LEN_45KM,
50            "num_ranges": num_ranges,
51            "first_range": scf.STD_FIRST_RANGE,
52            "intt": scf.INTT_7P,  # duration of an integration, in ms
53            "beam_angle": scf.STD_16_BEAM_ANGLE,
54            "rx_beam_order": beams_to_use,
55            "tx_beam_order": beams_to_use,
56            "scanbound": scf.easy_scanbound(scf.INTT_7P, beams_to_use), #1 min scan
57            "freq" : freq, #kHz
58            "acf": True,
59            "xcf": True,  # cross-correlation processing
60            "acfint": True,  # interferometer acfs
61            "wait_for_first_scanbound": False,
62        })

twofsound

Twofsound is a common variant of the normalscan experiment for SuperDARN. It does not update itself, so no update() method is necessary. It has two frequencies so will require two slices. The frequencies switch after a full scan (full cycle through the beams), therefore the interfacing between slices 0 and 1 should be ‘SCAN’.

twofsound.py
 1#!/usr/bin/python
 2
 3"""
 4    twofsound
 5    ~~~~~~~~~
 6    Standard operating Borealis experiment. Alternates transmitting in two different frequencies.
 7
 8    :copyright: 2023 SuperDARN Canada
 9"""
10
11import copy
12
13from experiment_prototype.experiment_prototype import ExperimentPrototype
14import borealis_experiments.superdarn_common_fields as scf
15
16
17class Twofsound(ExperimentPrototype):
18
19    def __init__(self, **kwargs):
20        cpid = 3503
21
22        if scf.IS_FORWARD_RADAR:
23            beams_to_use = scf.STD_16_FORWARD_BEAM_ORDER
24        else:
25            beams_to_use = scf.STD_16_REVERSE_BEAM_ORDER
26
27        if scf.options.site_id in ["cly", "rkn", "inv"]:
28            num_ranges = scf.POLARDARN_NUM_RANGES
29        if scf.options.site_id in ["sas", "pgr", "lab"]:
30            num_ranges = scf.STD_NUM_RANGES
31
32        tx_freq_1 = scf.COMMON_MODE_FREQ_1
33        tx_freq_2 = scf.COMMON_MODE_FREQ_2
34
35        if kwargs:
36            if 'freq1' in kwargs.keys():
37                tx_freq_1 = int(kwargs['freq1'])
38
39            if 'freq2' in kwargs.keys():
40                tx_freq_2 = int(kwargs['freq2'])
41
42        rxctrfreq = txctrfreq = int((tx_freq_1 + tx_freq_2) / 2)
43
44        slice_1 = {  # slice_id = 0, the first slice
45            "pulse_sequence": scf.SEQUENCE_7P,
46            "tau_spacing": scf.TAU_SPACING_7P,
47            "pulse_len": scf.PULSE_LEN_45KM,
48            "num_ranges": num_ranges,
49            "first_range": scf.STD_FIRST_RANGE,
50            "intt": scf.INTT_7P,  # duration of an integration, in ms
51            "beam_angle": scf.STD_16_BEAM_ANGLE,
52            "rx_beam_order": beams_to_use,
53            "tx_beam_order": beams_to_use,
54            "scanbound": scf.easy_scanbound(scf.INTT_7P, beams_to_use),
55            "freq": tx_freq_1,     # kHz
56            "txctrfreq": txctrfreq,
57            "rxctrfreq": rxctrfreq,
58            "acf": True,
59            "xcf": True,  # cross-correlation processing
60            "acfint": True,  # interferometer acfs
61        }
62
63        slice_2 = copy.deepcopy(slice_1)
64        slice_2['freq'] = tx_freq_2
65
66        super().__init__(cpid, comment_string='Twofsound classic scan-by-scan')
67
68        self.add_slice(slice_1)
69
70        self.add_slice(slice_2, interfacing_dict={0: 'SCAN'})
71

full_fov

See Full FOV Imaging for more information.

full_fov.py
  1#!/usr/bin/python
  2
  3"""
  4    full_fov
  5    ~~~~~~~~
  6    The mode transmits with a pre-calculated phase progression across the array which illuminates
  7    the full FOV, and receives on all antennas. The first pulse in each sequence starts on the 0.1
  8    second boundaries, to enable bistatic listening on other radars.
  9
 10    :copyright: 2022 SuperDARN Canada
 11    :author: Remington Rohel
 12"""
 13import numpy as np
 14
 15from utils.signals import get_phase_shift
 16import borealis_experiments.superdarn_common_fields as scf
 17from experiment_prototype.experiment_prototype import ExperimentPrototype
 18
 19
 20def rx_phase_pattern(beam_angle, freq_khz, antenna_count, antenna_spacing, offset=0.0):
 21    window = [0.08081232549588463, 0.12098514265395757, 0.23455777475180511, 0.4018918165398586,
 22              0.594054435182454, 0.7778186328978896, 0.9214100134552521, 1.0,
 23              1.0, 0.9214100134552521, 0.7778186328978896, 0.594054435182454,
 24              0.4018918165398586, 0.23455777475180511, 0.12098514265395757, 0.08081232549588463]
 25
 26    xcf_directions = {
 27        10400: [-28.8, -23.96, -19.92, -14.68, -11.24, -7.4, -3.48, -1.36,
 28                1.36, 3.48, 7.5, 11.24, 14.68, 19.92, 23.96, 28.8],
 29        10500: [-28.8, -23.86, -20.02, -14.78, -11.24, -7.5, -3.53, -1.41,
 30                1.41, 3.53, 7.5, 11.24, 14.78, 20.02, 23.86, 29.],
 31        10600: [-29., -23.76, -20.02, -14.78, -11.24, -7.5, -3.48, -1.32,
 32                1.32, 3.48, 7.6, 11.24, 14.78, 20.02, 23.76, 29.],
 33        10700: [-29.1, -23.76, -20.12, -14.88, -11.24, -7.6, -3.53, -1.32,
 34                1.32, 3.53, 7.6, 11.24, 14.88, 20.12, 23.76, 29.2],
 35        10800: [-29.3, -23.76, -20.12, -14.98, -11.24, -7.6, -3.53, -1.32,
 36                1.32, 3.53, 7.7, 11.24, 14.98, 20.12, 23.76, 29.4],
 37        10900: [-29.3, -23.66, -20.12, -15.08, -11.24, -7.7, -3.56, -1.32,
 38                1.32, 3.56, 7.7, 11.24, 15.08, 20.12, 23.66, 29.4],
 39        12200: [-28.4, -22.26, -17.62, -14.78, -11.24, -8.15, -4.96, -1.82,
 40                1.82, 4.96, 8.15, 11.24, 14.78, 17.62, 22.26, 28.5],
 41        12300: [-28.5, -22.46, -17.62, -14.78, -11.24, -8.1, -4.96, -1.82,
 42                1.82, 4.96, 8.1, 11.24, 14.78, 17.62, 22.46, 28.6],
 43        12500: [-28.7, -22.56, -17.62, -14.69, -11.24, -7.9, -4.88, -1.92,
 44                1.92, 4.88, 7.9, 11.24, 14.69, 17.62, 22.56, 28.8],
 45        13000: [-29.3, -22.86, -17.72, -14.58, -11.14, -7.6, -4.96, -2.12,
 46                2.12, 4.96, 7.6, 11.14, 14.58, 17.72, 22.86, 29.4],
 47        13100: [-29.6, -22.96, -17.82, -14.48, -11.34, -7.55, -4.93, -2.12,
 48                2.12, 4.93, 7.55, 11.34, 14.48, 17.82, 22.96, 29.7],
 49        13200: [-29.6, -23.06, -17.92, -14.48, -11.24, -7.6, -4.96, -2.12,
 50                2.12, 4.96, 7.6, 11.24, 14.48, 17.92, 23.06, 29.7],
 51        }
 52
 53    shift = get_phase_shift(xcf_directions[int(freq_khz)], freq_khz, antenna_count,
 54                            antenna_spacing, offset) * 0.9999999
 55
 56    # Apply a Hamming window to the antenna data streams of the main array
 57    if antenna_count == 16:
 58        shift = np.einsum('ij,j->ij', shift, np.array(window, dtype=np.float32))
 59
 60    return shift
 61
 62
 63class FullFOV(ExperimentPrototype):
 64    def __init__(self, **kwargs):
 65        """
 66        kwargs:
 67
 68        freq: int
 69
 70        """
 71        cpid = 3800
 72        super().__init__(cpid)
 73
 74        num_ranges = scf.STD_NUM_RANGES
 75        if scf.options.site_id in ["cly", "rkn", "inv"]:
 76            num_ranges = scf.POLARDARN_NUM_RANGES
 77
 78        # default frequency set here
 79        freq = scf.COMMON_MODE_FREQ_1
 80
 81        if kwargs:
 82            if 'freq' in kwargs.keys():
 83                freq = kwargs['freq']
 84
 85        print('Frequency set to {}'.format(freq))   # TODO: Log
 86
 87        num_antennas = scf.options.main_antenna_count
 88
 89        self.add_slice({  # slice_id = 0, there is only one slice.
 90            "pulse_sequence": scf.SEQUENCE_7P,
 91            "tau_spacing": scf.TAU_SPACING_7P,
 92            "pulse_len": scf.PULSE_LEN_45KM,
 93            "num_ranges": num_ranges,
 94            "first_range": scf.STD_FIRST_RANGE,
 95            "intt": scf.INTT_7P,  # duration of an integration, in ms
 96            "beam_angle": scf.STD_16_BEAM_ANGLE,
 97            "rx_beam_order": [[i for i in range(num_antennas)]],
 98            "tx_beam_order": [0],   # only one pattern
 99            "tx_antenna_pattern": scf.easy_widebeam,
100            "rx_antenna_pattern": rx_phase_pattern,
101            "freq": freq,  # kHz
102            "acf": True,
103            "xcf": True,  # cross-correlation processing
104            "acfint": True,  # interferometer acfs
105            #"align_sequences": True,     # align start of sequence to tenths of a second
106        })

bistatic_test

See Bistatic Experiments for more information.

bistatic_test.py
  1#!/usr/bin/python
  2
  3"""
  4    bistatic_test
  5    ~~~~~~~~~~~~~
  6    The mode transmits with a pre-calculated phase progression across the array which illuminates
  7    the full FOV, and receives on all antennas. The first pulse in each sequence starts on the 0.1
  8    second boundaries, to enable bistatic listening on other radars. This mode also chooses a
  9    frequency from another radar to listen in on, also across the entire FOV simultaneously.
 10
 11    :copyright: 2022 SuperDARN Canada
 12    :author: Remington Rohel
 13"""
 14
 15import borealis_experiments.superdarn_common_fields as scf
 16from experiment_prototype.experiment_prototype import ExperimentPrototype
 17
 18
 19class BistaticTest(ExperimentPrototype):
 20    """
 21    This experiment has different behaviour depending on the site that 
 22    is operating it. SAS, INV, and CLY operate normally (i.e. monostatically),
 23    while RKN and PGR 'listen in' on CLY, therefore operating as separate
 24    bistatic systems with CLY. All sites run a widebeam mode that 
 25    receives (and transmits for some sites) the entire FOV simultaneously.
 26    """
 27    def __init__(self, **kwargs):
 28        """
 29        kwargs:
 30            listen_to: str, one of the three-letter site codes. e.g. listen_to='cly'
 31            beam_order: str, beam order for tx. Only used if listen_to not specified. Format as '1,3,5,6-10',
 32                which will use beams [1, 3, 5, 6, 7, 8, 9, 10]
 33        """
 34        cpid = 3820
 35
 36        num_ranges = scf.STD_NUM_RANGES
 37
 38        common_freqs = {            # copied from superdarn_common_fields.py - September 2022
 39            'sas': [10500, 13000],
 40            'pgr': [10600, 13100],
 41            'rkn': [10900, 12300],
 42            'inv': [10800, 12200],
 43            'cly': [10700, 12500]
 44        }
 45
 46        # default frequency set here
 47        listen_to = kwargs.get('listen_to', scf.options.site_id)   # If 'listen_to' specified, tune in to that radar
 48        if listen_to not in common_freqs.keys():
 49            raise ValueError('Not a valid site ID: {}'.format(listen_to))
 50
 51        freq = common_freqs.get(listen_to)[0]
 52
 53        slice_0 = {
 54            "pulse_sequence": scf.SEQUENCE_7P,
 55            "tau_spacing": scf.TAU_SPACING_7P,
 56            "pulse_len": scf.PULSE_LEN_45KM,
 57            "num_ranges": num_ranges,
 58            "first_range": scf.STD_FIRST_RANGE,
 59            "intt": scf.INTT_7P,  # duration of an integration, in ms
 60            "beam_angle": scf.STD_16_BEAM_ANGLE,
 61            "freq": freq,  # kHz
 62            "scanbound": [i * 3.7 for i in range(len(scf.STD_16_BEAM_ANGLE))],  # align each aveperiod to 3.7s boundary
 63            "wait_for_first_scanbound": False,
 64            "align_sequences": True,     # align start of sequence to tenths of a second
 65        }
 66
 67        if 'listen_to' in kwargs.keys() and 'beam_order' in kwargs.keys():  # Mutually exclusive arguments
 68            raise ValueError('ERROR: Cannot specify both "listen_to" and "beam_order".')
 69
 70        if 'listen_to' not in kwargs.keys():  # Not listening to another radar, so must specify tx characteristics
 71            # beam_order set here
 72            if 'beam_order' in kwargs.keys():
 73                tx_beam_order = []
 74                beams = kwargs['beam_order'].split(',')
 75                for beam in beams:
 76                    # If a range was specified, include all numbers in that range (including endpoints)
 77                    if '-' in beam:
 78                        first_beam, last_beam = beam.split('-')
 79                        tx_beam_order.extend(range(int(first_beam), int(last_beam) + 1))
 80                    else:
 81                        tx_beam_order.append(int(beam))
 82                comment_str = 'Special tx beam order'
 83            else:
 84                tx_beam_order = [0]
 85                slice_0['tx_antenna_pattern'] = scf.easy_widebeam
 86                comment_str = 'Widebeam transmission'
 87
 88            slice_0['tx_beam_order'] = tx_beam_order
 89            rx_beam_order = [[i for i in range(len(scf.STD_16_BEAM_ANGLE))]] * len(tx_beam_order)
 90            slice_0['rx_beam_order'] = rx_beam_order    # Must have same first dimension as tx_beam_order
 91
 92        elif listen_to == scf.options.site_id:
 93            slice_0['rx_beam_order'] = [[i for i in range(len(scf.STD_16_BEAM_ANGLE))]]
 94            print('Defaulting to rx_only mode, "listen_to" set to this radar')
 95            comment_str = 'Widebeam listening mode'
 96
 97        else:
 98            slice_0['rx_beam_order'] = [[i for i in range(len(scf.STD_16_BEAM_ANGLE))]]
 99            comment_str = 'Bistatic widebeam mode - listening to {}'.format(listen_to)
100
101        super().__init__(cpid, comment_string=comment_str)
102
103        self.add_slice(slice_0)
104