-
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
bug in Projection Plots for SPH datasets #4788
Comments
I think basically the same issue also comes up in slices through SPH datasets: where Finally, I would suggest that we really need the z values for this as well. Now, this method calculates the kernel contributions for all particles as if the particle center lies exactly in the plane of the slice, but really, the kernel should probably be evaluated at the 3D-distance to the pixel center, to be consistent with how SPH interpolation is done in a simulation. |
The 3D version of this grid deposition pixelization_routines.pyx, line 1607 onwards: |
I think I have the Since this took me so long, actually merging this back into main yt will likely be a bit painful. |
I did put the tests I used in a script (attached): There is still the question of what to do with SPH particles that are less than a kernel size away from the projection region in depth, but are centered outside the region. YT regions, rays, etc. include any SPH particle that intersects region, but projections integrate the kernel over the line of sight assuming the line of sight goes all the way through the kernel. That means that, basically, the line of sight if effectively a bit longer than specified. For a projection around e.g., a single halo, where the edges are typically in some low-density region, that's probably not a problem, but in projections through full periodic volumes, that likely means double-counting particles less than a smoothing length from the boundary in the line-of-sight direction. |
@nastasha-w can you submit a PR for this? I think that this will be easier to look at this way. I'm ready and willing to start looking at it next week. |
Thanks! Here's the PR: #4939 |
Based on some tests with PR #4939 (this comment), I can now confirm that on test datasets from OWLS and Arepo, the resolution effects I showed in a test case in this issue play out in real data analysis, i.e., the bug is no longer just 'suspected'. |
Will take a look. Based on my initial quick look over the issue, this paper: https://ui.adsabs.harvard.edu/abs/2021arXiv210605281B/abstract will likely be relevant. More soon. |
Thanks @JBorrow! I had seen that paper a while ago. The backend here uses a bit of a different approach from that paper: instead of dividing the mass/number of ions/other integral quantity in each SPH particle across rectangular prisms defined by the pixel grid, the existing SPH projection back-end in |
Honestly, a bigger worry for me is currently the periodic edge handling. The way this currently works is that, if a simulation is periodic in a particular coordinate direction, particles are effectively 'doubled', i.e., placed on both relevant sides of the grid. Perpendicular to the line of sight direction, this works just fine. However, this is also applied along the line-of-sight direction, where I think it might destructively come together with the line-of-sight integration approach. |
I've addressed the periodicity concern above in PR #4939 now (comment describing the effect). |
@nastasha-w another one for you :) ready to close now that both big sph fixes are in? |
@chrishavlin Yup! |
Bug report
Bug summary
To my understanding, the pixel values in projection plots should represent the integral of some field over the line of sight through a pixel center. That means that pixels in different grids, but with the same center, should produce the same integrated values. However, this is not the case for the
FixedResolutionBuffer
projections of SPH datasets. I suspect this is due to the handling of small SPH particles (relative to the grid spacing) in the backend code: their contribution to the line-of-sight integrals seem to be systematically overestimated.I've proposed a fix, but I'd like someone else to confirm whether this problem is real and seems correctly diagnosed before I open a pull request.
Bug explanation
Here, I will explain the issue as I understand it from the backend code. I will demonstrate the problem in the next section.
The backend code at issue here is$\sum_{j} dl_j \cdot A_j$ , along a $j$ , $dl_j$ is the path length from e.g., #4783, and $A$ is the particle field to be projected.$N$ along one of those pencil beams, we then get
pixelize_sph_kernel_projection
in pixelization_routines.pyx, starting on line 1121. This code loops over the input particles, determines the extent of the region each particle might contribute to, and adds the (kernel-integrated path length) * (selected particle field) to each pixel's total. In #4783, I discuss some background on the reasoning for this method; the pixel values should be the same as theRay
, where the sum is over SPH particlesFor the column density or surface density
and
For a particle$j$ , $h_j$ is its smoothing length, $m_j$ is its mass, $\rho_j$ is its density, and $b_j$ is its impact parameter: its minimum distance to the line of sight.
The main loop in$dl_j$ in the code is split up into two parts: a factor $A_j \cdot m_j / (\rho_j \cdot h_j^2)$ which is straightforwardly calculated from the dataset fields, and the integral $\int U \left( \sqrt{(b_j/h_j)^2 + r^2} dr \right)$ , which only depends on $b_j/h_j$ , and is therefore calculated for a small number of values, then interpolated for the rest with the
pixelize_sph_kernel_projection
seems to implement thie. However, for small SPH particles, the smoothing lengths are tweaked in a way that seems to be inconsistent with the 'pencil-beam integral' approach to determining surface densities, and systematically overestimates their contributions to the surface density. This is because the calculation ofSPHKernelInterpolationTable
class.This adjustment which I think is causing trouble is made in lines 1221-1223:
where
h_j2
is the smoothing length used in the kernel function integral, andih_j2
is its inverse.hsml
is the smoothing length recorded in the dataset, anddx
anddy
are the pixel dimensions.However, the first factor in each particle's pixel contributions is calculated as
using the dataset smoothing length value
hsml[j]
. On the other hand, the integral is evaluated aswhere
posx_diff
is (x-coordinate of particle center - x-coordinate of pixel center)^2, andposy_diff
is the analogous value for y-coordinates. The integral evaluation (interpolation) happens withitab.interpolate(q_ij2)
. (This should indeed be a squared input value if I'm not mistaken; see #4783.)For sufficiently large SPH particles (compared to the pixel grid spacing), this issue does not arise as
fmax(hsml[j]*hsml[j], dx*dy)
will be equal to the squared dataset smoothing length. Here, the different parts of the particle surface density calculation use consistent smoothing lengths. However, for smaller particles, the integral factor is evaluated at a smaller normalized impact parameter (b / hsml), and will therefore be larger than it should be.For the inclusion of particles in a pixel calculation (i.e., the assignment non-zero$dl_j$ to particle $j$ for a given pixel center), the situation is complicated by the use of a mix of ($j$ even lies in the domain of the projection, line 1232
h_j2
,ih_j2
) andhsml[j]
in different parts of the calculation. To evaluate whether particle(and the analogous line 1236 for the y position) uses the true smoothing length$j$ might contribute to:
hsml[j]
. The other variables are the lower x-value bound in the gridxmin
, the upper boundxmax
, andpx
is the particle position x-coordinate (possibly with the box period added or subtracted to wrap around periodic edges). Starting on line 1238, the true smoothing length is also used to determine the square region of pixels particleThe
iclip
function returns its first value if it's between the bounds given as its second and third arguments, but returns the closest bound if it is not. (I'm assuming second arg < thrid arg here.)idx
andidy
are the inverse pixel lengths in the x- and y-directions, respectively.Two nested loops then iterate:
for xi in range(x0, x1)
andfor yi in range(y0, y1)
. Even for a smoothing length of zero, this will return a range of at least 2 pixels (xi
andyi
values) in each coordinate direction. These nested loops start on line 1249:Within these loops, the distance between the position of particle$j$ and the pixel center is consistently compared to $dl_j$ value (due to the integral part of the calculation) than their true smoothing lengths would give.
ih_j2
orh_j2
: the adjusted smoothing lengths. This means that all SPH particles in the grid domain will contribute a non-zero density to some line of sight within the grid, even if they don't actually intersect any of the sightlines in this grid. Small SPH particles that do intersect the grid will have a largerThis means that the grid projections will systematically overestimate the contribution of small SPH particles (compared to the pixel grid spacing) to the line-of-sight integrals through pixel centers.
Note that this issue will also affect off-axis SPH projections, since the backend for those (
off_axis_projection_SPH
, starting on line 1915 of pixelization_routines.pyx) calls pixelize_sph_kernel_projection` after rotating the particle coordinates.Proposed solution
If I am correct in diagnosing this issue, the fix would be simple: don't set a minimum effective smoothing length, and just set
instead.
This does mean that some small SPH particles will be entirely 'missed' by the projection calculation. I think this ok for a grid of sightliness: this method will only conserve mass in the projection in the limit of a very high grid density, but it does sample the range of surface/column densities values without 'smoothing out' the extreme values.
An alternative approach to projecting SPH data does conserve mass explicitly, at the cost of 'smoothing out' the extremes of the
surface/column density range. Here, instead of projecting densities onto lines of sight through pixel centers, the mass of each SPH particle is divided up into different rectangular prisms (the pixels of the grid, extending along the projection axis). Here, weights can calculated by evaluating the kernel at pixel centers, then normalizing the sum of these weights to 1 explicitly. Here, setting a minimum smoothing length is a straightforward way to make sure each particle contributes to at least one pixel, and its mass is not lost. However, I do not believe this 'loss avoidance' strategy is appropriate for the 'sightline through the center' approach to projections used in YT.
Code for reproduction
Unfortunately, setting up the test case means this isn't quite a minimal amount of code, but it does straightforwardly suggest a test for this issue. Basically, in the first part, I just set up a grid of SPH particles with constant spacing along the x, y, and z coordinate directions, and a smoothing length equal to half the spacing, so the particles just 'touch' each other.
I then set up
FixedResolutionBuffer
grids of increasing pixel resolution, keeping the centers of a few pixels fixed. The centers coincide precisely with SPH particle centers; I set it up that way for a different test. Using these increasingly dense grids, I then retrieve the gas surface density at 9 pixel centers that are at the same coordinate locations in the different grids.Actual outcome
Expected outcome
My expectation was that a pixel value represents the integral of a quantity (e.g., gas density) over a line of sight (pencil beam) through the pixel center. This means that the positions of the other pixels should not affect the value attributed to a pixel through the same center, e.g., if that center shows up in different pixel grids like in my test.
However, those values are only constant when the grid spacing is smaller than the SPH particle sizes. At lower pixel grid resolution, the surface densities come out too high.
Version Information
I installed most dependencies from conda (standard channel), but installed yt from source, using my conda environment's pip.
The text was updated successfully, but these errors were encountered: