Perturbed cases

We consider the following system:

\[\begin{split}\begin{bmatrix} c_0 & u_0 & 0 & \cdots & 0 & 0 & 0 & \cdots & 0 & 0 & l_0 \\ l_1 & c_1 & u_1 & \cdots & 0 & 0 & 0 & \cdots & 0 & 0 & 0 \\ 0 & l_2 & c_2 & \cdots & 0 & 0 & 0 & \cdots & 0 & 0 & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots & & \vdots & \vdots & \vdots \\ 0 & 0 & 0 & \cdots & c_{i-1} & u_{i-1} & 0 & \cdots & 0 & 0 & 0 \\ 0 & 0 & 0 & \cdots & l_{i } & c_{i } & u_{i } & \cdots & 0 & 0 & 0 \\ 0 & 0 & 0 & \cdots & 0 & l_{i+1} & c_{i+1} & \cdots & 0 & 0 & 0 \\ \vdots & \vdots & \vdots & & \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots \\ 0 & 0 & 0 & \cdots & 0 & 0 & 0 & \cdots & c_{n-3} & u_{n-3} & 0 \\ 0 & 0 & 0 & \cdots & 0 & 0 & 0 & \cdots & l_{n-2} & c_{n-2} & u_{n-2} \\ u_{n-1} & 0 & 0 & \cdots & 0 & 0 & 0 & \cdots & 0 & l_{n-1} & c_{n-1} \end{bmatrix} \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ \vdots \\ x_{i-1} \\ x_{i } \\ x_{i+1} \\ \vdots \\ x_{n-3} \\ x_{n-2} \\ x_{n-1} \end{bmatrix} = \begin{bmatrix} q_0 \\ q_1 \\ q_2 \\ \vdots \\ q_{i-1} \\ q_{i } \\ q_{i+1} \\ \vdots \\ q_{n-3} \\ q_{n-2} \\ q_{n-1} \end{bmatrix},\end{split}\]

where, compare with the original tri-diagonal system, the top-right and the bottom-left corners have non-zero values (\(l_0\) and \(u_{n - 1}\), respectively), which appears in particular when periodic boundary conditions are imposed.

Since we have

\[ \begin{align}\begin{aligned}c_0 x_0 + u_0 x_1 & = q_0 - l_0 x_{n - 1},\\l_{n - 2} x_{n - 3} + c_{n - 2} x_{n - 2} & = q_{n - 2} - u_{n - 2} x_{n - 1},\end{aligned}\end{align} \]

at the \(0\)-th and the \(n - 2\)-th rows, we reformulate the system as

\[\begin{split}\begin{bmatrix} c_0 & u_0 & 0 & \cdots & 0 & 0 & 0 & \cdots & 0 & 0 & 0 \\ l_1 & c_1 & u_1 & \cdots & 0 & 0 & 0 & \cdots & 0 & 0 & 0 \\ 0 & l_2 & c_2 & \cdots & 0 & 0 & 0 & \cdots & 0 & 0 & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots & & \vdots & \vdots & \vdots \\ 0 & 0 & 0 & \cdots & c_{i-1} & u_{i-1} & 0 & \cdots & 0 & 0 & 0 \\ 0 & 0 & 0 & \cdots & l_{i } & c_{i } & u_{i } & \cdots & 0 & 0 & 0 \\ 0 & 0 & 0 & \cdots & 0 & l_{i+1} & c_{i+1} & \cdots & 0 & 0 & 0 \\ \vdots & \vdots & \vdots & & \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots \\ 0 & 0 & 0 & \cdots & 0 & 0 & 0 & \cdots & c_{n-4} & u_{n-4} & 0 \\ 0 & 0 & 0 & \cdots & 0 & 0 & 0 & \cdots & l_{n-3} & c_{n-3} & u_{n-3} \\ 0 & 0 & 0 & \cdots & 0 & 0 & 0 & \cdots & 0 & l_{n-2} & c_{n-2} \end{bmatrix} \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ \vdots \\ x_{i-1} \\ x_{i } \\ x_{i+1} \\ \vdots \\ x_{n-4} \\ x_{n-3} \\ x_{n-2} \end{bmatrix} = \begin{bmatrix} q_0 - l_0 x_{n-1} \\ q_1 \\ q_2 \\ \vdots \\ q_{i-1} \\ q_{i } \\ q_{i+1} \\ \vdots \\ q_{n-4} \\ q_{n-3} \\ q_{n-2} - u_{n-2} x_{n-1} \end{bmatrix}.\end{split}\]

Note that we kick out \(x_{n-1}\) from the left-hand side, and the resulting system is now tri-diagonal with the size of \(n - 1\). For notational simplicity, hereafter we write this system as

\[A_{ij} x_j = b_i.\]

Although this is a purely tri-diagonal system (i.e., without perturbed elements), we cannot perform forward sweep as an unknown \(x_{n - 1}\) exists in the right-hand-side vector. To circumvent this issue, we split the system into two sub problems:

\[ \begin{align}\begin{aligned}A_{ij} x_j^{\prime} & = b_i^{\prime},\\A_{ij} x_j^{\prime\prime} & = b_i^{\prime\prime},\end{aligned}\end{align} \]

where

\[\begin{split}b_i^{\prime} = \begin{bmatrix} q_0 \\ q_1 \\ q_2 \\ \vdots \\ q_{i-1} \\ q_{i } \\ q_{i+1} \\ \vdots \\ q_{n-4} \\ q_{n-3} \\ q_{n-2} \end{bmatrix}, b_i^{\prime\prime} = \begin{bmatrix} - l_0 \\ 0 \\ 0 \\ \vdots \\ 0 \\ 0 \\ 0 \\ \vdots \\ 0 \\ 0 \\ - u_{n-2} \end{bmatrix},\end{split}\]

which satisfy

\[ \begin{align}\begin{aligned}& x_j = x_j^{\prime} + x_{n-1} x_j^{\prime\prime},\\& b_i = b_i^{\prime} + x_{n-1} b_i^{\prime\prime},\end{aligned}\end{align} \]

and thus the solution \(x_j\) is found as the superposition of the solution of these two sub systems.

To find \(x_{n - 1}\), we look at the \(n - 1\)-th row of the original system:

\[u_{n - 1} x_0 + l_{n - 1} x_{n - 2} + c_{n - 1} x_{n - 1} = q_{n - 1}.\]

By assigning

\[ \begin{align}\begin{aligned}x_0 & = x_0^{\prime} + x_{n - 1} x_0^{\prime\prime},\\x_{n - 2} & = x_{n - 2}^{\prime} + x_{n - 1} x_{n - 2}^{\prime\prime},\end{aligned}\end{align} \]

we notice

\[\left( u_{n - 1} x_0^{\prime} + l_{n - 1} x_{n - 2}^{\prime} \right) + \left( u_{n - 1} x_0^{\prime\prime} + l_{n - 1} x_{n - 2}^{\prime\prime} + c_{n - 1} \right) x_{n - 1} = q_{n - 1},\]

and thus

\[x_{n - 1} = \frac{ q_{n - 1} - u_{n - 1} x_0^{\prime} - l_{n - 1} x_{n - 2}^{\prime} }{ c_{n - 1} + u_{n - 1} x_0^{\prime\prime} + l_{n - 1} x_{n - 2}^{\prime\prime} }.\]

To summarize, the perturbed system is solved as follows.

  1. Prepare the right-hand-side vector of a sub system \(b_i^{\prime\prime}\):

    src/tri_diagonal_solver.c
    66for (size_t i = 0; i < size - 1; i++) {
    67  w[i] =
    68      i ==        0 ? - l[       0]
    69    : i == size - 2 ? - u[size - 2]
    70    : 0.;
    71}
    
  2. Solve two sub systems:

    Forward sweep:

    src/tri_diagonal_solver.c
    73v[0] = u[0] / c[0];
    74q[0] = q[0] / c[0];
    75w[0] = w[0] / c[0];
    
    src/tri_diagonal_solver.c
    77for (size_t i = 1; i < size - 2; i++) {
    78  // assume positive-definite system
    79  //   to skip zero-division checks
    80  const double val = 1. / (c[i] - l[i] * v[i - 1]);
    81  v[i] = val *  u[i];
    82  q[i] = val * (q[i] - l[i] * q[i - 1]);
    83  w[i] = val * (w[i] - l[i] * w[i - 1]);
    84}
    
    src/tri_diagonal_solver.c
    86// NOTE: do the same thing but consider singularity (degeneracy)
    87const double val = c[size - 2] - l[size - 2] * v[size - 3];
    88if (DBL_EPSILON < fabs(val)) {
    89  q[size - 2] = 1. / val * (q[size - 2] - l[size - 2] * q[size - 3]);
    90  w[size - 2] = 1. / val * (w[size - 2] - l[size - 2] * w[size - 3]);
    91} else {
    92  // singular
    93  q[size - 2] = 0.;
    94  w[size - 2] = 0.;
    95}
    

    Backward substitution:

    src/tri_diagonal_solver.c
     97for (size_t i = size - 3; ; i--) {
     98  q[i] -= v[i] * q[i + 1];
     99  w[i] -= v[i] * w[i + 1];
    100  if (0 == i) {
    101    break;
    102  }
    103}
    
  3. Couple the result of two sub systems:

    src/tri_diagonal_solver.c
    105const double num = q[size - 1] - u[size - 1] * q[0] - l[size - 1] * q[size - 2];
    106const double den = c[size - 1] + u[size - 1] * w[0] + l[size - 1] * w[size - 2];
    107q[size - 1] = fabs(den) < DBL_EPSILON ? 0. : num / den;
    108for (size_t i = 0; i < size - 1; i++) {
    109  q[i] = q[i] + q[size - 1] * w[i];
    110}