Skip to content

Commit eb4f55c

Browse files
author
Release Manager
committed
gh-40811: Fixes incorrect jordan form issue <!-- ^ 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 #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 #12345". --> Fixes #40803. Maxima jordan form was being incorrectly parsed by sage for matrices with repeated eigen values, leading to issue with jordan form subdivisions and hence with symbolic matrix power. ### 📝 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. - [x] I have linked a relevant issue or discussion. - [x] I have created tests covering the changes. - [x] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #40811 Reported by: Sahil Jain Reviewer(s): Sahil Jain, user202729
2 parents d2e4be5 + 33223d1 commit eb4f55c

File tree

5 files changed

+103
-44
lines changed

5 files changed

+103
-44
lines changed

src/sage/interfaces/interface.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -389,47 +389,47 @@ def new(self, code):
389389
# these should all be appropriately overloaded by the derived class
390390
###################################################################
391391

392-
def _left_list_delim(self):
392+
def _left_list_delim(self) -> str:
393393
return "["
394394

395-
def _right_list_delim(self):
395+
def _right_list_delim(self) -> str:
396396
return "]"
397397

398-
def _left_func_delim(self):
398+
def _left_func_delim(self) -> str:
399399
return "("
400400

401-
def _right_func_delim(self):
401+
def _right_func_delim(self) -> str:
402402
return ")"
403403

404-
def _assign_symbol(self):
404+
def _assign_symbol(self) -> str:
405405
return "="
406406

407-
def _equality_symbol(self):
407+
def _equality_symbol(self) -> str:
408408
raise NotImplementedError
409409

410410
# For efficiency purposes, you should definitely override these
411411
# in your derived class.
412-
def _true_symbol(self):
412+
def _true_symbol(self) -> str:
413413
try:
414414
return self.__true_symbol
415415
except AttributeError:
416416
self.__true_symbol = self.get('1 %s 1' % self._equality_symbol())
417417
return self.__true_symbol
418418

419-
def _false_symbol(self):
419+
def _false_symbol(self) -> str:
420420
try:
421421
return self.__false_symbol
422422
except AttributeError:
423423
self.__false_symbol = self.get('1 %s 2' % self._equality_symbol())
424424
return self.__false_symbol
425425

426-
def _lessthan_symbol(self):
426+
def _lessthan_symbol(self) -> str:
427427
return '<'
428428

429-
def _greaterthan_symbol(self):
429+
def _greaterthan_symbol(self) -> str:
430430
return '>'
431431

432-
def _inequality_symbol(self):
432+
def _inequality_symbol(self) -> str:
433433
return '!='
434434

435435
def _relation_symbols(self):
@@ -451,7 +451,7 @@ def _relation_symbols(self):
451451
operator.gt: self._greaterthan_symbol(),
452452
operator.ge: ">="}
453453

454-
def _exponent_symbol(self):
454+
def _exponent_symbol(self) -> str:
455455
"""
456456
Return the symbol used to denote ``*10^`` in floats, e.g 'e' in 1.5e6.
457457

src/sage/interfaces/maxima_abstract.py

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,30 @@
4848
# https://www.gnu.org/licenses/
4949
# ****************************************************************************
5050

51+
from itertools import islice
52+
import operator
5153
import os
5254
import re
53-
import sys
5455
import subprocess
55-
56-
from sage.env import DOT_SAGE, MAXIMA
57-
COMMANDS_CACHE = '%s/maxima_commandlist_cache.sobj' % DOT_SAGE
56+
import sys
5857

5958
from sage.cpython.string import bytes_to_str
60-
59+
from sage.env import DOT_SAGE, MAXIMA
60+
from sage.interfaces.tab_completion import ExtraTabCompletion
6161
from sage.misc.cachefunc import cached_method
62+
from sage.misc.instancedoc import instancedoc
6263
from sage.misc.multireplace import multiple_replace
63-
from sage.structure.richcmp import richcmp, rich_to_bool
64+
from sage.structure.richcmp import rich_to_bool, richcmp
65+
66+
from .interface import (
67+
AsciiArtString,
68+
Interface,
69+
InterfaceElement,
70+
InterfaceFunction,
71+
InterfaceFunctionElement,
72+
)
73+
COMMANDS_CACHE = '%s/maxima_commandlist_cache.sobj' % DOT_SAGE
6474

65-
from .interface import (Interface, InterfaceElement, InterfaceFunctionElement,
66-
InterfaceFunction, AsciiArtString)
67-
from sage.interfaces.tab_completion import ExtraTabCompletion
68-
from sage.misc.instancedoc import instancedoc
6975

7076
# The Maxima "apropos" command, e.g., apropos(det) gives a list
7177
# of all identifiers that begin in a certain way. This could
@@ -1632,13 +1638,13 @@ def dot(self, other):
16321638
Q = P(other)
16331639
return P('%s . %s' % (self.name(), Q.name()))
16341640

1635-
def __getitem__(self, n):
1641+
def __getitem__(self, i):
16361642
r"""
1637-
Return the `n`-th element of this list.
1643+
Return the `i`-th element of this list.
16381644
16391645
INPUT:
16401646
1641-
- ``n`` -- integer
1647+
- ``i`` -- integer or slice
16421648
16431649
OUTPUT: Maxima object
16441650
@@ -1658,13 +1664,30 @@ def __getitem__(self, n):
16581664
sage: v[10]
16591665
Traceback (most recent call last):
16601666
...
1661-
IndexError: n = (10) must be between 0 and 5
1662-
"""
1663-
n = int(n)
1664-
if n < 0 or n >= len(self):
1665-
raise IndexError("n = (%s) must be between %s and %s" % (n, 0, len(self)-1))
1666-
# If you change the n+1 to n below, better change __iter__ as well.
1667-
return InterfaceElement.__getitem__(self, n+1)
1667+
IndexError: i = (10) must be between 0 and 5
1668+
sage: v[2:]
1669+
[2*x^2, 3*x^3, 4*x^4, 5*x^5]
1670+
sage: v[:3]
1671+
[0, x, 2*x^2]
1672+
sage: v[1:4]
1673+
[x, 2*x^2, 3*x^3]
1674+
sage: v[3::-1]
1675+
[3*x^3, 2*x^2, x, 0]
1676+
"""
1677+
# Handle slices as well
1678+
if isinstance(i, slice):
1679+
start, stop, step = i.start or 0, i.stop or len(self), i.step or 1
1680+
if start >= 0 and stop >= 0 and step >= 0:
1681+
return list(islice(self, start, stop, step))
1682+
else:
1683+
# Hard to tackle slices esp with negative step
1684+
return list(self)[i]
1685+
else:
1686+
i = operator.index(i)
1687+
if i < 0 or i >= len(self):
1688+
raise IndexError("i = (%s) must be between %s and %s" % (i, 0, len(self)-1))
1689+
# If you change the i+1 to i below, better change __iter__ as well.
1690+
return InterfaceElement.__getitem__(self, i+1)
16681691

16691692
def __iter__(self):
16701693
"""
@@ -1679,8 +1702,9 @@ def __iter__(self):
16791702
sage: [e._sage_() for e in L]
16801703
[0, x, 2*x^2, 3*x^3, 4*x^4, 5*x^5]
16811704
"""
1682-
for i in range(len(self)):
1683-
yield self[i]
1705+
z = self.copy()
1706+
for _ in range(len(z)):
1707+
yield z.pop()
16841708

16851709
def subst(self, val):
16861710
"""
@@ -2108,7 +2132,7 @@ def integral(self, var):
21082132

21092133
integrate = integral
21102134

2111-
def _operation(self, operation, f=None):
2135+
def _operation(self, operation, other=None):
21122136
r"""
21132137
This is a utility function which factors out much of the
21142138
commonality used in the arithmetic operations for
@@ -2119,7 +2143,7 @@ def _operation(self, operation, f=None):
21192143
- ``operation`` -- string representing the operation
21202144
being performed. For example, '\*', or '1/'
21212145
2122-
- ``f`` -- the other operand; if ``f`` is
2146+
- ``other`` -- the other operand; if ``other`` is
21232147
``None``, then the operation is assumed to be unary
21242148
rather than binary
21252149
@@ -2136,16 +2160,16 @@ def _operation(self, operation, f=None):
21362160
1/sin(y+x)
21372161
"""
21382162
P = self._check_valid()
2139-
if isinstance(f, P._object_function_class()):
2140-
tmp = sorted(set(self.arguments() + f.arguments()))
2163+
if isinstance(other, P._object_function_class()):
2164+
tmp = sorted(set(self.arguments() + other.arguments()))
21412165
args = ','.join(tmp)
2142-
defn = "(%s)%s(%s)" % (self.definition(), operation, f.definition())
2143-
elif f is None:
2166+
defn = "(%s)%s(%s)" % (self.definition(), operation, other.definition())
2167+
elif other is None:
21442168
args = self.arguments(split=False)
21452169
defn = "%s(%s)" % (operation, self.definition())
21462170
else:
21472171
args = self.arguments(split=False)
2148-
defn = "(%s)%s(%s)" % (self.definition(), operation, repr(f))
2172+
defn = "(%s)%s(%s)" % (self.definition(), operation, repr(other))
21492173

21502174
return P.function(args, P.eval(defn))
21512175

src/sage/matrix/matrix2.pyx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21038,6 +21038,24 @@ def _matrix_power_symbolic(A, n):
2103821038
sage: B = A^n; B
2103921039
[ kronecker_delta(0, n) n*kronecker_delta(1, n)]
2104021040
[ 0 kronecker_delta(0, n)]
21041+
21042+
Check symbolic power of matrix with repeated eigenvalues (:issue:`40803`)::
21043+
21044+
sage: var('k n')
21045+
(k, n)
21046+
sage: A = matrix([[k,1,0,1],[1,k,1,0],[0,1,k,1],[1,0,1,k]])^n
21047+
sage: A
21048+
[1/4*(k + 2)^n + 1/4*(k - 2)^n + 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n - 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n]
21049+
[ 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n + 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n - 1/2*k^n]
21050+
[1/4*(k + 2)^n + 1/4*(k - 2)^n - 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n + 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n]
21051+
[ 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n - 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n + 1/2*k^n]
21052+
sage: B = matrix([[k,1,0,1],[1,k,1,0],[0,1,k,1],[1,0,1,k]], sparse=True)^n
21053+
sage: B
21054+
[1/4*(k + 2)^n + 1/4*(k - 2)^n + 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n - 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n]
21055+
[ 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n + 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n - 1/2*k^n]
21056+
[1/4*(k + 2)^n + 1/4*(k - 2)^n - 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n + 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n]
21057+
[ 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n - 1/2*k^n 1/4*(k + 2)^n - 1/4*(k - 2)^n 1/4*(k + 2)^n + 1/4*(k - 2)^n + 1/2*k^n]
21058+
2104121059
"""
2104221060
from sage.rings.qqbar import AlgebraicNumber
2104321061
from sage.matrix.constructor import matrix

src/sage/matrix/matrix_symbolic_dense.pyx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,12 @@ Check that :issue:`12778` is fixed::
153153
sage: parent(M)
154154
Full MatrixSpace of 3 by 4 dense matrices over Symbolic Ring
155155
"""
156-
157156
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
158157
from sage.structure.factorization import Factorization
159158

160159
from sage.matrix.matrix_generic_dense cimport Matrix_generic_dense
161160
from sage.matrix.constructor import matrix
161+
from sage.misc.flatten import flatten
162162

163163
cdef maxima
164164

@@ -702,12 +702,20 @@ cdef class Matrix_symbolic_dense(Matrix_generic_dense):
702702
sage: matrix([[a, b], [c, d]]).jordan_form(subdivide=False)
703703
[1/2*a + 1/2*d - 1/2*sqrt(a^2 + 4*b*c - 2*a*d + d^2) 0]
704704
[ 0 1/2*a + 1/2*d + 1/2*sqrt(a^2 + 4*b*c - 2*a*d + d^2)]
705+
706+
Check that :issue:`40803` is fixed::
707+
708+
sage: matrix([[a, 0], [0, a]]).jordan_form()
709+
[a|0]
710+
[-+-]
711+
[0|a]
705712
"""
706713
A = self._maxima_lib_()
707714
jordan_info = A.jordan()
708715
J = jordan_info.dispJordan()._sage_()
709716
if subdivide:
710-
v = [x[1] for x in jordan_info]
717+
# Repeated eigen values indices are part of the same list
718+
v = flatten([x[1:] for x in jordan_info])
711719
w = [sum(v[0:i]) for i in range(1, len(v))]
712720
J.subdivide(w, w)
713721
if transformation:

src/sage/matrix/matrix_symbolic_sparse.pyx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ from sage.structure.factorization import Factorization
167167

168168
from sage.matrix.matrix_generic_sparse cimport Matrix_generic_sparse
169169
from sage.matrix.constructor import matrix
170+
from sage.misc.flatten import flatten
170171

171172
cdef maxima
172173

@@ -709,12 +710,20 @@ cdef class Matrix_symbolic_sparse(Matrix_generic_sparse):
709710
sage: matrix([[a, b], [c, d]], sparse=True).jordan_form(subdivide=False)
710711
[1/2*a + 1/2*d - 1/2*sqrt(a^2 + 4*b*c - 2*a*d + d^2) 0]
711712
[ 0 1/2*a + 1/2*d + 1/2*sqrt(a^2 + 4*b*c - 2*a*d + d^2)]
713+
714+
Check that :issue:`40803` is fixed::
715+
716+
sage: matrix([[a, 0], [0, a]], sparse=True).jordan_form()
717+
[a|0]
718+
[-+-]
719+
[0|a]
712720
"""
713721
A = self._maxima_lib_()
714722
jordan_info = A.jordan()
715723
J = matrix(jordan_info.dispJordan()._sage_(), sparse=True)
716724
if subdivide:
717-
v = [x[1] for x in jordan_info]
725+
# Repeated eigen values indices are part of the same list
726+
v = flatten([x[1:] for x in jordan_info])
718727
w = [sum(v[0:i]) for i in range(1, len(v))]
719728
J.subdivide(w, w)
720729
if transformation:

0 commit comments

Comments
 (0)