Commit 560966ef authored by Lambert Patrick's avatar Lambert Patrick
Browse files

add cmaes

parent 8546f1e8
package fr.inria.optimization.cmaes;
import java.util.Properties;
/*
Copyright 2003, 2005, 2007 Nikolaus Hansen
e-mail: hansen .AT. bionik.tu-berlin.de
hansen .AT. lri.fr
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License, version 3,
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Last change: $Date: 2010-12-02 23:57:21 +0100 (Thu, 02 Dec 2010) $
*/
/** Simple container of (mostly generic) options for the
* optimization, like the maximum number of objective
* function evaluations, see class fields. No explicit setting of
* options is needed to
* initialize the CMA-ES ({@link CMAEvolutionStrategy#init()})
* and options of the CMA-ES can be set
* and changed any time, either via a property file and the method
* {@link CMAEvolutionStrategy#readProperties()}, or new values can simply be
* assigned to the fields of the public <code>opts</code> field of
* the class <code>CMAEvolutionStrategy</code> (yeah, I know, not exactly Java style).
*
*/
public class CMAOptions implements java.io.Serializable {
// needs to be public to make sure that a using class can excess Options.
// Therefore, if not nested, needs to move into a separate file
private static final long serialVersionUID = 2255162105325585121L;
/** number of initial iterations with diagonal covariance matrix, where
* 1 means always. Default is
* diagonalCovarianceMatrix=0, but this will presumably change in future.
* As long as iterations<=diagonalCovarianceMatrix
* the internal time complexity is linear in the search space dimensionality
* (memory requirements remain quadratic).
*/
public long diagonalCovarianceMatrix = 0; // -1;
/** lower bound for standard deviations (step sizes). The
* Array can be of any length. The i-th entry corresponds to
* the i-th variable. If length&#60;dim the last entry is recycled for
* all remaining variables. Zero entries mean, naturally, no
* lower bound. <P>CAVE: there is an interference with stopTolX (and stopTolXFactor):
* if lowerStdDev is larger than stopTolX, the termination criterion
* can never be satisfied.</P>
* <p>Example:
* <pre> CMAEvolutionStrategy es = new CMAEvolutionStrategy();
* es.options.lowerStandardDeviations = new double[]{1e-4,1e-8}; // 1e-8 for all but first variable
* </pre>
* @see #stopTolX
* @see #stopTolXFactor
* */
public double[] lowerStandardDeviations;
/** upper bound for standard deviations (step lengths).
* Zero entries mean no upper
* bound. Be aware of the interference with option stopTolUpXFactor.
* @see #lowerStandardDeviations
* @see #stopTolUpXFactor
* */
public double[] upperStandardDeviations;
/** stop if function value drops below the target
* function value stopFitness. Default = <code>Double.MIN_VALUE</code> */
public double stopFitness = Double.MIN_VALUE;
/** stop if the
* maximum function value difference of all iteration-best
* solutions of the last 10 +
* 30*N/lambda iterations
* and all solutions of the recent iteration
* become <= stopTolFun. Default = 1e-12.
* */
public double stopTolFun = 1e-12;
/** stop if the maximum function value difference of all iteration-best
* solutions of the last 10 +
* 30*N/lambda iterations become smaller than
* stopTolFunHist. Default = 1e-13. The measured objective
* function value differences do not include repair
* penalties. */
public double stopTolFunHist = 1e-13; // used if non-null
/** stop if search steps become smaller than stopTolX. Default = 0 */
public double stopTolX = 0.0;
/** stop if search steps become smaller than stopTolXFactor * initial step size.
* Default = 1e-11. */
public double stopTolXFactor = 1e-11; // used if TolX is null
/** stop if search steps become larger than stopTolUpXFactor
* * initial step size. Default = 1e3. When this termination
* criterion applies on a static objective function, the initial
* step-size was chosen far too
* small (or divergent behavior is observed). */
public double stopTolUpXFactor = 1e3; // multiplier for initial sigma
/** stop if the number of objective function evaluations exceed stopMaxFunEvals */
public long stopMaxFunEvals = Long.MAX_VALUE; // it is not straight forward to set a dimension dependent
// default as the user can first set stopMaxFunEvals
// and afterwards the dimension
/** stop if the number of iterations (generations) exceed stopMaxIter */
public long stopMaxIter = Long.MAX_VALUE;
/** if true stopping message "Manual:..." is generated */
public boolean stopnow = false;
/** flag used by methods iterate(), whether to write output to files.
* Methods write an output file if flgWriteFile&#62;0.
*/
/** determines whether CMA says hello after initialization.
* @see CMAEvolutionStrategy#helloWorld()
* */
public int verbosity = 1;
/** Output files written will have the names outputFileNamesPrefix*.dat */
public String outputFileNamesPrefix = "outcmaes";
/** if chosen > 0 the console output from functions <code>print...</code> is saved
* additionally into a file, by default <tt>outcmaesdisp.dat</tt> */
public int writeDisplayToFile = 1;
/** only for >= 1 results are always exactly reproducible, as otherwise the update of the
* eigensystem is conducted depending on time measurements, defaut is 0.2 */
public double maxTimeFractionForEigendecomposition = 0.2;
/** default is 0.1
*/
public double maxTimeFractionForWriteToDefaultFiles = 0.1;
/** checks eigendecomposition mainly for debugging purpose, default is 0==no-check;
* the function checkEigenSystem requires O(N^3) operations.
*/
public int checkEigenSystem = 0;
/** This is the only place where the reading of a new option needs to be declared
*
* @param properties
*/
void setOptions(Properties properties) {
String s;
diagonalCovarianceMatrix = getFirstToken(properties.getProperty("diagonalCovarianceMatrix"), diagonalCovarianceMatrix);
if((s = properties.getProperty("stopFitness")) != null)
stopFitness = Double.valueOf(getFirstToken(s));
stopTolFun = getFirstToken(properties.getProperty("stopTolFun"), stopTolFun);
stopTolFunHist = getFirstToken(properties.getProperty("stopTolFunHist"), stopTolFunHist);
stopTolX = getFirstToken(properties.getProperty("stopTolX"), stopTolX);
stopTolXFactor = getFirstToken(properties.getProperty("stopTolXFactor"), stopTolXFactor);
stopTolUpXFactor = getFirstToken(properties.getProperty("stopTolUpXFactor"), stopTolUpXFactor);
stopMaxFunEvals = getFirstToken(properties.getProperty("stopMaxFunEvals"), stopMaxFunEvals);
stopMaxIter = getFirstToken(properties.getProperty("stopMaxIter"), stopMaxIter);
if ((s = properties.getProperty("upperStandardDeviations")) != null && !s.equals(""))
upperStandardDeviations = parseDouble(getAllToken(s));
if ((s = properties.getProperty("lowerStandardDeviations")) != null && !s.equals(""))
lowerStandardDeviations = parseDouble(getAllToken(s));
outputFileNamesPrefix = properties.getProperty("outputFileNamesPrefix", outputFileNamesPrefix).split("\\s")[0];
maxTimeFractionForEigendecomposition =
getFirstToken(properties.getProperty("maxTimeFractionForEigendecomposition"),
maxTimeFractionForEigendecomposition);
maxTimeFractionForWriteToDefaultFiles =
getFirstToken(properties.getProperty("maxTimeFractionForWriteToDefaultFiles"),
maxTimeFractionForWriteToDefaultFiles);
stopnow = "now".equals(getFirstToken(properties.getProperty("stop")));
writeDisplayToFile = getFirstToken(properties.getProperty("writeDisplayToFile"), writeDisplayToFile);
checkEigenSystem = getFirstToken(properties.getProperty("checkEigenSystem"), checkEigenSystem);
}
/** Returns the double value of the first token of a string s or the default,
* if the string is null or empty. This method should become generic with respect to the
* type of second argument.
* @param s string where the first token is read from
* @param def double default value, in case the string is empty*/
public Double getFirstToken(String s, Double def) {
if (s == null)
return def;
String[] ar = s.split("\\s+");
if (ar[0].equals(""))
return def;
return Double.valueOf(ar[0]);
}
/** should become generic with type argument? */
public String getFirstToken(String s) {
if (s == null)
return "";
String[] ar = s.split(new String("\\s+"));
return ar[0];
}
/** Returns the Integer value of the first token of a string s or the default,
* if the string is null or empty. This method should become generic with respect to the
* type of second argument.
* @param s string where the first token is read from
* @param def Integer default value, in case the string is empty*/
public Integer getFirstToken(String s, Integer def) {
if (s == null)
return def;
String[] ar = s.split("\\s+");
if (ar[0].equals(""))
return def;
return Integer.valueOf(ar[0]);
}
// public <T> T getFirstToken(String s, T def) {
// if (s == null)
// return def;
// String[] ar = s.split("\\s+");
// if (ar[0].equals(""))
// return def;
// return (T)(ar[0]); /* this fails */
// }
private String removeComments(String s) {
int i;
// remove trailing comments
i = s.indexOf("#");
if (i >= 0)
s = s.substring(0,i);
i = s.indexOf("!");
if (i >= 0)
s = s.substring(0,i);
i = s.indexOf("%");
if (i >= 0)
s = s.substring(0,i);
i = s.indexOf("//");
if (i >= 0)
s = s.substring(0,i);
return s;
}
/** Returns def if s==null or empty, code dublicate, should become generic */
private Long getFirstToken(String s, Long def) {
if (s == null)
return def;
String[] ar = removeComments(s).split("\\s+");
if (ar[0].equals(""))
return def;
return Long.valueOf(ar[0]);
}
String[] getAllToken(String s) {
// split w.r.t. white spaces regexp \s+
return removeComments(s).split("\\s+");
}
double[] parseDouble(String[] ars) {
double[] ard = new double[ars.length];
for(int i = 0; i < ars.length; ++i) {
ard[i] = Double.parseDouble(ars[i]);
}
return ard;
}
}
package fr.inria.optimization.cmaes;
/*
Copyright 2003, 2005, 2007 Nikolaus Hansen
e-mail: hansen .AT. bionik.tu-berlin.de
hansen .AT. lri.fr
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License, version 3,
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Last change: $Date: 2010-12-02 23:57:21 +0100 (Thu, 02 Dec 2010) $
*/
/**
* Interface to strategy parameters for the CMA Evolution
* Strategy, most importantly the population size lambda, while the change
* of other parameters is discouraged.
* The class CMAParameters processes the
* strategy parameters, like population size and learning rates, for
* the class {@link CMAEvolutionStrategy} where the public field <code>parameters</code> of
* type <code>CMAParameters</code> can
* be used to set the parameter values. The method {@link #supplementRemainders(int, CMAOptions)}
* supplements those parameters that were not explicitly given,
* regarding dependencies
* (eg, the parent number, mu, cannot be larger than the
* population size lambda) and does a respective consistency checking via method
* {@link #check()}.
* Parameters cannot be changed after CMAEvolutionStrategy method init()
* was called.
* <P> Example code snippet:</P>
* <PRE>
CMAEvolutionStrategy cma = new CMAEvolutionStrategy();
cma.parameters.setPopulationSize(33); // set lambda
int mu = cma.parameters.getMu(); // will fail as mu was not set and missing
// parameters were not supplemented yet
cma.readProperties(); // read necessary initial values, might overwrite lambda
mu = cma.parameters.getMu(); // might still fail
cma.init(); // finalize initialization, supplement missing parameters
mu = cma.parameters.getMu(); // OK now
cma.parameters.setMu(4); // runtime error, parameters cannot be changed after init()
* </PRE>
*
* <P>Most commonly, the offspring population size lambda can be changed
* (increased) from its default value via setPopulationSize to improve the
* global search capability, see file CMAExample2.java. It is recommended to use the default
* values first! </P>
*
* @see CMAEvolutionStrategy#readProperties()
*/
public class CMAParameters implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = -1305062342816588003L;
int supplemented; // after supplementation it is undecidable whether a parameter was
// explicitly set from outside, therefore another supplementation is not advisable
int locked; // lock when lambda is used to new data structures
int lambda; /* -> mu, <- N */
int mu; /* -> weights, (lambda) */
double mucov; /* -> ccov */
double mueff; /* <- weights */
double[] weights; /* <- mu, -> mueff, mucov, ccov */
double damps; /* <- cs, maxeval, lambda */
double cs; /* -> damp, <- N */
double cc; /* <- N */
double ccov; /* <- mucov, <- N, <- diagonalcov */
double ccovsep; /* <- ccov */
double chiN;
public CMAParameters() {
mucov = -1;
ccov = -1;
}
/**
* Checks strategy parameter setting with respect to principle
* consistency. Returns a string with description of the first
* error found, otherwise an empty string "".
* */
public String check() {
if (lambda <= 1)
return "offspring population size lambda must be greater than onem is " + lambda;
if (mu < 1)
return "parent number mu must be greater or equal to one, is " + mu;
if (mu > lambda)
return "parent number mu " + mu + " must be smaller or equal to offspring population size lambda " + lambda;
if (weights.length != mu)
return "number of recombination weights " + weights.length + " disagrees with parent number mu " + mu;
if (cs <= 0 || cs > 1)
return "0 < cs <= 1 must hold for step-size cumulation parameter cs, is " + cs;
if (damps <= 0)
return "step-size damping parameter damps must be greater than zero, is " + damps;
if (cc <= 0 || cc > 1)
return "0 < cc <= 1 must hold for cumulation parameter cc, is " + cc;
if (mucov < 0)
return "mucov >= 0 must hold, is " + mucov;
if (ccov < 0)
return "learning parameter ccov >= 0 must hold, is " + ccov;
return "";
}
/** get default parameter setting depending on given dimension N
*
* @param N dimension
* @return default parameter setting
* @see #getDefaults(int, int)
*/
public CMAParameters getDefaults(int N) {
if (N == 0)
error("default parameters needs dimension been set");
CMAParameters p = new CMAParameters();
p.supplementRemainders(N, new CMAOptions());
return p;
}
/** get default parameter setting depending on dimension N and
* population size lambda. Code snippet to get, for example, the default parent
* number value mu (weighted recombination is default):
*
* <PRE>
* int default_mu_for_dimension_42 = new CMAParameters().getDefaults(42).getMu();
*
* CMAEvolutionStrategy cma = new CMAEvolutionStrategy(42);
* int the_same_most_convenient = cma.getParameterDefaults().getMu();
* int also_the_same = cma.getParameterDefaults(42).getMu();
* </PRE>
*
* @param N
* @param lambda
* @return default parameter setting
* @see #getDefaults(int, int)
*/
public CMAParameters getDefaults(int N, int lambda) {
CMAParameters p = new CMAParameters();
p.setLambda(lambda);
p.supplementRemainders(N, new CMAOptions());
return p;
}
/**
* Supplements all default parameter values that were not explicitly set already.
* Also checks whether the values that were already explicitly set are fine.
* @param N search space dimension
* @param opts {@link CMAOptions} where stopMaxFunEvals and
* stopMaxIter are used to set step-size damping parameter damps. This is of minor relevance.
*/
public void supplementRemainders(int N, CMAOptions opts) {
// parameters that can be zero were initialized to -1
if (supplemented > 0)
error("defaults cannot be supplemented twice");
if (N == 0)
error("dimension must be greater than zero");
supplemented = 1;
locked = 1;
chiN = Math.sqrt(N)
* (1.0 - 1.0 / (4.0 * N) + 1.0 / (21.0 * N * N));
// set parameters to their default if they were not set before
if (lambda <= 0)
lambda = (int) (4.0 + 3.0 * Math.log(N));
if (mu <= 0)
mu = (int) Math.floor(lambda/2.);
if (weights == null)
setWeights(mu, recombinationType);
else if (weights.length == 0)
setWeights(mu, recombinationType);
if (cs <= 0)
cs = (mueff+2) / (N+mueff+3);
if (damps <= 0)
damps =
(1 + 2 * Math.max(0, Math.sqrt((mueff - 1.) / (N + 1.)) - 1))
* Math.max(0.3, 1 - /* modification for short runs */
N / (1e-6+Math.min(opts.stopMaxIter,
opts.stopMaxFunEvals/lambda)))
+ cs ; /* minor increment */
if (cc <= 0)
cc = 4.0 / (N + 4.0);
if (mucov < 0)
mucov = mueff;
if (ccov < 0) { // TODO: setting should depend on gendiagonalcov
ccov = 2.0 / (N + 1.41) / (N + 1.41) / mucov
+ (1 - (1.0 / mucov))
* Math.min(1, (2 * mueff - 1) / (mueff + (N + 2) * (N + 2)));
ccovsep = Math.min(1, ccov * (N + 1.5) / 3.0);
}
// check everything
String s = check();
if (s == null)
;
else if (s.equals(""))
;
else
error(s); // if any prior setting does not work
} // supplementRemainders
/**
* Getter for property mu.
*
* @return Value of property mu.
*
*/
public int getMu() {
return mu;
}
/**
* Setter for parent number mu, be aware of the recombinationType when setting mu
*
* @param mu
* New value for the number of parents mu.
* @see #setRecombination(int, CMAParameters.RecombinationType)
* @see #setRecombinationWeights(CMAParameters.RecombinationType)
*/
public void setMu(int mu) {
if (locked != 0) // needed because of recombination weights
error("parameters are locked");
this.mu = mu;
}
/**
* Getter for offspring population size lambda, no check, whether lambda was already set properly
*
* @return Value of lambda
*
*/
public int getLambda() {
return lambda;
}
int flgLambdaChanged = 0; // not in use yet
/**
* Setter for offspring population size alias sample size
* alias lambda, use setPopulationSize() for outside use.
*
* @param lambda set population size
* @see #setPopulationSize()
*/
void setLambda(int lambda) {
if (locked != 0)
error("parameters cannot be set anymore");
this.lambda = lambda;
}
/** @see #getLambda() */
public int getPopulationSize() {
return getLambda();
}
/**
* Setter for offspring population size (lambda). If (only) lambda is
* set, other parameters, eg. mu and recombination weights and
* subsequently learning rates for the covariance matrix etc. are
* chosen accordingly
*
* @param lambda is the offspring population size
*/
public void setPopulationSize(int lambda) {
setLambda(lambda);
}
public enum RecombinationType {superlinear, linear, equal};
RecombinationType recombinationType = RecombinationType.superlinear; // otherwise null
/**
* Getter for property weights.
*
* @return Value of property weights.
*
*/
public double[] getWeights() {
return this.weights;
}
/**
* Recombination weights can be equal, linearly
* decreasing, or super-linearly decreasing (default). The respective parameter value is
* in enum RecombinationType.
* @param recombinationType
* @see #setRecombination
* @see #setMu
*/
public void setRecombinationWeights(RecombinationType recombinationType) {
if (locked != 0)
error("parameters cannot be set anymore");
this.recombinationType = recombinationType;
}
/**
* Sets parent number mu and the policy for choosing the recombination weights.
* Recombination weights can be equal, linearly
* decreasing, or super-linearly decreasing (default). The respective parameter value is
* The respective parameter value is
* in enum RecombinationType.
* For equal recombination weights mu=lambda/4 is appropriate, otherwise mu=lambda/2.
* @param mu
* @param recombinationType
*/
public void setRecombination(int mu, RecombinationType recombinationType) {
if (locked != 0)
error("parameters are locked");
this.mu = mu;
this.recombinationType = recombinationType;
}
/**
* Setter for recombination weights
*
* @param mu is the number of parents, number of weights > 0
*/
private void setWeights(int mu, RecombinationType recombinationType) {
double[] w = new double[mu];
if (recombinationType == RecombinationType.equal)
for (int i = 0; i < mu; ++i)
w[