-
Notifications
You must be signed in to change notification settings - Fork 280
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
Octree ghost zones — reloaded #2425
Conversation
I dig the fstrings in your example. |
Current version: The goodimport numpy as np
import yt
ds = yt.load('output_00080/info_00080.txt')
ad = ds.all_data()
center = ds.arr(ad.argmax('density'))
sp = ds.sphere(center, (100, 'kpc'))
ds.add_gradient_fields(('gas', 'density'))
center = np.array([.1, .5, .5])
w = np.array([.05, 1, 1])
reg = ds.box(center-w/2, center+w/2)
p = yt.ProjectionPlot(ds, 'x', ['density'] + [f'density_gradient_{k}' for k in 'xyz'], center=center, data_source=reg, weight_field='density')
p.set_axes_unit('Mpc')
for k in 'xyz':
p.set_log(f'density_gradient_{k}', True, linthresh=1e-55)
p.set_zlim(f'density_gradient_{k}', -1e-53, 1e-53)
p.set_cmap(f'density_gradient_{k}', 'bwr')
p.save('frames/the_good/') The badad = ds.all_data()
center = ds.arr(ad.argmax('density'))
sp = ds.sphere(center, (100, 'kpc'))
ds.add_gradient_fields(('gas', 'density'))
p = yt.SlicePlot(ds, 'x', ['density'] + [f'density_gradient_{k}' for k in 'xyz'], width=sp.radius, center=center, data_source=sp)
p.set_axes_unit('kpc')
dens = p.frb['density']
p.annotate_clear()
for k in 'xyz':
p.set_log(f'density_gradient_{k}', True, linthresh=1e-48)
p.set_zlim(f'density_gradient_{k}', -1e-45, 1e-45)
p.set_cmap(f'density_gradient_{k}', 'bwr')
p.annotate_domain_edges()
p.save('frames/the_bad/') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks pretty good, but I'm not sure that I've hit all the corner cases. Nice!
@yt-fido test this please |
This should be ready to go! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This all looks good to me, but I think it would good to see the answer tests run on this before it's merged since the gradient fields are changed. We could either include this in the PR #2437 or wait until that's in. @matthewturk, what do you think?
I suspect it will be fine to include it now. I will work on getting answer tests in #2437 working for the RAMSES datasets before merging this into that PR. |
I think as long as answer tests are run at some point, it's ok with me. If that happens as part of the yt-4.0 PR, that's fine, too. |
Since @brittonsmith approved the PR, I refactored parts of it to reduce code repetition and make it more readable. Sorry for updating a PR post-review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New changes look ok to me!
For information, I just rebased the branch on yt-4.0. @matthewturk, you mentioned #2437 (in #2425 (comment)), is it still a blocker to merge this PR? |
We're temporarily blocking all merges to the yt-4.0 branch until #2437 gets merged. Is that what you're asking? |
Yes, thanks for the reply @munkm ;) In the meantime is there anything I can do to prepare this PR for a future merge into yt-4? |
I'll do my best to review it today! I can't think of anything at the moment. 🙂 |
for i in range(-1, 3): | ||
ishift[0] = i | ||
for j in range(-1, 3): | ||
ishift[1] = j | ||
for k in range(-1, 3): | ||
ishift[2] = k | ||
self.set_neighbour_info(o, ishift) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This piece of code is very much optimizable. Here are possible routes to achieve much better performance:
- Work oct by oct, which would reduce the number of neighbor lookup from 4³=64 to 3³=27,
- Use faster neighbor lookup method(s). For now, all searches are started from the root mesh down to leaf nodes, but we could instead go up the tree from the central oct then down to find all neighbors (see e.g. https://geidav.wordpress.com/2017/12/02/advanced-octrees-4-finding-neighbor-nodes/).
- Pre-compute the face-neighbors of all octs.
Note that for the last point, algorithms exist that generate the neighbors of all octs in O(1) time (https://link.springer.com/article/10.1007/s13319-015-0060-9) during the octree construction.
Another possible solution would be to keep a unordered hash map of all the octs indexed by their (3-integers) position. With such structure, finding a neighbor takes O(1) time. This could even come as a replacement of the current pointer-based octree structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be in a comment. It obviously doesn't need to be implemented right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
@yt-fido test this please |
So the new tests answer tests are hidden here https://tests.yt-project.org/job/yt_py38_git/423/testReport/yt.frontends.ramses.tests (they pass). |
The test that you've added won't work as intended. There's a few things that need to be fulfilled for a method to work as an answer test:
Ultimately it would look roughly like this: @requires_ds(output_00080)
def test_field_accession():
ds = data_dir_load(output_00080)
fields = [
('gas', 'density'), # basic ones
('gas' ,'pressure'),
('gas', 'pressure_gradient_magnitude'), # requires ghost zones
)
dso = (
None,
("sphere", ([.1]*3, (0.01, 'unitary'))),
("sphere", ([.5]*3, (0.05, 'unitary'))),
("box", ([.1]*3, [.2]*3)),
)
for dobj_name in dso:
for field in fields:
test = FieldValuesTest(ds, field, dobj_name)
test.suffix = "accession"
test_field_accession.__name__ = test.description + test.suffix
yield test Having said all that, I think most of the things from this tests are already done in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not competent enough to approve this, but I left a few comments and questions, I hope it helps making this even better. In any case there's a lot going on here and you're doing great ! Keep up the awesome PRs
Thanks for the detailed answer. The answer tests should now be included in the already-existing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed my mind, here's my approval 🥇
@yt-fido test this please. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is really good. Only a few comments.
yt/data_objects/data_containers.py
Outdated
self.selector, | ||
gz[field][ngz:-ngz, ngz:-ngz, ngz:-ngz], | ||
rv, ind) | ||
data = gz[field][ngz:-ngz, ngz:-ngz, ngz:-ngz] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like getting rid of the conditional! Nice.
yt/geometry/oct_visitors.pxd
Outdated
cdef class BaseNeighbourVisitor(OctVisitor): | ||
cdef int idim # 0,1,2 for x,y,z | ||
cdef int direction # +1 for +x, -1 for -x | ||
cdef np.int8_t[:] neigh_ind |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
two quick things -- should neigh_ind
be a 3d view? and, it's a bit faster if we declare ::1
in them so that it knows they have stride 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
neigh_ind
has size 3. I actually just changed it to use a C array instead of a memory view.
yt/geometry/oct_visitors.pxd
Outdated
cdef np.int64_t[:] domain_inds | ||
|
||
cdef class NeighbourCellVisitor(BaseNeighbourVisitor): | ||
cdef np.uint8_t[:] levels |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here about stride
# Assuming periodicity | ||
if fcoords[i] < 0: | ||
fcoords[i] += 1 | ||
elif fcoords[i] > 1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you make this explicitly a float? and, shouldn't this be checking if it's greater than (and above, less than) the domain width?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually i may be wrong on that, but want confirmation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thing is, we are computing here the center of each cell, which cannot be at the edge. For example, the cell positions at level 2 in 1D would be [0.125, 0.375, 0.625, 0.875]
to which we add ±0.25
, so that should cause no problems.
However if you prefer, I can do
elif fcoords[i] > 1: | |
elif fcoords[i] >= 1: |
for i in range(-1, 3): | ||
ishift[0] = i | ||
for j in range(-1, 3): | ||
ishift[1] = j | ||
for k in range(-1, 3): | ||
ishift[2] = k | ||
self.set_neighbour_info(o, ishift) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be in a comment. It obviously doesn't need to be implemented right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments addressed! Thanks for them
|
||
if num_ghost_zones > 0: | ||
if not all(ds.periodicity): | ||
warnings.warn('Ghost zones will wrongly assume the domain to be periodic.') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I changed it to raise a RuntimeError
unless there's a better one to raise here?
fill_hydro(fd, file_handler.offset, | ||
file_handler.level_count, levels, cell_inds, | ||
file_handler.level_count, | ||
[self.domain_id-1], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the name of the variable explicit here and below (the next fill_hydro
occurrence).
oinds = oinds.reshape(-1, 64) | ||
cinds = cinds.reshape(-1, 64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, I changed the names.
yt/geometry/oct_container.pyx
Outdated
for i in range(levels.shape[0]): | ||
if levels[i] != level: continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
yt/geometry/oct_container.pyx
Outdated
------- | ||
oct_inds : int64 ndarray (nocts*8, ) | ||
The on-domain index of the octs containing each cell | ||
cell_inds : uint8 array (nocts*8, ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
yt/geometry/oct_visitors.pxd
Outdated
cdef class BaseNeighbourVisitor(OctVisitor): | ||
cdef int idim # 0,1,2 for x,y,z | ||
cdef int direction # +1 for +x, -1 for -x | ||
cdef np.int8_t[:] neigh_ind |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
neigh_ind
has size 3. I actually just changed it to use a C array instead of a memory view.
# Assuming periodicity | ||
if fcoords[i] < 0: | ||
fcoords[i] += 1 | ||
elif fcoords[i] > 1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thing is, we are computing here the center of each cell, which cannot be at the edge. For example, the cell positions at level 2 in 1D would be [0.125, 0.375, 0.625, 0.875]
to which we add ±0.25
, so that should cause no problems.
However if you prefer, I can do
elif fcoords[i] > 1: | |
elif fcoords[i] >= 1: |
for i in range(-1, 3): | ||
ishift[0] = i | ||
for j in range(-1, 3): | ||
ishift[1] = j | ||
for k in range(-1, 3): | ||
ishift[2] = k | ||
self.set_neighbour_info(o, ishift) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
PR Summary
This PR adds support for ghost zones (or a similar interface). This is another attempt than #2166 based on the OctVisitors machinery.
The good
The bad