spectre-core 0.0.24__py3-none-any.whl → 0.0.26__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.
Files changed (33) hide show
  1. spectre_core/__init__.py +1 -1
  2. spectre_core/capture_configs/_capture_config.py +2 -0
  3. spectre_core/plotting/_panel_stack.py +11 -0
  4. spectre_core/receivers/__init__.py +11 -8
  5. spectre_core/receivers/_factory.py +16 -11
  6. spectre_core/receivers/_receiver.py +246 -0
  7. spectre_core/receivers/_register.py +3 -3
  8. spectre_core/receivers/{_spec_names.py → _specs.py} +42 -7
  9. spectre_core/receivers/plugins/_b200mini.py +219 -34
  10. spectre_core/receivers/plugins/{gr/_usrp.py → _b200mini_gr.py} +38 -61
  11. spectre_core/receivers/plugins/_custom.py +20 -0
  12. spectre_core/receivers/plugins/{gr/_base.py → _gr.py} +1 -1
  13. spectre_core/receivers/plugins/_receiver_names.py +5 -3
  14. spectre_core/receivers/plugins/_rsp1a.py +38 -43
  15. spectre_core/receivers/plugins/_rsp1a_gr.py +112 -0
  16. spectre_core/receivers/plugins/_rspduo.py +47 -57
  17. spectre_core/receivers/plugins/_rspduo_gr.py +165 -0
  18. spectre_core/receivers/plugins/_sdrplay_receiver.py +146 -42
  19. spectre_core/receivers/plugins/_signal_generator.py +225 -0
  20. spectre_core/receivers/plugins/_signal_generator_gr.py +77 -0
  21. spectre_core/spectrograms/_analytical.py +18 -18
  22. {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/METADATA +1 -1
  23. {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/RECORD +26 -27
  24. spectre_core/receivers/_base.py +0 -242
  25. spectre_core/receivers/plugins/_test.py +0 -225
  26. spectre_core/receivers/plugins/_usrp.py +0 -213
  27. spectre_core/receivers/plugins/gr/__init__.py +0 -3
  28. spectre_core/receivers/plugins/gr/_rsp1a.py +0 -127
  29. spectre_core/receivers/plugins/gr/_rspduo.py +0 -202
  30. spectre_core/receivers/plugins/gr/_test.py +0 -117
  31. {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/WHEEL +0 -0
  32. {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/licenses/LICENSE +0 -0
  33. {spectre_core-0.0.24.dist-info → spectre_core-0.0.26.dist-info}/top_level.txt +0 -0
@@ -52,14 +52,14 @@ class _AnalyticalFactory:
52
52
  def __init__(self) -> None:
53
53
  """Initialises an instance of the `_AnalyticalFactory` class."""
54
54
  self._builders: dict[str, Callable[[int, CaptureConfig], Spectrogram]] = {
55
- "cosine_signal_1": self._cosine_signal_1,
56
- "tagged_staircase": self._tagged_staircase,
55
+ "cosine_wave": self._cosine_wave,
56
+ "constant_staircase": self._constant_staircase,
57
57
  }
58
58
 
59
59
  @property
60
60
  def builders(self) -> dict[str, Callable[[int, CaptureConfig], Spectrogram]]:
61
61
  """
62
- Provides a mapping from each `Test` receiver mode to its corresponding builder method.
62
+ Provides a mapping from each `SignalGenerator` receiver mode to its corresponding builder method.
63
63
 
64
64
  Each builder method generates the expected spectrograms for a session run in the associated mode.
65
65
  """
@@ -67,23 +67,23 @@ class _AnalyticalFactory:
67
67
 
68
68
  @property
69
69
  def test_modes(self) -> list[str]:
70
- """Returns the available modes for the `Test` receiver."""
70
+ """Returns the available modes for the `SignalGenerator` receiver."""
71
71
  return list(self.builders.keys())
72
72
 
73
73
  def get_spectrogram(
74
74
  self, num_spectrums: int, capture_config: CaptureConfig
75
75
  ) -> Spectrogram:
76
76
  """
77
- Generates an analytical spectrogram based on the capture config for a `Test` receiver.
77
+ Generates an analytical spectrogram based on the capture config for a `SignalGenerator` receiver.
78
78
 
79
79
  :param num_spectrums: The number of spectrums to include in the output spectrogram.
80
- :param capture_config: The capture config used for `Test` receiver data capture.
81
- :raises ValueError: Raised if the capture config is not associated with a `Test` receiver.
82
- :raises ModeNotFoundError: Raised if the specified `Test` mode in the capture config lacks
80
+ :param capture_config: The capture config used for `SignalGenerator` receiver data capture.
81
+ :raises ValueError: Raised if the capture config is not associated with a `SignalGenerator` receiver.
82
+ :raises ModeNotFoundError: Raised if the specified `SignalGenerator` mode in the capture config lacks
83
83
  a corresponding builder method.
84
- :return: The expected spectrogram for running a session with the `Test` receiver in the specified mode.
84
+ :return: The expected spectrogram for running a session with the `SignalGenerator` receiver in the specified mode.
85
85
  """
86
- if capture_config.receiver_name != "test":
86
+ if capture_config.receiver_name != "signal_generator":
87
87
  raise ValueError(
88
88
  f"Input capture config must correspond to the test receiver"
89
89
  )
@@ -95,10 +95,10 @@ class _AnalyticalFactory:
95
95
  )
96
96
  return builder_method(num_spectrums, capture_config)
97
97
 
98
- def _cosine_signal_1(
98
+ def _cosine_wave(
99
99
  self, num_spectrums: int, capture_config: CaptureConfig
100
100
  ) -> Spectrogram:
101
- """Creates the expected spectrogram for the `Test` receiver operating in the mode `cosine-signal-1`."""
101
+ """Creates the expected spectrogram for the `SignalGenerator` receiver operating in the mode `cosine_wave`."""
102
102
  # Extract necessary parameters from the capture config.
103
103
  window_size = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
104
104
  sample_rate = cast(int, capture_config.get_parameter_value(PName.SAMPLE_RATE))
@@ -146,10 +146,10 @@ class _AnalyticalFactory:
146
146
  SpectrumUnit.AMPLITUDE,
147
147
  )
148
148
 
149
- def _tagged_staircase(
149
+ def _constant_staircase(
150
150
  self, num_spectrums: int, capture_config: CaptureConfig
151
151
  ) -> Spectrogram:
152
- """Creates the expected spectrogram for the `Test` receiver operating in the mode `tagged-staircase`."""
152
+ """Creates the expected spectrogram for the `SignalGenerator` receiver operating in the mode `constant_staircase`."""
153
153
  # Extract necessary parameters from the capture config.
154
154
  window_size = cast(int, capture_config.get_parameter_value(PName.WINDOW_SIZE))
155
155
  min_samples_per_step = cast(
@@ -217,15 +217,15 @@ class _AnalyticalFactory:
217
217
  def get_analytical_spectrogram(
218
218
  num_spectrums: int, capture_config: CaptureConfig
219
219
  ) -> Spectrogram:
220
- """Each mode of the `Test` receiver generates a known synthetic signal. Based on this, we can
220
+ """Each mode of the `SignalGenerator` receiver generates a known synthetic signal. Based on this, we can
221
221
  derive an analytical solution that predicts the expected spectrogram for a session in that mode.
222
222
 
223
- This function constructs the analytical spectrogram using the capture config for a `Test`
223
+ This function constructs the analytical spectrogram using the capture config for a `SignalGenerator`
224
224
  receiver operating in a specific mode.
225
225
 
226
226
  :param num_spectrums: The number of spectrums in the output spectrogram.
227
227
  :param capture_config: The capture config used for data capture.
228
- :return: The expected, analytically derived spectrogram for the specified mode of the `Test` receiver.
228
+ :return: The expected, analytically derived spectrogram for the specified mode of the `SignalGenerator` receiver.
229
229
  """
230
230
  factory = _AnalyticalFactory()
231
231
  return factory.get_spectrogram(num_spectrums, capture_config)
@@ -234,7 +234,7 @@ def get_analytical_spectrogram(
234
234
  def validate_analytically(
235
235
  spectrogram: Spectrogram, capture_config: CaptureConfig, absolute_tolerance: float
236
236
  ) -> TestResults:
237
- """Validate a spectrogram generated during sessions with a `Test` receiver operating
237
+ """Validate a spectrogram generated during sessions with a `SignalGenerator` receiver operating
238
238
  in a particular mode.
239
239
 
240
240
  :param spectrogram: The spectrogram to be validated.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spectre-core
3
- Version: 0.0.24
3
+ Version: 0.0.26
4
4
  Summary: The core Python package used by the spectre program.
5
5
  Maintainer-email: Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -1,4 +1,4 @@
1
- spectre_core/__init__.py,sha256=Hsh_-YQxbxSv8T4mjUwiFbG-w2HMgaXutAsG7XmKz3A,184
1
+ spectre_core/__init__.py,sha256=886XLvO2Z9Dnk4FKUsWgHA6m-yd4ulobVDdKN0_MczE,184
2
2
  spectre_core/exceptions.py,sha256=MJSUkuH8BYFt9zfv9m4dNISELEit6Sv_M4ifXHmeBGU,1167
3
3
  spectre_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  spectre_core/_file_io/__init__.py,sha256=H_-6xE-T3yUyywTe4hQn-oU5ACUumY1O-7Sp80EnKfQ,358
@@ -12,7 +12,7 @@ spectre_core/batches/plugins/_batch_keys.py,sha256=KbtKIMCQIay6ulvMtKE6cc_nwXeTy
12
12
  spectre_core/batches/plugins/_callisto.py,sha256=Y03rDG2mc9sboYMeB9WxXWgsp0MXKb1ZMUKKJbEJ26Q,5871
13
13
  spectre_core/batches/plugins/_iq_stream.py,sha256=exyROl9HKudLga6Ziz0QAe8ipPm6kj0wXgA8DevjeF4,11804
14
14
  spectre_core/capture_configs/__init__.py,sha256=IAdhXKUY8KDwl4xBj2NRtAhfgLPdMcSDdZysUIC_HYw,1859
15
- spectre_core/capture_configs/_capture_config.py,sha256=iWBc6B8lreEgf7wUaxM4-_KQBaepYvwU2B4IiYAeUH8,4224
15
+ spectre_core/capture_configs/_capture_config.py,sha256=dRQxFutgvQ8GXmT7IMlIgi4rk05y7c_ECjdH9J_-MPg,4333
16
16
  spectre_core/capture_configs/_capture_modes.py,sha256=ESk-wb-yqUYgRSfl9HYJg01w03UwsS6kSSrkFKW9fNw,889
17
17
  spectre_core/capture_configs/_capture_templates.py,sha256=Y8i8_9voMTq0IbQVUt3wdhzXdr9p5naLMLPU0LNefwQ,9867
18
18
  spectre_core/capture_configs/_parameters.py,sha256=8SfNrfQD0JWpEqSJFNSQF4MFpzbPIvtfQXZgt_Sekgw,5318
@@ -36,7 +36,7 @@ spectre_core/plotting/__init__.py,sha256=AGu9ooOhVmzeLY7BDN7yj1W6ej1G6VBliIga0WR
36
36
  spectre_core/plotting/_base.py,sha256=0lm_KhnexgXqrx7TfskHJiut-2KTyZ-CmBfujc2Yl4I,8783
37
37
  spectre_core/plotting/_format.py,sha256=ACpwfQw5raBAQLRvLBfwQto-Hqg9JsmrZ5eLqU-51Kg,1352
38
38
  spectre_core/plotting/_panel_names.py,sha256=XxWIOfQRs5hhas1X6q9vRarH06SWMC_Kv96Hy4r6jYY,799
39
- spectre_core/plotting/_panel_stack.py,sha256=R278vkhvDYKkyJxuhkyA8yEVy7v0rbpCQ6QwGduDBq4,11619
39
+ spectre_core/plotting/_panel_stack.py,sha256=-YvLkF6ykJqLvtnHzguDr_jt3Teavw341bok9b4yJBI,12028
40
40
  spectre_core/plotting/_panels.py,sha256=9tk_ebpIN4ADx6vcauy-reUWg5gR_FYK9d_--_mG598,15982
41
41
  spectre_core/post_processing/__init__.py,sha256=jMIfEpEi1cH61nyZXXtUxYXxN1hinvWml1tdhhZrUEo,642
42
42
  spectre_core/post_processing/_base.py,sha256=cS7f0TRAbd_6vVG4dxngzdsftyq3py4PRXhEQWby-UE,6473
@@ -46,34 +46,33 @@ spectre_core/post_processing/_register.py,sha256=A0GkFiUVmD8DkqblxEiywMiDq00vPpI
46
46
  spectre_core/post_processing/plugins/_event_handler_keys.py,sha256=4Gs7xcr_TqefMbL_DDij1GhUtAdCoMmmGaonzftcAiM,610
47
47
  spectre_core/post_processing/plugins/_fixed_center_frequency.py,sha256=Bsq8uxEEQqxvR_Iot6rhr7qjz-I0Eb6sQ4WSiZvOYKI,4951
48
48
  spectre_core/post_processing/plugins/_swept_center_frequency.py,sha256=wxiKYWgD6vQMNbNtIDb9iMf8uXauoDTyOZcAhax0me4,19937
49
- spectre_core/receivers/__init__.py,sha256=bC01PtgD13yC2-a8_cKF3wuXX0rhLxOpkoUWdK6q-Qw,746
50
- spectre_core/receivers/_base.py,sha256=PYDikKZJLaNQkvCyXKgFj2L7ZNCVb-Tv5ZltocRW2ew,8875
51
- spectre_core/receivers/_factory.py,sha256=VKQ3LaBNwjQB4D0Lyu9to21_jNaky8RpTlcvCn-DPc0,1933
52
- spectre_core/receivers/_register.py,sha256=KhzhTWD0DKWRylvBYtd6ngLp74MF9d80x1xHaxMVlHk,1356
53
- spectre_core/receivers/_spec_names.py,sha256=TlQSBaBHg1I2IyQPBQH5dXyKObMj2CQLg3IZ3X6ZqGA,2332
49
+ spectre_core/receivers/__init__.py,sha256=91JLTWPAe-UvXh4zpN2dw3p0TspL5w2IlU1OU-BRAdc,850
50
+ spectre_core/receivers/_factory.py,sha256=CFyy2roBIpZEeeKuf6f1owVE_3IAmhuk2bYBNoFEgUE,2131
51
+ spectre_core/receivers/_receiver.py,sha256=WJfTNdgLU_pplq98xxAtpFSvGJzjHo7Gtx4LloCR8mo,8936
52
+ spectre_core/receivers/_register.py,sha256=j20Gb0CddcPlRSN5a74cwVpEdwqu9KMH-_p21pHDjWQ,1348
53
+ spectre_core/receivers/_specs.py,sha256=iOYWRwDf8cH-z5TM7yhZtIVFRZPioUtn2DCVfW20nMo,3482
54
54
  spectre_core/receivers/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- spectre_core/receivers/plugins/_b200mini.py,sha256=uM9UkGyWpQxPnuyBjGCw1bBIjVrieZWkxjPqmm9EVck,2695
56
- spectre_core/receivers/plugins/_receiver_names.py,sha256=XMrRsLjZzim7Vd-fnt8wF-I-nZK5emkw2MdD_I3QV10,486
57
- spectre_core/receivers/plugins/_rsp1a.py,sha256=QyWsznQ9bqQQHp4-dT6hEb0kTGvwlqRaQle9zGAtkYA,2493
58
- spectre_core/receivers/plugins/_rspduo.py,sha256=6zJluOvGHU1tjXKMsq0AMjKnnc3Zf3fy4TzuvbCxkBM,3159
59
- spectre_core/receivers/plugins/_sdrplay_receiver.py,sha256=ovrJmTym8qffcyFszzQu_tS0kqTQB6-JEAkP2EEEk1k,5198
60
- spectre_core/receivers/plugins/_test.py,sha256=o5KXPaHWiC0g9IouJq7GrzcL-YZuT_lCJMv41yPj-7Q,8071
61
- spectre_core/receivers/plugins/_usrp.py,sha256=S-05BtvDekHro0Ldxo6oIDzZvlZYpNwqw7Wmn5NbgUQ,6998
62
- spectre_core/receivers/plugins/gr/__init__.py,sha256=ObN2dlX46k3YlRF0fpjPvELvwbNSVr_N98_qmHF8yXM,160
63
- spectre_core/receivers/plugins/gr/_base.py,sha256=dZ5sqRC1qSsbc-90eFUCkdM-WDjQXSFo7aBzN9L1Q20,2461
64
- spectre_core/receivers/plugins/gr/_rsp1a.py,sha256=uRl1HHkaLYX9DEiThQTyW-bTmCDrSHyPfinzT4MKnG0,5399
65
- spectre_core/receivers/plugins/gr/_rspduo.py,sha256=NgW0oxkpewvL6IJmgrXbnwORL5yn0c3rpmpl3RCC8-M,8637
66
- spectre_core/receivers/plugins/gr/_test.py,sha256=3WmvBgfxkeVCpiwGLOq1aTQHLIiLi4qPNu6-NEwq4U0,4551
67
- spectre_core/receivers/plugins/gr/_usrp.py,sha256=0pyyW-Fn6rO1NlJXzXQswdJLdgbQxjD5z1PHhbhXxnk,5687
55
+ spectre_core/receivers/plugins/_b200mini.py,sha256=ob1aPIaQyNo6GFI6_fKuxDPfv9YAC1ImpDHZCLqTJwQ,8745
56
+ spectre_core/receivers/plugins/_b200mini_gr.py,sha256=TR2quhuYFMsbpOM6ahMPhTLvWJc-Te3j_GZfOybwpkQ,4926
57
+ spectre_core/receivers/plugins/_custom.py,sha256=XO_J0HBIBKfzpa1zm6qtDbqrbiH6cLUJhPT0CBsMg-0,623
58
+ spectre_core/receivers/plugins/_gr.py,sha256=g0pLqCsiW1muaF17A9jAC8hhp6VR1m89NYCIOgwDvxc,2452
59
+ spectre_core/receivers/plugins/_receiver_names.py,sha256=2grQdv76fdOGVQhJhtQOXhC22_Vu2zJ3EaSXTrmsit0,626
60
+ spectre_core/receivers/plugins/_rsp1a.py,sha256=6vx4uFUxkC4-7kC7qxL7WFLW2v1cUsvHX6pNsWNMsqA,2340
61
+ spectre_core/receivers/plugins/_rsp1a_gr.py,sha256=kI_Z8IoSdNP3AoQQGQrbcpeJctmA8WemWsQnm7oKEEk,4939
62
+ spectre_core/receivers/plugins/_rspduo.py,sha256=BjpODVms24qyMDJ_N5sYH7oKzsaa2hdF8JixRSLDq5Q,2766
63
+ spectre_core/receivers/plugins/_rspduo_gr.py,sha256=u-DSa_7wtCNEQkSqLMwC9d89C-rYOMImoH8xGGpCN-o,7517
64
+ spectre_core/receivers/plugins/_sdrplay_receiver.py,sha256=OqjILmsC0JmT5n_XlF5TMjmtsGWSb7l9E1NndAuNiHg,9868
65
+ spectre_core/receivers/plugins/_signal_generator.py,sha256=7-Vp60fqsfApWEvjcOqKpYAb-mAMaWPP4EGV3DUqnKE,7408
66
+ spectre_core/receivers/plugins/_signal_generator_gr.py,sha256=e6gPj1KJJqMmIZJkvT7n5XaoU7TTBeL5TKJXf9N99zo,3358
68
67
  spectre_core/spectrograms/__init__.py,sha256=olHW9pFhYg6-yCcLL6sP8C4noLwzgBGDOO9_RDyP6Cw,803
69
- spectre_core/spectrograms/_analytical.py,sha256=xW8Zm955ls-PqHBm9tf6nNyCWxrrhwZxUbPa4eXPbXs,11019
68
+ spectre_core/spectrograms/_analytical.py,sha256=YspKx8GfS8aFcCRhyLTTyCVHfM1WA7te2JwP8wN9Gq8,11166
70
69
  spectre_core/spectrograms/_array_operations.py,sha256=AsBDmxFMnC3hYRP6QCFN65Fh0OLUAY7CeCx8kqPG2ks,7518
71
70
  spectre_core/spectrograms/_spectrogram.py,sha256=k0Azaz1NRV-ls1NOrwb0TpKwwcpGzjxv-MFj-MTyXsE,27192
72
71
  spectre_core/spectrograms/_transform.py,sha256=S9V0dROKv3L8dg9EiPxR90XNeKaNmvbfZh66sFjETRo,11749
73
72
  spectre_core/wgetting/__init__.py,sha256=9QD-8DjYq0hbU7f-NQ_Cg-TLKNbq0E0PI3yWJXJoC4k,341
74
73
  spectre_core/wgetting/_callisto.py,sha256=eZyTG07WQic-w9STbx91bOYWhWFof85TX41WY5WrJg4,7148
75
- spectre_core-0.0.24.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
76
- spectre_core-0.0.24.dist-info/METADATA,sha256=FH1RnPWWBhp8alZcUdZhNx8JoF1ov0qtRAZm57QPdjk,41951
77
- spectre_core-0.0.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
78
- spectre_core-0.0.24.dist-info/top_level.txt,sha256=-UsyjpFohXgZpgcZ9QbVeXhsIyF3Am8RxNFNDV_Ta2Y,13
79
- spectre_core-0.0.24.dist-info/RECORD,,
74
+ spectre_core-0.0.26.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
75
+ spectre_core-0.0.26.dist-info/METADATA,sha256=ETJz8h_5EtRNtcCV22cS6AOv6J9FXh4ikHXe5ollR7g,41951
76
+ spectre_core-0.0.26.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
77
+ spectre_core-0.0.26.dist-info/top_level.txt,sha256=-UsyjpFohXgZpgcZ9QbVeXhsIyF3Am8RxNFNDV_Ta2Y,13
78
+ spectre_core-0.0.26.dist-info/RECORD,,
@@ -1,242 +0,0 @@
1
- # SPDX-FileCopyrightText: © 2024-2025 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
- # This file is part of SPECTRE
3
- # SPDX-License-Identifier: GPL-3.0-or-later
4
-
5
- from abc import ABC, abstractmethod
6
- from typing import Callable, Optional, Literal, overload, Any
7
-
8
- from spectre_core.exceptions import ModeNotFoundError
9
- from spectre_core.capture_configs import CaptureTemplate, Parameters, CaptureConfig
10
- from .plugins._receiver_names import ReceiverName
11
- from ._spec_names import SpecName
12
-
13
-
14
- class BaseReceiver(ABC):
15
- """Abstract base class for software-defined radio receivers."""
16
-
17
- def __init__(self, name: ReceiverName, mode: Optional[str] = None) -> None:
18
- """Initialise an instance of `BaseReceiver`.
19
-
20
- :param name: The name of the receiver.
21
- :param mode: The initial active operating mode. Defaults to None.
22
- """
23
- self._name = name
24
-
25
- self._specs: dict[SpecName, float | int | list[float | int]] = {}
26
- self._add_specs()
27
-
28
- self._capture_methods: dict[str, Callable[[str, Parameters], None]] = {}
29
- self._add_capture_methods()
30
-
31
- self._capture_templates: dict[str, CaptureTemplate] = {}
32
- self._add_capture_templates()
33
-
34
- self._pvalidators: dict[str, Callable[[Parameters], None]] = {}
35
- self._add_pvalidators()
36
-
37
- self._mode: Optional[str] = mode
38
-
39
- @abstractmethod
40
- def _add_specs(self) -> None:
41
- """Subclasses must use `add_spec` to add hardware specifications."""
42
-
43
- @abstractmethod
44
- def _add_capture_methods(self) -> None:
45
- """Subclasses must use `add_capture_method` to specify which method is called to capture
46
- data, for each operating mode."""
47
-
48
- @abstractmethod
49
- def _add_capture_templates(self) -> None:
50
- """Subclasses must use `add_capture_template` to define a `CaptureTemplate` for each operating mode."""
51
-
52
- @abstractmethod
53
- def _add_pvalidators(self) -> None:
54
- """Subclasses must use `add_pvalidator` to add a parameter validation function (pvalidator)
55
- for each operating mode."""
56
-
57
- @property
58
- def name(self) -> ReceiverName:
59
- """The name of the receiver."""
60
- return self._name
61
-
62
- @property
63
- def capture_methods(self) -> dict[str, Callable[[str, Parameters], None]]:
64
- """For each operating mode, the method which is called to capture data."""
65
- return self._capture_methods
66
-
67
- @property
68
- def capture_templates(self) -> dict[str, CaptureTemplate]:
69
- """For each operating mode, the corresponding `CaptureTemplate`."""
70
- return self._capture_templates
71
-
72
- @property
73
- def pvalidators(self) -> dict[str, Callable[[Parameters], None]]:
74
- """For each operating mode, the corresponding parameter validation function (pvalidator)."""
75
- return self._pvalidators
76
-
77
- @property
78
- def specs(self) -> dict[SpecName, float | int | list[float | int]]:
79
- """The hardware specifications."""
80
- return self._specs
81
-
82
- @property
83
- def modes(self) -> list[str]:
84
- """The operating modes for the receiver.
85
-
86
- :raises ValueError: If the modes are inconsistent between `capture_methods`,
87
- `pvalidators` and `capture_templates`.
88
- """
89
- capture_method_modes = list(self.capture_methods.keys())
90
- pvalidator_modes = list(self.pvalidators.keys())
91
- capture_template_modes = list(self.capture_templates.keys())
92
-
93
- if not capture_method_modes == pvalidator_modes == capture_template_modes:
94
- raise ValueError(f"Mode mismatch for the receiver {self.name}.")
95
- return capture_method_modes
96
-
97
- @property
98
- def mode(self) -> Optional[str]:
99
- """The active operating mode for the receiver, if set."""
100
- return self._mode
101
-
102
- @mode.setter
103
- def mode(self, value: str) -> None:
104
- """Set the active operating mode.
105
-
106
- :param value: The new operating mode to activate.
107
- :raises ModeNotFoundError: If the specified mode is not defined in `modes`.
108
- """
109
- if value not in self.modes:
110
- raise ModeNotFoundError(
111
- f"{value} is not a defined mode for the receiver {self.name}. "
112
- f"Expected one of {self.modes}"
113
- )
114
- self._mode = value
115
-
116
- def _get_active_mode(self) -> str:
117
- """Get the current mode, raising an error if none is set.
118
-
119
- :raises ValueError: If no mode is currently set
120
- :return: The current mode
121
- """
122
- if self._mode is None:
123
- raise ValueError(
124
- f"This operation requires an active mode for receiver `{self.name.value}`"
125
- )
126
- return self._mode
127
-
128
- @property
129
- def capture_method(self) -> Callable[[str, Parameters], None]:
130
- """Start capturing data under the active operating mode."""
131
- return self.capture_methods[self._get_active_mode()]
132
-
133
- @property
134
- def pvalidator(self) -> Callable[[Parameters], None]:
135
- """The parameter validation function for the active operating mode."""
136
- return self.pvalidators[self._get_active_mode()]
137
-
138
- @property
139
- def capture_template(self) -> CaptureTemplate:
140
- """The `CaptureTemplate` for the active operating mode."""
141
- return self._capture_templates[self._get_active_mode()]
142
-
143
- def add_capture_method(
144
- self, mode: str, capture_method: Callable[[str, Parameters], None]
145
- ) -> None:
146
- """
147
- Add a capture method for a specific operating mode.
148
-
149
- :param mode: The operating mode.
150
- :param capture_method: The function which captures data.
151
- """
152
- self._capture_methods[mode] = capture_method
153
-
154
- def add_pvalidator(
155
- self, mode: str, pvalidator: Callable[[Parameters], None]
156
- ) -> None:
157
- """
158
- Add a parameter validation function for a specific operating mode.
159
-
160
- :param mode: The operating mode.
161
- :param pvalidator: The validation function.
162
- """
163
- self._pvalidators[mode] = pvalidator
164
-
165
- def add_capture_template(
166
- self, mode: str, capture_template: CaptureTemplate
167
- ) -> None:
168
- """
169
- Add a `CaptureTemplate` for a specific operating mode.
170
-
171
- :param mode: The operating mode.
172
- :param capture_template: The capture template, defining parameter requirements.
173
- """
174
- self._capture_templates[mode] = capture_template
175
-
176
- def add_spec(self, name: SpecName, value: Any) -> None:
177
- """
178
- Add a hardware specification.
179
-
180
- :param name: The specification's name.
181
- :param value: The specification's value.
182
- """
183
- self.specs[name] = value
184
-
185
- def get_spec(self, spec_name: SpecName) -> Any:
186
- """
187
- Retrieve a hardware specification.
188
-
189
- :param spec_name: The name of the specification.
190
- :raises KeyError: If the specification is not found.
191
- :return: The specification's value.
192
- """
193
- if spec_name not in self.specs:
194
- raise KeyError(
195
- f"Spec not found with name '{spec_name}' "
196
- f"for the receiver '{self.name}'"
197
- )
198
- return self.specs[spec_name]
199
-
200
- def start_capture(self, tag: str) -> None:
201
- """Start capturing data in the active operating mode.
202
-
203
- :param tag: The tag of the capture config to load.
204
- :raises ValueError: If no mode is currently set
205
- """
206
- active_mode = self._get_active_mode()
207
- self.capture_methods[active_mode](tag, self.load_parameters(tag))
208
-
209
- def save_parameters(
210
- self, tag: str, parameters: Parameters, force: bool = False
211
- ) -> None:
212
- """Create a capture config according to the active operating mode and save the
213
- input parameters.
214
-
215
- :param tag: The tag identifying the capture config.
216
- :param parameters: The parameters to save in the capture config.
217
- :param force: If True, overwrites existing file if it exists.
218
- :raises ValueError: If no mode is currently set
219
- """
220
- active_mode = self._get_active_mode()
221
- parameters = self.capture_templates[active_mode].apply_template(parameters)
222
- self.pvalidators[active_mode](parameters)
223
-
224
- capture_config = CaptureConfig(tag)
225
- capture_config.save_parameters(self.name.value, active_mode, parameters, force)
226
-
227
- def load_parameters(self, tag: str) -> Parameters:
228
- """Load a capture config, and return the parameters it stores.
229
-
230
- :param tag: The tag identifying the capture config.
231
- :raises ValueError: If no mode is currently set
232
- :return: The validated parameters stored in the capture config.
233
- """
234
- active_mode = self._get_active_mode()
235
- capture_config = CaptureConfig(tag)
236
-
237
- parameters = self.capture_templates[active_mode].apply_template(
238
- capture_config.parameters
239
- )
240
- self.pvalidators[active_mode](parameters)
241
-
242
- return parameters
@@ -1,225 +0,0 @@
1
- # SPDX-FileCopyrightText: © 2024-2025 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
- # This file is part of SPECTRE
3
- # SPDX-License-Identifier: GPL-3.0-or-later
4
-
5
- from dataclasses import dataclass
6
- from typing import Callable, cast
7
-
8
- from spectre_core.capture_configs import (
9
- CaptureTemplate,
10
- CaptureMode,
11
- Parameters,
12
- Bound,
13
- PName,
14
- get_base_capture_template,
15
- make_base_capture_template,
16
- get_base_ptemplate,
17
- validate_window,
18
- )
19
- from .gr._test import CaptureMethod
20
- from ._receiver_names import ReceiverName
21
- from .._spec_names import SpecName
22
- from .._base import BaseReceiver
23
- from .._register import register_receiver
24
-
25
-
26
- @dataclass(frozen=True)
27
- class Mode:
28
- """An operating mode for the `Test` receiver."""
29
-
30
- COSINE_SIGNAL_1 = "cosine_signal_1"
31
- TAGGED_STAIRCASE = "tagged_staircase"
32
-
33
-
34
- @register_receiver(ReceiverName.TEST)
35
- class Test(BaseReceiver):
36
- """An entirely software-defined receiver, which generates synthetic signals."""
37
-
38
- def _add_specs(self) -> None:
39
- self.add_spec(SpecName.SAMPLE_RATE_LOWER_BOUND, 64000)
40
- self.add_spec(SpecName.SAMPLE_RATE_UPPER_BOUND, 640000)
41
- self.add_spec(SpecName.FREQUENCY_LOWER_BOUND, 16000)
42
- self.add_spec(SpecName.FREQUENCY_UPPER_BOUND, 160000)
43
-
44
- def _add_capture_methods(self) -> None:
45
- self.add_capture_method(Mode.COSINE_SIGNAL_1, CaptureMethod.cosine_signal_1)
46
- self.add_capture_method(Mode.TAGGED_STAIRCASE, CaptureMethod.tagged_staircase)
47
-
48
- def _add_pvalidators(self) -> None:
49
- self.add_pvalidator(
50
- Mode.COSINE_SIGNAL_1, self.__get_pvalidator_cosine_signal_1()
51
- )
52
- self.add_pvalidator(
53
- Mode.TAGGED_STAIRCASE, self.__get_pvalidator_tagged_staircase()
54
- )
55
-
56
- def _add_capture_templates(self) -> None:
57
- self.add_capture_template(
58
- Mode.COSINE_SIGNAL_1, self.__get_capture_template_cosine_signal_1()
59
- )
60
- self.add_capture_template(
61
- Mode.TAGGED_STAIRCASE, self.__get_capture_template_tagged_staircase()
62
- )
63
-
64
- def __get_capture_template_cosine_signal_1(self) -> CaptureTemplate:
65
- capture_template = get_base_capture_template(CaptureMode.FIXED_CENTER_FREQUENCY)
66
- capture_template.add_ptemplate(get_base_ptemplate(PName.AMPLITUDE))
67
- capture_template.add_ptemplate(get_base_ptemplate(PName.FREQUENCY))
68
-
69
- capture_template.set_defaults(
70
- (PName.BATCH_SIZE, 3.0),
71
- (PName.CENTER_FREQUENCY, 16000),
72
- (PName.AMPLITUDE, 2.0),
73
- (PName.FREQUENCY, 32000),
74
- (PName.SAMPLE_RATE, 128000),
75
- (PName.WINDOW_HOP, 512),
76
- (PName.WINDOW_SIZE, 512),
77
- (PName.WINDOW_TYPE, "boxcar"),
78
- )
79
-
80
- capture_template.enforce_defaults(
81
- PName.TIME_RESOLUTION,
82
- PName.TIME_RANGE,
83
- PName.FREQUENCY_RESOLUTION,
84
- PName.WINDOW_TYPE,
85
- )
86
-
87
- capture_template.add_pconstraint(
88
- PName.SAMPLE_RATE,
89
- [
90
- Bound(
91
- lower_bound=self.get_spec(SpecName.SAMPLE_RATE_LOWER_BOUND),
92
- upper_bound=self.get_spec(SpecName.SAMPLE_RATE_UPPER_BOUND),
93
- )
94
- ],
95
- )
96
- capture_template.add_pconstraint(
97
- PName.FREQUENCY,
98
- [
99
- Bound(
100
- lower_bound=self.get_spec(SpecName.FREQUENCY_LOWER_BOUND),
101
- upper_bound=self.get_spec(SpecName.FREQUENCY_UPPER_BOUND),
102
- )
103
- ],
104
- )
105
- return capture_template
106
-
107
- def __get_capture_template_tagged_staircase(self) -> CaptureTemplate:
108
- capture_template = make_base_capture_template(
109
- PName.TIME_RESOLUTION,
110
- PName.FREQUENCY_RESOLUTION,
111
- PName.TIME_RANGE,
112
- PName.SAMPLE_RATE,
113
- PName.BATCH_SIZE,
114
- PName.WINDOW_TYPE,
115
- PName.WINDOW_HOP,
116
- PName.WINDOW_SIZE,
117
- PName.EVENT_HANDLER_KEY,
118
- PName.BATCH_KEY,
119
- PName.WATCH_EXTENSION,
120
- PName.MIN_SAMPLES_PER_STEP,
121
- PName.MAX_SAMPLES_PER_STEP,
122
- PName.FREQUENCY_STEP,
123
- PName.STEP_INCREMENT,
124
- PName.OBS_ALT,
125
- PName.OBS_LAT,
126
- PName.OBS_LON,
127
- PName.OBJECT,
128
- PName.ORIGIN,
129
- PName.TELESCOPE,
130
- PName.INSTRUMENT,
131
- )
132
-
133
- capture_template.set_defaults(
134
- (PName.BATCH_SIZE, 3.0),
135
- (PName.FREQUENCY_STEP, 128000),
136
- (PName.MAX_SAMPLES_PER_STEP, 5000),
137
- (PName.MIN_SAMPLES_PER_STEP, 4000),
138
- (PName.SAMPLE_RATE, 128000),
139
- (PName.STEP_INCREMENT, 200),
140
- (PName.WINDOW_HOP, 512),
141
- (PName.WINDOW_SIZE, 512),
142
- (PName.WINDOW_TYPE, "boxcar"),
143
- (PName.EVENT_HANDLER_KEY, "swept_center_frequency"),
144
- (PName.BATCH_KEY, "iq_stream"),
145
- (PName.WATCH_EXTENSION, "bin"),
146
- )
147
-
148
- capture_template.enforce_defaults(
149
- PName.TIME_RESOLUTION,
150
- PName.TIME_RANGE,
151
- PName.FREQUENCY_RESOLUTION,
152
- PName.WINDOW_TYPE,
153
- PName.EVENT_HANDLER_KEY,
154
- PName.BATCH_KEY,
155
- PName.WATCH_EXTENSION,
156
- )
157
-
158
- return capture_template
159
-
160
- def __get_pvalidator_cosine_signal_1(self) -> Callable[[Parameters], None]:
161
- def pvalidator(parameters: Parameters) -> None:
162
- validate_window(parameters)
163
-
164
- sample_rate = cast(int, parameters.get_parameter_value(PName.SAMPLE_RATE))
165
- window_size = cast(int, parameters.get_parameter_value(PName.WINDOW_SIZE))
166
- frequency = cast(float, parameters.get_parameter_value(PName.FREQUENCY))
167
-
168
- # check that the sample rate is an integer multiple of the underlying signal frequency
169
- if sample_rate % frequency != 0:
170
- raise ValueError(
171
- "The sampling rate must be some integer multiple of frequency"
172
- )
173
-
174
- a = sample_rate / frequency
175
- if a < 2:
176
- raise ValueError(
177
- (
178
- f"The ratio of sampling rate over frequency must be greater than two. "
179
- f"Got {a}"
180
- )
181
- )
182
-
183
- # analytical requirement
184
- # if p is the number of sampled cycles, we can find that p = window_size / a
185
- # the number of sampled cycles must be a positive natural number.
186
- p = window_size / a
187
- if window_size % a != 0:
188
- raise ValueError(
189
- (
190
- f"The number of sampled cycles must be a positive natural number. "
191
- f"Computed that p={p}"
192
- )
193
- )
194
-
195
- return pvalidator
196
-
197
- def __get_pvalidator_tagged_staircase(self) -> Callable[[Parameters], None]:
198
- def pvalidator(parameters: Parameters) -> None:
199
- validate_window(parameters)
200
-
201
- freq_step = cast(
202
- float, parameters.get_parameter_value(PName.FREQUENCY_STEP)
203
- )
204
- sample_rate = cast(int, parameters.get_parameter_value(PName.SAMPLE_RATE))
205
- min_samples_per_step = cast(
206
- int, parameters.get_parameter_value(PName.MIN_SAMPLES_PER_STEP)
207
- )
208
- max_samples_per_step = cast(
209
- int, parameters.get_parameter_value(PName.MAX_SAMPLES_PER_STEP)
210
- )
211
-
212
- if freq_step != sample_rate:
213
- raise ValueError(
214
- f"The frequency step must be equal to the sampling rate"
215
- )
216
-
217
- if min_samples_per_step > max_samples_per_step:
218
- raise ValueError(
219
- (
220
- f"Minimum samples per step cannot be greater than the maximum samples per step. "
221
- f"Got {min_samples_per_step}, which is greater than {max_samples_per_step}"
222
- )
223
- )
224
-
225
- return pvalidator