Files
Jungfraujoch/image_analysis/bragg_prediction/RotationColumnRange.h
T
leonarski_fandClaude Opus 5.5 f117484592 GPU rotation prediction walks only the l of each column that can cross the Ewald sphere
bragg_rot_kernel_3d ran one thread for every (h, k, l) of the box on every frame, while the CPU
predictor already skips the l of an (h, k) column whose phi solution cannot lie within the
rocking-curve test's reach (BraggPredictionRot: f(l) = |p0|^2 + 2 S0.p0 bounded by f_max, at
most two intervals of l). The GPU predictor now walks the same l:

- RotationColumnRange.h: the CPU predictor's phi limit and column bound, moved into a
  host/device header shared by both, unchanged in arithmetic except that the four divisions
  by 2 C*.C* are a product with its reciprocal (each end is widened by a whole index, which
  covers the rounding).
- column_range_kernel: the l range of every column, one thread a column, in double; the
  column's b and c are evaluated as polynomials in h and k from coefficients set up on the
  host, and the blocks are 32 threads, because a consumer card runs double at a small
  fraction of the float rate on a unit the warps of a multiprocessor share (a warp per column
  with the range on lane 0 was slower than the whole box).
- bragg_rot_kernel_columns: a warp per column, its l spread over the lanes; per (h, k, l) the
  same compute_reflections_rot. The output is sorted afterwards (OrderAndSelect), so the
  predictions and their order are those of the whole box.

Kernel time on the RTX 5080 (nsys, whole run): 4nwv 1968 -> 415 ms (bragg 374 + ranges 41),
myob 45.5 -> 49.9 ms (small box: the ranges cost more than they save there, ~4 ms a run). On
the Windows laptop bragg_rot_kernel_3d was 9.4 s of 4nwv.

BraggPredictionRotGPU_ColumnRangeKeepsEveryReflection: on 300 random lattices, geometries and
rocking widths the pruned walk predicts bit for bit what the whole box does (the GPU twin of
the CPU test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXc1zD1yxHqaR1DGdCTHL4
2026-10-17 12:43:20 +02:00

72 lines
3.4 KiB
C

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
// Which l of an (h, k) column a rotation predictor needs to walk, shared by the CPU and the GPU predictor
// (BraggPredictionRot.cpp explains the bound) so that both walk the same l.
#include <cmath>
#include "../../common/JFJochMath.h"
#ifdef __CUDACC__
#define ROTATION_COLUMN_HD __host__ __device__ inline
#else
#define ROTATION_COLUMN_HD inline
#endif
// The largest |phi| at which a solution can still pass the rocking-curve test: a solution further from
// the frame has zeta (|phi| - half wedge) above the test's limit for every zeta >= min_zeta, the rocking
// width being largest at the resolution limit. With a 1e-3 rad margin, far above float rounding.
// -1 = nothing is bounded (no min_zeta, or a bound past pi).
ROTATION_COLUMN_HD double RotationPhiLimit(float min_zeta, float mos_angle_rad, float bandwidth_sigma,
float half_wavelength_A, float one_over_dmax,
float half_wedge_angle_rad, float mosaicity_multiplier) {
if (!(min_zeta > 0.0f))
return -1.0;
double mos_max_rad = mos_angle_rad;
if (bandwidth_sigma > 0.0f) {
const double sin_theta = half_wavelength_A * static_cast<double>(one_over_dmax);
const double dphi_bw = sin_theta < 1.0
? bandwidth_sigma * sin_theta / sqrt(1.0 - sin_theta * sin_theta)
: INFINITY;
mos_max_rad = sqrt(mos_max_rad * mos_max_rad + dphi_bw * dphi_bw);
}
const double phi_limit = half_wedge_angle_rad + (mosaicity_multiplier * mos_max_rad + 1e-5) / min_zeta + 1e-3;
return phi_limit < PI ? phi_limit : -1.0;
}
// The l of a column to walk: f(l) = C_C l^2 + b l + c <= f_max on [l_lo, l_hi], and f(l) < -f_max strictly
// inside (skip_lo, skip_hi), which may be empty. False where no l of the column can pass. inv_2C_C is
// 1 / (2 C_C): a product rather than four divisions, which on a GPU are each a sequence of double
// operations; the whole-index widening of each end covers the difference in rounding.
struct RotationColumn {
int l_lo, l_hi, skip_lo, skip_hi;
};
ROTATION_COLUMN_HD bool RotationColumnRange(double C_C, double inv_2C_C, double b, double c, double f_max,
int max_l, RotationColumn &col) {
// A root of the quadratic as an index, held inside the box first so the conversion cannot overflow.
const auto to_l = [max_l](double v) {
const double lo = -max_l - 2.0, hi = max_l + 2.0;
return static_cast<int>(v < lo ? lo : (v > hi ? hi : v));
};
col = {-max_l, max_l, max_l + 1, max_l + 1};
const double disc_hi = b * b - 4.0 * C_C * (c - f_max);
if (!(disc_hi >= 0.0))
return false;
const double r_hi = sqrt(disc_hi);
const int lo = to_l(ceil((-b - r_hi) * inv_2C_C)) - 1;
const int hi = to_l(floor((-b + r_hi) * inv_2C_C)) + 1;
col.l_lo = lo > col.l_lo ? lo : col.l_lo;
col.l_hi = hi < col.l_hi ? hi : col.l_hi;
const double disc_lo = b * b - 4.0 * C_C * (c + f_max);
if (disc_lo > 0.0) {
const double r_lo = sqrt(disc_lo);
col.skip_lo = to_l(floor((-b - r_lo) * inv_2C_C)) + 2;
col.skip_hi = to_l(ceil((-b + r_lo) * inv_2C_C)) - 2;
}
return true;
}