Add a TimestampProblem class to solve the timestamp estimation problem
The timestamp estimation problem is actually really close to a
MPC. It can be phrased to have a nice piecewise quadratic cost,
linear constraints, and solves well with similar solvers. The cost
function chosen is to penalize the squared difference between the
measured and filtered offset, and the difference in the corresponding
times.
The solver chosen in NLOPT, NLOPT_LD_SLSQP, is designed for exactly this
problem type, and uses the gradient to produce a really accurate answer.
Multiple solutions attempted are accurate to full double precision with
gmp.
The next step is to replace the current timestamp estimation algorithm
with this new algorithm.
Change-Id: I7939a97cb845b17ea1fafdf6a09d380cb2cc6d8d
diff --git a/WORKSPACE b/WORKSPACE
index aabd057..acf815f 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -157,6 +157,16 @@
url = "https://www.frc971.org/Build-Dependencies/clang_6p0.tar.gz",
)
+http_archive(
+ name = "com_github_stevengj_nlopt",
+ build_file = "@//debian:nlopt.BUILD",
+ patch_args = ["-p1"],
+ patches = ["//debian:nlopt.patch"],
+ sha256 = "2d65815b21c30813499fe19c63947f7da56b10c0d4a459dce05417899b43e461",
+ strip_prefix = "nlopt-496be736b8b249273838b891f4c8ca3669551127",
+ url = "https://www.frc971.org/Build-Dependencies/nlopt-496be736b8b249273838b891f4c8ca3669551127.zip",
+)
+
local_repository(
name = "com_google_absl",
path = "third_party/abseil",
diff --git a/aos/network/BUILD b/aos/network/BUILD
index b430d90..bf2cb00 100644
--- a/aos/network/BUILD
+++ b/aos/network/BUILD
@@ -484,6 +484,7 @@
"//aos/events:simulated_event_loop",
"//aos/time",
"//third_party/gmp",
+ "@com_github_stevengj_nlopt//:nlopt",
"@org_tuxfamily_eigen//:eigen",
],
)
@@ -500,3 +501,17 @@
"//aos/testing:googletest",
],
)
+
+cc_test(
+ name = "multinode_timestamp_filter_test",
+ srcs = [
+ "multinode_timestamp_filter_test.cc",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = [
+ ":multinode_timestamp_filter",
+ ":timestamp_filter",
+ "//aos/testing:googletest",
+ "@com_github_stevengj_nlopt//:nlopt",
+ ],
+)
diff --git a/aos/network/multinode_timestamp_filter.cc b/aos/network/multinode_timestamp_filter.cc
index 4f5e0e5..92d729c 100644
--- a/aos/network/multinode_timestamp_filter.cc
+++ b/aos/network/multinode_timestamp_filter.cc
@@ -1,12 +1,15 @@
#include "aos/network/multinode_timestamp_filter.h"
+#include <chrono>
#include <map>
+#include "absl/strings/str_join.h"
#include "aos/configuration.h"
#include "aos/events/simulated_event_loop.h"
#include "aos/network/timestamp_filter.h"
#include "aos/time/time.h"
#include "glog/logging.h"
+#include "nlopt.h"
DEFINE_bool(timestamps_to_csv, false,
"If true, write all the time synchronization information to a set "
@@ -16,6 +19,7 @@
namespace aos {
namespace message_bridge {
namespace {
+namespace chrono = std::chrono;
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> ToDouble(
Eigen::Matrix<mpq_class, Eigen::Dynamic, Eigen::Dynamic> in) {
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> result =
@@ -56,6 +60,95 @@
}
} // namespace
+TimestampProblem::TimestampProblem(size_t count) {
+ CHECK_GT(count, 1u);
+ filters_.resize(count);
+ base_clock_.resize(count);
+}
+
+// TODO(austin): Adjust simulated event loop factory to take lists of points
+// on all clocks and interpolate.
+//
+// TODO(austin): Add linear inequality constraints too.
+//
+// TODO(austin): Add a rate of change constraint from the last sample. 1
+// ms/s. Figure out how to define it. Do this last. This lets us handle
+// constraints going away, and constraints close in time.
+
+std::vector<double> TimestampProblem::Solve() {
+ // TODO(austin): Add constraints for relevant segments.
+ const size_t n = filters_.size() - 1u;
+ // NLOPT_LD_MMA and NLOPT_LD_LBFGS are alternative solvers, but SLSQP is a
+ // better fit for the quadratic nature of this problem.
+ nlopt_opt opt = nlopt_create(NLOPT_LD_SLSQP, n);
+ nlopt_set_min_objective(opt, TimestampProblem::DoCost, this);
+
+ // Ask for really good. This is very quadratic, so it should be pretty
+ // precise.
+ nlopt_set_xtol_rel(opt, 1e-19);
+
+ count_ = 0;
+
+ std::vector<double> result(n, 0.0);
+ double minf = 0.0;
+ CHECK_GE(nlopt_optimize(opt, result.data(), &minf), 0);
+
+ if (VLOG_IS_ON(1)) {
+ std::vector<double> gradient(n, 0.0);
+ Cost(result.data(), gradient.data());
+
+ // High precision formatter for the gradient.
+ struct MyFormatter {
+ void operator()(std::string *out, double i) const {
+ std::stringstream ss;
+ ss << std::setprecision(12) << std::fixed << i;
+ out->append(ss.str());
+ }
+ };
+
+ VLOG(1) << std::setprecision(12) << std::fixed << "Found minimum at f("
+ << result[0] << ") -> " << minf << " grad ["
+ << absl::StrJoin(gradient, ", ", MyFormatter()) << "] after "
+ << count_ << " cycles.";
+ }
+ return result;
+}
+
+double TimestampProblem::Cost(const double *x, double *grad) {
+ ++count_;
+
+ if (grad != nullptr) {
+ for (size_t i = 0; i < filters_.size() - 1u; ++i) {
+ grad[i] = 0;
+ }
+
+ for (size_t i = 0u; i < filters_.size(); ++i) {
+ for (const struct FilterPair &filter : filters_[i]) {
+ if (i != solution_node_) {
+ grad[NodeToSolutionIndex(i)] += filter.filter->DCostDta(
+ base_clock_[i], get_t(x, i), base_clock_[filter.b_index],
+ get_t(x, filter.b_index));
+ }
+ if (filter.b_index != solution_node_) {
+ grad[NodeToSolutionIndex(filter.b_index)] += filter.filter->DCostDtb(
+ base_clock_[i], get_t(x, i), base_clock_[filter.b_index],
+ get_t(x, filter.b_index));
+ }
+ }
+ }
+ }
+
+ double cost = 0;
+ for (size_t i = 0u; i < filters_.size(); ++i) {
+ for (const struct FilterPair &filter : filters_[i]) {
+ cost += filter.filter->Cost(base_clock_[i], get_t(x, i),
+ base_clock_[filter.b_index],
+ get_t(x, filter.b_index));
+ }
+ }
+ return cost;
+}
+
void MultiNodeNoncausalOffsetEstimator::Start(
SimulatedEventLoopFactory *factory) {
for (std::pair<const std::tuple<const Node *, const Node *>,
@@ -116,8 +209,7 @@
}
for (std::pair<const std::tuple<const Node *, const Node *>,
- message_bridge::NoncausalOffsetEstimator> &filter :
- filters_) {
+ message_bridge::NoncausalOffsetEstimator> &filter : filters_) {
message_bridge::NoncausalOffsetEstimator *estimator = &filter.second;
const std::deque<
@@ -162,8 +254,7 @@
<< recovered_offset - estimator->fit().offset().count() << ")";
const aos::distributed_clock::time_point a0 =
- node_a_factory->ToDistributedClock(
- std::get<0>(a_timestamps[0]));
+ node_a_factory->ToDistributedClock(std::get<0>(a_timestamps[0]));
const aos::distributed_clock::time_point a1 =
node_a_factory->ToDistributedClock(std::get<0>(a_timestamps[1]));
@@ -197,16 +288,16 @@
const aos::distributed_clock::time_point b1 =
node_b_factory->ToDistributedClock(std::get<0>(b_timestamps[1]));
- VLOG(2) << node_b->name()->string_view() << " timestamps()[0] = "
- << std::get<0>(b_timestamps[0]) << " -> " << b0
- << " distributed -> " << node_a->name()->string_view() << " "
+ VLOG(2) << node_b->name()->string_view()
+ << " timestamps()[0] = " << std::get<0>(b_timestamps[0]) << " -> "
+ << b0 << " distributed -> " << node_a->name()->string_view() << " "
<< node_a_factory->FromDistributedClock(b0)
<< ((b0 <= event_loop_factory_->distributed_now())
? ""
: " After now, investigate");
- VLOG(2) << node_b->name()->string_view() << " timestamps()[1] = "
- << std::get<0>(b_timestamps[1]) << " -> " << b1
- << " distributed -> " << node_a->name()->string_view() << " "
+ VLOG(2) << node_b->name()->string_view()
+ << " timestamps()[1] = " << std::get<0>(b_timestamps[1]) << " -> "
+ << b1 << " distributed -> " << node_a->name()->string_view() << " "
<< node_a_factory->FromDistributedClock(b1)
<< ((event_loop_factory_->distributed_now() <= b1)
? ""
@@ -311,8 +402,7 @@
map_matrix_(i, node_b_index) = mpq_class(1);
// -> sample
- filter.second
- .set_slope_pointer(&slope_matrix_(i, node_a_index));
+ filter.second.set_slope_pointer(&slope_matrix_(i, node_a_index));
filter.second.set_offset_pointer(&offset_matrix_(i, 0));
valid_matrix_(i) = false;
diff --git a/aos/network/multinode_timestamp_filter.h b/aos/network/multinode_timestamp_filter.h
index d5fdadd..64e0125 100644
--- a/aos/network/multinode_timestamp_filter.h
+++ b/aos/network/multinode_timestamp_filter.h
@@ -14,6 +14,106 @@
namespace aos {
namespace message_bridge {
+// A condensed representation of the time estimation problem statement. This is
+// designed to not have the concept of a Node object, or anything, just
+// measurement pairs and indices.
+//
+// The problem is defined to be the squared error between the offset computed
+// using packets from one node to another, and the corresponding difference in
+// time the pair of node. This handles connections with data in 1 direction and
+// connections with data in both.
+//
+// All solved times are relative to the times in base_clock, and all math is
+// done such that large values of base_clock don't contribute to numerical
+// precision problems as long as the resulting time is small.
+//
+// To make this object reusable, it has a solution_node index which specifies
+// which of the nodes is treated as an input and not solved for.
+class TimestampProblem {
+ public:
+ TimestampProblem(size_t count);
+
+ // Sets node to fix time for and not solve for.
+ void set_solution_node(size_t solution_node) {
+ solution_node_ = solution_node;
+ }
+ size_t solution_node() const { return solution_node_; }
+
+ // Sets and gets the base time for a node.
+ void set_base_clock(size_t i, monotonic_clock::time_point t) {
+ base_clock_[i] = t;
+ }
+ monotonic_clock::time_point base_clock(size_t i) const {
+ return base_clock_[i];
+ }
+
+ // Adds a timestamp filter from a -> b.
+ // filter[a_index]->Offset(ta) + ta => t(b_index);
+ void add_filter(size_t a_index, const NoncausalTimestampFilter *filter,
+ size_t b_index) {
+ filters_[a_index].emplace_back(filter, b_index);
+ }
+
+ // Solves the optimization problem phrased and returns the offsets from the
+ // base clock for each node, excluding the solution node.
+ std::vector<double> Solve();
+
+ // Returns the squared error for all of the offsets.
+ // x is the offsets from the base_clock for every node (in order) except the
+ // solution node. It should be one element shorter than the number of nodes
+ // this problem was constructed with.
+ // grad (if non-nullptr) is the place to put the current gradient and needs to
+ // be the same length as x.
+ double Cost(const double *x, double *grad);
+
+ // Returns the time offset from base for a node.
+ double get_t(const double *x, size_t time_index) {
+ return time_index == solution_node_ ? 0.0
+ : x[NodeToSolutionIndex(time_index)];
+ }
+
+ private:
+ // Static trampoline for nlopt. n is the number of constraints, x is input
+ // solution to solve for, grad is the gradient to fill out (if not nullptr),
+ // and data is an untyped pointer to a TimestampProblem.
+ static double DoCost(unsigned n, const double *x, double *grad, void *data) {
+ CHECK_EQ(n + 1u,
+ reinterpret_cast<TimestampProblem *>(data)->filters_.size());
+ return reinterpret_cast<TimestampProblem *>(data)->Cost(x, grad);
+ }
+
+ // Converts from a node index to an index in the solution.
+ size_t NodeToSolutionIndex(size_t node_index) {
+ // The solver is going to provide us a matrix with solution_node_ removed.
+ // The indices of all nodes before solution_node_ are in the same spot, and
+ // the indices of the nodes after solution node are shifted over.
+ return node_index < solution_node_ ? node_index : (node_index - 1);
+ }
+
+ // Number of times Cost has been called for tracking.
+ int count_ = 0;
+
+ // The node to hold fixed when solving.
+ size_t solution_node_ = 0;
+
+ // The optimization problem is solved as base_clock + x to minimize numerical
+ // precision problems. This contains all the base times. The base time
+ // corresponding to solution_node is fixed and not solved.
+ std::vector<monotonic_clock::time_point> base_clock_;
+
+ // Filter and the node index it is referencing.
+ // filter->Offset(ta) + ta => t_(b_node);
+ struct FilterPair {
+ FilterPair(const NoncausalTimestampFilter *my_filter, size_t my_b_index)
+ : filter(my_filter), b_index(my_b_index) {}
+ const NoncausalTimestampFilter *const filter;
+ const size_t b_index;
+ };
+
+ // List of filters indexed by node.
+ std::vector<std::vector<FilterPair>> filters_;
+};
+
// Class to hold a NoncausalOffsetEstimator per pair of communicating nodes, and
// to estimate and set the overall time of all nodes.
class MultiNodeNoncausalOffsetEstimator {
@@ -37,11 +137,6 @@
// Captures the start time.
void Start(SimulatedEventLoopFactory *factory);
- // Returns [ta; tb; ...] = tuple[0] * t + tuple[1]
- std::tuple<Eigen::Matrix<double, Eigen::Dynamic, 1>,
- Eigen::Matrix<double, Eigen::Dynamic, 1>>
- SolveOffsets();
-
// Returns the number of nodes.
size_t nodes_count() const {
return !configuration::MultiNode(logged_configuration())
@@ -80,6 +175,11 @@
void UpdateOffsets();
private:
+ // Returns [ta; tb; ...] = tuple[0] * t + tuple[1]
+ std::tuple<Eigen::Matrix<double, Eigen::Dynamic, 1>,
+ Eigen::Matrix<double, Eigen::Dynamic, 1>>
+ SolveOffsets();
+
SimulatedEventLoopFactory *event_loop_factory_;
const Configuration *logged_configuration_;
diff --git a/aos/network/multinode_timestamp_filter_test.cc b/aos/network/multinode_timestamp_filter_test.cc
new file mode 100644
index 0000000..28d0183
--- /dev/null
+++ b/aos/network/multinode_timestamp_filter_test.cc
@@ -0,0 +1,138 @@
+#include "aos/network/timestamp_filter.h"
+
+#include <chrono>
+
+#include <nlopt.h>
+#include "aos/configuration.h"
+#include "aos/json_to_flatbuffer.h"
+#include "aos/macros.h"
+#include "aos/network/multinode_timestamp_filter.h"
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace message_bridge {
+namespace testing {
+
+namespace chrono = std::chrono;
+using aos::monotonic_clock;
+
+mpq_class SolveExact(Line la, Line lb, monotonic_clock::time_point ta) {
+ mpq_class ma = la.mpq_slope();
+ mpq_class ba = la.mpq_offset();
+ mpq_class mb = lb.mpq_slope();
+ mpq_class bb = lb.mpq_offset();
+ // The min of a quadratic is when the slope is 0. Solve algebraically.
+ //
+ // 2.0 * (tb - (1 + d.ma) * ta - d.ba) + 2.0 * ((1.0 + d.mb) * tb - ta +
+ // d.bb) * (1.0 + d.mb) = 0;
+ //
+ // tb - (1 + d.ma) * ta - d.ba + ((1.0 + d.mb) *
+ // tb - ta + d.bb) * (1.0 + d.mb) = 0;
+ //
+ // tb - (1 + d.ma) * ta - d.ba + (1 + d.mb) (1 + d.mb) * tb - (1 + d.mb) ta
+ // + (1 + d.mb) d.bb = 0;
+ //
+ // (1 + (1 + d.mb) (1 + d.mb)) tb - ((1 + d.ma) + (1
+ // + d.mb)) * ta - d.ba + (1 + d.mb) d.bb = 0;
+ //
+ // tb = (((1 + d.ma) + (1 + d.mb)) * ta + d.ba - (1 + d.mb) d.bb) / (1 + (1
+ // + d.mb) (1 + d.mb))
+
+ mpq_class mpq_ta(message_bridge::FromInt64(ta.time_since_epoch().count()));
+ mpq_class one(1);
+ mpq_class mpq_tb =
+ (((one + ma) + (one + mb)) * mpq_ta + ba - (one + mb) * bb) /
+ (one + (one + mb) * (one + mb));
+ mpq_tb.canonicalize();
+ return mpq_tb;
+}
+
+// Tests that an infinite precision solution matches our numeric solver solution
+// for a couple of simple problems.
+TEST(TimestampProblemTest, Solve) {
+ const monotonic_clock::time_point e = monotonic_clock::epoch();
+ const monotonic_clock::time_point ta = e + chrono::milliseconds(500);
+
+ NoncausalTimestampFilter a;
+ // Delivered at e, sent at e + offset.
+ // Sent at 1.001, received at 0
+ a.Sample(e, chrono::milliseconds(1001));
+ a.Sample(e + chrono::milliseconds(1000), chrono::milliseconds(1001));
+ a.Sample(e + chrono::milliseconds(3000), chrono::milliseconds(999));
+
+ NoncausalTimestampFilter b;
+ // Sent at 0.001s, received at 1.000s
+ b.Sample(e + chrono::milliseconds(1000), -chrono::milliseconds(999));
+ b.Sample(e + chrono::milliseconds(2000), -chrono::milliseconds(1000));
+ b.Sample(e + chrono::milliseconds(4000), -chrono::milliseconds(1002));
+
+ TimestampProblem problem(2);
+ problem.set_base_clock(0, ta);
+ problem.set_base_clock(1, e);
+ problem.set_solution_node(0);
+ problem.add_filter(0, &a, 1);
+ problem.add_filter(1, &b, 0);
+
+ // Solve the problem with infinate precision as a verification and compare the
+ // result.
+ {
+ const std::vector<double> result = problem.Solve();
+
+ mpq_class tb_mpq =
+ SolveExact(a.FitLine(), b.FitLine(), problem.base_clock(0));
+ EXPECT_EQ(tb_mpq.get_d(), result[0])
+ << std::setprecision(12) << std::fixed << " Expected " << tb_mpq.get_d()
+ << " " << tb_mpq << " got " << result[0];
+ }
+
+ // Solve some other timestamps for grins.
+ {
+ problem.set_base_clock(0, e + chrono::milliseconds(500));
+ std::vector<double> result = problem.Solve();
+
+ mpq_class tb_mpq =
+ SolveExact(a.FitLine(), b.FitLine(), problem.base_clock(0));
+
+ EXPECT_EQ(tb_mpq.get_d(), result[0])
+ << std::setprecision(12) << std::fixed << " Expected " << tb_mpq.get_d()
+ << " " << tb_mpq << " got " << result[0];
+ }
+
+ // Now do the second line segment.
+ {
+ NoncausalTimestampFilter a;
+ a.Sample(e + chrono::milliseconds(1000), chrono::milliseconds(1001));
+ a.Sample(e + chrono::milliseconds(3000), chrono::milliseconds(999));
+
+ NoncausalTimestampFilter b;
+ b.Sample(e + chrono::milliseconds(2000), -chrono::milliseconds(1000));
+ b.Sample(e + chrono::milliseconds(4000), -chrono::milliseconds(1002));
+ {
+ problem.set_base_clock(0, e + chrono::milliseconds(1500));
+ const std::vector<double> result = problem.Solve();
+
+ mpq_class tb_mpq =
+ SolveExact(a.FitLine(), b.FitLine(), problem.base_clock(0));
+
+ EXPECT_NEAR(tb_mpq.get_d(), result[0], 1e-6)
+ << std::setprecision(12) << std::fixed << " Expected "
+ << tb_mpq.get_d() << " " << tb_mpq << " got " << result[0];
+ }
+
+ {
+ problem.set_base_clock(0, e + chrono::milliseconds(1600));
+ const std::vector<double> result = problem.Solve();
+
+ mpq_class tb_mpq =
+ SolveExact(a.FitLine(), b.FitLine(), problem.base_clock(0));
+
+ EXPECT_EQ(tb_mpq.get_d(), result[0])
+ << std::setprecision(12) << std::fixed << " Expected "
+ << tb_mpq.get_d() << " " << tb_mpq << " got " << result[0];
+ }
+ }
+}
+
+} // namespace testing
+} // namespace message_bridge
+} // namespace aos
diff --git a/debian/nlopt.BUILD b/debian/nlopt.BUILD
new file mode 100644
index 0000000..4648561
--- /dev/null
+++ b/debian/nlopt.BUILD
@@ -0,0 +1,148 @@
+genrule(
+ name = "empty_nlopt_config",
+ outs = ["build/nlopt_config.h"],
+ cmd = "echo > $(OUTS)",
+)
+
+cc_library(
+ name = "nlopt",
+ srcs = [
+ "build/nlopt_config.h",
+ "src/algs/ags/ags.cc",
+ "src/algs/ags/ags.h",
+ "src/algs/ags/data_types.hpp",
+ "src/algs/ags/evolvent.cc",
+ "src/algs/ags/evolvent.hpp",
+ "src/algs/ags/local_optimizer.cc",
+ "src/algs/ags/local_optimizer.hpp",
+ "src/algs/ags/solver.cc",
+ "src/algs/ags/solver.hpp",
+ "src/algs/auglag/auglag.c",
+ "src/algs/auglag/auglag.h",
+ "src/algs/bobyqa/bobyqa.c",
+ "src/algs/bobyqa/bobyqa.h",
+ "src/algs/cdirect/cdirect.c",
+ "src/algs/cdirect/cdirect.h",
+ "src/algs/cdirect/hybrid.c",
+ "src/algs/cobyla/cobyla.c",
+ "src/algs/cobyla/cobyla.h",
+ "src/algs/crs/crs.c",
+ "src/algs/crs/crs.h",
+ "src/algs/direct/DIRect.c",
+ "src/algs/direct/DIRserial.c",
+ "src/algs/direct/DIRsubrout.c",
+ "src/algs/direct/direct.h",
+ "src/algs/direct/direct-internal.h",
+ "src/algs/direct/direct_wrap.c",
+ "src/algs/esch/esch.c",
+ "src/algs/esch/esch.h",
+ "src/algs/isres/isres.c",
+ "src/algs/isres/isres.h",
+ "src/algs/luksan/luksan.h",
+ "src/algs/luksan/mssubs.c",
+ "src/algs/luksan/plip.c",
+ "src/algs/luksan/plis.c",
+ "src/algs/luksan/pnet.c",
+ "src/algs/luksan/pssubs.c",
+ "src/algs/mlsl/mlsl.c",
+ "src/algs/mlsl/mlsl.h",
+ "src/algs/mma/ccsa_quadratic.c",
+ "src/algs/mma/mma.c",
+ "src/algs/mma/mma.h",
+ "src/algs/neldermead/neldermead.h",
+ "src/algs/neldermead/nldrmd.c",
+ "src/algs/neldermead/sbplx.c",
+ "src/algs/newuoa/newuoa.c",
+ "src/algs/newuoa/newuoa.h",
+ "src/algs/praxis/praxis.c",
+ "src/algs/praxis/praxis.h",
+ "src/algs/slsqp/slsqp.c",
+ "src/algs/slsqp/slsqp.h",
+ "src/algs/stogo/global.cc",
+ "src/algs/stogo/global.h",
+ "src/algs/stogo/linalg.cc",
+ "src/algs/stogo/linalg.h",
+ "src/algs/stogo/local.cc",
+ "src/algs/stogo/local.h",
+ "src/algs/stogo/stogo.cc",
+ "src/algs/stogo/stogo.h",
+ "src/algs/stogo/stogo_config.h",
+ "src/algs/stogo/tools.cc",
+ "src/algs/stogo/tools.h",
+ "src/api/deprecated.c",
+ "src/api/f77api.c",
+ "src/api/f77funcs.h",
+ "src/api/f77funcs_.h",
+ "src/api/general.c",
+ "src/api/nlopt-internal.h",
+ "src/api/optimize.c",
+ "src/api/options.c",
+ "src/util/mt19937ar.c",
+ "src/util/nlopt-util.h",
+ "src/util/qsort_r.c",
+ "src/util/redblack.c",
+ "src/util/redblack.h",
+ "src/util/rescale.c",
+ "src/util/soboldata.h",
+ "src/util/sobolseq.c",
+ "src/util/stop.c",
+ "src/util/timer.c",
+ ],
+ hdrs = ["src/api/nlopt.h"],
+ copts = [
+ "-Wno-format-nonliteral",
+ "-DBUGFIX_VERSION=0",
+ "-DHAVE_COPYSIGN",
+ "-DHAVE_DLFCN_H",
+ "-DHAVE_FPCLASSIFY",
+ "-DHAVE_GETOPT_H",
+ "-DHAVE_GETOPT",
+ "-DHAVE_GETPID",
+ "-DHAVE_GETTID_SYSCALL=1",
+ "-DHAVE_GETTIMEOFDAY",
+ "-DHAVE_INTTYPES_H",
+ "-DHAVE_ISINF",
+ "-DHAVE_ISNAN",
+ "-DHAVE_MEMORY_H",
+ "-DHAVE_QSORT_R",
+ "-DHAVE_STDINT_H",
+ "-DHAVE_STDLIB_H",
+ "-DHAVE_STRINGS_H",
+ "-DHAVE_STRING_H",
+ "-DHAVE_SYS_STAT_H",
+ "-DHAVE_SYS_TYPES_H",
+ "-DHAVE_SYS_TIME_H",
+ "-DHAVE_TIME",
+ "-DHAVE_UINT32_T",
+ "-DHAVE_UNISTD_H",
+ "-DMAJOR_VERSION=2",
+ "-DMINOR_VERSION=7",
+ "-DTHREADLOCAL=__thread",
+ "-DTIME_WITH_SYS_TIME",
+ "-DNLOPT_CXX11",
+ "-DNLOPT_CXX",
+ ],
+ includes = [
+ "build",
+ "src/algs/ags",
+ "src/algs/auglag",
+ "src/algs/bobyqa",
+ "src/algs/cdirect",
+ "src/algs/cobyla",
+ "src/algs/crs",
+ "src/algs/direct",
+ "src/algs/esch",
+ "src/algs/isres",
+ "src/algs/luksan",
+ "src/algs/mlsl",
+ "src/algs/mma",
+ "src/algs/neldermead",
+ "src/algs/newuoa",
+ "src/algs/praxis",
+ "src/algs/slsqp",
+ "src/algs/stogo",
+ "src/api",
+ "src/util",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/debian/nlopt.patch b/debian/nlopt.patch
new file mode 100644
index 0000000..9b60608
--- /dev/null
+++ b/debian/nlopt.patch
@@ -0,0 +1,13 @@
+diff --git a/src/algs/ags/solver.cc b/src/algs/ags/solver.cc
+index 63b8760..b837112 100644
+--- a/src/algs/ags/solver.cc
++++ b/src/algs/ags/solver.cc
+@@ -38,6 +38,8 @@ namespace
+ mRightBound = rightBound;
+ }
+
++ virtual ~ProblemInternal() {}
++
+ double Calculate(const double* y, int fNumber) const
+ {
+ return mFunctions[fNumber](y);