Add files from test project
This commit is contained in:
parent
d10c955c61
commit
9880cf6655
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
.cache/
|
||||
__pycache__/
|
||||
4
Dockerfile
Normal file
4
Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
FROM alpine:3.21.3
|
||||
|
||||
RUN apk update
|
||||
RUN apk add cmake make g++ python3 eigen-dev spdlog-dev
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 an.tsouchlos
|
||||
Copyright (c) 2025 Andreas Tsouchlos
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
43
README.md
43
README.md
@ -1,3 +1,42 @@
|
||||
# homotopy-continuation-channel-decoding
|
||||
# Homotopy Continuation Channel Decoding
|
||||
|
||||
A project attempting to use homotopy continuation to perform channel decoding.
|
||||
A project using homotopy continuation methods to perform channel decoding.
|
||||
|
||||
This repository contains implementations in multiple programming languages,
|
||||
that are independent of each other. E.g., for each programming language there
|
||||
are dedicated examples.
|
||||
|
||||
| Directory | Content |
|
||||
| ---------- | ------------------ |
|
||||
| `docs/` | Documentation |
|
||||
| `scripts/` | Utility scripts |
|
||||
| `cpp/` | C++ source code |
|
||||
| `python/` | Python source code |
|
||||
|
||||
## Use Python Code
|
||||
|
||||
```bash
|
||||
$ python python/examples/toy_homotopy.py -o temp.csv
|
||||
$ python scripts/plot_solution_curve.py -i temp.csv
|
||||
```
|
||||
|
||||
## Use C++ Code
|
||||
|
||||
#### On Host System
|
||||
|
||||
```bash
|
||||
$ cmake -B build -S cpp
|
||||
$ cmake --build build
|
||||
$ ./build/toy_example_cpp/toy_example -o temp.csv
|
||||
$ python scripts/plot_solution_curve.py -i temp.csv
|
||||
```
|
||||
|
||||
#### In Docker Container
|
||||
|
||||
```bash
|
||||
$ docker build . -t hccd
|
||||
$ docker run --rm -it -u `id -u`:`id -g` -v $PWD:$PWD -w $PWD hccd cmake -B build -S cpp
|
||||
$ docker run --rm -it -u `id -u`:`id -g` -v $PWD:$PWD -w $PWD hccd cmake --build build
|
||||
$ docker run --rm -it -u `id -u`:`id -g` -v $PWD:$PWD -w $PWD hccd ./build/toy_example_cpp/toy_example -o temp.csv
|
||||
$ python scripts/plot_solution_curve.py -i temp.csv
|
||||
```
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
99
docs/general_idea.md
Normal file
99
docs/general_idea.md
Normal file
@ -0,0 +1,99 @@
|
||||
# Homotopy Continuation
|
||||
|
||||
### Introduction
|
||||
|
||||
The aim of a homotopy method consists in solving a system of N nonlinear
|
||||
equations in N variables \[1, p.1\]:
|
||||
|
||||
$$
|
||||
F(\bm{x}) = 0, \hspace{5mm} F: \mathbb{R}^N \rightarrow \mathbb{R}^N
|
||||
$$
|
||||
|
||||
This is achieved by defining a homotopy, or deformation,
|
||||
$H : \mathbb{R}^N \rightarrow \mathbb{R}^N$ such that
|
||||
|
||||
$$
|
||||
H(\bm{x}, 0) = G(\bm{x}), \hspace{5mm} H(\bm{x},1) = F(\bm{x}),
|
||||
$$
|
||||
|
||||
where $G: \mathbb{R}^N \rightarrow \mathbb{R}^N$ is trivial, having known zero
|
||||
points. One can then trace an implicitly defined curve
|
||||
$\bm{c}(s) \in H^{-1}(0)$ from $(\bm{x}_1,0)$ to $(\bm{x}_2, 1)$ [2, p.122].
|
||||
Typically, a convex homotopy such as
|
||||
|
||||
$$
|
||||
H(\bm{x}, t) := tG(\bm{x}) + (1-t)F(\bm{x})
|
||||
$$
|
||||
|
||||
is chosen [1, p.2].
|
||||
|
||||
### Implicit Definition of Solution Curve
|
||||
|
||||
"In the construction of the homotopy H(x, t) = 0, it may seem natural to use t
|
||||
as the designated parameter for the solution curve [...]. However, this
|
||||
parametrization has a severe limitation, as t cannot be used as a smooth
|
||||
parameter in certain situations. For example, [...] at points where ∂H/∂x is
|
||||
singular the solution curve of H(x, t) = 0 cannot be parametrized by t
|
||||
directly. Actually, one may avoid such difficulties by considering both x and t
|
||||
as independent variables and parametrize the smooth curve γ by the arc-length."
|
||||
[2, p. 125-126].
|
||||
|
||||
We can show that the solution curve can be implicitly defined as
|
||||
|
||||
$$
|
||||
\begin{align}
|
||||
DH(\bm{y}(s))\cdot \dot{\bm{y}}(s) = 0 \\[.5em]
|
||||
\text{det}\left(\begin{array}{c} DH(\bm{y}(s)) \\ \dot{\bm{y}}(s)\end{array}\right) = \sigma_0 \\[.5em]
|
||||
\lVert \dot{\bm{y}}(s) \rVert = 1 \\[.5em]
|
||||
\bm{y}(0) = (\bm{x}_0, 0)
|
||||
.
|
||||
\end{align}
|
||||
$$
|
||||
|
||||
where $DH(y)$ is the Jacobian of $H(y)$ [2, p.127] [1, p.9]. Equation (1)
|
||||
corresponds to $\dot{y}(s)$ being tangent to the solution curve, the purpose of
|
||||
(2) is to fix the direction in which the curve is traced.
|
||||
|
||||
### Tracing solution curves
|
||||
|
||||
"In principle, any of the available ODE solvers capable of integrating the
|
||||
above system can be used to trace the curve [...]. However, due to numerical
|
||||
stability concerns, a more preferable method to trace the curve is the
|
||||
'prediction-correction scheme'. [...] Among many different choices for the
|
||||
'predictors' as well as 'correctors', we shall present here a commonly used
|
||||
combination of the (generalized) Euler’s method for the predictor and Newton’s
|
||||
method for the corrector." [2, p.127]
|
||||
|
||||
#### Euler's Predictor
|
||||
|
||||
$$
|
||||
\hat{\bm{y}} = \bm{y}_0 + \Delta s \cdot \sigma \cdot \Delta \bm{y},
|
||||
$$
|
||||
|
||||
with $\Delta s$ denoting the step size and $\Delta y$ the numerical
|
||||
approximation of $\dot{y}(s)$ [2, p.128].
|
||||
|
||||
#### Newton's corrector
|
||||
|
||||
$$
|
||||
\bm{y} = \mathcal{N}^k(\hat{\bm{y}}), \hspace{5mm} \mathcal{N}(\hat{\bm{y}}) := \hat{\bm{y}} - (DH(\hat{\bm{y}}))^{+} H(\hat{\bm{y}}).
|
||||
$$
|
||||
|
||||
" [...] the shrinking distance
|
||||
$\lVert \mathcal{N}^j(\hat{\bm{y}}) - \mathcal{N}^{j-1} (\hat{\bm{y}})\rVert$
|
||||
between successive points produced by the iterations can be used as a criterion
|
||||
for convergence. Of course, if the iterations fail to converge, one must go
|
||||
back to adjust the step size for the Euler’s predictor." [2, p.130]
|
||||
|
||||
______________________________________________________________________
|
||||
|
||||
## References
|
||||
|
||||
\[1\]: E. L. Allgower and K. Georg, Introduction to numerical continuation
|
||||
methods. in Classics in applied mathematics, no. 45. Philadelphia, Pa: Society
|
||||
for Industrial and Applied Mathematics (SIAM, 3600 Market Street, Floor 6,
|
||||
Philadelphia, PA 19104), 2003. doi: 10.1137/1.9780898719154.
|
||||
|
||||
\[2\]: 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, doi: 10.4310/CIS.2015.v15.n2.a1.
|
||||
142
python/examples/repetition_code.py
Normal file
142
python/examples/repetition_code.py
Normal file
@ -0,0 +1,142 @@
|
||||
import argparse
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# autopep8: off
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(f"{os.path.dirname(os.path.abspath(__file__))}/../")
|
||||
|
||||
# TODO: How do I import PathTracker and HomotopyGenerator properly?
|
||||
from hccd import path_tracker, homotopy_generator
|
||||
# autopep8: on
|
||||
|
||||
|
||||
# class RepetitionCodeHomotopy:
|
||||
# """Helper type implementing necessary functions for PathTracker.
|
||||
#
|
||||
# Repetiton code homotopy:
|
||||
# G = [[x1],
|
||||
# [x2],
|
||||
# [x1]]
|
||||
#
|
||||
# F = [[1 - x1**2],
|
||||
# [1 - x2**2],
|
||||
# [1 - x1*x2]]
|
||||
#
|
||||
# H = (1-t)*G + t*F
|
||||
#
|
||||
# Note that
|
||||
# y := [[x1],
|
||||
# [x2],
|
||||
# [t]]
|
||||
# """
|
||||
# @staticmethod
|
||||
# def evaluate_H(y: np.ndarray) -> np.ndarray:
|
||||
# """Evaluate H at y."""
|
||||
# x1 = y[0]
|
||||
# x2 = y[1]
|
||||
# t = y[2]
|
||||
#
|
||||
# print(y)
|
||||
#
|
||||
# result = np.zeros(shape=3)
|
||||
# result[0] = -t*x1**2 + x1*(1-t) + t
|
||||
# result[1] = -t*x2**2 + x2*(1-t) + t
|
||||
# result[2] = -t*x1*x2 + x1*(1-t) + t
|
||||
#
|
||||
# return result
|
||||
#
|
||||
# @staticmethod
|
||||
# def evaluate_DH(y: np.ndarray) -> np.ndarray:
|
||||
# """Evaluate Jacobian of H at y."""
|
||||
# x1 = y[0]
|
||||
# x2 = y[1]
|
||||
# t = y[2]
|
||||
#
|
||||
# result = np.zeros(shape=(3, 3))
|
||||
# result[0, 0] = -2*t*x1 + (1-t)
|
||||
# result[0, 1] = 0
|
||||
# result[0, 2] = -x1**2 - x1 + 1
|
||||
# result[1, 0] = 0
|
||||
# result[1, 1] = -2*t*x2 + (1-t)
|
||||
# result[1, 2] = -x2**2 - x2 + 1
|
||||
# result[1, 0] = -t*x2 + (1-t)
|
||||
# result[1, 1] = -t*x1
|
||||
# result[1, 2] = -x1*x2 - x1 + 1
|
||||
#
|
||||
# return result
|
||||
|
||||
|
||||
def track_path(args):
|
||||
H = np.array([[1, 1, 1]])
|
||||
homotopy = homotopy_generator.HomotopyGenerator(H)
|
||||
|
||||
tracker = path_tracker.PathTracker(homotopy, args.euler_step_size, args.euler_max_tries,
|
||||
args.newton_max_iter, args.newton_convergence_threshold, args.sigma)
|
||||
|
||||
ys_start, ys_prime, ys_hat_e, ys = [], [], [], []
|
||||
|
||||
try:
|
||||
y = np.zeros(3)
|
||||
for i in range(args.num_iterations):
|
||||
y_start, y_prime, y_hat_e, y = tracker.transparent_step(y)
|
||||
ys_start.append(y_start)
|
||||
ys_prime.append(y_prime)
|
||||
ys_hat_e.append(y_hat_e)
|
||||
ys.append(y)
|
||||
print(f"Iteration {i}: {y}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
ys_start = np.array(ys_start)
|
||||
ys_prime = np.array(ys_prime)
|
||||
ys_hat_e = np.array(ys_hat_e)
|
||||
ys = np.array(ys)
|
||||
|
||||
df = pd.DataFrame({"x1b": ys_start[:, 0],
|
||||
"x2b": ys_start[:, 1],
|
||||
"tb": ys_start[:, 2],
|
||||
"x1p": ys_prime[:, 0],
|
||||
"x2p": ys_prime[:, 1],
|
||||
"tp": ys_prime[:, 2],
|
||||
"x1e": ys_hat_e[:, 0],
|
||||
"x2e": ys_hat_e[:, 1],
|
||||
"te": ys_hat_e[:, 2],
|
||||
"x1n": ys[:, 0],
|
||||
"x2n": ys[:, 1],
|
||||
"tn": ys[:, 2]
|
||||
})
|
||||
|
||||
if args.output:
|
||||
df.to_csv(args.output, index=False)
|
||||
else:
|
||||
print(df)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Homotopy continuation path tracker')
|
||||
|
||||
parser.add_argument("--verbose", default=False, action='store_true')
|
||||
parser.add_argument("--euler-step-size", type=float,
|
||||
default=0.05, help="Step size for Euler predictor")
|
||||
parser.add_argument("--euler-max-tries", type=int, default=5,
|
||||
help="Maximum number of tries for Euler predictor")
|
||||
parser.add_argument("--newton-max-iter", type=int, default=5,
|
||||
help="Maximum number of iterations for Newton corrector")
|
||||
parser.add_argument("--newton-convergence-threshold", type=float,
|
||||
default=0.01, help="Convergence threshold for Newton corrector")
|
||||
parser.add_argument("-s", "--sigma", type=int, default=1,
|
||||
help="Direction in which the path is traced")
|
||||
parser.add_argument("-o", "--output", type=str, help="Output csv file")
|
||||
parser.add_argument("-n", "--num-iterations", type=int, default=20,
|
||||
help="Number of iterations of the example program to run")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
track_path(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
131
python/examples/toy_homotopy.py
Normal file
131
python/examples/toy_homotopy.py
Normal file
@ -0,0 +1,131 @@
|
||||
import argparse
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# autopep8: off
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(f"{os.path.dirname(os.path.abspath(__file__))}/../")
|
||||
|
||||
# TODO: How do I import PathTracker and HomotopyGenerator properly?
|
||||
from hccd import path_tracker, homotopy_generator
|
||||
# autopep8: on
|
||||
|
||||
|
||||
class ToyHomotopy:
|
||||
"""Helper type implementing necessary functions for PathTracker.
|
||||
|
||||
Toy example homotopy:
|
||||
G = [[x1],
|
||||
[x2]]
|
||||
|
||||
F = [[x1 + x2 ],
|
||||
[x2 + 0.5]]
|
||||
|
||||
H = (1-t)*G + t*F
|
||||
|
||||
Note that
|
||||
y := [[x1],
|
||||
[x2],
|
||||
[t]]
|
||||
"""
|
||||
@staticmethod
|
||||
def evaluate_H(y: np.ndarray) -> np.ndarray:
|
||||
"""Evaluate H at y."""
|
||||
x1 = y[0]
|
||||
x2 = y[1]
|
||||
t = y[2]
|
||||
|
||||
result = np.zeros(shape=2)
|
||||
result[0] = x1 + t * x2
|
||||
result[1] = x2 + t * 0.5
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def evaluate_DH(y: np.ndarray) -> np.ndarray:
|
||||
"""Evaluate Jacobian of H at y."""
|
||||
x1 = y[0]
|
||||
x2 = y[1]
|
||||
t = y[2]
|
||||
|
||||
result = np.zeros(shape=(2, 3))
|
||||
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
|
||||
|
||||
|
||||
def track_path(args):
|
||||
tracker = path_tracker.PathTracker(ToyHomotopy, args.euler_step_size, args.euler_max_tries,
|
||||
args.newton_max_iter, args.newton_convergence_threshold, args.sigma)
|
||||
|
||||
ys_start, ys_prime, ys_hat_e, ys = [], [], [], []
|
||||
|
||||
try:
|
||||
y = np.zeros(3)
|
||||
for i in range(args.num_iterations):
|
||||
y_start, y_prime, y_hat_e, y = tracker.transparent_step(y)
|
||||
ys_start.append(y_start)
|
||||
ys_prime.append(y_prime)
|
||||
ys_hat_e.append(y_hat_e)
|
||||
ys.append(y)
|
||||
print(f"Iteration {i}: {y}")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
ys_start = np.array(ys_start)
|
||||
ys_prime = np.array(ys_prime)
|
||||
ys_hat_e = np.array(ys_hat_e)
|
||||
ys = np.array(ys)
|
||||
|
||||
df = pd.DataFrame({"x1b": ys_start[:, 0],
|
||||
"x2b": ys_start[:, 1],
|
||||
"tb": ys_start[:, 2],
|
||||
"x1p": ys_prime[:, 0],
|
||||
"x2p": ys_prime[:, 1],
|
||||
"tp": ys_prime[:, 2],
|
||||
"x1e": ys_hat_e[:, 0],
|
||||
"x2e": ys_hat_e[:, 1],
|
||||
"te": ys_hat_e[:, 2],
|
||||
"x1n": ys[:, 0],
|
||||
"x2n": ys[:, 1],
|
||||
"tn": ys[:, 2]
|
||||
})
|
||||
|
||||
if args.output:
|
||||
df.to_csv(args.output, index=False)
|
||||
else:
|
||||
print(df)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Homotopy continuation path tracker')
|
||||
|
||||
parser.add_argument("--verbose", default=False, action='store_true')
|
||||
parser.add_argument("--euler-step-size", type=float,
|
||||
default=0.05, help="Step size for Euler predictor")
|
||||
parser.add_argument("--euler-max-tries", type=int, default=5,
|
||||
help="Maximum number of tries for Euler predictor")
|
||||
parser.add_argument("--newton-max-iter", type=int, default=5,
|
||||
help="Maximum number of iterations for Newton corrector")
|
||||
parser.add_argument("--newton-convergence-threshold", type=float,
|
||||
default=0.01, help="Convergence threshold for Newton corrector")
|
||||
parser.add_argument("-s", "--sigma", type=int, default=1,
|
||||
help="Direction in which the path is traced")
|
||||
parser.add_argument("-o", "--output", type=str, help="Output csv file")
|
||||
parser.add_argument("-n", "--num-iterations", type=int, default=20,
|
||||
help="Number of iterations of the example program to run")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
track_path(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
0
python/hccd/__init__.py
Normal file
0
python/hccd/__init__.py
Normal file
6
python/hccd/__main__.py
Normal file
6
python/hccd/__main__.py
Normal file
@ -0,0 +1,6 @@
|
||||
def main():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
117
python/hccd/homotopy_generator.py
Normal file
117
python/hccd/homotopy_generator.py
Normal file
@ -0,0 +1,117 @@
|
||||
import numpy as np
|
||||
import sympy as sp
|
||||
from typing import List, Callable
|
||||
|
||||
|
||||
class HomotopyGenerator:
|
||||
"""Generates homotopy functions from a binary parity check matrix."""
|
||||
|
||||
def __init__(self, parity_check_matrix: np.ndarray):
|
||||
"""
|
||||
Initialize with a parity check matrix.
|
||||
|
||||
Args:
|
||||
parity_check_matrix: Binary matrix where rows represent parity checks
|
||||
and columns represent variables.
|
||||
"""
|
||||
self.H_matrix = parity_check_matrix
|
||||
self.num_checks, self.num_vars = parity_check_matrix.shape
|
||||
|
||||
# Create symbolic variables
|
||||
self.x_vars = [sp.symbols(f'x{i+1}') for i in range(self.num_vars)]
|
||||
self.t = sp.symbols('t')
|
||||
|
||||
# Generate G, F, and H
|
||||
self.G = self._create_G()
|
||||
self.F = self._create_F()
|
||||
self.H = self._create_H()
|
||||
|
||||
# Convert to callable functions
|
||||
self._H_lambda = self._create_H_lambda()
|
||||
self._DH_lambda = self._create_DH_lambda()
|
||||
|
||||
def _create_G(self) -> List[sp.Expr]:
|
||||
"""Create G polynomial system (the starting system)."""
|
||||
# For each variable xi, add the polynomial [xi]
|
||||
G = []
|
||||
for var in self.x_vars:
|
||||
G.append(var)
|
||||
|
||||
return G
|
||||
|
||||
def _create_F(self) -> List[sp.Expr]:
|
||||
"""Create F polynomial system (the target system)."""
|
||||
F = []
|
||||
|
||||
# Add 1 - xi^2 for each variable
|
||||
for var in self.x_vars:
|
||||
F.append(1 - var**2)
|
||||
|
||||
# Add parity check polynomials: 1 - x1*x2*...*xk for each parity check
|
||||
for row in self.H_matrix:
|
||||
# Create product of variables that participate in this check
|
||||
term = 1
|
||||
for i, bit in enumerate(row):
|
||||
if bit == 1:
|
||||
term *= self.x_vars[i]
|
||||
|
||||
if term != 1: # Only add if there are variables in this check
|
||||
F.append(1 - term)
|
||||
|
||||
return F
|
||||
|
||||
def _create_H(self) -> List[sp.Expr]:
|
||||
"""Create the homotopy H = (1-t)*G + t*F."""
|
||||
H = []
|
||||
|
||||
# Make sure G and F have the same length
|
||||
# Repeat variables from G if needed to match F's length
|
||||
G_extended = self.G.copy()
|
||||
while len(G_extended) < len(self.F):
|
||||
# Cycle through variables to repeat
|
||||
for i in range(min(self.num_vars, len(self.F) - len(G_extended))):
|
||||
G_extended.append(self.x_vars[i % self.num_vars])
|
||||
|
||||
# Create the homotopy
|
||||
for g, f in zip(G_extended, self.F):
|
||||
H.append((1 - self.t) * g + self.t * f)
|
||||
|
||||
return H
|
||||
|
||||
def _create_H_lambda(self) -> Callable:
|
||||
"""Create a lambda function to evaluate H."""
|
||||
all_vars = self.x_vars + [self.t]
|
||||
return sp.lambdify(all_vars, self.H, 'numpy')
|
||||
|
||||
def _create_DH_lambda(self) -> Callable:
|
||||
"""Create a lambda function to evaluate the Jacobian of H."""
|
||||
all_vars = self.x_vars + [self.t]
|
||||
jacobian = sp.Matrix([[sp.diff(expr, var)
|
||||
for var in all_vars] for expr in self.H])
|
||||
return sp.lambdify(all_vars, jacobian, 'numpy')
|
||||
|
||||
def evaluate_H(self, y: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Evaluate H at point y.
|
||||
|
||||
Args:
|
||||
y: Array of form [x1, x2, ..., xn, t] where xi are the variables
|
||||
and t is the homotopy parameter.
|
||||
|
||||
Returns:
|
||||
Array containing H evaluated at y.
|
||||
"""
|
||||
return np.array(self._H_lambda(*y))
|
||||
|
||||
def evaluate_DH(self, y: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Evaluate the Jacobian of H at point y.
|
||||
|
||||
Args:
|
||||
y: Array of form [x1, x2, ..., xn, t] where xi are the variables
|
||||
and t is the homotopy parameter.
|
||||
|
||||
Returns:
|
||||
Matrix containing the Jacobian of H evaluated at y.
|
||||
"""
|
||||
return np.array(self._DH_lambda(*y), dtype=float)
|
||||
84
python/hccd/path_tracker.py
Normal file
84
python/hccd/path_tracker.py
Normal file
@ -0,0 +1,84 @@
|
||||
import numpy as np
|
||||
import typing
|
||||
import scipy
|
||||
|
||||
|
||||
def _sign(val):
|
||||
return -1 * (val < 0) + 1 * (val >= 0)
|
||||
|
||||
|
||||
class PathTracker:
|
||||
"""
|
||||
Path trakcer for the homotopy continuation method. Uses a
|
||||
predictor-corrector scheme to trace a path defined by a homotopy.
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
def __init__(self, Homotopy, euler_step_size=0.05, euler_max_tries=10, newton_max_iter=5,
|
||||
newton_convergence_threshold=0.001, sigma=1):
|
||||
self.Homotopy = Homotopy
|
||||
self._euler_step_size = euler_step_size
|
||||
self._euler_max_tries = euler_max_tries
|
||||
self._newton_max_iter = newton_max_iter
|
||||
self._newton_convergence_threshold = newton_convergence_threshold
|
||||
self._sigma = sigma
|
||||
|
||||
def step(self, y):
|
||||
"""Perform one predictor-corrector step."""
|
||||
return self.transparent_step(y)[0]
|
||||
|
||||
def transparent_step(self, y) -> typing.Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray,]:
|
||||
"""Perform one predictor-corrector step, returning intermediate results."""
|
||||
for i in range(self._euler_max_tries):
|
||||
step_size = self._euler_step_size / (1 << i)
|
||||
|
||||
y_hat, y_prime = self._perform_euler_predictor_step(y, step_size)
|
||||
y_hat_n = self._perform_newtown_corrector_step(y_hat)
|
||||
|
||||
return y, y_prime, y_hat, y_hat_n
|
||||
|
||||
raise RuntimeError("Newton corrector did not converge")
|
||||
|
||||
def _perform_euler_predictor_step(self, y, step_size) -> typing.Tuple[np.ndarray, np.ndarray]:
|
||||
# Obtain y_prime
|
||||
|
||||
DH = self.Homotopy.evaluate_DH(y)
|
||||
ns = scipy.linalg.null_space(DH)
|
||||
|
||||
y_prime = ns[:, 0] * self._sigma
|
||||
|
||||
# Q, R = np.linalg.qr(np.transpose(DH), mode="complete")
|
||||
# y_prime = Q[:, 2]
|
||||
#
|
||||
# if _sign(np.linalg.det(Q)*np.linalg.det(R[:2, :])) != _sign(self._sigma):
|
||||
# y_prime = -y_prime
|
||||
|
||||
# Perform prediction
|
||||
|
||||
y_hat = y + step_size*y_prime
|
||||
|
||||
return y_hat, y_prime
|
||||
|
||||
def _perform_newtown_corrector_step(self, y) -> np.ndarray:
|
||||
prev_y = y
|
||||
|
||||
for _ in range(self._newton_max_iter):
|
||||
# Perform correction
|
||||
|
||||
DH = self.Homotopy.evaluate_DH(y)
|
||||
DH_pinv = np.linalg.pinv(DH)
|
||||
|
||||
y = y - DH_pinv @ self.Homotopy.evaluate_H(y)
|
||||
|
||||
# Check stopping criterion
|
||||
|
||||
if np.linalg.norm(y - prev_y) < self._newton_convergence_threshold:
|
||||
return y
|
||||
|
||||
prev_y = y
|
||||
|
||||
raise RuntimeError("Newton corrector did not converge")
|
||||
106
scripts/plot_solution_curve.py
Normal file
106
scripts/plot_solution_curve.py
Normal file
@ -0,0 +1,106 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import argparse
|
||||
|
||||
|
||||
def plot_2d(df: pd.DataFrame):
|
||||
df = pd.read_csv("temp.csv")
|
||||
|
||||
fig = plt.figure()
|
||||
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95)
|
||||
ax = fig.add_subplot()
|
||||
|
||||
# Analytically computed solution
|
||||
ts = df['tb']
|
||||
x2s = -ts / 2
|
||||
x1s = -ts * x2s
|
||||
|
||||
ax.scatter(x1s, x2s)
|
||||
|
||||
ax.set_xlabel("x1")
|
||||
ax.set_ylabel("x2")
|
||||
# ax.set_zlabel("t")
|
||||
|
||||
df = df.reset_index()
|
||||
|
||||
for _, row in df.iterrows():
|
||||
x1b = row['x1b']
|
||||
x2b = row['x2b']
|
||||
# x1p = row['x1p']
|
||||
# x2p = row['x2p']
|
||||
x1e = row['x1e']
|
||||
x2e = row['x2e']
|
||||
x1n = row['x1n']
|
||||
x2n = row['x2n']
|
||||
|
||||
ax.plot([x1b, x1e], [x2b, x2e], 'b')
|
||||
ax.plot([x1e, x1n], [x2e, x2n], 'r')
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
def plot_3d(df: pd.DataFrame):
|
||||
df = pd.read_csv("temp.csv")
|
||||
|
||||
fig = plt.figure()
|
||||
plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95)
|
||||
ax = fig.add_subplot(projection='3d')
|
||||
|
||||
# Analytically computed solution
|
||||
ts = df['tb']
|
||||
x2s = -ts / 2
|
||||
x1s = -ts * x2s
|
||||
|
||||
ax.scatter(x1s, x2s)
|
||||
ax.scatter(x1s, x2s, ts)
|
||||
|
||||
ax.set_xlabel("x1")
|
||||
ax.set_ylabel("x2")
|
||||
ax.set_zlabel("t")
|
||||
|
||||
df = df.reset_index()
|
||||
|
||||
for _, row in df.iterrows():
|
||||
x1b = row['x1b']
|
||||
x2b = row['x2b']
|
||||
tb = row['tb']
|
||||
# x1p = row['x1p']
|
||||
# x2p = row['x2p']
|
||||
# tp = row['tp']
|
||||
x1e = row['x1e']
|
||||
x2e = row['x2e']
|
||||
te = row['te']
|
||||
x1n = row['x1n']
|
||||
x2n = row['x2n']
|
||||
tn = row['tn']
|
||||
|
||||
ax.plot([x1b, x1e], [x2b, x2e], 'b', zs=[tb, te])
|
||||
ax.plot([x1e, x1n], [x2e, x2n], 'r', zs=[te, tn])
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
def main():
|
||||
# Parse command line arguments
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("--input", "-i", type=str, required=True,
|
||||
default="temp.csv", help="Filename of the csv file")
|
||||
parser.add_argument("--projection", "-p", type=str, required=False,
|
||||
default="2d", help="2d or 3d plot")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Plot data
|
||||
df = pd.read_csv(args.input)
|
||||
|
||||
if args.projection == "2d":
|
||||
plot_2d(df)
|
||||
elif args.projection == "3d":
|
||||
plot_3d(df)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user