A model object in D2S2 is an instance of a class, where that class confirms to a specific type or interface, as required by the parent model object to which it is assigned.
A user-coded model can inherit from any of the existing classes in the D2S2.Model namespace, to override or extend the functionality of it.
The main code parts of a custom model class is:
The most common use of custom model development is to implement simulation models for custom payloads or satellite components where there is no built-in equivalent or additional functionality is needed. A template structure of a new satellite component can be seen below. The generic Electrical Power System is used as an example.
namespace D2S2.Model.Satellite
{
public class ElectricalPowerSystem : SatellitePhysicalComponent, IInitialisable
{
// private members
// properties for dependent object binding
// read-only properties for exposing model state
// adjustable/input properties
public ElectricalPowerSystem()
{}
public void Initialise(DateTime time)
{}
public override Vector3d GetForce(SatelliteEnvironmentState environment)
{return Vector3d.Zero;}
public override Vector3d GetTorque(SatelliteEnvironmentState environment)
{return Vector3d.Zero;}
public override void UpdateEnvironment(DateTime time, SatelliteEnvironmentState environment, SatelliteKinematicState state, SurfaceModel surfaceModel)
{}
public override void UpdateEnd(TimeSpan timeDelta)
{}
}
}
D2S2 uses types to define which model objects may be used at various dependency points in the model. It is thus required to either inherit from the appropriate base class, or to implement a required interface. In the example above the Electrical Power System inherits from SatellitePhysicalComponent
which is the class that contains the fundamental physical parameters for a component that can be attached to a satellite. This is suitable for majority of custom components.
Other options are SatelliteElectricalComponent
which is an extention of the SatellitePhysicalComponent
which requires the custom component to also implement functions that define its power usage.
Similar to any class, private variables are used to store state or settings internal to the model. The member variables needed by the example EPS class is
private double powerIn = 0.0;
private double powerOut = 0.0;
private double currentBatteryPower = 0.0;
private double initialBatteryPower = 0.0;
private double maximumPowerStorage = 22.5;
private double powUsage = 0;
Model objects can be linked to one another creating a dependency. In the example there are two dependency lists:
private ModelList<D2S2.Model.Satellite.SolarPanel> solarPanels;
private ModelList<D2S2.Model.Satellite.SatelliteElectricalComponent> consumers;
[ModelDependency(DependencyType.Link, false)]
[Description("List of all power generating components")]
public ModelList<D2S2.Model.Satellite.SolarPanel> SolarPanels { get { return solarPanels; } }
[ModelDependency(DependencyType.Link, false)]
[Description("List of all power consuming components")]
public ModelList<D2S2.Model.Satellite.SatelliteElectricalComponent> PowerConsumers { get { return consumers; } }
One of the defined dependencies is for a list of all solar panels in the simulation and another of all components that will use power from the Electrical Power System. This is done by creating a private member list of a certain class. These dependencies can then be used to gain access to change the state or gain information from other models in the simulation. Dependencies are indicated in the scenario files.
The SolarPanels
and PowerConsumers
dependencies above are examples of Link type dependencies. It is allowed to assign existing model objects to these dependencies, but the EPS does not 'own' these dependent objects. Owner
type dependencies require that a new simulation object is created prior to assigning it. Removing an Owner
type dependent object will also delete it. The type of dependency, Link
or Owner
is spcified using the ModelDependency
attribute. The second argument to this attribute is a boolean parameter indicating if the dependency must be assigned before the model may be run (it will result in a model validation error otherwise).
Each model can have multiple public parameters. These are the values that can be changed or read by the user interface. These are created by defining a public property with at least get access:
[ModelValue(ModelValueType.State)]
[DisplayName("Generated Power")]
[Description("Total power generated [W]")]
[Category("Measurement")]
public UDouble<W> PowerInUnit { get { return new UDouble<W>(powerIn); } }
[ModelValue(ModelValueType.Initial)]
[DisplayName("Maximum Energy Storage")]
[Category("Battery Settings")]
[Description("Maximum energy storage capacity of batteries [W.hr]")]
public double MaximumPowerStorage
{
get
{
return maximumPowerStorage;
}
set
{
maximumPowerStorage = value;
OnPropertyValueChanged(GetProperty((ElectricalPowerSystem m) => m.MaximumPowerStorage));
}
}
The ModelValue
attribute defines the type of property. ModelValueType
can be one of the following:
Initial
- these are properties that can be set by the user, or other model objects, to change the model behaviour. Initial properties can only be changed if the simulation is in reset state.Input
- these are properties that can be set by the user, or other model objects, to change the model behaviour. Input properties can be changed while the simulation is already underway.State
- these are read-only properties (get access only) that report the state of the modelStatistic
- these are read-only properties (get access only) that report the state of the model. This is typically used for states where only the final value is important.Properties may be decorated using DisplayName
and Description
attributes. The display name and descrption defined here will be presented to the user in the UI, and can be used for user-friendly property names.
The DisplayName
and Description
attributes can also be applied to the model class itself.
The Initialise
function is a required function from the IInitialisable base interface and can be used to initialise the internal state of the model..
The base SatellitePhysicalComponent
class has virtual methods for GetForce()
and GetTorque()
. The values that are returned by these methods will influence the attitude and orbit dynamics of the satellite. Custom models that inherit from SatellitePhysicalComponent
can override these methods to simulate the spacecraft dynamics effect that payloads may have.
The logic or custom function that the model object must fulfil is usually contained in an Update method. In the case of the SatelliteComponent
base class, there are three such methods which may be overridden in inheriting classes.
void UpdateEnvironment(DateTime time, SatelliteEnvironmentState environment, SatelliteKinematicState state, SurfaceModel surfaceModel)
void Update(TimeSpan timeDelta)
void UpdateEnd(TimeSpan timeDelta)
All components in the satellite will have their UpdateEnvironment
method called initially. Following this, the Update
method for all components will be called, and finally all components' UpdateEnd
method will be called. The sequence makes it useful to control the order of actions, especially where satellite components interact with each other and the order in which component update methods are called are not guaranteed. It is up to the developer to best decide how to use these methods for custom logic.
The complete code of the generic ElectricalPowerSystem
can be found here.