7.4. LDL decomposition#

LDL decomposition is very similar to Cholesky factorization; however, there are some subtle differences. In this decomposition, a (real) symmetric positive definite matrix A is decomposed as follows

A=LDL,

where L is a lower triangular matrix, and D is a diagonal matrix.

Furthermore, We have,

[a11a12a1na21a22a2nan1an2ann]=[100l2110ln1ln21][d11000d22000dnn].

Solving this system, lij and dii can be identified as follows

dii=aiik=1i1lij2djj,lii=1dii(aijk=1i1likljkdkk),

See [Allaire, Kaber, Trabelsi, and Allaire, 2008] for the full derivation of this algorithm.

import numpy as np
import pandas as pd

def myLDL(A):
    '''
    Assuming that the matrix A is symmetric and positive definite,
    this function computes a unit lower triangular matrix L, and
    a diagonal matrix D, such that A = LDL^t
    Parameters
    ----------
    A : numpy array
        DESCRIPTION. Matrix A

    Returns
    -------
    L : numpy array
        DESCRIPTION. Matrix L from A = LDL^t
    D : numpy array
        DESCRIPTION. Matrix L from A = LDL^t

    '''
    
    n = A.shape[0]
    U = np.zeros([n,n], dtype = float)
    U[0, 0] = A[0, 0]
    U[1:, 0] = A[1:, 0]/U[0, 0]
    for j in range(1, n):
        U[j, j] = A[j, j]
        for k in range(0, j):
            U[j, j] = U[j, j] - U[k, k]*U[j, k]*U[j, k]
        for i in range(j+1, n):
            U[i, j] = A[i, j]
            for k in range(0, j):
                U[i, j] = U[i, j]- U[k, k]*U[i, k]*U[j, k]
            U[i, j] = U[i, j]/U[j, j]
    L = np.tril(U,-1) + np.eye(n, dtype=float)
    D = np.diag(np.diag(U))
    return L, D
function [L, D] = myLDL(A)
%{
Assuming that the matrix A is symmetric and positive definite,
this function computes a unit lower triangular matrix L, and
a diagonal matrix D, such that A = LDL^t
Parameters
----------
L : numpy array
    DESCRIPTION. Matrix L from A = LDL^t
D : numpy array
    DESCRIPTION. Matrix L from A = LDL^t
%}
n=length(A);
U = zeros(n,n);
U(1,1)=A(1,1);
U(2:n,1)=A(2:n,1)/U(1,1);
for j=2:n
   U(j,j)= A(j,j);
   for k=1:j-1
       U(j,j)=U(j,j)-U(k,k)*U(j,k)*U(j,k);
   end
   for i=j+1:n
       U(i,j)=A(i,j);
       for k=1:j-1
           U(i,j)=U(i,j)-U(k,k)*U(i,k)*U(j,k);
       end
       U(i,j)=U(i,j)/U(j,j);
   end
%
end
L = tril(U,-1)+eye(n,n);
D = diag(diag(U));
import sys
sys.path.insert(0,'..')
import hd_tools as hd
BokehJS 2.4.3 successfully loaded.

Example: Apply LDL decomposition on the following matrix and identify L, D, and L.

A=[7312381411412416].

Solution: We have,

import numpy as np
from hd_Matrix_Decomposition import myLDL
from IPython.display import display, Latex
from sympy import init_session, init_printing, Matrix

A = np.array([ [7, 3, -1, 2], [3, 8, 1, -4], [-1, 1, 4, -1], [2, -4, -1, 6] ])
L, D = myLDL(A)
display(Latex(r'L ='), Matrix(np.round(L, 2)))
display(Latex(r'D ='), Matrix(np.round(D, 2)))
display(Latex(r'LD L^T ='), Matrix(np.round(np.matmul(L,np.matmul(D,L.T)), 2)))
L=
[1.00000.431.0000.140.211.000.290.720.091.0]
D=
[7.000006.7100003.5500001.89]
LDLT=
[7.03.01.02.03.08.01.04.01.01.04.01.02.04.01.06.0]
hd.matrix_decomp_fig(mats = [L, D, L.T, np.round(np.matmul(L,np.matmul(D,L.T)), 2)],
                     labels = ['$L$', '$D$','$L^T$', '$LD L^T$'], nrows=1, ncols=4, figsize=(16, 6))
../_images/83ef6893c9f156a523b7acb69554dd9c8212c7e184bd4f5c5fbf244f91853b71.png

Note that we could get a similar results using function, scipy.linalg.ldl.

import scipy.linalg as linalg
L, D, P = linalg.ldl(A)
display(Latex(r'L ='), Matrix(np.round(L, 2)))
display(Latex(r'D ='), Matrix(np.round(D, 2)))
L=
[1.00000.431.0000.140.211.000.290.720.091.0]
D=
[7.000006.7100003.5500001.89]

7.4.1. Solving Linear systems using LDL decomposition#

We can solve the linear system Ax=b for x using LDL decomposition. To demonstrate this, we use the following example,

Example: Solve the following linear system using LDL decomposition.

{7x1+3x2x3+2x4=183x1+8x2+x34x4=6x2x1+4x3x4=92x14x2x3+6x4=15

Solution:

Let A=[7312381411412416] and b=[186915]. Then, this linear system can be also expressed as follows,

Ax=(LDLT)x=L(D(LTx))=b.

We have,

A = np.array([ [7, 3, -1, 2], [3, 8, 1, -4], [-1, 1, 4, -1], [2, -4, -1, 6] ])
b = np.array([[18],[ 6],[ 9],[15]])
L, D = myLDL(A)
display(Latex(r'L ='), Matrix(np.round(L, 2)))
display(Latex(r'D ='), Matrix(np.round(D, 2)))
L=
[1.00000.431.0000.140.211.000.290.720.091.0]
D=
[7.000006.7100003.5500001.89]

Now we can solve the following linear systems instead ${Lz=b,Dy=z,BTx=y.$

hd.matrix_decomp_fig(mats = [A, L, D, L.T], labels = ['$A$', '$L$', '$D$','$L^T$'], nrows=1, ncols=4, figsize=(16, 4))
../_images/45ab29e3e653ac4b5015b19cca0e58c4e9269c4a1a4d6e5f5344c33eab905175.png
# solving for z
z = np.linalg.solve(L, b)
display(Latex(r'z ='), Matrix(np.round(z, 2)))
z=
[18.01.7111.947.54]
# solving y
y = np.linalg.solve(D, z)
display(Latex(r'y ='), Matrix(np.round(y, 2)))
y=
[2.570.263.364.0]
# solving for x
x = np.linalg.solve(L.T, y)
display(Latex(r'x ='), Matrix(np.round(x, 2)))
x=
[1.02.03.04.0]

Let’s now solve the linear system directly and compare the results.

x_new = linalg.solve(A, b)
display(Latex(r'x ='), Matrix(np.round(x_new, 2)))
x=
[1.02.03.04.0]