You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by jm...@apache.org on 2007/02/15 12:39:51 UTC
svn commit: r507900 - /spamassassin/trunk/masses/craig-evolve.c
Author: jm
Date: Thu Feb 15 03:39:50 2007
New Revision: 507900
URL: http://svn.apache.org/viewvc?view=rev&rev=507900
Log:
reinstate GA evolver code from r6318, due to issues with perceptron
Added:
spamassassin/trunk/masses/craig-evolve.c
Added: spamassassin/trunk/masses/craig-evolve.c
URL: http://svn.apache.org/viewvc/spamassassin/trunk/masses/craig-evolve.c?view=auto&rev=507900
==============================================================================
--- spamassassin/trunk/masses/craig-evolve.c (added)
+++ spamassassin/trunk/masses/craig-evolve.c Thu Feb 15 03:39:50 2007
@@ -0,0 +1,1298 @@
+/* @COPYRIGHT */
+/*
+ * This program uses PGAPack to do its GA stuff.
+ * ftp://ftp.mcs.anl.gov/pub/pgapack/pgapack.tar.Z
+ * I used this one instead of galib because it uses MPI
+ * to spread load around. It also seems like the API is a little
+ * cleaner.
+ */
+
+#include "pgapack.h"
+
+#include <unistd.h>
+#include <math.h>
+#include "tmp/scores.h"
+#include "tmp/tests.h"
+
+extern int num_spam, num_ham; /* in tmp/tests.h */
+
+
+/* Craig's log(score) evaluator, not as aggressive against FPs I think.
+ */
+/* #define USE_LOG_SCORE_EVALUATION */
+
+/* Use score ranges derived from hit-frequencies S/O ratio,
+ * and numbers of mails hit.
+ */
+#define USE_SCORE_RANGES
+
+#define USE_VARIABLE_MUTATIONS
+
+/* Lamarkian evolution? */
+#define LAMARK
+
+double evaluate(PGAContext *, int, int);
+int GetIntegerParameter(char *query);
+void dump(FILE *);
+void WriteString(PGAContext *ctx, FILE *fp, int p, int pop);
+void showSummary(PGAContext *ctx);
+
+#if defined(USE_VARIABLE_MUTATIONS) || (! defined(USE_SCORE_RANGES))
+int myMutation(PGAContext *, int, int, double);
+# ifdef LAMARK
+int adapt(PGAContext *, int, int, int, int,int);
+# endif
+#endif
+
+#ifdef USE_VARIABLE_MUTATIONS
+void CreateString (PGAContext *, int, int, int);
+void Crossover (PGAContext *, int, int, int, int, int, int);
+void CopyString (PGAContext *, int, int, int, int);
+int DuplicateString (PGAContext *, int, int, int, int);
+MPI_Datatype BuildDT (PGAContext *, int, int);
+#endif
+
+void dump(FILE *);
+void WriteString(PGAContext *ctx, FILE *fp, int p, int pop);
+void showSummary(PGAContext *ctx);
+
+double evaluate_inner();
+
+
+
+double threshold = 5.0;
+double nybias = 10.0;
+/* const int exhaustive_eval = 1; */
+
+int no_change_val = 300;
+
+#ifdef USE_VARIABLE_MUTATIONS
+double mutation_rate = 0.03;
+double base_mutation_rate = 0.03;
+#ifdef LAMARK
+int adapt_yn = 0;
+int adapt_ny = 0;
+#endif
+double mutation_rate_modifier = 0.85;
+int num_better_same = 0;
+int num_worse = 0;
+#else
+const double mutation_rate = 0.03;
+#endif
+
+const double mutation_noise = 0.5;
+#ifdef USE_VARIABLE_MUTATIONS
+const double min_mutation_noise = 0.1;
+#endif
+const double regression_coefficient = 0.75;
+#ifndef USE_SCORE_RANGES
+const double SCORE_CAP = 4.0;
+const double NEG_SCORE_CAP = -9.0;
+#endif
+
+#ifdef USE_VARIABLE_MUTATIONS
+const double crossover_rate = 0.5;
+#else
+const double crossover_rate = 0.65;
+#endif
+
+int pop_size = 50;
+int replace_num = 33;
+
+const int maxiter = 30000;
+
+int justCount = 0;
+
+void usage()
+{
+#ifdef USE_MPI
+ int rank;
+ MPI_Comm_rank(MPI_COMM_WORLD, &rank);
+ if(rank == 0) {
+#endif
+ printf("usage: evolve [-s size] [args]\n"
+ "\n"
+ " -s size = population size (50 recommended)\n"
+ " -r replace = number of individuals to replace each generation (20 recommended)\n"
+ " -b nybias = bias towards false negatives (10.0 default)\n"
+ " -t threshold = threshold for spam/nonspam decision (5 default)\n"
+ "\n"
+ " -C = just count hits and exit, no evolution\n\n");
+#ifdef USE_MPI
+ }
+#endif
+ exit (30);
+}
+
+void init_data()
+{
+#ifdef USE_MPI
+ int rank;
+
+ MPI_Comm_rank(MPI_COMM_WORLD, &rank);
+
+ if (rank == 0) {
+#endif
+
+ loadtests();
+ loadscores();
+ nybias = nybias*((double)num_spam)/((double)num_ham);
+#ifdef USE_VARIABLE_MUTATIONS
+ mutation_rate_modifier = (double)pow(mutation_rate_modifier,
+ (double)1/num_mutable);
+#endif
+
+#ifdef USE_MPI
+ }
+
+ MPI_Bcast(num_tests_hit, num_nondup, MPI_CHAR, 0, MPI_COMM_WORLD);
+ MPI_Bcast(&nybias, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+ MPI_Bcast(is_spam, num_nondup, MPI_CHAR, 0, MPI_COMM_WORLD);
+ MPI_Bcast(tests_hit, num_nondup*max_hits_per_msg, MPI_SHORT, 0,
+ MPI_COMM_WORLD);
+#ifdef USE_VARIABLE_MUTATIONS
+ MPI_Bcast(&mutation_rate_modifier, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+#endif
+ MPI_Bcast(is_mutatable, num_scores, MPI_CHAR, 0, MPI_COMM_WORLD);
+ MPI_Bcast(range_lo, num_scores, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+ MPI_Bcast(range_hi, num_scores, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+ MPI_Bcast(bestscores, num_scores, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+ MPI_Bcast(scores, num_scores, MPI_DOUBLE, 0, MPI_COMM_WORLD);
+#endif
+}
+
+/* this is about 35% faster than calling PGAGetRealAllele() directly inside
+ * score_msg(), in my tests. */
+void
+load_scores_into_lookup(PGAContext *ctx, int p, int pop)
+{
+ int i;
+ for (i = 0; i < num_mutable; i++) {
+ lookup[i] = PGAGetRealAllele(ctx, p, pop, i);
+#ifdef LAMARK
+ yn_hit[i] = ny_hit[i] = 0;
+#endif
+ }
+}
+
+int main(int argc, char **argv) {
+ PGAContext *ctx;
+ int i,p;
+ int arg;
+
+#ifdef USE_MPI
+ MPI_Init(&argc, &argv);
+#endif
+
+ while ((arg = getopt (argc, argv, "b:r:s:t:C")) != -1) {
+ switch (arg) {
+ case 'b':
+ nybias = atof(optarg);
+ break;
+
+ case 't':
+ threshold = (double) atof(optarg);
+ break;
+
+ case 's':
+ pop_size = atoi(optarg);
+ break;
+
+ case 'r':
+ replace_num = atoi(optarg);
+ break;
+
+ case 'C':
+ justCount = 1;
+ break;
+
+ case '?':
+ usage();
+ break;
+ }
+ }
+
+ init_data();
+
+ ctx = PGACreate(&argc, argv, PGA_DATATYPE_REAL, num_scores, PGA_MINIMIZE);
+
+ PGASetUserFunction(ctx, PGA_USERFUNCTION_PRINTSTRING,
+ (void *)WriteString);
+ PGASetUserFunction(ctx, PGA_USERFUNCTION_ENDOFGEN, (void *)showSummary);
+
+ /* use a tiny population - just want to get into the evaluate function */
+ if (justCount) {
+ pop_size = 2;
+ replace_num = 1;
+ }
+
+ PGASetPopSize(ctx, pop_size);
+
+ PGASetRealInitRange (ctx, range_lo, range_hi);
+
+ PGASetMutationBoundedFlag(ctx, PGA_FALSE);
+
+ PGASetNumReplaceValue(ctx, replace_num);
+
+ /* Defaults to this - Allen */
+ /* PGASetMutationOrCrossoverFlag(ctx, PGA_TRUE); */
+
+ if (justCount) { /* don't allow any mutation or crossover */
+ PGASetMutationType(ctx, PGA_MUTATION_CONSTANT);
+ PGASetRealInitRange (ctx, bestscores, bestscores);
+ PGASetCrossoverProb(ctx, 0.0);
+ for(i=0; i<num_scores; i++) {
+ for(p=0; p<pop_size; p++) {
+ /* just counting? score[i] = defaultscore[i] in that case */
+ PGASetRealAllele(ctx, p, PGA_NEWPOP, i, bestscores[i]);
+ }
+ }
+ } else {
+#if (! defined(USE_SCORE_RANGES)) || defined(USE_VARIABLE_MUTATIONS)
+ PGASetUserFunction(ctx, PGA_USERFUNCTION_MUTATION, (void *)myMutation);
+#else
+ PGASetMutationType(ctx, PGA_MUTATION_RANGE);
+#endif
+
+ /* PGASetCrossoverType(ctx, PGA_CROSSOVER_ONEPT); */
+ PGASetCrossoverProb(ctx, crossover_rate);
+
+#ifdef USE_VARIABLE_MUTATIONS
+ mutation_rate = 0.15/sqrt(num_mutable);
+ base_mutation_rate = mutation_rate;
+ PGASetMutationProb(ctx, mutation_rate);
+ PGASetUserFunction(ctx, PGA_USERFUNCTION_CROSSOVER,
+ (void *)Crossover);
+ PGASetUserFunction(ctx, PGA_USERFUNCTION_CREATESTRING,
+ (void *)CreateString);
+ PGASetUserFunction(ctx, PGA_USERFUNCTION_COPYSTRING,
+ (void *)CopyString);
+ PGASetUserFunction(ctx, PGA_USERFUNCTION_DUPLICATE,
+ (void *)DuplicateString);
+ PGASetUserFunction(ctx, PGA_USERFUNCTION_BUILDDATATYPE,
+ (void *)BuildDT);
+#endif
+ }
+
+ PGASetPrintFrequencyValue(ctx,300);
+ PGASetPrintOptions(ctx, PGA_REPORT_AVERAGE);
+
+ PGASetStoppingRuleType(ctx, PGA_STOP_NOCHANGE);
+ PGASetMaxNoChangeValue(ctx, no_change_val);
+ PGASetMaxGAIterValue(ctx, maxiter);
+
+ PGASetUp(ctx);
+
+#ifndef USE_VARIABLE_MUTATIONS
+ if (! justCount) {
+ /* Now initialize the scores */
+ for(i=0; i<num_scores; i++) {
+ for(p=0; p<pop_size; p++) {
+
+#ifndef USE_SCORE_RANGES
+ if (is_mutatable[i]) {
+ if(bestscores[i] > SCORE_CAP) bestscores[i] = SCORE_CAP;
+ else if(bestscores[i] < NEG_SCORE_CAP) bestscores[i] =
+ NEG_SCORE_CAP;
+ }
+#endif
+ PGASetRealAllele(ctx, p, PGA_NEWPOP, i, bestscores[i]);
+ }
+ }
+ }
+#endif /* ! USE_VARIABLE_MUTATIONS */
+
+ PGARun(ctx, evaluate);
+
+ PGADestroy(ctx);
+
+#ifdef USE_MPI
+ MPI_Finalize();
+#endif
+
+ return(0);
+}
+
+int ga_yy,ga_yn,ga_ny,ga_nn;
+#ifdef USE_VARIABLE_MUTATIONS
+int num_mutated = 0;
+int var_mutated = 0;
+int iters_same_passed = 0;
+#ifdef LAMARK
+int weight_balance;
+int adapt_times = 0;
+int adapt_crossover = 0;
+int adapt_repeat = 0;
+int adapt_overshot = 0;
+int adapt_fp_add = 0;
+int adapt_fn_add = 0;
+#endif
+#endif
+double ynscore,nyscore,yyscore,nnscore;
+
+double score_msg(PGAContext *ctx, int p, int pop, int i)
+{
+ double msg_score = 0.0;
+ int j;
+
+ /* For every test the message hit on */
+ for(j=num_tests_hit[i]-1; j>=0; j--)
+ {
+ /* Up the message score by the allele for this test in the genome
+ * msg_score += PGAGetRealAllele(ctx, p, pop, tests_hit[i][j]); */
+ msg_score += lookup[tests_hit[i][j]];
+ }
+
+ msg_score += scores[i]; /* base from non-mutable */
+
+ /* Ok, now we know the score for this message.
+ * Let's see how this genome did... */
+
+ if(is_spam[i])
+ {
+ if(msg_score >= threshold)
+ {
+ /* Good positive */
+ ga_yy += tests_count[i];
+ yyscore += msg_score*tests_count[i];
+ /* Each true positive means yyscore += at least 5 */
+ }
+ else
+ {
+ /* False negative */
+ ga_yn += tests_count[i];
+ ynscore += msg_score*tests_count[i];
+ /* Each false negative means that ynscore += less than 5 */
+#ifdef LAMARK
+ for(j=num_tests_hit[i]-1; j>=0; j--)
+ yn_hit[tests_hit[i][j]] = 1;
+#endif
+ }
+ }
+ else
+ {
+ if(msg_score >= threshold)
+ {
+ /* False positive */
+ ga_ny += tests_count[i];
+ nyscore += msg_score*tests_count[i];
+ /* Each false positive means nyscore += more than 5 */
+#ifdef LAMARK
+ for(j=num_tests_hit[i]-1; j>=0; j--)
+ ny_hit[tests_hit[i][j]] = 1;
+#endif
+ }
+ else
+ {
+ /* Good negative */
+ ga_nn += tests_count[i];
+ nnscore += msg_score*tests_count[i];
+ /* Each good negative means nnscore += less than 5 */
+ }
+ }
+
+ return msg_score*tests_count[i];
+}
+
+double evaluate(PGAContext *ctx, int p, int pop)
+{
+ double tot_score = 0.0;
+ int i;
+
+ yyscore = ynscore = nyscore = nnscore = 0.0;
+ ga_yy=ga_yn=ga_ny=ga_nn=0;
+
+ load_scores_into_lookup(ctx, p, pop);
+
+ /* For every message */
+ for (i=num_nondup-1; i>=0; i--)
+ {
+ tot_score += score_msg(ctx,p,pop,i);
+ }
+
+ if (justCount) {
+ dump(stdout);
+ exit (0);
+ }
+
+ return evaluate_inner();
+}
+
+/* So can figure out how would evaluate without above - Allen */
+
+double evaluate_inner() {
+ double ynweight,nyweight;
+#if defined(USE_LOG_SCORE_EVALUATION) && defined(LAMARK)
+ double yn_balance, ny_balance;
+#endif
+
+#ifndef USE_LOG_SCORE_EVALUATION
+
+ /* just count how far they were from the threshold, in each case */
+ ynweight = (ga_yn * threshold) - ynscore;
+ nyweight = nyscore - (ga_ny * threshold);
+
+#ifdef LAMARK
+ if (ynweight > (nyweight*nybias))
+ weight_balance = -1;
+ else if (ynweight < (nyweight*nybias))
+ weight_balance = 1;
+ else
+ weight_balance = 0;
+#endif
+
+ return ynweight + /* all FNs' points from threshold */
+ nyweight*nybias; /* all FPs' points from threshold */
+
+#else
+ /* Craig's: use log(score).
+ *
+ * off for now, let's see how the more aggressive FP-reducing algo
+ * above works
+ */
+ if(nyscore>3) nyweight = log(nyscore); else nyweight = 0;
+ if(ynscore>3) ynweight = log(ynscore); else ynweight = 0;
+
+#ifdef LAMARK
+ yn_balance = (double)ga_yn + ynweight;
+ ny_balance = ((double)ga_ny + nyweight)*nybias;
+
+ if (yn_balance > ny_balance)
+ weight_balance = -1;
+ else if (yn_balance < ny_balance)
+ weight_balance = 1;
+ else
+ weight_balance = 0;
+#endif
+
+ return /*min false-neg*/(double)ga_yn +
+ /*weighted min false-pos*/((double)ga_ny)*nybias +
+ /*min score(false-pos)*/nyweight*nybias +
+ /*max score(false-neg)*/-ynweight;
+#endif
+}
+
+#ifdef LAMARK
+int adapt(PGAContext *ctx, int p, int pop, int done_eval, int threshold,
+ int repeat) {
+ double *myscores;
+ int i;
+ int changed = 0;
+ double tmp,old_evaluation,new_evaluation;
+
+ if (justCount) {
+ return 0;
+ }
+
+ adapt_times++;
+
+ if (done_eval && PGAGetEvaluationUpToDateFlag(ctx, p, pop))
+ old_evaluation = PGAGetEvaluation(ctx, p, pop);
+ else {
+ old_evaluation = evaluate(ctx, p, pop);
+ PGASetEvaluation(ctx, p, pop, old_evaluation);
+ PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE);
+ }
+
+ if ((double)ga_yn > ((double)ga_ny*nybias))
+ weight_balance--;
+ else if ((double)ga_yn < ((double)ga_ny*nybias))
+ weight_balance++;
+
+ if ((weight_balance < (threshold-1)) &&
+ (weight_balance > -threshold))
+ return 0;
+
+ myscores = PGAGetIndividual(ctx, p, pop)->chrom;
+
+ if (repeat) {
+ for (i = 0; i < num_mutable; i++) {
+ if ((yn_hit[i] && (weight_balance < 0)) ||
+ (ny_hit[i] && (weight_balance > 0))) {
+ if (((weight_balance < 0) &&
+#ifdef USE_SCORE_RANGES
+ (myscores[i] < range_hi[i]) &&
+#endif
+ (myscores[i] < -(double)0.01)) ||
+ ((weight_balance > 0) &&
+#ifdef USE_SCORE_RANGES
+ (myscores[i] > range_lo[i]) &&
+#endif
+ (myscores[i] > (double)0.01))) {
+ tmp_scores[i][0] = (double)0.001*rint(myscores[i]); /* reducing */
+#ifdef USE_SCORE_RANGES
+ if (((myscores[i] < -(double)0.01) && ((myscores[i] - tmp_scores[i][0]) > range_hi[i])) ||
+ ((myscores[i] > (double)0.01) && ((myscores[i] - tmp_scores[i][0]) < range_lo[i]))) {
+ tmp_scores[i][0] = 0;
+ }
+#endif
+ if (tmp_scores[i][0]) {
+ changed = 1;
+ lookup[i] = 0;
+ }
+ } else
+ tmp_scores[i][0] = 0;
+ } else
+ tmp_scores[i][0] = 0;
+ }
+
+ if (! changed) /* if can't reduce, don't do anything - safe */
+ return 0;
+
+ /* For every message */
+ for (i=num_nondup-1; i>=0; i--) {
+ tmp_total[i] = scores[i];
+ scores[i] =
+ score_msg(ctx,p,pop,i)/tests_count[i]; /* score sans ones modifying */
+ }
+
+ for (i = 0; i < num_mutable; i++) {
+ if (tmp_scores[i][0]) {
+ lookup[i] = myscores[i];
+ tmp_scores[i][1] = 1;
+ if (weight_balance < 0) {
+ yn_hit[i] = 1;
+ ny_hit[i] = 0;
+ } else {
+ ny_hit[i] = 1;
+ yn_hit[i] = 0;
+ }
+ } else {
+ lookup[i] = 0;
+ tmp_scores[i][1] = 0;
+ yn_hit[i] = ny_hit[i] = 0;
+ }
+ }
+
+ while (1) {
+ changed = 0;
+ for (i = 0; i < num_mutable; i++) {
+ if (((tmp_scores[i][0] < 0) && yn_hit[i] && /* going up */
+#ifdef USE_SCORE_RANGES
+ ((lookup[i] - tmp_scores[i][0]) < range_hi[i]) &&
+#endif
+ (weight_balance < 0) && (lookup[i] < -(double)0.01)) ||
+ ((tmp_scores[i][0] > 0) && ny_hit[i] && /* going down */
+#ifdef USE_SCORE_RANGES
+ ((lookup[i] - tmp_scores[i][0]) > range_lo[i]) &&
+#endif
+ (weight_balance > 0) &&
+ (lookup[i] > (double)0.01))) {
+ lookup[i] -= tmp_scores[i][0];
+ changed = 1;
+ } else
+ tmp_scores[i][0] = 0;
+ yn_hit[i] = ny_hit[i] = 0;
+ }
+
+ if (changed) {
+ if (weight_balance > 0)
+ adapt_ny++;
+ else
+ adapt_yn++;
+ adapt_repeat++;
+ } else
+ break;
+
+ yyscore = ynscore = nyscore = nnscore = 0.0;
+ ga_yy=ga_yn=ga_ny=ga_nn=0;
+
+ for (i=num_nondup-1; i>=0; i--)
+ (void)score_msg(ctx,p,pop,i);
+
+ new_evaluation = evaluate_inner();
+
+ if (new_evaluation > old_evaluation) {
+ for (i = 0; i < num_mutable; i++) {
+ if (tmp_scores[i][0])
+ lookup[i] += tmp_scores[i][0];
+ }
+ new_evaluation = old_evaluation;
+ adapt_overshot++;
+ break;
+ } else
+ old_evaluation = new_evaluation;
+
+ if ((double)ga_yn > ((double)ga_ny*nybias))
+ weight_balance--;
+ else if ((double)ga_yn < ((double)ga_ny*nybias))
+ weight_balance++;
+
+ if ((weight_balance < (threshold-1)) &&
+ (weight_balance > -threshold))
+ break;
+ }
+ for (i=num_nondup-1; i>=0; i--)
+ scores[i] = tmp_total[i];
+
+ for (i=0; i < num_mutable; i++) {
+ if (tmp_scores[i][1])
+ myscores[i] = lookup[i];
+ }
+
+ PGASetEvaluation(ctx, p, pop, new_evaluation);
+ PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE);
+
+ return 1;
+ } else {
+ for (i = 0; i < num_mutable; i++) {
+ if ((yn_hit[i] && (weight_balance < 0)) ||
+ (ny_hit[i] && (weight_balance > 0))) {
+ tmp = (double)0.001*rint(myscores[i]);
+ if (! tmp) {
+ if (myscores[i] > (double)0.01)
+ tmp = (double)0.001;
+ else if (myscores[i] < -(double)0.01)
+ tmp = -(double)0.001;
+ }
+#ifdef USE_SCORE_RANGES
+ if (tmp && (((myscores[i] > 0) &&
+ ((myscores[i] - tmp) < range_lo[i])) ||
+ ((myscores[i] < 0) &&
+ ((myscores[i] - tmp) > range_hi[i]))))
+ tmp = 0;
+#endif
+ if (tmp) {
+ myscores[i] -= tmp;
+ changed = 1;
+ }
+ }
+ }
+
+ if (changed) {
+ if (weight_balance > 0)
+ adapt_ny++;
+ else
+ adapt_yn++;
+ return 1;
+ } else
+ return 0;
+ }
+}
+#endif
+
+/*
+ * This mutation function tosses a weighted coin for each allele.
+ * If the allele is to be mutated, then the way it's mutated is to regress it
+ * toward the mean of the population for that allele, then add a little
+ * gaussian noise.
+ *
+ * [To the _mean_? Weird... - Allen]
+ *
+ * Aug 21 2002 jm: we now use ranges and allow PGA to take care of it, if
+ * USE_SCORE_RANGES is defined.
+ *
+ * Modified for variable mutations - 9/26/02 - Allen
+ *
+ */
+#if defined(USE_VARIABLE_MUTATIONS) || (! defined(USE_SCORE_RANGES))
+int myMutation(PGAContext *ctx, int p, int pop, double mr) {
+ int count=0;
+ int i;
+# ifdef USE_VARIABLE_MUTATIONS
+ double *myscores;
+ double old_evaluation,new_evaluation,min_score,max_score;
+
+ myscores = PGAGetIndividual(ctx, p, pop)->chrom;
+ if (PGAGetEvaluationUpToDateFlag(ctx, p, pop))
+ old_evaluation = PGAGetEvaluation(ctx, p, pop);
+ else {
+ old_evaluation = evaluate(ctx, p, pop);
+ PGASetEvaluation(ctx, p, pop, old_evaluation);
+ PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE);
+ }
+
+ for (i=0; i<num_mutable; i++) {
+ tmp_scores[i][0] = 0;
+ if (PGARandomFlip(ctx, mr)) {
+#ifdef USE_SCORE_RANGES
+ min_score = range_lo[i];
+ max_score = range_hi[i];
+#else
+ min_score = SCORE_CAP;
+ max_score = NEG_SCORE_CAP;
+#endif
+ if (myscores[i] > max_score)
+ myscores[i] = max_score;
+ else if (myscores[i] < min_score)
+ myscores[i] = min_score;
+
+ tmp_scores[i][1] = (max_score - min_score)/4;
+
+ myscores[i+num_scores] *=
+ pow(2,(PGARandomGaussian(ctx,0,mutation_noise*2)));
+
+ if (myscores[i+num_scores] < min_mutation_noise)
+ myscores[i+num_scores] = min_mutation_noise;
+ else if (myscores[i+num_scores] > tmp_scores[i][1])
+ myscores[i+num_scores] = tmp_scores[i][1];
+
+ while (! tmp_scores[i][0]) {
+ tmp_scores[i][0] = PGARandomGaussian(ctx,0,
+ myscores[i+num_scores]);
+#ifdef USE_SCORE_RANGES
+ if (((double)(myscores[i] + tmp_scores[i][0]) >= max_score) ||
+ ((double)(myscores[i] + tmp_scores[i][0]) <= min_score)) {
+ if (myscores[i+num_scores] > mutation_noise) {
+ myscores[i+num_scores] =
+ (myscores[i+num_scores] + mutation_noise)/2;
+ tmp_scores[i][0] = 0;
+ } else if ((double)(myscores[i] + tmp_scores[i][0]) >= max_score) {
+ tmp_scores[i][0] = max_score - myscores[i] - (double)0.001;
+ break;
+ } else {
+ tmp_scores[i][0] = min_score - myscores[i] + (double)0.001;
+ break;
+ }
+ }
+#endif
+ }
+ myscores[i] += tmp_scores[i][0];
+ count++;
+ }
+ }
+
+ if (count > 0) {
+ var_mutated++;
+ new_evaluation = evaluate(ctx, p, pop);
+
+ if (new_evaluation > old_evaluation) {
+ /* Did previous try go too far away? */
+ if (iters_same_passed) { /* in 2nd phase */
+ count = 0;
+ for (i=0; i<num_mutable; i++) {
+ if (tmp_scores[i][0]) {
+ if (myscores[i+num_scores] > mutation_noise) {
+ tmp_scores[i][1] = PGARandomGaussian(ctx,0,mutation_noise);
+ count++;
+ } else
+ tmp_scores[i][1] =
+ PGARandomGaussian(ctx,0,myscores[i+num_scores]);
+ tmp_scores[i][1] = copysign(tmp_scores[i][1],tmp_scores[i][0]);
+#ifdef USE_SCORE_RANGES
+ if ((double)(myscores[i] + tmp_scores[i][1]
+ - tmp_scores[i][0]) >= range_hi[i]) {
+ tmp_scores[i][1] = range_hi[i] - myscores[i] +
+ tmp_scores[i][0] - (double)0.001;
+ } else if ((double)(myscores[i] + tmp_scores[i][1]
+ - tmp_scores[i][0]) <= range_lo[i]) {
+ tmp_scores[i][1] = range_lo[i] - myscores[i] +
+ tmp_scores[i][0] + (double)0.001;
+ }
+#endif
+ myscores[i] += tmp_scores[i][1] - tmp_scores[i][0];
+ }
+ }
+
+ if (count > 0) {
+ num_mutated++;
+ new_evaluation = evaluate(ctx, p, pop);
+ if (PGAGetNoDuplicatesFlag(ctx) == PGA_FALSE) {
+ /* Hack to avoid redoing evaluation without need - Allen */
+ count = 0;
+ PGASetEvaluation(ctx, p, pop, new_evaluation);
+ PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE);
+ }
+ if (new_evaluation <= old_evaluation) {
+ /* Previous try went too far away */
+ if (mr < base_mutation_rate)
+ num_better_same++;
+ for (i=0; i<num_mutable; i++) {
+ if (tmp_scores[i][0] &&
+ (myscores[i+num_scores] > mutation_noise)
+ && (fabs(tmp_scores[i][1]) < fabs(tmp_scores[i][0])))
+ myscores[i+num_scores] =
+ (myscores[i+num_scores] + mutation_noise)/2;
+ }
+ } else {
+#ifdef LAMARK
+ if (mr < base_mutation_rate) {
+ count = adapt(ctx,p,pop,1,1,0);
+ if (count) {
+ count = adapt(ctx,p,pop,0,2,1);
+ if (count)
+ new_evaluation = PGAGetEvaluation(ctx, p, pop);
+ else
+ new_evaluation = evaluate(ctx, p, pop);
+ if (new_evaluation > old_evaluation)
+ num_worse++;
+ else
+ num_better_same++; /* only had to adapt once */
+
+ if (PGAGetNoDuplicatesFlag(ctx) == PGA_FALSE) {
+ /* Hack to avoid redoing evaluation without need - Allen */
+ count = 0;
+ PGASetEvaluation(ctx, p, pop, new_evaluation);
+ PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE);
+ }
+ } else
+ num_worse++;
+ } else
+#endif
+ num_worse++;
+ }
+ } else { /* didn't decrease mutation SD */
+#ifdef LAMARK
+ if (mr < base_mutation_rate) {
+ count = adapt(ctx,p,pop,0,1,0);
+ new_evaluation = evaluate(ctx, p, pop);
+ }
+#endif
+ if (new_evaluation > old_evaluation) {
+#ifdef LAMARK
+ if ((mr < base_mutation_rate) && count) {
+ count = adapt(ctx,p,pop,1,2,1);
+ if (count) {
+ new_evaluation = PGAGetEvaluation(ctx, p, pop);
+ if (new_evaluation > old_evaluation)
+ num_worse++;
+ else
+ num_better_same++;
+ } else
+ num_worse++;
+ } else
+#endif
+ num_worse++;
+ } else
+ num_better_same++;
+ if (PGAGetNoDuplicatesFlag(ctx) == PGA_FALSE) {
+ /* Hack to avoid redoing evaluation without need - Allen */
+ count = 0;
+ PGASetEvaluation(ctx, p, pop, new_evaluation);
+ PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE);
+ }
+ }
+
+ if ((! count) &&
+ (PGAGetNoDuplicatesFlag(ctx) == PGA_TRUE))
+ count++;
+
+ } else
+ num_worse++;
+ } else {
+ if (PGAGetNoDuplicatesFlag(ctx) == PGA_FALSE) {
+ /* Hack to avoid redoing evaluation without need - Allen */
+ count = 0;
+ PGASetEvaluation(ctx, p, pop, new_evaluation);
+ PGASetEvaluationUpToDateFlag(ctx, p, pop, PGA_TRUE);
+ }
+ num_better_same++;
+ }
+ }
+#ifdef LAMARK
+ else if (mr < base_mutation_rate) {
+ count = adapt(ctx,p,pop,1,2,0);
+ if (! count)
+ num_better_same++; /* adapt not working, use mutation */
+ }
+#endif
+
+# else /* USE_VARIABLE_MUTATIONS */
+ int j;
+
+ for (i=0; i<num_mutable; i++)
+ {
+ if(PGARandomFlip(ctx, mr))
+ {
+ double gene_sum=0.0;
+ /* Find the mean */
+ for(j=0; j<pop_size; j++) {
+ if(p!=j)
+ gene_sum += PGAGetRealAllele(ctx, j, pop, i);
+ }
+ gene_sum /= (double)(pop_size-1);
+ /* Regress towards it... */
+ gene_sum = (1.0-regression_coefficient)*gene_sum+regression_coefficient*PGAGetRealAllele(ctx, p, pop, i);
+ /* Set this gene in this allele to be the average, plus some gaussian noise */
+ if(gene_sum > SCORE_CAP)
+ gene_sum = SCORE_CAP;
+ else if(gene_sum < NEG_SCORE_CAP)
+ gene_sum = NEG_SCORE_CAP;
+ PGASetRealAllele(ctx, p, pop, i,
+ PGARandomGaussian(ctx, gene_sum, mutation_noise));
+ count++;
+ }
+ }
+# endif /* !USE_VARIABLE_MUTATIONS */
+ return count;
+}
+#endif /* USE_VARIABLE_MUTATIONS || !USE_SCORE_RANGES */
+
+void dump(FILE *fp)
+{
+ fprintf (fp,"\n# SUMMARY for threshold %3.1f:\n", threshold);
+ fprintf (fp,
+ "# Correctly non-spam: %6d %4.2f%% (%4.2f%% of non-spam corpus)\n",
+ ga_nn,
+ (ga_nn / (float) num_tests) * 100.0,
+ (ga_nn / (float) num_ham) * 100.0);
+ fprintf (fp,
+ "# Correctly spam: %6d %4.2f%% (%4.2f%% of spam corpus)\n",
+ ga_yy,
+ (ga_yy / (float) num_tests) * 100.0,
+ (ga_yy / (float) num_spam) * 100.0);
+ fprintf (fp,
+ "# False positives: %6d %4.2f%% (%4.2f%% of nonspam, %6.0f weighted)\n",
+ ga_ny,
+ (ga_ny / (float) num_tests) * 100.0,
+ (ga_ny / (float) num_ham) * 100.0,
+ nyscore*nybias);
+ fprintf (fp,
+ "# False negatives: %6d %4.2f%% (%4.2f%% of spam, %6.0f weighted)\n",
+ ga_yn,
+ (ga_yn / (float) num_tests) * 100.0,
+ (ga_yn / (float) num_spam) * 100.0,
+ ynscore);
+
+ fprintf (fp,"# Average score for spam: %3.1f nonspam: %3.1f\n",(ynscore+yyscore)/((double)(ga_yn+ga_yy)),(nyscore+nnscore)/((double)(ga_nn+ga_ny)));
+ fprintf (fp,"# Average for false-pos: %3.1f false-neg: %3.1f\n",(nyscore/(double)ga_ny),(ynscore/(double)ga_yn));
+
+ fprintf (fp,"# TOTAL: %6d %3.2f%%\n\n", num_tests, 100.0);
+}
+
+/*****************************************************************************
+ * WriteString sends a visual representation of the chromosome out to fp *
+ *****************************************************************************/
+void WriteString(PGAContext *ctx, FILE *fp, int p, int pop)
+{
+ int i;
+
+#ifdef USE_MPI
+ int rank;
+ MPI_Comm_rank(MPI_COMM_WORLD, &rank);
+
+ if(0 == rank)
+ {
+#endif
+ evaluate(ctx,p,pop);
+ dump(fp);
+ for(i=0; i<num_scores; i++)
+ {
+ fprintf(fp,"score %-30s %2.3f\n",
+ score_names[i],PGAGetRealAllele(ctx, p, pop, i));
+ }
+ fprintf ( fp,"\n" );
+#ifdef USE_MPI
+ }
+#endif
+}
+
+#ifdef USE_VARIABLE_MUTATIONS
+double last_best = 0;
+#endif
+
+void showSummary(PGAContext *ctx)
+{
+#ifdef USE_MPI
+ int rank;
+
+ MPI_Comm_rank(MPI_COMM_WORLD, &rank);
+
+ if(0 == rank)
+ {
+#endif
+ if(0 == PGAGetGAIterValue(ctx) % 300)
+ {
+ int genome = PGAGetBestIndex(ctx,PGA_OLDPOP);
+ FILE *scores_file = NULL;
+ (void)evaluate(ctx, genome, PGA_OLDPOP);
+ PGAGetEvaluation(ctx, genome, PGA_OLDPOP);
+ scores_file = fopen("craig-evolve.scores","w");
+ WriteString(ctx, scores_file, genome, PGA_OLDPOP);
+ fclose(scores_file);
+#ifdef USE_VARIABLE_MUTATIONS
+ if (! justCount) {
+ printf("\nPop size, replacement: %d %d\n",
+ pop_size, replace_num);
+ printf("\nMutations (rate, good, bad, var, num): %3.7f %d %d %d %d\n",
+ mutation_rate, num_better_same, num_worse, var_mutated,
+ num_mutated);
+ var_mutated = 0;
+ num_mutated = 0;
+ if (! iters_same_passed) {
+ if (! last_best)
+ last_best = ctx->rep.Best;
+ else if ((last_best*0.999) < ctx->rep.Best) /* too slow! */
+ iters_same_passed = 1;
+ else
+ last_best = ctx->rep.Best;
+ }
+#ifdef LAMARK
+ printf("\n");
+ printf("Adapt (t, fneg, fneg_add, fpos, fpos_add): %d %d %d %d %d\n",
+ adapt_times,adapt_yn,adapt_fn_add,adapt_ny,adapt_fp_add);
+ printf("Adapt (over, cross, repeat): %d %d %d\n",
+ adapt_overshot,adapt_crossover,adapt_repeat);
+ adapt_times = adapt_overshot = adapt_crossover = adapt_repeat =
+ adapt_yn = adapt_ny = adapt_fn_add = adapt_fp_add = 0;
+#endif
+ }
+#endif
+ dump(stdout);
+ }
+ else if(0 == PGAGetGAIterValue(ctx) % 5)
+ {
+ printf("%d",(PGAGetGAIterValue(ctx)/5)%10);
+ }
+
+#ifdef USE_VARIABLE_MUTATIONS
+ if (! justCount) {
+ if ((num_better_same*4) >= num_worse)
+ mutation_rate /= mutation_rate_modifier;
+ else if ((num_better_same*4) < num_worse) {
+ if ((mutation_rate > base_mutation_rate) || iters_same_passed)
+ mutation_rate *= mutation_rate_modifier;
+ else if (ctx->ga.ItersOfSame >= (no_change_val/2)) {
+ iters_same_passed = 1;
+ mutation_rate *= mutation_rate_modifier;
+ printf("\nMutation rate %3.7f (ItersOfSame %d)\n",
+ mutation_rate,ctx->ga.ItersOfSame);
+ } else
+ return;
+ }
+
+ if (mutation_rate > mutation_rate_modifier) {
+ mutation_rate = mutation_rate_modifier;
+ printf("\nMutation rate max: %3.7f\n",mutation_rate);
+ } else if (mutation_rate < 0.05/sqrt(num_mutable)) {
+ mutation_rate = 0.05/sqrt(num_mutable);
+ printf("\nMutation rate min: %3.7f\n",mutation_rate);
+ }
+
+ PGASetMutationProb(ctx, mutation_rate);
+
+ num_better_same = 0;
+ num_worse = 0;
+ }
+#endif
+
+#ifdef USE_MPI
+ }
+#endif
+}
+
+#ifdef USE_VARIABLE_MUTATIONS
+/*****************************************************************************
+ * CreateString allocates and initializes a chromosome. If InitFlag is *
+ * set to true, then it will initialize the chromosome using the best known *
+ * values; otherwise, it sets each double to 0.0 and each int to 0. *
+ *****************************************************************************/
+void CreateString(PGAContext *ctx, int p, int pop, int InitFlag) {
+ int i;
+ double *myscore;
+
+ PGAIndividual *new;
+
+ new = PGAGetIndividual(ctx, p, pop);
+ if (!(new->chrom = malloc(sizeof(double)*num_scores*2))) {
+ fprintf(stderr, "No room for new->chrom");
+ exit(1);
+ }
+ myscore = new->chrom;
+ if (InitFlag) {
+ for(i=0; i<num_scores; i++)
+ myscore[i] = bestscores[i];
+ for(i=num_scores; i<num_scores*2; i++)
+ myscore[i] = mutation_noise;
+ } else {
+ for(i=0; i<num_scores*2; i++)
+ myscore[i] = 0.0;
+ }
+}
+
+
+/*****************************************************************************
+ * Crossover implements uniform crossover on the chromosome. *
+ *****************************************************************************/
+void Crossover(PGAContext *ctx, int p1, int p2, int pop1, int t1, int t2,
+ int pop2) {
+ int i;
+ double *parent1, *parent2, *child1, *child2;
+ double pu;
+#ifdef LAMARK
+ double parent1_eval, parent2_eval, child1_eval, child2_eval;
+#endif
+
+ parent1 = PGAGetIndividual(ctx, p1, pop1)->chrom;
+ parent2 = PGAGetIndividual(ctx, p2, pop1)->chrom;
+ child1 = PGAGetIndividual(ctx, t1, pop2)->chrom;
+ child2 = PGAGetIndividual(ctx, t2, pop2)->chrom;
+
+ pu = PGAGetUniformCrossoverProb(ctx);
+
+ for (i = 0; i < num_mutable; i++) {
+ if (PGARandomFlip(ctx, pu)) {
+ child1[i] = parent2[i];
+ child2[i] = parent1[i];
+ if (num_mutated > 0) {
+ if (fabs(parent1[i+num_scores] - mutation_noise) >
+ fabs(parent1[i+num_scores] - parent2[i+num_scores]))
+ child2[i+num_scores] =
+ (parent1[i+num_scores] + parent2[i+num_scores])/2;
+ else
+ child2[i+num_scores] =
+ (parent1[i+num_scores] + mutation_noise)/2;
+ if (fabs(parent2[i+num_scores] - mutation_noise) >
+ fabs(parent2[i+num_scores] - parent1[i+num_scores]))
+ child1[i+num_scores] =
+ (parent2[i+num_scores] + parent1[i+num_scores])/2;
+ else
+ child1[i+num_scores] =
+ (parent2[i+num_scores] + mutation_noise)/2;
+ } else {
+ /* Doing intermediate recombination due to usage
+ * of exponential multiplication in mutation - Allen */
+ child1[i+num_scores] = child2[i+num_scores] =
+ (parent1[i+num_scores] + parent2[i+num_scores])/2;
+ }
+ } else {
+ child1[i] = parent1[i];
+ child2[i] = parent2[i];
+ if (pu < 0.5) { /* more grouped */
+ child1[i+num_scores] = parent1[i+num_scores];
+ child2[i+num_scores] = parent2[i+num_scores];
+ } else {
+ if (num_mutated > 0) {
+ if (fabs(parent1[i+num_scores] - mutation_noise) >
+ fabs(parent1[i+num_scores] - parent2[i+num_scores]))
+ child1[i+num_scores] =
+ (parent1[i+num_scores] + parent2[i+num_scores])/2;
+ else
+ child1[i+num_scores] =
+ (parent1[i+num_scores] + mutation_noise)/2;
+ if (fabs(parent2[i+num_scores] - mutation_noise) >
+ fabs(parent2[i+num_scores] - parent1[i+num_scores]))
+ child2[i+num_scores] =
+ (parent2[i+num_scores] + parent1[i+num_scores])/2;
+ else
+ child2[i+num_scores] =
+ (parent2[i+num_scores] + mutation_noise)/2;
+ } else {
+ /* Doing intermediate recombination due to usage
+ * of exponential multiplication in mutation - Allen */
+ child1[i+num_scores] = child2[i+num_scores] =
+ (parent1[i+num_scores] + parent2[i+num_scores])/2;
+ }
+ }
+ }
+ }
+ for (i = num_mutable; i < num_scores; i++) {
+ child1[i] = parent1[i];
+ child2[i] = parent2[i];
+ child1[i+num_scores] = parent1[i+num_scores];
+ child2[i+num_scores] = parent2[i+num_scores];
+ }
+
+#ifdef LAMARK
+ if ((PGAGetMutationAndCrossoverFlag(ctx) == PGA_FALSE) &&
+ (mutation_rate < base_mutation_rate) &&
+ (PGAGetEvaluationUpToDateFlag(ctx, p1, pop1) == PGA_TRUE) &&
+ (PGAGetEvaluationUpToDateFlag(ctx, p2, pop1) == PGA_TRUE)) {
+ parent1_eval = PGAGetEvaluation(ctx, p1, pop1);
+ parent2_eval = PGAGetEvaluation(ctx, p2, pop1);
+
+ if (PGARandomFlip(ctx, (double)0.5)) {
+ child1_eval = evaluate(ctx, t1, pop2);
+ if ((child1_eval > parent1_eval) &&
+ (child1_eval > parent2_eval)) {
+ /* Urk! */
+ if (PGARandomFlip(ctx, (double)(mutation_rate/base_mutation_rate)))
+ adapt_crossover += adapt(ctx, t1, pop2, 1, 2, 0);
+ else { /* low mr */
+ if (adapt(ctx, t1, pop2, 1, 1, 0))
+ adapt_crossover += adapt(ctx, t1, pop2, 0, 2, 1) + 1;
+ adapt_crossover += adapt(ctx, t2, pop2, 0, 2, 0);
+ }
+ } else {
+ PGASetEvaluation(ctx, t1, pop2, child1_eval);
+ PGASetEvaluationUpToDateFlag(ctx, t1, pop2, PGA_TRUE);
+ }
+ } else {
+ child2_eval = evaluate(ctx, t2, pop2);
+
+ if ((child2_eval > parent1_eval) &&
+ (child2_eval > parent2_eval)) {
+ /* Urk! */
+ if (PGARandomFlip(ctx, (double)(mutation_rate/base_mutation_rate)))
+ adapt_crossover += adapt(ctx, t2, pop2, 1, 2, 0);
+ else { /* low mr */
+ if (adapt(ctx, t2, pop2, 1, 1, 0))
+ adapt_crossover += adapt(ctx, t2, pop2, 0, 2, 1) + 1;
+ adapt_crossover += adapt(ctx, t1, pop2, 0, 2, 0);
+ }
+ } else {
+ PGASetEvaluation(ctx, t2, pop2, child2_eval);
+ PGASetEvaluationUpToDateFlag(ctx, t2, pop2, PGA_TRUE);
+ }
+ }
+ }
+
+#endif
+}
+
+
+/*****************************************************************************
+ * CopyString makes a copy of the chromosome at (p1, pop1) and puts it at *
+ * (p2, pop2). *
+ *****************************************************************************/
+void CopyString(PGAContext *ctx, int p1, int pop1, int p2, int pop2) {
+ void *d, *s;
+
+ s = PGAGetIndividual(ctx, p1, pop1)->chrom;
+ d = PGAGetIndividual(ctx, p2, pop2)->chrom;
+ memcpy(d, s, sizeof(double)*num_scores*2);
+}
+
+
+/*****************************************************************************
+ * DuplicateString compares two chromosomes and returns 1 if they are the *
+ * same and 0 if they are different. *
+ *****************************************************************************/
+int DuplicateString(PGAContext *ctx, int p1, int pop1, int p2, int pop2) {
+ void *a, *b;
+
+ a = PGAGetIndividual(ctx, p1, pop1)->chrom;
+ b = PGAGetIndividual(ctx, p2, pop2)->chrom;
+ return (!memcmp(a, b, sizeof(double)*num_scores*2));
+}
+
+/*****************************************************************************
+ * BuildDatattype builds an MPI datatype for sending strings to other *
+ * processors. Consult your favorite MPI manual for more information. *
+ *****************************************************************************/
+MPI_Datatype BuildDT(PGAContext *ctx, int p, int pop) {
+ MPI_Datatype DT_PGAIndividual;
+#ifdef USE_MPI
+ int counts[3];
+ MPI_Aint displs[3];
+ MPI_Datatype types[3];
+ PGAIndividual *P;
+
+ P = PGAGetIndividual(ctx, p, pop);
+
+ /* Build the MPI datatype. Every user defined function needs these.
+ * The first two calls are stuff that is internal to PGAPack, but
+ * the user still must include it. See pgapack.h for details one the
+ * fields (under PGAIndividual)
+ */
+ MPI_Address(&P->evalfunc, &displs[0]);
+ counts[0] = 2;
+ types[0] = MPI_DOUBLE;
+
+ /* Next, we have an integer, evaluptodate. */
+ MPI_Address(&P->evaluptodate, &displs[1]);
+ counts[1] = 1;
+ types[1] = MPI_INT;
+
+ /* Finally, we have the actual user-defined string. */
+ MPI_Address(P->chrom, &displs[2]);
+ counts[2] = num_scores*2;
+ types[2] = MPI_DOUBLE;
+
+ MPI_Type_struct(3, counts, displs, types, &DT_PGAIndividual);
+#endif /* defined(USE_MPI) */
+ MPI_Type_commit(&DT_PGAIndividual);
+ return(DT_PGAIndividual);
+}
+#endif /* defined(USE_VARIABLE_MUTATIONS) */
+