781 lines
19 KiB
JavaScript
781 lines
19 KiB
JavaScript
'use strict';
|
||
|
||
Object.defineProperty(exports, "__esModule", {
|
||
value: true
|
||
});
|
||
exports.Vector = undefined;
|
||
|
||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||
|
||
var _sylvester = require('./sylvester');
|
||
|
||
var _matrix = require('./matrix');
|
||
|
||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||
|
||
/**
|
||
* The Vector class is designed to model vectors in any number of dimensions.
|
||
* All the elements of a vector must be real numbers. Depending on what you’re
|
||
* using them for, it can be helpful to think of a vector either as a point
|
||
* in n-dimensional space, or as a line connecting
|
||
* the origin to that same point.
|
||
*/
|
||
var Vector = exports.Vector = function () {
|
||
|
||
/**
|
||
* Creates a new vector, initializing it with the provided elements.
|
||
* @param {Number[]} elements
|
||
*/
|
||
function Vector(elements) {
|
||
_classCallCheck(this, Vector);
|
||
|
||
this.elements = elements;
|
||
}
|
||
|
||
/**
|
||
* Returns the magnitude (also: euclidean norm, magnitude) of the vector.
|
||
*
|
||
* $example Vector.magnitude
|
||
* @see https://en.wikipedia.org/wiki/Euclidean_distance
|
||
* @return {Number}
|
||
*/
|
||
|
||
|
||
_createClass(Vector, [{
|
||
key: 'magnitude',
|
||
value: function magnitude() {
|
||
var sum = 0;
|
||
for (var i = 0; i < this.elements.length; i++) {
|
||
sum += this.elements[i] * this.elements[i];
|
||
}
|
||
|
||
return Math.sqrt(sum);
|
||
}
|
||
|
||
/**
|
||
* Returns the `ith` element if the vector. Returns null if `i` is out
|
||
* of bounds, indexing starts from 1.
|
||
*
|
||
* $example Vector.e
|
||
* @param {Number} i
|
||
* @return {Number}
|
||
*/
|
||
|
||
}, {
|
||
key: 'e',
|
||
value: function e(i) {
|
||
return i < 1 || i > this.elements.length ? null : this.elements[i - 1];
|
||
}
|
||
|
||
/**
|
||
* Returns the number of rows and columns the vector has.
|
||
*
|
||
* $example Vector.dimensions
|
||
* @return {IDimensions} the "rows" will always equal zero
|
||
*/
|
||
|
||
}, {
|
||
key: 'dimensions',
|
||
value: function dimensions() {
|
||
return {
|
||
rows: 1,
|
||
cols: this.elements.length
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Returns the number of rows the vector has.
|
||
*
|
||
* $example Vector.rows
|
||
* @return {Number} always `1`
|
||
*/
|
||
|
||
}, {
|
||
key: 'rows',
|
||
value: function rows() {
|
||
return 1;
|
||
}
|
||
|
||
/**
|
||
* Returns the number of columns the vector has.
|
||
*
|
||
* $example Vector.cols
|
||
* @return {Number}
|
||
*/
|
||
|
||
}, {
|
||
key: 'cols',
|
||
value: function cols() {
|
||
return this.elements.length;
|
||
}
|
||
|
||
/**
|
||
* Returns if the Vector is equal to the input vector.
|
||
* $example Vector.eql
|
||
* @param {Vector} vector
|
||
* @return {Boolean}
|
||
*/
|
||
|
||
}, {
|
||
key: 'eql',
|
||
value: function eql(vector) {
|
||
var n = this.elements.length;
|
||
var V = vector.elements || vector;
|
||
if (n !== V.length) {
|
||
return false;
|
||
}
|
||
while (n--) {
|
||
if (Math.abs(this.elements[n] - V[n]) > _sylvester.Sylvester.precision) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Returns a new function created by calling the iterator on all values of this vector.
|
||
* @param {Function} fn
|
||
* @return {Vector}
|
||
*/
|
||
|
||
}, {
|
||
key: 'map',
|
||
value: function map(fn) {
|
||
var n = this.elements.length;
|
||
var elements = new Array(n);
|
||
for (var i = 0; i < n; i++) {
|
||
elements[i] = fn(this.elements[i], i + 1);
|
||
}
|
||
|
||
return new Vector(elements);
|
||
}
|
||
|
||
/**
|
||
* Iterates through the elements of the vector
|
||
* @param {Function} fn called with the `(element, index)`
|
||
*/
|
||
|
||
}, {
|
||
key: 'each',
|
||
value: function each(fn) {
|
||
var n = this.elements.length;
|
||
for (var i = 0; i < n; i++) {
|
||
fn(this.elements[i], i + 1);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Returns a new vector created by normalizing this one to a have a
|
||
* magnitude of `1`. If the vector is the zero vector, it will not be modified.
|
||
*
|
||
* $example Vector.toUnitVector
|
||
* @return {Vector}
|
||
*/
|
||
|
||
}, {
|
||
key: 'toUnitVector',
|
||
value: function toUnitVector() {
|
||
var r = this.modulus();
|
||
if (r === 0) {
|
||
return this.dup();
|
||
}
|
||
|
||
return this.map(function (x) {
|
||
return x / r;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Returns the angle between this vector the argument in radians. If the
|
||
* vectors are mirrored across their axes this will return `NaN`.
|
||
* $example Vector.angleFrom
|
||
* @throws {DimensionalityMismatchError} If a vector is passed in with
|
||
* different dimensions
|
||
* @param {Vector} vector
|
||
* @return {Number}
|
||
*/
|
||
|
||
}, {
|
||
key: 'angleFrom',
|
||
value: function angleFrom(vector) {
|
||
var V = vector.elements || vector;
|
||
var n = this.elements.length;
|
||
if (n !== V.length) {
|
||
throw new _sylvester.DimensionalityMismatchError('Cannot compute the angle between vectors with different dimensionality');
|
||
}
|
||
|
||
// Work things out in parallel to save time
|
||
var dot = 0;
|
||
var mod1 = 0;
|
||
var mod2 = 0;
|
||
this.each(function (x, i) {
|
||
dot += x * V[i - 1];
|
||
mod1 += x * x;
|
||
mod2 += V[i - 1] * V[i - 1];
|
||
});
|
||
mod1 = Math.sqrt(mod1);
|
||
mod2 = Math.sqrt(mod2);
|
||
if (mod1 * mod2 === 0) {
|
||
return NaN;
|
||
}
|
||
|
||
var theta = dot / (mod1 * mod2);
|
||
if (theta < -1) {
|
||
theta = -1;
|
||
}
|
||
if (theta > 1) {
|
||
theta = 1;
|
||
}
|
||
return Math.acos(theta);
|
||
}
|
||
|
||
/**
|
||
* Returns whether the vectors are parallel to each other.
|
||
* $example Vector.isParallelTo
|
||
* @return {Boolean}
|
||
*/
|
||
|
||
}, {
|
||
key: 'isParallelTo',
|
||
value: function isParallelTo(vector) {
|
||
var angle = this.angleFrom(vector);
|
||
return angle === null ? false : angle <= _sylvester.Sylvester.precision;
|
||
}
|
||
|
||
/**
|
||
* Returns whether the vectors are antiparallel to each other.
|
||
* $example Vector.isAntiparallelTo
|
||
* @return {Boolean}
|
||
*/
|
||
|
||
}, {
|
||
key: 'isAntiparallelTo',
|
||
value: function isAntiparallelTo(vector) {
|
||
var angle = this.angleFrom(vector);
|
||
return angle === null ? false : Math.abs(angle - Math.PI) <= _sylvester.Sylvester.precision;
|
||
}
|
||
|
||
/**
|
||
* Returns whether the vectors are perpendicular to each other.
|
||
* $example Vector.isPerpendicularTo
|
||
* @return {Boolean}
|
||
*/
|
||
|
||
}, {
|
||
key: 'isPerpendicularTo',
|
||
value: function isPerpendicularTo(vector) {
|
||
return Math.abs(this.dot(vector)) <= _sylvester.Sylvester.precision;
|
||
}
|
||
}, {
|
||
key: '_runBinaryOp',
|
||
value: function _runBinaryOp(value, operator) {
|
||
if (typeof value === 'number') {
|
||
return this.map(function (v) {
|
||
return operator(v, value);
|
||
});
|
||
}
|
||
|
||
var values = value.elements || value;
|
||
if (this.elements.length !== values.length) {
|
||
throw new _sylvester.DimensionalityMismatchError('Cannot add vectors with different dimensions.');
|
||
}
|
||
|
||
return this.map(function (x, i) {
|
||
return operator(x, values[i - 1]);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* When the input is a constant, this returns the result of adding it to
|
||
* all cevtor elements. When it's a vector, the vectors will be added.
|
||
* $example Vector.add
|
||
* @throws {DimensionalityMismatchError} If a vector is passed in with
|
||
* different dimensions
|
||
* @param {Number|Number[]|Vector} value
|
||
* @return {Vector}
|
||
*/
|
||
|
||
}, {
|
||
key: 'add',
|
||
value: function add(value) {
|
||
return this._runBinaryOp(value, function (a, b) {
|
||
return a + b;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* When the input is a constant, this returns the result of subtracting it
|
||
* from all vector elements. When it's a vector, the vectors will be subtracted.
|
||
* $example Vector.subtract
|
||
* @throws {DimensionalityMismatchError} If a vector is passed in with
|
||
* different dimensions
|
||
* @param {Number|Number[]|Vector} value
|
||
* @return {Vector}
|
||
*/
|
||
|
||
}, {
|
||
key: 'subtract',
|
||
value: function subtract(value) {
|
||
return this._runBinaryOp(value, function (a, b) {
|
||
return a - b;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* When the input is a constant, this returns the result of multiplying it
|
||
* with all vector elements. When it's a vector, the vectors will be
|
||
* element-wise multiplied.
|
||
* $example Vector.multiply
|
||
* @throws {DimensionalityMismatchError} If a vector is passed in with
|
||
* different dimensions
|
||
* @param {Number|Number[]|Vector} value
|
||
* @return {Vector}
|
||
*/
|
||
|
||
}, {
|
||
key: 'multiply',
|
||
value: function multiply(value) {
|
||
return this._runBinaryOp(value, function (a, b) {
|
||
return a * b;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Returns the sum of all elements in the Vector.
|
||
* $example Vector.sum
|
||
* @return {Number}
|
||
*/
|
||
|
||
}, {
|
||
key: 'sum',
|
||
value: function sum() {
|
||
var sum = 0;
|
||
this.each(function (x) {
|
||
sum += x;
|
||
});
|
||
return sum;
|
||
}
|
||
|
||
/**
|
||
* Returns a new vector with the first `n` elements removed from the beginning.
|
||
* $example Vector.chomp
|
||
* @param {Number} n
|
||
* @return {Vector}
|
||
*/
|
||
|
||
}, {
|
||
key: 'chomp',
|
||
value: function chomp(n) {
|
||
var elements = [];
|
||
for (var i = n; i < this.elements.length; i++) {
|
||
elements.push(this.elements[i]);
|
||
}
|
||
|
||
return Vector.create(elements);
|
||
}
|
||
|
||
/**
|
||
* Returns a new vector consisting only of the first `n` elements.
|
||
* $example Vector.chomp
|
||
* @param {Number} n
|
||
* @return {Vector}
|
||
*/
|
||
|
||
}, {
|
||
key: 'top',
|
||
value: function top(n) {
|
||
var elements = [];
|
||
for (var i = 0; i < n; i++) {
|
||
elements.push(this.elements[i]);
|
||
}
|
||
|
||
return Vector.create(elements);
|
||
}
|
||
|
||
/**
|
||
* Returns a new vector with the provided `elements` concatenated on the end.
|
||
* $example Vector.augment
|
||
* @param {Number[]|Vector} elements
|
||
* @return {Vector}
|
||
*/
|
||
|
||
}, {
|
||
key: 'augment',
|
||
value: function augment(elements) {
|
||
return Vector.create(this.elements.concat(elements.elements || elements));
|
||
}
|
||
|
||
/**
|
||
* @alias Vector#multiply
|
||
*/
|
||
|
||
}, {
|
||
key: 'x',
|
||
value: function x(k) {
|
||
return this.multiply(k);
|
||
}
|
||
}, {
|
||
key: 'log',
|
||
value: function log() {
|
||
return Vector.log(this);
|
||
}
|
||
|
||
/**
|
||
* Returns the product of all elements in the vector.
|
||
* $example Vector.product
|
||
* @return {Number}
|
||
*/
|
||
|
||
}, {
|
||
key: 'product',
|
||
value: function product() {
|
||
var p = 1;
|
||
this.each(function (v) {
|
||
p *= v;
|
||
});
|
||
|
||
return p;
|
||
}
|
||
|
||
/**
|
||
* Returns the scalar (dot) product of the vector with the argument.
|
||
*
|
||
* $example Vector.dot
|
||
* @see https://en.wikipedia.org/wiki/Scalar_product
|
||
* @throws {DimensionalityMismatchError} If a vector is passed in with
|
||
* different dimensions
|
||
* @param {Vector|Number[]} vector
|
||
* @return {Number}
|
||
*/
|
||
|
||
}, {
|
||
key: 'dot',
|
||
value: function dot(vector) {
|
||
var V = vector.elements || vector;
|
||
var n = this.elements.length;
|
||
if (n !== V.length) {
|
||
throw new _sylvester.DimensionalityMismatchError('Cannot compute the dot product of vectors with different dimensionality');
|
||
}
|
||
|
||
var product = 0;
|
||
while (n--) {
|
||
product += this.elements[n] * V[n];
|
||
}
|
||
return product;
|
||
}
|
||
|
||
// Returns the vector product of the vector with the argument
|
||
// Both vectors must have dimensionality 3
|
||
|
||
}, {
|
||
key: 'cross',
|
||
value: function cross(vector) {
|
||
var B = vector.elements || vector;
|
||
if (this.elements.length !== 3 || B.length !== 3) {
|
||
return null;
|
||
}
|
||
var A = this.elements;
|
||
return Vector.create([A[1] * B[2] - A[2] * B[1], A[2] * B[0] - A[0] * B[2], A[0] * B[1] - A[1] * B[0]]);
|
||
}
|
||
|
||
// Returns the (absolute) largest element of the vector
|
||
|
||
}, {
|
||
key: 'max',
|
||
value: function max() {
|
||
var m = 0;
|
||
var i = this.elements.length;
|
||
while (i--) {
|
||
if (Math.abs(this.elements[i]) > Math.abs(m)) {
|
||
m = this.elements[i];
|
||
}
|
||
}
|
||
return m;
|
||
}
|
||
}, {
|
||
key: 'maxIndex',
|
||
value: function maxIndex() {
|
||
var m = 0;
|
||
var i = this.elements.length;
|
||
var maxIndex = -1;
|
||
|
||
while (i--) {
|
||
if (Math.abs(this.elements[i]) > Math.abs(m)) {
|
||
m = this.elements[i];
|
||
maxIndex = i + 1;
|
||
}
|
||
}
|
||
|
||
return maxIndex;
|
||
}
|
||
|
||
// Returns the index of the first match found
|
||
|
||
}, {
|
||
key: 'indexOf',
|
||
value: function indexOf(x) {
|
||
var index = null;
|
||
var n = this.elements.length;
|
||
for (var i = 0; i < n; i++) {
|
||
if (index === null && this.elements[i] === x) {
|
||
index = i + 1;
|
||
}
|
||
}
|
||
return index;
|
||
}
|
||
|
||
// Returns a diagonal matrix with the vector's elements as its diagonal elements
|
||
|
||
}, {
|
||
key: 'toDiagonalMatrix',
|
||
value: function toDiagonalMatrix() {
|
||
return _matrix.Matrix.Diagonal(this.elements);
|
||
}
|
||
|
||
// Returns the result of rounding the elements of the vector
|
||
|
||
}, {
|
||
key: 'round',
|
||
value: function round() {
|
||
return this.map(function (x) {
|
||
return Math.round(x);
|
||
});
|
||
}
|
||
|
||
// Transpose a Vector, return a 1xn Matrix
|
||
|
||
}, {
|
||
key: 'transpose',
|
||
value: function transpose() {
|
||
var rows = this.elements.length;
|
||
var elements = [];
|
||
|
||
for (var i = 0; i < rows; i++) {
|
||
elements.push([this.elements[i]]);
|
||
}
|
||
return _matrix.Matrix.create(elements);
|
||
}
|
||
|
||
// Returns a copy of the vector with elements set to the given value if they
|
||
// differ from it by less than Sylvester.precision
|
||
|
||
}, {
|
||
key: 'snapTo',
|
||
value: function snapTo(x) {
|
||
return this.map(function (y) {
|
||
return Math.abs(y - x) <= _sylvester.Sylvester.precision ? x : y;
|
||
});
|
||
}
|
||
|
||
// Returns the vector's distance from the argument, when considered as a point in space
|
||
|
||
}, {
|
||
key: 'distanceFrom',
|
||
value: function distanceFrom(obj) {
|
||
if (obj.anchor || obj.start && obj.end) {
|
||
return obj.distanceFrom(this);
|
||
}
|
||
var V = obj.elements || obj;
|
||
if (V.length !== this.elements.length) {
|
||
return null;
|
||
}
|
||
var sum = 0;
|
||
var part = void 0;
|
||
this.each(function (x, i) {
|
||
part = x - V[i - 1];
|
||
sum += part * part;
|
||
});
|
||
return Math.sqrt(sum);
|
||
}
|
||
|
||
// Returns true if the vector is point on the given line
|
||
|
||
}, {
|
||
key: 'liesOn',
|
||
value: function liesOn(line) {
|
||
return line.contains(this);
|
||
}
|
||
|
||
// Return true iff the vector is a point in the given plane
|
||
|
||
}, {
|
||
key: 'liesIn',
|
||
value: function liesIn(plane) {
|
||
return plane.contains(this);
|
||
}
|
||
|
||
// Rotates the vector about the given object. The object should be a
|
||
// point if the vector is 2D, and a line if it is 3D. Be careful with line directions!
|
||
|
||
}, {
|
||
key: 'rotate',
|
||
value: function rotate(t, obj) {
|
||
var V = void 0;
|
||
var R = null;
|
||
var x = void 0;
|
||
var y = void 0;
|
||
var z = void 0;
|
||
var C = void 0;
|
||
if (t.determinant) {
|
||
R = t.elements;
|
||
}
|
||
switch (this.elements.length) {
|
||
case 2:
|
||
V = obj.elements || obj;
|
||
if (V.length !== 2) {
|
||
return null;
|
||
}
|
||
if (!R) {
|
||
R = _matrix.Matrix.Rotation(t).elements;
|
||
}
|
||
x = this.elements[0] - V[0];
|
||
y = this.elements[1] - V[1];
|
||
return Vector.create([V[0] + R[0][0] * x + R[0][1] * y, V[1] + R[1][0] * x + R[1][1] * y]);
|
||
case 3:
|
||
if (!obj.direction) {
|
||
return null;
|
||
}
|
||
C = obj.pointClosestTo(this).elements;
|
||
if (!R) {
|
||
R = _matrix.Matrix.Rotation(t, obj.direction).elements;
|
||
}
|
||
x = this.elements[0] - C[0];
|
||
y = this.elements[1] - C[1];
|
||
z = this.elements[2] - C[2];
|
||
return Vector.create([C[0] + R[0][0] * x + R[0][1] * y + R[0][2] * z, C[1] + R[1][0] * x + R[1][1] * y + R[1][2] * z, C[2] + R[2][0] * x + R[2][1] * y + R[2][2] * z]);
|
||
default:
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// Returns the result of reflecting the point in the given point, line or plane
|
||
|
||
}, {
|
||
key: 'reflectionIn',
|
||
value: function reflectionIn(obj) {
|
||
if (obj.anchor) {
|
||
// obj is a plane or line
|
||
var P = this.elements.slice();
|
||
var C = obj.pointClosestTo(P).elements;
|
||
return Vector.create([C[0] + (C[0] - P[0]), C[1] + (C[1] - P[1]), C[2] + (C[2] - (P[2] || 0))]);
|
||
}
|
||
|
||
// obj is a point
|
||
var Q = obj.elements || obj;
|
||
if (this.elements.length !== Q.length) {
|
||
return null;
|
||
}
|
||
return this.map(function (x, i) {
|
||
return Q[i - 1] + (Q[i - 1] - x);
|
||
});
|
||
}
|
||
|
||
// Utility to make sure vectors are 3D. If they are 2D, a zero z-component is added
|
||
|
||
}, {
|
||
key: 'to3D',
|
||
value: function to3D() {
|
||
var V = this.dup();
|
||
switch (V.elements.length) {
|
||
case 3:
|
||
break;
|
||
case 2:
|
||
V.elements.push(0);
|
||
break;
|
||
default:
|
||
return null;
|
||
}
|
||
return V;
|
||
}
|
||
|
||
// Returns a string representation of the vector
|
||
|
||
}, {
|
||
key: 'inspect',
|
||
value: function inspect() {
|
||
return 'Vector<[' + this.elements.join(', ') + ']>';
|
||
}
|
||
|
||
// Set vector's elements from an array
|
||
|
||
}, {
|
||
key: 'setElements',
|
||
value: function setElements(els) {
|
||
this.elements = (els.elements || els).slice();
|
||
return this;
|
||
}
|
||
}, {
|
||
key: 'toJSON',
|
||
value: function toJSON() {
|
||
return this.elements;
|
||
}
|
||
|
||
// Constructor function
|
||
|
||
}], [{
|
||
key: 'create',
|
||
value: function create(elements) {
|
||
var V = new Vector();
|
||
return V.setElements(elements);
|
||
}
|
||
|
||
// Random vector of size n
|
||
|
||
}, {
|
||
key: 'Random',
|
||
value: function Random(n) {
|
||
var elements = [];
|
||
while (n--) {
|
||
elements.push(Math.random());
|
||
}
|
||
return Vector.create(elements);
|
||
}
|
||
}, {
|
||
key: 'Fill',
|
||
value: function Fill(n, v) {
|
||
var elements = [];
|
||
while (n--) {
|
||
elements.push(v);
|
||
}
|
||
return Vector.create(elements);
|
||
}
|
||
|
||
// Vector filled with zeros
|
||
|
||
}, {
|
||
key: 'Zero',
|
||
value: function Zero(n) {
|
||
return Vector.Fill(n, 0);
|
||
}
|
||
}, {
|
||
key: 'One',
|
||
value: function One(n) {
|
||
return Vector.Fill(n, 1);
|
||
}
|
||
}, {
|
||
key: 'log',
|
||
value: function log(v) {
|
||
return v.map(function (x) {
|
||
return Math.log(x);
|
||
});
|
||
}
|
||
}]);
|
||
|
||
return Vector;
|
||
}();
|
||
|
||
// i, j, k unit vectors
|
||
|
||
|
||
Vector.i = Vector.create([1, 0, 0]);
|
||
Vector.j = Vector.create([0, 1, 0]);
|
||
Vector.k = Vector.create([0, 0, 1]);
|
||
|
||
// The following are shims for deprecated methods removed in 1.0.0
|
||
Vector.prototype.modulus = Vector.prototype.magnitude;
|
||
Vector.prototype.norm = Vector.prototype.magnitude;
|
||
Vector.prototype.dup = function () {
|
||
return this.map(function (x) {
|
||
return x;
|
||
});
|
||
}; |