/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.mahout.ga.watchmaker.cd;
import java.util.List;
import java.util.Random;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.uncommons.watchmaker.framework.EvolutionaryOperator;
/**
* Mutation operator.
*/
public class CDMutation implements EvolutionaryOperator<CDRule> {
/** probability of mutating a variable */
private final double rate;
/** max size of the change (step-size) for each mutated variable */
private final double range;
/**
* mutation precision. Defines indirectly the minimal step-size and the
* distribution of mutation steps inside the mutation range.
*/
private final int k;
/**
*
* @param rate probability of mutating a variable
* @param range max step-size for each variable
* @param k mutation precision
*
* See http://www.geatbx.com/docu/algindex-04.html#P659_42386 real valued mutation
* for more information about the parameters
*/
public CDMutation(double rate, double range, int k) {
Preconditions.checkArgument(rate > 0.0 && rate <= 1.0, "mutation rate must be in (0, 1]");
Preconditions.checkArgument(range > 0.0 && range <= 1.0, "mutation range must be in (0, 1]");
Preconditions.checkArgument(k >= 0, "mutation precision must be nonnegative");
this.rate = rate;
this.range = range;
this.k = k;
}
@Override
public List<CDRule> apply(List<CDRule> selectedCandidates, Random rng) {
List<CDRule> mutatedPopulation = Lists.newArrayListWithCapacity(selectedCandidates.size());
for (CDRule ind : selectedCandidates) {
mutatedPopulation.add(mutate(ind, rng));
}
return mutatedPopulation;
}
protected CDRule mutate(CDRule rule, Random rng) {
DataSet dataset = DataSet.getDataSet();
for (int condInd = 0; condInd < rule.getNbConditions(); condInd++) {
if (rng.nextDouble() > rate) {
continue;
}
int attrInd = CDRule.attributeIndex(condInd);
rule.setW(condInd, rndDouble(rule.getW(condInd), 0.0, 1.0, rng));
if (dataset.isNumerical(attrInd)) {
rule.setV(condInd, rndDouble(rule.getV(condInd), dataset
.getMin(attrInd), dataset.getMax(attrInd), rng));
} else {
rule.setV(condInd, rndInt(dataset.getNbValues(attrInd), rng));
}
}
return rule;
}
/**
* returns a random double in the interval [min, max ].
*/
double rndDouble(double value, double min, double max, Random rng) {
double s = rng.nextDouble() * 2.0 - 1.0; // [-1, +1]
double r = range * (max - min) / 2;
double a = Math.pow(2, -k * rng.nextDouble());
double stp = s * r * a;
value += stp;
// clamp value to [min, max]
value = Math.max(min, value);
value = Math.min(max, value);
return value;
}
static int rndInt(int nbcategories, Random rng) {
return rng.nextInt(nbcategories);
}
}