menu
info Documentation

Developing Scripts

D2S2 scripts are simple sequential-execution programs written in C#. Scripts can be used for:

  1. Changing the simulation model through programmed changes. An example is a script that initialises a constellation of satellites.
  2. Changing the input or initial property values of model objects. An example is a script that will auto-configure the simulation model.
  3. Automated execution of simulations.

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.

Empty Script

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.

Accessing Model Objects

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:

  1. Traverse simulation graph
  2. Name lookup

Traverse simulation graph

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);
        }
    }

Name lookup

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;

Changing Model Input and Initial Values

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;

Adding or Removing Model Objects

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.

Running Simulations

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.

Halt Condition

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);
}

Per-iteration Update

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);
}

Running Multiple Simulations

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);
}

Output and Reporting

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.