Skip to content

Conversation

@rochSmets
Copy link
Contributor

Gaussian filter and find peaks are to be imported with from pyphare.core.ufuncs import gF, peakIds

For example, for a vectorField like the ion velocity with V = run.GetVi(time), it can be smoothed with a gaussian filter with sigma=6 as v = gF(V, sigma=6). Hence v is a different vectorField with exactly the same structure, except that the contained dataset are smoothed.

One can also look at the peaks with pks, hgs = peakIds(v, names=['x',], height=0.015) where we need to provide the name of the dataset, value for example for a scalarField. In this example, the kwarg height is forwarded to the find_peaks of scipy.ndimage. It goes the same for whatever kwargs that can be provided to find_peaks. The returned quantities are the same, that is a tuple with the peaks location and a tuple (of same size) with their height.

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

📝 Walkthrough

Walkthrough

Adds Gaussian-filter and peak-detection utilities for hierarchies, extends Yee-centering mappings to include value,x,y,z as primal, fixes _compute_sqrt to use "value" centering, and adds hierarchy interpolation support plus a build_interpolator helper; also minor plotting/API tweaks.

Changes

Cohort / File(s) Summary
Centering mappings
pyphare/pyphare/core/gridlayout.py
Expanded yee_centering per-direction dictionaries to include keys value, x, y, z set to "primal" (and propagated to lowercased lookup).
Operator centering fix
pyphare/pyphare/core/operators.py
_compute_sqrt now uses patch_datas["value"].centerings explicitly (replaced dynamic ref_name lookup). Minor formatting newline added in grad.
Gaussian filtering & peak detection
pyphare/pyphare/core/ufuncs.py
Added _compute_gaussian_filter_on_scalarfield, _compute_gaussian_filter_on_vectorfield, plus public gFilt, gF for Gaussian filtering across hierarchies and peakIds for peak finding; handles ghost regions, per-component centering, and uses scipy filter/find_peaks.
Hierarchy interpolation API
pyphare/pyphare/pharesee/hierarchy/hierarchy.py, pyphare/pyphare/pharesee/run/utils.py
Added PatchHierarchy.interpol(time, interp="nearest") returning per-quantity interpolators. Added build_interpolator(data, coords, interp, box, dl, qty, nbrGhosts) to construct 1D/2D interpolators and finest coordinates (supports nearest/bilinear for 2D, interp1d for 1D).
Minor plotting & formatting
pyphare/pyphare/pharesee/hierarchy/scalarfield.py
Added optional lw parameter to plot1d (default 1) and a trailing newline; no semantic change.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Caller
participant PatchHierarchy
participant FinestField
participant build_interpolator
Caller->>PatchHierarchy: interpol(time, interp)
PatchHierarchy->>FinestField: flat_finest_field(qty, time)
FinestField-->>PatchHierarchy: data, coords, box, dl
PatchHierarchy->>build_interpolator: build_interpolator(data, coords, interp, box, dl, qty, nbrGhosts)
build_interpolator-->>PatchHierarchy: interpolator, finest_coords
PatchHierarchy-->>Caller: {qty: interpolator, ...}

mermaid
sequenceDiagram
participant Caller
participant gF_gFilt
participant compute_hier_from
participant Patch
participant scipy_ndimage
Caller->>gF_gFilt: gF/gFilt(hier, sigma,...)
gF_gFilt->>compute_hier_from: dispatch per-patch
compute_hier_from->>Patch: provide patch_datas (with ghosts)
Patch->>scipy_ndimage: gaussian_filter(per-patch array)
scipy_ndimage-->>Patch: filtered array
Patch-->>gF_gFilt: updated patch_datas
gF_gFilt-->>Caller: filtered hierarchy

mermaid
sequenceDiagram
participant Caller
participant peakIds
participant compute_hier_from
participant Patch
participant scipy_signal
Caller->>peakIds: peakIds(hier, qty, peak_heights,...)
peakIds->>compute_hier_from: dispatch per-patch
compute_hier_from->>Patch: provide 1D slices / arrays
Patch->>scipy_signal: find_peaks(slice)
scipy_signal-->>Patch: peak indices & heights
Patch-->>peakIds: physical positions & heights
peakIds-->>Caller: aggregated peaks, heights

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: adding gaussian_filter and find_peaks functions for scalarfield and vectorfield objects.
Description check ✅ Passed The description is directly related to the changeset, providing clear usage examples and explaining the purpose of the gF and peakIds functions being added.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
pyphare/pyphare/pharesee/run/utils.py (1)

493-537: LGTM! Well-structured interpolator builder.

The function correctly:

  • Handles 1D and 2D cases with appropriate scipy interpolators
  • Builds finest_coords using yeeCoordsFor across dimensions
  • Raises clear errors for unsupported configurations

Minor: Consider using a custom exception class for the error messages to satisfy TRY003, but this is optional.

pyphare/pyphare/pharesee/hierarchy/hierarchy.py (1)

618-630: Use next(iter(...)) instead of list(...)[0].

Line 622 converts to a list just to access the first element. This is inefficient for large collections.

♻️ Suggested fix
-        nbrGhosts = list(self.level(0, time).patches[0].patch_datas.values())[0].ghosts_nbr
+        nbrGhosts = next(iter(self.level(0, time).patches[0].patch_datas.values())).ghosts_nbr
pyphare/pyphare/core/ufuncs.py (2)

72-72: Use next(iter(...)) instead of list(...)[0].

Per static analysis hint RUF015, prefer next(iter(...)) over converting to list just to access the first element.

♻️ Suggested fix
-    nb_ghosts = np.max(list(hier.level(0).patches[0].patch_datas.values())[0].ghosts_nbr)
+    nb_ghosts = np.max(next(iter(hier.level(0).patches[0].patch_datas.values())).ghosts_nbr)

129-157: LGTM with minor suggestion.

Good implementation of peak detection with mandatory peak_heights validation (line 145-147).

Minor: Add strict=True to zip() at line 155 to catch length mismatches early (per B905).

♻️ Suggested fix
-                for pk, hg in zip(pks[0], pks[1]['peak_heights']):
+                for pk, hg in zip(pks[0], pks[1]['peak_heights'], strict=True):

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 600adcf and 4e11904.

📒 Files selected for processing (6)
  • pyphare/pyphare/core/gridlayout.py
  • pyphare/pyphare/core/operators.py
  • pyphare/pyphare/core/ufuncs.py
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
  • pyphare/pyphare/pharesee/hierarchy/scalarfield.py
  • pyphare/pyphare/pharesee/run/utils.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • pyphare/pyphare/core/gridlayout.py
  • pyphare/pyphare/core/operators.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-10-18T13:23:32.074Z
Learnt from: PhilipDeegan
Repo: PHAREHUB/PHARE PR: 910
File: pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py:7-7
Timestamp: 2024-10-18T13:23:32.074Z
Learning: In the `pyphare.pharesee.hierarchy` module, importing `PatchHierarchy` and `format_timestamp` from `hierarchy.py` into `hierarchy_utils.py` is acceptable as long as `hierarchy.py` does not import `hierarchy_utils.py`, thereby avoiding a cyclic import.

Applied to files:

  • pyphare/pyphare/pharesee/hierarchy/scalarfield.py
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
  • pyphare/pyphare/core/ufuncs.py
📚 Learning: 2024-09-09T13:57:02.285Z
Learnt from: PhilipDeegan
Repo: PHAREHUB/PHARE PR: 888
File: pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py:566-568
Timestamp: 2024-09-09T13:57:02.285Z
Learning: Avoid commenting on draft PRs for PhilipDeegan unless explicitly requested.

Applied to files:

  • pyphare/pyphare/core/ufuncs.py
🧬 Code graph analysis (3)
pyphare/pyphare/pharesee/run/utils.py (4)
pyphare/pyphare/core/gridlayout.py (3)
  • nbrGhosts (247-251)
  • yeeCoordsFor (150-177)
  • yeeCoordsFor (374-403)
pyphare/pyphare/core/box.py (1)
  • nCells (36-38)
src/core/utilities/box/box.hpp (1)
  • upper (96-96)
pyphare/pyphare/pharesee/geometry.py (1)
  • append (220-228)
pyphare/pyphare/pharesee/hierarchy/hierarchy.py (2)
pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py (1)
  • flat_finest_field (330-350)
pyphare/pyphare/pharesee/run/utils.py (1)
  • build_interpolator (493-537)
pyphare/pyphare/core/ufuncs.py (4)
pyphare/pyphare/pharesee/hierarchy/scalarfield.py (1)
  • ScalarField (5-212)
pyphare/pyphare/pharesee/hierarchy/vectorfield.py (1)
  • VectorField (15-100)
pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py (1)
  • compute_hier_from (137-160)
pyphare/pyphare/pharesee/hierarchy/hierarchy.py (4)
  • level (219-220)
  • times (338-340)
  • interpol (618-630)
  • levels (214-217)
🪛 Ruff (0.14.11)
pyphare/pyphare/pharesee/run/utils.py

532-532: Avoid specifying long messages outside the exception class

(TRY003)


535-535: Avoid specifying long messages outside the exception class

(TRY003)

pyphare/pyphare/pharesee/hierarchy/hierarchy.py

622-622: Prefer next(...) over single element slice

Replace with next(...)

(RUF015)

pyphare/pyphare/core/ufuncs.py

72-72: Prefer next(...) over single element slice

Replace with next(...)

(RUF015)


120-120: Avoid specifying long messages outside the exception class

(TRY003)


136-136: Avoid specifying long messages outside the exception class

(TRY003)


147-147: Avoid specifying long messages outside the exception class

(TRY003)


155-155: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

⏰ 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). (6)
  • GitHub Check: build (ubuntu-latest, gcc)
  • GitHub Check: build (ubuntu-latest, clang)
  • GitHub Check: Analyze (cpp)
  • GitHub Check: Analyze (python)
  • GitHub Check: build (macos-14)
  • GitHub Check: build (macos-latest)
🔇 Additional comments (3)
pyphare/pyphare/pharesee/hierarchy/scalarfield.py (1)

209-213: LGTM!

The trailing blank line addition is a minor formatting change with no functional impact.

pyphare/pyphare/pharesee/hierarchy/hierarchy.py (1)

441-443: LGTM!

The lw (line width) parameter is correctly added with a sensible default of 1 and properly forwarded to the plot call.

pyphare/pyphare/core/ufuncs.py (1)

122-124: Potential slice issue when sigma < nb_ghosts.

The slice sigma-nb_ghosts[i] produces a negative start index when sigma < nb_ghosts[i]. While NumPy handles negative indices by counting from the end, this may not be the intended behavior and could extract incorrect data regions.

Consider adding validation or adjusting the slice logic:

# Ensure sigma >= nb_ghosts to avoid unexpected slicing behavior
start = max(0, sigma - nb_ghosts[i])
end = -max(1, sigma - nb_ghosts[i]) if sigma > nb_ghosts[i] else None

Alternatively, verify this edge case is handled correctly or document that sigma >= nb_ghosts is expected.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.


return final, dp(final, **kwargs)

def interpol(self, time, interp="nearest"):

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
pyphare.pharesee.hierarchy.hierarchy_utils
begins an import cycle.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

^^

Copy link

@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: 6

🤖 Fix all issues with AI agents
In @pyphare/pyphare/core/ufuncs.py:
- Around line 136-138: Replace the incorrect return of the sentinel
NotImplemented with raising a proper exception: in the function that assigns
time = times[0] and currently does "return NotImplemented", change that to raise
NotImplementedError with a short explanatory message (similar to the gFilt fix)
so callers get an exception instead of a bogus return value.
- Around line 81-89: The branch that currently returns NotImplemented should
instead raise NotImplementedError and the function must explicitly handle the
case hier.ndim != 1; update the block in ufuncs.py where hier is tested for ndim
== 1 (and where ScalarField/VectorField are handled via compute_hier_from and
_compute_gaussian_filter_on_scalarfield/_compute_gaussian_filter_on_vectorfield
with nb_ghosts and sigma) so that if hier is neither ScalarField nor VectorField
you raise NotImplementedError("Gaussian filter not implemented for type ..."),
and if hier.ndim != 1 raise NotImplementedError(f"Gaussian filter only
implemented for 1D hier, got ndim={hier.ndim}").
- Around line 173-175: The loop assumes pks[1]['peak_heights'] exists and uses
zip(pks[0], pks[1]['peak_heights']); update the code in the block that builds
pks_ and hgs_ to (1) guard the access to the 'peak_heights' key on pks[1] (e.g.,
check if 'peak_heights' in pks[1] or fetch via .get and handle a missing value
by skipping, defaulting, or raising a clear ValueError) and (2) call zip(...,
strict=True) so the zip between pks[0] and the heights enforces equal lengths;
keep references to pks, pks_[the accumulator], hgs_, patch.dl and patch.origin
when implementing the fix.
- Around line 115-123: In the ndim dispatch in pyphare/core/ufuncs.py replace
the duplicated "elif ndim == 2" branch with "elif ndim == 3" so the 3D call uses
func(r_[0], r_[1], r_[2]); also change the final "print('unvalid dimension')" to
raise a ValueError with a clear message (e.g., "invalid dimension") so invalid
ndim values are handled as exceptions; references: variables ndim, func, r_ in
the existing branch logic.

In @pyphare/pyphare/pharesee/hierarchy/scalarfield.py:
- Line 3: Remove the unused import flat_finest_field from
pyphare.pharesee.hierarchy.scalarfield by deleting the line that imports
flat_finest_field; search for the symbol flat_finest_field in this file to
confirm it's unused and ensure no other references exist before removing the
import statement from the top of the module.

In @pyphare/pyphare/pharesee/run/utils.py:
- Line 508: The nCells computation is off-by-one: replace the manual formula
nCells = box.upper - box.lower + 2 with the correct count using either nCells =
box.upper - box.lower + 1 or, better, use the Box.shape property (e.g., nCells =
box.shape) so the interpolation uses the correct grid dimensions; update the
occurrence of nCells in this module (the variable computed from box.upper and
box.lower) to use box.shape to avoid the extra cell.
🧹 Nitpick comments (8)
pyphare/pyphare/core/operators.py (1)

22-29: LGTM! Consider removing the TODO comment.

The refactoring to use patch_datas["value"].centerings directly is clearer and removes unnecessary indirection. Since the TODO has been addressed by this change, consider removing the comment.

♻️ Optional: Remove the TODO comment
 def _compute_sqrt(patch_datas, **kwargs):
-    # ref_name = next(iter(patch_datas.keys())) TODO this is always "value"
-
     dset = np.sqrt(patch_datas["value"][:])
 
     return (
pyphare/pyphare/pharesee/run/utils.py (1)

493-537: Significant code duplication with make_interpolator.

The build_interpolator function (lines 493-537) duplicates substantial logic from make_interpolator (lines 444-490). Both functions:

  • Create scipy interpolators (interp1d, LinearNDInterpolator, NearestNDInterpolator)
  • Generate finest coordinates using yeeCoordsFor
  • Handle 1D and 2D cases with similar error handling

Consider refactoring to extract common logic and reduce duplication.

💡 Refactoring suggestion

Consider creating a shared helper function for the interpolator creation logic, with parameters to handle the differences in coordinate generation (domain vs box based).

pyphare/pyphare/pharesee/hierarchy/hierarchy.py (1)

619-631: Consider defensive checks and use next() for cleaner iteration.

The interpol method has several assumptions that could cause runtime errors:

  1. Line 623 uses list(...)[0] when next(iter(...)) would be more idiomatic and efficient.
  2. The method assumes level 0 exists and has at least one patch with patch_datas.
  3. It assumes nbrGhosts is consistent across all quantities.
  4. Lines 627-628 assume each quantity exists in the first patch's patch_datas.

Consider adding validation checks or documenting these assumptions.

♻️ Proposed improvements
 def interpol(self, time, interp="nearest"):
     from pyphare.pharesee.hierarchy.hierarchy_utils import flat_finest_field
     from pyphare.pharesee.run.utils import build_interpolator
 
-    nbrGhosts = list(self.level(0).patches[0].patch_datas.values())[0].ghosts_nbr
+    # Get nbrGhosts from the first available patch data
+    first_patch = self.level(0).patches[0]
+    nbrGhosts = next(iter(first_patch.patch_datas.values())).ghosts_nbr
 
     interp_ = {}
     for qty in self.quantities():
         box = self.level(0).patches[0].patch_datas[qty].box
         dl = self.level(0).patches[0].patch_datas[qty].dl
         data, coords = flat_finest_field(self, qty, time=time)
         interp_[qty] = build_interpolator(data, coords, interp, box, dl, qty, nbrGhosts)
     return interp_
pyphare/pyphare/core/ufuncs.py (5)

5-8: Remove unused imports.

These imports are not used anywhere in the module and should be removed.

Suggested fix
-from pyphare.pharesee.hierarchy.hierarchy_utils import are_compatible_hierarchies
-from pyphare.core.phare_utilities import listify
-from pyphare.pharesee.hierarchy.patch import Patch
-from pyphare.pharesee.hierarchy.patchlevel import PatchLevel

14-16: Remove redundant import.

gaussian_filter is already imported at module level (line 10). The local import is unnecessary.

Suggested fix
 def _compute_gaussian_filter_on_scalarfield(patch_datas, **kwargs):
-    from scipy.ndimage import gaussian_filter
-
     ndim = patch_datas["value"].box.ndim

33-35: Remove redundant import.

Same issue as above — gaussian_filter is already available from the module-level import.

Suggested fix
 def _compute_gaussian_filter_on_vectorfield(patch_datas, **kwargs):
-    from scipy.ndimage import gaussian_filter
-
     ref_name = next(iter(patch_datas.keys()))

69-76: Clean up commented-out code and use next() idiom.

The commented-out block appears to be dead code. Line 76 should use next(iter(...)) instead of list(...)[0] for efficiency.

Suggested fix
-    # time0 = list(hier.times())[0]
-    # level0 = 0
-    # p0 = 0
-    # pd0 = hier.levels(time0)[level0].patches[p0].patch_datas
-    # key0 = list(pd0.keys())[0]
-    # nb_ghosts = np.max(pd0[key0].ghosts_nbr)
-
-    nb_ghosts = np.max(list(hier.level(0).patches[0].patch_datas.values())[0].ghosts_nbr)
+    nb_ghosts = np.max(next(iter(hier.level(0).patches[0].patch_datas.values())).ghosts_nbr)

151-179: Remove commented-out debug code.

The extensive commented-out print statements are debug artifacts and clutter the code.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cd180f and 3741813.

📒 Files selected for processing (6)
  • pyphare/pyphare/core/gridlayout.py
  • pyphare/pyphare/core/operators.py
  • pyphare/pyphare/core/ufuncs.py
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
  • pyphare/pyphare/pharesee/hierarchy/scalarfield.py
  • pyphare/pyphare/pharesee/run/utils.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-18T13:23:32.074Z
Learnt from: PhilipDeegan
Repo: PHAREHUB/PHARE PR: 910
File: pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py:7-7
Timestamp: 2024-10-18T13:23:32.074Z
Learning: In the `pyphare.pharesee.hierarchy` module, importing `PatchHierarchy` and `format_timestamp` from `hierarchy.py` into `hierarchy_utils.py` is acceptable as long as `hierarchy.py` does not import `hierarchy_utils.py`, thereby avoiding a cyclic import.

Applied to files:

  • pyphare/pyphare/core/ufuncs.py
  • pyphare/pyphare/pharesee/hierarchy/scalarfield.py
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
🧬 Code graph analysis (4)
pyphare/pyphare/core/ufuncs.py (3)
pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py (2)
  • compute_hier_from (137-160)
  • are_compatible_hierarchies (73-84)
pyphare/pyphare/core/phare_utilities.py (1)
  • listify (46-51)
pyphare/pyphare/pharesee/hierarchy/patch.py (1)
  • Patch (4-69)
pyphare/pyphare/pharesee/hierarchy/scalarfield.py (1)
pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py (1)
  • flat_finest_field (330-350)
pyphare/pyphare/pharesee/hierarchy/hierarchy.py (3)
pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py (1)
  • flat_finest_field (330-350)
pyphare/pyphare/pharesee/run/utils.py (1)
  • build_interpolator (493-537)
pyphare/pyphare/core/gridlayout.py (1)
  • nbrGhosts (247-251)
pyphare/pyphare/pharesee/run/utils.py (1)
pyphare/pyphare/core/gridlayout.py (3)
  • nbrGhosts (247-251)
  • yeeCoordsFor (150-177)
  • yeeCoordsFor (387-416)
🪛 GitHub Check: CodeQL
pyphare/pyphare/core/ufuncs.py

[warning] 119-119: Redundant comparison
Test is always false, because of this condition.


[warning] 120-120: Unreachable code
This statement is unreachable.


[notice] 5-5: Unused import
Import of 'are_compatible_hierarchies' is not used.


[notice] 6-6: Unused import
Import of 'listify' is not used.


[notice] 7-7: Unused import
Import of 'Patch' is not used.


[notice] 8-8: Unused import
Import of 'PatchLevel' is not used.


[notice] 66-66: Explicit returns mixed with implicit (fall through) returns
Mixing implicit and explicit returns may indicate an error, as implicit returns always return None.


[notice] 176-179: Commented-out code
This comment appears to contain commented-out code.

pyphare/pyphare/pharesee/hierarchy/scalarfield.py

[notice] 3-3: Unused import
Import of 'flat_finest_field' is not used.

pyphare/pyphare/pharesee/hierarchy/hierarchy.py

[notice] 619-619: Cyclic import
Import of module pyphare.pharesee.hierarchy.hierarchy_utils begins an import cycle.

🪛 Ruff (0.14.10)
pyphare/pyphare/core/ufuncs.py

76-76: Prefer next(...) over single element slice

Replace with next(...)

(RUF015)


173-173: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

pyphare/pyphare/pharesee/hierarchy/hierarchy.py

623-623: Prefer next(...) over single element slice

Replace with next(...)

(RUF015)

pyphare/pyphare/pharesee/run/utils.py

532-532: Avoid specifying long messages outside the exception class

(TRY003)


535-535: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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). (6)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (cpp)
  • GitHub Check: build (ubuntu-latest, clang)
  • GitHub Check: build (ubuntu-latest, gcc)
  • GitHub Check: build (macos-14)
  • GitHub Check: build (macos-latest)
🔇 Additional comments (4)
pyphare/pyphare/core/gridlayout.py (1)

47-50: LGTM!

The expansion of Yee centering mappings to include "value", "x", "y", and "z" keys with "primal" centering is consistent across all three spatial directions and aligns with the PR's goal of supporting Gaussian filtering and peak finding on scalar and vector fields.

Also applies to: 86-89, 125-128

pyphare/pyphare/pharesee/hierarchy/hierarchy.py (2)

442-444: LGTM!

The addition of the lw parameter retrieval improves the plot1d method's flexibility.


619-619: Cyclic import is acceptable in this context.

The cyclic import warning can be safely ignored. Based on learnings, importing from hierarchy_utils into hierarchy.py is acceptable as long as hierarchy.py does not create a circular dependency, which is the established pattern in this module.

pyphare/pyphare/core/ufuncs.py (1)

125-126: Verify slice bounds when nb_ghosts[i] >= sigma.

If nb_ghosts[i] == sigma, the slice becomes slice(0, 0) which is empty. If nb_ghosts[i] > sigma, the slice indices invert (e.g., slice(2, -2) when sigma=3 and nb_ghosts=5 would select less data than expected). Ensure this is the intended behavior.

Copy link

@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: 9

🤖 Fix all issues with AI agents
In @pyphare/pyphare/core/ufuncs.py:
- Around line 173-175: The loop assumes pks[1]['peak_heights'] exists causing a
KeyError when find_peaks was called without height; to fix, before the loop
obtain heights = pks[1].get('peak_heights') and if None compute heights from the
underlying signal at the peak indices (e.g., heights = signal_array[pks[0]]),
then iterate for pk, hg in zip(pks[0], heights) and append to pks_ and hgs_;
alternatively, if computing heights is impossible here, raise a clear error
indicating that the height argument is required.
- Around line 81-89: The branch only handles hier.ndim == 1 and returns the
sentinel NotImplemented for other cases; replace that with explicit error
handling and proper exception raising: instead of returning NotImplemented,
raise NotImplementedError with a message including hier.ndim and the field type,
and extend the conditional to explicitly handle expected dims (e.g., ndim == 2
and ndim == 3) by calling the appropriate compute_hier_from variant or, if those
variants are not available, raise NotImplementedError mentioning
_compute_gaussian_filter_on_scalarfield/_compute_gaussian_filter_on_vectorfield
and the unsupported dimension; ensure ScalarField/VectorField handling remains
intact and that compute_hier_from is invoked with nb_ghosts and sigma as before.
- Around line 115-123: The branch contains a copy-paste bug: change the second
`elif ndim == 2` to `elif ndim == 3` and call `func(r_[0], r_[1], r_[2])` to
produce `data` for 3D; also stop silently printing on invalid dimensions—raise a
ValueError (or return/raise) instead of `print('unvalid dimension')` so `data`
is always defined before subsequent use (e.g., the later `gaussian_filter`
call).
- Around line 143-145: The code mutates the caller's kwargs by calling
kwargs.pop("names"); instead, read names with kwargs.get("names") into names_
(or make a shallow copy like local_kwargs = dict(kwargs) and pop from that) and
then pass a filtered/local_kwargs to find_peaks so the original kwargs is not
modified; update the block that sets names_ and the subsequent call to
find_peaks to use the non-mutating approach (symbols to change: names_, kwargs,
find_peaks).
- Around line 5-10: Remove the unused imports at the top of ufuncs.py
(are_compatible_hierarchies, listify, Patch, PatchLevel) since they are not
referenced, and keep a single top-level "from scipy.ndimage import
gaussian_filter" import; also delete the redundant inner imports of
gaussian_filter inside the functions _compute_gaussian_filter_on_scalarfield and
_compute_gaussian_filter_on_vectorfield so both functions use the single
module-level gaussian_filter.

In @pyphare/pyphare/pharesee/hierarchy/hierarchy.py:
- Around line 618-630: The interpol method uses the provided time only for
flat_finest_field but calls self.level(0) without the time argument for
nbrGhosts, box and dl, and uses list(...)[0] to get the first patch; fix by
calling self.level(0, time=time) (or the correct API to request the same
timestamp) whenever accessing patch data for nbrGhosts, box and dl so those
values match the data passed to flat_finest_field, and replace
list(self.level(0).patches)[0] / list(...)[0] uses with next(iter(...)) to
satisfy the RUF015 suggestion; update references in interpol, specifically
nbrGhosts, box, dl and the self.level calls, and keep build_interpolator and
flat_finest_field usage unchanged.

In @pyphare/pyphare/pharesee/hierarchy/scalarfield.py:
- Line 3: Remove the unused import flat_finest_field from the top of the file
(the import statement "from pyphare.pharesee.hierarchy.hierarchy_utils import
flat_finest_field" in scalarfield.py); ensure no other references to
flat_finest_field remain in this module and run linters/tests to confirm the
unused-import warning is resolved.

In @pyphare/pyphare/pharesee/run/utils.py:
- Line 535: Update the ValueError message to reference the correct function
name: replace the string "make_interpolator is not yet 3d" with
"build_interpolator is not yet 3d" in the raise statement (the one that
currently raises ValueError near the build_interpolator logic) so the error
message matches the actual function name.
- Line 508: The nCells calculation is off by one: change the expression that
sets nCells from using "box.upper - box.lower + 2" to use "box.upper - box.lower
+ 1" (or simply use "box.shape") so it matches Box semantics and aligns with
yeeCoordsFor's internal +1 for primal centering; update the assignment where
nCells is computed in utils.py (the variable nCells and the surrounding
calculation) accordingly.
🧹 Nitpick comments (5)
pyphare/pyphare/core/operators.py (1)

22-29: LGTM with minor cleanup suggestion.

The simplification to directly use patch_datas["value"] is correct since _compute_sqrt operates on ScalarField which always has a "value" key. Consider removing the commented-out line entirely rather than leaving it as a TODO comment.

🧹 Suggested cleanup
 def _compute_sqrt(patch_datas, **kwargs):
-    # ref_name = next(iter(patch_datas.keys())) TODO this is always "value"
-
     dset = np.sqrt(patch_datas["value"][:])

     return (
         {"name": "value", "data": dset, "centering": patch_datas["value"].centerings},
     )
pyphare/pyphare/core/ufuncs.py (2)

76-76: Use next() instead of single-element slice.

Per Ruff RUF015, prefer next(iter(...)) over list(...)[0] for accessing the first element.

♻️ Proposed fix
-    nb_ghosts = np.max(list(hier.level(0).patches[0].patch_datas.values())[0].ghosts_nbr)
+    nb_ghosts = np.max(next(iter(hier.level(0).patches[0].patch_datas.values())).ghosts_nbr)

151-179: Remove commented-out debug code before merging.

Lines 151-163 and 167-179 contain commented-out debug prints. These should be removed to keep the codebase clean.

pyphare/pyphare/pharesee/run/utils.py (1)

493-537: Consider refactoring to reduce duplication with make_interpolator.

build_interpolator shares significant logic with make_interpolator (lines 444-490). The main differences are:

  1. Coordinate generation: build_interpolator uses box while make_interpolator uses domain
  2. nCells calculation differs

Consider extracting the common interpolator construction logic into a shared helper to reduce duplication.

pyphare/pyphare/pharesee/hierarchy/hierarchy.py (1)

618-620: Consider adding a docstring for the new public method.

The interpol method would benefit from documentation explaining its purpose, parameters, and return type. Based on build_interpolator's signature, this returns a dict mapping quantity names to (interpolator, finest_coords) tuples, which isn't immediately obvious.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3741813 and 600adcf.

📒 Files selected for processing (6)
  • pyphare/pyphare/core/gridlayout.py
  • pyphare/pyphare/core/operators.py
  • pyphare/pyphare/core/ufuncs.py
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
  • pyphare/pyphare/pharesee/hierarchy/scalarfield.py
  • pyphare/pyphare/pharesee/run/utils.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-10-18T13:23:32.074Z
Learnt from: PhilipDeegan
Repo: PHAREHUB/PHARE PR: 910
File: pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py:7-7
Timestamp: 2024-10-18T13:23:32.074Z
Learning: In the `pyphare.pharesee.hierarchy` module, importing `PatchHierarchy` and `format_timestamp` from `hierarchy.py` into `hierarchy_utils.py` is acceptable as long as `hierarchy.py` does not import `hierarchy_utils.py`, thereby avoiding a cyclic import.

Applied to files:

  • pyphare/pyphare/core/ufuncs.py
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
  • pyphare/pyphare/pharesee/hierarchy/scalarfield.py
📚 Learning: 2024-09-09T13:57:02.285Z
Learnt from: PhilipDeegan
Repo: PHAREHUB/PHARE PR: 888
File: pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py:566-568
Timestamp: 2024-09-09T13:57:02.285Z
Learning: Avoid commenting on draft PRs for PhilipDeegan unless explicitly requested.

Applied to files:

  • pyphare/pyphare/core/ufuncs.py
🧬 Code graph analysis (4)
pyphare/pyphare/core/ufuncs.py (3)
pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py (2)
  • compute_hier_from (137-160)
  • are_compatible_hierarchies (73-84)
pyphare/pyphare/core/phare_utilities.py (1)
  • listify (46-51)
pyphare/pyphare/pharesee/hierarchy/patch.py (1)
  • Patch (4-69)
pyphare/pyphare/pharesee/run/utils.py (1)
pyphare/pyphare/core/gridlayout.py (3)
  • nbrGhosts (247-251)
  • yeeCoordsFor (150-177)
  • yeeCoordsFor (374-403)
pyphare/pyphare/pharesee/hierarchy/hierarchy.py (2)
pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py (1)
  • flat_finest_field (330-350)
pyphare/pyphare/pharesee/run/utils.py (1)
  • build_interpolator (493-537)
pyphare/pyphare/pharesee/hierarchy/scalarfield.py (1)
pyphare/pyphare/pharesee/hierarchy/hierarchy_utils.py (1)
  • flat_finest_field (330-350)
🪛 GitHub Check: CodeQL
pyphare/pyphare/core/ufuncs.py

[warning] 119-119: Redundant comparison
Test is always false, because of this condition.


[warning] 120-120: Unreachable code
This statement is unreachable.


[notice] 5-5: Unused import
Import of 'are_compatible_hierarchies' is not used.


[notice] 6-6: Unused import
Import of 'listify' is not used.


[notice] 7-7: Unused import
Import of 'Patch' is not used.


[notice] 8-8: Unused import
Import of 'PatchLevel' is not used.


[notice] 66-66: Explicit returns mixed with implicit (fall through) returns
Mixing implicit and explicit returns may indicate an error, as implicit returns always return None.


[notice] 176-179: Commented-out code
This comment appears to contain commented-out code.

pyphare/pyphare/pharesee/hierarchy/hierarchy.py

[notice] 619-619: Cyclic import
Import of module pyphare.pharesee.hierarchy.hierarchy_utils begins an import cycle.

pyphare/pyphare/pharesee/hierarchy/scalarfield.py

[notice] 3-3: Unused import
Import of 'flat_finest_field' is not used.

🪛 Ruff (0.14.10)
pyphare/pyphare/core/ufuncs.py

76-76: Prefer next(...) over single element slice

Replace with next(...)

(RUF015)


173-173: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

pyphare/pyphare/pharesee/run/utils.py

532-532: Avoid specifying long messages outside the exception class

(TRY003)


535-535: Avoid specifying long messages outside the exception class

(TRY003)

pyphare/pyphare/pharesee/hierarchy/hierarchy.py

622-622: Prefer next(...) over single element slice

Replace with next(...)

(RUF015)

⏰ 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). (6)
  • GitHub Check: build (macos-14)
  • GitHub Check: build (ubuntu-latest, clang)
  • GitHub Check: build (ubuntu-latest, gcc)
  • GitHub Check: build (macos-latest)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (cpp)
🔇 Additional comments (3)
pyphare/pyphare/core/gridlayout.py (1)

47-50: LGTM!

The additions of "value", "x", "y", and "z" keys with "primal" centering extend the Yee centering mappings to support ScalarField and VectorField quantities. This is consistent with how moment-like quantities are centered and enables proper centering lookups in operators and ufuncs.

Also applies to: 86-89, 125-128

pyphare/pyphare/core/ufuncs.py (1)

125-126: Verify slice calculation logic.

The slice slice(sigma-nb_ghosts[i], -sigma+nb_ghosts[i]) differs from the pattern used in _compute_gaussian_filter_on_scalarfield which uses slice(nb_ghosts, -nb_ghosts). When sigma < nb_ghosts, this produces slice(negative, positive) which may not behave as expected (e.g., sigma=2, nb_ghosts=4 yields slice(-2, 2)).

pyphare/pyphare/pharesee/hierarchy/hierarchy.py (1)

441-443: LGTM!

The addition of the lw (line width) parameter with a sensible default of 1 is a clean enhancement that maintains backward compatibility while providing additional customization.

Comment on lines +173 to +157
for pk, hg in zip(pks[0], pks[1]['peak_heights']):
pks_ = np.append(pks_, np.add(np.multiply(pk, patch.dl), patch.origin))
hgs_ = np.append(hgs_, hg)
Copy link

@coderabbitai coderabbitai bot Jan 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

peak_heights key requires height argument to be passed.

The code assumes pks[1]['peak_heights'] exists, but scipy.signal.find_peaks only includes this key when the height argument is provided. If a user calls peakIds(hier) without height, this will raise a KeyError.

🐛 Proposed fix
-                for pk, hg in zip(pks[0], pks[1]['peak_heights']):
+                heights = pks[1].get('peak_heights', [None] * len(pks[0]))
+                for pk, hg in zip(pks[0], heights, strict=True):
                     pks_ = np.append(pks_, np.add(np.multiply(pk, patch.dl), patch.origin))
                     hgs_ = np.append(hgs_, hg)

Alternatively, document that height is a required argument, or raise a clear error if it's not provided.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for pk, hg in zip(pks[0], pks[1]['peak_heights']):
pks_ = np.append(pks_, np.add(np.multiply(pk, patch.dl), patch.origin))
hgs_ = np.append(hgs_, hg)
heights = pks[1].get('peak_heights', [None] * len(pks[0]))
for pk, hg in zip(pks[0], heights, strict=True):
pks_ = np.append(pks_, np.add(np.multiply(pk, patch.dl), patch.origin))
hgs_ = np.append(hgs_, hg)
🧰 Tools
🪛 Ruff (0.14.10)

173-173: zip() without an explicit strict= parameter

Add explicit value for parameter strict=

(B905)

🤖 Prompt for AI Agents
In @pyphare/pyphare/core/ufuncs.py around lines 173 - 175, The loop assumes
pks[1]['peak_heights'] exists causing a KeyError when find_peaks was called
without height; to fix, before the loop obtain heights =
pks[1].get('peak_heights') and if None compute heights from the underlying
signal at the peak indices (e.g., heights = signal_array[pks[0]]), then iterate
for pk, hg in zip(pks[0], heights) and append to pks_ and hgs_; alternatively,
if computing heights is impossible here, raise a clear error indicating that the
height argument is required.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmmm... I realize that the output of find_peaks depend on the kwargs... meaning that their height for example are given only if this kwargs is set. We hence need to discuss on how to do it. For now the kwargs peak_heights is mandatory to circumbent this problem.

Copy link

Choose a reason for hiding this comment

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

@rochSmets, thanks for clarifying! I understand you're making height a required parameter for now.

To prevent users from encountering a confusing KeyError, I'd suggest one of these approaches:

  1. Add validation at the start of peakIds:

    if "height" not in kwargs:
        raise ValueError("The 'height' parameter is required for peakIds")
  2. Add a docstring documenting the requirement:

    def peakIds(hier, **kwargs):
        """
        Find peaks in hierarchy data.
        
        Parameters:
            hier: Hierarchy object
            **kwargs: Arguments forwarded to scipy.signal.find_peaks.
                      The 'height' parameter is required.
        """
  3. Handle the missing key gracefully:

    if 'peak_heights' in pks[1]:
        for pk, hg in zip(pks[0], pks[1]['peak_heights']):
            pks_ = np.append(pks_, np.add(np.multiply(pk, patch.dl), patch.origin))
            hgs_ = np.append(hgs_, hg)
    else:
        # Handle case where height wasn't provided

Option 1 gives the clearest error message to users. Let me know if you'd like me to help implement any of these!

@@ -1,5 +1,6 @@
from .hierarchy import PatchHierarchy
from .hierarchy_utils import compute_hier_from, compute_rename, rename, _compute_neg
from pyphare.pharesee.hierarchy.hierarchy_utils import flat_finest_field
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove unused import.

The flat_finest_field import is not used anywhere in this file. As flagged by static analysis, this should be removed.

🧹 Proposed fix
 from .hierarchy import PatchHierarchy
-from .hierarchy_utils import compute_hier_from, compute_rename, rename, _compute_neg
-from pyphare.pharesee.hierarchy.hierarchy_utils import flat_finest_field
+from .hierarchy_utils import compute_hier_from, compute_rename, rename, _compute_neg
🧰 Tools
🪛 GitHub Check: CodeQL

[notice] 3-3: Unused import
Import of 'flat_finest_field' is not used.

🤖 Prompt for AI Agents
In @pyphare/pyphare/pharesee/hierarchy/scalarfield.py at line 3, Remove the
unused import flat_finest_field from the top of the file (the import statement
"from pyphare.pharesee.hierarchy.hierarchy_utils import flat_finest_field" in
scalarfield.py); ensure no other references to flat_finest_field remain in this
module and run linters/tests to confirm the unused-import warning is resolved.


dim = coords.ndim

nCells = box.upper-box.lower+2
Copy link

@coderabbitai coderabbitai bot Jan 12, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how nCells is calculated elsewhere in the codebase
rg -n "nCells\s*=" --type py -C 3

Repository: PHAREHUB/PHARE

Length of output: 1088


🏁 Script executed:

# Also inspect the specific file and context around line 508
sed -n '500,520p' pyphare/pyphare/pharesee/run/utils.py | cat -n

Repository: PHAREHUB/PHARE

Length of output: 723


🏁 Script executed:

# Search for box.upper and box.lower usage to understand the context
rg -n "box\.(upper|lower)" --type py -B2 -A2 | head -100

Repository: PHAREHUB/PHARE

Length of output: 7568


🏁 Script executed:

# Get broader context around line 508
sed -n '490,530p' pyphare/pyphare/pharesee/run/utils.py | cat -n

Repository: PHAREHUB/PHARE

Length of output: 1600


🏁 Script executed:

# Find the yeeCoordsFor function definition
rg -n "def yeeCoordsFor" --type py -A10

Repository: PHAREHUB/PHARE

Length of output: 1768


🏁 Script executed:

# Search for documentation or comments about nCells in relation to primal/dual centering
rg -n "primal|dual|centering" --type py -i -C2 | head -60

Repository: PHAREHUB/PHARE

Length of output: 4222


🏁 Script executed:

# Get full yeeCoordsFor function implementation
sed -n '150,200p' pyphare/pyphare/core/gridlayout.py | cat -n

Repository: PHAREHUB/PHARE

Length of output: 1952


🏁 Script executed:

# Look for how yeeCoordsFor is used elsewhere with nbrCells parameter
rg -n "yeeCoordsFor" --type py -B2 -A2 | grep -E "(yeeCoordsFor|nCells|nbrCells)" | head -40

Repository: PHAREHUB/PHARE

Length of output: 1457


🏁 Script executed:

# Check if there are any tests or examples for build_interpolator
rg -n "build_interpolator" --type py -B3 -A3

Repository: PHAREHUB/PHARE

Length of output: 1909


🏁 Script executed:

# Search for Box class definition and nCells method
rg -n "class Box|def nCells" --type py -B1 -A5 | head -80

Repository: PHAREHUB/PHARE

Length of output: 2257


🏁 Script executed:

# Look for any comments or documentation around the build_interpolator function
sed -n '493,515p' pyphare/pyphare/pharesee/run/utils.py | cat -n

Repository: PHAREHUB/PHARE

Length of output: 944


🏁 Script executed:

# Check what the typical pattern is for cell counts in the codebase
rg -n "upper\s*-\s*lower\s*\+" --type py | head -20

Repository: PHAREHUB/PHARE

Length of output: 40


🏁 Script executed:

# Get the full Box class implementation
sed -n '5,78p' pyphare/pyphare/core/box.py | cat -n

Repository: PHAREHUB/PHARE

Length of output: 2710


🏁 Script executed:

# Look for tests or usage of build_interpolator
rg -n "build_interpolator" --type py -B5 -A10

Repository: PHAREHUB/PHARE

Length of output: 3487


🏁 Script executed:

# Check how shape is computed in Box
rg -n "def shape|@property" pyphare/pyphare/core/box.py -A3

Repository: PHAREHUB/PHARE

Length of output: 200


🏁 Script executed:

# Check how yeeCoordsFor is called with nCells in other contexts
rg -n "yeeCoordsFor" pyphare/pyphare/pharesee/run/utils.py -B3 -A3

Repository: PHAREHUB/PHARE

Length of output: 1269


🏁 Script executed:

# Look for any tests that use build_interpolator to understand the expected behavior
fd -e py -path "*/test*" | xargs rg -l "build_interpolator" | head -5

Repository: PHAREHUB/PHARE

Length of output: 329


🏁 Script executed:

# Check if nCells should match Box.shape or if there's a specific reason for +2
rg -n "upper.*lower.*1|shape.*prod" pyphare/pyphare/core/box.py -B2 -A2

Repository: PHAREHUB/PHARE

Length of output: 858


🏁 Script executed:

# Let's check how box.shape relates to the calculation and verify nCells usage
python3 << 'EOF'
# If box.upper=10, box.lower=5:
upper, lower = 10, 5

# Current formula at line 508:
ncells_current = upper - lower + 2
print(f"Current formula (upper - lower + 2): {ncells_current}")

# Box.shape formula:
box_shape = upper - lower + 1
print(f"Box.shape formula (upper - lower + 1): {box_shape}")

# Difference:
print(f"Difference: {ncells_current - box_shape}")

# This means yeeCoordsFor will receive nCells that's 1 larger than box.shape
# Since yeeCoordsFor adds 1 for primal centering, the final size would be:
print(f"\nWith primal centering in yeeCoordsFor:")
print(f"Final size = nCells + 1 = {ncells_current + 1}")
print(f"vs box.shape + 1 = {box_shape + 1}")
EOF

Repository: PHAREHUB/PHARE

Length of output: 144


🏁 Script executed:

# Look for tests of build_interpolator or similar interpolation code
rg -n "interpol" pyphare/pyphare/pharesee/hierarchy/hierarchy.py -B5 -A15 | head -50

Repository: PHAREHUB/PHARE

Length of output: 1319


🏁 Script executed:

# Search for any comments or issues related to nCells or interpolation
rg -n "nCells|interpolat" pyphare/pyphare/pharesee/run/utils.py -B2 -A2

Repository: PHAREHUB/PHARE

Length of output: 2870


🏁 Script executed:

# Let's verify by checking if there are any related bug reports or comments
rg -n "TODO|FIXME|BUG|HACK" pyphare/pyphare/pharesee/run/utils.py -B2 -A2

Repository: PHAREHUB/PHARE

Length of output: 40


🏁 Script executed:

# Check the git history for context on this calculation
git log -p --all -S "box.upper-box.lower+2" -- pyphare/pyphare/pharesee/run/utils.py | head -100

Repository: PHAREHUB/PHARE

Length of output: 40


🏁 Script executed:

# Check if nCells is truly a scalar or array in build_interpolator context
sed -n '493,540p' pyphare/pyphare/pharesee/run/utils.py | cat -n

Repository: PHAREHUB/PHARE

Length of output: 1787


🏁 Script executed:

# Verify Box properties for multi-dimensional cases
rg -n "class.*Box|def __init__" pyphare/pyphare/core/box.py -A8 | head -50

Repository: PHAREHUB/PHARE

Length of output: 1408


🏁 Script executed:

# Check test cases to see expected box behavior
rg -n "Box\(" pyphare/pyphare_tests/test_core/test_box.py -B1 -A1 | head -40

Repository: PHAREHUB/PHARE

Length of output: 1933


🏁 Script executed:

# Check if nCells is being treated as array and compare with Box.shape behavior
python3 << 'EOF'
import numpy as np

# Simulate Box behavior
lower = np.array([5, 10])
upper = np.array([10, 20])

# Current formula in line 508
nCells_current = upper - lower + 2
print(f"Current formula (upper - lower + 2): {nCells_current}")

# Box.shape formula
box_shape = upper - lower + 1
print(f"Box.shape formula (upper - lower + 1): {box_shape}")

# What yeeCoordsFor receives
print(f"\nEach dimension:")
for i, (curr, shape) in enumerate(zip(nCells_current, box_shape)):
    print(f"  Dim {i}: current={curr}, shape={shape}, difference={curr - shape}")
EOF

Repository: PHAREHUB/PHARE

Length of output: 175


🏁 Script executed:

# Search for any documentation about why +2 is used specifically
rg -n "nCells|box\.upper|box\.lower" pyphare/pyphare/pharesee/run/utils.py -B5 -A1 | grep -A5 -B5 "508"

Repository: PHAREHUB/PHARE

Length of output: 404


🏁 Script executed:

# Check if there's any reference to the difference between make_interpolator and build_interpolator
git log --oneline --all -- pyphare/pyphare/pharesee/run/utils.py | head -20

Repository: PHAREHUB/PHARE

Length of output: 188


🏁 Script executed:

# Let's check if there are any tests for build_interpolator or the hierarchy.interpol method
find . -name "*.py" -type f -path "*/test*" -exec rg -l "interpol|build_interpolator" {} \;

Repository: PHAREHUB/PHARE

Length of output: 650


🏁 Script executed:

# Compare the two similar functions side by side
diff <(sed -n '444,490p' pyphare/pyphare/pharesee/run/utils.py) <(sed -n '493,537p' pyphare/pyphare/pharesee/run/utils.py)

Repository: PHAREHUB/PHARE

Length of output: 145


🏁 Script executed:

# Compare the nCells calculations between make_interpolator and build_interpolator
rg -n "nCells\s*=" pyphare/pyphare/pharesee/run/utils.py -B2 -A5

Repository: PHAREHUB/PHARE

Length of output: 733


🏁 Script executed:

# Check the interpolator tests to see if they validate the behavior
cat tests/core/numerics/interpolator/interpolator_test.py | head -100

Repository: PHAREHUB/PHARE

Length of output: 2790


🏁 Script executed:

# Look for any documentation in comments about the difference
sed -n '444,450p' pyphare/pyphare/pharesee/run/utils.py && echo "---" && sed -n '493,502p' pyphare/pyphare/pharesee/run/utils.py

Repository: PHAREHUB/PHARE

Length of output: 768


🏁 Script executed:

# Let's trace through the logic: check the actual usage in hierarchy.py to understand what box is
sed -n '618,630p' pyphare/pyphare/pharesee/hierarchy/hierarchy.py

Repository: PHAREHUB/PHARE

Length of output: 708


🏁 Script executed:

# Check if PatchData box includes or excludes ghosts
rg -n "class PatchData|def box|\.box\s*=" pyphare/pyphare/pharesee/hierarchy/patchdata.py -B1 -A3 | head -60

Repository: PHAREHUB/PHARE

Length of output: 346


🏁 Script executed:

# Final check: see if the +2 is consistent with including boundaries on both sides
python3 << 'EOF'
# If box represents inclusive cell indices from lower to upper
# For a 1D example: box.lower=5, box.upper=10
# Number of cells = 10 - 5 + 1 = 6 cells (indices 5,6,7,8,9,10)

# But for interpolation coordinates, we might want:
# - Points at cell boundaries: need 7 points (before cell 5, between each, after cell 10)
# That would be: upper - lower + 2

# This makes sense! The +2 gives the number of coordinate points needed
# if cells are interpreted as intervals from one point to the next

print("If cells are indexed intervals:")
print("  Box [5, 10] = 6 cells")
print("  Need 7 coordinate points for boundaries")
print("  Formula: 10 - 5 + 2 = 7 ✓")
print()
print("If cells are point locations (primal/dual on Yee grid):")
print("  Box [5, 10] = 6 cell centers")
print("  yeeCoordsFor adds +1 for primal -> 7 total")
print("  Formula should be: 10 - 5 + 1 = 6 (input), +1 inside yeeCoordsFor = 7")
EOF

Repository: PHAREHUB/PHARE

Length of output: 388


🏁 Script executed:

# Check the actual yeeCoordsFor logic more carefully with the nCells parameter
sed -n '150,175p' pyphare/pyphare/core/gridlayout.py | cat -n

Repository: PHAREHUB/PHARE

Length of output: 1032


🏁 Script executed:

# Look for tests that might validate build_interpolator behavior
rg -n "build_interpolator\|hierarchy.*interpol" tests/ -l

Repository: PHAREHUB/PHARE

Length of output: 40


🏁 Script executed:

# Check if there are integration tests using hierarchy.interpol
rg -n "\.interpol\(" tests/ -B3 -A3 | head -40

Repository: PHAREHUB/PHARE

Length of output: 40


Fix nCells calculation to match Box semantics.

The formula box.upper - box.lower + 2 adds an extra coordinate point beyond what the Box indices define. Should use box.upper - box.lower + 1 (equivalent to box.shape) since yeeCoordsFor adds 1 internally for primal centering. The current formula results in one extra coordinate extending beyond the box bounds.

🤖 Prompt for AI Agents
In @pyphare/pyphare/pharesee/run/utils.py at line 508, The nCells calculation is
off by one: change the expression that sets nCells from using "box.upper -
box.lower + 2" to use "box.upper - box.lower + 1" (or simply use "box.shape") so
it matches Box semantics and aligns with yeeCoordsFor's internal +1 for primal
centering; update the assignment where nCells is computed in utils.py (the
variable nCells and the surrounding calculation) accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

irrelevant

Copy link

Choose a reason for hiding this comment

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

@rochSmets, understood. I've noted your feedback on this comment.

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.

1 participant