package bsearch.representations; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import org.nlogo.util.MersenneTwisterFast; import bsearch.space.ParameterSpec; import bsearch.space.SearchSpace; /** * A simple Chromosomal representation with genotype is a list of values, one for each ParameterSpec in the search space. * The chromosome is not made up solely of bits, but instead of various mixed types (numbers, objects), as defined by each * ParameterSpec, and the way mutation is performed at a locus of the Chromosome is ParameterSpec specific. */ public strictfp class MixedTypeChromosome implements Chromosome { private Object[] paramVals; private SearchSpace searchSpace; private MixedTypeChromosome( SearchSpace searchSpace , MersenneTwisterFast rng ) { super(); this.searchSpace = searchSpace; List<ParameterSpec> paramSpecs = searchSpace.getParamSpecs(); paramVals = new Object[paramSpecs.size()]; int i = 0; for (ParameterSpec p: paramSpecs) { paramVals[i++] = p.generateRandomValue( rng ); } } private MixedTypeChromosome( 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."); } paramVals = new Object[paramSettings.size()]; int i = 0; for (ParameterSpec p: paramSpecs) { //TODO: could be checking each of these paramSettings to make sure it's valid for the corresponding paramSpec? // Or is it better not to? paramVals[i++] = paramSettings.get(p.getParameterName()); } } private MixedTypeChromosome( Object[] paramVals, SearchSpace searchSpace ) { super() ; this.paramVals = paramVals.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()) { paramSettings.put(p.getParameterName(),paramVals[i++]); } return paramSettings; } @Override public MixedTypeChromosome clone() { return new MixedTypeChromosome(paramVals, searchSpace); } public MixedTypeChromosome mutate(double mutRate, MersenneTwisterFast rng) { MixedTypeChromosome mc = this.clone(); int i = 0; for (ParameterSpec ps: searchSpace.getParamSpecs()) { if (rng.nextDouble() < mutRate) { //TODO: Consider exposing the mutationStrength parameter, instead of constant at 10% of parameter range. mc.paramVals[i] = ps.mutate( paramVals[i] , 0.1, rng ) ; } i++; } return mc; } public Chromosome[] crossoverWith(Chromosome other, MersenneTwisterFast rng) { if (! (other instanceof MixedTypeChromosome)) { throw new IllegalStateException("Can't crossover different types of Chromosomes."); } MixedTypeChromosome parent0 = this; MixedTypeChromosome parent1 = (MixedTypeChromosome) other; MixedTypeChromosome[] children = new MixedTypeChromosome[2]; children[0] = parent0.clone(); children[1] = parent1.clone(); int len = parent1.paramVals.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].paramVals[i] = parent1.paramVals[i]; children[1].paramVals[i] = parent0.paramVals[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 MixedTypeChromosome) { MixedTypeChromosome dc = (MixedTypeChromosome) other; return Arrays.equals( paramVals, dc.paramVals); } 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 (Object o: paramVals) { hashCode = hashCode * 2 + o.hashCode(); } return hashCode; } public static class Factory implements ChromosomeFactory { public Chromosome createChromosome(SearchSpace searchSpace, MersenneTwisterFast rng) { return new MixedTypeChromosome(searchSpace, rng); } public Chromosome createChromosome( SearchSpace searchSpace, LinkedHashMap<String,Object> paramSettings) { return new MixedTypeChromosome(searchSpace, paramSettings); } public String getHTMLHelpText() { return "<strong>MixedTypeChromosome</strong> This encoding most closely matches the way " + "that one commonly thinks of the ABM parameters. Each parameter is stored " + "separately with its own data type (discrete numeric, continuous numeric, categorical, " + "boolean, etc). Mutation applies to each parameter separately (e.g. continous " + "parameters use Gaussian mutation, boolean parameters get flipped)."; } } }