You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by er...@apache.org on 2022/06/27 13:24:54 UTC

[commons-math] branch feature__MATH-1563__genetic_algorithm updated (ddfd5bf85 -> aa0d427dd)

This is an automated email from the ASF dual-hosted git repository.

erans pushed a change to branch feature__MATH-1563__genetic_algorithm
in repository https://gitbox.apache.org/repos/asf/commons-math.git


    from ddfd5bf85 MATH-1618: Design proposal (WIP).
     new 69c120c01 git-wip-us => gitbox
     new 3c30a6d52 MATH-1644: Prevent computed probability from exceeding 1.
     new f0fe9ab8e MATH-1618: Replace class "OnePointCrossover" with "NPointCrossover".
     new 383256f8c MATH-1618: Make "stopping condition" a "BiPredicate".
     new aa0d427dd Javadoc.

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../ga/mathfunctions/MathFunctionOptimizer2.java   | 16 ++---
 .../commons/math4/ga2/GeneticAlgorithmFactory.java | 13 ++--
 .../org/apache/commons/math4/ga2/Population.java   | 24 ++++++-
 ...OnePointCrossover.java => NPointCrossover.java} | 77 ++++++++++++++++------
 .../commons/math4/ga2/gene/binary/Operators.java   |  7 +-
 .../package-info.java}                             | 21 +++---
 .../commons/math4/ga2/stop/UnchangedFitness.java   | 27 +++++---
 .../math4/legacy/stat/inference/BinomialTest.java  |  2 +-
 .../legacy/stat/inference/BinomialTestTest.java    |  7 ++
 doc/release/release.howto.txt                      |  4 +-
 10 files changed, 133 insertions(+), 65 deletions(-)
 rename commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/{OnePointCrossover.java => NPointCrossover.java} (50%)
 copy commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/{ChromosomeFactory.java => gene/package-info.java} (66%)


[commons-math] 05/05: Javadoc.

Posted by er...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch feature__MATH-1563__genetic_algorithm
in repository https://gitbox.apache.org/repos/asf/commons-math.git

commit aa0d427dd6dd1b98a3bfc97896ddef30eb4077de
Author: Gilles Sadowski <gi...@gmail.com>
AuthorDate: Mon Jun 27 15:16:27 2022 +0200

    Javadoc.
---
 .../org/apache/commons/math4/ga2/Population.java   | 24 +++++++++++++++---
 .../commons/math4/ga2/gene/package-info.java       | 29 ++++++++++++++++++++++
 2 files changed, 50 insertions(+), 3 deletions(-)

diff --git a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/Population.java b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/Population.java
index a75979acc..f8cf7639b 100644
--- a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/Population.java
+++ b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/Population.java
@@ -29,7 +29,23 @@ import org.apache.commons.rng.UniformRandomProvider;
 /**
  * Collection of chromosomes and associated fitness.
  *
- * Class is <em>not</em> thread-safe.
+ * <p>
+ * Notes:
+ * <ul>
+ *  <li>
+ *   Class is <em>not</em> thread-safe.
+ *  </li>
+ *  <li>
+ *   Class assumes that each chromosome instance (of type {@code <G>})
+ *   identifies a unique individual, irrespective of whether other
+ *   individuals share the same sequence of genes; hence type {@code <G>}
+ *   must <em>not</em> override method {@link Object#equals(Object)
+ *   equals(Object o)}.
+ *   In other words, if an overridden {@code equals} compares equality of
+ *   gene sequences, the current implementation of this class will only
+ *   contain individuals whose gene sequence is unique.
+ *  </li>
+ * </ul>
  *
  * @param <G> Genotype.
  * @param <P> Phenotype.
@@ -71,9 +87,13 @@ public class Population<G, P> {
 
     /**
      * Insert chromosomes into the population.
+     *
      * Fitness and rank are calculated.
      * If the fitness is {@code NaN}, the corresponding chromosome is
      * <em>not</em> added to the population.
+     * <p>
+     * Note: All the {@code chromosomes} are passed in a single call to the
+     * {@link FitnessService#apply(Function,Collection) fitness calculator}.
      *
      * @param chromosomes Chromosomes.
      */
@@ -102,8 +122,6 @@ public class Population<G, P> {
         }
     }
 
-    /**
-     *
     /**
      * Retrieves the rank.
      * Ranks are attributed in the range [0, N - 1] (where N is the
diff --git a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/package-info.java b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/package-info.java
new file mode 100644
index 000000000..2a10d7c27
--- /dev/null
+++ b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+/**
+ * Chromosome types (and operators that can be applied to them) are
+ * defined in sub-packages of this package.
+ *
+ * Note about implementing a specific representation (genotype):
+ * <ul>
+ *  <li>
+ *   Implementation must <em>not</em> override {@link Object#equals(Object)}
+ *   (cf. {@link org.apache.commons.math4.ga2.Population Population}).
+ *  </li>
+ * </ul>
+ */
+package org.apache.commons.math4.ga2.gene;


[commons-math] 02/05: MATH-1644: Prevent computed probability from exceeding 1.

Posted by er...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch feature__MATH-1563__genetic_algorithm
in repository https://gitbox.apache.org/repos/asf/commons-math.git

commit 3c30a6d520a13e1f2548ee7414356c10ec6c92f4
Author: Gilles Sadowski <gi...@gmail.com>
AuthorDate: Sat Apr 9 14:07:03 2022 +0200

    MATH-1644: Prevent computed probability from exceeding 1.
---
 .../apache/commons/math4/legacy/stat/inference/BinomialTest.java   | 2 +-
 .../commons/math4/legacy/stat/inference/BinomialTestTest.java      | 7 +++++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/inference/BinomialTest.java b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/inference/BinomialTest.java
index 80650c9b4..a7deb3187 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/inference/BinomialTest.java
+++ b/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/stat/inference/BinomialTest.java
@@ -138,7 +138,7 @@ public class BinomialTest {
                     if (criticalValueLow == criticalValueHigh) {
                         pTotal += pLow;
                     } else {
-                        pTotal += 2 * pLow;
+                        pTotal += 2 * Math.nextDown(pLow);
                     }
                     criticalValueLow++;
                     criticalValueHigh--;
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/inference/BinomialTestTest.java b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/inference/BinomialTestTest.java
index bf83dfa17..f480e0b66 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/inference/BinomialTestTest.java
+++ b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/stat/inference/BinomialTestTest.java
@@ -126,4 +126,11 @@ public class BinomialTestTest {
         Assert.assertFalse(testStatistic.binomialTest(trials, successes, probability, AlternativeHypothesis.GREATER_THAN, alpha01));
         Assert.assertFalse(testStatistic.binomialTest(trials, successes, probability, AlternativeHypothesis.LESS_THAN, alpha05));
     }
+
+    @Test
+    public void testMath1644() {
+        final BinomialTest bt = new BinomialTest();
+        final double pval = bt.binomialTest(10, 5, 0.5, AlternativeHypothesis.TWO_SIDED);
+        Assert.assertTrue("pval=" + pval, pval <= 1);
+    }
 }


[commons-math] 01/05: git-wip-us => gitbox

Posted by er...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch feature__MATH-1563__genetic_algorithm
in repository https://gitbox.apache.org/repos/asf/commons-math.git

commit 69c120c0158a061043bb77b2761a4786ae6aadd0
Author: Sebb <se...@apache.org>
AuthorDate: Tue Apr 5 11:37:39 2022 +0100

    git-wip-us => gitbox
---
 doc/release/release.howto.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/doc/release/release.howto.txt b/doc/release/release.howto.txt
index 2fa64bf8b..6f8275caf 100644
--- a/doc/release/release.howto.txt
+++ b/doc/release/release.howto.txt
@@ -227,7 +227,7 @@ Switch to a new directory out of your regular workspace, and retrieve
 the official tag from the Apache repository:
 
   $ cd /tmp
-  $ git clone https://git-wip-us.apache.org/repos/asf/commons-math.git --branch MATH_3_4_RC1
+  $ git clone https://gitbox.apache.org/repos/asf/commons-math.git --branch MATH_3_4_RC1
 
 In the command above, the --branch option accepts both branch names and tags names,
 so we specify directly the tag here. Git will warn that the resulting workspace
@@ -343,7 +343,7 @@ Tag name:
   MATH_3_4_RC1 (signature can be checked from git using 'git tag -v')
 
 Tag URL:
-  <https://git-wip-us.apache.org/repos/asf?p=commons-math.git;a=commit;h=cf4a9d70c9ac24dd7196995390171150e4e56451>
+  <https://gitbox.apache.org/repos/asf?p=commons-math.git;a=commit;h=cf4a9d70c9ac24dd7196995390171150e4e56451>
 
 Commit ID the tag points at:
   cf4a9d70c9ac24dd7196995390171150e4e56451


[commons-math] 04/05: MATH-1618: Make "stopping condition" a "BiPredicate".

Posted by er...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch feature__MATH-1563__genetic_algorithm
in repository https://gitbox.apache.org/repos/asf/commons-math.git

commit 383256f8cfcbf1de70f93a1d1ea0e7c972812af0
Author: Gilles Sadowski <gi...@gmail.com>
AuthorDate: Sun Jun 12 02:41:56 2022 +0200

    MATH-1618: Make "stopping condition" a "BiPredicate".
    
    Second argument is the generation count (as determined by the caller).
---
 .../ga/mathfunctions/MathFunctionOptimizer2.java   |  4 ++--
 .../commons/math4/ga2/GeneticAlgorithmFactory.java | 13 +++++------
 .../commons/math4/ga2/stop/UnchangedFitness.java   | 27 +++++++++++++++-------
 3 files changed, 27 insertions(+), 17 deletions(-)

diff --git a/commons-math-examples/examples-ga/examples-ga-math-functions/src/main/java/org/apache/commons/math4/examples/ga/mathfunctions/MathFunctionOptimizer2.java b/commons-math-examples/examples-ga/examples-ga-math-functions/src/main/java/org/apache/commons/math4/examples/ga/mathfunctions/MathFunctionOptimizer2.java
index 96ea5087b..ac128d89e 100644
--- a/commons-math-examples/examples-ga/examples-ga-math-functions/src/main/java/org/apache/commons/math4/examples/ga/mathfunctions/MathFunctionOptimizer2.java
+++ b/commons-math-examples/examples-ga/examples-ga-math-functions/src/main/java/org/apache/commons/math4/examples/ga/mathfunctions/MathFunctionOptimizer2.java
@@ -23,7 +23,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.function.Function;
 import java.util.function.ToDoubleFunction;
-import java.util.function.Predicate;
+import java.util.function.BiPredicate;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Callable;
@@ -100,7 +100,7 @@ public final class MathFunctionOptimizer2 {
         };
 
         // Stopping condition (not thread-safe).
-        final Predicate<Population<Chromosome, Coordinates>> stop =
+        final BiPredicate<Population<Chromosome, Coordinates>, Integer> stop =
             new UnchangedFitness(UnchangedFitness.Type.BEST,
                                  numGeneration);
 
diff --git a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/GeneticAlgorithmFactory.java b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/GeneticAlgorithmFactory.java
index 40ec98249..9386e5617 100644
--- a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/GeneticAlgorithmFactory.java
+++ b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/GeneticAlgorithmFactory.java
@@ -23,7 +23,7 @@ import java.util.ArrayList;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.function.Function;
-import java.util.function.Predicate;
+import java.util.function.BiPredicate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.commons.rng.simple.RandomSource;
@@ -43,7 +43,7 @@ public final class GeneticAlgorithmFactory<G, P> implements Callable<Population<
     /** Genotype to phenotype converter. */
     private final Function<G, P> decoder;
     /** Criterion for stopping the evolution. */
-    private final Predicate<Population<G, P>> stop;
+    private final BiPredicate<Population<G, P>, Integer> stop;
     /** Fitness calculator. */
     private final FitnessService<G, P> fitness;
     /** Chromosome selector. */
@@ -71,7 +71,7 @@ public final class GeneticAlgorithmFactory<G, P> implements Callable<Population<
      */
     private GeneticAlgorithmFactory(Collection<G> init,
                                     Function<G, P> decoder,
-                                    Predicate<Population<G, P>> stop,
+                                    BiPredicate<Population<G, P>, Integer> stop,
                                     FitnessService<G, P> fitness,
                                     Selection<G, P> selection,
                                     Map<GeneticOperator<G>, ApplicationRate> operators,
@@ -107,7 +107,7 @@ public final class GeneticAlgorithmFactory<G, P> implements Callable<Population<
      */
     public static <G, P> Callable<Population<G, P>> create(Collection<G> init,
                                                            Function<G, P> decoder,
-                                                           Predicate<Population<G, P>> stop,
+                                                           BiPredicate<Population<G, P>, Integer> stop,
                                                            FitnessService<G, P> fitness,
                                                            Selection<G, P> selection,
                                                            Map<GeneticOperator<G>, ApplicationRate> operators,
@@ -147,7 +147,7 @@ public final class GeneticAlgorithmFactory<G, P> implements Callable<Population<
                                                            ChromosomeFactory<G> initFactory,
                                                            int populationSize,
                                                            Function<G, P> decoder,
-                                                           Predicate<Population<G, P>> stop,
+                                                           BiPredicate<Population<G, P>, Integer> stop,
                                                            FitnessService<G, P> fitness,
                                                            Selection<G, P> selection,
                                                            Map<GeneticOperator<G>, ApplicationRate> operators,
@@ -181,8 +181,7 @@ public final class GeneticAlgorithmFactory<G, P> implements Callable<Population<
         currentGen.add(init);
         final UniformRandomProvider rng = random.create();
 
-        while (!stop.test(currentGen)) {
-            ++generation;
+        while (!stop.test(currentGen, ++generation)) {
             final Population<G, P> nextGen = new Population<>(popSize, decoder, fitness);
 
             applyElitism(currentGen, nextGen);
diff --git a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/stop/UnchangedFitness.java b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/stop/UnchangedFitness.java
index c261c434f..228abd9aa 100644
--- a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/stop/UnchangedFitness.java
+++ b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/stop/UnchangedFitness.java
@@ -18,25 +18,30 @@ package org.apache.commons.math4.ga2.stop;
 
 import java.util.Map;
 import java.util.function.ToDoubleFunction;
-import java.util.function.Predicate;
+import java.util.function.BiPredicate;
 import org.apache.commons.math4.ga2.Population;
 
 /**
  * Criterion for asserting convergence of a population.
- * Note: Class is <em>not</em> thread-safe.
+ * Notes:
+ * <ul>
+ *  <li>Class is <em>not</em> thread-safe.</li>
+ *  <li>A <em>new</em> instance must created for each GA run (otherwise
+ *   an {@link IllegalStateException} will be thrown).</li>
+ * </ul>
  *
  * @param <G> Genotype.
  * @param <P> Phenotype.
  */
-public class UnchangedFitness<G, P> implements Predicate<Population<G, P>> {
+public class UnchangedFitness<G, P> implements BiPredicate<Population<G, P>, Integer> {
     /** Function that computes the reference value. */
     private final ToDoubleFunction<Population<G, P>> calculator;
     /** Number of generations during which no change has happened. */
     private final int maxGenerations;
     /** Value for previous population. */
     private double previousFitness = Double.NaN;
-    /** Number of generations without changes. */
-    private int generations = 0;
+    /** Generation at which the last change has occurred. */
+    private int updatedGeneration = 0;
 
     /** What needs to be unchanged. */
     public enum Type {
@@ -71,14 +76,20 @@ public class UnchangedFitness<G, P> implements Predicate<Population<G, P>> {
 
     /** {@inheritDoc} */
     @Override
-    public boolean test(Population<G, P> population) {
+    public boolean test(Population<G, P> population,
+                        Integer generationCounter) {
+        final int genDiff = generationCounter - updatedGeneration;
+        if (genDiff < 0) {
+            throw new IllegalStateException("Incorrect usage");
+        }
+
         final double fitness = calculator.applyAsDouble(population);
         if (fitness == previousFitness) {
-            if (++generations > maxGenerations) {
+            if (genDiff > maxGenerations) {
                 return true;
             }
         } else {
-            generations = 0;
+            updatedGeneration = generationCounter;
             previousFitness = fitness;
         }
 


[commons-math] 03/05: MATH-1618: Replace class "OnePointCrossover" with "NPointCrossover".

Posted by er...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

erans pushed a commit to branch feature__MATH-1563__genetic_algorithm
in repository https://gitbox.apache.org/repos/asf/commons-math.git

commit f0fe9ab8eb73e981cde5c0c15ca9349f1f5a7185
Author: Gilles Sadowski <gi...@gmail.com>
AuthorDate: Fri Jun 10 10:06:25 2022 +0200

    MATH-1618: Replace class "OnePointCrossover" with "NPointCrossover".
    
    The former is a particular case of the latter.
---
 .../ga/mathfunctions/MathFunctionOptimizer2.java   | 12 ++--
 ...OnePointCrossover.java => NPointCrossover.java} | 77 ++++++++++++++++------
 .../commons/math4/ga2/gene/binary/Operators.java   |  7 +-
 3 files changed, 65 insertions(+), 31 deletions(-)

diff --git a/commons-math-examples/examples-ga/examples-ga-math-functions/src/main/java/org/apache/commons/math4/examples/ga/mathfunctions/MathFunctionOptimizer2.java b/commons-math-examples/examples-ga/examples-ga-math-functions/src/main/java/org/apache/commons/math4/examples/ga/mathfunctions/MathFunctionOptimizer2.java
index e8a16a375..96ea5087b 100644
--- a/commons-math-examples/examples-ga/examples-ga-math-functions/src/main/java/org/apache/commons/math4/examples/ga/mathfunctions/MathFunctionOptimizer2.java
+++ b/commons-math-examples/examples-ga/examples-ga-math-functions/src/main/java/org/apache/commons/math4/examples/ga/mathfunctions/MathFunctionOptimizer2.java
@@ -129,7 +129,7 @@ public final class MathFunctionOptimizer2 {
         // Offspring generators.
         final Map<GeneticOperator<Chromosome>, ApplicationRate> operators = new HashMap<>();
         operators.put(Operators.mutation(mutation), RateGenerators.constant(1));
-        operators.put(Operators.onePointCrossover(), RateGenerators.constant(crossover));
+        operators.put(Operators.nPointCrossover(1), RateGenerators.constant(crossover));
 
         final Callable<Population<Chromosome, Coordinates>> ga =
             GeneticAlgorithmFactory.<Chromosome, Coordinates>create(numGenes,
@@ -145,14 +145,12 @@ public final class MathFunctionOptimizer2 {
                                                                     new GenerationLogger());
 
         try {
-            // Run the GA and retrieve contents of the last generation.
-            final List<Map.Entry<Chromosome, Double>> lastGen = ga.call().contents(true);
-            final Map.Entry<Chromosome, Double> top = lastGen.get(0);
-            final Coordinates best = decoder.apply(top.getKey());
-            final double bestValue = fitnessFunction.applyAsDouble(best);
+            // Run the GA and retrieve the best individual from the last generation.
+            final Map.Entry<Chromosome, Double> best = ga.call().contents(true).get(0);
 
             // CHECKSTYLE: stop all
-            System.out.println("fitness=" + bestValue + " for " + best.toString());
+            System.out.println("fitness=" + best.getValue() +
+                               " for " + decoder.apply(best.getKey()).toString());
             // CHECKSTYLE: resume all
         } catch (Exception e) {
             // Rethrow.
diff --git a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/OnePointCrossover.java b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/NPointCrossover.java
similarity index 50%
rename from commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/OnePointCrossover.java
rename to commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/NPointCrossover.java
index 95342a7bb..d7ff29d5b 100644
--- a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/OnePointCrossover.java
+++ b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/NPointCrossover.java
@@ -27,7 +27,21 @@ import org.apache.commons.math4.ga2.AbstractCrossover;
  * Genetic operator.
  * Class is immutable.
  */
-/* package-private */ class OnePointCrossover extends AbstractCrossover<Chromosome> {
+/* package-private */ class NPointCrossover extends AbstractCrossover<Chromosome> {
+    /** Number of crossover points. */
+    private final int numberOfPoints;
+
+    /**
+     * @param n Number of crossover points.
+     */
+    NPointCrossover(int n) {
+        if (n <= 0) {
+            throw new IllegalArgumentException("Not strictly positive: " + n);
+        }
+
+        numberOfPoints = n;
+    }
+
     /** {@inheritDoc} */
     @Override
     protected List<Chromosome> apply(Chromosome parent1,
@@ -40,36 +54,57 @@ import org.apache.commons.math4.ga2.AbstractCrossover;
                                                parent2.size());
         }
 
-        // Index of crossover point.
-        final int xIndex = 1 + rng.nextInt(size - 1);
-        final BitSet p1 = parent1.asBitSet();
-        final BitSet p2 = parent2.asBitSet();
-        final BitSet c1;
-        final BitSet c2;
+        final BitSet c1 = parent1.asBitSet();
+        final BitSet c2 = parent2.asBitSet();
 
-        final int midIndex = size / 2;
-        if (xIndex > midIndex) {
-            c1 = parent1.asBitSet();
-            c2 = parent2.asBitSet();
+        // Number or remaining crossover points.
+        int remainingPoints = numberOfPoints;
+        // Index of crossover end point.
+        int xEnd = 0;
+        while (remainingPoints > 0) {
+            // Index of crossover start point.
+            final int xStart = xEnd + 1 + rng.nextInt(size - xEnd - remainingPoints);
 
-            for (int i = xIndex; i < size; i++) {
-                c1.set(i, p2.get(i));
-                c2.set(i, p1.get(i));
-            }
-        } else {
-            c1 = parent2.asBitSet();
-            c2 = parent1.asBitSet();
+            if (--remainingPoints > 0) {
+                xEnd = xStart + 1 + rng.nextInt(size - xStart - remainingPoints);
 
-            for (int i = 0; i < xIndex; i++) {
-                c1.set(i, p1.get(i));
-                c2.set(i, p2.get(i));
+                swap(c1, c2, xStart, xEnd);
+
+                --remainingPoints;
+            } else {
+                xEnd = xStart;
+                break;
             }
         }
 
+        if (numberOfPoints % 2 != 0) {
+            swap(c1, c2, xEnd, size);
+        }
+
         final List<Chromosome> offsprings = new ArrayList<>(2);
         offsprings.add(Chromosome.from(c1, size));
         offsprings.add(Chromosome.from(c2, size));
 
         return offsprings;
     }
+
+    /**
+     * Swaps contents (in-place) within the given range.
+     *
+     * @param a Chromosome.
+     * @param b Chromosome.
+     * @param start Index from which contents should be swapped.
+     * @param end Index at which swapping should stop.
+     */
+    private void swap(BitSet a,
+                      BitSet b,
+                      int start,
+                      int end) {
+        for (int i = start; i < end; i++) {
+            final boolean aV = a.get(i);
+            final boolean bV = b.get(i);
+            a.set(i, bV);
+            b.set(i, aV);
+        }
+    }
 }
diff --git a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/Operators.java b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/Operators.java
index 7030134f5..c3242956b 100644
--- a/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/Operators.java
+++ b/commons-math-ga2/src/main/java/org/apache/commons/math4/ga2/gene/binary/Operators.java
@@ -35,9 +35,10 @@ public final class Operators {
         return new Mutation(probability);
     }
     /**
-     * @return a one-point crossover operator.
+     * @param n Number of crossover points.
+     * @return an n-point crossover operator.
      */
-    public static GeneticOperator<Chromosome> onePointCrossover() {
-        return new OnePointCrossover();
+    public static GeneticOperator<Chromosome> nPointCrossover(int n) {
+        return new NPointCrossover(n);
     }
 }