add py module

This commit is contained in:
2026-05-19 14:17:14 +02:00
parent 16334e4834
commit 8b62881ae8
10 changed files with 114295 additions and 29 deletions
+200
View File
@@ -0,0 +1,200 @@
#include <nanobind/nanobind.h>
#include <nanobind/ndarray.h>
#include <nanobind/stl/string.h>
#include <nanobind/eigen/dense.h>
#include <autoopt/btls.hpp>
#include <autoopt/ellipse.hpp>
#include <autoopt/interface.hpp>
#include <autoopt/optimization_problem.hpp>
#include <autoopt/util.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
namespace nb = nanobind;
struct input_data {
size_t n_points;
double* data;
input_data(size_t n) : n_points(n) {
if (n_points == 0) {
data = nullptr;
} else {
data = new double[n_points * 2];
}
}
input_data(const input_data&) = delete;
input_data& operator=(const input_data&) = delete;
input_data(input_data&& other) noexcept
: n_points(other.n_points), data(other.data) {
other.data = nullptr;
other.n_points = 0;
}
input_data& operator=(input_data&& other) noexcept {
if (this != &other) {
delete[] data;
n_points = other.n_points;
data = other.data;
other.data = nullptr;
other.n_points = 0;
}
return *this;
}
~input_data() { delete[] data; }
void set_point(size_t i, double x, double y) {
data[i * 2] = x;
data[i * 2 + 1] = y;
}
};
input_data read_data(const std::filesystem::path& filename) {
// check if file exists
if (!std::filesystem::exists(filename)) {
throw std::runtime_error("File does not exist: " + filename.string());
}
std::fstream file(filename, std::ios::in);
std::unordered_map<double, size_t> index_map;
std::vector<double> x_values;
std::vector<std::vector<double>> data_points;
std::string line;
size_t index = 0;
double x_avg = 0.0;
while (std::getline(file, line)) {
if (line.empty() || line[0] == '#') {
std::cout << "Skipped: " << line << std::endl;
continue;
}
std::istringstream iss(line);
double x, y;
iss >> x;
for (size_t i = 1; i < 5; ++i) {
iss >> y; // skip unused columns
}
y = autoopt::arcsec2rad(-y);
if (index_map.find(x) == index_map.end()) {
index_map[x] = index++;
x_values.push_back(x);
data_points.emplace_back();
data_points.back().push_back(y);
x_avg += x;
continue;
}
data_points[index_map[x]].push_back(y);
}
x_avg /= x_values.size();
input_data result(index);
for (size_t i = 0; i < x_values.size(); ++i) {
double x = x_values[i];
double y_avg = 0.0;
for (double y : data_points[i]) {
y_avg += y;
}
y_avg /= data_points[i].size();
result.set_point(i, x - x_avg, y_avg);
}
return result;
}
struct ellipse_params {
double left_arm;
double right_arm;
double theta;
};
NB_MODULE(slopefit, m) {
m.def("import_dat_file", [](std::string filename) {
input_data data = read_data(filename);
std::cout << "First data point: (" << data.data[0] << ", " << data.data[1]
<< ")" << std::endl;
nb::ndarray<double, nb::numpy> array(data.data, {data.n_points, 2});
return nb::cast(array);
});
nb::class_<ellipse_params>(m, "EllipseParams")
.def(nb::init<double, double, double>())
.def_rw("left_arm", &ellipse_params::left_arm)
.def_rw("right_arm", &ellipse_params::right_arm)
.def_rw("theta", &ellipse_params::theta)
.def("__repr__", [](const ellipse_params& params) {
std::ostringstream oss;
oss << "EllipseParams(left_arm=" << params.left_arm
<< ", right_arm=" << params.right_arm << ", theta=" << params.theta
<< ")";
return oss.str();
}).def("__call__", [](const ellipse_params& params, nb::ndarray<double, nb::ndim<1>> xs) {
auto e = autoopt::ellipse(params.left_arm, params.right_arm,
autoopt::deg2rad(params.theta));
autoopt::quadric<double> q = e.to_quadric();
Eigen::VectorXd ys(xs.shape(0));
for (size_t i = 0; i < xs.shape(0); ++i) {
ys(i) = q.slope_at(xs(i));
}
return ys;
})
;
m.def(
"fit_ellipse",
[](nb::ndarray<double, nb::ndim<2>> data, ellipse_params initial_params,
ellipse_params delta) -> ellipse_params {
std::cout << "Fitting ellipse to data with " << data.shape(0)
<< " points." << std::endl;
if (data.shape(1) != 2) {
throw std::runtime_error("Data array must have shape (n_points, 2)");
}
std::vector<std::pair<double, double>> data_vec;
for (size_t i = 0; i < data.shape(0); ++i) {
data_vec.emplace_back(data(i, 0), data(i, 1));
}
std::cout << "Initial parameters: "
<< "left_arm=" << initial_params.left_arm
<< ", right_arm=" << initial_params.right_arm
<< ", theta=" << initial_params.theta << std::endl;
double midpoint_y = data_vec[data_vec.size() / 2].second;
Eigen::VectorX<double> init_params(4);
init_params(0) = initial_params.left_arm;
init_params(1) = initial_params.right_arm;
init_params(2) = autoopt::deg2rad(initial_params.theta);
init_params(3) = midpoint_y;
Eigen::VectorX<double> deltas(4);
deltas(0) = delta.left_arm;
deltas(1) = delta.right_arm;
deltas(2) = autoopt::deg2rad(delta.theta);
deltas(3) = autoopt::deg2rad(0.1);
std::cout << "calculating fit..." << std::endl;
Eigen::VectorX<double> fitted_params =
autoopt::fit_ellipse(data_vec, init_params, deltas);
ellipse_params result;
result.left_arm = fitted_params(0);
result.right_arm = fitted_params(1);
result.theta = autoopt::rad2deg(fitted_params(2));
return result;
});
}