@thi.ng/units
Version:
Extensible SI unit creation, conversions, quantities & calculations (incl. ~170 predefined units & constants)
170 lines (169 loc) • 5.11 kB
JavaScript
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
};