Ask Your Question

Revision history [back]

My answer elaborates on using the Jordan normal form to implement a broad class of matrix functions, the matrix power included. It relies on the jordan_form() method, which is implemented in exact rings.

The mathematical preliminaries can be found in extending scalar function for matrix functions and in the excellent book Functions of Matrices by N. Higham.

Failure of a straightforward approach

(using πš‚πšŠπšπšŽπ™ΌπšŠπšπš‘πšŸπšŽπš›πšœπš’πš˜πš—πŸ½.𝟺,πšπšŽπš•πšŽπšŠπšœπšŽπ™³πšŠπšπšŽ:𝟸𝟢𝟷𝟼⎯𝟷𝟢⎯𝟷𝟾)

Let

A = matrix(QQ, [[2, -1], [1,  0]])
var('k')
A^k

we get

sage: NotImplementedError: non-integral exponents not supported

Function of a matrix using Jordan normal form

The code below implements $f(A)$ using the Jordan normal form of $A$, see [N. Higham, Functions of Matrices, Sec. 1.2] for details.

def matrix_function_Jordan(A, f):

    # returns jordan matrix J and invertlbe matrix P such that A = P*J*~P
    [J, P] = A.jordan_form(transformation=True);

    fJ = zero_matrix(SR, J.ncols())
    num_Jordan_blocks = 1+len(J.subdivisions()[0])
    fJ.subdivide(J.subdivisions());

    for k in range(num_Jordan_blocks):

        # get Jordan block Jk
        Jk = J.subdivision(k, k)    

        # dimension of Jordan block Jk
        mk = Jk.ncols();

        fJk = zero_matrix(SR, mk, mk);

        # compute the first row of f(Jk)
        vk = [f.derivative(x, i)(Jk[i][i])/factorial(i) for i in range(mk)]

        # insert vk into each row (above the main diagonal)
        for i in range(mk):
            row_Jk_i = vector(SR, zero_vector(SR, i).list() + vk[0:mk-i])
            fJk.set_row(i, row_Jk_i)

        fJ.set_block(k, k, fJk)

    fA = P*fJ*~P

    return fA

Application: computing $A^k$

First, we define our matrix function:

sage: var('k'); pow_sym(x) = x^k

Let's consider the OP's example, a $2\times 2$ matrix with one Jordan block of size $2$:

sage: A = matrix(QQ, [[2, -1], [1,  0]])

This matrix is not diagonalizable.

sage: A.is_diagonalizable()
sage: False

Calling our function,

sage: matrix_function_Jordan(A, pow_sym)

gives $\newcommand{\Bold}[1]{\mathbf{#1}}\left(\begin{array}{rr} k + 1 & -k \\ k & -k + 1 \end{array}\right)$.

Pitfalls

A limitation of this approach is that we need the Jordan form. Notice that it is only implemented for exact rings (the Jordan form is unstable for inexact rings). Moreover, the spectrum should belong to the same ring. Consider for instance:

sage: A = matrix(QQ, [[0,4,1],[-1,1,5],[2,0,-89]]);
sage: [J, P] = A.jordan_form(transformation=True)

gives: RuntimeError: Some eigenvalue does not exist in Rational Field.

This is not a piece of cake for WolframAlpha either: this case givesStandard computation time exceeded...

My answer elaborates on using the Jordan normal form to implement a broad class of matrix functions, the matrix power included. It relies on the jordan_form() method, which is implemented in exact rings.

The mathematical preliminaries can be found in extending scalar function for matrix functions and in the excellent book Functions of Matrices by N. Higham.

Failure of a straightforward approach

(using πš‚πšŠπšπšŽπ™ΌπšŠπšπš‘πšŸπšŽπš›πšœπš’πš˜πš—πŸ½.𝟺,πšπšŽπš•πšŽπšŠπšœπšŽπ™³πšŠπšπšŽ:𝟸𝟢𝟷𝟼⎯𝟷𝟢⎯𝟷𝟾)

Let

A = matrix(QQ, [[2, -1], [1,  0]])
var('k')
A^k

we get

sage: NotImplementedError: non-integral exponents not supported

Function of a matrix using Jordan normal form

The code below implements $f(A)$ using the Jordan normal form of $A$, see [N. Higham, Functions of Matrices, Sec. 1.2] for details.

def matrix_function_Jordan(A, f):

    # returns jordan matrix J and invertlbe invertible matrix P such that A = P*J*~P
    [J, P] = A.jordan_form(transformation=True);

    fJ = zero_matrix(SR, J.ncols())
    num_Jordan_blocks = 1+len(J.subdivisions()[0])
    fJ.subdivide(J.subdivisions());

    for k in range(num_Jordan_blocks):

        # get Jordan block Jk
        Jk = J.subdivision(k, k)    

        # dimension of Jordan block Jk
        mk = Jk.ncols();

        fJk = zero_matrix(SR, mk, mk);

        # compute the first row of f(Jk)
        vk = [f.derivative(x, i)(Jk[i][i])/factorial(i) for i in range(mk)]

        # insert vk into each row (above the main diagonal)
        for i in range(mk):
            row_Jk_i = vector(SR, zero_vector(SR, i).list() + vk[0:mk-i])
            fJk.set_row(i, row_Jk_i)

        fJ.set_block(k, k, fJk)

    fA = P*fJ*~P

    return fA

Application: computing $A^k$

First, we define our matrix function:

sage: var('k'); pow_sym(x) = x^k

Let's consider the OP's example, a $2\times 2$ matrix with one Jordan block of size $2$:

sage: A = matrix(QQ, [[2, -1], [1,  0]])

This matrix is not diagonalizable.

sage: A.is_diagonalizable()
sage: False

Calling our function,

sage: matrix_function_Jordan(A, pow_sym)

gives $\newcommand{\Bold}[1]{\mathbf{#1}}\left(\begin{array}{rr} k + 1 & -k \\ k & -k + 1 \end{array}\right)$.

Pitfalls

A limitation of this approach is that we need the Jordan form. Notice that it is only implemented for exact rings (the Jordan form is unstable for inexact rings). Moreover, the spectrum should belong to the same ring. Consider for instance:

sage: A = matrix(QQ, [[0,4,1],[-1,1,5],[2,0,-89]]);
sage: [J, P] = A.jordan_form(transformation=True)

gives: RuntimeError: Some eigenvalue does not exist in Rational Field.

This is not a piece of cake for WolframAlpha either: this case givesStandard computation time exceeded...

My answer elaborates on using the Jordan normal form to implement a broad class of matrix functions, the matrix power included. It relies on the jordan_form() method, which is implemented in available for exact rings.

The mathematical preliminaries can be found in extending scalar function for matrix functions and in the excellent book Functions of Matrices by N. Higham.

Failure of a straightforward approach

(using πš‚πšŠπšπšŽπ™ΌπšŠπšπš‘πšŸπšŽπš›πšœπš’πš˜πš—πŸ½.𝟺,πšπšŽπš•πšŽπšŠπšœπšŽπ™³πšŠπšπšŽ:𝟸𝟢𝟷𝟼⎯𝟷𝟢⎯𝟷𝟾)

Let

A = matrix(QQ, [[2, -1], [1,  0]])
var('k')
A^k

we get

sage: NotImplementedError: non-integral exponents not supported

Function of a matrix using Jordan normal form

The code below implements $f(A)$ using the Jordan normal form of $A$, see [N. Higham, Functions of Matrices, Sec. 1.2] for details.

def matrix_function_Jordan(A, f):

    # returns jordan matrix J and invertible matrix P such that A = P*J*~P
    [J, P] = A.jordan_form(transformation=True);

    fJ = zero_matrix(SR, J.ncols())
    num_Jordan_blocks = 1+len(J.subdivisions()[0])
    fJ.subdivide(J.subdivisions());

    for k in range(num_Jordan_blocks):

        # get Jordan block Jk
        Jk = J.subdivision(k, k)    

        # dimension of Jordan block Jk
        mk = Jk.ncols();

        fJk = zero_matrix(SR, mk, mk);

        # compute the first row of f(Jk)
        vk = [f.derivative(x, i)(Jk[i][i])/factorial(i) for i in range(mk)]

        # insert vk into each row (above the main diagonal)
        for i in range(mk):
            row_Jk_i = vector(SR, zero_vector(SR, i).list() + vk[0:mk-i])
            fJk.set_row(i, row_Jk_i)

        fJ.set_block(k, k, fJk)

    fA = P*fJ*~P

    return fA

Application: computing $A^k$

First, we define our matrix function:

sage: var('k'); pow_sym(x) = x^k

Let's consider the OP's example, a $2\times 2$ matrix with one Jordan block of size $2$:

sage: A = matrix(QQ, [[2, -1], [1,  0]])

This matrix is not diagonalizable.

sage: A.is_diagonalizable()
sage: False

Calling our function,

sage: matrix_function_Jordan(A, pow_sym)

gives $\newcommand{\Bold}[1]{\mathbf{#1}}\left(\begin{array}{rr} k + 1 & -k \\ k & -k + 1 \end{array}\right)$.

Pitfalls

A limitation of this approach is that we need the Jordan form. Notice that it is only implemented for exact rings (the Jordan form is unstable for inexact rings). Moreover, the spectrum should belong to the same ring. Consider for instance:

sage: A = matrix(QQ, [[0,4,1],[-1,1,5],[2,0,-89]]);
sage: [J, P] = A.jordan_form(transformation=True)

gives: RuntimeError: Some eigenvalue does not exist in Rational Field.

This is not a piece of cake for WolframAlpha either: this case givesStandard computation time exceeded...

My answer elaborates on using the Jordan normal form to implement a broad class of matrix functions, the matrix power included. It relies on the jordan_form() method, available for exact rings.

The mathematical preliminaries can be found in extending scalar function for matrix functions and in the excellent book Functions of Matrices by N. Higham.

Failure of a straightforward approach

(using πš‚πšŠπšπšŽπ™ΌπšŠπšπš‘πšŸπšŽπš›πšœπš’πš˜πš—πŸ½.𝟺,πšπšŽπš•πšŽπšŠπšœπšŽπ™³πšŠπšπšŽ:𝟸𝟢𝟷𝟼⎯𝟷𝟢⎯𝟷𝟾)

Let

A = matrix(QQ, [[2, -1], [1,  0]])
var('k')
A^k

we get

sage: NotImplementedError: non-integral exponents not supported

Function of a matrix using Jordan normal form

The code below implements $f(A)$ using the Jordan normal form of $A$, see [N. Higham, Functions of Matrices, Sec. 1.2] for details.

def matrix_function_Jordan(A, f):

    # returns jordan matrix J and invertible matrix P such that A = P*J*~P
    [J, P] = A.jordan_form(transformation=True);

    fJ = zero_matrix(SR, J.ncols())
    num_Jordan_blocks = 1+len(J.subdivisions()[0])
    fJ.subdivide(J.subdivisions());

    for k in range(num_Jordan_blocks):

        # get Jordan block Jk
        Jk = J.subdivision(k, k)    

        # dimension of Jordan block Jk
        mk = Jk.ncols();

        fJk = zero_matrix(SR, mk, mk);

        # compute the first row of f(Jk)
        vk = [f.derivative(x, i)(Jk[i][i])/factorial(i) for i in range(mk)]

        # insert vk into each row (above the main diagonal)
        for i in range(mk):
            row_Jk_i = vector(SR, zero_vector(SR, i).list() + vk[0:mk-i])
            fJk.set_row(i, row_Jk_i)

        fJ.set_block(k, k, fJk)

    fA = P*fJ*~P

    return fA

Application: computing $A^k$

First, we define our matrix function:

sage: var('k'); pow_sym(x) = x^k

Let's consider the OP's example, a $2\times 2$ matrix with one Jordan block of size $2$:

sage: A = matrix(QQ, [[2, -1], [1,  0]])

This matrix is not diagonalizable.

sage: A.is_diagonalizable()
sage: False

Calling our function,

sage: matrix_function_Jordan(A, pow_sym)

gives $\newcommand{\Bold}[1]{\mathbf{#1}}\left(\begin{array}{rr} k + 1 & -k \\ k & -k + 1 \end{array}\right)$.

Pitfalls

A limitation of this approach is that we need the Jordan form. Notice that it is only implemented for exact rings (the Jordan form is unstable for inexact rings). Moreover, the spectrum should belong to the same ring. Consider for instance:

sage: A = matrix(QQ, [[0,4,1],[-1,1,5],[2,0,-89]]);
sage: [J, P] = A.jordan_form(transformation=True)

gives: RuntimeError: Some eigenvalue does not exist in Rational Field.

This is not a piece of cake for WolframAlpha either: this case giveswe get Standard computation time exceeded...

My answer elaborates on using the Jordan normal form to implement a broad class of matrix functions, the matrix power included. It relies on the jordan_form() method, available for exact rings.

The mathematical preliminaries can be found in extending scalar function for matrix functions and in the excellent book Functions of Matrices by N. Higham.

Failure of a straightforward approach

(using πš‚πšŠπšπšŽπ™ΌπšŠπšπš‘πšŸπšŽπš›πšœπš’πš˜πš—πŸ½.𝟺,πšπšŽπš•πšŽπšŠπšœπšŽπ™³πšŠπšπšŽ:𝟸𝟢𝟷𝟼⎯𝟷𝟢⎯𝟷𝟾)

Let

A = matrix(QQ, [[2, -1], [1,  0]])
var('k')
A^k

we get

sage: NotImplementedError: non-integral exponents not supported

Function of a matrix using Jordan normal form

The code below implements $f(A)$ using the Jordan normal form of $A$, see [N. Higham, Functions of Matrices, Sec. 1.2] for details.

def matrix_function_Jordan(A, f):

    # returns jordan matrix J and invertible matrix P such that A = P*J*~P
    [J, P] = A.jordan_form(transformation=True);
A.jordan_form(transformation=True)

    fJ = zero_matrix(SR, J.ncols())
    num_Jordan_blocks = 1+len(J.subdivisions()[0])
    fJ.subdivide(J.subdivisions());
fJ.subdivide(J.subdivisions())

    for k in range(num_Jordan_blocks):

        # get Jordan block Jk
        Jk = J.subdivision(k, k)    

        # dimension of Jordan block Jk
        mk = Jk.ncols();

        fJk = zero_matrix(SR, mk, mk);
mk)

        # compute the first row of f(Jk)
        vk = [f.derivative(x, i)(Jk[i][i])/factorial(i) for i in range(mk)]

        # insert vk into each row (above the main diagonal)
        for i in range(mk):
            row_Jk_i = vector(SR, zero_vector(SR, i).list() + vk[0:mk-i])
            fJk.set_row(i, row_Jk_i)

        fJ.set_block(k, k, fJk)

    fA = P*fJ*~P

    return fA

Application: computing $A^k$

First, we define our matrix function:

sage: var('k'); pow_sym(x) = x^k

Let's consider the OP's example, a $2\times 2$ matrix with one Jordan block of size $2$:

sage: A = matrix(QQ, [[2, -1], [1,  0]])

This matrix is not diagonalizable.

sage: A.is_diagonalizable()
sage: False

Calling our function,

sage: matrix_function_Jordan(A, pow_sym)

gives $\newcommand{\Bold}[1]{\mathbf{#1}}\left(\begin{array}{rr} k + 1 & -k \\ k & -k + 1 \end{array}\right)$.

Pitfalls

A limitation of this approach is that we need the Jordan form. Notice that it is only implemented for exact rings (the Jordan form is unstable for inexact rings). Moreover, the spectrum should belong to the same ring. Consider for instance:

sage: A = matrix(QQ, [[0,4,1],[-1,1,5],[2,0,-89]]);
sage: [J, P] = A.jordan_form(transformation=True)

gives: RuntimeError: Some eigenvalue does not exist in Rational Field.

This is not a piece of cake for WolframAlpha either: we get Standard computation time exceeded...

My answer elaborates on using the Jordan normal form to implement a broad class of matrix functions, the matrix power included. It relies on the jordan_form() method, available for exact rings.

The mathematical preliminaries can be found in extending scalar function for matrix functions and in the excellent book Functions of Matrices by N. Higham.

Failure of a straightforward approach

(using πš‚πšŠπšπšŽπ™ΌπšŠπšπš‘πšŸπšŽπš›πšœπš’πš˜πš—πŸ½.𝟺,πšπšŽπš•πšŽπšŠπšœπšŽπ™³πšŠπšπšŽ:𝟸𝟢𝟷𝟼⎯𝟷𝟢⎯𝟷𝟾)

Let

sage: A = matrix(QQ, [[2, -1], [1,  0]])
sage: var('k')
sage: A^k

we get

sage: NotImplementedError: non-integral exponents not supported

Function of a matrix using Jordan normal form

The code below implements $f(A)$ using the Jordan normal form of $A$, see [N. Higham, Functions of Matrices, Sec. 1.2] for details.

def matrix_function_Jordan(A, f):

    # returns jordan matrix J and invertible matrix P such that A = P*J*~P
    [J, P] = A.jordan_form(transformation=True)

    fJ = zero_matrix(SR, J.ncols())
    num_Jordan_blocks = 1+len(J.subdivisions()[0])
    fJ.subdivide(J.subdivisions())

    for k in range(num_Jordan_blocks):

        # get Jordan block Jk
        Jk = J.subdivision(k, k)    

        # dimension of Jordan block Jk
        mk = Jk.ncols();

        fJk = zero_matrix(SR, mk, mk)

        # compute the first row of f(Jk)
        vk = [f.derivative(x, i)(Jk[i][i])/factorial(i) for i in range(mk)]

        # insert vk into each row (above the main diagonal)
        for i in range(mk):
            row_Jk_i = vector(SR, zero_vector(SR, i).list() + vk[0:mk-i])
            fJk.set_row(i, row_Jk_i)

        fJ.set_block(k, k, fJk)

    fA = P*fJ*~P

    return fA

Application: computing $A^k$

First, we define our matrix function:

sage: var('k'); pow_sym(x) = x^k

Let's consider the OP's example, a $2\times 2$ matrix with one Jordan block of size $2$:

sage: A = matrix(QQ, [[2, -1], [1,  0]])

This matrix is not diagonalizable.

sage: A.is_diagonalizable()
sage: False

Calling our function,

sage: matrix_function_Jordan(A, pow_sym)

gives $\newcommand{\Bold}[1]{\mathbf{#1}}\left(\begin{array}{rr} k + 1 & -k \\ k & -k + 1 \end{array}\right)$.

Pitfalls

A limitation of this approach is that we need the Jordan form. Notice that it is only implemented for exact rings (the Jordan form is unstable for inexact rings). Moreover, the spectrum should belong to the same ring. Consider for instance:

sage: A = matrix(QQ, [[0,4,1],[-1,1,5],[2,0,-89]]);
sage: [J, P] = A.jordan_form(transformation=True)

gives: RuntimeError: Some eigenvalue does not exist in Rational Field.

This is not a piece of cake for WolframAlpha either: we get Standard computation time exceeded...