mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-20 00:51:42 +00:00
[wpiunits] Java units API rewrite (#6958)
Java generics are too limited to do what we need. This refactors generic code previously in Unit and Measure into unit-specific classes that can have unit-safe math operations (notably, times and divide) that can return values in known units instead of a wildcarded Measure<?>. Unit-specific measure implementations are automatically generated by ./wpiunits/generate_units.py, which generates generic interfaces and mutable and immutable implementations of those interfaces. These make up the bulk of the diff of this PR (approximately 9300 LOC). This also adds units for angular and linear velocities, accelerations, and momenta; moment of inertia; and torque.
This commit is contained in:
@@ -11,69 +11,106 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import edu.wpi.first.units.measure.Angle;
|
||||
import edu.wpi.first.units.measure.AngularMomentum;
|
||||
import edu.wpi.first.units.measure.Distance;
|
||||
import edu.wpi.first.units.measure.LinearVelocity;
|
||||
import edu.wpi.first.units.measure.Per;
|
||||
import edu.wpi.first.units.measure.Time;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class MeasureTest {
|
||||
@Test
|
||||
void testBasics() {
|
||||
Unit<Distance> unit = Units.Feet;
|
||||
DistanceUnit unit = Units.Feet;
|
||||
double magnitude = 10;
|
||||
Measure<Distance> m = unit.of(magnitude);
|
||||
Distance m = unit.of(magnitude);
|
||||
assertEquals(unit, m.unit(), "Wrong units");
|
||||
assertEquals(magnitude, m.magnitude(), 0, "Wrong magnitude");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultiply() {
|
||||
Measure<Distance> m = Units.Feet.of(1);
|
||||
Measure<Distance> m2 = m.times(10);
|
||||
assertEquals(10, m2.magnitude(), 1e-12);
|
||||
Distance m = Units.Feet.of(1);
|
||||
Distance m2 = m.times(10);
|
||||
assertEquals(10, m2.in(Units.Feet), 1e-12);
|
||||
assertNotSame(m2, m); // make sure state wasn't changed
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimesConversionFactor() {
|
||||
Distance m = Units.Feet.of(10);
|
||||
|
||||
Per<AngleUnit, DistanceUnit> conversion = Units.Degrees.of(10).divide(Units.Feet.of(1));
|
||||
Angle result = m.timesConversionFactor(conversion);
|
||||
assertEquals(Units.Degrees.of(100), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimesConversionFactorComplex() {
|
||||
Distance m = Units.Feet.of(1);
|
||||
|
||||
// Using a complex compound unit here
|
||||
// (Per<Mult<Mult<Mass, Per<Distance, Time>>, Distance>, Distance>)
|
||||
Per<AngularMomentumUnit, DistanceUnit> conversion =
|
||||
Units.KilogramMetersSquaredPerSecond.of(1).divide(Units.Foot.one());
|
||||
|
||||
AngularMomentum result = m.timesConversionFactor(conversion);
|
||||
assertEquals(Units.KilogramMetersSquaredPerSecond.of(1), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimesVelocityConversionFactor() {
|
||||
Time m = Units.Seconds.of(10);
|
||||
|
||||
LinearVelocity conversion = Units.MetersPerSecond.of(10);
|
||||
Distance result = m.timesConversionFactor(conversion);
|
||||
assertEquals(Units.Meters.of(100), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDivide() {
|
||||
Measure<Distance> m = Units.Meters.of(1);
|
||||
Measure<Distance> m2 = m.divide(10);
|
||||
Distance m = Units.Meters.of(1);
|
||||
Distance m2 = m.divide(10);
|
||||
assertEquals(0.1, m2.magnitude(), 0);
|
||||
assertNotSame(m2, m);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAdd() {
|
||||
Measure<Distance> m1 = Units.Feet.of(1);
|
||||
Measure<Distance> m2 = Units.Inches.of(2);
|
||||
Distance m1 = Units.Feet.of(1);
|
||||
Distance m2 = Units.Inches.of(2);
|
||||
assertTrue(m1.plus(m2).isEquivalent(Units.Feet.of(1 + 2 / 12d)));
|
||||
assertTrue(m2.plus(m1).isEquivalent(Units.Inches.of(14)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSubtract() {
|
||||
Measure<Distance> m1 = Units.Feet.of(1);
|
||||
Measure<Distance> m2 = Units.Inches.of(2);
|
||||
Distance m1 = Units.Feet.of(1);
|
||||
Distance m2 = Units.Inches.of(2);
|
||||
assertTrue(m1.minus(m2).isEquivalent(Units.Feet.of(1 - 2 / 12d)));
|
||||
assertTrue(m2.minus(m1).isEquivalent(Units.Inches.of(-10)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNegate() {
|
||||
Measure<Distance> m = Units.Feet.of(123);
|
||||
Measure<Distance> n = m.negate();
|
||||
assertEquals(-m.magnitude(), n.magnitude(), 1e-12);
|
||||
assertEquals(m.unit(), n.unit());
|
||||
void testUnaryMinus() {
|
||||
Distance m = Units.Feet.of(123);
|
||||
Distance negated = m.unaryMinus();
|
||||
assertEquals(-123, negated.in(Units.Feet), 1e-12);
|
||||
assertEquals(Units.Feet, negated.unit());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquivalency() {
|
||||
Measure<Distance> inches = Units.Inches.of(12);
|
||||
Measure<Distance> feet = Units.Feet.of(1);
|
||||
Distance inches = Units.Inches.of(12);
|
||||
Distance feet = Units.Feet.of(1);
|
||||
assertTrue(inches.isEquivalent(feet));
|
||||
assertTrue(feet.isEquivalent(inches));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAs() {
|
||||
Measure<Distance> m = Units.Inches.of(12);
|
||||
Distance m = Units.Inches.of(12);
|
||||
assertEquals(1, m.in(Units.Feet), Measure.EQUIVALENCE_THRESHOLD);
|
||||
}
|
||||
|
||||
@@ -84,7 +121,7 @@ class MeasureTest {
|
||||
|
||||
// 144 Kg / (53 ms) = (1000 / 53) * 144 Kg/s = (144,000 / 53) Kg/s
|
||||
|
||||
var result = measure.per(dt);
|
||||
var result = measure.divide(dt);
|
||||
assertEquals(144_000.0 / 53, result.baseUnitMagnitude(), 1e-5);
|
||||
assertEquals(Units.Kilograms.per(Units.Milliseconds), result.unit());
|
||||
}
|
||||
@@ -94,106 +131,33 @@ class MeasureTest {
|
||||
var measure = Units.Kilograms.of(144);
|
||||
var result = measure.per(Units.Millisecond);
|
||||
|
||||
assertEquals(Velocity.class, result.unit().getClass());
|
||||
assertEquals(VelocityUnit.class, result.unit().getClass());
|
||||
assertEquals(144_000.0, result.baseUnitMagnitude(), 1e-5);
|
||||
assertEquals(Units.Kilograms.per(Units.Milliseconds), result.unit());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimesMeasure() {
|
||||
var m1 = Units.Volts.of(1.567);
|
||||
var m2 = Units.Kilograms.of(8.4e-5);
|
||||
|
||||
assertEquals(Units.Volts.mult(Units.Kilograms).of(1.567 * 8.4e-5), m1.times(m2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimesUnitless() {
|
||||
var unit = new ExampleUnit(6);
|
||||
var measure = unit.of(2.5);
|
||||
var multiplier = Units.Percent.of(125); // 125% or 1.25x
|
||||
Measure<?> result = measure.times(multiplier);
|
||||
assertSame(unit, result.unit());
|
||||
|
||||
assertEquals(2.5 * 1.25, result.magnitude());
|
||||
assertEquals(2.5 * 1.25 * 6, result.baseUnitMagnitude());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimesPerWithDimensionalAnalysis() {
|
||||
var measureA = Units.Feet.of(62); // 62 feet
|
||||
var measureB = Units.Radians.of(6).per(Units.Inches); // 6 radians per inch
|
||||
Measure<?> aTimesB = measureA.times(measureB); // (62 feet) * (6 rad/inch) = 4464 rad
|
||||
assertEquals(Units.Radians, aTimesB.unit());
|
||||
assertEquals(4464, aTimesB.magnitude(), 1e-12);
|
||||
|
||||
Measure<?> bTimesA = measureB.times(measureA); // should be identical to the above
|
||||
assertTrue(bTimesA.isEquivalent(aTimesB));
|
||||
assertTrue(aTimesB.isEquivalent(bTimesA));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPerTimesPerWithDimensionalAnalysis() {
|
||||
var measureA = Units.Inches.of(16).per(Units.Volts);
|
||||
var measureB = Units.Millivolts.of(14).per(Units.Meters);
|
||||
Measure<?> aTimesB = measureA.times(measureB);
|
||||
assertEquals(Units.Value, aTimesB.unit());
|
||||
assertEquals((16 * 25.4 / 1000) * (14 / 1000.0), aTimesB.magnitude());
|
||||
assertEquals((16 * 25.4 / 1000) * (14 / 1000.0), aTimesB.baseUnitMagnitude());
|
||||
|
||||
Measure<?> bTimesA = measureB.times(measureA); // should be identical to the above
|
||||
assertTrue(bTimesA.isEquivalent(aTimesB));
|
||||
assertTrue(aTimesB.isEquivalent(bTimesA));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPerTimesMeasure() {
|
||||
var m1 = Units.Feet.per(Units.Milliseconds).of(19);
|
||||
var m2 = Units.Seconds.of(44);
|
||||
|
||||
// 19 ft/ms = 19,000 ft/s
|
||||
// 19,000 ft/s * 44s = 836,000 ft
|
||||
assertTrue(Units.Feet.of(836_000).isNear(m1.times(m2), 1e-12));
|
||||
|
||||
// 42 ex per foot * 17mm = 42 ex * 17mm / (304.8mm/ft) = 42 * 17 / 304.8 = 2.34252
|
||||
var exampleUnit = new ExampleUnit(1);
|
||||
var m3 = exampleUnit.per(Units.Feet).of(42);
|
||||
var m4 = Units.Millimeters.of(17);
|
||||
assertEquals(exampleUnit.of(42 * 17 / (12 * 25.4)), m3.times(m4));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDivideMeasure() {
|
||||
// Dimensionless divide
|
||||
var m1 = Units.Meters.of(6);
|
||||
var m2 = Units.Value.of(3);
|
||||
var result = m1.divide(m2);
|
||||
assertEquals(m1.divide(m2).magnitude(), 2);
|
||||
assertEquals(result.unit(), Units.Meters);
|
||||
assertEquals(2, m1.divide(m2).magnitude());
|
||||
assertEquals(Units.Meters, result.unit());
|
||||
// Velocity divide
|
||||
var m3 = Units.Meters.of(8);
|
||||
var m4 = Units.Meters.per(Units.Second).of(4);
|
||||
result = m3.divide(m4);
|
||||
assertEquals(result.magnitude(), 2);
|
||||
assertEquals(result.unit(), Units.Second);
|
||||
// Per divide
|
||||
var time = m3.divide(m4);
|
||||
assertEquals(2, time.magnitude());
|
||||
assertEquals(Units.Second, time.unit());
|
||||
// PerUnit divide
|
||||
var m5 = Units.Volts.of(6);
|
||||
var m6 = Units.Volts.per(Units.Meter).of(2);
|
||||
result = m5.divide(m6);
|
||||
assertEquals(result.magnitude(), 3);
|
||||
assertEquals(result.unit(), Units.Meter);
|
||||
// Fallthrough divide
|
||||
var m7 = Units.Seconds.of(10);
|
||||
var m8 = Units.Amps.of(2);
|
||||
result = m7.divide(m8);
|
||||
assertEquals(result.magnitude(), 5);
|
||||
assertEquals(result.unit(), Units.Seconds.per(Units.Amps));
|
||||
// Same base unit divide
|
||||
var m9 = Units.Meters.of(8);
|
||||
var m10 = Units.Meters.of(4);
|
||||
result = m9.divide(m10);
|
||||
assertEquals(result.magnitude(), 2);
|
||||
assertEquals(result.unit(), Units.Value);
|
||||
|
||||
// Voltage/(Voltage/Distance) -> Voltage * Distance/Voltage -> Distance
|
||||
var dist = m5.divide(m6);
|
||||
assertEquals(3, dist.magnitude());
|
||||
assertEquals(Units.Meter, dist.unit());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -249,57 +213,6 @@ class MeasureTest {
|
||||
assertFalse(base.gt(base));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTimesScalar() {
|
||||
var unit = new ExampleUnit(42);
|
||||
var measure = unit.of(4.2);
|
||||
var scalar = 18;
|
||||
var result = measure.times(scalar);
|
||||
assertNotSame(measure, result);
|
||||
assertSame(unit, result.unit());
|
||||
assertEquals(4.2 * 18, result.magnitude());
|
||||
assertEquals(4.2 * 42 * 18, result.baseUnitMagnitude());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPerUnit() {
|
||||
var unitA = new ExampleUnit(10);
|
||||
var unitB = new ExampleUnit(12);
|
||||
var measure = unitA.of(1.2);
|
||||
var result = measure.per(unitB);
|
||||
assertEquals(Per.combine(unitA, unitB), result.unit()); // A/B has base equivalent of 10/12
|
||||
assertEquals(1, result.baseUnitMagnitude()); // 10/12 * 12/10 = 1
|
||||
assertEquals(measure.magnitude(), result.magnitude());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddMeasureSameUnit() {
|
||||
var unit = new ExampleUnit(8.2);
|
||||
var measureA = unit.of(3.1);
|
||||
var measureB = unit.of(91.6);
|
||||
var result = measureA.plus(measureB);
|
||||
assertEquals(unit, result.unit());
|
||||
assertEquals(94.7, result.magnitude(), 1e-12);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddMeasuresDifferentUnits() {
|
||||
var unitA = new ExampleUnit(8.2);
|
||||
var unitB = new ExampleUnit(7.3);
|
||||
var measureA = unitA.of(5);
|
||||
var measureB = unitB.of(16);
|
||||
var aPlusB = measureA.plus(measureB);
|
||||
|
||||
assertEquals(unitA, aPlusB.unit());
|
||||
assertEquals(8.2 * 5 + 7.3 * 16, aPlusB.baseUnitMagnitude(), 1e-12);
|
||||
assertEquals(5 + (16 * 7.3 / 8.2), aPlusB.magnitude(), 1e-12);
|
||||
|
||||
var bPlusA = measureB.plus(measureA);
|
||||
assertEquals(unitB, bPlusA.unit());
|
||||
assertEquals(8.2 * 5 + 7.3 * 16, bPlusA.baseUnitMagnitude(), 1e-12);
|
||||
assertEquals(16 + (5 * 8.2 / 7.3), bPlusA.magnitude(), 1e-12);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMinNoArgs() {
|
||||
var min = Measure.min();
|
||||
@@ -388,29 +301,29 @@ class MeasureTest {
|
||||
assertTrue(measureCompared.isNear(measureComparing, Units.Millimeters.of(300)));
|
||||
assertFalse(measureCompared.isNear(measureComparing, Units.Centimeters.of(10)));
|
||||
|
||||
measureCompared = measureCompared.negate();
|
||||
measureComparing = measureComparing.negate();
|
||||
measureCompared = measureCompared.unaryMinus();
|
||||
measureComparing = measureComparing.unaryMinus();
|
||||
|
||||
// Negative value with positive tolerance
|
||||
assertTrue(measureCompared.isNear(measureComparing, Units.Millimeters.of(300)));
|
||||
assertFalse(measureCompared.isNear(measureComparing, Units.Centimeters.of(10)));
|
||||
|
||||
measureCompared = measureCompared.negate();
|
||||
measureComparing = measureComparing.negate();
|
||||
measureCompared = measureCompared.unaryMinus();
|
||||
measureComparing = measureComparing.unaryMinus();
|
||||
|
||||
// Positive value with negative tolerance
|
||||
assertTrue(measureCompared.isNear(measureComparing, Units.Millimeters.of(-300)));
|
||||
assertFalse(measureCompared.isNear(measureComparing, Units.Centimeters.of(-10)));
|
||||
|
||||
measureCompared = measureCompared.negate();
|
||||
measureComparing = measureComparing.negate();
|
||||
measureCompared = measureCompared.unaryMinus();
|
||||
measureComparing = measureComparing.unaryMinus();
|
||||
|
||||
// Negative value with negative tolerance.
|
||||
assertTrue(measureCompared.isNear(measureComparing, Units.Millimeters.of(-300)));
|
||||
assertFalse(measureCompared.isNear(measureComparing, Units.Centimeters.of(-10)));
|
||||
|
||||
measureCompared = measureCompared.negate();
|
||||
measureComparing = measureComparing.negate();
|
||||
measureCompared = measureCompared.unaryMinus();
|
||||
measureComparing = measureComparing.unaryMinus();
|
||||
|
||||
// Tolerance exact difference between measures.
|
||||
assertTrue(measureCompared.isNear(measureComparing, Units.Millimeters.of(200)));
|
||||
|
||||
Reference in New Issue
Block a user