spectre-core 0.0.15__py3-none-any.whl → 0.0.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spectre_core/capture_configs/__init__.py +3 -3
- spectre_core/capture_configs/_pnames.py +3 -1
- spectre_core/capture_configs/_ptemplates.py +16 -6
- spectre_core/capture_configs/_pvalidators.py +16 -0
- spectre_core/config/__init__.py +3 -2
- spectre_core/config/_paths.py +32 -0
- spectre_core/receivers/_base.py +1 -1
- spectre_core/receivers/_spec_names.py +18 -10
- spectre_core/receivers/plugins/_b200mini.py +20 -8
- spectre_core/receivers/plugins/_usrp.py +140 -5
- spectre_core/receivers/plugins/gr/_usrp.py +87 -10
- spectre_core/wgetting/_callisto.py +16 -7
- {spectre_core-0.0.15.dist-info → spectre_core-0.0.17.dist-info}/METADATA +1 -1
- {spectre_core-0.0.15.dist-info → spectre_core-0.0.17.dist-info}/RECORD +17 -17
- {spectre_core-0.0.15.dist-info → spectre_core-0.0.17.dist-info}/WHEEL +1 -1
- {spectre_core-0.0.15.dist-info → spectre_core-0.0.17.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.15.dist-info → spectre_core-0.0.17.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ from ._capture_modes import CaptureMode
|
|
9
9
|
from ._pvalidators import (
|
10
10
|
validate_fixed_center_frequency, validate_non_overlapping_steps, validate_num_samples_per_step,
|
11
11
|
validate_num_steps_per_sweep, validate_nyquist_criterion, validate_step_interval, validate_sweep_interval,
|
12
|
-
validate_swept_center_frequency, validate_window
|
12
|
+
validate_swept_center_frequency, validate_window, validate_sample_rate_with_master_clock_rate
|
13
13
|
)
|
14
14
|
from ._capture_config import CaptureConfig
|
15
15
|
from ._ptemplates import PTemplate, get_base_ptemplate
|
@@ -29,5 +29,5 @@ __all__ = [
|
|
29
29
|
"PConstraint", "PConstraint", "Bound", "OneOf", "EnforceSign", "PowerOfTwo", "make_base_capture_template", "PName",
|
30
30
|
"get_base_ptemplate", "BasePConstraint", "validate_fixed_center_frequency", "validate_non_overlapping_steps",
|
31
31
|
"validate_num_samples_per_step", "validate_num_steps_per_sweep", "validate_nyquist_criterion", "validate_step_interval",
|
32
|
-
"validate_sweep_interval", "validate_swept_center_frequency", "validate_window"
|
33
|
-
]
|
32
|
+
"validate_sweep_interval", "validate_swept_center_frequency", "validate_window", "validate_sample_rate_with_master_clock_rate"
|
33
|
+
]
|
@@ -300,14 +300,24 @@ _base_ptemplates: dict[PName, PTemplate] = {
|
|
300
300
|
pconstraints=[
|
301
301
|
EnforceSign.non_positive
|
302
302
|
]),
|
303
|
-
PName.
|
303
|
+
PName.GAIN: PTemplate(PName.GAIN,
|
304
304
|
float,
|
305
305
|
help = """
|
306
|
-
The
|
307
|
-
"""
|
308
|
-
|
309
|
-
|
310
|
-
|
306
|
+
The gain value for the SDR, in dB
|
307
|
+
"""
|
308
|
+
),
|
309
|
+
PName.MASTER_CLOCK_RATE: PTemplate(PName.MASTER_CLOCK_RATE,
|
310
|
+
int,
|
311
|
+
help = """
|
312
|
+
The primary reference clock for the SDR, specified in Hz.
|
313
|
+
"""
|
314
|
+
),
|
315
|
+
PName.WIRE_FORMAT: PTemplate(PName.WIRE_FORMAT,
|
316
|
+
str,
|
317
|
+
help = """
|
318
|
+
Controls the form of the data over the bus/network.
|
319
|
+
"""
|
320
|
+
),
|
311
321
|
PName.EVENT_HANDLER_KEY: PTemplate(PName.EVENT_HANDLER_KEY,
|
312
322
|
str,
|
313
323
|
help = """
|
@@ -184,6 +184,22 @@ def validate_step_interval(
|
|
184
184
|
f"derived api latency {api_retuning_latency} [s]; you may experience undefined behaviour!")
|
185
185
|
|
186
186
|
|
187
|
+
def validate_sample_rate_with_master_clock_rate(
|
188
|
+
parameters: Parameters,
|
189
|
+
) -> None:
|
190
|
+
"""Ensure that the master clock rate is an integer multiple of the sample rate.
|
191
|
+
|
192
|
+
:param parameters: The parameters to be validated.
|
193
|
+
:raises ValueError: If the master clock rate is not an integer multiple of the sample rate
|
194
|
+
"""
|
195
|
+
master_clock_rate = cast(int, parameters.get_parameter_value(PName.MASTER_CLOCK_RATE))
|
196
|
+
sample_rate = cast(int, parameters.get_parameter_value(PName.SAMPLE_RATE))
|
197
|
+
|
198
|
+
if master_clock_rate % sample_rate != 0:
|
199
|
+
raise ValueError(f"The master clock rate of {master_clock_rate} [Hz] is not an integer "
|
200
|
+
f"multiple of the sample rate {sample_rate} [Hz].")
|
201
|
+
|
202
|
+
|
187
203
|
def validate_fixed_center_frequency(
|
188
204
|
parameters: Parameters
|
189
205
|
) -> None:
|
spectre_core/config/__init__.py
CHANGED
@@ -6,7 +6,8 @@
|
|
6
6
|
"""General `spectre` package configurations."""
|
7
7
|
|
8
8
|
from ._paths import (
|
9
|
-
get_spectre_data_dir_path, get_batches_dir_path, get_configs_dir_path,
|
9
|
+
get_spectre_data_dir_path, get_batches_dir_path, get_configs_dir_path,
|
10
|
+
get_logs_dir_path, trim_spectre_data_dir_path
|
10
11
|
)
|
11
12
|
from ._time_formats import (
|
12
13
|
TimeFormat
|
@@ -14,5 +15,5 @@ from ._time_formats import (
|
|
14
15
|
|
15
16
|
__all__ = [
|
16
17
|
"get_spectre_data_dir_path", "get_batches_dir_path", "get_configs_dir_path", "get_logs_dir_path",
|
17
|
-
"TimeFormat"
|
18
|
+
"TimeFormat", "trim_spectre_data_dir_path"
|
18
19
|
]
|
spectre_core/config/_paths.py
CHANGED
@@ -115,3 +115,35 @@ def get_configs_dir_path(
|
|
115
115
|
:return: The directory path for configuration files.
|
116
116
|
"""
|
117
117
|
return _CONFIGS_DIR_PATH
|
118
|
+
|
119
|
+
|
120
|
+
def trim_spectre_data_dir_path(
|
121
|
+
full_path: str
|
122
|
+
) -> str:
|
123
|
+
"""Remove the `SPECTRE_DATA_DIR_PATH` prefix from a full file path.
|
124
|
+
|
125
|
+
This function returns the relative path of `full_path` with respect to
|
126
|
+
`SPECTRE_DATA_DIR_PATH`. It is useful for trimming absolute paths
|
127
|
+
to maintain consistency across different environments where the base
|
128
|
+
directory might differ.
|
129
|
+
|
130
|
+
:param full_path: The full file path to be trimmed.
|
131
|
+
:return: The relative path with `SPECTRE_DATA_DIR_PATH` removed.
|
132
|
+
"""
|
133
|
+
return os.path.relpath(full_path, _SPECTRE_DATA_DIR_PATH)
|
134
|
+
|
135
|
+
|
136
|
+
def add_spectre_data_dir_path(
|
137
|
+
rel_path: str
|
138
|
+
) -> str:
|
139
|
+
"""Prepend the `SPECTRE_DATA_DIR_PATH` prefix to a relative file path.
|
140
|
+
|
141
|
+
This function constructs an absolute path by joining the given relative
|
142
|
+
path with `SPECTRE_DATA_DIR_PATH`. It is useful for converting stored
|
143
|
+
relative paths back into full paths within the mounted directory.
|
144
|
+
|
145
|
+
:param rel_path: The relative file path to be appended.
|
146
|
+
:return: The full file path prefixed with `SPECTRE_DATA_DIR_PATH`.
|
147
|
+
"""
|
148
|
+
return os.path.join(_SPECTRE_DATA_DIR_PATH, rel_path)
|
149
|
+
|
spectre_core/receivers/_base.py
CHANGED
@@ -18,16 +18,24 @@ class SpecName(Enum):
|
|
18
18
|
Negative values indicate attenuation.
|
19
19
|
:ivar RF_GAIN_UPPER_BOUND: The upper bound for the radio frequency gain, in dB.
|
20
20
|
Negative values indicate attenuation.
|
21
|
+
:ivar GAIN_UPPER_BOUND: The upper bound for the gain, in dB.
|
22
|
+
:ivar WIRE_FORMATS: Supported data types transferred over the bus/network.
|
23
|
+
:ivar MASTER_CLOCK_RATE_LOWER_BOUND: The lower bound for the SDR reference clock rate, in Hz.
|
24
|
+
:ivar MASTER_CLOCK_RATE_UPPER_BOUND: The upper bound for the SDR reference clock rate, in Hz.
|
21
25
|
:ivar API_RETUNING_LATENCY: An empirical estimate of the delay between issuing a command
|
22
26
|
for a receiver to retune its center frequency and the actual physical update of the center frequency.
|
23
27
|
"""
|
24
|
-
FREQUENCY_LOWER_BOUND
|
25
|
-
FREQUENCY_UPPER_BOUND
|
26
|
-
SAMPLE_RATE_LOWER_BOUND
|
27
|
-
SAMPLE_RATE_UPPER_BOUND
|
28
|
-
BANDWIDTH_LOWER_BOUND
|
29
|
-
BANDWIDTH_UPPER_BOUND
|
30
|
-
BANDWIDTH_OPTIONS
|
31
|
-
IF_GAIN_UPPER_BOUND
|
32
|
-
RF_GAIN_UPPER_BOUND
|
33
|
-
|
28
|
+
FREQUENCY_LOWER_BOUND = "frequency_lower_bound"
|
29
|
+
FREQUENCY_UPPER_BOUND = "frequency_upper_bound"
|
30
|
+
SAMPLE_RATE_LOWER_BOUND = "sample_rate_lower_bound"
|
31
|
+
SAMPLE_RATE_UPPER_BOUND = "sample_rate_upper_bound"
|
32
|
+
BANDWIDTH_LOWER_BOUND = "bandwidth_lower_bound"
|
33
|
+
BANDWIDTH_UPPER_BOUND = "bandwidth_upper_bound"
|
34
|
+
BANDWIDTH_OPTIONS = "bandwidth_options"
|
35
|
+
IF_GAIN_UPPER_BOUND = "if_gain_upper_bound"
|
36
|
+
RF_GAIN_UPPER_BOUND = "rf_gain_upper_bound"
|
37
|
+
GAIN_UPPER_BOUND = "gain_upper_bound"
|
38
|
+
WIRE_FORMATS = "wire_formats"
|
39
|
+
MASTER_CLOCK_RATE_LOWER_BOUND = "master_clock_rate_lower_bound"
|
40
|
+
MASTER_CLOCK_RATE_UPPER_BOUND = "master_clock_rate_upper_bound"
|
41
|
+
API_RETUNING_LATENCY = "api_retuning_latency"
|
@@ -6,8 +6,8 @@ from dataclasses import dataclass
|
|
6
6
|
|
7
7
|
from ._receiver_names import ReceiverName
|
8
8
|
from ._usrp import (
|
9
|
-
get_pvalidator_fixed_center_frequency,
|
10
|
-
get_capture_template_fixed_center_frequency
|
9
|
+
get_pvalidator_fixed_center_frequency, get_pvalidator_swept_center_frequency,
|
10
|
+
get_capture_template_fixed_center_frequency, get_capture_template_swept_center_frequency
|
11
11
|
)
|
12
12
|
from .gr._usrp import CaptureMethod
|
13
13
|
from .._spec_names import SpecName
|
@@ -18,6 +18,7 @@ from .._register import register_receiver
|
|
18
18
|
class Mode:
|
19
19
|
"""An operating mode for the `B200mini` receiver."""
|
20
20
|
FIXED_CENTER_FREQUENCY = "fixed_center_frequency"
|
21
|
+
SWEPT_CENTER_FREQUENCY = "swept_center_frequency"
|
21
22
|
|
22
23
|
|
23
24
|
@register_receiver(ReceiverName.B200MINI)
|
@@ -26,12 +27,17 @@ class B200mini(BaseReceiver):
|
|
26
27
|
def _add_specs(
|
27
28
|
self
|
28
29
|
) -> None:
|
29
|
-
self.add_spec( SpecName.SAMPLE_RATE_LOWER_BOUND, 200e3 )
|
30
|
-
self.add_spec( SpecName.SAMPLE_RATE_UPPER_BOUND, 56e6 )
|
31
|
-
self.add_spec( SpecName.FREQUENCY_LOWER_BOUND
|
32
|
-
self.add_spec( SpecName.FREQUENCY_UPPER_BOUND
|
33
|
-
self.add_spec( SpecName.BANDWIDTH_LOWER_BOUND
|
34
|
-
self.add_spec( SpecName.BANDWIDTH_UPPER_BOUND
|
30
|
+
self.add_spec( SpecName.SAMPLE_RATE_LOWER_BOUND , 200e3 )
|
31
|
+
self.add_spec( SpecName.SAMPLE_RATE_UPPER_BOUND , 56e6 )
|
32
|
+
self.add_spec( SpecName.FREQUENCY_LOWER_BOUND , 70e6 )
|
33
|
+
self.add_spec( SpecName.FREQUENCY_UPPER_BOUND , 6e9 )
|
34
|
+
self.add_spec( SpecName.BANDWIDTH_LOWER_BOUND , 200e3 )
|
35
|
+
self.add_spec( SpecName.BANDWIDTH_UPPER_BOUND , 56e6 )
|
36
|
+
self.add_spec( SpecName.GAIN_UPPER_BOUND , 76 )
|
37
|
+
self.add_spec( SpecName.WIRE_FORMATS , ["sc8", "sc12", "sc16"])
|
38
|
+
self.add_spec( SpecName.MASTER_CLOCK_RATE_LOWER_BOUND, 5e6)
|
39
|
+
self.add_spec( SpecName.MASTER_CLOCK_RATE_UPPER_BOUND, 61.44e6)
|
40
|
+
self.add_spec( SpecName.API_RETUNING_LATENCY , 1e-5 ) # TODO: This is a ballpark, pending empirical testing
|
35
41
|
|
36
42
|
|
37
43
|
def _add_capture_methods(
|
@@ -39,6 +45,8 @@ class B200mini(BaseReceiver):
|
|
39
45
|
) -> None:
|
40
46
|
self.add_capture_method(Mode.FIXED_CENTER_FREQUENCY,
|
41
47
|
CaptureMethod.fixed_center_frequency)
|
48
|
+
self.add_capture_method(Mode.SWEPT_CENTER_FREQUENCY,
|
49
|
+
CaptureMethod.swept_center_frequency)
|
42
50
|
|
43
51
|
|
44
52
|
def _add_capture_templates(
|
@@ -46,6 +54,8 @@ class B200mini(BaseReceiver):
|
|
46
54
|
) -> None:
|
47
55
|
self.add_capture_template(Mode.FIXED_CENTER_FREQUENCY,
|
48
56
|
get_capture_template_fixed_center_frequency(self))
|
57
|
+
self.add_capture_template(Mode.SWEPT_CENTER_FREQUENCY,
|
58
|
+
get_capture_template_swept_center_frequency(self))
|
49
59
|
|
50
60
|
|
51
61
|
def _add_pvalidators(
|
@@ -53,6 +63,8 @@ class B200mini(BaseReceiver):
|
|
53
63
|
) -> None:
|
54
64
|
self.add_pvalidator(Mode.FIXED_CENTER_FREQUENCY,
|
55
65
|
get_pvalidator_fixed_center_frequency(self))
|
66
|
+
self.add_pvalidator(Mode.SWEPT_CENTER_FREQUENCY,
|
67
|
+
get_pvalidator_swept_center_frequency(self))
|
56
68
|
|
57
69
|
|
58
70
|
|
@@ -7,6 +7,7 @@ from typing import Callable, overload
|
|
7
7
|
from spectre_core.capture_configs import (
|
8
8
|
CaptureTemplate, CaptureMode, Parameters, Bound, PName,
|
9
9
|
get_base_capture_template, get_base_ptemplate, OneOf,
|
10
|
+
validate_sample_rate_with_master_clock_rate,
|
10
11
|
validate_fixed_center_frequency, validate_swept_center_frequency
|
11
12
|
)
|
12
13
|
from .._base import BaseReceiver
|
@@ -18,6 +19,17 @@ def get_pvalidator_fixed_center_frequency(
|
|
18
19
|
) -> Callable[[Parameters], None]:
|
19
20
|
def pvalidator(parameters: Parameters) -> None:
|
20
21
|
validate_fixed_center_frequency(parameters)
|
22
|
+
validate_sample_rate_with_master_clock_rate(parameters)
|
23
|
+
return pvalidator
|
24
|
+
|
25
|
+
|
26
|
+
def get_pvalidator_swept_center_frequency(
|
27
|
+
usrp_receiver: BaseReceiver
|
28
|
+
) -> Callable[[Parameters], None]:
|
29
|
+
def pvalidator(parameters: Parameters) -> None:
|
30
|
+
validate_swept_center_frequency(parameters,
|
31
|
+
usrp_receiver.get_spec(SpecName.API_RETUNING_LATENCY))
|
32
|
+
validate_sample_rate_with_master_clock_rate(parameters)
|
21
33
|
return pvalidator
|
22
34
|
|
23
35
|
|
@@ -27,17 +39,22 @@ def get_capture_template_fixed_center_frequency(
|
|
27
39
|
|
28
40
|
capture_template = get_base_capture_template( CaptureMode.FIXED_CENTER_FREQUENCY )
|
29
41
|
capture_template.add_ptemplate( get_base_ptemplate(PName.BANDWIDTH) )
|
30
|
-
capture_template.add_ptemplate( get_base_ptemplate(PName.
|
42
|
+
capture_template.add_ptemplate( get_base_ptemplate(PName.GAIN) )
|
43
|
+
capture_template.add_ptemplate( get_base_ptemplate(PName.WIRE_FORMAT) )
|
44
|
+
capture_template.add_ptemplate( get_base_ptemplate(PName.MASTER_CLOCK_RATE) )
|
31
45
|
|
46
|
+
# TODO: Delegate defaults to receiver subclasses. Currently, these are sensible defaults for the b200mini
|
32
47
|
capture_template.set_defaults(
|
33
|
-
(PName.BATCH_SIZE,
|
48
|
+
(PName.BATCH_SIZE, 4.0),
|
34
49
|
(PName.CENTER_FREQUENCY, 95800000),
|
35
|
-
(PName.SAMPLE_RATE,
|
36
|
-
(PName.BANDWIDTH,
|
50
|
+
(PName.SAMPLE_RATE, 2000000),
|
51
|
+
(PName.BANDWIDTH, 2000000),
|
37
52
|
(PName.WINDOW_HOP, 512),
|
38
53
|
(PName.WINDOW_SIZE, 1024),
|
39
54
|
(PName.WINDOW_TYPE, "blackman"),
|
40
|
-
(PName.
|
55
|
+
(PName.GAIN, 35),
|
56
|
+
(PName.WIRE_FORMAT, "sc16"),
|
57
|
+
(PName.MASTER_CLOCK_RATE, 40e6)
|
41
58
|
)
|
42
59
|
|
43
60
|
capture_template.add_pconstraint(
|
@@ -67,4 +84,122 @@ def get_capture_template_fixed_center_frequency(
|
|
67
84
|
)
|
68
85
|
]
|
69
86
|
)
|
87
|
+
capture_template.add_pconstraint(
|
88
|
+
PName.GAIN,
|
89
|
+
[
|
90
|
+
Bound(
|
91
|
+
lower_bound=0,
|
92
|
+
upper_bound=usrp_receiver.get_spec( SpecName.GAIN_UPPER_BOUND )
|
93
|
+
)
|
94
|
+
]
|
95
|
+
)
|
96
|
+
capture_template.add_pconstraint(
|
97
|
+
PName.WIRE_FORMAT,
|
98
|
+
[
|
99
|
+
OneOf(
|
100
|
+
usrp_receiver.get_spec( SpecName.WIRE_FORMATS )
|
101
|
+
)
|
102
|
+
]
|
103
|
+
)
|
104
|
+
capture_template.add_pconstraint(
|
105
|
+
PName.MASTER_CLOCK_RATE,
|
106
|
+
[
|
107
|
+
Bound(
|
108
|
+
lower_bound=usrp_receiver.get_spec( SpecName.MASTER_CLOCK_RATE_LOWER_BOUND ),
|
109
|
+
upper_bound=usrp_receiver.get_spec( SpecName.MASTER_CLOCK_RATE_UPPER_BOUND )
|
110
|
+
)
|
111
|
+
]
|
112
|
+
)
|
113
|
+
return capture_template
|
114
|
+
|
115
|
+
|
116
|
+
def get_capture_template_swept_center_frequency(
|
117
|
+
usrp_receiver: BaseReceiver
|
118
|
+
) -> CaptureTemplate:
|
119
|
+
|
120
|
+
capture_template = get_base_capture_template( CaptureMode.SWEPT_CENTER_FREQUENCY )
|
121
|
+
capture_template.add_ptemplate( get_base_ptemplate(PName.BANDWIDTH) )
|
122
|
+
capture_template.add_ptemplate( get_base_ptemplate(PName.GAIN) )
|
123
|
+
capture_template.add_ptemplate( get_base_ptemplate(PName.WIRE_FORMAT) )
|
124
|
+
capture_template.add_ptemplate( get_base_ptemplate(PName.MASTER_CLOCK_RATE) )
|
125
|
+
|
126
|
+
# TODO: Delegate defaults to receiver subclasses. Currently, these are sensible defaults for the b200mini
|
127
|
+
capture_template.set_defaults(
|
128
|
+
(PName.BATCH_SIZE, 4.0),
|
129
|
+
(PName.MIN_FREQUENCY, 95000000),
|
130
|
+
(PName.MAX_FREQUENCY, 105000000),
|
131
|
+
(PName.SAMPLES_PER_STEP, 30000),
|
132
|
+
(PName.FREQUENCY_STEP, 2000000),
|
133
|
+
(PName.SAMPLE_RATE, 2000000),
|
134
|
+
(PName.BANDWIDTH, 2000000),
|
135
|
+
(PName.WINDOW_HOP, 512),
|
136
|
+
(PName.WINDOW_SIZE, 1024),
|
137
|
+
(PName.WINDOW_TYPE, "blackman"),
|
138
|
+
(PName.GAIN, 35),
|
139
|
+
(PName.WIRE_FORMAT, "sc16"),
|
140
|
+
(PName.MASTER_CLOCK_RATE, 40e6)
|
141
|
+
)
|
142
|
+
|
143
|
+
capture_template.add_pconstraint(
|
144
|
+
PName.MIN_FREQUENCY,
|
145
|
+
[
|
146
|
+
Bound(
|
147
|
+
lower_bound=usrp_receiver.get_spec(SpecName.FREQUENCY_LOWER_BOUND),
|
148
|
+
upper_bound=usrp_receiver.get_spec(SpecName.FREQUENCY_UPPER_BOUND)
|
149
|
+
)
|
150
|
+
]
|
151
|
+
)
|
152
|
+
capture_template.add_pconstraint(
|
153
|
+
PName.MAX_FREQUENCY,
|
154
|
+
[
|
155
|
+
Bound(
|
156
|
+
lower_bound=usrp_receiver.get_spec(SpecName.FREQUENCY_LOWER_BOUND),
|
157
|
+
upper_bound=usrp_receiver.get_spec(SpecName.FREQUENCY_UPPER_BOUND)
|
158
|
+
)
|
159
|
+
]
|
160
|
+
)
|
161
|
+
capture_template.add_pconstraint(
|
162
|
+
PName.SAMPLE_RATE,
|
163
|
+
[
|
164
|
+
Bound(
|
165
|
+
lower_bound=usrp_receiver.get_spec(SpecName.SAMPLE_RATE_LOWER_BOUND),
|
166
|
+
upper_bound=usrp_receiver.get_spec(SpecName.SAMPLE_RATE_UPPER_BOUND)
|
167
|
+
)
|
168
|
+
]
|
169
|
+
)
|
170
|
+
capture_template.add_pconstraint(
|
171
|
+
PName.BANDWIDTH,
|
172
|
+
[
|
173
|
+
Bound(
|
174
|
+
lower_bound=usrp_receiver.get_spec(SpecName.BANDWIDTH_LOWER_BOUND),
|
175
|
+
upper_bound=usrp_receiver.get_spec(SpecName.BANDWIDTH_UPPER_BOUND),
|
176
|
+
)
|
177
|
+
]
|
178
|
+
)
|
179
|
+
capture_template.add_pconstraint(
|
180
|
+
PName.GAIN,
|
181
|
+
[
|
182
|
+
Bound(
|
183
|
+
lower_bound=0,
|
184
|
+
upper_bound=usrp_receiver.get_spec( SpecName.GAIN_UPPER_BOUND )
|
185
|
+
)
|
186
|
+
]
|
187
|
+
)
|
188
|
+
capture_template.add_pconstraint(
|
189
|
+
PName.WIRE_FORMAT,
|
190
|
+
[
|
191
|
+
OneOf(
|
192
|
+
usrp_receiver.get_spec( SpecName.WIRE_FORMATS )
|
193
|
+
)
|
194
|
+
]
|
195
|
+
)
|
196
|
+
capture_template.add_pconstraint(
|
197
|
+
PName.MASTER_CLOCK_RATE,
|
198
|
+
[
|
199
|
+
Bound(
|
200
|
+
lower_bound=usrp_receiver.get_spec( SpecName.MASTER_CLOCK_RATE_LOWER_BOUND ),
|
201
|
+
upper_bound=usrp_receiver.get_spec( SpecName.MASTER_CLOCK_RATE_UPPER_BOUND )
|
202
|
+
)
|
203
|
+
]
|
204
|
+
)
|
70
205
|
return capture_template
|
@@ -6,6 +6,9 @@ from functools import partial
|
|
6
6
|
from dataclasses import dataclass
|
7
7
|
import time
|
8
8
|
|
9
|
+
from logging import getLogger
|
10
|
+
_LOGGER = getLogger(__name__)
|
11
|
+
|
9
12
|
from spectre_core.capture_configs import Parameters, PName
|
10
13
|
from spectre_core.config import get_batches_dir_path
|
11
14
|
from ._base import capture, spectre_top_block
|
@@ -21,20 +24,24 @@ class _fixed_center_frequency(spectre_top_block):
|
|
21
24
|
from gnuradio import spectre
|
22
25
|
from gnuradio import uhd
|
23
26
|
|
24
|
-
#
|
25
|
-
sample_rate
|
26
|
-
|
27
|
-
center_freq
|
28
|
-
|
29
|
-
|
27
|
+
# Unpack capture config parameters
|
28
|
+
sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
|
29
|
+
gain = parameters.get_parameter_value(PName.GAIN)
|
30
|
+
center_freq = parameters.get_parameter_value(PName.CENTER_FREQUENCY)
|
31
|
+
master_clock_rate = parameters.get_parameter_value(PName.MASTER_CLOCK_RATE)
|
32
|
+
wire_format = parameters.get_parameter_value(PName.WIRE_FORMAT)
|
33
|
+
batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
|
34
|
+
bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
|
30
35
|
|
31
36
|
# Blocks
|
37
|
+
master_clock_rate = f"master_clock_rate={master_clock_rate}"
|
32
38
|
self.uhd_usrp_source_0 = uhd.usrp_source(
|
33
|
-
",".join(("", '')),
|
39
|
+
",".join(("", '', master_clock_rate)),
|
34
40
|
uhd.stream_args(
|
35
41
|
cpu_format="fc32",
|
42
|
+
otw_format=wire_format,
|
36
43
|
args='',
|
37
|
-
channels=
|
44
|
+
channels=[0],
|
38
45
|
),
|
39
46
|
)
|
40
47
|
self.uhd_usrp_source_0.set_samp_rate(sample_rate)
|
@@ -44,12 +51,14 @@ class _fixed_center_frequency(spectre_top_block):
|
|
44
51
|
self.uhd_usrp_source_0.set_antenna("RX2", 0)
|
45
52
|
self.uhd_usrp_source_0.set_bandwidth(bandwidth, 0)
|
46
53
|
self.uhd_usrp_source_0.set_rx_agc(False, 0)
|
47
|
-
self.uhd_usrp_source_0.
|
54
|
+
self.uhd_usrp_source_0.set_auto_dc_offset(False, 0)
|
55
|
+
self.uhd_usrp_source_0.set_auto_iq_balance(False, 0)
|
56
|
+
self.uhd_usrp_source_0.set_gain(gain, 0)
|
48
57
|
self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_batches_dir_path(),
|
49
58
|
tag,
|
50
59
|
batch_size,
|
51
60
|
sample_rate, False,
|
52
|
-
'
|
61
|
+
'rx_freq',
|
53
62
|
0)
|
54
63
|
|
55
64
|
|
@@ -57,6 +66,74 @@ class _fixed_center_frequency(spectre_top_block):
|
|
57
66
|
self.connect((self.uhd_usrp_source_0, 0), (self.spectre_batched_file_sink_0, 0))
|
58
67
|
|
59
68
|
|
69
|
+
class _swept_center_frequency(spectre_top_block):
|
70
|
+
def flowgraph(
|
71
|
+
self,
|
72
|
+
tag: str,
|
73
|
+
parameters: Parameters
|
74
|
+
) -> None:
|
75
|
+
# OOT module inline imports
|
76
|
+
from gnuradio import spectre
|
77
|
+
from gnuradio import uhd
|
78
|
+
|
79
|
+
# Unpack capture config parameters
|
80
|
+
sample_rate = parameters.get_parameter_value(PName.SAMPLE_RATE)
|
81
|
+
bandwidth = parameters.get_parameter_value(PName.BANDWIDTH)
|
82
|
+
min_frequency = parameters.get_parameter_value(PName.MIN_FREQUENCY)
|
83
|
+
max_frequency = parameters.get_parameter_value(PName.MAX_FREQUENCY)
|
84
|
+
frequency_step = parameters.get_parameter_value(PName.FREQUENCY_STEP)
|
85
|
+
samples_per_step = parameters.get_parameter_value(PName.SAMPLES_PER_STEP)
|
86
|
+
master_clock_rate = parameters.get_parameter_value(PName.MASTER_CLOCK_RATE)
|
87
|
+
master_clock_rate = master_clock_rate = parameters.get_parameter_value(PName.MASTER_CLOCK_RATE)
|
88
|
+
wire_format = parameters.get_parameter_value(PName.WIRE_FORMAT)
|
89
|
+
gain = parameters.get_parameter_value(PName.GAIN)
|
90
|
+
batch_size = parameters.get_parameter_value(PName.BATCH_SIZE)
|
91
|
+
|
92
|
+
# Blocks
|
93
|
+
_LOGGER.warning(f"USRP frequency sweep modes will not work as expected until a known bug is fixed in the USRP source block. "
|
94
|
+
f"Please refer to this GitHub issue for more information: https://github.com/gnuradio/gnuradio/issues/7725")
|
95
|
+
master_clock_rate = f"master_clock_rate={master_clock_rate}"
|
96
|
+
self.uhd_usrp_source_0 = uhd.usrp_source(
|
97
|
+
",".join(("", '', master_clock_rate)),
|
98
|
+
uhd.stream_args(
|
99
|
+
cpu_format="fc32",
|
100
|
+
otw_format=wire_format,
|
101
|
+
args='',
|
102
|
+
channels=[0],
|
103
|
+
),
|
104
|
+
)
|
105
|
+
self.uhd_usrp_source_0.set_samp_rate(sample_rate)
|
106
|
+
self.uhd_usrp_source_0.set_time_now(uhd.time_spec(time.time()), uhd.ALL_MBOARDS)
|
107
|
+
self.uhd_usrp_source_0.set_center_freq(min_frequency, 0)
|
108
|
+
self.uhd_usrp_source_0.set_antenna("RX2", 0)
|
109
|
+
self.uhd_usrp_source_0.set_bandwidth(bandwidth, 0)
|
110
|
+
self.uhd_usrp_source_0.set_rx_agc(False, 0)
|
111
|
+
self.uhd_usrp_source_0.set_auto_dc_offset(False, 0)
|
112
|
+
self.uhd_usrp_source_0.set_auto_iq_balance(False, 0)
|
113
|
+
self.uhd_usrp_source_0.set_gain(gain, 0)
|
114
|
+
|
115
|
+
self.spectre_sweep_driver_0 = spectre.sweep_driver(min_frequency,
|
116
|
+
max_frequency,
|
117
|
+
frequency_step,
|
118
|
+
sample_rate,
|
119
|
+
samples_per_step,
|
120
|
+
'freq')
|
121
|
+
|
122
|
+
self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_batches_dir_path(),
|
123
|
+
tag,
|
124
|
+
batch_size,
|
125
|
+
sample_rate,
|
126
|
+
True,
|
127
|
+
'rx_freq',
|
128
|
+
min_frequency)
|
129
|
+
|
130
|
+
# Connections
|
131
|
+
self.msg_connect((self.spectre_sweep_driver_0, 'freq'), (self.uhd_usrp_source_0, 'command'))
|
132
|
+
self.connect((self.uhd_usrp_source_0, 0), (self.spectre_batched_file_sink_0, 0))
|
133
|
+
self.connect((self.uhd_usrp_source_0, 0), (self.spectre_sweep_driver_0, 0))
|
134
|
+
|
135
|
+
|
60
136
|
@dataclass(frozen=True)
|
61
137
|
class CaptureMethod:
|
62
138
|
fixed_center_frequency = partial(capture, top_block_cls=_fixed_center_frequency)
|
139
|
+
swept_center_frequency = partial(capture, top_block_cls=_swept_center_frequency, max_noutput_items=1024)
|
@@ -8,7 +8,9 @@ import shutil
|
|
8
8
|
import gzip
|
9
9
|
from datetime import datetime
|
10
10
|
|
11
|
-
from spectre_core.config import
|
11
|
+
from spectre_core.config import (
|
12
|
+
get_spectre_data_dir_path, get_batches_dir_path, TimeFormat, trim_spectre_data_dir_path
|
13
|
+
)
|
12
14
|
|
13
15
|
from enum import Enum
|
14
16
|
|
@@ -129,30 +131,37 @@ def _get_batch_path(
|
|
129
131
|
|
130
132
|
def _unzip_file_to_batches(
|
131
133
|
gz_path: str
|
132
|
-
) ->
|
134
|
+
) -> str:
|
133
135
|
"""
|
134
136
|
Decompress a `.fit.gz` file and save it as a `.fits` batch file.
|
135
137
|
|
136
138
|
:param gz_path: Path to the `.fit.gz` file.
|
139
|
+
:return: The file path of the newly created batch file, relative to `SPECTRE_DATA_DIR_PATH`.
|
137
140
|
"""
|
138
141
|
fits_path = _get_batch_path(gz_path)
|
139
142
|
with gzip.open(gz_path, "rb") as f_in, open(fits_path, "wb") as f_out:
|
140
143
|
shutil.copyfileobj(f_in, f_out)
|
144
|
+
return trim_spectre_data_dir_path(f_out.name)
|
141
145
|
|
142
146
|
|
143
147
|
def _unzip_to_batches(
|
144
148
|
tmp_dir: str
|
145
|
-
) ->
|
149
|
+
) -> list[str]:
|
146
150
|
"""
|
147
151
|
Decompress all `.gz` files in a temporary directory and save them as `spectre`
|
148
152
|
batch files.
|
149
153
|
|
150
154
|
:param tmp_dir: Path to the temporary directory containing `.gz` files.
|
155
|
+
:return: A list of file names of all newly created batch files, relative to `SPECTRE_DATA_DIR_PATH`.
|
151
156
|
"""
|
157
|
+
batch_file_names = []
|
152
158
|
for entry in os.scandir(tmp_dir):
|
153
159
|
if entry.is_file() and entry.name.endswith(".gz"):
|
154
|
-
_unzip_file_to_batches(entry.path)
|
160
|
+
batch_file_names.append( _unzip_file_to_batches(entry.path) )
|
155
161
|
os.remove(entry.path)
|
162
|
+
shutil.rmtree(tmp_dir)
|
163
|
+
return batch_file_names
|
164
|
+
|
156
165
|
|
157
166
|
|
158
167
|
def _wget_callisto_data(
|
@@ -186,7 +195,7 @@ def download_callisto_data(
|
|
186
195
|
year: int,
|
187
196
|
month: int,
|
188
197
|
day: int
|
189
|
-
) ->
|
198
|
+
) -> list[str]:
|
190
199
|
"""
|
191
200
|
Download and decompress e-Callisto FITS files, saving them as `spectre` batch files.
|
192
201
|
|
@@ -194,6 +203,7 @@ def download_callisto_data(
|
|
194
203
|
:param year: Year of the observation.
|
195
204
|
:param month: Month of the observation.
|
196
205
|
:param day: Day of the observation.
|
206
|
+
:return: A list of file names of all newly created batch files, relative to `SPECTRE_DATA_DIR_PATH`.
|
197
207
|
"""
|
198
208
|
tmp_dir = os.path.join(get_spectre_data_dir_path(), "tmp")
|
199
209
|
# if there are any residual files in the temporary directory, remove them.
|
@@ -202,5 +212,4 @@ def download_callisto_data(
|
|
202
212
|
os.makedirs(tmp_dir, exist_ok=True)
|
203
213
|
|
204
214
|
_wget_callisto_data(instrument_code.value, year, month, day, tmp_dir)
|
205
|
-
_unzip_to_batches(tmp_dir)
|
206
|
-
shutil.rmtree(tmp_dir)
|
215
|
+
return sorted( _unzip_to_batches(tmp_dir) )
|
@@ -11,17 +11,17 @@ spectre_core/batches/_register.py,sha256=dSQC8KXj_jG8EiPwmKPdV0HSSalIZLaWt-8E29s
|
|
11
11
|
spectre_core/batches/plugins/_batch_keys.py,sha256=8v0KE1n0NAQX0i9SwwB4Lgkct7Q_jna-H5S0Gs6p1qg,544
|
12
12
|
spectre_core/batches/plugins/_callisto.py,sha256=ijm-VzGGlLQJjB2eQWw-04R6belCJ_NzSL9Jr7VTu2Q,6259
|
13
13
|
spectre_core/batches/plugins/_iq_stream.py,sha256=DSto5ZzPk7A4VAe_HUAKNd2M9NuWqxcL--_Vneo08Bc,12583
|
14
|
-
spectre_core/capture_configs/__init__.py,sha256=
|
14
|
+
spectre_core/capture_configs/__init__.py,sha256=bhwp1Kf2llzd8HpkMPxeM_ZJoIoCFLQoMZJ44AAH6s0,1738
|
15
15
|
spectre_core/capture_configs/_capture_config.py,sha256=ngbIzySgjsgBRJyrfgYZrJtr0wqcKDn8xBH0T1vi-yY,4546
|
16
16
|
spectre_core/capture_configs/_capture_modes.py,sha256=uFBMwHYJCDqqQfYJvAUKzKXTmSBANRZMLlRSKV8WRb8,898
|
17
17
|
spectre_core/capture_configs/_capture_templates.py,sha256=nSAxhOh1DeBzYY16evgOTkDuxRugGG5weJsICUouLPQ,10353
|
18
18
|
spectre_core/capture_configs/_parameters.py,sha256=9KoNuwzDKtnyeju53fkImi1SeUjDn89cNwDL8zxX-YU,5563
|
19
19
|
spectre_core/capture_configs/_pconstraints.py,sha256=bnl1m6M9iyo5KOsPKT_arwrrAZbxRKXVwTHQACzvs2g,5227
|
20
|
-
spectre_core/capture_configs/_pnames.py,sha256=
|
21
|
-
spectre_core/capture_configs/_ptemplates.py,sha256=
|
22
|
-
spectre_core/capture_configs/_pvalidators.py,sha256=
|
23
|
-
spectre_core/config/__init__.py,sha256=
|
24
|
-
spectre_core/config/_paths.py,sha256=
|
20
|
+
spectre_core/capture_configs/_pnames.py,sha256=3bJhT4kM5WuiH9SSs4UyBcq_mDybzx-bRiWJcIOqeLA,1975
|
21
|
+
spectre_core/capture_configs/_ptemplates.py,sha256=Pepzs7eq31WPdTYmIICJjIVOzf-j5LdA2SD_f2RUZ54,25609
|
22
|
+
spectre_core/capture_configs/_pvalidators.py,sha256=fRlFL3rTIpDU2G4iUKp3Lb_jFX5ddHNuUbY5hTzZQQg,10325
|
23
|
+
spectre_core/config/__init__.py,sha256=TwQAiDUJLWuwx4IFlStG7Mvb9EFU3u82z5ilPfVH6e0,565
|
24
|
+
spectre_core/config/_paths.py,sha256=k9jyPlaFHlM2pAwqPWlpivdc0jCiFFpy4tOny1uxc64,5224
|
25
25
|
spectre_core/config/_time_formats.py,sha256=gS0j5zIvBhnV7KMYvTloloIbVwmCYn8MMKn3zNeQ4Xc,857
|
26
26
|
spectre_core/jobs/__init__.py,sha256=WKTvxvpciedm6tsKjU02iXJhIdNsMDt-BnMVwVme2Bo,412
|
27
27
|
spectre_core/jobs/_jobs.py,sha256=gGpxsLZZ7EdXBYGH-r_pKnRGWSapr78E5SK_VnulaGg,3844
|
@@ -46,33 +46,33 @@ spectre_core/post_processing/plugins/_event_handler_keys.py,sha256=LPA71kKsaLPyT
|
|
46
46
|
spectre_core/post_processing/plugins/_fixed_center_frequency.py,sha256=24LB3iDMa2KvgM7jyRnEr7Y45VK46bUT7Nj8bXGiGwc,5231
|
47
47
|
spectre_core/post_processing/plugins/_swept_center_frequency.py,sha256=fzPAZ4zK1DRAHushUB-JS2IgKAuXyP4hdBLZJX6jBmo,20971
|
48
48
|
spectre_core/receivers/__init__.py,sha256=uT3XNn12gSfK_M_GE__sK-TRWra92qkttnUJ-_6uViE,714
|
49
|
-
spectre_core/receivers/_base.py,sha256=
|
49
|
+
spectre_core/receivers/_base.py,sha256=PSC8XWnlVyxkyLa_64Haq4bZTTIxGf42_X8z8_9gYng,8972
|
50
50
|
spectre_core/receivers/_factory.py,sha256=xViruRYb9kFjndY_GmhK5vNQ0VxCE3rbKB3uzqtp_zE,2018
|
51
51
|
spectre_core/receivers/_register.py,sha256=jWS3Q_ZOZcqUFunxJwR5VRZN4_eS3LzcP-5edQdmj4c,1366
|
52
|
-
spectre_core/receivers/_spec_names.py,sha256=
|
52
|
+
spectre_core/receivers/_spec_names.py,sha256=V370aeclNqW0hGdbx6C8v61GPnPDkSMol692igv0Aq4,2446
|
53
53
|
spectre_core/receivers/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
|
-
spectre_core/receivers/plugins/_b200mini.py,sha256=
|
54
|
+
spectre_core/receivers/plugins/_b200mini.py,sha256=l0baAg6tRdAfT7N1eAmHoQ9fJlKQO8-AsPZ7YAyc8t4,2890
|
55
55
|
spectre_core/receivers/plugins/_receiver_names.py,sha256=b8cUln47Ya8rr8vnnHQEGa7j9Hf-hJsNbTNtGuZaWlA,498
|
56
56
|
spectre_core/receivers/plugins/_rsp1a.py,sha256=zgPemoRjd93CvI9bRg7PHzgAUHB9xXg5LCvkLHa3jbI,2641
|
57
57
|
spectre_core/receivers/plugins/_rspduo.py,sha256=Sim1TXcG_g1ohWKwvMk7XzrQC8NGydCMGZEf9hhvX-0,3230
|
58
58
|
spectre_core/receivers/plugins/_sdrplay_receiver.py,sha256=bdfjGvKSFW_jfQFIQQcIIf8UfbuMwW04SK6YMNMIINI,5737
|
59
59
|
spectre_core/receivers/plugins/_test.py,sha256=t8oE4zQLYYWJV6SMIZAlNHs9mig263QJmP6XRLyF0RU,8277
|
60
|
-
spectre_core/receivers/plugins/_usrp.py,sha256=
|
60
|
+
spectre_core/receivers/plugins/_usrp.py,sha256=Z0KxOcreic4Bxefeyy6xJ8ips8Q76XBiQcOui56QClU,7603
|
61
61
|
spectre_core/receivers/plugins/gr/__init__.py,sha256=oFSWmGoXQLK5X5xHvWzTdNr9amuaiiGjZirXZVogACU,154
|
62
62
|
spectre_core/receivers/plugins/gr/_base.py,sha256=woFF6498aLIDf4EC7aD-TolY9LtZBqlLy-Vai_gfIvc,2571
|
63
63
|
spectre_core/receivers/plugins/gr/_rsp1a.py,sha256=JLMRCNouSblNizth7EmLrtWvbnpD-5zd7uhc6Pjcopw,6288
|
64
64
|
spectre_core/receivers/plugins/gr/_rspduo.py,sha256=s5g5nOQHiVHCESaHdj2VmgXl_LaiMaqXhTJfUbC7Iew,9811
|
65
65
|
spectre_core/receivers/plugins/gr/_test.py,sha256=YA3JKdJ_CT3J93pqHUjltKrQUoaDXzUloEJEXVHH718,5780
|
66
|
-
spectre_core/receivers/plugins/gr/_usrp.py,sha256=
|
66
|
+
spectre_core/receivers/plugins/gr/_usrp.py,sha256=qhJ701mU5hVI9X7QQN6V4xmrFi2VlDMTz3XfjbCkxRo,6635
|
67
67
|
spectre_core/spectrograms/__init__.py,sha256=AsiOmn9XrAAHUvK-fdhRddAxX4M1Wd6TCtdmxGkl3FA,763
|
68
68
|
spectre_core/spectrograms/_analytical.py,sha256=Axnt9JOJnWXRRuVU5nHPz5QU09KoWqNZkR5NnTX6kMY,11356
|
69
69
|
spectre_core/spectrograms/_array_operations.py,sha256=79vddwWqR5i6OkeD5L_84t8svslpmzW4b8uxbiCQl0I,7553
|
70
70
|
spectre_core/spectrograms/_spectrogram.py,sha256=WhHEt_QpmzspDqYlzdZcJ8CAXxRfs8-JfP0T3NHpjLQ,28205
|
71
71
|
spectre_core/spectrograms/_transform.py,sha256=WZ5jAe3bOpNldxHDSHPf8Q_1ifBdWqXB_mlF6DL1VuE,11734
|
72
72
|
spectre_core/wgetting/__init__.py,sha256=UkS0Z0wuuqpoZ1EL35wJcDpjBiAaZgdZ7064yGESxNE,341
|
73
|
-
spectre_core/wgetting/_callisto.py,sha256=
|
74
|
-
spectre_core-0.0.
|
75
|
-
spectre_core-0.0.
|
76
|
-
spectre_core-0.0.
|
77
|
-
spectre_core-0.0.
|
78
|
-
spectre_core-0.0.
|
73
|
+
spectre_core/wgetting/_callisto.py,sha256=B2cb1sqF7SGgHfJ2YDOY-6hbJAqQfwK3NH1NS0Jwfxg,7469
|
74
|
+
spectre_core-0.0.17.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
75
|
+
spectre_core-0.0.17.dist-info/METADATA,sha256=qW29apYQX3YJ39d5Zjc9hlYlSwwQ2E6PdzQ_rv17rH4,42100
|
76
|
+
spectre_core-0.0.17.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
77
|
+
spectre_core-0.0.17.dist-info/top_level.txt,sha256=-UsyjpFohXgZpgcZ9QbVeXhsIyF3Am8RxNFNDV_Ta2Y,13
|
78
|
+
spectre_core-0.0.17.dist-info/RECORD,,
|
File without changes
|
File without changes
|