From 652bbc482842faee992afd6a4f43e41c265f5e12 Mon Sep 17 00:00:00 2001 From: jeettrivedi Date: Fri, 14 Oct 2022 01:04:43 -0400 Subject: [PATCH] Added the Tridiagonal Algorithm. --- README.md | 1 + docs/README.md | 94 +++++- docs/index.html | 293 +++++++++++++++++- src/algorithms/math/index.js | 4 +- src/algorithms/math/tridiagonal_algorithm.js | 89 ++++++ .../math/testTridiagonalAlgorithm.js | 80 +++++ 6 files changed, 557 insertions(+), 4 deletions(-) create mode 100644 src/algorithms/math/tridiagonal_algorithm.js create mode 100644 test/algorithms/math/testTridiagonalAlgorithm.js diff --git a/README.md b/README.md index ce93985d..56001a9b 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ Various Math algorithms: - [Fast Exponentiation](https://github.com/manrajgrover/algorithms-js/blob/master/src/algorithms/math/fast_exp.js) - [GCD](https://github.com/manrajgrover/algorithms-js/blob/master/src/algorithms/math/gcd.js) - [LCM](https://github.com/manrajgrover/algorithms-js/blob/master/src/algorithms/math/lcm.js) +- [Tridiagonal Algorithm](https://github.com/manrajgrover/algorithms-js/blob/master/src/algorithms/math/tridiagonal_algorithm.js) #### String Various String algorithms: diff --git a/docs/README.md b/docs/README.md index 0f4eb555..1b4a0f6d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# [algorithms-js](https://github.com/manrajgrover/algorithms-js#readme) *0.0.8* +# [algorithms-js](https://github.com/manrajgrover/algorithms-js#readme) *0.0.13* > Algorithms Library in JavaScript @@ -254,6 +254,35 @@ Calculates LCM of two numbers +### src/algorithms/math/matrix_determinant.js + + +#### matrixDeterminant(M) + +Calculates the determinant of a non-singular matrix +with real entries using Bareiss's algorithm. + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| M | `Array` | Matrix with real entries. |   | + + + + +##### Returns + + +- `Number` determinant of the matrix M. +Reference: https://en.wikipedia.org/wiki/Bareiss_algorithm + + + + ### src/algorithms/math/modular_inverse.js @@ -306,6 +335,69 @@ Calculates modular inverse of a number +### src/algorithms/math/tridiagonal_algorithm.js + + +#### extractTridiagonalCoefficients(A, pad) + +Extracts the tridiagonal coefficients from +a square matrix A. Pads the upper and lower +diagonal arrays to ensure all three diagonals +are of equal length. + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| A | `Array` | Square matrix. |   | +| pad | `boolean` | If true, pads lower and upper diagonal arrays. |   | + + + + +##### Returns + + +- `Object` Returns object with keys `lowerDiagonal`, `centralDiagonal` and `upperDiagonal`. + + + +#### tridiagonalAlgorithm(A, d) + +Solves a linear system Ax=b where A is a tridiagonal matrix. + +Note: the method is only stable in some special cases +such as when the matrix is diagonally dominant or symmetric +positive definite (See reference). +For an algorithm stable in the general case, see: +http://www2.stat.duke.edu/~sayan/863/lec/linsys.pdf + + + + +##### Parameters + +| Name | Type | Description | | +| ---- | ---- | ----------- | -------- | +| A | `Array` | Tridiagonal Maxtrix. |   | +| d | `Array` | Vector of values. |   | + + + + +##### Returns + + +- array representing the solution vector x. Returns null if system is singular. + +Reference: https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm + + + + ### src/algorithms/search/binary_search.js diff --git a/docs/index.html b/docs/index.html index 9294cff2..44a483f2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,7 +3,7 @@ -algorithms-js 0.0.8 — Algorithms Library in JavaScript +algorithms-js 0.0.13 — Algorithms Library in JavaScript @@ -163,7 +163,7 @@

algorithms-js - 0.0.8 + 0.0.13

Algorithms Library in JavaScript

@@ -295,6 +295,21 @@

+
  • + src/algorithms/math/matrix_determinant.js + + +
  • + +
  • src/algorithms/math/modular_inverse.js
      @@ -315,6 +330,26 @@

      +
    • + src/algorithms/math/tridiagonal_algorithm.js + + +
    • + +
    • src/algorithms/search/binary_search.js
        @@ -1979,6 +2014,84 @@

        Returns

        + + + + + + + + + +
        + +

        + + matrixDeterminant(M) +

        + +
        + +

        Calculates the determinant of a non-singular matrix +with real entries using Bareiss's algorithm.

        + + +
        + + +
        + +

        Parameters

        + +
        + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescription
        M + Array +

        Matrix with real entries.

        +
        +
        +
        + +
        + + + + +
        + +

        Returns

        + + + +

        + Number +

        + +

        determinant of the matrix M. +Reference: https://en.wikipedia.org/wiki/Bareiss_algorithm

        + + + +
        @@ -2147,6 +2260,182 @@

        Returns

        + + + + + + + + + +
        + +

        + + extractTridiagonalCoefficients(A, pad) +

        + +
        + +

        Extracts the tridiagonal coefficients from +a square matrix A. Pads the upper and lower +diagonal arrays to ensure all three diagonals +are of equal length.

        + + +
        + + +
        + +

        Parameters

        + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescription
        A + Array +

        Square matrix.

        +
        +
        pad + boolean +

        If true, pads lower and upper diagonal arrays.

        +
        +
        +
        + +
        + + + + +
        + +

        Returns

        + + + +

        + Object +

        + +

        Returns object with keys lowerDiagonal, centralDiagonal and upperDiagonal.

        + + + + +
        + +
        + + +
        + +

        + + tridiagonalAlgorithm(A, d) +

        + +
        + +

        Solves a linear system Ax=b where A is a tridiagonal matrix.

        +

        Note: the method is only stable in some special cases +such as when the matrix is diagonally dominant or symmetric +positive definite (See reference). +For an algorithm stable in the general case, see: +http://www2.stat.duke.edu/~sayan/863/lec/linsys.pdf

        + + +
        + + +
        + +

        Parameters

        + +
        + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeDescription
        A + Array +

        Tridiagonal Maxtrix.

        +
        +
        d + Array +

        Vector of values.

        +
        +
        +
        + +
        + + + + +
        + +

        Returns

        + + + +

        +

        + +

        array representing the solution vector x. Returns null if system is singular.

        +

        Reference: https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm

        + + + +
        diff --git a/src/algorithms/math/index.js b/src/algorithms/math/index.js index 41b2e488..0c06df1c 100644 --- a/src/algorithms/math/index.js +++ b/src/algorithms/math/index.js @@ -3,11 +3,13 @@ const gcd = require('./gcd'); const fastexp = require('./fast_exp'); const lcm = require('./lcm'); const modularInverse = require('./modular_inverse'); +const tridiagonalAlgorithm = require('./tridiagonal_algorithm'); module.exports = { extendedEuclidean, gcd, fastexp, lcm, - modularInverse + modularInverse, + tridiagonalAlgorithm }; diff --git a/src/algorithms/math/tridiagonal_algorithm.js b/src/algorithms/math/tridiagonal_algorithm.js new file mode 100644 index 00000000..7673f042 --- /dev/null +++ b/src/algorithms/math/tridiagonal_algorithm.js @@ -0,0 +1,89 @@ +/** + * Extracts the tridiagonal coefficients from + * a square matrix A. Pads the upper and lower + * diagonal arrays to ensure all three diagonals + * are of equal length. + * @param {Array} A Square matrix. + * @param {boolean} pad If true, pads lower and upper diagonal arrays. + * @returns {Object} Returns object with keys `lowerDiagonal`, + * `centralDiagonal` and `upperDiagonal`. + */ +const extractTridiagonalCoefficients = (A, pad = false) => { + const n = A.length; + const upperDiag = [A[0][1]]; + const diag = [A[0][0]]; + const lowerDiag = []; + // padding lower diag vector if padding enabled. + if (pad) { + lowerDiag.push(0); + } + + // Extracting diagonal values. + for (let i = 1; i < n - 1; i += 1) { + upperDiag.push(A[i][i + 1]); + diag.push(A[i][i]); + lowerDiag.push(A[i][i - 1]); + } + + // Padding upper diag vector if padding enabled. + if (pad) { + upperDiag.push(0); + } + diag.push(A[n - 1][n - 1]); + lowerDiag.push(A[n - 1][n - 2]); + + return { + lowerDiagonal: lowerDiag, + centralDiagonal: diag, + upperDiagonal: upperDiag + }; +}; + +/** + * Solves a linear system Ax=b where A is a tridiagonal matrix. + * + * Note: the method is only stable in some special cases + * such as when the matrix is diagonally dominant or symmetric + * positive definite (See reference). + * For an algorithm stable in the general case, see: + * http://www2.stat.duke.edu/~sayan/863/lec/linsys.pdf + * + * @param {Array} A Tridiagonal Maxtrix. + * @param {Array} d Vector of values. + * @return array representing the solution vector x. + * Returns null if system is singular. + * + * Reference: https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm + */ +const tridiagonalAlgorithm = (A, d) => { + // Extracting the diagonals. + const n = A.length; + const diagonals = extractTridiagonalCoefficients(A, true); + const a = diagonals.lowerDiagonal; + const b = diagonals.centralDiagonal; + const c = diagonals.upperDiagonal; + + // Solution vector. + const x = new Array(n); + let w = 0; + // Forward sweep. + for (let i = 1; i < n; i += 1) { + w = a[i] / b[i - 1]; + b[i] -= w * c[i - 1]; + d[i] -= w * d[i - 1]; + } + // Back substitution. + x[n - 1] = d[n - 1] / b[n - 1]; + for (let i = n - 2; i >= 0; i -= 1) { + x[i] = (d[i] - (c[i] * x[i + 1])) / b[i]; + } + + // If solution has NaN's, return null. + const nanValPresent = x.filter(val => isNaN(val)).length > 0; + if (nanValPresent) { + return null; + } + return x; +}; + +module.exports = tridiagonalAlgorithm; diff --git a/test/algorithms/math/testTridiagonalAlgorithm.js b/test/algorithms/math/testTridiagonalAlgorithm.js new file mode 100644 index 00000000..bab2096e --- /dev/null +++ b/test/algorithms/math/testTridiagonalAlgorithm.js @@ -0,0 +1,80 @@ +/* eslint-env mocha */ +const tridiagonalAlgorithm = require('../../../src/').algorithms.math.tridiagonalAlgorithm; + +const assert = require('assert'); + +// Creates a square zero matrix of size n. +const createEmptyMatrix = (n) => { + const zeroMatrix = new Array(n).fill(0); + for (let i = 0; i < n; i += 1) { + zeroMatrix[i] = new Array(n).fill(0); + } + return zeroMatrix; +}; + +// Creates an identity matrix of size n. +const generateIdentityMatrix = (n) => { + const identityMatrix = createEmptyMatrix(n); + for (let i = 0; i < n; i += 1) { + identityMatrix[i][i] = 1; + } + return identityMatrix; +}; + +// Checks if two vectors are equal up to a tolerance value. +function equalUptoTolerance(a, b, tol = 0) { + for (let i = 0; i < a.length; i += 1) { + if (Math.abs(a[i] - b[i]) > tol) { + return false; + } + } + return true; +} + +describe('Tridiagonal Algorithm', () => { + it('should return vector of 1\'s', () => { + const idMatrix4 = generateIdentityMatrix(4); + const idMatrix7 = generateIdentityMatrix(7); + const d4 = new Array(4).fill(1); + const d7 = new Array(7).fill(1); + assert.deepEqual(tridiagonalAlgorithm(idMatrix4, d4), d4); + assert.deepEqual(tridiagonalAlgorithm(idMatrix7, d7), d7); + }); + + it('should return a vector of 0\'s', () => { + const A = new Array(9); + for (let i = 0; i < 9; i += 1) { + A[i] = Array.from({ length: 9 }, () => 1 + Math.floor(Math.random() * 9)); + } + const d = new Array(9).fill(0); + assert.deepEqual(tridiagonalAlgorithm(A, d), d); + }); + + it('should return `null` for system with no solutions', () => { + const idMatrix7 = generateIdentityMatrix(7); + idMatrix7[6][6] = 0; + const d = new Array(7).fill(1); + assert.equal(tridiagonalAlgorithm(idMatrix7, d), null); + }); + + it('should return correct solution when diagnoally dominant.', () => { + const testTridiagMatrix = [[3, 1, 0, 0], + [1, 3, 1, 0], + [0, 1, 3, 1], + [0, 0, 1, 3]]; + const d = [2, 3, 3, 2]; + const expectedSol = [5 / 11, 7 / 11, 7 / 11, 5 / 11]; + assert.equal(equalUptoTolerance(tridiagonalAlgorithm(testTridiagMatrix, d), + expectedSol, 1E-12), true); + }); + + it('should return correct solution when A is symmetric positive definite.', () => { + const testTridiagMatrix = [[1, 3 / 4, 0], + [3 / 4, 1, 3 / 4], + [0, 3 / 4, 1]]; + const d = [1, 2, 3]; + const expectedSol = [-5, 8, -3]; + assert.equal(equalUptoTolerance(tridiagonalAlgorithm(testTridiagMatrix, d), + expectedSol, 1E-12), true); + }); +});