UNPKG

@qudtlib/core

Version:

Data model for QUDTLib

804 lines (803 loc) 33 kB
import { Unit } from "./unit.js"; import { arrayContains, arrayDeduplicate, arrayMin, BooleanComparator, compareUsingEquals, findInIterable, NumberComparator, StringComparator, ONE, ZERO, isNullish, } from "./utils.js"; import { Prefix } from "./prefix.js"; import { DerivedUnitSearchMode } from "./derivedUnitSearchMode.js"; import { FactorUnit } from "./factorUnit.js"; import { FactorUnits } from "./factorUnits.js"; import { QuantityValue } from "./quantityValue.js"; import { QudtNamespaces } from "./qudtNamespaces.js"; export class QudtlibConfig { constructor() { this.units = new Map(); this.quantityKinds = new Map(); this.prefixes = new Map(); this.systemsOfUnits = new Map(); this.unitsByDimensionVector = new Map(); } indexUnitsByDimensionVector() { this.units.forEach((u) => { const dimVector = u.dimensionVectorIri; if (!isNullish(dimVector)) { let unitsWithSameDimVector = this.unitsByDimensionVector.get(dimVector); if (isNullish(unitsWithSameDimVector)) { unitsWithSameDimVector = []; this.unitsByDimensionVector.set(dimVector, unitsWithSameDimVector); } unitsWithSameDimVector.push(u); } }); } } export const config = new QudtlibConfig(); export class Qudt { /** * Returns the {@link Unit} identified the specified IRI. For example, <code> * unit("http://qudt.org/vocab/unit/N-PER-M2")</code> yields `Units.N__PER__M2`, * if that unit has been loaded; * * @param iri the requested unit IRI * @return the unit or `undefined` if no unit is found */ static unit(unitIri) { return config.units.get(unitIri); } /** * Same as {@link #unit(string)} but throws an exception if no unit is found. * @param unitIri the unit IRI * @return the unit */ static unitRequired(unitIri) { const ret = Qudt.unit(unitIri); if (typeof ret === "undefined") { throw `Unit ${unitIri} not found`; } return ret; } /** * Returns the first unit found whose label matches the specified label after replacing any * underscore with space and ignoring case (US locale). If more intricate matching is needed, * clients can use `{@link #allUnits()}.filter(...)`. * * @param label the matched label * @return the first unit found */ static unitFromLabel(label) { const matcher = new CaseInsensitiveUnderscoreIgnoringLabelMatcher(label); const firstMatch = findInIterable(config.units.values(), (u) => matcher.matchesLangStrings(u.labels) || (!!u.currencyCode && matcher.matchesString(u.currencyCode))); return firstMatch; } static unitFromLabelRequired(label) { const match = this.unitFromLabel(label); if (!match) throw `No unit found for label ${label}`; return match; } static unitFromLocalname(localname) { return Qudt.unit(Qudt.unitIriFromLocalname(localname)); } static unitFromLocalnameRequired(localname) { return Qudt.unitRequired(Qudt.unitIriFromLocalname(localname)); } static unitIriFromLocalname(localname) { return Qudt.NAMESPACES.unit.makeIriInNamespace(localname); } /** * @deprecated The currencies have been moved back into the units graph. Just use the units methods. */ static currencyFromLocalname(localname) { return this.unitFromLocalname(localname); } /** * @deprecated The currencies have been moved back into the units graph. Just use the units methods. */ static currencyFromLocalnameRequired(localname) { return this.unitFromLocalnameRequired(localname); } /** * @deprecated The currencies have been moved back into the units graph. Just use the units methods. */ static currencyIriFromLocalname(localname) { return this.unitIriFromLocalname(localname); } static quantityKind(quantityKindIri) { return config.quantityKinds.get(quantityKindIri); } static quantityKindRequired(quantityKindIri) { const ret = Qudt.quantityKind(quantityKindIri); if (typeof ret === "undefined") { throw `QuantityKind ${quantityKindIri} not found`; } return ret; } static quantityKindFromLocalname(localname) { return Qudt.quantityKind(Qudt.quantityKindIriFromLocalname(localname)); } static quantityKindFromLocalnameRequired(localname) { return Qudt.quantityKindRequired(Qudt.quantityKindIriFromLocalname(localname)); } static quantityKindIriFromLocalname(localname) { return Qudt.NAMESPACES.quantityKind.makeIriInNamespace(localname); } static quantityKinds(unit) { return unit.quantityKindIris.map((iri) => Qudt.quantityKindRequired(iri)); } static quantityKindsBroad(unit) { let current = Qudt.quantityKinds(unit); const result = []; current.forEach((qk) => result.push(qk)); while (current.length) { current = current .flatMap((qk) => qk.broaderQuantityKindIris) .map((iri) => Qudt.quantityKindRequired(iri)); current.forEach((qk) => result.includes(qk) || result.push(qk)); } return result; } static isBroaderQuantityKind(suspectedBroader, quantityKind) { const broader = quantityKind.broaderQuantityKindIris; if (broader.length === 0) { return false; } if (broader.includes(suspectedBroader.iri)) { return true; } return broader.some((b) => Qudt.isBroaderQuantityKind(Qudt.quantityKindRequired(b), suspectedBroader)); } static prefixFromLabelRequired(label) { const match = this.prefixFromLabel(label); if (!match) throw `No prefix found for label ${label}`; return match; } static prefixFromLabel(label) { const matcher = new CaseInsensitiveUnderscoreIgnoringLabelMatcher(label); const firstMatch = findInIterable(config.prefixes.values(), (u) => matcher.matchesLangStrings(u.labels)); return firstMatch; } static prefixFromLocalname(localname) { return Qudt.prefix(Qudt.prefixIriFromLocalname(localname)); } static prefixFromLocalnameRequired(localname) { return Qudt.prefixRequired(Qudt.prefixIriFromLocalname(localname)); } static prefixIriFromLocalname(localname) { return Qudt.NAMESPACES.prefix.makeIriInNamespace(localname); } static prefix(prefixIri) { return config.prefixes.get(prefixIri); } static prefixRequired(prefixIri) { const ret = Qudt.prefix(prefixIri); if (typeof ret === "undefined") { throw `Prefix ${prefixIri} not found`; } return ret; } static systemOfUnitsFromLabelRequired(label) { const match = this.systemOfUnitsFromLabel(label); if (!match) throw `No systemOfUnits found for label ${label}`; return match; } static systemOfUnitsFromLabel(label) { const matcher = new CaseInsensitiveUnderscoreIgnoringLabelMatcher(label); const firstMatch = findInIterable(config.systemsOfUnits.values(), (u) => matcher.matchesLangStrings(u.labels)); return firstMatch; } static systemOfUnitsFromLocalname(localname) { return Qudt.systemOfUnits(Qudt.systemOfUnitsIriFromLocalname(localname)); } static systemOfUnitsFromLocalnameRequired(localname) { return Qudt.systemOfUnitsRequired(Qudt.systemOfUnitsIriFromLocalname(localname)); } static systemOfUnitsIriFromLocalname(localname) { return Qudt.NAMESPACES.systemOfUnits.makeIriInNamespace(localname); } static systemOfUnits(systemOfUnitsIri) { return config.systemsOfUnits.get(systemOfUnitsIri); } static systemOfUnitsRequired(systemOfUnitsIri) { const ret = Qudt.systemOfUnits(systemOfUnitsIri); if (typeof ret === "undefined") { throw `SystemOfUnits ${systemOfUnitsIri} not found`; } return ret; } /** * Obtains units based on factor units, using the specified {@link DerivedUnitSearchMode}. * * For example, * * ``` * const spec = new Map<Unit, number>(); * spec.set(Units.M, 1); * spec.set(Units.KiloGM, 1); * spec.set(Units.SEC, -2); * Qudt.derivedUnitsFrom Map( * DerivedUnitSearchMode.BEST_MATCH, spec); * ``` * * will yield an array containing the Newton Unit ({@code Qudt.Units.N}) * * @param searchMode the {@link DerivedUnitSearchMode} to use * @param factorUnits a map containing unit to exponent entries. * @return the derived units that match the given factor units */ static derivedUnitsFromMap(searchMode, factorUnits) { const flattened = []; for (const [key, value] of factorUnits.entries()) { flattened.push(key, value); } return this.derivedUnitsFromExponentUnitPairs(searchMode, ...flattened); } /** * Obtains units based on factor units. * * @param searchMode the {@link DerivedUnitSearchMode} to use * @param factorUnits the factor units * @return the derived unit that match the given factor units * @see #derivedUnitsFromMap(DerivedUnitSearchMode, Map) */ static derivedUnitsFromFactorUnits(searchMode, ...factorUnits) { return this.derivedUnitsFromFactorUnitSelection(searchMode, new FactorUnits(factorUnits)); } /** * Vararg method, must be an even number of arguments, always alternating types of Unit|String * and Integer. * * @param factorUnitSpec alternating Unit (representing a unit IRI) and Decimal|number (the * exponent) * @return the units that match */ static derivedUnitsFromExponentUnitPairs(searchMode, ...factorUnitSpecs) { const spec = []; for (let i = 0; i < factorUnitSpecs.length; i++) { const specAtI = factorUnitSpecs[i]; if (i % 2 == 0 && specAtI instanceof Unit) { spec[i] = specAtI; } else if (i % 2 == 0 && typeof specAtI === "string") { const unitString = specAtI; let unit = Qudt.unit(unitString); if (typeof unit === "undefined") { unit = Qudt.unitFromLocalname(unitString); } if (typeof unit === "undefined") { unit = Qudt.unitFromLabel(unitString); } if (typeof unit === "undefined") { throw `Unable to find unit for string ${unitString}, interpreted as iri, label or localname`; } spec[i] = unit; } else if (i % 2 == 1 && typeof specAtI === "number") { spec[i] = specAtI; } else { throw `Cannot handle input ${specAtI} at 0-based position ${i}`; } } const initialFactorUnitSelection = FactorUnits.ofFactorUnitSpec(...spec); return Qudt.derivedUnitsFromFactorUnitSelection(searchMode, initialFactorUnitSelection); } /** * @param searchMode the {@link DerivedUnitSearchMode} to use * @param selection the factor unit selection * @return the units that match * @see #derivedUnitsFromMap(DerivedUnitSearchMode, Map) */ static derivedUnitsFromFactorUnitSelection(searchMode, selection) { const matchingUnits = this.findMatchingUnits(selection); if (searchMode === DerivedUnitSearchMode.ALL || matchingUnits.length < 2) { return matchingUnits.sort(Qudt.bestMatchForFactorUnitsComparator(selection)); } else { const requestedUnits = selection.contractExponents().factorUnits; return [ arrayMin(matchingUnits, Qudt.bestMatchForFactorUnitsComparator(selection)), ]; } } static bestMatchForFactorUnitsComparator(requestedFactorUnits) { const reqNorm = requestedFactorUnits.normalize(); const reqNum = requestedFactorUnits.numerator(); const reqNumNorm = reqNum.normalize(); const reqDen = requestedFactorUnits.denominator(); const reqDenNorm = reqDen.normalize(); const reqLocalNamePossibilities = requestedFactorUnits.generateAllLocalnamePossibilities(); return (left, right) => { if (left.factorUnits.equals(requestedFactorUnits)) { if (!right.factorUnits.equals(requestedFactorUnits)) { return -1; } } else { if (right.factorUnits.equals(requestedFactorUnits)) { return 1; } } if (right.deprecated) { if (!left.deprecated) { return -1; } } else { if (left.deprecated) { return 1; } } if (right.isDefinedAsOtherUnit() && left.factorUnits.factorUnits?.length == 1 && left.factorUnits.factorUnits[0].unit.equals(right) && right.factorUnits.factorUnits[0].exponent === left.factorUnits.factorUnits[0]?.exponent) { // if a unit is just another name of another unit with same exponent, // prefer the other (thus L would be preferred over DeciM) return -1; } if (left.isDefinedAsOtherUnit() && right.factorUnits.factorUnits?.length == 1 && right.factorUnits.factorUnits[0].unit.equals(left) && left.factorUnits.factorUnits[0].exponent === right.factorUnits.factorUnits[0]?.exponent) { return 1; } if (left.getIriLocalname().indexOf("-") === -1) { if (right.getIriLocalname().indexOf("-") > -1) { return -1; // prefer a derived unit with a new name (such as W, J, N etc.) } } else if (right.getIriLocalname().indexOf("-") === -1) { return 1; } const diffFactorsCountDen = Qudt.expandedFactorsCountDiff(left.factorUnits.denominator(), right.factorUnits.denominator(), reqDen); if (diffFactorsCountDen != 0) { return diffFactorsCountDen; } const diffFactorsCountNum = Qudt.expandedFactorsCountDiff(left.factorUnits.numerator(), right.factorUnits.numerator(), reqNum); if (diffFactorsCountNum != 0) { return diffFactorsCountNum; } const factorCountDiff = Qudt.expandedFactorsCountDiff(left.factorUnits, right.factorUnits, requestedFactorUnits); if (factorCountDiff != 0) { return factorCountDiff; } if (left.dependents >= 10 && left.dependents > 2 * right.dependents) { return -1; // prefer a unit that has more dependents (other units that refer to // it as their factor unit or base unit) } else if (right.dependents >= 10 && right.dependents > 2 * left.dependents) { return 1; } const leftLocalname = left.getIriLocalname(); const rightLocalname = right.getIriLocalname(); if (reqLocalNamePossibilities.includes(leftLocalname)) { if (!reqLocalNamePossibilities.includes(rightLocalname)) { return -1; } } else if (reqLocalNamePossibilities.includes(rightLocalname)) { return 1; } const leftUnderscores = Qudt.countUnderscores(leftLocalname); const rightUnderscores = Qudt.countUnderscores(rightLocalname); if (leftUnderscores < rightUnderscores) { return -1; // prefer a unit without modifier in one of its components } else if (leftUnderscores > rightUnderscores) { return 1; } if (left.factorUnits.equals(reqNorm)) { if (!right.factorUnits.equals(reqNorm)) { return -1; // prefer a unit that matches the normalized factors exactly } } else { if (right.factorUnits.equals(reqNorm)) { return 1; } } return StringComparator(left.getIriLocalname(), right.getIriLocalname()); }; } static countUnderscores(str) { if (!str) return 0; let count = 0; for (const char of str) { if (char === "_") count++; } return count; } static expandedFactorsCountDiff(leftDen, rightDen, target) { const leftFactorsDenCnt = leftDen.expand().length; const rightFactorsDenCnt = rightDen.expand().length; const reqFactorsDenCnt = target.expand().length; const diffFactorsCountDen = Math.abs(reqFactorsDenCnt - leftFactorsDenCnt) - Math.abs(reqFactorsDenCnt - rightFactorsDenCnt); return diffFactorsCountDen; } static findMatchingUnits(initialFactorUnitSelection) { const matchingUnits = []; const unitsWithSameDimVector = this.getUnitsByDimensionVector(initialFactorUnitSelection.getDimensionVector()); if (isNullish(unitsWithSameDimVector) || unitsWithSameDimVector.length == 0) { return []; } for (const unit of unitsWithSameDimVector) { if (unit.matches(initialFactorUnitSelection)) { matchingUnits.push(unit); } } arrayDeduplicate(matchingUnits, compareUsingEquals); return matchingUnits; } static getUnitsByDimensionVector(dimVector) { let dimVectorIri = undefined; if (typeof dimVector === "string") { dimVectorIri = dimVector; } else { dimVectorIri = dimVector.getDimensionVectorIri(); } const unitsWithSameDimVector = config.unitsByDimensionVector.get(dimVectorIri); if (isNullish(unitsWithSameDimVector)) { return []; } return unitsWithSameDimVector; } static getUnitsWithSameDimensionVector(unit) { if (isNullish(unit.dimensionVectorIri)) { throw new Error(`unit ${unit.getIriAbbreviated()} does not have a dimension vector iri`); } return this.getUnitsByDimensionVector(unit.dimensionVectorIri); } /** * Returns the unit resulting from scaling the specified `unit` with the specified `prefix`. * NOTE: if you have unit/prefix labels (not IRIs) - such as "KILO", "NEWTON", use {@link #scaleUnitFromLabels(string, string):Unit}. * * @param prefix the prefix to use for scaling or its IRI. * @param baseUnit the unit to scale or its IRI * @return the resulting unit * @throws exception if no such unit is found */ static scale(prefix, baseUnit) { const thePrefix = prefix instanceof Prefix ? prefix : this.prefixRequired(prefix); const theUnit = baseUnit instanceof Unit ? baseUnit : this.unitRequired(baseUnit); // special case: KiloGM is not a scaling of GM, it's the other way round. Handle special case here. if (thePrefix.iri.endsWith("/Kilo") && theUnit.iri.endsWith("/GM")) { return this.unitFromLocalname("KiloGM"); } const candidates = this.getUnitsWithSameDimensionVector(theUnit); for (const u of candidates) { if (u.prefix?.equals(thePrefix) && u.scalingOf?.equals(theUnit)) { return u; } } throw `No scaled unit found with base unit ${theUnit.toString()} and prefix ${thePrefix.toString()}`; } /** * Returns the unit resulting from scaling the specified `baseUnitLabel` label * (such as "METER") with the specified `prefixLabel` (such as "KILO"). * * @param prefixLabel the label of the prefix, case-insensitive , such as "KILO", or "kilo" * @param baseUnitLabel the label of the base unit, case-insensitive, such as "Meter" */ static scaleUnitFromLabels(prefixLabel, baseUnitLabel) { return this.scale(Qudt.prefixFromLabelRequired(prefixLabel), Qudt.unitFromLabelRequired(baseUnitLabel)); } /** * Returns the list of {@link FactorUnit}s of the specified `unit`. * * @param unit the unit to get factors for * @return the factors of the unit or an empty list if the unit is not a derived unit */ static factorUnits(unit) { return FactorUnit.contractExponents(unit.getLeafFactorUnitsWithCumulativeExponents()); } /** * Perform mathematical simplification on factor units. Only simplifies units with exponents of the same sign. * * For example, * ``` * N / M * M -> N per M^2 * ``` * * @param factorUnits the factor units to simplify * @return the simplified factor units. * @deprecated `use contractExponents(FactorUnit[]): FactorUnit[]` instead */ static simplifyFactorUnits(factorUnits) { return FactorUnit.contractExponents(factorUnits); } static contractFactorUnits(factorUnits) { return FactorUnit.contractExponents(factorUnits); } static reduceFactorUnits(factorUnits) { return FactorUnit.reduceExponents(factorUnits); } static unscale(unit, treatKiloGmAsUnscaled = true, treatPrefixlessAsUnscaled = true) { if (!unit.scalingOf) { return unit; } if (treatPrefixlessAsUnscaled && !unit.prefix) { return unit; } if (treatKiloGmAsUnscaled && unit.getIriAbbreviated() === "unit:KiloGM") { return unit; } return unit.scalingOf; } /** * Return a list of {@link FactorUnit}s with the same exponents as the specified `factorUnits` but their base units as units. * * @param factorUnits the factor units to unscale * @return the unscaled factor units */ static unscaleFactorUnits(factorUnits, treatKiloGMAsUnscaled = true, treatPrefixlessAsUnscaled = true) { return factorUnits.map((fu) => new FactorUnit(Qudt.unscale(fu.unit, treatKiloGMAsUnscaled, treatPrefixlessAsUnscaled), fu.exponent)); } /** * Instantiates a QuantityValue. * @param value a Decimal * @param unit a Unit or unit IRI. * @return a QuantityValue with the specified data */ static quantityValue(value, unit) { if (typeof unit === "string") { return new QuantityValue(value, Qudt.unitRequired(unit)); } return new QuantityValue(value, unit); } /** * Converts the specified value from the unit it is in (`fromUnit`) to the specified target unit (`toUnit`). * @param value: a Decimal, the value to convert. * @param fromUnit: a Unit or string. A string is interpreted as a Unit IRI. * @param toUnit: a Unit or string. A string is interpreted as a Unit IRI. * @return the resulting value */ static convert(value, fromUnit, toUnit, quantityKind) { if (!fromUnit) { throw "Parameter 'fromUnit' is required"; } if (!toUnit) { throw "Parameter 'toUnit' is required"; } const from = typeof fromUnit === "string" ? Qudt.unitRequired(fromUnit) : fromUnit; const to = typeof toUnit === "string" ? Qudt.unitRequired(toUnit) : toUnit; const qk = isNullish(quantityKind) ? undefined : typeof quantityKind === "string" ? Qudt.quantityKindRequired(quantityKind) : quantityKind; return from.convert(value, to, qk); } /** * Converts the specified QuantityValue from to the specified target unit (`toUnit`). * @param value: a Decimal, the value to convert. * @param fromUnit: a Unit or string. A string is interpreted as a Unit IRI. * @param toUnit: a Unit or string. A string is interpreted as a Unit IRI. * @return a QuantityValue holding the result */ static convertQuantityValue(from, toUnit, quantityKind) { if (!from) { throw "Parameter 'from' is required"; } if (!toUnit) { throw "Parameter 'toUnit' is required"; } const to = typeof toUnit === "string" ? Qudt.unitRequired(toUnit) : toUnit; const qk = isNullish(quantityKind) ? undefined : typeof quantityKind === "string" ? Qudt.quantityKindRequired(quantityKind) : quantityKind; return from.convert(to, qk); } /** * Returns `true` if the two units can be converted into each other. * @param fromUnit a Unit or unit IRI * @param toUnit a Unit or unit IRI * @return a boolean indicating whether the units are convertible. */ static isConvertible(fromUnit, toUnit) { const from = typeof fromUnit === "string" ? Qudt.unitRequired(fromUnit) : fromUnit; const to = typeof toUnit === "string" ? Qudt.unitRequired(toUnit) : toUnit; return from.isConvertible(to); } /** * Returns a [Unit, Decimal] tuple containing the base unit of the specified `unit` * along with the scale factor needed to convert values from the base unit to * the specified unit. * * @param unit the unit to scale to its base * @return a [Unit, Decimal] tuple with the base unit and the required scale factor */ static scaleToBaseUnit(unit) { if (!unit.scalingOf) { return { unit: unit, factor: ONE }; } const baseUnit = unit.scalingOf; return { unit: baseUnit, factor: unit.getConversionMultiplier(baseUnit) }; } static allUnits() { const ret = []; for (const unit of config.units.values()) { ret.push(unit); } return ret; } static allQuantityKinds() { const ret = []; for (const quantityKind of config.quantityKinds.values()) { ret.push(quantityKind); } return ret; } static allPrefixes() { const ret = []; for (const prefix of config.prefixes.values()) { ret.push(prefix); } return ret; } static allSystemsOfUnits() { const ret = []; for (const prefix of config.systemsOfUnits.values()) { ret.push(prefix); } return ret; } static allUnitsOfSystem(system) { const ret = []; for (const unit of config.units.values()) { if (system.allowsUnit(unit)) { ret.push(unit); } } return ret; } /** * Returns the first unit obtained using {@link #correspondingUnitsInSystem(Unit, * SystemOfUnits)}. * * @return the unit corresponding to the specified unit in the specified systemOfUnits. */ static correspondingUnitInSystem(unit, systemOfUnits) { const correspondingUnits = Qudt.correspondingUnitsInSystem(unit, systemOfUnits); if (typeof correspondingUnits !== "undefined" && correspondingUnits.length > 0) { return correspondingUnits[0]; } return undefined; } /** * Gets units that correspond to the specified unit are allowed in the specified systemOfUnits. * The resulting units have to * * <ol> * <li>have the same dimension vector as the unit * <li>share at least one quantityKind with unit * </ol> * * and they are ascending sorted by dissimilarity in magnitude to the magnitude of the specified * unit, i.e. the first unit returned is the closest in magnitude. * * <p>If two resulting units have the same magnitude difference from the specified one, the * following comparisons are made consecutively until a difference is found: * * <ol> * <li>the base unit of the specified system is ranked first * <li>conversion offset closer to the one of the specified unit is ranked first * <li>the unscaled unit is ranked first * <li>the unit that has a symbol is ranked first * <li>the unit with more quantityKinds is ranked first * <li>the units are ranked by their IRIs lexicographically * </ol> * * that is a base unit of the system is ranked first. If none or both are base units, the one * with a conversion offset closer to the specified unit's conversion offset is ranked first. * * @param unit * @param systemOfUnits * @return */ static correspondingUnitsInSystem(unit, systemOfUnits) { if (systemOfUnits.allowsUnit(unit)) { return [unit]; } const elegible = Array.from(config.units.values()) .filter((u) => systemOfUnits.allowsUnit(u)) .filter((u) => u.dimensionVectorIri === unit.dimensionVectorIri); if (elegible.length === 1) { return elegible; } let candidates = [...elegible]; // get the unit that is closest in magnitude (conversionFactor) // recursively check for factor units candidates = candidates.filter((u) => u.quantityKinds.some((q) => arrayContains(unit.quantityKinds, q))); if (candidates.length === 1) { return candidates; } candidates.sort((l, r) => { const scaleDiffL = Math.abs(Qudt.scaleDifference(l, unit)); const scaleDiffR = Math.abs(Qudt.scaleDifference(r, unit)); const diff = Math.sign(scaleDiffL - scaleDiffR); if (diff !== 0) { return diff; } // tie breaker: base unit ranked before non-base unit let cmp = BooleanComparator(systemOfUnits.hasBaseUnit(r), systemOfUnits.hasBaseUnit(l)); if (cmp !== 0) { return cmp; } // tie breaker: closer offset const offsetDiffL = Math.abs(Qudt.offsetDifference(l, unit)); const offsetDiffR = Math.abs(Qudt.offsetDifference(r, unit)); cmp = Math.sign(offsetDiffL - offsetDiffR); if (cmp != 0) { return cmp; } // tie breaker: perfer unit that is not scaled cmp = BooleanComparator(l.isScaled(), r.isScaled()); if (cmp != 0) { return cmp; } // tie breaker prefer the unit that has a symbol (it's more likely to be // commonly used): cmp = BooleanComparator(r.hasSymbol(), l.hasSymbol()); if (cmp != 0) { return cmp; } // tie breaker: prefer unit with more quantity kinds (it's less specific) cmp = NumberComparator(l.quantityKinds.length, r.quantityKinds.length); if (cmp != 0) { return cmp; } // tie breaker: lexicographically compare iris. return StringComparator(l.iri, r.iri); }); return candidates; } static scaleDifference(u1, u2) { const u1Log10 = u1.conversionMultiplier.log(10); const u2Log10 = u2.conversionMultiplier.log(10); return u1Log10.toNumber() - u2Log10.toNumber(); } static offsetDifference(u1, u2) { const u1Log10 = Qudt.logOrZeroRequireNonNegative(u1.conversionOffset.abs()); const u2Log10 = Qudt.logOrZeroRequireNonNegative(u2.conversionOffset.abs()); return u1Log10.toNumber() - u2Log10.toNumber(); } static logOrZeroRequireNonNegative(val) { if (val.isNegative()) { throw `Cannot get logarithm of negative value ${val}`; } if (val.isZero()) { return ZERO; } return val.log(10); } } Qudt.NAMESPACES = QudtNamespaces; class CaseInsensitiveUnderscoreIgnoringLabelMatcher { constructor(searchTerm) { this.compareForEquality = this.convert(searchTerm); } convert(term) { return term.replaceAll("_", " ").toLocaleUpperCase("en-US"); } matchesString(searchTerm) { return this.convert(searchTerm) === this.compareForEquality; } matchesLangString(searchTerm) { return this.convert(searchTerm.text) === this.compareForEquality; } matchesLangStrings(searchTerms) { return searchTerms.some((st) => this.convert(st.text) === this.compareForEquality); } } //# sourceMappingURL=qudt.js.map