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.

normalscan

Normalscan is a very common experiment for SuperDARN. 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"""
 2normalscan
 3~~~~~~~~~~
 4Standard radar operating experiment. Transmits a single frequency signal.
 5
 6:copyright: 2023 SuperDARN Canada
 7"""
 8
 9import borealis_experiments.superdarn_common_fields as scf
10from utils.experiment_prototype import ExperimentPrototype
11
12
13class Normalscan(ExperimentPrototype):
14    cpid = 151
15
16    def __init__(self, **kwargs):
17        """
18        kwargs:
19
20        freq: int
21
22        """
23        super().__init__()
24
25        # default frequency set here
26        freq = kwargs.get("freq", scf.COMMON_MODE_FREQ_1)
27
28        self.add_slice(
29            {  # slice_id = 0, there is only one slice.
30                "pulse_sequence": scf.SEQUENCE_7P,
31                "tau_spacing": scf.TAU_SPACING_7P,
32                "pulse_len": scf.PULSE_LEN_45KM,
33                "num_ranges": scf.STD_NUM_RANGES,
34                "first_range": scf.STD_FIRST_RANGE,
35                "intt": scf.INTT_MS,  # duration of an integration, in ms
36                "beam_angle": scf.STD_BEAM_ANGLES,
37                "rx_beam_order": scf.STD_BEAM_ORDER,
38                "tx_beam_order": scf.STD_BEAM_ORDER,
39                "scanbound": scf.STD_SCANBOUND,
40                "freq": freq,  # kHz
41                "acf": True,
42                "xcf": True,  # cross-correlation processing
43                "acfint": True,  # interferometer acfs
44                "wait_for_first_scanbound": False,
45            }
46        )

twofsound

Twofsound is a common variant of the normalscan experiment for SuperDARN. 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"""
 4twofsound
 5~~~~~~~~~
 6Standard operating Borealis experiment. Alternates transmitting in two different frequencies.
 7
 8:copyright: 2023 SuperDARN Canada
 9"""
10
11import copy
12
13from utils.experiment_prototype import ExperimentPrototype
14import borealis_experiments.superdarn_common_fields as scf
15
16
17class Twofsound(ExperimentPrototype):
18    cpid = 3503
19
20    def __init__(self, **kwargs):
21
22        tx_freq_1 = int(kwargs.get("freq1", scf.COMMON_MODE_FREQ_1))
23        tx_freq_2 = int(kwargs.get("freq2", scf.COMMON_MODE_FREQ_2))
24
25        rxctrfreq = txctrfreq = int((tx_freq_1 + tx_freq_2) / 2)
26
27        slice_1 = {  # slice_id = 0, the first slice
28            "pulse_sequence": scf.SEQUENCE_7P,
29            "tau_spacing": scf.TAU_SPACING_7P,
30            "pulse_len": scf.PULSE_LEN_45KM,
31            "num_ranges": scf.STD_NUM_RANGES,
32            "first_range": scf.STD_FIRST_RANGE,
33            "intt": scf.INTT_MS,  # duration of an integration, in ms
34            "beam_angle": scf.STD_BEAM_ANGLES,
35            "rx_beam_order": scf.STD_BEAM_ORDER,
36            "tx_beam_order": scf.STD_BEAM_ORDER,
37            "scanbound": scf.STD_SCANBOUND,
38            "freq": tx_freq_1,  # kHz
39            "txctrfreq": txctrfreq,
40            "rxctrfreq": rxctrfreq,
41            "acf": True,
42            "xcf": True,  # cross-correlation processing
43            "acfint": True,  # interferometer acfs
44        }
45
46        slice_2 = copy.deepcopy(slice_1)
47        slice_2["freq"] = tx_freq_2
48
49        super().__init__(comment_string="Twofsound classic scan-by-scan")
50
51        self.add_slice(slice_1)
52
53        self.add_slice(slice_2, interfacing_dict={0: "SCAN"})

full_fov

See Full FOV Imaging for more information.

full_fov.py
  1#!/usr/bin/python
  2
  3"""
  4full_fov
  5~~~~~~~~
  6The mode transmits with a pre-calculated phase progression across the array which illuminates
  7the full FOV, and receives on all antennas. The first pulse in each sequence starts on the 0.1
  8second boundaries, to enable bistatic listening on other radars.
  9
 10:copyright: 2022 SuperDARN Canada
 11:author: Remington Rohel
 12"""
 13
 14import numpy as np
 15
 16from utils.signals import get_phase_shift
 17import borealis_experiments.superdarn_common_fields as scf
 18from utils.experiment_prototype import ExperimentPrototype
 19
 20
 21def rx_phase_pattern(beam_angle, freq_khz, antenna_locations):
 22    # Chebyshev 30-dB window
 23    window = [
 24        0.2910,
 25        0.3173,
 26        0.4557,
 27        0.6018,
 28        0.7424,
 29        0.8637,
 30        0.9528,
 31        1.0000,
 32        1.0000,
 33        0.9528,
 34        0.8637,
 35        0.7424,
 36        0.6018,
 37        0.4557,
 38        0.3173,
 39        0.2910,
 40    ]
 41
 42    adjusted_rx_beam_directions = {
 43        10400: [
 44            -25.0,
 45            -21.2,
 46            -18.3,
 47            -15.5,
 48            -11.4,
 49            -7.7,
 50            -5.0,
 51            -2.1,
 52            2.1,
 53            5.0,
 54            7.7,
 55            11.4,
 56            15.5,
 57            18.3,
 58            21.2,
 59            25.0,
 60        ],
 61        10500: [
 62            -24.8,
 63            -20.7,
 64            -17.9,
 65            -14.8,
 66            -11.8,
 67            -8.6,
 68            -4.9,
 69            -1.9,
 70            1.9,
 71            4.9,
 72            8.6,
 73            11.8,
 74            14.8,
 75            17.9,
 76            20.7,
 77            24.8,
 78        ],
 79        10600: [
 80            -25.0,
 81            -20.7,
 82            -17.8,
 83            -14.9,
 84            -11.9,
 85            -8.5,
 86            -4.8,
 87            -1.9,
 88            1.9,
 89            4.8,
 90            8.5,
 91            11.9,
 92            14.9,
 93            17.8,
 94            20.7,
 95            25.0,
 96        ],
 97        10700: [
 98            -24.5,
 99            -21.4,
100            -18.1,
101            -15.3,
102            -11.5,
103            -7.7,
104            -5.1,
105            -2.1,
106            2.1,
107            5.1,
108            7.7,
109            11.5,
110            15.3,
111            18.1,
112            21.4,
113            24.5,
114        ],
115        10800: [
116            -25.0,
117            -20.9,
118            -17.8,
119            -15.3,
120            -11.6,
121            -7.8,
122            -4.8,
123            -2.1,
124            2.1,
125            4.8,
126            7.8,
127            11.6,
128            15.3,
129            17.8,
130            20.9,
131            25.0,
132        ],
133        10900: [
134            -24.9,
135            -20.9,
136            -17.7,
137            -15.3,
138            -11.7,
139            -7.8,
140            -4.7,
141            -2.0,
142            2.0,
143            4.7,
144            7.8,
145            11.7,
146            15.3,
147            17.7,
148            20.9,
149            25.0,
150        ],
151        12200: [
152            -24.4,
153            -21.5,
154            -17.7,
155            -14.2,
156            -11.5,
157            -8.2,
158            -4.8,
159            -1.8,
160            1.8,
161            4.8,
162            8.2,
163            11.5,
164            14.2,
165            17.7,
166            21.5,
167            24.4,
168        ],
169        12300: [
170            -24.2,
171            -21.5,
172            -17.5,
173            -14.4,
174            -11.5,
175            -7.9,
176            -5.0,
177            -2.1,
178            2.1,
179            5.0,
180            7.9,
181            11.5,
182            14.4,
183            17.5,
184            21.5,
185            24.2,
186        ],
187        12500: [
188            -24.3,
189            -21.4,
190            -17.8,
191            -14.1,
192            -11.4,
193            -8.2,
194            -4.9,
195            -1.8,
196            1.8,
197            4.9,
198            8.2,
199            11.4,
200            14.1,
201            17.8,
202            21.4,
203            24.3,
204        ],
205        13000: [
206            -23.9,
207            -21.5,
208            -18.4,
209            -14.8,
210            -11.4,
211            -7.8,
212            -4.6,
213            -2.4,
214            2.4,
215            4.6,
216            7.8,
217            11.4,
218            14.8,
219            18.4,
220            21.5,
221            23.9,
222        ],
223        13100: [
224            -24.5,
225            -21.0,
226            -18.4,
227            -13.7,
228            -11.1,
229            -8.5,
230            -4.7,
231            -1.5,
232            1.5,
233            4.7,
234            8.5,
235            11.1,
236            13.7,
237            18.4,
238            21.0,
239            24.5,
240        ],
241        13200: [
242            -24.8,
243            -21.6,
244            -18.4,
245            -14.0,
246            -11.7,
247            -8.4,
248            -4.6,
249            -2.2,
250            2.2,
251            4.6,
252            8.4,
253            11.7,
254            14.0,
255            18.4,
256            21.6,
257            24.8,
258        ],
259    }
260
261    shift = (
262        get_phase_shift(
263            adjusted_rx_beam_directions[int(freq_khz)],
264            [freq_khz],
265            antenna_locations[:, 0],
266        )[0]
267        * 0.9999999
268    )
269
270    # Apply a window to the antenna data streams of the main array
271    if antenna_locations.shape[0] == 16:
272        shift = np.einsum("ij,j->ij", shift, np.array(window, dtype=np.float32))
273
274    return shift
275
276
277class FullFOV(ExperimentPrototype):
278    cpid = 3800
279
280    def __init__(self, **kwargs):
281        """
282        kwargs:
283
284        freq: int
285
286        """
287        super().__init__()
288
289        # default frequency set here
290        freq = kwargs.get("freq", scf.COMMON_MODE_FREQ_1)
291
292        self.add_slice(
293            {  # slice_id = 0, there is only one slice.
294                "pulse_sequence": scf.SEQUENCE_7P,
295                "tau_spacing": scf.TAU_SPACING_7P,
296                "pulse_len": scf.PULSE_LEN_45KM,
297                "num_ranges": scf.STD_NUM_RANGES,
298                "first_range": scf.STD_FIRST_RANGE,
299                "intt": scf.INTT_MS,  # duration of an integration, in ms
300                "beam_angle": scf.STD_BEAM_ANGLES,
301                "rx_beam_order": [[i for i in range(len(scf.STD_BEAM_ANGLES))]],
302                "tx_beam_order": [0],  # only one pattern
303                "tx_antenna_pattern": scf.easy_widebeam,
304                "rx_antenna_pattern": rx_phase_pattern,
305                "freq": freq,  # kHz
306                "acf": True,
307                "xcf": True,  # cross-correlation processing
308                "acfint": True,  # interferometer acfs
309            }
310        )

bistatic_test

See Bistatic Experiments for more information.

bistatic_test.py
  1#!/usr/bin/python
  2
  3"""
  4bistatic_test
  5~~~~~~~~~~~~~
  6The mode transmits with a pre-calculated phase progression across the array which illuminates
  7the full FOV, and receives on all antennas. The first pulse in each sequence starts on the 0.1
  8second boundaries, to enable bistatic listening on other radars. This mode also chooses a
  9frequency 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 borealis_experiments.superdarn_common_fields import STD_SCANBOUND
 17from utils import decimation_scheme as dm
 18from utils.experiment_prototype import ExperimentPrototype
 19
 20
 21def two_stage_filter():
 22    """
 23    Two-stage kaiser window scheme.
 24
 25    Works well with the following parameters:
 26    sample_rate = 5e6
 27    dm_rate = [30, 50]
 28    transition_width = [150e3, 25e3]
 29    cutoff_hz = [10e3, 5e3]
 30    ripple_db = [115, 50]
 31    """
 32    sample_rate = 5e6  # 5 MHz
 33    dm_rate = [30, 50]  # downsampling rates after filters
 34    transition_width = [150e3, 30e3]  # transition from passband to stopband
 35    cutoff_hz = [10e3, 5e3]  # bandwidth for output of filter
 36    ripple_db = [115, 50]  # dB between passband and stopband
 37    scaling_factors = [1000.0, 10000.0]  # multiplicative factors for each filter stage
 38
 39    dm_rate_so_far = 1
 40    stages = []
 41    for i in range(2):
 42        rate = sample_rate / dm_rate_so_far
 43        taps = scaling_factors[i] * dm.create_firwin_filter_by_attenuation(
 44            rate, transition_width[i], cutoff_hz[i], ripple_db[i]
 45        )
 46        stages.append(dm.DecimationStage(i, rate, dm_rate[i], taps.tolist()))
 47        dm_rate_so_far *= dm_rate[i]
 48
 49    scheme = dm.DecimationScheme(
 50        sample_rate, sample_rate / dm_rate_so_far, stages=stages
 51    )
 52
 53    return scheme
 54
 55
 56class BistaticTest(ExperimentPrototype):
 57    """
 58    This experiment has different behaviour depending on the site that
 59    is operating it. SAS, INV, and CLY operate normally (i.e. monostatically),
 60    while RKN and PGR 'listen in' on CLY, therefore operating as separate
 61    bistatic systems with CLY. All sites run a widebeam mode that
 62    receives (and transmits for some sites) the entire FOV simultaneously.
 63    """
 64
 65    cpid = 3820
 66
 67    def __init__(self, **kwargs):
 68        """
 69        kwargs:
 70            listen_to: str, one of the three-letter site codes. e.g. listen_to='cly'
 71            beam_order: str, beam order for tx. Only used if listen_to not specified. Format as '1,3,5,6-10',
 72                which will use beams [1, 3, 5, 6, 7, 8, 9, 10]
 73        """
 74
 75        common_freqs = {
 76            k: v["common"] for k, v in scf.__default_freqs__.items() if k != "default"
 77        }
 78
 79        # default frequency set here
 80        listen_to = kwargs.get(
 81            "listen_to", scf.config.site_id
 82        )  # If 'listen_to' specified, tune in to that radar
 83        if listen_to not in common_freqs.keys():
 84            raise ValueError("Not a valid site ID: {}".format(listen_to))
 85
 86        freq = common_freqs.get(listen_to)[0]
 87
 88        slice_0 = {
 89            "pulse_sequence": scf.SEQUENCE_7P,
 90            "tau_spacing": scf.TAU_SPACING_7P,
 91            "pulse_len": scf.PULSE_LEN_45KM,
 92            "num_ranges": scf.STD_NUM_RANGES,
 93            "first_range": scf.STD_FIRST_RANGE,
 94            "intt": scf.INTT_MS,
 95            "beam_angle": scf.STD_BEAM_ANGLES,
 96            "freq": freq,  # kHz
 97            "scanbound": STD_SCANBOUND,
 98            "wait_for_first_scanbound": False,
 99            "decimation_scheme": two_stage_filter(),
100            "align_sequences": True,  # align start of sequence to tenths of a second
101        }
102
103        if (
104            "listen_to" in kwargs.keys() and "beam_order" in kwargs.keys()
105        ):  # Mutually exclusive arguments
106            raise ValueError('ERROR: Cannot specify both "listen_to" and "beam_order".')
107
108        if (
109            "listen_to" not in kwargs.keys()
110        ):  # Not listening to another radar, so must specify tx characteristics
111            # beam_order set here
112            if "beam_order" in kwargs.keys():
113                tx_beam_order = []
114                beams = kwargs["beam_order"].split(",")
115                for beam in beams:
116                    # If a range was specified, include all numbers in that range (including endpoints)
117                    if "-" in beam:
118                        first_beam, last_beam = beam.split("-")
119                        tx_beam_order.extend(range(int(first_beam), int(last_beam) + 1))
120                    else:
121                        tx_beam_order.append(int(beam))
122                comment_str = "Special tx beam order"
123            else:
124                tx_beam_order = [0]
125                slice_0["tx_antenna_pattern"] = scf.easy_widebeam
126                comment_str = "Widebeam transmission"
127
128            slice_0["tx_beam_order"] = tx_beam_order
129            rx_beam_order = [[i for i in range(len(scf.STD_BEAM_ANGLES))]] * len(
130                tx_beam_order
131            )
132            slice_0["rx_beam_order"] = (
133                rx_beam_order  # Must have same first dimension as tx_beam_order
134            )
135
136        elif listen_to == scf.config.site_id:
137            slice_0["rx_beam_order"] = [[i for i in range(len(scf.STD_BEAM_ANGLES))]]
138            print('Defaulting to rx_only mode, "listen_to" set to this radar')
139            comment_str = "Widebeam listening mode"
140
141        else:
142            slice_0["rx_beam_order"] = [[i for i in range(len(scf.STD_BEAM_ANGLES))]]
143            comment_str = "Bistatic widebeam mode - listening to {}".format(listen_to)
144
145        super().__init__(comment_string=comment_str)
146
147        self.add_slice(slice_0)