Hide keyboard shortcuts

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

""" 

Hasse diagrams of finite atomic and coatomic lattices. 

 

This module provides the function 

:func:`Hasse_diagram_from_incidences` for computing Hasse diagrams of 

finite atomic and coatomic lattices in the sense of partially ordered 

sets where any two elements have meet and joint. For example, the face 

lattice of a polyhedron. 

""" 

 

#***************************************************************************** 

# Copyright (C) 2010 Andrey Novoseltsev <novoselt@gmail.com> 

# 

# Distributed under the terms of the GNU General Public License (GPL) 

# 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

from __future__ import print_function 

 

 

 

 

 

def Hasse_diagram_from_incidences(atom_to_coatoms, coatom_to_atoms, 

face_constructor=None, 

required_atoms=None, 

key = None, 

**kwds): 

r""" 

Compute the Hasse diagram of an atomic and coatomic lattice. 

 

INPUT: 

 

- ``atom_to_coatoms`` -- list, ``atom_to_coatom[i]`` should list all 

coatoms over the ``i``-th atom; 

 

- ``coatom_to_atoms`` -- list, ``coatom_to_atom[i]`` should list all 

atoms under the ``i``-th coatom; 

 

- ``face_constructor`` -- function or class taking as the first two 

arguments sorted :class:`tuple` of integers and any keyword arguments. 

It will be called to construct a face over atoms passed as the first 

argument and under coatoms passed as the second argument. Default 

implementation will just return these two tuples as a tuple; 

 

- ``required_atoms`` -- list of atoms (default:None). Each 

non-empty "face" requires at least on of the specified atoms 

present. Used to ensure that each face has a vertex. 

 

- ``key`` -- any hashable value (default: None). It is passed down 

to :class:`~sage.combinat.posets.posets.FinitePoset`. 

 

- all other keyword arguments will be passed to ``face_constructor`` on 

each call. 

 

OUTPUT: 

 

- :class:`finite poset <sage.combinat.posets.posets.FinitePoset>` with 

elements constructed by ``face_constructor``. 

 

.. NOTE:: 

 

In addition to the specified partial order, finite posets in Sage have 

internal total linear order of elements which extends the partial one. 

This function will try to make this internal order to start with the 

bottom and atoms in the order corresponding to ``atom_to_coatoms`` and 

to finish with coatoms in the order corresponding to 

``coatom_to_atoms`` and the top. This may not be possible if atoms and 

coatoms are the same, in which case the preference is given to the 

first list. 

 

ALGORITHM: 

 

The detailed description of the used algorithm is given in [KP2002]_. 

 

The code of this function follows the pseudo-code description in the 

section 2.5 of the paper, although it is mostly based on frozen sets 

instead of sorted lists - this makes the implementation easier and should 

not cost a big performance penalty. (If one wants to make this function 

faster, it should be probably written in Cython.) 

 

While the title of the paper mentions only polytopes, the algorithm (and 

the implementation provided here) is applicable to any atomic and coatomic 

lattice if both incidences are given, see Section 3.4. 

 

In particular, this function can be used for strictly convex cones and 

complete fans. 

 

REFERENCES: [KP2002]_ 

 

AUTHORS: 

 

- Andrey Novoseltsev (2010-05-13) with thanks to Marshall Hampton for the 

reference. 

 

EXAMPLES: 

 

Let's construct the Hasse diagram of a lattice of subsets of {0, 1, 2}. 

Our atoms are {0}, {1}, and {2}, while our coatoms are {0,1}, {0,2}, and 

{1,2}. Then incidences are :: 

 

sage: atom_to_coatoms = [(0,1), (0,2), (1,2)] 

sage: coatom_to_atoms = [(0,1), (0,2), (1,2)] 

 

and we can compute the Hasse diagram as :: 

 

sage: L = sage.geometry.cone.Hasse_diagram_from_incidences( 

....: atom_to_coatoms, coatom_to_atoms) 

sage: L 

Finite poset containing 8 elements with distinguished linear extension 

sage: for level in L.level_sets(): print(level) 

[((), (0, 1, 2))] 

[((0,), (0, 1)), ((1,), (0, 2)), ((2,), (1, 2))] 

[((0, 1), (0,)), ((0, 2), (1,)), ((1, 2), (2,))] 

[((0, 1, 2), ())] 

 

For more involved examples see the *source code* of 

:meth:`sage.geometry.cone.ConvexRationalPolyhedralCone.face_lattice` and 

:meth:`sage.geometry.fan.RationalPolyhedralFan._compute_cone_lattice`. 

""" 

from sage.graphs.digraph import DiGraph 

from sage.combinat.posets.posets import FinitePoset 

def default_face_constructor(atoms, coatoms, **kwds): 

return (atoms, coatoms) 

if face_constructor is None: 

face_constructor = default_face_constructor 

atom_to_coatoms = [frozenset(atc) for atc in atom_to_coatoms] 

A = frozenset(range(len(atom_to_coatoms))) # All atoms 

coatom_to_atoms = [frozenset(cta) for cta in coatom_to_atoms] 

C = frozenset(range(len(coatom_to_atoms))) # All coatoms 

# Comments with numbers correspond to steps in Section 2.5 of the article 

L = DiGraph(1) # 3: initialize L 

faces = dict() 

atoms = frozenset() 

coatoms = C 

faces[atoms, coatoms] = 0 

next_index = 1 

Q = [(atoms, coatoms)] # 4: initialize Q with the empty face 

while Q: # 5 

q_atoms, q_coatoms = Q.pop() # 6: remove some q from Q 

q = faces[q_atoms, q_coatoms] 

# 7: compute H = {closure(q+atom) : atom not in atoms of q} 

H = dict() 

candidates = set(A.difference(q_atoms)) 

for atom in candidates: 

coatoms = q_coatoms.intersection(atom_to_coatoms[atom]) 

atoms = A 

for coatom in coatoms: 

atoms = atoms.intersection(coatom_to_atoms[coatom]) 

H[atom] = (atoms, coatoms) 

# 8: compute the set G of minimal sets in H 

minimals = set([]) 

while candidates: 

candidate = candidates.pop() 

atoms = H[candidate][0] 

if atoms.isdisjoint(candidates) and atoms.isdisjoint(minimals): 

minimals.add(candidate) 

# Now G == {H[atom] : atom in minimals} 

for atom in minimals: # 9: for g in G: 

g_atoms, g_coatoms = H[atom] 

if not required_atoms is None: 

if g_atoms.isdisjoint(required_atoms): 

continue 

if (g_atoms, g_coatoms) in faces: 

g = faces[g_atoms, g_coatoms] 

else: # 11: if g was newly created 

g = next_index 

faces[g_atoms, g_coatoms] = g 

next_index += 1 

Q.append((g_atoms, g_coatoms)) # 12 

L.add_edge(q, g) # 14 

# End of algorithm, now construct a FinitePoset. 

# In principle, it is recommended to use Poset or in this case perhaps 

# even LatticePoset, but it seems to take several times more time 

# than the above computation, makes unnecessary copies, and crashes. 

# So for now we will mimic the relevant code from Poset. 

 

# Enumeration of graph vertices must be a linear extension of the poset 

new_order = L.topological_sort() 

# Make sure that coatoms are in the end in proper order 

tail = [faces[atoms, frozenset([coatom])] 

for coatom, atoms in enumerate(coatom_to_atoms)] 

tail.append(faces[A, frozenset()]) 

new_order = [n for n in new_order if n not in tail] + tail 

# Make sure that atoms are in the beginning in proper order 

head = [0] # We know that the empty face has index 0 

head.extend(faces[frozenset([atom]), coatoms] 

for atom, coatoms in enumerate(atom_to_coatoms) 

if required_atoms is None or atom in required_atoms) 

new_order = head + [n for n in new_order if n not in head] 

# "Invert" this list to a dictionary 

labels = dict() 

for new, old in enumerate(new_order): 

labels[old] = new 

L.relabel(labels) 

# Construct the actual poset elements 

elements = [None] * next_index 

for face, index in faces.items(): 

atoms, coatoms = face 

elements[labels[index]] = face_constructor( 

tuple(sorted(atoms)), tuple(sorted(coatoms)), **kwds) 

D = {i:f for i,f in enumerate(elements)} 

L.relabel(D) 

return FinitePoset(L, elements, key = key)