1   /* Contributed in the public domain.
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;
18  
19  import org.hamcrest.Description;
20  import org.hamcrest.Matcher;
21  import org.hamcrest.Matchers;
22  import org.hamcrest.SelfDescribing;
23  import org.hamcrest.TypeSafeDiagnosingMatcher;
24  import org.hamcrest.TypeSafeMatcher;
25  import org.hipparchus.CalculusFieldElement;
26  import org.hipparchus.FieldElement;
27  import org.hipparchus.dfp.Dfp;
28  import org.hipparchus.geometry.euclidean.threed.Rotation;
29  import org.hipparchus.geometry.euclidean.threed.Vector3D;
30  import org.hipparchus.linear.RealMatrix;
31  import org.hipparchus.util.FastMath;
32  import org.hipparchus.util.Precision;
33  import org.orekit.attitudes.Attitude;
34  import org.orekit.bodies.GeodeticPoint;
35  import org.orekit.time.AbsoluteDate;
36  import org.orekit.utils.Constants;
37  import org.orekit.utils.PVCoordinates;
38  
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.Collection;
42  
43  import static org.hamcrest.CoreMatchers.is;
44  import static org.hamcrest.Matchers.array;
45  
46  /**
47   * A set of matchers specific to the Orekit value classes.
48   *
49   * @author Evan Ward
50   */
51  public class OrekitMatchers {
52  
53      /**
54       * Match a geodetic point
55       *
56       * @param lat latitude matcher, in radians
57       * @param lon longitude matcher, in radians
58       * @param alt altitude matcher, in meters
59       * @return a {@link GeodeticPoint} matcher
60       */
61      public static Matcher<GeodeticPoint> geodeticPoint(
62              final Matcher<Double> lat,
63              final Matcher<Double> lon,
64              final Matcher<Double> alt) {
65          return new TypeSafeDiagnosingMatcher<GeodeticPoint>() {
66              @Override
67              public void describeTo(Description description) {
68                  description.appendList("GeodeticPoint[", ", ", "]",
69                          Arrays.<SelfDescribing>asList(lat, lon, alt));
70              }
71  
72              @Override
73              protected boolean matchesSafely(GeodeticPoint item,
74                                              Description mismatchDescription) {
75                  if (!lat.matches(item.getLatitude())) {
76                      mismatchDescription.appendText("the latitude ");
77                      lat.describeMismatch(item.getLatitude(),
78                              mismatchDescription);
79                      return false;
80                  }
81                  if (!lon.matches(item.getLongitude())) {
82                      mismatchDescription.appendText("the longitude ");
83                      lon.describeMismatch(item.getLongitude(),
84                              mismatchDescription);
85                      return false;
86                  }
87                  if (!alt.matches(item.getAltitude())) {
88                      mismatchDescription.appendText("the altitude ");
89                      alt.describeMismatch(item.getAltitude(),
90                              mismatchDescription);
91                      return false;
92                  }
93                  return true;
94              }
95          };
96      }
97  
98      /**
99       * Match a geodetic point
100      *
101      * @param lat latitude, in radians
102      * @param lon longitude, in radians
103      * @param alt altitude, in meters
104      * @return matcher of a {@link GeodeticPoint}
105      */
106     public static Matcher<GeodeticPoint> geodeticPoint(double lat,
107                                                        double lon,
108                                                        double alt) {
109         return geodeticPoint(is(lat), is(lon), is(alt));
110     }
111 
112     /**
113      * Match a geodetic point by comparing it with another one.
114      *
115      * @param expected the expected value
116      * @param absTol   the absolute tolerance on the comparison, in meters.
117      *                 Differences less than this value will be ignored.
118      * @return a {@link GeodeticPoint} matcher
119      */
120     public static Matcher<GeodeticPoint> geodeticPointCloseTo(
121             GeodeticPoint expected, double absTol) {
122         double angularAbsTol = absTol / Constants.WGS84_EARTH_EQUATORIAL_RADIUS;
123         return geodeticPoint(closeTo(expected.getLatitude(), angularAbsTol),
124                 closeTo(expected.getLongitude(), angularAbsTol),
125                 closeTo(expected.getAltitude(), absTol));
126     }
127 
128     /**
129      * Match a geodetic point by comparing it with another one.
130      *
131      * @param expected the expected value
132      * @param ulps     the ulps difference allowed
133      * @return a {@link GeodeticPoint} matcher
134      * @see #relativelyCloseTo
135      */
136     public static Matcher<GeodeticPoint> geodeticPointCloseTo(
137             GeodeticPoint expected, int ulps) {
138         return geodeticPoint(relativelyCloseTo(expected.getLatitude(), ulps),
139                 relativelyCloseTo(expected.getLongitude(), ulps),
140                 relativelyCloseTo(expected.getAltitude(), ulps));
141     }
142 
143     /**
144      * Matches a {@link Vector3D} based on its three coordinates.
145      *
146      * @param x matcher for the x coordinate
147      * @param y matcher for the y coordinate
148      * @param z matcher for the z coordinate
149      * @return a vector matcher
150      */
151     public static Matcher<Vector3D> vector(final Matcher<Double> x,
152                                            final Matcher<Double> y,
153                                            final Matcher<Double> z) {
154         return new TypeSafeDiagnosingMatcher<Vector3D>() {
155             @Override
156             public void describeTo(Description description) {
157                 description.appendList("Vector3D[", ", ", "]",
158                         Arrays.<SelfDescribing>asList(x, y, z));
159             }
160 
161             @Override
162             protected boolean matchesSafely(Vector3D item,
163                                             Description mismatchDescription) {
164                 if (!x.matches(item.getX())) {
165                     mismatchDescription.appendText("the x coordinate ");
166                     x.describeMismatch(item.getX(), mismatchDescription);
167                     return false;
168                 }
169                 if (!y.matches(item.getY())) {
170                     mismatchDescription.appendText("the y coordinate ");
171                     y.describeMismatch(item.getY(), mismatchDescription);
172                     return false;
173                 }
174                 if (!z.matches(item.getZ())) {
175                     mismatchDescription.appendText("the z coordinate ");
176                     z.describeMismatch(item.getZ(), mismatchDescription);
177                     return false;
178                 }
179                 return true;
180             }
181         };
182     }
183 
184     /**
185      * Matches a {@link Vector3D} close to another one.
186      *
187      * @param vector the reference vector
188      * @param absTol absolute tolerance of comparison, in each dimension
189      * @return a vector matcher.
190      */
191     public static Matcher<Vector3D> vectorCloseTo(Vector3D vector, double absTol) {
192         return vector(closeTo(vector.getX(), absTol),
193                 closeTo(vector.getY(), absTol), closeTo(vector.getZ(), absTol));
194     }
195 
196     /**
197      * Matches a {@link Vector3D} close to another one.
198      *
199      * @param vector the reference vector
200      * @param ulps   the relative tolerance, in units in last place, of the
201      *               Comparison of each dimension.
202      * @return a vector matcher.
203      */
204     public static Matcher<Vector3D> vectorCloseTo(Vector3D vector, int ulps) {
205         return vector(relativelyCloseTo(vector.getX(), ulps),
206                 relativelyCloseTo(vector.getY(), ulps),
207                 relativelyCloseTo(vector.getZ(), ulps));
208     }
209 
210     /**
211      * Alias for {@link #vectorCloseTo(Vector3D, int)}
212      *
213      * @param x    the x component
214      * @param y    the y component
215      * @param z    the z component
216      * @param ulps the relative tolerance, in ulps
217      * @return a vector matcher
218      */
219     public static Matcher<Vector3D> vectorCloseTo(double x, double y, double z, int ulps) {
220         return vectorCloseTo(new Vector3D(x, y, z), ulps);
221     }
222 
223     /**
224      * Matches a {@link Vector3D} to another one.
225      *
226      * @param vector the reference vector
227      * @param absTol the absolute tolerance of comparison, in each dimension.
228      * @param ulps   the relative tolerance of comparison in each dimension, in
229      *               units in last place.
230      * @return a matcher that matches if either the absolute or relative
231      * comparison matches in each dimension.
232      */
233     public static Matcher<Vector3D> vectorCloseTo(Vector3D vector,
234                                                   double absTol, int ulps) {
235         return vector(numberCloseTo(vector.getX(), absTol, ulps),
236                 numberCloseTo(vector.getY(), absTol, ulps),
237                 numberCloseTo(vector.getZ(), absTol, ulps));
238     }
239 
240     /**
241      * Matches a {@link Vector3D} to another one.
242      *
243      * @param vector the reference vector
244      * @param absTol the absolute tolerance of comparison, in each dimension.
245      * @param relTol the relative tolerance of comparison, which is multiplied
246      *               by {@code vector}'s magnitude.
247      * @return a matcher that matches if either the absolute or relative
248      * comparison matches in each dimension.
249      */
250     public static Matcher<Vector3D> vectorCloseTo(Vector3D vector,
251                                                   double absTol,
252                                                   double relTol) {
253         final double tol = FastMath.max(absTol, relTol * vector.getNorm());
254         return vector(
255                 closeTo(vector.getX(), tol),
256                 closeTo(vector.getY(), tol),
257                 closeTo(vector.getZ(), tol));
258     }
259 
260     /**
261      * Match a {@link PVCoordinates}
262      *
263      * @param position matcher for the position
264      * @param velocity matcher for the velocity
265      * @return a matcher of {@link PVCoordinates}
266      */
267     public static Matcher<PVCoordinates> pvIs(
268             final Matcher<? super Vector3D> position,
269             final Matcher<? super Vector3D> velocity) {
270         return new TypeSafeDiagnosingMatcher<PVCoordinates>() {
271 
272             @Override
273             public void describeTo(Description description) {
274                 description.appendText("position ");
275                 description.appendDescriptionOf(position);
276                 description.appendText(" and velocity ");
277                 description.appendDescriptionOf(velocity);
278             }
279 
280             @Override
281             protected boolean matchesSafely(PVCoordinates item,
282                     Description mismatchDescription) {
283                 if (!position.matches(item.getPosition())) {
284                     // position doesn't match
285                     mismatchDescription.appendText("position ");
286                     position.describeMismatch(item.getPosition(), mismatchDescription);
287                     return false;
288                 } else if (!velocity.matches(item.getVelocity())) {
289                     // velocity doesn't match
290                     mismatchDescription.appendText("velocity ");
291                     velocity.describeMismatch(item.getVelocity(), mismatchDescription);
292                     return false;
293                 } else {
294                     // both p and v matched
295                     return true;
296                 }
297             }
298         };
299     }
300 
301     /**
302      * Check that a {@link PVCoordinates} is the same as another one.
303      *
304      * @param pv the reference {@link PVCoordinates}
305      * @return a {@link PVCoordinates} {@link Matcher}
306      */
307     public static Matcher<PVCoordinates> pvIs(PVCoordinates pv) {
308         return pvCloseTo(pv, 0);
309     }
310 
311     /**
312      * Match a {@link PVCoordinates} close to another one.
313      *
314      * @param pv     the reference {@link PVCoordinates}
315      * @param absTol distance a matched {@link PVCoordinates} can be from the reference in
316      *               any one coordinate.
317      * @return a matcher of {@link PVCoordinates}.
318      */
319     public static Matcher<PVCoordinates> pvCloseTo(PVCoordinates pv,
320                                                    double absTol) {
321         return pvIs(vectorCloseTo(pv.getPosition(), absTol),
322                 vectorCloseTo(pv.getVelocity(), absTol));
323     }
324 
325     /**
326      * Match a {@link PVCoordinates} close to another one.
327      *
328      * @param pv   the reference {@link PVCoordinates}
329      * @param ulps the units in last place any coordinate can be off by.
330      * @return a matcher of {@link PVCoordinates}.
331      */
332     public static Matcher<PVCoordinates> pvCloseTo(PVCoordinates pv, int ulps) {
333         return pvIs(vectorCloseTo(pv.getPosition(), ulps),
334                 vectorCloseTo(pv.getVelocity(), ulps));
335     }
336 
337     /**
338      * Checks if two numbers are relatively close to each other. For absolute
339      * comparisons, see {@link #closeTo(double, double)}.
340      *
341      * @param expected the expected value in the relative comparison
342      * @param ulps     the units in last place of {@code expected} the two
343      *                 numbers can be off by.
344      * @return a matcher of numbers
345      */
346     public static Matcher<Double> relativelyCloseTo(final double expected,
347                                                     final int ulps) {
348         return new TypeSafeDiagnosingMatcher<Double>() {
349 
350             @Override
351             public void describeTo(Description description) {
352                 description.appendText("a numeric value within ")
353                         .appendValue(ulps).appendText(" ulps of ")
354                         .appendValue(expected);
355             }
356 
357             @Override
358             protected boolean matchesSafely(Double item,
359                                             Description mismatchDescription) {
360                 if (!Precision.equals(item, expected, ulps)) {
361                     mismatchDescription
362                             .appendValue(item)
363                             .appendText(" was off by ")
364                             .appendValue(
365                                     Double.doubleToLongBits(item)
366                                             - Double.doubleToLongBits(expected))
367                             .appendText(" ulps");
368                     return false;
369                 }
370                 return true;
371             }
372         };
373     }
374 
375     /**
376      * Check a number is close to another number using a relative
377      * <strong>or</strong> absolute comparison.
378      *
379      * @param number the expected value
380      * @param absTol absolute tolerance of comparison
381      * @param ulps   units in last place tolerance for relative comparison
382      * @return a matcher that matches if the differences is less than or equal
383      * to absTol <strong>or</strong> the two numbers differ by less or equal
384      * ulps.
385      */
386     public static Matcher<Double> numberCloseTo(double number, double absTol,
387                                                 int ulps) {
388         Collection<Matcher<Double>> matchers = new ArrayList<>(2);
389         matchers.add(closeTo(number, absTol));
390         matchers.add(relativelyCloseTo(number, ulps));
391         return anyOf(matchers);
392     }
393 
394     /**
395      * Check a number is close to another number using a relative <strong>or</strong>
396      * absolute comparison.
397      *
398      * @param expected the expected value
399      * @param absTol   absolute tolerance of comparison
400      * @param relTol   relative tolerance of {@code number}.
401      * @return a matcher that matches if the differences is less than or equal to absTol
402      * <strong>or</strong> the two numbers differ by less than the relative tolerance.
403      */
404     public static Matcher<Double> numberCloseTo(final double expected,
405                                                 final double absTol,
406                                                 final double relTol) {
407         return new TypeSafeDiagnosingMatcher<Double>() {
408 
409             @Override
410             public void describeTo(Description description) {
411                 description.appendText("a numeric value within ")
412                         .appendValue(absTol).appendText(" absolute tolerance or")
413                         .appendValue(relTol).appendText(" relative tolerance of ")
414                         .appendValue(expected);
415             }
416 
417             @Override
418             protected boolean matchesSafely(Double item,
419                                             Description mismatchDescription) {
420                 final double tol = FastMath.max(absTol, relTol * FastMath.abs(expected));
421                 final double difference = item - expected;
422                 if (FastMath.abs(difference) > tol || Double.isNaN(item)
423                         || Double.isNaN(expected)) {
424                     mismatchDescription
425                             .appendValue(item)
426                             .appendText(" was off by ")
427                             .appendValue(difference)
428                             .appendText(" absolute and ")
429                             .appendValue(difference / FastMath.abs(expected))
430                             .appendText(" relative");
431                     return false;
432                 }
433                 return true;
434             }
435         };
436     }
437 
438     /**
439      * match a double array based on matchers for each element.
440      *
441      * @param matchers matcher for each element.
442      * @return a matcher for a double[]
443      * @see org.hamcrest.collection.IsArray
444      */
445     @SafeVarargs
446     public static Matcher<double[]> doubleArrayContaining(
447             Matcher<? super Double>... matchers) {
448         return new TypeSafeDiagnosingMatcher<double[]>() {
449 
450             @Override
451             public void describeTo(Description description) {
452                 description.appendList("[", ", ", "]", Arrays.<Matcher<?>>asList(matchers));
453             }
454 
455             @Override
456             protected boolean matchesSafely(double[] actual,
457                                             Description mismatchDescription) {
458                 if (actual.length != matchers.length) {
459                     mismatchDescription.appendText("array length was " + actual.length);
460                     return false;
461                 }
462                 for (int i = 0; i < actual.length; i++) {
463                     if (!matchers[i].matches(actual[i])) {
464                         mismatchDescription.appendText("in element " + i + " ");
465                         matchers[i].describeMismatch(actual[i],
466                                 mismatchDescription);
467                         return false;
468                     }
469                 }
470                 return true;
471             }
472         };
473     }
474 
475     /**
476      * Check that the double[] contains exactly the specified values.
477      *
478      * @param values the expected values.
479      * @return matcher for double[]s
480      * @see Matchers#arrayContaining(Object...)
481      */
482     public static Matcher<double[]> doubleArrayContaining(double... values) {
483         @SuppressWarnings("unchecked")
484         Matcher<Double>[] elementMatchers = new Matcher[values.length];
485         for (int i = 0; i < elementMatchers.length; i++) {
486             elementMatchers[i] = Matchers.is(values[i]);
487         }
488         return doubleArrayContaining(elementMatchers);
489     }
490 
491     /**
492      * Check that a double[] contains the specified values to within an absolute
493      * tolerance.
494      *
495      * @param values the expected values
496      * @param absTol the absolute tolerance used in comparisons
497      * @return a {@link Matcher} for double[]'s
498      */
499     public static Matcher<double[]> doubleArrayContaining(
500             final double[] values, final double absTol) {
501         @SuppressWarnings("unchecked")
502         Matcher<Double>[] elementMatchers = new Matcher[values.length];
503         for (int i = 0; i < elementMatchers.length; i++) {
504             elementMatchers[i] = closeTo(values[i], absTol);
505         }
506         return doubleArrayContaining(elementMatchers);
507     }
508 
509     /**
510      * Check that a double[] contains the specified values to within a relative
511      * tolerance.
512      *
513      * @param values the expected values
514      * @param ulps   the units in last place the values can be off by. These are ulps of
515      *               the expected values. Specifying 1 ulp corresponds to ~16 decimal
516      *               digits of accuracy.
517      * @return a {@link Matcher} for a double[]
518      */
519     public static Matcher<double[]> doubleArrayContaining(
520             final double[] values, final int ulps) {
521         @SuppressWarnings("unchecked")
522         Matcher<Double>[] elementMatchers = new Matcher[values.length];
523         for (int i = 0; i < elementMatchers.length; i++) {
524             elementMatchers[i] = relativelyCloseTo(values[i], ulps);
525         }
526         return doubleArrayContaining(elementMatchers);
527     }
528 
529     /**
530      * Check that a double[] contains the specified values to within a relative tolerance
531      * or absolute tolerance. Specifically, each comparison will pass if the difference is
532      * less than {@code absTol} <em>Or</em> the difference, expressed in ulps is less than
533      * {@code ulps}.
534      *
535      * @param values the expected values
536      * @param absTol absolute tolerance on comparisons
537      * @param ulps   relative tolerance, in units in last place
538      * @return matcher for the double array
539      */
540     public static Matcher<double[]> doubleArrayContaining(
541             final double[] values, final double absTol, final int ulps) {
542         @SuppressWarnings("unchecked")
543         Matcher<Double>[] elementMatchers = new Matcher[values.length];
544         for (int i = 0; i < elementMatchers.length; i++) {
545             elementMatchers[i] = numberCloseTo(values[i], absTol, ulps);
546         }
547         return doubleArrayContaining(elementMatchers);
548     }
549 
550     /**
551      * Match a {@link RealMatrix#getData()} as a double[][].
552      *
553      * @param dataMatcher matcher for the data as a double[][]
554      * @return a {@link RealMatrix} matcher
555      */
556     public static Matcher<RealMatrix> matrix(
557             final Matcher<double[][]> dataMatcher) {
558         return new TypeSafeDiagnosingMatcher<RealMatrix>() {
559             @Override
560             public void describeTo(Description description) {
561                 description.appendText("matrix that ").appendDescriptionOf(
562                         dataMatcher);
563             }
564 
565             @Override
566             protected boolean matchesSafely(RealMatrix item,
567                                             Description mismatchDescription) {
568                 if (dataMatcher.matches(item.getData())) {
569                     return true;
570                 } else {
571                     mismatchDescription.appendText("matrix did not match ");
572                     dataMatcher.describeMismatch(item.getData(),
573                             mismatchDescription);
574                     return false;
575                 }
576             }
577         };
578     }
579 
580     /**
581      * Match a {@link RealMatrix} to within the given absolute tolerance
582      *
583      * @param expected the expected values
584      * @param absTol   the absolute tolerance on the comparison
585      * @return a matrix matcher
586      * @see #matrix(Matcher)
587      * @see #doubleArrayContaining(double[], double)
588      */
589     public static Matcher<RealMatrix> matrixCloseTo(RealMatrix expected,
590                                                     double absTol) {
591         @SuppressWarnings("rawtypes")
592         Matcher[] rowMatchers = new Matcher[expected.getRowDimension()];
593         for (int i = 0; i < rowMatchers.length; i++) {
594             rowMatchers[i] = doubleArrayContaining(expected.getRow(i), absTol);
595         }
596         @SuppressWarnings("unchecked")
597         Matcher<double[][]> dataMatcher = array(rowMatchers);
598         return matrix(dataMatcher);
599     }
600 
601     /**
602      * Match a {@link RealMatrix} to within the given absolute tolerance
603      *
604      * @param expected the expected values
605      * @param absTol   the absolute tolerance on the comparison
606      * @param ulps     the maximum allowable difference, in units in last place
607      * @return a matrix matcher
608      * @see #matrix(Matcher)
609      * @see #doubleArrayContaining(double[], double)
610      */
611     public static Matcher<RealMatrix> matrixCloseTo(RealMatrix expected,
612                                                     double absTol, int ulps) {
613         @SuppressWarnings("rawtypes")
614         Matcher[] rowMatchers = new Matcher[expected.getRowDimension()];
615         for (int i = 0; i < rowMatchers.length; i++) {
616             rowMatchers[i] = doubleArrayContaining(expected.getRow(i), absTol,
617                     ulps);
618         }
619         @SuppressWarnings("unchecked")
620         Matcher<double[][]> dataMatcher = array(rowMatchers);
621         return matrix(dataMatcher);
622     }
623 
624     /**
625      * Create a matcher that matches if at least one of the given matchers match. Gives
626      * better descriptions that {@link org.hamcrest.CoreMatchers#anyOf(Iterable)}.
627      *
628      * @param matchers to try.
629      * @param <T>      type of object to match.
630      * @return a new matcher.
631      */
632     public static <T> Matcher<T> anyOf(Collection<? extends Matcher<? super T>> matchers) {
633         return new TypeSafeDiagnosingMatcher<T>() {
634             @Override
635             protected boolean matchesSafely(final T item,
636                                             final Description mismatchDescription) {
637                 boolean first = true;
638                 for (Matcher<? super T> matcher : matchers) {
639                     if (matcher.matches(item)) {
640                         return true;
641                     } else {
642                         if (!first) {
643                             mismatchDescription.appendText(" and ");
644                         }
645                         matcher.describeMismatch(item, mismatchDescription);
646                     }
647                     first = false;
648                 }
649                 return false;
650             }
651 
652             @Override
653             public void describeTo(final Description description) {
654                 description.appendList("(", " or ", ")", matchers);
655             }
656         };
657     }
658 
659     /* Copid from Hamcrest's IsCloseTo under the new BSD license.
660      * Copyright (c) 2000-2006 hamcrest.org
661      */
662 
663     /**
664      * Creates a matcher of {@link Double}s that matches when an examined double
665      * is equal to the specified <code>operand</code>, within a range of +/-
666      * <code>error</code>. <p/> For example:
667      * <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
668      *
669      * @param value the expected value of matching doubles
670      * @param delta the delta (+/-) within which matches will be allowed
671      * @return a double matcher.
672      */
673     public static Matcher<Double> closeTo(final double value,
674                                           final double delta) {
675 
676         return new TypeSafeMatcher<Double>() {
677             @Override
678             public boolean matchesSafely(Double item) {
679                 return actualDelta(item) <= 0.0;
680             }
681 
682             @Override
683             public void describeMismatchSafely(Double item, Description mismatchDescription) {
684                 mismatchDescription.appendValue(item)
685                         .appendText(" differed by ")
686                         .appendValue(actualDelta(item));
687             }
688 
689             @Override
690             public void describeTo(Description description) {
691                 description.appendText("a numeric value within ")
692                         .appendValue(delta)
693                         .appendText(" of ")
694                         .appendValue(value);
695             }
696 
697             private double actualDelta(Double item) {
698                 return (FastMath.abs((item - value)) - delta);
699             }
700         };
701 
702     }
703 
704     /**
705      * Creates a matcher of {@link Double}s that matches when an examined double
706      * is equal to the specified <code>operand</code>, within a range of +/-
707      * <code>error</code>. <p/> For example:
708      * <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
709      *
710      * @param value the expected value of matching doubles
711      * @param delta the delta (+/-) within which matches will be allowed
712      * @return a double matcher.
713      */
714     public static <T extends CalculusFieldElement<T>> Matcher<T> closeTo(
715             final T value,
716             final double delta) {
717 
718         return new TypeSafeMatcher<T>() {
719             @Override
720             public boolean matchesSafely(T item) {
721                 // actualDelta(item) <= 0.0
722                 return actualDelta(item).sign().getReal() <= 0.0;
723             }
724 
725             @Override
726             public void describeMismatchSafely(T item, Description mismatchDescription) {
727                 mismatchDescription.appendValue(item)
728                         .appendText(" differed by ")
729                         .appendValue(actualDelta(item));
730             }
731 
732             @Override
733             public void describeTo(Description description) {
734                 description.appendText("a numeric value within ")
735                         .appendValue(delta)
736                         .appendText(" of ")
737                         .appendValue(value);
738             }
739 
740             private T actualDelta(T item) {
741                 return item.subtract(value).abs().subtract(delta);
742             }
743         };
744 
745     }
746 
747     /* Replace with Matchers.greaterThan(...) if hamcrest becomes available. */
748 
749     /**
750      * Create a matcher to see if a value is greater than another one using {@link
751      * Comparable#compareTo(Object)}.
752      *
753      * @param expected value.
754      * @param <T>      type of value.
755      * @return matcher of value.
756      */
757     public static <T extends Comparable<T>> Matcher<T> greaterThan(final T expected) {
758         return new TypeSafeDiagnosingMatcher<T>() {
759             @Override
760             protected boolean matchesSafely(T item, Description mismatchDescription) {
761                 if (expected.compareTo(item) >= 0) {
762                     mismatchDescription.appendText("less than or equal to ")
763                             .appendValue(expected);
764                     return false;
765                 }
766                 return true;
767             }
768 
769             @Override
770             public void describeTo(Description description) {
771                 description.appendText("greater than ").appendValue(expected);
772             }
773         };
774     }
775 
776 
777     /**
778      * Matcher for the distance in seconds between two {@link AbsoluteDate}s. Uses {@link
779      * AbsoluteDate#durationFrom(AbsoluteDate)}.
780      *
781      * @param date         the date to compare with.
782      * @param valueMatcher the matcher for the delta. For example, {@code closeTo(0,
783      *                     1e-10)}.
784      * @return a matcher that checks the time difference between two {@link
785      * AbsoluteDate}s.
786      */
787     public static Matcher<AbsoluteDate> durationFrom(final AbsoluteDate date,
788                                                      final Matcher<Double> valueMatcher) {
789         return new TypeSafeDiagnosingMatcher<AbsoluteDate>() {
790             @Override
791             public void describeTo(Description description) {
792                 description.appendText("delta from ");
793                 description.appendValue(date);
794                 description.appendText(" is ");
795                 description.appendDescriptionOf(valueMatcher);
796             }
797 
798             @Override
799             protected boolean matchesSafely(AbsoluteDate item,
800                                             Description mismatchDescription) {
801                 double delta = item.durationFrom(date);
802                 boolean matched = valueMatcher.matches(delta);
803                 if (!matched) {
804                     mismatchDescription.appendText("delta to ");
805                     mismatchDescription.appendValue(item);
806                     mismatchDescription.appendText(" ");
807                     valueMatcher.describeMismatch(delta, mismatchDescription);
808                 }
809                 return matched;
810             }
811         };
812     }
813 
814     /**
815      * Matcher that compares to {@link Rotation}s using {@link Rotation#distance(Rotation,
816      * Rotation)}.
817      *
818      * @param from         one of the rotations to compare
819      * @param valueMatcher matcher for the distances. For example {@code closeTo(0,
820      *                     1e-10)}.
821      * @return a matcher for rotations.
822      */
823     public static Matcher<Rotation> distanceIs(final Rotation from,
824                                                final Matcher<Double> valueMatcher) {
825         return new TypeSafeDiagnosingMatcher<Rotation>() {
826 
827             @Override
828             public void describeTo(Description description) {
829                 description.appendText("distance from ");
830                 description.appendValue(from);
831                 description.appendText(" is ");
832                 description.appendDescriptionOf(valueMatcher);
833             }
834 
835             @Override
836             protected boolean matchesSafely(Rotation item,
837                                             Description mismatchDescription) {
838                 double distance = Rotation.distance(from, item);
839                 boolean matched = valueMatcher.matches(distance);
840                 if (!matched) {
841                     mismatchDescription.appendText("distance to ");
842                     mismatchDescription.appendValue(item);
843                     mismatchDescription.appendText(" was ");
844                     valueMatcher
845                             .describeMismatch(distance, mismatchDescription);
846                 }
847                 return matched;
848             }
849         };
850     }
851 
852     /**
853      * Check that two attitudes are equivalent.
854      *
855      * @param expected attitude.
856      * @return attitude matcher.
857      */
858     public static Matcher<Attitude> attitudeIs(final Attitude expected) {
859         final Matcher<AbsoluteDate> dateMatcher = durationFrom(expected.getDate(), is(0.0));
860         final Matcher<Rotation> rotationMatcher = distanceIs(expected.getRotation(), is(0.0));
861         final Matcher<Vector3D> spinMatcher = vectorCloseTo(expected.getSpin(), 0);
862         final Matcher<Vector3D> accelerationMatcher =
863                 vectorCloseTo(expected.getRotationAcceleration(), 0);
864         final Rotation r = expected.getRotation();
865         return new TypeSafeDiagnosingMatcher<Attitude>() {
866 
867             @Override
868             protected boolean matchesSafely(Attitude item,
869                                             final Description mismatchDescription) {
870                 item = item.withReferenceFrame(expected.getReferenceFrame());
871                 if (!dateMatcher.matches(item)) {
872                     mismatchDescription.appendText("date ")
873                             .appendDescriptionOf(dateMatcher);
874                 }
875                 if (!rotationMatcher.matches(item.getRotation())) {
876                     mismatchDescription.appendText("rotation ")
877                             .appendDescriptionOf(rotationMatcher);
878                     return false;
879                 }
880                 if (!spinMatcher.matches(item.getSpin())) {
881                     mismatchDescription.appendText("spin ")
882                             .appendDescriptionOf(spinMatcher);
883                     return false;
884                 }
885                 if (!accelerationMatcher.matches(item.getRotationAcceleration())) {
886                     mismatchDescription.appendText("rotation acceleration ")
887                             .appendDescriptionOf(accelerationMatcher);
888                     return false;
889                 }
890                 return true;
891             }
892 
893             @Override
894             public void describeTo(Description description) {
895                 description.appendText("attitude on ").appendValue(expected.getDate())
896                         .appendText(" rotation ").appendValueList("[", ",", "]", r.getQ0(), r.getQ1(), r.getQ2(), r.getQ3())
897                         .appendText(" spin ").appendDescriptionOf(spinMatcher)
898                         .appendText(" acceleration ").appendDescriptionOf(accelerationMatcher);
899             }
900         };
901     }
902 
903 
904 }