@qudtlib/core
Version:
Data model for QUDTLib
275 lines (274 loc) • 10.6 kB
JavaScript
import { FactorUnit } from "./factorUnit.js";
import { arrayEqualsIgnoreOrdering, compareUsingEquals, isNullish, ONE, } from "./utils.js";
import { Unit } from "./unit.js";
import { DimensionVector } from "./dimensionVector.js";
/**
* Class representing a set of FactorUnits and a conversionMultiplier, so the units can be
* used to replace any other unit of the same dimensionality.
*/
export class FactorUnits {
constructor(factorUnits, scaleFactor = ONE) {
this.normalized = undefined;
this.dimensionVector = undefined;
this.factorUnits = factorUnits;
this.scaleFactor = scaleFactor;
}
static ofUnit(unit) {
return new FactorUnits([FactorUnit.ofUnit(unit)]);
}
static ofFactorUnitSpecWithScaleFactor(scalar, ...factorUnitSpec) {
const withoutScalar = this.ofFactorUnitSpec(...factorUnitSpec);
return new FactorUnits(withoutScalar.factorUnits, scalar);
}
static ofFactorUnitSpec(...factorUnitSpec) {
if (factorUnitSpec.length % 2 !== 0) {
throw "An even number of arguments is required";
}
if (factorUnitSpec.length > 14) {
throw "No more than 14 arguments (7 factor units) are supported";
}
const factorUnits = [];
for (let i = 0; i < factorUnitSpec.length; i += 2) {
const requestedUnit = factorUnitSpec[i];
const requestedExponent = factorUnitSpec[i + 1];
if (!(requestedUnit instanceof Unit)) {
throw `argument at 0-based position ${i} is not of type Unit. The input must be between 1 and 7 Unit, exponent pairs`;
}
if (typeof requestedExponent !== "number" ||
!Number.isInteger(requestedExponent)) {
throw `argument at 0-based position ${i + 1} is not of type number or not an integer. The input must be between 1 and 7 Unit, exponent pairs`;
}
factorUnits.push(new FactorUnit(requestedUnit, requestedExponent));
}
return new FactorUnits(factorUnits);
}
static empty() {
return FactorUnits.EMPTY_FACTOR_UNITS;
}
/**
* Returns this ScaledFactorUnits object, raised to the specified power.
* @param power
*/
pow(power) {
return new FactorUnits(this.factorUnits.map((fu) => fu.pow(power)), this.scaleFactor.pow(power));
}
/**
*
* @param other
*/
combineWith(other) {
if (!other) {
return this;
}
return new FactorUnits(FactorUnit.contractExponents([...this.factorUnits, ...other.factorUnits]), this.scaleFactor.mul(other.scaleFactor));
}
/**
* Returns this ScaledFactorUnits object, with its conversionMultiplier multiplied by the specified value.
*
* @param by
*/
scale(by) {
return new FactorUnits(this.factorUnits, this.scaleFactor.mul(by));
}
isRatioOfSameUnits() {
return (this.factorUnits.length === 2 &&
this.factorUnits[0].unit.equals(this.factorUnits[1].unit) &&
this.factorUnits[0].exponent === this.factorUnits[1].exponent * -1);
}
reduceExponents() {
return new FactorUnits(FactorUnit.reduceExponents(this.factorUnits), this.scaleFactor);
}
contractExponents() {
return new FactorUnits(FactorUnit.contractExponents(this.factorUnits), this.scaleFactor);
}
hasFactorUnits() {
if (isNullish(this.factorUnits)) {
return false;
}
if (this.factorUnits.length === 0) {
return false;
}
if (this.factorUnits.length === 1 &&
!this.factorUnits[0].unit.factorUnits.equals(this)) {
return true;
}
if (this.factorUnits.length === 1 &&
this.factorUnits[0].exponent === 1 &&
ONE.equals(this.scaleFactor)) {
return false;
}
return true;
}
/**
* Returns true iff this factorUnits object has exactly one factor unit, which has exponent 1.
*/
isOneOtherUnitWithExponentOne() {
if (isNullish(this.factorUnits)) {
return false;
}
if (this.factorUnits.length !== 1) {
return false;
}
const factorUnit = this.factorUnits[0];
if (factorUnit.exponent !== 1) {
return false;
}
if (factorUnit.unit.factorUnits.equals(this)) {
return false;
}
return true;
}
normalize() {
if (!isNullish(this.normalized)) {
return this.normalized;
}
let normalized = null;
if (this.hasFactorUnits()) {
const mapped = this.factorUnits.map((fu) => fu.unit.normalize().pow(fu.exponent));
normalized = mapped.reduce((prev, cur) => prev.combineWith(cur));
}
else {
normalized = new FactorUnits(this.factorUnits, this.scaleFactor);
}
if (!normalized.isRatioOfSameUnits()) {
normalized = normalized.reduceExponents();
}
this.normalized = normalized.scale(this.scaleFactor);
return this.normalized;
}
expand() {
return FactorUnits.expandFactors(this);
}
static expandFactors(factorUnits) {
if (!factorUnits.hasFactorUnits()) {
return [...factorUnits.factorUnits];
}
return factorUnits.factorUnits.flatMap((fu) => FactorUnits.expandFactors(fu.unit.factorUnits));
}
getAllPossibleFactorUnitCombinations() {
return FactorUnit.getAllPossibleFactorUnitCombinations(this.factorUnits);
}
/**
* Returns a FactorUnits object containing the scaleFactor and the factors in the numerator of
* this FactorUnits object. Note that any derived units in the numerator are returned without
* recursive decomposition. For example, for `5.0 * M2-PER-N` a FactorUnit object representing
* `N` is returned.
*
* @return a FactorUnits object representing the scaleFactor and the numerator units of this
* unit.
*/
numerator() {
return new FactorUnits(this.numeratorFactors(), this.scaleFactor);
}
numeratorFactors() {
return this.factorUnits.filter((fu) => fu.exponent > 0);
}
/**
* Returns a FactorUnits object containing the factors in the denominator of this FactorUnits
* object. Note that any derived units in the denominator are returned without recursive
* decomposition. For example, for `5.0 * ` a FactorUnit object representing `5.0 * N` is
* returned.
*
* @return a FactorUnits object representing the scaleFactor and the numerator units of this
* unit.
*/
denominator() {
return new FactorUnits(this.denominatorFactors());
}
getDimensionVector() {
if (isNullish(this.dimensionVector)) {
this.dimensionVector = this.computeDimensionVector();
}
return this.dimensionVector;
}
computeDimensionVector() {
if (isNullish(this.factorUnits) || this.factorUnits.length == 0) {
return DimensionVector.DIMENSIONLESS;
}
let dv = undefined;
for (const fu of this.factorUnits) {
const factorUnitFeatureVector = fu.getDimensionVector();
if (isNullish(factorUnitFeatureVector)) {
throw new Error(`Cannot compute dimension vector of factor units ${this.toString()}: ${fu.unit.getIriAbbreviated()} does not have a dimension vector`);
}
if (isNullish(dv)) {
dv = factorUnitFeatureVector;
}
else {
dv = dv.combine(factorUnitFeatureVector);
}
}
return dv;
}
denominatorFactors() {
return this.factorUnits
.filter((fu) => fu.exponent < 0)
.map((fu) => fu.pow(-1));
}
getLocalname() {
return this.generateLocalname(this.numeratorFactors()
.map((fu) => FactorUnits.factorUnitLocalname(fu))
.join("-"), this.denominatorFactors()
.map((fu) => FactorUnits.factorUnitLocalname(fu))
.join("-"));
}
static isEmptyOrNullish(val) {
return val === null || typeof val === "undefined" || val.length == 0;
}
generateLocalname(numerator = "", denominator = "") {
let completeString = numerator;
if (!FactorUnits.isEmptyOrNullish(denominator)) {
if (!FactorUnits.isEmptyOrNullish(numerator)) {
completeString += "-";
}
completeString += "PER-" + denominator;
}
return completeString;
}
static factorUnitLocalname(fu) {
return (fu.unit.getIriLocalname() +
(Math.abs(fu.exponent) > 1 ? Math.abs(fu.exponent) : ""));
}
static permutate(strings) {
const ret = [];
if (strings.length <= 1) {
ret.push(strings);
return ret;
}
for (let i = 0; i < strings.length; i++) {
const otherElements = [...strings];
otherElements.splice(i, 1);
const othersPermutated = this.permutate(otherElements);
for (const otherPermutated of othersPermutated) {
otherPermutated.unshift(strings[i]);
}
ret.push(...othersPermutated);
}
return ret;
}
permutateFactorUnitLocalnames(predicate) {
return FactorUnits.permutate(this.factorUnits.filter(predicate).map(FactorUnits.factorUnitLocalname)).map((strings) => strings.join("-"));
}
generateAllLocalnamePossibilities() {
return this.permutateFactorUnitLocalnames((fu) => fu.exponent > 0).flatMap((numeratorString) => this.permutateFactorUnitLocalnames((fu) => fu.exponent < 0).map((denominatorString) => this.generateLocalname(numeratorString, denominatorString)));
}
equals(other) {
if (!other) {
return false;
}
return (this.scaleFactor.eq(other?.scaleFactor) &&
arrayEqualsIgnoreOrdering(this.factorUnits, other?.factorUnits, compareUsingEquals));
}
toString() {
return ((this.scaleFactor.eq(ONE) ? "" : this.scaleFactor.toString() + "*") +
(this.factorUnits.length > 0
? "[" +
this.factorUnits
.map((s) => s.toString())
.reduce((p, n) => p + ", " + n) +
"]"
: "[no factors]"));
}
}
FactorUnits.EMPTY_FACTOR_UNITS = new FactorUnits([], ONE);
//# sourceMappingURL=factorUnits.js.map