FLAME  devel
 All Classes Functions Variables Typedefs Enumerations Pages
API Usage (C++ and Python)

C++ Simulation API Usage

The example examples/sim.cpp demonstrates the basic process of running a simulation against an existing sim_type and set of Elements. This is a simplified version of cli.cpp, which is compiled as the run_flame executable.

Most user code will only need base.h and config.h.

#include "flame/config.h"
#include "flame/base.h"

Before constructing a Machine it is necessary to register any "sim_type"s to be used. This will typically be the first thing a program does.

registerLinear();

Running the simulation with the C++ API begins by populating a Config. This will often by done using the lattice parser (GLPSParser).

Parsing will succeed as long as the file is syntactically correct. For example, "sim_type" and element types are not validated.

std::auto_ptr<Config> conf;
{
GLPSParser parser;
conf.reset(parser.parse_file(argv[1]));
}

Next construct a Machine. It is at this step that "sim_type" and element types are validated.

Machine mymachine(*conf);

Now allocate an approprate StateBase based on the "sim_type". More than one StateBase may be allocated and used with a single Machine. The allocated StateBase must only be used with the Machine which allocated it.

This StateBase sub-class is be initialized based a Config. In this example an empty Config is provided, which will leave the state with some "sim_type" specific defaults ("sim_type=Vector" defaults to all zeros). Generally this will only be done when the lattice begins with a "source" element, which will overwrite the state based on its Config.

Alternately, a Config can be provided to Machine::allocState() to provide non-default values.

std::auto_ptr<StateBase> thestate(mymachine.allocState());

The Machine::propagate() method is used to run the simulation. When propagate() returns, the StateBase has been modified to reflect the final state.

By default propagate() starts with the first element and continues through the last element.

mymachine.propagate(thestate.get());

Before exiting some cleanup may be to clear the registry of "sim_type"s.

Python Simulation API Usage

The example examples/sim.py demonstrates the basic process of running a simulation using the python API.

Importing "flame" also registers a standard set of "sim_type"s.

1 from flame import Machine

Read an parse a lattice file and construct a Machine.

1 with open(sys.argv[1], "r") as F:
2  mymachine = Machine(F)

Allocate a State using an empty Config.

1 thestate = mymachine.allocState({})

Propagate through all elements.

1 mymachine.propagate(thestate)

Defining a sim_type

The example defines a new sim_type and two Elements. The simulation itself will be trivial.

The full example can be found in examples/customsim.cpp

Define state struct

The first task is to define the state structure. This contains the variables which define the state of one "bunch". In this case "x" and "xv", which represent a transverse velocity and relative position. In addition the absolute logitudinal StateBase::pos is inherited.

struct State1D : public StateBase
{
double x, // transverse position
xv; // transverse veclocity

The member variables of State1D are initialized from a Config.

State1D(const Config& c)
,x(c.get<double>("x", 0.0)) // m
,xv(c.get<double>("xv", 0.0)) // m/s
{}

The remainder of State1D is boilerplate which is repeated for each member variable.

Define source element type

Now define the "source" element type. By convention this is an element which simply overwrites it's input state with predefined values (eg. from lattice file). So it is simplest to give this struct it's own internal State1D instance.

struct Element1DSource : public ElementVoid
{
State1D initial;

Initialization of the element can also initialize this internal State1D from the same Config.

Element1DSource(const Config& c)
,initial(c)
{}

So the same "x" and "xv" used to initialize the state can be given to a source element.

srcname : source, x=0, xv=0.1, L=0.5;

With this lattice file entry, the "x" and "xv" are used to initialize the internal State1D. "L" is used to initialize ElementVoid::length.

Next define the simulated action of this element with its advance() method. This method should modify it's input State1D. In this case, the source element simply overwrites the input state using its internal State1D.

virtual void advance(StateBase& s)
{
s.assign(initial);
s.pos += length; // source element ususaly has zero length, but not required
}

Define another element type

Now define another "generic" element type which transforms the state in a more interesting way.

struct Element1DGeneric : public ElementVoid
{
double xa, // transverse acceleration
tt; // logitudinal transit time
Element1DGeneric(const Config& c)
,xa(c.get<double>("A", 0.0)) // m/s2
{
// length will never change, so only need to compute transit time once
tt = length/C0; // sec.
}

Here we see that the name given to Config::get need not match a c++ variable name. Also that some calculations can be done once, and the result stored in a class member variable for later re-use.

virtual void advance(StateBase& s)
{
State1D &ST = static_cast<State1D&>(s); // safe since sim_type=Simple1D will only use State1D
ST.pos += length;
ST.xv += xa*tt;
ST.x += xa*tt*tt;
}

The advance() function incrementally updates the state.

Registration

So far three types have been defined: State1D, Element1DSource, and Element1DGeneric. If nothing further is done there will be no way to make use of this code. As these definitions appear in an anonymous C++ namespace a modern compiler is able to issue a warn that this code is unreachable.

In order for this code to be reachable it must be tied into the FLAME framework in some way. This is accomplished with Machine::registerState and Machine::registerElement.

By convention a function "register...()" is defined, which must be called exactly once before Machine can make use of these definitions.

void register1D()
{
Machine::registerState<State1D>("Simple1D");
Machine::registerElement<Element1DSource>("Simple1D", "source");
Machine::registerElement<Element1DGeneric>("Simple1D", "generic");
}

Now to make use Simple1D we expand on examples/sim.cpp

static const char lattice[] = ""
"sim_type = \"Simple1D\";\n"
"src : source, x = 1e-5, xv = 1e-6;\n"
"elem1 : generic, A = 1, L = 10;\n"
"elem2 : generic, A = -2, L = 10;\n"
"example : LINE = (src, elem1, elem2);\n"
;
int main(int argc, char *argv[])
{
try {
register1D();
std::auto_ptr<Config> conf;
{
GLPSParser parser;
conf.reset(parser.parse_byte(lattice, sizeof(lattice)-1));
}
/* conf now contains (using python notation)
* {"sim_type":"Simple1D",
* "elements": [
* {"type":"source", "name":"src", "x":1e-5, "xv":1e-6},
* {"type":"generic", "name":"elem1", "A":1.0, "L":10.0},
* {"type":"generic", "name":"elem2", "A":-2.0, "L":10.0},
* ],
* }
*/
Machine mymachine(*conf);
/* During Machine construction
* sim_type=Simple1D with type=source selects Element1DSource, which is constructed once
*
* sim_type=Simple1D with type=source selects Element1DGeneric, which is constructed twice.
* first with A=1, then a second time with A=-2
*
* mymachine now has 3 elements. mymachine.size()==3
*/
mymachine.set_trace(&std::cout); // print intermediates
std::auto_ptr<StateBase> thestate(mymachine.allocState());
// propagate through the source element to initialize the state based on
// values of "x" and "xv" from the lattice
mymachine.propagate(thestate.get(), 0, 1);
std::cout<<"Source state "<<*thestate<<"\n";
// propagate through remaining elements
mymachine.propagate(thestate.get(), 1);
std::cout<<"Final state "<<*thestate<<"\n";
return 0;
} catch(std::exception& e) {
std::cerr<<"Error: "<<e.what()<<"\n";
return 1;
}
}