Skip to content

Commit 34af903

Browse files
Remove dead valid-face branch and identity scatter from hash table build
num_hash_per_face is never zero (quantization is monotone), so the valid mask was always all-True and the scatter an identity permutation. Removing them drops three entry-length temporaries (~35% peak build memory). Accumulate entry counts in int64 to avoid overflow. Hash table output verified byte-identical. Co-authored-by: Claude <noreply@anthropic.com>
1 parent ac22b34 commit 34af903

1 file changed

Lines changed: 29 additions & 57 deletions

File tree

src/parcels/_core/spatialhash.py

Lines changed: 29 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -241,66 +241,38 @@ def _initialize_hash_table(self):
241241
num_hash_per_face = (nx * ny * nz).astype(
242242
np.int32, copy=False
243243
) # Since nx, ny, nz are in the 10-bit range, their product fits in int32
244-
total_hash_entries = int(num_hash_per_face.sum())
245-
246-
# Preallocate output arrays
247-
morton_codes = np.zeros(total_hash_entries, dtype=np.uint32)
248-
249-
# Compute the j, i indices corresponding to each hash entry
244+
# Sums over faces can exceed int32, so accumulate in int64
245+
total_hash_entries = int(num_hash_per_face.sum(dtype=np.int64))
246+
# Entry indices fit in int32 for all but extreme cases; fall back to int64 when needed
247+
idx_dtype = np.int64 if total_hash_entries > np.iinfo(np.int32).max else np.int32
248+
249+
# Every face overlaps at least one hash cell (nx, ny, nz >= 1 since quantization
250+
# is monotone), and contributes one hash entry per cell of its quantized bounding
251+
# box. Entries are generated in face-major order: face_ids maps each entry to its
252+
# face, and intra enumerates the cells of that face's box (0..num_hash_per_face-1).
250253
nface = np.size(self._xlow)
251254
face_ids = np.repeat(np.arange(nface, dtype=np.int32), num_hash_per_face)
252-
offsets = np.concatenate(([0], np.cumsum(num_hash_per_face))).astype(np.int32)[:-1]
255+
face_starts = np.concatenate(([0], np.cumsum(num_hash_per_face, dtype=np.int64)))[:-1]
256+
intra = np.arange(total_hash_entries, dtype=idx_dtype) - np.repeat(
257+
face_starts.astype(idx_dtype, copy=False), num_hash_per_face
258+
)
253259

254-
valid = num_hash_per_face != 0
255-
if not np.any(valid):
256-
# nothing to do
257-
pass
258-
else:
259-
# Grab only valid faces to avoid empty arrays
260-
nx_v = np.asarray(nx[valid], dtype=np.int32)
261-
ny_v = np.asarray(ny[valid], dtype=np.int32)
262-
nz_v = np.asarray(nz[valid], dtype=np.int32)
263-
xlow_v = np.asarray(xqlow[valid], dtype=np.int32)
264-
ylow_v = np.asarray(yqlow[valid], dtype=np.int32)
265-
zlow_v = np.asarray(zqlow[valid], dtype=np.int32)
266-
starts_v = np.asarray(offsets[valid], dtype=np.int32)
267-
268-
# Count of elements per valid face (should match num_hash_per_face[valid])
269-
counts = (nx_v * ny_v * nz_v).astype(np.int32)
270-
total = int(counts.sum())
271-
272-
# Map each global element to its face and output position
273-
start_for_elem = np.repeat(starts_v, counts) # shape (total,)
274-
275-
# Intra-face linear index for each element (0..counts_i-1)
276-
# Offsets per face within the concatenation of valid faces:
277-
face_starts_local = np.cumsum(np.r_[0, counts[:-1]])
278-
intra = np.arange(total, dtype=np.int32) - np.repeat(face_starts_local, counts)
279-
280-
# Derive (zi, yi, xi) from intra using per-face sizes
281-
ny_nz = np.repeat(ny_v * nz_v, counts)
282-
nz_rep = np.repeat(nz_v, counts)
283-
284-
xi = intra // ny_nz
285-
rem = intra % ny_nz
286-
yi = rem // nz_rep
287-
zi = rem % nz_rep
288-
289-
# Add per-face lows
290-
x0 = np.repeat(xlow_v, counts)
291-
y0 = np.repeat(ylow_v, counts)
292-
z0 = np.repeat(zlow_v, counts)
293-
294-
xq = x0 + xi
295-
yq = y0 + yi
296-
zq = z0 + zi
297-
298-
# Vectorized morton encode for all elements at once
299-
codes_all = _encode_quantized_morton3d(xq, yq, zq)
300-
301-
# Scatter into the preallocated output using computed absolute indices
302-
out_idx = start_for_elem + intra
303-
morton_codes[out_idx] = codes_all
260+
# Derive (xi, yi, zi) cell offsets within each face's box from intra,
261+
# then shift by the per-face low corner to get quantized cell coordinates
262+
ny_nz = np.repeat(ny * nz, num_hash_per_face)
263+
nz_rep = np.repeat(nz, num_hash_per_face)
264+
265+
xi = intra // ny_nz
266+
rem = intra % ny_nz
267+
yi = rem // nz_rep
268+
zi = rem % nz_rep
269+
270+
xq = np.repeat(xqlow, num_hash_per_face) + xi
271+
yq = np.repeat(yqlow, num_hash_per_face) + yi
272+
zq = np.repeat(zqlow, num_hash_per_face) + zi
273+
274+
# Vectorized morton encode for all entries at once, already in face-major order
275+
morton_codes = _encode_quantized_morton3d(xq, yq, zq)
304276

305277
# Sort face indices by morton code
306278
order = np.argsort(morton_codes)

0 commit comments

Comments
 (0)