TrickLogo

HomeDocumentation HomeBuilding a Simulation → Model Source Code

This section details the syntax for creating headers and source code that Trick can process.

It also details the operation of the Trick Interface Code Generator (ICG) that processes headers, and the Module Interface Specification Processor (MIS) that processes source code.

Programming Language Support

The majority of model source for simulations is written in C and C++. Trick supports auto generating IO code to peek and poke C and C++ structures, classes, and enumerations. Trick also generates the necessary makefile rules to compile and link C and C++ model code into the simulation.

Models written in other languages may be included in the simulation. It is possible to include Fortran 77, Fortran 90, Ada, and/or Java code in the simulation. These models cannot be called directly from the Trick scheduler, but may be called through C language wrapper functions provided by the user that executes the other language calls.

Header Files

Trick processes header files in order to auto generate IO source code for the simulation. IO source code is the heart of how Trick does its input processing. The following describes the syntax for a header file.

/* [TRICK_HEADER]
PURPOSE:
    (Purpose statement.)
[LANGUAGE: (C++)]
[LIBRARY DEPENDENCY:
    (
     (object.o|model.c|lib.a|lib.so|<relative_path>/lib.a)
     [(object_n.o|model_n.c|lib_n.a|lib_n.so|<relative_path>/lib_n.a)]
    )]
[ICG IGNORE TYPES:
    ((Type #1) (Type #n)])]
[PYTHON_MODULE: (module_name)]
[REFERENCES:
    ((Reference #1) (Reference #n)])]
[ASSUMPTIONS AND LIMITATIONS:
    ((Assumption #1) (Assumption #n)])]
[PROGRAMMERS:
   (((Name) (Company) (Date) [(other info)])
   [((Name) (Company) (Date) [(other info)])]]
[ICG: (No|Nocomment)]

Introduced in Trick 16
@trick_parse(everything|attributes|dependencies_only)
@trick_exclude_typename(Type)

*/

typedef enum {
    enum_label [= enum_value],
    last_enum_label [= enum_value]
} enum_name;

[typedef] struct [struct_tag] {
    char|short|int|long|long long|
    unsigned char|unsigned short|unsigned int|unsigned long|unsigned long long|
    float|double [*]* param_name [[dim]]*;
        /* [**|*i|*o|*io] (trick_io|io)([**|*i|*o|*io]) (trick_chkpnt_io|cio)([**|*i|*o|*io])
           trick_units([measurement_units]) description */

    any_other_type [*]* param_name [[dim]]*;
        /* [**|*i|*o|*io] trick_io([**|*i|*o|*io]) trick_chkpnt_io([**|*i|*o|*io])
           trick_units(measurement_units) description */
} struct_name;

class <class_name> {
    [
     friend InputProcessor;
     friend init_attr<class_name>();
    ]

    (public|protected|private):
    char|short|int|long|long long|
    unsigned char|unsigned short|unsigned int|unsigned long|unsigned long long|
    float|double [*]* param_name [[dim]]*;
        /* [**|*i|*o|*io] trick_io([**|*i|*o|*io]) trick_chkpnt_io([**|*i|*o|*io])
           trick_units([measurement_units]) description */

    any_other_type [*]* param_name [[dim]]*;
        /* [**|*i|*o|*io] trick_io([**|*i|*o|*io]) trick_chkpnt_io([**|*i|*o|*io])
           trick_units([measurement_units])measurement_units description */
};

Comment Header

The Trick comment header, which is optional, begins with /* PURPOSE:. Within the Trick comment header, the PROGRAMMERS, REFERENCES, ASSUMPTIONS AND LIMITATIONS and ICG are optional entries. Since parentheses, (), are used to delineate fields within the comment header, parentheses are not allowed as characters within the comment header's fields. Any other formatted comments may appear before and/or after the Trick comment header.

C++ Language Override, LANGUAGE: (C++)

If a header file has a C++ extension (e.g *.hh ) Trick’s parsers will realize that it is a C++ file and handle it appropriately. If the extension is *.h, Trick will assume it is a C file (not C++). If you want to make a C++ header file name with the *.h extension, you must explicitly tell Trick it is a C++ file with the LANGUAGE: (C++) declaration in the Trick comment header.

Telling ICG to ignore this header file, ICG: (No)

If ICG: (No) is in the comment header, Trick will not to process the header. This is useful if the header contains anything that Trick cannot process, or if the programmer wishes Trick to skip this header. For skipping entire sets of headers, see next item.

If ICG: (Nocomment) is in the comment header, Trick will not process any comments within the file. This option is useful if the user wants ICG to process the file but the file does not have comments that are Trick compliant.

Library Dependencies

Library dependencies are the model source code files required by the simulation. They can be listed 1) within model header files, 2) within model source files, or 3) within the S_define. Each library dependency only needs to be listed once, and the preferred approach is to list each library dependency within its respective model header file.

Listing Library Dependencies Within Model Header Files: (preferred approach)
Listing library dependencies within the model header files is as simple as providing the path of each source file for which the header file makes declarations. The path should be relative to the base path that was set in S_overrides.mk (See the link below).

Compiling and Building the Simulation

By doing it this way, you don't have to recall every single source file in your simulation when you're listing the library dependencies. You only need to list the source files relevant to the current header file, and Trick does the heavy lifting by bringing them all together when it processes the header files.

A model header consistent with this approach would contain a LIBRARY DEPENDENCY field that looked like the following:

LIBRARY DEPENDENCY:
    ((relative_path/source_file.c)
     (relative_path/other_source_file.cpp))

Listing Library Dependencies Within Model Source Files:
If you find it more intuitive to instead list library dependencies for each model source file, it is possible to do so. In each model source file, list the object files and libraries that the current model source file depends on. Self-references are allowed, but not necessary. For a file this.c which calls

The LIBRARY DEPENDENCY field might look like this:

LIBRARY DEPENDENCY:
    ((this.o)
     (that.o)
     (my_library/libdog.a)
     (libcow.so)
     (${FOO_ENV_VAR}/foo.o))

For references to objects outside the current source directory, the directory paths must be specified relative to the bin_${TRICK_HOST_CPU} directory. In this example, the that.c function might also have its own library dependency list, but the that.c dependencies do not need to appear in the this.c dependency list. The CP will automatically search and sort all the object code dependencies; the developer just has to make sure all dependencies are listed in the appropriate files.

There are two ways to specify dependencies to actual libraries, i.e. lib*.a files:

* <relative path>="">/<library name>.a

If you use a relative path to the library, Trick will search the TRICK_CFLAGS for a directory that contains source code for the library. Once Trick locates the source code, it will automatically build the library and link it in the simulation.

* <library name>.a

If you do NOT specify a relative path, Trick will NOT build the library for you. It will simply search your -L paths in your TRICK_USER_LINK_LIBS for the library. If found, it will link the library into the simulation.

You may also have Trick link in a shared (lib*.so) library. You must supply the *.so extension. Trick will not automatically build a shared library, but it is smart enough to use it during link time.

The LIBRARY DEPENDENCY field also handles the #ifdef, #else and #endif statements such that different object files and libraries may be linked for different cases. The previous example might look like this:

LIBRARY DEPENDENCY:
    ((this.o)
     (that.o)
#ifdef __animals
     (my_library/libdog.a)
     (libcow.so)
#else
     (my_library/lib.a)
#endif
     (${FOO_ENV_VAR}/foo.o))

Listing Library Dependencies Within the S_define:
Listing library dependencies within the S_define is just like listing them within the model header files, but all model source files are listed in one spot instead of per header file. If you choose to do it this way, don't forget to update the list each time you add or remove a model source file.

ICG_IGNORE_TYPES

The ICG IGNORE TYPES field lists the structs or classes to be ignored. Any parameters of this type or inherited from are ignored. The ICG IGNORE TYPES field is only valid for the current file. It does not extend to included header files.

PYTHON_MODULE

Specifying a python_module name will place any class/struct and function definitions in this header file in a python module of the same name. All classes and functions are flattened into the python trick namespace by default. This capability allows users to avoid possible name collisions between names when they are flattened. An empty python_module statement will be ignored.

Compiler Directives

Trick handles all compiler directives (#if, #ifdef, #endif, #define, #include, etc.) ICG also uses the -D and -U command line arguments for defines and undefines, respectively.

trick_parse

The trick_parse directive is a Doxygen style field serving the same functionality as the PURPOSE: keyword and ICG: (No). The trick_parse directive like all Doxygen style directives are prefixed with either a \ or an @ character.

@trick_parse(everything): Treat this comment as the Trick header comment. Search for library dependencies in this comment, and treat all comments following variable definitions as Trick comments.

@trick_parse(attributes): Treat this comment as the Trick header comment. Search for library dependencies in this comment. Create I/O attributes for the classes and structures in the file, but do not read comments following variable definitions.

@trick_parse(dependencies_only): Treat this comment as the Trick header comment. Search for library dependencies in this comment. Do not process the file any further.

trick_exclude_typename

@trick_exclude_typename(type) is equivalent to ICG_IGNORE_TYPES in a Doxygen style field. The trick_exclude field lists the structs or classes to be ignored. Multiple trick_exclude_typename fields may be used to ignore multiple types.

Enumerated Type Definitions

Trick provides complete support for enumerated types. Simple mathematical expressions using enumerated types are supported as well.

An example follows:

a.h

typedef enum {
  FIRST_ENUM = 45
} A_ENUM;

b.h

#include "a.h"

typedef enum {
  ME_TOO = 2
} OTHER_ENUM;

typedef enum {
  SECOND_ENUM = FIRST_ENUM,
  THIRD_ENUM = FIRST_ENUM * 3,
  FOURTH_ENUM = SECOND_ENUM * 4,
  FIFTH_ENUM = ME_TOO * 6
} B_ENUM;

c.h

#include "b.h"

typedef struct {
  int dummy;                      /* No comment necessary */
  A_ENUM ae;                      /* No comment necessary */
  B_ENUM be;                      /* No comment necessary */
  int ia1[FIRST_ENUM];            /* No comment necessary */
  int ia2[SECOND_ENUM];           /* No comment necessary */
  int ia3[FIFTH_ENUM];            /* No comment necessary */
} DATA;

Data Structure Definitions and Parameter Declarations

The data structure type definition statements, typedef struct { ... } name;, and typedef union { ... } name; struct Foo { } name; follows standard C syntax, and are supported by Trick. However, Trick requires a C comment immediately following every parameter declaration.

Parameter Data Types

Trick allows any data type declaration within the data structure typedef statement. However, only the following data types will be processed by Trick:

1 int, 1 short, 1 long, 1 long long 1 char, 1 (un)signed int, 1 (un)signed short, 1 (un)signed long, 1 (un)signed char, 1 (un)signed long long, 1 (un)signed short int, 1 (un)signed long int, 1 float, 1 double, 1 wchar_t, 1 FILE * 1 Bit fields (signed and unsigned) and 1 previously processed structure, union, enumerated types and typedefs.

All other types are ignored. Types may be defined and used within the same header if the types are defined before they are used (this is a C syntax rule, too).

Pointers

Any combination of pointers and array dimensions up to 8 dimensions may be used for parameter declarations; for example, double ** four_dimensional_array[2][2];, will be processed. Void pointers and function pointers are not processed. Parameters declared with pointers (like four_dimensional_array example), are treated differently; these are called unconstrained arrays. Trick will generate dynamic memory allocation source code for the developer which allows the developer to size the array dimensions (represented by the pointers) via special syntax in the runstream input file. The developer may 1) use the input file to input data to the arrays, 2) output the data via standard Trick logging functions, or 3) share the data through the variable server.

The user does have the option to perform their own memory management for parameters declared as pointers. In this case, instead of specifying the allocation in the input file, the user may allocate the data in a job. In order for Trick to process the data as if it was its own managed memory (and provide capabilities like logging, checkpointing, etc.), the memory address, and number and size of the allocation must be passed to the Trick TMM_declare_extern_var function. The user is also responsible for freeing the memory when done.

Intrinsic typedef and struct Support

Types declared using typedef struct, typedef union, and typedef enum are recognized by Trick. Intrinsic typedefs are supported as well and may be nested in structures. The example that follows details a header that Trick will handle:

typedef unsigned char my_uchar;
typedef char my_char;
typedef wchar_t my_wchar;
typedef short int my_shortint;
typedef short my_short;
typedef unsigned short int my_ushortint;
typedef unsigned short my_ushort;
typedef int my_int;
typedef unsigned int my_uint;
typedef long int my_longint;
typedef long my_long;
typedef unsigned long int my_ulongint;
typedef unsigned long my_ulong;
typedef float my_float;
typedef double my_double;
typedef my_short my_short2;

struct Animal_Sound {
   int moo;            /* -- Cow */
   int baa;            /* -- Lamb */
   int sss;            /* -- Snake */
};

typedef struct {
  my_uchar uc;             /* -- unsigned char */
  my_char c;               /* -- char */
  my_char ca[80];          /* -- char */
  my_wchar wc;             /* -- wchar_t */
  my wchar wca[100];       /* -- wchar_t */
  my_shortint si;          /* -- short int */
  my_short *s;             /* -- short stuff */
  my_ushortint usi;        /* -- short stuff */
  my_ushort us;            /* -- short stuff */
  my_int i;                /* -- count */
  my_int ia[5];            /* -- count */
  my_uint ui;              /* -- count */
  my_longint li;           /* -- count */
  my_long l;               /* -- count */
  my_ulongint uli;         /* -- count */
  my_ulong ul;             /* -- count */
  my_float f;              /* -- count */
  my_double d;             /* -- count */
  my_short2 s20;           /* -- short 20 */
  my_short2 s21;           /* -- short 21 */
  struct Animal_Sound as;  /* -- Wild Kingdom */
} DATA;

typedef DATA MY_DATA;
typedef MY_DATA MY_DATA_2;

typedef struct {
  DATA id;        /* -- testing typedef of struct */
  MY_DATA mid;    /* -- testing typedef of struct */
  MY_DATA_2 mid2; /* -- testing typedef of struct */
} DATA_2;

Parameter Comments

Each parameter declaration within a data structure definition may be accompanied by a trailing comment. There are six possible fields in the parameter comment, but only two are required. All six fields of the parameter comment are stored for later reuse at simulation runtime.

The Input/Output Specification

The first three fields in the parameter comment are optional and specify the input/output processing for the parameter. I/O permissions may be set globally or individual capabilities may set their permissions separately. I/O permissions for checkpointing is available to set separately.

To set all permissions for general variable access, start the comment with one of the following fields, [**|*i|*o|*io], trick_io([**|*i|*o|*io]) or io([**|*i|*o|*io]). These are equivalent forms to set general variable access.

Checkpoint I/O may be set separately by adding trick_chkpnt_io([**|*i|*o|*io]) or cio([**|*i|*o|*io]) to the comment. If this optional field is not present, the general I/O access field is used to determine checkpoint permissions.

The Measurement Units Specification

The second field, trick_units([measurement_units]), is a required field and specifies the internal source code units for the parameter. These units are important because they give the input processor the knowledge of what units the user's input data needs to be converted to. Trick uses a third-party package, UDUNITS, for units support. It's syntax is specified here.

User Defined Attributes Fields

Following the measurement units specification, in the parameter comment, are two optional, user-defined attribute fields. Using these fields, a user can associate (up to 2) character strings with a parameter. These strings are stored in the ATTRIBUTES structures (in the io_src directory) generated by ICG. The first of these optional fields is delimited by brackets (‘[‘ and ‘]’) and is stored in the element ATTRIBUTES->alias. The second is delimited by braces (‘{‘ and ‘}’) and is stored in the element ATTRIBUTES->user_defined. The definition of the ATTRIBUTES structure is found in $TRICK_HOME/trick_source/sim_services/include/attributes.h.

Description Fields

The description field is required and must be the last field of the comment. The description field is basically everything after the first three fields. The description field may span multiple lines.

C++ Header Files

C++ headers may include constructs and concepts not found in C header files. In addition to all C syntax, Trick parses and understands many C++ features.

Public, Protected, and Private Access

Trick generates several files to support its various features. The data recorder and checkpointer rely on code produced by the Interface Code Generator (ICG), which bookkeeps the memory layout of variables within the simulation. public members are always available to these features. protected and private data is also available if there is no use of TRICK_ICG in the header file. If use is found, Trick will issue a warning during simulation compilation, and private and protected data will only be accessible to Trick if the following friends are added to the offending classes:

friend class InputProcessor;
friend void init_attr<class_name>();

The input processor and variable server rely on code produced by a third-party tool, the Simplified Wrapper and Interface Generator (SWIG). SWIG provides the functions that allow access to simulation variables from Python contexts. These features can only access public members. It is not possible to expose protected and private data to them.

Inheritance

Trick may use model code with any type of inheritance. Some limitations are present to Trick's ability to input process, checkpoint, etc. inherited variables.

Namespaces

ICG supports namespaces and nested scopes. Data recording and variable access via Trick View should work regardless of how many levels there are.

Namespaces and nested scopes are similarly supported in Python contexts, such as the input file and variable server, with some caveats regarding templates.

  1. A template instantiation may be unqualified (have no use of the scope resolution operator ::) only if its corresponding template is declared in the immediately-enclosing namespace.
  2. Otherwise, a template instantiation must be fully qualified, starting from the global namespace.
  3. Finally, instantiations of templates declared within the same class must be excluded from SWIG.

In the following examples, all template instantiations occur in example::prime::Soup. The immediately-enclosing namespace is prime, so only instantiations of templates declared directly in prime (only Celery) may be unqualified. All other template instantiations must be fully qualified, starting from the global namespace, even if the C++ name lookup process would find them with partial qualification.

template <class T> class Potato {};

namespace example {

  template <class T> class Onion {};

  namespace peer {
    template <class T> class Raddish {};
  }

  namespace prime {

    namespace inner {
        template <class T> class Carrot {};
    }

    template <class T> class Celery {};

    class Soup {

      public:
        template <class T> class Broth {};

        ::Potato<int> potato;                      // Rule 2
        example::Onion<int> onion;                 // Rule 2
        example::peer::Raddish<int> raddish;       // Rule 2
        example::prime::inner::Carrot<int> carrot; // Rule 2
        Celery<int> celery;                        // Rule 1
#ifndef SWIG
        Broth<int> broth;                          // Rule 3
#endif
    };
  }

}

Function Overloading

Trick parses function declarations for input file use. The python input processor understands class method overloading. Overloaded methods with different arguments may be called in the input files. Default arguments are to methods are understood and honored in the input file. Operator overloading is skipped by Trick processors. Operator overloading is not implemented in the input file.

Templates and the Standard Template Libraries (STL)

Trick attempts to process user defined templates. Simple templates are handled. We do not have a good definition of simple. Typedefs of templates is supported and encouraged. All protected and private data is ignored within templates. This is because it is not possible to specify the correct io_src friend function. Templates within templates are not processed. Finally abstract templates are not supported by Trick. These templates should be excluded from Trick processing. See below to see how to exclude code from processing.

STLs may be used in models. However, STL variables are not data recordable, they are not visible in the variable server, nor are they directly accessible in the input file. Some STLs are automatically checkpointed: array, vector, list, deque, set, multiset, map, multimap, stack, queue, priority_queue, pair.

Noncopyable Objects

Sometimes classes contain members that are not copyable or the math modeler wants to prevent the class from being copied. Declaring an unimplemented private copy constructor and assignment, "=", operator prevents the class from being copied.

class CantCopyMe {
 private:
  CantCopyMe(const CantCopyMe&);
  CantCopyMe& operator= (const CantCopyMe);
}

When using such classes in Trick, classes that include non copyable classes must also declare themselves not copyable. this extends all the way up to sim objects in the S_define.

class MysimObject : public Trick::SimObject {
 public:
  CantCopyMe ccm;
 private:
  MysimObject(const MysimObject&);
  MysimObject& operator= (const MysimObject);
}

Source Code in Header Files

Trick attempts to skip over class code in header files while searching for class variables and method declarations. However, code can sometimes confuse Trick and cause it to abort processing of header files. It is recommended to keep code out of the header file.

Library Dependencies

It is good practice to list all the source code files that define class methods in the class header file.

Excluding Header File Code

There are several ways to exclude code from processing.

Excluding Directories

Add paths to exclude to the TRICK_ICG_EXCLUDE environment variable or makefile variable. This works for both C and C++ headers.

Excluding File

Add ICG: (No) to the Trick comment header.

Excluding Lines

When processing header files Trick defines 2 #define variables, TRICK_ICG and SWIG. Code may be excluded by enclosing it in #ifndef blocks.

#ifndef TRICK_ICG
code that cannot be processed by ICG
#ifndef SWIG
code that cannot be processed by ICG or SWIG
#endif
#endif

Source Files

By source files, in this context, we mean functional model source code, i.e. *.c files.

/* [TRICK_HEADER]
PURPOSE:
    (Purpose statement.)
[REFERENCES:
    ((Reference #1)
    [(Reference #n)])]
[ASSUMPTIONS AND LIMITATIONS:
    ((Assumption #1)
    [(Assumption #n)])]
[LIBRARY DEPENDENCY:
    (
     (object.o|lib.a|lib.so|<relative_path>/lib.a)
     [(object_n.o|lib_n.a|lib_n.so|<relative_path>/lib_n.a)]
    )]
[PROGRAMMERS:
   (((Name) (Company) (Date) [(other info)])
   [((Name) (Company) (Date) [(other info)])])]
*/

// source code...

Comment Header

The Trick header is an optional comment block at the top of each source file. It is used for auto-documentation, and more importantly is the means of specifying dependencies to objects or libraries not processed by Trick. Separate functions within a source file do NOT require additional headers. Since parentheses, ( ), are used to delineate fields within the comment header, parentheses are not allowed as characters within the comment fields. NOTE: Even if you are coding a C++ file, you must still specify the comment header using C style comments (not C++ style comments).

Job Description

Source Code

Trick is only interested in the header comment if one is present in source code files. Anything goes for the rest of the source code file.

Trick Version Compatibility

Trick is always changing. The interface to Trick functions may change with each major version. Sometimes even minor version upgrades change the interface. When Trick builds model source code, it includes -DTRICK_VER= and -DTRICK_MINOR=<minor_version> to the TRICK_CFLAGS and TRICK_CXXFLAGS. This allows developers to key off the Trick version in model source code. If there are any compile issues dependent on Trick version, this #define may be useful.

Continue to Environment Variables