Added Debian Builds

Xaye now runs on Debian with Mono 2.10.6. You can grab the latest builds from the links on the right.

Quick benchmark comparing Power Pack and the Xaye MKL wrapper on matrix operations

I ran a quick benchmark comparing adding, multiplying, and transposing F# Power Pack matrices using the Power Pack operators and our MKL wrapper. As you can see below, the MKL wrapper does pretty well - particularly with multiplication.  For addition and multiplication you just need to use the +! and *! operators instead of the standard + and * ones.

Matrix
Order
PP Transpose MKL Transpose Relative
Performance
PP Add MKL Add Relative
Performance
PP Multiply MKL Transpose Relative
Performance
100 0.18 0.09 2.00 0.09 0.01 9.10 3.55 0.09 39.00
225 0.45 0.27 1.67 0.64 0.18 3.50 50.73 0.55 93.00
400 1.36 0.91 1.50 1.91 0.36 5.25 237.65 2.45 96.81
512 2.55 1.82 1.40 3.18 0.64 5.00 585.76 4.73 123.90
600 5.27 1.45 3.62 5.45 1.18 4.62 1220.07 7.27 167.75
1024 18.55 8.00 2.32 15.64 3.73 4.20 8829.41 42.28 208.86
2055 79.00 25.18 3.14 88.19 19.27 4.58 107127.22 314.11 341.05

Times are in milliseconds and the benchmark was ran on a six core, 3.3 GHz i7 running x64 Windows 7. Relative performance was computed as PP/MKL.

Initial Genetic Optimization Drop

We’ve added a continuous, single objective function genetic algorithm (GA) to Xaye.  The GA provides the selection, mating, and mutations methods discussed in chapter 2 and 3 of “Practical Genetic Algorithms” by Haupt and Haupt. The code is extensible so users can add their own routines.

The simplest way to use the Xaye GA is use the solve method. It only requires a cost/fitness function and bounds on the variables of the cost function. For an example, lets minimize:    x^{2}+x cos(x)    for -10 < x < 10

let cost = fun (x:float[]) ->(x.[0]*x.[0]+x.[0]) * Math.Cos(x.[0])
let bound = {lower = -10.0; upper = 10.0}
let bounds = [| bound; |]
let status = solve cost bounds
printfn "%A" status
{Iteration = 69;
 Best = ([|9.620350903|], -100.2237551);
 NoImprovement = 25;
 Termination = NoImprovement;}

The solve function returns a Status record that contains the number of iterations, best solution found, how many iterations since we found the best solution (NoImprovement), and why the optimization terminated. In this case, solve needed 69 iterations to find the minimum and it terminated early because it was no longer improving on the best solution (we default to a maximum of a 1000 iterations and stop if we there was no  improvement after 25 iterations). It uses the our defaultMate function (top x chromosome selection, pairing parents using a rank squared weighted roulette, and a heuristic blend crossover), the uniformMutate function, and generates a random starting population of 20. These defaults work well for relatively simple functions.

Lets try a little more difficult cost function: \sum_{1}^{2}\left | x_{n} \right |- 10 cos \left ( \left | \sqrt{ 10X_{n} } \right | \right )     for -10 < x < 10.  The minimum is at x,y=0.

let cost = fun (x:float[]) ->
   let x0 = Math.Abs(x.[0])
   let x1 = Math.Abs(x.[1])
   x0 - 10.0*Math.Cos(Math.Sqrt(10.0*x0)) + x1 - 10.0*Math.Cos(Math.Sqrt(10.0*x1))
let bound = {lower = -10.0; upper = 10.0}
let bounds = [| bound; |]
let status = solve cost bounds
printfn "%A" status
{Iteration = 41;
 Best = ([|3.510642794; 0.007216450758|], -15.48906593);
 NoImprovement = 25;
 Termination = NoImprovement;}

We got stuck at a local minimum. Instead of using the solve function, we can use the defaultEvolve function. This uses the same default mating and mutation methods as solve, but allows us tweak the optimization options. Lets increase the size of the population, decrease the selection rate (the percentage of the current generation to use for mating and to copy into the next generation), and increase the number of no improvement iterations. We will now sample more of the solution space but at a cost of a longer running time. As you can see below, we now find the global minimum.

let options = {DefaultOptions with PopulationSize=1000; SelectionRate=0.1; NoImprovement=50 }
let status = defaultEvolve options cost bounds
printfn "%A" status
{Iteration = 92;
 Best = ([|1.585990353e-17; -4.292601412e-18|], -20.0);
 NoImprovement = 50;
 Termination = NoImprovement;}

We also have a fully customizable evolve function that allows users to build any type of GA.

type Fitness = float
type Gene = float
type Chromosome = Gene[]
type Population = Chromosome[]
type EvaluatedPopulation = (Chromosome*Fitness)[]
type Parents = Chromosome*Chromosome
type Cost = Chromosome -> Fitness
type Mate = EvaluatedPopulation -> Population
type Mutate =  Population -> Population
type Convergence = Options -> Status -> EvaluatedPopulation -> bool
evolve : Options -> Cost -> Mate -> Mutate -> Convergence -> Population -> Status

For example, to create a GA where we replace the full population each generation, use Haupt’s extrapolation crossover, a 3 chromosome tournament for selecting parents, and use a normally distributed mutation method (gene + random normal(0,1) ), we’d:

let cost = fun (x:float[]) ->
   let x0 = Math.Abs(x.[0])
   let x1 = Math.Abs(x.[1])
   x0 - 10.0*Math.Cos(Math.Sqrt(10.0*x0)) + x1 - 10.0*Math.Cos(Math.Sqrt(10.0*x1))
let bound = {lower = -10.0; upper = 10.0}
let bounds = [| bound; bound; |]
let replaceAll (options:Options) (bounds:Bounds[]) (population:EvaluatedPopulation) : Population =
   let pairs = options.PopulationSize/2
   let pairing = pairTournament3 options.Random pairs
   let crossover = extrapolationCrossover options.Random
   population |> pairing |> crossover

use norm = new Normal01()
let mutation = mutateNormal options norm bounds
let initial = generateInitial options bounds
let status = evolve DefaultOptions cost mate mutation defaultConvergence initial

defaultConvergence just checks to see if we’ve reached the maximum number of iterations or that the best solution hasn’t improved in options.NoImprovement number of iterations.

Next we’ll implements parts of chapter 5: hybrid GA (mix the GA with a local optimizer), multiple objective functions, and a parallel GA . And tweak the current implementation to make it more “functional” – still getting the hang of functional programming.