Skip to content

Expand interp cart grid testing#574

Open
djps wants to merge 92 commits into
waltsims:masterfrom
djps:expand_interp_cart_grid_testing
Open

Expand interp cart grid testing#574
djps wants to merge 92 commits into
waltsims:masterfrom
djps:expand_interp_cart_grid_testing

Conversation

@djps
Copy link
Copy Markdown
Collaborator

@djps djps commented Mar 20, 2025

This extends the tests for ìnterp_cart_grid` so that different sensor data is stored at different positions.

Greptile Summary

This PR expands test coverage for interp_cart_data and grid2cart by introducing phase-varied sensor data (so each sensor position carries a distinct signal), fixing the MATLAB 2D reference collector to use makeCartCircle instead of the wrong makeCartSphere, rewriting the broken linear interpolation branch with a proper projection-based approach, and adding a new 2D circular-sensor time-reversal example.

  • kwave/utils/conversion.py — fixed the always-false 0 <= dim > 3 dimension guard and added MATLAB-compatible Fortran-order sorting to grid2cart, aligning cart_data and order_index with MATLAB's output.
  • kwave/utils/interp.py — replaced the completely broken linear path (which called np.append non-in-place) with a correct projection-based two-point interpolation and added @typechecker with jaxtyping annotations.
  • New MATLAB-comparison tests and expanded unit tests add edge-case coverage for 1-point and 2-point nearest-neighbor cases and dimension-mismatch guards; the linear interpolation test is present but its assertion is commented out.

Confidence Score: 3/5

The core library changes look correct and the CI infrastructure regenerates MATLAB reference data automatically, but two concrete gaps remain before merging.

The test_interp_cart_data_2_points_linear test runs but asserts nothing — the key assertion is commented out — so the newly rewritten linear interpolation path has no automated correctness check. Separately, pr_2D_circular_sensor.py hardcodes is_gpu_simulation=True while its companion notebook correctly uses False, meaning the example script will crash on any CPU-only machine.

tests/test_utils.py (commented-out assertion in the linear test) and examples/pr_2D_circular_sensor/pr_2D_circular_sensor.py (is_gpu_simulation=True).

Important Files Changed

Filename Overview
kwave/utils/interp.py Added type annotations and @typechecker to interp_cart_data; rewrote the broken linear interpolation using projection math; added dimension-consistency validation; linear fallback to nearest-neighbor is silent.
kwave/utils/conversion.py Fixed the buggy 0 <= dim > 3 guard (was always False for valid dims); added MATLAB Fortran-order sorting so cart_data and order_index now match MATLAB's grid2cart output ordering.
tests/test_utils.py New unit tests for grid2cart and interp_cart_data edge cases; test_interp_cart_data_2_points_linear has a commented-out assertion and print statements remain in test bodies.
tests/matlab_test_data_collectors/matlab_collectors/collect_interpCartData.m Fixed 2D case to use makeCartCircle instead of the incorrect makeCartSphere; introduced phase-shifted sinusoid sensor data so different sensors carry different signals; linear test case remains commented out.
tests/matlab_test_data_collectors/python_testers/test_grid2cart.py New MATLAB-comparison test for grid2cart; validates cart coordinates and flat Fortran-order indices against MATLAB reference; depends on CI-generated collectedValues/grid2cart.mat.
examples/pr_2D_circular_sensor/pr_2D_circular_sensor.py New 2D circular sensor time-reversal example Python script; is_gpu_simulation=True will fail on CPU-only machines, unlike the companion notebook which correctly defaults to False.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["interp_cart_data(kgrid, cart_sensor_data,\ncart_sensor_mask, binary_sensor_mask, interp)"] --> B{dim in 2 or 3?}
    B -- No --> ERR1["ValueError: must be 2D or 3D"]
    B -- Yes --> C{cart mask shape matches kgrid.dim?}
    C -- No --> ERR2["ValueError: dimension mismatch"]
    C -- Yes --> D{binary mask ndim matches kgrid.dim?}
    D -- No --> ERR3["ValueError: binary mask dim mismatch"]
    D -- Yes --> E["grid2cart - cart_bsm now Fortran-order sorted"]
    E --> F["Loop over binary sensor points"]
    F --> G{interp mode}
    G -- nearest --> H["argmin dist - copy cart row"]
    G -- linear --> I{n_cart_points ge 2?}
    I -- No --> ERR4["ValueError: not enough points"]
    I -- Yes --> J["sort by dist, get p1 and p2"]
    J --> K{project target onto p1 to p2 segment}
    K -- 0 le c1 le 1 --> L["Linear blend result"]
    K -- outside segment --> M["Nearest-neighbor fallback silent"]
    G -- other --> ERR5["ValueError: unknown interp"]
    H --> OUT["binary_sensor_data"]
    L --> OUT
    M --> OUT
Loading

Reviews (1): Last reviewed commit: "Merge branch 'master' into expand_interp..." | Re-trigger Greptile

Greptile also left 4 inline comments on this PR.

@waltsims
Copy link
Copy Markdown
Owner

Related to #572

@waltsims waltsims force-pushed the expand_interp_cart_grid_testing branch from e51338a to 290c1d3 Compare March 28, 2025 03:14
@waltsims waltsims force-pushed the expand_interp_cart_grid_testing branch from 1cedec5 to 8974a47 Compare March 28, 2025 04:38
@codecov

This comment was marked as outdated.

@djps
Copy link
Copy Markdown
Collaborator Author

djps commented May 6, 2025

I have no idea why ruff/isort is not working, although it doesn't matter too much.

Have have increased the test coverage for interp_cart_data,

@djps djps closed this May 6, 2025
@djps djps reopened this May 6, 2025
@djps
Copy link
Copy Markdown
Collaborator Author

djps commented May 6, 2025

ok, i have absolutely no idea why ruff/isort fails and although it is not important, i would like to know why.

As for the test coverage - I am not sure where the motifications of interp_cart_data come from, but there are tests which help maintain the code coverage but don't actually ensure the code is tested, for example test_interp_cart_data_2_points_linear in test_utils.py. I had put a pytest.skip pragma in to stop them running as it is gives a false impression.

@waltsims
Copy link
Copy Markdown
Owner

@greptile-app

Comment thread tests/test_utils.py
Comment on lines +49 to +59
def test_interp_cart_data_2_points_linear():
kgrid = kWaveGrid([1000, 100, 10], [1, 1, 1])
binary_sensor_mask = np.zeros((1000, 100, 10), dtype=bool)
binary_sensor_mask[501, 51, 7] = True
cart_sensor_mask = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 2.0]], dtype=np.float32).T # sensor at the origin and another point
cart_sensor_data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=np.float32) # 3 time steps
logging.debug(cart_sensor_data)
interp_data = interp_cart_data(kgrid, cart_sensor_data, cart_sensor_mask, binary_sensor_mask, "linear")
# TODO: find expected value from matlab. In this case we revert to nearest because point is not between p1 and p2.
logging.debug(interp_data)
# assert np.allclose(interp_data, cart_sensor_data), "not close enough"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Linear interpolation test makes no assertions

test_interp_cart_data_2_points_linear calls interp_cart_data with interp="linear" but the only assertion (assert np.allclose(...)) is commented out with a TODO. The test runs without verifying any result — it will never fail even if the linear interpolation is completely wrong. Since the stated goal of this PR is to expand testing, this should either be completed with a concrete expected value or the test should be marked @pytest.mark.skip with an explanation so it is clearly not a passing validation.

Comment on lines +94 to +97
execution_options = SimulationExecutionOptions(
is_gpu_simulation=True,
delete_data=False,
verbose_level=2)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 is_gpu_simulation=True in the standalone Python script but the companion notebook sets it to False. Running this file on any machine without a CUDA GPU will fail at runtime. The notebook's default is the safer choice for a portable example.

Suggested change
execution_options = SimulationExecutionOptions(
is_gpu_simulation=True,
delete_data=False,
verbose_level=2)
execution_options = SimulationExecutionOptions(
is_gpu_simulation=False,
delete_data=False,
verbose_level=2)

Comment thread tests/test_utils.py
Comment on lines +69 to +78
cart_sensor_data0 = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=np.float32) # 3 time steps, shape: (2,3)
print(cart_sensor_data0)
interp_data0 = interp_cart_data(kgrid, cart_sensor_data0, cart_sensor_mask, binary_sensor_mask)
print(interp_data0)
# TODO: find expected value from matlab, current behavior is round up to nearest neighbor
assert np.allclose(interp_data0, cart_sensor_data0), "not close enough"
cart_sensor_data1 = np.array([[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0]], dtype=np.float32) # 5 time steps, shape: (2,5)
print(cart_sensor_data1)
interp_data1 = interp_cart_data(kgrid, cart_sensor_data1, cart_sensor_mask, binary_sensor_mask)
print(interp_data1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 print() calls in test code pollute captured output and make pytest -s noisy. Use logging.debug() to keep output clean.

Suggested change
cart_sensor_data0 = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=np.float32) # 3 time steps, shape: (2,3)
print(cart_sensor_data0)
interp_data0 = interp_cart_data(kgrid, cart_sensor_data0, cart_sensor_mask, binary_sensor_mask)
print(interp_data0)
# TODO: find expected value from matlab, current behavior is round up to nearest neighbor
assert np.allclose(interp_data0, cart_sensor_data0), "not close enough"
cart_sensor_data1 = np.array([[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0]], dtype=np.float32) # 5 time steps, shape: (2,5)
print(cart_sensor_data1)
interp_data1 = interp_cart_data(kgrid, cart_sensor_data1, cart_sensor_mask, binary_sensor_mask)
print(interp_data1)
cart_sensor_data0 = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=np.float32) # 3 time steps, shape: (2,3)
logging.debug(cart_sensor_data0)
interp_data0 = interp_cart_data(kgrid, cart_sensor_data0, cart_sensor_mask, binary_sensor_mask)
logging.debug(interp_data0)
# TODO: find expected value from matlab, current behavior is round up to nearest neighbor
assert np.allclose(interp_data0, cart_sensor_data0), "not close enough"
cart_sensor_data1 = np.array([[1.0, 2.0, 3.0, 4.0, 5.0], [6.0, 7.0, 8.0, 9.0, 10.0]], dtype=np.float32) # 5 time steps, shape: (2,5)
logging.debug(cart_sensor_data1)
interp_data1 = interp_cart_data(kgrid, cart_sensor_data1, cart_sensor_mask, binary_sensor_mask)
logging.debug(interp_data1)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread kwave/utils/interp.py
Comment on lines +310 to +316
if 0 <= c1 <= 1:
# Linear interpolation
binary_sensor_data[point_index, :] = (1 - c1) * cart_sensor_data[indices[0], :] + c1 * cart_sensor_data[indices[1], :]
else:
# Point is not between p1 and p2
# Option 1: Use nearest neighbor
binary_sensor_data[point_index, :] = cart_sensor_data[indices[0], :]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent fallback to nearest-neighbor in linear mode

When the target binary-sensor point projects outside the segment between the two nearest Cartesian points (c1 < 0 or c1 > 1), the code silently uses nearest-neighbour without any log message. A caller who explicitly requested interp="linear" will receive nearest-neighbour results with no indication. At minimum, logging.debug (or logging.warning) should be emitted so users can diagnose unexpected interpolation behaviour.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants