Use of static condensation to solve a linear system

When using the spectral element method (SEM) to solve partial differential equations (PDE), it is common for the basis function to have modes with support on boundaries (of elements) and modes with support only inside an element. This structure can be exploited when solving the linear system.

Here, as an exercise for implementing the solver using this technique, we will use it to solve a symmetric linear system that is decomposed as

\begin{equation} A\cdot x = \left[ \begin{matrix} A_{bb} & A_{bi} \\ A_{ib} & A_{ii} \\ \end{matrix}\right] \cdot \left\{ \begin{matrix} x_b \\ x_i \\ \end{matrix}\right\} = \left\{\begin{matrix} f_b \\ f_i \\\end{matrix}\right\} \end{equation}

where $A_{bb}$, $A_{bi}$, $A_{ib}$ and $A_{ii}$ are submatrices.

The second row can be solved for $x_i$:

$$ x_i = A_{ii}^{-1} f_i - A_{ii}^{-1} A_{ib} x_b $$

Substituting this on the first row:

$$ \left( A_{bb} - A_{bi}A_{ii}^{-1} A_{ib} \right) x_b = f_b - A_{bi}A_{ii}^{-1} f_i $$

This can be rewritten as

$$ A'_{bb} \cdot x_b = f'_b $$

where $A'_{bb} = A_{bb} - A_{bi}A_{ii}^{-1} A_{ib}$ and $f'_b = f_b - A_{bi}A_{ii}^{-1} f_i$.

With these transormations, if the linear system is symmetric and positive and definite, to solve a linear system, we need to use the following steps:

  • Compute $A_{ii}^{-1}$ (in reality do a Cholesky decomposition).
  • Compute $A_{bi} A_{ii}^{-1}$
  • Compute the Choleksy factorization of $A'_{bb} = A_{bb} - A_{bi}A_{ii}^{-1} \cdot A_{ib}$

To solve the linear system given a RHS (f):

  • Compute $f'_b = f_b - A_{bi}A_{ii}^{-1} f_i$.
  • Using the Cholesky factorization of $A'_{bb}$ compute $x_b$.
  • Now, using the Choleksy factorization of $A_{ii}$ compute $x_i = A_{ii}^{-1} f_i - A_{ii}^{-1} A_{ib} x_b$

A function to generate random symmetric matrices


In [862]:
function random_matrix(n, symm=true, diag=4.0)
    A = randn(n,n)
    for i = 1:n
        A[i,i] += diag
    end
    
    if symm
        A = (A + A') / 2
    end
    
    return A
end


Out[862]:
random_matrix (generic function with 3 methods)

Creating the matrix


In [863]:
N = 12
nb = 6
ni = N - nb


Out[863]:
6

In [864]:
A = [
 3.73295   0.351419  -0.534708   0.301579   0.558047;
  0.351419  6.48719    0.31895    0.198893   0.400289;
 -0.534708  0.31895    4.81739    0.309232  -0.103873;
  0.301579  0.198893   0.309232   5.411     -0.37619 ;
    0.558047  0.400289  -0.103873  -0.37619    3.44621 ]


A = random_matrix(N);

Obtaining the sub-matrices


In [865]:
Abb = A[1:nb, 1:nb];

In [866]:
Abi = A[1:nb, (nb+1):N];

In [867]:
Aib = A[(nb+1):N, 1:nb];

In [868]:
Aii = A[(nb+1):N, (nb+1):N];

Loading the appropriate modules


In [869]:
using ArrayViews
using Base.LinAlg.BLAS.gemm!
using Base.LinAlg.BLAS.gemv!
using Base.LinAlg.LAPACK.potrf!
using Base.LinAlg.LAPACK.potrs!

Cholesky decompostion of $A_{ii}$


In [870]:
potrf!('L', Aii);

Computing the matrix $A_{bi}A_{ii}^{-1}$. This is kindy of tricky. Remembering that $(A\cdot B)^T = A^T\cdot B^T$, and that the system is symmetric, if we compute $M = A_{ii}^{-1}\cdot A_{ib}$, then $M^T = \left(A_{ii}^{-1}\cdot A_{ib}\right)^T = A_{bi}\cdot A_{ii}^{-1}$


In [871]:
M = copy(Aib);
potrs!('L', Aii, M);

Now we need to compute $A'_{bb} = A_{bb} - A_{bi}A_{ii}^{-1} A_{ib} = A_{bb} - M^T\cdot A_{ib}$. BLAS makes it simple:


In [872]:
gemm!('T', 'N', -1.0, M, Aib, 1.0, Abb);

Cholesky decomposition of $A'_{bb}$:


In [873]:
potrf!('L', Abb);

Solving for a RHS

This is just a test. Let's just create any RHS:


In [874]:
f = float([1:N]);

In [875]:
fbi = copy(f);

In [876]:
fb = view(fbi, 1:nb)
fi = view(fbi, (nb+1):N);

Solving for boundary modes. First we need to correct the RHS: $f'_b = f_b - A_{bi}A_{ii}^{-1} f_i$.


In [877]:
gemv!('T', -1.0, M, fi, 1.0, fb);

Solve the boundary linear system:


In [878]:
potrs!('L', Abb, fb);

Variable fb contains $x_b$. Now solve the equation for $x_i$:


In [879]:
potrs!('L', Aii, fi)
gemv!('N', -1.0, M, fb, 1.0, fi);

In [880]:
x = copy(fbi)


Out[880]:
12-element Array{Float64,1}:
  -6.57535 
 -10.7013  
  -4.55052 
  32.3598  
  -0.339103
  -1.47511 
  -9.48928 
  21.0901  
  22.3424  
  27.3757  
  10.3601  
  13.1598  

Typical solution:


In [881]:
x0 = A\f


Out[881]:
12-element Array{Float64,1}:
 -1.11107 
  4.29908 
  1.02735 
 -6.4175  
  2.94724 
 10.5839  
  7.3945  
 -2.05764 
 -0.92681 
 -1.69711 
 -6.00506 
  0.288274

In [882]:
x0 - x


Out[882]:
12-element Array{Float64,1}:
   5.46427
  15.0003 
   5.57787
 -38.7773 
   3.28634
  12.059  
  16.8838 
 -23.1477 
 -23.2692 
 -29.0728 
 -16.3652 
 -12.8715 

Non symmetric matrices

In this case,

$$ A_{bi}^T \ne A_{ib} $$

Generating a nonrandom matrix B


In [883]:
B = random_matrix(N, false)


Out[883]:
12x12 Array{Float64,2}:
  4.45551    -0.906613   -0.823319   …   0.517527  -0.247443   -1.66694 
  1.43368     4.23531     0.408496      -1.21947   -0.0969868   0.120294
 -1.0214      0.196459    4.52213       -1.04513    0.305883    1.81411 
 -0.0252314   0.489283   -0.514954      -1.04699   -0.79036     0.197852
 -1.81308     0.191856    0.636454       1.38187    0.658353   -0.377176
 -0.581252   -0.0686093  -0.212306   …   0.295353   0.719947   -0.34562 
  0.434826   -0.593841    0.513981      -0.23067    0.539087   -1.09095 
 -1.39304    -0.191897   -0.377663      -1.40171   -0.177718   -0.251838
  0.759108   -1.1473      0.0818392      0.322833   0.148805   -0.256483
 -2.17155    -0.893701   -0.061407       4.77927    1.01985    -0.664695
 -1.24363    -0.859376    1.4048     …  -0.556837   2.80906    -1.1961  
 -0.279687    0.753762    1.39864        0.62235   -1.86631     5.22377 

In [884]:
Bbb = B[1:nb, 1:nb]
Bbi = B[1:nb, (nb+1):N]
Bib = B[(nb+1):N, 1:nb]
Bii = B[(nb+1):N, (nb+1):N];

In [886]:
using Base.LinAlg.LAPACK.getrf!

Bii, ipiv, info = getrf!(Bii);


Out[886]:
(
6x6 Array{Float64,2}:
  2.56014    -0.674388    0.505812  -0.23067     0.539087  -1.09095 
 -0.0119063   4.65643     0.156798  -1.40446    -0.1713    -0.264827
 -0.0949156  -0.136648    2.6524     0.109022    0.176565  -0.396219
 -0.133515   -0.0224777   0.122207   4.70358     1.0664    -0.767885
 -0.0220794   0.061648   -0.300763  -0.0940896   2.98496   -1.39528 
  0.297474    0.0613333  -0.177359   0.169327   -0.725444   4.61209 ,

[1,2,3,4,5,6],0)

We need to calculate the matrix $A_{bi}A_{ii}^{-1}AA_{ib}$. In this case the matrix is not symmetric and therefore we need to calculate the matrix

$$ M = \left(A_{bi}A_{ii}^{-1}\right)^T = \left(A_{ii}^{-1}\right)^T A_{bi}^T $$

In [887]:
using Base.LinAlg.LAPACK.getrs!

M2 = Bbi'

getrs!('T', Bii, ipiv, M2)


Out[887]:
6x6 Array{Float64,2}:
  0.769793  -0.821204   -0.306636   0.00996761  0.760765   -0.21825   
  0.366967   0.0827027   0.215796   0.209344    0.210894    0.36648   
 -0.418189   0.431749    1.18649    0.387315    0.427295    0.592604  
  0.260161  -0.269119   -0.248919  -0.207993    0.344091    0.145847  
 -0.436382   0.116143    0.497323  -0.220233    0.0725595   0.243173  
 -0.227997  -0.13094     0.434101  -0.00782678  0.178222    0.00925945

When calculating the RHS, we need to compute $M_b = A_{ii}^{-1}A_{ib} \ne M^T$. No problem just pre-calculate it and store for further use.


In [888]:
M2b = copy(Bib)
getrs!('N', Bii, ipiv, M2b);

In [889]:
gemm!('T', 'N', -1.0, M2, Bib, 1.0, Bbb);

In [890]:
M2' * Bib - MM
Bbb - Bbb2


Out[890]:
6x6 Array{Float64,2}:
  0.0           0.0           0.0          …   0.0           4.44089e-16
  0.0          -8.88178e-16  -1.11022e-16     -2.22045e-16  -1.11022e-16
 -2.22045e-16   2.22045e-16   0.0              0.0           0.0        
  0.0           1.11022e-16  -5.55112e-17      1.11022e-16  -5.55112e-17
  0.0           0.0          -1.11022e-16      0.0           2.22045e-16
 -2.22045e-16  -1.11022e-16   5.55112e-17  …   0.0           0.0        

In [891]:
Bbb, ipiv2, info = getrf!(Bbb)


Out[891]:
(
6x6 Array{Float64,2}:
  4.90792    -0.8295    -0.0982744  -0.281448   1.34586     2.3475   
  0.224462    4.40304    0.851991   -0.451879  -1.82632    -0.0424158
 -0.262591    0.244588   3.10886    -1.70836    0.873596   -0.906604 
 -0.154791    0.109491  -0.0880235   2.9913    -0.478555    0.687424 
 -0.262263    0.270795  -0.0953552  -0.317662   7.08327     1.55027  
  0.0399438   0.208374  -0.170298   -0.508367  -0.0796304   3.17599  ,

[1,2,3,4,5,6],0)

Solving for a RHS


In [892]:
g = float([1:N;])
gbi = copy(g)


Out[892]:
12-element Array{Float64,1}:
  1.0
  2.0
  3.0
  4.0
  5.0
  6.0
  7.0
  8.0
  9.0
 10.0
 11.0
 12.0

In [893]:
gb = view(gbi, 1:nb)
gi = view(gbi, (nb+1):N)


Out[893]:
6-element ContiguousView{Float64,1,Array{Float64,1}}:
  7.0
  8.0
  9.0
 10.0
 11.0
 12.0

In [894]:
gemv!('T', -1.0, M2, gi, 1.0, gb);

In [895]:
getrs!('N', Bbb, ipiv2, gb);
gb


Out[895]:
6-element ContiguousView{Float64,1,Array{Float64,1}}:
  2.35372 
  1.84105 
 -5.26632 
  0.956587
 -1.4038  
 -2.98605 

In [896]:
getrs!('N', Bii, ipiv, gi)
gemv!('N', -1.0, M2b, gb, 1.0, gi);

In [897]:
y = copy(gbi)
y0 = B\g

hcat(y0, y, y-y0)


Out[897]:
12x3 Array{Float64,2}:
  2.35372    2.35372   -2.66454e-15
  1.84105    1.84105    4.44089e-16
 -5.26632   -5.26632    7.10543e-15
  0.956587   0.956587   2.22045e-16
 -1.4038    -1.4038    -6.66134e-16
 -2.98605   -2.98605    3.9968e-15 
  3.61448    3.61448   -4.44089e-16
  2.06506    2.06506    4.44089e-16
  4.6023     4.6023    -4.44089e-15
  0.575544   0.575544   1.22125e-15
 13.4291    13.4291    -5.32907e-15
  8.25518    8.25518   -5.32907e-15

In [ ]: