Main Content

Block LDL' factorization for Hermitian indefinite matrices

`L = ldl(A)`

[L,D] = ldl(A)

[L,D,P] = ldl(A)

[L,D,p] = ldl(A,'vector')

[U,D,P] = ldl(A,'upper')

[U,D,p] = ldl(A,'upper','vector')

[L,D,P,S] = ldl(A)

[L,D,P,S] = LDL(A,THRESH)

[U,D,p,S] = LDL(A,THRESH,'upper','vector')

`L = ldl(A)`

returns only
the permuted lower triangular matrix `L`

as in the
two-output form. The permutation information is lost, as is the block
diagonal factor `D`

. By default, `ldl`

references
only the diagonal and lower triangle of `A`

, and
assumes that the upper triangle is the complex conjugate transpose
of the lower triangle. Therefore `[L,D,P] = ldl(TRIL(A))`

and ```
[L,D,P]
= ldl(A)
```

both return the exact same factors. Note, this syntax
is not valid for sparse `A`

.

`[L,D] = ldl(A)`

stores a
block diagonal matrix `D`

and a permuted lower triangular
matrix in `L`

such that `A = L*D*L'`

.
The block diagonal matrix `D`

has 1-by-1 and 2-by-2
blocks on its diagonal. Note, this syntax is not valid for sparse `A`

.

`[L,D,P] = ldl(A)`

returns
unit lower triangular matrix `L`

, block diagonal `D`

,
and permutation matrix `P`

such that ```
P'*A*P
= L*D*L'
```

. This is equivalent to `[L,D,P] = ldl(A,'matrix')`

.

`[L,D,p] = ldl(A,'vector')`

returns
the permutation information as a vector, `p`

, instead
of a matrix. The `p`

output is a row vector such
that `A(p,p) = L*D*L'`

.

`[U,D,P] = ldl(A,'upper')`

references
only the diagonal and upper triangle of `A`

and assumes
that the lower triangle is the complex conjugate transpose of the
upper triangle. This syntax returns a unit upper triangular matrix `U`

such
that `P'*A*P = U'*D*U`

(assuming that `A`

is
Hermitian, and not just upper triangular). Similarly, ```
[L,D,P]
= ldl(A,'lower')
```

gives the default behavior.

`[U,D,p] = ldl(A,'upper','vector')`

returns
the permutation information as a vector, `p`

, as
does `[L,D,p] = ldl(A,'lower','vector')`

. `A`

must
be a full matrix.

`[L,D,P,S] = ldl(A)`

returns unit lower triangular
matrix `L`

, block diagonal `D`

, permutation matrix
`P`

, and scaling matrix `S`

such that ```
P'*S*A*S*P
= L*D*L'
```

. This syntax is only available for real sparse matrices, and only the
lower triangle of `A`

is referenced.

`[L,D,P,S] = LDL(A,THRESH)`

uses `THRESH`

as the pivot
tolerance in the algorithm. `THRESH`

must be a double scalar lying in the
interval `[0, 0.5]`

. The default value for `THRESH`

is
`0.01`

. Using smaller values of `THRESH`

may give faster
factorization times and fewer entries, but may also result in a less stable factorization.
This syntax is available only for real sparse matrices.

`[U,D,p,S] = LDL(A,THRESH,'upper','vector')`

sets
the pivot tolerance and returns upper triangular `U`

and
permutation vector `p`

as described above.

These examples illustrate the use of the various forms of the `ldl`

function,
including the one-, two-, and three-output form, and the use of the `vector`

and `upper`

options.
The topics covered are:

Before running any of these examples, you will need to generate the following positive definite and indefinite Hermitian matrices:

A = full(delsq(numgrid('L', 10))); B = gallery('uniformdata',10,0); M = [eye(10) B; B' zeros(10)];

The structure of `M`

here is very common in
optimization and fluid-flow problems, and `M`

is
in fact indefinite. Note that the positive definite matrix `A`

must
be full, as `ldl`

does not accept sparse arguments.

The two-output form of `ldl`

returns `L`

and `D`

such
that `A-(L*D*L')`

is small, `L`

is
permuted unit lower triangular, and `D`

is a block
2-by-2 diagonal. Note also that, because `A`

is positive
definite, the diagonal of `D`

is all positive:

[LA,DA] = ldl(A); fprintf(1, ... 'The factorization error ||A - LA*DA*LA''|| is %g\n', ... norm(A - LA*DA*LA')); neginds = find(diag(DA) < 0)

Given `a`

`b`

, solve `Ax=b`

using `LA`

, `DA`

:

bA = sum(A,2); x = LA'\(DA\(LA\bA)); fprintf(... 'The absolute error norm ||x - ones(size(bA))|| is %g\n', ... norm(x - ones(size(bA))));

The three output form returns the permutation matrix as well,
so that `L`

is in fact unit lower triangular:

[Lm, Dm, Pm] = ldl(M); fprintf(1, ... 'The error norm ||Pm''*M*Pm - Lm*Dm*Lm''|| is %g\n', ... norm(Pm'*M*Pm - Lm*Dm*Lm')); fprintf(1, ... 'The difference between Lm and tril(Lm) is %g\n', ... norm(Lm - tril(Lm)));

Given `b`

, solve `Mx=b`

using `Lm`

, `Dm`

,
and `Pm`

:

bM = sum(M,2); x = Pm*(Lm'\(Dm\(Lm\(Pm'*bM)))); fprintf(... 'The absolute error norm ||x - ones(size(b))|| is %g\n', ... norm(x - ones(size(bM))));

`D`

is a block diagonal matrix with 1-by-1
blocks and 2-by-2 blocks. That makes it a special case of a tridiagonal
matrix. When the input matrix is positive definite, `D`

is
almost always diagonal (depending on how definite the matrix is).
When the matrix is indefinite however, `D`

may be
diagonal or it may express the block structure. For example, with `A`

as
above, `DA`

is diagonal. But if you shift `A`

just
a bit, you end up with an indefinite matrix, and then you can compute
a `D`

that has the block structure.

figure; spy(DA); title('Structure of D from ldl(A)'); [Las, Das] = ldl(A - 4*eye(size(A))); figure; spy(Das); title('Structure of D from ldl(A - 4*eye(size(A)))');

Like the `lu`

function, `ldl`

accepts
an argument that determines whether the function returns a permutation
vector or permutation matrix. `ldl`

returns the
latter by default. When you select `'vector'`

, the
function executes faster and uses less memory. For this reason, specifying
the `'vector'`

option is recommended. Another thing
to note is that indexing is typically faster than multiplying for
this kind of operation:

[Lm, Dm, pm] = ldl(M, 'vector'); fprintf(1, 'The error norm ||M(pm,pm) - Lm*Dm*Lm''|| is %g\n', ... norm(M(pm,pm) - Lm*Dm*Lm')); % Solve a system with this kind of factorization. clear x; x(pm,:) = Lm'\(Dm\(Lm\(bM(pm,:)))); fprintf('The absolute error norm ||x - ones(size(b))|| is %g\n', ... norm(x - ones(size(bM))));

Like the `chol`

function, `ldl`

accepts
an argument that determines which triangle of the input matrix is
referenced, and also whether `ldl`

returns a lower
(`L`

) or upper (`L'`

) triangular
factor. For dense matrices, there are no real savings with using the
upper triangular version instead of the lower triangular version:

Ml = tril(M); [Lml, Dml, Pml] = ldl(Ml, 'lower'); % 'lower' is default behavior. fprintf(1, ... 'The difference between Lml and Lm is %g\n', norm(Lml - Lm)); [Umu, Dmu, pmu] = ldl(triu(M), 'upper', 'vector'); fprintf(1, ... 'The difference between Umu and Lm'' is %g\n', norm(Umu - Lm')); % Solve a system using this factorization. clear x; x(pm,:) = Umu\(Dmu\(Umu'\(bM(pmu,:)))); fprintf(... 'The absolute error norm ||x - ones(size(b))|| is %g\n', ... norm(x - ones(size(bM))));

When specifying both the `'upper'`

and `'vector'`

options, `'upper'`

must
precede `'vector'`

in the argument list.

When using the `linsolve`

function,
you may experience better performance by exploiting the knowledge
that a system has a symmetric matrix. The matrices used in the examples
above are a bit small to see this so, for this example, generate a
larger matrix. The matrix here is symmetric positive definite, and
below we will see that with each bit of knowledge about the matrix,
there is a corresponding speedup. That is, the symmetric solver is
faster than the general solver while the symmetric positive definite
solver is faster than the symmetric solver:

Abig = full(delsq(numgrid('L', 30))); bbig = sum(Abig, 2); LSopts.POSDEF = false; LSopts.SYM = false; tic; linsolve(Abig, bbig, LSopts); toc; LSopts.SYM = true; tic; linsolve(Abig, bbig, LSopts); toc; LSopts.POSDEF = true; tic; linsolve(Abig, bbig, LSopts); toc;

[1] Ashcraft, C., R.G. Grimes, and J.G. Lewis.
“Accurate Symmetric Indefinite Linear Equations Solvers.” *SIAM
J. Matrix Anal. Appl.* Vol. 20. Number 2, 1998, pp. 513–561.

[2] Duff, I. S. "MA57 — A new code for the solution of sparse symmetric definite and indefinite systems." Technical Report RAL-TR-2002-024, Rutherford Appleton Laboratory, 2002.