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