View Javadoc
1   package trafficcontrol;
2   
3   import static org.junit.Assert.assertEquals;
4   import static org.junit.Assert.assertTrue;
5   import static org.junit.Assert.fail;
6   
7   import java.io.ByteArrayOutputStream;
8   import java.io.PrintStream;
9   import java.util.LinkedHashMap;
10  import java.util.LinkedHashSet;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import javax.naming.NamingException;
15  
16  import org.djunits.value.vdouble.scalar.Duration;
17  import org.djunits.value.vdouble.scalar.Time;
18  import org.djutils.immutablecollections.ImmutableSet;
19  import org.mockito.ArgumentMatchers;
20  import org.mockito.Mockito;
21  import org.mockito.invocation.InvocationOnMock;
22  import org.mockito.stubbing.Answer;
23  import org.opentrafficsim.core.dsol.OTSModelInterface;
24  import org.opentrafficsim.core.dsol.OTSSimulator;
25  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
26  import org.opentrafficsim.core.network.NetworkException;
27  import org.opentrafficsim.core.network.OTSNetwork;
28  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
29  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor;
30  import org.opentrafficsim.trafficcontrol.FixedTimeController;
31  import org.opentrafficsim.trafficcontrol.FixedTimeController.SignalGroup;
32  
33  import nl.tudelft.simulation.dsol.SimRuntimeException;
34  
35  /**
36   * Test the fixed time traffic controller class.
37   * <p>
38   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
39   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
40   * <p>
41   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Feb 21, 2019 <br>
42   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
43   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
44   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
45   */
46  public class TestFixedTimeController
47  {
48  
49      /**
50       * Test the constructors and initializers of the signal group and fixed time controller classes.
51       * @throws SimRuntimeException if that happens uncaught; this test has failed
52       * @throws NamingException if that happens uncaught; this test has failed
53       * @throws NetworkException on exception
54       */
55      // TODO: @Test
56      public void testConstructors() throws SimRuntimeException, NamingException, NetworkException
57      {
58          String signalGroupId = "sgId";
59          Set<String> trafficLightIds = new LinkedHashSet<>();
60          String trafficLightId = "08.1";
61          trafficLightIds.add(trafficLightId);
62          Duration signalGroupOffset = Duration.instantiateSI(5);
63          Duration preGreen = Duration.instantiateSI(2);
64          Duration green = Duration.instantiateSI(10);
65          Duration yellow = Duration.instantiateSI(3.5);
66          try
67          {
68              new SignalGroup(null, trafficLightIds, signalGroupOffset, preGreen, green, yellow);
69              fail("Null pointer for signalGroupId should have thrown a null pointer exception");
70          }
71          catch (NullPointerException npe)
72          {
73              // Ignore expected exception
74          }
75          try
76          {
77              new SignalGroup(signalGroupId, null, signalGroupOffset, preGreen, green, yellow);
78              fail("Null pointer for trafficLightIds should have thrown a null pointer exception");
79          }
80          catch (NullPointerException npe)
81          {
82              // Ignore expected exception
83          }
84          try
85          {
86              new SignalGroup(signalGroupId, trafficLightIds, null, preGreen, green, yellow);
87              fail("Null pointer for signalGroupOffset should have thrown a null pointer exception");
88          }
89          catch (NullPointerException npe)
90          {
91              // Ignore expected exception
92          }
93          try
94          {
95              new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, null, green, yellow);
96              fail("Null pointer for preGreen should have thrown a null pointer exception");
97          }
98          catch (NullPointerException npe)
99          {
100             // Ignore expected exception
101         }
102         try
103         {
104             new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, null, yellow);
105             fail("Null pointer for green should have thrown a null pointer exception");
106         }
107         catch (NullPointerException npe)
108         {
109             // Ignore expected exception
110         }
111         try
112         {
113             new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, green, null);
114             fail("Null pointer for yellow should have thrown a null pointer exception");
115         }
116         catch (NullPointerException npe)
117         {
118             // Ignore expected exception
119         }
120         try
121         {
122             new SignalGroup(signalGroupId, new LinkedHashSet<String>(), signalGroupOffset, preGreen, green, yellow);
123             fail("Empty list of traffic light ids should have thrown an illegal argument exception");
124         }
125         catch (IllegalArgumentException iae)
126         {
127             // Ignore expected exception
128         }
129         // Test the controller that adds default for pre green time
130         SignalGroup sg = new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, green, yellow);
131         assertEquals("default for pre green", 0, sg.getPreGreen().si, 0);
132         assertEquals("green", green.si, sg.getGreen().si, 0);
133         assertEquals("yellow", yellow.si, sg.getYellow().si, 0);
134         // Now that we've tested all ways that the constructor should have told us to go to hell, create a signal group
135         sg = new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, green, yellow);
136         assertEquals("group id", signalGroupId, sg.getId());
137         assertTrue("toString returns something descriptive", sg.toString().startsWith("SignalGroup ["));
138 
139         String ftcId = "FTCid";
140         OTSSimulatorInterface simulator = new OTSSimulator("TestFixedTimeController");
141         simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600), createModelMock());
142         Map<String, TrafficLight> trafficLightMap = new LinkedHashMap<String, TrafficLight>();
143         String networkId = "networkID";
144         trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator));
145         OTSNetwork network = new OTSNetwork(networkId, true, simulator);
146         network.addObject(trafficLightMap.get(trafficLightId));
147 
148         Duration cycleTime = Duration.instantiateSI(90);
149         Duration offset = Duration.instantiateSI(20);
150         Set<SignalGroup> signalGroups = new LinkedHashSet<>();
151         ImmutableSet<String> ids = sg.getTrafficLightIds();
152         for (String tlId : ids)
153         {
154             assertTrue("returned id is in provided set", trafficLightMap.containsKey(tlId));
155         }
156         for (String tlId : trafficLightMap.keySet())
157         {
158             assertTrue("provided id is returned", ids.contains(tlId));
159         }
160         signalGroups.add(sg);
161         try
162         {
163             new FixedTimeController(null, simulator, network, cycleTime, offset, signalGroups);
164             fail("Null pointer for controller id should have thrown an exception");
165         }
166         catch (NullPointerException npe)
167         {
168             // Ignore
169         }
170         try
171         {
172             new FixedTimeController(ftcId, null, network, cycleTime, offset, signalGroups);
173             fail("Null pointer for simulator should have thrown an exception");
174         }
175         catch (NullPointerException npe)
176         {
177             // Ignore
178         }
179         try
180         {
181             new FixedTimeController(ftcId, simulator, null, cycleTime, offset, signalGroups);
182             fail("Null pointer for network should have thrown an exception");
183         }
184         catch (NullPointerException npe)
185         {
186             // Ignore
187         }
188         try
189         {
190             new FixedTimeController(ftcId, simulator, network, null, offset, signalGroups);
191             fail("Null pointer for cycle time should have thrown an exception");
192         }
193         catch (NullPointerException npe)
194         {
195             // Ignore
196         }
197         try
198         {
199             new FixedTimeController(ftcId, simulator, network, cycleTime, null, signalGroups);
200             fail("Null pointer for offset should have thrown an exception");
201         }
202         catch (NullPointerException npe)
203         {
204             // Ignore
205         }
206         try
207         {
208             new FixedTimeController(ftcId, simulator, network, cycleTime, offset, null);
209             fail("Null pointer for signal groups should have thrown an exception");
210         }
211         catch (NullPointerException npe)
212         {
213             // Ignore
214         }
215         try
216         {
217             new FixedTimeController(ftcId, simulator, network, cycleTime, offset, new LinkedHashSet<SignalGroup>());
218             fail("Empty signal groups should have thrown an exception");
219         }
220         catch (IllegalArgumentException iae)
221         {
222             // Ignore
223         }
224         try
225         {
226             new FixedTimeController(ftcId, simulator, network, Duration.instantiateSI(0), offset, signalGroups);
227             fail("Illegal cycle time should hav thrown an exception");
228         }
229         catch (IllegalArgumentException iae)
230         {
231             // Ignore
232         }
233         try
234         {
235             new FixedTimeController(ftcId, simulator, network, Duration.instantiateSI(-10), offset, signalGroups);
236             fail("Illegal cycle time should hav thrown an exception");
237         }
238         catch (IllegalArgumentException iae)
239         {
240             // Ignore
241         }
242         // Not testing check for identical signal groups; yet
243         // Now that we've tested all ways that the constructor should have told us to go to hell, create a controller
244         FixedTimeController ftc = new FixedTimeController(ftcId, simulator, network, cycleTime, offset, signalGroups);
245         assertEquals("FTC id", ftcId, ftc.getId());
246         assertTrue("toString returns something descriptive", ftc.toString().startsWith("FixedTimeController ["));
247 
248         simulator.runUpTo(Time.instantiateSI(1));
249         while (simulator.isStartingOrRunning())
250         {
251             try
252             {
253                 Thread.sleep(100);
254             }
255             catch (InterruptedException exception)
256             {
257                 exception.printStackTrace();
258             }
259         }
260         for (TrafficLight tl : sg.getTrafficLights())
261         {
262             assertTrue("acquired traffic light is in the proved set", trafficLightMap.containsKey(tl.getId()));
263         }
264         assertEquals("red time makes up remainder of cycle time", cycleTime.minus(preGreen).minus(green).minus(yellow).si,
265                 sg.getRed().si, 0.0001);
266     }
267 
268     /**
269      * Test detection of non-disjoint sets of traffic lights.
270      * @throws NamingException on exception
271      * @throws SimRuntimeException on exception
272      * @throws NetworkException on exception
273      */
274     // TODO: @Test
275     public void testDisjoint() throws SimRuntimeException, NamingException, NetworkException
276     {
277         String signalGroupId = "sgId1";
278         Set<String> trafficLightIds1 = new LinkedHashSet<>();
279         String trafficLightId = "08.1";
280         trafficLightIds1.add(trafficLightId);
281         Duration signalGroupOffset = Duration.instantiateSI(5);
282         Duration preGreen = Duration.instantiateSI(2);
283         Duration green = Duration.instantiateSI(10);
284         Duration yellow = Duration.instantiateSI(3.5);
285         SignalGroup sg1 = new SignalGroup(signalGroupId, trafficLightIds1, signalGroupOffset, preGreen, green, yellow);
286         String signalGroupId2 = "sgId2";
287         Set<String> trafficLightIds2 = new LinkedHashSet<>();
288         trafficLightIds2.add(trafficLightId);
289         SignalGroup sg2 = new SignalGroup(signalGroupId2, trafficLightIds2, signalGroupOffset, preGreen, green, yellow);
290 
291         String ftcId = "FTCid";
292         OTSSimulatorInterface simulator = new OTSSimulator("TestFixedTimeController");
293         simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600), createModelMock());
294         Map<String, TrafficLight> trafficLightMap = new LinkedHashMap<String, TrafficLight>();
295         String networkId = "networkID";
296         trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator));
297         OTSNetwork network = new OTSNetwork(networkId, true, simulator);
298         network.addObject(trafficLightMap.get(trafficLightId));
299 
300         Duration cycleTime = Duration.instantiateSI(90);
301         Duration offset = Duration.instantiateSI(20);
302         Set<SignalGroup> signalGroups = new LinkedHashSet<>();
303         signalGroups.add(sg1);
304         signalGroups.add(sg2);
305         try
306         {
307             new FixedTimeController(ftcId, simulator, network, cycleTime, offset, signalGroups);
308             fail("Same traffic light in different signal groups should have thrown an IllegalArgumnentException");
309         }
310         catch (IllegalArgumentException iae)
311         {
312             // Ignore
313         }
314     }
315 
316     /**
317      * Test timing of fixed time controller.
318      * @throws SimRuntimeException if that happens uncaught; this test has failed
319      * @throws NamingException if that happens uncaught; this test has failed
320      * @throws NetworkException on exception
321      */
322     // TODO: @Test
323     public void testTimings() throws SimRuntimeException, NamingException, NetworkException
324     {
325         String signalGroupId = "sgId";
326         Set<String> trafficLightIds = new LinkedHashSet<>();
327         String trafficLightId = "08.1";
328         trafficLightIds.add(trafficLightId);
329         Set<SignalGroup> signalGroups = new LinkedHashSet<>();
330         for (int cycleTime : new int[] {60, 90})
331         {
332             Duration cycle = Duration.instantiateSI(cycleTime);
333             for (int ftcOffsetTime : new int[] {-100, -10, 0, 10, 100})
334             {
335                 Duration ftcOffset = Duration.instantiateSI(ftcOffsetTime);
336                 for (int sgOffsetTime : new int[] {-99, -9, 0, 9, 99})
337                 {
338                     Duration sgOffset = Duration.instantiateSI(sgOffsetTime);
339                     for (int preGreenTime : new int[] {0, 3})
340                     {
341                         Duration preGreen = Duration.instantiateSI(preGreenTime);
342                         for (int greenTime : new int[] {5, 15, 100})
343                         {
344                             Duration green = Duration.instantiateSI(greenTime);
345                             for (double yellowTime : new double[] {0, 3.5, 4.5})
346                             {
347                                 Duration yellow = Duration.instantiateSI(yellowTime);
348                                 double minimumCycleTime = preGreenTime + greenTime + yellowTime;
349                                 SignalGroup sg =
350                                         new SignalGroup(signalGroupId, trafficLightIds, sgOffset, preGreen, green, yellow);
351                                 signalGroups.clear();
352                                 signalGroups.add(sg);
353                                 String ftcId = "FTCid";
354                                 OTSSimulatorInterface simulator = new OTSSimulator("TestFixedTimeController");
355                                 simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600), createModelMock());
356                                 Map<String, TrafficLight> trafficLightMap = new LinkedHashMap<String, TrafficLight>();
357                                 String networkId = "networkID";
358                                 trafficLightMap.put(trafficLightId,
359                                         createTrafficLightMock(trafficLightId, networkId, simulator));
360                                 OTSNetwork network = new OTSNetwork(networkId, true, simulator);
361                                 network.addObject(trafficLightMap.get(trafficLightId));
362                                 // System.out.println(cycle);
363                                 FixedTimeController ftc =
364                                         new FixedTimeController(ftcId, simulator, network, cycle, ftcOffset, signalGroups);
365                                 // System.out.print(ftc);
366                                 if (cycleTime < minimumCycleTime)
367                                 {
368                                     PrintStream originalError = System.err;
369                                     boolean exceptionThrown = false;
370                                     try
371                                     {
372                                         while (simulator.getSimulatorTime().si <= 0)
373                                         {
374                                             System.setErr(new PrintStream(new ByteArrayOutputStream()));
375                                             try
376                                             {
377                                                 simulator.step();
378                                             }
379                                             finally
380                                             {
381                                                 System.setErr(originalError);
382                                             }
383                                         }
384                                     }
385                                     catch (SimRuntimeException exception)
386                                     {
387                                         exceptionThrown = true;
388                                         assertTrue("exception explains cycle time problem",
389                                                 exception.getCause().getCause().getMessage().contains("Cycle time shorter "));
390                                     }
391                                     assertTrue("Too short cycle time should have thrown a SimRuntimeException",
392                                             exceptionThrown);
393                                 }
394                                 else
395                                 {
396                                     // All transitions are at multiples of 0.5 seconds; check the state at 0.25 and 0.75 in each
397                                     // second
398                                     for (int second = 0; second <= 300; second++)
399                                     {
400                                         Object[] args = new Object[] {simulator, ftc, Boolean.TRUE};
401                                         simulator.scheduleEventAbs(Time.instantiateSI(second + 0.25), this, this, "checkState",
402                                                 args);
403                                         simulator.scheduleEventAbs(Time.instantiateSI(second + 0.75), this, this, "checkState",
404                                                 args);
405                                     }
406                                     Time stopTime = Time.instantiateSI(300);
407                                     simulator.runUpTo(stopTime);
408                                     while (simulator.isStartingOrRunning())
409                                     {
410                                         try
411                                         {
412                                             Thread.sleep(1);
413                                         }
414                                         catch (InterruptedException exception)
415                                         {
416                                             exception.printStackTrace();
417                                         }
418                                     }
419                                     if (simulator.getSimulatorTime().lt(stopTime))
420                                     {
421                                         // something went wrong; call checkState with stopSimulatorOnError set to false
422                                         checkState(simulator, ftc, Boolean.FALSE);
423                                         fail("checkState should have thrown an assert error");
424                                     }
425                                 }
426                             }
427                         }
428                     }
429                 }
430             }
431         }
432     }
433 
434     /**
435      * Check that the current state of a fixed time traffic light controller matches the design.
436      * @param simulator OTSSimulatorInterface; the simulator
437      * @param ftc FixedTimeController; the fixed time traffic light controller
438      * @param stopSimulatorOnError boolean; if true; stop the simulator on error; if false; execute the failing assert on error
439      */
440     public void checkState(final OTSSimulatorInterface simulator, final FixedTimeController ftc,
441             final boolean stopSimulatorOnError)
442     {
443         double cycleTime = ftc.getCycleTime().si;
444         double time = simulator.getSimulatorTime().si;
445         double mainOffset = ftc.getOffset().si;
446         for (SignalGroup sg : ftc.getSignalGroups())
447         {
448             double phaseOffset = sg.getOffset().si + mainOffset;
449             double phase = time + phaseOffset;
450             while (phase < 0)
451             {
452                 phase += cycleTime;
453             }
454             phase %= cycleTime;
455             TrafficLightColor expectedColor = null;
456             if (phase < sg.getPreGreen().si)
457             {
458                 expectedColor = TrafficLightColor.PREGREEN;
459             }
460             else if (phase < sg.getPreGreen().plus(sg.getGreen()).si)
461             {
462                 expectedColor = TrafficLightColor.GREEN;
463             }
464             else if (phase < sg.getPreGreen().plus(sg.getGreen()).plus(sg.getYellow()).si)
465             {
466                 expectedColor = TrafficLightColor.YELLOW;
467             }
468             else
469             {
470                 expectedColor = TrafficLightColor.RED;
471             }
472             // Verify the color of all traffic lights
473             for (TrafficLight tl : sg.getTrafficLights())
474             {
475                 if (!expectedColor.equals(tl.getTrafficLightColor()))
476                 {
477                     if (stopSimulatorOnError)
478                     {
479                         try
480                         {
481                             simulator.stop();
482                         }
483                         catch (SimRuntimeException exception)
484                         {
485                             exception.printStackTrace();
486                         }
487                     }
488                     else
489                     {
490                         assertEquals(
491                                 "Traffic light color mismatch at simulator time " + simulator.getSimulatorTime()
492                                         + " of signal group " + sg,
493                                 expectedColor + " which is in phase " + phase + " of cycle time " + cycleTime,
494                                 tl.getTrafficLightColor());
495                     }
496                 }
497             }
498         }
499     }
500 
501     /**
502      * Create a mocked OTSModelInterface.
503      * @return OTSModelInterface
504      */
505     public OTSModelInterface createModelMock()
506     {
507         return Mockito.mock(OTSModelInterface.class);
508     }
509 
510     /** Remember current state of all mocked traffic lights. */
511     private Map<String, TrafficLightColor> currentTrafficLightColors = new LinkedHashMap<>();
512 
513     /**
514      * Mock a traffic light.
515      * @param id String; value that will be returned by the getId method
516      * @param networkId String; name of network (prepended to id for result of getFullId method)
517      * @param simulator TODO
518      * @return TrafficLight
519      */
520     public TrafficLight createTrafficLightMock(final String id, final String networkId, final OTSSimulatorInterface simulator)
521     {
522         TrafficLight result = Mockito.mock(TrafficLight.class);
523         Mockito.when(result.getId()).thenReturn(id);
524         Mockito.when(result.getFullId()).thenReturn(networkId + "." + id);
525         Mockito.when(result.getTrafficLightColor()).thenAnswer(new Answer<TrafficLightColor>()
526         {
527             @Override
528             public TrafficLightColor answer(final InvocationOnMock invocation) throws Throwable
529             {
530                 return TestFixedTimeController.this.currentTrafficLightColors.get(result.getFullId());
531             }
532         });
533         Mockito.doAnswer((Answer<Void>) invocation ->
534         {
535             TrafficLightColor tlc = invocation.getArgument(0);
536             // System.out.println(simulator.getSimulatorTime() + " changing color of " + result.getFullId() + " from "
537             // + this.currentTrafficLightColors.get(result.getFullId()) + " to " + tlc);
538             this.currentTrafficLightColors.put(result.getFullId(), tlc);
539             return null;
540         }).when(result).setTrafficLightColor(ArgumentMatchers.any(TrafficLightColor.class));
541         this.currentTrafficLightColors.put(result.getFullId(), TrafficLightColor.BLACK);
542         return result;
543     }
544 
545 }