1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.utils.units;
18  
19  import java.util.Arrays;
20  import java.util.Collections;
21  
22  import org.hipparchus.fraction.Fraction;
23  import org.hipparchus.util.Binary64;
24  import org.hipparchus.util.FastMath;
25  import org.junit.jupiter.api.Assertions;
26  import org.junit.jupiter.api.Test;
27  import org.orekit.errors.OrekitException;
28  import org.orekit.errors.OrekitMessages;
29  import org.orekit.utils.Constants;
30  
31  /**
32   * Unit tests for {@link Lexer}.
33   *
34   * @author Luc Maisonobe
35   */
36  public class UnitTest {
37  
38      @Test
39      public void testTime() {
40          Assertions.assertEquals(       1.0, Unit.SECOND.toSI(1.0), 1.0e-10);
41          Assertions.assertEquals(      60.0, Unit.MINUTE.toSI(1.0), 1.0e-10);
42          Assertions.assertEquals(    3600.0, Unit.HOUR.toSI(1.0),   1.0e-10);
43          Assertions.assertEquals(   86400.0, Unit.DAY.toSI(1.0),    1.0e-10);
44          Assertions.assertEquals(31557600.0, Unit.YEAR.toSI(1.0),   1.0e-10);
45          Assertions.assertEquals(31557600.0, Unit.YEAR.toSI(Double.valueOf(1.0)),   1.0e-10);
46          Assertions.assertEquals(31557600.0, Unit.YEAR.toSI(new Binary64(1.0)).getReal(),   1.0e-10);
47          Assertions.assertEquals(1.0,        Unit.SECOND.fromSI(     1.0), 1.0e-10);
48          Assertions.assertEquals(1.0,        Unit.MINUTE.fromSI(    60.0), 1.0e-10);
49          Assertions.assertEquals(1.0,        Unit.HOUR.fromSI(    3600.0), 1.0e-10);
50          Assertions.assertEquals(1.0,        Unit.DAY.fromSI(    86400.0), 1.0e-10);
51          Assertions.assertEquals(1.0,        Unit.YEAR.fromSI(31557600.0), 1.0e-10);
52          Assertions.assertEquals(1.0,        Unit.YEAR.fromSI(Double.valueOf(31557600.0)), 1.0e-10);
53          Assertions.assertEquals(1.0,        Unit.YEAR.fromSI(new Binary64(31557600.0)).getReal(), 1.0e-10);
54          Assertions.assertEquals(365.25,     Unit.DAY.fromSI(Unit.YEAR.toSI(1.0)), 1.0e-10);
55      }
56  
57      @Test
58      public void testSI() {
59          Assertions.assertTrue(Unit.PERCENT.sameDimensionSI().sameDimension(Unit.ONE));
60          Assertions.assertEquals("1", Unit.PERCENT.sameDimensionSI().getName());
61          Assertions.assertEquals("m³.s⁻²", Unit.parse("km**3/s**2").sameDimensionSI().getName());
62          Assertions.assertEquals("m⁻³.s⁻⁶.rad^(2/5)", Unit.parse("µas^(2/5)/(h**(2)×m)³").sameDimensionSI().getName());
63  
64      }
65  
66      @Test
67      public void testEquals() {
68          final Unit u1 = Unit.parse("kg/m³");
69          final Unit u2 = Unit.parse("kg/m^3");
70          Assertions.assertNotSame(u1, u2);
71          Assertions.assertEquals(u1, u2);
72          Assertions.assertNotEquals(u1.getName(), u2.getName());
73          Assertions.assertNotEquals(u1, Unit.parse("A").alias(u1.getName()));
74          Assertions.assertNotEquals(u1, null);
75          Assertions.assertNotEquals(u1, u1.getName());
76          Assertions.assertEquals(19160943, u1.hashCode());
77      }
78  
79      @Test
80      public void testReference() {
81          checkReference(Unit.NONE,                         "n/a",                     1.0,  0,  0,  0,  0, 0);
82          checkReference(Unit.ONE,                            "1",                     1.0,  0,  0,  0,  0, 0);
83          checkReference(Unit.PERCENT,                        "%",                    0.01,  0,  0,  0,  0, 0);
84          checkReference(Unit.SECOND,                         "s",                     1.0,  0,  0,  1,  0, 0);
85          checkReference(Unit.MINUTE,                       "min",                    60.0,  0,  0,  1,  0, 0);
86          checkReference(Unit.HOUR,                           "h",                  3600.0,  0,  0,  1,  0, 0);
87          checkReference(Unit.DAY,                            "d",                 86400.0,  0,  0,  1,  0, 0);
88          checkReference(Unit.YEAR,                           "a",              31557600.0,  0,  0,  1,  0, 0);
89          checkReference(Unit.HERTZ,                         "Hz",                     1.0,  0,  0, -1,  0, 0);
90          checkReference(Unit.METRE,                          "m",                     1.0,  0,  1,  0,  0, 0);
91          checkReference(Unit.KILOMETRE,                     "km",                  1000.0,  0,  1,  0,  0, 0);
92          checkReference(Unit.KILOGRAM,                      "kg",                     1.0,  1,  0,  0,  0, 0);
93          checkReference(Unit.GRAM,                           "g",                   0.001,  1,  0,  0,  0, 0);
94          checkReference(Unit.AMPERE,                         "A",                     1.0,  0,  0,  0,  1, 0);
95          checkReference(Unit.RADIAN,                       "rad",                     1.0,  0,  0,  0,  0, 1);
96          checkReference(Unit.DEGREE,                         "°",  FastMath.PI /    180.0,  0,  0,  0,  0, 1);
97          checkReference(Unit.ARC_MINUTE,                     "′",  FastMath.PI /  10800.0,  0,  0,  0,  0, 1);
98          checkReference(Unit.ARC_SECOND,                     "″",  FastMath.PI / 648000.0,  0,  0,  0,  0, 1);
99          checkReference(Unit.REVOLUTION,                   "rev",       2.0 * FastMath.PI,  0,  0,  0,  0, 1);
100         checkReference(Unit.NEWTON,                         "N",                     1.0,  1,  1, -2,  0, 0);
101         checkReference(Unit.PASCAL,                        "Pa",                     1.0,  1, -1, -2,  0, 0);
102         checkReference(Unit.BAR,                          "bar",                100000.0,  1, -1, -2,  0, 0);
103         checkReference(Unit.JOULE,                          "J",                     1.0,  1,  2, -2,  0, 0);
104         checkReference(Unit.WATT,                           "W",                     1.0,  1,  2, -3,  0, 0);
105         checkReference(Unit.COULOMB,                        "C",                     1.0,  0,  0,  1,  1, 0);
106         checkReference(Unit.VOLT,                           "V",                     1.0,  1,  2, -3, -1, 0);
107         checkReference(Unit.OHM,                            "Ω",                     1.0,  1,  2, -3, -2, 0);
108         checkReference(Unit.TESLA,                          "T",                     1.0,  1,  0, -2, -1, 0);
109         checkReference(Unit.SOLAR_FLUX_UNIT,              "SFU",                 1.0e-22,  1,  0, -2,  0, 0);
110         checkReference(Unit.TOTAL_ELECTRON_CONTENT_UNIT, "TECU",                  1.0e16,  0, -2,  0,  0, 0);
111 
112     }
113 
114     private void checkReference(final Unit unit, final String name, final double scale,
115                                 final int mass, final int length, final int time,
116                                 final int current, final int angle) {
117         Assertions.assertEquals(name, unit.toString());
118         Assertions.assertEquals(scale, unit.getScale(), 1.0e-10);
119         Assertions.assertEquals(new Fraction(mass),     unit.getMass());
120         Assertions.assertEquals(new Fraction(length),   unit.getLength());
121         Assertions.assertEquals(new Fraction(time),     unit.getTime());
122         Assertions.assertEquals(new Fraction(current),  unit.getCurrent());
123         Assertions.assertEquals(new Fraction(angle),    unit.getAngle());
124     }
125 
126     @Test
127     public void testNotAUnit() {
128         Assertions.assertSame(Unit.NONE, Unit.parse("n/a"));
129     }
130 
131     @Test
132     public void testOneUnit() {
133         checkReference("1",
134                        1.0,
135                        Fraction.ZERO, Fraction.ZERO, Fraction.ZERO, Fraction.ZERO);
136     }
137 
138     @Test
139     public void testND() {
140         // nd does not mean "not defined", but nano-day…
141         checkReference("nd",
142                        Prefix.NANO.getFactor() * Constants.JULIAN_DAY,
143                        Fraction.ZERO, Fraction.ZERO, Fraction.ONE, Fraction.ZERO);
144     }
145 
146     @Test
147     public void testPredefinedUnit() {
148         checkReference("MHz",
149                        1.0e6,
150                        Fraction.ZERO, Fraction.ZERO, Fraction.MINUS_ONE, Fraction.ZERO);
151     }
152 
153     @Test
154     public void testSquareRoot() {
155         checkReference("km/√d",
156                        1000.0 / FastMath.sqrt(Constants.JULIAN_DAY),
157                        Fraction.ZERO, Fraction.ONE, new Fraction(-1, 2), Fraction.ZERO);
158     }
159 
160     @Test
161     public void testChain() {
162         checkReference("kg.m^(3/4).s⁻¹",
163                        1.0,
164                        Fraction.ONE, new Fraction(3, 4), Fraction.MINUS_ONE, Fraction.ZERO);
165     }
166 
167     @Test
168     public void testExponents() {
169         checkReference("µas^⅖/(h**(2)×m)³",
170                        FastMath.pow(FastMath.toRadians(1.0 / 3.6e9), 0.4) / FastMath.pow(3600, 6),
171                        Fraction.ZERO, new Fraction(-3, 1), new Fraction(-6, 1), new Fraction(2, 5));
172     }
173 
174     @Test
175     public void testCompoundInSquareRoot() {
176         checkReference("km/√(kg.s)",
177                        1000.0,
178                        new Fraction(-1, 2), Fraction.ONE, new Fraction(-1, 2), Fraction.ZERO);
179     }
180 
181     @Test
182     public void testLeftAssociativity() {
183         checkReference("(kg/m)/s²",
184                        1.0,
185                        Fraction.ONE, Fraction.MINUS_ONE, new Fraction(-2), Fraction.ZERO);
186         checkReference("kg/(m/s²)",
187                        1.0,
188                        Fraction.ONE, Fraction.MINUS_ONE, Fraction.TWO, Fraction.ZERO);
189         checkReference("kg/m/s²",
190                        1.0,
191                        Fraction.ONE, Fraction.MINUS_ONE, new Fraction(-2), Fraction.ZERO);
192     }
193 
194     @Test
195     public void testCcsdsRoot() {
196         checkReference("km**0.5/s",
197                        FastMath.sqrt(1000.0),
198                        Fraction.ZERO, Fraction.ONE_HALF, Fraction.MINUS_ONE, Fraction.ZERO);
199         checkReference("km/s**0.5",
200                        1000.0,
201                        Fraction.ZERO, Fraction.ONE, new Fraction(-1, 2), Fraction.ZERO);
202     }
203 
204     @Test
205     public void testNumber() {
206         checkReference("#/yr",
207                        1.0 / Constants.JULIAN_YEAR,
208                        Fraction.ZERO, Fraction.ZERO, Fraction.MINUS_ONE, Fraction.ZERO);
209     }
210 
211     @Test
212     public void testReciprocal() {
213         checkReference("1/s",
214                        1.0,
215                        Fraction.ZERO, Fraction.ZERO, Fraction.MINUS_ONE, Fraction.ZERO);
216     }
217 
218     @Test
219     public void testSeveralMicro() {
220         checkReference("µs", // here we use U+00B5, MICRO SIGN
221                        1.0e-6,
222                        Fraction.ZERO, Fraction.ZERO, Fraction.ONE, Fraction.ZERO);
223         checkReference("μs", // here we use U+03BC, GREEK SMALL LETTER MU
224                        1.0e-6,
225                        Fraction.ZERO, Fraction.ZERO, Fraction.ONE, Fraction.ZERO);
226     }
227 
228     @Test
229     public void testEmpty() {
230         expectFailure("");
231     }
232 
233     @Test
234     public void testIncompleteExponent1() {
235         expectFailure("m.g^(2/)");
236     }
237 
238     @Test
239     public void testIncompleteExponent2() {
240         expectFailure("m.g^(2m)");
241     }
242 
243     @Test
244     public void testMissingClosingParenthesis() {
245         expectFailure("m.(W");
246     }
247 
248     @Test
249     public void testGarbageOnInput() {
250         expectFailure("kg+s");
251     }
252 
253     @Test
254     public void testFactor() {
255         checkReference("kg/3s",
256                        1.0 / 3.0,
257                        Fraction.ONE, Fraction.ZERO, Fraction.MINUS_ONE, Fraction.ZERO);
258     }
259 
260     @Test
261     public void testMissingUnit() {
262         expectFailure("km/√");
263     }
264 
265     @Test
266     public void testRootAndPower() {
267         expectFailure("km/√d³");
268     }
269 
270     @Test
271     public void testRootAndParenthesisedPower() {
272         checkReference("km/√(d³)",
273                        1000.0 / (Constants.JULIAN_DAY * FastMath.sqrt(Constants.JULIAN_DAY)),
274                        Fraction.ZERO, Fraction.ONE, new Fraction(-3, 2), Fraction.ZERO);
275     }
276 
277     @Test
278     public void checkWrongNumber() {
279         try {
280             Unit.ensureCompatible("some description",
281                                     Arrays.asList(Unit.METRE, Unit.METRE, Unit.METRE),
282                                     false,
283                                     Collections.singletonList(Unit.KILOMETRE));
284             Assertions.fail("an exception should have been thrown");
285         } catch (OrekitException oe) {
286             Assertions.assertEquals(OrekitMessages.WRONG_NB_COMPONENTS, oe.getSpecifier());
287             Assertions.assertEquals("some description", oe.getParts()[0]);
288             Assertions.assertEquals(3, ((Integer) oe.getParts()[1]).intValue());
289             Assertions.assertEquals(1, ((Integer) oe.getParts()[2]).intValue());
290         }
291     }
292 
293     @Test
294     public void checkWrongUnitsDimension() {
295         try {
296             Unit.ensureCompatible(null,
297                                     Arrays.asList(Unit.METRE, Unit.METRE, Unit.METRE),
298                                     false,
299                                     Arrays.asList(Unit.METRE, Unit.SECOND, Unit.METRE));
300             Assertions.fail("an exception should have been thrown");
301         } catch (OrekitException oe) {
302             Assertions.assertEquals(OrekitMessages.INCOMPATIBLE_UNITS, oe.getSpecifier());
303             Assertions.assertEquals("m", oe.getParts()[0]);
304             Assertions.assertEquals("s", oe.getParts()[1]);
305         }
306     }
307 
308     @Test
309     public void checkWrongUnitsScale() {
310         try {
311             Unit.ensureCompatible(null,
312                                     Arrays.asList(Unit.METRE, Unit.METRE, Unit.METRE),
313                                     false,
314                                     Arrays.asList(Unit.METRE, Unit.KILOMETRE, Unit.METRE));
315             Assertions.fail("an exception should have been thrown");
316         } catch (OrekitException oe) {
317             Assertions.assertEquals(OrekitMessages.INCOMPATIBLE_UNITS, oe.getSpecifier());
318             Assertions.assertEquals("m",  oe.getParts()[0]);
319             Assertions.assertEquals("km", oe.getParts()[1]);
320         }
321     }
322 
323     @Test
324     public void checkCorrectUnitsScale() {
325         Unit.ensureCompatible(null,
326                                 Arrays.asList(Unit.METRE, Unit.METRE, Unit.METRE),
327                                 true,
328                                 Arrays.asList(Unit.METRE, Unit.KILOMETRE, Unit.METRE));
329     }
330 
331     private void checkReference(final String unitSpecification, final double scale,
332                                 final Fraction mass, final Fraction length,
333                                 final Fraction time, final Fraction angle) {
334         final Unit unit = Unit.parse(unitSpecification);
335         Assertions.assertEquals(unitSpecification, unit.toString());
336         Assertions.assertEquals(scale,  unit.getScale(), 1.0e-10 * scale);
337         Assertions.assertEquals(mass,   unit.getMass());
338         Assertions.assertEquals(length, unit.getLength());
339         Assertions.assertEquals(time,   unit.getTime());
340         Assertions.assertEquals(angle,  unit.getAngle());
341     }
342 
343     private void expectFailure(final String unitSpecification) {
344         try {
345             Unit.parse(unitSpecification);
346             Assertions.fail("an exception should have been thrown");
347         } catch (OrekitException oe) {
348             Assertions.assertEquals(OrekitMessages.UNKNOWN_UNIT, oe.getSpecifier());
349             Assertions.assertEquals(unitSpecification, oe.getParts()[0]);
350         }
351     }
352 
353 }