Skip to content

Commit

Permalink
Merge 5ef3e03 into 7c333fa
Browse files Browse the repository at this point in the history
  • Loading branch information
ngfelixl committed Feb 4, 2019
2 parents 7c333fa + 5ef3e03 commit 8182aa8
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 13 deletions.
43 changes: 37 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
![license](https://img.shields.io/npm/l/@geometric/vector.svg)
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier)

Extends the JavaScript array with n-dimensional Vector math capabilities. Well tested,
controlled error flow, focus on usability. This is an early stage, so it might be faced
some API changes in the future.
Extends the JavaScript array with n-dimensional Vector and Matrix math capabilities. Well tested,
controlled error flow, focus on usability. It even is capable of solving systems of linear equations.
This is an early stage, so it might be faced some API changes in the future.

## Table of contents

Expand All @@ -31,6 +31,8 @@ some API changes in the future.
4. [Trace](#34-trace)
5. [Submatrix extraction](#35-submatrix-extraction)
6. [Transpose](#36-transpose)
7. [Solve system of linear equations](#37-system-of-linear-equations)
8. [Diagonal matrices](#38-diagonal-matrices)
4. [Convex Hull](#4-convex-hull)
5. [Testing](#5-testing)
6. [Roadmap](#6-roadmap)
Expand Down Expand Up @@ -216,9 +218,9 @@ const vector2 = new Vector(0, 1, 0);
const vector = vector1.cross(vector2); // [0, 0, 1]
```

The last multiplication is the element-wise multiplication. For this purpose
the `multiplyElementWise` is provided. It requires two equally dimensioned
vectors and returns a vector with the same dimension.
The last multiplication is the element-wise multiplication / division. For this purpose
the `multiplyElementWise` or `divideElementWise` is provided. It requires two
equally dimensioned vectors and returns a vector with the same dimension.

### 2.9 Rotation

Expand Down Expand Up @@ -350,6 +352,35 @@ matrix.transpose();
console.log(matrix); // [[1, 3, 5], [2, 4, 6]]
```

### 3.7 System of linear equations

The matrix class is also able to solve systems of linear equations.

```
Ax = b
```

where *A* is the matrix, *b* is the result (vector) and *x* is the vector we
want to compute.

```typescript
const matrix = new Matrix().identity(3);
const vector = new Vector(-1, 2, -3);
const result = matrix.solve(vector); // [-1, 2, -3]
```

This also works with non-trivial cases. It returns `null` if no solution was found.

### 3.8 Diagonal matrices

The matrix diagonalization is based on **Gauss**-elimination. Use it as follows

```
const matrix = new Matrix().from([2, 3, 4], [1, 2, 3], [5, 6, 7]);
matrix.diagonalize();
console.log(matrix); // [[x, y, z], [0, a, b], [0, 0, c]]
```

## 4 Convex Hull

The script also contains a helper function to detect the 2-dimensional
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@geometric/vector",
"version": "0.1.0",
"version": "0.2.0",
"description": "Extend JavaScript arrays with vector capabilities",
"main": "dist/lib/index.js",
"module": "dist/lib-esm/index.js",
Expand Down
45 changes: 45 additions & 0 deletions src/matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,51 @@ export class Matrix extends Array<Vector> {
return this;
}

/**
* Solves the system of linear equations using Cramer's rule. Throws an
* error if there is no solution.
* @param vector
*/
solve(vector: Vector): Vector | null {
if (vector.length !== this.shape[1]) {
throw new Error(`Vector has to be of size ${this.shape[1]} but is ${vector.length}`);
}
const det = this.determinant;
if (det !== 0) {
let determinants = new Vector(vector.length);

for (let i = 0; i < vector.length; i++) {
const temp = new Matrix().from(this);
temp.transpose();
temp[i].from(vector);
temp.transpose();
determinants[i] = temp.determinant;
}

determinants = determinants.dot(1 / det) as Vector;

return determinants;
} else {
return null;
}
}

/**
* Diagonlize the matrix to the upper triangular using
* Gauss elimination.
* @param matrix
*/
diagonalize() {
for (let i = 0; i < this.length; i++) {
for (let j = i + 1; j < this.length; j++) {
const factor = this[j][i] / this[i][i];
const row = this[i].dot(factor) as Vector;
this[j] = this[j].add(row.invert());
}
}
return this;
}

/**
* Computes the trace of the matrix. Matrix has to be
* of shape NxN
Expand Down
20 changes: 20 additions & 0 deletions src/vector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,26 @@ export class Vector extends Array<number> {
return result;
}

/**
* Calculate the element wise multiplication of two vectors.
* This operation is also called Hadamard-product or Schur-product.
* It requires the vector dimensions to be equal.
* @param vector
*
*/
divideElementWise(vector: Vector): Vector {
if (this.length !== vector.length) {
throw new Error(`Can't divide unequal sized vectors element-wise`);
}

const result = new Vector();
for (let i = 0; i < this.length; i++) {
result[i] = this[i] / vector[i];
}

return result;
}

/**
* ## Invert
*
Expand Down
79 changes: 73 additions & 6 deletions test/matrix.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Matrix, Vector } from '../src/index';
import { expect } from 'chai';

const test2X3 = [
const ERROR_THRESHOLD = 0.000000000000001;
const test3x2 = [
[2, 4], [1, -1], [2.5, 1]
]

Expand Down Expand Up @@ -98,7 +99,7 @@ describe('matrix', () => {
});

it('should throw an error if matrix not NxN-shaped', () => {
const matrix = new Matrix().from(test2X3);
const matrix = new Matrix().from(test3x2);

expect(() => { matrix.identity(); })
.to.throw(new RegExp('Matrix has to be NxN shaped'));
Expand Down Expand Up @@ -194,7 +195,7 @@ describe('matrix', () => {

describe('scalar', () => {
it('should be multiplicate scalar values', () => {
const matrix = new Matrix(2, 3).from(test2X3);
const matrix = new Matrix(2, 3).from(test3x2);

const result = matrix.dot(2);

Expand All @@ -205,7 +206,7 @@ describe('matrix', () => {
});

it('should multiplicate float scalar values', () => {
const matrix = new Matrix(2, 3).from(test2X3);
const matrix = new Matrix(2, 3).from(test3x2);

const result = matrix.dot(0.5);

Expand All @@ -217,7 +218,7 @@ describe('matrix', () => {

describe('vector', () => {
it('should be multiplicate vectors', () => {
const matrix = new Matrix(3, 2).from(test2X3);
const matrix = new Matrix(3, 2).from(test3x2);
const vector = new Vector(-1, 2);
const result = matrix.dot(vector);

Expand All @@ -226,7 +227,7 @@ describe('matrix', () => {
});

it('should throw an error if vector has wrong dimensions', () => {
const matrix = new Matrix(3, 2).from(test2X3);
const matrix = new Matrix(3, 2).from(test3x2);
const vector = new Vector(4).zeros();

expect(() => { matrix.dot(vector); })
Expand Down Expand Up @@ -281,4 +282,70 @@ describe('matrix', () => {
});
});
});

describe('solve', () => {
it('should solve the 3d identity linear system of equations', () => {
const matrix = new Matrix().identity(3);
const vector = new Vector(1, 2, 3);
const solution = matrix.solve(vector);

expect(solution).to.eql([1, 2, 3]);
});

it('should solve the test 2d linear system of equations', () => {
const matrix = new Matrix().from([
[2, 4],
[-1, 3]
]);
const vector = new Vector(0.5, -1);

const solution = matrix.solve(vector);
expect(solution).to.exist;

if (solution) {
const error = [Math.abs(solution[0] - 0.55), Math.abs(solution[1] + 0.15)];
expect(error[0]).to.lessThan(ERROR_THRESHOLD);
expect(error[1]).to.lessThan(ERROR_THRESHOLD);
}
});

it('should throw an error if vector has invalid dimenions', () => {
const matrix = new Matrix(2, 2);
const vector = new Vector(3);

expect(() => { matrix.solve(vector); })
.to.throw(new RegExp(`Vector has to be of size 2 but is 3`));
});

it('should return NULL if system is not solvable', () => {
const matrix = new Matrix(2, 2).zeros();
const vector = new Vector(2).random();

const solution = matrix.solve(vector);

expect(solution).to.be.null;
});
});

describe('diagonalize', () => {
it('should do nothing on diagonal matrices', () => {
const data = [
[1, 23, 1],
[0, 1, 3],
[0, 0, 3]
];
const matrix = new Matrix().from(data);
matrix.diagonalize();

expect(matrix).to.eql(data);
});

it('should diagonalize a 3x2 matrix', () => {
const matrix = new Matrix().from(test3x2);
matrix.diagonalize();
expect(matrix[1][0]).to.equal(0);
expect(matrix[2][0]).to.equal(0);
expect(matrix[2][1]).to.equal(0);
});
});
});
20 changes: 20 additions & 0 deletions test/vector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,26 @@ describe('vector', () => {
});
});


describe('divideElementWise', () => {
it('should divide two 3D vectors element wise', () => {
const vector = new Vector(3, 4, 5);

const multiplication = vector.divideElementWise(vector);

expect(multiplication).to.eql([1, 1, 1]);
});

it('should throw an error if vector dimensions are not equal', () => {
const vector1 = new Vector(0, 1);
const vector2 = new Vector(2, 3, 4);

expect(() => { vector1.divideElementWise(vector2); })
.to.throw(`Can't divide unequal sized vectors element-wise`);
});
});


describe('signedAngle', () => {
it('should calculate PI/2 for [1, 0] and [0, 1]', () => {
const vector1 = new Vector(1, 0);
Expand Down

0 comments on commit 8182aa8

Please sign in to comment.