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#!/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’.
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.
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.
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