Skip to content

Commit e4e37c1

Browse files
committed
implement doubly_lexical_ordering in 01-matrix
1 parent 276aa66 commit e4e37c1

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed

src/doc/en/reference/references/index.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ REFERENCES:
269269
finite Drinfeld modules.* manuscripta mathematica 93, 1 (01 Aug 1997),
270270
369–379. https://doi.org/10.1007/BF02677478
271271
272+
.. [Anna1987] Lubiw, Anna. Doubly Lexical Orderings of Matrices.
273+
SIAM Journal on Computing 16.5 (1987): 854-879.
274+
272275
.. [ANR2023] Robert Angarone, Anastasia Nathanson, and Victor Reiner. *Chow rings of
273276
matroids as permutation representations*, 2023. :arxiv:`2309.14312`.
274277
@@ -3421,6 +3424,10 @@ REFERENCES:
34213424
.. [Hoc] Winfried Hochstaettler, "About the Tic-Tac-Toe Matroid",
34223425
preprint.
34233426
3427+
.. [Hoffman1985] Hoffman, Alan J., Anthonius Wilhelmus Johannes Kolen, and Michel Sakarovitch.
3428+
Totally-balanced and greedy matrices.
3429+
SIAM Journal on Algebraic Discrete Methods 6.4 (1985): 721-730.
3430+
34243431
.. [HJ18] Thorsten Holm and Peter Jorgensen
34253432
*A p-angulated generalisation of Conway and Coxeter's theorem on frieze patterns*,
34263433
International Mathematics Research Notices (2018)

src/sage/matrix/matrix_mod2_dense.pyx

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2132,6 +2132,169 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse
21322132
verbose("done computing right kernel matrix over integers mod 2 for %sx%s matrix" % (self.nrows(), self.ncols()),level=1, t=tm)
21332133
return 'computed-pluq', M
21342134

2135+
def doubly_lexical_ordering(self, inplace=False):
2136+
r"""
2137+
Return a doubly lexical ordering of the matrix.
2138+
2139+
A doubly lexical ordering of a matrix is an ordering of the rows
2140+
and of the columns of the matrix so that both the rows and the
2141+
columns, as vectors, are lexically increasing. See [Anna1987]_.
2142+
A lexical ordering of vectors is the standard dictionary ordering,
2143+
except that vectors will be read from highest to lowest coordinate.
2144+
Thus row vectors will be compared from right to left, and column
2145+
vectors from bottom to top.
2146+
2147+
INPUT:
2148+
2149+
- ``inplace`` -- boolean (default: ``False``); using ``inplace=True``
2150+
will permute the rows and columns of the current matrix
2151+
according to a doubly lexical ordering. This will modify the matrix.
2152+
2153+
OUTPUT:
2154+
2155+
Returns a pair (``row_ordering``, ``col_ordering``). Each item
2156+
is a ``PermutationGroupElement`` that represents a doubly lexical
2157+
ordering of the rows or columns.
2158+
2159+
.. SEEALSO::
2160+
2161+
- :meth:`~sage.matrix.matrix2.Matrix.permutation_normal_form` --
2162+
a similar matrix normal form
2163+
2164+
ALGORITHM:
2165+
2166+
The algorithm is adapted from section 3 of [Hoffman1985]_. The time
2167+
complexity of this algorithm is `O(n \cdot m^2)` for a `n \times m`
2168+
matrix.
2169+
2170+
EXAMPLES:
2171+
2172+
sage: A = Matrix(GF(2), [
2173+
....: [0, 1],
2174+
....: [1, 0]])
2175+
sage: r, c = A.doubly_lexical_ordering()
2176+
sage: r
2177+
(1,2)
2178+
sage: c
2179+
()
2180+
sage: A.permute_rows_and_columns(r, c); A
2181+
[1 0]
2182+
[0 1]
2183+
2184+
::
2185+
2186+
sage: A = Matrix(GF(2), [
2187+
....: [0, 1],
2188+
....: [1, 0]])
2189+
sage: r, c = A.doubly_lexical_ordering(inplace=True); A
2190+
[1 0]
2191+
[0 1]
2192+
2193+
TESTS:
2194+
2195+
sage: A = Matrix(GF(2), [
2196+
....: [1, 1, 0, 0, 0, 0, 0],
2197+
....: [1, 1, 0, 0, 0, 0, 0],
2198+
....: [1, 1, 0, 1, 0, 0, 0],
2199+
....: [0, 0, 1, 1, 0, 0, 0],
2200+
....: [0, 1, 1, 1, 1, 0, 0],
2201+
....: [0, 0, 0, 0, 0, 1, 1],
2202+
....: [0, 0, 0, 0, 0, 1, 1],
2203+
....: [0, 0, 0, 0, 1, 1, 1],
2204+
....: [0, 0, 0, 1, 1, 1, 0]])
2205+
sage: r, c = A.doubly_lexical_ordering()
2206+
sage: B = A.with_permuted_rows_and_columns(r, c)
2207+
sage: flag = True
2208+
sage: for i in range(B.ncols()):
2209+
....: for j in range(i):
2210+
....: for k in reversed(range(B.nrows())):
2211+
....: if B[k][j] > B[k][i]:
2212+
....: flag = False
2213+
....: break
2214+
....: if B[k][j] < B[k][i]:
2215+
....: break
2216+
....:
2217+
sage: for i in range(B.nrows()):
2218+
....: for j in range(i):
2219+
....: for k in reversed(range(B.ncols())):
2220+
....: if B[j][k] > B[i][k]:
2221+
....: flag = False
2222+
....: break
2223+
....: if B[j][k] < B[i][k]:
2224+
....: break
2225+
....:
2226+
sage: flag
2227+
True
2228+
sage: r, c = A.doubly_lexical_ordering(inplace=True)
2229+
sage: A == B
2230+
True
2231+
2232+
"""
2233+
2234+
partition_rows = [False for _ in range(self._nrows - 1)]
2235+
partition_num = 1
2236+
row_swapped = list(range(1, self._nrows + 1))
2237+
col_swapped = list(range(1, self._ncols + 1))
2238+
2239+
cdef Matrix_mod2_dense A = self if inplace else self.__copy__()
2240+
2241+
for i in reversed(range(1, A._ncols + 1)):
2242+
2243+
# count 1 for each partition and column
2244+
count1 = [[0 for _ in range(partition_num)] for _ in range(i)]
2245+
for col in range(i):
2246+
parition_i = 0
2247+
for row in reversed(range(A._nrows)):
2248+
count1[col][parition_i] += 1 if mzd_read_bit(A._entries, row, col) else 0
2249+
if row > 0 and partition_rows[row - 1]:
2250+
parition_i += 1
2251+
2252+
# calculate largest_col = col s.t. count1[col] is lexicographically largest (0 <= col < i)
2253+
largest_col = 0
2254+
largest_count1 = count1[0]
2255+
for col in range(1, i):
2256+
if count1[col] >= largest_count1:
2257+
largest_col = col
2258+
largest_count1 = count1[col]
2259+
2260+
# We refine each partition of rows according to the value of A[:][largest_col].
2261+
# and also move down rows that satisfy A[row][largest_col] = 1 in each partition.
2262+
partition_start = 0
2263+
for _ in range(partition_num):
2264+
partition_end = partition_start
2265+
while partition_end < A._nrows - 1 and not partition_rows[partition_end]:
2266+
partition_end += 1
2267+
row_start = partition_start
2268+
row_end = partition_end
2269+
while row_start < row_end:
2270+
while row_start < row_end and not mzd_read_bit(A._entries, row_start, largest_col):
2271+
row_start += 1
2272+
while row_start < row_end and mzd_read_bit(A._entries, row_end, largest_col):
2273+
row_end -= 1
2274+
if row_start < row_end: # swap row
2275+
A.swap_rows_c(row_start, row_end)
2276+
row_swapped[row_start], row_swapped[row_end] = row_swapped[row_end], row_swapped[row_start]
2277+
partition_start = partition_end + 1 # for next partition
2278+
2279+
for row in range(A._nrows - 1):
2280+
if mzd_read_bit(A._entries, row, largest_col) != mzd_read_bit(A._entries, row + 1, largest_col):
2281+
if not partition_rows[row]:
2282+
partition_rows[row] = True
2283+
partition_num += 1
2284+
2285+
# swap column
2286+
A.swap_columns_c(largest_col, i - 1)
2287+
col_swapped[largest_col], col_swapped[i - 1] = col_swapped[i - 1], col_swapped[largest_col]
2288+
2289+
from sage.groups.perm_gps.permgroup_named import SymmetricGroup
2290+
from sage.groups.perm_gps.permgroup_element import make_permgroup_element_v2
2291+
symmetric_group_nrows = SymmetricGroup(self._nrows)
2292+
symmetric_group_ncols = SymmetricGroup(self._ncols)
2293+
row_ordering = make_permgroup_element_v2(symmetric_group_nrows, row_swapped, symmetric_group_nrows.domain())
2294+
col_ordering = make_permgroup_element_v2(symmetric_group_ncols, col_swapped, symmetric_group_ncols.domain())
2295+
2296+
return row_ordering, col_ordering
2297+
21352298
# Used for hashing
21362299
cdef int i, k
21372300
cdef unsigned long parity_table[256]

0 commit comments

Comments
 (0)