Skip to content

[SPH] Add L2 error to dustysettle example#1906

Draft
tdavidcl wants to merge 9 commits into
Shamrock-code:mainfrom
tdavidcl:dustysettle_l2
Draft

[SPH] Add L2 error to dustysettle example#1906
tdavidcl wants to merge 9 commits into
Shamrock-code:mainfrom
tdavidcl:dustysettle_l2

Conversation

@tdavidcl

@tdavidcl tdavidcl commented Jul 6, 2026

Copy link
Copy Markdown
Member

No description provided.

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Thanks @tdavidcl for opening this PR!

You can do multiple things directly here:
1 - Comment pre-commit.ci run to run pre-commit checks.
2 - Comment pre-commit.ci autofix to apply fixes.
3 - Add label autofix.ci to fix authorship & pre-commit for every commit made.
4 - Add label light-ci to only trigger a reduced & faster version of the CI (need the full one before merge).
5 - Add label trigger-ci to create an empty commit to trigger the CI.

Once the workflow completes a message will appear displaying informations related to the run.

Also the PR gets automatically reviewed by gemini, you can:
1 - Comment /gemini review to trigger a review
2 - Comment /gemini summary for a summary
3 - Tag it using @gemini-code-assist either in the PR or in review comments on files

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request implements a periodic HCP box setup using a new pybind11 binding get_periodic_hcp_box, updates the dust settling time scale, and adds L2 error calculation and plotting. The review feedback highlights several critical improvements: first, binning the 3D SPH particle data before computing the L2 error to avoid undefined behavior with np.interp; second, wrapping the new plotting code in an MPI rank check to prevent race conditions; third, ensuring <pybind11/stl.h> is included for proper type casting in the C++ binding; and finally, addressing PEP 8 style violations and potential asymmetric bounds from negative floor division.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

field_field_sorted = field[z_field_sort_args]

# Interpolate field to the same grid as the reference field
field_interp = np.interp(z_ref, z_field_sorted, field_field_sorted)

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.

high

Using np.interp directly on raw 3D SPH particle data projected onto the 1D $z$-axis is problematic for two reasons:

  1. SPH particles are distributed in 3D, meaning there will be multiple particles with very close or identical $z$ coordinates but different field values (scatter). np.interp expects a single-valued function on a strictly increasing grid. If there are duplicate $z$ values, the behavior of np.interp is undefined.
  2. It does not perform any averaging or smoothing, making the L2 error highly sensitive to individual particle noise rather than the overall fluid profile.

To get a meaningful L2 error, you should first bin the particle data along the $z$-axis (e.g., using scipy.stats.binned_statistic or np.histogram) to obtain a clean 1D profile, and then compute the L2 error against the reference solution.

Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment on lines +881 to +895
# %%
# Plot the L2 errors

plt.figure()
for k in range(ndust):
plt.plot(t_l2, l2_errors[k], label=f"dust {k}")
print(f"L2 error for dust {k} = {l2_errors[k]}")
plt.xlabel("t")
plt.ylabel("L2 error")
plt.yscale("log")
plt.title("L2 errors")
plt.legend()
plt.tight_layout()
plt.savefig(f"{dump_folder}/plots/l2_errors.png")
plt.show() No newline at end of file

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.

high

The new plotting block for L2 errors is executed on all MPI ranks. In a parallel run, this will cause all ranks to simultaneously attempt to plot, call plt.show() (which can block or crash in headless environments), and write to l2_errors.png (causing file corruption or race conditions).

This entire block should be wrapped in an if shamrock.sys.world_rank() == 0: check to ensure only the root process handles plotting and file saving.

Suggested change
# %%
# Plot the L2 errors
plt.figure()
for k in range(ndust):
plt.plot(t_l2, l2_errors[k], label=f"dust {k}")
print(f"L2 error for dust {k} = {l2_errors[k]}")
plt.xlabel("t")
plt.ylabel("L2 error")
plt.yscale("log")
plt.title("L2 errors")
plt.legend()
plt.tight_layout()
plt.savefig(f"{dump_folder}/plots/l2_errors.png")
plt.show()
# %%
# Plot the L2 errors
if shamrock.sys.world_rank() == 0:
plt.figure()
for k in range(ndust):
plt.plot(t_l2, l2_errors[k], label=f"dust {k}")
print(f"L2 error for dust {k} = {l2_errors[k]}")
plt.xlabel("t")
plt.ylabel("L2 error")
plt.yscale("log")
plt.title("L2 errors")
plt.legend()
plt.tight_layout()
plt.savefig(f"{dump_folder}/plots/l2_errors.png")
plt.show()

Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment on lines +142 to +146
lx = 12
ly = 12
lz = 512
lmin = (-lx//2,-ly//2,-lz//2)
lmax = (lx//2,ly//2,lz//2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Adhering to PEP 8 style guidelines, binary operators (like //) and commas should be separated by spaces.

Additionally, using -lx // 2 can lead to asymmetric box bounds if lx is odd (since floor division of negative numbers rounds towards negative infinity, e.g., -11 // 2 = -6 while 11 // 2 = 5). Using -(lx // 2) is more robust and ensures symmetric bounds.

Suggested change
lx = 12
ly = 12
lz = 512
lmin = (-lx//2,-ly//2,-lz//2)
lmax = (lx//2,ly//2,lz//2)
lx = 12
ly = 12
lz = 512
lmin = (-(lx // 2), -(ly // 2), -(lz // 2))
lmax = (lx // 2, ly // 2, lz // 2)
References
  1. PEP 8: Always surround binary operators with a single space on either side, and use a space after commas. (link)

Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment on lines +547 to +569
def compute_L2_error(z,field, z_ref, field_ref):

z_field_sort_args = np.argsort(z)
z_field_sorted = z[z_field_sort_args]
field_field_sorted = field[z_field_sort_args]

# Interpolate field to the same grid as the reference field
field_interp = np.interp(z_ref, z_field_sorted, field_field_sorted)

# Compute delta
delta = field_interp - field_ref

# Compute L2 func
L2_func = delta ** 2

# Compute L2 integral # Use the appropriate trapezoidal integration function
if hasattr(np, "trapezoid"):
L2_integral = np.trapezoid(L2_func, z_ref) # NumPy >= 2.0
else:
L2_integral = np.trapz(L2_func, z_ref) # NumPy < 2.0


return np.sqrt(L2_integral)

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.

medium

Adhering to PEP 8 style guidelines, there should be a space after the comma in the function arguments (z, field), and double blank lines inside the function body should be avoided.

def compute_L2_error(z, field, z_ref, field_ref):
    z_field_sort_args = np.argsort(z)
    z_field_sorted = z[z_field_sort_args]
    field_field_sorted = field[z_field_sort_args]

    # Interpolate field to the same grid as the reference field
    field_interp = np.interp(z_ref, z_field_sorted, field_field_sorted)

    # Compute delta
    delta = field_interp - field_ref

    # Compute L2 func
    L2_func = delta ** 2

    # Compute L2 integral    # Use the appropriate trapezoidal integration function
    if hasattr(np, "trapezoid"):
        L2_integral = np.trapezoid(L2_func, z_ref)  # NumPy >= 2.0
    else:
        L2_integral = np.trapz(L2_func, z_ref)       # NumPy < 2.0

    return np.sqrt(L2_integral)
References
  1. PEP 8: Use a single space after commas in function definitions, and avoid extraneous blank lines inside functions. (link)

Comment on lines +863 to +865
math_module.def(
"get_periodic_hcp_box",
[](f64 dr, std::array<i32, 3> box_min, std::array<i32, 3> box_max) {

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.

medium

The new binding get_periodic_hcp_box uses std::array<i32, 3> in its lambda signature. For pybind11 to automatically cast Python lists/tuples to std::array, the header <pybind11/stl.h> must be included. Please ensure that #include <pybind11/stl.h> is explicitly included at the top of pyshammath.cpp if it is not already transitively included, to prevent compilation or runtime type-casting errors.

Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment thread examples/sph/run_dustysettle_tvi.py Outdated
Comment thread examples/sph/run_dustysettle_tvi.py Outdated
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Workflow report

workflow report corresponding to commit b3fafad
Commiter email is 66853113+pre-commit-ci[bot]@users.noreply.github.com
You are using github private e-mail. This prevent proper tracing of who contributed what, please disable it (see Keep my email addresses private).

Pre-commit check report

Some failures were detected in base source checks checks.
Check the On PR / Linting / Base source checks (pull_request) job in the tests for more detailed output

❌ ruff-check

�[1m�[91mNPY201 �[0m�[1m`np.trapz` will be removed in NumPy 2.0. Use `numpy.trapezoid` on NumPy 2.0, or ignore this warning on earlier versions.�[0m
   �[1m�[94m-->�[0m examples/sph/run_dustysettle_tvi.py:567:23
    �[1m�[94m|�[0m
�[1m�[94m565 |�[0m         L2_integral = np.trapezoid(L2_func, z_ref)  # NumPy >= 2.0
�[1m�[94m566 |�[0m     else:
�[1m�[94m567 |�[0m         L2_integral = np.trapz(L2_func, z_ref)  # NumPy < 2.0
    �[1m�[94m|�[0m                       �[1m�[91m^^^^^^^^�[0m
�[1m�[94m568 |�[0m
�[1m�[94m569 |�[0m     return np.sqrt(L2_integral)
    �[1m�[94m|�[0m
�[1m�[96mhelp�[0m: �[1mReplace with `numpy.trapezoid` (requires NumPy 2.0 or greater)�[0m

Found 1 error.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).

Suggested changes

Detailed changes :

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant