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