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 +10 -0
- ihmt/config.py +38 -0
- ihmt/configs/default.yaml +82 -0
- ihmt/corrector.py +223 -0
- ihmt/meta.py +510 -0
- ihmt/pulse.py +336 -0
- ihmt/run.py +293 -0
- ihmt/sequence.py +481 -0
- ihmt/simulator.py +390 -0
- ihmt/system.py +1095 -0
- ihmt/trajector.py +117 -0
- ihmt/vectorized/pulse.py +326 -0
- ihmt-0.9.0.dist-info/METADATA +2221 -0
- ihmt-0.9.0.dist-info/RECORD +16 -0
- ihmt-0.9.0.dist-info/WHEEL +4 -0
- ihmt-0.9.0.dist-info/licenses/LICENSE +304 -0
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)
|