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/Set.h"
20 #include "polymake/Rational.h"
21 #include "polymake/Matrix.h"
22 #include "polymake/graph/DoublyConnectedEdgeList.h"
23 #include "polymake/topaz/DomeVolumeVisitor.h"
24 #include "polymake/IncidenceMatrix.h"
25 #include "polymake/Graph.h"
26 #include "polymake/Vector.h"
27 #include "polymake/graph/graph_iterators.h"
28 #include <cmath>
29
30
31 namespace polymake { namespace topaz {
32
33 using DoublyConnectedEdgeList = graph::dcel::DoublyConnectedEdgeList;
34 using HalfEdge = graph::dcel::HalfEdge;
35 using Face = graph::dcel::Face;
36
37 class PotatoVisitor : public graph::NodeVisitor<> {
38 private:
39 Graph<Directed>& dual_tree;
40
41 // The triangulation of the surface.
42 DoublyConnectedEdgeList& dcel;
43
44 // The vertices of the covering triangulation.
45 std::vector<Vector<Rational>> points;
46
47 // The co-vertices of the covering triangulation.
48 std::vector<Vector<Rational>> co_points;
49
50 // Mapping vectors to their position in points
51 Map<Vector<Rational>, Int> vertex_map;
52
53 // The number of nodes in the dual graph.
54 const Int num_nodes_depth;
55
56 // The triangles of the triangulation, triplets of corresponding indices.
57 Array<Set<Int>> triangles;
58
59 // mapping: BFS_node_id -> (half_edge_id, [vec_tail,vec_head])
60 using edge_pair_t = std::pair<Int, Matrix<Rational>>;
61 Map<Int, edge_pair_t> edge_map;
62
63 Int curr_num_nodes;
64
65 // A count of the nodes that were visited already.
66 Int num_visited;
67
68 public:
69
70 // This is needed for the BFS-iterator to not just stop in depth one.
71 static constexpr bool visit_all_edges = true;
72
73 /*
74 The PV is initialized with the given graph G (usually only one node for the first triangle (0,0->next,0->next->next) ),
75 the triangulation dcel (containing the A-coordinates),
76 and the depth we want to visit the dual tree.
77 */
PotatoVisitor(Graph<Directed> & G,DoublyConnectedEdgeList & dcel_,const Matrix<Rational> & first_two_vertices,Int dual_tree_depth)78 PotatoVisitor(Graph<Directed>& G, DoublyConnectedEdgeList& dcel_, const Matrix<Rational>& first_two_vertices, Int dual_tree_depth)
79 : dual_tree(G)
80 , dcel(dcel_)
81 , num_nodes_depth(3*((1L << dual_tree_depth)-1)+1)
82 , triangles(num_nodes_depth)
83 , curr_num_nodes(0)
84 , num_visited(0)
85 {
86 firstTriangle(first_two_vertices);
87 }
88
89 //adding the first triangle, first vectors (half_edge_0 tail and head) are rows of first_two_vertices
firstTriangle(const Matrix<Rational> & first_two_vertices)90 void firstTriangle(const Matrix<Rational>& first_two_vertices)
91 {
92 const HalfEdge* uv = dcel.getHalfEdge(0);
93 const HalfEdge* vu = uv->getTwin();
94 const HalfEdge* vw = uv->getNext();
95 const HalfEdge* wu = vw->getNext();
96 const HalfEdge* uw = wu->getTwin();
97 const Rational& a_uv = uv->getLength();
98 const Rational& a_vu = vu->getLength();
99 const Rational& a_uw = uw->getLength();
100 const Rational& a_vw = vw->getLength();
101 const Face* uvw = uv->getFace();
102 const Rational& a_uvw = uvw->getDetCoord();
103
104 const Vector<Rational> first_vec = first_two_vertices[0];
105 const Vector<Rational> second_vec = first_two_vertices[1];
106 addVertex(first_vec);
107 addVertex(second_vec);
108
109 const Vector<Rational> first_co_vec{ Rational(0), a_uv, a_uw/a_uvw };
110 const Vector<Rational> second_co_vec{ a_vu, Rational(0), a_vw/a_uvw };
111 addCoVertex(first_co_vec);
112 addCoVertex(second_co_vec);
113
114 const Matrix<Rational> M_edge = vector2row(first_vec) / second_vec;
115 edge_map[0] = edge_pair_t(0, M_edge);
116
117 const Int new_node_id = dual_tree.add_node();
118 dual_tree.add_edge(0, new_node_id);
119 const Matrix<Rational> M_twin_edge = vector2row(second_vec) / first_vec;
120 edge_map[new_node_id] = edge_pair_t(1, M_twin_edge);
121
122 curr_num_nodes += 2;
123 }
124
125 /*
126 The vertex vec becomes a new row of the matrix points.
127 The vertex map at vec is set to the corresponding index of the row in points (with index shift due to counting from 0)
128 */
addVertex(const Vector<Rational> & vec)129 void addVertex(const Vector<Rational>& vec)
130 {
131 vertex_map[vec] = points.size();
132 points.push_back(vec);
133 }
134
addCoVertex(const Vector<Rational> & co_vec)135 void addCoVertex(const Vector<Rational>& co_vec)
136 {
137 co_points.push_back(co_vec);
138 }
139
140
numVisited() const141 Int numVisited() const { return num_visited; }
142
operator ()(Int n)143 bool operator()(Int n)
144 {
145 return operator()(n, n);
146 }
147
148
149 // this is what happens when we call bfs++
operator ()(Int n_from,Int n_to)150 bool operator()(Int n_from, Int n_to)
151 {
152 if (visited.contains(n_to)) return false;
153 /*
154 The node n_to was not visited yet, therefore the corresponding triangle (its known halfedge) did not contribute.
155 We extract the known half edge as well as the two known horocycles .
156 Then we compute the position of the third horocycle, and put two new adjacent triangles (half edges) in the queue.
157 */
158 const edge_pair_t& edge_pair = edge_map[n_to];
159 const Vector<Rational> vec_u = edge_pair.second[0];
160 const Vector<Rational> vec_v = edge_pair.second[1];
161 const HalfEdge* uv = dcel.getHalfEdge(edge_pair.first);
162 const HalfEdge* vw = uv->getNext();
163 const HalfEdge* wv = vw->getTwin();
164 const HalfEdge* wu = vw->getNext();
165 const HalfEdge* uw = wu->getTwin();
166 const Rational& a_vw = vw->getLength();
167 const Rational& a_uw = uw->getLength();
168 const Rational& a_wv = wv->getLength();
169 const Rational& a_wu = wu->getLength();
170 const Face* uvw = uv->getFace();
171 const Rational& a_uvw = uvw->getDetCoord();
172 const Vector<Rational>& l_u = co_points[vertex_map[vec_u]];
173 const Vector<Rational>& l_v = co_points[vertex_map[vec_v]];
174 const Vector<Rational> vec_w = thirdVector(vec_u, vec_v, l_u, l_v, a_uw, a_vw, a_uvw);
175 const Vector<Rational> co_vec_w = thirdCoVector(vec_u, vec_v, vec_w, a_wu, a_wv);
176 addVertex(vec_w);
177 addCoVertex(co_vec_w);
178
179 triangles[n_to] = Set<Int>{ vertex_map[vec_u], vertex_map[vec_v], vertex_map[vec_w] };
180
181 if (dual_tree.nodes() < num_nodes_depth) {
182 // Add the two new half edges to the queue, update the edge_map and the dual_tree.
183 // Note that we change the sign of the second horocycle (head of wv and uw) since we want to guarantee a positive determinant.
184
185 const Matrix<Rational> M_wv = vector2row(vec_w) / vec_v;
186 const Matrix<Rational> M_uw = vector2row(vec_u) / vec_w;
187
188 const Int wv_node_id = dual_tree.add_node();
189 dual_tree.add_edge(n_to, wv_node_id);
190 edge_map[wv_node_id] = edge_pair_t(dcel.getHalfEdgeId(wv), M_wv);
191 const Int uw_node_id = dual_tree.add_node();
192 dual_tree.add_edge(n_to, uw_node_id);
193 edge_map[uw_node_id] = edge_pair_t(dcel.getHalfEdgeId(uw), M_uw);
194
195 curr_num_nodes += 2;
196 }
197
198 visited += n_to;
199 ++num_visited;
200
201 return true;
202 }
203
getPoints() const204 Matrix<Rational> getPoints() const
205 {
206 return Matrix<Rational>(points);
207 }
208
getTriangles() const209 const Array<Set<Int>>& getTriangles() const
210 {
211 return triangles;
212 }
213
getDcel() const214 DoublyConnectedEdgeList& getDcel() const
215 {
216 return dcel;
217 }
218
thirdVector(const Vector<Rational> & vec_u,const Vector<Rational> & vec_v,const Vector<Rational> & l_u,const Vector<Rational> & l_v,const Rational & a_uw,const Rational & a_vw,const Rational & a_uvw)219 Vector<Rational> thirdVector(const Vector<Rational>& vec_u, const Vector<Rational>& vec_v, const Vector<Rational>& l_u,
220 const Vector<Rational>& l_v, const Rational& a_uw, const Rational& a_vw, const Rational& a_uvw)
221 {
222 const Vector<Rational> u_cross_v{ vec_u[1]*vec_v[2] - vec_u[2]*vec_v[1],
223 vec_u[2]*vec_v[0] - vec_u[0]*vec_v[2],
224 vec_u[0]*vec_v[1] - vec_u[1]*vec_v[0] };
225 const Matrix<Rational> A = vector2row(l_u) / l_v / u_cross_v;
226 const Matrix<Rational> A_inv = inv(A);
227 const Vector<Rational> b{ a_uw, a_vw, a_uvw };
228 const Vector<Rational> third_vector = A_inv*b;
229 if (third_vector[0]+third_vector[1]+third_vector[2] < 0)
230 throw std::runtime_error("You should choose a different affine chart");
231 return A_inv*b;
232 }
233
thirdCoVector(const Vector<Rational> & vec_u,const Vector<Rational> & vec_v,const Vector<Rational> & vec_w,const Rational & a_wu,const Rational & a_wv)234 Vector<Rational> thirdCoVector(const Vector<Rational>& vec_u, const Vector<Rational>& vec_v, const Vector<Rational>& vec_w, const Rational& a_wu, const Rational& a_wv)
235 {
236 const Matrix<Rational> A = vector2row(vec_w) / vec_u / vec_v;
237 const Matrix<Rational> A_inv = inv(A);
238 const Vector<Rational> b{ Rational(0), a_wu, a_wv };
239 return A_inv*b;
240 }
241
242 }; // end class PotatoVisitor
243
244
245 class PotatoBuilder {
246 Graph<Directed> dual_tree; //part of the dual tree of the triangulation
247 Int cur_depth;
248 graph::BFSiterator< Graph<Directed>, graph::VisitorTag<PotatoVisitor> > bfs_it;
249
250 public:
251
252 // construct from a dcel and the horo matrix of the first edge
PotatoBuilder(DoublyConnectedEdgeList & dcel,const Matrix<Rational> & first_two_vertices,Int depth_in)253 PotatoBuilder(DoublyConnectedEdgeList& dcel, const Matrix<Rational>& first_two_vertices, Int depth_in)
254 : dual_tree(1) //start with a one-node graph
255 , cur_depth(depth_in)
256 , bfs_it(dual_tree, PotatoVisitor(dual_tree, dcel, first_two_vertices, depth_in), nodes(dual_tree).front()) {}
257
getDepth() const258 Int getDepth() const { return cur_depth; }
259
260 // get the covering triangulation up to a given depth
computeCoveringTriangulation()261 BigObject computeCoveringTriangulation()
262 {
263 Int num_nodes = 3*((1L << cur_depth)-1)+1; //number of nodes of a binary tree with ternary root of given depth
264 while (bfs_it.node_visitor().numVisited() < num_nodes) {
265 ++bfs_it;
266 }
267
268 const Matrix<Rational> points = ones_vector<Rational>() | bfs_it.node_visitor().getPoints();
269 const Array<Set<Int>> triangles = bfs_it.node_visitor().getTriangles();
270
271 return BigObject("fan::PolyhedralComplex<Rational>",
272 "POINTS", points,
273 "INPUT_POLYTOPES", triangles);
274 }
275
276 }; // end class PotatoBuilder
277
278
279 // Compute a finite part of the triangulation covering the convex RP^2 surface given by A-coordinates "a_coords" on the triangulation "dcel_data".
280 // The rows of the 2x2 matrix "first_two_vertices" are the vertices of the first edge in R^3 that covers the first edge.
281 // The triangulation is calculated up to depth "depth" in the dual spanning tree rooted at the first triangle.
282 // Set "lifted" true in order to produce the concrete decorated triangulation in R^3.
projective_potato(const Matrix<Int> & dcel_data,const Vector<Rational> & a_coords,const Matrix<Rational> & first_two_vertices,Int depth,OptionSet options)283 BigObject projective_potato(const Matrix<Int>& dcel_data, const Vector<Rational>& a_coords, const Matrix<Rational>& first_two_vertices,
284 Int depth, OptionSet options)
285 {
286 const bool lifted = options["lifted"];
287 DoublyConnectedEdgeList dcel(dcel_data);
288 dcel.setAcoords(a_coords);
289 PotatoBuilder pot(dcel, first_two_vertices, depth);
290 BigObject triang = pot.computeCoveringTriangulation();
291 if (lifted) return triang;
292
293 const Matrix<Rational> points = triang.give("POINTS");
294 const Matrix<Rational> scaled = dcel.normalize(points.minor(All, range_from(1)));
295 return BigObject("fan::PolyhedralComplex", mlist<Rational>(),
296 "POINTS", ones_vector<Rational>() | scaled,
297 "INPUT_POLYTOPES", triang.give("MAXIMAL_POLYTOPES"));
298 }
299
300 InsertEmbeddedRule("REQUIRE_APPLICATION fan\n\n");
301 UserFunction4perl("# @category Producing other objects\n"
302 "# Computes the triangulated convex projective set that covers the convex RP^2 surface."
303 "# The latter is given by the DCEL data for the triangulation of the surface along with A-coordinates (one positive Rational for each oriented edge and each triangle)."
304 "# Obviously, we only can compute a finite part of the infinite covering triangulation"
305 "# @param Matrix<Int> DCEL_data"
306 "# @param Vector<Rational> A_coords"
307 "# @param Matrix<Rational> first_two_vertices at the moment has to be the Matrix with rows (1,0,0),(0,1,0)"
308 "# @param Int depth"
309 "# @option Bool lifted for producing the lifted triangulation in R^3 with vertices in the light cone"
310 "# @return fan::PolyhedralComplex<Rational>"
311 "# @example The following computes a covering triangulation of a once punctured torus up to depth 5:"
312 "# > $T1 = new Matrix<Int>([[0,0,2,3,0,1],[0,0,4,5,0,1],[0,0,0,1,0,1]]);"
313 "# > $p = projective_potato($T1,new Vector([1,1,1,1,1,1,2,2]),new Matrix([[1,0,0],[0,1,0]]),1);"
314 "# > print $p->VERTICES;"
315 "# | 1 1 0 0"
316 "# | 1 0 1 0"
317 "# | 1 0 0 1"
318 "# | 1 1 1 -1"
319 "# | 1 -1/5 2/5 4/5"
320 "# | 1 2/5 -1/5 4/5",
321 &projective_potato, "projective_potato($ $ $ $ {lifted => 0})");
322 } }
323
324 // Local Variables:
325 // mode:C++
326 // c-basic-offset:3
327 // indent-tabs-mode:nil
328 // End:
329