1 /*
2 	This program is free software; you can redistribute it and/or
3 	modify it under the terms of the GNU General Public License
4 	as published by the Free Software Foundation; either version 2
5 	of the License, or (at your option) any later version.
6 
7 	This program is distributed in the hope that it will be useful,
8 	but WITHOUT ANY WARRANTY; without even the implied warranty of
9 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 	GNU General Public License for more details.
11 
12 	You should have received a copy of the GNU General Public License
13 	along with this program; if not, write to the Free Software
14 	Foundation, Inc., 51 Franklin Street, Fifth Floor,
15 	Boston, MA  02110-1301, USA.
16 
17 	---
18 	Copyright (C) 2011 - 2015, Simon Hampe <simon.hampe@googlemail.com>
19 
20 	---
21 	Copyright (c) 2016-2021
22 	Ewgenij Gawrilow, Michael Joswig, and the polymake team
23 	Technische Universität Berlin, Germany
24 	https://polymake.org
25 
26 	Implements lines_in_cubic_helper.h
27 	*/
28 
29 #include "polymake/client.h"
30 #include "polymake/Rational.h"
31 #include "polymake/Matrix.h"
32 #include "polymake/Vector.h"
33 #include "polymake/linalg.h"
34 #include "polymake/IncidenceMatrix.h"
35 #include "polymake/tropical/codim_one_with_locality.h"
36 #include "polymake/tropical/divisor.h"
37 #include "polymake/tropical/linear_algebra_tools.h"
38 #include "polymake/tropical/lines_in_cubic_reachable.h"
39 #include "polymake/tropical/lines_in_cubic_helper.h"
40 #include "polymake/tropical/lines_in_cubic_data.h"
41 #include "polymake/polytope/convex_hull.h"
42 
43 
44 namespace polymake { namespace tropical {
45 
46 /**
47    @brief Takes a fan_intersection_result and cleans up the result so that no cone is contained in another and that cones are sorted according to their dimension.
48    @param fan_intersection_result fir
49    @return DirectionIntersection
50 */
cleanUpIntersection(const fan_intersection_result & fir)51 DirectionIntersection cleanUpIntersection(const fan_intersection_result& fir)
52 {
53   DirectionIntersection result;
54   result.rays = fir.rays;
55   IncidenceMatrix<> fir_cones = fir.cones;
56   // First we sort all cells according to their dimension
57   Set<Int> cell_set;
58   Set<Int> edge_set;
59   Set<Int> point_set;
60   for (Int fc = 0; fc < fir_cones.rows(); ++fc) {
61     if (fir_cones.row(fc).size() > 2) cell_set += fc;
62     if (fir_cones.row(fc).size() == 2) edge_set += fc;
63     if (fir_cones.row(fc).size() == 1) point_set += fc;
64   }
65   // Go through all edges, compare to cells, remove redundant ones
66   Set<Int> redundant_edges;
67   for (auto e = entire(edge_set); !e.at_end(); ++e) {
68     bool found_container = false;
69     for (auto c = entire(cell_set); !c.at_end(); ++c) {
70       if ( (fir_cones.row(*c) * fir_cones.row(*e)).size() == fir_cones.row(*e).size()) {
71         found_container = true;
72         break;
73       }
74     }
75     if (found_container) redundant_edges += (*e);
76   }
77   edge_set -= redundant_edges;
78 
79   // Same for points
80   Set<Int> redundant_points;
81   for (auto p = entire(point_set); !p.at_end(); ++p) {
82     bool found_container = false;
83     Set<Int> containers = cell_set + edge_set;
84     for (auto c = entire(containers); !c.at_end(); ++c) {
85       if ( (fir_cones.row(*c) * fir_cones.row(*p)).size() == fir_cones.row(*p).size()) {
86         found_container = true;
87         break;
88       }
89     }
90     if (found_container) redundant_points += (*p);
91   }
92   point_set -= redundant_points;
93 
94   result.cells = fir_cones.minor(cell_set,All);
95   result.edges = fir_cones.minor(edge_set,All);
96   result.points = fir_cones.minor(point_set,All);
97 
98   return result;
99 } // END cleanUpIntersection
100 
101 /**
102    @brief This takes a (two-dimensional) cone in R^3 in terms of a subset of rays and computes all codimension one faces.
103    @return A FacetData object (see above)
104 */
computeFacets(const Matrix<Rational> & rays,const Set<Int> & cone)105 FacetData computeFacets(const Matrix<Rational>& rays, const Set<Int>& cone)
106 {
107   // Compute facet equations and store them
108   const auto ceq = polytope::enumerate_facets(rays.minor(cone, All), false);
109   FacetData result;
110   result.eq = ceq.second.row(0);
111   result.ineqs = ceq.first;
112   // Now go through all inequalities and find the corresponding vertices
113   Set<Int> exclude_ineqs;
114   RestrictedIncidenceMatrix<> facets;
115   for (auto ineq=entire<indexed>(rows(result.ineqs)); !ineq.at_end(); ++ineq) {
116     Set<Int> facet;
117     bool vertex_seen=false;
118     for (auto c=entire(cone); !c.at_end(); ++c) {
119       if (is_zero((*ineq) * rays.row(*c))) {
120         facet.push_back(*c);
121         if (!vertex_seen && !is_zero(rays(*c, 0))) {
122           vertex_seen=true;
123         }
124       }
125     }
126     // Each facet has to have at least one vertex
127     if (vertex_seen) {
128       facets /= facet;
129     } else {
130       exclude_ineqs += ineq.index();
131     }
132   }
133   result.facets = std::move(facets);
134   if (!exclude_ineqs.empty()) {
135     result.ineqs = result.ineqs.minor(~exclude_ineqs, All);
136   }
137   return result;
138 }
139 
140 // ------------------------------------------------------------------------------------------------
141 
142 /**
143    @brief This takes a vertex in the cubic (whose function's domain is describes by frays and fcones) and a direction and computes the vertex w farthest away from vertex in this direction, such that the convex hull of vertex and w still lies in X. It returns the empty vertex, if the complete half-line lies in X.
144 */
maximalDistanceVector(const Vector<Rational> & vertex,const Vector<Rational> & direction,const Matrix<Rational> & frays,const IncidenceMatrix<> & fcones,const Matrix<Rational> & funmat)145 Vector<Rational> maximalDistanceVector(const Vector<Rational>& vertex,
146                                        const Vector<Rational>& direction,
147                                        const Matrix<Rational>& frays,
148                                        const IncidenceMatrix<>& fcones,
149                                        const Matrix<Rational>& funmat)
150 {
151   // Create the one-dimensional half-line from vertex
152   Matrix<Rational> hl_rays = vector2row(vertex) / direction;
153   IncidenceMatrix<> hl_cones({ { 0, 1 } });
154   Matrix<Rational> lin(0, hl_rays.cols());
155   //Intersect with f-domain
156   DirectionIntersection ref_line =
157     cleanUpIntersection(fan_intersection(hl_rays, lin, hl_cones, frays,lin,fcones));
158   // Find vertex
159   Int v_index = -1;
160   for (Int r = 0; r < ref_line.rays.rows(); ++r) {
161     if (ref_line.rays.row(r) == vertex) {
162       v_index = r;
163       break;
164     }
165   }
166   IncidenceMatrix<> rays_in_cones = T(ref_line.edges);
167   // Go through edges, starting at vertex and check if it is contained in X
168   Int current_edge = *(rays_in_cones.row(v_index).begin());
169   Int current_vertex = v_index;
170   // When the current edge has only one vertex, we're done
171   do {
172     // Check if the edge lies in X
173     Vector<Rational> interior_point =
174       accumulate(rows(ref_line.rays.minor(ref_line.edges.row(current_edge),All)), operations::add()) /
175       accumulate(ref_line.rays.minor(ref_line.edges.row(current_edge),All).col(0),operations::add());
176     if (!maximumAttainedTwice(funmat * interior_point)) {
177       return ref_line.rays.row(current_vertex);
178     } else {
179       current_vertex = *( (ref_line.edges.row(current_edge) - current_vertex).begin());
180       if (ref_line.rays.row(current_vertex)[0] == 0) {
181         current_vertex = -1;
182       } else {
183         current_edge = *( (rays_in_cones.row(current_vertex) - current_edge).begin());
184       }
185     }
186   } while(current_vertex >= 0);
187 
188   return Vector<Rational>();
189 }
190 
191 // ------------------------------------------------------------------------------------------------
192 
193 /**
194    @brief Takes two vectors v1 and v2 and a standard direction e_i + e_j for i,j in {0,..,3} and computes the rational number r, such that v1 + r*direction = v2. Returns also zero, if no such number exists. It also accepts a 0-dimensional vertex for v2 and will return 0 in that case.
195 */
vertexDistance(const Vector<Rational> & v1,const Vector<Rational> & v2,const Vector<Rational> & direction)196 Rational vertexDistance(const Vector<Rational>& v1, const Vector<Rational>& v2, const Vector<Rational>& direction)
197 {
198   if (v2.dim() == 0) return 0;
199   Vector<Rational> diff = v2 - v1;
200   Rational div = 0;
201   for (Int i = 1; i <= 3; ++i) {
202     if ((diff[i] == 0 && direction[i] != 0) || (diff[i] != 0 && direction[i] == 0))
203       return 0;
204     if (diff[i] != 0) {
205       Rational d = diff[i] / direction[i];
206       if (div == 0) {
207         div = d;
208       } else {
209         if (d != div) return 0;
210       }
211     }
212   }
213   return div;
214 }
215 
216 // ------------------------------------------------------------------------------------------------
217 
218 /**
219    @brief Takes a vertex family and computes the index of the standard direction in 0,..,3 corresponding to its edge
220 */
vertexFamilyDirection(const VertexFamily & f)221 Int vertexFamilyDirection(const VertexFamily& f)
222 {
223   Vector<Rational> dir;
224   if (f.edge(0,0) == 0) dir = f.edge.row(0);
225   if (f.edge(1,0) == 0) dir = f.edge.row(1);
226   if (dir.dim() == 0) dir = f.edge.row(0) - f.edge.row(1);
227   if (dir[1] == 0 && dir[2] == 0) return 3;
228   if (dir[1] == 0 && dir[3] == 0) return 2;
229   if (dir[2] == 0 && dir[3] == 0) return 1;
230   return 0;
231 }
232 
233 // ------------------------------------------------------------------------------------------------
234 
235 /**
236    @brief Computes all edge families lying in a 2-dimensional cone for a given direction
237    @param DirectionIntersection cone A cone, refined along f
238    @param Matrix<Rational> z_border The intersection of the cone with a cone in the 0-i-rechable locus
239    @param Matrix<Rational> c_border The intersection of the cone with a cone in the j-k-reachable locus
240    @param Int leafAtZero The index of the leaf together with 0
241    @param Matrix<Rational> funmat The function matrix of f, made compatible for vector multiplication
242    @return LinesInCellResult A list of all edge families and edge lines lying in the cone
243 */
computeEdgeFamilies(const DirectionIntersection & cone,const Matrix<Rational> & z_border,const Matrix<Rational> & c_border,Int leafAtZero,const Matrix<Rational> & funmat)244 LinesInCellResult computeEdgeFamilies(const DirectionIntersection& cone,
245                                       const Matrix<Rational>& z_border,
246                                       const Matrix<Rational>& c_border,
247                                       Int leafAtZero,
248                                       const Matrix<Rational>& funmat)
249 {
250   Matrix<Rational> degree = -unit_matrix<Rational>(3);
251   degree = ones_vector<Rational>(3) / degree;
252   degree = zero_vector<Rational>(4) | degree;
253 
254   // First we project all vertices of the cone onto z_border
255   Matrix<Rational> z_edge_rays = z_border;
256   RestrictedIncidenceMatrix<> z_edges_grow={ {0, 1} };
257   Vector<Rational> direction = degree.row(0) + degree.row(leafAtZero);
258   Vector<Int> rem(sequence(1,3) - leafAtZero);
259 
260   for (Int dr = 0; dr < cone.rays.rows(); ++dr) {
261     if (cone.rays(dr,0) == 1) {
262       // We go through all edges of z_edges until we find one that intersects (vertex + R_>=0 *direction)
263       // This is computed as follows: Assume p is a vertex of an edge of z_edges and that w is the direction
264       // from p into that edge (either a ray or p2-p1, if the edge is bounded). Then we compute a linear representation of (vertex - p_1) in terms of w and
265       // direction. If a representation exists and the second edge generator is also a vertex, we have to check
266       // that the coefficient of w = p2-p1 is in between 0 and 1, otherwise it has to be > 0
267       //
268       // If we find an intersecting edge, we refine it (in z_edges) such that it contains the intersection point.
269       for (Int ze = 0; ze < z_edges_grow.rows(); ++ze) {
270         Matrix<Rational> edge_generators = z_border.minor(z_edges_grow.row(ze), All);
271         Vector<Rational> p1 = edge_generators(0,0) == 0? edge_generators.row(1) : edge_generators.row(0);
272         bool bounded = edge_generators(0,0) == edge_generators(1,0);
273         Vector<Rational> w;
274         if (bounded) {
275           w = edge_generators.row(1) - edge_generators.row(0);
276         } else {
277           w = edge_generators(0,0) == 0 ? edge_generators.row(0) : edge_generators.row(1);
278         }
279         Vector<Rational> lin_rep = linearRepresentation(cone.rays.row(dr) - p1, vector2row(w) / direction);
280         // Check that:
281         //  - There is a representation
282         //  - The coefficient of w is > 0 (and < 1 if bounded)
283         if (lin_rep.dim() > 0) {
284           if (lin_rep[0] > 0 && (!bounded || lin_rep[0] < 1)) {
285             // Then we add a vertex, stop searching for an edge and go to the next vertex
286             Vector<Rational> new_vertex = p1 + lin_rep[0]*w;
287             z_edge_rays /= new_vertex;
288             auto edge_index_list=z_edges_grow.row(ze).begin();
289             const Set<Int> one_cone={ *edge_index_list, z_edge_rays.rows()-1 };
290             const Set<Int> other_cone={ *++edge_index_list, z_edge_rays.rows()-1 };
291             z_edges_grow.row(ze)=one_cone;
292             z_edges_grow /= other_cone;
293             break;
294           }
295         } // END if linear rep exists
296       } // END iterate z_edges
297     } // END if vertex
298   } // END project vertices
299 
300   const IncidenceMatrix<> z_edges(std::move(z_edges_grow));
301   // Then refine the cone along the new z_edges - direction
302   // and compute codim one data
303   Matrix<Rational> dummy_lineality(0, z_border.cols());
304   DirectionIntersection refined_cone =
305     cleanUpIntersection(fan_intersection(cone.rays, dummy_lineality, cone.cells,
306                                          z_edge_rays, dummy_lineality / direction, z_edges));
307   CodimensionOneResult codimData =
308     calculateCodimOneData(refined_cone.rays, refined_cone.cells,  dummy_lineality, IncidenceMatrix<>());
309   IncidenceMatrix<> codim =codimData.codimOneCones;
310   IncidenceMatrix<> rayInCo = T(codim);
311   IncidenceMatrix<> coInMax = codimData.codimOneInMaximal;
312   IncidenceMatrix<> maxInCo = T(coInMax);
313 
314   // Find all edges that span direction
315   Set<Int> direction_edges;
316   for (Int cc = 0; cc < codim.rows(); ++cc) {
317     Matrix<Rational> cc_rays = refined_cone.rays.minor(codim.row(cc),All);
318     Vector<Rational> cc_span;
319     if (cc_rays(0,0) == cc_rays(1,0)) {
320       cc_span = cc_rays.row(0) - cc_rays.row(1);
321     } else {
322       cc_span = cc_rays(0,0) == 0 ? cc_rays.row(0) : cc_rays.row(1);
323     }
324     if (cc_span[leafAtZero] == 0 && cc_span[rem[0]] == cc_span[rem[1]])
325       direction_edges += cc;
326   }
327   Set<Int> remaining_codim = sequence(0,codim.rows()) - direction_edges;
328 
329   // Now we go through all z_edges, find the corresponding codim cone in the refined cone
330   // and check if the complete "path" to the other side lies in X. In addition we keep track of all
331   // vertices of z_edges that do not lie in a 2-dim path to the other side and have to be checked
332   // for isolated solutions
333   Vector<EdgeFamily> family_list(0);
334   Vector<EdgeLine> line_list(0);
335   Vector<VertexLine> vertex_list(0);
336 
337   Set<Int> all_vertices_this_side;
338   Set<Int> covered_vertices;
339 
340   for (Int ze = 0; ze < z_edges.rows(); ++ze) {
341     Matrix<Rational> ze_rays = z_edge_rays.minor(z_edges.row(ze), All);
342     // Find rays in refined cone
343     Int index_1 = -1;
344     Int index_2 = -1;
345     for (Int rc = 0; rc < refined_cone.rays.rows(); ++rc) {
346       if (refined_cone.rays.row(rc) == ze_rays.row(0)) index_1 = rc;
347       if (refined_cone.rays.row(rc) == ze_rays.row(1)) index_2 = rc;
348       if (index_1 >= 0 && index_2 >= 0) break;
349     }
350     if (refined_cone.rays(index_1,0) == 1) all_vertices_this_side += index_1;
351     if (refined_cone.rays(index_2,0) == 1) all_vertices_this_side += index_2;
352 
353     // Now find the codimension one cone containing these two rays
354     Int co_index = -1;
355     for (auto cc = entire(remaining_codim); !cc.at_end(); ++cc) {
356       if (codim.row(*cc).contains(index_1) && codim.row(*cc).contains(index_2)) {
357         co_index = *cc;
358         break;
359       }
360     }
361     remaining_codim -= co_index;
362 
363     // Find all maximal cones on the "path" to the other side and check if they are in X
364     Int current_cone = *(coInMax.row(co_index).begin());
365     bool found_bad = false;
366     Int cone_index_other_side = -1;
367     while (current_cone >= 0) {
368       // Compute interior point and check if it is in X
369       Vector<Rational> interior_point =
370         accumulate( rows(refined_cone.rays.minor(refined_cone.cells.row(current_cone),All)), operations::add()) /
371         accumulate(refined_cone.rays.minor(refined_cone.cells.row(current_cone),All).col(0),operations::add());
372       if (!maximumAttainedTwice(funmat * interior_point)) {
373         found_bad = true;
374         break;
375       }
376 
377       // If all is fine, find the next maximal cone, i.e. find an unused codimension one face
378       // in that cone and take the other maximal cone adjacent to it.
379       Int next_codim = *( (maxInCo.row(current_cone) * remaining_codim).begin() );
380       remaining_codim -= next_codim;
381 
382       Set<Int> next_max = coInMax.row(next_codim) - current_cone;
383       // If there is no other maximal cone, we have arrived at the other end
384       if (next_max.size() == 0) {
385         cone_index_other_side = next_codim;
386         current_cone = -1;
387       } else {
388         current_cone = *(next_max.begin());
389       }
390     } //END iterate maximal cones to the other side
391 
392     // If everything lies in X, add the family (and register the vertices as "covered")
393     if (!found_bad) {
394       EdgeFamily ef;
395       ef.edgesAtZero = Vector< Matrix<Rational> >(0);
396       ef.edgesAwayZero = Vector< Matrix<Rational> >(0);
397       ef.leafAtZero = leafAtZero;
398       ef.edgesAtZero |= ze_rays;
399       ef.edgesAwayZero |= refined_cone.rays.minor(codim.row(cone_index_other_side),All);
400       ef.borderAtZero = ef.edgesAtZero[0];
401       ef.borderAwayZero = ef.edgesAwayZero[0];
402       family_list |= ef;
403 
404       covered_vertices += codim.row(co_index);
405     }
406 
407   } //END iterate z_edges
408 
409   // Finally we consider all vertices not in a 2-dim family and check if the line to the other
410   // side is contained in X
411   Set<Int> vertices_to_check = all_vertices_this_side - covered_vertices;
412   Set<Int> used_up_edges;
413   Set<Int> used_up_vertices;
414   for (auto vtc = entire(vertices_to_check); !vtc.at_end(); ++vtc) {
415     used_up_vertices += *vtc;
416 
417     // Find first edge containing it
418     Set<Int> current_edge_set = rayInCo.row(*vtc) * direction_edges;
419 
420     // If there is no edge, we have a vertex line
421     if (current_edge_set.empty()) {
422       VertexLine vl;
423       vl.vertex = refined_cone.rays.row(*vtc);
424       vl.cells = Set<Int>();
425       vertex_list |= vl;
426       continue;
427     }
428 
429     Int current_edge = *(current_edge_set.begin());
430 
431     used_up_edges += current_edge;
432     Int  vertex_index_other_side;
433     bool found_bad = true;
434     while (current_edge >= 0) {
435       // Compute interior point and check if it is in X
436       Vector<Rational> interior_point =
437         accumulate( rows(refined_cone.rays.minor(codim.row(current_edge),All)), operations::add()) /
438         accumulate(refined_cone.rays.minor(codim.row(current_edge),All).col(0),operations::add());
439       if (!maximumAttainedTwice(funmat * interior_point)) {
440         found_bad = true;
441         break;
442       }
443 
444       // Find next edge. If there is none, we have arrived at the other side
445       Int next_vertex = *( (codim.row(current_edge) - used_up_vertices).begin());
446       used_up_vertices += next_vertex;
447       Set<Int> next_edge_set = (rayInCo.row(next_vertex)*direction_edges) - used_up_edges;
448       if (next_edge_set.empty()) {
449         vertex_index_other_side = next_vertex;
450         current_edge = -1;
451       } else {
452         current_edge = *(next_edge_set.begin());
453         used_up_edges += current_edge;
454       }
455     } // END iterate edges to other side
456     if (!found_bad) {
457       EdgeLine el;
458       el.leafAtZero = leafAtZero;
459       el.vertexAtZero = refined_cone.rays.row(*vtc);
460       el.vertexAwayZero = refined_cone.rays.row(vertex_index_other_side);
461       line_list |= el;
462     }
463   } //END iterate non-contained vertices
464 
465   // Return result
466   LinesInCellResult result;
467   result.edge_families = family_list;
468   result.edge_lines = line_list;
469   result.vertex_lines = vertex_list;
470 
471   return result;
472 
473 } // END computeEdgeFamilies
474 
475 } }
476