-
Notifications
You must be signed in to change notification settings - Fork 1
Java Program
The evolutionary algorithm runs on the Java side, the JEAF evolutionary algorithm framework is used as a basis to set up the process. JEAF provides a library of classes that can be used to configure and run different types of evolutionary algorithms. Three parts have to be defined in order for JEAF to work: An objective function class, a main class and a configuration file.
#JEAF Main Class
A JEAF main class example can be found here.
The JEAF main class has two main purposes, the first one is to create the objects necessary for the evolutionary algorithm to run, this also includes providing the location of the configuration file to these objects, and the second one is starting the simulator in headless mode for the simulation to run.
In order to start the simulator the ProcessBuilder
class is used. The ProcessBuilder
class provides a way to run terminal commands and programs in the host OS directly from Java. ProcessBuilder
requires a directory from which it can run the program, the directory where V-REP is located, and a file (System/log
) to which it can redirect the program's output.
String vrepcommand = new String("./vrep" + 0 + ".sh");
/* Initialize a v-rep simulator */
try {
ProcessBuilder qq = new ProcessBuilder(vrepcommand, "-h",
"/home/rodrigo/V-REP/Modular/Maze/MazeBuilder01.ttt");
qq.directory(new File("/home/rodrigo/V-REP/Vrep" + 0 + "/"));
File log = new File("Simout/log");
qq.redirectErrorStream(true);
qq.redirectOutput(Redirect.appendTo(log));
qq.start();
Thread.sleep(10000);
} catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
}
The class constructor receives the command to be run on the terminal, in this case vrepcommand
contains the V-REP simulator executable file name, -h
indicates that the simulator will be run on headless mode and the last part contains a file that is opened when the simulator starts.
To start the evolutionary algorithm an EAFFacade
, EvolutionaryAlgorithm
and StopTest
objects must be created, a built in random number generator must be initialized and the path to a configuration file must be provided:
EAFRandom.init(seed != -Long.MAX_VALUE ? seed : System
.currentTimeMillis());
EAFFacade facade = new EAFFacade();
EvolutionaryAlgorithm algorithm;
StopTest stopTest;
EAFRandom.init();
// Provide the configuration file location
algorithm = facade.createAlgorithm("./"
+ "bin/examples/EvoconfigMaze.xml");
stopTest = facade.createStopTest("./"
+ "bin/examples/EvoconfigMaze.xml");
JEAF will set up its classes to run the evolutionary algorithm based on the parameters specified in the configuration file. The evolutionary algorithm can then be started:
// Start the evolutionary process
facade.resolve(stopTest, algorithm);
Once the evolutionary algorithm has met any of the termination criteria specified in the configuration file the main class will proceed to close the simulator by issuing a killall -r vrep
command in the terminal, by using the ProcessBuilder
class again
// Kill all the v-rep processes
try {
ProcessBuilder qq = new ProcessBuilder("killall", "vrep" + 0);
File log = new File("Simout/log");
qq.redirectErrorStream(true);
qq.redirectOutput(Redirect.appendTo(log));
Process p = qq.start();
int exitVal = p.waitFor();
System.out.println("Terminated vrep" + 0 + " with error code "
+ exitVal);
} catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
}
#Objective Function Class
The objective function determines how the fitness is measured. A general objective function receives the information needed to evaluate an individual or a solution in the evolutionary algorithm, evaluates it and returns the corresponding fitness.
An objective function class example can be found here.
The objective function class uses the evaluate
method to evaluate an individual, this method receives and individual from the evolutionary algorithm as a real number (Double) array.
public double evaluate(double[] values) {
The values
array contents are then packed as a string signal by using the java remoteApi functions provided by V-REP:
// Pack Floats into one String data signal
FloatWA ControlParam = new FloatWA(3);
float[] CP = new float[3];
CP[0] = ampli;
CP[1] = offset;
CP[2] = phase;
ControlParam.setArray(CP);
CharWA strCP = new CharWA(1);
strCP.setArray(ControlParam.getCharArrayFromArray());
In the case of the example the individual parameters represent the CPG controller parameters to be used in all modules when doing the fitness test.
Other parameters necessary for the simulation are also packed into string signals to be sent to the simulator, this includes the number of modules to be used in simulation, their orientation and the maximum time for the simulation to take:
// Pack Integers into one String data signal
IntWA NumberandOri = new IntWA(Numberofmodules + 3);
int[] NO = new int[Numberofmodules + 3];
NO[0] = Numberofmodules;
NO[1] = MaxTime;
NO[2] = myRank;
for (int i = 3; i < Numberofmodules + 3; i++) {
NO[i] = orientation[i - 3];
}
NumberandOri.setArray(NO);
CharWA strNO = new CharWA(1);
strNO.setArray(NumberandOri.getCharArrayFromArray());
The environment setting is also sent:
// Maze Parameters (Already a string)
char[] mazeseq = new char[] { 's' }; // Default Maze Sequence
// char[] mazeseq = new char[]{'s','l','s','s','l','s','l','s','l','l'};
CharWA strSeq = new CharWA(1);
strSeq.setArray(mazeseq);
A vrep
object is created in order to communicate with the simulator, communication starts when vrep.simxStart()
is invoked and the previously packed signals are set in the simulation.
// Simulator interaction start
remoteApi vrep = new remoteApi(); // Create simulator control object
vrep.simxFinish(-1); // just in case, close all opened connections
// Connect with the corresponding simulator remote server
int clientID = vrep.simxStart("127.0.0.1", 19997, true,
true, 5000, 5);
if (clientID != -1) {
// Set Simulator signal values
vrep.simxSetStringSignal(clientID, "NumberandOri", strNO,
vrep.simx_opmode_oneshot_wait);
vrep.simxSetStringSignal(clientID, "ControlParam", strCP,
vrep.simx_opmode_oneshot_wait);
vrep.simxSetStringSignal(clientID, "Maze", strSeq,
vrep.simx_opmode_oneshot_wait);
Among the vrep.simxStart
method parameters the second one, 19997
in this case, indicates the port of the server to which the vrep
object will connect, this port number should be checked in the simulator's remoteApiConnections.txt file.
##Running the simulation
The method RunSimulation
is used then to run the now configured simulation test
rfitness = RunSimulation(vrep, clientID, MaxTime, myRank);
The RunSimulation
method starts the simulation and monitors whether the termination signal finished
is set:
// Start Simulation
int ret = vrep.simxStartSimulation(clientID,
vrep.simx_opmode_oneshot_wait);
System.out.println("Start: " + ret + " sim: " + threadnum);
// Setting up and waiting for finished flag
vrep.simxGetIntegerSignal(clientID, "finished", out,
vrep.simx_opmode_streaming);
out.setValue(0);
The finished
signal information may contain garbage or never be set, so an appropriate maximum time for the simulator to finish is defined and checked every time the signals is read:
while (out.getValue() == 0) {
if (vrep.simxGetIntegerSignal(clientID, "finished", out,
vrep.simx_opmode_buffer) == vrep.simx_return_ok) {
// We received a fitness signal and everything is ok
// System.out.println("Retrieved Signal: "+Integer.toString(out.getValue()));
} else {
// We have not received the fitness message and out may contain
// garbage so we set it up to 0 again
out.setValue(0);
stopTime = System.currentTimeMillis();
elapsedTime = stopTime - startTime;
if (elapsedTime > 300000) {
System.out
.println("Too much time has passed attempting to restart simulator");
fitnessout[0] = -1; // This signals that the output is not
// ok and the simulator should be
// restarted
fitnessout[1] = 1000;
return fitnessout;
}
}
}
Once finished
is set the simulation is stopped and the results are read, it is important to stop listening to the finished
signal since it could cause memory problems if left unattended:
// Stop listening to the finished signal
vrep.simxGetIntegerSignal(clientID, "finished", out,
vrep.simx_opmode_discontinue);
// Stop simulation
vrep.simxStopSimulation(clientID, vrep.simx_opmode_oneshot_wait);
// Read simulation results
ret = vrep.simxGetStringSignal(clientID, "Position", datastring,
vrep.simx_opmode_oneshot_wait);
if (ret != vrep.simx_return_ok) {
System.out.println("Position Signal not received");
}
out2.initArrayFromCharArray(datastring.getArray());
The results signal Position
data could also be corrupted or may never arrive, if this happens the RunSimulation
method returns an error by placing a -1
value in the first position of its return array. If everything is alright the method returns the fitness reported by the simulator in the second position of its return array:
if (out2.getArray().length == 0) {
// If out2 is empty the simulator is not working properly and should
// be restarted
System.out.println("out2 is empty");
fitnessout[0] = -1; // This signals that the output is not ok and
// the simulator should be restarted
fitnessout[1] = 1000;
return fitnessout;
}
fitnessout[0] = 0;
if (out2.getArray()[0] == 0) {
// The robot could get out of the maze so the fitness is the time it
// spent
fitnessout[1] = out2.getArray()[1] * 0.01f;
} else if (out2.getArray()[1] == 0) {
// The robot could not get out of the maze so the fitness is the
// distance to goal + the maximum time allowed
fitnessout[1] = out2.getArray()[0] + (float) MaxTime * 0.01f;
}
// Return the fitness of the run
return fitnessout;
After the simulation is run communication with the simulator server is terminated:
vrep.simxFinish(clientID);
The total fitness is then calculated depending on the the type of test, for this example no special calculations are performed so the fitness is exactly the same one that was returned from the simulator, finally the evaluate
method returns the total fitness:
double fitnessd;
fitnessd = fitness;
return fitnessd;
If the objective function class fails to communicate with the simulator it will retry a set number of times:
// Number of retries in case of simulator crash
int maxTries = 5;
// Retry if there is a simulator crash
for (int j = 0; j < maxTries; j++) {
// Simulator interaction start
remoteApi vrep = new remoteApi(); // Create simulator control object
vrep.simxFinish(-1); // just in case, close all opened connections
// Connect with the corresponding simulator remote server
int clientID = vrep.simxStart("127.0.0.1", 19997 - myRank, true,
true, 5000, 5);
if (clientID != -1) {
.
.
.
} else {
// No connection could be established
System.out.println("Failed connecting to remote API server");
System.out.println("Trying again for the " + j + " time");
continue;
}
break;
}
Also if the data returned from the simulator is not received correctly, or not sent at all, the program will attempt to restart the simulator itself by using the RestartSim()
method:
if (rfitness[0] == -1) {
RestartSim(myRank, j);
continue;
}
This method uses the ProcessBuilder
class to terminate the simulator using the killall
linux command and restarts it in headless mode with an scene opened. Check the JEAF Main Class section to see a similar process.
#JEAF Configuration File
The configuration file is an XML formatted file that contains all the configuration parameters for JEAF to run a specific evolutionary algorithm.
An example configuration file can be found here.
The configuration file begins by defining the type of algorithm that JEAF is going to run as well as how the fitness is going to be compared, that is if it is going to be maximized or minimized.
<EvolutionaryAlgorithm>
<Class>es.udc.gii.common.eaf.algorithm.EvolutionaryStrategy</Class>
<Comparator>es.udc.gii.common.eaf.algorithm.fitness.comparator.MinimizingFitnessComparator</Comparator>
Next the type of evaluation strategy should be defined, in the example a serial evaluation strategy is used, that means that individuals will be evaluated by the objective function one after the other.
<EvaluationStrategy>
<Class>es.udc.gii.common.eaf.algorithm.evaluate.SerialEvaluationStrategy</Class>
</EvaluationStrategy>
Population size and the type of individual comes next, since the example program uses individuals described by an array of 3 real numbers, representing the controller parameters, the chromosome size is 3.
<Population>
<Size>5</Size>
<Individual>
<Class> es.udc.gii.common.eaf.algorithm.population.Individual</Class>
<Chromosome size="3">
</Chromosome>
</Individual>
</Population>
Next the mutation and crossover operators should be defined, in the example differential evolution is used so the operators should also define the special parameters F and CR used in this type of evolutionary strategies.
<OperatorChains>
<ReproductionChain>
<Operator>
<Class>es.udc.gii.common.eaf.algorithm.operator.reproduction.mutation.EvolutionaryStrategyMutation</Class>
<Operator>es.udc.gii.common.eaf.algorithm.operator.reproduction.mutation.de.DEMutationOperator</Operator>
<MutationStrategy>
<Class>es.udc.gii.common.eaf.algorithm.operator.reproduction.mutation.de.mutationStrategy.RandomDEMutationStrategy</Class>
<F>
<Class>es.udc.gii.common.eaf.plugin.parameter.Constant</Class>
<Value>0.9</Value>
</F>
<DiffVector>1</DiffVector>
</MutationStrategy>
<CrossOverScheme>
<Class>es.udc.gii.common.eaf.algorithm.operator.reproduction.mutation.de.crossover.BinCrossOverScheme</Class>
<CR>
<Class>es.udc.gii.common.eaf.plugin.parameter.Constant</Class>
<Value>0.9</Value>
</CR>
</CrossOverScheme>
</Operator>
</ReproductionChain>
<ReplaceChain>
<Operator>
<Class>es.udc.gii.common.eaf.algorithm.operator.replace.EvolutionaryStrategyReplaceOperator</Class>
</Operator>
</ReplaceChain>
The objective function class location is specified next.
<Objective>
<CheckBounds/>
<ObjectiveFunction>
<Class>examples.CalcFitnessMaze</Class>
</ObjectiveFunction>
Several types of stop tests can be used, for example a maximum number of generations stop test is used here.
<StopTests>
<StopTest>
<Class>es.udc.gii.common.eaf.stoptest.EvolveGenerationsStopTest</Class>
<Generations>3</Generations>
</StopTest>
Finally various ways of recording the results are provided within JEAF, in the case of the example the best individual per generation and the mean of the best individual among generations are recorded by using the BestMeanLogTool
and BestIndividualLogTool
classes.
<LogTool>
<Log>
<Class>es.udc.gii.common.eaf.log.BestMeanLogTool</Class>
<Folder>Testout</Folder>
<Name>TestEx</Name>
</Log>
<Log>
<Class>es.udc.gii.common.eaf.log.BestIndividualLogTool</Class>
<Folder>Testout</Folder>
<Name>BestEx</Name>
<Number>1</Number>
</Log>