Exploiting matrix structure Tobin A. Driscoll Richard J. Braun 
A common situation in computation is that a problem has certain properties or structure that can be used to get a faster or more accurate solution. There are many properties of a matrix that can affect LU factorization. Here is an easy one to check.
Diagonal dominance has a computationally useful implication.
We next consider three other types of matrices that cause the LU factorization to be specialized in some important way.
2.9.1 Banded matrices ¶ A matrix A \mathbf{A} A upper bandwidth  b u b_u b u  j − i > b u j-i > b_u j − i > b u  A i j = 0 A_{ij}=0 A ij  = 0 lower bandwidth  b ℓ b_\ell b ℓ  i − j > b ℓ i-j > b_\ell i − j > b ℓ  A i j = 0 A_{ij}=0 A ij  = 0 bandwidth b u + b ℓ + 1 b_u+b_\ell+1 b u  + b ℓ  + 1 b u = b ℓ = 1 b_u=b_\ell=1 b u  = b ℓ  = 1 tridiagonal matrix 
Example 2.9.1  (Banded matrices)
Example 2.9.1 Here is a small tridiagonal matrix. Note that there is one fewer element on the super- and subdiagonals than on the main diagonal.
Tip
Use fill to create an array of a given size, with each element equal to a provided value.
A = diagm( -1 => [4, 3, 2, 1, 0], 
    0 => [2, 2, 0, 2, 1, 2], 
    1 => fill(-1, 5) )6×6 Matrix{Int64}:
 2  -1   0   0   0   0
 4   2  -1   0   0   0
 0   3   0  -1   0   0
 0   0   2   2  -1   0
 0   0   0   1   1  -1
 0   0   0   0   0   2 
We can extract the elements on any diagonal using the diag function. The main or central diagonal is numbered zero, above and to the right of that is positive, and below and to the left is negative.
Tip
The diag function extracts the elements from a specified diagonal of a matrix.
@show diag_main = diag(A);
@show diag_minusone = diag(A, -1);diag_main = diag(A) = [2, 2, 0, 2, 1, 2]
diag_minusone = diag(A, -1) = [4, 3, 2, 1, 0]
 The lower and upper bandwidths of A \mathbf{A} A 
6×6 LowerTriangular{Float64, Matrix{Float64}}:
 1.0   ⋅     ⋅        ⋅         ⋅    ⋅ 
 2.0  1.0    ⋅        ⋅         ⋅    ⋅ 
 0.0  0.75  1.0       ⋅         ⋅    ⋅ 
 0.0  0.0   2.66667  1.0        ⋅    ⋅ 
 0.0  0.0   0.0      0.214286  1.0   ⋅ 
 0.0  0.0   0.0      0.0       0.0  1.0 
6×6 UpperTriangular{Float64, Matrix{Float64}}:
 2.0  -1.0   0.0    0.0       0.0       0.0
  ⋅    4.0  -1.0    0.0       0.0       0.0
  ⋅     ⋅    0.75  -1.0       0.0       0.0
  ⋅     ⋅     ⋅     4.66667  -1.0       0.0
  ⋅     ⋅     ⋅      ⋅        1.21429  -1.0
  ⋅     ⋅     ⋅      ⋅         ⋅        2.0 
Example 2.9.1 Here is a small tridiagonal matrix. Note that there is one fewer element on the super- and subdiagonals than on the main diagonal.
A = [ 2 -1  0  0  0  0
      4  2 -1  0  0  0
      0  3  0 -1  0  0
      0  0  2  2 -1  0
      0  0  0  1  1 -1
      0  0  0  0  0  2 ];
We can extract the elements on any diagonal using the diag function. The main or central diagonal is numbered zero, above and to the right of that is positive, and below and to the left is negative.
Tip
The diag function extracts the elements from a specified diagonal of a matrix.
diag_main = diag(A, 0)'
diag_plusone = diag(A, 1)'
diag_minusone = diag(A,-1)'A = A + diag([5 8 6 7], 2)The lower and upper bandwidths of A \mathbf{A} A 
Example 2.9.1 Here is a matrix with both lower and upper bandwidth equal to one. Such a matrix is called tridiagonal.
A = array([ 
    [2, -1,  0,  0,  0,  0],
    [4,  2, -1,  0,  0,  0],
    [0,  3,  0, -1,  0,  0],
    [0,  0,  2,  2, -1,  0],
    [0,  0,  0,  1,  1, -1],
    [0,  0,  0,  0,  0,  2 ]
    ])
We can extract the elements on any diagonal using the diag function from numpy. The “main” or central diagonal is numbered zero, above and to the right of that is positive, and below and to the left is negative.
We can also construct matrices by specifying a diagonal with the diag function.
A = A + diag([pi, 8, 6, 7], 2)
print(A)[[ 2.         -1.          3.14159265  0.          0.          0.        ]
 [ 4.          2.         -1.          8.          0.          0.        ]
 [ 0.          3.          0.         -1.          6.          0.        ]
 [ 0.          0.          2.          2.         -1.          7.        ]
 [ 0.          0.          0.          1.          1.         -1.        ]
 [ 0.          0.          0.          0.          0.          2.        ]]
 L, U = FNC.lufact(A)
print(L)[[1.         0.         0.         0.         0.         0.        ]
 [2.         1.         0.         0.         0.         0.        ]
 [0.         0.75       1.         0.         0.         0.        ]
 [0.         0.         0.36614016 1.         0.         0.        ]
 [0.         0.         0.         0.21915497 1.         0.        ]
 [0.         0.         0.         0.         0.         1.        ]]
 [[ 2.         -1.          3.14159265  0.          0.          0.        ]
 [ 0.          4.         -7.28318531  8.          0.          0.        ]
 [ 0.          0.          5.46238898 -7.          6.          0.        ]
 [ 0.          0.          0.          4.56298115 -3.19684099  7.        ]
 [ 0.          0.          0.          0.          1.70060359 -2.53408479]
 [ 0.          0.          0.          0.          0.          2.        ]]
 Observe above that the lower and upper bandwidths of A \mathbf{A} A 
If row pivoting is not used, the L \mathbf{L} L U \mathbf{U} U A \mathbf{A} A 
In order to exploit the savings offered by sparsity, we would need to make modifications to Function 2.4.1 sparse . Sparse matrices are covered in more detail in Chapters 7 and 8.
2.9.2 Symmetric matrices ¶ Recall from Definition 2.2.2 A \mathbf{A} A A T = A \mathbf{A}^T = \mathbf{A} A T = A 
In A = L U \mathbf{A}=\mathbf{L}\mathbf{U} A = LU L \mathbf{L} L U \mathbf{U} U 
A = L D L T , \mathbf{A}=\mathbf{L}\mathbf{D}\mathbf{L}^T, A = LD L T , where L \mathbf{L} L D \mathbf{D} D (2.4.4) 
Theorem 2.9.2  (Linear combination of outer products)
Let D \mathbf{D} D n × n n\times n n × n d 1 , d 2 , … , d n d_1,d_2,\ldots,d_n d 1  , d 2  , … , d n  A \mathbf{A} A B \mathbf{B} B n × n n\times n n × n A \mathbf{A} A a 1 , … , a n \mathbf{a}_1,\dots,\mathbf{a}_n a 1  , … , a n  B \mathbf{B} B b 1 T , … , b n T \mathbf{b}_1^T,\dots,\mathbf{b}_n^T b 1 T  , … , b n T  
A D B = ∑ k = 1 n d k a k b k T . \mathbf{A}\mathbf{D}\mathbf{B} = \sum_{k=1}^n d_k \mathbf{a}_k \mathbf{b}_k^T. ADB = k = 1 ∑ n  d k  a k  b k T  . Let’s derive the LDLT ^T T 
Example 2.9.2  (Symmetric LDL
T ^T T  factorization)
Example 2.9.2 We begin with a symmetric A \mathbf{A} A 
A₁ = [  2     4     4     2
        4     5     8    -5
        4     8     6     2
        2    -5     2   -26 ];
We won’t use pivoting, so the pivot element is at position (1,1). This will become the first element on the diagonal of D \mathbf{D} D L \mathbf{L} L 
L = diagm(ones(4))
d = zeros(4)
d[1] = A₁[1, 1]
L[:, 1] = A₁[:, 1] / d[1]
A₂ = A₁ - d[1] * L[:, 1] * L[:, 1]'4×4 Matrix{Float64}:
 0.0   0.0   0.0    0.0
 0.0  -3.0   0.0   -9.0
 0.0   0.0  -2.0   -2.0
 0.0  -9.0  -2.0  -28.0 
We are now set up the same way for the submatrix in rows and columns 2–4.
d[2] = A₂[2, 2]
L[:, 2] = A₂[:, 2] / d[2]
A₃ = A₂ - d[2] * L[:, 2] * L[:, 2]'4×4 Matrix{Float64}:
 0.0  0.0   0.0   0.0
 0.0  0.0   0.0   0.0
 0.0  0.0  -2.0  -2.0
 0.0  0.0  -2.0  -1.0 
We continue working our way down the diagonal.
d[3] = A₃[3, 3]
L[:, 3] = A₃[:, 3] / d[3]
A₄ = A₃ - d[3] * L[:, 3] * L[:, 3]'
d[4] = A₄[4, 4]
@show d;
Ld = [2.0, -3.0, -2.0, 1.0]
 4×4 Matrix{Float64}:
 1.0  -0.0  -0.0  0.0
 2.0   1.0  -0.0  0.0
 2.0  -0.0   1.0  0.0
 1.0   3.0   1.0  1.0 
We have arrived at the desired factorization, which we can validate:
opnorm(A₁ - (L * diagm(d) * L'))Example 2.9.2 We begin with a symmetric A \mathbf{A} A 
A_1 = [ 2     4     4     2
        4     5     8    -5
        4     8     6     2
        2    -5     2   -26 ];
We won’t use pivoting, so the pivot element is at position (1,1). This will become the first element on the diagonal of D \mathbf{D} D L \mathbf{L} L 
L = eye(4);
d = zeros(4, 1);
d(1) = A_1(1, 1);
L(:, 1) = A_1(:, 1) / d(1);
A_2 = A_1 - d(1) * L(:, 1) * L(:, 1)'We are now set up the same way for the submatrix in rows and columns 2–4.
d(2) = A_2(2, 2);
L(:, 2) = A_2(:, 2) / d(2);
A_3 = A_2 - d(2) * L(:, 2) * L(:, 2)'We continue working our way down the diagonal.
d(3) = A_3(3, 3);
L(:, 3) = A_3(:, 3) / d(3);
A_4 = A_3 - d(3) * L(:, 3) * L(:, 3)'
d(4) = A_4(4, 4);
d
LWe have arrived at the desired factorization, which we can validate:
norm(A_1 - (L * diag(d) * L'))Example 2.9.2 We begin with a symmetric A \mathbf{A} A 
A_1 = array([
    [2,     4,     4,     2],
    [4,     5,     8,    -5],
    [4,     8,     6,     2],
    [2,    -5,     2,   -26]
    ])
We won’t use pivoting, so the pivot element is at position (1,1). This will become the first element on the diagonal of D \mathbf{D} D L \mathbf{L} L 
L = eye(4)
d = zeros(4)
d[0] = A_1[0, 0]
L[:, 0] = A_1[:, 0] / d[0]
A_2 = A_1 - d[0] * outer(L[:, 0], L[:, 0])
print(A_2)[[  0.   0.   0.   0.]
 [  0.  -3.   0.  -9.]
 [  0.   0.  -2.  -2.]
 [  0.  -9.  -2. -28.]]
 We are now set up the same way for the submatrix in rows and columns 2–4.
d[1] = A_2[1, 1]
L[:, 1] = A_2[:, 1] / d[1]
A_3 = A_2 - d[1] * outer(L[:, 1], L[:, 1])
print(A_3)[[ 0.  0.  0.  0.]
 [ 0.  0.  0.  0.]
 [ 0.  0. -2. -2.]
 [ 0.  0. -2. -1.]]
 We continue working our way down the diagonal.
d[2] = A_3[2, 2]
L[:, 2] = A_3[:, 2] / d[2]
A_4 = A_3 - d[2] * outer(L[:, 2], L[:, 2])
print(A_4)[[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 1.]]
 We have arrived at the desired factorization.
d[3] = A_4[3, 3]
print("diagonal of D:")
print(d)
print("L:")
print(L)diagonal of D:
[ 2. -3. -2.  1.]
L:
[[ 1. -0. -0.  0.]
 [ 2.  1. -0.  0.]
 [ 2. -0.  1.  0.]
 [ 1.  3.  1.  1.]]
 This should be comparable to machine roundoff:
print(norm(A_1 - (L @ diag(d) @ L.T), 2) / norm(A_1))In practice, we don’t actually have to carry out any arithmetic in the upper triangle of A \mathbf{A} A T ^T T 
Just as pivoting is necessary to stabilize LU factorization, the LDLT ^T T 
2.9.3 Symmetric positive definite matrices ¶ Suppose that A \mathbf{A} A n × n n\times n n × n x ∈ R n \mathbf{x}\in\mathbb{R}^n x ∈ R n x T A x \mathbf{x}^T\mathbf{A}\mathbf{x} x T Ax 1 × n 1\times n 1 × n n × n n\times n n × n n × 1 n\times 1 n × 1 quadratic form . It can be expressed as
An SPD  matrixsymmetric positive definite matrix , is a real n × n n\times n n × n A \mathbf{A} A 
x T A x > 0   \mathbf{x}^T \mathbf{A} \mathbf{x} > 0 x T Ax > 0 for all nonzero x ∈ R n \mathbf{x}\in\mathbb{R}^n x ∈ R n 
The definiteness property is usually difficult to check directly from the definition. There are some equivalent conditions, though. For instance, a symmetric matrix is positive definite if and only if its eigenvalues are all real positive numbers. SPD  matrices have important properties and appear in applications in which the definiteness is known for theoretical reasons.
Let us consider what definiteness means to the LDLT ^T T 
0 < x T A x = x T L D L T x = z T D z ,   0 < \mathbf{x}^T\mathbf{A}\mathbf{x} = \mathbf{x}^T \mathbf{L} \mathbf{D} \mathbf{L}^T \mathbf{x} = \mathbf{z}^T \mathbf{D} \mathbf{z}, 0 < x T Ax = x T LD L T x = z T Dz , where z = L T x \mathbf{z}=\mathbf{L}^T \mathbf{x} z = L T x L \mathbf{L} L x = L − T z \mathbf{x}=\mathbf{L}^{-T}\mathbf{z} x = L − T z z = e k \mathbf{z}=\mathbf{e}_k z = e k  k = 1 , … , n k=1,\ldots,n k = 1 , … , n D k k > 0 D_{kk}>0 D kk  > 0 k k k [1] 
D = [ D 11 D 22 ⋱ D n n ] = [ D 11 D 22 ⋱ D n n ]   2 = ( D 1 / 2 ) 2 .   \mathbf{D} =
  \begin{bmatrix}
    D_{11} &        &        & \\
           & D_{22} &        & \\
           &        & \ddots & \\
           &        &        & D_{nn}
  \end{bmatrix}
=   \begin{bmatrix}
    \sqrt{D_{11}} &        &        & \\
           & \sqrt{D_{22}} &        & \\
           &        & \ddots & \\
           &        &        & \sqrt{D_{nn}}
  \end{bmatrix}^{\,2}
= \bigl( \mathbf{D}^{1/2} \bigr)^2. D = ⎣ ⎡  D 11   D 22   ⋱  D nn   ⎦ ⎤  = ⎣ ⎡  D 11    D 22    ⋱  D nn    ⎦ ⎤  2 = ( D 1/2 ) 2 . Now we have A = L D 1 / 2 D 1 / 2 L T = R T R \mathbf{A}=\mathbf{L}\mathbf{D}^{1/2}\mathbf{D}^{1/2}\mathbf{L}^T= \mathbf{R}^T \mathbf{R} A = L D 1/2 D 1/2 L T = R T R R = D 1 / 2 L T \mathbf{R} =\mathbf{D}^{1/2}\mathbf{L}^T R = D 1/2 L T 
While the unpivoted LDLT ^T T SPD  case one can prove that pivoting is not necessary for the existence nor the stability of the Cholesky factorization.
The speed and stability of the Cholesky factorization make it the top choice for solving linear systems with SPD  matrices. As a side benefit, the Cholesky algorithm fails (in the form of an imaginary square root or division by zero) if and only if the matrix A \mathbf{A} A 
Example 2.9.3  (Cholesky factorization)
Example 2.9.3 A randomly chosen matrix is extremely unlikely to be symmetric. However, there is a simple way to symmetrize one.
A = rand(1.0:9.0, 4, 4)
B = A + A'4×4 Matrix{Float64}:
  8.0  17.0  11.0  8.0
 17.0   8.0  11.0  9.0
 11.0  11.0  18.0  9.0
  8.0   9.0   9.0  2.0 
Similarly, a random symmetric matrix is unlikely to be positive definite. The Cholesky algorithm always detects a non-PD matrix by quitting with an error.
Tip
The cholesky function computes a Cholesky factorization if possible, or throws an error for a non-positive-definite matrix. However, it does not  check for symmetry.
cholesky(B)    # throws an errorPosDefException: matrix is not positive definite; Factorization failed.
Stacktrace:
  [1]  checkpositivedefinite 
     @   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ factorization.jl:68  [inlined] 
  [2]  #cholesky!#163 
     @   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ cholesky.jl:269  [inlined] 
  [3]  cholesky! 
     @   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ cholesky.jl:267  [inlined] 
  [4]  #cholesky!#164 
     @   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ cholesky.jl:301  [inlined] 
  [5]  cholesky!  (repeats 2 times) 
     @   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ cholesky.jl:295  [inlined] 
  [6]  _cholesky 
     @   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ cholesky.jl:411  [inlined] 
  [7]  cholesky ( A :: Matrix {Float64} , :: NoPivot;  check :: Bool ) 
     @   LinearAlgebra   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ cholesky.jl:401 
  [8]  cholesky 
     @   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ cholesky.jl:401  [inlined] 
  [9]  cholesky ( A :: Matrix {Float64} ) 
     @   LinearAlgebra   ~/.julia/juliaup/julia-1.11.5+0.aarch64.apple.darwin14/share/julia/stdlib/v1.11/LinearAlgebra/src/ cholesky.jl:401 
 [10] top-level scope
     @   In[139]:1 It’s not hard to manufacture an SPD  matrix to try out the Cholesky factorization.
B = A' * A
cf = cholesky(B)Cholesky{Float64, Matrix{Float64}}
U factor:
4×4 UpperTriangular{Float64, Matrix{Float64}}:
 12.5698  10.1036   12.0925    5.64846
   ⋅       6.55116   0.430883  0.905241
   ⋅        ⋅        4.19363   4.12678
   ⋅        ⋅         ⋅        0.495176 
What’s returned is a factorization object. Another step is required to extract the factor as a matrix:
4×4 UpperTriangular{Float64, Matrix{Float64}}:
 12.5698  10.1036   12.0925    5.64846
   ⋅       6.55116   0.430883  0.905241
   ⋅        ⋅        4.19363   4.12678
   ⋅        ⋅         ⋅        0.495176 
Here we validate the factorization:
opnorm(R' * R - B) / opnorm(B)Example 2.9.3 A randomly chosen matrix is extremely unlikely to be symmetric. However, there is a simple way to symmetrize one.
A = magic(4) + eye(4);
B = A + A'Similarly, a random symmetric matrix is unlikely to be positive definite. The Cholesky algorithm always detects a non-PD matrix by quitting with an error.
Tip
The chol function computes a Cholesky factorization if possible, or throws an error for a non-positive-definite matrix.
chol(B)    % throws an errorError using chol
Matrix must be positive definite. It’s not hard to manufacture an SPD  matrix to try out the Cholesky factorization.
Here we validate the factorization:
norm(R' * R - B) / norm(B)Example 2.9.3 A randomly chosen matrix is extremely unlikely to be symmetric. However, there is a simple way to symmetrize one.
A = 1.0 + floor(9 * random.rand(4, 4))
B = A + A.T
print(B)[[ 4. 11.  9.  6.]
 [11. 10.  8.  7.]
 [ 9.  8. 12. 11.]
 [ 6.  7. 11.  2.]]
 Similarly, a random symmetric matrix is unlikely to be positive definite. The Cholesky algorithm always detects a non-PD matrix by quitting with an error.
from scipy.linalg import cholesky
cholesky(B)    # raises an error: not positive definite--------------------------------------------------------------------------- 
 LinAlgError                                Traceback (most recent call last)
 Cell   In[138] , line 2 
       1   from   scipy . linalg   import  cholesky
 ---->  2   cholesky ( B )      # raises an error: not positive definite 
 File  ~/miniforge3/envs/myst/lib/python3.13/site-packages/scipy/linalg/_decomp_cholesky.py:101 , in  cholesky (a, lower, overwrite_a, check_finite) 
      46   def   cholesky (a, lower= False , overwrite_a= False , check_finite= True ):
      47        """ 
      48       Compute the Cholesky decomposition of a matrix. 
      49  
    (...)      99  
     100       """ 
 -->  101      c, lower =  _cholesky ( a ,   lower = lower ,   overwrite_a = overwrite_a ,   clean = True , 
     102                             check_finite = check_finite ) 
     103       return  c
 File  ~/miniforge3/envs/myst/lib/python3.13/site-packages/scipy/linalg/_decomp_cholesky.py:38 , in  _cholesky (a, lower, overwrite_a, clean, check_finite) 
      36  c, info = potrf(a1, lower=lower, overwrite_a=overwrite_a, clean=clean)
      37   if  info >  0 :
 --->  38       raise  LinAlgError( " %d -th leading minor of the array is not positive  " 
      39                         " definite "  % info)
      40   if  info <  0 :
      41       raise   ValueError ( f ' LAPACK reported an illegal value in  { -info } -th argument ' 
      42                        ' on entry to  " POTRF " . ' )
 LinAlgError : 2-th leading minor of the array is not positive definite It’s not hard to manufacture an SPD  matrix to try out the Cholesky factorization:
B = A.T @ A
R = cholesky(B)
print(R)[[9.89949494 7.17208307 9.89949494 2.82842712]
 [0.         4.19061147 5.01120186 1.602221  ]
 [0.         0.         5.82132767 4.63312643]
 [0.         0.         0.         1.99173977]]
 print(norm(R.T @ R - B) / norm(B))2.9.4 Exercises ¶ ⌨ Using Function 2.4.1 luband that accepts as inputs a banded matrix and its upper and lower bandwidths, and returns the LU factors (without pivoting). The function should avoid doing arithmetic using the locations that are known to stay zero. (Hint: Refer to the more efficient form of lufact given in Efficiency of matrix computations . You only need to limit the rows and columns that are accessed within the loop.)
Test your function on a 7 × 7 7 \times 7 7 × 7 
A i j = { 1 i + j , − 1 ≤ i − j ≤ 2 , 0 otherwise. A_{ij} = \begin{cases} \frac{1}{i+j}, & -1 \le i-j \le 2,\\ 
0 & \text{otherwise.} \end{cases} A ij  = { i + j 1  , 0  − 1 ≤ i − j ≤ 2 , otherwise.  ⌨ (Julia-specific)  The Tridiagonal matrix type invokes a specialized algorithm for solving a linear system.
(a)  Set n=2000 and t=0.  In a loop that runs 100 times, generate a linear system via
A = triu( tril(rand(n, n),1), -1)
b = ones(n)
x = A \ bUsing @elapsed, increment t by the time it takes to perform A\b. Print out the final value of t.
(b)  Repeat the experiment of part (a), but generate the matrix via
A = Tridiagonal(rand(n,n))What is the ratio of running times for part (a) and (b)?
(c)  Now perform the experiment of part (b) for n = 2000 , 2400 , 2800 , … , 4000 n=2000,2400,2800,\ldots,4000 n = 2000 , 2400 , 2800 , … , 4000 n n n n n n O ( n ) O(n) O ( n ) O ( n 2 ) O(n^2) O ( n 2 ) O ( n 3 ) O(n^3) O ( n 3 ) n n n 
✍ Prove that if A \mathbf{A} A A T A \mathbf{A}^T\mathbf{A} A T A SPD . (Hint: First, check symmetry. Then show that x T A T A x ≥ 0 \mathbf{x}^T\mathbf{A}^T\mathbf{A}\mathbf{x} \ge 0 x T A T Ax ≥ 0 x \mathbf{x} x x ≠ 0 \mathbf{x}\neq \boldsymbol{0} x  = 0 
Except for this diagonal, positive definite case, it’s not trivial to define the square root of a matrix, so don’t generalize the notation used here.