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/graph/Lattice.h"
20 #include "polymake/graph/ShrinkingLattice.h"
21 #include "polymake/graph/Decoration.h"
22 #include "polymake/Array.h"
23 #include "polymake/topaz/morse_matching_tools.h"
24 #include <cassert>
25
26
27 namespace polymake { namespace topaz {
28
29 namespace {
30
31 using namespace morse_matching_tools;
32
33 /**@brief Compute lexicographic ordering of the edges
34 * @param M subset of the arcs
35 * @param order output: order of arcs
36 * @param bottomLevel lowest level
37 * @param topLevel hightest level
38 *
39 * - We loop through each level, beginning at the top
40 * - We sort the faces in a level lexicographically.
41 * - For each face @a F in the lexicographic order,
42 * we sort its facets lexicographically and insert
43 * the corresponding arc numbers into the order.
44 *
45 * @a order is a std::vector, because we want to change its size dynamically.
46 * Might be replaced by @c Array and precomputation of the size.
47 */
orderEdgesLex(const graph::ShrinkingLattice<graph::lattice::BasicDecoration> & M,std::vector<Int> & order,Int bottomLevel,Int topLevel)48 void orderEdgesLex(const graph::ShrinkingLattice<graph::lattice::BasicDecoration>& M, std::vector<Int>& order, Int bottomLevel, Int topLevel)
49 {
50 const Int d = M.rank()-2; // do not count empty face
51
52 order.clear();
53
54 // create edge-number map
55 EdgeMap<Directed, Int> E(M.graph());
56
57 Int m = 0;
58 for (Int k = 0; k < d; ++k)
59 for (const auto f : M.nodes_of_rank(k+1))
60 for (auto e = M.out_edges(f).begin(); !e.at_end(); ++e)
61 E[*e] = m++;
62
63 // loop over levels, starting from the top most
64 for (Int k = topLevel; k >= bottomLevel; --k) {
65 // collect all nodes of dimension k
66 std::vector<Set<Int> > FS; // vertex sets of faces
67 std::vector<Int> FN; // face numbers
68 std::vector<Int> index; // indices to sort
69 const Int n_nodes = M.nodes_of_rank(k+1).size();
70 FS.reserve(n_nodes); FN.reserve(n_nodes); index.reserve(n_nodes);
71 Int i = 0;
72 for (const auto f : M.nodes_of_rank(k+1)) {
73 const Set<Int>& F = M.face(f); // get vertex set corresponding to face
74 FS.push_back(F); // store set and
75 FN.push_back(f); // face number
76 index.push_back(i++);
77 }
78
79 // sort
80 CompareByProperty<Int, std::vector<Set<Int>>> cmp(FS);
81 std::sort(index.begin(), index.end(), cmp);
82
83 // pass through faces of dim k in lexicographic order
84 for (const Int fi : index)
85 {
86 const Set<Int> F = FS[fi]; // get current face
87
88 // collect neighbors of F
89 std::vector<Set<Int>> N;
90 std::vector<Int> EN;
91 std::vector<Int> index2;
92 const Int n_edges = M.in_edges(FN[fi]).size();
93 N.reserve(n_edges); EN.reserve(n_edges); index2.reserve(n_edges);
94 Int l = 0;
95 for (auto e = M.in_edges(FN[fi]).begin(); !e.at_end(); ++e)
96 {
97 const Set<Int> S = M.face(e.from_node()); // get vertex set corr. to neighbor
98 N.push_back(S); // store it
99 EN.push_back(E[*e]); // store edge number
100 index2.push_back(l++);
101 }
102
103 // sort
104 CompareByProperty<Int, std::vector<Set<Int>>> Cmp(N);
105 std::sort(index2.begin(), index2.end(), Cmp);
106
107 /// pass through neighbors in lexicographic order
108 for (const Int fi2 : index2)
109 order.push_back(EN[fi2]);
110 }
111 }
112 }
113
114 #ifndef NDEBUG
115 #if 0
116 // these two are unused now
117
118 /**@brief Check whether a given solution is acyclic
119 * @param M collection of arcs in Hasse diagram
120 * @param marked marker for DFS-search (node has been visited)
121 * @param base base value for marker (see @c checkAcyclicDFS())
122 * @returns @c true if the solution is acyclic
123 *
124 * We assume that @c marked has been initialized some time ago.
125 * @warning @a base is changed!
126 */
127 bool checkAcyclic(const graph::ShrinkingLattice<graph::lattice::BasicDecoration>& M, const MorseEdgeMap& EM, Array<Int>& marked, Int& base)
128 {
129 const Int d = M.rank()-2; // do not count empty face and top face
130
131 // check each level in turn
132 for (Int k = 0; k < d; ++k) {
133 // increase base
134 base += 2;
135
136 // check each face of dim k
137 for (const auto v : M.nodes_of_rank(k+1)) {
138 assert( v > 0 && v <= (M.nodes()-2) );
139
140 // for each unmarked face
141 if (marked[v] < base) {
142 const bool acyclic = checkAcyclicDFS(M, EM, marked, v, true, base);
143 if (! acyclic)
144 return false;
145 }
146 }
147 }
148 return true;
149 }
150
151
152 /**@brief Check whether a given solution is acyclic
153 * @param M collection of arcs in Hasse diagram
154 * @param k dimension of the level to be checked
155 * @param marked marker for DFS-search (node has been visited)
156 * @param base base value for marker (see @c checkAcyclicDFS())
157 * @returns @c true if the solution is acyclic
158 *
159 * We assume that @c marked has been initialized some time ago.
160 */
161 bool checkAcyclic(const graph::ShrinkingLattice<graph::lattice::BasicDecoration>& M, const MorseEdgeMap& EM, Int k, Array<Int>& marked, Int base)
162 {
163 // check each face of dim k
164 for (const auto v : M.nodes_of_rank(k+1)) {
165 assert( v > 0 && v <= (M.nodes()-2) );
166
167 // for each unmarked face
168 if (marked[v] < base) {
169 const bool acyclic = checkAcyclicDFS(M, EM, marked, v, true, base);
170 if (! acyclic)
171 return false;
172 }
173 }
174 return true;
175 }
176 #endif
177
178 /**@brief Check whether a given solution is acyclic
179 * @param M collection of arcs in Hasse diagram
180 * @returns @c true if the solution is acyclic
181 */
checkAcyclic(const graph::ShrinkingLattice<graph::lattice::BasicDecoration> & M,const MorseEdgeMap & EM)182 bool checkAcyclic(const graph::ShrinkingLattice<graph::lattice::BasicDecoration>& M, const MorseEdgeMap& EM)
183 {
184 const Int d = M.rank()-2; // do not count empty face
185 const Int n = M.nodes()-2; // and top face
186
187 Array<Int> marked(n+1);
188 // initialize markers
189 for (Int i = 0; i <= n; ++i)
190 marked[i] = 0;
191
192 // for each level in turn
193 Int base = -1;
194 for (Int k = 0; k < d; ++k) {
195 // increase base
196 base += 2;
197
198 // check each face of dim k
199 for (const auto v : M.nodes_of_rank(k+1)) {
200 assert( (v > 0) && (v <= n) );
201
202 // for each unmarked face
203 if (marked[v] < base) {
204 if (!checkAcyclicDFS(M, EM, marked, v, true, base))
205 return false;
206 }
207 }
208 }
209 return true;
210 }
211
212
213 /**@brief Check whether the given collection of arcs satisfies the matching constraints
214 * @param M collection of arcs in Hasse diagram
215 * @returns @c true if the given arc set is a matching
216 */
checkMatching(const graph::ShrinkingLattice<graph::lattice::BasicDecoration> & M,const MorseEdgeMap & EM)217 bool checkMatching(const graph::ShrinkingLattice<graph::lattice::BasicDecoration>& M, const MorseEdgeMap& EM)
218 {
219 #if POLYMAKE_DEBUG
220 const bool debug_print = get_debug_level() > 1;
221 #endif
222
223 const Int d = M.rank()-2;
224
225 // for each level in turn
226 for (Int k = 0; k <= d; ++k) {
227 // check each face of dim k
228 for (const auto f : M.nodes_of_rank(k+1)) {
229 Int inc = 0; // count incident arcs
230 if (k > 0) {
231 for (auto e = M.in_edges(f).begin(); !e.at_end(); ++e) {
232 // if arc is in collection, i.e. reverse we have an incident arc
233 if (EM[*e])
234 ++inc;
235 // if a node has more than two incident arcs the matching property is violated
236 if (inc >= 2) {
237 #if POLYMAKE_DEBUG
238 if (debug_print) cout << "Matching propery violated for node " << f << "=" << M.face(f) << endl;
239 #endif
240 return false;
241 }
242 }
243 }
244 if (k < d) {
245 for (auto e = M.out_edges(f).begin(); !e.at_end(); ++e) {
246 // if arc is in collection, i.e. reverse we have an incident arc
247 if (EM[*e])
248 ++inc;
249 // if a node has more than two incident arcs the matching property is violated
250 if (inc >= 2) {
251 #if POLYMAKE_DEBUG
252 if (debug_print) cout << "Matching propery violated for node " << f << "=" << M.face(f) << endl;
253 #endif
254 return false;
255 }
256 }
257 }
258 }
259 }
260 return true;
261 }
262 #endif
263 #if POLYMAKE_DEBUG
print_reversed_edges(const graph::ShrinkingLattice<graph::lattice::BasicDecoration> & M,const MorseEdgeMap & EM)264 void print_reversed_edges(const graph::ShrinkingLattice<graph::lattice::BasicDecoration>& M, const MorseEdgeMap& EM)
265 {
266 cout << "critical Morse edges:\n";
267 for (auto e = entire(edges(M.graph())); !e.at_end(); ++e)
268 if (EM[*e])
269 cout << "(" << M.face(e.from_node()) << "," << M.face(e.to_node()) << ")";
270 cout << endl;
271 }
272 #endif
273 }
274
275 // Compute a Morse matching. Two heuristics are implemented:
276 //
277 // (1) A simple greedy algorithm:
278 // The arcs are visited in lexicographical order, i.e.
279 // we proceed by levels from top to bottom,
280 // visit the faces in each dimension in lexicographical order,
281 // and visited the faces covered by these faces in lexicographical order.
282 // This heuristic is used by default and with heuristic => 1.
283 //
284 // (2) A Morse matching can be improved by canceling critical cells
285 // along unique alternating paths, see function
286 // processAlternatingPaths() in file morse_matching_tools.cc .
287 // This idea is due to Robin Forman:
288 // Morse Theory for Cell-Complexes,
289 // Advances in Math., 134 (1998), pp. 90-145.
290 // This heuristic is used by default and with heuristic => 2.
291 //
292 // default setting is to use both, i.e., to run the greedy algorithm
293 // and then improve the result by the canceling algorithm.
294 //
295 // Morse matchings for the bottom level can be found optimally by
296 // spanning tree techniques. This can be enabled by the option
297 // levels => 1. If the complex is a pseudo-manifold the same can be
298 // done for the top level (option levels => 2). By specifying option
299 // levels => 0, both levels can be computed by spanning trees.
300 // For 2-dim pseudo-manifolds this computes an optimal Morse matching.
301
302
303
morse_matching(BigObject p,OptionSet options)304 MorseEdgeMap morse_matching(BigObject p, OptionSet options)
305 {
306 // determine which heuristic to run
307 bool runGreedy = true; // default is to turn both heuristics on
308 bool runCancel = true;
309 const Int heuristic = options["heuristic"];
310 if (heuristic == 1) runCancel = false;
311 if (heuristic == 2) runGreedy = false;
312
313 #if POLYMAKE_DEBUG
314 const bool debug_print = get_debug_level() > 1;
315 if (debug_print)
316 cout << "heuristic: " << heuristic
317 << ", runGreedy=" << runGreedy
318 << ", runCancel=" << runCancel << endl;
319 #endif
320
321 graph::Lattice<graph::lattice::BasicDecoration> M_obj = p.give("HASSE_DIAGRAM");
322 graph::ShrinkingLattice<graph::lattice::BasicDecoration> M(M_obj); // not const, will be modified
323 const Int d = M.rank()-2;
324 Int size = 0;
325
326 #if POLYMAKE_DEBUG
327 if (debug_print) cout << "d: " << d << endl;
328 #endif
329
330 // find lowest and highest levels
331 Int bottomLevel = 0;
332 Int topLevel = d;
333 const Int levels = options["levels"];
334 if (levels == 1)
335 bottomLevel = 1;
336 if (levels == 2)
337 topLevel = d-1;
338 if (levels == 0) {
339 bottomLevel = 1;
340 topLevel = d-1;
341 }
342
343 #if POLYMAKE_DEBUG
344 if (debug_print)
345 cout << "levels: " << levels
346 << ", bottomLevel=" << bottomLevel
347 << ", topLevel=" << topLevel << endl;
348 #endif
349
350 MorseEdgeMap EM(M.graph());
351
352 #if POLYMAKE_DEBUG
353 if (debug_print) cout << "Initialized EdgeMap" << endl;
354 #endif
355
356 // check whether complex is a pseudo-manifold
357 if (topLevel < d) {
358 const bool is_pmf = p.give("PSEUDO_MANIFOLD");
359 if (!is_pmf)
360 throw std::runtime_error("Error. Complex is not a pseudo-manifold, which is necessary for option levels != 1.");
361 }
362
363 // count arcs and fill up maps
364 std::vector<Int> varLevel;
365 varLevel.reserve(M.edges()); // maybe a little bit too much, but more efficient than allocating incrementally
366 Int m = 0; // number of arcs
367 Int numFaces = 0; // number of faces
368 for (Int k = 0; k < d; ++k)
369 for (auto f = entire(M.nodes_of_rank(k+1)); !f.at_end(); ++f, ++numFaces)
370 for (auto e = entire(M.out_edges(*f)); !e.at_end(); ++e, ++m)
371 varLevel.push_back(k);
372
373 #if POLYMAKE_DEBUG
374 if (debug_print)
375 cout << "Dimension of complex: " << d << "\n"
376 "Faces in complex: " << numFaces << "\n"
377 "Arcs in Hasse Diagram: " << m << "\n"
378 "Bottom level: " << bottomLevel << "\n"
379 "Top level: " << topLevel << endl;
380 #endif
381
382
383 // run greedy algorithm if requested
384 if (runGreedy) {
385 #if POLYMAKE_DEBUG
386 if (debug_print) cout << "\nComputing Morse matching by greedy heuristic ..." << endl;
387 #endif
388 // compute lexicographic order
389 std::vector<Int> varOrder;
390 varOrder.reserve(M.edges());
391 orderEdgesLex(M, varOrder, bottomLevel, topLevel);
392
393 // compute greedy solution
394 size = greedyHeuristic(M, EM, varLevel, varOrder.begin(), varOrder.end());
395 assert( checkMatching(M, EM) );
396 assert( checkAcyclic(M, EM) );
397
398 #if POLYMAKE_DEBUG
399 if (debug_print) {
400 cout << "Found solution of size: " << size << endl;
401 print_reversed_edges(M, EM);
402 cout << "Number of critical faces: " << numFaces-2*size << endl;
403 }
404 #endif
405 }
406
407 // run canceling algorithm if requested
408 if (runCancel) {
409 #if POLYMAKE_DEBUG
410 if (debug_print) cout << "\nTrying to improve Morse matching by canceling algorithm ..." << endl;
411 #endif
412 processAlternatingPaths(M, EM, size, bottomLevel, topLevel);
413 assert( checkMatching(M, EM) );
414 assert( checkAcyclic(M, EM) );
415
416 #if POLYMAKE_DEBUG
417 if (debug_print) {
418 cout << "\nFound solution of size: " << size << endl;
419 print_reversed_edges(M, EM);
420 cout << "Number of critical faces: " << numFaces-2*size << endl;
421 }
422 #endif
423 }
424
425 // complete solution to top level
426 if (topLevel < d) {
427 #if POLYMAKE_DEBUG
428 if (debug_print) cout << "\nCompleting to top level ..." << endl;
429 #endif
430 completeToTopLevel(M, EM);
431 assert( checkMatching(M, EM) );
432 assert( checkAcyclic(M, EM) );
433
434 #if POLYMAKE_DEBUG
435 if (debug_print) {
436 size = count_marked_edges(EM);
437 cout << "Found solution of size: " << size << endl;
438 print_reversed_edges(M, EM);
439 cout << "Number of critical faces: " << numFaces-2*size << endl;
440 }
441 #endif
442 }
443
444 // complete solution to bottom level
445 if (bottomLevel > 0) {
446 #if POLYMAKE_DEBUG
447 if (debug_print) cout << "\nCompleting to bottom level ..." << endl;
448 #endif
449 completeToBottomLevel(M, EM);
450 assert( checkMatching(M, EM) );
451 assert( checkAcyclic(M, EM) );
452
453 #if POLYMAKE_DEBUG
454 if (debug_print) {
455 size = count_marked_edges(EM);
456 cout << "Found solution of size: " << size << endl;
457 print_reversed_edges(M, EM);
458 cout << "Number of critical faces: " << numFaces-2*size << endl;
459 }
460 #endif
461 }
462
463 return EM;
464 }
465
466 Function4perl(&morse_matching, "morse_matching($ { heuristic => 0, levels => 0 })");
467
468 } }
469
470 // Local Variables:
471 // mode:C++
472 // c-basic-offset:3
473 // indent-tabs-mode:nil
474 // End:
475