Files
Jungfraujoch/common/AzimuthalIntegrationSettings.cpp
T
leonarski_fandClaude Opus 5 2ef8841983 Azimuthal integration: keep the low Q limit below the maximum
Making the high limit optional removed the implicit upper bound on the low one
(it used to follow from high <= maxQ and high > low), so --azim-min-q 50 with no
maximum is accepted and ResolveHighQ then calls std::clamp with its lower bound
above its upper bound, which is undefined.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 10:56:06 +02:00

162 lines
5.7 KiB
C++

// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <algorithm>
#include <cmath>
#include "AzimuthalIntegrationSettings.h"
#include "JFJochException.h"
#define check_max(param, val, max) if ((val) > (max)) throw JFJochException(JFJochExceptionCategory::InputParameterAboveMax, param)
#define check_min(param, val, min) if ((val) < (min)) throw JFJochException(JFJochExceptionCategory::InputParameterBelowMin, param)
#define check_finite(param, val) if (!std::isfinite(val)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, param)
AzimuthalIntegrationSettings::AzimuthalIntegrationSettings() {
UpdateBinCount();
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::SolidAngleCorrection(bool input) {
solid_angle_correction = input;
return *this;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::QRange_recipA(float low, std::optional<float> high) {
check_finite("Low Q for azimuthal integration", low);
check_min("Low Q for azimuthal integration", low, minQ_recipA);
// The low limit has to leave room for at least one bin below maxQ. This used to follow from the
// high limit being mandatory (high <= maxQ and high > low); once "unset" became legal, ResolveHighQ
// was left computing std::clamp(q, low + spacing, maxQ) with the lower bound above the upper one.
check_max("Low Q for azimuthal integration", low, maxQ_recipA - q_spacing);
if (high.has_value()) {
check_finite("High Q for azimuthal integration", *high);
check_max("High Q for azimuthal integration", *high, maxQ_recipA);
if (*high <= low)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"High Q must be higher than low Q");
}
requested_high_q_recipA = high;
low_q_recipA = low;
// Until ResolveHighQ runs, an unset limit keeps the value the bins were last built from.
high_q_recipA = high.value_or(high_q_recipA);
UpdateBinCount();
return *this;
}
void AzimuthalIntegrationSettings::ResolveHighQ(float detector_max_q_recipA) {
if (requested_high_q_recipA.has_value())
return;
high_q_recipA = std::clamp(detector_max_q_recipA, low_q_recipA + q_spacing, maxQ_recipA);
UpdateBinCount();
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::QSpacing_recipA(float input) {
check_finite("Q spacing for azimuthal integration", input);
check_min("Q spacing for azimuthal integration", input, minQ_recipA);
q_spacing = input;
UpdateBinCount();
return *this;
}
bool AzimuthalIntegrationSettings::IsSolidAngleCorrection() const {
return solid_angle_correction;
}
float AzimuthalIntegrationSettings::GetHighQ_recipA() const {
return high_q_recipA;
}
std::optional<float> AzimuthalIntegrationSettings::GetRequestedHighQ_recipA() const {
return requested_high_q_recipA;
}
float AzimuthalIntegrationSettings::GetLowQ_recipA() const {
return low_q_recipA;
}
float AzimuthalIntegrationSettings::GetQSpacing_recipA() const {
return q_spacing;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::BkgEstimateQRange_recipA(float low, float high) {
check_finite("Low Q for background estimation", low);
check_finite("High Q for background estimation", high);
check_max("High Q for background estimation", high, maxQ_recipA);
check_min("Low Q for background estimation", low, minQ_recipA);
if (high <= low)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"High Q must be higher than low Q");
bkg_estimate_low_q_recipA = low;
bkg_estimate_high_q_recipA = high;
return *this;
}
float AzimuthalIntegrationSettings::GetBkgEstimateLowQ_recipA() const {
return bkg_estimate_low_q_recipA;
}
float AzimuthalIntegrationSettings::GetBkgEstimateHighQ_recipA() const {
return bkg_estimate_high_q_recipA;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::PolarizationCorrection(bool input) {
polarization_correction = input;
return *this;
}
bool AzimuthalIntegrationSettings::IsPolarizationCorrection() const {
return polarization_correction;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::AzimuthalBinCount(int32_t input) {
check_min("Azimuthal bin count", input, 1);
check_max("Azimuthal bin count", input, 512);
azim_bins = input;
UpdateBinCount();
return *this;
}
AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::ForceCPUinFPGAWorkflow(bool input) {
force_cpu_in_fpga_workflow = input;
return *this;
}
bool AzimuthalIntegrationSettings::IsForceCPUinFPGAWorkflow() const {
return force_cpu_in_fpga_workflow;
}
int32_t AzimuthalIntegrationSettings::GetQBinCount() const {
return q_bins;
}
int32_t AzimuthalIntegrationSettings::GetAzimuthalBinCount() const {
return azim_bins;
}
int32_t AzimuthalIntegrationSettings::GetBinCount() const {
return total_bins;
}
uint16_t AzimuthalIntegrationSettings::QToBin(float q) const {
return std::min<uint16_t>(GetBinCount() - 1, std::floor(std::max(0.0f, (q - GetLowQ_recipA()) / GetQSpacing_recipA())));
}
uint16_t AzimuthalIntegrationSettings::GetBin(float q, float phi_deg) const {
if (q < low_q_recipA || q >= high_q_recipA)
return UINT16_MAX;
if (phi_deg < 0.0 || phi_deg >= 360)
return UINT16_MAX;
int16_t q_bin = std::floor((q - low_q_recipA) / q_spacing);
int16_t phi_bin = std::floor(phi_deg / 360.0f * azim_bins);
return q_bin + phi_bin * GetQBinCount();
}
void AzimuthalIntegrationSettings::UpdateBinCount() {
q_bins = std::ceil((high_q_recipA - low_q_recipA) / q_spacing);
total_bins = q_bins * azim_bins;
}