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/fan/stacky_fundamental_domain.h"
20 #include "polymake/topaz/hasse_diagram.h"
21 #include "polymake/graph/LatticePermutation.h"
22 
23 namespace polymake { namespace fan {
24 
25    using graph::Lattice;
26    using graph::lattice::BasicDecoration;
27    using graph::lattice::Sequential;
28    using graph::lattice::Nonsequential;
29 
30 
31 template<typename Scalar>
32 Set<Int>
orbit_rep(const Matrix<Scalar> & subdiv_rays,const hash_set<Array<Int>> & entire_group,const Map<Vector<Scalar>,Int> & index_of_ray_orbit_member,const Set<Int> & face)33 orbit_rep(const Matrix<Scalar>& subdiv_rays,
34           const hash_set<Array<Int>>& entire_group,
35           const Map<Vector<Scalar>, Int>& index_of_ray_orbit_member,
36           const Set<Int>& face)
37 {
38    Set<Set<Int>> the_orbit;
39    Set<Int> image;
40    for (const Array<Int>& homog_coo_perm: entire_group) {
41       pm::operations::group::action<Vector<Scalar>, pm::operations::group::on_container, Array<Int>> this_action(homog_coo_perm);
42       image.clear();
43       for (const Int i: face)
44          image += index_of_ray_orbit_member[this_action(subdiv_rays[i])];
45       the_orbit += image;
46    }
47    return the_orbit.front();
48 }
49 
50 template<typename Scalar>
51 std::vector<Int>
find_facet_rep_indices(const Array<Set<Int>> & SD_facets,const Matrix<Scalar> & subdiv_rays,const Map<Vector<Scalar>,Int> & index_of_ray_orbit_member,const hash_set<Array<Int>> & entire_group,const Graph<> & dual_graph,const Int verbosity)52 find_facet_rep_indices(const Array<Set<Int>>& SD_facets,
53                        const Matrix<Scalar>& subdiv_rays,
54                        const Map<Vector<Scalar>, Int>& index_of_ray_orbit_member,
55                        const hash_set<Array<Int>>& entire_group,
56                        const Graph<>& dual_graph,
57                        const Int verbosity)
58 {
59    Set<Int> seen_indices {0};
60    std::list<Int> queue {0};
61    std::vector<Int> accepted_indices;
62    Set<Set<Int>> facet_reps;
63 
64    while (queue.size()) {
65       if (facet_reps.size() != convert_to<Int>(accepted_indices.size())) {
66          cerr << "problem: facet_reps (" << facet_reps.size() << "):\n" << facet_reps << endl
67               << "accepted_indices (" << accepted_indices.size() << "):\n" << accepted_indices << endl;
68          throw std::runtime_error("stop");
69       }
70       const Int current_index(queue.front()); queue.pop_front();
71       const Set<Int> rep = orbit_rep(subdiv_rays, entire_group, index_of_ray_orbit_member, SD_facets[current_index]);
72 
73       if (verbosity>2)
74          cerr << "\nqueue now " << queue << ", current_index: " << current_index << ": " << SD_facets[current_index]
75               << " -> " << rep << endl;
76 
77       if (facet_reps.collect(rep))
78          continue;
79 
80       accepted_indices.push_back(current_index);
81       if (verbosity > 2)
82          cerr << "accepted " << current_index << " = " << SD_facets[current_index] << endl;
83 
84       for (auto nb_iter = entire(dual_graph.adjacent_nodes(current_index)); !nb_iter.at_end(); ++nb_iter)
85          if (!seen_indices.contains(*nb_iter)) {
86             queue.push_back(*nb_iter);
87             seen_indices += *nb_iter;
88 
89             if (verbosity > 2)
90                cerr << "added neighbor " << *nb_iter << ": " << SD_facets[*nb_iter] << endl;
91          }
92    }
93    if (verbosity > 2)
94       cerr << "done with queue" << endl << endl;
95 
96    return accepted_indices;
97 }
98 
99 template<typename LabelType, typename Scalar>
100 struct FacetsLabelsCoordinates {
101    FacetsLabelsReordering<LabelType> flr;
102    Matrix<Scalar> coordinates;
103    Matrix<Scalar> used_original_coordinates;
104 };
105 
106 template<typename Scalar, typename FacetType, typename LabelType>
107 FacetsLabelsCoordinates<LabelType, Scalar>
squeeze_data(const FacetType & facets,const LabelType & labels,const Matrix<Scalar> & coordinates,const Int verbosity)108 squeeze_data(const FacetType& facets,
109              const LabelType& labels,
110              const Matrix<Scalar>& coordinates,
111              const Int verbosity)
112 {
113    FacetsLabelsCoordinates<LabelType, Scalar> squeezed_flc;
114    squeezed_flc.flr = squeeze_facets_and_labels(facets, labels, verbosity);
115    squeezed_flc.coordinates = coordinates.minor(squeezed_flc.flr.reordering, All);
116 
117    Set<Int> used_original_labels;
118    for (const auto& ssi: squeezed_flc.flr.labels)
119       used_original_labels += ssi.front();
120 
121    squeezed_flc.used_original_coordinates = coordinates.minor(used_original_labels, All);
122    return squeezed_flc;
123 }
124 
125 template<typename Scalar>
126 void
paranoid_consistency_test(const Array<Set<Int>> & SD_facets,const Matrix<Scalar> & subdiv_rays,const Map<Vector<Scalar>,Int> & index_of_ray_orbit_member,const hash_set<Array<Int>> & entire_group,const std::vector<Int> & accepted_indices)127 paranoid_consistency_test(const Array<Set<Int>>& SD_facets,
128                           const Matrix<Scalar>& subdiv_rays,
129                           const Map<Vector<Scalar>, Int>& index_of_ray_orbit_member,
130                           const hash_set<Array<Int>>& entire_group,
131                           const std::vector<Int>& accepted_indices)
132 {
133    cerr << "paranoid_consistency_test" << endl;
134    Set<Set<Int>> accepted_reps;
135    for (const Int i: accepted_indices)
136       accepted_reps += orbit_rep(subdiv_rays, entire_group, index_of_ray_orbit_member, SD_facets[i]);
137    if (accepted_reps.size() != convert_to<Int>(accepted_indices.size())) {
138       cerr << "problem: accepted_indices(" << accepted_indices.size() << "):\n" << accepted_indices << endl
139            << "accepted_reps(" << accepted_reps.size() << "):\n" << accepted_reps << endl << endl;
140       throw std::runtime_error("stop");
141    }
142    cerr << "Each rep represents one orbit" << endl;
143 
144    Set<Set<Int>> all_reps;
145    for (const auto& f: SD_facets)
146       all_reps += orbit_rep(subdiv_rays, entire_group, index_of_ray_orbit_member, f);
147    if (all_reps != accepted_reps) {
148       cerr << "problem: all_reps(" << all_reps.size() << "):\n" << all_reps << endl
149            << "accepted_reps(" << accepted_reps.size() << "):\n" << accepted_reps << endl << endl;
150       throw std::runtime_error("stop");
151    }
152    cerr << "Each original simplex has a representative" << endl;
153 
154    Map<Set<Int>,Int> index_of_facet_image;
155    for (auto fit = entire<indexed>(SD_facets); !fit.at_end(); ++fit)
156       index_of_facet_image[*fit] = fit.index();
157 
158 
159    Map<Vector<Scalar>, Int> index_of_ray;
160    for (auto rit = entire<indexed>(rows(subdiv_rays)); !rit.at_end(); ++rit)
161       index_of_ray[Vector<Scalar>(*rit)] = rit.index();
162 
163    Set<Int> seen_indices;
164    std::vector<Set<Int>> image_indices_store;
165    for (Int i: accepted_indices) {
166       Set<Int> image_indices;
167       Set<Set<Int>> image_simplices;
168       Set<Int> image_simplex;
169       for (const auto& g: entire_group) {
170          pm::operations::group::action<Vector<Scalar>, pm::operations::group::on_container, Array<Int>> this_action(g);
171          image_simplex.clear();
172          for (const Int j: SD_facets[i]) {
173             const Vector<Scalar> ray_image(this_action(subdiv_rays[j]));
174             if (!index_of_ray.contains(ray_image)) {
175                const Int next_index(index_of_ray.size());
176                index_of_ray[ray_image] = next_index;
177             }
178             image_simplex += index_of_ray[ray_image];
179          }
180          if (!index_of_facet_image.contains(image_simplex)) {
181             const Int next_index(index_of_facet_image.size());
182             index_of_facet_image[image_simplex] = next_index;
183          }
184          image_indices += index_of_facet_image[image_simplex];
185          image_simplices += image_simplex;
186       }
187       seen_indices += image_indices;
188       image_indices_store.push_back(image_indices);
189    }
190 
191    if (incl(Set<Int>(sequence(0, SD_facets.size())), seen_indices) > 0)  {
192       cerr << "problem: have not exhausted all simplices:\n" << seen_indices << endl;
193       throw std::runtime_error("stop");
194    }
195 
196    cerr << "reps represent all simplices." << endl;
197 
198    for (std::size_t i=0; i<image_indices_store.size() - 1; ++i)
199       for (std::size_t j=i+1; j<image_indices_store.size(); ++j)
200          if ((image_indices_store[i] * image_indices_store[j]).size()) {
201             cerr << "...but some simplices are not uniquely represented:" << endl
202                  << i << ": " << image_indices_store[i] << endl
203                  << j << ": " << image_indices_store[j] << endl << endl
204                  << "store:\n";
205             for (std::size_t k=0; k <= std::max(i,j); ++k)
206                cerr << k << ": " << image_indices_store[k] << endl;
207             throw std::runtime_error("stop");
208          }
209 
210    cerr << "test passed. size of fundamental domain: " << accepted_indices.size() << endl << endl;
211 }
212 
213 template<typename Scalar>
214 BigObject
stacky_fundamental_domain(BigObject StackyFan,OptionSet options)215 stacky_fundamental_domain(BigObject StackyFan, OptionSet options)
216 {
217    const Int verbosity = options["verbosity"];
218    if (verbosity)
219       cerr << "\n*** in stacky_fundamental_domain ***" << endl;
220 
221    const Matrix<Scalar> generating_rays = StackyFan.give("GENERATING_RAYS");
222    if (verbosity > 2) {
223       cerr << "generating_rays:\n";
224       for (Int i=0; i<generating_rays.rows(); ++i)
225          cerr << i << ": " << generating_rays[i] << endl;
226       cerr << endl << endl;
227    }
228 
229    const bool has_trivial_group(!StackyFan.exists("GROUP"));
230    if (has_trivial_group && verbosity > 2)
231       cerr << "trivial automorphism group" << endl << endl;
232 
233    const Lattice<BasicDecoration, Sequential> HD = StackyFan.give("GENERATING_HASSE_DIAGRAM");
234 
235    const auto facets_and_labels = topaz::first_barycentric_subdivision(HD);
236    const auto& SD_facets(facets_and_labels.facets);
237    const auto& SD_labels(facets_and_labels.labels);
238 
239    if (verbosity > 2) {
240       cerr << "SD_labels (" << SD_labels.size() << "):\n";
241       for (Int i=0; i<SD_labels.size(); ++i)
242          cerr << i << ": " << SD_labels[i] << endl;
243       cerr << endl << endl
244            << "SD_facets (" << SD_facets.size() << "):\n" << SD_facets << endl << endl;
245    }
246 
247    Matrix<Scalar> subdiv_rays(SD_labels.size(), generating_rays.cols());
248    for (Int i=0; i<SD_labels.size(); ++i)
249       subdiv_rays[i] = accumulate(rows(generating_rays.minor(SD_labels[i].front(), All)), operations::add());
250    if (verbosity > 2) {
251       cerr << "subdiv_rays:\n";
252       for (Int i=0; i<subdiv_rays.rows(); ++i)
253          cerr << i << ": " << subdiv_rays[i] << endl;
254       cerr << endl << endl;
255    }
256 
257    if (has_trivial_group) {
258       BigObject fd("topaz::GeometricSimplicialComplex", mlist<Scalar>(),
259                    "COORDINATES", subdiv_rays,
260                    "FACETS", SD_facets);
261       fd.attach("IS_FUNDAMENTAL_DOMAIN") << bool(true);
262       fd.attach("VERTEX_LABELS_AS_SETS") << SD_labels;
263       fd.attach("USED_ORIGINAL_COORDINATES") << subdiv_rays;
264 
265       return fd;
266    }
267 
268    const Array<Array<Int>> homog_action_generators = StackyFan.give("GROUP.HOMOGENEOUS_COORDINATE_ACTION.GENERATORS");
269    if (verbosity > 3)
270       cerr << "action generators:\n" << homog_action_generators << endl;
271 
272    Int max_index(0);
273    Map<Vector<Scalar>, Int> index_of_ray_orbit_member;
274    for (const auto& v: rows(subdiv_rays)) {
275       const auto the_orbit(group::unordered_orbit<pm::operations::group::on_container,
276                                                   Array<Int>, Vector<Scalar>>(homog_action_generators, Vector<Scalar>(v)));
277 
278       // if one element of the orbit has already been indexed, go to the next orbit
279       if (index_of_ray_orbit_member.exists(*(the_orbit.begin())))
280          continue;
281       for (const auto& member: the_orbit)
282          if (!index_of_ray_orbit_member.exists(member))
283             index_of_ray_orbit_member[member] = max_index++;
284    }
285 
286    if (verbosity > 2)
287       cerr << "\nindex_of_ray_orbit_member (" << index_of_ray_orbit_member.size() << "):\n" << index_of_ray_orbit_member << endl << endl;
288 
289    const hash_set<Array<Int>> entire_group = group::unordered_orbit<pm::operations::group::on_container,
290                                                                     Array<Int>, Array<Int>>(homog_action_generators, Array<Int>(sequence(0,homog_action_generators[0].size())));
291    if (verbosity)
292       cerr << "group size: " << entire_group.size() << endl;
293    if (verbosity > 2)
294       cerr << "entire_group:\n" << entire_group << endl << endl;
295 
296    if (verbosity)
297       cerr << "calculating dual graph of complex with " << SD_facets.size() << " facets and " << SD_labels.size() << " vertices" << endl;
298 
299    BigObject SC("topaz::SimplicialComplex",
300                 "FACETS", SD_facets);
301    const Graph<> dual_graph = SC.give("DUAL_GRAPH.ADJACENCY");
302    if (verbosity > 2)
303       cerr << "dual graph:\n" << dual_graph << endl << endl;
304 
305    const std::vector<Int> facet_rep_indices = find_facet_rep_indices(SD_facets, subdiv_rays, index_of_ray_orbit_member, entire_group, dual_graph, verbosity);
306    if (verbosity)
307       cerr << "fundamental domain in first barycentric subdivision has "
308            << facet_rep_indices.size() << " simplices out of " << SD_facets.size() << endl;
309    if (verbosity > 2)
310       cerr << "accepted facet_rep_indices:\n" << facet_rep_indices << endl << endl
311            << "corresponding to facets\n" << select(SD_facets, facet_rep_indices) << endl << endl;
312 
313    if (verbosity > 2)
314       paranoid_consistency_test(SD_facets, subdiv_rays, index_of_ray_orbit_member, entire_group, facet_rep_indices);
315 
316    const auto squeezed_flc = squeeze_data(select(SD_facets, facet_rep_indices), SD_labels, subdiv_rays, verbosity);
317    if (verbosity > 2) {
318       cerr << "squeezed facets:\n" << squeezed_flc.flr.facets
319            << "\n\nsqueezed labels:\n";
320       for (auto it = entire<indexed>(squeezed_flc.flr.labels); !it.at_end(); ++it)
321          cerr << it.index() << ": " << *it << endl;
322 
323       cerr << "\n\nsqueezed coordinates:\n" << squeezed_flc.coordinates
324            << "\n\nused original coordinates:\n" << squeezed_flc.used_original_coordinates << endl << endl;
325    }
326 
327    BigObject hc_a("group::PermutationAction", "GENERATORS", homog_action_generators);
328    BigObject g("group::Group", "Aut",
329                "HOMOGENEOUS_COORDINATE_ACTION", hc_a);
330 
331 
332    BigObject fd("topaz::GeometricSimplicialComplex", mlist<Scalar>(),
333                 "COORDINATES", squeezed_flc.coordinates,
334                 "FACETS", squeezed_flc.flr.facets,
335                 "GROUP", g);
336    fd.attach("IS_FUNDAMENTAL_DOMAIN") << bool(true);
337    fd.attach("VERTEX_LABELS_AS_SETS") << squeezed_flc.flr.labels;
338    fd.attach("USED_ORIGINAL_COORDINATES") << squeezed_flc.used_original_coordinates;
339    return fd;
340 }
341 
342 UserFunctionTemplate4perl("# @category Symmetry"
343                           "# Find a fundamental domain for a cone modulo the action of a symmetry group."
344                           "# The fundamental domain will be a subcomplex, with connected DUAL_GRAPH,"
345                           "# of the first barycentric subdivision that is found via a breadth-first search."
346                           "# @param DisjointStackyFan F"
347                           "# @return topaz::GeometricSimplicialComplex",
348                           "stacky_fundamental_domain<Scalar>(DisjointStackyFan<Scalar>, { verbosity=>0 })");
349 
350 } }
351 
352 // Local Variables:
353 // mode:C++
354 // c-basic-offset:3
355 // indent-tabs-mode:nil
356 // End:
357 
358