Coverage for local/lib/python2.7/site-packages/sage/combinat/posets/hasse_diagram.py : 87%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# -*- coding: utf-8 -*- r""" Hasse diagrams of posets
{INDEX_OF_FUNCTIONS}
"""
#***************************************************************************** # Copyright (C) 2008 Peter Jipsen <jipsen@chapman.edu> # Copyright (C) 2008 Franco Saliola <saliola@gmail.com> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** from __future__ import print_function
from six.moves import range
from sage.graphs.digraph import DiGraph from sage.matrix.constructor import matrix from sage.rings.integer_ring import ZZ from sage.misc.lazy_attribute import lazy_attribute from sage.misc.cachefunc import cached_method from sage.misc.superseded import deprecated_function_alias
class LatticeError(ValueError): """ Helper exception class to forward elements without meet or join to upper level, so that the user will see "No meet for a and b" instead of "No meet for 1 and 2". """
def __init__(self, fail, x, y): """ Initialize the exception.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import LatticeError sage: error = LatticeError('join', 3, 8) sage: error.x 3 """
def __str__(self): """ Return string representation of the exception.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import LatticeError sage: error = LatticeError('meet', 15, 18) sage: error.__str__() 'no meet for 15 and 18' """
class HasseDiagram(DiGraph): """ The Hasse diagram of a poset. This is just a transitively-reduced, directed, acyclic graph without loops or multiple edges.
.. note::
We assume that ``range(n)`` is a linear extension of the poset. That is, ``range(n)`` is the vertex set and a topological sort of the digraph.
This should not be called directly, use Poset instead; all type checking happens there.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]}); H Hasse diagram of a poset containing 4 elements sage: TestSuite(H).run() """ def _repr_(self): r""" TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]}) sage: H._repr_() 'Hasse diagram of a poset containing 4 elements' """
def linear_extension(self): r""" Return a linear extension
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]}) sage: H.linear_extension() [0, 1, 2, 3] """ # Recall: we assume range(n) is a linear extension.
def linear_extensions(self): r""" Return all linear extensions
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]}) sage: H.linear_extensions() [[0, 1, 2, 3], [0, 2, 1, 3]] """
def greedy_linear_extensions_iterator(self): """ Return an iterator over greedy linear extensions of the Hasse diagram.
A linear extension `[e_1, e_2, \ldots, e_n]` is *greedy* if for every `i` either `e_{i+1}` covers `e_i` or all upper covers of `e_i` have at least one lower cover that is not in `[e_1, e_2, \ldots, e_i]`.
Informally said a linear extension is greedy if it "always goes up when possible" and so has no unnecessary jumps.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: N5 = HasseDiagram({0: [1, 2], 2: [3], 1: [4], 3: [4]}) sage: for l in N5.greedy_linear_extensions_iterator(): ....: print(l) [0, 1, 2, 3, 4] [0, 2, 3, 1, 4]
TESTS:
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: list(HasseDiagram({}).greedy_linear_extensions_iterator()) [[]] sage: H = HasseDiagram({0: []}) sage: list(H.greedy_linear_extensions_iterator()) [[0]] """
not any(low in S_ for low in self.neighbors_in(x))]
# Python3-todo: use yield from
def supergreedy_linear_extensions_iterator(self): """ Return an iterator over supergreedy linear extensions of the Hasse diagram.
A linear extension `[e_1, e_2, \ldots, e_n]` is *supergreedy* if, for every `i` and `j` where `i > j`, `e_i` covers `e_j` if for every `i > k > j` at least one lower cover of `e_k` is not in `[e_1, e_2, \ldots, e_k]`.
Informally said a linear extension is supergreedy if it "always goes as high possible, and withdraw so less as possible". These are also called depth-first linear extensions.
EXAMPLES:
We show the difference between "only greedy" and supergreedy extensions::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2], 2: [3, 4]}) sage: G_ext = list(H.greedy_linear_extensions_iterator()) sage: SG_ext = list(H.supergreedy_linear_extensions_iterator()) sage: [0, 2, 3, 1, 4] in G_ext True sage: [0, 2, 3, 1, 4] in SG_ext False
sage: len(SG_ext) 4
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: list(HasseDiagram({}).supergreedy_linear_extensions_iterator()) [[]] sage: list(HasseDiagram({0: [], 1: []}).supergreedy_linear_extensions_iterator()) [[0, 1], [1, 0]] """
else: else:
# Python3-todo: use yield from
def is_linear_extension(self, lin_ext=None): r""" Test if an ordering is a linear extension.
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]}) sage: H.is_linear_extension(list(range(4))) True sage: H.is_linear_extension([3,2,1,0]) False """ return False else: return True
def cover_relations_iterator(self): r""" Iterate over cover relations.
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2,3], 1:[3,4], 2:[5], 3:[5], 4:[5]}) sage: list(H.cover_relations_iterator()) [(0, 2), (0, 3), (1, 3), (1, 4), (2, 5), (3, 5), (4, 5)] """
def cover_relations(self): r""" Return the list of cover relations.
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2,3], 1:[3,4], 2:[5], 3:[5], 4:[5]}) sage: H.cover_relations() [(0, 2), (0, 3), (1, 3), (1, 4), (2, 5), (3, 5), (4, 5)] """
def is_lequal(self, i, j): """ Returns True if i is less than or equal to j in the poset, and False otherwise.
.. note::
If the :meth:`lequal_matrix` has been computed, then this method is redefined to use the cached matrix (see :meth:`_alternate_is_lequal`).
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) sage: x,y,z = 0, 1, 4 sage: H.is_lequal(x,y) False sage: H.is_lequal(y,x) False sage: H.is_lequal(x,z) True sage: H.is_lequal(y,z) True sage: H.is_lequal(z,z) True """ (i < j and j in self.breadth_first_search(i))
def is_less_than(self, x, y): r""" Returns True if ``x`` is less than or equal to ``y`` in the poset, and False otherwise.
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) sage: x,y,z = 0, 1, 4 sage: H.is_less_than(x,y) False sage: H.is_less_than(y,x) False sage: H.is_less_than(x,z) True sage: H.is_less_than(y,z) True sage: H.is_less_than(z,z) False """ else:
def is_gequal(self, x, y): r""" Returns ``True`` if ``x`` is greater than or equal to ``y``, and ``False`` otherwise.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: Q = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) sage: x,y,z = 0,1,4 sage: Q.is_gequal(x,y) False sage: Q.is_gequal(y,x) False sage: Q.is_gequal(x,z) False sage: Q.is_gequal(z,x) True sage: Q.is_gequal(z,y) True sage: Q.is_gequal(z,z) True """
def is_greater_than(self, x, y): """ Returns ``True`` if ``x`` is greater than but not equal to ``y``, and ``False`` otherwise.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: Q = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) sage: x,y,z = 0,1,4 sage: Q.is_greater_than(x,y) False sage: Q.is_greater_than(y,x) False sage: Q.is_greater_than(x,z) False sage: Q.is_greater_than(z,x) True sage: Q.is_greater_than(z,y) True sage: Q.is_greater_than(z,z) False """
def minimal_elements(self): """ Returns a list of the minimal elements of the poset.
EXAMPLES::
sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]}) sage: P(0) in P.minimal_elements() True sage: P(1) in P.minimal_elements() True sage: P(2) in P.minimal_elements() True """
def maximal_elements(self): """ Returns a list of the maximal elements of the poset.
EXAMPLES::
sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]}) sage: P.maximal_elements() [4] """
def bottom(self): """ Returns the bottom element of the poset, if it exists.
EXAMPLES::
sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]}) sage: P.bottom() is None True sage: Q = Poset({0:[1],1:[]}) sage: Q.bottom() 0 """
def has_bottom(self): """ Returns True if the poset has a unique minimal element.
EXAMPLES::
sage: P = Poset({0:[3],1:[3],2:[3],3:[4],4:[]}) sage: P.has_bottom() False sage: Q = Poset({0:[1],1:[]}) sage: Q.has_bottom() True """
def top(self): """ Returns the top element of the poset, if it exists.
EXAMPLES::
sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) sage: P.top() is None True sage: Q = Poset({0:[1],1:[]}) sage: Q.top() 1 """
def has_top(self): """ Returns ``True`` if the poset contains a unique maximal element, and ``False`` otherwise.
EXAMPLES::
sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) sage: P.has_top() False sage: Q = Poset({0:[1],1:[]}) sage: Q.has_top() True """
def is_bounded(self): """ Returns True if the poset contains a unique maximal element and a unique minimal element, and False otherwise.
EXAMPLES::
sage: P = Poset({0:[3],1:[3],2:[3],3:[4,5],4:[],5:[]}) sage: P.is_bounded() False sage: Q = Poset({0:[1],1:[]}) sage: Q.is_bounded() True """
def is_chain(self): """ Returns True if the poset is totally ordered, and False otherwise.
EXAMPLES::
sage: L = Poset({0:[1],1:[2],2:[3],3:[4]}) sage: L.is_chain() True sage: V = Poset({0:[1,2]}) sage: V.is_chain() False
TESTS:
Check :trac:`15330`::
sage: p = Poset(DiGraph({0:[1],2:[1]})) sage: p.is_chain() False """ all(d<=1 for d in self.out_degree()) and # max outdegree is <= 1 all(d<=1 for d in self.in_degree())) # max indegree is <= 1
def is_antichain_of_poset(self, elms): """ Return ``True`` if ``elms`` is an antichain of the Hasse diagram and ``False`` otherwise.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4], 3: [4]}) sage: H.is_antichain_of_poset([1, 2, 3]) True sage: H.is_antichain_of_poset([0, 2, 3]) False """
combinations(elms_sorted, 2))
def dual(self): """ Returns a poset that is dual to the given poset.
EXAMPLES::
sage: P = posets.IntegerPartitions(4) sage: H = P._hasse_diagram; H Hasse diagram of a poset containing 5 elements sage: H.dual() Hasse diagram of a poset containing 5 elements
TESTS::
sage: H = posets.IntegerPartitions(4)._hasse_diagram sage: H.is_isomorphic( H.dual().dual() ) True sage: H.is_isomorphic( H.dual() ) False """
def _precompute_intervals(self): """ Precompute all intervals of the poset.
This will significantly speed up computing congruences. On the other hand it will cost much more memory. Currently this is "hidden" feature. See example below of using.
EXAMPLES::
sage: B4 = posets.BooleanLattice(4) sage: B4.is_isoform() # Slow True sage: B4._hasse_diagram._precompute_intervals() sage: B4 = posets.BooleanLattice(4) sage: B4.is_isoform() # Faster now True """
for v in range(n)] for up in v_up]
def _alternate_interval(self, x, y): """ Return the list of the elements greater than or equal to ``x`` and less than or equal to ``y``.
The list is sorted by numerical value, which is one linear extension for the elements of the interval, but not necessary the same as returned by ``interval()``.
This will be taken to use when ``_precompute_intervals()`` is called.
EXAMPLES::
sage: P = posets.BooleanLattice(3) sage: P.interval(1, 7) [1, 3, 5, 7] sage: P._hasse_diagram._precompute_intervals() sage: P.interval(1, 7) # Uses this function [1, 3, 5, 7]
"""
def interval(self, x, y): """ Return a list of the elements `z` of ``self`` such that `x \leq z \leq y`. The order is that induced by the ordering in ``self.linear_extension``.
INPUT:
- ``x`` -- any element of the poset
- ``y`` -- any element of the poset
EXAMPLES::
sage: uc = [[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]] sage: dag = DiGraph(dict(zip(range(len(uc)),uc))) sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram(dag) sage: I = set([2,5,6,4,7]) sage: I == set(H.interval(2,7)) True """ self.is_lequal(x, z) and self.is_lequal(z, y)]
closed_interval = interval
def open_interval(self, x, y): """ Return a list of the elements `z` of ``self`` such that `x < z < y`. The order is that induced by the ordering in ``self.linear_extension``.
EXAMPLES::
sage: uc = [[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]] sage: dag = DiGraph(dict(zip(range(len(uc)),uc))) sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram(dag) sage: set([5,6,4]) == set(H.open_interval(2,7)) True sage: H.open_interval(7,2) [] """ else:
def rank_function(self): r""" Return the (normalized) rank function of the poset, if it exists.
A *rank function* of a poset `P` is a function `r` that maps elements of `P` to integers and satisfies: `r(x) = r(y) + 1` if `x` covers `y`. The function `r` is normalized such that its minimum value on every connected component of the Hasse diagram of `P` is `0`. This determines the function `r` uniquely (when it exists).
OUTPUT:
- a lambda function, if the poset admits a rank function - ``None``, if the poset does not admit a rank function
EXAMPLES::
sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]) sage: P.rank_function() is not None True sage: P = Poset(([1,2,3,4],[[1,4],[2,3],[3,4]]), facade = True) sage: P.rank_function() is not None True sage: P = Poset(([1,2,3,4,5],[[1,2],[2,3],[3,4],[1,5],[5,4]]), facade = True) sage: P.rank_function() is not None False sage: P = Poset(([1,2,3,4,5,6,7,8],[[1,4],[2,3],[3,4],[5,7],[6,7]]), facade = True) sage: f = P.rank_function(); f is not None True sage: f(5) 0 sage: f(2) 0
TESTS::
sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]) sage: r = P.rank_function() sage: for u,v in P.cover_relations_iterator(): ....: if r(v) != r(u) + 1: ....: print("Bug in rank_function!")
::
sage: Q = Poset([[1,2],[4],[3],[4],[]]) sage: Q.rank_function() is None True
test for ticket :trac:`14006`::
sage: H = Poset()._hasse_diagram sage: s = dumps(H) sage: f = H.rank_function() sage: s = dumps(H) """
@lazy_attribute def _rank(self): r""" Builds the rank function of the poset, if it exists, i.e. an array ``d`` where ``d[object] = self.rank_function()(object)``
A *rank function* of a poset `P` is a function `r` that maps elements of `P` to integers and satisfies: `r(x) = r(y) + 1` if `x` covers `y`. The function `r` is normalized such that its minimum value on every connected component of the Hasse diagram of `P` is `0`. This determines the function `r` uniquely (when it exists).
EXAMPLES::
sage: H = Poset()._hasse_diagram sage: H._rank [] sage: H = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])._hasse_diagram sage: H._rank [0, 1, 1, 2, 2, 1, 2, 3] sage: H = Poset(([1,2,3,4,5],[[1,2],[2,3],[3,4],[1,5],[5,4]]))._hasse_diagram sage: H._rank is None True """ # rank[i] is the rank of point i. It is equal to None until the rank of # i is computed # then look at the neighbors of the neighbors ... # Normalize the ranks of vertices in the connected component # so that smallest is 0: #now, all ranks are set.
def rank(self,element=None): r""" Returns the rank of ``element``, or the rank of the poset if ``element`` is ``None``. (The rank of a poset is the length of the longest chain of elements of the poset.)
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H.rank(5) 2 sage: H.rank() 3 sage: Q = HasseDiagram({0:[1,2],1:[3],2:[],3:[]}) sage: Q.rank() 2 sage: Q.rank(1) 1 """ else:
def is_ranked(self): r""" Returns True if the poset is ranked, and False otherwise.
A poset is *ranked* if it admits a rank function. For more information about the rank function, see :meth:`~rank_function` and :meth:`~is_graded`.
EXAMPLES::
sage: P = Poset([[1],[2],[3],[4],[]]) sage: P.is_ranked() True sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) sage: Q.is_ranked() False """ return bool(self.rank_function())
def covers(self,x,y): """ Returns True if y covers x and False otherwise.
EXAMPLES::
sage: Q = Poset([[1,5],[2,6],[3],[4],[],[6,3],[4]]) sage: Q.covers(Q(1),Q(6)) True sage: Q.covers(Q(1),Q(4)) False """ return self.has_edge(x,y)
def upper_covers_iterator(self,element): r""" Returns the list of elements that cover ``element``.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: list(H.upper_covers_iterator(0)) [1, 2, 3] sage: list(H.upper_covers_iterator(7)) [] """
def lower_covers_iterator(self,element): r""" Returns the list of elements that are covered by ``element``.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: list(H.lower_covers_iterator(0)) [] sage: list(H.lower_covers_iterator(4)) [1, 2] """
def cardinality(self): r""" Returns the number of elements in the poset.
EXAMPLES::
sage: Poset([[1,2,3],[4],[4],[4],[]]).cardinality() 5
TESTS:
For a time, this function was named ``size()``, which would override the same-named method of the underlying digraph. :trac:`8735` renamed this method to ``cardinality()`` with a deprecation warning. :trac:`11214` removed the warning since code for graphs was raising the warning inadvertently. This tests that ``size()`` for a Hasse diagram returns the number of edges in the digraph. ::
sage: L = posets.BooleanLattice(5) sage: H = L.hasse_diagram() sage: H.size() 80 sage: H.size() == H.num_edges() True """
def moebius_function(self,i,j): # dumb algorithm r""" Returns the value of the Möbius function of the poset on the elements ``i`` and ``j``.
EXAMPLES::
sage: P = Poset([[1,2,3],[4],[4],[4],[]]) sage: H = P._hasse_diagram sage: H.moebius_function(0,4) 2 sage: for u,v in P.cover_relations_iterator(): ....: if P.moebius_function(u,v) != -1: ....: print("Bug in moebius_function!") """ self._moebius_function_values[(i,j)] = 0 else: self._moebius_function_values[(i,j)] = 0 else: -sum([self.moebius_function(i,k) for k in ci[:-1]]) mobius_function = deprecated_function_alias(19855, moebius_function)
def moebius_function_matrix(self): r""" Returns the matrix of the Möbius function of this poset
This returns the sparse matrix over `\ZZ` whose ``(x, y)`` entry is the value of the Möbius function of ``self`` evaluated on ``x`` and ``y``, and redefines :meth:`moebius_function` to use it.
.. NOTE::
The result is cached in :meth:`_moebius_function_matrix`.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H.moebius_function_matrix() [ 1 -1 -1 -1 1 0 1 0] [ 0 1 0 0 -1 0 0 0] [ 0 0 1 0 -1 -1 -1 2] [ 0 0 0 1 0 0 -1 0] [ 0 0 0 0 1 0 0 -1] [ 0 0 0 0 0 1 0 -1] [ 0 0 0 0 0 0 1 -1] [ 0 0 0 0 0 0 0 1]
TESTS::
sage: H.moebius_function_matrix().is_immutable() True sage: hasattr(H,'_moebius_function_matrix') True
sage: H.moebius_function == H._moebius_function_from_matrix True """ mobius_function_matrix = deprecated_function_alias(19855, moebius_function_matrix)
# Redefine self.moebius_function def _moebius_function_from_matrix(self, i,j): r""" Returns the value of the Möbius function of the poset on the elements ``i`` and ``j``.
EXAMPLES::
sage: P = Poset([[1,2,3],[4],[4],[4],[]]) sage: H = P._hasse_diagram sage: H.moebius_function(0,4) # indirect doctest 2 sage: for u,v in P.cover_relations_iterator(): ....: if P.moebius_function(u,v) != -1: ....: print("Bug in moebius_function!")
This uses ``self._moebius_function_matrix``, as computed by :meth:`moebius_function_matrix`. """ return self._moebius_function_matrix[i,j] _mobius_function_from_matrix = deprecated_function_alias(19855, _moebius_function_from_matrix)
@cached_method def coxeter_transformation(self): r""" Returns the matrix of the Auslander-Reiten translation acting on the Grothendieck group of the derived category of modules on the poset, in the basis of simple modules.
EXAMPLES::
sage: M = posets.PentagonPoset()._hasse_diagram.coxeter_transformation(); M [ 0 0 0 0 -1] [ 0 0 0 1 -1] [ 0 1 0 0 -1] [-1 1 1 0 -1] [-1 1 0 1 -1]
TESTS::
sage: M = posets.PentagonPoset()._hasse_diagram.coxeter_transformation() sage: M**8 == 1 True """
def order_filter(self, elements): """ Return the order filter generated by a list of elements.
`I` is an order filter if, for any `x` in `I` and `y` such that `y \ge x`, then `y` is in `I`.
EXAMPLES::
sage: H = posets.BooleanLattice(4)._hasse_diagram sage: H.order_filter([3,8]) [3, 7, 8, 9, 10, 11, 12, 13, 14, 15] """
def principal_order_filter(self, i): """ Returns the order filter generated by ``i``.
EXAMPLES::
sage: H = posets.BooleanLattice(4)._hasse_diagram sage: H.principal_order_filter(2) [2, 3, 6, 7, 10, 11, 14, 15] """
def order_ideal(self, elements): """ Return the order ideal generated by a list of elements.
`I` is an order ideal if, for any `x` in `I` and `y` such that `y \le x`, then `y` is in `I`.
EXAMPLES::
sage: H = posets.BooleanLattice(4)._hasse_diagram sage: H.order_ideal([7,10]) [0, 1, 2, 3, 4, 5, 6, 7, 8, 10] """ self.depth_first_search(elements, neighbors=self.neighbors_in)))
def principal_order_ideal(self, i): """ Returns the order ideal generated by `i`.
EXAMPLES::
sage: H = posets.BooleanLattice(4)._hasse_diagram sage: H.principal_order_ideal(6) [0, 2, 4, 6] """
@lazy_attribute def _leq_matrix(self): r""" Computes a matrix whose ``(i,j)`` entry is 1 if ``i`` is less than ``j`` in the poset, and 0 otherwise; and redefines ``__lt__`` to use this matrix.
EXAMPLES::
sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]) sage: H = P._hasse_diagram sage: H._leq_matrix [1 1 1 1 1 1 1 1] [0 1 0 1 0 0 0 1] [0 0 1 1 1 0 1 1] [0 0 0 1 0 0 0 1] [0 0 0 0 1 0 0 1] [0 0 0 0 0 1 1 1] [0 0 0 0 0 0 1 1] [0 0 0 0 0 0 0 1]
""" # Create the matrix # Redefine self.is_lequal # Return the matrix
def lequal_matrix(self): """ Returns the matrix whose ``(i,j)`` entry is 1 if ``i`` is less than ``j`` in the poset, and 0 otherwise; and redefines ``__lt__`` to use this matrix.
EXAMPLES::
sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]]) sage: H = P._hasse_diagram sage: H.lequal_matrix() [1 1 1 1 1 1 1 1] [0 1 0 1 0 0 0 1] [0 0 1 1 1 0 1 1] [0 0 0 1 0 0 0 1] [0 0 0 0 1 0 0 1] [0 0 0 0 0 1 1 1] [0 0 0 0 0 0 1 1] [0 0 0 0 0 0 0 1]
TESTS::
sage: H.lequal_matrix().is_immutable() True """
def _alternate_is_lequal(self,i,j): r""" Returns ``True`` if ``i`` is less than or equal to ``j`` in ``self``, and ``False`` otherwise.
.. NOTE::
If the :meth:`lequal_matrix` has been computed, then :meth:`is_lequal` is redefined to use the cached matrix.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2], 1:[2], 2:[3], 3:[4], 4:[]}) sage: H.lequal_matrix() [1 0 1 1 1] [0 1 1 1 1] [0 0 1 1 1] [0 0 0 1 1] [0 0 0 0 1] sage: x,y,z = 0, 1, 4 sage: H._alternate_is_lequal(x,y) False sage: H._alternate_is_lequal(y,x) False sage: H._alternate_is_lequal(x,z) True sage: H._alternate_is_lequal(y,z) True sage: H._alternate_is_lequal(z,z) True """
def prime_elements(self): r""" Return the join-prime and meet-prime elements of the bounded poset.
An element `x` of a poset `P` is join-prime if the subposet induced by `\{y \in P \mid y \not\ge x\}` has a top element. Meet-prime is defined dually.
.. NOTE::
The poset is expected to be bounded, and this is *not* checked.
OUTPUT:
A pair `(j, m)` where `j` is a list of join-prime elements and `m` is a list of meet-prime elements.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]}) sage: H.prime_elements() ([1, 2], [2, 3]) """
# The complement of the upper set of a join-prime must have # a top element. Maximal elements of the complement are those # covered by only elements in the upper set. If there is only # one maximal element, it is a meet-prime and 'e' is a # join-prime. all(u_ in upset for u_ in self.neighbor_out_iterator(m))):
# Join-primes are join-irreducibles, only check those.
@lazy_attribute def _meet(self): r""" Return the matrix of meets of ``self``. The ``(x,y)``-entry of this matrix is the meet of ``x`` and ``y`` in ``self``.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H._meet [0 0 0 0 0 0 0 0] [0 1 0 0 1 0 0 1] [0 0 2 0 2 2 2 2] [0 0 0 3 0 0 3 3] [0 1 2 0 4 2 2 4] [0 0 2 0 2 5 2 5] [0 0 2 3 2 2 6 6] [0 1 2 3 4 5 6 7]
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2,3],1:[2,3]}) sage: H.meet_matrix() Traceback (most recent call last): ... ValueError: not a meet-semilattice: no bottom element
sage: H = HasseDiagram({0:[1,2],1:[3,4],2:[3,4]}) sage: H.meet_matrix() Traceback (most recent call last): ... LatticeError: no meet for ...
sage: L = LatticePoset({0:[1,2,3],1:[4],2:[4],3:[4]}) sage: P = L.dual() sage: P.meet(2,3) 4 """
def meet_matrix(self): r""" Returns the matrix of meets of ``self``. The ``(x,y)``-entry of this matrix is the meet of ``x`` and ``y`` in ``self``.
This algorithm is modelled after the algorithm of Freese-Jezek-Nation (p217). It can also be found on page 140 of [Gec81]_.
.. NOTE::
Once the matrix has been computed, it is stored in :meth:`_meet_matrix`. Delete this attribute if you want to recompute the matrix.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H.meet_matrix() [0 0 0 0 0 0 0 0] [0 1 0 0 1 0 0 1] [0 0 2 0 2 2 2 2] [0 0 0 3 0 0 3 3] [0 1 2 0 4 2 2 4] [0 0 2 0 2 5 2 5] [0 0 2 3 2 2 6 6] [0 1 2 3 4 5 6 7]
REFERENCE:
.. [Gec81] Fundamentals of Computation Theory Gecseg, F. Proceedings of the 1981 International Fct-Conference Szeged, Hungaria, August 24-28, vol 117 Springer-Verlag, 1981
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2,3],1:[2,3]}) sage: H.meet_matrix() Traceback (most recent call last): ... ValueError: not a meet-semilattice: no bottom element
sage: H = HasseDiagram({0:[1,2],1:[3,4],2:[3,4]}) sage: H.meet_matrix() Traceback (most recent call last): ... LatticeError: no meet for ... """
def is_meet_semilattice(self): r""" Returns ``True`` if ``self`` has a meet operation, and ``False`` otherwise.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H.is_meet_semilattice() True
sage: H = HasseDiagram({0:[1,2],1:[3],2:[3],3:[]}) sage: H.is_meet_semilattice() True
sage: H = HasseDiagram({0:[2,3],1:[2,3]}) sage: H.is_meet_semilattice() False """ else:
@lazy_attribute def _join(self): r""" Computes a matrix whose ``(x,y)``-entry is the join of ``x`` and ``y`` in ``self``
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H.join_matrix() # indirect doctest [0 1 2 3 4 5 6 7] [1 1 4 7 4 7 7 7] [2 4 2 6 4 5 6 7] [3 7 6 3 7 7 6 7] [4 4 4 7 4 7 7 7] [5 7 5 7 7 5 7 7] [6 7 6 6 7 7 6 7] [7 7 7 7 7 7 7 7]
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2,3],1:[2,3]}) sage: H.join_matrix() Traceback (most recent call last): ... ValueError: not a join-semilattice: no top element
sage: H = HasseDiagram({0:[2,3],1:[2,3],2:[4],3:[4]}) sage: H.join_matrix() Traceback (most recent call last): ... LatticeError: no join for ...
sage: L = LatticePoset({0:[1,2,3],1:[4],2:[4],3:[4]}) sage: P = L.dual() sage: P.join(2,3) 0 """
def join_matrix(self): r""" Returns the matrix of joins of ``self``. The ``(x,y)``-entry of this matrix is the join of ``x`` and ``y`` in ``self``.
This algorithm is modelled after the algorithm of Freese-Jezek-Nation (p217). It can also be found on page 140 of [Gec81]_.
.. note::
Once the matrix has been computed, it is stored in :meth:`_join_matrix`. Delete this attribute if you want to recompute the matrix.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H.join_matrix() [0 1 2 3 4 5 6 7] [1 1 4 7 4 7 7 7] [2 4 2 6 4 5 6 7] [3 7 6 3 7 7 6 7] [4 4 4 7 4 7 7 7] [5 7 5 7 7 5 7 7] [6 7 6 6 7 7 6 7] [7 7 7 7 7 7 7 7]
TESTS::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[2,3],1:[2,3]}) sage: H.join_matrix() Traceback (most recent call last): ... ValueError: not a join-semilattice: no top element
sage: H = HasseDiagram({0:[2,3],1:[2,3],2:[4],3:[4]}) sage: H.join_matrix() Traceback (most recent call last): ... LatticeError: no join for ... """
def is_join_semilattice(self): r""" Returns ``True`` if ``self`` has a join operation, and ``False`` otherwise.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H.is_join_semilattice() True sage: H = HasseDiagram({0:[2,3],1:[2,3]}) sage: H.is_join_semilattice() False sage: H = HasseDiagram({0:[2,3],1:[2,3],2:[4],3:[4]}) sage: H.is_join_semilattice() False """ else:
def find_nonsemidistributive_elements(self, meet_or_join): r""" Check if the lattice is semidistributive or not.
INPUT:
- ``meet_or_join`` -- string ``'meet'`` or ``'join'`` to decide if to check for join-semidistributivity or meet-semidistributivity
OUTPUT:
- ``None`` if the lattice is semidistributive OR - tuple ``(u, e, x, y)`` such that `u = e \vee x = e \vee y` but `u \neq e \vee (x \wedge y)` if ``meet_or_join=='join'`` and `u = e \wedge x = e \wedge y` but `u \neq e \wedge (x \vee y)` if ``meet_or_join=='meet'``
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1, 2], 1:[3, 4], 2:[4, 5], 3:[6], ....: 4:[6], 5:[6]}) sage: H.find_nonsemidistributive_elements('join') is None False sage: H.find_nonsemidistributive_elements('meet') is None True """ else: raise ValueError("meet_or_join must be 'join' or 'meet'")
def is_distributive_lattice(self): # still a dumb algorithm... r""" Deprecated.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,3,2],1:[4],2:[4,5,6],3:[6],4:[7],5:[7],6:[7],7:[]}) sage: H.is_distributive_lattice() doctest:...: DeprecationWarning: This function will be removed in future versions. Convert ... False """ except ValueError: return False return True
def vertical_decomposition(self, return_list=False): """ Return vertical decomposition of the lattice.
This is the backend function for vertical decomposition functions of lattices.
The property of being vertically decomposable is defined for lattices. This is *not* checked, and the function works with any bounded poset.
INPUT:
- ``return_list``, a boolean. If ``False`` (the default), return an element that is not the top neither the bottom element of the lattice, but is comparable to all elements of the lattice, if the lattice is vertically decomposable and ``None`` otherwise. If ``True``, return list of decomposition elements.
EXAMPLES::
sage: H = posets.BooleanLattice(4)._hasse_diagram sage: H.vertical_decomposition() is None True sage: P = Poset( ([1,2,3,6,12,18,36], attrcall("divides")) ) sage: P._hasse_diagram.vertical_decomposition() 3 sage: P._hasse_diagram.vertical_decomposition(return_list=True) [3] """ return [] else: else:
def is_complemented(self): """ Return an element of the lattice that has no complement.
If the lattice is complemented, return ``None``.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram
sage: H = HasseDiagram({0:[1, 2], 1:[3], 2:[3], 3:[4]}) sage: H.is_complemented() 1
sage: H = HasseDiagram({0:[1, 2, 3], 1:[4], 2:[4], 3:[4]}) sage: H.is_complemented() is None True """
else:
def pseudocomplement(self, element): """ Return the pseudocomplement of ``element``, if it exists.
The pseudocomplement is the greatest element whose meet with given element is the bottom element. It may not exist, and then the function returns ``None``.
INPUT:
- ``element`` -- an element of the lattice.
OUTPUT:
An element of the Hasse diagram, i.e. an integer, or ``None`` if the pseudocomplement does not exist.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]}) sage: H.pseudocomplement(2) 3
sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4], 3: [4]}) sage: H.pseudocomplement(2) is None True """
def orthocomplementations_iterator(self): r""" Return an iterator over orthocomplementations of the lattice.
OUTPUT:
An iterator that gives plain list of integers.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2], 1:[3,4], 3:[5], 4:[5], 2:[6,7], ....: 6:[8], 7:[8], 5:[9], 8:[9]}) sage: list(H.orthocomplementations_iterator()) [[9, 8, 5, 6, 7, 2, 3, 4, 1, 0], [9, 8, 5, 7, 6, 2, 4, 3, 1, 0]]
ALGORITHM:
As ``DiamondPoset(2*n+2)`` has `(2n)!/(n!2^n)` different orthocomplementations, the complexity of listing all of them is necessarily `O(n!)`.
An orthocomplemented lattice is self-dual, so that for example orthocomplement of an atom is a coatom. This function basically just computes list of possible orthocomplementations for every element (i.e. they must be complements and "duals"), and then tries to fit them all.
TESTS:
Special and corner cases::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram() # Empty sage: list(H.orthocomplementations_iterator()) [[]] sage: H = HasseDiagram({0:[]}) # One element sage: list(H.orthocomplementations_iterator()) [[0]] sage: H = HasseDiagram({0:[1]}) # Two elements sage: list(H.orthocomplementations_iterator()) [[1, 0]]
Trivial cases: odd number of elements, not self-dual, not complemented::
sage: H = posets.DiamondPoset(5)._hasse_diagram sage: list(H.orthocomplementations_iterator()) [] sage: H = posets.ChainPoset(4)._hasse_diagram sage: list(H.orthocomplementations_iterator()) [] 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]]) ) sage: list(H.orthocomplementations_iterator()) [] sage: H = HasseDiagram({0:[1, 2, 3], 1: [4], 2:[4], 3: [5], 4:[5]}) sage: list(H.orthocomplementations_iterator()) []
Complemented, self-dual and even number of elements, but not orthocomplemented::
sage: H = HasseDiagram( ([[0, 1], [1, 2], [2, 3], [0, 4], [4, 5], [0, 6], [3, 7], [5, 7], [6, 7]]) ) sage: list(H.orthocomplementations_iterator()) []
Unique orthocomplementations; second is not uniquely complemented, but has only one orthocomplementation.
sage: H = posets.BooleanLattice(4)._hasse_diagram # Uniquely complemented sage: len(list(H.orthocomplementations_iterator())) 1 sage: H = HasseDiagram({0:[1, 2], 1:[3], 2:[4], 3:[5], 4:[5]}) sage: len([_ for _ in H.orthocomplementations_iterator()]) 1
"Lengthening diamond" must keep the number of orthocomplementations::
sage: H = HasseDiagram( ([[0, 1], [0, 2], [0, 3], [0, 4], [1, 5], [2, 5], [3, 5], [4, 5]]) ) sage: n = len([_ for _ in H.orthocomplementations_iterator()]); n 3 sage: H = HasseDiagram('M]??O?@??C??OA???OA??@?A??C?A??O??') sage: len([_ for _ in H.orthocomplementations_iterator()]) == n True
This lattice has an unique "possible orthocomplement" for every element, but they can not be fit together; orthocomplement pairs would be 0-11, 1-7, 2-4, 3-10, 5-9 and 6-8, and then orthocomplements for chain 0-1-6-11 would be 11-7-8-0, which is not a chain::
sage: H = HasseDiagram('KTGG_?AAC?O?o?@?@?E?@?@??') sage: list([_ for _ in H.orthocomplementations_iterator()]) [] """
# Special cases first
# We compute possible orthocomplements, i.e. elements # with "dual position" and complement to each other.
# Fix following after ticket #20727 self._meet[e, x] == 0 and self._join[e, x] == n-1 and x in orbits[orbit_number[dual_isomorphism[e]]]]
# Fitting is done by this recursive function: else:
# A little optimization # Every element might have one possible orthocomplement, # but so that they don't fit together. Must check that.
def find_nonsemimodular_pair(self, upper): """ Return pair of elements showing the lattice is not modular.
INPUT:
- upper, a Boolean -- if ``True``, test wheter the lattice is upper semimodular; otherwise test whether the lattice is lower semimodular.
OUTPUT:
``None``, if the lattice is semimodular. Pair `(a, b)` violating semimodularity otherwise.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1, 2], 1:[3, 4], 2:[4, 5], 3:[6], 4:[6], 5:[6]}) sage: H.find_nonsemimodular_pair(upper=True) is None True sage: H.find_nonsemimodular_pair(upper=False) (5, 3)
sage: H_ = HasseDiagram(H.reverse().relabel(lambda x: 6-x, inplace=False)) sage: H_.find_nonsemimodular_pair(upper=True) (3, 1) sage: H_.find_nonsemimodular_pair(upper=False) is None True """ else:
def antichains_iterator(self): r""" Return an iterator over the antichains of the poset.
.. note::
The algorithm is based on Freese-Jezek-Nation p. 226. It does a depth first search through the set of all antichains organized in a prefix tree.
EXAMPLES::
sage: P = posets.PentagonPoset() sage: H = P._hasse_diagram sage: H.antichains_iterator() <generator object antichains_iterator at ...> sage: list(H.antichains_iterator()) [[], [4], [3], [2], [1], [1, 3], [1, 2], [0]]
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0:[1,2],1:[4],2:[3],3:[4]}) sage: list(H.antichains_iterator()) [[], [4], [3], [2], [1], [1, 3], [1, 2], [0]]
sage: H = HasseDiagram({0:[],1:[],2:[]}) sage: list(H.antichains_iterator()) [[], [2], [1], [1, 2], [0], [0, 2], [0, 1], [0, 1, 2]]
sage: H = HasseDiagram({0:[1],1:[2],2:[3],3:[4]}) sage: list(H.antichains_iterator()) [[], [4], [3], [2], [1], [0]]
TESTS::
sage: H = Poset()._hasse_diagram sage: list(H.antichains_iterator()) [[]] """ # NOTE: Ordering of antichains as a prefix tree is crucial for # congruences_iterator() to work. Change it, if you change this.
# Complexity note: # antichains_queues never grows longer than self.cardinality(). # Indeed, if a appears before b in antichains_queues, then # the largest element of a is strictly smaller than that of b. # Invariant: # - the elements of antichain are independent # - the elements of queue are independent from those of antichain
def are_incomparable(self, i, j): """ Returns whether ``i`` and ``j`` are incomparable in the poset
INPUT:
- ``i``, ``j`` -- vertices of this Hasse diagram
EXAMPLES::
sage: P = posets.PentagonPoset() sage: H = P._hasse_diagram sage: H.are_incomparable(1,2) True sage: [ (i,j) for i in H.vertices() for j in H.vertices() if H.are_incomparable(i,j)] [(1, 2), (1, 3), (2, 1), (3, 1)] """
def are_comparable(self, i, j): """ Returns whether ``i`` and ``j`` are comparable in the poset
INPUT:
- ``i``, ``j`` -- vertices of this Hasse diagram
EXAMPLES::
sage: P = posets.PentagonPoset() sage: H = P._hasse_diagram sage: H.are_comparable(1,2) False sage: [ (i,j) for i in H.vertices() for j in H.vertices() if H.are_comparable(i,j)] [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1), (1, 4), (2, 0), (2, 2), (2, 3), (2, 4), (3, 0), (3, 2), (3, 3), (3, 4), (4, 0), (4, 1), (4, 2), (4, 3), (4, 4)] """
def antichains(self, element_class = list): """ Returns all antichains of ``self``, organized as a prefix tree
INPUT:
- ``element_class`` -- (default:list) an iterable type
EXAMPLES::
sage: P = posets.PentagonPoset() sage: H = P._hasse_diagram sage: A = H.antichains() sage: list(A) [[], [0], [1], [1, 2], [1, 3], [2], [3], [4]] sage: A.cardinality() 8 sage: [1,3] in A True sage: [1,4] in A False
TESTS::
sage: TestSuite(A).run(skip = "_test_pickling")
.. note:: It's actually the pickling of the cached method :meth:`coxeter_transformation` that fails ...
TESTS::
sage: A = Poset()._hasse_diagram.antichains() sage: list(A) [[]] sage: TestSuite(A).run() """ self.are_incomparable, element_class = element_class)
def chains(self, element_class=list, exclude=None): """ Return all chains of ``self``, organized as a prefix tree.
INPUT:
- ``element_class`` -- (default: ``list``) an iterable type
- ``exclude`` -- elements of the poset to be excluded (default: ``None``)
OUTPUT:
The enumerated set (with a forest structure given by prefix ordering) consisting of all chains of ``self``, each of which is given as an ``element_class``.
EXAMPLES::
sage: P = posets.PentagonPoset() sage: H = P._hasse_diagram sage: A = H.chains() sage: list(A) [[], [0], [0, 1], [0, 1, 4], [0, 2], [0, 2, 3], [0, 2, 3, 4], [0, 2, 4], [0, 3], [0, 3, 4], [0, 4], [1], [1, 4], [2], [2, 3], [2, 3, 4], [2, 4], [3], [3, 4], [4]] sage: A.cardinality() 20 sage: [1,3] in A False sage: [1,4] in A True
One can exclude some vertices::
sage: list(H.chains(exclude=[4, 3])) [[], [0], [0, 1], [0, 2], [1], [2]]
The ``element_class`` keyword determines how the chains are being returned:
sage: P = Poset({1: [2, 3], 2: [4]}) sage: list(P._hasse_diagram.chains(element_class=tuple)) [(), (0,), (0, 1), (0, 1, 2), (0, 2), (0, 3), (1,), (1, 2), (2,), (3,)] sage: list(P._hasse_diagram.chains()) [[], [0], [0, 1], [0, 1, 2], [0, 2], [0, 3], [1], [1, 2], [2], [3]]
(Note that taking the Hasse diagram has renamed the vertices.)
sage: list(P._hasse_diagram.chains(element_class=tuple, exclude=[0])) [(), (1,), (1, 2), (2,), (3,)]
.. SEEALSO:: :meth:`antichains` """ else: self.are_comparable, element_class = element_class)
def sublattices_iterator(self, elms, min_e): """ Return an iterator over sublattices of the Hasse diagram.
INPUT:
- ``elms`` -- elements already in sublattice; use set() at start - ``min_e`` -- smallest new element to add for new sublattices
OUTPUT:
List of sublattices as sets of integers.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2], 1:[3], 2:[3]}) sage: it = H.sublattices_iterator(set(), 0); it <generator object sublattices_iterator at ...> sage: next(it) set() sage: next(it) {0} """ # Python3-note: "yield from" would be simpler. else:
def maximal_sublattices(self): """ Return maximal sublattices of the lattice.
EXAMPLES::
sage: L = posets.PentagonPoset() sage: ms = L._hasse_diagram.maximal_sublattices() sage: sorted(ms, key=sorted) [{0, 1, 2, 4}, {0, 1, 3, 4}, {0, 2, 3, 4}] """
""" Helper function to get sublattice generated by list of elements. """
# First try to append an element # Let's try to add 'e' and see what happens. # Skip this, if it generated a back-reference. # Now sl is whole lattice, so we continue and try # appending another element.
# Can not append. Try to increment last element.
# Can not append nor increment. "Backtracking".
# Special case to handle at last. result.append(set(range(1, N)))
def frattini_sublattice(self): """ Return the list of elements of the Frattini sublattice of the lattice.
EXAMPLES::
sage: H = posets.PentagonPoset()._hasse_diagram sage: H.frattini_sublattice() [0, 4] """ # Just a direct computation, no optimization at all. all(e in ms for ms in max_sublats)]
def kappa_dual(self, a): r""" Return the minimum element smaller than the element covering ``a`` but not smaller than ``a``.
Define `\kappa^*(a)` as the minimum element of `(\downarrow a_*) \setminus (\downarrow a)`, where `a_*` is the element covering `a`. It is always a join-irreducible element, if it exists.
.. NOTE::
Element ``a`` is expected to be meet-irreducible, and this is *not* checked.
INPUT:
- ``a`` -- a join-irreducible element of the lattice
OUTPUT:
The element `\kappa^*(a)` or ``None`` if there is not a unique smallest element with given constraints.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2], 1: [3, 4], 2: [4, 5], 3: [6], 4: [6], 5: [6]}) sage: H.kappa_dual(3) 2 sage: H.kappa_dual(4) is None True
TESTS::
sage: H = HasseDiagram({0: [1]}) sage: H.kappa_dual(0) 1 """
def skeleton(self): """ Return the skeleton of the lattice.
The lattice is expected to be pseudocomplemented and non-empty.
The skeleton of the lattice is the subposet induced by those elements that are the pseudocomplement to at least one element.
OUTPUT:
List of elements such that the subposet induced by them is the skeleton of the lattice.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2], 1: [3, 4], 2: [4], ....: 3: [5], 4: [5]}) sage: H.skeleton() [5, 2, 0, 3] """
else:
def is_convex_subset(self, S): r""" Return ``True`` if `S` is a convex subset of the poset, and ``False`` otherwise.
A subset `S` is *convex* in the poset if `b \in S` whenever `a, c \in S` and `a \le b \le c`.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: B3 = HasseDiagram({0: [1, 2, 4], 1: [3, 5], 2: [3, 6], ....: 3: [7], 4: [5, 6], 5: [7], 6: [7]}) sage: B3.is_convex_subset([1, 3, 5, 4]) # Also connected True sage: B3.is_convex_subset([1, 3, 4]) # Not connected True
sage: B3.is_convex_subset([0, 1, 2, 3, 6]) # No, 0 < 4 < 6 False sage: B3.is_convex_subset([0, 1, 2, 7]) # No, 1 < 3 < 7. False
TESTS::
sage: B3.is_convex_subset([]) True sage: B3.is_convex_subset([6]) True """
# Now b not in S, b > a and a in S. if v <= s_max and v not in ok]
def neutral_elements(self): """ Return the list of neutral elements of the lattice.
An element `a` in a lattice is neutral if the sublattice generated by `a`, `x` and `y` is distributive for every `x`, `y` in the lattice.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H= HasseDiagram({0: [1, 2], 1: [4], 2: [3], 3: [4, 5], ....: 4: [6], 5:[6]}) sage: sorted(H.neutral_elements()) [0, 4, 6]
ALGORITHM:
Basically we just check the distributivity against all element pairs `x, y` to see if element `a` is neutral or not.
If we found that `a, x, y` is not a distributive triple, we add all three to list of non-neutral elements. If we found `a` to be neutral, we add it to list of neutral elements. When testing we skip already found neutral elements, as they can't be our `x` or `y`.
We skip `a, x, y` as trivial if it is a chain. We do that by letting `x` to be a non-comparable to `a`; `y` can be any element.
We first try to found `x` and `y` from elements not yet tested, so that we could get three birds with one stone.
And last, the top and bottom elements are always neutral and need not be tested. """
jn[jn[meet_ax, mt[a, y]], mt[x, y]]): jn[jn[meet_ax, mt[a, y]], mt[x, y]]): jn[jn[meet_ax, mt[a, y]], mt[x, y]]): jn[jn[meet_ax, mt[a, y]], mt[x, y]]):
else:
def kappa(self, a): r""" Return the maximum element greater than the element covered by ``a`` but not greater than ``a``.
Define `\kappa(a)` as the maximum element of `(\uparrow a_*) \setminus (\uparrow a)`, where `a_*` is the element covered by `a`. It is always a meet-irreducible element, if it exists.
.. NOTE::
Element ``a`` is expected to be join-irreducible, and this is *not* checked.
INPUT:
- ``a`` -- a join-irreducible element of the lattice
OUTPUT:
The element `\kappa(a)` or ``None`` if there is not a unique greatest element with given constraints.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4, 5], 3: [5], 4: [6], 5: [6]}) sage: H.kappa(1) 5 sage: H.kappa(2) is None True
TESTS::
sage: H = HasseDiagram({0: [1]}) sage: H.kappa(1) 0 """
def atoms_of_congruence_lattice(self): r""" Return atoms of the congruence lattice.
In other words, return "minimal non-trivial" congruences: A congruence is minimal if the only finer (as a partition of set of elements) congruence is the trivial congruence where every block contains only one element.
.. SEEALSO:: :meth:`congruence`
OUTPUT:
List of congruences, every congruence as :class:`sage.combinat.set_partition.SetPartition`
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: N5 = HasseDiagram({0: [1, 2], 1: [4], 2: [3], 3:[4]}) sage: N5.atoms_of_congruence_lattice() [{{0}, {1}, {2, 3}, {4}}] sage: Hex = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [5], 4: [5]}) sage: Hex.atoms_of_congruence_lattice() [{{0}, {1}, {2, 4}, {3}, {5}}, {{0}, {1, 3}, {2}, {4}, {5}}]
ALGORITHM:
Every atom is a join-irreducible. Every join-irreducible of `\mathrm{Con}(L)` is a principal congruence generated by a meet-irreducible element and the only element covering it (and also by a join-irreducible element and the only element covered by it). Hence we check those principal congruences to find the minimal ones. """ # Note: A lattice L if subdirectly reducible (i.e. is a sublattice # of a Cartesian product of two smaller lattices) iff Con(L) has # at least two atoms. That's were this is used for.
# Get smaller set, meet- or join-irreducibles irr = [(v, next(self.neighbor_in_iterator(v))) for v in join_irreducibles] else:
def congruence(self, parts, start=None, stop_pairs=[]): """ Return the congruence ``start`` "extended" by ``parts``.
``start`` is assumed to be a valid congruence of the lattice, and this is *not* checked.
INPUT:
- ``parts`` -- a list of lists; congruences to add - ``start`` -- a disjoint set; already computed congruence (or ``None``) - ``stop_pairs`` -- a list of pairs; list of pairs for stopping computation
OUTPUT:
``None``, if the congruence generated by ``start`` and ``parts`` together contains a block that has elements `a, b` so that ``(a, b)`` is in the list ``stop_pairs``. Otherwise the least congruence that contains a block whose subset is `p` for every `p` in ``parts`` or ``start``, given as :class:`sage.sets.disjoint_set.DisjointSet_class`.
ALGORITHM:
Use the quadrilateral argument from page 120 of [Dav1997]_.
Basically we take one block from todo-list, search quadrilateral blocks up and down against the block, and then complete them to closed intervals and add to todo-list.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]}) sage: cong = H.congruence([[0, 1]]); cong {{0, 1, 3}, {2, 4}} sage: H.congruence([[0, 2]], start=cong) {{0, 1, 2, 3, 4}}
sage: H.congruence([[0, 1]], stop_pairs=[(1, 3)]) is None True
TESTS::
sage: H = HasseDiagram('HT@O?GO?OE?G@??') sage: H.congruence([[0, 1]]).number_of_subsets() 1 sage: H = HasseDiagram('HW_oC?@@O@?O@??') sage: H.congruence([[0, 1]]).number_of_subsets() 1
Check :trac:`21861`::
sage: H = HasseDiagram({0: [1, 2], 1: [3], 2: [4], 3: [4]}) sage: tmp = H.congruence([[1, 3]]) sage: tmp.number_of_subsets() 4 sage: H.congruence([[0, 1]], start=tmp).number_of_subsets() 2 sage: tmp.number_of_subsets() 4 """
""" Return the smallest interval containing elements in the set S. """
# Following is needed for cases like # posets.BooleanLattice(3).congruence([(0,1), (0,2), (0,4)])
# First check if we should stop now.
# We take one block and try to find as big interval # as possible to unify as a new block by the quadrilateral # argument.
else:
else: # Not found, so... else: else: # Nothing found
# Something was found, so we put this block back to todo # together with just found new block.
# Now the interval [c, d] will be of the same block. # It may "crab" other blocks within, and that can be # recursive process. In particular it may also combine to # [a, b] block we just used.
# This removes duplicates from todo.
def find_nontrivial_congruence(self): """ Return a pair that generates non-trivial congruence or ``None`` if there is not any.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram({0: [1, 2], 1: [5], 2: [3, 4], 3: [5], 4: [5]}) sage: H.find_nontrivial_congruence() {{0, 1}, {2, 3, 4, 5}}
sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4], 3: [4]}) sage: H.find_nontrivial_congruence() is None True
ALGORITHM:
See http://www.math.hawaii.edu/~ralph/Preprints/conlat.pdf:
If `\Theta` is a join irreducible element of a `\mathrm{Con}(L)`, then there is at least one join-irreducible `j` and one meet-irreducible `m` such that `\Theta` is both the principal congruence generated by `(j^*, j)`, where `j^*` is the unique lower cover of `j`, and the principal congruence generated by `(m, m^*)`, where `m^*` is the unique upper cover of `m`.
So, we only check join irreducibles or meet irreducibles, whichever is a smaller set. To optimize more we stop computation whenever it founds a pair that we know to generate one-element congruence. """ irr = [(v, self.neighbors_in(v)[0]) for v in join_irreducibles] else:
def principal_congruences_poset(self): r""" Return the poset of join-irreducibles of the congruence lattice.
OUTPUT:
A pair `(P, D)` where `P` is a poset and `D` is a dictionary.
Elements of `P` are pairs `(x, y)` such that `x` is an element of the lattice and `y` is an element covering it. In the poset `(a, b)` is less than `(c, d)` iff the principal congruence generated by `(a, b)` is refinement of the principal congruence generated by `(c, d)`.
`D` is a dictionary from pairs `(x, y)` to the congruence (given as DisjointSet) generated by the pair.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: N5 = HasseDiagram({0: [1, 2], 1: [4], 2: [3], 3: [4]}) sage: P, D = N5.principal_congruences_poset() sage: P Finite poset containing 3 elements sage: P.bottom() (2, 3) sage: D[(2, 3)] {{0}, {1}, {2, 3}, {4}} """
# Select smaller set, meet- or join-irreducibles else:
# Todo: Make a function that creates the poset from a set # by comparison function with minimal number of comparisons.
def congruences_iterator(self): """ Return an iterator over all congruences of the lattice.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram('GY@OQ?OW@?O?') sage: it = H.congruences_iterator(); it <generator object ...> sage: sorted([cong.number_of_subsets() for cong in it]) [1, 2, 2, 2, 4, 4, 4, 8] """
# We have congs[(x,y)], but we want congs[((x,y))].
def is_congruence_normal(self): """ Return ``True`` if the lattice can be constructed from the one-element lattice with Day doubling constructions of convex subsets.
Subsets to double does not need to be lower nor upper pseudo-intervals. On the other hand they must be convex, i.e. doubling a non-convex but municipal subset will give a lattice that returns ``False`` from this function.
EXAMPLES::
sage: from sage.combinat.posets.hasse_diagram import HasseDiagram sage: H = HasseDiagram('IX?Q@?AG?OG?W?O@??') sage: H.is_congruence_normal() True
The 5-element diamond is the smallest non-example::
sage: H = HasseDiagram({0: [1, 2, 3], 1: [4], 2: [4], 3: [4]}) sage: H.is_congruence_normal() False
This is done by doubling a non-convex subset::
sage: H = HasseDiagram('OQC?a?@CO?G_C@?GA?O??_??@?BO?A_?G??C??_?@???') sage: H.is_congruence_normal() False
TESTS::
sage: HasseDiagram().is_congruence_normal() True sage: HasseDiagram({0: []}).is_congruence_normal() True
ALGORITHM:
See http://www.math.hawaii.edu/~jb/inflation.pdf """
from sage.misc.rest_index_of_methods import gen_rest_table_index __doc__ = __doc__.format(INDEX_OF_FUNCTIONS=gen_rest_table_index(HasseDiagram)) |