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