1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
48
49
50
51 public class OrekitMatchers {
52
53
54
55
56
57
58
59
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
100
101
102
103
104
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
114
115
116
117
118
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
130
131
132
133
134
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
145
146
147
148
149
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
186
187
188
189
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
198
199
200
201
202
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
212
213
214
215
216
217
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
225
226
227
228
229
230
231
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
242
243
244
245
246
247
248
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
262
263
264
265
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
285 mismatchDescription.appendText("position ");
286 position.describeMismatch(item.getPosition(), mismatchDescription);
287 return false;
288 } else if (!velocity.matches(item.getVelocity())) {
289
290 mismatchDescription.appendText("velocity ");
291 velocity.describeMismatch(item.getVelocity(), mismatchDescription);
292 return false;
293 } else {
294
295 return true;
296 }
297 }
298 };
299 }
300
301
302
303
304
305
306
307 public static Matcher<PVCoordinates> pvIs(PVCoordinates pv) {
308 return pvCloseTo(pv, 0);
309 }
310
311
312
313
314
315
316
317
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
327
328
329
330
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
339
340
341
342
343
344
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
377
378
379
380
381
382
383
384
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
396
397
398
399
400
401
402
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
440
441
442
443
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
477
478
479
480
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
493
494
495
496
497
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
511
512
513
514
515
516
517
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
531
532
533
534
535
536
537
538
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
552
553
554
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
582
583
584
585
586
587
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
603
604
605
606
607
608
609
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
626
627
628
629
630
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
660
661
662
663
664
665
666
667
668
669
670
671
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
706
707
708
709
710
711
712
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
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
748
749
750
751
752
753
754
755
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
779
780
781
782
783
784
785
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
816
817
818
819
820
821
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
854
855
856
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 }