package iterators; import main.MoreMath; import parameters.*; class IteratorUtilFuncs { IteratorUtilFuncs() {} /** This method scans along a single parameter axis to find how far it can go in either direction before the score goes above the threshold score. It scans by bisecting the distance between the original parameter value and the edges repeatedly, moving the next bisection point closer or farther from the original parameter based on whether the current point gives a good score. It stops when it takes a step smaller than precision * (upperThresh - lowerThresh). Note that the algorithm used will often not notice valleys inbetween two peaks, so the answers are likely to be inaccurate if there are two distinct good regions. */ static MultiFloatReturn findEdgesForParameter(int par_num, ParameterSet parsTV, float [] p, float precision, ModelIterator iterator) { float score; float orig_value = p[par_num]; MultiFloatReturn ret = new MultiFloatReturn(); // Check in larger direction // First check for trivial condition that largest value is good. // Note that this is a bit dangerous since it assumes the no peaks inbetween, but we might miss peaks anyway float within_target; if(parsTV.getVariationMode(par_num) == Parameter.LINEAR) within_target = precision * (parsTV.getUpperBound(par_num) - parsTV.getLowerBound(par_num)); else within_target = (float)(precision * (Math.log(parsTV.getUpperBound(par_num)) - Math.log(parsTV.getLowerBound(par_num)))); float last_par_value = p[par_num]; p[par_num] = parsTV.getUpperBound(par_num); score = iterator.F(p); boolean passed = iterator.getStopper().didPass(); if(passed) ret.f2 = parsTV.getUpperBound(par_num); else { // Keep cutting distance in half looking for cliff float new_par_value = (p[par_num] + last_par_value) / 2; boolean done = false; while( !done ) { if(parsTV.getVariationMode(par_num) == Parameter.LINEAR) done = (Math.abs(p[par_num] - last_par_value) < within_target); else done = (Math.abs(Math.log(p[par_num]) - Math.log(last_par_value)) < within_target); if(parsTV.getVariationMode(par_num) == Parameter.LINEAR) { if(passed) new_par_value = p[par_num] + Math.abs(p[par_num] - last_par_value) / 2; else new_par_value = p[par_num] - Math.abs(p[par_num] - last_par_value) / 2; } else { p[par_num] = (float)Math.log(p[par_num]); last_par_value = (float)Math.log(last_par_value); if(passed) new_par_value = p[par_num] + Math.abs(p[par_num] - last_par_value) / 2; else new_par_value = p[par_num] - Math.abs(p[par_num] - last_par_value) / 2; new_par_value = (float)Math.exp(new_par_value); p[par_num] = (float)Math.exp(p[par_num]); } last_par_value = p[par_num]; p[par_num] = new_par_value; score = iterator.F(p); passed = iterator.getStopper().didPass(); } // Since we didn't necessarily stop at a spot that has a good score, take a guess at where the edge // would be. I've decided to use the current parameter value if the score is good, and only guess otherwise. // This doesn't guarantee that the returned edge is good but biases in that direction. if(passed) ret.f2 = p[par_num]; else { if(parsTV.getVariationMode(par_num) == Parameter.LINEAR) ret.f2 = p[par_num] - Math.abs(p[par_num] - last_par_value) / 2; else { ret.f2 = (float)(Math.log(p[par_num]) - Math.abs(Math.log(p[par_num]) - Math.log(last_par_value))) / 2; ret.f2 = (float)Math.exp(ret.f2); } } } System.out.println("high edge " + p[par_num] + " " + last_par_value + " " + passed + " " + within_target); // Check in smaller direction last_par_value = orig_value; p[par_num] = parsTV.getLowerBound(par_num); score = iterator.F(p); passed = iterator.getStopper().didPass(); if(passed) ret.f1 = parsTV.getLowerBound(par_num); else { // Keep cutting distance in half looking for cliff float new_par_value = (p[par_num] + last_par_value) / 2; boolean done = false; while( !done ) { System.out.println("Lower new value " + new_par_value); if(parsTV.getVariationMode(par_num) == Parameter.LINEAR) done = (Math.abs(p[par_num] - last_par_value) < within_target); else done = (Math.abs(Math.log(p[par_num]) - Math.log(last_par_value)) < within_target); if(parsTV.getVariationMode(par_num) == Parameter.LINEAR) { if(passed) new_par_value = p[par_num] - Math.abs(p[par_num] - last_par_value) / 2; else new_par_value = p[par_num] + Math.abs(p[par_num] - last_par_value) / 2; } else { p[par_num] = (float)Math.log(p[par_num]); last_par_value = (float)Math.log(last_par_value); if(passed) new_par_value = p[par_num] - Math.abs(p[par_num] - last_par_value) / 2; else new_par_value = p[par_num] + Math.abs(p[par_num] - last_par_value) / 2; new_par_value = (float)Math.exp(new_par_value); p[par_num] = (float)Math.exp(p[par_num]); } last_par_value = p[par_num]; p[par_num] = new_par_value; score = iterator.F(p); passed = iterator.getStopper().didPass(); } if(passed) ret.f1 = p[par_num]; else { if(parsTV.getVariationMode(par_num) == Parameter.LINEAR) ret.f1 = p[par_num] - Math.abs(p[par_num] - last_par_value) / 2; else { ret.f1 = (float)(Math.log(p[par_num]) + Math.abs(Math.log(p[par_num]) - Math.log(last_par_value)) / 2); ret.f1 = (float)Math.exp(ret.f1); } } } System.out.println("low edge " + p[par_num] + " " + last_par_value + " " + passed); p[par_num] = orig_value; return ret; } public final static int RANDOM_ALLELE = 0, AVERAGE_ALLELE = 1, RANGE_ALLELE = 3; public static int matingType = RANDOM_ALLELE; /** Returns the current matingType which determines how a daughter parameter is created from two parent parameters. */ public static int getMatingType() { return matingType; } /** Set the current matingType which determines how a daughter parameter is created from two parent parameters. Choices are RANDOM_ALLELE which randomly chooses one of the two parent parameters; AVERAGE_ALLELE which averages the two parent parameters; RANGE_ALLELE which returns a value somewhere between the two parent parameters. */ public static void setMatingType(int type) { matingType = type; } /** Returns an 'allele' for a parameter based on two 'parent' alleles, according to the current mating type. Mating type is set by setMatingType(). */ static float mateAlleles(float allele1, float allele2, float mutation_rate, float mutation_amount) { switch(getMatingType()) { case RANDOM_ALLELE: float rnd = MoreMath.random(); if(rnd < mutation_rate) // Mutate in a direction away from other allele { if(allele1 < allele2) return allele1 - allele1 * mutation_amount * MoreMath.random(); else return allele1 + allele1 * mutation_amount * MoreMath.random(); } else if(rnd < 0.5) return allele1; else if(rnd > 1 - mutation_rate) { if(allele2 < allele1) return allele2 - allele2 * mutation_amount * MoreMath.random(); else return allele2 + allele2 * mutation_amount * MoreMath.random(); } else return allele2; case AVERAGE_ALLELE: if(mutation_rate == 0) return (allele1 + allele2) / 2f; else return (allele1 + allele2) / 2f + mutation_amount * (MoreMath.random() - 0.5f) * allele1; case RANGE_ALLELE: if(mutation_rate == 0) return allele1 + (allele2 - allele1) * MoreMath.random(); return (allele1 + (allele2 - allele1) * MoreMath.random()) + mutation_amount * (MoreMath.random() - 0.5f) * allele1; default: System.out.println("Error: Illegal mating type when trying to cross two parameter sets."); return 0f; } } /** Takes two arrays of parameters and "crosses" them. The crossing can happen using several different algorithms, which are chosen with a flag called matingType. Returns a new array of parameters that is the offspring of the parents. The parent arrays are not changed. */ static float [] crossParameters(float [] p1, float [] p2, float mutation_rate, float mutation_amount) { float [] p = new float[p1.length]; for(int param_num = 0; param_num < p.length; param_num++) { p[param_num] = mateAlleles(p1[param_num], p2[param_num], mutation_rate, mutation_amount); } return p; } }