Migrate solver internals from F-order to C-order#676
Merged
Conversation
Solver internals (kspace_solver.py): all flatten(order="F")/reshape(..., order="F") converted to ravel()/reshape(). C-order strides for bilinear interpolation. C++ backend (cpp_simulation.py): added _fix_output_order() to transpose full-grid fields and permute sensor time-series rows from F-indexed to C-indexed. Entry point (kspaceFirstOrder.py): added _reshape_sensor_to_grid() for full-grid sensors — returns (Nt, *grid_shape) instead of (n_sensor, Nt). Removed order="F" from smooth_p0 reshape. Public APIs: cart2grid, combine_sensor_data, get_distributed_source_signal now accept order= param with FutureWarning when defaulting to "F". Tests: updated shape assertions for new output format. Integration test helper _to_matlab_shape() converts C-order output back to MATLAB F-flat for comparison. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e double ravel - _FULL_GRID_SUFFIXES: both kspaceFirstOrder.py and cpp_simulation.py now have the full 7-entry tuple including _*_all variants. Fixes latent bug where _strip_pml would not crop C++ _max_all/_min_all/_rms_all fields. - cpp_simulation._fix_output_order: remove ambiguous `n_sensor in val.shape` check — C++ always outputs (n_sensor, Nt), only check axis 0. - kwave_array.py: cache ravel result in loop instead of computing twice per element in get_distributed_source_signal and combine_sensor_data. - conftest._to_matlab_shape: vectorize with moveaxis+reshape instead of Python for-loop over time steps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fdc57d8 to
6c3eda6
Compare
Owner
Author
|
@greptile-app |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #676 +/- ##
==========================================
+ Coverage 74.09% 74.23% +0.14%
==========================================
Files 56 56
Lines 7926 8012 +86
Branches 1543 1566 +23
==========================================
+ Hits 5873 5948 +75
- Misses 1435 1441 +6
- Partials 618 623 +5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Unit tests for _fix_output_order (CppSimulation), _reshape_sensor_to_grid, and FutureWarning assertions for cart2grid, combine_sensor_data, and get_distributed_source_signal. Covers the uncovered diff paths that caused codecov/patch to fail (60% → target 74%). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Handle the case where Python returns (Nt, N) and MATLAB expects (N, Nt) — both are 2D arrays, so the ndim >= 3 check missed it. Flagged by Greptile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep the simplest possible output contract: all time-series are (n_sensor, Nt) with sensor points in C-flattened order. Aggregates are (n_sensor,). No automatic reshaping. Users who want spatial structure call reshape_to_grid(data, grid_shape) which converts (n_sensor, Nt) → (*grid_shape, Nt). Removes _reshape_sensor_to_grid and all the time-first reshaping logic. Integration tests use _c_to_f_reorder to compare C-flat Python output against F-flat MATLAB references. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Owner
Author
|
@greptile-app |
…arning sentinel P1: simulate_from_dicts now reorders multi-row source signals from MATLAB's F-flat mask ordering to C-flat before passing to the solver. Previously this was documented but unguarded — callers with multi-source signals would get wrong physics. P2: FutureWarning in cart2grid, combine_sensor_data, get_distributed_source_signal now uses a sentinel default so explicit order="F" does NOT warn. Only implicit (unspecified) default triggers the deprecation warning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Owner
Author
|
@greptile-app |
Tests: 1D noop, 2D/3D multi-source reordering, single-source noop, uniform broadcast noop, missing mask, scalar mask, velocity sources. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Owner
Author
|
@greptile-app |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The new kspaceFirstOrder() API now returns C-ordered sensor data:
Core changes:
Greptile Summary
This PR performs a comprehensive migration of all solver internals from Fortran (column-major) to C (row-major) array ordering. The native Python solver (
kspace_solver.py) now uses plainravel()/reshape()throughout; the C++ backend path gains a new_fix_output_orderpost-processing step that transposes full-grid HDF5 fields and permutes sensor rows from F-indexed to C-indexed order. Two deprecation-guarded API changes (cart2gridandkWaveArraymethods) provide a migration path for callers that relied on F-order outputs. A newreshape_to_gridconvenience helper and extensive unit tests round out the change.\n\nKey points:\n- The bilinear interpolation strides are correctly recomputed for C-order vianp.cumprod([1] + list(self.grid_shape[:0:-1]))[::-1].\n- The F→C permutation logic (searchsorted on F-flat nonzero indices) is consistent across_fix_output_order(output) and_f_to_c_source_reorder(MATLAB input).\n-plans/release-strategy.mddocuments the new helper with the wrong name (_reshape_sensor_to_gridinstead ofreshape_to_grid) and wrong dimension order ((Nt, *grid_shape)instead of(*grid_shape, Nt)).\n-simulate_from_dictsbuildsgrid_shapewithout theint()cast thatcreate_simulationapplies, which may break MATLAB interop callers using rawscipy.io.loadmatoutput.Confidence Score: 5/5
Safe to merge — all remaining findings are P2 documentation/style issues that do not affect correctness for the primary Python solver path.
The core F→C migration logic is well-tested, the strides computation is mathematically correct, and the permutation approach is consistent between input (MATLAB interop source reorder) and output (_fix_output_order). The two open findings are: a stale/incorrect description in an internal planning document, and a missing int() cast in the MATLAB interop path that only matters for legacy callers using raw loadmat output.
kwave/solvers/kspace_solver.py (simulate_from_dicts int cast), plans/release-strategy.md (wrong function name/shape)
Important Files Changed
reshape_to_gridhelper, updates_FULL_GRID_SUFFIXES, switches source.p0 reshape and cart2grid to C-order. Logic is correct; docstring clearly states (n_sensor, Nt) → (*grid_shape, Nt) shape contract._fix_output_ordermethod transposes full-grid fields and reorders sensor rows from F-indexed to C-indexed using a searchsorted permutation. Shape-based disambiguation for n_sensor==Nt ambiguity is still present (flagged in previous review thread).simulate_from_dictsbuilds grid_shape without explicit int() conversion, unlike create_simulation which uses int(N) explicitly.orderparameter with a FutureWarning sentinel, defaulting to "F" for backward compat. reorder_index extraction now uses the passed order parameter correctly._reshape_sensor_to_grid()with(Nt, *grid_shape)output, but the implemented function isreshape_to_grid()returning(*grid_shape, Nt)—both the name and dimension order are wrong.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A["kspaceFirstOrder() call"] --> B{Backend?} B -->|python| C["kspace_solver.Simulation\n(C-order internally)"] B -->|cpp| D["CppSimulation._write_hdf5\n(F-order — unchanged)"] C --> E["_extract: f.ravel()\n(C-flat sensor indices)"] E --> F["result: (n_sensor, Nt)\nC-flat row order"] D --> G["C++ binary runs\nwrites F-order HDF5"] G --> H["_parse_output\n(h5py reads reversed dims)"] H --> I["_fix_output_order"] I --> I1["Step 1: transpose full-grid fields\n(Ny,Nx) → (Nx,Ny)"] I --> I2["Step 2: permute sensor rows\nF-indexed → C-indexed"] I1 & I2 --> F F --> J{Full binary mask?} J -->|No| K["Return (n_sensor, Nt)\nC-flat order"] J -->|Yes| L["reshape_to_grid(data, grid_shape)"] L --> M["Returns (*grid_shape, Nt)"] subgraph MATLAB_interop ["MATLAB interop (simulate_from_dicts)"] N["_f_to_c_source_reorder\nsource rows: F-flat → C-flat"] --> A endComments Outside Diff (3)
tests/test_cross_backend.py, line 1024 (link)get_distributed_source_signalis called withoutorder='C', so it defaults toorder='F'and emits aFutureWarning. More critically, the distributed source rows are indexed by F-flattened mask positions, but the new Python solver's_build_source_opassigns rowito the i-th C-flattened active mask position. For a 3D disc element with spatially-varying BLI weights, this assigns the wrong weight to each source grid point.The test only checks that backends agree with each other (
_assert_p_close), not against a reference, so it will pass even though the simulated physics are wrong.tests/test_cross_backend.py, line 854 (link)("python", "gpu")entry inCOMBOShas no corresponding skip guard in_collect_collectskips the CPP backend whenkgrid.dim < 2, but there is no equivalent guard for("python", "gpu")when CuPy is unavailable._availablereturnsFalsein that case, so the pair is silently dropped — which is fine. However, if only one backend is available,_assert_p_closeraises:This makes CI results non-deterministic depending on which extras are installed. Consider adding a
pytest.skipcall insidetest_all_combos_agreewhen fewer than two combos are actually runnable.kwave/solvers/kspace_solver.py, line 782-792 (link)simulate_from_dictssilently produces wrong results for multi-source MATLAB callsThe new docstring correctly identifies that MATLAB sends source signal rows in F-flattened order, but the function still just delegates to
create_simulation(...).run()without any reordering. Any existing MATLAB caller that provides a multi-row source signal (i.e., multiple source points, each with its own time series) will now silently receive incorrectly scrambled simulation output — the source values will be injected at the wrong spatial locations.Since
simulate_from_dictsis explicitly the MATLAB interop entry point, callers cannot be expected to perform shim-layer reordering themselves without a breaking API change or at least a loud error. Options:ValueError/NotImplementedErrorguard forn_src > 1until the shim is updated.matlab_compat=Falseflag and do the F→C row reordering here.Leaving it as a silent correctness break for all multi-point MATLAB source users is a regression that is hard to diagnose.
Reviews (5): Last reviewed commit: "Shallow-copy source dict in _f_to_c_sour..." | Re-trigger Greptile