ihmt 0.9.0__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.
ihmt/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ from ihmt.meta import Signal, Duration, Frequency, AngularFrequency, Angle, CompositeDictionary
2
+ from ihmt.pulse import Tukey
3
+ from ihmt.system import System
4
+ from ihmt.sequence import Sequence
5
+ from ihmt.simulator import Simulator
6
+ from ihmt.corrector import Corrector
7
+ from ihmt.trajector import Trajector
8
+ from ihmt.run import SingleRun, GridRuns, SampledRuns
9
+
10
+ from ihmt.vectorized.pulse import TukeyVector
ihmt/config.py ADDED
@@ -0,0 +1,38 @@
1
+ from logging import getLogger, NullHandler
2
+ from pathlib import Path
3
+ from yaml import safe_load # noqa: F401
4
+ from typing import Any
5
+
6
+ logger = getLogger(__name__)
7
+ logger.addHandler(NullHandler())
8
+ logger.debug('`config` module loaded successfully')
9
+
10
+
11
+ default: Config
12
+ BP_3T : Config
13
+ MC_3T : Config
14
+ BP_7T : Config
15
+ MC_7T : Config
16
+
17
+
18
+ class Config(dict):
19
+ def __init__(self, mapping: Any):
20
+ super().__init__(mapping)
21
+
22
+ def as_dict(self):
23
+ ...
24
+
25
+ def to_file(self):
26
+ ...
27
+
28
+ def __getitem__(self, subscript: str):
29
+ return dict.__getitem__(self, subscript)
30
+
31
+
32
+ for file in (Path(__file__).parent / 'configs').glob('*.yaml'):
33
+ with open(file, 'r') as f:
34
+ try:
35
+ exec(f'{file.stem} = safe_load(f)')
36
+ logger.debug(f"<{file}> configuration file loaded successfully.")
37
+ except Exception as e:
38
+ logger.error(e)
@@ -0,0 +1,82 @@
1
+ run: # mandatory, provides the parameters for simulator.SingleRun
2
+ pw: 0.001
3
+ r_tukey: 0.3
4
+ fa_sat: 200
5
+ offset: 7000
6
+
7
+ FLAG_Signal: ALL
8
+ N_altern: 1
9
+ np: 4
10
+ nb: 10
11
+ turbo: 80
12
+ N_dummyADC: 3
13
+ dt: 0.0015
14
+ btr: 0.1
15
+ btrlast: 0.001
16
+ es: 0.006
17
+ tr: 3
18
+ fa_rage: 5
19
+
20
+ M0a: 1
21
+ M0b: 0.1
22
+ T1f: 1
23
+ T2f: 0.1
24
+ T1b: 1
25
+ T1D: 0.01
26
+ T2b: 1.e-5
27
+ R: 10
28
+ poolBound_lineshapeAsymmetry: -593.83
29
+
30
+ export_read: True
31
+ output_fullVector: True
32
+
33
+ outputDir: ./output/
34
+ filePrefix: ''
35
+ export: True
36
+
37
+ log: # if `log` category is undefined, no logging will happen
38
+ # https://docs.python.org/3/library/logging.config.html#dictionary-schema-details
39
+ version: 1
40
+ disable_existing_loggers: false
41
+
42
+ formatters:
43
+ standard:
44
+ format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
45
+
46
+ error:
47
+ format: "%(asctime)s - %(name)s - %(levelname)s <PID %(process)d:%(processName)s> %(name)s.%(funcName)s(): %(message)s"
48
+
49
+ handlers:
50
+ root_file_handler:
51
+ class: logging.FileHandler
52
+ level: INFO # NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL
53
+ formatter: standard
54
+ filename: ./output/logs.txt
55
+ mode: w # a: append, w: write
56
+ # encoding: utf8
57
+
58
+ debug_root_file_handler:
59
+ class: logging.FileHandler
60
+ level: DEBUG # NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL
61
+ formatter: error
62
+ filename: ./output/logs_debug.txt
63
+ # suffix: "%Y%m%d-%H%M%S"
64
+ mode: w # a: append, w: write
65
+ # encoding: utf8
66
+
67
+ console:
68
+ class: logging.StreamHandler
69
+ level: INFO # NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL
70
+ formatter: standard
71
+ stream: ext://sys.stdout
72
+
73
+ error_console:
74
+ class: logging.StreamHandler
75
+ level: ERROR # NOTSET, DEBUG, INFO, WARNING, ERROR, CRITICAL
76
+ formatter: error
77
+ stream: ext://sys.stderr
78
+
79
+ root:
80
+ level: DEBUG
81
+ # handlers: [console, error_console]
82
+ handlers: [console, error_console, root_file_handler, debug_root_file_handler]
ihmt/corrector.py ADDED
@@ -0,0 +1,223 @@
1
+ from logging import getLogger, NullHandler
2
+ from numpy import linspace, float64, ndarray, number, meshgrid, vstack, nan, ones
3
+ from functools import partial
4
+ from scipy.interpolate import PchipInterpolator, RegularGridInterpolator
5
+ from copy import deepcopy
6
+
7
+ from ihmt.meta import _Event, Signal, CompositeDictionary
8
+ from ihmt.simulator import Simulator
9
+ from ihmt.run import GridRuns
10
+
11
+ logger = getLogger(__name__)
12
+ logger.addHandler(NullHandler())
13
+ logger.debug("`corrector` module loaded successfully")
14
+
15
+
16
+ class Corrector(_Event):
17
+ _ranges: dict[str, ndarray[number]]
18
+ _simulator: Simulator
19
+
20
+ _simulated: dict[str, ndarray[number]]
21
+ _nominals: dict[str, float]
22
+ _interpolants: dict[str, PchipInterpolator | RegularGridInterpolator]
23
+
24
+ _classAttributes: tuple[str] = (
25
+ "ranges",
26
+ "simulator",
27
+ "simulated",
28
+ "mesh",
29
+ "nominals",
30
+ "interpolants",
31
+ )
32
+
33
+ @staticmethod
34
+ def Simple(simulator: Simulator) -> Corrector:
35
+ return Corrector(
36
+ simulator=simulator,
37
+ ranges={"flipAngle": simulator.pulse.flipAngle * linspace(0.1, 1.5, 141)},
38
+ )
39
+
40
+ def __init__(self, simulator: Simulator, ranges: dict[str, ndarray[number]]):
41
+ self.simulator = simulator
42
+ self.ranges = ranges
43
+
44
+ self.onChange(
45
+ "ranges",
46
+ [
47
+ lambda: self._reset_computed_attributes(
48
+ ["simulated", "nominals", "interpolants"]
49
+ )
50
+ ],
51
+ )
52
+ self.onChange(
53
+ "simulator",
54
+ [
55
+ lambda: self._reset_computed_attributes(
56
+ ["simulated", "nominals", "interpolants"]
57
+ )
58
+ ],
59
+ )
60
+
61
+ def copy(self) -> Corrector:
62
+ return Corrector(self.simulator.copy(), deepcopy(self.ranges))
63
+
64
+ def apply(
65
+ self,
66
+ parameter_maps: dict[str, ndarray[number]],
67
+ data_maps: dict[Signal, ndarray[number]],
68
+ ) -> CompositeDictionary[Signal, ndarray[number]]:
69
+ for key in self.ranges.keys():
70
+ if key not in parameter_maps.keys():
71
+ raise KeyError(f"Missing key `{key}` in parameter map dictionary.")
72
+
73
+ for key in data_maps.keys():
74
+ if type(key) != Signal:
75
+ raise TypeError(
76
+ f"Accepting `{type(Signal)}` flags only. Received `{type(key)}`."
77
+ )
78
+
79
+ shape = None
80
+ for key, val in (parameter_maps | data_maps).items():
81
+ if shape is None:
82
+ shape = val.shape
83
+ continue
84
+ if val.shape != shape:
85
+ raise ValueError(
86
+ f"Arrays need to match shape. Received shape `{val.shape}` for array `{key}` while trying to match shape `{shape}`."
87
+ )
88
+
89
+ mask = (
90
+ ones(shape).astype(bool)
91
+ if "mask" not in parameter_maps.keys()
92
+ else parameter_maps["mask"].astype(bool)
93
+ )
94
+
95
+ parameters = vstack(
96
+ [parameter_maps[key][mask].flatten() for key in self.ranges.keys()]
97
+ ).T
98
+
99
+ corrected: dict[Signal, ndarray[number]] = dict()
100
+ for key, value in data_maps.items():
101
+ corrected[key] = value.copy().astype(float64)
102
+ corrected[key][mask] *= (
103
+ self.nominals[key] / self.interpolants[key](parameters)
104
+ ).squeeze()
105
+
106
+ return CompositeDictionary(corrected)
107
+
108
+ #####
109
+ # BELOW: property getters and setters
110
+ #####
111
+ @property
112
+ def ranges(self) -> dict[str, ndarray[number]]:
113
+ return self._ranges
114
+
115
+ @ranges.setter
116
+ def ranges(self, val: dict[str, ndarray[number]]):
117
+ self._ranges = deepcopy(val)
118
+ for val in self._ranges.values():
119
+ val.setflags(write=False)
120
+ self._changed("ranges")
121
+
122
+ @property
123
+ def simulator(self) -> Simulator:
124
+ return self._simulator
125
+
126
+ @simulator.setter
127
+ def simulator(self, val: Simulator):
128
+ self._simulator = val
129
+ self._changed("simulator")
130
+
131
+ @property # immutable for the user, so only getter is defined
132
+ def simulated(self) -> CompositeDictionary[str, ndarray[number]]:
133
+ if not hasattr(self, "_simulated"):
134
+ sim = self.simulator.copy()
135
+ sim.output_vectorSlice = slice(1)
136
+ sim.export_readMatrix = False
137
+ self._simulated = CompositeDictionary(
138
+ GridRuns(sim, list(self.ranges.keys()), self.ranges, safe=True)
139
+ ).squeeze()
140
+ return self._simulated
141
+
142
+ @property # immutable for the user, so only getter is defined
143
+ def mesh(self) -> dict[str, ndarray[number]]:
144
+ if not hasattr(self, "_mesh"):
145
+ tmp = dict()
146
+ mesh = meshgrid(*list(self.ranges.values()), indexing="ij", sparse=True)
147
+ for key, val in zip(self.ranges.keys(), mesh):
148
+ tmp[key] = val
149
+ tmp[key].setflags(write=False)
150
+ self._mesh = tmp
151
+ return self._mesh
152
+
153
+ @property # immutable for the user, so only getter is defined
154
+ def nominals(self) -> CompositeDictionary[str, float]:
155
+ if not hasattr(self, "_nominals"):
156
+ tmp = self.simulator.output_vectorSlice
157
+ self.simulator.output_vectorSlice = slice(1)
158
+ self._nominals = CompositeDictionary(self.simulator.SteadyState())
159
+ self.simulator.output_vectorSlice = tmp
160
+ return self._nominals
161
+
162
+ @property # immutable for the user, so only getter is defined
163
+ def interpolants(self):
164
+ if not hasattr(self, "_interpolants"):
165
+ self._interpolants = InterpolantDictionary(
166
+ interpolator=(
167
+ PchipInterpolator
168
+ if len(self.ranges) == 1
169
+ else partial(
170
+ RegularGridInterpolator, bounds_error=False, fill_value=nan
171
+ )
172
+ ),
173
+ ranges=self.ranges,
174
+ simulated=self.simulated,
175
+ )
176
+ return self._interpolants
177
+
178
+
179
+ class InterpolantDictionary(dict):
180
+ def __init__(
181
+ self,
182
+ interpolator: PchipInterpolator | RegularGridInterpolator,
183
+ ranges: dict[str, ndarray[number]],
184
+ simulated: CompositeDictionary[str, ndarray[number]],
185
+ ):
186
+ self._interpolator = interpolator
187
+ self._ranges = tuple(ranges.values())
188
+ self._simulated = simulated
189
+
190
+ if len(self._ranges) == 1:
191
+ self._ranges = tuple(self._ranges[0].tolist())
192
+
193
+ super().__init__()
194
+
195
+ def __getitem__(self, subscript: Signal):
196
+ if type(subscript) != Signal:
197
+ raise TypeError(
198
+ f"Accepting `{type(Signal)}` flags only. Received `{type(subscript)}`."
199
+ )
200
+
201
+ if subscript == Signal.ALL:
202
+ for subscript in Signal.values():
203
+ if (subscript != Signal.ALL) and (subscript not in self.keys()):
204
+ try:
205
+ dict.__setitem__(
206
+ self,
207
+ subscript,
208
+ self._interpolator(
209
+ self._ranges, self._simulated[subscript]
210
+ ),
211
+ )
212
+ except Exception as _:
213
+ pass
214
+ return self
215
+
216
+ elif (subscript not in self.keys()) and (subscript in Signal.values()):
217
+ dict.__setitem__(
218
+ self,
219
+ subscript,
220
+ self._interpolator(self._ranges, self._simulated[subscript]),
221
+ )
222
+
223
+ return dict.__getitem__(self, subscript)