Skip to content

Commit 26c19ce

Browse files
authored
Merge pull request #68 from jbrown1618/cholesky-decomposition
Implemented Cholesky Decomposition
2 parents e616321 + 077630d commit 26c19ce

9 files changed

+105
-10
lines changed

docs/VECTOR.api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ export class NumberOperations extends ScalarOperations<number> {
378378
getAdditiveInverse(x: number): number;
379379
getMultiplicativeIdentity(): number;
380380
getMultiplicativeInverse(x: number): number | undefined;
381-
getPrincipalSquareRoot(x: number): number;
381+
getPrincipalSquareRoot(x: number): number | undefined;
382382
multiply(first: number, second: number): number;
383383
norm(x: number): number;
384384
prettyPrint(x: number): string;
@@ -453,7 +453,7 @@ export abstract class ScalarOperations<S> {
453453
abstract getAdditiveInverse(x: S): S;
454454
abstract getMultiplicativeIdentity(): S;
455455
abstract getMultiplicativeInverse(x: S): S | undefined;
456-
abstract getPrincipalSquareRoot(x: S): S;
456+
abstract getPrincipalSquareRoot(x: S): S | undefined;
457457
abstract multiply(first: S, second: S): S;
458458
negativeOne(): S;
459459
abstract norm(x: S): number;

docs/api/vector.numberoperations.getprincipalsquareroot.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Returns the principal square root of a scalar.
99
<b>Signature:</b>
1010

1111
```typescript
12-
getPrincipalSquareRoot(x: number): number;
12+
getPrincipalSquareRoot(x: number): number | undefined;
1313
```
1414

1515
## Parameters
@@ -20,7 +20,7 @@ getPrincipalSquareRoot(x: number): number;
2020

2121
<b>Returns:</b>
2222

23-
`number`
23+
`number | undefined`
2424

2525
The square root
2626

docs/api/vector.scalaroperations.getprincipalsquareroot.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Returns the principal square root of a scalar.
99
<b>Signature:</b>
1010

1111
```typescript
12-
abstract getPrincipalSquareRoot(x: S): S;
12+
abstract getPrincipalSquareRoot(x: S): S | undefined;
1313
```
1414

1515
## Parameters
@@ -20,7 +20,7 @@ abstract getPrincipalSquareRoot(x: S): S;
2020

2121
<b>Returns:</b>
2222

23-
`S`
23+
`S | undefined`
2424

2525
The square root
2626

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@josh-brown/vector",
33
"author": "Joshua Brown <[email protected]>",
4-
"version": "0.0.3",
4+
"version": "0.1.0",
55
"description": "A linear algebra library written in TypeScript that focuses on generality, extensibility, and ease of use.",
66
"license": "ISC",
77
"repository": {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
import { NumberMatrix } from '../types/matrix/NumberMatrix';
4+
import { calculateCholeskyDecomposition } from './CholeskyDecomposition';
5+
6+
describe('CholeskyDecomposition', () => {
7+
const matrixBuilder = NumberMatrix.builder();
8+
9+
describe('calculateCholeskyDecomposition', () => {
10+
it('calculates the Cholesky Decomposition of a matrix A', () => {
11+
const A = matrixBuilder.fromArray([[4, 12, -16], [12, 37, -43], [-16, -43, 98]]);
12+
const expectedL = matrixBuilder.fromArray([[2, 0, 0], [6, 1, 0], [-8, 5, 3]]);
13+
14+
const decomposition = calculateCholeskyDecomposition(A);
15+
expect(decomposition).not.to.be.undefined;
16+
const { L } = decomposition as any;
17+
expect(L).to.deep.equal(expectedL);
18+
19+
// Check that LL* equals A
20+
const LLstar = L.multiply(L.adjoint());
21+
expect(LLstar.equals(A)).to.be.true;
22+
});
23+
24+
it('returns undefined for a non-square matrix', () => {
25+
const A = matrixBuilder.fromArray([[1, 2]]);
26+
expect(calculateCholeskyDecomposition(A)).to.be.undefined;
27+
});
28+
29+
it('returns undefined for a non-symmetric matrix', () => {
30+
const A = matrixBuilder.fromArray([[4, 12, -16], [12, 37, -43], [-16, -46, 98]]);
31+
expect(calculateCholeskyDecomposition(A)).to.be.undefined;
32+
});
33+
34+
it('returns undefined for a non-positive-definite matrix', () => {
35+
const A = matrixBuilder.fromArray([[-1]]);
36+
expect(calculateCholeskyDecomposition(A)).to.be.undefined;
37+
});
38+
});
39+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Matrix } from '../types/matrix/Matrix';
2+
import { isHermitian } from '../utilities/MatrixProperties';
3+
4+
export interface CholeskyDecomposition<S> {
5+
L: Matrix<S>;
6+
}
7+
8+
/**
9+
* Uses the serial version of the Cholesky algorith to calculate the Cholesky
10+
* decomposition of a matrix `A`.
11+
*
12+
* @remarks
13+
* A Cholesky decomposition of a matrix `A` consists of a lower-triangular
14+
* matrix `L` such that _LL* = A_.
15+
*
16+
* A Cholesky decomposition only exists if `A` is symmetrix and positive-definite.
17+
* @param A - The matrix to decompose
18+
*/
19+
export function calculateCholeskyDecomposition<S>(
20+
A: Matrix<S>
21+
): CholeskyDecomposition<S> | undefined {
22+
if (!isHermitian(A)) return undefined;
23+
24+
const ops = A.ops();
25+
const builder = A.builder();
26+
const dim = A.getNumberOfColumns();
27+
28+
let L = builder.zeros(dim);
29+
30+
for (let j = 0; j < dim; j++) {
31+
const Ajj = A.getEntry(j, j);
32+
let entrySquared = Ajj;
33+
for (let p = 0; p < j; p++) {
34+
const Ljp = L.getEntry(j, p);
35+
entrySquared = ops.subtract(entrySquared, ops.multiply(Ljp, ops.conjugate(Ljp)));
36+
}
37+
const Ljj = ops.getPrincipalSquareRoot(entrySquared);
38+
if (Ljj === undefined) return undefined;
39+
L = L.set(j, j, Ljj);
40+
41+
for (let i = j + 1; i < dim; i++) {
42+
let difference = A.getEntry(i, j);
43+
for (let p = 0; p < j; p++) {
44+
const Lip = L.getEntry(i, p);
45+
const Ljp = L.getEntry(j, p);
46+
difference = ops.subtract(difference, ops.multiply(Lip, ops.conjugate(Ljp)));
47+
}
48+
const Lij = ops.divide(difference, Ljj);
49+
if (Lij === undefined) return undefined;
50+
51+
L = L.set(i, j, Lij);
52+
}
53+
}
54+
return { L };
55+
}

src/types/scalar/NumberOperations.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ describe('NumberOperations', () => {
100100
it('returns the positive square root', () => {
101101
testNumbers(n => {
102102
if (n < 0) {
103-
expect(ops.getPrincipalSquareRoot(n)).to.be.NaN;
103+
expect(ops.getPrincipalSquareRoot(n)).to.be.undefined;
104104
} else {
105105
expect(ops.getPrincipalSquareRoot(n)).to.equal(Math.sqrt(n));
106106
}

src/types/scalar/NumberOperations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export class NumberOperations extends ScalarOperations<number> {
6262
/**
6363
* {@inheritdoc ScalarOperations.getPrincipalSquareRoot}
6464
*/
65-
public getPrincipalSquareRoot(x: number): number {
65+
public getPrincipalSquareRoot(x: number): number | undefined {
66+
if (x < 0) return undefined;
6667
return Math.sqrt(x);
6768
}
6869

src/types/scalar/ScalarOperations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export abstract class ScalarOperations<S> {
170170
* @returns The square root
171171
* @public
172172
*/
173-
public abstract getPrincipalSquareRoot(x: S): S;
173+
public abstract getPrincipalSquareRoot(x: S): S | undefined;
174174

175175
/**
176176
* Returns the norm (absolute value or magnitude) of a scalar

0 commit comments

Comments
 (0)