Skip to content

My First HSF Project

Eric Mehiel edited this page May 15, 2024 · 39 revisions

A simple HSF project with one asset and one subsystem

In this case, we are going to build a simple model of a spacecraft in low Earth orbit (LEO) with one subsystem. The spacecraft will have an Earth observing mission and the subsystem will be a camera. We will have a hand full of targets we want to observe as the spacecraft is in orbit. We will also rely on the orbital propagator built into HSF for the time being.

To create a HSF simulation there are several key elements that must be defined:

  1. The Simulation Parameters
  2. The Task Deck
  3. The System Model

Setting up the Simulation Scenario

All input files to HSF are written using JSON. JSON data comes in key-value pairs. Some key-value pair are required, some are optional, and some are defined by the user for use in the simulation. The first required JSON document is one that describes the basics of the simulation scenario. The scenario JSON document requires two key-value pairs with a few attributes. They are:

{
"name": "Aeolus",
"version": 1.0,
"simulationParameters": {
    "startJD": 2459295.0,
    "startSeconds": 0.0,
    "endSeconds": 5504.0,
    "stepSeconds": 15
  },
  "schedulerParameters": {
    "maxSchedules": 10,
    "cropTo": 5
  }
}

With the simulation JSON document, you can define the name and version of the simulation scenario. These values are defined by the user to help uniquely describe the simulation. The simulationParameters and scheduleParamters keys are used to define important attributes of the simulation.

The simulationParameters contains attributes that define the time span of the simulation. The time span is defined by the start of the simulation startJD the start of the simulation given as a Julian Date. The start of the simulation startSeconds in seconds relative to the startJD (by default, this is 0.0 seconds). The end of the simulation endSeconds in seconds relative to the simStartJD attribute. The last attribute of the simulationParameters tag, stepSeconds sets the fundamental simulation time step. The HSF scheduler is implemented using a for loop that starts at startSeconds, ends at endSeconds, with a step size of stepSeconds. While this is the fundamental time step driving the simulation, please note that the subsystems you model can determine their own state at any time.

In this case, the startJD is set to March 21st, 2021, at noon, UTC. This start time is consumed by all environmental models in HSF. The startJD may or may not be used by these models, but all output is relative to this date/time.

The startSeconds is set to 0.0 seconds, and the endSeconds is set to 5504 seconds since this approximately one orbit for our system in LEO (more on defining these parameters later).

The final key-value of the scenario JSON document is the scheduleParameters key. As described in the user guide, HSF uses a breadth-first search algorithm to build potential schedules. Since the number of possible schedules can grow quickly, the user can set the maximum number of schedules that will be carried to the next time step, and the number of schedules that HSF will track if the maximum number of schedules is reached. In this case, maxSchedules is set to 10 schedules, and cropTo is set to 5 schedules. In this example, at the end of one simulation time step, if there are more than 10 possible schedules, HSF will crop the number of possible schedules to 5 schedules. If there are only eight possible schedules, HSF will carry all eight to the next simulation time step.

Build a JSON file with the above key-values named myFirstHSFScenario.json. Save the file in a folder containing the HSF executable. Normally, you would save this input file in the Samples/myFirstHSFProject folder. If you are working in Visual Studio, the Horizon executable will be in either ..\Horizon\bin\Debug, or ..\Horizon\bin\Release. Either folder will work.

Setting up the Task Deck and set of Tasks

In HSF, a schedule is built by adding Events in time sequence. An Event is created when an Assest can perform a Task at a given combination of simulation time and location or Target. Therefore, we need to let HSF know all the possible Tasks the system could perform. This is an important caveat, just because a Target is added to the target deck does not ensure a Task associated with that Target can be scheduled as an Event. We add all these tasks to an XML file known as the Target Deck.

Formally, a Task is defined as, An action to be performed at a target, with limitations and suggestions for scheduling. We'll get into this more in further tutorials, but here a task will be defined as a location (or Target) on the ground we want the satellite to take a picture of. Each task will also have a value that will be used to drive the HSF scheduler.

The HSF Target Deck has the following tags and attributes:

<TARGETDECK>
    <TARGET
	TargetName = "imagetarget1"
	TargetType = "LocationTarget"
	Value = "3"
        TaskType = "IMAGING"
	MaxTimes = "10">
	<DynamicState
	    DynamicStateType = "STATIC_LLA"
	    ICs = "[39.2945; -71.1559; 0]">
	</DynamicState>
    </TARGET>
</TARGETDECK>

In this case, there is only one target in the target deck, but we could simply add more targets to the target deck if we wanted. A Target requires the TargetName, TargetType, Value attributes, and the tag. The TargetName is for convivence, the TargetType is defined be the user and can be used by the subsystem models (more in next section), and Value is used by the Schedule Evaluator as part of the breadth first schedule building process. The tag tells HSF about how the target can (or cannot) move around. In this case, the DynamicStateType is set to STATIC_LLA meaning the target does not move (static) and is defined using components of Latitude, Longitude, and Altitude. The initial conditions for the target are defined by the tag. Since the target is static, it will start and stay at the initial conditions for all time. There are several other ways to define the tag.

The tag attributes of TaskType and MaxTimes are consumed by the Task class. The TaskType is similar to TargetType and can be used by the subsystem models to determine what the subsystem should do. The MaxTimes attribute is there incase a task can only be performed a maximum number of times. This is an older attribute of HSF and may become depreciated in future releases.

Create a Target Deck in the same folder as the Scenario file named myFirstHSFTargetDeck.xml. Put 10 targets in the target deck with the following attributes:

TargetName TargetType Value TaskType MaxTimes DynamicStateType ICs
Image1 LocationTarget 3 IMAGING 10 STATIC_LLA [1; 2; 0]
Image2 LocationTarget 2 IMAGING 10 STATIC_LLA [2; 4; 0]
Image3 LocationTarget 1 IMAGING 10 STATIC_LLA [3; 6; 0]
Image4 LocationTarget 13 IMAGING 10 STATIC_LLA [4; 8; 0]
Image5 LocationTarget 10 IMAGING 10 STATIC_LLA [5; 10; 0]
Image6 LocationTarget 7 IMAGING 10 STATIC_LLA [6; 12; 0]
Image7 LocationTarget 8 IMAGING 10 STATIC_LLA [7; 14; 0]
Image8 LocationTarget 5 IMAGING 10 STATIC_LLA [8; 16; 0]
Image9 LocationTarget 9 IMAGING 10 STATIC_LLA [9; 18; 0]
Image10 LocationTarget 14 IMAGING 10 STATIC_LLA [10; 20; 0]

Setting up the System Model

Finally, we need to create a model file that is used to determine if a subsystem can perform a task. In this tutorial, the system is a simple spacecraft is orbit taking pictures of the targets, but in general, the model you create can be far more complicated and is highly customizable by using python scripts. We will start simple and add more capability in additional tutorials.

HSF system files contain the following minimum XML tags and attributes:

<MODEL>
    <ASSET assetName="Asset1">
        <DynamicState
	    DynamicStateType="PREDETERMINED_ECI"
	    ICs="[7378.137; 0.0; 0.0; 0.0; 6.02088; 4.215866]">
            <EOMS EOMSType="orbital_EOMS"></EOMS>
        </DynamicState>
        <SUBSYSTEM
	    subsystemName = "Camera"
	    type="scripted"
            src="Camera.py"
	    className="Camera">
            <PARAMETER
                name="imageCaptureTime" 
                type="double" 
                value="3" />
            <STATE
                type="Matrix"
                key="ECI_Pointing_Vector(XYZ)"
                value="[0.0; 0.0; 0.0]"
                name="POINTVEC_KEY" />
        </SUBSYSTEM>
    </ASSET>
</MODEL>

The first tag, lets HSF know we are defining a model. The second tag, lets HSF know we are building a new Asset. In it's basic form, and Asset is a collection of tags with a tag. Another way to say it, is that an Asset is the spacecraft with a location defined over time.

The tag is used again in the Asset tag to let HSF know how the Asset moves around. In this case, the asset is of DynamicStateType="PREDETERMINED_ECI". This means the position and velocity of the asset are in units of (km) and (km/s) relative to the Earth Centered Inertial (ECI) reference frame. This is a common set of units and reference frame used for spacecraft in orbit. We also need to supply the initial conditions of the spacecraft in the appropriate units. It is important to notice here that order is not important, but you will need to be consistent throughout the simulation and in all your python code. In this case, the ICs are given as the x-, y-, and z-components of position and velocity, respectfully.

When an asset (or any other object that has a DynamicStateType that is not static) you need to supply an Equations of Motion or EOM tag. The tag then needs to tell HSF where the Equations of Motion are defined. In this case, we are going to use the Orbital_EOMS defined within the HSF framework (a simple restricted 2-body gravity model). In future tutorials, we will modify this to include the J2 perturbations in python code. Regardless, the EOMs are integrated in time for HSF to know the position and velocity (the dynamic state) of the Asset using a adaptive 4th order Runge-Kutta solver.

Finally, each Asset can have one or more tags. These tags are highly customizable and can point to various places within the HSF framework or to external python code. The subsystem is named with the subsystemName attribute for convivence. In this case, the attribute Type is set to scripted meaning the state of the subsystem is determined by a python script. Hence we need the python script location (the src attribute) and the name of the python class. In most cases, this will be the same as the subsystemName, but they can be different if needed.

Within a we define two tags, the and the . The tag is used to define any variables the system model uses as part of determining it's own state. As such, we use the tag to define the initial conditions of any state we want to update and track with the system model. Here we are going define the time it takes to capture an image with the camera (imageCaptureTime), and track the pointing vector from the spacecraft to the target at the start of the task. Subsystem parameters need a name, a type, and a value.

We need to supply the type of state variable we are defining (here the pointing vector is of type Matrix) and name and initial value of the state as a Key-Value pair. Here we use "ECI_Pointing_Vector(XYZ)" as the name, and [0, 0, 0] as the initial value. The name of the state variable only needs to be a valid string. The name attribute of the tag is the variable name used in Camera.py script to access the value of the state variable. The value of name needs to be a string that is a valid Python variable name. How this state variable is used is discussed below.

Create a HSF system model in the same folder as the HSF executable named myFirstHSFSystem.xml. You are almost ready to run your first simulation!

Define the Subsystem Model

The last task to complete before you can run HSF is to write the python script that tells HSF how the Camera subsystem works. This is one of the powerful attributes of HSF. The HSF scheduling framework knows nothing about the inner details of the system you are modeling. The scheduling framework only needs to know if a proposed task (target at time combination) can be performed by the system model you define. As such, your job in defining the camera subsystem is to tell the HSF scheduler true or false based on the task and the current state of the spacecraft. Simple, huh?

The HSF repo contains a subsystem python template with all the functions that need to be overridden. In most cases, we can return the baseclass method (from the C# code) but in this case, are going to add a small amount of functionality to the python CanPerform method. This is the method that is called by the HSF schduler to ask if the system can perform the task. Get it, CanPerform. Cute huh?

The python subsystem template looks like the following:

import sys
import clr
import System.Collections.Generic
import System
clr.AddReference('System.Core')
clr.AddReference('IronPython')
clr.AddReference('System.Xml')
clr.AddReferenceByName('Utilities')
clr.AddReferenceByName('HSFUniverse')
clr.AddReferenceByName('UserModel')
clr.AddReferenceByName('MissionElements')
clr.AddReferenceByName('HSFSystem')

import System.Xml
import HSFSystem
import MissionElements
import Utilities
import HSFUniverse
import UserModel
from HSFSystem import *
from System.Xml import XmlNode
from Utilities import *
from HSFUniverse import *
from UserModel import *
from MissionElements import *
from System import Func, Delegate
from System.Collections.Generic import Dictionary
from IronPython.Compiler import CallTarget0

class GenericSubsystem(HSFSystem.Subsystem):

    def CanPerform(self, event, universe):
        return super(GenericSubsystem, self).CanPerform(event, universe)

    def CanExtend(self, event, universe, extendTo):
        return super(GenericSubsystem, self).CanExtend(event, universe, extendTo)

    def DependencyCollector(self, currentEvent):
        return super(GenericSubsystem, self).DependencyCollector(currentEvent)

    def GetDependencyDictionary(self):
        dep = Dictionary[str, Delegate]()
        return dep

    def GetDependencyCollector(self):
        return Func[Event,  Utilities.HSFProfile[System.Double](self.DependencyCollector)

The first dozen or so lines obviously import various python and HSF libraries. In future versions, the HSF team plans to create a single HSF Python module that will be required to run Subsystem models. The important part of the template starts with the class definition for the GenericSubsystem. The python subsystem is instantiated as a python instance of a C# subsystem base class. Hence, the default class to super.

For this project, we want the camera to record the pointing vector to the target at the start of the task. To do this, do the following:

  1. Change the name of the class to Camera this name needs to match the name given in the tag. To make this change complete, you also need to change the call to GenericSubsystem in the CanExtend and DependencyCollector methods.
  2. No Python constructor is needed since all properties of the subsystem are handled by the HSF framework, including the state variables and subsystem parameters discussed above. In HSF, state variables are stored as <key, value> pairs.
  3. Now, define the functionality of the Camera subsystem. In the CanPerform method, replace the call to super with the following lines of code:
ts = event.GetTaskStart(self.Asset)
        
position = self.Asset.AssetDynamicState
scPositionECI = position.PositionECI(ts)
targetPositionECI = event.GetAssetTask(self.Asset).Target.DynamicState.PositionECI(ts)
pointingVectorECI = targetPositionECI - scPositionECI

event.State.AddValue(self.POINTVEC_KEY, ts, pointingVectorECI)
event.SetTaskStart(self.Asset, ts)
event.SetTaskEnd(self.Asset, ts + self.imageCaptureTime)

return True

Save the file as Camera.py in the same folder as the HSF executable.

Run HSF

You are now ready to run HSF and gather output data. To run HSF, do the following:

  1. Open a Command Prompt using the cmd command in Windows.
  2. Navigate to the folder where the Horizon.exe file is located along with the input files and python scripts.
  3. In the Command Prompt, type Horizon -s myFirstHSFScenario.xml -t myFirstHSFTargetDeck.xml -m myFirstHSFSystem.xml -subpath samples\myFirstHSFProject\ This command will run Horizon with the scenario (-s), target deck (-t), and model (-m) files we created.

Your schedule information is now available as a text file at C:\HorizonLog and will be named with the format output-date. Your state data is now written out to the file asset1_eci_pointing_vector_xyz.csv_ at C:\HorizonLog\Scratch.

Next Steps

In the next tutorials we will add a constraint to the pointing vector angle. Then we will add a data buffer subsystem and create a dependency between the camera and data buffer subsystems. Hold tight!