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)