
io.github.qudtlib.tools.contributions.archive.CheckConversionMultipliers Maven / Gradle / Ivy
package io.github.qudtlib.tools.contributions.archive;
import static io.github.qudtlib.math.BigDec.isRelativeDifferenceGreaterThan;
import static io.github.qudtlib.model.Units.*;
import static java.util.stream.Collectors.*;
import io.github.qudtlib.Qudt;
import io.github.qudtlib.model.*;
import io.github.qudtlib.tools.contribute.QudtEntityGenerator;
import io.github.qudtlib.tools.contribute.Tool;
import io.github.qudtlib.tools.contribute.support.FormattingHelper;
import io.github.qudtlib.tools.contribute.support.IndentedOutputStream;
import io.github.qudtlib.tools.contribute.support.SelectionHelper;
import io.github.qudtlib.tools.contribute.support.tree.Node;
import io.github.qudtlib.tools.contribute.support.tree.QuantityKindTree;
import io.github.qudtlib.tools.contribute.support.tree.UnitTree;
import io.github.qudtlib.vocab.QUDT;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.impl.TreeModel;
public class CheckConversionMultipliers {
private static final String NO_DIM_VECTOR = "[no dimension vector]";
private static class UnitConversionFactor {
private Unit unit;
private BigDecimal factor;
public UnitConversionFactor(Unit unit, BigDecimal factor) {
this.unit = unit;
this.factor = factor;
}
public boolean conversionFailed() {
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UnitConversionFactor)) return false;
UnitConversionFactor that = (UnitConversionFactor) o;
return Objects.equals(unit, that.unit) && Objects.equals(factor, that.factor);
}
@Override
public int hashCode() {
return Objects.hash(unit, factor);
}
}
private static class UnitConversionFailed extends UnitConversionFactor {
private String errormessage;
public UnitConversionFailed(Unit unit, BigDecimal factor, String errormessage) {
super(unit, factor);
this.errormessage = errormessage;
}
public boolean conversionFailed() {
return true;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof UnitConversionFailed)) return false;
if (!super.equals(o)) return false;
UnitConversionFailed that = (UnitConversionFailed) o;
return Objects.equals(errormessage, that.errormessage);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), errormessage);
}
}
public static void main(String[] args) {
Model statementsToAdd = new TreeModel();
Model statementsToDelete = new TreeModel();
ByteArrayOutputStream ttlOut = new ByteArrayOutputStream();
PrintStream ttlPrintStream = new PrintStream(ttlOut);
QudtEntityGenerator entityGenerator = new QudtEntityGenerator();
entityGenerator.unitOfWork(
tool -> {
List dimVectors =
findAllDimensionVectors().stream().sorted().collect(toList());
for (String dimVector : dimVectors) {
analyzeUnitsWithDimVector(
dimVector, ttlPrintStream, statementsToDelete, tool);
}
System.out.format("statements to add: %d\n", statementsToAdd.size());
System.out.format("statements to delete: %d\n", statementsToDelete.size());
System.out.println("STATEMENTS TO ADD");
tool.writeOut(statementsToAdd, System.out, s -> true);
try {
ttlPrintStream.close();
System.out.println(ttlOut.toString("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
System.out.println("STATEMENTS TO DELETE");
System.out.println("PREFIX qudt: ");
System.out.println("DELETE { ?u qudt:conversionMultiplier ?m } ");
System.out.println("WHERE { ?u qudt:conversionMultiplier ?m .");
System.out.println("VALUES ?u {");
System.out.println(
statementsToDelete.stream()
.map(s -> s.getSubject())
.filter(s -> s.isIRI())
.map(s -> "<" + ((IRI) s).toString() + ">")
.collect(joining("\n\t")));
System.out.println("}}");
});
}
private static void analyzeUnitsWithDimVector(
String dimVector, PrintStream ttlPrintStream, Model statementsToDelete, Tool tool) {
boolean writeToStdout = false;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (PrintStream output = new PrintStream(outputStream)) {
output.println(
"you may want to look for this pattern in the result: \\[no conversionMultiplier\\].+(\\n.+)*\\[no conversionMultiplier\\] - it might reveal problems");
output.println(String.format("\n-------- ANALYSIS FOR %s --------", dimVector));
StringBuilder stringBuilder = new StringBuilder();
Set qks = SelectionHelper.getQuantityKindsByDimensionVector(dimVector);
SelectionHelper.getUnitsByDimensionVector(dimVector).stream()
.map(Unit::getQuantityKinds)
.flatMap(Collection::stream)
.forEach(qks::add);
qks.remove(Qudt.QuantityKinds.Unknown);
if (qks.isEmpty()) {
output.println(
"No quantity kinds or only qk:Unknown associated with this dimension vector");
} else {
QuantityKindTree.makeAndFormatQuantityKindAndUnitTree(
qks, node -> formatTreeNode(node), stringBuilder);
output.println("Quantity kinds and units with dimension vector " + dimVector);
output.print(stringBuilder.toString());
}
Set baseUnits = identifyBaseUnitsForDimVector(dimVector);
if (DimensionVector.of(dimVector).map(DimensionVector::isDimensionless).orElse(false)) {
output.println("skipping dimensionless units (too many for this check)");
return;
}
output.println(
String.format(
"Identifying bases, i.e. units with Dimension Vector %s and conversionMultiplier 1.0 ...",
QudtNamespaces.dimensionVector.abbreviate(dimVector)));
List bases =
baseUnits.stream()
.sorted(Comparator.comparing(u -> u.getIri()))
.collect(toList());
if (bases.isEmpty()) {
output.println("No bases found");
} else {
output.println("Base candidates: " + unitCollectionToString(bases));
}
UnitConversionFactor[][] conversionMatrix = fullUnitConversionMatrix(bases);
List correctBases = findCorrectBases(bases, conversionMatrix);
if (!correctBases.isEmpty()) {
output.println("correct bases: " + unitCollectionToString(correctBases));
}
List incorrectBases =
bases.stream().filter(u -> !correctBases.contains(u)).collect(toList());
if (!incorrectBases.isEmpty()) {
output.println("incorrect bases " + unitCollectionToString(incorrectBases));
}
Optional bestBaseOpt = findBestBase(correctBases);
if (bestBaseOpt.isPresent()) {
output.println("best base unit: " + bestBaseOpt.get().getIriAbbreviated());
}
output.println(
String.format(
"Identifying non-base units, i.e. units with conversionMultiplier != 1.0 or without one ...",
QudtNamespaces.dimensionVector.abbreviate(dimVector)));
List nonBaseUnits = collectNonBaseUnits(dimVector, bases, incorrectBases);
if (!nonBaseUnits.isEmpty()) {
output.println("None-base units:" + unitCollectionToString(nonBaseUnits));
Set conversionImpossible = new HashSet<>();
Set multiplierSeemsWrong = new HashSet<>();
Set missingMultiplierComputed = new HashSet<>();
Set missingScalingOfAdded = new HashSet<>();
ValueFactory vf = SimpleValueFactory.getInstance();
PrintStream commentsForTTl =
new IndentedOutputStream(ttlPrintStream, " # ").printStream();
for (Unit nonBaseUnit : nonBaseUnits) {
if (!nonBaseUnit.isScaled()
&& !nonBaseUnit.hasFactorUnits()
&& !Qudt.SystemsOfUnits.SI.hasBaseUnit(nonBaseUnit)
&& nonBaseUnit != GM
&& nonBaseUnit.getConversionMultiplier().isPresent()) {
missingScalingOfAdded.add(nonBaseUnit);
if (bases.isEmpty()) {
output.println(
"Found unit without isScalingOf or factorUnit, but no base to link it to: "
+ nonBaseUnit.getIriAbbreviated());
} else {
if (bestBaseOpt.isPresent()) {
ttlPrintStream.format(
"%s %s %s .\n",
nonBaseUnit.getIriAbbreviated(),
QudtNamespaces.qudt.abbreviate(QUDT.isScalingOf.toString()),
bestBaseOpt.get().getIriAbbreviated());
} else {
output.println(
"Found unit without isScalingOf or factorUnit, but no base to link it to after filtering. Candidates were: "
+ unitCollectionToString(bases));
}
}
}
if (bestBaseOpt.isEmpty()) {
output.println("No unit with multiplier 1.0 qualifies as base unit");
if (nonBaseUnit.hasFactorUnits()) {
BigDecimal factor =
nonBaseUnit.getFactorUnits().getConversionMultiplier();
if (nonBaseUnit.getConversionMultiplier().isEmpty()) {
missingMultiplierComputed.add(nonBaseUnit);
commentsForTTl.println(
String.format(
"%s has no conversionMultiplier, but we've found out that it is %s",
nonBaseUnit.getIriAbbreviated(),
factor.toString()));
printConversionMultiplierTriple(
ttlPrintStream, commentsForTTl, nonBaseUnit, factor);
} else {
if (isRelativeDifferenceGreaterThan(
factor,
nonBaseUnit.getConversionMultiplier().get(),
new BigDecimal("0.001"))) {
multiplierSeemsWrong.add(nonBaseUnit);
statementsToDelete.add(
vf.createIRI(nonBaseUnit.getIri()),
QUDT.conversionMultiplier,
vf.createLiteral(
nonBaseUnit.getConversionMultiplier().get()));
commentsForTTl.format(
"%s has conversionMultiplier %s, but we've calculated it as %s (relative diff: %s - %s)\n",
nonBaseUnit.getIriAbbreviated(),
nonBaseUnit.getConversionMultiplier().get().toString(),
factor.toString(),
relativeValueDifference(
nonBaseUnit
.getConversionMultiplier()
.get(),
factor)
.toString(),
greaterThan(
relativeValueDifference(
factor,
nonBaseUnit
.getConversionMultiplier()
.get()),
new BigDecimal("0.1"))
? "big difference"
: "small difference");
printConversionMultiplierTriple(
ttlPrintStream, commentsForTTl, nonBaseUnit, factor);
}
}
}
} else {
Unit base = bestBaseOpt.get();
try {
BigDecimal conversionFactorToBase =
nonBaseUnit.getFactorUnits().conversionFactor(base);
BigDecimal conversionFactorOfFactorUnits =
nonBaseUnit.getFactorUnits().getConversionMultiplier();
if (isRelativeDifferenceGreaterThan(
conversionFactorToBase,
conversionFactorOfFactorUnits,
new BigDecimal("0.001"))) {
commentsForTTl.println(
String.format(
"%s converts to base %s as 1 %s = %s %s, but the conversionMultiplier calculated based on its factorUnits is %s",
nonBaseUnit.getIriAbbreviated(),
base.getIriAbbreviated(),
nonBaseUnit.toString(),
conversionFactorToBase.toString(),
base.toString(),
conversionFactorOfFactorUnits.toString()));
}
if (nonBaseUnit.getConversionMultiplier().isEmpty()) {
commentsForTTl.println(
String.format(
"%s has no conversionMultiplier, but we've found out that 1 %s = %s %s",
nonBaseUnit.getIriAbbreviated(),
nonBaseUnit.toString(),
conversionFactorToBase.toString(),
base.toString()));
printConversionMultiplierTriple(
ttlPrintStream,
commentsForTTl,
nonBaseUnit,
conversionFactorToBase);
missingMultiplierComputed.add(nonBaseUnit);
} else {
if (isRelativeDifferenceGreaterThan(
nonBaseUnit.getConversionMultiplier().get(),
conversionFactorOfFactorUnits,
new BigDecimal("0.001"))
|| isRelativeDifferenceGreaterThan(
conversionFactorToBase,
conversionFactorOfFactorUnits,
new BigDecimal("0.001"))) {
multiplierSeemsWrong.add(nonBaseUnit);
statementsToDelete.add(
vf.createIRI(nonBaseUnit.getIri()),
QUDT.conversionMultiplier,
vf.createLiteral(
nonBaseUnit.getConversionMultiplier().get()));
commentsForTTl.format(
"%s has conversionMultiplier %s, but we've found out that 1 %s = %s %s (relative diff: %s - %s)\n",
nonBaseUnit.getIriAbbreviated(),
nonBaseUnit.getConversionMultiplier().get().toString(),
nonBaseUnit.toString(),
conversionFactorToBase.toString(),
base.toString(),
relativeValueDifference(
nonBaseUnit
.getConversionMultiplier()
.get(),
conversionFactorToBase)
.toString(),
greaterThan(
relativeValueDifference(
conversionFactorToBase,
nonBaseUnit
.getConversionMultiplier()
.get()),
new BigDecimal("0.1"))
? "big difference"
: "small difference");
printConversionMultiplierTriple(
ttlPrintStream,
commentsForTTl,
nonBaseUnit,
conversionFactorToBase);
}
}
} catch (Exception e) {
output.println(
String.format(
"cannot convert %s to %s - %s",
nonBaseUnit.getIriAbbreviated(),
base.getIriAbbreviated(),
e.getMessage()));
conversionImpossible.add(nonBaseUnit);
}
}
}
output.println("RESULT OF ANALYSIS:");
boolean allCorrect =
conversionImpossible.isEmpty()
&& multiplierSeemsWrong.isEmpty()
&& missingMultiplierComputed.isEmpty()
&& missingScalingOfAdded.isEmpty();
if (allCorrect) {
output.println("all non-base units seem correct");
} else {
writeToStdout = true;
if (!conversionImpossible.isEmpty()) {
output.println(
" - conversion impossible: "
+ unitCollectionToString(conversionImpossible));
}
if (!multiplierSeemsWrong.isEmpty()) {
output.println(
" - multiplier seems off: "
+ unitCollectionToString(multiplierSeemsWrong));
}
if (!missingMultiplierComputed.isEmpty()) {
output.println(
" - missing multiplier computed: "
+ unitCollectionToString(missingMultiplierComputed));
}
if (!missingScalingOfAdded.isEmpty()) {
output.println(
" - missing scalingOf (possibly added): "
+ unitCollectionToString(missingScalingOfAdded));
}
}
} else {
output.println("No non-base units found");
}
if (writeToStdout) {
try {
System.out.println(outputStream.toString("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
}
private static void printConversionMultiplierTriple(
PrintStream printStream,
PrintStream commentStream,
Unit nonBaseUnit,
BigDecimal conversionFactorToBase) {
String factorUnitTree =
UnitTree.makeFactorUnitTreeShowingConversionMultipliers(nonBaseUnit);
commentStream.print(factorUnitTree);
printStream.format(
"%s %s %s .\n\n",
nonBaseUnit.getIriAbbreviated(),
QudtNamespaces.qudt.abbreviate(QUDT.conversionMultiplier.toString()),
FormattingHelper.format(conversionFactorToBase));
}
private static Optional findBestBase(List bases) {
return bases.stream()
.filter(u -> Qudt.SystemsOfUnits.SI.allowsUnit(u))
.sorted(Comparator.comparing(u -> countFactorUnits(u)))
.findFirst();
}
private static int countFactorUnits(Unit unit) {
if (unit.hasFactorUnits()) {
return 1
+ unit.getFactorUnits().getFactorUnits().stream()
.mapToInt(u -> countFactorUnits(u.getUnit()))
.sum();
} else {
return 1;
}
}
private static String formatTreeNode(Node node) {
Object data = node.getData();
if (data instanceof Unit) {
Unit u = (Unit) data;
return String.format(
"%s (%s) %s",
u.getIriAbbreviated(),
u.getConversionMultiplier()
.map(m -> m.toString())
.orElse("[NO CONVERSION MULTIPLIER]"),
u.isDeprecated() ? "[deprecated]" : "");
} else if (data instanceof QuantityKind) {
QuantityKind qk = (QuantityKind) data;
return QudtNamespaces.quantityKind.abbreviate(qk.getIri());
} else {
return data.toString();
}
}
private static String unitCollectionToString(Collection bases) {
return bases.stream().map(Unit::getIriAbbreviated).collect(joining(", "));
}
private static List collectNonBaseUnits(
String dimVector, List bases, List incorrectBases) {
List otherUnits =
Qudt.allUnits().stream()
.filter(u -> isAcceptableUnit(u))
.filter(u -> u.getDimensionVectorIri().orElse("[none]").equals(dimVector))
.filter(
u ->
u.getConversionMultiplier().isEmpty()
|| u.getConversionMultiplier()
.get()
.compareTo(BigDecimal.ZERO)
!= 0)
.filter(u -> !bases.contains(u))
.collect(toList());
otherUnits.addAll(incorrectBases);
return otherUnits;
}
private static final List inacceptableUnits = List.of(DeciB, B);
private static boolean isAcceptableUnit(Unit u) {
return !u.isDeprecated()
&& !u.getFactorUnits().normalize().getFactorUnits().stream()
.anyMatch(fu -> inacceptableUnits.contains(fu.getUnit()));
}
private static UnitConversionFactor[][] fullUnitConversionMatrix(List bases) {
int nBases = bases.size();
UnitConversionFactor[][] conversions = new UnitConversionFactor[nBases][nBases];
for (int i = 0; i < nBases; i++) {
Unit from = bases.get(i);
Set unitFactors = new HashSet<>();
for (int j = 0; j < nBases; j++) {
Unit to = bases.get(j);
UnitConversionFactor unitFactor;
try {
if (i == j) {
unitFactor =
new UnitConversionFactor(
to, from.getFactorUnits().conversionFactor(to));
} else {
BigDecimal factor = from.getFactorUnits().conversionFactor(to);
unitFactor = new UnitConversionFactor(to, factor);
}
} catch (Exception e) {
unitFactor = new UnitConversionFailed(to, BigDecimal.ZERO, e.getMessage());
}
conversions[i][j] = unitFactor;
}
}
return conversions;
}
private static Set identifyBaseUnitsForDimVector(String dimVector) {
Set baseUnits =
Qudt.allUnits().stream()
.filter(u -> isAcceptableUnit(u))
.filter(
u ->
u.getDimensionVectorIri()
.orElse(NO_DIM_VECTOR)
.equals(dimVector))
.filter(
u ->
u.getConversionMultiplier()
.map(m -> m.compareTo(BigDecimal.ONE) == 0)
.orElse(false))
.collect(toSet());
return baseUnits;
}
private static Set findAllDimensionVectors() {
return Stream.concat(
Qudt.allUnits().stream()
.map(q -> q.getDimensionVectorIri().orElse(NO_DIM_VECTOR)),
Qudt.allQuantityKinds().stream()
.map(q -> q.getDimensionVectorIri().orElse(NO_DIM_VECTOR)))
.collect(toSet());
}
private static List findCorrectBases(
List bases, UnitConversionFactor[][] conversions) {
int nBases = bases.size();
List incorrectBaseIndices = new ArrayList<>();
for (int rounds = 0; rounds < nBases; rounds++) {
int[] numCorrectConversions = new int[nBases];
for (int i = 0; i < nBases; i++) {
if (incorrectBaseIndices.contains(i)) {
continue;
}
int numCorrectConversionsI = 0;
for (int j = 0; j < nBases; j++) {
if (incorrectBaseIndices.contains(j)) {
continue;
}
if (conversions[i][j].factor.compareTo(BigDecimal.ONE) == 0) {
numCorrectConversionsI += 1;
}
}
numCorrectConversions[i] = numCorrectConversionsI;
}
int worstUnit = findMinIndex(numCorrectConversions, incorrectBaseIndices);
int numCorrectInWorst = numCorrectConversions[worstUnit];
if (numCorrectInWorst >= nBases - incorrectBaseIndices.size()) {
return IntStream.range(0, nBases)
.filter(i -> !incorrectBaseIndices.contains(i))
.mapToObj(bases::get)
.collect(toList());
}
incorrectBaseIndices.add(worstUnit);
}
return List.of();
}
private static int findMinIndex(int[] values, List ignoreIndices) {
int minVal = Integer.MAX_VALUE;
int minIndex = -1;
for (int i = 0; i < values.length; i++) {
if (ignoreIndices.contains(i)) {
continue;
}
if (values[i] < minVal) {
minIndex = i;
minVal = values[i];
}
}
return minIndex;
}
/**
* Returns the difference between the two values in relation to the value of their mean
*
* @return
*/
static BigDecimal relativeValueDifference(BigDecimal left, BigDecimal right) {
Objects.requireNonNull(left);
Objects.requireNonNull(right);
BigDecimal mean =
left.add(right)
.divide(BigDecimal.valueOf(2), MathContext.DECIMAL128)
.abs(MathContext.DECIMAL128);
BigDecimal diff =
left.abs(MathContext.DECIMAL128)
.subtract(right.abs(MathContext.DECIMAL128))
.abs(MathContext.DECIMAL128);
return diff.divide(mean, MathContext.DECIMAL128).abs();
}
static boolean greaterThan(BigDecimal left, BigDecimal right) {
Objects.requireNonNull(left);
Objects.requireNonNull(right);
return left.subtract(right).signum() > 0;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy