From 4d0fd06a537d5f01c33ad623183f8c3fbbd873b0 Mon Sep 17 00:00:00 2001 From: shawnhatch Date: Tue, 8 Oct 2024 13:56:19 -0400 Subject: [PATCH] Added units of measure capability --- .../hhs/aspr/ms/util/measures/BaseUnit.java | 232 +++ .../aspr/ms/util/measures/ComposedUnit.java | 521 +++++++ .../hhs/aspr/ms/util/measures/Constant.java | 197 +++ .../hhs/aspr/ms/util/measures/Measure.java | 90 ++ .../aspr/ms/util/measures/MeasuresError.java | 37 + .../hhs/aspr/ms/util/measures/Quantity.java | 565 +++++++ .../ms/util/measures/StandardMeasures.java | 102 ++ .../ms/util/measures/TemperatureScale.java | 240 +++ .../aspr/ms/util/measures/AT_BaseUnit.java | 286 ++++ .../ms/util/measures/AT_ComposedUnit.java | 832 ++++++++++ .../aspr/ms/util/measures/AT_Constant.java | 416 +++++ .../hhs/aspr/ms/util/measures/AT_Measure.java | 118 ++ .../ms/util/measures/AT_MeasuresError.java | 28 + .../aspr/ms/util/measures/AT_Quantity.java | 1346 +++++++++++++++++ .../ms/util/measures/AT_StandardMeasures.java | 501 ++++++ .../ms/util/measures/AT_TemperatureScale.java | 92 ++ 16 files changed, 5603 insertions(+) create mode 100644 src/main/java/gov/hhs/aspr/ms/util/measures/BaseUnit.java create mode 100644 src/main/java/gov/hhs/aspr/ms/util/measures/ComposedUnit.java create mode 100644 src/main/java/gov/hhs/aspr/ms/util/measures/Constant.java create mode 100644 src/main/java/gov/hhs/aspr/ms/util/measures/Measure.java create mode 100644 src/main/java/gov/hhs/aspr/ms/util/measures/MeasuresError.java create mode 100644 src/main/java/gov/hhs/aspr/ms/util/measures/Quantity.java create mode 100644 src/main/java/gov/hhs/aspr/ms/util/measures/StandardMeasures.java create mode 100644 src/main/java/gov/hhs/aspr/ms/util/measures/TemperatureScale.java create mode 100644 src/test/java/gov/hhs/aspr/ms/util/measures/AT_BaseUnit.java create mode 100644 src/test/java/gov/hhs/aspr/ms/util/measures/AT_ComposedUnit.java create mode 100644 src/test/java/gov/hhs/aspr/ms/util/measures/AT_Constant.java create mode 100644 src/test/java/gov/hhs/aspr/ms/util/measures/AT_Measure.java create mode 100644 src/test/java/gov/hhs/aspr/ms/util/measures/AT_MeasuresError.java create mode 100644 src/test/java/gov/hhs/aspr/ms/util/measures/AT_Quantity.java create mode 100644 src/test/java/gov/hhs/aspr/ms/util/measures/AT_StandardMeasures.java create mode 100644 src/test/java/gov/hhs/aspr/ms/util/measures/AT_TemperatureScale.java diff --git a/src/main/java/gov/hhs/aspr/ms/util/measures/BaseUnit.java b/src/main/java/gov/hhs/aspr/ms/util/measures/BaseUnit.java new file mode 100644 index 0000000..4f3f663 --- /dev/null +++ b/src/main/java/gov/hhs/aspr/ms/util/measures/BaseUnit.java @@ -0,0 +1,232 @@ +package gov.hhs.aspr.ms.util.measures; + +import gov.hhs.aspr.ms.util.errors.ContractException; + +/** + * A BaseUnit represents a unit of measure for some particular measure. For + * example, meter, foot, mile all measure length and second, minute and hour all + * measure time. + */ +public final class BaseUnit { + private final String name; + private final double value; + private final String shortName; + private final Measure measure; + + /** + * Creates a unit from another unit and applies a scalar to that unit. The name + * and short name are used in labeling. + * + * @throws ContractException + * + */ + public BaseUnit(BaseUnit baseUnit, double scalar, String name, String shortName) { + if (name == null) { + throw new ContractException(MeasuresError.NULL_UNIT_NAME); + } + + if (name.isBlank()) { + throw new ContractException(MeasuresError.BLANK_UNIT_NAME); + } + + if (shortName == null) { + throw new ContractException(MeasuresError.NULL_UNIT_NAME); + } + + if (shortName.isBlank()) { + throw new ContractException(MeasuresError.BLANK_UNIT_NAME); + } + + if (baseUnit == null) { + throw new ContractException(MeasuresError.NULL_UNIT); + } + + if (scalar <= 0) { + throw new ContractException(MeasuresError.NON_POSITIVE_SCALAR_VALUE); + } + + this.measure = baseUnit.getMeasure(); + this.name = name; + this.shortName = shortName; + this.value = baseUnit.getValue() * scalar; + } + + /** + * Creates a unit based on the give measure to that unit. The name and short + * name are used in labeling. The unit's value will be 1. + * + * @throws ContractException + * + */ + public BaseUnit(Measure measure, String name, String shortName) { + if (name == null) { + throw new ContractException(MeasuresError.NULL_UNIT_NAME); + } + + if (name.isBlank()) { + throw new ContractException(MeasuresError.BLANK_UNIT_NAME); + } + + if (shortName == null) { + throw new ContractException(MeasuresError.NULL_UNIT_NAME); + } + + if (shortName.isBlank()) { + throw new ContractException(MeasuresError.BLANK_UNIT_NAME); + } + + if (measure == null) { + throw new ContractException(MeasuresError.NULL_MEASURE); + } + + this.measure = measure; + this.name = name; + this.shortName = shortName; + this.value = 1; + } + + /** + * Returns the name of this unit + */ + public String getLongName() { + return name; + } + + /** + * Returns the short name of this unit + */ + public String getShortName() { + return shortName; + } + + /** + * Returns the measure of this unit + */ + public Measure getMeasure() { + return measure; + } + + /** + * Returns the value of this unit. Typically, one unit for each measure has a + * value of 1 and is the basis for all other units sharing that measure. + */ + public double getValue() { + return value; + } + + /** + * Boilerplate implementation consistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((measure == null) ? 0 : measure.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((shortName == null) ? 0 : shortName.hashCode()); + long temp; + temp = Double.doubleToLongBits(value); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * Two units are equal if and only if the have equal measures, equal names, + * equal short names and equal values. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof BaseUnit)) { + return false; + } + BaseUnit other = (BaseUnit) obj; + if (measure == null) { + if (other.measure != null) { + return false; + } + } else if (!measure.equals(other.measure)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (shortName == null) { + if (other.shortName != null) { + return false; + } + } else if (!shortName.equals(other.shortName)) { + return false; + } + if (Double.doubleToLongBits(value) != Double.doubleToLongBits(other.value)) { + return false; + } + return true; + } + + /** + * Returns the string representation of this unit in the form: + * + * BaseUnit [measure=Measure [name=length], value=1000.0, name=kilometer, + * shortName=km] + * + */ + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("BaseUnit [measure="); + builder.append(measure); + builder.append(", value="); + builder.append(value); + builder.append(", name="); + builder.append(name); + builder.append(", shortName="); + builder.append(shortName); + builder.append("]"); + return builder.toString(); + } + + /** + * Returns the ComposedUnit formed from this base unit. + */ + public ComposedUnit asComposedUnit() { + return ComposedUnit.builder().setBaseUnit(this, 1).build(); + + } + +} diff --git a/src/main/java/gov/hhs/aspr/ms/util/measures/ComposedUnit.java b/src/main/java/gov/hhs/aspr/ms/util/measures/ComposedUnit.java new file mode 100644 index 0000000..954f268 --- /dev/null +++ b/src/main/java/gov/hhs/aspr/ms/util/measures/ComposedUnit.java @@ -0,0 +1,521 @@ +package gov.hhs.aspr.ms.util.measures; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.util.errors.ContractException; + +/** + * Represents the composition of multiple base units raised to non-zero integer + * powers. + */ +public final class ComposedUnit { + /* + * Internal class for mapping a measure to a base unit raised to a non-zero + * integer power. + */ + private static class UnitPower { + private final BaseUnit baseUnit; + private final Integer power; + + public UnitPower(BaseUnit baseUnit, Integer power) { + super(); + this.baseUnit = baseUnit; + this.power = power; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((power == null) ? 0 : power.hashCode()); + result = prime * result + ((baseUnit == null) ? 0 : baseUnit.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof UnitPower)) { + return false; + } + UnitPower other = (UnitPower) obj; + if (power == null) { + if (other.power != null) { + return false; + } + } else if (!power.equals(other.power)) { + return false; + } + if (baseUnit == null) { + if (other.baseUnit != null) { + return false; + } + } else if (!baseUnit.equals(other.baseUnit)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("UnitPower [baseUnit="); + builder.append(baseUnit); + builder.append(", power="); + builder.append(power); + builder.append("]"); + return builder.toString(); + } + + } + + private final double value; + private final String longName; + private final String shortName; + private final Map measures = new LinkedHashMap<>(); + + private static class Data { + private Map measures = new TreeMap<>((m1, m2) -> m1.getName().compareTo(m2.getName())); + private String shortName; + private String longName; + } + + private ComposedUnit(Data data) { + shortName = data.shortName; + longName = data.longName; + measures.putAll(data.measures); + + double product = 1; + for (Measure measure : measures.keySet()) { + UnitPower unitPower = measures.get(measure); + product *= FastMath.pow(unitPower.baseUnit.getValue(), unitPower.power); + } + value = product; + } + + /** + * Returns the normalized value that is the product of one and its base unit's + * values raised to their associated powers. + */ + public double getValue() { + return value; + } + + /** + * Returns the power for the given measure in this composed unit. Powers that + * are either not set or set to zero in the builder are not present. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_MEASURE} if the + * measure is null
  • + *
+ */ + public Optional getPower(Measure measure) { + if (measure == null) { + throw new ContractException(MeasuresError.NULL_MEASURE); + } + UnitPower unitPower = measures.get(measure); + if (unitPower != null) { + return Optional.of(unitPower.power); + } + return Optional.ofNullable(null); + } + + /** + * Returns the base units for this composed unit ordered by measure name. + * + */ + public List getBaseUnits() { + List result = new ArrayList<>(); + for (UnitPower unitPower : measures.values()) { + result.add(unitPower.baseUnit); + } + return result; + } + + /** + * Returns the b for the given measure in this composed unit. Powers that are + * either not set or set to zero in the builder are not present. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_MEASURE} if the + * measure is null
  • + *
+ */ + public Optional getBaseUnit(Measure measure) { + if (measure == null) { + throw new ContractException(MeasuresError.NULL_MEASURE); + } + UnitPower unitPower = measures.get(measure); + if (unitPower != null) { + return Optional.of(unitPower.baseUnit); + } + return Optional.ofNullable(null); + } + + /** + * Returns a new instance of the Builder class + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Fluent builder class for ComposedUnit + */ + public static class Builder { + private Data data = new Data(); + + private Builder() { + } + + /** + * Sets the unit for the unit's measure. If the power is non-zero, the base unit + * and power replace the current unit and power for the unit's measure. If the + * power is zero, then the current unit for the unit's measure is removed. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_UNIT} if the + * base unit is null
  • + *
+ */ + public Builder setBaseUnit(BaseUnit baseUnit, int power) { + if (baseUnit == null) { + throw new ContractException(MeasuresError.NULL_UNIT); + } + if (power != 0) { + data.measures.put(baseUnit.getMeasure(), new UnitPower(baseUnit, power)); + } else { + data.measures.remove(baseUnit.getMeasure()); + } + return this; + } + + /** + * Sets the long name for the composed unit. Defaults to null. If the long name + * is not set or is set to null, the long name is replaced by the long label. + */ + public Builder setLongName(String longName) { + data.longName = longName; + return this; + } + + /** + * Sets the short name for the composed unit. Defaults to null. If the short + * name is not set or is set to null, the long name is replaced by the long + * label. + */ + public Builder setShortName(String shortName) { + data.shortName = shortName; + return this; + } + + /** + * Sets the long and short names for the composed unit. Defaults to null. If the + * long or short name is not set or is set to null, they are replaced by the + * long or short labels respectively. + */ + public Builder setNames(String longName, String shortName) { + data.longName = longName; + data.shortName = shortName; + return this; + } + + /** + * Returns the composed unit from the collected input. + */ + public ComposedUnit build() { + return new ComposedUnit(data); + } + } + + /** + * Returns true if and only if there are no base units associated with this + * composed unit. The composed unit is simply the scalar value of 1. + */ + public boolean isMeasureLess() { + return measures.isEmpty(); + } + + /** + * Returns true if and only if this composed unit and the given composed unit + * have the same measures(but perhaps different base units) to the same integer + * powers. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_COMPOSITE} if + * the composed unit is null
  • + *
+ */ + public boolean isCompatible(ComposedUnit composedUnit) { + if (composedUnit == null) { + throw new ContractException(MeasuresError.NULL_COMPOSITE); + } + if (!measures.keySet().equals(composedUnit.measures.keySet())) { + return false; + } + for (Measure measure : measures.keySet()) { + Integer power1 = measures.get(measure).power; + Integer power2 = composedUnit.measures.get(measure).power; + if (!power1.equals(power2)) { + return false; + } + } + return true; + } + + /** + * Boilerplate implementation consistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((longName == null) ? 0 : longName.hashCode()); + result = prime * result + ((measures == null) ? 0 : measures.hashCode()); + result = prime * result + ((shortName == null) ? 0 : shortName.hashCode()); + return result; + } + + /** + * Two composed units are equal if and only if their units and powers are equal + * and their assigned long and short names are equal. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ComposedUnit)) { + return false; + } + ComposedUnit other = (ComposedUnit) obj; + if (longName == null) { + if (other.longName != null) { + return false; + } + } else if (!longName.equals(other.longName)) { + return false; + } + if (measures == null) { + if (other.measures != null) { + return false; + } + } else if (!measures.equals(other.measures)) { + return false; + } + if (shortName == null) { + if (other.shortName != null) { + return false; + } + } else if (!shortName.equals(other.shortName)) { + return false; + } + return true; + } + + /** + * Returns the string representation of this composed unit. Example of meter per + * second. Measures are listed in alphabetical order. + * + * ComposedUnit [value=1.0, longName=meters per second, shortName=mps, + * measures={Measure [name=length]=UnitPower [baseUnit=BaseUnit [measure=Measure + * [name=length], value=1.0, name=meter, shortName=m], power=1], Measure + * [name=time]=UnitPower [baseUnit=BaseUnit [measure=Measure [name=time], + * value=1.0, name=second, shortName=s], power=-1]}] + */ + @Override + public String toString() { + StringBuilder builder2 = new StringBuilder(); + builder2.append("ComposedUnit [value="); + builder2.append(value); + builder2.append(", longName="); + builder2.append(longName); + builder2.append(", shortName="); + builder2.append(shortName); + builder2.append(", measures="); + builder2.append(measures); + builder2.append("]"); + return builder2.toString(); + } + + /** + * Returns the label for this composed unit based on the short names of the + * corresponding units. Power values are displayed after a ^ symbol. Example for + * meters per second: m^1 s^-1 + */ + public String getShortLabel() { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Measure measure : measures.keySet()) { + UnitPower unitPower = measures.get(measure); + if (first) { + first = false; + } else { + sb.append(" "); + } + sb.append(unitPower.baseUnit.getShortName()); + sb.append("^"); + sb.append(unitPower.power); + } + return sb.toString(); + } + + /** + * Returns the long name of this composed unit if one was assigned. Otherwise, + * the short label is returned. + */ + public String getLongName() { + if (longName != null) { + return longName; + } + return getLongLabel(); + } + + /** + * Returns the short name of this composed unit if one was assigned. Otherwise, + * the short label is returned. + */ + public String getShortName() { + if (shortName != null) { + return shortName; + } + return getShortLabel(); + } + + /** + * Returns the label for this composed unit based on the long names of the + * corresponding units. Power values are displayed after a ^ symbol. Example for + * meters per second: meter^1 second^-1 + */ + public String getLongLabel() { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Measure measure : measures.keySet()) { + UnitPower unitPower = measures.get(measure); + if (first) { + first = false; + } else { + sb.append(" "); + } + sb.append(unitPower.baseUnit.getLongName()); + sb.append("^"); + sb.append(unitPower.power); + } + return sb.toString(); + } + + /** + * Returns the product of the two composites, favoring the units of the base + * composite over the aux composite. + */ + static ComposedUnit getCompositeProduct(ComposedUnit baseComposite, ComposedUnit auxComposite) { + Builder builder = builder(); + + // Favor the units of the base + for (Measure measure : baseComposite.measures.keySet()) { + UnitPower baseUnitPower = baseComposite.measures.get(measure); + int power = baseUnitPower.power; + UnitPower auxUnitPower = auxComposite.measures.get(measure); + if (auxUnitPower != null) { + power += auxUnitPower.power; + } + builder.setBaseUnit(baseUnitPower.baseUnit, power); + } + + // Any measures not in the base need to be captured + for (Measure measure : auxComposite.measures.keySet()) { + UnitPower auxUnitPower = auxComposite.measures.get(measure); + int power = auxUnitPower.power; + UnitPower baseUnitPower = baseComposite.measures.get(measure); + if (baseUnitPower == null) { + builder.setBaseUnit(auxUnitPower.baseUnit, power); + } + } + + return builder.build(); + + } + + /** + * Returns the quotient of the two composites, favoring the units of the base + * composite over the aux composite. + */ + static ComposedUnit getCompositeQuotient(ComposedUnit baseComposite, ComposedUnit auxComposite) { + Builder builder = builder(); + + // Favor the units of the base + for (Measure measure : baseComposite.measures.keySet()) { + UnitPower baseUnitPower = baseComposite.measures.get(measure); + int power = baseUnitPower.power; + UnitPower auxUnitPower = auxComposite.measures.get(measure); + if (auxUnitPower != null) { + power -= auxUnitPower.power; + } + builder.setBaseUnit(baseUnitPower.baseUnit, power); + } + + // Any measures not in the base need to be captured + for (Measure measure : auxComposite.measures.keySet()) { + UnitPower auxUnitPower = auxComposite.measures.get(measure); + int power = auxUnitPower.power; + UnitPower baseUnitPower = baseComposite.measures.get(measure); + if (baseUnitPower == null) { + builder.setBaseUnit(auxUnitPower.baseUnit, -power); + } + } + + return builder.build(); + + } + + static ComposedUnit getInverse(ComposedUnit composedUnit) { + Builder builder = builder(); + for (Measure measure : composedUnit.measures.keySet()) { + UnitPower unitPower = composedUnit.measures.get(measure); + builder.setBaseUnit(unitPower.baseUnit, -unitPower.power); + } + return builder.build(); + } + + static ComposedUnit getPowerComposite(ComposedUnit composedUnit, int power) { + Builder builder = builder(); + for (Measure measure : composedUnit.measures.keySet()) { + UnitPower unitPower = composedUnit.measures.get(measure); + builder.setBaseUnit(unitPower.baseUnit, unitPower.power * power); + } + return builder.build(); + } + + static ComposedUnit getRootComposite(ComposedUnit composedUnit, int root) { + if (root < 1) { + throw new ContractException(MeasuresError.NON_POSITIVE_ROOT); + } + if (root == 1) { + return composedUnit; + } + Builder builder = builder(); + for (Measure measure : composedUnit.measures.keySet()) { + UnitPower unitPower = composedUnit.measures.get(measure); + if (unitPower.power % root != 0) { + throw new ContractException(MeasuresError.POWER_IS_NOT_ROOT_COMPATIBLE); + } + builder.setBaseUnit(unitPower.baseUnit, unitPower.power / root); + } + return builder.build(); + } + +} diff --git a/src/main/java/gov/hhs/aspr/ms/util/measures/Constant.java b/src/main/java/gov/hhs/aspr/ms/util/measures/Constant.java new file mode 100644 index 0000000..ff3df9e --- /dev/null +++ b/src/main/java/gov/hhs/aspr/ms/util/measures/Constant.java @@ -0,0 +1,197 @@ +package gov.hhs.aspr.ms.util.measures; + +import gov.hhs.aspr.ms.util.errors.ContractException; + +/** + * Represents a constant quantity with long and short names. + */ +public final class Constant { + private final String longName; + private final String shortName; + private final Quantity quantity; + + /** + * Constructs the Constant from the quantity and names + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_UNIT_NAME } if + * the long longName is null
  • + *
  • {@linkplain MeasuresError#BLANK_UNIT_NAME } if + * the long longName is empty or contains only white + * space characters
  • + *
  • {@linkplain MeasuresError#NULL_UNIT_NAME } if + * the short longName is null
  • + *
  • {@linkplain MeasuresError#BLANK_UNIT_NAME } if + * the short longName is empty or contains only white + * space characters
  • + *
  • {@linkplain MeasuresError#NULL_QUANTITY } if + * the quantity is null
  • + *
+ */ + public Constant(Quantity quantity, String longName, String shortName) { + + if (longName == null) { + throw new ContractException(MeasuresError.NULL_UNIT_NAME); + } + + if (longName.isBlank()) { + throw new ContractException(MeasuresError.BLANK_UNIT_NAME); + } + + if (shortName == null) { + throw new ContractException(MeasuresError.NULL_UNIT_NAME); + } + + if (shortName.isBlank()) { + throw new ContractException(MeasuresError.BLANK_UNIT_NAME); + } + + if (quantity == null) { + throw new ContractException(MeasuresError.NULL_QUANTITY); + } + + this.quantity = quantity; + this.longName = longName; + this.shortName = shortName; + } + + /** + * Returns the long name + */ + public String getLongName() { + return longName; + } + + /** + * Returns the short name + */ + public String getShortName() { + return shortName; + } + + /** + * Returns the quantity + */ + public Quantity getQuantity() { + return quantity; + } + + /** + * Boilerplate implementation consistent with equals + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((longName == null) ? 0 : longName.hashCode()); + result = prime * result + ((quantity == null) ? 0 : quantity.hashCode()); + result = prime * result + ((shortName == null) ? 0 : shortName.hashCode()); + return result; + } + + /** + * Two constants are equal if and only if their quantities and names are equal + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Constant)) { + return false; + } + Constant other = (Constant) obj; + if (longName == null) { + if (other.longName != null) { + return false; + } + } else if (!longName.equals(other.longName)) { + return false; + } + if (quantity == null) { + if (other.quantity != null) { + return false; + } + } else if (!quantity.equals(other.quantity)) { + return false; + } + if (shortName == null) { + if (other.shortName != null) { + return false; + } + } else if (!shortName.equals(other.shortName)) { + return false; + } + return true; + } + + /** + * Returns the string representation of this constant. Example: earth gravity = + * 9.80665 meters per second squared. + * + * Constant [longName=earth_gravity, shortName=g, quantity=Quantity + * [composedUnit=ComposedUnit [value=1.0, longName=null, shortName=null, + * measures={Measure [name=length]=UnitPower [baseUnit=BaseUnit [measure=Measure + * [name=length], value=1.0, name=meter, shortName=m], power=1], Measure + * [name=time]=UnitPower [baseUnit=BaseUnit [measure=Measure [name=time], + * value=1.0, name=second, shortName=s], power=-2]}], value=9.80665]] + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Constant [longName="); + builder.append(longName); + builder.append(", shortName="); + builder.append(shortName); + builder.append(", quantity="); + builder.append(quantity); + builder.append("]"); + return builder.toString(); + } + + /** + * Returns the constant represented as its value concatenated with its short + * name. Example, 12 mph + */ + public String getShortString(Quantity quantity) { + double convertedValue = getConvertedValue(quantity); + return convertedValue + " " + shortName; + } + + /** + * Returns the constant represented as its value concatenated with its short + * name. Example, 12 miles per hour + */ + public String getLongString(Quantity quantity) { + double convertedValue = getConvertedValue(quantity); + return convertedValue + " " + longName; + } + + /** + * Returns the value of the quantity resulting from the division of the given + * quantity and the quantity contained in this constant. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the quantity does not have the same measures to + * the same powers
  • + *
+ */ + public double getConvertedValue(Quantity quantity) { + if (quantity == null) { + throw new ContractException(MeasuresError.NULL_QUANTITY); + } + if (!quantity.getComposedUnit().isCompatible(this.quantity.getComposedUnit())) { + throw new ContractException(MeasuresError.INCOMPATIBLE_MEASURES); + } + + double v1 = this.quantity.getValue() * this.quantity.getComposedUnit().getValue(); + double v2 = quantity.getValue() * quantity.getComposedUnit().getValue(); + return v2 / v1; + } + +} diff --git a/src/main/java/gov/hhs/aspr/ms/util/measures/Measure.java b/src/main/java/gov/hhs/aspr/ms/util/measures/Measure.java new file mode 100644 index 0000000..07ae252 --- /dev/null +++ b/src/main/java/gov/hhs/aspr/ms/util/measures/Measure.java @@ -0,0 +1,90 @@ +package gov.hhs.aspr.ms.util.measures; + +import gov.hhs.aspr.ms.util.errors.ContractException; + +/** + * Represents a type of thing to be measured that is irreducible. Examples: + * distance, time and mass. Volume would not be good choice for a measure since + * a volume can be viewed as the cube of distance. Each measure is identified by + * its name. + */ +public final class Measure { + + private final String name; + + /** + * Constructs the Measure from the given name. Names are by convention in lower + * snake case. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_MEASURE_NAME} if + * the name is null
  • + *
  • {@linkplain MeasuresError#BLANK_MEASURE_NAME} + * if the name is empty of contains only white space + * characters
  • + *
+ * + */ + public Measure(String name) { + if (name == null) { + throw new ContractException(MeasuresError.NULL_MEASURE_NAME); + } + if (name.isBlank()) { + throw new ContractException(MeasuresError.BLANK_MEASURE_NAME); + } + this.name = name; + } + + /** + * Returns the name of the measure + */ + public String getName() { + return name; + } + + /** + * Standard boilerplate implementation consistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + /** + * Two measures are equal if and only if their names are equal + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Measure)) { + return false; + } + Measure other = (Measure) obj; + if (!name.equals(other.name)) { + return false; + } + return true; + } + + /** + * Returns the string representation of the measure in the form: + * + * Measure [name=X] + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Measure [name="); + builder.append(name); + builder.append("]"); + return builder.toString(); + } + + +} diff --git a/src/main/java/gov/hhs/aspr/ms/util/measures/MeasuresError.java b/src/main/java/gov/hhs/aspr/ms/util/measures/MeasuresError.java new file mode 100644 index 0000000..eafc8a1 --- /dev/null +++ b/src/main/java/gov/hhs/aspr/ms/util/measures/MeasuresError.java @@ -0,0 +1,37 @@ +package gov.hhs.aspr.ms.util.measures; + +import gov.hhs.aspr.ms.util.errors.ContractError; +import gov.hhs.aspr.ms.util.errors.ContractException; + +/** + * An enumeration supporting {@link ContractException} that acts as a general + * description of the exception. + */ +public enum MeasuresError implements ContractError { + NULL_MEASURE_NAME("Null measure name"), + BLANK_MEASURE_NAME("Blank measure name"), + NULL_UNIT("Null unit"), + NULL_UNIT_NAME("Null unit name"), + BLANK_UNIT_NAME("Blank unit name"), + NULL_MEASURE("Null measure"),// + NON_POSITIVE_SCALAR_VALUE("Non-positive unit value"), + NULL_COMPOSITE("Null composite"), + INCOMPATIBLE_MEASURES("Measure power values do not match"),// + NON_POSITIVE_ROOT("The root is not a positive integer"), + POWER_IS_NOT_ROOT_COMPATIBLE("A measure's power value is not a multiple of the root"), + NULL_QUANTITY("Null quantity"), + +// NULL_CONSTANT_DESCRIPTION("Null constant description"), +// NULL_CONSTANT_LABEL("Null constant label"), + ; + private final String description; + + private MeasuresError(final String description) { + this.description = description; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/src/main/java/gov/hhs/aspr/ms/util/measures/Quantity.java b/src/main/java/gov/hhs/aspr/ms/util/measures/Quantity.java new file mode 100644 index 0000000..ca5fee2 --- /dev/null +++ b/src/main/java/gov/hhs/aspr/ms/util/measures/Quantity.java @@ -0,0 +1,565 @@ +package gov.hhs.aspr.ms.util.measures; + +import org.apache.commons.math3.util.FastMath; + +import gov.hhs.aspr.ms.util.errors.ContractException; + +/** + * An immutable representation of a scaled quantity with multiple integer + * dimensions. + */ +public final class Quantity { + private final ComposedUnit composedUnit; + + private final double value; + + /** + * Returns a quantity from the given composed unit and value. + */ + public Quantity(ComposedUnit composedUnit, double value) { + if (composedUnit == null) { + throw new ContractException(MeasuresError.NULL_COMPOSITE); + } + this.composedUnit = composedUnit; + this.value = value; + } + + /** + * Returns a quantity from the given base unit and value. The base unit is used + * to create a composed unit. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_UNIT} if the + * base unit is null
  • + *
+ */ + public Quantity(BaseUnit baseUnit, double value) { + if (baseUnit == null) { + throw new ContractException(MeasuresError.NULL_UNIT); + } + this.composedUnit = baseUnit.asComposedUnit(); + this.value = value; + } + + /** + * Returns the value value + */ + public double getValue() { + return value; + } + + /** + * Returns the composedUnit + */ + public ComposedUnit getComposedUnit() { + return composedUnit; + } + + /** + * Returns true if and only if the ratio of this quantity to the given quantity + * differ from 1 by no more than the tolerance. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the quantity does not have matching + * dimensions
  • + *
+ */ + public boolean e(Quantity quantity, double tolerance) { + validateQuantityNotNull(quantity); + validateQuantityIsCompatible(quantity); + double v1 = value * composedUnit.getValue(); + double v2 = quantity.value * quantity.composedUnit.getValue(); + return FastMath.abs(1 - (v2 / v1)) <= tolerance; + } + + /** + * Returns true if and only if this quantity is greater than the given quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the quantity does not have matching + * dimensions
  • + *
+ */ + public boolean gt(Quantity quantity) { + validateQuantityNotNull(quantity); + validateQuantityIsCompatible(quantity); + double v1 = value * composedUnit.getValue(); + double v2 = quantity.value * quantity.composedUnit.getValue(); + return v1 > v2; + } + + /** + * Returns true if and only if this quantity is less than the given quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the quantity does not have matching + * dimensions
  • + *
+ */ + public boolean lt(Quantity quantity) { + validateQuantityNotNull(quantity); + validateQuantityIsCompatible(quantity); + double v1 = value * composedUnit.getValue(); + double v2 = quantity.value * quantity.composedUnit.getValue(); + return v1 < v2; + } + + /** + * Returns true if and only if this quantity is greater than or equal to the + * given quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the quantity does not have matching + * dimensions
  • + *
+ */ + public boolean gte(Quantity quantity) { + validateQuantityNotNull(quantity); + validateQuantityIsCompatible(quantity); + double v1 = value * composedUnit.getValue(); + double v2 = quantity.value * quantity.composedUnit.getValue(); + return v1 >= v2; + } + + /** + * Returns true if and only if this quantity is less than or equal to the given + * quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the quantity does not have matching + * dimensions
  • + *
+ */ + public boolean lte(Quantity quantity) { + validateQuantityNotNull(quantity); + validateQuantityIsCompatible(quantity); + double v1 = value * composedUnit.getValue(); + double v2 = quantity.value * quantity.composedUnit.getValue(); + return v1 <= v2; + } + + private void validateQuantityNotNull(Quantity quantity) { + if (quantity == null) { + throw new ContractException(MeasuresError.NULL_QUANTITY); + } + } + + /* + * Assumes quantity is not null. + */ + private void validateQuantityIsCompatible(Quantity quantity) { + if (!composedUnit.isCompatible(quantity.composedUnit)) { + throw new ContractException(MeasuresError.INCOMPATIBLE_MEASURES); + } + } + + /** + * Returns a new Quantity resulting from the addition of the given quantity to + * this quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the quantity does not have equal powers over it + * measures
  • + * + *
+ */ + public Quantity add(Quantity quantity) { + validateQuantityNotNull(quantity); + validateQuantityIsCompatible(quantity); + if (quantity.composedUnit.getValue() == composedUnit.getValue()) { + return new Quantity(composedUnit, value + quantity.getValue()); + } + double v = quantity.value * quantity.composedUnit.getValue() / composedUnit.getValue(); + return new Quantity(composedUnit, value + v); + } + + /** + * Returns a new Quantity resulting from the subtraction of the given quantity + * from this quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the quantity does not have equal powers over it + * measures
  • + * + *
+ */ + public Quantity sub(Quantity quantity) { + validateQuantityNotNull(quantity); + validateQuantityIsCompatible(quantity); + if (quantity.composedUnit.getValue() == composedUnit.getValue()) { + return new Quantity(composedUnit, value - quantity.getValue()); + } + double v = quantity.value * quantity.composedUnit.getValue() / composedUnit.getValue(); + return new Quantity(composedUnit, value - v); + } + + /** + * Returns a new Quantity resulting from the multiplication of this quantity by + * the given quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
+ */ + public Quantity mult(Quantity quantity) { + validateQuantityNotNull(quantity); + ComposedUnit newComposite = ComposedUnit.getCompositeProduct(composedUnit, quantity.composedUnit); + double compositeFactor = (composedUnit.getValue() * quantity.composedUnit.getValue()) / newComposite.getValue(); + double newValue = value * quantity.value * compositeFactor; + return new Quantity(newComposite, newValue); + } + + /** + * Returns a new Quantity resulting from the division of this quantity by the + * given quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
+ */ + public Quantity div(Quantity quantity) { + validateQuantityNotNull(quantity); + ComposedUnit newComposite = ComposedUnit.getCompositeQuotient(composedUnit, quantity.composedUnit); + double compositeFactor = composedUnit.getValue() / (newComposite.getValue() * quantity.composedUnit.getValue()); + double newValue = value / quantity.value * compositeFactor; + return new Quantity(newComposite, newValue); + } + + /** + * Returns a new Quantity resulting from the inversion of this quantity + * + */ + public Quantity invert() { + ComposedUnit newComposite = ComposedUnit.getInverse(composedUnit); + double newValue = 1.0 / value; + return new Quantity(newComposite, newValue); + } + + /** + * Returns a new Quantity resulting from raising this quantity to the given + * power + * + * + */ + public Quantity pow(int power) { + ComposedUnit newComposite = ComposedUnit.getPowerComposite(composedUnit, power); + double newValue = FastMath.pow(value, power); + return new Quantity(newComposite, newValue); + } + + /** + * Returns a new Quantity resulting from the inversion of this quantity + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NON_POSITIVE_ROOT} if + * the root is not positive
  • + *
  • {@linkplain MeasuresError#POWER_IS_NOT_ROOT_COMPATIBLE} + * if any of the measure powers is not divisible by + * the root
  • + *
+ */ + public Quantity root(int root) { + ComposedUnit newComposite = ComposedUnit.getRootComposite(composedUnit, root); + double newValue = FastMath.pow(value, 1.0 / root); + return new Quantity(newComposite, newValue); + } + + /** + * Returns a new Quantity that is equal to this quantity, but has been rebased + * to the given composedUnit + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_COMPOSITE} if + * the composedUnit is null
  • + *
  • {@linkplain MeasuresError#INCOMPATIBLE_MEASURES} + * if the composedUnit is not compatible with this + * quantity's composedUnit
  • + *
+ */ + public Quantity rebase(ComposedUnit composedUnit) { + if (composedUnit == null) { + throw new ContractException(MeasuresError.NULL_COMPOSITE); + } + if (!this.composedUnit.isCompatible(composedUnit)) { + throw new ContractException(MeasuresError.INCOMPATIBLE_MEASURES); + } + double compositeFactor = this.composedUnit.getValue() / composedUnit.getValue(); + double newValue = value * compositeFactor; + return new Quantity(composedUnit, newValue); + } + + /** + * Returns a new Quantity resulting from the value multiplication of this + * quantity by the given value + * + */ + public Quantity scale(double scalar) { + return new Quantity(composedUnit, this.value * scalar);// + } + + /** + * Returns a new Quantity resulting the replacement of the value. + * + */ + public Quantity setValue(double value) { + return new Quantity(composedUnit, value);// + } + + /** + * Returns a new Quantity resulting from adding 1 to the value + */ + public Quantity inc() { + return new Quantity(composedUnit, value + 1);// + } + + /** + * Returns a new Quantity resulting from adding the given value to the current + * value + */ + public Quantity inc(double value) { + return new Quantity(composedUnit, this.value + value);// + } + + /** + * Returns a new Quantity resulting from subtracting 1 from the value + */ + public Quantity dec() { + return new Quantity(composedUnit, value - 1);// + } + + /** + * Returns a new Quantity resulting from subtracting the given value from the + * current value + */ + + public Quantity dec(double value) { + return new Quantity(composedUnit, this.value - value);// + } + + /** + * Returns true if and only if the value is finite + */ + public boolean isFinite() { + return Double.isFinite(value); + } + + /** + * Returns true if and only if the value is zero + */ + public boolean isZero() { + return value == 0; + } + + /** + * Returns true if and only if the value is positive + */ + public boolean isPositive() { + return value > 0; + } + + /** + * Returns true if and only if the value is negative + */ + public boolean isNegative() { + return value < 0; + } + + /** + * Returns true if and only if the value is non-positive + */ + public boolean isNonPositive() { + return value <= 0; + } + + /** + * Returns true if and only if the value is non-negative + */ + public boolean isNonNegative() { + return value >= 0; + } + + /** + * Returns true if and only if the measure powers of this quantity and the given + * quantity are equal. + * + * @throws ContractException + *
    + *
  • {@linkplain MeasuresError#NULL_QUANTITY} if the + * quantity is null
  • + *
+ */ + public boolean isCompatible(Quantity quantity) { + validateQuantityNotNull(quantity); + return composedUnit.isCompatible(quantity.composedUnit); + } + + /** + * Return true if and only if each of the dimension values is zero. + */ + public boolean isMeasureLess() { + return composedUnit.isMeasureLess(); + } + + /** + * Boilerplate implementation conistent with equals() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((composedUnit == null) ? 0 : composedUnit.hashCode()); + long temp; + temp = Double.doubleToLongBits(value); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + /** + * Two quantities are equal if and only if they have the equal composed units + * and equal values. For example, the quantities q1 = new Quantity(FOOT,1) and + * q2 = new Quantity(INCH,12) are not equal even though they are equivalent. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Quantity)) { + return false; + } + Quantity other = (Quantity) obj; + if (composedUnit == null) { + if (other.composedUnit != null) { + return false; + } + } else if (!composedUnit.equals(other.composedUnit)) { + return false; + } + if (Double.doubleToLongBits(value) != Double.doubleToLongBits(other.value)) { + return false; + } + return true; + } + + /** + * Returns the string representation of this quantity. Example form of 14.5 + * meters per second squared: + * + * Quantity [composedUnit=ComposedUnit [value=1.0, longName=acceleration, + * shortName=acc, measures={Measure [name=length]=UnitPower [baseUnit=BaseUnit + * [measure=Measure [name=length], value=1.0, name=meter, shortName=m], + * power=1], Measure [name=time]=UnitPower [baseUnit=BaseUnit [measure=Measure + * [name=time], value=1.0, name=second, shortName=s], power=-2]}], value=14.5] + */ + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("Quantity [composedUnit="); + builder.append(composedUnit); + builder.append(", value="); + builder.append(value); + builder.append("]"); + return builder.toString(); + } + + /** + * Returns the value combined with the short label of this quantity's composed + * unit. Example: + * + * "12.56 m^1 sec^-1" + */ + + public String getShortLabel() { + StringBuilder builder = new StringBuilder(); + builder.append(value); + builder.append(" "); + builder.append(composedUnit.getShortLabel()); + return builder.toString(); + } + + /** + * Returns the value combined with the long label of this quantity's composed + * unit. Example: + * + * "12.56 meter^1 second^-1" + */ + public String getLongLabel() { + StringBuilder builder = new StringBuilder(); + builder.append(value); + builder.append(" "); + builder.append(composedUnit.getLongLabel()); + return builder.toString(); + } + + /** + * Returns the value combined with the short name of this quantity's composed + * unit. Example: + * + * "12.56 mps" + */ + public String getShortName() { + StringBuilder builder = new StringBuilder(); + builder.append(value); + builder.append(" "); + builder.append(composedUnit.getShortName()); + return builder.toString(); + } + + /** + * Returns the value combined with the long name of this quantity's composed + * unit. Example: + * + * "12.56 meters per second" + */ + public String getLongName() { + StringBuilder builder = new StringBuilder(); + builder.append(value); + builder.append(" "); + builder.append(composedUnit.getLongName()); + return builder.toString(); + } + + /** + * + * name -- override the label + * + * label -- go back to unit name + */ + +} diff --git a/src/main/java/gov/hhs/aspr/ms/util/measures/StandardMeasures.java b/src/main/java/gov/hhs/aspr/ms/util/measures/StandardMeasures.java new file mode 100644 index 0000000..75c6cbc --- /dev/null +++ b/src/main/java/gov/hhs/aspr/ms/util/measures/StandardMeasures.java @@ -0,0 +1,102 @@ +package gov.hhs.aspr.ms.util.measures; + +import org.apache.commons.math3.util.FastMath; + +/** + * A collection of standard measures, base units, composed units and constants + */ +public final class StandardMeasures { + + public static Measure LENGTH = new Measure("length"); + public static Measure MASS = new Measure("mass"); + public static Measure TIME = new Measure("time"); + public static Measure CURRENT = new Measure("current"); + public static Measure TEMPERATURE = new Measure("temperature"); + public static Measure LUMINOSITY = new Measure("luminosity"); + public static Measure ANGLE = new Measure("angle"); + public static Measure SOLID_ANGLE = new Measure("solid_angle"); + + + // Length + public static BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + public static BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + public static BaseUnit DM = new BaseUnit(CM, 10, "decimeter", "dm"); + public static BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + public static BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + public static BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + // Time + public static BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + public static BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + public static BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + public static BaseUnit DAY = new BaseUnit(HOUR, 24, "day", "d"); + + // Mass + public static BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + public static BaseUnit GRAM = new BaseUnit(KILOGRAM, 0.001, "gram", "g"); + public static BaseUnit MILLIGRAM = new BaseUnit(GRAM, 0.001, "milligram", "mg"); + public static BaseUnit MICROGRAM = new BaseUnit(MILLIGRAM, 0.001, "microgram", "mcg"); + + //Current + public static BaseUnit AMPERE = new BaseUnit(CURRENT, "ampere", "A"); + + //TemperatureScale + public static BaseUnit KELVIN = new BaseUnit(TEMPERATURE, "kelvin", "K"); + + //Luminosity + public static BaseUnit CANDELA = new BaseUnit(LUMINOSITY, "candela", "cd"); + + //Angle + public static BaseUnit RADIAN = new BaseUnit(ANGLE, "raidan", "rad"); + public static BaseUnit DEGREE = new BaseUnit(RADIAN, FastMath.PI/180,"degree", "deg"); + + //Solid Angle + public static BaseUnit STERADIAN = new BaseUnit(SOLID_ANGLE, "steradian", "st"); + + //speed + public static ComposedUnit MPH = ComposedUnit.builder().setBaseUnit(MILE, 1).setBaseUnit(HOUR, -1).build(); + public static ComposedUnit MPS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -1).build(); + + //acceleration + public static ComposedUnit ACCELERATION_MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + public static Constant EARTH_GRAVITY = new Constant(new Quantity(ACCELERATION_MPSS, 9.80665), "earth_gravity", "eg"); + + //Liquid volume + public static ComposedUnit ML = ComposedUnit.builder().setBaseUnit(CM, 3).setShortName("ml").setLongName("milliliter") + .build(); + + public static ComposedUnit LITER = ComposedUnit.builder().setBaseUnit(DM, 3).setShortName("L").setLongName("liter") + .build(); + + //common scalar values + + public final static double QUECTO = 1E-30; + public final static double RONTO = 1E-27; + public final static double YOCTO = 1E-24; + public final static double ZEPTO = 1E-21; + public final static double ATTO = 1E-18; + public final static double FEMTO = 1E-15; + public final static double PICO = 1E-12; + public final static double NANO = 1E-9; + public final static double MICRO = 1E-6; + public final static double MILLI = 1E-3; + public final static double CENTI = 1E-2; + public final static double DECI = 1E-1; + public final static double DECA = 1E1; + public final static double HECTO = 1E2; + public final static double KILO = 1E3; + public final static double MEGA = 1E6; + public final static double GIGA = 1E9; + public final static double TERA = 1E12; + public final static double PETA = 1E15; + public final static double EXA = 1E18; + public final static double ZETTA = 1E21; + public final static double YOTTA = 1E24; + public final static double RONNA = 1E27; + public final static double QUETTA = 1E30; + + private StandardMeasures() {} + + + +} diff --git a/src/main/java/gov/hhs/aspr/ms/util/measures/TemperatureScale.java b/src/main/java/gov/hhs/aspr/ms/util/measures/TemperatureScale.java new file mode 100644 index 0000000..ebec5c6 --- /dev/null +++ b/src/main/java/gov/hhs/aspr/ms/util/measures/TemperatureScale.java @@ -0,0 +1,240 @@ +package gov.hhs.aspr.ms.util.measures; + +/** + * A utility enum for converting absolute and relative temperatures across + * various temperature scales. + */ +public enum TemperatureScale { + + /** + * fahrenheit = 9*(kelvin - 273.15)/5+32 + */ + FAHRENHEIT { + + @Override + protected double toAbsoluteKelvin(double value) { + return (value - 32) / 9 * 5 + 273.15; + } + + @Override + protected double fromAbsoluteKelvin(double value) { + return (value - 273.15) * 9 / 5 + 32; + } + + @Override + protected double toRelativeKelvin(double value) { + return value / 9 * 5; + } + + @Override + protected double fromRelativeKelvin(double value) { + return value * 9 / 5; + } + }, + + /** + * Kelvin is the fundamental scale + * + * kelvin = kelvin + */ + KELVIN { + + @Override + protected double toAbsoluteKelvin(double value) { + return value; + } + + @Override + protected double fromAbsoluteKelvin(double value) { + return value; + } + + @Override + protected double toRelativeKelvin(double value) { + return value; + } + + @Override + protected double fromRelativeKelvin(double value) { + return value; + } + }, + + /** + * rankine = 9*kelvin/5 + */ + RANKINE { + + @Override + protected double toAbsoluteKelvin(double value) { + return value * 5 / 9; + } + + @Override + protected double fromAbsoluteKelvin(double value) { + return value * 9 / 5; + } + + @Override + protected double toRelativeKelvin(double value) { + return value * 5 / 9; + } + + @Override + protected double fromRelativeKelvin(double value) { + return value * 9 / 5; + } + }, + /** + * delisle = 3*(373.15 - kelvin)/2 + */ + DELISLE { + + @Override + protected double toAbsoluteKelvin(double value) { + return 373.15 - value * 2 / 3; + } + + @Override + protected double fromAbsoluteKelvin(double value) { + return (373.15 - value) * 3 / 2; + } + + @Override + protected double toRelativeKelvin(double value) { + return -value * 2 / 3; + } + + @Override + protected double fromRelativeKelvin(double value) { + return -value * 3 / 2; + } + }, + /** + * newton = 0.33*(kelvin - 273.15) + */ + NEWTON { + + @Override + protected double toAbsoluteKelvin(double value) { + return value / 0.33 + 273.15; + } + + @Override + protected double fromAbsoluteKelvin(double value) { + return (value - 273.15) * 0.33; + } + + @Override + protected double toRelativeKelvin(double value) { + return value / 0.33; + } + + @Override + protected double fromRelativeKelvin(double value) { + return value * 0.33; + } + }, + + /** + * reaumur = 4*(kelvin - 273.15)/5 + */ + REAUMUR { + + @Override + protected double toAbsoluteKelvin(double value) { + return value * 5 / 4 + 273.15; + } + + @Override + protected double fromAbsoluteKelvin(double value) { + return (value - 273.15) * 4 / 5; + } + + @Override + protected double toRelativeKelvin(double value) { + return value * 5 / 4; + } + + @Override + protected double fromRelativeKelvin(double value) { + return value * 4 / 5; + } + }, + + /** + * romer = 21*(kelvin - 273.15)/40 + 7.5 + */ + ROMER { + + @Override + protected double toAbsoluteKelvin(double value) { + return (value - 7.5) * 40 / 21 + 273.15; + } + + @Override + protected double fromAbsoluteKelvin(double value) { + return (value - 273.15) * 21 / 40 + 7.5; + } + + @Override + protected double toRelativeKelvin(double value) { + return value * 40 / 21; + } + + @Override + protected double fromRelativeKelvin(double value) { + return value * 21 / 40; + } + }, + + /** + * celsius = kelvin - 273.15 + */ + CELSIUS { + @Override + protected double toAbsoluteKelvin(double value) { + return value + 273.15; + } + + @Override + protected double fromAbsoluteKelvin(double value) { + return value - 273.15; + } + + @Override + protected double toRelativeKelvin(double value) { + return value; + } + + @Override + protected double fromRelativeKelvin(double value) { + return value; + } + }; + + protected abstract double toAbsoluteKelvin(double value); + + protected abstract double fromAbsoluteKelvin(double value); + + protected abstract double toRelativeKelvin(double value); + + protected abstract double fromRelativeKelvin(double value); + + /** + * Converts the absolute degrees in the given scale to this scale's absolute + * temperature degree value. + */ + public double fromAbsolute(double absoluteDegrees, TemperatureScale temperatureScale) { + return fromAbsoluteKelvin(temperatureScale.toAbsoluteKelvin(absoluteDegrees)); + } + + /** + * Converts the relative degrees in the given scale to this scale's relative + * temperature degree value. + */ + public double fromRelative(double relativeDegrees, TemperatureScale temperatureScale) { + return fromRelativeKelvin(temperatureScale.toRelativeKelvin(relativeDegrees)); + } + +} diff --git a/src/test/java/gov/hhs/aspr/ms/util/measures/AT_BaseUnit.java b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_BaseUnit.java new file mode 100644 index 0000000..ddd921b --- /dev/null +++ b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_BaseUnit.java @@ -0,0 +1,286 @@ +package gov.hhs.aspr.ms.util.measures; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.util.annotations.UnitTestConstructor; +import gov.hhs.aspr.ms.util.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.util.errors.ContractException; +import gov.hhs.aspr.ms.util.random.RandomGeneratorProvider; + +public class AT_BaseUnit { + @Test + @UnitTestConstructor(target = BaseUnit.class, args = { Measure.class, String.class, String.class }) + public void testUnit_Measure() { + + // postcondition tests are covered by the remaining tests + + Measure LENGTH = new Measure("length"); + + // precondition test: if the name is null + ContractException contractException = assertThrows(ContractException.class, + () -> new BaseUnit(LENGTH, null, "short_name")); + assertEquals(MeasuresError.NULL_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the name is empty or contains only white space + contractException = assertThrows(ContractException.class, () -> new BaseUnit(LENGTH, "", "short_name")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> new BaseUnit(LENGTH, " ", "short_name")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the short name is null + contractException = assertThrows(ContractException.class, () -> new BaseUnit(LENGTH, "long_name", null)); + assertEquals(MeasuresError.NULL_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the short name is empty or contains only white space + contractException = assertThrows(ContractException.class, () -> new BaseUnit(LENGTH, "long_name", "")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> new BaseUnit(LENGTH, "long_name", " ")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the measure is null + contractException = assertThrows(ContractException.class, () -> new BaseUnit(null, "long_name", "short_name")); + assertEquals(MeasuresError.NULL_MEASURE, contractException.getErrorType()); + } + + @Test + @UnitTestConstructor(target = BaseUnit.class, args = { BaseUnit.class, double.class, String.class, String.class }) + public void testUnit_Unit() { + // postcondition tests are covered by the remaining tests + + Measure LENGTH = new Measure("length"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + + // precondition test: if the long name is null + ContractException contractException = assertThrows(ContractException.class, + () -> new BaseUnit(METER, 10, null, "short_name")); + assertEquals(MeasuresError.NULL_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the long name is empty or contains only white space + contractException = assertThrows(ContractException.class, () -> new BaseUnit(METER, 10, "", "short_name")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> new BaseUnit(METER, 10, " ", "short_name")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the short name is null + contractException = assertThrows(ContractException.class, () -> new BaseUnit(METER, 10, "long_name", null)); + assertEquals(MeasuresError.NULL_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the short name is empty or contains only white space + contractException = assertThrows(ContractException.class, () -> new BaseUnit(METER, 10, "long_name", "")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> new BaseUnit(METER, 10, "long_name", " ")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the unit is null + contractException = assertThrows(ContractException.class, + () -> new BaseUnit(null, 10, "long_name", "short_name")); + assertEquals(MeasuresError.NULL_UNIT, contractException.getErrorType()); + + // precondition test: if the scalar is not positive + contractException = assertThrows(ContractException.class, + () -> new BaseUnit(METER, -2, "long_name", "short_name")); + assertEquals(MeasuresError.NON_POSITIVE_SCALAR_VALUE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = BaseUnit.class, name = "equals", args = { Object.class }) + public void testEquals() { + // units are equal if and only if they have the same contents + + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + + // Length + BaseUnit A = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit B = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit C = new BaseUnit(LENGTH, "meter", "c"); + BaseUnit D = new BaseUnit(LENGTH, "d", "m"); + BaseUnit E = new BaseUnit(A, 1, "meter", "m"); + BaseUnit F = new BaseUnit(A, 1.6, "meter", "m"); + BaseUnit G = new BaseUnit(TIME, "meter", "m"); + + assertTrue(A.equals(B)); + assertFalse(A.equals(C)); + assertFalse(A.equals(D)); + assertTrue(A.equals(E)); + assertFalse(A.equals(F)); + assertFalse(A.equals(G)); + + // not equal to null + assertFalse(A.equals(null)); + + // not equal to other types + assertFalse(A.equals(new Object())); + + // reflexive + assertTrue(A.equals(A)); + assertTrue(B.equals(B)); + assertTrue(C.equals(C)); + assertTrue(D.equals(D)); + assertTrue(E.equals(E)); + assertTrue(F.equals(F)); + assertTrue(G.equals(G)); + + // symmetric, transitive, stable + for (int i = 0; i < 5; i++) { + assertTrue(A.equals(B)); + assertTrue(B.equals(A)); + assertTrue(A.equals(E)); + assertTrue(E.equals(A)); + assertTrue(B.equals(E)); + assertTrue(E.equals(B)); + } + } + + @Test + @UnitTestMethod(target = BaseUnit.class, name = "getMeasure", args = {}) + public void testGetMeasure() { + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + + BaseUnit YARD = new BaseUnit(METER, 3.0, "yard", "yd"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + + assertEquals(LENGTH, METER.getMeasure()); + assertEquals(LENGTH, YARD.getMeasure()); + assertEquals(TIME, SECOND.getMeasure()); + assertEquals(TIME, MINUTE.getMeasure()); + } + + @Test + @UnitTestMethod(target = BaseUnit.class, name = "getLongName", args = {}) + public void tetsGetLongName() { + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + + BaseUnit YARD = new BaseUnit(METER, 3.0, "yard", "yd"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + + assertEquals("meter", METER.getLongName()); + assertEquals("second", SECOND.getLongName()); + assertEquals("yard", YARD.getLongName()); + assertEquals("minute", MINUTE.getLongName()); + + } + + @Test + @UnitTestMethod(target = BaseUnit.class, name = "getShortName", args = {}) + public void testGetShortName() { + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + + BaseUnit YARD = new BaseUnit(METER, 3.0, "yard", "yd"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + + assertEquals("m", METER.getShortName()); + assertEquals("s", SECOND.getShortName()); + assertEquals("yd", YARD.getShortName()); + assertEquals("min", MINUTE.getShortName()); + } + + @Test + @UnitTestMethod(target = BaseUnit.class, name = "getValue", args = {}) + public void testGetValue() { + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + + BaseUnit YARD = new BaseUnit(METER, 3.0, "yard", "yd"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + + assertEquals(1, METER.getValue()); + assertEquals(1, SECOND.getValue()); + assertEquals(3.0, YARD.getValue()); + assertEquals(60.0, MINUTE.getValue()); + } + + @Test + @UnitTestMethod(target = BaseUnit.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6170924863472890528L); + + //equal objects have equal hash codes + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + + // Length + BaseUnit METER1 = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit METER2 = new BaseUnit(LENGTH, "meter", "m"); + assertEquals(METER1, METER2); + assertEquals(METER1.hashCode(), METER2.hashCode()); + + BaseUnit SECOND1 = new BaseUnit(TIME, "second", "s"); + BaseUnit SECOND2 = new BaseUnit(TIME, "second", "s"); + assertEquals(SECOND1, SECOND2); + assertEquals(SECOND1.hashCode(), SECOND2.hashCode()); + + + //hash codes are reasonably distributed + Sethashcodes = new LinkedHashSet<>(); + + for(int i= 0;i<100;i++) { + BaseUnit baseUnit; + if(randomGenerator.nextBoolean()) { + baseUnit = new BaseUnit(LENGTH, "name"+randomGenerator.nextInt(1000000), "n"+randomGenerator.nextInt(1000000)); + }else { + baseUnit = new BaseUnit(TIME, "name"+randomGenerator.nextInt(1000000), "n"+randomGenerator.nextInt(1000000)); + } + hashcodes.add(baseUnit.hashCode()); + } + assertEquals(100, hashcodes.size()); + + } + + @Test + @UnitTestMethod(target = BaseUnit.class, name = "toString", args = {}) + public void testToString() { + Measure LENGTH = new Measure("length"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit KILOMETER = new BaseUnit(METER, 1000, "kilometer", "km"); + String actualValue = KILOMETER.toString(); + String expectedValue = "BaseUnit [measure=Measure [name=length], value=1000.0, name=kilometer, shortName=km]"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = BaseUnit.class, name = "asComposedUnit", args = {}) + public void testAsComposedUnit() { + Measure LENGTH = new Measure("length"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01,"centimeter", "cm"); + + ComposedUnit expectedValue = ComposedUnit.builder().setBaseUnit(METER, 1).build(); + ComposedUnit actualValue = METER.asComposedUnit(); + assertEquals(expectedValue, actualValue); + + expectedValue = ComposedUnit.builder().setBaseUnit(CM, 1).build(); + actualValue = CM.asComposedUnit(); + assertEquals(expectedValue, actualValue); + + + } +} diff --git a/src/test/java/gov/hhs/aspr/ms/util/measures/AT_ComposedUnit.java b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_ComposedUnit.java new file mode 100644 index 0000000..468d8f1 --- /dev/null +++ b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_ComposedUnit.java @@ -0,0 +1,832 @@ +package gov.hhs.aspr.ms.util.measures; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.util.annotations.UnitTag; +import gov.hhs.aspr.ms.util.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.util.errors.ContractException; +import gov.hhs.aspr.ms.util.random.RandomGeneratorProvider; + +public class AT_ComposedUnit { + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "getBaseUnits", args = {}) + public void testGetBaseUnits() { + + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + BaseUnit OUNCE = new BaseUnit(POUND, 1.0 / 16, "ounce", "oz"); + + // case 1 + ComposedUnit composedUnit = ComposedUnit.builder().setBaseUnit(OUNCE, 2).setBaseUnit(HOUR, 3).build(); + List expectedBaseUnits = new ArrayList<>(); + expectedBaseUnits.add(OUNCE); + expectedBaseUnits.add(HOUR); + + List actualBaseUnits = composedUnit.getBaseUnits(); + assertEquals(expectedBaseUnits, actualBaseUnits); + + // case 2 + composedUnit = ComposedUnit.builder().setBaseUnit(HOUR, -2).setBaseUnit(MILE, 2).build(); + expectedBaseUnits = new ArrayList<>(); + expectedBaseUnits.add(MILE); + expectedBaseUnits.add(HOUR); + + actualBaseUnits = composedUnit.getBaseUnits(); + assertEquals(expectedBaseUnits, actualBaseUnits); + + // case 3 + composedUnit = ComposedUnit.builder().setBaseUnit(KILOGRAM, 1).setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + expectedBaseUnits = new ArrayList<>(); + expectedBaseUnits.add(METER); + expectedBaseUnits.add(KILOGRAM); + expectedBaseUnits.add(SECOND); + + actualBaseUnits = composedUnit.getBaseUnits(); + assertEquals(expectedBaseUnits, actualBaseUnits); + + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "builder", args = {}) + public void testBuilder() { + assertNotNull(ComposedUnit.builder()); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "equals", args = { Object.class }) + public void testEquals() { + // Two composed units are equal if and only if their units and powers are equal + + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + ComposedUnit MPS1 = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .build(); + + ComposedUnit MPS2 = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .build(); + + ComposedUnit MPH1 = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .build(); + + ComposedUnit MPH2 = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .setNames("miles per hour", "mph")// + .build(); + + assertEquals(MPS1, MPS2); + assertNotEquals(MPS1, MPH1); + assertNotEquals(MPH1, MPH2); + + // not equal null + assertFalse(MPS1.equals(null)); + assertFalse(MPS2.equals(null)); + assertFalse(MPH1.equals(null)); + assertFalse(MPH2.equals(null)); + + // not equal to other types + assertFalse(MPS1.equals(new Object())); + assertFalse(MPS2.equals(new Object())); + assertFalse(MPH1.equals(new Object())); + assertFalse(MPH2.equals(new Object())); + + // reflexive + assertTrue(MPS1.equals(MPS1)); + assertTrue(MPS2.equals(MPS2)); + assertTrue(MPH1.equals(MPH1)); + assertTrue(MPH2.equals(MPH2)); + + // symmetric, transitive and stable + for (int i = 0; i < 5; i++) { + assertTrue(MPS1.equals(MPS2)); + assertTrue(MPS2.equals(MPS1)); + } + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "getLongLabel", args = {}) + public void testGetLongLabel() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + + // sample 1 + String actualValue = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .build().getLongLabel(); + + String expectedValue = "meter^1 second^-1"; + assertEquals(expectedValue, actualValue); + + // sample 2 + actualValue = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .build().getLongLabel(); + + expectedValue = "mile^1 hour^-1"; + assertEquals(expectedValue, actualValue); + + // sample 2 + actualValue = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(KILOGRAM, 1)// + .setBaseUnit(SECOND, -2)// + .build().getLongLabel(); + + expectedValue = "meter^1 kilogram^1 second^-2"; + assertEquals(expectedValue, actualValue); + + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "getLongName", args = {}) + public void testGetLongName() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + // sample 1 -- without setting long name + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .build(); + String actualValue = composedUnit.getLongName(); + String expectedValue = composedUnit.getLongLabel(); + assertEquals(expectedValue, actualValue); + + // sample 2 -- with setting long name + expectedValue = "miles_per_hour"; + composedUnit = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .setLongName(expectedValue).build(); + actualValue = composedUnit.getLongName(); + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "getShortName", args = {}) + public void testGetShortName() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + // sample 1 -- without setting short name + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .build(); + String actualValue = composedUnit.getShortName(); + String expectedValue = composedUnit.getShortLabel(); + assertEquals(expectedValue, actualValue); + + // sample 2 -- with setting short name + expectedValue = "mph"; + composedUnit = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .setShortName(expectedValue).build(); + actualValue = composedUnit.getShortName(); + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "getPower", args = { Measure.class }) + public void testGetPower() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + Measure AMPERE = new Measure("electric_current"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(KILOGRAM, 1)// + .setBaseUnit(METER, 2)// + .setBaseUnit(SECOND, -3)// + .build(); + + Optional optional = composedUnit.getPower(MASS); + assertTrue(optional.isPresent()); + assertEquals(1, optional.get()); + + optional = composedUnit.getPower(LENGTH); + assertTrue(optional.isPresent()); + assertEquals(2, optional.get()); + + optional = composedUnit.getPower(TIME); + assertTrue(optional.isPresent()); + assertEquals(-3, optional.get()); + + optional = composedUnit.getPower(AMPERE); + assertFalse(optional.isPresent()); + + // precondition test: if the measure is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .build().getPower(null); + }); + + assertEquals(MeasuresError.NULL_MEASURE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "getShortLabel", args = {}) + public void testGetShortLabel() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + + // sample 1 + String actualValue = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .build().getShortLabel(); + + String expectedValue = "m^1 s^-1"; + assertEquals(expectedValue, actualValue); + + // sample 2 + actualValue = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .build().getShortLabel(); + + expectedValue = "mi^1 h^-1"; + assertEquals(expectedValue, actualValue); + + // sample 3 + actualValue = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(KILOGRAM, 1)// + .setBaseUnit(SECOND, -2)// + .build().getShortLabel(); + + expectedValue = "m^1 kg^1 s^-2"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "getBaseUnit", args = { Measure.class }) + public void testGetBaseUnit() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + Measure AMPERE = new Measure("electric_current"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(KILOGRAM, 1)// + .setBaseUnit(METER, 2)// + .setBaseUnit(SECOND, -3)// + .build(); + + Optional optional = composedUnit.getBaseUnit(MASS); + assertTrue(optional.isPresent()); + assertEquals(KILOGRAM, optional.get()); + + optional = composedUnit.getBaseUnit(LENGTH); + assertTrue(optional.isPresent()); + assertEquals(METER, optional.get()); + + optional = composedUnit.getBaseUnit(TIME); + assertTrue(optional.isPresent()); + assertEquals(SECOND, optional.get()); + + optional = composedUnit.getBaseUnit(AMPERE); + assertFalse(optional.isPresent()); + + // precondition test: if the measure is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .build().getBaseUnit(null); + }); + + assertEquals(MeasuresError.NULL_MEASURE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "getValue", args = {}) + public void getValue() { + // The unit-less case should result in a value of 1 + assertEquals(1.0, ComposedUnit.builder()// + .build().getValue()); + + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + + // example 1: miles per hour + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .setNames("miles per hour", "mph")// + .build(); + + /* + * note: we calculate the expected value by mirroring the order of operations in + * the units above so that we can derive an exact value + */ + double expectedValue = (1.0 / 100) * 2.54 * 12 * 5280 / 3600; + double actualValue = composedUnit.getValue(); + + assertEquals(expectedValue, actualValue); + + // example 2 + composedUnit = ComposedUnit.builder()// + .setBaseUnit(POUND, 1)// + .setBaseUnit(INCH, -2)// + .setNames("pounds per square foot", "psi")// + .build(); + + double poundValue = 0.45359237; + double inchValue = 0.01 * 2.54; + expectedValue = poundValue / (inchValue * inchValue); + actualValue = composedUnit.getValue(); + assertEquals(expectedValue, actualValue); + + } + + /* + * Creates a randomized ComposedUnit using several time, length and mass units + * to powers in [-5,-1]U[1,5]. + */ + private ComposedUnit getRandomComposedUnit(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + List timeUnits = new ArrayList<>(); + timeUnits.add(SECOND); + timeUnits.add(MINUTE); + timeUnits.add(HOUR); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + List distanceUnits = new ArrayList<>(); + distanceUnits.add(METER); + distanceUnits.add(CM); + distanceUnits.add(INCH); + distanceUnits.add(FOOT); + distanceUnits.add(MILE); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + BaseUnit OUNCE = new BaseUnit(POUND, 1.0 / 16, "ounce", "oz"); + List massUnits = new ArrayList<>(); + massUnits.add(KILOGRAM); + massUnits.add(POUND); + massUnits.add(OUNCE); + + BaseUnit timeUnit = timeUnits.get(randomGenerator.nextInt(timeUnits.size())); + BaseUnit distanceUnit = distanceUnits.get(randomGenerator.nextInt(distanceUnits.size())); + BaseUnit massUnit = massUnits.get(randomGenerator.nextInt(massUnits.size())); + + int timePower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + int distancePower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + int massPower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + + return ComposedUnit.builder()// + .setBaseUnit(timeUnit, timePower)// + .setBaseUnit(distanceUnit, distancePower)// + .setBaseUnit(massUnit, massPower)// + .build(); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5013191714334523037L); + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + ComposedUnit composedUnit1 = getRandomComposedUnit(seed); + ComposedUnit composedUnit2 = getRandomComposedUnit(seed); + assertEquals(composedUnit1, composedUnit2); + assertEquals(composedUnit1.hashCode(), composedUnit2.hashCode()); + } + + // hash codes are reasonably distributed + + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + ComposedUnit composedUnit = getRandomComposedUnit(randomGenerator.nextLong()); + hashCodes.add(composedUnit.hashCode()); + } + assertTrue(hashCodes.size() > 95); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "isCompatible", args = { ComposedUnit.class }) + public void testIsCompatible() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + BaseUnit OUNCE = new BaseUnit(POUND, 1.0 / 16, "ounce", "oz"); + + ComposedUnit composedUnit1 = ComposedUnit.builder().setBaseUnit(HOUR, 1).setBaseUnit(METER, -1).build(); + ComposedUnit composedUnit2 = ComposedUnit.builder().setBaseUnit(MINUTE, 1).setBaseUnit(FOOT, -1).build(); + assertTrue(composedUnit1.isCompatible(composedUnit2)); + assertTrue(composedUnit2.isCompatible(composedUnit1)); + + composedUnit1 = ComposedUnit.builder().setBaseUnit(MILE, 1).setBaseUnit(HOUR, -1).build(); + composedUnit2 = ComposedUnit.builder().setBaseUnit(FOOT, 1).setBaseUnit(SECOND, -2).build(); + assertFalse(composedUnit1.isCompatible(composedUnit2)); + assertFalse(composedUnit2.isCompatible(composedUnit1)); + + composedUnit1 = ComposedUnit.builder().setBaseUnit(OUNCE, 1).build(); + composedUnit2 = ComposedUnit.builder().setBaseUnit(POUND, 1).build(); + assertTrue(composedUnit1.isCompatible(composedUnit2)); + assertTrue(composedUnit2.isCompatible(composedUnit1)); + + composedUnit1 = ComposedUnit.builder().setBaseUnit(OUNCE, 1).build(); + composedUnit2 = ComposedUnit.builder().setBaseUnit(POUND, 2).build(); + assertFalse(composedUnit1.isCompatible(composedUnit2)); + assertFalse(composedUnit2.isCompatible(composedUnit1)); + + // precondition test: if the composed unit is null + ContractException contractException = assertThrows(ContractException.class, + () -> ComposedUnit.builder().build().isCompatible(null)); + assertEquals(MeasuresError.NULL_COMPOSITE, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "isMeasureLess", args = {}) + public void testIsMeasureLess() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(METER, 0)// + .setBaseUnit(SECOND, 0)// + .setNames("long_name", "short_name").build(); + + assertTrue(composedUnit.isMeasureLess()); + + composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, 0)// + .setNames("long_name", "short_name").build(); + assertFalse(composedUnit.isMeasureLess()); + } + + @Test + @UnitTestMethod(target = ComposedUnit.class, name = "toString", args = {}) + public void testToString() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPS = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .setNames("meters per second", "mps").build(); + String actualValue = MPS.toString(); + + String expectedValue = "ComposedUnit [value=1.0, longName=meters per second, shortName=mps, measures={Measure [name=length]=UnitPower [baseUnit=BaseUnit [measure=Measure [name=length], value=1.0, name=meter, shortName=m], power=1], Measure [name=time]=UnitPower [baseUnit=BaseUnit [measure=Measure [name=time], value=1.0, name=second, shortName=s], power=-1]}]"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = ComposedUnit.Builder.class, name = "build", args = {}, tags = { UnitTag.LOCAL_PROXY }) + public void testBuild() { + // covered by the other tests + } + + @Test + @UnitTestMethod(target = ComposedUnit.Builder.class, name = "setBaseUnit", args = { BaseUnit.class, int.class }) + public void testSetBaseUnit() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(KILOGRAM, 1)// + .setBaseUnit(METER, 2)// + .setBaseUnit(SECOND, -3)// + .build(); + + Optional optionalUnit = composedUnit.getBaseUnit(MASS); + assertTrue(optionalUnit.isPresent()); + assertEquals(KILOGRAM, optionalUnit.get()); + Optional optionalPower = composedUnit.getPower(MASS); + assertTrue(optionalPower.isPresent()); + assertEquals(1, optionalPower.get()); + + optionalUnit = composedUnit.getBaseUnit(LENGTH); + assertTrue(optionalUnit.isPresent()); + assertEquals(METER, optionalUnit.get()); + optionalPower = composedUnit.getPower(LENGTH); + assertTrue(optionalPower.isPresent()); + assertEquals(2, optionalPower.get()); + + optionalUnit = composedUnit.getBaseUnit(TIME); + assertTrue(optionalUnit.isPresent()); + assertEquals(SECOND, optionalUnit.get()); + optionalPower = composedUnit.getPower(TIME); + assertTrue(optionalPower.isPresent()); + assertEquals(-3, optionalPower.get()); + + // precondition test: if the base unit is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ComposedUnit.builder()// + .setBaseUnit(null, 1)// + .build(); + }); + + assertEquals(MeasuresError.NULL_UNIT, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = ComposedUnit.Builder.class, name = "setLongName", args = { String.class }) + public void testSetLongName() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + // sample 1 -- without setting long name + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .build(); + String actualValue = composedUnit.getLongName(); + String expectedValue = composedUnit.getLongLabel(); + assertEquals(expectedValue, actualValue); + + // sample 2 -- with setting long name + expectedValue = "miles_per_hour"; + composedUnit = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .setLongName(expectedValue).build(); + actualValue = composedUnit.getLongName(); + assertEquals(expectedValue, actualValue); + + // sample 3 -- with setting long name to null + composedUnit = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .setLongName(null).build(); + expectedValue = composedUnit.getLongLabel(); + actualValue = composedUnit.getLongName(); + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = ComposedUnit.Builder.class, name = "setShortName", args = { String.class }) + public void testSetShortName() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + + // sample 1 -- without setting short name + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .build(); + String actualValue = composedUnit.getShortName(); + String expectedValue = composedUnit.getShortLabel(); + assertEquals(expectedValue, actualValue); + + // sample 2 -- with setting short name + expectedValue = "mph"; + composedUnit = ComposedUnit.builder()// + .setBaseUnit(MILE, 1)// + .setBaseUnit(HOUR, -1)// + .setShortName(expectedValue).build(); + actualValue = composedUnit.getShortName(); + assertEquals(expectedValue, actualValue); + + // sample 3 -- with setting short name to null + composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .setShortName(null).build(); + actualValue = composedUnit.getShortName(); + expectedValue = composedUnit.getShortLabel(); + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = ComposedUnit.Builder.class, name = "setNames", args = { String.class, String.class }) + public void testSetNames() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + + // case 1 -- null long name, null short name + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .setNames(null, null)// + .build(); + + String actualValue = composedUnit.getLongName(); + String expectedValue = composedUnit.getLongLabel(); + assertEquals(expectedValue, actualValue); + + actualValue = composedUnit.getShortName(); + expectedValue = composedUnit.getShortLabel(); + assertEquals(expectedValue, actualValue); + + // case 2 -- null long name, non-null short name + composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .setNames(null, "short_name").build(); + + actualValue = composedUnit.getLongName(); + expectedValue = composedUnit.getLongLabel(); + assertEquals(expectedValue, actualValue); + + actualValue = composedUnit.getShortName(); + expectedValue = "short_name"; + assertEquals(expectedValue, actualValue); + + // case 3 -- non-null long name, null short name + composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .setNames("long_name", null).build(); + + actualValue = composedUnit.getLongName(); + expectedValue = "long_name"; + assertEquals(expectedValue, actualValue); + + actualValue = composedUnit.getShortName(); + expectedValue = composedUnit.getShortLabel(); + assertEquals(expectedValue, actualValue); + + // case 4 -- non-null long name, non-null short name + composedUnit = ComposedUnit.builder()// + .setBaseUnit(METER, 1)// + .setBaseUnit(SECOND, -1)// + .setNames("long_name", "short_name").build(); + + actualValue = composedUnit.getLongName(); + expectedValue = "long_name"; + assertEquals(expectedValue, actualValue); + + actualValue = composedUnit.getShortName(); + expectedValue = "short_name"; + assertEquals(expectedValue, actualValue); + } + +} diff --git a/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Constant.java b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Constant.java new file mode 100644 index 0000000..d30c69e --- /dev/null +++ b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Constant.java @@ -0,0 +1,416 @@ +package gov.hhs.aspr.ms.util.measures; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.util.annotations.UnitTestConstructor; +import gov.hhs.aspr.ms.util.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.util.errors.ContractException; +import gov.hhs.aspr.ms.util.random.RandomGeneratorProvider; + +public class AT_Constant { + + @Test + @UnitTestConstructor(target = Constant.class, args = { Quantity.class, String.class, String.class }) + public void testConstant() { + // postcondition tests covered by the remaining tests + + Measure LENGTH = new Measure("length"); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + + Quantity quantity = new Quantity(METER, 103.34778); + // precondition test: if the long name is null + ContractException contractException = assertThrows(ContractException.class, + () -> new Constant(quantity, null, "short_name")); + assertEquals(MeasuresError.NULL_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the long name is empty or contains only white space + // characters + contractException = assertThrows(ContractException.class, () -> new Constant(quantity, "", "short_name")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> new Constant(quantity, " ", "short_name")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the short name is null + contractException = assertThrows(ContractException.class, () -> new Constant(quantity, "long_name", null)); + assertEquals(MeasuresError.NULL_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the short name is empty or contains only white space + // characters + contractException = assertThrows(ContractException.class, () -> new Constant(quantity, "long_name", "")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + contractException = assertThrows(ContractException.class, () -> new Constant(quantity, "long_name", " ")); + assertEquals(MeasuresError.BLANK_UNIT_NAME, contractException.getErrorType()); + + // precondition test: if the quantity is null + contractException = assertThrows(ContractException.class, () -> new Constant(null, "long_name", "short_name")); + assertEquals(MeasuresError.NULL_QUANTITY, contractException.getErrorType()); + } + + /* + * Creates a randomized Constant using several time, length and mass units to + * powers in [-5,-1]U[1,5], a random value and randomized long and short names. + */ + private Constant getRandomConstant(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + List timeUnits = new ArrayList<>(); + timeUnits.add(SECOND); + timeUnits.add(MINUTE); + timeUnits.add(HOUR); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + List distanceUnits = new ArrayList<>(); + distanceUnits.add(METER); + distanceUnits.add(CM); + distanceUnits.add(INCH); + distanceUnits.add(FOOT); + distanceUnits.add(MILE); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + BaseUnit OUNCE = new BaseUnit(POUND, 1.0 / 16, "ounce", "oz"); + List massUnits = new ArrayList<>(); + massUnits.add(KILOGRAM); + massUnits.add(POUND); + massUnits.add(OUNCE); + + BaseUnit timeUnit = timeUnits.get(randomGenerator.nextInt(timeUnits.size())); + BaseUnit distanceUnit = distanceUnits.get(randomGenerator.nextInt(distanceUnits.size())); + BaseUnit massUnit = massUnits.get(randomGenerator.nextInt(massUnits.size())); + + int timePower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + int distancePower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + int massPower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(timeUnit, timePower)// + .setBaseUnit(distanceUnit, distancePower)// + .setBaseUnit(massUnit, massPower)// + .build(); + + double value = randomGenerator.nextDouble(); + Quantity quantity = new Quantity(composedUnit, value); + String longName = "name_" + randomGenerator.nextInt(10000); + String shortName = "n_" + randomGenerator.nextInt(10000); + return new Constant(quantity, longName, shortName); + } + + @Test + @UnitTestMethod(target = Constant.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8800448024744882881L); + // Two constants are equal if and only if their quantities and names are equal + + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + Constant constant1 = getRandomConstant(seed); + Constant constant2 = getRandomConstant(seed); + assertEquals(constant1, constant2); + } + + // not equal to null + for (int i = 0; i < 30; i++) { + Constant constant = getRandomConstant(randomGenerator.nextLong()); + assertFalse(constant.equals(null)); + } + + // not equal to another type + for (int i = 0; i < 30; i++) { + Constant constant = getRandomConstant(randomGenerator.nextLong()); + assertFalse(constant.equals(new Object())); + } + + // reflexive + for (int i = 0; i < 30; i++) { + Constant constant = getRandomConstant(randomGenerator.nextLong()); + assertTrue(constant.equals(constant)); + } + + // symmetric, transitive, stable + for (int i = 0; i < 30; i++) { + Constant constant = getRandomConstant(randomGenerator.nextLong()); + assertTrue(constant.equals(constant)); + } + + } + + /* + * Creates two randomized quantities using several time, length and mass units + * to powers in [-5,-1]U[1,5]. They will have random values, but will agree on + * measures and powers. + */ + private Pair getRandomCompatibleQuanties(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + List timeUnits = new ArrayList<>(); + timeUnits.add(SECOND); + timeUnits.add(MINUTE); + timeUnits.add(HOUR); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + List distanceUnits = new ArrayList<>(); + distanceUnits.add(METER); + distanceUnits.add(CM); + distanceUnits.add(INCH); + distanceUnits.add(FOOT); + distanceUnits.add(MILE); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + BaseUnit OUNCE = new BaseUnit(POUND, 1.0 / 16, "ounce", "oz"); + List massUnits = new ArrayList<>(); + massUnits.add(KILOGRAM); + massUnits.add(POUND); + massUnits.add(OUNCE); + + BaseUnit timeUnit1 = timeUnits.get(randomGenerator.nextInt(timeUnits.size())); + BaseUnit distanceUnit1 = distanceUnits.get(randomGenerator.nextInt(distanceUnits.size())); + BaseUnit massUnit1 = massUnits.get(randomGenerator.nextInt(massUnits.size())); + + BaseUnit timeUnit2 = timeUnits.get(randomGenerator.nextInt(timeUnits.size())); + BaseUnit distanceUnit2 = distanceUnits.get(randomGenerator.nextInt(distanceUnits.size())); + BaseUnit massUnit2 = massUnits.get(randomGenerator.nextInt(massUnits.size())); + + int timePower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + int distancePower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + int massPower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + + ComposedUnit composedUnit1 = ComposedUnit.builder()// + .setBaseUnit(timeUnit1, timePower)// + .setBaseUnit(distanceUnit1, distancePower)// + .setBaseUnit(massUnit1, massPower)// + .build(); + + ComposedUnit composedUnit2 = ComposedUnit.builder()// + .setBaseUnit(timeUnit2, timePower)// + .setBaseUnit(distanceUnit2, distancePower)// + .setBaseUnit(massUnit2, massPower)// + .build(); + + double value1 = randomGenerator.nextDouble(); + double value2 = randomGenerator.nextDouble(); + + Quantity quantity1 = new Quantity(composedUnit1, value1); + Quantity quantity2 = new Quantity(composedUnit2, value2); + + return new Pair<>(quantity1, quantity2); + } + + @Test + @UnitTestMethod(target = Constant.class, name = "getConvertedValue", args = { Quantity.class }) + public void testGetConvertedValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7009329198205894838L); + // Two constants are equal if and only if their quantities and names are equal + + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + Pair pair = getRandomCompatibleQuanties(seed); + Quantity quantity1 = pair.getFirst(); + Quantity quantity2 = pair.getSecond(); + Constant constant = new Constant(quantity1, "long_name", "short_name"); + double actualValue = constant.getConvertedValue(quantity2); + double expectedValue = quantity2.rebase(quantity1.getComposedUnit()).getValue() / quantity1.getValue(); + /* + * Slight differences in how the expected and actual values are calculated + * prevent us from having an exact match + */ + assertTrue(1 - (actualValue / expectedValue) < 1e-12); + } + + } + + /* + * Returns a randomized time quantity. + */ + private Quantity getRandomizedTimeQuanity(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Measure measure = new Measure("TIME"); + + BaseUnit SECOND = new BaseUnit(measure, "second", "sec"); + BaseUnit MINUTE = new BaseUnit(SECOND,60,"minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE,60,"hour", "hr"); + BaseUnit DAY = new BaseUnit(HOUR,24,"day", "dy"); + + List baseUnits = new ArrayList<>(); + baseUnits.add(SECOND); + baseUnits.add(MINUTE); + baseUnits.add(HOUR); + baseUnits.add(DAY); + + BaseUnit baseUnit = baseUnits.get(randomGenerator.nextInt(baseUnits.size())); + return new Quantity(baseUnit, randomGenerator.nextDouble()/10+0.9); + } + + @Test + @UnitTestMethod(target = Constant.class, name = "getLongString", args = { Quantity.class }) + public void testGetLongString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(778126436041924435L); + + for (int i = 0; i < 30; i++) { + Quantity constantQuantity = getRandomizedTimeQuanity(randomGenerator.nextLong()); + String longName = "long_name_"+randomGenerator.nextInt(100000); + Constant constant = new Constant(constantQuantity, longName, "short_name"); + Quantity conversionQuantity = getRandomizedTimeQuanity(randomGenerator.nextLong()); + + double normal1 = constantQuantity.getComposedUnit().getValue()*constantQuantity.getValue(); + double normal2 = conversionQuantity.getComposedUnit().getValue()*conversionQuantity.getValue(); + + String expectedValue = normal2/normal1+" "+longName; + + String actualValue = constant.getLongString(conversionQuantity); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Constant.class, name = "getShortString", args = { Quantity.class }) + public void testGetShortString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2863726012144072861L); + + for (int i = 0; i < 30; i++) { + Quantity constantQuantity = getRandomizedTimeQuanity(randomGenerator.nextLong()); + String shortName = "short_name_"+randomGenerator.nextInt(100000); + Constant constant = new Constant(constantQuantity, "long_name", shortName); + Quantity conversionQuantity = getRandomizedTimeQuanity(randomGenerator.nextLong()); + + double normal1 = constantQuantity.getComposedUnit().getValue()*constantQuantity.getValue(); + double normal2 = conversionQuantity.getComposedUnit().getValue()*conversionQuantity.getValue(); + + String expectedValue = normal2/normal1+" "+shortName; + + String actualValue = constant.getShortString(conversionQuantity); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Constant.class, name = "getLongName", args = {}) + public void testGetLongName() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(598688435087897951L); + + for (int i = 0; i < 30; i++) { + Quantity constantQuantity = getRandomizedTimeQuanity(randomGenerator.nextLong()); + String expectedValue = "long_name_"+randomGenerator.nextInt(100000); + Constant constant = new Constant(constantQuantity, expectedValue, "short_name"); + String actualValue = constant.getLongName(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Constant.class, name = "getQuantity", args = {}) + public void testGetQuantity() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5230784933008333474L); + for (int i = 0; i < 30; i++) { + Quantity expectedValue = getRandomizedTimeQuanity(randomGenerator.nextLong()); + Constant constant = new Constant(expectedValue, "long_name", "short_name"); + Quantity actualValue = constant.getQuantity(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Constant.class, name = "getShortName", args = {}) + public void testGetShortName() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3900313195340096046L); + + for (int i = 0; i < 30; i++) { + Quantity constantQuantity = getRandomizedTimeQuanity(randomGenerator.nextLong()); + String expectedValue = "short_name_"+randomGenerator.nextInt(100000); + Constant constant = new Constant(constantQuantity,"long_name", expectedValue); + String actualValue = constant.getShortName(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Constant.class, name = "getShortString", args = { Quantity.class }) + public void getShortString() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(598688435087897951L); + + for (int i = 0; i < 30; i++) { + Quantity constantQuantity = getRandomizedTimeQuanity(randomGenerator.nextLong()); + String expectedValue = "short_name_"+randomGenerator.nextInt(100000); + Constant constant = new Constant(constantQuantity, "long_name",expectedValue); + String actualValue = constant.getShortName(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Constant.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3747610952977439670L); + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + Constant constant1 = getRandomConstant(seed); + Constant constant2 = getRandomConstant(seed); + assertEquals(constant1, constant2); + assertEquals(constant1.hashCode(), constant2.hashCode()); + } + + // hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Constant constant = getRandomConstant(randomGenerator.nextLong()); + hashCodes.add(constant.hashCode()); + } + assertEquals(100, hashCodes.size()); + + } + + @Test + @UnitTestMethod(target = Constant.class, name = "toString", args = {}) + public void testToString() { + + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + Quantity quantity = new Quantity(MPSS, 9.80665); + + Constant EARTH_GRAVITY = new Constant(quantity, "earth_gravity", "g"); + + String expectedValue = "Constant [longName=earth_gravity, shortName=g, quantity=Quantity [composedUnit=ComposedUnit [value=1.0, longName=null, shortName=null, measures={Measure [name=length]=UnitPower [baseUnit=BaseUnit [measure=Measure [name=length], value=1.0, name=meter, shortName=m], power=1], Measure [name=time]=UnitPower [baseUnit=BaseUnit [measure=Measure [name=time], value=1.0, name=second, shortName=s], power=-2]}], value=9.80665]]"; + String actualValue = EARTH_GRAVITY.toString(); + assertEquals(expectedValue, actualValue); + } + +} diff --git a/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Measure.java b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Measure.java new file mode 100644 index 0000000..e86ac9f --- /dev/null +++ b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Measure.java @@ -0,0 +1,118 @@ +package gov.hhs.aspr.ms.util.measures; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.util.annotations.UnitTestConstructor; +import gov.hhs.aspr.ms.util.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.util.errors.ContractException; +import gov.hhs.aspr.ms.util.random.RandomGeneratorProvider; + +public class AT_Measure { + + @Test + @UnitTestConstructor(target = Measure.class, args = { String.class }) + public void testMeasure() { + // there are no postcondition tests + + // precondition test: if the name is null + ContractException contractException = assertThrows(ContractException.class, () -> new Measure(null)); + assertEquals(MeasuresError.NULL_MEASURE_NAME, contractException.getErrorType()); + + // precondition test: if the name is blank + contractException = assertThrows(ContractException.class, () -> new Measure("")); + assertEquals(MeasuresError.BLANK_MEASURE_NAME, contractException.getErrorType()); + + // precondition test: if the name is blank + contractException = assertThrows(ContractException.class, () -> new Measure(" ")); + assertEquals(MeasuresError.BLANK_MEASURE_NAME, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = Measure.class, name = "equals", args = { Object.class }) + public void testEquals() { + + // only measures with the same name are equal + assertEquals(new Measure("a"), new Measure("a")); + assertNotEquals(new Measure("a"), new Measure("b")); + assertNotEquals(new Measure("a"), new Measure("A")); + + // not equal to null + Measure a = new Measure("a"); + Measure b = new Measure("b"); + assertFalse(a.equals(null)); + assertFalse(b.equals(null)); + + // not equal to other object + assertFalse(a.equals(new Object())); + + // reflexive + assertTrue(a.equals(a)); + assertTrue(b.equals(b)); + + // symmetric, transitive, stable + Measure c1 = new Measure("c"); + Measure c2 = new Measure("c"); + for (int i = 0; i < 5; i++) { + assertTrue(c1.equals(c2)); + assertTrue(c2.equals(c1)); + } + } + + @Test + @UnitTestMethod(target = Measure.class, name = "getName", args = {}) + public void testGetName() { + + for (int i = 0; i < 30; i++) { + String expectedValue = "name" + i; + Measure measure = new Measure(expectedValue); + String actualValue = measure.getName(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Measure.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(3826969971798275453L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + String name = "name"+randomGenerator.nextInt(1000000000); + Measure m1 = new Measure(name); + Measure m2 = new Measure(name); + assertEquals(m1, m2); + assertEquals(m1.hashCode(), m2.hashCode()); + } + + // hash codes are reasonably distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Measure measure = new Measure("name"+randomGenerator.nextInt(1000000000)); + hashCodes.add(measure.hashCode()); + } + assertEquals(100, hashCodes.size()); + + + } + + @Test + @UnitTestMethod(target = Measure.class, name = "toString", args = {}) + public void testToString() { + Measure measure = new Measure("someMeasure"); + String actualValue = measure.toString(); + String expectedValue = "Measure [name=someMeasure]"; + assertEquals(expectedValue, actualValue); + } + +} diff --git a/src/test/java/gov/hhs/aspr/ms/util/measures/AT_MeasuresError.java b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_MeasuresError.java new file mode 100644 index 0000000..08f686c --- /dev/null +++ b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_MeasuresError.java @@ -0,0 +1,28 @@ +package gov.hhs.aspr.ms.util.measures; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.util.annotations.UnitTestMethod; + +public class AT_MeasuresError { + + @Test + @UnitTestMethod(target = MeasuresError.class, name = "getDescription", args = {}) + public void test() { + // show that each description is a unique, non-null and non-empty string + Set descriptions = new LinkedHashSet<>(); + for (MeasuresError measuresError : MeasuresError.values()) { + String description = measuresError.getDescription(); + assertNotNull(description, "null description for " + measuresError); + assertTrue(description.length() > 0, "empty string for " + measuresError); + boolean unique = descriptions.add(description); + assertTrue(unique, "description for " + measuresError + " is not unique"); + } + } +} diff --git a/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Quantity.java b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Quantity.java new file mode 100644 index 0000000..be5695c --- /dev/null +++ b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_Quantity.java @@ -0,0 +1,1346 @@ +package gov.hhs.aspr.ms.util.measures; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.math3.random.RandomGenerator; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Pair; +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.util.annotations.UnitTestConstructor; +import gov.hhs.aspr.ms.util.annotations.UnitTestMethod; +import gov.hhs.aspr.ms.util.errors.ContractException; +import gov.hhs.aspr.ms.util.random.RandomGeneratorProvider; +import gov.hhs.aspr.ms.util.wrappers.MutableInteger; + +public class AT_Quantity { + + /* + * Creates a randomized ComposedUnit using several time, length and mass units + * to powers in [-5,-1]U[1,5]. + */ + private ComposedUnit getRandomComposedUnit(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + List timeUnits = new ArrayList<>(); + timeUnits.add(SECOND); + timeUnits.add(MINUTE); + timeUnits.add(HOUR); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + List distanceUnits = new ArrayList<>(); + distanceUnits.add(METER); + distanceUnits.add(CM); + distanceUnits.add(INCH); + distanceUnits.add(FOOT); + distanceUnits.add(MILE); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + BaseUnit OUNCE = new BaseUnit(POUND, 1.0 / 16, "ounce", "oz"); + List massUnits = new ArrayList<>(); + massUnits.add(KILOGRAM); + massUnits.add(POUND); + massUnits.add(OUNCE); + + BaseUnit timeUnit = timeUnits.get(randomGenerator.nextInt(timeUnits.size())); + BaseUnit distanceUnit = distanceUnits.get(randomGenerator.nextInt(distanceUnits.size())); + BaseUnit massUnit = massUnits.get(randomGenerator.nextInt(massUnits.size())); + + int timePower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + int distancePower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + int massPower = randomGenerator.nextInt(5) + 1 * (randomGenerator.nextInt(2) * 2 - 1); + + return ComposedUnit.builder()// + .setBaseUnit(timeUnit, timePower)// + .setBaseUnit(distanceUnit, distancePower)// + .setBaseUnit(massUnit, massPower)// + .build(); + } + + private int generateNonZeroPower(RandomGenerator randomGenerator) { + int result = randomGenerator.nextInt(5) + 1; + if (randomGenerator.nextBoolean()) { + result = -result; + } + return result; + } + + // Returns an array of three integers. The array will have 0, 1 or 2 zero + // entries in even distribution. The non-zero values will be bounded to [-5,5] + private int[] selectPowers(RandomGenerator randomGenerator) { + + int[] result = new int[3]; + + int numberOfZeroEntries = randomGenerator.nextInt(3); + switch (numberOfZeroEntries) { + case 1: + int excludedIndex = randomGenerator.nextInt(3); + for (int i = 0; i < 3; i++) { + if (i != excludedIndex) { + result[i] = generateNonZeroPower(randomGenerator); + } + } + break; + case 2: + int includedIndex = randomGenerator.nextInt(3); + result[includedIndex] = generateNonZeroPower(randomGenerator); + break; + default:// 0 + for (int i = 0; i < 3; i++) { + result[i] = generateNonZeroPower(randomGenerator); + } + break; + } + + return result; + } + + /* + * Creates a randomized Quantity using several time, length and mass units to + * powers in [-5,-1]U[1,5], a random value. The resultant quantities will have + * even chances of having 1, 2 or 3 measures. + */ + private Quantity getRandomQuantity(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + List timeUnits = new ArrayList<>(); + timeUnits.add(SECOND); + timeUnits.add(MINUTE); + timeUnits.add(HOUR); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + List distanceUnits = new ArrayList<>(); + distanceUnits.add(METER); + distanceUnits.add(CM); + distanceUnits.add(INCH); + distanceUnits.add(FOOT); + distanceUnits.add(MILE); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + BaseUnit OUNCE = new BaseUnit(POUND, 1.0 / 16, "ounce", "oz"); + List massUnits = new ArrayList<>(); + massUnits.add(KILOGRAM); + massUnits.add(POUND); + massUnits.add(OUNCE); + + BaseUnit timeUnit = timeUnits.get(randomGenerator.nextInt(timeUnits.size())); + BaseUnit distanceUnit = distanceUnits.get(randomGenerator.nextInt(distanceUnits.size())); + BaseUnit massUnit = massUnits.get(randomGenerator.nextInt(massUnits.size())); + + int[] selectedPowers = selectPowers(randomGenerator); + + int timePower = selectedPowers[0]; + int distancePower = selectedPowers[1]; + int massPower = selectedPowers[2]; + + ComposedUnit composedUnit = ComposedUnit.builder()// + .setBaseUnit(timeUnit, timePower)// + .setBaseUnit(distanceUnit, distancePower)// + .setBaseUnit(massUnit, massPower)// + .build(); + + double value = randomGenerator.nextDouble(); + return new Quantity(composedUnit, value); + } + + /* + * Creates two randomized quantities using several time, length and mass units + * to powers in [-5,-1]U[1,5]. They will have random positive values(bounded to + * the interval [0.0001,1), but will agree on measures and powers. + */ + private Pair getRandomCompatibleQuanties(long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + Measure MASS = new Measure("mass"); + + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + BaseUnit HOUR = new BaseUnit(MINUTE, 60, "hour", "h"); + List timeUnits = new ArrayList<>(); + timeUnits.add(SECOND); + timeUnits.add(MINUTE); + timeUnits.add(HOUR); + + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + BaseUnit CM = new BaseUnit(METER, 0.01, "centimeter", "cm"); + BaseUnit INCH = new BaseUnit(CM, 2.54, "inch", "in"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit MILE = new BaseUnit(FOOT, 5280, "mile", "mi"); + List distanceUnits = new ArrayList<>(); + distanceUnits.add(METER); + distanceUnits.add(CM); + distanceUnits.add(INCH); + distanceUnits.add(FOOT); + distanceUnits.add(MILE); + + BaseUnit KILOGRAM = new BaseUnit(MASS, "kilogram", "kg"); + BaseUnit POUND = new BaseUnit(KILOGRAM, 0.45359237, "pound", "lb"); + BaseUnit OUNCE = new BaseUnit(POUND, 1.0 / 16, "ounce", "oz"); + List massUnits = new ArrayList<>(); + massUnits.add(KILOGRAM); + massUnits.add(POUND); + massUnits.add(OUNCE); + + BaseUnit timeUnit1 = timeUnits.get(randomGenerator.nextInt(timeUnits.size())); + BaseUnit distanceUnit1 = distanceUnits.get(randomGenerator.nextInt(distanceUnits.size())); + BaseUnit massUnit1 = massUnits.get(randomGenerator.nextInt(massUnits.size())); + + BaseUnit timeUnit2 = timeUnits.get(randomGenerator.nextInt(timeUnits.size())); + BaseUnit distanceUnit2 = distanceUnits.get(randomGenerator.nextInt(distanceUnits.size())); + BaseUnit massUnit2 = massUnits.get(randomGenerator.nextInt(massUnits.size())); + + int[] selectedPowers = selectPowers(randomGenerator); + + int timePower = selectedPowers[0]; + int distancePower = selectedPowers[1]; + int massPower = selectedPowers[2]; + + ComposedUnit composedUnit1 = ComposedUnit.builder()// + .setBaseUnit(timeUnit1, timePower)// + .setBaseUnit(distanceUnit1, distancePower)// + .setBaseUnit(massUnit1, massPower)// + .build(); + + ComposedUnit composedUnit2 = ComposedUnit.builder()// + .setBaseUnit(timeUnit2, timePower)// + .setBaseUnit(distanceUnit2, distancePower)// + .setBaseUnit(massUnit2, massPower)// + .build(); + + double value1 = randomGenerator.nextDouble() * 0.9999 + 0.0001; + double value2 = randomGenerator.nextDouble() * 0.9999 + 0.0001; + + Quantity quantity1 = new Quantity(composedUnit1, value1); + Quantity quantity2 = new Quantity(composedUnit2, value2); + + return new Pair<>(quantity1, quantity2); + } + + @Test + @UnitTestConstructor(target = Quantity.class, args = { ComposedUnit.class, double.class }) + public void testQuantity_Composite() { + // postcontition tests covered by the other tests + + // precondition test: if the composed unit is null + ContractException contractException = assertThrows(ContractException.class, () -> { + ComposedUnit composedUnit = null; + new Quantity(composedUnit, 1.2); + }); + assertEquals(MeasuresError.NULL_COMPOSITE, contractException.getErrorType()); + } + + @Test + @UnitTestConstructor(target = Quantity.class, args = { BaseUnit.class, double.class }) + public void testQuantity_Unit() { + // postcontition tests covered by the other tests + + // precondition test: if the base unit is null + ContractException contractException = assertThrows(ContractException.class, () -> { + BaseUnit baseUnit = null; + new Quantity(baseUnit, 1.2); + }); + assertEquals(MeasuresError.NULL_UNIT, contractException.getErrorType()); + } + + /* + * Returns a new quantity by altering one of the original quantity's powers. + * Assumes that the given quantity has at least one power. + */ + private Quantity getQuantityWithAlteredPower(Quantity quantity, long seed) { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed); + + ComposedUnit composedUnit = quantity.getComposedUnit(); + List baseUnits = composedUnit.getBaseUnits(); + BaseUnit alteredBaseUnit = baseUnits.get(randomGenerator.nextInt(baseUnits.size())); + int alteredPower = composedUnit.getPower(alteredBaseUnit.getMeasure()).get() + 1; + + ComposedUnit.Builder builder = ComposedUnit.builder(); + for (BaseUnit baseUnit : baseUnits) { + if (baseUnit != alteredBaseUnit) { + int power = composedUnit.getPower(baseUnit.getMeasure()).get(); + builder.setBaseUnit(baseUnit, power); + } else { + builder.setBaseUnit(alteredBaseUnit, alteredPower); + } + } + ComposedUnit newComposedUnit = builder.build(); + return new Quantity(newComposedUnit, quantity.getValue()); + } + + private boolean equalWithinTolerance(double expected, double actual) { + return FastMath.abs(1 - actual / expected) <= 1e-12; + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "add", args = { Quantity.class }) + public void testAdd() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8658754499063356339L); + for (int i = 0; i < 30; i++) { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + Quantity q3 = q1.add(q2); + + // the resultant quantity should have the same units as the first quantity + assertEquals(q1.getComposedUnit(), q3.getComposedUnit()); + + double expectedValue = q1.getValue() + + q2.getValue() * q2.getComposedUnit().getValue() / q1.getComposedUnit().getValue(); + double actualValue = q3.getValue(); + + assertTrue(equalWithinTolerance(expectedValue, actualValue)); + } + + // precondition test: if the quantity is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + quantity.add(null); + }); + assertEquals(MeasuresError.NULL_QUANTITY, contractException.getErrorType()); + + // precondition test: if the quantity does not have equal powers over it + // measures + contractException = assertThrows(ContractException.class, () -> { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + Quantity q3 = getQuantityWithAlteredPower(q2, randomGenerator.nextLong()); + q1.add(q3); + }); + assertEquals(MeasuresError.INCOMPATIBLE_MEASURES, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "div", args = { Quantity.class }) + public void testDiv() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1487645678038623637L); + for (int i = 0; i < 30; i++) { + Quantity q1 = getRandomQuantity(randomGenerator.nextLong()); + Quantity q2 = getRandomQuantity(randomGenerator.nextLong()); + Quantity q3 = q1.div(q2); + + // the resultant composed unit should primarily match q1 and secondarily match + // q2 + Map> measureMap = new LinkedHashMap<>(); + ComposedUnit c1 = q1.getComposedUnit(); + for (BaseUnit baseUnit : c1.getBaseUnits()) { + int power = c1.getPower(baseUnit.getMeasure()).get(); + measureMap.put(baseUnit.getMeasure(), new Pair<>(baseUnit, new MutableInteger(power))); + } + ComposedUnit c2 = q2.getComposedUnit(); + for (BaseUnit baseUnit : c2.getBaseUnits()) { + int power = c2.getPower(baseUnit.getMeasure()).get(); + Pair pair = measureMap.get(baseUnit.getMeasure()); + if (pair != null) { + pair.getSecond().decrement(power); + } else { + measureMap.put(baseUnit.getMeasure(), new Pair<>(baseUnit, new MutableInteger(-power))); + } + } + + ComposedUnit.Builder builder = ComposedUnit.builder(); + for (Pair pair : measureMap.values()) { + BaseUnit baseUnit = pair.getFirst(); + MutableInteger power = pair.getSecond(); + builder.setBaseUnit(baseUnit, power.getValue()); + } + ComposedUnit c3 = builder.build(); + assertEquals(c3, q3.getComposedUnit()); + + double compositeFactor = c1.getValue() / (c3.getValue() * c2.getValue()); + double expectedValue = q1.getValue() / q2.getValue() * compositeFactor; + + double actualValue = q3.getValue(); + + /* + * By calculating the expected value exactly how it is implemented in the div + * method, we can avoid precision issues in the comparison. + */ + assertEquals(expectedValue, actualValue); + + } + + // precondition test: if the quantity is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + quantity.div(null); + }); + assertEquals(MeasuresError.NULL_QUANTITY, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "e", args = { Quantity.class, double.class }) + public void testE() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2589875454956650034L); + for (int i = 0; i < 100; i++) { + + /* + * We know that getRandomCompatibleQuanties() returns quantities with positive + * values, so we don't have to consider the cases where scaling a zero would + * have no effect. + */ + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + + // alter the value of q2 so that it will be numerically very close to q1 + double v = q1.getValue() * q1.getComposedUnit().getValue() / q2.getComposedUnit().getValue(); + q2 = q2.setValue(v); + + assertTrue(q1.e(q2, 1e-12)); + + Quantity q3 = q2.scale(1 + 1e-10); + assertFalse(q1.e(q3, 1e-12)); + + Quantity q4 = q2.scale(1 - 1e-10); + assertFalse(q1.e(q4, 1e-12)); + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "equals", args = { Object.class }) + public void testEquals() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6449542055211533914L); + + /** + * Two quantities are equal if and only if they have the equal composed units + * and equal values. For example, the quantities q1 = new Quantity(FOOT,1) and + * q2 = new Quantity(INCH,12) are not equal even though they are equivalent. + */ + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + BaseUnit INCH = new BaseUnit(LENGTH, "foot", "ft"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "sec"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + + // these are identical and so should be equal + Quantity q1 = new Quantity(MINUTE, 37.5); + Quantity q2 = new Quantity(MINUTE, 37.5); + assertTrue(q1.equals(q2)); + + // these represent the same length and are equivalent but not equal + q1 = new Quantity(FOOT, 1); + q2 = new Quantity(INCH, 12); + assertFalse(q1.equals(q2)); + + // not equal null + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + assertFalse(quantity.equals(null)); + } + + // not equal another type + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + assertFalse(quantity.equals(new Object())); + } + + // reflexive + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + assertTrue(quantity.equals(quantity)); + } + + // symmetric, transitive and stable + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + Quantity quantity1 = getRandomQuantity(seed); + Quantity quantity2 = getRandomQuantity(seed); + for (int j = 0; j < 5; j++) { + assertTrue(quantity1.equals(quantity2)); + assertTrue(quantity2.equals(quantity1)); + } + } + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "getComposite", args = {}) + public void testGetComposite() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8887201502112472056L); + for (int i = 0; i < 30; i++) { + ComposedUnit expectedComposedUnit = getRandomComposedUnit(randomGenerator.nextLong()); + Quantity quantity = new Quantity(expectedComposedUnit, randomGenerator.nextDouble()); + ComposedUnit actualComposedUnit = quantity.getComposedUnit(); + assertEquals(expectedComposedUnit, actualComposedUnit); + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "getValue", args = {}) + public void testGetValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(1368153997671183276L); + + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + + for (int i = 0; i < 100; i++) { + double expectedValue = randomGenerator.nextDouble(); + Quantity quantity = new Quantity(SECOND, expectedValue); + double actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + + quantity = new Quantity(MPSS, expectedValue); + actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "gt", args = { Quantity.class }) + public void testGt() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5292505696005214354L); + for (int i = 0; i < 100; i++) { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + + double v1 = q1.getValue() * q1.getComposedUnit().getValue(); + double v2 = q2.getValue() * q2.getComposedUnit().getValue(); + boolean expectedValue = v1 > v2; + boolean actualValue = q1.gt(q2); + assertEquals(expectedValue, actualValue); + + assertFalse(q1.gt(q1)); + + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "gte", args = { Quantity.class }) + public void testGte() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7838228582280275499L); + for (int i = 0; i < 100; i++) { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + + double v1 = q1.getValue() * q1.getComposedUnit().getValue(); + double v2 = q2.getValue() * q2.getComposedUnit().getValue(); + boolean expectedValue = v1 >= v2; + boolean actualValue = q1.gte(q2); + assertEquals(expectedValue, actualValue); + + assertTrue(q1.gte(q1)); + + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "lt", args = { Quantity.class }) + public void testLt() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2589875454956650034L); + for (int i = 0; i < 100; i++) { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + + double v1 = q1.getValue() * q1.getComposedUnit().getValue(); + double v2 = q2.getValue() * q2.getComposedUnit().getValue(); + boolean expectedValue = v1 < v2; + boolean actualValue = q1.lt(q2); + assertEquals(expectedValue, actualValue); + + assertFalse(q1.lt(q1)); + + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "lte", args = { Quantity.class }) + public void testLte() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2373098157771611180L); + for (int i = 0; i < 100; i++) { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + + double v1 = q1.getValue() * q1.getComposedUnit().getValue(); + double v2 = q2.getValue() * q2.getComposedUnit().getValue(); + boolean expectedValue = v1 <= v2; + boolean actualValue = q1.lte(q2); + assertEquals(expectedValue, actualValue); + + assertTrue(q1.lte(q1)); + + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "isCompatible", args = { Quantity.class }) + public void testIsCompatible() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7864129897851447411L); + + // we know that the getRandomCompatibleQuanties() returns pairs of compatible + // quantities + for (int i = 0; i < 30; i++) { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + assertTrue(q1.isCompatible(q2)); + Quantity q3 = getQuantityWithAlteredPower(q2, randomGenerator.nextLong()); + assertFalse(q1.isCompatible(q3)); + } + + // precondition test: if the quantity is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + quantity.isCompatible(null); + }); + assertEquals(MeasuresError.NULL_QUANTITY, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "mult", args = { Quantity.class }) + public void testMult() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8658754499063356339L); + for (int i = 0; i < 30; i++) { + Quantity q1 = getRandomQuantity(randomGenerator.nextLong()); + Quantity q2 = getRandomQuantity(randomGenerator.nextLong()); + Quantity q3 = q1.mult(q2); + + // the resultant composed unit should primarily match q1 and secondarily match + // q2 + Map> measureMap = new LinkedHashMap<>(); + ComposedUnit c1 = q1.getComposedUnit(); + for (BaseUnit baseUnit : c1.getBaseUnits()) { + int power = c1.getPower(baseUnit.getMeasure()).get(); + measureMap.put(baseUnit.getMeasure(), new Pair<>(baseUnit, new MutableInteger(power))); + } + ComposedUnit c2 = q2.getComposedUnit(); + for (BaseUnit baseUnit : c2.getBaseUnits()) { + int power = c2.getPower(baseUnit.getMeasure()).get(); + Pair pair = measureMap.get(baseUnit.getMeasure()); + if (pair != null) { + pair.getSecond().increment(power); + } else { + measureMap.put(baseUnit.getMeasure(), new Pair<>(baseUnit, new MutableInteger(power))); + } + } + + ComposedUnit.Builder builder = ComposedUnit.builder(); + for (Pair pair : measureMap.values()) { + BaseUnit baseUnit = pair.getFirst(); + MutableInteger power = pair.getSecond(); + builder.setBaseUnit(baseUnit, power.getValue()); + } + ComposedUnit c3 = builder.build(); + assertEquals(c3, q3.getComposedUnit()); + + double compositeFactor = (c1.getValue() * c2.getValue()) / c3.getValue(); + double expectedValue = q1.getValue() * q2.getValue() * compositeFactor; + + double actualValue = q3.getValue(); + + /* + * By calculating the expected value exactly how it is implemented in the + * multiply method, we can avoid precision issues in the comparison. + */ + assertEquals(expectedValue, actualValue); + + } + + // precondition test: if the quantity is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + quantity.mult(null); + }); + assertEquals(MeasuresError.NULL_QUANTITY, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "sub", args = { Quantity.class }) + public void testSub() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5445545737245805697L); + for (int i = 0; i < 30; i++) { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + Quantity q3 = q1.sub(q2); + + // the resultant quantity should have the same units as the first quantity + assertEquals(q1.getComposedUnit(), q3.getComposedUnit()); + + double expectedValue = q1.getValue() + - q2.getValue() * q2.getComposedUnit().getValue() / q1.getComposedUnit().getValue(); + double actualValue = q3.getValue(); + + assertTrue(equalWithinTolerance(expectedValue, actualValue)); + } + + // precondition test: if the quantity is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + quantity.add(null); + }); + assertEquals(MeasuresError.NULL_QUANTITY, contractException.getErrorType()); + + // precondition test: if the quantity does not have equal powers over it + // measures + contractException = assertThrows(ContractException.class, () -> { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + Quantity q2 = pair.getSecond(); + Quantity q3 = getQuantityWithAlteredPower(q2, randomGenerator.nextLong()); + q1.sub(q3); + }); + assertEquals(MeasuresError.INCOMPATIBLE_MEASURES, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "hashCode", args = {}) + public void testHashCode() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6449542055211533914L); + + // equal objects have equal hash codes + for (int i = 0; i < 30; i++) { + long seed = randomGenerator.nextLong(); + Quantity quantity1 = getRandomQuantity(seed); + Quantity quantity2 = getRandomQuantity(seed); + assertEquals(quantity1, quantity2); + assertEquals(quantity1.hashCode(), quantity2.hashCode()); + } + + // hash codes are stable + for (int i = 0; i < 30; i++) { + + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + int expectedHashCode = quantity.hashCode(); + for (int j = 0; j < 5; j++) { + assertEquals(expectedHashCode, quantity.hashCode()); + } + } + + // hash codes are reasonable distributed + Set hashCodes = new LinkedHashSet<>(); + for (int i = 0; i < 100; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + hashCodes.add(quantity.hashCode()); + } + assertEquals(100, hashCodes.size()); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "invert", args = {}) + public void testInvert() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(5331525545262629742L); + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + Quantity invertedQuantity = quantity.invert(); + double expectedValue = 1.0 / quantity.getValue(); + double actualValue = invertedQuantity.getValue(); + assertEquals(expectedValue, actualValue); + + ComposedUnit composedUnit = quantity.getComposedUnit(); + ComposedUnit.Builder builder = ComposedUnit.builder(); + for (BaseUnit baseUnit : composedUnit.getBaseUnits()) { + builder.setBaseUnit(baseUnit, -composedUnit.getPower(baseUnit.getMeasure()).get()); + } + ComposedUnit expectedComposedUnit = builder.build(); + ComposedUnit actualComposedUnit = invertedQuantity.getComposedUnit(); + assertEquals(expectedComposedUnit, actualComposedUnit); + } + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "isFinite", args = {}) + public void testIsFinite() { + + Measure TIME = new Measure("time"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + + Quantity quantity = new Quantity(SECOND, 1); + assertTrue(quantity.isFinite()); + + quantity = new Quantity(SECOND, 0); + assertTrue(quantity.isFinite()); + + quantity = new Quantity(SECOND, -1); + assertTrue(quantity.isFinite()); + + quantity = new Quantity(SECOND, Double.POSITIVE_INFINITY); + assertFalse(quantity.isFinite()); + + quantity = new Quantity(SECOND, Double.NEGATIVE_INFINITY); + assertFalse(quantity.isFinite()); + + quantity = new Quantity(SECOND, Double.NaN); + assertFalse(quantity.isFinite()); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "isNegative", args = {}) + public void testIsNegative() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + + Quantity quantity = new Quantity(SECOND, 0.001); + assertFalse(quantity.isNegative()); + + quantity = new Quantity(SECOND, 0); + assertFalse(quantity.isNegative()); + + quantity = new Quantity(SECOND, -0.001); + assertTrue(quantity.isNegative()); + + quantity = new Quantity(MPSS, 0.001); + assertFalse(quantity.isNegative()); + + quantity = new Quantity(MPSS, 0); + assertFalse(quantity.isNegative()); + + quantity = new Quantity(MPSS, -0.001); + assertTrue(quantity.isNegative()); + + quantity = new Quantity(MPSS, Double.NaN); + assertFalse(quantity.isNegative()); + + quantity = new Quantity(MPSS, Double.NEGATIVE_INFINITY); + assertTrue(quantity.isNegative()); + + quantity = new Quantity(MPSS, Double.POSITIVE_INFINITY); + assertFalse(quantity.isNegative()); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "isNonNegative", args = {}) + public void testIsNonNegative() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + + Quantity quantity = new Quantity(SECOND, 0.001); + assertTrue(quantity.isNonNegative()); + + quantity = new Quantity(SECOND, 0); + assertTrue(quantity.isNonNegative()); + + quantity = new Quantity(SECOND, -0.001); + assertFalse(quantity.isNonNegative()); + + quantity = new Quantity(MPSS, 0.001); + assertTrue(quantity.isNonNegative()); + + quantity = new Quantity(MPSS, 0); + assertTrue(quantity.isNonNegative()); + + quantity = new Quantity(MPSS, -0.001); + assertFalse(quantity.isNonNegative()); + + quantity = new Quantity(MPSS, Double.NaN); + assertFalse(quantity.isNonNegative()); + + quantity = new Quantity(MPSS, Double.NEGATIVE_INFINITY); + assertFalse(quantity.isNonNegative()); + + quantity = new Quantity(MPSS, Double.POSITIVE_INFINITY); + assertTrue(quantity.isNonNegative()); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "isNonPositive", args = {}) + public void testIsNonPositive() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + + Quantity quantity = new Quantity(SECOND, 0.001); + assertFalse(quantity.isNonPositive()); + + quantity = new Quantity(SECOND, 0); + assertTrue(quantity.isNonPositive()); + + quantity = new Quantity(SECOND, -0.001); + assertTrue(quantity.isNonPositive()); + + quantity = new Quantity(MPSS, 0.001); + assertFalse(quantity.isNonPositive()); + + quantity = new Quantity(MPSS, 0); + assertTrue(quantity.isNonPositive()); + + quantity = new Quantity(MPSS, -0.001); + assertTrue(quantity.isNonPositive()); + + quantity = new Quantity(MPSS, Double.NaN); + assertFalse(quantity.isNonPositive()); + + quantity = new Quantity(MPSS, Double.NEGATIVE_INFINITY); + assertTrue(quantity.isNonPositive()); + + quantity = new Quantity(MPSS, Double.POSITIVE_INFINITY); + assertFalse(quantity.isNonPositive()); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "isPositive", args = {}) + public void testIsPositive() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + + Quantity quantity = new Quantity(SECOND, 0.001); + assertTrue(quantity.isPositive()); + + quantity = new Quantity(SECOND, 0); + assertFalse(quantity.isPositive()); + + quantity = new Quantity(SECOND, -0.001); + assertFalse(quantity.isPositive()); + + quantity = new Quantity(MPSS, 0.001); + assertTrue(quantity.isPositive()); + + quantity = new Quantity(MPSS, 0); + assertFalse(quantity.isPositive()); + + quantity = new Quantity(MPSS, -0.001); + assertFalse(quantity.isPositive()); + + quantity = new Quantity(MPSS, Double.NaN); + assertFalse(quantity.isPositive()); + + quantity = new Quantity(MPSS, Double.NEGATIVE_INFINITY); + assertFalse(quantity.isPositive()); + + quantity = new Quantity(MPSS, Double.POSITIVE_INFINITY); + assertTrue(quantity.isPositive()); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "isMeasureLess", args = {}) + public void testIsMeasureLess() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + + Quantity quantity = new Quantity(SECOND, 1); + assertFalse(quantity.isMeasureLess()); + + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + quantity = new Quantity(MPSS, 12.6); + assertFalse(quantity.isMeasureLess()); + + quantity = new Quantity(ComposedUnit.builder().build(), 12.6); + assertTrue(quantity.isMeasureLess()); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "isZero", args = {}) + public void testIsZero() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + + Quantity quantity = new Quantity(SECOND, 0.001); + assertFalse(quantity.isZero()); + + quantity = new Quantity(SECOND, 0); + assertTrue(quantity.isZero()); + + quantity = new Quantity(SECOND, -0); + assertTrue(quantity.isZero()); + + quantity = new Quantity(SECOND, -0.001); + assertFalse(quantity.isZero()); + + quantity = new Quantity(MPSS, Double.NaN); + assertFalse(quantity.isZero()); + + quantity = new Quantity(MPSS, Double.NEGATIVE_INFINITY); + assertFalse(quantity.isZero()); + + quantity = new Quantity(MPSS, Double.POSITIVE_INFINITY); + assertFalse(quantity.isZero()); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "pow", args = { int.class }) + public void testPow() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9003235440386204107L); + for (int i = 0; i < 100; i++) { + Quantity q1 = getRandomQuantity(randomGenerator.nextLong()); + for (int power = 0; power < 5; power++) { + Quantity q2 = q1.pow(power); + ComposedUnit composedUnit = q1.getComposedUnit(); + ComposedUnit.Builder builder = ComposedUnit.builder(); + for (BaseUnit baseUnit : composedUnit.getBaseUnits()) { + int p = composedUnit.getPower(baseUnit.getMeasure()).get(); + builder.setBaseUnit(baseUnit, power * p); + } + ComposedUnit expectedComposedUnit = builder.build(); + ComposedUnit actualComposedUnit = q2.getComposedUnit(); + assertEquals(expectedComposedUnit, actualComposedUnit); + + double expectedValue = FastMath.pow(q1.getValue(), power); + double actualValue = q2.getValue(); + assertEquals(expectedValue, actualValue); + } + } + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "rebase", args = { ComposedUnit.class }) + public void testRebase() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6431753317939915457L); + for (int i = 0; i < 100; i++) { + Pair pair = getRandomCompatibleQuanties(randomGenerator.nextLong()); + Quantity q1 = pair.getFirst(); + ComposedUnit composedUnit = pair.getSecond().getComposedUnit(); + Quantity q3 = q1.rebase(composedUnit); + + assertEquals(composedUnit, q3.getComposedUnit()); + + double compositeFactor = q1.getComposedUnit().getValue() / composedUnit.getValue(); + double expectedValue = q1.getValue() * compositeFactor; + double actualValue = q3.getValue(); + + assertEquals(expectedValue, actualValue); + } + + // precondition test : if the composedUnit is null + ContractException contractException = assertThrows(ContractException.class, () -> { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + ComposedUnit nullComposedUnit = null; + quantity.rebase(nullComposedUnit); + }); + assertEquals(MeasuresError.NULL_COMPOSITE, contractException.getErrorType()); + + // precondition test : if the composedUnit is not compatible with this + // quantity's composedUnit + contractException = assertThrows(ContractException.class, () -> { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + ComposedUnit composedUnit = getQuantityWithAlteredPower(quantity, randomGenerator.nextLong()) + .getComposedUnit(); + quantity.rebase(composedUnit); + }); + assertEquals(MeasuresError.INCOMPATIBLE_MEASURES, contractException.getErrorType()); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "root", args = { int.class }) + public void testRoot() { + + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(9003235440386204107L); + for (int i = 0; i < 100; i++) { + Quantity q1 = getRandomQuantity(randomGenerator.nextLong()); + for (int power = 1; power < 5; power++) { + Quantity q2 = q1.pow(power); + Quantity q3 = q2.root(power); + + ComposedUnit expectedComposedUnit = q1.getComposedUnit(); + ComposedUnit actualComposedUnit = q3.getComposedUnit(); + assertEquals(expectedComposedUnit, actualComposedUnit); + + double expectedValue = q1.getValue(); + double actualValue = q3.getValue(); + + assertTrue(equalWithinTolerance(expectedValue, actualValue)); + } + } + + // precondition test: if the root is not positive + ContractException contractException = assertThrows(ContractException.class, () -> { + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + BaseUnit INCH = new BaseUnit(LENGTH, "foot", "ft"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "sec"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + + ComposedUnit FSPMS = ComposedUnit.builder().setBaseUnit(FOOT, 2).setBaseUnit(MINUTE, -2).build(); + Quantity q = new Quantity(FSPMS, 3.6); + q.root(-1); + }); + assertEquals(MeasuresError.NON_POSITIVE_ROOT, contractException.getErrorType()); + + // precondition test: if any of the measure powers is not divisible by the root + contractException = assertThrows(ContractException.class, () -> { + Measure LENGTH = new Measure("length"); + Measure TIME = new Measure("time"); + BaseUnit INCH = new BaseUnit(LENGTH, "foot", "ft"); + BaseUnit FOOT = new BaseUnit(INCH, 12, "foot", "ft"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "sec"); + BaseUnit MINUTE = new BaseUnit(SECOND, 60, "minute", "min"); + + ComposedUnit FSPMS = ComposedUnit.builder().setBaseUnit(FOOT, 2).setBaseUnit(MINUTE, -2).build(); + Quantity q = new Quantity(FSPMS, 3.6); + q.root(3); + + }); + assertEquals(MeasuresError.POWER_IS_NOT_ROOT_COMPATIBLE, contractException.getErrorType()); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "scale", args = { double.class }) + public void testScale() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(7438551332551547296L); + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + double scalar = randomGenerator.nextDouble(); + double expectedValue = quantity.getValue() * scalar; + quantity = quantity.scale(scalar); + double actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "setValue", args = { double.class }) + public void testSetValue() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(6407582171193740886L); + + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + + for (int i = 0; i < 100; i++) { + + Quantity quantity = new Quantity(SECOND, randomGenerator.nextDouble()); + double expectedValue = randomGenerator.nextDouble(); + quantity = quantity.setValue(expectedValue); + double actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + + quantity = new Quantity(MPSS, randomGenerator.nextDouble()); + expectedValue = randomGenerator.nextDouble(); + quantity = quantity.setValue(expectedValue); + actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "getLongName", args = {}) + public void testGetLongName() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + + // we create two composed units, one with and one without names + ComposedUnit MPSS1 = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + ComposedUnit MPSS2 = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).setShortName("acc") + .setLongName("acceleration").build(); + + Quantity quantity = new Quantity(MPSS1, 14.5); + String actualValue = quantity.getLongName(); + String expectedValue = quantity.getValue() + " " + MPSS1.getLongName(); + assertEquals(expectedValue, actualValue); + + quantity = new Quantity(MPSS2, 14.5); + actualValue = quantity.getLongName(); + expectedValue = quantity.getValue() + " " + MPSS2.getLongName(); + assertEquals(expectedValue, actualValue); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "getLongLabel", args = {}) + public void testGetLongLabel() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + + // we create two composed units, one with and one without names + ComposedUnit MPSS1 = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + ComposedUnit MPSS2 = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).setShortName("acc") + .setLongName("acceleration").build(); + + Quantity quantity = new Quantity(MPSS1, 14.5); + String actualValue = quantity.getLongLabel(); + String expectedValue = quantity.getValue() + " " + MPSS1.getLongLabel(); + assertEquals(expectedValue, actualValue); + + quantity = new Quantity(MPSS2, 14.5); + actualValue = quantity.getLongLabel(); + expectedValue = quantity.getValue() + " " + MPSS2.getLongLabel(); + assertEquals(expectedValue, actualValue); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "getShortLabel", args = {}) + public void testGetShortLabel() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + + // we create two composed units, one with and one without names + ComposedUnit MPSS1 = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + ComposedUnit MPSS2 = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).setShortName("acc") + .setLongName("acceleration").build(); + + Quantity quantity = new Quantity(MPSS1, 14.5); + String actualValue = quantity.getShortLabel(); + String expectedValue = quantity.getValue() + " " + MPSS1.getShortLabel(); + assertEquals(expectedValue, actualValue); + + quantity = new Quantity(MPSS2, 14.5); + actualValue = quantity.getShortLabel(); + expectedValue = quantity.getValue() + " " + MPSS2.getShortLabel(); + assertEquals(expectedValue, actualValue); + + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "getShortName", args = {}) + public void testGetShortName() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + ComposedUnit MPSS1 = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).build(); + ComposedUnit MPSS2 = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).setShortName("acc") + .setLongName("acceleration").build(); + + Quantity quantity = new Quantity(MPSS1, 14.5); + String actualValue = quantity.getShortName(); + String expectedValue = quantity.getValue() + " " + MPSS1.getShortName(); + assertEquals(expectedValue, actualValue); + + quantity = new Quantity(MPSS2, 14.5); + actualValue = quantity.getShortName(); + expectedValue = quantity.getValue() + " " + MPSS2.getShortName(); + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "toString", args = {}) + public void testToString() { + Measure TIME = new Measure("time"); + Measure LENGTH = new Measure("length"); + BaseUnit SECOND = new BaseUnit(TIME, "second", "s"); + BaseUnit METER = new BaseUnit(LENGTH, "meter", "m"); + + ComposedUnit MPSS = ComposedUnit.builder().setBaseUnit(METER, 1).setBaseUnit(SECOND, -2).setShortName("acc") + .setLongName("acceleration").build(); + + Quantity quantity = new Quantity(MPSS, 14.5); + String actualValue = quantity.toString(); + String expectedValue = "Quantity [composedUnit=" + + "ComposedUnit [value=1.0, longName=acceleration, shortName=acc, " + + "measures={Measure [name=length]=UnitPower [baseUnit=BaseUnit [measure=Measure [name=length], value=1.0, name=meter, shortName=m], power=1]," + + " Measure [name=time]=UnitPower [baseUnit=BaseUnit [measure=Measure [name=time], value=1.0, name=second, shortName=s], power=-2]}]," + + " value=14.5]"; + assertEquals(expectedValue, actualValue); + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "inc", args = {}) + public void testInc() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8441552572669386207L); + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + double expectedValue = quantity.getValue() + 1; + quantity = quantity.inc(); + double actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "inc", args = { double.class }) + public void testInc_value() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8963912264107163805L); + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + double amount = randomGenerator.nextDouble(); + double expectedValue = quantity.getValue() + amount; + quantity = quantity.inc(amount); + double actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "dec", args = {}) + public void testDec() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(2045113016927822606L); + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + double expectedValue = quantity.getValue() - 1; + quantity = quantity.dec(); + double actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + } + } + + @Test + @UnitTestMethod(target = Quantity.class, name = "dec", args = { double.class }) + public void testDec_value() { + RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(8963912264107163805L); + for (int i = 0; i < 30; i++) { + Quantity quantity = getRandomQuantity(randomGenerator.nextLong()); + double amount = randomGenerator.nextDouble(); + double expectedValue = quantity.getValue() - amount; + quantity = quantity.dec(amount); + double actualValue = quantity.getValue(); + assertEquals(expectedValue, actualValue); + } + } +} diff --git a/src/test/java/gov/hhs/aspr/ms/util/measures/AT_StandardMeasures.java b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_StandardMeasures.java new file mode 100644 index 0000000..5a2e04a --- /dev/null +++ b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_StandardMeasures.java @@ -0,0 +1,501 @@ +package gov.hhs.aspr.ms.util.measures; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.util.annotations.UnitTestField; + +public class AT_StandardMeasures { + @Test + @UnitTestField(target = StandardMeasures.class, name = "LENGTH") + public void test_LENGTH() { + assertInstanceOf(Measure.class, StandardMeasures.LENGTH); + assertEquals("length", StandardMeasures.LENGTH.getName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MASS") + public void test_MASS() { + assertInstanceOf(Measure.class, StandardMeasures.MASS); + assertEquals("mass", StandardMeasures.MASS.getName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "TIME") + public void test_TIME() { + assertInstanceOf(Measure.class, StandardMeasures.TIME); + assertEquals("time", StandardMeasures.TIME.getName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "CURRENT") + public void test_CURRENT() { + assertInstanceOf(Measure.class, StandardMeasures.CURRENT); + assertEquals("current", StandardMeasures.CURRENT.getName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "TEMPERATURE") + public void test_TEMPERATURE() { + assertInstanceOf(Measure.class, StandardMeasures.TEMPERATURE); + assertEquals("temperature", StandardMeasures.TEMPERATURE.getName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "LUMINOSITY") + public void test_LUMINOSITY() { + assertInstanceOf(Measure.class, StandardMeasures.LUMINOSITY); + assertEquals("luminosity", StandardMeasures.LUMINOSITY.getName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "ANGLE") + public void test_ANGLE() { + assertInstanceOf(Measure.class, StandardMeasures.ANGLE); + assertEquals("angle", StandardMeasures.ANGLE.getName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "SOLID_ANGLE") + public void test_SOLID_ANGLE() { + assertInstanceOf(Measure.class, StandardMeasures.SOLID_ANGLE); + assertEquals("solid_angle", StandardMeasures.SOLID_ANGLE.getName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "METER") + public void test_METER() { + assertInstanceOf(BaseUnit.class, StandardMeasures.METER); + assertEquals("meter", StandardMeasures.METER.getLongName()); + assertEquals("m", StandardMeasures.METER.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "CM") + public void test_CM() { + assertInstanceOf(BaseUnit.class, StandardMeasures.CM); + assertEquals("centimeter", StandardMeasures.CM.getLongName()); + assertEquals("cm", StandardMeasures.CM.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "DM") + public void test_DM() { + assertInstanceOf(BaseUnit.class, StandardMeasures.DM); + assertEquals("decimeter", StandardMeasures.DM.getLongName()); + assertEquals("dm", StandardMeasures.DM.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "INCH") + public void test_INCH() { + assertInstanceOf(BaseUnit.class, StandardMeasures.INCH); + assertEquals("inch", StandardMeasures.INCH.getLongName()); + assertEquals("in", StandardMeasures.INCH.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "FOOT") + public void test_FOOT() { + assertInstanceOf(BaseUnit.class, StandardMeasures.FOOT); + assertEquals("foot", StandardMeasures.FOOT.getLongName()); + assertEquals("ft", StandardMeasures.FOOT.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MILE") + public void test_MILE() { + assertInstanceOf(BaseUnit.class, StandardMeasures.MILE); + assertEquals("mile", StandardMeasures.MILE.getLongName()); + assertEquals("mi", StandardMeasures.MILE.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "SECOND") + public void test_SECOND() { + assertInstanceOf(BaseUnit.class, StandardMeasures.SECOND); + assertEquals("second", StandardMeasures.SECOND.getLongName()); + assertEquals("s", StandardMeasures.SECOND.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MINUTE") + public void test_MINUTE() { + assertInstanceOf(BaseUnit.class, StandardMeasures.MINUTE); + assertEquals("minute", StandardMeasures.MINUTE.getLongName()); + assertEquals("min", StandardMeasures.MINUTE.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "HOUR") + public void test_HOUR() { + assertInstanceOf(BaseUnit.class, StandardMeasures.HOUR); + assertEquals("hour", StandardMeasures.HOUR.getLongName()); + assertEquals("h", StandardMeasures.HOUR.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "DAY") + public void test_DAY() { + assertInstanceOf(BaseUnit.class, StandardMeasures.DAY); + assertEquals("day", StandardMeasures.DAY.getLongName()); + assertEquals("d", StandardMeasures.DAY.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "KILOGRAM") + public void test_KILOGRAM() { + assertInstanceOf(BaseUnit.class, StandardMeasures.KILOGRAM); + assertEquals("kilogram", StandardMeasures.KILOGRAM.getLongName()); + assertEquals("kg", StandardMeasures.KILOGRAM.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "GRAM") + public void test_GRAM() { + assertInstanceOf(BaseUnit.class, StandardMeasures.GRAM); + assertEquals("gram", StandardMeasures.GRAM.getLongName()); + assertEquals("g", StandardMeasures.GRAM.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MILLIGRAM") + public void test_MILLIGRAM() { + assertInstanceOf(BaseUnit.class, StandardMeasures.MILLIGRAM); + assertEquals("milligram", StandardMeasures.MILLIGRAM.getLongName()); + assertEquals("mg", StandardMeasures.MILLIGRAM.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MICROGRAM") + public void test_MICROGRAM() { + assertInstanceOf(BaseUnit.class, StandardMeasures.MICROGRAM); + assertEquals("microgram", StandardMeasures.MICROGRAM.getLongName()); + assertEquals("mcg", StandardMeasures.MICROGRAM.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "AMPERE") + public void test_AMPERE() { + assertInstanceOf(BaseUnit.class, StandardMeasures.AMPERE); + assertEquals("ampere", StandardMeasures.AMPERE.getLongName()); + assertEquals("A", StandardMeasures.AMPERE.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "KELVIN") + public void test_KELVIN() { + assertInstanceOf(BaseUnit.class, StandardMeasures.KELVIN); + assertEquals("kelvin", StandardMeasures.KELVIN.getLongName()); + assertEquals("K", StandardMeasures.KELVIN.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "CANDELA") + public void test_CANDELA() { + assertInstanceOf(BaseUnit.class, StandardMeasures.CANDELA); + assertEquals("candela", StandardMeasures.CANDELA.getLongName()); + assertEquals("cd", StandardMeasures.CANDELA.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "RADIAN") + public void test_RADIAN() { + assertInstanceOf(BaseUnit.class, StandardMeasures.RADIAN); + assertEquals("raidan", StandardMeasures.RADIAN.getLongName()); + assertEquals("rad", StandardMeasures.RADIAN.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "DEGREE") + public void test_DEGREE() { + assertInstanceOf(BaseUnit.class, StandardMeasures.DEGREE); + assertEquals("degree", StandardMeasures.DEGREE.getLongName()); + assertEquals("deg", StandardMeasures.DEGREE.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "STERADIAN") + public void test_STERADIAN() { + assertInstanceOf(BaseUnit.class, StandardMeasures.STERADIAN); + assertEquals("steradian", StandardMeasures.STERADIAN.getLongName()); + assertEquals("st", StandardMeasures.STERADIAN.getShortName()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MPH") + public void test_MPH() { + assertInstanceOf(ComposedUnit.class, StandardMeasures.MPH); + assertEquals(2, StandardMeasures.MPH.getBaseUnits().size()); + // mile + Optional optionalUnit = StandardMeasures.MPH.getBaseUnit(StandardMeasures.MILE.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.MILE, optionalUnit.get()); + Optional optionalPower = StandardMeasures.MPH.getPower(StandardMeasures.MILE.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(1, optionalPower.get()); + + // hour + optionalUnit = StandardMeasures.MPH.getBaseUnit(StandardMeasures.HOUR.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.HOUR, optionalUnit.get()); + optionalPower = StandardMeasures.MPH.getPower(StandardMeasures.HOUR.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(-1, optionalPower.get()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MPS") + public void test_MPS() { + assertInstanceOf(ComposedUnit.class, StandardMeasures.MPS); + assertEquals(2, StandardMeasures.MPS.getBaseUnits().size()); + // meter + Optional optionalUnit = StandardMeasures.MPS.getBaseUnit(StandardMeasures.METER.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.METER, optionalUnit.get()); + Optional optionalPower = StandardMeasures.MPS.getPower(StandardMeasures.METER.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(1, optionalPower.get()); + + // second + optionalUnit = StandardMeasures.MPS.getBaseUnit(StandardMeasures.SECOND.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.SECOND, optionalUnit.get()); + optionalPower = StandardMeasures.MPS.getPower(StandardMeasures.SECOND.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(-1, optionalPower.get()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "ACCELERATION_MPSS") + public void test_ACCELERATION_MPSS() { + assertInstanceOf(ComposedUnit.class, StandardMeasures.ACCELERATION_MPSS); + assertEquals(2, StandardMeasures.ACCELERATION_MPSS.getBaseUnits().size()); + // meter + Optional optionalUnit = StandardMeasures.ACCELERATION_MPSS + .getBaseUnit(StandardMeasures.METER.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.METER, optionalUnit.get()); + Optional optionalPower = StandardMeasures.ACCELERATION_MPSS + .getPower(StandardMeasures.METER.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(1, optionalPower.get()); + + // second + optionalUnit = StandardMeasures.ACCELERATION_MPSS.getBaseUnit(StandardMeasures.SECOND.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.SECOND, optionalUnit.get()); + optionalPower = StandardMeasures.ACCELERATION_MPSS.getPower(StandardMeasures.SECOND.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(-2, optionalPower.get()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "EARTH_GRAVITY") + public void test_EARTH_GRAVITY() { + assertInstanceOf(Constant.class, StandardMeasures.EARTH_GRAVITY); + Quantity quantity = StandardMeasures.EARTH_GRAVITY.getQuantity(); + ComposedUnit composedUnit = quantity.getComposedUnit(); + + assertEquals(9.80665, quantity.getValue()); + + assertEquals(2, composedUnit.getBaseUnits().size()); + + // meter + Optional optionalUnit = composedUnit.getBaseUnit(StandardMeasures.METER.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.METER, optionalUnit.get()); + Optional optionalPower = StandardMeasures.ACCELERATION_MPSS + .getPower(StandardMeasures.METER.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(1, optionalPower.get()); + + // second + optionalUnit = composedUnit.getBaseUnit(StandardMeasures.SECOND.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.SECOND, optionalUnit.get()); + optionalPower = StandardMeasures.ACCELERATION_MPSS.getPower(StandardMeasures.SECOND.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(-2, optionalPower.get()); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "ML") + public void test_ML() { + assertInstanceOf(ComposedUnit.class, StandardMeasures.ML); + assertEquals(1, StandardMeasures.ML.getBaseUnits().size()); + // centimeter + Optional optionalUnit = StandardMeasures.ML.getBaseUnit(StandardMeasures.CM.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.CM, optionalUnit.get()); + Optional optionalPower = StandardMeasures.ML.getPower(StandardMeasures.CM.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(3, optionalPower.get()); + + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "LITER") + public void test_LITER() { + assertInstanceOf(ComposedUnit.class, StandardMeasures.LITER); + assertEquals(1, StandardMeasures.LITER.getBaseUnits().size()); + // decimeter + Optional optionalUnit = StandardMeasures.LITER.getBaseUnit(StandardMeasures.DM.getMeasure()); + assertTrue(optionalUnit.isPresent()); + assertEquals(StandardMeasures.DM, optionalUnit.get()); + Optional optionalPower = StandardMeasures.LITER.getPower(StandardMeasures.DM.getMeasure()); + assertTrue(optionalPower.isPresent()); + assertEquals(3, optionalPower.get()); + + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "QUECTO") + public void test_QUECTO() { + assertEquals(1E-30, StandardMeasures.QUECTO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "RONTO") + public void test_RONTO() { + assertEquals(1E-27, StandardMeasures.RONTO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "YOCTO") + public void test_YOCTO() { + assertEquals(1E-24, StandardMeasures.YOCTO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "ZEPTO") + public void test_ZEPTO() { + assertEquals(1E-21, StandardMeasures.ZEPTO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "ATTO") + public void test_ATTO() { + assertEquals(1E-18, StandardMeasures.ATTO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "FEMTO") + public void test_FEMTO() { + assertEquals(1E-15, StandardMeasures.FEMTO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "PICO") + public void test_PICO() { + assertEquals(1E-12, StandardMeasures.PICO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "NANO") + public void test_NANO() { + assertEquals(1E-9, StandardMeasures.NANO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MICRO") + public void test_MICRO() { + assertEquals(1E-6, StandardMeasures.MICRO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MILLI") + public void test_MILLI() { + assertEquals(1E-3, StandardMeasures.MILLI); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "CENTI") + public void test_CENTI() { + assertEquals(1E-2, StandardMeasures.CENTI); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "DECI") + public void test_DECI() { + assertEquals(1E-1, StandardMeasures.DECI); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "DECA") + public void test_DECA() { + assertEquals(1E1, StandardMeasures.DECA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "HECTO") + public void test_HECTO() { + assertEquals(1E2, StandardMeasures.HECTO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "KILO") + public void test_KILO() { + assertEquals(1E3, StandardMeasures.KILO); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "MEGA") + public void test_MEGA() { + assertEquals(1E6, StandardMeasures.MEGA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "GIGA") + public void test_GIGA() { + assertEquals(1E9, StandardMeasures.GIGA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "TERA") + public void test_TERA() { + assertEquals(1E12, StandardMeasures.TERA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "PETA") + public void test_PETA() { + assertEquals(1E15, StandardMeasures.PETA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "EXA") + public void test_EXA() { + assertEquals(1E18, StandardMeasures.EXA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "ZETTA") + public void test_ZETTA() { + assertEquals(1E21, StandardMeasures.ZETTA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "YOTTA") + public void test_YOTTA() { + assertEquals(1E24, StandardMeasures.YOTTA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "RONNA") + public void test_RONNA() { + assertEquals(1E27, StandardMeasures.RONNA); + } + + @Test + @UnitTestField(target = StandardMeasures.class, name = "QUETTA") + public void test_QUETTA() { + assertEquals(1E30, StandardMeasures.QUETTA); + } + +} diff --git a/src/test/java/gov/hhs/aspr/ms/util/measures/AT_TemperatureScale.java b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_TemperatureScale.java new file mode 100644 index 0000000..3489a99 --- /dev/null +++ b/src/test/java/gov/hhs/aspr/ms/util/measures/AT_TemperatureScale.java @@ -0,0 +1,92 @@ +package gov.hhs.aspr.ms.util.measures; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import gov.hhs.aspr.ms.util.annotations.UnitTestMethod; + +public class AT_TemperatureScale { + + @Test + @UnitTestMethod(target = TemperatureScale.class, name = "fromAbsolute", args = { double.class, + TemperatureScale.class }) + public void testFromAbsolute() { + Map relativeKelvinMap = new LinkedHashMap<>(); + relativeKelvinMap.put(TemperatureScale.CELSIUS, 1.0); + relativeKelvinMap.put(TemperatureScale.DELISLE, -2.0 / 3); + relativeKelvinMap.put(TemperatureScale.FAHRENHEIT, 5.0 / 9); + relativeKelvinMap.put(TemperatureScale.KELVIN, 1.0); + relativeKelvinMap.put(TemperatureScale.NEWTON, 1 / 0.33); + relativeKelvinMap.put(TemperatureScale.RANKINE, 5.0 / 9); + relativeKelvinMap.put(TemperatureScale.REAUMUR, 5.0 / 4); + relativeKelvinMap.put(TemperatureScale.ROMER, 40.0 / 21); + + assertEquals(relativeKelvinMap.size(), TemperatureScale.values().length); + + Map offsetKelvinMap = new LinkedHashMap<>(); + offsetKelvinMap.put(TemperatureScale.CELSIUS, 273.15); + offsetKelvinMap.put(TemperatureScale.DELISLE, 373.15); + offsetKelvinMap.put(TemperatureScale.FAHRENHEIT, -32.0 / 9 * 5 + 273.15); + offsetKelvinMap.put(TemperatureScale.KELVIN, 0.0); + offsetKelvinMap.put(TemperatureScale.NEWTON, 273.15); + offsetKelvinMap.put(TemperatureScale.RANKINE, 0.0); + offsetKelvinMap.put(TemperatureScale.REAUMUR, 273.15); + offsetKelvinMap.put(TemperatureScale.ROMER, -7.5 * 40 / 21 + 273.15); + + assertEquals(offsetKelvinMap.size(), TemperatureScale.values().length); + + List temps = new ArrayList<>(); + temps.add(-10.0); + temps.add(0.0); + temps.add(10.0); + + for (TemperatureScale temperatureScale1 : TemperatureScale.values()) { + + for (TemperatureScale temperatureScale2 : TemperatureScale.values()) { + for (Double temp : temps) { + double expectedValue = temp; + expectedValue *= relativeKelvinMap.get(temperatureScale1); + expectedValue += offsetKelvinMap.get(temperatureScale1); + expectedValue -= offsetKelvinMap.get(temperatureScale2); + expectedValue /= relativeKelvinMap.get(temperatureScale2); + double actualValue = temperatureScale2.fromAbsolute(temp, temperatureScale1); + assertEquals(expectedValue, actualValue, 1e-12); + } + } + } + } + + @Test + @UnitTestMethod(target = TemperatureScale.class, name = "fromRelative", args = { double.class, + TemperatureScale.class }) + public void testFromRelative() { + Map relativeKelvinMap = new LinkedHashMap<>(); + relativeKelvinMap.put(TemperatureScale.CELSIUS, 1.0); + relativeKelvinMap.put(TemperatureScale.DELISLE, -2.0 / 3); + relativeKelvinMap.put(TemperatureScale.FAHRENHEIT, 5.0 / 9); + relativeKelvinMap.put(TemperatureScale.KELVIN, 1.0); + relativeKelvinMap.put(TemperatureScale.NEWTON, 1 / 0.33); + relativeKelvinMap.put(TemperatureScale.RANKINE, 5.0 / 9); + relativeKelvinMap.put(TemperatureScale.REAUMUR, 5.0 / 4); + relativeKelvinMap.put(TemperatureScale.ROMER, 40.0 / 21); + + assertEquals(relativeKelvinMap.size(), TemperatureScale.values().length); + + for (TemperatureScale temperatureScale1 : TemperatureScale.values()) { + double a = relativeKelvinMap.get(temperatureScale1); + for (TemperatureScale temperatureScale2 : TemperatureScale.values()) { + double b = relativeKelvinMap.get(temperatureScale2); + double expectedValue = a / b; + double actualValue = temperatureScale2.fromRelative(1.0, temperatureScale1); + assertEquals(expectedValue, actualValue, 1e-12); + } + } + } + +}