-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathbind_problem.cpp
More file actions
156 lines (141 loc) · 6.15 KB
/
Copy pathbind_problem.cpp
File metadata and controls
156 lines (141 loc) · 6.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright (c) Sleipnir contributors
#include <nanobind/nanobind.h>
#include <nanobind/stl/function.h>
#include <chrono>
#include <format>
#include <functional>
#include <string>
#include <utility>
#include <sleipnir/autodiff/variable.hpp>
#include <sleipnir/autodiff/variable_matrix.hpp>
#include <sleipnir/optimization/problem.hpp>
#include <sleipnir/optimization/solver/iteration_info.hpp>
#include <sleipnir/optimization/solver/options.hpp>
#include "docstrings.hpp"
#include "for_each_type.hpp"
namespace nb = nanobind;
namespace slp {
void bind_problem(nb::class_<Problem<double>>& cls) {
using namespace nb::literals;
cls.def(
"__init__",
[](Problem<double>* self) {
new (self) Problem<double>();
// Make Python signals (e.g., SIGINT from Ctrl-C) abort the solve
self->add_persistent_callback([](const IterationInfo<double>&) -> bool {
if (PyErr_CheckSignals() != 0) {
throw nb::python_error();
}
return false;
});
},
DOC(slp, Problem, Problem));
cls.def("decision_variable",
nb::overload_cast<>(&Problem<double>::decision_variable),
DOC(slp, Problem, decision_variable));
cls.def("decision_variable",
nb::overload_cast<int, int>(&Problem<double>::decision_variable),
"rows"_a, "cols"_a = 1, DOC(slp, Problem, decision_variable, 2));
cls.def("symmetric_decision_variable",
&Problem<double>::symmetric_decision_variable, "rows"_a,
DOC(slp, Problem, symmetric_decision_variable));
for_each_type<double, const Variable<double>&, const VariableMatrix<double>&>(
[&]<typename T> {
cls.def(
"minimize",
[](Problem<double>& self, T cost) { self.minimize(cost); },
"cost"_a, DOC(slp, Problem, minimize));
cls.def(
"maximize",
[](Problem<double>& self, T objective) {
self.maximize(objective);
},
"objective"_a, DOC(slp, Problem, maximize));
});
cls.def("subject_to",
nb::overload_cast<const EqualityConstraints<double>&>(
&Problem<double>::subject_to),
"constraint"_a, DOC(slp, Problem, subject_to));
cls.def("subject_to",
nb::overload_cast<const InequalityConstraints<double>&>(
&Problem<double>::subject_to),
"constraint"_a, DOC(slp, Problem, subject_to, 3));
cls.def("cost_function_type", &Problem<double>::cost_function_type,
DOC(slp, Problem, cost_function_type));
cls.def("equality_constraint_type",
&Problem<double>::equality_constraint_type,
DOC(slp, Problem, equality_constraint_type));
cls.def("inequality_constraint_type",
&Problem<double>::inequality_constraint_type,
DOC(slp, Problem, inequality_constraint_type));
cls.def(
"solve",
[](Problem<double>& self, const nb::kwargs& kwargs) {
Options options;
bool spy = false;
for (auto [key, value] : kwargs) {
// XXX: The keyword arguments are manually copied from the struct
// members in include/sleipnir/optimization/solver/options.hpp.
//
// C++'s Problem<double>::solve() takes an Options object instead of
// keyword arguments, so there's no compile-time checking that the
// arguments match.
auto key_str = nb::cast<std::string>(key);
if (key_str == "tolerance") {
options.tolerance = nb::cast<double>(value);
} else if (key_str == "max_iterations") {
options.max_iterations = nb::cast<int>(value);
} else if (key_str == "timeout") {
options.timeout =
std::chrono::duration<double>{nb::cast<double>(value)};
} else if (key_str == "feasible_ipm") {
options.feasible_ipm = nb::cast<bool>(value);
} else if (key_str == "diagnostics") {
options.diagnostics = nb::cast<bool>(value);
} else if (key_str == "spy") {
spy = nb::cast<bool>(value);
} else {
throw nb::key_error(
std::format("Invalid keyword argument: {}", key_str).c_str());
}
}
return self.solve(options, spy);
},
// XXX: The keyword argument docs are manually copied from the struct
// member docs in include/sleipnir/optimization/solver/options.hpp.
//
// C++'s Problem<double>::solve() takes an Options object instead of
// keyword arguments, so pybind11_mkdoc generates the wrong docs.
R"doc(Solves the optimization problem. The solution will be stored in the
original variables used to construct the problem.
Args:
tolerance: The solver will stop once the error is below this tolerance.
(default: 1e-8)
max_iterations: The maximum number of solver iterations before returning a
solution. (default: 5000)
timeout: The maximum elapsed wall clock time before returning a solution.
(default: infinity)
feasible_ipm: Enables the feasible interior-point method.
When the inequality constraints are all feasible, step sizes are reduced
when necessary to prevent them becoming infeasible again. This is useful
when parts of the problem are ill-conditioned in infeasible regions
(e.g., square root of a negative value). This can slow or prevent
progress toward a solution though, so only enable it if necessary.
(default: False)
diagnostics: Enables diagnostic output.
See https://sleipnirgroup.github.io/Sleipnir/md_usage.html#output for
more information. (default: False)
spy: Enables writing sparsity patterns of H, Aₑ, and Aᵢ to files named
H.spy, A_e.spy, and A_i.spy respectively during solve. Use tools/spy.py
to plot them. (default: False))doc");
cls.def(
"add_callback",
[](Problem<double>& self,
std::function<bool(const IterationInfo<double>& info)> callback) {
self.add_callback(std::move(callback));
},
"callback"_a, DOC(slp, Problem, add_callback, 2));
cls.def("clear_callbacks", &Problem<double>::clear_callbacks,
DOC(slp, Problem, clear_callbacks));
}
} // namespace slp