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
72 lines
3.4 KiB
C
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;
|
|
}
|