diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index cbbaad63..946422aa 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -25,7 +25,7 @@ jobs:
         java-version: '11'
 
     - name: Build docs with Maven
-      run: mvn javadoc:javadoc
+      run: mvn compile javadoc:javadoc
 
     - name: Copy to Documentation Website Location
       run: |
@@ -48,7 +48,7 @@ jobs:
     - name: Commit documentation changes
       run: |
         if [[ `git status --porcelain` ]]; then
-          git config --global user.name 'Vincent A Cicirello'
+          git config --global user.name 'Vincent A. Cicirello'
           git config --global user.email 'cicirello@users.noreply.github.com'
           git add -A
           git commit -m "Automated API website updates."
diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml
index 67cfa062..48f22b96 100644
--- a/.github/workflows/maven-publish.yml
+++ b/.github/workflows/maven-publish.yml
@@ -61,5 +61,6 @@ jobs:
         gh release upload ${TAG} target/${{ env.artifact_name }}-${{ steps.get_version.outputs.VERSION }}.jar
         gh release upload ${TAG} target/${{ env.artifact_name }}-${{ steps.get_version.outputs.VERSION }}-sources.jar
         gh release upload ${TAG} target/${{ env.artifact_name }}-${{ steps.get_version.outputs.VERSION }}-javadoc.jar
+        gh release upload ${TAG} target/${{ env.artifact_name }}-${{ steps.get_version.outputs.VERSION }}-jar-with-dependencies.jar
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.lift.toml b/.lift.toml
index 82f2f296..4f94a375 100644
--- a/.lift.toml
+++ b/.lift.toml
@@ -1,5 +1,7 @@
 build = "mvn"
 
+jdk11 = true
+
 # don't run eslint... it is for js and will detect false positives 
 # in javadoc directories.
 
@@ -23,6 +25,6 @@ ignore = ["PREDICTABLE_RANDOM"]
 
 ignoreFiles = """
 docs/api/jquery/
-tests/
+src/test/
 *.js
 """
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c003767..47fe7899 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [Unreleased] - 2021-09-10
+## [Unreleased] - 2021-09-24
 
 ### Added
 
@@ -19,6 +19,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### CI/CD
 
 
+## [3.0.0] - 2021-09-24
+
+### Changed
+* Minimum supported Java version is now Java 11+.
+* The library now uses Java modules, providing the
+  module `org.cicirello.jpt`, which includes the
+  existing packages `org.cicirello.permutations` and
+  `org.cicirello.sequences`, as well as their
+  subpackages.
+* The randomization and other math utilities, previously
+  contained in the package `org.cicirello.math` and its
+  subpackages have been moved to a new library
+  [&rho;&mu;](https://rho-mu.cicirello.org), within a module
+  `org.cicirello.rho_mu`. That module is now a dependency
+  of jpt, declared with a `requires transitive` to ease the
+  transition for existing users of JPT (they should only 
+  need a `requires org.cicirello.jpt` to get access to
+  that functionality as well).
+* Similar to the above, the package `org.cicirello.util`
+  has been moved out of JPT to a new library
+  [org.cicirello.core](https://core.cicirello.org), within 
+  a module `org.cicirello.core`. The package name of
+  `org.cicirello.util` has been kept. This new module is
+  now a dependency of JPT. And just as in the case with the
+  math and randomization utilities, JPT declares the module
+  requires with `requires transitive` to ease the transition
+  of existing users.
+* We will now also publish a `jar-with-dependencies` for those
+  users of the library who do not use a dependency manager. This
+  fat jar contains the library and all dependencies. This fat
+  jar is not modularized (unlike the regular jar), as Java 
+  only allows a single module per jar. However, it is still 
+  built for a Java 11 target.
+
+
 ## [2.6.5] - 2021-09-10
 
 ### Other
diff --git a/README.md b/README.md
index 403785d2..7afc806c 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,8 @@ Copyright (C) 2018-2021 [Vincent A. Cicirello](https://www.cicirello.org/).
 
 Website: https://jpt.cicirello.org/
 
+API documentation: https://jpt.cicirello.org/api
+
 | __Publications About the Library__ | [![DOI](http://joss.theoj.org/papers/10.21105/joss.00950/status.svg)](https://doi.org/10.21105/joss.00950) |
 | :--- | :--- |
 | __Packages and Releases__ | [![Maven Central](https://img.shields.io/maven-central/v/org.cicirello/jpt.svg?label=Maven%20Central&logo=apachemaven)](https://search.maven.org/artifact/org.cicirello/jpt) [![GitHub release (latest by date)](https://img.shields.io/github/v/release/cicirello/JavaPermutationTools?logo=GitHub)](https://github.com/cicirello/JavaPermutationTools/releases) |
@@ -29,16 +31,13 @@ computation on permutations and sequences. It includes implementations of a vari
 of permutation distance metrics as well as distance metrics on sequences (i.e., Strings, 
 arrays, and other ordered data types). 
 
-## Repository Organization
-
-The GitHub repository is organized as follows:
-* The [/src](src) directory contains all of the source code for JavaPermutationTools.
-* The [/tests](tests) directory contains JUnit test cases for all functionality of the library.
-* The [/docs](docs) directory contains the javadoc documentation in a sub-directory /docs/api. The /docs directory is also the location of the website for the project hosted via GitHub pages at https://jpt.cicirello.org/.
+## Java 11+
 
-## Java 8
+Beginning with version 3.0.0, the library supports Java 11+. Our development process 
+utilizes OpenJDK 11, and all jar files released via Maven Central, GitHub Packages, 
+and GitHub Releases are built with a Java 11 target.
 
-The library supports Java 8 or higher, including both Oracle JDK 8 and OpenJDK 8.
+Versions prior to 3.0.0 previously required Java 8+.
 
 ## Versioning Scheme
 
@@ -73,17 +72,53 @@ comments is not advised. Some of the example in the examples repository are
 based on the experiments from published papers that have either used the library
 directly, or which led to some of the code in the library.
 
+## Java Modules
+
+This library provides a Java module, `org.cicirello.jpt`. To use in your project,
+add the following to your `module-info.java`:
+
+```Java
+module your.module.name.here {
+	requires org.cicirello.jpt;
+}
+```
+
+This module includes the `org.cicirello.permutations` and `org.cicirello.sequences`
+packages as well as their subpackages. See the [API documentation](https://jpt.cicirello.org/api) 
+for details.
+
+Beginning with version 3.0.0, randomization and other math utilities, and some
+generic utilities, have been
+moved to a pair of new libraries [&rho;&mu;](https://github.com/cicirello/rho-mu)
+and [org.cicirello.core](https://github.com/cicirello/core), which are
+now dependencies of JavaPermutationTools. Your dependency manager (see next section)
+will handle downloading these for you. 
+
+To ease the transition of users of the library who may have been relying on those
+utilities, we have configured the `module-info.java` for the `org.cicirello.jpt` module to
+require these transitively so that your application should only need to
+require `org.cicirello.jpt` to access the functionality of those new modules.
+However, it is possible that you __may__ actually need the following to access
+some of that functionality, such as the RandomIndexer class, or other math or utility classes:
+
+```Java
+module your.module.name.here {
+	requires org.cicirello.jpt;
+	requires org.cicirello.rho_mu;
+	requires org.cicirello.core;
+}
+```
+
 ## Importing the Library from Maven Central
 
 Add this to the dependencies section of your pom.xml, replacing the version number 
-with the version you want to use (note that the library has been available in Maven
-Central since version 2.1.2).
+with the version you want to use.
 
 ```XML
 <dependency>
   <groupId>org.cicirello</groupId>
   <artifactId>jpt</artifactId>
-  <version>2.6.0</version>
+  <version>3.0.0</version>
 </dependency>
 ```
 
@@ -109,13 +144,20 @@ If you don't use a dependency manager that supports importing from Maven Central
 or if you simply prefer to download manually, prebuilt jars are also attached to 
 each [GitHub Release](https://github.com/cicirello/JavaPermutationTools).
 
+In addition to the regular jar of the library, we also regularly publish a
+`jar-with-dependencies`. The `jar-with-dependencies` is built for a Java 11 target,
+but does not contain any module declarations (unlike the regular jar file which does).
+Therefore, the `jar-with-dependencies` should be used on the classpath. 
+
 ## License
 
 The JPT library is licensed under the [GNU General Public License 3.0](https://www.gnu.org/licenses/gpl-3.0.en.html).
 
-
 ## Contribute
 
-Report bugs, suggestions, feature requests, etc via the [issues tracker](https://github.com/cicirello/JavaPermutationTools/issues).  If you would 
-like to directly contribute new code, you may also fork the repository, 
-and create pull requests.
+If you would like to contribute in any way, such 
+as reporting bugs, suggesting new functionality, or code contributions 
+such as bug fixes or implementations of new functionality, then start 
+by reading the [contribution guidelines](https://github.com/cicirello/.github/blob/main/CONTRIBUTING.md).
+This project has adopted 
+the [Contributor Covenant Code of Conduct](https://github.com/cicirello/.github/blob/main/CODE_OF_CONDUCT.md).
diff --git a/docs/index.html b/docs/index.html
index 6a15bad3..8c338d19 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -187,12 +187,35 @@ <h3>Importing from Maven Central</h3>
 <pre><code>&lt;dependency&gt;
   &lt;groupId&gt;org.cicirello&lt;/groupId&gt;
   &lt;artifactId&gt;jpt&lt;/artifactId&gt;
-  &lt;version&gt;2.6.1&lt;/version&gt;
+  &lt;version&gt;3.0.0&lt;/version&gt;
 &lt;/dependency&gt;
 </code></pre>
 <p>Just replace the version number with your desired version of the library.</p>
 </section>
 <section>
+<h3>Java Modules</h3>
+<p>The library provides a Java module, org.cicirello.jpt. To use in your project, add the 
+following to your module-info.java:</p>
+<pre><code>
+module your.module.name.here {
+    requires org.cicirello.jpt;
+}
+</code></pre>
+<p>This module includes the org.cicirello.permutations and org.cicirello.sequences
+packages as well as their subpackages. See the <a href="https://jpt.cicirello.org/api">API documentation</a> 
+for details.</p>
+<p>Beginning with version 3.0.0, randomization and other math utilities, and some
+generic utilities, have been moved to a pair of new 
+libraries <a href="https://rho-mu.cicirello.org/">&rho;&mu;</a>
+and <a href="https://core.cicirello.org/">org.cicirello.core</a>, which are
+now dependencies of JavaPermutationTools. Your dependency manager (see previous section)
+will handle downloading these for you.</p>
+<p>To ease the transition of users of the library who may have been relying on those
+utilities, we have configured the module-info.java for the org.cicirello.jpt module to
+require these transitively so that your application should only need to
+require org.cicirello.jpt to access the functionality of those new modules.</p>
+</section>
+<section>
 <h3>Semantic Versioning</h3>
 <p>The JPT uses <a href="https://semver.org/">Semantic Versioning</a> with 
 version numbers of the form: MAJOR.MINOR.PATCH, where differences in MAJOR 
diff --git a/pom.xml b/pom.xml
index 9347164e..4f672313 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
  
 	<groupId>org.cicirello</groupId>
 	<artifactId>jpt</artifactId>
-	<version>2.2.0</version>
+	<version>3.0.0</version>
 	<packaging>jar</packaging>
 
 	<name>JavaPermutationTools</name>
@@ -172,36 +172,31 @@
 			<scope>test</scope>
 			<optional>true</optional>
 		</dependency>
+		<dependency>
+			<groupId>org.cicirello</groupId>
+			<artifactId>rho-mu</artifactId>
+			<version>1.1.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.cicirello</groupId>
+			<artifactId>core</artifactId>
+			<version>1.0.0</version>
+		</dependency>
 	</dependencies>
   
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-		<maven.compiler.source>1.8</maven.compiler.source>
-		<maven.compiler.target>1.8</maven.compiler.target>
+		<maven.compiler.release>11</maven.compiler.release>
 	</properties>
   
 	<build>
-		<sourceDirectory>${project.basedir}/src</sourceDirectory>
-		<scriptSourceDirectory>${project.basedir}/src/scripts</scriptSourceDirectory>
-		<testSourceDirectory>${project.basedir}/tests</testSourceDirectory>
-		<resources>
-			<resource>
-				<directory>${project.basedir}/src/resources</directory>
-			</resource>
-		</resources>
-		<testResources>
-			<testResource>
-				<directory>${project.basedir}/tests/resources</directory>
-			</testResource>
-		</testResources>
 		<plugins>
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<version>3.8.1</version>
 				<configuration>
-					<source>1.8</source>
-					<target>1.8</target>
+					<release>11</release>
 				</configuration>
 			</plugin>
 			<plugin>
@@ -230,21 +225,17 @@
 					</execution>
 				</executions>
 				<configuration>
-					<source>1.8</source>
 					<failOnWarnings>true</failOnWarnings>
 					<failOnError>true</failOnError>
-					<additionalJOptions>
-						<additionalJOption>--no-module-directories</additionalJOption>
-					</additionalJOptions>
 					<windowtitle>JavaPermutationTools - A Java library for computation on permutations</windowtitle>
 					<doctitle>JavaPermutationTools - A Java library for computation on permutations</doctitle>
 					<author>false</author>
 					<version>false</version>
 					<nosince>true</nosince>
-					<overview>${project.build.sourceDirectory}/overview.html</overview>
 					<notimestamp>true</notimestamp>
 					<links>
-						<link>https://docs.oracle.com/javase/8/docs/api</link>
+						<link>https://rho-mu.cicirello.org/api</link>
+						<link>https://core.cicirello.org/api</link>
 					</links>
 					<bottom><![CDATA[Copyright &copy; 2005-2021 <a href=\"https://www.cicirello.org/\" target=_top>Vincent A. Cicirello</a>.  All rights reserved.]]></bottom>
 				</configuration>
@@ -263,6 +254,43 @@
 					</includes>
 				</configuration>
 			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-shade-plugin</artifactId>
+				<version>3.2.4</version>
+				<configuration>
+					<shadedArtifactAttached>true</shadedArtifactAttached>
+					<shadedClassifierName>jar-with-dependencies</shadedClassifierName>
+					<filters>
+						<filter>
+							<artifact>*:*</artifact>
+							<excludes>
+								<exclude>module-info.class</exclude>
+							</excludes>
+						</filter>
+						<filter>
+							<artifact>org.cicirello:core</artifact>
+							<excludes>
+								<exclude>META-INF/MANIFEST.MF</exclude>
+							</excludes>
+						</filter>
+						<filter>
+							<artifact>org.cicirello:rho-mu</artifact>
+							<excludes>
+								<exclude>META-INF/MANIFEST.MF</exclude>
+							</excludes>
+						</filter>
+					</filters>
+				</configuration>
+				<executions>
+					<execution>
+						<phase>package</phase>
+						<goals>
+							<goal>shade</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
 		</plugins>
 	</build>  
 </project>
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644
index 00000000..9c82b1db
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,62 @@
+/*
+ * JavaPermutationTools - A Java library for computation on permutations.
+ * Copyright 2005-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
+ *
+ * JavaPermutationTools is free software: you can 
+ * redistribute it and/or modify it under the terms of the GNU 
+ * General Public License as published by the Free Software 
+ * Foundation, either version 3 of the License, or (at your 
+ * option) any later version.
+ *
+ * JavaPermutationTools is distributed in the hope 
+ * that it will be useful, but WITHOUT ANY WARRANTY; without even 
+ * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
+ * PARTICULAR PURPOSE.  See the GNU General Public License for more 
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * <h2>JavaPermutationTools - A Java library for computation on permutations</h2>
+ * 
+ * <p>JavaPermutationTools (JPT) is a Java library for representing and generating permutations 
+ * and sequences, as well as performing computation on permutations and sequences. This includes 
+ * implementations of a variety of permutation distance metrics as well as distance metrics on 
+ * sequences (i.e., Strings, arrays, and other ordered data types).</p>
+ *
+ * <p>The <a href="https://github.com/cicirello/JavaPermutationTools" target=_top>source code</a> 
+ * is hosted on GitHub; and is licensed under the 
+ * <a href="https://www.gnu.org/licenses/gpl-3.0.html" target=_top>GNU General Public License 
+ * Version 3 (GPLv3)</a>. The library's jar files are regularly published 
+ * to <a href="https://search.maven.org/artifact/org.cicirello/jpt" target=_top>Maven Central</a>, 
+ * from which it is easily imported into software projects using Maven and other 
+ * commonly used build tools.  Additionally, there is a 
+ * GitHub <a href="https://github.com/cicirello/jpt-examples" target=_top>repository of 
+ * example programs</a> that show basic usage of the JPT library, as well as replication programs that 
+ * reproduce results found in published papers.</p>
+ *
+ * <p>For more information see the <a href="https://jpt.cicirello.org/" target=_top>JavaPermutationTools 
+ * website</a>. Potential contributors, please see the source repository, and/or 
+ * contact <a href="https://www.cicirello.org/" target=_top>Vincent A. Cicirello</a>.</p>
+ *
+ * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a>
+ */
+module org.cicirello.jpt {
+	exports org.cicirello.permutations;
+	exports org.cicirello.permutations.distance;
+	exports org.cicirello.sequences;
+	exports org.cicirello.sequences.distance;
+	
+	// Specified transitive on these to minimize the impact
+	// of the introduction of modules on users' existing code
+	// that might assume access to the classes/interfaces from these
+	// modules. Strictly speaking, these do not need to be transitive
+	// for jpt because jpt's public interface does not depend on anything
+	// in these (e.g., no objects of any classes from these modules are
+	// returned or expected as parameters).
+	requires transitive org.cicirello.rho_mu;
+	requires transitive org.cicirello.core;
+}
\ No newline at end of file
diff --git a/src/org/cicirello/permutations/Permutation.java b/src/main/java/org/cicirello/permutations/Permutation.java
similarity index 99%
rename from src/org/cicirello/permutations/Permutation.java
rename to src/main/java/org/cicirello/permutations/Permutation.java
index 54bc2d51..e1413804 100644
--- a/src/org/cicirello/permutations/Permutation.java
+++ b/src/main/java/org/cicirello/permutations/Permutation.java
@@ -205,6 +205,10 @@ public Permutation(Permutation p, int length) {
 		}
 	}
 	
+	/**
+	 * Creates an identical copy of this object.
+	 * @return an identical copy of this object
+	 */
 	@Override
 	public Permutation copy() {
 		return new Permutation(this);
diff --git a/src/org/cicirello/permutations/PermutationIterator.java b/src/main/java/org/cicirello/permutations/PermutationIterator.java
similarity index 100%
rename from src/org/cicirello/permutations/PermutationIterator.java
rename to src/main/java/org/cicirello/permutations/PermutationIterator.java
diff --git a/src/org/cicirello/permutations/distance/AcyclicEdgeDistance.java b/src/main/java/org/cicirello/permutations/distance/AcyclicEdgeDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/AcyclicEdgeDistance.java
rename to src/main/java/org/cicirello/permutations/distance/AcyclicEdgeDistance.java
diff --git a/src/org/cicirello/permutations/distance/BlockInterchangeDistance.java b/src/main/java/org/cicirello/permutations/distance/BlockInterchangeDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/BlockInterchangeDistance.java
rename to src/main/java/org/cicirello/permutations/distance/BlockInterchangeDistance.java
diff --git a/src/org/cicirello/permutations/distance/CyclicEdgeDistance.java b/src/main/java/org/cicirello/permutations/distance/CyclicEdgeDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/CyclicEdgeDistance.java
rename to src/main/java/org/cicirello/permutations/distance/CyclicEdgeDistance.java
diff --git a/src/org/cicirello/permutations/distance/CyclicIndependentDistance.java b/src/main/java/org/cicirello/permutations/distance/CyclicIndependentDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/CyclicIndependentDistance.java
rename to src/main/java/org/cicirello/permutations/distance/CyclicIndependentDistance.java
diff --git a/src/org/cicirello/permutations/distance/CyclicIndependentDistanceDouble.java b/src/main/java/org/cicirello/permutations/distance/CyclicIndependentDistanceDouble.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/CyclicIndependentDistanceDouble.java
rename to src/main/java/org/cicirello/permutations/distance/CyclicIndependentDistanceDouble.java
diff --git a/src/org/cicirello/permutations/distance/CyclicRTypeDistance.java b/src/main/java/org/cicirello/permutations/distance/CyclicRTypeDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/CyclicRTypeDistance.java
rename to src/main/java/org/cicirello/permutations/distance/CyclicRTypeDistance.java
diff --git a/src/org/cicirello/permutations/distance/CyclicReversalIndependentDistance.java b/src/main/java/org/cicirello/permutations/distance/CyclicReversalIndependentDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/CyclicReversalIndependentDistance.java
rename to src/main/java/org/cicirello/permutations/distance/CyclicReversalIndependentDistance.java
diff --git a/src/org/cicirello/permutations/distance/CyclicReversalIndependentDistanceDouble.java b/src/main/java/org/cicirello/permutations/distance/CyclicReversalIndependentDistanceDouble.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/CyclicReversalIndependentDistanceDouble.java
rename to src/main/java/org/cicirello/permutations/distance/CyclicReversalIndependentDistanceDouble.java
diff --git a/src/org/cicirello/permutations/distance/DeviationDistance.java b/src/main/java/org/cicirello/permutations/distance/DeviationDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/DeviationDistance.java
rename to src/main/java/org/cicirello/permutations/distance/DeviationDistance.java
diff --git a/src/org/cicirello/permutations/distance/DeviationDistanceNormalized.java b/src/main/java/org/cicirello/permutations/distance/DeviationDistanceNormalized.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/DeviationDistanceNormalized.java
rename to src/main/java/org/cicirello/permutations/distance/DeviationDistanceNormalized.java
diff --git a/src/org/cicirello/permutations/distance/DeviationDistanceNormalized2005.java b/src/main/java/org/cicirello/permutations/distance/DeviationDistanceNormalized2005.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/DeviationDistanceNormalized2005.java
rename to src/main/java/org/cicirello/permutations/distance/DeviationDistanceNormalized2005.java
diff --git a/src/org/cicirello/permutations/distance/EditDistance.java b/src/main/java/org/cicirello/permutations/distance/EditDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/EditDistance.java
rename to src/main/java/org/cicirello/permutations/distance/EditDistance.java
diff --git a/src/org/cicirello/permutations/distance/ExactMatchDistance.java b/src/main/java/org/cicirello/permutations/distance/ExactMatchDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/ExactMatchDistance.java
rename to src/main/java/org/cicirello/permutations/distance/ExactMatchDistance.java
diff --git a/src/org/cicirello/permutations/distance/InterchangeDistance.java b/src/main/java/org/cicirello/permutations/distance/InterchangeDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/InterchangeDistance.java
rename to src/main/java/org/cicirello/permutations/distance/InterchangeDistance.java
diff --git a/src/org/cicirello/permutations/distance/KendallTauDistance.java b/src/main/java/org/cicirello/permutations/distance/KendallTauDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/KendallTauDistance.java
rename to src/main/java/org/cicirello/permutations/distance/KendallTauDistance.java
diff --git a/src/org/cicirello/permutations/distance/LeeDistance.java b/src/main/java/org/cicirello/permutations/distance/LeeDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/LeeDistance.java
rename to src/main/java/org/cicirello/permutations/distance/LeeDistance.java
diff --git a/src/org/cicirello/permutations/distance/NormalizedPermutationDistanceMeasurer.java b/src/main/java/org/cicirello/permutations/distance/NormalizedPermutationDistanceMeasurer.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/NormalizedPermutationDistanceMeasurer.java
rename to src/main/java/org/cicirello/permutations/distance/NormalizedPermutationDistanceMeasurer.java
diff --git a/src/org/cicirello/permutations/distance/NormalizedPermutationDistanceMeasurerDouble.java b/src/main/java/org/cicirello/permutations/distance/NormalizedPermutationDistanceMeasurerDouble.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/NormalizedPermutationDistanceMeasurerDouble.java
rename to src/main/java/org/cicirello/permutations/distance/NormalizedPermutationDistanceMeasurerDouble.java
diff --git a/src/org/cicirello/permutations/distance/PermutationDistanceMeasurer.java b/src/main/java/org/cicirello/permutations/distance/PermutationDistanceMeasurer.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/PermutationDistanceMeasurer.java
rename to src/main/java/org/cicirello/permutations/distance/PermutationDistanceMeasurer.java
diff --git a/src/org/cicirello/permutations/distance/PermutationDistanceMeasurerDouble.java b/src/main/java/org/cicirello/permutations/distance/PermutationDistanceMeasurerDouble.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/PermutationDistanceMeasurerDouble.java
rename to src/main/java/org/cicirello/permutations/distance/PermutationDistanceMeasurerDouble.java
diff --git a/src/org/cicirello/permutations/distance/RTypeDistance.java b/src/main/java/org/cicirello/permutations/distance/RTypeDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/RTypeDistance.java
rename to src/main/java/org/cicirello/permutations/distance/RTypeDistance.java
diff --git a/src/org/cicirello/permutations/distance/ReinsertionDistance.java b/src/main/java/org/cicirello/permutations/distance/ReinsertionDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/ReinsertionDistance.java
rename to src/main/java/org/cicirello/permutations/distance/ReinsertionDistance.java
diff --git a/src/org/cicirello/permutations/distance/ReversalDistance.java b/src/main/java/org/cicirello/permutations/distance/ReversalDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/ReversalDistance.java
rename to src/main/java/org/cicirello/permutations/distance/ReversalDistance.java
diff --git a/src/org/cicirello/permutations/distance/ReversalIndependentDistance.java b/src/main/java/org/cicirello/permutations/distance/ReversalIndependentDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/ReversalIndependentDistance.java
rename to src/main/java/org/cicirello/permutations/distance/ReversalIndependentDistance.java
diff --git a/src/org/cicirello/permutations/distance/ReversalIndependentDistanceDouble.java b/src/main/java/org/cicirello/permutations/distance/ReversalIndependentDistanceDouble.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/ReversalIndependentDistanceDouble.java
rename to src/main/java/org/cicirello/permutations/distance/ReversalIndependentDistanceDouble.java
diff --git a/src/org/cicirello/permutations/distance/ScrambleDistance.java b/src/main/java/org/cicirello/permutations/distance/ScrambleDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/ScrambleDistance.java
rename to src/main/java/org/cicirello/permutations/distance/ScrambleDistance.java
diff --git a/src/org/cicirello/permutations/distance/SquaredDeviationDistance.java b/src/main/java/org/cicirello/permutations/distance/SquaredDeviationDistance.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/SquaredDeviationDistance.java
rename to src/main/java/org/cicirello/permutations/distance/SquaredDeviationDistance.java
diff --git a/src/org/cicirello/permutations/distance/package-info.java b/src/main/java/org/cicirello/permutations/distance/package-info.java
similarity index 100%
rename from src/org/cicirello/permutations/distance/package-info.java
rename to src/main/java/org/cicirello/permutations/distance/package-info.java
diff --git a/src/org/cicirello/permutations/package-info.java b/src/main/java/org/cicirello/permutations/package-info.java
similarity index 100%
rename from src/org/cicirello/permutations/package-info.java
rename to src/main/java/org/cicirello/permutations/package-info.java
diff --git a/src/org/cicirello/sequences/SequenceSampler.java b/src/main/java/org/cicirello/sequences/SequenceSampler.java
similarity index 100%
rename from src/org/cicirello/sequences/SequenceSampler.java
rename to src/main/java/org/cicirello/sequences/SequenceSampler.java
diff --git a/src/org/cicirello/sequences/distance/EditDistance.java b/src/main/java/org/cicirello/sequences/distance/EditDistance.java
similarity index 100%
rename from src/org/cicirello/sequences/distance/EditDistance.java
rename to src/main/java/org/cicirello/sequences/distance/EditDistance.java
diff --git a/src/org/cicirello/sequences/distance/ExactMatchDistance.java b/src/main/java/org/cicirello/sequences/distance/ExactMatchDistance.java
similarity index 100%
rename from src/org/cicirello/sequences/distance/ExactMatchDistance.java
rename to src/main/java/org/cicirello/sequences/distance/ExactMatchDistance.java
diff --git a/src/org/cicirello/sequences/distance/KendallTauSequenceDistance.java b/src/main/java/org/cicirello/sequences/distance/KendallTauSequenceDistance.java
similarity index 100%
rename from src/org/cicirello/sequences/distance/KendallTauSequenceDistance.java
rename to src/main/java/org/cicirello/sequences/distance/KendallTauSequenceDistance.java
diff --git a/src/org/cicirello/sequences/distance/LongestCommonSubsequenceDistance.java b/src/main/java/org/cicirello/sequences/distance/LongestCommonSubsequenceDistance.java
similarity index 100%
rename from src/org/cicirello/sequences/distance/LongestCommonSubsequenceDistance.java
rename to src/main/java/org/cicirello/sequences/distance/LongestCommonSubsequenceDistance.java
diff --git a/src/org/cicirello/sequences/distance/SequenceDistanceMeasurer.java b/src/main/java/org/cicirello/sequences/distance/SequenceDistanceMeasurer.java
similarity index 100%
rename from src/org/cicirello/sequences/distance/SequenceDistanceMeasurer.java
rename to src/main/java/org/cicirello/sequences/distance/SequenceDistanceMeasurer.java
diff --git a/src/org/cicirello/sequences/distance/SequenceDistanceMeasurerDouble.java b/src/main/java/org/cicirello/sequences/distance/SequenceDistanceMeasurerDouble.java
similarity index 100%
rename from src/org/cicirello/sequences/distance/SequenceDistanceMeasurerDouble.java
rename to src/main/java/org/cicirello/sequences/distance/SequenceDistanceMeasurerDouble.java
diff --git a/src/org/cicirello/sequences/distance/package-info.java b/src/main/java/org/cicirello/sequences/distance/package-info.java
similarity index 100%
rename from src/org/cicirello/sequences/distance/package-info.java
rename to src/main/java/org/cicirello/sequences/distance/package-info.java
diff --git a/src/org/cicirello/sequences/package-info.java b/src/main/java/org/cicirello/sequences/package-info.java
similarity index 100%
rename from src/org/cicirello/sequences/package-info.java
rename to src/main/java/org/cicirello/sequences/package-info.java
diff --git a/src/org/cicirello/math/MathFunctions.java b/src/org/cicirello/math/MathFunctions.java
deleted file mode 100644
index f3e9b030..00000000
--- a/src/org/cicirello/math/MathFunctions.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * Copyright 2019-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package org.cicirello.math;
-
-/**
- * MathFunctions is a class of utility methods that implement various mathematical functions.
- *
- * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a> 
- * @version 5.13.2021
- *
- */
-public final class MathFunctions {
-	
-	/*
-	 * Utility class of static methods, so constructor is private to prevent
-	 * instantiation.
-	 */
-	private MathFunctions() {}
-	
-	/**
-	 * Computes a<sup>b</sup>, where the exponent b is an integer.
-	 * This is more efficient than using {@link Math#pow} since
-	 * it exploits the integer type of the exponent.
-	 * @param a The base.
-	 * @param b The exponent.
-	 * @return a<sup>b</sup>
-	 */
-	public static double pow(double a, int b) {
-		if (b >= 0) {
-			double c = 1;
-			while (b > 0) {
-				if ((b & 1) == 1) c *= a;
-				b = b >> 1;
-				a = a * a;
-			}
-			return c;
-		} else {
-			return 1.0 / pow(a, -b);
-		} 
-	}
-	
-	/**
-	 * Implementation of the natural log of the absolute value of
-	 * the gamma function.
-	 * @param n input parameter to the function
-	 * @return ln(abs(gamma(n)))
-	 */
-	public static double logGamma(double n) {
-		if (!Double.isFinite(n)) return n;
-		if (n >= 2.556348e305) {
-			// input parameter is too large to calculate function
-			return Double.POSITIVE_INFINITY;
-		} else if (n <= -2.556348e305) {
-			// input parameter is too large to calculate function
-			return Double.NEGATIVE_INFINITY;
-		} else if (n < -34.0) {
-			if (n < -4.5035996273704955e15) {
-				// inputs lower than this are essentially negative integers
-				// due to precision of floating-point numbers.
-				// This is the smallest negative double with an ulp less than 1.
-				return Double.POSITIVE_INFINITY;
-			}
-			n = -n; 
-			double p = ((long)n);
-			if (p == n) return Double.POSITIVE_INFINITY;
-			double z = n - p;
-			if (z > 0.5) {
-				p = p + 1.0;
-				z = p - n;
-			}
-			z = n * Math.sin(z * Math.PI);
-			// This check doesn't seem to be necessary given addition of first check in block.
-			// if (z == 0.0) return Double.POSITIVE_INFINITY;
-			// ln(PI)
-			final double LOG_PI = 1.1447298858494002;
-			return LOG_PI - Math.log(z) - logGamma(n);
-		} else if (n < 13.0) {
-			double z = 1.0;
-			int p = 0;
-			double u = n;
-			while (u >= 3.0) {
-				p--;
-				u = n + p;
-				z = z * u;
-			}
-			while (u < 2.0) {
-				if (u == 0.0) return Double.POSITIVE_INFINITY;
-				z = z / u;
-				p++;
-				u = n + p;
-			}
-			if (z < 0.0) z = -z;
-			if (u == 2.0) return Math.log(z);
-			p -= 2;
-			n = n + p; 
-			return Math.log(z) + 
-				n * evaluatePolynomial(n, POLY_APPROX_2) / evaluatePolynomial(n, POLY_APPROX_3);
-		} else {
-			// ln(sqrt(2pi))
-			final double LOG_SQRT_PI2 = 0.9189385332046727;
-			double q = (n - 0.5) * Math.log(n) - n + LOG_SQRT_PI2; 
-			if (n > 1.0e8) return q;
-			double p = 1.0 / (n * n);
-			if (n >= 1000.0) {
-				q = q + ((7.936507936507937E-4 * p - 0.002777777777777778) 
-					* p + 0.08333333333333333) / n;
-			} else {
-				q += evaluatePolynomial(p, STIRLING_EXPANSION_LN_GAMMA) / n;
-			}
-			return q;
-		}
-	}
-	
-	// HELPERS FOR logGamma BEGIN HERE
-	
-	private static double evaluatePolynomial(double x, double polynomial[]) {
-		double result = polynomial[0];
-		for (int i = 1; i < polynomial.length; i++) {
-			result = result * x + polynomial[i];
-		}
-		return result;
-	}
-	
-	private static final double[] STIRLING_EXPANSION_LN_GAMMA = {
-		8.116141674705085E-4, -5.950619042843014E-4,
-		7.936503404577169E-4, -0.002777777777300997,
-		0.08333333333333319
-	};
-
-	private static final double[] POLY_APPROX_2 = {
-		-1378.2515256912086, -38801.631513463784,
-		-331612.9927388712, -1162370.974927623,
-		-1721737.0082083966, -853555.6642457654
-	};
-
-	private static final double[] POLY_APPROX_3 = {
-		1.0, 
-		-351.81570143652345, -17064.210665188115,
-		-220528.59055385445, -1139334.4436798252,
-		-2532523.0717758294, -2018891.4143353277,
-	};
-	
-	// HELPERS FOR logGamma END HERE
-
-}
\ No newline at end of file
diff --git a/src/org/cicirello/math/la/JacobiDiagonalization.java b/src/org/cicirello/math/la/JacobiDiagonalization.java
deleted file mode 100644
index 10ccb723..00000000
--- a/src/org/cicirello/math/la/JacobiDiagonalization.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright 2018, 2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package org.cicirello.math.la;
-
-/**
- * This class uses Jacobi iteration to compute the eigenvalues and eigenvectors of a symmetric matrix, provided as a 2-D Java array.
- *
- * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a>  
- * @version 2.15.2021
- */
-public class JacobiDiagonalization {
-	
-	/**
-	 * Default precision level for convergence check.
-	 */
-	public static final double EPSILON = 1e-5;
-	
-	/**
-	 * Default max number of iterations.
-	 */
-	public static final int MAX_ITERATIONS = 10000000;
-	
-	private double[][] a;
-	private final int N;
-	private double[][] eigenvectors;
-	private double[] eigenvalues;
-	
-	
-	/**
-	 * Initializes a Jacobi iteration for finding eigenvalues and eigenvectors of a matrix.
-	 * @param matrix a square matrix
-	 */
-	public JacobiDiagonalization(int[][] matrix) {
-		if (matrix.length != matrix[0].length) throw new IllegalArgumentException("Must be a square matrix.");
-		N = matrix.length;
-		a = new double[N][N];
-		for (int i = 0; i < N; i++) {
-			for (int j = 0; j < N; j++) {
-				a[i][j] = matrix[i][j];
-			}
-		}
-	}
-	
-	/**
-	 * Initializes a Jacobi iteration for finding eigenvalues and eigenvectors of a matrix.
-	 * @param matrix a square matrix
-	 */
-	public JacobiDiagonalization(double[][] matrix) {
-		if (matrix.length != matrix[0].length) throw new IllegalArgumentException("Must be a square matrix.");
-		N = matrix.length;
-		a = new double[N][N];
-		for (int i = 0; i < N; i++) {
-			for (int j = 0; j < N; j++) {
-				a[i][j] = matrix[i][j];
-			}
-		}
-	}
-	
-	/**
-	 * Gets the computed eigenvectors.
-	 * @return The eigenvectors are the columns of the returned matrix.  Returns null if not yet computed.
-	 */
-	public double[][] eigenvectors() {
-		return eigenvectors;
-	}
-	
-	/**
-	 * Gets the computed eigenvalues.
-	 * @return The eigenvalues.  Returns null if not yet computed.
-	 */
-	public double[] eigenvalues() {
-		return eigenvalues;
-	}
-	
-	/**
-	 * Computes the eigenvalues and eigenvectors using Jacobi Iteration.  Uses the default precision level JacobiDiagonalization.EPSILON
-	 * where off-diagonal elements less than JacobiDiagonalization.EPSILON in absolute value are set to 0.
-	 * Uses the default max number of iterations JacobiDiagonalization.MAX_ITERATIONS.
-	 * @return true if Jacobi method converged, and false if terminated due to maximum number of iterations
-	 */
-	public boolean compute() {
-		return compute(EPSILON, MAX_ITERATIONS);
-	}
-	
-	/**
-	 * Computes the eigenvalues and eigenvectors using Jacobi Iteration.  Uses the default max number of iterations JacobiDiagonalization.MAX_ITERATIONS.
-	 *
-	 * @param epsilon Precision level, where off-diagonal elements less than epsilon in absolute value are set to 0.
-	 * @return true if Jacobi method converged, and false if terminated due to maximum number of iterations
-	 */
-	public boolean compute(double epsilon) {
-		return compute(epsilon, MAX_ITERATIONS);
-	}
-	
-	/**
-	 * Computes the eigenvalues and eigenvectors using Jacobi Iteration.  Uses the default precision level JacobiDiagonalization.EPSILON
-	 * where off-diagonal elements less than JacobiDiagonalization.EPSILON in absolute value are set to 0. 
-	 *
-	 * @param maxIters Maximum number of iterations.
-	 * @return true if Jacobi method converged, and false if terminated due to maximum number of iterations
-	 */
-	public boolean compute(int maxIters) {
-		return compute(EPSILON, maxIters);
-	}
-	
-	/**
-	 * Computes the eigenvalues and eigenvectors using Jacobi Iteration.  
-	 *
-	 * @param epsilon Precision level, where off-diagonal elements less than epsilon in absolute value are set to 0.
-	 * @param maxIters Maximum number of iterations.
-	 * @return true if Jacobi method converged, and false if terminated due to maximum number of iterations
-	 */
-	public boolean compute(double epsilon, int maxIters) {
-		for (int i = 0; i < maxIters; i++) {
-			if (oneIteration(epsilon)) {
-				return true;
-			}
-		}
-		return false;
-	}
-	
-	private boolean oneIteration(double epsilon) {
-		// find largest in absolute value element not on main diagonal
-		int p = -1;
-		int q = -1;
-		double a_pq = 0;
-		double sumOffDiagonal = 0;
-		for (int i = 0; i < N; i++) {
-			for (int j = 0; j < N; j++) {
-				if (i==j) continue;
-				double absAij = Math.abs(a[i][j]);
-				if (absAij > 0 && absAij < epsilon) {
-					// zero out any small entries
-					absAij = a[i][j] = 0.0;
-				}
-				if (p < 0 || absAij > a_pq) {
-					p=i;
-					q=j;
-					a_pq = absAij;
-				}
-				sumOffDiagonal = sumOffDiagonal + absAij;
-			}
-		}
-		// check for convergence
-		if (sumOffDiagonal == 0.0) {
-			if (eigenvalues == null) {
-				eigenvalues = new double[N];
-				for (int i = 0; i < N; i++) {
-					eigenvalues[i] = a[i][i];
-				}
-			}
-			if (eigenvectors == null) {
-				eigenvectors = copy(a);
-			}
-			return true;
-		}
-		/* This appears to be an impossibility, so I'm removing it.
-		// If a_pq is small, simply set entry to 0 and don't rotate.
-		if (a_pq < epsilon) {
-			a[p][q] = 0;
-			return false;
-		}
-		*/
-		// compute some values
-		double f = -a_pq;
-		double g = 0.5*(a[p][p]-a[q][q]);
-		double g_sign = g < 0 ? -1 : 1;
-		double h = g_sign * f / Math.sqrt(f*f + g*g);
-		double sin_theta = h / Math.sqrt(2*(1+Math.sqrt(1-h*h)));
-		double cos_theta = Math.sqrt(1 - sin_theta * sin_theta);
-		// compute rotation 
-		double e_pp = cos_theta;
-		double e_qq = cos_theta;
-		double e_pq = sin_theta;
-		double e_qp = -sin_theta;
-		// update eigenvector approximations
-		if (eigenvectors == null) {
-			eigenvectors = new double[N][N];
-			for (int i = 0; i < N; i++) {
-				eigenvectors[i][i] = 1;
-			}
-			eigenvectors[p][p] = eigenvectors[q][q] = cos_theta;
-			eigenvectors[p][q] = sin_theta;
-			eigenvectors[q][p] = -sin_theta;
-		} else {
-			double[][] temp = new double[N][2];
-			for (int i = 0; i < N; i++) {
-				temp[i][0] = eigenvectors[i][p]*e_pp + eigenvectors[i][q]*e_qp;
-				temp[i][1] = eigenvectors[i][p]*e_pq + eigenvectors[i][q]*e_qq;
-			}
-			for (int i = 0; i < N; i++) {
-				eigenvectors[i][p] = temp[i][0];
-				eigenvectors[i][q] = temp[i][1];
-			}
-		}
-		// rotate A
-		double[][] temp = new double[N][2];
-		for (int i = 0; i < N; i++) {
-			temp[i][0] = a[i][p]*e_pp + a[i][q]*e_qp;
-			temp[i][1] = a[i][p]*e_pq + a[i][q]*e_qq;
-		}
-		for (int i = 0; i < N; i++) {
-			a[i][p] = temp[i][0];
-			a[i][q] = temp[i][1];
-		}
-		for (int i = 0; i < N; i++) {
-			temp[i][0] = a[p][i]*e_pp + a[q][i]*e_qp;
-			temp[i][1] = a[p][i]*e_pq + a[q][i]*e_qq;
-		}
-		for (int i = 0; i < N; i++) {
-			a[p][i] = temp[i][0];
-			a[q][i] = temp[i][1];
-		}
-		// update eigenvalues
-		if (eigenvalues == null) {
-			eigenvalues = new double[N];
-		} 
-		for (int i = 0; i < N; i++) {
-			eigenvalues[i] = a[i][i];
-		}
-		return false; 
-	}
-	
-	private double[][] copy(double[][] array) {
-		double[][] result = new double[array.length][];
-		for (int i = 0; i < result.length; i++) {
-			result[i] = array[i].clone();
-		}
-		return result;
-	}
-	
-}
\ No newline at end of file
diff --git a/src/org/cicirello/math/la/MatrixOps.java b/src/org/cicirello/math/la/MatrixOps.java
deleted file mode 100644
index 25af34b3..00000000
--- a/src/org/cicirello/math/la/MatrixOps.java
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Copyright 2018-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package org.cicirello.math.la;
-
-/**
- * Utility class of basic linear algebra matrix operations, where matrices are 
- * represented as 2-D Java arrays.
- *
- * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a>  
- * @version 5.13.2021
- */
-public final class MatrixOps {
-	
-	// Utility class of static methods.  Should not be instantiated.
-	private MatrixOps() {}
-	
-	/**
-	 * Transpose a square matrix inline.
-	 * @param matrix the matrix to transpose, with result replacing contents of matrix.
-	 * @return The matrix is returned for convenience for passing to other 
-	 * methods requiring a matrix as parameter.  It is the same
-	 *         object reference that was passed as a parameter.
-	 */
-	public static int[][] transposeSquareMatrixInline(int[][] matrix) {
-		if (matrix.length > 0 && matrix.length != matrix[0].length) throw new IllegalArgumentException("Matrix must be square.");
-		for (int i = 0; i < matrix.length; i++) {
-			for (int j = i+1; j < matrix.length; j++) {
-				int temp = matrix[i][j];
-				matrix[i][j] = matrix[j][i];
-				matrix[j][i] = temp;
-			}
-		}
-		return matrix;
-	}
-	
-	/**
-	 * Transpose a square matrix inline.
-	 * @param matrix the matrix to transpose, with result replacing contents of matrix.
-	 * @return The matrix is returned for convenience for passing to other methods requiring a matrix as parameter.  It is the same
-	 *         object reference that was passed as a parameter.
-	 */
-	public static double[][] transposeSquareMatrixInline(double[][] matrix) {
-		if (matrix.length > 0 && matrix.length != matrix[0].length) throw new IllegalArgumentException("Matrix must be square.");
-		for (int i = 0; i < matrix.length; i++) {
-			for (int j = i+1; j < matrix.length; j++) {
-				double temp = matrix[i][j];
-				matrix[i][j] = matrix[j][i];
-				matrix[j][i] = temp;
-			}
-		}
-		return matrix;
-	}
-	
-	/**
-	 * Computes C = A + B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @param C if C is null then a new matrix is constructed for result, otherwise C is used for result.
-	 * @return A reference to the C matrix.
-	 */
-	public static int[][] sum(int[][] A, int[][] B, int[][] C) {
-		if (A.length != B.length) throw new IllegalArgumentException("Number of rows of A and B must be equal.");
-		if (A.length == 0) {
-			if (C==null) return new int[0][0];
-			else if (C.length==0) return C;
-			else throw new IllegalArgumentException("Number of rows of A, B, and C must be equal.");
-		}
-		if (A[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of A and B must be equal.");
-		if (C == null) {
-			C = new int[A.length][A[0].length];
-		} else {
-			if (C.length != B.length) throw new IllegalArgumentException("Number of rows of A, B, and C must be equal.");
-			if (C[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of A, B, and C must be equal.");
-		}
-		for (int i = 0; i < C.length; i++) {
-			for (int j = 0; j < C[i].length; j++) {
-				C[i][j] = A[i][j] + B[i][j];
-			}
-		}
-		return C;
-	}
-	
-	/**
-	 * Computes C = A + B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @param C if C is null then a new matrix is constructed for result, otherwise C is used for result.
-	 * @return A reference to the C matrix.
-	 */
-	public static double[][] sum(double[][] A, double[][] B, double[][] C) {
-		if (A.length != B.length) throw new IllegalArgumentException("Number of rows of A and B must be equal.");
-		if (A.length == 0) {
-			if (C==null) return new double[0][0];
-			else if (C.length==0) return C;
-			else throw new IllegalArgumentException("Number of rows of A, B, and C must be equal.");
-		}
-		if (A[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of A and B must be equal.");
-		if (C == null) {
-			C = new double[A.length][A[0].length];
-		} else {
-			if (C.length != B.length) throw new IllegalArgumentException("Number of rows of A, B, and C must be equal.");
-			if (C[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of A, B, and C must be equal.");
-		}
-		for (int i = 0; i < C.length; i++) {
-			for (int j = 0; j < C[i].length; j++) {
-				C[i][j] = A[i][j] + B[i][j];
-			}
-		}
-		return C;
-	}
-	
-	/**
-	 * Computes C = A + B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @return A reference to a new matrix C containing the sum.
-	 */
-	public static int[][] sum(int[][] A, int[][] B) {
-		return sum(A, B, null);
-	}
-	
-	/**
-	 * Computes C = A + B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @return A reference to a new matrix C containing the sum.
-	 */
-	public static double[][] sum(double[][] A, double[][] B) {
-		return sum(A, B, null);
-	}
-	
-	/**
-	 * Computes C = A - B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @param C if C is null then a new matrix is constructed for result, otherwise C is used for result.
-	 * @return A reference to the C matrix.
-	 */
-	public static int[][] difference(int[][] A, int[][] B, int[][] C) {
-		if (A.length != B.length) throw new IllegalArgumentException("Number of rows of A and B must be equal.");
-		if (A.length == 0) {
-			if (C==null) return new int[0][0];
-			else if (C.length==0) return C;
-			else throw new IllegalArgumentException("Number of rows of A, B, and C must be equal.");
-		}
-		if (A[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of A and B must be equal.");
-		if (C == null) {
-			C = new int[A.length][A[0].length];
-		} else {
-			if (C.length != B.length) throw new IllegalArgumentException("Number of rows of A, B, and C must be equal.");
-			if (C[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of A, B, and C must be equal.");
-		}
-		for (int i = 0; i < C.length; i++) {
-			for (int j = 0; j < C[i].length; j++) {
-				C[i][j] = A[i][j] - B[i][j];
-			}
-		}
-		return C;
-	}
-	
-	/**
-	 * Computes C = A - B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @param C if C is null then a new matrix is constructed for result, otherwise C is used for result.
-	 * @return A reference to the C matrix.
-	 */
-	public static double[][] difference(double[][] A, double[][] B, double[][] C) {
-		if (A.length != B.length) throw new IllegalArgumentException("Number of rows of A and B must be equal.");
-		if (A.length == 0) {
-			if (C==null) return new double[0][0];
-			else if (C.length==0) return C;
-			else throw new IllegalArgumentException("Number of rows of A, B, and C must be equal.");
-		}
-		if (A[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of A and B must be equal.");
-		if (C == null) {
-			C = new double[A.length][A[0].length];
-		} else {
-			if (C.length != B.length) throw new IllegalArgumentException("Number of rows of A, B, and C must be equal.");
-			if (C[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of A, B, and C must be equal.");
-		}
-		for (int i = 0; i < C.length; i++) {
-			for (int j = 0; j < C[i].length; j++) {
-				C[i][j] = A[i][j] - B[i][j];
-			}
-		}
-		return C;
-	}
-	
-	/**
-	 * Computes C = A - B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @return A reference to a new matrix C containing the difference.
-	 */
-	public static int[][] difference(int[][] A, int[][] B) {
-		return difference(A, B, null);
-	}
-	
-	/**
-	 * Computes C = A - B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @return A reference to a new matrix C containing the difference.
-	 */
-	public static double[][] difference(double[][] A, double[][] B) {
-		return difference(A, B, null);
-	}
-	
-	/**
-	 * Computes C = A * B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @param C if C is null then a new matrix is constructed for result, otherwise C is used for result.
-	 * @return A reference to the C matrix.
-	 */
-	public static int[][] product(int[][] A, int[][] B, int[][] C) {
-		if ((A.length == 0 && B.length > 0) || (A.length > 0 && B.length == 0)) {
-			throw new IllegalArgumentException("If either matrix has 0 rows, both must.");
-		} else if (A.length == 0) {
-			if (C==null) return new int[0][0];
-			else if (C.length==0) return C;
-			else throw new IllegalArgumentException("C's dimensions are inconsistent with A and B.");
-		}
-		if (A[0].length != B.length) throw new IllegalArgumentException("Number of columns of A must equal number of rows of B.");
-		if (C == null) C = new int[A.length][B[0].length];
-		else {
-			if (C.length != A.length) throw new IllegalArgumentException("Number of rows of A and C must be equal.");
-			if (C[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of B and C must be equal.");
-		}
-		for (int i = 0; i < A.length; i++) {
-			for (int j = 0; j < B[0].length; j++) {
-				C[i][j] = 0;
-				for (int k = 0; k < B.length; k++) {
-					C[i][j] = C[i][j] + A[i][k]*B[k][j];
-				}
-			}
-		}
-		return C;
-	}
-	
-	/**
-	 * Computes C = A * B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @param C if C is null then a new matrix is constructed for result, otherwise C is used for result.
-	 * @return A reference to the C matrix.
-	 */
-	public static double[][] product(double[][] A, double[][] B, double[][] C) {
-		if ((A.length == 0 && B.length > 0) || (A.length > 0 && B.length == 0)) {
-			throw new IllegalArgumentException("If either matrix has 0 rows, both must.");
-		} else if (A.length == 0) {
-			if (C==null) return new double[0][0];
-			else if (C.length==0) return C;
-			else throw new IllegalArgumentException("C's dimensions are inconsistent with A and B.");
-		}
-		if (A[0].length != B.length) throw new IllegalArgumentException("Number of columns of A must equal number of rows of B.");
-		if (C == null) C = new double[A.length][B[0].length];
-		else {
-			if (C.length != A.length) throw new IllegalArgumentException("Number of rows of A and C must be equal.");
-			if (C[0].length != B[0].length) throw new IllegalArgumentException("Number of columns of B and C must be equal.");
-		}
-		for (int i = 0; i < A.length; i++) {
-			for (int j = 0; j < B[0].length; j++) {
-				C[i][j] = 0;
-				for (int k = 0; k < B.length; k++) {
-					C[i][j] = C[i][j] + A[i][k]*B[k][j];
-				}
-			}
-		}
-		return C;
-	}
-	
-	/**
-	 * Computes C = A * B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @return A reference to a new matrix C containing the product.
-	 */
-	public static int[][] product(int[][] A, int[][] B) {
-		return product(A,B,null);
-	}
-	
-	/**
-	 * Computes C = A * B.
-	 * @param A matrix
-	 * @param B matrix
-	 * @return A reference to a new matrix C containing the product.
-	 */
-	public static double[][] product(double[][] A, double[][] B) {
-		return product(A,B,null);
-	}
-}
\ No newline at end of file
diff --git a/src/org/cicirello/math/la/package-info.java b/src/org/cicirello/math/la/package-info.java
deleted file mode 100644
index e6ee49ff..00000000
--- a/src/org/cicirello/math/la/package-info.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2018 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
- 
- /**
- * Collection of classes related to linear algebra. 
- * @version 1.18.7.1
- * @since 1.3
- */
-package org.cicirello.math.la;
\ No newline at end of file
diff --git a/src/org/cicirello/math/package-info.java b/src/org/cicirello/math/package-info.java
deleted file mode 100644
index 304848d5..00000000
--- a/src/org/cicirello/math/package-info.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2019 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
- 
- /**
- * Collection of classes related to a variety of mathematical concepts. 
- * @version 8.22.2019
- * @since 2.0
- */
-package org.cicirello.math;
-
-
diff --git a/src/org/cicirello/math/rand/BTPE.java b/src/org/cicirello/math/rand/BTPE.java
deleted file mode 100644
index b1ebd6e0..00000000
--- a/src/org/cicirello/math/rand/BTPE.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Copyright 2019-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package org.cicirello.math.rand;
-
-import org.cicirello.math.MathFunctions;
-import java.util.SplittableRandom;
-import java.util.Random;
-
-/**
- * <p>This class enables generating random variates from a binomial distribution.
- * This is a mostly direct implementation of the BTPE algorithm described in the
- * article: Voratas Kachitvichyanukul and Bruce W. Schmeiser. 1988. 
- * Binomial random variate generation. Commun. ACM 31, 2 (February 1988), 
- * 216-222. DOI=http://dx.doi.org/10.1145/42372.42381 </p>
- *
- * <p>There are a few, mostly minor, differences in my Java implementation of
- * the algorithm described in that paper.  First, there are several minor
- * optimizations, such as replacing division by constants with multiplication of constants
- * wherever practical.  As mentioned in the above article, BTPE is only valid
- * when n*min(p,1-p) &ge; 10.  The authors of that article include a survey of
- * various alternative approaches one might use when n*min(p,1-p) &lt; 10.  We use
- * a simple approach based on the inverse transform in that case.</p>
- *
- * <p>We cache the various constants that depend only on n and p.  We only recompute
- * these if a different n or p is given as a parameter.  To enable a thread safe approach,
- * we maintain one constant cache for each thread using ThreadLocal.  Specifically, we maintain
- * one instance of BTPE per thread using ThreadLocal.</p>
- * 
- * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a> 
- * @version 2.12.2021
- */
-final class BTPE {
-	
-	/**
-	 * Generates a pseudorandom integer from a binomial distribution.
-	 * @param n Number of trials for the binomial distribution.
-	 * @param p The probability of a successful trial.
-	 * @param generator The source of randomness.
-	 * @return A pseudorandom integer from a binomial distribution.
-	 */
-	public static int nextBinomial(int n, double p, Random generator) {
-		return threadLocalInstance(n, p).next(generator);
-	}
-	
-	/**
-	 * Generates a pseudorandom integer from a binomial distribution.
-	 * @param n Number of trials for the binomial distribution.
-	 * @param p The probability of a successful trial.
-	 * @param generator The source of randomness.
-	 * @return A pseudorandom integer from a binomial distribution.
-	 */
-	public static int nextBinomial(int n, double p, SplittableRandom generator) {
-		return threadLocalInstance(n, p).next(generator);
-	}
-	
-	// We cache constants until n or p changes.  We use ThreadLocal for
-	// cache so that we are thread safe.  Each thread has its own cache.
-	private static final ThreadLocal<BTPE> tl = new ThreadLocal<BTPE>();
-	
-	/*
-	 * Private helper to handle getting the thread local instance of BTPE,
-	 * initializing if necessary, and generating if it doesn't already exist.
-	 */
-	private static BTPE threadLocalInstance(int n, double p) {
-		BTPE btpe = tl.get();
-		if (btpe == null) {
-			btpe = new BTPE(n, p);
-			tl.set(btpe);
-		} else if (btpe.n != n || btpe.p != p) {
-			btpe.init(n, p);
-		}
-		return btpe;
-	}
-	
-	// Variables for caching constants that only change when n or p changes.
-	// Constant cache for BTPE and simple inverse based approach
-	private int n;
-	private double p;
-	private double r;
-	private double q;
-	
-	// Constant cache for BTPE
-	private double f_m;
-	private int m;
-	private double nr;
-	private double nrq;
-	private double p1;
-	private double x_m;
-	private double x_l;
-	private double x_r;
-	private double c;
-	private double lambda_l;
-	private double lambda_r;
-	private double p2;
-	private double p3;
-	private double p4;
-	private double nrqInv;
-	
-	// Constant cache for simple inverse based approach
-	private double s;
-	private double a;
-	private double pow0;
-	
-	// BTPE is not valid if n*min(p,1-p) < 10
-	private static final int BTPE_CUTOFF = 10;
-	
-	// Private constructor
-	private BTPE(int n, double p) {
-		init(n, p);
-	}
-	
-	// call this if n or p changes
-	private void init(int n, double p) {
-		// Step 0: Set-up constants, etc.
-		this.n = n;
-		this.p = p;
-		if (p <= 0.5) {
-			r = p; 
-			q = 1 - p; 
-		} else {
-			q = p;
-			r = 1 - p;
-		}
-		nr = n*r; 
-		s = r / q;
-		if (nr < BTPE_CUTOFF) {
-			a = (n+1)*s;
-			pow0 = MathFunctions.pow(q, n);		
-		} else {			
-			f_m = nr + r; 
-			m = (int)f_m;  
-			nrq = nr*q; 
-			p1 = Math.floor(2.195 * Math.sqrt(nrq) - 4.6*q) + 0.5; 
-			x_m = m + 0.5; 
-			x_l = x_m - p1; 
-			x_r = x_m + p1; 
-			c = 0.134 + 20.5/(15.3 + m); 
-			a = (f_m - x_l) / (f_m - x_l * r); 
-			lambda_l = a * (1 + 0.5 * a); 
-			a = (x_r - f_m) / (x_r * q); 
-			lambda_r = a * (1 + 0.5 * a); 
-			p2 = p1 * (1 + c + c); 
-			p3 = p2 + c / lambda_l; 
-			p4 = p3 + c / lambda_r; 
-			nrqInv = 1.0 / nrq;
-		}
-	}
-	
-	private int next(Random generator) {
-				
-		if (nr < BTPE_CUTOFF) {
-			// Use simple algorithm based on inverse transform since BTPE is only valid
-			// when n*min(p, 1-p) >= 10.
-			double u = generator.nextDouble();
-			int y = 0;
-			double pow = pow0;
-			while (u > pow) {
-				u -= pow;
-				y++;
-				pow = pow * (a/y-s);
-			}
-			return p > 0.5 ? n - y : y;
-		}
-		
-		int y;
-		while (true) {
-			
-			// Step 1.
-			double u = p4 * generator.nextDouble(); 
-			double v = generator.nextDouble(); 
-			if (u <= p1) { 
-				y = (int)(x_m - p1*v + u); 
-				break;
-			}
-			
-			// Step 2: parallelograms
-			if (u <= p2) {
-				double x = x_l + (u - p1) / c;
-				v = v*c + 1 - Math.abs(m - x + 0.5) / p1;
-				if (v > 1) continue;
-				y = (int)x;
-			} else {
-				// Step 3: left exponential tail
-				if (u <= p3) {
-					y = (int)Math.floor(x_l + Math.log(v)/lambda_l);
-					if (y < 0) continue;
-					v = v * (u - p2) * lambda_l;
-				} else {
-					// Step 4: right exponential tail
-					y = (int)Math.floor(x_r - Math.log(v)/lambda_r);
-					if (y > n) continue;
-					v = v * (u - p3) * lambda_r;
-				}
-			}
-			
-			// Step 5: Acceptance/rejection comparisons.
-			
-			// Step 5.0
-			int k = m > y ? m - y : y - m;
-			if (k <= 20 || k >= nrq * 0.5 - 1) {
-				// Step 5.1
-				a = s * (n+1);
-				double f = 1.0;
-				if (m < y) {
-					for (int i = m + 1; i <= y; i++) {
-						f = f*(a/i-s);
-					}
-				} else if (m > y) {
-					for (int i = y + 1; i <= m; i++) {
-						f = f/(a/i-s);
-					}
-				}
-				if (v > f) continue;
-				break;
-			} else {
-				// Step 5.2: Squeezing
-				double rho = (k * nrqInv) * ((k * (k*0.3333333333333333 + 0.625) + 0.16666666666666666) * nrqInv + 0.5);
-				double t = -k*k*(0.5*nrqInv);
-				a = Math.log(v);
-				if (a < t - rho) {
-					break;
-				}
-				if (a > t + rho) continue;
-			}
-			
-			// Step 5.3: Final acceptance/rejection test
-			int x1 = y + 1;
-			int f1 = m + 1;
-			int z = n + 1 - m;
-			int w = n - y + 1;
-			if (a > x_m * Math.log(((double)f1)/x1) + (n - m + 0.5)*Math.log(((double)z)/w) + (y-m)*Math.log(w*r/(x1*q)) + stirlingApproximation(f1) + stirlingApproximation(z) + stirlingApproximation(x1) + stirlingApproximation(w)) continue;
-			break;
-		}
-		
-		return p > 0.5 ? n - y : y;
-	}
-	
-	private int next(SplittableRandom generator) {
-				
-		if (nr < BTPE_CUTOFF) {
-			// Use simple algorithm based on inverse transform since BTPE is only valid
-			// when n*min(p, 1-p) >= 10.
-			double u = generator.nextDouble();
-			int y = 0;
-			double pow = pow0;
-			while (u > pow) {
-				u -= pow;
-				y++;
-				pow = pow * (a/y-s);
-			}
-			return p > 0.5 ? n - y : y;
-		}
-		
-		int y;
-		while (true) {
-			
-			// Step 1.
-			double u = p4 * generator.nextDouble(); 
-			double v = generator.nextDouble(); 
-			if (u <= p1) { 
-				y = (int)(x_m - p1*v + u); 
-				break;
-			}
-			
-			// Step 2: parallelograms
-			if (u <= p2) {
-				double x = x_l + (u - p1) / c;
-				v = v*c + 1 - Math.abs(m - x + 0.5) / p1;
-				if (v > 1) continue;
-				y = (int)x;
-			} else {
-				// Step 3: left exponential tail
-				if (u <= p3) {
-					y = (int)Math.floor(x_l + Math.log(v)/lambda_l);
-					if (y < 0) continue;
-					v = v * (u - p2) * lambda_l;
-				} else {
-					// Step 4: right exponential tail
-					y = (int)Math.floor(x_r - Math.log(v)/lambda_r);
-					if (y > n) continue;
-					v = v * (u - p3) * lambda_r;
-				}
-			}
-			
-			// Step 5: Acceptance/rejection comparisons.
-			
-			// Step 5.0
-			int k = m > y ? m - y : y - m;
-			if (k <= 20 || k >= nrq * 0.5 - 1) {
-				// Step 5.1
-				a = s * (n+1);
-				double f = 1.0;
-				if (m < y) {
-					for (int i = m + 1; i <= y; i++) {
-						f = f*(a/i-s);
-					}
-				} else if (m > y) {
-					for (int i = y + 1; i <= m; i++) {
-						f = f/(a/i-s);
-					}
-				}
-				if (v > f) continue;
-				break;
-			} else {
-				// Step 5.2: Squeezing
-				double rho = (k * nrqInv) * ((k * (k*0.3333333333333333 + 0.625) + 0.16666666666666666) * nrqInv + 0.5);
-				double t = -k*k*(0.5*nrqInv);
-				a = Math.log(v);
-				if (a < t - rho) {
-					break;
-				}
-				if (a > t + rho) continue;
-			}
-			
-			// Step 5.3: Final acceptance/rejection test
-			int x1 = y + 1;
-			int f1 = m + 1;
-			int z = n + 1 - m;
-			int w = n - y + 1;
-			if (a > x_m * Math.log(((double)f1)/x1) + (n - m + 0.5)*Math.log(((double)z)/w) + (y-m)*Math.log(w*r/(x1*q)) + stirlingApproximation(f1) + stirlingApproximation(z) + stirlingApproximation(x1) + stirlingApproximation(w)) continue;
-			break;
-		}
-		
-		return p > 0.5 ? n - y : y;
-	}
-	
-	private double stirlingApproximation(int x) {
-		int x2 = x * x;
-		return (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / x2) / x2) / x2) / x2) / x / 166320.0;
-	}
-	
-}
\ No newline at end of file
diff --git a/src/org/cicirello/math/rand/RandomIndexer.java b/src/org/cicirello/math/rand/RandomIndexer.java
deleted file mode 100644
index 11d69060..00000000
--- a/src/org/cicirello/math/rand/RandomIndexer.java
+++ /dev/null
@@ -1,1441 +0,0 @@
-/*
- * Copyright 2019-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-package org.cicirello.math.rand;
-
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.Random;
-import java.util.SplittableRandom;
-
-/**
- * RandomIndexer is a class of utility methods related to 
- * efficiently generating random indexes, and combination of indexes, into arrays.
- * The methods of this class neither directly operate, nor depend, on arrays, and can thus
- * be used wherever you need random integer values.  The name of the class is derived
- * from the motivating case, the case of efficiently generating random indexes into an array.
- *
- * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a> 
- * @version 5.13.2021
- *
- */
-public final class RandomIndexer {
-	
-	/**
-	 * Class of static utility methods, so instantiation is not allowed.
-	 */
-	private RandomIndexer() {}
-	
-	/**
-	 * <p>Generates a random integer uniformly distributed in the interval: [0, bound).</p>
-	 * <p>This method uses ThreadLocalRandom as the pseudorandom number generator, and is thus
-	 * safe, and efficient (i.e., non-blocking), for use with threads.  However, 
-	 * it does not use ThreadLocalRandom.nextInt(int bound)
-	 * method.  Instead, our nextInt(int bound) method is an implementation of 
-	 * the algorithm proposed in the article: Daniel Lemire, "Fast Random Integer 
-	 * Generation in an Interval," ACM Transactions on Modeling and Computer Simulation, 29(1), 2019.</p>
-	 * <p>This method is significantly faster than ThreadLocalRandom.nextInt(int bound).</p>
-	 *
-	 * @param bound Upper bound, exclusive, on range of random integers (must be positive).
-	 * @return a random integer between 0 (inclusive) and bound (exclusive).
-	 * @throws IllegalArgumentException if the bound is not positive
-	 */
-	public static int nextInt(int bound) {
-		return nextInt(bound, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random integer uniformly distributed in the interval: [0, bound).</p>
-	 * <p>This method uses a SplittableRandom object passed as a parameter 
-	 * as the pseudorandom number generator.  
-	 * However, it does not use SplittableRandom.nextInt(int bound)
-	 * method.  Instead, our nextInt(int bound) method is an implementation of 
-	 * the algorithm proposed in the article: Daniel Lemire, "Fast Random Integer 
-	 * Generation in an Interval," ACM Transactions on Modeling and Computer Simulation, 29(1), 2019.</p>
-	 * <p>This method is significantly faster than SplittableRandom.nextInt(int bound).</p>
-	 *
-	 * @param bound Upper bound, exclusive, on range of random integers (must be positive).
-	 * @param gen A source of randomness.
-	 * @return a random integer between 0 (inclusive) and bound (exclusive).
-	 * @throws IllegalArgumentException if the bound is not positive
-	 */
-	public static int nextInt(int bound, SplittableRandom gen) {
-		if (bound < 1) throw new IllegalArgumentException("bound must be positive");
-		//Commented out lines handle bound that is a power of 2 as a special case
-		//Seems to only marginally speed computation in special case while adding to cost of
-		//general case.
-		//int b1 = bound - 1;
-		//if ((bound & b1) == 0) return gen.nextInt() & b1;
-		long product = (long)(gen.nextInt() & 0x7fffffff) * (long)bound;
-		int low31 = (int)product & 0x7fffffff;
-		if (low31 < bound) {
-			int threshold = (0x80000000-bound) % bound;
-			while (low31 < threshold) {
-				product = (long)(gen.nextInt() & 0x7fffffff) * (long)bound;
-				low31 = (int)product & 0x7fffffff;
-			}
-		}
-		return (int)(product >> 31);
-	}
-	
-	/**
-	 * <p>Generates a random integer uniformly distributed in the interval: [0, bound).</p>
-	 * <p>This method uses a Random object passed as a parameter 
-	 * as the pseudorandom number generator.  
-	 * However, it does not use Random.nextInt(int bound)
-	 * method.  Instead, our nextInt(int bound) method is an implementation of 
-	 * the algorithm proposed in the article: Daniel Lemire, "Fast Random Integer 
-	 * Generation in an Interval," ACM Transactions on Modeling and Computer Simulation, 29(1), 2019.</p>
-	 * <p>This method is significantly faster than Random.nextInt(int bound).</p>
-	 *
-	 * @param bound Upper bound, exclusive, on range of random integers (must be positive).
-	 * @param gen A source of randomness.
-	 * @return a random integer between 0 (inclusive) and bound (exclusive).
-	 * @throws IllegalArgumentException if the bound is not positive
-	 */
-	public static int nextInt(int bound, Random gen) {
-		if (bound < 1) throw new IllegalArgumentException("bound must be positive");
-		//Commented out lines handle bound that is a power of 2 as a special case
-		//Seems to only marginally speed computation in special case while adding to cost of
-		//general case.
-		//int b1 = bound - 1;
-		//if ((bound & b1) == 0) return gen.nextInt() & b1;
-		long product = (long)(gen.nextInt() & 0x7fffffff) * (long)bound;
-		int low31 = (int)product & 0x7fffffff;
-		if (low31 < bound) {
-			int threshold = (0x80000000-bound) % bound;
-			while (low31 < threshold) {
-				product = (long)(gen.nextInt() & 0x7fffffff) * (long)bound;
-				low31 = (int)product & 0x7fffffff;
-			}
-		}
-		return (int)(product >> 31);
-	}
-	
-	/**
-	 * <p>Generates a random integer in the interval: [0, bound).</p>
-	 * <p>This method uses ThreadLocalRandom as the pseudorandom number generator, and is thus
-	 * safe, and efficient (i.e., non-blocking), for use with threads.  
-	 * However, it does not use ThreadLocalRandom.nextInt(int bound)
-	 * method.  Instead, our nextBiasedInt(int bound) method computes a random int in the target interval
-	 * via a multiplication and a shift, rather than the more common mod.  This method does not
-	 * correct for bias via rejection sampling, and thus some values in the interval [0, bound)
-	 * may be more likely than others.  There is no bias for bound values that are powers of 2.
-	 * Otherwise, the lower the value of bound, the less bias; and the higher
-	 * the value of bound, the more bias.  If your bound is relatively low, and if your application
-	 * does not require strict uniformity, then this method is significantly faster than any
-	 * approach that corrects for bias.  We started with  
-	 * the algorithm proposed in the article: Daniel Lemire, "Fast Random Integer 
-	 * Generation in an Interval," ACM Transactions on Modeling and Computer Simulation, 29(1), 2019.
-	 * But we removed from it the rejection sampling portion.</p>
-	 *
-	 * @param bound Upper bound, exclusive, on range of random integers (must be positive).
-	 * @return a random integer between 0 (inclusive) and bound (exclusive).
-	 * @throws IllegalArgumentException if the bound is not positive
-	 */
-	public static int nextBiasedInt(int bound) {
-		if (bound < 1) throw new IllegalArgumentException("bound must be positive");
-		return (int)(((long)(ThreadLocalRandom.current().nextInt() & 0x7fffffff) * (long)bound) >> 31);
-	}
-	
-	/**
-	 * <p>Generates a random integer in the interval: [0, bound).</p>
-	 * <p>This method uses SplittableRandom as the pseudorandom number generator, and is thus
-	 * safe for use with threads.  However, it does not use SplittableRandom.nextInt(int bound)
-	 * method.  Instead, our nextBiasedInt(int bound) method computes a random int in the target interval
-	 * via a multiplication and a shift, rather than the more common mod.  This method does not
-	 * correct for bias via rejection sampling, and thus some values in the interval [0, bound)
-	 * may be more likely than others.  There is no bias for bound values that are powers of 2.
-	 * Otherwise, the lower the value of bound, the less bias; and the higher
-	 * the value of bound, the more bias.  If your bound is relatively low, and if your application
-	 * does not require strict uniformity, then this method is significantly faster than any
-	 * approach that corrects for bias.  We started with  
-	 * the algorithm proposed in the article: Daniel Lemire, "Fast Random Integer 
-	 * Generation in an Interval," ACM Transactions on Modeling and Computer Simulation, 29(1), 2019.
-	 * But we removed from it the rejection sampling portion.</p>
-	 *
-	 * @param bound Upper bound, exclusive, on range of random integers (must be positive).
-	 * @param gen A source of randomness.
-	 * @return a random integer between 0 (inclusive) and bound (exclusive).
-	 * @throws IllegalArgumentException if the bound is not positive
-	 */
-	public static int nextBiasedInt(int bound, SplittableRandom gen) {
-		if (bound < 1) throw new IllegalArgumentException("bound must be positive");
-		return (int)(((long)(gen.nextInt() & 0x7fffffff) * (long)bound) >> 31);
-	}
-	
-	/**
-	 * <p>Generates a random integer in the interval: [0, bound).</p>
-	 * <p>This method uses Random as the pseudorandom number generator, and is thus
-	 * safe for use with threads.  However, it does not use Random.nextInt(int bound)
-	 * method.  Instead, our nextBiasedInt(int bound) 
-	 * method computes a random int in the target interval
-	 * via a multiplication and a shift, 
-	 * rather than the more common mod.  This method does not
-	 * correct for bias via rejection sampling, 
-	 * and thus some values in the interval [0, bound)
-	 * may be more likely than others.  There is no 
-	 * bias for bound values that are powers of 2.
-	 * Otherwise, the lower the value of bound, the less bias; and the higher
-	 * the value of bound, the more bias.  
-	 * If your bound is relatively low, and if your application
-	 * does not require strict uniformity, 
-	 * then this method is significantly faster than any
-	 * approach that corrects for bias.  We started with  
-	 * the algorithm proposed in the article: Daniel Lemire, "Fast Random Integer 
-	 * Generation in an Interval," ACM Transactions 
-	 * on Modeling and Computer Simulation, 29(1), 2019.
-	 * But we removed from it the rejection sampling portion.</p>
-	 *
-	 * @param bound Upper bound, exclusive, on range of random integers (must be positive).
-	 * @param gen A source of randomness.
-	 * @return a random integer between 0 (inclusive) and bound (exclusive).
-	 * @throws IllegalArgumentException if the bound is not positive
-	 */
-	public static int nextBiasedInt(int bound, Random gen) {
-		if (bound < 1) throw new IllegalArgumentException("bound must be positive");
-		return (int)(((long)(gen.nextInt() & 0x7fffffff) * (long)bound) >> 31);
-	}
-	
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>  
-	 * <p>Uses the reservoir sampling algorithm (Algorithm R) 
-	 * from J. Vitter's 1985 article "Random Sampling
-	 * with a Reservoir" from ACM Transactions on Mathematical Software.  
-	 * The runtime is O(n)
-	 * and it generates O(n-k) random numbers.  Thus, it is an 
-	 * especially good choice as k
-	 * approaches n.  Only constant extra space required.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, 
-	 * and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sampleReservoir(int n, int k, int[] result) {
-		return sampleReservoir(n, k, result, ThreadLocalRandom.current());
-	}
-	
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>  
-	 * <p>Uses the reservoir sampling algorithm (Algorithm R) 
-	 * from J. Vitter's 1985 article "Random Sampling
-	 * with a Reservoir" from ACM Transactions 
-	 * on Mathematical Software.  The runtime is O(n)
-	 * and it generates O(n-k) random numbers.  
-	 * Thus, it is an especially good choice as k
-	 * approaches n.  Only constant extra space required.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sampleReservoir(int n, int k, int[] result, Random gen) {
-		if (k > n) throw new IllegalArgumentException("k must be no greater than n");
-		if (result == null || result.length < k) result = new int[k];
-		for (int i = 0; i < k; i++) {
-			result[i] = i;
-		}
-		for (int i = k; i < n; i++) {
-			int j = nextInt(i+1, gen);
-			if (j < k) {
-				result[j] = i;
-			}
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>  
-	 * <p>Uses the reservoir sampling algorithm (Algorithm R) 
-	 * from J. Vitter's 1985 article "Random Sampling
-	 * with a Reservoir" from ACM Transactions 
-	 * on Mathematical Software.  The runtime is O(n)
-	 * and it generates O(n-k) random numbers.  Thus, it is an especially good choice as k
-	 * approaches n.  Only constant extra space required.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sampleReservoir(int n, int k, int[] result, SplittableRandom gen) {
-		if (k > n) throw new IllegalArgumentException("k must be no greater than n");
-		if (result == null || result.length < k) result = new int[k];
-		for (int i = 0; i < k; i++) {
-			result[i] = i;
-		}
-		for (int i = k; i < n; i++) {
-			int j = nextInt(i+1, gen);
-			if (j < k) {
-				result[j] = i;
-			}
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>  
-	 * <p>This implements the algorithm SELECT of S. Goodman and S. Hedetniemi, as described in:
-	 * J Ernvall, O Nevalainen, "An Algorithm for Unbiased Random Sampling," The 
-	 * Computer Journal, 25(1):45-47, 1982.</p>
-	 * <p>The runtime is O(n)
-	 * and it generates O(k) random numbers.  Thus, it is a better 
-	 * choice than sampleReservoir when k &lt; n-k.
-	 * However, this uses O(n) extra space, whereas the reservoir algorithm
-	 * uses no extra space.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, and efficient (i.e., non-blocking), 
-	 * for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] samplePool(int n, int k, int[] result) {
-		return samplePool(n, k, result, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>
-	 * <p>This implements the algorithm SELECT of S. Goodman and S. Hedetniemi, as described in:
-	 * J Ernvall, O Nevalainen, "An Algorithm for Unbiased Random Sampling," The 
-	 * Computer Journal, 25(1):45-47, 1982.</p>
-	 * <p>The runtime is O(n)
-	 * and it generates O(k) random numbers.  Thus, it is a better 
-	 * choice than sampleReservoir when k &lt; n-k.
-	 * However, this uses O(n) extra space, whereas the reservoir algorithm
-	 * uses no extra space.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] samplePool(int n, int k, int[] result, SplittableRandom gen) {
-		if (k > n) throw new IllegalArgumentException("k must be no greater than n");
-		if (result == null || result.length < k) result = new int[k];
-		int[] pool = new int[n];
-		for (int i = 0; i < n; i++) pool[i] = i;
-		int remaining = n;
-		for (int i = 0; i < k; i++) {
-			int temp = nextInt(remaining, gen);
-			result[i] = pool[temp];
-			remaining--;
-			pool[temp] = pool[remaining];
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>
-	 * <p>This implements the algorithm SELECT of S. Goodman and S. Hedetniemi, as described in:
-	 * J Ernvall, O Nevalainen, "An Algorithm for Unbiased Random Sampling," The 
-	 * Computer Journal, 25(1):45-47, 1982.</p>
-	 * <p>The runtime is O(n)
-	 * and it generates O(k) random numbers.  Thus, it is a better 
-	 * choice than sampleReservoir when k &lt; n-k.
-	 * However, this uses O(n) extra space, whereas the reservoir algorithm
-	 * uses no extra space.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] samplePool(int n, int k, int[] result, Random gen) {
-		if (k > n) throw new IllegalArgumentException("k must be no greater than n");
-		if (result == null || result.length < k) result = new int[k];
-		int[] pool = new int[n];
-		for (int i = 0; i < n; i++) pool[i] = i;
-		int remaining = n;
-		for (int i = 0; i < k; i++) {
-			int temp = nextInt(remaining, gen);
-			result[i] = pool[temp];
-			remaining--;
-			pool[temp] = pool[remaining];
-		}
-		return result;
-	}
-	
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>  
-	 * <p>The runtime is O(k<sup>2</sup>)
-	 * and it generates O(k) random numbers.  Thus, it is a better 
-	 * choice than both sampleReservoir and samplePool when k<sup>2</sup> &lt; n.
-	 * Just like sampleReservoir, the sampleInsertion method only requires O(1) extra space,
-	 * while samplePool requires O(n) extra space.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, and efficient (i.e., non-blocking), 
-	 * for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sampleInsertion(int n, int k, int[] result) {
-		return sampleInsertion(n, k, result, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>  
-	 * <p>The runtime is O(k<sup>2</sup>)
-	 * and it generates O(k) random numbers.  Thus, it is a better 
-	 * choice than both sampleReservoir and samplePool when k<sup>2</sup> &lt; n.
-	 * Just like sampleReservoir, the sampleInsertion method only requires O(1) extra space,
-	 * while samplePool requires O(n) extra space.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @param gen The source of randomness.
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sampleInsertion(int n, int k, int[] result, SplittableRandom gen) {
-		if (k > n) throw new IllegalArgumentException("k must be no greater than n");
-		if (result == null || result.length < k) result = new int[k];
-		for (int i = 0; i < k; i++) {
-			int temp = nextInt(n-i, gen);
-			int j = k-i; 
-			for ( ; j < k; j++) {
-				if (temp >= result[j]) {
-					temp++;
-					result[j-1] = result[j];
-				} else break;
-			}
-			result[j-1] = temp;
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>  
-	 * <p>The runtime is O(k<sup>2</sup>)
-	 * and it generates O(k) random numbers.  Thus, it is a better 
-	 * choice than both sampleReservoir and samplePool when k<sup>2</sup> &lt; n.
-	 * Just like sampleReservoir, the sampleInsertion method only requires O(1) extra space,
-	 * while samplePool requires O(n) extra space.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @param gen The source of randomness.
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sampleInsertion(int n, int k, int[] result, Random gen) {
-		if (k > n) throw new IllegalArgumentException("k must be no greater than n");
-		if (result == null || result.length < k) result = new int[k];
-		for (int i = 0; i < k; i++) {
-			int temp = nextInt(n-i, gen);
-			int j = k-i; 
-			for ( ; j < k; j++) {
-				if (temp >= result[j]) {
-					temp++;
-					result[j-1] = result[j];
-				} else break;
-			}
-			result[j-1] = temp;
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample, without replacement, from the
-	 * set of integers in the interval [0, n).  Each of the n integers 
-	 * has a probability p of inclusion in the sample.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * source of randomness, and is thus safe, and efficient (i.e., non-blocking), 
-	 * for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param p The probability that each of the n integers is included in the sample.
-	 * @return An array containing the sample.
-	 * @since 2.0
-	 */
-	public static int[] sample(int n, double p) {
-		return sample(n, p, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample, without replacement, from the
-	 * set of integers in the interval [0, n).  Each of the n integers 
-	 * has a probability p of inclusion in the sample.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * source of randomness, and is thus safe, and efficient (i.e., non-blocking), 
-	 * for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param p The probability that each of the n integers is included in the sample.
-	 * @param r The source of randomness.
-	 * @return An array containing the sample.
-	 * @since 2.0
-	 */
-	public static int[] sample(int n, double p, SplittableRandom r) {
-		if (p <= 0) {
-			return new int[0];
-		} else if (p >= 1) {
-			int[] result = new int[n];
-			for (int i = 0; i < n; i++) result[i] = i;
-			return result;
-		} else {
-			return sample(n, RandomVariates.nextBinomial(n, p, r), null, r);
-		}
-	}
-	
-	/**
-	 * <p>Generates a random sample, without replacement, from the
-	 * set of integers in the interval [0, n).  Each of the n integers 
-	 * has a probability p of inclusion in the sample.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * source of randomness, and is thus safe, and efficient (i.e., non-blocking), 
-	 * for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param p The probability that each of the n integers is included in the sample.
-	 * @param r The source of randomness.
-	 * @return An array containing the sample.
-	 * @since 2.0
-	 */
-	public static int[] sample(int n, double p, Random r) {
-		if (p <= 0) {
-			return new int[0];
-		} else if (p >= 1) {
-			int[] result = new int[n];
-			for (int i = 0; i < n; i++) result[i] = i;
-			return result;
-		} else {
-			return sample(n, RandomVariates.nextBinomial(n, p, r), null, r);
-		}
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>
-	 * <p>This method chooses among the RandomIndexer.samplePool, 
-	 * RandomIndexer.sampleReservoir, and RandomIndexer.sampleInsertion 
-	 * methods based on the values of n and k.</p>
-	 * <p>The runtime is O(min(n, k<sup>2</sup>))
-	 * and it generates O(min(k, n-k)) random numbers.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, and efficient (i.e., non-blocking), 
-	 * for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sample(int n, int k, int[] result) {
-		if (k + k < n) {
-			if (k * k < n) return sampleInsertion(n, k, result, ThreadLocalRandom.current());
-			else return samplePool(n, k, result, ThreadLocalRandom.current());
-		} else return sampleReservoir(n, k, result, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>
-	 * <p>This method chooses among the RandomIndexer.samplePool, 
-	 * RandomIndexer.sampleReservoir, and RandomIndexer.sampleInsertion 
-	 * methods based on the values of n and k.</p>
-	 * <p>The runtime is O(min(n, k<sup>2</sup>))
-	 * and it generates O(min(k, n-k)) random numbers.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sample(int n, int k, int[] result, SplittableRandom gen) {
-		if (k + k < n) {
-			if (k * k < n) return sampleInsertion(n, k, result, gen);
-			else return samplePool(n, k, result, gen);
-		} else return sampleReservoir(n, k, result, gen);
-	}
-	
-	/**
-	 * <p>Generates a random sample of k integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose k combinations are equally
-	 * likely.</p>
-	 * <p>This method chooses among the RandomIndexer.samplePool, 
-	 * RandomIndexer.sampleReservoir, and RandomIndexer.sampleInsertion 
-	 * methods based on the values of n and k.</p>
-	 * <p>The runtime is O(min(n, k<sup>2</sup>))
-	 * and it generates O(min(k, n-k)) random numbers.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param k The size of the desired sample.
-	 * @param result An array to hold the sample that is generated.  If result is null
-	 * or if result.length is less than k, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the sample of k randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if k &gt; n.
-	 * @throws NegativeArraySizeException if k &lt; 0.
-	 */
-	public static int[] sample(int n, int k, int[] result, Random gen) {
-		if (k + k < n) {
-			if (k * k < n) return sampleInsertion(n, k, result, gen);
-			else return samplePool(n, k, result, gen);
-		} else return sampleReservoir(n, k, result, gen);
-	}
-	
-	/**
-	 * <p>Generates a random sample of 2 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 2 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, and efficient (i.e., non-blocking), 
-	 * for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 2, then this method will construct an array for the result. 
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).
-	 * @throws IllegalArgumentException if n &lt; 2.
-	 */
-	public static int[] nextIntPair(int n, int[] result) {
-		return nextIntPair(n, result, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of 2 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 2 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 2, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).  
-	 * @throws IllegalArgumentException if n &lt; 2.
-	 */
-	public static int[] nextIntPair(int n, int[] result, SplittableRandom gen) {
-		if (result == null || result.length < 2) result = new int[2];
-		result[0] = nextInt(n, gen);
-		result[1] = nextInt(n-1, gen);
-		if (result[1] >= result[0]) {
-			result[1]++;
-		} 
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 2 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 2 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 2, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).  
-	 * @throws IllegalArgumentException if n &lt; 2.
-	 */
-	public static int[] nextIntPair(int n, int[] result, Random gen) {
-		if (result == null || result.length < 2) result = new int[2];
-		result[0] = nextInt(n, gen);
-		result[1] = nextInt(n-1, gen);
-		if (result[1] >= result[0]) {
-			result[1]++;
-		} 
-		return result;
-	}
-	
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 3 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, 
-	 * and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).  
-	 * @throws IllegalArgumentException if n &lt; 3.
-	 */
-	public static int[] nextIntTriple(int n, int[] result) {
-		return nextIntTriple(n, result, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 3 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, 
-	 * and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @param sort If true, the result is sorted in increasing order; otherwise it is in arbitrary order.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).  
-	 * @throws IllegalArgumentException if n &lt; 3.
-	 */
-	public static int[] nextIntTriple(int n, int[] result, boolean sort) {
-		return nextIntTriple(n, result, sort, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 3 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @param sort If true, the result is sorted in increasing order; otherwise it is in arbitrary order.
-	 * @param gen The source of randomness.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).  
-	 * @throws IllegalArgumentException if n &lt; 3.
-	 */
-	public static int[] nextIntTriple(int n, int[] result, boolean sort, SplittableRandom gen) {
-		if (result == null || result.length < 3) result = new int[3];
-		result[0] = nextInt(n, gen);
-		result[1] = nextInt(n-1, gen);
-		result[2] = nextInt(n-2, gen);
-		if (sort) adjustSortTriple(result);
-		else adjustTriple(result);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 3 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @param sort If true, the result is sorted in increasing order; otherwise it is in arbitrary order.
-	 * @param gen The source of randomness.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).  
-	 * @throws IllegalArgumentException if n &lt; 3.
-	 */
-	public static int[] nextIntTriple(int n, int[] result, boolean sort, Random gen) {
-		if (result == null || result.length < 3) result = new int[3];
-		result[0] = nextInt(n, gen);
-		result[1] = nextInt(n-1, gen);
-		result[2] = nextInt(n-2, gen);
-		if (sort) adjustSortTriple(result);
-		else adjustTriple(result);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 3 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).  
-	 * @throws IllegalArgumentException if n &lt; 3.
-	 */
-	public static int[] nextIntTriple(int n, int[] result, SplittableRandom gen) {
-		if (result == null || result.length < 3) result = new int[3];
-		result[0] = nextInt(n, gen);
-		result[1] = nextInt(n-1, gen);
-		result[2] = nextInt(n-2, gen);
-		adjustTriple(result);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, without replacement, from the
-	 * set of integers in the interval [0, n).  All n choose 3 combinations are equally
-	 * likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers from the interval [0, n).  
-	 * @throws IllegalArgumentException if n &lt; 3.
-	 */
-	public static int[] nextIntTriple(int n, int[] result, Random gen) {
-		if (result == null || result.length < 3) result = new int[3];
-		result[0] = nextInt(n, gen);
-		result[1] = nextInt(n-1, gen);
-		result[2] = nextInt(n-2, gen);
-		adjustTriple(result);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length,
-	 * where an "array mask" is an array of boolean values of the same length as another array.
-	 * Each position in the result is equally likely true or false.</p>
-	 * <p>Runtime: O(n).</p>
-	 * <p>This method uses ThreadLocalRandom as the source of randomness,
-	 * and is thus safe, and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @return An array of n randomly generated boolean values.
-	 */
-	public static boolean[] arrayMask(int n) {
-		return arrayMask(n, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length,
-	 * where an "array mask" is an array of boolean values of the same length as another array.
-	 * Each position in the result is equally likely true or false.</p>
-	 * <p>Runtime: O(n).</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @param gen The source of randomness.
-	 * @return An array of n randomly generated boolean values.
-	 */
-	public static boolean[] arrayMask(int n, SplittableRandom gen) {
-		boolean[] result = new boolean[n];
-		for (int i = 0; i < n; i++) {
-			result[i] = gen.nextBoolean();
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length,
-	 * where an "array mask" is an array of boolean values of the same length as another array.
-	 * Each position in the result is equally likely true or false.</p>
-	 * <p>Runtime: O(n).</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @param gen The source of randomness.
-	 * @return An array of n randomly generated boolean values.
-	 */
-	public static boolean[] arrayMask(int n, Random gen) {
-		boolean[] result = new boolean[n];
-		for (int i = 0; i < n; i++) {
-			result[i] = gen.nextBoolean();
-		}
-		return result;
-	}
-	
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length and specified number of true values,
-	 * where an "array mask" is an array of boolean values of the same length as another array.</p>
-	 * <p>Runtime: O(min(n, k<sup>2</sup>)), and it uses O(min(k, n-k)) random numbers.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, 
-	 * and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @param k The desired number of true values, which must be no greater than n.
-	 * @return An array of n boolean values, exactly k of which are equal to true.
-	 */
-	public static boolean[] arrayMask(int n, int k) {
-		return arrayMask(n, k, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length and specified number of true values,
-	 * where an "array mask" is an array of boolean values of the same length as another array.</p>
-	 * <p>Runtime: O(min(n, k<sup>2</sup>)), and it uses O(min(k, n-k)) random numbers.</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @param k The desired number of true values, which must be no greater than n.
-	 * @param gen The source of randomness.
-	 * @return An array of n boolean values, exactly k of which are equal to true.
-	 */
-	public static boolean[] arrayMask(int n, int k, SplittableRandom gen) {
-		boolean[] result = new boolean[n];
-		if (k >= n) {
-			for (int i = 0; i < n; i++) result[i] = true;
-		} else if (k > 0) {
-			int[] indexes = sample(n, k, null, gen);
-			for (int i = 0; i < k; i++) {
-				result[indexes[i]] = true;
-			}
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length and specified number of true values,
-	 * where an "array mask" is an array of boolean values of the same length as another array.</p>
-	 * <p>Runtime: O(min(n, k<sup>2</sup>)), and it uses O(min(k, n-k)) random numbers.</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @param k The desired number of true values, which must be no greater than n.
-	 * @param gen The source of randomness.
-	 * @return An array of n boolean values, exactly k of which are equal to true.
-	 */
-	public static boolean[] arrayMask(int n, int k, Random gen) {
-		boolean[] result = new boolean[n];
-		if (k >= n) {
-			for (int i = 0; i < n; i++) result[i] = true;
-		} else if (k > 0) {
-			int[] indexes = sample(n, k, null, gen);
-			for (int i = 0; i < k; i++) {
-				result[indexes[i]] = true;
-			}
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length,
-	 * where an "array mask" is an array of boolean values of the same length as another array.</p>
-	 * <p>Runtime: O(n), and it uses O(n) random doubles.</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, 
-	 * and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @param p The probability that an element of the result is true.
-	 * @return An array of n boolean values, such that each element is true with probability p.
-	 * @since 1.5
-	 */
-	public static boolean[] arrayMask(int n, double p) {
-		return arrayMask(n, p, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length,
-	 * where an "array mask" is an array of boolean values of the same length as another array.</p>
-	 * <p>Runtime: O(n), and it uses O(n) random doubles.</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @param p The probability that an element of the result is true.
-	 * @param gen The source of randomness.
-	 * @return An array of n boolean values, such that each element is true with probability p.
-	 * @since 1.5
-	 */
-	public static boolean[] arrayMask(int n, double p, SplittableRandom gen) {
-		boolean[] result = new boolean[n];
-		if (p >= 1) {
-			for (int i = 0; i < n; i++) result[i] = true;
-		} else if (p > 0) {
-			int[] s = sample(n, p, gen);
-			for (int i = 0; i < s.length; i++) {
-				result[s[i]] = true;
-			}
-		}
-		return result;
-	}
-	
-	/**
-	 * <p>Generates an "array mask" of a specified length,
-	 * where an "array mask" is an array of boolean values of the same length as another array.</p>
-	 * <p>Runtime: O(n), and it uses O(n) random doubles.</p>
-	 *
-	 * @param n The length of the array mask.
-	 * @param p The probability that an element of the result is true.
-	 * @param gen The source of randomness.
-	 * @return An array of n boolean values, such that each element is true with probability p.
-	 * @since 1.5
-	 */
-	public static boolean[] arrayMask(int n, double p, Random gen) {
-		boolean[] result = new boolean[n];
-		if (p >= 1) {
-			for (int i = 0; i < n; i++) result[i] = true;
-		} else if (p > 0) {
-			int[] s = sample(n, p, gen);
-			for (int i = 0; i < s.length; i++) {
-				result[s[i]] = true;
-			}
-		}
-		return result;
-	}
-	
-	
-	
-	/**
-	 * <p>Generates a random sample of 2 integers, i, j, without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window.  
-	 * All pairs that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, 
-	 * and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the pair.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 2, then this method will construct an array for the result. 
-	 * @return An array containing the pair of 
-	 * randomly chosen integers, i, j, 
-	 * from the interval [0, n), such that |i-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 1 or n &lt; 2.
-	 */
-	public static int[] nextWindowedIntPair(int n, int window, int[] result) {
-		return nextWindowedIntPair(n, window, result, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of 2 integers, i, j, without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window.  
-	 * All pairs that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the pair.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 2, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers, i, j, 
-	 * from the interval [0, n), such that |i-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 1 or n &lt; 2.
-	 */
-	public static int[] nextWindowedIntPair(int n, int window, int[] result, SplittableRandom gen) {
-		if (window >= n - 1) return nextIntPair(n, result, gen);
-		if (result == null || result.length < 2) result = new int[2];
-		final int z1 = n - window;
-		final int z2 = z1 + z1;
-		int i = nextInt(z2 + window - 1, gen);
-		int j = nextInt(window, gen);
-		setAndAdjustWindowedPair(result, i, j, z1, z2);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 2 integers, i, j, without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window.  
-	 * All pairs that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the pair.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 2, then this method will construct an array for the result. 
-	 * @param gen Source of randomness.
-	 * @return An array containing the pair of 
-	 * randomly chosen integers, i, j, 
-	 * from the interval [0, n), such that |i-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 1 or n &lt; 2.
-	 */
-	public static int[] nextWindowedIntPair(int n, int window, int[] result, Random gen) {
-		if (window >= n - 1) return nextIntPair(n, result, gen);
-		if (result == null || result.length < 2) result = new int[2];
-		final int z1 = n - window;
-		final int z2 = z1 + z1;
-		int i = nextInt(z2 + window - 1, gen);
-		int j = nextInt(window, gen);
-		setAndAdjustWindowedPair(result, i, j, z1, z2);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, i, j, k without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * All triples that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, 
-	 * and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the triple.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @return An array containing the triple of 
-	 * randomly chosen integers, i, j, k 
-	 * from the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 2 or n &lt; 3.
-	 */
-	public static int[] nextWindowedIntTriple(int n, int window, int[] result) {
-		return nextWindowedIntTriple(n, window, result, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, i, j, k without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * All triples that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 * <p>This method uses ThreadLocalRandom as the 
-	 * pseudorandom number generator, and is thus safe, 
-	 * and efficient (i.e., non-blocking), for use with threads.</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the triple.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result.
-	 * @param sort If true, the result is sorted in increasing order, otherwise it is in random order.
-	 * @return An array containing the triple of 
-	 * randomly chosen integers, i, j, k 
-	 * from the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 2 or n &lt; 3.
-	 * @since 2.0
-	 */
-	public static int[] nextWindowedIntTriple(int n, int window, int[] result, boolean sort) {
-		return nextWindowedIntTriple(n, window, result, sort, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, i, j, k without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * All triples that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the triple.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @param gen The source of randomness.
-	 * @return An array containing the triple of 
-	 * randomly chosen integers, i, j, k 
-	 * from the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 2 or n &lt; 3.
-	 */
-	public static int[] nextWindowedIntTriple(int n, int window, int[] result, SplittableRandom gen) {
-		if (window >= n - 1) return nextIntTriple(n, result, gen);
-		if (result == null || result.length < 3) result = new int[3];
-		final int z1 = n - window;
-		final int z3 = 3*z1;
-		int i = nextInt(z3 + window - 2, gen);
-		int j = nextInt(window, gen);
-		int k = nextInt(window - 1, gen);
-		setAndAdjustWindowedTriple(result, i, j, k, z1, z3);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, i, j, k without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * All triples that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the triple.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result.
-	 * @param sort If true, the result is sorted in increasing order, otherwise it is in random order.
-	 * @param gen The source of randomness.
-	 * @return An array containing the triple of 
-	 * randomly chosen integers, i, j, k 
-	 * from the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 2 or n &lt; 3.
-	 * @since 2.0
-	 */
-	public static int[] nextWindowedIntTriple(int n, int window, int[] result, boolean sort, SplittableRandom gen) {
-		if (window >= n - 1) return nextIntTriple(n, result, sort, gen);
-		if (result == null || result.length < 3) result = new int[3];
-		final int z1 = n - window;
-		final int z3 = 3*z1;
-		int i = nextInt(z3 + window - 2, gen);
-		int j = nextInt(window, gen);
-		int k = nextInt(window - 1, gen);
-		if (sort) sortSetAndAdjustWindowedTriple(result, i, j, k, z1, z3);
-		else setAndAdjustWindowedTriple(result, i, j, k, z1, z3);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, i, j, k without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * All triples that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the triple.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @param gen The source of randomness.
-	 * @return An array containing the triple of 
-	 * randomly chosen integers, i, j, k 
-	 * from the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 2 or n &lt; 3.
-	 */
-	public static int[] nextWindowedIntTriple(int n, int window, int[] result, Random gen) {
-		if (window >= n - 1) return nextIntTriple(n, result, gen);
-		if (result == null || result.length < 3) result = new int[3];
-		final int z1 = n - window;
-		final int z3 = 3*z1;
-		int i = nextInt(z3 + window - 2, gen);
-		int j = nextInt(window, gen);
-		int k = nextInt(window - 1, gen);
-		setAndAdjustWindowedTriple(result, i, j, k, z1, z3);
-		return result;
-	}
-	
-	/**
-	 * <p>Generates a random sample of 3 integers, i, j, k without replacement, from the
-	 * set of integers in the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * All triples that satisfy the window constraint are equally likely.</p>
-	 * <p>The runtime is O(1).</p>
-	 *
-	 * @param n The number of integers to choose from.
-	 * @param window The maximum difference between the integers of the triple.
-	 * @param result An array to hold the pair that is generated.  If result is null
-	 * or if result.length is less than 3, then this method will construct an array for the result. 
-	 * @param sort If true, the result is sorted in increasing order, otherwise it is in random order.
-	 * @param gen The source of randomness.
-	 * @return An array containing the triple of 
-	 * randomly chosen integers, i, j, k 
-	 * from the interval [0, n), such that |i-j| &le; window, and 
-	 * |i-k| &le; window, and |k-j| &le; window.  
-	 * @throws IllegalArgumentException if window &lt; 2 or n &lt; 3.
-	 * @since 2.0
-	 */
-	public static int[] nextWindowedIntTriple(int n, int window, int[] result, boolean sort, Random gen) {
-		if (window >= n - 1) return nextIntTriple(n, result, sort, gen);
-		if (result == null || result.length < 3) result = new int[3];
-		final int z1 = n - window;
-		final int z3 = 3*z1;
-		int i = nextInt(z3 + window - 2, gen);
-		int j = nextInt(window, gen);
-		int k = nextInt(window - 1, gen);
-		if (sort) sortSetAndAdjustWindowedTriple(result, i, j, k, z1, z3);
-		else setAndAdjustWindowedTriple(result, i, j, k, z1, z3);
-		return result;
-	}
-	
-	
-	
-	private static void adjustTriple(int[] result) {
-		if (result[1] >= result[0]) {
-			result[1]++;
-			if (result[2] >= result[0]) {
-				result[2]++;
-				if (result[2] >= result[1]) result[2]++;
-			}
-		} else {
-			if (result[2] >= result[1]) {
-				result[2]++;
-				if (result[2] >= result[0]) result[2]++;
-			}
-		}
-	}
-	
-	private static void adjustSortTriple(int[] result) {
-		if (result[1] >= result[0]) {
-			result[1]++;
-		} else {
-			int temp = result[0];
-			result[0] = result[1];
-			result[1] = temp;
-		}
-		if (result[2] >= result[0]) {
-			result[2]++;
-			if (result[2] >= result[1]) {
-				result[2]++;
-			} else {
-				int temp = result[1];
-				result[1] = result[2];
-				result[2] = temp;
-			}
-		} else {
-			int temp = result[2];
-			result[2] = result[1];
-			result[1] = result[0];
-			result[0] = temp;
-		}
-	}
-	
-	private static void setAndAdjustWindowedPair(int[] result, int i, int j, final int z1, final int z2) {
-		if (i < z2) {
-			if ((i & 1) == 0) {
-				result[0] = i >> 1;
-				result[1] = result[0] + 1 + j;
-			} else {
-				result[1] = i >> 1;
-				result[0] = result[1] + 1 + j;
-			}
-		} else {
-			i -= z1;
-			j += z1;
-			if (i >= j) result[0] = i + 1;
-			else result[0] = i;
-			result[1] = j;
-		}
-	}
-	
-	
-	private static void setAndAdjustWindowedTriple(int[] result, int i, int j, int k, final int z1, final int z3) {
-		if (k >= j) {
-			k++;
-		}
-		if (i < z3) {
-			int q = i / 3;
-			int r = i % 3;
-			result[r] = q;
-			if (r==0) {
-				result[1] = q + 1 + j;
-				result[2] = q + 1 + k;
-			} else if (r==1) {
-				result[0] = q + 1 + j;
-				result[2] = q + 1 + k;
-			} else {
-				result[0] = q + 1 + j;
-				result[1] = q + 1 + k;
-			}
-		} else {
-			i = i - z3 + z1;
-			j += z1;
-			k += z1;
-			if (j < k) {
-				if (i >= j) {
-					i++;
-					if (i >= k) i++;
-				}
-			} else {
-				if (i >= k) {
-					i++;
-					if (i >= j) i++;
-				}
-			}
-			result[0] = i;
-			result[1] = j;
-			result[2] = k;
-		}
-	}
-	
-	private static void sortSetAndAdjustWindowedTriple(int[] result, int i, int j, int k, final int z1, final int z3) {
-		if (k >= j) {
-			k++;
-		} else {
-			int t = j;
-			j = k;
-			k = t;
-		}
-		if (i < z3) {
-			int q = i / 3;
-			result[0] = q;
-			result[1] = q + 1 + j;
-			result[2] = q + 1 + k;
-		} else {
-			i = i - z3 + z1;
-			j += z1;
-			k += z1;
-			if (i >= j) {
-				i++;
-				result[0] = j;
-				if (i >= k) {
-					i++;
-					result[1] = k;
-					result[2] = i;
-				} else {
-					result[1] = i;
-					result[2] = k;
-				}
-			} else {
-				result[0] = i;
-				result[1] = j;
-				result[2] = k;
-			}			
-		}
-	}
-	
-}
\ No newline at end of file
diff --git a/src/org/cicirello/math/rand/RandomVariates.java b/src/org/cicirello/math/rand/RandomVariates.java
deleted file mode 100644
index 98b445a5..00000000
--- a/src/org/cicirello/math/rand/RandomVariates.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright 2019 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package org.cicirello.math.rand;
-
-import org.cicirello.math.MathFunctions;
-import java.util.SplittableRandom;
-import java.util.Random;
-import java.util.concurrent.ThreadLocalRandom;
-
-/**
- * This utility class provides methods for generating random variates from
- * different distributions.
- *
- * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a> 
- * @version 8.28.2019
- * @since 2.0
- *
- */
-public final class RandomVariates {
-	
-	/*
-	 * Utility class of static methods, so constructor is private to prevent
-	 * instantiation.
-	 */
-	private RandomVariates() {}
-	
-	/**
-	 * Generates a pseudorandom integer from a binomial distribution.
-	 * The source of randomness is via the {@link ThreadLocalRandom}
-	 * class, and thus this method is both safe and efficient for use with threads.
-	 * @param n Number of trials for the binomial distribution.
-	 * @param p The probability of a successful trial.
-	 * @return A pseudorandom integer from a binomial distribution.
-	 */
-	public static int nextBinomial(int n, double p) {
-		return BTPE.nextBinomial(n, p, ThreadLocalRandom.current());
-	}
-	
-	/**
-	 * Generates a pseudorandom integer from a binomial distribution.
-	 * @param n Number of trials for the binomial distribution.
-	 * @param p The probability of a successful trial.
-	 * @param r The source of randomness.
-	 * @return A pseudorandom integer from a binomial distribution.
-	 */
-	public static int nextBinomial(int n, double p, Random r) {
-		return BTPE.nextBinomial(n, p, r);
-	}
-	
-	/**
-	 * Generates a pseudorandom integer from a binomial distribution.
-	 * @param n Number of trials for the binomial distribution.
-	 * @param p The probability of a successful trial.
-	 * @param r The source of randomness.
-	 * @return A pseudorandom integer from a binomial distribution.
-	 */
-	public static int nextBinomial(int n, double p, SplittableRandom r) {
-		return BTPE.nextBinomial(n, p, r);
-	}	
-	
-	/**
-	 * Generates a pseudorandom number from a Cauchy distribution.
-	 * @param median The median of the Cauchy.
-	 * @param scale The scale parameter of the Cauchy.
-	 * @return a pseudorandom number from a Cauchy distribution
-	 */
-	public static double nextCauchy(double median, double scale) {
-		return median + internalNextCauchy(scale, internalNextTransformedU(ThreadLocalRandom.current(), ThreadLocalRandom.current().nextDouble()));
-	}
-	
-	/**
-	 * Generates a pseudorandom number from a Cauchy distribution with median 0
-	 * and chosen scale parameter.
-	 * @param scale The scale parameter of the Cauchy.
-	 * @return a pseudorandom number from a Cauchy distribution
-	 */
-	public static double nextCauchy(double scale) {
-		return internalNextCauchy(scale, internalNextTransformedU(ThreadLocalRandom.current(), ThreadLocalRandom.current().nextDouble()));
-	}
-	
-	/**
-	 * Generates a pseudorandom number from a Cauchy distribution.
-	 * @param median The median of the Cauchy.
-	 * @param scale The scale parameter of the Cauchy.
-	 * @param r The source of randomness.
-	 * @return a pseudorandom number from a Cauchy distribution
-	 */
-	public static double nextCauchy(double median, double scale, Random r) {
-		return median + internalNextCauchy(scale, internalNextTransformedU(r, r.nextDouble()));
-	}
-	
-	/**
-	 * Generates a pseudorandom number from a Cauchy distribution with median 0
-	 * and chosen scale parameter.
-	 * @param scale The scale parameter of the Cauchy.
-	 * @param r The source of randomness.
-	 * @return a pseudorandom number from a Cauchy distribution
-	 */
-	public static double nextCauchy(double scale, Random r) {
-		return internalNextCauchy(scale, internalNextTransformedU(r, r.nextDouble()));
-	}
-	
-	/**
-	 * Generates a pseudorandom number from a Cauchy distribution.
-	 * @param median The median of the Cauchy.
-	 * @param scale The scale parameter of the Cauchy.
-	 * @param r The source of randomness.
-	 * @return a pseudorandom number from a Cauchy distribution
-	 */
-	public static double nextCauchy(double median, double scale, SplittableRandom r) {
-		return median + internalNextCauchy(scale, internalNextTransformedU(r, r.nextDouble()));
-	}
-	
-	/**
-	 * Generates a pseudorandom number from a Cauchy distribution with median 0
-	 * and chosen scale parameter.
-	 * @param scale The scale parameter of the Cauchy.
-	 * @param r The source of randomness.
-	 * @return a pseudorandom number from a Cauchy distribution
-	 */
-	public static double nextCauchy(double scale, SplittableRandom r) {
-		return internalNextCauchy(scale, internalNextTransformedU(r, r.nextDouble()));
-	}
-	
-	/*
-	 * INTERNAL METHODS FOR nextCauchy start here.
-	 *
-	 * Inverse Method:
-	 *      Mathematically, it should be: median + scale * tan(PI * (u - 0.5)),
-	 * where u is uniformly random from the interval [0, 1].
-	 * However, since tan goes through one complete cycle every PI,
-	 * we can replace it with: median + scale * tan(PI * u), going from
-	 * 0 to PI, rather than from -PI/2 to PI/2.  This is equivalent
-	 * as far as generating a random Cauchy variate is concerned, but saves
-	 * one arithmetic operation.  
-	 *     At first glance, it may appear as if we might be
-	 * doubly sampling u == 0 since tan(0)==tan(PI), however, our uniform
-	 * random numbers are generated from [0, 1), so that right endpoint will never
-	 * be sampled.
-	 *     We have one special case to consider.  When u==0.5, we have tan(PI/2),
-	 * which is undefined.  In the limit, however, tan(PI/2) is infinity.
-	 * We could map this to the constant for infinity.  However, this would introduce
-	 * a very slight bias in favor of positive results since our interval considers
-	 * from tan(0) through tan(PI-epsilon), which doesn't include tan(-PI/2), though it 
-	 * comes close since tan(PI/2+epsilon)==tan(-PI/2+epsilon).  In the limit, tan(-PI/2)
-	 * is -infinity.  So mapping tan(PI/2) to infinity would result in one extra value that
-	 * leads to a positive result relative to the number of values that lead to negative results.
-	 *     We handle this in the following way.  First, when u==0.5, we generate a 
-	 * random boolean to control whether u==0.5 means PI/2 or -PI/2.  Second, rather than
-	 * map to the constants for positive and negative infinity from the Double class, we
-	 * pass these along to the tan method and let it do its thing numerically, which is
-	 * a value around 1.6ish * 10 to the power 16 (and negative of that in the case of -PI/2).
-	 *
-	 */
-	 
-	/*
-	 * package-private to facilitate testing.
-	 */
-	static double internalNextCauchy(double scale, double u) {
-		return scale * StrictMath.tan(StrictMath.PI * u);
-	}
-	
-	/*
-	 * package-private to facilitate testing.
-	 */
-	static double internalNextTransformedU(Random r, double u) {
-		return u == 0.5 && r.nextBoolean() ? -0.5 : u;
-	}
-	
-	/*
-	 * package-private to facilitate testing.
-	 */
-	static double internalNextTransformedU(SplittableRandom r, double u) {
-		return u == 0.5 && r.nextBoolean() ? -0.5 : u;
-	}
-}
\ No newline at end of file
diff --git a/src/org/cicirello/math/rand/package-info.java b/src/org/cicirello/math/rand/package-info.java
deleted file mode 100644
index 3a210c85..00000000
--- a/src/org/cicirello/math/rand/package-info.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2019 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
- 
- /**
- * Collection of classes related to random number generation. 
- * @version 1.19.5.20
- * @since 1.4
- */
-package org.cicirello.math.rand;
-
-
diff --git a/src/org/cicirello/math/stats/Statistics.java b/src/org/cicirello/math/stats/Statistics.java
deleted file mode 100644
index 7e81ab05..00000000
--- a/src/org/cicirello/math/stats/Statistics.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright 2018-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-package org.cicirello.math.stats;
-
-/**
- * Utility class of basic statistics.
- *
- * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a> 
- * @version 5.13.2021
- */
-public final class Statistics {
-	
-	// Utility class of static methods.  Should not be instantiated.
-	private Statistics() {}
-	
-	/**
-	 * Computes mean of a dataset.
-	 * @param data The dataset.
-	 * @return the mean of the data.
-	 */
-	public static double mean(int[] data) {
-		int sum = 0;
-		for (int e : data) sum = sum + e;
-		return 1.0 * sum / data.length;
-	}
-	
-	/**
-	 * Computes mean of a dataset.
-	 * @param data The dataset.
-	 * @return the mean of the data.
-	 */
-	public static double mean(double[] data) {
-		double mean = 0;
-		for (double e : data) mean = mean + e;
-		mean = mean / data.length;
-		return mean;
-	}
-	
-	/**
-	 * Computes variance of a population.
-	 * @param data The dataset.
-	 * @return the variance of the data.
-	 */
-	public static double variance(int[] data) {
-		if (data.length < 2) return 0.0;
-		double mean = mean(data);
-		double sumSquares = 0;
-		double sum = 0;
-		for (int e : data) {
-			sumSquares = sumSquares + (e-mean)*(e-mean); 
-			sum = sum + (e-mean);
-		}
-		return (sumSquares - sum*sum/data.length)/data.length;
-	}
-	
-	
-	/**
-	 * Computes variance of a population.
-	 * @param data The dataset.
-	 * @return the variance of the data.
-	 */
-	public static double variance(double[] data) {
-		if (data.length < 2) return 0.0;
-		double mean = mean(data);
-		double sumSquares = 0;
-		double sum = 0;
-		for (double e : data) {
-			sumSquares = sumSquares + (e-mean)*(e-mean); 
-			sum = sum + (e-mean);
-		}
-		return (sumSquares - sum*sum/data.length)/data.length;
-	}
-	
-	/**
-	 * Computes variance of a sample.
-	 * @param data The dataset.
-	 * @return the variance of the data.
-	 */
-	public static double varianceSample(int[] data) {
-		if (data.length < 2) return 0.0;
-		double mean = mean(data);
-		double sumSquares = 0;
-		double sum = 0;
-		for (int e : data) {
-			sumSquares = sumSquares + (e-mean)*(e-mean); 
-			sum = sum + (e-mean);
-		}
-		return (sumSquares - sum*sum/data.length)/(data.length-1.0);
-	}
-	
-	
-	/**
-	 * Computes variance of a sample.
-	 * @param data The dataset.
-	 * @return the variance of the data.
-	 */
-	public static double varianceSample(double[] data) {
-		if (data.length < 2) return 0.0;
-		double mean = mean(data);
-		double sumSquares = 0;
-		double sum = 0;
-		for (double e : data) {
-			sumSquares = sumSquares + (e-mean)*(e-mean); 
-			sum = sum + (e-mean);
-		}
-		return (sumSquares - sum*sum/data.length)/(data.length-1.0);
-	}
-	
-	/**
-	 * Computes covariance for a pair of random variables.
-	 * @param X Array of samples of first variable.
-	 * @param Y Array of samples of second variable.
-	 * @return the covariance of X and Y.
-	 */
-	public static double covariance(int[] X, int[] Y) {
-		if (X.length < 2) return 0.0;
-		if (X.length != Y.length) throw new IllegalArgumentException("Arrays must have same length!");
-		double meanX = mean(X);
-		double meanY = mean(Y);
-		double sumX = 0;
-		double sumY = 0;
-		double sumProduct = 0;
-		for (int i = 0; i < X.length; i++) {
-			sumX = sumX + (X[i] - meanX);
-			sumY = sumY + (Y[i] - meanY);
-			sumProduct = sumProduct + (X[i] - meanX)*(Y[i] - meanY);
-		}
-		return (sumProduct - sumX*sumY/X.length)/X.length;
-	}
-	
-		
-	/**
-	 * Computes covariance for a pair of random variables.
-	 * @param X Array of samples of first variable.
-	 * @param Y Array of samples of second variable.
-	 * @return the covariance of X and Y.
-	 */
-	public static double covariance(double[] X, double[] Y) {
-		if (X.length < 2) return 0.0;
-		if (X.length != Y.length) throw new IllegalArgumentException("Arrays must have same length!");
-		double meanX = mean(X);
-		double meanY = mean(Y);
-		double sumX = 0;
-		double sumY = 0;
-		double sumProduct = 0;
-		for (int i = 0; i < X.length; i++) {
-			sumX = sumX + (X[i] - meanX);
-			sumY = sumY + (Y[i] - meanY);
-			sumProduct = sumProduct + (X[i] - meanX)*(Y[i] - meanY);
-		}
-		return (sumProduct - sumX*sumY/X.length)/X.length;
-	}
-	
-	/**
-	 * Computes correlation coefficient for a pair of random variables.
-	 * @param X Array of samples of first variable.
-	 * @param Y Array of samples of second variable.
-	 * @return the correlation coefficient of X and Y.
-	 */
-	public static double correlation(int[] X, int[] Y) {
-		double varX = variance(X);
-		if (varX == 0.0) return 0.0;
-		double varY = variance(Y);
-		if (varY == 0.0) return 0.0;
-		double covar = covariance(X,Y);
-		if (covar == 0.0) return 0.0;
-		
-		boolean negate = false;
-		if (covar < 0.0) {
-			negate = true;
-			covar = -covar;
-		}
-		double logCovar = Math.log(covar);
-		double logVarX = Math.log(varX);
-		double logVarY = Math.log(varY);
-		double corr = Math.exp(logCovar - 0.5 * logVarX - 0.5 * logVarY);
-		if (negate) corr = -corr;
-		return corr;
-	}
-	
-		
-	/**
-	 * Computes correlation coefficient for a pair of random variables.
-	 * @param X Array of samples of first variable.
-	 * @param Y Array of samples of second variable.
-	 * @return the correlation coefficient of X and Y.
-	 */
-	public static double correlation(double[] X, double[] Y) {
-		double varX = variance(X);
-		if (varX == 0.0) return 0.0;
-		double varY = variance(Y);
-		if (varY == 0.0) return 0.0;
-		double covar = covariance(X,Y);
-		if (covar == 0.0) return 0.0;
-		
-		boolean negate = false;
-		if (covar < 0.0) {
-			negate = true;
-			covar = -covar;
-		}
-		double logCovar = Math.log(covar);
-		double logVarX = Math.log(varX);
-		double logVarY = Math.log(varY);
-		double corr = Math.exp(logCovar - 0.5 * logVarX - 0.5 * logVarY);
-		if (negate) corr = -corr;
-		return corr;
-	}
-	
-	/**
-	 * Computes correlation matrix.
-	 * @param data The data with random variables in rows and samples in columns.
-	 * @return the correlation matrix, M, where M[i][j] is the correlation coefficient of data[i] and data[j].
-	 */
-	public static double[][] correlationMatrix(int[][] data) {
-		double[][] corr = new double[data.length][data.length];
-		for (int i = 0; i < data.length; i++) {
-			corr[i][i] = 1.0;
-			for (int j = i+1; j < data.length; j++) {
-				corr[i][j] = corr[j][i] = correlation(data[i], data[j]);
-			}
-		}
-		return corr;
-	}
-	
-	/**
-	 * Computes correlation matrix.
-	 * @param data The data with random variables in rows and samples in columns.
-	 * @return the correlation matrix, M, where M[i][j] is the correlation coefficient of data[i] and data[j].
-	 */
-	public static double[][] correlationMatrix(double[][] data) {
-		double[][] corr = new double[data.length][data.length];
-		for (int i = 0; i < data.length; i++) {
-			corr[i][i] = 1.0;
-			for (int j = i+1; j < data.length; j++) {
-				corr[i][j] = corr[j][i] = correlation(data[i], data[j]);
-			}
-		}
-		return corr;
-	}
-	
-	/**
-	 * Welch's t-test, also known as t-test with unequal variances.
-	 * The Welch's t-test can be used when variances are unequal and
-	 * is also applicable if sample sizes differ.
-	 *
-	 * @param data1 First dataset.
-	 * @param data2 Second dataset.
-	 * @return The t statistic.
-	 * @since 1.4
-	 */
-	public static double tTestUnequalVariances(double[] data1, double[] data2) {
-		return (mean(data1)-mean(data2)) / Math.sqrt(varianceSample(data1)/data1.length + varianceSample(data2)/data2.length);
-	}
-	
-	/**
-	 * Welch's t-test, also known as t-test with unequal variances.
-	 * The Welch's t-test can be used when variances are unequal and
-	 * is also applicable if sample sizes differ.
-	 *
-	 * @param data1 First dataset.
-	 * @param data2 Second dataset.
-	 * @return The t statistic.
-	 * @since 1.4
-	 */
-	public static double tTestUnequalVariances(int[] data1, int[] data2) {
-		return (mean(data1)-mean(data2)) / Math.sqrt(varianceSample(data1)/data1.length + varianceSample(data2)/data2.length);
-	}
-	
-	/**
-	 * Welch's t-test, also known as t-test with unequal variances.
-	 * The Welch's t-test can be used when variances are unequal and
-	 * is also applicable if sample sizes differ.  This method computes
-	 * both the t statistic, as well as the approximate degrees of freedom.
-	 *
-	 * @param data1 First dataset.
-	 * @param data2 Second dataset.
-	 * @return An array, a, of length 2 such that a[0] is the t statistic (as a Double object), and
-	 * a[1] is the degrees of freedom (as an Integer object).
-	 * @since 1.4
-	 */
-	public static Number[] tTestWelch(double[] data1, double[] data2) {
-		Number[] result = new Number[2];
-		double term1 = varianceSample(data1)/data1.length;
-		double term2 = varianceSample(data2)/data2.length;
-		double termSum = term1 + term2;
-		double vDenom = term1*term1/(data1.length-1) + term2*term2/(data2.length-1);
-		double v = termSum*termSum/vDenom; 
-		Double d = (mean(data1)-mean(data2)) / Math.sqrt(termSum);
-		result[0] = d;
-		Integer i = (int)v;
-		result[1] = i;
-		return result;
-	}
-	
-	/**
-	 * Welch's t-test, also known as t-test with unequal variances.
-	 * The Welch's t-test can be used when variances are unequal and
-	 * is also applicable if sample sizes differ.  This method computes
-	 * both the t statistic, as well as the approximate degrees of freedom.
-	 *
-	 * @param data1 First dataset.
-	 * @param data2 Second dataset.
-	 * @return An array, a, of length 2 such that a[0] is the t statistic (as a Double object), and
-	 * a[1] is the degrees of freedom (as an Integer object).
-	 * @since 1.4
-	 */
-	public static Number[] tTestWelch(int[] data1, int[] data2) {
-		Number[] result = new Number[2];
-		double term1 = varianceSample(data1)/data1.length;
-		double term2 = varianceSample(data2)/data2.length;
-		double termSum = term1 + term2;
-		double vDenom = term1*term1/(data1.length-1) + term2*term2/(data2.length-1);
-		double v = termSum*termSum/vDenom; 	
-		Double d = (mean(data1)-mean(data2)) / Math.sqrt(termSum);
-		result[0] = d;
-		Integer i = (int)v;
-		result[1] = i;
-		return result;
-	}
-}
-
diff --git a/src/org/cicirello/math/stats/package-info.java b/src/org/cicirello/math/stats/package-info.java
deleted file mode 100644
index 30c7b57c..00000000
--- a/src/org/cicirello/math/stats/package-info.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2018 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
- 
- /**
- * Collection of classes related to statistics. 
- * @version 1.18.7.1
- * @since 1.0
- */
-package org.cicirello.math.stats;
-
-
diff --git a/src/org/cicirello/util/Copyable.java b/src/org/cicirello/util/Copyable.java
deleted file mode 100644
index 61330f83..00000000
--- a/src/org/cicirello/util/Copyable.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2019-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- */
- 
-package org.cicirello.util;
-
-/**
- * A simple interface for objects that can be copied.
- *
- * @author <a href=https://www.cicirello.org/ target=_top>Vincent A. Cicirello</a>, 
- * <a href=https://www.cicirello.org/ target=_top>https://www.cicirello.org/</a>
- * @version 4.22.2021
- */
-public interface Copyable<T extends Copyable<T>> {
-	
-	/**
-	 * Creates an identical copy of this object.
-	 * @return an identical copy of this object.
-	 */
-	T copy();
-}
diff --git a/src/org/cicirello/util/package-info.java b/src/org/cicirello/util/package-info.java
deleted file mode 100644
index 191d85f2..00000000
--- a/src/org/cicirello/util/package-info.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2019-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
- 
- /**
- * This package includes classes and interfaces that serve some useful utility purpose. 
- * @version 4.22.2021
- */
-package org.cicirello.util;
diff --git a/src/overview.html b/src/overview.html
deleted file mode 100644
index 46e4ea26..00000000
--- a/src/overview.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<html>
-<body>
-<p>JavaPermutationTools (JPT) is a Java 
-library for representing and generating permutations and sequences, as well as performing 
-computation on permutations and sequences. This includes implementations of a variety of 
-permutation distance metrics as well as distance metrics on sequences (i.e., Strings, arrays, 
-and other ordered data types).</p>
-<p>The <a href="https://github.com/cicirello/JavaPermutationTools" target=_top>source code</a> 
-is hosted on GitHub; and is licensed under the 
-<a href="https://www.gnu.org/licenses/gpl-3.0.html" target=_top>GNU General Public License 
-Version 3 (GPLv3)</a>. The library's jar files are regularly published 
-to <a href="https://search.maven.org/artifact/org.cicirello/jpt" target=_top>Maven Central</a>, 
-from which it is easily imported into software projects using Maven and other 
-commonly used build tools.  Additionally, there is a 
-GitHub <a href="https://github.com/cicirello/jpt-examples" target=_top>repository of 
-example programs</a> that show basic usage of the JPT library, as well as replication programs that 
-reproduce results found in published papers.</p>
-<p>For more information see the <a href="https://jpt.cicirello.org/" target=_top>JavaPermutationTools website</a>.
-Potential contributors, please see the source repository, and/or 
-contact <a href="https://www.cicirello.org/" target=_top>Vincent A. Cicirello</a>.</p>
-</body>
-</html>
\ No newline at end of file
diff --git a/tests/org/cicirello/permutations/PermutationTestCases.java b/src/test/java/org/cicirello/permutations/PermutationTestCases.java
similarity index 100%
rename from tests/org/cicirello/permutations/PermutationTestCases.java
rename to src/test/java/org/cicirello/permutations/PermutationTestCases.java
diff --git a/tests/org/cicirello/permutations/distance/PermutationDistanceMaxTests.java b/src/test/java/org/cicirello/permutations/distance/PermutationDistanceMaxTests.java
similarity index 100%
rename from tests/org/cicirello/permutations/distance/PermutationDistanceMaxTests.java
rename to src/test/java/org/cicirello/permutations/distance/PermutationDistanceMaxTests.java
diff --git a/tests/org/cicirello/permutations/distance/PermutationDistanceNormTests.java b/src/test/java/org/cicirello/permutations/distance/PermutationDistanceNormTests.java
similarity index 100%
rename from tests/org/cicirello/permutations/distance/PermutationDistanceNormTests.java
rename to src/test/java/org/cicirello/permutations/distance/PermutationDistanceNormTests.java
diff --git a/tests/org/cicirello/permutations/distance/PermutationDistanceTests.java b/src/test/java/org/cicirello/permutations/distance/PermutationDistanceTests.java
similarity index 100%
rename from tests/org/cicirello/permutations/distance/PermutationDistanceTests.java
rename to src/test/java/org/cicirello/permutations/distance/PermutationDistanceTests.java
diff --git a/tests/org/cicirello/sequences/SequenceSamplerTests.java b/src/test/java/org/cicirello/sequences/SequenceSamplerTests.java
similarity index 100%
rename from tests/org/cicirello/sequences/SequenceSamplerTests.java
rename to src/test/java/org/cicirello/sequences/SequenceSamplerTests.java
diff --git a/tests/org/cicirello/sequences/distance/SequenceDistanceTests.java b/src/test/java/org/cicirello/sequences/distance/SequenceDistanceTests.java
similarity index 100%
rename from tests/org/cicirello/sequences/distance/SequenceDistanceTests.java
rename to src/test/java/org/cicirello/sequences/distance/SequenceDistanceTests.java
diff --git a/tests/org/cicirello/math/MathFunctionsTests.java b/tests/org/cicirello/math/MathFunctionsTests.java
deleted file mode 100644
index f31cbe87..00000000
--- a/tests/org/cicirello/math/MathFunctionsTests.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2019 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-
-package org.cicirello.math;
-
-import org.junit.*;
-import static org.junit.Assert.*;
-
-/**
- * Test cases for the methods of the MathFunctions class.
- */
-public class MathFunctionsTests {
-	
-	// precision for floating-point comparisons
-	private static final double EPSILON = 1e-10;
-	
-	@Test
-	public void testPow() {
-		for (int i = 0; i <= 16; i++) {
-			assertEquals(Math.pow(5.0, i), MathFunctions.pow(5.0, i), EPSILON);
-		}
-		for (int i = -1; i >= -16; i--) {
-			assertEquals(Math.pow(5.0, i), MathFunctions.pow(5.0, i), EPSILON);
-		}
-	}
-	
-	@Test
-	public void testLogGamma() {
-		final int MAX = 20;
-		final int D_MAX = 35;
-		
-		long fact = 1;
-		for (int n = 1; n <= MAX+1; n++) {
-			long gammaOfN = fact;
-			double expected = StrictMath.log(gammaOfN);
-			assertEquals(expected, MathFunctions.logGamma(n), EPSILON);
-			if (n < MAX+1) fact = fact * ((long)n);
-		}
-		double f = fact;
-		f = f * (MAX+1);
-		for (int n = MAX+2; n <= D_MAX; n++) {
-			double gammaOfN = f;
-			double expected = StrictMath.log(gammaOfN);
-			assertEquals(expected, MathFunctions.logGamma(n), EPSILON);
-			f = f * n;
-		}
-		double gammaOf100=0;
-		for (int n = D_MAX+1; n <= 100; n++) {
-			gammaOf100 = f;
-			f = f * n;
-		}
-		assertEquals(StrictMath.log(gammaOf100), MathFunctions.logGamma(100), EPSILON);
-		double logGammaOf1000 = StrictMath.log(gammaOf100);
-		for (int n = 100; n < 1000; n++) {
-			logGammaOf1000 += StrictMath.log(n);
-		}
-		assertEquals(logGammaOf1000, MathFunctions.logGamma(1000), EPSILON);
-		double logGammaOf2000 = logGammaOf1000;
-		for (int n = 1000; n < 2000; n++) {
-			logGammaOf2000 += StrictMath.log(n);
-		}
-		assertEquals(logGammaOf2000, MathFunctions.logGamma(2000), EPSILON);
-	}
-	
-	@Test
-	public void testLogGammaExtremes() {
-		assertEquals(Double.POSITIVE_INFINITY, MathFunctions.logGamma(Double.POSITIVE_INFINITY), EPSILON);
-		assertEquals(Double.NEGATIVE_INFINITY, MathFunctions.logGamma(Double.NEGATIVE_INFINITY), EPSILON);
-		final double HUGE = 2.556348e305;
-		final double LARGEST_NEGATIVE_NONINTEGER_D = -4.5035996273704955e15;
-		final double TOO_NEGATIVE = LARGEST_NEGATIVE_NONINTEGER_D - Math.ulp(LARGEST_NEGATIVE_NONINTEGER_D);
-		assertEquals(Double.POSITIVE_INFINITY, MathFunctions.logGamma(HUGE), EPSILON);
-		assertEquals(Double.NEGATIVE_INFINITY, MathFunctions.logGamma(-HUGE), EPSILON);
-		assertEquals(Double.POSITIVE_INFINITY, MathFunctions.logGamma(TOO_NEGATIVE), EPSILON);
-		assertEquals(Double.POSITIVE_INFINITY, MathFunctions.logGamma(-35.0), EPSILON);
-		assertEquals(Math.log(2.4163319638582305e-38), MathFunctions.logGamma(-34.1), EPSILON);
-		assertEquals(Math.log(1.3299204362846542e-39), MathFunctions.logGamma(-34.6), EPSILON);
-		assertEquals(1.0e8*(Math.log(1.0e8 + 0.5)-1)+0.4189385332, MathFunctions.logGamma(1.0e8 + 0.5), EPSILON);
-		assertEquals(Double.POSITIVE_INFINITY, MathFunctions.logGamma(0), EPSILON);
-		assertEquals(Math.log(1.329340388179137), MathFunctions.logGamma(2.5), EPSILON);
-		assertEquals(Math.log(0.94530872048294), MathFunctions.logGamma(-2.5), EPSILON);
-	}
-	
-}
\ No newline at end of file
diff --git a/tests/org/cicirello/math/la/LinearAlgebraTests.java b/tests/org/cicirello/math/la/LinearAlgebraTests.java
deleted file mode 100644
index 7bd03249..00000000
--- a/tests/org/cicirello/math/la/LinearAlgebraTests.java
+++ /dev/null
@@ -1,895 +0,0 @@
-/*
- * Copyright 2018-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-
-package org.cicirello.math.la;
-
-import org.junit.*;
-import static org.junit.Assert.*;
-import java.util.Random;
-
-/**
- * JUnit 4 tests for the methods of the Statistics class.
- */
-public class LinearAlgebraTests {
-	
-	private static double EPSILON = 1e-10;
-	
-	@Test
-	public void testTransposeInts() {
-		for (int n = 0; n < 5; n++) {
-			int[][] m = getSquareIntsForTranspose(n);
-			int[][] transpose = getSquareIntsForTranspose(n);
-			int[][] result = MatrixOps.transposeSquareMatrixInline(transpose);
-			assertEquals("should return reference to parameter", (Object)transpose, (Object)result);
-			for (int i = 0; i < n; i++) {
-				for (int j = 0; j < n; j++) {
-					assertEquals("position [i][j] should be equal to position [j][i] of original", m[j][i], transpose[i][j]);
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.transposeSquareMatrixInline(new int[1][2])
-		);
-	}
-	
-	@Test
-	public void testTransposeDoubles() {
-		for (int n = 0; n < 5; n++) {
-			double[][] m = getSquareDoublesForTranspose(n);
-			double[][] transpose = getSquareDoublesForTranspose(n);
-			double[][] result = MatrixOps.transposeSquareMatrixInline(transpose);
-			assertEquals("should return reference to parameter", (Object)transpose, (Object)result);
-			for (int i = 0; i < n; i++) {
-				for (int j = 0; j < n; j++) {
-					assertEquals("position [i][j] should be equal to position [j][i] of original", m[j][i], transpose[i][j], 0.0);
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.transposeSquareMatrixInline(new double[1][2])
-		);
-	}
-	
-	@Test
-	public void testSumInts() {
-		for (int n = 0; n < 5; n++) {
-			for (int m = 0; m < 5; m++) {
-				int[][] a = getMatrixAInts(n,m);
-				int[][] b = getMatrixBInts(n,m);
-				int[][] c = MatrixOps.sum(a,b,null);
-				assertEquals("result rows",n, c.length);
-				if (n > 0) assertEquals("result cols", m, c[0].length);
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("sum",n*m+1, c[i][j]);
-					}
-				}
-				c = null;
-				c = MatrixOps.sum(a,b);
-				assertEquals("result rows",n, c.length);
-				if (n > 0) assertEquals("result cols", m, c[0].length);
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("sum",n*m+1, c[i][j]);
-					}
-				}
-				int[][] c2 = new int[n][m];
-				int[][] c3 = MatrixOps.sum(a,b,c2);
-				assertEquals((Object)c2, (Object)c3);
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("sum",n*m+1, c2[i][j]);
-					}
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new int[1][2], new int[2][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new int[1][2], new int[1][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new int[1][1], new int[1][1], new int[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new int[1][1], new int[1][1], new int[2][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new int[0][0], new int[0][0], new int[1][2])
-		);
-	}
-	
-	@Test
-	public void testSumDoubles() {
-		for (int n = 0; n < 5; n++) {
-			for (int m = 0; m < 5; m++) {
-				double[][] a = getMatrixA_D(n,m);
-				double[][] b = getMatrixB_D(n,m);
-				double[][] c = MatrixOps.sum(a,b,null);
-				assertEquals("result rows",n, c.length);
-				if (n > 0) assertEquals("result cols", m, c[0].length);
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("sum",n*m+1, c[i][j], EPSILON);
-					}
-				}
-				c = null;
-				c = MatrixOps.sum(a,b);
-				assertEquals("result rows",n, c.length);
-				if (n > 0) assertEquals("result cols", m, c[0].length);
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("sum",n*m+1, c[i][j], EPSILON);
-					}
-				}
-				double[][] c2 = new double[n][m];
-				double[][] c3 = MatrixOps.sum(a,b,c2);
-				assertEquals((Object)c2, (Object)c3);
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("sum",n*m+1, c2[i][j], EPSILON);
-					}
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new double[1][2], new double[2][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new double[1][2], new double[1][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new double[1][1], new double[1][1], new double[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new double[1][1], new double[1][1], new double[2][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.sum(new double[0][0], new double[0][0], new double[1][2])
-		);
-	}
-	
-	@Test
-	public void testDifferenceInts() {
-		for (int n = 0; n < 5; n++) {
-			for (int m = 0; m < 5; m++) {
-				int[][] a = fillMatrix(n,m,n*m+1);
-				int[][] b = getMatrixBInts(n,m);
-				int[][] c = MatrixOps.difference(a,b,null);
-				assertEquals("result rows",n, c.length);
-				if (n > 0) assertEquals("result cols", m, c[0].length);
-				int k = 1;
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("diff",k, c[i][j]);
-						k++;
-					}
-				}
-				c = null;
-				c = MatrixOps.difference(a,b);
-				assertEquals("result rows",n, c.length);
-				if (n > 0) assertEquals("result cols", m, c[0].length);
-				k = 1;
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("diff",k, c[i][j]);
-						k++;
-					}
-				}
-				int[][] c2 = new int[n][m];
-				int[][] c3 = MatrixOps.difference(a,b,c2);
-				assertEquals((Object)c2, (Object)c3);
-				k = 1;
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("diff",k, c2[i][j]);
-						k++;
-					}
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new int[1][2], new int[2][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new int[1][2], new int[1][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new int[1][1], new int[1][1], new int[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new int[1][1], new int[1][1], new int[2][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new int[0][0], new int[0][0], new int[1][2])
-		);
-	}
-	
-	@Test
-	public void testDifferenceDoubles() {
-		for (int n = 0; n < 5; n++) {
-			for (int m = 0; m < 5; m++) {
-				double[][] a = fillMatrix(n,m,n*m+1.0);
-				double[][] b = getMatrixB_D(n,m);
-				double[][] c = MatrixOps.difference(a,b,null);
-				assertEquals("result rows",n, c.length);
-				if (n > 0) assertEquals("result cols", m, c[0].length);
-				int k = 1;
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("diff",k, c[i][j], EPSILON);
-						k++;
-					}
-				}
-				c = null;
-				c = MatrixOps.difference(a,b);
-				assertEquals("result rows",n, c.length);
-				if (n > 0) assertEquals("result cols", m, c[0].length);
-				k = 1;
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("diff",k, c[i][j], EPSILON);
-						k++;
-					}
-				}
-				double[][] c2 = new double[n][m];
-				double[][] c3 = MatrixOps.difference(a,b,c2);
-				assertEquals((Object)c2, (Object)c3);
-				k = 1;
-				for (int i = 0; i < n; i++) {
-					for (int j = 0; j < m; j++) {
-						assertEquals("diff",k, c2[i][j], EPSILON);
-						k++;
-					}
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new double[1][2], new double[2][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new double[1][2], new double[1][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new double[1][1], new double[1][1], new double[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new double[1][1], new double[1][1], new double[2][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.difference(new double[0][0], new double[0][0], new double[1][2])
-		);
-	}
-	
-	@Test
-	public void testProductIdentityInt() {
-		int[][] A = new int[0][0];
-		int[][] B = new int[0][0];
-		int[][] C = MatrixOps.product(A,B,null);
-		assertEquals(0, C.length);
-		C = new int[0][0];
-		int[][] C2 = MatrixOps.product(A,B,C);
-		assertEquals(0, C.length);
-		assertEquals((Object)C, (Object)C2);
-		C = null;
-		C = MatrixOps.product(A,B);
-		assertEquals(0, C.length);
-		for (int n = 1; n < 5; n++) {
-			int[][] I = getI(n);
-			for (int m = 1; m < 5; m++) {
-				C = C2 = null;
-				A = getMatrixAInts(n,m);
-				C = MatrixOps.product(I,A,null);
-				assertEquals(A.length, C.length);
-				assertEquals(A[0].length, C[0].length);
-				for (int i = 0; i < A.length; i++) {
-					assertArrayEquals(A[i], C[i]);
-				}
-				C = new int[n][m];
-				C2 = MatrixOps.product(I,A,C);
-				assertEquals((Object)C, (Object)C2);
-				assertEquals(A.length, C2.length);
-				assertEquals(A[0].length, C2[0].length);
-				for (int i = 0; i < A.length; i++) {
-					assertArrayEquals(A[i], C2[i]);
-				}
-				C = null;
-				C = MatrixOps.product(I,A);
-				assertEquals(A.length, C.length);
-				assertEquals(A[0].length, C[0].length);
-				for (int i = 0; i < A.length; i++) {
-					assertArrayEquals(A[i], C[i]);
-				}
-				B = getMatrixAInts(m,n);
-				C = MatrixOps.product(B,I,null);
-				assertEquals(B.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				for (int i = 0; i < B.length; i++) {
-					assertArrayEquals(B[i], C[i]);
-				}
-				C = new int[m][n];
-				C2 = MatrixOps.product(B,I,C);
-				assertEquals((Object)C, (Object)C2);
-				assertEquals(B.length, C2.length);
-				assertEquals(B[0].length, C2[0].length);
-				for (int i = 0; i < B.length; i++) {
-					assertArrayEquals(B[i], C2[i]);
-				}
-				C = null;
-				C = MatrixOps.product(B,I);
-				assertEquals(B.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				for (int i = 0; i < B.length; i++) {
-					assertArrayEquals(B[i], C[i]);
-				}
-			}
-		}
-		
-	}
-	
-	@Test
-	public void testProductIdentityDouble() {
-		double[][] A = new double[0][0];
-		double[][] B = new double[0][0];
-		double[][] C = MatrixOps.product(A,B,null);
-		assertEquals(0, C.length);
-		C = new double[0][0];
-		double[][] C2 = MatrixOps.product(A,B,C);
-		assertEquals(0, C.length);
-		assertEquals((Object)C, (Object)C2);
-		C = null;
-		C = MatrixOps.product(A,B);
-		assertEquals(0, C.length);
-		for (int n = 1; n < 5; n++) {
-			double[][] I = getI_d(n);
-			for (int m = 1; m < 5; m++) {
-				C = C2 = null;
-				A = getMatrixA_D(n,m);
-				C = MatrixOps.product(I,A,null);
-				assertEquals(A.length, C.length);
-				assertEquals(A[0].length, C[0].length);
-				for (int i = 0; i < A.length; i++) {
-					assertArrayEquals(A[i], C[i], EPSILON);
-				}
-				C = new double[n][m];
-				C2 = MatrixOps.product(I,A,C);
-				assertEquals((Object)C, (Object)C2);
-				assertEquals(A.length, C2.length);
-				assertEquals(A[0].length, C2[0].length);
-				for (int i = 0; i < A.length; i++) {
-					assertArrayEquals(A[i], C2[i], EPSILON);
-				}
-				C = null;
-				C = MatrixOps.product(I,A);
-				assertEquals(A.length, C.length);
-				assertEquals(A[0].length, C[0].length);
-				for (int i = 0; i < A.length; i++) {
-					assertArrayEquals(A[i], C[i], EPSILON);
-				}
-				B = getMatrixA_D(m,n);
-				C = MatrixOps.product(B,I,null);
-				assertEquals(B.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				for (int i = 0; i < B.length; i++) {
-					assertArrayEquals(B[i], C[i], EPSILON);
-				}
-				C = new double[m][n];
-				C2 = MatrixOps.product(B,I,C);
-				assertEquals((Object)C, (Object)C2);
-				assertEquals(B.length, C2.length);
-				assertEquals(B[0].length, C2[0].length);
-				for (int i = 0; i < B.length; i++) {
-					assertArrayEquals(B[i], C2[i], EPSILON);
-				}
-				C = null;
-				C = MatrixOps.product(B,I);
-				assertEquals(B.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				for (int i = 0; i < B.length; i++) {
-					assertArrayEquals(B[i], C[i], EPSILON);
-				}
-			}
-		}	
-	}
-	
-	@Test
-	public void testProductInt() {
-		for (int n = 1; n < 5; n++) {
-			for (int m = 1; m < 5; m++) {
-				int[][] A = getMultA(n,m);
-				int[][] B = getMultB(m,n);
-				int[][] C = MatrixOps.product(A, B);
-				assertEquals(A.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				assertEquals("[0][0]", (m)*(m+1)*(2*m+1)/6, C[0][0]);
-				int sum = (m+1)*m/2;
-				for (int i = 1; i < C[0].length; i++) {
-					assertEquals("[0][i]", i*sum, C[0][i]);
-				}
-				for (int i = 1; i < C.length; i++) {
-					assertArrayEquals(C[0], C[i]);
-				}
-				C = null;
-				C = MatrixOps.product(A, B, null);
-				assertEquals(A.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				assertEquals("[0][0]", (m)*(m+1)*(2*m+1)/6, C[0][0]);
-				for (int i = 1; i < C[0].length; i++) {
-					assertEquals("[0][i]", i*sum, C[0][i]);
-				}
-				for (int i = 1; i < C.length; i++) {
-					assertArrayEquals(C[0], C[i]);
-				}
-				int[][] C2 = new int[n][n];
-				C = null;
-				C = MatrixOps.product(A, B, C2);
-				assertEquals((Object)C, (Object)C2);
-				assertEquals(A.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				assertEquals("[0][0]", (m)*(m+1)*(2*m+1)/6, C[0][0]);
-				for (int i = 1; i < C[0].length; i++) {
-					assertEquals("[0][i]", i*sum, C[0][i]);
-				}
-				for (int i = 1; i < C.length; i++) {
-					assertArrayEquals(C[0], C[i]);
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new int[1][2], new int[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new int[0][1], new int[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new int[1][1], new int[0][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new int[1][1], new int[1][1], new int[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new int[1][1], new int[1][1], new int[2][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new int[0][0], new int[0][0], new int[1][2])
-		);
-	}
-	
-	@Test
-	public void testProductDouble() {
-		for (int n = 1; n < 5; n++) {
-			for (int m = 1; m < 5; m++) {
-				double[][] A = getMultA_d(n,m);
-				double[][] B = getMultB_d(m,n);
-				double[][] C = MatrixOps.product(A, B);
-				assertEquals(A.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				assertEquals("[0][0]", (m)*(m+1)*(2*m+1)/6.0, C[0][0], EPSILON);
-				double sum = (m+1)*m/2;
-				for (int i = 1; i < C[0].length; i++) {
-					assertEquals("[0][i]", i*sum, C[0][i], EPSILON);
-				}
-				for (int i = 1; i < C.length; i++) {
-					assertArrayEquals(C[0], C[i], EPSILON);
-				}
-				C = null;
-				C = MatrixOps.product(A, B, null);
-				assertEquals(A.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				assertEquals("[0][0]", (m)*(m+1)*(2*m+1)/6.0, C[0][0], EPSILON);
-				for (int i = 1; i < C[0].length; i++) {
-					assertEquals("[0][i]", i*sum, C[0][i], EPSILON);
-				}
-				for (int i = 1; i < C.length; i++) {
-					assertArrayEquals(C[0], C[i], EPSILON);
-				}
-				double[][] C2 = new double[n][n];
-				C = null;
-				C = MatrixOps.product(A, B, C2);
-				assertEquals((Object)C, (Object)C2);
-				assertEquals(A.length, C.length);
-				assertEquals(B[0].length, C[0].length);
-				assertEquals("[0][0]", (m)*(m+1)*(2*m+1)/6.0, C[0][0], EPSILON);
-				for (int i = 1; i < C[0].length; i++) {
-					assertEquals("[0][i]", i*sum, C[0][i], EPSILON);
-				}
-				for (int i = 1; i < C.length; i++) {
-					assertArrayEquals(C[0], C[i], EPSILON);
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new double[1][2], new double[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new double[0][1], new double[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new double[1][1], new double[0][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new double[1][1], new double[1][1], new double[1][2])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new double[1][1], new double[1][1], new double[2][1])
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> MatrixOps.product(new double[0][0], new double[0][0], new double[1][2])
-		);
-	}
-	
-	@Test
-	public void testJacobi() {
-		for (int n = 1; n <= 10; n++) {
-			double[][] A = getDiagonal(n);
-			JacobiDiagonalization jd = new JacobiDiagonalization(A);
-			assertNull(jd.eigenvectors());
-			assertNull(jd.eigenvalues());
-			assertTrue(jd.compute());
-			double[][] P = jd.eigenvectors();
-			double[] eig = jd.eigenvalues();
-			double[][] lambda = new double[eig.length][eig.length];
-			for (int i = 0; i < lambda.length; i++) {
-				lambda[i][i] = eig[i];
-			}
-			double[][] left = MatrixOps.product(A, P);
-			double[][] right = MatrixOps.product(P, lambda);
-			assertEquals(left.length, right.length);
-			assertEquals(left[0].length, right[0].length);
-			for (int i = 0; i < left.length; i++) {
-				assertArrayEquals("diagonal already", left[i], right[i], JacobiDiagonalization.EPSILON);
-			}
-			if (n <= 5) {
-				A = getSymmetric(n);
-				jd = new JacobiDiagonalization(A);
-				assertNull("eigvectors should be null", jd.eigenvectors());
-				assertNull("eigvalues should be null", jd.eigenvalues());
-				assertTrue("should converge", jd.compute());
-				P = jd.eigenvectors();
-				eig = jd.eigenvalues();
-				lambda = new double[eig.length][eig.length];
-				for (int i = 0; i < lambda.length; i++) {
-					lambda[i][i] = eig[i];
-				}
-				left = MatrixOps.product(A, P);
-				right = MatrixOps.product(P, lambda);
-				assertEquals(left.length, right.length);
-				assertEquals(left[0].length, right[0].length);
-				for (int i = 0; i < left.length; i++) {
-					assertArrayEquals("symmetric: " + n, left[i], right[i], 10*n*JacobiDiagonalization.EPSILON);
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> new JacobiDiagonalization(new double[2][3])
-		);
-	}
-	
-	@Test
-	public void testJacobiTooManyIterations() {
-		double[][] A = getSymmetric(5);
-		JacobiDiagonalization jd = new JacobiDiagonalization(A);
-		assertFalse(jd.compute(1));
-	}
-	
-	@Test
-	public void testJacobiInt() {
-		for (int n = 1; n <= 5; n++) {
-			int[][] Aint = getDiagonalInt(n);
-			double[][] A = toDoubleMatrix(Aint, true);
-			JacobiDiagonalization jd = new JacobiDiagonalization(Aint);
-			assertNull(jd.eigenvectors());
-			assertNull(jd.eigenvalues());
-			assertTrue(jd.compute(JacobiDiagonalization.EPSILON));
-			double[][] P = jd.eigenvectors();
-			double[] eig = jd.eigenvalues();
-			double[][] lambda = new double[eig.length][eig.length];
-			for (int i = 0; i < lambda.length; i++) {
-				lambda[i][i] = eig[i];
-			}
-			double[][] left = MatrixOps.product(A, P);
-			double[][] right = MatrixOps.product(P, lambda);
-			assertEquals(left.length, right.length);
-			assertEquals(left[0].length, right[0].length);
-			for (int i = 0; i < left.length; i++) {
-				assertArrayEquals("diagonal already", left[i], right[i], JacobiDiagonalization.EPSILON);
-			}
-			if (n <= 5) {
-				Aint = getSymmetricInt(n);
-				A = toDoubleMatrix(Aint, false);
-				jd = new JacobiDiagonalization(A);
-				assertNull("eigvectors should be null", jd.eigenvectors());
-				assertNull("eigvalues should be null", jd.eigenvalues());
-				assertTrue("should converge", jd.compute(JacobiDiagonalization.MAX_ITERATIONS/10));
-				P = jd.eigenvectors();
-				eig = jd.eigenvalues();
-				lambda = new double[eig.length][eig.length];
-				for (int i = 0; i < lambda.length; i++) {
-					lambda[i][i] = eig[i];
-				}
-				left = MatrixOps.product(A, P);
-				right = MatrixOps.product(P, lambda);
-				assertEquals(left.length, right.length);
-				assertEquals(left[0].length, right[0].length);
-				for (int i = 0; i < left.length; i++) {
-					assertArrayEquals("symmetric: " + n, left[i], right[i], 10*n*JacobiDiagonalization.EPSILON);
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> new JacobiDiagonalization(new int[2][3])
-		);
-	}
-	
-	private double[][] getSymmetric(int n) {
-		double[][] A = new double[n][n];
-		Random gen = new Random(42);
-		for (int i = 0; i < n; i++) {
-			A[i][i] = gen.nextDouble()+1;
-			for (int j = i+1; j < n; j++) {
-				A[i][j] = A[j][i] = gen.nextDouble()+1;
-			}
-		}
-		return A;
-	}
-	
-	private int[][] getSymmetricInt(int n) {
-		int[][] A = new int[n][n];
-		Random gen = new Random(42);
-		for (int i = 0; i < n; i++) {
-			A[i][i] = gen.nextInt(2)+1;
-			for (int j = i+1; j < n; j++) {
-				A[i][j] = A[j][i] = gen.nextInt(2)+1;
-			}
-		}
-		return A;
-	}
-	
-	private double[][] getDiagonal(int n) {
-		double[][] A = new double[n][n];
-		Random gen = new Random(42);
-		for (int i = 0; i < n; i++) {
-			A[i][i] = 99*gen.nextDouble() + 1;
-		}
-		return A;
-	}
-	
-	private int[][] getDiagonalInt(int n) {
-		int[][] A = new int[n][n];
-		Random gen = new Random(42);
-		for (int i = 0; i < n; i++) {
-			A[i][i] = gen.nextInt(99) + 1;
-		}
-		return A;
-	}
-	
-	private double[][] toDoubleMatrix(int[][] M, boolean diag) {
-		double[][] A = new double[M.length][M[0].length];
-		for (int i = 0; i < A.length; i++) {
-			if (diag) {
-				A[i][i] = M[i][i];
-			} else {
-				for (int j = 0; j < A[i].length; j++) {
-					A[i][j] = M[i][j];
-				}
-			}
-		}
-		return A;
-	}
-	
-	private double[][] getMultA_d(int n, int m) {
-		double[][] A = new double[n][m];
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < m; j++) {
-				A[i][j] = j+1;
-			}
-		}
-		return A;
-	}
-	
-	private double[][] getMultB_d(int n, int m) {
-		double[][] B = new double[n][m];
-		for (int i = 0; i < n; i++) {
-			B[i][0] = i+1;
-			for (int j = 1; j < m; j++) {
-				B[i][j] = j;
-			}
-		}
-		return B;
-	}
-	
-	
-	private int[][] getMultA(int n, int m) {
-		int[][] A = new int[n][m];
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < m; j++) {
-				A[i][j] = j+1;
-			}
-		}
-		return A;
-	}
-	
-	private int[][] getMultB(int n, int m) {
-		int[][] B = new int[n][m];
-		for (int i = 0; i < n; i++) {
-			B[i][0] = i+1;
-			for (int j = 1; j < m; j++) {
-				B[i][j] = j;
-			}
-		}
-		return B;
-	}
-	
-	private int[][] getI(int n) {
-		int[][] I = new int[n][n];
-		for (int i = 0; i < n; i++) {
-			I[i][i] = 1;
-		}
-		return I;
-	}
-	
-	private double[][] getI_d(int n) {
-		double[][] I = new double[n][n];
-		for (int i = 0; i < n; i++) {
-			I[i][i] = 1.0;
-		}
-		return I;
-	}
-	
-	private int[][] fillMatrix(int n, int m, int value) {
-		if (n==0) m = 0;
-		int[][] M = new int[n][m];
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < m; j++) {
-				M[i][j] = value;
-			}
-		}
-		return M;
-	}
-	
-	private double[][] fillMatrix(int n, int m, double value) {
-		if (n==0) m = 0;
-		double[][] M = new double[n][m];
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < m; j++) {
-				M[i][j] = value;
-			}
-		}
-		return M;
-	}
-	
-	private double[][] getMatrixA_D(int n, int m) {
-		if (n==0) m = 0;
-		double[][] M = new double[n][m];
-		int k = 1;
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < m; j++) {
-				M[i][j] = k;
-				k++;
-			}
-		}
-		return M;
-	}
-	
-	private double[][] getMatrixB_D(int n, int m) {
-		if (n==0) m = 0;
-		double[][] M = new double[n][m];
-		int k = n*m;
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < m; j++) {
-				M[i][j] = k;
-				k--;
-			}
-		}
-		return M;
-	}
-	
-	private int[][] getMatrixAInts(int n, int m) {
-		if (n==0) m = 0;
-		int[][] M = new int[n][m];
-		int k = 1;
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < m; j++) {
-				M[i][j] = k;
-				k++;
-			}
-		}
-		return M;
-	}
-	
-	private int[][] getMatrixBInts(int n, int m) {
-		if (n==0) m = 0;
-		int[][] M = new int[n][m];
-		int k = n*m;
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < m; j++) {
-				M[i][j] = k;
-				k--;
-			}
-		}
-		return M;
-	}
-	
-	
-	private int[][] getSquareIntsForTranspose(int n) {
-		int[][] m = new int[n][n];
-		int k = 0;
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < n; j++) {
-				m[i][j] = k;
-				k++;
-			}
-		}
-		return m;
-	}
-	
-	private double[][] getSquareDoublesForTranspose(int n) {
-		double[][] m = new double[n][n];
-		int k = 0;
-		for (int i = 0; i < n; i++) {
-			for (int j = 0; j < n; j++) {
-				m[i][j] = k;
-				k++;
-			}
-		}
-		return m;
-	}
-	
-}
\ No newline at end of file
diff --git a/tests/org/cicirello/math/rand/RandomIndexerSampleTests.java b/tests/org/cicirello/math/rand/RandomIndexerSampleTests.java
deleted file mode 100644
index b42d7dfd..00000000
--- a/tests/org/cicirello/math/rand/RandomIndexerSampleTests.java
+++ /dev/null
@@ -1,1751 +0,0 @@
-/*
- * Copyright 2019-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-
-package org.cicirello.math.rand;
-
-import org.junit.*;
-import static org.junit.Assert.*;
-
-import java.util.Random;
-import java.util.Arrays;
-import java.util.SplittableRandom;
-
-/**
- * JUnit 4 tests for the methods of the RandomIndexer class.
- */
-public class RandomIndexerSampleTests {
-	
-	private static double EPSILON = 1e-10;
-	
-	// Part of each test case in this class is a chi square goodness of fit test
-	// on a large number of samples to test for uniformity of results.
-	// Some of these tests are of methods that rely on ThreadLocalRandom, which
-	// doesn't allow setting a seed, so it is impossible to 100% replicate tests
-	// from one run of the test set to the next.
-	// These test cases fully passed the most recent time run.
-	// If you make any changes to the sampling methods that depend upon ThreadLocalRandom,
-	// then change this constant to false, run all tests, and switch back to true if
-	// they pass.  In the event that the chi square tests "fail" for methods that rely on
-	// ThreadLocalRandom, then look carefully at the statistical test results, and rerun
-	// since a "failure" is not necessarily an actual failure (statistically, the chi square
-	// test statistic can be above the cutoff 5% of the time).
-	private static final boolean DISABLE_CHI_SQUARE_TESTS = true;
-	
-	@Test
-	public void testSamplePRandom() {
-		final int TRIALS = 1000;
-		Random r = new Random(42);
-		double[] P = {0.25, 0.5, 0.75};
-		int n = 100;
-		for (double p : P) {
-			int sum = 0;
-			for (int i = 0; i < TRIALS; i++) {
-				int[] result = RandomIndexer.sample(n, p, r);
-				assertTrue("verify correct range and no duplicates", validSample(n, result));
-				sum += result.length;
-			}
-			double ave = 1.0 * sum / TRIALS;
-			assertTrue("verify correct sampling frequency", Math.abs(n*p-ave) <= 5);
-		}
-		int[] result = RandomIndexer.sample(4, 0.0, r);
-		assertEquals(0, result.length);
-		result = RandomIndexer.sample(4, 1.0, r);
-		assertEquals(4, result.length);
-	}
-	
-	@Test
-	public void testSamplePSplittable() {
-		final int TRIALS = 1000;
-		SplittableRandom r = new SplittableRandom(42);
-		double[] P = {0.25, 0.5, 0.75};
-		int n = 100;
-		for (double p : P) {
-			int sum = 0;
-			for (int i = 0; i < TRIALS; i++) {
-				int[] result = RandomIndexer.sample(n, p, r);
-				assertTrue("verify correct range and no duplicates", validSample(n, result));
-				sum += result.length;
-			}
-			double ave = 1.0 * sum / TRIALS;
-			assertTrue("verify correct sampling frequency", Math.abs(n*p-ave) <= 5);
-		}
-		int[] result = RandomIndexer.sample(4, 0.0, r);
-		assertEquals(0, result.length);
-		result = RandomIndexer.sample(4, 1.0, r);
-		assertEquals(4, result.length);
-	}
-	
-	@Test
-	public void testSamplePThreadLocal() {
-		final int TRIALS = 1000;
-		double[] P = {0.25, 0.5, 0.75};
-		int n = 100;
-		for (double p : P) {
-			int sum = 0;
-			for (int i = 0; i < TRIALS; i++) {
-				int[] result = RandomIndexer.sample(n, p);
-				assertTrue("verify correct range and no duplicates", validSample(n, result));
-				sum += result.length;
-			}
-			// Don't do this check since we can't seed.
-			//double ave = 1.0 * sum / TRIALS;
-			//assertTrue("verify correct sampling frequency", Math.abs(n*p-ave) <= 5);
-		}
-		int[] result = RandomIndexer.sample(4, 0.0);
-		assertEquals(0, result.length);
-		result = RandomIndexer.sample(4, 1.0);
-		assertEquals(4, result.length);
-	}
-	
-	@Test
-	public void testSampleReservoir_ThreadLocalRandom() {
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.sampleReservoir(n, k, null);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.sampleReservoir(1, 2, null)
-		);
-	}
-	
-	@Test
-	public void testSampleReservoir_SplittableRandom() {
-		SplittableRandom gen = new SplittableRandom(42);
-		final int REPS_PER_BUCKET = 100;
-		
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.sampleReservoir(n, k, null, gen);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		for (int n = 1; n <= 5; n++) {
-			int m = n < 3 ? n : 3;
-			for (int k = 1; k <= m; k++) {
-				int countH = 0;
-				for (int trial = 0; trial < 100; trial++) {
-					int[] buckets1 = new int[n];
-					int[][] buckets2 = new int[n][n];
-					int[][][] buckets3 = new int[n][n][n];
-					int numBuckets = k==1 ? n : (k==2 ? n*(n-1)/2 : n*(n-1)*(n-2)/6);
-					for (int j = 0; j < REPS_PER_BUCKET * numBuckets; j++) {
-						int[] result = RandomIndexer.sampleReservoir(n, k, null, gen);
-						Arrays.sort(result);
-						switch (k) {
-							case 1: buckets1[result[0]] += 1; break;
-							case 2: buckets2[result[0]][result[1]] += 1; break;
-							case 3: buckets3[result[0]][result[1]][result[2]] += 1; break;
-						}
-					}
-					switch (k) {
-						case 1: 
-						for (int x = 0; x < n; x++) {
-							assertTrue("failed to generate any samples: "+x, buckets1[x]>0);
-						}
-						double chi1 = chiSquare(buckets1, numBuckets);
-						if (chi1 > limit95[numBuckets-1]) countH++;
-						break;
-						case 2: 
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								assertTrue("failed to generate any samples: ("+x+", "+y+")", buckets2[x][y]>0);
-							}
-						}
-						double chi2 = chiSquare(buckets2, numBuckets);
-						if (chi2 > limit95[numBuckets-1]) countH++;
-						break;
-						case 3:
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								for (int z = y+1; z < n; z++) {
-									assertTrue("failed to generate any samples: ("+x+", "+y+", "+z+")", buckets3[x][y][z]>0);
-								}
-							}
-						}
-						double chi3 = chiSquare(buckets3, numBuckets);
-						if (chi3 > limit95[numBuckets-1]) countH++;
-						break;
-					}
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH, countH <= 10);
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.sampleReservoir(1, 2, null, gen)
-		);
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.sampleReservoir(5, 2, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.sampleReservoir(5, 3, expected, gen);
-		assertTrue(expected != actual);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testSampleReservoir_Random() {
-		Random gen = new Random(42);
-		final int REPS_PER_BUCKET = 100;
-		
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.sampleReservoir(n, k, null, gen);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		for (int n = 1; n <= 5; n++) {
-			int m = n < 3 ? n : 3;
-			for (int k = 1; k <= m; k++) {
-				int countH = 0;
-				for (int trial = 0; trial < 100; trial++) {
-					int[] buckets1 = new int[n];
-					int[][] buckets2 = new int[n][n];
-					int[][][] buckets3 = new int[n][n][n];
-					int numBuckets = k==1 ? n : (k==2 ? n*(n-1)/2 : n*(n-1)*(n-2)/6);
-					for (int j = 0; j < REPS_PER_BUCKET * numBuckets; j++) {
-						int[] result = RandomIndexer.sampleReservoir(n, k, null, gen);
-						Arrays.sort(result);
-						switch (k) {
-							case 1: buckets1[result[0]] += 1; break;
-							case 2: buckets2[result[0]][result[1]] += 1; break;
-							case 3: buckets3[result[0]][result[1]][result[2]] += 1; break;
-						}
-					}
-					switch (k) {
-						case 1: 
-						for (int x = 0; x < n; x++) {
-							assertTrue("failed to generate any samples: "+x, buckets1[x]>0);
-						}
-						double chi1 = chiSquare(buckets1, numBuckets);
-						if (chi1 > limit95[numBuckets-1]) countH++;
-						break;
-						case 2: 
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								assertTrue("failed to generate any samples: ("+x+", "+y+")", buckets2[x][y]>0);
-							}
-						}
-						double chi2 = chiSquare(buckets2, numBuckets);
-						if (chi2 > limit95[numBuckets-1]) countH++;
-						break;
-						case 3:
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								for (int z = y+1; z < n; z++) {
-									assertTrue("failed to generate any samples: ("+x+", "+y+", "+z+")", buckets3[x][y][z]>0);
-								}
-							}
-						}
-						double chi3 = chiSquare(buckets3, numBuckets);
-						if (chi3 > limit95[numBuckets-1]) countH++;
-						break;
-					}
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH, countH <= 10);
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.sampleReservoir(1, 2, null, gen)
-		);
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.sampleReservoir(5, 2, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.sampleReservoir(5, 3, expected, gen);
-		assertTrue(expected != actual);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testSamplePool_ThreadLocalRandom() {
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.samplePool(n, k, null);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.samplePool(1, 2, null)
-		);
-	}
-	
-	@Test
-	public void testSamplePool_SplittableRandom() {
-		SplittableRandom gen = new SplittableRandom(42);
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 100;
-		
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.samplePool(n, k, null, gen);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		for (int n = 1; n <= 5; n++) {
-			int m = n < 3 ? n : 3;
-			for (int k = 1; k <= m; k++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[] buckets1 = new int[n];
-					int[][] buckets2 = new int[n][n];
-					int[][][] buckets3 = new int[n][n][n];
-					int numBuckets = k==1 ? n : (k==2 ? n*(n-1)/2 : n*(n-1)*(n-2)/6);
-					for (int j = 0; j < REPS_PER_BUCKET * numBuckets; j++) {
-						int[] result = RandomIndexer.samplePool(n, k, null, gen);
-						Arrays.sort(result);
-						switch (k) {
-							case 1: buckets1[result[0]] += 1; break;
-							case 2: buckets2[result[0]][result[1]] += 1; break;
-							case 3: buckets3[result[0]][result[1]][result[2]] += 1; break;
-						}
-					}
-					switch (k) {
-						case 1: 
-						for (int x = 0; x < n; x++) {
-							assertTrue("failed to generate any samples: "+x, buckets1[x]>0);
-						}
-						double chi1 = chiSquare(buckets1, numBuckets);
-						if (chi1 > limit95[numBuckets-1]) countH++;
-						break;
-						case 2: 
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								assertTrue("failed to generate any samples: ("+x+", "+y+")", buckets2[x][y]>0);
-							}
-						}
-						double chi2 = chiSquare(buckets2, numBuckets);
-						if (chi2 > limit95[numBuckets-1]) countH++;
-						break;
-						case 3:
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								for (int z = y+1; z < n; z++) {
-									assertTrue("failed to generate any samples: ("+x+", "+y+", "+z+")", buckets3[x][y][z]>0);
-								}
-							}
-						}
-						double chi3 = chiSquare(buckets3, numBuckets);
-						if (chi3 > limit95[numBuckets-1]) countH++;
-						break;
-					}
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n+" k="+k, countH <= TRIALS*0.1);
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.samplePool(1, 2, null, gen)
-		);
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.samplePool(5, 2, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.samplePool(5, 3, expected, gen);
-		assertTrue(expected != actual);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testSamplePool_Random() {
-		Random gen = new Random(42);
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 100;
-		
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.samplePool(n, k, null, gen);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		for (int n = 1; n <= 5; n++) {
-			int m = n < 3 ? n : 3;
-			for (int k = 1; k <= m; k++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[] buckets1 = new int[n];
-					int[][] buckets2 = new int[n][n];
-					int[][][] buckets3 = new int[n][n][n];
-					int numBuckets = k==1 ? n : (k==2 ? n*(n-1)/2 : n*(n-1)*(n-2)/6);
-					for (int j = 0; j < REPS_PER_BUCKET * numBuckets; j++) {
-						int[] result = RandomIndexer.samplePool(n, k, null, gen);
-						Arrays.sort(result);
-						switch (k) {
-							case 1: buckets1[result[0]] += 1; break;
-							case 2: buckets2[result[0]][result[1]] += 1; break;
-							case 3: buckets3[result[0]][result[1]][result[2]] += 1; break;
-						}
-					}
-					switch (k) {
-						case 1: 
-						for (int x = 0; x < n; x++) {
-							assertTrue("failed to generate any samples: "+x, buckets1[x]>0);
-						}
-						double chi1 = chiSquare(buckets1, numBuckets);
-						if (chi1 > limit95[numBuckets-1]) countH++;
-						break;
-						case 2: 
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								assertTrue("failed to generate any samples: ("+x+", "+y+")", buckets2[x][y]>0);
-							}
-						}
-						double chi2 = chiSquare(buckets2, numBuckets);
-						if (chi2 > limit95[numBuckets-1]) countH++;
-						break;
-						case 3:
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								for (int z = y+1; z < n; z++) {
-									assertTrue("failed to generate any samples: ("+x+", "+y+", "+z+")", buckets3[x][y][z]>0);
-								}
-							}
-						}
-						double chi3 = chiSquare(buckets3, numBuckets);
-						if (chi3 > limit95[numBuckets-1]) countH++;
-						break;
-					}
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n+" k="+k, countH <= TRIALS*0.1);
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.samplePool(1, 2, null, gen)
-		);
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.samplePool(5, 2, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.samplePool(5, 3, expected, gen);
-		assertTrue(expected != actual);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testSample_ThreadLocalRandom() {
-		for (int n = 1; n <= 7; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = null;
-				result = RandomIndexer.sample(n, k, result);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-	}
-	
-	@Test
-	public void testSample_SplittableRandom() {
-		SplittableRandom gen = new SplittableRandom(40);
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 100;
-		
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.sample(n, k, null, gen);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		for (int n = 1; n <= 5; n++) {
-			int m = n < 3 ? n : 3;
-			for (int k = 1; k <= m; k++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[] buckets1 = new int[n];
-					int[][] buckets2 = new int[n][n];
-					int[][][] buckets3 = new int[n][n][n];
-					int numBuckets = k==1 ? n : (k==2 ? n*(n-1)/2 : n*(n-1)*(n-2)/6);
-					for (int j = 0; j < REPS_PER_BUCKET * numBuckets; j++) {
-						int[] result = RandomIndexer.sample(n, k, null, gen);
-						Arrays.sort(result);
-						switch (k) {
-							case 1: buckets1[result[0]] += 1; break;
-							case 2: buckets2[result[0]][result[1]] += 1; break;
-							case 3: buckets3[result[0]][result[1]][result[2]] += 1; break;
-						}
-					}
-					switch (k) {
-						case 1: 
-						for (int x = 0; x < n; x++) {
-							assertTrue("failed to generate any samples: "+x, buckets1[x]>0);
-						}
-						double chi1 = chiSquare(buckets1, numBuckets);
-						if (chi1 > limit95[numBuckets-1]) countH++;
-						break;
-						case 2: 
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								assertTrue("failed to generate any samples: ("+x+", "+y+")", buckets2[x][y]>0);
-							}
-						}
-						double chi2 = chiSquare(buckets2, numBuckets);
-						if (chi2 > limit95[numBuckets-1]) countH++;
-						break;
-						case 3:
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								for (int z = y+1; z < n; z++) {
-									assertTrue("failed to generate any samples: ("+x+", "+y+", "+z+")", buckets3[x][y][z]>0);
-								}
-							}
-						}
-						double chi3 = chiSquare(buckets3, numBuckets);
-						if (chi3 > limit95[numBuckets-1]) countH++;
-						break;
-					}
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n+" k="+k, countH <= TRIALS*0.1);
-			}
-		}
-	}
-	
-	@Test
-	public void testSample_Random() {
-		Random gen = new Random(42);
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 100;
-		
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.sample(n, k, null, gen);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		for (int n = 1; n <= 5; n++) {
-			int m = n < 3 ? n : 3;
-			for (int k = 1; k <= m; k++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[] buckets1 = new int[n];
-					int[][] buckets2 = new int[n][n];
-					int[][][] buckets3 = new int[n][n][n];
-					int numBuckets = k==1 ? n : (k==2 ? n*(n-1)/2 : n*(n-1)*(n-2)/6);
-					for (int j = 0; j < REPS_PER_BUCKET * numBuckets; j++) {
-						int[] result = RandomIndexer.sample(n, k, null, gen);
-						Arrays.sort(result);
-						switch (k) {
-							case 1: buckets1[result[0]] += 1; break;
-							case 2: buckets2[result[0]][result[1]] += 1; break;
-							case 3: buckets3[result[0]][result[1]][result[2]] += 1; break;
-						}
-					}
-					switch (k) {
-						case 1: 
-						for (int x = 0; x < n; x++) {
-							assertTrue("failed to generate any samples: "+x, buckets1[x]>0);
-						}
-						double chi1 = chiSquare(buckets1, numBuckets);
-						if (chi1 > limit95[numBuckets-1]) countH++;
-						break;
-						case 2: 
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								assertTrue("failed to generate any samples: ("+x+", "+y+")", buckets2[x][y]>0);
-							}
-						}
-						double chi2 = chiSquare(buckets2, numBuckets);
-						if (chi2 > limit95[numBuckets-1]) countH++;
-						break;
-						case 3:
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								for (int z = y+1; z < n; z++) {
-									assertTrue("failed to generate any samples: ("+x+", "+y+", "+z+")", buckets3[x][y][z]>0);
-								}
-							}
-						}
-						double chi3 = chiSquare(buckets3, numBuckets);
-						if (chi3 > limit95[numBuckets-1]) countH++;
-						break;
-					}
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n+" k="+k, countH <= TRIALS*0.1);
-			}
-		}
-	}
-	
-	@Test
-	public void testPair_ThreadLocalRandom() {
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 200;
-		
-		for (int n = 2; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntPair(n, null);
-				assertEquals("Length of result should be 2", 2, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[1] < n);
-				assertTrue("integers should be at least 0", result[1] >= 0);
-				assertTrue("integers should be less than " + n, result[0] < n);
-			}
-		}
-		
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.nextIntPair(5, expected);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntPair(5, new int[1]);
-		assertEquals(2, actual.length);
-		
-		if (DISABLE_CHI_SQUARE_TESTS) return;
-		for (int n = 2; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][] buckets = new int[n][n];
-				int numBuckets = n*(n-1); // /2;
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntPair(n, null);
-					buckets[result[0]][result[1]]++;
-				}
-				double chi = chiSquareAll(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-	}
-	
-	@Test
-	public void testTriple_ThreadLocalRandom() {
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 200;
-		
-		for (int n = 3; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntTriple(n, null);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				Arrays.sort(result);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-			}
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextIntTriple(5, expected);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntTriple(5, new int[2]);
-		assertEquals(3, actual.length);
-		
-		if (DISABLE_CHI_SQUARE_TESTS) return;
-		for (int n = 3; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][][] buckets = new int[n][n][n];
-				int[] sortBuckets = new int[6];
-				int numBuckets = n*(n-1)*(n-2); 
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntTriple(n, null);
-					buckets[result[0]][result[1]][result[2]]++;
-				}
-				double chi = chiSquareAll(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-	}
-	
-	@Test
-	public void testTripleSorted_ThreadLocalRandom() {
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 200;
-		
-		for (int n = 3; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntTriple(n, null, true);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertTrue("result should be sorted", result[0] < result[1]);
-				assertTrue("result should be sorted", result[1] < result[2]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-			}
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextIntTriple(5, expected, false);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntTriple(5, new int[2], false);
-		assertEquals(3, actual.length);
-		
-		if (DISABLE_CHI_SQUARE_TESTS) return;
-		for (int n = 3; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][][] buckets = new int[n][n][n];
-				int numBuckets = n*(n-1)*(n-2)/6;
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntTriple(n, null, true);
-					buckets[result[0]][result[1]][result[2]]++;
-				}
-				double chi = chiSquare(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-	}
-	
-	
-	@Test
-	public void testTriple_SplittableRandom() {
-		SplittableRandom gen = new SplittableRandom(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 3; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntTriple(n, null, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				Arrays.sort(result);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-			}
-		}
-		for (int n = 3; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][][] buckets = new int[n][n][n];
-				int numBuckets = n*(n-1)*(n-2);
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntTriple(n, null, gen);
-					buckets[result[0]][result[1]][result[2]]++;
-				}
-				double chi = chiSquareAll(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextIntTriple(5, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntTriple(5, new int[2], gen);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testTripleSorted_SplittableRandom() {
-		SplittableRandom gen = new SplittableRandom(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 3; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntTriple(n, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertTrue("result should be sorted", result[0] < result[1]);
-				assertTrue("result should be sorted", result[1] < result[2]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-			}
-		}
-		for (int n = 3; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][][] buckets = new int[n][n][n];
-				int numBuckets = n*(n-1)*(n-2)/6;
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntTriple(n, null, true, gen);
-					buckets[result[0]][result[1]][result[2]]++;
-				}
-				double chi = chiSquare(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextIntTriple(5, expected, false, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntTriple(5, new int[2], false, gen);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testTriple_Random() {
-		Random gen = new Random(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 3; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntTriple(n, null, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				Arrays.sort(result);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-			}
-		}
-		for (int n = 3; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][][] buckets = new int[n][n][n];
-				int numBuckets = n*(n-1)*(n-2);
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntTriple(n, null, gen);
-					buckets[result[0]][result[1]][result[2]]++;
-				}
-				double chi = chiSquareAll(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextIntTriple(5, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntTriple(5, new int[2], gen);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testTripleSorted_Random() {
-		Random gen = new Random(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 3; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntTriple(n, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertTrue("result should be sorted", result[0] < result[1]);
-				assertTrue("result should be sorted", result[1] < result[2]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-			}
-		}
-		for (int n = 3; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][][] buckets = new int[n][n][n];
-				int numBuckets = n*(n-1)*(n-2)/6;
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntTriple(n, null, true, gen);
-					buckets[result[0]][result[1]][result[2]]++;
-				}
-				double chi = chiSquare(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextIntTriple(5, expected, false, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntTriple(5, new int[2], false, gen);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testPair_SplittableRandom() {
-		SplittableRandom gen = new SplittableRandom(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 2; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntPair(n, null, gen);
-				assertEquals("Length of result should be 2", 2, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[1] < n);
-				assertTrue("integers should be at least 0", result[1] >= 0);
-				assertTrue("integers should be less than " + n, result[0] < n);
-			}
-		}
-		for (int n = 2; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][] buckets = new int[n][n];
-				int numBuckets = n*(n-1); // /2;
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntPair(n, null, gen);
-					buckets[result[0]][result[1]]++;
-				}
-				double chi = chiSquareAll(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-		
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.nextIntPair(5, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntPair(5, new int[1], gen);
-		assertEquals(2, actual.length);
-	}
-	
-	@Test
-	public void testPair_Random() {
-		Random gen = new Random(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 2; n <= 6; n++) {
-			for (int i = 0; i < 10; i++) {
-				int[] result = RandomIndexer.nextIntPair(n, null, gen);
-				assertEquals("Length of result should be 2", 2, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[1] < n);
-				assertTrue("integers should be at least 0", result[1] >= 0);
-				assertTrue("integers should be less than " + n, result[0] < n);
-			}
-		}
-		for (int n = 2; n <= 6; n++) {
-			int countH = 0;
-			for (int trial = 0; trial < TRIALS; trial++) {
-				int[][] buckets = new int[n][n];
-				int numBuckets = n*(n-1); //  /2;
-				for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-					int[] result = RandomIndexer.nextIntPair(n, null, gen);
-					buckets[result[0]][result[1]]++;
-				}
-				double chi = chiSquareAll(buckets, numBuckets);
-				if (chi > limit95[numBuckets-1]) countH++;
-			}
-			assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-		}
-		
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.nextIntPair(5, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextIntPair(5, new int[1], gen);
-		assertEquals(2, actual.length);
-	}
-	
-	@Test
-	public void testSampleInsertion_ThreadLocalRandom() {
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.sampleInsertion(n, k, null);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.sampleInsertion(1, 2, null)
-		);
-	}
-	
-	@Test
-	public void testSampleInsertion_SplittableRandom() {
-		SplittableRandom gen = new SplittableRandom(42);
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 100;
-		
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.sampleInsertion(n, k, null, gen);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		for (int n = 1; n <= 5; n++) {
-			int m = n < 3 ? n : 3;
-			for (int k = 1; k <= m; k++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[] buckets1 = new int[n];
-					int[][] buckets2 = new int[n][n];
-					int[][][] buckets3 = new int[n][n][n];
-					int numBuckets = k==1 ? n : (k==2 ? n*(n-1)/2 : n*(n-1)*(n-2)/6);
-					for (int j = 0; j < REPS_PER_BUCKET * numBuckets; j++) {
-						int[] result = RandomIndexer.sampleInsertion(n, k, null, gen);
-						Arrays.sort(result);
-						switch (k) {
-							case 1: buckets1[result[0]] += 1; break;
-							case 2: buckets2[result[0]][result[1]] += 1; break;
-							case 3: buckets3[result[0]][result[1]][result[2]] += 1; break;
-						}
-					}
-					switch (k) {
-						case 1: 
-						for (int x = 0; x < n; x++) {
-							assertTrue("failed to generate any samples: "+x, buckets1[x]>0);
-						}
-						double chi1 = chiSquare(buckets1, numBuckets);
-						if (chi1 > limit95[numBuckets-1]) countH++;
-						break;
-						case 2: 
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								assertTrue("failed to generate any samples: ("+x+", "+y+")", buckets2[x][y]>0);
-							}
-						}
-						double chi2 = chiSquare(buckets2, numBuckets);
-						if (chi2 > limit95[numBuckets-1]) countH++;
-						break;
-						case 3:
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								for (int z = y+1; z < n; z++) {
-									assertTrue("failed to generate any samples: ("+x+", "+y+", "+z+")", buckets3[x][y][z]>0);
-								}
-							}
-						}
-						double chi3 = chiSquare(buckets3, numBuckets);
-						if (chi3 > limit95[numBuckets-1]) countH++;
-						break;
-					}
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n+" k="+k, countH <= TRIALS*0.1);
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.sampleInsertion(1, 2, null, gen)
-		);
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.sampleInsertion(5, 2, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.sampleInsertion(5, 3, expected, gen);
-		assertTrue(expected != actual);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testSampleInsertion_Random() {
-		Random gen = new Random(42);
-		final int REPS_PER_BUCKET = 200;
-		final int TRIALS = 100;
-		
-		for (int n = 1; n <= 6; n++) {
-			for (int k = 0; k <= n; k++) {
-				int[] result = RandomIndexer.sampleInsertion(n, k, null, gen);
-				assertEquals("Length of result should be " + k, k, result.length);
-				boolean[] inResult = new boolean[n];
-				for (int i = 0; i < k; i++) {
-					assertTrue("Each integer should be at least 0 and less than " + k, result[i] < n && result[i] >= 0);
-					assertFalse("Result shouldn't contain duplicates", inResult[result[i]]);
-					inResult[result[i]] = true;
-				}
-			}
-		}
-		for (int n = 1; n <= 5; n++) {
-			int m = n < 3 ? n : 3;
-			for (int k = 1; k <= m; k++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[] buckets1 = new int[n];
-					int[][] buckets2 = new int[n][n];
-					int[][][] buckets3 = new int[n][n][n];
-					int numBuckets = k==1 ? n : (k==2 ? n*(n-1)/2 : n*(n-1)*(n-2)/6);
-					for (int j = 0; j < REPS_PER_BUCKET * numBuckets; j++) {
-						int[] result = RandomIndexer.sampleInsertion(n, k, null, gen);
-						Arrays.sort(result);
-						switch (k) {
-							case 1: buckets1[result[0]] += 1; break;
-							case 2: buckets2[result[0]][result[1]] += 1; break;
-							case 3: buckets3[result[0]][result[1]][result[2]] += 1; break;
-						}
-					}
-					switch (k) {
-						case 1: 
-						for (int x = 0; x < n; x++) {
-							assertTrue("failed to generate any samples: "+x, buckets1[x]>0);
-						}
-						double chi1 = chiSquare(buckets1, numBuckets);
-						if (chi1 > limit95[numBuckets-1]) countH++;
-						break;
-						case 2: 
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								assertTrue("failed to generate any samples: ("+x+", "+y+")", buckets2[x][y]>0);
-							}
-						}
-						double chi2 = chiSquare(buckets2, numBuckets);
-						if (chi2 > limit95[numBuckets-1]) countH++;
-						break;
-						case 3:
-						for (int x = 0; x < n; x++) {
-							for (int y = x+1; y < n; y++) {
-								for (int z = y+1; z < n; z++) {
-									assertTrue("failed to generate any samples: ("+x+", "+y+", "+z+")", buckets3[x][y][z]>0);
-								}
-							}
-						}
-						double chi3 = chiSquare(buckets3, numBuckets);
-						if (chi3 > limit95[numBuckets-1]) countH++;
-						break;
-					}
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n+" k="+k, countH <= TRIALS*0.1);
-			}
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.sampleInsertion(1, 2, null, gen)
-		);
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.sampleInsertion(5, 2, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.sampleInsertion(5, 3, expected, gen);
-		assertTrue(expected != actual);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testNextWindowedIntPair_TLR() {
-		final int REPS_PER_BUCKET = 600;
-		final int TRIALS = 100;
-		
-		for (int n = 2; n <= 10; n++) {
-			for (int w = 1; w < n; w++) {
-				int[] result = RandomIndexer.nextWindowedIntPair(n, w, null);
-				assertEquals("Length of result should be 2", 2, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				if (result[0] > result[1]) {
-					int temp = result[0];
-					result[0] = result[1];
-					result[1] = temp;
-				}
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[1] < n);
-				assertTrue("integers should be within window w="+w, result[1]-result[0] <= w);
-			}
-		}
-		
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.nextWindowedIntPair(5, 1, expected);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntPair(5, 1, new int[1]);
-		assertEquals(2, actual.length);
-		
-		if (DISABLE_CHI_SQUARE_TESTS) return;
-		for (int n = 2; n <= 7; n++) {
-			for (int w = 1; w < n; w++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[][] buckets = new int[n][n];
-					int numBuckets = w*(n-w) + w*(w-1)/2;
-					numBuckets *= 2;
-					for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-						int[] result = RandomIndexer.nextWindowedIntPair(n, w, null);
-						buckets[result[0]][result[1]]++;
-					}
-					int[] flatBuckets = new int[numBuckets];
-					int k = 0;
-					for (int i = 0; i < n; i++) {
-						for (int j = 0; j < n; j++) {
-							if (j==i) continue;
-							if (j > i && j-i>w) continue;
-							if (i > j && i-j>w) continue;
-							flatBuckets[k] = buckets[i][j];
-							k++;
-						}
-					}
-					double chi = chiSquare(flatBuckets, numBuckets);
-					if (chi > limit95[numBuckets-1]) countH++;
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-			}
-		}
-	}
-	
-	@Test
-	public void testNextWindowedIntPair_SR() {
-		SplittableRandom gen = new SplittableRandom(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 2; n <= 10; n++) {
-			for (int w = 1; w < n; w++) {
-				int[] result = RandomIndexer.nextWindowedIntPair(n, w, null, gen);
-				assertEquals("Length of result should be 2", 2, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				if (result[0] > result[1]) {
-					int temp = result[0];
-					result[0] = result[1];
-					result[1] = temp;
-				}
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[1] < n);
-				assertTrue("integers should be within window w="+w, result[1]-result[0] <= w);
-			}
-		}
-		for (int n = 2; n <= 7; n++) {
-			for (int w = 1; w < n; w++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[][] buckets = new int[n][n];
-					int numBuckets = w*(n-w) + w*(w-1)/2;
-					numBuckets *= 2;
-					for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-						int[] result = RandomIndexer.nextWindowedIntPair(n, w, null, gen);
-						buckets[result[0]][result[1]]++;
-					}
-					int[] flatBuckets = new int[numBuckets];
-					int k = 0;
-					for (int i = 0; i < n; i++) {
-						for (int j = 0; j < n; j++) {
-							if (j==i) continue;
-							if (j > i && j-i>w) continue;
-							if (i > j && i-j>w) continue;
-							flatBuckets[k] = buckets[i][j];
-							k++;
-						}
-					}
-					double chi = chiSquare(flatBuckets, numBuckets);
-					if (chi > limit95[numBuckets-1]) countH++;
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-			}
-		}
-		
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.nextWindowedIntPair(5, 1, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntPair(5, 1, new int[1], gen);
-		assertEquals(2, actual.length);
-	}
-	
-	@Test
-	public void testNextWindowedIntPair_R() {
-		Random gen = new Random(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 2; n <= 10; n++) {
-			for (int w = 1; w < n; w++) {
-				int[] result = RandomIndexer.nextWindowedIntPair(n, w, null, gen);
-				assertEquals("Length of result should be 2", 2, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				if (result[0] > result[1]) {
-					int temp = result[0];
-					result[0] = result[1];
-					result[1] = temp;
-				}
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[1] < n);
-				assertTrue("integers should be within window w="+w, result[1]-result[0] <= w);
-			}
-		}
-		for (int n = 2; n <= 7; n++) {
-			for (int w = 1; w < n; w++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[][] buckets = new int[n][n];
-					int numBuckets = w*(n-w) + w*(w-1)/2;
-					numBuckets *= 2;
-					for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-						int[] result = RandomIndexer.nextWindowedIntPair(n, w, null, gen);
-						buckets[result[0]][result[1]]++;
-					}
-					int[] flatBuckets = new int[numBuckets];
-					int k = 0;
-					for (int i = 0; i < n; i++) {
-						for (int j = 0; j < n; j++) {
-							if (j==i) continue;
-							if (j > i && j-i>w) continue;
-							if (i > j && i-j>w) continue;
-							flatBuckets[k] = buckets[i][j];
-							k++;
-						}
-					}
-					double chi = chiSquare(flatBuckets, numBuckets);
-					if (chi > limit95[numBuckets-1]) countH++;
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-			}
-		}
-		
-		int[] expected = new int[2];
-		int[] actual = RandomIndexer.nextWindowedIntPair(5, 1, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntPair(5, 1, new int[1], gen);
-		assertEquals(2, actual.length);
-	}
-	
-	@Test
-	public void testNextWindowedIntTriple_TLR() {
-		final int REPS_PER_BUCKET = 300;
-		final int TRIALS = 100;
-		
-		for (int n = 3; n <= 10; n++) {
-			for (int w = 2; w < n; w++) {
-				int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				Arrays.sort(result);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-				assertTrue("integers should be within window w="+w, result[2]-result[0] <= w);
-				result = RandomIndexer.nextWindowedIntTriple(n, w, null, true);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				String state = "result=(" + result[0] + ", " + result[1] + ", " + result[2] + "), w="+w + " n="+n;
-				assertTrue("integers should be sorted: " + state, result[0] < result[1]);
-				assertTrue("integers should be sorted: " + state, result[1] < result[2]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-				assertTrue("integers should be within window w="+w, result[2]-result[0] <= w);
-			}
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextWindowedIntTriple(5, 3, expected);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, new int[2]);
-		assertEquals(3, actual.length);
-		
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, expected, false);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, new int[2], false);
-		assertEquals(3, actual.length);
-		
-		if (DISABLE_CHI_SQUARE_TESTS) return;
-		for (int n = 3; n <= 6; n++) {
-			for (int w = 2; w < n; w++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[][][] buckets = new int[n][n][n];
-					int numBuckets = w*(n-w)*(w-1)/2 + w*(w-1)*(w-2)/6;
-					numBuckets *= 6;
-					for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-						int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null);
-						buckets[result[0]][result[1]][result[2]]++;
-					}
-					int[] flatBuckets = new int[numBuckets];
-					int k = 0;
-					for (int i = 0; i < n; i++) {
-						for (int j = 0; j < n; j++) {
-							if (j==i) continue;
-							if (Math.abs(j-i) > w) continue;
-							for (int h = 0; h < n; h++) {
-								if (h==i || h==j) continue;
-								if (Math.abs(h-i) > w) continue;
-								if (Math.abs(j-h) > w) continue;
-								flatBuckets[k] = buckets[i][j][h];
-								k++;
-							}
-						}
-					}
-					double chi = chiSquare(flatBuckets, numBuckets);
-					if (chi > limit95[numBuckets-1]) countH++;
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-			}
-		}
-	}
-	
-	@Test
-	public void testNextWindowedIntTriple_SR() {
-		SplittableRandom gen = new SplittableRandom(42);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		
-		{
-			// Make sure sorting covers all cases:
-			// This part of the test aims to fully cover the
-			// helper method: sortSetAndAdjustWindowedTriple
-			
-			// This is the easy path through the method, even one sample
-			// should be sufficient with n=3 and w=2.
-			for (int i = 0; i < 3; i++) {
-				int[] result = RandomIndexer.nextWindowedIntTriple(3, 2, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertEquals(0, result[0]);
-				assertEquals(1, result[1]);
-				assertEquals(2, result[2]);
-			}
-			
-			// With n=1000 and w=n-1, approximately 99.7% of the following samples should
-			// go through the alternate path from above. That alternate path has
-			// 3 potential subpaths, all approximately equally likely.
-			int n = 1000;
-			int w = n-1;
-			for (int i = 0; i < 30; i++) {
-				int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertTrue(result[0] < result[1]);
-				assertTrue(result[1] < result[2]);
-			}
-			n = 10000;
-			w = n-1;
-			for (int i = 0; i < 30; i++) {
-				int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertTrue(result[0] < result[1]);
-				assertTrue(result[1] < result[2]);
-			}
-			n = 100;
-			w = n-1;
-			for (int i = 0; i < 30; i++) {
-				int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertTrue(result[0] < result[1]);
-				assertTrue(result[1] < result[2]);
-			}
-			n = 10;
-			w = n-1;
-			for (int i = 0; i < 30; i++) {
-				int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertTrue(result[0] < result[1]);
-				assertTrue(result[1] < result[2]);
-			}
-		}
-		
-		for (int n = 3; n <= 10; n++) {
-			for (int w = 2; w < n; w++) {
-				int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				Arrays.sort(result);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-				assertTrue("integers should be within window w="+w, result[2]-result[0] <= w);
-				result = RandomIndexer.nextWindowedIntTriple(n, w, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				String state = "result=(" + result[0] + ", " + result[1] + ", " + result[2] + "), w="+w + " n="+n;
-				assertTrue("integers should be sorted: " + state, result[0] < result[1]);
-				assertTrue("integers should be sorted: " + state, result[1] < result[2]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-				assertTrue("integers should be within window w="+w, result[2]-result[0] <= w);
-			}
-		}
-		for (int n = 3; n <= 6; n++) {
-			for (int w = 2; w < n; w++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[][][] buckets = new int[n][n][n];
-					int numBuckets = w*(n-w)*(w-1)/2 + w*(w-1)*(w-2)/6;
-					numBuckets *= 6;
-					for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-						int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null, gen);
-						buckets[result[0]][result[1]][result[2]]++;
-					}
-					int[] flatBuckets = new int[numBuckets];
-					int k = 0;
-					for (int i = 0; i < n; i++) {
-						for (int j = 0; j < n; j++) {
-							if (j==i) continue;
-							if (Math.abs(j-i) > w) continue;
-							for (int h = 0; h < n; h++) {
-								if (h==i || h==j) continue;
-								if (Math.abs(h-i) > w) continue;
-								if (Math.abs(j-h) > w) continue;
-								flatBuckets[k] = buckets[i][j][h];
-								k++;
-							}
-						}
-					}
-					double chi = chiSquare(flatBuckets, numBuckets);
-					if (chi > limit95[numBuckets-1]) countH++;
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-			}
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextWindowedIntTriple(5, 3, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, new int[2], gen);
-		assertEquals(3, actual.length);
-		
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, expected, false, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, new int[2], false, gen);
-		assertEquals(3, actual.length);
-	}
-	
-	@Test
-	public void testNextWindowedIntTriple_R() {
-		Random gen = new Random(41);
-		final int REPS_PER_BUCKET = 100;
-		final int TRIALS = 100;
-		
-		for (int n = 3; n <= 10; n++) {
-			for (int w = 2; w < n; w++) {
-				int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				Arrays.sort(result);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-				assertTrue("integers should be within window w="+w, result[2]-result[0] <= w);
-				result = RandomIndexer.nextWindowedIntTriple(n, w, null, true, gen);
-				assertEquals("Length of result should be 3", 3, result.length);
-				assertNotEquals("integers should be different", result[0], result[1]);
-				assertNotEquals("integers should be different", result[0], result[2]);
-				assertNotEquals("integers should be different", result[2], result[1]);
-				String state = "result=(" + result[0] + ", " + result[1] + ", " + result[2] + "), w="+w + " n="+n;
-				assertTrue("integers should be sorted: " + state, result[0] < result[1]);
-				assertTrue("integers should be sorted: " + state, result[1] < result[2]);
-				assertTrue("integers should be at least 0", result[0] >= 0);
-				assertTrue("integers should be less than " + n, result[2] < n);
-				assertTrue("integers should be within window w="+w, result[2]-result[0] <= w);
-			}
-		}
-		for (int n = 3; n <= 6; n++) {
-			for (int w = 2; w < n; w++) {
-				int countH = 0;
-				for (int trial = 0; trial < TRIALS; trial++) {
-					int[][][] buckets = new int[n][n][n];
-					int numBuckets = w*(n-w)*(w-1)/2 + w*(w-1)*(w-2)/6;
-					numBuckets *= 6;
-					for (int i = 0; i < REPS_PER_BUCKET * numBuckets; i++) {
-						int[] result = RandomIndexer.nextWindowedIntTriple(n, w, null, gen);
-						buckets[result[0]][result[1]][result[2]]++;
-					}
-					int[] flatBuckets = new int[numBuckets];
-					int k = 0;
-					for (int i = 0; i < n; i++) {
-						for (int j = 0; j < n; j++) {
-							if (j==i) continue;
-							if (Math.abs(j-i) > w) continue;
-							for (int h = 0; h < n; h++) {
-								if (h==i || h==j) continue;
-								if (Math.abs(h-i) > w) continue;
-								if (Math.abs(j-h) > w) continue;
-								flatBuckets[k] = buckets[i][j][h];
-								k++;
-							}
-						}
-					}
-					double chi = chiSquare(flatBuckets, numBuckets);
-					if (chi > limit95[numBuckets-1]) countH++;
-				}
-				assertTrue("chi square too high too often, countHigh=" + countH + " n="+n, countH <= TRIALS*0.1);
-			}
-		}
-		
-		int[] expected = new int[3];
-		int[] actual = RandomIndexer.nextWindowedIntTriple(5, 3, expected, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, new int[2], gen);
-		assertEquals(3, actual.length);
-		
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, expected, false, gen);
-		assertTrue(expected == actual);
-		actual = RandomIndexer.nextWindowedIntTriple(5, 3, new int[2], false, gen);
-		assertEquals(3, actual.length);
-	}
-	
-	private boolean validSample(int n, int[] result) {
-		boolean[] inResult = new boolean[n];
-		for (int i = 0; i < result.length; i++) {
-			if (result[i] >= n || result[i] < 0) return false;
-			if (inResult[i]) return false;
-			inResult[i] = true;
-		}
-		return true;
-	}
-	
-	
-	private double chiSquare(int[] buckets, int numBuckets) {
-		int x = 0;
-		int n = 0;
-		for (int e : buckets) {
-			x = x + e*e;
-			n += e;
-		}
-		return 1.0*x / (n/numBuckets) - n;
-	}
-	
-	private double chiSquare(int[][] buckets, int numBuckets) {
-		int m = 0;
-		int n = 0;
-		for (int x = 0; x < buckets.length; x++) {
-			for (int y = x+1; y < buckets.length; y++) {
-				int e = buckets[x][y];
-				m = m + e*e;
-				n += e;
-			}
-		}
-		return 1.0*m / (n/numBuckets) - n;
-	}
-	
-	private double chiSquareAll(int[][] buckets, int numBuckets) {
-		int m = 0;
-		int n = 0;
-		for (int x = 0; x < buckets.length; x++) {
-			for (int y = 0; y < buckets.length; y++) {
-				if (x==y) continue;
-				int e = buckets[x][y];
-				m = m + e*e;
-				n += e;
-			}
-		}
-		return 1.0*m / (n/numBuckets) - n;
-	}
-	
-	private double chiSquare(int[][][] buckets, int numBuckets) {
-		int m = 0;
-		int n = 0;
-		for (int x = 0; x < buckets.length; x++) {
-			for (int y = x+1; y < buckets.length; y++) {
-				for (int z = y+1; z < buckets.length; z++) {
-					int e = buckets[x][y][z];
-					m = m + e*e;
-					n += e;
-				}
-			}
-		}
-		return 1.0*m / (n/numBuckets) - n;
-	}
-	
-	private double chiSquareAll(int[][][] buckets, int numBuckets) {
-		int m = 0;
-		int n = 0;
-		for (int x = 0; x < buckets.length; x++) {
-			for (int y = 0; y < buckets.length; y++) {
-				if (y==x) continue;
-				for (int z = 0; z < buckets.length; z++) {
-					if (z==y || z==x) continue;
-					int e = buckets[x][y][z];
-					m = m + e*e;
-					n += e;
-				}
-			}
-		}
-		return 1.0*m / (n/numBuckets) - n;
-	}
-	
-	private final static double[] limit95 = {
-		EPSILON, 3.841458821, 5.991464547, 7.814727903,
-		9.487729037, 11.07049769, 12.59158724, 14.06714045,
-		15.50731306, 16.9189776, 18.30703805, 19.67513757,
-		21.02606982, 22.36203249, 23.6847913, 24.99579014,
-		26.2962276, 27.58711164, 28.86929943, 30.14352721,
-		31.41043284, 32.67057334, 33.92443847, 35.17246163,
-		36.4150285, 37.65248413, 38.88513866, 40.11327207,
-		41.33713815, 42.5569678, 43.77297183, 44.98534328,
-		46.19425952, 47.39988392, 48.60236737, 49.80184957,
-		50.99846017, 52.19231973, 53.38354062, 54.57222776,
-		55.75847928, 56.94238715, 58.12403768, 59.30351203,
-		60.48088658, 61.65623338, 62.82962041, 64.00111197,
-		65.1707689, 66.33864886, 67.50480655, 68.66929391,
-		69.83216034, 70.99345283, 72.15321617, 73.31149303,
-		74.46832416, 75.62374847, 76.77780316, 77.93052381,
-		79.08194449, 80.23209785, 81.38101519, 82.52872654,
-		83.67526074, 84.8206455, 85.96490744, 87.1080722,
-		88.25016442, 89.39120787, 90.53122543, 91.67023918,
-		92.80827038, 93.9453396, 95.08146667, 96.21667075,
-		97.35097038, 98.48438346, 99.61692732, 100.7486187,
-		101.879474, 103.0095087, 104.1387382, 105.2671773,
-		106.3948402, 107.521741, 108.647893, 109.7733094,
-		110.8980028, 112.0219857, 113.1452701, 114.2678677,
-		115.3897897, 116.5110473, 117.6316511, 118.7516118,
-		119.8709393, 120.9896437, 122.1077346, 123.2252215,
-		124.3421134, 125.4584194, 126.5741482, 127.6893083,
-		128.8039079, 129.9179553, 131.0314583, 132.1444245,
-		133.2568617, 134.3687771, 135.4801779, 136.5910712,
-		137.7014639, 138.8113626, 139.9207739, 141.0297043,
-		142.13816, 143.2461473, 144.353672, 145.4607402,
-		146.5673576 };
-}
\ No newline at end of file
diff --git a/tests/org/cicirello/math/rand/RandomIndexerTests.java b/tests/org/cicirello/math/rand/RandomIndexerTests.java
deleted file mode 100644
index a05e5f96..00000000
--- a/tests/org/cicirello/math/rand/RandomIndexerTests.java
+++ /dev/null
@@ -1,555 +0,0 @@
-/*
- * Copyright 2019-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-
-package org.cicirello.math.rand;
-
-import org.junit.*;
-import static org.junit.Assert.*;
-
-import java.util.Random;
-import java.util.SplittableRandom;
-
-/**
- * JUnit 4 tests for the methods of the RandomIndexer class.
- */
-public class RandomIndexerTests {
-	
-	private static double EPSILON = 1e-10;
-	
-	@Test
-	public void testArrayMask() {
-		SplittableRandom r1 = new SplittableRandom(42);
-		Random r2 = new Random(42);
-		for (int n = 1; n <= 5; n++) {
-			for (int k = 0; k <= n; k++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, k);
-				assertEquals("length incorrect", n, mask.length);
-				int count = 0;
-				for (int i = 0; i < mask.length; i++) {
-					if (mask[i]) count++;
-				}
-				assertEquals("wrong number of true values", k, count);
-				
-				mask = RandomIndexer.arrayMask(n, k, r1);
-				assertEquals("length incorrect", n, mask.length);
-				count = 0;
-				for (int i = 0; i < mask.length; i++) {
-					if (mask[i]) count++;
-				}
-				assertEquals("wrong number of true values", k, count);
-				
-				mask = RandomIndexer.arrayMask(n, k, r2);
-				assertEquals("length incorrect", n, mask.length);
-				count = 0;
-				for (int i = 0; i < mask.length; i++) {
-					if (mask[i]) count++;
-				}
-				assertEquals("wrong number of true values", k, count);
-				
-				mask = RandomIndexer.arrayMask(n);
-				assertEquals("length incorrect", n, mask.length);
-				
-				mask = RandomIndexer.arrayMask(n, r1);
-				assertEquals("length incorrect", n, mask.length);
-				
-				mask = RandomIndexer.arrayMask(n, r2);
-				assertEquals("length incorrect", n, mask.length);
-			}
-		}
-		
-		final int TRIALS = 120000;
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets);
-			assertTrue("Using ThreadLocalRandom, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, r1);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets);
-			assertTrue("Using SplittableRandom, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, r2);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets);
-			assertTrue("Using Random, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-	}
-	
-	@Test
-	public void testArrayMaskP05() {
-		double p = 0.5;
-		SplittableRandom r1 = new SplittableRandom(42);
-		Random r2 = new Random(42);
-		for (int n = 1; n <= 5; n++) {
-			for (int k = 0; k <= n; k++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p);
-				assertEquals("length incorrect", n, mask.length);
-				
-				mask = RandomIndexer.arrayMask(n, p, r1);
-				assertEquals("length incorrect", n, mask.length);
-				
-				mask = RandomIndexer.arrayMask(n, p, r2);
-				assertEquals("length incorrect", n, mask.length);
-			}
-		}
-		
-		final int TRIALS = 120000;
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets);
-			assertTrue("Using ThreadLocalRandom, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p, r1);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets);
-			assertTrue("Using SplittableRandom, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p, r2);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets);
-			assertTrue("Using Random, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-	}
-	
-	@Test
-	public void testArrayMaskP075() {
-		double p = 0.75;
-		double[] pa = {0.25, 0.75};
-		SplittableRandom r1 = new SplittableRandom(42);
-		Random r2 = new Random(42);
-		for (int n = 1; n <= 5; n++) {
-			for (int k = 0; k <= n; k++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p);
-				assertEquals("length incorrect", n, mask.length);
-				
-				mask = RandomIndexer.arrayMask(n, p, r1);
-				assertEquals("length incorrect", n, mask.length);
-				
-				mask = RandomIndexer.arrayMask(n, p, r2);
-				assertEquals("length incorrect", n, mask.length);
-			}
-		}
-		
-		final int TRIALS = 120000;
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets, pa);
-			assertTrue("Using ThreadLocalRandom, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p, r1);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets, pa);
-			assertTrue("Using SplittableRandom, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p, r2);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets, pa);
-			assertTrue("Using Random, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-	}
-	
-	@Test
-	public void testArrayMaskP025() {
-		double p = 0.25;
-		double[] pa = {0.75, 0.25};
-		SplittableRandom r1 = new SplittableRandom(42);
-		Random r2 = new Random(42);
-		for (int n = 1; n <= 5; n++) {
-			for (int k = 0; k <= n; k++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p);
-				assertEquals("length incorrect", n, mask.length);
-				
-				mask = RandomIndexer.arrayMask(n, p, r1);
-				assertEquals("length incorrect", n, mask.length);
-				
-				mask = RandomIndexer.arrayMask(n, p, r2);
-				assertEquals("length incorrect", n, mask.length);
-			}
-		}
-		
-		final int TRIALS = 120000;
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets, pa);
-			assertTrue("Using ThreadLocalRandom, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p, r1);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets, pa);
-			assertTrue("Using SplittableRandom, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-		for (int n = 1; n <= 5; n++) {
-			final int MAX = TRIALS / n;
-			int[] buckets = {0, 0};
-			for (int t = 0; t < MAX; t++) {
-				boolean[] mask = RandomIndexer.arrayMask(n, p, r2);
-				for (int i = 0; i < n; i++) {
-					if (mask[i]) buckets[1]++;
-					else buckets[0]++;
-				}
-			}
-			double chi = chiSquare(buckets, pa);
-			assertTrue("Using Random, chi square goodness of fit test: " + chi, chi <= 3.841);
-		}
-	}
-	
-	@Test
-	public void testArrayMaskBoundaryCases() {
-		SplittableRandom r1 = new SplittableRandom(42);
-		boolean[] mask = RandomIndexer.arrayMask(10, 1.0, r1);
-		assertEquals(10, mask.length);
-		for (int i = 0; i < mask.length; i++) {
-			assertTrue(mask[i]);
-		}
-		mask = RandomIndexer.arrayMask(10, 0.0, r1);
-		assertEquals(10, mask.length);
-		for (int i = 0; i < mask.length; i++) {
-			assertFalse(mask[i]);
-		}
-		Random r2 = new Random(42);
-		mask = RandomIndexer.arrayMask(10, 1.0, r2);
-		assertEquals(10, mask.length);
-		for (int i = 0; i < mask.length; i++) {
-			assertTrue(mask[i]);
-		}
-		mask = RandomIndexer.arrayMask(10, 0.0, r2);
-		assertEquals(10, mask.length);
-		for (int i = 0; i < mask.length; i++) {
-			assertFalse(mask[i]);
-		}
-	}
-	
-	@Test
-	public void testRandInt_ThreadLocalRandom() {
-		final int REPS_PER_BUCKET = 100;
-		double[] limit95 = {
-			EPSILON, 3.841, 5.991, 7.815, 9.488, 
-			11.07, 12.59, 14.07, 15.51, 16.92, 
-			18.31, 19.68, 21.03
-		};
-		int countH = 0;
-		for (int i = 1; i <= 13; i++) {
-			for (int trial = 0; trial < 100; trial++) {
-				int[] a = new int[i];
-				for (int j = 0; j < REPS_PER_BUCKET * i; j++) {
-					int k = RandomIndexer.nextInt(i);
-					assertTrue("nextInt outside range for bound="+i, k < i && k >= 0);
-					a[k] += 1;
-				}
-				for (int k = 0; k < i; k++) {
-					assertTrue("failed to generate any samples of "+k,a[k]>0);
-				}
-				double chi = chiSquare(a);
-				if (chi > limit95[i-1]) countH++;
-			}
-		}
-		assertTrue("chi square too high too often, countHigh=" + countH, countH <= 130);
-	}
-	
-	@Test
-	public void testRandInt_SplittableRandom() {
-		final int REPS_PER_BUCKET = 100;
-		SplittableRandom gen = new SplittableRandom(42);
-		double[] limit95 = {
-			EPSILON, 3.841, 5.991, 7.815, 9.488, 
-			11.07, 12.59, 14.07, 15.51, 16.92, 
-			18.31, 19.68, 21.03
-		};
-		int countH = 0;
-		for (int i = 1; i <= 13; i++) {
-			for (int trial = 0; trial < 100; trial++) {
-				int[] a = new int[i];
-				for (int j = 0; j < REPS_PER_BUCKET * i; j++) {
-					int k = RandomIndexer.nextInt(i, gen);
-					assertTrue("nextInt outside range for bound="+i, k < i && k >= 0);
-					a[k] += 1;
-				}
-				for (int k = 0; k < i; k++) {
-					assertTrue("failed to generate any samples of "+k,a[k]>0);
-				}
-				double chi = chiSquare(a);
-				if (chi > limit95[i-1]) countH++;
-			}
-		}
-		assertTrue("chi square too high too often, countHigh=" + countH, countH <= 130);
-	}
-	
-	@Test
-	public void testRandInt_Random() {
-		final int REPS_PER_BUCKET = 100;
-		Random gen = new Random(42);
-		double[] limit95 = {
-			EPSILON, 3.841, 5.991, 7.815, 9.488, 
-			11.07, 12.59, 14.07, 15.51, 16.92, 
-			18.31, 19.68, 21.03
-		};
-		int countH = 0;
-		for (int i = 1; i <= 13; i++) {
-			for (int trial = 0; trial < 100; trial++) {
-				int[] a = new int[i];
-				for (int j = 0; j < REPS_PER_BUCKET * i; j++) {
-					int k = RandomIndexer.nextInt(i, gen);
-					assertTrue("nextInt outside range for bound="+i, k < i && k >= 0);
-					a[k] += 1;
-				}
-				for (int k = 0; k < i; k++) {
-					assertTrue("failed to generate any samples of "+k,a[k]>0);
-				}
-				double chi = chiSquare(a);
-				if (chi > limit95[i-1]) countH++;
-			}
-		}
-		assertTrue("chi square too high too often, countHigh=" + countH, countH <= 130);
-	}
-	
-	@Test
-	public void testRandIntLargeBound_Splittable() {
-		SplittableRandom r = new SplittableRandom(42);
-		final int N = 5;
-		final int HIGH_BOUND = 0x40000001;
-		int[] v = new int[N];
-		for (int i = 0; i < N; i++) {
-			int value = RandomIndexer.nextInt(HIGH_BOUND, r);
-			assertTrue(value < HIGH_BOUND);
-			v[i] = value;
-			for (int j = i-1; j >= 0; j--) {
-				// Note that a failure here might be OK, but unlikely 
-				// due to small number of random samples from large bound.
-				assertNotEquals(v[j], value);
-			}
-		}
-	}
-	
-	@Test
-	public void testRandIntLargeBound_Random() {
-		Random r = new Random(42);
-		final int N = 5;
-		final int HIGH_BOUND = 0x40000001;
-		int[] v = new int[N];
-		for (int i = 0; i < N; i++) {
-			int value = RandomIndexer.nextInt(HIGH_BOUND, r);
-			assertTrue(value < HIGH_BOUND);
-			v[i] = value;
-			for (int j = i-1; j >= 0; j--) {
-				// Note that a failure here might be OK, but unlikely 
-				// due to small number of random samples from large bound.
-				assertNotEquals(v[j], value);
-			}
-		}
-	}
-	
-	@Test
-	public void testRandBiasedInt_ThreadLocalRandom() {
-		final int REPS_PER_BUCKET = 20;
-		for (int i = 1; i <= 13; i++) {
-			for (int trial = 0; trial < 10; trial++) {
-				int[] a = new int[i];
-				for (int j = 0; j < REPS_PER_BUCKET * i; j++) {
-					int k = RandomIndexer.nextBiasedInt(i);
-					assertTrue("nextInt outside range for bound="+i, k < i && k >= 0);
-					a[k] += 1;
-				}
-				for (int k = 0; k < i; k++) {
-					assertTrue("failed to generate any samples of "+k,a[k]>0);
-				}
-			}
-		}
-	}
-	
-	@Test
-	public void testRandBiasedInt_SplittableRandom() {
-		final int REPS_PER_BUCKET = 20;
-		SplittableRandom gen = new SplittableRandom(42);
-		for (int i = 1; i <= 13; i++) {
-			for (int trial = 0; trial < 10; trial++) {
-				int[] a = new int[i];
-				for (int j = 0; j < REPS_PER_BUCKET * i; j++) {
-					int k = RandomIndexer.nextBiasedInt(i, gen);
-					assertTrue("nextInt outside range for bound="+i, k < i && k >= 0);
-					a[k] += 1;
-				}
-				for (int k = 0; k < i; k++) {
-					assertTrue("failed to generate any samples of "+k,a[k]>0);
-				}
-			}
-		}
-	}
-	
-	@Test
-	public void testRandBiasedInt_Random() {
-		final int REPS_PER_BUCKET = 20;
-		Random gen = new Random(42);
-		for (int i = 1; i <= 13; i++) {
-			for (int trial = 0; trial < 10; trial++) {
-				int[] a = new int[i];
-				for (int j = 0; j < REPS_PER_BUCKET * i; j++) {
-					int k = RandomIndexer.nextBiasedInt(i, gen);
-					assertTrue("nextInt outside range for bound="+i, k < i && k >= 0);
-					a[k] += 1;
-				}
-				for (int k = 0; k < i; k++) {
-					assertTrue("failed to generate any samples of "+k,a[k]>0);
-				}
-			}
-		}
-	}
-	
-	@Test
-	public void testExceptions() {
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.nextInt(0)
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.nextInt(0, new SplittableRandom())
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.nextInt(0, new Random())
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.nextBiasedInt(0)
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.nextBiasedInt(0, new SplittableRandom())
-		);
-		thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> RandomIndexer.nextBiasedInt(0, new Random())
-		);
-	}
-	
-	
-	
-	private double chiSquare(int[] buckets) {
-		int x = 0;
-		int n = 0;
-		for (int e : buckets) {
-			x = x + e*e;
-			n += e;
-		}
-		return 1.0*x / (n/buckets.length) - n;
-	}
-	
-	private double chiSquare(int[] buckets, double[] p) {
-		double x = 0;
-		int n = 0;
-		for (int i = 0; i < buckets.length; i++) {
-			int e = buckets[i];
-			x = x + e*e/p[i];
-			n += e;
-		}
-		return x / n - n;
-	}
-}
\ No newline at end of file
diff --git a/tests/org/cicirello/math/rand/RandomVariatesTests.java b/tests/org/cicirello/math/rand/RandomVariatesTests.java
deleted file mode 100644
index 0b829c87..00000000
--- a/tests/org/cicirello/math/rand/RandomVariatesTests.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright 2019-2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-
-package org.cicirello.math.rand;
-
-import org.junit.*;
-import static org.junit.Assert.*;
-
-import java.util.Random;
-import java.util.SplittableRandom;
-import org.cicirello.math.MathFunctions;
-
-/**
- * JUnit 4 tests for the methods of the RandomVariates class.
- */
-public class RandomVariatesTests {
-	
-	private static double EPSILON = 1e-10;
-	
-	@Test
-	public void testNextBinomialSplittable() {
-		SplittableRandom r = new SplittableRandom(42);
-		final int MAX_N = 10;
-		double[] crit = {3.841, 5.991, 7.815, 9.488, 11.07, 12.59, 14.07, 15.51, 16.92, 18.31};
-		double[] P = {0.25, 0.5, 0.75};
-		for (double p : P) {
-			double q = 1-p;
-			for (int n = 1; n <= MAX_N; n++) {
-				double[] dist = new double[n+1];
-				for (int i = 0; i < dist.length; i++) {
-					dist[i] = CHOOSE[n][i] * MathFunctions.pow(p, i) * Math.pow(q, n-i);
-				}
-				final double TRIALS = 300 * (n+1); //1.0 / Math.min(dist[0], dist[n]) * 10 * (n+1);
-				int[] buckets = new int[n+1];
-				for (int i = 0; i < TRIALS; i++) {
-					int b = RandomVariates.nextBinomial(n, p, r);
-					buckets[b]++;
-				}
-				double chi = chiSquare(buckets, dist);
-				assertTrue("n="+n+" p="+p+ " chi="+chi, chi <= crit[n-1]);
-			}
-		}
-		for (double p : P) {
-			double q = 1-p;
-			for (int n = 30; n <= 50; n+=20) {
-				int which = n == 30 ? 0 : 1;
-				double[] dist = new double[n+1];
-				for (int i = 0; i < dist.length; i++) {
-					dist[i] = CHOOSE_LARGE[which][i] * MathFunctions.pow(p, i) * Math.pow(q, n-i);
-				}
-				final double TRIALS = 300 * (n+1); //1.0 / Math.min(dist[0], dist[n]) * 10 * (n+1);
-				int[] buckets = new int[n+1];
-				for (int i = 0; i < TRIALS; i++) {
-					int b = RandomVariates.nextBinomial(n, p, r);
-					buckets[b]++;
-				}
-				double chi = chiSquare(buckets, dist);
-				double critV = n == 30 ? 43.77 : 67.50;
-				assertTrue("n="+n+" p="+p+ " chi="+chi, chi <= critV);
-			}
-		}
-	}
-	
-	@Test
-	public void testNextBinomialRandom() {
-		Random r = new Random(81);
-		final int MAX_N = 10;
-		double[] crit = {3.841, 5.991, 7.815, 9.488, 11.07, 12.59, 14.07, 15.51, 16.92, 18.31};
-		double[] P = {0.25, 0.5, 0.75};
-		for (double p : P) {
-			double q = 1-p;
-			for (int n = 1; n <= MAX_N; n++) {
-				double[] dist = new double[n+1];
-				for (int i = 0; i < dist.length; i++) {
-					dist[i] = CHOOSE[n][i] * MathFunctions.pow(p, i) * Math.pow(q, n-i);
-				}
-				final double TRIALS = 300 * (n+1); //1.0 / Math.min(dist[0], dist[n]) * 10 * (n+1);
-				int[] buckets = new int[n+1];
-				for (int i = 0; i < TRIALS; i++) {
-					int b = RandomVariates.nextBinomial(n, p, r);
-					assertTrue("Invalid result, (n, p)=("+n+", "+p+"), r="+b, b >= 0 && b <= n);
-					buckets[b]++;
-				}
-				double chi = chiSquare(buckets, dist);
-				assertTrue("n="+n+" p="+p+ " chi="+chi, chi <= crit[n-1]);
-			}
-		}
-		for (double p : P) {
-			double q = 1-p;
-			for (int n = 30; n <= 50; n+=20) {
-				int which = n == 30 ? 0 : 1;
-				double[] dist = new double[n+1];
-				for (int i = 0; i < dist.length; i++) {
-					dist[i] = CHOOSE_LARGE[which][i] * MathFunctions.pow(p, i) * Math.pow(q, n-i);
-				}
-				final double TRIALS = 300 * (n+1); //1.0 / Math.min(dist[0], dist[n]) * 10 * (n+1);
-				int[] buckets = new int[n+1];
-				for (int i = 0; i < TRIALS; i++) {
-					int b = RandomVariates.nextBinomial(n, p, r);
-					buckets[b]++;
-				}
-				double chi = chiSquare(buckets, dist);
-				double critV = n == 30 ? 43.77 : 67.50;
-				assertTrue("n="+n+" p="+p+ " chi="+chi, chi <= critV);
-			}
-		}
-	}
-	
-	@Test
-	public void testNextBinomialLargeN_Splittable() {
-		SplittableRandom r = new SplittableRandom(42);
-		final int N = 1000;
-		final int K = 20;
-		int t01 = 0;
-		int t25 = 0;
-		int t50 = 0;
-		for (int i = 0; i < K; i++) {
-			int b01 = RandomVariates.nextBinomial(N, 0.01, r);
-			int b25 = RandomVariates.nextBinomial(N, 0.25, r);
-			int b50 = RandomVariates.nextBinomial(N, 0.5, r);
-			assertTrue(b01 >= 0 && b01 <= N);
-			assertTrue(b25 >= 0 && b25 <= N);
-			assertTrue(b50 >= 0 && b50 <= N);
-			t01 += b01;
-			t25 += b25;
-			t50 += b50;
-		}
-		assertTrue(Math.abs(N*0.5 - 1.0*t50/K) <= N*0.125);
-		assertTrue(Math.abs(N*0.25 - 1.0*t25/K) <= N*0.125);
-		assertTrue(Math.abs(N*0.01 - 1.0*t01/K) <= N*0.125);
-	}
-	
-	@Test
-	public void testNextBinomialLargeN_Random() {
-		Random r = new Random(42);
-		final int N = 3000;
-		final int K = 20;
-		int t01 = 0;
-		int t25 = 0;
-		int t50 = 0;
-		for (int i = 0; i < K; i++) {
-			int b01 = RandomVariates.nextBinomial(N, 0.01, r);
-			int b25 = RandomVariates.nextBinomial(N, 0.25, r);
-			int b50 = RandomVariates.nextBinomial(N, 0.5, r);
-			assertTrue(b01 >= 0 && b01 <= N);
-			assertTrue(b25 >= 0 && b25 <= N);
-			assertTrue(b50 >= 0 && b50 <= N);
-			t01 += b01;
-			t25 += b25;
-			t50 += b50;
-		}
-		assertTrue(Math.abs(N*0.5 - 1.0*t50/K) <= N*0.125);
-		assertTrue(Math.abs(N*0.25 - 1.0*t25/K) <= N*0.125);
-		assertTrue(Math.abs(N*0.01 - 1.0*t01/K) <= N*0.125);
-	}
-	
-	@Test
-	public void testNextBinomialThreadLocal() {
-		// Since we cannot seed ThreadLocalRandom, this test case is
-		// not 100% replicable.  Additionally, we know that this version of
-		// the method simply calls the version that takes a Random as a parameter.
-		// Since we did test that version for goodness of fit with a binomial,
-		// and since replication is not possible without a seed, we simply verify
-		// that over a large number of trials that most possible outcomes are produced.
-		final int MAX_N = 10;
-		double[] P = {0.5, 0.75};
-		for (double p : P) {
-			for (int n = 1; n <= MAX_N; n++) {
-				final double TRIALS = 2000 * (n+1); 
-				boolean[] generated = new boolean[n+1];
-				int count = 0;
-				for (int i = 0; i < TRIALS; i++) {
-					int b = RandomVariates.nextBinomial(n, p);
-					if (!generated[b]) {
-						generated[b] = true;
-						count++;
-						if (count >= n-1) break;
-					} 
-				}
-				assertTrue("Verifying most possible outcomes produced over large number of trials", count >= n-1);
-			}
-		}
-	}
-	
-	@Test
-	public void testNextCauchyRandom() {
-		final int TRIALS = 8000;
-		Random r = new Random(42);
-		double[] scale = {1, 2, 4};
-		final double sqrt2 = Math.sqrt(2);
-		for (double s : scale) {
-			double[] boundaries = { -s*(sqrt2+1), -s, -s*(sqrt2-1), 0 , s*(sqrt2-1), s, s*(sqrt2+1)};
-			int[] buckets = new int[8];
-			for (int j = 0; j < TRIALS; j++) {
-				double v = RandomVariates.nextCauchy(s, r);
-				int i = 0;
-				for (; i < boundaries.length; i++) {
-					if (v < boundaries[i]) break;
-				}
-				buckets[i]++;
-			}
-			double chi = chiSquare(buckets);
-			// critical value for 8-1=7 degrees of freedom is 14.07 (at the .95 level).
-			assertTrue("Verifying chi square statistic below .95 critical value: chi="+chi+" crit=14.07", chi <= 14.07);
-		}
-		double median = 5.0;
-		double s = 1.0;
-		double[] boundaries = { median - s*(sqrt2+1), median - s, median - s*(sqrt2-1), median , median + s*(sqrt2-1), median + s, median + s*(sqrt2+1)};
-		int[] buckets = new int[8];
-		for (int j = 0; j < TRIALS; j++) {
-			double v = RandomVariates.nextCauchy(median, s, r);
-			int i = 0;
-			for (; i < boundaries.length; i++) {
-				if (v < boundaries[i]) break;
-			}
-			buckets[i]++;
-		}
-		double chi = chiSquare(buckets);
-		// critical value for 8-1=7 degrees of freedom is 14.07 (at the .95 level).
-		assertTrue("Verifying chi square statistic below .95 critical value: chi="+chi+" crit=14.07", chi <= 14.07);
-	}
-	
-	@Test
-	public void testNextCauchyThreadLocalRandom() {
-		// Since we cannot seed ThreadLocalRandom, this test case is
-		// not 100% replicable.  Additionally, we know that this version of
-		// the method simply calls the version that takes a Random as a parameter.
-		// Since we did test that version for goodness of fit,
-		// and since replication is not possible without a seed, we simply verify
-		// that over a large number of trials that samples are generated in each of the buckets
-		// used for the goodness of fit tests in the class Random case.
-		final int TRIALS = 800;
-		double[] scale = {1, 2, 4};
-		final double sqrt2 = Math.sqrt(2);
-		for (double s : scale) {
-			double[] boundaries = { -s*(sqrt2+1), -s, -s*(sqrt2-1), 0 , s*(sqrt2-1), s, s*(sqrt2+1)};
-			int[] buckets = new int[8];
-			for (int j = 0; j < TRIALS; j++) {
-				double v = RandomVariates.nextCauchy(s);
-				int i = 0;
-				for (; i < boundaries.length; i++) {
-					if (v < boundaries[i]) break;
-				}
-				buckets[i]++;
-			}
-			for (int i = 0; i < buckets.length; i++) {
-				assertTrue("verifying at least 1 sample in each bucket, scale="+s + " bucket="+i, buckets[i] > 0);
-			}
-		}
-		double median = 5.0;
-		double s = 1.0;
-		double[] boundaries = { median - s*(sqrt2+1), median - s, median - s*(sqrt2-1), median , median + s*(sqrt2-1), median + s, median + s*(sqrt2+1)};
-		int[] buckets = new int[8];
-		for (int j = 0; j < TRIALS; j++) {
-			double v = RandomVariates.nextCauchy(median, s);
-			int i = 0;
-			for (; i < boundaries.length; i++) {
-				if (v < boundaries[i]) break;
-			}
-			buckets[i]++;
-		}
-		for (int i = 0; i < buckets.length; i++) {
-			assertTrue("verifying at least 1 sample in each bucket, median=5 bucket="+i, buckets[i] > 0);
-		}
-	}
-	
-	@Test
-	public void testNextCauchySplittableRandom() {
-		final int TRIALS = 8000;
-		SplittableRandom r = new SplittableRandom(53);
-		double[] scale = {1, 2, 4};
-		final double sqrt2 = Math.sqrt(2);
-		for (double s : scale) {
-			double[] boundaries = { -s*(sqrt2+1), -s, -s*(sqrt2-1), 0 , s*(sqrt2-1), s, s*(sqrt2+1)};
-			int[] buckets = new int[8];
-			for (int j = 0; j < TRIALS; j++) {
-				double v = RandomVariates.nextCauchy(s, r);
-				int i = 0;
-				for (; i < boundaries.length; i++) {
-					if (v < boundaries[i]) break;
-				}
-				buckets[i]++;
-			}
-			double chi = chiSquare(buckets);
-			// critical value for 8-1=7 degrees of freedom is 14.07 (at the .95 level).
-			assertTrue("Verifying chi square statistic below .95 critical value: chi="+chi+" crit=14.07", chi <= 14.07);
-		}
-		double median = 5.0;
-		double s = 1.0;
-		double[] boundaries = { median - s*(sqrt2+1), median - s, median - s*(sqrt2-1), median , median + s*(sqrt2-1), median + s, median + s*(sqrt2+1)};
-		int[] buckets = new int[8];
-		for (int j = 0; j < TRIALS; j++) {
-			double v = RandomVariates.nextCauchy(median, s, r);
-			int i = 0;
-			for (; i < boundaries.length; i++) {
-				if (v < boundaries[i]) break;
-			}
-			buckets[i]++;
-		}
-		double chi = chiSquare(buckets);
-		// critical value for 8-1=7 degrees of freedom is 14.07 (at the .95 level).
-		assertTrue("Verifying chi square statistic below .95 critical value: chi="+chi+" crit=14.07", chi <= 14.07);
-	}
-	
-	@Test
-	public void testNextCauchyHelpersEdgeCase() {
-		SplittableRandom r = new SplittableRandom(53);
-		boolean foundPositive = false;
-		boolean foundNegative = false;
-		for (int i = 0; i < 4; i++) {
-			double u = RandomVariates.internalNextTransformedU(r, 0.5);
-			assertEquals(0.5, Math.abs(u), 0.0);
-			if (u > 0) foundPositive = true;
-			else foundNegative = true;
-		}
-		assertTrue(foundPositive);
-		assertTrue(foundNegative);
-		
-		Random r2 = new Random(53);
-		foundPositive = false;
-		foundNegative = false;
-		for (int i = 0; i < 4; i++) {
-			double u = RandomVariates.internalNextTransformedU(r2, 0.5);
-			assertEquals(0.5, Math.abs(u), 0.0);
-			if (u > 0) foundPositive = true;
-			else foundNegative = true;
-		}
-		assertTrue(foundPositive);
-		assertTrue(foundNegative);
-	}
-	
-	private double chiSquare(int[] buckets) {
-		int n = 0;
-		int v = 0;
-		for (int i = 0; i < buckets.length; i++) {
-			n += buckets[i];
-			v += (buckets[i]*buckets[i]);
-		}
-		return v * buckets.length * 1.0 / n - n; 
-	}
-	
-	private double chiSquare(int[] buckets, double[] dist) {
-		double v = 0;
-		int n = 0;
-		for (int i = 0; i < buckets.length; i++) {
-			n += buckets[i];
-		}
-		for (int i = 0; i < buckets.length; i++) {
-			double np = n * dist[i];
-			v += (buckets[i] - np) * (buckets[i] - np) / np;
-		}
-		return v;
-	}
-	
-	private static final double[][] CHOOSE_LARGE = {
-		{1f, 30f, 435f, 4060f, 27405f, 142506f, 593775f, 2035800f, 5852925f, 14307150f, 30045015f, 54627300f, 86493225f, 119759850f, 145422675f, 155117520f, 145422675f, 119759850f, 86493225f, 54627300f, 30045015f, 14307150f, 5852925f, 2035800f, 593775f, 142506f, 27405f, 4060f, 435f, 30f, 1},
-		{1f, 50f, 1225f, 19600f, 230300f, 2118760f, 15890700f, 99884400f, 536878650f, 2505433700f, 10272278170f, 37353738800f, 1.214E+11f, 3.54861E+11f, 9.37846E+11f, 2.25083E+12f, 4.92369E+12f, 9.84738E+12f, 1.80535E+13f, 3.04059E+13f, 4.71292E+13f, 6.73274E+13f, 8.87498E+13f, 1.08043E+14f, 1.21549E+14f, 1.26411E+14f, 1.21549E+14f, 1.08043E+14f, 8.87498E+13f, 6.73274E+13f, 4.71292E+13f, 3.04059E+13f, 1.80535E+13f, 9.84738E+12f, 4.92369E+12f, 2.25083E+12f, 9.37846E+11f, 3.54861E+11f, 1.214E+11f, 37353738800f, 10272278170f, 2505433700f, 536878650f, 99884400f, 15890700f, 2118760f, 230300f, 19600f, 1225f, 50f, 1}
-	};
-	
-	private static final int[][] CHOOSE = {
-		{1},
-		{1, 1},
-		{1, 2, 1},
-		{1, 3, 3, 1},
-		{1, 4, 6, 4, 1},
-		{1, 5, 10, 10, 5, 1},
-		{1, 6, 15, 20, 15, 6, 1},
-		{1, 7, 21, 35, 35, 21, 7, 1},
-		{1, 8, 28, 56, 70, 56, 28, 8, 1},
-		{1, 9, 36, 84, 126, 126, 84, 36, 9, 1},
-		{1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1}
-	};
-}
\ No newline at end of file
diff --git a/tests/org/cicirello/math/stats/StatisticsTests.java b/tests/org/cicirello/math/stats/StatisticsTests.java
deleted file mode 100644
index c35f7706..00000000
--- a/tests/org/cicirello/math/stats/StatisticsTests.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright 2018, 2021 Vincent A. Cicirello, <https://www.cicirello.org/>.
- *
- * This file is part of JavaPermutationTools (https://jpt.cicirello.org/).
- *
- * JavaPermutationTools is free software: you can 
- * redistribute it and/or modify it under the terms of the GNU 
- * General Public License as published by the Free Software 
- * Foundation, either version 3 of the License, or (at your 
- * option) any later version.
- *
- * JavaPermutationTools is distributed in the hope 
- * that it will be useful, but WITHOUT ANY WARRANTY; without even 
- * the implied warranty of MERCHANTABILITY or FITNESS FOR A 
- * PARTICULAR PURPOSE.  See the GNU General Public License for more 
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with JavaPermutationTools.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-
-package org.cicirello.math.stats;
-
-import org.junit.*;
-import static org.junit.Assert.*;
-
-/**
- * JUnit 4 tests for the methods of the Statistics class.
- */
-public class StatisticsTests {
-	
-	private static double EPSILON = 1e-10;
-	
-	@Test
-	public void testMeanOfInts() {
-		for (int n = 1; n <= 10; n++) {
-			int[] data = new int[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = 3;
-			}
-			assertEquals("All elements the same", 3.0, Statistics.mean(data), EPSILON);
-		}
-		for (int n = 1; n <= 10; n++) {
-			int[] data = new int[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = i+1;
-			}
-			assertEquals("ints from 1 to n", (n+1)/2.0, Statistics.mean(data), EPSILON);
-		}	
-	}
-	
-	@Test
-	public void testMeanOfDoubles() {
-		for (int n = 1; n <= 10; n++) {
-			double[] data = new double[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = 3;
-			}
-			assertEquals("All elements the same", 3.0, Statistics.mean(data), EPSILON);
-		}
-		for (int n = 1; n <= 10; n++) {
-			double[] data = new double[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = i+1;
-			}
-			assertEquals("ints from 1 to n as doubles", (n+1)/2.0, Statistics.mean(data), EPSILON);
-		}	
-	}
-	
-	@Test
-	public void testVarianceOfInts() {
-		for (int n = 1; n <= 10; n++) {
-			int[] data = new int[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = 3;
-			}
-			assertEquals("All elements the same", 0.0, Statistics.variance(data), EPSILON);
-		}
-		double[] expected = {0.25, 2.0/3.0, 5.0/4.0 };
-		for (int n = 2; n <= 4; n++) {
-			int[] data = new int[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = i+1;
-			}
-			assertEquals("ints from 1 to n", expected[n-2], Statistics.variance(data), EPSILON);
-		}	
-	}
-	
-	@Test
-	public void testVarianceOfDoubles() {
-		for (int n = 1; n <= 10; n++) {
-			double[] data = new double[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = 3;
-			}
-			assertEquals("All elements the same", 0.0, Statistics.variance(data), EPSILON);
-		}
-		double[] expected = {0.25, 2.0/3.0, 5.0/4.0 };
-		for (int n = 2; n <= 4; n++) {
-			double[] data = new double[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = i+1;
-			}
-			assertEquals("ints from 1 to n", expected[n-2], Statistics.variance(data), EPSILON);
-		}	
-	}
-	
-	@Test
-	public void testSampleVarianceOfInts() {
-		for (int n = 1; n <= 10; n++) {
-			int[] data = new int[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = 3;
-			}
-			assertEquals("All elements the same", 0.0, Statistics.varianceSample(data), EPSILON);
-		}
-		// Note: This assumes that the variance method passes its tests.
-		for (int n = 2; n <= 10; n++) {
-			int[] data = new int[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = i+1;
-			}
-			assertEquals("ints from 1 to n", Statistics.variance(data)*n/(n-1), Statistics.varianceSample(data), EPSILON);
-		}	
-	}
-	
-	@Test
-	public void testSampleVarianceOfDoubles() {
-		for (int n = 1; n <= 10; n++) {
-			double[] data = new double[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = 3;
-			}
-			assertEquals("All elements the same", 0.0, Statistics.varianceSample(data), EPSILON);
-		}
-		// Note: This assumes that the variance method passes its tests.
-		for (int n = 2; n <= 10; n++) {
-			double[] data = new double[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = i+1;
-			}
-			assertEquals("ints from 1 to n", Statistics.variance(data)*n/(n-1), Statistics.varianceSample(data), EPSILON);
-		}	
-	}
-	
-	@Test
-	public void testCovarianceOfInts() {
-		for (int n = 1; n <= 10; n++) {
-			int[] data = new int[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = 3;
-			}
-			assertEquals("covariance of X with itself, X all same", 0.0, Statistics.covariance(data, data.clone()), EPSILON);
-		}
-		double[] expected = {0.25, 2.0/3.0, 5.0/4.0 };
-		for (int n = 2; n <= 4; n++) {
-			int[] data = new int[n];
-			int[] data2 = new int[n];
-			int[] data3 = new int[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = i+1;
-				data2[i] = 3 * data[i];
-				data3[i] = 4 * data[i];
-			}
-			// assumes variance method already tested... uses it in expected result
-			assertEquals("covariance of X with itself", Statistics.variance(data), Statistics.covariance(data, data.clone()), EPSILON);
-			assertEquals("covariance of X with itself", 3*4*Statistics.variance(data), Statistics.covariance(data2, data3), EPSILON);
-		}	
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> Statistics.covariance(new int[3], new int[4])
-		);
-	}
-	
-	@Test
-	public void testCovarianceOfDoubles() {
-		for (int n = 1; n <= 10; n++) {
-			double[] data = new double[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = 3;
-			}
-			assertEquals("covariance of X with itself, X all same", 0.0, Statistics.covariance(data, data.clone()), EPSILON);
-		}
-		double[] expected = {0.25, 2.0/3.0, 5.0/4.0 };
-		for (int n = 2; n <= 4; n++) {
-			double[] data = new double[n];
-			double[] data2 = new double[n];
-			double[] data3 = new double[n];
-			for (int i = 0; i < n; i++) {
-				data[i] = i+1;
-				data2[i] = 3.3 * data[i];
-				data3[i] = 1.4 * data[i];
-			}
-			// assumes variance method already tested... uses it in expected result
-			assertEquals("covariance of X with itself", Statistics.variance(data), Statistics.covariance(data, data.clone()), EPSILON);
-			assertEquals("covariance of X with itself", 3.3*1.4*Statistics.variance(data), Statistics.covariance(data2, data3), EPSILON);
-		}
-		IllegalArgumentException thrown = assertThrows( 
-			IllegalArgumentException.class,
-			() -> Statistics.covariance(new double[3], new double[4])
-		);		
-	}
-	
-	
-	@Test
-	public void testCorrelationOfInts() {
-		for (int n = 2; n <= 10; n++) {
-			int[] x = new int[n];
-			for (int i = 0; i < n; i++) {
-				x[i] = i+1;
-			}
-			int[] y = x.clone();
-			assertEquals("X and Y identical", 1.0, Statistics.correlation(x, y), EPSILON);
-			for (int i = 0; i < n; i++) {
-				y[i] *= -1;
-			}
-			assertEquals("Y = -X", -1.0, Statistics.correlation(x, y), EPSILON);
-			for (int i = 0; i < n; i++) {
-				y[i] = 3*x[i];
-			}
-			assertEquals("Y = alpha * X", 1.0, Statistics.correlation(x, y), EPSILON);
-			for (int i = 0; i < n; i++) {
-				y[i] *= -1;
-			}
-			assertEquals("Y = -alpha * X", -1.0, Statistics.correlation(x, y), EPSILON);
-		}
-		int[] x = { 2, 2, 2 };
-		int[] y = { 2, 1, 3 };
-		assertEquals(0.0, Statistics.correlation(x, y), 0.0);
-		assertEquals(0.0, Statistics.correlation(y, x), 0.0);
-		int[] z = { 4, 1, 1 };
-		assertEquals(0.0, Statistics.correlation(y, z), 0.0);
-	}
-	
-	@Test
-	public void testCorrelationOfDoubles() {
-		for (int n = 2; n <= 10; n++) {
-			double[] x = new double[n];
-			for (int i = 0; i < n; i++) {
-				x[i] = i+1;
-			}
-			double[] y = x.clone();
-			assertEquals("X and Y identical", 1.0, Statistics.correlation(x, y), EPSILON);
-			for (int i = 0; i < n; i++) {
-				y[i] *= -1;
-			}
-			assertEquals("Y = -X", -1.0, Statistics.correlation(x, y), EPSILON);
-			for (int i = 0; i < n; i++) {
-				y[i] = 3*x[i];
-			}
-			assertEquals("Y = alpha * X", 1.0, Statistics.correlation(x, y), EPSILON);
-			for (int i = 0; i < n; i++) {
-				y[i] *= -1;
-			}
-			assertEquals("Y = -alpha * X", -1.0, Statistics.correlation(x, y), EPSILON);
-		}
-		double[] x = { 2, 2, 2 };
-		double[] y = { 2, 1, 3 };
-		assertEquals(0.0, Statistics.correlation(x, y), 0.0);
-		assertEquals(0.0, Statistics.correlation(y, x), 0.0);
-		double[] z = { 4, 1, 1 };
-		assertEquals(0.0, Statistics.correlation(y, z), 0.0);
-	}
-	
-	@Test
-	public void testCorrelationMatrixOfInts() {
-		int[][] data = {
-			{2, 1, 3},
-			{6, 3, 9},
-			{4, 1, 1},
-			{2, 3, 1},
-			{4, 6, 2}
-		};
-		double[][] M = Statistics.correlationMatrix(data);
-		assertEquals(data.length, M.length);
-		assertEquals(data.length, M[0].length);
-		double[][] expected = {
-			{1.0, 1.0, 0.0, -1.0, -1.0},
-			{1.0, 1.0, 0.0, -1.0, -1.0},
-			{0.0, 0.0, 1.0, 0.0, 0.0},
-			{-1.0, -1.0, 0.0, 1.0, 1.0},
-			{-1.0, -1.0, 0.0, 1.0, 1.0}
-		};
-		for (int i = 0; i < M.length; i++) {
-			for (int j = 0; j < M.length; j++) {
-				assertEquals(expected[i][j], M[i][j], EPSILON);
-			}
-		}
-	}
-	
-	@Test
-	public void testCorrelationMatrixOfDoubles() {
-		double[][] data = {
-			{2, 1, 3},
-			{6, 3, 9},
-			{4, 1, 1},
-			{2, 3, 1},
-			{4, 6, 2}
-		};
-		double[][] M = Statistics.correlationMatrix(data);
-		assertEquals(data.length, M.length);
-		assertEquals(data.length, M[0].length);
-		double[][] expected = {
-			{1.0, 1.0, 0.0, -1.0, -1.0},
-			{1.0, 1.0, 0.0, -1.0, -1.0},
-			{0.0, 0.0, 1.0, 0.0, 0.0},
-			{-1.0, -1.0, 0.0, 1.0, 1.0},
-			{-1.0, -1.0, 0.0, 1.0, 1.0}
-		};
-		for (int i = 0; i < M.length; i++) {
-			for (int j = 0; j < M.length; j++) {
-				assertEquals(expected[i][j], M[i][j], EPSILON);
-			}
-		}
-	}
-	
-	@Test
-	public void testWelchsTTestDoubles() {
-		double[] a1 = {27.5, 21.0, 19.0, 23.6, 17.0, 17.9, 
-			16.9, 20.1, 21.9, 22.6, 23.1, 19.6, 19.0, 21.7, 21.4};
-		double[] a2 = {27.1, 22.0, 20.8, 23.4, 23.4, 23.5, 
-			25.8, 22.0, 24.8, 20.2, 21.9, 22.1, 22.9, 20.5, 24.4};
-		assertEquals(-2.46, Statistics.tTestUnequalVariances(a1, a2), 0.01);
-		assertEquals(2.46, Statistics.tTestUnequalVariances(a2, a1), 0.01);
-		double[] b1 = {17.2, 20.9, 22.6, 18.1, 21.7, 21.4, 23.5, 24.2, 14.7, 21.8};
-		double[] b2 = {21.5, 22.8, 21.0, 23.0, 21.6, 23.6, 22.5, 20.7, 23.4, 21.8, 
-			20.7, 21.7, 21.5, 22.5, 23.6, 21.5, 22.5, 23.5, 21.5, 21.8};
-		assertEquals(-1.57, Statistics.tTestUnequalVariances(b1, b2), 0.01);
-		assertEquals(1.57, Statistics.tTestUnequalVariances(b2, b1), 0.01);
-		
-		Number[] result = Statistics.tTestWelch(a1, a2);
-		assertEquals(-2.46, (Double)result[0], 0.01);
-		assertEquals(24, ((Integer)result[1]).intValue());
-		result = Statistics.tTestWelch(a2, a1);
-		assertEquals(2.46, (Double)result[0], 0.01);
-		assertEquals(24, ((Integer)result[1]).intValue());
-		result = Statistics.tTestWelch(b1, b2);
-		assertEquals(-1.57, (Double)result[0], 0.01);
-		assertEquals(9, ((Integer)result[1]).intValue());
-		result = Statistics.tTestWelch(b2, b1);
-		assertEquals(1.57, (Double)result[0], 0.01);
-		assertEquals(9, ((Integer)result[1]).intValue());
-	}
-	
-	@Test
-	public void testWelchsTTestInts() {
-		int[] a1 = {2, 4, 7, 1, 8, 10, 11, 6, 2, 9, 1, 23, 12, 0, 7};
-		int[] a2 = {17, 2, 8, 19, 12, 4, 2, 0, 22, 7, 7, 1, 9, 12, 13};
-		assertEquals(-0.9173, Statistics.tTestUnequalVariances(a1, a2), 0.0001);
-		assertEquals(0.9173, Statistics.tTestUnequalVariances(a2, a1), 0.0001);
-		int[] b1 = {2, 4, 7, 1, 8, 10, 11, 6, 2, 9};
-		int[] b2 = {17, 2, 8, 19, 12, 4, 2, 0, 22, 7, 7, 1, 9, 12, 13, 0, 14, 1, 6, 30};
-		assertEquals(-1.5369, Statistics.tTestUnequalVariances(b1, b2), 0.0001);
-		assertEquals(1.5369, Statistics.tTestUnequalVariances(b2, b1), 0.0001);
-		
-		Number[] result = Statistics.tTestWelch(a1, a2);
-		assertEquals(-0.9173, (Double)result[0], 0.0001);
-		assertEquals(27, ((Integer)result[1]).intValue());
-		result = Statistics.tTestWelch(a2, a1);
-		assertEquals(0.9173, (Double)result[0], 0.0001);
-		assertEquals(27, ((Integer)result[1]).intValue());
-		result = Statistics.tTestWelch(b1, b2);
-		assertEquals(-1.5369, (Double)result[0], 0.0001);
-		assertEquals(27, ((Integer)result[1]).intValue());
-		result = Statistics.tTestWelch(b2, b1);
-		assertEquals(1.5369, (Double)result[0], 0.0001);
-		assertEquals(27, ((Integer)result[1]).intValue());
-	}
-}
\ No newline at end of file
