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