diff --git a/dist/js/ArrayWithIds.d.ts b/dist/js/ArrayWithIds.d.ts new file mode 100644 index 00000000..06dd7a96 --- /dev/null +++ b/dist/js/ArrayWithIds.d.ts @@ -0,0 +1,27 @@ +import { RoundingOptions, ValueWithId } from "./ValueWithId"; +export declare class ArrayWithIds { + values: T[]; + ids: number[]; + constructor(values?: T[], ids?: number[]); + static fromValues>(this: new (values: U[], ids: number[]) => C, values: U[]): C; + static fromObjects>(this: new (values: U[], ids: number[]) => C, objects: { + id: number; + value: U; + }[]): C; + toJSON(): object[]; + toValueWithIdArray(): ValueWithId[]; + getElementValueByIndex(index: number): T | undefined; + getElementIdByValue(value: T): number | undefined; + filterByValues(valuesToKeep: T | T[]): void; + filterByIndices(indices: number | number[]): void; + filterByIds(ids: number | number[], invert?: boolean): void; + equals(other: ArrayWithIds): boolean; + mapArrayInPlace(func: (value: T) => T): void; + addItem(value: T, id?: number): void; + removeItem(index: number, id?: number): void; +} +export declare class RoundedArrayWithIds extends ArrayWithIds { + readonly roundingOptions: RoundingOptions; + constructor(values?: T[], ids?: number[], options?: RoundingOptions); + toJSON(): object[]; +} diff --git a/dist/js/ArrayWithIds.js b/dist/js/ArrayWithIds.js new file mode 100644 index 00000000..04631e22 --- /dev/null +++ b/dist/js/ArrayWithIds.js @@ -0,0 +1,112 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoundedArrayWithIds = exports.ArrayWithIds = void 0; +const ValueWithId_1 = require("./ValueWithId"); +class ArrayWithIds { + constructor(values = [], ids = []) { + if (values.length !== ids.length) { + throw new Error("Values and IDs must have the same length"); + } + this.values = [...values]; + this.ids = [...ids]; + } + static fromValues(values) { + const ids = values.map((_, i) => i); + return new this(values, ids); + } + static fromObjects(objects) { + const values = objects.map((obj) => obj.value); + const ids = objects.map((obj) => obj.id); + return new this(values, ids); + } + toJSON() { + return this.values.map((value, index) => ({ + id: this.ids[index], + value: value !== null && + typeof value === "object" && + "toJSON" in value && + typeof value.toJSON === "function" + ? value.toJSON() + : value, + })); + } + toValueWithIdArray() { + return this.values.map((value, index) => ValueWithId_1.ValueWithId.fromValueAndId(value, this.ids[index])); + } + getElementValueByIndex(index) { + return this.values[index]; + } + getElementIdByValue(value) { + const index = this.values.findIndex((v) => Array.isArray(v) && Array.isArray(value) + ? v.length === value.length && v.every((val, idx) => val === value[idx]) + : v === value); + return index !== -1 ? this.ids[index] : undefined; + } + filterByValues(valuesToKeep) { + const toHash = (v) => (Array.isArray(v) ? JSON.stringify(v) : String(v)); + const keepSet = new Set(Array.isArray(valuesToKeep) ? valuesToKeep.map(toHash) : [toHash(valuesToKeep)]); + const filtered = this.values + .map((value, i) => [value, this.ids[i]]) + .filter(([value]) => keepSet.has(toHash(value))); + this.values = filtered.map(([v]) => v); + this.ids = filtered.map(([_, id]) => id); + } + filterByIndices(indices) { + const keepSet = new Set(Array.isArray(indices) ? indices : [indices]); + this.values = this.values.filter((_, i) => keepSet.has(i)); + this.ids = this.ids.filter((_, i) => keepSet.has(i)); + } + filterByIds(ids, invert = false) { + const idSet = new Set(Array.isArray(ids) ? ids : [ids]); + const keep = invert + ? this.ids.map((id, i) => (idSet.has(id) ? -1 : i)).filter((i) => i >= 0) + : this.ids.map((id, i) => (idSet.has(id) ? i : -1)).filter((i) => i >= 0); + this.values = keep.map((i) => this.values[i]); + this.ids = keep.map((i) => this.ids[i]); + } + equals(other) { + if (!(other instanceof ArrayWithIds)) + return false; + if (this.values.length !== other.values.length) + return false; + if (this.ids.length !== other.ids.length) + return false; + return (this.values.every((v, i) => { + const ov = other.values[i]; + return Array.isArray(v) && Array.isArray(ov) + ? v.length === ov.length && v.every((val, idx) => val === ov[idx]) + : v === ov; + }) && this.ids.every((id, i) => id === other.ids[i])); + } + mapArrayInPlace(func) { + this.values = this.values.map(func); + } + addItem(value, id) { + const newId = id !== null && id !== void 0 ? id : Math.max(-1, ...this.ids) + 1; + this.values.push(value); + this.ids.push(newId); + } + removeItem(index, id) { + if (id !== undefined) { + index = this.ids.indexOf(id); + if (index === -1) + throw new Error("ID not found"); + } + if (index < 0 || index >= this.values.length) { + throw new Error("Index out of range"); + } + this.values.splice(index, 1); + this.ids.splice(index, 1); + } +} +exports.ArrayWithIds = ArrayWithIds; +class RoundedArrayWithIds extends ArrayWithIds { + constructor(values = [], ids = [], options = ValueWithId_1.defaultRoundingOptions) { + super(values, ids); + this.roundingOptions = options; + } + toJSON() { + return this.values.map((value, index) => new ValueWithId_1.RoundedValueWithId(this.ids[index], value, this.roundingOptions).toJSON()); + } +} +exports.RoundedArrayWithIds = RoundedArrayWithIds; diff --git a/dist/js/ValueWithId.d.ts b/dist/js/ValueWithId.d.ts new file mode 100644 index 00000000..2e3c67a2 --- /dev/null +++ b/dist/js/ValueWithId.d.ts @@ -0,0 +1,39 @@ +import { ObjectWithIdAndValueSchema } from "@mat3ra/esse/dist/js/types"; +import { RoundingMethodEnum } from "./math"; +interface ValueWithIdSchema { + id: ObjectWithIdAndValueSchema["id"]; + value: T | null; +} +export declare class ValueWithId { + id: number; + value: T | null; + static defaultConfig: { + id: number; + value: null; + }; + static fromValueAndId>(this: new (args: { + id: number; + value: U; + }) => C, value: U, id?: number): C; + constructor({ id, value }?: ValueWithIdSchema); + /** + * Converts the instance to a plain JavaScript object. + */ + toJSON(): object; + /** + * Checks if this instance is equal to another ValueWithId. + */ + equals(other: ValueWithId): boolean; +} +export interface RoundingOptions { + precision: number; + roundingMethod: RoundingMethodEnum; +} +export declare const defaultRoundingOptions: RoundingOptions; +export declare class RoundedValueWithId extends ValueWithId { + readonly precision: number; + readonly roundingMethod: RoundingMethodEnum; + constructor(id: number, value: T, options?: RoundingOptions); + toJSON(): object; +} +export {}; diff --git a/dist/js/ValueWithId.js b/dist/js/ValueWithId.js new file mode 100644 index 00000000..0f4d19ba --- /dev/null +++ b/dist/js/ValueWithId.js @@ -0,0 +1,68 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoundedValueWithId = exports.defaultRoundingOptions = exports.ValueWithId = void 0; +const math_1 = require("./math"); +class ValueWithId { + static fromValueAndId(value, id = 0) { + return new this({ id, value }); + } + constructor({ id, value } = ValueWithId.defaultConfig) { + this.id = id; + this.value = value; + } + /** + * Converts the instance to a plain JavaScript object. + */ + toJSON() { + if (this.value !== null && + typeof this.value === "object" && + "toJSON" in this.value && + typeof this.value.toJSON === "function") { + return { id: this.id, value: this.value.toJSON() }; + } + return { id: this.id, value: this.value }; + } + /** + * Checks if this instance is equal to another ValueWithId. + */ + equals(other) { + if (!(other instanceof ValueWithId)) + return false; + // because U may differ from T, we cast to unknown when comparing + const v1 = this.value; + const v2 = other.value; + if (Array.isArray(v1) && Array.isArray(v2)) { + if (v1.length !== v2.length) + return false; + for (let i = 0; i < v1.length; i++) { + if (v1[i] !== v2[i]) + return false; + } + return this.id === other.id; + } + return this.id === other.id && v1 === v2; + } +} +exports.ValueWithId = ValueWithId; +ValueWithId.defaultConfig = { + id: 0, + value: null, +}; +exports.defaultRoundingOptions = { + precision: 9, + roundingMethod: math_1.RoundingMethodEnum.HalfAwayFromZero, +}; +class RoundedValueWithId extends ValueWithId { + constructor(id, value, options = exports.defaultRoundingOptions) { + super({ id, value }); + this.precision = options.precision; + this.roundingMethod = options.roundingMethod; + } + toJSON() { + return { + id: this.id, + value: math_1.math.roundArrayOrNumber(this.value, this.precision, this.roundingMethod), + }; + } +} +exports.RoundedValueWithId = RoundedValueWithId; diff --git a/dist/js/constants.d.ts b/dist/js/constants.d.ts index 0878c4eb..a2041a4d 100644 --- a/dist/js/constants.d.ts +++ b/dist/js/constants.d.ts @@ -1,30 +1,51 @@ -export namespace coefficients { - let EV_TO_RY: number; - let BOHR_TO_ANGSTROM: number; - let ANGSTROM_TO_BOHR: number; - let EV_A_TO_RY_BOHR: number; -} -export namespace tolerance { - let length: number; - let lengthAngstrom: number; - let pointsDistance: number; -} -export namespace units { - let bohr: string; - let angstrom: string; - let degree: string; - let radian: string; - let alat: string; -} -export namespace ATOMIC_COORD_UNITS { - let crystal: string; - let cartesian: string; -} -export const HASH_TOLERANCE: 3; -declare namespace _default { - export { coefficients }; - export { tolerance }; - export { units }; - export { ATOMIC_COORD_UNITS }; -} +export declare const coefficients: { + EV_TO_RY: number; + BOHR_TO_ANGSTROM: number; + ANGSTROM_TO_BOHR: number; + EV_A_TO_RY_BOHR: number; +}; +export declare const tolerance: { + length: number; + lengthAngstrom: number; + pointsDistance: number; +}; +export declare const units: { + bohr: string; + angstrom: string; + degree: string; + radian: string; + alat: string; +}; +/** + * @summary Coordinates units for a material's basis. + */ +export declare const ATOMIC_COORD_UNITS: { + crystal: string; + cartesian: string; +}; +export declare const HASH_TOLERANCE = 3; +declare const _default: { + coefficients: { + EV_TO_RY: number; + BOHR_TO_ANGSTROM: number; + ANGSTROM_TO_BOHR: number; + EV_A_TO_RY_BOHR: number; + }; + tolerance: { + length: number; + lengthAngstrom: number; + pointsDistance: number; + }; + units: { + bohr: string; + angstrom: string; + degree: string; + radian: string; + alat: string; + }; + ATOMIC_COORD_UNITS: { + crystal: string; + cartesian: string; + }; +}; export default _default; diff --git a/dist/js/index.d.ts b/dist/js/index.d.ts index 26cab50e..d52bc1d8 100644 --- a/dist/js/index.d.ts +++ b/dist/js/index.d.ts @@ -1,8 +1,21 @@ +import { ArrayWithIds, RoundedArrayWithIds } from "./ArrayWithIds"; import * as context from "./context"; import * as entity from "./entity"; import * as utils from "./utils"; -export declare const Code: { +import { RoundedValueWithId, ValueWithId } from "./ValueWithId"; +import { RoundedVector3D, Vector3D } from "./vector"; +export { ArrayWithIds, ValueWithId, RoundedValueWithId, RoundedArrayWithIds, RoundedVector3D, Vector3D, }; +export { entity, context, utils }; +declare const Code: { + ArrayWithIds: typeof ArrayWithIds; + ValueWithId: typeof ValueWithId; + RoundedArrayWithIds: typeof RoundedArrayWithIds; + RoundedValueWithId: typeof RoundedValueWithId; + RoundedVector3D: typeof RoundedVector3D; + Vector3D: typeof Vector3D; entity: typeof entity; context: typeof context; utils: typeof utils; }; +export type CodeType = typeof Code; +export default Code; diff --git a/dist/js/index.js b/dist/js/index.js index 4fbad54c..846b45f8 100644 --- a/dist/js/index.js +++ b/dist/js/index.js @@ -33,12 +33,31 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -exports.Code = void 0; +exports.utils = exports.context = exports.entity = exports.Vector3D = exports.RoundedVector3D = exports.RoundedArrayWithIds = exports.RoundedValueWithId = exports.ValueWithId = exports.ArrayWithIds = void 0; +const ArrayWithIds_1 = require("./ArrayWithIds"); +Object.defineProperty(exports, "ArrayWithIds", { enumerable: true, get: function () { return ArrayWithIds_1.ArrayWithIds; } }); +Object.defineProperty(exports, "RoundedArrayWithIds", { enumerable: true, get: function () { return ArrayWithIds_1.RoundedArrayWithIds; } }); const context = __importStar(require("./context")); +exports.context = context; const entity = __importStar(require("./entity")); +exports.entity = entity; const utils = __importStar(require("./utils")); -exports.Code = { +exports.utils = utils; +const ValueWithId_1 = require("./ValueWithId"); +Object.defineProperty(exports, "RoundedValueWithId", { enumerable: true, get: function () { return ValueWithId_1.RoundedValueWithId; } }); +Object.defineProperty(exports, "ValueWithId", { enumerable: true, get: function () { return ValueWithId_1.ValueWithId; } }); +const vector_1 = require("./vector"); +Object.defineProperty(exports, "RoundedVector3D", { enumerable: true, get: function () { return vector_1.RoundedVector3D; } }); +Object.defineProperty(exports, "Vector3D", { enumerable: true, get: function () { return vector_1.Vector3D; } }); +const Code = { + ArrayWithIds: ArrayWithIds_1.ArrayWithIds, + ValueWithId: ValueWithId_1.ValueWithId, + RoundedArrayWithIds: ArrayWithIds_1.RoundedArrayWithIds, + RoundedValueWithId: ValueWithId_1.RoundedValueWithId, + RoundedVector3D: vector_1.RoundedVector3D, + Vector3D: vector_1.Vector3D, entity, context, utils, }; +exports.default = Code; diff --git a/dist/js/math.d.ts b/dist/js/math.d.ts index 4dc8643e..6abf0ed7 100644 --- a/dist/js/math.d.ts +++ b/dist/js/math.d.ts @@ -31,6 +31,7 @@ export declare enum RoundingMethodEnum { HalfAwayFromZero = "halfAwayFromZero" } export declare const roundCustom: (value: number, decimals?: number, method?: RoundingMethodEnum) => number; +export declare const roundArrayOrNumber: (value: unknown, decimals?: number, method?: RoundingMethodEnum) => unknown; /** * @summary Wrapper for native [Number.toPrecision](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Number/toPrecision) method. * Returns a string representing the Number object to the specified precision. @@ -52,8 +53,6 @@ export declare const math: { angleUpTo90: (a: number[], b: number[], unit: string) => number; vDist: (v1: number[], v2: number[]) => number | undefined; vEqualWithTolerance: (vec1: number[], vec2: number[], tolerance?: number) => boolean; - roundToZero: (n: number) => number; - precise: (x: number, n?: number) => number; mod: (num: number, tolerance?: number) => number; isBetweenZeroInclusiveAndOne: (number: number, tolerance?: number) => boolean; cartesianProduct: (...arg: number[][]) => number[][]; @@ -61,10 +60,13 @@ export declare const math: { combinations: (a: number, b: number, c: number) => number[][]; combinationsFromIntervals: (arrA: number[], arrB: number[], arrC: number[]) => number[][]; calculateSegmentsBetweenPoints3D: (point1: (string | number)[], point2: (string | number)[], n: number | string) => number[][]; + roundToZero: (n: number) => number; + precise: (x: number, n?: number) => number; roundValueToNDecimals: (value: number, decimals?: number) => number; numberToPrecision: typeof numberToPrecision; roundCustom: (value: number, decimals?: number, method?: RoundingMethodEnum) => number; RoundingMethod: typeof RoundingMethodEnum; + roundArrayOrNumber: (value: unknown, decimals?: number, method?: RoundingMethodEnum) => unknown; e: number; pi: number; i: number; @@ -83,11 +85,60 @@ export declare const math: { version: string; expression: mathjs.MathNode; json: mathjs.MathJsJson; - config: (options: any) => void; + config: (options: mathjs.ConfigOptions) => mathjs.ConfigOptions; + typed: (name: string, signatures: Record any>) => ((...args: any[]) => any); + bignumber(x?: number | string | mathjs.Fraction | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix | boolean | mathjs.Fraction | null): mathjs.BigNumber; + boolean(x: string | number | boolean | mathjs.MathArray | mathjs.Matrix | null): boolean | mathjs.MathArray | mathjs.Matrix; + chain(value?: any): mathjs.MathJsChain; + complex(arg?: mathjs.Complex | string | mathjs.PolarCoordinates): mathjs.Complex; + complex(arg?: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + complex(re: number, im: number): mathjs.Complex; + createUnit(name: string, definition?: string | mathjs.UnitDefinition, options?: mathjs.CreateUnitOptions): mathjs.Unit; + createUnit(units: Record, options?: mathjs.CreateUnitOptions): mathjs.Unit; + fraction(args: mathjs.Fraction | mathjs.MathArray | mathjs.Matrix): mathjs.Fraction | mathjs.MathArray | mathjs.Matrix; + fraction(numerator: number | string | mathjs.MathArray | mathjs.Matrix, denominator?: number | string | mathjs.MathArray | mathjs.Matrix): mathjs.Fraction | mathjs.MathArray | mathjs.Matrix; + index(...ranges: any[]): mathjs.Index; + matrix(format?: "sparse" | "dense"): mathjs.Matrix; + matrix(data: mathjs.MathArray | mathjs.Matrix, format?: "sparse" | "dense", dataType?: string): mathjs.Matrix; + number(value?: string | number | mathjs.BigNumber | mathjs.Fraction | boolean | mathjs.MathArray | mathjs.Matrix | mathjs.Unit | null): number | mathjs.MathArray | mathjs.Matrix; + number(unit: mathjs.Unit, valuelessUnit: mathjs.Unit | string): number; + sparse(data?: mathjs.MathArray | mathjs.Matrix, dataType?: string): mathjs.Matrix; + splitUnit(unit: mathjs.Unit, parts: mathjs.Unit[]): mathjs.Unit[]; + string(value: mathjs.MathType | null): string | mathjs.MathArray | mathjs.Matrix; + unit(unit: string): mathjs.Unit; + unit(value: number | mathjs.MathArray | mathjs.Matrix, unit: string): mathjs.Unit; + compile(expr: mathjs.MathExpression): mathjs.EvalFunction; + compile(exprs: mathjs.MathExpression[]): mathjs.EvalFunction[]; + eval(expr: mathjs.MathExpression | mathjs.MathExpression[] | mathjs.Matrix, scope?: object): any; + help(search: () => any): mathjs.Help; + parse(expr: mathjs.MathExpression, options?: any): mathjs.MathNode; + parse(exprs: mathjs.MathExpression[], options?: any): mathjs.MathNode[]; + parser(): mathjs.Parser; + derivative(expr: mathjs.MathNode | string, variable: mathjs.MathNode | string, options?: { + simplify: boolean; + }): mathjs.MathNode; lsolve(L: mathjs.Matrix | mathjs.MathArray, b: mathjs.Matrix | mathjs.MathArray): mathjs.Matrix | mathjs.MathArray; - lup(A?: mathjs.Matrix | mathjs.MathArray): mathjs.MathArray; - lusolve(A: mathjs.Matrix | mathjs.MathArray | number, b: mathjs.Matrix | mathjs.MathArray): mathjs.Matrix | mathjs.MathArray; - slu(A: mathjs.Matrix, order: number, threshold: number): any; + lup(A?: mathjs.Matrix | mathjs.MathArray): { + L: mathjs.MathArray | mathjs.Matrix; + U: mathjs.MathArray | mathjs.Matrix; + P: number[]; + }; + lusolve(A: mathjs.Matrix | mathjs.MathArray | number, b: mathjs.Matrix | mathjs.MathArray, order?: number, threshold?: number): mathjs.Matrix | mathjs.MathArray; + qr(A: mathjs.Matrix | mathjs.MathArray): { + Q: mathjs.MathArray | mathjs.Matrix; + R: mathjs.MathArray | mathjs.Matrix; + }; + rationalize(expr: mathjs.MathNode | string, optional?: object | boolean, detailed?: true): { + expression: mathjs.MathNode | string; + variables: string[]; + coefficients: mathjs.MathType[]; + }; + rationalize(expr: mathjs.MathNode | string, optional?: object | boolean, detailed?: false): mathjs.MathNode; + simplify(expr: mathjs.MathNode | string, rules?: Array<({ + l: string; + r: string; + } | string | ((node: mathjs.MathNode) => mathjs.MathNode))>, scope?: object): mathjs.MathNode; + slu(A: mathjs.Matrix, order: number, threshold: number): object; usolve(U: mathjs.Matrix | mathjs.MathArray, b: mathjs.Matrix | mathjs.MathArray): mathjs.Matrix | mathjs.MathArray; abs(x: number): number; abs(x: mathjs.BigNumber): mathjs.BigNumber; @@ -129,6 +180,11 @@ export declare const math: { exp(x: mathjs.Complex): mathjs.Complex; exp(x: mathjs.MathArray): mathjs.MathArray; exp(x: mathjs.Matrix): mathjs.Matrix; + expm1(x: number): number; + expm1(x: mathjs.BigNumber): mathjs.BigNumber; + expm1(x: mathjs.Complex): mathjs.Complex; + expm1(x: mathjs.MathArray): mathjs.MathArray; + expm1(x: mathjs.Matrix): mathjs.Matrix; fix(x: number): number; fix(x: mathjs.BigNumber): mathjs.BigNumber; fix(x: mathjs.Fraction): mathjs.Fraction; @@ -148,7 +204,6 @@ export declare const math: { gcd(...args: mathjs.Matrix[]): mathjs.Matrix; hypot(...args: number[]): number; hypot(...args: mathjs.BigNumber[]): mathjs.BigNumber; - kron(x: mathjs.Matrix | mathjs.MathArray, y: mathjs.Matrix | mathjs.MathArray): mathjs.Matrix; lcm(a: number, b: number): number; lcm(a: mathjs.BigNumber, b: mathjs.BigNumber): mathjs.BigNumber; lcm(a: mathjs.MathArray, b: mathjs.MathArray): mathjs.MathArray; @@ -159,7 +214,17 @@ export declare const math: { log10(x: mathjs.Complex): mathjs.Complex; log10(x: mathjs.MathArray): mathjs.MathArray; log10(x: mathjs.Matrix): mathjs.Matrix; - multiply(x: mathjs.MathArray | mathjs.Matrix, y: mathjs.MathType): mathjs.Matrix; + log1p(x: number, base?: number | mathjs.BigNumber | mathjs.Complex): number; + log1p(x: mathjs.BigNumber, base?: number | mathjs.BigNumber | mathjs.Complex): mathjs.BigNumber; + log1p(x: mathjs.Complex, base?: number | mathjs.BigNumber | mathjs.Complex): mathjs.Complex; + log1p(x: mathjs.MathArray, base?: number | mathjs.BigNumber | mathjs.Complex): mathjs.MathArray; + log1p(x: mathjs.Matrix, base?: number | mathjs.BigNumber | mathjs.Complex): mathjs.Matrix; + log2(x: number): number; + log2(x: mathjs.BigNumber): mathjs.BigNumber; + log2(x: mathjs.Complex): mathjs.Complex; + log2(x: mathjs.MathArray): mathjs.MathArray; + log2(x: mathjs.Matrix): mathjs.Matrix; + multiply(x: mathjs.Matrix | mathjs.MathArray, y: mathjs.MathType): mathjs.Matrix | mathjs.MathArray; multiply(x: mathjs.Unit, y: mathjs.Unit): mathjs.Unit; multiply(x: number, y: number): number; multiply(x: mathjs.MathType, y: mathjs.MathType): mathjs.MathType; @@ -209,10 +274,10 @@ export declare const math: { bitNot(x: mathjs.BigNumber): mathjs.BigNumber; bitNot(x: mathjs.MathArray): mathjs.MathArray; bitNot(x: mathjs.Matrix): mathjs.Matrix; - bitOr(x: number): number; - bitOr(x: mathjs.BigNumber): mathjs.BigNumber; - bitOr(x: mathjs.MathArray): mathjs.MathArray; - bitOr(x: mathjs.Matrix): mathjs.Matrix; + bitOr(x: number, y: number): number; + bitOr(x: mathjs.BigNumber, y: mathjs.BigNumber): mathjs.BigNumber; + bitOr(x: mathjs.MathArray, y: mathjs.MathArray): mathjs.MathArray; + bitOr(x: mathjs.Matrix, y: mathjs.Matrix): mathjs.Matrix; bitXor(x: number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix, y: number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix): number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix; leftShift(x: number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix, y: number | mathjs.BigNumber): number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix; rightArithShift(x: number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix, y: number | mathjs.BigNumber): number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix; @@ -224,83 +289,83 @@ export declare const math: { composition(n: number | mathjs.BigNumber, k: number | mathjs.BigNumber): number | mathjs.BigNumber; stirlingS2(n: number | mathjs.BigNumber, k: number | mathjs.BigNumber): number | mathjs.BigNumber; arg(x: number | mathjs.Complex): number; + arg(x: mathjs.BigNumber | mathjs.Complex): mathjs.BigNumber; arg(x: mathjs.MathArray): mathjs.MathArray; arg(x: mathjs.Matrix): mathjs.Matrix; conj(x: number | mathjs.BigNumber | mathjs.Complex | mathjs.MathArray | mathjs.Matrix): number | mathjs.BigNumber | mathjs.Complex | mathjs.MathArray | mathjs.Matrix; im(x: number | mathjs.BigNumber | mathjs.Complex | mathjs.MathArray | mathjs.Matrix): number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix; re(x: number | mathjs.BigNumber | mathjs.Complex | mathjs.MathArray | mathjs.Matrix): number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix; - bignumber(x?: number | string | mathjs.MathArray | mathjs.Matrix | boolean): mathjs.BigNumber; - boolean(x: string | number | boolean | mathjs.MathArray | mathjs.Matrix): boolean | mathjs.MathArray | mathjs.Matrix; - chain(value?: any): mathjs.MathJsChain; - complex(arg?: mathjs.Complex | string | mathjs.MathArray | mathjs.PolarCoordinates): mathjs.Complex; - complex(re: number, im: number): mathjs.Complex; - fraction(numerator: number | string | mathjs.MathArray | mathjs.Matrix, denominator?: number | string | mathjs.MathArray | mathjs.Matrix): mathjs.Fraction | mathjs.MathArray | mathjs.Matrix; - index(...ranges: any[]): mathjs.Index; - matrix(format?: "sparse" | "dense"): mathjs.Matrix; - matrix(data: mathjs.MathArray | mathjs.Matrix, format?: "sparse" | "dense", dataType?: string): mathjs.Matrix; - number(value?: string | number | boolean | mathjs.MathArray | mathjs.Matrix | mathjs.Unit | mathjs.BigNumber | mathjs.Fraction): number | mathjs.MathArray | mathjs.Matrix; - number(unit: mathjs.Unit, valuelessUnit: mathjs.Unit | string): number | mathjs.MathArray | mathjs.Matrix; - sparse(data?: mathjs.MathArray | mathjs.Matrix, dataType?: string): mathjs.Matrix; - string(value: any): string | mathjs.MathArray | mathjs.Matrix; - unit(unit: string): mathjs.Unit; - unit(value: number, unit: string): mathjs.Unit; - createUnit(name: string, definition?: string | mathjs.UnitDefinition, options?: mathjs.CreateUnitOptions): mathjs.Unit; - createUnit(units: Record, options?: mathjs.CreateUnitOptions): mathjs.Unit; - compile(expr: mathjs.MathExpression): mathjs.EvalFunction; - compile(exprs: mathjs.MathExpression[]): mathjs.EvalFunction[]; - eval(expr: mathjs.MathExpression | mathjs.MathExpression[], scope?: any): any; - help(search: any): mathjs.Help; - parse(expr: mathjs.MathExpression, options?: any): mathjs.MathNode; - parse(exprs: mathjs.MathExpression[], options?: any): mathjs.MathNode[]; - parser(): mathjs.Parser; - distance(x: mathjs.MathType, y: mathjs.MathType): number | mathjs.BigNumber; + distance(x: mathjs.MathArray | mathjs.Matrix | object, y: mathjs.MathArray | mathjs.Matrix | object): number | mathjs.BigNumber; intersect(w: mathjs.MathArray | mathjs.Matrix, x: mathjs.MathArray | mathjs.Matrix, y: mathjs.MathArray | mathjs.Matrix, z: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray; and(x: number | mathjs.BigNumber | mathjs.Complex | mathjs.Unit | mathjs.MathArray | mathjs.Matrix, y: number | mathjs.BigNumber | mathjs.Complex | mathjs.Unit | mathjs.MathArray | mathjs.Matrix): boolean | mathjs.MathArray | mathjs.Matrix; not(x: number | mathjs.BigNumber | mathjs.Complex | mathjs.Unit | mathjs.MathArray | mathjs.Matrix): boolean | mathjs.MathArray | mathjs.Matrix; or(x: number | mathjs.BigNumber | mathjs.Complex | mathjs.Unit | mathjs.MathArray | mathjs.Matrix, y: number | mathjs.BigNumber | mathjs.Complex | mathjs.Unit | mathjs.MathArray | mathjs.Matrix): boolean | mathjs.MathArray | mathjs.Matrix; xor(x: number | mathjs.BigNumber | mathjs.Complex | mathjs.Unit | mathjs.MathArray | mathjs.Matrix, y: number | mathjs.BigNumber | mathjs.Complex | mathjs.Unit | mathjs.MathArray | mathjs.Matrix): boolean | mathjs.MathArray | mathjs.Matrix; - concat(...args: Array): mathjs.MathArray | mathjs.Matrix; - cross(x: mathjs.MathArray | mathjs.Matrix, y: mathjs.MathArray | mathjs.Matrix): mathjs.Matrix; + concat(...args: Array): mathjs.MathArray | mathjs.Matrix; + cross(x: mathjs.MathArray | mathjs.Matrix, y: mathjs.MathArray | mathjs.Matrix): mathjs.Matrix | mathjs.MathArray; det(x: mathjs.MathArray | mathjs.Matrix): number; diag(X: mathjs.MathArray | mathjs.Matrix, format?: string): mathjs.Matrix; - diag(X: mathjs.MathArray | mathjs.Matrix, k: number | mathjs.BigNumber, format?: string): mathjs.Matrix; + diag(X: mathjs.MathArray | mathjs.Matrix, k: number | mathjs.BigNumber, format?: string): mathjs.Matrix | mathjs.MathArray; dot(x: mathjs.MathArray | mathjs.Matrix, y: mathjs.MathArray | mathjs.Matrix): number; - eye(n: number | number[], format?: string): mathjs.Matrix; - eye(m: number, n: number, format?: string): mathjs.Matrix; + expm(x: mathjs.Matrix): mathjs.Matrix; + identity(size: number | number[] | mathjs.Matrix | mathjs.MathArray, format?: string): mathjs.Matrix | mathjs.MathArray | number; + identity(m: number, n: number, format?: string): mathjs.Matrix | mathjs.MathArray | number; + filter(x: mathjs.Matrix | mathjs.MathArray | string[], test: ((value: any, index: any, matrix: mathjs.Matrix | mathjs.MathArray | string[]) => boolean) | RegExp): mathjs.Matrix | mathjs.MathArray; flatten(x: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + forEach(x: mathjs.Matrix | mathjs.MathArray, callback: ((value: any, index: any, matrix: mathjs.Matrix | mathjs.MathArray) => void)): void; inv(x: number | mathjs.Complex | mathjs.MathArray | mathjs.Matrix): number | mathjs.Complex | mathjs.MathArray | mathjs.Matrix; - ones(n: number | number[], format?: string): mathjs.MathArray | mathjs.Matrix; + kron(x: mathjs.Matrix | mathjs.MathArray, y: mathjs.Matrix | mathjs.MathArray): mathjs.Matrix; + map(x: mathjs.Matrix | mathjs.MathArray, callback: ((value: any, index: any, matrix: mathjs.Matrix | mathjs.MathArray) => mathjs.MathType | string)): mathjs.Matrix | mathjs.MathArray; + ones(size: number | number[], format?: string): mathjs.MathArray | mathjs.Matrix; ones(m: number, n: number, format?: string): mathjs.MathArray | mathjs.Matrix; + partitionSelect(x: mathjs.MathArray | mathjs.Matrix, k: number, compare?: "asc" | "desc" | ((a: any, b: any) => number)): any; range(str: string, includeEnd?: boolean): mathjs.Matrix; range(start: number | mathjs.BigNumber, end: number | mathjs.BigNumber, includeEnd?: boolean): mathjs.Matrix; range(start: number | mathjs.BigNumber, end: number | mathjs.BigNumber, step: number | mathjs.BigNumber, includeEnd?: boolean): mathjs.Matrix; + reshape(x: mathjs.MathArray | mathjs.Matrix, sizes: number[]): mathjs.MathArray | mathjs.Matrix; resize(x: mathjs.MathArray | mathjs.Matrix, size: mathjs.MathArray | mathjs.Matrix, defaultValue?: number | string): mathjs.MathArray | mathjs.Matrix; size(x: boolean | number | mathjs.Complex | mathjs.Unit | string | mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + sort(x: mathjs.Matrix | mathjs.MathArray, compare: ((a: any, b: any) => number) | "asc" | "desc" | "natural"): mathjs.Matrix | mathjs.MathArray; + sqrtm(A: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; squeeze(x: mathjs.MathArray | mathjs.Matrix): mathjs.Matrix | mathjs.MathArray; subset(value: mathjs.MathArray | mathjs.Matrix | string, index: mathjs.Index, replacement?: any, defaultValue?: any): mathjs.MathArray | mathjs.Matrix | string; trace(x: mathjs.MathArray | mathjs.Matrix): number; transpose(x: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; - zeros(n: number | number[], format?: string): mathjs.MathArray | mathjs.Matrix; + zeros(size: number | number[], format?: string): mathjs.MathArray | mathjs.Matrix; zeros(m: number, n: number, format?: string): mathjs.MathArray | mathjs.Matrix; - distribution(name: string): mathjs.Distribution; factorial(n: number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix): number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix; gamma(n: number | mathjs.MathArray | mathjs.Matrix): number | mathjs.MathArray | mathjs.Matrix; - kldivergence(x: mathjs.MathArray | mathjs.Matrix, y: mathjs.MathArray | mathjs.Matrix): number; + kldivergence(q: mathjs.MathArray | mathjs.Matrix, p: mathjs.MathArray | mathjs.Matrix): number; multinomial(a: number[] | mathjs.BigNumber[]): number | mathjs.BigNumber; permutations(n: number | mathjs.BigNumber, k?: number | mathjs.BigNumber): number | mathjs.BigNumber; - pickRandom(array: number[]): number; + pickRandom(array: number[], number?: number, weights?: number[]): number; random(min?: number, max?: number): number; random(size: mathjs.MathArray | mathjs.Matrix, min?: number, max?: number): mathjs.MathArray | mathjs.Matrix; randomInt(min: number, max?: number): number; randomInt(size: mathjs.MathArray | mathjs.Matrix, min?: number, max?: number): mathjs.MathArray | mathjs.Matrix; - compare(x: mathjs.MathType, y: mathjs.MathType): number | mathjs.BigNumber | mathjs.Fraction | mathjs.MathArray | mathjs.Matrix; + compare(x: mathjs.MathType | string, y: mathjs.MathType | string): number | mathjs.BigNumber | mathjs.Fraction | mathjs.MathArray | mathjs.Matrix; + compareNatural(x: any, y: any): number; + compareText(x: string | mathjs.MathArray | mathjs.Matrix, y: string | mathjs.MathArray | mathjs.Matrix): number | mathjs.MathArray | mathjs.Matrix; deepEqual(x: mathjs.MathType, y: mathjs.MathType): number | mathjs.BigNumber | mathjs.Fraction | mathjs.Complex | mathjs.Unit | mathjs.MathArray | mathjs.Matrix; - equal(x: mathjs.MathType, y: mathjs.MathType): boolean | mathjs.MathArray | mathjs.Matrix; - larger(x: mathjs.MathType, y: mathjs.MathType): boolean | mathjs.MathArray | mathjs.Matrix; - largerEq(x: mathjs.MathType, y: mathjs.MathType): boolean | mathjs.MathArray | mathjs.Matrix; - smaller(x: mathjs.MathType, y: mathjs.MathType): boolean | mathjs.MathArray | mathjs.Matrix; - smallerEq(x: mathjs.MathType, y: mathjs.MathType): boolean | mathjs.MathArray | mathjs.Matrix; - unequal(x: mathjs.MathType, y: mathjs.MathType): boolean | mathjs.MathArray | mathjs.Matrix; + equal(x: mathjs.MathType | string, y: mathjs.MathType | string): boolean | mathjs.MathArray | mathjs.Matrix; + equalText(x: string | mathjs.MathArray | mathjs.Matrix, y: string | mathjs.MathArray | mathjs.Matrix): number | mathjs.MathArray | mathjs.Matrix; + larger(x: mathjs.MathType | string, y: mathjs.MathType | string): boolean | mathjs.MathArray | mathjs.Matrix; + largerEq(x: mathjs.MathType | string, y: mathjs.MathType | string): boolean | mathjs.MathArray | mathjs.Matrix; + smaller(x: mathjs.MathType | string, y: mathjs.MathType | string): boolean | mathjs.MathArray | mathjs.Matrix; + smallerEq(x: mathjs.MathType | string, y: mathjs.MathType | string): boolean | mathjs.MathArray | mathjs.Matrix; + unequal(x: mathjs.MathType | string, y: mathjs.MathType | string): boolean | mathjs.MathArray | mathjs.Matrix; + setCartesian(a1: mathjs.MathArray | mathjs.Matrix, a2: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + setDifference(a1: mathjs.MathArray | mathjs.Matrix, a2: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + setDistinct(a: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + setIntersect(a1: mathjs.MathArray | mathjs.Matrix, a2: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + setIsSubset(a1: mathjs.MathArray | mathjs.Matrix, a2: mathjs.MathArray | mathjs.Matrix): boolean; + setMultiplicity(e: number | mathjs.BigNumber | mathjs.Fraction | mathjs.Complex, a: mathjs.MathArray | mathjs.Matrix): number; + setPowerset(a: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + setSize(a: mathjs.MathArray | mathjs.Matrix): number; + setSymDifference(a1: mathjs.MathArray | mathjs.Matrix, a2: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + setUnion(a1: mathjs.MathArray | mathjs.Matrix, a2: mathjs.MathArray | mathjs.Matrix): mathjs.MathArray | mathjs.Matrix; + erf(x: number | mathjs.MathArray | mathjs.Matrix): number | mathjs.MathArray | mathjs.Matrix; + mad(array: mathjs.MathArray | mathjs.Matrix): any; max(...args: mathjs.MathType[]): any; max(A: mathjs.MathArray | mathjs.Matrix, dim?: number): any; mean(...args: mathjs.MathType[]): any; @@ -311,11 +376,13 @@ export declare const math: { mode(...args: mathjs.MathType[]): any; prod(...args: mathjs.MathType[]): any; quantileSeq(A: mathjs.MathArray | mathjs.Matrix, prob: number | mathjs.BigNumber | mathjs.MathArray, sorted?: boolean): number | mathjs.BigNumber | mathjs.Unit | mathjs.MathArray; - std(array: mathjs.MathArray | mathjs.Matrix, normalization?: string): number; + std(array: mathjs.MathArray | mathjs.Matrix, normalization?: "unbiased" | "uncorrected" | "biased" | "unbiased"): number; sum(...args: Array): any; sum(array: mathjs.MathArray | mathjs.Matrix): any; var(...args: Array): any; - var(array: mathjs.MathArray | mathjs.Matrix, normalization?: string): any; + var(array: mathjs.MathArray | mathjs.Matrix, normalization?: "unbiased" | "uncorrected" | "biased" | "unbiased"): any; + format(value: any, options?: mathjs.FormatOptions | number | ((item: any) => string), callback?: ((value: any) => string)): string; + print(template: string, values: any, precision?: number, options?: number | object): void; acos(x: number): number; acos(x: mathjs.BigNumber): mathjs.BigNumber; acos(x: mathjs.Complex): mathjs.Complex; @@ -369,6 +436,11 @@ export declare const math: { atanh(x: mathjs.BigNumber): mathjs.BigNumber; atanh(x: mathjs.MathArray): mathjs.MathArray; atanh(x: mathjs.Matrix): mathjs.Matrix; + cos(x: number | mathjs.Unit): number; + cos(x: mathjs.BigNumber): mathjs.BigNumber; + cos(x: mathjs.Complex): mathjs.Complex; + cos(x: mathjs.MathArray): mathjs.MathArray; + cos(x: mathjs.Matrix): mathjs.Matrix; cosh(x: number | mathjs.Unit): number; cosh(x: mathjs.BigNumber): mathjs.BigNumber; cosh(x: mathjs.Complex): mathjs.Complex; @@ -403,11 +475,6 @@ export declare const math: { sin(x: mathjs.Complex): mathjs.Complex; sin(x: mathjs.MathArray): mathjs.MathArray; sin(x: mathjs.Matrix): mathjs.Matrix; - cos(x: number | mathjs.Unit): number; - cos(x: mathjs.BigNumber): mathjs.BigNumber; - cos(x: mathjs.Complex): mathjs.Complex; - cos(x: mathjs.MathArray): mathjs.MathArray; - cos(x: mathjs.Matrix): mathjs.Matrix; sinh(x: number | mathjs.Unit): number; sinh(x: mathjs.BigNumber): mathjs.BigNumber; sinh(x: mathjs.Complex): mathjs.Complex; @@ -425,17 +492,13 @@ export declare const math: { tanh(x: mathjs.Matrix): mathjs.Matrix; to(x: mathjs.Unit | mathjs.MathArray | mathjs.Matrix, unit: mathjs.Unit | string): mathjs.Unit | mathjs.MathArray | mathjs.Matrix; clone(x: any): any; - filter(x: mathjs.MathArray | mathjs.Matrix, test: RegExp | ((item: any) => boolean)): mathjs.MathArray | mathjs.Matrix; - forEach: (x: mathjs.MathArray | mathjs.Matrix, callback: (item: any) => any) => void; - format(value: any, options?: mathjs.FormatOptions | number | ((item: any) => string)): string; - isInteger(x: any): boolean; - isNegative(x: any): boolean; - isNumeric(x: any): boolean; - isPositive(x: any): boolean; - isZero(x: any): boolean; - map(x: mathjs.MathArray | mathjs.Matrix, callback: (item: any) => any): mathjs.MathArray | mathjs.Matrix; - partitionSelect(x: mathjs.MathArray | mathjs.Matrix, k: number, compare?: string | ((a: any, b: any) => number)): any; - print: (template: string, values: any, precision?: number) => void; - sort(x: mathjs.MathArray | mathjs.Matrix, compare?: string | ((a: any, b: any) => number)): mathjs.MathArray | mathjs.Matrix; + isInteger(x: number | mathjs.BigNumber | mathjs.Fraction | mathjs.MathArray | mathjs.Matrix): boolean; + isNaN(x: number | mathjs.BigNumber | mathjs.Fraction | mathjs.MathArray | mathjs.Matrix | mathjs.Unit): boolean; + isNegative(x: number | mathjs.BigNumber | mathjs.Fraction | mathjs.MathArray | mathjs.Matrix | mathjs.Unit): boolean; + isNumeric(x: any): x is number | mathjs.BigNumber | mathjs.Fraction | boolean; + isPositive(x: number | mathjs.BigNumber | mathjs.Fraction | mathjs.MathArray | mathjs.Matrix | mathjs.Unit): boolean; + isPrime(x: number | mathjs.BigNumber | mathjs.MathArray | mathjs.Matrix): boolean; + isZero(x: number | mathjs.BigNumber | mathjs.Fraction | mathjs.MathArray | mathjs.Matrix | mathjs.Unit | mathjs.Complex): boolean; typeof(x: any): string; + import(object: mathjs.ImportObject | mathjs.ImportObject[], options: mathjs.ImportOptions): void; }; diff --git a/dist/js/math.js b/dist/js/math.js index 73ae1008..e92525c5 100644 --- a/dist/js/math.js +++ b/dist/js/math.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.math = exports.roundCustom = exports.RoundingMethodEnum = void 0; +exports.math = exports.roundArrayOrNumber = exports.roundCustom = exports.RoundingMethodEnum = void 0; exports.numberToPrecision = numberToPrecision; /* eslint-disable */ const mathjs_1 = __importDefault(require("mathjs")); @@ -206,6 +206,13 @@ const roundCustom = (value, decimals = 0, method = RoundingMethodEnum.HalfAwayFr return (roundedAbs * sign) / factor; }; exports.roundCustom = roundCustom; +const roundArrayOrNumber = (value, decimals = 9, method = RoundingMethodEnum.HalfAwayFromZero) => { + if (Array.isArray(value)) { + return value.map((v) => (typeof v === "number" ? (0, exports.roundCustom)(v, decimals, method) : v)); + } + return typeof value === "number" ? (0, exports.roundCustom)(value, decimals, method) : value; +}; +exports.roundArrayOrNumber = roundArrayOrNumber; /** * @summary Returns n splits of the passed segment. */ @@ -252,8 +259,6 @@ exports.math = { angleUpTo90, vDist, vEqualWithTolerance, - roundToZero, - precise, mod, isBetweenZeroInclusiveAndOne, cartesianProduct, @@ -261,8 +266,11 @@ exports.math = { combinations, combinationsFromIntervals, calculateSegmentsBetweenPoints3D, + roundToZero, + precise, roundValueToNDecimals, numberToPrecision, roundCustom: exports.roundCustom, RoundingMethod: RoundingMethodEnum, + roundArrayOrNumber: exports.roundArrayOrNumber, }; diff --git a/dist/js/vector.d.ts b/dist/js/vector.d.ts new file mode 100644 index 00000000..f86cefa4 --- /dev/null +++ b/dist/js/vector.d.ts @@ -0,0 +1,22 @@ +import { Vector3DSchema } from "@mat3ra/esse/dist/js/types"; +export declare class Vector3D { + static atol: number; + private _value; + constructor(value: number[] | Vector3DSchema); + get value(): Vector3DSchema; + get x(): number; + get y(): number; + get z(): number; + equals(other: number[] | Vector3DSchema | Vector3D): boolean; + get norm(): number; +} +export declare class RoundedVector3D extends Vector3D { + static roundPrecision: number; + toJSON(skipRounding?: boolean): Vector3DSchema; + get value_rounded(): Vector3DSchema; + get x_rounded(): number; + get y_rounded(): number; + get z_rounded(): number; + equals(other: Vector3DSchema | RoundedVector3D): boolean; + get norm_rounded(): number; +} diff --git a/dist/js/vector.js b/dist/js/vector.js new file mode 100644 index 00000000..82d2ad41 --- /dev/null +++ b/dist/js/vector.js @@ -0,0 +1,75 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RoundedVector3D = exports.Vector3D = void 0; +const math_1 = require("./math"); +class Vector3D { + constructor(value) { + if (!Array.isArray(value) || + value.length !== 3 || + !value.every((v) => typeof v === "number")) { + throw new Error("Vector3D must be a tuple of exactly 3 numbers."); + } + this._value = [...value]; + } + get value() { + return this._value; + } + get x() { + return this._value[0]; + } + get y() { + return this._value[1]; + } + get z() { + return this._value[2]; + } + equals(other) { + if (Array.isArray(other)) { + if (other.length !== 3) { + throw new Error("Input must be a 3-element array."); + } + other = other; + } + const arr1 = this._value; + const arr2 = other instanceof Vector3D ? other.value : other; + return math_1.math.vEqualWithTolerance(arr1, arr2, Vector3D.atol); + } + get norm() { + return math_1.math.vlen(this._value); + } +} +exports.Vector3D = Vector3D; +Vector3D.atol = 1e-8; +class RoundedVector3D extends Vector3D { + toJSON(skipRounding = false) { + const rounded = skipRounding + ? this.value + : math_1.math.roundArrayOrNumber(this.value, RoundedVector3D.roundPrecision); + return [...rounded]; + } + get value_rounded() { + return this.toJSON(); + } + get x_rounded() { + return this.value_rounded[0]; + } + get y_rounded() { + return this.value_rounded[1]; + } + get z_rounded() { + return this.value_rounded[2]; + } + equals(other) { + const arr1 = this.value_rounded; + const arr2 = Array.isArray(other) + ? new RoundedVector3D(other).value_rounded + : other.value_rounded; + const atol = RoundedVector3D.atol || 10 ** -RoundedVector3D.roundPrecision; + return math_1.math.vEqualWithTolerance(arr1, arr2, atol); + } + get norm_rounded() { + return math_1.math.roundArrayOrNumber(this.norm, RoundedVector3D.roundPrecision); + } +} +exports.RoundedVector3D = RoundedVector3D; +RoundedVector3D.roundPrecision = 9; diff --git a/package-lock.json b/package-lock.json index 81121dce..ab51fc26 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "license": "Apache-2.0", "dependencies": { - "@types/mathjs": "^3.21.1", + "@types/mathjs": "^5.0.1", "crypto-js": "^4.2.0", "js-yaml": "^4.1.0", "json-schema": "^0.4.0", @@ -34,7 +34,7 @@ "@babel/register": "^7.25.7", "@babel/runtime-corejs3": "^7.25.7", "@exabyte-io/eslint-config": "^2025.1.15-0", - "@mat3ra/esse": "2025.4.15-0", + "@mat3ra/esse": "2025.4.22-0", "@mat3ra/tsconfig": "2024.6.3-0", "@types/chai": "^4.3.20", "@types/crypto-js": "^4.2.2", @@ -2322,11 +2322,10 @@ "dev": true }, "node_modules/@mat3ra/esse": { - "version": "2025.4.15-0", - "resolved": "https://registry.npmjs.org/@mat3ra/esse/-/esse-2025.4.15-0.tgz", - "integrity": "sha512-T8IA2+BlGdoWKgsxgq6683dUF8molFk4n1uMzc72nBTz6fuBsCM579AL1hT3Ls8Iz51a4/YzWUNj8Xy+dNHU0Q==", + "version": "2025.4.22-0", + "resolved": "https://registry.npmjs.org/@mat3ra/esse/-/esse-2025.4.22-0.tgz", + "integrity": "sha512-bhpMHMuf6JTDXN2Pae8L+Zsn/JWBVZCqtLo1LgkvKK9QkjuUcjT8ixSGPYOjYEKe6wak+AUI0CqqgUBcVCS1KQ==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@babel/cli": "^7.27.0", "@babel/core": "^7.26.10", @@ -2535,9 +2534,9 @@ "dev": true }, "node_modules/@types/mathjs": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/@types/mathjs/-/mathjs-3.21.1.tgz", - "integrity": "sha512-2q5o+7V6/aJqvaap6jmQPQddIwDh35KFx61SbKfoT8dkchL+AsqtQhrpxo99by0L4uXehZTl4gSyiJMjyA0kRg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/mathjs/-/mathjs-5.0.1.tgz", + "integrity": "sha512-EFBuueI+BRed9bnUO6/9my55b4FH+VQIvqMm58h9JGbtaGCkqr3YSDhnmVbM1SJjF//8SURERSypzNwejOk7lA==", "dependencies": { "decimal.js": "^10.0.0" } diff --git a/package.json b/package.json index 2c63ec29..c2fac1df 100644 --- a/package.json +++ b/package.json @@ -15,15 +15,7 @@ "type": "git", "url": "https://github.com/Exabyte-io/code.js.git" }, - "main": "dist/index.js", - "exports": { - "./dist/js/context": "./dist/js/context/index.js", - "./dist/js/entity": "./dist/js/entity/index.js", - "./dist/js/utils": "./dist/js/utils/index.js", - "./dist/js/constants": "./dist/js/constants.js", - "./dist/js/math": "./dist/js/math.js", - "./dist/js/utils/schemas": "./dist/js/utils/schemas.js" - }, + "main": "./dist/js/index.js", "files": [ "/dist", "/src/js", @@ -50,7 +42,7 @@ "underscore": "^1.13.7", "underscore.string": "^3.3.6", "uuid": "8.3.2", - "@types/mathjs": "^3.21.1" + "@types/mathjs": "^5.0.1" }, "devDependencies": { "@babel/cli": "^7.25.7", @@ -63,7 +55,7 @@ "@babel/register": "^7.25.7", "@babel/runtime-corejs3": "^7.25.7", "@exabyte-io/eslint-config": "^2025.1.15-0", - "@mat3ra/esse": "2025.4.15-0", + "@mat3ra/esse": "2025.4.22-0", "@mat3ra/tsconfig": "2024.6.3-0", "@types/chai": "^4.3.20", "@types/crypto-js": "^4.2.2", diff --git a/src/js/ArrayWithIds.ts b/src/js/ArrayWithIds.ts new file mode 100644 index 00000000..3938d366 --- /dev/null +++ b/src/js/ArrayWithIds.ts @@ -0,0 +1,157 @@ +import { + defaultRoundingOptions, + RoundedValueWithId, + RoundingOptions, + ValueWithId, +} from "./ValueWithId"; + +export class ArrayWithIds { + values: T[]; + + ids: number[]; + + constructor(values: T[] = [], ids: number[] = []) { + if (values.length !== ids.length) { + throw new Error("Values and IDs must have the same length"); + } + this.values = [...values]; + this.ids = [...ids]; + } + + static fromValues>( + this: new (values: U[], ids: number[]) => C, + values: U[], + ): C { + const ids = values.map((_, i) => i); + return new this(values, ids); + } + + static fromObjects>( + this: new (values: U[], ids: number[]) => C, + objects: { id: number; value: U }[], + ): C { + const values = objects.map((obj) => obj.value); + const ids = objects.map((obj) => obj.id); + return new this(values, ids); + } + + toJSON(): object[] { + return this.values.map((value, index) => ({ + id: this.ids[index], + value: + value !== null && + typeof value === "object" && + "toJSON" in value && + typeof (value as any).toJSON === "function" + ? (value as any).toJSON() + : value, + })); + } + + toValueWithIdArray(): ValueWithId[] { + return this.values.map((value, index) => + ValueWithId.fromValueAndId(value, this.ids[index]), + ); + } + + getElementValueByIndex(index: number): T | undefined { + return this.values[index]; + } + + getElementIdByValue(value: T): number | undefined { + const index = this.values.findIndex((v) => + Array.isArray(v) && Array.isArray(value) + ? v.length === value.length && v.every((val, idx) => val === value[idx]) + : v === value, + ); + return index !== -1 ? this.ids[index] : undefined; + } + + filterByValues(valuesToKeep: T | T[]): void { + const toHash = (v: any) => (Array.isArray(v) ? JSON.stringify(v) : String(v)); + const keepSet = new Set( + Array.isArray(valuesToKeep) ? valuesToKeep.map(toHash) : [toHash(valuesToKeep)], + ); + + const filtered = this.values + .map((value, i) => [value, this.ids[i]] as [T, number]) + .filter(([value]) => keepSet.has(toHash(value))); + + this.values = filtered.map(([v]) => v); + this.ids = filtered.map(([_, id]) => id); + } + + filterByIndices(indices: number | number[]): void { + const keepSet = new Set(Array.isArray(indices) ? indices : [indices]); + this.values = this.values.filter((_, i) => keepSet.has(i)); + this.ids = this.ids.filter((_, i) => keepSet.has(i)); + } + + filterByIds(ids: number | number[], invert = false): void { + const idSet = new Set(Array.isArray(ids) ? ids : [ids]); + const keep = invert + ? this.ids.map((id, i) => (idSet.has(id) ? -1 : i)).filter((i) => i >= 0) + : this.ids.map((id, i) => (idSet.has(id) ? i : -1)).filter((i) => i >= 0); + + this.values = keep.map((i) => this.values[i]); + this.ids = keep.map((i) => this.ids[i]); + } + + equals(other: ArrayWithIds): boolean { + if (!(other instanceof ArrayWithIds)) return false; + if (this.values.length !== other.values.length) return false; + if (this.ids.length !== other.ids.length) return false; + + return ( + this.values.every((v, i) => { + const ov = other.values[i]; + return Array.isArray(v) && Array.isArray(ov) + ? v.length === ov.length && v.every((val, idx) => val === ov[idx]) + : v === ov; + }) && this.ids.every((id, i) => id === other.ids[i]) + ); + } + + mapArrayInPlace(func: (value: T) => T): void { + this.values = this.values.map(func); + } + + addItem(value: T, id?: number): void { + const newId = id ?? Math.max(-1, ...this.ids) + 1; + this.values.push(value); + this.ids.push(newId); + } + + removeItem(index: number, id?: number): void { + if (id !== undefined) { + index = this.ids.indexOf(id); + if (index === -1) throw new Error("ID not found"); + } + + if (index < 0 || index >= this.values.length) { + throw new Error("Index out of range"); + } + + this.values.splice(index, 1); + this.ids.splice(index, 1); + } +} + +export class RoundedArrayWithIds extends ArrayWithIds { + readonly roundingOptions: RoundingOptions; + + constructor( + values: T[] = [], + ids: number[] = [], + options: RoundingOptions = defaultRoundingOptions, + ) { + super(values, ids); + this.roundingOptions = options; + } + + override toJSON(): object[] { + return this.values.map((value, index) => + new RoundedValueWithId(this.ids[index], value, this.roundingOptions).toJSON(), + ); + } +} diff --git a/src/js/ValueWithId.ts b/src/js/ValueWithId.ts new file mode 100644 index 00000000..747ada7f --- /dev/null +++ b/src/js/ValueWithId.ts @@ -0,0 +1,97 @@ +import { ObjectWithIdAndValueSchema } from "@mat3ra/esse/dist/js/types"; + +import { math, RoundingMethodEnum } from "./math"; + +interface ValueWithIdSchema { + id: ObjectWithIdAndValueSchema["id"]; + value: T | null; +} + +export class ValueWithId { + id: number; + + value: T | null; + + static defaultConfig = { + id: 0, + value: null, + }; + + static fromValueAndId>( + this: new (args: { id: number; value: U }) => C, + value: U, + id = 0, + ): C { + return new this({ id, value }); + } + + constructor({ id, value }: ValueWithIdSchema = ValueWithId.defaultConfig) { + this.id = id; + this.value = value; + } + + /** + * Converts the instance to a plain JavaScript object. + */ + toJSON(): object { + if ( + this.value !== null && + typeof this.value === "object" && + "toJSON" in this.value && + typeof (this.value as any).toJSON === "function" + ) { + return { id: this.id, value: (this.value as any).toJSON() }; + } + return { id: this.id, value: this.value }; + } + + /** + * Checks if this instance is equal to another ValueWithId. + */ + equals(other: ValueWithId): boolean { + if (!(other instanceof ValueWithId)) return false; + + // because U may differ from T, we cast to unknown when comparing + const v1 = this.value as unknown; + const v2 = other.value as unknown; + + if (Array.isArray(v1) && Array.isArray(v2)) { + if (v1.length !== v2.length) return false; + for (let i = 0; i < v1.length; i++) { + if (v1[i] !== v2[i]) return false; + } + return this.id === other.id; + } + + return this.id === other.id && v1 === v2; + } +} + +export interface RoundingOptions { + precision: number; + roundingMethod: RoundingMethodEnum; +} + +export const defaultRoundingOptions: RoundingOptions = { + precision: 9, + roundingMethod: RoundingMethodEnum.HalfAwayFromZero, +}; + +export class RoundedValueWithId extends ValueWithId { + readonly precision: number; + + readonly roundingMethod: RoundingMethodEnum; + + constructor(id: number, value: T, options: RoundingOptions = defaultRoundingOptions) { + super({ id, value }); + this.precision = options.precision; + this.roundingMethod = options.roundingMethod; + } + + override toJSON(): object { + return { + id: this.id, + value: math.roundArrayOrNumber(this.value, this.precision, this.roundingMethod), + }; + } +} diff --git a/src/js/constants.js b/src/js/constants.ts similarity index 100% rename from src/js/constants.js rename to src/js/constants.ts diff --git a/src/js/index.ts b/src/js/index.ts index c27c9098..0894b9f7 100644 --- a/src/js/index.ts +++ b/src/js/index.ts @@ -1,9 +1,31 @@ +import { ArrayWithIds, RoundedArrayWithIds } from "./ArrayWithIds"; import * as context from "./context"; import * as entity from "./entity"; import * as utils from "./utils"; +import { RoundedValueWithId, ValueWithId } from "./ValueWithId"; +import { RoundedVector3D, Vector3D } from "./vector"; -export const Code = { +export { + ArrayWithIds, + ValueWithId, + RoundedValueWithId, + RoundedArrayWithIds, + RoundedVector3D, + Vector3D, +}; +export { entity, context, utils }; + +const Code = { + ArrayWithIds, + ValueWithId, + RoundedArrayWithIds, + RoundedValueWithId, + RoundedVector3D, + Vector3D, entity, context, utils, }; + +export type CodeType = typeof Code; +export default Code; diff --git a/src/js/math.ts b/src/js/math.ts index 2d9c6024..2ab927d7 100644 --- a/src/js/math.ts +++ b/src/js/math.ts @@ -259,6 +259,17 @@ export const roundCustom = ( return (roundedAbs * sign) / factor; }; +export const roundArrayOrNumber = ( + value: unknown, + decimals = 9, + method = RoundingMethodEnum.HalfAwayFromZero, +) => { + if (Array.isArray(value)) { + return value.map((v) => (typeof v === "number" ? roundCustom(v, decimals, method) : v)); + } + return typeof value === "number" ? roundCustom(value, decimals, method) : value; +}; + /** * @summary Returns n splits of the passed segment. */ @@ -312,8 +323,6 @@ export const math = { angleUpTo90, vDist, vEqualWithTolerance, - roundToZero, - precise, mod, isBetweenZeroInclusiveAndOne, cartesianProduct, @@ -321,8 +330,11 @@ export const math = { combinations, combinationsFromIntervals, calculateSegmentsBetweenPoints3D, + roundToZero, + precise, roundValueToNDecimals, numberToPrecision, roundCustom, RoundingMethod: RoundingMethodEnum, + roundArrayOrNumber, }; diff --git a/src/js/vector.ts b/src/js/vector.ts new file mode 100644 index 00000000..2952f6c7 --- /dev/null +++ b/src/js/vector.ts @@ -0,0 +1,95 @@ +import { Vector3DSchema } from "@mat3ra/esse/dist/js/types"; + +import { math } from "./math"; + +export class Vector3D { + static atol = 1e-8; + + private _value: Vector3DSchema; + + constructor(value: number[] | Vector3DSchema) { + if ( + !Array.isArray(value) || + value.length !== 3 || + !value.every((v) => typeof v === "number") + ) { + throw new Error("Vector3D must be a tuple of exactly 3 numbers."); + } + this._value = [...value] as Vector3DSchema; + } + + get value(): Vector3DSchema { + return this._value; + } + + get x(): number { + return this._value[0]; + } + + get y(): number { + return this._value[1]; + } + + get z(): number { + return this._value[2]; + } + + equals(other: number[] | Vector3DSchema | Vector3D): boolean { + if (Array.isArray(other)) { + if (other.length !== 3) { + throw new Error("Input must be a 3-element array."); + } + other = other as Vector3DSchema; + } + const arr1 = this._value; + const arr2 = other instanceof Vector3D ? other.value : other; + return math.vEqualWithTolerance(arr1, arr2, Vector3D.atol); + } + + get norm(): number { + return math.vlen(this._value); + } +} + +export class RoundedVector3D extends Vector3D { + static roundPrecision = 9; + + toJSON(skipRounding = false): Vector3DSchema { + const rounded = skipRounding + ? this.value + : (math.roundArrayOrNumber( + this.value, + RoundedVector3D.roundPrecision, + ) as Vector3DSchema); + return [...rounded] as Vector3DSchema; + } + + get value_rounded(): Vector3DSchema { + return this.toJSON(); + } + + get x_rounded(): number { + return this.value_rounded[0]; + } + + get y_rounded(): number { + return this.value_rounded[1]; + } + + get z_rounded(): number { + return this.value_rounded[2]; + } + + override equals(other: Vector3DSchema | RoundedVector3D): boolean { + const arr1 = this.value_rounded; + const arr2 = Array.isArray(other) + ? new RoundedVector3D(other).value_rounded + : other.value_rounded; + const atol = RoundedVector3D.atol || 10 ** -RoundedVector3D.roundPrecision; + return math.vEqualWithTolerance(arr1, arr2, atol); + } + + get norm_rounded(): number { + return math.roundArrayOrNumber(this.norm, RoundedVector3D.roundPrecision) as number; + } +} diff --git a/tests/js/arrayWithIds.tests.ts b/tests/js/arrayWithIds.tests.ts new file mode 100644 index 00000000..59085700 --- /dev/null +++ b/tests/js/arrayWithIds.tests.ts @@ -0,0 +1,223 @@ +import { expect } from "chai"; + +import { ArrayWithIds, RoundedArrayWithIds } from "../../src/js/ArrayWithIds"; +import { RoundingMethodEnum } from "../../src/js/math"; +import { ValueWithId } from "../../src/js/ValueWithId"; + +// Base dataset +const NUMBERS = [1, 2, 3, 4, 5]; +const STRINGS = ["value1", "value2", "value3", "value4", "value5"]; + +// Slices and subsets +const FIRST_THREE_NUMBERS = [1, 2, 3]; +const FIRST_THREE_STRINGS = ["value1", "value2", "value3"]; +const FIRST_THREE_IDS = [0, 1, 2]; + +// Boundary and error cases +const OUT_OF_RANGE_INDEX = 99; +const NON_EXISTENT_ID = 99; + +// Filtering criteria +const INDEX_FILTER = [1, 3]; +const ID_FILTER = [0, 2, 4]; +const EXPECTED_VALUES_BY_INDEX_FILTER = [2, 4]; +const EXPECTED_IDS_BY_INDEX_FILTER = [1, 3]; +const EXPECTED_VALUES_BY_ID_FILTER = ["value1", "value3", "value5"]; +const EXPECTED_IDS_BY_ID_FILTER = ID_FILTER; + +// Additions +const VALUE_TO_ADD = "value4"; +const MANUAL_ID_TO_ADD = 99; +const VALUES_AFTER_ADD = STRINGS.slice(0, 4); +const IDS_AFTER_ADD = [0, 1, 2, 3]; + +// ID+value objects +const EXPECTED_IDS_FROM_OBJECTS = [10, 20, 30]; +const OBJECTS_WITH_IDS = [ + { id: 10, value: "value1" }, + { id: 20, value: "value2" }, + { id: 30, value: "value3" }, +]; + +// Nested array values +const ARRAY_VALUES = [ + [1, 2], + [3, 4], + [5, 6], +]; +const ARRAY_VALUES_TO_KEEP = [ + [1, 2], + [5, 6], +]; +const ARRAY_IDS_TO_KEEP = [0, 2]; + +// Rounding +const FLOAT_VALUES = [1.23456789, 2.34567891, 3.45678912]; +const FLOAT_ARRAYS = [ + [1.23456789, 4.56789123], + [2.34567891, 5.67891234], +]; +const DEFAULT_ROUNDED_IDS = [1, 2, 3]; +const ROUNDED_RESULT_2DP = [ + { id: 1, value: 1.23 }, + { id: 2, value: 2.35 }, + { id: 3, value: 3.46 }, +]; +const ROUNDED_ARRAY_RESULT_2DP = [ + { id: 1, value: [1.23, 4.57] }, + { id: 2, value: [2.35, 5.68] }, +]; + +// Rounding + filtering +const ROUNDED_NUMBERS_EXAMPLE = [1.23, 2.34, 3.45]; +const FILTER_IDS_ROUNDED = [1, 3]; +const ROUNDED_VALUES_AFTER_FILTER = [1.23, 3.45]; +const ROUNDED_IDS_AFTER_FILTER = [1, 3]; + +describe("ArrayWithIds Tests", () => { + it("should create from values with sequential IDs", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_NUMBERS); + expect(arrayWithIds.values).to.deep.equal(FIRST_THREE_NUMBERS); + expect(arrayWithIds.ids).to.deep.equal(FIRST_THREE_IDS); + }); + + it("should create from objects with id and value properties", () => { + const arrayWithIds = ArrayWithIds.fromObjects(OBJECTS_WITH_IDS); + expect(arrayWithIds.values).to.deep.equal(FIRST_THREE_STRINGS); + expect(arrayWithIds.ids).to.deep.equal(EXPECTED_IDS_FROM_OBJECTS); + }); + + it("should convert to JSON format", () => { + const arrayWithIds = ArrayWithIds.fromObjects(OBJECTS_WITH_IDS); + expect(arrayWithIds.toJSON()).to.deep.equal(OBJECTS_WITH_IDS); + }); + + it("should get element value by index", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + expect(arrayWithIds.getElementValueByIndex(1)).to.equal("value2"); + expect(arrayWithIds.getElementValueByIndex(OUT_OF_RANGE_INDEX)).to.equal(undefined); + }); + + it("should get element ID by value", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + expect(arrayWithIds.getElementIdByValue("value3")).to.equal(2); + expect(arrayWithIds.getElementIdByValue("valueX")).to.equal(undefined); + }); + + it("should filter by values", () => { + const arrayWithIds = ArrayWithIds.fromValues(ARRAY_VALUES); + arrayWithIds.filterByValues(ARRAY_VALUES_TO_KEEP); + expect(arrayWithIds.values).to.deep.equal(ARRAY_VALUES_TO_KEEP); + expect(arrayWithIds.ids).to.deep.equal(ARRAY_IDS_TO_KEEP); + }); + + it("should filter by indices", () => { + const arrayWithIds = ArrayWithIds.fromValues(NUMBERS); + arrayWithIds.filterByIndices(INDEX_FILTER); + expect(arrayWithIds.values).to.deep.equal(EXPECTED_VALUES_BY_INDEX_FILTER); + expect(arrayWithIds.ids).to.deep.equal(EXPECTED_IDS_BY_INDEX_FILTER); + }); + + it("should filter by IDs", () => { + const arrayWithIds = ArrayWithIds.fromValues(STRINGS); + arrayWithIds.filterByIds(ID_FILTER); + expect(arrayWithIds.values).to.deep.equal(EXPECTED_VALUES_BY_ID_FILTER); + expect(arrayWithIds.ids).to.deep.equal(EXPECTED_IDS_BY_ID_FILTER); + }); + + it("should map array in-place", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + arrayWithIds.mapArrayInPlace((v) => v.toUpperCase()); + expect(arrayWithIds.values).to.deep.equal(["VALUE1", "VALUE2", "VALUE3"]); + }); + + it("should add items with auto-incrementing IDs", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + arrayWithIds.addItem(VALUE_TO_ADD); + expect(arrayWithIds.values).to.deep.equal(VALUES_AFTER_ADD); + expect(arrayWithIds.ids).to.deep.equal(IDS_AFTER_ADD); + }); + + it("should add items with specified IDs", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + arrayWithIds.addItem(VALUE_TO_ADD, MANUAL_ID_TO_ADD); + expect(arrayWithIds.values).to.deep.equal([...FIRST_THREE_STRINGS, VALUE_TO_ADD]); + expect(arrayWithIds.ids).to.deep.equal([0, 1, 2, MANUAL_ID_TO_ADD]); + }); + + it("should remove item by index", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + arrayWithIds.removeItem(1); + expect(arrayWithIds.values).to.deep.equal(["value1", "value3"]); + expect(arrayWithIds.ids).to.deep.equal([0, 2]); + }); + + it("should remove item by ID", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + arrayWithIds.removeItem(0, 1); + expect(arrayWithIds.values).to.deep.equal(["value1", "value3"]); + expect(arrayWithIds.ids).to.deep.equal([0, 2]); + }); + + it("should throw error when removing by non-existent ID", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + expect(() => arrayWithIds.removeItem(0, NON_EXISTENT_ID)).to.throw("ID not found"); + }); + + it("should throw error when removing by out-of-range index", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_STRINGS); + expect(() => arrayWithIds.removeItem(OUT_OF_RANGE_INDEX)).to.throw("Index out of range"); + }); + + it("should correctly compare equality", () => { + const a = ArrayWithIds.fromValues(FIRST_THREE_NUMBERS); + const b = ArrayWithIds.fromValues(FIRST_THREE_NUMBERS); + const c = ArrayWithIds.fromValues([1, 2, 4]); + expect(a.equals(b)).to.equal(true); + expect(a.equals(c)).to.equal(false); + + const d = new ArrayWithIds([1, 2, 3], [0, 1, 2]); + expect(a.equals(d)).to.equal(true); + + const e = new ArrayWithIds([1, 2, 3], [10, 20, 30]); + expect(a.equals(e)).to.equal(false); + }); + + it("should convert to array of ValueWithId objects", () => { + const arrayWithIds = ArrayWithIds.fromValues(FIRST_THREE_NUMBERS); + const valueWithIdArray = arrayWithIds.toValueWithIdArray(); + + expect(valueWithIdArray.length).to.equal(3); + expect(valueWithIdArray[0]).to.be.instanceOf(ValueWithId); + expect(valueWithIdArray[0].id).to.equal(0); + expect(valueWithIdArray[0].value).to.equal(1); + }); +}); + +describe("RoundedArrayWithIds Tests", () => { + it("should round values when converting to JSON", () => { + const roundedArray = new RoundedArrayWithIds(FLOAT_VALUES, DEFAULT_ROUNDED_IDS, { + precision: 2, + roundingMethod: RoundingMethodEnum.HalfAwayFromZero, + }); + expect(roundedArray.toJSON()).to.deep.equal(ROUNDED_RESULT_2DP); + }); + + it("should round array values when converting to JSON", () => { + const roundedArray = new RoundedArrayWithIds(FLOAT_ARRAYS, [1, 2], { + precision: 2, + roundingMethod: RoundingMethodEnum.HalfAwayFromZero, + }); + expect(roundedArray.toJSON()).to.deep.equal(ROUNDED_ARRAY_RESULT_2DP); + }); + + it("should inherit methods from ArrayWithIds", () => { + const roundedArray = new RoundedArrayWithIds(ROUNDED_NUMBERS_EXAMPLE, DEFAULT_ROUNDED_IDS); + roundedArray.filterByIds(FILTER_IDS_ROUNDED); + expect(roundedArray.values).to.deep.equal(ROUNDED_VALUES_AFTER_FILTER); + expect(roundedArray.ids).to.deep.equal(ROUNDED_IDS_AFTER_FILTER); + + const expected = new ArrayWithIds(ROUNDED_VALUES_AFTER_FILTER, ROUNDED_IDS_AFTER_FILTER); + expect(roundedArray.equals(expected)).to.be.true; + }); +}); diff --git a/tests/js/valueWithId.tests.ts b/tests/js/valueWithId.tests.ts new file mode 100644 index 00000000..71352ccf --- /dev/null +++ b/tests/js/valueWithId.tests.ts @@ -0,0 +1,111 @@ +import { expect } from "chai"; + +import { RoundingMethodEnum } from "../../src/js/math"; +import { RoundedValueWithId, ValueWithId } from "../../src/js/ValueWithId"; + +// default constructor values +const DEFAULT_ID = 0; +const DEFAULT_VALUE = null; + +// test values for primitives +const TEST_ID = 1; +const TEST_VALUE = "testValue"; +const DIFFERENT_VALUE = "differentValue"; + +// test values for arrays +const ARRAY_VALUE = [1, 2, 3]; +const ARRAY_VALUE_EQUAL = [1, 2, 3]; +const ARRAY_VALUE_DIFF = [1, 2, 4]; +const ARRAY_VALUE_SHORTER = [1, 2]; + +// rounding input values +const FLOAT_VALUE_TO_ROUND = 1.23456789; +const ARRAY_OF_FLOATS = [1.23456789, 2.34567891, 3.45678912]; + +// expected results +const ROUNDED_SINGLE_VALUE = 1.235; +const ROUNDED_ARRAY_VALUES = [1.23, 2.35, 3.46]; + +// bankers rounding edge cases +const VALUE_AT_HALF_TO_EVEN_DOWN = 2.5; // should round to 2 +const VALUE_AT_HALF_TO_EVEN_UP = 3.5; // should round to 4 +const EXPECTED_EVEN_DOWN = 2; +const EXPECTED_EVEN_UP = 4; + +describe("ValueWithId Tests", () => { + it("should create with default values", () => { + const valueWithId = new ValueWithId(); + expect(valueWithId.id).to.equal(DEFAULT_ID); + expect(valueWithId.value).to.be.equal(DEFAULT_VALUE); + }); + + it("should create with specified id and value", () => { + const valueWithId = ValueWithId.fromValueAndId(TEST_VALUE, TEST_ID); + expect(valueWithId.id).to.equal(TEST_ID); + expect(valueWithId.value).to.be.equal(TEST_VALUE); + }); + + it("should convert to JSON format", () => { + const valueWithId = ValueWithId.fromValueAndId(TEST_VALUE, TEST_ID); + const jsonResult = valueWithId.toJSON(); + expect(jsonResult).to.deep.equal({ id: TEST_ID, value: TEST_VALUE }); + }); + + it("should correctly compare equality with primitive values", () => { + const a = ValueWithId.fromValueAndId(TEST_VALUE, TEST_ID); + const b = ValueWithId.fromValueAndId(TEST_VALUE, TEST_ID); + const c = ValueWithId.fromValueAndId(DIFFERENT_VALUE, TEST_ID); + + expect(a.equals(b)).to.equal(true); + expect(a.equals(c)).to.equal(false); + }); + + it("should correctly compare equality with array values", () => { + const a = ValueWithId.fromValueAndId(ARRAY_VALUE, TEST_ID); + const b = ValueWithId.fromValueAndId(ARRAY_VALUE_EQUAL, TEST_ID); + const c = ValueWithId.fromValueAndId(ARRAY_VALUE_DIFF, TEST_ID); + const d = ValueWithId.fromValueAndId(ARRAY_VALUE_SHORTER, TEST_ID); + + expect(a.equals(b)).to.equal(true); + expect(a.equals(c)).to.equal(false); + expect(a.equals(d)).to.equal(false); + }); +}); + +describe("RoundedValueWithId Tests", () => { + it("should round numeric values with specified precision", () => { + const roundedValueWithId = new RoundedValueWithId(TEST_ID, FLOAT_VALUE_TO_ROUND, { + precision: 3, + roundingMethod: RoundingMethodEnum.HalfAwayFromZero, + }); + + const result = roundedValueWithId.toJSON() as { id: number; value: number }; + expect(result).to.deep.equal({ id: TEST_ID, value: ROUNDED_SINGLE_VALUE }); + }); + + it("should round array values with specified precision", () => { + const roundedValueWithId = new RoundedValueWithId(TEST_ID, ARRAY_OF_FLOATS, { + precision: 2, + roundingMethod: RoundingMethodEnum.HalfAwayFromZero, + }); + + const result = roundedValueWithId.toJSON() as { id: number; value: number[] }; + expect(result).to.deep.equal({ id: TEST_ID, value: ROUNDED_ARRAY_VALUES }); + }); + + it("should properly apply bankers rounding when specified", () => { + const roundToEvenCase1 = new RoundedValueWithId(TEST_ID, VALUE_AT_HALF_TO_EVEN_DOWN, { + precision: 0, + roundingMethod: RoundingMethodEnum.Bankers, + }); + const result1 = roundToEvenCase1.toJSON() as { id: number; value: number }; + expect(result1.value).to.equal(EXPECTED_EVEN_DOWN); + + const roundToEvenCase2 = new RoundedValueWithId(TEST_ID, VALUE_AT_HALF_TO_EVEN_UP, { + precision: 0, + roundingMethod: RoundingMethodEnum.Bankers, + }); + const result2 = roundToEvenCase2.toJSON() as { id: number; value: number }; + expect(result2.value).to.equal(EXPECTED_EVEN_UP); + }); +}); diff --git a/tests/js/vector.ts b/tests/js/vector.ts new file mode 100644 index 00000000..ef3df070 --- /dev/null +++ b/tests/js/vector.ts @@ -0,0 +1,86 @@ +import { expect } from "chai"; + +import { RoundedVector3D, Vector3D } from "../../src/js/vector"; + +const VECTOR_FLOAT: [number, number, number] = [1.23456789, 2.345678901, 3.456789012]; +const VECTOR_FLOAT_NORM = 4.3561172682906; +const FLOAT_PRECISION = 1e-8; + +const VECTOR_FLOAT_DIFFERENT_WITHIN_TOL: [number, number, number] = [ + 1.23456789999, 2.345678901, 3.456789012, +]; +const VECTOR_FLOAT_DIFFERENT_OUTSIDE_TOL: [number, number, number] = [ + 1.2345699999, 2.345678901, 3.456789012, +]; + +const VECTOR_FLOAT_ROUNDED_4: [number, number, number] = [1.2346, 2.3457, 3.4568]; +const VECTOR_FLOAT_ROUNDED_3: [number, number, number] = [1.235, 2.346, 3.457]; + +describe("Vector3D", () => { + it("should do init and value access", () => { + const vector = new Vector3D(VECTOR_FLOAT); + expect(vector.value).to.deep.equal(VECTOR_FLOAT); + expect(vector.x).to.be.closeTo(1.23456789, FLOAT_PRECISION); + expect(vector.y).to.be.closeTo(2.345678901, FLOAT_PRECISION); + expect(vector.z).to.be.closeTo(3.456789012, FLOAT_PRECISION); + }); + + it("should do init with wrong type throws", () => { + expect(() => new Vector3D([1, 2, "3"] as any)).to.throw(); + }); + + it("should do init with wrong size throws", () => { + expect(() => new Vector3D([1, 2] as any)).to.throw(); + }); + + it("should do equality", () => { + const vector = new Vector3D(VECTOR_FLOAT); + expect(vector.equals(VECTOR_FLOAT)).to.equal(true); + expect(vector.equals(VECTOR_FLOAT_DIFFERENT_WITHIN_TOL)).to.equal(true); + expect(vector.equals(VECTOR_FLOAT_DIFFERENT_OUTSIDE_TOL)).to.equal(false); + }); + + it("should do norm is close to expected", () => { + const vector = new Vector3D(VECTOR_FLOAT); + expect(Math.abs(vector.norm - VECTOR_FLOAT_NORM)).to.be.lessThan(FLOAT_PRECISION); + }); +}); + +describe("RoundedVector3D", () => { + it("should do init and default value access", () => { + const vector = new RoundedVector3D(VECTOR_FLOAT); + expect(vector.value).to.deep.equal(VECTOR_FLOAT); + }); + + it("should do serialization with precision 4", () => { + RoundedVector3D.roundPrecision = 4; + const vector = new RoundedVector3D(VECTOR_FLOAT); + + expect(vector.toJSON()).to.deep.equal(VECTOR_FLOAT_ROUNDED_4); + expect(vector.value_rounded).to.deep.equal(VECTOR_FLOAT_ROUNDED_4); + expect(vector.x_rounded).to.be.deep.equal(VECTOR_FLOAT_ROUNDED_4[0]); + expect(vector.y_rounded).to.be.deep.equal(VECTOR_FLOAT_ROUNDED_4[1]); + expect(vector.z_rounded).to.be.deep.equal(VECTOR_FLOAT_ROUNDED_4[2]); + }); + + it("should do serialization with precision 3", () => { + RoundedVector3D.roundPrecision = 3; + const vector = new RoundedVector3D(VECTOR_FLOAT); + + expect(vector.toJSON()).to.deep.equal(VECTOR_FLOAT_ROUNDED_3); + expect(vector.value_rounded).to.deep.equal(VECTOR_FLOAT_ROUNDED_3); + }); + + it("should do equality changes with precision", () => { + RoundedVector3D.roundPrecision = 4; + let vector = new RoundedVector3D(VECTOR_FLOAT); + expect(vector.equals(VECTOR_FLOAT)).to.equal(true); + expect(vector.equals(VECTOR_FLOAT_ROUNDED_4)).to.equal(true); + expect(vector.equals(VECTOR_FLOAT_ROUNDED_3)).to.equal(false); + + RoundedVector3D.roundPrecision = 3; + vector = new RoundedVector3D(VECTOR_FLOAT); + expect(vector.equals(VECTOR_FLOAT_ROUNDED_4)).to.equal(true); + expect(vector.equals(VECTOR_FLOAT_ROUNDED_3)).to.equal(true); + }); +});