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
+16
View File
@@ -35,6 +35,22 @@ void define_ClusterVector(py::module &m, const std::string &typestr) {
.def(py::init()) // TODO change!!!
.def(
"__call__",
[](ClusterVector<ClusterType> &self, py::array_t<bool> mask) {
return self(make_view_1d(mask));
},
py::arg("mask"), R"(
Create a copy of the clustervector and apply a boolean mask to the ClusterVector.
Parameters
----------
mask : 1d boolean numpy array
Mask to apply to the ClusterVector. Must be the same length as the number of clusters in the ClusterVector.
)")
.def("push_back",
[](ClusterVector<ClusterType> &self, const ClusterType &cluster) {
self.push_back(cluster);
+135 -107
View File
@@ -5,64 +5,88 @@
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include "aare/Chi2.hpp"
#include "aare/Fit.hpp"
#include "aare/FitModel.hpp"
#include "aare/Models.hpp"
#include "aare/Chi2.hpp"
namespace py = pybind11;
using namespace pybind11::literals;
template <typename Model, typename FCN>
py::object fit_dispatch(
const aare::FitModel<Model>& model,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast> y,
py::object y_err_obj,
int n_threads);
py::object
fit_dispatch(const aare::FitModel<Model> &model,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast> y,
py::object y_err_obj, int n_threads);
template <typename Model>
void bind_fit_model(py::module& m, const char* name) {
template <typename Model> void bind_fit_model(py::module &m, const char *name) {
using FM = aare::FitModel<Model>;
using FCN = aare::func::Chi2Model1DGrad<Model>;
py::class_<FM>(m, name)
.def(py::init<unsigned int, unsigned int, double, bool>(),
py::arg("strategy") = 0,
py::arg("max_calls") = 100,
py::arg("tolerance") = 0.5,
py::arg("compute_errors") = false)
.def("SetParLimits", &FM::SetParLimits, py::arg("idx"), py::arg("lo"), py::arg("hi"))
.def("FixParameter", &FM::FixParameter, py::arg("idx"), py::arg("val"))
.def("ReleaseParameter", &FM::ReleaseParameter, py::arg("idx"))
.def("SetParameter", &FM::SetParameter, py::arg("idx"), py::arg("val"))
py::arg("strategy") = 0, py::arg("max_calls") = 100,
py::arg("tolerance") = 0.5, py::arg("compute_errors") = false)
.def("SetParLimits",
py::overload_cast<unsigned int, double, double>(&FM::SetParLimits),
py::arg("idx"), py::arg("lo"), py::arg("hi"))
.def("SetParLimits",
py::overload_cast<const std::string &, double, double>(
&FM::SetParLimits),
py::arg("idx"), py::arg("lo"), py::arg("hi"))
.def("FixParameter",
py::overload_cast<unsigned int, double>(&FM::FixParameter),
py::arg("idx"), py::arg("val"))
.def("FixParameter",
py::overload_cast<const std::string &, double>(&FM::FixParameter),
py::arg("idx"), py::arg("val"))
.def("ReleaseParameter",
py::overload_cast<unsigned int>(&FM::ReleaseParameter),
py::arg("idx"))
.def("ReleaseParameter",
py::overload_cast<const std::string &>(&FM::ReleaseParameter),
py::arg("idx"))
.def("SetParameter",
py::overload_cast<unsigned int, double>(&FM::SetParameter),
py::arg("idx"), py::arg("val"))
.def("SetParameter",
py::overload_cast<const std::string &, double>(&FM::SetParameter),
py::arg("idx"), py::arg("val"))
.def("GetParName", &FM::GetParName, py::arg("idx"))
.def("GetParNames", &FM::GetParNames)
.def_property_readonly("par_names", &FM::GetParNames)
.def_property_readonly("n_par",
[](py::object /*cls*/) { return Model::npar; })
.def_property("max_calls", &FM::max_calls, &FM::SetMaxCalls)
.def_property("tolerance", &FM::tolerance, &FM::SetTolerance)
.def_property("compute_errors", &FM::compute_errors, &FM::SetComputeErrors)
.def("__call__",
[](const FM& /*self*/,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast> par)
{
.def_property("compute_errors", &FM::compute_errors,
&FM::SetComputeErrors)
.def(
"__call__",
[](const FM & /*self*/,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast>
par) {
auto x_view = make_view_1d(x);
auto p_view = make_view_1d(par);
std::vector<double> pvec(p_view.begin(), p_view.end());
auto* result = new aare::NDArray<double, 1>({x_view.size()});
auto *result = new aare::NDArray<double, 1>({x_view.size()});
for (ssize_t i = 0; i < x_view.size(); ++i)
(*result)(i) = Model::eval(x_view[i], pvec);
return return_image_data(result);
},
py::arg("x"), py::arg("par"))
.def("fit",
[](const FM& self,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast> y,
py::object y_err_obj,
int n_threads) -> py::object
{
return fit_dispatch<Model, FCN>(self, x, y, y_err_obj, n_threads);
.def(
"fit",
[](const FM &self,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast> y,
py::object y_err_obj, int n_threads) -> py::object {
return fit_dispatch<Model, FCN>(self, x, y, y_err_obj,
n_threads);
},
R"doc(
Fit this model to 1D or 3D data using Minuit2.
@@ -78,24 +102,21 @@ void bind_fit_model(py::module& m, const char* name) {
n_threads : int
Number of threads for 3D parallel loop.
)doc",
py::arg("x"),
py::arg("y"),
py::arg("y_err") = py::none(),
py::arg("x"), py::arg("y"), py::arg("y_err") = py::none(),
py::arg("n_threads") = 4);
}
template <typename Model>
py::dict pack_1d_result_dict(const aare::NDArray<double, 1>& result,
bool compute_errors)
{
py::dict pack_1d_result_dict(const aare::NDArray<double, 1> &result,
bool compute_errors) {
constexpr std::size_t npar = Model::npar;
auto res = result.view();
auto par_out = new NDArray<double, 1>({npar}, 0.0);
auto chi2_out = new NDArray<double, 1>({1}, 0.0);
auto par_out = new NDArray<double, 1>({npar}, 0.0);
auto chi2_out = new NDArray<double, 1>({1}, 0.0);
auto par_view = par_out->view();
auto par_view = par_out->view();
auto chi2_view = chi2_out->view();
for (std::size_t i = 0; i < npar; ++i) {
@@ -103,7 +124,7 @@ py::dict pack_1d_result_dict(const aare::NDArray<double, 1>& result,
}
if (compute_errors) {
auto err_out = new NDArray<double, 1>({npar}, 0.0);
auto err_out = new NDArray<double, 1>({npar}, 0.0);
auto err_view = err_out->view();
for (std::size_t i = 0; i < npar; ++i) {
@@ -112,58 +133,59 @@ py::dict pack_1d_result_dict(const aare::NDArray<double, 1>& result,
chi2_view(0) = res(2 * npar);
return py::dict(
"par"_a = return_image_data(par_out),
"par_err"_a = return_image_data(err_out),
"chi2"_a = return_image_data(chi2_out));
return py::dict("par"_a = return_image_data(par_out),
"par_err"_a = return_image_data(err_out),
"chi2"_a = return_image_data(chi2_out));
} else {
chi2_view(0) = res(npar);
return py::dict(
"par"_a = return_image_data(par_out),
"chi2"_a = return_image_data(chi2_out));
return py::dict("par"_a = return_image_data(par_out),
"chi2"_a = return_image_data(chi2_out));
}
}
// Helper: typed dispatch for one Model, handles 1D/3D + y_err logic
template <typename Model, typename FCN>
py::object fit_dispatch(
const aare::FitModel<Model>& model,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast> y,
py::object y_err_obj,
int n_threads)
{
py::object
fit_dispatch(const aare::FitModel<Model> &model,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast> y,
py::object y_err_obj, int n_threads) {
constexpr std::size_t npar = Model::npar;
if (y.ndim() == 3) {
auto par_out = new NDArray<double, 3>({y.shape(0), y.shape(1), npar}, 0.0);
auto chi2_out= new NDArray<double, 2>({y.shape(0), y.shape(1)}, 0.0);
auto par_out =
new NDArray<double, 3>({y.shape(0), y.shape(1), npar}, 0.0);
auto chi2_out = new NDArray<double, 2>({y.shape(0), y.shape(1)}, 0.0);
auto x_view = make_view_1d(x);
auto y_view = make_view_3d(y);
if (!y_err_obj.is_none()) {
auto y_err = py::cast<py::array_t<double,
py::array::c_style | py::array::forcecast>>(y_err_obj);
auto y_err = py::cast<
py::array_t<double, py::array::c_style | py::array::forcecast>>(
y_err_obj);
if (y_err.ndim() != 3) {
throw std::runtime_error("For 3D input y, y_err must also be 3D.");
throw std::runtime_error(
"For 3D input y, y_err must also be 3D.");
}
auto err_out = new NDArray<double, 3>({y.shape(0), y.shape(1), npar}, 0.0);
auto err_out =
new NDArray<double, 3>({y.shape(0), y.shape(1), npar}, 0.0);
auto y_view_err = make_view_3d(y_err);
aare::fit_3d<Model, FCN>(model, x_view, y_view, y_view_err,
par_out->view(), err_out->view(), chi2_out->view(), n_threads);
aare::fit_3d<Model, FCN>(model, x_view, y_view, y_view_err,
par_out->view(), err_out->view(),
chi2_out->view(), n_threads);
if (model.compute_errors()) {
return py::dict("par"_a = return_image_data(par_out),
return py::dict("par"_a = return_image_data(par_out),
"par_err"_a = return_image_data(err_out),
"chi2"_a = return_image_data(chi2_out));
"chi2"_a = return_image_data(chi2_out));
} else {
delete err_out;
return py::dict("par"_a = return_image_data(par_out),
return py::dict("par"_a = return_image_data(par_out),
"chi2"_a = return_image_data(chi2_out));
}
} else {
@@ -171,9 +193,10 @@ py::object fit_dispatch(
NDView<double, 3> dummy_err{};
NDView<double, 3> dummy_err_out{};
aare::fit_3d<Model, FCN>(model, x_view, y_view, dummy_err,
par_out->view(), dummy_err_out, chi2_out->view(), n_threads);
aare::fit_3d<Model, FCN>(model, x_view, y_view, dummy_err,
par_out->view(), dummy_err_out,
chi2_out->view(), n_threads);
return py::dict("par"_a = return_image_data(par_out),
"chi2"_a = return_image_data(chi2_out));
}
@@ -184,15 +207,18 @@ py::object fit_dispatch(
auto y_view = make_view_1d(y);
if (!y_err_obj.is_none()) {
auto y_err = py::cast<py::array_t<double,
py::array::c_style | py::array::forcecast>>(y_err_obj);
auto y_err = py::cast<
py::array_t<double, py::array::c_style | py::array::forcecast>>(
y_err_obj);
if (y_err.ndim() != 1) {
throw std::runtime_error("For 1D input y, y_err must also be 1D.");
throw std::runtime_error(
"For 1D input y, y_err must also be 1D.");
}
auto y_view_err = make_view_1d(y_err);
result = aare::fit_pixel<Model, FCN>(model, x_view, y_view, y_view_err);
result =
aare::fit_pixel<Model, FCN>(model, x_view, y_view, y_view_err);
} else {
result = aare::fit_pixel<Model, FCN>(model, x_view, y_view);
}
@@ -395,7 +421,6 @@ void define_fit_bindings(py::module &m) {
)",
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
m.def(
"fit_pol1",
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
@@ -567,7 +592,6 @@ void define_fit_bindings(py::module &m) {
)",
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
m.def(
"fit_scurve2",
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
@@ -653,7 +677,6 @@ void define_fit_bindings(py::module &m) {
)",
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
// ── Bind model classes ──────────────────────────────────────────
bind_fit_model<aare::model::Gaussian>(m, "Gaussian");
bind_fit_model<aare::model::RisingScurve>(m, "RisingScurve");
@@ -661,48 +684,57 @@ void define_fit_bindings(py::module &m) {
bind_fit_model<aare::model::Pol1>(m, "Pol1");
bind_fit_model<aare::model::Pol2>(m, "Pol2");
m.def("fit",
m.def(
"fit",
[](py::object model_obj,
py::array_t<double, py::array::c_style | py::array::forcecast> x,
py::array_t<double, py::array::c_style | py::array::forcecast> y,
py::object y_err_obj,
int n_threads) -> py::object
{
py::object y_err_obj, int n_threads) -> py::object {
using namespace aare::model;
using namespace aare::func;
// ── Polynomial of degree 1 ───────
if(py::isinstance< aare::FitModel<Pol1> >(model_obj)) {
const auto& mdl = model_obj.cast< const aare::FitModel<Pol1>& >();
return fit_dispatch<Pol1, Chi2Pol1>(mdl, x, y, y_err_obj, n_threads);
if (py::isinstance<aare::FitModel<Pol1>>(model_obj)) {
const auto &mdl =
model_obj.cast<const aare::FitModel<Pol1> &>();
return fit_dispatch<Pol1, Chi2Pol1>(mdl, x, y, y_err_obj,
n_threads);
}
// ── Polynomial of degree 2 ───────
if(py::isinstance< aare::FitModel<Pol2> >(model_obj)) {
const auto& mdl = model_obj.cast< const aare::FitModel<Pol2>& >();
return fit_dispatch<Pol2, Chi2Pol2>(mdl, x, y, y_err_obj, n_threads);
if (py::isinstance<aare::FitModel<Pol2>>(model_obj)) {
const auto &mdl =
model_obj.cast<const aare::FitModel<Pol2> &>();
return fit_dispatch<Pol2, Chi2Pol2>(mdl, x, y, y_err_obj,
n_threads);
}
// ── Gaussian ───────
if(py::isinstance< aare::FitModel<Gaussian> >(model_obj)) {
const auto& mdl = model_obj.cast< const aare::FitModel<Gaussian>& >();
return fit_dispatch<Gaussian, Chi2Gaussian>(mdl, x, y, y_err_obj, n_threads);
if (py::isinstance<aare::FitModel<Gaussian>>(model_obj)) {
const auto &mdl =
model_obj.cast<const aare::FitModel<Gaussian> &>();
return fit_dispatch<Gaussian, Chi2Gaussian>(
mdl, x, y, y_err_obj, n_threads);
}
// ── Rising Scurve ───────
if(py::isinstance< aare::FitModel<RisingScurve> >(model_obj)) {
const auto& mdl = model_obj.cast< const aare::FitModel<RisingScurve>& >();
return fit_dispatch<RisingScurve, Chi2RisingScurve>(mdl, x, y, y_err_obj, n_threads);
if (py::isinstance<aare::FitModel<RisingScurve>>(model_obj)) {
const auto &mdl =
model_obj.cast<const aare::FitModel<RisingScurve> &>();
return fit_dispatch<RisingScurve, Chi2RisingScurve>(
mdl, x, y, y_err_obj, n_threads);
}
// ── Falling Scurve ───────
if(py::isinstance< aare::FitModel<FallingScurve> >(model_obj)) {
const auto& mdl = model_obj.cast< const aare::FitModel<FallingScurve>& >();
return fit_dispatch<FallingScurve, Chi2FallingScurve>(mdl, x, y, y_err_obj, n_threads);
if (py::isinstance<aare::FitModel<FallingScurve>>(model_obj)) {
const auto &mdl =
model_obj.cast<const aare::FitModel<FallingScurve> &>();
return fit_dispatch<FallingScurve, Chi2FallingScurve>(
mdl, x, y, y_err_obj, n_threads);
}
throw std::runtime_error(
"Unknown model type. Expected Pol1, Pol2, Gaussian, RisingScurve or FallingScurve."
);
"Unknown model type. Expected Pol1, Pol2, Gaussian, "
"RisingScurve or FallingScurve.");
},
R"(
Fit a model to 1D or 3D data using Minuit2.
@@ -733,10 +765,6 @@ void define_fit_bindings(py::module &m) {
"par_err" : (rows, cols, npar) parameter errors (if compute_errors).
"chi2" : (rows, cols) chi-squared per pixel.
)",
py::arg("model"),
py::arg("x"),
py::arg("y"),
py::arg("y_err") = py::none(),
py::arg("n_threads") = 4
);
py::arg("model"), py::arg("x"), py::arg("y"),
py::arg("y_err") = py::none(), py::arg("n_threads") = 4);
}