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