Random MDP

Intro

This page is intended to show you how to start with the Optsicom Optimization Framework by showing an easy example solving the Maximum Diversity Problem (MDP) problem.

The minimum classes you will need in order to get some results are:

  • MDPProblem
  • MDPInstance
  • MDPSolution
  • MDPInstancesRepository
  • MDPRandomConstructive
  • RandomConstructiveExperiment

MDPProblem

This class must extend the class Problem from the framework. Here you will set the type of problem, by stating if it is a "max is best" problem or a "min is best" one.

As you can see on the code bellow, INSTANCE is a final static variable, which means it won't change and you will have one and one only instance at a time so we won't mess things up.

package es.optsicom.problem.mdp.approx;

import java.io.File;
import es.optsicom.framework.Problem;
import es.optsicom.framework.instancefile.InstancesRepository;
import es.optsicom.framework.util.BestMode;

public class MDPProblem extends Problem{

    private final static MDPProblem INSTANCE = new MDPProblem();

    public MDPProblem(){
        super("MDP", BestMode.MAX_IS_BEST);
    }

    @Override
    public InstancesRepository getInstancesRepository(File instanceFileDir,
            String useCase) {

        return new MDPInstancesRepository(useCase);
    }

    public static Problem getInstance() {
        return INSTANCE;
    }
}

MDPInstance

This class must inherit from AbstractInstance. Here we will store our instance of the problem along with other information about it which may be useful at some time. You will have to override the method getProblem that returns your instance, but you can use other methods like "getM" or "getN" to return the size of the solution and the size of the problem, respectively.

package es.optsicom.problem.mdp.approx;

import es.optsicom.framework.AbstractInstance;
import es.optsicom.framework.Problem;
import es.optsicom.framework.instancefile.InstanceFile;

import es.optsicom.problem.mdp.approx.MDPProblem;
import es.optsicom.problem.mdp.approx.util.Matrix;

public class MDPInstance extends AbstractInstance  {

    String filename;
    Matrix<Double> instance;
    int solutionsize;

    public MDPInstance(InstanceFile instanceFile, String name, Matrix<Double> m, int s) {
        super(instanceFile);
        filename = name;
        instance = m;
        solutionsize = s;

    }

    public int getM(){
        return solutionsize;
    }

    public int getN(){
        return instance.getRows();
    }

    public double getWeight(int node1, int node2){
        return instance.get(node1, node2);
    }

    @Override
    public Problem getProblem()
    {
        return MDPProblem.getInstance();
    }

}

MDPSolution

MDPSolution must extend the generic class Solution<MDPInstance>. In this class you will have to decide how to implement your solution. Here you will write for example, a method to calculate the weight of a solution, or create a copy, or compare it with other solutions.

In this example I choosed a double array approximation. In the first array i will store the number of the nodes that are part of my solution and in the second one I will store the values of the nodes compared with the other nodes of the solution. This means that if i add up all the values from the second array, i will have the weight of the solution multiplied by 2. Because if my first node is the node "A", my first value will be the comparation between "A" and all other nodes in solution, but all other nodes will be compared to the node "A" too! I used this implementation because this way i will be able to see which one is the worst node of the solution at a very low cost.

I also added nodes and values get and set methods to use the black box implementation in case i change my mind about the implementation.

package es.optsicom.problem.mdp.approx;
import es.optsicom.framework.*;
import java.util.ArrayList;

public class MDPSolution extends Solution<MDPInstance>{

    private ArrayList<Integer> solution;
    private ArrayList<Double> values; //value of every node so you can always see the worst of them
    private double weight=0;
    private boolean neverset=true;
    private MDPInstance instance;

    @SuppressWarnings("unchecked")
    public MDPSolution(ArrayList<Integer> nodes, MDPInstance instance){
        this.instance = instance;
        solution = (ArrayList<Integer>) nodes.clone();
        values = new ArrayList<Double>(instance.getM());
        for (int i=0; i<instance.getM(); i++){
            values.add((double) 0);
        }
    }

    @Override
    public double getWeight() {
        if (neverset){
            this.calculatePreciseWeight();
            neverset = false;
        }
        return weight;
    }

    public int length(){
        return this.instance.getM();
    }

    @Override
    public Solution<MDPInstance> createCopy() {
        MDPSolution sol = new MDPSolution(this.solution, this.instance);
        for (int i=0; i<this.instance.getM(); i++){
            sol.setValue(i,this.values.get(i));
        }
        sol.setWeight(this.weight);
        return sol;
    }

    @Override
    public MDPInstance getInstance() {
        return instance;
    }

    @Override
    public void calculatePreciseWeight() {
        double plus=0;
        double w=0;
        for (int i = 0; i<instance.getM(); i++){
            values.set(i, (double) 0);
        }
        for (int i = 0; i<instance.getM()-1; i++){
            for (int j=i+1; j<instance.getM(); j++){
                w=instance.getWeight(solution.get(i),solution.get(j));
                values.set(i, values.get(i)+w);
                values.set(j, values.get(j)+w);
            }
            plus += values.get(i);
        }
        //In the iteration the last node's value is not added to plus
        weight = plus + values.get(instance.getM()-1);

        //the node's weight is calculated with all partners so its doubled
        weight = weight/2;

    }

    @Override
    public Object getInfoToSave() {
        // [N1,N2,N3,...,Nn]=Weight
        String s="[";
        for (int i=0; i<instance.getM()-1;i++)
            s+=solution.get(i)+",";
        s+=solution.get(instance.getM()-1)+"]="+weight;

        return s;
    }

    @Override
    public boolean isBetterThan(Solution<MDPInstance> aSolution) {

        return instance.getProblem().getQualityComparator().compare(this, aSolution) > 0;
    }

    public void setWeight(double weight2){
        weight = weight2;
    }

    public void setValue(int i, Double w){
        values.set(i, w);
    }

    public boolean belongs (int obj){
        //True if obj belongs to the solution.
        return solution.contains(obj);
    }

    public double nodeValue (int position, int node){
        //Calculates the potential value of a node in the given position.
        double acum=0;
        for (int i=0; i<solution.size(); i++)
            if (i!=position){
                acum+=instance.getWeight(solution.get(i), node);
            }    
        return acum;
    }

    public void nodeChange(int position, int node){
        // Also updates the Weight
        double oldnodevalue=0;
        double newnodevalue=0;
        double nodefullvalue=0;
        for (int i=0; i<solution.size(); i++)
            if (i!=position){
                oldnodevalue=instance.getWeight(solution.get(i), solution.get(position));
                newnodevalue=instance.getWeight(solution.get(i), node);
                values.set(i, values.get(i)-oldnodevalue+newnodevalue);
                weight=weight-oldnodevalue+newnodevalue;
                nodefullvalue+=newnodevalue;
            }
        solution.set(position, node);
        values.set(position, nodefullvalue);
    }

    public void nodeInsert(int node, int pos){
        solution.set(pos, node);
    }

    public void ValueInsert(double value, int pos){
        values.set(pos, value);
    }

    public int getNode(int pos){
        return solution.get(pos);
    }

    public double getValue(int pos){
        return values.get(pos);
    }

    public int getWorst(){
        int worst=0;
        double worstvalue=99999;
        for(int i=0; i<solution.size();i++){
            if (worstvalue>values.get(i)){
                worst=i;
                worstvalue=this.getValue(i);
            }
        }
        return worst;
    }

}

MDPInstancesRepository

This class extends FileInstancesRepository from the framework. Here you will read set how to read a instance from a file and store it in a variable.

I decided to store the instance in a NxN symmetrical matrix so it won't matter when i consult a weight which node I put in first place since it will result in the same value.

package es.optsicom.problem.mdp.approx;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import es.optsicom.framework.Instance;
import es.optsicom.framework.graph.matrix.FormatException;
import es.optsicom.framework.instancefile.*;
import es.optsicom.problem.mdp.approx.util.Matrix;

public class MDPInstancesRepository extends FileInstancesRepository{

    public MDPInstancesRepository(String useCase) {
        super(useCase);
    }

    @Override
    public Instance loadInstance(InstanceFile instanceFile) throws IOException {

        String name;
        int nodes, solutionsize;
        Matrix<Double> weights;

        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(instanceFile.getFile())));

        try{
            //First Line
            String line = br.readLine();

            if (line == null) {
                throw new FormatException("The file is empty");
            }

            String [] tokens = line.split(" ");
            name = tokens[0];
            nodes = Integer.parseInt(tokens[1]);
            solutionsize = Integer.parseInt(tokens[2]);
            weights = new Matrix<Double>(nodes,nodes);
            weights.init((Double) ((double) 0));

            //Rest of instance
            line = br.readLine();
            do{
                tokens = line.split(" ");
                weights.set(Integer.parseInt(tokens[0]),
                            Integer.parseInt(tokens[1]),
                            Double.parseDouble(tokens[2]));
                weights.set(Integer.parseInt(tokens[1]),
                            Integer.parseInt(tokens[0]),
                            Double.parseDouble(tokens[2]));
                line = br.readLine();
            }while(line != null);
            return new MDPInstance(instanceFile,name,weights,solutionsize);

        }catch (NumberFormatException e) {
            throw new FormatException("File format exception",e);
        }

    }

}

MDPRandomConstructive

The constructive class must extend from de generic class AbstractConstructive<MDPSolution, MDPInstance>. We need a constructive to build solutions which will be improved by our metaheuristics. In this case we are building random solutions.

package es.optsicom.problem.mdp.constructives;

import java.util.ArrayList;
import java.util.Random;

import es.optsicom.framework.approx.constructive.AbstractConstructive;
import es.optsicom.problem.mdp.approx.MDPInstance;
import es.optsicom.problem.mdp.approx.MDPSolution;

public class MDPRandomConstructive extends
        AbstractConstructive<MDPSolution, MDPInstance> {

    @Override
    public MDPSolution createSolution() {
        int n;
        // List of selected nodes
        ArrayList<Integer> nodes = new ArrayList<Integer>();

        Random aleatorio = new Random();
        for (;;) {
            do {
                n = aleatorio.nextInt(getInstance().getN());
            } while (nodes.contains(n));

            // Add to the list the selected node
            nodes.add(n);

            // Stops when the number of nodes is the desired number
            if (nodes.size() == getInstance().getM()) {
                break;
            }
        }

        // Returns the solution
        return new MDPSolution(nodes, getInstance());

    }

    @Override
    public void initSolutionCreation() {

    }

    @Override
    public void initSolutionCreationByNum(int numSolutions) {

    }

    @Override
    public void initSolutionCreationByTime(long millis) {

    }

    @Override
    public boolean isDeterminist() {
        return false;
    }

}

RandomConstructiveExperiment

package es.optsicom.problem.mdp.constructives;

import java.util.ArrayList;
import java.util.Random;

import es.optsicom.framework.approx.constructive.AbstractConstructive;
import es.optsicom.problem.mdp.approx.MDPInstance;
import es.optsicom.problem.mdp.approx.MDPSolution;

public class MDPRandomConstructive extends
        AbstractConstructive<MDPSolution, MDPInstance> {

    @Override
    public MDPSolution createSolution() {
        int n;
        // List of selected nodes
        ArrayList<Integer> nodes = new ArrayList<Integer>();

        Random aleatorio = new Random();
        for (;;) {
            do {
                n = aleatorio.nextInt(getInstance().getN());
            } while (nodes.contains(n));

            // Add to the list the selected node
            nodes.add(n);

            // Stops when the number of nodes is the desired number
            if (nodes.size() == getInstance().getM()) {
                break;
            }
        }

        // Returns the solution
        return new MDPSolution(nodes, getInstance());

    }

    @Override
    public void initSolutionCreation() {

    }

    @Override
    public void initSolutionCreationByNum(int numSolutions) {

    }

    @Override
    public void initSolutionCreationByTime(long millis) {

    }

    @Override
    public boolean isDeterminist() {
        return false;
    }

}

RandomConstructiveExperiment

Finally we get to the main class. Here we will set the specific class we are using on this execution. In case we have more than one constructive, or more than one improvement here we will choose which ones we are using. Here we will also select the set of instances we are using for the execution.

package es.optsicom.problem.mdp.experiment;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import es.optsicom.framework.analysis.CommonExpAnalysis;
import es.optsicom.framework.approx.ApproxMethod;
import es.optsicom.framework.approx.algorithm.ConstructiveMethod;
import es.optsicom.framework.approx.experiment.ApproxMethodExperiment;
import es.optsicom.framework.instancefile.InstanceFile;
import es.optsicom.framework.instancefile.InstanceFileSet;
import es.optsicom.framework.instancefile.InstancesRepository;
import es.optsicom.problem.mdp.approx.MDPProblem;
import es.optsicom.problem.mdp.approx.MDPInstance;
import es.optsicom.problem.mdp.approx.MDPSolution;
import es.optsicom.problem.mdp.constructives.MDPRandomConstructive;

public class RandomConstructiveExperiment {

static int NUM_EXECUTIONS = 1;
static int MAX_TIME = 2000;

    public static void main(String[] args) {

        // 1. Methods
        List<ApproxMethod<MDPSolution, MDPInstance>> scMethods = new ArrayList<ApproxMethod<MDPSolution, MDPInstance>>();
        ConstructiveMethod<MDPSolution, MDPInstance> constructive = new ConstructiveMethod<MDPSolution, MDPInstance>(new MDPRandomConstructive());
        scMethods.add(constructive);

        // 2. Instances
        InstancesRepository repository = MDPProblem.getInstance().getInstancesRepository();
        InstanceFileSet set = repository.getInstanceFileSet("type1");    
        List<InstanceFile> instanceFiles = set.getInstanceFiles();    

        // 3. Experiment
        ApproxMethodExperiment<MDPSolution, MDPInstance> experiment = new ApproxMethodExperiment<MDPSolution, MDPInstance>(scMethods,NUM_EXECUTIONS);        
        experiment.setInstanceFiles(instanceFiles);        
        experiment.setRecordEvolution(true);

        // 4. execution
        try {
            experiment.executeExperiment();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }        

        // 5. Analysis

        // Getting the directory with the results
        File resultsDir = experiment.getResultsDir();

        // Performing an analysis over the data
        CommonExpAnalysis.showBestValueByMethodAndInstance(resultsDir, MAX_TIME);        

    }    
}