TrickLogo

HomeTutorial Home → Analytical Cannon Simulation

Building & Running a Trick-based Simulation

Contents


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:

%  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:

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

/*************************************************************************
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.

% 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

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.


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, 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.

Composite Units (Making Units From Existing Units)

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.

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:

Unicode Characters in Units

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 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
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

/******************************* 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:

% 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

/*************************************************************************
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.

Listing 5 - cannon_analytic.c

/*****************************************************************************
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.

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

/************************************************************************
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

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

/************************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.

Trick Header

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.

Included Files

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.

public : CANNON cannon ;

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) ;

Default Data Job

The default data jobs are called one time before the initialization jobs.

("default_data") cannon_default_data(&cannon) ;

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) ;

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

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.

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_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:

Listing 8 - S_overrides.mk

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.

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
export TRICK_CFLAGS="-g -Wall -Wmissing-prototypes -Wextra -Wshadow"
export TRICK_CXXFLAGS="-g -Wall -Wextra -Wshadow"

For Your .cshrc File
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 :

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

Troubleshooting A Bad Build

Here are some common problems.

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

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>

Sim Execution

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.


Next Page