The whole-run passes retain every frame's integrated reflections until scaling is done - thousands of vectors of a few megabytes each, allocated by the image workers in the allocator's per-thread arenas. When a pass hands them back, most of that memory stays in those arenas as holes, and the next pass's workers (new threads) do not reuse it, so on a fine-sliced long axis gigabytes of freed reflections were carried to the end of the run. IndexAndRefine now copies each retained frame's reflections into a ReflectionArena: 64 MiB blocks, each its own mapping, carved by a bump pointer and returned to the system in one piece when the last vector in them is gone. IntegrationOutcome::reflections becomes a std::vector with an allocator that uses the arena when given one and plain new/delete otherwise (copies go to the heap), so the read sites are unchanged; the few functions that took the vector by type now take a span. No arithmetic changes; merged output byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nW6FNRP1bBJJ8pfHiByAT
38 lines
1.7 KiB
C++
38 lines
1.7 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <algorithm>
|
|
|
|
#include "../common/Reflection.h"
|
|
#include "../common/CrystalLattice.h"
|
|
#include "../common/DiffractionGeometry.h"
|
|
#include "ReflectionArena.h"
|
|
|
|
// A frame's integrated reflections. The whole-run passes keep them in a ReflectionArena (see there);
|
|
// anywhere else the allocator is plain new/delete.
|
|
using ReflectionVector = std::vector<Reflection, ArenaAllocator<Reflection>>;
|
|
|
|
struct IntegrationOutcome {
|
|
DiffractionGeometry geom;
|
|
CrystalLattice latt;
|
|
ReflectionVector reflections;
|
|
std::optional<float> mosaicity_deg;
|
|
std::optional<float> image_scale_cc;
|
|
std::optional<int64_t> image_scale_cc_n;
|
|
std::optional<float> image_scale_g;
|
|
std::optional<float> image_scale_wedge_deg;
|
|
};
|
|
|
|
// How far apart, in frames, two partials of one raw hkl may sit and still belong to the same rocking
|
|
// event. The quantity being bridged is an ANGLE - a reflecting range, a tenth to half a degree - so
|
|
// spelling it as a frame count makes the bridge grow with the slicing: at 1 deg per frame the two
|
|
// frames that were always allowed are 2 deg of dead rotation, as wide as a whole event, and two
|
|
// genuine Ewald crossings fuse into one "full". Half a degree of bridge instead, floored at one frame
|
|
// (never cut an event at its own neighbours) and capped at the two frames that were always used. For
|
|
// any wedge of 0.25 deg or less the quotient is at least two and the cap returns the literal 2.0f, so
|
|
// finely sliced data is bridged exactly as before.
|
|
inline float RockingEventFrameGap(float wedge_deg) {
|
|
return std::min(2.0f, std::max(1.0f, 0.5f / wedge_deg));
|
|
} |