Home → Tutorial Home → Analytical Cannon Simulation |
---|
Contents
In this and subsequent sections, we're going to build and run a Trick-based cannonball simulation.
We'll begin by creating a directory system to hold our simulation source code:
% 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
To represent the cannonball model, we need to create a header file (cannon.h) that will contain:
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.
/*************************************************************************
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
cannon.h
Header FileUsing 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.
% 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.
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:
These are each described in Figure 2 and in the sections below.
Figure 2 - Data Member Comments
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.The comment field is extracted and used in GUI tools to describe variables.
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, 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 page lists optional prefixes, and many of the most commonly used units in simulations at the Johnson Space Center Engineering Branch.
Often, units are composed of other predefined units as in the following unit specification examples:
Note the operators /
(division), .
(multiplication), ^2
(square), and
^3
(cube) for composing unit specs.
Unit prefixes, listed in the table Unit Prefixes
, below can also be prepended
to unit specifications, as in the following examples:
Some units and unit-prefixes can also be represented using unicode characters.
For example:
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.
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 |
In Trick, a unit specification of "--" means unit-less. If your variable doesn't have units, use "--" as the unit specification.
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.
/******************************* TRICK HEADER ****************************
PURPOSE: (Set the initial data values)
*************************************************************************/
/* Model Include files */
#include <math.h>
#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.
% cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/src
% vi cannon_init.c
Type in the contents of Listing 3 and save.
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.
/*************************************************************************
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
% cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/include
% vi cannon_analytic.h
Type in the contents of Listing 4 and save.
/*****************************************************************************
PURPOSE: ( Analytical Cannon )
*****************************************************************************/
#include <stdio.h>
#include <math.h>
#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.
% cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/src
% vi cannon_analytic.c
Type in the contents of Listing 5 and save.
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.
/************************************************************************
PURPOSE: (Print the final cannon ball state.)
*************************************************************************/
#include <stdio.h>
#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 ;
}
% cd $HOME/trick_sims/SIM_cannon_analytic/models/cannon/src
% vi cannon_shutdown.c
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.
/************************TRICK HEADER*************************
PURPOSE:
(S_define file for SIM_cannon_analytic)
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.
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) in
conjunction with cannon/src
to find the listed files. The entire path
name following the $TRICK_CFLAGS
path must be included.
#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), 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).
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.
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.
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.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.% cd $HOME/trick_sims/SIM_cannon_analytic
% vi S_define
Type in the contents of Listing 7 and save.
The pieces are in order. The simulation is ready to be built!
$TRICK_CFLAGS
and $TRICK_CXXFLAGS
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++ compilerIn 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_analytic.h
. We intend for this path to be relative to the
models
directory that we created in our SIM_cannon_analytic
directory. The complete
path to our cannon.h header file should be:
${HOME}/trick_sims/SIM_cannon_analytic/models/cannon/include/cannon_analytic.h
We need to specify either the absolute path to the models
directory, or the
relative location of the models
directory with respect to the top-level
simulation directory (the location of S_define) as the base-path.
We can specify the base-path(s) to the compilers, and to Trick, by adding
-Idir 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:
TRICK_CFLAGS += -Imodels
TRICK_CXXFLAGS += -Imodels
When Trick encounters relative paths in an S_define, it prepends these base-path(s) to the relative paths to create a complete path to the file, thus allowing it to be located.
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.
export TRICK_CFLAGS="-g -Wall -Wmissing-prototypes -Wextra -Wshadow"
export TRICK_CXXFLAGS="-g -Wall -Wextra -Wshadow"
TRICK_CFLAGS= -g -Wall -Wmissing-prototypes -Wextra -Wshadow
TRICK_CXXFLAGS= -g -Wall -Wextra -Wshadow
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 :
Trick Build Process Complete
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.
% ls
S_overrides.mk makefile
S_sie.resource trick.zip
S_define S_source.hh
S_main_<your_platform_name_here>.exe build
Here are some common problems.
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.
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.
trick.stop(5.2)
By convention, the input file is placed in a RUN_*
directory.
% cd $HOME/trick_sims/SIM_cannon_analytic
% mkdir RUN_test
% cd RUN_test
% vi input.py <edit and save>
To run the simulation, simply execute the S_main*exe
:
% 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.