AbstractSimulationScript.java
package org.opentrafficsim.swing.script;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import org.djunits.value.vdouble.scalar.Duration;
import org.djutils.cli.Checkable;
import org.djutils.cli.CliException;
import org.djutils.cli.CliUtil;
import org.djutils.event.Event;
import org.djutils.event.EventListener;
import org.djutils.exceptions.Throw;
import org.djutils.exceptions.Try;
import org.djutils.reflection.ClassUtil;
import org.opentrafficsim.base.OtsRuntimeException;
import org.opentrafficsim.base.logger.Logger;
import org.opentrafficsim.core.dsol.AbstractOtsModel;
import org.opentrafficsim.core.dsol.OtsAnimator;
import org.opentrafficsim.core.dsol.OtsSimulator;
import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
import org.opentrafficsim.core.perception.HistoryManagerDevs;
import org.opentrafficsim.road.network.RoadNetwork;
import org.opentrafficsim.swing.gui.OtsSimulationApplication;
import org.opentrafficsim.swing.gui.OtsSimulationPanel;
import org.opentrafficsim.swing.gui.OtsSimulationPanelDecorator;
import nl.tudelft.simulation.dsol.SimRuntimeException;
import nl.tudelft.simulation.dsol.experiment.Replication;
import nl.tudelft.simulation.dsol.simulators.ReplicationState;
import nl.tudelft.simulation.jstats.streams.MersenneTwister;
import nl.tudelft.simulation.jstats.streams.StreamInterface;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
/**
* Template for simulation script. This class allows the user to run a single visualized simulation, or to batch-run the same
* model. Parameters can be given through the command-line using djutils-ext. Fields can be added to sub-classes using the
* {@code @Options} and similar annotations. Default values of the properties in this abstract class can be overwritten by the
* sub-class using {@code CliUtil.changeDefaultValue()}.
* <p>
* Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* </p>
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
*/
@Command(description = "Simulation script", name = "Program", mixinStandardHelpOptions = true, showDefaultValues = true)
public abstract class AbstractSimulationScript implements EventListener, Checkable
{
/** Name. */
private final String name;
/** Description. */
private final String description;
/** Simulator. */
private OtsSimulatorInterface simulator;
/** Network. */
private RoadNetwork network;
/** Seed. */
@Option(names = "--seed", description = "Seed", defaultValue = "1")
private long seed;
/** Start time. */
@Option(names = {"-s", "--startTime"}, description = "Start time (of day)", defaultValue = "0s")
private Duration startTime;
/** Warm-up time. */
@Option(names = {"-w", "--warmupTime"}, description = "Warm-up time", defaultValue = "0s")
private Duration warmupTime;
/** Simulation time. */
@Option(names = {"-t", "--simulationTime"}, description = "Simulation time (including warm-up time)",
defaultValue = "3600s")
private Duration simulationTime;
/** Simulation time. */
@Option(names = {"-h", "--history"}, description = "Guaranteed history time", defaultValue = "0s")
private Duration historyTime;
/** Auto-run. */
@Option(names = {"-a", "--autorun"}, description = "Autorun", negatable = true, defaultValue = "false")
private boolean autorun;
/**
* Constructor.
* @param name name
* @param description description
*/
protected AbstractSimulationScript(final String name, final String description)
{
this.name = name;
this.description = description;
try
{
CliUtil.changeCommandName(this, this.name);
CliUtil.changeCommandDescription(this, this.description);
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
CliUtil.changeCommandVersion(this,
formatter.format(new Date(ClassUtil.classFileDescriptorForClass(this.getClass()).getLastChangedDate())));
}
catch (IllegalStateException | IllegalArgumentException | CliException exception)
{
throw new OtsRuntimeException("Exception while setting properties in @Command annotation.", exception);
}
}
/**
* Returns the seed.
* @return seed
*/
public long getSeed()
{
return this.seed;
}
/**
* Returns the start time.
* @return start time
*/
public Duration getStartTime()
{
return this.startTime;
}
/**
* Returns the warm-up time.
* @return warm-up time
*/
public Duration getWarmupTime()
{
return this.warmupTime;
}
/**
* Returns the simulation time.
* @return simulation time
*/
public Duration getSimulationTime()
{
return this.simulationTime;
}
/**
* Returns whether to auto-run.
* @return whether to auto-run
*/
public boolean isAutorun()
{
return this.autorun;
}
@Override
public void check() throws Exception
{
Throw.when(this.seed < 0, IllegalArgumentException.class, "Seed should be positive");
Throw.when(this.warmupTime.si < 0.0, IllegalArgumentException.class, "Warm-up time should be positive");
Throw.when(this.simulationTime.si < 0.0, IllegalArgumentException.class, "Simulation time should be positive");
Throw.when(this.simulationTime.si < this.warmupTime.si, IllegalArgumentException.class,
"Simulation time should be longer than warm-up time");
}
/**
* Starts the simulation.
* @throws Exception on any exception
*/
public void start() throws Exception
{
if (isAutorun())
{
this.simulator = new OtsSimulator(this.name);
final ScriptModel scriptModel = new ScriptModel(this.simulator);
this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel,
new HistoryManagerDevs(this.simulator, this.historyTime, Duration.ofSI(10.0)));
this.simulator.addListener(this, Replication.END_REPLICATION_EVENT);
double tReport = 60.0;
Duration t = this.simulator.getSimulatorTime();
while (t.si < this.simulationTime.si)
{
this.simulator.step();
t = this.simulator.getSimulatorTime();
if (t.si >= tReport)
{
Logger.ots().info("Simulation time is " + t);
tReport += 60.0;
}
}
if (!this.simulator.getReplicationState().equals(ReplicationState.ENDED))
{
onSimulationEnd();
}
System.exit(0);
}
else
{
this.simulator = new OtsAnimator(this.name);
final ScriptModel scriptModel = new ScriptModel(this.simulator);
this.simulator.initialize(this.startTime, this.warmupTime, this.simulationTime, scriptModel,
new HistoryManagerDevs(this.simulator, this.historyTime, Duration.ofSI(10.0)));
OtsSimulationPanel animationPanel = new OtsSimulationPanel(scriptModel.getNetwork(), getDecorator());
OtsSimulationApplication<ScriptModel> app = new OtsSimulationApplication<ScriptModel>(scriptModel, animationPanel);
app.setExitOnClose(true);
animationPanel.enableSimulationControlButtons();
}
}
@Override
public void notify(final Event event)
{
if (event.getType().equals(Replication.END_REPLICATION_EVENT))
{
onSimulationEnd();
}
}
/**
* Returns the simulator.
* @return simulator
*/
public OtsSimulatorInterface getSimulator()
{
return AbstractSimulationScript.this.simulator;
}
/**
* Returns the network.
* @return network
*/
public RoadNetwork getNetwork()
{
return AbstractSimulationScript.this.network;
}
// Overridable methods
/**
* Method that is called when the simulation has ended. This can be used to store data.
*/
protected void onSimulationEnd()
{
//
}
/**
* Returns a decorator. The default implementation returns all default implementations of the decorator methods.
* @return decorator
*/
protected OtsSimulationPanelDecorator getDecorator()
{
return new OtsSimulationPanelDecorator()
{
};
}
// Abstract methods
/**
* Sets up the simulation based on provided properties. Properties can be obtained with {@code getProperty()}. Setting up a
* simulation should at least create a network and some demand. Additionally this may setup traffic control, sampling, etc.
* @param sim simulator
* @return network
* @throws Exception on any exception
*/
protected abstract RoadNetwork setupSimulation(OtsSimulatorInterface sim) throws Exception;
// Nested classes
/**
* Model.
*/
private class ScriptModel extends AbstractOtsModel
{
/**
* Constructor.
* @param simulator simulator
*/
ScriptModel(final OtsSimulatorInterface simulator)
{
super(simulator);
AbstractSimulationScript.this.simulator = simulator;
}
@Override
public void constructModel() throws SimRuntimeException
{
Map<String, StreamInterface> streams = new LinkedHashMap<>();
StreamInterface stream = new MersenneTwister(getSeed());
streams.put("generation", stream);
stream = new MersenneTwister(getSeed() + 1);
streams.put("default", stream);
AbstractSimulationScript.this.simulator.getModel().getStreams().putAll(streams);
AbstractSimulationScript.this.network =
Try.assign(() -> AbstractSimulationScript.this.setupSimulation(AbstractSimulationScript.this.simulator),
OtsRuntimeException.class, "Exception while setting up simulation.");
AbstractSimulationScript.this.simulator.addListener(AbstractSimulationScript.this,
Replication.END_REPLICATION_EVENT);
}
@Override
public RoadNetwork getNetwork()
{
return AbstractSimulationScript.this.network;
}
}
}