I recently wrote a post on the Cholesky decomposition of a matrix. You can read more about the Cholesky decomposition here; https://harrybevins.blogspot.com/2020/04/cholesky-decomposition-and-identity.html. A closely related and more stable decomposition is the LDL decomposition which has the form,
Q=LDL*,
where L is a lower triangular matrix with diagonal entries equal to 1, L* is it's complex conjugate and D is a diagonal matrix. Again an LDL decomposition can be performed using the Scipy or numpy linear algebra packages but it is a far more rewarding experience to write the code. This also often leads to a better understanding of what is happening during this decomposition.
The relationship between the two decompositions, Cholesky and LDL, can be expressed like so,
Q=LDL*=LD1/2(D)1/2*L*=LD1/2(LD1/2)*.
A simple way to calculate the LDL decomposition from the Cholesky decomposition is therefore then to say that if S is a diagonal matrix containing the diagonal elements of Lcholesky then,
D=S2
L=LcholeskyS−1.
However, the Cholesky algorithm involves taking a square root which can be avoided by building the LDL decomposition with recurrence relations. Written out as matrices the decomposition has the following form,
Q=LDL*=(100L2110L31L321)(D1000D2000D3)(1L21L3101L32001)
By multiplying this out we can see that,
Q=(D1L21D1L31D1L21D1L221D1+D2L31L21D1+L32D2L31D1L31L21D1+L32D2L231D1+L232D2+D3)
which by comparison to the elements of Q leads to the following recurrence relations,
Dj=Qjj−∑j−1k=1L2jkDk,
Lij=1Dj(Qij−∑j−1k=1LikLjkDk) for i>j.
These are fairly straightforward relationships that can be implemented easily in Python. In order to re-use this code elsewhere I have defined it in a class as follows,
I then proceed to test this code. I use the same code to generate Q as I did in my demonstration of the Cholesky decomposition,
N here defines the dimensions of Q which is a positive definite matrix and as discussed in my previous article 'phi' is the basis functions for a polynomial model I had been using in a wider application of the Cholesky decomposition. I can then call my class acting on Q and calculate L and D which are class attributes with the following code,
To check everything is working properly I can print the difference between Q and LDL* which I correctly find to be 0 for all entries,
We can also plot the various components of the decomposition as a visual aid and this can be seen below.
Again this is a fun little task that can demonstrate the differences and similarities between a Cholesky decomposition and an LDL decomposition. It also serves as means for a better understanding of a linear algebra function that is often called but not understood entirely.
As always the code is available at; https://github.com/htjb/Blog/tree/master/LDLDecomposition. I have included also in this repository a modified version of the Cholesky function I previously wrote to demonstrate the mathematics at the beginning of this post and the close relationship between the two operations.
Thanks for reading!
Q=LDL*,
where L is a lower triangular matrix with diagonal entries equal to 1, L* is it's complex conjugate and D is a diagonal matrix. Again an LDL decomposition can be performed using the Scipy or numpy linear algebra packages but it is a far more rewarding experience to write the code. This also often leads to a better understanding of what is happening during this decomposition.
The relationship between the two decompositions, Cholesky and LDL, can be expressed like so,
Q=LDL*=LD1/2(D)1/2*L*=LD1/2(LD1/2)*.
A simple way to calculate the LDL decomposition from the Cholesky decomposition is therefore then to say that if S is a diagonal matrix containing the diagonal elements of Lcholesky then,
D=S2
L=LcholeskyS−1.
However, the Cholesky algorithm involves taking a square root which can be avoided by building the LDL decomposition with recurrence relations. Written out as matrices the decomposition has the following form,
Q=LDL*=(100L2110L31L321)(D1000D2000D3)(1L21L3101L32001)
By multiplying this out we can see that,
Q=(D1L21D1L31D1L21D1L221D1+D2L31L21D1+L32D2L31D1L31L21D1+L32D2L231D1+L232D2+D3)
which by comparison to the elements of Q leads to the following recurrence relations,
Dj=Qjj−∑j−1k=1L2jkDk,
Lij=1Dj(Qij−∑j−1k=1LikLjkDk) for i>j.
These are fairly straightforward relationships that can be implemented easily in Python. In order to re-use this code elsewhere I have defined it in a class as follows,
import numpy as np class ldl(object): #ldl decomposition def __init__(self, Q): self.Q = Q self.L, self.D = self.calc() def calc(self): L = np.zeros(self.Q.shape) D = np.zeros(len(self.Q)) for i in range(L.shape[1]): for j in range(L.shape[0]): D[j] = (self.Q[j, j] - np.sum([L[j, k]**2*D[k] for k in range(j)])) if i == j: L[i, j] = 1 if i > j: L[i, j] = ((1/D[j])*(self.Q[i, j] - np.sum([L[i, k]*L[j, k]*D[k] for k in range(j)]))) D = D * np.identity(len(self.Q)) return L, D
I then proceed to test this code. I use the same code to generate Q as I did in my demonstration of the Cholesky decomposition,
x = np.linspace(40, 120, 100) x_ = x/x.max() N = 6 phi = np.empty([len(x), N]) for i in range(len(x)): for l in range(N): phi[i, l] = x_[i]**(l) Q = phi.T @ phi
N here defines the dimensions of Q which is a positive definite matrix and as discussed in my previous article 'phi' is the basis functions for a polynomial model I had been using in a wider application of the Cholesky decomposition. I can then call my class acting on Q and calculate L and D which are class attributes with the following code,
LD = ldl(Q) L, D = LD.L, LD.D
To check everything is working properly I can print the difference between Q and LDL* which I correctly find to be 0 for all entries,
print( Q - L @ D @ L.T)
We can also plot the various components of the decomposition as a visual aid and this can be seen below.
Again this is a fun little task that can demonstrate the differences and similarities between a Cholesky decomposition and an LDL decomposition. It also serves as means for a better understanding of a linear algebra function that is often called but not understood entirely.
As always the code is available at; https://github.com/htjb/Blog/tree/master/LDLDecomposition. I have included also in this repository a modified version of the Cholesky function I previously wrote to demonstrate the mathematics at the beginning of this post and the close relationship between the two operations.
Thanks for reading!
Comments
Post a Comment