19enum class Status { Optimal, PrimaryInfeasible, DualInfeasible };
27inline constexpr auto eps = 100 * std::numeric_limits<Scalar>::epsilon();
28inline constexpr auto inf = std::numeric_limits<Scalar>::infinity();
37inline std::tuple<Scalar, Scalar, Status> solve_impl(std::vector<HalfPlane> &);
57template<std::ranges::range R>
58inline std::tuple<Scalar, Scalar, Status> solve(Scalar cx, Scalar cy,
const R & rows)
59 requires(std::tuple_size_v<std::ranges::range_value_t<R>> == 3)
61 const Scalar sqnorm = cx * cx + cy * cy;
63 if (sqnorm < detail::eps) {
return {0, 0, Status::Optimal}; }
65 if (std::ranges::empty(rows)) {
return {0, 0, Status::DualInfeasible}; }
69 const Scalar cP = cy / sqnorm;
70 const Scalar sP = -cx / sqnorm;
73 std::vector<detail::HalfPlane> input;
74 input.reserve(std::ranges::size(rows));
75 for (
const auto [a, b, c] : rows) {
76 const double ra = cP * a + sP * b;
77 const double rb = -sP * a + cP * b;
79 const double norm = ra * ra + rb * rb;
81 if (norm > detail::eps && c < detail::inf) {
82 input.push_back(detail::HalfPlane{
93 for (
const auto & hp : input) { lambda = std::max(lambda, std::abs(hp.c)); }
95 for (
auto & hp : input) { hp.c /= lambda; }
97 const auto [xt_opt, yt_opt, status] = solve_impl(input);
100 const auto mul = [](
Scalar a,
Scalar b) {
return std::abs(a) > detail::eps ? a * b : 0; };
104 lambda * (mul(cP, xt_opt) - mul(sP, yt_opt)),
105 lambda * (mul(sP, xt_opt) + mul(cP, yt_opt)),
117using ValDer = std::tuple<Scalar, Scalar>;
120using ValSubDer = std::tuple<Scalar, Scalar, Scalar>;
123inline constexpr auto active_y_upper = [](
const auto & hp) {
return hp.active && hp.b > 0; };
124inline constexpr auto active_y_lower = [](
const auto & hp) {
return hp.active && hp.b < 0; };
127inline constexpr auto hp_to_yslope = [](
const HalfPlane & hp,
Scalar x) -> std::pair<Scalar, Scalar> {
129 const Scalar alpha = -hp.a / hp.b;
130 const Scalar beta = hp.c / hp.b;
133 if (std::abs(alpha) <= eps) {
return {beta, alpha}; }
135 return {alpha * x + beta, alpha};
141inline std::optional<Scalar> intersection(
const HalfPlane & hp1,
const HalfPlane & hp2)
143 const Scalar lhs = hp1.a * hp2.b - hp2.a * hp1.b;
144 if (std::abs(lhs) > eps) {
return (hp2.b * hp1.c - hp1.b * hp2.c) / lhs; }
149inline const auto gfun = [](
const std::ranges::range
auto & hps,
const Scalar x) -> ValSubDer {
150 ValSubDer ret{-inf, 0, 0};
151 for (
const auto & hp : hps | std::views::filter(active_y_lower)) {
152 const auto [yx, dyx] = hp_to_yslope(hp, x);
153 if (std::get<0>(ret) > yx + eps) {
155 }
else if (yx > std::get<0>(ret) + eps) {
156 ret = {yx, dyx, dyx};
158 std::get<1>(ret) = std::min(std::get<1>(ret), dyx);
159 std::get<2>(ret) = std::max(std::get<2>(ret), dyx);
166inline const auto hfun = [](
const std::ranges::range
auto & hps,
const Scalar x) -> ValSubDer {
167 ValSubDer ret{inf, 0, 0};
168 for (
const auto & hp : hps | std::views::filter(active_y_upper)) {
169 const auto [yx, dyx] = hp_to_yslope(hp, x);
170 if (std::get<0>(ret) + eps < yx) {
172 }
else if (yx + eps < std::get<0>(ret)) {
173 ret = {yx, dyx, dyx};
175 std::get<1>(ret) = std::min(std::get<1>(ret), dyx);
176 std::get<2>(ret) = std::max(std::get<2>(ret), dyx);
183inline std::optional<Scalar> find_candidate(std::vector<HalfPlane> & hps,
Scalar a,
Scalar b)
185 std::optional<typename std::vector<HalfPlane>::iterator> it1_store{};
188 std::priority_queue<Scalar> small, large;
189 const auto addnum = [&small, &large](
Scalar d) {
191 large.push(-small.top());
193 while (small.size() < large.size()) {
194 small.push(-large.top());
201 for (
auto it2 = hps.begin(); it2 != hps.end(); ++it2) {
202 if (!active_y_lower(*it2)) {
continue; }
204 if (!it1_store.has_value()) {
209 auto it1 = *it1_store;
211 const auto isec = intersection(*it1, *it2);
213 int8_t redundant = 0;
215 if (isec.has_value()) {
216 if (a + eps < *isec && *isec + eps < b) {
220 if (a + eps >= *isec) {
221 const auto [v1, dv1] = hp_to_yslope(*it1, a);
222 const auto [v2, dv2] = hp_to_yslope(*it2, a);
225 }
else if (v2 + eps < v1) {
228 redundant = dv1 <= dv2 ? 1 : 2;
230 }
else if (*isec + eps >= b) {
231 const auto [v1, dv1] = hp_to_yslope(*it1, b);
232 const auto [v2, dv2] = hp_to_yslope(*it2, b);
235 }
else if (v2 + eps < v1) {
238 redundant = dv1 >= dv2 ? 1 : 2;
243 redundant = hp_to_yslope(*it1, 0) < hp_to_yslope(*it2, 0) ? 1 : 2;
246 if (redundant == 1) {
249 }
else if (redundant == 2) {
258 for (
auto it2 = hps.begin(); it2 != hps.end(); ++it2) {
260 if (!active_y_upper(*it2)) {
continue; }
262 if (!it1_store.has_value()) {
267 auto it1 = *it1_store;
269 const auto isec = intersection(*it1, *it2);
271 int8_t redundant = 0;
273 if (isec.has_value()) {
274 if (a + eps < *isec && *isec + eps < b) {
278 if (a + eps >= *isec) {
279 const auto [v1, dv1] = hp_to_yslope(*it1, a);
280 const auto [v2, dv2] = hp_to_yslope(*it2, a);
283 }
else if (v2 + eps < v1) {
286 redundant = dv1 <= dv2 ? 2 : 1;
288 }
else if (*isec + eps >= b) {
289 const auto [v1, dv1] = hp_to_yslope(*it1, b);
290 const auto [v2, dv2] = hp_to_yslope(*it2, b);
293 }
else if (v2 + eps < v1) {
296 redundant = dv1 >= dv2 ? 2 : 1;
301 redundant = hp_to_yslope(*it1, 0) < hp_to_yslope(*it2, 0) ? 2 : 1;
304 if (redundant == 1) {
307 }
else if (redundant == 2) {
314 if (small.empty() && std::count_if(hps.cbegin(), hps.cend(), active_y_lower) == 1) {
315 const auto hp_l = *std::find_if(std::ranges::begin(hps), std::ranges::end(hps), active_y_lower);
316 for (
auto & hp_u : hps | std::views::filter(active_y_upper)) {
317 const auto isec = intersection(hp_l, hp_u);
318 if (isec.has_value() && a + eps < *isec && *isec + eps < b) { addnum(*isec); }
322 if (small.empty()) {
return {}; }
338inline uint8_t check(
const std::vector<HalfPlane> & hps,
const Scalar x)
340 const auto [gx, sg, Sg] = gfun(hps, x);
341 const auto [hx, sh, Sh] = hfun(hps, x);
343 if (gx <= hx + eps) {
353 if (sg > 0 && sg >= Sh) {
355 }
else if (Sg < 0 && Sg < sh) {
364 }
else if (sg > Sh) {
383inline std::tuple<Scalar, Scalar, Status> solve_impl(std::vector<HalfPlane> & hps)
387 hps | std::views::filter([](
const auto & hp) {
return hp.active && std::abs(hp.b) < eps && hp.a < 0; });
390 Scalar a = std::transform_reduce(
391 std::ranges::begin(hps_x_lower),
392 std::ranges::end(hps_x_lower),
394 [](
const Scalar avar,
const Scalar bvar) {
return std::max(avar, bvar); },
395 [](
const HalfPlane & hp) {
return hp.c / hp.a; });
399 hps | std::views::filter([](
const auto & hp) {
return hp.active && std::abs(hp.b) < eps && hp.a > 0; });
402 Scalar b = std::transform_reduce(
403 std::ranges::begin(hps_x_upper),
404 std::ranges::end(hps_x_upper),
406 [](
const Scalar avar,
const Scalar bvar) {
return std::min(avar, bvar); },
407 [](
const HalfPlane & hp) {
return hp.c / hp.a; });
410 for (
auto iter = hps.size(); iter > 0; --iter) {
411 const auto x = find_candidate(hps, a, b);
414 hps.erase(std::remove_if(hps.begin(), hps.end(), [](
const auto & hp) { return !hp.active; }), hps.end());
416 if (!x.has_value()) {
break; }
418 switch (check(hps, *x)) {
420 return {*x, std::get<0>(gfun(hps, *x)), Status::Optimal};
429 return {0, inf, Status::PrimaryInfeasible};
435 const auto [ga, sga, Sga] = gfun(hps, a);
436 const auto [ha, sha, Sha] = hfun(hps, a);
438 const auto [gb, sgb, Sgb] = gfun(hps, b);
439 const auto [hb, shb, Shb] = hfun(hps, b);
441 if (ga == ha && gb == hb && std::abs(ga) == inf && std::abs(gb) == inf) {
443 if (gfun(hps, 0) <= hfun(hps, 0)) {
444 return {0., 0., Status::DualInfeasible};
446 return {0., 0., Status::PrimaryInfeasible};
450 if (ga > ha && gb > hb) {
451 return {0, 0, Status::PrimaryInfeasible};
452 }
else if ((ga <= ha && gb > hb) || ga < gb) {
453 return {a, ga, ga == -inf ? Status::DualInfeasible : Status::Optimal};
455 return {b, gb, gb == -inf ? Status::DualInfeasible : Status::Optimal};
typename traits::man< M >::Scalar Scalar
Manifold scalar type.
Halfplane represented as inequality ax + by <= c.