views:

167

answers:

3

I am evaluating the use of code generation for my flight simulation project. More specifically there is a requirement to allow "the average engineer" (no offence I am one myself) to define the differential equations that describe the dynamic system in a more natural syntax than C++ provides. The idea is to devise a abstract descriptor language that can be easily understood and edited to generate C++ code from. This descriptor is supplied by the modelling engineer and used by the ones implementing and maintaining the simulation environment to generate code.

I've got something like this in mind:

model Aircraft has
   state x1, x2;     
   state x3;
   input double : u;
   input bool : flag1, flag2;
   algebraic double : x1x2;
   model Engine : tw1, tw2;
   model Gear : gear;
   model ISA : isa;
   trim routine HorizontalFight;
   trim routine OnGround, General;
   constant double : c1, c2;
   constant int : ci1;
begin differential equations
   x1' = x1 + 2.*x2;
   x2' = x2 + x1x2;
begin algebraic equations
   x1x2 = x1*x2 + x1';
end model

It is important to retain the flexibility of the C language thus the descriptor language is meant to only define certain parts of the definition and implementation of the model class. This way one engineer provides the model in from of the descriptor language as exemplified above and the maintenance engineer will add all the code to read parameters from files, start/stop/pause the execution of the simulation and how a concrete object gets instantiated.

My first though is to either generate two files from the descriptor file: one .h file containing declarations and one .cpp file containing the implementation of certain functions. These then need to be #included at appropriate places

[File Aircarft.h]

class Aircraft
{
public:
    void Aircraft(..); // hand-written constructor
    void ReadParameters(string &file_name); // hand-written

private:
    /* more hand wirtten boiler-plate code */

    /* generate declarations follow */
    #include "Aircraft.generated.decl" 

};

[File Aircraft.cpp]

Aircraft::Aircraft(..) { /* hand-written constructor implementation */ }

/* more hand-written implementation code */

/* generated implementation code follows */
#include "Aircraft.generated.impl"

Any thoughts or suggestions?

EDIT1:

I would like to clarify that there exists a framework to express the differential equations and the (real-time) simulation of a dynamic sytem. The problem is that most of the engineers with domain knowledge are very hesitant regarding the use of C++. Indeed we would like to provide a easer way for them to contribute their part (the mathematical formulation) while retaining the flexibility of C++.

There is of course the possibility to use the MEX compiler to generate C-code from MATLAB/Simulink models but we would like to avoid the associated license fees.

Refactoring the whole thing to use a general scripting language is probably out of scope in terms of workforce available. Furthermore this would -- as far as I can grasp it now and on the fly -- require the engineers to learn a scripting language, too. I am not yet convinced that this would be easier than the syntax exemplified above.

EDIT2: Accepted answer

I accept the answer given by Richard Harrison due to sharing his exeprience with custom code-generators. I will also consider the hints regarding scripting interfaces although I am afraind I will not find the time to implement all this as part of my thesis. Still these pointers might help to convince my coworkers to consider a more coherent class heriarchy. Thanks a lot!

+2  A: 

The problem with this approach is that any errors in the mathematical syntax will only be detected when the code is actually compiled by the C++ compiler. At that point it is very difficult to give good error messages that relate back to the specific lines in error in the specification language. So unless your specification language processor also performs syntax and semantic checks on the specification (i.e. it is at least a partial compiler for the specification language), I would not take this approach.

anon
You got a point there. Syntax errors can be expected and will be catched only by the compiler. But this is true if you type the expressions directly into C-code, too. Whereas a good parser might catch the most obvious errors beforehand and might even add some syntactical sugar such as the so-beloved ^ operator for exponentials.
Arne
+1 for mentioning the issue of syntax errors
Arne
anon
@Neil You are right, of course this would shadow the XOR operator. But in the domain of differential equations and especially when most of the engineers are trained using MATLAB/Simulink the situation seems to be reversed: exponentiation is used frequently but most people are not even aware of XOR let alone use it in their equations.
Arne
+1 for mentioning the shadowing of XOR by redefining the operator into exponentialI will look up the passage in Stroustrup's book.
Arne
This is easily solved by the #line directive.
Hans Passant
Yes it has, check chapter 16. Careful with those snarky comments, they can bounce back.
Hans Passant
+1  A: 

I would go another way. Implement all primitive types, composition functionality, traversal, simulation, and flow control functionality in C/C++ and add a rich interactive shell.

This can be done with minimum effort using SWIG. It adds a scripting interface to the application and lots of scripting languages are supported.

Having a simulator with rich shell interface the engineers (not software engineers) with domain knowledge can easily create prototype scripts for simulation/modelling, debug and tune these scripts, and then, if the performance is not enough, the bottleneck code can be easily ported by the software owner from the scripts to the native C/C++ code.

This approach is used in the most EDA systems (Cadence, Synopsys, etc.) having to simulate the systems with >10e9 units in the model and has proven to be the best solution for the CAD software. In fact, it is almost never required to rewrite scripts into the native language, because the most time is spent in differential equations systems solvers, implemented in native languages.

ADDITION: You can have a look at the tutorial explaining how to enable a scripting interface in C++ program. I would choose TCL as it is a very simple yet sufficient scripting language, all commands can fit on one page. There should not be any problems teaching engineers and you can always document only a small subset of the functionality, to mimic your original syntax example.

Your simulation script would look as below

package require my_models ;# load and initialize models
package require my_preconditioners ;# load and initialize preconditioners
package require my_solvers ;# load and initialize solvers
package require my_simulators ;# load simulators

set mdl [model_create "boeing_777_new"] ;# create jet model
set wing [model_load "ceramic_112233_long" "/mnt/proj/777/wing.models"] ;# load wings models

model_set_param $wing "paint" "silver_xx_233445" ;# change some parameter

model_add_element $mdl "wing_left" [model_new_instance $wing] #; instantiate a wing and add it to the jet, left
model_add_element $mdl "wing_right" [model_new_instance $wing] #; instantiate a wing and add it to the jet, right

set sim [simulator_load "simplified_linear_air_flow" "/mnt/proj/777/worlds.xml"] #; load some linear simulator with predefined parameters from an xml file

simulator_add_object [model_new_instance $mdl] ;# instantiate a jet in the simulator

simulator_set_param $sim "altitude" 12000
simulator_set_param $sim "temperature" -54

simulator_set_output "/tmp/sim.dmp"
simulator_run "10 sec"

exit
bobah
Please see the edit to my question. I am still interested in a small sample of these scripts you mentioned, to get a better idea of the whole matter -- in case you got a snippet at hand.
Arne
@Arne - see my addition to the answer
bobah
@bobah Thanks for the sample and the pointer to the tutorial. I am still unsure wether I can handle implementing a full-flegded scripting interface since it's essentially my diploma thesis and there is some "scientific" stuff do do, too. Nevertheless, I appreciate your comment and will consider it.
Arne
+1  A: 

Generally I've always found based on the code generators that I've written that code generators are best avoided as they tend to be fiddly, hard to implement and an artificial limitation. In effect a code generator will grow beyond original intentions.

It is wise to realise that unless you really want to design and implement a new language specifically tailored to your domain you are best off finding a solution using pre-existing technologies.

I know it seems that a code generator will make things easier, save typing and generally be wonderful, but realistically this is too optimistic.

If I were doing this in C++ I would firstly spend some time designing a coherent object model, possibly using the approach taken with JSBSim of having the model contained within an XML file.

So following your edit I would say that it is probably best to invest the time in building a clean model, some good documented examples and some training.

The following is a rough prototype model for illustration. It may not be relevant given your edit, but as I've just spent a while putting it together I figured I might as well post it.

#include <string>
using namespace std;

//
// Fundamental element of simulation - an ExecModule is a concise unit of simulation work. It will
// be called by the main real time executive at the frequency specified by the getExecRate() method.
// Communication between modules is either via datapool, or by using BusMessages.
class ExecModule
{
public:
    virtual bool initialise(long time_ms) = 0;
    virtual long run(long ms)  = 0;
    virtual long getExecRate()  = 0;
    virtual string getModuleDescription() = 0;
}

class GeoCoordinate
{
public:
    GeoCoordinate(double lat, double lon, double alt);
};

class Model
{
public:
    virtual void DifferentialEquations() = 0;
    virtual void AlgebraicEquations() = 0;

};
class State
{
public:
    Value Prime();
    Prime(Value &v);
    State operator *(State c);
}

class AircraftModel : public ExecModule, public Model
{
private:
    State x1, x2;
    State x3;
    InputDouble u;
    InputBool flag1, flag2;
    AlgebraicDouble  x1x2;
    Model tw1, tw2; // engine
    Model gear;
    Model isa;
    TrimRoutine HorizontalFight;
    TrimRoutine OnGround, General;
    ConstantDouble c1, c2;
    ConstantInt : ci1;

public:
    AircraftModel()
    {
    }

public:
    virtual void DifferentialEquations()
    {
        x1.Prime(2.0*x2);
        x2.Prime(x1x2);
    }

    virtual void AlgebraicEquations()
    {
        x1x2 = x1 * x2 + x1.Prime();
    }

public: // Required for ExecModule
    string getModuleDescription()
    {
        return "Aircraft Model";
    }

    long getExecRate()
    {
        return 33L;//ms (30hz)
    }

    long run(long ms)
    {
        return 0L;
    }

    bool initialise(long time_ms)
    {
        // called by the Exec (in sequence) when initialisation is required.
    }

};

class SimLoad
{
public:
// exec modules to load
    class Model *aircraft_model;
    class Model *engine_model;
    class Model *aerodynamics_model;
    class GPSSimulator *gps;
    class FeaturesDataProvider *runways;
    class ArincDB *arincDB;
    class ExecSystem *execSystem;

    SimLoad()
    {
        engine_model = new EngineModel();
        aerodynamics_model = new AeroDynamicsModel();
        aircraft_model = new AircraftModel();
        arincDB = new ArincDB();
        gps = new GPSSimulator();

        // ensure that the simulated systems are loaded in the correct
        // sequence. Notice that the exec system provides two schedulers which
        // we select manually to allow for threading. Each thread within the exec is 
        // synchronised at the start of each frame (iteration) however within each frame
        // each thread is free running so care needs to be taken when scheduling dependant
        // modules across different threads.
        execSystem.scheduler.addModule(engine_model);
        execSystem.scheduler.addModule(aerodynamics_model);
        execSystem.scheduler.addModule(aircraft_model);
        execSystem.scheduler1.addModule(gps);

        runways = new ArincRunwayProvider(arincDB);

        execSystem.start(); // 
    }
}
Richard Harrison
+1 for you effort in providing an example. I appreciate your comment regarding the probable evolution of code generators.
Arne
I'll have to disagree. Code generators are tools. Like any tool, a code generator will do wonders when properly done/used. But then, to do that, it's another story :)
Rui Curado