spectre-core 0.0.9__tar.gz → 0.0.11__tar.gz

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 (116) hide show
  1. {spectre_core-0.0.9 → spectre_core-0.0.11}/PKG-INFO +1 -1
  2. {spectre_core-0.0.9 → spectre_core-0.0.11}/pyproject.toml +1 -1
  3. spectre_core-0.0.11/src/spectre_core/_file_io/__init__.py +15 -0
  4. spectre_core-0.0.11/src/spectre_core/_file_io/file_handlers.py +128 -0
  5. spectre_core-0.0.11/src/spectre_core/capture_configs/__init__.py +29 -0
  6. spectre_core-0.0.11/src/spectre_core/capture_configs/_capture_config.py +85 -0
  7. spectre_core-0.0.11/src/spectre_core/capture_configs/_capture_templates.py +222 -0
  8. spectre_core-0.0.11/src/spectre_core/capture_configs/_parameters.py +110 -0
  9. spectre_core-0.0.11/src/spectre_core/capture_configs/_pconstraints.py +82 -0
  10. spectre_core-0.0.11/src/spectre_core/capture_configs/_ptemplates.py +450 -0
  11. spectre_core-0.0.11/src/spectre_core/capture_configs/_pvalidators.py +171 -0
  12. spectre_core-0.0.11/src/spectre_core/chunks/__init__.py +22 -0
  13. spectre_core-0.0.9/src/spectre_core/chunks/base.py → spectre_core-0.0.11/src/spectre_core/chunks/_base.py +15 -60
  14. spectre_core-0.0.9/src/spectre_core/chunks/__init__.py → spectre_core-0.0.11/src/spectre_core/chunks/_chunks.py +8 -14
  15. spectre_core-0.0.9/src/spectre_core/chunks/factory.py → spectre_core-0.0.11/src/spectre_core/chunks/_factory.py +6 -7
  16. spectre_core-0.0.9/src/spectre_core/chunks/library/callisto/chunk.py → spectre_core-0.0.11/src/spectre_core/chunks/library/_callisto.py +4 -7
  17. spectre_core-0.0.9/src/spectre_core/chunks/library/fixed/chunk.py → spectre_core-0.0.11/src/spectre_core/chunks/library/_fixed_center_frequency.py +7 -64
  18. spectre_core-0.0.11/src/spectre_core/chunks/library/_swept_center_frequency.py +103 -0
  19. spectre_core-0.0.11/src/spectre_core/config/__init__.py +20 -0
  20. spectre_core-0.0.11/src/spectre_core/config/_paths.py +77 -0
  21. spectre_core-0.0.11/src/spectre_core/config/_time_formats.py +15 -0
  22. {spectre_core-0.0.9 → spectre_core-0.0.11}/src/spectre_core/exceptions.py +4 -5
  23. spectre_core-0.0.11/src/spectre_core/logging/__init__.py +11 -0
  24. spectre_core-0.0.11/src/spectre_core/logging/_configure.py +35 -0
  25. spectre_core-0.0.11/src/spectre_core/logging/_decorators.py +19 -0
  26. spectre_core-0.0.9/src/spectre_core/logging.py → spectre_core-0.0.11/src/spectre_core/logging/_log_handlers.py +13 -58
  27. {spectre_core-0.0.9/src/spectre_core/plotting/library → spectre_core-0.0.11/src/spectre_core/plotting}/__init__.py +6 -2
  28. spectre_core-0.0.9/src/spectre_core/plotting/base.py → spectre_core-0.0.11/src/spectre_core/plotting/_base.py +40 -20
  29. spectre_core-0.0.11/src/spectre_core/plotting/_format.py +18 -0
  30. spectre_core-0.0.9/src/spectre_core/plotting/panel_stack.py → spectre_core-0.0.11/src/spectre_core/plotting/_panel_stack.py +48 -48
  31. spectre_core-0.0.11/src/spectre_core/plotting/_panels.py +234 -0
  32. spectre_core-0.0.11/src/spectre_core/post_processing/__init__.py +14 -0
  33. spectre_core-0.0.11/src/spectre_core/post_processing/_base.py +119 -0
  34. spectre_core-0.0.9/src/spectre_core/post_processing/factory.py → spectre_core-0.0.11/src/spectre_core/post_processing/_factory.py +7 -6
  35. spectre_core-0.0.9/src/spectre_core/post_processing/post_processor.py → spectre_core-0.0.11/src/spectre_core/post_processing/_post_processor.py +3 -3
  36. spectre_core-0.0.11/src/spectre_core/post_processing/library/_fixed_center_frequency.py +115 -0
  37. spectre_core-0.0.11/src/spectre_core/post_processing/library/_swept_center_frequency.py +382 -0
  38. spectre_core-0.0.11/src/spectre_core/receivers/__init__.py +17 -0
  39. spectre_core-0.0.11/src/spectre_core/receivers/_base.py +180 -0
  40. spectre_core-0.0.9/src/spectre_core/receivers/factory.py → spectre_core-0.0.11/src/spectre_core/receivers/_factory.py +2 -2
  41. spectre_core-0.0.11/src/spectre_core/receivers/_spec_names.py +20 -0
  42. spectre_core-0.0.11/src/spectre_core/receivers/gr/_base.py +33 -0
  43. spectre_core-0.0.11/src/spectre_core/receivers/gr/_rsp1a.py +158 -0
  44. spectre_core-0.0.11/src/spectre_core/receivers/gr/_rspduo.py +227 -0
  45. spectre_core-0.0.11/src/spectre_core/receivers/gr/_test.py +123 -0
  46. spectre_core-0.0.11/src/spectre_core/receivers/library/_rsp1a.py +61 -0
  47. spectre_core-0.0.11/src/spectre_core/receivers/library/_rspduo.py +69 -0
  48. spectre_core-0.0.11/src/spectre_core/receivers/library/_sdrplay_receiver.py +185 -0
  49. spectre_core-0.0.11/src/spectre_core/receivers/library/_test.py +221 -0
  50. spectre_core-0.0.11/src/spectre_core/spectrograms/__init__.py +21 -0
  51. spectre_core-0.0.9/src/spectre_core/spectrograms/analytical.py → spectre_core-0.0.11/src/spectre_core/spectrograms/_analytical.py +29 -27
  52. spectre_core-0.0.9/src/spectre_core/spectrograms/array_operations.py → spectre_core-0.0.11/src/spectre_core/spectrograms/_array_operations.py +47 -1
  53. spectre_core-0.0.9/src/spectre_core/spectrograms/spectrogram.py → spectre_core-0.0.11/src/spectre_core/spectrograms/_spectrogram.py +62 -35
  54. spectre_core-0.0.9/src/spectre_core/spectrograms/transform.py → spectre_core-0.0.11/src/spectre_core/spectrograms/_transform.py +76 -89
  55. {spectre_core-0.0.9/src/spectre_core/post_processing/library → spectre_core-0.0.11/src/spectre_core/wgetting}/__init__.py +4 -5
  56. spectre_core-0.0.11/src/spectre_core/wgetting/_callisto.py +155 -0
  57. {spectre_core-0.0.9 → spectre_core-0.0.11}/src/spectre_core.egg-info/PKG-INFO +1 -1
  58. spectre_core-0.0.11/src/spectre_core.egg-info/SOURCES.txt +67 -0
  59. spectre_core-0.0.9/src/spectre_core/cfg.py +0 -116
  60. spectre_core-0.0.9/src/spectre_core/chunks/library/__init__.py +0 -8
  61. spectre_core-0.0.9/src/spectre_core/chunks/library/fixed/__init__.py +0 -0
  62. spectre_core-0.0.9/src/spectre_core/chunks/library/sweep/__init__.py +0 -0
  63. spectre_core-0.0.9/src/spectre_core/chunks/library/sweep/chunk.py +0 -400
  64. spectre_core-0.0.9/src/spectre_core/dynamic_imports.py +0 -22
  65. spectre_core-0.0.9/src/spectre_core/file_handlers/base.py +0 -68
  66. spectre_core-0.0.9/src/spectre_core/file_handlers/configs.py +0 -271
  67. spectre_core-0.0.9/src/spectre_core/file_handlers/json.py +0 -40
  68. spectre_core-0.0.9/src/spectre_core/file_handlers/text.py +0 -21
  69. spectre_core-0.0.9/src/spectre_core/plotting/__init__.py +0 -5
  70. spectre_core-0.0.9/src/spectre_core/plotting/factory.py +0 -26
  71. spectre_core-0.0.9/src/spectre_core/plotting/format.py +0 -19
  72. spectre_core-0.0.9/src/spectre_core/plotting/library/frequency_cuts/panel.py +0 -74
  73. spectre_core-0.0.9/src/spectre_core/plotting/library/integral_over_frequency/panel.py +0 -34
  74. spectre_core-0.0.9/src/spectre_core/plotting/library/spectrogram/panel.py +0 -92
  75. spectre_core-0.0.9/src/spectre_core/plotting/library/time_cuts/panel.py +0 -77
  76. spectre_core-0.0.9/src/spectre_core/plotting/panel_register.py +0 -13
  77. spectre_core-0.0.9/src/spectre_core/post_processing/__init__.py +0 -6
  78. spectre_core-0.0.9/src/spectre_core/post_processing/base.py +0 -132
  79. spectre_core-0.0.9/src/spectre_core/post_processing/library/fixed/__init__.py +0 -0
  80. spectre_core-0.0.9/src/spectre_core/post_processing/library/fixed/event_handler.py +0 -40
  81. spectre_core-0.0.9/src/spectre_core/post_processing/library/sweep/event_handler.py +0 -54
  82. spectre_core-0.0.9/src/spectre_core/receivers/__init__.py +0 -6
  83. spectre_core-0.0.9/src/spectre_core/receivers/base.py +0 -422
  84. spectre_core-0.0.9/src/spectre_core/receivers/library/__init__.py +0 -7
  85. spectre_core-0.0.9/src/spectre_core/receivers/library/rsp1a/__init__.py +0 -0
  86. spectre_core-0.0.9/src/spectre_core/receivers/library/rsp1a/gr/__init__.py +0 -0
  87. spectre_core-0.0.9/src/spectre_core/receivers/library/rsp1a/gr/fixed.py +0 -104
  88. spectre_core-0.0.9/src/spectre_core/receivers/library/rsp1a/gr/sweep.py +0 -129
  89. spectre_core-0.0.9/src/spectre_core/receivers/library/rsp1a/receiver.py +0 -68
  90. spectre_core-0.0.9/src/spectre_core/receivers/library/rspduo/__init__.py +0 -0
  91. spectre_core-0.0.9/src/spectre_core/receivers/library/rspduo/gr/__init__.py +0 -0
  92. spectre_core-0.0.9/src/spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +0 -114
  93. spectre_core-0.0.9/src/spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +0 -131
  94. spectre_core-0.0.9/src/spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py +0 -120
  95. spectre_core-0.0.9/src/spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py +0 -119
  96. spectre_core-0.0.9/src/spectre_core/receivers/library/rspduo/receiver.py +0 -97
  97. spectre_core-0.0.9/src/spectre_core/receivers/library/test/__init__.py +0 -0
  98. spectre_core-0.0.9/src/spectre_core/receivers/library/test/gr/__init__.py +0 -0
  99. spectre_core-0.0.9/src/spectre_core/receivers/library/test/gr/cosine_signal_1.py +0 -83
  100. spectre_core-0.0.9/src/spectre_core/receivers/library/test/gr/tagged_staircase.py +0 -93
  101. spectre_core-0.0.9/src/spectre_core/receivers/library/test/receiver.py +0 -203
  102. spectre_core-0.0.9/src/spectre_core/receivers/validators.py +0 -231
  103. spectre_core-0.0.9/src/spectre_core/spectrograms/__init__.py +0 -3
  104. spectre_core-0.0.9/src/spectre_core/web_fetch/callisto.py +0 -101
  105. spectre_core-0.0.9/src/spectre_core.egg-info/SOURCES.txt +0 -77
  106. {spectre_core-0.0.9 → spectre_core-0.0.11}/LICENSE +0 -0
  107. {spectre_core-0.0.9 → spectre_core-0.0.11}/README.md +0 -0
  108. {spectre_core-0.0.9 → spectre_core-0.0.11}/setup.cfg +0 -0
  109. {spectre_core-0.0.9/src/spectre_core/chunks/library/callisto → spectre_core-0.0.11/src/spectre_core}/__init__.py +0 -0
  110. /spectre_core-0.0.9/src/spectre_core/chunks/chunk_register.py → /spectre_core-0.0.11/src/spectre_core/chunks/_register.py +0 -0
  111. /spectre_core-0.0.9/src/spectre_core/post_processing/event_handler_register.py → /spectre_core-0.0.11/src/spectre_core/post_processing/_register.py +0 -0
  112. /spectre_core-0.0.9/src/spectre_core/receivers/receiver_register.py → /spectre_core-0.0.11/src/spectre_core/receivers/_register.py +0 -0
  113. {spectre_core-0.0.9/src/spectre_core → spectre_core-0.0.11/src/spectre_core/receivers/gr}/__init__.py +0 -0
  114. {spectre_core-0.0.9 → spectre_core-0.0.11}/src/spectre_core.egg-info/dependency_links.txt +0 -0
  115. {spectre_core-0.0.9 → spectre_core-0.0.11}/src/spectre_core.egg-info/requires.txt +0 -0
  116. {spectre_core-0.0.9 → spectre_core-0.0.11}/src/spectre_core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spectre-core
3
- Version: 0.0.9
3
+ Version: 0.0.11
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spectre-core"
7
- version = "0.0.9"
7
+ version = "0.0.11"
8
8
  maintainers = [
9
9
  { name="Jimmy Fitzpatrick", email="jcfitzpatrick12@gmail.com" },
10
10
  ]
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ """
6
+ Internal file handling.
7
+ """
8
+
9
+ from .file_handlers import (
10
+ BaseFileHandler, JsonHandler, TextHandler,
11
+ )
12
+
13
+ __all__ = [
14
+ "BaseFileHandler", "JsonHandler", "TextHandler"
15
+ ]
@@ -0,0 +1,128 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import os
6
+ import json
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, Optional
9
+
10
+
11
+ class BaseFileHandler(ABC):
12
+ def __init__(self,
13
+ parent_path: str,
14
+ base_file_name: str,
15
+ extension: Optional[str] = None):
16
+ self._parent_path = parent_path
17
+ self._base_file_name = base_file_name
18
+ self._extension = extension
19
+
20
+
21
+ @abstractmethod
22
+ def read(self) -> Any:
23
+ pass
24
+
25
+
26
+ @property
27
+ def parent_path(self) -> str:
28
+ return self._parent_path
29
+
30
+
31
+ @property
32
+ def base_file_name(self) -> str:
33
+ return self._base_file_name
34
+
35
+
36
+ @property
37
+ def extension(self) -> Optional[str]:
38
+ return self._extension
39
+
40
+
41
+ @property
42
+ def file_name(self) -> str:
43
+ return self._base_file_name if (self._extension is None) else f"{self._base_file_name}.{self._extension}"
44
+
45
+
46
+ @property
47
+ def file_path(self) -> str:
48
+ return os.path.join(self._parent_path, self.file_name)
49
+
50
+
51
+ @property
52
+ def exists(self) -> bool:
53
+ return os.path.exists(self.file_path)
54
+
55
+
56
+ def make_parent_path(self) -> None:
57
+ os.makedirs(self.parent_path, exist_ok=True)
58
+
59
+
60
+ def delete(self,
61
+ ignore_if_missing: bool = False) -> None:
62
+ if not self.exists and not ignore_if_missing:
63
+ raise FileNotFoundError(f"{self.file_name} does not exist, and so cannot be deleted")
64
+ else:
65
+ os.remove(self.file_path)
66
+
67
+
68
+ def cat(self) -> None:
69
+ print(self.read())
70
+
71
+
72
+ class JsonHandler(BaseFileHandler):
73
+ def __init__(self,
74
+ parent_path: str,
75
+ base_file_name: str,
76
+ extension: str = "json",
77
+ **kwargs):
78
+
79
+ self._dict = None # cache
80
+ super().__init__(parent_path,
81
+ base_file_name,
82
+ extension,
83
+ **kwargs)
84
+
85
+
86
+ def read(self) -> dict[str, Any]:
87
+ with open(self.file_path, 'r') as f:
88
+ return json.load(f)
89
+
90
+
91
+ def save(self,
92
+ d: dict,
93
+ force: bool = False) -> None:
94
+ self.make_parent_path()
95
+
96
+ if self.exists:
97
+ if force:
98
+ pass
99
+ else:
100
+ raise RuntimeError((f"{self.file_name} already exists, write has been abandoned. "
101
+ f"You can override this functionality with `force`"))
102
+
103
+ with open(self.file_path, 'w') as file:
104
+ json.dump(d, file, indent=4)
105
+
106
+
107
+ @property
108
+ def dict(self) -> dict[str, Any]:
109
+ if self._dict is None:
110
+ self._dict = self.read()
111
+ return self._dict
112
+
113
+
114
+ class TextHandler(BaseFileHandler):
115
+ def __init__(self,
116
+ parent_path: str,
117
+ base_file_name: str,
118
+ extension: str = "txt",
119
+ **kwargs):
120
+ super().__init__(parent_path,
121
+ base_file_name,
122
+ extension,
123
+ **kwargs)
124
+
125
+
126
+ def read(self) -> dict:
127
+ with open(self.file_path, 'r') as f:
128
+ return f.read()
@@ -0,0 +1,29 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ """
6
+ Capture configuration files.
7
+ """
8
+
9
+ from ._pvalidators import PValidators
10
+ from ._capture_config import CaptureConfig
11
+ from ._parameters import (
12
+ Parameter, Parameters, parse_string_parameters, make_parameters
13
+ )
14
+ from ._capture_templates import (
15
+ CaptureTemplate, CaptureModes, get_base_capture_template, make_base_capture_template
16
+ )
17
+ from ._pconstraints import (
18
+ PConstraint, PConstraints, Bound, OneOf
19
+ )
20
+ from ._ptemplates import (
21
+ PTemplate, PNames, get_base_ptemplate,
22
+ )
23
+
24
+ __all__ = [
25
+ "Parameter", "Parameters", "parse_string_parameters", "make_parameters",
26
+ "PConstraint", "PConstraints", "Bound", "OneOf", "PNames", "PTemplate",
27
+ "get_base_ptemplate", "PValidators", "CaptureConfig", "CaptureTemplate",
28
+ "CaptureModes", "get_base_capture_template", "make_base_capture_template"
29
+ ]
@@ -0,0 +1,85 @@
1
+ # SPDX-FileCopyrightText: © 2024 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
+
7
+ from spectre_core._file_io import JsonHandler
8
+ from spectre_core.config import get_configs_dir_path
9
+ from spectre_core.exceptions import InvalidTagError
10
+ from ._parameters import (
11
+ Parameter,
12
+ Parameters,
13
+ make_parameters
14
+ )
15
+
16
+ @dataclass
17
+ class _CaptureConfigKeys:
18
+ RECEIVER_NAME = "receiver_name"
19
+ RECEIVER_MODE = "receiver_mode"
20
+ PARAMETERS = "parameters"
21
+
22
+
23
+ class CaptureConfig(JsonHandler):
24
+ def __init__(self,
25
+ tag: str):
26
+ self._validate_tag(tag)
27
+ self._tag = tag
28
+ super().__init__(get_configs_dir_path(),
29
+ f"capture_{tag}")
30
+
31
+ @property
32
+ def tag(self) -> str:
33
+ """Unique identifier for the capture config."""
34
+ return self._tag
35
+
36
+
37
+ def _validate_tag(self,
38
+ tag: str) -> None:
39
+ if "_" in tag:
40
+ raise InvalidTagError(f"Tags cannot contain an underscore. Received {tag}")
41
+ if "callisto" in tag:
42
+ raise InvalidTagError(f'"callisto" cannot be a substring in a native tag. Received "{tag}"')
43
+
44
+
45
+ @property
46
+ def receiver_name(self) -> str:
47
+ """The name of the receiver which created the capture config."""
48
+ return self.dict[_CaptureConfigKeys.RECEIVER_NAME]
49
+
50
+
51
+ @property
52
+ def receiver_mode(self) -> str:
53
+ """The mode of the receiver which created the capture config."""
54
+ return self.dict[_CaptureConfigKeys.RECEIVER_MODE]
55
+
56
+
57
+ @property
58
+ def parameters(self) -> Parameters:
59
+ """The parameters stored inside the capture config."""
60
+ return make_parameters( self.dict[_CaptureConfigKeys.PARAMETERS] )
61
+
62
+
63
+ def get_parameter(self,
64
+ name: str) -> Parameter:
65
+ return self.parameters.get_parameter(name)
66
+
67
+
68
+ def get_parameter_value(self,
69
+ name: str) -> Parameter:
70
+ return self.parameters.get_parameter_value(name)
71
+
72
+
73
+ def save_parameters(self,
74
+ receiver_name: str,
75
+ receiver_mode: str,
76
+ parameters: Parameters,
77
+ force: bool = False):
78
+ """Write the input parameters to file."""
79
+ d = {
80
+ _CaptureConfigKeys.RECEIVER_MODE: receiver_mode,
81
+ _CaptureConfigKeys.RECEIVER_NAME: receiver_name,
82
+ _CaptureConfigKeys.PARAMETERS : parameters.to_dict()
83
+ }
84
+ self.save(d,
85
+ force=force)
@@ -0,0 +1,222 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from copy import deepcopy
6
+ from typing import Any
7
+ from dataclasses import dataclass
8
+
9
+ from ._parameters import Parameter, Parameters
10
+ from ._pconstraints import PConstraint
11
+ from ._ptemplates import (
12
+ PTemplate,
13
+ PNames,
14
+ get_base_ptemplate
15
+ )
16
+
17
+ class CaptureTemplate:
18
+ """A managed collection of PTemplates"""
19
+ def __init__(self):
20
+ self._ptemplates: dict[str, PTemplate] = {}
21
+
22
+
23
+ @property
24
+ def name_list(self) -> list[str]:
25
+ """List the names of all stored PTemplates."""
26
+ return list(self._ptemplates.keys())
27
+
28
+
29
+ def add_ptemplate(self,
30
+ ptemplate: PTemplate) -> None:
31
+ """Add a ptemplate to this capture template."""
32
+ self._ptemplates[ptemplate.name] = ptemplate
33
+
34
+
35
+ def get_ptemplate(self,
36
+ parameter_name: str) -> PTemplate:
37
+ """Get the ptemplate corresponding with the parameter name."""
38
+ if parameter_name not in self._ptemplates:
39
+ raise ValueError(f"Parameter with name '{parameter_name}' is not found in the template. "
40
+ f"Expected one of {self.name_list}")
41
+ return self._ptemplates[parameter_name]
42
+
43
+
44
+ def __apply_parameter_template(self,
45
+ parameter: Parameter):
46
+ """Apply the corresponding parameter template to the input parameter"""
47
+ ptemplate = self.get_ptemplate(parameter.name)
48
+ parameter.value = ptemplate.apply_template(parameter.value)
49
+
50
+
51
+ def __apply_parameter_templates(self,
52
+ parameters: Parameters) -> None:
53
+ """Apply the corresponding parameter template to all explictly specified parameters"""
54
+ for parameter in parameters:
55
+ self.__apply_parameter_template(parameter)
56
+
57
+
58
+ def __fill_missing_with_defaults(self,
59
+ parameters: Parameters) -> None:
60
+ """For any missing parameters (as per the capture template), use the corresponding default value."""
61
+ for ptemplate in self:
62
+ if ptemplate.name not in parameters.name_list:
63
+ parameter = ptemplate.make_parameter()
64
+ parameters.add_parameter(parameter.name,
65
+ parameter.value)
66
+
67
+
68
+ def apply_template(self,
69
+ parameters: Parameters) -> Parameters:
70
+ """Validate parameters, fill missing with defaults, and return anew."""
71
+ self.__apply_parameter_templates(parameters)
72
+ self.__fill_missing_with_defaults(parameters)
73
+ return parameters
74
+
75
+
76
+ def __iter__(self):
77
+ """Iterate over stored ptemplates"""
78
+ yield from self._ptemplates.values()
79
+
80
+
81
+ def set_default(self,
82
+ parameter_name: str,
83
+ default: Any) -> None:
84
+ """Set the default of an existing ptemplate."""
85
+ self.get_ptemplate(parameter_name).default = default
86
+
87
+
88
+ def set_defaults(self,
89
+ *ptuples: tuple[str, Any]) -> None:
90
+ """Update defaults for multiple ptemplates."""
91
+ for (parameter_name, default) in ptuples:
92
+ self.set_default(parameter_name, default)
93
+
94
+
95
+ def enforce_default(self,
96
+ parameter_name: str) -> None:
97
+ """Enforce the default of an existing ptemplate"""
98
+ self.get_ptemplate(parameter_name).enforce_default = True
99
+
100
+
101
+ def enforce_defaults(self,
102
+ *parameter_names: str) -> None:
103
+ """Enforce defaults for multiple parameter names."""
104
+ for name in parameter_names:
105
+ self.enforce_default(name)
106
+
107
+
108
+ def add_pconstraint(self,
109
+ parameter_name: str,
110
+ pconstraints: list[PConstraint]) -> None:
111
+ """Add a pconstraint to an existing ptemplate"""
112
+ for pconstraint in pconstraints:
113
+ self.get_ptemplate(parameter_name).add_pconstraint(pconstraint)
114
+
115
+
116
+ def to_dict(self) -> dict:
117
+ return {ptemplate.name: ptemplate.to_dict() for ptemplate in self}
118
+
119
+
120
+ def make_base_capture_template(*parameter_names: str):
121
+ """Make a capture template, composed entirely of base ptemplates."""
122
+ capture_template = CaptureTemplate()
123
+ for name in parameter_names:
124
+ capture_template.add_ptemplate( get_base_ptemplate(name) )
125
+ return capture_template
126
+
127
+
128
+ @dataclass(frozen=True)
129
+ class CaptureModes:
130
+ """Pre-defined capture types"""
131
+ FIXED_CENTER_FREQUENCY: str = "fixed-center-frequency"
132
+ SWEPT_CENTER_FREQUENCY: str = "swept-center-frequency"
133
+
134
+
135
+ def _make_fixed_frequency_capture_template(
136
+ ) -> CaptureTemplate:
137
+ """The absolute minimum required parameters for any fixed frequency capture template."""
138
+ capture_template = make_base_capture_template(
139
+ PNames.BATCH_SIZE,
140
+ PNames.CENTER_FREQUENCY,
141
+ PNames.CHUNK_KEY,
142
+ PNames.EVENT_HANDLER_KEY,
143
+ PNames.FREQUENCY_RESOLUTION,
144
+ PNames.INSTRUMENT,
145
+ PNames.OBS_ALT,
146
+ PNames.OBS_LAT,
147
+ PNames.OBS_LON,
148
+ PNames.OBJECT,
149
+ PNames.ORIGIN,
150
+ PNames.SAMPLE_RATE,
151
+ PNames.TELESCOPE,
152
+ PNames.TIME_RANGE,
153
+ PNames.TIME_RESOLUTION,
154
+ PNames.WATCH_EXTENSION,
155
+ PNames.WINDOW_HOP,
156
+ PNames.WINDOW_SIZE,
157
+ PNames.WINDOW_TYPE,
158
+ )
159
+ capture_template.set_defaults(
160
+ (PNames.EVENT_HANDLER_KEY, CaptureModes.FIXED_CENTER_FREQUENCY),
161
+ (PNames.CHUNK_KEY, CaptureModes.FIXED_CENTER_FREQUENCY),
162
+ (PNames.WATCH_EXTENSION, "bin")
163
+ )
164
+ capture_template.enforce_defaults(
165
+ PNames.EVENT_HANDLER_KEY,
166
+ PNames.CHUNK_KEY,
167
+ PNames.WATCH_EXTENSION
168
+ )
169
+ return capture_template
170
+
171
+ def _make_swept_frequency_capture_template(
172
+ ) -> CaptureTemplate:
173
+ """The absolute minimum required parameters for any swept frequency capture template."""
174
+ capture_template = make_base_capture_template(
175
+ PNames.BATCH_SIZE,
176
+ PNames.CHUNK_KEY,
177
+ PNames.EVENT_HANDLER_KEY,
178
+ PNames.FREQUENCY_RESOLUTION,
179
+ PNames.FREQUENCY_STEP,
180
+ PNames.INSTRUMENT,
181
+ PNames.MAX_FREQUENCY,
182
+ PNames.MIN_FREQUENCY,
183
+ PNames.OBS_ALT,
184
+ PNames.OBS_LAT,
185
+ PNames.OBS_LON,
186
+ PNames.OBJECT,
187
+ PNames.ORIGIN,
188
+ PNames.SAMPLE_RATE,
189
+ PNames.SAMPLES_PER_STEP,
190
+ PNames.TELESCOPE,
191
+ PNames.TIME_RANGE,
192
+ PNames.TIME_RESOLUTION,
193
+ PNames.WATCH_EXTENSION,
194
+ PNames.WINDOW_HOP,
195
+ PNames.WINDOW_SIZE,
196
+ PNames.WINDOW_TYPE)
197
+ capture_template.set_defaults(
198
+ (PNames.EVENT_HANDLER_KEY, CaptureModes.SWEPT_CENTER_FREQUENCY),
199
+ (PNames.CHUNK_KEY, CaptureModes.SWEPT_CENTER_FREQUENCY),
200
+ (PNames.WATCH_EXTENSION, "bin")
201
+ )
202
+ capture_template.enforce_defaults(
203
+ PNames.EVENT_HANDLER_KEY,
204
+ PNames.CHUNK_KEY,
205
+ PNames.WATCH_EXTENSION
206
+ )
207
+ return capture_template
208
+
209
+
210
+ _base_capture_templates = {
211
+ CaptureModes.FIXED_CENTER_FREQUENCY: _make_fixed_frequency_capture_template(),
212
+ CaptureModes.SWEPT_CENTER_FREQUENCY: _make_swept_frequency_capture_template()
213
+ }
214
+
215
+ def get_base_capture_template(
216
+ capture_mode: str
217
+ ) -> CaptureTemplate:
218
+ """Create a fresh deep copy of a pre-defined capture template"""
219
+ if capture_mode not in _base_capture_templates:
220
+ raise KeyError(f"No capture template found for the capture mode '{capture_mode}'. "
221
+ f"Expected one of {list(_base_capture_templates.keys())}")
222
+ return deepcopy( _base_capture_templates[capture_mode] )
@@ -0,0 +1,110 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from typing import Any, Optional, TypeVar
6
+
7
+ T = TypeVar('T')
8
+
9
+ class Parameter:
10
+ """A named value."""
11
+ def __init__(self,
12
+ name: str,
13
+ value: Optional[T] = None):
14
+ self._name = name
15
+ self._value: Optional[T] = value
16
+
17
+
18
+ @property
19
+ def name(self) -> str:
20
+ """The parameter name."""
21
+ return self._name
22
+
23
+
24
+ @property
25
+ def value(self) -> Optional[T]:
26
+ """The parameter value."""
27
+ return self._value
28
+
29
+
30
+ @value.setter
31
+ def value(self, v: Optional[T]) -> None:
32
+ """Update the parameter value."""
33
+ self._value = v
34
+
35
+
36
+ class Parameters:
37
+ """A collection of parameters."""
38
+ def __init__(self):
39
+ self._parameters: dict[str, Parameter] = {}
40
+
41
+
42
+ @property
43
+ def name_list(self) -> list[str]:
44
+ """List the names of stored parameters."""
45
+ return list(self._parameters.keys())
46
+
47
+
48
+ def add_parameter(self,
49
+ name: str,
50
+ value: Optional[T] = None,
51
+ force: bool = False) -> None:
52
+ """Add a new parameter."""
53
+ if name in self._parameters and not force:
54
+ raise ValueError(f"Cannot add a parameter with name '{name}', "
55
+ f"since a parameter already exists with that name. "
56
+ f"You can overrride this functionality with 'force', "
57
+ f"to overwrite the existing parameter.")
58
+ self._parameters[name] = Parameter(name, value)
59
+
60
+
61
+ def get_parameter(self,
62
+ name: str) -> Parameter:
63
+ """Get the parameter with the corresponding name."""
64
+ if name not in self._parameters:
65
+ raise KeyError(f"Parameter with name '{name}' does not exist. "
66
+ f"Expected one of {self.name_list}")
67
+ return self._parameters[name]
68
+
69
+
70
+ def get_parameter_value(self,
71
+ name: str) -> Optional[T]:
72
+ """Get the value of the parameter with the corresponding name."""
73
+ return self.get_parameter(name).value
74
+
75
+
76
+ def __iter__(self):
77
+ """Iterate over stored parameters"""
78
+ yield from self._parameters.values()
79
+
80
+
81
+ def to_dict(self) -> dict:
82
+ """Convert the class instance to an equivalent dictionary representation"""
83
+ return {p.name: p.value for p in self}
84
+
85
+ def _parse_string_parameter(string_parameter: str) -> list[str]:
86
+ """Parse string of the form 'a=b; into a list of the form [a, b]"""
87
+ if not string_parameter or '=' not in string_parameter:
88
+ raise ValueError(f"Invalid format: '{string_parameter}'. Expected 'KEY=VALUE'.")
89
+ if string_parameter.startswith('=') or string_parameter.endswith('='):
90
+ raise ValueError(f"Invalid format: '{string_parameter}'. Expected 'KEY=VALUE'.")
91
+ # remove leading and trailing whitespace.
92
+ string_parameter = string_parameter.strip()
93
+ return string_parameter.split('=', 1)
94
+
95
+
96
+ def parse_string_parameters(string_parameters: list[str]) -> dict[str, str]:
97
+ """Parses a list of strings of the form 'a=b'; into a dictionary mapping each 'a' to each 'b'"""
98
+ d = {}
99
+ for string_parameter in string_parameters:
100
+ k, v = _parse_string_parameter(string_parameter)
101
+ d[k] = v
102
+ return d
103
+
104
+
105
+ def make_parameters(d: dict[str, Any]):
106
+ """Make an instance of parameters based on the input dictionary"""
107
+ parameters = Parameters()
108
+ for k, v in d.items():
109
+ parameters.add_parameter(k, v)
110
+ return parameters
@@ -0,0 +1,82 @@
1
+ # SPDX-FileCopyrightText: © 2024 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 abc import ABC, abstractmethod
7
+ from typing import TypeVar, Optional, Any
8
+ from numbers import Number
9
+
10
+ T = TypeVar('T')
11
+
12
+ class PConstraint(ABC):
13
+ """Abstract base class for parameter constraints."""
14
+
15
+ @abstractmethod
16
+ def constrain(self, value: T) -> None:
17
+ """Apply a constraint to the input parameter."""
18
+ pass
19
+
20
+ def __format__(self, format_spec: str = "") -> str:
21
+ attrs = ", ".join(f"{key}={value!r}" for key, value in vars(self).items())
22
+ return f"{self.__class__.__name__}({attrs})"
23
+
24
+ class Bound(PConstraint):
25
+ """Constrain a parameter value to a specific range."""
26
+
27
+ def __init__(self,
28
+ lower_bound: Optional[Number] = None,
29
+ upper_bound: Optional[Number] = None,
30
+ strict_lower: bool = False,
31
+ strict_upper: bool = False):
32
+ self._lower_bound = lower_bound
33
+ self._upper_bound = upper_bound
34
+ self._strict_lower = strict_lower
35
+ self._strict_upper = strict_upper
36
+
37
+ def constrain(self, value: Number) -> None:
38
+ if self._lower_bound is not None:
39
+ if self._strict_lower and value <= self._lower_bound:
40
+ raise ValueError(f"Value must be strictly greater than {self._lower_bound}. "
41
+ f"Got {value}.")
42
+ if not self._strict_lower and value < self._lower_bound:
43
+ raise ValueError(f"Value must be greater than or equal to {self._lower_bound}. "
44
+ f"Got {value}.")
45
+
46
+ if self._upper_bound is not None:
47
+ if self._strict_upper and value >= self._upper_bound:
48
+ raise ValueError(f"Value must be strictly less than {self._upper_bound}. "
49
+ f"Got {value}.")
50
+ if not self._strict_upper and value > self._upper_bound:
51
+ raise ValueError(f"Value must be less than or equal to {self._upper_bound}. "
52
+ f"Got {value}.")
53
+
54
+
55
+ class OneOf(PConstraint):
56
+ """Constrain a parameter to one of a set of defined options."""
57
+
58
+ def __init__(self, options: list[Any] = None):
59
+ self._options = options
60
+
61
+
62
+ def constrain(self, value: Any) -> None:
63
+ if value not in self._options:
64
+ raise ValueError(f"Value must be one of {self._options}. Got {value}.")
65
+
66
+
67
+ class _PowerOfTwo(PConstraint):
68
+ """Constrain a parameter value to be a power of two."""
69
+
70
+ def constrain(self, value: Number) -> None:
71
+ if value <= 0 or (value & (value - 1)) != 0:
72
+ raise ValueError(f"Value must be a power of two. Got {value}.")
73
+
74
+
75
+ @dataclass(frozen=True)
76
+ class PConstraints:
77
+ """Ready-made pconstraint instances for frequent use-cases."""
78
+ power_of_two = _PowerOfTwo()
79
+ enforce_positive = Bound(lower_bound=0, strict_lower=True)
80
+ enforce_negative = Bound(upper_bound=0, strict_upper=True)
81
+ enforce_non_negative = Bound(lower_bound=0, strict_lower=False)
82
+ enforce_non_positive = Bound(upper_bound=0, strict_upper=False)