Merge branch 'main' into feature/cuda_clusterfinder
Build on RHEL8 / build (push) Successful in 3m15s
Build on RHEL9 / build (push) Successful in 3m39s
Run tests using data on local RHEL8 / build (push) Successful in 3m51s

This commit is contained in:
Khalil Ferjaoui
2026-04-23 13:52:52 +02:00
44 changed files with 4251 additions and 3801 deletions
+36 -30
View File
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: MPL-2.0
#pragma once
#include <stdexcept>
#include "aare/Models.hpp"
#include "aare/NDView.hpp"
#include <Minuit2/FCNGradientBase.h>
#include <algorithm>
#include <cmath>
#include <stdexcept>
#include <vector>
#include <Minuit2/FCNGradientBase.h>
#include "aare/NDView.hpp"
#include "aare/Models.hpp"
namespace aare {
@@ -29,7 +29,7 @@ namespace func {
*
* By providing analytic gradients we avoid 2*npar extra function evaluations
* per Minuit step that would otherwise be spent on finite differences.
*
*
* @throws std::invalid_argument if par.size() != Model::npar.
*
* Invalid model parameters do not throw; they return a large penalty
@@ -37,31 +37,32 @@ namespace func {
*/
template <class Model>
class Chi2Model1DGrad : public ROOT::Minuit2::FCNGradientBase {
public:
Chi2Model1DGrad(NDView<double, 1> x,
NDView<double, 1> y)
public:
Chi2Model1DGrad(NDView<double, 1> x, NDView<double, 1> y)
: x_(x), y_(y), s_(), weighted_(false) {}
Chi2Model1DGrad(NDView<double, 1> x,
NDView<double, 1> y,
Chi2Model1DGrad(NDView<double, 1> x, NDView<double, 1> y,
NDView<double, 1> y_err)
: x_(x), y_(y), s_(y_err), weighted_(true) {}
~Chi2Model1DGrad() override = default;
double operator()(const std::vector<double>& par) const override {
double operator()(const std::vector<double> &par) const override {
if (par.size() != Model::npar) {
throw std::invalid_argument("Chi2Model1DGrad: wrong parameter vector size.");
throw std::invalid_argument(
"Chi2Model1DGrad: wrong parameter vector size.");
}
if (!Model::is_valid(par)) return 1e20;
if (!Model::is_valid(par))
return 1e20;
double chi2 = 0.0;
if (weighted_) {
for (ssize_t i = 0; i < x_.size(); ++i) {
const double si = s_[i];
if (si == 0.0) continue;
if (si == 0.0)
continue;
const double f_i = Model::eval(x_[i], par);
const double r_i = y_[i] - f_i;
@@ -78,13 +79,16 @@ public:
return chi2;
}
std::vector<double> Gradient(const std::vector<double>& par) const override {
std::vector<double>
Gradient(const std::vector<double> &par) const override {
if (par.size() != Model::npar) {
throw std::invalid_argument("Chi2Model1DGrad: wrong parameter vector size.");
throw std::invalid_argument(
"Chi2Model1DGrad: wrong parameter vector size.");
}
std::vector<double> grad(Model::npar, 0.0);
if (!Model::is_valid(par)) return grad;
if (!Model::is_valid(par))
return grad;
std::array<double, Model::npar> df{};
double f_i = 0.0;
@@ -92,12 +96,13 @@ public:
if (weighted_) {
for (ssize_t i = 0; i < x_.size(); ++i) {
const double si = s_[i];
if (si == 0.0) continue;
if (si == 0.0)
continue;
Model::eval_and_grad(x_[i], par, f_i, df);
const double r_i = y_[i] - f_i;
const double c = -2.0 * r_i / (si * si);
const double c = -2.0 * r_i / (si * si);
for (std::size_t k = 0; k < Model::npar; ++k) {
grad[k] += c * df[k];
@@ -108,7 +113,7 @@ public:
Model::eval_and_grad(x_[i], par, f_i, df);
const double r_i = y_[i] - f_i;
const double c = -2.0 * r_i;
const double c = -2.0 * r_i;
for (std::size_t k = 0; k < Model::npar; ++k) {
grad[k] += c * df[k];
@@ -119,10 +124,11 @@ public:
return grad;
}
/** @brief Error definition: 1.0 for chi-squared (delta_chi2 = 1 -> 1-sigma). */
/** @brief Error definition: 1.0 for chi-squared (delta_chi2 = 1 ->
* 1-sigma). */
double Up() const override { return 1.0; }
private:
private:
NDView<double, 1> x_;
NDView<double, 1> y_;
NDView<double, 1> s_;
@@ -131,12 +137,12 @@ private:
// ── Convenient aliases ──────────────────────────────────────────────
using Chi2Gaussian = Chi2Model1DGrad<aare::model::Gaussian>;
using Chi2RisingScurve = Chi2Model1DGrad<aare::model::RisingScurve>;
using Chi2Gaussian = Chi2Model1DGrad<aare::model::Gaussian>;
using Chi2RisingScurve = Chi2Model1DGrad<aare::model::RisingScurve>;
using Chi2FallingScurve = Chi2Model1DGrad<aare::model::FallingScurve>;
using Chi2Pol1 = Chi2Model1DGrad<aare::model::Pol1>;
using Chi2Pol2 = Chi2Model1DGrad<aare::model::Pol2>;
using Chi2Pol1 = Chi2Model1DGrad<aare::model::Pol1>;
using Chi2Pol2 = Chi2Model1DGrad<aare::model::Pol2>;
} // namespace aare::func
} // namespace func
} // aare
} // namespace aare
+1 -1
View File
@@ -17,7 +17,7 @@ template <class ItemType> class CircularFifo {
aare::ProducerConsumerQueue<ItemType> filled_slots;
public:
CircularFifo() : CircularFifo(100){};
CircularFifo() : CircularFifo(100) {};
CircularFifo(uint32_t size)
: fifo_size(size), free_slots(size + 1), filled_slots(size + 1) {
+21
View File
@@ -59,6 +59,27 @@ class ClusterVector<Cluster<T, ClusterSizeX, ClusterSizeY, CoordType>> {
other.m_data.clear();
}
/**
* @brief Create a copy of the clustervector by filtering clusters in the
* ClusterVector using a boolean mask.
* @param mask boolean 1d mask
* @return ClusterVector containing only the clusters where the mask is
* true
*/
ClusterVector operator()(NDView<bool, 1> mask) {
if (static_cast<size_t>(mask.size()) != m_data.size()) {
throw std::runtime_error(
LOCATION + "Mask size does not match number of clusters");
}
ClusterVector result(capacity(), frame_number());
for (size_t i = 0; i < m_data.size(); ++i) {
if (mask(i)) {
result.push_back(m_data[i]);
}
}
return result;
}
// Move assignment operator
ClusterVector &operator=(ClusterVector &&other) noexcept {
if (this != &other) {
+66 -66
View File
@@ -5,17 +5,17 @@
#include <fmt/core.h>
#include <vector>
#include "aare/utils/par.hpp"
#include "aare/utils/task.hpp"
#include "aare/NDArray.hpp"
#include "aare/Chi2.hpp"
#include "aare/FitModel.hpp"
#include "aare/NDArray.hpp"
#include "aare/utils/par.hpp"
#include "aare/utils/task.hpp"
#include "Minuit2/FunctionMinimum.h"
#include "Minuit2/MnMigrad.h"
#include "Minuit2/MnHesse.h"
#include "Minuit2/MnUserParameters.h"
#include "Minuit2/MnMigrad.h"
#include "Minuit2/MnPrint.h"
#include "Minuit2/MnUserParameters.h"
namespace aare {
@@ -34,7 +34,6 @@ NDArray<double, 1> scurve2(NDView<double, 1> x, NDView<double, 1> par);
} // namespace func
static constexpr int DEFAULT_NUM_THREADS = 4;
/**
@@ -80,7 +79,6 @@ void fit_gaus(NDView<double, 1> x, NDView<double, 3> y, NDView<double, 3> y_err,
NDView<double, 3> par_out, NDView<double, 3> par_err_out,
NDView<double, 2> chi2_out, int n_threads = DEFAULT_NUM_THREADS);
NDArray<double, 1> fit_pol1(NDView<double, 1> x, NDView<double, 1> y);
NDArray<double, 3> fit_pol1(NDView<double, 1> x, NDView<double, 3> y,
@@ -136,7 +134,8 @@ void fit_scurve2(NDView<double, 1> x, NDView<double, 3> y,
* - Neither: both value and step size auto-filled from data.
*
* @tparam Model Model struct (Gaussian, RisingScurve, …).
* @tparam FCN Chi2 functor type (Chi2Model1D or Chi2Model1DGrad instantiation).
* @tparam FCN Chi2 functor type (Chi2Model1D or Chi2Model1DGrad
* instantiation).
*
* @param model The FitModel configuration (read-only).
* @param upar_local Thread-local clone of model.upar(). Modified in place.
@@ -148,20 +147,19 @@ void fit_scurve2(NDView<double, 1> x, NDView<double, 3> y,
* - compute_errors: [p0..pN, err0..errN, chi2] -> 2*npar + 1
* - otherwise: [p0..pN, chi2] -> npar + 1
*/
template<typename Model, typename FCN>
NDArray<double, 1> fit_pixel(const FitModel<Model>& model,
ROOT::Minuit2::MnUserParameters& upar_local,
NDView<double, 1> x,
NDView<double, 1> y,
NDView<double, 1> y_err) {
template <typename Model, typename FCN>
NDArray<double, 1> fit_pixel(const FitModel<Model> &model,
ROOT::Minuit2::MnUserParameters &upar_local,
NDView<double, 1> x, NDView<double, 1> y,
NDView<double, 1> y_err) {
constexpr std::size_t npar = Model::npar;
constexpr std::size_t npar = Model::npar;
const bool want_errors = model.compute_errors();
const ssize_t result_size = want_errors ? (2 * npar + 1) : (npar + 1);
// ──── automatic parameter estimation ─────────────
auto start = Model::estimate_par(x,y);
auto start = Model::estimate_par(x, y);
// dead / degenerate pixel guard
if (!Model::is_valid(std::vector<double>(start.begin(), start.end()))) {
return NDArray<double, 1>({result_size}, 0.0);
@@ -170,18 +168,18 @@ NDArray<double, 1> fit_pixel(const FitModel<Model>& model,
// ──── data-range statistics for step sizes ─────────────
double x_range, y_range, slope_scale;
model::compute_ranges(x, y, x_range, y_range, slope_scale);
std::array<double, npar> steps{};
Model::compute_steps(start, x_range, y_range, slope_scale, steps);
// ── apply auto-estimates respecting user precedence ─────────────
for(std::size_t i = 0; i < npar; ++i){
for (std::size_t i = 0; i < npar; ++i) {
// fixed: do not touch at all
if(model.is_user_fixed(i)){
if (model.is_user_fixed(i)) {
continue;
}
if(!model.is_user_start(i)){
if (!model.is_user_start(i)) {
upar_local.SetValue(i, start[i]);
}
@@ -193,7 +191,8 @@ NDArray<double, 1> fit_pixel(const FitModel<Model>& model,
// ──── run minimizer ────────
ROOT::Minuit2::MnMigrad migrad(chi2, upar_local, model.strategy());
ROOT::Minuit2::FunctionMinimum min = migrad(model.max_calls(), model.tolerance());
ROOT::Minuit2::FunctionMinimum min =
migrad(model.max_calls(), model.tolerance());
if (!min.IsValid())
return NDArray<double, 1>({result_size}, 0.0);
@@ -202,20 +201,20 @@ NDArray<double, 1> fit_pixel(const FitModel<Model>& model,
if (want_errors) {
ROOT::Minuit2::MnHesse hesse;
hesse(chi2, min);
const auto& values = min.UserState().Params();
const auto& errors = min.UserState().Errors();
const auto &values = min.UserState().Params();
const auto &errors = min.UserState().Errors();
NDArray<double, 1> result({result_size});
for (std::size_t k = 0; k < npar; ++k) {
result[k] = values[k];
result[k] = values[k];
result[npar + k] = errors[k];
}
result[2 * npar] = min.Fval();
return result;
}
const auto& values = min.UserState().Params();
const auto &values = min.UserState().Params();
NDArray<double, 1> result({result_size});
for (std::size_t k = 0; k < npar; ++k)
result[k] = values[k];
@@ -225,21 +224,16 @@ NDArray<double, 1> fit_pixel(const FitModel<Model>& model,
// ── self-contained for 1D / standalone use ─────────
template <typename Model, typename FCN>
NDArray<double, 1> fit_pixel(const FitModel<Model>& model,
NDView<double, 1> x,
NDView<double, 1> y,
NDView<double, 1> y_err)
{
NDArray<double, 1> fit_pixel(const FitModel<Model> &model, NDView<double, 1> x,
NDView<double, 1> y, NDView<double, 1> y_err) {
auto upar_local = model.upar();
return fit_pixel<Model, FCN>(model, upar_local, x, y, y_err);
}
// Overload: uncertainties not provided
template <typename Model, typename FCN>
NDArray<double, 1> fit_pixel(const FitModel<Model>& model,
NDView<double, 1> x,
NDView<double, 1> y)
{
NDArray<double, 1> fit_pixel(const FitModel<Model> &model, NDView<double, 1> x,
NDView<double, 1> y) {
auto upar_local = model.upar();
return fit_pixel<Model, FCN>(model, upar_local, x, y, NDView<double, 1>{});
}
@@ -257,68 +251,74 @@ NDArray<double, 1> fit_pixel(const FitModel<Model>& model,
* @param model Fit configuration shared by all pixels.
* @param x Scan points, shape `(n_scan)`.
* @param y Measured values, shape `(rows, cols, n_scan)`.
* @param y_err Uncertainties, same shape as y, or empty for unweighted fits.
* @param y_err Uncertainties, same shape as y, or empty for unweighted
* fits.
* @param par_out Output parameters, shape `(rows, cols, npar)`.
* @param err_out Output parameter errors, shape `(rows, cols, npar)`, if used.
* @param chi2_out Output chi-squared / objective values, shape `(rows, cols)`.
* @param err_out Output parameter errors, shape `(rows, cols, npar)`, if
* used.
* @param chi2_out Output chi-squared / objective values, shape `(rows,
* cols)`.
* @param n_threads Number of threads used to split rows.
*
*/
template <typename Model, typename FCN>
void fit_3d(const FitModel<Model>& model,
NDView<double, 1> x, // (n_scan)
NDView<double, 3> y, // (rows, cols, n_scan)
NDView<double, 3> y_err, // (rows, cols, n_scan) or empty for unweighted fit
NDView<double, 3> par_out,
NDView<double, 3> err_out,
NDView<double, 2> chi2_out,
int n_threads)
{
void fit_3d(
const FitModel<Model> &model, NDView<double, 1> x, // (n_scan)
NDView<double, 3> y, // (rows, cols, n_scan)
NDView<double, 3> y_err, // (rows, cols, n_scan) or empty for unweighted fit
NDView<double, 3> par_out, NDView<double, 3> err_out,
NDView<double, 2> chi2_out, int n_threads) {
const std::size_t npar = Model::npar;
// ──── checks ───────
if (x.size() != y.shape(2))
throw std::runtime_error("fit_3d: x.size() must match y.shape(2).");
if (par_out.shape(0) != y.shape(0) || par_out.shape(1) != y.shape(1) || par_out.shape(2) != npar)
if (par_out.shape(0) != y.shape(0) || par_out.shape(1) != y.shape(1) ||
par_out.shape(2) != npar)
throw std::runtime_error("par_out must have shape [rows, cols, npar].");
if (chi2_out.shape(0) != y.shape(0) || chi2_out.shape(1) != y.shape(1))
throw std::runtime_error("chi2_out must have shape [rows, cols].");
const bool has_errors = (y_err.size() > 0);
const bool want_par_errors = (err_out.size() > 0) && model.compute_errors();
if (has_errors) {
if (y.shape(0) != y_err.shape(0) || y.shape(1) != y_err.shape(1) || y.shape(2) != y_err.shape(2))
throw std::runtime_error("fit_3d: y and y_err must have identical shape.");
if (y.shape(0) != y_err.shape(0) || y.shape(1) != y_err.shape(1) ||
y.shape(2) != y_err.shape(2))
throw std::runtime_error(
"fit_3d: y and y_err must have identical shape.");
if (err_out.shape(0) != y.shape(0) || err_out.shape(1) != y.shape(1) || err_out.shape(2) != npar)
throw std::runtime_error("err_out must have shape [rows, cols, npar].");
if (err_out.shape(0) != y.shape(0) || err_out.shape(1) != y.shape(1) ||
err_out.shape(2) != npar)
throw std::runtime_error(
"err_out must have shape [rows, cols, npar].");
}
// ──── parallel dispatch ───────
auto process = [&](ssize_t first_row, ssize_t last_row) {
// one clone per thread
auto upar_local = model.upar();
// one clone per thread
auto upar_local = model.upar();
for (ssize_t row = first_row; row < last_row; row++) {
for (ssize_t col = 0; col < y.shape(1); col++) {
NDView<double, 1> values(&y(row, col, 0), {y.shape(2)});
NDView<double, 1> errors = has_errors
? NDView<double, 1>(&y_err(row, col, 0), {y_err.shape(2)})
: NDView<double, 1>{};
NDView<double, 1> errors =
has_errors ? NDView<double, 1>(&y_err(row, col, 0),
{y_err.shape(2)})
: NDView<double, 1>{};
auto res = fit_pixel<Model, FCN>(model, upar_local, x, values, errors);
for(std::size_t k = 0; k < npar; ++k) {
auto res =
fit_pixel<Model, FCN>(model, upar_local, x, values, errors);
for (std::size_t k = 0; k < npar; ++k) {
par_out(row, col, k) = res(k);
}
if (want_par_errors) {
for(std::size_t k = 0; k < npar; ++k){
for (std::size_t k = 0; k < npar; ++k) {
err_out(row, col, k) = res(npar + k);
}
chi2_out(row, col) = res(2 * npar);
+72 -39
View File
@@ -1,17 +1,15 @@
// SPDX-License-Identifier: MPL-2.0
#pragma once
#include <type_traits>
#include "aare/Models.hpp"
#include <type_traits>
#include "Minuit2/MnUserParameters.h"
#include "Minuit2/MnStrategy.h"
#include "Minuit2/MnUserParameters.h"
namespace aare {
template <typename Model>
class FitModel {
template <typename Model> class FitModel {
ROOT::Minuit2::MnUserParameters upar_;
ROOT::Minuit2::MnStrategy strategy_;
unsigned int max_calls_;
@@ -21,45 +19,51 @@ class FitModel {
std::array<bool, Model::npar> user_fixed_{};
std::array<bool, Model::npar> user_start_{};
public:
/** @brief Safely resolve a parameter name to its index. */
unsigned int checked_index(const std::string &name) const {
for (std::size_t i = 0; i < npar; ++i) {
if (upar_.Name(i) == name)
return static_cast<unsigned int>(i);
}
throw std::runtime_error("FitModel: unknown parameter name '" + name +
"'");
}
public:
static constexpr std::size_t npar = Model::npar;
/**
* @brief Construct a fit model with sensible defaults.
*
* @param strategy Minuit2 strategy level (0 = fast/gradient, 1 = default).
* @param strategy Minuit2 strategy level (0 = fast/gradient, 1 =
* default).
* @param max_calls Maximum FCN calls per pixel minimisation.
* @param tolerance Minuit2 EDM tolerance.
* @param compute_errors If true, run MnHesse after minimisation.
*/
FitModel(unsigned int strategy = 0,
unsigned int max_calls = 100,
double tolerance = 0.5,
bool compute_errors = false)
: strategy_(strategy),
max_calls_(max_calls),
tolerance_(tolerance),
compute_errors_(compute_errors)
{
for(std::size_t i = 0; i < npar; ++i){
FitModel(unsigned int strategy = 0, unsigned int max_calls = 100,
double tolerance = 0.5, bool compute_errors = false)
: strategy_(strategy), max_calls_(max_calls), tolerance_(tolerance),
compute_errors_(compute_errors) {
for (std::size_t i = 0; i < npar; ++i) {
const auto pi = Model::param_info[i];
const bool has_lo = std::isfinite(pi.default_lo);
const bool has_hi = std::isfinite(pi.default_hi);
// Add parameters and valid bounds
if (has_lo && has_hi){
if (has_lo && has_hi) {
upar_.Add(pi.name, 0.0, 1.0, pi.default_lo, pi.default_hi);
} else if (has_lo) {
upar_.Add(pi.name, 0.0, 1.0, pi.default_lo, 1e6);
} else {
upar_.Add(pi.name, 0.0, 1.0);
}
}
}
}
/** @brief Set lower and upper bounds for parameter idx.*/
void SetParLimits(unsigned int idx, double lo, double hi) {
upar_.SetLimits(idx, lo, hi);
void SetParLimits(unsigned int idx, double lo, double hi) {
upar_.SetLimits(idx, lo, hi);
}
/**
@@ -67,38 +71,67 @@ public:
*
* Excluded from minimisation. Automatic estimates will not touch it.
*/
void FixParameter(unsigned int idx, double val) {
upar_.SetValue(idx, val);
void FixParameter(unsigned int idx, double val) {
SetParameter(idx, val);
upar_.Fix(idx);
user_start_[idx] = true;
user_fixed_[idx] = true;
}
/** @brief Release a previously fixed parameter, re-enabling auto estimates. */
/** @brief Release a previously fixed parameter, re-enabling auto estimates.
*/
void ReleaseParameter(unsigned int idx) {
upar_.Release(idx);
user_fixed_[idx] = false;
}
void ReleaseParameter(const std::string &name) {
ReleaseParameter(checked_index(name));
}
/** @brief Set an explicit starting value for parameter idx.*/
void SetParameter(unsigned int idx, double val) {
upar_.SetValue(idx, val);
user_start_[idx] = true;
}
void SetMaxCalls(unsigned int n) { max_calls_ = n; }
void SetTolerance(double t) { tolerance_ = t; }
void SetComputeErrors(bool b) { compute_errors_ = b; }
void SetParameter(const std::string &name, double val) {
// go through index to maintain user_start_ bookkeeping
SetParameter(checked_index(name), val);
}
void FixParameter(const std::string &name, double val) {
// go through index to maintain user_fixed_ bookkeeping
FixParameter(checked_index(name), val);
}
void SetParLimits(const std::string &name, double lo, double hi) {
SetParLimits(checked_index(name), lo, hi);
}
std::string GetParName(unsigned int idx) const {
return upar_.GetName(idx);
}
std::vector<std::string> GetParNames() const {
std::vector<std::string> names;
for (std::size_t i = 0; i < npar; ++i)
names.push_back(GetParName(i));
return names;
}
static constexpr std::size_t GetNpar() noexcept { return npar; }
void SetMaxCalls(unsigned int n) { max_calls_ = n; }
void SetTolerance(double t) { tolerance_ = t; }
void SetComputeErrors(bool b) { compute_errors_ = b; }
// accessors
const ROOT::Minuit2::MnUserParameters& upar() const { return upar_; }
const ROOT::Minuit2::MnStrategy& strategy() const { return strategy_; }
unsigned int max_calls() const { return max_calls_; }
double tolerance() const { return tolerance_; }
bool compute_errors() const { return compute_errors_; }
bool is_user_fixed(unsigned int idx) const { return user_fixed_[idx]; }
bool is_user_start(unsigned int idx) const { return user_start_[idx]; }
const ROOT::Minuit2::MnUserParameters &upar() const { return upar_; }
const ROOT::Minuit2::MnStrategy &strategy() const { return strategy_; }
unsigned int max_calls() const { return max_calls_; }
double tolerance() const { return tolerance_; }
bool compute_errors() const { return compute_errors_; }
bool is_user_fixed(unsigned int idx) const { return user_fixed_[idx]; }
bool is_user_start(unsigned int idx) const { return user_start_[idx]; }
};
} // namespace aare
} // namespace aare
+8 -5
View File
@@ -171,11 +171,14 @@ Coordinate2D Interpolator::transform_eta_values(const Eta2<T> &eta) const {
if (static_cast<ssize_t>(ix) >= m_etabinsx.size() - 1 ||
static_cast<ssize_t>(iy) >= m_etabinsy.size() - 1 ||
static_cast<ssize_t>(ie) >= m_energy_bins.size() - 1)
throw std::runtime_error(
fmt::format("Eta values out of bounds of eta distribution: eta.x = "
"{:.4f}, eta.y = {:.4f}, energy = {:.4f}",
eta.x, eta.y, eta.sum));
static_cast<ssize_t>(ie) >= m_energy_bins.size() - 1) {
throw std::runtime_error(fmt::format(
"Eta values (eta.x = {:4f}, eta.y = {:4f}, energy = {}) out of "
"bounds of eta distribution with largest values: (eta.x = {:4f}, "
"eta.y = {:4f}, energy = {:4f})",
eta.x, eta.y, eta.sum, *(m_etabinsx.end() - 1),
*(m_etabinsy.end() - 1), *(m_energy_bins.end() - 1)));
}
// TODO: bilinear interpolation only works if all bins have a size > 1 -
// otherwise bilinear interpolation with zero values which skew the
+194 -178
View File
@@ -1,18 +1,40 @@
// SPDX-License-Identifier: MPL-2.0
#pragma once
#include "aare/NDView.hpp"
#include <algorithm>
#include <array>
#include <cmath>
#include <vector>
#include <limits>
#include <algorithm>
#include "aare/NDView.hpp"
#include <vector>
namespace aare::model {
inline constexpr double inv_sqrt2 = 0.70710678118654752440;
inline constexpr double inv_sqrt2 = 0.70710678118654752440;
inline constexpr double inv_sqrt_2pi = 0.39894228040143267794;
inline double fast_erf(double x) {
// AbramowitzStegun Handbook of Mathematical Functions
// erf approximation with max error ~1.5e-7, faster than std::erf.
const double a1 = 0.254829592;
const double a2 = -0.284496736;
const double a3 = 1.421413741;
const double a4 = -1.453152027;
const double a5 = 1.061405429;
const double p = 0.3275911;
const int sign = x < 0 ? -1 : 1;
x = std::abs(x);
// 7.1.26
const double t = 1.0 / (1.0 + p * x);
const double y =
1.0 -
(((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t) * std::exp(-x * x);
return sign * y;
}
/**
* @brief Per-parameter metadata: name and optional default bounds.
@@ -21,11 +43,11 @@ inline constexpr double inv_sqrt_2pi = 0.39894228040143267794;
* Unbounded directions use ±no_bound as sentinels.
*/
struct ParamInfo {
const char* name; // name of parameter
double default_lo; // lower bound value
double default_hi; // upper bound value
const char *name; // name of parameter
double default_lo; // lower bound value
double default_hi; // upper bound value
};
inline constexpr double no_bound = std::numeric_limits<double>::infinity();
/**
@@ -33,18 +55,15 @@ inline constexpr double no_bound = std::numeric_limits<double>::infinity();
*
* Model-independent, called once per pixel.
*/
inline void compute_ranges(NDView<double, 1> x,
NDView<double, 1> y,
double& x_range,
double& y_range,
double& slope_scale)
{
inline void compute_ranges(NDView<double, 1> x, NDView<double, 1> y,
double &x_range, double &y_range,
double &slope_scale) {
const auto [x_min, x_max] = std::minmax_element(x.begin(), x.end());
const auto [y_min, y_max] = std::minmax_element(y.begin(), y.end());
x_range = std::max(*x_max - *x_min, 1e-9);
y_range = std::max(*y_max - *y_min, 1e-9);
slope_scale = std::max(y_range / x_range, 1e-9);
}
@@ -70,50 +89,45 @@ struct Pol1 {
static constexpr std::size_t npar = 2;
static constexpr std::array<ParamInfo, npar> param_info = {{
{"p0", -no_bound, no_bound},
{"p1", -no_bound, no_bound},
{"p0", -no_bound, no_bound},
{"p1", -no_bound, no_bound},
}};
static double eval(double x, const std::vector<double>& par) {
static double eval(double x, const std::vector<double> &par) {
return par[0] + par[1] * x;
}
static void eval_and_grad(double x,
const std::vector<double>& par,
double& f,
std::array<double, npar>& g)
{
static void eval_and_grad(double x, const std::vector<double> &par,
double &f, std::array<double, npar> &g) {
f = par[0] + par[1] * x;
g[0] = 1.0 ; // df/dp0
g[1] = x; // df/dp1
g[0] = 1.0; // df/dp0
g[1] = x; // df/dp1
}
static bool is_valid([[maybe_unused]] const std::vector<double>& par) {
return true; // always valid
static bool is_valid([[maybe_unused]] const std::vector<double> &par) {
return true; // always valid
}
/** @brief Estimate from endpoints: slope = dy/dx, intercept from first point. */
/** @brief Estimate from endpoints: slope = dy/dx, intercept from first
* point. */
static std::array<double, npar> estimate_par(NDView<double, 1> x,
NDView<double, 1> y)
{
const double dx = x[x.size()-1] - x[0];
const double dy = y[y.size()-1] - y[0];
const double slope = (std::abs(dx) > 1e-12) ? dy/dx : 0.0;
NDView<double, 1> y) {
const double dx = x[x.size() - 1] - x[0];
const double dy = y[y.size() - 1] - y[0];
const double slope = (std::abs(dx) > 1e-12) ? dy / dx : 0.0;
const double intercept = y[0] - slope * x[0];
return {intercept, slope};
}
static void compute_steps(const std::array<double, npar>& start,
[[maybe_unused]] double x_range, double y_range, double slope_scale,
std::array<double, npar>& steps)
{
static void compute_steps(const std::array<double, npar> &start,
[[maybe_unused]] double x_range, double y_range,
double slope_scale,
std::array<double, npar> &steps) {
steps[0] = std::max(0.1 * std::abs(start[0]), 0.1 * y_range);
steps[1] = 0.1 * slope_scale;
}
};
// _____________________________________________________________________
@@ -124,7 +138,7 @@ struct Pol1 {
/**
* @brief Polynomial fuction of degree 2
*
* f(x) = p0 + p1 * x + p2 * x * x
* f(x) = p0 + p1 * x + p2 * x * x
*
* Parameters:
* par[0] = p0 (constant term)
@@ -140,29 +154,26 @@ struct Pol2 {
static constexpr std::size_t npar = 3;
static constexpr std::array<ParamInfo, npar> param_info = {{
{"p0", -no_bound, no_bound},
{"p1", -no_bound, no_bound},
{"p2", -no_bound, no_bound},
{"p0", -no_bound, no_bound},
{"p1", -no_bound, no_bound},
{"p2", -no_bound, no_bound},
}};
static double eval(double x, const std::vector<double>& par) {
static double eval(double x, const std::vector<double> &par) {
return par[0] + par[1] * x + par[2] * x * x;
}
static void eval_and_grad(double x,
const std::vector<double>& par,
double& f,
std::array<double, npar>& g)
{
static void eval_and_grad(double x, const std::vector<double> &par,
double &f, std::array<double, npar> &g) {
f = par[0] + par[1] * x + par[2] * x * x;
g[0] = 1.0 ; // df/dp0
g[1] = x; // df/dp1
g[2] = x * x; // df/dp2
g[0] = 1.0; // df/dp0
g[1] = x; // df/dp1
g[2] = x * x; // df/dp2
}
static bool is_valid([[maybe_unused]]const std::vector<double>& par) {
return true; // always valid
static bool is_valid([[maybe_unused]] const std::vector<double> &par) {
return true; // always valid
}
/**
@@ -184,23 +195,22 @@ struct Pol2 {
* when curvature is negligible i.e. slope_e ≈ slope_s -> p2 ≈ 0.
*/
static std::array<double, npar> estimate_par(NDView<double, 1> x,
NDView<double, 1> y)
{
NDView<double, 1> y) {
const ssize_t n = y.size();
const ssize_t tail = std::max<ssize_t>(n / 10, 2);
// start: slope from first 10%
const double x_s = (x[0] + x[tail-1]) * 0.5;
const double slope_s = (y[tail-1] - y[0]) / (x[tail-1] - x[0]);
const double x_s = (x[0] + x[tail - 1]) * 0.5;
const double slope_s = (y[tail - 1] - y[0]) / (x[tail - 1] - x[0]);
// end: slope from last 10%
const double x_e = (x[n-tail] + x[n-1]) * 0.5;
const double slope_e = (y[n-1] - y[n-tail]) / (x[n-1] - x[n-tail]);
const double x_e = (x[n - tail] + x[n - 1]) * 0.5;
const double slope_e =
(y[n - 1] - y[n - tail]) / (x[n - 1] - x[n - tail]);
const double dx = x_e - x_s;
const double p2 = (std::abs(dx) > 1e-12)
? (slope_e - slope_s) / (2.0 * dx)
: 0.0;
const double p2 =
(std::abs(dx) > 1e-12) ? (slope_e - slope_s) / (2.0 * dx) : 0.0;
// p1 from slope_s = p1 + 2*p2*x_s
const double p1 = slope_s - 2.0 * p2 * x_s;
@@ -211,11 +221,10 @@ struct Pol2 {
return {p0, p1, p2};
}
static void compute_steps(const std::array<double, npar>& start,
double x_range, double y_range, double slope_scale,
std::array<double, npar>& steps)
{
static void compute_steps(const std::array<double, npar> &start,
double x_range, double y_range,
double slope_scale,
std::array<double, npar> &steps) {
steps[0] = std::max(0.1 * std::abs(start[0]), 0.1 * y_range);
steps[1] = 0.1 * slope_scale;
steps[2] = 0.1 * slope_scale / std::max(x_range, 1e-12);
@@ -246,43 +255,41 @@ struct Gaussian {
static constexpr std::size_t npar = 3;
static constexpr std::array<ParamInfo, npar> param_info = {{
{"A", -no_bound, no_bound},
{"mu", -no_bound, no_bound},
{"sig", 1e-12, no_bound},
{"A", -no_bound, no_bound},
{"mu", -no_bound, no_bound},
{"sigma", 1e-12, no_bound},
}};
static double eval(double x, const std::vector<double>& par) {
const double A = par[0];
const double mu = par[1];
static double eval(double x, const std::vector<double> &par) {
const double A = par[0];
const double mu = par[1];
const double sig = par[2];
const double dx = x - mu;
const double dx = x - mu;
const double inv_2sig2 = 1.0 / (2.0 * sig * sig);
return A * std::exp(-dx * dx * inv_2sig2);
}
static void eval_and_grad(double x,
const std::vector<double>& par,
double& f,
std::array<double, npar>& g)
{
const double A = par[0];
const double mu = par[1];
static void eval_and_grad(double x, const std::vector<double> &par,
double &f, std::array<double, npar> &g) {
const double A = par[0];
const double mu = par[1];
const double sig = par[2];
const double dx = x - mu;
const double dx = x - mu;
const double inv_2sig2 = 1.0 / (2.0 * sig * sig);
const double e = std::exp(-dx * dx * inv_2sig2);
const double e = std::exp(-dx * dx * inv_2sig2);
f = A * e;
g[0] = e; // df/dA
g[1] = 2.0 * A * e * dx * inv_2sig2; // df/dmu = A*e*(x-mu)/sig^2
g[2] = 2.0 * A * e * dx * dx * inv_2sig2 / sig; // df/dsigma = A*e*(x-mu)^2/sig^3
g[0] = e; // df/dA
g[1] = 2.0 * A * e * dx * inv_2sig2; // df/dmu = A*e*(x-mu)/sig^2
g[2] = 2.0 * A * e * dx * dx * inv_2sig2 /
sig; // df/dsigma = A*e*(x-mu)^2/sig^3
}
/** @brief Reject degenerate sigma (zero width). */
static bool is_valid(const std::vector<double>& par) {
static bool is_valid(const std::vector<double> &par) {
return par[2] != 0.0;
}
@@ -294,41 +301,45 @@ struct Gaussian {
* sigma = FWHM / 2.35
*/
static std::array<double, npar> estimate_par(NDView<double, 1> x,
NDView<double, 1> y)
{
NDView<double, 1> y) {
// find peak
const auto max_it = std::max_element(y.begin(), y.end());
const ssize_t i_max = std::distance(y.begin(), max_it);
const double A = *max_it;
const double A = *max_it;
const double mu = x[i_max];
// FWHM estimate
const double half = A * 0.5;
double x_lo = mu, x_hi = mu;
for (ssize_t i = i_max; i >= 0; --i)
if (y[i] < half) { x_lo = x[i]; break; }
if (y[i] < half) {
x_lo = x[i];
break;
}
for (ssize_t i = i_max; i < y.size(); ++i)
if (y[i] < half) { x_hi = x[i]; break; }
if (y[i] < half) {
x_hi = x[i];
break;
}
const double sig = std::max((x_hi - x_lo) / 2.35, 1e-6);
return {A, mu, sig};
}
/**
* @brief Data-driven Minuit step sizes.
*/
static void compute_steps(const std::array<double, npar>& start,
double x_range, double y_range, double /*slope_scale*/,
std::array<double, npar>& steps)
{
static void compute_steps(const std::array<double, npar> &start,
double x_range, double y_range,
double /*slope_scale*/,
std::array<double, npar> &steps) {
steps[0] = std::max(0.1 * std::abs(start[0]), 0.1 * y_range);
steps[1] = 0.05 * x_range;
steps[2] = 0.05 * x_range;
}
};
// _____________________________________________________________________
//
// RisingScurve
@@ -338,15 +349,16 @@ struct Gaussian {
* @brief Rising S-curve (error-function step) with linear baseline and
* post-step slope.
*
* f(x) = (p0 + p1*x) + 0.5*(1 + erf((x - p2) / (sqrt(2)*p3))) * (p4 + p5*(x - p2))
* f(x) = (p0 + p1*x) + 0.5*(1 + erf((x - mu) / (sqrt(2)*sigma))) * (A + C*(x -
* mu)))
*
* Parameters:
* par[0] = p0 (baseline offset)
* par[1] = p1 (baseline slope)
* par[2] = p2 (threshold / inflection point)
* par[3] = p3 (transition width, must be > 0)
* par[4] = p4 (step amplitude)
* par[5] = p5 (post-step slope)
* par[2] = mu (inflection point)
* par[3] = sigma (transition width, must be > 0)
* par[4] = A (step amplitude)
* par[5] = C (post-step slope)
*/
struct RisingScurve {
static constexpr std::size_t npar = 6;
@@ -354,13 +366,13 @@ struct RisingScurve {
static constexpr std::array<ParamInfo, npar> param_info = {{
{"p0", -no_bound, no_bound},
{"p1", -no_bound, no_bound},
{"p2", -no_bound, no_bound},
{"p3", 1e-12, no_bound},
{"p4", -no_bound, no_bound},
{"p5", -no_bound, no_bound},
{"mu", -no_bound, no_bound},
{"sigma", 1e-12, no_bound},
{"A", -no_bound, no_bound},
{"C", -no_bound, no_bound},
}};
static double eval(double x, const std::vector<double>& par) {
static double eval(double x, const std::vector<double> &par) {
const double p0 = par[0];
const double p1 = par[1];
const double p2 = par[2];
@@ -368,8 +380,8 @@ struct RisingScurve {
const double p4 = par[4];
const double p5 = par[5];
const double dx = x - p2;
const double step = 0.5 * (1.0 + std::erf(dx * inv_sqrt2 / p3));
const double dx = x - p2;
const double step = 0.5 * (1.0 + fast_erf(dx * inv_sqrt2 / p3));
return (p0 + p1 * x) + step * (p4 + p5 * dx);
}
@@ -381,11 +393,8 @@ struct RisingScurve {
* dS/dp2 = -(1/sqrt(2*pi)) * exp(-z^2) / p3
* dS/dp3 = -(1/sqrt(2*pi)) * exp(-z^2) * (x-p2) / p3^2
*/
static void eval_and_grad(double x,
const std::vector<double>& par,
double& f,
std::array<double, npar>& g)
{
static void eval_and_grad(double x, const std::vector<double> &par,
double &f, std::array<double, npar> &g) {
const double p0 = par[0];
const double p1 = par[1];
const double p2 = par[2];
@@ -393,50 +402,48 @@ struct RisingScurve {
const double p4 = par[4];
const double p5 = par[5];
const double dx = x - p2;
const double z = dx * inv_sqrt2 / p3;
const double step = 0.5 * (1.0 + std::erf(z));
const double amp = p4 + p5 * dx;
const double dx = x - p2;
const double z = dx * inv_sqrt2 / p3;
const double step = 0.5 * (1.0 + fast_erf(z));
const double amp = p4 + p5 * dx;
f = (p0 + p1 * x) + step * amp;
const double e = std::exp(-z * z);
const double e = std::exp(-z * z);
const double dSdp2 = -inv_sqrt_2pi * e / p3;
const double dSdp3 = -inv_sqrt_2pi * e * dx / (p3 * p3);
g[0] = 1.0; // df/dp0
g[1] = x; // df/dp1
g[2] = dSdp2 * amp - step * p5; // df/dp2
g[3] = dSdp3 * amp; // df/dp3
g[4] = step; // df/dp4
g[5] = step * dx; // df/dp5
g[0] = 1.0; // df/dp0
g[1] = x; // df/dp1
g[2] = dSdp2 * amp - step * p5; // df/dp2
g[3] = dSdp3 * amp; // df/dp3
g[4] = step; // df/dp4
g[5] = step * dx; // df/dp5
}
/** @brief Reject degenerate width (zero transition width). */
static bool is_valid(const std::vector<double>& par) {
static bool is_valid(const std::vector<double> &par) {
return par[3] != 0.0;
}
/** @brief Data-driven initial parameter estimates for a rising S-curve. */
static std::array<double, npar> estimate_par(NDView<double, 1> x,
NDView<double, 1> y)
{
NDView<double, 1> y) {
const ssize_t n = y.size();
// baseline: average of first ~10% of points (before turn-on)
ssize_t n_base = std::max<ssize_t>(n / 10, 2);
double sum_y = 0, sum_xy = 0, sum_x = 0, sum_x2 = 0;
for (ssize_t i = 0; i < n_base; ++i) {
sum_y += y[i];
sum_x += x[i];
sum_y += y[i];
sum_x += x[i];
sum_xy += x[i] * y[i];
sum_x2 += x[i] * x[i];
}
double denom = n_base * sum_x2 - sum_x * sum_x;
double p1 = (std::abs(denom) > 1e-30)
? (n_base * sum_xy - sum_x * sum_y) / denom
: 0.0;
? (n_base * sum_xy - sum_x * sum_y) / denom
: 0.0;
double p0 = (sum_y - p1 * sum_x) / n_base;
// plateau: average of last ~10%
@@ -466,10 +473,16 @@ struct RisingScurve {
double y_90 = baseline_at_mid + 0.9 * p4;
double x_10 = x[0], x_90 = x[n - 1];
for (ssize_t i = 0; i < n; ++i) {
if (y[i] >= y_10) { x_10 = x[i]; break; }
if (y[i] >= y_10) {
x_10 = x[i];
break;
}
}
for (ssize_t i = 0; i < n; ++i) {
if (y[i] >= y_90) { x_90 = x[i]; break; }
if (y[i] >= y_90) {
x_90 = x[i];
break;
}
}
// for a Gaussian CDF: 10%-90% width = 2 * 1.2816 * sigma
double p3 = std::max((x_90 - x_10) / 2.5631, 1.0);
@@ -478,11 +491,11 @@ struct RisingScurve {
return {p0, p1, p2, p3, p4, p5};
}
static void compute_steps(const std::array<double, npar>& start,
double x_range, double y_range, double slope_scale,
std::array<double, npar>& steps)
{
static void compute_steps(const std::array<double, npar> &start,
double x_range, double y_range,
double slope_scale,
std::array<double, npar> &steps) {
steps[0] = std::max(0.1 * std::abs(start[0]), 0.1 * y_range);
steps[1] = 0.1 * slope_scale;
steps[2] = 0.05 * x_range;
@@ -492,7 +505,6 @@ struct RisingScurve {
}
};
// _____________________________________________________________________
//
// FallingScurve
@@ -502,7 +514,8 @@ struct RisingScurve {
* @brief Falling S-curve (complementary error-function step) with linear
* baseline and post-step slope.
*
* f(x) = (p0 + p1*x) + 0.5*(1 - erf((x - p2) / (sqrt(2)*p3))) * (p4 + p5*(x - p2))
* f(x) = (p0 + p1*x) + 0.5*(1 - erf((x - mu) / (sqrt(2)*sigma))) * (A + C*(x -
* mu))
*
* Parameters are identical to RisingScurve. The only difference is the
* sign of the erf term, which flips the step direction (and the signs of
@@ -514,13 +527,13 @@ struct FallingScurve {
static constexpr std::array<ParamInfo, npar> param_info = {{
{"p0", -no_bound, no_bound},
{"p1", -no_bound, no_bound},
{"p2", -no_bound, no_bound},
{"p3", 1e-12, no_bound},
{"p4", -no_bound, no_bound},
{"p5", -no_bound, no_bound},
{"mu", -no_bound, no_bound},
{"sigma", 1e-12, no_bound},
{"A", -no_bound, no_bound},
{"C", -no_bound, no_bound},
}};
static double eval(double x, const std::vector<double>& par) {
static double eval(double x, const std::vector<double> &par) {
const double p0 = par[0];
const double p1 = par[1];
const double p2 = par[2];
@@ -528,16 +541,13 @@ struct FallingScurve {
const double p4 = par[4];
const double p5 = par[5];
const double dx = x - p2;
const double step = 0.5 * (1.0 - std::erf(dx * inv_sqrt2 / p3));
const double dx = x - p2;
const double step = 0.5 * (1.0 - fast_erf(dx * inv_sqrt2 / p3));
return (p0 + p1 * x) + step * (p4 + p5 * dx);
}
static void eval_and_grad(double x,
const std::vector<double>& par,
double& f,
std::array<double, npar>& g)
{
static void eval_and_grad(double x, const std::vector<double> &par,
double &f, std::array<double, npar> &g) {
const double p0 = par[0];
const double p1 = par[1];
const double p2 = par[2];
@@ -545,15 +555,15 @@ struct FallingScurve {
const double p4 = par[4];
const double p5 = par[5];
const double dx = x - p2;
const double z = dx * inv_sqrt2 / p3;
const double step = 0.5 * (1.0 - std::erf(z));
const double amp = p4 + p5 * dx;
const double dx = x - p2;
const double z = dx * inv_sqrt2 / p3;
const double step = 0.5 * (1.0 - fast_erf(z));
const double amp = p4 + p5 * dx;
f = (p0 + p1 * x) + step * amp;
const double e = std::exp(-z * z);
const double dSdp2 = +inv_sqrt_2pi * e / p3; // sign flipped vs rising
const double e = std::exp(-z * z);
const double dSdp2 = +inv_sqrt_2pi * e / p3; // sign flipped vs rising
const double dSdp3 = +inv_sqrt_2pi * e * dx / (p3 * p3);
g[0] = 1.0;
@@ -565,29 +575,28 @@ struct FallingScurve {
}
/** @brief Reject degenerate width (zero transition width). */
static bool is_valid(const std::vector<double>& par) {
static bool is_valid(const std::vector<double> &par) {
return par[3] != 0.0;
}
/** @brief Data-driven initial parameter estimates for a falling S-curve. */
static std::array<double, npar> estimate_par(NDView<double, 1> x,
NDView<double, 1> y)
{
NDView<double, 1> y) {
const ssize_t n = y.size();
// baseline: last ~10% of points (after turn-off)
ssize_t n_base = std::max<ssize_t>(n / 10, 2);
double sum_y = 0, sum_xy = 0, sum_x = 0, sum_x2 = 0;
for (ssize_t i = n - n_base; i < n; ++i) {
sum_y += y[i];
sum_x += x[i];
sum_y += y[i];
sum_x += x[i];
sum_xy += x[i] * y[i];
sum_x2 += x[i] * x[i];
}
double denom = n_base * sum_x2 - sum_x * sum_x;
double p1 = (std::abs(denom) > 1e-30)
? (n_base * sum_xy - sum_x * sum_y) / denom
: 0.0;
? (n_base * sum_xy - sum_x * sum_y) / denom
: 0.0;
double p0 = (sum_y - p1 * sum_x) / n_base;
// plateau: average of first ~10%
@@ -617,10 +626,16 @@ struct FallingScurve {
double y_10 = baseline_at_mid + 0.1 * p4;
double x_90 = x[0], x_10 = x[n - 1];
for (ssize_t i = 0; i < n; ++i) {
if (y[i] <= y_90) { x_90 = x[i]; break; }
if (y[i] <= y_90) {
x_90 = x[i];
break;
}
}
for (ssize_t i = 0; i < n; ++i) {
if (y[i] <= y_10) { x_10 = x[i]; break; }
if (y[i] <= y_10) {
x_10 = x[i];
break;
}
}
// same CDF relationship: 10%-90% width = 2 * 1.2816 * sigma
double p3 = std::max((x_10 - x_90) / 2.5631, 1.0);
@@ -629,12 +644,13 @@ struct FallingScurve {
return {p0, p1, p2, p3, p4, p5};
}
static void compute_steps(const std::array<double, npar>& start,
double x_range, double y_range, double slope_scale,
std::array<double, npar>& steps)
{
RisingScurve::compute_steps(start, x_range, y_range, slope_scale, steps);
static void compute_steps(const std::array<double, npar> &start,
double x_range, double y_range,
double slope_scale,
std::array<double, npar> &steps) {
RisingScurve::compute_steps(start, x_range, y_range, slope_scale,
steps);
}
};
+1 -1
View File
@@ -36,7 +36,7 @@ class NDArray : public ArrayExpr<NDArray<T, Ndim>, Ndim> {
* @brief Default constructor. Constructs an empty NDArray.
*
*/
NDArray() : shape_(), strides_(c_strides<Ndim>(shape_)), data_(nullptr){};
NDArray() : shape_(), strides_(c_strides<Ndim>(shape_)), data_(nullptr) {};
/**
* @brief Construct a new NDArray object with a given shape.
+1 -1
View File
@@ -63,7 +63,7 @@ template <class T> struct ProducerConsumerQueue {
return *this;
}
ProducerConsumerQueue() : ProducerConsumerQueue(2){};
ProducerConsumerQueue() : ProducerConsumerQueue(2) {};
// size must be >= 2.
//
// Also, note that the number of usable slots in the queue at any
+1 -1
View File
@@ -67,7 +67,7 @@ class Logger {
public:
Logger() = default;
explicit Logger(TLogLevel level) : m_level(level){};
explicit Logger(TLogLevel level) : m_level(level) {};
~Logger() {
// output in the destructor to allow for << syntax
os << RESET << '\n';
+2 -2
View File
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: MPL-2.0
#pragma once
#include "aare/NDView.hpp"
#include "aare/utils/task.hpp"
#include <thread>
#include <utility>
#include <vector>
#include "aare/NDView.hpp"
#include "aare/utils/task.hpp"
namespace aare {