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        slice_1 = {  # slice_id = 0, the first slice
43            "pulse_sequence": scf.SEQUENCE_7P,
44            "tau_spacing": scf.TAU_SPACING_7P,
45            "pulse_len": scf.PULSE_LEN_45KM,
46            "num_ranges": num_ranges,
47            "first_range": scf.STD_FIRST_RANGE,
48            "intt": scf.INTT_7P,  # duration of an integration, in ms
49            "beam_angle": scf.STD_16_BEAM_ANGLE,
50            "rx_beam_order": beams_to_use,
51            "tx_beam_order": beams_to_use,
52            "scanbound" : scf.easy_scanbound(scf.INTT_7P, beams_to_use),
53            "freq" : tx_freq_1,     # kHz
54            "acf": True,
55            "xcf": True,  # cross-correlation processing
56            "acfint": True,  # interferometer acfs
57        }
58
59        slice_2 = copy.deepcopy(slice_1)
60        slice_2['freq'] = tx_freq_2
61
62        list_of_slices = [slice_1, slice_2]
63        sum_of_freq = 0
64        for slice in list_of_slices:
65            sum_of_freq += slice['freq']# kHz, oscillator mixer frequency on the USRP for TX
66        rxctrfreq = txctrfreq = int(sum_of_freq/len(list_of_slices))
67
68
69        super().__init__(cpid, txctrfreq=txctrfreq, rxctrfreq=rxctrfreq,
70                comment_string='Twofsound classic scan-by-scan')
71
72        self.add_slice(slice_1)
73
74        self.add_slice(slice_2, interfacing_dict={0: 'SCAN'})
75

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"""
13
14import numpy as np
15from experiment_prototype.experiment_utils.sample_building import get_phase_shift
16
17import borealis_experiments.superdarn_common_fields as scf
18from experiment_prototype.experiment_prototype import ExperimentPrototype
19
20
21def rx_phase_pattern(beam_angle, freq_khz, antenna_count, antenna_spacing, offset=0.0):
22
23    xcf_directions = {
24        10400: [-28.8, -23.96, -19.92, -14.68, -11.24, -7.4, -3.48, -1.36,
25                1.36, 3.48, 7.5, 11.24, 14.68, 19.92, 23.96, 28.8],
26        10500: [-28.8, -23.86, -20.02, -14.78, -11.24, -7.5, -3.53, -1.41,
27                1.41, 3.53, 7.5, 11.24, 14.78, 20.02, 23.86, 29.],
28        10600: [-29., -23.76, -20.02, -14.78, -11.24, -7.5, -3.48, -1.32,
29                1.32, 3.48, 7.6, 11.24, 14.78, 20.02, 23.76, 29.],
30        10700: [-29.1, -23.76, -20.12, -14.88, -11.24, -7.6, -3.53, -1.32,
31                1.32, 3.53, 7.6, 11.24, 14.88, 20.12, 23.76, 29.2],
32        10800: [-29.3, -23.76, -20.12, -14.98, -11.24, -7.6, -3.53, -1.32,
33                1.32, 3.53, 7.7, 11.24, 14.98, 20.12, 23.76, 29.4],
34        10900: [-29.3, -23.66, -20.12, -15.08, -11.24, -7.7, -3.56, -1.32,
35                1.32, 3.56, 7.7, 11.24, 15.08, 20.12, 23.66, 29.4],
36        12200: [-28.4, -22.26, -17.62, -14.78, -11.24, -8.15, -4.96, -1.82,
37                1.82, 4.96, 8.15, 11.24, 14.78, 17.62, 22.26, 28.5],
38        12300: [-28.5, -22.46, -17.62, -14.78, -11.24, -8.1, -4.96, -1.82,
39                1.82, 4.96, 8.1, 11.24, 14.78, 17.62, 22.46, 28.6],
40        12500: [-28.7, -22.56, -17.62, -14.69, -11.24, -7.9, -4.88, -1.92,
41                1.92, 4.88, 7.9, 11.24, 14.69, 17.62, 22.56, 28.8],
42        13000: [-29.3, -22.86, -17.72, -14.58, -11.14, -7.6, -4.96, -2.12,
43                2.12, 4.96, 7.6, 11.14, 14.58, 17.72, 22.86, 29.4],
44        13100: [-29.6, -22.96, -17.82, -14.48, -11.34, -7.55, -4.93, -2.12,
45                2.12, 4.93, 7.55, 11.34, 14.48, 17.82, 22.96, 29.7],
46        13200: [-29.6, -23.06, -17.92, -14.48, -11.24, -7.6, -4.96, -2.12,
47                2.12, 4.96, 7.6, 11.24, 14.48, 17.92, 23.06, 29.7],
48        }
49
50    return get_phase_shift(xcf_directions[int(freq_khz)], freq_khz, antenna_count,
51                           antenna_spacing, offset) * 0.9999999
52
53
54class FullFOV(ExperimentPrototype):
55    def __init__(self, **kwargs):
56        """
57        kwargs:
58
59        freq: int
60
61        """
62        cpid = 3800
63        super().__init__(cpid)
64
65        num_ranges = scf.STD_NUM_RANGES
66        if scf.options.site_id in ["cly", "rkn", "inv"]:
67            num_ranges = scf.POLARDARN_NUM_RANGES
68
69        # default frequency set here
70        freq = scf.COMMON_MODE_FREQ_1
71
72        if kwargs:
73            if 'freq' in kwargs.keys():
74                freq = kwargs['freq']
75
76        print('Frequency set to {}'.format(freq))   # TODO: Log
77
78        num_antennas = scf.options.main_antenna_count
79
80        self.add_slice({  # slice_id = 0, there is only one slice.
81            "pulse_sequence": scf.SEQUENCE_7P,
82            "tau_spacing": scf.TAU_SPACING_7P,
83            "pulse_len": scf.PULSE_LEN_45KM,
84            "num_ranges": num_ranges,
85            "first_range": scf.STD_FIRST_RANGE,
86            "intt": scf.INTT_7P,  # duration of an integration, in ms
87            "beam_angle": scf.STD_16_BEAM_ANGLE,
88            "rx_beam_order": [[i for i in range(num_antennas)]],
89            "tx_beam_order": [0],   # only one pattern
90            "tx_antenna_pattern": scf.easy_widebeam,
91            "rx_antenna_pattern": rx_phase_pattern,
92            "freq": freq,  # kHz
93            "acf": True,
94            "xcf": True,  # cross-correlation processing
95            "acfint": True,  # interferometer acfs
96            "align_sequences": True,     # align start of sequence to tenths of a second
97        })

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