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
+20 -26
View File
@@ -1,18 +1,20 @@
# SPDX-License-Identifier: MPL-2.0
find_package (Python 3.10 COMPONENTS Interpreter Development.Module REQUIRED)
find_package(
Python 3.10
COMPONENTS Interpreter Development.Module
REQUIRED)
set(PYBIND11_FINDPYTHON ON) # Needed for RH8
# Download or find pybind11 depending on configuration
if(AARE_FETCH_PYBIND11)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11
GIT_TAG v2.13.6
)
FetchContent_MakeAvailable(pybind11)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11
GIT_TAG v3.0.3)
FetchContent_MakeAvailable(pybind11)
else()
find_package(pybind11 2.13 REQUIRED)
find_package(pybind11 3.0.3 REQUIRED)
endif()
# ---- Main CPU module --------------------------------------------------------
@@ -25,10 +27,9 @@ endif()
pybind11_add_module(_aare NO_EXTRAS src/module.cpp)
target_link_libraries(_aare PRIVATE aare_core aare_compiler_flags)
target_include_directories(_aare SYSTEM PRIVATE
$<TARGET_PROPERTY:Minuit2::Minuit2,INTERFACE_INCLUDE_DIRECTORIES>
)
target_include_directories(
_aare SYSTEM
PRIVATE $<TARGET_PROPERTY:Minuit2::Minuit2,INTERFACE_INCLUDE_DIRECTORIES>)
set_target_properties(_aare PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/aare
@@ -61,7 +62,7 @@ if(AARE_CUDA)
endif()
# List of python files to be copied to the build directory
set( PYTHON_FILES
set(PYTHON_FILES
aare/__init__.py
aare/CtbRawFile.py
aare/ClusterFinder.py
@@ -72,32 +73,25 @@ set( PYTHON_FILES
aare/RawFile.py
aare/transform.py
aare/ScanParameters.py
aare/utils.py
)
aare/utils.py)
# Copy the python files to the build directory
foreach(FILE ${PYTHON_FILES})
configure_file(${FILE} ${CMAKE_BINARY_DIR}/${FILE} )
configure_file(${FILE} ${CMAKE_BINARY_DIR}/${FILE})
endforeach(FILE ${PYTHON_FILES})
# set_target_properties(_aare PROPERTIES
# LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/aare
# )
set(PYTHON_EXAMPLES
examples/play.py
examples/fits.py
)
set(PYTHON_EXAMPLES examples/play.py examples/fits.py)
# Copy the python examples to the build directory
foreach(FILE ${PYTHON_EXAMPLES})
configure_file(${FILE} ${CMAKE_BINARY_DIR}/${FILE} )
message(STATUS "Copying ${FILE} to ${CMAKE_BINARY_DIR}/${FILE}")
configure_file(${FILE} ${CMAKE_BINARY_DIR}/${FILE})
message(STATUS "Copying ${FILE} to ${CMAKE_BINARY_DIR}/${FILE}")
endforeach(FILE ${PYTHON_EXAMPLES})
if(AARE_INSTALL_PYTHONEXT)
set(AARE_PY_INSTALL_TARGETS _aare)
if(AARE_CUDA)
@@ -116,4 +110,4 @@ if(AARE_INSTALL_PYTHONEXT)
DESTINATION aare
COMPONENT python
)
endif()
endif()
+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);
}
File diff suppressed because one or more lines are too long
+19 -1
View File
@@ -109,4 +109,22 @@ def test_3x3_reduction():
assert reduced_cv.size == 2
assert reduced_cv[0]["x"] == 5
assert reduced_cv[0]["y"] == 5
assert (reduced_cv[0]["data"] == np.array([[2.0, 1.0, 1.0], [2.0, 3.0, 1.0], [2.0, 1.0, 1.0]], dtype=np.double)).all()
assert (reduced_cv[0]["data"] == np.array([[2.0, 1.0, 1.0], [2.0, 3.0, 1.0], [2.0, 1.0, 1.0]], dtype=np.double)).all()
def test_masking():
cv = _aare.ClusterVector_Cluster3x3i()
cv.push_back(_aare.Cluster3x3i(19, 22, np.array([0,1,0,2,3,0,2,1,0], dtype=np.int32)))
cv.push_back(_aare.Cluster3x3i(1, 2, np.ones(9, dtype=np.int32)))
assert cv.size == 2
mask = np.array([False, True], dtype=bool)
cv_masked = cv(mask)
assert cv_masked.size == 1
cv_masked_array = np.array(cv_masked, copy=False)
assert cv_masked_array[0]["x"] == 1
assert cv_masked_array[0]["y"] == 2
assert (cv_masked_array[0]["data"] == np.ones((3,3),dtype=np.int32)).all()
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long