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