In this section I will introduce how to enable Computational Steering for a simple simulation. For this a simple “simulation” will be used, which is presented in the next paragraph.
The example simulation just initializes a double array of defined size (in this example 1000) and then updates the array
elements in each loop iteration with the average of its two neighbours multiplied with a variable called attenuation.
The Main routine consists of just the construction of a Simulation object and an infinite loop in which routines from it are called.
#include "simulation.h" int main (int argc, char** argv) { Simulation* sim = new Simulation(); while (true) { // do one simulation step sim->oneStep(); // increment the step number sim->incrementSteps(); // print the array sim->getData()->print(); sleep (1); } return 0; }
The Simulation class contains the “heart” of the simulation - the routine oneStep () in which the averaging is done. In other simulations a similar class will be of course much more complex, but still the rough structure should be similar.
#ifndef __SIMULATION_H__ #define __SIMULATION_H__ #include "simulationData.h" #include <unistd.h> class Simulation { public: Simulation (); ~Simulation (); void oneStep (); void incrementSteps () {data->incrementSteps();}; SimulationData* getData () {return data;}; private: SimulationData* data; }; #endif
#include "simulation.h" Simulation::Simulation () { data = new SimulationData (); data -> allocateA (); data -> initialize (); } Simulation::~Simulation () { } void Simulation::oneStep () { double* raw = data -> getA (); // the new value at a position is the average of the neighbour values // multiplied with an attenuation value for (int i = 1; i < data->getDataLength()-1; i++) { raw[i] = data->getAttenuation() * (raw[i-1]+raw[i+1])/2.; } sleep (1); }
The SimulationData object contains the data used for the simulation. In this case it is in principle the array on which the averaging is done.
#ifndef __SIM_DATA_H__ #define __SIM_DATA_H__ #define SIZE 10 class SimulationData { public: SimulationData (); ~SimulationData (); static int getSteps(){return steps;} int getDataLength (){return dataLength;} double getAttenuation () {return attenuationFactor;}; void setAttenuation (double val) {attenuationFactor = val;}; void incrementSteps () {steps++;}; double * getA(){return a;} void allocateA(); void initialize(); void print (); private: double *a; int dataLength; static int steps; double attenuationFactor; }; #endif
#include "simulationData.h" #include <iostream> #include <cmath> int SimulationData::steps = 0; SimulationData::SimulationData () { attenuationFactor = 1.0; allocateA(); initialize(); } SimulationData::~SimulationData () { delete[] a; } void SimulationData::allocateA() { a = new double[SIZE]; dataLength = SIZE; } void SimulationData::initialize() { for (int i=0;i<SIZE;i++) { a[i]= sin(M_PI*(double)i/SIZE)*cos((double)i); } } void SimulationData::print () { for (int i = 0; i<SIZE; i++) { std::cout << a[i] << " "; } std::cout << std::endl; }
With a Makefile like this, it should compile (you may need to change the compiler):
CXX = g++ CXXFLAGS = -O2 -g -Wall LIBS = INCS = OBJS_SIM = simulation.o simulationData.o simMain.o TARGET_SIM = simMain all: $(TARGET_SIM) %.o: %.cpp $(CXX) $(CXXFLAGS) $(INCS) -c $< -o $@ $(TARGET_SIM): $(OBJS_SIM) $(CXX) -o $@ $(OBJS_SIM) $(LIBS) clean: rm -f $(OBJS_CLIENT) $(OBJS_SIM) $(TARGET_CLIENT) $(TARGET_SIM)
The steps to enable Steering in your simulations are similar every time:
To see how the framework works exactly look at the description.
Here is how to realize it in the code (I will use the SteereoSocketCommunicator for this example, other communicators can be also used, of course):
Add these Headers to simMain.cpp:
#include <steereoSimSteering.h> #include <steereoSocketCommunicator.h>
Add the following lines before the loop:
// 1. SteereoSimSteering* simSteer = new SteereoSimSteering(); // 2. SteereoSocketCommunicator* simComm= new SteereoSocketCommunicator ("44445"); simSteer->setCommunicator (simComm); // 3. simSteer->startListening ();
And (usually) inside the loop:
// 4. simSteer->processQueue ();
After that your simulation will listen to requests from clients at port 44445 and will process these requests once in every step (The requests won't be processed immediately !). Now we have to configure which requests can be processed.
(to compile this, you will need the Makefile from below, which includes the libSteereo library)
Generally if you want steering functionality, you have to write an own command to realize it.
But in most cases you will only want to be able to directly manipulate variables. For this the command SteerParameterCommand is already included in the library. For writing own commands look here.
Lets assume we want to steer the attenuation parameter and the simulation data array.
Add to the header files:
#include <steerParameterCommand.h>
And before the simulation loop you register the parameter, which you want to be able to manipulate:
SteerParameterCommand::registerScalarParameter ("attenuation", sim->getData(), &SimulationData::getAttenuation, &SimulationData::setAttenuation); SteerParameterCommand::registerArrayParameter ("dataArray", sim->getData()->getA(), sim->getData()->getDataLength());
The attenuation parameter has been registered via getter and setter method, whereas the data array A has been registered via pointer.
It also could have been the other way around.
The complete simulation now look like this:
#include "simulation.h" #include <steereoSimSteering.h> #include <steereoSocketCommunicator.h> #include <steerParameterCommand.h> int main (int argc, char** argv) { Simulation* sim = new Simulation(); SteereoSimSteering* simSteer = new SteereoSimSteering(); SteereoSocketCommunicator* simComm= new SteereoSocketCommunicator ("44445"); simSteer->setCommunicator (simComm); SteerParameterCommand::registerScalarParameter ("attenuation", sim->getData(), &SimulationData::getAttenuation, &SimulationData::setAttenuation); SteerParameterCommand::registerArrayParameter ("dataArray", sim->getData()->getA(), sim->getData()->getDataLength()); simSteer->startListening (); while (true) { sim->oneStep(); sim->incrementSteps(); sim->getData()->print(); simSteer->processQueue (); sleep (1); } return 0; }
Below is the new Makefile which also includes Steereo libraries. If you did not “make install”, or get errors stating header files were not found, you might need to adjust the paths in STEEREO_INC and STEEREO_LIB
CXX = g++ CXXFLAGS = -O2 -g -Wall -fmessage-length=0 STEEREO_INC=/usr/local/include/steereo STEEREO_LIB=/usr/local/lib/steereo LIBS = -L$(STEEREO_LIB) -lSteereo -ldl INCS = -I$(STEEREO_INC) OBJS_SIM = simulation.o simulationData.o simMain.o TARGET_SIM = simMain all: $(TARGET_SIM) %.o: %.cpp $(CXX) $(CXXFLAGS) $(INCS) -c $< -o $@ $(TARGET_SIM): $(OBJS_SIM) $(CXX) -o $@ $(OBJS_SIM) $(LIBS) clean: rm -f $(OBJS_SIM) $(TARGET_SIM)
Now that the simulation is steerable, all we need is a possibility to steer. Therefore we now write a client, that sends steering requests to the
simulation. The Client can be a standalone program or integrated into another program (e.g. as a module for a visualization tool).
The following should be included in every steering client (Quite similar to the simulation side):
You have to use following includes:
#include <steereoClientSteering.h> #include <steereoSocketCommunicator.h>
The code needs something like this (the following code connects to a simulation which listens for requests on ip 127.0.0.1 and port 44445)
// 1. SteereoClientSteering* clientSteer = new SteereoClientSteering; // 2. SteereoSocketCommunicator* clientComm = new SteereoSocketCommunicator; // 3. clientSteer->startConnection (clientComm, "127.0.0.1", "44445"); // 4. clientSteer->startAccepting();
And at the end
clientSteer->closeConnections();
The next step is to register functions, which should receive the responses of the simulation, and to actually make requests !
A basic client (which still does nothing, but will be enhanced) could look like this:
#include <iostream> #include <steereoClientSteering.h> #include <steereoSocketCommunicator.h> #define DATASIZE 10 SteereoClientSteering* clientSteer; int main (int argc, char** argv) { clientSteer = new SteereoClientSteering; double* myDataArray = new double[DATASIZE]; double myAttenuation; SteereoSocketCommunicator* clientComm = new SteereoSocketCommunicator; clientSteer->startConnection (clientComm, "127.0.0.1", "44445"); clientSteer->startAccepting(); std::string input = "3.14"; while (input != "q") { std::cin >> input; } // 5. clientSteer->closeConnections (); return 0; }
This new Makefile will also compile the client:
CXX = g++ CXXFLAGS = -O2 -g -Wall -fmessage-length=0 STEEREO_INC=/usr/local/include/steereo STEEREO_LIB=/usr/local/lib/steereo LIBS = -L$(STEEREO_LIB) -lSteereo -ldl INCS = -I$(STEEREO_INC) OBJS_SIM = simulation.o simulationData.o simMain.o OBJS_CLIENT = clientMain.o TARGET_SIM = simMain TARGET_CLIENT = clientMain all: $(TARGET_SIM) $(TARGET_CLIENT) %.o: %.cpp $(CXX) $(CXXFLAGS) $(INCS) -c $< -o $@ $(TARGET_SIM): $(OBJS_SIM) $(CXX) -o $@ $(OBJS_SIM) $(LIBS) $(TARGET_CLIENT): $(OBJS_CLIENT) $(CXX) -o $@ $(OBJS_CLIENT) $(LIBS) clean: rm -f $(OBJS_CLIENT) $(OBJS_SIM) $(TARGET_CLIENT) $(TARGET_SIM)
If you just want to steer parameters (which you registered at the SteerParameterCommand on simulation side) the
class SteerParameterClient can support you:
To use it, you have to:
Add the following to the includes:
#include <steerParameterClient.h>
For the code you should add (before startAccepting() would be the best):
// 1. SteerParameterClient* paramClient = new SteerParameterClient(); // 2. paramClient->setClient (clientSteer); // 3. paramClient->addAction (); // 4. paramClient->registerScalarParameter ("attenuation", &myAttenuation); paramClient->registerArrayParameter ("dataArray", &myDataArray, DATASIZE);
The set and get methods operate on the memory, which was assigned to the parameters, when they were registered. With the isParameterUpdated (std::string paramName) method, you can then check, if the parameter has already been updated (this won't happen immediately !).
In the following code, the get and set requests are done in the while loop (note: with SteereoLogger::setOutputLevel(2) only the warnings and errors from Steereo are printed):
#include <iostream> #include <steereoClientSteering.h> #include <steereoSocketCommunicator.h> #include <steerParameterClient.h> #define DATASIZE 10 SteereoClientSteering* clientSteer; int main (int argc, char** argv) { clientSteer = new SteereoClientSteering; double* myDataArray = new double[DATASIZE]; double myAttenuation; for (int i = 0; i < DATASIZE; i++) { myDataArray[i] = 0; } SteerParameterClient* paramClient = new SteerParameterClient(); SteereoLogger::setOutputLevel(2); SteereoSocketCommunicator* clientComm = new SteereoSocketCommunicator; clientSteer->startConnection (clientComm, "127.0.0.1", "44445"); paramClient->setClient (clientSteer); paramClient->addAction (); paramClient->registerScalarParameter ("attenuation", &myAttenuation); paramClient->registerArrayParameter ("dataArray", myDataArray, DATASIZE); clientSteer->startAccepting(); std::string input = "\0"; while (input != "q") { std::cout << "(c)hange attenuation, (g)et attenuation, get (d)ata array or (q)uit ? "; std::cin >> input; if (input == "c") { std::cout << "input new attenuation: "; std::cin >> myAttenuation; paramClient->requestSetParameter ("attenuation"); while (!paramClient->isParameterUpdated ("attenuation")) {} } else if (input == "g") { paramClient->requestGetParameter ("attenuation"); while (!paramClient->isParameterUpdated ("attenuation")) {} std::cout << "The current attenuation is " << myAttenuation << std::endl; } else if (input == "d") { paramClient->requestGetParameter ("dataArray"); while (!paramClient->isParameterUpdated ("dataArray")) {} std::cout << "The current data array is: "; for (int i = 0; i < DATASIZE; i++) { std::cout << myDataArray[i] << ", "; } std::cout << std::endl; } } clientSteer->closeConnections (); return 0; }
Now we have a working simulation and a working client to play around with.
Not everyone wants to do only parameter steering. For example you are interested in the mean value of the data array, so let us write a command, that calculates and
sends the mean value on demand (an alternative could be to request the whole data and calculate the average on client side).
Every new command has to be derived from SteereoCommand and you should implement:
Not necessary, but nice to have:
The Command which calculates and sends the mean value would look like this (The command will have two parameters startIndex and endIndex, and the mean value will be calculated from the values between the indices):
#ifndef MEANVALUECOMMAND_H_ #define MEANVALUECOMMAND_H_ #include <steereoCommand.h> #include "simulationData.h" class MeanValueCommand : public SteereoCommand { public: MeanValueCommand(); virtual ~MeanValueCommand(); ReturnType execute (); void setParameters (std::list<std::string> params); static SteereoCommand* generateNewInstance (); private: int startIndex, endIndex; }; #endif
#include "meanValueCommand.h" // 1. MeanValueCommand::MeanValueCommand() : SteereoCommand (false, "meanValueCommand") { } MeanValueCommand::~MeanValueCommand() { } // 2. ReturnType MeanValueCommand::execute () { double* data = (double*) ((SimulationData*) this->getData())->getA(); double sum = 0; int valueNumber = endIndex - startIndex + 1; for (int i = startIndex; i <= endIndex; i++) { sum += data[i]; } if (valueNumber > 0) { sum /= valueNumber; } this->getCommunicator()->sendDouble(this->getConnection()->getDataConnection(), sum); return EXECUTED; } void MeanValueCommand::setParameters (std::list<std::string> params) { startIndex = 0; endIndex = this->getDataSize() - 1; int paramNum = params.size(); if (paramNum >= 1) { startIndex = atoi (params.front().c_str()); params.pop_front(); } if (paramNum >= 2) { endIndex = atoi (params.front().c_str()); params.pop_front(); } } // 3. SteereoCommand* MeanValueCommand::generateNewInstance () { return new MeanValueCommand; }
Adding meanValueCommand.o to the OBJS_SIM variable in the Makefile should make sure, that the command gets compiled into the simulation. Alternatively you could also compile the command as a dynamic library (how to handle that will be covered in the Fortran/C section).
As the command is now written, we should let the simulation and the client know about this.
In the simulation you have to:
After including the header file of the command, following lines should do the job in our example:
// 1. simSteer->registerCommand (&MeanValueCommand::generateNewInstance, "meanValueCommand"); // 2. MeanValueCommand::addData ("meanValueCommand", sim->getData()); MeanValueCommand::addDataSize("meanValueCommand", sim->getData()->getDataLength());
That was it on the simulation side, a little more has to be done for the client.
The client will be notified, as a command is executed on the simulation side, and has the possibility to act upon it with a registered handler. You have to register a handler, if you want to receive data from the executed command, as the receive operation will have to be inside that handler function. In our case we first define an additional global value for the mean value:
double meanValue = 0;
Then our handler function for the meanValueCommand is this:
void handleAnswer (request req) { if (strcmp (req.steeringMethod, "meanValueCommand") == 0) { clientSteer->getComm()->receiveDouble(clientSteer->getConnection()->getDataConnection(), &meanValue); } }
In the main function you should register the handler,
clientSteer->addAction("meanValueCommand", &handleAnswer);
and make requests:
else if (input == "m") { int commandID = clientSteer->makeRequest ("meanValueCommand", "0 9"); while (!clientSteer->hasBeenExecuted(commandID)) {} std::cout << "The mean value is " << meanValue << std::endl; }
Here the hasBeenExecuted(int commandID) function tells you, if the request has been already fulfilled.
Per default the intra communicator (SteereoIntraCommunicator), which is used for communication within Steereo is a SteereoSequentialIntraCommunicator, which is only suitable for sequential simulations. For parallel simulations you can use a SteereoMPIIntraCommunicator which handles the gathering of data from different processes with MPI calls.
SteereoMPIIntraCommunicator* mpiIntraComm = new SteereoMPIIntraCommunicator(); // generate partNum partitions out of ownSize processes for me of rank ownRank. mpiIntraComm->generateEqually(ownRank, partNum, ownSize); // set the MPIIntraCommunicator as IntraCommunicator now _steer->setIntraCommunicator(mpiIntraComm); // am I the local "root" of my partition ? if (mpiIntraComm->amIRoot()) { // add "partitionNumber" to the base port number portNumber += (ownRank * partNum) / ownSize; std::stringstream strstr; strstr << portNumber; // create the communicator with the just now calculated port number and set it for the SteereoSimSteering instance _steer->setCommunicator(new SteereoSocketCommunicator(strstr.str())); }
#include "simulation.h" #include <steereoSimSteering.h> #include <steereoSocketCommunicator.h> #include <steerParameterCommand.h> #include "meanValueCommand.h" int main (int argc, char** argv) { Simulation* sim = new Simulation(); SteereoSimSteering* simSteer = new SteereoSimSteering(); SteereoSocketCommunicator* simComm= new SteereoSocketCommunicator ("44445"); simSteer->setCommunicator (simComm); simSteer->registerCommand (&MeanValueCommand::generateNewInstance, "meanValueCommand"); SteerParameterCommand::registerScalarParameter ("attenuation", sim->getData(), &SimulationData::getAttenuation, &SimulationData::setAttenuation); SteerParameterCommand::registerArrayParameter ("dataArray", sim->getData()->getA(), sim->getData()->getDataLength()); MeanValueCommand::addData ("meanValueCommand", sim->getData()); MeanValueCommand::addDataSize("meanValueCommand", sim->getData()->getDataLength()); simSteer->startListening (); while (true) { sim->oneStep(); sim->incrementSteps(); sim->getData()->print(); simSteer->processQueue (); sleep (1); } return 0; }
#include <iostream> #include <steereoClientSteering.h> #include <steereoSocketCommunicator.h> #include <steerParameterClient.h> #define DATASIZE 10 SteereoClientSteering* clientSteer; double meanValue = 0; void handleAnswer (request req) { if (strcmp (req.steeringMethod, "meanValueCommand") == 0) { clientSteer->getCommunicator()->receiveDouble(clientSteer->getConnection()->getDataConnection(), &meanValue); std::cout << "meanValueCommand returns the value " << meanValue <<std::endl; } } int main (int argc, char** argv) { clientSteer = new SteereoClientSteering; double* myDataArray = new double[DATASIZE]; double myAttenuation; for (int i = 0; i < DATASIZE; i++) { myDataArray[i] = 0; } SteerParameterClient* paramClient = new SteerParameterClient(); SteereoLogger::setOutputLevel(2); SteereoSocketCommunicator* clientComm = new SteereoSocketCommunicator; clientSteer->startConnection (clientComm, "127.0.0.1", "44445"); paramClient->setClient (clientSteer); paramClient->addAction (); paramClient->registerScalarParameter ("attenuation", &myAttenuation); paramClient->registerArrayParameter ("dataArray", myDataArray, DATASIZE); clientSteer->addAction("meanValueCommand", &handleAnswer); clientSteer->startAccepting(); std::string input = "\0"; while (input != "q") { std::cout << "(c)hange attenuation, (g)et attenuation, get (d)ata array, get (m)ean value or (q)uit ? "; std::cin >> input; if (input == "c") { std::cout << "input new attenuation: "; std::cin >> myAttenuation; paramClient->requestSetParameter ("attenuation"); while (!paramClient->isParameterUpdated ("attenuation")) {} } else if (input == "g") { paramClient->requestGetParameter ("attenuation"); while (!paramClient->isParameterUpdated ("attenuation")) {} std::cout << "The current attenuation is " << myAttenuation << std::endl; } else if (input == "m") { int commandID; commandID = clientSteer->makeRequest ("meanValueCommand", "0 9"); while (!clientSteer->hasBeenExecuted(commandID)) {} std::cout << "The mean value is " << meanValue << std::endl; } else if (input == "d") { paramClient->requestGetParameter ("dataArray"); while (!paramClient->isParameterUpdated ("dataArray")) {} std::cout << "The current data array is: "; for (int i = 0; i < DATASIZE; i++) { std::cout << myDataArray[i] << ", "; } std::cout << std::endl; } } clientSteer->closeConnections (); return 0; }
CXX = g++ CXXFLAGS = -O2 -g #-Wall -fmessage-length=0 STEEREO_INC=/usr/local/include/steereo STEEREO_LIB=/usr/local/lib/steereo LIBS = -L$(STEEREO_LIB) -lSteereo -ldl INCS = -I$(STEEREO_INC) OBJS_SIM = simulation.o simulationData.o simMain.o meanValueCommand.o OBJS_CLIENT = clientMain.o TARGET_SIM = simMain TARGET_CLIENT = clientMain all: $(TARGET_SIM) $(TARGET_CLIENT) %.o: %.cpp $(CXX) $(CXXFLAGS) $(INCS) -c $< -o $@ $(TARGET_SIM): $(OBJS_SIM) $(CXX) -o $@ $(OBJS_SIM) $(LIBS) $(TARGET_CLIENT): $(OBJS_CLIENT) $(CXX) -o $@ $(OBJS_CLIENT) $(LIBS) clean: rm -f $(OBJS_CLIENT) $(OBJS_SIM) $(TARGET_CLIENT) $(TARGET_SIM)