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 this
4    * work for additional information regarding copyright ownership. The ASF
5    * licenses this file to You under the Apache License, Version 2.0 (the
6    * "License"); you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * https://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, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package org.hipparchus.linear;
18  
19  import org.hipparchus.analysis.UnivariateFunction;
20  import org.hipparchus.analysis.function.Sin;
21  import org.hipparchus.exception.MathRuntimeException;
22  import org.hipparchus.linear.RealVector.Entry;
23  import org.hipparchus.util.FastMath;
24  import org.junit.jupiter.api.Test;
25  
26  import java.lang.reflect.InvocationTargetException;
27  import java.lang.reflect.Method;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.Random;
31  import java.util.Set;
32  
33  import static org.junit.jupiter.api.Assertions.assertFalse;
34  import static org.junit.jupiter.api.Assertions.assertThrows;
35  import static org.junit.jupiter.api.Assertions.assertTrue;
36  
37  /**
38   * This is an abstract test of the {@link
39   * RealVector#unmodifiableRealVector(RealVector) unmodifiable vector}
40   * implementation. These unmodifiable vectors decorate a (modifiable)
41   * {@link RealVector}; therefore, a new implementation of this abstract
42   * test should be considered for each implementation of
43   * {@link RealVector}.
44   *
45   *
46   */
47  public abstract class UnmodifiableRealVectorAbstractTest {
48      /** The dimension of the randomly generated vectors. */
49      protected static final int DIM = 100;
50      /** Absolute tolerance. */
51      protected static final double EPS = 10 * Math.ulp(1d);
52      /**
53       * The list of methods which are excluded from the general test
54       * {@link #testAllButExcluded()}.
55       */
56      protected static final Set<String> EXCLUDE = new HashSet<String>();
57      /** The random number generator (always initialized with the same seed. */
58      protected static final Random RANDOM;
59  
60      static {
61          EXCLUDE.add("getEntry");
62          EXCLUDE.add("setEntry");
63          EXCLUDE.add("addToEntry");
64          EXCLUDE.add("getSubVector");
65          EXCLUDE.add("setSubVector");
66          EXCLUDE.add("iterator");
67          EXCLUDE.add("sparseIterator");
68          EXCLUDE.add("walkInDefaultOrder");
69          EXCLUDE.add("walkInOptimizedOrder");
70          EXCLUDE.add("ebeDivide");
71          EXCLUDE.add("ebeMultiply");
72  
73          // Excluded because they are inherited from "Object".
74          for (Method m : Object.class.getMethods()) {
75              EXCLUDE.add(m.getName());
76          }
77          RANDOM = new Random(20110813);
78      }
79  
80      /**
81       * Returns {@code true} if the specified {@code double} are equal (within a
82       * given tolerance).
83       *
84       * @param x First {@code double}.
85       * @param y Second {@code double}.
86       * @return {@code true} if {@code x} and {@code y} are equal.
87       */
88      public static boolean equals(final double x, final double y) {
89          if (x == y) {
90              return true;
91          } else if (FastMath.abs(x) <= EPS) {
92              return FastMath.abs(y) <= EPS;
93          } else if (FastMath.abs(y) <= EPS) {
94              return FastMath.abs(x) <= EPS;
95          } else {
96              return FastMath.abs(x - y) <= EPS * FastMath.min(FastMath.abs(x), FastMath.abs(y));
97          }
98      }
99  
100     /**
101      * Returns {@code true} if the specified {@code double} arrays are equal
102      * (within a given tolerance).
103      *
104      * @param x First array.
105      * @param y Second array.
106      * @return {@code true} if {@code x} and {@code y} are equal.
107      */
108     public static boolean equals(final double[] x, final double[] y) {
109         if (x.length != y.length) {
110             return false;
111         }
112         final int n = x.length;
113         for (int i = 0; i < n; i++) {
114             if (!equals(x[i], y[i])) {
115                 return false;
116             }
117         }
118         return true;
119     }
120 
121     /**
122      * Returns {@code true} if the specified {@code RealVector} are equal
123      * (within a given tolerance).
124      *
125      * @param x First vector.
126      * @param y Second vector.
127      * @return {@code true} if {@code x} and {@code y} are equal.
128      */
129     public static boolean equals(final RealVector x, final RealVector y) {
130         if (x.getDimension() != y.getDimension()) {
131             return false;
132         }
133         final int n = x.getDimension();
134         for (int i = 0; i < n; i++) {
135             if (!equals(x.getEntry(i), y.getEntry(i))) {
136                 return false;
137             }
138         }
139         return true;
140     }
141 
142     /**
143      * Returns {@code true} if the specified {@code RealVector} is equal to the
144      * specified {@code double} array (within a given tolerance).
145      *
146      * @param x Vector.
147      * @param y Array.
148      * @return {@code true} if {@code x} and {@code y} are equal.
149      */
150     public static boolean equals(final RealVector x, final double[] y) {
151         if (x.getDimension() != y.length) {
152             return false;
153         }
154         final int n = x.getDimension();
155         for (int i = 0; i < n; i++) {
156             if (!equals(x.getEntry(i), y[i])) {
157                 return false;
158             }
159         }
160         return true;
161     }
162 
163     /**
164      * Returns {@code true} if the specified {@code RealMatrix} are equal
165      * (within a given tolerance).
166      *
167      * @param x First matrix.
168      * @param y Second matrix.
169      * @return {@code true} if {@code x} and {@code y} are equal.
170      */
171     public static boolean equals(final RealMatrix x, final RealMatrix y) {
172         if (x.getRowDimension() != y.getRowDimension()) {
173             return false;
174         }
175         if (x.getColumnDimension() != y.getColumnDimension()) {
176             return false;
177         }
178         final int rows = x.getRowDimension();
179         final int cols = x.getColumnDimension();
180         for (int i = 0; i < rows; i++) {
181             for (int j = 0; j < cols; j++) {
182                 if (!equals(x.getEntry(i, j), y.getEntry(i, j))) {
183                     return false;
184                 }
185             }
186         }
187         return true;
188     }
189 
190     /**
191      * Returns {@code true} if the specified {@code Object} are equal.
192      *
193      * @param x First object.
194      * @param y Second object.
195      * @return {@code true} if {@code x} and {@code y} are equal.
196      * @throws IllegalArgumentException if {@code x} and {@code y} could
197      * not be compared.
198      */
199     public static boolean equals(final Object x, final Object y) {
200         if (x instanceof Boolean) {
201             if (y instanceof Boolean) {
202                 return ((Boolean) x).booleanValue() == ((Boolean) y)
203                         .booleanValue();
204             } else {
205                 return false;
206             }
207         }
208         if (x instanceof Integer) {
209             if (y instanceof Integer) {
210                 return ((Integer) x).intValue() == ((Integer) y).intValue();
211             } else {
212                 return false;
213             }
214         } else if (x instanceof Double) {
215             if (y instanceof Double) {
216                 return equals(((Double) x).doubleValue(),
217                         ((Double) y).doubleValue());
218             } else {
219                 return false;
220             }
221         } else if (x instanceof double[]) {
222             if (y instanceof double[]) {
223                 return equals((double[]) x, (double[]) y);
224             } else if (y instanceof RealVector) {
225                 return equals((RealVector) y, (double[]) x);
226             } else {
227                 return false;
228             }
229         } else if (x instanceof RealVector) {
230             if (y instanceof double[]) {
231                 return equals((RealVector) x, (double[]) y);
232             } else if (y instanceof RealVector) {
233                 return equals((RealVector) x, (RealVector) y);
234             } else {
235                 return false;
236             }
237         } else if (x instanceof RealMatrix) {
238             if (y instanceof RealMatrix) {
239                 return equals((RealMatrix) x, (RealMatrix) y);
240             } else {
241                 return false;
242             }
243         } else {
244             throw new IllegalArgumentException("could not compare " + x + ", "
245                     + y);
246         }
247     }
248 
249     /**
250      * Creates a new random vector of a specified type. This vector is then to
251      * be wrapped in an unmodifiable vector.
252      *
253      * @return a new random vector.
254      */
255     public abstract RealVector createVector();
256 
257     /**
258      * Creates a new random object of the specified type.
259      *
260      * @param c Class of the object to be created.
261      * @return a new random object.
262      * @throws IllegalArgumentException if the specified class is not
263      * recognized by this method.
264      */
265     public Object createParameter(final Class<?> c) {
266         if (c == Integer.TYPE) {
267             return Integer.valueOf(RANDOM.nextInt());
268         } else if (c == Double.TYPE) {
269             return Double.valueOf(RANDOM.nextDouble());
270         } else if (c == double[].class) {
271             final double[] v = new double[DIM];
272             for (int i = 0; i < DIM; i++) {
273                 v[i] = RANDOM.nextDouble();
274             }
275             return v;
276         } else if (c.isAssignableFrom(RealVector.class)) {
277             return createVector();
278         } else if (c.isAssignableFrom(UnivariateFunction.class)) {
279             return new Sin();
280         } else {
281             throw new IllegalArgumentException("could not create " + c);
282         }
283     }
284 
285     /**
286      * This is the general test of most methods in the
287      * {@link RealVector#unmodifiableRealVector(RealVector) unmodifiable vector}.
288      * It works as follows.
289      * First, an unmodifiable view of a copy of the specified random vector
290      * {@code u} is created: this defines {@code v}. Then the <em>same</em>
291      * method {@code m} is invoked on {@code u} and {@code v}, with randomly
292      * generated parameters {@code args}.
293      * If it turns out that {@code u} has changed after the call of method
294      * {@code m}, then this test checks that the call of this method on
295      * {@code v} resulted in a {@link MathRuntimeException}. If
296      * {@code u} was not modified, then this test checks that the results
297      * returned by the call of method {@code m} on {@code u} and {@code v}
298      * returned the same result.
299      *
300      * @param m Method to be tested.
301      * @param u Random vector from which the unmodifiable view is to be
302      *constructed.
303      * @param args Arguments to be passed to method {@code m}.
304      */
305     private void callMethod(final Method m,
306                             final RealVector u,
307                             final Object... args)
308         throws IllegalAccessException,
309                IllegalArgumentException,
310                InvocationTargetException {
311         final RealVector uu = u.copy();
312         final RealVector v = RealVector.unmodifiableRealVector(u.copy());
313         Object exp = m.invoke(u, args);
314         if (equals(uu, u)) {
315             Object act = m.invoke(v, args);
316             assertTrue(equals(uu, v),
317                               m.toGenericString() + ", unmodifiable vector has changed");
318             assertTrue(equals(exp, act),
319                               m.toGenericString() + ", wrong result");
320 
321         } else {
322             boolean flag = false;
323             try {
324                 m.invoke(v, args);
325             } catch (InvocationTargetException e) {
326                 if (e.getCause() instanceof MathRuntimeException) {
327                     flag = true;
328                 }
329             }
330             assertTrue(flag, m.toGenericString()+", exception should have been thrown");
331         }
332     }
333 
334     /**
335      * This test calls {@link #callMethod(Method, RealVector, Object...)} on
336      * every method defined in interface {@link RealVector}. It generates the
337      * appropriate random arguments. Some methods are manually excluded (see
338      * {@link #EXCLUDE}), they must be handled by separate tests.
339      */
340     @Test
341     public void testAllButExcluded()
342         throws IllegalAccessException,
343                IllegalArgumentException,
344                InvocationTargetException {
345         Method[] method = RealVector.class.getMethods();
346         for (int i = 0; i < method.length; i++) {
347             Method m = method[i];
348             if (!EXCLUDE.contains(m.getName())) {
349                 RealVector u = (RealVector) createParameter(RealVector.class);
350                 Class<?>[] paramType = m.getParameterTypes();
351                 Object[] param = new Object[paramType.length];
352                 for (int j = 0; j < paramType.length; j++) {
353                     param[j] = createParameter(paramType[j]);
354                 }
355                 callMethod(m, u, param);
356             }
357         }
358     }
359 
360     @Test
361     public void testGetEntry() {
362         RealVector u = createVector();
363         RealVector v = RealVector.unmodifiableRealVector(u);
364         for (int i = 0; i < DIM; i++) {
365             assertTrue(equals(u.getEntry(i), v.getEntry(i)));
366         }
367     }
368 
369     @Test
370     public void testSetEntry() {
371         assertThrows(MathRuntimeException.class, () -> {
372             RealVector u = createVector();
373             RealVector v = RealVector.unmodifiableRealVector(u);
374             for (int i = 0; i < DIM; i++) {
375                 v.setEntry(i, 0d);
376             }
377         });
378     }
379 
380     @Test
381     public void testAddToEntry() {
382         assertThrows(MathRuntimeException.class, () -> {
383             RealVector u = createVector();
384             RealVector v = RealVector.unmodifiableRealVector(u);
385             for (int i = 0; i < DIM; i++) {
386                 v.addToEntry(i, 0d);
387             }
388         });
389     }
390 
391     @Test
392     public void testGetSubVector() {
393         RealVector u = createVector();
394         RealVector v = RealVector.unmodifiableRealVector(u);
395         for (int i = 0; i < DIM; i++) {
396             for (int n = 1; n < DIM - i; n++) {
397                 RealVector exp = u.getSubVector(i, n);
398                 RealVector act = v.getSubVector(i, n);
399                 assertTrue(equals(exp, act));
400             }
401         }
402     }
403 
404     @Test
405     public void testSetSubVector() {
406         assertThrows(MathRuntimeException.class, () -> {
407             RealVector u = createVector();
408             RealVector v = RealVector.unmodifiableRealVector(u);
409             v.setSubVector(0, new ArrayRealVector());
410         });
411     }
412 
413     @Test
414     public void testIterator() {
415         RealVector u = createVector();
416         Iterator<Entry> i = u.iterator();
417         RealVector v = RealVector.unmodifiableRealVector(u.copy());
418         Iterator<Entry> j = v.iterator();
419         boolean flag;
420         while (i.hasNext()) {
421             assertTrue(j.hasNext());
422             Entry exp = i.next();
423             Entry act = j.next();
424             assertTrue(equals(exp.getIndex(), act.getIndex()));
425             assertTrue(equals(exp.getValue(), act.getValue()));
426             exp.setIndex(RANDOM.nextInt(DIM));
427             act.setIndex(RANDOM.nextInt(DIM));
428             flag = false;
429             try {
430                 act.setValue(RANDOM.nextDouble());
431             } catch (MathRuntimeException e) {
432                 flag = true;
433             }
434             assertTrue(flag, "exception should have been thrown");
435         }
436         assertFalse(j.hasNext());
437     }
438 
439     @Test
440     public void testSparseIterator() {
441         RealVector u = createVector();
442         Iterator<Entry> i = u.sparseIterator();
443         RealVector v = RealVector.unmodifiableRealVector(u.copy());
444         Iterator<Entry> j = v.sparseIterator();
445         boolean flag;
446         while (i.hasNext()) {
447             assertTrue(j.hasNext());
448             Entry exp = i.next();
449             Entry act = j.next();
450             assertTrue(equals(exp.getIndex(), act.getIndex()));
451             assertTrue(equals(exp.getValue(), act.getValue()));
452             exp.setIndex(RANDOM.nextInt(DIM));
453             act.setIndex(RANDOM.nextInt(DIM));
454             flag = false;
455             try {
456                 act.setValue(RANDOM.nextDouble());
457             } catch (MathRuntimeException e) {
458                 flag = true;
459             }
460             assertTrue(flag, "exception should have been thrown");
461         }
462         assertFalse(j.hasNext());
463     }
464 }