From 0385323c36083fddb2a782a9c3f79dd63adf4fa9 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sat, 28 Mar 2026 21:07:03 -0700 Subject: [PATCH 1/6] Restructure examples: flatten ported/, drop example_ prefix, parametrize tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move 29 examples from examples/ported/example_*.py to examples/*.py - Drop redundant example_ prefix (files are already in examples/) - Move 22 old subdirectory examples to examples/legacy/ - Delete 4 superseded demo scripts - Rewrite test_example_parity.py: 15 copy-paste classes → table-driven parametrized tests (550 → 290 lines, 35 pass / 2 skip, zero noise skips) Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/ivp_1D_simulation.py | 168 +++--- ..._3D_simulation.py => ivp_3D_simulation.py} | 0 ...nsor_mask.py => ivp_binary_sensor_mask.py} | 0 ..._medium.py => ivp_heterogeneous_medium.py} | 0 ...us_medium.py => ivp_homogeneous_medium.py} | 0 ...image.py => ivp_loading_external_image.py} | 0 ...orms.py => ivp_photoacoustic_waveforms.py} | 0 ....py => ivp_recording_particle_velocity.py} | 0 ...vie_files.py => ivp_saving_movie_files.py} | 0 .../{ => legacy}/at_array_as_sensor/README.md | 0 .../at_array_as_sensor.ipynb | 0 .../at_array_as_sensor/at_array_as_sensor.py | 0 .../{ => legacy}/at_array_as_source/README.md | 0 .../at_array_as_source.ipynb | 0 .../at_array_as_source/at_array_as_source.py | 0 .../at_circular_piston_3D/README.md | 0 .../at_circular_piston_3D.ipynb | 0 .../at_circular_piston_3D.py | 0 .../at_circular_piston_AS/README.md | 0 .../at_circular_piston_AS.ipynb | 0 .../at_circular_piston_AS.py | 0 .../at_focused_annular_array_3D/README.md | 0 .../at_focused_annular_array_3D.ipynb | 0 .../at_focused_annular_array_3D.py | 0 .../{ => legacy}/at_focused_bowl_3D/README.md | 0 .../at_focused_bowl_3D.ipynb | 0 .../at_focused_bowl_3D/at_focused_bowl_3D.py | 0 .../{ => legacy}/at_focused_bowl_AS/README.md | 0 .../at_focused_bowl_AS.ipynb | 0 .../at_focused_bowl_AS/at_focused_bowl_AS.py | 0 .../at_linear_array_transducer/README.md | 0 .../at_linear_array_transducer.ipynb | 0 .../at_linear_array_transducer.py | 0 examples/{ => legacy}/checkpointing/README.md | 0 .../{ => legacy}/checkpointing/checkpoint.py | 0 .../ivp_photoacoustic_waveforms/README.md | 0 .../ivp_photoacoustic_waveforms.ipynb | 0 .../ivp_photoacoustic_waveforms.py | 0 .../na_controlling_the_pml/README.md | 0 .../modified_matlab_example.m | 0 .../na_controlling_the_pml.ipynb | 0 .../pr_2D_FFT_line_sensor/README.md | 0 .../pr_2D_FFT_line_sensor.ipynb | 0 .../pr_2D_FFT_line_sensor.py | 0 .../pr_2D_TR_line_sensor/README.md | 0 .../pr_2D_TR_line_sensor.ipynb | 0 .../pr_2D_TR_line_sensor.py | 0 .../pr_3D_FFT_planar_sensor/README.md | 0 .../pr_3D_FFT_planar_sensor.ipynb | 0 .../pr_3D_FFT_planar_sensor.py | 0 .../pr_3D_TR_planar_sensor/README.md | 0 .../pr_3D_TR_planar_sensor.ipynb | 0 .../pr_3D_TR_planar_sensor.py | 0 .../sd_directivity_modelling_2D/README.md | 0 .../sd_directivity_modelling_2D.ipynb | 0 .../sd_directivity_modelling_2D.py | 0 .../sd_focussed_detector_2D/README.md | 0 .../sd_focussed_detector_2D.ipynb | 0 .../sd_focussed_detector_2D.py | 0 .../sd_focussed_detector_3D/README.md | 0 .../sd_focussed_detector_3D.ipynb | 0 .../sd_focussed_detector_3D.py | 0 .../{ => legacy}/us_beam_patterns/README.md | 0 .../us_beam_patterns/us_beam_patterns.ipynb | 0 .../us_beam_patterns/us_beam_patterns.py | 0 .../us_bmode_linear_transducer/README.md | 0 .../example_utils.py | 0 .../us_bmode_linear_transducer.ipynb | 0 .../us_bmode_linear_transducer.py | 0 .../us_bmode_phased_array/README.md | 0 .../us_bmode_phased_array.ipynb | 0 .../us_bmode_phased_array.py | 0 .../us_defining_transducer/README.md | 0 .../us_defining_transducer.ipynb | 0 .../us_defining_transducer.py | 0 ...g_the_PML.py => na_controlling_the_PML.py} | 0 ...ering_part_1.py => na_filtering_part_1.py} | 0 ...ering_part_2.py => na_filtering_part_2.py} | 0 ...ering_part_3.py => na_filtering_part_3.py} | 0 ...earity.py => na_modelling_nonlinearity.py} | 0 ...rmance.py => na_optimising_performance.py} | 0 ...ce_smoothing.py => na_source_smoothing.py} | 0 examples/new_api_ivp_2D.py | 39 -- examples/new_api_transducer_3D.py | 48 -- examples/ported/example_ivp_1D_simulation.py | 111 ---- ...ine_sensor.py => pr_2D_FFT_line_sensor.py} | 0 ...r_sensor.py => pr_3D_FFT_planar_sensor.py} | 0 ...ts.py => sd_directional_array_elements.py} | 0 ...g_2D.py => sd_directivity_modelling_2D.py} | 0 ...g_3D.py => sd_directivity_modelling_3D.py} | 0 ...ector_2D.py => sd_focussed_detector_2D.py} | 0 ...ector_3D.py => sd_focussed_detector_3D.py} | 0 ...3D_simulation.py => tvsp_3D_simulation.py} | 0 ...ppler_effect.py => tvsp_doppler_effect.py} | 0 ...e.py => tvsp_homogeneous_medium_dipole.py} | 0 ...py => tvsp_homogeneous_medium_monopole.py} | 0 ..._tvsp_snells_law.py => tvsp_snells_law.py} | 0 ...array.py => tvsp_steering_linear_array.py} | 0 examples/unified_entry_point_demo.py | 85 --- tests/test_example_parity.py | 533 +++++------------- 100 files changed, 236 insertions(+), 748 deletions(-) rename examples/{ported/example_ivp_3D_simulation.py => ivp_3D_simulation.py} (100%) rename examples/{ported/example_ivp_binary_sensor_mask.py => ivp_binary_sensor_mask.py} (100%) rename examples/{ported/example_ivp_heterogeneous_medium.py => ivp_heterogeneous_medium.py} (100%) rename examples/{ported/example_ivp_homogeneous_medium.py => ivp_homogeneous_medium.py} (100%) rename examples/{ported/example_ivp_loading_external_image.py => ivp_loading_external_image.py} (100%) rename examples/{ported/example_ivp_photoacoustic_waveforms.py => ivp_photoacoustic_waveforms.py} (100%) rename examples/{ported/example_ivp_recording_particle_velocity.py => ivp_recording_particle_velocity.py} (100%) rename examples/{ported/example_ivp_saving_movie_files.py => ivp_saving_movie_files.py} (100%) rename examples/{ => legacy}/at_array_as_sensor/README.md (100%) rename examples/{ => legacy}/at_array_as_sensor/at_array_as_sensor.ipynb (100%) rename examples/{ => legacy}/at_array_as_sensor/at_array_as_sensor.py (100%) rename examples/{ => legacy}/at_array_as_source/README.md (100%) rename examples/{ => legacy}/at_array_as_source/at_array_as_source.ipynb (100%) rename examples/{ => legacy}/at_array_as_source/at_array_as_source.py (100%) rename examples/{ => legacy}/at_circular_piston_3D/README.md (100%) rename examples/{ => legacy}/at_circular_piston_3D/at_circular_piston_3D.ipynb (100%) rename examples/{ => legacy}/at_circular_piston_3D/at_circular_piston_3D.py (100%) rename examples/{ => legacy}/at_circular_piston_AS/README.md (100%) rename examples/{ => legacy}/at_circular_piston_AS/at_circular_piston_AS.ipynb (100%) rename examples/{ => legacy}/at_circular_piston_AS/at_circular_piston_AS.py (100%) rename examples/{ => legacy}/at_focused_annular_array_3D/README.md (100%) rename examples/{ => legacy}/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb (100%) rename examples/{ => legacy}/at_focused_annular_array_3D/at_focused_annular_array_3D.py (100%) rename examples/{ => legacy}/at_focused_bowl_3D/README.md (100%) rename examples/{ => legacy}/at_focused_bowl_3D/at_focused_bowl_3D.ipynb (100%) rename examples/{ => legacy}/at_focused_bowl_3D/at_focused_bowl_3D.py (100%) rename examples/{ => legacy}/at_focused_bowl_AS/README.md (100%) rename examples/{ => legacy}/at_focused_bowl_AS/at_focused_bowl_AS.ipynb (100%) rename examples/{ => legacy}/at_focused_bowl_AS/at_focused_bowl_AS.py (100%) rename examples/{ => legacy}/at_linear_array_transducer/README.md (100%) rename examples/{ => legacy}/at_linear_array_transducer/at_linear_array_transducer.ipynb (100%) rename examples/{ => legacy}/at_linear_array_transducer/at_linear_array_transducer.py (100%) rename examples/{ => legacy}/checkpointing/README.md (100%) rename examples/{ => legacy}/checkpointing/checkpoint.py (100%) rename examples/{ => legacy}/ivp_photoacoustic_waveforms/README.md (100%) rename examples/{ => legacy}/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb (100%) rename examples/{ => legacy}/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py (100%) rename examples/{ => legacy}/na_controlling_the_pml/README.md (100%) rename examples/{ => legacy}/na_controlling_the_pml/modified_matlab_example.m (100%) rename examples/{ => legacy}/na_controlling_the_pml/na_controlling_the_pml.ipynb (100%) rename examples/{ => legacy}/pr_2D_FFT_line_sensor/README.md (100%) rename examples/{ => legacy}/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb (100%) rename examples/{ => legacy}/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py (100%) rename examples/{ => legacy}/pr_2D_TR_line_sensor/README.md (100%) rename examples/{ => legacy}/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb (100%) rename examples/{ => legacy}/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py (100%) rename examples/{ => legacy}/pr_3D_FFT_planar_sensor/README.md (100%) rename examples/{ => legacy}/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb (100%) rename examples/{ => legacy}/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py (100%) rename examples/{ => legacy}/pr_3D_TR_planar_sensor/README.md (100%) rename examples/{ => legacy}/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb (100%) rename examples/{ => legacy}/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py (100%) rename examples/{ => legacy}/sd_directivity_modelling_2D/README.md (100%) rename examples/{ => legacy}/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb (100%) rename examples/{ => legacy}/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py (100%) rename examples/{ => legacy}/sd_focussed_detector_2D/README.md (100%) rename examples/{ => legacy}/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb (100%) rename examples/{ => legacy}/sd_focussed_detector_2D/sd_focussed_detector_2D.py (100%) rename examples/{ => legacy}/sd_focussed_detector_3D/README.md (100%) rename examples/{ => legacy}/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb (100%) rename examples/{ => legacy}/sd_focussed_detector_3D/sd_focussed_detector_3D.py (100%) rename examples/{ => legacy}/us_beam_patterns/README.md (100%) rename examples/{ => legacy}/us_beam_patterns/us_beam_patterns.ipynb (100%) rename examples/{ => legacy}/us_beam_patterns/us_beam_patterns.py (100%) rename examples/{ => legacy}/us_bmode_linear_transducer/README.md (100%) rename examples/{ => legacy}/us_bmode_linear_transducer/example_utils.py (100%) rename examples/{ => legacy}/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb (100%) rename examples/{ => legacy}/us_bmode_linear_transducer/us_bmode_linear_transducer.py (100%) rename examples/{ => legacy}/us_bmode_phased_array/README.md (100%) rename examples/{ => legacy}/us_bmode_phased_array/us_bmode_phased_array.ipynb (100%) rename examples/{ => legacy}/us_bmode_phased_array/us_bmode_phased_array.py (100%) rename examples/{ => legacy}/us_defining_transducer/README.md (100%) rename examples/{ => legacy}/us_defining_transducer/us_defining_transducer.ipynb (100%) rename examples/{ => legacy}/us_defining_transducer/us_defining_transducer.py (100%) rename examples/{ported/example_na_controlling_the_PML.py => na_controlling_the_PML.py} (100%) rename examples/{ported/example_na_filtering_part_1.py => na_filtering_part_1.py} (100%) rename examples/{ported/example_na_filtering_part_2.py => na_filtering_part_2.py} (100%) rename examples/{ported/example_na_filtering_part_3.py => na_filtering_part_3.py} (100%) rename examples/{ported/example_na_modelling_nonlinearity.py => na_modelling_nonlinearity.py} (100%) rename examples/{ported/example_na_optimising_performance.py => na_optimising_performance.py} (100%) rename examples/{ported/example_na_source_smoothing.py => na_source_smoothing.py} (100%) delete mode 100644 examples/new_api_ivp_2D.py delete mode 100644 examples/new_api_transducer_3D.py delete mode 100644 examples/ported/example_ivp_1D_simulation.py rename examples/{ported/example_pr_2D_FFT_line_sensor.py => pr_2D_FFT_line_sensor.py} (100%) rename examples/{ported/example_pr_3D_FFT_planar_sensor.py => pr_3D_FFT_planar_sensor.py} (100%) rename examples/{ported/example_sd_directional_array_elements.py => sd_directional_array_elements.py} (100%) rename examples/{ported/example_sd_directivity_modelling_2D.py => sd_directivity_modelling_2D.py} (100%) rename examples/{ported/example_sd_directivity_modelling_3D.py => sd_directivity_modelling_3D.py} (100%) rename examples/{ported/example_sd_focussed_detector_2D.py => sd_focussed_detector_2D.py} (100%) rename examples/{ported/example_sd_focussed_detector_3D.py => sd_focussed_detector_3D.py} (100%) rename examples/{ported/example_tvsp_3D_simulation.py => tvsp_3D_simulation.py} (100%) rename examples/{ported/example_tvsp_doppler_effect.py => tvsp_doppler_effect.py} (100%) rename examples/{ported/example_tvsp_homogeneous_medium_dipole.py => tvsp_homogeneous_medium_dipole.py} (100%) rename examples/{ported/example_tvsp_homogeneous_medium_monopole.py => tvsp_homogeneous_medium_monopole.py} (100%) rename examples/{ported/example_tvsp_snells_law.py => tvsp_snells_law.py} (100%) rename examples/{ported/example_tvsp_steering_linear_array.py => tvsp_steering_linear_array.py} (100%) delete mode 100644 examples/unified_entry_point_demo.py diff --git a/examples/ivp_1D_simulation.py b/examples/ivp_1D_simulation.py index 41e83d26..205837db 100644 --- a/examples/ivp_1D_simulation.py +++ b/examples/ivp_1D_simulation.py @@ -1,11 +1,19 @@ """ -1D Initial Value Problem — heterogeneous medium with reflections. +Simulations In One Dimension Example -A smooth pressure pulse propagates through a medium with varying -sound speed and density. Reflections at impedance boundaries and -sensor time-series are recorded and plotted. +Ported from: k-Wave/examples/example_ivp_1D_simulation.m + +Simulates the pressure field generated by an initial pressure distribution +(a smoothly shaped sinusoidal pulse) within a one-dimensional heterogeneous +propagation medium. It builds on the Homogeneous and Heterogeneous +Propagation Medium examples. + +The domain has a region of higher sound speed on the left (2000 m/s vs +1500 m/s) and higher density on the right (1500 kg/m^3 vs 1000 kg/m^3). +You should observe the initial pulse splitting into left- and right- +travelling waves, with partial reflections and transmission at the +impedance boundaries. """ -import matplotlib.pyplot as plt import numpy as np from kwave.data import Vector @@ -15,67 +23,89 @@ from kwave.ksource import kSource from kwave.kspaceFirstOrder import kspaceFirstOrder -# -- Grid -- -Nx = 512 -dx = 0.05e-3 # [m] -kgrid = kWaveGrid(Vector([Nx]), Vector([dx])) - -# -- Heterogeneous medium -- -sound_speed = 1500 * np.ones(Nx) -sound_speed[: Nx // 3] = 2000 # faster region (bone-like) - -density = 1000 * np.ones(Nx) -density[4 * Nx // 5 :] = 1500 # denser region - -medium = kWaveMedium(sound_speed=sound_speed, density=density) - -# -- Time stepping -- -kgrid.makeTime(sound_speed, cfl=0.3) - -# -- Source: smooth sinusoidal pulse -- -source = kSource() -source.p0 = np.zeros(Nx) -x0, width = 280, 100 -pulse = 0.5 * (np.sin(np.arange(width + 1) * np.pi / width - np.pi / 2) + 1) -source.p0[x0 : x0 + width + 1] = pulse - -# -- Sensor: two points -- -sensor_mask = np.zeros(Nx) -sensor_mask[Nx // 4] = 1 # left sensor -sensor_mask[3 * Nx // 4] = 1 # right sensor -sensor = kSensor(mask=sensor_mask) - -# -- Run -- -result = kspaceFirstOrder(kgrid, medium, source, sensor, backend="python") - -print(f"Sensor data shape: {result['p'].shape}") # (2, Nt) - -# -- Plot -- -t_us = np.arange(kgrid.Nt) * float(kgrid.dt) * 1e6 -x_mm = np.arange(Nx) * dx * 1e3 - -fig, axes = plt.subplots(1, 3, figsize=(14, 4)) - -axes[0].plot(x_mm, source.p0) -axes[0].set_xlabel("x (mm)") -axes[0].set_ylabel("Pressure (Pa)") -axes[0].set_title("Initial pressure") - -axes[1].plot(x_mm, sound_speed, label="c₀") -ax2 = axes[1].twinx() -ax2.plot(x_mm, density, color="tab:red", label="ρ₀") -axes[1].set_xlabel("x (mm)") -axes[1].set_ylabel("Sound speed (m/s)") -ax2.set_ylabel("Density (kg/m³)") -axes[1].set_title("Medium properties") - -axes[2].plot(t_us, result["p"][0], label=f"x = {Nx // 4 * dx * 1e3:.1f} mm") -axes[2].plot(t_us, result["p"][1], label=f"x = {3 * Nx // 4 * dx * 1e3:.1f} mm") -axes[2].set_xlabel("Time (μs)") -axes[2].set_ylabel("Pressure (Pa)") -axes[2].set_title("Sensor signals") -axes[2].legend() - -plt.tight_layout() -plt.savefig("example_1d_ivp.png", dpi=150) -plt.show() + +def setup(): + """Set up the simulation physics (grid, medium, source). + + Returns: + tuple: (kgrid, medium, source) + """ + + # create the computational grid + Nx = 512 # number of grid points in the x direction + dx = 0.05e-3 # grid point spacing in the x direction [m] + kgrid = kWaveGrid(Vector([Nx]), Vector([dx])) + + # define the properties of the propagation medium + c = 1500 * np.ones(Nx) # [m/s] + c[: round(Nx / 3)] = 2000 # MATLAB: 1:round(Nx/3) + rho = 1000 * np.ones(Nx) # [kg/m^3] + rho[round(4 * Nx / 5) - 1 :] = 1500 # MATLAB: round(4*Nx/5):end (1-based -> 0-based) + medium = kWaveMedium(sound_speed=c, density=rho) + + # create initial pressure distribution using a smoothly shaped sinusoid + x_pos = 280 # starting grid point for the pulse [grid points] + width = 100 # pulse width [grid points] + height = 1.0 # pulse amplitude [au] + in_arr = np.linspace(0, 2 * np.pi, width + 1) # exactly 101 points + p0 = np.concatenate( + [ + np.zeros(x_pos), + (height / 2) * np.sin(in_arr - np.pi / 2) + (height / 2), + np.zeros(Nx - x_pos - (width + 1)), + ] + ) + source = kSource() + source.p0 = p0 + + # set the simulation time to capture reflections + t_end = 2.5 * (Nx * dx) / np.max(c) # [s] + kgrid.makeTime(c, t_end=t_end) + + return kgrid, medium, source + + +def run(backend="python", device="cpu", quiet=True): + """Run with the original Cartesian two-point sensor. + + The MATLAB original uses sensor.mask = [-10e-3, 10e-3], which places + two Cartesian sensor points at x = -10 mm and x = +10 mm. + + Returns: + dict: Simulation results with key 'p' (2 x time_steps). + """ + kgrid, medium, source = setup() + + # create a Cartesian sensor mask (two points along x-axis) + sensor = kSensor(mask=np.array([[-10e-3, 10e-3]])) # [m] + + return kspaceFirstOrder( + kgrid, + medium, + source, + sensor, + backend=backend, + device=device, + quiet=quiet, + pml_inside=True, + ) + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + result = run(quiet=False) + p = np.asarray(result["p"]) + + fig, ax = plt.subplots(figsize=(10, 5)) + + ax.plot(p[0, :], "b-", label="Sensor Position 1 (x = -10 mm)") + ax.plot(p[1, :], "r-", label="Sensor Position 2 (x = +10 mm)") + ax.set_ylim(-0.1, 0.7) + ax.set_xlabel("Time Step") + ax.set_ylabel("Pressure") + ax.legend() + ax.set_title("Simulations In One Dimension") + + fig.tight_layout() + plt.show() diff --git a/examples/ported/example_ivp_3D_simulation.py b/examples/ivp_3D_simulation.py similarity index 100% rename from examples/ported/example_ivp_3D_simulation.py rename to examples/ivp_3D_simulation.py diff --git a/examples/ported/example_ivp_binary_sensor_mask.py b/examples/ivp_binary_sensor_mask.py similarity index 100% rename from examples/ported/example_ivp_binary_sensor_mask.py rename to examples/ivp_binary_sensor_mask.py diff --git a/examples/ported/example_ivp_heterogeneous_medium.py b/examples/ivp_heterogeneous_medium.py similarity index 100% rename from examples/ported/example_ivp_heterogeneous_medium.py rename to examples/ivp_heterogeneous_medium.py diff --git a/examples/ported/example_ivp_homogeneous_medium.py b/examples/ivp_homogeneous_medium.py similarity index 100% rename from examples/ported/example_ivp_homogeneous_medium.py rename to examples/ivp_homogeneous_medium.py diff --git a/examples/ported/example_ivp_loading_external_image.py b/examples/ivp_loading_external_image.py similarity index 100% rename from examples/ported/example_ivp_loading_external_image.py rename to examples/ivp_loading_external_image.py diff --git a/examples/ported/example_ivp_photoacoustic_waveforms.py b/examples/ivp_photoacoustic_waveforms.py similarity index 100% rename from examples/ported/example_ivp_photoacoustic_waveforms.py rename to examples/ivp_photoacoustic_waveforms.py diff --git a/examples/ported/example_ivp_recording_particle_velocity.py b/examples/ivp_recording_particle_velocity.py similarity index 100% rename from examples/ported/example_ivp_recording_particle_velocity.py rename to examples/ivp_recording_particle_velocity.py diff --git a/examples/ported/example_ivp_saving_movie_files.py b/examples/ivp_saving_movie_files.py similarity index 100% rename from examples/ported/example_ivp_saving_movie_files.py rename to examples/ivp_saving_movie_files.py diff --git a/examples/at_array_as_sensor/README.md b/examples/legacy/at_array_as_sensor/README.md similarity index 100% rename from examples/at_array_as_sensor/README.md rename to examples/legacy/at_array_as_sensor/README.md diff --git a/examples/at_array_as_sensor/at_array_as_sensor.ipynb b/examples/legacy/at_array_as_sensor/at_array_as_sensor.ipynb similarity index 100% rename from examples/at_array_as_sensor/at_array_as_sensor.ipynb rename to examples/legacy/at_array_as_sensor/at_array_as_sensor.ipynb diff --git a/examples/at_array_as_sensor/at_array_as_sensor.py b/examples/legacy/at_array_as_sensor/at_array_as_sensor.py similarity index 100% rename from examples/at_array_as_sensor/at_array_as_sensor.py rename to examples/legacy/at_array_as_sensor/at_array_as_sensor.py diff --git a/examples/at_array_as_source/README.md b/examples/legacy/at_array_as_source/README.md similarity index 100% rename from examples/at_array_as_source/README.md rename to examples/legacy/at_array_as_source/README.md diff --git a/examples/at_array_as_source/at_array_as_source.ipynb b/examples/legacy/at_array_as_source/at_array_as_source.ipynb similarity index 100% rename from examples/at_array_as_source/at_array_as_source.ipynb rename to examples/legacy/at_array_as_source/at_array_as_source.ipynb diff --git a/examples/at_array_as_source/at_array_as_source.py b/examples/legacy/at_array_as_source/at_array_as_source.py similarity index 100% rename from examples/at_array_as_source/at_array_as_source.py rename to examples/legacy/at_array_as_source/at_array_as_source.py diff --git a/examples/at_circular_piston_3D/README.md b/examples/legacy/at_circular_piston_3D/README.md similarity index 100% rename from examples/at_circular_piston_3D/README.md rename to examples/legacy/at_circular_piston_3D/README.md diff --git a/examples/at_circular_piston_3D/at_circular_piston_3D.ipynb b/examples/legacy/at_circular_piston_3D/at_circular_piston_3D.ipynb similarity index 100% rename from examples/at_circular_piston_3D/at_circular_piston_3D.ipynb rename to examples/legacy/at_circular_piston_3D/at_circular_piston_3D.ipynb diff --git a/examples/at_circular_piston_3D/at_circular_piston_3D.py b/examples/legacy/at_circular_piston_3D/at_circular_piston_3D.py similarity index 100% rename from examples/at_circular_piston_3D/at_circular_piston_3D.py rename to examples/legacy/at_circular_piston_3D/at_circular_piston_3D.py diff --git a/examples/at_circular_piston_AS/README.md b/examples/legacy/at_circular_piston_AS/README.md similarity index 100% rename from examples/at_circular_piston_AS/README.md rename to examples/legacy/at_circular_piston_AS/README.md diff --git a/examples/at_circular_piston_AS/at_circular_piston_AS.ipynb b/examples/legacy/at_circular_piston_AS/at_circular_piston_AS.ipynb similarity index 100% rename from examples/at_circular_piston_AS/at_circular_piston_AS.ipynb rename to examples/legacy/at_circular_piston_AS/at_circular_piston_AS.ipynb diff --git a/examples/at_circular_piston_AS/at_circular_piston_AS.py b/examples/legacy/at_circular_piston_AS/at_circular_piston_AS.py similarity index 100% rename from examples/at_circular_piston_AS/at_circular_piston_AS.py rename to examples/legacy/at_circular_piston_AS/at_circular_piston_AS.py diff --git a/examples/at_focused_annular_array_3D/README.md b/examples/legacy/at_focused_annular_array_3D/README.md similarity index 100% rename from examples/at_focused_annular_array_3D/README.md rename to examples/legacy/at_focused_annular_array_3D/README.md diff --git a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb b/examples/legacy/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb similarity index 100% rename from examples/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb rename to examples/legacy/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb diff --git a/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py b/examples/legacy/at_focused_annular_array_3D/at_focused_annular_array_3D.py similarity index 100% rename from examples/at_focused_annular_array_3D/at_focused_annular_array_3D.py rename to examples/legacy/at_focused_annular_array_3D/at_focused_annular_array_3D.py diff --git a/examples/at_focused_bowl_3D/README.md b/examples/legacy/at_focused_bowl_3D/README.md similarity index 100% rename from examples/at_focused_bowl_3D/README.md rename to examples/legacy/at_focused_bowl_3D/README.md diff --git a/examples/at_focused_bowl_3D/at_focused_bowl_3D.ipynb b/examples/legacy/at_focused_bowl_3D/at_focused_bowl_3D.ipynb similarity index 100% rename from examples/at_focused_bowl_3D/at_focused_bowl_3D.ipynb rename to examples/legacy/at_focused_bowl_3D/at_focused_bowl_3D.ipynb diff --git a/examples/at_focused_bowl_3D/at_focused_bowl_3D.py b/examples/legacy/at_focused_bowl_3D/at_focused_bowl_3D.py similarity index 100% rename from examples/at_focused_bowl_3D/at_focused_bowl_3D.py rename to examples/legacy/at_focused_bowl_3D/at_focused_bowl_3D.py diff --git a/examples/at_focused_bowl_AS/README.md b/examples/legacy/at_focused_bowl_AS/README.md similarity index 100% rename from examples/at_focused_bowl_AS/README.md rename to examples/legacy/at_focused_bowl_AS/README.md diff --git a/examples/at_focused_bowl_AS/at_focused_bowl_AS.ipynb b/examples/legacy/at_focused_bowl_AS/at_focused_bowl_AS.ipynb similarity index 100% rename from examples/at_focused_bowl_AS/at_focused_bowl_AS.ipynb rename to examples/legacy/at_focused_bowl_AS/at_focused_bowl_AS.ipynb diff --git a/examples/at_focused_bowl_AS/at_focused_bowl_AS.py b/examples/legacy/at_focused_bowl_AS/at_focused_bowl_AS.py similarity index 100% rename from examples/at_focused_bowl_AS/at_focused_bowl_AS.py rename to examples/legacy/at_focused_bowl_AS/at_focused_bowl_AS.py diff --git a/examples/at_linear_array_transducer/README.md b/examples/legacy/at_linear_array_transducer/README.md similarity index 100% rename from examples/at_linear_array_transducer/README.md rename to examples/legacy/at_linear_array_transducer/README.md diff --git a/examples/at_linear_array_transducer/at_linear_array_transducer.ipynb b/examples/legacy/at_linear_array_transducer/at_linear_array_transducer.ipynb similarity index 100% rename from examples/at_linear_array_transducer/at_linear_array_transducer.ipynb rename to examples/legacy/at_linear_array_transducer/at_linear_array_transducer.ipynb diff --git a/examples/at_linear_array_transducer/at_linear_array_transducer.py b/examples/legacy/at_linear_array_transducer/at_linear_array_transducer.py similarity index 100% rename from examples/at_linear_array_transducer/at_linear_array_transducer.py rename to examples/legacy/at_linear_array_transducer/at_linear_array_transducer.py diff --git a/examples/checkpointing/README.md b/examples/legacy/checkpointing/README.md similarity index 100% rename from examples/checkpointing/README.md rename to examples/legacy/checkpointing/README.md diff --git a/examples/checkpointing/checkpoint.py b/examples/legacy/checkpointing/checkpoint.py similarity index 100% rename from examples/checkpointing/checkpoint.py rename to examples/legacy/checkpointing/checkpoint.py diff --git a/examples/ivp_photoacoustic_waveforms/README.md b/examples/legacy/ivp_photoacoustic_waveforms/README.md similarity index 100% rename from examples/ivp_photoacoustic_waveforms/README.md rename to examples/legacy/ivp_photoacoustic_waveforms/README.md diff --git a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb b/examples/legacy/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb similarity index 100% rename from examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb rename to examples/legacy/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb diff --git a/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py b/examples/legacy/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py similarity index 100% rename from examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py rename to examples/legacy/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.py diff --git a/examples/na_controlling_the_pml/README.md b/examples/legacy/na_controlling_the_pml/README.md similarity index 100% rename from examples/na_controlling_the_pml/README.md rename to examples/legacy/na_controlling_the_pml/README.md diff --git a/examples/na_controlling_the_pml/modified_matlab_example.m b/examples/legacy/na_controlling_the_pml/modified_matlab_example.m similarity index 100% rename from examples/na_controlling_the_pml/modified_matlab_example.m rename to examples/legacy/na_controlling_the_pml/modified_matlab_example.m diff --git a/examples/na_controlling_the_pml/na_controlling_the_pml.ipynb b/examples/legacy/na_controlling_the_pml/na_controlling_the_pml.ipynb similarity index 100% rename from examples/na_controlling_the_pml/na_controlling_the_pml.ipynb rename to examples/legacy/na_controlling_the_pml/na_controlling_the_pml.ipynb diff --git a/examples/pr_2D_FFT_line_sensor/README.md b/examples/legacy/pr_2D_FFT_line_sensor/README.md similarity index 100% rename from examples/pr_2D_FFT_line_sensor/README.md rename to examples/legacy/pr_2D_FFT_line_sensor/README.md diff --git a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb b/examples/legacy/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb similarity index 100% rename from examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb rename to examples/legacy/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb diff --git a/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py b/examples/legacy/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py similarity index 100% rename from examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py rename to examples/legacy/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.py diff --git a/examples/pr_2D_TR_line_sensor/README.md b/examples/legacy/pr_2D_TR_line_sensor/README.md similarity index 100% rename from examples/pr_2D_TR_line_sensor/README.md rename to examples/legacy/pr_2D_TR_line_sensor/README.md diff --git a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb b/examples/legacy/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb similarity index 100% rename from examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb rename to examples/legacy/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb diff --git a/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py b/examples/legacy/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py similarity index 100% rename from examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py rename to examples/legacy/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.py diff --git a/examples/pr_3D_FFT_planar_sensor/README.md b/examples/legacy/pr_3D_FFT_planar_sensor/README.md similarity index 100% rename from examples/pr_3D_FFT_planar_sensor/README.md rename to examples/legacy/pr_3D_FFT_planar_sensor/README.md diff --git a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb b/examples/legacy/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb similarity index 100% rename from examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb rename to examples/legacy/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb diff --git a/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py b/examples/legacy/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py similarity index 100% rename from examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py rename to examples/legacy/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.py diff --git a/examples/pr_3D_TR_planar_sensor/README.md b/examples/legacy/pr_3D_TR_planar_sensor/README.md similarity index 100% rename from examples/pr_3D_TR_planar_sensor/README.md rename to examples/legacy/pr_3D_TR_planar_sensor/README.md diff --git a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb b/examples/legacy/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb similarity index 100% rename from examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb rename to examples/legacy/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb diff --git a/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py b/examples/legacy/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py similarity index 100% rename from examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py rename to examples/legacy/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.py diff --git a/examples/sd_directivity_modelling_2D/README.md b/examples/legacy/sd_directivity_modelling_2D/README.md similarity index 100% rename from examples/sd_directivity_modelling_2D/README.md rename to examples/legacy/sd_directivity_modelling_2D/README.md diff --git a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb b/examples/legacy/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb similarity index 100% rename from examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb rename to examples/legacy/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb diff --git a/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py b/examples/legacy/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py similarity index 100% rename from examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py rename to examples/legacy/sd_directivity_modelling_2D/sd_directivity_modelling_2D.py diff --git a/examples/sd_focussed_detector_2D/README.md b/examples/legacy/sd_focussed_detector_2D/README.md similarity index 100% rename from examples/sd_focussed_detector_2D/README.md rename to examples/legacy/sd_focussed_detector_2D/README.md diff --git a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb b/examples/legacy/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb similarity index 100% rename from examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb rename to examples/legacy/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb diff --git a/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py b/examples/legacy/sd_focussed_detector_2D/sd_focussed_detector_2D.py similarity index 100% rename from examples/sd_focussed_detector_2D/sd_focussed_detector_2D.py rename to examples/legacy/sd_focussed_detector_2D/sd_focussed_detector_2D.py diff --git a/examples/sd_focussed_detector_3D/README.md b/examples/legacy/sd_focussed_detector_3D/README.md similarity index 100% rename from examples/sd_focussed_detector_3D/README.md rename to examples/legacy/sd_focussed_detector_3D/README.md diff --git a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb b/examples/legacy/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb similarity index 100% rename from examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb rename to examples/legacy/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb diff --git a/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py b/examples/legacy/sd_focussed_detector_3D/sd_focussed_detector_3D.py similarity index 100% rename from examples/sd_focussed_detector_3D/sd_focussed_detector_3D.py rename to examples/legacy/sd_focussed_detector_3D/sd_focussed_detector_3D.py diff --git a/examples/us_beam_patterns/README.md b/examples/legacy/us_beam_patterns/README.md similarity index 100% rename from examples/us_beam_patterns/README.md rename to examples/legacy/us_beam_patterns/README.md diff --git a/examples/us_beam_patterns/us_beam_patterns.ipynb b/examples/legacy/us_beam_patterns/us_beam_patterns.ipynb similarity index 100% rename from examples/us_beam_patterns/us_beam_patterns.ipynb rename to examples/legacy/us_beam_patterns/us_beam_patterns.ipynb diff --git a/examples/us_beam_patterns/us_beam_patterns.py b/examples/legacy/us_beam_patterns/us_beam_patterns.py similarity index 100% rename from examples/us_beam_patterns/us_beam_patterns.py rename to examples/legacy/us_beam_patterns/us_beam_patterns.py diff --git a/examples/us_bmode_linear_transducer/README.md b/examples/legacy/us_bmode_linear_transducer/README.md similarity index 100% rename from examples/us_bmode_linear_transducer/README.md rename to examples/legacy/us_bmode_linear_transducer/README.md diff --git a/examples/us_bmode_linear_transducer/example_utils.py b/examples/legacy/us_bmode_linear_transducer/example_utils.py similarity index 100% rename from examples/us_bmode_linear_transducer/example_utils.py rename to examples/legacy/us_bmode_linear_transducer/example_utils.py diff --git a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb b/examples/legacy/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb similarity index 100% rename from examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb rename to examples/legacy/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb diff --git a/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py b/examples/legacy/us_bmode_linear_transducer/us_bmode_linear_transducer.py similarity index 100% rename from examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py rename to examples/legacy/us_bmode_linear_transducer/us_bmode_linear_transducer.py diff --git a/examples/us_bmode_phased_array/README.md b/examples/legacy/us_bmode_phased_array/README.md similarity index 100% rename from examples/us_bmode_phased_array/README.md rename to examples/legacy/us_bmode_phased_array/README.md diff --git a/examples/us_bmode_phased_array/us_bmode_phased_array.ipynb b/examples/legacy/us_bmode_phased_array/us_bmode_phased_array.ipynb similarity index 100% rename from examples/us_bmode_phased_array/us_bmode_phased_array.ipynb rename to examples/legacy/us_bmode_phased_array/us_bmode_phased_array.ipynb diff --git a/examples/us_bmode_phased_array/us_bmode_phased_array.py b/examples/legacy/us_bmode_phased_array/us_bmode_phased_array.py similarity index 100% rename from examples/us_bmode_phased_array/us_bmode_phased_array.py rename to examples/legacy/us_bmode_phased_array/us_bmode_phased_array.py diff --git a/examples/us_defining_transducer/README.md b/examples/legacy/us_defining_transducer/README.md similarity index 100% rename from examples/us_defining_transducer/README.md rename to examples/legacy/us_defining_transducer/README.md diff --git a/examples/us_defining_transducer/us_defining_transducer.ipynb b/examples/legacy/us_defining_transducer/us_defining_transducer.ipynb similarity index 100% rename from examples/us_defining_transducer/us_defining_transducer.ipynb rename to examples/legacy/us_defining_transducer/us_defining_transducer.ipynb diff --git a/examples/us_defining_transducer/us_defining_transducer.py b/examples/legacy/us_defining_transducer/us_defining_transducer.py similarity index 100% rename from examples/us_defining_transducer/us_defining_transducer.py rename to examples/legacy/us_defining_transducer/us_defining_transducer.py diff --git a/examples/ported/example_na_controlling_the_PML.py b/examples/na_controlling_the_PML.py similarity index 100% rename from examples/ported/example_na_controlling_the_PML.py rename to examples/na_controlling_the_PML.py diff --git a/examples/ported/example_na_filtering_part_1.py b/examples/na_filtering_part_1.py similarity index 100% rename from examples/ported/example_na_filtering_part_1.py rename to examples/na_filtering_part_1.py diff --git a/examples/ported/example_na_filtering_part_2.py b/examples/na_filtering_part_2.py similarity index 100% rename from examples/ported/example_na_filtering_part_2.py rename to examples/na_filtering_part_2.py diff --git a/examples/ported/example_na_filtering_part_3.py b/examples/na_filtering_part_3.py similarity index 100% rename from examples/ported/example_na_filtering_part_3.py rename to examples/na_filtering_part_3.py diff --git a/examples/ported/example_na_modelling_nonlinearity.py b/examples/na_modelling_nonlinearity.py similarity index 100% rename from examples/ported/example_na_modelling_nonlinearity.py rename to examples/na_modelling_nonlinearity.py diff --git a/examples/ported/example_na_optimising_performance.py b/examples/na_optimising_performance.py similarity index 100% rename from examples/ported/example_na_optimising_performance.py rename to examples/na_optimising_performance.py diff --git a/examples/ported/example_na_source_smoothing.py b/examples/na_source_smoothing.py similarity index 100% rename from examples/ported/example_na_source_smoothing.py rename to examples/na_source_smoothing.py diff --git a/examples/new_api_ivp_2D.py b/examples/new_api_ivp_2D.py deleted file mode 100644 index 46adf6e1..00000000 --- a/examples/new_api_ivp_2D.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -2D Initial Value Problem using the new unified API. - -A disc-shaped initial pressure distribution propagates outward in a -homogeneous medium. Demonstrates the simplest usage of kspaceFirstOrder(). -""" -import numpy as np - -from kwave.data import Vector -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 -from kwave.utils.mapgen import make_disc - -# Grid -grid_size = Vector([128, 128]) -grid_spacing = Vector([0.1e-3, 0.1e-3]) -kgrid = kWaveGrid(grid_size, grid_spacing) -kgrid.makeTime(1500) - -# Medium -medium = kWaveMedium(sound_speed=1500, density=1000) - -# Source: disc-shaped initial pressure -source = kSource() -source.p0 = make_disc(grid_size, Vector([64, 64]), 5).astype(float) - -# Sensor: record everywhere -sensor = kSensor(mask=np.ones((128, 128), dtype=bool)) - -# Run -result = kspaceFirstOrder(kgrid, medium, source, sensor) - -print(f"Sensor data shape: {result['p'].shape}") -print(f"Final pressure shape: {result['p_final'].shape}") -print(f"Max recorded pressure: {np.max(np.abs(result['p'])):.6f}") -print("2D IVP example completed successfully!") diff --git a/examples/new_api_transducer_3D.py b/examples/new_api_transducer_3D.py deleted file mode 100644 index 13cdcd97..00000000 --- a/examples/new_api_transducer_3D.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -3D simulation using the new unified API. - -Demonstrates 3D wave propagation with the unified kspaceFirstOrder() function. -For auto PML sizing, use larger grids (>128) where the auto-selected PML -leaves sufficient interior points. -""" -import numpy as np - -from kwave.data import Vector -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 - -# 3D grid for demonstration -grid_size = Vector([64, 64, 64]) -grid_spacing = Vector([0.1e-3, 0.1e-3, 0.1e-3]) -kgrid = kWaveGrid(grid_size, grid_spacing) -kgrid.makeTime(1500) - -# Homogeneous medium -medium = kWaveMedium(sound_speed=1500, density=1000) - -# Point source at center -source = kSource() -p0 = np.zeros((64, 64, 64)) -p0[32, 32, 32] = 1.0 -source.p0 = p0 - -# Record on a plane -sensor_mask = np.zeros((64, 64, 64), dtype=bool) -sensor_mask[:, :, 32] = True -sensor = kSensor(mask=sensor_mask) - -# Run with custom PML size -result = kspaceFirstOrder( - kgrid, - medium, - source, - sensor, - pml_size=10, -) - -print(f"Sensor data shape: {result['p'].shape}") -print(f"Final pressure shape: {result['p_final'].shape}") -print("3D transducer example with auto PML completed successfully!") diff --git a/examples/ported/example_ivp_1D_simulation.py b/examples/ported/example_ivp_1D_simulation.py deleted file mode 100644 index 205837db..00000000 --- a/examples/ported/example_ivp_1D_simulation.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Simulations In One Dimension Example - -Ported from: k-Wave/examples/example_ivp_1D_simulation.m - -Simulates the pressure field generated by an initial pressure distribution -(a smoothly shaped sinusoidal pulse) within a one-dimensional heterogeneous -propagation medium. It builds on the Homogeneous and Heterogeneous -Propagation Medium examples. - -The domain has a region of higher sound speed on the left (2000 m/s vs -1500 m/s) and higher density on the right (1500 kg/m^3 vs 1000 kg/m^3). -You should observe the initial pulse splitting into left- and right- -travelling waves, with partial reflections and transmission at the -impedance boundaries. -""" -import numpy as np - -from kwave.data import Vector -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 setup(): - """Set up the simulation physics (grid, medium, source). - - Returns: - tuple: (kgrid, medium, source) - """ - - # create the computational grid - Nx = 512 # number of grid points in the x direction - dx = 0.05e-3 # grid point spacing in the x direction [m] - kgrid = kWaveGrid(Vector([Nx]), Vector([dx])) - - # define the properties of the propagation medium - c = 1500 * np.ones(Nx) # [m/s] - c[: round(Nx / 3)] = 2000 # MATLAB: 1:round(Nx/3) - rho = 1000 * np.ones(Nx) # [kg/m^3] - rho[round(4 * Nx / 5) - 1 :] = 1500 # MATLAB: round(4*Nx/5):end (1-based -> 0-based) - medium = kWaveMedium(sound_speed=c, density=rho) - - # create initial pressure distribution using a smoothly shaped sinusoid - x_pos = 280 # starting grid point for the pulse [grid points] - width = 100 # pulse width [grid points] - height = 1.0 # pulse amplitude [au] - in_arr = np.linspace(0, 2 * np.pi, width + 1) # exactly 101 points - p0 = np.concatenate( - [ - np.zeros(x_pos), - (height / 2) * np.sin(in_arr - np.pi / 2) + (height / 2), - np.zeros(Nx - x_pos - (width + 1)), - ] - ) - source = kSource() - source.p0 = p0 - - # set the simulation time to capture reflections - t_end = 2.5 * (Nx * dx) / np.max(c) # [s] - kgrid.makeTime(c, t_end=t_end) - - return kgrid, medium, source - - -def run(backend="python", device="cpu", quiet=True): - """Run with the original Cartesian two-point sensor. - - The MATLAB original uses sensor.mask = [-10e-3, 10e-3], which places - two Cartesian sensor points at x = -10 mm and x = +10 mm. - - Returns: - dict: Simulation results with key 'p' (2 x time_steps). - """ - kgrid, medium, source = setup() - - # create a Cartesian sensor mask (two points along x-axis) - sensor = kSensor(mask=np.array([[-10e-3, 10e-3]])) # [m] - - return kspaceFirstOrder( - kgrid, - medium, - source, - sensor, - backend=backend, - device=device, - quiet=quiet, - pml_inside=True, - ) - - -if __name__ == "__main__": - import matplotlib.pyplot as plt - - result = run(quiet=False) - p = np.asarray(result["p"]) - - fig, ax = plt.subplots(figsize=(10, 5)) - - ax.plot(p[0, :], "b-", label="Sensor Position 1 (x = -10 mm)") - ax.plot(p[1, :], "r-", label="Sensor Position 2 (x = +10 mm)") - ax.set_ylim(-0.1, 0.7) - ax.set_xlabel("Time Step") - ax.set_ylabel("Pressure") - ax.legend() - ax.set_title("Simulations In One Dimension") - - fig.tight_layout() - plt.show() diff --git a/examples/ported/example_pr_2D_FFT_line_sensor.py b/examples/pr_2D_FFT_line_sensor.py similarity index 100% rename from examples/ported/example_pr_2D_FFT_line_sensor.py rename to examples/pr_2D_FFT_line_sensor.py diff --git a/examples/ported/example_pr_3D_FFT_planar_sensor.py b/examples/pr_3D_FFT_planar_sensor.py similarity index 100% rename from examples/ported/example_pr_3D_FFT_planar_sensor.py rename to examples/pr_3D_FFT_planar_sensor.py diff --git a/examples/ported/example_sd_directional_array_elements.py b/examples/sd_directional_array_elements.py similarity index 100% rename from examples/ported/example_sd_directional_array_elements.py rename to examples/sd_directional_array_elements.py diff --git a/examples/ported/example_sd_directivity_modelling_2D.py b/examples/sd_directivity_modelling_2D.py similarity index 100% rename from examples/ported/example_sd_directivity_modelling_2D.py rename to examples/sd_directivity_modelling_2D.py diff --git a/examples/ported/example_sd_directivity_modelling_3D.py b/examples/sd_directivity_modelling_3D.py similarity index 100% rename from examples/ported/example_sd_directivity_modelling_3D.py rename to examples/sd_directivity_modelling_3D.py diff --git a/examples/ported/example_sd_focussed_detector_2D.py b/examples/sd_focussed_detector_2D.py similarity index 100% rename from examples/ported/example_sd_focussed_detector_2D.py rename to examples/sd_focussed_detector_2D.py diff --git a/examples/ported/example_sd_focussed_detector_3D.py b/examples/sd_focussed_detector_3D.py similarity index 100% rename from examples/ported/example_sd_focussed_detector_3D.py rename to examples/sd_focussed_detector_3D.py diff --git a/examples/ported/example_tvsp_3D_simulation.py b/examples/tvsp_3D_simulation.py similarity index 100% rename from examples/ported/example_tvsp_3D_simulation.py rename to examples/tvsp_3D_simulation.py diff --git a/examples/ported/example_tvsp_doppler_effect.py b/examples/tvsp_doppler_effect.py similarity index 100% rename from examples/ported/example_tvsp_doppler_effect.py rename to examples/tvsp_doppler_effect.py diff --git a/examples/ported/example_tvsp_homogeneous_medium_dipole.py b/examples/tvsp_homogeneous_medium_dipole.py similarity index 100% rename from examples/ported/example_tvsp_homogeneous_medium_dipole.py rename to examples/tvsp_homogeneous_medium_dipole.py diff --git a/examples/ported/example_tvsp_homogeneous_medium_monopole.py b/examples/tvsp_homogeneous_medium_monopole.py similarity index 100% rename from examples/ported/example_tvsp_homogeneous_medium_monopole.py rename to examples/tvsp_homogeneous_medium_monopole.py diff --git a/examples/ported/example_tvsp_snells_law.py b/examples/tvsp_snells_law.py similarity index 100% rename from examples/ported/example_tvsp_snells_law.py rename to examples/tvsp_snells_law.py diff --git a/examples/ported/example_tvsp_steering_linear_array.py b/examples/tvsp_steering_linear_array.py similarity index 100% rename from examples/ported/example_tvsp_steering_linear_array.py rename to examples/tvsp_steering_linear_array.py diff --git a/examples/unified_entry_point_demo.py b/examples/unified_entry_point_demo.py deleted file mode 100644 index fbf35e6b..00000000 --- a/examples/unified_entry_point_demo.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Unified Entry Point Demo — kspaceFirstOrder() - -Demonstrates the new single-function API for k-Wave simulations. -Compare with the legacy approach that required separate options classes. -""" -import numpy as np - -from kwave.data import Vector -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 - -# ============================================================ -# Setup (same for all backends) -# ============================================================ -grid_size = Vector([128, 128]) -kgrid = kWaveGrid(grid_size, Vector([0.1e-3, 0.1e-3])) -kgrid.makeTime(1500) - -medium = kWaveMedium(sound_speed=1500, density=1000) - -source = kSource() -p0 = np.zeros((128, 128)) -p0[64, 64] = 1.0 -source.p0 = p0 - -sensor = kSensor(mask=np.ones((128, 128), dtype=bool)) - -# ============================================================ -# Example 1: Native CPU (default) -# ============================================================ -print("Running native CPU simulation...") -result = kspaceFirstOrder(kgrid, medium, source, sensor) -print(f" Sensor data: {result['p'].shape}") - -# ============================================================ -# Example 2: Custom PML -# ============================================================ -print("\nRunning with custom PML (10, 15)...") -result = kspaceFirstOrder(kgrid, medium, source, sensor, pml_size=(10, 15)) -print(f" Sensor data: {result['p'].shape}") - -# ============================================================ -# Example 3: Auto PML -# ============================================================ -print("\nRunning with auto PML...") -result = kspaceFirstOrder(kgrid, medium, source, sensor, pml_size="auto") -print(f" Sensor data: {result['p'].shape}") - -# ============================================================ -# Example 4: C++ save_only (writes HDF5 for cluster submission) -# ============================================================ -import tempfile - -print("\nSaving HDF5 for C++ binary...") -result = kspaceFirstOrder( - kgrid, - medium, - source, - sensor, - backend="cpp", - save_only=True, - data_path=tempfile.mkdtemp(), -) -print(f" Input file: {result['input_file']}") - -# ============================================================ -# Example 5: Migrating from legacy options -# ============================================================ -print("\nMigrating from legacy options...") -from kwave.compat import options_to_kwargs -from kwave.options.simulation_options import SimulationOptions - -sim_opts = SimulationOptions(smooth_p0=False) -kwargs = options_to_kwargs(simulation_options=sim_opts) -# Remove backend-specific kwargs that would require C++ binary -kwargs.pop("data_path", None) -kwargs.pop("backend", None) -result = kspaceFirstOrder(kgrid, medium, source, sensor, **kwargs) -print(f" Sensor data: {result['p'].shape}") - -print("\nAll examples completed successfully!") diff --git a/tests/test_example_parity.py b/tests/test_example_parity.py index 0b877202..c76e28bb 100644 --- a/tests/test_example_parity.py +++ b/tests/test_example_parity.py @@ -11,6 +11,7 @@ MATLAB references are per-example v7 .mat files in the sibling k-wave-cupy repo. Regenerate with: ``arch -arm64 matlab -batch "run('tests/gen_ref_batch1.m')"`` """ +import importlib import os import numpy as np @@ -43,10 +44,24 @@ def _load_ref(name): # --------------------------------------------------------------------------- -def _reorder_fullgrid_p(matlab_p, Nx, Ny): +def _reorder_fullgrid(matlab_arr, grid_shape): """Reorder MATLAB full-grid sensor data from F-order to C-order.""" - Nt = matlab_p.shape[1] - return matlab_p.reshape(Nx, Ny, Nt, order="F").reshape(Nx * Ny, Nt) + Nt = matlab_arr.shape[1] + n_pts = int(np.prod(grid_shape)) + return matlab_arr.reshape(*grid_shape, Nt, order="F").reshape(n_pts, Nt) + + +def _grid_shape_from_ref(ref, ndim): + """Extract grid shape from reference data, inferring from p_final if needed.""" + if ndim == 1: + return (int(ref["Nx"]),) if "Nx" in ref else ref["p_final"].shape[:1] + if ndim == 2: + if "Nx" in ref and "Ny" in ref: + return (int(ref["Nx"]), int(ref["Ny"])) + return ref["p_final"].shape[:2] + if "Nx" in ref and "Ny" in ref and "Nz" in ref: + return (int(ref["Nx"]), int(ref["Ny"]), int(ref["Nz"])) + return ref["p_final"].shape[:3] # --------------------------------------------------------------------------- @@ -91,458 +106,184 @@ def _run_with_binary_sensor(setup_fn, ndim, record=None): ) -# --------------------------------------------------------------------------- -# Tests -# --------------------------------------------------------------------------- - - -@pytest.mark.matlab_parity -class TestIVPHomogeneousMedium: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_ivp_homogeneous_medium import setup - - return _run_with_binary_sensor(setup, ndim=2) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("ivp_homogeneous_medium") - - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final") - +def _pml_crop(ndim): + """PML crop slices for p_final.""" + return tuple(slice(PML_SIZE, -PML_SIZE) for _ in range(ndim)) -@pytest.mark.matlab_parity -class TestIVPHeterogeneousMedium: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_ivp_heterogeneous_medium import setup - - return _run_with_binary_sensor(setup, ndim=2) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("ivp_heterogeneous_medium") - - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final") - - -@pytest.mark.matlab_parity -class TestIVPBinarySensorMask: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_ivp_binary_sensor_mask import setup - - return _run_with_binary_sensor(setup, ndim=2) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("ivp_binary_sensor_mask") - - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final") - - -@pytest.mark.matlab_parity -class TestIVPRecordingParticleVelocity: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_ivp_recording_particle_velocity import setup - - return _run_with_binary_sensor(setup, ndim=2, record=["p", "p_final", "ux", "uy"]) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("ivp_recording_particle_velocity") - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final") - - def test_ux(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_ux = _reorder_fullgrid_p(ref["ux"], Nx, Ny) - _assert_close(np.asarray(result["ux"]), matlab_ux, "ux") - - def test_uy(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_uy = _reorder_fullgrid_p(ref["uy"], Nx, Ny) - _assert_close(np.asarray(result["uy"]), matlab_uy, "uy") - - -@pytest.mark.matlab_parity -class TestIVP1DSimulation: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_ivp_1D_simulation import setup - - return _run_with_binary_sensor(setup, ndim=1) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("ivp_1D_simulation") - - def test_p(self, result, ref): - matlab_p = ref["p"] # 1D: no F-to-C reorder needed - _assert_close(np.asarray(result["p"]), matlab_p, "p", thresh=THRESH) +# --------------------------------------------------------------------------- +# Example registry +# --------------------------------------------------------------------------- - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH) +# (name, ndim, thresh) — standard examples tested for p + p_final +_EXAMPLES = [ + # IVP + ("ivp_homogeneous_medium", 2, THRESH), + ("ivp_heterogeneous_medium", 2, THRESH), + ("ivp_binary_sensor_mask", 2, THRESH), + ("ivp_1D_simulation", 1, THRESH), + ("ivp_loading_external_image", 2, THRESH), + ("ivp_photoacoustic_waveforms", 2, THRESH), + # TVSP + ("tvsp_homogeneous_medium_monopole", 2, THRESH_TVSP), + ("tvsp_homogeneous_medium_dipole", 2, THRESH_TVSP), + ("tvsp_steering_linear_array", 2, THRESH_TVSP), + ("tvsp_snells_law", 2, THRESH_TVSP), + ("tvsp_doppler_effect", 2, THRESH_TVSP), + # NA (filtering) + ("na_filtering_part_1", 1, THRESH), + ("na_filtering_part_2", 1, THRESH_TVSP), + ("na_filtering_part_3", 1, THRESH_TVSP), +] # --------------------------------------------------------------------------- -# Batch 2: TVSP examples +# Standard parity tests: p + p_final # --------------------------------------------------------------------------- @pytest.mark.matlab_parity -class TestTVSPMonopole: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_tvsp_homogeneous_medium_monopole import setup - - return _run_with_binary_sensor(setup, ndim=2) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("tvsp_homogeneous_medium_monopole") - - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH_TVSP) +class TestStandardExamples: + """Table-driven parity tests for examples using setup() + binary sensor.""" + @pytest.fixture(scope="class", params=_EXAMPLES, ids=[e[0] for e in _EXAMPLES]) + def scenario(self, request): + name, ndim, thresh = request.param + mod = importlib.import_module(f"examples.{name}") + try: + result = _run_with_binary_sensor(mod.setup, ndim) + except FileNotFoundError: + pytest.skip(f"Asset not found for {name}") + ref = _load_ref(name) + return result, ref, ndim, thresh -@pytest.mark.matlab_parity -class TestTVSPDipole: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_tvsp_homogeneous_medium_dipole import setup - - return _run_with_binary_sensor(setup, ndim=2) + def test_p(self, scenario): + result, ref, ndim, thresh = scenario + matlab_p = ref["p"] + if ndim >= 2: + matlab_p = _reorder_fullgrid(matlab_p, _grid_shape_from_ref(ref, ndim)) + _assert_close(np.asarray(result["p"]), matlab_p, "p", thresh) - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("tvsp_homogeneous_medium_dipole") + def test_p_final(self, scenario): + result, ref, ndim, thresh = scenario + matlab_pf = ref["p_final"] + if ndim == 1: + matlab_pf = matlab_pf.ravel() + matlab_pf = matlab_pf[_pml_crop(ndim)] + _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh) - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH_TVSP) +# --------------------------------------------------------------------------- +# Velocity parity test (records ux, uy in addition to p, p_final) +# --------------------------------------------------------------------------- @pytest.mark.matlab_parity -class TestTVSPSteeringLinearArray: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_tvsp_steering_linear_array import setup - - return _run_with_binary_sensor(setup, ndim=2) - +class TestIVPRecordingParticleVelocity: @pytest.fixture(scope="class") - def ref(self): - return _load_ref("tvsp_steering_linear_array") + def scenario(self): + from examples.ivp_recording_particle_velocity import setup - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") + result = _run_with_binary_sensor(setup, ndim=2, record=["p", "p_final", "ux", "uy"]) + ref = _load_ref("ivp_recording_particle_velocity") + shape = _grid_shape_from_ref(ref, 2) + return result, ref, shape - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH_TVSP) + def test_p(self, scenario): + result, ref, shape = scenario + _assert_close(np.asarray(result["p"]), _reorder_fullgrid(ref["p"], shape), "p") + def test_p_final(self, scenario): + result, ref, shape = scenario + _assert_close(np.asarray(result["p_final"]), ref["p_final"][_pml_crop(2)], "p_final") -@pytest.mark.matlab_parity -class TestTVSPSnellsLaw: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_tvsp_snells_law import setup - - return _run_with_binary_sensor(setup, ndim=2) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("tvsp_snells_law") - - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") + def test_ux(self, scenario): + result, ref, shape = scenario + _assert_close(np.asarray(result["ux"]), _reorder_fullgrid(ref["ux"], shape), "ux") - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH_TVSP) + def test_uy(self, scenario): + result, ref, shape = scenario + _assert_close(np.asarray(result["uy"]), _reorder_fullgrid(ref["uy"], shape), "uy") # --------------------------------------------------------------------------- -# Batch 3: NA examples (PML, nonlinearity, filtering) +# Custom-run examples (call run() directly, non-standard PML/record) # --------------------------------------------------------------------------- @pytest.mark.matlab_parity class TestNAControllingPML: @pytest.fixture(scope="class") - def result(self): - # This example's physics includes non-default PML settings (alpha=0), - # so we call run() directly rather than _run_with_binary_sensor. - from examples.ported.example_na_controlling_the_PML import run + def scenario(self): + from examples.na_controlling_the_PML import run - return run() + ref = _load_ref("na_controlling_the_PML") + return run(), ref, _grid_shape_from_ref(ref, 2) - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("na_controlling_the_PML") - - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") + def test_p(self, scenario): + result, ref, shape = scenario + _assert_close(np.asarray(result["p"]), _reorder_fullgrid(ref["p"], shape), "p") - def test_p_final(self, result, ref): - # pml_inside=True with pml_alpha=0: full field valid, no PML crop + def test_p_final(self, scenario): + result, ref, shape = scenario + # pml_alpha=0 means no absorption at boundary — full field valid, no PML crop _assert_close(np.asarray(result["p_final"]), ref["p_final"], "p_final") -@pytest.mark.matlab_parity -class TestNAFilteringPart1: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_na_filtering_part_1 import setup - - return _run_with_binary_sensor(setup, ndim=1) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("na_filtering_part_1") - - def test_p(self, result, ref): - matlab_p = ref["p"] - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"].ravel()[PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final") - - -@pytest.mark.matlab_parity -class TestNAFilteringPart2: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_na_filtering_part_2 import setup - - return _run_with_binary_sensor(setup, ndim=1) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("na_filtering_part_2") - - def test_p(self, result, ref): - matlab_p = ref["p"] - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"].ravel()[PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH_TVSP) - - -@pytest.mark.matlab_parity -class TestNAFilteringPart3: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_na_filtering_part_3 import setup - - return _run_with_binary_sensor(setup, ndim=1) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("na_filtering_part_3") - - def test_p(self, result, ref): - matlab_p = ref["p"] - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"].ravel()[PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH_TVSP) - - @pytest.mark.matlab_parity class TestNAModellingNonlinearity: @pytest.fixture(scope="class") - def result(self): - # Uses record_start_index and non-default PML, so call run() directly - from examples.ported.example_na_modelling_nonlinearity import run + def scenario(self): + from examples.na_modelling_nonlinearity import run - return run() + return run(), _load_ref("na_modelling_nonlinearity") - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("na_modelling_nonlinearity") - - def test_p(self, result, ref): - matlab_p = ref["p"] - _assert_close(np.asarray(result["p"]), matlab_p, "p", thresh=THRESH_TVSP) + def test_p(self, scenario): + result, ref = scenario + _assert_close(np.asarray(result["p"]), ref["p"], "p", thresh=THRESH_TVSP) # --------------------------------------------------------------------------- -# Batch 4: 3D, photoacoustic, SD examples +# 3D examples — skipped pending investigation # --------------------------------------------------------------------------- - -# TODO: investigate 3D p_final mismatch — Python max 7e-5 at (20,20,24) vs -# MATLAB max 2e-4 at (60,53,19). Not an axis permutation (all 6 tried). -# Likely a difference in p0 smoothing or heterogeneous medium setup for 3D. -# The 3D solver passes symmetry tests on homogeneous media, so the physics is correct. -@pytest.mark.matlab_parity -@pytest.mark.skip(reason="3D p_final axis ordering mismatch — needs investigation") -class TestIVP3DSimulation: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_ivp_3D_simulation import setup - - return _run_with_binary_sensor(setup, ndim=3) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("ivp_3D_simulation") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final") - - -@pytest.mark.matlab_parity -class TestTVSPDopplerEffect: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_tvsp_doppler_effect import setup - - return _run_with_binary_sensor(setup, ndim=2) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("tvsp_doppler_effect") - - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p", thresh=THRESH_TVSP) - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH_TVSP) +_SKIPPED_3D = [ + # TODO: investigate 3D p_final mismatch — Python max 7e-5 at (20,20,24) vs + # MATLAB max 2e-4 at (60,53,19). Not an axis permutation (all 6 tried). + # Likely a difference in p0 smoothing or heterogeneous medium setup for 3D. + ("ivp_3D_simulation", 3, THRESH, ["p_final"]), + ("tvsp_3D_simulation", 3, THRESH_TVSP, ["p_final"]), +] @pytest.mark.matlab_parity @pytest.mark.skip(reason="3D p_final axis ordering mismatch — needs investigation") -class TestTVSP3DSimulation: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_tvsp_3D_simulation import setup - - return _run_with_binary_sensor(setup, ndim=3, record=["p_final"]) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("tvsp_3D_simulation") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh=THRESH_TVSP) - - -@pytest.mark.matlab_parity -class TestIVPLoadingExternalImage: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_ivp_loading_external_image import setup - - try: - return _run_with_binary_sensor(setup, ndim=2) - except FileNotFoundError: - pytest.skip("EXAMPLE_source_one.png not found (requires k-wave-cupy sibling repo)") - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("ivp_loading_external_image") - - def test_p(self, result, ref): - Nx, Ny = int(ref["Nx"]), int(ref["Ny"]) - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") - - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final") - - -@pytest.mark.matlab_parity -class TestIVPPhotoacousticWaveforms: - @pytest.fixture(scope="class") - def result(self): - from examples.ported.example_ivp_photoacoustic_waveforms import setup - - return _run_with_binary_sensor(setup, ndim=2) - - @pytest.fixture(scope="class") - def ref(self): - return _load_ref("ivp_photoacoustic_waveforms") - - def test_p(self, result, ref): - Nx, Ny = 64, 64 - matlab_p = _reorder_fullgrid_p(ref["p"], Nx, Ny) - _assert_close(np.asarray(result["p"]), matlab_p, "p") +class TestSkipped3D: + @pytest.fixture(scope="class", params=_SKIPPED_3D, ids=[e[0] for e in _SKIPPED_3D]) + def scenario(self, request): + name, ndim, thresh, record = request.param + mod = importlib.import_module(f"examples.{name}") + result = _run_with_binary_sensor(mod.setup, ndim, record) + ref = _load_ref(name) + return result, ref, ndim, thresh - def test_p_final(self, result, ref): - matlab_pf = ref["p_final"][PML_SIZE:-PML_SIZE, PML_SIZE:-PML_SIZE] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final") + def test_p_final(self, scenario): + result, ref, ndim, thresh = scenario + matlab_pf = ref["p_final"][_pml_crop(ndim)] + _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh) # --------------------------------------------------------------------------- # TODO: Parity tests still needed for these ported examples: # -# - example_ivp_saving_movie_files (standard 2D IVP — straightforward) -# - example_na_optimising_performance (uses Cartesian sensor — needs custom ref) -# - example_na_source_smoothing (uses kspaceFirstOrder not kspaceSecondOrder) -# - example_pr_2D_FFT_line_sensor (forward sim with pml_inside=False) -# - example_pr_3D_FFT_planar_sensor (forward sim, pml_inside deviation) -# - example_sd_directional_array_elements (multi-element averaging — custom harness) -# - example_sd_directivity_modelling_2D (11 sims — custom harness) -# - example_sd_directivity_modelling_3D (11 sims on 64^3 — slow, custom harness) +# - ivp_saving_movie_files (standard 2D IVP — straightforward) +# - na_optimising_performance (uses Cartesian sensor — needs custom ref) +# - na_source_smoothing (uses kspaceFirstOrder not kspaceSecondOrder) +# - pr_2D_FFT_line_sensor (forward sim with pml_inside=False) +# - pr_3D_FFT_planar_sensor (forward sim, pml_inside deviation) +# - sd_directional_array_elements (multi-element averaging — custom harness) +# - sd_directivity_modelling_2D (11 sims — custom harness) +# - sd_directivity_modelling_3D (11 sims on 64^3 — slow, custom harness) # # The SD directivity examples need a different test pattern: call run() # directly (not _run_with_binary_sensor) and compare element-level output. From ffe8607de49520bca5ce43e331ea4e2a3fd10880 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sat, 28 Mar 2026 21:18:39 -0700 Subject: [PATCH 2/6] Add jupytext notebook generation, simplify CI - Add jupytext.toml and generate-notebooks.yml: auto-generates .ipynb from examples/*.py on push to master, enabling Colab links - Fix run-examples.yml: exclude legacy/, trigger on PRs touching examples - Delete test_example.yml (disabled, superseded by run-examples.yml) Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/generate-notebooks.yml | 42 ++++++++++++++++++++++++ .github/workflows/run-examples.yml | 8 +++-- .github/workflows/test_example.yml | 29 ---------------- jupytext.toml | 3 ++ 4 files changed, 51 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/generate-notebooks.yml delete mode 100644 .github/workflows/test_example.yml create mode 100644 jupytext.toml diff --git a/.github/workflows/generate-notebooks.yml b/.github/workflows/generate-notebooks.yml new file mode 100644 index 00000000..9f3ddac2 --- /dev/null +++ b/.github/workflows/generate-notebooks.yml @@ -0,0 +1,42 @@ +name: Generate Notebooks + +on: + push: + branches: [master] + paths: ['examples/*.py'] + +jobs: + generate: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install jupytext + run: pip install jupytext + + - name: Generate notebooks + run: | + mkdir -p notebooks + for py in examples/*.py; do + [ -f "$py" ] || continue + basename=$(basename "$py") + jupytext --to notebook "$py" --output "notebooks/${basename%.py}.ipynb" + done + + - name: Commit notebooks + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add notebooks/ + if git diff --cached --quiet; then + echo "No notebook changes" + else + git commit -m "Auto-generate notebooks from examples [skip ci]" + git push origin HEAD:master + fi diff --git a/.github/workflows/run-examples.yml b/.github/workflows/run-examples.yml index ec470a28..721af370 100644 --- a/.github/workflows/run-examples.yml +++ b/.github/workflows/run-examples.yml @@ -4,6 +4,10 @@ on: schedule: - cron: '0 0 * * 1' # Every Monday at 00:00 UTC workflow_dispatch: # Manual trigger + pull_request: + paths: + - 'examples/*.py' + - '.github/workflows/run-examples.yml' jobs: discover-examples: @@ -14,8 +18,8 @@ jobs: - uses: actions/checkout@v4 - id: find-examples run: | - # Find all Python files in examples subdirectories - EXAMPLES=$(find examples -name "*.py" -not -path "*/\.*" | jq -R -s -c 'split("\n")[:-1]') + # Find ported examples (exclude legacy/ and hidden files) + EXAMPLES=$(find examples -maxdepth 1 -name "*.py" -not -path "*/\.*" | jq -R -s -c 'split("\n")[:-1]') echo "examples=$EXAMPLES" >> "$GITHUB_OUTPUT" run-examples: diff --git a/.github/workflows/test_example.yml b/.github/workflows/test_example.yml deleted file mode 100644 index ce05b4ba..00000000 --- a/.github/workflows/test_example.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: test_example - -on: workflow_dispatch # disabled: gdown fails due to Google Drive bot detection - -jobs: - test_example: - strategy: - matrix: - os: [ "windows-latest", "ubuntu-latest" , "macos-latest"] - python-version: [ "3.10", "3.11", "3.12", "3.13" ] - runs-on: ${{matrix.os}} - steps: - - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - - name: Install dependencies - run: | - pip install -e '.[example]' - - name: Run example script - run: | - python3 examples/us_bmode_linear_transducer/us_bmode_linear_transducer.py - - name: Upload example results - uses: actions/upload-artifact@v4 - with: - name: example_bmode_reconstruction_results_${{ matrix.os }}_${{ matrix.python-version }} - path: ${{ github.workspace }}/example_bmode.png diff --git a/jupytext.toml b/jupytext.toml new file mode 100644 index 00000000..7aecd114 --- /dev/null +++ b/jupytext.toml @@ -0,0 +1,3 @@ +# Jupytext config: .py files with # %% markers are the source of truth. +# Generate notebooks with: jupytext --to notebook examples/*.py +formats = "py:percent" From 9e2415095743f25fa14a693c19a98382fb18f540 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sat, 28 Mar 2026 21:24:07 -0700 Subject: [PATCH 3/6] Fix broken doc links and restore per-field test thresholds - Update all doc/README links from examples/X/ to examples/legacy/X/ (fixes link-check CI failure) - Separate p_thresh and p_final_thresh in test registry: TVSP examples keep machine-precision (5e-13) for test_p, looser (5e-11) for test_p_final (addresses Greptile P2 review finding) Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/README.md | 2 +- docs/examples_guide.rst | 40 +++++++++++----------- docs/get_started/first_simulation.rst | 16 ++++----- examples/README.md | 48 +++++++++++++-------------- tests/test_example_parity.py | 48 +++++++++++++-------------- 5 files changed, 77 insertions(+), 77 deletions(-) diff --git a/docs/README.md b/docs/README.md index ac06ad9b..6a0a5482 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ to leverage pythonic practices. ![](_static/example_bmode.png) -A large [collection of examples](../examples/) exists to get started with k-wave-python. All examples can be run in Google Colab notebooks with a few clicks. One can begin with e.g. the [B-mode reconstruction example notebook](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb). +A large [collection of examples](../examples/) exists to get started with k-wave-python. All examples can be run in Google Colab notebooks with a few clicks. One can begin with e.g. the [B-mode reconstruction example notebook](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb). This example file steps through the process of: 1. Generating a simulation medium diff --git a/docs/examples_guide.rst b/docs/examples_guide.rst index 908f810f..def95218 100644 --- a/docs/examples_guide.rst +++ b/docs/examples_guide.rst @@ -17,7 +17,7 @@ Basic Wave Propagation (IVP - Initial Value Problems) * - Example - Core Concept - Topics - * - :ghdir:`examples/ivp_photoacoustic_waveforms/` + * - :ghdir:`examples/legacy/ivp_photoacoustic_waveforms/` - 2D vs 3D wave propagation physics - **IVP** • Wave spreading • Compact support @@ -33,13 +33,13 @@ Simple Transducers & Sources * - Example - Core Concept - Topics - * - :ghdir:`examples/us_defining_transducer/` + * - :ghdir:`examples/legacy/us_defining_transducer/` - Basic ultrasound transducer setup - **US** • Transducer basics • Time-varying sources - * - :ghdir:`examples/at_circular_piston_3D/` + * - :ghdir:`examples/legacy/at_circular_piston_3D/` - Simple focused geometry - **AT** • 3D focusing • Geometric sources - * - :ghdir:`examples/at_circular_piston_AS/` + * - :ghdir:`examples/legacy/at_circular_piston_AS/` - Computational efficiency with symmetry - **AT** • Axisymmetric • Computational optimization @@ -55,16 +55,16 @@ Medical Imaging Applications * - Example - Application - Topics - * - :ghdir:`examples/us_beam_patterns/` + * - :ghdir:`examples/legacy/us_beam_patterns/` - Understanding acoustic beam formation - **US** • Beam focusing • Field patterns - * - :ghdir:`examples/us_bmode_linear_transducer/` + * - :ghdir:`examples/legacy/us_bmode_linear_transducer/` - Complete ultrasound imaging pipeline - **US** • Medical imaging • Signal processing - * - :ghdir:`examples/pr_2D_FFT_line_sensor/` + * - :ghdir:`examples/legacy/pr_2D_FFT_line_sensor/` - Photoacoustic image reconstruction - **PR** • Image reconstruction • FFT methods - * - :ghdir:`examples/pr_2D_TR_line_sensor/` + * - :ghdir:`examples/legacy/pr_2D_TR_line_sensor/` - Alternative reconstruction approach - **PR** • Time reversal • Reconstruction @@ -80,19 +80,19 @@ Advanced Transducer Modeling (AT - Array Transducers) * - Example - Advanced Technique - Topics - * - :ghdir:`examples/at_array_as_source/` + * - :ghdir:`examples/legacy/at_array_as_source/` - kWaveArray for complex geometries - **AT** • Array modeling • Anti-aliasing - * - :ghdir:`examples/at_array_as_sensor/` + * - :ghdir:`examples/legacy/at_array_as_sensor/` - Complex sensor array geometries - **AT** • Sensor arrays • Flexible positioning - * - :ghdir:`examples/at_linear_array_transducer/` + * - :ghdir:`examples/legacy/at_linear_array_transducer/` - Multi-element linear arrays - **AT** • Linear arrays • Element spacing - * - :ghdir:`examples/at_focused_bowl_3D/` + * - :ghdir:`examples/legacy/at_focused_bowl_3D/` - 3D focused ultrasound therapy - **AT** • Therapeutic US • 3D focusing - * - :ghdir:`examples/at_focused_annular_array_3D/` + * - :ghdir:`examples/legacy/at_focused_annular_array_3D/` - Multi-element focused systems - **AT** • Annular arrays • Complex focusing @@ -108,13 +108,13 @@ Advanced Imaging & Reconstruction (PR - Pressure/Photoacoustic Reconstruction) * - Example - Reconstruction Method - Topics - * - :ghdir:`examples/pr_3D_FFT_planar_sensor/` + * - :ghdir:`examples/legacy/pr_3D_FFT_planar_sensor/` - 3D FFT-based reconstruction - **PR** • 3D imaging • Planar arrays - * - :ghdir:`examples/pr_3D_TR_planar_sensor/` + * - :ghdir:`examples/legacy/pr_3D_TR_planar_sensor/` - 3D time reversal reconstruction - **PR** • 3D time reversal • Volumetric imaging - * - :ghdir:`examples/us_bmode_phased_array/` + * - :ghdir:`examples/legacy/us_bmode_phased_array/` - Advanced ultrasound beamforming - **US** • Phased arrays • Electronic steering @@ -130,13 +130,13 @@ Sensor Physics & Directivity (SD - Sensor Directivity) * - Example - Physics Concept - Topics - * - :ghdir:`examples/sd_directivity_modelling_2D/` + * - :ghdir:`examples/legacy/sd_directivity_modelling_2D/` - How sensor size affects measurements - **SD** • Directivity • Finite sensor size - * - :ghdir:`examples/sd_focussed_detector_2D/` + * - :ghdir:`examples/legacy/sd_focussed_detector_2D/` - Directional sensor sensitivity - **SD** • Focused detection • Sensor design - * - :ghdir:`examples/sd_focussed_detector_3D/` + * - :ghdir:`examples/legacy/sd_focussed_detector_3D/` - 3D focused sensor modeling - **SD** • 3D detection • Sensor focusing @@ -152,7 +152,7 @@ Computational Optimization (NA - Numerical Analysis) * - Example - Optimization Topic - Topics - * - :ghdir:`examples/na_controlling_the_pml/` + * - :ghdir:`examples/legacy/na_controlling_the_pml/` - Boundary conditions and efficiency - **NA** • PML boundaries • Computational domains diff --git a/docs/get_started/first_simulation.rst b/docs/get_started/first_simulation.rst index beb3d117..4dca88f7 100644 --- a/docs/get_started/first_simulation.rst +++ b/docs/get_started/first_simulation.rst @@ -194,22 +194,22 @@ Now that you understand the four-component structure, explore these examples to **Beginner Examples** (start here): -- :ghfile:`Photoacoustic Waveforms ` - See how 2D and 3D wave propagation differs -- :ghfile:`Defining Transducers ` - Learn about ultrasound transducers +- :ghfile:`Photoacoustic Waveforms ` - See how 2D and 3D wave propagation differs +- :ghfile:`Defining Transducers ` - Learn about ultrasound transducers **Medical Imaging Applications**: -- :ghfile:`B-mode Linear Transducer ` - Full B-mode ultrasound imaging pipeline -- :ghfile:`2D FFT Line Sensor ` - Photoacoustic image reconstruction +- :ghfile:`B-mode Linear Transducer ` - Full B-mode ultrasound imaging pipeline +- :ghfile:`2D FFT Line Sensor ` - Photoacoustic image reconstruction **Advanced Transducer Modeling**: -- :ghfile:`Array as Source ` - Complex array transducers without staircasing -- :ghfile:`Focused Bowl 3D ` - Focused ultrasound applications +- :ghfile:`Array as Source ` - Complex array transducers without staircasing +- :ghfile:`Focused Bowl 3D ` - Focused ultrasound applications **Acoustic Field Analysis**: -- :ghfile:`Beam Patterns ` - Understand beam formation and focusing -- :ghfile:`Focused Detector 2D ` - Sensor directivity effects +- :ghfile:`Beam Patterns ` - Understand beam formation and focusing +- :ghfile:`Focused Detector 2D ` - Sensor directivity effects Each example builds on the same four-component framework but demonstrates different aspects of acoustic simulation. The key insight is that no matter how complex the application, every k-Wave simulation follows this same logical structure. \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index de463edb..c1cf7993 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,38 +6,38 @@ Every example has a short readme.md file which briefly describes the purpose of ## List of Examples -- [Array as a sensor](at_array_as_sensor/) ([original example](http://www.k-wave.org/documentation/example_at_array_as_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_array_as_sensor/at_array_as_sensor.ipynb)) -- [Array as a source](at_array_as_source/) ([original example](http://www.k-wave.org/documentation/example_at_array_as_source.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_array_as_source/at_array_as_source.ipynb)) -- [Linear array transducer](at_linear_array_transducer/) - ([original example](http://www.k-wave.org/documentation/example_at_linear_array_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_linear_array_transducer/at_linear_array_transducer.ipynb)) -- [Photoacoustic Waveforms](ivp_photoacoustic_waveforms/) ([original example](http://www.k-wave.org/documentation/example_ivp_photoacoustic_waveforms.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb)) -- [Controlling the PML](na_controlling_the_pml/) - ([original example](http://www.k-wave.org/documentation/example_na_controlling_the_pml.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/na_controlling_the_pml/na_controlling_the_pml.ipynb)) -- [Defining An Ultrasound Transducer Example](us_defining_transducer) ([original example](http://www.k-wave.org/documentation/example_us_defining_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_defining_transducer/us_defining_transducer.ipynb)) -- [Simulating Ultrasound Beam Patterns](us_beam_patterns/) ([original example](http://www.k-wave.org/documentation/example_us_beam_patterns), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_beam_patterns/us_beam_patterns.ipynb)) -- [Linear transducer B-mode](us_bmode_linear_transducer/) ([original example](http://www.k-wave.org/documentation/example_us_bmode_linear_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb)) -- [Phased array B-mode](us_bmode_phased_array/) - ([original example](http://www.k-wave.org/documentation/example_us_bmode_phased_array.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/us_bmode_phased_array/us_bmode_phased_array.ipynb)) -- [Circular piston transducer](at_circular_piston_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading3), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_circular_piston_3D/at_circular_piston_3D.ipynb)) +- [Array as a sensor](legacy/at_array_as_sensor/) ([original example](http://www.k-wave.org/documentation/example_at_array_as_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/at_array_as_sensor/at_array_as_sensor.ipynb)) +- [Array as a source](legacy/at_array_as_source/) ([original example](http://www.k-wave.org/documentation/example_at_array_as_source.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/at_array_as_source/at_array_as_source.ipynb)) +- [Linear array transducer](legacy/at_linear_array_transducer/) + ([original example](http://www.k-wave.org/documentation/example_at_linear_array_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/at_linear_array_transducer/at_linear_array_transducer.ipynb)) +- [Photoacoustic Waveforms](legacy/ivp_photoacoustic_waveforms/) ([original example](http://www.k-wave.org/documentation/example_ivp_photoacoustic_waveforms.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/ivp_photoacoustic_waveforms/ivp_photoacoustic_waveforms.ipynb)) +- [Controlling the PML](legacy/na_controlling_the_pml/) + ([original example](http://www.k-wave.org/documentation/example_na_controlling_the_pml.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/na_controlling_the_pml/na_controlling_the_pml.ipynb)) +- [Defining An Ultrasound Transducer Example](legacy/us_defining_transducer) ([original example](http://www.k-wave.org/documentation/example_us_defining_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/us_defining_transducer/us_defining_transducer.ipynb)) +- [Simulating Ultrasound Beam Patterns](legacy/us_beam_patterns/) ([original example](http://www.k-wave.org/documentation/example_us_beam_patterns), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/us_beam_patterns/us_beam_patterns.ipynb)) +- [Linear transducer B-mode](legacy/us_bmode_linear_transducer/) ([original example](http://www.k-wave.org/documentation/example_us_bmode_linear_transducer.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/us_bmode_linear_transducer/us_bmode_linear_transducer.ipynb)) +- [Phased array B-mode](legacy/us_bmode_phased_array/) + ([original example](http://www.k-wave.org/documentation/example_us_bmode_phased_array.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/us_bmode_phased_array/us_bmode_phased_array.ipynb)) +- [Circular piston transducer](legacy/at_circular_piston_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading3), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/at_circular_piston_3D/at_circular_piston_3D.ipynb)) -- [Circular piston transducer (axisymmetric)](at_circular_piston_AS/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading4), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_circular_piston_AS/at_circular_piston_AS.ipynb)) +- [Circular piston transducer (axisymmetric)](legacy/at_circular_piston_AS/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading4), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/at_circular_piston_AS/at_circular_piston_AS.ipynb)) -- [Focused bowl transducer](at_focused_bowl_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading5), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_focused_bowl_3D/at_focused_bowl_3D.ipynb)) +- [Focused bowl transducer](legacy/at_focused_bowl_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading5), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/at_focused_bowl_3D/at_focused_bowl_3D.ipynb)) -- [Focused bowl transducer (axisymmetric)](at_focused_bowl_AS/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading6), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_focused_bowl_AS/at_focused_bowl_AS.ipynb)) +- [Focused bowl transducer (axisymmetric)](legacy/at_focused_bowl_AS/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading6), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/at_focused_bowl_AS/at_focused_bowl_AS.ipynb)) -- [Focused annular array](at_focused_annular_array_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading7), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb)) -- [2D FFT Reconstruction For A Line Sensor](pr_2D_FFT_line_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_2D_fft_line_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb)) -- [3D FFT Reconstruction For A Planar Sensor](pr_3D_FFT_planar_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_3D_fft_planar_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb)) -- [2D Time Reversal Reconstruction For A Line Sensor](pr_2D_TR_line_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_2D_tr_line_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb)) -- [3D Time Reversal Reconstruction For A Planar Sensor](pr_3D_TR_planar_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_3D_tr_planar_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb)) +- [Focused annular array](legacy/at_focused_annular_array_3D/) ([original example](http://www.k-wave.org/documentation/example_at_piston_and_bowl_transducers.php#heading7), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/at_focused_annular_array_3D/at_focused_annular_array_3D.ipynb)) +- [2D FFT Reconstruction For A Line Sensor](legacy/pr_2D_FFT_line_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_2D_fft_line_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/pr_2D_FFT_line_sensor/pr_2D_FFT_line_sensor.ipynb)) +- [3D FFT Reconstruction For A Planar Sensor](legacy/pr_3D_FFT_planar_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_3D_fft_planar_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/pr_3D_FFT_planar_sensor/pr_3D_FFT_planar_sensor.ipynb)) +- [2D Time Reversal Reconstruction For A Line Sensor](legacy/pr_2D_TR_line_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_2D_tr_line_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/pr_2D_TR_line_sensor/pr_2D_TR_line_sensor.ipynb)) +- [3D Time Reversal Reconstruction For A Planar Sensor](legacy/pr_3D_TR_planar_sensor/) ([original example](http://www.k-wave.org/documentation/example_pr_3D_tr_planar_sensor.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/pr_3D_TR_planar_sensor/pr_3D_TR_planar_sensor.ipynb)) -- [Focussed Detector In 2D Example](sd_focussed_detector_2D/) ([original example](http://www.k-wave.org/documentation/example_sd_focussed_detector_2D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb)) +- [Focussed Detector In 2D Example](legacy/sd_focussed_detector_2D/) ([original example](http://www.k-wave.org/documentation/example_sd_focussed_detector_2D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/sd_focussed_detector_2D/sd_focussed_detector_2D.ipynb)) -- [Focussed Detector In 3D Example](sd_focussed_detector_3D/) ([original example](http://www.k-wave.org/documentation/example_sd_focussed_detector_3D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb)) +- [Focussed Detector In 3D Example](legacy/sd_focussed_detector_3D/) ([original example](http://www.k-wave.org/documentation/example_sd_focussed_detector_3D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/sd_focussed_detector_3D/sd_focussed_detector_3D.ipynb)) -- [Modelling Sensor Directivity In 2D Example](sd_directivity_modelling_2D/) ([original example](http://www.k-wave.org/documentation/example_sd_directivity_modelling_2D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb)) +- [Modelling Sensor Directivity In 2D Example](legacy/sd_directivity_modelling_2D/) ([original example](http://www.k-wave.org/documentation/example_sd_directivity_modelling_2D.php), [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/waltsims/k-wave-python/blob/HEAD/examples/legacy/sd_directivity_modelling_2D/sd_directivity_modelling_2D.ipynb)) ## Contributing new examples diff --git a/tests/test_example_parity.py b/tests/test_example_parity.py index c76e28bb..61198f7e 100644 --- a/tests/test_example_parity.py +++ b/tests/test_example_parity.py @@ -115,25 +115,25 @@ def _pml_crop(ndim): # Example registry # --------------------------------------------------------------------------- -# (name, ndim, thresh) — standard examples tested for p + p_final +# (name, ndim, p_thresh, p_final_thresh) _EXAMPLES = [ - # IVP - ("ivp_homogeneous_medium", 2, THRESH), - ("ivp_heterogeneous_medium", 2, THRESH), - ("ivp_binary_sensor_mask", 2, THRESH), - ("ivp_1D_simulation", 1, THRESH), - ("ivp_loading_external_image", 2, THRESH), - ("ivp_photoacoustic_waveforms", 2, THRESH), - # TVSP - ("tvsp_homogeneous_medium_monopole", 2, THRESH_TVSP), - ("tvsp_homogeneous_medium_dipole", 2, THRESH_TVSP), - ("tvsp_steering_linear_array", 2, THRESH_TVSP), - ("tvsp_snells_law", 2, THRESH_TVSP), - ("tvsp_doppler_effect", 2, THRESH_TVSP), - # NA (filtering) - ("na_filtering_part_1", 1, THRESH), - ("na_filtering_part_2", 1, THRESH_TVSP), - ("na_filtering_part_3", 1, THRESH_TVSP), + # IVP — machine precision for both p and p_final + ("ivp_homogeneous_medium", 2, THRESH, THRESH), + ("ivp_heterogeneous_medium", 2, THRESH, THRESH), + ("ivp_binary_sensor_mask", 2, THRESH, THRESH), + ("ivp_1D_simulation", 1, THRESH, THRESH), + ("ivp_loading_external_image", 2, THRESH, THRESH), + ("ivp_photoacoustic_waveforms", 2, THRESH, THRESH), + # TVSP — p is machine precision, p_final is looser (more FFT round-trips) + ("tvsp_homogeneous_medium_monopole", 2, THRESH, THRESH_TVSP), + ("tvsp_homogeneous_medium_dipole", 2, THRESH, THRESH_TVSP), + ("tvsp_steering_linear_array", 2, THRESH, THRESH_TVSP), + ("tvsp_snells_law", 2, THRESH, THRESH_TVSP), + ("tvsp_doppler_effect", 2, THRESH_TVSP, THRESH_TVSP), + # NA (filtering) — p is machine precision, p_final looser for parts 2/3 + ("na_filtering_part_1", 1, THRESH, THRESH), + ("na_filtering_part_2", 1, THRESH, THRESH_TVSP), + ("na_filtering_part_3", 1, THRESH, THRESH_TVSP), ] @@ -148,29 +148,29 @@ class TestStandardExamples: @pytest.fixture(scope="class", params=_EXAMPLES, ids=[e[0] for e in _EXAMPLES]) def scenario(self, request): - name, ndim, thresh = request.param + name, ndim, p_thresh, pf_thresh = request.param mod = importlib.import_module(f"examples.{name}") try: result = _run_with_binary_sensor(mod.setup, ndim) except FileNotFoundError: pytest.skip(f"Asset not found for {name}") ref = _load_ref(name) - return result, ref, ndim, thresh + return result, ref, ndim, p_thresh, pf_thresh def test_p(self, scenario): - result, ref, ndim, thresh = scenario + result, ref, ndim, p_thresh, _pf_thresh = scenario matlab_p = ref["p"] if ndim >= 2: matlab_p = _reorder_fullgrid(matlab_p, _grid_shape_from_ref(ref, ndim)) - _assert_close(np.asarray(result["p"]), matlab_p, "p", thresh) + _assert_close(np.asarray(result["p"]), matlab_p, "p", p_thresh) def test_p_final(self, scenario): - result, ref, ndim, thresh = scenario + result, ref, ndim, _p_thresh, pf_thresh = scenario matlab_pf = ref["p_final"] if ndim == 1: matlab_pf = matlab_pf.ravel() matlab_pf = matlab_pf[_pml_crop(ndim)] - _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", thresh) + _assert_close(np.asarray(result["p_final"]), matlab_pf, "p_final", pf_thresh) # --------------------------------------------------------------------------- From 78a64291b5ac1fa5372f7a49f672be870c4d84d7 Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sat, 28 Mar 2026 21:31:03 -0700 Subject: [PATCH 4/6] Fix example plotting bugs exposed by run-examples CI - na_filtering_part_{1,2,3}: cast f_max to float (was array from k_max) - tvsp_steering_linear_array: infer p_final grid shape instead of hardcoding 128x128 (PML stripping changes the size) - ivp_loading_external_image: fix _REPO_ROOT after move from examples/ported/ to examples/ (one fewer parent dir) Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/ivp_loading_external_image.py | 2 +- examples/na_filtering_part_1.py | 2 +- examples/na_filtering_part_2.py | 2 +- examples/na_filtering_part_3.py | 2 +- examples/tvsp_steering_linear_array.py | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/ivp_loading_external_image.py b/examples/ivp_loading_external_image.py index a84e2591..a10c7598 100644 --- a/examples/ivp_loading_external_image.py +++ b/examples/ivp_loading_external_image.py @@ -33,7 +33,7 @@ from kwave.utils.matrix import resize _IMAGE_FILENAME = "EXAMPLE_source_one.png" -_REPO_ROOT = os.path.join(os.path.dirname(__file__), "..", "..") +_REPO_ROOT = os.path.join(os.path.dirname(__file__), "..") def _find_image(): diff --git a/examples/na_filtering_part_1.py b/examples/na_filtering_part_1.py index 8ce8b5ee..d7f63466 100644 --- a/examples/na_filtering_part_1.py +++ b/examples/na_filtering_part_1.py @@ -132,7 +132,7 @@ def run(backend="python", device="cpu", quiet=True): input_as = np.abs(np.fft.rfft(source_p)) / N output_as = np.abs(np.fft.rfft(p[sensor_row, :])) / N - f_max = kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi) + f_max = float(kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi)) f_MHz = f * 1e-6 ax2.plot(f_MHz, input_as, "k-", label="Input spectrum") diff --git a/examples/na_filtering_part_2.py b/examples/na_filtering_part_2.py index 5079d9a9..0f0490ee 100644 --- a/examples/na_filtering_part_2.py +++ b/examples/na_filtering_part_2.py @@ -151,7 +151,7 @@ def run(backend="python", device="cpu", quiet=True): f = np.fft.rfftfreq(N, d=dt) output_as = np.abs(np.fft.rfft(p[sensor_row, :])) / N - f_max = kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi) + f_max = float(kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi)) f_MHz = f * 1e-6 ax2.plot(f_MHz, output_as, "b-", label="Recorded spectrum") diff --git a/examples/na_filtering_part_3.py b/examples/na_filtering_part_3.py index 945eb870..7e558a2a 100644 --- a/examples/na_filtering_part_3.py +++ b/examples/na_filtering_part_3.py @@ -142,7 +142,7 @@ def run(backend="python", device="cpu", quiet=True): orig_as = np.abs(np.fft.rfft(source_func_orig)) / Nt filt_as = np.abs(np.fft.rfft(source_p)) / Nt - f_max = kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi) + f_max = float(kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi)) f_MHz = f * 1e-6 ax2.plot(f_MHz, orig_as, "k-", label="Original spectrum") diff --git a/examples/tvsp_steering_linear_array.py b/examples/tvsp_steering_linear_array.py index e95e771b..5629e9ae 100644 --- a/examples/tvsp_steering_linear_array.py +++ b/examples/tvsp_steering_linear_array.py @@ -112,13 +112,13 @@ def run(backend="python", device="cpu", quiet=True): if __name__ == "__main__": import matplotlib.pyplot as plt + kgrid, _, source = setup() result = run(quiet=False) - p_final = np.asarray(result["p_final"]).reshape(128, 128) + pf = np.asarray(result["p_final"]) + side = int(np.sqrt(pf.size)) + p_final = pf.reshape(side, side) fig, axes = plt.subplots(1, 2, figsize=(12, 5)) - - # plot p_final - kgrid, _, source = setup() ax = axes[0] im = ax.imshow( p_final.T, From 228636f12bb59e4ba1bf08ec38441fe179e7bcab Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sat, 28 Mar 2026 21:45:36 -0700 Subject: [PATCH 5/6] Fix k_max array: use np.max() before float() conversion kgrid.k_max is a multi-element array in 1D grids; np.max() reduces it to a scalar before float() conversion for axvline. Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/na_filtering_part_1.py | 2 +- examples/na_filtering_part_2.py | 2 +- examples/na_filtering_part_3.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/na_filtering_part_1.py b/examples/na_filtering_part_1.py index d7f63466..b70919fb 100644 --- a/examples/na_filtering_part_1.py +++ b/examples/na_filtering_part_1.py @@ -132,7 +132,7 @@ def run(backend="python", device="cpu", quiet=True): input_as = np.abs(np.fft.rfft(source_p)) / N output_as = np.abs(np.fft.rfft(p[sensor_row, :])) / N - f_max = float(kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi)) + f_max = float(np.max(kgrid.k_max) * np.min(medium.sound_speed) / (2 * np.pi)) f_MHz = f * 1e-6 ax2.plot(f_MHz, input_as, "k-", label="Input spectrum") diff --git a/examples/na_filtering_part_2.py b/examples/na_filtering_part_2.py index 0f0490ee..400417a2 100644 --- a/examples/na_filtering_part_2.py +++ b/examples/na_filtering_part_2.py @@ -151,7 +151,7 @@ def run(backend="python", device="cpu", quiet=True): f = np.fft.rfftfreq(N, d=dt) output_as = np.abs(np.fft.rfft(p[sensor_row, :])) / N - f_max = float(kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi)) + f_max = float(np.max(kgrid.k_max) * np.min(medium.sound_speed) / (2 * np.pi)) f_MHz = f * 1e-6 ax2.plot(f_MHz, output_as, "b-", label="Recorded spectrum") diff --git a/examples/na_filtering_part_3.py b/examples/na_filtering_part_3.py index 7e558a2a..7c73d8ae 100644 --- a/examples/na_filtering_part_3.py +++ b/examples/na_filtering_part_3.py @@ -142,7 +142,7 @@ def run(backend="python", device="cpu", quiet=True): orig_as = np.abs(np.fft.rfft(source_func_orig)) / Nt filt_as = np.abs(np.fft.rfft(source_p)) / Nt - f_max = float(kgrid.k_max * np.min(medium.sound_speed) / (2 * np.pi)) + f_max = float(np.max(kgrid.k_max) * np.min(medium.sound_speed) / (2 * np.pi)) f_MHz = f * 1e-6 ax2.plot(f_MHz, orig_as, "k-", label="Original spectrum") From a6d4e7e81b5f274660e04ea0660af7b24bf1054a Mon Sep 17 00:00:00 2001 From: Walter Simson Date: Sat, 28 Mar 2026 22:16:43 -0700 Subject: [PATCH 6/6] Add # %% cell markers, update release plan, fix Greptile findings - Add # %% jupytext cell markers to all 29 examples (imports, setup, run, __main__) so generated notebooks have useful multi-cell structure - Pin jupytext version, add git pull --rebase before push to prevent non-fast-forward race condition (Greptile P1) - Update release plan: v0.6.2 done items, v0.6.3 feature list, simplification targets, real-world validation step Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/generate-notebooks.yml | 3 +- examples/ivp_1D_simulation.py | 4 ++ examples/ivp_3D_simulation.py | 4 ++ examples/ivp_binary_sensor_mask.py | 4 ++ examples/ivp_heterogeneous_medium.py | 4 ++ examples/ivp_homogeneous_medium.py | 4 ++ examples/ivp_loading_external_image.py | 4 ++ examples/ivp_photoacoustic_waveforms.py | 6 ++ examples/ivp_recording_particle_velocity.py | 4 ++ examples/ivp_saving_movie_files.py | 4 ++ examples/na_controlling_the_PML.py | 4 ++ examples/na_filtering_part_1.py | 4 ++ examples/na_filtering_part_2.py | 4 ++ examples/na_filtering_part_3.py | 4 ++ examples/na_modelling_nonlinearity.py | 4 ++ examples/na_optimising_performance.py | 4 ++ examples/na_source_smoothing.py | 4 ++ examples/pr_2D_FFT_line_sensor.py | 4 ++ examples/pr_3D_FFT_planar_sensor.py | 4 ++ examples/sd_directional_array_elements.py | 4 ++ examples/sd_directivity_modelling_2D.py | 4 ++ examples/sd_directivity_modelling_3D.py | 4 ++ examples/sd_focussed_detector_2D.py | 4 ++ examples/sd_focussed_detector_3D.py | 4 ++ examples/tvsp_3D_simulation.py | 4 ++ examples/tvsp_doppler_effect.py | 4 ++ examples/tvsp_homogeneous_medium_dipole.py | 4 ++ examples/tvsp_homogeneous_medium_monopole.py | 4 ++ examples/tvsp_snells_law.py | 4 ++ examples/tvsp_steering_linear_array.py | 4 ++ plans/release-strategy.md | 75 +++++++++++++++----- 31 files changed, 176 insertions(+), 20 deletions(-) diff --git a/.github/workflows/generate-notebooks.yml b/.github/workflows/generate-notebooks.yml index 9f3ddac2..f654abad 100644 --- a/.github/workflows/generate-notebooks.yml +++ b/.github/workflows/generate-notebooks.yml @@ -18,7 +18,7 @@ jobs: python-version: '3.10' - name: Install jupytext - run: pip install jupytext + run: pip install 'jupytext>=1.16,<2' - name: Generate notebooks run: | @@ -33,6 +33,7 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + git pull --rebase origin master git add notebooks/ if git diff --cached --quiet; then echo "No notebook changes" diff --git a/examples/ivp_1D_simulation.py b/examples/ivp_1D_simulation.py index 205837db..6daecd2f 100644 --- a/examples/ivp_1D_simulation.py +++ b/examples/ivp_1D_simulation.py @@ -14,6 +14,7 @@ travelling waves, with partial reflections and transmission at the impedance boundaries. """ +# %% import numpy as np from kwave.data import Vector @@ -24,6 +25,7 @@ from kwave.kspaceFirstOrder import kspaceFirstOrder +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -65,6 +67,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with the original Cartesian two-point sensor. @@ -91,6 +94,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/ivp_3D_simulation.py b/examples/ivp_3D_simulation.py index e9c3e21a..0479f7a7 100644 --- a/examples/ivp_3D_simulation.py +++ b/examples/ivp_3D_simulation.py @@ -12,6 +12,7 @@ It builds on the Homogeneous Propagation Medium and Heterogeneous Propagation Medium examples. """ +# %% import numpy as np from kwave.data import Vector @@ -23,6 +24,7 @@ from kwave.utils.mapgen import make_ball +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -79,6 +81,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with the original Cartesian sensor. @@ -110,6 +113,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/ivp_binary_sensor_mask.py b/examples/ivp_binary_sensor_mask.py index 2972e9f9..1f9aed6c 100644 --- a/examples/ivp_binary_sensor_mask.py +++ b/examples/ivp_binary_sensor_mask.py @@ -13,6 +13,7 @@ expanding wavefronts, now sampled at grid-aligned sensor positions along a three-quarter arc. """ +# %% import numpy as np from kwave.data import Vector @@ -24,6 +25,7 @@ from kwave.utils.mapgen import make_circle, make_disc +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -69,6 +71,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with the original binary arc sensor. @@ -107,6 +110,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/ivp_heterogeneous_medium.py b/examples/ivp_heterogeneous_medium.py index 69223419..131c78f1 100644 --- a/examples/ivp_heterogeneous_medium.py +++ b/examples/ivp_heterogeneous_medium.py @@ -13,6 +13,7 @@ You should observe wavefront distortion and partial reflections at the impedance boundaries. """ +# %% import numpy as np from kwave.data import Vector @@ -24,6 +25,7 @@ from kwave.utils.mapgen import make_cart_circle, make_disc +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -69,6 +71,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with the original Cartesian circular sensor. @@ -95,6 +98,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/ivp_homogeneous_medium.py b/examples/ivp_homogeneous_medium.py index 7ad2dda9..d80bb349 100644 --- a/examples/ivp_homogeneous_medium.py +++ b/examples/ivp_homogeneous_medium.py @@ -13,6 +13,7 @@ expanding circular wavefronts (one from each disc) that are progressively attenuated by the absorbing medium. """ +# %% import numpy as np from kwave.data import Vector @@ -24,6 +25,7 @@ from kwave.utils.mapgen import make_cart_circle, make_disc +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -69,6 +71,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with the original Cartesian circular sensor. @@ -95,6 +98,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/ivp_loading_external_image.py b/examples/ivp_loading_external_image.py index a10c7598..f2a59059 100644 --- a/examples/ivp_loading_external_image.py +++ b/examples/ivp_loading_external_image.py @@ -18,6 +18,7 @@ It builds on the Homogeneous Propagation Medium Example. """ +# %% import os import numpy as np @@ -49,6 +50,7 @@ def _find_image(): raise FileNotFoundError(f"Cannot find {_IMAGE_FILENAME}. Looked in: {candidates}") +# %% def setup(): """Set up simulation physics (grid, medium, source). @@ -89,6 +91,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a full-grid binary sensor recording p and p_final. @@ -117,6 +120,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/ivp_photoacoustic_waveforms.py b/examples/ivp_photoacoustic_waveforms.py index 15f51289..accc68d6 100644 --- a/examples/ivp_photoacoustic_waveforms.py +++ b/examples/ivp_photoacoustic_waveforms.py @@ -14,6 +14,7 @@ setup() prepares all three dimensionalities (1D slab, 2D disc, 3D ball). run() executes the 2D case, which is the most representative. """ +# %% import numpy as np from kwave.data import Vector @@ -25,6 +26,7 @@ from kwave.utils.mapgen import make_ball, make_disc +# %% def setup(): """Set up the simulation physics for 1D, 2D, and 3D cases. @@ -88,6 +90,7 @@ def setup(): return kgrid, medium, source +# %% def setup_1d(): """Set up the 1D case (slab source, single sensor point). @@ -125,6 +128,7 @@ def setup_1d(): return kgrid, medium, source, sensor +# %% def setup_3d(): """Set up the 3D case (ball source, single sensor point). @@ -163,6 +167,7 @@ def setup_3d(): return kgrid, medium, source, sensor +# %% def run(backend="python", device="cpu", quiet=True): """Run the 2D case with a full-grid binary sensor. @@ -189,6 +194,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/ivp_recording_particle_velocity.py b/examples/ivp_recording_particle_velocity.py index 21464487..bccea98a 100644 --- a/examples/ivp_recording_particle_velocity.py +++ b/examples/ivp_recording_particle_velocity.py @@ -12,6 +12,7 @@ the y-axis show the opposite — illustrating the vector nature of acoustic particle velocity. """ +# %% import numpy as np from kwave.data import Vector @@ -23,6 +24,7 @@ from kwave.utils.mapgen import make_disc +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -60,6 +62,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with four binary sensor points at cardinal directions. @@ -95,6 +98,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/ivp_saving_movie_files.py b/examples/ivp_saving_movie_files.py index ad2a6ea3..82764252 100644 --- a/examples/ivp_saving_movie_files.py +++ b/examples/ivp_saving_movie_files.py @@ -8,6 +8,7 @@ Cartesian circular sensor. Movie saving is skipped; only the simulation and p_final recording are ported. """ +# %% import numpy as np from kwave.data import Vector @@ -19,6 +20,7 @@ from kwave.utils.mapgen import make_cart_circle, make_disc +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -57,6 +59,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run the simulation with a Cartesian circular sensor. @@ -84,6 +87,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/na_controlling_the_PML.py b/examples/na_controlling_the_PML.py index 677f50c2..ccd626aa 100644 --- a/examples/na_controlling_the_PML.py +++ b/examples/na_controlling_the_PML.py @@ -22,6 +22,7 @@ Builds on: example_ivp_homogeneous_medium (same grid, medium, and initial pressure distribution but without absorption). """ +# %% import numpy as np from kwave.data import Vector @@ -33,6 +34,7 @@ from kwave.utils.mapgen import make_disc +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -124,6 +126,7 @@ def _run_case(case, backend="python", device="cpu", quiet=True): return kspaceFirstOrder(kgrid, medium, source, sensor, **kwargs) +# %% def run(backend="python", device="cpu", quiet=True): """Run with PML alpha = 0 (default case, PML with no absorption). @@ -136,6 +139,7 @@ def run(backend="python", device="cpu", quiet=True): return _run_case(1, backend=backend, device=device, quiet=quiet) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/na_filtering_part_1.py b/examples/na_filtering_part_1.py index b70919fb..8adb8db7 100644 --- a/examples/na_filtering_part_1.py +++ b/examples/na_filtering_part_1.py @@ -14,6 +14,7 @@ The PML is placed *outside* the computational domain (PMLInside = false) with alpha = 0 (no absorption) so it acts purely as a grid extension. """ +# %% import numpy as np from kwave.data import Vector @@ -24,6 +25,7 @@ from kwave.kspaceFirstOrder import kspaceFirstOrder +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -68,6 +70,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a full-grid binary sensor. @@ -99,6 +102,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/na_filtering_part_2.py b/examples/na_filtering_part_2.py index 400417a2..5800487a 100644 --- a/examples/na_filtering_part_2.py +++ b/examples/na_filtering_part_2.py @@ -12,6 +12,7 @@ The default CFL case is ported: dt = 7 ns fixed, Nt = 1024 steps. """ +# %% import numpy as np from kwave.data import Vector @@ -23,6 +24,7 @@ from kwave.utils.filters import smooth +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -91,6 +93,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a full-grid binary sensor. @@ -118,6 +121,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/na_filtering_part_3.py b/examples/na_filtering_part_3.py index 7c73d8ae..58766111 100644 --- a/examples/na_filtering_part_3.py +++ b/examples/na_filtering_part_3.py @@ -13,6 +13,7 @@ The default case (example_number = 1) is ported: causal filter with default PPW and transition width. """ +# %% import numpy as np from kwave.data import Vector @@ -24,6 +25,7 @@ from kwave.utils.filters import filter_time_series +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -77,6 +79,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a full-grid binary sensor. @@ -104,6 +107,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/na_modelling_nonlinearity.py b/examples/na_modelling_nonlinearity.py index b6f7a586..499b5c0e 100644 --- a/examples/na_modelling_nonlinearity.py +++ b/examples/na_modelling_nonlinearity.py @@ -26,6 +26,7 @@ Builds on: example_tvsp_homogeneous_medium_monopole (time-varying pressure source), but uses a 1D grid with nonlinear medium properties. """ +# %% import numpy as np from kwave.data import Vector @@ -36,6 +37,7 @@ from kwave.kspaceFirstOrder import kspaceFirstOrder +# %% def setup(): """Set up the nonlinear 1D simulation physics (grid, medium, source). @@ -132,6 +134,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a full-grid binary sensor recording p. @@ -177,6 +180,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/na_optimising_performance.py b/examples/na_optimising_performance.py index 46909515..1ff5031f 100644 --- a/examples/na_optimising_performance.py +++ b/examples/na_optimising_performance.py @@ -8,6 +8,7 @@ for the Python backend). Uses the binary sensor mask approach (MATLAB case 2) for optimal performance. """ +# %% from pathlib import Path import numpy as np @@ -36,6 +37,7 @@ def _find_image(): raise FileNotFoundError("Cannot find EXAMPLE_source_two.bmp. " "Expected in /tests/EXAMPLE_source_two.bmp") +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -70,6 +72,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a Cartesian circular sensor. @@ -97,6 +100,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/na_source_smoothing.py b/examples/na_source_smoothing.py index 75c1967f..9cf5fad6 100644 --- a/examples/na_source_smoothing.py +++ b/examples/na_source_smoothing.py @@ -12,6 +12,7 @@ frequency-domain window to a delta-function initial pressure before propagating. """ +# %% import numpy as np from kwave.data import Vector @@ -23,6 +24,7 @@ from kwave.utils.signals import get_win +# %% def setup(): """Set up the simulation physics (grid, medium, source) with no window. @@ -60,6 +62,7 @@ def _apply_window(p0, Nx, window_type): return np.real(np.fft.ifft(np.fft.fftshift(np.fft.fftshift(np.fft.fft(p0)) * win))) / cg +# %% def run(backend="python", device="cpu", quiet=True): """Run three simulations (no window, Hanning, Blackman). @@ -100,6 +103,7 @@ def run(backend="python", device="cpu", quiet=True): return results +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/pr_2D_FFT_line_sensor.py b/examples/pr_2D_FFT_line_sensor.py index 388f25c3..ef186e71 100644 --- a/examples/pr_2D_FFT_line_sensor.py +++ b/examples/pr_2D_FFT_line_sensor.py @@ -8,6 +8,7 @@ simulation is ported; the FFT-based reconstruction (kspaceLineRecon) is post-processing and is not included. """ +# %% import numpy as np from kwave.data import Vector @@ -20,6 +21,7 @@ from kwave.utils.mapgen import make_disc +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -57,6 +59,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run the forward simulation with a binary line sensor. @@ -88,6 +91,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/pr_3D_FFT_planar_sensor.py b/examples/pr_3D_FFT_planar_sensor.py index 9b20b29d..d4e1d1c6 100644 --- a/examples/pr_3D_FFT_planar_sensor.py +++ b/examples/pr_3D_FFT_planar_sensor.py @@ -11,6 +11,7 @@ Note: uses scale=1 (small grid). The DataCast 'single' option from MATLAB is not needed for the Python backend. """ +# %% import numpy as np from kwave.data import Vector @@ -23,6 +24,7 @@ from kwave.utils.mapgen import make_ball +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -65,6 +67,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run the forward simulation with a binary planar sensor. @@ -97,6 +100,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/sd_directional_array_elements.py b/examples/sd_directional_array_elements.py index fcb0e493..abbfcdb5 100644 --- a/examples/sd_directional_array_elements.py +++ b/examples/sd_directional_array_elements.py @@ -18,6 +18,7 @@ author: Ben Cox and Bradley Treeby date: 29th October 2010 """ +# %% import numpy as np from kwave.data import Vector @@ -30,6 +31,7 @@ from kwave.utils.mapgen import make_circle +# %% def setup(): """Set up simulation physics (grid, medium, source). @@ -69,6 +71,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run single simulation with a 13-element semicircular sensor array. @@ -164,6 +167,7 @@ def run(backend="python", device="cpu", quiet=True): } +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/sd_directivity_modelling_2D.py b/examples/sd_directivity_modelling_2D.py index 1f07ac27..5a89962f 100644 --- a/examples/sd_directivity_modelling_2D.py +++ b/examples/sd_directivity_modelling_2D.py @@ -16,6 +16,7 @@ author: Ben Cox and Bradley Treeby date: 28th October 2010 """ +# %% import numpy as np from kwave.data import Vector @@ -29,6 +30,7 @@ from kwave.utils.mapgen import make_cart_circle +# %% def setup(): """Set up simulation physics (grid, medium, source template). @@ -68,6 +70,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run 11 simulations (one per source angle) and return directivity data. @@ -140,6 +143,7 @@ def run(backend="python", device="cpu", quiet=True): } +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/sd_directivity_modelling_3D.py b/examples/sd_directivity_modelling_3D.py index 1678382c..d720802a 100644 --- a/examples/sd_directivity_modelling_3D.py +++ b/examples/sd_directivity_modelling_3D.py @@ -19,6 +19,7 @@ author: Ben Cox and Bradley Treeby date: 29th October 2010 """ +# %% import numpy as np from kwave.data import Vector @@ -32,6 +33,7 @@ from kwave.utils.mapgen import make_cart_circle +# %% def setup(): """Set up simulation physics (grid, medium, source template). @@ -66,6 +68,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run 11 simulations (one per source angle) and return directivity data. @@ -147,6 +150,7 @@ def run(backend="python", device="cpu", quiet=True): } +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/sd_focussed_detector_2D.py b/examples/sd_focussed_detector_2D.py index b2f65b6c..f2dc3ceb 100644 --- a/examples/sd_focussed_detector_2D.py +++ b/examples/sd_focussed_detector_2D.py @@ -12,6 +12,7 @@ author: Ben Cox and Bradley Treeby date: 20th January 2010 """ +# %% import numpy as np from kwave.data import Vector @@ -23,6 +24,7 @@ from kwave.utils.mapgen import make_circle, make_disc +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -60,6 +62,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run both on-focus and off-focus simulations with a binary arc sensor. @@ -135,6 +138,7 @@ def run(backend="python", device="cpu", quiet=True): } +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/sd_focussed_detector_3D.py b/examples/sd_focussed_detector_3D.py index 3764ebc6..79018f87 100644 --- a/examples/sd_focussed_detector_3D.py +++ b/examples/sd_focussed_detector_3D.py @@ -13,6 +13,7 @@ author: Ben Cox and Bradley Treeby date: 29th October 2010 """ +# %% import numpy as np from kwave.data import Vector @@ -25,6 +26,7 @@ from kwave.utils.mapgen import make_bowl +# %% def setup(): """Set up simulation physics (grid, medium, source). @@ -66,6 +68,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run on-axis and off-axis simulations with a concave bowl sensor. @@ -153,6 +156,7 @@ def run(backend="python", device="cpu", quiet=True): } +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/tvsp_3D_simulation.py b/examples/tvsp_3D_simulation.py index d25ea8ec..c67702f9 100644 --- a/examples/tvsp_3D_simulation.py +++ b/examples/tvsp_3D_simulation.py @@ -16,6 +16,7 @@ It builds on the Monopole Point Source and 3D IVP examples. """ +# %% import numpy as np from kwave.data import Vector @@ -27,6 +28,7 @@ from kwave.utils.filters import filter_time_series +# %% def setup(): """Set up simulation physics (grid, medium, source). @@ -89,6 +91,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a full-grid binary sensor recording p_final. @@ -117,6 +120,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/tvsp_doppler_effect.py b/examples/tvsp_doppler_effect.py index d02c98eb..c618f277 100644 --- a/examples/tvsp_doppler_effect.py +++ b/examples/tvsp_doppler_effect.py @@ -16,6 +16,7 @@ It builds on the Monopole Point Source In A Homogeneous Propagation Medium Example. """ +# %% import numpy as np from kwave.data import Vector @@ -27,6 +28,7 @@ from kwave.utils.filters import filter_time_series +# %% def setup(): """Set up simulation physics (grid, medium, source). @@ -123,6 +125,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a single-point sensor and full-grid for p_final. @@ -160,6 +163,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/tvsp_homogeneous_medium_dipole.py b/examples/tvsp_homogeneous_medium_dipole.py index 3f318e19..78a0eaaa 100644 --- a/examples/tvsp_homogeneous_medium_dipole.py +++ b/examples/tvsp_homogeneous_medium_dipole.py @@ -10,6 +10,7 @@ radiation pattern. A single-point sensor records the resulting pressure and the final pressure field. """ +# %% import numpy as np from kwave.data import Vector @@ -21,6 +22,7 @@ from kwave.utils.filters import filter_time_series +# %% def setup(): """Set up simulation physics. Returns (kgrid, medium, source).""" @@ -60,6 +62,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with original sensor from MATLAB example.""" kgrid, medium, source = setup() @@ -83,6 +86,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/tvsp_homogeneous_medium_monopole.py b/examples/tvsp_homogeneous_medium_monopole.py index 051d9919..5da100bc 100644 --- a/examples/tvsp_homogeneous_medium_monopole.py +++ b/examples/tvsp_homogeneous_medium_monopole.py @@ -12,6 +12,7 @@ This is the first time-varying source example and builds on the initial value problem demonstrations. """ +# %% import numpy as np from kwave.data import Vector @@ -23,6 +24,7 @@ from kwave.utils.filters import filter_time_series +# %% def setup(): """Set up simulation physics (grid, medium, source). @@ -70,6 +72,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with the original single-point sensor from the MATLAB example. @@ -100,6 +103,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/tvsp_snells_law.py b/examples/tvsp_snells_law.py index 90690cb0..1d552bf0 100644 --- a/examples/tvsp_snells_law.py +++ b/examples/tvsp_snells_law.py @@ -10,6 +10,7 @@ You should observe the transmitted beam refracting according to Snell's law at the interface, and the reflected beam at the complementary angle. """ +# %% import numpy as np from kwave.data import Vector @@ -21,6 +22,7 @@ from kwave.utils.signals import tone_burst +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -86,6 +88,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run with a full-grid sensor recording p_final. @@ -112,6 +115,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/examples/tvsp_steering_linear_array.py b/examples/tvsp_steering_linear_array.py index 5629e9ae..754a68c1 100644 --- a/examples/tvsp_steering_linear_array.py +++ b/examples/tvsp_steering_linear_array.py @@ -12,6 +12,7 @@ Here we record p and p_final on a full binary grid so the result can be compared quantitatively with the MATLAB reference. """ +# %% import numpy as np from kwave.data import Vector @@ -23,6 +24,7 @@ from kwave.utils.signals import tone_burst +# %% def setup(): """Set up the simulation physics (grid, medium, source). @@ -84,6 +86,7 @@ def setup(): return kgrid, medium, source +# %% def run(backend="python", device="cpu", quiet=True): """Run the simulation with a full-grid binary sensor. @@ -109,6 +112,7 @@ def run(backend="python", device="cpu", quiet=True): ) +# %% if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/plans/release-strategy.md b/plans/release-strategy.md index a9d3b279..ae1dd857 100644 --- a/plans/release-strategy.md +++ b/plans/release-strategy.md @@ -11,8 +11,9 @@ This release strategy brings the unified solver architecture to fruition. | **0.5.0** | Finalize master/main | Stabilize current codebase | | **0.6.0** | Python Solver + Unified API + Deprecation | Python solver, `kspaceFirstOrder()` kwargs, Future warnings | | **0.6.1** | C-order Migration (Helpers) | Migrate utils/helpers from F-order to C-order, keep legacy API working | -| **0.6.2** | Example Migration | Port remaining examples to new `kspaceFirstOrder()` API | -| **0.6.3** | Axisymmetric Support | Axisymmetric solver in new API, port AS examples | +| **0.6.2** | Example Restructure & Docs Gallery | Flatten examples, jupytext notebooks, docs gallery, CuPy validation | +| **0.6.3** | Tier 2 Features + Examples | Time-reversal, rect sensors, sound_speed_ref, port Tier 2 examples | +| **0.6.4** | Axisymmetric Support | Axisymmetric solver in new API, port AS examples | | **0.7.0** | CLI (`kwp`) | Command-line interface for running simulations | | **1.0.0** | Clean Release | Remove deprecated code. Simple, readable, fast. | | **2.0.0** | Performance & Scale | nanobind CUDA, MPI, Devito, multi-GPU | @@ -234,23 +235,59 @@ Atomic migration of all solver internals from Fortran-order to C-order. The `ksp **Unchanged (by design):** `matlab.py` (F-order boundary), `mapgen.py` (geometry), `kgrid.py` (MATLAB inputs), `cpp_simulation._write_hdf5()` (C++ binary format), legacy `kspaceFirstOrder2D/3D`. -### v0.6.2 — Example Porting - -Port remaining examples from legacy `kspaceFirstOrder2D/3D` to unified `kspaceFirstOrder()`. Strategy: MATLAB reference → k-wave-cupy validation → standalone Python port → CI fixture. - -**Current status (port-examples branch):** 29 of 75 examples ported (all Tier 1). 70 parity tests pass, 2 skipped (3D axis mismatch). See `docs/porting-plan.md` for full breakdown. - -**CI simplification steps:** -1. **Bundle small test assets in the repo** — images like `EXAMPLE_source_one.png` (332B) and `EXAMPLE_source_two.bmp` (32KB) already live in `tests/`. Examples now look there first. -2. **Pre-generate MATLAB references offline** — `.mat` files (totalling ~2.5GB) are too large for git. Generate them locally with MATLAB, upload as GitHub release assets or cache artifacts. -3. **CI test tiers:** - - **Tier 1 (every PR):** `test_native_solver.py` + old C++ API tests — no MATLAB needed, ~2s - - **Tier 2 (nightly/manual):** `test_example_parity.py` — downloads pre-generated `.mat` refs from release assets, ~30s - - **Tier 3 (release):** Full MATLAB regeneration + parity — requires MATLAB runner -4. **Register pytest markers** — `matlab_parity` marker registered in `pytest.ini` for selective runs: `pytest -m "not matlab_parity"` skips parity tests cleanly. -5. **Graceful skip on missing assets** — parity tests skip (not error) when `.mat` refs or image files are absent. - -### v0.6.3 — Axisymmetric Support +### v0.6.2 — Example Restructure & Docs Gallery + +**Status: in progress (restructure-examples branch, PR #686)** + +Flatten example directory, add jupytext notebook generation, build docs gallery. + +**Done:** +- 29 Tier 1 examples ported to `setup()/run()/__main__` pattern with `kspaceFirstOrder()` +- Flattened `examples/ported/` → `examples/`, dropped `example_` prefix +- Old subdirectory examples moved to `examples/legacy/` +- Test file parametrized: 15 classes → table-driven (550 → 290 lines), 35 pass / 2 skip +- Separate `p_thresh` / `p_final_thresh` per example (machine precision for time-series) +- `jupytext.toml` + `generate-notebooks.yml` — auto-generates `.ipynb` on merge to master +- `run-examples.yml` triggers on PRs touching `examples/`, excludes `legacy/` +- Deleted dead `test_example.yml` + +**Remaining for v0.6.2:** +1. **Docs example gallery** — Add nbsphinx or sphinx-gallery to docs build; render generated notebooks as HTML pages with "Open in Colab" badges. Wire into `test_pages.yml`. +2. **8 more parity tests** — ivp_saving_movie_files, na_optimising_performance, na_source_smoothing, pr_2D_FFT_line_sensor, pr_3D_FFT_planar_sensor, sd_directional_array_elements, sd_directivity_modelling_2D/3D +3. **Investigate 3D p_final mismatch** — 2 skipped tests (likely p0 smoothing or medium mapping difference) +4. **CuPy GPU validation** — Run all 29 examples on DigitalOcean GPU droplet, verify NumPy↔CuPy parity +5. **Real-world validation** — Run representative simulations from published papers or user workflows to catch edge cases not covered by toy examples + +**CI test tiers (implemented):** +- **Tier 1 (every PR):** `test_native_solver.py` + old C++ API tests — no MATLAB needed, ~2s +- **Tier 2 (every PR):** `test_example_parity.py` — skips gracefully when `.mat` refs absent +- **Tier 3 (weekly + PR):** `run-examples.yml` — runs all examples end-to-end +- **`matlab_parity` marker** registered in `pytest.ini` for selective runs + +### v0.6.3 — New Features + Updated Examples + +**Goal:** Add solver features needed by Tier 2 examples, port those examples, update docs gallery. + +**Features to add:** +- **Time-reversal reconstruction** — needed by 9 PR examples (`pr_2D_TR_*`, `pr_3D_TR_*`) +- **Rectangular/corner sensor masks** — needed by 5 examples +- **`sound_speed_ref`** — needed by `tvsp_slit_diffraction` +- **Frequency-response sensor** — needed by `ivp_sensor_frequency_response` +- **Directional sensor** — needed by `sd_sensor_directivity_2D` + +**Per-feature workflow:** +1. Implement feature in solver (`kspace_solver.py`) +2. Port the MATLAB examples that need it +3. Generate MATLAB references, add parity tests +4. Update docs gallery with new examples + +**Simplification targets:** +- Delete `examples/legacy/` once all examples are ported or confirmed obsolete +- Consolidate `tests/integration/` and `tests/test_example_parity.py` test infrastructure +- Remove unused MATLAB collector infrastructure if parity tests replace it +- Audit `kWaveSimulation_helper/` — delete helpers superseded by `kspaceFirstOrder()` + +### v0.6.4 — Axisymmetric Support Axisymmetric = dimensionality reduction (3D→2D or 2D→1D). Not a separate solver — wrapper around `kspaceFirstOrder()` with radial symmetry terms added to the wave equation.