#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 index_map; std::vector x_values; std::vector> 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 array(data.data, {data.n_points, 2}); return nb::cast(array); }); nb::class_(m, "EllipseParams") .def(nb::init()) .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> xs) { auto e = autoopt::ellipse(params.left_arm, params.right_arm, autoopt::deg2rad(params.theta)); autoopt::quadric 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> 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> 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 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 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 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; }); }