All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 * *
    *
  1. have the same dimension vector as the unit *
  2. 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: * *

    *
  1. the base unit of the specified system is ranked first *
  2. conversion offset closer to the one of the specified unit is ranked first *
  3. the unscaled unit is ranked first *
  4. the unit that has a symbol is ranked first *
  5. the unit with more quantityKinds is ranked first *
  6. 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