View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF 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.apache.commons.geometry.euclidean.oned;
18  
19  import java.util.function.UnaryOperator;
20  
21  import org.apache.commons.geometry.core.GeometryTestUtils;
22  import org.apache.commons.geometry.euclidean.EuclideanTestUtils;
23  import org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.Test;
25  
26  class AffineTransformMatrix1DTest {
27  
28      private static final double EPS = 1e-12;
29  
30      @Test
31      void testOf() {
32          // act
33          final AffineTransformMatrix1D transform = AffineTransformMatrix1D.of(1, 2);
34  
35          // assert
36          Assertions.assertTrue(transform.preservesOrientation());
37  
38          final double[] result = transform.toArray();
39          Assertions.assertArrayEquals(new double[] {1, 2}, result, 0.0);
40      }
41  
42      @Test
43      void testOf_invalidDimensions() {
44          // act/assert
45          GeometryTestUtils.assertThrowsWithMessage(() -> AffineTransformMatrix1D.of(1),
46                  IllegalArgumentException.class, "Dimension mismatch: 1 != 2");
47      }
48  
49      @Test
50      void testFrom() {
51          // act/assert
52          Assertions.assertArrayEquals(new double[] {1, 0},
53                  AffineTransformMatrix1D.from(UnaryOperator.identity()).toArray(), EPS);
54          Assertions.assertArrayEquals(new double[] {1, 2},
55                  AffineTransformMatrix1D.from(v -> v.add(Vector1D.of(2))).toArray(), EPS);
56          Assertions.assertArrayEquals(new double[] {3, 0},
57                  AffineTransformMatrix1D.from(v -> v.multiply(3)).toArray(), EPS);
58          Assertions.assertArrayEquals(new double[] {3, 6},
59                  AffineTransformMatrix1D.from(v -> v.add(Vector1D.of(2)).multiply(3)).toArray(), EPS);
60      }
61  
62      @Test
63      void testFrom_invalidFunction() {
64          // act/assert
65          Assertions.assertThrows(IllegalArgumentException.class, () -> AffineTransformMatrix1D.from(v -> v.multiply(0)));
66      }
67  
68      @Test
69      void testIdentity() {
70          // act
71          final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
72  
73          // assert
74          Assertions.assertTrue(transform.preservesOrientation());
75  
76          final double[] expected = {1, 0};
77          Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
78      }
79  
80      @Test
81      void testCreateTranslation_value() {
82          // act
83          final AffineTransformMatrix1D transform = AffineTransformMatrix1D.createTranslation(2);
84  
85          // assert
86          Assertions.assertTrue(transform.preservesOrientation());
87  
88          final double[] expected = {1, 2};
89          Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
90      }
91  
92      @Test
93      void testCreateTranslation_vector() {
94          // act
95          final AffineTransformMatrix1D transform = AffineTransformMatrix1D.createTranslation(Vector1D.of(5));
96  
97          // assert
98          Assertions.assertTrue(transform.preservesOrientation());
99  
100         final double[] expected = {1, 5};
101         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
102     }
103 
104     @Test
105     void testTranslate_value() {
106         // arrange
107         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 10);
108 
109         // act
110         final AffineTransformMatrix1D result = a.translate(4);
111 
112         // assert
113         Assertions.assertTrue(result.preservesOrientation());
114 
115         final double[] expected = {2, 14};
116         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
117     }
118 
119     @Test
120     void testTranslate_vector() {
121         // arrange
122         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 10);
123 
124         // act
125         final AffineTransformMatrix1D result = a.translate(Vector1D.of(7));
126 
127         // assert
128         Assertions.assertTrue(result.preservesOrientation());
129 
130         final double[] expected = {2, 17};
131         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
132     }
133 
134     @Test
135     void testCreateScale_vector() {
136         // act
137         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(Vector1D.of(4));
138 
139         // assert
140         Assertions.assertTrue(transform.preservesOrientation());
141 
142         final double[] expected = {4, 0};
143         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
144     }
145 
146     @Test
147     void testCreateScale_value() {
148         // act
149         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(7);
150 
151         // assert
152         Assertions.assertTrue(transform.preservesOrientation());
153 
154         final double[] expected = {7, 0};
155         Assertions.assertArrayEquals(expected, transform.toArray(), 0.0);
156     }
157 
158     @Test
159     void testScale_value() {
160         // arrange
161         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 10);
162 
163         // act
164         final AffineTransformMatrix1D result = a.scale(4);
165 
166         // assert
167         Assertions.assertTrue(result.preservesOrientation());
168 
169         final double[] expected = {8, 40};
170         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
171     }
172 
173     @Test
174     void testScale_vector() {
175         // arrange
176         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 10);
177 
178         // act
179         final AffineTransformMatrix1D result = a.scale(Vector1D.of(7));
180 
181         // assert
182         Assertions.assertTrue(result.preservesOrientation());
183 
184         final double[] expected = {14, 70};
185         Assertions.assertArrayEquals(expected, result.toArray(), 0.0);
186     }
187 
188     @Test
189     void testApply_identity() {
190         // arrange
191         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
192 
193         // act/assert
194         runWithCoordinates(x -> {
195             final Vector1D v = Vector1D.of(x);
196 
197             EuclideanTestUtils.assertCoordinatesEqual(v, transform.apply(v), EPS);
198         });
199     }
200 
201     @Test
202     void testApply_translate() {
203         // arrange
204         final Vector1D translation = Vector1D.of(-Math.PI);
205 
206         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
207                 .translate(translation);
208 
209         // act/assert
210         runWithCoordinates(x -> {
211             final Vector1D vec = Vector1D.of(x);
212 
213             final Vector1D expectedVec = vec.add(translation);
214 
215             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
216         });
217     }
218 
219     @Test
220     void testApply_scale() {
221         // arrange
222         final Vector1D factor = Vector1D.of(2.0);
223 
224         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
225                 .scale(factor);
226 
227         // act/assert
228         runWithCoordinates(x -> {
229             final Vector1D vec = Vector1D.of(x);
230 
231             final Vector1D expectedVec = Vector1D.of(factor.getX() * x);
232 
233             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
234         });
235     }
236 
237     @Test
238     void testApply_translateThenScale() {
239         // arrange
240         final Vector1D translation = Vector1D.of(-2.0);
241         final Vector1D scale = Vector1D.of(5.0);
242 
243         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
244                 .translate(translation)
245                 .scale(scale);
246 
247         // act/assert
248         EuclideanTestUtils.assertCoordinatesEqual(Vector1D.of(-5), transform.apply(Vector1D.of(1)), EPS);
249 
250         runWithCoordinates(x -> {
251             final Vector1D vec = Vector1D.of(x);
252 
253             final Vector1D expectedVec = Vector1D.of(
254                         (x + translation.getX()) * scale.getX()
255                     );
256 
257             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
258         });
259     }
260 
261     @Test
262     void testApply_scaleThenTranslate() {
263         // arrange
264         final Vector1D scale = Vector1D.of(5.0);
265         final Vector1D translation = Vector1D.of(-2.0);
266 
267         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
268                 .scale(scale)
269                 .translate(translation);
270 
271         // act/assert
272         runWithCoordinates(x -> {
273             final Vector1D vec = Vector1D.of(x);
274 
275             final Vector1D expectedVec = Vector1D.of(
276                         (x * scale.getX()) + translation.getX()
277                     );
278 
279             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
280         });
281     }
282 
283     @Test
284     void testApplyX() {
285         // arrange
286         final Vector1D translation = Vector1D.of(-2.0);
287         final Vector1D scale = Vector1D.of(5.0);
288 
289         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
290                 .translate(translation)
291                 .scale(scale);
292 
293         // act/assert
294         runWithCoordinates(x -> {
295             final double expected = (x + translation.getX()) * scale.getX();
296 
297             Assertions.assertEquals(expected, transform.applyX(x), EPS);
298         });
299     }
300 
301     @Test
302     void testApplyVector_identity() {
303         // arrange
304         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
305 
306         // act/assert
307         runWithCoordinates(x -> {
308             final Vector1D v = Vector1D.of(x);
309 
310             EuclideanTestUtils.assertCoordinatesEqual(v, transform.applyVector(v), EPS);
311         });
312     }
313 
314     @Test
315     void testApplyVector_translate() {
316         // arrange
317         final Vector1D translation = Vector1D.of(-Math.PI);
318 
319         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
320                 .translate(translation);
321 
322         // act/assert
323         runWithCoordinates(x -> {
324             final Vector1D vec = Vector1D.of(x);
325 
326             EuclideanTestUtils.assertCoordinatesEqual(vec, transform.applyVector(vec), EPS);
327         });
328     }
329 
330     @Test
331     void testApplyVector_scale() {
332         // arrange
333         final Vector1D factor = Vector1D.of(2.0);
334 
335         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
336                 .scale(factor);
337 
338         // act/assert
339         runWithCoordinates(x -> {
340             final Vector1D vec = Vector1D.of(x);
341 
342             final Vector1D expectedVec = Vector1D.of(factor.getX() * x);
343 
344             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyVector(vec), EPS);
345         });
346     }
347 
348     @Test
349     void testApplyVector_representsDisplacement() {
350         // arrange
351         final Vector1D p1 = Vector1D.of(Math.PI);
352 
353         final Vector1D translation = Vector1D.of(-2.0);
354         final Vector1D scale = Vector1D.of(5.0);
355 
356         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
357                 .translate(translation)
358                 .scale(scale);
359 
360         // act/assert
361         runWithCoordinates(x -> {
362             final Vector1D p2 = Vector1D.of(x);
363             final Vector1D input = p1.subtract(p2);
364 
365             final Vector1D expectedVec = transform.apply(p1).subtract(transform.apply(p2));
366 
367             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyVector(input), EPS);
368         });
369     }
370 
371     @Test
372     void testApplyVectorX() {
373         // arrange
374         final Vector1D p1 = Vector1D.of(Math.PI);
375 
376         final Vector1D translation = Vector1D.of(-2.0);
377         final Vector1D scale = Vector1D.of(5.0);
378 
379         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
380                 .translate(translation)
381                 .scale(scale);
382 
383         // act/assert
384         runWithCoordinates(x -> {
385             final Vector1D p2 = p1.add(Vector1D.of(x));
386 
387             final double expected = transform.apply(p1).vectorTo(transform.apply(p2)).getX();
388 
389             Assertions.assertEquals(expected, transform.applyVectorX(x), EPS);
390         });
391     }
392 
393     @Test
394     void testApplyDirection_identity() {
395         // arrange
396         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity();
397 
398         // act/assert
399         runWithCoordinates(x -> {
400             final Vector1D v = Vector1D.of(x);
401 
402             EuclideanTestUtils.assertCoordinatesEqual(v.normalize(), transform.applyDirection(v), EPS);
403         }, true);
404     }
405 
406     @Test
407     void testApplyDirection_translate() {
408         // arrange
409         final Vector1D translation = Vector1D.of(-Math.PI);
410 
411         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
412                 .translate(translation);
413 
414         // act/assert
415         runWithCoordinates(x -> {
416             final Vector1D vec = Vector1D.of(x);
417 
418             EuclideanTestUtils.assertCoordinatesEqual(vec.normalize(), transform.applyDirection(vec), EPS);
419         }, true);
420     }
421 
422     @Test
423     void testApplyDirection_scale() {
424         // arrange
425         final Vector1D factor = Vector1D.of(2.0);
426 
427         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
428                 .scale(factor);
429 
430         // act/assert
431         runWithCoordinates(x -> {
432             final Vector1D vec = Vector1D.of(x);
433 
434             final Vector1D expectedVec = Vector1D.of(factor.getX() * x).normalize();
435 
436             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyDirection(vec), EPS);
437         }, true);
438     }
439 
440     @Test
441     void testApplyDirection_representsNormalizedDisplacement() {
442         // arrange
443         final Vector1D p1 = Vector1D.of(Math.PI);
444 
445         final Vector1D translation = Vector1D.of(-2.0);
446         final Vector1D scale = Vector1D.of(5.0);
447 
448         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.identity()
449                 .translate(translation)
450                 .scale(scale);
451 
452         // act/assert
453         runWithCoordinates(x -> {
454             final Vector1D p2 = Vector1D.of(x);
455             final Vector1D input = p1.subtract(p2);
456 
457             final Vector1D expectedVec = transform.apply(p1).subtract(transform.apply(p2)).normalize();
458 
459             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.applyDirection(input), EPS);
460         });
461     }
462 
463     @Test
464     void testApplyDirection_illegalNorm() {
465         // act/assert
466         Assertions.assertThrows(IllegalArgumentException.class, () -> AffineTransformMatrix1D.createScale(0).applyDirection(Vector1D.Unit.PLUS));
467         Assertions.assertThrows(IllegalArgumentException.class, () -> AffineTransformMatrix1D.createScale(2).applyDirection(Vector1D.ZERO));
468     }
469 
470     @Test
471     void testDeterminant() {
472         // act/assert
473         Assertions.assertEquals(0.0, AffineTransformMatrix1D.of(0, 1).determinant(), EPS);
474         Assertions.assertEquals(1.0, AffineTransformMatrix1D.of(1, 0).determinant(), EPS);
475         Assertions.assertEquals(-1.0, AffineTransformMatrix1D.of(-1, 2).determinant(), EPS);
476     }
477 
478     @Test
479     void testPreservesOrientation() {
480         // act/assert
481         Assertions.assertFalse(AffineTransformMatrix1D.of(0, 1).preservesOrientation());
482         Assertions.assertTrue(AffineTransformMatrix1D.of(1, 0).preservesOrientation());
483         Assertions.assertFalse(AffineTransformMatrix1D.of(-1, 2).preservesOrientation());
484     }
485 
486     @Test
487     void testMultiply() {
488         // arrange
489         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 3);
490         final AffineTransformMatrix1D b = AffineTransformMatrix1D.of(13, 14);
491 
492         // act
493         final AffineTransformMatrix1D result = a.multiply(b);
494 
495         // assert
496         final double[] arr = result.toArray();
497         Assertions.assertArrayEquals(new double[] {26, 31}, arr, EPS);
498     }
499 
500     @Test
501     void testMultiply_combinesTransformOperations() {
502         // arrange
503         final Vector1D translation1 = Vector1D.of(1);
504         final double scale = 2.0;
505         final Vector1D translation2 = Vector1D.of(4);
506 
507         final AffineTransformMatrix1D a = AffineTransformMatrix1D.createTranslation(translation1);
508         final AffineTransformMatrix1D b = AffineTransformMatrix1D.createScale(scale);
509         final AffineTransformMatrix1D c = AffineTransformMatrix1D.identity();
510         final AffineTransformMatrix1D d = AffineTransformMatrix1D.createTranslation(translation2);
511 
512         // act
513         final AffineTransformMatrix1D transform = d.multiply(c).multiply(b).multiply(a);
514 
515         // assert
516         runWithCoordinates(x -> {
517             final Vector1D vec = Vector1D.of(x);
518 
519             final Vector1D expectedVec = vec
520                     .add(translation1)
521                     .multiply(scale)
522                     .add(translation2);
523 
524             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
525         });
526     }
527 
528     @Test
529     void testPremultiply() {
530         // arrange
531         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(2, 3);
532         final AffineTransformMatrix1D b = AffineTransformMatrix1D.of(13, 14);
533 
534         // act
535         final AffineTransformMatrix1D result = b.premultiply(a);
536 
537         // assert
538         final double[] arr = result.toArray();
539         Assertions.assertArrayEquals(new double[] {26, 31}, arr, EPS);
540     }
541 
542     @Test
543     void testPremultiply_combinesTransformOperations() {
544         // arrange
545         final Vector1D translation1 = Vector1D.of(1);
546         final double scale = 2.0;
547         final Vector1D translation2 = Vector1D.of(4);
548 
549         final AffineTransformMatrix1D a = AffineTransformMatrix1D.createTranslation(translation1);
550         final AffineTransformMatrix1D b = AffineTransformMatrix1D.createScale(scale);
551         final AffineTransformMatrix1D c = AffineTransformMatrix1D.identity();
552         final AffineTransformMatrix1D d = AffineTransformMatrix1D.createTranslation(translation2);
553 
554         // act
555         final AffineTransformMatrix1D transform = a.premultiply(b).premultiply(c).premultiply(d);
556 
557         // assert
558         runWithCoordinates(x -> {
559             final Vector1D vec = Vector1D.of(x);
560 
561             final Vector1D expectedVec = vec
562                     .add(translation1)
563                     .multiply(scale)
564                     .add(translation2);
565 
566             EuclideanTestUtils.assertCoordinatesEqual(expectedVec, transform.apply(vec), EPS);
567         });
568     }
569 
570     @Test
571     void testLinear() {
572         // arrange
573         final AffineTransformMatrix1D mat = AffineTransformMatrix1D.of(2, 3);
574 
575         // act
576         final AffineTransformMatrix1D result = mat.linear();
577 
578         // assert
579         Assertions.assertArrayEquals(new double[] {2, 0}, result.toArray(), 0.0);
580     }
581 
582     @Test
583     void testLinearTranspose() {
584         // arrange
585         final AffineTransformMatrix1D mat = AffineTransformMatrix1D.of(2, 3);
586 
587         // act
588         final AffineTransformMatrix1D result = mat.linearTranspose();
589 
590         // assert
591         Assertions.assertArrayEquals(new double[] {2, 0}, result.toArray(), 0.0);
592     }
593 
594     @Test
595     void testNormalTransform() {
596         // act/assert
597         checkNormalTransform(AffineTransformMatrix1D.identity());
598 
599         checkNormalTransform(AffineTransformMatrix1D.createTranslation(4));
600         checkNormalTransform(AffineTransformMatrix1D.createTranslation(-4));
601 
602         checkNormalTransform(AffineTransformMatrix1D.createScale(2));
603         checkNormalTransform(AffineTransformMatrix1D.createScale(-2));
604 
605         checkNormalTransform(AffineTransformMatrix1D.createScale(2).translate(3));
606         checkNormalTransform(AffineTransformMatrix1D.createScale(2).translate(-3));
607         checkNormalTransform(AffineTransformMatrix1D.createTranslation(2).scale(-3));
608         checkNormalTransform(AffineTransformMatrix1D.createTranslation(-4).scale(-1));
609     }
610 
611     private void checkNormalTransform(final AffineTransformMatrix1D transform) {
612         final AffineTransformMatrix1D normalTransform = transform.normalTransform();
613 
614         final Vector1D expectedPlus = transform.apply(Vector1D.Unit.PLUS)
615                 .subtract(transform.apply(Vector1D.ZERO))
616                 .normalize();
617 
618         final Vector1D expectedMinus = transform.apply(Vector1D.Unit.MINUS)
619                 .subtract(transform.apply(Vector1D.ZERO))
620                 .normalize();
621 
622         EuclideanTestUtils.assertCoordinatesEqual(expectedPlus,
623                 normalTransform.apply(Vector1D.Unit.PLUS).normalize(), EPS);
624         EuclideanTestUtils.assertCoordinatesEqual(expectedMinus,
625                 normalTransform.apply(Vector1D.Unit.MINUS).normalize(), EPS);
626     }
627 
628     @Test
629     void testNormalTransform_nonInvertible() {
630         // act/assert
631         Assertions.assertThrows(IllegalStateException.class, () -> AffineTransformMatrix1D.createScale(0).normalTransform());
632     }
633 
634     @Test
635     void testInverse_identity() {
636         // act
637         final AffineTransformMatrix1D inverse = AffineTransformMatrix1D.identity().inverse();
638 
639         // assert
640         final double[] expected = {1, 0};
641         Assertions.assertArrayEquals(expected, inverse.toArray(), 0.0);
642     }
643 
644     @Test
645     void testInverse_multiplyByInverse_producesIdentity() {
646         // arrange
647         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(1, 3);
648 
649         final AffineTransformMatrix1D inv = a.inverse();
650 
651         // act
652         final AffineTransformMatrix1D result = inv.multiply(a);
653 
654         // assert
655         final double[] expected = {1, 0};
656         Assertions.assertArrayEquals(expected, result.toArray(), EPS);
657     }
658 
659     @Test
660     void testInverse_translate() {
661         // arrange
662         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.createTranslation(3);
663 
664         // act
665         final AffineTransformMatrix1D inverse = transform.inverse();
666 
667         // assert
668         final double[] expected = {1, -3};
669         Assertions.assertArrayEquals(expected, inverse.toArray(), 0.0);
670     }
671 
672     @Test
673     void testInverse_scale() {
674         // arrange
675         final AffineTransformMatrix1D transform = AffineTransformMatrix1D.createScale(10);
676 
677         // act
678         final AffineTransformMatrix1D inverse = transform.inverse();
679 
680         // assert
681         final double[] expected = {0.1, 0};
682         Assertions.assertArrayEquals(expected, inverse.toArray(), 0.0);
683     }
684 
685     @Test
686     void testInverse_undoesOriginalTransform_translationAndScale() {
687         // arrange
688         final Vector1D v1 = Vector1D.ZERO;
689         final Vector1D v2 = Vector1D.Unit.PLUS;
690         final Vector1D v3 = Vector1D.of(1.5);
691         final Vector1D v4 = Vector1D.of(-2);
692 
693         // act/assert
694         runWithCoordinates(x -> {
695             final AffineTransformMatrix1D transform = AffineTransformMatrix1D
696                         .createTranslation(x)
697                         .scale(2)
698                         .translate(x / 3);
699 
700             final AffineTransformMatrix1D inverse = transform.inverse();
701 
702             EuclideanTestUtils.assertCoordinatesEqual(v1, inverse.apply(transform.apply(v1)), EPS);
703             EuclideanTestUtils.assertCoordinatesEqual(v2, inverse.apply(transform.apply(v2)), EPS);
704             EuclideanTestUtils.assertCoordinatesEqual(v3, inverse.apply(transform.apply(v3)), EPS);
705             EuclideanTestUtils.assertCoordinatesEqual(v4, inverse.apply(transform.apply(v4)), EPS);
706         });
707     }
708 
709     @Test
710     void testInverse_nonInvertible() {
711         // act/assert
712         GeometryTestUtils.assertThrowsWithMessage(() -> {
713             AffineTransformMatrix1D.of(0, 0).inverse();
714         }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is 0.0");
715 
716         GeometryTestUtils.assertThrowsWithMessage(() -> {
717             AffineTransformMatrix1D.of(Double.NaN, 0).inverse();
718         }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is NaN");
719 
720         GeometryTestUtils.assertThrowsWithMessage(() -> {
721             AffineTransformMatrix1D.of(Double.NEGATIVE_INFINITY, 0.0).inverse();
722         }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is -Infinity");
723 
724         GeometryTestUtils.assertThrowsWithMessage(() -> {
725             AffineTransformMatrix1D.of(Double.POSITIVE_INFINITY, 0).inverse();
726         }, IllegalStateException.class, "Matrix is not invertible; matrix determinant is Infinity");
727 
728         GeometryTestUtils.assertThrowsWithMessage(() -> {
729             AffineTransformMatrix1D.of(1, Double.NaN).inverse();
730         }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: NaN");
731 
732         GeometryTestUtils.assertThrowsWithMessage(() -> {
733             AffineTransformMatrix1D.of(1, Double.NEGATIVE_INFINITY).inverse();
734         }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: -Infinity");
735 
736         GeometryTestUtils.assertThrowsWithMessage(() -> {
737             AffineTransformMatrix1D.of(1, Double.POSITIVE_INFINITY).inverse();
738         }, IllegalStateException.class, "Matrix is not invertible; invalid matrix element: Infinity");
739     }
740 
741     @Test
742     void testHashCode() {
743         // act
744         final int orig = AffineTransformMatrix1D.of(1, 2).hashCode();
745         final int same = AffineTransformMatrix1D.of(1, 2).hashCode();
746 
747         // assert
748         Assertions.assertEquals(orig, same);
749 
750         Assertions.assertNotEquals(orig, AffineTransformMatrix1D.of(0, 2).hashCode());
751         Assertions.assertNotEquals(orig, AffineTransformMatrix1D.of(1, 0).hashCode());
752     }
753 
754     @Test
755     void testEquals() {
756         // arrange
757         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(1, 2);
758 
759         // act/assert
760         GeometryTestUtils.assertSimpleEqualsCases(a);
761 
762         Assertions.assertNotEquals(a, AffineTransformMatrix1D.of(0, 2));
763         Assertions.assertNotEquals(a, AffineTransformMatrix1D.of(1, 0));
764     }
765 
766     @Test
767     void testEqualsAndHashCode_signedZeroConsistency() {
768         // arrange
769         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(0.0, -0.0);
770         final AffineTransformMatrix1D b = AffineTransformMatrix1D.of(-0.0, 0.0);
771         final AffineTransformMatrix1D c = AffineTransformMatrix1D.of(0.0, -0.0);
772         final AffineTransformMatrix1D d = AffineTransformMatrix1D.of(-0.0, 0.0);
773 
774         // act/assert
775         Assertions.assertFalse(a.equals(b));
776 
777         Assertions.assertTrue(a.equals(c));
778         Assertions.assertEquals(a.hashCode(), c.hashCode());
779 
780         Assertions.assertTrue(b.equals(d));
781         Assertions.assertEquals(b.hashCode(), d.hashCode());
782     }
783 
784     @Test
785     void testToString() {
786         // arrange
787         final AffineTransformMatrix1D a = AffineTransformMatrix1D.of(1, 2);
788 
789         // act
790         final String result = a.toString();
791 
792         // assert
793         Assertions.assertEquals("[ 1.0, 2.0 ]", result);
794     }
795 
796     @FunctionalInterface
797     private interface Coordinate1DTest {
798 
799         void run(double x);
800     }
801 
802     private static void runWithCoordinates(final Coordinate1DTest test) {
803         runWithCoordinates(test, false);
804     }
805 
806     private static void runWithCoordinates(final Coordinate1DTest test, final boolean skipZero) {
807         runWithCoordinates(test, -1e-2, 1e-2, 5e-3, skipZero);
808         runWithCoordinates(test, -1e2, 1e2, 5, skipZero);
809     }
810 
811     private static void runWithCoordinates(final Coordinate1DTest test, final double min, final double max, final double step, final boolean skipZero) {
812         for (double x = min; x <= max; x += step) {
813             if (!skipZero || x != 0.0) {
814                 test.run(x);
815             }
816         }
817     }
818 }