Skip to content

Consolidate cartesian interp option and introduce structured SimulationResult return#647

Open
faridyagubbayli wants to merge 6 commits into
masterfrom
chore/minor-code-improvements
Open

Consolidate cartesian interp option and introduce structured SimulationResult return#647
faridyagubbayli wants to merge 6 commits into
masterfrom
chore/minor-code-improvements

Conversation

@faridyagubbayli
Copy link
Copy Markdown
Collaborator

@faridyagubbayli faridyagubbayli commented Oct 30, 2025

I removed the duplicate cart_interp option and kept cartesian_interp to make the API clear and consistent, then switched the executor return from a loose dotdict to a typed SimulationResult. This cuts confusion around options, improves IDE help and readability, and makes it safer and simpler to work with outputs from the k‑Wave binaries.

Changes

  • kwave/options/simulation_options.py: removed cart_interp, kept cartesian_interp, cleaned docs/validation.
  • tests/test_tvsp_3D_simulation.py, tests/test_ivp_3D_simulation.py: switched to cartesian_interp.
  • kwave/data.py: added SimulationResult dataclass (+ from_dotdict).
  • kwave/executor.py: return SimulationResult from run_simulation.
  • kwave/kspaceFirstOrder2D.py, kwave/kspaceFirstOrder3D.py, kwave/kspaceFirstOrderAS.py: updated return annotations/imports.
  • tests/test_executor.py: assertions now expect SimulationResult.

Compatibility

  • Non-breaking at call sites; return type is now structured. Consumers relying on dict-like access should migrate to attributes.

Tests

  • Updated unit tests pass locally; basic import/creation of SimulationResult verified.

Summary by CodeRabbit

  • Breaking Changes

    • SimulationOptions parameter renamed from cart_interp to cartesian_interp (update call sites).
  • New Features

    • Simulation solvers now return a structured SimulationResult object consolidating grid info and arrays for pressure, velocity, and intensity.
  • Tests

    • Tests updated to expect and validate the new structured SimulationResult outputs.

Greptile Summary

This PR consolidates the duplicate cart_interp/cartesian_interp option (keeping only cartesian_interp) and replaces the untyped dotdict return from Executor.run_simulation with a structured SimulationResult dataclass that provides IDE completion and typed access to all sensor output fields.

  • SimulationResult is introduced in kwave/data.py with from_dotdict() that translates binary HDF5 dataset names (including the inverted velocity naming convention where binary u = staggered, binary u_non_staggered = collocated) to a consistent user-facing API, plus backward-compat __getitem__/__contains__ methods.
  • Executor.run_simulation now calls from_dotdict after optionally cropping the PML, and the three kspaceFirstOrder* wrapper modules update their return annotations to Optional[SimulationResult].
  • Tests are updated to assert isinstance(sensor_data, SimulationResult) and to swap cart_interp for cartesian_interp.

Confidence Score: 3/5

Not safe to merge yet — velocity component fields recorded through the binary backend will silently resolve to None, and existing callers using cart_interp will get a TypeError at runtime without a migration path.

Two defects in the core data path remain unresolved from earlier review rounds. First, from_dotdict looks up combined keys (u_final, u_max_all, u_min_all) while the C++ binary writes per-component HDF5 datasets (ux_final/uy_final/uz_final, ux_max_all/uy_max_all/uz_max_all, etc.) — every user recording velocity _final or _all fields gets silently None in the result. Second, cart_interp was hard-deleted without a deprecation shim, breaking all existing call sites that pass it as a keyword argument.

kwave/data.py (from_dotdict velocity key mappings) and kwave/options/simulation_options.py (cart_interp removal without backward-compat shim)

Important Files Changed

Filename Overview
kwave/data.py Introduces SimulationResult dataclass with from_dotdict factory; contains/getitem use hasattr (too broad), and the u_max_all/u_min_all/u_final lookups use combined keys that don't match the binary's per-component HDF5 dataset names.
kwave/executor.py run_simulation now returns SimulationResult.from_dotdict(sensor_data) after optional PML crop; _crop_pml correctly modifies the dotdict before from_dotdict is called, but velocity component fields (ux_final, etc.) are lost during translation.
kwave/options/simulation_options.py Removes cart_interp field entirely (hard breaking change for existing callers); cartesian_interp is kept and validation is unchanged.
tests/test_executor.py Switches from MagicMock dict to real dict, adds SimulationResult isinstance assertions, and correctly isolates tracking mocks for p_final/p_max_all crop verification.
kwave/kspaceFirstOrder3D.py Return annotations updated to Optional[SimulationResult]; unused deprecated import removed; no functional changes.
kwave/kspaceFirstOrder2D.py Return annotations updated to Optional[SimulationResult]; import added; no functional changes.
kwave/kspaceFirstOrderAS.py Return annotations updated to Optional[SimulationResult]; two adjacent string literals concatenated into single strings; no functional changes.
tests/test_ivp_3D_simulation.py Mechanical rename of cart_interp to cartesian_interp; docstring indentation corrected.
tests/test_tvsp_3D_simulation.py Mechanical rename of cart_interp to cartesian_interp; docstring indentation corrected.

Reviews (4): Last reviewed commit: "SimulationResult: align field names with..." | Re-trigger Greptile

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 30, 2025

Walkthrough

Adds a new public data type SimulationResult for kWave simulation outputs, updates simulation entry points and executor to return it, removes the cart_interp option in favor of cartesian_interp, and updates tests to expect SimulationResult instances.

Changes

Cohort / File(s) Summary
New Data Structure
kwave/data.py
Adds SimulationResult class with grid metadata and optional per-field arrays, plus from_dotdict() constructor and dictionary-like methods (__getitem__, __contains__) for compatibility.
Executor & Return Types
kwave/executor.py, kwave/kspaceFirstOrder2D.py, kwave/kspaceFirstOrder3D.py, kwave/kspaceFirstOrderAS.py
Imports SimulationResult; updates function/method return annotations to Optional[SimulationResult]; executor.run_simulation now returns SimulationResult.from_dotdict(sensor_data).
Options Cleanup
kwave/options/simulation_options.py
Removes the public SimulationOptions.cart_interp field (retains cartesian_interp).
Tests — executor
tests/test_executor.py
Replaces mock dict usage and updates assertions to expect SimulationResult instances; adjusts cropping-related mocks and checks.
Tests — simulation usage
tests/test_ivp_3D_simulation.py, tests/test_tvsp_3D_simulation.py
Updates test code to pass cartesian_interp instead of removed cart_interp.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant Executor
  participant NativeSim as "Native Simulation (C/engine)"
  participant SimResult as "SimulationResult.from_dotdict"

  Caller->>Executor: run_simulation(input, output, options)
  Executor->>NativeSim: invoke simulation with files/options
  NativeSim-->>Executor: sensor_data (dict)
  Note right of Executor: previously returned raw dict/dotdict
  Executor->>SimResult: SimulationResult.from_dotdict(sensor_data)
  SimResult-->>Executor: SimulationResult instance
  Executor-->>Caller: SimulationResult
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Areas needing extra attention:
    • SimulationResult.from_dotdict() mapping completeness (all sensor keys, shapes, None handling)
    • Backward-compatibility of __getitem__ / __contains__ behavior vs. previous dotdict access patterns
    • Consistency of updated return annotations across 2D/3D/AS modules and any callers
    • Tests that mock / inspect sensor arrays (cropping behavior) to ensure mocks reflect new type semantics
    • Removal of cart_interp: check for any remaining references across codebase or docs

Poem

🐰 I stitched the bytes into a tidy chest,
Fields aligned, no arrays in unrest.
from_dotdict hummed a gentle tune,
Old dict ways still hop in the moon—
A neat result, hugged by a rabbit's zest. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Consolidate cartesian interp option and introduce structured SimulationResult return" accurately reflects the two major changes in the changeset. It correctly identifies the removal of the duplicate cart_interp option in favor of cartesian_interp in SimulationOptions, and it clearly describes the introduction of the new SimulationResult dataclass that replaces the previous loose dotdict return type across multiple execution functions. The title is concise, specific, and would enable a teammate scanning the repository history to understand the primary purpose of the changes without needing to inspect the details.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/minor-code-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64699ce and 088aa86.

📒 Files selected for processing (9)
  • kwave/data.py (2 hunks)
  • kwave/executor.py (3 hunks)
  • kwave/kspaceFirstOrder2D.py (3 hunks)
  • kwave/kspaceFirstOrder3D.py (4 hunks)
  • kwave/kspaceFirstOrderAS.py (5 hunks)
  • kwave/options/simulation_options.py (0 hunks)
  • tests/test_executor.py (3 hunks)
  • tests/test_ivp_3D_simulation.py (2 hunks)
  • tests/test_tvsp_3D_simulation.py (2 hunks)
💤 Files with no reviewable changes (1)
  • kwave/options/simulation_options.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-11T16:12:04.876Z
Learnt from: waltsims
PR: waltsims/k-wave-python#625
File: docs/get_started/first_simulation.rst:147-154
Timestamp: 2025-09-11T16:12:04.876Z
Learning: In k-Wave Python, kspaceFirstOrder2D function takes separate simulation_options and execution_options parameters, not a single options dict. Use SimulationOptions and SimulationExecutionOptions classes from kwave.options, with snake_case attributes like pml_inside instead of MATLAB-style PMLInside.

Applied to files:

  • tests/test_ivp_3D_simulation.py
  • tests/test_tvsp_3D_simulation.py
  • kwave/kspaceFirstOrder2D.py
  • kwave/executor.py
  • kwave/kspaceFirstOrder3D.py
  • kwave/kspaceFirstOrderAS.py
🧬 Code graph analysis (7)
tests/test_ivp_3D_simulation.py (1)
kwave/kWaveSimulation.py (1)
  • kWaveSimulation (34-1552)
kwave/data.py (1)
kwave/kgrid.py (3)
  • Nx (157-161)
  • Ny (164-168)
  • Nz (171-175)
kwave/kspaceFirstOrder2D.py (1)
kwave/data.py (1)
  • SimulationResult (125-236)
kwave/executor.py (1)
kwave/data.py (2)
  • SimulationResult (125-236)
  • from_dotdict (196-236)
kwave/kspaceFirstOrder3D.py (1)
kwave/data.py (1)
  • SimulationResult (125-236)
tests/test_executor.py (1)
kwave/data.py (1)
  • SimulationResult (125-236)
kwave/kspaceFirstOrderAS.py (1)
kwave/data.py (1)
  • SimulationResult (125-236)
🪛 GitHub Actions: pytest
tests/test_executor.py

[error] 221-221: TestExecutor.test_sensor_data_cropping_with_pml_inside: AssertionError - expected sensor_data['p_final'] to match mock values; actual value did not equal expected (1 != 20).


[error] 192-192: TestExecutor.test_sensor_data_cropping_with_pml_outside: getitem on p_final was not called as expected; cropping did not occur.

🔇 Additional comments (9)
tests/test_tvsp_3D_simulation.py (1)

75-75: LGTM! Parameter renamed to use the consolidated API.

The change from cart_interp to cartesian_interp correctly reflects the API consolidation mentioned in the PR objectives.

kwave/executor.py (1)

9-9: LGTM! Clean integration of SimulationResult return type.

The changes correctly:

  • Import the new SimulationResult type
  • Annotate the return type (non-Optional is correct since there are no early returns in this method)
  • Wrap the dotdict in SimulationResult after PML cropping

The implementation preserves the existing cropping logic while providing a structured return type with backward compatibility.

Also applies to: 36-36, 65-65

tests/test_ivp_3D_simulation.py (1)

73-73: LGTM! Parameter renamed to match consolidated API.

Consistent with the broader API cleanup replacing cart_interp with cartesian_interp.

kwave/data.py (1)

124-236: LGTM! Well-structured dataclass with good backward compatibility.

The SimulationResult implementation is clean and well-designed:

  • Clear separation between required grid metadata and optional sensor data fields
  • Backward-compatible dict-like access via __getitem__ and __contains__
  • Safe construction from dotdict with sensible defaults
  • Appropriate type hints throughout

The from_dotdict classmethod properly handles missing keys and type conversions, ensuring robust construction from HDF5-loaded data.

kwave/kspaceFirstOrderAS.py (3)

2-2: LGTM! Return types correctly annotated as Optional[SimulationResult].

The Optional return type is appropriate because of the early return at line 366 when save_to_disk_exit=True. The imports are correctly added to support the new type annotations.

Also applies to: 7-7, 34-34, 93-93


162-162: Improved warning message clarity.

The warning message appears to have been improved (possibly fixing a typo or improving wording). Without seeing the old version, this looks like a reasonable refinement.


300-300: Assertion message consolidated to single line.

Good cleanup - keeping assertion messages as single strings improves readability and maintainability.

kwave/kspaceFirstOrder2D.py (1)

1-1: LGTM! Return type annotations correctly added.

The Optional[SimulationResult] return type is appropriate for both functions due to the early return path at line 359 when save_to_disk_exit=True. This provides better type safety and IDE support.

Also applies to: 6-6, 89-89, 149-149

kwave/kspaceFirstOrder3D.py (1)

1-1: LGTM! Return type annotations consistently applied across all 3D simulation functions.

The Optional[SimulationResult] return type is correctly applied to all three functions (GPU, CPU wrapper, and main function) to account for the early return at line 371 when save_to_disk_exit=True.

Also applies to: 5-5, 29-29, 83-83, 140-140

Comment thread tests/test_executor.py
@codecov
Copy link
Copy Markdown

codecov Bot commented Oct 30, 2025

Codecov Report

❌ Patch coverage is 93.61702% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.57%. Comparing base (cf4ec8f) to head (45967ab).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
kwave/data.py 91.89% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #647      +/-   ##
==========================================
+ Coverage   75.06%   76.57%   +1.50%     
==========================================
  Files          57       57              
  Lines        8126     8686     +560     
  Branches     1582     1804     +222     
==========================================
+ Hits         6100     6651     +551     
+ Misses       1405     1398       -7     
- Partials      621      637      +16     
Flag Coverage Δ
3.10 76.54% <93.61%> (+1.50%) ⬆️
3.11 76.54% <93.61%> (+1.50%) ⬆️
3.12 76.54% <93.61%> (+1.50%) ⬆️
3.13 76.54% <93.61%> (+1.50%) ⬆️
macos-latest 76.43% <93.61%> (+1.43%) ⬆️
ubuntu-latest 76.47% <93.61%> (+1.48%) ⬆️
windows-latest 76.34% <93.61%> (+1.40%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/test_executor.py (2)

177-209: Cropping test correctly updated for SimulationResult.

The test logic is sound:

  • Tracking mocks verify that _crop_pml is called with correct slice operations
  • Field assertions confirm non-cropped data is preserved
  • SimulationResult instance check validates the return type

The test effectively verifies the control flow and cropping behavior.

Optional: Consider strengthening verification of cropped values.

Currently, the test verifies that cropping functions were called but doesn't verify the final cropped array shapes or values in the SimulationResult. You could enhance this by:

# Configure mocks to return arrays with realistic shapes
cropped_2d = np.zeros((16, 16))  # After cropping 20x20 with PML=2
cropped_3d = np.zeros((16, 16, 16))
original_two_d_output.__getitem__.return_value = cropped_2d
original_three_d_output.__getitem__.return_value = cropped_3d

# Then verify the shapes in the result
self.assertEqual(sensor_data["p_final"].shape, (16, 16))
self.assertEqual(sensor_data["p_max_all"].shape, (16, 16, 16))

This would provide stronger verification that the cropping logic produces the expected output, not just that it was invoked.


211-243: Correct verification that cropping is skipped when pml_inside is True.

The test properly verifies that when pml_inside=True, the cropping functions are not called and all fields remain unchanged in the SimulationResult.

Optional: Same enhancement opportunity as the pml_outside test.

Consider configuring mocks with realistic array shapes and verifying the final array dimensions in the SimulationResult to strengthen the test's verification of data correctness.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 088aa86 and 4daf678.

📒 Files selected for processing (1)
  • tests/test_executor.py (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_executor.py (2)
kwave/data.py (1)
  • SimulationResult (125-236)
kwave/executor.py (2)
  • Executor (14-144)
  • run_simulation (36-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (macos-latest, 3.11)
  • GitHub Check: test (macos-latest, 3.12)
  • GitHub Check: test (macos-latest, 3.10)
  • GitHub Check: test (macos-latest, 3.13)
  • GitHub Check: test (macos-latest, 3.12)
🔇 Additional comments (3)
tests/test_executor.py (3)

12-12: LGTM: Import updated for SimulationResult.

The import correctly reflects the updated return type from the executor.


66-67: Good fix: Using real dict for SimulationResult compatibility.

This change is necessary because SimulationResult.from_dotdict expects a real dictionary with working .get() method semantics, which MagicMock doesn't provide correctly.


111-111: LGTM: Assertions correctly verify SimulationResult return type.

The updated assertions properly check that run_simulation returns SimulationResult instances instead of dotdict.

Also applies to: 131-131

Comment thread kwave/data.py
Copy link
Copy Markdown
Collaborator

@djps djps left a comment

Choose a reason for hiding this comment

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

I think to increase utility of this, you need more data: specifically the spatial and temporal resolution.

For 2D simulations is Nz (and dz) 1 or None?

If you want to take a data set and compute some key metrics you would need more information as well, such as the frequency (which is needed for the mechanical index). I think it should be an aim to have data from which all key metrics can be derived without needing input files.

@faridyagubbayli
Copy link
Copy Markdown
Collaborator Author

@djps @waltsims could you please summarize the discussion outcomes from our meeting today? I didn't catch everything, unfortunately. How do we want to proceed with this PR?

Comment on lines -206 to -208
* cart_interp: Interpolation mode used to extract the pressure when a Cartesian sensor mask is given.
If set to 'nearest', duplicated data points are discarded and sensor_data
will be returned with fewer points than specified by sensor.mask (default = 'linear').
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Perhaps we document Cartesian interp with the old docstring?

@waltsims
Copy link
Copy Markdown
Owner

@greptile re-review

Comment thread kwave/data.py
Comment on lines +166 to +193
def __getitem__(self, key: str):
"""
Enable dictionary-style access for backward compatibility.

Args:
key: Field name to access

Returns:
Value of the field

Raises:
KeyError: If the field does not exist
"""
if hasattr(self, key):
return getattr(self, key)
raise KeyError(f"'{key}' field not found in SimulationResult")

def __contains__(self, key: str) -> bool:
"""
Enable dictionary-style membership testing for backward compatibility.

Args:
key: Field name to check

Returns:
True if the field exists, False otherwise
"""
return hasattr(self, key)
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 __contains__ and __getitem__ use hasattr, which returns True for any attribute on the object — including methods like from_dotdict, __init__, and dunder attributes. A caller doing 'from_dotdict' in result gets True and result['from_dotdict'] returns the classmethod rather than raising KeyError. The intent is backward compatibility for sensor data fields, so the check should be restricted to declared dataclass fields only.

Suggested change
def __getitem__(self, key: str):
"""
Enable dictionary-style access for backward compatibility.
Args:
key: Field name to access
Returns:
Value of the field
Raises:
KeyError: If the field does not exist
"""
if hasattr(self, key):
return getattr(self, key)
raise KeyError(f"'{key}' field not found in SimulationResult")
def __contains__(self, key: str) -> bool:
"""
Enable dictionary-style membership testing for backward compatibility.
Args:
key: Field name to check
Returns:
True if the field exists, False otherwise
"""
return hasattr(self, key)
def __getitem__(self, key: str):
"""
Enable dictionary-style access for backward compatibility.
Args:
key: Field name to access
Returns:
Value of the field
Raises:
KeyError: If the field does not exist
"""
import dataclasses
field_names = {f.name for f in dataclasses.fields(self)}
if key in field_names:
return getattr(self, key)
raise KeyError(f"'{key}' field not found in SimulationResult")
def __contains__(self, key: str) -> bool:
"""
Enable dictionary-style membership testing for backward compatibility.
Args:
key: Field name to check
Returns:
True if the field exists, False otherwise
"""
import dataclasses
return key in {f.name for f in dataclasses.fields(self)}

Comment thread kwave/data.py
Comment on lines +195 to +236
@classmethod
def from_dotdict(cls, data: dict) -> "SimulationResult":
"""
Create SimulationResult from dotdict returned by parse_executable_output.

Args:
data: Dictionary containing simulation results from HDF5 file

Returns:
SimulationResult instance with all available fields populated
"""
return cls(
# Grid information
Nx=int(data.get("Nx", 0)),
Ny=int(data.get("Ny", 0)),
Nz=int(data.get("Nz", 0)),
Nt=int(data.get("Nt", 0)),
pml_x_size=int(data.get("pml_x_size", 0)),
pml_y_size=int(data.get("pml_y_size", 0)),
pml_z_size=int(data.get("pml_z_size", 0)),
axisymmetric_flag=bool(data.get("axisymmetric_flag", False)),
# Pressure fields
p_raw=data.get("p_raw"),
p_max=data.get("p_max"),
p_min=data.get("p_min"),
p_rms=data.get("p_rms"),
p_max_all=data.get("p_max_all"),
p_min_all=data.get("p_min_all"),
p_final=data.get("p_final"),
# Velocity fields
u_raw=data.get("u_raw"),
u_max=data.get("u_max"),
u_min=data.get("u_min"),
u_rms=data.get("u_rms"),
u_max_all=data.get("u_max_all"),
u_min_all=data.get("u_min_all"),
u_final=data.get("u_final"),
u_non_staggered_raw=data.get("u_non_staggered_raw"),
# Intensity fields
I_avg=data.get("I_avg"),
I=data.get("I"),
)
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 Velocity component fields silently dropped

_crop_pml in executor.py iterates over individual velocity-component field names (ux_final, uy_final, uz_final, ux_max_all, uy_max_all, uz_max_all, ux_min_all, uy_min_all, uz_min_all) because those are the keys the kWave C++ binary actually writes to the HDF5 output. from_dotdict maps u_final, u_max_all, and u_min_all instead — keys that are absent from the real output. data.get("u_final") therefore always returns None, and the cropped velocity data (already modified in the dotdict by _crop_pml) is silently discarded. Any user recording velocity fields will receive a SimulationResult where all those fields are None.

@@ -85,7 +81,6 @@ class SimulationOptions(object):
"""

simulation_type: SimulationType = SimulationType.FLUID
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 cart_interp removal is a hard breaking change

Removing cart_interp as a named constructor argument means any existing caller passing SimulationOptions(cart_interp="nearest") will get a TypeError at runtime. The PR description's claim of "non-breaking at call sites" applies only to the return-type change; the option rename is explicitly a breaking change for anyone not on the new API. Documenting a migration path or providing a __post_init__ deprecation path (accepting cart_interp and forwarding it with a warning) would be safer for library consumers.

@waltsims waltsims force-pushed the chore/minor-code-improvements branch from 4daf678 to 82a8d7f Compare May 16, 2026 17:19
@waltsims
Copy link
Copy Markdown
Owner

Rebased onto master to resolve conflicts (the conflict was purely import-order on kwave/kspaceFirstOrder2D.py / 3D.py — added Optional to the typing import while keeping the import warnings from master). All 4 commits replayed cleanly; tests/test_executor.py 10/10 pass locally. Waiting on Greptile re-review on the new SHA before merging.

waltsims and others added 2 commits May 16, 2026 10:19
…fix)

The binary's CLI flag is --p_raw but the HDF5 output dataset is /p — the
"_raw" wording only exists in the C++ CLI surface, never in the actual
data the wrapper reads back. The original PR field names (p_raw, u_raw,
u_non_staggered_raw) leaked an implementation detail and also broke the
existing read path: from_dotdict was looking up data["p_raw"] but the
parsed dict contains data["p"], so p_raw was always None after a real
binary run (no tests caught this because they mock the parse output).

Renamed:
- p_raw -> p
- u_raw -> u
- u_non_staggered_raw -> u_non_staggered

This matches the existing sensor.record API (sensor.record = ("p",))
and the previous dict return shape, so every existing caller doing
result["p"] keeps working.

Co-Authored-By: faridyagubbayli <faridyagubbayli@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@waltsims
Copy link
Copy Markdown
Owner

Pushed two fixes (45967ab):

1. Field naming aligned with sensor.record keys (drops the _raw suffix that mirrored the binary's CLI flag):

This also fixes a latent bug: the binary's HDF5 dataset is named /p (and /u, etc.) — only the CLI flag is --p_raw. The original p_raw=data.get('p_raw') lookup was always returning None after a real binary run. The new lookups use the actual dataset keys (data.get('p'), etc.), and existing callers doing result['p'] keep working.

2. Velocity naming uses the modern convention from the Python solver:

  • result.u = collocated (pressure-grid) velocity
  • result.u_staggered = mid-cell staggered velocity

The legacy C++ binary names this backwards (its u is mid-cell, its u_non_staggered is collocated), so from_dotdict translates: u = data.get('u_non_staggered'), u_staggered = data.get('u'). This matches what kwave/solvers/kspace_solver.py:253-258 already does internally — the wrapper now hides the binary's quirk so the user-facing API is consistent across backends.

cc @faridyagubbayli — please flag if you'd prefer a different mapping. 70/70 unit tests pass locally. Re-triggering Greptile.

@greptile re-review

Comment thread kwave/data.py
Comment on lines +172 to +199
def __getitem__(self, key: str):
"""
Enable dictionary-style access for backward compatibility.

Args:
key: Field name to access

Returns:
Value of the field

Raises:
KeyError: If the field does not exist
"""
if hasattr(self, key):
return getattr(self, key)
raise KeyError(f"'{key}' field not found in SimulationResult")

def __contains__(self, key: str) -> bool:
"""
Enable dictionary-style membership testing for backward compatibility.

Args:
key: Field name to check

Returns:
True if the field exists, False otherwise
"""
return hasattr(self, key)
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 Incomplete dict-compatibility bridge breaks .get() callers

SimulationResult implements __getitem__ and __contains__ for backward compatibility, but omits get(), keys(), items(), values(), and __iter__. Any existing code that calls result.get("p", None), iterates for k in result, or calls result.keys() will raise AttributeError at runtime — a silent regression for users migrating from the old dotdict return type. The PR description claims the return-type change is non-breaking, but without these methods the compatibility surface is materially incomplete.

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