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.
1"""
2 normalscan
3 ~~~~~~~~~~
4 Standard 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 experiment_prototype.experiment_prototype import ExperimentPrototype
11
12
13class Normalscan(ExperimentPrototype):
14 def __init__(self, **kwargs):
15 """
16 kwargs:
17
18 freq: int
19
20 """
21 cpid = 151
22 super().__init__(cpid)
23
24 if scf.IS_FORWARD_RADAR:
25 beams_to_use = scf.STD_16_FORWARD_BEAM_ORDER
26 else:
27 beams_to_use = scf.STD_16_REVERSE_BEAM_ORDER
28
29 # default frequency set here
30 freq = kwargs.get("freq", scf.COMMON_MODE_FREQ_1)
31
32 self.add_slice({ # slice_id = 0, there is only one slice.
33 "pulse_sequence": scf.SEQUENCE_7P,
34 "tau_spacing": scf.TAU_SPACING_7P,
35 "pulse_len": scf.PULSE_LEN_45KM,
36 "num_ranges": scf.STD_NUM_RANGES,
37 "first_range": scf.STD_FIRST_RANGE,
38 "intt": scf.INTT_7P, # duration of an integration, in ms
39 "beam_angle": scf.STD_16_BEAM_ANGLE,
40 "rx_beam_order": beams_to_use,
41 "tx_beam_order": beams_to_use,
42 "scanbound": scf.easy_scanbound(scf.INTT_7P, beams_to_use), #1 min scan
43 "freq" : freq, #kHz
44 "acf": True,
45 "xcf": True, # cross-correlation processing
46 "acfint": True, # interferometer acfs
47 "wait_for_first_scanbound": False,
48 })
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’.
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.
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_locations):
21 # Chebyshev 30-dB window
22 window = [0.2910, 0.3173, 0.4557, 0.6018, 0.7424, 0.8637, 0.9528, 1.0000,
23 1.0000, 0.9528, 0.8637, 0.7424, 0.6018, 0.4557, 0.3173, 0.2910]
24
25 adjusted_rx_beam_directions = {
26 10400: [-25., -21.2, -18.3, -15.5, -11.4, -7.7, -5., -2.1,
27 2.1, 5., 7.7, 11.4, 15.5, 18.3, 21.2, 25.],
28 10500: [-24.8, -20.7, -17.9, -14.8, -11.8, -8.6, -4.9, -1.9,
29 1.9, 4.9, 8.6, 11.8, 14.8, 17.9, 20.7, 24.8],
30 10600: [-25., -20.7, -17.8, -14.9, -11.9, -8.5, -4.8, -1.9,
31 1.9, 4.8, 8.5, 11.9, 14.9, 17.8, 20.7, 25.],
32 10700: [-24.5, -21.4, -18.1, -15.3, -11.5, -7.7, -5.1, -2.1,
33 2.1, 5.1, 7.7, 11.5, 15.3, 18.1, 21.4, 24.5],
34 10800: [-25., -20.9, -17.8, -15.3, -11.6, -7.8, -4.8, -2.1,
35 2.1, 4.8, 7.8, 11.6, 15.3, 17.8, 20.9, 25.],
36 10900: [-24.9, -20.9, -17.7, -15.3, -11.7, -7.8, -4.7, -2.,
37 2., 4.7, 7.8, 11.7, 15.3, 17.7, 20.9, 25.],
38 12200: [-24.4, -21.5, -17.7, -14.2, -11.5, -8.2, -4.8, -1.8,
39 1.8, 4.8, 8.2, 11.5, 14.2, 17.7, 21.5, 24.4],
40 12300: [-24.2, -21.5, -17.5, -14.4, -11.5, -7.9, -5., -2.1,
41 2.1, 5., 7.9, 11.5, 14.4, 17.5, 21.5, 24.2],
42 12500: [-24.3, -21.4, -17.8, -14.1, -11.4, -8.2, -4.9, -1.8,
43 1.8, 4.9, 8.2, 11.4, 14.1, 17.8, 21.4, 24.3],
44 13000: [-23.9, -21.5, -18.4, -14.8, -11.4, -7.8, -4.6, -2.4,
45 2.4, 4.6, 7.8, 11.4, 14.8, 18.4, 21.5, 23.9],
46 13100: [-24.5, -21., -18.4, -13.7, -11.1, -8.5, -4.7, -1.5,
47 1.5, 4.7, 8.5, 11.1, 13.7, 18.4, 21., 24.5],
48 13200: [-24.8, -21.6, -18.4, -14., -11.7, -8.4, -4.6, -2.2,
49 2.2, 4.6, 8.4, 11.7, 14., 18.4, 21.6, 24.8],
50 }
51
52 shift = get_phase_shift(adjusted_rx_beam_directions[int(freq_khz)], freq_khz, antenna_locations[:, 0]) * 0.9999999
53
54 # Apply a window to the antenna data streams of the main array
55 if antenna_locations.shape[0] == 16:
56 shift = np.einsum('ij,j->ij', shift, np.array(window, dtype=np.float32))
57
58 return shift
59
60
61class FullFOV(ExperimentPrototype):
62 def __init__(self, **kwargs):
63 """
64 kwargs:
65
66 freq: int
67
68 """
69 cpid = 3800
70 super().__init__(cpid)
71
72 # default frequency set here
73 freq = scf.COMMON_MODE_FREQ_1
74
75 if kwargs:
76 if 'freq' in kwargs.keys():
77 freq = kwargs['freq']
78
79 print('Frequency set to {}'.format(freq)) # TODO: Log
80
81 self.add_slice({ # slice_id = 0, there is only one slice.
82 "pulse_sequence": scf.SEQUENCE_7P,
83 "tau_spacing": scf.TAU_SPACING_7P,
84 "pulse_len": scf.PULSE_LEN_45KM,
85 "num_ranges": scf.STD_NUM_RANGES,
86 "first_range": scf.STD_FIRST_RANGE,
87 "intt": scf.INTT_7P, # duration of an integration, in ms
88 "beam_angle": scf.STD_16_BEAM_ANGLE,
89 "rx_beam_order": [[i for i in range(len(scf.STD_16_BEAM_ANGLE))]],
90 "tx_beam_order": [0], # only one pattern
91 "tx_antenna_pattern": scf.easy_widebeam,
92 "rx_antenna_pattern": rx_phase_pattern,
93 "freq": freq, # kHz
94 "acf": True,
95 "xcf": True, # cross-correlation processing
96 "acfint": True, # interferometer acfs
97 })
bistatic_test¶
See Bistatic Experiments for more information.
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_utils import decimation_scheme as dm
17from experiment_prototype.experiment_prototype import ExperimentPrototype
18
19
20def two_stage_filter():
21 """
22 Two-stage kaiser window scheme.
23
24 Works well with the following parameters:
25 sample_rate = 5e6
26 dm_rate = [30, 50]
27 transition_width = [150e3, 25e3]
28 cutoff_hz = [10e3, 5e3]
29 ripple_db = [115, 50]
30 """
31 sample_rate = 5e6 # 5 MHz
32 dm_rate = [30, 50] # downsampling rates after filters
33 transition_width = [150e3, 30e3] # transition from passband to stopband
34 cutoff_hz = [10e3, 5e3] # bandwidth for output of filter
35 ripple_db = [115, 50] # dB between passband and stopband
36 scaling_factors = [1000.0, 10000.0] # multiplicative factors for each filter stage
37
38 dm_rate_so_far = 1
39 stages = []
40 for i in range(2):
41 rate = sample_rate / dm_rate_so_far
42 taps = scaling_factors[i] * dm.create_firwin_filter_by_attenuation(
43 rate, transition_width[i], cutoff_hz[i], ripple_db[i]
44 )
45 stages.append(dm.DecimationStage(i, rate, dm_rate[i], taps.tolist()))
46 dm_rate_so_far *= dm_rate[i]
47
48 scheme = dm.DecimationScheme(sample_rate, sample_rate / dm_rate_so_far, stages=stages)
49
50 return scheme
51
52
53class BistaticTest(ExperimentPrototype):
54 """
55 This experiment has different behaviour depending on the site that
56 is operating it. SAS, INV, and CLY operate normally (i.e. monostatically),
57 while RKN and PGR 'listen in' on CLY, therefore operating as separate
58 bistatic systems with CLY. All sites run a widebeam mode that
59 receives (and transmits for some sites) the entire FOV simultaneously.
60 """
61 def __init__(self, **kwargs):
62 """
63 kwargs:
64 listen_to: str, one of the three-letter site codes. e.g. listen_to='cly'
65 beam_order: str, beam order for tx. Only used if listen_to not specified. Format as '1,3,5,6-10',
66 which will use beams [1, 3, 5, 6, 7, 8, 9, 10]
67 """
68 cpid = 3820
69
70 common_freqs = { # copied from superdarn_common_fields.py - March 2025
71 'sas': [10800, 13000],
72 'pgr': [10900, 13100],
73 'rkn': [10600, 12300],
74 'inv': [10500, 12200],
75 'cly': [10700, 12500]
76 }
77
78 # default frequency set here
79 listen_to = kwargs.get('listen_to', scf.options.site_id) # If 'listen_to' specified, tune in to that radar
80 if listen_to not in common_freqs.keys():
81 raise ValueError('Not a valid site ID: {}'.format(listen_to))
82
83 freq = common_freqs.get(listen_to)[0]
84
85 slice_0 = {
86 "pulse_sequence": scf.SEQUENCE_7P,
87 "tau_spacing": scf.TAU_SPACING_7P,
88 "pulse_len": scf.PULSE_LEN_45KM,
89 "num_ranges": scf.STD_NUM_RANGES,
90 "first_range": scf.STD_FIRST_RANGE,
91 "intt": scf.INTT_7P, # duration of an integration, in ms
92 "beam_angle": scf.STD_16_BEAM_ANGLE,
93 "freq": freq, # kHz
94 "scanbound": [i * 3.7 for i in range(len(scf.STD_16_BEAM_ANGLE))], # align each aveperiod to 3.7s boundary
95 "wait_for_first_scanbound": False,
96 "decimation_scheme": two_stage_filter(),
97 "align_sequences": True, # align start of sequence to tenths of a second
98 }
99
100 if 'listen_to' in kwargs.keys() and 'beam_order' in kwargs.keys(): # Mutually exclusive arguments
101 raise ValueError('ERROR: Cannot specify both "listen_to" and "beam_order".')
102
103 if 'listen_to' not in kwargs.keys(): # Not listening to another radar, so must specify tx characteristics
104 # beam_order set here
105 if 'beam_order' in kwargs.keys():
106 tx_beam_order = []
107 beams = kwargs['beam_order'].split(',')
108 for beam in beams:
109 # If a range was specified, include all numbers in that range (including endpoints)
110 if '-' in beam:
111 first_beam, last_beam = beam.split('-')
112 tx_beam_order.extend(range(int(first_beam), int(last_beam) + 1))
113 else:
114 tx_beam_order.append(int(beam))
115 comment_str = 'Special tx beam order'
116 else:
117 tx_beam_order = [0]
118 slice_0['tx_antenna_pattern'] = scf.easy_widebeam
119 comment_str = 'Widebeam transmission'
120
121 slice_0['tx_beam_order'] = tx_beam_order
122 rx_beam_order = [[i for i in range(len(scf.STD_16_BEAM_ANGLE))]] * len(tx_beam_order)
123 slice_0['rx_beam_order'] = rx_beam_order # Must have same first dimension as tx_beam_order
124
125 elif listen_to == scf.options.site_id:
126 slice_0['rx_beam_order'] = [[i for i in range(len(scf.STD_16_BEAM_ANGLE))]]
127 print('Defaulting to rx_only mode, "listen_to" set to this radar')
128 comment_str = 'Widebeam listening mode'
129
130 else:
131 slice_0['rx_beam_order'] = [[i for i in range(len(scf.STD_16_BEAM_ANGLE))]]
132 comment_str = 'Bistatic widebeam mode - listening to {}'.format(listen_to)
133
134 super().__init__(cpid, comment_string=comment_str)
135
136 self.add_slice(slice_0)
137