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 #include "polymake/client.h"
19 #include "polymake/vector"
20 #include "polymake/list"
21 #include "polymake/linalg.h"
22 #include "polymake/polytope/solve_LP.h"
23 #include "polymake/Map.h"
24 #include "polymake/Graph.h"
25 #include "polymake/Set.h"
26 #include "polymake/IncidenceMatrix.h"
27 #include "polymake/common/labels.h"
28 
29 namespace polymake { namespace polytope {
30 namespace {
31 
32 template <typename E, typename Matrix, typename Vector1, typename Vector2>
assign_facet_through_points(const GenericMatrix<Matrix,E> & M,const GenericVector<Vector1,E> & V_cut,GenericVector<Vector2,E> && f)33 void assign_facet_through_points(const GenericMatrix<Matrix, E>& M,
34                                  const GenericVector<Vector1, E>& V_cut,
35                                  GenericVector<Vector2, E>&& f)
36 {
37    f = null_space(M)[0];
38    if (f * V_cut > 0) f.negate();
39 }
40 
41 } // end anonymous namespace
42 
43 template <typename Scalar, typename TSet>
truncation(BigObject p_in,const GenericSet<TSet> & trunc_vertices,OptionSet options)44 BigObject truncation(BigObject p_in, const GenericSet<TSet>& trunc_vertices, OptionSet options)
45 {
46    if (options.exists("cutoff") && options.exists("no_coordinates"))
47       throw std::runtime_error("truncation: cannot specify cutoff and no_coordinates options simultaneously");
48 
49    const bool pointed = p_in.give("POINTED");
50    if (!pointed)
51       throw std::runtime_error("truncation: input should be pointed");
52 
53    const bool noc = options["no_coordinates"],
54       relabel = !options["no_labels"];
55 
56    const IncidenceMatrix<> VIF=p_in.give("VERTICES_IN_FACETS");
57    const Graph<> G=p_in.give("GRAPH.ADJACENCY");
58 
59    bool inequalities;
60 
61    const Int n_vertices = VIF.cols(), n_facets=VIF.rows();
62 
63    if (n_vertices < 2)
64       throw std::runtime_error("truncation: cannot truncate polytopes with dimension < 1");
65 
66    typedef Map<Int, Int> vertex_map_type;
67    vertex_map_type vertex_map;          // truncated vertex => the first of the new vertices
68 
69    Int n_vertices_out, n_trunc_vertices;
70    if (trunc_vertices.top().empty())
71       throw std::runtime_error("truncation: no vertices to truncate specified");
72    if (trunc_vertices.top().front() < 0 || trunc_vertices.top().back() >= n_vertices)
73       throw std::runtime_error("vertex numbers out of range");
74 
75    BigObject p_out("Polytope", mlist<Scalar>());
76    if (std::is_same<TSet, Set<Int>>::value)
77       p_out.set_description() << p_in.name() << " with vertices " << trunc_vertices << " truncated" << endl;
78 
79    n_trunc_vertices=trunc_vertices.top().size();
80    n_vertices_out=n_vertices-n_trunc_vertices;
81 
82    for (auto v = entire(trunc_vertices.top()); !v.at_end(); ++v) {
83       vertex_map[*v] = n_vertices_out;
84       n_vertices_out += G.degree(*v);
85    }
86 
87    Int n_facets_out = n_facets+n_trunc_vertices;
88    IncidenceMatrix<> VIF_out(n_facets_out, n_vertices_out);
89 
90    // first inherit the original facets along with untouched vertices in them
91    if (n_trunc_vertices < n_vertices)
92       copy_range(entire(rows(VIF.minor(All,~keys(vertex_map)))), rows(VIF_out).begin());
93 
94    Int new_facet = n_facets;
95    for (const auto& vm : vertex_map) {
96       Int new_vertex = vm.second;
97       for (auto nb = entire(G.adjacent_nodes(vm.first));  !nb.at_end();  ++nb, ++new_vertex) {
98          // the new vertex inherits the ridge from the truncated vertex,
99          // and it belongs to the new facet
100          (VIF_out.col(new_vertex) = VIF.col(vm.first) * VIF.col(*nb)) += new_facet;
101       }
102       ++new_facet;
103    }
104 
105    std::vector<std::string> labels_out;
106    if (relabel) {
107       const std::vector<std::string> labels = common::read_labels(p_in, "VERTEX_LABELS", n_vertices);
108       labels_out.resize(n_vertices_out);
109       copy_range(entire(select(labels, ~keys(vertex_map))), labels_out.begin());
110 
111       for (const auto& vm : vertex_map) {
112          Int new_vertex = vm.second;
113          for (auto nb = entire(G.adjacent_nodes(vm.first));  !nb.at_end();  ++nb, ++new_vertex) {
114             labels_out[new_vertex]=labels[vm.first] + '-' + labels[*nb];
115          }
116       }
117    }
118 
119    if (noc) {
120       if (p_in.exists("COMBINATORIAL_DIM")) {
121          const Int dim = p_in.give("COMBINATORIAL_DIM");
122          p_out.take("COMBINATORIAL_DIM") << dim;
123          inequalities = (dim == 1);
124       } else {
125          inequalities = true;
126       }
127    } else {
128       Scalar cutoff_factor = Scalar(1)/Scalar(2);
129       if (options["cutoff"] >> cutoff_factor && (cutoff_factor<=0 || cutoff_factor>1))
130          throw std::runtime_error("cutoff factor must be within (0,1]");
131 
132       std::vector<Int> renumber_vertices;
133       if (cutoff_factor == 1) {
134          renumber_vertices.resize(n_vertices);
135          copy_range(sequence(0).begin(), select(renumber_vertices, ~keys(vertex_map)).begin());
136       }
137       const Int dim = p_in.give("CONE_DIM");
138       inequalities = (cutoff_factor == 1 || dim == 2);
139 
140       const Matrix<Scalar> V=p_in.give("VERTICES"),
141          F=p_in.give("FACETS"),
142          AH=p_in.give("AFFINE_HULL");
143 
144       Matrix<Scalar> F_out = F / zero_matrix<Scalar>(n_trunc_vertices, F.cols());
145       Matrix<Scalar> orth(AH);
146       if (orth.cols() != 0) orth.col(0).fill(0);
147 
148       auto new_facet_it = rows(F_out).begin() + n_facets;
149       for (const auto& vm : vertex_map) {
150          const Int v_cut_off = vm.first;
151          Matrix<Scalar> basis(G.out_degree(v_cut_off), V.cols());
152          const bool simple_vertex = basis.rows()+AH.rows() == V.cols()-1;
153 
154          auto b = rows(basis).begin();
155          for (auto nb_v = entire(G.adjacent_nodes(v_cut_off)); !nb_v.at_end(); ++nb_v, ++b) {
156             if (vertex_map.exists(*nb_v))
157                *b = (1-cutoff_factor/2) * V[v_cut_off] + cutoff_factor/2 * V[*nb_v];
158             else
159                *b = (1-cutoff_factor) * V[v_cut_off] + cutoff_factor * V[*nb_v];
160          }
161          if (simple_vertex) {
162             // calculate a hyperplane thru the basis points
163             assign_facet_through_points(basis/orth, V[v_cut_off], *new_facet_it);
164          } else {
165             // look for a valid separating hyperplane farthest from the vertex being cut off
166             const auto S = solve_LP(basis, orth, V[v_cut_off], false);
167             if (S.status != LP_status::valid)
168                throw std::runtime_error("truncation: wrong LP");
169             *new_facet_it = S.solution;
170          }
171 
172          if (cutoff_factor==1) {
173             // we must take care of coinciding vertices
174             Int new_vertex = vm.second;
175             b = rows(basis).begin();
176             for (auto nb_v = entire(G.adjacent_nodes(v_cut_off)); !nb_v.at_end();  ++nb_v, ++new_vertex) {
177 
178                if (!simple_vertex && !is_zero((*new_facet_it)*(*b))) continue;       // doesn't touch this vertex
179 
180                Int other_vertex;
181                auto otv = vertex_map.find(*nb_v);
182                if (!otv.at_end()) {
183                   // pairs of coinciding new vertices should not be handled twice
184                   if (v_cut_off < *nb_v) continue;
185                   other_vertex = otv->second;
186                   // number of the opposite new vertex can be found only by enumeration of adjacent_nodes...
187                   for (auto other_nb_v = G.adjacent_nodes(*nb_v).begin();
188                        *other_nb_v != v_cut_off;  ++other_nb_v, ++other_vertex) ;
189                } else {
190                   other_vertex=renumber_vertices[*nb_v];
191                }
192                // the new vertex coincides with the neighbor and disappears
193                VIF_out.col(other_vertex) += VIF_out.col(new_vertex).back(); // the new facet
194                VIF_out.col(new_vertex).clear();
195                if (relabel) labels_out[new_vertex].clear();
196             }
197          }
198          ++new_facet_it;
199       }
200 
201       // if cutoff is 1 some old facets might not be facets anymore
202       // if dim is 1 then vertices are facets. So we truncate facets
203       if (inequalities){
204         F_out /= unit_vector<Scalar>(F_out.cols(), 0);
205         p_out.take("INEQUALITIES")  << F_out;
206         p_out.take("EQUATIONS") << AH;
207       } else {
208         p_out.take("FACETS")  << F_out;
209         p_out.take("AFFINE_HULL") << AH;
210       }
211 
212       if (cutoff_factor==1) {
213          VIF_out.squeeze();
214          if (relabel) {
215             labels_out.resize(std::remove(labels_out.begin()+n_vertices-n_trunc_vertices,
216                                           labels_out.end(), std::string())
217                               - labels_out.begin());
218          }
219       }
220    }
221 
222    p_out.take("N_VERTICES") << VIF_out.cols();
223    p_out.take("FEASIBLE") << true;
224 
225    // if cutoff is 1 some old facets might not be facets anymore
226    if (!inequalities){
227      p_out.take("VERTICES_IN_FACETS") << VIF_out;
228    }
229 
230    if (relabel)
231       p_out.take("VERTEX_LABELS") << labels_out;
232 
233    return p_out;
234 }
235 
236 template<typename Scalar>
truncation(BigObject p_in,const pm::all_selector &,OptionSet options)237 BigObject truncation(BigObject p_in, const pm::all_selector&, OptionSet options)
238 {
239    const Int n_verts = p_in.give("N_VERTICES");
240    BigObject p_out = truncation<Scalar>(p_in, sequence(0, n_verts), options);
241    p_out.set_description() << p_in.name() << " with all vertices truncated" << endl;
242    return p_out;
243 }
244 
245 template<typename Scalar>
truncation(BigObject p_in,Int vertex,OptionSet options)246 BigObject truncation(BigObject p_in, Int vertex, OptionSet options)
247 {
248    BigObject p_out = truncation<Scalar>(p_in, scalar2set(vertex), options);
249    p_out.set_description() << p_in.name() << " with vertex " << vertex << " truncated" << endl;
250    return p_out;
251 }
252 
253 template<typename Scalar>
truncation(BigObject p_in,const Array<Int> & verts,OptionSet options)254 BigObject truncation(BigObject p_in, const Array<Int>& verts, OptionSet options)
255 {
256    Set<Int> trunc_vertices;
257    for (const auto& vi : verts)
258       trunc_vertices += vi;
259 
260    if (verts.size() != trunc_vertices.size())
261       throw std::runtime_error("truncation: repeating vertex numbers in the list");
262 
263    return truncation<Scalar>(p_in, trunc_vertices, options);
264 }
265 
266 UserFunctionTemplate4perl("# @category Producing a polytope from polytopes"
267                           "# "
268                           "# Cut off one or more vertices of a polyhedron."
269                           "# "
270                           "# The exact location of the cutting hyperplane(s) can be controlled by the"
271                           "# option //cutoff//, a rational number between 0 and 1."
272                           "# When //cutoff//=0, the hyperplane would go through the chosen vertex, thus cutting off nothing."
273                           "# When //cutoff//=1, the hyperplane touches the nearest neighbor vertex of a polyhedron."
274                           "# "
275                           "# Alternatively, the option //no_coordinates// can be specified to produce a"
276                           "# pure combinatorial description of the resulting polytope, which corresponds to"
277                           "# the cutoff factor 1/2."
278                           "# @param Polytope P"
279                           "# @param Set<Int> trunc_vertices the vertex/vertices to be cut off;"
280                           "#   A single vertex to be cut off is specified by its number."
281                           "#   Several vertices can be passed in a Set or in an anonymous array of indices: [n1,n2,...]"
282                           "#   Special keyword __All__ means that all vertices are to be cut off."
283                           "# @option Scalar cutoff controls the exact location of the cutting hyperplane(s);"
284                           "#   rational number between 0 and 1; default value: 1/2"
285                           "# @option Bool no_coordinates produces a pure combinatorial description (in contrast to //cutoff//)"
286                           "# @option Bool no_labels Do not copy [[VERTEX_LABELS]] from the original polytope. default: 0"
287                           "#   New vertices get labels of the form 'LABEL1-LABEL2', where LABEL1 is the original label"
288                           "#   of the truncated vertex, and LABEL2 is the original label of its neighbor."
289                           "# @return Polytope"
290                           "# @example To truncate the second vertex of the square at 1/4, try this:"
291                           "# > $p = truncation(cube(2),2,cutoff=>1/4);"
292                           "# > print $p->VERTICES;"
293                           "# | 1 -1 -1"
294                           "# | 1 1 -1"
295                           "# | 1 1 1"
296                           "# | 1 -1 1/2"
297                           "# | 1 -1/2 1"
298                           "# @author Kerstin Fritzsche (initial version)",
299                           "truncation<Scalar>(Polytope<Scalar> * {cutoff=>undef, no_coordinates=>undef, no_labels=>0})");
300 } }
301 
302 // Local Variables:
303 // mode:C++
304 // c-basic-offset:3
305 // indent-tabs-mode:nil
306 // End:
307