Add files from test project
This commit is contained in:
15
cpp/CMakeLists.txt
Normal file
15
cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
project(HomotopyContinuation)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(Eigen3 REQUIRED)
|
||||
|
||||
add_library(hccd INTERFACE)
|
||||
target_include_directories(hccd INTERFACE include)
|
||||
|
||||
add_subdirectory(examples)
|
||||
1
cpp/examples/CMakeLists.txt
Normal file
1
cpp/examples/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
add_subdirectory(toy_homotopy)
|
||||
7
cpp/examples/toy_homotopy/CMakeLists.txt
Normal file
7
cpp/examples/toy_homotopy/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
set(CMAKE_BuilD_TYPE Release)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
add_executable(toy_homotopy toy_homotopy.cpp)
|
||||
target_link_libraries(toy_homotopy PRIVATE hccd spdlog::spdlog Eigen3::Eigen)
|
||||
target_include_directories(toy_homotopy PRIVATE hccd lib/argparse/include)
|
||||
2739
cpp/examples/toy_homotopy/argparse.hpp
Normal file
2739
cpp/examples/toy_homotopy/argparse.hpp
Normal file
File diff suppressed because it is too large
Load Diff
187
cpp/examples/toy_homotopy/toy_homotopy.cpp
Normal file
187
cpp/examples/toy_homotopy/toy_homotopy.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
// STL includes
|
||||
#include <expected>
|
||||
|
||||
// Library includes
|
||||
#include <hccd/PathTracker.hpp>
|
||||
#include <hccd/util.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
// Project includes
|
||||
#include "argparse.hpp"
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// Homotopy definition
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
///
|
||||
/// @brief Helper type implementing necessary functions for PathTracker
|
||||
/// @details Toy example homotopy:
|
||||
/// G = [[x1],
|
||||
/// [x2]]
|
||||
///
|
||||
/// F = [[x1 + x2 ],
|
||||
/// [x2 + 0.5]]
|
||||
///
|
||||
/// H = (1-t)*G + t*F
|
||||
///
|
||||
/// @details Note that
|
||||
/// y := [[x1],
|
||||
/// [x2],
|
||||
/// [t]]
|
||||
///
|
||||
struct ToyHomotopy {
|
||||
///
|
||||
/// @brief Evaluate H at y
|
||||
///
|
||||
static Eigen::VectorXd evaluate_H(Eigen::VectorXd y) {
|
||||
Eigen::VectorXd result(2);
|
||||
|
||||
double x1 = y(0);
|
||||
double x2 = y(1);
|
||||
double t = y(2);
|
||||
|
||||
result(0) = x1 + t * x2;
|
||||
result(1) = x2 + t * 0.5;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Evaluate Jacobian of H at y
|
||||
///
|
||||
static Eigen::MatrixXd evaluate_DH(Eigen::VectorXd y) {
|
||||
Eigen::MatrixXd result(2, 3);
|
||||
|
||||
double x1 = y(0);
|
||||
double x2 = y(1);
|
||||
double t = y(2);
|
||||
|
||||
result(0, 0) = 1;
|
||||
result(0, 1) = t;
|
||||
result(0, 2) = x2;
|
||||
result(1, 0) = 0;
|
||||
result(1, 1) = 1;
|
||||
result(1, 2) = 0.5;
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// Perform path tracking
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
void tracker_example(hccd::Settings settings, std::size_t num_iterations,
|
||||
std::string output = "temp.csv") {
|
||||
|
||||
hccd::PathTracker<ToyHomotopy> tracker{settings};
|
||||
|
||||
std::ofstream out;
|
||||
if (output != "") {
|
||||
out.open(output);
|
||||
out << "x1b," << "x2b," << "tb," << "x1p," << "x2p," << "tp," << "x1e,"
|
||||
<< "x2e," << "te," << "x1n," << "x2n," << "tn," << std::endl;
|
||||
}
|
||||
|
||||
Eigen::VectorXd y = Eigen::VectorXd::Zero(3);
|
||||
|
||||
for (int i = 0; i < num_iterations; ++i) {
|
||||
spdlog::info("Iteration {}", i);
|
||||
|
||||
auto res = tracker.transparent_step(y);
|
||||
if (!res) {
|
||||
spdlog::error(
|
||||
"Newton corrector failed to converge on iteration {} ", i);
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
Eigen::VectorXd y_start, y_prime, y_hat_e;
|
||||
std::tie(y_start, y_prime, y_hat_e, y) = res.value();
|
||||
|
||||
spdlog::info("y:{}", y);
|
||||
|
||||
if (output != "") {
|
||||
out << y_start(0) << "," << y_start(1) << "," << y_start(2) << ","
|
||||
<< y_prime(0) << "," << y_prime(1) << "," << y_prime(2) << ","
|
||||
<< y_hat_e(0) << "," << y_hat_e(1) << "," << y_hat_e(2) << ","
|
||||
<< y(0) << "," << y(1) << "," << y(2) << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// User Interface
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
// Parse command line arguments
|
||||
|
||||
argparse::ArgumentParser program("Homotopy continuation path tracker");
|
||||
|
||||
program.add_argument("--verbose")
|
||||
.default_value<bool>(false)
|
||||
.implicit_value(true);
|
||||
program.add_argument("--euler-step-size")
|
||||
.help("Step size for Euler predictor")
|
||||
.default_value<double>(0.05)
|
||||
.scan<'g', double>();
|
||||
program.add_argument("--euler-max-tries")
|
||||
.help("Maximum number of tries for Euler predictor")
|
||||
.default_value<unsigned>(5)
|
||||
.scan<'u', unsigned>();
|
||||
program.add_argument("--newton-max-iter")
|
||||
.help("Maximum number of iterations for Newton corrector")
|
||||
.default_value<unsigned>(5)
|
||||
.scan<'u', unsigned>();
|
||||
program.add_argument("--newton-convergence-threshold")
|
||||
.help("Convergence threshold for Newton corrector")
|
||||
.default_value<double>(0.01)
|
||||
.scan<'g', double>();
|
||||
program.add_argument("-s", "--sigma")
|
||||
.help("Direction in which the path is traced")
|
||||
.default_value<int>(1)
|
||||
.scan<'i', int>();
|
||||
|
||||
program.add_argument("-o", "--output")
|
||||
.help("Output csv file")
|
||||
.default_value<std::string>("");
|
||||
program.add_argument("-n", "--num-iterations")
|
||||
.help("Number of iterations of the example program to run")
|
||||
.default_value<std::size_t>(20)
|
||||
.scan<'u', std::size_t>();
|
||||
|
||||
try {
|
||||
program.parse_args(argc, argv);
|
||||
} catch (const std::runtime_error& err) {
|
||||
spdlog::error("{}", err.what());
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
// Run program
|
||||
|
||||
if (program["--verbose"] == true) spdlog::set_level(spdlog::level::debug);
|
||||
|
||||
hccd::Settings settings{
|
||||
.euler_step_size = program.get<double>("--euler-step-size"),
|
||||
.euler_max_tries = program.get<unsigned>("--euler-max-tries"),
|
||||
.newton_max_iter = program.get<unsigned>("--newton-max-iter"),
|
||||
.newton_convergence_threshold =
|
||||
program.get<double>("--newton-convergence-threshold"),
|
||||
.sigma = program.get<int>("--sigma"),
|
||||
};
|
||||
|
||||
tracker_example(settings, program.get<std::size_t>("--num-iterations"),
|
||||
program.get<std::string>("--output"));
|
||||
}
|
||||
156
cpp/include/hccd/PathTracker.hpp
Normal file
156
cpp/include/hccd/PathTracker.hpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
// STL includes
|
||||
#include <expected>
|
||||
|
||||
// Library includes
|
||||
#include <Eigen/Dense>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
// Project includes
|
||||
#include "util.hpp"
|
||||
|
||||
|
||||
namespace hccd {
|
||||
|
||||
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
concept homotopy_c = requires(T) {
|
||||
{ T::evaluate_H(Eigen::VectorXd()) } -> std::same_as<Eigen::VectorXd>;
|
||||
{ T::evaluate_DH(Eigen::VectorXd()) } -> std::same_as<Eigen::MatrixXd>;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
|
||||
///
|
||||
/// @brief Settings for PathTracker
|
||||
///
|
||||
struct Settings {
|
||||
double euler_step_size = 0.05;
|
||||
unsigned euler_max_tries = 5;
|
||||
unsigned newton_max_iter = 5;
|
||||
double newton_convergence_threshold = 0.01;
|
||||
int sigma = 1; ///< Direction in which the path is traced
|
||||
};
|
||||
|
||||
///
|
||||
/// @brief Path tracker for the homotopy continuation method
|
||||
/// @details Uses a predictor-corrector scheme to trace a path defined by a
|
||||
/// homotopy.
|
||||
/// @details References:
|
||||
/// [1] T. Chen and T.-Y. Li, “Homotopy continuation method for solving
|
||||
/// systems of nonlinear and polynomial equations,” Communications in
|
||||
/// Information and Systems, vol. 15, no. 2, pp. 119–307, 2015
|
||||
///
|
||||
/// @tparam homotopy_c Homotopy defining the path
|
||||
///
|
||||
template <detail::homotopy_c Homotopy>
|
||||
class PathTracker {
|
||||
public:
|
||||
enum Error { NewtonNotConverged };
|
||||
|
||||
PathTracker(Settings settings) : m_settings{settings} {
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Perform one predictor-corrector step
|
||||
///
|
||||
Eigen::VectorXd step(Eigen::VectorXd y) {
|
||||
auto res = transparent_step(y);
|
||||
|
||||
if (res) {
|
||||
return res.value().first;
|
||||
} else {
|
||||
return std::unexpected(res.error());
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// @brief Perform one predictor-corrector step, returning intermediate
|
||||
/// results
|
||||
///
|
||||
std::expected<std::tuple<Eigen::VectorXd, Eigen::VectorXd, Eigen::VectorXd,
|
||||
Eigen::VectorXd>,
|
||||
Error>
|
||||
transparent_step(Eigen::VectorXd y) {
|
||||
for (int i = 0; i < m_settings.euler_max_tries; ++i) {
|
||||
double step_size = m_settings.euler_step_size / (1 << i);
|
||||
|
||||
const auto [y_hat, y_prime] =
|
||||
perform_euler_predictor_step(y, step_size);
|
||||
|
||||
auto res = perform_newton_corrector_step(y_hat);
|
||||
|
||||
if (res) return {{y, y_prime, y_hat, res.value()}};
|
||||
}
|
||||
|
||||
return std::unexpected(Error::NewtonNotConverged);
|
||||
}
|
||||
|
||||
private:
|
||||
Settings m_settings;
|
||||
|
||||
std::pair<Eigen::VectorXd, Eigen::VectorXd>
|
||||
perform_euler_predictor_step(Eigen::VectorXd y, double step_size) {
|
||||
/// Obtain y_prime
|
||||
|
||||
Eigen::MatrixXd DH = Homotopy::evaluate_DH(y);
|
||||
auto qr = DH.transpose().colPivHouseholderQr();
|
||||
Eigen::MatrixXd Q = qr.matrixQ();
|
||||
Eigen::MatrixXd R = qr.matrixR();
|
||||
|
||||
Eigen::VectorXd y_prime = Q.col(2);
|
||||
|
||||
spdlog::debug("Q: \t\t{}x{}; det={}", Q.rows(), Q.cols(),
|
||||
Q.determinant());
|
||||
spdlog::debug("R.topRows(2): {}x{}; det={}", R.topRows(2).rows(),
|
||||
R.topRows(2).cols(), R.topRows(2).determinant());
|
||||
|
||||
if (sign(Q.determinant() * R.topRows(2).determinant()) !=
|
||||
sign(m_settings.sigma))
|
||||
y_prime = -y_prime;
|
||||
|
||||
/// Perform prediction
|
||||
|
||||
Eigen::VectorXd y_hat = y + step_size * y_prime;
|
||||
|
||||
return {y_hat, y_prime};
|
||||
}
|
||||
|
||||
std::expected<Eigen::VectorXd, Error>
|
||||
perform_newton_corrector_step(Eigen::VectorXd y) {
|
||||
Eigen::VectorXd prev_y = y;
|
||||
|
||||
for (int i = 0; i < m_settings.newton_max_iter; ++i) {
|
||||
|
||||
/// Perform correction
|
||||
|
||||
Eigen::MatrixXd DH = Homotopy::evaluate_DH(y);
|
||||
Eigen::MatrixXd DH_pinv =
|
||||
DH.completeOrthogonalDecomposition().pseudoInverse();
|
||||
|
||||
y = y - DH_pinv * Homotopy::evaluate_H(y);
|
||||
|
||||
/// Check stopping criterion
|
||||
|
||||
spdlog::debug("Newton iteration {}: ||y-prev_y||={}", i,
|
||||
(y - prev_y).norm());
|
||||
if ((y - prev_y).norm() < m_settings.newton_convergence_threshold)
|
||||
return y;
|
||||
|
||||
prev_y = y;
|
||||
}
|
||||
|
||||
return std::unexpected(Error::NewtonNotConverged);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static int sign(T val) {
|
||||
return -1 * (val < T(0)) + 1 * (val >= T(0));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
} // namespace hccd
|
||||
37
cpp/include/hccd/util.hpp
Normal file
37
cpp/include/hccd/util.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
// #include <Eigen/src/Core/util/ForwardDeclarations.h>
|
||||
#include <spdlog/fmt/ostr.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
|
||||
/// @brief Boilerplate code to enable fmtlib to print Eigen::MatrixXd
|
||||
template <>
|
||||
struct fmt::formatter<Eigen::MatrixXd> : fmt::ostream_formatter {};
|
||||
/// @brief Boilerplate code to enable fmtlib to print Eigen::VectorXd
|
||||
template <>
|
||||
struct fmt::formatter<Eigen::VectorXd> : fmt::ostream_formatter {
|
||||
|
||||
template <typename Context>
|
||||
auto format(const Eigen::VectorXd& value, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
|
||||
auto buffer = basic_memory_buffer<char>();
|
||||
auto&& formatbuf =
|
||||
detail::formatbuf<std::basic_streambuf<char>>(buffer);
|
||||
auto&& output = std::basic_ostream<char>(&formatbuf);
|
||||
output.imbue(std::locale::classic());
|
||||
|
||||
output << "[";
|
||||
for (int i = 0; i < value.size(); ++i) {
|
||||
output << value(i);
|
||||
if (i < value.size() - 1) output << ", ";
|
||||
}
|
||||
output << "]";
|
||||
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
return formatter<basic_string_view<char>, char>::format(
|
||||
{buffer.data(), buffer.size()}, ctx);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user