Repository navigation
Add support for geom objects to be rendered with galactic or ecliptic frames - #185
Conversation
|
I don't know the motivation for
|
bbeafcb to
43823f4
Compare
This allows faster memory resizing when combining maps.
…ory. With claude assistance to save memory on collapsing the last axis.
sidneymau
left a comment
There was a problem hiding this comment.
A few minor comments, but everything looks reasonable to me overall!
| m += healsparse.Circle(ra=202.0, dec=21.0, radius=0.5, value=10) | ||
|
|
||
|
|
||
| Using Geometric Shapes To Set And Erase Pixels |
There was a problem hiding this comment.
From the documentation, it's not clear what is meant by "erasing" a pixel -- does that mean to set the value to UNSEEN? To free the associated memory?
There was a problem hiding this comment.
I will rewrite the documentation to make this clear. And I should say "clear" pixels, not erase, because that's what the code is called. And what it does is (a) set all pixels to the sentinel value (which is typically UNSEEN for floating point maps, and False for boolean maps, and either 0 or -MAXINT for integer maps) (b) never expand the coverage map (because all of those are already invalid/sentinel values). The associated memory is not freed unless you defragment the map. I'll add all this to the docs.
| in this frame, and output will be equatorial/celestial coordinates. | ||
| """ | ||
| def __init__(self, *, ra, dec, radius, value, nside_render=None): | ||
| def __init__(self, *, ra, dec, radius, value, nside_render=None, geom_frame="equatorial"): |
There was a problem hiding this comment.
I would think frame would make more sense than geom_frame throughout -- it's implicit that the frame is the local frame for this object already
There was a problem hiding this comment.
Makes sense! I'll update.
| self._n_valid = None | ||
|
|
||
| # Convert to an array, no-copy if possible. | ||
| _pixels = np.atleast_1d(np.asarray(pixels)) |
There was a problem hiding this comment.
I think atleast_1d already converts objects to appropriate array-like objects, so asarray is probably unnecessary (and redundant) here. Did you find this was necessary?
There was a problem hiding this comment.
I think you are right.
| start, stop = pixel_ranges[:, 0], pixel_ranges[:, 1] | ||
| cov0 = np.right_shift(start, shift) | ||
| cov1 = np.right_shift(stop - 1, shift) # inclusive; no overrun at the top | ||
| nseg = cov1 - cov0 + 1 |
There was a problem hiding this comment.
Is this guaranteed to be nonnegative? I can't seem to break that, so it probably is
There was a problem hiding this comment.
As long as the pixel ranges are in healpix range ... but now I'm not sure if that's explicitly checked or if it'll just raise an error when called into hpgeom.
| cov0 = np.right_shift(start, shift) | ||
| cov1 = np.right_shift(stop - 1, shift) # inclusive; no overrun at the top | ||
| nseg = cov1 - cov0 + 1 | ||
| rng = np.repeat(np.arange(len(start)), nseg) |
There was a problem hiding this comment.
Using rng as the variable name for anything other than a random number generator is bound to be confusing in the future
| (key.__class__)) | ||
|
|
||
| def clear_pixels(self, key): | ||
| """Clear part of a healsparse map. |
There was a problem hiding this comment.
Similar to my comment about "Erase" earlier -- what is actually happening here? m[key] = None is also shorthand for something more specific. Is the memory associated with each of these keys freed? Or are the values densely set to UNSEEN?
There was a problem hiding this comment.
I will expand the docstring along with the documentation.
| summand += np.sum(last_unpacked[0], dtype=np.int64) | ||
| if mid_data is not None: | ||
| summand += np.sum(self._bit_count(mid_data), dtype=np.int64) | ||
| summand += np.sum(self._last_axis_bit_counts(mid_data, mid_data.size * 8)) |
There was a problem hiding this comment.
Should this use a dtype itemsize instead of 8?
There was a problem hiding this comment.
Ah, so the packed boolean array must always beuint8. That gives the most flexibility and efficiency for packing bits. I will add a self._itemsize or something just so it is always clear.
But the changes here on the PR involve the "count number of bits set", not the "check the bits that are set" (which I haven't noticed a problem with but I'll profile that a bit). When I originally wrote this code numpy2 was only in beta, and therefore I couldn't use the new numpy2 bitwise_count routine. This is much faster than the LUT method I was doing before. And it can be made even FASTER if you use wide words (simply viewing the uint8 array as 64-bit words) which then has two effects: (a) the temporary output is 8x smaller (less memory overhead and smaller allocations); (b) numpy will use a special optimized bit-counting instruction on processors that support it. I actually think that the biggest win is moving to the numpy bitwise_count (which is in c rather than via python); second is the memory reduction; third is the special instruction. But all that is somewhat academic, why not do all the optimizations available in numpy 2?
| @@ -681,16 +683,31 @@ def _test_bits_at_locs(self, locs): | |||
|
|
|||
| return self._data[_locs // 8] & (1 << (_locs % 8).astype(np.uint8)) != 0 | |||
There was a problem hiding this comment.
another place where np.dtype(np.int64).itemsize might be useful for generality? E.g., if we ever want a super high resolution map that needs int128 pixels, or if there is ever a need for optimizing smaller maps with smaller bit-packed integers? Or maybe that is overkill...
This PR also adds more convenient ways to clear pixels, including
del m[geom]and some optimizations made possible when using pixel ranges.Also, thanks to some tips from @sidneymau on some memory inefficiencies he has seen I did some investigation into the memory usage when combining very large packed boolean maps and found:
n_validorget_valid_area()on a bit-packed map is 12x faster for DP2-sized maps.