diff --git a/common/AzimuthalIntegrationSettings.cpp b/common/AzimuthalIntegrationSettings.cpp index 6f73cdc68..7dbaff0cb 100644 --- a/common/AzimuthalIntegrationSettings.cpp +++ b/common/AzimuthalIntegrationSettings.cpp @@ -26,7 +26,7 @@ AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::QRange_recipA(float // 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); + check_max("Low Q for azimuthal integration", low, maxQ_recipA - GetRequestedQSpacing_recipA().value_or(minQ_recipA)); if (high.has_value()) { check_finite("High Q for azimuthal integration", *high); check_max("High Q for azimuthal integration", *high, maxQ_recipA); @@ -47,7 +47,8 @@ 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); + high_q_recipA = std::clamp(detector_max_q_recipA, low_q_recipA + GetRequestedQSpacing_recipA().value_or(minQ_recipA), + maxQ_recipA); UpdateBinCount(); } @@ -127,17 +128,6 @@ std::vector AzimuthalIntegrationSettings::GetQBinCentres_recipA() const return ret; } -std::vector AzimuthalIntegrationSettings::GetQBinEdges_recipA() const { - std::vector ret(q_bins + 1); - for (int32_t i = 0; i <= q_bins; i++) { - if (q_bin_spacing == QBinSpacing::Linear) - ret[i] = bin_step * static_cast(i) + low_q_recipA; - else - ret[i] = std::pow(10.0, std::log10(static_cast(low_q_recipA)) + bin_step * static_cast(i)); - } - return ret; -} - AzimuthalIntegrationSettings &AzimuthalIntegrationSettings::BkgEstimateQRange_recipA(float low, float high) { check_finite("Low Q for background estimation", low); check_finite("High Q for background estimation", high); diff --git a/common/AzimuthalIntegrationSettings.h b/common/AzimuthalIntegrationSettings.h index 6c4ebe2c4..5bcaf27be 100644 --- a/common/AzimuthalIntegrationSettings.h +++ b/common/AzimuthalIntegrationSettings.h @@ -68,15 +68,15 @@ public: [[nodiscard]] float GetHighQ_recipA() const; [[nodiscard]] std::optional GetRequestedHighQ_recipA() const; [[nodiscard]] float GetLowQ_recipA() const; - // The width of a linear bin (as given, or as the bin count makes it). + // The width of a linear bin (as given, or as the bin count makes it). Under log10 binning it is the + // last linear spacing and means nothing about the bins - use GetQBinCentres_recipA. [[nodiscard]] float GetQSpacing_recipA() const; [[nodiscard]] QBinSpacing GetQBinSpacing() const; // The spacing and the bin count as they were given; one of them is unset. [[nodiscard]] std::optional GetRequestedQSpacing_recipA() const; [[nodiscard]] std::optional GetRequestedQBinCount() const; - // Centre of every Q bin (mid-point of its edges on the bin scale), and its GetQBinCount() + 1 edges. + // Centre of every Q bin: the mid-point of its edges on the bin scale. [[nodiscard]] std::vector GetQBinCentres_recipA() const; - [[nodiscard]] std::vector GetQBinEdges_recipA() const; [[nodiscard]] int32_t GetBinCount() const; [[nodiscard]] int32_t GetQBinCount() const; diff --git a/docs/JFJOCH_BROKER.md b/docs/JFJOCH_BROKER.md index 3ce30a9a7..6424b5404 100644 --- a/docs/JFJOCH_BROKER.md +++ b/docs/JFJOCH_BROKER.md @@ -179,6 +179,8 @@ setting (`/config/dataset`, `polarization_factor`), not part of `azim_int`. The are linear by default, set by `q_spacing` (0.01 Å⁻¹ when nothing is given) or by their number, `q_bins` - one or the other, not both. `"q_binning": "log10"` lays them out in equal steps of log10 Q instead (fine at low Q, coarse at high Q); it takes `q_bins` and refuses `q_spacing`. +A GET returns the one that was set, so once `q_bins` (or log10) is configured the reply carries +`q_bins` and no `q_spacing`, which a client generated before `q_spacing` became optional cannot parse. ## Following a collection diff --git a/tests/AzimuthalIntegrationTest.cpp b/tests/AzimuthalIntegrationTest.cpp index 084a2bdbc..b773cd671 100644 --- a/tests/AzimuthalIntegrationTest.cpp +++ b/tests/AzimuthalIntegrationTest.cpp @@ -678,8 +678,10 @@ TEST_CASE("AzimuthalIntegrationSettings_QBinning_Validation","[AzimuthalIntegrat CHECK(s.GetQBinCount() == 50); } -// The linear spacing bins exactly as it always has: the same count, the same bin for every q and the -// same centres, compared bit for bit with the formulas it used before log10 binning was added. +// The linear spacing bins as it always has: the same count, the same bin for every q and the same +// centres, compared bit for bit with the formulas it used before log10 binning was added. The one +// exception, which this uniform sample of q cannot reach, is a q that rounds onto the far edge of the +// last bin: it now stays in that bin (AzimuthalIntegrationSettings_Linear_FarEdge). TEST_CASE("AzimuthalIntegrationSettings_Linear_Unchanged","[AzimuthalIntegration]") { const float low = 0.1f, high = 4.5f, spacing = 0.01f; AzimuthalIntegrationSettings s; @@ -704,14 +706,22 @@ TEST_CASE("AzimuthalIntegrationSettings_Linear_Unchanged","[AzimuthalIntegration } } +// A q just below high Q whose (q - low) / spacing rounds up to the bin count used to spill into the +// first bin of the next azimuthal row. +TEST_CASE("AzimuthalIntegrationSettings_Linear_FarEdge","[AzimuthalIntegration]") { + const float low = 0.1f, high = 3.7f, spacing = 0.1f; + AzimuthalIntegrationSettings s; + s.QSpacing_recipA(spacing).QRange_recipA(low, high).AzimuthalBinCount(2); + const float q = std::nextafter(high, 0.0f); + REQUIRE(static_cast(std::floor((q - low) / spacing)) == s.GetQBinCount()); + CHECK(s.GetBin(q, 350.0f) == 2 * s.GetQBinCount() - 1); +} + TEST_CASE("AzimuthalIntegrationSettings_Linear_BinCount","[AzimuthalIntegration]") { AzimuthalIntegrationSettings s; s.QRange_recipA(0.1f, 4.1f).QBinning(QBinSpacing::Linear, std::nullopt, 40); + CHECK(s.GetQSpacing_recipA() == Catch::Approx(0.1)); - const auto edges = s.GetQBinEdges_recipA(); - REQUIRE(edges.size() == 41); - CHECK(edges.front() == Catch::Approx(0.1)); - CHECK(edges.back() == Catch::Approx(4.1)); const auto centres = s.GetQBinCentres_recipA(); CHECK(centres[0] == Catch::Approx(0.15)); CHECK(centres[39] == Catch::Approx(4.05)); @@ -730,12 +740,12 @@ TEST_CASE("AzimuthalIntegrationSettings_Log10","[AzimuthalIntegration]") { REQUIRE(s.GetQBinCount() == n); REQUIRE(s.GetBinCount() == 2 * n); - const auto edges = s.GetQBinEdges_recipA(); + // The edges as the docs state them: q_i = low (high / low)^(i / n). + std::vector edges(n + 1); + for (int i = 0; i <= n; i++) + edges[i] = low * std::pow(static_cast(high) / low, static_cast(i) / n); const auto centres = s.GetQBinCentres_recipA(); - REQUIRE(edges.size() == n + 1); REQUIRE(centres.size() == n); - CHECK(edges.front() == Catch::Approx(low)); - CHECK(edges.back() == Catch::Approx(high)); // Equal steps in log10(q): three decades in 30 bins, every edge 10^0.1 times the one before. for (int i = 0; i < n; i++) { CHECK(edges[i + 1] > edges[i]); @@ -779,7 +789,9 @@ TEST_CASE("AzimuthalIntegrationMapping_Log10","[AzimuthalIntegration]") { PixelMask pixel_mask(x); AzimuthalIntegrationMapping mapping(x, pixel_mask); const auto &s = mapping.Settings(); - const auto edges = s.GetQBinEdges_recipA(); + std::vector edges(101); + for (int i = 0; i <= 100; i++) + edges[i] = 0.01 * std::pow(2.0 / 0.01, i / 100.0); const auto centres = s.GetQBinCentres_recipA(); REQUIRE(mapping.GetQBinCount() == 100);