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