Skip to content

dis_plot can plot a single point by particle and also a cut#1119

Open
rochSmets wants to merge 6 commits intoPHAREHUB:masterfrom
rochSmets:dplot
Open

dis_plot can plot a single point by particle and also a cut#1119
rochSmets wants to merge 6 commits intoPHAREHUB:masterfrom
rochSmets:dplot

Conversation

@rochSmets
Copy link
Contributor

To plot a dis_plot using a point per particle (and not a pcolormesh of a given binning of the phase space), the command line could be

p,f = ions.dist_plot(axis=("x", "Vx"),
                     ax=ax,
                     norm = 0.4,
                     finest=True,
                     plain=True,
                     stride=10,
                     color='tab:gray',
                     alpha=1,
                     markersize=2,
                     vmin=-1,vmax=1,
                     title="weak perturbation at time : {:.2f}".format(time),
                    )

where plain says we want a point per particle, stride says that we plot 1 over stride particle, color, alpha and markersize being straightforward.

It could also be

p,(f, a) = ions.dist_plot(axis=("Vx",),
                          drawstyle="default",
                          ax=ax,
                          finest=True,
                          cuts=((40, 80),),
                          vmin=-0.6,vmax=0.6,
                          dv=0.02,
                          title="weak perturbation at time : {:.2f}".format(time),
                         )

to have a 1-D distribution (that is f(v_x) in this example). By default, drawstyle="mid-steps" to make sure that the choice for the binning is explicit, so that drawstyle="default" can be a kwargs. Hence, cuts is a tuple of dim ndim, where each element is a tuple of 2 elements containg lower and upper values of the cutting box for the iven direction. By default, cuts=None, meaning that all the particles are considered.

It has to be clear that even if the kwarg name is cut, this is a cut in position space, but not in velocity space. It means that in the plot of f(v_x) for example, we have an implicit summation over v_y and v_z.

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

dist_plot now branches on axis length to handle 1D vs 2D plotting, adding explicit 2D histogram creation, optional Gaussian/median filtering, and plain vs color-mapped rendering. Hierarchy aggregation now guards against empty patch lists and avoids indexing single-element axes.

Changes

Cohort / File(s) Summary
Hierarchy axis & aggregation
pyphare/pyphare/pharesee/hierarchy/hierarchy.py
Avoids indexing axis[0] for single-element axes; when aggregating non-finest data, handle empty patch_datas by setting tmp=None and conditionally merging to skip None.
Plotting: 2D histogram, filtering, rendering
pyphare/pyphare/pharesee/plotting.py
Adds explicit len(axis) == 2 branch using histogram2d with weights; introduces mutually exclusive gaussian and median filtering, and two render modes (color-mapped image with colorbar or plain markers/line).
Plotting: 1D path, cuts, and labeling
pyphare/pyphare/pharesee/plotting.py
For 1D, supports cuts (Box) producing new_particles used for histogramming, averages, and bulk lines; enforces velocity-axis checks for abscissa; updates default labels and units (e.g., "$x \\ (d_p)$", "$y \\ (d_p)$").

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant dist_plot
  participant HistEngine as Histogram Engine
  participant Filter as Filter (Gaussian/Median)
  participant Renderer as Matplotlib Renderer

  Caller->>dist_plot: call with axis, vbins, weights, cuts, options
  dist_plot->>dist_plot: evaluate len(axis)
  alt 2D path
    dist_plot->>HistEngine: compute 2D histogram (histogram2d) with weights
    HistEngine-->>dist_plot: hist, xedges, yedges
    alt filtering requested
      dist_plot->>Filter: apply gaussian or median filter
      Filter-->>dist_plot: filtered_hist
    end
    dist_plot->>Renderer: render image (colormap + colorbar) or plain markers/lines
    Renderer-->>Caller: display/return figure
  else 1D path
    dist_plot->>dist_plot: apply cuts -> new_particles (optional)
    dist_plot->>HistEngine: compute 1D histogram from (new_)particles
    HistEngine-->>dist_plot: hist, edges
    dist_plot->>Renderer: plot line (drawstyle) and averages/bulk lines
    Renderer-->>Caller: display/return figure
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: adding point-per-particle plotting (via plain parameter) and cut/box selection functionality to dist_plot.
Description check ✅ Passed The description is directly related to the changeset, providing concrete code examples and clear explanations of the new plain and cuts parameters for dist_plot functionality.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

stride = kwargs.get("stride", 1)
markersize = kwargs.get("markersize", 0.5)
alpha = kwargs.get("alpha", 0.5)
im = ax.plot(x[::stride], y[::stride],

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable im is not used.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep, like the one defined with the previous pcolormesh. But lets keep this handle around !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep, but let's keep it,

cmin = kwargs.get("color_min", h.min())
cmin = max(cmin, 1e-4)
xh_ = 0.5*(xh[:-1]+xh[1:])
im = ax.plot(xh_, h, drawstyle=drawstyle)

Check notice

Code scanning / CodeQL

Unused local variable Note

Variable im is not used.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

see above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the same,

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pyphare/pyphare/pharesee/plotting.py (3)

167-184: new_particles undefined in 2D axis mode, causing NameError when bulk=True.

The variable new_particles is only assigned in the len(axis) == 1 branch (lines 124-136). When len(axis) == 2 and bulk=True, this code will raise NameError.

Additionally, the reference to axis[1] at line 177 will cause IndexError in 1D mode.

🐛 Proposed fix
     if "bulk" in kwargs:
         if kwargs["bulk"] is True:
+            # Use particles for 2D, new_particles for 1D
+            bulk_particles = new_particles if len(axis) == 1 else particles
             if axis[0] in vaxis:
                 ax.axvline(
                     np.average(
-                        new_particles.v[:, vaxis[axis[0]]], weights=new_particles.weights
+                        bulk_particles.v[:, vaxis[axis[0]]], weights=bulk_particles.weights
                     ),
                     color="w",
                     ls="--",
                 )
-            if axis[1] in vaxis:
+            if len(axis) > 1 and axis[1] in vaxis:
                 ax.axhline(
                     np.average(
-                        new_particles.v[:, vaxis[axis[1]]], weights=new_particles.weights
+                        bulk_particles.v[:, vaxis[axis[1]]], weights=bulk_particles.weights
                     ),
                     color="w",
                     ls="--",
                 )

189-197: interp feature uses variables only defined in 2D mode.

The interpolation logic references yh and image, which are only defined when len(axis) == 2. If interp=True with a 1D axis specification, this will raise NameError.

🐛 Proposed fix: guard interp block for 2D only
     interp = kwargs.get("interp", False)

-    if interp:
+    if interp and len(axis) == 2:
         xbins = 0.5 * (xh[1:] + xh[:-1])
         ybins = 0.5 * (yh[1:] + yh[:-1])
         xx, yy = np.meshgrid(xbins, ybins, indexing="ij")
         coords = np.array([xx.flatten(), yy.flatten()]).T
         interpdist = LinearNDInterpolator(coords, image.T.flatten())
         return fig, ax, interpdist, xbins, ybins
+    elif interp and len(axis) == 1:
+        raise ValueError("interp=True is not supported for 1D axis plots")

154-157: KDE feature uses y which is undefined in 1D mode.

The kde block references both x and y, but in the 1D axis branch, y is never defined. This will cause NameError when kde=True with a single-axis specification.

🐛 Proposed fix: guard KDE for 2D only
-    if kwargs.get("kde", False) is True:
+    if kwargs.get("kde", False) is True and len(axis) == 2:
         import seaborn as sns

         sns.kdeplot(x=x, y=y, ax=ax, color="w")
🤖 Fix all issues with AI agents
In @pyphare/pyphare/pharesee/plotting.py:
- Around line 95-101: The code sets norm only for "log" and "linear" color_scale
values, so if color_scale is anything else norm remains uninitialized and
ax.pcolormesh will raise UnboundLocalError; fix by adding an explicit else
branch after the existing if/elif to either (a) raise a clear ValueError
mentioning the unsupported color_scale, or (b) fall back to a default (e.g., set
norm = Normalize(cmin, cmax)); update the block around
color_scale/LogNorm/Normalize and the ax.pcolormesh call so norm is always
defined or an exception is raised with a helpful message.
- Around line 56-67: The code in the block handling len(axis)==2 can leave x or
y undefined (used later in np.histogram2d) when axis values aren't matched;
ensure x and y are always assigned before the histogram call by handling all
expected axis name cases (e.g., check axis[0]/axis[1] for vaxis entries and for
explicit spatial names like "x","y","z"), and if an unknown axis is passed raise
a clear ValueError; update the branch logic around vaxis, axis, and particles
(the symbols vaxis, axis, particles, and the histogram2d call) so x and y are
initialized for every valid input path or an explicit error is thrown.
🧹 Nitpick comments (1)
pyphare/pyphare/pharesee/plotting.py (1)

387-388: LaTeX string uses \\ which renders as a line break, not a space.

The double backslash in LaTeX ($x \\ (d_p)$) creates a line break. For a space between x and (d_p), use \ (backslash-space) or a regular space.

♻️ Suggested fix
-    ax.set_xlabel(kwargs.get("xlabel", "$x \\ (d_p)$"))
-    ax.set_ylabel(kwargs.get("ylabel", "$y \\ (d_p)$"))
+    ax.set_xlabel(kwargs.get("xlabel", r"$x\ (d_p)$"))
+    ax.set_ylabel(kwargs.get("ylabel", r"$y\ (d_p)$"))

Using raw strings (r"...") avoids escaping issues with backslashes.

📜 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 7c0c1a8.

📒 Files selected for processing (2)
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
  • pyphare/pyphare/pharesee/plotting.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/pharesee/hierarchy/hierarchy.py
🧬 Code graph analysis (1)
pyphare/pyphare/pharesee/plotting.py (2)
pyphare/pyphare/pharesee/particles.py (4)
  • x (46-49)
  • y (52-55)
  • size (75-76)
  • select (102-135)
pyphare/pyphare/core/box.py (1)
  • Box (5-69)
🪛 GitHub Check: CodeQL
pyphare/pyphare/pharesee/plotting.py

[failure] 66-66: Potentially uninitialized local variable
Local variable 'x' may be used before it is initialized.


[failure] 66-66: Potentially uninitialized local variable
Local variable 'y' may be used before it is initialized.


[failure] 101-101: Potentially uninitialized local variable
Local variable 'norm' may be used before it is initialized.


[failure] 145-145: Potentially uninitialized local variable
Local variable 'x' may be used before it is initialized.


[notice] 109-109: Unused local variable
Variable im is not used.


[notice] 149-149: Unused local variable
Variable im is not used.

🪛 Ruff (0.14.10)
pyphare/pyphare/pharesee/plotting.py

82-84: 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 (macos-latest)
  • GitHub Check: build (macos-14)
  • GitHub Check: build (ubuntu-latest, gcc)
  • GitHub Check: build (ubuntu-latest, clang)
🔇 Additional comments (1)
pyphare/pyphare/pharesee/hierarchy/hierarchy.py (1)

586-594: LGTM!

The axis length branching logic correctly handles both 2D and 1D axis specifications. The 2D case preserves the original behavior, and the 1D case appropriately assigns vbins directly without attempting to access axis[0].

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pyphare/pyphare/pharesee/plotting.py (3)

162-165: NameError when kde=True with 1D axis.

In the 1D branch (len(axis) == 1), only x is defined. The KDE section references y which will raise NameError. KDE plotting should be guarded to only run for 2D cases.

🐛 Proposed fix
-    if kwargs.get("kde", False) is True:
+    if kwargs.get("kde", False) is True and len(axis) == 2:
         import seaborn as sns
 
         sns.kdeplot(x=x, y=y, ax=ax, color="w")

175-192: NameError and IndexError in bulk velocity section.

Two issues here:

  1. new_particles undefined in 2D branch: new_particles is only assigned in the 1D branch (lines 134-142). When using bulk=True with a 2D axis, lines 180 and 188 will raise NameError.

  2. IndexError on axis[1] in 1D case: Line 185 accesses axis[1], but when len(axis) == 1, this will raise IndexError.

🐛 Proposed fix
     if "bulk" in kwargs:
         if kwargs["bulk"] is True:
+            # Use appropriate particles reference based on branch
+            bulk_particles = new_particles if len(axis) == 1 else particles
             if axis[0] in vaxis:
                 ax.axvline(
                     np.average(
-                        new_particles.v[:, vaxis[axis[0]]], weights=new_particles.weights
+                        bulk_particles.v[:, vaxis[axis[0]]], weights=bulk_particles.weights
                     ),
                     color="w",
                     ls="--",
                 )
-            if axis[1] in vaxis:
+            if len(axis) == 2 and axis[1] in vaxis:
                 ax.axhline(
                     np.average(
-                        new_particles.v[:, vaxis[axis[1]]], weights=new_particles.weights
+                        bulk_particles.v[:, vaxis[axis[1]]], weights=bulk_particles.weights
                     ),
                     color="w",
                     ls="--",
                 )

197-205: NameError when interp=True with 1D axis.

Variables yh and image are only defined in the 2D branch. Using interp=True with a 1D axis will raise NameError. The interpolation feature is inherently 2D, so it should be guarded.

🐛 Proposed fix
     interp = kwargs.get("interp", False)
 
-    if interp:
+    if interp and len(axis) == 2:
         xbins = 0.5 * (xh[1:] + xh[:-1])
         ybins = 0.5 * (yh[1:] + yh[:-1])
         xx, yy = np.meshgrid(xbins, ybins, indexing="ij")
         coords = np.array([xx.flatten(), yy.flatten()]).T
         interpdist = LinearNDInterpolator(coords, image.T.flatten())
         return fig, ax, interpdist, xbins, ybins
+    elif interp and len(axis) == 1:
+        raise ValueError("interp=True is only supported for 2D axis")
🤖 Fix all issues with AI agents
In `@pyphare/pyphare/pharesee/plotting.py`:
- Around line 130-140: The cuts are meant to be in position space but
particles.select(box_new) defaults to cell indices; update the ndim==1 branch in
plotting.py to call particles.select(box_new, box_type="pos") so the Box bounds
are interpreted as positions (use symbols cuts, Box, particles.select, ndim); do
not change the 2D/3D branches until particles.select supports box_type="pos" for
ndim>1 (or add an explicit guard/clear TODO comment/assert there).
📜 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 7c0c1a8 and a752c9d.

📒 Files selected for processing (2)
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
  • pyphare/pyphare/pharesee/plotting.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
🧰 Additional context used
🧬 Code graph analysis (1)
pyphare/pyphare/pharesee/plotting.py (2)
pyphare/pyphare/pharesee/particles.py (3)
  • x (46-49)
  • y (52-55)
  • select (102-135)
pyphare/pyphare/core/box.py (1)
  • Box (5-69)
🪛 Ruff (0.14.11)
pyphare/pyphare/pharesee/plotting.py

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

(TRY003)


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

(TRY003)


86-88: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


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

56-71: LGTM for axis extraction and error handling.

The uninitialized variable issues flagged in previous reviews are now properly addressed with explicit ValueError raises for unsupported axis combinations.


73-88: Well-structured filter handling with proper mutual exclusion.

The logic correctly ensures gaussian and median filters cannot be applied simultaneously, with a clear error message when both are specified.


395-396: Good improvement to default axis labels with units.

The updated default labels with proper LaTeX formatting and units ($d_p$) improve clarity for users.

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

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
pyphare/pyphare/pharesee/plotting.py (3)

162-165: NameError when kde=True with 1D axis.

The KDE plot uses both x and y, but y is only defined in the 2D branch. Using kde=True with len(axis) == 1 will crash.

🐛 Proposed fix to guard KDE for 2D only
-    if kwargs.get("kde", False) is True:
+    if kwargs.get("kde", False) is True and len(axis) == 2:
         import seaborn as sns

         sns.kdeplot(x=x, y=y, ax=ax, color="w")

175-192: NameError when bulk=True with 2D axis.

new_particles is only assigned in the 1D branch (inside len(axis) == 1). For 2D plots, this code path will crash because new_particles is undefined.

🐛 Proposed fix to use correct particle reference
     if "bulk" in kwargs:
         if kwargs["bulk"] is True:
+            # Use new_particles if defined (1D with cuts), otherwise use particles
+            p = new_particles if 'new_particles' in dir() else particles
             if axis[0] in vaxis:
                 ax.axvline(
                     np.average(
-                        new_particles.v[:, vaxis[axis[0]]], weights=new_particles.weights
+                        p.v[:, vaxis[axis[0]]], weights=p.weights
                     ),
                     color="w",
                     ls="--",
                 )
-            if axis[1] in vaxis:
+            if len(axis) == 2 and axis[1] in vaxis:
                 ax.axhline(
                     np.average(
-                        new_particles.v[:, vaxis[axis[1]]], weights=new_particles.weights
+                        p.v[:, vaxis[axis[1]]], weights=p.weights
                     ),
                     color="w",
                     ls="--",
                 )

Alternatively, define new_particles = particles at the start of dist_plot and only reassign when cuts are applied.


197-205: NameError when interp=True with 1D axis.

Variables yh and image are only defined in the 2D branch. The interpolation feature will crash for 1D plots.

🐛 Proposed fix to guard interp for 2D only
     interp = kwargs.get("interp", False)

-    if interp:
+    if interp and len(axis) == 2:
         xbins = 0.5 * (xh[1:] + xh[:-1])
         ybins = 0.5 * (yh[1:] + yh[:-1])
         xx, yy = np.meshgrid(xbins, ybins, indexing="ij")
         coords = np.array([xx.flatten(), yy.flatten()]).T
         interpdist = LinearNDInterpolator(coords, image.T.flatten())
         return fig, ax, interpdist, xbins, ybins
+    elif interp and len(axis) == 1:
+        raise ValueError("interp=True is only supported for 2D distribution plots")
🧹 Nitpick comments (1)
pyphare/pyphare/pharesee/plotting.py (1)

126-126: Dead code: bins is reassigned before use.

This assignment is immediately overwritten at line 151 before bins is ever used. Remove this line to avoid confusion.

♻️ Suggested fix
     elif len(axis) == 1:
-        bins = kwargs.get("bins", (50))
         cuts = kwargs.get("cuts", None)
         ndim = particles.ndim
📜 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 a752c9d and 004ff09.

📒 Files selected for processing (2)
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
  • pyphare/pyphare/pharesee/plotting.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyphare/pyphare/pharesee/hierarchy/hierarchy.py
🧰 Additional context used
🧬 Code graph analysis (1)
pyphare/pyphare/pharesee/plotting.py (2)
pyphare/pyphare/pharesee/particles.py (3)
  • x (46-49)
  • y (52-55)
  • select (102-135)
pyphare/pyphare/core/box.py (1)
  • Box (5-69)
🪛 Ruff (0.14.11)
pyphare/pyphare/pharesee/plotting.py

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

(TRY003)


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

(TRY003)


86-88: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


149-149: 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: build (ubuntu-latest, gcc)
  • GitHub Check: build (ubuntu-latest, clang)
  • GitHub Check: Analyze (cpp)
  • GitHub Check: build (macos-14)
  • GitHub Check: build (macos-latest)
  • GitHub Check: Analyze (python)
🔇 Additional comments (2)
pyphare/pyphare/pharesee/plotting.py (2)

56-71: LGTM - Axis handling properly guards against uninitialized variables.

The ValueError raises for unsupported axis combinations correctly address the static analysis warnings from previous reviews. The explicit error messages make the supported axis combinations clear.


395-396: LGTM - Axis labels updated to use ion inertial length units.

The label change from $x c / \omega_p$ to $x \ (d_p)$ provides cleaner notation using the ion inertial length normalization.

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

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pyphare/pyphare/pharesee/plotting.py (1)

175-189: new_particles is undefined in 2‑D when bulk=True.
Line 180/188 uses new_particles, but it’s only assigned in the 1‑D branch. This raises UnboundLocalError for 2‑D plots with bulk=True.

🧩 Suggested fix
     axis = kwargs.get("axis", ("Vx", "Vy"))
     vaxis = {"Vx": 0, "Vy": 1, "Vz": 2}
+    new_particles = particles
🤖 Fix all issues with AI agents
In `@pyphare/pyphare/pharesee/plotting.py`:
- Around line 146-149: The 1-D branch fails because y is never defined but
sns.kdeplot is later called with y=y; in the block handling len(axis)==1 (when
axis[0] in vaxis), set y=None (or call sns.kdeplot without the y argument) and
ensure the subsequent call to sns.kdeplot uses only x when y is None; update the
code paths around the axis/vaxis check and the sns.kdeplot invocation
(references: axis, vaxis, new_particles.v, and sns.kdeplot) so that for 1-D
plots you pass only x (or x and y=None) and avoid referencing an undefined y
(apply same fix to the other occurrence around lines 162-165).
- Around line 20-22: The docstring for the "axis" parameter in
pyphare.pyphare.pharesee.plotting.py contains a malformed entry `(Vy")`; update
the axis list so all entries are properly quoted and comma-separated (e.g.,
replace `(Vy")` with ("Vy") and ensure consistency like ("Vx"), ("Vy"), ("Vz")
and pairs ("Vx","Vy") etc.) in the plotting module's docstring so the axis
options are readable and valid.
- Around line 111-120: The code reads stride = kwargs.get("stride", 1) and then
uses x[::stride] which fails for stride=0; update the plotting function to
validate/coerce stride before slicing: ensure stride is an integer >= 1 (e.g.
convert via int() and if <= 0 either set stride = 1 or raise a ValueError with a
clear message), and then use that validated stride when calling ax.plot (the
variables to look for are the stride assignment and the ax.plot call where
x[::stride], y[::stride] are used).
- Around line 127-134: The code assumes cuts[0] is a tuple and fails if it is
None; update the ndim==1 branch to handle None entries by checking cuts[0] is
not None before constructing Box and calling particles.select: if cuts[0] is
None leave new_particles as the original particles (or skip selection),
otherwise create box_new = Box(cuts[0][0], cuts[0][1]) and set new_particles =
particles.select(box_new, box_type="pos"); ensure this pattern (check for None)
is applied wherever cuts[i] is used so particles.select is only called with a
valid Box.
♻️ Duplicate comments (1)
pyphare/pyphare/pharesee/plotting.py (1)

135-140: Guard unsupported position‑space cuts for ndim > 1.
particles.select(..., box_type="pos") asserts ndim == 1, so this path will crash. This was already flagged earlier.

🚧 Suggested guard
             elif ndim == 2:
-                box_new = Box((cuts[0][0], cuts[1][0]), (cuts[0][1], cuts[1][1]))  # TODO need to be tested
-                new_particles = particles.select(box_new, box_type="pos")
+                raise NotImplementedError("Position-space cuts for 2D are not supported yet")
             else:
-                box_new = Box((cuts[0][0], cuts[1][0], cuts[2][0]), (cuts[0][1], cuts[1][1], cuts[2][1]))  # TODO need to be tested
-                new_particles = particles.select(box_new, box_type="pos")
+                raise NotImplementedError("Position-space cuts for 3D are not supported yet")

Comment on lines 20 to 22
* axis : ("x", "Vx"), ("x", "Vy"), ("x", "Vz"), ("Vx", "Vy") (default) --
("Vx", "Vz"), ("Vy", "Vz")
("Vx", "Vz"), ("Vy", "Vz"), ("Vx"), (Vy"), ("Vz")
* bins : number of bins in each dimension, default is (50,50)
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

Fix malformed axis docstring entry.
Line 21 has (Vy"), which reads as a typo and can confuse users.

✏️ Suggested fix
-       ("Vx", "Vz"), ("Vy", "Vz"), ("Vx"), (Vy"), ("Vz")
+       ("Vx", "Vz"), ("Vy", "Vz"), ("Vx"), ("Vy"), ("Vz")
🤖 Prompt for AI Agents
In `@pyphare/pyphare/pharesee/plotting.py` around lines 20 - 22, The docstring for
the "axis" parameter in pyphare.pyphare.pharesee.plotting.py contains a
malformed entry `(Vy")`; update the axis list so all entries are properly quoted
and comma-separated (e.g., replace `(Vy")` with ("Vy") and ensure consistency
like ("Vx"), ("Vy"), ("Vz") and pairs ("Vx","Vy") etc.) in the plotting module's
docstring so the axis options are readable and valid.

Comment on lines +111 to +120
color = kwargs.get("color", "k")
stride = kwargs.get("stride", 1)
markersize = kwargs.get("markersize", 0.5)
alpha = kwargs.get("alpha", 0.5)
im = ax.plot(x[::stride], y[::stride],
color=color,
linewidth=0,
marker='.',
markersize=markersize,
alpha=alpha)
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

Validate stride to avoid invalid slicing.
Line 112 allows stride=0, which raises ValueError in slicing.

🧩 Proposed guard
         stride = kwargs.get("stride", 1)
+        if stride <= 0:
+            raise ValueError("stride must be a positive integer")
         markersize = kwargs.get("markersize", 0.5)
🤖 Prompt for AI Agents
In `@pyphare/pyphare/pharesee/plotting.py` around lines 111 - 120, The code reads
stride = kwargs.get("stride", 1) and then uses x[::stride] which fails for
stride=0; update the plotting function to validate/coerce stride before slicing:
ensure stride is an integer >= 1 (e.g. convert via int() and if <= 0 either set
stride = 1 or raise a ValueError with a clear message), and then use that
validated stride when calling ax.plot (the variables to look for are the stride
assignment and the ax.plot call where x[::stride], y[::stride] are used).

Comment on lines +127 to +134
cuts = kwargs.get("cuts", None)
ndim = particles.ndim

if cuts is not None:
from pyphare.core.box import Box
if ndim == 1:
box_new = Box(cuts[0][0], cuts[0][1])
new_particles = particles.select(box_new, box_type="pos")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle None entries in cuts as described by the API.
The code assumes every cuts[i] is a tuple; None will throw at cuts[0][0].

🧩 One‑dimensional fix (minimal)
         if cuts is not None:
             from pyphare.core.box import Box
             if ndim == 1:
-                box_new = Box(cuts[0][0], cuts[0][1])
-                new_particles = particles.select(box_new, box_type="pos")
+                if cuts[0] is None:
+                    new_particles = particles
+                else:
+                    box_new = Box(cuts[0][0], cuts[0][1])
+                    new_particles = particles.select(box_new, box_type="pos")
🤖 Prompt for AI Agents
In `@pyphare/pyphare/pharesee/plotting.py` around lines 127 - 134, The code
assumes cuts[0] is a tuple and fails if it is None; update the ndim==1 branch to
handle None entries by checking cuts[0] is not None before constructing Box and
calling particles.select: if cuts[0] is None leave new_particles as the original
particles (or skip selection), otherwise create box_new = Box(cuts[0][0],
cuts[0][1]) and set new_particles = particles.select(box_new, box_type="pos");
ensure this pattern (check for None) is applied wherever cuts[i] is used so
particles.select is only called with a valid Box.

Comment on lines +146 to +149
if axis[0] in vaxis:
x = new_particles.v[:, vaxis[axis[0]]]
else:
raise ValueError("For 1-D dist_plot, the abscissa has to be a velocity axis")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

KDE path breaks for 1‑D axis.
When len(axis)==1, y is never defined, but Line 165 still calls sns.kdeplot(x=x, y=y, ...).

🧩 Proposed fix
     if kwargs.get("kde", False) is True:
         import seaborn as sns
-
-        sns.kdeplot(x=x, y=y, ax=ax, color="w")
+        if len(axis) == 1:
+            sns.kdeplot(x=x, ax=ax, color="w")
+        else:
+            sns.kdeplot(x=x, y=y, ax=ax, color="w")

Also applies to: 162-165

🧰 Tools
🪛 GitHub Check: CodeQL

[notice] 149-149: Unused local variable
Variable im is not used.

🪛 Ruff (0.14.13)

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

(TRY003)

🤖 Prompt for AI Agents
In `@pyphare/pyphare/pharesee/plotting.py` around lines 146 - 149, The 1-D branch
fails because y is never defined but sns.kdeplot is later called with y=y; in
the block handling len(axis)==1 (when axis[0] in vaxis), set y=None (or call
sns.kdeplot without the y argument) and ensure the subsequent call to
sns.kdeplot uses only x when y is None; update the code paths around the
axis/vaxis check and the sns.kdeplot invocation (references: axis, vaxis,
new_particles.v, and sns.kdeplot) so that for 1-D plots you pass only x (or x
and y=None) and avoid referencing an undefined y (apply same fix to the other
occurrence around lines 162-165).

elif axis[0] == "x":
x = particles.x
else:
raise ValueError("Only abscissa and velocity X-axis are supported yet")
Copy link
Member

Choose a reason for hiding this comment

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

do we consider the option (y,v) or (z,v) or (v,y) and (v,z) ?

alpha = kwargs.get("alpha", 0.5)
im = ax.plot(x[::stride], y[::stride],
color=color,
linewidth=0,
Copy link
Member

Choose a reason for hiding this comment

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

I think the more correct way to do this plot would be to use ax.scatter() rather than a plot with a 0 size linewidth (which rather should even be linestyle="none")

ax.set_ylabel(kwargs.get("ylabel", axis[1]))


elif len(axis) == 1:
Copy link
Member

Choose a reason for hiding this comment

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

I think axis should be listified (pyphare.core.phare_utilities.listify) to allow users who just want one axis to do axis="x" rather than having to type axis=("x",)

PhilipDeegan and others added 5 commits February 11, 2026 17:08
max battle : overlapped patch values are assigned to their max to avoid spurious floating point errors mismatches. Also updated smallest_patch_size calculation to use interpolation order.
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