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