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