(* :Title: Differential Evolution *)
(* :Context: DifferentialEvolution` *)
(* :Author: Mark Fisher *)
(* :Package version: 1.1, August 2000 *)
(* :Mathematica Version 4.0 *)
(* :Discussion:
This package implements the differential evolution algorithm for
continuous function optimization. The algorithm is due to Kenneth
Price and Rainer Storn.
Motto: "Only the low-cost survive."
The algorithm starts with a randomly chosen population of n real
vectors, each with d >= 2 components. A cost function is given. (Cost
is "negative fitness.") The cost of each member of the current
population is determined. Each member then becomes a "target." The
target is "mated" with a noisy vector randomly constructed from other
members of the current generation to produce an offspring. The
offspring replaces the target in the next generation if its cost is
lower.
The mate is the sum of a "base" vector (whose construction is
described below) and the scaled difference between two randomly chosen
vectors. (The scale is given by the "weighting factor.") The offspring
is constructed from the target and the mate by randomly choosing
"genes" (i.e. components) from either the target or the mate. The
probability that a gene comes from the mate is determined by the
"cross-over rate." (At least one gene always comes from the mate, even
if the cross-over rate is zero.) There are two ways to choose the base
vector: (1) the base vector is chosen randomly from the current
generation; (2) the base vector is a linear combination of the target
and the "best" vector of the current generation (in terms of cost).
The linear combination is determined by the "use-best weighting
factor."
There is no convergence criterion built in. One simply runs the
algorithm for a given number of generations. Convergence could be
based on the dispersion of the costs or the population.
*)
(* :References:
(1) Kenneth Price and Rainer Storn, "Differential Evolution,"
Dr. Dobb's Journal, April 1997.
(2) Kenneth Price and Rainer Storn, "Differential Evolution---A simple
and efficient adaptive scheme for global optimization over continuous
spaces," Working Paper TR-95-012, International Computer Science
Institute, Berkeley, CA.
*)
BeginPackage["DifferentialEvolution`",
{"Statistics`DescriptiveStatistics`"}]
DifferentialEvolution::usage = "DifferentialEvolution[costfun, pop0, opts]
takes a function (costfun) of a d-dimensional point and an inital
population (pop0) of n points. (Thus pop0 is an n by d matrix.) The initial
population should have about 10 * d points chosen randomly.
DifferentialEvolution returns the minimum value of the function after
NumberOfGenerations. (The default setting is NumberOfGenerations -> 200.)
The option UseBest controls whether to use the best member of the
population in constructing the vector to mate with the target. Other
options are WeightingFactor, CrossoverRate, BestWeightingFactor,
PrintSummary, WriteHistory, and EvolutionHistory."
NumberOfGenerations::usage = "NumberOfGenerations is an option for
DifferentialEvolution. It specifies the number of generations to run."
WeightingFactor::usage = "WeightingFactor is an option for
DifferentialEvolution. It scales the difference between the two
randomly chosen vectors to be added to the base vector in constructing
the mate."
CrossoverRate::usage = "CrossoverRate is an option for
DifferentialEvolution. It determines the probability that an
offspring's gene comes from the mate rather than the target. At least
on gene always comes from the mate, even when the cross-over rate is
zero."
$EvolutionHistory::usage = "$EvolutionHistory is the history of values
and populations of points from the most recent use of the function
DifferentialEvolution."
EvolutionHistory::usage = "EvolutionHistory is an option for
DifferentialEvolution. It determines whether the entire history of values
and populations of points from the most recent use of the function
DifferentialEvolution is kept. The default is EvolutionHistory -> False.
For EvolutionHistory -> True, the history is assigned to the variable
$EvolutionHistory."
$GenerationNumber::usage = "$GenerationNumber is the internal
generation counter. Its value can be examined in a Dialog session."
UseBest::usage = "UseBest is an option for DifferentialEvolution. The
default setting is UseBest -> False, which combines three randomly
chosen vectors to mate with the target. UseBest -> True combines (1)
the best of the population, (2) the target, and (3) two randomly
chosen vectors to mate with the target."
BestWeightingFactor::usage = "BestWeightingFactor is an option for
DifferentialEvolution. It controls the extend to which the best of the
population is introduced into the vector that is mated with the
target. It is only used with the option UseBest -> True."
PrintSummary::usage = "PrintSummary is an option for
DifferentialEvolution. It specifies whether to print summary
statistics of each generation. The default is PrintSummary -> False."
WriteHistory::usage = "WriteHistory is an option for
DifferentialEvolution. It specifies whether to write the population
and cost history to files. The default is WriteHistory -> False."
HistoryFileName::usage = "HistoryFileName is an option for
DifferentialEvolution. It is used in conjunction with WriteHistory ->
True to specify the name of the file where the population and cost
history will be stored. The default is HistoryFileName ->
\"history.m\"."
Begin["`Private`"]
Options[DifferentialEvolution] = Sort @ {NumberOfGenerations -> 200,
WeightingFactor -> .8, CrossoverRate -> .5, UseBest -> False,
BestWeightingFactor -> .9, PrintSummary -> False, WriteHistory -> False,
HistoryFileName -> "history.m", EvolutionHistory -> False}
DifferentialEvolution::filename = "The file name `1` is not a string."
DifferentialEvolution::toosmall = "The initial population must have at least
4 members."
DifferentialEvolution[costfun_, pop0_?(MatrixQ[#, NumberQ]&),
opts___?OptionQ] :=
Module[{maxgen, wf, cr, usebest, lambda, hist, fun,
print, write, file, n, d, cost0, cost, pop, pos, min},
{maxgen, wf, cr, usebest, lambda, print, write, file, hist} =
{NumberOfGenerations, WeightingFactor, CrossoverRate, UseBest,
BestWeightingFactor, PrintSummary, WriteHistory,
HistoryFileName, EvolutionHistory} /. {opts} /.
Options[DifferentialEvolution];
{usebest, print, write, hist} = TrueQ /@ {usebest, print, write, hist};
If[!StringQ[file], Message[DifferentialEvolution::filename, file];
Return[$Failed]];
{n, d} = Dimensions[pop0];
If[n < 4, Message[DifferentialEvolution::toosmall]; Return[$Failed]];
cost0 = costfun /@ pop0;
$GenerationNumber = 0;
If[print, PrintSummary[{cost0, pop0}]];
If[write, Export[file, Flatten /@ Transpose[{cost0, pop0}], "Table"]];
fun = If[hist, NestList, Nest];
return = fun[NextGen[#, costfun, n, d, wf, cr, lambda, usebest,
print, write, file]&, {cost0, pop0}, maxgen];
If[hist,
$EvolutionHistory = return; {cost, pop} = Last[return],
{cost, pop} = return];
min = Min[cost];
pos = Position[cost, min][[1, 1]];
{min, pop[[pos]]}
]
NextGen[{cost_, pop_}, costfun_, n_, d_, wf_, cr_, lambda_,
usebest_, print_, write_, file_] :=
Module[{a, b, c, best, base, mate, genes, offspring, score,
cost1, pop1, strm},
If[usebest,
(* then *)
{a, b} = ChooseAB[n];
best = Position[cost, Min[cost]][[1,1]];
base = Table[lambda pop[[best]], {n}] + (1 - lambda) pop,
(* else *)
{a, b, c} = ChooseABC[n];
base = pop[[c]]
];
mate = base + wf (pop[[a]] - pop[[b]]);
genes = Genes[n, d, cr];
offspring = genes pop + (1 - genes) mate;
score = costfun /@ offspring;
{cost1, pop1} = Transpose @ MapThread[If[#1 <= #3, {#1, #2}, {#3, #4}]&,
{score, offspring, cost, pop}];
$GenerationNumber++;
If[print, PrintSummary[{cost1, pop1}]];
If[write,
ExportAppend[file, Flatten /@ Transpose[{cost1, pop1}], "Table"]];
{cost1, pop1}
]
ExportAppend[file_String, expr_, format_String] :=
Module[{stream},
stream = OpenAppend[file];
WriteString[stream, ExportString[expr, format]];
Close[stream]
]
PrintSummary[{cost_, pop_}] :=
Module[{labels, vals, pos, vec},
labels = {"Generation", "Minimum", "Mean", "Standard deviation",
"Best vector"};
vals = Prepend[Through[{Min, Mean, StandardDeviation}[cost]],
$GenerationNumber];
pos = Position[cost, vals[[2]]][[1, 1]];
vec = pop[[pos]];
Print @ TableForm[Transpose[{labels, Append[vals, vec]}],
TableDepth -> 2]
]
ChooseAB = Compile[{{n, _Integer}},
Transpose @ Table[
Module[{a=0, b=0},
While[a = Random[Integer, {1, n}]; a == pos, Null];
While[b = Random[Integer, {1, n}]; b == pos || b == a, Null];
{a, b}],
{pos, n}]
]
ChooseABC = Compile[{{n, _Integer}},
Transpose @ Table[
Module[{a=0, b=0, c=0},
While[a = Random[Integer, {1, n}]; a == pos, Null];
While[b = Random[Integer, {1, n}]; b == pos || b == a, Null];
While[c = Random[Integer, {1, n}]; c == pos || c == b || c == a, Null];
{a, b, c}],
{pos, n}]
]
(* 1 => from target, 0 => from mate *)
(* "last" gene always comes from mate, even when cr = 0 *)
Genes[n_, d_, cr_] :=
MapThread[
RotateRight, (* randomly pick first gene *)
{
Transpose @ Append[Table[Ceiling[Random[] - cr], {d-1}, {n}],
Table[0, {n}]], (* last gene from mate *)
Table[Random[Integer, {1, d}], {n}] (* rotate by this amount *)
}]
End[]
EndPackage[]
(* :Examples: *)
(* First example:
costfun[{x_, y_, z_}] := x^2 + y^2 + z^2
xb = 50
pop0 = Table[xb (2 Table[Random[], {3}] - 1), {30}];
DifferentialEvolution[costfun, pop0]
(* to see how this works: *)
{vals, points} = Transpose[$EvolutionHistory];
(* rate of improvement *)
logdiffs = Apply[Subtract, Partition[Log[Min /@ vals], 2, 1], {1}];
ListPlot[logdiffs, PlotJoined -> True, PlotStyle -> Red, PlotRange -> All];
(* population evoloution *)
graphs = Graphics3D[{PointSize[.02],Point /@ #}]& /@ Take[points, 40];
Show[#, PlotRange -> Table[{-xb, xb}, {3}]]& /@ graphs;
*)
(* Second example:
f[{x0_, x1_}] := 100*(x0^2 - x1)^2 + (1 - x0)^2;
xb = 2.048;
pop0 = Table[xb (2 Table[Random[], {2}] - 1), {30}];
DifferentialEvolution[f, pop0, NumberOfGenerations -> 200,
EvolutionHistory -> True]
(* to see how this works: *)
{vals, points} = Transpose[$EvolutionHistory];
mins = Min /@ vals;
(* rate of improvement *)
logdiffs = Apply[Subtract, Partition[Log[mins], 2, 1], {1}];
ListPlot[logdiffs, PlotJoined -> True, PlotStyle -> Red, PlotRange -> All];
(* population evolution *)
pos = Flatten @ MapThread[First @ Position @ ## &, {vals, mins}];
minpoints = MapThread[#1[[#2]]&, {points, pos}];
graphs = Graphics[{PointSize[.02],Point /@ #}]& /@ points;
minpts = Graphics[{PointSize[.02],Red, Point[#]}]& /@ minpoints;
cp = Graphics[
ContourPlot[f[{x0, x1}], {x0, -xb, xb}, {x1, -xb, xb},
ContourShading -> True, Contours ->Prepend[40Range[10], 10],
PlotPoints -> 45, DisplayFunction -> Identity,
ColorFunction -> (GrayLevel[.5 + #^(1/4)/2]&)]];
Show[{cp, Graphics[{Line[{{-xb,1},{xb,1}}], Line[{{1,-xb},{1,xb}}]}],#},
PlotRange -> {{-xb, xb}, {-xb, xb}}, Frame -> True, AspectRatio -> 1,
DisplayFunction -> $DisplayFunction]& /@
Take[Transpose[{graphs, minpts}], 101];
*)