1 /* Copyright (c) 1997-2021
2 Ewgenij Gawrilow, Michael Joswig, and the polymake team
3 Technische Universität Berlin, Germany
4 https://polymake.org
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
9 later version: http://www.gnu.org/licenses/gpl.txt.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15 --------------------------------------------------------------------------------
16 */
17
18 #pragma once
19
20 #include <cstddef> // needed for gcc 4.9, see http://gcc.gnu.org/gcc-4.9/porting_to.html
21
22 #if defined(__APPLE__)
23 #pragma clang diagnostic push
24 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
25 #endif
26
27 #include <gmpxx.h> //for mpz/mpq-handling
28
29 #if defined(__APPLE__)
30 #pragma clang diagnostic pop
31 #endif
32
33 #include "polymake/ListMatrix.h"
34 #include "polymake/internal/gmpxx_traits.h"
35 #include "polymake/polytope/ppl_interface.h"
36 #include "polymake/common/lattice_tools.h"
37 #include "polymake/linalg.h"
38 #include "polymake/hash_set"
39 #include "polymake/polytope/compress_incidence.h"
40
41 #if defined(__clang__)
42 #pragma clang diagnostic push
43 #pragma clang diagnostic ignored "-Wshadow"
44 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
45 #elif defined(__GNUC__)
46 #pragma GCC diagnostic push
47 #pragma GCC diagnostic ignored "-Wshadow"
48 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
49 #pragma GCC diagnostic ignored "-Wconversion"
50 #endif
51
52 #include <ppl.hh>
53
54 #if defined(__clang__)
55 #pragma clang diagnostic pop
56 #elif defined(__GNUC__)
57 #pragma GCC diagnostic pop
58 #endif
59
60 #include <fenv.h>
61
62 namespace PPL = Parma_Polyhedra_Library;
63
64 namespace polymake { namespace polytope { namespace ppl_interface {
65
66 //! PPL tends to modify the floating point operation mode, in particular, the rounding direction.
67 //! This seems to happen during initial load of the ppl library,
68 //! therefore the mode preferred by PPL is captured in a singleton living in this shared object (polytope).
69 //! Since this shared object depends on libppl, its global constructors will always be executed after libppl is loaded.
70
71 class fp_mode_setter
72 {
73 public:
fp_mode_setter()74 fp_mode_setter()
75 {
76 fesetround(captured.mode);
77 }
78
~fp_mode_setter()79 ~fp_mode_setter()
80 {
81 fesetround(FE_TONEAREST);
82 }
83
84 private:
85 class init
86 {
87 public:
init()88 init()
89 : version(PPL::version_major()) // ensure libppl is really loaded
90 , mode(fegetround())
91 {
92 fesetround(FE_TONEAREST);
93 }
94
95 const int version;
96 const int mode;
97 };
98
99 static init captured;
100 };
101
102 namespace {
103
104 // Constructs an (integral) mpz-vector by multiplying with lcm of denominators.
105 // Note: this is different from 'primitive', e.g. (2/3,4/3)->(2,4), not (1,2)
106 template <typename Scalar>
convert_to_mpz(const Vector<Scalar> & v,const Integer & denom)107 std::vector<mpz_class> convert_to_mpz(const Vector<Scalar>& v, const Integer& denom)
108 {
109 Vector<Integer> v_multi(denom*v); //This cast works since denom*v is integral by construction!!
110
111 std::vector<mpz_class> mpz_vec(v.dim());
112 for (Int i = 0; i < v.dim(); ++i) {
113 mpz_vec[i] = mpz_class(v_multi[i].get_rep());
114 }
115 return mpz_vec;
116 }
117
118 // Translates a ppl generator into a (homogenized) polymake vector,
119 // depending on its type (point, ray, line).
120 template <typename Scalar>
ppl_gen_to_vec(const PPL::Generator & gen,const bool isCone)121 Vector<Scalar> ppl_gen_to_vec(const PPL::Generator& gen, const bool isCone)
122 {
123 const Int dim = gen.space_dimension()+1;
124 Vector<Scalar> vec(dim);
125
126 for (Int i = 1; i < dim; ++i)
127 vec[i] = Integer(gen.coefficient(PPL::Variable(i-1)));
128
129 if (gen.is_point()) { // in case of isCone, generators are rays or lines except for (0,...,0)
130
131 // gen.divisor only works for PPL-(closure-)points
132 vec /= Integer(gen.divisor());
133 vec[0] = 1;
134 } else {
135 assert(gen.is_ray() || gen.is_line());
136 }
137
138 return vec;
139 }
140
141 // Translates a ppl constraint (n entries, inhomogeneous term as an attribute)
142 // into a polymake vector.
143 template <typename Scalar>
ppl_constraint_to_vec(const PPL::Constraint & cons,const bool isCone)144 Vector<Scalar> ppl_constraint_to_vec(const PPL::Constraint& cons, const bool isCone)
145 {
146 const Int dim = cons.space_dimension()+1;
147 Vector<Scalar> vec(dim);
148 vec[0] = cons.inhomogeneous_term(); // will be overwritten if isCone = true
149 for (Int i = 1; i < dim; ++i)
150 vec[i] = Integer(cons.coefficient(PPL::Variable(i-1)));
151
152 return vec;
153 }
154
155
156 template <typename Scalar>
construct_ppl_polyhedron_H(const Matrix<Scalar> & Inequalities,const Matrix<Scalar> & Equations,const bool isCone)157 PPL::C_Polyhedron construct_ppl_polyhedron_H(const Matrix<Scalar>& Inequalities, const Matrix<Scalar>& Equations, const bool isCone)
158 {
159 /* Linear expressions in H representations store the inhomogeneous term at index 0,
160 * and the variables' coefficients at indices 1, 2, ..., space_dim.
161 */
162
163 PPL::Constraint_System cs;
164
165 const Int dim = std::max(Inequalities.cols(), Equations.cols())-1;
166
167 cs.set_space_dimension(dim);
168
169 // insert inequalities
170 for (auto row_it = entire(rows(Inequalities)); !row_it.at_end(); ++row_it) {
171 Integer lcm_of_row_denom(lcm(denominators(*row_it)));
172 std::vector<mpz_class> coefficients = convert_to_mpz<Scalar>(*row_it, lcm_of_row_denom);
173 // PPL variables have indices 0, 1, ..., space_dim-1.
174 PPL::Linear_Expression e;
175 for (Int j = dim; j >= 1; --j) {
176 e += coefficients[j] * PPL::Variable(j-1);
177 }
178 e += coefficients[0];
179
180 cs.insert(e >= 0);
181 }
182
183 // insert equations
184 for (auto row_it = entire(rows(Equations)); !row_it.at_end(); ++row_it) {
185 Integer lcm_of_row_denom(lcm(denominators(*row_it)));
186 std::vector<mpz_class> coefficients = convert_to_mpz<Scalar>(*row_it, lcm_of_row_denom);
187 // PPL variables have indices 0, 1, ..., space_dim-1.
188 PPL::Linear_Expression e;
189 for (Int j = dim; j >= 1; --j) {
190 e += coefficients[j] * PPL::Variable(j-1);
191 }
192 e += coefficients[0];
193
194 cs.insert(e == 0);
195 }
196
197 PPL::C_Polyhedron ppl_poly(cs);
198 return ppl_poly;
199 }
200
201
202 template <typename Scalar>
construct_ppl_polyhedron_V(const Matrix<Scalar> & Points,const Matrix<Scalar> & Lineality,const bool isCone)203 PPL::C_Polyhedron construct_ppl_polyhedron_V(const Matrix<Scalar>& Points, const Matrix<Scalar>& Lineality, const bool isCone)
204 {
205 // The V representation
206 PPL::Generator_System gs;
207 const Int dim = std::max(Points.cols(), Lineality.cols())-1; // necessary if only one matrix has entries
208
209 gs.set_space_dimension(dim);
210
211 /* Cones need an additional point (0,...,0) in ppl.
212 * Furthermore, the cone is translated into a polytope.
213 */
214 if (isCone) {
215 PPL::Generator v = PPL::point(0*PPL::Variable(dim-1)); //origin
216 gs.insert(v);
217 }
218
219 // insert points/rays
220 for (auto row_it : attach_selector(rows(Points), operations::non_zero())) {
221 Integer lcm_of_row_denom(lcm(denominators(row_it)));
222 std::vector<mpz_class> coefficients = convert_to_mpz<Scalar>(row_it, lcm_of_row_denom);
223 // PPL variables have indices 0, 1, ..., space_dim-1.
224 PPL::Linear_Expression e;
225 for (Int j = dim; j >= 1; --j) {
226 e += coefficients[j] * PPL::Variable(j-1);
227 }
228
229 if (coefficients[0] != 0) {
230 PPL::Generator v = PPL::point(e, lcm_of_row_denom.gmp() ); // v is a point
231 gs.insert(v);
232 } else {
233 PPL::Generator v = PPL::ray(e); // v is a ray
234 gs.insert(v);
235 }
236 }
237
238 // insert linealities
239 for (auto row_it : attach_selector(rows(Lineality), operations::non_zero())) {
240 Integer lcm_of_row_denom(lcm(denominators(row_it)));
241 std::vector<mpz_class> coefficients = convert_to_mpz<Scalar>(row_it, lcm_of_row_denom);
242 // PPL variables have indices 0, 1, ..., space_dim-1.
243 PPL::Linear_Expression e;
244 for (Int j = dim; j >= 1; --j) {
245 e += coefficients[j] * PPL::Variable(j-1);
246 }
247 PPL::Generator l = line(e);
248 gs.insert(l);
249 }
250
251 PPL::C_Polyhedron ppl_poly(gs);
252 return ppl_poly;
253 }
254
255 } // End of namespace
256
257
258 /* FIXME (1): Don't know how to handle Float and Rational
259 simultaneously w.r.t. defining the optimal value */
260
261 template <typename Scalar>
262 convex_hull_result<Scalar>
enumerate_facets(const Matrix<Scalar> & Points,const Matrix<Scalar> & Lineality,const bool isCone)263 ConvexHullSolver<Scalar>::enumerate_facets(const Matrix<Scalar>& Points, const Matrix<Scalar>& Lineality, const bool isCone) const
264 {
265 const Int num_columns = std::max(Points.cols(), Lineality.cols());
266
267 PPL::C_Polyhedron polyhedron = construct_ppl_polyhedron_V(Points, Lineality, isCone);
268 Set<Int> far_face(far_points(Points));
269
270 PPL::Constraint_System cs = polyhedron.minimized_constraints();
271 ListMatrix< Vector<Scalar> > facet_list(0, num_columns);
272 ListMatrix< Vector<Scalar> > affine_hull_list(0, num_columns);
273
274 const auto triv_ineq=unit_vector<Scalar>(num_columns, 0);
275
276 for (PPL::Constraint_System::const_iterator csi = cs.begin(); csi != cs.end(); ++csi) {
277 const PPL::Constraint& c = *csi;
278 Vector<Scalar> row = ppl_constraint_to_vec<Scalar>(c, isCone);
279 if (!(isCone && row == triv_ineq )) {
280 if (c.is_inequality()) {
281 // TODO: std::move(row) when move constructors implemented for vector classes
282 facet_list /= row;
283 } else {
284 assert(c.is_equality());
285 // TODO: std::move(row) when move constructors implemented for vector classes
286 affine_hull_list /= row;
287 }
288 }
289 }
290
291 // ppl seems to compute the far face inequality (shown in cs.ascii_dump())
292 // but the iterator above skips it...
293 // So we use the following to determine whether it is needed and add it manually:
294
295 // We use the rank of the far-face rays to determine
296 // whether we need to add the trivial inequality as facet.
297 // The case that p is just a point is also covered by this!
298 if (!isCone && rank(Points.minor(far_face,All)/Lineality)+1 == num_columns - affine_hull_list.rows()) {
299 facet_list /= triv_ineq;
300 }
301
302 return { Matrix<Scalar>(facet_list), Matrix<Scalar>(affine_hull_list) };
303 }
304
305
306 template <typename Scalar>
307 convex_hull_result<Scalar>
enumerate_vertices(const Matrix<Scalar> & Inequalities,const Matrix<Scalar> & Equations,const bool isCone)308 ConvexHullSolver<Scalar>::enumerate_vertices(const Matrix<Scalar>& Inequalities, const Matrix<Scalar>& Equations, const bool isCone) const
309 {
310 const Int num_columns = std::max(Inequalities.cols(), Equations.cols());
311 // an empty exterior description defines the empty (infeasible) polytope
312 // (ppl would return the whole space)
313 // for cones this is the full space and correctly handled later on
314 if (!isCone && Inequalities.rows() + Equations.rows() == 0)
315 return { Matrix<Scalar>(0, num_columns), Matrix<Scalar>(0, num_columns) };
316
317 PPL::C_Polyhedron polyhedron = construct_ppl_polyhedron_H(Inequalities, Equations, isCone);
318 PPL::Generator_System gs = polyhedron.minimized_generators();
319 ListMatrix<Vector<Scalar>> vertex_list(0,num_columns);
320 ListMatrix<Vector<Scalar>> lin_space_list(0,num_columns);
321
322 const auto cone_origin=unit_vector<Scalar>(num_columns, 0);
323
324 for (PPL::Generator_System::const_iterator gsi = gs.begin(); gsi != gs.end(); ++gsi) {
325 const PPL::Generator& g = *gsi;
326 Vector<Scalar> row = ppl_gen_to_vec<Scalar>(g, isCone);
327 if (!(isCone && row == cone_origin)) {
328 if (g.is_point() || g.is_ray()) {
329 // TODO: std::move(row) when move constructors implemented for vector classes
330 vertex_list /= row;
331 } else {
332 assert(g.is_line());
333 // TODO: std::move(row) when move constructors implemented for vector classes
334 lin_space_list /= row;
335 }
336 }
337 }
338
339 return { Matrix<Scalar>(vertex_list), Matrix<Scalar>(lin_space_list) };
340 }
341
342 template <typename Scalar>
343 LP_Solution<Scalar>
solve(const Matrix<Scalar> & Inequalities,const Matrix<Scalar> & Equations,const Vector<Scalar> & Objective,bool maximize,bool)344 LP_Solver<Scalar>::solve(const Matrix<Scalar>& Inequalities, const Matrix<Scalar>& Equations,
345 const Vector<Scalar>& Objective, bool maximize, bool) const
346 {
347 // establish the PPL rounding mode temporarily
348 fp_mode_setter fp_mode;
349
350 LP_Solution<Scalar> result;
351
352 const Int num_columns = std::max(Inequalities.cols(), Equations.cols())-1;
353 if (num_columns == -1) {
354 result.status = LP_status::infeasible;
355 return result;
356 }
357
358 PPL::C_Polyhedron polyhedron = construct_ppl_polyhedron_H(Inequalities, Equations, 0); // isCone = 0
359
360 // Linear program
361 const Integer lcm_of_obj_denom = lcm(denominators(Objective));
362 std::vector<mpz_class> objective = convert_to_mpz<Scalar>(Objective, lcm_of_obj_denom);
363
364 PPL::Linear_Expression e;
365 for (Int j = num_columns; j >= 1; --j) {
366 e += objective[j] * PPL::Variable(j-1);
367 }
368 e += objective[0];
369 PPL::Coefficient bound_n, bound_d; // same as mpz_class
370 bool is_opt;
371 PPL::Generator g_opt = PPL::point();
372 const bool solvable = maximize ? polyhedron.maximize(e, bound_n, bound_d, is_opt, g_opt)
373 : polyhedron.minimize(e, bound_n, bound_d, is_opt, g_opt);
374
375 if (!solvable) { // ppl returns false if input is infeasible OR unbounded!
376 result.status = polyhedron.is_empty() ? LP_status::infeasible : LP_status::unbounded;
377 } else {
378 result.status = LP_status::valid;
379 result.solution = ppl_gen_to_vec<Scalar>(g_opt, false);
380
381 // opt_val needs to be divided additionally by the lcm of the denominators
382 // of the objective vector since the constructed Linear_Expression e
383 // has been multiplied by this factor in 'convert_to_mpz'.
384 // Note that ppl seems to work only with integral objective functions.
385 result.objective_value.set(Integer(bound_n), Integer(bound_d)*lcm_of_obj_denom);
386 }
387 return result;
388 }
389
390 } } }
391
392
393 // Local Variables:
394 // mode:C++
395 // c-basic-offset:3
396 // indent-tabs-mode:nil
397 // End:
398