
io.github.qudtlib.Qudt Maven / Gradle / Ivy
package io.github.qudtlib;
import static java.util.stream.Collectors.toList;
import com.java2s.Log10BigDecimal;
import io.github.qudtlib.exception.IncompleteDataException;
import io.github.qudtlib.exception.InconvertibleQuantitiesException;
import io.github.qudtlib.exception.NotFoundException;
import io.github.qudtlib.init.Initializer;
import io.github.qudtlib.model.*;
import io.github.qudtlib.support.fractional.FractionalDimensionVector;
import io.github.qudtlib.support.fractional.FractionalUnits;
import io.github.qudtlib.support.index.Flag;
import io.github.qudtlib.support.index.SearchIndex;
import io.github.qudtlib.support.parse.UnitParser;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Main QUDTLib interface.
*
* A few examples:
*
*
{@code
* // Converting 38.5° Celsius into Fahrenheit:
* Qudt.convert(new BigDecimal("38.5"), Qudt.Units.DEG_C, Qudt.Units.DEG_F);
* // finding unit for factors: m, kg, and s^-2:
* Set myUnits =
* Qudt.derivedUnit(
* Qudt.Units.M, 1,
* Qudt.Units.KiloGM, 1,
* Qudt.Units.SEC, -2);
* // finding factors of Newton:
* List myFactorUnits = Qudt.Units.N.getFactorUnits();
* // Converting 1N into kN (using QuantityValue):
* QuantityValue quantityValue = new QuantityValue(new BigDecimal("1"), Qudt.Units.N);
* QuantityValue converted = Qudt.convert(quantityValue, Qudt.Units.KiloN);
* }
*
* @author Florian Kleedorfer
* @version 1.0
*/
@SuppressWarnings("unused")
public class Qudt {
private static final Map units;
private static final Map quantityKinds;
private static final Map prefixes;
private static final Map systemsOfUnits;
private static final Map constantValues;
private static final Map physicalConstants;
private static final BigDecimal BD_1000 = new BigDecimal("1000");
public abstract static class NAMESPACES extends QudtNamespaces {}
/*
* The constants for units, quantity kinds and prefixes are kept in separate package-protected classes as they are
* quite numerous and would clutter this class too much. They are made available
*/
public abstract static class Units extends io.github.qudtlib.model.Units {}
public abstract static class QuantityKinds extends io.github.qudtlib.model.QuantityKinds {}
public abstract static class Prefixes extends io.github.qudtlib.model.Prefixes {}
public abstract static class SystemsOfUnits extends io.github.qudtlib.model.SystemsOfUnits {}
public abstract static class PhysicalConstants
extends io.github.qudtlib.model.PhysicalConstants {}
private static final SearchIndex unitIndex = new SearchIndex<>(true);
private static final Map> unitsByDimensionVector =
new HashMap<>();
/* Use the Initializer to load and wire all prefixes, units and quantityKinds. */
static {
Initializer initializer = null;
try {
Class> type = Class.forName("io.github.qudtlib.init.InitializerImpl");
initializer = (Initializer) type.getConstructor().newInstance();
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
"Cannot initialize QUDTlib\n\nMake sure you have either \n\n\tqudtlib-init-rdf \nor \n\n\tqudtlib-init-hardcoded \n\non your classpath!",
e);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"Cannot initialize QUDTlib - No zero-arg constructor found in InitializerImpl",
e);
}
if (initializer != null) {
Initializer.Definitions definitions = initializer.loadData();
prefixes = initializer.buildPrefixes(definitions);
units = initializer.buildUnits(definitions);
quantityKinds = initializer.buildQuantityKinds(definitions);
systemsOfUnits = initializer.buildSystemsOfUnits(definitions);
constantValues = initializer.buildConstantValues(definitions);
physicalConstants = initializer.buildPhysicalConstants(definitions);
} else {
prefixes = new HashMap<>();
units = new HashMap<>();
quantityKinds = new HashMap<>();
systemsOfUnits = new HashMap<>();
constantValues = new HashMap<>();
physicalConstants = new HashMap<>();
System.err.println(
"\n\n\n ERROR: The QUDTlib data model has not been initialized properly and will not work\n\n\n");
}
reindexUnitsForSearch();
reindexUnitsForComparison();
}
private enum UNIT_INDEX_KEYS {
SYMBOL,
UCUM_CODE,
LABEL,
IRI_LOCALNAME,
EMPTY_VALUE
}
private static void reindexUnitsForComparison() {
unitsByDimensionVector.clear();
for (Unit u : units.values()) {
try {
Optional dvOpt = u.getDimensionVector();
if (dvOpt.isEmpty()) {
System.err.println("no dimension vector for: " + u.getIriAbbreviated());
continue;
}
DimensionVector dv = dvOpt.get();
SortedSet similarUnits = unitsByDimensionVector.get(dv);
if (similarUnits == null) {
similarUnits = new TreeSet<>(Comparator.comparing(Unit::getIri));
unitsByDimensionVector.put(dv, similarUnits);
}
similarUnits.add(u);
} catch (IncompleteDataException e) {
System.err.println(
"error calculating dimension vector for: " + u.getIriAbbreviated());
}
}
}
private static void reindexUnitsForSearch() {
unitIndex.clear();
for (Unit u : Qudt.units.values()) {
unitIndex.put(
UNIT_INDEX_KEYS.SYMBOL
+ u.getSymbol().orElse(UNIT_INDEX_KEYS.EMPTY_VALUE.toString()),
u);
u.getAltSymbols().stream()
.forEach(symbol -> unitIndex.put(UNIT_INDEX_KEYS.SYMBOL + symbol, u));
unitIndex.put(
UNIT_INDEX_KEYS.UCUM_CODE
+ u.getUcumCode().orElse(UNIT_INDEX_KEYS.EMPTY_VALUE.toString()),
u);
unitIndex.put(UNIT_INDEX_KEYS.IRI_LOCALNAME + u.getIriLocalname(), u);
u.getLabels().stream()
.map(LangString::getString)
.forEach(
s -> {
unitIndex.put(UNIT_INDEX_KEYS.UCUM_CODE + s, u);
});
}
}
/*
* public methods
*/
/**
* Returns a {@link Unit} for the specified localname (i.e. the last element of the Unit IRI).
* For example, unitFromLocalName("N-PER-M2")
yields the unit with IRI
* http://qudt.org/vocab/unit/N-PER-M2
.
*
* @param localname the local name of the IRI that identifies the requested unit.
* @return the unit
* @throws NotFoundException if no such unit is found.
*/
public static Optional unitFromLocalname(String localname) {
return unit(unitIriFromLocalname(localname));
}
public static Unit unitFromLocalnameRequired(String localname) {
return unitRequired(unitIriFromLocalname(localname));
}
public static Unit currencyFromLocalnameRequired(String localname) {
return unitRequired(currencyIriFromLocalname(localname));
}
public static Optional currencyFromLocalname(String localname) {
return unit(currencyIriFromLocalname(localname));
}
/**
* 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()}.stream().filter(...)
.
*
* @param label the matched label
* @return the first unit found
*/
public static Optional unitFromLabel(String label) {
LabelMatcher labelMatcher = new LabelMatcher(label);
return units.values().stream()
.filter(u -> u.getLabels().stream().anyMatch(labelMatcher::matches))
.findFirst();
}
public static Set unitsByIriLocalname(
String iriLocalname, boolean matchPrefix, boolean caseInsensitive) {
return unitIndex.get(
UNIT_INDEX_KEYS.IRI_LOCALNAME + iriLocalname,
Flag.matchPrefix(matchPrefix).caseInsensitive(caseInsensitive).getBits());
}
public static Set unitsBySymbol(
String symbol, boolean matchPrefix, boolean caseInsensitive) {
return unitIndex.get(
UNIT_INDEX_KEYS.SYMBOL + symbol,
Flag.matchPrefix(matchPrefix).caseInsensitive(caseInsensitive).getBits());
}
public static Set unitsByUcumCode(
String ucumCode, boolean matchPrefix, boolean caseInsensitive) {
return unitIndex.get(
UNIT_INDEX_KEYS.UCUM_CODE + ucumCode,
Flag.matchPrefix(matchPrefix).caseInsensitive(caseInsensitive).getBits());
}
public static Set unitsByLabel(
String label, boolean matchPrefix, boolean caseInsensitive) {
return unitIndex.get(
UNIT_INDEX_KEYS.LABEL + label,
Flag.matchPrefix(matchPrefix).caseInsensitive(caseInsensitive).getBits());
}
/**
* Parse the unit specified in the `input` String and filter the result by the specific
* quantityKind's applicableUnits.
*
* @param input the string to parse
* @param quantityKind the quantitykind that the units are required to be applicable to
* @return the set of units that match the input string and quantitykind
*/
public static Set parseUnit(String input, QuantityKind quantityKind) {
return new UnitParser(input, quantityKind).parse();
}
public static Set parseUnit(String input) {
return new UnitParser(input).parse();
}
public static Unit unitFromLabelRequired(String label) {
return unitFromLabel(label)
.orElseThrow(
() -> new NotFoundException("No unit found for label '" + label + "'"));
}
/**
* Returns the {@link Unit} identified the specified IRI. For example,
* unit("http://qudt.org/vocab/unit/N-PER-M2")
yields {@code Qudt.Units.N__PER__M2};
*
* @param iri the requested unit IRI
* @return the unit
*/
public static Optional unit(String iri) {
return Optional.ofNullable(units.get(iri));
}
public static Unit unitRequired(String iri) {
return Optional.ofNullable(units.get(iri))
.orElseThrow(() -> new NotFoundException("No unit found for Iri " + iri));
}
/**
* Returns a unit IRI with the specified localname (even if no such unit exists in the model).
*
* @param localname the local name of the IRI that identifies the requested unit.
* @return the full IRI, possibly identifying a unit
*/
public static String unitIriFromLocalname(String localname) {
return NAMESPACES.unit.makeIriInNamespace(localname);
}
public static String currencyIriFromLocalname(String localname) {
return NAMESPACES.currency.makeIriInNamespace(localname);
}
public static Unit scale(String prefixLabel, String baseUnitLabel) {
LabelMatcher labelMatcher = new LabelMatcher(baseUnitLabel);
return units.values().stream()
.filter(u -> u.getPrefix().isPresent())
.filter(
u ->
u.getPrefix().get().getLabels().stream()
.anyMatch(
pl -> pl.getString().equalsIgnoreCase(prefixLabel)))
.filter(u -> u.getScalingOf().isPresent())
.filter(
u ->
u.getScalingOf().get().getLabels().stream()
.anyMatch(labelMatcher::matches))
.findFirst()
.orElseThrow(
() ->
new NotFoundException(
String.format(
"No scaled unit found with base unit '%s' and prefix '%s'",
baseUnitLabel, prefixLabel)));
}
/**
* Returns the unit resulting from scaling the specified {@code unit} with the specified {@code
* prefix}.
*
* @param prefix the prefix to use for scaling
* @param baseUnit the unit to scale
* @return the resulting unit
* @throws NotFoundException if no such unit is present in the model.
*/
public static Unit scale(Prefix prefix, Unit baseUnit) {
return units.values().stream()
.filter(u -> u.getPrefix().isPresent())
.filter(u -> u.getPrefix().get().equals(prefix))
.filter(u -> u.getScalingOf().isPresent())
.filter(u -> u.getScalingOf().get().equals(baseUnit))
.findFirst()
.orElseThrow(
() ->
new NotFoundException(
String.format(
"No scaled unit found with base unit '%s' and prefix '%s'",
baseUnit, prefix)));
}
/**
* Returns the base unit of the specified scaled {@code unit}. For example, {@code
* unscale(Qudt.Units.KiloM)} returns {@code Qudt.Units.M}. {@code Qudt.Units.KiloGM} as well as
* any unit that does not have a prefix (such as {@code Qudt.Units.HR}) are treated as
* non-scaled units, i.e. returned directly.
*
* @param unit the scaled unit
* @return the base unit
*/
public static Unit unscale(Unit unit) {
return unscale(unit, true, true);
}
/**
* Returns the base unit of the specified scaled {@code unit}. For example, {@code
* unscale(Qudt.Units.KiloM)} returns {@code Qudt.Units.M}. The parameter {@code
* treatKiloGmAsUnscaled} and {@code treatPrefixlessAsUnscaled} decide whether {@code
* Qudt.Units.KiloGM} and units without prefixes (such as {@code Qudt.Units.HR}), respectively,
* are treated a non-scaled units. directly.
*
* @param unit
* @param treatKiloGmAsUnscaled
* @param treatPrefixlessAsUnscaled
* @return
*/
public static Unit unscale(
Unit unit, boolean treatKiloGmAsUnscaled, boolean treatPrefixlessAsUnscaled) {
if (unit.getScalingOf().isEmpty()) {
return unit;
}
if (treatPrefixlessAsUnscaled && unit.getPrefix().isEmpty()) {
return unit;
}
if (treatKiloGmAsUnscaled && unit.getIriAbbreviated().equals("unit:KiloGM")) {
return unit;
}
return unit.getScalingOf().get();
}
/**
* Returns the list of {@link FactorUnit}s of the specified {@code 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
*/
public static List factorUnits(Unit unit) {
return simplifyFactorUnits(unit.getLeafFactorUnitsWithCumulativeExponents());
}
/**
* Perform mathematical simplification on factor units. For example, {@code N per M per M -> N
* per M^2 }
*
* @param factorUnits the factor units to simplify
* @return the simplified factor units.
*/
public static List simplifyFactorUnits(List factorUnits) {
return new ArrayList<>(
factorUnits.stream()
.collect(
Collectors.toMap(
FactorUnit::getKind,
Function.identity(),
FactorUnit::combine))
.values());
}
/**
* Return a list of {@link FactorUnit}s with the same exponents as the specified {@code
* factorUnits} but their base units as units.
*
* @param factorUnits the factor units to unscale
* @return the unscaled factor units
*/
public static List unscale(List factorUnits) {
return unscale(factorUnits, true, true);
}
public static List unscale(
List factorUnits,
boolean treatKiloGmAsUnscaled,
boolean treatPrefixlessAsUnscaled) {
return factorUnits.stream()
.map(
uf ->
FactorUnit.builder()
.unit(
unscale(
uf.getUnit(),
treatKiloGmAsUnscaled,
treatPrefixlessAsUnscaled))
.exponent(uf.getExponent())
.build())
.collect(toList());
}
/**
* Obtains units based on factor units.
*
* For example,
*
*
{@code
* Qudt.derivedUnitsFrom Map(
* FactorUnitMatchingMode.EXACT,
* Map.of(
* Qudt.Units.M, 1,
* Qudt.Units.KiloGM, 1,
* Qudt.Units.SEC, -2));
* }
*
* will yield a Set 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
*/
public static List unitsFromMap(
DerivedUnitSearchMode searchMode, Map factorUnits) {
Object[] arr = new Object[factorUnits.size() * 2];
return unitsFromUnitExponentPairs(
searchMode,
factorUnits.entrySet().stream()
.flatMap(e -> Stream.of(e.getKey(), e.getValue()))
.collect(Collectors.toList())
.toArray(arr));
}
/**
* Deprecated - use Qudt.unitsFromMap({@link DerivedUnitSearchMode}, FactorUnits)
* instead.
*
* @param searchMode
* @param factorUnits
* @return
*/
@Deprecated(since = "6.2", forRemoval = true)
public static Set derivedUnitsFromMap(
DerivedUnitSearchMode searchMode, Map factorUnits) {
return new HashSet<>(unitsFromMap(searchMode, factorUnits));
}
/**
* 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 #unitsFromMap(DerivedUnitSearchMode, Map)
*/
public static List unitsFromFactorUnits(
DerivedUnitSearchMode searchMode, List factorUnits) {
FactorUnits selection = new FactorUnits(factorUnits);
return derivedUnitListFromFactorUnits(searchMode, selection);
}
/**
* Deprecated - use Qudt.unitsFromFactorUnits({@link DerivedUnitSearchMode}, FactorUnits)
*
instead.
*
* @param searchMode
* @param factorUnits
* @return
*/
@Deprecated(since = "6.2", forRemoval = true)
public static Set derivedUnitsFromFactorUnits(
DerivedUnitSearchMode searchMode, List factorUnits) {
return new HashSet<>(unitsFromFactorUnits(searchMode, factorUnits));
}
/**
* Vararg method, must be an even number of arguments, always alternating types of Unit|String
* and Integer.
*
* @param searchMode the {@link DerivedUnitSearchMode} to use
* @param factorUnitSpec alternating (unit, exponent) pairs. The unit can be specified as {@link
* Unit} or String. In the latter case, it can be a unit IRI, a unit IRI's local name or a
* unit's label. The exponent must be an Integer.
* @return the units that match
* @see #unitsFromMap(DerivedUnitSearchMode, Map)
*/
public static List unitsFromUnitExponentPairs(
DerivedUnitSearchMode searchMode, final Object... factorUnitSpec) {
Object[] spec = new Object[factorUnitSpec.length];
for (int i = 0; i < factorUnitSpec.length; i++) {
if (i % 2 == 0 && factorUnitSpec[i] instanceof Unit) {
spec[i] = factorUnitSpec[i];
} else if (i % 2 == 0 && factorUnitSpec[i] instanceof String) {
String unitString = (String) factorUnitSpec[i];
Optional unitOpt = unit(unitString);
if (unitOpt.isEmpty()) {
unitOpt = unitFromLocalname(unitString);
}
if (unitOpt.isEmpty()) {
unitOpt = unitFromLabel(unitString);
}
if (unitOpt.isEmpty()) {
throw new NotFoundException(
String.format(
"Unable to find unit for string %s, interpreted as iri, label, or localname",
unitString));
}
spec[i] = unitOpt.get();
} else if (i % 2 == 1 && factorUnitSpec[i] instanceof Integer) {
spec[i] = factorUnitSpec[i];
} else {
throw new IllegalArgumentException(
String.format(
"Cannot handle input '%s' at 0-base position %d",
factorUnitSpec[i].toString(), i));
}
}
FactorUnits selection = FactorUnits.ofFactorUnitSpec(spec);
return derivedUnitListFromFactorUnits(searchMode, selection);
}
/**
* Deprecated - use
* Qudt.unitsFromUnitExponentPairs({@link DerivedUnitSearchMode}, Object...
instead.
*
* @param searchMode
* @param factorUnitSpec
* @return
*/
@Deprecated(since = "6.2", forRemoval = true)
public static Set derivedUnitsFromUnitExponentPairs(
DerivedUnitSearchMode searchMode, final Object... factorUnitSpec) {
return new HashSet<>(unitsFromUnitExponentPairs(searchMode, factorUnitSpec));
}
/**
* @param searchMode the {@link DerivedUnitSearchMode} to use
* @param selection the factor unit selection
* @return the units that match
* @see #unitsFromMap(DerivedUnitSearchMode, Map)
*/
private static List derivedUnitListFromFactorUnits(
DerivedUnitSearchMode searchMode, FactorUnits selection) {
Set matchingUnits = unitsByDimensionVector.get(selection.getDimensionVector());
if (matchingUnits == null) {
return List.of();
}
matchingUnits =
matchingUnits.stream()
.filter(u -> u.matches(selection))
.collect(Collectors.toSet());
if (searchMode == DerivedUnitSearchMode.ALL || matchingUnits.size() < 2) {
return matchingUnits.stream()
.sorted(bestMatchForFactorUnitsComparator(selection))
.collect(toList());
}
return matchingUnits.stream().min(bestMatchForFactorUnitsComparator(selection)).stream()
.collect(Collectors.toList());
}
public static List unitsWithSameFractionalDimensionVector(Unit unit) {
Objects.requireNonNull(unit);
FractionalDimensionVector fdv = FractionalUnits.getFractionalDimensionVector(unit);
return Qudt.units.values().stream()
.filter(
u -> {
try {
return fdv.equals(FractionalUnits.getFractionalDimensionVector(u));
} catch (Exception e) {
return false;
}
})
.collect(Collectors.toList());
}
private static Comparator bestMatchForFactorUnitsComparator(
FactorUnits requestedFactorUnits) {
FactorUnits reqNorm = requestedFactorUnits.normalize();
FactorUnits reqNum = requestedFactorUnits.numerator();
FactorUnits reqNumNorm = reqNum.normalize();
FactorUnits reqDen = requestedFactorUnits.denominator();
FactorUnits reqDenNorm = reqDen.normalize();
List reqLocalNamePossibilities =
requestedFactorUnits.generateAllLocalnamePossibilities();
return new Comparator() {
@Override
public int compare(Unit left, Unit right) {
if (left.getFactorUnits().equals(requestedFactorUnits)) {
if (!right.getFactorUnits().equals(requestedFactorUnits)) {
return -1;
}
} else {
if (right.getFactorUnits().equals(requestedFactorUnits)) {
return 1;
}
}
if (!left.getIriLocalname().contains("-")) {
if (right.getIriLocalname().contains("-")) {
return -1; // prefer a derived unit with a new name (such as W, J, N etc.)
}
} else if (!right.getIriLocalname().contains("-")) {
return 1;
}
FactorUnits leftDen = left.getFactorUnits().denominator();
FactorUnits rightDen = right.getFactorUnits().denominator();
int leftFactorsDenCnt = leftDen.expand().size();
int rightFactorsDenCnt = rightDen.expand().size();
int reqFactorsDenCnt = reqDen.expand().size();
int diffFactorsCountDen =
Math.abs(reqFactorsDenCnt - leftFactorsDenCnt)
- Math.abs(reqFactorsDenCnt - rightFactorsDenCnt);
if (diffFactorsCountDen != 0) {
return diffFactorsCountDen;
}
FactorUnits leftNum = left.getFactorUnits().numerator();
FactorUnits rightNum = right.getFactorUnits().denominator();
int leftFactorsNumCnt = leftNum.expand().size();
int rightFactorsNumCnt = rightNum.expand().size();
int reqFactorsNumCnt = reqNum.expand().size();
int diffFactorsCountNum =
Math.abs(reqFactorsNumCnt - leftFactorsNumCnt)
- Math.abs(reqFactorsNumCnt - rightFactorsNumCnt);
if (diffFactorsCountNum != 0) {
return diffFactorsCountNum;
}
int leftCnt = left.getFactorUnits().expand().size();
int rightCnt = right.getFactorUnits().expand().size();
int reqCnt = requestedFactorUnits.expand().size();
if (leftCnt == reqCnt) {
if (rightCnt != reqCnt) {
return -1;
}
} else {
if (rightCnt == reqCnt) {
return 1;
}
}
if (reqLocalNamePossibilities.contains(left.getIriLocalname())) {
if (!reqLocalNamePossibilities.contains(right.getIriLocalname())) {
return -1;
}
} else if (reqLocalNamePossibilities.contains(right.getIriLocalname())) {
return 1;
}
return left.getIriLocalname().compareTo(right.getIriLocalname());
}
};
}
private static String getIriLocalName(String iri) {
return iri.replaceAll("^.+[/|#]", "");
}
/**
* Returns the base unit of the specified {@code 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 Map.Entry with the base unit and the required scale factor
*/
public static Map.Entry scaleToBaseUnit(Unit unit) {
if (!unit.isScaled()) {
return Map.entry(unit, BigDecimal.ONE);
}
Unit baseUnit =
unit.getScalingOf()
.orElseThrow(
() ->
new IllegalStateException(
"Scaled unit has null isScalingOf() unit - that's a bug!"));
BigDecimal multiplier = unit.getConversionMultiplier(baseUnit);
return Map.entry(baseUnit, multiplier);
}
/**
* Returns a {@link QuantityKind} for the specified localname (i.e. the last element of the Unit
* IRI). For example, quantityKindFromLocalName("Width")
yields the quantityKind
* with IRI http://qudt.org/vocab/quantitykind/Width
.
*
* @param localname the local name of the IRI that identifies the requested quantityKind.
* @return the quantityKind
* @throws NotFoundException if no such quantityKind is found.
*/
public static Optional quantityKindFromLocalname(String localname) {
return quantityKind(quantityKindIriFromLocalname(localname));
}
public static QuantityKind quantityKindFromLocalnameRequired(String localname) {
return quantityKindRequired(quantityKindIriFromLocalname(localname));
}
/**
* Returns the {@link QuantityKind} identified the specified IRI. For example,
* quantityKind("http://qudt.org/vocab/quantitykind/Width")
yields {@code
* Qudt.QuantityKinds.Width};
*
* @param iri the requested quantityKind IRI
* @return the quantityKind
* @throws NotFoundException if no such quantityKind is found.
*/
public static Optional quantityKind(String iri) {
return Optional.ofNullable(quantityKinds.get(iri));
}
public static QuantityKind quantityKindRequired(String iri) {
return quantityKind(iri)
.orElseThrow(() -> new NotFoundException("QuantityKind not found: " + iri));
}
/**
* Returns the {@link QuantityKind}s associated with the specified {@link Unit}.
*
* @param unit the unit
* @return the quantity kinds
*/
public static Set quantityKinds(Unit unit) {
return unit.getQuantityKinds().stream().collect(Collectors.toUnmodifiableSet());
}
/**
* Returns the {@link QuantityKind}s associated with the specified {@link Unit}, transitively
* following their skos:broader
relationship.
*
* @param unit the unit
* @return the quantity kinds
*/
public static Set quantityKindsBroad(Unit unit) {
Set current = quantityKinds(unit);
Set result = new HashSet<>(current);
while (!current.isEmpty()) {
current =
current.stream()
.flatMap(qk -> qk.getBroaderQuantityKinds().stream())
.collect(Collectors.toSet());
result.addAll(current);
}
return result;
}
/**
* Returns a quantityKind IRI with the specified localname (even if no such quantityKind exists
* in the model).
*
* @param localname the local name of the IRI that identifies the requested quantityKind.
* @return the full IRI, possibly identifying a quantityKind
*/
public static String quantityKindIriFromLocalname(String localname) {
return NAMESPACES.quantityKind.makeIriInNamespace(localname);
}
/**
* Returns a prefix IRI with the specified localname (even if no such prefix exists in the
* model).
*
* @param localname the local name of the IRI that identifies the requested prefix.
* @return the full IRI, possibly identifying a prefix
*/
public static String prefixIriFromLocalname(String localname) {
return NAMESPACES.prefix.makeIriInNamespace(localname);
}
/**
* Returns a {@link Prefix} for the specified localname (i.e. the last element of the Unit IRI).
* For example, prefixFromLocalName("Mega")
yields the prefix with IRI
* http://qudt.org/vocab/prefix/Mega
.
*
* @param localname the local name of the IRI that identifies the requested prefix.
* @return the prefix
* @throws NotFoundException if no such prefix is found.
*/
public static Prefix prefixFromLocalnameRequired(String localname) {
return prefixRequired(prefixIriFromLocalname(localname));
}
public static Optional prefixFromLocalname(String localname) {
return prefix(prefixIriFromLocalname(localname));
}
/**
* Returns the {@link Prefix} identified the specified IRI. For example,
* prefix("http://qudt.org/vocab/prefix/Mega")
yields {@code Qudt.Prefixes.Mega};
*
* @param iri the requested prefix IRI
* @return the prefix
* @throws NotFoundException if no such prefix is found.
*/
public static Optional prefix(String iri) {
return Optional.ofNullable(prefixes.get(iri));
}
public static Prefix prefixRequired(String iri) {
return prefix(iri).orElseThrow(() -> new NotFoundException("Prefix not found: " + iri));
}
/**
* Returns a constantValue IRI with the specified localname (even if no such constantValue
* exists in the model).
*
* @param localname the local name of the IRI that identifies the requested constantValue.
* @return the full IRI, possibly identifying a constantValue
*/
public static String constantValueIriFromLocalname(String localname) {
return NAMESPACES.constant.makeIriInNamespace(localname);
}
/**
* Returns a {@link ConstantValue} for the specified localname (i.e. the last element of the
* Unit IRI). For example, constantValueFromLocalName("Mega")
yields the
* constantValue with IRI
* http://qudt.org/vocab/constantValue/Mega
.
*
* @param localname the local name of the IRI that identifies the requested constantValue.
* @return the constantValue
* @throws NotFoundException if no such constantValue is found.
*/
public static ConstantValue constantValueFromLocalnameRequired(String localname) {
return constantValueRequired(constantValueIriFromLocalname(localname));
}
public static Optional constantValueFromLocalname(String localname) {
return constantValue(constantValueIriFromLocalname(localname));
}
/**
* Returns the {@link ConstantValue} identified the specified IRI. For example,
* constantValue("http://qudt.org/vocab/constantValue/Mega")
yields {@code
* Qudt.ConstantValuees.Mega};
*
* @param iri the requested constantValue IRI
* @return the constantValue
* @throws NotFoundException if no such constantValue is found.
*/
public static Optional constantValue(String iri) {
return Optional.ofNullable(constantValues.get(iri));
}
public static ConstantValue constantValueRequired(String iri) {
return constantValue(iri)
.orElseThrow(() -> new NotFoundException("ConstantValue not found: " + iri));
}
/**
* Returns a physicalConstant IRI with the specified localname (even if no such physicalConstant
* exists in the model).
*
* @param localname the local name of the IRI that identifies the requested physicalConstant.
* @return the full IRI, possibly identifying a physicalConstant
*/
public static String physicalConstantIriFromLocalname(String localname) {
return NAMESPACES.constant.makeIriInNamespace(localname);
}
/**
* Returns a {@link PhysicalConstant} for the specified localname (i.e. the last element of the
* Unit IRI). For example, physicalConstantFromLocalName("Mega")
yields the
* physicalConstant with IRI
* http://qudt.org/vocab/physicalConstant/Mega
.
*
* @param localname the local name of the IRI that identifies the requested physicalConstant.
* @return the physicalConstant
* @throws NotFoundException if no such physicalConstant is found.
*/
public static PhysicalConstant physicalConstantFromLocalnameRequired(String localname) {
return physicalConstantRequired(physicalConstantIriFromLocalname(localname));
}
public static Optional physicalConstantFromLocalname(String localname) {
return physicalConstant(physicalConstantIriFromLocalname(localname));
}
/**
* Returns the {@link PhysicalConstant} identified the specified IRI. For example,
* physicalConstant("http://qudt.org/vocab/physicalConstant/Mega")
yields {@code
* Qudt.PhysicalConstantes.Mega};
*
* @param iri the requested physicalConstant IRI
* @return the physicalConstant
* @throws NotFoundException if no such physicalConstant is found.
*/
public static Optional physicalConstant(String iri) {
return Optional.ofNullable(physicalConstants.get(iri));
}
public static PhysicalConstant physicalConstantRequired(String iri) {
return physicalConstant(iri)
.orElseThrow(() -> new NotFoundException("PhysicalConstant not found: " + iri));
}
/**
* Returns a {@link SystemOfUnits} for the specified localname (i.e. the last element of the
* SystemOfUnits IRI). For example, systemOfUnitsFromLocalName("N-PER-M2")
yields
* the systemOfUnits with IRI
* http://qudt.org/vocab/systemOfUnits/N-PER-M2
.
*
* @param localname the local name of the IRI that identifies the requested systemOfUnits.
* @return the systemOfUnits
* @throws NotFoundException if no such systemOfUnits is found.
*/
public static Optional systemOfUnitsFromLocalname(String localname) {
return systemOfUnits(systemOfUnitsIriFromLocalname(localname));
}
public static SystemOfUnits systemOfUnitsFromLocalnameRequired(String localname) {
return systemOfUnitsRequired(systemOfUnitsIriFromLocalname(localname));
}
/**
* Returns the first systemOfUnits 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 #allSystemsOfUnits()}.stream().filter(...)
.
*
* @param label the matched label
* @return the first systemOfUnits found
*/
public static Optional systemOfUnitsFromLabel(String label) {
LabelMatcher labelMatcher = new LabelMatcher(label);
return systemsOfUnits.values().stream()
.filter(u -> u.getLabels().stream().anyMatch(labelMatcher::matches))
.findFirst();
}
public static SystemOfUnits systemOfUnitsFromLabelRequired(String label) {
return systemOfUnitsFromLabel(label)
.orElseThrow(
() ->
new NotFoundException(
"No systemOfUnits found for label '" + label + "'"));
}
/**
* Returns the {@link SystemOfUnits} identified the specified IRI. For example,
* systemOfUnits("http://qudt.org/vocab/systemOfUnits/N-PER-M2")
yields {@code
* Qudt.SystemOfUnitss.N__PER__M2};
*
* @param iri the requested systemOfUnits IRI
* @return the systemOfUnits
*/
public static Optional systemOfUnits(String iri) {
return Optional.ofNullable(systemsOfUnits.get(iri));
}
public static SystemOfUnits systemOfUnitsRequired(String iri) {
return Optional.ofNullable(systemsOfUnits.get(iri))
.orElseThrow(() -> new NotFoundException("No systemOfUnits found for Iri " + iri));
}
/**
* Returns a systemOfUnits IRI with the specified localname (even if no such systemOfUnits
* exists in the model).
*
* @param localname the local name of the IRI that identifies the requested systemOfUnits.
* @return the full IRI, possibly identifying a systemOfUnits
*/
public static String systemOfUnitsIriFromLocalname(String localname) {
return NAMESPACES.systemOfUnits.makeIriInNamespace(localname);
}
/**
* Instantiates a {@link QuantityValue}.
*
* @param value the value
* @param unitIri the Unit IRI
* @return the resulting QuantityValue
* @throws NotFoundException if no unit is found for the specified unitIri
*/
public static QuantityValue quantityValue(BigDecimal value, String unitIri) {
return new QuantityValue(value, unitRequired(unitIri));
}
/**
* Instantiates a {@link QuantityValue}.
*
* @param value the value
* @param unit the unit
* @return the new quantity value
*/
public static QuantityValue quantityValue(BigDecimal value, Unit unit) {
return new QuantityValue(value, unit);
}
/**
* Convert the specified {@link QuantityValue} from
into the specified target
* {@link Unit} toUnit
.
*
* @param from the quantity value to convert
* @param toUnit the target unit
* @return a new {@link QuantityValue} object holding the result.
* @throws InconvertibleQuantitiesException if the conversion is not possible
*/
public static QuantityValue convert(QuantityValue from, Unit toUnit)
throws InconvertibleQuantitiesException {
return convert(from, toUnit, null);
}
/**
* Convert the specified {@link QuantityValue} from
into the specified target
* {@link Unit} toUnit
.
*
* @param from the quantity value to convert
* @param toUnit the target unit
* @param quantityKind optional quantity kind for handling edge cases (temperature difference)
* @return a new {@link QuantityValue} object holding the result.
* @throws InconvertibleQuantitiesException if the conversion is not possible
*/
public static QuantityValue convert(QuantityValue from, Unit toUnit, QuantityKind quantityKind)
throws InconvertibleQuantitiesException {
return from.convert(toUnit, quantityKind);
}
/**
* Convert the specified {@link QuantityValue} from
into the {@link Unit}
* identified by the specified IRI toUnitIri
.
*
* @param from the quantity value to convert
* @param toUnitIri the IRI of the target unit
* @return a new {@link QuantityValue} object holding the result.
* @throws InconvertibleQuantitiesException if the conversion is not possible
* @throws NotFoundException if toUnitIri
does not identify a unit in the model
*/
public static QuantityValue convert(QuantityValue from, String toUnitIri)
throws InconvertibleQuantitiesException, NotFoundException {
Unit toUnit = unitRequired(toUnitIri);
return quantityValue(convert(from.getValue(), from.getUnit(), toUnit), toUnit);
}
/**
* Convert the specified fromValue
, interpreted to be in the unit identified by
* fromUnitIri
into the unit identified by the specified IRI toUnitIri
* .
*
* @param fromValue the value to convert
* @param fromUnitIri the IRI of unit the fromValue
is in
* @param toUnitIri the IRI of the target unit
* @return the resulting value
* @throws InconvertibleQuantitiesException if the conversion is not possible
* @throws NotFoundException if fromUnitIri
or toUnitIri
does not
* identify a unit in the model
*/
public static BigDecimal convert(BigDecimal fromValue, String fromUnitIri, String toUnitIri)
throws InconvertibleQuantitiesException, NotFoundException {
return convert(fromValue, unitRequired(fromUnitIri), unitRequired(toUnitIri));
}
/**
* Convert the specified fromValue
, interpreted to be in the {@link Unit}
* fromUnit
into the unit toUnit
.
*
* @param fromValue the value to convert
* @param fromUnit the unit of the value
* @param toUnit the target unit
* @return the resulting value
* @throws InconvertibleQuantitiesException if the conversion is not possible
*/
public static BigDecimal convert(BigDecimal fromValue, Unit fromUnit, Unit toUnit)
throws InconvertibleQuantitiesException {
return convert(fromValue, fromUnit, toUnit, null);
}
/**
* Convert the specified fromValue
, interpreted to be in the {@link Unit}
* fromUnit
into the unit toUnit
.
*
* @param fromValue the value to convert
* @param fromUnit the unit of the value
* @param toUnit the target unit
* @param quantityKind optional quantity kind for handling edge cases (temperature difference)
* @return the resulting value
* @throws InconvertibleQuantitiesException if the conversion is not possible
*/
public static BigDecimal convert(
BigDecimal fromValue, Unit fromUnit, Unit toUnit, QuantityKind quantityKind)
throws InconvertibleQuantitiesException {
return fromUnit.convert(fromValue, toUnit);
}
/**
* Indicates whether the two specified {@link Unit}s are convertible into each other.
*
* @param fromUnit a unit
* @param toUnit another unit
* @return true
if the units are convertible.
*/
public static boolean isConvertible(Unit fromUnit, Unit toUnit) {
return fromUnit.isConvertible(toUnit);
}
/**
* Returns a friendly message about QUDTLib.
*
* @return a greeting and some stats
*/
public static String getGreeting() {
return "This is QUDTLib-Java (https://github.com/qudtlib/qudtlib.java)\n"
+ "based on the QUDT ontology (https://qudt.org/)\n"
+ "happily providing\n"
+ "\t"
+ units.size()
+ " units\n"
+ "\t"
+ quantityKinds.size()
+ " quantityKinds\n"
+ "\t"
+ prefixes.size()
+ " prefixes\n";
}
private static class LabelMatcher {
private final String labelToMatch;
public LabelMatcher(String labelToMatch) {
this.labelToMatch = labelToMatch.replaceAll("_", " ").toUpperCase(Locale.US);
}
public boolean matches(LangString candidateLabel) {
return matches(candidateLabel.getString());
}
public boolean matches(String candiateLabel) {
return candiateLabel.toUpperCase(Locale.US).equals(labelToMatch);
}
}
static Map getPrefixesMap() {
return Collections.unmodifiableMap(prefixes);
}
static Map getQuantityKindsMap() {
return Collections.unmodifiableMap(quantityKinds);
}
static Map getUnitsMap() {
return Collections.unmodifiableMap(units);
}
static Map getSystemsOfUnitsMap() {
return Collections.unmodifiableMap(systemsOfUnits);
}
static Map getPhysicalConstantsMap() {
return Collections.unmodifiableMap(physicalConstants);
}
static Map getConstantValuesMap() {
return Collections.unmodifiableMap(constantValues);
}
/**
* Returns all {@link Unit}s in the model.
*
* @return all units
*/
public static Collection allUnits() {
return Collections.unmodifiableCollection(units.values());
}
/**
* Returns all {@link QuantityKind}s in the model.
*
* @return all quantity kinds
*/
public static Collection allQuantityKinds() {
return Collections.unmodifiableCollection(quantityKinds.values());
}
/**
* Returns all {@link Prefix}es in the model.
*
* @return all prefixes
*/
public static Collection allPrefixes() {
return Collections.unmodifiableCollection(prefixes.values());
}
/**
* Returns all {@link SystemOfUnits}s in the model.
*
* @return all systemsOfUnits
*/
public static Collection allSystemsOfUnits() {
return Collections.unmodifiableCollection(systemsOfUnits.values());
}
public static Collection allPhysicalConstant() {
return Collections.unmodifiableCollection(physicalConstants.values());
}
public static Collection allConstantValues() {
return Collections.unmodifiableCollection(constantValues.values());
}
public static Collection allUnitsOfSystem(SystemOfUnits system) {
return units.values().stream()
.filter(system::allowsUnit)
.collect(Collectors.toUnmodifiableSet());
}
/**
* Returns the first unit obtained using {@link #correspondingUnitsInSystem(Unit,
* SystemOfUnits)}.
*
* @return the unit corresponding to the specified unit in the specified systemOfUnits.
*/
public static Optional correspondingUnitInSystem(Unit unit, SystemOfUnits systemOfUnits) {
return correspondingUnitsInSystem(unit, systemOfUnits).stream().findFirst();
}
/**
* Gets units that correspond to the specified unit are allowed in the specified systemOfUnits.
* The resulting units have to
*
*
* - have the same dimension vector as the unit
*
- share at least one quantityKind with unit
*
*
* 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.
*
* If two resulting units have the same magnitude difference from the specified one, the
* following comparisons are made consecutively until a difference is found:
*
*
* - the base unit of the specified system is ranked first
*
- conversion offset closer to the one of the specified unit is ranked first
*
- the unscaled unit is ranked first
*
- the unit that has a symbol is ranked first
*
- the unit with more quantityKinds is ranked first
*
- the units are ranked by their IRIs lexicographically
*
*
* 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
*/
public static List correspondingUnitsInSystem(Unit unit, SystemOfUnits systemOfUnits) {
if (systemOfUnits.allowsUnit(unit)) {
return List.of(unit);
}
List elegible =
Qudt.getUnitsMap().values().stream()
.filter(u -> systemOfUnits.allowsUnit(u))
.filter(u -> u.getDimensionVectorIri().equals(unit.getDimensionVectorIri()))
.filter(u -> !u.equals(unit))
.collect(Collectors.toList());
if (elegible.size() == 1) {
return elegible;
}
List candidates = new ArrayList(elegible);
// get the unit that is closest in magnitude (conversionFactor)
// recursively check for factor units
candidates = new ArrayList(elegible);
candidates.removeIf(
u ->
!u.getQuantityKinds().stream()
.anyMatch(q -> unit.getQuantityKinds().contains(q)));
if (candidates.size() == 1) {
return candidates;
}
candidates.sort(
(Unit l, Unit r) -> {
double scaleDiffL = Math.abs(scaleDifference(l, unit));
double scaleDiffR = Math.abs(scaleDifference(r, unit));
double diff = Math.signum(scaleDiffL - scaleDiffR);
if (diff != 0) {
return (int) diff;
}
// tie breaker: base unit ranked before non-base unit
int cmp =
Boolean.compare(
systemOfUnits.hasBaseUnit(r), systemOfUnits.hasBaseUnit(l));
if (cmp != 0) {
return cmp;
}
// tie breaker: closer offset
double offsetDiffL = Math.abs(offsetDifference(l, unit));
double offsetDiffR = Math.abs(offsetDifference(r, unit));
cmp = (int) Math.signum(offsetDiffL - offsetDiffR);
if (cmp != 0) {
return cmp;
}
// tie breaker: perfer unit that is not scaled
cmp = Boolean.compare(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 = Boolean.compare(r.getSymbol().isPresent(), l.getSymbol().isPresent());
if (cmp != 0) {
return cmp;
}
// tie breaker: prefer unit with more quantity kinds (it's less specific)
cmp = Integer.compare(l.getQuantityKinds().size(), r.getQuantityKinds().size());
if (cmp != 0) {
return cmp;
}
// tie breaker: lexicographically compare iris.
return l.getIri().compareTo(r.getIri());
});
return candidates;
}
private static double scaleDifference(Unit u1, Unit u2) {
BigDecimal u1Log10 =
Log10BigDecimal.log10(u1.getConversionMultiplier().orElse(BigDecimal.ONE));
BigDecimal u2Log10 =
Log10BigDecimal.log10(u2.getConversionMultiplier().orElse(BigDecimal.ONE));
return u1Log10.doubleValue() - u2Log10.doubleValue();
}
private static double offsetDifference(Unit u1, Unit u2) {
BigDecimal u1Log10 = u1.getConversionOffset().orElse(BigDecimal.ZERO).abs();
if (u1Log10.compareTo(BigDecimal.ZERO) > 0) {
u1Log10 = Log10BigDecimal.log10(u1Log10);
}
BigDecimal u2Log10 = u2.getConversionOffset().orElse(BigDecimal.ZERO).abs();
if (u2Log10.compareTo(BigDecimal.ZERO) > 0) {
u2Log10 = Log10BigDecimal.log10(u2Log10);
}
return u1Log10.doubleValue() - u2Log10.doubleValue();
}
public static void addQuantityKind(QuantityKind quantityKind) {
quantityKinds.put(quantityKind.getIri(), quantityKind);
}
public static void addUnit(Unit unit) {
units.put(unit.getIri(), unit);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy