Skip to content

Commit 478e7e0

Browse files
author
Release Manager
committed
sagemathgh-36845: Give more precise answer on symbolic power of a matrix - Changed _matrix_power_symbolic function to consider mk=0 case - Changed the _matrix_power_symbolic function to handle all the cases - Created tests covering the changes This PR improves the answer given by _matrix_power_symbolic() function, which involves finding the symbolic power of a matrix, and also gives more precise answer on evalution. eg, before, when we were evaluating the zeroth power of a matrix sometimes it was giving wrong answer like below, ```python sage: n = var("n") sage: A = matrix(ZZ, [[1,-1], [-1,1]]) sage: An = A^(n) [ 1/2*2^n -1/2*2^n] [-1/2*2^n 1/2*2^n] sage: An({n:0}) [ 1/2 -1/2] [-1/2 1/2] ``` Here, for n=0, it is not giving an identity matrix which is wrong, but after the change it will give answer as below, ```python sage: n = var("n") sage: A = matrix(ZZ, [[1,-1], [-1,1]]) sage: An = A^(n) [ 1/2*2^(2*n + 1) + 1/2*kronecker_delta(0, 2*n + 1) -1/2*2^(2*n + 1) + 1/2*kronecker_delta(0, 2*n + 1)] [-1/2*2^(2*n + 1) + 1/2*kronecker_delta(0, 2*n + 1) 1/2*2^(2*n + 1) + 1/2*kronecker_delta(0, 2*n + 1)] sage: An({n:0}) [1 0] [0 1] ``` Also, This patch fixes sagemath#36838 . The function, _matrix_power_symbolic() was giving symbolic power of a nilpotent matrix as a null matrix but the correct answer should be represented in the form of kronecker_delta() function. In the case of mk=0, simplifying only binomial term should work because afterall the form 0^(n-i) should be simplifed to kronecker_delta() function and no further simplification is needed. And in every other case it is simplifying whole term so it will handle all other cases. <!-- ^^^^^ Please provide a concise, informative and self-explanatory title. Don't put issue numbers in there, do this in the PR body below. For example, instead of "Fixes sagemath#1234" use "Introduce new method to calculate 1+1" --> <!-- Describe your changes here in detail --> <!-- Why is this change required? What problem does it solve? --> <!-- If this PR resolves an open issue, please link to it here. For example "Fixes sagemath#12345". --> <!-- If your change requires a documentation PR, please link it appropriately. --> ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> <!-- If your change requires a documentation PR, please link it appropriately --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> <!-- Feel free to remove irrelevant items. --> - [x] The title is concise, informative, and self-explanatory. - [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. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on - sagemath#12345: short description why this is a dependency - sagemath#34567: ... --> <!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! --> None URL: sagemath#36845 Reported by: Ruchit Jagodara Reviewer(s): Dima Pasechnik, phul-ste, Ruchit Jagodara
2 parents 55cdfc7 + f95793b commit 478e7e0

File tree

1 file changed

+30
-17
lines changed

1 file changed

+30
-17
lines changed

src/sage/matrix/matrix2.pyx

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,10 @@ cdef class Matrix(Matrix1):
216216
from sage.matrix.constructor import matrix
217217
if self.is_sparse():
218218
return matrix({ij: self[ij].subs(*args, **kwds) for ij in self.nonzero_positions()},
219-
nrows=self._nrows, ncols=self._ncols, sparse=True)
219+
nrows=self._nrows, ncols=self._ncols, sparse=True)
220220
else:
221221
return matrix([a.subs(*args, **kwds) for a in self.list()],
222-
nrows=self._nrows, ncols=self._ncols, sparse=False)
222+
nrows=self._nrows, ncols=self._ncols, sparse=False)
223223

224224
def solve_left(self, B, check=True):
225225
"""
@@ -3183,14 +3183,12 @@ cdef class Matrix(Matrix1):
31833183
"""
31843184

31853185
# Validate assertions
3186-
#
31873186
if not self.is_square():
31883187
raise ValueError("self must be a square matrix")
31893188

31903189
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
31913190

31923191
# Extract parameters
3193-
#
31943192
cdef Matrix M = <Matrix> self
31953193
n = M._ncols
31963194
R = M._base_ring
@@ -3199,7 +3197,6 @@ cdef class Matrix(Matrix1):
31993197
# Corner cases
32003198
# N.B. We already tested for M to be square, hence we do not need to
32013199
# test for 0 x n or m x 0 matrices.
3202-
#
32033200
if n == 0:
32043201
return S.one()
32053202

@@ -3215,7 +3212,6 @@ cdef class Matrix(Matrix1):
32153212
#
32163213
# N.B. The documentation is still 1-based, although the code, after
32173214
# having been ported from Magma to Sage, is 0-based.
3218-
#
32193215
from sage.matrix.constructor import matrix
32203216

32213217
F = [R.zero()] * n
@@ -3226,30 +3222,25 @@ cdef class Matrix(Matrix1):
32263222
for t in range(1,n):
32273223

32283224
# Set a(1, t) to be M(<=t, t)
3229-
#
32303225
for i in range(t+1):
32313226
a.set_unsafe(0, i, M.get_unsafe(i, t))
32323227

32333228
# Set A[1, t] to be the (t)th entry in a[1, t]
3234-
#
32353229
A[0] = M.get_unsafe(t, t)
32363230

32373231
for p in range(1, t):
32383232

32393233
# Set a(p, t) to the product of M[<=t, <=t] * a(p-1, t)
3240-
#
32413234
for i in range(t+1):
32423235
s = R.zero()
32433236
for j in range(t+1):
32443237
s = s + M.get_unsafe(i, j) * a.get_unsafe(p-1, j)
32453238
a.set_unsafe(p, i, s)
32463239

32473240
# Set A[p, t] to be the (t)th entry in a[p, t]
3248-
#
32493241
A[p] = a.get_unsafe(p, t)
32503242

32513243
# Set A[t, t] to be M[t, <=t] * a(p-1, t)
3252-
#
32533244
s = R.zero()
32543245
for j in range(t+1):
32553246
s = s + M.get_unsafe(t, j) * a.get_unsafe(t-1, j)
@@ -3262,7 +3253,7 @@ cdef class Matrix(Matrix1):
32623253
F[p] = s - A[p]
32633254

32643255
X = S.gen(0)
3265-
f = X ** n + S(list(reversed(F)))
3256+
f = X**n + S(list(reversed(F)))
32663257

32673258
return f
32683259

@@ -3634,7 +3625,7 @@ cdef class Matrix(Matrix1):
36343625
# using the rows of a matrix.) Also see Cohen's first GTM,
36353626
# Algorithm 2.2.9.
36363627

3637-
cdef Py_ssize_t i, m, n,
3628+
cdef Py_ssize_t i, m, n
36383629
n = self._nrows
36393630

36403631
cdef Matrix c
@@ -18607,8 +18598,8 @@ def _matrix_power_symbolic(A, n):
1860718598

1860818599
sage: A = matrix(ZZ, [[1,-1], [-1,1]])
1860918600
sage: A^(2*n+1) # needs sage.symbolic
18610-
[ 1/2*2^(2*n + 1) -1/2*2^(2*n + 1)]
18611-
[-1/2*2^(2*n + 1) 1/2*2^(2*n + 1)]
18601+
[ 1/2*2^(2*n + 1) + 1/2*kronecker_delta(0, 2*n + 1) -1/2*2^(2*n + 1) + 1/2*kronecker_delta(0, 2*n + 1)]
18602+
[-1/2*2^(2*n + 1) + 1/2*kronecker_delta(0, 2*n + 1) 1/2*2^(2*n + 1) + 1/2*kronecker_delta(0, 2*n + 1)]
1861218603

1861318604
Check if :trac:`23215` is fixed::
1861418605

@@ -18618,12 +18609,25 @@ def _matrix_power_symbolic(A, n):
1861818609
-1/2*I*(a + I*b)^k + 1/2*I*(a - I*b)^k,
1861918610
1/2*I*(a + I*b)^k - 1/2*I*(a - I*b)^k,
1862018611
1/2*(a + I*b)^k + 1/2*(a - I*b)^k]
18612+
18613+
Check if :trac:`36838` is fixed:Checking symbolic power of
18614+
nilpotent matrix::
18615+
18616+
sage: A = matrix([[0,1],[0,0]]); A
18617+
[0 1]
18618+
[0 0]
18619+
sage: n = var('n'); n
18620+
n
18621+
sage: B = A^n; B
18622+
[ kronecker_delta(0, n) n*kronecker_delta(1, n)]
18623+
[ 0 kronecker_delta(0, n)]
1862118624
"""
1862218625
from sage.rings.qqbar import AlgebraicNumber
1862318626
from sage.matrix.constructor import matrix
1862418627
from sage.functions.other import binomial
1862518628
from sage.symbolic.ring import SR
1862618629
from sage.rings.qqbar import QQbar
18630+
from sage.functions.generalized import kronecker_delta
1862718631

1862818632
got_SR = A.base_ring() == SR
1862918633

@@ -18656,8 +18660,17 @@ def _matrix_power_symbolic(A, n):
1865618660
# D^i(f) / i! with f = x^n and D = differentiation wrt x
1865718661
if hasattr(mk, 'radical_expression'):
1865818662
mk = mk.radical_expression()
18659-
vk = [(binomial(n, i) * mk**(n-i)).simplify_full()
18660-
for i in range(nk)]
18663+
18664+
18665+
# When the variable "mk" is equal to zero, it is advisable to employ the Kronecker delta function
18666+
# instead of utilizing the numerical value zero. This choice is made to encompass scenarios where
18667+
# the power of zero is also equal to zero.
18668+
if mk:
18669+
vk = [(binomial(n, i) * mk._pow_(n-i)).simplify_full()
18670+
for i in range(nk)]
18671+
else:
18672+
vk = [(binomial(n, i).simplify_full() * kronecker_delta(n,i))
18673+
for i in range(nk)]
1866118674

1866218675
# Form block Mk and insert it in M
1866318676
Mk = matrix(SR, [[SR.zero()]*i + vk[:nk-i] for i in range(nk)])

0 commit comments

Comments
 (0)