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