Skip to content
This repository was archived by the owner on Feb 1, 2023. It is now read-only.

Commit 4c8a1ec

Browse files
author
Release Manager
committed
Trac #28447: posets: introduce a boolean lequal_matrix
Mainly for better speed and also smaller memory usage. * New lazy attributes of Hasse diagrams: {{{_leq_storage}}} and {{{_leq_matrix_boolean}}}. The first one contains a list of sets describing the comparison relation. The second attribute now calls this data. * Previous lazy attribute {{{_leq_matrix}}} is computed from the {{{_leq_storage}}} data. * method {{{lequal_matrix}}} calls either, with unchanged default behaviour * New algorithm for {{{_leq_storage}}} that tries to outsmart the naive transitive closure, by leveraging the poset structure and the known linear extension URL: https://trac.sagemath.org/28447 Reported by: chapoton Ticket author(s): Frédéric Chapoton Reviewer(s): Travis Scrimshaw
2 parents b9d913a + 500b2d8 commit 4c8a1ec

File tree

2 files changed

+121
-35
lines changed

2 files changed

+121
-35
lines changed

src/sage/combinat/posets/hasse_diagram.py

Lines changed: 113 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from sage.graphs.digraph import DiGraph
2323
from sage.matrix.constructor import matrix
2424
from sage.rings.integer_ring import ZZ
25+
from sage.rings.finite_rings.finite_field_constructor import GF
2526
from sage.misc.lazy_attribute import lazy_attribute
2627
from sage.misc.cachefunc import cached_method
2728
from sage.misc.rest_index_of_methods import gen_rest_table_index
@@ -300,7 +301,7 @@ def is_lequal(self, i, j):
300301
.. note::
301302
302303
If the :meth:`lequal_matrix` has been computed, then this method is
303-
redefined to use the cached matrix (see :meth:`_alternate_is_lequal`).
304+
redefined to use the cached data (see :meth:`_alternate_is_lequal`).
304305
305306
TESTS::
306307
@@ -974,7 +975,12 @@ def moebius_function_matrix(self, algorithm='recursive'):
974975
975976
.. TODO::
976977
977-
Use an inversion algorithm for triangular matrices.
978+
Try to make a specific multimodular matrix inversion
979+
algorithm for this kind of sparse triangular matrices
980+
where the non-zero entries of the inverse are in known
981+
positions.
982+
983+
.. SEEALSO:: :meth:`lequal_matrix`, :meth:`coxeter_transformation`
978984
979985
EXAMPLES::
980986
@@ -1011,10 +1017,11 @@ def moebius_function_matrix(self, algorithm='recursive'):
10111017
if not hasattr(self, '_moebius_function_matrix'):
10121018
if algorithm == 'recursive':
10131019
n = self.cardinality()
1014-
L = self.lequal_matrix()
1015-
m = L.dict(copy=True)
1016-
greater_than = [sorted(L[i].dict()) for i in range(n)]
1020+
gt = self._leq_storage
1021+
greater_than = [sorted(gt[i]) for i in range(n)]
1022+
m = {}
10171023
for i in range(n - 1, -1, -1):
1024+
m[(i, i)] = ZZ.one()
10181025
for k in greater_than[i]:
10191026
if k != i:
10201027
m[(i, k)] = -ZZ.sum(m[(j, k)]
@@ -1057,6 +1064,8 @@ def coxeter_transformation(self):
10571064
the Grothendieck group of the derived category of modules on the
10581065
poset, in the basis of simple modules.
10591066
1067+
.. SEEALSO:: :meth:`lequal_matrix`, :meth:`moebius_function_matrix`
1068+
10601069
EXAMPLES::
10611070
10621071
sage: M = posets.PentagonPoset()._hasse_diagram.coxeter_transformation(); M
@@ -1130,17 +1139,45 @@ def principal_order_ideal(self, i):
11301139
return self.order_ideal([i])
11311140

11321141
@lazy_attribute
1133-
def _leq_matrix(self):
1142+
def _leq_storage(self):
1143+
"""
1144+
Store the comparison relation as a list of Python sets.
1145+
1146+
The `i`-th item in the list is the set of elements greater than `i`.
1147+
1148+
EXAMPLES::
1149+
1150+
sage: H = posets.DiamondPoset(7)._hasse_diagram
1151+
sage: H._leq_storage
1152+
[{0, 1, 2, 3, 4, 5, 6}, {1, 6}, {2, 6}, {3, 6}, {4, 6}, {5, 6}, {6}]
1153+
"""
1154+
n = self.order()
1155+
greater_than = [set([i]) for i in range(n)]
1156+
for i in range(n - 1, -1, -1):
1157+
gt = greater_than[i]
1158+
for j in self.neighbors_out(i):
1159+
gt = gt.union(greater_than[j])
1160+
greater_than[i] = gt
1161+
1162+
# Redefine self.is_lequal
1163+
self.is_lequal = self._alternate_is_lequal
1164+
1165+
return greater_than
1166+
1167+
@lazy_attribute
1168+
def _leq_matrix_boolean(self):
11341169
r"""
1135-
Computes a matrix whose ``(i,j)`` entry is 1 if ``i`` is less than
1136-
``j`` in the poset, and 0 otherwise; and redefines ``__lt__`` to
1137-
use this matrix.
1170+
Compute a boolean matrix whose ``(i,j)`` entry is 1 if ``i``
1171+
is less than ``j`` in the poset, and 0 otherwise; and
1172+
redefines ``__lt__`` to use this matrix.
1173+
1174+
.. SEEALSO:: :meth:`_leq_matrix`
11381175
11391176
EXAMPLES::
11401177
11411178
sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])
11421179
sage: H = P._hasse_diagram
1143-
sage: H._leq_matrix
1180+
sage: M = H._leq_matrix_boolean; M
11441181
[1 1 1 1 1 1 1 1]
11451182
[0 1 0 1 0 0 0 1]
11461183
[0 0 1 1 1 0 1 1]
@@ -1149,32 +1186,70 @@ def _leq_matrix(self):
11491186
[0 0 0 0 0 1 1 1]
11501187
[0 0 0 0 0 0 1 1]
11511188
[0 0 0 0 0 0 0 1]
1189+
sage: M.base_ring()
1190+
Finite Field of size 2
1191+
"""
1192+
n = self.order()
1193+
R = GF(2)
1194+
one = R.one()
1195+
greater_than = self._leq_storage
1196+
D = {(i, j): one for i in range(n) for j in greater_than[i]}
1197+
M = matrix(R, n, n, D, sparse=True)
1198+
M.set_immutable()
1199+
return M
1200+
1201+
@lazy_attribute
1202+
def _leq_matrix(self):
1203+
r"""
1204+
Compute an integer matrix whose ``(i,j)`` entry is 1 if ``i``
1205+
is less than ``j`` in the poset, and 0 otherwise.
11521206
1207+
.. SEEALSO:: :meth:`_leq_matrix_boolean`
1208+
1209+
EXAMPLES::
1210+
1211+
sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])
1212+
sage: H = P._hasse_diagram
1213+
sage: M = H._leq_matrix; M
1214+
[1 1 1 1 1 1 1 1]
1215+
[0 1 0 1 0 0 0 1]
1216+
[0 0 1 1 1 0 1 1]
1217+
[0 0 0 1 0 0 0 1]
1218+
[0 0 0 0 1 0 0 1]
1219+
[0 0 0 0 0 1 1 1]
1220+
[0 0 0 0 0 0 1 1]
1221+
[0 0 0 0 0 0 0 1]
1222+
sage: M.base_ring()
1223+
Integer Ring
11531224
"""
1154-
# Create the matrix
11551225
n = self.order()
1156-
D = {}
1157-
for i in range(n):
1158-
for v in self.breadth_first_search(i):
1159-
D[(i, v)] = 1
1226+
one = ZZ.one()
1227+
greater_than = self._leq_storage
1228+
D = {(i, j): one for i in range(n) for j in greater_than[i]}
11601229
M = matrix(ZZ, n, n, D, sparse=True)
11611230
M.set_immutable()
1162-
# Redefine self.is_lequal
1163-
self.is_lequal = self._alternate_is_lequal
1164-
# Return the matrix
11651231
return M
11661232

1167-
def lequal_matrix(self):
1233+
def lequal_matrix(self, boolean=False):
11681234
"""
1169-
Return the matrix whose ``(i,j)`` entry is 1 if ``i`` is less
1235+
Return a matrix whose ``(i,j)`` entry is 1 if ``i`` is less
11701236
than ``j`` in the poset, and 0 otherwise; and redefines
1171-
``__lt__`` to use this matrix.
1237+
``__lt__`` to use the boolean version of this matrix.
1238+
1239+
INPUT:
1240+
1241+
- ``boolean`` -- optional flag (default ``False``) telling whether to
1242+
return a matrix with coefficients in `\GF(2)` or in `\ZZ`
1243+
1244+
.. SEEALSO::
1245+
1246+
:meth:`moebius_function_matrix`, :meth:`coxeter_transformation`
11721247
11731248
EXAMPLES::
11741249
11751250
sage: P = Poset([[1,3,2],[4],[4,5,6],[6],[7],[7],[7],[]])
11761251
sage: H = P._hasse_diagram
1177-
sage: H.lequal_matrix()
1252+
sage: M = H.lequal_matrix(); M
11781253
[1 1 1 1 1 1 1 1]
11791254
[0 1 0 1 0 0 0 1]
11801255
[0 0 1 1 1 0 1 1]
@@ -1183,13 +1258,24 @@ def lequal_matrix(self):
11831258
[0 0 0 0 0 1 1 1]
11841259
[0 0 0 0 0 0 1 1]
11851260
[0 0 0 0 0 0 0 1]
1261+
sage: M.base_ring()
1262+
Integer Ring
1263+
1264+
sage: P = posets.DiamondPoset(6)
1265+
sage: H = P._hasse_diagram
1266+
sage: M = H.lequal_matrix(boolean=True)
1267+
sage: M.base_ring()
1268+
Finite Field of size 2
11861269
11871270
TESTS::
11881271
11891272
sage: H.lequal_matrix().is_immutable()
11901273
True
11911274
"""
1192-
return self._leq_matrix
1275+
if boolean:
1276+
return self._leq_matrix_boolean
1277+
else:
1278+
return self._leq_matrix
11931279

11941280
def _alternate_is_lequal(self, i, j):
11951281
r"""
@@ -1199,7 +1285,7 @@ def _alternate_is_lequal(self, i, j):
11991285
.. NOTE::
12001286
12011287
If the :meth:`lequal_matrix` has been computed, then
1202-
:meth:`is_lequal` is redefined to use the cached matrix.
1288+
:meth:`is_lequal` is redefined to use the cached data.
12031289
12041290
EXAMPLES::
12051291
@@ -1223,7 +1309,7 @@ def _alternate_is_lequal(self, i, j):
12231309
sage: H._alternate_is_lequal(z,z)
12241310
True
12251311
"""
1226-
return bool(self._leq_matrix[i, j])
1312+
return j in self._leq_storage[i]
12271313

12281314
def prime_elements(self):
12291315
r"""
@@ -2026,7 +2112,7 @@ def are_incomparable(self, i, j):
20262112
sage: [ (i,j) for i in H.vertices() for j in H.vertices() if H.are_incomparable(i,j)]
20272113
[(1, 2), (1, 3), (2, 1), (3, 1)]
20282114
"""
2029-
mat = self._leq_matrix
2115+
mat = self._leq_matrix_boolean
20302116
return not mat[i, j] and not mat[j, i]
20312117

20322118
def are_comparable(self, i, j):
@@ -2046,7 +2132,7 @@ def are_comparable(self, i, j):
20462132
sage: [ (i,j) for i in H.vertices() for j in H.vertices() if H.are_comparable(i,j)]
20472133
[(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)]
20482134
"""
2049-
mat = self._leq_matrix
2135+
mat = self._leq_matrix_boolean
20502136
return bool(mat[i, j]) or bool(mat[j, i])
20512137

20522138
def antichains(self, element_class=list):

src/sage/combinat/posets/posets.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3597,7 +3597,7 @@ def cardinality(self):
35973597
"""
35983598
return Integer(self._hasse_diagram.order())
35993599

3600-
def moebius_function(self,x,y):
3600+
def moebius_function(self, x, y):
36013601
r"""
36023602
Return the value of the Möbius function of the poset on the
36033603
elements x and y.
@@ -3630,12 +3630,12 @@ def moebius_function(self,x,y):
36303630
sage: sum([Q.moebius_function(Q(0),v) for v in Q])
36313631
0
36323632
"""
3633-
i,j = map(self._element_to_vertex,(x,y))
3634-
return self._hasse_diagram.moebius_function(i,j)
3633+
i, j = map(self._element_to_vertex, (x, y))
3634+
return self._hasse_diagram.moebius_function(i, j)
36353635

3636-
def moebius_function_matrix(self, ring = ZZ, sparse = False):
3636+
def moebius_function_matrix(self, ring=ZZ, sparse=False):
36373637
r"""
3638-
Returns a matrix whose ``(i,j)`` entry is the value of the Möbius
3638+
Return a matrix whose ``(i,j)`` entry is the value of the Möbius
36393639
function evaluated at ``self.linear_extension()[i]`` and
36403640
``self.linear_extension()[j]``.
36413641
@@ -3674,9 +3674,9 @@ def moebius_function_matrix(self, ring = ZZ, sparse = False):
36743674
M = M.dense_matrix()
36753675
return M
36763676

3677-
def lequal_matrix(self, ring = ZZ, sparse = False):
3677+
def lequal_matrix(self, ring=ZZ, sparse=False):
36783678
"""
3679-
Computes the matrix whose ``(i,j)`` entry is 1 if
3679+
Compute the matrix whose ``(i,j)`` entry is 1 if
36803680
``self.linear_extension()[i] < self.linear_extension()[j]`` and 0
36813681
otherwise.
36823682
@@ -3712,7 +3712,7 @@ def lequal_matrix(self, ring = ZZ, sparse = False):
37123712
sage: P.lequal_matrix(ring=QQ, sparse=False).parent()
37133713
Full MatrixSpace of 8 by 8 dense matrices over Rational Field
37143714
"""
3715-
M = self._hasse_diagram.lequal_matrix()
3715+
M = self._hasse_diagram.lequal_matrix(boolean=False)
37163716
if ring is not ZZ:
37173717
M = M.change_ring(ring)
37183718
if not sparse:

0 commit comments

Comments
 (0)