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
+ *
+ * - {@linkplain MeasuresError#NULL_UNIT_NAME} if
+ * the name is null
+ * - {@linkplain MeasuresError#BLANK_UNIT_NAME} if
+ * the name is empty or contains only white space
+ * characters
+ * - {@linkplain MeasuresError#NULL_UNIT_NAME} if
+ * the short name is null
+ * - {@linkplain MeasuresError#BLANK_UNIT_NAME} if
+ * the short name is empty or contains only white
+ * space characters
+ * - {@linkplain MeasuresError#NULL_UNIT} if the
+ * unit is null
+ * - {@linkplain MeasuresError#NON_POSITIVE_SCALAR_VALUE}
+ * if the scalar is not positive
+ *
+ */
+ 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
+ *
+ * - {@linkplain MeasuresError#NULL_UNIT_NAME} if
+ * the name is null
+ * - {@linkplain MeasuresError#BLANK_UNIT_NAME} if
+ * the name is empty or contains only white space
+ * characters
+ * - {@linkplain MeasuresError#NULL_UNIT_NAME} if
+ * the short name is null
+ * - {@linkplain MeasuresError#BLANK_UNIT_NAME} if
+ * the short name is empty or contains only white
+ * space characters
+ * - {@linkplain MeasuresError#NULL_MEASURE} if the
+ * measure is null
+ *
+ */
+ 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);
+ }
+ }
+ }
+
+}