rugnux: sixteen-bit Miller indices in the ingest sort key and the post-refine partial

Both arrays are one record per integrated observation - tens of millions on a fine-sliced long axis,
gigabytes each - and both carry the raw hkl only to sort and group on. A Miller index needs sixteen
bits (|h| <= a / d_min, in the hundreds even on the longest axis at atomic resolution), which takes
the ingest sort key from 24 to 20 bytes and the post-refine partial from 32 to 28.

Same comparisons, same order; merged output byte-identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nW6FNRP1bBJJ8pfHiByAT
This commit is contained in:
2026-09-20 18:45:17 +02:00
co-authored by Claude Opus 5
parent 9de53ad015
commit eb6589f647
4 changed files with 14 additions and 6 deletions
@@ -219,7 +219,9 @@ PostRefineObservations GatherPostRefineObservations(std::vector<IntegrationOutco
if (!std::isfinite(r.I) || !std::isfinite(r.sigma) || r.sigma <= 0.0f) continue;
const float ox = std::isfinite(r.observed_x) ? r.observed_x : NAN;
const float oy = std::isfinite(r.observed_y) ? r.observed_y : NAN;
pts[fill[r.h - h_lo]++] = Partial{r.h, r.k, r.l, r.image_number, r.I, r.sigma, ox, oy};
pts[fill[r.h - h_lo]++] = Partial{static_cast<int16_t>(r.h), static_cast<int16_t>(r.k),
static_cast<int16_t>(r.l), r.image_number,
r.I, r.sigma, ox, oy};
}
// The caller said it will never read this image's reflections again, so hand the
// vector back the moment it is consumed: the payload shrinks image by image as it
+4 -1
View File
@@ -94,7 +94,10 @@ struct PostRefineSettings {
// per-bucket sort move all of it. The goniometer angle is not stored - it is a function of the
// image number alone, and is rebuilt from it where it is needed.
struct PostRefinePartial {
int h, k, l;
// Sixteen bits are plenty for a Miller index - |h| <= a / d_min, in the hundreds even on the
// longest axis at atomic resolution - and here four bytes of every partial are four bytes of an
// array that is gigabytes on a large cell.
int16_t h, k, l;
float img;
float I, sigma;
float obs_x, obs_y; // observed spot centroid (pixels); NAN if the box sum found no centroid
@@ -432,7 +432,8 @@ void RotationScaleMerge::Ingest() {
for (int o = lo; o < hi; ++o) {
int32_t at = src_start[o];
for (const auto &r : partials_out[o].reflections) {
keys[fill[r.h - h_min]++] = SortKey{r.h, r.k, r.l, r.image_number, at, r.d};
keys[fill[r.h - h_min]++] = SortKey{static_cast<int16_t>(r.h), static_cast<int16_t>(r.k),
static_cast<int16_t>(r.l), r.image_number, at, r.d};
++at;
}
}
@@ -142,10 +142,12 @@ private:
// The narrow per-observation record the ingest sort orders: the raw hkl the runs are cut on, the
// frame position that breaks a tie inside one, the observation's own index (which makes the order
// total - see the .cpp), and the resolution the range test reads. Twenty-four bytes against the
// Obs's eighty, and it is all the ingest needs before it knows which observations survive.
// total - see the .cpp), and the resolution the range test reads. Twenty bytes against the Obs's
// eighty, and it is all the ingest needs before it knows which observations survive. Sixteen bits
// are plenty for a Miller index - |h| <= a / d_min, in the hundreds even on the longest axis at
// atomic resolution - and on a long axis this array sets the sort's memory high-water mark.
struct SortKey {
int32_t h, k, l;
int16_t h, k, l;
float image_number;
int32_t idx;
float d;