UNPKG

@thi.ng/units

Version:

Extensible SI unit creation, conversions, quantities & calculations (incl. ~170 predefined units & constants)

170 lines (169 loc) 5.11 kB
import { isArray } from "@thi.ng/checks"; import { isNumber } from "@thi.ng/checks/is-number"; import { isString } from "@thi.ng/checks/is-string"; import { equivArrayLike } from "@thi.ng/equiv"; import { assert } from "@thi.ng/errors/assert"; import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; import { NONE, PREFIXES } from "./api.js"; const UNITS = {}; const unit = (dim, scale, offset = 0, coherent2 = false) => ({ dim: isNumber(dim) ? __oneHot(dim) : dim, scale, offset, coherent: coherent2 }); const coherent = (dim) => unit(dim, 1, 0, true); const dimensionless = (scale, offset = 0, coherent2 = false) => unit(NONE.dim, scale, offset, coherent2); const defUnit = (sym, name, unit2, force = false) => { if (UNITS[sym] && !force) illegalArgs(`attempt to override unit: ${sym}`); return UNITS[sym] = { ...unit2, sym, name }; }; const asUnit = (id) => { for (let i = 0; i < id.length; i++) { const pre = id.substring(0, i); const unit2 = UNITS[id.substring(i)]; if (unit2) { return PREFIXES[pre] !== void 0 ? prefix(pre, unit2) : !pre ? unit2 : illegalArgs(`unknown unit: ${id}`); } } for (let u in UNITS) { if (UNITS[u].name === id) return UNITS[u]; } illegalArgs(`unknown unit: ${id}`); }; const prefix = (id, unit2, coherent2 = false) => { const $u = __ensureUnit(unit2); return $u.coherent ? mul($u, PREFIXES[id], coherent2) : illegalArgs("unit isn't coherent"); }; class Quantity { constructor(value) { this.value = value; } deref() { return isArray(this.value) ? this.value.map((x) => x.scale) : this.value.scale; } } const quantity = (value, unit2) => new Quantity( isNumber(value) ? mul(unit2, value) : value.map((x) => mul(unit2, x)) ); function mul(a, b, coherent2 = false) { if (a instanceof Quantity) return __combineQ(mul, a, b); const $a = __ensureUnit(a); if (isNumber(b)) return unit($a.dim, $a.scale * b, $a.offset, coherent2); const $b = __ensureUnit(b); return unit( $a.dim.map((x, i) => x + $b.dim[i]), $a.scale * $b.scale, 0, coherent2 ); } function div(a, b, coherent2 = false) { if (a instanceof Quantity) return __combineQ(div, a, b); const $a = __ensureUnit(a); if (isNumber(b)) { return unit($a.dim, $a.scale / b, $a.offset, coherent2); } const $b = __ensureUnit(b); return unit( $a.dim.map((x, i) => x - $b.dim[i]), $a.scale / $b.scale, 0, coherent2 ); } function reciprocal(u, coherent2 = false) { return u instanceof Quantity ? new Quantity( isArray(u.value) ? u.value.map((x) => div(NONE, x)) : div(NONE, u.value) ) : div(NONE, u, coherent2); } const pow = (u, k, coherent2 = false) => { const $u = __ensureUnit(u); return unit( $u.dim.map((x) => x * k), $u.scale ** k, 0, coherent2 ); }; function convert(x, a, b) { const $src = __ensureUnit(a); if (x instanceof Quantity) { return isArray(x.value) ? x.value.map((y) => convert(1, y, $src)) : convert(1, x.value, $src); } const $dest = __ensureUnit(b); const xnorm = x * $src.scale + $src.offset; if (isReciprocal($src, $dest)) return (1 / xnorm - $dest.offset) / $dest.scale; assert(equivArrayLike($src.dim, $dest.dim), "incompatible dimensions"); return (xnorm - $dest.offset) / $dest.scale; } const isConvertible = (src, dest) => { if (src instanceof Quantity) return isConvertible(__qunit(src), dest); const $src = __ensureUnit(src); const $dest = __ensureUnit(dest); return isReciprocal($src, $dest) || equivArrayLike($src.dim, $dest.dim); }; const isDimensionless = (u) => u instanceof Quantity ? isDimensionless(__qunit(u)) : __ensureUnit(u).dim.every((x) => x === 0); const isReciprocal = (a, b) => { const { dim: $a } = __ensureUnit(a); const { dim: $b } = __ensureUnit(b); let ok = false; for (let i = 0; i < 7; i++) { const xa = $a[i]; const xb = $b[i]; if (xa === 0 && xb === 0) continue; if (xa !== -xb) return false; ok = true; } return ok; }; const formatSI = (u) => { if (u instanceof Quantity) return formatSI(__qunit(u)); const { dim } = __ensureUnit(u); const SI = ["kg", "m", "s", "A", "K", "mol", "cd"]; const acc = []; for (let i = 0; i < 7; i++) { const x = dim[i]; if (x !== 0) acc.push(SI[i] + (x !== 1 ? x : "")); } return acc.length ? acc.join("\xB7") : "<dimensionless>"; }; const __ensureUnit = (x) => isString(x) ? asUnit(x) : x; const __oneHot = (x) => { const dims = new Array(7).fill(0); dims[x] = 1; return dims; }; const __qunit = (q) => isArray(q.value) ? q.value[0] : q.value; const __combineQ = (op, a, b) => { const $b = b; const vecA = isArray(a.value); const vecB = isArray($b.value); return new Quantity( vecA ? vecB ? a.value.map((x, i) => op(x, $b.value[i])) : a.value.map((x) => op(x, $b.value)) : vecB ? $b.value.map((x) => op(a.value, x)) : op(a.value, $b.value) ); }; export { Quantity, UNITS, asUnit, coherent, convert, defUnit, dimensionless, div, formatSI, isConvertible, isDimensionless, isReciprocal, mul, pow, prefix, quantity, reciprocal, unit };