Skip to content

Commit 966647a

Browse files
waltsimsclaude
andcommitted
Fix NaN when alpha_power is near 1.0, respect alpha_mode in all paths
Three issues caused NaN output with attenuation exponents near 1.0: 1. The Python solver ignored medium.alpha_mode entirely, always computing the dispersion term containing tan(pi*alpha_power/2) which diverges at alpha_power=1. Now respects 'no_dispersion' and 'no_absorption'. 2. No validation caught alpha_power values *near* 1.0 (only == 1 exactly). Now raises ValueError when |alpha_power - 1| < 0.05 unless alpha_mode='no_dispersion'. 3. The C++ backend silently ignored alpha_mode (never written to HDF5). Now warns that alpha_mode is unsupported by the C++ binary, directing users to backend='python'. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ba1adea commit 966647a

4 files changed

Lines changed: 42 additions & 6 deletions

File tree

kwave/kWaveSimulation_helper/save_to_disk_func.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
import os
3+
import warnings
34

45
import numpy as np
56
from scipy.io import savemat
@@ -229,6 +230,15 @@ def grab_medium_props(integer_variables, float_variables, medium, is_elastic_cod
229230
integer_variables.absorbing_flag = 0
230231

231232
if medium.absorbing:
233+
alpha_mode = getattr(medium, "alpha_mode", None)
234+
if alpha_mode in ("no_absorption", "no_dispersion"):
235+
warnings.warn(
236+
f"medium.alpha_mode='{alpha_mode}' is not supported by the C++ backend "
237+
f"and will be silently ignored. The C++ binary always computes both "
238+
f"absorption and dispersion when absorbing_flag > 0. Use backend='python' "
239+
f"to honor alpha_mode.",
240+
stacklevel=4,
241+
)
232242
if is_elastic_code: # pragma: no cover
233243
# add to the variable list
234244
float_variables["chi"] = None

kwave/solvers/cpp_simulation.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import stat
1010
import subprocess
1111
import tempfile
12+
import warnings
1213

1314
import numpy as np
1415

@@ -36,6 +37,16 @@ def __init__(self, kgrid, medium, source, sensor, *, pml_size, pml_alpha, use_sg
3637
self.use_sg = use_sg
3738
self.ndim = kgrid.dim
3839

40+
alpha_mode = getattr(medium, "alpha_mode", None)
41+
if alpha_mode in ("no_absorption", "no_dispersion"):
42+
warnings.warn(
43+
f"medium.alpha_mode='{alpha_mode}' is not supported by the C++ backend "
44+
f"and will be silently ignored. The C++ binary always computes both "
45+
f"absorption and dispersion when absorbing_flag > 0. Use backend='python' "
46+
f"to honor alpha_mode.",
47+
stacklevel=3,
48+
)
49+
3950
def prepare(self, data_path=None):
4051
"""Write HDF5 input file. Returns (input_file, output_file)."""
4152
if data_path is None:

kwave/solvers/kspace_solver.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ def _setup_physics_operators(self):
411411

412412
# Absorption/dispersion
413413
alpha_coeff_raw = getattr(self.medium, "alpha_coeff", 0)
414+
alpha_mode = getattr(self.medium, "alpha_mode", None)
414415
if not _is_enabled(alpha_coeff_raw):
415416
self._absorption = lambda div_u: 0
416417
self._dispersion = lambda rho: 0
@@ -419,17 +420,23 @@ def _setup_physics_operators(self):
419420
alpha_power = float(xp.array(getattr(self.medium, "alpha_power", 1.5)).flatten()[0])
420421
alpha_np = 100 * alpha_coeff * (1e-6 / (2 * np.pi)) ** alpha_power / (20 * np.log10(np.e))
421422

423+
no_absorption = alpha_mode == "no_absorption"
424+
no_dispersion = alpha_mode == "no_dispersion"
425+
422426
if abs(alpha_power - 2.0) < 1e-10: # Stokes
423-
self._absorption = lambda div_u: -2 * alpha_np * self.c0 * self.rho0 * div_u
427+
self._absorption = lambda div_u: 0 if no_absorption else -2 * alpha_np * self.c0 * self.rho0 * div_u
424428
self._dispersion = lambda rho: 0
425429
else: # Power-law with fractional Laplacian
426430
tau = -2 * alpha_np * self.c0 ** (alpha_power - 1)
427-
eta = 2 * alpha_np * self.c0**alpha_power * xp.tan(np.pi * alpha_power / 2)
428431
nabla1 = self._fractional_laplacian(alpha_power - 2)
429-
nabla2 = self._fractional_laplacian(alpha_power - 1)
430-
# Fractional Laplacian already includes full k-space structure; no kappa needed
431-
self._absorption = lambda div_u: tau * self._diff(self.rho0 * div_u, nabla1)
432-
self._dispersion = lambda rho: eta * self._diff(rho, nabla2)
432+
self._absorption = (lambda div_u: 0) if no_absorption else (lambda div_u: tau * self._diff(self.rho0 * div_u, nabla1))
433+
434+
if no_dispersion:
435+
self._dispersion = lambda rho: 0
436+
else:
437+
eta = 2 * alpha_np * self.c0**alpha_power * xp.tan(np.pi * alpha_power / 2)
438+
nabla2 = self._fractional_laplacian(alpha_power - 1)
439+
self._dispersion = lambda rho: eta * self._diff(rho, nabla2)
433440

434441
# Nonlinearity
435442
BonA_raw = getattr(self.medium, "BonA", 0)

kwave/solvers/validation.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ def validate_medium(medium, kgrid):
4747
power = float(np.asarray(medium.alpha_power).flat[0])
4848
if power < 0 or power > 3:
4949
warnings.warn(f"medium.alpha_power={power} is outside typical range [0, 3].", stacklevel=3)
50+
alpha_mode = getattr(medium, "alpha_mode", None)
51+
if abs(power - 1.0) < 0.05 and alpha_mode != "no_dispersion":
52+
raise ValueError(
53+
f"medium.alpha_power={power} is too close to 1.0. The dispersion term "
54+
f"contains tan(pi*alpha_power/2), which diverges at alpha_power=1. "
55+
f"Set medium.alpha_mode='no_dispersion' to disable the dispersion term, "
56+
f"or use an alpha_power value further from 1.0."
57+
)
5058

5159

5260
def validate_pml(pml_size, kgrid):

0 commit comments

Comments
 (0)