mathjslab
Version:
MathJSLab - Interpreter with language syntax like MATLAB/Octave
1,042 lines (973 loc) • 41.2 kB
text/typescript
import { ComplexDecimal } from './complex-decimal';
declare let EvaluatorPointer: any;
export type dimRange = {
start: number;
stride: number;
stop: number;
};
export type ComplexDecimalRange = {
start: ComplexDecimal;
stride: ComplexDecimal;
stop: ComplexDecimal;
};
export class MultiArray {
dim: Array<number>;
array: Array<Array<ComplexDecimal>>;
static functions: { [name: string]: Function } = {
size: MultiArray.size,
sub2ind: MultiArray.sub2ind,
ind2sub: MultiArray.ind2sub,
zeros: MultiArray.zeros,
ones: MultiArray.ones,
eye: MultiArray.eye,
inv: MultiArray.inv,
det: MultiArray.det,
trace: MultiArray.trace,
rows: MultiArray.rows,
cols: MultiArray.cols,
minor: MultiArray.minor,
cofactor: MultiArray.cofactor,
adj: MultiArray.adj,
pivot: MultiArray.pivot,
lu: MultiArray.lu,
min: MultiArray.min,
max: MultiArray.max,
mean: MultiArray.mean,
horzcat: MultiArray.horzcat,
vertcat: MultiArray.vertcat,
gauss: MultiArray.gauss,
};
constructor(shape?: number[], fill?: ComplexDecimal) {
if (!shape) {
this.dim = [0, 0];
this.array = [];
} else if (!fill) {
this.dim = shape.slice();
this.array = new Array(this.dim[0]);
} else {
this.dim = shape.slice();
this.array = new Array(this.dim[0]);
for (let i = 0; i < this.dim[0]; i++) {
this.array[i] = new Array(this.dim[1]).fill(fill);
// for (let j = 0; j < this.dim[1]; j++) {
// this.array[i][j] = new ComplexDecimal(fill.re, fill.im);
// }
}
}
}
static firstRow(row: Array<any>): MultiArray {
const result = new MultiArray([1, row.length]);
result.array[0] = row;
return result;
}
static appendRow(matrix: MultiArray, row: Array<any>): MultiArray {
matrix.array.push(row);
matrix.dim[0]++;
return matrix;
}
static unparse(tree: MultiArray, that: any): string {
let arraystr = '';
for (let i = 0; i < tree.dim[0]; i++) {
for (let j = 0; j < tree.dim[1]; j++) {
arraystr += that.Unparse(tree.array[i][j]) + ',';
}
arraystr = arraystr.substring(0, arraystr.length - 1);
arraystr += ';';
}
arraystr = arraystr.substring(0, arraystr.length - 1);
return '[' + arraystr + ']';
}
static unparseML(tree: MultiArray, that: any): string {
let temp = '';
temp += '<mrow><mo>[</mo><mtable>';
for (let i = 0; i < tree.dim[0]; i++) {
temp += '<mtr>';
for (let j = 0; j < tree.dim[1]; j++) {
temp += '<mtd>' + that.unparserML(tree.array[i][j]) + '</mtd>';
}
temp += '</mtr>';
}
temp +=
tree.array.length > 0
? '</mtable><mo>]</mo></mrow>'
: "<mspace width='0.5em'/></mtable><mo>]</mo></mrow><mo>(</mo><mn>0</mn><mi>×</mi><mn>0</mn><mo>)</mo>";
return temp;
}
static evaluate(tree: MultiArray, that: any, fname: string): MultiArray {
const result = new MultiArray();
for (let i = 0, k = 0; i < tree.array.length; i++, k++) {
result.array.push([]);
let h = 1;
for (let j = 0; j < tree.array[i].length; j++) {
const temp = that.Evaluator(tree.array[i][j], false, fname);
if (MultiArray.isThis(temp)) {
if (j == 0) {
h = temp.array.length;
result.array.splice(k, 1, temp.array[0]);
for (let n = 1; n < h; n++) {
result.array.splice(k + n, 0, temp.array[n]);
}
} else {
for (let n = 0; n < temp.array.length; n++) {
for (let m = 0; m < temp.array[0].length; m++) {
result.array[k + n].push(temp.array[n][m]);
}
}
}
} else {
result.array[k][j] = temp;
}
}
k += h - 1;
if (i != 0) {
if (result.array[i].length != result.array[0].length)
throw new Error('vertical dimensions mismatch (' + k + 'x' + result.array[0].length + ' vs 1x' + result.array[i].length + ')');
}
}
result.dim = [result.array.length, result.array.length ? result.array[0].length : 0];
return result;
}
static isThis(obj: any): boolean {
return 'array' in obj;
}
static isRange(obj: any): boolean {
return 'start' in obj;
}
static mat_0x0(): MultiArray {
return new MultiArray([0, 0]);
}
static arrayEqual(left: Array<number>, right: Array<number>): boolean {
return !(left < right || left > right);
}
static isSameDim(left: MultiArray, right: MultiArray) {
return !(left.dim < right.dim || left.dim > right.dim);
}
static number2matrix1x1(value: ComplexDecimal | MultiArray) {
if ('array' in value) {
return value;
} else if ('re' in value) {
const result = new MultiArray([1, 1]);
result.array[0] = [value];
return result;
// return new MultiArray([1,1],value)
}
}
static clone(M: MultiArray): MultiArray {
const result = new MultiArray(M.dim);
for (let i = 0; i < M.dim[0]; i++) {
result.array[i] = new Array(M.dim[1]);
for (let j = 0; j < M.dim[1]; j++) {
result.array[i][j] = Object.assign({}, M.array[i][j]);
}
}
return result;
}
static toLogical(M: MultiArray): ComplexDecimal {
for (let i = 0; i < M.dim[0]; i++) {
for (let j = 0; j < M.dim[1]; j++) {
const value = ComplexDecimal.toMaxPrecision(M.array[i][j]);
if (value.re.eq(0) && value.im.eq(0)) {
return ComplexDecimal.false();
}
}
}
return ComplexDecimal.true();
}
static map(M: MultiArray, f: Function): MultiArray {
const result = new MultiArray(M.dim.slice());
for (let i = 0; i < M.dim[0]; i++) {
result.array[i] = M.array[i].map(f as any);
}
return result;
}
static expandRange(startNode: ComplexDecimal, stopNode: ComplexDecimal, strideNode?: ComplexDecimal | null): MultiArray {
const temp = [];
const s = strideNode ? strideNode.re.toNumber() : 1;
for (let n = startNode.re.toNumber(), i = 0; n <= stopNode.re.toNumber(); n += s, i++) {
temp[i] = new ComplexDecimal(n, 0);
}
const result = new MultiArray([1, temp.length]);
result.array = [temp];
return result;
}
static testIndex(k: ComplexDecimal, bound: number, matrix: MultiArray, input: string): number {
if (!k.re.isInteger() || !k.re.gte(1)) throw new Error(input + ': subscripts must be either integers greater than or equal 1 or logicals');
if (!k.im.eq(0)) throw new Error(input + ': subscripts must be real');
const result = k.re.toNumber() - 1;
if (result >= bound) {
throw new Error(input + ': out of bound ' + bound + ' (dimensions are ' + matrix.dim[0] + 'x' + matrix.dim[1] + ')');
}
return result;
}
static oneRowToDim(M: Array<ComplexDecimal> | MultiArray): Array<number> {
if (Array.isArray(M)) {
const result = [];
for (let i = 0; i < M.length; i++) {
result[i] = M[i].re.toNumber();
}
return result;
} else {
const result = [];
for (let i = 0; i < M.array[0].length; i++) {
result[i] = M.array[0][i].re.toNumber();
}
return result;
}
}
static subMatrix(temp: MultiArray, id: string, argumentsList: Array<any>): any {
if (argumentsList.length == 1) {
// single value indexing
if ('array' in argumentsList[0]) {
const result = new MultiArray(argumentsList[0].dim.slice(0, 2));
for (let i = 0; i < argumentsList[0].dim[0]; i++) {
result.array[i] = new Array(argumentsList[0].dim[1]);
for (let j = 0; j < argumentsList[0].dim[1]; j++) {
const n = MultiArray.testIndex(
argumentsList[0].array[i][j],
temp.dim[0] * temp.dim[1],
temp,
id + '(' + argumentsList[0].array[i][j].re.toNumber() + ')',
);
result.array[i][j] = temp.array[n % temp.dim[1]][Math.floor(n / temp.dim[1])];
}
}
return result;
} else {
const n = MultiArray.testIndex(argumentsList[0], temp.dim[0] * temp.dim[1], temp, id + '(' + argumentsList[0].re.toNumber() + ')');
return temp.array[n % temp.dim[1]][Math.floor(n / temp.dim[1])];
}
} else if (argumentsList.length == 2) {
// double value indexing
argumentsList[0] = MultiArray.number2matrix1x1(argumentsList[0]);
argumentsList[1] = MultiArray.number2matrix1x1(argumentsList[1]);
const rows = argumentsList[0].dim[0] * argumentsList[0].dim[1];
const cols = argumentsList[1].dim[0] * argumentsList[1].dim[1];
const result = new MultiArray([rows, cols]);
let s = 0;
for (let j = 0; j < argumentsList[0].dim[1]; j++) {
for (let i = 0; i < argumentsList[0].dim[0]; i++) {
const p = MultiArray.testIndex(
argumentsList[0].array[i][j],
temp.dim[0],
temp,
id + '(' + argumentsList[0].array[i][j].re.toNumber() + ',_)',
);
for (let n = 0; n < argumentsList[1].dim[1]; n++) {
for (let m = 0; m < argumentsList[1].dim[0]; m++) {
const q = MultiArray.testIndex(
argumentsList[1].array[m][n],
temp.dim[1],
temp,
id + '(_,' + argumentsList[1].array[m][n].re.toNumber() + ')',
);
if (!(s % cols)) {
result.array[Math.floor(s / cols)] = new Array(cols);
}
result.array[Math.floor(s / cols)][s % cols] = temp.array[p][q];
s++;
}
}
}
}
if (result.dim[0] == 1 && result.dim[1] == 1) {
return result.array[0][0];
} else {
return result;
}
} else {
throw new Error(id + '(_,_,...): out of bounds (dimensions are ' + temp.dim[0] + 'x' + temp.dim[1] + ')');
}
}
static mul(left: MultiArray, right: MultiArray): MultiArray {
if (left.dim[1] == right.dim[0]) {
//matrix multiplication
const result = new MultiArray([left.dim[0], right.dim[1]]);
for (let i = 0; i < left.dim[0]; i++) {
result.array[i] = new Array(right.dim[1]).fill(ComplexDecimal.zero());
for (let j = 0; j < right.dim[1]; j++) {
for (let n = 0; n < left.dim[1]; n++) {
result.array[i][j] = ComplexDecimal.add(result.array[i][j], ComplexDecimal.mul(left.array[i][n], right.array[n][j]));
}
}
}
return result;
} else {
throw new Error(
'operator *: nonconformant arguments (op1 is ' +
left.dim[0] +
'x' +
left.dim[1] +
', op2 is ' +
right.dim[0] +
'x' +
right.dim[1] +
')',
);
}
}
static scalarOpMultiArray(
op: 'add' | 'sub' | 'mul' | 'rdiv' | 'ldiv' | 'pow' | 'lt' | 'lte' | 'eq' | 'gte' | 'gt' | 'ne' | 'and' | 'or',
left: ComplexDecimal,
right: MultiArray,
): MultiArray {
const result = new MultiArray(right.dim);
for (let i = 0; i < result.dim[0]; i++) {
result.array[i] = new Array(result.dim[1]);
for (let j = 0; j < result.dim[1]; j++) {
result.array[i][j] = ComplexDecimal[op](left, right.array[i][j]);
}
}
return result;
}
static MultiArrayOpScalar(
op: 'add' | 'sub' | 'mul' | 'rdiv' | 'ldiv' | 'pow' | 'lt' | 'lte' | 'eq' | 'gte' | 'gt' | 'ne' | 'and' | 'or',
left: MultiArray,
right: ComplexDecimal,
): MultiArray {
const result = new MultiArray(left.dim);
for (let i = 0; i < result.dim[0]; i++) {
result.array[i] = new Array(result.dim[1]);
for (let j = 0; j < result.dim[1]; j++) {
result.array[i][j] = ComplexDecimal[op](left.array[i][j], right);
}
}
return result;
}
static leftOp(op: 'clone' | 'neg' | 'not', right: MultiArray): MultiArray {
const result = new MultiArray(right.dim);
for (let i = 0; i < result.dim[0]; i++) {
result.array[i] = new Array(result.dim[1]);
for (let j = 0; j < result.dim[1]; j++) {
result.array[i][j] = ComplexDecimal[op](right.array[i][j]);
}
}
return result;
}
static ewiseOp(
op: 'add' | 'sub' | 'mul' | 'rdiv' | 'ldiv' | 'pow' | 'lt' | 'lte' | 'eq' | 'gte' | 'gt' | 'ne' | 'and' | 'or',
left: MultiArray,
right: MultiArray,
): MultiArray {
if (MultiArray.arrayEqual(left.dim.slice(0, 2), right.dim.slice(0, 2))) {
// left and right has same number of rows and columns
const result = new MultiArray(left.dim);
for (let i = 0; i < result.dim[0]; i++) {
result.array[i] = new Array(result.dim[1]);
for (let j = 0; j < result.dim[1]; j++) {
result.array[i][j] = ComplexDecimal[op](left.array[i][j], right.array[i][j]);
}
}
return result;
} else if (left.dim[0] == right.dim[0]) {
// left and right has same number of rows
let col, matrix;
if (left.dim[1] == 1) {
// left has one column
col = left;
matrix = right;
} else if (right.dim[1] == 1) {
// right has one column
col = right;
matrix = left;
} else {
throw new Error(
'operator ' +
op +
': nonconformant arguments (op1 is ' +
left.dim[0] +
'x' +
left.dim[1] +
', op2 is ' +
right.dim[0] +
'x' +
right.dim[1] +
')',
);
}
const result = new MultiArray([col.dim[0], matrix.dim[1]]);
for (let i = 0; i < col.dim[0]; i++) {
result.array[i] = new Array(matrix.dim[1]);
for (let j = 0; j < matrix.dim[1]; j++) {
result.array[i][j] = ComplexDecimal[op](col.array[i][0], matrix.array[i][j]);
}
}
return result;
} else if (left.dim[1] == right.dim[1]) {
// left and right has same number of columns
let row, matrix;
if (left.dim[0] == 1) {
// left has one row
row = left;
matrix = right;
} else if (right.dim[0] == 1) {
// right has one row
row = right;
matrix = left;
} else {
throw new Error(
'operator ' +
op +
': nonconformant arguments (op1 is ' +
left.dim[0] +
'x' +
left.dim[1] +
', op2 is ' +
right.dim[0] +
'x' +
right.dim[1] +
')',
);
}
const result = new MultiArray([matrix.dim[0], row.dim[1]]);
for (let i = 0; i < matrix.dim[0]; i++) {
result.array[i] = new Array(row.dim[1]);
for (let j = 0; j < row.dim[1]; j++) {
result.array[i][j] = ComplexDecimal[op](row.array[0][j], matrix.array[i][j]);
}
}
return result;
} else if (left.dim[0] == 1 && right.dim[1] == 1) {
// left has one row and right has one column
const result = new MultiArray([right.dim[0], left.dim[1]]);
for (let i = 0; i < right.dim[0]; i++) {
result.array[i] = new Array(left.dim[1]);
for (let j = 0; j < left.dim[1]; j++) {
result.array[i][j] = ComplexDecimal[op](left.array[0][j], right.array[i][0]);
}
}
return result;
} else if (left.dim[1] == 1 && right.dim[0] == 1) {
// left has one column and right has one row
const result = new MultiArray([left.dim[0], right.dim[1]]);
for (let i = 0; i < left.dim[0]; i++) {
result.array[i] = new Array(right.dim[1]);
for (let j = 0; j < right.dim[1]; j++) {
result.array[i][j] = ComplexDecimal[op](left.array[i][0], right.array[0][j]);
}
}
return result;
} else {
throw new Error(
'operator ' +
op +
': nonconformant arguments (op1 is ' +
left.dim[0] +
'x' +
left.dim[1] +
', op2 is ' +
right.dim[0] +
'x' +
right.dim[1] +
')',
);
}
}
static inv(M: MultiArray): MultiArray {
// Returns the inverse of matrix `M`.
// from http://blog.acipo.com/matrix-inversion-in-javascript/
// I use Guassian Elimination to calculate the inverse:
// (1) 'augment' the matrix (left) by the identity (on the right)
// (2) Turn the matrix on the left into the identity by elemetry row ops
// (3) The matrix on the right is the inverse (was the identity matrix)
// There are 3 elemtary row ops: (I combine b and c in my code)
// (a) Swap 2 rows
// (b) Multiply a row by a scalar
// (c) Add 2 rows
//if the matrix isn't square: exit (error)
if (M.dim[0] == M.dim[1]) {
//create the identity matrix (I), and a clone (C) of the original
let i = 0,
ii = 0,
j = 0,
e = ComplexDecimal.zero();
const dim = M.dim[0];
const I: Array<Array<ComplexDecimal>> = [],
C: Array<Array<any>> = [];
for (i = 0; i < dim; i += 1) {
// Create the row
I[I.length] = [];
C[C.length] = [];
for (j = 0; j < dim; j += 1) {
//if we're on the diagonal, put a 1 (for identity)
if (i == j) {
I[i][j] = ComplexDecimal.one();
} else {
I[i][j] = ComplexDecimal.zero();
}
// Also, make the clone of the original
C[i][j] = M.array[i][j];
}
}
// Perform elementary row operations
for (i = 0; i < dim; i += 1) {
// get the element e on the diagonal
e = C[i][i];
// if we have a 0 on the diagonal (we'll need to swap with a lower row)
if (e.re.eq(0) && e.im.eq(0)) {
//look through every row below the i'th row
for (ii = i + 1; ii < dim; ii += 1) {
//if the ii'th row has a non-0 in the i'th col
if (!C[ii][i].re.eq(0) && !C[ii][i].im.eq(0)) {
//it would make the diagonal have a non-0 so swap it
for (j = 0; j < dim; j++) {
e = C[i][j]; //temp store i'th row
C[i][j] = C[ii][j]; //replace i'th row by ii'th
C[ii][j] = e; //repace ii'th by temp
e = I[i][j]; //temp store i'th row
I[i][j] = I[ii][j]; //replace i'th row by ii'th
I[ii][j] = e; //repace ii'th by temp
}
//don't bother checking other rows since we've swapped
break;
}
}
//get the new diagonal
e = C[i][i];
//if it's still 0, not invertable (error)
if (e.re.eq(0) && e.im.eq(0)) {
return new MultiArray([M.dim[0], M.dim[1]], ComplexDecimal.inf_0());
}
}
// Scale this row down by e (so we have a 1 on the diagonal)
for (j = 0; j < dim; j++) {
C[i][j] = ComplexDecimal.rdiv(C[i][j], e); //apply to original matrix
I[i][j] = ComplexDecimal.rdiv(I[i][j], e); //apply to identity
}
// Subtract this row (scaled appropriately for each row) from ALL of
// the other rows so that there will be 0's in this column in the
// rows above and below this one
for (ii = 0; ii < dim; ii++) {
// Only apply to other rows (we want a 1 on the diagonal)
if (ii == i) {
continue;
}
// We want to change this element to 0
e = C[ii][i];
// Subtract (the row above(or below) scaled by e) from (the
// current row) but start at the i'th column and assume all the
// stuff left of diagonal is 0 (which it should be if we made this
// algorithm correctly)
for (j = 0; j < dim; j++) {
C[ii][j] = ComplexDecimal.sub(C[ii][j], ComplexDecimal.mul(e, C[i][j])); //apply to original matrix
I[ii][j] = ComplexDecimal.sub(I[ii][j], ComplexDecimal.mul(e, I[i][j])); //apply to identity
}
}
}
//we've done all operations, C should be the identity
//matrix I should be the inverse:
const result = new MultiArray(M.dim);
result.array = I;
return result;
} else {
throw new Error('inverse: A must be a square matrix');
}
}
static zeros(...args: any): MultiArray | ComplexDecimal {
if (!args.length) {
return ComplexDecimal.zero();
} else if (args.length == 1) {
if ('re' in args[0]) {
return new MultiArray([args[0].re.toNumber(), args[0].re.toNumber()], ComplexDecimal.zero());
} else {
return new MultiArray(MultiArray.oneRowToDim(args[0]), ComplexDecimal.zero());
}
} else {
return new MultiArray(MultiArray.oneRowToDim(args), ComplexDecimal.zero());
}
}
static ones(...args: any): MultiArray | ComplexDecimal {
if (!args.length) {
return ComplexDecimal.one();
} else if (args.length == 1) {
if ('re' in args[0]) {
return new MultiArray([args[0].re.toNumber(), args[0].re.toNumber()], ComplexDecimal.one());
} else {
return new MultiArray(MultiArray.oneRowToDim(args[0]), ComplexDecimal.one());
}
} else {
return new MultiArray(MultiArray.oneRowToDim(args), ComplexDecimal.one());
}
}
static eye(i: ComplexDecimal, j?: ComplexDecimal): MultiArray {
if (!i.re.isInteger()) throw new Error('Invalid call to eye. Non integer number argument.');
const temp = new MultiArray([i.re.toNumber(), j ? j.re.toNumber() : i.re.toNumber()], ComplexDecimal.zero());
for (let n = 0; n < Math.min(i.re.toNumber(), j ? j.re.toNumber() : i.re.toNumber()); n++) {
temp.array[n][n] = ComplexDecimal.one();
}
return temp;
}
static pow(left: MultiArray, right: ComplexDecimal): MultiArray {
let temp1; // matrix power (multiple multiplication)
if (right.re.isInteger() && right.im.eq(0)) {
if (right.re.eq(0)) {
temp1 = MultiArray.eye(new ComplexDecimal(left.dim[0], 0));
} else if (right.re.gt(0)) {
temp1 = MultiArray.clone(left);
} else {
temp1 = MultiArray.inv(left);
}
if (Math.abs(right.re.toNumber()) != 1) {
let temp2 = MultiArray.clone(temp1);
for (let i = 1; i < Math.abs(right.re.toNumber()); i++) {
temp2 = MultiArray.mul(temp2, temp1);
}
temp1 = temp2;
}
return temp1;
} else {
throw new Error("exponent must be integer real in matrix '^'");
}
}
static transpose(left: MultiArray): MultiArray {
const result = new MultiArray(left.dim.slice(0, 2).reverse());
for (let i = 0; i < left.dim[1]; i++) {
result.array[i] = new Array(left.dim[0]);
for (let j = 0; j < left.dim[0]; j++) {
result.array[i][j] = Object.assign({}, left.array[j][i]);
}
}
return result;
}
static ctranspose(left: MultiArray): MultiArray {
const result = new MultiArray(left.dim.slice(0, 2).reverse());
for (let i = 0; i < left.dim[1]; i++) {
result.array[i] = new Array(left.dim[0]);
for (let j = 0; j < left.dim[0]; j++) {
result.array[i][j] = ComplexDecimal.conj(left.array[j][i]);
}
}
return result;
}
static horzcat(L: MultiArray, R: MultiArray): MultiArray {
if (L.dim[0] == R.dim[0]) {
const temp = new MultiArray([L.dim[0], L.dim[1] + R.dim[1]]);
for (let i = 0; i < L.dim[0]; i++) {
temp.array[i] = [];
for (let j = 0; j < L.dim[1]; j++) {
temp.array[i][j] = Object.assign({}, L.array[i][j]);
}
for (let j = 0; j < R.dim[1]; j++) {
temp.array[i][j + L.dim[1]] = Object.assign({}, R.array[i][j]);
}
}
return temp;
} else {
throw new Error('invalid dimensions in horzcat function');
}
}
static vertcat(U: MultiArray, D: MultiArray): MultiArray {
if (U.dim[1] == D.dim[1]) {
const temp = new MultiArray([U.dim[0] + D.dim[0], U.dim[1]]);
for (let i = 0; i < U.dim[0]; i++) {
temp.array[i] = [];
for (let j = 0; j < U.dim[1]; j++) {
temp.array[i][j] = Object.assign({}, U.array[i][j]);
}
}
for (let i = 0; i < D.dim[0]; i++) {
temp.array[i + U.dim[0]] = [];
for (let j = 0; j < D.dim[1]; j++) {
temp.array[i + U.dim[0]][j] = Object.assign({}, D.array[i][j]);
}
}
return temp;
} else {
throw new Error('invalid dimensions in vertcat function');
}
}
static det(M: MultiArray): ComplexDecimal {
if (M.dim[0] == M.dim[1]) {
let det = ComplexDecimal.zero();
if (M.dim[0] == 1) det = M.array[0][0];
else if (M.dim[0] == 2)
det = ComplexDecimal.sub(ComplexDecimal.mul(M.array[0][0], M.array[1][1]), ComplexDecimal.mul(M.array[0][1], M.array[1][0]));
else {
det = ComplexDecimal.zero();
for (let j1 = 0; j1 < M.dim[0]; j1++) {
const m = new MultiArray([M.dim[0] - 1, M.dim[0] - 1], ComplexDecimal.zero());
for (let i = 1; i < M.dim[0]; i++) {
let j2 = 0;
for (let j = 0; j < M.dim[0]; j++) {
if (j == j1) continue;
m.array[i - 1][j2] = M.array[i][j];
j2++;
}
}
det = ComplexDecimal.add(
det,
ComplexDecimal.mul(new ComplexDecimal(Math.pow(-1, 2.0 + j1), 0), ComplexDecimal.mul(M.array[0][j1], MultiArray.det(m))),
);
}
}
return det;
} else {
throw new Error('det: A must be a square matrix');
}
}
static trace(M: MultiArray): ComplexDecimal {
if (M.dim[0] == M.dim[1]) {
let temp: ComplexDecimal = ComplexDecimal.zero();
for (let i = 0; i < M.dim[0]; i++) {
temp = ComplexDecimal.add(temp, M.array[i][i]);
}
return temp;
} else {
throw new Error('trace: invalid dimensions');
}
}
static rows(M: MultiArray): ComplexDecimal {
return new ComplexDecimal(M.dim[0], 0);
}
static cols(M: MultiArray): ComplexDecimal {
return new ComplexDecimal(M.dim[1], 0);
}
static minor(M: MultiArray, p: ComplexDecimal, q: ComplexDecimal): MultiArray {
// minor of matrix (remove line and column)
const temp = MultiArray.clone(M);
temp.array.splice(p.re.toNumber() - 1, 1);
for (let i = 0; i < M.dim[0] - 1; i++) {
temp.array[i].splice(q.re.toNumber() - 1, 1);
}
temp.dim[0]--;
temp.dim[1]--;
return temp;
}
static cofactor(M: MultiArray): MultiArray {
const temp = new MultiArray([M.dim[0], M.dim[1]], ComplexDecimal.zero());
for (let i = 0; i < M.dim[0]; i++) {
for (let j = 0; j < M.dim[1]; j++) {
const minor = MultiArray.minor(M, new ComplexDecimal(i + 1, 0), new ComplexDecimal(j + 1, 0));
let sign: ComplexDecimal;
if ((i + j) % 2 == 0) {
sign = ComplexDecimal.one();
} else {
sign = ComplexDecimal.minusone();
}
temp.array[i][j] = ComplexDecimal.mul(sign, MultiArray.det(minor));
}
}
return temp;
}
static min(M: MultiArray): MultiArray | ComplexDecimal {
let temp: MultiArray;
if (M.dim[0] === 1) {
temp = MultiArray.transpose(M);
} else {
temp = M;
}
const result = new MultiArray([1, temp.dim[1]]);
// result.array = new Array(temp.dim[0]);
// result.array[0] = new Array();
result.array = [new Array(temp.dim[1])];
for (let j = 0; j < temp.dim[1]; j++) {
result.array[0][j] = temp.array[0][j];
for (let i = 1; i < temp.dim[0]; i++) {
result.array[0][j] = ComplexDecimal.min(result.array[0][j], temp.array[i][j]);
}
}
if (temp.dim[1] === 1) {
return result.array[0][0];
} else {
return result;
}
}
static max(M: MultiArray): MultiArray | ComplexDecimal {
let temp: MultiArray;
if (M.dim[0] === 1) {
temp = MultiArray.transpose(M);
} else {
temp = M;
}
const result = new MultiArray([1, temp.dim[1]]);
// result.array = new Array(temp.dim[0]);
result.array = [new Array(temp.dim[1])];
for (let j = 0; j < temp.dim[1]; j++) {
result.array[0][j] = temp.array[0][j];
for (let i = 1; i < temp.dim[0]; i++) {
result.array[0][j] = ComplexDecimal.max(result.array[0][j], temp.array[i][j]);
}
}
if (temp.dim[1] === 1) {
return result.array[0][0];
} else {
return result;
}
}
static mean(M: MultiArray): MultiArray | ComplexDecimal {
let temp: MultiArray;
if (M.dim[0] === 1) {
temp = MultiArray.transpose(M);
} else {
temp = M;
}
const result = new MultiArray([1, temp.dim[1]]);
// result.array = new Array(temp.dim[0]);
result.array = [new Array(temp.dim[1])];
for (let j = 0; j < temp.dim[1]; j++) {
result.array[0][j] = temp.array[0][j];
for (let i = 1; i < temp.dim[0]; i++) {
result.array[0][j] = ComplexDecimal.add(result.array[0][j], temp.array[i][j]);
}
result.array[0][j] = ComplexDecimal.rdiv(result.array[0][j], new ComplexDecimal(temp.dim[0], 0));
}
if (temp.dim[1] === 1) {
return result.array[0][0];
} else {
return result;
}
}
static gauss(M: MultiArray, x: MultiArray): MultiArray | undefined {
// Gaussian elimination algorithm for solving systems of linear equations.
// Adapted from: https://github.com/itsravenous/gaussian-elimination
if (M.dim[0] != M.dim[1]) throw new Error('invalid dimensions in function gauss');
const A: MultiArray = MultiArray.clone(M);
let i: number, k: number, j: number;
const DMin = Math.min(x.dim[0], x.dim[1]);
if (DMin == x.dim[1]) {
x = MultiArray.transpose(x);
}
// Just make a single matrix
for (i = 0; i < A.dim[0]; i++) {
A.array[i].push(x.array[0][i]);
}
const n = A.dim[0];
for (i = 0; i < n; i++) {
// Search for maximum in this column
let maxEl = ComplexDecimal.abs(A.array[i][i]),
maxRow = i;
for (k = i + 1; k < n; k++) {
if (ComplexDecimal.abs(A.array[k][i]).re.gt(maxEl.re)) {
maxEl = ComplexDecimal.abs(A.array[k][i]);
maxRow = k;
}
}
// Swap maximum row with current row (column by column)
for (k = i; k < n + 1; k++) {
const tmp = A.array[maxRow][k];
A.array[maxRow][k] = A.array[i][k];
A.array[i][k] = tmp;
}
// Make all rows below this one 0 in current column
for (k = i + 1; k < n; k++) {
const c = ComplexDecimal.rdiv(ComplexDecimal.neg(A.array[k][i]), A.array[i][i]);
for (j = i; j < n + 1; j++) {
if (i === j) {
A.array[k][j] = ComplexDecimal.zero();
} else {
A.array[k][j] = ComplexDecimal.add(A.array[k][j], ComplexDecimal.mul(c, A.array[i][j]));
}
}
}
}
// Solve equation Ax=b for an upper triangular matrix A
const X = new MultiArray([1, n], ComplexDecimal.zero());
for (i = n - 1; i > -1; i--) {
X.array[0][i] = ComplexDecimal.rdiv(A.array[i][n], A.array[i][i]);
for (k = i - 1; k > -1; k--) {
A.array[k][n] = ComplexDecimal.sub(A.array[k][n], ComplexDecimal.mul(A.array[k][i], X.array[0][i]));
}
}
return X;
}
static lu(M: MultiArray): any {
const n = M.dim[0];
const lower = new MultiArray([M.dim[0], M.dim[1]], ComplexDecimal.zero());
const upper = new MultiArray([M.dim[0], M.dim[1]], ComplexDecimal.zero());
// Decomposing matrix into Upper and
// Lower triangular matrix
for (let i = 0; i < n; i++) {
// Upper Triangular
for (let k = i; k < n; k++) {
// Summation of L(i, j) * U(j, k)
let sum: ComplexDecimal = ComplexDecimal.zero();
for (let j = 0; j < i; j++) {
sum = ComplexDecimal.add(sum, ComplexDecimal.mul(lower.array[i][j], upper.array[j][k]));
}
// Evaluating U(i, k)
upper.array[i][k] = ComplexDecimal.sub(M.array[i][k], sum);
}
// Lower Triangular
for (let k = i; k < n; k++) {
if (i == k) {
// Diagonal as 1
lower.array[i][i] = ComplexDecimal.one();
} else {
// Summation of L(k, j) * U(j, i)
let sum: ComplexDecimal = ComplexDecimal.zero();
for (let j = 0; j < i; j++) {
sum = ComplexDecimal.add(sum, ComplexDecimal.mul(lower.array[k][j], upper.array[j][i]));
}
// Evaluating L(k, i)
lower.array[k][i] = ComplexDecimal.rdiv(ComplexDecimal.sub(M.array[k][i], sum), upper.array[i][i]);
}
}
}
return EvaluatorPointer.nodeOp('*', lower, upper);
}
static pivot(M: MultiArray): MultiArray {
const n = M.dim[0];
const id = MultiArray.eye(new ComplexDecimal(n, 0));
for (let i = 0; i < n; i++) {
let maxm = ComplexDecimal.abs(M.array[i][i]);
let row = i;
for (let j = i; j < n; j++)
if (ComplexDecimal.abs(M.array[j][i]).re.gt(maxm.re)) {
maxm = ComplexDecimal.abs(M.array[j][i]);
row = j;
}
if (i != row) {
const tmp = id.array[i];
id.array[i] = id.array[row];
id.array[row] = tmp;
}
}
return id;
}
static adj(M: MultiArray): MultiArray {
return MultiArray.ctranspose(MultiArray.cofactor(M));
}
static size(X: any, DIM: any) {
if (arguments.length == 1) {
if ('re' in X) {
return { array: [[ComplexDecimal.one(), ComplexDecimal.one()]] };
} else if ('array' in X) {
return { array: [[new ComplexDecimal(X.dim[0], 0), new ComplexDecimal(X.dim[1], 0)]] };
}
} else if (arguments.length == 2) {
if ('re' in DIM && DIM.im.eq(0)) {
if ('re' in X) {
return ComplexDecimal.one();
} else if ('array' in X) {
if (Math.trunc(DIM.re.toNumber()) == 1) {
return new ComplexDecimal(X.dim[0], 0);
} else if (Math.trunc(DIM.re.toNumber()) == 2) {
return new ComplexDecimal(X.dim[1], 0);
} else if (Math.trunc(DIM.re.toNumber()) > 2) {
return new ComplexDecimal(1, 0);
} else {
throw new Error('size: requested dimension DIM (= ' + Math.trunc(DIM.re.toNumber()) + ') out of range');
}
}
} else {
throw new Error('size: DIM must be a positive integer');
}
} else {
throw new Error('Invalid call to size.');
}
}
static sub2ind(DIMS: any, ...S: any) {
if (arguments.length > 1) {
const n = DIMS;
return new ComplexDecimal(1, 0);
} else {
throw new Error('Invalid call to sub2ind.');
}
}
static ind2sub(DIMS: any, IND: any) {
if (arguments.length == 2) {
return new ComplexDecimal(1, 0);
} else {
throw new Error('Invalid call to ind2sub.');
}
}
}