// // Author: Ryan Seghers // // Copyright (C) 2013 Ryan Seghers // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the irrevocable, perpetual, worldwide, and royalty-free // rights to use, copy, modify, merge, publish, distribute, sublicense, // display, perform, create derivative works from and/or sell copies of // the Software, both in source and object code form, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Diagnostics; using System.Text; namespace CubicSpline { /// /// A tri-diagonal matrix has non-zero entries only on the main diagonal, the diagonal above the main (super), and the /// diagonal below the main (sub). /// /// /// /// This is based on the wikipedia article: http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm /// /// /// The entries in the matrix on a particular row are A[i], B[i], and C[i] where i is the row index. /// B is the main diagonal, and so for an NxN matrix B is length N and all elements are used. /// So for row 0, the first two values are B[0] and C[0]. /// And for row N-1, the last two values are A[N-1] and B[N-1]. /// That means that A[0] is not actually on the matrix and is therefore never used, and same with C[N-1]. /// /// public class TriDiagonalMatrixF { /// /// The values for the sub-diagonal. A[0] is never used. /// public float[] A; /// /// The values for the main diagonal. /// public float[] B; /// /// The values for the super-diagonal. C[C.Length-1] is never used. /// public float[] C; /// /// The width and height of this matrix. /// public int N { get { return (A != null ? A.Length : 0); } } /// /// Indexer. Setter throws an exception if you try to set any not on the super, main, or sub diagonals. /// public float this[int row, int col] { get { int di = row - col; if (di == 0) { return B[row]; } else if (di == -1) { Debug.Assert(row < N - 1); return C[row]; } else if (di == 1) { Debug.Assert(row > 0); return A[row]; } else return 0; } set { int di = row - col; if (di == 0) { B[row] = value; } else if (di == -1) { Debug.Assert(row < N - 1); C[row] = value; } else if (di == 1) { Debug.Assert(row > 0); A[row] = value; } else { throw new ArgumentException("Only the main, super, and sub diagonals can be set."); } } } /// /// Construct an NxN matrix. /// public TriDiagonalMatrixF(int n) { this.A = new float[n]; this.B = new float[n]; this.C = new float[n]; } /// /// Produce a string representation of the contents of this matrix. /// /// Optional. For String.Format. Must include the colon. Examples are ':0.000' and ',5:0.00' /// Optional. Per-line indentation prefix. public string ToDisplayString(string fmt = "", string prefix = "") { if (this.N > 0) { var s = new StringBuilder(); string formatString = "{0" + fmt + "}"; for (int r = 0; r < N; r++) { s.Append(prefix); for (int c = 0; c < N; c++) { s.AppendFormat(formatString, this[r, c]); if (c < N - 1) s.Append(", "); } s.AppendLine(); } return s.ToString(); } else { return prefix + "0x0 Matrix"; } } /// /// Solve the system of equations this*x=d given the specified d. /// /// /// Uses the Thomas algorithm described in the wikipedia article: http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm /// Not optimized. Not destructive. /// /// Right side of the equation. public float[] Solve(float[] d) { int n = this.N; if (d.Length != n) { throw new ArgumentException("The input d is not the same size as this matrix."); } // cPrime float[] cPrime = new float[n]; cPrime[0] = C[0] / B[0]; for (int i = 1; i < n; i++) { cPrime[i] = C[i] / (B[i] - cPrime[i-1] * A[i]); } // dPrime float[] dPrime = new float[n]; dPrime[0] = d[0] / B[0]; for (int i = 1; i < n; i++) { dPrime[i] = (d[i] - dPrime[i-1]*A[i]) / (B[i] - cPrime[i - 1] * A[i]); } // Back substitution float[] x = new float[n]; x[n - 1] = dPrime[n - 1]; for (int i = n-2; i >= 0; i--) { x[i] = dPrime[i] - cPrime[i] * x[i + 1]; } return x; } } }