Table of Contents

Howto

Introduction

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.

Small example simulation

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.

simMain.cpp
#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.

simulation.h
#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
simulation.cpp
#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.

simulationData.h
#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
simulationData.cpp
#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):

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

Enable basic Steering Capabilities in the simulation

The steps to enable Steering in your simulations are similar every time:

  1. Create an instance of SteereoSimSteering
  2. Create an instance of a class derived from SteereoCommunicator (e.g. SteereoSocketCommunicator) and assign it to the SteereoSimSteering instance
  3. start listening to requests from clients (with a method of the SteereoSimSteering object)
  4. process the queue with all the requests the Simulation got (a good place is at the end of your simulation queue)

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)

Using Parameter Steering

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:

simMain.cpp
#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

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

Basic Steering Client

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

  1. Create an instance of SteereoClientSteering
  2. Create an instance of a class derived from SteereoCommunicator (e.g. SteereoSocketCommunicator) and assign it to the SteereoClientSteering instance
  3. start a connection to the simulation
  4. start listening (accepting) to the responses for the sent requests
  5. when done, close the connection to the simulation

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:

clientMain.cpp
#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:

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

Client for ParameterSteering

Setup

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:

  1. Create an instance of SteerParameterClient
  2. Assign a SteereoClientSteering instance to it
  3. To let SteerParameterClient handle the responses from the simulation, call the method addAction().
  4. Register the variables you want to steer and assign them memory on client side

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

Make set and get requests

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

clientMain.cpp
#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.

Writing Own Commands

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:

  1. setting the command name inside the constructor
  2. implement the execute function, as it contains the main functionality !
  3. implement the generateNewInstance function, which handles the generation of new Instances of this command. Normally a “return new commandName” should suffice, but sometimes you could want to pass some parameters on.

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

meanValueCommand.h
#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
meanValueCommand.cpp
#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).

Using own Commands

As the command is now written, we should let the simulation and the client know about this.

Modifications in Simulation

In the simulation you have to:

  1. register the command with its name and generateNewInstance function
  2. set the data and data size, if needed in the command

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.

Modifications in 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.

Parallel Steering

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

Complete simulation and client with Makefile

simMain.cpp
#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;
}
clientMain.cpp
#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;
}
Makefile
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)