Skip to content

Commit a2aab87

Browse files
author
Release Manager
committed
sagemathgh-37606: Border matrix <!-- ^ Please provide a concise and informative title. --> <!-- ^ Don't put issue numbers in the title, do this in the PR description below. --> <!-- ^ For example, instead of "Fixes sagemath#12345" use "Introduce new method to calculate 1 + 2". --> <!-- v Describe your changes below in detail. --> <!-- v Why is this change required? What problem does it solve? --> <!-- v If this PR resolves an open issue, please link to it here. For example, "Fixes sagemath#12345". --> Plain TeX users may remember `\bordermatrix`. Here we implement this as options of the `Matrix` class's `str` method. ``` sage: M = matrix([[1,2,3], [4,5,6], [7,8,9]]) sage: M.subdivide(None, 2) sage: print(M.str(unicode=True, ....: top_border=['ab', 'cde', 'f'], ....: bottom_border=['*', '', ''], ....: left_border=[1, 10, 100], ....: right_border=['', ' <', ''])) ab cde f 1⎛ 1 2│ 3⎞ 10⎜ 4 5│ 6⎟ < 100⎝ 7 8│ 9⎠ * ``` Follow-up PR: As the guiding application for this feature, we equip finite-dimensional modules with basis with methods `_repr_matrix`, `_ascii_art_matrix`, `_unicode_art_matrix` that can be used as in this example: ``` sage: M = matrix(ZZ, [[1, 0, 0], [0, 1, 0]], ....: column_keys=['a', 'b', 'c'], ....: row_keys=['v', 'w']); M Generic morphism: From: Free module generated by {'a', 'b', 'c'} over Integer Ring To: Free module generated by {'v', 'w'} over Integer Ring sage: M._unicode_art_ = M._unicode_art_matrix sage: unicode_art(M) # indirect doctest a b c v⎛1 0 0⎞ w⎝0 1 0⎠ ``` ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [ ] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [ ] I have updated the documentation accordingly. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - sagemath#12345: short description why this is a dependency --> <!-- - sagemath#34567: ... --> URL: sagemath#37606 Reported by: Matthias Köppe Reviewer(s): David Coudert, Matthias Köppe
2 parents eadc09d + d45422e commit a2aab87

File tree

2 files changed

+140
-19
lines changed

2 files changed

+140
-19
lines changed

src/sage/matrix/matrix0.pyx

Lines changed: 119 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,7 +1796,9 @@ cdef class Matrix(sage.structure.element.Matrix):
17961796
return self.str()
17971797

17981798
def str(self, rep_mapping=None, zero=None, plus_one=None, minus_one=None,
1799-
*, unicode=False, shape=None, character_art=False):
1799+
*, unicode=False, shape=None, character_art=False,
1800+
left_border=None, right_border=None,
1801+
top_border=None, bottom_border=None):
18001802
r"""
18011803
Return a nice string representation of the matrix.
18021804
@@ -1846,6 +1848,16 @@ cdef class Matrix(sage.structure.element.Matrix):
18461848
:class:`~sage.typeset.unicode_art.UnicodeArt` which support line
18471849
breaking of wide matrices that exceed the window width
18481850
1851+
- ``left_border``, ``right_border`` -- sequence (default: ``None``);
1852+
if not ``None``, call :func:`str` on the elements and use the
1853+
results as labels for the rows of the matrix. The labels appear
1854+
outside of the parentheses.
1855+
1856+
- ``top_border``, ``bottom_border`` -- sequence (default: ``None``);
1857+
if not ``None``, call :func:`str` on the elements and use the
1858+
results as labels for the columns of the matrix. The labels appear
1859+
outside of the parentheses.
1860+
18491861
EXAMPLES::
18501862
18511863
sage: R = PolynomialRing(QQ,6,'z')
@@ -1921,6 +1933,21 @@ cdef class Matrix(sage.structure.element.Matrix):
19211933
[ 0.333333333333333 66.6666666666667]
19221934
[ -3.00000000000000 1.00000000000000e6]
19231935
1936+
Matrices with borders::
1937+
1938+
sage: M = matrix([[1,2,3], [4,5,6], [7,8,9]])
1939+
sage: M.subdivide(None, 2)
1940+
sage: print(M.str(unicode=True,
1941+
....: top_border=['ab', 'cde', 'f'],
1942+
....: bottom_border=['*', '', ''],
1943+
....: left_border=[1, 10, 100],
1944+
....: right_border=['', ' <', '']))
1945+
ab cde f
1946+
1⎛ 1 2│ 3⎞
1947+
10⎜ 4 5│ 6⎟ <
1948+
100⎝ 7 8│ 9⎠
1949+
*
1950+
19241951
TESTS:
19251952
19261953
Prior to :issue:`11544` this could take a full minute to run (2011). ::
@@ -1958,6 +1985,32 @@ cdef class Matrix(sage.structure.element.Matrix):
19581985
sage: matrix(R, [[2/3 - 10^6 * x^3 + 3 * y + O(x, y)^4]])
19591986
[2/3 + 3*y - 1000000*x^3 + O(x, y)^4]
19601987
sage: matrix.options._reset()
1988+
1989+
Edge cases of matrices with borders::
1990+
1991+
sage: print(matrix(ZZ, 0, 0).str(
1992+
....: top_border=[], bottom_border=[], left_border=[], right_border=[]))
1993+
[]
1994+
sage: print(matrix(ZZ, 0, 4).str(
1995+
....: unicode=True,
1996+
....: top_border='abcd', bottom_border=range(4)))
1997+
()
1998+
sage: print(matrix(ZZ, 1, 4).str(
1999+
....: unicode=True,
2000+
....: top_border='abcd', bottom_border=range(4)))
2001+
a b c d
2002+
(0 0 0 0)
2003+
0 1 2 3
2004+
sage: print(matrix(ZZ, 2, 4).str(
2005+
....: unicode=True,
2006+
....: top_border='abcd', bottom_border=range(4), left_border='uv'))
2007+
a b c d
2008+
u⎛0 0 0 0⎞
2009+
v⎝0 0 0 0⎠
2010+
0 1 2 3
2011+
sage: print(matrix(ZZ, 2, 0).str(
2012+
....: top_border='', left_border='uv', right_border=['*', '']))
2013+
[]
19612014
"""
19622015
cdef Py_ssize_t nr, nc, r, c
19632016
nr = self._nrows
@@ -2063,6 +2116,12 @@ cdef class Matrix(sage.structure.element.Matrix):
20632116

20642117
# compute column widths
20652118
S = []
2119+
if top_border is not None:
2120+
for x in top_border:
2121+
S.append(str(x))
2122+
top_count = 1
2123+
else:
2124+
top_count = 0
20662125
for x in entries:
20672126
# Override the usual representations with those specified
20682127
if callable(rep_mapping):
@@ -2075,39 +2134,83 @@ cdef class Matrix(sage.structure.element.Matrix):
20752134
else:
20762135
rep = repr(x)
20772136
S.append(rep)
2137+
if bottom_border is not None:
2138+
for x in bottom_border:
2139+
S.append(str(x))
2140+
bottom_count = 1
2141+
else:
2142+
bottom_count = 0
20782143

20792144
width = max(map(len, S))
2145+
left = []
20802146
rows = []
2147+
right = []
20812148

20822149
hline = cl.join(hl * ((width + 1)*(b - a) - 1)
2083-
for a,b in zip([0] + col_divs, col_divs + [nc]))
2150+
for a,b in zip([0] + col_divs, col_divs + [nc]))
20842151

20852152
# compute rows
2086-
for r from 0 <= r < nr:
2087-
rows += [hline] * row_divs.count(r)
2153+
for r in range(-top_count, nr + bottom_count):
2154+
if 0 <= r < nr:
2155+
n = row_divs.count(r)
2156+
if n:
2157+
left.extend([""] * n)
2158+
rows.extend([hline] * n)
2159+
right.extend([""] * n)
2160+
if left_border is not None and 0 <= r < nr:
2161+
left.append(str(left_border[r]))
2162+
else:
2163+
left.append("")
20882164
s = ""
20892165
for c from 0 <= c < nc:
20902166
if col_div_counts[c]:
2091-
sep = vl * col_div_counts[c]
2167+
if 0 <= r < nr:
2168+
sep = vl * col_div_counts[c]
2169+
else:
2170+
sep = " " * col_div_counts[c]
20922171
elif c == 0:
20932172
sep = ""
20942173
else:
20952174
sep = " "
2096-
entry = S[r * nc + c]
2175+
entry = S[(r + top_count) * nc + c]
20972176
entry = " " * (width - len(entry)) + entry
20982177
s = s + sep + entry
2099-
s = s + vl * col_div_counts[nc]
2178+
else:
2179+
if 0 <= r < nr:
2180+
s = s + vl * col_div_counts[nc]
2181+
else:
2182+
s = s + " " * col_div_counts[nc]
21002183
rows.append(s)
2101-
rows += [hline] * row_divs.count(nr)
2102-
2103-
last_row = len(rows) - 1
2104-
if last_row == 0:
2105-
rows[0] = slb + rows[0] + srb
2184+
if right_border is not None and 0 <= r < nr:
2185+
right.append(str(right_border[r]))
2186+
else:
2187+
right.append("")
2188+
else:
2189+
if nr == nr + bottom_count:
2190+
n = row_divs.count(nr)
2191+
if n:
2192+
left.extend([""] * n)
2193+
rows.extend([hline] * n)
2194+
right.extend([""] * n)
2195+
2196+
# left and right brackets
2197+
for i in range(top_count):
2198+
rows[i] = " "*len(slb) + rows[i] + " "*len(srb)
2199+
if len(rows) == top_count + 1 + bottom_count:
2200+
rows[top_count] = slb + rows[top_count] + srb
21062201
else:
2107-
rows[0] = tlb + rows[0] + trb
2108-
for r from 1 <= r < last_row:
2109-
rows[r] = mlb + rows[r] + mrb
2110-
rows[last_row] = blb + rows[last_row] + brb
2202+
rows[top_count] = tlb + rows[top_count] + trb
2203+
for i in range(top_count + 1, len(rows) - bottom_count - 1):
2204+
rows[i] = mlb + rows[i] + mrb
2205+
rows[-1 - bottom_count] = blb + rows[-1 - bottom_count] + brb
2206+
for i in range(bottom_count):
2207+
rows[-1 - i] = " "*len(slb) + rows[-1 - i] + " "*len(srb)
2208+
2209+
# left and right border
2210+
left_width = max(len(s) for s in left)
2211+
right_width = max(len(s) for s in right)
2212+
for i in range(len(rows)):
2213+
rows[i] = left[i].rjust(left_width) + rows[i] + right[i].rjust(right_width)
21112214

21122215
if character_art:
21132216
breakpoints = []

src/sage/matrix/matrix_mod2_dense.pyx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,9 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse
346346

347347

348348
def str(self, rep_mapping=None, zero=None, plus_one=None, minus_one=None,
349-
*, unicode=False, shape=None, character_art=False):
349+
*, unicode=False, shape=None, character_art=False,
350+
left_border=None, right_border=None,
351+
top_border=None, bottom_border=None):
350352
r"""
351353
Return a nice string representation of the matrix.
352354
@@ -384,6 +386,16 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse
384386
:class:`~sage.typeset.unicode_art.UnicodeArt` which support line
385387
breaking of wide matrices that exceed the window width
386388
389+
- ``left_border``, ``right_border`` -- sequence (default: ``None``);
390+
if not ``None``, call :func:`str` on the elements and use the
391+
results as labels for the rows of the matrix. The labels appear
392+
outside of the parentheses.
393+
394+
- ``top_border``, ``bottom_border`` -- sequence (default: ``None``);
395+
if not ``None``, call :func:`str` on the elements and use the
396+
results as labels for the columns of the matrix. The labels appear
397+
outside of the parentheses.
398+
387399
EXAMPLES::
388400
389401
sage: B = matrix(GF(2), 3, 3, [0, 1, 0, 0, 1, 1, 0, 0, 0])
@@ -416,13 +428,19 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse
416428
# Set the mapping based on keyword arguments
417429
# We ignore minus_one (it's only there for compatibility with Matrix)
418430
if (rep_mapping is not None or zero is not None or plus_one is not None
419-
or unicode or shape is not None or character_art):
431+
or unicode or shape is not None or character_art
432+
or left_border is not None or right_border is not None
433+
or top_border is not None or bottom_border is not None):
420434
# Shunt mappings off to the generic code since they might not be
421435
# single characters
422436
return matrix_dense.Matrix_dense.str(self, rep_mapping=rep_mapping,
423437
zero=zero, plus_one=plus_one,
424438
unicode=unicode, shape=shape,
425-
character_art=character_art)
439+
character_art=character_art,
440+
left_border=left_border,
441+
right_border=right_border,
442+
top_border=top_border,
443+
bottom_border=bottom_border)
426444

427445
if self._nrows == 0 or self._ncols == 0:
428446
return "[]"

0 commit comments

Comments
 (0)