001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.units;
006
007import java.util.Objects;
008
009/**
010 * Unit of measurement that defines a quantity, such as grams, meters, or seconds.
011 *
012 * <p>This is the base class for units. Actual units (such as {@link Units#Grams} and {@link
013 * Units#Meters}) can be found in the {@link Units} class.
014 *
015 * @param <U> the self type, e.g. {@code class SomeUnit extends Unit<SomeUnit>}
016 */
017public class Unit<U extends Unit<U>> {
018  private final UnaryFunction m_toBaseConverter;
019  private final UnaryFunction m_fromBaseConverter;
020
021  private final U m_baseUnit;
022
023  private Measure<U> m_zero;
024  private Measure<U> m_one;
025
026  private final String m_name;
027  private final String m_symbol;
028
029  /**
030   * Creates a new unit defined by its relationship to some base unit.
031   *
032   * @param baseUnit the base unit, e.g. Meters for distances. Set this to {@code null} if the unit
033   *     being constructed is its own base unit
034   * @param toBaseConverter a function for converting units of this type to the base unit
035   * @param fromBaseConverter a function for converting units of the base unit to this one
036   * @param name the name of the unit. This should be a singular noun (so "Meter", not "Meters")
037   * @param symbol the short symbol for the unit, such as "m" for meters or "lb." for pounds
038   */
039  @SuppressWarnings("unchecked")
040  protected Unit(
041      U baseUnit,
042      UnaryFunction toBaseConverter,
043      UnaryFunction fromBaseConverter,
044      String name,
045      String symbol) {
046    m_baseUnit = baseUnit == null ? (U) this : baseUnit;
047    m_toBaseConverter = Objects.requireNonNull(toBaseConverter);
048    m_fromBaseConverter = Objects.requireNonNull(fromBaseConverter);
049    m_name = Objects.requireNonNull(name);
050    m_symbol = Objects.requireNonNull(symbol);
051  }
052
053  /**
054   * Creates a new unit with the given name and multiplier to the base unit.
055   *
056   * @param baseUnit the base unit, e.g. Meters for distances
057   * @param baseUnitEquivalent the multiplier to convert this unit to the base unit of this type.
058   *     For example, meters has a multiplier of 1, mm has a multiplier of 1e3, and km has
059   *     multiplier of 1e-3.
060   * @param name the name of the unit. This should be a singular noun (so "Meter", not "Meters")
061   * @param symbol the short symbol for the unit, such as "m" for meters or "lb." for pounds
062   */
063  protected Unit(U baseUnit, double baseUnitEquivalent, String name, String symbol) {
064    this(baseUnit, x -> x * baseUnitEquivalent, x -> x / baseUnitEquivalent, name, symbol);
065  }
066
067  /**
068   * Gets the base unit of measurement that this unit is derived from. If the unit is the base unit,
069   * the unit will be returned.
070   *
071   * <pre><code>
072   *   Unit baseUnit = new Unit(null, ...);
073   *   baseUnit.getBaseUnit(); // returns baseUnit
074   *
075   *   Unit derivedUnit = new Unit(baseUnit, ...);
076   *   derivedUnit.getBaseUnit(); // returns baseUnit
077   * </code></pre>
078   *
079   * @return the base unit
080   */
081  public U getBaseUnit() {
082    return m_baseUnit;
083  }
084
085  /**
086   * Checks if this unit is the base unit for its own system of measurement.
087   *
088   * @return true if this is the base unit, false if not
089   */
090  public boolean isBaseUnit() {
091    return this.equals(m_baseUnit);
092  }
093
094  /**
095   * Converts a value in terms of base units to a value in terms of this unit.
096   *
097   * @param valueInBaseUnits the value in base units to convert
098   * @return the equivalent value in terms of this unit
099   */
100  public double fromBaseUnits(double valueInBaseUnits) {
101    return m_fromBaseConverter.apply(valueInBaseUnits);
102  }
103
104  /**
105   * Converts a value in terms of this unit to a value in terms of the base unit.
106   *
107   * @param valueInNativeUnits the value in terms of this unit to convert
108   * @return the equivalent value in terms of the base unit
109   */
110  public double toBaseUnits(double valueInNativeUnits) {
111    return m_toBaseConverter.apply(valueInNativeUnits);
112  }
113
114  /**
115   * Converts a magnitude in terms of another unit of the same dimension to a magnitude in terms of
116   * this unit.
117   *
118   * <pre>
119   *   Inches.convertFrom(12, Feet) // 144.0
120   *   Kilograms.convertFrom(2.2, Pounds) // 0.9979024
121   * </pre>
122   *
123   * @param magnitude a magnitude measured in another unit
124   * @param otherUnit the unit to convert the magnitude to
125   * @return the corresponding value in terms of this unit.
126   */
127  public double convertFrom(double magnitude, Unit<U> otherUnit) {
128    if (this.equivalent(otherUnit)) {
129      // same unit, don't bother converting
130      return magnitude;
131    }
132    return this.fromBaseUnits(otherUnit.toBaseUnits(magnitude));
133  }
134
135  /**
136   * Gets the conversion function used to convert values to base unit terms. This generally
137   * shouldn't need to be used directly; prefer {@link #toBaseUnits(double)} instead.
138   *
139   * @return the conversion function
140   */
141  public UnaryFunction getConverterToBase() {
142    return m_toBaseConverter;
143  }
144
145  /**
146   * Gets the conversion function used to convert values to terms of this unit. This generally
147   * shouldn't need to be used directly; prefer {@link #fromBaseUnits(double)} instead.
148   *
149   * @return the conversion function
150   */
151  public UnaryFunction getConverterFromBase() {
152    return m_fromBaseConverter;
153  }
154
155  /**
156   * Creates a new measure of this unit with the given value. The resulting measure is
157   * <i>immutable</i> and cannot have its value modified.
158   *
159   * @param magnitude the magnitude of the measure to create
160   * @return the measure
161   */
162  public Measure<U> of(double magnitude) {
163    if (magnitude == 0) {
164      // reuse static object
165      return zero();
166    }
167    if (magnitude == 1) {
168      // reuse static object
169      return one();
170    }
171    return ImmutableMeasure.ofRelativeUnits(magnitude, this);
172  }
173
174  /**
175   * Creates a new measure with a magnitude equal to the given base unit magnitude, converted to be
176   * in terms of this unit.
177   *
178   * @param baseUnitMagnitude the magnitude of the measure in terms of the base unit
179   * @return the measure
180   */
181  public Measure<U> ofBaseUnits(double baseUnitMagnitude) {
182    return ImmutableMeasure.ofBaseUnits(baseUnitMagnitude, this);
183  }
184
185  /**
186   * Gets a measure with a magnitude of 0 in terms of this unit.
187   *
188   * @return the zero-valued measure
189   */
190  public Measure<U> zero() {
191    // lazy init because 'this' is null in object initialization
192    if (m_zero == null) {
193      m_zero = ImmutableMeasure.ofRelativeUnits(0, this);
194    }
195    return m_zero;
196  }
197
198  /**
199   * Gets a measure with a magnitude of 1 in terms of this unit.
200   *
201   * @return the 1-valued measure
202   */
203  public Measure<U> one() {
204    // lazy init because 'this' is null in object initialization
205    if (m_one == null) {
206      m_one = ImmutableMeasure.ofRelativeUnits(1, this);
207    }
208    return m_one;
209  }
210
211  /**
212   * Creates a velocity unit derived from this one. Can be chained to denote velocity, acceleration,
213   * jerk, etc.
214   *
215   * <pre>
216   *   Meters.per(Second) // linear velocity
217   *   Kilograms.per(Second) // mass flow
218   *   Feet.per(Second).per(Second).of(32) // roughly 1G of acceleration
219   * </pre>
220   *
221   * @param period the time period of the velocity, such as seconds or milliseconds
222   * @return a velocity unit corresponding to the rate of change of this unit over time
223   */
224  public Velocity<U> per(Time period) {
225    return Velocity.combine(this, period);
226  }
227
228  /**
229   * Takes this unit and creates a new proportional unit where this unit is the numerator and the
230   * given denominator is the denominator.
231   *
232   * <pre>
233   *   Volts.per(Meter) // V/m
234   * </pre>
235   *
236   * @param <D> the type of the denominator units
237   * @param denominator the denominator of the proportional unit
238   * @return a combined proportional unit
239   */
240  @SuppressWarnings("unchecked")
241  public <D extends Unit<D>> Per<U, D> per(D denominator) {
242    return Per.combine((U) this, denominator);
243  }
244
245  /**
246   * Takes this unit and creates a new combinatory unit equivalent to this unit multiplied by
247   * another.
248   *
249   * <pre>
250   *   Volts.mult(Meter) // V*m
251   * </pre>
252   *
253   * @param <U2> the type of the unit to multiply by
254   * @param other the unit to multiply by
255   * @return a combined unit equivalent to this unit multiplied by the other
256   */
257  @SuppressWarnings("unchecked")
258  public <U2 extends Unit<U2>> Mult<U, U2> mult(U2 other) {
259    return Mult.combine((U) this, other);
260  }
261
262  /**
263   * Checks if this unit is equivalent to another one. Equivalence is determined by both units
264   * having the same base type and treat the same base unit magnitude as the same magnitude in their
265   * own units, to within {@link Measure#EQUIVALENCE_THRESHOLD}.
266   *
267   * @param other the unit to compare to.
268   * @return true if both units are equivalent, false if not
269   */
270  public boolean equivalent(Unit<?> other) {
271    if (!getClass().equals(other.getClass())) {
272      // different unit types, not compatible
273      return false;
274    }
275
276    double arbitrary = 16_777.214; // 2^24 / 1e3
277
278    return Math.abs(
279                this.m_fromBaseConverter.apply(arbitrary)
280                    - other.m_fromBaseConverter.apply(arbitrary))
281            <= Measure.EQUIVALENCE_THRESHOLD
282        && Math.abs(
283                this.m_toBaseConverter.apply(arbitrary) - other.m_toBaseConverter.apply(arbitrary))
284            <= Measure.EQUIVALENCE_THRESHOLD;
285  }
286
287  @Override
288  public boolean equals(Object o) {
289    if (this == o) {
290      return true;
291    }
292    if (!(o instanceof Unit)) {
293      return false;
294    }
295    Unit<?> that = (Unit<?>) o;
296    return m_name.equals(that.m_name) && m_symbol.equals(that.m_symbol) && this.equivalent(that);
297  }
298
299  @Override
300  public int hashCode() {
301    return Objects.hash(m_toBaseConverter, m_fromBaseConverter, m_name, m_symbol);
302  }
303
304  /**
305   * Gets the name of this unit.
306   *
307   * @return the unit's name
308   */
309  public String name() {
310    return m_name;
311  }
312
313  /**
314   * Gets the symbol of this unit.
315   *
316   * @return the unit's symbol
317   */
318  public String symbol() {
319    return m_symbol;
320  }
321
322  @Override
323  public String toString() {
324    return name();
325  }
326}