FREVO Tutorial - New Problem

This tutorial explains how to model and implement an exampe problem in FREVO. FREVO (see www.frevotool.tk) is an open-source framework developed in Java to help engineers and scientists in evolutionary design and optimization tasks. The major feature of FREVO is the component-wise decomposition and separation of the key building blocks for an optimization task. We identify these as the problem definition, solution representation and the optimization method.

In this tutorial we will implement a problem definition. In particular, it will be a simulation where agents try to find an emergency exit. As a prerequisite, you should have installed Java, eclipse, downloaded Frevo and imported it as an eclipse project.

Let's start:

Image:FindingComponentCreator.jpg

Click on "Create" and follow the instructions that are shown. A new folder, containing your *.java file, and an *.xml file will be generated. (in this case "EmergencyExit.java" and "EmergencyExit.xml")

 package emergencyexit;

 

 import interfaces.IProblem;

 import interfaces.IRepresentation;

 

 public class EmergencyExit2 extends IProblem {

 

        @Override

        public double getResult(IRepresentation[] candidates) {

               // TODO Auto-generated method stub

               return 0;

        }

 

        @Override

        public void replayWithVisualization(IRepresentation[] candidates) {

               // TODO Auto-generated method stub

 

        }

 

 }

o    getResult() is called to simulate without visualization, it should return a fitness value.

o    replayWithVisualization() is (as the name says) called to replay the simulation with a (possible graphical) visualization.

For this tutorial I started with a very simple simulation:

  int steps;

  int xpositionofEmergencyExit = 0;

  int ypositionofEmergencyExit = 0;

  int width;

  int height;

  int xpositionofAgent;

  int ypositionofAgent;

  IRepresentation[] c;

 

void calcSim(){

  

    xpositionofEmergencyExit = Integer.parseInt(getProperties().get("xpositionofEmergencyExit").getValue());

    ypositionofEmergencyExit = Integer.parseInt(getProperties().get("ypositionofEmergencyExit").getValue());

    xpositionofAgent = Integer.parseInt(getProperties().get("xpositionofAgent").getValue());

    ypositionofAgent = Integer.parseInt(getProperties().get("ypositionofAgent").getValue());

  

    for (int step = 0; step < steps; step++) {

      Vector<Float> input = new Vector<Float>();

      input.add((float) (xpositionofEmergencyExit - xpositionofAgent));

      input.add((float) (ypositionofEmergencyExit - ypositionofAgent));

 

      Vector<Float> output = c[0].getOutput(input);

 

      float xVelocity = output.get(0).floatValue()*2.0f-1.0f;

      float yVelocity = output.get(1).floatValue()*2.0f-1.0f;

 

      if /* */(xVelocity >= 1.0 && xpositionofAgent < width - 1) xpositionofAgent += 1;

      else if (xVelocity <= -1.0 && xpositionofAgent > 0 /*   */) xpositionofAgent -= 1;

      if /* */(yVelocity >= 1.0 && ypositionofAgent < height - 1) ypositionofAgent += 1;

      else if (yVelocity <= -1.0 && ypositionofAgent > 0 /*    */) ypositionofAgent -= 1;

    }

  }

The position of the emergency exit and the agent are read from the *.xml file which is accessed getProperties().get(name).getValue(). name represents the name of the value in the *.xml file. The value of "steps", "width" and "height" are written in the functions getResult() and replayWithVisualization(). The main function of FREVO is to find the best function between input and the output automatically. So you just have to collect all the inputs and the representation (here it is c[0]) will return the output. It is important that all the inputs and all the outputs are always in the same order. The output is always a float value between 0.0 and 1.0. You have to decide how to handle these outputs. In this simulation the output defines how the agent moves.

public double getResult(IRepresentation[] candidates) {

    steps = Integer.parseInt(getProperties().get("steps").getValue());

    width = Integer.parseInt(getProperties().get("width").getValue());

    height = Integer.parseInt(getProperties().get("height").getValue());

    c = candidates;

  

    calcSim();

  

    return -Math.sqrt(Math.pow((xpositionofEmergencyExit - xpositionofAgent), 2)

    /*            */+ Math.pow((ypositionofEmergencyExit - ypositionofAgent), 2));

  }

As we said before the values of “steps”, “width” and “height” have to be written in this function. They are read from the *.xml file. The return value of this function says how good this representation was. It says if this value is high, the connection of input and output is good. For the emergencyExit simulation this value is the negative distance between the agent and the emergency exit. So if the agent reaches the emergency exit within the amount of steps, the distance will be 0 and so it says it is a good way of connecting input and output.

So, we have to edit EmergencyExit.xml in order to add the component-specific properties:

  <properties>

        <entry key="xpositionofAgent" type="INT" value="50"/>

        <entry key="ypositionofAgent" type="INT" value="50"/>

        <entry key="xpositionofEmergencyExit" type="INT" value="99"/>

        <entry key="ypositionofEmergencyExit" type="INT" value="99"/>

        <entry key="width" type="INT" value="100"/>

        <entry key="height" type="INT" value="100"/>

        <entry key="steps" type="INT" value="50"/>

  </properties>

 

and to configure the number of inputs and outputs for the agent:

 

    <entry key="inputnumber" type="INT" value="2"/>

    <entry key="outputnumber" type="INT" value="2"/>

  WhiteBoard whiteboard;

  Display display;

 

  @Override

  public void replayWithVisualization(IRepresentation[] candidates) {

    steps = 0;

    c = candidates;

    width = Integer.parseInt(getProperties().get("width").getValue());

    height = Integer.parseInt(getProperties().get("height").getValue());

    display = new Display(440, 495, "SimplifiedEmergencyExit");

    display.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

    whiteboard = new WhiteBoard(400, 400, width, height, 1);

    whiteboard.addColorToScale(0, Color.WHITE);

    whiteboard.addColorToScale(1, Color.BLACK);

    whiteboard.addColorToScale(2, Color.GREEN);

    JButton minusbutton = new JButton("-");

    JButton plusbutton = new JButton("+");

    display.add(whiteboard);

    display.add(minusbutton);

    display.add(plusbutton);

    minusbutton.addActionListener(new ActionListener() {

 

      @Override

      public void actionPerformed(ActionEvent e) {

        if (steps > 0) steps--;

        calcSim();

        displayResult();

        display.setTitle("Simplified Emergency Exit    Step: " + steps);

      }

    });

    plusbutton.addActionListener(new ActionListener() {

 

      @Override

      public void actionPerformed(ActionEvent e) {

        steps++;

        calcSim();

        displayResult();

        display.setTitle("Simplified Emergency Exit    Step: " + steps);

      }

    });

    display.setVisible(true);

    calcSim();

    displayResult();

    display.setTitle("Simplified Emergency Exit    Step: " + steps);

  }

 

  private void displayResult() {

    int[][] data = new int[width][height];

    for (int x = 0; x < width; x++) {

      for (int y = 0; y < height; y++) {

        if /* */(x == xpositionofEmergencyExit && y == ypositionofEmergencyExit) data[x][y] = 2;

        else if (x == xpositionofAgent /*    */&& y == ypositionofAgent) /*    */data[x][y] = 1;

        else /*                                                                */data[x][y] = 0;

      }

    }

    whiteboard.setData(data);

    whiteboard.repaint();

  }

Therefore the class WhiteBoard can be used. It is an extension of JPanel which represents a two- or three-dimensional grid of data in form of colors or pictures in a grid. It only has to be initialized and added to a JFrame or an extension of JFrame. Here the class Display is used. It is an extension of JFrame with a new constructor and a few settings that have already been done. The window of this visualization contains the WhiteBoard and two buttons, which are used to go through the simulation step by step. The simulation always does as much steps as the value of “steps” says. The simulation with visualization always works with the same representation and the same starting conditions. So it is possible to increase the value of “steps” and start a simulation from the beginning by clicking on plusbutton without having any differences in the agent’s behaviour. You will just see the next step of the simulation. For displaying the result the position of the agent and the emergency exit have to be converted into a two-dimensional array. Also the color-scale of the WhiteBoard has to be set. The conversion is done by displayResult(). The setting of the colorscale is done in replayWithVisualization by the function addColorToScale(int lowerLimit, Color c). All values within lowerLimit and the next lowerLimit or, if there is no next lowerLimit, the top, have the color c. As soon as this has been done you have to set the data which should be shown by the WhiteBoard. The data is set with the function setData(int[][] data). The call of repaint() will force the WhiteBoard to visualize the data which will look like this:

Image:SimulationWithVisualization.jpg

The black square represents the agent and the green square the emergency exit.

A more complex simulation

Now let’s implement a simulation of higher complexity:

import java.awt.Color;

import java.awt.Dimension;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.util.Random;

import java.util.Vector;

 

import javax.swing.JButton;

import javax.swing.JFrame;

import javax.swing.JTextField;

 

import GridVisualization.*;

 

import interfaces.IProblem;

import interfaces.IRepresentation;

 

public class EmergencyExit extends IProblem {

 

  int               steps;

  int               width;

  int               height;

  int               numberofAgents;

  int               numberofExits;

  int               seed;

  agent[]           agents;

  Exit[]            EmergencyExits;

  IRepresentation[] c;

  JTextField        seedTextField;

 

  // this function is called to simulate without visualization. It is used to find the best Representation

  @Override

  public double getResult(IRepresentation[] candidates) {

    // read config from xml file

    steps = Integer.parseInt(getProperties().get("steps").getValue());

    width = Integer.parseInt(getProperties().get("width").getValue());

    height = Integer.parseInt(getProperties().get("height").getValue());

    seed = Integer.parseInt(getProperties().get("seedforEmergencyExits").getValue());

    c = candidates;

    double Fitness = 0;

    for (int s = seed; s < seed + 10; s++) {

      setupField(s);

      Fitness += calcSim();

    }

   

    return (Fitness / 10);

  }

 

  WhiteBoard whiteboard;

  Display    display;

 

  @Override

  public void replayWithVisualization(IRepresentation[] candidates) {

    c = candidates;

    // read config from xml file

    width = Integer.parseInt(getProperties().get("width").getValue());

    height = Integer.parseInt(getProperties().get("height").getValue());

    seed = Integer.parseInt(getProperties().get("seedforEmergencyExits").getValue());

    display = new Display(840, 695, "EmergencyExit");

    display.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

   

    whiteboard = new WhiteBoard(600, 600, width, height, 1);

    whiteboard.addColorToScale(0, Color.WHITE);

    // you can decide whether to take images or colors to represent things on the whiteboard

    //whiteboard.addColorToScale(1, Color.BLACK);

    whiteboard.addImageToScale(1, "Components\\Problems\\EmergencyExit\\agent.png");

    //whiteboard.addColorToScale(2, Color.GREEN);

    whiteboard.addImageToScale(2, "Components\\Problems\\EmergencyExit\\EmergencyExit.png");

    JButton minusbutton = new JButton("<--");

    JButton plusbutton = new JButton("-->");

    seedTextField = new JTextField("" + seed);

    seedTextField.setPreferredSize(new Dimension(50, 20));

    JButton startButton = new JButton("Change seed");

    display.add(whiteboard);

    display.add(minusbutton);

    display.add(plusbutton);

    display.add(seedTextField);

    display.add(startButton);

    minusbutton.addActionListener(new ActionListener() {

     

      @Override

      public void actionPerformed(ActionEvent e) {

        if (steps > 0) steps--;

        setupField(seed);

        double Fitness = calcSim();

        displayResult();

        int agentsleft = 0;

        for (agent a : agents) {

          if (!a.hasReachedExit) agentsleft++;

        }

        String FitnessString = String.format("%.2f", Fitness);

        display.setTitle("Emergency Exit    Step: " + steps + "   Fitness: " + FitnessString + "  Number of Agents left: " + agentsleft);

      }

    });

    plusbutton.addActionListener(new ActionListener() {

     

      @Override

      public void actionPerformed(ActionEvent e) {

        steps++;

        setupField(seed);

        double Fitness = calcSim();

        displayResult();

        int agentsleft = 0;

        for (agent a : agents) {

          if (!a.hasReachedExit) agentsleft++;

        }

        String FitnessString = String.format("%.4f", Fitness);

        display.setTitle("Emergency Exit    Step: " + steps + "   Fitness: " + FitnessString + "  Number of Agents left: " + agentsleft);

      }

    });

    startButton.addActionListener(new ActionListener() {

     

      @Override

      public void actionPerformed(ActionEvent e) {

        steps = 0;

        seed = Integer.parseInt(seedTextField.getText());

        setupField(seed);

        double Fitness = calcSim();

        displayResult();

        int agentsleft = 0;

        for (agent a : agents) {

          if (!a.hasReachedExit) agentsleft++;

        }

        String FitnessString = String.format("%.4f", Fitness);

        display.setTitle("Emergency Exit    Step: " + steps + "   Fitness: " + FitnessString + "  Number of Agents left: " + agentsleft);

      }

    });

    display.setVisible(true);

    setupField(seed);

    double Fitness = calcSim();

    displayResult();

    int agentsleft = 0;

    for (agent a : agents) {

      if (!a.hasReachedExit) agentsleft++;

    }

    String FitnessString = String.format("%.4f", Fitness);

    display.setTitle("Emergency Exit    Step: " + steps + "   Fitness: " + FitnessString + "  Number of Agents left: " + agentsleft);

  }

 

  /**

   * Displays the result of the last Simulation

   */

  private void displayResult() {

    int[][] data = new int[width][height];

    for (int x = 0; x < width; x++) {

      for (int y = 0; y < height; y++) {

        data[x][y] = 0;

      }

    }

    for (agent a : agents) {

      data[a.xpos][a.ypos] = 1;

    }

    for (Exit e : EmergencyExits) {

      data[e.xpos][e.ypos] = 2;

    }

  

    whiteboard.setData(data);

    whiteboard.repaint();

  }

 

  void setupField(int s) {

    // read config from xml file

    numberofAgents /**/= Integer.parseInt(getProperties().get("NumberofAgents").getValue());

    numberofExits /* */= Integer.parseInt(getProperties().get("NumberofEmergencyExits").getValue());

   

    numberofAgents /**/= Math.min(numberofAgents, Math.max(width, height));

    numberofExits /* */= Math.min(numberofExits, width * height);

    agents /*        */= new agent[numberofAgents];

    EmergencyExits /**/= new Exit[numberofExits];

    // it is important to reset the Representation. Otherwise there would sometimes be simulation mistakes because the Representation wouldn't start from the same seed

    c[0].reset();

    // create the agents and place them in a straight line from the upper left to the lower right corner

   

    for (int i = 0; i < agents.length; i++) {

      agents[i] = new agent(c[0].clone(), (i + 1) * width / (agents.length + 1), (i + 1) * height / (agents.length + 1), false);

    }

    Random positionGenerator = new Random(s);

    for (int i = 0; i < EmergencyExits.length; i++) {

      EmergencyExits[i] = new Exit();

      boolean PositionExistsAlready = false;

      do {

        EmergencyExits[i].xpos = positionGenerator.nextInt(width);

        EmergencyExits[i].ypos = positionGenerator.nextInt(width);

        PositionExistsAlready = false;

        for (int j = 0; j < i; j++) {

          if (EmergencyExits[i].xpos == EmergencyExits[j].xpos && EmergencyExits[i].ypos == EmergencyExits[j].ypos) PositionExistsAlready = true;

        }

      } while (PositionExistsAlready);

    }

  }

 

  /**

   * Calculates one Simulation whit a certain amount of steps, which has to be defined before calling this method

   *

   * @return Returns the negative Sum of the distances between the agents and the Emergency Exit

   */

  double calcSim() {

   

    for (int step = 0; step < steps; step++) {

      for (int i = 0; i < agents.length; i++) {

        agent a = agents[i];

        if (!a.hasReachedExit) {

         

          Exit nearestExit = EmergencyExits[0];

          double minimumDistance = Math.sqrt(Math.pow(EmergencyExits[0].xpos - a.xpos, 2) + Math.pow(EmergencyExits[0].ypos - a.ypos, 2));

          for (int e = 0; e < EmergencyExits.length; e++) {

            double Distance = Math.sqrt(Math.pow(EmergencyExits[e].xpos - a.xpos, 2) + Math.pow(EmergencyExits[e].ypos - a.ypos, 2));

            if (Distance < minimumDistance) {

              minimumDistance = Distance;

              nearestExit = EmergencyExits[e];

            }

          }

        

          // input[0] .. horizontal distance between the agent and the nearest Emergency Exit

          // input[1] .. vertical distance between the agent and the nearest Emergency Exit

          // input[2] .. field north      of the agent is occupied

          // input[3] .. field north-east of the agent is occupied

          // input[4] .. field east       of the agent is occupied

          // input[5] .. field south-east of the agent is occupied

          // input[6] .. field south      of the agent is occupied

          // input[7] .. field south-west of the agent is occupied

          // input[8] .. field west       of the agent is occupied

          // input[9] .. field north-west of the agent is occupied

         

          // determine which fields around the agent are occupied by another agent

          boolean northoccupied /**/= false;

          boolean northeastoccupied = false;

          boolean eastoccupied /* */= false;

          boolean southeastoccupied = false;

          boolean southoccupied /**/= false;

          boolean southwestoccupied = false;

          boolean westoccupied /* */= false;

          boolean northwestoccupied = false;

         

          for (int j = 0; j < agents.length; j++) {

            agent ag = agents[j];

            if (!ag.hasReachedExit) { // If a agent has reached the Emergency Exit he cannot occupy a field

              if (a.xpos /**/== ag.xpos && a.ypos - 1 == ag.ypos) northoccupied /**/= true;

              if (a.xpos + 1 == ag.xpos && a.ypos - 1 == ag.ypos) northeastoccupied = true;

              if (a.xpos + 1 == ag.xpos && a.ypos /**/== ag.ypos) eastoccupied /* */= true;

              if (a.xpos + 1 == ag.xpos && a.ypos + 1 == ag.ypos) southeastoccupied = true;

              if (a.xpos /**/== ag.xpos && a.ypos + 1 == ag.ypos) southoccupied /**/= true;

              if (a.xpos - 1 == ag.xpos && a.ypos + 1 == ag.ypos) southwestoccupied = true;

              if (a.xpos - 1 == ag.xpos && a.ypos /**/== ag.ypos) westoccupied /* */= true;

              if (a.xpos - 1 == ag.xpos && a.ypos - 1 == ag.ypos) northwestoccupied = true;

            }

          }

         

          Vector<Float> input = new Vector<Float>();

          input.add((float) (nearestExit.xpos - a.xpos));

          input.add((float) (nearestExit.ypos - a.ypos));

          input.add(northoccupied /**/? 1.0f : 0.0f);

          input.add(northeastoccupied ? 1.0f : 0.0f);

          input.add(eastoccupied /* */? 1.0f : 0.0f);

          input.add(southeastoccupied ? 1.0f : 0.0f);

          input.add(southoccupied /**/? 1.0f : 0.0f);

          input.add(southwestoccupied ? 1.0f : 0.0f);

          input.add(westoccupied /* */? 1.0f : 0.0f);

          input.add(northwestoccupied ? 1.0f : 0.0f);

         

          // output[0] .. horizontal velocity of the agent

          // output[1] .. vertical velocity of the agent

          Vector<Float> output = a.representation.getOutput(input);

         

          // the elements of output are float values between 0.0 and 1.0

          // for the simulation it is useful to format these values so that you can see what each value means

          float xVfloat = output.get(0).floatValue() * 2.0f - 1.0f;

          float yVfloat = output.get(1).floatValue() * 2.0f - 1.0f;

         

          int xVelocity = Math.round(xVfloat); // -1 .. move one field in negative horizontal direction

                                               //  0 .. do not move in any horizontal direction

                                               //  1 .. move one field in positive horizontal direction

          int yVelocity = Math.round(yVfloat); // -1 .. move one field in negative vertical direction

                                               //  0 .. do not move in any vertical direction

                                               //  1 .. move one field in positive vertical direction

         

          // move the agent (only if the place, that he wants to move is not occupied by another agent)

          if /*   */(xVelocity == 0/* */&& yVelocity == -1/**/&& !northoccupied /**/&& /*                  */a.ypos > 0) {

            a.xpos += 0;

            a.ypos += -1;

          } else if (xVelocity == 1/* */&& yVelocity == -1/**/&& !northeastoccupied && a.xpos < width - 1 && a.ypos > 0) {

            a.xpos += 1;

            a.ypos += -1;

          } else if (xVelocity == 1/* */&& yVelocity == 0/* */&& !eastoccupied /* */&& a.xpos < width - 1) {

            a.xpos += 1;

            a.ypos += 0;

          } else if (xVelocity == 1/* */&& yVelocity == 1/* */&& !southeastoccupied && a.xpos < width - 1 && a.ypos < height - 1) {

            a.xpos += 1;

            a.ypos += 1;

          } else if (xVelocity == 0/* */&& yVelocity == 1/* */&& !southoccupied /**/&& /*                  */a.ypos < height - 1) {

            a.xpos += 0;

            a.ypos += 1;

          } else if (xVelocity == -1/**/&& yVelocity == 1/* */&& !southwestoccupied && a.xpos > 0 /*    */&& a.ypos < height - 1) {

            a.xpos += -1;

            a.ypos += 1;

          } else if (xVelocity == -1/**/&& yVelocity == 0/* */&& !westoccupied /* */&& a.xpos > 0/*    */) {

            a.xpos += -1;

            a.ypos += 0;

          } else if (xVelocity == -1/**/&& yVelocity == -1/**/&& !northwestoccupied && a.xpos > 0 /*    */&& a.ypos > 0) {

            a.xpos += -1;

            a.ypos += -1;

          }

         

          for (int n = 0; n < EmergencyExits.length && !a.hasReachedExit; n++) {

            if (a.xpos == EmergencyExits[n].xpos && a.ypos == EmergencyExits[n].ypos) a.hasReachedExit = true;

            else /*                                                                 */a.hasReachedExit = false;

          }

        }

      }

    }

    double Fitness = 0;

    for (agent a : agents) {

      double minimumDistance = Math.sqrt(Math.pow(EmergencyExits[0].xpos - a.xpos, 2) + Math.pow(EmergencyExits[0].ypos - a.ypos, 2));

      for (int e = 0; e < EmergencyExits.length; e++) {

        double Distance = Math.sqrt(Math.pow(EmergencyExits[e].xpos - a.xpos, 2) + Math.pow(EmergencyExits[e].ypos - a.ypos, 2));

        if (Distance < minimumDistance) {

          minimumDistance = Distance;

        }

      }

      Fitness += -minimumDistance / Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));

    }

   

    return (Fitness / numberofAgents);

  }

 

  public class agent {

    public IRepresentation representation;

    public int             xpos;

    public int             ypos;

    public boolean         hasReachedExit;

   

    /**

     * @param r Representation for this Agent

     * @param x defines the x position of this Agent

     * @param y defines the y position of this Agent

     */

    public agent(IRepresentation r, int x, int y, boolean reachedExit) {

      representation = r;

      xpos = x;

      ypos = y;

      hasReachedExit = reachedExit;

    }

  }

 

  public class Exit {

    public int xpos;

    public int ypos;

  }

}

The main changes are that there are more than one agent and also more than on emergency exits. The agents are distributed over the field in a straight line from the upper left to the lower right corner of the field. The emergency exits are distributed by random with a seed. It also shows that the same connection of input and output should be able to start with different starting conditions and nevertheless to find the exit for all agents. This is done by adding a loop to getResult() which changes the starting seed of setupField().

The respective xml-File for the extended problem is

  <properties>

        <entry key="steps" type="INT" value="50"/>

        <entry key="width" type="INT" value="20"/>

        <entry key="height" type="INT" value="20"/>

        <entry key="seedforEmergencyExits" type="INT" value="0"/>

        <entry key="NumberofAgents" type="INT" value="10"/>

        <entry key="NumberofEmergencyExits" type="INT" value="5"/>

        <entry key="NumberofBlockades" type="INT" value="20"/>

        <entry key="NumberofEvaluations" type="INT" value="10"/>

        <entry key="Filename" type="FILE" value="Components\Problems\EmergencyExit\Stadium.ser"/>

  </properties>