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