View Javadoc
1   package org.opentrafficsim.core.distributions;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertFalse;
5   import static org.junit.Assert.assertNotEquals;
6   import static org.junit.Assert.assertTrue;
7   import static org.junit.Assert.fail;
8   
9   import java.lang.reflect.Field;
10  import java.util.ArrayList;
11  import java.util.List;
12  
13  import org.junit.Test;
14  import org.opentrafficsim.core.distributions.Distribution.FrequencyAndObject;
15  
16  import nl.tudelft.simulation.jstats.streams.MersenneTwister;
17  import nl.tudelft.simulation.jstats.streams.StreamInterface;
18  
19  /**
20   * Test the Distribution class.
21   * <p>
22   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
23   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
24   * <p>
25   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Mar 4, 2016 <br>
26   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
27   */
28  public class DistributionTest
29  {
30      /**
31       * Test the Distribution class.
32       * @throws ProbabilityException the test fails if this happens uncaught
33       */
34      @Test
35      public final void distributionTest() throws ProbabilityException
36      {
37          StreamInterface si = new MersenneTwister(1234);
38          try
39          {
40              new Distribution<TestObject>(null, si);
41              fail("Null pointer for generators should have thrown a ProbabilityException");
42          }
43          catch (ProbabilityException npe)
44          {
45              // Ignore expected exception
46          }
47  
48          List<Distribution.FrequencyAndObject<TestObject>> generators =
49                  new ArrayList<Distribution.FrequencyAndObject<TestObject>>();
50          try
51          {
52              new Distribution<TestObject>(generators, null);
53              fail("Null pointer for stream interface should have thrown a ProbabilityException");
54          }
55          catch (ProbabilityException npe)
56          {
57              // Ignore expected exception
58          }
59  
60          Distribution<TestObject> dist = new Distribution<TestObject>(generators, si);
61          assertEquals("size should be 0", 0, dist.size());
62          try
63          {
64              dist.draw();
65              fail("draw with empty set should have thrown a ProbabilityException");
66          }
67          catch (ProbabilityException pe)
68          {
69              // Ignore expected exception
70          }
71  
72          TestObject to = new TestObject();
73          Distribution.FrequencyAndObject<TestObject> generator =
74                  new Distribution.FrequencyAndObject<DistributionTest.TestObject>(123, to);
75          try
76          {
77              dist.add(1, generator);
78              fail("should have thrown an IndexOutOfBoundsException");
79          }
80          catch (IndexOutOfBoundsException e)
81          {
82              // Ignore expected exception
83          }
84  
85          try
86          {
87              dist.add(-1, generator);
88              fail("should have thrown an IndexOutOfBoundsException");
89          }
90          catch (IndexOutOfBoundsException e)
91          {
92              // Ignore expected exception
93          }
94  
95          dist.add(0, generator);
96          assertEquals("size should now be 1", 1, dist.size());
97          dist.remove(0);
98          assertEquals("size should be 0", 0, dist.size());
99          dist.add(generator);
100         assertEquals("size should now be 1", 1, dist.size());
101         try
102         {
103             dist.remove(1);
104             fail("Bad index for remove should have thrown an IndexOutOfBoundsException");
105         }
106         catch (IndexOutOfBoundsException e)
107         {
108             // Ignore expected exception
109         }
110 
111         try
112         {
113             dist.remove(-1);
114             fail("Bad index for remove should have thrown an IndexOutOfBoundsException");
115         }
116         catch (IndexOutOfBoundsException e)
117         {
118             // Ignore expected exception
119         }
120 
121         for (int i = 0; i < 1000; i++)
122         {
123             TestObject to2 = dist.draw();
124             assertEquals("Result of draw() should be equal to to", to, to2);
125         }
126         try
127         {
128             dist.modifyFrequency(1, 1);
129             fail("Bad index for modify should have thrown an IndexOutOfBoundsException");
130         }
131         catch (ProbabilityException e)
132         {
133             // Ignore expected exception
134         }
135 
136         try
137         {
138             dist.modifyFrequency(-1, 1);
139             fail("Bad index for modify should have thrown an IndexOutOfBoundsException");
140         }
141         catch (ProbabilityException e)
142         {
143             // Ignore expected exception
144         }
145 
146         try
147         {
148             dist.modifyFrequency(0, -1);
149             fail("Bad frequency for modify should have thrown a ProbabilityException");
150         }
151         catch (ProbabilityException pe)
152         {
153             // Ignore expected exception
154         }
155 
156         dist.modifyFrequency(0, 0);
157         try
158         {
159             dist.draw();
160             fail("Sum of frequencies == 0 should have thrown a ProbabilityException");
161         }
162         catch (ProbabilityException pe)
163         {
164             // Ignore expected exception
165         }
166 
167         TestObject to2 = new TestObject();
168         dist.add(new Distribution.FrequencyAndObject<TestObject>(10, to2));
169         assertEquals("element 0 should be to", to, dist.get(0).getObject());
170         assertEquals("element 1 should be to2", to2, dist.get(1).getObject());
171         assertEquals("frequency of element 0 should be 0", 0, dist.get(0).getFrequency(), 0.00001);
172         assertEquals("frequency of element 1 should be 10", 10, dist.get(1).getFrequency(), 0.00001);
173         for (int i = 0; i < 1000; i++)
174         {
175             TestObject to3 = dist.draw();
176             assertEquals("Result of draw() should be equal to to2", to2, to3);
177         }
178         try
179         {
180             dist.get(-1);
181             fail("Negative index should have thrown in a ProbabilityException");
182         }
183         catch (ProbabilityException pe)
184         {
185             // Ignore expected exception
186         }
187 
188         try
189         {
190             dist.get(2);
191             fail("Too high index should have thrown in a ProbabilityException");
192         }
193         catch (ProbabilityException pe)
194         {
195             // Ignore expected exception
196         }
197 
198         dist.modifyFrequency(0, 5);
199         int[] observed = new int[2];
200         for (int i = 0; i < 10000; i++)
201         {
202             TestObject to3 = dist.draw();
203             if (to3.equals(to))
204             {
205                 observed[0]++;
206             }
207             else if (to3.equals(to2))
208             {
209                 observed[1]++;
210             }
211             else
212             {
213                 fail("draw returned something we didn't add");
214             }
215         }
216         // With the seeded MersenneTwister observed contains the values 3385 and 6615
217         // System.out.println("Observed frequencies: [" + observed[0] + ", " + observed[1] + "]");
218         assertEquals("Total number of draws should add up to 10000", 10000, observed[0] + observed[1]);
219         assertTrue("observed frequency of to should be about 3333", 2500 < observed[0] && observed[0] < 4000);
220         dist.clear();
221         assertEquals("after clear the set should be empty", 0, dist.size());
222         try
223         {
224             dist.draw();
225             fail("Empty set should throw a ProbabilityException");
226         }
227         catch (ProbabilityException pe)
228         {
229             // Ignore expected exception
230         }
231         // Construct a Distribution from a List of generators
232         generators = new ArrayList<Distribution.FrequencyAndObject<TestObject>>();
233         generators.add(new FrequencyAndObject<DistributionTest.TestObject>(123, to));
234         generators.add(new FrequencyAndObject<DistributionTest.TestObject>(456, to2));
235         dist = new Distribution<TestObject>(generators, si);
236         assertEquals("element 0 should be to", to, dist.get(0).getObject());
237         assertEquals("element 1 should be to2", to2, dist.get(1).getObject());
238         assertEquals("frequency of element 0 should be 123", 123, dist.get(0).getFrequency(), 0.00001);
239         assertEquals("frequency of element 1 should be 456", 456, dist.get(1).getFrequency(), 0.00001);
240         generators.set(1, new FrequencyAndObject<DistributionTest.TestObject>(-1, to2));
241         try
242         {
243             new Distribution<TestObject>(generators, si);
244             fail("Negative frequency should have thrown a ProbabilityException");
245         }
246         catch (ProbabilityException pe)
247         {
248             // Ignore expected exception
249         }
250 
251         try
252         {
253             dist.add(generators.get(1));
254             fail("Negative frequency should have thrown a ProbabilityException");
255         }
256         catch (ProbabilityException pe)
257         {
258             // Ignore expected exception
259         }
260 
261         try
262         {
263             dist.set(-1, new FrequencyAndObject<DistributionTest.TestObject>(456, to2));
264         }
265         catch (ProbabilityException pe)
266         {
267             // Ignore expected exception
268         }
269 
270         try
271         {
272             dist.set(dist.size(), new FrequencyAndObject<DistributionTest.TestObject>(456, to2));
273         }
274         catch (ProbabilityException pe)
275         {
276             // Ignore expected exception
277         }
278 
279         // Modify the internal value "cumulativeTotal" to increase the odds that Distribution resorts to returning the first
280         // non-zero frequency object
281         double badTotal = 1000;
282         try
283         {
284             Field field = dist.getClass().getDeclaredField("cumulativeTotal");
285             field.setAccessible(true);
286             field.setDouble(dist, badTotal);
287         }
288         catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException exception)
289         {
290             exception.printStackTrace();
291         }
292 
293         observed = new int[2];
294         for (int i = 0; i < 10000; i++)
295         {
296             TestObject to3 = dist.draw();
297             if (to3.equals(to))
298             {
299                 observed[0]++;
300             }
301             else if (to3.equals(to2))
302             {
303                 observed[1]++;
304             }
305             else
306             {
307                 fail("draw returned something we didn't add");
308             }
309         }
310         // System.out.println("dist is " + dist);
311         double badFrequency = badTotal - dist.get(0).getFrequency() - dist.get(1).getFrequency();
312         double expectedIn0 = (dist.get(0).getFrequency() + badFrequency) / badTotal * 10000;
313         // System.out.println("Observed frequencies: [" + observed[0] + ", " + observed[1] + "]; expected in 0 " + expectedIn0);
314         assertEquals("Total number of draws should add up to 10000", 10000, observed[0] + observed[1]);
315         assertTrue("observed frequency of to should be about " + expectedIn0,
316                 expectedIn0 - 500 < observed[0] && observed[0] < expectedIn0 + 500);
317         // When frequency of element 0 is 0; the draw method should never return it; even if the cumulativeTotal is wrong
318         dist.modifyFrequency(0, 0);
319         try
320         {
321             Field field = dist.getClass().getDeclaredField("cumulativeTotal");
322             field.setAccessible(true);
323             field.setDouble(dist, badTotal);
324         }
325         catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException exception)
326         {
327             exception.printStackTrace();
328         }
329 
330         observed = new int[2];
331         for (int i = 0; i < 10000; i++)
332         {
333             TestObject to3 = dist.draw();
334             if (to3.equals(to))
335             {
336                 observed[0]++;
337             }
338             else if (to3.equals(to2))
339             {
340                 observed[1]++;
341             }
342             else
343             {
344                 fail("draw returned something we didn't add");
345             }
346         }
347         // System.out.println("dist is " + dist);
348         // System.out.println("Observed frequencies: [" + observed[0] + ", " + observed[1] + "]; expected in 0 " + expectedIn0);
349         assertEquals("Total number of draws should add up to 10000", 10000, observed[0] + observed[1]);
350         assertEquals("observed frequency of to should be 0", 0, observed[0]);
351         try
352         {
353             dist.modifyFrequency(dist.size(), 0);
354         }
355         catch (ProbabilityException pe)
356         {
357             // Ignore expected exception
358         }
359 
360         try
361         {
362             dist.modifyFrequency(-1, 0);
363         }
364         catch (ProbabilityException pe)
365         {
366             // Ignore expected exception
367         }
368 
369         // Execute the clear method and verify that size is 0
370         dist.clear();
371         assertEquals("after clear the set should be empty", 0, dist.size());
372     }
373 
374     /** Object used as generic parameter. */
375     class TestObject
376     {
377         //
378     }
379 
380     /**
381      * Test hashCode and equals.
382      * @throws ProbabilityException if that happens uncaught; this test has failed.
383      */
384     @SuppressWarnings("unlikely-arg-type")
385     @Test
386     public void testHashCodeAndEquals() throws ProbabilityException
387     {
388         StreamInterface si = new MersenneTwister(1234);
389         Distribution<Double> distribution = new Distribution<>(si);
390         distribution.add(new FrequencyAndObject<Double>(Math.PI, 10d));
391         distribution.add(new FrequencyAndObject<Double>(2 * Math.PI, 20d));
392         assertTrue("distribution is equal to itself", distribution.equals(distribution));
393         assertFalse("distribution is not equal to null", distribution.equals(null));
394         assertFalse("distribution is not equal to something totally different", distribution.equals("junk"));
395         Distribution<Double> distribution2 = new Distribution<>(si);
396         assertFalse("Distribution is not equal to other distribution containing different set of frequency and object",
397                 distribution.equals(distribution2));
398         distribution2.add(new FrequencyAndObject<Double>(Math.PI, 10d));
399         distribution2.add(new FrequencyAndObject<Double>(2 * Math.PI, 20d));
400         // TODO: Next test fails because the random field of the Distribution does not implement equals
401         // assertTrue(
402         // "Distributions is equal to other distribution containing exact same frequencies and objects and random source",
403         // distribution.equals(distribution2));
404         assertTrue("The toString method returns something descriptive", distribution.toString().startsWith("Distribution"));
405     }
406 
407     /**
408      * Test the FrequencyAndObject sub class.
409      */
410     @SuppressWarnings("unlikely-arg-type")
411     @Test
412     public void frequencyAndObjectTest()
413     {
414         FrequencyAndObject<String> fao1 = new FrequencyAndObject<>(Math.PI, "One");
415         assertEquals("frequency matches", Math.PI, fao1.getFrequency(), 0);
416         assertEquals("object matchs", "One", fao1.getObject());
417         assertTrue("FreqencyAndObject is equal to itself", fao1.equals(fao1));
418         assertFalse("FrequencyAndObject is not equal to null", fao1.equals(null));
419         assertFalse("FrequencyAndObject is not equal to some totally unrelated object", fao1.equals("Bla"));
420         FrequencyAndObject<String> fao2 = new FrequencyAndObject<>(Math.PI, "One");
421         assertTrue("FrequencyAndObject is equal to another FrequencyAndObject with same frequency and same object",
422                 fao1.equals(fao2));
423         assertEquals("FrequencyAndObject has same hashCode as another FrequencyAndObject with same frequency and same object",
424                 fao1.hashCode(), fao2.hashCode());
425         fao2 = new FrequencyAndObject<>(Math.PI, "Two");
426         assertFalse("FrequencyAndObject is not equal to another FrequencyAndObject with same frequency but other object",
427                 fao1.equals(fao2));
428         fao2 = new FrequencyAndObject<>(Math.E, "One");
429         assertFalse("FrequencyAndObject is not equal to another FrequencyAndObject with different frequency but same object",
430                 fao1.equals(fao2));
431         assertNotEquals("FrequencyAndObject has different hashCode than another FrequencyAndObject with different frequency "
432                 + "and same object", fao1.hashCode(), fao2.hashCode());
433         fao2 = new FrequencyAndObject<>(Math.PI, null);
434         assertFalse("FrequencyAndObject is not equal to another FrequencyAndObject with same frequency but null object",
435                 fao1.equals(fao2));
436         assertFalse("FrequencyAndObject is not equal to another FrequencyAndObject with same frequency but null object",
437                 fao2.equals(fao1));
438         assertTrue("The toString methods returns something descriptive", fao1.toString().startsWith("FrequencyAndObject"));
439     }
440 
441 }