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 2021/05/21 13:42:31 UTC

[commons-math] 01/02: MATH-1578: Modularization.

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

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

commit 5e59617d85a2fb8eaab0bd84711b3e48b698abe6
Author: Gilles Sadowski <gi...@gmail.com>
AuthorDate: Sat May 15 17:09:39 2021 +0200

    MATH-1578: Modularization.
    
    Code moved from "o.a.c.math4.legacy.ml.neuralnet" into a dedicated module.
    
    Refactored "TSP" example application (formerly provided as a unit test).
---
 .../examples-sofm/chinese-rings/pom.xml            |  42 +-
 .../examples/sofm/chineserings/ChineseRings.java   |  29 +-
 .../sofm/chineserings/ChineseRingsClassifier.java  |  35 +-
 .../examples/sofm/chineserings/StandAlone.java     |   5 +-
 commons-math-examples/examples-sofm/pom.xml        |   3 +-
 commons-math-examples/examples-sofm/tsp/LICENCE    | 457 +++++++++++++++++++++
 commons-math-examples/examples-sofm/tsp/NOTICE     |   9 +
 .../examples-sofm/{ => tsp}/pom.xml                |  43 +-
 .../commons/math4/examples/sofm/tsp/City.java      | 185 +++++++++
 .../math4/examples/sofm/tsp/StandAlone.java        | 162 ++++++++
 .../sofm/tsp/TravellingSalesmanSolver.java         | 331 +++++++++++++++
 commons-math-examples/pom.xml                      |  36 ++
 commons-math-legacy/pom.xml                        |   2 +-
 .../ml/neuralnet/sofm/KohonenTrainingTaskTest.java | 221 ----------
 .../neuralnet/sofm/TravellingSalesmanSolver.java   | 427 -------------------
 commons-math-neuralnet/LICENCE                     | 457 +++++++++++++++++++++
 commons-math-neuralnet/NOTICE                      |   9 +
 commons-math-neuralnet/pom.xml                     |  75 ++++
 .../commons/math4/neuralnet/DistanceMeasure.java   |  11 +-
 .../commons/math4/neuralnet/EuclideanDistance.java |  38 +-
 .../math4}/neuralnet/FeatureInitializer.java       |   2 +-
 .../neuralnet/FeatureInitializerFactory.java       |  39 +-
 .../commons/math4}/neuralnet/MapRanking.java       |  20 +-
 .../apache/commons/math4}/neuralnet/MapUtils.java  |  16 +-
 .../apache/commons/math4}/neuralnet/Network.java   |  18 +-
 .../apache/commons/math4}/neuralnet/Neuron.java    |  15 +-
 .../math4}/neuralnet/SquareNeighbourhood.java      |   2 +-
 .../commons/math4}/neuralnet/UpdateAction.java     |   2 +-
 .../neuralnet/internal/NeuralNetException.java     |  51 +++
 .../math4}/neuralnet/oned/NeuronString.java        |  21 +-
 .../math4}/neuralnet/oned/package-info.java        |   2 +-
 .../commons/math4}/neuralnet/package-info.java     |   2 +-
 .../math4}/neuralnet/sofm/KohonenTrainingTask.java |   4 +-
 .../math4}/neuralnet/sofm/KohonenUpdateAction.java |  60 ++-
 .../neuralnet/sofm/LearningFactorFunction.java     |   2 +-
 .../sofm/LearningFactorFunctionFactory.java        |  35 +-
 .../neuralnet/sofm/NeighbourhoodSizeFunction.java  |   2 +-
 .../sofm/NeighbourhoodSizeFunctionFactory.java     |  30 +-
 .../math4}/neuralnet/sofm/package-info.java        |   2 +-
 .../sofm/util/ExponentialDecayFunction.java        |  34 +-
 .../sofm/util/QuasiSigmoidDecayFunction.java       |  38 +-
 .../math4}/neuralnet/sofm/util/package-info.java   |   2 +-
 .../math4}/neuralnet/twod/NeuronSquareMesh2D.java  |  56 +--
 .../math4}/neuralnet/twod/package-info.java        |   2 +-
 .../math4}/neuralnet/twod/util/LocationFinder.java |  13 +-
 .../neuralnet/twod/util/MapDataVisualization.java  |   4 +-
 .../neuralnet/twod/util/MapVisualization.java      |   4 +-
 .../neuralnet/twod/util/SmoothedDataHistogram.java |  19 +-
 .../neuralnet/twod/util/UnifiedDistanceMatrix.java |  24 +-
 .../math4}/neuralnet/twod/util/package-info.java   |   2 +-
 .../commons/math4}/neuralnet/MapRankingTest.java   |  19 +-
 .../commons/math4}/neuralnet/NetworkTest.java      |  11 +-
 .../commons/math4}/neuralnet/NeuronTest.java       |   2 +-
 .../math4}/neuralnet/OffsetFeatureInitializer.java |   2 +-
 .../math4}/neuralnet/oned/NeuronStringTest.java    |  17 +-
 .../neuralnet/sofm/KohonenUpdateActionTest.java    |  38 +-
 .../sofm/LearningFactorFunctionFactoryTest.java    |  23 +-
 .../sofm/NeighbourhoodSizeFunctionFactoryTest.java |  18 +-
 .../sofm/util/ExponentialDecayFunctionTest.java    |  18 +-
 .../sofm/util/QuasiSigmoidDecayFunctionTest.java   |  16 +-
 .../neuralnet/twod/NeuronSquareMesh2DTest.java     |  33 +-
 .../neuralnet/twod/util/LocationFinderTest.java    |  18 +-
 pom.xml                                            |   1 +
 src/changes/changes.xml                            |   7 +
 64 files changed, 2219 insertions(+), 1104 deletions(-)

diff --git a/commons-math-examples/examples-sofm/chinese-rings/pom.xml b/commons-math-examples/examples-sofm/chinese-rings/pom.xml
index 86b170f..905e009 100644
--- a/commons-math-examples/examples-sofm/chinese-rings/pom.xml
+++ b/commons-math-examples/examples-sofm/chinese-rings/pom.xml
@@ -54,43 +54,11 @@
       <artifactId>commons-geometry-euclidean</artifactId>
     </dependency>
 
-  </dependencies>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-math4-legacy</artifactId>
+    </dependency>
 
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-shade-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>shade</goal>
-            </goals>
-            <configuration>
-              <finalName>${uberjar.name}</finalName>
-              <minimizeJar>true</minimizeJar>
-              <transformers>
-                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
-                  <mainClass>${project.mainClass}</mainClass>
-                </transformer>
-              </transformers>
-              <filters>
-                <filter>
-                  <!-- Shading signed JARs will fail without this. http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar -->
-                  <artifact>*:*</artifact>
-                  <excludes>
-                    <exclude>META-INF/*.SF</exclude>
-                    <exclude>META-INF/*.DSA</exclude>
-                    <exclude>META-INF/*.RSA</exclude>
-                  </excludes>
-                </filter>
-              </filters>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
+  </dependencies>
 
 </project>
diff --git a/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/ChineseRings.java b/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/ChineseRings.java
index 83e4e68..0090ebe 100644
--- a/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/ChineseRings.java
+++ b/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/ChineseRings.java
@@ -22,11 +22,10 @@ import java.util.Iterator;
 import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.simple.RandomSource;
 import org.apache.commons.rng.sampling.UnitSphereSampler;
+import org.apache.commons.rng.sampling.distribution.ContinuousUniformSampler;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 import org.apache.commons.geometry.euclidean.threed.rotation.Rotation3D;
 import org.apache.commons.geometry.euclidean.threed.rotation.QuaternionRotation;
-import org.apache.commons.statistics.distribution.ContinuousDistribution;
-import org.apache.commons.statistics.distribution.UniformContinuousDistribution;
 
 /**
  * Class that creates two intertwined rings in 3D-space.
@@ -63,11 +62,14 @@ class ChineseRings {
         // Create two rings lying in xy-plane.
         final UnitSphereSampler unit = new UnitSphereSampler(2, rng);
 
-        final ContinuousDistribution.Sampler radius1
-            = new UniformContinuousDistribution(radiusRing1 - halfWidthRing1,
-                                                radiusRing1 + halfWidthRing1).createSampler(rng);
-        final ContinuousDistribution.Sampler widthRing1
-            = new UniformContinuousDistribution(-halfWidthRing1, halfWidthRing1).createSampler(rng);
+        final ContinuousUniformSampler radius1
+            = new ContinuousUniformSampler(rng,
+                                           radiusRing1 - halfWidthRing1,
+                                           radiusRing1 + halfWidthRing1);
+        final ContinuousUniformSampler widthRing1
+            = new ContinuousUniformSampler(rng,
+                                           -halfWidthRing1,
+                                           halfWidthRing1);
 
         for (int i = 0; i < numPointsRing1; i++) {
             final double[] v = unit.nextVector();
@@ -78,11 +80,14 @@ class ChineseRings {
                                        widthRing1.sample());
         }
 
-        final ContinuousDistribution.Sampler radius2
-            = new UniformContinuousDistribution(radiusRing2 - halfWidthRing2,
-                                                radiusRing2 + halfWidthRing2).createSampler(rng);
-        final ContinuousDistribution.Sampler widthRing2
-            = new UniformContinuousDistribution(-halfWidthRing2, halfWidthRing2).createSampler(rng);
+        final ContinuousUniformSampler radius2
+            = new ContinuousUniformSampler(rng,
+                                           radiusRing2 - halfWidthRing2,
+                                           radiusRing2 + halfWidthRing2);
+        final ContinuousUniformSampler widthRing2
+            = new ContinuousUniformSampler(rng,
+                                           -halfWidthRing2,
+                                           halfWidthRing2);
 
         for (int i = 0; i < numPointsRing2; i++) {
             final double[] v = unit.nextVector();
diff --git a/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/ChineseRingsClassifier.java b/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/ChineseRingsClassifier.java
index f5343fb..4f1c39e 100644
--- a/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/ChineseRingsClassifier.java
+++ b/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/ChineseRingsClassifier.java
@@ -23,19 +23,19 @@ import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.rng.simple.RandomSource;
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.SquareNeighbourhood;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializerFactory;
-import org.apache.commons.math4.legacy.ml.neuralnet.MapUtils;
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.LearningFactorFunction;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.LearningFactorFunctionFactory;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.NeighbourhoodSizeFunction;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.NeighbourhoodSizeFunctionFactory;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.KohonenUpdateAction;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.KohonenTrainingTask;
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
-import org.apache.commons.math4.legacy.ml.distance.EuclideanDistance;
+import org.apache.commons.math4.neuralnet.SquareNeighbourhood;
+import org.apache.commons.math4.neuralnet.FeatureInitializer;
+import org.apache.commons.math4.neuralnet.FeatureInitializerFactory;
+import org.apache.commons.math4.neuralnet.MapUtils;
+import org.apache.commons.math4.neuralnet.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.EuclideanDistance;
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math4.neuralnet.sofm.LearningFactorFunction;
+import org.apache.commons.math4.neuralnet.sofm.LearningFactorFunctionFactory;
+import org.apache.commons.math4.neuralnet.sofm.NeighbourhoodSizeFunction;
+import org.apache.commons.math4.neuralnet.sofm.NeighbourhoodSizeFunctionFactory;
+import org.apache.commons.math4.neuralnet.sofm.KohonenUpdateAction;
+import org.apache.commons.math4.neuralnet.sofm.KohonenTrainingTask;
 import org.apache.commons.math4.legacy.stat.descriptive.SummaryStatistics;
 
 /**
@@ -92,7 +92,7 @@ class ChineseRingsClassifier {
                                                createRandomIterator(numSamplesPerTask),
                                                action);
         }
-        
+
         return tasks;
     }
 
@@ -147,10 +147,11 @@ class ChineseRingsClassifier {
             s * centre[2].getStandardDeviation()
         };
 
+        final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
         return new FeatureInitializer[] {
-            FeatureInitializerFactory.uniform(mean[0] - dev[0], mean[0] + dev[0]),
-            FeatureInitializerFactory.uniform(mean[1] - dev[1], mean[1] + dev[1]),
-            FeatureInitializerFactory.uniform(mean[2] - dev[2], mean[2] + dev[2])
+            FeatureInitializerFactory.uniform(rng, mean[0] - dev[0], mean[0] + dev[0]),
+            FeatureInitializerFactory.uniform(rng, mean[1] - dev[1], mean[1] + dev[1]),
+            FeatureInitializerFactory.uniform(rng, mean[2] - dev[2], mean[2] + dev[2])
         };
     }
 
diff --git a/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/StandAlone.java b/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/StandAlone.java
index 71f72a6..f23d20f 100644
--- a/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/StandAlone.java
+++ b/commons-math-examples/examples-sofm/chinese-rings/src/main/java/org/apache/commons/math4/examples/sofm/chineserings/StandAlone.java
@@ -26,7 +26,7 @@ import picocli.CommandLine.Option;
 import picocli.CommandLine.Command;
 
 import org.apache.commons.geometry.euclidean.threed.Vector3D;
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
 
 /**
  * Application class.
@@ -48,8 +48,7 @@ public class StandAlone implements Callable<Void> {
     private String _outputFile = null;
 
     /**
-     * Program entry point.  All solver parameters must be provided
-     * through a {@link java.util.Properties Properties} file.
+     * Program entry point.
      *
      * @param args Command line arguments and options.
      */
diff --git a/commons-math-examples/examples-sofm/pom.xml b/commons-math-examples/examples-sofm/pom.xml
index 8c3befd..928b698 100644
--- a/commons-math-examples/examples-sofm/pom.xml
+++ b/commons-math-examples/examples-sofm/pom.xml
@@ -40,7 +40,7 @@
 
     <dependency>
       <groupId>org.apache.commons</groupId>
-      <artifactId>commons-math4-legacy</artifactId>
+      <artifactId>commons-math4-neuralnet</artifactId>
     </dependency>
 
     <dependency>
@@ -52,6 +52,7 @@
 
   <modules>
     <module>chinese-rings</module>
+    <module>tsp</module>
   </modules>
 
 </project>
diff --git a/commons-math-examples/examples-sofm/tsp/LICENCE b/commons-math-examples/examples-sofm/tsp/LICENCE
new file mode 100644
index 0000000..d97b49a
--- /dev/null
+++ b/commons-math-examples/examples-sofm/tsp/LICENCE
@@ -0,0 +1,457 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
+
+
+Apache Commons Math includes the following code provided to the ASF under the
+Apache License 2.0:
+
+ - The inverse error function implementation in the Erf class is based on CUDA
+   code developed by Mike Giles, Oxford-Man Institute of Quantitative Finance,
+   and published in GPU Computing Gems, volume 2, 2010 (grant received on
+   March 23th 2013)
+ - The LinearConstraint, LinearObjectiveFunction, LinearOptimizer,
+   RelationShip, SimplexSolver and SimplexTableau classes in package
+   org.apache.commons.math3.optimization.linear include software developed by
+   Benjamin McCann (http://www.benmccann.com) and distributed with
+   the following copyright: Copyright 2009 Google Inc. (grant received on
+   March 16th 2009)
+ - The class "org.apache.commons.math3.exception.util.LocalizedFormatsTest" which
+   is an adapted version of "OrekitMessagesTest" test class for the Orekit library
+ - The "org.apache.commons.math3.analysis.interpolation.HermiteInterpolator"
+   has been imported from the Orekit space flight dynamics library.
+
+===============================================================================
+ 
+
+
+APACHE COMMONS MATH DERIVATIVE WORKS: 
+
+The Apache commons-math library includes a number of subcomponents
+whose implementation is derived from original sources written
+in C or Fortran.  License terms of the original sources
+are reproduced below.
+
+===============================================================================
+For the lmder, lmpar and qrsolv Fortran routine from minpack and translated in
+the LevenbergMarquardtOptimizer class in package
+org.apache.commons.math3.optimization.general 
+Original source copyright and license statement:
+
+Minpack Copyright Notice (1999) University of Chicago.  All rights reserved
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions of source code must retain the above
+copyright notice, this list of conditions and the following
+disclaimer.
+
+2. Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following
+disclaimer in the documentation and/or other materials
+provided with the distribution.
+
+3. The end-user documentation included with the
+redistribution, if any, must include the following
+acknowledgment:
+
+   "This product includes software developed by the
+   University of Chicago, as Operator of Argonne National
+   Laboratory.
+
+Alternately, this acknowledgment may appear in the software
+itself, if and wherever such third-party acknowledgments
+normally appear.
+
+4. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
+WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
+UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
+THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
+OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
+OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
+USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
+THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
+DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
+UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
+BE CORRECTED.
+
+5. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
+HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
+ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
+INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
+ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
+PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
+SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
+(INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
+EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
+POSSIBILITY OF SUCH LOSS OR DAMAGES.
+===============================================================================
+
+Copyright and license statement for the odex Fortran routine developed by
+E. Hairer and G. Wanner and translated in GraggBulirschStoerIntegrator class
+in package org.apache.commons.math3.ode.nonstiff:
+
+
+Copyright (c) 2004, Ernst Hairer
+
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are 
+met:
+
+- Redistributions of source code must retain the above copyright 
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright 
+notice, this list of conditions and the following disclaimer in the 
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+===============================================================================
+
+Copyright and license statement for the original Mersenne twister C
+routines translated in MersenneTwister class in package 
+org.apache.commons.math3.random:
+
+   Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+   All rights reserved.                          
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+     1. Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+
+     2. Redistributions in binary form must reproduce the above copyright
+        notice, this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+
+     3. The names of its contributors may not be used to endorse or promote 
+        products derived from this software without specific prior written 
+        permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+===============================================================================
+
+The initial code for shuffling an array (originally in class
+"org.apache.commons.math3.random.RandomDataGenerator", now replaced by
+a method in class "org.apache.commons.math3.util.MathArrays") was
+inspired from the algorithm description provided in
+"Algorithms", by Ian Craw and John Pulham (University of Aberdeen 1999).
+The textbook (containing a proof that the shuffle is uniformly random) is
+available here:
+  http://citeseerx.ist.psu.edu/viewdoc/download;?doi=10.1.1.173.1898&rep=rep1&type=pdf
+
+===============================================================================
+License statement for the direction numbers in the resource files for Sobol sequences.
+
+-----------------------------------------------------------------------------
+Licence pertaining to sobol.cc and the accompanying sets of direction numbers
+
+-----------------------------------------------------------------------------
+Copyright (c) 2008, Frances Y. Kuo and Stephen Joe
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the names of the copyright holders nor the names of the
+      University of New South Wales and the University of Waikato
+      and its contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+===============================================================================
+
+The initial commit of package "org.apache.commons.math3.ml.neuralnet" is
+an adapted version of code developed in the context of the Data Processing
+and Analysis Consortium (DPAC) of the "Gaia" project of the European Space
+Agency (ESA).
+===============================================================================
+
+The initial commit of the class "org.apache.commons.math3.special.BesselJ" is
+an adapted version of code translated from the netlib Fortran program, rjbesl
+http://www.netlib.org/specfun/rjbesl by R.J. Cody at Argonne National
+Laboratory (USA).  There is no license or copyright statement included with the
+original Fortran sources.
+===============================================================================
+
+
+The BracketFinder (package org.apache.commons.math3.optimization.univariate)
+and PowellOptimizer (package org.apache.commons.math3.optimization.general)
+classes are based on the Python code in module "optimize.py" (version 0.5)
+developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/)
+Copyright © 2003-2009 SciPy Developers.
+
+SciPy license
+Copyright © 2001, 2002 Enthought, Inc.
+All rights reserved.
+
+Copyright © 2003-2013 SciPy Developers.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the name of Enthought nor the names of the SciPy Developers may
+      be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+===============================================================================
+
diff --git a/commons-math-examples/examples-sofm/tsp/NOTICE b/commons-math-examples/examples-sofm/tsp/NOTICE
new file mode 100644
index 0000000..587cd7f
--- /dev/null
+++ b/commons-math-examples/examples-sofm/tsp/NOTICE
@@ -0,0 +1,9 @@
+Apache Commons Math
+Copyright 2001-2020 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes software developed for Orekit by
+CS Systèmes d'Information (http://www.c-s.fr/)
+Copyright 2010-2012 CS Systèmes d'Information
diff --git a/commons-math-examples/examples-sofm/pom.xml b/commons-math-examples/examples-sofm/tsp/pom.xml
similarity index 55%
copy from commons-math-examples/examples-sofm/pom.xml
copy to commons-math-examples/examples-sofm/tsp/pom.xml
index 8c3befd..0360b7c 100644
--- a/commons-math-examples/examples-sofm/pom.xml
+++ b/commons-math-examples/examples-sofm/tsp/pom.xml
@@ -20,38 +20,31 @@
 
   <parent>
     <groupId>org.apache.commons</groupId>
-    <artifactId>commons-math-examples</artifactId>
+    <artifactId>examples-sofm</artifactId>
     <version>4.0-SNAPSHOT</version>
   </parent>
 
-  <artifactId>examples-sofm</artifactId>
+  <artifactId>examples-sofm-tsp</artifactId>
   <version>4.0-SNAPSHOT</version>
-  <packaging>pom</packaging>
-  <name>SOFM</name>
+  <!-- This name is used in the shaded jar to provide the application title for the version information. -->
+  <name>Traveling Salesman Problem</name>
 
-  <description>Self-organized feature map (ANN) sample codes.</description>
+  <description>SOFM used for optimization.</description>
 
   <properties>
-      <!-- Workaround to avoid duplicating config files. -->
-    <math.parent.dir>${basedir}/../..</math.parent.dir>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
+
+    <!-- OSGi -->
+    <commons.osgi.symbolicName>org.apache.commons.math4.examples.sofm.tsp</commons.osgi.symbolicName>
+    <commons.osgi.export>org.apache.commons.math4.examples.sofm.tsp</commons.osgi.export>
+    <!-- Java 9+ -->
+    <commons.automatic.module.name>org.apache.commons.math4.examples.sofm.tsp</commons.automatic.module.name>
+    <!-- Workaround to avoid duplicating config files. -->
+    <math.parent.dir>${basedir}/../../..</math.parent.dir>
+
+    <uberjar.name>examples-sofm-tsp</uberjar.name>
+    <project.mainClass>org.apache.commons.math4.examples.sofm.tsp.StandAlone</project.mainClass>
   </properties>
 
-  <dependencies>
-
-    <dependency>
-      <groupId>org.apache.commons</groupId>
-      <artifactId>commons-math4-legacy</artifactId>
-    </dependency>
-
-    <dependency>
-      <groupId>info.picocli</groupId>
-      <artifactId>picocli</artifactId>
-    </dependency>
-
-  </dependencies>
-
-  <modules>
-    <module>chinese-rings</module>
-  </modules>
-
 </project>
diff --git a/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/City.java b/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/City.java
new file mode 100644
index 0000000..da10eea
--- /dev/null
+++ b/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/City.java
@@ -0,0 +1,185 @@
+/*
+ * 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.commons.math4.examples.sofm.tsp;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * A city, represented by a name and two-dimensional coordinates.
+ */
+public class City {
+    /** Identifier. */
+    final String name;
+    /** x-coordinate. */
+    final double x;
+    /** y-coordinate. */
+    final double y;
+
+    /**
+     * @param name Name.
+     * @param x Cartesian x-coordinate.
+     * @param y Cartesian y-coordinate.
+     */
+    public City(String name,
+                double x,
+                double y) {
+        this.name = name;
+        this.x = x;
+        this.y = y;
+    }
+
+    /**
+     * @return the name.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return the (x, y) coordinates.
+     */
+    public double[] getCoordinates() {
+        return new double[] { x, y };
+    }
+
+    /**
+     * Computes the distance between this city and
+     * the given point.
+     *
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @return the distance between {@code (x, y)} and this
+     * city.
+     */
+    public double distance(double x,
+                           double y) {
+        final double xDiff = this.x - x;
+        final double yDiff = this.y - y;
+
+        return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+    }
+
+    /**
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @param cities City list.
+     * @return the city whose coordinates are closest to {@code (x, y)}.
+     */
+    public static City closest(double x,
+                               double y,
+                               Set<City> cities) {
+        City closest = null;
+        double min = Double.POSITIVE_INFINITY;
+        for (City c : cities) {
+            final double d = c.distance(x, y);
+            if (d < min) {
+                min = d;
+                closest = c;
+            }
+        }
+        return closest;
+    }
+
+    /**
+     * Computes the barycentre of all city locations.
+     *
+     * @param cities City list.
+     * @return the barycentre.
+     */
+    public static double[] barycentre(Set<City> cities) {
+        double xB = 0;
+        double yB = 0;
+
+        int count = 0;
+        for (City c : cities) {
+            final double[] coord = c.getCoordinates();
+            xB += coord[0];
+            yB += coord[1];
+
+            ++count;
+        }
+
+        return new double[] { xB / count, yB / count };
+    }
+
+    /**
+     * Computes the largest distance between the point at coordinates
+     * {@code (x, y)} and any of the cities.
+     *
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @param cities City list.
+     * @return the largest distance.
+     */
+    public static double largestDistance(double x,
+                                         double y,
+                                         Set<City> cities) {
+        double maxDist = 0;
+        for (City c : cities) {
+            final double dist = c.distance(x, y);
+            if (dist > maxDist) {
+                maxDist = dist;
+            }
+        }
+
+        return maxDist;
+    }
+
+    /**
+     * @param cities List of cities.
+     * @return a list with no duplicate city.
+     */
+    public static Set<City> unique(City[] cities) {
+        final Set<City> uniqueCities = new HashSet<>();
+        uniqueCities.addAll(Arrays.asList(cities));
+        return uniqueCities;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof City) {
+            final City other = (City) o;
+            return x == other.x &&
+                y == other.y;
+        }
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        final long c1 = Double.doubleToLongBits(x);
+        result = 31 * result + (int) (c1 ^ (c1 >>> 32));
+
+        final long c2 = Double.doubleToLongBits(y);
+        result = 31 * result + (int) (c2 ^ (c2 >>> 32));
+
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return getName();
+    }
+}
diff --git a/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/StandAlone.java b/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/StandAlone.java
new file mode 100644
index 0000000..2d77877
--- /dev/null
+++ b/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/StandAlone.java
@@ -0,0 +1,162 @@
+/*
+ * 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.commons.math4.examples.sofm.tsp;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.Callable;
+
+import picocli.CommandLine;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Command;
+
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
+
+/**
+ * Application class.
+ */
+@Command(description = "Run the application",
+         mixinStandardHelpOptions = true)
+public class StandAlone implements Callable<Void> {
+    @Option(names = { "-n" }, paramLabel = "neuronsPerCity",
+            description = "Average number of neurons per city (default: ${DEFAULT-VALUE}).")
+    private double _neuronsPerCity = 2.2;
+    @Option(names = { "-s" }, paramLabel = "numSamples",
+            description = "Number of samples for the training (default: ${DEFAULT-VALUE}).")
+    private long _numSamples = 2000L;
+    @Option(names = { "-j" }, paramLabel = "numJobs",
+            description = "Number of concurrent tasks (default: ${DEFAULT-VALUE}).")
+    private int _numJobs = Runtime.getRuntime().availableProcessors();
+    @Option(names = { "-m" }, paramLabel = "maxTrials",
+            description = "Maximal number of trials (default: ${DEFAULT-VALUE}).")
+    private int _maxTrials = 10;
+    @Option(names = { "-o" }, paramLabel = "outputFile", required = true,
+            description = "Output file name.")
+    private String _outputFile = null;
+
+    /**
+     * Program entry point.
+     *
+     * @param args Command line arguments and options.
+     */
+    public static void main(String[] args) {
+        CommandLine.call(new StandAlone(), args);
+    }
+
+    @Override
+    public Void call() throws Exception {
+        // Cities (in optimal travel order).
+        final City[] cities = new City[] {
+            new City("o0", 0, 0),
+            new City("o1", 1, 0),
+            new City("o2", 2, 0),
+            new City("o3", 3, 0),
+            new City("o4", 3, 1),
+            new City("o5", 3, 2),
+            new City("o6", 3, 3),
+            new City("o7", 2, 3),
+            new City("o8", 1, 3),
+            new City("o9", 0, 3),
+            new City("i3", 1, 2),
+            new City("i2", 2, 2),
+            new City("i1", 2, 1),
+            new City("i0", 1, 1),
+        };
+
+        final UniformRandomProvider rng = RandomSource.create(RandomSource.KISS);
+        City[] best = null;
+        int maxCities = 0;
+        double minDist = Double.POSITIVE_INFINITY;
+
+        int count = 0;
+        while (count++ < _maxTrials) {
+            final City[] travel = TravellingSalesmanSolver.solve(cities,
+                                                                 _neuronsPerCity,
+                                                                 _numSamples,
+                                                                 _numJobs,
+                                                                 rng);
+            final int numCities = City.unique(travel).size();
+            if (numCities > maxCities) {
+                best = travel;
+                maxCities = numCities;
+            }
+
+            if (numCities == cities.length) {
+                final double dist = computeDistance(travel);
+                if (dist < minDist) {
+                    minDist = dist;
+                    best = travel;
+                }
+            }
+        }
+
+        printSummary(_outputFile, best, computeDistance(cities));
+
+        return null;
+    }
+    /**
+     * Compute the distance covered by the salesman, including
+     * the trip back (from the last to first city).
+     *
+     * @param cityList List of cities visited during the travel.
+     * @return the total distance.
+     */
+    private static double computeDistance(City[] cityList) {
+        double dist = 0;
+        for (int i = 0; i < cityList.length; i++) {
+            final double[] currentCoord = cityList[i].getCoordinates();
+            final double[] nextCoord = cityList[(i + 1) % cityList.length].getCoordinates();
+
+            final double xDiff = currentCoord[0] - nextCoord[0];
+            final double yDiff = currentCoord[1] - nextCoord[1];
+
+            dist += Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+        }
+
+        return dist;
+    }
+
+    /**
+     * Prints a summary of the current state of the solver to the
+     * given file name.
+     *
+     * @param fileName File.
+     * @param travel Solution.
+     * @param optimalDistance Length of shortest path.
+     */
+    private static void printSummary(String fileName,
+                                     City[] travel,
+                                     double optimalDistance) {
+        try (final PrintWriter out = new PrintWriter(fileName)) {
+            out.println("# Number of unique cities: " + City.unique(travel).size());
+            out.println("# Travel distance: " + computeDistance(travel));
+            out.println("# Optimal travel distance: " + optimalDistance);
+
+            for (int i = 0; i < travel.length; i++) {
+                final City c = travel[i];
+                final double[] coord = c.getCoordinates();
+                out.println(coord[0] + " " + coord[1] + " # " + c.getName());
+            }
+        } catch (Exception e) {
+            // Do nothing.
+        }
+    }
+}
diff --git a/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/TravellingSalesmanSolver.java b/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/TravellingSalesmanSolver.java
new file mode 100644
index 0000000..956dc24
--- /dev/null
+++ b/commons-math-examples/examples-sofm/tsp/src/main/java/org/apache/commons/math4/examples/sofm/tsp/TravellingSalesmanSolver.java
@@ -0,0 +1,331 @@
+/*
+ * 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.commons.math4.examples.sofm.tsp;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.function.DoubleUnaryOperator;
+import java.util.concurrent.Future;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ExecutionException;
+
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.sampling.CollectionSampler;
+import org.apache.commons.rng.sampling.distribution.ContinuousUniformSampler;
+
+import org.apache.commons.math4.neuralnet.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.EuclideanDistance;
+import org.apache.commons.math4.neuralnet.FeatureInitializer;
+import org.apache.commons.math4.neuralnet.FeatureInitializerFactory;
+import org.apache.commons.math4.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.Neuron;
+import org.apache.commons.math4.neuralnet.oned.NeuronString;
+import org.apache.commons.math4.neuralnet.sofm.KohonenUpdateAction;
+import org.apache.commons.math4.neuralnet.sofm.KohonenTrainingTask;
+import org.apache.commons.math4.neuralnet.sofm.NeighbourhoodSizeFunctionFactory;
+import org.apache.commons.math4.neuralnet.sofm.NeighbourhoodSizeFunction;
+import org.apache.commons.math4.neuralnet.sofm.LearningFactorFunctionFactory;
+import org.apache.commons.math4.neuralnet.sofm.LearningFactorFunction;
+
+/**
+ * Handles the <a href="https://en.wikipedia.org/wiki/Travelling_salesman_problem">
+ * "Travelling Salesman's Problem"</a> (i.e. trying to find the sequence of
+ * cities that minimizes the travel distance) using a 1D SOFM.
+ */
+public class TravellingSalesmanSolver {
+    private static final long FIRST_NEURON_ID = 0;
+    /** SOFM. */
+    private final Network net;
+    /** Distance function. */
+    private final DistanceMeasure distance = new EuclideanDistance();
+    /** Total number of neurons. */
+    private final int numberOfNeurons;
+
+    /**
+     * @param numNeurons Number of neurons.
+     * @param init Neuron intializers.
+     */
+    private TravellingSalesmanSolver(int numNeurons,
+                                     FeatureInitializer[] init) {
+        // Total number of neurons.
+        numberOfNeurons = numNeurons;
+
+        // Create a network with circle topology.
+        net = new NeuronString(numberOfNeurons, true, init).getNetwork();
+    }
+
+    /**
+     * @param cities List of cities to be visited.
+     * @param neuronsPerCity Average number of neurons per city.
+     * @param numUpdates Number of updates for training the network.
+     * @param numTasks Number of concurrent tasks.
+     * @param random RNG for presenting samples to the trainer.
+     * @return the solution (list of cities in travel order).
+     */
+    public static City[] solve(City[] cities,
+                               double neuronsPerCity,
+                               long numUpdates,
+                               int numTasks,
+                               UniformRandomProvider random) {
+        if (cities.length <= 2) {
+            return cities;
+        }
+
+        // Make sure that each city will appear only once in the list.
+        final Set<City> uniqueCities = City.unique(cities);
+
+        final int numNeurons = (int) (neuronsPerCity * uniqueCities.size());
+        if (numNeurons < uniqueCities.size()) {
+            throw new IllegalArgumentException("Too few neurons");
+        }
+
+        // Set up network.
+        final FeatureInitializer[] init = makeInitializers(numNeurons,
+                                                           uniqueCities,
+                                                           random);
+        final TravellingSalesmanSolver solver = new TravellingSalesmanSolver(numNeurons,
+                                                                             init);
+
+        // Parallel execution.
+        final ExecutorService service = Executors.newCachedThreadPool();
+        final Runnable[] tasks = solver.createTasks(uniqueCities,
+                                                    random,
+                                                    numTasks,
+                                                    numUpdates / numTasks);
+        final List<Future<?>> execOutput = new ArrayList<>();
+        // Run tasks.
+        for (Runnable r : tasks) {
+            execOutput.add(service.submit(r));
+        }
+        // Wait for completion (ignoring return value).
+        try {
+            for (Future<?> f : execOutput) {
+                f.get();
+            }
+        } catch (InterruptedException|ExecutionException e) {
+            throw new RuntimeException(e);
+        }
+        // Terminate all threads.
+        service.shutdown();
+
+        return solver.getCityList(uniqueCities).toArray(new City[0]);
+    }
+
+    /**
+     * Creates training tasks.
+     *
+     * @param cities List of cities to be visited.
+     * @param random RNG for presenting samples to the trainer.
+     * @param numTasks Number of tasks to create.
+     * @param numSamplesPerTask Number of training samples per task.
+     * @return the created tasks.
+     */
+    private Runnable[] createTasks(Set<City> cities,
+                                   UniformRandomProvider random,
+                                   int numTasks,
+                                   long numSamplesPerTask) {
+        final Runnable[] tasks = new Runnable[numTasks];
+        final LearningFactorFunction learning
+            = LearningFactorFunctionFactory.exponentialDecay(0.9,
+                                                             0.05,
+                                                             numSamplesPerTask / 2);
+        final NeighbourhoodSizeFunction neighbourhood
+            = NeighbourhoodSizeFunctionFactory.exponentialDecay(numberOfNeurons,
+                                                                1,
+                                                                numSamplesPerTask / 2);
+
+        for (int i = 0; i < numTasks; i++) {
+            final KohonenUpdateAction action = new KohonenUpdateAction(distance,
+                                                                       learning,
+                                                                       neighbourhood);
+            tasks[i] = new KohonenTrainingTask(net,
+                                               createIterator(numSamplesPerTask,
+                                                              cities,
+                                                              random),
+                                               action);
+        }
+
+        return tasks;
+    }
+
+    /**
+     * Creates an iterator that will present a series of city's coordinates
+     * in random order.
+     *
+     * @param numSamples Number of samples.
+     * @param cities Cities.
+     * @param random RNG.
+     * @return the iterator.
+     */
+    private static Iterator<double[]> createIterator(final long numSamples,
+                                                     final Set<City> uniqueCities,
+                                                     final UniformRandomProvider random) {
+        final CollectionSampler<City> sampler = new CollectionSampler(random, uniqueCities);
+
+        return new Iterator<double[]>() {
+            /** Number of samples. */
+            private long n = 0;
+            /** {@inheritDoc} */
+            @Override
+            public boolean hasNext() {
+                return n < numSamples;
+            }
+            /** {@inheritDoc} */
+            @Override
+            public double[] next() {
+                ++n;
+                return sampler.sample().getCoordinates();
+            }
+            /** {@inheritDoc} */
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * @return the list of linked neurons (i.e. the one-dimensional SOFM).
+     */
+    private List<Neuron> getNeuronList() {
+        // Sequence of coordinates.
+        final List<Neuron> list = new ArrayList<>();
+
+        // First neuron.
+        Neuron current = net.getNeuron(FIRST_NEURON_ID);
+        while (true) {
+            list.add(current);
+            final Collection<Neuron> neighbours
+                = net.getNeighbours(current, list);
+
+            final Iterator<Neuron> iter = neighbours.iterator();
+            if (!iter.hasNext()) {
+                // All neurons have been visited.
+                break;
+            }
+
+            current = iter.next();
+        }
+
+        return list;
+    }
+
+    /**
+     * @return the list of features (coordinates) of linked neurons.
+     */
+    public List<double[]> getCoordinatesList() {
+        // Sequence of coordinates.
+        final List<double[]> coordinatesList = new ArrayList<>();
+
+        for (Neuron n : getNeuronList()) {
+            coordinatesList.add(n.getFeatures());
+        }
+
+        return coordinatesList;
+    }
+
+    /**
+     * Returns the travel proposed by the solver.
+     *
+     * @param cities Cities
+     * @return the list of cities in travel order.
+     */
+    private List<City> getCityList(Set<City> cities) {
+        final List<double[]> coord = getCoordinatesList();
+        final List<City> cityList = new ArrayList<>();
+        City previous = null;
+        for (int i = 0, max = coord.size(); i < max; i++) {
+            final double[] c = coord.get(i);
+            final City next = City.closest(c[0], c[1], cities);
+            if (!next.equals(previous)) {
+                cityList.add(next);
+                previous = next;
+            }
+        }
+        return cityList;
+    }
+
+    /**
+     * Creates the features' initializers: an approximate circle around the
+     * barycentre of the cities.
+     *
+     * @param numNeurons Number of neurons.
+     * @param uniqueCities Cities.
+     * @param random RNG.
+     * @return an array containing the two initializers.
+     */
+    private static FeatureInitializer[] makeInitializers(final int numNeurons,
+                                                         final Set<City> uniqueCities,
+                                                         final UniformRandomProvider random) {
+        // Barycentre.
+        final double[] centre = City.barycentre(uniqueCities);
+        // Largest distance from centre.
+        final double radius = 0.5 * City.largestDistance(centre[0], centre[1], uniqueCities);
+
+        final double omega = 2 * Math.PI / numNeurons;
+        final DoubleUnaryOperator h1 = new HarmonicOscillator(radius, omega, 0, centre[0]);
+        final DoubleUnaryOperator h2 = new HarmonicOscillator(radius, omega, 0.5 * Math.PI, centre[1]);
+
+        final double r = 0.05 * radius;
+        final ContinuousUniformSampler u = new ContinuousUniformSampler(random, -r, r);
+
+        return new FeatureInitializer[] {
+            FeatureInitializerFactory.randomize(u, FeatureInitializerFactory.function(h1, 0, 1)),
+            FeatureInitializerFactory.randomize(u, FeatureInitializerFactory.function(h2, 0, 1))
+        };
+    }
+}
+
+/**
+ * Function.
+ */
+class HarmonicOscillator implements DoubleUnaryOperator {
+    /** Amplitude. */
+    final double amplitude;
+    /** Angular speed. */
+    final double omega;
+    /** Phase. */
+    final double phase;
+    /** Offset. */
+    final double offset;
+
+    /**
+     * @param amplitude Amplitude.
+     * @param omega Angular speed.
+     * @param phase Phase.
+     * @param offset Offset (ordinate).
+     */
+    public HarmonicOscillator(double amplitude,
+                              double omega,
+                              double phase,
+                              double offset) {
+        this.amplitude = amplitude;
+        this.omega = omega;
+        this.phase = phase;
+        this.offset = offset;
+    }
+
+    @Override
+    public double applyAsDouble(double x) {
+        return offset + amplitude * Math.cos(omega * x + phase);
+    }
+}
diff --git a/commons-math-examples/pom.xml b/commons-math-examples/pom.xml
index 3b663e9..2b37839 100644
--- a/commons-math-examples/pom.xml
+++ b/commons-math-examples/pom.xml
@@ -110,6 +110,42 @@
       </plugins>
 
     </pluginManagement>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <finalName>${uberjar.name}</finalName>
+              <minimizeJar>true</minimizeJar>
+              <transformers>
+                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+                  <mainClass>${project.mainClass}</mainClass>
+                </transformer>
+              </transformers>
+              <filters>
+                <filter>
+                  <!-- Shading signed JARs will fail without this. http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar -->
+                  <artifact>*:*</artifact>
+                  <excludes>
+                    <exclude>META-INF/*.SF</exclude>
+                    <exclude>META-INF/*.DSA</exclude>
+                    <exclude>META-INF/*.RSA</exclude>
+                  </excludes>
+                </filter>
+              </filters>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+
   </build>
 
   <modules>
diff --git a/commons-math-legacy/pom.xml b/commons-math-legacy/pom.xml
index 30fb6ef..ae33056 100644
--- a/commons-math-legacy/pom.xml
+++ b/commons-math-legacy/pom.xml
@@ -29,7 +29,7 @@
   <artifactId>commons-math4-legacy</artifactId>
   <name>Apache Commons Math (Legacy)</name>
 
-  <description>Legacy codes that are either currently unsupported or not yet modularized.</description>
+  <description>Codes that are either currently unsupported or not yet modularized.</description>
 
   <properties>
     <!-- The Java Module System Name -->
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenTrainingTaskTest.java b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenTrainingTaskTest.java
deleted file mode 100644
index e5802aa..0000000
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenTrainingTaskTest.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * 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.commons.math4.legacy.ml.neuralnet.sofm;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-
-import org.apache.commons.math4.legacy.Retry;
-import org.apache.commons.math4.legacy.RetryRunner;
-import org.apache.commons.math4.legacy.util.FastMath;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link KohonenTrainingTask}
- */
-@RunWith(RetryRunner.class)
-public class KohonenTrainingTaskTest {
-    @Test
-    public void testTravellerSalesmanSquareTourSequentialSolver() {
-        // Cities (in optimal travel order).
-        final City[] squareOfCities = new City[] {
-            new City("o0", 0, 0),
-            new City("o1", 1, 0),
-            new City("o2", 2, 0),
-            new City("o3", 3, 0),
-            new City("o4", 3, 1),
-            new City("o5", 3, 2),
-            new City("o6", 3, 3),
-            new City("o7", 2, 3),
-            new City("o8", 1, 3),
-            new City("o9", 0, 3),
-            new City("i3", 1, 2),
-            new City("i2", 2, 2),
-            new City("i1", 2, 1),
-            new City("i0", 1, 1),
-        };
-
-        // Seed that allows the unit test to always succeed.
-        final long seed = 1245632379L;
-
-        final TravellingSalesmanSolver solver = new TravellingSalesmanSolver(squareOfCities, 2, seed);
-        // printSummary("before.travel.seq.dat", solver);
-        final Runnable task = solver.createSequentialTask(15000);
-        task.run();
-
-        // All update attempts must be successful in the absence of concurrency.
-        Assert.assertEquals(solver.getUpdateRatio(), 1, 0d);
-
-        // printSummary("after.travel.seq.dat", solver);
-        final City[] result = solver.getCityList();
-        Assert.assertEquals(squareOfCities.length,
-                            uniqueCities(result).size());
-        final double ratio = computeTravelDistance(squareOfCities) / computeTravelDistance(result);
-        Assert.assertEquals(1, ratio, 1e-1); // We do not require the optimal travel.
-    }
-
-    // Test can sometimes fail: Run several times.
-    @Test
-    @Retry
-    public void testTravellerSalesmanSquareTourParallelSolver() throws ExecutionException {
-        // Cities (in optimal travel order).
-        final City[] squareOfCities = new City[] {
-            new City("o0", 0, 0),
-            new City("o1", 1, 0),
-            new City("o2", 2, 0),
-            new City("o3", 3, 0),
-            new City("o4", 3, 1),
-            new City("o5", 3, 2),
-            new City("o6", 3, 3),
-            new City("o7", 2, 3),
-            new City("o8", 1, 3),
-            new City("o9", 0, 3),
-            new City("i3", 1, 2),
-            new City("i2", 2, 2),
-            new City("i1", 2, 1),
-            new City("i0", 1, 1),
-        };
-
-        // Seed that allows the unit test to always succeed.
-        final long seed = 534712311L;
-
-        final TravellingSalesmanSolver solver = new TravellingSalesmanSolver(squareOfCities, 2, seed);
-        // printSummary("before.travel.par.dat", solver);
-
-        // Parallel execution.
-        final ExecutorService service = Executors.newCachedThreadPool();
-        final int numProcs = Runtime.getRuntime().availableProcessors();
-        final Runnable[] tasks = solver.createParallelTasks(numProcs, 5000);
-        final List<Future<?>> execOutput = new ArrayList<>();
-        // Run tasks.
-        for (Runnable r : tasks) {
-            execOutput.add(service.submit(r));
-        }
-        // Wait for completion (ignoring return value).
-        try {
-            for (Future<?> f : execOutput) {
-                f.get();
-            }
-        } catch (InterruptedException ignored) {}
-        // Terminate all threads.
-        service.shutdown();
-
-        if (numProcs > 1) {
-            // We expect that some update attempts will be concurrent.
-            Assert.assertTrue(solver.getUpdateRatio() < 1);
-        }
-
-        // printSummary("after.travel.par.dat", solver);
-        final City[] result = solver.getCityList();
-        Assert.assertEquals(squareOfCities.length,
-                            uniqueCities(result).size());
-        final double ratio = computeTravelDistance(squareOfCities) / computeTravelDistance(result);
-        Assert.assertEquals(1, ratio, 1e-1); // We do not require the optimal travel.
-    }
-
-    /**
-     * Creates a map of the travel suggested by the solver.
-     *
-     * @param solver Solver.
-     * @return a 4-columns table: {@code <x (neuron)> <y (neuron)> <x (city)> <y (city)>}.
-     */
-    private String travelCoordinatesTable(TravellingSalesmanSolver solver) {
-        final StringBuilder s = new StringBuilder();
-        for (double[] c : solver.getCoordinatesList()) {
-            s.append(c[0]).append(" ").append(c[1]).append(" ");
-            final City city = solver.getClosestCity(c[0], c[1]);
-            final double[] cityCoord = city.getCoordinates();
-            s.append(cityCoord[0]).append(" ").append(cityCoord[1]).append(" ");
-            s.append("   # ").append(city.getName()).append("\n");
-        }
-        return s.toString();
-    }
-
-    /**
-     * Compute the distance covered by the salesman, including
-     * the trip back (from the last to first city).
-     *
-     * @param cityList List of cities visited during the travel.
-     * @return the total distance.
-     */
-    private Collection<City> uniqueCities(City[] cityList) {
-        final Set<City> unique = new HashSet<>();
-        unique.addAll(Arrays.asList(cityList));
-        return unique;
-    }
-
-    /**
-     * Compute the distance covered by the salesman, including
-     * the trip back (from the last to first city).
-     *
-     * @param cityList List of cities visited during the travel.
-     * @return the total distance.
-     */
-    private double computeTravelDistance(City[] cityList) {
-        double dist = 0;
-        for (int i = 0; i < cityList.length; i++) {
-            final double[] currentCoord = cityList[i].getCoordinates();
-            final double[] nextCoord = cityList[(i + 1) % cityList.length].getCoordinates();
-
-            final double xDiff = currentCoord[0] - nextCoord[0];
-            final double yDiff = currentCoord[1] - nextCoord[1];
-
-            dist += FastMath.sqrt(xDiff * xDiff + yDiff * yDiff);
-        }
-
-        return dist;
-    }
-
-    /**
-     * Prints a summary of the current state of the solver to the
-     * given file name.
-     *
-     * @param fileName File.
-     * @param solver Solver.
-     */
-    @SuppressWarnings("unused")
-    private void printSummary(String fileName,
-                              TravellingSalesmanSolver solver) {
-        PrintWriter out = null;
-        try {
-            out = new PrintWriter(fileName);
-            out.println(travelCoordinatesTable(solver));
-
-            final City[] result = solver.getCityList();
-            out.println("# Number of unique cities: " + uniqueCities(result).size());
-            out.println("# Travel distance: " + computeTravelDistance(result));
-        } catch (Exception e) {
-            // Do nothing.
-        } finally {
-            if (out != null) {
-                out.close();
-            }
-        }
-    }
-}
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/TravellingSalesmanSolver.java b/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/TravellingSalesmanSolver.java
deleted file mode 100644
index 2989f93..0000000
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/TravellingSalesmanSolver.java
+++ /dev/null
@@ -1,427 +0,0 @@
-/*
- * 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.commons.math4.legacy.ml.neuralnet.sofm;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.HashSet;
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.apache.commons.math4.legacy.analysis.FunctionUtils;
-import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
-import org.apache.commons.math4.legacy.analysis.function.Constant;
-import org.apache.commons.math4.legacy.analysis.function.HarmonicOscillator;
-import org.apache.commons.statistics.distribution.ContinuousDistribution;
-import org.apache.commons.statistics.distribution.UniformContinuousDistribution;
-import org.apache.commons.math4.legacy.exception.MathUnsupportedOperationException;
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
-import org.apache.commons.math4.legacy.ml.distance.EuclideanDistance;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializerFactory;
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
-import org.apache.commons.math4.legacy.ml.neuralnet.oned.NeuronString;
-import org.apache.commons.rng.simple.RandomSource;
-import org.apache.commons.rng.UniformRandomProvider;
-import org.apache.commons.math4.legacy.util.FastMath;
-
-/**
- * Solves the "Travelling Salesman's Problem" (i.e. trying to find the
- * sequence of cities that minimizes the travel distance) using a 1D
- * SOFM.
- */
-public class TravellingSalesmanSolver {
-    private static final long FIRST_NEURON_ID = 0;
-    /** RNG. */
-    private final UniformRandomProvider random;
-    /** Set of cities. */
-    private final Set<City> cities = new HashSet<>();
-    /** SOFM. */
-    private final Network net;
-    /** Distance function. */
-    private final DistanceMeasure distance = new EuclideanDistance();
-    /** Total number of neurons. */
-    private final int numberOfNeurons;
-
-    /**
-     * @param cityList List of cities to visit in a single travel.
-     * @param numNeuronsPerCity Number of neurons per city.
-     */
-    public TravellingSalesmanSolver(City[] cityList,
-                                    double numNeuronsPerCity) {
-        this(cityList, numNeuronsPerCity, RandomSource.createLong());
-    }
-
-    /**
-     * @param cityList List of cities to visit in a single travel.
-     * @param numNeuronsPerCity Number of neurons per city.
-     * @param seed Seed for the RNG that is used to present the samples
-     * to the trainer.
-     */
-    public TravellingSalesmanSolver(City[] cityList,
-                                    double numNeuronsPerCity,
-                                    long seed) {
-        random = RandomSource.create(RandomSource.WELL_1024_A, seed);
-
-        // Make sure that each city will appear only once in the list.
-        cities.addAll(Arrays.asList(cityList));
-
-        // Total number of neurons.
-        numberOfNeurons = (int) numNeuronsPerCity * cities.size();
-
-        // Create a network with circle topology.
-        net = new NeuronString(numberOfNeurons, true, makeInitializers()).getNetwork();
-    }
-
-    /**
-     * Creates training tasks.
-     *
-     * @param numTasks Number of tasks to create.
-     * @param numSamplesPerTask Number of training samples per task.
-     * @return the created tasks.
-     */
-    public Runnable[] createParallelTasks(int numTasks,
-                                          long numSamplesPerTask) {
-        final Runnable[] tasks = new Runnable[numTasks];
-        final LearningFactorFunction learning
-            = LearningFactorFunctionFactory.exponentialDecay(2e-1,
-                                                             5e-2,
-                                                             numSamplesPerTask / 2);
-        final NeighbourhoodSizeFunction neighbourhood
-            = NeighbourhoodSizeFunctionFactory.exponentialDecay(0.5 * numberOfNeurons,
-                                                                0.1 * numberOfNeurons,
-                                                                numSamplesPerTask / 2);
-
-        for (int i = 0; i < numTasks; i++) {
-            final KohonenUpdateAction action = new KohonenUpdateAction(distance,
-                                                                       learning,
-                                                                       neighbourhood);
-            tasks[i] = new KohonenTrainingTask(net,
-                                               createRandomIterator(numSamplesPerTask),
-                                               action);
-        }
-
-        return tasks;
-    }
-
-    /**
-     * Creates a training task.
-     *
-     * @param numSamples Number of training samples.
-     * @return the created task.
-     */
-    public Runnable createSequentialTask(long numSamples) {
-        return createParallelTasks(1, numSamples)[0];
-    }
-
-    /**
-     * Measures the network's concurrent update performance.
-     *
-     * @return the ratio between the number of successful network updates
-     * and the number of update attempts.
-     */
-    public double getUpdateRatio() {
-        return computeUpdateRatio(net);
-    }
-
-    /**
-     * Measures the network's concurrent update performance.
-     *
-     * @param net Network to be trained with the SOFM algorithm.
-     * @return the ratio between the number of successful network updates
-     * and the number of update attempts.
-     */
-    private static double computeUpdateRatio(Network net) {
-        long numAttempts = 0;
-        long numSuccesses = 0;
-
-        for (Neuron n : net) {
-            numAttempts += n.getNumberOfAttemptedUpdates();
-            numSuccesses += n.getNumberOfSuccessfulUpdates();
-        }
-
-        return (double) numSuccesses / (double) numAttempts;
-    }
-
-    /**
-     * Creates an iterator that will present a series of city's coordinates in
-     * a random order.
-     *
-     * @param numSamples Number of samples.
-     * @return the iterator.
-     */
-    private Iterator<double[]> createRandomIterator(final long numSamples) {
-        final List<City> cityList = new ArrayList<>();
-        cityList.addAll(cities);
-
-        return new Iterator<double[]>() {
-            /** Number of samples. */
-            private long n = 0;
-            /** {@inheritDoc} */
-            @Override
-            public boolean hasNext() {
-                return n < numSamples;
-            }
-            /** {@inheritDoc} */
-            @Override
-            public double[] next() {
-                ++n;
-                return cityList.get(random.nextInt(cityList.size())).getCoordinates();
-            }
-            /** {@inheritDoc} */
-            @Override
-            public void remove() {
-                throw new MathUnsupportedOperationException();
-            }
-        };
-    }
-
-    /**
-     * @return the list of linked neurons (i.e. the one-dimensional
-     * SOFM).
-     */
-    private List<Neuron> getNeuronList() {
-        // Sequence of coordinates.
-        final List<Neuron> list = new ArrayList<>();
-
-        // First neuron.
-        Neuron current = net.getNeuron(FIRST_NEURON_ID);
-        while (true) {
-            list.add(current);
-            final Collection<Neuron> neighbours
-                = net.getNeighbours(current, list);
-
-            final Iterator<Neuron> iter = neighbours.iterator();
-            if (!iter.hasNext()) {
-                // All neurons have been visited.
-                break;
-            }
-
-            current = iter.next();
-        }
-
-        return list;
-    }
-
-    /**
-     * @return the list of features (coordinates) of linked neurons.
-     */
-    public List<double[]> getCoordinatesList() {
-        // Sequence of coordinates.
-        final List<double[]> coordinatesList = new ArrayList<>();
-
-        for (Neuron n : getNeuronList()) {
-            coordinatesList.add(n.getFeatures());
-        }
-
-        return coordinatesList;
-    }
-
-    /**
-     * Returns the travel proposed by the solver.
-     * Note: cities can be missing or duplicated.
-     *
-     * @return the list of cities in travel order.
-     */
-    public City[] getCityList() {
-        final List<double[]> coord = getCoordinatesList();
-        final City[] cityList = new City[coord.size()];
-        for (int i = 0; i < cityList.length; i++) {
-            final double[] c = coord.get(i);
-            cityList[i] = getClosestCity(c[0], c[1]);
-        }
-        return cityList;
-    }
-
-    /**
-     * @param x x-coordinate.
-     * @param y y-coordinate.
-     * @return the city whose coordinates are closest to {@code (x, y)}.
-     */
-    public City getClosestCity(double x,
-                               double y) {
-        City closest = null;
-        double min = Double.POSITIVE_INFINITY;
-        for (City c : cities) {
-            final double d = c.distance(x, y);
-            if (d < min) {
-                min = d;
-                closest = c;
-            }
-        }
-        return closest;
-    }
-
-    /**
-     * Computes the barycentre of all city locations.
-     *
-     * @param cities City list.
-     * @return the barycentre.
-     */
-    private static double[] barycentre(Set<City> cities) {
-        double xB = 0;
-        double yB = 0;
-
-        int count = 0;
-        for (City c : cities) {
-            final double[] coord = c.getCoordinates();
-            xB += coord[0];
-            yB += coord[1];
-
-            ++count;
-        }
-
-        return new double[] { xB / count, yB / count };
-    }
-
-    /**
-     * Computes the largest distance between the point at coordinates
-     * {@code (x, y)} and any of the cities.
-     *
-     * @param x x-coordinate.
-     * @param y y-coordinate.
-     * @param cities City list.
-     * @return the largest distance.
-     */
-    private static double largestDistance(double x,
-                                          double y,
-                                          Set<City> cities) {
-        double maxDist = 0;
-        for (City c : cities) {
-            final double dist = c.distance(x, y);
-            if (dist > maxDist) {
-                maxDist = dist;
-            }
-        }
-
-        return maxDist;
-    }
-
-    /**
-     * Creates the features' initializers: an approximate circle around the
-     * barycentre of the cities.
-     *
-     * @return an array containing the two initializers.
-     */
-    private FeatureInitializer[] makeInitializers() {
-        // Barycentre.
-        final double[] centre = barycentre(cities);
-        // Largest distance from centre.
-        final double radius = 0.5 * largestDistance(centre[0], centre[1], cities);
-
-        final double omega = 2 * Math.PI / numberOfNeurons;
-        final UnivariateFunction h1 = new HarmonicOscillator(radius, omega, 0);
-        final UnivariateFunction h2 = new HarmonicOscillator(radius, omega, 0.5 * Math.PI);
-
-        final UnivariateFunction f1 = FunctionUtils.add(h1, new Constant(centre[0]));
-        final UnivariateFunction f2 = FunctionUtils.add(h2, new Constant(centre[1]));
-
-        final ContinuousDistribution u = new UniformContinuousDistribution(-0.05 * radius, 0.05 * radius);
-
-        return new FeatureInitializer[] {
-            FeatureInitializerFactory.randomize(u.createSampler(random),
-                                                FeatureInitializerFactory.function(f1, 0, 1)),
-            FeatureInitializerFactory.randomize(u.createSampler(random),
-                                                FeatureInitializerFactory.function(f2, 0, 1))
-        };
-    }
-}
-
-/**
- * A city, represented by a name and two-dimensional coordinates.
- */
-class City {
-    /** Identifier. */
-    final String name;
-    /** x-coordinate. */
-    final double x;
-    /** y-coordinate. */
-    final double y;
-
-    /**
-     * @param name Name.
-     * @param x Cartesian x-coordinate.
-     * @param y Cartesian y-coordinate.
-     */
-    public City(String name,
-                double x,
-                double y) {
-        this.name = name;
-        this.x = x;
-        this.y = y;
-    }
-
-    /**
-     * @retun the name.
-     */
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * @return the (x, y) coordinates.
-     */
-    public double[] getCoordinates() {
-        return new double[] { x, y };
-    }
-
-    /**
-     * Computes the distance between this city and
-     * the given point.
-     *
-     * @param x x-coordinate.
-     * @param y y-coordinate.
-     * @return the distance between {@code (x, y)} and this
-     * city.
-     */
-    public double distance(double x,
-                           double y) {
-        final double xDiff = this.x - x;
-        final double yDiff = this.y - y;
-
-        return FastMath.sqrt(xDiff * xDiff + yDiff * yDiff);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof City) {
-            final City other = (City) o;
-            return x == other.x &&
-                y == other.y;
-        }
-        return false;
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public int hashCode() {
-        int result = 17;
-
-        final long c1 = Double.doubleToLongBits(x);
-        result = 31 * result + (int) (c1 ^ (c1 >>> 32));
-
-        final long c2 = Double.doubleToLongBits(y);
-        result = 31 * result + (int) (c2 ^ (c2 >>> 32));
-
-        return result;
-    }
-}
diff --git a/commons-math-neuralnet/LICENCE b/commons-math-neuralnet/LICENCE
new file mode 100644
index 0000000..d97b49a
--- /dev/null
+++ b/commons-math-neuralnet/LICENCE
@@ -0,0 +1,457 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
+
+
+Apache Commons Math includes the following code provided to the ASF under the
+Apache License 2.0:
+
+ - The inverse error function implementation in the Erf class is based on CUDA
+   code developed by Mike Giles, Oxford-Man Institute of Quantitative Finance,
+   and published in GPU Computing Gems, volume 2, 2010 (grant received on
+   March 23th 2013)
+ - The LinearConstraint, LinearObjectiveFunction, LinearOptimizer,
+   RelationShip, SimplexSolver and SimplexTableau classes in package
+   org.apache.commons.math3.optimization.linear include software developed by
+   Benjamin McCann (http://www.benmccann.com) and distributed with
+   the following copyright: Copyright 2009 Google Inc. (grant received on
+   March 16th 2009)
+ - The class "org.apache.commons.math3.exception.util.LocalizedFormatsTest" which
+   is an adapted version of "OrekitMessagesTest" test class for the Orekit library
+ - The "org.apache.commons.math3.analysis.interpolation.HermiteInterpolator"
+   has been imported from the Orekit space flight dynamics library.
+
+===============================================================================
+ 
+
+
+APACHE COMMONS MATH DERIVATIVE WORKS: 
+
+The Apache commons-math library includes a number of subcomponents
+whose implementation is derived from original sources written
+in C or Fortran.  License terms of the original sources
+are reproduced below.
+
+===============================================================================
+For the lmder, lmpar and qrsolv Fortran routine from minpack and translated in
+the LevenbergMarquardtOptimizer class in package
+org.apache.commons.math3.optimization.general 
+Original source copyright and license statement:
+
+Minpack Copyright Notice (1999) University of Chicago.  All rights reserved
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions of source code must retain the above
+copyright notice, this list of conditions and the following
+disclaimer.
+
+2. Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following
+disclaimer in the documentation and/or other materials
+provided with the distribution.
+
+3. The end-user documentation included with the
+redistribution, if any, must include the following
+acknowledgment:
+
+   "This product includes software developed by the
+   University of Chicago, as Operator of Argonne National
+   Laboratory.
+
+Alternately, this acknowledgment may appear in the software
+itself, if and wherever such third-party acknowledgments
+normally appear.
+
+4. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
+WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
+UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
+THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
+OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
+OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
+USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
+THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
+DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
+UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
+BE CORRECTED.
+
+5. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
+HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
+ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
+INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
+ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
+PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
+SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
+(INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
+EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
+POSSIBILITY OF SUCH LOSS OR DAMAGES.
+===============================================================================
+
+Copyright and license statement for the odex Fortran routine developed by
+E. Hairer and G. Wanner and translated in GraggBulirschStoerIntegrator class
+in package org.apache.commons.math3.ode.nonstiff:
+
+
+Copyright (c) 2004, Ernst Hairer
+
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are 
+met:
+
+- Redistributions of source code must retain the above copyright 
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright 
+notice, this list of conditions and the following disclaimer in the 
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+===============================================================================
+
+Copyright and license statement for the original Mersenne twister C
+routines translated in MersenneTwister class in package 
+org.apache.commons.math3.random:
+
+   Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+   All rights reserved.                          
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+     1. Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+
+     2. Redistributions in binary form must reproduce the above copyright
+        notice, this list of conditions and the following disclaimer in the
+        documentation and/or other materials provided with the distribution.
+
+     3. The names of its contributors may not be used to endorse or promote 
+        products derived from this software without specific prior written 
+        permission.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+===============================================================================
+
+The initial code for shuffling an array (originally in class
+"org.apache.commons.math3.random.RandomDataGenerator", now replaced by
+a method in class "org.apache.commons.math3.util.MathArrays") was
+inspired from the algorithm description provided in
+"Algorithms", by Ian Craw and John Pulham (University of Aberdeen 1999).
+The textbook (containing a proof that the shuffle is uniformly random) is
+available here:
+  http://citeseerx.ist.psu.edu/viewdoc/download;?doi=10.1.1.173.1898&rep=rep1&type=pdf
+
+===============================================================================
+License statement for the direction numbers in the resource files for Sobol sequences.
+
+-----------------------------------------------------------------------------
+Licence pertaining to sobol.cc and the accompanying sets of direction numbers
+
+-----------------------------------------------------------------------------
+Copyright (c) 2008, Frances Y. Kuo and Stephen Joe
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the names of the copyright holders nor the names of the
+      University of New South Wales and the University of Waikato
+      and its contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+===============================================================================
+
+The initial commit of package "org.apache.commons.math3.ml.neuralnet" is
+an adapted version of code developed in the context of the Data Processing
+and Analysis Consortium (DPAC) of the "Gaia" project of the European Space
+Agency (ESA).
+===============================================================================
+
+The initial commit of the class "org.apache.commons.math3.special.BesselJ" is
+an adapted version of code translated from the netlib Fortran program, rjbesl
+http://www.netlib.org/specfun/rjbesl by R.J. Cody at Argonne National
+Laboratory (USA).  There is no license or copyright statement included with the
+original Fortran sources.
+===============================================================================
+
+
+The BracketFinder (package org.apache.commons.math3.optimization.univariate)
+and PowellOptimizer (package org.apache.commons.math3.optimization.general)
+classes are based on the Python code in module "optimize.py" (version 0.5)
+developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/)
+Copyright © 2003-2009 SciPy Developers.
+
+SciPy license
+Copyright © 2001, 2002 Enthought, Inc.
+All rights reserved.
+
+Copyright © 2003-2013 SciPy Developers.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+    * Neither the name of Enthought nor the names of the SciPy Developers may
+      be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+===============================================================================
+
diff --git a/commons-math-neuralnet/NOTICE b/commons-math-neuralnet/NOTICE
new file mode 100644
index 0000000..587cd7f
--- /dev/null
+++ b/commons-math-neuralnet/NOTICE
@@ -0,0 +1,9 @@
+Apache Commons Math
+Copyright 2001-2020 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes software developed for Orekit by
+CS Systèmes d'Information (http://www.c-s.fr/)
+Copyright 2010-2012 CS Systèmes d'Information
diff --git a/commons-math-neuralnet/pom.xml b/commons-math-neuralnet/pom.xml
new file mode 100644
index 0000000..56357cd
--- /dev/null
+++ b/commons-math-neuralnet/pom.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<!--
+   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.
+-->
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+         xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.commons</groupId>
+    <artifactId>commons-math-parent</artifactId>
+    <version>4.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>commons-math4-neuralnet</artifactId>
+  <name>Artificial neural networks</name>
+
+  <description>Artificial neural networks: Self-organizing feature map (SOFM).</description>
+
+  <properties>
+    <!-- The Java Module System Name -->
+    <commons.module.name>org.apache.commons.math4.neuralnet</commons.module.name>
+    <!-- This value must reflect the current name of the base package. -->
+    <commons.osgi.symbolicName>org.apache.commons.math4.neuralnet</commons.osgi.symbolicName>
+    <!-- OSGi -->
+    <commons.osgi.export>org.apache.commons.math4.neuralnet</commons.osgi.export>
+    <!-- Workaround to avoid duplicating config files. -->
+    <math.parent.dir>${basedir}/..</math.parent.dir>
+  </properties>
+
+  <dependencies>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-numbers-core</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-rng-simple</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-rng-sampling</artifactId>
+    </dependency>
+
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <rerunFailingTestsCount>9</rerunFailingTestsCount>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/package-info.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/DistanceMeasure.java
similarity index 75%
copy from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/package-info.java
copy to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/DistanceMeasure.java
index 8c02239..68d846d 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/package-info.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/DistanceMeasure.java
@@ -15,8 +15,13 @@
  * limitations under the License.
  */
 
+package org.apache.commons.math4.neuralnet;
+
+import java.util.function.ToDoubleBiFunction;
+
 /**
- * One-dimensional neural networks.
+ * Interface for distance measures between two n-dimensional vectors.
+ *
+ * @since 4.0
  */
-
-package org.apache.commons.math4.legacy.ml.neuralnet.oned;
+public interface DistanceMeasure extends ToDoubleBiFunction<double[],double[]> {}
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/EuclideanDistance.java
similarity index 51%
copy from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java
copy to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/EuclideanDistance.java
index c7dc7a0..ca0165c 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/EuclideanDistance.java
@@ -15,23 +15,31 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet;
+
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
 
 /**
- * Provides the network neighbourhood's size as a function of the
- * number of calls already performed during the learning task.
- * The "neighbourhood" is the set of neurons that can be reached
- * by traversing at most the number of links returned by this
- * function.
+ * Euclidean distance measures of n-dimensional vectors.
  *
- * @since 3.3
+ * @since 4.0
  */
-public interface NeighbourhoodSizeFunction {
-    /**
-     * Computes the neighbourhood size at the current call.
-     *
-     * @param numCall Current step of the training task.
-     * @return the value of the function at {@code numCall}.
-     */
-    int value(long numCall);
+public class EuclideanDistance implements DistanceMeasure {
+    /** {@inheritDoc} */
+    @Override
+    public double applyAsDouble(double[] a,
+                                double[] b) {
+        final int len = a.length;
+        if (len != b.length) {
+            throw new NeuralNetException(NeuralNetException.SIZE_MISMATCH,
+                                         len, b.length);
+        }
+
+        double sum = 0;
+        for (int i = 0; i < len; i++) {
+            final double diff = a[i] - b[i];
+            sum += diff * diff;
+        }
+        return Math.sqrt(sum);
+    }
 }
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/FeatureInitializer.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/FeatureInitializer.java
similarity index 95%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/FeatureInitializer.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/FeatureInitializer.java
index f841c2c..049f4bc 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/FeatureInitializer.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/FeatureInitializer.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 /**
  * Defines how to assign the first value of a neuron's feature.
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/FeatureInitializerFactory.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/FeatureInitializerFactory.java
similarity index 68%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/FeatureInitializerFactory.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/FeatureInitializerFactory.java
index 5474793..60f511c 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/FeatureInitializerFactory.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/FeatureInitializerFactory.java
@@ -15,14 +15,13 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
+
+import java.util.function.DoubleUnaryOperator;
 
-import org.apache.commons.math4.legacy.analysis.UnivariateFunction;
-import org.apache.commons.math4.legacy.analysis.function.Constant;
-import org.apache.commons.statistics.distribution.ContinuousDistribution;
-import org.apache.commons.statistics.distribution.UniformContinuousDistribution;
-import org.apache.commons.rng.simple.RandomSource;
 import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+import org.apache.commons.rng.sampling.distribution.ContinuousUniformSampler;
 
 /**
  * Creates functions that will select the initial values of a neuron's
@@ -43,29 +42,13 @@ public class FeatureInitializerFactory {
      * uniform distribution.
      * @return an initializer such that the features will be initialized with
      * values within the given range.
-     * @throws org.apache.commons.math4.legacy.exception.NumberIsTooLargeException
-     * if {@code min >= max}.
+     * @throws IllegalArgumentException if {@code min >= max}.
      */
     public static FeatureInitializer uniform(final UniformRandomProvider rng,
                                              final double min,
                                              final double max) {
-        return randomize(new UniformContinuousDistribution(min, max).createSampler(rng),
-                         function(new Constant(0), 0, 0));
-    }
-
-    /**
-     * Uniform sampling of the given range.
-     *
-     * @param min Lower bound of the range.
-     * @param max Upper bound of the range.
-     * @return an initializer such that the features will be initialized with
-     * values within the given range.
-     * @throws org.apache.commons.math4.legacy.exception.NumberIsTooLargeException
-     * if {@code min >= max}.
-     */
-    public static FeatureInitializer uniform(final double min,
-                                             final double max) {
-        return uniform(RandomSource.create(RandomSource.WELL_19937_C), min, max);
+        return randomize(new ContinuousUniformSampler(rng, min, max),
+                         function((x) -> 0, 0, 0));
     }
 
     /**
@@ -78,7 +61,7 @@ public class FeatureInitializerFactory {
      * @param inc Increment
      * @return the initializer.
      */
-    public static FeatureInitializer function(final UnivariateFunction f,
+    public static FeatureInitializer function(final DoubleUnaryOperator f,
                                               final double init,
                                               final double inc) {
         return new FeatureInitializer() {
@@ -88,7 +71,7 @@ public class FeatureInitializerFactory {
             /** {@inheritDoc} */
             @Override
             public double value() {
-                final double result = f.value(arg);
+                final double result = f.applyAsDouble(arg);
                 arg += inc;
                 return result;
             }
@@ -103,7 +86,7 @@ public class FeatureInitializerFactory {
      * @return an initializer whose {@link FeatureInitializer#value() value}
      * method will return {@code orig.value() + random.sample()}.
      */
-    public static FeatureInitializer randomize(final ContinuousDistribution.Sampler random,
+    public static FeatureInitializer randomize(final ContinuousUniformSampler random,
                                                final FeatureInitializer orig) {
         return new FeatureInitializer() {
             /** {@inheritDoc} */
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/MapRanking.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/MapRanking.java
similarity index 86%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/MapRanking.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/MapRanking.java
index d65165b..09b875b 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/MapRanking.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/MapRanking.java
@@ -15,15 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 import java.util.List;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 
-import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
 
 /**
  * Utility for ranking the units (neurons) of a network.
@@ -59,9 +58,8 @@ public class MapRanking {
      * @param features Data.
      * @return the list of neurons sorted in decreasing order of distance to
      * the given data.
-     * @throws org.apache.commons.math4.legacy.exception.DimensionMismatchException
-     * if the size of the input is not compatible with the neurons features
-     * size.
+     * @throws IllegalArgumentException if the size of the input is not
+     * compatible with the neurons features size.
      */
     public List<Neuron> rank(double[] features) {
         return rank(features, map.size());
@@ -75,15 +73,13 @@ public class MapRanking {
      * @param max Maximum size of the returned list.
      * @return the list of neurons sorted in decreasing order of distance to
      * the given data.
-     * @throws org.apache.commons.math4.legacy.exception.DimensionMismatchException
-     * if the size of the input is not compatible with the neurons features
-     * size.
-     * @throws NotStrictlyPositiveException if {@code max <= 0}.
+     * @throws IllegalArgumentException if the size of the input is not
+     * compatible with the neurons features size or {@code max <= 0}.
      */
     public List<Neuron> rank(double[] features,
                              int max) {
         if (max <= 0) {
-            throw new NotStrictlyPositiveException(max);
+            throw new NeuralNetException(NeuralNetException.NOT_STRICTLY_POSITIVE, max);
         }
         final int m = max <= map.size() ?
             max :
@@ -91,7 +87,7 @@ public class MapRanking {
         final List<PairNeuronDouble> list = new ArrayList<>(m);
 
         for (final Neuron n : map) {
-            final double d = distance.compute(n.getFeatures(), features);
+            final double d = distance.applyAsDouble(n.getFeatures(), features);
             final PairNeuronDouble p = new PairNeuronDouble(n, d);
 
             if (list.size() < m) {
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/MapUtils.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/MapUtils.java
similarity index 86%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/MapUtils.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/MapUtils.java
index ad94491..985d510 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/MapUtils.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/MapUtils.java
@@ -15,11 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 import java.util.List;
-import org.apache.commons.math4.legacy.exception.NoDataException;
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
+
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
 
 /**
  * Utilities for network maps.
@@ -41,7 +41,7 @@ public class MapUtils {
      * @param neurons List of neurons to scan.
      * @param distance Distance function.
      * @return the error.
-     * @throws NoDataException if {@code data} is empty.
+     * @throws IllegalArgumentException if {@code data} is empty.
      */
     public static double computeQuantizationError(Iterable<double[]> data,
                                                   Iterable<Neuron> neurons,
@@ -52,11 +52,11 @@ public class MapUtils {
         int count = 0;
         for (double[] f : data) {
             ++count;
-            d += distance.compute(f, rank.rank(f, 1).get(0).getFeatures());
+            d += distance.applyAsDouble(f, rank.rank(f, 1).get(0).getFeatures());
         }
 
         if (count == 0) {
-            throw new NoDataException();
+            throw new NeuralNetException(NeuralNetException.NO_DATA);
         }
 
         return d / count;
@@ -71,7 +71,7 @@ public class MapUtils {
      * @param net Network.
      * @param distance Distance function.
      * @return the error.
-     * @throws NoDataException if {@code data} is empty.
+     * @throws IllegalArgumentException if {@code data} is empty.
      */
     public static double computeTopographicError(Iterable<double[]> data,
                                                  Network net,
@@ -91,7 +91,7 @@ public class MapUtils {
         }
 
         if (count == 0) {
-            throw new NoDataException();
+            throw new NeuralNetException(NeuralNetException.NO_DATA);
         }
 
         return ((double) notAdjacentCount) / count;
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/Network.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/Network.java
similarity index 96%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/Network.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/Network.java
index cf87222..7de495a 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/Network.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/Network.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 import java.io.Serializable;
 import java.io.ObjectInputStream;
@@ -33,8 +33,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
 
-import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
-import org.apache.commons.math4.legacy.exception.MathIllegalStateException;
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
 
 /**
  * Neural network, composed of {@link Neuron} instances and the links
@@ -90,7 +89,7 @@ public class Network
      * @param neuronList Neurons.
      * @param neighbourIdList Links associated to each of the neurons in
      * {@code neuronList}.
-     * @throws MathIllegalStateException if an inconsistency is detected
+     * @throws IllegalStateException if an inconsistency is detected
      * (which probably means that the serialized form has been corrupted).
      */
     Network(long nextId,
@@ -99,14 +98,14 @@ public class Network
             long[][] neighbourIdList) {
         final int numNeurons = neuronList.length;
         if (numNeurons != neighbourIdList.length) {
-            throw new MathIllegalStateException();
+            throw new IllegalStateException();
         }
 
         for (int i = 0; i < numNeurons; i++) {
             final Neuron n = neuronList[i];
             final long id = n.getIdentifier();
             if (id >= nextId) {
-                throw new MathIllegalStateException();
+                throw new IllegalStateException();
             }
             neuronMap.put(id, n);
             linkMap.put(id, new HashSet<Long>());
@@ -117,7 +116,7 @@ public class Network
             final Set<Long> aLinks = linkMap.get(aId);
             for (Long bId : neighbourIdList[i]) {
                 if (neuronMap.get(bId) == null) {
-                    throw new MathIllegalStateException();
+                    throw new IllegalStateException();
                 }
                 addLinkToLinkSet(aLinks, bId);
             }
@@ -191,13 +190,14 @@ public class Network
      *
      * @param features Initial values for the neuron's features.
      * @return the neuron's identifier.
-     * @throws DimensionMismatchException if the length of {@code features}
+     * @throws IllegalArgumentException if the length of {@code features}
      * is different from the expected size (as set by the
      * {@link #Network(long,int) constructor}).
      */
     public long createNeuron(double[] features) {
         if (features.length != featureSize) {
-            throw new DimensionMismatchException(features.length, featureSize);
+            throw new NeuralNetException(NeuralNetException.SIZE_MISMATCH,
+                                         features.length, featureSize);
         }
 
         final long id = createNextId();
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/Neuron.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/Neuron.java
similarity index 94%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/Neuron.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/Neuron.java
index ec25653..2032ca3 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/Neuron.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/Neuron.java
@@ -15,16 +15,15 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 import java.io.Serializable;
 import java.io.ObjectInputStream;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
 import org.apache.commons.numbers.core.Precision;
-
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
 
 /**
  * Describes a neuron element of a neural network.
@@ -134,14 +133,15 @@ public class Neuron implements Serializable {
      * @param update Features's new values.
      * @return {@code true} if the update was successful, {@code false}
      * otherwise.
-     * @throws DimensionMismatchException if the length of {@code update} is
+     * @throws IllegalArgumentException if the length of {@code update} is
      * not the same as specified in the {@link #Neuron(long,double[])
      * constructor}.
      */
     public boolean compareAndSetFeatures(double[] expect,
                                          double[] update) {
         if (update.length != size) {
-            throw new DimensionMismatchException(update.length, size);
+            throw new NeuralNetException(NeuralNetException.SIZE_MISMATCH,
+                                         update.length, size);
         }
 
         // Get the internal reference. Note that this must not be a copy;
@@ -200,7 +200,7 @@ public class Neuron implements Serializable {
      *
      * @param current Current values.
      * @param expect Expected values.
-     * @throws DimensionMismatchException if the length of {@code expected}
+     * @throws IllegalArgumentException if the length of {@code expect}
      * is not the same as specified in the {@link #Neuron(long,double[])
      * constructor}.
      * @return {@code true} if the arrays contain the same values.
@@ -208,7 +208,8 @@ public class Neuron implements Serializable {
     private boolean containSameValues(double[] current,
                                       double[] expect) {
         if (expect.length != size) {
-            throw new DimensionMismatchException(expect.length, size);
+            throw new NeuralNetException(NeuralNetException.SIZE_MISMATCH,
+                                         expect.length, size);
         }
 
         for (int i = 0; i < size; i++) {
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/SquareNeighbourhood.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/SquareNeighbourhood.java
similarity index 96%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/SquareNeighbourhood.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/SquareNeighbourhood.java
index ca8126c..890512b 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/SquareNeighbourhood.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/SquareNeighbourhood.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 /**
  * Defines neighbourhood types.
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/UpdateAction.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/UpdateAction.java
similarity index 95%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/UpdateAction.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/UpdateAction.java
index 7fe684b..6914c86 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/UpdateAction.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/UpdateAction.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 /**
  * Describes how to update the network in response to a training
diff --git a/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/internal/NeuralNetException.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/internal/NeuralNetException.java
new file mode 100644
index 0000000..7e4fe9d
--- /dev/null
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/internal/NeuralNetException.java
@@ -0,0 +1,51 @@
+/*
+ * 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.commons.math4.neuralnet.internal;
+
+import java.text.MessageFormat;
+
+/**
+ * Exception class with constants for frequently used messages.
+ */
+public class NeuralNetException extends IllegalArgumentException {
+    /** Error message for "out of range" condition. */
+    public static final String OUT_OF_RANGE = "Number {0} is out of range [{1}, {2}]";
+    /** Error message for "not strictly positive" condition. */
+    public static final String NOT_STRICTLY_POSITIVE = "Number {0} is not strictly positive";
+    /** Error message for "too large" condition. */
+    public static final String TOO_LARGE = "Number {0} is larger than {1}";
+    /** Error message for "too small" condition. */
+    public static final String TOO_SMALL = "Number {0} is smaller than {1}";
+    /** Error message for "out of range" condition. */
+    public static final String NO_DATA = "No data";
+    /** Error message for "size mismatch" condition. */
+    public static final String SIZE_MISMATCH = "Size mismatch: {0} != {1}";
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 20210515L;
+
+    /**
+     * Create an exception where the message is constructed by applying
+     * the {@code format()} method from {@code java.text.MessageFormat}.
+     *
+     * @param message Message format (with replaceable parameters).
+     * @param formatArguments Actual arguments to be displayed in the message.
+     */
+    public NeuralNetException(String message, Object... formatArguments) {
+        super(MessageFormat.format(message, formatArguments));
+    }
+}
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/NeuronString.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/oned/NeuronString.java
similarity index 90%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/NeuronString.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/oned/NeuronString.java
index dacdaab..5c998b2 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/NeuronString.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/oned/NeuronString.java
@@ -15,15 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.oned;
+package org.apache.commons.math4.neuralnet.oned;
 
 import java.io.Serializable;
 import java.io.ObjectInputStream;
 
-import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
-import org.apache.commons.math4.legacy.exception.OutOfRangeException;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
+import org.apache.commons.math4.neuralnet.FeatureInitializer;
+import org.apache.commons.math4.neuralnet.Network;
 
 /**
  * Neural network with the topology of a one-dimensional line.
@@ -54,14 +53,14 @@ public class NeuronString implements Serializable {
      * neurons will be linked together).
      * @param featuresList Arrays that will initialize the features sets of
      * the network's neurons.
-     * @throws NumberIsTooSmallException if {@code num < 2}.
+     * @throws IllegalArgumentException if {@code num < 2}.
      */
     NeuronString(boolean wrap,
                  double[][] featuresList) {
         size = featuresList.length;
 
         if (size < 2) {
-            throw new NumberIsTooSmallException(size, 2, true);
+            throw new NeuralNetException(NeuralNetException.TOO_SMALL, size, 2);
         }
 
         this.wrap = wrap;
@@ -96,13 +95,13 @@ public class NeuronString implements Serializable {
      * neurons will be linked together).
      * @param featureInit Arrays that will initialize the features sets of
      * the network's neurons.
-     * @throws NumberIsTooSmallException if {@code num < 2}.
+     * @throws IllegalArgumentException if {@code num < 2}.
      */
     public NeuronString(int num,
                         boolean wrap,
                         FeatureInitializer[] featureInit) {
         if (num < 2) {
-            throw new NumberIsTooSmallException(num, 2, true);
+            throw new NeuralNetException(NeuralNetException.TOO_SMALL, num, 2);
         }
 
         size = num;
@@ -153,12 +152,12 @@ public class NeuronString implements Serializable {
      *
      * @param i Neuron index.
      * @return the features of the neuron at index {@code i}.
-     * @throws OutOfRangeException if {@code i} is out of range.
+     * @throws IllegalArgumentException if {@code i} is out of range.
      */
     public double[] getFeatures(int i) {
         if (i < 0 ||
             i >= size) {
-            throw new OutOfRangeException(i, 0, size - 1);
+            throw new NeuralNetException(NeuralNetException.OUT_OF_RANGE, i, 0, size - 1);
         }
 
         return network.getNeuron(identifiers[i]).getFeatures();
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/package-info.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/oned/package-info.java
similarity index 93%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/package-info.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/oned/package-info.java
index 8c02239..d7c1197 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/package-info.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/oned/package-info.java
@@ -19,4 +19,4 @@
  * One-dimensional neural networks.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.oned;
+package org.apache.commons.math4.neuralnet.oned;
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/package-info.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/package-info.java
similarity index 93%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/package-info.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/package-info.java
index 3ffa46c..2498200 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/package-info.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/package-info.java
@@ -19,4 +19,4 @@
  * Neural networks.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenTrainingTask.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/KohonenTrainingTask.java
similarity index 94%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenTrainingTask.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/KohonenTrainingTask.java
index 8bd4e52..70d891e 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenTrainingTask.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/KohonenTrainingTask.java
@@ -15,11 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
 import java.util.Iterator;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.Network;
 
 /**
  * Trainer for Kohonen's Self-Organizing Map.
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenUpdateAction.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/KohonenUpdateAction.java
similarity index 83%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenUpdateAction.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/KohonenUpdateAction.java
index 6119d36..917551a 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenUpdateAction.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/KohonenUpdateAction.java
@@ -15,19 +15,18 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.DoubleUnaryOperator;
 
-import org.apache.commons.math4.legacy.analysis.function.Gaussian;
-import org.apache.commons.math4.legacy.linear.ArrayRealVector;
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
-import org.apache.commons.math4.legacy.ml.neuralnet.MapRanking;
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
-import org.apache.commons.math4.legacy.ml.neuralnet.UpdateAction;
+import org.apache.commons.math4.neuralnet.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.MapRanking;
+import org.apache.commons.math4.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.Neuron;
+import org.apache.commons.math4.neuralnet.UpdateAction;
 
 /**
  * Update formula for <a href="http://en.wikipedia.org/wiki/Kohonen">
@@ -105,9 +104,7 @@ public class KohonenUpdateAction implements UpdateAction {
         // The farther away the neighbour is from the winning neuron, the
         // smaller the learning rate will become.
         final Gaussian neighbourhoodDecay
-            = new Gaussian(currentLearning,
-                           0,
-                           currentNeighbourhood);
+            = new Gaussian(currentLearning, currentNeighbourhood);
 
         if (currentNeighbourhood > 0) {
             // Initial set of neurons only contains the winning neuron.
@@ -124,11 +121,11 @@ public class KohonenUpdateAction implements UpdateAction {
 
                 // Update all the neighbours.
                 for (Neuron n : neighbours) {
-                    updateNeighbouringNeuron(n, features, neighbourhoodDecay.value(radius));
+                    updateNeighbouringNeuron(n, features, neighbourhoodDecay.applyAsDouble(radius));
                 }
 
                 // Add the neighbours to the exclude list so that they will
-                // not be update more than once per training step.
+                // not be updated more than once per training step.
                 exclude.addAll(neighbours);
                 ++radius;
             } while (radius <= currentNeighbourhood);
@@ -220,9 +217,38 @@ public class KohonenUpdateAction implements UpdateAction {
     private double[] computeFeatures(double[] current,
                                      double[] sample,
                                      double learningRate) {
-        final ArrayRealVector c = new ArrayRealVector(current, false);
-        final ArrayRealVector s = new ArrayRealVector(sample, false);
-        // c + learningRate * (s - c)
-        return s.subtract(c).mapMultiplyToSelf(learningRate).add(c).toArray();
+        final int len = current.length;
+        final double[] r = new double[len];
+        for (int i = 0; i < len; i++) {
+            final double c = current[i];
+            final double s = sample[i];
+            r[i] = c + learningRate * (s - c);
+        }
+        return r;
+    }
+
+    /**
+     * Gaussian function with zero mean.
+     */
+    private static class Gaussian implements DoubleUnaryOperator {
+        /** Inverse of twice the square of the standard deviation. */
+        private final double i2s2;
+        /** Normalization factor. */
+        private final double norm;
+
+        /**
+         * @param norm Normalization factor.
+         * @param sigma Standard deviation.
+         */
+        public Gaussian(double norm,
+                        double sigma) {
+            this.norm = norm;
+            i2s2 = 1d / (2 * sigma * sigma);
+        }
+
+        @Override
+        public double applyAsDouble(double x) {
+            return norm * Math.exp(-x * x * i2s2);
+        }
     }
 }
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunction.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunction.java
similarity index 95%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunction.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunction.java
index 948ea69..a90a459 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunction.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunction.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
 /**
  * Provides the learning rate as a function of the number of calls
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunctionFactory.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunctionFactory.java
similarity index 72%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunctionFactory.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunctionFactory.java
index 1ada170..0eaeeff 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunctionFactory.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunctionFactory.java
@@ -15,11 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
-import org.apache.commons.math4.legacy.exception.OutOfRangeException;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.util.ExponentialDecayFunction;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.util.QuasiSigmoidDecayFunction;
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
+import org.apache.commons.math4.neuralnet.sofm.util.ExponentialDecayFunction;
+import org.apache.commons.math4.neuralnet.sofm.util.QuasiSigmoidDecayFunction;
 
 /**
  * Factory for creating instances of {@link LearningFactorFunction}.
@@ -45,21 +45,16 @@ public class LearningFactorFunctionFactory {
      * @param numCall Argument for which the function returns
      * {@code valueAtNumCall}.
      * @return the learning factor function.
-     * @throws org.apache.commons.math4.legacy.exception.OutOfRangeException
-     * if {@code initValue <= 0} or {@code initValue > 1}.
-     * @throws org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException
-     * if {@code valueAtNumCall <= 0}.
-     * @throws org.apache.commons.math4.legacy.exception.NumberIsTooLargeException
-     * if {@code valueAtNumCall >= initValue}.
-     * @throws org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException
-     * if {@code numCall <= 0}.
+     * @throws IllegalArgumentException if {@code initValue <= 0},
+     * {@code initValue > 1} {@code valueAtNumCall <= 0},
+     * {@code valueAtNumCall >= initValue} or {@code numCall <= 0}.
      */
     public static LearningFactorFunction exponentialDecay(final double initValue,
                                                           final double valueAtNumCall,
                                                           final long numCall) {
         if (initValue <= 0 ||
             initValue > 1) {
-            throw new OutOfRangeException(initValue, 0, 1);
+            throw new NeuralNetException(NeuralNetException.OUT_OF_RANGE, initValue, 0, 1);
         }
 
         return new LearningFactorFunction() {
@@ -70,7 +65,7 @@ public class LearningFactorFunctionFactory {
             /** {@inheritDoc} */
             @Override
             public double value(long n) {
-                return decay.value(n);
+                return decay.applyAsDouble(n);
             }
         };
     }
@@ -89,19 +84,15 @@ public class LearningFactorFunctionFactory {
      * @param slope Value of the function derivative at {@code numCall}.
      * @param numCall Inflexion point.
      * @return the learning factor function.
-     * @throws org.apache.commons.math4.legacy.exception.OutOfRangeException
-     * if {@code initValue <= 0} or {@code initValue > 1}.
-     * @throws org.apache.commons.math4.legacy.exception.NumberIsTooLargeException
-     * if {@code slope >= 0}.
-     * @throws org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException
-     * if {@code numCall <= 0}.
+     * @throws IllegalArgumentException if {@code initValue <= 0},
+     * {@code initValue > 1}, {@code slope >= 0} or {@code numCall <= 0}.
      */
     public static LearningFactorFunction quasiSigmoidDecay(final double initValue,
                                                            final double slope,
                                                            final long numCall) {
         if (initValue <= 0 ||
             initValue > 1) {
-            throw new OutOfRangeException(initValue, 0, 1);
+            throw new NeuralNetException(NeuralNetException.OUT_OF_RANGE, initValue, 0, 1);
         }
 
         return new LearningFactorFunction() {
@@ -112,7 +103,7 @@ public class LearningFactorFunctionFactory {
             /** {@inheritDoc} */
             @Override
             public double value(long n) {
-                return decay.value(n);
+                return decay.applyAsDouble(n);
             }
         };
     }
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunction.java
similarity index 95%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunction.java
index c7dc7a0..1fd90e6 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunction.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunction.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
 /**
  * Provides the network neighbourhood's size as a function of the
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java
similarity index 73%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java
index 73e48ed..d2b81be 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunctionFactory.java
@@ -15,11 +15,10 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.util.ExponentialDecayFunction;
-import org.apache.commons.math4.legacy.ml.neuralnet.sofm.util.QuasiSigmoidDecayFunction;
-import org.apache.commons.math4.legacy.util.FastMath;
+import org.apache.commons.math4.neuralnet.sofm.util.ExponentialDecayFunction;
+import org.apache.commons.math4.neuralnet.sofm.util.QuasiSigmoidDecayFunction;
 
 /**
  * Factory for creating instances of {@link NeighbourhoodSizeFunction}.
@@ -45,14 +44,9 @@ public class NeighbourhoodSizeFunctionFactory {
      * @param numCall Argument for which the function returns
      * {@code valueAtNumCall}.
      * @return the neighbourhood size function.
-     * @throws org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException
-     * if {@code initValue <= 0}.
-     * @throws org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException
-     * if {@code valueAtNumCall <= 0}.
-     * @throws org.apache.commons.math4.legacy.exception.NumberIsTooLargeException
-     * if {@code valueAtNumCall >= initValue}.
-     * @throws org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException
-     * if {@code numCall <= 0}.
+     * @throws IllegalArgumentException if {@code initValue <= 0},
+     * {@code valueAtNumCall <= 0}, {@code valueAtNumCall >= initValue}
+     * or {@code numCall <= 0}.
      */
     public static NeighbourhoodSizeFunction exponentialDecay(final double initValue,
                                                              final double valueAtNumCall,
@@ -65,7 +59,7 @@ public class NeighbourhoodSizeFunctionFactory {
             /** {@inheritDoc} */
             @Override
             public int value(long n) {
-                return (int) FastMath.rint(decay.value(n));
+                return (int) Math.rint(decay.applyAsDouble(n));
             }
         };
     }
@@ -84,12 +78,8 @@ public class NeighbourhoodSizeFunctionFactory {
      * @param slope Value of the function derivative at {@code numCall}.
      * @param numCall Inflexion point.
      * @return the neighbourhood size function.
-     * @throws org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException
-     * if {@code initValue <= 0}.
-     * @throws org.apache.commons.math4.legacy.exception.NumberIsTooLargeException
-     * if {@code slope >= 0}.
-     * @throws org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException
-     * if {@code numCall <= 0}.
+     * @throws IllegalArgumentException if {@code initValue <= 0},
+     * {@code slope >= 0} or {@code numCall <= 0}.
      */
     public static NeighbourhoodSizeFunction quasiSigmoidDecay(final double initValue,
                                                               final double slope,
@@ -102,7 +92,7 @@ public class NeighbourhoodSizeFunctionFactory {
             /** {@inheritDoc} */
             @Override
             public int value(long n) {
-                return (int) FastMath.rint(decay.value(n));
+                return (int) Math.rint(decay.applyAsDouble(n));
             }
         };
     }
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/package-info.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/package-info.java
similarity index 93%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/package-info.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/package-info.java
index 563b2eb..588cf3c 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/package-info.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/package-info.java
@@ -19,4 +19,4 @@
  * Self Organizing Feature Map.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/ExponentialDecayFunction.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/ExponentialDecayFunction.java
similarity index 64%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/ExponentialDecayFunction.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/ExponentialDecayFunction.java
index 6ad4e7b..4d65120 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/ExponentialDecayFunction.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/ExponentialDecayFunction.java
@@ -15,11 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm.util;
+package org.apache.commons.math4.neuralnet.sofm.util;
 
-import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
-import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
-import org.apache.commons.math4.legacy.util.FastMath;
+import java.util.function.LongToDoubleFunction;
+
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
 
 /**
  * Exponential decay function: <code>a e<sup>-x / b</sup></code>,
@@ -29,7 +29,7 @@ import org.apache.commons.math4.legacy.util.FastMath;
  *
  * @since 3.3
  */
-public class ExponentialDecayFunction {
+public class ExponentialDecayFunction implements LongToDoubleFunction {
     /** Factor {@code a}. */
     private final double a;
     /** Factor {@code 1 / b}. */
@@ -42,33 +42,32 @@ public class ExponentialDecayFunction {
      *  <li>{@code b = -numCall / ln(valueAtNumCall / initValue)}</li>
      * </ul>
      *
-     * @param initValue Initial value, i.e. {@link #value(long) value(0)}.
+     * @param initValue Initial value, i.e. {@link #applyAsDouble(long) applyAsDouble(0)}.
      * @param valueAtNumCall Value of the function at {@code numCall}.
      * @param numCall Argument for which the function returns
      * {@code valueAtNumCall}.
-     * @throws NotStrictlyPositiveException if {@code initValue <= 0}.
-     * @throws NotStrictlyPositiveException if {@code valueAtNumCall <= 0}.
-     * @throws NumberIsTooLargeException if {@code valueAtNumCall >= initValue}.
-     * @throws NotStrictlyPositiveException if {@code numCall <= 0}.
+     * @throws IllegalArgumentException if {@code initValue <= 0},
+     * {@code valueAtNumCall <= 0}, {@code valueAtNumCall >= initValue} or
+     * {@code numCall <= 0}.
      */
     public ExponentialDecayFunction(double initValue,
                                     double valueAtNumCall,
                                     long numCall) {
         if (initValue <= 0) {
-            throw new NotStrictlyPositiveException(initValue);
+            throw new NeuralNetException(NeuralNetException.NOT_STRICTLY_POSITIVE, initValue);
         }
         if (valueAtNumCall <= 0) {
-            throw new NotStrictlyPositiveException(valueAtNumCall);
+            throw new NeuralNetException(NeuralNetException.NOT_STRICTLY_POSITIVE, valueAtNumCall);
         }
         if (valueAtNumCall >= initValue) {
-            throw new NumberIsTooLargeException(valueAtNumCall, initValue, false);
+            throw new NeuralNetException(NeuralNetException.TOO_LARGE, valueAtNumCall, initValue);
         }
         if (numCall <= 0) {
-            throw new NotStrictlyPositiveException(numCall);
+            throw new NeuralNetException(NeuralNetException.NOT_STRICTLY_POSITIVE, numCall);
         }
 
         a = initValue;
-        oneOverB = -FastMath.log(valueAtNumCall / initValue) / numCall;
+        oneOverB = -Math.log(valueAtNumCall / initValue) / numCall;
     }
 
     /**
@@ -77,7 +76,8 @@ public class ExponentialDecayFunction {
      * @param numCall Current step of the training task.
      * @return the value of the function at {@code numCall}.
      */
-    public double value(long numCall) {
-        return a * FastMath.exp(-numCall * oneOverB);
+    @Override
+    public double applyAsDouble(long numCall) {
+        return a * Math.exp(-numCall * oneOverB);
     }
 }
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java
similarity index 64%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java
index 2569a27..3873b8f 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/QuasiSigmoidDecayFunction.java
@@ -15,11 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm.util;
+package org.apache.commons.math4.neuralnet.sofm.util;
 
-import org.apache.commons.math4.legacy.analysis.function.Logistic;
-import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
-import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
+import java.util.function.DoubleUnaryOperator;
+import java.util.function.LongToDoubleFunction;
+
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
 
 /**
  * Decay function whose shape is similar to a sigmoid.
@@ -28,9 +29,9 @@ import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
  *
  * @since 3.3
  */
-public class QuasiSigmoidDecayFunction {
+public class QuasiSigmoidDecayFunction implements LongToDoubleFunction {
     /** Sigmoid. */
-    private final Logistic sigmoid;
+    private final DoubleUnaryOperator sigmoid;
     /** See {@link #value(long)}. */
     private final double scale;
 
@@ -43,35 +44,31 @@ public class QuasiSigmoidDecayFunction {
      *  <li>{@code slope = f'(numCall)}</li>
      * </ul>
      *
-     * @param initValue Initial value, i.e. {@link #value(long) value(0)}.
+     * @param initValue Initial value, i.e. {@link #applyAsDouble(long) applyAsDouble(0)}.
      * @param slope Value of the function derivative at {@code numCall}.
      * @param numCall Inflexion point.
-     * @throws NotStrictlyPositiveException if {@code initValue <= 0}.
-     * @throws NumberIsTooLargeException if {@code slope >= 0}.
-     * @throws NotStrictlyPositiveException if {@code numCall <= 0}.
+     * @throws IllegalArgumentException if {@code initValue <= 0},
+     * {@code slope >= 0} or {@code numCall <= 0}.
      */
     public QuasiSigmoidDecayFunction(double initValue,
                                      double slope,
                                      long numCall) {
         if (initValue <= 0) {
-            throw new NotStrictlyPositiveException(initValue);
+            throw new NeuralNetException(NeuralNetException.NOT_STRICTLY_POSITIVE, initValue);
         }
         if (slope >= 0) {
-            throw new NumberIsTooLargeException(slope, 0, false);
+            throw new NeuralNetException(NeuralNetException.TOO_LARGE, slope, 0);
         }
         if (numCall <= 1) {
-            throw new NotStrictlyPositiveException(numCall);
+            throw new NeuralNetException(NeuralNetException.TOO_SMALL, numCall, 1);
         }
 
         final double k = initValue;
         final double m = numCall;
         final double b = 4 * slope / initValue;
-        final double q = 1;
-        final double a = 0;
-        final double n = 1;
-        sigmoid = new Logistic(k, m, b, q, a, n);
+        sigmoid = (x) -> k / (1 + Math.exp(b * (m - x)));
 
-        final double y0 = sigmoid.value(0);
+        final double y0 = sigmoid.applyAsDouble(0d);
         scale = k / y0;
     }
 
@@ -81,7 +78,8 @@ public class QuasiSigmoidDecayFunction {
      * @param numCall Current step of the training task.
      * @return the value of the function at {@code numCall}.
      */
-    public double value(long numCall) {
-        return scale * sigmoid.value(numCall);
+    @Override
+    public double applyAsDouble(long numCall) {
+        return scale * sigmoid.applyAsDouble((double) numCall);
     }
 }
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/package-info.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/package-info.java
similarity index 92%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/package-info.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/package-info.java
index 054bee9..2351ebc 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/package-info.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/sofm/util/package-info.java
@@ -19,4 +19,4 @@
  * Miscellaneous utilities.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm.util;
+package org.apache.commons.math4.neuralnet.sofm.util;
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/NeuronSquareMesh2D.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/NeuronSquareMesh2D.java
similarity index 94%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/NeuronSquareMesh2D.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/NeuronSquareMesh2D.java
index 9501ccf..de0718b 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/NeuronSquareMesh2D.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/NeuronSquareMesh2D.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod;
+package org.apache.commons.math4.neuralnet.twod;
 
 import java.util.List;
 import java.util.ArrayList;
@@ -24,17 +24,15 @@ import java.util.Collection;
 import java.io.Serializable;
 import java.io.ObjectInputStream;
 
-import org.apache.commons.math4.legacy.exception.MathInternalError;
-import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
-import org.apache.commons.math4.legacy.exception.OutOfRangeException;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
-import org.apache.commons.math4.legacy.ml.neuralnet.SquareNeighbourhood;
-import org.apache.commons.math4.legacy.ml.neuralnet.MapRanking;
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.util.LocationFinder;
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
-import org.apache.commons.math4.legacy.ml.distance.EuclideanDistance;
+import org.apache.commons.math4.neuralnet.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.EuclideanDistance;
+import org.apache.commons.math4.neuralnet.FeatureInitializer;
+import org.apache.commons.math4.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.Neuron;
+import org.apache.commons.math4.neuralnet.SquareNeighbourhood;
+import org.apache.commons.math4.neuralnet.MapRanking;
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
+import org.apache.commons.math4.neuralnet.twod.util.LocationFinder;
 
 /**
  * Neural network with the topology of a two-dimensional surface.
@@ -44,7 +42,7 @@ import org.apache.commons.math4.legacy.ml.distance.EuclideanDistance;
  * <a href="http://en.wikipedia.org/wiki/Kohonen">
  *  Self Organizing Feature Map</a>.
  *
- * @see org.apache.commons.math4.legacy.ml.neuralnet.sofm
+ * @see org.apache.commons.math4.neuralnet.sofm
  * @since 3.3
  */
 public class NeuronSquareMesh2D
@@ -106,7 +104,7 @@ public class NeuronSquareMesh2D
      * @param neighbourhoodType Neighbourhood type.
      * @param featuresList Arrays that will initialize the features sets of
      * the network's neurons.
-     * @throws NumberIsTooSmallException if {@code numRows < 2} or
+     * @throws IllegalArgumentException if {@code numRows < 2} or
      * {@code numCols < 2}.
      */
     NeuronSquareMesh2D(boolean wrapRowDim,
@@ -117,10 +115,10 @@ public class NeuronSquareMesh2D
         numberOfColumns = featuresList[0].length;
 
         if (numberOfRows < 2) {
-            throw new NumberIsTooSmallException(numberOfRows, 2, true);
+            throw new NeuralNetException(NeuralNetException.TOO_SMALL, numberOfRows, 2);
         }
         if (numberOfColumns < 2) {
-            throw new NumberIsTooSmallException(numberOfColumns, 2, true);
+            throw new NeuralNetException(NeuralNetException.TOO_SMALL, numberOfColumns, 2);
         }
 
         wrapRows = wrapRowDim;
@@ -164,7 +162,7 @@ public class NeuronSquareMesh2D
      * corresponding element of the features set of each newly created
      * neuron. In particular, the size of this array defines the size of
      * feature set.
-     * @throws NumberIsTooSmallException if {@code numRows < 2} or
+     * @throws IllegalArgumentException if {@code numRows < 2} or
      * {@code numCols < 2}.
      */
     public NeuronSquareMesh2D(int numRows,
@@ -174,10 +172,10 @@ public class NeuronSquareMesh2D
                               SquareNeighbourhood neighbourhoodType,
                               FeatureInitializer[] featureInit) {
         if (numRows < 2) {
-            throw new NumberIsTooSmallException(numRows, 2, true);
+            throw new NeuralNetException(NeuralNetException.TOO_SMALL, numRows, 2);
         }
         if (numCols < 2) {
-            throw new NumberIsTooSmallException(numCols, 2, true);
+            throw new NeuralNetException(NeuralNetException.TOO_SMALL, numCols, 2);
         }
 
         numberOfRows = numRows;
@@ -299,7 +297,7 @@ public class NeuronSquareMesh2D
      * @param i Row index.
      * @param j Column index.
      * @return the neuron at {@code (i, j)}.
-     * @throws OutOfRangeException if {@code i} or {@code j} is
+     * @throws IllegalArgumentException if {@code i} or {@code j} is
      * out of range.
      *
      * @see #getNeuron(int,int,HorizontalDirection,VerticalDirection)
@@ -308,11 +306,13 @@ public class NeuronSquareMesh2D
                             int j) {
         if (i < 0 ||
             i >= numberOfRows) {
-            throw new OutOfRangeException(i, 0, numberOfRows - 1);
+            throw new NeuralNetException(NeuralNetException.OUT_OF_RANGE,
+                                         i, 0, numberOfRows - 1);
         }
         if (j < 0 ||
             j >= numberOfColumns) {
-            throw new OutOfRangeException(j, 0, numberOfColumns - 1);
+            throw new NeuralNetException(NeuralNetException.OUT_OF_RANGE,
+                                         i, 0, numberOfColumns - 1);
         }
 
         return network.getNeuron(identifiers[i][j]);
@@ -389,7 +389,7 @@ public class NeuronSquareMesh2D
             break;
         default:
             // Should never happen.
-            throw new MathInternalError();
+            throw new IllegalStateException();
         }
         int colIndex = col + colOffset;
         if (wrapColumns) {
@@ -413,7 +413,7 @@ public class NeuronSquareMesh2D
             break;
         default:
             // Should never happen.
-            throw new MathInternalError();
+            throw new IllegalStateException();
         }
         int rowIndex = row + rowOffset;
         if (wrapRows) {
@@ -555,7 +555,7 @@ public class NeuronSquareMesh2D
                     break;
 
                 default:
-                    throw new MathInternalError(); // Cannot happen.
+                    throw new IllegalStateException(); // Cannot happen.
                 }
 
                 final Neuron aNeuron = network.getNeuron(identifiers[i][j]);
@@ -729,7 +729,7 @@ public class NeuronSquareMesh2D
                 hitCounter[rowBest][colBest] += 1;
 
                 // Aggregate quantization error.
-                quantizationError[rowBest][colBest] += DISTANCE.compute(sample, best.getFeatures());
+                quantizationError[rowBest][colBest] += DISTANCE.applyAsDouble(sample, best.getFeatures());
 
                 // Aggregate topographic error.
                 if (!net.getNeighbours(best).contains(secondBest)) {
@@ -748,7 +748,7 @@ public class NeuronSquareMesh2D
                     int neighbourCount = 0;
                     for (Neuron n : neighbours) {
                         ++neighbourCount;
-                        uDistance += DISTANCE.compute(features, n.getFeatures());
+                        uDistance += DISTANCE.applyAsDouble(features, n.getFeatures());
                     }
 
                     final int hitCount = hitCounter[r][c];
@@ -810,7 +810,7 @@ public class NeuronSquareMesh2D
          * Each bin will contain the average distance between a unit and all its
          * neighbours will be computed (and stored in the pixel corresponding to
          * that unit of the 2D-map).  The number of neighbours taken into account
-         * depends on the network {@link org.apache.commons.math4.legacy.ml.neuralnet.SquareNeighbourhood
+         * depends on the network {@link org.apache.commons.math4.neuralnet.SquareNeighbourhood
          * neighbourhood type}.
          */
         public double[][] getUMatrix() {
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/package-info.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/package-info.java
similarity index 93%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/package-info.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/package-info.java
index d710ac6..8cf9e6d 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/package-info.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/package-info.java
@@ -19,4 +19,4 @@
  * Two-dimensional neural networks.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod;
+package org.apache.commons.math4.neuralnet.twod;
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/LocationFinder.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/LocationFinder.java
similarity index 85%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/LocationFinder.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/LocationFinder.java
index 52088ea..7ba8d2d 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/LocationFinder.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/LocationFinder.java
@@ -15,13 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod.util;
+package org.apache.commons.math4.neuralnet.twod.util;
 
 import java.util.Map;
 import java.util.HashMap;
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
-import org.apache.commons.math4.legacy.exception.MathIllegalStateException;
+import org.apache.commons.math4.neuralnet.Neuron;
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
 
 /**
  * Helper class to find the grid coordinates of a neuron.
@@ -71,10 +70,10 @@ public class LocationFinder {
      *
      * @param map Map.
      *
-     * @throws MathIllegalStateException if the network contains non-unique
+     * @throws IllegalStateException if the network contains non-unique
      * identifiers.  This indicates an inconsistent state due to a bug in
      * the construction code of the underlying
-     * {@link org.apache.commons.math4.legacy.ml.neuralnet.Network network}.
+     * {@link org.apache.commons.math4.neuralnet.Network network}.
      */
     public LocationFinder(NeuronSquareMesh2D map) {
         final int nR = map.getNumberOfRows();
@@ -84,7 +83,7 @@ public class LocationFinder {
             for (int c = 0; c < nC; c++) {
                 final Long id = map.getNeuron(r, c).getIdentifier();
                 if (locations.get(id) != null) {
-                    throw new MathIllegalStateException();
+                    throw new IllegalStateException();
                 }
                 locations.put(id, new Location(r, c));
             }
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/MapDataVisualization.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/MapDataVisualization.java
similarity index 90%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/MapDataVisualization.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/MapDataVisualization.java
index e646110..d75d25a 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/MapDataVisualization.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/MapDataVisualization.java
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod.util;
+package org.apache.commons.math4.neuralnet.twod.util;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
 
 /**
  * Interface for algorithms that compute some metrics of the projection of
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/MapVisualization.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/MapVisualization.java
similarity index 89%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/MapVisualization.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/MapVisualization.java
index 63287b7..5a764fa 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/MapVisualization.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/MapVisualization.java
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod.util;
+package org.apache.commons.math4.neuralnet.twod.util;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
 
 /**
  * Interface for algorithms that compute some property of a 2D-map.
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/SmoothedDataHistogram.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/SmoothedDataHistogram.java
similarity index 82%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/SmoothedDataHistogram.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/SmoothedDataHistogram.java
index c2a07df..b59d924 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/SmoothedDataHistogram.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/SmoothedDataHistogram.java
@@ -15,14 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod.util;
+package org.apache.commons.math4.neuralnet.twod.util;
 
 import java.util.List;
-import org.apache.commons.math4.legacy.ml.neuralnet.MapRanking;
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
-import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
+import org.apache.commons.math4.neuralnet.internal.NeuralNetException;
+import org.apache.commons.math4.neuralnet.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.MapRanking;
+import org.apache.commons.math4.neuralnet.Neuron;
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
 
 /**
  * Visualization of high-dimensional data projection on a 2D-map.
@@ -62,8 +62,8 @@ public class SmoothedDataHistogram implements MapDataVisualization {
     /**
      * {@inheritDoc}
      *
-     * @throws NumberIsTooSmallException if the size of the {@code map}
-     * is smaller than the number of {@link #SmoothedDataHistogram(int,DistanceMeasure)
+     * @throws IllegalArgumentException if the size of the {@code map} is
+     * smaller than the number of {@link #SmoothedDataHistogram(int,DistanceMeasure)
      * smoothing bins}.
      */
     @Override
@@ -74,7 +74,8 @@ public class SmoothedDataHistogram implements MapDataVisualization {
 
         final int mapSize = nR * nC;
         if (mapSize < smoothingBins) {
-            throw new NumberIsTooSmallException(mapSize, smoothingBins, true);
+            throw new NeuralNetException(NeuralNetException.TOO_SMALL,
+                                         mapSize, smoothingBins);
         }
 
         final LocationFinder finder = new LocationFinder(map);
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/UnifiedDistanceMatrix.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/UnifiedDistanceMatrix.java
similarity index 87%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/UnifiedDistanceMatrix.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/UnifiedDistanceMatrix.java
index 4caa914..3a6c909 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/UnifiedDistanceMatrix.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/UnifiedDistanceMatrix.java
@@ -15,11 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod.util;
+package org.apache.commons.math4.neuralnet.twod.util;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.Neuron;
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
 
 /**
  * <a href="http://en.wikipedia.org/wiki/U-Matrix">U-Matrix</a>
@@ -85,8 +85,8 @@ public class UnifiedDistanceMatrix implements MapVisualization {
                                           NeuronSquareMesh2D.HorizontalDirection.RIGHT,
                                           NeuronSquareMesh2D.VerticalDirection.CENTER);
                 if (neighbour != null) {
-                    uMatrix[iR][jR + 1] = distance.compute(current,
-                                                           neighbour.getFeatures());
+                    uMatrix[iR][jR + 1] = distance.applyAsDouble(current,
+                                                                 neighbour.getFeatures());
                 }
 
                 // Bottom-center neighbour.
@@ -94,8 +94,8 @@ public class UnifiedDistanceMatrix implements MapVisualization {
                                           NeuronSquareMesh2D.HorizontalDirection.CENTER,
                                           NeuronSquareMesh2D.VerticalDirection.DOWN);
                 if (neighbour != null) {
-                    uMatrix[iR + 1][jR] = distance.compute(current,
-                                                           neighbour.getFeatures());
+                    uMatrix[iR + 1][jR] = distance.applyAsDouble(current,
+                                                                 neighbour.getFeatures());
                 }
             }
         }
@@ -126,13 +126,13 @@ public class UnifiedDistanceMatrix implements MapVisualization {
 
                 final double current2BottomRight = bottomRight == null ?
                     0 :
-                    distance.compute(current.getFeatures(),
-                                     bottomRight.getFeatures());
+                    distance.applyAsDouble(current.getFeatures(),
+                                           bottomRight.getFeatures());
                 final double right2Bottom = (right == null ||
                                              bottom == null) ?
                     0 :
-                    distance.compute(right.getFeatures(),
-                                     bottom.getFeatures());
+                    distance.applyAsDouble(right.getFeatures(),
+                                           bottom.getFeatures());
 
                 // Bottom-right slot.
                 uMatrix[iR + 1][jR + 1] = 0.5 * (current2BottomRight + right2Bottom);
diff --git a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/package-info.java b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/package-info.java
similarity index 93%
rename from commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/package-info.java
rename to commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/package-info.java
index 15d3b49..1df7bd2 100644
--- a/commons-math-legacy/src/main/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/package-info.java
+++ b/commons-math-neuralnet/src/main/java/org/apache/commons/math4/neuralnet/twod/util/package-info.java
@@ -19,4 +19,4 @@
  * Utilities to visualize two-dimensional neural networks.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod.util;
+package org.apache.commons.math4.neuralnet.twod.util;
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/MapRankingTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/MapRankingTest.java
similarity index 88%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/MapRankingTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/MapRankingTest.java
index cfbb474..f307d93 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/MapRankingTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/MapRankingTest.java
@@ -15,17 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 import java.util.Set;
 import java.util.HashSet;
 import java.util.List;
-import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
-import org.apache.commons.math4.legacy.ml.distance.EuclideanDistance;
-import org.apache.commons.math4.legacy.ml.neuralnet.oned.NeuronString;
+
 import org.junit.Test;
 import org.junit.Assert;
 
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+
+import org.apache.commons.math4.neuralnet.oned.NeuronString;
+
 /**
  * Tests for {@link MapRanking} class.
  */
@@ -37,8 +40,9 @@ public class MapRankingTest {
      */
     @Test
     public void testFindClosestNeuron() {
+        final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
         final FeatureInitializer init
-            = new OffsetFeatureInitializer(FeatureInitializerFactory.uniform(-0.1, 0.1));
+            = new OffsetFeatureInitializer(FeatureInitializerFactory.uniform(rng, -0.1, 0.1));
         final FeatureInitializer[] initArray = { init };
 
         final MapRanking ranking = new MapRanking(new NeuronString(3, false, initArray).getNetwork(),
@@ -90,10 +94,11 @@ public class MapRankingTest {
         Assert.assertEquals(3, allBest.size());
     }
 
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testRankPrecondition() {
+        final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
         final FeatureInitializer init
-            = new OffsetFeatureInitializer(FeatureInitializerFactory.uniform(-0.1, 0.1));
+            = new OffsetFeatureInitializer(FeatureInitializerFactory.uniform(rng, -0.1, 0.1));
         final FeatureInitializer[] initArray = { init };
 
         new MapRanking(new NeuronString(3, false, initArray).getNetwork(),
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/NetworkTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/NetworkTest.java
similarity index 95%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/NetworkTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/NetworkTest.java
index 2fb7d80..21f435d 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/NetworkTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/NetworkTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -25,15 +25,20 @@ import java.io.ObjectOutputStream;
 import java.util.Collection;
 import java.util.NoSuchElementException;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
 import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
+
 /**
  * Tests for {@link Network}.
  */
 public class NetworkTest {
-    final FeatureInitializer init = FeatureInitializerFactory.uniform(0, 2);
+    private final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+    private final FeatureInitializer init = FeatureInitializerFactory.uniform(rng, 0, 2);
 
     @Test
     public void testGetFeaturesSize() {
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/NeuronTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/NeuronTest.java
similarity index 98%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/NeuronTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/NeuronTest.java
index 567d461..3b4f7cf 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/NeuronTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/NeuronTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 import java.io.ByteArrayOutputStream;
 import java.io.ByteArrayInputStream;
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/OffsetFeatureInitializer.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/OffsetFeatureInitializer.java
similarity index 96%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/OffsetFeatureInitializer.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/OffsetFeatureInitializer.java
index 11619ab..dc40be2 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/OffsetFeatureInitializer.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/OffsetFeatureInitializer.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet;
+package org.apache.commons.math4.neuralnet;
 
 
 /**
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/NeuronStringTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/oned/NeuronStringTest.java
similarity index 92%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/NeuronStringTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/oned/NeuronStringTest.java
index 76a0aa0..04c1305 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/oned/NeuronStringTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/oned/NeuronStringTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.oned;
+package org.apache.commons.math4.neuralnet.oned;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -25,19 +25,24 @@ import java.io.ObjectOutputStream;
 import java.util.ArrayList;
 import java.util.Collection;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializerFactory;
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
 import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+
+import org.apache.commons.math4.neuralnet.FeatureInitializer;
+import org.apache.commons.math4.neuralnet.FeatureInitializerFactory;
+import org.apache.commons.math4.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.Neuron;
+
 /**
  * Tests for {@link NeuronString} and {@link Network} functionality for
  * a one-dimensional network.
  */
 public class NeuronStringTest {
-    final FeatureInitializer init = FeatureInitializerFactory.uniform(0, 2);
+    private final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+    private final FeatureInitializer init = FeatureInitializerFactory.uniform(rng, 0, 2);
 
     /*
      * Test assumes that the network is
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenUpdateActionTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/KohonenUpdateActionTest.java
similarity index 70%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenUpdateActionTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/KohonenUpdateActionTest.java
index 21350ab..d729a8e 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/KohonenUpdateActionTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/KohonenUpdateActionTest.java
@@ -15,25 +15,31 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
-import org.apache.commons.math4.legacy.ml.distance.DistanceMeasure;
-import org.apache.commons.math4.legacy.ml.distance.EuclideanDistance;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializerFactory;
-import org.apache.commons.math4.legacy.ml.neuralnet.MapRanking;
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
-import org.apache.commons.math4.legacy.ml.neuralnet.OffsetFeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.UpdateAction;
-import org.apache.commons.math4.legacy.ml.neuralnet.oned.NeuronString;
 import org.junit.Test;
 import org.junit.Assert;
 
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+
+import org.apache.commons.math4.neuralnet.DistanceMeasure;
+import org.apache.commons.math4.neuralnet.EuclideanDistance;
+import org.apache.commons.math4.neuralnet.FeatureInitializer;
+import org.apache.commons.math4.neuralnet.FeatureInitializerFactory;
+import org.apache.commons.math4.neuralnet.MapRanking;
+import org.apache.commons.math4.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.Neuron;
+import org.apache.commons.math4.neuralnet.OffsetFeatureInitializer;
+import org.apache.commons.math4.neuralnet.UpdateAction;
+import org.apache.commons.math4.neuralnet.oned.NeuronString;
+
 /**
  * Tests for {@link KohonenUpdateAction} class.
  */
 public class KohonenUpdateActionTest {
+    private final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+
     /*
      * Test assumes that the network is
      *
@@ -42,7 +48,7 @@ public class KohonenUpdateActionTest {
     @Test
     public void testUpdate() {
         final FeatureInitializer init
-            = new OffsetFeatureInitializer(FeatureInitializerFactory.uniform(0, 0.1));
+            = new OffsetFeatureInitializer(FeatureInitializerFactory.uniform(rng, 0, 0.1));
         final FeatureInitializer[] initArray = { init };
 
         final int netSize = 3;
@@ -65,25 +71,25 @@ public class KohonenUpdateActionTest {
         final double[] distancesBefore = new double[netSize];
         int count = 0;
         for (Neuron n : net) {
-            distancesBefore[count++] = dist.compute(n.getFeatures(), features);
+            distancesBefore[count++] = dist.applyAsDouble(n.getFeatures(), features);
         }
         final Neuron bestBefore = rank.rank(features, 1).get(0);
 
         // Initial distance from the best match is larger than zero.
-        Assert.assertTrue(dist.compute(bestBefore.getFeatures(), features) >= 0.2);
+        Assert.assertTrue(dist.applyAsDouble(bestBefore.getFeatures(), features) >= 0.2);
 
         update.update(net, features);
 
         final double[] distancesAfter = new double[netSize];
         count = 0;
         for (Neuron n : net) {
-            distancesAfter[count++] = dist.compute(n.getFeatures(), features);
+            distancesAfter[count++] = dist.applyAsDouble(n.getFeatures(), features);
         }
         final Neuron bestAfter = rank.rank(features, 1).get(0);
 
         Assert.assertEquals(bestBefore, bestAfter);
         // Distance is now zero.
-        Assert.assertEquals(0, dist.compute(bestAfter.getFeatures(), features), 1e-16);
+        Assert.assertEquals(0, dist.applyAsDouble(bestAfter.getFeatures(), features), 1e-16);
 
         for (int i = 0; i < netSize; i++) {
             // All distances have decreased.
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunctionFactoryTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunctionFactoryTest.java
similarity index 80%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunctionFactoryTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunctionFactoryTest.java
index 5d72254..b976f1f 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/LearningFactorFunctionFactoryTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/LearningFactorFunctionFactoryTest.java
@@ -15,11 +15,8 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
-import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
-import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
-import org.apache.commons.math4.legacy.exception.OutOfRangeException;
 import org.junit.Test;
 import org.junit.Assert;
 
@@ -27,23 +24,23 @@ import org.junit.Assert;
  * Tests for {@link LearningFactorFunctionFactory} class.
  */
 public class LearningFactorFunctionFactoryTest {
-    @Test(expected=OutOfRangeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition0() {
         LearningFactorFunctionFactory.exponentialDecay(0d, 0d, 2);
     }
-    @Test(expected=OutOfRangeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition1() {
         LearningFactorFunctionFactory.exponentialDecay(1 + 1e-10, 0d, 2);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition2() {
         LearningFactorFunctionFactory.exponentialDecay(1d, 0d, 2);
     }
-    @Test(expected=NumberIsTooLargeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition3() {
         LearningFactorFunctionFactory.exponentialDecay(1d, 1d, 100);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition4() {
         LearningFactorFunctionFactory.exponentialDecay(1d, 0.2, 0);
     }
@@ -61,19 +58,19 @@ public class LearningFactorFunctionFactoryTest {
         Assert.assertEquals(0, f.value(Long.MAX_VALUE), 0d);
     }
 
-    @Test(expected=OutOfRangeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testQuasiSigmoidDecayPrecondition0() {
         LearningFactorFunctionFactory.quasiSigmoidDecay(0d, -1d, 2);
     }
-    @Test(expected=OutOfRangeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testQuasiSigmoidDecayPrecondition1() {
         LearningFactorFunctionFactory.quasiSigmoidDecay(1 + 1e-10, -1d, 2);
     }
-    @Test(expected=NumberIsTooLargeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testQuasiSigmoidDecayPrecondition3() {
         LearningFactorFunctionFactory.quasiSigmoidDecay(1d, 0d, 100);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testQuasiSigmoidDecayPrecondition4() {
         LearningFactorFunctionFactory.quasiSigmoidDecay(1d, -1d, 0);
     }
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactoryTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunctionFactoryTest.java
similarity index 82%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactoryTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunctionFactoryTest.java
index 97bb52a..35c7957 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/NeighbourhoodSizeFunctionFactoryTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/NeighbourhoodSizeFunctionFactoryTest.java
@@ -15,10 +15,8 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm;
+package org.apache.commons.math4.neuralnet.sofm;
 
-import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
-import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
 import org.junit.Test;
 import org.junit.Assert;
 
@@ -26,19 +24,19 @@ import org.junit.Assert;
  * Tests for {@link NeighbourhoodSizeFunctionFactory} class.
  */
 public class NeighbourhoodSizeFunctionFactoryTest {
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition1() {
         NeighbourhoodSizeFunctionFactory.exponentialDecay(0, 0, 2);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition2() {
         NeighbourhoodSizeFunctionFactory.exponentialDecay(1, 0, 2);
     }
-    @Test(expected=NumberIsTooLargeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition3() {
         NeighbourhoodSizeFunctionFactory.exponentialDecay(1, 1, 100);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testExponentialDecayPrecondition4() {
         NeighbourhoodSizeFunctionFactory.exponentialDecay(2, 1, 0);
     }
@@ -56,15 +54,15 @@ public class NeighbourhoodSizeFunctionFactoryTest {
         Assert.assertEquals(0, f.value(Long.MAX_VALUE));
     }
 
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testQuasiSigmoidDecayPrecondition1() {
         NeighbourhoodSizeFunctionFactory.quasiSigmoidDecay(0d, -1d, 2);
     }
-    @Test(expected=NumberIsTooLargeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testQuasiSigmoidDecayPrecondition3() {
         NeighbourhoodSizeFunctionFactory.quasiSigmoidDecay(1d, 0d, 100);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testQuasiSigmoidDecayPrecondition4() {
         NeighbourhoodSizeFunctionFactory.quasiSigmoidDecay(1d, -1d, 0);
     }
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/ExponentialDecayFunctionTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/util/ExponentialDecayFunctionTest.java
similarity index 72%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/ExponentialDecayFunctionTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/util/ExponentialDecayFunctionTest.java
index 9615927..fdc7860 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/ExponentialDecayFunctionTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/util/ExponentialDecayFunctionTest.java
@@ -15,10 +15,8 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm.util;
+package org.apache.commons.math4.neuralnet.sofm.util;
 
-import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
-import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
 import org.junit.Test;
 import org.junit.Assert;
 
@@ -26,19 +24,19 @@ import org.junit.Assert;
  * Tests for {@link ExponentialDecayFunction} class
  */
 public class ExponentialDecayFunctionTest {
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testPrecondition1() {
         new ExponentialDecayFunction(0d, 0d, 2);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testPrecondition2() {
         new ExponentialDecayFunction(1d, 0d, 2);
     }
-    @Test(expected=NumberIsTooLargeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testPrecondition3() {
         new ExponentialDecayFunction(1d, 1d, 100);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testPrecondition4() {
         new ExponentialDecayFunction(1d, 0.2, 0);
     }
@@ -50,8 +48,8 @@ public class ExponentialDecayFunctionTest {
         final double valueAtN = 3;
         final ExponentialDecayFunction f = new ExponentialDecayFunction(init, valueAtN, n);
 
-        Assert.assertEquals(init, f.value(0), 0d);
-        Assert.assertEquals(valueAtN, f.value(n), 0d);
-        Assert.assertEquals(0, f.value(Long.MAX_VALUE), 0d);
+        Assert.assertEquals(init, f.applyAsDouble(0), 0d);
+        Assert.assertEquals(valueAtN, f.applyAsDouble(n), 0d);
+        Assert.assertEquals(0, f.applyAsDouble(Long.MAX_VALUE), 0d);
     }
 }
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunctionTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/util/QuasiSigmoidDecayFunctionTest.java
similarity index 72%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunctionTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/util/QuasiSigmoidDecayFunctionTest.java
index 0a18207..c936561 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/sofm/util/QuasiSigmoidDecayFunctionTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/sofm/util/QuasiSigmoidDecayFunctionTest.java
@@ -15,10 +15,8 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.sofm.util;
+package org.apache.commons.math4.neuralnet.sofm.util;
 
-import org.apache.commons.math4.legacy.exception.NotStrictlyPositiveException;
-import org.apache.commons.math4.legacy.exception.NumberIsTooLargeException;
 import org.junit.Test;
 import org.junit.Assert;
 
@@ -26,15 +24,15 @@ import org.junit.Assert;
  * Tests for {@link QuasiSigmoidDecayFunction} class
  */
 public class QuasiSigmoidDecayFunctionTest {
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testPrecondition1() {
         new QuasiSigmoidDecayFunction(0d, -1d, 2);
     }
-    @Test(expected=NumberIsTooLargeException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testPrecondition3() {
         new QuasiSigmoidDecayFunction(1d, 0d, 100);
     }
-    @Test(expected=NotStrictlyPositiveException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testPrecondition4() {
         new QuasiSigmoidDecayFunction(1d, -1d, 0);
     }
@@ -46,9 +44,9 @@ public class QuasiSigmoidDecayFunctionTest {
         final double slope = -1e-1;
         final QuasiSigmoidDecayFunction f = new QuasiSigmoidDecayFunction(init, slope, n);
 
-        Assert.assertEquals(init, f.value(0), 0d);
+        Assert.assertEquals(init, f.applyAsDouble(0), 0d);
         // Very approximate derivative.
-        Assert.assertEquals(slope, f.value(n + 1) - f.value(n), 1e-4);
-        Assert.assertEquals(0, f.value(Long.MAX_VALUE), 0d);
+        Assert.assertEquals(slope, f.applyAsDouble(n + 1) - f.applyAsDouble(n), 1e-4);
+        Assert.assertEquals(0, f.applyAsDouble(Long.MAX_VALUE), 0d);
     }
 }
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/NeuronSquareMesh2DTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/twod/NeuronSquareMesh2DTest.java
similarity index 97%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/NeuronSquareMesh2DTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/twod/NeuronSquareMesh2DTest.java
index 0312c50..da4cc19 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/NeuronSquareMesh2DTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/twod/NeuronSquareMesh2DTest.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod;
+package org.apache.commons.math4.neuralnet.twod;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -29,24 +29,27 @@ import java.util.List;
 import java.util.stream.StreamSupport;
 import java.util.stream.Collectors;
 
-import org.apache.commons.math4.legacy.exception.NumberIsTooSmallException;
-import org.apache.commons.math4.legacy.exception.OutOfRangeException;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializerFactory;
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
-import org.apache.commons.math4.legacy.ml.neuralnet.Neuron;
-import org.apache.commons.math4.legacy.ml.neuralnet.SquareNeighbourhood;
 import org.junit.Assert;
 import org.junit.Test;
 
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+
+import org.apache.commons.math4.neuralnet.FeatureInitializer;
+import org.apache.commons.math4.neuralnet.FeatureInitializerFactory;
+import org.apache.commons.math4.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.Neuron;
+import org.apache.commons.math4.neuralnet.SquareNeighbourhood;
+
 /**
  * Tests for {@link NeuronSquareMesh2D} and {@link Network} functionality for
  * a two-dimensional network.
  */
 public class NeuronSquareMesh2DTest {
-    final FeatureInitializer init = FeatureInitializerFactory.uniform(0, 2);
+    private final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+    private final FeatureInitializer init = FeatureInitializerFactory.uniform(rng, 0, 2);
 
-    @Test(expected=NumberIsTooSmallException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testMinimalNetworkSize1() {
         final FeatureInitializer[] initArray = { init };
 
@@ -56,7 +59,7 @@ public class NeuronSquareMesh2DTest {
                                initArray);
     }
 
-    @Test(expected=NumberIsTooSmallException.class)
+    @Test(expected=IllegalArgumentException.class)
     public void testMinimalNetworkSize2() {
         final FeatureInitializer[] initArray = { init };
 
@@ -710,25 +713,25 @@ public class NeuronSquareMesh2DTest {
         try {
             net.getNeuron(2, 0);
             Assert.fail("exception expected");
-        } catch (OutOfRangeException e) {
+        } catch (IllegalArgumentException e) {
             // Expected.
         }
         try {
             net.getNeuron(0, 2);
             Assert.fail("exception expected");
-        } catch (OutOfRangeException e) {
+        } catch (IllegalArgumentException e) {
             // Expected.
         }
         try {
             net.getNeuron(-1, 0);
             Assert.fail("exception expected");
-        } catch (OutOfRangeException e) {
+        } catch (IllegalArgumentException e) {
             // Expected.
         }
         try {
             net.getNeuron(0, -1);
             Assert.fail("exception expected");
-        } catch (OutOfRangeException e) {
+        } catch (IllegalArgumentException e) {
             // Expected.
         }
     }
diff --git a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/LocationFinderTest.java b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/twod/util/LocationFinderTest.java
similarity index 77%
rename from commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/LocationFinderTest.java
rename to commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/twod/util/LocationFinderTest.java
index 2e0b9a2..c77b544 100644
--- a/commons-math-legacy/src/test/java/org/apache/commons/math4/legacy/ml/neuralnet/twod/util/LocationFinderTest.java
+++ b/commons-math-neuralnet/src/test/java/org/apache/commons/math4/neuralnet/twod/util/LocationFinderTest.java
@@ -15,13 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.commons.math4.legacy.ml.neuralnet.twod.util;
+package org.apache.commons.math4.neuralnet.twod.util;
 
-import org.apache.commons.math4.legacy.ml.neuralnet.Network;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializer;
-import org.apache.commons.math4.legacy.ml.neuralnet.FeatureInitializerFactory;
-import org.apache.commons.math4.legacy.ml.neuralnet.SquareNeighbourhood;
-import org.apache.commons.math4.legacy.ml.neuralnet.twod.NeuronSquareMesh2D;
+import org.apache.commons.rng.UniformRandomProvider;
+import org.apache.commons.rng.simple.RandomSource;
+
+import org.apache.commons.math4.neuralnet.Network;
+import org.apache.commons.math4.neuralnet.FeatureInitializer;
+import org.apache.commons.math4.neuralnet.FeatureInitializerFactory;
+import org.apache.commons.math4.neuralnet.SquareNeighbourhood;
+import org.apache.commons.math4.neuralnet.twod.NeuronSquareMesh2D;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -29,7 +32,8 @@ import org.junit.Test;
  * Test for {@link LocationFinder}.
  */
 public class LocationFinderTest {
-    final FeatureInitializer init = FeatureInitializerFactory.uniform(0, 2);
+    private final UniformRandomProvider rng = RandomSource.create(RandomSource.SPLIT_MIX_64);
+    private final FeatureInitializer init = FeatureInitializerFactory.uniform(rng, 0, 2);
 
     /*
      * Test assumes that the network is
diff --git a/pom.xml b/pom.xml
index 0806bef..e4a8127 100644
--- a/pom.xml
+++ b/pom.xml
@@ -106,6 +106,7 @@
 
   <modules>
     <module>commons-math-legacy</module>
+    <module>commons-math-neuralnet</module>
     <module>commons-math-examples</module>
   </modules>
 
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 0876768..a9f80b2 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -54,6 +54,13 @@ If the output is not quite correct, check for invisible trailing spaces!
     </release>
 
     <release version="4.0" date="XXXX-XX-XX" description="">
+      <action dev="erans" type="update" issue="MATH-1578">
+        ANN codes moved into a dedicated maven module.
+      </action>
+      <action dev="erans" type="update" issue="MATH-1575">
+        Modularization set-up: All codes were moved into a "legacy" maven module.
+        WIP: Functionalities should gradually be moved into dedicated modules.
+      </action>
       <action dev="erans" type="fix" issue="MATH-1565" due-to="Randy Strauss">
         Add context to "OutOfRangeException".
       </action>