Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Fix intersections between SPH particles and regions when the boundary is periodic #4050

Merged
merged 3 commits into from
Aug 5, 2022

Conversation

jzuhone
Copy link
Contributor

@jzuhone jzuhone commented Aug 4, 2022

PR Summary

This PR fixes the issue raised by #3916 and seen in other contexts where sometimes particles which properly belong to a rectangular region straddling a periodic boundary would get missed.

I think something was not quite right in the overall logic, but I have to be honest I am not certain. What I do know for sure is that instead of checking the particle position itself for particles that lay outside the boundary of a region, we should be checking the "left_edge" and "right_edge" of the particle in question (defined by the +/- the radius away from the position), and then computing the radius of the particle to the edge. That was inspired by considering the region/cell intersection implementation:

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cdef int select_bbox(self, np.float64_t left_edge[3],
np.float64_t right_edge[3]) nogil:
cdef int i
for i in range(3):
if (right_edge[i] < self.left_edge[i] and \
left_edge[i] >= self.right_edge_shift[i]) or \
left_edge[i] >= self.right_edge[i]:
return 0
return 1

This PR implements that logic, and makes the code logic clearer. It also uses the already defined periodic_difference in the superclass to compute the distance between the particle position and the region edge. However, I noticed during this exercise that we have this same logic implemented in two different places (both of which are used):

Here:

@cython.boundscheck(False)
@cython.wraparound(False)
@cython.cdivision(True)
cdef np.float64_t periodic_difference(self, np.float64_t x1, np.float64_t x2, int d) nogil:
# domain_width is already in code units, and we assume what is fed in
# is too.
cdef np.float64_t rel = x1 - x2
if self.periodicity[d]:
if rel > self.domain_width[d] * 0.5:
rel -= self.domain_width[d]
elif rel < -self.domain_width[d] * 0.5:
rel += self.domain_width[d]
return rel

and here:

cdef inline np.float64_t _periodic_dist(np.float64_t x1, np.float64_t x2,
np.float64_t dw, bint periodic) nogil:
cdef np.float64_t rel = x1 - x2
if not periodic: return rel
if rel > dw * 0.5:
rel -= dw
elif rel < -dw * 0.5:
rel += dw
return rel

Should I presume the latter would be faster?

Also, it's possible this change may break some answer tests.

PR Checklist

  • New features are documented, with docstrings and narrative docs
  • Adds a test for any bugs fixed. Adds tests for new features.

@matthewturk
Copy link
Member

THANK YOU, John.

My guess is that the latter probably is faster, but there may be cases where we can't call it, or where type conversion may end up being more expensive than we'd expect.

@jzuhone
Copy link
Contributor Author

jzuhone commented Aug 4, 2022

By the way, this is my modified script from @neutrinoceros:

import numpy as np
import yt


def fake_ds(hsml_factor):
    npart = 3**3
    x = np.empty(npart)
    y = np.empty(npart)
    z = np.empty(npart)

    tot = 0
    for i in range(0, 3):
        for j in range(0, 3):
            for k in range(0, 3):
                x[tot] = i + 0.5
                y[tot] = j + 0.5
                z[tot] = k + 0.5
                tot += 1

    data = {
        "particle_position_x": (x, "cm"),
        "particle_position_y": (y, "cm"),
        "particle_position_z": (z, "cm"),
        "particle_mass": (np.ones(npart), "g"),
        "particle_velocity_x": (np.zeros(npart), "cm/s"),
        "particle_velocity_y": (np.zeros(npart), "cm/s"),
        "particle_velocity_z": (np.zeros(npart), "cm/s"),
        "smoothing_length": (0.05 * np.ones(npart) * hsml_factor, "cm"),
        "density": (np.ones(npart), "g/cm**3"),
        "temperature": (np.ones(npart), "K"),
    }

    bbox = np.array([[0, 3], [0, 3], [0, 3]])

    return yt.load_particles(data=data, length_unit=1.0, bbox=bbox)


ds = fake_ds(10)

for w in (3, 2, 1.5, 1.0, 0.5):
    center = np.array([2.8]*3)
    proj = yt.ProjectionPlot(
        ds, "z", ("gas", "density"), center=center, width=w,
    )
    proj.save(f"/tmp/test_{w=}.png")

    le = center-0.5*w
    re = center+0.5*w
    box = ds.box(le, re)
    print(box["io","smoothing_length"].size)
    proj = yt.ProjectionPlot(
        ds, "z", ("gas", "density"), center=center, data_source=box)
    proj.save(f"/tmp/test_box_{w=}.png")

which adds more widths and it also adds a test where we keep the image fixed at the domain width but make the region itself smaller. As expected, the number of particles plotted decreases.

And the results:

test_box_w=0 5

test_box_w=1 0

test_box_w=1 5

test_box_w=2

test_box_w=3

test_w=0 5

test_w=1 0

test_w=1 5

test_w=2

test_w=3

@neutrinoceros
Copy link
Member

Very nice, thanks for solving this. I will not be able to review this properly until tomorrow, but if Matt wants to push the button, I don't want to make you guys wait for me when it looks "obviously right".

@jzuhone
Copy link
Contributor Author

jzuhone commented Aug 4, 2022

Ping @benopp99 let's test on that dataset of yours

matthewturk
matthewturk previously approved these changes Aug 4, 2022
Copy link
Member

@matthewturk matthewturk left a comment

Choose a reason for hiding this comment

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

I think this looks right.

@benopp99
Copy link

benopp99 commented Aug 4, 2022 via email

@benopp99
Copy link

benopp99 commented Aug 4, 2022 via email

@jzuhone
Copy link
Contributor Author

jzuhone commented Aug 4, 2022

@benopp99 I can confirm this code fixes your plots.

@benopp99
Copy link

benopp99 commented Aug 4, 2022 via email

@jzuhone
Copy link
Contributor Author

jzuhone commented Aug 4, 2022

@benopp99 you can always check out this branch until then :) but it will be merged into main no later than tomorrow I assume.

@jzuhone
Copy link
Contributor Author

jzuhone commented Aug 5, 2022

Ok, I added a test for this. Provided it passes, this is ready to go.

Copy link
Member

@neutrinoceros neutrinoceros left a comment

Choose a reason for hiding this comment

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

LGTM, nice craft !

@neutrinoceros neutrinoceros merged commit f7d7a80 into yt-project:main Aug 5, 2022
neutrinoceros added a commit to neutrinoceros/yt that referenced this pull request Aug 8, 2022
BUG: Fix intersections between SPH particles and regions when the boundary is periodic
@neutrinoceros neutrinoceros added this to the 4.0.5 milestone Aug 9, 2022
@jzuhone jzuhone deleted the fix_periodicity branch November 7, 2022 17:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants