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