// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // 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 #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(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(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; }