mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-04-22 05:34:35 +02:00
CI/CD: Integrate pre-commit hooks and GitHub Actions workflow (#303)
To improve codebase quality and reduce human error, this PR introduces the pre-commit framework. This ensures that all code adheres to project standards before it is even committed, maintaining a consistent style and catching common mistakes early. Key Changes: - Code Formatting: Automated C++ formatting using clang-format (based on the project's .clang-format file). - Syntax Validation: Basic checks for file integrity and syntax. - Spell Check: Automated scanning for typos in source code and comments. - CMake Formatting: Standardization of CMakeLists.txt and .cmake configuration files. - GitHub Workflow: Added a CI action that validates every Pull Request against the pre-commit configuration to ensure compliance. The configuration includes a [ci] block to handle automated fixes within the PR. Currently, this is disabled. If we want the CI to automatically commit formatting fixes back to the PR branch, this can be toggled to true in .pre-commit-config.yaml. ```yaml ci: autofix_commit_msg: [pre-commit] auto fixes from pre-commit hooks autofix_prs: false autoupdate_schedule: monthly ``` The last large commit with the fit functions, for example, was not formatted according to the clang-format rules. This PR would allow to avoid similar mistakes in the future. Python fomat with `ruff` for tests and sanitiser for `.ipynb` notebooks can be added as well.
This commit is contained in:
+132
-112
@@ -5,72 +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", 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"))
|
||||
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_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.
|
||||
@@ -86,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) {
|
||||
@@ -111,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) {
|
||||
@@ -120,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 {
|
||||
@@ -179,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));
|
||||
}
|
||||
@@ -192,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);
|
||||
}
|
||||
@@ -403,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,
|
||||
@@ -575,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,
|
||||
@@ -661,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");
|
||||
@@ -669,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.
|
||||
@@ -741,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);
|
||||
}
|
||||
Reference in New Issue
Block a user