Tutorial Introduction
This tutorial walks the user through how to build a simple script in C++ to propagate a simple orbit. For teaching purposes, it does not take advantage of any simplifications.
To build a simple script, the following steps are needed, in order of execution:
-
Create the simulation executive and configure the simulation
-
Create any bodies and frames used in the simulation which are not created by a model
-
Create models and connect signals
-
Set up logging
-
Call executive startup
-
Initialize frame states
-
Call simulation run
NOTE: This tutorial explains the code — for a fully working script with includes and other necessary items for compilation, look at the example location below
Example code for this tutorial can be found in: modelspace/cpp/scripts/tutorials/simple_gravity/script.cpp
The following describes the code to be executed, in order.
Creating the Simulation
To create a simulation, we construct the main function (or any function) which receives an argc and argv — these are used to initialize the simulation arguments. For non-C++ folks, we don’t need to worry too much about what they do. ModelSpace handles that for us.
int main(int argc, char **argv) {
// Create our simulation executive
SimulationExecutive exc; // Create our executive -- by convention named exc
exc.parseArgs(argc, argv);
exc.setRateHz(1); // We can setRateHz or setRateSec -- default is 1 second
}
Creating bodies and frames
Next, we create any bodies and frames that will be propagated in the simulation. Bodies and frames are described in more detail in another tutorial, but for now we need to know that frames contain attitude and translational information. Any frame which is the child of the simulation’s root frame will also be propagated for free. For this tutorial case, we just need a spacecraft body.
In the line of code here, we are creating a variable spacecraft_body, giving it the same name in the first argument, and giving it a parent frame (which is almost always the root frame) with the second.
// Now create our frames, bodies, and nodes
// By default, the simulation comes with only a root frame, which is owned by the
// simulation executive. From that, we can create any number of frames, bodies, and
// nodes for planets, bodies, etc.
// For this VERY simple example, we will treat the root frame as if it is our planet
// frame for the purposes of gravity, so we only need a spacecraft body and node to
// apply forces
BodyD spacecraft_body("spacecraft_body", exc.rootFrame()); // Make our body a child of root frame
Create Models
Next, we create our models. For this simple example, we just need two models — a frame state sensor to sense the spacecraft wrt our planet, and a gravity model to produce forces on the body. Note the use of the mapTo function maps the inputs of one model to the outputs of another. This connection is lightweight, easy, and simple.
// Now, create our frame state sensor -- we'll use this model to evaluate
// where our spacecraft is at
FrameStateSensorModel fs_model(exc, ALL, "fs_model"); // Create our frame state sensor model
fs_model.params.target_frame_ptr(&spacecraft_body); // Set our target sensed frame as spacecraft
fs_model.params.reference_frame_ptr(exc.rootFrame()); // Set our reference frame as root
// First instantiate our gravity model with appropriate parameters
PointMassGravityModel gravity_model(exc);
gravity_model.inputs.pos_body__f.mapTo(fs_model.outputs.pos_tgt_ref__ref);
NodeD gravity_node("gravity_node", &spacecraft_body); // Create a node to apply our gravity force
gravity_node.force.mapTo(gravity_model.outputs.grav_force__f);
Set up logging
Next, we set up our logging. Logging involves the creation of a logger — these can be CSV or HDF5. After the logger is created, parameters are added. Parameters can be either added by the string address of the model signal or the signal directly. Both examples are included below in this tutorial. Once all parameters are set, the logger should be added to the executive.
In this example, the first argument is the variable to be added to logging, and the second is the name the variable will be added under.
In the addLog command, we’re adding sc_position_log at a rate of 1 Hz (log every second)
// Set up our logging. Here we'll create a simple CSV logger to record our
// time and spacecraft state in the root frame
CsvLogger sc_position_log(exc, "sc_state.csv"); // Create our CSV logger to file sc_state.csv
sc_position_log.addParameter(exc.time()->base_time, "time"); // Add time as a logged parameter
sc_position_log.addParameter(fs_model.outputs.pos_tgt_ref__ref, "sc_pos"); // Add our spacecraft position via direct ref
sc_position_log.addParameter(".exc.fs_model.outputs.vel_tgt_ref__ref", "sc_vel");// Add our spacecraft velocity via string address
exc.logManager()->addLog(sc_position_log, 1); // Add our sc log and set it to run at a rate of 1 Hz
Calling Simulation Startup
The next step in the simulation build is to call startup. The startup step initializes each model, in order, as well as initializing the simulation integrator.
// Call startup on our executive. This will initialize our exec and,
// recursively, all of our models.
exc.startup();
Initializing states
The final step before running our simulation is to initialize each of the frame states. We leave this step for last because it is easier when each of the frames and models has already been initialized.
In this example, we’re setting the body position and velocity according to the root frame. It’s also possible to set attitude and angular velocity.
// Set the initial state of our spacecraft
// Note: by default, all frame objects (including bodies and nodes) are initialized
// at 0, 0, 0 -- we want our gravity node at the CG of our body, so no action necessary
// Initialize our body to circular orbit at 400 km altitude
CartesianVector3D body_position__root({6778000.0, 0.0, 0.0}); // All units are in meters
CartesianVector3D body_velocity__root({0.0, 7668.64, 0.0}); // All units are in m/s
spacecraft_body.setRootRelPosition(body_position__root);
spacecraft_body.setRootRelVelocity(body_velocity__root);
Running the Sim
Our final step is to run the simulation. Calling run() will automatically run the sim until the end time is reached. Alternatively, the sim can be manually stepped with step() until end time is reached.
// Now run our simulation by calling our sim.run. This will automatically
// terminate when our sim reaches its end time
exc.run();