passagemath-graphs 10.6.1rc1__cp310-cp310-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. passagemath_graphs-10.6.1rc1.dist-info/METADATA +292 -0
  2. passagemath_graphs-10.6.1rc1.dist-info/RECORD +260 -0
  3. passagemath_graphs-10.6.1rc1.dist-info/WHEEL +5 -0
  4. passagemath_graphs-10.6.1rc1.dist-info/top_level.txt +2 -0
  5. passagemath_graphs.libs/libgcc_s-69c45f16.so.1 +0 -0
  6. passagemath_graphs.libs/libgmp-8e78bd9b.so.10.5.0 +0 -0
  7. passagemath_graphs.libs/libstdc++-1f1a71be.so.6.0.33 +0 -0
  8. sage/all__sagemath_graphs.py +39 -0
  9. sage/combinat/abstract_tree.py +2723 -0
  10. sage/combinat/all__sagemath_graphs.py +34 -0
  11. sage/combinat/binary_tree.py +5306 -0
  12. sage/combinat/cluster_algebra_quiver/all.py +22 -0
  13. sage/combinat/cluster_algebra_quiver/cluster_seed.py +5208 -0
  14. sage/combinat/cluster_algebra_quiver/interact.py +124 -0
  15. sage/combinat/cluster_algebra_quiver/mutation_class.py +625 -0
  16. sage/combinat/cluster_algebra_quiver/mutation_type.py +1555 -0
  17. sage/combinat/cluster_algebra_quiver/quiver.py +2290 -0
  18. sage/combinat/cluster_algebra_quiver/quiver_mutation_type.py +2468 -0
  19. sage/combinat/designs/MOLS_handbook_data.py +570 -0
  20. sage/combinat/designs/all.py +58 -0
  21. sage/combinat/designs/bibd.py +1655 -0
  22. sage/combinat/designs/block_design.py +1071 -0
  23. sage/combinat/designs/covering_array.py +269 -0
  24. sage/combinat/designs/covering_design.py +530 -0
  25. sage/combinat/designs/database.py +5615 -0
  26. sage/combinat/designs/design_catalog.py +122 -0
  27. sage/combinat/designs/designs_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  28. sage/combinat/designs/designs_pyx.pxd +21 -0
  29. sage/combinat/designs/designs_pyx.pyx +993 -0
  30. sage/combinat/designs/difference_family.py +3951 -0
  31. sage/combinat/designs/difference_matrices.py +279 -0
  32. sage/combinat/designs/evenly_distributed_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  33. sage/combinat/designs/evenly_distributed_sets.pyx +661 -0
  34. sage/combinat/designs/ext_rep.py +1064 -0
  35. sage/combinat/designs/gen_quadrangles_with_spread.cpython-310-aarch64-linux-gnu.so +0 -0
  36. sage/combinat/designs/gen_quadrangles_with_spread.pyx +339 -0
  37. sage/combinat/designs/group_divisible_designs.py +361 -0
  38. sage/combinat/designs/incidence_structures.py +2357 -0
  39. sage/combinat/designs/latin_squares.py +581 -0
  40. sage/combinat/designs/orthogonal_arrays.py +2244 -0
  41. sage/combinat/designs/orthogonal_arrays_build_recursive.py +1780 -0
  42. sage/combinat/designs/orthogonal_arrays_find_recursive.cpython-310-aarch64-linux-gnu.so +0 -0
  43. sage/combinat/designs/orthogonal_arrays_find_recursive.pyx +967 -0
  44. sage/combinat/designs/resolvable_bibd.py +815 -0
  45. sage/combinat/designs/steiner_quadruple_systems.py +1306 -0
  46. sage/combinat/designs/subhypergraph_search.cpython-310-aarch64-linux-gnu.so +0 -0
  47. sage/combinat/designs/subhypergraph_search.pyx +530 -0
  48. sage/combinat/designs/twographs.py +306 -0
  49. sage/combinat/finite_state_machine.py +14874 -0
  50. sage/combinat/finite_state_machine_generators.py +2006 -0
  51. sage/combinat/graph_path.py +448 -0
  52. sage/combinat/interval_posets.py +3908 -0
  53. sage/combinat/nu_tamari_lattice.py +269 -0
  54. sage/combinat/ordered_tree.py +1446 -0
  55. sage/combinat/posets/all.py +46 -0
  56. sage/combinat/posets/bubble_shuffle.py +247 -0
  57. sage/combinat/posets/cartesian_product.py +493 -0
  58. sage/combinat/posets/d_complete.py +182 -0
  59. sage/combinat/posets/elements.py +273 -0
  60. sage/combinat/posets/forest.py +30 -0
  61. sage/combinat/posets/hasse_cython.cpython-310-aarch64-linux-gnu.so +0 -0
  62. sage/combinat/posets/hasse_cython.pyx +174 -0
  63. sage/combinat/posets/hasse_diagram.py +3672 -0
  64. sage/combinat/posets/hochschild_lattice.py +158 -0
  65. sage/combinat/posets/incidence_algebras.py +794 -0
  66. sage/combinat/posets/lattices.py +5117 -0
  67. sage/combinat/posets/linear_extension_iterator.cpython-310-aarch64-linux-gnu.so +0 -0
  68. sage/combinat/posets/linear_extension_iterator.pyx +292 -0
  69. sage/combinat/posets/linear_extensions.py +1037 -0
  70. sage/combinat/posets/mobile.py +275 -0
  71. sage/combinat/posets/moebius_algebra.py +776 -0
  72. sage/combinat/posets/poset_examples.py +2178 -0
  73. sage/combinat/posets/posets.py +9360 -0
  74. sage/combinat/rooted_tree.py +1070 -0
  75. sage/combinat/shard_order.py +239 -0
  76. sage/combinat/tamari_lattices.py +384 -0
  77. sage/combinat/yang_baxter_graph.py +923 -0
  78. sage/databases/all__sagemath_graphs.py +1 -0
  79. sage/databases/knotinfo_db.py +1231 -0
  80. sage/ext_data/all__sagemath_graphs.py +1 -0
  81. sage/ext_data/graphs/graph_plot_js.html +330 -0
  82. sage/ext_data/kenzo/CP2.txt +45 -0
  83. sage/ext_data/kenzo/CP3.txt +349 -0
  84. sage/ext_data/kenzo/CP4.txt +4774 -0
  85. sage/ext_data/kenzo/README.txt +49 -0
  86. sage/ext_data/kenzo/S4.txt +20 -0
  87. sage/graphs/all.py +42 -0
  88. sage/graphs/asteroidal_triples.cpython-310-aarch64-linux-gnu.so +0 -0
  89. sage/graphs/asteroidal_triples.pyx +320 -0
  90. sage/graphs/base/all.py +1 -0
  91. sage/graphs/base/boost_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  92. sage/graphs/base/boost_graph.pxd +106 -0
  93. sage/graphs/base/boost_graph.pyx +3045 -0
  94. sage/graphs/base/c_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  95. sage/graphs/base/c_graph.pxd +106 -0
  96. sage/graphs/base/c_graph.pyx +5096 -0
  97. sage/graphs/base/dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  98. sage/graphs/base/dense_graph.pxd +28 -0
  99. sage/graphs/base/dense_graph.pyx +801 -0
  100. sage/graphs/base/graph_backends.cpython-310-aarch64-linux-gnu.so +0 -0
  101. sage/graphs/base/graph_backends.pxd +5 -0
  102. sage/graphs/base/graph_backends.pyx +797 -0
  103. sage/graphs/base/overview.py +85 -0
  104. sage/graphs/base/sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  105. sage/graphs/base/sparse_graph.pxd +90 -0
  106. sage/graphs/base/sparse_graph.pyx +1653 -0
  107. sage/graphs/base/static_dense_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  108. sage/graphs/base/static_dense_graph.pxd +5 -0
  109. sage/graphs/base/static_dense_graph.pyx +1032 -0
  110. sage/graphs/base/static_sparse_backend.cpython-310-aarch64-linux-gnu.so +0 -0
  111. sage/graphs/base/static_sparse_backend.pxd +27 -0
  112. sage/graphs/base/static_sparse_backend.pyx +1583 -0
  113. sage/graphs/base/static_sparse_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  114. sage/graphs/base/static_sparse_graph.pxd +37 -0
  115. sage/graphs/base/static_sparse_graph.pyx +1375 -0
  116. sage/graphs/bipartite_graph.py +2732 -0
  117. sage/graphs/centrality.cpython-310-aarch64-linux-gnu.so +0 -0
  118. sage/graphs/centrality.pyx +1038 -0
  119. sage/graphs/cographs.py +519 -0
  120. sage/graphs/comparability.cpython-310-aarch64-linux-gnu.so +0 -0
  121. sage/graphs/comparability.pyx +851 -0
  122. sage/graphs/connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  123. sage/graphs/connectivity.pxd +157 -0
  124. sage/graphs/connectivity.pyx +4813 -0
  125. sage/graphs/convexity_properties.cpython-310-aarch64-linux-gnu.so +0 -0
  126. sage/graphs/convexity_properties.pxd +16 -0
  127. sage/graphs/convexity_properties.pyx +870 -0
  128. sage/graphs/digraph.py +4754 -0
  129. sage/graphs/digraph_generators.py +1993 -0
  130. sage/graphs/distances_all_pairs.cpython-310-aarch64-linux-gnu.so +0 -0
  131. sage/graphs/distances_all_pairs.pxd +12 -0
  132. sage/graphs/distances_all_pairs.pyx +2938 -0
  133. sage/graphs/domination.py +1363 -0
  134. sage/graphs/dot2tex_utils.py +100 -0
  135. sage/graphs/edge_connectivity.cpython-310-aarch64-linux-gnu.so +0 -0
  136. sage/graphs/edge_connectivity.pyx +1215 -0
  137. sage/graphs/generators/all.py +1 -0
  138. sage/graphs/generators/basic.py +1769 -0
  139. sage/graphs/generators/chessboard.py +538 -0
  140. sage/graphs/generators/classical_geometries.py +1611 -0
  141. sage/graphs/generators/degree_sequence.py +235 -0
  142. sage/graphs/generators/distance_regular.cpython-310-aarch64-linux-gnu.so +0 -0
  143. sage/graphs/generators/distance_regular.pyx +2846 -0
  144. sage/graphs/generators/families.py +4759 -0
  145. sage/graphs/generators/intersection.py +565 -0
  146. sage/graphs/generators/platonic_solids.py +262 -0
  147. sage/graphs/generators/random.py +2623 -0
  148. sage/graphs/generators/smallgraphs.py +5741 -0
  149. sage/graphs/generators/world_map.py +724 -0
  150. sage/graphs/generic_graph.py +26867 -0
  151. sage/graphs/generic_graph_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  152. sage/graphs/generic_graph_pyx.pxd +34 -0
  153. sage/graphs/generic_graph_pyx.pyx +1673 -0
  154. sage/graphs/genus.cpython-310-aarch64-linux-gnu.so +0 -0
  155. sage/graphs/genus.pyx +622 -0
  156. sage/graphs/graph.py +9645 -0
  157. sage/graphs/graph_coloring.cpython-310-aarch64-linux-gnu.so +0 -0
  158. sage/graphs/graph_coloring.pyx +2284 -0
  159. sage/graphs/graph_database.py +1177 -0
  160. sage/graphs/graph_decompositions/all.py +1 -0
  161. sage/graphs/graph_decompositions/bandwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  162. sage/graphs/graph_decompositions/bandwidth.pyx +428 -0
  163. sage/graphs/graph_decompositions/clique_separators.cpython-310-aarch64-linux-gnu.so +0 -0
  164. sage/graphs/graph_decompositions/clique_separators.pyx +616 -0
  165. sage/graphs/graph_decompositions/cutwidth.cpython-310-aarch64-linux-gnu.so +0 -0
  166. sage/graphs/graph_decompositions/cutwidth.pyx +753 -0
  167. sage/graphs/graph_decompositions/fast_digraph.cpython-310-aarch64-linux-gnu.so +0 -0
  168. sage/graphs/graph_decompositions/fast_digraph.pxd +13 -0
  169. sage/graphs/graph_decompositions/fast_digraph.pyx +212 -0
  170. sage/graphs/graph_decompositions/graph_products.cpython-310-aarch64-linux-gnu.so +0 -0
  171. sage/graphs/graph_decompositions/graph_products.pyx +508 -0
  172. sage/graphs/graph_decompositions/modular_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  173. sage/graphs/graph_decompositions/modular_decomposition.pxd +27 -0
  174. sage/graphs/graph_decompositions/modular_decomposition.pyx +1536 -0
  175. sage/graphs/graph_decompositions/slice_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  176. sage/graphs/graph_decompositions/slice_decomposition.pxd +18 -0
  177. sage/graphs/graph_decompositions/slice_decomposition.pyx +1106 -0
  178. sage/graphs/graph_decompositions/tree_decomposition.cpython-310-aarch64-linux-gnu.so +0 -0
  179. sage/graphs/graph_decompositions/tree_decomposition.pxd +17 -0
  180. sage/graphs/graph_decompositions/tree_decomposition.pyx +1996 -0
  181. sage/graphs/graph_decompositions/vertex_separation.cpython-310-aarch64-linux-gnu.so +0 -0
  182. sage/graphs/graph_decompositions/vertex_separation.pxd +5 -0
  183. sage/graphs/graph_decompositions/vertex_separation.pyx +1963 -0
  184. sage/graphs/graph_editor.py +82 -0
  185. sage/graphs/graph_generators.py +3314 -0
  186. sage/graphs/graph_generators_pyx.cpython-310-aarch64-linux-gnu.so +0 -0
  187. sage/graphs/graph_generators_pyx.pyx +95 -0
  188. sage/graphs/graph_input.py +812 -0
  189. sage/graphs/graph_latex.py +2064 -0
  190. sage/graphs/graph_list.py +410 -0
  191. sage/graphs/graph_plot.py +1756 -0
  192. sage/graphs/graph_plot_js.py +338 -0
  193. sage/graphs/hyperbolicity.cpython-310-aarch64-linux-gnu.so +0 -0
  194. sage/graphs/hyperbolicity.pyx +1704 -0
  195. sage/graphs/hypergraph_generators.py +364 -0
  196. sage/graphs/independent_sets.cpython-310-aarch64-linux-gnu.so +0 -0
  197. sage/graphs/independent_sets.pxd +13 -0
  198. sage/graphs/independent_sets.pyx +402 -0
  199. sage/graphs/isgci.py +1033 -0
  200. sage/graphs/isoperimetric_inequalities.cpython-310-aarch64-linux-gnu.so +0 -0
  201. sage/graphs/isoperimetric_inequalities.pyx +489 -0
  202. sage/graphs/line_graph.cpython-310-aarch64-linux-gnu.so +0 -0
  203. sage/graphs/line_graph.pyx +743 -0
  204. sage/graphs/lovasz_theta.py +77 -0
  205. sage/graphs/matching.py +1633 -0
  206. sage/graphs/matching_covered_graph.py +3590 -0
  207. sage/graphs/orientations.py +1489 -0
  208. sage/graphs/partial_cube.py +459 -0
  209. sage/graphs/path_enumeration.cpython-310-aarch64-linux-gnu.so +0 -0
  210. sage/graphs/path_enumeration.pyx +2040 -0
  211. sage/graphs/pq_trees.py +1129 -0
  212. sage/graphs/print_graphs.py +201 -0
  213. sage/graphs/schnyder.py +865 -0
  214. sage/graphs/spanning_tree.cpython-310-aarch64-linux-gnu.so +0 -0
  215. sage/graphs/spanning_tree.pyx +1457 -0
  216. sage/graphs/strongly_regular_db.cpython-310-aarch64-linux-gnu.so +0 -0
  217. sage/graphs/strongly_regular_db.pyx +3340 -0
  218. sage/graphs/traversals.cpython-310-aarch64-linux-gnu.so +0 -0
  219. sage/graphs/traversals.pxd +9 -0
  220. sage/graphs/traversals.pyx +1872 -0
  221. sage/graphs/trees.cpython-310-aarch64-linux-gnu.so +0 -0
  222. sage/graphs/trees.pxd +15 -0
  223. sage/graphs/trees.pyx +310 -0
  224. sage/graphs/tutte_polynomial.py +713 -0
  225. sage/graphs/views.cpython-310-aarch64-linux-gnu.so +0 -0
  226. sage/graphs/views.pyx +794 -0
  227. sage/graphs/weakly_chordal.cpython-310-aarch64-linux-gnu.so +0 -0
  228. sage/graphs/weakly_chordal.pyx +604 -0
  229. sage/groups/all__sagemath_graphs.py +1 -0
  230. sage/groups/perm_gps/all__sagemath_graphs.py +1 -0
  231. sage/groups/perm_gps/partn_ref/all__sagemath_graphs.py +1 -0
  232. sage/groups/perm_gps/partn_ref/refinement_graphs.cpython-310-aarch64-linux-gnu.so +0 -0
  233. sage/groups/perm_gps/partn_ref/refinement_graphs.pxd +38 -0
  234. sage/groups/perm_gps/partn_ref/refinement_graphs.pyx +1666 -0
  235. sage/knots/all.py +6 -0
  236. sage/knots/free_knotinfo_monoid.py +507 -0
  237. sage/knots/gauss_code.py +291 -0
  238. sage/knots/knot.py +682 -0
  239. sage/knots/knot_table.py +284 -0
  240. sage/knots/knotinfo.py +2900 -0
  241. sage/knots/link.py +4715 -0
  242. sage/sandpiles/all.py +13 -0
  243. sage/sandpiles/examples.py +225 -0
  244. sage/sandpiles/sandpile.py +6365 -0
  245. sage/topology/all.py +22 -0
  246. sage/topology/cell_complex.py +1214 -0
  247. sage/topology/cubical_complex.py +1976 -0
  248. sage/topology/delta_complex.py +1806 -0
  249. sage/topology/filtered_simplicial_complex.py +744 -0
  250. sage/topology/moment_angle_complex.py +823 -0
  251. sage/topology/simplicial_complex.py +5160 -0
  252. sage/topology/simplicial_complex_catalog.py +92 -0
  253. sage/topology/simplicial_complex_examples.py +1680 -0
  254. sage/topology/simplicial_complex_homset.py +205 -0
  255. sage/topology/simplicial_complex_morphism.py +836 -0
  256. sage/topology/simplicial_set.py +4102 -0
  257. sage/topology/simplicial_set_catalog.py +55 -0
  258. sage/topology/simplicial_set_constructions.py +2954 -0
  259. sage/topology/simplicial_set_examples.py +865 -0
  260. sage/topology/simplicial_set_morphism.py +1464 -0
@@ -0,0 +1,3672 @@
1
+ # sage_setup: distribution = sagemath-graphs
2
+ r"""
3
+ Hasse diagrams of posets
4
+
5
+ {INDEX_OF_FUNCTIONS}
6
+ """
7
+ # ****************************************************************************
8
+ # Copyright (C) 2008 Peter Jipsen <jipsen@chapman.edu>
9
+ # Copyright (C) 2008 Franco Saliola <saliola@gmail.com>
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU General Public License as published by
13
+ # the Free Software Foundation, either version 2 of the License, or
14
+ # (at your option) any later version.
15
+ # https://www.gnu.org/licenses/
16
+ # ****************************************************************************
17
+ from __future__ import annotations
18
+
19
+ from collections import deque
20
+
21
+ from sage.arith.misc import binomial
22
+ from sage.combinat.posets.hasse_cython import IncreasingChains
23
+ from sage.graphs.digraph import DiGraph
24
+ from sage.misc.cachefunc import cached_method
25
+ from sage.misc.lazy_attribute import lazy_attribute
26
+ from sage.misc.lazy_import import lazy_import
27
+ from sage.misc.rest_index_of_methods import gen_rest_table_index
28
+ from sage.rings.integer_ring import ZZ
29
+
30
+ lazy_import('sage.combinat.posets.hasse_cython_flint',
31
+ ['moebius_matrix_fast', 'coxeter_matrix_fast',
32
+ 'chain_poly'])
33
+ lazy_import('sage.matrix.constructor', 'matrix')
34
+ lazy_import('sage.rings.finite_rings.finite_field_constructor', 'GF')
35
+
36
+
37
+ class LatticeError(ValueError):
38
+ """
39
+ Helper exception class to forward elements without meet or
40
+ join to upper level, so that the user will see "No meet for
41
+ a and b" instead of "No meet for 1 and 2".
42
+ """
43
+
44
+ def __init__(self, fail, x, y) -> None:
45
+ """
46
+ Initialize the exception.
47
+
48
+ EXAMPLES::
49
+
50
+ sage: from sage.combinat.posets.hasse_diagram import LatticeError
51
+ sage: error = LatticeError('join', 3, 8)
52
+ sage: error.x
53
+ 3
54
+ """
55
+ ValueError.__init__(self, None)
56
+ self.fail = fail
57
+ self.x = x
58
+ self.y = y
59
+
60
+ def __str__(self) -> str:
61
+ """
62
+ Return string representation of the exception.
63
+
64
+ EXAMPLES::
65
+
66
+ sage: from sage.combinat.posets.hasse_diagram import LatticeError
67
+ sage: error = LatticeError('meet', 15, 18)
68
+ sage: error.__str__()
69
+ 'no meet for 15 and 18'
70
+ """
71
+ return f"no {self.fail} for {self.x} and {self.y}"
72
+
73
+
74
+ class HasseDiagram(DiGraph):
75
+ """
76
+ The Hasse diagram of a poset. This is just a transitively-reduced,
77
+ directed, acyclic graph without loops or multiple edges.
78
+
79
+ .. NOTE::
80
+
81
+ We assume that ``range(n)`` is a linear extension of the poset.
82
+ That is, ``range(n)`` is the vertex set and a topological sort of
83
+ the digraph.
84
+
85
+ This should not be called directly, use Poset instead; all type
86
+ checking happens there.
87
+
88
+ EXAMPLES::
89
+
90
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
91
+ sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]}); H
92
+ Hasse diagram of a poset containing 4 elements
93
+ sage: TestSuite(H).run()
94
+ """
95
+ def _repr_(self) -> str:
96
+ r"""
97
+ TESTS::
98
+
99
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
100
+ sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]})
101
+ sage: H._repr_()
102
+ 'Hasse diagram of a poset containing 4 elements'
103
+ """
104
+ return "Hasse diagram of a poset containing %s elements" % self.order()
105
+
106
+ def linear_extension(self):
107
+ r"""
108
+ Return a linear extension.
109
+
110
+ EXAMPLES::
111
+
112
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
113
+ sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]})
114
+ sage: H.linear_extension()
115
+ [0, 1, 2, 3]
116
+ """
117
+ # Recall: we assume range(n) is a linear extension.
118
+ return list(range(len(self)))
119
+
120
+ def linear_extensions(self):
121
+ r"""
122
+ Return an iterator over all linear extensions.
123
+
124
+ EXAMPLES::
125
+
126
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
127
+ sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]})
128
+ sage: list(H.linear_extensions()) # needs sage.modules
129
+ [[0, 1, 2, 3], [0, 2, 1, 3]]
130
+ """
131
+ from sage.combinat.posets.linear_extension_iterator import linear_extension_iterator
132
+ return linear_extension_iterator(self)
133
+
134
+ def greedy_linear_extensions_iterator(self):
135
+ r"""
136
+ Return an iterator over greedy linear extensions of the Hasse diagram.
137
+
138
+ A linear extension `[e_1, e_2, \ldots, e_n]` is *greedy* if for
139
+ every `i` either `e_{i+1}` covers `e_i` or all upper covers
140
+ of `e_i` have at least one lower cover that is not in
141
+ `[e_1, e_2, \ldots, e_i]`.
142
+
143
+ Informally said a linear extension is greedy if it "always
144
+ goes up when possible" and so has no unnecessary jumps.
145
+
146
+ EXAMPLES::
147
+
148
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
149
+ sage: N5 = HasseDiagram({0: [1, 2], 2: [3], 1: [4], 3: [4]})
150
+ sage: for l in N5.greedy_linear_extensions_iterator():
151
+ ....: print(l)
152
+ [0, 1, 2, 3, 4]
153
+ [0, 2, 3, 1, 4]
154
+
155
+ TESTS::
156
+
157
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
158
+ sage: list(HasseDiagram({}).greedy_linear_extensions_iterator())
159
+ [[]]
160
+ sage: H = HasseDiagram({0: []})
161
+ sage: list(H.greedy_linear_extensions_iterator())
162
+ [[0]]
163
+ """
164
+ N = self.order()
165
+
166
+ def greedy_rec(H, linext):
167
+ if len(linext) == N:
168
+ yield linext
169
+
170
+ S = []
171
+ if linext:
172
+ S = [x for x in H.neighbor_out_iterator(linext[-1])
173
+ if all(low in linext for low in H.neighbor_in_iterator(x))]
174
+ if not S:
175
+ S_ = set(self).difference(set(linext))
176
+ S = [x for x in S_
177
+ if not any(low in S_
178
+ for low in self.neighbor_in_iterator(x))]
179
+
180
+ for e in S:
181
+ yield from greedy_rec(H, linext + [e])
182
+
183
+ return greedy_rec(self, [])
184
+
185
+ def supergreedy_linear_extensions_iterator(self):
186
+ r"""
187
+ Return an iterator over supergreedy linear extensions of the Hasse diagram.
188
+
189
+ A linear extension `[e_1, e_2, \ldots, e_n]` is *supergreedy* if,
190
+ for every `i` and `j` where `i > j`, `e_i` covers `e_j` if for
191
+ every `i > k > j` at least one lower cover of `e_k` is not in
192
+ `[e_1, e_2, \ldots, e_k]`.
193
+
194
+ Informally said a linear extension is supergreedy if it "always
195
+ goes as high possible, and withdraw so less as possible".
196
+ These are also called depth-first linear extensions.
197
+
198
+ EXAMPLES:
199
+
200
+ We show the difference between "only greedy" and supergreedy
201
+ extensions::
202
+
203
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
204
+ sage: H = HasseDiagram({0: [1, 2], 2: [3, 4]})
205
+ sage: G_ext = list(H.greedy_linear_extensions_iterator())
206
+ sage: SG_ext = list(H.supergreedy_linear_extensions_iterator())
207
+ sage: [0, 2, 3, 1, 4] in G_ext
208
+ True
209
+ sage: [0, 2, 3, 1, 4] in SG_ext
210
+ False
211
+
212
+ sage: len(SG_ext)
213
+ 4
214
+
215
+ TESTS::
216
+
217
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
218
+ sage: list(HasseDiagram({}).supergreedy_linear_extensions_iterator())
219
+ [[]]
220
+ sage: list(HasseDiagram({0: [], 1: []}).supergreedy_linear_extensions_iterator())
221
+ [[0, 1], [1, 0]]
222
+ """
223
+ N = self.order()
224
+
225
+ def supergreedy_rec(H, linext):
226
+ k = len(linext)
227
+
228
+ if k == N:
229
+ yield linext
230
+
231
+ else:
232
+ S = []
233
+ while not S:
234
+ if not k: # Start from new minimal element
235
+ S = [x for x in self.sources() if x not in linext]
236
+ else:
237
+ S = [x for x in self.neighbor_out_iterator(linext[k - 1])
238
+ if x not in linext and
239
+ all(low in linext
240
+ for low in self.neighbor_in_iterator(x))]
241
+ k -= 1
242
+
243
+ for e in S:
244
+ yield from supergreedy_rec(H, linext + [e])
245
+
246
+ return supergreedy_rec(self, [])
247
+
248
+ def is_linear_extension(self, lin_ext=None) -> bool:
249
+ r"""
250
+ Test if an ordering is a linear extension.
251
+
252
+ EXAMPLES::
253
+
254
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
255
+ sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]})
256
+ sage: H.is_linear_extension(list(range(4)))
257
+ True
258
+ sage: H.is_linear_extension([3,2,1,0])
259
+ False
260
+ """
261
+ if lin_ext is None or lin_ext == list(range(len(self))):
262
+ return all(x < y for x, y in self.cover_relations_iterator())
263
+ indices = {x: lin_ext.index(x) for x in self}
264
+ return all(indices[x] < indices[y]
265
+ for x, y in self.cover_relations_iterator())
266
+
267
+ def cover_relations_iterator(self):
268
+ r"""
269
+ Iterate over cover relations.
270
+
271
+ EXAMPLES::
272
+
273
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
274
+ sage: H = HasseDiagram({0:[2,3], 1:[3,4], 2:[5], 3:[5], 4:[5]})
275
+ sage: list(H.cover_relations_iterator())
276
+ [(0, 2), (0, 3), (1, 3), (1, 4), (2, 5), (3, 5), (4, 5)]
277
+ """
278
+ yield from self.edge_iterator(labels=False)
279
+
280
+ def cover_relations(self):
281
+ r"""
282
+ Return the list of cover relations.
283
+
284
+ EXAMPLES::
285
+
286
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
287
+ sage: H = HasseDiagram({0:[2,3], 1:[3,4], 2:[5], 3:[5], 4:[5]})
288
+ sage: H.cover_relations()
289
+ [(0, 2), (0, 3), (1, 3), (1, 4), (2, 5), (3, 5), (4, 5)]
290
+ """
291
+ return list(self.cover_relations_iterator())
292
+
293
+ def is_lequal(self, i, j) -> bool:
294
+ """
295
+ Return ``True`` if i is less than or equal to j in the poset, and
296
+ ``False`` otherwise.
297
+
298
+ .. NOTE::
299
+
300
+ If the :meth:`lequal_matrix` has been computed, then this method is
301
+ redefined to use the cached data (see :meth:`_alternate_is_lequal`).
302
+
303
+ EXAMPLES::
304
+
305
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
306
+ sage: H = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]})
307
+ sage: x,y,z = 0, 1, 4
308
+ sage: H.is_lequal(x,y)
309
+ False
310
+ sage: H.is_lequal(y,x)
311
+ False
312
+ sage: H.is_lequal(x,z)
313
+ True
314
+ sage: H.is_lequal(y,z)
315
+ True
316
+ sage: H.is_lequal(z,z)
317
+ True
318
+ """
319
+ return i == j or (i < j and j in self.breadth_first_search(i))
320
+
321
+ def is_less_than(self, x, y) -> bool:
322
+ r"""
323
+ Return ``True`` if ``x`` is less than but not equal to ``y`` in the
324
+ poset, and ``False`` otherwise.
325
+
326
+ EXAMPLES::
327
+
328
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
329
+ sage: H = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]})
330
+ sage: x,y,z = 0, 1, 4
331
+ sage: H.is_less_than(x,y)
332
+ False
333
+ sage: H.is_less_than(y,x)
334
+ False
335
+ sage: H.is_less_than(x,z)
336
+ True
337
+ sage: H.is_less_than(y,z)
338
+ True
339
+ sage: H.is_less_than(z,z)
340
+ False
341
+ """
342
+ if x == y:
343
+ return False
344
+ return self.is_lequal(x, y)
345
+
346
+ def is_gequal(self, x, y) -> bool:
347
+ r"""
348
+ Return ``True`` if ``x`` is greater than or equal to ``y``, and
349
+ ``False`` otherwise.
350
+
351
+ EXAMPLES::
352
+
353
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
354
+ sage: Q = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]})
355
+ sage: x,y,z = 0,1,4
356
+ sage: Q.is_gequal(x,y)
357
+ False
358
+ sage: Q.is_gequal(y,x)
359
+ False
360
+ sage: Q.is_gequal(x,z)
361
+ False
362
+ sage: Q.is_gequal(z,x)
363
+ True
364
+ sage: Q.is_gequal(z,y)
365
+ True
366
+ sage: Q.is_gequal(z,z)
367
+ True
368
+ """
369
+ return self.is_lequal(y, x)
370
+
371
+ def is_greater_than(self, x, y) -> bool:
372
+ """
373
+ Return ``True`` if ``x`` is greater than but not equal to
374
+ ``y``, and ``False`` otherwise.
375
+
376
+ EXAMPLES::
377
+
378
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
379
+ sage: Q = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]})
380
+ sage: x,y,z = 0,1,4
381
+ sage: Q.is_greater_than(x,y)
382
+ False
383
+ sage: Q.is_greater_than(y,x)
384
+ False
385
+ sage: Q.is_greater_than(x,z)
386
+ False
387
+ sage: Q.is_greater_than(z,x)
388
+ True
389
+ sage: Q.is_greater_than(z,y)
390
+ True
391
+ sage: Q.is_greater_than(z,z)
392
+ False
393
+ """
394
+ return self.is_less_than(y, x)
395
+
396
+ def minimal_elements(self):
397
+ """
398
+ Return a list of the minimal elements of the poset.
399
+
400
+ EXAMPLES::
401
+
402
+ sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]})
403
+ sage: P(0) in P.minimal_elements()
404
+ True
405
+ sage: P(1) in P.minimal_elements()
406
+ True
407
+ sage: P(2) in P.minimal_elements()
408
+ True
409
+ """
410
+ return self.sources()
411
+
412
+ def maximal_elements(self):
413
+ """
414
+ Return a list of the maximal elements of the poset.
415
+
416
+ EXAMPLES::
417
+
418
+ sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]})
419
+ sage: P.maximal_elements()
420
+ [4]
421
+ """
422
+ return self.sinks()
423
+
424
+ @cached_method
425
+ def bottom(self):
426
+ """
427
+ Return the bottom element of the poset, if it exists.
428
+
429
+ EXAMPLES::
430
+
431
+ sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]})
432
+ sage: P.bottom() is None
433
+ True
434
+ sage: Q = Poset({0:[1],1:[]})
435
+ sage: Q.bottom()
436
+ 0
437
+ """
438
+ min_elms = self.minimal_elements()
439
+ if len(min_elms) == 1:
440
+ return min_elms[0]
441
+ return None
442
+
443
+ def has_bottom(self) -> bool:
444
+ """
445
+ Return ``True`` if the poset has a unique minimal element.
446
+
447
+ EXAMPLES::
448
+
449
+ sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]})
450
+ sage: P.has_bottom()
451
+ False
452
+ sage: Q = Poset({0:[1],1:[]})
453
+ sage: Q.has_bottom()
454
+ True
455
+ """
456
+ return self.bottom() is not None
457
+
458
+ def top(self):
459
+ """
460
+ Return the top element of the poset, if it exists.
461
+
462
+ EXAMPLES::
463
+
464
+ sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]})
465
+ sage: P.top() is None
466
+ True
467
+ sage: Q = Poset({0:[1],1:[]})
468
+ sage: Q.top()
469
+ 1
470
+ """
471
+ max_elms = self.maximal_elements()
472
+ if len(max_elms) == 1:
473
+ return max_elms[0]
474
+ return None
475
+
476
+ def has_top(self) -> bool:
477
+ """
478
+ Return ``True`` if the poset contains a unique maximal element, and
479
+ ``False`` otherwise.
480
+
481
+ EXAMPLES::
482
+
483
+ sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]})
484
+ sage: P.has_top()
485
+ False
486
+ sage: Q = Poset({0:[1],1:[]})
487
+ sage: Q.has_top()
488
+ True
489
+ """
490
+ return self.top() is not None
491
+
492
+ def is_bounded(self) -> bool:
493
+ """
494
+ Return ``True`` if the poset contains a unique maximal element and a
495
+ unique minimal element, and ``False`` otherwise.
496
+
497
+ EXAMPLES::
498
+
499
+ sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]})
500
+ sage: P.is_bounded()
501
+ False
502
+ sage: Q = Poset({0:[1],1:[]})
503
+ sage: Q.is_bounded()
504
+ True
505
+ """
506
+ return self.has_top() and self.has_bottom()
507
+
508
+ def is_chain(self) -> bool:
509
+ """
510
+ Return ``True`` if the poset is totally ordered, and ``False`` otherwise.
511
+
512
+ EXAMPLES::
513
+
514
+ sage: L = Poset({0:[1],1:[2],2:[3],3:[4]})
515
+ sage: L.is_chain()
516
+ True
517
+ sage: V = Poset({0:[1,2]})
518
+ sage: V.is_chain()
519
+ False
520
+
521
+ TESTS:
522
+
523
+ Check :issue:`15330`::
524
+
525
+ sage: p = Poset(DiGraph({0:[1],2:[1]}))
526
+ sage: p.is_chain()
527
+ False
528
+ """
529
+ if self.cardinality() == 0:
530
+ return True
531
+ return (self.num_edges() + 1 == self.num_verts() and # tree
532
+ all(d <= 1 for d in self.out_degree()) and
533
+ all(d <= 1 for d in self.in_degree()))
534
+
535
+ def is_antichain_of_poset(self, elms) -> bool:
536
+ """
537
+ Return ``True`` if ``elms`` is an antichain of the Hasse
538
+ diagram and ``False`` otherwise.
539
+
540
+ EXAMPLES::
541
+
542
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
543
+ sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4], 3: [4]})
544
+ sage: H.is_antichain_of_poset([1, 2, 3])
545
+ True
546
+ sage: H.is_antichain_of_poset([0, 2, 3])
547
+ False
548
+ """
549
+ from itertools import combinations
550
+ elms_sorted = sorted(set(elms))
551
+ return not any(self.is_lequal(a, b) for a, b in
552
+ combinations(elms_sorted, 2))
553
+
554
+ def dual(self):
555
+ """
556
+ Return a poset that is dual to the given poset.
557
+
558
+ This means that it has the same elements but opposite order.
559
+ The elements are renumbered to ensure that ``range(n)``
560
+ is a linear extension.
561
+
562
+ EXAMPLES::
563
+
564
+ sage: P = posets.IntegerPartitions(4) # needs sage.combinat
565
+ sage: H = P._hasse_diagram; H # needs sage.combinat
566
+ Hasse diagram of a poset containing 5 elements
567
+ sage: H.dual() # needs sage.combinat
568
+ Hasse diagram of a poset containing 5 elements
569
+
570
+ TESTS::
571
+
572
+ sage: H = posets.IntegerPartitions(4)._hasse_diagram # needs sage.combinat
573
+ sage: H.is_isomorphic( H.dual().dual() ) # needs sage.combinat
574
+ True
575
+ sage: H.is_isomorphic( H.dual() ) # needs sage.combinat
576
+ False
577
+ """
578
+ H = self.reverse(immutable=False)
579
+ H.relabel(perm=list(range(H.num_verts() - 1, -1, -1)), inplace=True)
580
+ return HasseDiagram(H)
581
+
582
+ def _precompute_intervals(self):
583
+ """
584
+ Precompute all intervals of the poset.
585
+
586
+ This will significantly speed up computing congruences. On the
587
+ other hand, it will cost much more memory. Currently this is
588
+ a hidden feature. See the example below for how to use it.
589
+
590
+ EXAMPLES::
591
+
592
+ sage: B4 = posets.BooleanLattice(4)
593
+ sage: B4.is_isoform() # Slow # needs sage.combinat sage.modules
594
+ True
595
+ sage: B4._hasse_diagram._precompute_intervals()
596
+ sage: B4 = posets.BooleanLattice(4)
597
+ sage: B4.is_isoform() # Faster now # needs sage.combinat sage.modules
598
+ True
599
+ """
600
+ n = self.order()
601
+ v_up = (frozenset(self.depth_first_search(v)) for v in range(n))
602
+ v_down = [frozenset(self.depth_first_search(v, neighbors=self.neighbor_in_iterator))
603
+ for v in range(n)]
604
+ self._intervals = [[sorted(up.intersection(down)) for down in v_down]
605
+ for up in v_up]
606
+
607
+ def interval(self, x, y) -> list:
608
+ r"""
609
+ Return a list of the elements `z` of ``self`` such that
610
+ `x \leq z \leq y`.
611
+
612
+ The order is that induced by the ordering in ``self.linear_extension``.
613
+
614
+ INPUT:
615
+
616
+ - ``x`` -- any element of the poset
617
+
618
+ - ``y`` -- any element of the poset
619
+
620
+ .. NOTE::
621
+
622
+ The method :meth:`_precompute_intervals()` creates a cache
623
+ which is used if available, making the function very fast.
624
+
625
+ .. SEEALSO:: :meth:`interval_iterator`
626
+
627
+ EXAMPLES::
628
+
629
+ sage: uc = [[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]
630
+ sage: dag = DiGraph(dict(zip(range(len(uc)),uc)))
631
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
632
+ sage: H = HasseDiagram(dag)
633
+ sage: I = set([2,5,6,4,7])
634
+ sage: I == set(H.interval(2,7))
635
+ True
636
+ """
637
+ try:
638
+ # when the intervals have been precomputed
639
+ return self._intervals[x][y]
640
+ except AttributeError:
641
+ return list(self.interval_iterator(x, y))
642
+
643
+ def interval_iterator(self, x, y):
644
+ r"""
645
+ Return an iterator of the elements `z` of ``self`` such that
646
+ `x \leq z \leq y`.
647
+
648
+ INPUT:
649
+
650
+ - ``x`` -- any element of the poset
651
+
652
+ - ``y`` -- any element of the poset
653
+
654
+ .. SEEALSO:: :meth:`interval`
655
+
656
+ .. NOTE::
657
+
658
+ This becomes much faster when first calling :meth:`_leq_storage`,
659
+ which precomputes the principal upper ideals.
660
+
661
+ EXAMPLES::
662
+
663
+ sage: uc = [[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]
664
+ sage: dag = DiGraph(dict(zip(range(len(uc)),uc)))
665
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
666
+ sage: H = HasseDiagram(dag)
667
+ sage: I = set([2,5,6,4,7])
668
+ sage: I == set(H.interval_iterator(2,7))
669
+ True
670
+ """
671
+ for z in range(x, y + 1):
672
+ if self.is_lequal(x, z) and self.is_lequal(z, y):
673
+ yield z
674
+
675
+ closed_interval = interval
676
+
677
+ def open_interval(self, x, y) -> list:
678
+ """
679
+ Return a list of the elements `z` of ``self`` such that `x < z < y`.
680
+
681
+ The order is that induced by the ordering in ``self.linear_extension``.
682
+
683
+ EXAMPLES::
684
+
685
+ sage: uc = [[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]
686
+ sage: dag = DiGraph(dict(zip(range(len(uc)),uc)))
687
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
688
+ sage: H = HasseDiagram(dag)
689
+ sage: set([5,6,4]) == set(H.open_interval(2,7))
690
+ True
691
+ sage: H.open_interval(7,2)
692
+ []
693
+ """
694
+ ci = self.interval(x, y)
695
+ if not ci:
696
+ return []
697
+ return ci[1:-1]
698
+
699
+ def rank_function(self):
700
+ r"""
701
+ Return the (normalized) rank function of the poset,
702
+ if it exists.
703
+
704
+ A *rank function* of a poset `P` is a function `r`
705
+ that maps elements of `P` to integers and satisfies:
706
+ `r(x) = r(y) + 1` if `x` covers `y`. The function `r`
707
+ is normalized such that its minimum value on every
708
+ connected component of the Hasse diagram of `P` is
709
+ `0`. This determines the function `r` uniquely (when
710
+ it exists).
711
+
712
+ OUTPUT:
713
+
714
+ - a lambda function, if the poset admits a rank function
715
+ - ``None``, if the poset does not admit a rank function
716
+
717
+ EXAMPLES::
718
+
719
+ sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])
720
+ sage: P.rank_function() is not None
721
+ True
722
+ sage: P = Poset(([1,2,3,4],[[1,4],[2,3],[3,4]]), facade = True)
723
+ sage: P.rank_function() is not None
724
+ True
725
+ sage: P = Poset(([1,2,3,4,5],[[1,2],[2,3],[3,4],[1,5],[5,4]]), facade = True)
726
+ sage: P.rank_function() is not None
727
+ False
728
+ sage: P = Poset(([1,2,3,4,5,6,7,8],[[1,4],[2,3],[3,4],[5,7],[6,7]]), facade = True)
729
+ sage: f = P.rank_function(); f is not None
730
+ True
731
+ sage: f(5)
732
+ 0
733
+ sage: f(2)
734
+ 0
735
+
736
+ TESTS::
737
+
738
+ sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])
739
+ sage: r = P.rank_function()
740
+ sage: for u,v in P.cover_relations_iterator():
741
+ ....: if r(v) != r(u) + 1:
742
+ ....: print("Bug in rank_function!")
743
+
744
+ ::
745
+
746
+ sage: Q = Poset([[1,2],[4],[3],[4],[]])
747
+ sage: Q.rank_function() is None
748
+ True
749
+
750
+ test for issue :issue:`14006`::
751
+
752
+ sage: H = Poset()._hasse_diagram
753
+ sage: s = dumps(H)
754
+ sage: f = H.rank_function()
755
+ sage: s = dumps(H)
756
+ """
757
+ if self._rank is None:
758
+ return None
759
+ # the rank function is just the getitem of the list
760
+ return self._rank.__getitem__
761
+
762
+ @lazy_attribute
763
+ def _rank(self):
764
+ r"""
765
+ Build the rank function of the poset, if it exists, i.e.
766
+ an array ``d`` where ``d[object] = self.rank_function()(object)``
767
+
768
+ A *rank function* of a poset `P` is a function `r`
769
+ that maps elements of `P` to integers and satisfies:
770
+ `r(x) = r(y) + 1` if `x` covers `y`. The function `r`
771
+ is normalized such that its minimum value on every
772
+ connected component of the Hasse diagram of `P` is
773
+ `0`. This determines the function `r` uniquely (when
774
+ it exists).
775
+
776
+ EXAMPLES::
777
+
778
+ sage: H = Poset()._hasse_diagram
779
+ sage: H._rank
780
+ []
781
+ sage: H = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])._hasse_diagram
782
+ sage: H._rank
783
+ [0, 1, 1, 2, 2, 1, 2, 3]
784
+ sage: H = Poset(([1,2,3,4,5],[[1,2],[2,3],[3,4],[1,5],[5,4]]))._hasse_diagram
785
+ sage: H._rank is None
786
+ True
787
+ """
788
+ # rank[i] is the rank of point i. It is equal to None until the rank of
789
+ # i is computed
790
+ rank = [None] * self.order()
791
+ not_found = set(self.vertex_iterator())
792
+ while not_found:
793
+ y = not_found.pop()
794
+ rank[y] = 0 # We set some vertex to have rank 0
795
+ component = {y}
796
+ queue = {y}
797
+ while queue:
798
+ # look at the neighbors of y and set the ranks;
799
+ # then look at the neighbors of the neighbors ...
800
+ y = queue.pop()
801
+ for x in self.neighbor_out_iterator(y):
802
+ if rank[x] is None:
803
+ rank[x] = rank[y] + 1
804
+ queue.add(x)
805
+ component.add(x)
806
+ for x in self.neighbor_in_iterator(y):
807
+ if rank[x] is None:
808
+ rank[x] = rank[y] - 1
809
+ queue.add(x)
810
+ component.add(x)
811
+ elif rank[x] != rank[y] - 1:
812
+ return None
813
+ # Normalize the ranks of vertices in the connected component
814
+ # so that smallest is 0:
815
+ m = min(rank[j] for j in component)
816
+ for j in component:
817
+ rank[j] -= m
818
+ not_found.difference_update(component)
819
+ # now, all ranks are set.
820
+ return rank
821
+
822
+ def rank(self, element=None):
823
+ r"""
824
+ Return the rank of ``element``, or the rank of the poset if
825
+ ``element`` is ``None``. (The rank of a poset is the length of
826
+ the longest chain of elements of the poset.)
827
+
828
+ EXAMPLES::
829
+
830
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
831
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
832
+ sage: H.rank(5)
833
+ 2
834
+ sage: H.rank()
835
+ 3
836
+ sage: Q = HasseDiagram({0:[1,2],1:[3],2:[],3:[]})
837
+ sage: Q.rank()
838
+ 2
839
+ sage: Q.rank(1)
840
+ 1
841
+ """
842
+ if element is None:
843
+ return len(self.level_sets()) - 1
844
+ return self.rank_function()(element)
845
+
846
+ def is_ranked(self) -> bool:
847
+ r"""
848
+ Return ``True`` if the poset is ranked, and ``False`` otherwise.
849
+
850
+ A poset is *ranked* if it admits a rank function. For more information
851
+ about the rank function, see :meth:`~rank_function`
852
+ and :meth:`~is_graded`.
853
+
854
+ EXAMPLES::
855
+
856
+ sage: P = Poset([[1],[2],[3],[4],[]])
857
+ sage: P.is_ranked()
858
+ True
859
+ sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]])
860
+ sage: Q.is_ranked()
861
+ False
862
+ """
863
+ return bool(self.rank_function())
864
+
865
+ def covers(self, x, y):
866
+ """
867
+ Return ``True`` if y covers x and ``False`` otherwise.
868
+
869
+ EXAMPLES::
870
+
871
+ sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]])
872
+ sage: Q.covers(Q(1),Q(6))
873
+ True
874
+ sage: Q.covers(Q(1),Q(4))
875
+ False
876
+ """
877
+ return self.has_edge(x, y)
878
+
879
+ def upper_covers_iterator(self, element):
880
+ r"""
881
+ Return the list of elements that cover ``element``.
882
+
883
+ EXAMPLES::
884
+
885
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
886
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
887
+ sage: list(H.upper_covers_iterator(0))
888
+ [1, 2, 3]
889
+ sage: list(H.upper_covers_iterator(7))
890
+ []
891
+ """
892
+ yield from self.neighbor_out_iterator(element)
893
+
894
+ def lower_covers_iterator(self, element):
895
+ r"""
896
+ Return the list of elements that are covered by ``element``.
897
+
898
+ EXAMPLES::
899
+
900
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
901
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
902
+ sage: list(H.lower_covers_iterator(0))
903
+ []
904
+ sage: list(H.lower_covers_iterator(4))
905
+ [1, 2]
906
+ """
907
+ yield from self.neighbor_in_iterator(element)
908
+
909
+ def cardinality(self):
910
+ r"""
911
+ Return the number of elements in the poset.
912
+
913
+ EXAMPLES::
914
+
915
+ sage: Poset([[1,2,3],[4],[4],[4],[]]).cardinality()
916
+ 5
917
+
918
+ TESTS:
919
+
920
+ For a time, this function was named ``size()``, which
921
+ would override the same-named method of the underlying
922
+ digraph. :issue:`8735` renamed this method to ``cardinality()``
923
+ with a deprecation warning. :issue:`11214` removed the warning
924
+ since code for graphs was raising the warning inadvertently.
925
+ This tests that ``size()`` for a Hasse diagram returns the
926
+ number of edges in the digraph. ::
927
+
928
+ sage: L = posets.BooleanLattice(5)
929
+ sage: H = L.hasse_diagram()
930
+ sage: H.size()
931
+ 80
932
+ sage: H.size() == H.num_edges()
933
+ True
934
+ """
935
+ return self.order()
936
+
937
+ def moebius_function(self, i, j): # dumb algorithm
938
+ r"""
939
+ Return the value of the Möbius function of the poset
940
+ on the elements ``i`` and ``j``.
941
+
942
+ EXAMPLES::
943
+
944
+ sage: P = Poset([[1,2,3],[4],[4],[4],[]])
945
+ sage: H = P._hasse_diagram
946
+ sage: H.moebius_function(0,4)
947
+ 2
948
+ sage: for u,v in P.cover_relations_iterator():
949
+ ....: if P.moebius_function(u,v) != -1:
950
+ ....: print("Bug in moebius_function!")
951
+ """
952
+ try:
953
+ return self._moebius_function_values[(i, j)]
954
+ except AttributeError:
955
+ self._moebius_function_values = {}
956
+ return self.moebius_function(i, j)
957
+ except KeyError:
958
+ if i == j:
959
+ self._moebius_function_values[(i, j)] = 1
960
+ elif i > j:
961
+ self._moebius_function_values[(i, j)] = 0
962
+ else:
963
+ ci = self.interval(i, j)
964
+ if not ci:
965
+ self._moebius_function_values[(i, j)] = 0
966
+ else:
967
+ self._moebius_function_values[(i, j)] = -sum(self.moebius_function(i, k) for k in ci[:-1])
968
+ return self._moebius_function_values[(i, j)]
969
+
970
+ def bottom_moebius_function(self, j):
971
+ r"""
972
+ Return the value of the Möbius function of the poset
973
+ on the elements ``zero`` and ``j``, where ``zero`` is
974
+ ``self.bottom()``, the unique minimal element of the poset.
975
+
976
+ EXAMPLES::
977
+
978
+ sage: P = Poset({0: [1,2]})
979
+ sage: hasse = P._hasse_diagram
980
+ sage: hasse.bottom_moebius_function(1)
981
+ -1
982
+ sage: hasse.bottom_moebius_function(2)
983
+ -1
984
+ sage: P = Poset({0: [1,3], 1:[2], 2:[4], 3:[4]})
985
+ sage: hasse = P._hasse_diagram
986
+ sage: for i in range(5):
987
+ ....: print(hasse.bottom_moebius_function(i))
988
+ 1
989
+ -1
990
+ 0
991
+ -1
992
+ 1
993
+
994
+ TESTS::
995
+
996
+ sage: P = Poset({0:[2], 1:[2]})
997
+ sage: hasse = P._hasse_diagram
998
+ sage: hasse.bottom_moebius_function(1)
999
+ Traceback (most recent call last):
1000
+ ...
1001
+ ValueError: the poset does not have a bottom element
1002
+ """
1003
+ zero = self.bottom()
1004
+ if zero is None:
1005
+ raise ValueError("the poset does not have a bottom element")
1006
+ # if the value has already been computed, either by self.moebius_function
1007
+ # or by self.bottom_moebius_function, then just use the cached value.
1008
+ try:
1009
+ return self._moebius_function_values[(zero, j)]
1010
+ # if the dict has not been initialized, do that and try again
1011
+ except AttributeError:
1012
+ self._moebius_function_values = {}
1013
+ return self.bottom_moebius_function(j)
1014
+ # if mu(zero, j) has not already been computed, we'll get a key error.
1015
+ except KeyError:
1016
+ if zero == j:
1017
+ self._moebius_function_values[(zero, j)] = 1
1018
+ # since zero is the minimal element, we can ignore the case that zero > j,
1019
+ # and move on to computing the interval, which is exactly the order ideal.
1020
+ else:
1021
+ # do the depth_first_search over order_ideal, because we don't care
1022
+ # about sorting the elements of the order ideal.
1023
+ ci = self._backend.depth_first_search(j, reverse=True)
1024
+ next(ci) # throw out the first element, which is j
1025
+ self._moebius_function_values[(zero, j)] = -sum(self.bottom_moebius_function(k) for k in ci)
1026
+ return self._moebius_function_values[(zero, j)]
1027
+
1028
+ def moebius_function_matrix(self, algorithm='cython'):
1029
+ r"""
1030
+ Return the matrix of the Möbius function of this poset.
1031
+
1032
+ This returns the matrix over `\ZZ` whose ``(x, y)`` entry
1033
+ is the value of the Möbius function of ``self`` evaluated on
1034
+ ``x`` and ``y``, and redefines :meth:`moebius_function` to use it.
1035
+
1036
+ INPUT:
1037
+
1038
+ - ``algorithm`` -- ``'recursive'``, ``'matrix'`` or ``'cython'``
1039
+ (default)
1040
+
1041
+ This uses either the recursive formula, a generic matrix inversion
1042
+ or a specific matrix inversion coded in Cython.
1043
+
1044
+ OUTPUT: a dense matrix for the algorithm ``cython``, a sparse matrix otherwise
1045
+
1046
+ .. NOTE::
1047
+
1048
+ The result is cached in :meth:`_moebius_function_matrix`.
1049
+
1050
+ .. SEEALSO:: :meth:`lequal_matrix`, :meth:`coxeter_transformation`
1051
+
1052
+ EXAMPLES::
1053
+
1054
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1055
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
1056
+ sage: H.moebius_function_matrix() # needs sage.libs.flint sage.modules
1057
+ [ 1 -1 -1 -1 1 0 1 0]
1058
+ [ 0 1 0 0 -1 0 0 0]
1059
+ [ 0 0 1 0 -1 -1 -1 2]
1060
+ [ 0 0 0 1 0 0 -1 0]
1061
+ [ 0 0 0 0 1 0 0 -1]
1062
+ [ 0 0 0 0 0 1 0 -1]
1063
+ [ 0 0 0 0 0 0 1 -1]
1064
+ [ 0 0 0 0 0 0 0 1]
1065
+
1066
+ TESTS::
1067
+
1068
+ sage: # needs sage.libs.flint sage.modules
1069
+ sage: H.moebius_function_matrix().is_immutable()
1070
+ True
1071
+ sage: hasattr(H,'_moebius_function_matrix')
1072
+ True
1073
+ sage: H.moebius_function == H._moebius_function_from_matrix
1074
+ True
1075
+ sage: H = posets.TamariLattice(3)._hasse_diagram
1076
+ sage: M = H.moebius_function_matrix('matrix'); M
1077
+ [ 1 -1 -1 0 1]
1078
+ [ 0 1 0 0 -1]
1079
+ [ 0 0 1 -1 0]
1080
+ [ 0 0 0 1 -1]
1081
+ [ 0 0 0 0 1]
1082
+ sage: _ = H.__dict__.pop('_moebius_function_matrix')
1083
+ sage: H.moebius_function_matrix('cython') == M
1084
+ True
1085
+ sage: _ = H.__dict__.pop('_moebius_function_matrix')
1086
+ sage: H.moebius_function_matrix('recursive') == M
1087
+ True
1088
+ sage: _ = H.__dict__.pop('_moebius_function_matrix')
1089
+ sage: H.moebius_function_matrix('banana')
1090
+ Traceback (most recent call last):
1091
+ ...
1092
+ ValueError: unknown algorithm
1093
+ """
1094
+ if not hasattr(self, '_moebius_function_matrix'):
1095
+ if algorithm == 'recursive':
1096
+ n = self.cardinality()
1097
+ gt = self._leq_storage
1098
+ greater_than = [sorted(gt[i]) for i in range(n)]
1099
+ m = {}
1100
+ for i in range(n - 1, -1, -1):
1101
+ m[(i, i)] = ZZ.one()
1102
+ available = []
1103
+ for k in greater_than[i]:
1104
+ if k != i:
1105
+ available.append(k)
1106
+ m[(i, k)] = -ZZ.sum(m[(j, k)]
1107
+ for j in available
1108
+ if k in greater_than[j])
1109
+ M = matrix(ZZ, n, n, m, sparse=True) # noqa: F821
1110
+ elif algorithm == "matrix":
1111
+ M = self.lequal_matrix().inverse_of_unit()
1112
+ elif algorithm == "cython":
1113
+ M = moebius_matrix_fast(self._leq_storage) # noqa: F821
1114
+ else:
1115
+ raise ValueError("unknown algorithm")
1116
+ self._moebius_function_matrix = M
1117
+ self._moebius_function_matrix.set_immutable()
1118
+ self.moebius_function = self._moebius_function_from_matrix
1119
+ return self._moebius_function_matrix
1120
+
1121
+ # Redefine self.moebius_function
1122
+ def _moebius_function_from_matrix(self, i, j):
1123
+ r"""
1124
+ Return the value of the Möbius function of the poset
1125
+ on the elements ``i`` and ``j``.
1126
+
1127
+ EXAMPLES::
1128
+
1129
+ sage: P = Poset([[1,2,3],[4],[4],[4],[]])
1130
+ sage: H = P._hasse_diagram
1131
+ sage: H.moebius_function(0,4) # indirect doctest
1132
+ 2
1133
+ sage: for u,v in P.cover_relations_iterator():
1134
+ ....: if P.moebius_function(u,v) != -1:
1135
+ ....: print("Bug in moebius_function!")
1136
+
1137
+ This uses ``self._moebius_function_matrix``, as computed by
1138
+ :meth:`moebius_function_matrix`.
1139
+ """
1140
+ return self._moebius_function_matrix[i, j]
1141
+
1142
+ @cached_method
1143
+ def coxeter_transformation(self, algorithm='cython'):
1144
+ r"""
1145
+ Return the matrix of the Auslander-Reiten translation acting on
1146
+ the Grothendieck group of the derived category of modules on the
1147
+ poset, in the basis of simple modules.
1148
+
1149
+ INPUT:
1150
+
1151
+ - ``algorithm`` -- ``'cython'`` (default) or ``'matrix'``
1152
+
1153
+ This uses either a specific matrix code in Cython, or generic matrices.
1154
+
1155
+ .. SEEALSO:: :meth:`lequal_matrix`, :meth:`moebius_function_matrix`
1156
+
1157
+ EXAMPLES::
1158
+
1159
+ sage: # needs sage.libs.flint sage.modules
1160
+ sage: P = posets.PentagonPoset()._hasse_diagram
1161
+ sage: M = P.coxeter_transformation(); M
1162
+ [ 0 0 0 0 -1]
1163
+ [ 0 0 0 1 -1]
1164
+ [ 0 1 0 0 -1]
1165
+ [-1 1 1 0 -1]
1166
+ [-1 1 0 1 -1]
1167
+ sage: P.__dict__['coxeter_transformation'].clear_cache()
1168
+ sage: P.coxeter_transformation(algorithm='matrix') == M
1169
+ True
1170
+
1171
+ TESTS::
1172
+
1173
+ sage: # needs sage.libs.flint sage.modules
1174
+ sage: P = posets.PentagonPoset()._hasse_diagram
1175
+ sage: M = P.coxeter_transformation()
1176
+ sage: M**8 == 1
1177
+ True
1178
+ sage: P.__dict__['coxeter_transformation'].clear_cache()
1179
+ sage: P.coxeter_transformation(algorithm='banana')
1180
+ Traceback (most recent call last):
1181
+ ...
1182
+ ValueError: unknown algorithm
1183
+ """
1184
+ if algorithm == 'matrix':
1185
+ return - self.lequal_matrix() * self.moebius_function_matrix().transpose()
1186
+ if algorithm == 'cython':
1187
+ return coxeter_matrix_fast(self._leq_storage) # noqa: F821
1188
+ raise ValueError("unknown algorithm")
1189
+
1190
+ def order_filter(self, elements):
1191
+ r"""
1192
+ Return the order filter generated by a list of elements.
1193
+
1194
+ `I` is an order filter if, for any `x` in `I` and `y` such that
1195
+ `y \ge x`, then `y` is in `I`.
1196
+
1197
+ EXAMPLES::
1198
+
1199
+ sage: H = posets.BooleanLattice(4)._hasse_diagram
1200
+ sage: H.order_filter([3,8])
1201
+ [3, 7, 8, 9, 10, 11, 12, 13, 14, 15]
1202
+ """
1203
+ return sorted(self.depth_first_search(elements))
1204
+
1205
+ def principal_order_filter(self, i):
1206
+ """
1207
+ Return the order filter generated by ``i``.
1208
+
1209
+ EXAMPLES::
1210
+
1211
+ sage: H = posets.BooleanLattice(4)._hasse_diagram
1212
+ sage: H.principal_order_filter(2)
1213
+ [2, 3, 6, 7, 10, 11, 14, 15]
1214
+ """
1215
+ return self.order_filter([i])
1216
+
1217
+ def order_ideal(self, elements):
1218
+ r"""
1219
+ Return the order ideal generated by a list of elements.
1220
+
1221
+ `I` is an order ideal if, for any `x` in `I` and `y` such that
1222
+ `y \le x`, then `y` is in `I`.
1223
+
1224
+ EXAMPLES::
1225
+
1226
+ sage: H = posets.BooleanLattice(4)._hasse_diagram
1227
+ sage: H.order_ideal([7,10])
1228
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 10]
1229
+ """
1230
+ return sorted(self.depth_first_search(elements,
1231
+ neighbors=self.neighbor_in_iterator))
1232
+
1233
+ def order_ideal_cardinality(self, elements):
1234
+ r"""
1235
+ Return the cardinality of the order ideal generated by ``elements``.
1236
+
1237
+ `I` is an order ideal if, for any `x` in `I` and `y` such that
1238
+ `y \le x`, then `y` is in `I`.
1239
+
1240
+ EXAMPLES::
1241
+
1242
+ sage: H = posets.BooleanLattice(4)._hasse_diagram
1243
+ sage: H.order_ideal_cardinality([7,10])
1244
+ 10
1245
+ """
1246
+ seen = set()
1247
+ q = deque(elements)
1248
+ size = 0
1249
+ while q:
1250
+ v = q.popleft()
1251
+ if v in seen:
1252
+ continue
1253
+ size += 1
1254
+ seen.add(v)
1255
+ q.extend(self.neighbor_in_iterator(v))
1256
+
1257
+ return ZZ(size)
1258
+
1259
+ def principal_order_ideal(self, i):
1260
+ """
1261
+ Return the order ideal generated by `i`.
1262
+
1263
+ EXAMPLES::
1264
+
1265
+ sage: H = posets.BooleanLattice(4)._hasse_diagram
1266
+ sage: H.principal_order_ideal(6)
1267
+ [0, 2, 4, 6]
1268
+ """
1269
+ return self.order_ideal([i])
1270
+
1271
+ @lazy_attribute
1272
+ def _leq_storage(self):
1273
+ """
1274
+ Store the comparison relation as a list of Python sets.
1275
+
1276
+ The `i`-th item in the list is the set of elements greater than `i`.
1277
+
1278
+ EXAMPLES::
1279
+
1280
+ sage: H = posets.DiamondPoset(7)._hasse_diagram
1281
+ sage: H._leq_storage
1282
+ [{0, 1, 2, 3, 4, 5, 6}, {1, 6}, {2, 6}, {3, 6}, {4, 6}, {5, 6}, {6}]
1283
+ """
1284
+ n = self.order()
1285
+ greater_than = [{i} for i in range(n)]
1286
+ for i in range(n - 1, -1, -1):
1287
+ gt = greater_than[i]
1288
+ for j in self.neighbor_out_iterator(i):
1289
+ gt = gt.union(greater_than[j])
1290
+ greater_than[i] = gt
1291
+
1292
+ # Redefine self.is_lequal
1293
+ self.is_lequal = self._alternate_is_lequal
1294
+
1295
+ return greater_than
1296
+
1297
+ @lazy_attribute
1298
+ def _leq_matrix_boolean(self):
1299
+ r"""
1300
+ Compute a boolean matrix whose ``(i,j)`` entry is 1 if ``i``
1301
+ is less than ``j`` in the poset, and 0 otherwise; and
1302
+ redefines ``__lt__`` to use this matrix.
1303
+
1304
+ .. SEEALSO:: :meth:`_leq_matrix`
1305
+
1306
+ EXAMPLES::
1307
+
1308
+ sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])
1309
+ sage: H = P._hasse_diagram
1310
+ sage: M = H._leq_matrix_boolean; M # needs sage.modules
1311
+ [1 1 1 1 1 1 1 1]
1312
+ [0 1 0 1 0 0 0 1]
1313
+ [0 0 1 1 1 0 1 1]
1314
+ [0 0 0 1 0 0 0 1]
1315
+ [0 0 0 0 1 0 0 1]
1316
+ [0 0 0 0 0 1 1 1]
1317
+ [0 0 0 0 0 0 1 1]
1318
+ [0 0 0 0 0 0 0 1]
1319
+ sage: M.base_ring() # needs sage.modules
1320
+ Finite Field of size 2
1321
+ """
1322
+ n = self.order()
1323
+ R = GF(2) # noqa: F821
1324
+ one = R.one()
1325
+ greater_than = self._leq_storage
1326
+ D = {(i, j): one for i in range(n) for j in greater_than[i]}
1327
+ M = matrix(R, n, n, D, sparse=True) # noqa: F821
1328
+ M.set_immutable()
1329
+ return M
1330
+
1331
+ @lazy_attribute
1332
+ def _leq_matrix(self):
1333
+ r"""
1334
+ Compute an integer matrix whose ``(i,j)`` entry is 1 if ``i``
1335
+ is less than ``j`` in the poset, and 0 otherwise.
1336
+
1337
+ .. SEEALSO:: :meth:`_leq_matrix_boolean`
1338
+
1339
+ EXAMPLES::
1340
+
1341
+ sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])
1342
+ sage: H = P._hasse_diagram
1343
+ sage: M = H._leq_matrix; M # needs sage.modules
1344
+ [1 1 1 1 1 1 1 1]
1345
+ [0 1 0 1 0 0 0 1]
1346
+ [0 0 1 1 1 0 1 1]
1347
+ [0 0 0 1 0 0 0 1]
1348
+ [0 0 0 0 1 0 0 1]
1349
+ [0 0 0 0 0 1 1 1]
1350
+ [0 0 0 0 0 0 1 1]
1351
+ [0 0 0 0 0 0 0 1]
1352
+ sage: M.base_ring() # needs sage.modules
1353
+ Integer Ring
1354
+ """
1355
+ n = self.order()
1356
+ greater_than = self._leq_storage
1357
+ D = {(i, j): 1 for i in range(n) for j in greater_than[i]}
1358
+ return matrix(ZZ, n, n, D, sparse=True, immutable=True) # noqa: F821
1359
+
1360
+ def lequal_matrix(self, boolean=False):
1361
+ r"""
1362
+ Return a matrix whose ``(i,j)`` entry is 1 if ``i`` is less
1363
+ than ``j`` in the poset, and 0 otherwise; and redefines
1364
+ ``__lt__`` to use the boolean version of this matrix.
1365
+
1366
+ INPUT:
1367
+
1368
+ - ``boolean`` -- flag (default: ``False``); whether to
1369
+ return a matrix with coefficients in `\GF(2)` or in `\ZZ`
1370
+
1371
+ .. SEEALSO::
1372
+
1373
+ :meth:`moebius_function_matrix`, :meth:`coxeter_transformation`
1374
+
1375
+ EXAMPLES::
1376
+
1377
+ sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])
1378
+ sage: H = P._hasse_diagram
1379
+ sage: M = H.lequal_matrix(); M # needs sage.modules
1380
+ [1 1 1 1 1 1 1 1]
1381
+ [0 1 0 1 0 0 0 1]
1382
+ [0 0 1 1 1 0 1 1]
1383
+ [0 0 0 1 0 0 0 1]
1384
+ [0 0 0 0 1 0 0 1]
1385
+ [0 0 0 0 0 1 1 1]
1386
+ [0 0 0 0 0 0 1 1]
1387
+ [0 0 0 0 0 0 0 1]
1388
+ sage: M.base_ring() # needs sage.modules
1389
+ Integer Ring
1390
+
1391
+ sage: P = posets.DiamondPoset(6)
1392
+ sage: H = P._hasse_diagram
1393
+ sage: M = H.lequal_matrix(boolean=True) # needs sage.modules
1394
+ sage: M.base_ring() # needs sage.modules
1395
+ Finite Field of size 2
1396
+
1397
+ TESTS::
1398
+
1399
+ sage: H.lequal_matrix().is_immutable() # needs sage.modules
1400
+ True
1401
+ """
1402
+ if boolean:
1403
+ return self._leq_matrix_boolean
1404
+ return self._leq_matrix
1405
+
1406
+ def _alternate_is_lequal(self, i, j):
1407
+ r"""
1408
+ Return ``True`` if ``i`` is less than or equal to ``j`` in
1409
+ ``self``, and ``False`` otherwise.
1410
+
1411
+ .. NOTE::
1412
+
1413
+ If the :meth:`lequal_matrix` has been computed, then
1414
+ :meth:`is_lequal` is redefined to use the cached data.
1415
+
1416
+ EXAMPLES::
1417
+
1418
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1419
+ sage: H = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]})
1420
+ sage: H.lequal_matrix() # needs sage.modules
1421
+ [1 0 1 1 1]
1422
+ [0 1 1 1 1]
1423
+ [0 0 1 1 1]
1424
+ [0 0 0 1 1]
1425
+ [0 0 0 0 1]
1426
+ sage: x,y,z = 0, 1, 4
1427
+ sage: H._alternate_is_lequal(x,y)
1428
+ False
1429
+ sage: H._alternate_is_lequal(y,x)
1430
+ False
1431
+ sage: H._alternate_is_lequal(x,z)
1432
+ True
1433
+ sage: H._alternate_is_lequal(y,z)
1434
+ True
1435
+ sage: H._alternate_is_lequal(z,z)
1436
+ True
1437
+ """
1438
+ return j in self._leq_storage[i]
1439
+
1440
+ def prime_elements(self):
1441
+ r"""
1442
+ Return the join-prime and meet-prime elements of the bounded poset.
1443
+
1444
+ An element `x` of a poset `P` is join-prime if the subposet
1445
+ induced by `\{y \in P \mid y \not\ge x\}` has a top element.
1446
+ Meet-prime is defined dually.
1447
+
1448
+ .. NOTE::
1449
+
1450
+ The poset is expected to be bounded, and this is *not* checked.
1451
+
1452
+ OUTPUT:
1453
+
1454
+ A pair `(j, m)` where `j` is a list of join-prime elements
1455
+ and `m` is a list of meet-prime elements.
1456
+
1457
+ EXAMPLES::
1458
+
1459
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1460
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]})
1461
+ sage: H.prime_elements()
1462
+ ([1, 2], [2, 3])
1463
+ """
1464
+ n = self.order()
1465
+ join_primes = []
1466
+ meet_primes = []
1467
+
1468
+ def add_elements(e):
1469
+ upset = frozenset(self.depth_first_search(e))
1470
+ # The complement of the upper set of a join-prime must have
1471
+ # a top element. Maximal elements of the complement are those
1472
+ # covered by only elements in the upper set. If there is only
1473
+ # one maximal element, it is a meet-prime and 'e' is a
1474
+ # join-prime.
1475
+ meet_prime = None
1476
+ for u in upset:
1477
+ for m in self.neighbor_in_iterator(u):
1478
+ if (m not in upset and
1479
+ all(u_ in upset for u_ in
1480
+ self.neighbor_out_iterator(m))):
1481
+ if meet_prime is not None:
1482
+ return
1483
+ meet_prime = m
1484
+ join_primes.append(e)
1485
+ meet_primes.append(meet_prime)
1486
+
1487
+ for e in range(n):
1488
+ # Join-primes are join-irreducibles, only check those.
1489
+ if self.in_degree(e) == 1:
1490
+ add_elements(e)
1491
+
1492
+ return join_primes, meet_primes
1493
+
1494
+ @lazy_attribute
1495
+ def _meet(self):
1496
+ r"""
1497
+ Return the matrix of meets of ``self``.
1498
+
1499
+ The ``(x,y)``-entry of this matrix is the meet of ``x`` and
1500
+ ``y`` in ``self`` if the meet exists; and `-1` otherwise.
1501
+
1502
+ EXAMPLES::
1503
+
1504
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1505
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
1506
+ sage: H._meet # needs sage.modules
1507
+ [0 0 0 0 0 0 0 0]
1508
+ [0 1 0 0 1 0 0 1]
1509
+ [0 0 2 0 2 2 2 2]
1510
+ [0 0 0 3 0 0 3 3]
1511
+ [0 1 2 0 4 2 2 4]
1512
+ [0 0 2 0 2 5 2 5]
1513
+ [0 0 2 3 2 2 6 6]
1514
+ [0 1 2 3 4 5 6 7]
1515
+
1516
+ sage: H = HasseDiagram({0:[2,3],1:[2,3]})
1517
+ sage: H._meet # needs sage.modules
1518
+ [ 0 -1 0 0]
1519
+ [-1 1 1 1]
1520
+ [ 0 1 2 -1]
1521
+ [ 0 1 -1 3]
1522
+
1523
+ sage: H = HasseDiagram({0:[1,2],1:[3,4],2:[3,4]})
1524
+ sage: H._meet # needs sage.modules
1525
+ [ 0 0 0 0 0]
1526
+ [ 0 1 0 1 1]
1527
+ [ 0 0 2 2 2]
1528
+ [ 0 1 2 3 -1]
1529
+ [ 0 1 2 -1 4]
1530
+
1531
+ TESTS::
1532
+
1533
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1534
+ sage: L = LatticePoset({0:[1,2,3],1:[4],2:[4],3:[4]}) # needs sage.modules
1535
+ sage: P = L.dual() # needs sage.modules
1536
+ sage: P.meet(2,3) # needs sage.modules
1537
+ 4
1538
+ """
1539
+ self._meet_semilattice_failure = ()
1540
+ n = self.cardinality()
1541
+ if n == 0:
1542
+ return matrix(0) # noqa: F821
1543
+ meet = [[-1 for x in range(n)] for x in range(n)]
1544
+ lc = [self.neighbors_in(x) for x in range(n)] # Lc = lower covers
1545
+
1546
+ for x in range(n):
1547
+ meet[x][x] = x
1548
+ for y in range(x):
1549
+ T = [meet[y][z] for z in lc[x] if meet[y][z] != -1]
1550
+ if not T:
1551
+ q = -1
1552
+ else:
1553
+ q = max(T)
1554
+ for z in T:
1555
+ if meet[z][q] != z:
1556
+ q = -1
1557
+ break
1558
+ meet[x][y] = q
1559
+ meet[y][x] = q
1560
+ if q == -1:
1561
+ self._meet_semilattice_failure += ((x, y),)
1562
+ return matrix(ZZ, meet) # noqa: F821
1563
+
1564
+ def meet_matrix(self):
1565
+ r"""
1566
+ Return the matrix of meets of ``self``, when ``self`` is a
1567
+ meet-semilattice; raise an error otherwise.
1568
+
1569
+ The ``(x,y)``-entry of this matrix is the meet of ``x`` and
1570
+ ``y`` in ``self``.
1571
+
1572
+ This algorithm is modelled after the algorithm of Freese-Jezek-Nation
1573
+ (p217). It can also be found on page 140 of [Gec81]_.
1574
+
1575
+ .. NOTE::
1576
+
1577
+ If ``self`` is a meet-semilattice, then the return of this method
1578
+ is the same as :meth:`_meet`. Once the matrix has been computed,
1579
+ it is stored in :meth:`_meet`. Delete this attribute if you want to
1580
+ recompute the matrix.
1581
+
1582
+ EXAMPLES::
1583
+
1584
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1585
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
1586
+ sage: H.meet_matrix() # needs sage.modules
1587
+ [0 0 0 0 0 0 0 0]
1588
+ [0 1 0 0 1 0 0 1]
1589
+ [0 0 2 0 2 2 2 2]
1590
+ [0 0 0 3 0 0 3 3]
1591
+ [0 1 2 0 4 2 2 4]
1592
+ [0 0 2 0 2 5 2 5]
1593
+ [0 0 2 3 2 2 6 6]
1594
+ [0 1 2 3 4 5 6 7]
1595
+
1596
+ REFERENCE:
1597
+
1598
+ .. [Gec81] Fundamentals of Computation Theory
1599
+ Gecseg, F.
1600
+ Proceedings of the 1981 International Fct-Conference
1601
+ Szeged, Hungaria, August 24-28, vol 117
1602
+ Springer-Verlag, 1981
1603
+
1604
+ TESTS::
1605
+
1606
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1607
+ sage: H = HasseDiagram({0:[2,3],1:[2,3]})
1608
+ sage: H.meet_matrix()
1609
+ Traceback (most recent call last):
1610
+ ...
1611
+ ValueError: not a meet-semilattice: no bottom element
1612
+
1613
+ sage: H = HasseDiagram({0:[1,2],1:[3,4],2:[3,4]})
1614
+ sage: H.meet_matrix() # needs sage.modules
1615
+ Traceback (most recent call last):
1616
+ ...
1617
+ LatticeError: no meet for ...
1618
+ """
1619
+ n = self.cardinality()
1620
+ if (n != 0) and (not self.has_bottom()):
1621
+ raise ValueError("not a meet-semilattice: no bottom element")
1622
+ # call the attribute to build the matrix and _meet_semilattice_failure
1623
+ mt = self._meet
1624
+ if self._meet_semilattice_failure:
1625
+ x, y = self._meet_semilattice_failure[0]
1626
+ raise LatticeError('meet', x, y)
1627
+ return mt
1628
+
1629
+ def is_meet_semilattice(self) -> bool:
1630
+ r"""
1631
+ Return ``True`` if ``self`` has a meet operation, and
1632
+ ``False`` otherwise.
1633
+
1634
+ EXAMPLES::
1635
+
1636
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1637
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
1638
+ sage: H.is_meet_semilattice() # needs sage.modules
1639
+ True
1640
+
1641
+ sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]})
1642
+ sage: H.is_meet_semilattice() # needs sage.modules
1643
+ True
1644
+
1645
+ sage: H = HasseDiagram({0:[2,3],1:[2,3]})
1646
+ sage: H.is_meet_semilattice() # needs sage.modules
1647
+ False
1648
+
1649
+ sage: H = HasseDiagram({0:[1,2],1:[3,4],2:[3,4]})
1650
+ sage: H.is_meet_semilattice() # needs sage.modules
1651
+ False
1652
+ """
1653
+ try:
1654
+ self.meet_matrix()
1655
+ except ValueError:
1656
+ return False
1657
+ else:
1658
+ return True
1659
+
1660
+ @lazy_attribute
1661
+ def _join(self):
1662
+ r"""
1663
+ Compute a matrix whose ``(x,y)``-entry is the join of ``x``
1664
+ and ``y`` in ``self`` if the join exists; and `-1` otherwise.
1665
+
1666
+ EXAMPLES::
1667
+
1668
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1669
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
1670
+ sage: H._join # needs sage.modules
1671
+ [0 1 2 3 4 5 6 7]
1672
+ [1 1 4 7 4 7 7 7]
1673
+ [2 4 2 6 4 5 6 7]
1674
+ [3 7 6 3 7 7 6 7]
1675
+ [4 4 4 7 4 7 7 7]
1676
+ [5 7 5 7 7 5 7 7]
1677
+ [6 7 6 6 7 7 6 7]
1678
+ [7 7 7 7 7 7 7 7]
1679
+
1680
+ sage: H = HasseDiagram({0:[2,3],1:[2,3]})
1681
+ sage: H._join # needs sage.modules
1682
+ [ 0 -1 2 3]
1683
+ [-1 1 2 3]
1684
+ [ 2 2 2 -1]
1685
+ [ 3 3 -1 3]
1686
+
1687
+ sage: H = HasseDiagram({0:[2,3],1:[2,3],2:[4],3:[4]})
1688
+ sage: H._join # needs sage.modules
1689
+ [ 0 -1 2 3 4]
1690
+ [-1 1 2 3 4]
1691
+ [ 2 2 2 4 4]
1692
+ [ 3 3 4 3 4]
1693
+ [ 4 4 4 4 4]
1694
+
1695
+ TESTS::
1696
+
1697
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1698
+ sage: L = LatticePoset({0:[1,2,3],1:[4],2:[4],3:[4]}) # needs sage.modules
1699
+ sage: P = L.dual() # needs sage.modules
1700
+ sage: P.join(2,3) # needs sage.modules
1701
+ 0
1702
+ """
1703
+ self._join_semilattice_failure = ()
1704
+ n = self.cardinality()
1705
+ if n == 0:
1706
+ return matrix(0) # noqa: F821
1707
+ join = [[-1 for x in range(n)] for x in range(n)]
1708
+ uc = [self.neighbors_out(x) for x in range(n)] # uc = upper covers
1709
+
1710
+ for x in range(n - 1, -1, -1):
1711
+ join[x][x] = x
1712
+ for y in range(n - 1, x, -1):
1713
+ T = [join[y][z] for z in uc[x] if join[y][z] != -1]
1714
+ if not T:
1715
+ q = -1
1716
+ else:
1717
+ q = min(T)
1718
+ for z in T:
1719
+ if join[z][q] != z:
1720
+ q = -1
1721
+ break
1722
+ join[x][y] = q
1723
+ join[y][x] = q
1724
+ if q == -1:
1725
+ self._join_semilattice_failure += ((x, y),)
1726
+
1727
+ return matrix(ZZ, join) # noqa: F821
1728
+
1729
+ def join_matrix(self):
1730
+ r"""
1731
+ Return the matrix of joins of ``self``, when ``self`` is a
1732
+ join-semilattice; raise an error otherwise.
1733
+
1734
+ The ``(x,y)``-entry of this matrix is the join of ``x`` and
1735
+ ``y`` in ``self``.
1736
+
1737
+ This algorithm is modelled after the algorithm of Freese-Jezek-Nation
1738
+ (p217). It can also be found on page 140 of [Gec81]_.
1739
+
1740
+ .. NOTE::
1741
+
1742
+ If ``self`` is a join-semilattice, then the return of this method
1743
+ is the same as :meth:`_join`. Once the matrix has been computed,
1744
+ it is stored in :meth:`_join`. Delete this attribute if you want
1745
+ to recompute the matrix.
1746
+
1747
+ EXAMPLES::
1748
+
1749
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1750
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
1751
+ sage: H.join_matrix() # needs sage.modules
1752
+ [0 1 2 3 4 5 6 7]
1753
+ [1 1 4 7 4 7 7 7]
1754
+ [2 4 2 6 4 5 6 7]
1755
+ [3 7 6 3 7 7 6 7]
1756
+ [4 4 4 7 4 7 7 7]
1757
+ [5 7 5 7 7 5 7 7]
1758
+ [6 7 6 6 7 7 6 7]
1759
+ [7 7 7 7 7 7 7 7]
1760
+
1761
+ TESTS::
1762
+
1763
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1764
+ sage: H = HasseDiagram({0:[2,3],1:[2,3]})
1765
+ sage: H.join_matrix()
1766
+ Traceback (most recent call last):
1767
+ ...
1768
+ ValueError: not a join-semilattice: no top element
1769
+
1770
+ sage: H = HasseDiagram({0:[2,3],1:[2,3],2:[4],3:[4]})
1771
+ sage: H.join_matrix() # needs sage.modules
1772
+ Traceback (most recent call last):
1773
+ ...
1774
+ LatticeError: no join for ...
1775
+ """
1776
+ n = self.cardinality()
1777
+ if (n != 0) and (not self.has_top()):
1778
+ raise ValueError("not a join-semilattice: no top element")
1779
+ # call the attribute to build the matrix and _join_semilattice_failure
1780
+ jn = self._join
1781
+ if self._join_semilattice_failure:
1782
+ x, y = self._join_semilattice_failure[0]
1783
+ raise LatticeError('join', x, y)
1784
+ return jn
1785
+
1786
+ def is_join_semilattice(self) -> bool:
1787
+ r"""
1788
+ Return ``True`` if ``self`` has a join operation, and
1789
+ ``False`` otherwise.
1790
+
1791
+ EXAMPLES::
1792
+
1793
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1794
+ sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]})
1795
+ sage: H.is_join_semilattice() # needs sage.modules
1796
+ True
1797
+ sage: H = HasseDiagram({0:[2,3],1:[2,3]})
1798
+ sage: H.is_join_semilattice() # needs sage.modules
1799
+ False
1800
+ sage: H = HasseDiagram({0:[2,3],1:[2,3],2:[4],3:[4]})
1801
+ sage: H.is_join_semilattice() # needs sage.modules
1802
+ False
1803
+ """
1804
+ try:
1805
+ self.join_matrix()
1806
+ except ValueError:
1807
+ return False
1808
+ else:
1809
+ return True
1810
+
1811
+ def find_nonsemidistributive_elements(self, meet_or_join):
1812
+ r"""
1813
+ Check if the lattice is semidistributive or not.
1814
+
1815
+ INPUT:
1816
+
1817
+ - ``meet_or_join`` -- string ``'meet'`` or ``'join'``
1818
+ to decide if to check for join-semidistributivity or
1819
+ meet-semidistributivity
1820
+
1821
+ OUTPUT:
1822
+
1823
+ - ``None`` if the lattice is semidistributive OR
1824
+ - tuple ``(u, e, x, y)`` such that
1825
+ `u = e \vee x = e \vee y` but `u \neq e \vee (x \wedge y)`
1826
+ if ``meet_or_join=='join'`` and
1827
+ `u = e \wedge x = e \wedge y` but `u \neq e \wedge (x \vee y)`
1828
+ if ``meet_or_join=='meet'``
1829
+
1830
+ EXAMPLES::
1831
+
1832
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1833
+ sage: H = HasseDiagram({0:[1, 2], 1:[3, 4], 2:[4, 5], 3:[6],
1834
+ ....: 4:[6], 5:[6]})
1835
+ sage: H.find_nonsemidistributive_elements('join') is None # needs sage.modules
1836
+ False
1837
+ sage: H.find_nonsemidistributive_elements('meet') is None # needs sage.modules
1838
+ True
1839
+ """
1840
+ if meet_or_join == 'join':
1841
+ M1 = self.join_matrix()
1842
+ M2 = self.meet_matrix()
1843
+ elif meet_or_join == 'meet':
1844
+ M1 = self.meet_matrix()
1845
+ M2 = self.join_matrix()
1846
+ else:
1847
+ raise ValueError("meet_or_join must be 'join' or 'meet'")
1848
+
1849
+ n = self.order()
1850
+
1851
+ for e in range(n):
1852
+ for x in range(n):
1853
+ u = M1[e, x]
1854
+ for y in range(x):
1855
+ if u == M1[e, y] and u != M1[e, M2[x, y]]:
1856
+ return (u, e, x, y)
1857
+
1858
+ return None
1859
+
1860
+ def vertical_decomposition(self, return_list=False):
1861
+ """
1862
+ Return vertical decomposition of the lattice.
1863
+
1864
+ This is the backend function for vertical decomposition
1865
+ functions of lattices.
1866
+
1867
+ The property of being vertically decomposable is defined for lattices.
1868
+ This is *not* checked, and the function works with any bounded poset.
1869
+
1870
+ INPUT:
1871
+
1872
+ - ``return_list`` -- boolean (default: ``False``); if ``False`` (the
1873
+ default), return an element that is not the top neither the bottom
1874
+ element of the lattice, but is comparable to all elements of the
1875
+ lattice, if the lattice is vertically decomposable and ``None``
1876
+ otherwise. If ``True``, return list of decomposition elements.
1877
+
1878
+ EXAMPLES::
1879
+
1880
+ sage: H = posets.BooleanLattice(4)._hasse_diagram
1881
+ sage: H.vertical_decomposition() is None
1882
+ True
1883
+ sage: P = Poset( ([1,2,3,6,12,18,36], attrcall("divides")) )
1884
+ sage: P._hasse_diagram.vertical_decomposition()
1885
+ 3
1886
+ sage: P._hasse_diagram.vertical_decomposition(return_list=True)
1887
+ [3]
1888
+ """
1889
+ n = self.cardinality()
1890
+ if n < 3:
1891
+ if return_list:
1892
+ return []
1893
+ return None
1894
+ result = [] # Never take the bottom element to list.
1895
+ m = 0
1896
+ for i in range(n - 1):
1897
+ for j in self.outgoing_edge_iterator(i):
1898
+ m = max(m, j[1])
1899
+ if m == i + 1:
1900
+ if not return_list:
1901
+ if m < n - 1:
1902
+ return m
1903
+ return None
1904
+ result.append(m)
1905
+ result.pop() # Remove the top element.
1906
+ return result
1907
+
1908
+ def is_complemented(self) -> int | None:
1909
+ """
1910
+ Return an element of the lattice that has no complement.
1911
+
1912
+ If the lattice is complemented, return ``None``.
1913
+
1914
+ EXAMPLES::
1915
+
1916
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1917
+
1918
+ sage: H = HasseDiagram({0:[1, 2], 1:[3], 2:[3], 3:[4]})
1919
+ sage: H.is_complemented() # needs sage.modules
1920
+ 1
1921
+
1922
+ sage: H = HasseDiagram({0:[1, 2, 3], 1:[4], 2:[4], 3:[4]})
1923
+ sage: H.is_complemented() is None # needs sage.modules
1924
+ True
1925
+ """
1926
+ mt = self.meet_matrix()
1927
+ jn = self.join_matrix()
1928
+ top = self.cardinality() - 1
1929
+ has_complement = [False] * top
1930
+
1931
+ for i in range(1, top):
1932
+ if has_complement[i]:
1933
+ continue
1934
+ for j in range(top, 0, -1):
1935
+ if jn[i, j] == top and mt[i, j] == 0:
1936
+ has_complement[j] = True
1937
+ break
1938
+ else:
1939
+ return i
1940
+
1941
+ return None
1942
+
1943
+ def pseudocomplement(self, element):
1944
+ """
1945
+ Return the pseudocomplement of ``element``, if it exists.
1946
+
1947
+ The pseudocomplement is the greatest element whose
1948
+ meet with given element is the bottom element. It may
1949
+ not exist, and then the function returns ``None``.
1950
+
1951
+ INPUT:
1952
+
1953
+ - ``element`` -- an element of the lattice
1954
+
1955
+ OUTPUT:
1956
+
1957
+ An element of the Hasse diagram, i.e. an integer, or
1958
+ ``None`` if the pseudocomplement does not exist.
1959
+
1960
+ EXAMPLES::
1961
+
1962
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1963
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]})
1964
+ sage: H.pseudocomplement(2) # needs sage.modules
1965
+ 3
1966
+
1967
+ sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4], 3: [4]})
1968
+ sage: H.pseudocomplement(2) is None # needs sage.modules
1969
+ True
1970
+ """
1971
+ e = self.order() - 1
1972
+ mt = self.meet_matrix()
1973
+ while mt[e, element] != 0:
1974
+ e -= 1
1975
+ e1 = e
1976
+ while e1 > 0:
1977
+ if mt[e1, element] == 0 and not self.is_lequal(e1, e):
1978
+ return None
1979
+ e1 -= 1
1980
+ return e
1981
+
1982
+ def orthocomplementations_iterator(self):
1983
+ r"""
1984
+ Return an iterator over orthocomplementations of the lattice.
1985
+
1986
+ OUTPUT: an iterator that gives plain list of integers
1987
+
1988
+ EXAMPLES::
1989
+
1990
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
1991
+ sage: H = HasseDiagram({0:[1,2], 1:[3,4], 3:[5], 4:[5], 2:[6,7],
1992
+ ....: 6:[8], 7:[8], 5:[9], 8:[9]})
1993
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
1994
+ [[9, 8, 5, 6, 7, 2, 3, 4, 1, 0], [9, 8, 5, 7, 6, 2, 4, 3, 1, 0]]
1995
+
1996
+ ALGORITHM:
1997
+
1998
+ As ``DiamondPoset(2*n+2)`` has `(2n)!/(n!2^n)` different
1999
+ orthocomplementations, the complexity of listing all of
2000
+ them is necessarily `O(n!)`.
2001
+
2002
+ An orthocomplemented lattice is self-dual, so that for example
2003
+ orthocomplement of an atom is a coatom. This function
2004
+ basically just computes list of possible orthocomplementations
2005
+ for every element (i.e. they must be complements and "duals"),
2006
+ and then tries to fit them all.
2007
+
2008
+ TESTS:
2009
+
2010
+ Special and corner cases::
2011
+
2012
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2013
+ sage: H = HasseDiagram() # Empty
2014
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2015
+ [[]]
2016
+ sage: H = HasseDiagram({0:[]}) # One element
2017
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2018
+ [[0]]
2019
+ sage: H = HasseDiagram({0:[1]}) # Two elements
2020
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2021
+ [[1, 0]]
2022
+
2023
+ Trivial cases: odd number of elements, not self-dual, not complemented::
2024
+
2025
+ sage: H = posets.DiamondPoset(5)._hasse_diagram
2026
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2027
+ []
2028
+ sage: H = posets.ChainPoset(4)._hasse_diagram
2029
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2030
+ []
2031
+ sage: H = HasseDiagram( ([[0, 1], [0, 2], [0, 3], [1, 4], [1, 8], [4, 6], [4, 7], [6, 9], [7, 9], [2, 5], [3, 5], [5, 8], [8, 9]]) )
2032
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2033
+ []
2034
+ sage: H = HasseDiagram({0:[1, 2, 3], 1: [4], 2:[4], 3: [5], 4:[5]})
2035
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2036
+ []
2037
+
2038
+ Complemented, self-dual and even number of elements, but
2039
+ not orthocomplemented::
2040
+
2041
+ sage: H = HasseDiagram( ([[0, 1], [1, 2], [2, 3], [0, 4], [4, 5], [0, 6], [3, 7], [5, 7], [6, 7]]) )
2042
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2043
+ []
2044
+
2045
+ Unique orthocomplementations; second is not uniquely complemented,
2046
+ but has only one orthocomplementation::
2047
+
2048
+ sage: H = posets.BooleanLattice(4)._hasse_diagram # Uniquely complemented
2049
+ sage: len(list(H.orthocomplementations_iterator())) # needs sage.groups
2050
+ 1
2051
+ sage: H = HasseDiagram({0:[1, 2], 1:[3], 2:[4], 3:[5], 4:[5]})
2052
+ sage: len([_ for _ in H.orthocomplementations_iterator()]) # needs sage.groups
2053
+ 1
2054
+
2055
+ "Lengthening diamond" must keep the number of orthocomplementations::
2056
+
2057
+ sage: H = HasseDiagram( ([[0, 1], [0, 2], [0, 3], [0, 4], [1, 5], [2, 5], [3, 5], [4, 5]]) )
2058
+ sage: n = len([_ for _ in H.orthocomplementations_iterator()]); n # needs sage.groups
2059
+ 3
2060
+ sage: H = HasseDiagram('M]??O?@??C??OA???OA??@?A??C?A??O??')
2061
+ sage: len([_ for _ in H.orthocomplementations_iterator()]) == n # needs sage.groups
2062
+ True
2063
+
2064
+ This lattice has an unique "possible orthocomplement" for every
2065
+ element, but they can not be fit together; orthocomplement pairs
2066
+ would be 0-11, 1-7, 2-4, 3-10, 5-9 and 6-8, and then orthocomplements
2067
+ for chain 0-1-6-11 would be 11-7-8-0, which is not a chain::
2068
+
2069
+ sage: H = HasseDiagram('KTGG_?AAC?O?o?@?@?E?@?@??')
2070
+ sage: list(H.orthocomplementations_iterator()) # needs sage.groups
2071
+ []
2072
+ """
2073
+ n = self.order()
2074
+
2075
+ # Special cases first
2076
+ if n == 0:
2077
+ yield []
2078
+ return
2079
+ if n == 1:
2080
+ yield [0]
2081
+ return
2082
+ if n % 2:
2083
+ return
2084
+
2085
+ dual_isomorphism = self.is_isomorphic(self.reverse(), certificate=True)[1]
2086
+ if dual_isomorphism is None: # i.e. if the lattice is not self-dual.
2087
+ return
2088
+
2089
+ # We compute possible orthocomplements, i.e. elements
2090
+ # with "dual position" and complement to each other.
2091
+
2092
+ orbits = self.automorphism_group(return_group=False, orbits=True)
2093
+
2094
+ orbit_number = [None] * n
2095
+ for ind, orbit in enumerate(orbits):
2096
+ for e in orbit:
2097
+ orbit_number[e] = ind
2098
+
2099
+ mt = self.meet_matrix()
2100
+ jn = self.join_matrix()
2101
+
2102
+ items = ((e, dual_isomorphism[e]) for e in range(n))
2103
+
2104
+ # Fix following after issue #20727
2105
+ comps = [[x for x in range(n)
2106
+ if mt[e, x] == 0 and jn[e, x] == n - 1 and
2107
+ x in orbits[orbit_number[dual_e]]]
2108
+ for e, dual_e in items]
2109
+
2110
+ # Fitting is done by this recursive function:
2111
+ def recursive_fit(orthocomplements, unbinded):
2112
+ if not unbinded:
2113
+ yield orthocomplements
2114
+ else:
2115
+ next_to_fit = unbinded[0]
2116
+ possible_values = [x for x in comps[next_to_fit]
2117
+ if x not in orthocomplements]
2118
+ for x in self.lower_covers_iterator(next_to_fit):
2119
+ if orthocomplements[x] is not None:
2120
+ possible_values = [y for y in possible_values if self.has_edge(y, orthocomplements[x])]
2121
+ for x in self.upper_covers_iterator(next_to_fit):
2122
+ if orthocomplements[x] is not None:
2123
+ possible_values = [y for y in possible_values if self.has_edge(orthocomplements[x], y)]
2124
+
2125
+ for e in possible_values:
2126
+
2127
+ new_binded = orthocomplements[:]
2128
+ new_binded[next_to_fit] = e
2129
+ new_binded[e] = next_to_fit
2130
+
2131
+ new_unbinded = unbinded[1:] # Remove next_to_fit
2132
+ new_unbinded.remove(e)
2133
+
2134
+ yield from recursive_fit(new_binded, new_unbinded)
2135
+
2136
+ start = [None] * n
2137
+ # A little optimization
2138
+ for e in range(n):
2139
+ if not comps[e]: # Not any possible orthocomplement
2140
+ return
2141
+ if len(comps[e]) == 1: # Do not re-fit this every time
2142
+ e_ = comps[e][0]
2143
+ # Every element might have one possible orthocomplement,
2144
+ # but so that they don't fit together. Must check that.
2145
+ for lc in self.lower_covers_iterator(e):
2146
+ if not (start[lc] is None or self.has_edge(e_, start[lc])):
2147
+ return
2148
+ if start[e_] is None:
2149
+ start[e] = e_
2150
+ start[e_] = e
2151
+ start_unbinded = [e for e in range(n) if start[e] is None]
2152
+
2153
+ yield from recursive_fit(start, start_unbinded)
2154
+
2155
+ def find_nonsemimodular_pair(self, upper):
2156
+ """
2157
+ Return pair of elements showing the lattice is not modular.
2158
+
2159
+ INPUT:
2160
+
2161
+ - ``upper`` -- boolean; if ``True``, test whether the lattice is
2162
+ upper semimodular. Otherwise test whether the lattice is
2163
+ lower semimodular.
2164
+
2165
+ OUTPUT:
2166
+
2167
+ ``None``, if the lattice is semimodular. Pair `(a, b)` violating
2168
+ semimodularity otherwise.
2169
+
2170
+ EXAMPLES::
2171
+
2172
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2173
+ sage: H = HasseDiagram({0:[1, 2], 1:[3, 4], 2:[4, 5], 3:[6], 4:[6], 5:[6]})
2174
+ sage: H.find_nonsemimodular_pair(upper=True) is None
2175
+ True
2176
+ sage: H.find_nonsemimodular_pair(upper=False)
2177
+ (5, 3)
2178
+
2179
+ sage: H_ = HasseDiagram(H.reverse().relabel(lambda x: 6-x, inplace=False))
2180
+ sage: H_.find_nonsemimodular_pair(upper=True)
2181
+ (3, 1)
2182
+ sage: H_.find_nonsemimodular_pair(upper=False) is None
2183
+ True
2184
+ """
2185
+ neighbors = self.neighbors_out if upper else self.neighbors_in
2186
+
2187
+ n = self.order()
2188
+ for e in range(n):
2189
+ covers = neighbors(e)
2190
+ covers_len = len(covers)
2191
+ if covers_len < 2:
2192
+ continue
2193
+ for a_i in range(covers_len):
2194
+ a = covers[a_i]
2195
+ covers_a = neighbors(a)
2196
+ for b_i in range(a_i):
2197
+ b = covers[b_i]
2198
+ if not any(j in covers_a for j in neighbors(b)):
2199
+ return (a, b)
2200
+ return None
2201
+
2202
+ def antichains_iterator(self):
2203
+ r"""
2204
+ Return an iterator over the antichains of the poset.
2205
+
2206
+ .. NOTE::
2207
+
2208
+ The algorithm is based on Freese-Jezek-Nation p. 226.
2209
+ It does a depth first search through the set of all
2210
+ antichains organized in a prefix tree.
2211
+
2212
+ EXAMPLES::
2213
+
2214
+ sage: # needs sage.modules
2215
+ sage: P = posets.PentagonPoset()
2216
+ sage: H = P._hasse_diagram
2217
+ sage: H.antichains_iterator()
2218
+ <generator object ...antichains_iterator at ...>
2219
+ sage: list(H.antichains_iterator())
2220
+ [[], [4], [3], [2], [1], [1, 3], [1, 2], [0]]
2221
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2222
+ sage: H = HasseDiagram({0:[1,2],1:[4],2:[3],3:[4]})
2223
+ sage: list(H.antichains_iterator())
2224
+ [[], [4], [3], [2], [1], [1, 3], [1, 2], [0]]
2225
+ sage: H = HasseDiagram({0:[],1:[],2:[]})
2226
+ sage: list(H.antichains_iterator())
2227
+ [[], [2], [1], [1, 2], [0], [0, 2], [0, 1], [0, 1, 2]]
2228
+ sage: H = HasseDiagram({0:[1],1:[2],2:[3],3:[4]})
2229
+ sage: list(H.antichains_iterator())
2230
+ [[], [4], [3], [2], [1], [0]]
2231
+
2232
+ TESTS::
2233
+
2234
+ sage: H = Poset()._hasse_diagram
2235
+ sage: list(H.antichains_iterator()) # needs sage.modules
2236
+ [[]]
2237
+ """
2238
+ # NOTE: Ordering of antichains as a prefix tree is crucial for
2239
+ # congruences_iterator() to work. Change it, if you change this.
2240
+
2241
+ # Complexity note:
2242
+ # antichains_queues never grows longer than self.cardinality().
2243
+ # Indeed, if a appears before b in antichains_queues, then
2244
+ # the largest element of a is strictly smaller than that of b.
2245
+ antichains_queues = [([], list(range(self.cardinality() - 1, -1, -1)))]
2246
+ leq = self.lequal_matrix()
2247
+ while antichains_queues:
2248
+ (antichain, queue) = antichains_queues.pop()
2249
+ # Invariant:
2250
+ # - the elements of antichain are independent
2251
+ # - the elements of queue are independent from those of antichain
2252
+ yield antichain
2253
+ while queue:
2254
+ x = queue.pop()
2255
+ new_antichain = antichain + [x]
2256
+ new_queue = [t for t in queue if not (leq[t, x] or leq[x, t])]
2257
+ antichains_queues.append((new_antichain, new_queue))
2258
+
2259
+ def are_incomparable(self, i, j):
2260
+ """
2261
+ Return whether ``i`` and ``j`` are incomparable in the poset.
2262
+
2263
+ INPUT:
2264
+
2265
+ - ``i``, ``j`` -- vertices of this Hasse diagram
2266
+
2267
+ EXAMPLES::
2268
+
2269
+ sage: # needs sage.modules
2270
+ sage: P = posets.PentagonPoset()
2271
+ sage: H = P._hasse_diagram
2272
+ sage: H.are_incomparable(1,2)
2273
+ True
2274
+ sage: V = H.vertices(sort=True)
2275
+ sage: [ (i,j) for i in V for j in V if H.are_incomparable(i,j)]
2276
+ [(1, 2), (1, 3), (2, 1), (3, 1)]
2277
+ """
2278
+ if i == j:
2279
+ return False
2280
+ if i > j:
2281
+ i, j = j, i
2282
+ mat = self._leq_matrix_boolean
2283
+ return not mat[i, j]
2284
+
2285
+ def are_comparable(self, i, j):
2286
+ """
2287
+ Return whether ``i`` and ``j`` are comparable in the poset.
2288
+
2289
+ INPUT:
2290
+
2291
+ - ``i``, ``j`` -- vertices of this Hasse diagram
2292
+
2293
+ EXAMPLES::
2294
+
2295
+ sage: # needs sage.modules
2296
+ sage: P = posets.PentagonPoset()
2297
+ sage: H = P._hasse_diagram
2298
+ sage: H.are_comparable(1,2)
2299
+ False
2300
+ sage: V = H.vertices(sort=True)
2301
+ sage: [ (i,j) for i in V for j in V if H.are_comparable(i,j)]
2302
+ [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1), (1, 4),
2303
+ (2, 0), (2, 2), (2, 3), (2, 4), (3, 0), (3, 2), (3, 3), (3, 4),
2304
+ (4, 0), (4, 1), (4, 2), (4, 3), (4, 4)]
2305
+ """
2306
+ if i == j:
2307
+ return True
2308
+ if i > j:
2309
+ i, j = j, i
2310
+ mat = self._leq_matrix_boolean
2311
+ return bool(mat[i, j])
2312
+
2313
+ def antichains(self, element_class=list):
2314
+ """
2315
+ Return all antichains of ``self``, organized as a prefix tree.
2316
+
2317
+ INPUT:
2318
+
2319
+ - ``element_class`` -- (default: ``list``) an iterable type
2320
+
2321
+ EXAMPLES::
2322
+
2323
+ sage: # needs sage.modules
2324
+ sage: P = posets.PentagonPoset()
2325
+ sage: H = P._hasse_diagram
2326
+ sage: A = H.antichains()
2327
+ sage: list(A)
2328
+ [[], [0], [1], [1, 2], [1, 3], [2], [3], [4]]
2329
+ sage: A.cardinality()
2330
+ 8
2331
+ sage: [1,3] in A
2332
+ True
2333
+ sage: [1,4] in A
2334
+ False
2335
+
2336
+ TESTS::
2337
+
2338
+ sage: # needs sage.modules
2339
+ sage: TestSuite(A).run()
2340
+ sage: A = Poset()._hasse_diagram.antichains()
2341
+ sage: list(A)
2342
+ [[]]
2343
+ sage: TestSuite(A).run()
2344
+ """
2345
+ from sage.combinat.subsets_pairwise import PairwiseCompatibleSubsets
2346
+ return PairwiseCompatibleSubsets(self.vertices(sort=True),
2347
+ self.are_incomparable,
2348
+ element_class=element_class)
2349
+
2350
+ def chains(self, element_class=list, exclude=None, conversion=None):
2351
+ """
2352
+ Return all chains of ``self``, organized as a prefix tree.
2353
+
2354
+ INPUT:
2355
+
2356
+ - ``element_class`` -- (default: ``list``) an iterable type
2357
+
2358
+ - ``exclude`` -- elements of the poset to be excluded
2359
+ (default: ``None``)
2360
+
2361
+ - ``conversion`` -- (default: ``None``) used to pass
2362
+ the list of elements of the poset in their fixed order
2363
+
2364
+ OUTPUT:
2365
+
2366
+ The enumerated set (with a forest structure given by prefix
2367
+ ordering) consisting of all chains of ``self``, each of
2368
+ which is given as an ``element_class``.
2369
+
2370
+ If ``conversion`` is given, then the chains are converted
2371
+ to chain of elements of this list.
2372
+
2373
+ EXAMPLES::
2374
+
2375
+ sage: # needs sage.modules
2376
+ sage: P = posets.PentagonPoset()
2377
+ sage: H = P._hasse_diagram
2378
+ sage: A = H.chains()
2379
+ sage: list(A)
2380
+ [[], [0], [0, 1], [0, 1, 4], [0, 2], [0, 2, 3], [0, 2, 3, 4], [0, 2, 4],
2381
+ [0, 3], [0, 3, 4], [0, 4], [1], [1, 4], [2], [2, 3], [2, 3, 4], [2, 4],
2382
+ [3], [3, 4], [4]]
2383
+ sage: A.cardinality()
2384
+ 20
2385
+ sage: [1,3] in A
2386
+ False
2387
+ sage: [1,4] in A
2388
+ True
2389
+
2390
+ One can exclude some vertices::
2391
+
2392
+ sage: # needs sage.modules
2393
+ sage: list(H.chains(exclude=[4, 3]))
2394
+ [[], [0], [0, 1], [0, 2], [1], [2]]
2395
+
2396
+ The ``element_class`` keyword determines how the chains are
2397
+ being returned::
2398
+
2399
+ sage: P = Poset({1: [2, 3], 2: [4]})
2400
+ sage: list(P._hasse_diagram.chains(element_class=tuple))
2401
+ [(), (0,), (0, 1), (0, 1, 2), (0, 2), (0, 3), (1,), (1, 2), (2,), (3,)]
2402
+ sage: list(P._hasse_diagram.chains())
2403
+ [[], [0], [0, 1], [0, 1, 2], [0, 2], [0, 3], [1], [1, 2], [2], [3]]
2404
+
2405
+ (Note that taking the Hasse diagram has renamed the vertices.) ::
2406
+
2407
+ sage: list(P._hasse_diagram.chains(element_class=tuple, exclude=[0]))
2408
+ [(), (1,), (1, 2), (2,), (3,)]
2409
+
2410
+ .. SEEALSO:: :meth:`antichains`
2411
+ """
2412
+ return IncreasingChains(self._leq_storage, element_class, exclude, conversion)
2413
+
2414
+ def chain_polynomial(self):
2415
+ """
2416
+ Return the chain polynomial of the poset.
2417
+
2418
+ The coefficient of `q^k` is the number of chains of `k`
2419
+ elements in the poset. List of coefficients of this polynomial
2420
+ is also called a *f-vector* of the poset.
2421
+
2422
+ EXAMPLES::
2423
+
2424
+ sage: P = posets.ChainPoset(3)
2425
+ sage: H = P._hasse_diagram
2426
+ sage: t = H.chain_polynomial(); t # needs sage.libs.flint
2427
+ q^3 + 3*q^2 + 3*q + 1
2428
+ """
2429
+ return chain_poly(self._leq_storage)._sage_('q') # noqa: F821
2430
+
2431
+ def is_linear_interval(self, t_min, t_max) -> bool:
2432
+ """
2433
+ Return whether the interval ``[t_min, t_max]`` is linear.
2434
+
2435
+ This means that this interval is a total order.
2436
+
2437
+ EXAMPLES::
2438
+ sage: # needs sage.modules
2439
+ sage: P = posets.PentagonPoset()
2440
+ sage: H = P._hasse_diagram
2441
+ sage: H.is_linear_interval(0, 4)
2442
+ False
2443
+ sage: H.is_linear_interval(0, 3)
2444
+ True
2445
+ sage: H.is_linear_interval(1, 3)
2446
+ False
2447
+ sage: H.is_linear_interval(1, 1)
2448
+ True
2449
+
2450
+ TESTS::
2451
+
2452
+ sage: P = posets.TamariLattice(3)
2453
+ sage: H = P._hasse_diagram
2454
+ sage: D = H._leq_storage
2455
+ sage: a, b = H.bottom(), H.top()
2456
+ sage: H.is_linear_interval(a, b)
2457
+ False
2458
+ sage: H.is_linear_interval(a, a)
2459
+ True
2460
+ """
2461
+ if '_leq_storage' in self.__dict__:
2462
+ if not self.is_lequal(t_min, t_max): # very quick check
2463
+ return False
2464
+ t = t_max
2465
+ while t != t_min:
2466
+ found = False
2467
+ for u in self.neighbor_in_iterator(t):
2468
+ if self.is_lequal(t_min, u):
2469
+ if not found:
2470
+ found = True
2471
+ t = u
2472
+ else:
2473
+ return False
2474
+ return True
2475
+
2476
+ # fall back to default implementation
2477
+ it = self.all_paths_iterator([t_min], [t_max],
2478
+ simple=True, trivial=True)
2479
+ try:
2480
+ next(it)
2481
+ except StopIteration: # not comparable
2482
+ return False
2483
+ try:
2484
+ next(it)
2485
+ except StopIteration: # one path
2486
+ return True
2487
+ return False
2488
+
2489
+ def diamonds(self) -> tuple:
2490
+ r"""
2491
+ Return the list of diamonds of ``self``.
2492
+
2493
+ A diamond is the following subgraph of the Hasse diagram::
2494
+
2495
+ z
2496
+ / \
2497
+ x y
2498
+ \ /
2499
+ w
2500
+
2501
+ Thus each edge represents a cover relation in the Hasse diagram.
2502
+ We represent his as the tuple `(w, x, y, z)`.
2503
+
2504
+ OUTPUT: a tuple with
2505
+
2506
+ - a list of all diamonds in the Hasse Diagram,
2507
+ - a boolean checking that every `w,x,y` that form a ``V``, there is a
2508
+ unique element `z`, which completes the diamond.
2509
+
2510
+ EXAMPLES::
2511
+
2512
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2513
+ sage: H = HasseDiagram({0: [1,2], 1: [3], 2: [3], 3: []})
2514
+ sage: H.diamonds()
2515
+ ([(0, 1, 2, 3)], True)
2516
+
2517
+ sage: P = posets.YoungDiagramPoset(Partition([3, 2, 2])) # needs sage.combinat sage.modules
2518
+ sage: H = P._hasse_diagram # needs sage.combinat sage.modules
2519
+ sage: H.diamonds() # needs sage.combinat sage.modules
2520
+ ([(0, 1, 3, 4), (3, 4, 5, 6)], False)
2521
+ """
2522
+ diamonds = []
2523
+ all_diamonds_completed = True
2524
+ for w in self.vertices(sort=True):
2525
+ covers = self.neighbors_out(w)
2526
+ for i, x in enumerate(covers):
2527
+ for y in covers[i + 1:]:
2528
+ zs = self.common_upper_covers([x, y])
2529
+ if len(zs) != 1:
2530
+ all_diamonds_completed = False
2531
+ diamonds.extend((w, x, y, z) for z in zs)
2532
+ return (diamonds, all_diamonds_completed)
2533
+
2534
+ def common_upper_covers(self, vertices):
2535
+ r"""
2536
+ Return the list of all common upper covers of ``vertices``.
2537
+
2538
+ EXAMPLES::
2539
+
2540
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2541
+ sage: H = HasseDiagram({0: [1,2], 1: [3], 2: [3], 3: []})
2542
+ sage: H.common_upper_covers([1, 2])
2543
+ [3]
2544
+
2545
+ sage: from sage.combinat.posets.poset_examples import Posets
2546
+ sage: H = Posets.YoungDiagramPoset(Partition([3, 2, 2]))._hasse_diagram # needs sage.combinat sage.modules
2547
+ sage: H.common_upper_covers([4, 5]) # needs sage.combinat sage.modules
2548
+ [6]
2549
+ """
2550
+ covers = set(self.neighbor_out_iterator(vertices.pop()))
2551
+ for v in vertices:
2552
+ covers = covers.intersection(self.neighbor_out_iterator(v))
2553
+ return list(covers)
2554
+
2555
+ def common_lower_covers(self, vertices):
2556
+ r"""
2557
+ Return the list of all common lower covers of ``vertices``.
2558
+
2559
+ EXAMPLES::
2560
+
2561
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2562
+ sage: H = HasseDiagram({0: [1,2], 1: [3], 2: [3], 3: []})
2563
+ sage: H.common_lower_covers([1, 2])
2564
+ [0]
2565
+
2566
+ sage: from sage.combinat.posets.poset_examples import Posets
2567
+ sage: H = Posets.YoungDiagramPoset(Partition([3, 2, 2]))._hasse_diagram # needs sage.combinat sage.modules
2568
+ sage: H.common_lower_covers([4, 5]) # needs sage.combinat sage.modules
2569
+ [3]
2570
+ """
2571
+ covers = set(self.neighbor_in_iterator(vertices.pop()))
2572
+ for v in vertices:
2573
+ covers = covers.intersection(self.neighbor_in_iterator(v))
2574
+ return list(covers)
2575
+
2576
+ def _trivial_nonregular_congruence(self):
2577
+ """
2578
+ Return a pair of elements giving "trivial" non-regular congruence.
2579
+
2580
+ This returns a pair `a, b` such that `b` covers only `a` and
2581
+ `a` is covered by only `b`, and either `a` has one lower cover
2582
+ or `b` has one upper cover. If no such pair exists, return
2583
+ ``None``.
2584
+
2585
+ This pair gives a trivial non-regular congruence.
2586
+
2587
+ The Hasse diagram is expected to be bounded.
2588
+
2589
+ EXAMPLES::
2590
+
2591
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2592
+ sage: H = HasseDiagram({0: [1, 2], 1: [4], 2: [3], 3: [4]})
2593
+ sage: H._trivial_nonregular_congruence()
2594
+ (2, 3)
2595
+
2596
+ TESTS::
2597
+
2598
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [3]})
2599
+ sage: H._trivial_nonregular_congruence() is None
2600
+ True
2601
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [3], 3: [4]})
2602
+ sage: H._trivial_nonregular_congruence()
2603
+ (3, 4)
2604
+ sage: H = HasseDiagram({0: [1], 1: [2, 3], 2: [4], 3: [4]})
2605
+ sage: H._trivial_nonregular_congruence()
2606
+ (0, 1)
2607
+ sage: H = HasseDiagram({0: [1]})
2608
+ sage: H._trivial_nonregular_congruence() is None
2609
+ True
2610
+ """
2611
+ n = self.order()
2612
+ if n == 2:
2613
+ return None
2614
+ if self.out_degree(0) == 1:
2615
+ return (0, 1)
2616
+ if self.in_degree(n - 1) == 1:
2617
+ return (n - 2, n - 1)
2618
+ for v in range(1, n - 1):
2619
+ if self.in_degree(v) == 1 and self.out_degree(v) == 1:
2620
+ v_ = next(self.neighbor_out_iterator(v))
2621
+ if self.in_degree(v_) == 1 and self.out_degree(v_) == 1:
2622
+ return (v, v_)
2623
+ return None
2624
+
2625
+ def sublattices_iterator(self, elms, min_e):
2626
+ """
2627
+ Return an iterator over sublattices of the Hasse diagram.
2628
+
2629
+ INPUT:
2630
+
2631
+ - ``elms`` -- elements already in sublattice; use set() at start
2632
+ - ``min_e`` -- smallest new element to add for new sublattices
2633
+
2634
+ OUTPUT: list of sublattices as sets of integers
2635
+
2636
+ EXAMPLES::
2637
+
2638
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2639
+ sage: H = HasseDiagram({0: [1, 2], 1:[3], 2:[3]})
2640
+ sage: it = H.sublattices_iterator(set(), 0); it
2641
+ <generator object ...sublattices_iterator at ...>
2642
+ sage: next(it) # needs sage.modules
2643
+ set()
2644
+ sage: next(it) # needs sage.modules
2645
+ {0}
2646
+ """
2647
+ yield elms
2648
+ mt = self.meet_matrix()
2649
+ jn = self.join_matrix()
2650
+ for e in range(min_e, self.cardinality()):
2651
+ if e in elms:
2652
+ continue
2653
+ current_set = set(elms)
2654
+ gens = {e}
2655
+ while gens:
2656
+ g = gens.pop()
2657
+ if g < e and g not in elms:
2658
+ break
2659
+ if g in current_set:
2660
+ continue
2661
+ for x in current_set:
2662
+ gens.add(mt[x, g])
2663
+ gens.add(jn[x, g])
2664
+ current_set.add(g)
2665
+ else:
2666
+ yield from self.sublattices_iterator(current_set, e + 1)
2667
+
2668
+ def maximal_sublattices(self):
2669
+ """
2670
+ Return maximal sublattices of the lattice.
2671
+
2672
+ EXAMPLES::
2673
+
2674
+ sage: L = posets.PentagonPoset() # needs sage.modules
2675
+ sage: ms = L._hasse_diagram.maximal_sublattices() # needs sage.modules
2676
+ sage: sorted(ms, key=sorted) # needs sage.modules
2677
+ [{0, 1, 2, 4}, {0, 1, 3, 4}, {0, 2, 3, 4}]
2678
+ """
2679
+ jn = self.join_matrix()
2680
+ mt = self.meet_matrix()
2681
+
2682
+ def sublattice(elms, e):
2683
+ """
2684
+ Helper function to get sublattice generated by list
2685
+ of elements.
2686
+ """
2687
+ gens_remaining = {e}
2688
+ current_set = set(elms)
2689
+
2690
+ while gens_remaining:
2691
+ g = gens_remaining.pop()
2692
+ if g in current_set:
2693
+ continue
2694
+ for x in current_set:
2695
+ gens_remaining.add(jn[x, g])
2696
+ gens_remaining.add(mt[x, g])
2697
+ current_set.add(g)
2698
+
2699
+ return current_set
2700
+
2701
+ N = self.cardinality()
2702
+ elms = [0]
2703
+ sublats = [{0}]
2704
+ result = []
2705
+ skip = -1
2706
+
2707
+ while True:
2708
+ # First try to append an element
2709
+ found_element_to_append = False
2710
+ e = elms[-1]
2711
+ while e != skip:
2712
+ e += 1
2713
+ if e == N:
2714
+ maybe_found = sublats[-1]
2715
+ if not any(maybe_found.issubset(x) for x in result):
2716
+ result.append(sublats[-1])
2717
+ break
2718
+ if e in sublats[-1]:
2719
+ continue
2720
+ # Let's try to add 'e' and see what happens.
2721
+ sl = sublattice(sublats[-1], e)
2722
+ if len(sl) < N:
2723
+ # Skip this, if it generated a back-reference.
2724
+ new_elms = sl.difference(sublats[-1])
2725
+ if not any(x < e for x in new_elms):
2726
+ found_element_to_append = True
2727
+ break
2728
+ # Now sl is whole lattice, so we continue and try
2729
+ # appending another element.
2730
+
2731
+ if found_element_to_append:
2732
+ elms.append(e)
2733
+ sublats.append(sl)
2734
+ continue
2735
+
2736
+ # Can not append. Try to increment last element.
2737
+ e = elms.pop()
2738
+ sublats.pop()
2739
+
2740
+ last_element_increment = True
2741
+ while True:
2742
+ e += 1
2743
+ if e == N:
2744
+ last_element_increment = False
2745
+ break
2746
+ if e in sublats[-1]:
2747
+ continue
2748
+ sl = sublattice(sublats[-1], e)
2749
+ if len(sl) == N:
2750
+ continue
2751
+
2752
+ new_elms = sl.difference(set(sublats[-1]))
2753
+ if any(x < e for x in new_elms):
2754
+ continue
2755
+
2756
+ elms.append(e)
2757
+ sublats.append(sl)
2758
+ break
2759
+
2760
+ if not last_element_increment:
2761
+ # Can not append nor increment. "Backtracking".
2762
+ skip = elms[-1]
2763
+ if skip == 0:
2764
+ break
2765
+
2766
+ # Special case to handle at last.
2767
+ if self.out_degree(0) == 1:
2768
+ result.append(set(range(1, N)))
2769
+
2770
+ return result
2771
+
2772
+ def frattini_sublattice(self):
2773
+ """
2774
+ Return the list of elements of the Frattini sublattice of the lattice.
2775
+
2776
+ EXAMPLES::
2777
+
2778
+ sage: H = posets.PentagonPoset()._hasse_diagram # needs sage.modules
2779
+ sage: H.frattini_sublattice() # needs sage.modules
2780
+ [0, 4]
2781
+ """
2782
+ # Just a direct computation, no optimization at all.
2783
+ n = self.cardinality()
2784
+ if n == 0 or n == 2:
2785
+ return []
2786
+ if n == 1:
2787
+ return [0]
2788
+ max_sublats = self.maximal_sublattices()
2789
+ return [e for e in range(self.cardinality()) if
2790
+ all(e in ms for ms in max_sublats)]
2791
+
2792
+ def kappa_dual(self, a):
2793
+ r"""
2794
+ Return the minimum element smaller than the element covering
2795
+ ``a`` but not smaller than ``a``.
2796
+
2797
+ Define `\kappa^*(a)` as the minimum element of
2798
+ `(\downarrow a_*) \setminus (\downarrow a)`, where `a_*` is the element
2799
+ covering `a`. It is always a join-irreducible element, if it exists.
2800
+
2801
+ .. NOTE::
2802
+
2803
+ Element ``a`` is expected to be meet-irreducible, and
2804
+ this is *not* checked.
2805
+
2806
+ INPUT:
2807
+
2808
+ - ``a`` -- a join-irreducible element of the lattice
2809
+
2810
+ OUTPUT:
2811
+
2812
+ The element `\kappa^*(a)` or ``None`` if there
2813
+ is not a unique smallest element with given constraints.
2814
+
2815
+ EXAMPLES::
2816
+
2817
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2818
+ sage: H = HasseDiagram({0: [1, 2], 1: [3, 4], 2: [4, 5], 3: [6], 4: [6], 5: [6]})
2819
+ sage: H.kappa_dual(3)
2820
+ 2
2821
+ sage: H.kappa_dual(4) is None
2822
+ True
2823
+
2824
+ TESTS::
2825
+
2826
+ sage: H = HasseDiagram({0: [1]})
2827
+ sage: H.kappa_dual(0)
2828
+ 1
2829
+ """
2830
+ uc = next(self.neighbor_out_iterator(a))
2831
+ if self.in_degree(uc) == 1:
2832
+ return uc
2833
+ lt_a = set(self.depth_first_search(a, neighbors=self.neighbor_in_iterator))
2834
+ tmp = set(self.depth_first_search(uc, neighbors=lambda v: [v_ for v_ in self.neighbor_in_iterator(v) if v_ not in lt_a]))
2835
+ result = None
2836
+ for e in tmp:
2837
+ if all(x not in tmp for x in self.neighbor_in_iterator(e)):
2838
+ if result:
2839
+ return None
2840
+ result = e
2841
+ return result
2842
+
2843
+ def skeleton(self):
2844
+ """
2845
+ Return the skeleton of the lattice.
2846
+
2847
+ The lattice is expected to be pseudocomplemented and non-empty.
2848
+
2849
+ The skeleton of the lattice is the subposet induced by
2850
+ those elements that are the pseudocomplement to at least one
2851
+ element.
2852
+
2853
+ OUTPUT:
2854
+
2855
+ List of elements such that the subposet induced by them is
2856
+ the skeleton of the lattice.
2857
+
2858
+ EXAMPLES::
2859
+
2860
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2861
+ sage: H = HasseDiagram({0: [1, 2], 1: [3, 4], 2: [4],
2862
+ ....: 3: [5], 4: [5]})
2863
+ sage: H.skeleton() # needs sage.modules
2864
+ [5, 2, 0, 3]
2865
+ """
2866
+ p_atoms = []
2867
+ for atom in self.neighbor_out_iterator(0):
2868
+ p_atom = self.pseudocomplement(atom)
2869
+ if p_atom is None:
2870
+ raise ValueError("lattice is not pseudocomplemented")
2871
+ p_atoms.append(p_atom)
2872
+ n = len(p_atoms)
2873
+ mt = self.meet_matrix()
2874
+ pos = [0] * n
2875
+ meets = [self.order() - 1] * n
2876
+ result = [self.order() - 1]
2877
+ i = 0
2878
+
2879
+ while i >= 0:
2880
+ new_meet = mt[meets[i - 1], p_atoms[pos[i]]]
2881
+ result.append(new_meet)
2882
+ if pos[i] == n - 1:
2883
+ i -= 1
2884
+ pos[i] += 1
2885
+ else:
2886
+ meets[i] = new_meet
2887
+ pos[i + 1] = pos[i] + 1
2888
+ i += 1
2889
+
2890
+ return result
2891
+
2892
+ def is_convex_subset(self, S) -> bool:
2893
+ r"""
2894
+ Return ``True`` if `S` is a convex subset of the poset,
2895
+ and ``False`` otherwise.
2896
+
2897
+ A subset `S` is *convex* in the poset if `b \in S` whenever
2898
+ `a, c \in S` and `a \le b \le c`.
2899
+
2900
+ EXAMPLES::
2901
+
2902
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2903
+ sage: B3 = HasseDiagram({0: [1, 2, 4], 1: [3, 5], 2: [3, 6],
2904
+ ....: 3: [7], 4: [5, 6], 5: [7], 6: [7]})
2905
+ sage: B3.is_convex_subset([1, 3, 5, 4]) # Also connected
2906
+ True
2907
+ sage: B3.is_convex_subset([1, 3, 4]) # Not connected
2908
+ True
2909
+
2910
+ sage: B3.is_convex_subset([0, 1, 2, 3, 6]) # No, 0 < 4 < 6
2911
+ False
2912
+ sage: B3.is_convex_subset([0, 1, 2, 7]) # No, 1 < 3 < 7.
2913
+ False
2914
+
2915
+ TESTS::
2916
+
2917
+ sage: B3.is_convex_subset([])
2918
+ True
2919
+ sage: B3.is_convex_subset([6])
2920
+ True
2921
+ """
2922
+ if not S: # S is empty set
2923
+ return True
2924
+ s_max = max(S)
2925
+ ok = set() # Already checked elements not less than any element is S.
2926
+
2927
+ for a in S:
2928
+ for b in self.neighbor_out_iterator(a):
2929
+ if b >= s_max or b in S:
2930
+ continue
2931
+ # Now b not in S, b > a and a in S.
2932
+
2933
+ def neighbors(v_):
2934
+ return [v for v in self.neighbor_out_iterator(v_)
2935
+ if v <= s_max and v not in ok]
2936
+ for c in self.depth_first_search(b, neighbors=neighbors):
2937
+ if c in S: # Now c in S, b not in S, a in S, a < b < c.
2938
+ return False
2939
+ ok.add(c) # Do not re-check this for being our b.
2940
+
2941
+ return True
2942
+
2943
+ def neutral_elements(self) -> set:
2944
+ """
2945
+ Return the list of neutral elements of the lattice.
2946
+
2947
+ An element `a` in a lattice is neutral if the sublattice
2948
+ generated by `a`, `x` and `y` is distributive for every
2949
+ `x`, `y` in the lattice.
2950
+
2951
+ EXAMPLES::
2952
+
2953
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
2954
+ sage: H = HasseDiagram({0: [1, 2], 1: [4], 2: [3], 3: [4, 5],
2955
+ ....: 4: [6], 5: [6]})
2956
+ sage: sorted(H.neutral_elements()) # needs sage.modules
2957
+ [0, 4, 6]
2958
+
2959
+ ALGORITHM:
2960
+
2961
+ Basically we just check the distributivity against all element
2962
+ pairs `x, y` to see if element `a` is neutral or not.
2963
+
2964
+ If we found that `a, x, y` is not a distributive triple, we add
2965
+ all three to list of non-neutral elements. If we found `a` to
2966
+ be neutral, we add it to list of neutral elements. When testing
2967
+ we skip already found neutral elements, as they can't be our `x`
2968
+ or `y`.
2969
+
2970
+ We skip `a, x, y` as trivial if it is a chain. We do that by
2971
+ letting `x` to be a non-comparable to `a`; `y` can be any element.
2972
+
2973
+ We first try to found `x` and `y` from elements not yet tested,
2974
+ so that we could get three birds with one stone.
2975
+
2976
+ And last, the top and bottom elements are always neutral and
2977
+ need not be tested.
2978
+ """
2979
+ n = self.order()
2980
+ if n < 5:
2981
+ return set(range(n))
2982
+
2983
+ todo = set(range(1, n - 1))
2984
+ neutrals = {0, n - 1}
2985
+ notneutrals = set()
2986
+ all_elements = set(range(n))
2987
+
2988
+ mt = self.meet_matrix()
2989
+ jn = self.join_matrix()
2990
+
2991
+ def is_neutral(a) -> bool:
2992
+ noncomp = all_elements.difference(self.depth_first_search(a))
2993
+ noncomp.difference_update(self.depth_first_search(a, neighbors=self.neighbor_in_iterator))
2994
+
2995
+ for x in noncomp.intersection(todo):
2996
+ meet_ax = mt[a, x]
2997
+ join_ax = jn[a, x]
2998
+ for y in todo:
2999
+ if (mt[mt[join_ax, jn[a, y]], jn[x, y]] !=
3000
+ jn[jn[meet_ax, mt[a, y]], mt[x, y]]):
3001
+ notneutrals.add(x)
3002
+ notneutrals.add(y)
3003
+ return False
3004
+ for y in notneutrals:
3005
+ if (mt[mt[join_ax, jn[a, y]], jn[x, y]] !=
3006
+ jn[jn[meet_ax, mt[a, y]], mt[x, y]]):
3007
+ notneutrals.add(x)
3008
+ return False
3009
+ for x in noncomp.difference(todo):
3010
+ meet_ax = mt[a, x]
3011
+ join_ax = jn[a, x]
3012
+ for y in todo:
3013
+ if (mt[mt[join_ax, jn[a, y]], jn[x, y]] !=
3014
+ jn[jn[meet_ax, mt[a, y]], mt[x, y]]):
3015
+ notneutrals.add(y)
3016
+ return False
3017
+ for y in notneutrals:
3018
+ if (mt[mt[join_ax, jn[a, y]], jn[x, y]] !=
3019
+ jn[jn[meet_ax, mt[a, y]], mt[x, y]]):
3020
+ return False
3021
+ return True
3022
+
3023
+ while todo:
3024
+ e = todo.pop()
3025
+ if is_neutral(e):
3026
+ neutrals.add(e)
3027
+ else:
3028
+ notneutrals.add(e)
3029
+
3030
+ return neutrals
3031
+
3032
+ def kappa(self, a):
3033
+ r"""
3034
+ Return the maximum element greater than the element covered
3035
+ by ``a`` but not greater than ``a``.
3036
+
3037
+ Define `\kappa(a)` as the maximum element of
3038
+ `(\uparrow a_*) \setminus (\uparrow a)`, where `a_*` is the element
3039
+ covered by `a`. It is always a meet-irreducible element, if it exists.
3040
+
3041
+ .. NOTE::
3042
+
3043
+ Element ``a`` is expected to be join-irreducible, and
3044
+ this is *not* checked.
3045
+
3046
+ INPUT:
3047
+
3048
+ - ``a`` -- a join-irreducible element of the lattice
3049
+
3050
+ OUTPUT:
3051
+
3052
+ The element `\kappa(a)` or ``None`` if there
3053
+ is not a unique greatest element with given constraints.
3054
+
3055
+ EXAMPLES::
3056
+
3057
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3058
+ sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4, 5], 3: [5], 4: [6], 5: [6]})
3059
+ sage: H.kappa(1)
3060
+ 5
3061
+ sage: H.kappa(2) is None
3062
+ True
3063
+
3064
+ TESTS::
3065
+
3066
+ sage: H = HasseDiagram({0: [1]})
3067
+ sage: H.kappa(1)
3068
+ 0
3069
+ """
3070
+ lc = next(self.neighbor_in_iterator(a))
3071
+ if self.out_degree(lc) == 1:
3072
+ return lc
3073
+ gt_a = set(self.depth_first_search(a))
3074
+ tmp = set(self.depth_first_search(lc, neighbors=lambda v: [v_ for v_ in self.neighbor_out_iterator(v) if v_ not in gt_a]))
3075
+ result = None
3076
+ for e in tmp:
3077
+ if all(x not in tmp for x in self.neighbor_out_iterator(e)):
3078
+ if result:
3079
+ return None
3080
+ result = e
3081
+ return result
3082
+
3083
+ def atoms_of_congruence_lattice(self) -> list:
3084
+ r"""
3085
+ Return atoms of the congruence lattice.
3086
+
3087
+ In other words, return "minimal non-trivial" congruences:
3088
+ A congruence is minimal if the only finer (as a partition
3089
+ of set of elements) congruence is the trivial congruence
3090
+ where every block contains only one element.
3091
+
3092
+ .. SEEALSO:: :meth:`congruence`
3093
+
3094
+ OUTPUT:
3095
+
3096
+ List of congruences, every congruence as
3097
+ :class:`sage.combinat.set_partition.SetPartition`
3098
+
3099
+ EXAMPLES::
3100
+
3101
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3102
+ sage: N5 = HasseDiagram({0: [1, 2], 1: [4], 2: [3], 3:[4]})
3103
+ sage: N5.atoms_of_congruence_lattice() # needs sage.combinat sage.modules
3104
+ [{{0}, {1}, {2, 3}, {4}}]
3105
+ sage: Hex = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [5], 4: [5]})
3106
+ sage: Hex.atoms_of_congruence_lattice() # needs sage.combinat sage.modules
3107
+ [{{0}, {1}, {2, 4}, {3}, {5}}, {{0}, {1, 3}, {2}, {4}, {5}}]
3108
+
3109
+ ALGORITHM:
3110
+
3111
+ Every atom is a join-irreducible. Every join-irreducible of
3112
+ `\mathrm{Con}(L)` is a principal congruence generated by a
3113
+ meet-irreducible element and the only element covering it (and also
3114
+ by a join-irreducible element and the only element covered by it).
3115
+ Hence we check those principal congruences to find the minimal ones.
3116
+ """
3117
+ # Note: A lattice L if subdirectly reducible (i.e. is a sublattice
3118
+ # of a Cartesian product of two smaller lattices) iff Con(L) has
3119
+ # at least two atoms. That's were this is used for.
3120
+
3121
+ from sage.combinat.set_partition import SetPartitions
3122
+
3123
+ # Get smaller set, meet- or join-irreducibles
3124
+ join_irreducibles = [v for v in self if self.in_degree(v) == 1]
3125
+ meet_irreducibles = [v for v in self if self.out_degree(v) == 1]
3126
+ if len(join_irreducibles) < len(meet_irreducibles):
3127
+ irr = [(v, next(self.neighbor_in_iterator(v))) for v in join_irreducibles]
3128
+ else:
3129
+ irr = [(next(self.neighbor_out_iterator(v)), v) for v in meet_irreducibles]
3130
+
3131
+ S = SetPartitions(range(self.order()))
3132
+ min_congruences = []
3133
+ already_tried = []
3134
+
3135
+ while irr:
3136
+ next_pair = irr.pop()
3137
+ cong = self.congruence([next_pair], stop_pairs=already_tried)
3138
+ already_tried.append(next_pair)
3139
+ if cong is not None:
3140
+ cong = S(cong)
3141
+ min_congruences = [c for c in min_congruences if c != cong and not S.is_less_than(cong, c)]
3142
+ if not any(S.is_less_than(c, cong) for c in min_congruences):
3143
+ min_congruences.append(cong)
3144
+
3145
+ return min_congruences
3146
+
3147
+ def congruence(self, parts, start=None, stop_pairs=None):
3148
+ """
3149
+ Return the congruence ``start`` "extended" by ``parts``.
3150
+
3151
+ ``start`` is assumed to be a valid congruence of the lattice,
3152
+ and this is *not* checked.
3153
+
3154
+ INPUT:
3155
+
3156
+ - ``parts`` -- list of lists; congruences to add
3157
+ - ``start`` -- a disjoint set; already computed congruence (or ``None``)
3158
+ - ``stop_pairs`` -- list of pairs; list of pairs for stopping computation
3159
+
3160
+ OUTPUT:
3161
+
3162
+ ``None``, if the congruence generated by ``start`` and ``parts``
3163
+ together contains a block that has elements `a, b` so that ``(a, b)``
3164
+ is in the list ``stop_pairs``. Otherwise the least congruence that
3165
+ contains a block whose subset is `p` for every `p` in ``parts`` or
3166
+ ``start``, given as :class:`sage.sets.disjoint_set.DisjointSet_class`.
3167
+
3168
+ ALGORITHM:
3169
+
3170
+ Use the quadrilateral argument from page 120 of [Dav1997]_.
3171
+
3172
+ Basically we take one block from todo-list, search quadrilateral
3173
+ blocks up and down against the block, and then complete them to
3174
+ closed intervals and add to todo-list.
3175
+
3176
+ EXAMPLES::
3177
+
3178
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3179
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]})
3180
+ sage: cong = H.congruence([[0, 1]]); cong # needs sage.modules
3181
+ {{0, 1, 3}, {2, 4}}
3182
+ sage: H.congruence([[0, 2]], start=cong) # needs sage.modules
3183
+ {{0, 1, 2, 3, 4}}
3184
+
3185
+ sage: H.congruence([[0, 1]], stop_pairs=[(1, 3)]) is None # needs sage.modules
3186
+ True
3187
+
3188
+ TESTS::
3189
+
3190
+ sage: H = HasseDiagram('HT@O?GO?OE?G@??')
3191
+ sage: H.congruence([[0, 1]]).number_of_subsets() # needs sage.modules
3192
+ 1
3193
+ sage: H = HasseDiagram('HW_oC?@@O@?O@??')
3194
+ sage: H.congruence([[0, 1]]).number_of_subsets() # needs sage.modules
3195
+ 1
3196
+
3197
+ Check :issue:`21861`::
3198
+
3199
+ sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]})
3200
+ sage: tmp = H.congruence([[1, 3]]) # needs sage.modules
3201
+ sage: tmp.number_of_subsets() # needs sage.modules
3202
+ 4
3203
+ sage: H.congruence([[0, 1]], start=tmp).number_of_subsets() # needs sage.modules
3204
+ 2
3205
+ sage: tmp.number_of_subsets() # needs sage.modules
3206
+ 4
3207
+ """
3208
+ from sage.sets.disjoint_set import DisjointSet
3209
+ from copy import copy
3210
+
3211
+ if stop_pairs is None:
3212
+ stop_pairs = []
3213
+
3214
+ n = self.order()
3215
+ mt = self.meet_matrix()
3216
+ jn = self.join_matrix()
3217
+
3218
+ def fill_to_interval(S):
3219
+ """
3220
+ Return the smallest interval containing elements in the set S.
3221
+ """
3222
+ m = n - 1
3223
+ for e in S:
3224
+ m = mt[m, e]
3225
+ j = 0
3226
+ for e in S:
3227
+ j = jn[j, e]
3228
+ return self.interval(m, j)
3229
+
3230
+ cong = copy(start) if start else DisjointSet(n)
3231
+ t = -1
3232
+
3233
+ while t != cong.number_of_subsets():
3234
+ for part in parts:
3235
+ if part: # Skip empty parts
3236
+ c = part[0]
3237
+ for e in fill_to_interval(part):
3238
+ cong.union(e, c)
3239
+ t = cong.number_of_subsets()
3240
+
3241
+ # Following is needed for cases like
3242
+ # posets.BooleanLattice(3).congruence([(0,1), (0,2), (0,4)])
3243
+ for c in list(cong):
3244
+ r = c[0]
3245
+ for v in fill_to_interval(c):
3246
+ cong.union(r, v)
3247
+
3248
+ todo = {cong.find(e) for part in parts for e in part}
3249
+
3250
+ while todo:
3251
+
3252
+ # First check if we should stop now.
3253
+ for a, b in stop_pairs:
3254
+ if cong.find(a) == cong.find(b):
3255
+ return None
3256
+
3257
+ # We take one block and try to find as big interval
3258
+ # as possible to unify as a new block by the quadrilateral
3259
+ # argument.
3260
+ block = sorted(cong.root_to_elements_dict()[cong.find(todo.pop())])
3261
+
3262
+ b = block[-1]
3263
+ for a in block: # Quadrilateral up
3264
+ for c in self.neighbor_out_iterator(a):
3265
+ if c not in block:
3266
+ d = jn[c, b]
3267
+ if cong.find(d) != cong.find(c):
3268
+ break
3269
+ else:
3270
+ continue
3271
+ break
3272
+
3273
+ else: # Not found, so...
3274
+ a = block[0]
3275
+ for b in reversed(block): # ...quadrilateral down
3276
+ for d in self.neighbor_in_iterator(b):
3277
+ if d not in block:
3278
+ c = mt[d, a]
3279
+ if cong.find(c) != cong.find(d):
3280
+ break
3281
+ else:
3282
+ continue
3283
+ break
3284
+ else: # Nothing found
3285
+ continue
3286
+
3287
+ # Something was found, so we put this block back to todo
3288
+ # together with just found new block.
3289
+ todo.add(a)
3290
+ todo.add(c)
3291
+
3292
+ # Now the interval [c, d] will be of the same block.
3293
+ # It may "crab" other blocks within, and that can be
3294
+ # recursive process. In particular it may also combine to
3295
+ # [a, b] block we just used.
3296
+ while c is not None:
3297
+ newblock = cong.find(c)
3298
+ for i in self.interval(c, d):
3299
+ cong.union(newblock, i)
3300
+ C = cong.root_to_elements_dict()[cong.find(newblock)]
3301
+ mins = [i for i in C if all(i_ not in C for i_ in self.neighbor_in_iterator(i))]
3302
+ maxs = [i for i in C if all(i_ not in C for i_ in self.neighbor_out_iterator(i))]
3303
+ c = None # To stop loop, if this is not changed below.
3304
+ if len(mins) > 1 or len(maxs) > 1:
3305
+ c = n - 1
3306
+ for m in mins:
3307
+ c = mt[c, m]
3308
+ d = 0
3309
+ for m in maxs:
3310
+ d = jn[d, m]
3311
+
3312
+ # This removes duplicates from todo.
3313
+ todo = {cong.find(x) for x in todo}
3314
+
3315
+ return cong
3316
+
3317
+ def find_nontrivial_congruence(self):
3318
+ r"""
3319
+ Return a pair that generates non-trivial congruence or
3320
+ ``None`` if there is not any.
3321
+
3322
+ EXAMPLES::
3323
+
3324
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3325
+ sage: H = HasseDiagram({0: [1, 2], 1: [5], 2: [3, 4], 3: [5], 4: [5]})
3326
+ sage: H.find_nontrivial_congruence() # needs sage.modules
3327
+ {{0, 1}, {2, 3, 4, 5}}
3328
+
3329
+ sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4], 3: [4]})
3330
+ sage: H.find_nontrivial_congruence() is None # needs sage.modules
3331
+ True
3332
+
3333
+ ALGORITHM:
3334
+
3335
+ See https://www.math.hawaii.edu/~ralph/Preprints/conlat.pdf:
3336
+
3337
+ If `\Theta` is a join irreducible element of a `\mathrm{Con}(L)`,
3338
+ then there is at least one join-irreducible `j` and one
3339
+ meet-irreducible `m` such that `\Theta` is both the principal
3340
+ congruence generated by `(j^*, j)`, where `j^*` is the unique
3341
+ lower cover of `j`, and the principal congruence generated by
3342
+ `(m, m^*)`, where `m^*` is the unique upper cover of `m`.
3343
+
3344
+ So, we only check join irreducibles or meet irreducibles,
3345
+ whichever is a smaller set. To optimize more we stop computation
3346
+ whenever it finds a pair that we know to generate one-element
3347
+ congruence.
3348
+ """
3349
+ join_irreducibles = [v for v in self if self.in_degree(v) == 1]
3350
+ meet_irreducibles = [v for v in self if self.out_degree(v) == 1]
3351
+ if len(join_irreducibles) < len(meet_irreducibles):
3352
+ irr = [(v, next(self.neighbor_in_iterator(v))) for v in join_irreducibles]
3353
+ else:
3354
+ irr = [(next(self.neighbor_out_iterator(v)), v) for v in meet_irreducibles]
3355
+ irr.sort(key=lambda x: x[0] - x[1])
3356
+ tried = []
3357
+ for pair in irr:
3358
+ cong = self.congruence([pair], stop_pairs=tried)
3359
+ if cong is not None and cong.number_of_subsets() > 1:
3360
+ return cong
3361
+ tried.append(pair)
3362
+ return None
3363
+
3364
+ def principal_congruences_poset(self):
3365
+ r"""
3366
+ Return the poset of join-irreducibles of the congruence lattice.
3367
+
3368
+ OUTPUT:
3369
+
3370
+ A pair `(P, D)` where `P` is a poset and `D` is a dictionary.
3371
+
3372
+ Elements of `P` are pairs `(x, y)` such that `x` is an element
3373
+ of the lattice and `y` is an element covering it. In the poset
3374
+ `(a, b)` is less than `(c, d)` iff the principal congruence
3375
+ generated by `(a, b)` is refinement of the principal congruence
3376
+ generated by `(c, d)`.
3377
+
3378
+ `D` is a dictionary from pairs `(x, y)` to the congruence
3379
+ (given as DisjointSet) generated by the pair.
3380
+
3381
+ EXAMPLES::
3382
+
3383
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3384
+ sage: N5 = HasseDiagram({0: [1, 2], 1: [4], 2: [3], 3: [4]})
3385
+ sage: P, D = N5.principal_congruences_poset() # needs sage.combinat sage.modules
3386
+ sage: P # needs sage.combinat sage.modules
3387
+ Finite poset containing 3 elements
3388
+ sage: P.bottom() # needs sage.combinat sage.modules
3389
+ (2, 3)
3390
+ sage: D[(2, 3)] # needs sage.combinat sage.modules
3391
+ {{0}, {1}, {2, 3}, {4}}
3392
+ """
3393
+ from sage.combinat.set_partition import SetPartition, SetPartitions
3394
+ from sage.combinat.posets.posets import Poset
3395
+
3396
+ n = self.order()
3397
+
3398
+ # Select smaller set, meet- or join-irreducibles
3399
+ if self.in_degree_sequence().count(1) > self.out_degree_sequence().count(1):
3400
+ irr = [(e, next(self.neighbor_out_iterator(e))) for e in range(n) if self.out_degree(e) == 1]
3401
+ else:
3402
+ irr = [(next(self.neighbor_in_iterator(e)), e) for e in range(n) if self.in_degree(e) == 1]
3403
+
3404
+ D = {}
3405
+ P = {}
3406
+ uniq_congs = set()
3407
+ for ab in irr:
3408
+ cong = self.congruence([ab])
3409
+ cong_ = SetPartition(cong)
3410
+ if cong_ not in uniq_congs:
3411
+ uniq_congs.add(cong_)
3412
+ D[ab] = cong
3413
+ P[ab] = cong_
3414
+
3415
+ # TODO: Make a function that creates the poset from a set
3416
+ # by comparison function with minimal number of comparisons.
3417
+
3418
+ T = SetPartitions(n)
3419
+ P = DiGraph([D, lambda a, b: T.is_less_than(P[a], P[b])])
3420
+ return (Poset(P), D)
3421
+
3422
+ def congruences_iterator(self):
3423
+ """
3424
+ Return an iterator over all congruences of the lattice.
3425
+
3426
+ EXAMPLES::
3427
+
3428
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3429
+ sage: H = HasseDiagram('GY@OQ?OW@?O?')
3430
+ sage: it = H.congruences_iterator(); it
3431
+ <generator object ...>
3432
+ sage: sorted([cong.number_of_subsets() for cong in it]) # needs sage.combinat sage.modules
3433
+ [1, 2, 2, 2, 4, 4, 4, 8]
3434
+ """
3435
+ from sage.sets.disjoint_set import DisjointSet
3436
+
3437
+ P, congs = self.principal_congruences_poset()
3438
+ for a in P.antichains_iterator():
3439
+ achain = tuple(a)
3440
+ n = len(achain)
3441
+ if n == 0:
3442
+ yield DisjointSet(self.order())
3443
+ if n == 1:
3444
+ # We have congs[(x,y)], but we want congs[((x,y))].
3445
+ congs[achain] = congs[a[0]]
3446
+ yield congs[achain[0]]
3447
+ if n > 1:
3448
+ c = congs[achain[:-1]]
3449
+ c = self.congruence([achain[-1]], start=c)
3450
+ yield c
3451
+ congs[achain] = c
3452
+
3453
+ def is_congruence_normal(self) -> bool:
3454
+ """
3455
+ Return ``True`` if the lattice can be constructed from the one-element
3456
+ lattice with Day doubling constructions of convex subsets.
3457
+
3458
+ Subsets to double does not need to be lower nor upper pseudo-intervals.
3459
+ On the other hand they must be convex, i.e. doubling a non-convex but
3460
+ municipal subset will give a lattice that returns ``False`` from
3461
+ this function.
3462
+
3463
+ EXAMPLES::
3464
+
3465
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3466
+ sage: H = HasseDiagram('IX?Q@?AG?OG?W?O@??')
3467
+ sage: H.is_congruence_normal() # needs sage.combinat sage.modules
3468
+ True
3469
+
3470
+ The 5-element diamond is the smallest non-example::
3471
+
3472
+ sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4], 3: [4]})
3473
+ sage: H.is_congruence_normal() # needs sage.combinat sage.modules
3474
+ False
3475
+
3476
+ This is done by doubling a non-convex subset::
3477
+
3478
+ sage: H = HasseDiagram('OQC?a?@CO?G_C@?GA?O??_??@?BO?A_?G??C??_?@???')
3479
+ sage: H.is_congruence_normal() # needs sage.combinat sage.modules
3480
+ False
3481
+
3482
+ TESTS::
3483
+
3484
+ sage: HasseDiagram().is_congruence_normal() # needs sage.combinat sage.modules
3485
+ True
3486
+ sage: HasseDiagram({0: []}).is_congruence_normal() # needs sage.combinat sage.modules
3487
+ True
3488
+
3489
+ ALGORITHM:
3490
+
3491
+ See http://www.math.hawaii.edu/~jb/inflation.pdf
3492
+ """
3493
+ from sage.combinat.set_partition import SetPartition
3494
+
3495
+ n = self.order()
3496
+ congs_ji: dict[SetPartition, list] = {}
3497
+
3498
+ for ji in range(n):
3499
+ if self.in_degree(ji) == 1:
3500
+ cong = SetPartition(self.congruence([[ji, next(self.neighbor_in_iterator(ji))]])) # type:ignore
3501
+ if cong not in congs_ji:
3502
+ congs_ji[cong] = []
3503
+ congs_ji[cong].append(ji)
3504
+
3505
+ for mi in range(n):
3506
+ if self.out_degree(mi) == 1:
3507
+ cong = SetPartition(self.congruence([[mi, next(self.neighbor_out_iterator(mi))]])) # type:ignore
3508
+ if any(self.is_lequal(ji, mi) for ji in congs_ji[cong]):
3509
+ return False
3510
+
3511
+ return True
3512
+
3513
+ @staticmethod
3514
+ def _glue_spectra(a_spec, b_spec, orientation):
3515
+ r"""
3516
+ Return the `a`-spectrum of a poset by merging ``a_spec`` and ``b_spec``.
3517
+
3518
+ ``a_spec`` and ``b_spec`` are the `a`-spectrum and `b`-spectrum of two different
3519
+ posets (see :meth:`atkinson` for the definition of `a`-spectrum).
3520
+
3521
+ The orientation determines whether `a < b` or `b < a` in the combined poset.
3522
+
3523
+ This is a helper method for :meth:`atkinson`.
3524
+
3525
+ INPUT:
3526
+
3527
+ - ``a_spec`` -- list; the `a`-spectrum of a poset `P`
3528
+
3529
+ - ``b_spec`` -- list; the `b`-spectrum of a poset `Q`
3530
+
3531
+ - ``orientation`` -- boolean; ``True`` if `a < b`, ``False`` otherwise
3532
+
3533
+ OUTPUT:
3534
+
3535
+ The `a`-spectrum (or `b`-spectrum, depending on orientation),
3536
+ returned as a list, of the poset which is a disjoint union
3537
+ of `P` and `Q`, together with the additional
3538
+ covering relation `a < b`.
3539
+
3540
+ EXAMPLES::
3541
+
3542
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3543
+ sage: Pdata = [0, 1, 2, 0]
3544
+ sage: Qdata = [1, 1, 0]
3545
+ sage: HasseDiagram._glue_spectra(Pdata, Qdata, True)
3546
+ [0, 20, 28, 18, 0, 0, 0]
3547
+
3548
+ sage: Pdata = [0, 0, 2]
3549
+ sage: Qdata = [0, 1]
3550
+ sage: HasseDiagram._glue_spectra(Pdata, Qdata, False)
3551
+ [0, 0, 0, 0, 8]
3552
+ """
3553
+ new_a_spec = []
3554
+
3555
+ if not orientation:
3556
+ a_spec, b_spec = b_spec, a_spec
3557
+
3558
+ p = len(a_spec)
3559
+ q = len(b_spec)
3560
+
3561
+ for r in range(1, p + q + 1):
3562
+ new_a_spec.append(0)
3563
+ for i in range(max(1, r - q), min(p, r) + 1):
3564
+ k_val = binomial(r - 1, i - 1) * binomial(p + q - r, p - i)
3565
+ if orientation:
3566
+ inner_sum = sum(b_spec[j - 1] for j in range(r - i + 1, len(b_spec) + 1))
3567
+ else:
3568
+ inner_sum = sum(b_spec[j - 1] for j in range(1, r - i + 1))
3569
+ new_a_spec[-1] = new_a_spec[-1] + (a_spec[i - 1] * k_val * inner_sum)
3570
+
3571
+ return new_a_spec
3572
+
3573
+ def _split(self, a, b):
3574
+ r"""
3575
+ Return the two connected components obtained by deleting the covering
3576
+ relation `a < b` from a Hasse diagram that is a tree.
3577
+
3578
+ This is a helper method for :meth:`FinitePoset.atkinson`.
3579
+
3580
+ INPUT:
3581
+
3582
+ - ``a`` -- an element of the poset
3583
+ - ``b`` -- an element of the poset which covers ``a``
3584
+
3585
+ OUTPUT:
3586
+
3587
+ A list containing two posets which are the connected components
3588
+ of this poset after deleting the covering relation `a < b`.
3589
+
3590
+ EXAMPLES::
3591
+
3592
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3593
+ sage: H = HasseDiagram({0: [1, 2], 1: [], 2: []})
3594
+ sage: H._split(0, 1)
3595
+ [Hasse diagram of a poset containing 2 elements, Hasse diagram of a poset containing 1 elements]
3596
+
3597
+ sage: H = HasseDiagram({0: [1], 1: [2], 2: [3], 3: [4], 4: []})
3598
+ sage: H._split(1, 2)
3599
+ [Hasse diagram of a poset containing 2 elements, Hasse diagram of a poset containing 3 elements]
3600
+
3601
+ TESTS::
3602
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3603
+ sage: H = HasseDiagram({0: [1,2,3], 1: [4, 5], 2: [4, 6], 3: [5, 6], 4: [7], 5: [7], 6: [7], 7: []})
3604
+ sage: H._split(0, 1)
3605
+ Traceback (most recent call last):
3606
+ ...
3607
+ ValueError: wrong number of connected components after the covering relation is deleted
3608
+
3609
+ sage: H = HasseDiagram({0: [1], 1: [], 2: []})
3610
+ sage: H._split(0, 1)
3611
+ Traceback (most recent call last):
3612
+ ...
3613
+ ValueError: wrong number of connected components after the covering relation is deleted
3614
+ """
3615
+ split_hasse = self.copy(self)
3616
+ split_hasse.delete_edge(a, b)
3617
+ components = split_hasse.connected_components_subgraphs()
3618
+ if not len(components) == 2:
3619
+ raise ValueError("wrong number of connected components after the covering relation is deleted")
3620
+
3621
+ c1, c2 = components
3622
+ if a in c2:
3623
+ c1, c2 = c2, c1
3624
+
3625
+ return [c1, c2]
3626
+
3627
+ def _spectrum_of_tree(self, a):
3628
+ r"""
3629
+ Return the `a`-spectrum of a poset whose underlying graph is a tree.
3630
+
3631
+ This is a helper method for :meth:`FinitePoset.atkinson`.
3632
+
3633
+ INPUT:
3634
+
3635
+ - ``a`` -- an element of the poset
3636
+
3637
+ OUTPUT:
3638
+
3639
+ The `a`-spectrum of this poset, returned as a list.
3640
+
3641
+ EXAMPLES::
3642
+
3643
+ sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
3644
+ sage: H = HasseDiagram({0: [2], 1: [2], 2: [3, 4], 3: [], 4: []})
3645
+ sage: H._spectrum_of_tree(0)
3646
+ [2, 2, 0, 0, 0]
3647
+
3648
+ sage: H = HasseDiagram({0: [2], 1: [2], 2: [3, 4], 3: [], 4: []})
3649
+ sage: H._spectrum_of_tree(2)
3650
+ [0, 0, 4, 0, 0]
3651
+
3652
+ sage: H = HasseDiagram({0: [2], 1: [2], 2: [3, 4], 3: [], 4: []})
3653
+ sage: H._spectrum_of_tree(3)
3654
+ [0, 0, 0, 2, 2]
3655
+ """
3656
+ upper_covers = self.neighbors_out(a)
3657
+ lower_covers = self.neighbors_in(a)
3658
+ if not upper_covers and not lower_covers:
3659
+ return [1]
3660
+ if upper_covers:
3661
+ b = upper_covers[0]
3662
+ orientation = True
3663
+ else:
3664
+ (a, b) = (lower_covers[0], a)
3665
+ orientation = False
3666
+ P, Q = self._split(a, b)
3667
+ a_spec = P._spectrum_of_tree(a)
3668
+ b_spec = Q._spectrum_of_tree(b)
3669
+ return HasseDiagram._glue_spectra(a_spec, b_spec, orientation)
3670
+
3671
+
3672
+ __doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(HasseDiagram))