| [Home](/trick) → [Tutorial Home](Tutorial) → Analytical Cannon Simulation | |-------------------------------------------------------------------------| # Building & Running a Trick-based Simulation **Contents** * [Organizing the Simulation Code in Directories](#organizing-the-simulation-code-in-directories)
* [Representing the Cannonball](#representing-the-cannonball)
- [Listing 2 : **cannon.h**](#listing_2_cannon_h)
- [The Input/Output (I/O) Specification](#the-input_output-io-specification)
- [Units Specification](#units-specification)
* [Initializing the Cannonball Simulation](#initializing-the-cannonball-simulation)
- [Listing 3 : **cannon_init.c**](#listing_3_cannon_init_c)
* [Updating the Cannonball State Over Time](#updating-the-cannonball-state-over-time)
- [listing 4 : **cannon\_analytic.h**](#listing_4_cannon_analytic_h)
- [listing 5 : **cannon\_analytic.c**](#listing_5_cannon_analytic_c)
* [Cannonball Cleanup And Shutdown](#cannonball_cleanup_and_shutdown) - [listing 6 : **cannon\_shutdown.c**](#listing_6_cannon_shutdown_c)
* [The Simulation Definition File (S_define)](#simulation-definition-file)
- [listing 7 : **S_define**](#listing_7_s_define)
* [Compiling, and Building the Simulation](#compiling-and-building-the-simulation)
- [Listing 8 : **S_overrides.mk**](#listing_8_s_overrides.mk) * [Running the Simulation](#running-the-simulation)
- [Listing 9 : **input.py**](#listing_9_input_py) *** In this and subsequent sections, we're going to build and run a Trick-based cannonball simulation. --- ### Organizing the Simulation Code in Directories We'll begin by creating a directory system to hold our simulation source code: ```bash % cd $HOME % mkdir -p trick_sims/SIM_cannon_analytic % mkdir -p trick_sims/SIM_cannon_analytic/models/cannon/src % mkdir -p trick_sims/SIM_cannon_analytic/models/cannon/include ``` --- ### Representing the Cannonball To represent the cannonball model, we need to create a header file (**cannon.h**) that will contain: * A CANNON structure to hold the state of the cannonball, and * Prototypes for cannonball functions The CANNON data-type contains the cannonball's initial conditions, its acceleration, velocity, and position, the model time, whether the cannonball has impacted the ground, and the time of impact. The prototypes will declare two functions for initializing our CANNON data-type. We'll discuss these in the next section. **Listing 2 - `cannon.h`** ```c /************************************************************************* PURPOSE: (Represent the state and initial conditions of a cannonball) **************************************************************************/ #ifndef CANNON_H #define CANNON_H typedef struct { double vel0[2] ; /* *i m Init velocity of cannonball */ double pos0[2] ; /* *i m Init position of cannonball */ double init_speed ; /* *i m/s Init barrel speed */ double init_angle ; /* *i rad Angle of cannon */ double acc[2] ; /* m/s2 xy-acceleration */ double vel[2] ; /* m/s xy-velocity */ double pos[2] ; /* m xy-position */ double time; /* s Model time */ int impact ; /* -- Has impact occured? */ double impactTime; /* s Time of Impact */ } CANNON ; #ifdef __cplusplus extern "C" { #endif int cannon_default_data(CANNON*) ; int cannon_init(CANNON*) ; int cannon_shutdown(CANNON*) ; #ifdef __cplusplus } #endif #endif ``` #### Creating The `cannon.h` Header File Using your favorite text editor, create and save the file `cannon.h` from **Listing 2**. We will assume from this point that your favorite text editor is **vi**. So when you see **vi** following the %, just replace it with **emacs**, **nedit**, **jot**, **wordpad**, **kate**, **bbedit**, or whatever you like. ```bash % cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/include % vi cannon.h ``` Type, or cut and paste the contents of **Listing 2** and save. ### Deciphering The Trick Comments In The Header File In the file above, note the comments at the top, and to the right of each structure member. These are specially formatted comments that are parsed by Trick. The comment at the top of the file, containing the keyword `PURPOSE:` (the colon is part of the keyword) is called a "Trick header". The presence of a Trick header lets Trick know that it should scan the file to collect information about the data types it contains. The full Trick header syntax will be detailed later, for now, `PURPOSE:` is all that is necessary. To the right of each structure member is a comment that (optionally) provides the following information for the member: 1. Input/Output Specification 2. Units Specification 3. Description These are each described in **Figure 2** and in the sections below. ![DataMemberComments](images/DataMemberComments.png) **Figure 2 - Data Member Comments** --- #### The Input/Output (I/O) Specification An I/O specification is an optional field that specifies data flow direction for a variable. The default, `*io` , specifies that both input and output are allowed. * `*i` tells Trick that the parameter will be input only. * `*o` tells Trick that the parameter is output only. * `*io` is the default and means the parameter may be used for input or output. * `**` tells Trick that you do NOT want this parameter processed. --- #### Comment Field The comment field is extracted and used in GUI tools to describe variables. --- #### Units Specification A unit specification indicates the units of measure for a variable. For example, in the figure above, (m/s) indicates that `init_speed` is a measure of meters per second. Unit specs allow unit conversions to be performed by the Trick input file processor, Trick View and plotting tools. As of Version 17.0, Trick uses [UDUNITS2](http://www.unidata.ucar.edu/software/udunits/), an Open Source unit conversion package, developed at http://www.unidata.ucar.edu. It is similar in many respects to Trick's previous unit conversion package, but, frankly it's a lot better. Like the previous Trick unit conversion package, UDUNITS2 supports unit-prefixes (for example: kilo, micro, etc.) as well as unit-composition, the ability to compose unit specifications from previously defined unit specifications (for example: m/s, kg.m/s^2). Unlike the previous unit conversion, its units database is much more substantial, it's more extensible, its design is more capable, and it supports Unicode characters in unit specifications. Below, we are going to see how to specify commonly needed unit specifications for our Trick simulations. But, we are not going to describe the full capability of UDUNITS2 package. In order to see **ALL** available unit definitions, one would need to look at the UDUNITS2 xml files that comprise the units database. Rather than requiring that, the [Common Units & Unit Prefixes](ATutUnitTables) page lists optional prefixes, and many of the most commonly used units in simulations at the Johnson Space Center Engineering Branch. #### Composite Units (Making Units From Existing Units) Often, units are composed of other predefined units as in the following unit specification examples: * **m/s** (meters per second, speed) * **m/s^2** (meters per second squared, acceleration) * **kg.m/s^2** (Newtons, force) * **m^3** (cubic meters, volume) Note the operators `/` (division), `.`(multiplication), `^2`(square), and `^3`(cube) for composing unit specs. #### Scaling Units With Unit Prefixes Unit prefixes, listed in the table `Unit Prefixes`, below can also be prepended to unit specifications, as in the following examples: * **k**m, **kilo**meters * **M**W, **mega**watts #### Unicode Characters in Units Some units and unit-prefixes can also be represented using unicode characters. For example: * **^2** can instead be represented as **²** (Unicode char U+00B2). * **^3** can instead be represented as **³** (Unicode char U+00B3). * The prefix **micro**, or **u** can be represented as **μ** (Unicode char U+03BC). * The unit **ohms** can be represented as **Ω** (Unicode char U+2126 or U+03A9). So, one could specify **m/s²** rather than **m/s^2**, or **m³** rather than **m^3**, or **μm** rather than **micrometers**. The table below lists Unicode characters that can be used in units specifications. ### Unicode Characters Used in Units Specifications | Character | Unicode Number | Unicode Name | |-----------|----------------| ----------------------| | ° | U+00B0 | Degree Sign | | ² | U+00B2 | Superscript Two | | ³ | U+00B3 | Superscript Three | | Ω | U+03A9 | Greek Capital Letter Omega | | μ | U+03BC | Greek Small Letter Mu | | π | U+03C0 | Greek Small Letter Pi | | ′ | U+2032 | Prime | | ″ | U+2033 | Double Prime | | ℃ | U+2103 | Degree Celsius | | ℉ | U+2109 | Degree Fahrenheit | | Ω | U+2126 | Ohm Sign | | K | U+212A | Kelvin Sign | | Å | U+212B | Angstrom Sign | #### Specifying "No Units" In Trick, a unit specification of "--" means *unit-less*. If your variable doesn't have units, use "--" as the unit specification. --- ### Initializing the Cannonball Simulation The Trickless simulation performed a two-part initialization of the simulation variables. The first part assigned default values to the simulation parameters. The second part performed calculations necessary to initialize the remaining simulation variables. Trick based simulations perform a three-part initialization of simulation variables. The first part runs "**default-data**" jobs, that is, it calls one or more user-provided C functions, whose purpose is to set default values for the simulation's variables. In the second initialization step, Trick executes the simulation's Python "**input file**". Variable assignments can be made in the input file. If a parameter value isn't set in the input file, its default value is used. In the third and final initialization step, Trick runs "**initialization**" jobs. These perform any final initialization calculations, needed prior to running the sim. The two functions in the listing below will serve as the default-data and initialization jobs for our cannonball simulation. These are the functions for which we created the prototypes in the cannon.h header file. We'll create the python input file in a later section. **Listing 3 - `cannon_init.c`** ```c /******************************* TRICK HEADER **************************** PURPOSE: (Set the initial data values) *************************************************************************/ /* Model Include files */ #include #include "../include/cannon.h" /* default data job */ int cannon_default_data( CANNON* C ) { C->acc[0] = 0.0; C->acc[1] = -9.81; C->init_angle = M_PI/6 ; C->init_speed = 50.0 ; C->pos0[0] = 0.0 ; C->pos0[1] = 0.0 ; C->time = 0.0 ; C->impact = 0 ; C->impactTime = 0.0 ; return 0 ; } /* initialization job */ int cannon_init( CANNON* C) { C->vel0[0] = C->init_speed*cos(C->init_angle); C->vel0[1] = C->init_speed*sin(C->init_angle); C->vel[0] = C->vel0[0] ; C->vel[1] = C->vel0[1] ; C->impactTime = 0.0; C->impact = 0.0; return 0 ; } ``` Some important things to note: * These are just C functions. Trick will have them compiled, and linked into the simulation executable. * Both functions' arguments have a pointer to the CANNON data-type which was defined in `cannon.h`. * Both functions return an **int**. Returning 0 indicates success. Non-zero indicates failure. The return values can optionally be used (by setting trick\_ret in the S\_define) to terminate the simulation. ```bash % cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/src % vi cannon_init.c ``` Type in the contents of **Listing 3** and save. ### Updating The Cannonball State Over Time Trick's job scheduler provides a **"Scheduled"** job type for periodically calling functions when the sim is in RUN (cyclic) mode. In the case of our cannonball simulation, where there is an analytical solution, we can calculate the the cannonball state by evaluating a function at each time step. **Listing 4 - `cannon_analytic.h`** ```c /************************************************************************* PURPOSE: ( Cannon Analytic Model ) **************************************************************************/ #ifndef CANNON_ANALYTIC_H #define CANNON_ANALYTIC_H #include "cannon.h" #ifdef __cplusplus extern "C" { #endif int cannon_analytic(CANNON*) ; #ifdef __cplusplus } #endif #endif ``` ```bash % cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/include % vi cannon_analytic.h ``` Type in the contents of **Listing 4** and save. **Listing 5 - `cannon_analytic.c`** ```c /***************************************************************************** PURPOSE: ( Analytical Cannon ) *****************************************************************************/ #include #include #include "../include/cannon_analytic.h" int cannon_analytic( CANNON* C ) { C->acc[0] = 0.00; C->acc[1] = -9.81 ; C->vel[0] = C->vel0[0] + C->acc[0] * C->time ; C->vel[1] = C->vel0[1] + C->acc[1] * C->time ; C->pos[0] = C->pos0[0] + (C->vel0[0] + (0.5) * C->acc[0] * C->time) * C->time ; C->pos[1] = C->pos0[1] + (C->vel0[1] + (0.5) * C->acc[1] * C->time) * C->time ; if (C->pos[1] < 0.0) { C->impactTime = (- C->vel0[1] - sqrt( C->vel0[1] * C->vel0[1] - 2 * C->pos0[1]))/C->acc[1]; C->pos[0] = C->impactTime * C->vel0[0]; C->pos[1] = 0.0; C->vel[0] = 0.0; C->vel[1] = 0.0; if ( !C->impact ) { C->impact = 1; fprintf(stderr, "\n\nIMPACT: t = %.9f, pos[0] = %.9f\n\n", C->impactTime, C->pos[0] ) ; } } /* * Increment time by the time delta associated with this job * Note that the 0.01 matches the frequency of this job * as specified in the S_define. */ C->time += 0.01 ; return 0 ; } ``` This routine looks much like the routine found in our Trick-less simulation. It is the piece that was surrounded by a while-loop. Underneath, Trick will surround this job with its own while-loop. As in the case of the `cannon_init()` routine, there is nothing particularly special about `cannon_analytic()`. It is just another C function that will be compiled into an object, and later, linked with a number of libraries to create a simulation executable. ```bash % cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/src % vi cannon_analytic.c ``` Type in the contents of **Listing 5** and save. ### Cannonball Cleanup And Shutdown **shutdown** job types are called by Trick's job scheduler when the simulation ends. These types of jobs are for doing anything that one might want to do at the end of a simulation, like releasing resources, or doing some final result calculation, or maybe just printing a message. In our case we're just going to print the final cannon ball state. **Listing 6 - `cannon_shutdown.c `** ```c /************************************************************************ PURPOSE: (Print the final cannon ball state.) *************************************************************************/ #include #include "../include/cannon.h" #include "trick/exec_proto.h" int cannon_shutdown( CANNON* C) { double t = exec_get_sim_time(); printf( "========================================\n"); printf( " Cannon Ball State at Shutdown \n"); printf( "t = %g\n", t); printf( "pos = [%.9f, %.9f]\n", C->pos[0], C->pos[1]); printf( "vel = [%.9f, %.9f]\n", C->vel[0], C->vel[1]); printf( "========================================\n"); return 0 ; } ``` ```bash % cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/src % vi cannon_shutdown.c ``` ## The Simulation Definition File (S_define) To automate the build process of a Trick based simulation, Trick needs to know user source code locations, data types, functions, variables and scheduling requirements of a simulations models. This starts with the simulation definition file (**S_define**), an example of which, that we will use to define our Cannonball simulation is shown in Listing 7, below. **Listing 7 - `S_define`** ```c++ /************************TRICK HEADER************************* PURPOSE: (This S_define works with the RUN_analytic input file) LIBRARY DEPENDENCIES: ( (cannon/src/cannon_init.c) (cannon/src/cannon_analytic.c) (cannon/src/cannon_shutdown.c) ) *************************************************************/ #include "sim_objects/default_trick_sys.sm" ##include "cannon/include/cannon_analytic.h" class CannonSimObject : public Trick::SimObject { public: CANNON cannon; CannonSimObject() { ("default_data") cannon_default_data( &cannon ) ; ("initialization") cannon_init( &cannon ) ; (0.01, "scheduled") cannon_analytic( &cannon ) ; ("shutdown") cannon_shutdown( &cannon ) ; } } ; CannonSimObject dyn ; ``` The `S_define` file syntax is C++ with a couple of Trick specific constructs. Let us dissect this S_define file to see what makes it tick. ### Trick Header * `PURPOSE:` keyword tells Trick to scan the remainder of the file for data types, variable definitions, job scheduling specifications and compilation unit dependencies. * `LIBRARY_DEPENDENCY: ((cannon/src/cannon_analytic.c) ...` Lists the *compilation units* (the .c source files), upon which this S_define depends. The specified source files are the starting point for the recursive determination of the list of files that need to be compiled and linked to build the simulation. Trick headers that are included within each of these files may specify additional source code dependencies, and so forth. Libraries may also be specified for linking into the final executable. Trick uses your `$TRICK_CFLAGS` environment variable ([see section 3.2 of the Trick User Guide](/trick/documentation/building_a_simulation/Environment-Variables)) in conjunction with `cannon/src` to find the listed files. The entire path name following the `$TRICK_CFLAGS` path must be included. ### Included Files * `#include "sim_objects/default_trick_sys.sm"` This line is mandatory in an S_define. It includes predefined data types, variable definitions, and jobs the that provide standard Trick Simulation functionality. * `##include "cannon/include/cannon_analytic.h"` The S_define must `##include` type definitions for all of the classes and structures that it uses. It also needs to include prototypes for all of the functions that it calls. You may also put the prototypes in the `S_define` block using ([user code blocks](/trick/documentation/building_a_simulation/Simulation-Definition-File#user-code-block)), but if you need to call any of the C functions from the input file then you must include the prototypes in a header file (the preferred method). ### Data Lines `Class CannonSimObject : public Trick::SimObject` The sim object is defined as a C++ class and must be derived from the base class SimObject. * `Class CannonSimObject` The name of the sim_object class is arbitrary. * `public Trick::SimObject` As mentioned above, your sim_object class must be derived from the Trick base class SimObject. `public : CANNON cannon ;` * `CANNON` This is the name of the structure typedef that you created in the cannon.h header. * `cannon` This is an alias for the CANNON structure. It is mandatory. * `CannonSimObject()` This is the constructor of the sim_object and it will contain the job declarations. ### Initialization Job It is custom to refer to the C-functions created by the developer as **jobs**. The statement below tells Trick how to handle the `cannon_init()` job. ("initialization") cannon_init( &cannon) ; * `("initialization")` This assigns `cannon_init()` a job classification of `initialization`. There are many classes of jobs. The job classes, for the most part, determine the order the job is called in the **executive loop**. If there are two jobs of the same class in the `S_define`, the one seen first in the `S_define` is called first. Jobs that are classified `initialization` will be called once before the main executive loop and will not be called again. * `cannon_init(` The name of the function we created in $HOME/trick_sims/models/cannon/src/cannon_init.c. * `&cannon)` This is the actual value passed to cannon_init(). It is the address of the object 'CANNON' structure and "cannon" is the alias for the CANNON structure. ### Default Data Job The default data jobs are called one time before the initialization jobs. `("default_data") cannon_default_data(&cannon) ;` * `("default_data")` This assigns cannon\_default\_data() a job classification of *default_data*. ### Scheduled Job The next job needs to be called at a regular frequency while the cannonball is flying in the air. A *scheduled* class job is one of many jobs that can be called at a given frequency. The only thing new about the declaration for cannon\_analytic is the additional specification of 0.01. `(0.01, "scheduled") cannon_analytic(&cannon) ;` * `(0.01, "scheduled")` The 0.01 specification tells Trick to run this job every 0.01 seconds (starting at time=0.0). "scheduled" is the job classification. ### Create The S\_define ``` % cd $HOME/trick_sims/SIM_cannon_analytic % vi S_define ``` Type in the contents of **Listing 7** and save. ## Compiling, and Building the Simulation The pieces are in order. The simulation is ready to be built! ### Setting `$TRICK_CFLAGS` and `$TRICK_CXXFLAGS` ![TRICK_CFLAGS WARNING](images/Warning_TRICK_CFLAGS.png) Before we continue with the magical building of the cannonball, **PLEASE** take the time to understand this section. It will save you much heartache and time. The environment variables **`$TRICK_CFLAGS`** and **`$TRICK_CXXFLAGS`** are used to provide TRICK, and the compilers that it uses with information that is necessary to build your sim. Most importantly, they will tell TRICK where to find your model files. They also provide a way for you to invoke some very useful compiler options. * `$TRICK_CFLAGS` is used by the C compiler and by the Trick Interface Code Generator. * `$TRICK_CXXFLAGS` is for C++ compiler #### Resolving Relative Paths In the files that we have created so far, the file paths in `#include` directives and in the `LIBRARY_DEPENDENCY` sections, are **relative** paths. These paths are relative to a **base-path**, that we still need to specify. For example, the `S_define` file listed above, `#includes` the relative path: `cannon/include/cannon.h`. We intend for this path to be relative to the `trick_models` directory that we created in our `$HOME` directory. The complete path to our cannon.h header file should be: ![Trick Path Construction](images/TrickPaths.png) So, we need to specify the base-path(s), to the compilers, and to Trick by adding -I*dir* options, that contain the base-paths, to `$TRICK_CFLAGS` and `$TRICK_CXXFLAGS`. The easiest, and most portable way of setting `TRICK_CFLAGS` for your simulation is to create a file named **`S_overrides.mk`** in your simulation directory, and then add the following lines to it: **Listing 8 - `S_overrides.mk`** ```sh TRICK_CFLAGS += -Imodels TRICK_CXXFLAGS += -Imodels ``` When Trick encounters relative paths, these base-paths will be prepended to the relative paths to create a complete path to the file, thus allowing it to be located. #### Additional Compiler Flag Recommendations Some additional compiler flags recommendations are provided in the `.cshrc` and `.profile` snippets below. They tell the compilers to provide debugging support and to check for and warn of possibly dubious code constructs. ##### For Your .profile File ```bash export TRICK_CFLAGS="-g -Wall -Wmissing-prototypes -Wextra -Wshadow" export TRICK_CXXFLAGS="-g -Wall -Wextra -Wshadow" ``` ##### For Your .cshrc File ```bash TRICK_CFLAGS= -g -Wall -Wmissing-prototypes -Wextra -Wshadow TRICK_CXXFLAGS= -g -Wall -Wextra -Wshadow ``` ### trick-CP The source code and environment are set up. The Trick simulation build tool is called **trick-CP** (Trick Configuration Processor). It is responsible for parsing through the S_define, finding structures, functions, and ultimately creating a simulation executable. ``` % cd $HOME/trick_sims/SIM_cannon_analytic % trick-CP ``` If you typed everything perfectly... Trick is installed properly... there are no bugs in the tutorial... the stars are aligned... and Trick is in a good mood... You should, ultimately see : ![Simulation Make Complete](images/SimMakeComplete.png) Now, take a look at the sim directory. Is there an `S_main*.exe` file?? (* is a wildcard, instead of * you will see the name of your platform). If so, cool deal. If not, scream!, then take a look at the next section "Troubleshooting A Bad Build". If all went well, you will notice several other files now resident in the `SIM_cannon_analytic` directory. ```bash % ls Modified_data S_overrides.mk makefile RUN_test S_sie.resource trick.zip S_define S_source.hh S_main_.exe build ``` #### Troubleshooting A Bad Build Here are some common problems. * Trick cannot seem to find a function or structure that you have in your S_define. * Make sure that your TRICK_CFLAGS are set. * You have a misspelling. * In order for Trick to find a job, argument types must match exactly. * Trick barfs when building the simulation * One of your C routines may not compile because of a C syntax error. * Trick was not installed properly on your system. * trick-CP croaks - You may have a syntax error in your S_define. ## Running The Simulation You've done a lot of work to get to this point. You've created a header, a default data job, an initialization job, a scheduled job, and an S_define. You've also had to set up an environment and trudge through trick-CP errors. The tiny Trickless main() program may be looking short-n-sweet at this point! There can't be anything more to do!?! There is one more file to create to get the cannonball out of the barrel and into the air. ### Simulation Input File Every Trick simulation needs an input file. This input file will be simple (only one line). In practice, input files can get ridiculously complex. The input file is processed by Python. There is no need to recompile the simulation after changing the input file. The file is interpreted. **Listing 9 - input.py** ```python trick.stop(5.2) ``` By convention, the input file is placed in a `RUN_*` directory. ```bash % cd $HOME/trick_sims/SIM_cannon_analytic % mkdir RUN_test % cd RUN_test % vi input.py ``` ### Sim Execution To run the simulation, simply execute the `S_main*exe`: ```bash % cd $HOME/trick_sims/SIM_cannon_analytic % ./S_main_*.exe RUN_test/input.py ``` If all is well, something similar to the following sample output will be displayed on the terminal. ``` IMPACT: t = 5.096839959, pos[0] = 220.699644186 ======================================== Cannon Ball State at Shutdown t = 5.2 pos = [220.699644186, 0.000000000] vel = [0.000000000, 0.000000000] ======================================== REALTIME SHUTDOWN STATS: REALTIME TOTAL OVERRUNS: 0 ACTUAL INIT TIME: 0.203 ACTUAL ELAPSED TIME: 12.434 SIMULATION TERMINATED IN PROCESS: 0 ROUTINE: Executive_loop_single_thread.cpp:98 DIAGNOSTIC: Reached termination time SIMULATION START TIME: 0.000 SIMULATION STOP TIME: 5.200 SIMULATION ELAPSED TIME: 5.200 ACTUAL CPU TIME USED: 0.198 SIMULATION / CPU TIME: 26.264 INITIALIZATION CPU TIME: 0.144 ``` We got the same answer! But, what about the trajectory? In the next section, we’ll see how to record our simulation variables to a file, so we can plot them. --- [Next Page](ATutRecordingData)