2.4 Result

In our previous example, we've finished with solver which will successfully apply an algorithm to a problem, but we did not get any information about it at all. This is where Result steps in.

Due to nature of solvers being able to run several different algorithms or problems in one Solver#run(), one solver must be able to hold several results. This is done by implementing interface Result, which can hold our results. This can be actually done by simple list as has its default implementation SimpleResult.

As Result holds information about whole Solver#run(), ResultEntry provides information about just one solver iteration. Objects of this class contains all required data about how did the algorithm's application ended, such as number of iterations, time spent or best found solution.

Let's try to add such a result entry to our demo from previous chapter. Creating a result entry is easy - just create a result entry using one of its constructors. We will use ResultEntry#ResultEntry(...). There is however one thing we have not seen yet - PreciseTimestamp. This class is used to measure time more precisely than simple system time, but we need not to discuss in any further now. We will just create an instance of it before we run our algorithm and then we'll add it to result entry as a start time.

From result entry requirements we have an algorithm and a problem we used and a PreciseTimestamp. It also requires best Configuration, best fitness and optimize counter. We take first two from algorithm itself - algorithms are required to provide this information when they implement the Algorithm interface (Algorithm#getBestConfiguration() and Algorithm#getBestFitness() methods). The last one, optimize counter, is responsibility of solver to provide. But since we know we made 10 optimize steps, we could just supply number 10.

package basicusage.result;

import cz.cvut.felk.cig.jcop.algorithm.Algorithm;
import cz.cvut.felk.cig.jcop.problem.BaseObjectiveProblem;
import cz.cvut.felk.cig.jcop.problem.Problem;
import cz.cvut.felk.cig.jcop.result.Result;
import cz.cvut.felk.cig.jcop.result.ResultEntry;
import cz.cvut.felk.cig.jcop.result.SimpleResult;
import cz.cvut.felk.cig.jcop.solver.BaseSolver;
import cz.cvut.felk.cig.jcop.solver.Solver;
import cz.cvut.felk.cig.jcop.util.PreciseTimestamp;

public class PrimitiveSolver extends BaseSolver implements Solver {
    protected Problem problem;
    protected Algorithm algorithm;
    protected Result result = new SimpleResult();

    public PrimitiveSolver(Algorithm algorithm, Problem problem) {
        this.algorithm = algorithm;
        this.problem = problem;
    }

    public void run() {
        // init algorithm
        this.algorithm.init(new BaseObjectiveProblem(this.problem));

        // take starting time
        PreciseTimestamp startTimestamp = new PreciseTimestamp();

        // make 10 optimizations
        for (int i = 0; i < 10; i++) this.algorithm.optimize();

        // create result entry
        ResultEntry resultEntry = new ResultEntry(this.algorithm, this.problem, this.algorithm.getBestConfiguration(), this.algorithm.getBestFitness(), 10, startTimestamp);
    }
}

Now we have result entry for our 10-steps-optimization solver iteration and we need to add it to a result. From Solver interface we are obliged to implement Solver#getResult() method. But since we are extending BaseSolver, Result has been automatically created for us and we will just add new result entry to it.

package basicusage.result;

import /** ... */

public class PrimitiveSolver extends BaseSolver implements Solver {
    /** ... */
    public void run() {
        /** ... */
        // add it to result
        this.getResult().addEntry(resultEntry);
    }
}

Now we have our results we need to display them (or better - render them). Classes implementing Render interface are used to render results in different ways (for example XML, console etc). There are several in-built renders and we will use SimpleRender which just dumps result to console. To register a render to solver use Solver#addRender(Render). This is usually done outside of solver, because solver itself does not necessarily know how you want to render data. It know just how to apply algorithms on problems, not how results are to be displayed. Lets update our DemoPrimitiveSolver from previous chapter:

package basicusage.result;

import cz.cvut.felk.cig.jcop.algorithm.Algorithm;
import cz.cvut.felk.cig.jcop.algorithm.graphsearch.bfs.BreadthFirstSearch;
import cz.cvut.felk.cig.jcop.problem.Problem;
import cz.cvut.felk.cig.jcop.problem.knapsack.Knapsack;
import cz.cvut.felk.cig.jcop.result.render.SimpleRender;
import cz.cvut.felk.cig.jcop.solver.Solver;

public class DemoPrimitiveSolver {
    public static void main(String[] args) {
        // create problem & algorithm
        Problem problem = new Knapsack("9000 4 100 18 114 42 136 88 192 3 223");
        Algorithm algorithm = new BreadthFirstSearch();

        // create solver
        Solver solver = new PrimitiveSolver(algorithm, problem);

        // add simple render
        solver.addRender(new SimpleRender());

        // run!
        solver.run();

        // render results
        solver.render();
    }
}

We've added SimpleRender to our solver and after Solver#run() is finished, we've called Solver#render(), which will render results with all registered renders. You should get something like this in console, if not, you've probably done something wrong:

=== Algorithm BreadthFirstSearch [] used on problem Knapsack [line=9000 4 100 18 114 42 136 88 192 3 223] ===
  CPU Time:                       1 [ms]
  System Time:                    1 [ms]
  User Time:                      1 [ms]
  Clock Time:                    16 [ms]
  Optimize counter:              10 [-]
  Optimize/sec (CPU):         10000 [1/s]
  Optimize/sec (Clock):         625 [1/s]
  Best solution:         Configuration{attributes=[0, 1, 0, 1], operationHistory={0:Empty knapsack created, 1:AddOperation{knapsackItem=KnapsackItem{index=1, weight=42, price=136}}, 2:AddOperation{knapsackItem=KnapsackItem{index=3, weight=3, price=223}}}}
  Depth:                          2 [-]
  Fitness:                    359,0 [-]
  Ended without exception

Because of the fact that we used BaseSolver as parent to our solver, we even do not need to add SimpleRender, it is default one (if no other is supplied), so our DemoPrimitiveSolver could be even shorter. Also note that there is SimpleSolver implementation in JCOP which is very much like the one we've just created, although it is a bit more sophisticated. But since for the most part it works the same as our PrimitiveSolver, we will be using it instead in next chapters.

Both classes could be downloaded here (zip, tgz, 7z).

Full list of bundled renders and listeners can be found in Appendix E.