View Javadoc
1   package org.opentrafficsim.swing.script;
2   
3   import java.text.SimpleDateFormat;
4   import java.util.Collections;
5   import java.util.Date;
6   import java.util.LinkedHashMap;
7   import java.util.List;
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.base.OtsRuntimeException;
22  import org.opentrafficsim.base.logger.Logger;
23  import org.opentrafficsim.core.dsol.AbstractOtsModel;
24  import org.opentrafficsim.core.dsol.OtsAnimator;
25  import org.opentrafficsim.core.dsol.OtsSimulator;
26  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
27  import org.opentrafficsim.core.gtu.Gtu;
28  import org.opentrafficsim.core.gtu.GtuType;
29  import org.opentrafficsim.core.network.Network;
30  import org.opentrafficsim.core.perception.HistoryManagerDevs;
31  import org.opentrafficsim.draw.colorer.Colorer;
32  import org.opentrafficsim.draw.gtu.DefaultCarAnimation.GtuData.GtuMarker;
33  import org.opentrafficsim.road.network.RoadNetwork;
34  import org.opentrafficsim.swing.gui.AnimationToggles;
35  import org.opentrafficsim.swing.gui.OtsAnimationPanel;
36  import org.opentrafficsim.swing.gui.OtsSimulationApplication;
37  import org.opentrafficsim.swing.gui.OtsSwingApplication;
38  
39  import nl.tudelft.simulation.dsol.SimRuntimeException;
40  import nl.tudelft.simulation.dsol.experiment.Replication;
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-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
53   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
54   * </p>
55   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
56   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
57   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
58   */
59  @Command(description = "Test program for CLI", name = "Program", mixinStandardHelpOptions = true, showDefaultValues = true)
60  public abstract class AbstractSimulationScript implements EventListener, Checkable
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 RoadNetwork network;
73  
74      /** GTU colorers. */
75      private List<Colorer<? super Gtu>> gtuColorers = OtsSwingApplication.DEFAULT_GTU_COLORERS;
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      /** Simulation time. */
95      @Option(names = {"-h", "--history"}, description = "Guaranteed history time", defaultValue = "0s")
96      private Duration historyTime;
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 name
105      * @param description 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.classFileDescriptorForClass(this.getClass()).getLastChangedDate())));
118         }
119         catch (IllegalStateException | IllegalArgumentException | CliException exception)
120         {
121             throw new OtsRuntimeException("Exception while setting properties in @Command annotation.", exception);
122         }
123     }
124 
125     /**
126      * Returns the seed.
127      * @return seed
128      */
129     public long getSeed()
130     {
131         return this.seed;
132     }
133 
134     /**
135      * Returns the start time.
136      * @return start time
137      */
138     public Time getStartTime()
139     {
140         return this.startTime;
141     }
142 
143     /**
144      * Returns the warm-up time.
145      * @return warm-up time
146      */
147     public Duration getWarmupTime()
148     {
149         return this.warmupTime;
150     }
151 
152     /**
153      * Returns the simulation time.
154      * @return simulation time
155      */
156     public Duration getSimulationTime()
157     {
158         return this.simulationTime;
159     }
160 
161     /**
162      * Returns whether to autorun.
163      * @return whether to autorun
164      */
165     public boolean isAutorun()
166     {
167         return this.autorun;
168     }
169 
170     /**
171      * Set GTU colorers.
172      * @param colorers GTU colorers
173      */
174     public final void setGtuColorers(final List<Colorer<? super Gtu>> colorers)
175     {
176         this.gtuColorers = colorers;
177     }
178 
179     /**
180      * Returns the GTU colorers.
181      * @return returns the GTU colorers
182      */
183     public final List<Colorer<? super Gtu>> getGtuColorers()
184     {
185         return this.gtuColorers;
186     }
187 
188     /**
189      * Returns map of (non-default) GTU type markers. The default implementation of this method returns an empty map.
190      * @return map of GTU type markers
191      */
192     public Map<GtuType, GtuMarker> getGtuMarkers()
193     {
194         return Collections.emptyMap();
195     }
196 
197     @Override
198     public void check() throws Exception
199     {
200         Throw.when(this.seed < 0, IllegalArgumentException.class, "Seed should be positive");
201         Throw.when(this.warmupTime.si < 0.0, IllegalArgumentException.class, "Warm-up time should be positive");
202         Throw.when(this.simulationTime.si < 0.0, IllegalArgumentException.class, "Simulation time should be positive");
203         Throw.when(this.simulationTime.si < this.warmupTime.si, IllegalArgumentException.class,
204                 "Simulation time should be longer than warm-up time");
205     }
206 
207     /**
208      * Starts the simulation.
209      * @throws Exception on any exception
210      */
211     public final void start() throws Exception
212     {
213         if (isAutorun())
214         {
215             // TODO: wait until simulation control buttons are enabled (indicating that the tabs have been added)
216             this.simulator = new OtsSimulator(this.name);
217             final ScriptModel scriptModel = new ScriptModel(this.simulator);
218             this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel,
219                     new HistoryManagerDevs(this.simulator, this.historyTime, Duration.ofSI(10.0)));
220             this.simulator.addListener(this, Replication.END_REPLICATION_EVENT);
221             double tReport = 60.0;
222             Duration t = this.simulator.getSimulatorTime();
223             while (t.si < this.simulationTime.si)
224             {
225                 this.simulator.step();
226                 t = this.simulator.getSimulatorTime();
227                 if (t.si >= tReport)
228                 {
229                     Logger.ots().info("Simulation time is " + t);
230                     tReport += 60.0;
231                 }
232             }
233             // sim.stop(); // end of simulation event
234             onSimulationEnd(); // TODO this is temporary for as long as stop() gives an exception
235             System.exit(0);
236         }
237         else
238         {
239             this.simulator = new OtsAnimator(this.name);
240             final ScriptModel scriptModel = new ScriptModel(this.simulator);
241             this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel,
242                     new HistoryManagerDevs(this.simulator, this.historyTime, Duration.ofSI(10.0)));
243             OtsAnimationPanel animationPanel = new OtsAnimationPanel(scriptModel.getNetwork().getExtent(),
244                     (OtsAnimator) this.simulator, scriptModel, getGtuColorers(), scriptModel.getNetwork());
245             setAnimationToggles(animationPanel);
246             setupDemo(animationPanel, scriptModel.getNetwork());
247             OtsSimulationApplication<ScriptModel> app =
248                     new OtsSimulationApplication<ScriptModel>(scriptModel, animationPanel, getGtuMarkers())
249                     {
250                         /** */
251                         private static final long serialVersionUID = 20190130L;
252 
253                         @Override
254                         protected void setAnimationToggles()
255                         {
256                             // override with nothing to prevent double toggles
257                         }
258                     };
259             addTabs(this.simulator, app);
260             app.setExitOnClose(true);
261             animationPanel.enableSimulationControlButtons();
262         }
263     }
264 
265     @Override
266     public void notify(final Event event)
267     {
268         if (event.getType().equals(Replication.END_REPLICATION_EVENT))
269         {
270             // try
271             // {
272             // getSimulator().scheduleEventNow(this, this, "onSimulationEnd", null);
273             // }
274             // catch (SimRuntimeException exception)
275             // {
276             // throw new OtsRuntimeException(exception);
277             // }
278             onSimulationEnd();
279             // solve bug that event is fired twice
280             AbstractSimulationScript.this.simulator.removeListener(AbstractSimulationScript.this,
281                     Replication.END_REPLICATION_EVENT);
282         }
283     }
284 
285     /**
286      * Returns the simulator.
287      * @return simulator
288      */
289     public final OtsSimulatorInterface getSimulator()
290     {
291         return AbstractSimulationScript.this.simulator;
292     }
293 
294     /**
295      * Returns the network.
296      * @return network
297      */
298     public final RoadNetwork 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 network
308      * @param animationPanel animation panel
309      */
310     protected void animateNetwork(final Network net, final OtsAnimationPanel animationPanel)
311     {
312         DefaultAnimationFactory.animateNetwork(net, net.getSimulator(),
313                 animationPanel.getColorControlPanel().getGtuColorerManager(), getGtuMarkers());
314     }
315 
316     /**
317      * Adds tabs to the animation. May be overridden.
318      * @param sim simulator
319      * @param animation animation to add tabs to
320      */
321     protected void addTabs(final OtsSimulatorInterface sim, final OtsSimulationApplication<?> animation)
322     {
323         //
324     }
325 
326     /**
327      * Method that is called when the simulation has ended. This can be used to store data.
328      */
329     protected void onSimulationEnd()
330     {
331         //
332     }
333 
334     /**
335      * Method that is called when the animation has been created, to add components for a demo.
336      * @param animationPanel animation panel
337      * @param net network
338      */
339     protected void setupDemo(final OtsAnimationPanel animationPanel, final RoadNetwork net)
340     {
341         //
342     }
343 
344     /**
345      * Sets the animation toggles. May be overridden.
346      * @param animation animation to set the toggle on
347      */
348     protected void setAnimationToggles(final OtsAnimationPanel animation)
349     {
350         AnimationToggles.setIconAnimationTogglesStandard(animation);
351     }
352 
353     // Abstract methods
354 
355     /**
356      * Sets up the simulation based on provided properties. Properties can be obtained with {@code getProperty()}. Setting up a
357      * simulation should at least create a network and some demand. Additionally this may setup traffic control, sampling, etc.
358      * @param sim simulator
359      * @return network
360      * @throws Exception on any exception
361      */
362     protected abstract RoadNetwork setupSimulation(OtsSimulatorInterface sim) throws Exception;
363 
364     // Nested classes
365 
366     /**
367      * Model.
368      * <p>
369      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
370      * <br>
371      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
372      * </p>
373      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
374      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
375      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
376      */
377     private class ScriptModel extends AbstractOtsModel
378     {
379         /**
380          * @param simulator the simulator
381          */
382         @SuppressWarnings("synthetic-access")
383         ScriptModel(final OtsSimulatorInterface simulator)
384         {
385             super(simulator);
386             AbstractSimulationScript.this.simulator = simulator;
387         }
388 
389         @SuppressWarnings("synthetic-access")
390         @Override
391         public void constructModel() throws SimRuntimeException
392         {
393             Map<String, StreamInterface> streams = new LinkedHashMap<>();
394             StreamInterface stream = new MersenneTwister(getSeed());
395             streams.put("generation", stream);
396             stream = new MersenneTwister(getSeed() + 1);
397             streams.put("default", stream);
398             AbstractSimulationScript.this.simulator.getModel().getStreams().putAll(streams);
399             AbstractSimulationScript.this.network =
400                     Try.assign(() -> AbstractSimulationScript.this.setupSimulation(AbstractSimulationScript.this.simulator),
401                             OtsRuntimeException.class, "Exception while setting up simulation.");
402             AbstractSimulationScript.this.simulator.addListener(AbstractSimulationScript.this,
403                     Replication.END_REPLICATION_EVENT);
404         }
405 
406         @SuppressWarnings("synthetic-access")
407         @Override
408         public RoadNetwork getNetwork()
409         {
410             return AbstractSimulationScript.this.network;
411         }
412 
413     }
414 
415 }