UNPKG

mathjslab

Version:

MathJSLab - An interpreter with language syntax like MATLAB®/Octave, ISBN 978-65-00-82338-7.

1,021 lines (970 loc) 42 kB
import { ComplexDecimal } from './ComplexDecimal'; import { type TBinaryOperationName } from './ComplexDecimal'; import { type ElementType, MultiArray } from './MultiArray'; import { Evaluator } from './Evaluator'; import { CharString } from './CharString'; import * as AST from './AST'; import { Structure } from './Structure'; import { Decimal } from 'decimal.js'; abstract class CoreFunctions { public static functions: Record<string, Function> = { isempty: CoreFunctions.isempty, isscalar: CoreFunctions.isscalar, ismatrix: CoreFunctions.ismatrix, isvector: CoreFunctions.isvector, iscell: CoreFunctions.iscell, isrow: CoreFunctions.isrow, iscolumn: CoreFunctions.iscolumn, isstruct: CoreFunctions.isstruct, ndims: CoreFunctions.ndims, rows: CoreFunctions.rows, columns: CoreFunctions.columns, length: CoreFunctions.Length, numel: CoreFunctions.numel, ind2sub: CoreFunctions.ind2sub, sub2ind: CoreFunctions.sub2ind, size: CoreFunctions.size, colon: CoreFunctions.colon, linspace: CoreFunctions.linspace, logspace: CoreFunctions.logspace, meshgrid: CoreFunctions.meshgrid, ndgrid: CoreFunctions.ndgrid, repmat: CoreFunctions.repmat, reshape: CoreFunctions.reshape, squeeze: CoreFunctions.squeeze, zeros: CoreFunctions.zeros, ones: CoreFunctions.ones, rand: CoreFunctions.rand, randi: CoreFunctions.randi, cat: CoreFunctions.cat, horzcat: CoreFunctions.horzcat, vertcat: CoreFunctions.vertcat, sum: CoreFunctions.sum, sumsq: CoreFunctions.sumsq, prod: CoreFunctions.prod, mean: CoreFunctions.mean, min: CoreFunctions.min, max: CoreFunctions.max, cummin: CoreFunctions.cummin, cummax: CoreFunctions.cummax, cumsum: CoreFunctions.cumsum, cumprod: CoreFunctions.cumprod, struct: CoreFunctions.struct, }; /** * Throw invalid call error if (optional) test is true. * @param name */ public static throwInvalidCallError(name: string, test: boolean = true): void { if (test) { throw new Error(`Invalid call to ${name}. Type 'help ${name}' to see correct usage.`); } } /** * * @param name * @param M */ public static throwErrorIfCellArray(name: string, M: MultiArray | ComplexDecimal): void { if (M instanceof MultiArray && M.isCell) { throw new Error(`${name}: wrong type argument 'cell'`); } } /** * Return true if M is an empty matrix * @param X * @returns */ public static isempty(X: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('isempty', arguments.length !== 1); return MultiArray.isEmpty(X) ? ComplexDecimal.true() : ComplexDecimal.false(); } /** * Return true if X is a scalar. * @param X * @returns */ public static isscalar(X: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('isscalar', arguments.length !== 1); return MultiArray.isScalar(X) ? ComplexDecimal.true() : ComplexDecimal.false(); } /** * Return true if X is a 2-D array. * @param X * @returns */ public static ismatrix(X: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('ismatrix', arguments.length !== 1); return MultiArray.isMatrix(X) ? ComplexDecimal.true() : ComplexDecimal.false(); } /** * Return true if X is a vector. * @param X * @returns */ public static isvector(X: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('isvector', arguments.length !== 1); return MultiArray.isVector(X) ? ComplexDecimal.true() : ComplexDecimal.false(); } /** * Return true if X is a cell array object. * @param M * @returns */ public static iscell(X: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('iscell', arguments.length !== 1); return MultiArray.isCellArray(X) ? ComplexDecimal.true() : ComplexDecimal.false(); } /** * Return true if X is a row vector. * @param X * @returns */ public static isrow(X: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('isrow', arguments.length !== 1); return MultiArray.isRowVector(X) ? ComplexDecimal.true() : ComplexDecimal.false(); } /** * Return true if X is a column vector. * @param X * @returns */ public static iscolumn(X: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('iscolumn', arguments.length !== 1); return MultiArray.isColumnVector(X) ? ComplexDecimal.true() : ComplexDecimal.false(); } /** * Return true if X is a column vector. * @param X * @returns */ public static isstruct(X: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('isstruct', arguments.length !== 1); return Structure.isStructure(X) ? ComplexDecimal.true() : ComplexDecimal.false(); } /** * Return the number of dimensions of M. * @param M * @returns */ public static ndims(M: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('ndims', arguments.length !== 1); return new ComplexDecimal(MultiArray.scalarToMultiArray(M).dimension.length); } /** * eturn the number of rows of M. * @param M * @returns */ public static rows(M: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('rows', arguments.length !== 1); return new ComplexDecimal(MultiArray.scalarToMultiArray(M).dimension[0]); } /** * Return the number of columns of M. * @param M * @returns */ public static columns(M: ElementType): ComplexDecimal { CoreFunctions.throwInvalidCallError('columns', arguments.length !== 1); return new ComplexDecimal(MultiArray.scalarToMultiArray(M).dimension[1]); } /** * Return the length of the object M. The length is the number of elements * along the largest dimension. * @param M * @returns */ public static Length(M: ElementType): ComplexDecimal { // Capitalized name so as not to conflict with the built-in 'Function.length' property. CoreFunctions.throwInvalidCallError('length', arguments.length !== 1); return new ComplexDecimal(Math.max(...MultiArray.scalarToMultiArray(M).dimension)); } public static numel(M: ElementType, ...IDX: ElementType[]): ComplexDecimal { if (IDX.length === 0) { return M instanceof MultiArray ? new ComplexDecimal(MultiArray.linearLength(M)) : ComplexDecimal.one(); } else { const m = MultiArray.scalarToMultiArray(M); const index = IDX.map((idx, i) => { if (idx instanceof MultiArray) { return MultiArray.linearLength(idx); } else if (idx instanceof CharString && idx.str === ':') { return i < m.dimension.length ? m.dimension[i] : 1; } else { return 1; } }); return new ComplexDecimal(index.reduce((p, c) => p * c, 1)); } } /** * Convert linear indices to subscripts. * @param DIMS * @param IND * @returns */ public static ind2sub(DIMS: ElementType, IND: ElementType): AST.NodeReturnList { CoreFunctions.throwInvalidCallError('ind2sub', arguments.length !== 2); return AST.nodeReturnList((length: number, index: number): ElementType => { if (length === 1) { return IND; } else { let dims = (MultiArray.linearize(DIMS) as ComplexDecimal[]).map((value) => value.re.toNumber()); let lenghtGreater = false; if (length > dims.length) { MultiArray.appendSingletonTail(dims, length); lenghtGreater = true; } else { dims = dims.slice(0, length - 1); } const ind = MultiArray.scalarToMultiArray(IND); const result = new MultiArray(ind.dimension); const subscript = ind.array.map((row) => row.map((value) => MultiArray.ind2subNumber(dims, (value as ComplexDecimal).re.toNumber()))); if (index === length - 1 && lenghtGreater) { result.array = subscript.map((row) => row.map((value) => new ComplexDecimal(value[length]))); } else { result.array = subscript.map((row) => row.map((value) => new ComplexDecimal(value[index]))); } result.type = ComplexDecimal.REAL; return MultiArray.MultiArrayToScalar(result); } }); } /** * Convert subscripts to linear indices. * @param DIMS * @param S * @returns */ public static sub2ind(DIMS: ElementType, ...S: ElementType[]): ElementType { CoreFunctions.throwInvalidCallError('sub2ind', arguments.length < 2); const dims = (MultiArray.linearize(DIMS) as ComplexDecimal[]).map((value) => value.re.toNumber()); const subscript: MultiArray[] = S.map((s) => MultiArray.scalarToMultiArray(s)); for (let s = 1; s < subscript.length; s++) { if (!MultiArray.arrayEquals(subscript[0].dimension, subscript[s].dimension)) { throw new Error('sub2ind: all subscripts must be of the same size.'); } } MultiArray.appendSingletonTail(dims, subscript.length); const result = new MultiArray(subscript[0].dimension); for (let n = 0; n < MultiArray.linearLength(subscript[0]); n++) { const subscriptN = subscript.map((s) => { const [i, j] = MultiArray.linearIndexToMultiArrayRowColumn(s.dimension[0], s.dimension[1], n); return s.array[i][j] as ComplexDecimal; }); const index = MultiArray.parseSubscript(dims, subscriptN, 'index '); const [p, q] = MultiArray.linearIndexToMultiArrayRowColumn(result.dimension[0], result.dimension[1], n); result.array[p][q] = new ComplexDecimal(index + 1); } return MultiArray.MultiArrayToScalar(result); } /** * Returns array dimensions. * @param M MultiArray * @param DIM Dimensions * @returns Dimensions of `M` parameter. */ public static size(M: ElementType, ...DIM: ElementType[]): ElementType { CoreFunctions.throwInvalidCallError('size', arguments.length < 1); const parseDimension = (dimension: ComplexDecimal): number => { const dim = dimension.re.toNumber(); if (dim < 1 || !dimension.re.trunc().eq(dimension.re)) { throw new Error(`size: requested dimension DIM (= ${dim}) out of range. DIM must be a positive integer.`); } return dim; }; const sizeDim = M instanceof MultiArray ? M.dimension.slice() : [1, 1]; if (DIM.length === 0) { const result = new MultiArray([1, sizeDim.length]); result.array[0] = sizeDim.map((d) => new ComplexDecimal(d)); result.type = ComplexDecimal.REAL; return result; } else { const dims = DIM.length === 1 && DIM[0] instanceof MultiArray ? (MultiArray.linearize(DIM[0]) as ComplexDecimal[]).map((dim) => parseDimension(dim)) : DIM.map((dim: any) => parseDimension(MultiArray.firstElement(dim) as ComplexDecimal)); MultiArray.appendSingletonTail(sizeDim, Math.max(...dims)); const result = new MultiArray([1, dims.length]); result.array[0] = dims.map((dim: number) => new ComplexDecimal(sizeDim[dim - 1])); result.type = ComplexDecimal.REAL; return MultiArray.MultiArrayToScalar(result); } } /** * Return the result of the colon expression. * @param args * @returns */ public static colon(...args: ElementType[]): ElementType { if (args.length === 2) { return MultiArray.expandRange(MultiArray.firstElement(args[0]) as ComplexDecimal, MultiArray.firstElement(args[1]) as ComplexDecimal); } else if (args.length === 3) { return MultiArray.expandRange( MultiArray.firstElement(args[0]) as ComplexDecimal, MultiArray.firstElement(args[2]) as ComplexDecimal, MultiArray.firstElement(args[1]) as ComplexDecimal, ); } else { CoreFunctions.throwInvalidCallError('colon'); } } /** * Return a row vector with linearly spaced elements. * @param args * @returns */ public static linspace(...args: ElementType[]): ElementType { let start: ComplexDecimal[] = []; let end: ComplexDecimal[] = []; let n: ComplexDecimal | MultiArray = ComplexDecimal.one(); const linearizeStartEnd = () => { const errorMessage = 'linspace: START, END must be scalars or vectors.'; if (args[0] instanceof MultiArray) { if (!MultiArray.isVector(args[0])) { throw new Error(errorMessage); } start = MultiArray.linearize(args[0]) as ComplexDecimal[]; } else { start = [args[0] as ComplexDecimal]; } if (args[1] instanceof MultiArray) { if (!MultiArray.isVector(args[1])) { throw new Error(errorMessage); } end = MultiArray.linearize(args[1]) as ComplexDecimal[]; } else { end = [args[1] as ComplexDecimal]; } }; if (args.length === 2) { linearizeStartEnd(); n = new ComplexDecimal(100); } else if (args.length === 3) { linearizeStartEnd(); n = MultiArray.MultiArrayToScalar(args[2]) as MultiArray | ComplexDecimal; if (n instanceof MultiArray) { throw new Error('linspace: N must be a scalar.'); } } else { CoreFunctions.throwInvalidCallError('linspace'); } if (start.length !== end.length) { throw new Error('linspace: vectors must be of equal length'); } n.re = Decimal.floor(n.re); if (n.re.isNegative()) { n.re = new Decimal(0); } n.im = new Decimal(0); const result = new MultiArray([start.length, n.re.toNumber()]); for (let i = 0; i < start.length; i++) { const delta = ComplexDecimal.rdiv(ComplexDecimal.sub(end[i], start[i]), ComplexDecimal.sub(n, ComplexDecimal.one())); result.array[i][0] = start[i]; for (let j = 1; j < n.re.toNumber() - 1; j++) { result.array[i][j] = ComplexDecimal.add(start[i], ComplexDecimal.mul(new ComplexDecimal(j), delta)); } result.array[i][n.re.toNumber() - 1] = end[i]; } return MultiArray.MultiArrayToScalar(result); } /** * Return a row vector with elements logarithmically spaced. * @param args * @returns */ public static logspace(...args: ElementType[]): ElementType { let start: ComplexDecimal[] = []; let end: ComplexDecimal[] = []; let n: ComplexDecimal | MultiArray = ComplexDecimal.one(); const linearizeStartEnd = () => { const errorMessage = 'logspace: START, END must be scalars or vectors.'; if (args[0] instanceof MultiArray) { if (!MultiArray.isVector(args[0])) { throw new Error(errorMessage); } start = MultiArray.linearize(args[0]) as ComplexDecimal[]; } else { start = [args[0] as ComplexDecimal]; } if (args[1] instanceof MultiArray) { if (!MultiArray.isVector(args[1])) { throw new Error(errorMessage); } end = MultiArray.linearize(args[1]) as ComplexDecimal[]; } else { end = [args[1] as ComplexDecimal]; } }; if (args.length === 2) { linearizeStartEnd(); n = new ComplexDecimal(50); } else if (args.length === 3) { linearizeStartEnd(); n = MultiArray.MultiArrayToScalar(args[2]) as MultiArray | ComplexDecimal; if (n instanceof MultiArray) { throw new Error('logspace: N must be a scalar.'); } } else { CoreFunctions.throwInvalidCallError('linspace'); } if (start.length !== end.length) { throw new Error('logspace: vectors must be of equal length'); } n.re = Decimal.floor(n.re); if (n.re.isNegative()) { n.re = new Decimal(0); } n.im = new Decimal(0); const result = new MultiArray([start.length, n.re.toNumber()]); for (let i = 0; i < start.length; i++) { if (Boolean(ComplexDecimal.eq(end[i], ComplexDecimal.pi()).re.toNumber())) { end[i] = ComplexDecimal.log10(ComplexDecimal.pi()); } const delta = ComplexDecimal.rdiv(ComplexDecimal.sub(end[i], start[i]), ComplexDecimal.sub(n, ComplexDecimal.one())); result.array[i][0] = ComplexDecimal.power(new ComplexDecimal(10), start[i]); for (let j = 1; j < n.re.toNumber() - 1; j++) { result.array[i][j] = ComplexDecimal.power(new ComplexDecimal(10), ComplexDecimal.add(start[i], ComplexDecimal.mul(new ComplexDecimal(j), delta))); } result.array[i][n.re.toNumber() - 1] = ComplexDecimal.power(new ComplexDecimal(10), end[i]); } return MultiArray.MultiArrayToScalar(result) as MultiArray | ComplexDecimal; } /** * Generate 2-D and 3-D grids. * @param args * @returns */ public static meshgrid(...args: ElementType[]): AST.NodeReturnList { CoreFunctions.throwInvalidCallError('meshgrid', args.length > 3 || args.length < 1); const argsLinearized: ElementType[][] = []; for (let i = 0; i < 3; i++) { if (args.length > i) { if (args[i] instanceof MultiArray) { if (!MultiArray.isVector(args[i])) { throw new Error('meshgrid: arguments must be vectors.'); } argsLinearized[i] = MultiArray.firstVector(args[i]); } else { argsLinearized[i] = [args[i]]; } } else { break; } } return AST.nodeReturnList((length: number, index: number): ElementType => { if (length > 3) { throw new Error('meshgrid: function called with too many outputs.'); } const args: ElementType[][] = argsLinearized; while (args.length < length) { args[args.length] = args[args.length - 1]; } const result = length > 2 ? new MultiArray([args[1].length, args[0].length, args[2].length]) : new MultiArray([args[1].length, args[0].length]); switch (index) { case 0: for (let i = 0; i < result.array.length; i++) { result.array[i] = args[0]; } case 1: for (let p = 0; p < result.array.length; p += result.dimension[0]) { for (let i = 0; i < result.dimension[0]; i++) { result.array[p + i] = new Array(result.dimension[1]).fill(args[1][i]); } } case 2: for (let p = 0, n = 0; p < result.array.length; p += result.dimension[0], n++) { for (let i = 0; i < result.dimension[0]; i++) { result.array[p + i] = new Array(result.dimension[1]).fill(args[2][n]); } } } return MultiArray.MultiArrayToScalar(result); }); } /** * Given n vectors X1, ..., Xn, returns n arrays of n dimensions. * @returns */ public static ndgrid(...args: ElementType[]): AST.NodeReturnList { const argsLinearized: MultiArray[] = []; for (let i = 0; i < args.length; i++) { if (args[i] instanceof MultiArray) { if (!MultiArray.isVector(args[i])) { throw new Error('ndgrid: arguments must be vectors.'); } argsLinearized[i] = args[i] as MultiArray; } else { argsLinearized[i] = MultiArray.scalarToMultiArray(args[i]); } } return AST.nodeReturnList((length: number, index: number): ElementType => { const args: MultiArray[] = argsLinearized; if (args.length === 1) { while (args.length < length) { args[args.length] = args[args.length - 1]; } } if (length > args.length) { throw new Error('ndgrid: function called with too many outputs.'); } const shape: number[] = args.map((M) => M.dimension[0] * M.dimension[1]); const r: number[] = new Array(args.length).fill(1); r[index] = shape[index]; shape[index] = 1; return MultiArray.evaluate(new MultiArray(shape, MultiArray.reshape(args[index], r))); }); } /** * Repeat N-D array. * @param A * @param dim * @returns */ public static repmat(A: ElementType, ...dim: ElementType[]): ElementType { let dimension: ElementType[]; if (dim.length === 1) { dimension = MultiArray.firstVector(dim[0]); } else { const dimArray = new Array(dim.length); dimension = dim.map((d, i) => { const result = MultiArray.MultiArrayToScalar(d); dimArray[i] = result instanceof MultiArray ? 1 : 0; return result; }); if (dimArray.reduce((p, c) => p + c, 0)) { throw new Error('repmat: all input arguments must be scalar.'); } } return MultiArray.evaluate( new MultiArray( dimension.map((value) => (value as ComplexDecimal).re.toNumber()), A, ), ); } /** * Return a matrix with the specified dimensions whose elements are taken from the matrix M. * @param M * @param dimension * @returns */ public static reshape(M: ElementType, ...dimension: ElementType[]): ElementType { const m = MultiArray.scalarToMultiArray(M); let d: number = -1; const dims = dimension.map((dim, i) => { const element = MultiArray.firstElement(dim) as ComplexDecimal; if (MultiArray.isEmpty(element)) { if (d < 0) { d = i; return 1; } else { throw new Error('reshape: only a single dimension can be unknown.'); } } else { return element.re.toNumber(); } }); return MultiArray.reshape(m, dims, d >= 0 ? d : undefined); } /** * Remove singleton dimensions. * @param args * @returns */ public static squeeze(...args: ElementType[]): ElementType { CoreFunctions.throwInvalidCallError('squeeze', args.length !== 1); if (args[0] instanceof MultiArray && !args[0].isCell) { if (args[0].dimension.length > 2) { return MultiArray.reshape( args[0], args[0].dimension.filter((value) => value !== 1), ); } else { return args[0]; } } else { return args[0]; } } /** * Create MultiArray with all elements equals `fill` parameter. * @param fill Value to fill MultiArray. * @param dimension Dimensions of created MultiArray. * @returns MultiArray filled with `fill` parameter. */ private static newFilled(fill: ElementType, name: string, ...dimension: ElementType[]): ElementType { let dims: number[]; if (dimension.length === 0) { return fill; } else if (dimension.length === 1) { const m = MultiArray.scalarToMultiArray(dimension[0]); if (m.dimension.length > 2 || m.dimension[0] !== 1) { throw new Error(`${name} (A): use ${name} (size (A)) instead.`); } dims = m.array[0].map((data) => (data as ComplexDecimal).re.toNumber()); if (dims.length === 1) { dims[dims.length] = dims[0]; } } else { dims = (dimension as (MultiArray | ComplexDecimal)[]).map((dim) => { if (dim instanceof MultiArray) { throw new Error(`${name}: dimensions must be scalars.`); } return dim.re.toNumber(); }); } return MultiArray.MultiArrayToScalar(new MultiArray(dims, fill)); } /** * Create MultiArray with all elements filled with `fillFunction` result. * The parameter passed to `fillFunction` is a linear index of element. * @param fillFunction Function to be called and the result fills element of MultiArray created. * @param dimension Dimensions of created MultiArray. * @returns MultiArray filled with `fillFunction` results for each element. */ private static newFilledEach(fillFunction: (index: number) => ElementType, ...dimension: ElementType[]): ElementType { let dims: number[]; if (dimension.length === 1) { dims = (MultiArray.linearize(dimension[0]) as ComplexDecimal[]).map((dim) => dim.re.toNumber()); } else if (dimension.length === 0) { return fillFunction(0); } else { dims = dimension.map((dim) => (MultiArray.firstElement(dim) as ComplexDecimal).re.toNumber()); } if (dims.length === 1) { dims[dims.length] = dims[0]; } const result = new MultiArray(dims); for (let n = 0; n < MultiArray.linearLength(result); n++) { const [i, j] = MultiArray.linearIndexToMultiArrayRowColumn(result.dimension[0], result.dimension[1], n); result.array[i][j] = fillFunction(n); } MultiArray.setType(result); return MultiArray.MultiArrayToScalar(result); } /** * Create array of all zeros. * @param dimension * @returns */ public static zeros(...dimension: ElementType[]): ElementType { return CoreFunctions.newFilled(ComplexDecimal.zero(), 'zeros', ...dimension); } /** * Create array of all ones. * @param dimension * @returns */ public static ones(...dimension: ElementType[]): ElementType { return CoreFunctions.newFilled(ComplexDecimal.one(), 'ones', ...dimension); } /** * Uniformly distributed pseudorandom numbers distributed on the * interval (0, 1). * @param dimension * @returns */ public static rand(...dimension: ElementType[]): ElementType { return CoreFunctions.newFilledEach(() => ComplexDecimal.random(), ...dimension); } /** * Uniformly distributed pseudorandom integers. * @param imax * @param args * @returns */ public static randi(range: ElementType, ...dimension: ElementType[]): ElementType { let imin: ComplexDecimal; let imax: ComplexDecimal; if (range instanceof MultiArray) { const rangeLinearized = MultiArray.linearize(range) as ComplexDecimal[]; if (rangeLinearized.length > 1) { imin = rangeLinearized[0]; imax = rangeLinearized[1]; } else if (rangeLinearized.length > 0) { imin = ComplexDecimal.zero(); imax = rangeLinearized[0]; } else { throw new Error('bounds(1): out of bound 0 (dimensions are 0x0)'); } } else { imin = ComplexDecimal.zero(); imax = range as ComplexDecimal; } if (!(imin.re.isInt() && imax.re.isInt())) { throw new Error(`randi: must be integer bounds.`); } if (ComplexDecimal.gt(imax, imin)) { return CoreFunctions.newFilledEach( imin.re.isZero() ? () => ComplexDecimal.round(ComplexDecimal.mul(imax, ComplexDecimal.random())) : () => ComplexDecimal.round(ComplexDecimal.add(ComplexDecimal.mul(ComplexDecimal.sub(imax, imin), ComplexDecimal.random()), imin)), ...dimension, ); } else { if (imin.re.isZero()) { throw new Error(`randi: require imax >= 1.`); } else { throw new Error(`randi: require imax > imin.`); } } } /** * Return the concatenation of N-D array objects, ARRAY1, ARRAY2, ..., * ARRAYN along dimension `DIM`. * @param DIM Dimension of concatenation. * @param ARRAY Arrays to concatenate. * @returns Concatenated arrays along dimension `DIM`. */ public static cat(DIM: ElementType, ...ARRAY: ElementType[]): MultiArray { return MultiArray.concatenate((MultiArray.firstElement(DIM) as ComplexDecimal).re.toNumber() - 1, 'cat', ...ARRAY.map((m) => MultiArray.scalarToMultiArray(m))); } /** * Concatenate arrays horizontally. * @param ARRAY Arrays to concatenate horizontally. * @returns Concatenated arrays horizontally. */ public static horzcat(...ARRAY: ElementType[]): MultiArray { return MultiArray.concatenate(1, 'horzcat', ...ARRAY.map((m) => MultiArray.scalarToMultiArray(m))); } /** * Concatenate arrays vertically. * @param ARRAY Arrays to concatenate vertically. * @returns Concatenated arrays vertically. */ public static vertcat(...ARRAY: ElementType[]): MultiArray { return MultiArray.concatenate(0, 'vertcat', ...ARRAY.map((m) => MultiArray.scalarToMultiArray(m))); } /** * Calculate sum of elements along dimension DIM. * @param M Array * @param DIM Dimension * @returns Array with sum of elements along dimension DIM. */ public static sum(M: MultiArray, DIM?: ElementType): ElementType { // TODO: Test if MultiArray.reduceToArray is better than MultiArray.reduce. return MultiArray.reduce(DIM ? (MultiArray.firstElement(DIM) as ComplexDecimal).re.toNumber() - 1 : MultiArray.firstNonSingleDimension(M), M, (p, c) => ComplexDecimal.add(p as ComplexDecimal, c as ComplexDecimal), ); } /** * Calculate sum of squares of elements along dimension DIM. * @param M Matrix * @param DIM Dimension * @returns One dimensional matrix with sum of squares of elements along dimension DIM. */ public static sumsq(M: MultiArray, DIM?: ElementType): ElementType { // TODO: Test if MultiArray.reduceToArray is better than MultiArray.reduce. return MultiArray.reduce(DIM ? (MultiArray.firstElement(DIM) as ComplexDecimal).re.toNumber() - 1 : MultiArray.firstNonSingleDimension(M), M, (p, c) => ComplexDecimal.add( ComplexDecimal.mul(p as ComplexDecimal, ComplexDecimal.conj(p as ComplexDecimal)), ComplexDecimal.mul(c as ComplexDecimal, ComplexDecimal.conj(c as ComplexDecimal)), ), ); } /** * Calculate product of elements along dimension DIM. * @param M Matrix * @param DIM Dimension * @returns One dimensional matrix with product of elements along dimension DIM. */ public static prod(M: MultiArray, DIM?: ElementType): ElementType { // TODO: Test if MultiArray.reduceToArray is better than MultiArray.reduce. return MultiArray.reduce(DIM ? (MultiArray.firstElement(DIM) as ComplexDecimal).re.toNumber() - 1 : MultiArray.firstNonSingleDimension(M), M, (p, c) => ComplexDecimal.mul(p as ComplexDecimal, c as ComplexDecimal), ); } /** * Calculate average or mean of elements along dimension DIM. * @param M Matrix * @param DIM Dimension * @returns One dimensional matrix with product of elements along dimension DIM. */ public static mean(M: MultiArray, DIM?: ElementType): ElementType { // TODO: Test if MultiArray.reduceToArray is better than MultiArray.reduce. const dim = DIM ? (MultiArray.firstElement(DIM) as ComplexDecimal).re.toNumber() - 1 : MultiArray.firstNonSingleDimension(M); const sum = MultiArray.reduce(dim, M, (p, c) => ComplexDecimal.add(p as ComplexDecimal, c as ComplexDecimal)); return MultiArray.MultiArrayOpScalar('rdiv', MultiArray.scalarToMultiArray(sum), new ComplexDecimal(M.dimension[dim])); } /** * Base method of min and max user functions. * @param op 'min' or 'max'. * @param args One to three arguments like user function. * @returns Return like user function. */ private static minMax(op: 'min' | 'max', ...args: ElementType[]): MultiArray | AST.NodeReturnList | undefined { const minMaxAlogDimension = (M: MultiArray, dimension: number) => { const reduced = MultiArray.reduceToArray(dimension, M); const resultM = new MultiArray(reduced.dimension); const indexM = new MultiArray(reduced.dimension); const cmp = op === 'min' ? 'lt' : 'gt'; for (let i = 0; i < indexM.array.length; i++) { for (let j = 0; j < indexM.array[0].length; j++) { const [m, n] = ComplexDecimal[`minMaxArray${args[0]!.type < 2 ? 'Real' : 'Complex'}WithIndex`](cmp, ...(reduced.array[i][j] as unknown as ComplexDecimal[])); resultM.array[i][j] = m; indexM.array[i][j] = new ComplexDecimal(n + 1); } } return AST.nodeReturnList((length: number, index: number): any => { Evaluator.throwErrorIfGreaterThanReturnList(2, length); return MultiArray.MultiArrayToScalar(index === 0 ? resultM : indexM); }); }; switch (args.length) { case 1: // Along first non singleton dimension. const dimension = MultiArray.firstNonSingleDimension(MultiArray.scalarToMultiArray(args[0])); return minMaxAlogDimension(MultiArray.scalarToMultiArray(args[0]), dimension); case 2: // Broadcast return MultiArray.elementWiseOperation((op + 'Wise') as TBinaryOperationName, MultiArray.scalarToMultiArray(args[0]), args[1] as MultiArray); case 3: // Along selected dimension. if (!MultiArray.isEmpty(args[1])) { // Error if second argument is different from [](0x0). throw new Error(`${op}: second argument is ignored`); } return minMaxAlogDimension(MultiArray.scalarToMultiArray(args[0]), (MultiArray.firstElement(args[2]) as ComplexDecimal).re.toNumber() - 1); default: CoreFunctions.throwInvalidCallError(op); } } /** * Minimum elements of array. * @param M * @returns */ public static min(...args: ElementType[]): MultiArray | AST.NodeReturnList | undefined { return CoreFunctions.minMax('min', ...args); } /** * Maximum elements of array. * @param M * @returns */ public static max(...args: ElementType[]): MultiArray | AST.NodeReturnList | undefined { return CoreFunctions.minMax('max', ...args); } /** * Base method of cummin and cummax user functions. * @param op 'min' or 'max'. * @param M MultiArray. * @param DIM Dimension in which the cumulative operation occurs. * @returns MultiArray with cumulative values along dimension DIM. */ private static cumMinMax(op: 'min' | 'max', M: ElementType, DIM?: ElementType): AST.NodeReturnList { M = MultiArray.scalarToMultiArray(M); const indexM = new MultiArray(M.dimension); let compare: ComplexDecimal; let index: ComplexDecimal; const result = MultiArray.alongDimensionMap(DIM ? (MultiArray.firstElement(DIM) as ComplexDecimal).re.toNumber() - 1 : 1, M, (element, d, i, j) => { if (d === 0) { compare = element as ComplexDecimal; index = ComplexDecimal.one(); } else { if (ComplexDecimal[op === 'min' ? 'lt' : 'gt'](element as ComplexDecimal, compare).re.toNumber()) { index = new ComplexDecimal(d + 1); compare = element as ComplexDecimal; } } indexM.array[i][j] = index; return compare; }); return AST.nodeReturnList((length: number, index: number): ElementType => { Evaluator.throwErrorIfGreaterThanReturnList(2, length); return MultiArray.MultiArrayToScalar(index === 0 ? result : indexM); }); } /** * * @param M * @param DIM * @returns */ public static cummin(M: ElementType, DIM?: ElementType): AST.NodeReturnList { return CoreFunctions.cumMinMax('min', M, DIM); } /** * * @param M * @param DIM * @returns */ public static cummax(M: ElementType, DIM?: ElementType): AST.NodeReturnList { return CoreFunctions.cumMinMax('max', M, DIM); } /** * * @param op * @param M * @param DIM * @returns */ private static cumSumProd(op: 'add' | 'mul', M: ElementType, DIM?: ElementType): ElementType { M = MultiArray.scalarToMultiArray(M); const initialValue = op === 'add' ? ComplexDecimal.zero() : ComplexDecimal.one(); const result = MultiArray.alongDimensionMap( DIM ? (MultiArray.firstElement(DIM) as ComplexDecimal).re.toNumber() - 1 : MultiArray.firstNonSingleDimension(M), M, ( (cum) => (element, dimension) => (cum = dimension !== 0 ? ComplexDecimal[op](cum, element as ComplexDecimal) : (element as ComplexDecimal)) )(initialValue), ); MultiArray.setType(result); return result; } /** * * @param M * @param DIM * @returns */ public static cumsum(M: ElementType, DIM?: ElementType): ElementType { return CoreFunctions.cumSumProd('add', M, DIM); } /** * * @param M * @param DIM * @returns */ public static cumprod(M: ElementType, DIM?: ElementType): ElementType { return CoreFunctions.cumSumProd('mul', M, DIM); } /** * * @param args * @returns */ public static struct(...args: ElementType[]): ElementType { const errorMessage = `struct: additional arguments must occur as "field", VALUE pairs`; if (args.length === 0) { return new Structure({}); } else if (args.length === 1) { if (args[0] instanceof MultiArray && MultiArray.isEmpty(args[0])) { return MultiArray.emptyArray(); } else if (args[0] instanceof Structure) { return args[0].copy(); } else { throw new Error(errorMessage); } } else { if (args.length % 2 !== 0) { throw new Error(errorMessage); } const resultFields: Record<string, ElementType> = {}; for (let i = 0; i < args.length; i += 2) { if (args[i] instanceof CharString) { resultFields[(args[i] as CharString).str] = args[i + 1]; } else { throw new Error(errorMessage); } } return new Structure(resultFields); } } } export { CoreFunctions }; export default CoreFunctions;