<commit_before>/*
 * Copyright 2013-{year} <NAME> (<EMAIL>)
 *
 * Licensed 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 com.antigenomics.jsres;

import java.util.Arrays;
import java.util.Random;

public final class SRES {
    public static final Random random = new Random(51102);

    private static final int boundingTries = 10;
    private final Objective objective;
    private final double tau, tauDash, rankingPenalizationFactor;
    private final int lambda, mu, numberOfFeatures, numberOfSweeps;

    public SRES(Objective objective) {
        this(objective, 200, 30, 1.0, 200, 0.45);
    }

    public SRES(Objective objective,
                int lambda, int mu,
                double expectedConvergenceRate,
                int numberOfSweeps, double rankingPenalizationFactor) {
        this.objective = objective;
        this.numberOfFeatures = objective.getNumberOfFeatures();
        this.lambda = lambda;
        this.mu = mu;
        this.tau = expectedConvergenceRate / Math.sqrt(2 * Math.sqrt(numberOfFeatures));
        this.tauDash = expectedConvergenceRate / Math.sqrt(2 * numberOfFeatures);
        this.numberOfSweeps = numberOfSweeps;
        this.rankingPenalizationFactor = rankingPenalizationFactor;
    }

    public SolutionSet run() {
        return run(1750);
    }

    public SolutionSet run(int numberOfGenerations) {
        SolutionSet solutionSet = new SolutionSet();

        for (int i = 0; i < numberOfGenerations; i++) {
            solutionSet.evaluate();
            solutionSet.sort();
            solutionSet = solutionSet.evolve();
        }

        solutionSet.evaluate();
        solutionSet.sort();

        return solutionSet;
    }

    public class SolutionSet {
        private final Solution[] solutions;

        SolutionSet() {
            this.solutions = new Solution[lambda];
            for (int i = 0; i < lambda; i++) {
                solutions[i] = new Solution(objective.generateFeatureVector(), objective.getMutationRates());
            }
        }

        SolutionSet(Solution[] solutions) {
            this.solutions = solutions;
        }

        void sort() {
            for (int i = 0; i < numberOfSweeps; i++) {
                boolean swapped = false;
                for (int j = 0; j < lambda - 1; j++) {
                    double p = random.nextDouble(),
                            p1 = solutions[j].getPenalty(),
                            p2 = solutions[j + 1].getPenalty();
                    if (p < rankingPenalizationFactor || (p1 == 0 && p2 == 0)) {
                        // no solution break constraints or penalization is not applied
                        if (solutions[j].getFitness() > solutions[j + 1].getFitness()) {
                            swap(j, j + 1);
                            swapped = true;
                        }
                    } else if (p1 > p2) {
                        // constraint penalization
                        swap(j, j + 1);
                        swapped = true;
                    }
                }
                if (!swapped) {
                    // sorted
                    break;
                }
            }
        }

        SolutionSet evolve() {
            Solution[] newSolutions = new Solution[lambda];

            int j = 0;
            for (int i = 0; i < lambda; i++) {
                // mutation rates are sampled from top solutions
                double[] sampledMutationRates = new double[numberOfFeatures];

                for (int k = 0; k < numberOfFeatures; k++) {
                    sampledMutationRates[k] = solutions[random.nextInt(mu)].mutationRates[k];
                }

                // top individual generates offspring
                newSolutions[i] = solutions[j].generateOffspring(sampledMutationRates);

                if (j++ > mu) {
                    // cycle top mu solutions
                    j = 0;
                }
            }

            return new SolutionSet(newSolutions);
        }

        void evaluate() {
            Arrays.stream(solutions).parallel().forEach(Solution::evaluate);
        }

        private void swap(int i, int j) {
            Solution tmp = solutions[i];
            solutions[i] = solutions[j];
            solutions[j] = tmp;
        }

        public Solution[] getSolutions() {
            return solutions;
        }

        public Solution getBestSolution() {
            return solutions[0];
        }
    }

    public class Solution {
        private final double[] features, mutationRates;
        private Objective.Result objectiveResult = null;

        Solution() {
            this.features = new double[numberOfFeatures];
            this.mutationRates = new double[numberOfFeatures];
        }

        Solution(double[] features, double[] mutationRates) {
            this.features = features;
            this.mutationRates = mutationRates;
        }

        public double[] getFeatures() {
            return features;
        }

        public Objective.Result getObjectiveResult() {
            return objectiveResult;
        }

        double getFitness() {
            return objectiveResult.getValue();
        }

        double getPenalty() {
            return objectiveResult.getPenalty();
        }

        Solution generateOffspring(double[] sampledMutationRates) {
            Solution offspring = new Solution();

            double globalLearningRate = tauDash * random.nextGaussian();

            for (int i = 0; i < numberOfFeatures; i++) {
                // global intermediate recombination for mutation rates
                double newMutationRate = (mutationRates[i] + sampledMutationRates[i]) / 2 *
                        // lognormal update for mutation rates
                        Math.exp(globalLearningRate + tau * random.nextGaussian());
                offspring.mutationRates[i] = newMutationRate;
                // generate offspring features
                offspring.features[i] = features[i]; // in case fail to bound

                double newFeatureValue;
                for (int j = 0; j < boundingTries; j++) {
                    // try generating new feature value and check whether it is in bounds
                    newFeatureValue = features[i] + newMutationRate * random.nextGaussian();
                    if (objective.inBounds(newFeatureValue, i)) {
                        offspring.features[i] = newFeatureValue;
                        break;
                    }
                }

            }

            return offspring;
        }

        void evaluate() {
            objectiveResult = objective.evaluate(features);
        }
    }
}<commit_msg>SRES: add verbosity, fix sorting order (-rank), enforce bounds on mutation rates (CRITICAL)<commit_after>/*
 * Copyright 2013-{year} <NAME> (<EMAIL>)
 *
 * Licensed 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 com.antigenomics.jsres;

import java.util.Arrays;
import java.util.Random;

public final class SRES {
    public static final Random random = new Random(51102);

    private static final int boundingTries = 10;
    private final Objective objective;
    private final double tau, tauDash, rankingPenalizationFactor;
    private final int lambda, mu, numberOfFeatures, numberOfSweeps;

    private final boolean verbose;

    public SRES(Objective objective) {
        this(objective, true);
    }

    public SRES(Objective objective, boolean verbose) {
        this(objective, verbose, 200, 30, 1.0, 200, 0.45);
    }

    public SRES(Objective objective, boolean verbose,
                int lambda, int mu,
                double expectedConvergenceRate,
                int numberOfSweeps, double rankingPenalizationFactor) {
        this.objective = objective;
        this.verbose = verbose;
        this.numberOfFeatures = objective.getNumberOfFeatures();
        this.lambda = lambda;
        this.mu = mu;
        this.tau = expectedConvergenceRate / Math.sqrt(2 * Math.sqrt(numberOfFeatures));
        this.tauDash = expectedConvergenceRate / Math.sqrt(2 * numberOfFeatures);
        this.numberOfSweeps = numberOfSweeps;
        this.rankingPenalizationFactor = rankingPenalizationFactor;
    }

    public SolutionSet run() {
        return run(1750);
    }

    public SolutionSet run(int numberOfGenerations) {
        SolutionSet solutionSet = new SolutionSet();

        for (int i = 0; i < numberOfGenerations; i++) {
            solutionSet.evaluate();
            solutionSet.sort();

            if (verbose && i % 100 == 0) {
                Solution bestSolution = solutionSet.getBestSolution();
                System.out.println("SRES ran for " + i + " generations, best solution fitness is " +
                        bestSolution.getFitness() + ", penalty is " + bestSolution.getPenalty());
            }

            solutionSet = solutionSet.evolve();
        }

        solutionSet.evaluate();
        solutionSet.sort();

        if (verbose) {
            Solution bestSolution = solutionSet.getBestSolution();
            System.out.println("SRES finished, best solution fitness is " +
                    bestSolution.getFitness() + ", penalty is " + bestSolution.getPenalty());
        }

        return solutionSet;
    }

    public class SolutionSet {
        private final Solution[] solutions;

        SolutionSet() {
            this.solutions = new Solution[lambda];
            for (int i = 0; i < lambda; i++) {
                solutions[i] = new Solution(objective.generateFeatureVector(), objective.getMutationRates());
            }
        }

        SolutionSet(Solution[] solutions) {
            this.solutions = solutions;
        }

        void sort() {
            for (int i = 0; i < numberOfSweeps; i++) {
                boolean swapped = false;
                for (int j = 0; j < lambda - 1; j++) {
                    double p = random.nextDouble(),
                            p1 = solutions[j].getPenalty(),
                            p2 = solutions[j + 1].getPenalty();
                    if (p < rankingPenalizationFactor || (p1 == 0 && p2 == 0)) {
                        // no solution break constraints or penalization is not applied
                        if (solutions[j].getFitness() < solutions[j + 1].getFitness()) {
                            swap(j, j + 1);
                            swapped = true;
                        }
                    } else if (p1 < p2) {
                        // constraint penalization
                        swap(j, j + 1);
                        swapped = true;
                    }
                }
                if (!swapped) {
                    // sorted
                    break;
                }
            }
        }

        SolutionSet evolve() {
            Solution[] newSolutions = new Solution[lambda];

            int j = 0;
            for (int i = 0; i < lambda; i++) {
                // mutation rates are sampled from top solutions
                double[] sampledMutationRates = new double[numberOfFeatures];

                for (int k = 0; k < numberOfFeatures; k++) {
                    sampledMutationRates[k] = solutions[random.nextInt(mu)].mutationRates[k];
                }

                // top individual generates offspring
                newSolutions[i] = solutions[j].generateOffspring(sampledMutationRates);

                if (j++ > mu) {
                    // cycle top mu solutions
                    j = 0;
                }
            }

            return new SolutionSet(newSolutions);
        }

        void evaluate() {
            Arrays.stream(solutions).parallel().forEach(Solution::evaluate);
        }

        private void swap(int i, int j) {
            Solution tmp = solutions[i];
            solutions[i] = solutions[j];
            solutions[j] = tmp;
        }

        public Solution[] getSolutions() {
            return solutions;
        }

        public Solution getBestSolution() {
            return solutions[0];
        }
    }

    public class Solution {
        private final double[] features, mutationRates;
        private Objective.Result objectiveResult = null;

        Solution() {
            this.features = new double[numberOfFeatures];
            this.mutationRates = new double[numberOfFeatures];
        }

        Solution(double[] features, double[] mutationRates) {
            this.features = features;
            this.mutationRates = mutationRates;
        }

        public double[] getFeatures() {
            return features;
        }

        public Objective.Result getObjectiveResult() {
            return objectiveResult;
        }

        double getFitness() {
            return objectiveResult.getObjective().isMaximizationProblem() ?
                    objectiveResult.getValue() : -objectiveResult.getValue();
        }

        double getPenalty() {
            return objectiveResult.getPenalty();
        }

        Solution generateOffspring(double[] sampledMutationRates) {
            Solution offspring = new Solution();

            double globalLearningRate = tauDash * random.nextGaussian();

            double[] mutationRateConstraints = objective.getMutationRates();

            for (int i = 0; i < numberOfFeatures; i++) {
                // global intermediate recombination for mutation rates
                double newMutationRate = (mutationRates[i] + sampledMutationRates[i]) / 2 *
                        // lognormal update for mutation rates
                        Math.exp(globalLearningRate + tau * random.nextGaussian());
                offspring.mutationRates[i] = Math.min(newMutationRate, mutationRateConstraints[i]);
                // generate offspring features
                offspring.features[i] = features[i]; // in case fail to bound

                double newFeatureValue;
                for (int j = 0; j < boundingTries; j++) {
                    // try generating new feature value and check whether it is in bounds
                    newFeatureValue = features[i] + newMutationRate * random.nextGaussian();
                    if (objective.inBounds(newFeatureValue, i)) {
                        offspring.features[i] = newFeatureValue;
                        break;
                    }
                }

            }

            return offspring;
        }

        void evaluate() {
            objectiveResult = objective.evaluate(features);
        }

        @Override
        public String toString() {
            return "SOLUTION " + Arrays.toString(features) + ",fitness is " + getFitness() + ", penalty is " +
                    getPenalty() + "\n" + objectiveResult.toString();
        }
    }
}
{'fraction_non_alphanumeric': 0.09184063136456212, 'fraction_numerical': 0.007764765784114053, 'mean_word_length': 1.6777436946148603, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6123431', 'n_tokens_mistral': 4138, 'n_tokens_neox': 3754, 'n_words': 1081}