View Javadoc
1   package org.opentrafficsim.swing.script;
2   
3   import java.awt.Dimension;
4   import java.io.Serializable;
5   import java.rmi.RemoteException;
6   import java.text.SimpleDateFormat;
7   import java.util.Date;
8   import java.util.LinkedHashMap;
9   import java.util.Map;
10  
11  import org.djunits.value.vdouble.scalar.Duration;
12  import org.djunits.value.vdouble.scalar.Time;
13  import org.djutils.cli.Checkable;
14  import org.djutils.cli.CliException;
15  import org.djutils.cli.CliUtil;
16  import org.djutils.event.EventInterface;
17  import org.djutils.event.EventListenerInterface;
18  import org.djutils.exceptions.Throw;
19  import org.djutils.exceptions.Try;
20  import org.djutils.reflection.ClassUtil;
21  import org.opentrafficsim.core.animation.gtu.colorer.GTUColorer;
22  import org.opentrafficsim.core.dsol.AbstractOTSModel;
23  import org.opentrafficsim.core.dsol.OTSAnimator;
24  import org.opentrafficsim.core.dsol.OTSSimulator;
25  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
26  import org.opentrafficsim.core.network.OTSNetwork;
27  import org.opentrafficsim.draw.core.OTSDrawingException;
28  import org.opentrafficsim.draw.factory.DefaultAnimationFactory;
29  import org.opentrafficsim.road.network.OTSRoadNetwork;
30  import org.opentrafficsim.swing.gui.AnimationToggles;
31  import org.opentrafficsim.swing.gui.OTSAnimationPanel;
32  import org.opentrafficsim.swing.gui.OTSSimulationApplication;
33  import org.opentrafficsim.swing.gui.OTSSwingApplication;
34  
35  import nl.tudelft.simulation.dsol.SimRuntimeException;
36  import nl.tudelft.simulation.dsol.experiment.ReplicationInterface;
37  import nl.tudelft.simulation.jstats.streams.MersenneTwister;
38  import nl.tudelft.simulation.jstats.streams.StreamInterface;
39  import picocli.CommandLine.Command;
40  import picocli.CommandLine.Option;
41  
42  /**
43   * Template for simulation script. This class allows the user to run a single visualized simulation, or to batch-run the same
44   * model. Parameters can be given through the command-line using djutils-ext. Fields can be added to sub-classes using the
45   * {@code @Options} and similar annotations. Default values of the properties in this abstract class can be overwritten by the
46   * sub-class using {@code CliUtil.changeDefaultValue()}.
47   * <p>
48   * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
49   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
50   * <p>
51   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 9 apr. 2018 <br>
52   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
53   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
54   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
55   */
56  @Command(description = "Test program for CLI", name = "Program", mixinStandardHelpOptions = true, showDefaultValues = true)
57  public abstract class AbstractSimulationScript implements EventListenerInterface, Checkable
58  {
59      /** */
60      private static final long serialVersionUID = 20200129L;
61  
62      /** Name. */
63      private final String name;
64  
65      /** Description. */
66      private final String description;
67  
68      /** The simulator. */
69      private OTSSimulatorInterface simulator;
70  
71      /** The network. */
72      private OTSRoadNetwork network;
73  
74      /** GTU colorer. */
75      private GTUColorer gtuColorer = OTSSwingApplication.DEFAULT_COLORER;
76  
77      /** Seed. */
78      @Option(names = "--seed", description = "Seed", defaultValue = "1")
79      private long seed;
80  
81      /** Start time. */
82      @Option(names = { "-s", "--startTime" }, description = "Start time", defaultValue = "0s")
83      private Time startTime;
84  
85      /** Warm-up time. */
86      @Option(names = { "-w", "--warmupTime" }, description = "Warm-up time", defaultValue = "0s")
87      private Duration warmupTime;
88  
89      /** Simulation time. */
90      @Option(names = { "-t", "--simulationTime" }, description = "Simulation time (including warm-up time)",
91              defaultValue = "3600s")
92      private Duration simulationTime;
93  
94      /** Autorun. */
95      @Option(names = { "-a", "--autorun" }, description = "Autorun", negatable = true, defaultValue = "false")
96      private boolean autorun;
97  
98      /**
99       * Constructor.
100      * @param name String; name
101      * @param description String; description
102      */
103     protected AbstractSimulationScript(final String name, final String description)
104     {
105         this.name = name;
106         this.description = description;
107         try
108         {
109             CliUtil.changeCommandName(this, this.name);
110             CliUtil.changeCommandDescription(this, this.description);
111             SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
112             CliUtil.changeCommandVersion(this,
113                     formatter.format(new Date(ClassUtil.classFileDescriptor(this.getClass()).getLastChangedDate())));
114         }
115         catch (IllegalStateException | IllegalArgumentException | CliException exception)
116         {
117             throw new RuntimeException("Exception while setting properties in @Command annotation.", exception);
118         }
119     }
120 
121     /**
122      * Returns the seed.
123      * @return long; seed
124      */
125     public long getSeed()
126     {
127         return this.seed;
128     }
129 
130     /**
131      * Returns the start time.
132      * @return Time; start time
133      */
134     public Time getStartTime()
135     {
136         return this.startTime;
137     }
138 
139     /**
140      * Returns the warm-up time.
141      * @return Duration; warm-up time
142      */
143     public Duration getWarmupTime()
144     {
145         return this.warmupTime;
146     }
147 
148     /**
149      * Returns the simulation time.
150      * @return Duration; simulation time
151      */
152     public Duration getSimulationTime()
153     {
154         return this.simulationTime;
155     }
156 
157     /**
158      * Returns whether to autorun.
159      * @return boolean; whether to autorun
160      */
161     public boolean isAutorun()
162     {
163         return this.autorun;
164     }
165 
166     /**
167      * Set GTU colorer.
168      * @param colorer GTUColorer; GTU colorer
169      */
170     public final void setGtuColorer(final GTUColorer colorer)
171     {
172         this.gtuColorer = colorer;
173     }
174 
175     /**
176      * Returns the GTU colorer.
177      * @return returns the GTU colorer
178      */
179     public final GTUColorer getGtuColorer()
180     {
181         return this.gtuColorer;
182     }
183 
184     /** {@inheritDoc} */
185     @Override
186     public void check() throws Exception
187     {
188         Throw.when(this.seed < 0, IllegalArgumentException.class, "Seed should be positive");
189         Throw.when(this.warmupTime.si < 0.0, IllegalArgumentException.class, "Warm-up time should be positive");
190         Throw.when(this.simulationTime.si < 0.0, IllegalArgumentException.class, "Simulation time should be positive");
191         Throw.when(this.simulationTime.si < this.warmupTime.si, IllegalArgumentException.class,
192                 "Simulation time should be longer than warmp-up time");
193     }
194 
195     /**
196      * Starts the simulation.
197      * @throws Exception on any exception
198      */
199     public final void start() throws Exception
200     {
201         if (isAutorun())
202         {
203             // TODO: wait until simulation control buttons are enabled (indicating that the tabs have been added)
204             this.simulator = new OTSSimulator(this.name);
205             final ScriptModel scriptModel = new ScriptModel(this.simulator);
206             this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel);
207             this.simulator.addListener(this, ReplicationInterface.END_REPLICATION_EVENT);
208             double tReport = 60.0;
209             Time t = this.simulator.getSimulatorAbsTime();
210             while (t.si < this.simulationTime.si)
211             {
212                 this.simulator.step();
213                 t = this.simulator.getSimulatorAbsTime();
214                 if (t.si >= tReport)
215                 {
216                     System.out.println("Simulation time is " + t);
217                     tReport += 60.0;
218                 }
219             }
220             // sim.stop(); // end of simulation event
221             onSimulationEnd(); // TODO this is temporary for as long as stop() gives an exception
222             System.exit(0);
223         }
224         else
225         {
226             this.simulator = new OTSAnimator(this.name);
227             final ScriptModel scriptModel = new ScriptModel(this.simulator);
228             this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel);
229             OTSAnimationPanel animationPanel =
230                     new OTSAnimationPanel(scriptModel.getNetwork().getExtent(), new Dimension(800, 600),
231                             (OTSAnimator) this.simulator, scriptModel, getGtuColorer(), scriptModel.getNetwork());
232             setAnimationToggles(animationPanel);
233             animateNetwork(scriptModel.getNetwork());
234             setupDemo(animationPanel, scriptModel.getNetwork());
235             OTSSimulationApplication<ScriptModel> app = new OTSSimulationApplication<ScriptModel>(scriptModel, animationPanel)
236             {
237                 /** */
238                 private static final long serialVersionUID = 20190130L;
239 
240                 /** {@inheritDoc} */
241                 @Override
242                 protected void animateNetwork() throws OTSDrawingException
243                 {
244                     // override with nothing to prevent double toggles
245                 }
246 
247                 /** {@inheritDoc} */
248                 @Override
249                 protected void setAnimationToggles()
250                 {
251                     // override with nothing to prevent double toggles
252                 }
253             };
254             addTabs(this.simulator, app);
255             app.setExitOnClose(true);
256             animationPanel.enableSimulationControlButtons();
257         }
258     }
259 
260     /** {@inheritDoc} */
261     @Override
262     public void notify(final EventInterface event) throws RemoteException
263     {
264         if (event.getType().equals(ReplicationInterface.END_REPLICATION_EVENT))
265         {
266             // try
267             // {
268             // getSimulator().scheduleEventNow(this, this, "onSimulationEnd", null);
269             // }
270             // catch (SimRuntimeException exception)
271             // {
272             // throw new RuntimeException(exception);
273             // }
274             onSimulationEnd();
275             // solve bug that event is fired twice
276             AbstractSimulationScript.this.simulator.removeListener(AbstractSimulationScript.this,
277                     ReplicationInterface.END_REPLICATION_EVENT);
278         }
279     }
280 
281     /**
282      * Returns the simulator.
283      * @return OTSSimulatorInterface; simulator
284      */
285     public final OTSSimulatorInterface getSimulator()
286     {
287         return AbstractSimulationScript.this.simulator;
288     }
289 
290     /**
291      * Returns the network.
292      * @return OTSNetwork; network
293      */
294     public final OTSRoadNetwork getNetwork()
295     {
296         return AbstractSimulationScript.this.network;
297     }
298 
299     // Overridable methods
300 
301     /**
302      * Creates animations for nodes, links and lanes. This can be used if the network is not read from XML.
303      * @param net OTSNetwork; network
304      */
305     protected void animateNetwork(final OTSNetwork net)
306     {
307         try
308         {
309             DefaultAnimationFactory.animateNetwork(net, net.getSimulator(), getGtuColorer());
310         }
311         catch (OTSDrawingException exception)
312         {
313             throw new RuntimeException("Exception while creating network animation.", exception);
314         }
315     }
316 
317     /**
318      * Adds tabs to the animation. May be overridden.
319      * @param sim OTSSimulatorInterface; simulator
320      * @param animation OTSSimulationApplication&lt;?&gt;; animation to add tabs to
321      */
322     protected void addTabs(final OTSSimulatorInterface sim, final OTSSimulationApplication<?> animation)
323     {
324         //
325     }
326 
327     /**
328      * Method that is called when the simulation has ended. This can be used to store data.
329      */
330     protected void onSimulationEnd()
331     {
332         //
333     }
334 
335     /**
336      * Method that is called when the animation has been created, to add components for a demo.
337      * @param animationPanel OTSAnimationPanel; animation panel
338      * @param net OTSNetwork; network
339      */
340     protected void setupDemo(final OTSAnimationPanel animationPanel, final OTSRoadNetwork net)
341     {
342         //
343     }
344 
345     /**
346      * Sets the animation toggles. May be overridden.
347      * @param animation OTSAnimationPanel; animation to set the toggle on
348      */
349     protected void setAnimationToggles(final OTSAnimationPanel animation)
350     {
351         AnimationToggles.setIconAnimationTogglesStandard(animation);
352     }
353 
354     // Abstract methods
355 
356     /**
357      * Sets up the simulation based on provided properties. Properties can be obtained with {@code getProperty()}. Setting up a
358      * simulation should at least create a network and some demand. Additionally this may setup traffic control, sampling, etc.
359      * @param sim OTSSimulatorInterface; simulator
360      * @return OTSNetwork; network
361      * @throws Exception on any exception
362      */
363     protected abstract OTSRoadNetwork setupSimulation(OTSSimulatorInterface sim) throws Exception;
364 
365     // Nested classes
366 
367     /**
368      * Model.
369      * <p>
370      * Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
371      * <br>
372      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
373      * <p>
374      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 9 apr. 2018 <br>
375      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
376      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
377      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
378      */
379     private class ScriptModel extends AbstractOTSModel
380     {
381         /** */
382         private static final long serialVersionUID = 20180409L;
383 
384         /**
385          * @param simulator OTSSimulatorInterface; the simulator
386          */
387         @SuppressWarnings("synthetic-access")
388         ScriptModel(final OTSSimulatorInterface simulator)
389         {
390             super(simulator);
391             AbstractSimulationScript.this.simulator = simulator;
392         }
393 
394         /** {@inheritDoc} */
395         @SuppressWarnings("synthetic-access")
396         @Override
397         public void constructModel() throws SimRuntimeException
398         {
399             Map<String, StreamInterface> streams = new LinkedHashMap<>();
400             StreamInterface stream = new MersenneTwister(getSeed());
401             streams.put("generation", stream);
402             stream = new MersenneTwister(getSeed() + 1);
403             streams.put("default", stream);
404             AbstractSimulationScript.this.simulator.getModel().getStreams().putAll(streams);
405             AbstractSimulationScript.this.network =
406                     Try.assign(() -> AbstractSimulationScript.this.setupSimulation(AbstractSimulationScript.this.simulator),
407                             RuntimeException.class, "Exception while setting up simulation.");
408             try
409             {
410                 AbstractSimulationScript.this.simulator.addListener(AbstractSimulationScript.this,
411                         ReplicationInterface.END_REPLICATION_EVENT);
412             }
413             catch (RemoteException exception)
414             {
415                 throw new SimRuntimeException(exception);
416             }
417         }
418 
419         /** {@inheritDoc} */
420         @SuppressWarnings("synthetic-access")
421         @Override
422         public OTSRoadNetwork getNetwork()
423         {
424             return AbstractSimulationScript.this.network;
425         }
426 
427         /** {@inheritDoc} */
428         @Override
429         public Serializable getSourceId()
430         {
431             return getShortName();
432         }
433         
434     }
435 
436 }