View Javadoc
1   package org.opentrafficsim.swing.script;
2   
3   import java.text.SimpleDateFormat;
4   import java.util.Date;
5   import java.util.LinkedHashMap;
6   import java.util.Map;
7   
8   import org.djunits.value.vdouble.scalar.Duration;
9   import org.djutils.cli.Checkable;
10  import org.djutils.cli.CliException;
11  import org.djutils.cli.CliUtil;
12  import org.djutils.event.Event;
13  import org.djutils.event.EventListener;
14  import org.djutils.exceptions.Throw;
15  import org.djutils.exceptions.Try;
16  import org.djutils.reflection.ClassUtil;
17  import org.opentrafficsim.base.OtsRuntimeException;
18  import org.opentrafficsim.base.logger.Logger;
19  import org.opentrafficsim.core.dsol.AbstractOtsModel;
20  import org.opentrafficsim.core.dsol.OtsAnimator;
21  import org.opentrafficsim.core.dsol.OtsSimulator;
22  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
23  import org.opentrafficsim.core.perception.HistoryManagerDevs;
24  import org.opentrafficsim.road.network.RoadNetwork;
25  import org.opentrafficsim.swing.gui.OtsSimulationApplication;
26  import org.opentrafficsim.swing.gui.OtsSimulationPanel;
27  import org.opentrafficsim.swing.gui.OtsSimulationPanelDecorator;
28  
29  import nl.tudelft.simulation.dsol.SimRuntimeException;
30  import nl.tudelft.simulation.dsol.experiment.Replication;
31  import nl.tudelft.simulation.dsol.simulators.ReplicationState;
32  import nl.tudelft.simulation.jstats.streams.MersenneTwister;
33  import nl.tudelft.simulation.jstats.streams.StreamInterface;
34  import picocli.CommandLine.Command;
35  import picocli.CommandLine.Option;
36  
37  /**
38   * Template for simulation script. This class allows the user to run a single visualized simulation, or to batch-run the same
39   * model. Parameters can be given through the command-line using djutils-ext. Fields can be added to sub-classes using the
40   * {@code @Options} and similar annotations. Default values of the properties in this abstract class can be overwritten by the
41   * sub-class using {@code CliUtil.changeDefaultValue()}.
42   * <p>
43   * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
44   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
45   * </p>
46   * @author Alexander Verbraeck
47   * @author Peter Knoppers
48   * @author Wouter Schakel
49   */
50  @Command(description = "Simulation script", name = "Program", mixinStandardHelpOptions = true, showDefaultValues = true)
51  public abstract class AbstractSimulationScript implements EventListener, Checkable
52  {
53      /** Name. */
54      private final String name;
55  
56      /** Description. */
57      private final String description;
58  
59      /** Simulator. */
60      private OtsSimulatorInterface simulator;
61  
62      /** Network. */
63      private RoadNetwork network;
64  
65      /** Seed. */
66      @Option(names = "--seed", description = "Seed", defaultValue = "1")
67      private long seed;
68  
69      /** Start time. */
70      @Option(names = {"-s", "--startTime"}, description = "Start time (of day)", defaultValue = "0s")
71      private Duration startTime;
72  
73      /** Warm-up time. */
74      @Option(names = {"-w", "--warmupTime"}, description = "Warm-up time", defaultValue = "0s")
75      private Duration warmupTime;
76  
77      /** Simulation time. */
78      @Option(names = {"-t", "--simulationTime"}, description = "Simulation time (including warm-up time)",
79              defaultValue = "3600s")
80      private Duration simulationTime;
81  
82      /** Simulation time. */
83      @Option(names = {"-h", "--history"}, description = "Guaranteed history time", defaultValue = "0s")
84      private Duration historyTime;
85  
86      /** Auto-run. */
87      @Option(names = {"-a", "--autorun"}, description = "Autorun", negatable = true, defaultValue = "false")
88      private boolean autorun;
89  
90      /**
91       * Constructor.
92       * @param name name
93       * @param description description
94       */
95      protected AbstractSimulationScript(final String name, final String description)
96      {
97          this.name = name;
98          this.description = description;
99          try
100         {
101             CliUtil.changeCommandName(this, this.name);
102             CliUtil.changeCommandDescription(this, this.description);
103             SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
104             CliUtil.changeCommandVersion(this,
105                     formatter.format(new Date(ClassUtil.classFileDescriptorForClass(this.getClass()).getLastChangedDate())));
106         }
107         catch (IllegalStateException | IllegalArgumentException | CliException exception)
108         {
109             throw new OtsRuntimeException("Exception while setting properties in @Command annotation.", exception);
110         }
111     }
112 
113     /**
114      * Returns the seed.
115      * @return seed
116      */
117     public long getSeed()
118     {
119         return this.seed;
120     }
121 
122     /**
123      * Returns the start time.
124      * @return start time
125      */
126     public Duration getStartTime()
127     {
128         return this.startTime;
129     }
130 
131     /**
132      * Returns the warm-up time.
133      * @return warm-up time
134      */
135     public Duration getWarmupTime()
136     {
137         return this.warmupTime;
138     }
139 
140     /**
141      * Returns the simulation time.
142      * @return simulation time
143      */
144     public Duration getSimulationTime()
145     {
146         return this.simulationTime;
147     }
148 
149     /**
150      * Returns whether to auto-run.
151      * @return whether to auto-run
152      */
153     public boolean isAutorun()
154     {
155         return this.autorun;
156     }
157 
158     @Override
159     public void check() throws Exception
160     {
161         Throw.when(this.seed < 0, IllegalArgumentException.class, "Seed should be positive");
162         Throw.when(this.warmupTime.si < 0.0, IllegalArgumentException.class, "Warm-up time should be positive");
163         Throw.when(this.simulationTime.si < 0.0, IllegalArgumentException.class, "Simulation time should be positive");
164         Throw.when(this.simulationTime.si < this.warmupTime.si, IllegalArgumentException.class,
165                 "Simulation time should be longer than warm-up time");
166     }
167 
168     /**
169      * Starts the simulation.
170      * @throws Exception on any exception
171      */
172     public void start() throws Exception
173     {
174         if (isAutorun())
175         {
176             this.simulator = new OtsSimulator(this.name);
177             final ScriptModel scriptModel = new ScriptModel(this.simulator);
178             this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel,
179                     new HistoryManagerDevs(this.simulator, this.historyTime, Duration.ofSI(10.0)));
180             this.simulator.addListener(this, Replication.END_REPLICATION_EVENT);
181             double tReport = 60.0;
182             Duration t = this.simulator.getSimulatorTime();
183             while (t.si < this.simulationTime.si)
184             {
185                 this.simulator.step();
186                 t = this.simulator.getSimulatorTime();
187                 if (t.si >= tReport)
188                 {
189                     Logger.ots().info("Simulation time is " + t);
190                     tReport += 60.0;
191                 }
192             }
193             if (!this.simulator.getReplicationState().equals(ReplicationState.ENDED))
194             {
195                 onSimulationEnd();
196             }
197             System.exit(0);
198         }
199         else
200         {
201             this.simulator = new OtsAnimator(this.name);
202             final ScriptModel scriptModel = new ScriptModel(this.simulator);
203             this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel,
204                     new HistoryManagerDevs(this.simulator, this.historyTime, Duration.ofSI(10.0)));
205             OtsSimulationPanel animationPanel = new OtsSimulationPanel(scriptModel.getNetwork(), getDecorator());
206             OtsSimulationApplication<ScriptModel> app = new OtsSimulationApplication<ScriptModel>(scriptModel, animationPanel);
207             app.setExitOnClose(true);
208             animationPanel.enableSimulationControlButtons();
209         }
210     }
211 
212     @Override
213     public void notify(final Event event)
214     {
215         if (event.getType().equals(Replication.END_REPLICATION_EVENT))
216         {
217             onSimulationEnd();
218         }
219     }
220 
221     /**
222      * Returns the simulator.
223      * @return simulator
224      */
225     public OtsSimulatorInterface getSimulator()
226     {
227         return AbstractSimulationScript.this.simulator;
228     }
229 
230     /**
231      * Returns the network.
232      * @return network
233      */
234     public RoadNetwork getNetwork()
235     {
236         return AbstractSimulationScript.this.network;
237     }
238 
239     // Overridable methods
240 
241     /**
242      * Method that is called when the simulation has ended. This can be used to store data.
243      */
244     protected void onSimulationEnd()
245     {
246         //
247     }
248 
249     /**
250      * Returns a decorator. The default implementation returns all default implementations of the decorator methods.
251      * @return decorator
252      */
253     protected OtsSimulationPanelDecorator getDecorator()
254     {
255         return new OtsSimulationPanelDecorator()
256         {
257         };
258     }
259 
260     // Abstract methods
261 
262     /**
263      * Sets up the simulation based on provided properties. Properties can be obtained with {@code getProperty()}. Setting up a
264      * simulation should at least create a network and some demand. Additionally this may setup traffic control, sampling, etc.
265      * @param sim simulator
266      * @return network
267      * @throws Exception on any exception
268      */
269     protected abstract RoadNetwork setupSimulation(OtsSimulatorInterface sim) throws Exception;
270 
271     // Nested classes
272 
273     /**
274      * Model.
275      */
276     private class ScriptModel extends AbstractOtsModel
277     {
278 
279         /**
280          * Constructor.
281          * @param simulator simulator
282          */
283         ScriptModel(final OtsSimulatorInterface simulator)
284         {
285             super(simulator);
286             AbstractSimulationScript.this.simulator = simulator;
287         }
288 
289         @Override
290         public void constructModel() throws SimRuntimeException
291         {
292             Map<String, StreamInterface> streams = new LinkedHashMap<>();
293             StreamInterface stream = new MersenneTwister(getSeed());
294             streams.put("generation", stream);
295             stream = new MersenneTwister(getSeed() + 1);
296             streams.put("default", stream);
297             AbstractSimulationScript.this.simulator.getModel().getStreams().putAll(streams);
298             AbstractSimulationScript.this.network =
299                     Try.assign(() -> AbstractSimulationScript.this.setupSimulation(AbstractSimulationScript.this.simulator),
300                             OtsRuntimeException.class, "Exception while setting up simulation.");
301             AbstractSimulationScript.this.simulator.addListener(AbstractSimulationScript.this,
302                     Replication.END_REPLICATION_EVENT);
303         }
304 
305         @Override
306         public RoadNetwork getNetwork()
307         {
308             return AbstractSimulationScript.this.network;
309         }
310 
311     }
312 
313 }