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.Collections;
7   import java.util.Date;
8   import java.util.LinkedHashMap;
9   import java.util.List;
10  import java.util.Map;
11  
12  import org.djunits.value.vdouble.scalar.Duration;
13  import org.djunits.value.vdouble.scalar.Time;
14  import org.djutils.cli.Checkable;
15  import org.djutils.cli.CliException;
16  import org.djutils.cli.CliUtil;
17  import org.djutils.event.Event;
18  import org.djutils.event.EventListener;
19  import org.djutils.exceptions.Throw;
20  import org.djutils.exceptions.Try;
21  import org.djutils.reflection.ClassUtil;
22  import org.opentrafficsim.animation.DefaultAnimationFactory;
23  import org.opentrafficsim.animation.gtu.colorer.GtuColorer;
24  import org.opentrafficsim.core.dsol.AbstractOtsModel;
25  import org.opentrafficsim.core.dsol.OtsAnimator;
26  import org.opentrafficsim.core.dsol.OtsSimulator;
27  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
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.gtu.DefaultCarAnimation.GtuData.GtuMarker;
32  import org.opentrafficsim.road.network.RoadNetwork;
33  import org.opentrafficsim.swing.gui.AnimationToggles;
34  import org.opentrafficsim.swing.gui.OtsAnimationPanel;
35  import org.opentrafficsim.swing.gui.OtsSimulationApplication;
36  import org.opentrafficsim.swing.gui.OtsSwingApplication;
37  
38  import nl.tudelft.simulation.dsol.SimRuntimeException;
39  import nl.tudelft.simulation.dsol.experiment.Replication;
40  import nl.tudelft.simulation.jstats.streams.MersenneTwister;
41  import nl.tudelft.simulation.jstats.streams.StreamInterface;
42  import picocli.CommandLine.Command;
43  import picocli.CommandLine.Option;
44  
45  /**
46   * Template for simulation script. This class allows the user to run a single visualized simulation, or to batch-run the same
47   * model. Parameters can be given through the command-line using djutils-ext. Fields can be added to sub-classes using the
48   * {@code @Options} and similar annotations. Default values of the properties in this abstract class can be overwritten by the
49   * sub-class using {@code CliUtil.changeDefaultValue()}.
50   * <p>
51   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
52   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
53   * </p>
54   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
55   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
56   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
57   */
58  @Command(description = "Test program for CLI", name = "Program", mixinStandardHelpOptions = true, showDefaultValues = true)
59  public abstract class AbstractSimulationScript implements EventListener, 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 RoadNetwork network;
75  
76      /** GTU colorers. */
77      private List<GtuColorer> gtuColorers = OtsSwingApplication.DEFAULT_GTU_COLORERS;
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      /** Simulation time. */
97      @Option(names = {"-h", "--history"}, description = "Guaranteed history time", defaultValue = "0s")
98      private Duration historyTime;
99  
100     /** Autorun. */
101     @Option(names = {"-a", "--autorun"}, description = "Autorun", negatable = true, defaultValue = "false")
102     private boolean autorun;
103 
104     /**
105      * Constructor.
106      * @param name name
107      * @param description description
108      */
109     protected AbstractSimulationScript(final String name, final String description)
110     {
111         this.name = name;
112         this.description = description;
113         try
114         {
115             CliUtil.changeCommandName(this, this.name);
116             CliUtil.changeCommandDescription(this, this.description);
117             SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
118             CliUtil.changeCommandVersion(this,
119                     formatter.format(new Date(ClassUtil.classFileDescriptor(this.getClass()).getLastChangedDate())));
120         }
121         catch (IllegalStateException | IllegalArgumentException | CliException exception)
122         {
123             throw new RuntimeException("Exception while setting properties in @Command annotation.", exception);
124         }
125     }
126 
127     /**
128      * Returns the seed.
129      * @return seed
130      */
131     public long getSeed()
132     {
133         return this.seed;
134     }
135 
136     /**
137      * Returns the start time.
138      * @return start time
139      */
140     public Time getStartTime()
141     {
142         return this.startTime;
143     }
144 
145     /**
146      * Returns the warm-up time.
147      * @return warm-up time
148      */
149     public Duration getWarmupTime()
150     {
151         return this.warmupTime;
152     }
153 
154     /**
155      * Returns the simulation time.
156      * @return simulation time
157      */
158     public Duration getSimulationTime()
159     {
160         return this.simulationTime;
161     }
162 
163     /**
164      * Returns whether to autorun.
165      * @return whether to autorun
166      */
167     public boolean isAutorun()
168     {
169         return this.autorun;
170     }
171 
172     /**
173      * Set GTU colorers.
174      * @param colorers GTU colorers
175      */
176     public final void setGtuColorers(final List<GtuColorer> colorers)
177     {
178         this.gtuColorers = colorers;
179     }
180 
181     /**
182      * Returns the GTU colorers.
183      * @return returns the GTU colorers
184      */
185     public final List<GtuColorer> getGtuColorers()
186     {
187         return this.gtuColorers;
188     }
189 
190     /**
191      * Returns map of (non-default) GTU type markers. The default implementation of this method returns an empty map.
192      * @return map of GTU type markers
193      */
194     public Map<GtuType, GtuMarker> getGtuMarkers()
195     {
196         return Collections.emptyMap();
197     }
198 
199     @Override
200     public void check() throws Exception
201     {
202         Throw.when(this.seed < 0, IllegalArgumentException.class, "Seed should be positive");
203         Throw.when(this.warmupTime.si < 0.0, IllegalArgumentException.class, "Warm-up time should be positive");
204         Throw.when(this.simulationTime.si < 0.0, IllegalArgumentException.class, "Simulation time should be positive");
205         Throw.when(this.simulationTime.si < this.warmupTime.si, IllegalArgumentException.class,
206                 "Simulation time should be longer than warm-up time");
207     }
208 
209     /**
210      * Starts the simulation.
211      * @throws Exception on any exception
212      */
213     public final void start() throws Exception
214     {
215         if (isAutorun())
216         {
217             // TODO: wait until simulation control buttons are enabled (indicating that the tabs have been added)
218             this.simulator = new OtsSimulator(this.name);
219             final ScriptModel scriptModel = new ScriptModel(this.simulator);
220             this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel,
221                     new HistoryManagerDevs(this.simulator, this.historyTime, Duration.instantiateSI(10.0)));
222             this.simulator.addListener(this, Replication.END_REPLICATION_EVENT);
223             double tReport = 60.0;
224             Time t = this.simulator.getSimulatorAbsTime();
225             while (t.si < this.simulationTime.si)
226             {
227                 this.simulator.step();
228                 t = this.simulator.getSimulatorAbsTime();
229                 if (t.si >= tReport)
230                 {
231                     System.out.println("Simulation time is " + t);
232                     tReport += 60.0;
233                 }
234             }
235             // sim.stop(); // end of simulation event
236             onSimulationEnd(); // TODO this is temporary for as long as stop() gives an exception
237             System.exit(0);
238         }
239         else
240         {
241             this.simulator = new OtsAnimator(this.name);
242             final ScriptModel scriptModel = new ScriptModel(this.simulator);
243             this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel,
244                     new HistoryManagerDevs(this.simulator, this.historyTime, Duration.instantiateSI(10.0)));
245             OtsAnimationPanel animationPanel =
246                     new OtsAnimationPanel(scriptModel.getNetwork().getExtent(), new Dimension(800, 600),
247                             (OtsAnimator) this.simulator, scriptModel, getGtuColorers(), scriptModel.getNetwork());
248             setAnimationToggles(animationPanel);
249             setupDemo(animationPanel, scriptModel.getNetwork());
250             OtsSimulationApplication<ScriptModel> app =
251                     new OtsSimulationApplication<ScriptModel>(scriptModel, animationPanel, getGtuMarkers())
252                     {
253                         /** */
254                         private static final long serialVersionUID = 20190130L;
255 
256                         @Override
257                         protected void setAnimationToggles()
258                         {
259                             // override with nothing to prevent double toggles
260                         }
261                     };
262             addTabs(this.simulator, app);
263             app.setExitOnClose(true);
264             animationPanel.enableSimulationControlButtons();
265         }
266     }
267 
268     @Override
269     public void notify(final Event event) throws RemoteException
270     {
271         if (event.getType().equals(Replication.END_REPLICATION_EVENT))
272         {
273             // try
274             // {
275             // getSimulator().scheduleEventNow(this, this, "onSimulationEnd", null);
276             // }
277             // catch (SimRuntimeException exception)
278             // {
279             // throw new RuntimeException(exception);
280             // }
281             onSimulationEnd();
282             // solve bug that event is fired twice
283             AbstractSimulationScript.this.simulator.removeListener(AbstractSimulationScript.this,
284                     Replication.END_REPLICATION_EVENT);
285         }
286     }
287 
288     /**
289      * Returns the simulator.
290      * @return simulator
291      */
292     public final OtsSimulatorInterface getSimulator()
293     {
294         return AbstractSimulationScript.this.simulator;
295     }
296 
297     /**
298      * Returns the network.
299      * @return network
300      */
301     public final RoadNetwork getNetwork()
302     {
303         return AbstractSimulationScript.this.network;
304     }
305 
306     // Overridable methods
307 
308     /**
309      * Creates animations for nodes, links and lanes. This can be used if the network is not read from XML.
310      * @param net network
311      * @param animationPanel animation panel
312      */
313     protected void animateNetwork(final Network net, final OtsAnimationPanel animationPanel)
314     {
315         DefaultAnimationFactory.animateNetwork(net, net.getSimulator(),
316                 animationPanel.getColorControlPanel().getGtuColorerManager(), getGtuMarkers());
317     }
318 
319     /**
320      * Adds tabs to the animation. May be overridden.
321      * @param sim simulator
322      * @param animation 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 animation panel
340      * @param net network
341      */
342     protected void setupDemo(final OtsAnimationPanel animationPanel, final RoadNetwork net)
343     {
344         //
345     }
346 
347     /**
348      * Sets the animation toggles. May be overridden.
349      * @param animation 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 simulator
362      * @return network
363      * @throws Exception on any exception
364      */
365     protected abstract RoadNetwork setupSimulation(OtsSimulatorInterface sim) throws Exception;
366 
367     // Nested classes
368 
369     /**
370      * Model.
371      * <p>
372      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
373      * <br>
374      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
375      * </p>
376      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
377      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
378      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
379      */
380     private class ScriptModel extends AbstractOtsModel
381     {
382         /** */
383         private static final long serialVersionUID = 20180409L;
384 
385         /**
386          * @param simulator the simulator
387          */
388         @SuppressWarnings("synthetic-access")
389         ScriptModel(final OtsSimulatorInterface simulator)
390         {
391             super(simulator);
392             AbstractSimulationScript.this.simulator = simulator;
393         }
394 
395         @SuppressWarnings("synthetic-access")
396         @Override
397         public void constructModel() throws SimRuntimeException
398         {
399             Map<String, StreamInterface> streams = new LinkedHashMap<>();
400             StreamInterface stream = new MersenneTwister(getSeed());
401             streams.put("generation", stream);
402             stream = new MersenneTwister(getSeed() + 1);
403             streams.put("default", stream);
404             AbstractSimulationScript.this.simulator.getModel().getStreams().putAll(streams);
405             AbstractSimulationScript.this.network =
406                     Try.assign(() -> AbstractSimulationScript.this.setupSimulation(AbstractSimulationScript.this.simulator),
407                             RuntimeException.class, "Exception while setting up simulation.");
408             try
409             {
410                 AbstractSimulationScript.this.simulator.addListener(AbstractSimulationScript.this,
411                         Replication.END_REPLICATION_EVENT);
412             }
413             catch (RemoteException exception)
414             {
415                 throw new SimRuntimeException(exception);
416             }
417         }
418 
419         @SuppressWarnings("synthetic-access")
420         @Override
421         public RoadNetwork getNetwork()
422         {
423             return AbstractSimulationScript.this.network;
424         }
425 
426     }
427 
428 }