D2S2 scripts are simple sequential-execution programs written in C#. Scripts can be used for:
Scripts in D2S2 inherit from the SimulationScript
base class. The user-coded script has access to methods in the base class to access model objects, and control simulation execution.
Your script should inherit from the SimulationScript
class. An empty script template is given below:
using D2S2.Model;
using D2S2.Model.Satellite;
using D2S2.Shared.MathPhysics;
using D2S2.Shared.Satellite;
using D2S2.Simulation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace namespace D2S2.Testing
{
[DisplayName("Empty Sample Script")]
public class SampleScript : SimulationScript
{
protected override void Run()
{
ReportProgress("Starting Sample Script....");
ReportProgress("Starting Sample Script....Complete");
}
public override bool HaltSimulation(DateTime simTime, object userContext)
{
return false;
}
public override void SimulationIteration(DateTime simTime, object userContext)
{
}
}
}
The Run()
function will be called by D2S2 when the user presses the Run button on the Run Script window.
The ReportProgress
method is one of the methods in the SimulationScript
and is simply used to output text to the Script Output panel.
If the above empty script template is compiled and executed within D2S2, the output in Figure 1 should be visible in the Script Output panel.
Figure 1: Output generated by the Empty Script template.
Model objects in the simulation are instances of C# classes, and they can be manipulated in C# scripts by obtaining a reference to it. There are two ways to "find" simulation model objects:
References to simulation model objects are obtained through the SimulationManager
, available as inherited property in the SimulationScript
base class. The root simulation object can be found using
SatelliteSimulation simRoot = (SatelliteSimulation)SimulationManager.Model;
The root simulation object is guaranteed to be of type D2S2.Model.SatelliteSimulation
. The root object then has dependent properties for Earth
, Sun
, Moon
, Satellites
, and GeoLocations
. The Model View is just a graphic depiction of the same dependencies.
Figure 2: Model View showing model graph and dependent model objects.
The Satellites
and GeoLocations
dependencies are lists. An example code snippet to display the names of all satellites, and all the components in each satellite, can be seen below.
foreach (SatelliteModelBase satellite in simRoot.Satellites)
{
ReportProgress(satellite.Name);
foreach (SatelliteComponent component in satellite.Components)
{
ReportProgress(" " + component.Name);
}
}
Model objects can also be looked up directly by name reference. The TryGetModelObject
helper method may be used in custom scripts, and shows how model objects can be found from their names in the simulation.
protected object TryGetModelObject(string objName)
{
if (SimulationManager.ModelObjectIds.Contains(objName))
return SimulationManager.GetModelObject(objName);
return null;
}
// Get handle to satellite - states and environment
SatelliteModel sat = TryGetModelObject("SimSat") as SatelliteModel;
Input and initial values of model objects are all available as public properties of the model object class. The Model Reference
documentation contains details on all available properties per model object. The code example below demonstrates how to initialise a satellite with SGP4 kinematic model's initial orbit.
if (simRoot.Satellites.Count == 0)
throw new Exception("There are no satellites in the simulation model!");
SatelliteModelBase sat1 = simRoot.Satellites[0];
if (!(sat1.KinematicModel is Sgp4OrbitKinematicModel))
throw new Exception("This scripts expects the satellite to have a SGP4 kinematic model");
Sgp4OrbitKinematicModel orbitModel = (Sgp4OrbitKinematicModel)sat1.KinematicModel;
orbitModel.Inclination = 97.4;
orbitModel.MeanMotion = 15.2;
orbitModel.Eccentricity = 0.0001;
A new model object is typically created using standard new
syntax in C#. All model objects in the simulation have a Name
property, and this must be assigned before adding it to the rest of the model through a dependency.
Dependencies that take a list of dependent objects use the Add
method to add new objects to the list.
SolarPanel panelX = new SolarPanel();
panelX.Name = "X-Solar Panel";
satellite.Components.Add(panelX);
And single dependencies are simply assigned, i.e.
SunModel sun = new SunModel();
sun.Name = "Sol";
simRoot.Sun = sun;
Model objects are "deleted" when they are no longer referenced anywhere in the model graph.
Note that an exception will occur if a model object is added or deleted if the simulation is not in the reset state. Scripts that modify the simulation model can only be run after resetting the simulation.
To perform a simulation run, the function RunSimulation(object userContext)
is used. This is equivalent to starting the simulation through the user interface. The method will block until the simulation has reached the end time, specified in the simulation settings, or until a user-defined 'halt' condition is met.
The HaltSimulation
method can be overridden to introduce custom logic to end the simulation. The simulation will stop when this method returns True. In the sample code below the simulation will stop after simulating 4000 seconds:
// Condition where simulation will end - currently set to end at a certain simulation duration
public override bool HaltSimulation(DateTime simTime, object userContext)
{
return (simTime - SimulationControlService.SimulationStart) >= TimeSpan.FromSeconds(4000);
}
It is also possible to introduce custom code at every simulation iteration. The SimulationIteration
method is called at the end of every iteration before the time step is applied.
In the example below, simple logging functionality is implemented which creates a CSV file with the satellite's current position in its orbit.
// Function is called at each iteration of the simulation - place state machine and logging here
public override void SimulationIteration(DateTime simTime, object userContext)
{
// First iteration, write titles of columns
if((simTime - SimulationControlService.SimulationStart) == TimeSpan.FromSeconds(0))
{
line = "Time, PositionX, PositionY, PositionZ";
sw.WriteLine(line);
}
// Populate one line of csv file
line = "" + simTime.ToUniversalTime();
line = line + "," + sat.KinematicState.Position.X + "," + sat.KinematicState.Position.Y + "," + sat.KinematicState.Position.Z;
sw.WriteLine(line);
}
The userContext
method parameter can be used to inform the HaltSimulation
and SimulationIteration
overrides of the context in the script's Run
sequence, from where RunSimulation
was called. This allows the logic in HaltSimulation
and SimulationIternation
to distinguish between simulation runs, or to pass data. An example script that runs a simulation twice, with different simulation durations, is shown below:
protected override void Run()
{
// Run simulation
RunSimulation(1);
// run it again, for longer
RunSimulation(2);
}
public override bool HaltSimulation(DateTime simTime, object userContext)
{
int runno = (int)userContext;
if (runno == 1)
return (simTime - SimulationControlService.SimulationStart) >= TimeSpan.FromSeconds(4000);
else
return (simTime - SimulationControlService.SimulationStart) >= TimeSpan.FromSeconds(8000);
}
The ReportProgress
method will output plain mono-spaced font text to the Script Output. Each string that is passed will have automatic line termination appended.
It is also possible to output markdown to the Script Output using the ReportMarkdown
method. The string that is passed to ReportMarkdown
must contain the entire markdown block. The recommended practise is to use StringBuilder
or similar to construct the markdown before calling ReportMarkdown
.
Figure 3: Example markdown output for a grid of results, produced by a script.
Note that markdown reporting is still in development, and not all markdown syntax is supported.