-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathkmedium.py
More file actions
237 lines (197 loc) · 9.21 KB
/
kmedium.py
File metadata and controls
237 lines (197 loc) · 9.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import logging
from dataclasses import dataclass
from typing import List, Optional, Sequence, Union
import numpy as np
import kwave.utils.checks
@dataclass
class kWaveMedium(object):
"""
Medium properties for k-Wave simulations.
Note: For heterogeneous medium parameters, medium.sound_speed and medium.density
must be given in matrix form with the same dimensions as kgrid. For homogeneous
medium parameters, these can be given as single numeric values. If the medium is
homogeneous and velocity inputs or outputs are not required, it is not necessary
to specify medium.density.
"""
# sound speed distribution within the acoustic medium [m/s] | required to be defined
sound_speed: Union[float, int, np.ndarray]
# reference sound speed used within the k-space operator (phase correction term) [m/s]
sound_speed_ref: Optional[Union[float, int, np.ndarray]] = None
# density distribution within the acoustic medium [kg/m^3]
density: Optional[Union[float, int, np.ndarray]] = None
# power law absorption coefficient [dB/(MHz^y cm)]
alpha_coeff: Optional[Union[float, int, np.ndarray]] = None
# power law absorption exponent
alpha_power: Optional[Union[float, int, np.ndarray]] = None
# optional input to force either the absorption or dispersion terms in the equation of state to be excluded;
# valid inputs are 'no_absorption' or 'no_dispersion'
alpha_mode: Optional[str] = None
# frequency domain filter applied to the absorption and dispersion terms in the equation of state
alpha_filter: Optional[np.ndarray] = None
# two element array used to control the sign of absorption and dispersion terms in the equation of state
alpha_sign: Optional[np.ndarray] = None
# parameter of nonlinearity
BonA: Optional[Union[float, int, np.ndarray]] = None
# is the medium absorbing?
absorbing: bool = False
# is the medium absorbing stokes?
stokes: bool = False
def __post_init__(self):
self.sound_speed = np.atleast_1d(self.sound_speed)
def check_fields(self, kgrid_shape: Sequence[int]) -> None:
"""
Check whether the given properties are valid
Args:
kgrid_shape: Shape of the kWaveGrid
Returns:
None
"""
# check the absorption mode input is valid
if self.alpha_mode is not None:
assert self.alpha_mode in [
"no_absorption",
"no_dispersion",
"stokes",
], "medium.alpha_mode must be set to 'no_absorption', 'no_dispersion', or 'stokes'."
# check the absorption filter input is valid
if self.alpha_filter is not None and self.alpha_filter.shape != tuple(kgrid_shape):
raise ValueError("medium.alpha_filter must be the same size as the computational grid.")
# check the absorption sign input is valid
if self.alpha_sign is not None:
alpha_sign_arr = np.atleast_1d(self.alpha_sign)
if alpha_sign_arr.size != 2 or not np.issubdtype(alpha_sign_arr.dtype, np.number):
raise ValueError(
"medium.alpha_sign must be a 2 element numeric array controlling absorption and dispersion, respectively."
)
# check alpha_coeff is non-negative and real
if self.alpha_coeff is not None and (not np.all(np.isreal(self.alpha_coeff)) or np.any(self.alpha_coeff < 0)):
raise ValueError("medium.alpha_coeff must be non-negative and real.")
def is_defined(self, *fields) -> List[bool]:
"""
Check if the field(s) are defined or None
Args:
*fields: String list of the fields
Returns:
Boolean list
"""
results = []
for f in fields:
results.append(getattr(self, f) is not None)
return results
def ensure_defined(self, *fields) -> None:
"""
Assert that the field(s) are defined (not None)
Args:
*fields: String list of the fields
Returns:
None
"""
for f in fields:
assert getattr(self, f) is not None, f"The field {f} must not be None"
def is_nonlinear(self) -> bool:
"""
Check if the medium is nonlinear
Returns:
whether the fluid simulation is nonlinear
"""
return self.BonA is not None
def set_absorbing(self, is_absorbing, is_stokes=False) -> None:
"""
Change medium's absorbing and stokes properties
Args:
is_absorbing: Is the medium absorbing
is_stokes: Is the medium stokes
Returns:
None
"""
# only stokes absorption is supported in the axisymmetric code
self.absorbing, self.stokes = is_absorbing, is_stokes
if is_absorbing:
if is_stokes:
self._check_absorbing_with_stokes()
else:
self._check_absorbing_without_stokes()
def _check_absorbing_without_stokes(self) -> None:
"""
Check if the medium properties are set correctly for absorbing simulation without stokes
Returns:
None
"""
# enforce both absorption parameters
self.ensure_defined("alpha_coeff", "alpha_power")
# check y is a scalar
assert np.isscalar(self.alpha_power), "medium.alpha_power must be scalar."
# check y is real and within 0 to 3
assert (
np.all(np.isreal(self.alpha_coeff)) and 0 <= self.alpha_power < 3
), "medium.alpha_power must be a real number between 0 and 3."
# display warning if y is close to 1 and the dispersion term has not been set to zero
if self.alpha_mode != "no_dispersion":
assert self.alpha_power != 1, """The power law dispersion term in the equation of state is not valid for medium.alpha_power = 1.
This error can be avoided by choosing a power law exponent close to, but not exactly, 1.
If modelling acoustic absorption for medium.alpha_power = 1 is important and modelling dispersion is not
critical, this error can also be avoided by setting medium.alpha_mode to 'no_dispersion'"""
def _check_absorbing_with_stokes(self):
"""
Check if the medium properties are set correctly for absorbing simulation with stokes
Returns:
None
"""
# enforce absorption coefficient
self.ensure_defined("alpha_coeff")
# give warning if y is specified
if self.alpha_power is not None:
ap = np.asarray(self.alpha_power)
if ap.size != 1 or not np.isclose(ap.item(), 2.0):
logging.warning("the axisymmetric code and stokes absorption assume alpha_power = 2, user value ignored.")
# overwrite y value
self.alpha_power = 2
# don't allow medium.alpha_mode with the axisymmetric code
if self.alpha_mode is not None and (self.alpha_mode in ["no_absorption", "no_dispersion"]):
raise NotImplementedError(
"Input option medium.alpha_mode is not supported with the axisymmetric code " "or medium.alpha_mode = " "stokes" "."
)
# don't allow alpha_filter with stokes absorption (no variables are applied in k-space)
assert self.alpha_filter is None, (
"Input option medium.alpha_filter is not supported with the axisymmetric code " "or medium.alpha_mode = 'stokes'. "
)
##########################################
# Elastic-code related properties - raise error when accessed
##########################################
_ELASTIC_CODE_ACCESS_ERROR_TEXT_ = "Elastic simulation and related properties are not supported!"
@property
def sound_speed_shear(self): # pragma: no cover
"""
Shear sound speed (used in elastic simulations | not supported currently!)
"""
raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_)
@property
def sound_speed_ref_shear(self): # pragma: no cover
"""
Shear sound speed reference (used in elastic simulations | not supported currently!)
"""
raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_)
@property
def sound_speed_compression(self): # pragma: no cover
"""
Compression sound speed (used in elastic simulations | not supported currently!)
"""
raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_)
@property
def sound_speed_ref_compression(self): # pragma: no cover
"""
Compression sound speed reference (used in elastic simulations | not supported currently!)
"""
raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_)
@property
def alpha_coeff_compression(self): # pragma: no cover
"""
Compression alpha coefficient (used in elastic simulations | not supported currently!)
"""
raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_)
@property
def alpha_coeff_shear(self): # pragma: no cover
"""
Shear alpha coefficient (used in elastic simulations | not supported currently!)
"""
raise NotImplementedError(self._ELASTIC_CODE_ACCESS_ERROR_TEXT_)