package bsearch.representations; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import org.nlogo.util.MersenneTwisterFast; import bsearch.space.DoubleContinuousSpec; import bsearch.space.ParameterSpec; import bsearch.space.SearchSpace; /** * A Chromosomal representation where each parameter is represented by a real number on the interval [0,1), * regardless of whether the parameter only takes on a discrete set of values, or is in an entirely different range. */ public strictfp class RealHypercubeChromosome implements Chromosome { private double[] dVals; private SearchSpace searchSpace; private RealHypercubeChromosome( SearchSpace searchSpace , MersenneTwisterFast rng ) { super(); this.searchSpace = searchSpace; List<ParameterSpec> paramSpecs = searchSpace.getParamSpecs(); dVals = new double[paramSpecs.size()]; for (int i = 0 ; i < dVals.length; i++) { dVals[i] = rng.nextDouble(); } } private RealHypercubeChromosome( SearchSpace searchSpace , LinkedHashMap<String,Object> paramSettings) { super(); this.searchSpace = searchSpace; List<ParameterSpec> paramSpecs = searchSpace.getParamSpecs(); if (paramSpecs.size() != paramSettings.size()) { throw new IllegalStateException("# of parameter settings does not match search space parameter specifications."); } dVals = new double[paramSpecs.size()]; for (int i = 0 ; i < dVals.length; i++) { ParameterSpec paramSpec = paramSpecs.get(i); Object val = paramSettings.get(paramSpec.getParameterName()); if (paramSpec instanceof DoubleContinuousSpec) { DoubleContinuousSpec dspec = (DoubleContinuousSpec) paramSpec; double paramVal = (Double) val; dVals[i] = (paramVal - dspec.getMin()) / (dspec.getMax() - dspec.getMin()); } else { long choice = paramSpec.getChoiceIndexFromValue(val, paramSpec.choiceCount()); dVals[i] = (double) choice / (double) paramSpec.choiceCount(); } } } private RealHypercubeChromosome( double[] dVals, SearchSpace searchSpace ) { super() ; this.dVals = dVals.clone(); this.searchSpace = searchSpace ; } public LinkedHashMap<String,Object> getParamSettings() { LinkedHashMap<String,Object> paramSettings = new LinkedHashMap<String,Object>(); int i = 0; for (ParameterSpec p: searchSpace.getParamSpecs()) { double dVal = dVals[i++]; if (p.choiceCount() < 0) // real-valued { if (p instanceof DoubleContinuousSpec) { DoubleContinuousSpec dspec = (DoubleContinuousSpec) p; double paramVal = dspec.enforceValidRange(dVal * (dspec.getMax() - dspec.getMin()) + dspec.getMin()); paramSettings.put(p.getParameterName(),paramVal); } else { throw new IllegalStateException("Unknown real-valued parameter specification - this type of Chromosomal representation can't handle it."); } } else { long choice = (long) StrictMath.floor(dVal * p.choiceCount()); Object paramVal = p.getValueFromChoice(choice, p.choiceCount()); paramSettings.put(p.getParameterName(),paramVal); } } return paramSettings; } @Override public RealHypercubeChromosome clone() { return new RealHypercubeChromosome(dVals, searchSpace); } public RealHypercubeChromosome mutate(double mutRate, MersenneTwisterFast rng) { RealHypercubeChromosome rhc = this.clone(); for (int i = 0 ; i < dVals.length; i++) { if (rng.nextDouble() < mutRate) { double newVal = (dVals[i] + 0.10 * rng.nextGaussian()); if (newVal < 0.0 || newVal >= 1.0) { newVal -= StrictMath.floor(newVal); //make it fall between 0.0 and 1.0 } rhc.dVals[i] = newVal; } } return rhc; } public Chromosome[] crossoverWith(Chromosome other, MersenneTwisterFast rng) { if (! (other instanceof RealHypercubeChromosome)) { throw new IllegalStateException("Can't crossover different types of Chromosomes."); } RealHypercubeChromosome parent0 = this; RealHypercubeChromosome parent1 = (RealHypercubeChromosome) other; RealHypercubeChromosome[] children = new RealHypercubeChromosome[2]; children[0] = parent0.clone(); children[1] = parent1.clone(); int len = parent1.dVals.length - 1 ; int splitPoint; if (len > 0) { splitPoint = 1 + rng.nextInt( len ) ; } else // we only have one parameter being searched, so crossover doesn't really happen. { splitPoint = 0; } for (int i = 0 ; i < len; i++) { if (i >= splitPoint) { children[0].dVals[i] = parent1.dVals[i]; children[1].dVals[i] = parent0.dVals[i]; } } return children; } public SearchSpace getSearchSpace() { return searchSpace; } /** * Redefine equality for our Chromosomes. * Two chromosomes are considered equal if they represent the same parameter values. * */ @Override public boolean equals(Object other) { if (other instanceof RealHypercubeChromosome) { RealHypercubeChromosome dc = (RealHypercubeChromosome) other; return Arrays.equals( dVals, dc.dVals); } return false; } /** * Reflects the redefined equals(Object) method, and it * should provide more efficient hashing for Chromosome keys in hashmaps. */ @Override public int hashCode() { int hashCode = 0; for (Double d: dVals) { hashCode = hashCode * 2 + d.hashCode(); } return hashCode; } public static class Factory implements ChromosomeFactory { public Chromosome createChromosome(SearchSpace searchSpace, MersenneTwisterFast rng) { return new RealHypercubeChromosome(searchSpace, rng); } public Chromosome createChromosome( SearchSpace searchSpace, LinkedHashMap<String,Object> paramSettings) { return new RealHypercubeChromosome(searchSpace, paramSettings); } public String getHTMLHelpText() { return "<strong>RealHypercubeChromosome</strong> In this encoding, every parameter " + "(numeric or not) is represented by a <I>real-valued</I> continuous variable. " + "This encoding exists mainly to facilitate the (future) use of algorithms " + "that assume a continuous numeric space (such as Particle Swarm Optimization)," + " and allow them to be applied even when some of the model parameters are not numeric."; } } }