-
Notifications
You must be signed in to change notification settings - Fork 53
Fix alpha mode near unity #704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
waltsims
wants to merge
12
commits into
master
Choose a base branch
from
fix-alpha-mode-near-unity
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
8422347
Add CLAUDE.md for Claude Code onboarding
waltsims 1dfb845
Fix CppSimulation source flags to write signal length, not boolean
waltsims c5b35b7
Add comment clarifying misleading *_source_flag HDF5 field names
waltsims ba1adea
Use .shape[-1] instead of len([0]) for source flag values
waltsims 966647a
Fix NaN when alpha_power is near 1.0, respect alpha_mode in all paths
waltsims 6e1e5ef
Add tests for alpha_power near 1.0 and alpha_mode handling
waltsims 7a4ce97
Improve near-unity validation message; skip unused nabla1 computation
waltsims 2f985fa
Revert unnecessary nabla1 conditional — not worth the complexity
waltsims bcef90d
Simplify: deduplicate warning, fix lambda pattern, use direct attr ac…
waltsims 73b5584
Revert direct attr access back to getattr (moving to separate PR)
waltsims fb0c0e3
Revert "Revert direct attr access back to getattr (moving to separate…
waltsims ccce919
Merge branch 'master' into fix-alpha-mode-near-unity
waltsims File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| """Tests for alpha_power near 1.0 (tan(pi*y/2) singularity).""" | ||
| import numpy as np | ||
| import pytest | ||
|
|
||
| from kwave.kgrid import kWaveGrid | ||
| from kwave.kmedium import kWaveMedium | ||
| from kwave.ksensor import kSensor | ||
| from kwave.ksource import kSource | ||
| from kwave.kspaceFirstOrder import kspaceFirstOrder | ||
|
|
||
|
|
||
| def _make_sim(): | ||
| """Create a minimal 2D simulation with a point source.""" | ||
| N = (64, 64) | ||
| dx = (1e-4, 1e-4) | ||
| kgrid = kWaveGrid(N, dx) | ||
| kgrid.makeTime(1500) | ||
|
|
||
| source = kSource() | ||
| source.p0 = np.zeros(N) | ||
| source.p0[32, 32] = 1.0 | ||
|
|
||
| sensor = kSensor(mask=np.ones(N)) | ||
| return kgrid, source, sensor | ||
|
|
||
|
|
||
| def test_alpha_power_near_unity_raises_without_no_dispersion(): | ||
| """alpha_power close to 1.0 must raise when dispersion is enabled.""" | ||
| kgrid, source, sensor = _make_sim() | ||
| medium = kWaveMedium(sound_speed=1500, density=1000, alpha_coeff=0.5, alpha_power=1.01) | ||
|
|
||
| with pytest.raises(ValueError, match="too close to 1.0"): | ||
| kspaceFirstOrder(kgrid, medium, source, sensor, pml_inside=True, quiet=True) | ||
|
|
||
|
|
||
| def test_alpha_power_near_unity_works_with_no_dispersion(): | ||
| """alpha_power close to 1.0 must produce valid output when dispersion is disabled.""" | ||
| kgrid, source, sensor = _make_sim() | ||
| medium = kWaveMedium( | ||
| sound_speed=1500, | ||
| density=1000, | ||
| alpha_coeff=0.5, | ||
| alpha_power=1.01, | ||
| alpha_mode="no_dispersion", | ||
| ) | ||
|
|
||
| result = kspaceFirstOrder(kgrid, medium, source, sensor, pml_inside=True, quiet=True) | ||
| p = np.asarray(result["p"]) | ||
| assert not np.any(np.isnan(p)), "Output contains NaN with alpha_power=1.01 and no_dispersion" | ||
| assert not np.all(p == 0), "Output is all zeros — absorption had no effect" | ||
|
|
||
|
|
||
| def test_alpha_mode_no_absorption(): | ||
| """alpha_mode='no_absorption' should disable absorption but keep dispersion.""" | ||
| kgrid, source, sensor = _make_sim() | ||
| medium_lossy = kWaveMedium(sound_speed=1500, density=1000, alpha_coeff=0.5, alpha_power=1.5) | ||
| medium_no_abs = kWaveMedium( | ||
| sound_speed=1500, | ||
| density=1000, | ||
| alpha_coeff=0.5, | ||
| alpha_power=1.5, | ||
| alpha_mode="no_absorption", | ||
| ) | ||
|
|
||
| result_lossy = kspaceFirstOrder(kgrid, medium_lossy, source, sensor, pml_inside=True, quiet=True) | ||
| result_no_abs = kspaceFirstOrder(kgrid, medium_no_abs, source, sensor, pml_inside=True, quiet=True) | ||
|
|
||
| p_lossy = np.asarray(result_lossy["p"]) | ||
| p_no_abs = np.asarray(result_no_abs["p"]) | ||
|
|
||
| assert not np.any(np.isnan(p_no_abs)) | ||
| # Disabling absorption should change the output | ||
| assert not np.allclose(p_lossy, p_no_abs), "Disabling absorption should change the output" | ||
|
|
||
|
|
||
| def test_alpha_mode_no_dispersion(): | ||
| """alpha_mode='no_dispersion' should disable dispersion but keep absorption.""" | ||
| kgrid, source, sensor = _make_sim() | ||
| medium_lossy = kWaveMedium(sound_speed=1500, density=1000, alpha_coeff=0.5, alpha_power=1.5) | ||
| medium_no_disp = kWaveMedium( | ||
| sound_speed=1500, | ||
| density=1000, | ||
| alpha_coeff=0.5, | ||
| alpha_power=1.5, | ||
| alpha_mode="no_dispersion", | ||
| ) | ||
|
|
||
| result_lossy = kspaceFirstOrder(kgrid, medium_lossy, source, sensor, pml_inside=True, quiet=True) | ||
| result_no_disp = kspaceFirstOrder(kgrid, medium_no_disp, source, sensor, pml_inside=True, quiet=True) | ||
|
|
||
| p_lossy = np.asarray(result_lossy["p"]) | ||
| p_no_disp = np.asarray(result_no_disp["p"]) | ||
|
|
||
| assert not np.any(np.isnan(p_no_disp)) | ||
| # Both should have absorption so similar amplitude, but waveforms differ | ||
| assert not np.allclose(p_lossy, p_no_disp), "Disabling dispersion should change the output" | ||
|
|
||
|
|
||
| def test_alpha_power_normal_range_unaffected(): | ||
| """alpha_power=1.5 (well away from singularity) should work as before.""" | ||
| kgrid, source, sensor = _make_sim() | ||
| medium = kWaveMedium(sound_speed=1500, density=1000, alpha_coeff=0.5, alpha_power=1.5) | ||
|
|
||
| result = kspaceFirstOrder(kgrid, medium, source, sensor, pml_inside=True, quiet=True) | ||
| p = np.asarray(result["p"]) | ||
| assert not np.any(np.isnan(p)) | ||
| assert p.shape[0] > 0 | ||
|
|
||
|
|
||
| def test_cpp_backend_warns_on_alpha_mode(tmp_path): | ||
| """C++ backend should warn when alpha_mode is set (it cannot honor it).""" | ||
| kgrid, source, sensor = _make_sim() | ||
| medium = kWaveMedium( | ||
| sound_speed=1500, | ||
| density=1000, | ||
| alpha_coeff=0.5, | ||
| alpha_power=1.5, | ||
| alpha_mode="no_dispersion", | ||
| ) | ||
|
|
||
| with pytest.warns(UserWarning, match="not supported by the C\\+\\+ backend"): | ||
| kspaceFirstOrder( | ||
| kgrid, | ||
| medium, | ||
| source, | ||
| sensor, | ||
| pml_inside=True, | ||
| quiet=True, | ||
| backend="cpp", | ||
| save_only=True, | ||
| data_path=str(tmp_path), | ||
| ) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The guard at line 51 raises a
ValueErrorforabs(power - 1.0) < 0.05unlessalpha_mode == 'no_dispersion'. Becausevalidate_simulationis called before backend dispatch inkspaceFirstOrder.py(line 178), this check fires for the C++ backend too.The problem is the suggested escape hatch —
alpha_mode='no_dispersion'— is explicitly unsupported by the C++ backend. Setting it only triggers a separate warning that it will be "silently ignored." So a C++ user withalpha_power=0.97must:alpha_mode='no_dispersion'to pass validation.no_dispersionis silently ignored by the C++ backend.The error message even recommends
alpha_mode='no_dispersion'as the solution, but that recommendation is only valid for the Python backend. For C++ users this creates a misleading Catch-22.Consider either (a) skipping this check when the C++ backend is active (threading the
backendparameter through tovalidate_medium), or (b) updating the error message to clarify it only applies tobackend='python'and suggestingalpha_powerfurther from 1.0 as the actionable fix for C++ users: