evorobotexperiment.cpp
1 /********************************************************************************
2  * FARSA Experiments Library *
3  * Copyright (C) 2007-2012 *
4  * Stefano Nolfi <stefano.nolfi@istc.cnr.it> *
5  * Onofrio Gigliotta <onofrio.gigliotta@istc.cnr.it> *
6  * Gianluca Massera <emmegian@yahoo.it> *
7  * Tomassino Ferrauto <tomassino.ferrauto@istc.cnr.it> *
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  * This program is distributed in the hope that it will be useful, *
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
17  * GNU General Public License for more details. *
18  * *
19  * You should have received a copy of the GNU General Public License *
20  * along with this program; if not, write to the Free Software *
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
22  ********************************************************************************/
23 
24 #include "evorobotexperiment.h"
25 #include "sensors.h"
26 #include "motors.h"
27 #include "evoga.h"
28 #include "configurationhelper.h"
29 #include "factory.h"
30 #include "logger.h"
31 
32 #include <QFile>
33 #include <QTextStream>
34 #include <QString>
35 #include <QTime>
36 #include <QThread>
37 #include <iostream>
38 #include <cstdlib>
39 
40 // All the suff below is to avoid warnings on Windows about the use of unsafe
41 // functions. This should be only a temporary workaround, the solution is stop
42 // using C string and file functions...
43 #if defined(_MSC_VER)
44  #pragma warning(push)
45  #pragma warning(disable:4996)
46 #endif
47 
48 namespace farsa {
49 
50 /*
51  * Experiment constructor
52  */
54 {
55  ga = NULL;
56  world = NULL;
57  savedConfigurationParameters = NULL;
58  savedPrefix = NULL;
59  ntrial = 0;
60  stopCurrentTrial = false;
61  skipCurrentTrial = false;
62  restartCurrentTrial = false;
63  endCurrentIndividualLife = false;
64  arena = NULL;
65  agentIdSelected = 0;
66 
67  gaPhase = NONE;
68 
69  // fitness and additional fitness components are not displayed by default
70 
71  // By default we don't run in batch mode
72  batchRunning = false;
73 
74  // Stating which resources we use here. This is here in the constructor so that we are sure to
75  // be the first to declare resources (if we did this later we should have used addUsableResources
76  // because child classes could declare they will use resources before us)
77  usableResources( QStringList() << "world" << "arena" << "experiment" << "robot" << "evonet" << "neuronsIterator" );
78 }
79 
81 {
82  // Removing resources
83  try {
84  deleteResource("experiment");
85  deleteResource("arena");
86  deleteResource("world");
87  deleteResource("robot");
88  deleteResource("evonet");
89  deleteResource("neuronsIterator");
90  } catch (...) {
91  // Doing nothing, this is here just to prevent throwing an exception from the destructor
92  }
93 
94  foreach( EmbodiedAgent* agent, eagents ) {
95  delete agent;
96  }
97 
98  delete savedConfigurationParameters;
99  delete savedPrefix;
100  delete arena;
101  delete world;
102 }
103 
105 {
106  // Saving configuration parameters and prefix for cloning
107  delete savedConfigurationParameters;
108  delete savedPrefix;
109  savedConfigurationParameters = new ConfigurationParameters(params);
110  savedConfigurationParameters->shareObserversWith(params);
111  savedPrefix = new QString(prefix);
112  // Setting ourself as resource manager in the configuration parameters object
113  params.setResourcesUser(this);
114  savedConfigurationParameters->setResourcesUser(this);
115 
116  ntrials = 1;
117  nsteps = 1;
118 
119  batchRunning = ConfigurationHelper::getBool(params, "__INTERNAL__/BatchRunning", batchRunning); // If we are running in batch or not
120  ntrials = ConfigurationHelper::getInt(params, prefix + "ntrials", ntrials); // number of trials to do
121  nsteps = ConfigurationHelper::getInt(params, prefix + "nsteps", nsteps); // number of step for each trial
122 
123  // create a World by default in order to exit from here with all configured properly
124  // if they are already created it will not destroy and recreate
125  recreateWorld();
126  // Creates the arena (if the group Arena is present)
127  recreateArena();
128  // Creates the Embodied Agents
129  // number of agents to create
130  int nagents = ConfigurationHelper::getInt(params, prefix + "nagents", 1);
131  for( int i=0; i<nagents; i++ ) {
132  eagents.append( new EmbodiedAgent(i,this) );
133  }
134  selectAgent(0);
135 
136  // declaring other resources
137  declareResource( "experiment", static_cast<ParameterSettableWithConfigureFunction*>(this) );
138 
139  Logger::info( params.getValue(prefix+"type") + " Configured" );
140 }
141 
143 {
144  Logger::error("NOT IMPLEMENTED (EvoRobotExperiment::save)");
145  abort();
146 }
147 
148 void EvoRobotExperiment::describe( QString type ) {
149  Descriptor d = addTypeDescription( type, "The experimental setup that defines the conditions and the fitness function of the evolutionary experiment" );
150  d.describeInt( "ntrials" ).def(1).limits(1,MaxInteger).runtime( &EvoRobotExperiment::setNTrials, &EvoRobotExperiment::getNTrials ).help("The number of trials the individual will be tested to calculate its fitness");
151  d.describeInt( "nsteps" ).def(1).limits(1,MaxInteger).help("The number of step a trials will last");
152  d.describeInt( "nagents" ).def(1).limits(1,MaxInteger).help("The number of embodied agents to create", "This parameter allow to setup experiments with more than one robot; all agents are clones");
153  d.describeSubgroup( "NET" ).props( IsMandatory ).type( "Evonet" ).help( "The Neural Network controlling the robot");
154  d.describeSubgroup( "ROBOT" ).props( IsMandatory ).type( "Robot" ).help( "The robot");
155  d.describeSubgroup( "Sensor" ).props( AllowMultiple ).type( "Sensor" ).help( "One of the Sensors from which the neural network will receive information about the environment" );
156  d.describeSubgroup( "Motor" ).props( AllowMultiple ).type( "Motor" ).help( "One of the Motors with which the neural network acts on the robot and on the environment" );
157  d.describeSubgroup( "Arena" ).type( "Arena" ).help( "The arena where robots live");
158 
159  SubgroupDescriptor world = d.describeSubgroup( "World" ).help( "Parameters affecting the simulated World" );
160  world.describeReal( "timestep" ).def(0.05).runtime( &EvoRobotExperiment::setWorldTimestep, &EvoRobotExperiment::getWorldTimeStep ).help( "The time in seconds corresponding to one simulated step of the World" );
161 }
162 
164 {
165  if (!batchRunning) {
166  // preventing gas from using multithread, which is not supported if the GUI is present
168  }
169  // Doing evolution by default
170  gaPhase=EvoRobotExperiment::INEVOLUTION;
171 }
172 
173 void EvoRobotExperiment::doTrial()
174 {
175  restartCurrentTrial = false;
176  stopCurrentTrial = false;
177  trialFitnessValue = 0.0;
178  for(nstep = 0; nstep < nsteps; nstep++) {
179  initStep( nstep );
180  if ( ga->commitStep() || restartCurrentTrial ) {
181  break;
182  }
183  doStep();
184  if ( ga->commitStep() ) break;
185  endStep( nstep );
186  if (ga->commitStep() || stopCurrentTrial || restartCurrentTrial) {
187  break;
188  }
189  }
190 }
191 
193 {
194  endCurrentIndividualLife = false;
195  totalFitnessValue = 0.0;
196 
197  initIndividual(individual);
198  if ( ga->commitStep() ) return;
199 
200  for (ntrial = 0; ntrial < ntrials; ntrial++) {
201  skipCurrentTrial = false;
202 
203  initTrial(ntrial);
204  if ( ga->commitStep() ) break;
205  if (skipCurrentTrial) { // && !ga->commitStep()) {
206  continue;
207  }
208  if (!endCurrentIndividualLife) {
209  doTrial();
210  }
211  if ( ga->isStopped() ) break;
212  if (restartCurrentTrial) {
213  ntrial--;
214  continue;
215  }
216  endTrial(ntrial);
217 
218  if (gaPhase == INTEST) {
219  Logger::info("Fitness for trial: " + QString::number(trialFitnessValue));
220  }
221 
222  if ( ga->commitStep() || endCurrentIndividualLife ) {
223  break;
224  }
225 
226  }
227 
228  endIndividual(individual);
229  ga->commitStep();
230 }
231 
233 {
234 }
235 
237 {
238 }
239 
241 {
242  // reset the neural controller
243  ResourcesLocker locker(this);
244  foreach( EmbodiedAgent* agent, eagents ) {
245  agent->evonet->resetNet();
246  }
247 }
248 
250 {
251 }
252 
254 {
255  return totalFitnessValue;
256 }
257 
259 {
260 }
261 
263 {
264 }
265 
267 {
268 }
269 
271 {
273 }
274 
276 {
277 }
278 
280 {
281 }
282 
283 void EvoRobotExperiment::doStep()
284 {
285  // There is no getResource below, but we are actually using resources so we must take the lock.
286  // We don't acquire the lock here, but lock and unlock when needed in the body of the function
287  ResourcesLocker locker(this, false);
288 
289  // update sensors
290  foreach( EmbodiedAgent* agent, eagents ) {
291  for (int s = 0; s < agent->sensors.size(); s++) {
292  agent->sensors[s]->update();
293  }
294  }
296  // update the neural controller
297  locker.lock();
298  foreach( EmbodiedAgent* agent, eagents ) {
299  agent->evonet->updateNet();
300  }
301  locker.unlock();
302  if (!batchRunning) {
303  // To use platform-independent sleep functions we have to do this...
304  class T : public QThread
305  {
306  public:
307  using QThread::sleep;
308  using QThread::msleep;
309  using QThread::usleep;
310  };
311  // We sleep for few milliseconds when the GUI is active to avoid flooding it with
312  // events and freeze it
313  T::msleep(3);
314  }
316  // setting motors
317  foreach( EmbodiedAgent* agent, eagents ) {
318  for (int m = 0; m < agent->motors.size(); m++) {
319  agent->motors[m]->update();
320  }
321  }
323  // advance the world simulation
324  locker.lock();
325  world->advance();
326  locker.unlock();
327 }
328 
330 {
331  stopCurrentTrial = true;
332 }
333 
335 {
336  skipCurrentTrial = true;
337 }
338 
340 {
341  restartCurrentTrial = true;
342 }
343 
345 {
346  endCurrentIndividualLife = true;
347  stopCurrentTrial = true;
348 }
349 
351 {
352  ResourcesLocker locker(this);
353 
354  return eagents[0]->evonet->freeParameters();
355 }
356 
357 Sensor* EvoRobotExperiment::getSensor( QString name, int id ) {
358  if ( eagents[id]->sensorsMap.contains( name ) ) {
359  return eagents[id]->sensorsMap[name];
360  } else {
361  Logger::error( "getSensor returned NULL pointer because there is no sensor named "+name+" in the agent "+QString::number(id) );
362  return NULL;
363  }
364 }
365 
366 Motor* EvoRobotExperiment::getMotor( QString name, int id ) {
367  if ( eagents[id]->motorsMap.contains( name ) ) {
368  return eagents[id]->motorsMap[name];
369  } else {
370  Logger::error( "getMotor returned NULL pointer because there is no motor named "+name+" in the agent "+QString::number(id) );
371  return NULL;
372  }
373 }
374 
376 {
377  return batchRunning;
378 }
379 
381  return eagents.size();
382 }
383 
385  agentIdSelected = id;
386  declareResource( "robot",
387  eagents[agentIdSelected]->robot,
388  eagents[agentIdSelected]->resourcePrefix+"robot" );
389  declareResource( "evonet",
390  static_cast<farsa::ParameterSettable*>(eagents[agentIdSelected]->evonet),
391  eagents[agentIdSelected]->resourcePrefix+"evonet" );
392  declareResource( "neuronsIterator",
393  eagents[agentIdSelected]->neuronsIterator,
394  eagents[agentIdSelected]->resourcePrefix+"neuronsIterator" );
395 }
396 
398 {
399  return eagents[id]->evonet;
400 }
401 
403 {
404  this->ga = ga;
405 }
406 
408  return ga;
409 }
410 
412 {
413  ResourcesLocker locker(this);
414  foreach( EmbodiedAgent* agent, eagents ) {
415  agent->evonet->getParameters(genes);
416  }
417 }
418 
420 {
421  Logger::error("EvoRobotExperiment::setTestingAgentAndSeed() not yet implemented");
422 }
423 
425  // Saving the old robot, the old arena and world to delete them after the new world has been
426  // created (as world is a resource, we need the old instance to exists during notifications.
427  // It can be safely deleted afterward)
428  World* const old_world = world;
429 
430  // TODO: parametrize the name and the dontUseYarp and all other parameters
431  world = new World( "World", true );
432  world->setTimeStep(0.05f);
433  world->setSize( wVector( -2.0f, -2.0f, -0.50f ), wVector( +2.0f, +2.0f, +2.0f ) );
434  world->setFrictionModel( "exact" );
435  world->setSolverModel( "exact" );
436  world->setMultiThread( 1 );
437 
438  // Removing deleted resources (if they existed) and then re-declaring world
439  if ( arena != NULL ) {
440  deleteResource( "arena" );
441  delete arena;
442  arena = NULL;
443  }
444 
445  if ( eagents.size() > 0 ) {
446  deleteResource( "robot" );
447  for( int i=0; i<eagents.size(); i++ ) {
448  deleteResource( eagents[i]->resourcePrefix+"robot" );
449  delete eagents[i]->robot;
450  }
451  }
452 
453  declareResource( "world", world );
454 
455  // Now we can actually free memory
456  delete old_world;
457 }
458 
460  eagents[id]->recreateRobot();
461  if ( id == agentIdSelected ) {
462  // rebind the resource of the robot
463  declareResource( "robot",
464  eagents[agentIdSelected]->robot,
465  eagents[agentIdSelected]->resourcePrefix+"robot" );
466  }
467 }
468 
470  // First of all we need to check whether there is an Arena group or not
471  if (!ConfigurationHelper::hasGroup( *savedConfigurationParameters, (*savedPrefix) + "Arena" ) ) {
472  // This is just to be sure...
473  arena = NULL;
474  return;
475  }
476 
477  // to be sure that a World exist
478  if ( !world ) {
479  recreateWorld();
480  }
481 
482  // Taking lock because we need to use world
483  ResourcesLocker locker(this);
484  // Saving the old arena to delete it after the new arena has been created
485  Arena* const old_arena = arena;
486 
487  // Now creating the arena. We first set ourself as the resouce manager
488  savedConfigurationParameters->setResourcesUser(this);
489  arena = savedConfigurationParameters->getObjectFromGroup<Arena>((*savedPrefix) + "Arena");
490  arena->shareResourcesWith(this);
491  arena->addRobots(QStringList() << "robot");
492 
493  // Unlocking before redeclaring the arena resource
494  locker.unlock();
495 
496  declareResource("arena", static_cast<Resource*>(arena), "world");
497  delete old_arena;
498 }
499 
501  eagents[id]->recreateNeuralNetwork();
502  if ( id == agentIdSelected ) {
503  // rebind the resource of the evonet
504  declareResource( "evonet",
505  eagents[agentIdSelected]->robot,
506  eagents[agentIdSelected]->resourcePrefix+"evonet" );
507  }
508 }
509 
510 void EvoRobotExperiment::setWorldTimestep( float timestep ) {
511  ResourcesLocker locker(this);
512  if ( !world ) return;
513  world->setTimeStep( timestep );
514 }
515 
516 float EvoRobotExperiment::getWorldTimeStep() const {
517  ResourcesLocker locker((ConcurrentResourcesUser*)(this));
518  if ( !world ) {
519  return 0.05;
520  } else {
521  return world->timeStep();
522  }
523 }
524 
525 EvoRobotExperiment::EmbodiedAgent::EmbodiedAgent( int id, EvoRobotExperiment* exp ) {
526  this->id = id;
527  this->exp = exp;
528  evonet = NULL;
529  neuronsIterator = new EvonetIterator();
530  robot = NULL;
531  resourcePrefix = QString("agent[%1]:").arg(id);
532 
533  exp->savedConfigurationParameters->setResourcesUser(exp);
534  // add to the experiment the resources will create here
535  exp->addUsableResource( resourcePrefix+"evonet" );
536  recreateRobot();
537 
538  // Reading the sensors parameters. For each sensor there must be a subgroup Sensor:NN where NN is a progressive number
539  // (needed to specify the sensors order). Here we also actually create sensors
540  QStringList sensorsList = exp->savedConfigurationParameters->getGroupsWithPrefixList((*(exp->savedPrefix)), "Sensor:");
541  sensorsList.sort();
542  foreach( QString sensorGroup, sensorsList ) {
543  // Trick for injecting the correct resource names for robot and neuronsIterator
544  // !! WARNING !! this tricks works for some specific group of sensors
545  // if some others will be added, also this trick has to be adapted
546  exp->savedConfigurationParameters->createParameter(
547  (*(exp->savedPrefix))+sensorGroup, "neuronsIterator", resourcePrefix+"neuronsIterator" );
548  exp->savedConfigurationParameters->createParameter(
549  (*(exp->savedPrefix))+sensorGroup, "icub", resourcePrefix+"robot" );
550  exp->savedConfigurationParameters->createParameter(
551  (*(exp->savedPrefix))+sensorGroup, "marxbot", resourcePrefix+"robot" );
552  exp->savedConfigurationParameters->createParameter(
553  (*(exp->savedPrefix))+sensorGroup, "epuck", resourcePrefix+"robot" );
554  // !! END OF TRICK !!
555  Sensor* sensor = exp->savedConfigurationParameters->getObjectFromGroup<Sensor>((*(exp->savedPrefix)) + sensorGroup);
556  if ( sensor == NULL ) {
557  Logger::error("Cannot create the Sensor from group " + *(exp->savedPrefix) + sensorGroup + ". Aborting");
558  abort();
559  }
560  // in order to avoid name clash when using more than one Sensor,
561  // the Sensors are renamed using the same name of the Group when they don't have a name assigned
562  if ( sensor->name() == QString("unnamed") ) {
563  sensor->setName( sensorGroup );
564  }
565  sensors.append( sensor );
566  sensors.last()->shareResourcesWith( exp );
567  Logger::info( "Created a Sensor named "+sensor->name() );
568  // if the user manually set the name and create a name clash, it is only reported as error in Logger
569  if ( sensorsMap.contains( sensor->name() ) ) {
570  Logger::error( "More than one sensor has name "+sensor->name()+" !! The name has to be unique !!" );
571  } else {
572  // add to the map
573  sensorsMap[sensor->name()] = sensor;
574  }
575  }
576 
577  // Now we do for motors what we did for sensors. Motor groups are in the form Motor:NN
578  QStringList motorsList = exp->savedConfigurationParameters->getGroupsWithPrefixList((*(exp->savedPrefix)), "Motor:");
579  motorsList.sort();
580  foreach( QString motorGroup, motorsList ) {
581  // Trick for injecting the correct resource names for robot and neuronsIterator
582  // !! WARNING !! this tricks works for some specific group of sensors
583  // if some others will be added, also this trick has to be adapted
584  exp->savedConfigurationParameters->createParameter(
585  (*(exp->savedPrefix))+motorGroup, "neuronsIterator", resourcePrefix+"neuronsIterator" );
586  exp->savedConfigurationParameters->createParameter(
587  (*(exp->savedPrefix))+motorGroup, "icub", resourcePrefix+"robot" );
588  exp->savedConfigurationParameters->createParameter(
589  (*(exp->savedPrefix))+motorGroup, "marxbot", resourcePrefix+"robot" );
590  exp->savedConfigurationParameters->createParameter(
591  (*(exp->savedPrefix))+motorGroup, "epuck", resourcePrefix+"robot" );
592  // !! END OF TRICK !!
593  Motor* motor = exp->savedConfigurationParameters->getObjectFromGroup<Motor>((*(exp->savedPrefix)) + motorGroup);
594  if (motor == NULL) {
595  Logger::error("Cannot create the Motor from group " + *(exp->savedPrefix) + motorGroup + ". Aborting");
596  abort();
597  }
598  // in order to avoid name clash when using more than one Motor,
599  // the Motors are renamed using the same name of the Group when they don't have a name assigned
600  if ( motor->name() == QString("unnamed") ) {
601  motor->setName( motorGroup );
602  }
603  motors.append( motor );
604  motors.last()->shareResourcesWith( exp );
605  Logger::info( "Created a Motor named "+motor->name() );
606  // if the user manually set the name and create a name clash, it is only reported as error in Logger
607  if ( motorsMap.contains( motor->name() ) ) {
608  Logger::error( "More than one motor has name "+motor->name()+" !! The name has to be unique !!" );
609  } else {
610  // add to the map
611  motorsMap[motor->name()] = motor;
612  }
613  }
614 
615  recreateNeuralNetwork();
616 
617  exp->declareResource( resourcePrefix+"neuronsIterator", neuronsIterator, resourcePrefix+"evonet" );
618 }
619 
620 EvoRobotExperiment::EmbodiedAgent::~EmbodiedAgent() {
621  delete robot;
622  delete evonet;
623  delete neuronsIterator;
624  for (int i = 0; i < sensors.size(); i++) {
625  delete sensors[i];
626  }
627  for (int i = 0; i < motors.size(); i++) {
628  delete motors[i];
629  }
630 }
631 
632 void EvoRobotExperiment::EmbodiedAgent::recreateRobot() {
633  // to be sure that a World exist
634  if ( !(exp->world) ) {
635  exp->recreateWorld();
636  }
637 
638  // Taking lock because we need to use world
639  ResourcesLocker locker(exp);
640  // Saving the old robot to delete it after the new robot has been created
641  Robot* const old_robot = robot;
642 
643  // Now creating the robot
644  exp->savedConfigurationParameters->setResourcesUser(exp);
645  robot = exp->savedConfigurationParameters->getObjectFromGroup<Robot>((*(exp->savedPrefix)) + "ROBOT");
646 
647  // Unlocking before redeclaring the robot resource
648  locker.unlock();
649 
650  exp->declareResource( resourcePrefix+"robot", robot, "world" );
651  delete old_robot;
652 }
653 
654 void EvoRobotExperiment::EmbodiedAgent::recreateNeuralNetwork() {
655  // Saving the old evonet to delete it after the new evonet has been created
656  Evonet* const old_evonet = evonet;
657 
658  // Check the subgroup [NET]
659  if ( exp->savedConfigurationParameters->getValue( (*(exp->savedPrefix))+"NET/netFile" ).isEmpty() ) {
660  // calculate the number of sensors and motors neurons
661  int nSensors = 0;
662  foreach( Sensor* sensor, sensors ) {
663  nSensors += sensor->size();
664  }
665  int nMotors = 0;
666  foreach( Motor* motor, motors ) {
667  nMotors += motor->size();
668  }
669  // it inject the calculated nSensor and nMotors
670  exp->savedConfigurationParameters->createParameter( (*(exp->savedPrefix))+"NET", "nSensors", QString::number(nSensors) );
671  exp->savedConfigurationParameters->createParameter( (*(exp->savedPrefix))+"NET", "nMotors", QString::number(nMotors) );
672  }
673  // Now creating the neural network. We first set ourself as the resouce manager, then we lock resources (because during configuration
674  // evonet could use resources, but the resource user it will use is not thread safe (SimpleResourceUser))
675  ResourcesLocker locker(exp);
676  exp->savedConfigurationParameters->setResourcesUser(exp);
677  evonet = exp->savedConfigurationParameters->getObjectFromGroup<Evonet>( (*(exp->savedPrefix))+"NET" );
678  locker.unlock();
679 
680  exp->declareResource( resourcePrefix+"evonet", static_cast<farsa::ParameterSettable*>(evonet) );
681 
682  // Here we have to take the lock again because we are going to change neuronsIterator
683  locker.lock();
684  delete old_evonet;
685  neuronsIterator->setEvonet( evonet );
686  // create the blocks associated to the network
687  int startIndex = 0;
688  foreach( Sensor* sensor, sensors ) {
689  neuronsIterator->defineBlock( sensor->name(), EvonetIterator::InputLayer, startIndex, sensor->size() );
690  startIndex += sensor->size();
691  }
692  startIndex = 0;
693  foreach( Motor* motor, motors ) {
694  neuronsIterator->defineBlock( motor->name(), EvonetIterator::OutputLayer, startIndex, motor->size() );
695  startIndex += motor->size();
696  }
697 }
698 
699 } // end namespace farsa
700 
701 // All the suff below is to restore the warning state on Windows
702 #if defined(_MSC_VER)
703  #pragma warning(pop)
704 #endif