diff options
| author | Yohann Roussel <yroussel@google.com> | 2014-03-19 16:25:37 +0100 |
|---|---|---|
| committer | Yohann Roussel <yroussel@google.com> | 2014-03-20 15:13:33 +0100 |
| commit | 4eceb95409e844fdc33c9c706e1dc307bfd40303 (patch) | |
| tree | ee9f4f3fc79f757c79081c336bce4f1782c6ccd8 /maths | |
| parent | 3d2402901b1a6462e2cf47a6fd09711f327961c3 (diff) | |
| download | toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.gz toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.bz2 toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.zip | |
Initial Jack import.
Change-Id: I953cf0a520195a7187d791b2885848ad0d5a9b43
Diffstat (limited to 'maths')
97 files changed, 10951 insertions, 0 deletions
diff --git a/maths/Android.mk b/maths/Android.mk new file mode 100644 index 00000000..44bca4e1 --- /dev/null +++ b/maths/Android.mk @@ -0,0 +1,30 @@ +# Copyright (C) 2013 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) + +# +# Maths +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, core/src/java/main) + +LOCAL_MODULE := maths-jack + +LOCAL_MODULE_TAGS := optional + +LOCAL_JAVA_LIBRARIES := + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/maths/CHANGELOG.txt b/maths/CHANGELOG.txt new file mode 100644 index 00000000..074556f0 --- /dev/null +++ b/maths/CHANGELOG.txt @@ -0,0 +1,110 @@ +Changes in version 1.2.3 +------------------------ + +* Fixed bug in convertBytesToLong method in the BinaryUtils class. + +* Added OSGi bundle metadata (contributed by Dave LeBlanc). + +* Updated documentation to explain why setSeed does nothing for any of the + RNGs. + + +Changes in version 1.2.2 +------------------------ + +* Fixed serialisation of MersenneTwisterRNG and CellularAutomatonRNG. + +* Made XORShiftRNG and CMWC4096RNG thread-safe. + +* Added source JAR to distribution. + + +Changes in version 1.2.1 +------------------------ + +* Converted internal state of XORShiftRNG from an array to five separate + fields. This results in improved performance. Optimisation suggested by Jos + Hirth. + +* Optimised the Probability class so that it doesn't make an RNG call when the + probability is 1. + +* Added swapSubstring method to enable efficient exchange of data between two + BitStrings. + + +Changes in verison 1.2 +---------------------- + +* Added two new fast PRNGs (CMWC and XOR). CMWC provides a very long period + and the XOR RNG is twice as fast as the Mersenne Twister. See + http://school.anhb.uwa.edu.au/personalpages/kwessen/shared/Marsaglia03.html + for descriptions. + +* Fixed RandomDotOrgSeedGenerator so that it can generate seeds > 1kb in size. + +* Changed RandomDotOrgSeedGenerator so that it uses HTTPS for communicating + with the website (ISSUE#9). + +* Introduced the Probability class, a new numeric type. This immutatble value + type encapsulates a probability value between zero and one. The class + enforces the 0..1 bounds and provides convenient methods for working with + probabilities. + +* Added Rational type to enable arithmetic on rational numbers without loss of + precision. + +* Added restrictRange methods to Maths class. These adjust a given value so + that it falls between specified minimum and maximum values. + +* Made caching of BigInteger factorials thread-safe. + + +Changes in version 1.1 +---------------------- + +* PermutationGenerator and CombinationGenerator now implement + java.lang.Iterable (ISSUE#1). + +* Moved PermutationGenerator and CombinationGenerator into new combinatorics + package. + +* Moved NumberGenerator and its implementations into new number package. + +* Added demo program for different probability distributions (ISSUE#2). + +* Added method to the DataSet class for calculating the harmonic mean. + +* RNGs no longer log any information to stdout (ISSUE#7) by default. + The DefaultSeedGenerator class will log the seeds that it generates + only if the org.uncommons.maths.random.debug System property is set to true. + +* Relaxed restriction on the maximum size of the element set used by + CombinationGenerator. The maximum was 20 but bigger sets are now permitted + as long as the total number of combinations is no more than 2^63 (ISSUE#8). + +* Added a cache for BigInteger factorial values. This avoids expensive + recalculation in code that repeatedly computes factorials. + + +Changes in version 1.0.2 +------------------------ + +* Added generator for exponential distribution. + +* Fixed possibility of out-of-range values in continuous uniform generator. + +* Modified AESCounterRNG to allow seed size (AES key size) to be specified. + Defaults to 128-bits. 192-bits and 256-bits are supported but they require + the unlimited strength cryptography policy files (available from Sun) to be + installed on the local machine. + + +Changes in version 1.0.1 +------------------------ + +* Added minimum, maximum and median methods to DataSet class. + +* Fixed logic around zero-length combinations and permutations. + +* Improved unit test coverage. diff --git a/maths/LICENCE.txt b/maths/LICENCE.txt new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/maths/LICENCE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/maths/NOTICE.txt b/maths/NOTICE.txt new file mode 100644 index 00000000..b8b8cbe6 --- /dev/null +++ b/maths/NOTICE.txt @@ -0,0 +1,19 @@ +_______________________________________________________________________________ + + Uncommons Maths (https://uncommons-maths.dev.java.net) + Copyright 2006-2012 Daniel W. Dyer (http://www.dandyer.co.uk) +_______________________________________________________________________________ + +Acknowledgements: +----------------- +This software includes a Java port of the cellular automaton pseudorandom +number generator developed by Tony Pasqualoni +(http://home.southernct.edu/~pasqualonia1/ca/report.html). + +This software includes a Java port of the Mersenne Twister pseudorandom +number generator developed by Makoto Matsumoto and Takuji Nishimura +(http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html). + +This software also includes modified versions of the PermutationGenerator and +CombinationGenerator Java classes written by Michael Gilleland +(http://www.mgilleland.com/). diff --git a/maths/README.android b/maths/README.android new file mode 100644 index 00000000..e8023b17 --- /dev/null +++ b/maths/README.android @@ -0,0 +1,7 @@ +URL: http://maths.uncommons.org/ +Version: 1.2.3 +License: Apache 2.O +Description: "Random number generators, probability distributions, combinatorics and statistics for Java." + +This code was taken from 3da7cfe177f431f7bc113ac9793e3a8c2db5f279 +(git://github.com/dwdyer/uncommons-maths) diff --git a/maths/build.xml b/maths/build.xml new file mode 100644 index 00000000..c666ff84 --- /dev/null +++ b/maths/build.xml @@ -0,0 +1,292 @@ +<!-- ========================================================================= + Copyright 2006-2012 Daniel W. Dyer + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +========================================================================== --> +<project name="uncommons-maths" + default="dist" + basedir="." + xmlns:uncommons="antlib:org.uncommons.antlib"> + <description>Ant build file for Uncommons Maths library.</description> + +<!-- ================================================================== + GLOBAL BUILD PROPERTIES +=================================================================== --> + + <!-- Project-global locations. --> + <property name="conf.dir" value="etc" /> + <property name="lib.dir" value="lib" /> + <property name="lib.compiletime" value="${lib.dir}/compiletime" /> + <property name="lib.runtime" value="${lib.dir}/runtime" /> + <property name="dist.dir" value="./dist" /> + <property name="docs.dir" value="./docs" /> + <property name="coverage.dir" value="${docs.dir}/coverage" /> + <property name="test-results.dir" value="${docs.dir}/test-results" /> + <property name="release.dir" value="release" /> + <property name="web.dir" value="website" /> + <property name="temp.dir" value="temp" /> + + <!-- Classpath for compilation and tests. --> + <path id="base.path"> + <fileset dir="${lib.dir}"> + <include name="**/*.jar" /> + </fileset> + </path> + + <property name="version" value="1.2.3"/> + <property name="artifact.identifier" value="uncommons-maths-${version}"/> + + <!-- This is the minimum coverage percentage (for both lines and + branches) that will be tolerated. This is used to prevent + regressions in coverage. The threshold will be raised as + test coverage improves. --> + <property name="minimum.coverage" value="95" /> + + <taskdef uri="antlib:org.uncommons.antlib" + resource="org/uncommons/antlib/antlib.xml" + classpathref="base.path"/> + + + <!-- ================================================================== + MACROS + =================================================================== --> + + <macrodef name="diehard"> + <attribute name="rng.class"/> + <attribute name="results.file"/> + <sequential> + <mkdir dir="${temp.dir}" /> + <delete file="${temp.dir}/random.dat" /> + <java classname="org.uncommons.maths.random.DiehardInputGenerator" classpath="core/build/classes/main"> + <arg value="@{rng.class}"/> + <arg value="${temp.dir}/random.dat"/> + </java> + <exec dir="${lib.compiletime}/diehard" + executable="${lib.compiletime}/diehard/diehard" + output="@{results.file}" + input="${lib.compiletime}/diehard/input.txt"/> + </sequential> + </macrodef> + + +<!-- ================================================================== + TARGETS FOR BUILDING THE SOFTWARE +=================================================================== --> + + <!-- Builds everything from scratch. --> + <target name="all" + depends="clean, dist, test, docs" + description="Builds everything, excluding docs, from scratch."/> + + + <!-- Deletes all directories and files created by the build process. --> + <target name="clean" + description="Remove all files created by the build process." > + <uncommons:clean module="core" /> + <uncommons:clean module="demo" /> + <delete dir="${docs.dir}" /> + <delete dir="${dist.dir}" /> + <delete dir="${release.dir}" /> + <delete dir="${temp.dir}" /> + <delete file="velocity.log" /> + </target> + + + <target name="core" + description="Builds Uncommons Maths module."> + <uncommons:compile module="core" /> + <uncommons:jar module="core" jarfile="${artifact.identifier}-temp.jar"/> + <typedef resource="aQute/bnd/ant/taskdef.properties" classpathref="base.path" /> + <bnd classpath="core/${build.dir}/${artifact.identifier}-temp.jar" + eclipse="false" + failok="false" + exceptions="true" + files="uncommons-maths.bnd" + output="core/${build.dir}/${artifact.identifier}.jar"/> + <!-- Only need one JAR, so delete the non-OSGi version. --> + <delete file="core/${build.dir}/${artifact.identifier}-temp.jar" /> + </target> + + + <target name="demo" + depends="core" + description="Builds the demo module."> + <uncommons:compile module="demo" /> + <uncommons:jar module="demo" + jarfile="uncommons-maths-demo-${version}.jar" + classpath="${artifact.identifier}.jar jfreechart-1.0.8.jar jcommon-1.0.12.jar" + mainclass="org.uncommons.maths.demo.RandomDemo" /> + </target> + + + <!-- Copy all necessary files to distribution directory. --> + <target name="dist" + depends="core, demo" + description="Generate the project distribution." > + <uncommons:dist /> + + <mkdir dir="${dist.dir}/src" /> + <copy todir="${dist.dir}/src" flatten="true"> + <fileset dir="." includes="**/${build.dir}/*-src.jar"/> + </copy> + </target> + + + <!-- Build source JAR files for inclusion in the release. --> + <target name="source" description="Build source JARs."> + <uncommons:source module="core" jarfile="${artifact.identifier}-src.jar" /> + </target> + + + <!-- Create the release artifacts. --> + <target name="release" + depends="clean, source, dist, test, docs" + description="Creates the release archives."> + <uncommons:release name="${artifact.identifier}" /> + </target> + + + <target name="release-maven" + depends="clean, dist" + description="Deploys the core Maths module to the Java.net Maven repository."> + <uncommons:maven-deploy module="core" + version="${version}" + username="${maven.user}" + password="${maven.password}"/> + </target> + +<!-- ================================================================== + TARGETS FOR GENERATING TEST REPORTS & DOCUMENTATION + =================================================================== --> + + <!-- Runs unit tests for all modules. --> + <target name="test" + depends="dist" + description="Run the unit test suite."> + <mkdir dir="${temp.dir}" /> + + <!-- Bytecode instrumentation to enable collection of test coverage data. --> + <taskdef resource="tasks.properties" classpathref="base.path" /> + <cobertura-instrument todir="${temp.dir}" + datafile="${temp.dir}/cobertura.ser"> + <fileset dir="${dist.dir}" includes="${artifact.identifier}.jar"/> + </cobertura-instrument> + + <!-- Run the unit tests on the instrumented classes. --> + <taskdef resource="testngtasks" classpathref="base.path"/> + <path id="test.path"> + <dirset dir="."> + <include name="core/${classes.dir}/test" /> + </dirset> + <fileset dir="${temp.dir}" includes="*.jar"/> + <path refid="base.path" /> + </path> + <mkdir dir="${test-results.dir}" /> + <testng classpathref="test.path" + outputdir="${test-results.dir}" + haltonfailure="false" + useDefaultListeners="false" + listeners="org.uncommons.reportng.HTMLReporter, + org.uncommons.reportng.JUnitXMLReporter"> + <xmlfileset dir="${conf.dir}" includes="testng.xml"/> + <sysproperty key="org.uncommons.maths.random.debug" + value="true" /> + <sysproperty key="net.sourceforge.cobertura.datafile" + file="${temp.dir}/cobertura.ser" /> + <sysproperty key="org.uncommons.reportng.title" + value="Uncommons Maths Unit Test Report" /> + <sysproperty key="org.uncommons.reportng.coverage-report" + value="../../coverage/index.html" /> + </testng> + + <!-- Generate the HTML coverage report. --> + <mkdir dir="${coverage.dir}" /> + <cobertura-report format="html" + destdir="${coverage.dir}" + datafile="${temp.dir}/cobertura.ser"> + <fileset dir="core/${java.dir}/main"> + <include name="**/*.java" /> + </fileset> + </cobertura-report> + <!-- Generate an XML report for Hudson. --> + <cobertura-report format="xml" + destdir="${coverage.dir}" + datafile="${temp.dir}/cobertura.ser"> + <fileset dir="core/${java.dir}/main"> + <include name="**/*.java" /> + </fileset> + </cobertura-report> + + <!-- If the coverage is poor, fail. --> + <cobertura-check totallinerate="${minimum.coverage}" + totalbranchrate="${minimum.coverage}" + datafile="${temp.dir}/cobertura.ser"/> + + <!-- Clean up afterwards. --> + <delete dir="${temp.dir}" /> + <delete file="velocity.log" /> + </target> + + + <target name="diehard" + description="Run DIEHARD test suite against each RNG." + depends="core"> + <delete dir="${docs.dir}/diehard" /> + <mkdir dir="${docs.dir}/diehard" /> + <diehard rng.class="org.uncommons.maths.random.AESCounterRNG" + results.file="${docs.dir}/diehard/aes.txt" /> + <diehard rng.class="org.uncommons.maths.random.CellularAutomatonRNG" + results.file="${docs.dir}/diehard/automaton.txt" /> + <diehard rng.class="org.uncommons.maths.random.CMWC4096RNG" + results.file="${docs.dir}/diehard/cmwc.txt" /> + <diehard rng.class="org.uncommons.maths.random.MersenneTwisterRNG" + results.file="${docs.dir}/diehard/mersenne.txt" /> + <diehard rng.class="org.uncommons.maths.random.XORShiftRNG" + results.file="${docs.dir}/diehard/xor.txt" /> + <!-- Test java.util.Random for comparison. --> + <diehard rng.class="org.uncommons.maths.random.JavaRNG" + results.file="${docs.dir}/diehard/java.txt" /> + </target> + + + <!-- Generates API documentation for all modules. --> + <target name="docs" + description="Generates Javadoc API documentation."> + <uncommons:javadoc title="Uncommons Maths API" + version="${version}" + excludes="demo/**/*.java" /> + </target> + + +<!-- ================================================================== + TARGETS FOR UPDATING THE PROJECT WEBSITE + =================================================================== --> + + <!-- Refresh the API documentation tree for the project website. --> + <target name="website-docs" + description="Re-builds the website Javadocs." + depends="dist"> + <!-- Delete all existing HTML files and then regenerate the docs over the top. --> + <delete> + <fileset dir="${web.dir}/api"> + <include name="**/*.html" /> + </fileset> + </delete> + <uncommons:javadoc dir="${web.dir}/api" + title="Uncommons Maths API" + version="${version}" + excludes="demo/**/*.java" /> + <copy todir="${web.dir}" file="./CHANGELOG.txt" /> + </target> + +</project> diff --git a/maths/core/pom.xml b/maths/core/pom.xml new file mode 100644 index 00000000..fda3e739 --- /dev/null +++ b/maths/core/pom.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + +<modelVersion>4.0.0</modelVersion> + <name>Uncommons Maths</name> + <groupId>org.uncommons.maths</groupId> + <artifactId>uncommons-maths</artifactId> + <version>@VERSION@</version> + <packaging>jar</packaging> + <url>http://maths.uncommons.org/</url> + <description>Random number generators, probability distributions, combinatorics and statistics for Java.</description> + + <scm> + <connection>scm:git:git://github.com/dwdyer/uncommons-maths.git</connection> + <developerConnection>scm:git:git@github.com:dwdyer/uncommons-maths.git</developerConnection> + <url>http://github.com/dwdyer/uncommons-maths</url> + </scm> + + <developers> + <developer> + <id>dwdyer</id> + <name>Dan Dyer</name> + </developer> + </developers> + + <licenses> + <license> + <name>Apache License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.html</url> + <distribution>repo</distribution> + </license> + </licenses> + +</project> diff --git a/maths/core/src/java/main/org/uncommons/maths/Maths.java b/maths/core/src/java/main/org/uncommons/maths/Maths.java new file mode 100644 index 00000000..9b2ca0e3 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/Maths.java @@ -0,0 +1,230 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths; + +import java.math.BigInteger; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Maths operations not provided by {@link Math java.lang.Math}. + * @author Daniel Dyer + */ +public final class Maths +{ + // The biggest factorial that can be calculated using 64-bit signed longs. + private static final int MAX_LONG_FACTORIAL = 20; + + // Cache BigInteger factorial values because they are expensive to generate. + private static final int CACHE_SIZE = 256; + private static final ConcurrentMap<Integer, BigInteger> BIG_FACTORIALS + = new ConcurrentHashMap<Integer, BigInteger>(); + + private Maths() + { + // Prevent instantiation. + } + + + /** + * Calculates the factorial of n where n is a number in the + * range 0 - 20. Zero factorial is equal to 1. For values of + * n greater than 20 you must use {@link #bigFactorial(int)}. + * @param n The factorial to calculate. + * @return The factorial of n. + * @see #bigFactorial(int) + */ + public static long factorial(int n) + { + if (n < 0 || n > MAX_LONG_FACTORIAL) + { + throw new IllegalArgumentException("Argument must be in the range 0 - 20."); + } + long factorial = 1; + for (int i = n; i > 1; i--) + { + factorial *= i; + } + return factorial; + } + + + /** + * Calculates the factorial of n where n is a positive integer. + * Zero factorial is equal to 1. For values of n up to 20, consider + * using {@link #factorial(int)} instead since it uses a faster + * implementation. + * @param n The factorial to calculate. + * @return The factorial of n. + * @see #factorial(int) + */ + public static BigInteger bigFactorial(int n) + { + if (n < 0) + { + throw new IllegalArgumentException("Argument must greater than or equal to zero."); + } + + BigInteger factorial = null; + if (n < CACHE_SIZE) // Check for a cached value. + { + factorial = BIG_FACTORIALS.get(n); + } + + if (factorial == null) + { + factorial = BigInteger.ONE; + for (int i = n; i > 1; i--) + { + factorial = factorial.multiply(BigInteger.valueOf(i)); + } + if (n < CACHE_SIZE) // Cache value. + { + BIG_FACTORIALS.putIfAbsent(n, factorial); + } + } + + return factorial; + } + + + /** + * Calculate the first argument raised to the power of the second. + * This method only supports non-negative powers. + * @param value The number to be raised. + * @param power The exponent (must be positive). + * @return {@code value} raised to {@code power}. + */ + public static long raiseToPower(int value, int power) + { + if (power < 0) + { + throw new IllegalArgumentException("This method does not support negative powers."); + } + long result = 1; + for (int i = 0; i < power; i++) + { + result *= value; + } + return result; + } + + + /** + * Calculate logarithms for arbitrary bases. + * @param base The base for the logarithm. + * @param arg The value to calculate the logarithm for. + * @return The log of {@code arg} in the specified {@code base}. + */ + public static double log(double base, double arg) + { + // Use natural logarithms and change the base. + return Math.log(arg) / Math.log(base); + } + + + /** + * Checks that two values are approximately equal (plus or minus a specified tolerance). + * @param value1 The first value to compare. + * @param value2 The second value to compare. + * @param tolerance How much (in percentage terms, as a percentage of the first value) + * the values are allowed to differ and still be considered equal. Expressed as a value + * between 0 and 1. + * @return true if the values are approximately equal, false otherwise. + */ + public static boolean approxEquals(double value1, + double value2, + double tolerance) + { + if (tolerance < 0 || tolerance > 1) + { + throw new IllegalArgumentException("Tolerance must be between 0 and 1."); + } + return Math.abs(value1 - value2) <= value1 * tolerance; + } + + + /** + * If the specified value is not greater than or equal to the specified minimum and + * less than or equal to the specified maximum, adjust it so that it is. + * @param value The value to check. + * @param min The minimum permitted value. + * @param max The maximum permitted value. + * @return {@code value} if it is between the specified limits, {@code min} if the value + * is too low, or {@code max} if the value is too high. + * @since 1.2 + */ + public static int restrictRange(int value, int min, int max) + { + return Math.min((Math.max(value, min)), max); + } + + + /** + * If the specified value is not greater than or equal to the specified minimum and + * less than or equal to the specified maximum, adjust it so that it is. + * @param value The value to check. + * @param min The minimum permitted value. + * @param max The maximum permitted value. + * @return {@code value} if it is between the specified limits, {@code min} if the value + * is too low, or {@code max} if the value is too high. + * @since 1.2 + */ + public static long restrictRange(long value, long min, long max) + { + return Math.min((Math.max(value, min)), max); + } + + + /** + * If the specified value is not greater than or equal to the specified minimum and + * less than or equal to the specified maximum, adjust it so that it is. + * @param value The value to check. + * @param min The minimum permitted value. + * @param max The maximum permitted value. + * @return {@code value} if it is between the specified limits, {@code min} if the value + * is too low, or {@code max} if the value is too high. + * @since 1.2 + */ + public static double restrictRange(double value, double min, double max) + { + return Math.min((Math.max(value, min)), max); + } + + + /** + * Determines the greatest common divisor of a pair of natural numbers + * using the Euclidean algorithm. This method only works with natural + * numbers. If negative integers are passed in, the absolute values will + * be used. The return value is always positive. + * @param a The first value. + * @param b The second value. + * @return The greatest common divisor. + * @since 1.2 + */ + public static long greatestCommonDivisor(long a, long b) + { + a = Math.abs(a); + b = Math.abs(b); + while (b != 0) + { + long temp = b; + b = a % b; + a = temp; + } + return a; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/binary/BinaryUtils.java b/maths/core/src/java/main/org/uncommons/maths/binary/BinaryUtils.java new file mode 100644 index 00000000..6991e1fd --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/binary/BinaryUtils.java @@ -0,0 +1,168 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.binary; + +/** + * Utility methods for working with binary and hex data. + * @author Daniel Dyer + */ +public final class BinaryUtils +{ + // Mask for casting a byte to an int, bit-by-bit (with + // bitwise AND) with no special consideration for the sign bit. + private static final int BITWISE_BYTE_TO_INT = 0x000000FF; + + private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private BinaryUtils() + { + // Prevents instantiation of utility class. + } + + + /** + * Converts an array of bytes in to a String of hexadecimal characters (0 - F). + * @param data An array of bytes to convert to a String. + * @return A hexadecimal String representation of the data. + */ + public static String convertBytesToHexString(byte[] data) + { + StringBuilder buffer = new StringBuilder(data.length * 2); + for (byte b : data) + { + buffer.append(HEX_CHARS[(b >>> 4) & 0x0F]); + buffer.append(HEX_CHARS[b & 0x0F]); + } + return buffer.toString(); + } + + + /** + * Converts a hexadecimal String (such as one generated by the + * {@link #convertBytesToHexString(byte[])} method) into an array of bytes. + * @param hex The hexadecimal String to be converted into an array of bytes. + * @return An array of bytes that. + */ + public static byte[] convertHexStringToBytes(String hex) + { + if (hex.length() % 2 != 0) + { + throw new IllegalArgumentException("Hex string must have even number of characters."); + } + byte[] seed = new byte[hex.length() / 2]; + for (int i = 0; i < seed.length; i++) + { + int index = i * 2; + seed[i] = (byte) Integer.parseInt(hex.substring(index, index + 2), 16); + } + return seed; + } + + + + /** + * Take four bytes from the specified position in the specified + * block and convert them into a 32-bit int, using the big-endian + * convention. + * @param bytes The data to read from. + * @param offset The position to start reading the 4-byte int from. + * @return The 32-bit integer represented by the four bytes. + */ + public static int convertBytesToInt(byte[] bytes, int offset) + { + return (BITWISE_BYTE_TO_INT & bytes[offset + 3]) + | ((BITWISE_BYTE_TO_INT & bytes[offset + 2]) << 8) + | ((BITWISE_BYTE_TO_INT & bytes[offset + 1]) << 16) + | ((BITWISE_BYTE_TO_INT & bytes[offset]) << 24); + } + + + /** + * Convert an array of bytes into an array of ints. 4 bytes from the + * input data map to a single int in the output data. + * @param bytes The data to read from. + * @return An array of 32-bit integers constructed from the data. + * @since 1.1 + */ + public static int[] convertBytesToInts(byte[] bytes) + { + if (bytes.length % 4 != 0) + { + throw new IllegalArgumentException("Number of input bytes must be a multiple of 4."); + } + int[] ints = new int[bytes.length / 4]; + for (int i = 0; i < ints.length; i++) + { + ints[i] = convertBytesToInt(bytes, i * 4); + } + return ints; + } + + + /** + * Utility method to convert an array of bytes into a long. Byte ordered is + * assumed to be big-endian. + * @param bytes The data to read from. + * @param offset The position to start reading the 8-byte long from. + * @return The 64-bit integer represented by the eight bytes. + * @since 1.1 + */ + public static long convertBytesToLong(byte[] bytes, int offset) + { + long value = 0; + for (int i = offset; i < offset + 8; i++) + { + byte b = bytes[i]; + value <<= 8; + value |= b; + } + return value; + + } + + + /** + * Converts a floating point value in the range 0 - 1 into a fixed + * point bit string (where the most significant bit has a value of 0.5). + * @param value The value to convert (must be between zero and one). + * @return A bit string representing the value in fixed-point format. + */ + public static BitString convertDoubleToFixedPointBits(double value) + { + if (value < 0.0d || value >= 1.0d) + { + throw new IllegalArgumentException("Value must be between 0 and 1."); + } + StringBuilder bits = new StringBuilder(64); + double bitValue = 0.5d; + double d = value; + while (d > 0) + { + if (d >= bitValue) + { + bits.append('1'); + d -= bitValue; + } + else + { + bits.append('0'); + } + bitValue /= 2; + } + return new BitString(bits.toString()); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/binary/BitString.java b/maths/core/src/java/main/org/uncommons/maths/binary/BitString.java new file mode 100644 index 00000000..db92d38f --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/binary/BitString.java @@ -0,0 +1,354 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.binary; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Random; + +/** + * <p>Implementation of a fixed-length bit-string. Internally, bits are packed into an + * array of ints. This implementation makes more efficient use of space than the + * alternative approach of using an array of booleans.</p> + * <p>This class is preferable to {@link java.util.BitSet} if a fixed number of bits is + * required.</p> + * @author Daniel Dyer + */ +public final class BitString implements Cloneable, Serializable +{ + private static final int WORD_LENGTH = 32; + + private final int length; + + /** + * Store the bits packed in an array of 32-bit ints. This field cannot + * be declared final because it must be cloneable. + */ + private int[] data; + + + /** + * Creates a bit string of the specified length with all bits + * initially set to zero (off). + * @param length The number of bits. + */ + public BitString(int length) + { + if (length < 0) + { + throw new IllegalArgumentException("Length must be non-negative."); + } + this.length = length; + this.data = new int[(length + WORD_LENGTH - 1) / WORD_LENGTH]; + } + + + /** + * Creates a bit string of the specified length with each bit set + * randomly (the distribution of bits is uniform so long as the output + * from the provided RNG is also uniform). Using this constructor is + * more efficient than creating a bit string and then randomly setting + * each bit individually. + * @param length The number of bits. + * @param rng A source of randomness. + */ + public BitString(int length, Random rng) + { + this(length); + // We can set bits 32 at a time rather than calling rng.nextBoolean() + // and setting each one individually. + for (int i = 0; i < data.length; i++) + { + data[i] = rng.nextInt(); + } + // If the last word is not fully utilised, zero any out-of-bounds bits. + // This is necessary because the countSetBits() methods will count + // out-of-bounds bits. + int bitsUsed = length % WORD_LENGTH; + if (bitsUsed < WORD_LENGTH) + { + int unusedBits = WORD_LENGTH - bitsUsed; + int mask = 0xFFFFFFFF >>> unusedBits; + data[data.length - 1] &= mask; + } + } + + + /** + * Initialises the bit string from a character string of 1s and 0s + * in big-endian order. + * @param value A character string of ones and zeros. + */ + public BitString(String value) + { + this(value.length()); + for (int i = 0; i < value.length(); i++) + { + if (value.charAt(i) == '1') + { + setBit(value.length() - (i + 1), true); + } + else if (value.charAt(i) != '0') + { + throw new IllegalArgumentException("Illegal character at position " + i); + } + } + } + + + /** + * @return The length of this bit string. + */ + public int getLength() + { + return length; + } + + + /** + * Returns the bit at the specified index. + * @param index The index of the bit to look-up (0 is the least-significant bit). + * @return A boolean indicating whether the bit is set or not. + * @throws IndexOutOfBoundsException If the specified index is not a bit + * position in this bit string. + */ + public boolean getBit(int index) + { + assertValidIndex(index); + int word = index / WORD_LENGTH; + int offset = index % WORD_LENGTH; + return (data[word] & (1 << offset)) != 0; + } + + + /** + * Sets the bit at the specified index. + * @param index The index of the bit to set (0 is the least-significant bit). + * @param set A boolean indicating whether the bit should be set or not. + * @throws IndexOutOfBoundsException If the specified index is not a bit + * position in this bit string. + */ + public void setBit(int index, boolean set) + { + assertValidIndex(index); + int word = index / WORD_LENGTH; + int offset = index % WORD_LENGTH; + if (set) + { + data[word] |= (1 << offset); + } + else // Unset the bit. + { + data[word] &= ~(1 << offset); + } + } + + + /** + * Inverts the value of the bit at the specified index. + * @param index The bit to flip (0 is the least-significant bit). + * @throws IndexOutOfBoundsException If the specified index is not a bit + * position in this bit string. + */ + public void flipBit(int index) + { + assertValidIndex(index); + int word = index / WORD_LENGTH; + int offset = index % WORD_LENGTH; + data[word] ^= (1 << offset); + } + + + /** + * Helper method to check whether a bit index is valid or not. + * @param index The index to check. + * @throws IndexOutOfBoundsException If the index is not valid. + */ + private void assertValidIndex(int index) + { + if (index >= length || index < 0) + { + throw new IndexOutOfBoundsException("Invalid index: " + index + " (length: " + length + ")"); + } + } + + + /** + * @return The number of bits that are 1s rather than 0s. + */ + public int countSetBits() + { + int count = 0; + for (int x : data) + { + while (x != 0) + { + x &= (x - 1); // Unsets the least significant on bit. + ++count; // Count how many times we have to unset a bit before x equals zero. + } + } + return count; + } + + + /** + * @return The number of bits that are 0s rather than 1s. + */ + public int countUnsetBits() + { + return length - countSetBits(); + } + + + /** + * Interprets this bit string as being a binary numeric value and returns + * the integer that it represents. + * @return A {@link BigInteger} that contains the numeric value represented + * by this bit string. + */ + public BigInteger toNumber() + { + return (new BigInteger(toString(), 2)); + } + + + /** + * An efficient method for exchanging data between two bit strings. Both bit strings must + * be long enough that they contain the full length of the specified substring. + * @param other The bitstring with which this bitstring should swap bits. + * @param start The start position for the substrings to be exchanged. All bit + * indices are big-endian, which means position 0 is the rightmost bit. + * @param length The number of contiguous bits to swap. + */ + public void swapSubstring(BitString other, int start, int length) + { + assertValidIndex(start); + other.assertValidIndex(start); + + int word = start / WORD_LENGTH; + + int partialWordSize = (WORD_LENGTH - start) % WORD_LENGTH; + if (partialWordSize > 0) + { + swapBits(other, word, 0xFFFFFFFF << (WORD_LENGTH - partialWordSize)); + ++word; + } + + int remainingBits = length - partialWordSize; + int stop = remainingBits / WORD_LENGTH; + for (int i = word; i < stop; i++) + { + int temp = data[i]; + data[i] = other.data[i]; + other.data[i] = temp; + } + + remainingBits %= WORD_LENGTH; + if (remainingBits > 0) + { + swapBits(other, word, 0xFFFFFFFF >>> (WORD_LENGTH - remainingBits)); + } + } + + + /** + * @param other The BitString to exchange bits with. + * @param word The word index of the word that will be swapped between the two bit strings. + * @param swapMask A mask that specifies which bits in the word will be swapped. + */ + private void swapBits(BitString other, int word, int swapMask) + { + int preserveMask = ~swapMask; + int preservedThis = data[word] & preserveMask; + int preservedThat = other.data[word] & preserveMask; + int swapThis = data[word] & swapMask; + int swapThat = other.data[word] & swapMask; + data[word] = preservedThis | swapThat; + other.data[word] = preservedThat | swapThis; + } + + + /** + * Creates a textual representation of this bit string in big-endian + * order (index 0 is the right-most bit). + * @return This bit string rendered as a String of 1s and 0s. + */ + @Override + public String toString() + { + StringBuilder buffer = new StringBuilder(); + for (int i = length - 1; i >= 0; i--) + { + buffer.append(getBit(i) ? '1' : '0'); + } + return buffer.toString(); + } + + + /** + * @return An identical copy of this bit string. + */ + @Override + public BitString clone() + { + try + { + BitString clone = (BitString) super.clone(); + clone.data = data.clone(); + return clone; + } + catch (CloneNotSupportedException ex) + { + // Not possible. + throw (Error) new InternalError("Cloning failed.").initCause(ex); + } + } + + + /** + * @return True if the argument is a BitString instance and both bit + * strings are the same length with identical bits set/unset. + */ + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + BitString bitString = (BitString) o; + + return length == bitString.length && Arrays.equals(data, bitString.data); + } + + + /** + * Over-ridden to be consistent with {@link #equals(Object)}. + */ + @Override + public int hashCode() + { + int result = length; + result = 31 * result + Arrays.hashCode(data); + return result; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/binary/package-info.java b/maths/core/src/java/main/org/uncommons/maths/binary/package-info.java new file mode 100644 index 00000000..d16dd024 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/binary/package-info.java @@ -0,0 +1,20 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +/** + * Classes for manipulating binary data. + * @author Daniel Dyer + */ +package org.uncommons.maths.binary; diff --git a/maths/core/src/java/main/org/uncommons/maths/combinatorics/CombinationGenerator.java b/maths/core/src/java/main/org/uncommons/maths/combinatorics/CombinationGenerator.java new file mode 100644 index 00000000..dd4cea83 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/combinatorics/CombinationGenerator.java @@ -0,0 +1,288 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.combinatorics; + +import java.lang.reflect.Array; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import org.uncommons.maths.Maths; + +/** + * Combination generator for generating all combinations of a given size from + * the specified set of elements. For performance reasons, this implementation + * is restricted to operating with set sizes and combination lengths that produce + * no more than 2^63 different combinations. + * @param <T> The type of element that the combinations are made from. + * @author Daniel Dyer (modified from the original version written by Michael + * Gilleland of Merriam Park Software - + * <a href="http://www.merriampark.com/perm.htm">http://www.merriampark.com/comb.htm</a>). + * @see PermutationGenerator + */ +public class CombinationGenerator<T> implements Iterable<List<T>> +{ + private final T[] elements; + private final int[] combinationIndices; + private long remainingCombinations; + private long totalCombinations; + + /** + * Create a combination generator that generates all combinations of + * a specified length from the given set. + * @param elements The set from which to generate combinations. + * @param combinationLength The length of the combinations to be generated. + */ + public CombinationGenerator(T[] elements, + int combinationLength) + { + if (combinationLength > elements.length) + { + throw new IllegalArgumentException("Combination length cannot be greater than set size."); + } + + this.elements = elements.clone(); + this.combinationIndices = new int[combinationLength]; + + BigInteger sizeFactorial = Maths.bigFactorial(elements.length); + BigInteger lengthFactorial = Maths.bigFactorial(combinationLength); + BigInteger differenceFactorial = Maths.bigFactorial(elements.length - combinationLength); + BigInteger total = sizeFactorial.divide(differenceFactorial.multiply(lengthFactorial)); + + if (total.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) + { + throw new IllegalArgumentException("Total number of combinations must not be more than 2^63."); + } + + totalCombinations = total.longValue(); + reset(); + } + + + /** + * Create a combination generator that generates all combinations of + * a specified length from the given set. + * @param elements The set from which to generate combinations. + * @param combinationLength The length of the combinations to be generated. + */ + @SuppressWarnings("unchecked") + public CombinationGenerator(Collection<T> elements, + int combinationLength) + { + this(elements.toArray((T[]) new Object[elements.size()]), + combinationLength); + } + + + /** + * Reset the combination generator. + */ + public final void reset() + { + for (int i = 0; i < combinationIndices.length; i++) + { + combinationIndices[i] = i; + } + remainingCombinations = totalCombinations; + } + + + /** + * @return The number of combinations not yet generated. + */ + public long getRemainingCombinations() + { + return remainingCombinations; + } + + + /** + * Are there more combinations? + * @return true if there are more combinations available, false otherwise. + */ + public boolean hasMore() + { + return remainingCombinations > 0; + } + + + /** + * @return The total number of combinations. + */ + public long getTotalCombinations() + { + return totalCombinations; + } + + + /** + * Generate the next combination and return an array containing + * the appropriate elements. + * @see #nextCombinationAsArray(Object[]) + * @see #nextCombinationAsList() + * @return An array containing the elements that make up the next combination. + */ + @SuppressWarnings("unchecked") + public T[] nextCombinationAsArray() + { + T[] combination = (T[]) Array.newInstance(elements.getClass().getComponentType(), + combinationIndices.length); + return nextCombinationAsArray(combination); + } + + + /** + * Generate the next combination and return an array containing + * the appropriate elements. This overloaded method allows the caller + * to provide an array that will be used and returned. + * The purpose of this is to improve performance when iterating over + * combinations. If the {@link #nextCombinationAsArray()} method is + * used it will create a new array every time. When iterating over + * combinations this will result in lots of short-lived objects that + * have to be garbage collected. This method allows a single array + * instance to be reused in such circumstances. + * @param destination Provides an array to use to create the + * combination. The specified array must be the same length as a + * combination. + * @return The provided array now containing the elements of the combination. + */ + public T[] nextCombinationAsArray(T[] destination) + { + if (destination.length != combinationIndices.length) + { + throw new IllegalArgumentException("Destination array must be the same length as combinations."); + } + generateNextCombinationIndices(); + for (int i = 0; i < combinationIndices.length; i++) + { + destination[i] = elements[combinationIndices[i]]; + } + return destination; + } + + + /** + * Generate the next combination and return a list containing the + * appropriate elements. + * @see #nextCombinationAsList(List) + * @see #nextCombinationAsArray() + * @return A list containing the elements that make up the next combination. + */ + public List<T> nextCombinationAsList() + { + return nextCombinationAsList(new ArrayList<T>(elements.length)); + } + + + /** + * Generate the next combination and return a list containing + * the appropriate elements. This overloaded method allows the caller + * to provide a list that will be used and returned. + * The purpose of this is to improve performance when iterating over + * combinations. If the {@link #nextCombinationAsList()} method is + * used it will create a new list every time. When iterating over + * combinations this will result in lots of short-lived objects that + * have to be garbage collected. This method allows a single list + * instance to be reused in such circumstances. + * @param destination Provides a list to use to create the + * combination. + * @return The provided list now containing the elements of the combination. + */ + public List<T> nextCombinationAsList(List<T> destination) + { + generateNextCombinationIndices(); + // Generate actual combination. + destination.clear(); + for (int i : combinationIndices) + { + destination.add(elements[i]); + } + return destination; + } + + + + /** + * Generate the indices into the elements array for the next combination. The + * algorithm is from Kenneth H. Rosen, Discrete Mathematics and Its Applications, + * 2nd edition (NY: McGraw-Hill, 1991), p. 286. + */ + private void generateNextCombinationIndices() + { + if (remainingCombinations == 0) + { + throw new IllegalStateException("There are no combinations remaining. " + + "Generator must be reset to continue using."); + } + else if (remainingCombinations < totalCombinations) + { + int i = combinationIndices.length - 1; + while (combinationIndices[i] == elements.length - combinationIndices.length + i) + { + i--; + } + ++combinationIndices[i]; + for (int j = i + 1; j < combinationIndices.length; j++) + { + combinationIndices[j] = combinationIndices[i] + j - i; + } + } + --remainingCombinations; + } + + + /** + * <p>Provides a read-only iterator for iterating over the combinations + * generated by this object. This method is the implementation of the + * {@link Iterable} interface that permits instances of this class to be + * used with the new-style for loop.</p> + * <p>For example:</p> + * <pre> + * List<Integer> elements = Arrays.asList(1, 2, 3); + * CombinationGenerator<Integer> combinations = new CombinationGenerator(elements, 2); + * for (List<Integer> c : combinations) + * { + * // Do something with each combination. + * } + * </pre> + * @return An iterator. + * @since 1.1 + */ + public Iterator<List<T>> iterator() + { + return new Iterator<List<T>>() + { + public boolean hasNext() + { + return hasMore(); + } + + + public List<T> next() + { + return nextCombinationAsList(); + } + + + public void remove() + { + throw new UnsupportedOperationException("Iterator does not support removal."); + } + }; + } + +} diff --git a/maths/core/src/java/main/org/uncommons/maths/combinatorics/PermutationGenerator.java b/maths/core/src/java/main/org/uncommons/maths/combinatorics/PermutationGenerator.java new file mode 100644 index 00000000..d9769453 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/combinatorics/PermutationGenerator.java @@ -0,0 +1,304 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.combinatorics; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import org.uncommons.maths.Maths; + +/** + * Permutation generator for generating all permutations for all sets up to + * 20 elements in size. While 20 may seem a low limit, bear in mind that + * the number of permutations of a set of size n is n! For a set of 21 + * items, the number of permutations is bigger than can be stored in Java's + * 64-bit long integer data type. Therefore it seems unlikely that you + * could ever generate, let alone process, all of the permutations in a + * reasonable time frame. For this reason the implementation is optimised for + * sets of size 20 or less (this affords better performance by allowing primitive + * numeric types to be used for calculations rather than + * {@link java.math.BigInteger}). + * @param <T> The type of element that the permutation will consist of. + * @author Daniel Dyer (modified from the original version written by Michael + * Gilleland of Merriam Park Software - + * <a href="http://www.merriampark.com/perm.htm">http://www.merriampark.com/perm.htm</a>). + * @see CombinationGenerator + */ +public class PermutationGenerator<T> implements Iterable<List<T>> +{ + private final T[] elements; + private final int[] permutationIndices; + private long remainingPermutations; + private long totalPermutations; + + + /** + * Permutation generator that generates all possible orderings of + * the elements in the specified set. + * @param elements The elements to permute. + */ + public PermutationGenerator(T[] elements) + { + if (elements.length > 20) + { + throw new IllegalArgumentException("Size must be less than or equal to 20."); + } + this.elements = elements.clone(); + permutationIndices = new int[elements.length]; + totalPermutations = Maths.factorial(elements.length); + reset(); + } + + + /** + * Permutation generator that generates all possible orderings of + * the elements in the specified set. + * @param elements The elements to permute. + */ + @SuppressWarnings("unchecked") + public PermutationGenerator(Collection<T> elements) + { + this(elements.toArray((T[]) new Object[elements.size()])); + } + + + /** + * Resets the generator state. + */ + public final void reset() + { + for (int i = 0; i < permutationIndices.length; i++) + { + permutationIndices[i] = i; + } + remainingPermutations = totalPermutations; + } + + + /** + * Returns the number of permutations not yet generated. + * @return The number of unique permutations still to be generated. + */ + public long getRemainingPermutations() + { + return remainingPermutations; + } + + + /** + * Returns the total number of unique permutations that can be + * generated for the given set of elements. + * @return The total number of permutations. + */ + public long getTotalPermutations() + { + return totalPermutations; + } + + + /** + * Are there more permutations that have not yet been returned? + * @return true if there are more permutations, false otherwise. + */ + public boolean hasMore() + { + return remainingPermutations > 0; + } + + + /** + * Generate the next permutation and return an array containing + * the elements in the appropriate order. + * @see #nextPermutationAsArray(Object[]) + * @see #nextPermutationAsList() + * @return The next permutation as an array. + */ + @SuppressWarnings("unchecked") + public T[] nextPermutationAsArray() + { + T[] permutation = (T[]) Array.newInstance(elements.getClass().getComponentType(), + permutationIndices.length); + return nextPermutationAsArray(permutation); + } + + + /** + * Generate the next permutation and return an array containing + * the elements in the appropriate order. This overloaded method + * allows the caller to provide an array that will be used and returned. + * The purpose of this is to improve performance when iterating over + * permutations. If the {@link #nextPermutationAsArray()} method is + * used it will create a new array every time. When iterating over + * permutations this will result in lots of short-lived objects that + * have to be garbage collected. This method allows a single array + * instance to be reused in such circumstances. + * @param destination Provides an array to use to create the + * permutation. The specified array must be the same length as a + * permutation. This is the array that will be returned, once + * it has been filled with the elements in the appropriate order. + * @return The next permutation as an array. + */ + public T[] nextPermutationAsArray(T[] destination) + { + if (destination.length != elements.length) + { + throw new IllegalArgumentException("Destination array must be the same length as permutations."); + } + generateNextPermutationIndices(); + // Generate actual permutation. + for (int i = 0; i < permutationIndices.length; i++) + { + destination[i] = elements[permutationIndices[i]]; + } + return destination; + } + + + /** + * Generate the next permutation and return a list containing + * the elements in the appropriate order. + * @see #nextPermutationAsList(java.util.List) + * @see #nextPermutationAsArray() + * @return The next permutation as a list. + */ + public List<T> nextPermutationAsList() + { + List<T> permutation = new ArrayList<T>(elements.length); + return nextPermutationAsList(permutation); + } + + + /** + * Generate the next permutation and return a list containing + * the elements in the appropriate order. This overloaded method + * allows the caller to provide a list that will be used and returned. + * The purpose of this is to improve performance when iterating over + * permutations. If the {@link #nextPermutationAsList()} method is + * used it will create a new list every time. When iterating over + * permutations this will result in lots of short-lived objects that + * have to be garbage collected. This method allows a single list + * instance to be reused in such circumstances. + * @param destination Provides a list to use to create the + * permutation. This is the list that will be returned, once + * it has been filled with the elements in the appropriate order. + * @return The next permutation as a list. + */ + public List<T> nextPermutationAsList(List<T> destination) + { + generateNextPermutationIndices(); + // Generate actual permutation. + destination.clear(); + for (int i : permutationIndices) + { + destination.add(elements[i]); + } + return destination; + } + + + /** + * Generate the indices into the elements array for the next permutation. The + * algorithm is from Kenneth H. Rosen, Discrete Mathematics and its Applications, + * 2nd edition (NY: McGraw-Hill, 1991), p. 284) + */ + private void generateNextPermutationIndices() + { + if (remainingPermutations == 0) + { + throw new IllegalStateException("There are no permutations remaining. " + + "Generator must be reset to continue using."); + } + else if (remainingPermutations < totalPermutations) + { + // Find largest index j with permutationIndices[j] < permutationIndices[j + 1] + int j = permutationIndices.length - 2; + while (permutationIndices[j] > permutationIndices[j + 1]) + { + j--; + } + + // Find index k such that permutationIndices[k] is smallest integer greater than + // permutationIndices[j] to the right of permutationIndices[j]. + int k = permutationIndices.length - 1; + while (permutationIndices[j] > permutationIndices[k]) + { + k--; + } + + // Interchange permutation indices. + int temp = permutationIndices[k]; + permutationIndices[k] = permutationIndices[j]; + permutationIndices[j] = temp; + + // Put tail end of permutation after jth position in increasing order. + int r = permutationIndices.length - 1; + int s = j + 1; + + while (r > s) + { + temp = permutationIndices[s]; + permutationIndices[s] = permutationIndices[r]; + permutationIndices[r] = temp; + r--; + s++; + } + } + --remainingPermutations; + } + + + /** + * <p>Provides a read-only iterator for iterating over the permutations + * generated by this object. This method is the implementation of the + * {@link Iterable} interface that permits instances of this class to be + * used with the new-style for loop.</p> + * <p>For example:</p> + * <pre> + * List<Integer> elements = Arrays.asList(1, 2, 3); + * PermutationGenerator<Integer> permutations = new PermutationGenerator(elements); + * for (List<Integer> p : permutations) + * { + * // Do something with each permutation. + * } + * </pre> + * @return An iterator. + * @since 1.1 + */ + public Iterator<List<T>> iterator() + { + return new Iterator<List<T>>() + { + public boolean hasNext() + { + return hasMore(); + } + + + public List<T> next() + { + return nextPermutationAsList(); + } + + + public void remove() + { + throw new UnsupportedOperationException("Iterator does not support removal."); + } + }; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/combinatorics/package-info.java b/maths/core/src/java/main/org/uncommons/maths/combinatorics/package-info.java new file mode 100644 index 00000000..526da3bf --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/combinatorics/package-info.java @@ -0,0 +1,23 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +/** + * Utility classes for combinatorics. Includes classes for generating permutations, + * combinations and cartesian products. + * @author Michael Gilleland (original implementations of PermutationGenerator and + * CombinationGenerator that the Uncommons versions are derived from). + * @author Daniel Dyer (Uncommons modifications to the original versions). + */ +package org.uncommons.maths.combinatorics; diff --git a/maths/core/src/java/main/org/uncommons/maths/number/AdjustableNumberGenerator.java b/maths/core/src/java/main/org/uncommons/maths/number/AdjustableNumberGenerator.java new file mode 100644 index 00000000..bb07019d --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/number/AdjustableNumberGenerator.java @@ -0,0 +1,81 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.number; + +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * Implementation of {@link NumberGenerator} that works similarly to + * {@link ConstantGenerator} but allows the returned + * value to be changed after instantiation. + * The most useful application of this type of number generator is to permit + * runtime re-configuration of objects that rely on {@link NumberGenerator}s + * for their parameters. This can be achieved by creating UI components (e.g. + * sliders and spinners) that invoke {@link #setValue(Number)} when their state + * changes. + * @param <T> The type of number generated by this number generator. + * @author Daniel Dyer + */ +public class AdjustableNumberGenerator<T extends Number> implements NumberGenerator<T> +{ + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private T value; + + /** + * @param value The initial value returned by all invocations of {@link #nextValue()} + * (until it is modified by a call to {@link #setValue(Number)}. + */ + public AdjustableNumberGenerator(T value) + { + this.value = value; + } + + + /** + * Change the value that is returned by this generator. + * @param value The new value to return. + */ + public void setValue(T value) + { + try + { + lock.writeLock().lock(); + this.value = value; + } + finally + { + lock.writeLock().unlock(); + } + } + + + /** + * {@inheritDoc} + */ + public T nextValue() + { + try + { + lock.readLock().lock(); + return value; + } + finally + { + lock.readLock().unlock(); + } + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/number/ConstantGenerator.java b/maths/core/src/java/main/org/uncommons/maths/number/ConstantGenerator.java new file mode 100644 index 00000000..121a56f3 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/number/ConstantGenerator.java @@ -0,0 +1,46 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.number; + +/** + * Convenience implementation of {@link NumberGenerator} that always + * returns the same value. + * @param <T> The numeric type (Integer, Long, Double, etc.) of the constant. + * @author Daniel Dyer + */ +public class ConstantGenerator<T extends Number> implements NumberGenerator<T> +{ + private final T constant; + + /** + * Creates a number generator that always returns the same + * values. + * @param constant The value to be returned by all invocations + * of the {@link #nextValue()} method. + */ + public ConstantGenerator(T constant) + { + this.constant = constant; + } + + /** + * @return The constant value specified when the generator was constructed. + */ + public T nextValue() + { + return constant; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/number/NumberGenerator.java b/maths/core/src/java/main/org/uncommons/maths/number/NumberGenerator.java new file mode 100644 index 00000000..44d6f9fe --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/number/NumberGenerator.java @@ -0,0 +1,34 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.number; + +/** + * Interface for providing different types of sequences of numbers. This is + * a simple but powerful abstraction that provides considerable flexibility + * in implementing classes that require numeric configuration. Refer to the + * implementations in this package for examples of how it can be used. + * @param <T> The type (Integer, Long, Double, etc.) of number to generate. + * @author Daniel Dyer + * @see ConstantGenerator + * @see AdjustableNumberGenerator + */ +public interface NumberGenerator<T extends Number> +{ + /** + * @return The next value from the generator. + */ + T nextValue(); +} diff --git a/maths/core/src/java/main/org/uncommons/maths/number/Rational.java b/maths/core/src/java/main/org/uncommons/maths/number/Rational.java new file mode 100644 index 00000000..faf4bc38 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/number/Rational.java @@ -0,0 +1,342 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.number; + +import java.math.BigDecimal; +import java.math.BigInteger; +import org.uncommons.maths.Maths; + +/** + * Immutable value object for representing a rational number (or vulgar fraction). + * Instances of this class provide a way to perform arithmetic on fractional values + * without loss of precision. + * This implementation automatically simplifies fractions (3/6 is stored as 1/2). + * The implementation also requires that the denominator is positive. The numerator + * may be negative. + * @author Daniel Dyer + * @since 1.2 + */ +public final class Rational extends Number implements Comparable<Rational> +{ + /** + * Convenient constant representing a value of zero (0/1 as a rational). + */ + public static final Rational ZERO = new Rational(0); + + /** + * Convenient constant representing a value of a quarter (1/4 as a rational). + */ + public static final Rational QUARTER = new Rational(1, 4); + + /** + * Convenient constant representing a value of a third (1/3 as a rational). + */ + public static final Rational THIRD = new Rational(1, 3); + + /** + * Convenient constant representing a value of a half (1/2 as a rational). + */ + public static final Rational HALF = new Rational(1, 2); + + /** + * Convenient constant representing a value of two thirds (2/3 as a rational). + */ + public static final Rational TWO_THIRDS = new Rational(2, 3); + + /** + * Convenient constant representing a value of three quarters (3/4 as a rational). + */ + public static final Rational THREE_QUARTERS = new Rational(3, 4); + + /** + * Convenient constant representing a value of one (1/1 as a rational). + */ + public static final Rational ONE = new Rational(1); + + private final long numerator; + private final long denominator; + + + /** + * Creates a vulgar fraction with the specified numerator and denominator. + * @param numerator The fraction's numerator (may be negative). + * @param denominator The fraction's denominator (must be greater than or + * equal to 1). + */ + public Rational(long numerator, long denominator) + { + if (denominator < 1) + { + throw new IllegalArgumentException("Denominator must be non-zero and positive."); + } + long gcd = Maths.greatestCommonDivisor(numerator, denominator); + this.numerator = numerator / gcd; + this.denominator = denominator / gcd; + } + + + /** + * Creates a rational value equivalent to the specified integer value. + * @param value The value of this rational as an integer. + */ + public Rational(long value) + { + this(value, 1); + } + + + /** + * Creates a rational value equivalent to the specified decimal value. + * @param value The value of this rational as a fractional decimal. + * @throws ArithmeticException If the BigDecimal value is too large to be + * represented as a Rational. + */ + public Rational(BigDecimal value) + { + BigDecimal trimmedValue = value.stripTrailingZeros(); + BigInteger denominator = BigInteger.TEN.pow(trimmedValue.scale()); + BigInteger numerator = trimmedValue.unscaledValue(); + BigInteger gcd = numerator.gcd(denominator); + this.numerator = numerator.divide(gcd).longValue(); + this.denominator = denominator.divide(gcd).longValue(); + } + + + /** + * Returns the numerator of the fraction. + * @return The numerator. + */ + public long getNumerator() + { + return numerator; + } + + + /** + * Returns the denominator (divisor) of the fraction. + * @return The denominator. + */ + public long getDenominator() + { + return denominator; + } + + + /** + * Add the specified value to this value and return the result as a new object + * (also a rational). If the two values have different denominators, they will + * first be converted so that they have common denominators. + * @param value The value to add to this rational. + * @return A new rational value that is the sum of this value and the specified + * value. + */ + public Rational add(Rational value) + { + if (denominator == value.getDenominator()) + { + return new Rational(numerator + value.getNumerator(), denominator); + } + else + { + return new Rational(numerator * value.getDenominator() + value.getNumerator() * denominator, + denominator * value.getDenominator()); + } + } + + + /** + * Subtract the specified value from this value and return the result as a new object + * (also a rational). If the two values have different denominators, they will + * first be converted so that they have common denominators. + * @param value The value to subtract from this rational. + * @return A new rational value that is the result of subtracting the specified value + * from this value. + */ + public Rational subtract(Rational value) + { + if (denominator == value.getDenominator()) + { + return new Rational(numerator - value.getNumerator(), denominator); + } + else + { + return new Rational(numerator * value.getDenominator() - value.getNumerator() * denominator, + denominator * value.getDenominator()); + } + } + + + /** + * Multiply this rational by the specified value and return the result as a new + * object (also a Rational). + * @param value The amount to multiply by. + * @return A new rational value that is the result of multiplying this value by + * the specified value. + */ + public Rational multiply(Rational value) + { + return new Rational(numerator * value.getNumerator(), + denominator * value.getDenominator()); + } + + + /** + * Divide this rational by the specified value and return the result as a new + * object (also a Rational). + * @param value The amount to divide by. + * @return A new rational value that is the result of dividing this value by + * the specified value. + */ + public Rational divide(Rational value) + { + return new Rational(numerator * value.getDenominator(), + denominator * value.getNumerator()); + } + + + /** + * Returns the integer equivalent of this rational number. If + * there is no exact integer representation, it returns the closest + * integer that is lower than the rational value (effectively the + * truncated version of calling {@link #doubleValue()}). + * @return The (truncated) integer value of this rational. + */ + public int intValue() + { + return (int) longValue(); + } + + + /** + * Returns the integer equivalent of this rational number as a long. + * If there is no exact integer representation, it returns the closest + * long that is lower than the rational value (effectively the + * truncated version of calling {@link #doubleValue()}). + * @return The (truncated) long value of this rational. + */ + public long longValue() + { + return (long) doubleValue(); + } + + + /** + * Returns the result of dividing the numerator by the denominator. + * Will result in a loss of precision for fractions that have no + * exact float representation. + * @return The closest double-precision floating point equivalent of + * the fraction represented by this object. + */ + public float floatValue() + { + return (float) doubleValue(); + } + + + /** + * Returns the result of dividing the numerator by the denominator. + * Will result in a loss of precision for fractions that have no + * exact double representation. + * @return The closest double-precision floating point equivalent of + * the fraction represented by this object. + */ + public double doubleValue() + { + return (double) numerator / denominator; + } + + + /** + * Determines whether this rational value is equal to some other object. + * To be considered equal the other object must also be a Rational object + * with an indentical numerator and denominator. + * @param other The object to compare against. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) + { + if (this == other) + { + return true; + } + if (other == null || getClass() != other.getClass()) + { + return false; + } + Rational rational = (Rational) other; + + return denominator == rational.getDenominator() + && numerator == rational.getNumerator(); + } + + + /** + * Over-ridden to be consistent with {@link #equals(Object)}. + * @return The hash code value. + */ + @Override + public int hashCode() + { + int result = (int) (numerator ^ (numerator >>> 32)); + result = 31 * result + (int) (denominator ^ (denominator >>> 32)); + return result; + } + + + /** + * Returns a String representation of the rational number, expressed as + * a vulgar fraction (i.e. 1 and 1/3 is shown as 4/3). If the rational + * is equal to an integer, the value is simply displayed as that integer + * with no fractional part (i.e. 2/1 is shown as 2). + * @return A string representation of this rational value. + */ + @Override + public String toString() + { + StringBuilder buffer = new StringBuilder(); + buffer.append(numerator); + if (denominator != 1) + { + buffer.append('/'); + buffer.append(denominator); + } + return buffer.toString(); + } + + + /** + * Compares this value with the specified object for order. Returns a negative + * integer, zero, or a positive integer as this value is less than, equal to, or + * greater than the specified value. + * @param other Another Rational value. + * @return A negative integer, zero, or a positive integer as this value is less + * than, equal to, or greater than the specified value. + */ + public int compareTo(Rational other) + { + if (denominator == other.getDenominator()) + { + return ((Long) numerator).compareTo(other.getNumerator()); + } + else + { + Long adjustedNumerator = numerator * other.getDenominator(); + Long otherAdjustedNumerator = other.getNumerator() * denominator; + return adjustedNumerator.compareTo(otherAdjustedNumerator); + } + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/number/package-info.java b/maths/core/src/java/main/org/uncommons/maths/number/package-info.java new file mode 100644 index 00000000..836f48ad --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/number/package-info.java @@ -0,0 +1,20 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +/** + * Custom numeric data types and classes for working with java.lang.Number and + * its sub-classes. + */ +package org.uncommons.maths.number; diff --git a/maths/core/src/java/main/org/uncommons/maths/package-info.java b/maths/core/src/java/main/org/uncommons/maths/package-info.java new file mode 100644 index 00000000..d467d3d7 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/package-info.java @@ -0,0 +1,20 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +/** + * A set of utility classes for mathematical operations. + * @author Daniel Dyer + */ +package org.uncommons.maths; diff --git a/maths/core/src/java/main/org/uncommons/maths/random/AESCounterRNG.java b/maths/core/src/java/main/org/uncommons/maths/random/AESCounterRNG.java new file mode 100644 index 00000000..a4b4bd25 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/AESCounterRNG.java @@ -0,0 +1,216 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.GeneralSecurityException; +import java.security.Key; +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import javax.crypto.Cipher; +import org.uncommons.maths.binary.BinaryUtils; + +/** + * <p>Non-linear random number generator based on the AES block cipher in counter mode. + * Uses the seed as a key to encrypt a 128-bit counter using AES(Rijndael).</p> + * + * <p>By default, we only use a 128-bit key for the cipher because any larger key requires + * the inconvenience of installing the unlimited strength cryptography policy + * files for the Java platform. Larger keys may be used (192 or 256 bits) but if the + * cryptography policy files are not installed, a + * {@link java.security.GeneralSecurityException} will be thrown.</p> + * + * <p><em>NOTE: Because instances of this class require 128-bit seeds, it is not + * possible to seed this RNG using the {@link #setSeed(long)} method inherited + * from {@link Random}. Calls to this method will have no effect. + * Instead the seed must be set by a constructor.</em></p> + * + * <p><em>NOTE: THIS CLASS IS NOT SERIALIZABLE</em></p> + * + * @author Daniel Dyer + */ +public class AESCounterRNG extends Random implements RepeatableRNG +{ + private static final int DEFAULT_SEED_SIZE_BYTES = 16; + + private final byte[] seed; + private final Cipher cipher; // TO DO: This field is not Serializable. + private final byte[] counter = new byte[16]; // 128-bit counter. + + // Lock to prevent concurrent modification of the RNG's internal state. + private final ReentrantLock lock = new ReentrantLock(); + + + private byte[] currentBlock = null; + private int index = 0; + + + /** + * Creates a new RNG and seeds it using 128 bits from the default seeding strategy. + * @throws GeneralSecurityException If there is a problem initialising the AES cipher. + */ + public AESCounterRNG() throws GeneralSecurityException + { + this(DEFAULT_SEED_SIZE_BYTES); + } + + + /** + * Seed the RNG using the provided seed generation strategy to create a 128-bit + * seed. + * @param seedGenerator The seed generation strategy that will provide + * the seed value for this RNG. + * @throws SeedException If there is a problem generating a seed. + * @throws GeneralSecurityException If there is a problem initialising the AES cipher. + */ + public AESCounterRNG(SeedGenerator seedGenerator) throws SeedException, + GeneralSecurityException + { + this(seedGenerator.generateSeed(DEFAULT_SEED_SIZE_BYTES)); + } + + + /** + * Seed the RNG using the default seed generation strategy to create a seed of the + * specified size. + * @param seedSizeBytes The number of bytes to use for seed data. Valid values + * are 16 (128 bits), 24 (192 bits) and 32 (256 bits). Any other values will + * result in an exception from the AES implementation. + * @throws GeneralSecurityException If there is a problem initialising the AES cipher. + * @since 1.0.2 + */ + public AESCounterRNG(int seedSizeBytes) throws GeneralSecurityException + { + this(DefaultSeedGenerator.getInstance().generateSeed(seedSizeBytes)); + } + + + /** + * Creates an RNG and seeds it with the specified seed data. + * @param seed The seed data used to initialise the RNG. + * @throws GeneralSecurityException If there is a problem initialising the AES cipher. + */ + public AESCounterRNG(byte[] seed) throws GeneralSecurityException + { + if (seed == null) + { + throw new IllegalArgumentException("AES RNG requires a 128-bit, 192-bit or 256-bit seed."); + } + this.seed = seed.clone(); + + cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, new AESKey(this.seed)); + } + + + /** + * {@inheritDoc} + */ + public byte[] getSeed() + { + return seed.clone(); + } + + + private void incrementCounter() + { + for (int i = 0; i < counter.length; i++) + { + ++counter[i]; + if (counter[i] != 0) // Check whether we need to loop again to carry the one. + { + break; + } + } + } + + + /** + * Generates a single 128-bit block (16 bytes). + * @throws GeneralSecurityException If there is a problem with the cipher + * that generates the random data. + * @return A 16-byte block of random data. + */ + private byte[] nextBlock() throws GeneralSecurityException + { + incrementCounter(); + return cipher.doFinal(counter); + } + + + /** + * {@inheritDoc} + */ + @Override + protected final int next(int bits) + { + int result; + try + { + lock.lock(); + if (currentBlock == null || currentBlock.length - index < 4) + { + try + { + currentBlock = nextBlock(); + index = 0; + } + catch (GeneralSecurityException ex) + { + // Should never happen. If initialisation succeeds without exceptions + // we should be able to proceed indefinitely without exceptions. + throw new IllegalStateException("Failed creating next random block.", ex); + } + } + result = BinaryUtils.convertBytesToInt(currentBlock, index); + index += 4; + } + finally + { + lock.unlock(); + } + return result >>> (32 - bits); + } + + + + /** + * Trivial key implementation for use with AES cipher. + */ + private static final class AESKey implements Key + { + private final byte[] keyData; + + private AESKey(byte[] keyData) + { + this.keyData = keyData; + } + + public String getAlgorithm() + { + return "AES"; + } + + public String getFormat() + { + return "RAW"; + } + + public byte[] getEncoded() + { + return keyData; + } + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/BinomialGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/BinomialGenerator.java new file mode 100644 index 00000000..ac81bd00 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/BinomialGenerator.java @@ -0,0 +1,146 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.uncommons.maths.binary.BinaryUtils; +import org.uncommons.maths.binary.BitString; +import org.uncommons.maths.number.ConstantGenerator; +import org.uncommons.maths.number.NumberGenerator; + +/** + * Discrete random sequence that follows a + * <a href="http://en.wikipedia.org/wiki/Binomial_distribution" target="_top">binomial + * distribution</a>. + * @author Daniel Dyer + */ +public class BinomialGenerator implements NumberGenerator<Integer> +{ + private final Random rng; + private final NumberGenerator<Integer> n; + private final NumberGenerator<Double> p; + + // Cache the fixed-point representation of p to avoid having to + // recalculate it for each value generated. Only calculate it + // if and when p changes. + private transient BitString pBits; + private transient double lastP; + + + /** + * <p>Creates a generator of binomially-distributed values. The number of + * trials ({@literal n}) and the probability of success in each trial + * ({@literal p}) are determined by the provided {@link NumberGenerator}s. + * This means that the statistical parameters of this generator may change + * over time. One example of where this is useful is if the {@literal n} + * and {@literal p} generators are attached to GUI controls that allow a + * user to tweak the parameters while a program is running.</p> + * <p>To create a Binomial generator with a constant {@literal n} and + * {@literal p}, use the {@link #BinomialGenerator(int, double, Random)} + * constructor instead.</p> + * @param n A {@link NumberGenerator} that provides the number of trials for + * the Binomial distribution used for the next generated value. This generator + * must produce only positive values. + * @param p A {@link NumberGenerator} that provides the probability of succes + * in a single trial for the Binomial distribution used for the next + * generated value. This generator must produce only values in the range 0 - 1. + * @param rng The source of randomness. + */ + public BinomialGenerator(NumberGenerator<Integer> n, + NumberGenerator<Double> p, + Random rng) + { + this.n = n; + this.p = p; + this.rng = rng; + } + + + /** + * Creates a generator of binomially-distributed values from a distribution + * with the specified parameters. + * @param n The number of trials (and therefore the maximum possible value returned + * by this sequence). + * @param p The probability (between 0 and 1) of success in any one trial. + * @param rng The source of randomness used to generate the binomial values. + */ + public BinomialGenerator(int n, + double p, + Random rng) + { + this(new ConstantGenerator<Integer>(n), + new ConstantGenerator<Double>(p), + rng); + if (n <= 0) + { + throw new IllegalArgumentException("n must be a positive integer."); + } + if (p <= 0 || p >= 1) + { + throw new IllegalArgumentException("p must be between 0 and 1."); + } + } + + + /** + * Generate the next binomial value from the current values of + * {@literal n} and {@literal p}. The algorithm used is from + * The Art of Computer Programming Volume 2 (Seminumerical Algorithms) + * by Donald Knuth (page 589 in the Third Edition) where it is + * credited to J.H. Ahrens. + */ + public Integer nextValue() + { + // Regenerate the fixed point representation of p if it has changed. + double newP = p.nextValue(); + if (pBits == null || newP != lastP) + { + lastP = newP; + pBits = BinaryUtils.convertDoubleToFixedPointBits(newP); + } + + int trials = n.nextValue(); + int totalSuccesses = 0; + int pIndex = pBits.getLength() - 1; + + while (trials > 0 && pIndex >= 0) + { + int successes = binomialWithEvenProbability(trials); + trials -= successes; + if (pBits.getBit(pIndex)) + { + totalSuccesses += successes; + } + --pIndex; + } + + return totalSuccesses; + } + + + /** + * Generating binomial values when {@literal p = 0.5} is straightforward. + * It simply a case of generating {@literal n} random bits and + * counting how many are 1s. + * @param n The number of trials. + * @return The number of successful outcomes from {@literal n} trials. + */ + private int binomialWithEvenProbability(int n) + { + BitString bits = new BitString(n, rng); + return bits.countSetBits(); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/CMWC4096RNG.java b/maths/core/src/java/main/org/uncommons/maths/random/CMWC4096RNG.java new file mode 100644 index 00000000..e195972e --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/CMWC4096RNG.java @@ -0,0 +1,129 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import org.uncommons.maths.binary.BinaryUtils; + +/** + * <p>A Java version of George Marsaglia's + * <a href="http://school.anhb.uwa.edu.au/personalpages/kwessen/shared/Marsaglia03.html">Complementary + * Multiply With Carry (CMWC) RNG</a>. + * This is a very fast PRNG with an extremely long period (2^131104). It should be used + * in preference to the {@link MersenneTwisterRNG} when a very long period is required.</p> + * + * <p>One potential drawback of this RNG is that it requires significantly more seed data than + * the other RNGs provided by Uncommons Maths. It requires just over 16 kilobytes, which may + * be a problem if your are obtaining seed data from a slow or limited entropy source. + * In contrast, the Mersenne Twister requires only 128 bits of seed data.</p> + * + * <p><em>NOTE: Because instances of this class require 16-kilobyte seeds, it is not + * possible to seed this RNG using the {@link #setSeed(long)} method inherited + * from {@link Random}. Calls to this method will have no effect. + * Instead the seed must be set by a constructor.</em></p> + * + * @author Daniel Dyer + * @since 1.2 + */ +public class CMWC4096RNG extends Random implements RepeatableRNG +{ + private static final int SEED_SIZE_BYTES = 16384; // Needs 4,096 32-bit integers. + + private static final long A = 18782L; + + private final byte[] seed; + private final int[] state; + private int carry = 362436; // TO DO: This should be randomly generated. + private int index = 4095; + + // Lock to prevent concurrent modification of the RNG's internal state. + private final ReentrantLock lock = new ReentrantLock(); + + + /** + * Creates a new RNG and seeds it using the default seeding strategy. + */ + public CMWC4096RNG() + { + this(DefaultSeedGenerator.getInstance().generateSeed(SEED_SIZE_BYTES)); + } + + + /** + * Seed the RNG using the provided seed generation strategy. + * @param seedGenerator The seed generation strategy that will provide + * the seed value for this RNG. + * @throws SeedException If there is a problem generating a seed. + */ + public CMWC4096RNG(SeedGenerator seedGenerator) throws SeedException + { + this(seedGenerator.generateSeed(SEED_SIZE_BYTES)); + } + + + + /** + * Creates an RNG and seeds it with the specified seed data. + * @param seed The seed data used to initialise the RNG. + */ + public CMWC4096RNG(byte[] seed) + { + if (seed == null || seed.length != SEED_SIZE_BYTES) + { + throw new IllegalArgumentException("CMWC RNG requires 16kb of seed data."); + } + this.seed = seed.clone(); + this.state = BinaryUtils.convertBytesToInts(seed); + } + + + /** + * {@inheritDoc} + */ + public byte[] getSeed() + { + return seed.clone(); + } + + + /** + * {@inheritDoc} + */ + @Override + protected int next(int bits) + { + try + { + lock.lock(); + index = (index + 1) & 4095; + long t = A * (state[index] & 0xFFFFFFFFL) + carry; + carry = (int) (t >> 32); + int x = ((int) t) + carry; + if (x < carry) + { + x++; + carry++; + } + state[index] = 0xFFFFFFFE - x; + return state[index] >>> (32 - bits); + } + finally + { + lock.unlock(); + } + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/CellularAutomatonRNG.java b/maths/core/src/java/main/org/uncommons/maths/random/CellularAutomatonRNG.java new file mode 100644 index 00000000..3a879ecc --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/CellularAutomatonRNG.java @@ -0,0 +1,199 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import org.uncommons.maths.binary.BinaryUtils; + +/** + * <p>Java port of the + * <a href="http://home.southernct.edu/~pasqualonia1/ca/report.html" target="_top">cellular + * automaton pseudorandom number generator</a> developed by Tony Pasqualoni.</p> + * + * <p><em>NOTE: Instances of this class do not use the seeding mechanism inherited + * from {@link Random}. Calls to the {@link #setSeed(long)} method will have no + * effect. Instead the seed must be set by a constructor.</em></p> + * + * @author Tony Pasqualoni (original C version) + * @author Daniel Dyer (Java port) + */ +public class CellularAutomatonRNG extends Random implements RepeatableRNG +{ + private static final int SEED_SIZE_BYTES = 4; + private static final int AUTOMATON_LENGTH = 2056; + + private static final int[] RNG_RULE = + { + 100, 75, 16, 3, 229, 51, 197, 118, 24, 62, 198, 11, 141, 152, 241, 188, + 2, 17, 71, 47, 179, 177, 126, 231, 202, 243, 59, 25, 77, 196, 30, 134, + 199, 163, 34, 216, 21, 84, 37, 182, 224, 186, 64, 79, 225, 45, 143, 20, + 48, 147, 209, 221, 125, 29, 99, 12, 46, 190, 102, 220, 80, 215, 242, 105, + 15, 53, 0, 67, 68, 69, 70, 89, 109, 195, 170, 78, 210, 131, 42, 110, + 181, 145, 40, 114, 254, 85, 107, 87, 72, 192, 90, 201, 162, 122, 86, 252, + 94, 129, 98, 132, 193, 249, 156, 172, 219, 230, 153, 54, 180, 151, 83, 214, + 123, 88, 164, 167, 116, 117, 7, 27, 23, 213, 235, 5, 65, 124, 60, 127, + 236, 149, 44, 28, 58, 121, 191, 13, 250, 10, 232, 112, 101, 217, 183, 239, + 8, 32, 228, 174, 49, 113, 247, 158, 106, 218, 154, 66, 226, 157, 50, 26, + 253, 93, 205, 41, 133, 165, 61, 161, 187, 169, 6, 171, 81, 248, 56, 175, + 246, 36, 178, 52, 57, 212, 39, 176, 184, 185, 245, 63, 35, 189, 206, 76, + 104, 233, 194, 19, 43, 159, 108, 55, 200, 155, 14, 74, 244, 255, 222, 207, + 208, 137, 128, 135, 96, 144, 18, 95, 234, 139, 173, 92, 1, 203, 115, 223, + 130, 97, 91, 227, 146, 4, 31, 120, 211, 38, 22, 138, 140, 237, 238, 251, + 240, 160, 142, 119, 73, 103, 166, 33, 148, 9, 111, 136, 168, 150, 82, 204, + 100, 75, 16, 3, 229, 51, 197, 118, 24, 62, 198, 11, 141, 152, 241, 188, + 2, 17, 71, 47, 179, 177, 126, 231, 202, 243, 59, 25, 77, 196, 30, 134, + 199, 163, 34, 216, 21, 84, 37, 182, 224, 186, 64, 79, 225, 45, 143, 20, + 48, 147, 209, 221, 125, 29, 99, 12, 46, 190, 102, 220, 80, 215, 242, 105, + 15, 53, 0, 67, 68, 69, 70, 89, 109, 195, 170, 78, 210, 131, 42, 110, + 181, 145, 40, 114, 254, 85, 107, 87, 72, 192, 90, 201, 162, 122, 86, 252, + 94, 129, 98, 132, 193, 249, 156, 172, 219, 230, 153, 54, 180, 151, 83, 214, + 123, 88, 164, 167, 116, 117, 7, 27, 23, 213, 235, 5, 65, 124, 60, 127, + 236, 149, 44, 28, 58, 121, 191, 13, 250, 10, 232, 112, 101, 217, 183, 239, + 8, 32, 228, 174, 49, 113, 247, 158, 106, 218, 154, 66, 226, 157, 50, 26, + 253, 93, 205, 41, 133, 165, 61, 161, 187, 169, 6, 171, 81, 248, 56, 175, + 246, 36, 178, 52, 57, 212, 39, 176, 184, 185, 245, 63, 35, 189, 206, 76, + 104, 233, 194, 19, 43, 159, 108, 55, 200, 155, 14, 74, 244, 255, 222, 207, + 208, 137, 128, 135, 96, 144, 18, 95, 234, 139, 173, 92, 1, 203, 115, 223, + 130, 97, 91, 227, 146, 4, 31, 120, 211, 38, 22, 138, 140, 237, 238, 251, + 240, 160, 142, 119, 73, 103, 166, 33, 148, 9, 111, 136, 168, 150, 82 + }; + + + private final byte[] seed; + private final int[] cells = new int[AUTOMATON_LENGTH]; + + // Lock to prevent concurrent modification of the RNG's internal state. + private final ReentrantLock lock = new ReentrantLock(); + + private int currentCellIndex = AUTOMATON_LENGTH - 1; + + + /** + * Creates a new RNG and seeds it using the default seeding strategy. + */ + public CellularAutomatonRNG() + { + this(DefaultSeedGenerator.getInstance().generateSeed(SEED_SIZE_BYTES)); + } + + + /** + * Seed the RNG using the provided seed generation strategy. + * @param seedGenerator The seed generation strategy that will provide + * the seed value for this RNG. + * @throws SeedException If there is a problem generating a seed. + */ + public CellularAutomatonRNG(SeedGenerator seedGenerator) throws SeedException + { + this(seedGenerator.generateSeed(SEED_SIZE_BYTES)); + } + + + /** + * Creates an RNG and seeds it with the specified seed data. + * @param seed The seed data used to initialise the RNG. + */ + public CellularAutomatonRNG(byte[] seed) + { + if (seed == null || seed.length != SEED_SIZE_BYTES) + { + throw new IllegalArgumentException("Cellular Automaton RNG requires a 32-bit (4-byte) seed."); + } + this.seed = seed.clone(); + + // Set initial cell states using seed. + cells[AUTOMATON_LENGTH - 1] = seed[0] + 128; + cells[AUTOMATON_LENGTH - 2] = seed[1] + 128; + cells[AUTOMATON_LENGTH - 3] = seed[2] + 128; + cells[AUTOMATON_LENGTH - 4] = seed[3] + 128; + + int seedAsInt = BinaryUtils.convertBytesToInt(seed, 0); + if (seedAsInt != 0xFFFFFFFF) + { + seedAsInt++; + } + for (int i = 0; i < AUTOMATON_LENGTH - 4; i++) + { + cells[i] = 0x000000FF & (seedAsInt >> (i % 32)); + } + + // Evolve automaton before returning integers. + for (int i = 0; i < AUTOMATON_LENGTH * AUTOMATON_LENGTH / 4; i++) + { + next(32); + } + } + + + /** + * {@inheritDoc} + */ + @Override + public int next(int bits) + { + int result; + try + { + lock.lock(); + // Set cell addresses using address of current cell. + int cellC = currentCellIndex - 1; + int cellB = cellC - 1; + int cellA = cellB - 1; + + // Update cell states using rule table. + cells[currentCellIndex] = RNG_RULE[cells[cellC] + cells[currentCellIndex]]; + cells[cellC] = RNG_RULE[cells[cellB] + cells[cellC]]; + cells[cellB] = RNG_RULE[cells[cellA] + cells[cellB]]; + + // Update the state of cellA and shift current cell to the left by 4 bytes. + if (cellA == 0) + { + cells[cellA] = RNG_RULE[cells[cellA]]; + currentCellIndex = AUTOMATON_LENGTH - 1; + } + else + { + cells[cellA] = RNG_RULE[cells[cellA - 1] + cells[cellA]]; + currentCellIndex -= 4; + } + result = convertCellsToInt(cells, cellA); + } + finally + { + lock.unlock(); + } + return result >>> (32 - bits); + } + + + /** + * {@inheritDoc} + */ + public byte[] getSeed() + { + return seed; + } + + + private static int convertCellsToInt(int[] cells, int offset) + { + return cells[offset] + + (cells[offset + 1] << 8) + + (cells[offset + 2] << 16) + + (cells[offset + 3] << 24); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/ContinuousUniformGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/ContinuousUniformGenerator.java new file mode 100644 index 00000000..6e8c80dc --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/ContinuousUniformGenerator.java @@ -0,0 +1,49 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.uncommons.maths.number.NumberGenerator; + +/** + * Continuous, uniformly distributed random sequence. Generates + * values in the range {@literal mininum (inclusive) ... maximum (exclusive)}. + * @author Daniel Dyer + */ +public class ContinuousUniformGenerator implements NumberGenerator<Double> +{ + private final Random rng; + private final double range; + private final double minimumValue; + + public ContinuousUniformGenerator(double minimumValue, + double maximumValue, + Random rng) + { + this.rng = rng; + this.minimumValue = minimumValue; + this.range = maximumValue - minimumValue; + } + + + /** + * {@inheritDoc} + */ + public Double nextValue() + { + return rng.nextDouble() * range + minimumValue; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/DefaultSeedGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/DefaultSeedGenerator.java new file mode 100644 index 00000000..7a0f1051 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/DefaultSeedGenerator.java @@ -0,0 +1,98 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import org.uncommons.maths.binary.BinaryUtils; + +/** + * Seed generator that maintains multiple strategies for seed + * generation and will delegate to the best one available for the + * current operating environment. + * @author Daniel Dyer + */ +public final class DefaultSeedGenerator implements SeedGenerator +{ + private static final String DEBUG_PROPERTY = "org.uncommons.maths.random.debug"; + + /** Singleton instance. */ + private static final DefaultSeedGenerator INSTANCE = new DefaultSeedGenerator(); + + /** Delegate generators. */ + private static final SeedGenerator[] GENERATORS = new SeedGenerator[] + { + new DevRandomSeedGenerator(), + new RandomDotOrgSeedGenerator(), + new SecureRandomSeedGenerator() + }; + + + private DefaultSeedGenerator() + { + // Private constructor prevents external instantiation. + } + + + /** + * @return The singleton instance of this class. + */ + public static DefaultSeedGenerator getInstance() + { + return INSTANCE; + } + + + /** + * Generates a seed by trying each of the available strategies in + * turn until one succeeds. Tries the most suitable strategy first + * and eventually degrades to the least suitable (but guaranteed to + * work) strategy. + * @param length The length (in bytes) of the seed. + * @return A random seed of the requested length. + */ + public byte[] generateSeed(int length) + { + for (SeedGenerator generator : GENERATORS) + { + try + { + byte[] seed = generator.generateSeed(length); + try + { + boolean debug = System.getProperty(DEBUG_PROPERTY, "false").equals("true"); + if (debug) + { + String seedString = BinaryUtils.convertBytesToHexString(seed); + System.out.println(seed.length + " bytes of seed data acquired from " + generator + ":"); + System.out.println(" " + seedString); + } + } + catch (SecurityException ex) + { + // Ignore, means we can't read the property so just default to false. + } + + return seed; + } + catch (SeedException ex) + { + // Ignore and try the next generator... + } + } + // This shouldn't happen as at least one the generators should be + // able to generate a seed. + throw new IllegalStateException("All available seed generation strategies failed."); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/DevRandomSeedGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/DevRandomSeedGenerator.java new file mode 100644 index 00000000..73606bdd --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/DevRandomSeedGenerator.java @@ -0,0 +1,90 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * RNG seed strategy that gets data from {@literal /dev/random} on systems + * that provide it (e.g. Solaris/Linux). If {@literal /dev/random} does not + * exist or is not accessible, a {@link SeedException} is thrown. + * @author Daniel Dyer + */ +public class DevRandomSeedGenerator implements SeedGenerator +{ + private static final File DEV_RANDOM = new File("/dev/random"); + + /** + * {@inheritDoc} + * @return The requested number of random bytes, read directly from + * {@literal /dev/random}. + * @throws SeedException If {@literal /dev/random} does not exist or is + * not accessible + */ + public byte[] generateSeed(int length) throws SeedException + { + FileInputStream file = null; + try + { + file = new FileInputStream(DEV_RANDOM); + byte[] randomSeed = new byte[length]; + int count = 0; + while (count < length) + { + int bytesRead = file.read(randomSeed, count, length - count); + if (bytesRead == -1) + { + throw new SeedException("EOF encountered reading random data."); + } + count += bytesRead; + } + return randomSeed; + } + catch (IOException ex) + { + throw new SeedException("Failed reading from " + DEV_RANDOM.getName(), ex); + } + catch (SecurityException ex) + { + // Might be thrown if resource access is restricted (such as in + // an applet sandbox). + throw new SeedException("SecurityManager prevented access to " + DEV_RANDOM.getName(), ex); + } + finally + { + if (file != null) + { + try + { + file.close(); + } + catch (IOException ex) + { + // Ignore. + } + } + } + } + + + @Override + public String toString() + { + return "/dev/random"; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/DiehardInputGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/DiehardInputGenerator.java new file mode 100644 index 00000000..2b0eb44e --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/DiehardInputGenerator.java @@ -0,0 +1,91 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Random; + +/** + * Utility to generate an input file for the + * <a href="http://stat.fsu.edu/pub/diehard/" target="_top">DIEHARD</a> suite of statistical + * tests for random number generators. + * @author Daniel Dyer + */ +public final class DiehardInputGenerator +{ + // How many 32-bit values should be written to the output file. + private static final int INT_COUNT = 3000000; + + private DiehardInputGenerator() + { + // Prevents instantiation. + } + + + /** + * @param args The first argument is the class name of the RNG, the second + * is the file to use for output. + * @throws Exception If there are problems setting up the RNG or writing to + * the output file. + */ + @SuppressWarnings("unchecked") + public static void main(String[] args) throws Exception + { + if (args.length != 2) + { + System.out.println("Expected arguments:"); + System.out.println("\t<Fully-qualified RNG class name> <Output file>"); + System.exit(1); + } + Class<? extends Random> rngClass = (Class<? extends Random>) Class.forName(args[0]); + File outputFile = new File(args[1]); + generateOutputFile(rngClass.newInstance(), outputFile); + } + + + /** + * Generates a file of random data in a format suitable for the DIEHARD test. + * DIEHARD requires 3 million 32-bit integers. + * @param rng The random number generator to use to generate the data. + * @param outputFile The file that the random data is written to. + * @throws IOException If there is a problem writing to the file. + */ + public static void generateOutputFile(Random rng, + File outputFile) throws IOException + { + DataOutputStream dataOutput = null; + try + { + dataOutput = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile))); + for (int i = 0; i < INT_COUNT; i++) + { + dataOutput.writeInt(rng.nextInt()); + } + dataOutput.flush(); + } + finally + { + if (dataOutput != null) + { + dataOutput.close(); + } + } + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/DiscreteUniformGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/DiscreteUniformGenerator.java new file mode 100644 index 00000000..5eb56787 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/DiscreteUniformGenerator.java @@ -0,0 +1,49 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.uncommons.maths.number.NumberGenerator; + +/** + * Discrete, uniformly distributed random sequence. Generates + * values between the specified minimum and maximum values (inclusive). + * @author Daniel Dyer + */ +public class DiscreteUniformGenerator implements NumberGenerator<Integer> +{ + private final Random rng; + private final int range; + private final int minimumValue; + + public DiscreteUniformGenerator(int minimumValue, + int maximumValue, + Random rng) + { + this.rng = rng; + this.minimumValue = minimumValue; + this.range = maximumValue - minimumValue + 1; + } + + + /** + * {@inheritDoc} + */ + public Integer nextValue() + { + return rng.nextInt(range) + minimumValue; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/ExponentialGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/ExponentialGenerator.java new file mode 100644 index 00000000..ab24da8b --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/ExponentialGenerator.java @@ -0,0 +1,83 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.uncommons.maths.number.ConstantGenerator; +import org.uncommons.maths.number.NumberGenerator; + +/** + * Continuous random sequence that follows an + * <a href="http://en.wikipedia.org/wiki/Exponential_distribution" target="_top">exponential + * distribution</a>. + * @author Daniel Dyer + * @since 1.0.2 + */ +public class ExponentialGenerator implements NumberGenerator<Double> +{ + private final NumberGenerator<Double> rate; + private final Random rng; + + + /** + * Creates a generator of exponentially-distributed values from a distribution + * with a rate controlled by the specified generator parameter. The mean of + * this distribution is {@literal 1 / rate} + * and the variance is {@literal 1 / rate^2}. + * @param rate A number generator that provides values to use as the rate for + * the exponential distribution. This generator must only return non-zero, positive + * values. + * @param rng The source of randomness used to generate the exponential values. + */ + public ExponentialGenerator(NumberGenerator<Double> rate, + Random rng) + { + this.rate = rate; + this.rng = rng; + } + + + /** + * Creates a generator of exponentially-distributed values from a distribution + * with the specified rate. The mean of this distribution is {@literal 1 / rate} + * and the variance is {@literal 1 / rate^2}. + * @param rate The rate (lamda) of the exponential distribution. + * @param rng The source of randomness used to generate the exponential values. + */ + public ExponentialGenerator(double rate, + Random rng) + { + this(new ConstantGenerator<Double>(rate), rng); + } + + + /** + * Generate the next exponential value from the current value of + * {@literal rate}. + * @return The next exponentially-distributed value. + */ + public Double nextValue() + { + double u; + do + { + // Get a uniformly-distributed random double between + // zero (inclusive) and 1 (exclusive) + u = rng.nextDouble(); + } while (u == 0d); // Reject zero, u must be positive for this to work. + return (-Math.log(u)) / rate.nextValue(); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/GaussianGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/GaussianGenerator.java new file mode 100644 index 00000000..50981999 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/GaussianGenerator.java @@ -0,0 +1,86 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.uncommons.maths.number.ConstantGenerator; +import org.uncommons.maths.number.NumberGenerator; + +/** + * <a href="http://en.wikipedia.org/wiki/Normal_distribution" target="_top">Normally distributed</a> + * random sequence. + * @author Daniel Dyer + */ +public class GaussianGenerator implements NumberGenerator<Double> +{ + private final Random rng; + private final NumberGenerator<Double> mean; + private final NumberGenerator<Double> standardDeviation; + + + /** + * <p>Creates a generator of normally-distributed values. The mean and + * standard deviation are determined by the provided + * {@link NumberGenerator}s. This means that the statistical parameters + * of this generator may change over time. One example of where this + * is useful is if the mean and standard deviation generators are attached + * to GUI controls that allow a user to tweak the parameters while a + * program is running.</p> + * <p>To create a Gaussian generator with a constant mean and standard + * deviation, use the {@link #GaussianGenerator(double, double, Random)} + * constructor instead.</p> + * @param mean A {@link NumberGenerator} that provides the mean of the + * Gaussian distribution used for the next generated value. + * @param standardDeviation A {@link NumberGenerator} that provides the + * standard deviation of the Gaussian distribution used for the next + * generated value. + * @param rng The source of randomness. + */ + public GaussianGenerator(NumberGenerator<Double> mean, + NumberGenerator<Double> standardDeviation, + Random rng) + { + this.mean = mean; + this.standardDeviation = standardDeviation; + this.rng = rng; + } + + + /** + * Creates a generator of normally-distributed values from a distribution + * with the specified mean and standard deviation. + * @param mean The mean of the values generated. + * @param standardDeviation The standard deviation of the values generated. + * @param rng The source of randomness. + */ + public GaussianGenerator(double mean, + double standardDeviation, + Random rng) + { + this(new ConstantGenerator<Double>(mean), + new ConstantGenerator<Double>(standardDeviation), + rng); + } + + + /** + * {@inheritDoc} + */ + public Double nextValue() + { + return rng.nextGaussian() * standardDeviation.nextValue() + mean.nextValue(); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/JavaRNG.java b/maths/core/src/java/main/org/uncommons/maths/random/JavaRNG.java new file mode 100644 index 00000000..69e932a9 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/JavaRNG.java @@ -0,0 +1,96 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.uncommons.maths.binary.BinaryUtils; + +/** + * <p>This is the default {@link Random JDK RNG} extended + * to implement the {@link RepeatableRNG} interface (for consistency with + * the other RNGs in this package).</p> + * + * <p>The {@link MersenneTwisterRNG} should be used in preference to this + * class because it is statistically more random and performs slightly + * better.</p> + * + * <p><em>NOTE: Instances of this class do not use the seeding mechanism inherited + * from {@link Random}. Calls to the {@link #setSeed(long)} method will have no + * effect. Instead the seed must be set by a constructor.</em></p> + * + * @author Daniel Dyer + */ +public class JavaRNG extends Random implements RepeatableRNG +{ + private static final int SEED_SIZE_BYTES = 8; + + private final byte[] seed; + + + /** + * Creates a new RNG and seeds it using the default seeding strategy. + */ + public JavaRNG() + { + this(DefaultSeedGenerator.getInstance().generateSeed(SEED_SIZE_BYTES)); + } + + + /** + * Seed the RNG using the provided seed generation strategy. + * @param seedGenerator The seed generation strategy that will provide + * the seed value for this RNG. + * @throws SeedException If there is a problem generating a seed. + */ + public JavaRNG(SeedGenerator seedGenerator) throws SeedException + { + this(seedGenerator.generateSeed(SEED_SIZE_BYTES)); + } + + + /** + * Creates an RNG and seeds it with the specified seed data. + * @param seed The seed data used to initialise the RNG. + */ + public JavaRNG(byte[] seed) + { + super(createLongSeed(seed)); + this.seed = seed.clone(); + } + + + /** + * Helper method to convert seed bytes into the long value required by the + * super class. + */ + private static long createLongSeed(byte[] seed) + { + if (seed == null || seed.length != SEED_SIZE_BYTES) + { + throw new IllegalArgumentException("Java RNG requires a 64-bit (8-byte) seed."); + } + return BinaryUtils.convertBytesToLong(seed, 0); + } + + + /** + * {@inheritDoc} + */ + public byte[] getSeed() + { + return seed.clone(); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/MersenneTwisterRNG.java b/maths/core/src/java/main/org/uncommons/maths/random/MersenneTwisterRNG.java new file mode 100644 index 00000000..c855f9a3 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/MersenneTwisterRNG.java @@ -0,0 +1,204 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import org.uncommons.maths.binary.BinaryUtils; + +/** + * <p>Random number generator based on the + * <a href="http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html" target="_top">Mersenne + * Twister</a> algorithm developed by Makoto Matsumoto and Takuji Nishimura.</p> + * + * <p>This is a very fast random number generator with good statistical + * properties (it passes the full DIEHARD suite). This is the best RNG + * for most experiments. If a non-linear generator is required, use + * the slower {@link AESCounterRNG} RNG.</p> + * + * <p>This PRNG is deterministic, which can be advantageous for testing purposes + * since the output is repeatable. If multiple instances of this class are created + * with the same seed they will all have identical output.</p> + * + * <p>This code is translated from the original C version and assumes that we + * will always seed from an array of bytes. I don't pretend to know the + * meanings of the magic numbers or how it works, it just does.</p> + * + * <p><em>NOTE: Because instances of this class require 128-bit seeds, it is not + * possible to seed this RNG using the {@link #setSeed(long)} method inherited + * from {@link Random}. Calls to this method will have no effect. + * Instead the seed must be set by a constructor.</em></p> + * + * @author Makoto Matsumoto and Takuji Nishimura (original C version) + * @author Daniel Dyer (Java port) + */ +public class MersenneTwisterRNG extends Random implements RepeatableRNG +{ + // The actual seed size isn't that important, but it should be a multiple of 4. + private static final int SEED_SIZE_BYTES = 16; + + // Magic numbers from original C version. + private static final int N = 624; + private static final int M = 397; + private static final int[] MAG01 = {0, 0x9908b0df}; + private static final int UPPER_MASK = 0x80000000; + private static final int LOWER_MASK = 0x7fffffff; + private static final int BOOTSTRAP_SEED = 19650218; + private static final int BOOTSTRAP_FACTOR = 1812433253; + private static final int SEED_FACTOR1 = 1664525; + private static final int SEED_FACTOR2 = 1566083941; + private static final int GENERATE_MASK1 = 0x9d2c5680; + private static final int GENERATE_MASK2 = 0xefc60000; + + private final byte[] seed; + + // Lock to prevent concurrent modification of the RNG's internal state. + private final ReentrantLock lock = new ReentrantLock(); + + private final int[] mt = new int[N]; // State vector. + private int mtIndex = 0; // Index into state vector. + + + /** + * Creates a new RNG and seeds it using the default seeding strategy. + */ + public MersenneTwisterRNG() + { + this(DefaultSeedGenerator.getInstance().generateSeed(SEED_SIZE_BYTES)); + } + + + /** + * Seed the RNG using the provided seed generation strategy. + * @param seedGenerator The seed generation strategy that will provide + * the seed value for this RNG. + * @throws SeedException If there is a problem generating a seed. + */ + public MersenneTwisterRNG(SeedGenerator seedGenerator) throws SeedException + { + this(seedGenerator.generateSeed(SEED_SIZE_BYTES)); + } + + + + /** + * Creates an RNG and seeds it with the specified seed data. + * @param seed The seed data used to initialise the RNG. + */ + public MersenneTwisterRNG(byte[] seed) + { + if (seed == null || seed.length != SEED_SIZE_BYTES) + { + throw new IllegalArgumentException("Mersenne Twister RNG requires a 128-bit (16-byte) seed."); + } + this.seed = seed.clone(); + + int[] seedInts = BinaryUtils.convertBytesToInts(this.seed); + + // This section is translated from the init_genrand code in the C version. + mt[0] = BOOTSTRAP_SEED; + for (mtIndex = 1; mtIndex < N; mtIndex++) + { + mt[mtIndex] = (BOOTSTRAP_FACTOR + * (mt[mtIndex - 1] ^ (mt[mtIndex - 1] >>> 30)) + + mtIndex); + } + + // This section is translated from the init_by_array code in the C version. + int i = 1; + int j = 0; + for (int k = Math.max(N, seedInts.length); k > 0; k--) + { + mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >>> 30)) * SEED_FACTOR1)) + seedInts[j] + j; + i++; + j++; + if (i >= N) + { + mt[0] = mt[N - 1]; + i = 1; + } + if (j >= seedInts.length) + { + j = 0; + } + } + for (int k = N - 1; k > 0; k--) + { + mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >>> 30)) * SEED_FACTOR2)) - i; + i++; + if (i >= N) + { + mt[0] = mt[N - 1]; + i = 1; + } + } + mt[0] = UPPER_MASK; // Most significant bit is 1 - guarantees non-zero initial array. + } + + + /** + * {@inheritDoc} + */ + public byte[] getSeed() + { + return seed.clone(); + } + + + /** + * {@inheritDoc} + */ + @Override + protected final int next(int bits) + { + int y; + try + { + lock.lock(); + if (mtIndex >= N) // Generate N ints at a time. + { + int kk; + for (kk = 0; kk < N - M; kk++) + { + y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); + mt[kk] = mt[kk + M] ^ (y >>> 1) ^ MAG01[y & 0x1]; + } + for (; kk < N - 1; kk++) + { + y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK); + mt[kk] = mt[kk + (M - N)] ^ (y >>> 1) ^ MAG01[y & 0x1]; + } + y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK); + mt[N - 1] = mt[M - 1] ^ (y >>> 1) ^ MAG01[y & 0x1]; + + mtIndex = 0; + } + + y = mt[mtIndex++]; + } + finally + { + lock.unlock(); + } + // Tempering + y ^= (y >>> 11); + y ^= (y << 7) & GENERATE_MASK1; + y ^= (y << 15) & GENERATE_MASK2; + y ^= (y >>> 18); + + return y >>> (32 - bits); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/PoissonGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/PoissonGenerator.java new file mode 100644 index 00000000..2653b8a0 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/PoissonGenerator.java @@ -0,0 +1,90 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.uncommons.maths.number.ConstantGenerator; +import org.uncommons.maths.number.NumberGenerator; + +/** + * Discrete random sequence that follows a + * <a href="http://en.wikipedia.org/wiki/Poisson_distribution" target="_top">Poisson + * distribution</a>. + * @author Daniel Dyer + */ +public class PoissonGenerator implements NumberGenerator<Integer> +{ + private final Random rng; + private final NumberGenerator<Double> mean; + + + /** + * <p>Creates a generator of Poisson-distributed values. The mean is + * determined by the provided {@link org.uncommons.maths.number.NumberGenerator}. This means that + * the statistical parameters of this generator may change over time. + * One example of where this is useful is if the mean generator is attached + * to a GUI control that allows a user to tweak the parameters while a + * program is running.</p> + * <p>To create a Poisson generator with a constant mean, use the + * {@link #PoissonGenerator(double, Random)} constructor instead.</p> + * @param mean A {@link NumberGenerator} that provides the mean of the + * Poisson distribution used for the next generated value. + * @param rng The source of randomness. + */ + public PoissonGenerator(NumberGenerator<Double> mean, + Random rng) + { + this.mean = mean; + this.rng = rng; + } + + + /** + * Creates a generator of Poisson-distributed values from a distribution + * with the specified mean. + * @param mean The mean of the values generated. + * @param rng The source of randomness. + */ + public PoissonGenerator(double mean, + Random rng) + { + this(new ConstantGenerator<Double>(mean), rng); + if (mean <= 0) + { + throw new IllegalArgumentException("Mean must be a positive value."); + } + } + + + /** + * {@inheritDoc} + */ + public Integer nextValue() + { + int x = 0; + double t = 0.0; + while (true) + { + t -= Math.log(rng.nextDouble()) / mean.nextValue(); + if (t > 1.0) + { + break; + } + ++x; + } + return x; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/Probability.java b/maths/core/src/java/main/org/uncommons/maths/random/Probability.java new file mode 100644 index 00000000..d0074be4 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/Probability.java @@ -0,0 +1,214 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; + +/** + * Immutable value type for probabilities. Forces numeric probabilities to be within the + * range 0..1 inclusive and provides useful utility methods for working with + * probabilities (such as generating an event with a given probability). + * @author Daniel Dyer + * @since 1.2 + */ +public final class Probability extends Number implements Comparable<Probability> +{ + /** + * Convenient constant representing a probability of zero. If an event has + * a probability of zero it will never happen (it is an impossibility). + * @see #ONE + * @see #EVENS + */ + public static final Probability ZERO = new Probability(0); + + /** + * Convenient constant representing a probability of 0.5 (used to model + * an event that has a 50/50 chance of occurring). + * @see #ZERO + * @see #ONE + */ + public static final Probability EVENS = new Probability(0.5d); + + /** + * Convenient constant representing a probability of one. An event with + * a probability of one is a certainty. + * @see #ZERO + * @see #EVENS + */ + public static final Probability ONE = new Probability(1); + + private final double probability; + + /** + * @param probability The probability value (a number in the range 0..1 inclusive). A + * value of zero means that an event is guaranteed not to happen. A value of 1 means + * it is guaranteed to occur. + */ + public Probability(final double probability) + { + if (probability < 0 || probability > 1) + { + throw new IllegalArgumentException("Probability must be in the range 0..1 inclusive."); + } + this.probability = probability; + } + + + /** + * Generates an event according the probability value {@literal p}. + * @param rng A source of randomness for generating events. + * @return True with a probability of {@literal p}, false with a probability of + * {@literal 1 - p}. + */ + public boolean nextEvent(Random rng) + { + // Don't bother generating an random value if the result is guaranteed. + return probability == 1 || rng.nextDouble() < probability; + } + + + /** + * The complement of a probability p is 1 - p. If p = 0.75, then the complement is 0.25. + * @return The complement of this probability. + */ + public Probability getComplement() + { + return new Probability(1 - probability); + } + + + /** + * Converting a fractional probability into an integer is not meaningful since + * all useful information is discarded. For this reason, this method is over-ridden + * to thrown an {@link ArithmeticException}, except when the probability is exactly + * zero or one. + * @throws ArithmeticException Unless the probability is exactly zero or one. + * @return An integer probability. + */ + @Override + public int intValue() + { + if (probability % 1 == 0) + { + return (int) probability; + } + else + { + throw new ArithmeticException("Cannot convert probability to integer due to loss of precision."); + } + } + + + /** + * Converting a fractional probability into an integer is not meaningful since + * all useful information is discarded. For this reason, this method is over-ridden + * to thrown an {@link ArithmeticException}, except when the probability is exactly + * zero or one. + * @throws ArithmeticException Unless the probability is exactly zero or one. + * @return An integer probability. + */ + @Override + public long longValue() + { + return intValue(); + } + + + /** + * Returns the probability value as a float. + * @return A real number between 0 and 1 inclusive. + */ + @Override + public float floatValue() + { + return (float) probability; + } + + + /** + * Returns the probability value as a double. + * @return A real number between 0 and 1 inclusive. + */ + @Override + public double doubleValue() + { + return probability; + } + + + /** + * Determines whether this probability value is equal to some other object. + * To be considered equal the other object must also be a Probability object + * with an indentical probability value. + * @param other The object to compare against. + * @return True if the objects are equal, false otherwise. + */ + @Override + public boolean equals(Object other) + { + if (this == other) + { + return true; + } + if (other == null || getClass() != other.getClass()) + { + return false; + } + + Probability that = (Probability) other; + + return Double.compare(that.probability, probability) == 0; + } + + + /** + * Over-ridden to be consistent with {@link #equals(Object)}. + * @return The hash code value. + */ + @Override + public int hashCode() + { + long temp = probability == 0.0d ? 0L : Double.doubleToLongBits(probability); + return (int) (temp ^ (temp >>> 32)); + } + + + /** + * Compares this value with the specified object for order. Returns a negative + * integer, zero, or a positive integer as this value is less than, equal to, or + * greater than the specified value. + * @param other Another Probability value. + * @return A negative integer, zero, or a positive integer as this value is less + * than, equal to, or greater than the specified value. + */ + public int compareTo(Probability other) + { + return Double.compare(this.probability, other.probability); + } + + + /** + * Formats the probability as a String. This is simply the string representation + * of the double value encapsulated by this probability object. + * @return A string representation of the probability value (a number between + * 0 and 1 inclusive). + */ + @Override + public String toString() + { + return String.valueOf(probability); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/RandomDotOrgSeedGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/RandomDotOrgSeedGenerator.java new file mode 100644 index 00000000..da922bd9 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/RandomDotOrgSeedGenerator.java @@ -0,0 +1,140 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.text.MessageFormat; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Connects to the <a href="http://www.random.org" target="_top">random.org</a> + * website (via HTTPS) and downloads a set of random bits to use as seed data. It + * is generally better to use the {@link DevRandomSeedGenerator} where possible, + * as it should be much quicker. This seed generator is most useful on Microsoft + * Windows and other platforms that do not provide {@literal /dev/random}. + * @author Daniel Dyer + */ +public class RandomDotOrgSeedGenerator implements SeedGenerator +{ + private static final String BASE_URL = "https://www.random.org"; + + /** The URL from which the random bytes are retrieved. */ + private static final String RANDOM_URL = BASE_URL + "/integers/?num={0,number,0}&min=0&max=255&col=1&base=16&format=plain&rnd=new"; + + /** Used to identify the client to the random.org service. */ + private static final String USER_AGENT = RandomDotOrgSeedGenerator.class.getName(); + + /** Random.org does not allow requests for more than 10k integers at once. */ + private static final int MAX_REQUEST_SIZE = 10000; + + private static final Lock cacheLock = new ReentrantLock(); + private static byte[] cache = new byte[1024]; + private static int cacheOffset = cache.length; + + /** + * {@inheritDoc} + */ + public byte[] generateSeed(int length) throws SeedException + { + byte[] seedData = new byte[length]; + try + { + cacheLock.lock(); + int count = 0; + while (count < length) + { + if (cacheOffset < cache.length) + { + int numberOfBytes = Math.min(length - count, cache.length - cacheOffset); + System.arraycopy(cache, cacheOffset, seedData, count, numberOfBytes); + count += numberOfBytes; + cacheOffset += numberOfBytes; + } + else + { + refreshCache(length - count); + } + } + } + catch (IOException ex) + { + throw new SeedException("Failed downloading bytes from " + BASE_URL, ex); + } + catch (SecurityException ex) + { + // Might be thrown if resource access is restricted (such as in an applet sandbox). + throw new SeedException("SecurityManager prevented access to " + BASE_URL, ex); + } + finally + { + cacheLock.unlock(); + } + return seedData; + } + + + /** + * @param requiredBytes The preferred number of bytes to request from random.org. + * The implementation may request more and cache the excess (to avoid making lots + * of small requests). Alternatively, it may request fewer if the required number + * is greater than that permitted by random.org for a single request. + * @throws IOException If there is a problem downloading the random bits. + */ + private void refreshCache(int requiredBytes) throws IOException + { + int numberOfBytes = Math.max(requiredBytes, cache.length); + numberOfBytes = Math.min(numberOfBytes, MAX_REQUEST_SIZE); + if (numberOfBytes != cache.length) + { + cache = new byte[numberOfBytes]; + cacheOffset = numberOfBytes; + } + URL url = new URL(MessageFormat.format(RANDOM_URL, numberOfBytes)); + URLConnection connection = url.openConnection(); + connection.setRequestProperty("User-Agent", USER_AGENT); + BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + + try + { + int index = -1; + for (String line = reader.readLine(); line != null; line = reader.readLine()) + { + cache[++index] = (byte) Integer.parseInt(line, 16); + } + if (index < cache.length - 1) + { + throw new IOException("Insufficient data received."); + } + cacheOffset = 0; + } + finally + { + reader.close(); + } + } + + + @Override + public String toString() + { + return BASE_URL; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/RepeatableRNG.java b/maths/core/src/java/main/org/uncommons/maths/random/RepeatableRNG.java new file mode 100644 index 00000000..feec1305 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/RepeatableRNG.java @@ -0,0 +1,32 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +/** + * Deterministic random number generators are repeatable, which can prove + * useful for testing and validation. This interface defines an operation + * to return the seed data from a repeatable RNG. This seed value can then + * be reused to create a random source with identical output. + * @author Daniel Dyer + */ +public interface RepeatableRNG +{ + /** + * @return The seed data used to initialise this pseudo-random + * number generator. + */ + byte[] getSeed(); +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/SecureRandomSeedGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/SecureRandomSeedGenerator.java new file mode 100644 index 00000000..00c33b03 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/SecureRandomSeedGenerator.java @@ -0,0 +1,52 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.SecureRandom; + +/** + * <p>{@link SeedGenerator} implementation that uses Java's bundled + * {@link SecureRandom} RNG to generate random seed data.</p> + * + * <p>The advantage of using SecureRandom for seeding but not as the + * primary RNG is that we can use it to seed RNGs that are much faster + * than SecureRandom.</p> + * + * <p>This is the only seeding strategy that is guaranteed to work on all + * platforms and therefore is provided as a fall-back option should + * none of the other provided {@link SeedGenerator} implementations be + * useable.</p> + * @author Daniel Dyer + */ +public class SecureRandomSeedGenerator implements SeedGenerator +{ + private static final SecureRandom SOURCE = new SecureRandom(); + + /** + * {@inheritDoc} + */ + public byte[] generateSeed(int length) throws SeedException + { + return SOURCE.generateSeed(length); + } + + + @Override + public String toString() + { + return "java.security.SecureRandom"; + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/SeedException.java b/maths/core/src/java/main/org/uncommons/maths/random/SeedException.java new file mode 100644 index 00000000..42619f32 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/SeedException.java @@ -0,0 +1,42 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +/** + * Exception thrown by {@link SeedGenerator} implementations when + * they are unable to generate a new seed for an RNG. + * @author Daniel Dyer + */ +public class SeedException extends Exception +{ + /** + * @param message Details of the problem. + */ + public SeedException(String message) + { + super(message); + } + + + /** + * @param message Details of the problem. + * @param cause The root cause of the problem. + */ + public SeedException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/SeedGenerator.java b/maths/core/src/java/main/org/uncommons/maths/random/SeedGenerator.java new file mode 100644 index 00000000..707dbb0d --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/SeedGenerator.java @@ -0,0 +1,31 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +/** + * Strategy interface for seeding random number generators. + * @author Daniel Dyer + */ +public interface SeedGenerator +{ + /** + * Generate a seed value for a random number generator. + * @param length The length of the seed to generate (in bytes). + * @return A byte array containing the seed data. + * @throws SeedException If a seed cannot be generated for any reason. + */ + byte[] generateSeed(int length) throws SeedException; +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/XORShiftRNG.java b/maths/core/src/java/main/org/uncommons/maths/random/XORShiftRNG.java new file mode 100644 index 00000000..54569a0a --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/XORShiftRNG.java @@ -0,0 +1,128 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import java.util.concurrent.locks.ReentrantLock; +import org.uncommons.maths.binary.BinaryUtils; + +/** + * <p>Very fast pseudo random number generator. See + * <a href="http://school.anhb.uwa.edu.au/personalpages/kwessen/shared/Marsaglia03.html">this + * page</a> for a description. This RNG has a period of about 2^160, which is not as long + * as the {@link MersenneTwisterRNG} but it is faster.</p> + * + * <p><em>NOTE: Because instances of this class require 160-bit seeds, it is not + * possible to seed this RNG using the {@link #setSeed(long)} method inherited + * from {@link Random}. Calls to this method will have no effect. + * Instead the seed must be set by a constructor.</em></p> + * + * @author Daniel Dyer + * @since 1.2 + */ +public class XORShiftRNG extends Random implements RepeatableRNG +{ + private static final int SEED_SIZE_BYTES = 20; // Needs 5 32-bit integers. + + // Previously used an array for state but using separate fields proved to be + // faster. + private int state1; + private int state2; + private int state3; + private int state4; + private int state5; + + private final byte[] seed; + + + // Lock to prevent concurrent modification of the RNG's internal state. + private final ReentrantLock lock = new ReentrantLock(); + + + /** + * Creates a new RNG and seeds it using the default seeding strategy. + */ + public XORShiftRNG() + { + this(DefaultSeedGenerator.getInstance().generateSeed(SEED_SIZE_BYTES)); + } + + + /** + * Seed the RNG using the provided seed generation strategy. + * @param seedGenerator The seed generation strategy that will provide + * the seed value for this RNG. + * @throws SeedException If there is a problem generating a seed. + */ + public XORShiftRNG(SeedGenerator seedGenerator) throws SeedException + { + this(seedGenerator.generateSeed(SEED_SIZE_BYTES)); + } + + + /** + * Creates an RNG and seeds it with the specified seed data. + * @param seed The seed data used to initialise the RNG. + */ + public XORShiftRNG(byte[] seed) + { + if (seed == null || seed.length != SEED_SIZE_BYTES) + { + throw new IllegalArgumentException("XOR shift RNG requires 160 bits of seed data."); + } + this.seed = seed.clone(); + int[] state = BinaryUtils.convertBytesToInts(seed); + this.state1 = state[0]; + this.state2 = state[1]; + this.state3 = state[2]; + this.state4 = state[3]; + this.state5 = state[4]; + } + + + /** + * {@inheritDoc} + */ + public byte[] getSeed() + { + return seed.clone(); + } + + + /** + * {@inheritDoc} + */ + @Override + protected int next(int bits) + { + try + { + lock.lock(); + int t = (state1 ^ (state1 >> 7)); + state1 = state2; + state2 = state3; + state3 = state4; + state4 = state5; + state5 = (state5 ^ (state5 << 6)) ^ (t ^ (t << 13)); + int value = (state2 + state2 + 1) * state5; + return value >>> (32 - bits); + } + finally + { + lock.unlock(); + } + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/random/package-info.java b/maths/core/src/java/main/org/uncommons/maths/random/package-info.java new file mode 100644 index 00000000..bf522140 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/random/package-info.java @@ -0,0 +1,22 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +/** + * <p>This package provides deterministic, repeatable, pseudo-random number + * generators, a set of strategies for seeding them, and classes for generating + * values from different probability distributions.</p> + * @author Daniel Dyer + */ +package org.uncommons.maths.random; diff --git a/maths/core/src/java/main/org/uncommons/maths/statistics/DataSet.java b/maths/core/src/java/main/org/uncommons/maths/statistics/DataSet.java new file mode 100644 index 00000000..3026b841 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/statistics/DataSet.java @@ -0,0 +1,362 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.statistics; + +import java.util.Arrays; + +/** + * Utility class for calculating statistics for a finite data set. + * @author Daniel Dyer + * @see <a href="http://betterexplained.com/articles/how-to-analyze-data-using-the-average"> + * How To Analyze Data Using the Average</a> + */ +public class DataSet +{ + private static final int DEFAULT_CAPACITY = 50; + private static final double GROWTH_RATE = 1.5d; + + private double[] dataSet; + private int dataSetSize = 0; + + private double total = 0; + private double product = 1; + private double reciprocalSum = 0; + private double minimum = Double.MAX_VALUE; + private double maximum = Double.MIN_VALUE; + + + /** + * Creates an empty data set with a default initial capacity. + */ + public DataSet() + { + this(DEFAULT_CAPACITY); + } + + + /** + * Creates an empty data set with the specified initial capacity. + * @param capacity The initial capacity for the data set (this number + * of values will be able to be added without needing to resize the + * internal data storage). + */ + public DataSet(int capacity) + { + this.dataSet = new double[capacity]; + this.dataSetSize = 0; + } + + + /** + * Creates a data set and populates it with the specified values. + * @param dataSet The values to add to this data set. + */ + public DataSet(double[] dataSet) + { + this.dataSet = dataSet.clone(); + this.dataSetSize = dataSet.length; + for (double value : this.dataSet) + { + updateStatsWithNewValue(value); + } + } + + + /** + * Adds a single value to the data set and updates any + * statistics that are calculated cumulatively. + * @param value The value to add. + */ + public void addValue(double value) + { + if (dataSetSize == dataSet.length) + { + // Increase the capacity of the array. + int newLength = (int) (GROWTH_RATE * dataSetSize); + double[] newDataSet = new double[newLength]; + System.arraycopy(dataSet, 0, newDataSet, 0, dataSetSize); + dataSet = newDataSet; + } + dataSet[dataSetSize] = value; + updateStatsWithNewValue(value); + ++dataSetSize; + } + + + private void updateStatsWithNewValue(double value) + { + total += value; + product *= value; + reciprocalSum += 1 / value; + minimum = Math.min(minimum, value); + maximum = Math.max(maximum, value); + } + + + private void assertNotEmpty() + { + if (getSize() == 0) + { + throw new EmptyDataSetException(); + } + } + + + /** + * Returns the number of values in this data set. + * @return The size of the data set. + */ + public final int getSize() + { + return dataSetSize; + } + + + /** + * @return The smallest value in the data set. + * @throws EmptyDataSetException If the data set is empty. + * @since 1.0.1 + */ + public final double getMinimum() + { + assertNotEmpty(); + return minimum; + } + + + /** + * @return The biggest value in the data set. + * @throws EmptyDataSetException If the data set is empty. + * @since 1.0.1 + */ + public final double getMaximum() + { + assertNotEmpty(); + return maximum; + } + + + /** + * Determines the median value of the data set. + * @return If the number of elements is odd, returns the middle element. + * If the number of elements is even, returns the midpoint of the two + * middle elements. + * @since 1.0.1 + */ + public final double getMedian() + { + assertNotEmpty(); + // Sort the data (take a copy to do this). + double[] dataCopy = new double[getSize()]; + System.arraycopy(dataSet, 0, dataCopy, 0, dataCopy.length); + Arrays.sort(dataCopy); + int midPoint = dataCopy.length / 2; + if (dataCopy.length % 2 != 0) + { + return dataCopy[midPoint]; + } + else + { + return dataCopy[midPoint - 1] + (dataCopy[midPoint] - dataCopy[midPoint - 1]) / 2; + } + } + + + /** + * @return The sum of all values. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getAggregate() + { + assertNotEmpty(); + return total; + } + + + /** + * @return The product of all values. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getProduct() + { + assertNotEmpty(); + return product; + } + + + /** + * The arithemthic mean of an n-element set is the sum of + * all the elements divided by n. The arithmetic mean is often + * referred to simply as the "mean" or "average" of a data set. + * @see #getGeometricMean() + * @return The arithmetic mean of all elements in the data set. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getArithmeticMean() + { + assertNotEmpty(); + return total / dataSetSize; + } + + + + /** + * The geometric mean of an n-element set is the nth-root of + * the product of all the elements. The geometric mean is used + * for finding the average factor (e.g. an average interest rate). + * @see #getArithmeticMean() + * @see #getHarmonicMean() + * @return The geometric mean of all elements in the data set. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getGeometricMean() + { + assertNotEmpty(); + return Math.pow(product, 1.0d / dataSetSize); + } + + + /** + * The harmonic mean of an n-element set is {@literal n} divided by the sum + * of the reciprocals of the values (where the reciprocal of a value + * {@literal x} is 1/x). The harmonic mean is used to calculate an average + * rate (e.g. an average speed). + * @see #getArithmeticMean() + * @see #getGeometricMean() + * @since 1.1 + * @return The harmonic mean of all the elements in the data set. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getHarmonicMean() + { + assertNotEmpty(); + return dataSetSize / reciprocalSum; + } + + + /** + * Calculates the mean absolute deviation of the data set. This + * is the average (absolute) amount that a single value deviates from + * the arithmetic mean. + * @see #getArithmeticMean() + * @see #getVariance() + * @see #getStandardDeviation() + * @return The mean absolute deviation of the data set. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getMeanDeviation() + { + double mean = getArithmeticMean(); + double diffs = 0; + for (int i = 0; i < dataSetSize; i++) + { + diffs += Math.abs(mean - dataSet[i]); + } + return diffs / dataSetSize; + } + + + + /** + * Calculates the variance (a measure of statistical dispersion) + * of the data set. There are different measures of variance + * depending on whether the data set is itself a finite population + * or is a sample from some larger population. For large data sets + * the difference is negligible. This method calculates the + * population variance. + * @see #getSampleVariance() + * @see #getStandardDeviation() + * @see #getMeanDeviation() + * @return The population variance of the data set. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getVariance() + { + return sumSquaredDiffs() / getSize(); + } + + + /** + * Helper method for variance calculations. + * @return The sum of the squares of the differences between + * each value and the arithmetic mean. + * @throws EmptyDataSetException If the data set is empty. + */ + private double sumSquaredDiffs() + { + double mean = getArithmeticMean(); + double squaredDiffs = 0; + for (int i = 0; i < getSize(); i++) + { + double diff = mean - dataSet[i]; + squaredDiffs += (diff * diff); + } + return squaredDiffs; + } + + + /** + * The standard deviation is the square root of the variance. + * This method calculates the population standard deviation as + * opposed to the sample standard deviation. For large data + * sets the difference is negligible. + * @see #getSampleStandardDeviation() + * @see #getVariance() + * @see #getMeanDeviation() + * @return The standard deviation of the population. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getStandardDeviation() + { + return Math.sqrt(getVariance()); + } + + + /** + * Calculates the variance (a measure of statistical dispersion) + * of the data set. There are different measures of variance + * depending on whether the data set is itself a finite population + * or is a sample from some larger population. For large data sets + * the difference is negligible. This method calculates the sample + * variance. + * @see #getVariance() + * @see #getSampleStandardDeviation() + * @see #getMeanDeviation() + * @return The sample variance of the data set. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getSampleVariance() + { + return sumSquaredDiffs() / (getSize() - 1); + } + + + /** + * The sample standard deviation is the square root of the + * sample variance. For large data sets the difference + * between sample standard deviation and population standard + * deviation is negligible. + * @see #getStandardDeviation() + * @see #getSampleVariance() + * @see #getMeanDeviation() + * @return The sample standard deviation of the data set. + * @throws EmptyDataSetException If the data set is empty. + */ + public final double getSampleStandardDeviation() + { + return Math.sqrt(getSampleVariance()); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/statistics/EmptyDataSetException.java b/maths/core/src/java/main/org/uncommons/maths/statistics/EmptyDataSetException.java new file mode 100644 index 00000000..f0a2e77c --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/statistics/EmptyDataSetException.java @@ -0,0 +1,30 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.statistics; + +/** + * Unchecked exception thrown when an attempt is made to obtain + * statistics from a {@link DataSet} that has had no values added + * to it. + * @author Daniel Dyer + */ +public class EmptyDataSetException extends RuntimeException +{ + public EmptyDataSetException() + { + super("No values in data set."); + } +} diff --git a/maths/core/src/java/main/org/uncommons/maths/statistics/package-info.java b/maths/core/src/java/main/org/uncommons/maths/statistics/package-info.java new file mode 100644 index 00000000..bbef6b36 --- /dev/null +++ b/maths/core/src/java/main/org/uncommons/maths/statistics/package-info.java @@ -0,0 +1,20 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +/** + * Utility classes for statistical analysis. + * @author Daniel Dyer + */ +package org.uncommons.maths.statistics; diff --git a/maths/core/src/java/test/org/uncommons/maths/MathsTest.java b/maths/core/src/java/test/org/uncommons/maths/MathsTest.java new file mode 100644 index 00000000..f5c11508 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/MathsTest.java @@ -0,0 +1,213 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths; + +import java.math.BigInteger; +import org.testng.annotations.Test; + +/** + * Unit test for mathematical utility methods. + * @author Daniel Dyer + */ +public class MathsTest +{ + private static final long SIX_FACTORIAL = 720; + private static final long TWENTY_FACTORIAL = 2432902008176640000L; + private static final BigInteger TWENTY_FIVE_FACTORIAL + = BigInteger.valueOf(TWENTY_FACTORIAL).multiply(BigInteger.valueOf(6375600)); + + @Test + public void testFactorial() + { + // Make sure that the correct value (1) is returned for zero + // factorial. + assert Maths.factorial(0) == 1 : "0! should be 1." ; + // Make sure that the correct value (1) is returned for one + // factorial. + assert Maths.factorial(1) == 1 : "1! should be 1." ; + // Make sure that the correct results are returned for other values. + assert Maths.factorial(6) == SIX_FACTORIAL : "6! should be " + SIX_FACTORIAL ; + assert Maths.factorial(20) == TWENTY_FACTORIAL : "20! should be " + TWENTY_FACTORIAL ; + } + + + @Test + public void testBigFactorial() + { + // Make sure that the correct value (1) is returned for zero + // factorial. + assert Maths.bigFactorial(0).equals(BigInteger.ONE) : "0! should be 1." ; + // Make sure that the correct value (1) is returned for one + // factorial. + assert Maths.bigFactorial(1).equals(BigInteger.ONE) : "1! should be 1." ; + // Make sure that the correct results are returned for other values + assert Maths.bigFactorial(6).longValue() == SIX_FACTORIAL : "6! should be " + SIX_FACTORIAL ; + assert Maths.bigFactorial(20).longValue() == TWENTY_FACTORIAL : "20! should be " + TWENTY_FACTORIAL ; + // Make sure that the correct value is returned for factorials + // outside of the range of longs. + assert Maths.bigFactorial(25).equals(TWENTY_FIVE_FACTORIAL) : "25! should be " + TWENTY_FIVE_FACTORIAL; + } + + + /** + * Factorials of negative integers are not supported. This test + * checks that an appropriate exception is thrown. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNegativeFactorial() + { + Maths.factorial(-1); // Should throw an exception. + } + + + /** + * The standard factorial method (the one that uses longs rather than + * BigIntegers) cannot calculate factorials larger than 20!. It should + * throw an exception rather than overflow silently. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testFactorialTooBig() + { + Maths.factorial(21); // Should throw an exception. + } + + + /** + * Factorials of negative integers are not supported. This test + * checks that an appropriate exception is thrown. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNegativeBigFactorial() + { + Maths.bigFactorial(-1); // Should throw an exception. + } + + + @Test + public void testRaiseToPower() + { + assert Maths.raiseToPower(0, 0) == 1 : "Any value raised to the power of zero should equal 1."; + assert Maths.raiseToPower(5, 0) == 1 : "Any value raised to the power of zero should equal 1."; + assert Maths.raiseToPower(123, 1) == 123 : "Any value raised to the power of one should be unchanged."; + assert Maths.raiseToPower(250, 2) == 62500 : "250^2 incorrectly calculated,"; + assert Maths.raiseToPower(2, 10) == 1024 : "2^10 incorrectly calculated."; + // Check values that generate a result outside of the range of an int. + assert Maths.raiseToPower(2, 34) == 17179869184L : "2^34 incorrectly calculated."; + } + + + /** + * Negative powers are not supported by the raiseToPower method. This + * test checks that an appropriate exception is thrown. + */ + @Test(dependsOnMethods = "testRaiseToPower", + expectedExceptions = IllegalArgumentException.class) + public void testNegativePower() + { + Maths.raiseToPower(1, -2); // Should throw an exception. + } + + + @Test + public void testLog() + { + double log = Maths.log(2, 8); + assert Math.round(log) == 3 : "Base-2 logarithm of 8 should be 3, is " + log; + } + + + @Test + public void testApproxEquals() + { + assert Maths.approxEquals(1.1d, 1.2d, 0.1d) : "1.1 and 1.2 should be equal with tolerance of 10%"; + assert !Maths.approxEquals(1.1d, 1.3d, 0.1d) : "1.1 and 1.3 should be unequal with tolerance of 10%"; + } + + + @Test + public void testApproxEqualsToleranceZero() + { + // Zero tolerance should allow exactly equivalent values. + assert Maths.approxEquals(1d, 1d, 0d) : "Identical values should be equal with zero tolerance."; + } + + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testApproxEqualsToleranceTooLow() + { + Maths.approxEquals(0d, 1d, -0.1d); // Tolerance too low (<0), should throw exception. + } + + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testApproxEqualsToleranceTooHigh() + { + Maths.approxEquals(0d, 1d, 1.2d); // Tolerance too high (>1), should throw exception. + } + + + @Test + public void testRestrictRangeValueWithinRange() + { + assert Maths.restrictRange(5, 0, 10) == 5 : "Value should not be altered when it is in-range."; + assert Maths.restrictRange(5L, 0L, 10L) == 5L : "Value should not be altered when it is in-range."; + assert Maths.restrictRange(5d, 0d, 10d) == 5d : "Value should not be altered when it is in-range."; + } + + + @Test + public void testRestrictRangeValueTooLow() + { + assert Maths.restrictRange(-1, 0, 10) == 0 : "Minimum should be returned when value is too low."; + assert Maths.restrictRange(-1L, 0L, 10L) == 0L : "Minimum should be returned when value is too low."; + assert Maths.restrictRange(-0.1d, 0d, 10d) == 0d : "Minimum should be returned when value is too low."; + } + + + @Test + public void testRestrictRangeValueTooHigh() + { + assert Maths.restrictRange(11, 0, 10) == 10 : "Maximum should be returned when value is too high."; + assert Maths.restrictRange(11L, 0L, 10L) == 10L : "Maximum should be returned when value is too high."; + assert Maths.restrictRange(10.1d, 0d, 10d) == 10d : "Maximum should be returned when value is too high."; + } + + + @Test + public void testGreatestCommonDivisor() + { + long gcd = Maths.greatestCommonDivisor(9, 12); + assert gcd == 3 : "GCD should be 3, is " + gcd; + } + + + @Test + public void testNoCommonDivisorGreaterThanOne() + { + long gcd = Maths.greatestCommonDivisor(11, 12); + assert gcd == 1 : "GCD should be 1, is " + gcd; + } + + + @Test + public void testGreatestCommonDivisorNegatives() + { + long gcd = Maths.greatestCommonDivisor(-9, 12); + assert gcd == 3 : "GCD should be 3, is " + gcd; + gcd = Maths.greatestCommonDivisor(10, -12); + assert gcd == 2 : "GCD should be 2, is " + gcd; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/binary/BinaryUtilsTest.java b/maths/core/src/java/test/org/uncommons/maths/binary/BinaryUtilsTest.java new file mode 100644 index 00000000..8fe57d37 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/binary/BinaryUtilsTest.java @@ -0,0 +1,171 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.binary; + +import java.util.Arrays; +import org.testng.annotations.Test; + +/** + * Unit test for binary/hex utility methods. + * @author Daniel Dyer + */ +public class BinaryUtilsTest +{ + @Test + public void testBytesToHexString() + { + byte[] seed = new byte[] {124, 11, 0, -76, -3, 127, -128, -1}; + String expectedHex = "7C0B00B4FD7F80FF"; + String generatedHex = BinaryUtils.convertBytesToHexString(seed); + assert generatedHex.equals(expectedHex) : "Wrong hex string: " + generatedHex; + } + + + @Test + public void testHexStringToBytes() + { + String hex = "7C0B00B4FD7F80FF"; + byte[] expectedData = new byte[] {124, 11, 0, -76, -3, 127, -128, -1}; + byte[] generatedData = BinaryUtils.convertHexStringToBytes(hex); + assert Arrays.equals(generatedData, expectedData) : "Wrong byte array: " + Arrays.toString(generatedData); + } + + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidHexStringLength() + { + // Hex string should have even number of characters (2 per byte), so + // this should throw an exception. + BinaryUtils.convertHexStringToBytes("F2C"); + } + + + /** + * Make sure that the conversion method correctly converts 4 bytes to an + * integer assuming big-endian convention. + */ + @Test + public void testConvertBytesToInt() + { + byte[] bytes = new byte[]{8, 4, 2, 1}; + final int expected = 134480385; + int result = BinaryUtils.convertBytesToInt(bytes, 0); + assert expected == result : "Expected " + expected + ", was " + result; + } + + + /** + * Make sure that the conversion method correctly converts multiples of 4 bytes to an + * array of integers assuming big-endian convention. + */ + @Test + public void testConvertBytesToInts() + { + byte[] bytes = new byte[]{0, 0, 0, 16, 8, 4, 2, 1}; + final int expected1 = 16; + final int expected2 = 134480385; + int[] result = BinaryUtils.convertBytesToInts(bytes); + assert expected1 == result[0] : "Expected first int to be " + expected1 + ", was " + result[0]; + assert expected2 == result[1] : "Expected second int to be " + expected2 + ", was " + result[1]; + } + + + /** + * Make sure that the conversion method throws an exception if the number of bytes is + * not a multiple of 4. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testConvertWrongNumberOfBytesToInts() + { + byte[] bytes = new byte[]{0, 0, 16, 8, 4, 2, 1}; + BinaryUtils.convertBytesToInts(bytes); + } + + + /** + * Make sure that the conversion method correctly converts 8 bytes to a + * long assuming big-endian convention. + */ + @Test + public void testConvertBytesToLong() + { + byte[] bytes = new byte[]{0, 0, 0, 16, 8, 4, 2, 1}; + final long expected = 68853957121L; + long result = BinaryUtils.convertBytesToLong(bytes, 0); + assert expected == result : "Expected " + expected + ", was " + result; + } + + + /** + * Regression test for failure to correctly convert values that contain negative bytes. + */ + @Test + public void testConvertNegativeBytesToLong() + { + byte[] bytes = new byte[]{-121, 30, 107, -100, -76, -8, 53, 81}; + final long expected = -510639L; + long result = BinaryUtils.convertBytesToLong(bytes, 0); + assert expected == result : "Expected " + expected + ", was " + result; + } + + + @Test + public void testConvertFixedPoint() + { + final double value = 0.6875d; + BitString bits = BinaryUtils.convertDoubleToFixedPointBits(value); + assert bits.toString().equals("1011") : "Binary representation should be 1011, is " + bits.toString(); + } + + + /** + * Makes sure that zero is dealt with correctly by the fixed point conversion + * method. + */ + @Test(dependsOnMethods = "testConvertFixedPoint") + public void testConvertFixedPointZero() + { + BitString bits = BinaryUtils.convertDoubleToFixedPointBits(0d); + assert bits.countSetBits() == 0 : "Binary representation should be 0"; + } + + + /** + * An attempt to convert a value of 1 or greater should result in an exception + * since there is no way to represent these values in our fixed point scheme + * (which has no places to the left of the decimal point). Not throwing an + * exception would be a bug. + */ + @Test(dependsOnMethods = "testConvertFixedPoint", + expectedExceptions = IllegalArgumentException.class) + public void testConvertFixedPointTooHigh() + { + BinaryUtils.convertDoubleToFixedPointBits(1d); + } + + + /** + * An attempt to convert a negative value should result in an exception + * since there is no way to represent these values in our fixed point scheme + * (there is no sign bit). Not throwing an exception would be a bug. + */ + @Test(dependsOnMethods = "testConvertFixedPoint", + expectedExceptions = IllegalArgumentException.class) + public void testConvertFixedPointNegative() + { + BinaryUtils.convertDoubleToFixedPointBits(-0.5d); + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/binary/BitStringTest.java b/maths/core/src/java/test/org/uncommons/maths/binary/BitStringTest.java new file mode 100644 index 00000000..f1f52165 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/binary/BitStringTest.java @@ -0,0 +1,327 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.binary; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigInteger; +import java.util.Random; +import org.testng.annotations.Test; + +/** + * Unit test for the {@link BitString} type. + * @author Daniel Dyer + */ +public class BitStringTest +{ + /** + * Check that a bit string is constructed correctly, with + * the correct length and all bits initially set to zero. + */ + @Test + public void testCreateBitString() + { + BitString bitString = new BitString(100); + assert bitString.getLength() == 100 : "BitString created with wrong length: " + bitString.getLength(); + for (int i = 0; i < bitString.getLength(); i++) + { + assert !bitString.getBit(i) : "Bit " + i + " should not be set."; + } + } + + + /** + * Check that a random bit string of the correct length is constructed. + */ + @Test(dependsOnMethods = "testCreateBitString") + public void testCreateRandomBitString() + { + BitString bitString = new BitString(100, new Random()); + assert bitString.getLength() == 100 : "BitString created with wrong length: " + bitString.getLength(); + } + + + /** + * Make sure that bits are set correctly. + */ + @Test(dependsOnMethods = "testCreateBitString") + public void testSetBits() + { + BitString bitString = new BitString(5); + bitString.setBit(1, true); + bitString.setBit(4, true); + // Testing with non-symmetrical string to ensure that there are no endian + // problems. + assert !bitString.getBit(0) : "Bit 0 should not be set."; + assert bitString.getBit(1) : "Bit 1 should be set."; + assert !bitString.getBit(2) : "Bit 2 should not be set."; + assert !bitString.getBit(3) : "Bit 3 should not be set."; + assert bitString.getBit(4) : "Bit 4 should be set."; + // Test unsetting a bit. + bitString.setBit(4, false); + assert !bitString.getBit(4) : "Bit 4 should be unset."; + } + + + /** + * Make sure bit-flipping works as expected. + */ + @Test(dependsOnMethods = "testCreateBitString") + public void testFlipBits() + { + BitString bitString = new BitString(5); + bitString.flipBit(2); + assert bitString.getBit(2) : "Flipping unset bit failed."; + bitString.flipBit(2); + assert !bitString.getBit(2) : "Flipping set bit failed."; + } + + + /** + * Checks that string representations are correctly generated. + */ + @Test(dependsOnMethods = "testSetBits") + public void testToString() + { + BitString bitString = new BitString(10); + bitString.setBit(3, true); + bitString.setBit(7, true); + bitString.setBit(8, true); + String string = bitString.toString(); + // Testing with leading zero to make sure it isn't omitted. + assert string.equals("0110001000") : "Incorrect string representation: " + string; + } + + + /** + * Checks that the String-parsing constructor works correctly. + */ + @Test(dependsOnMethods = "testToString") + public void testParsing() + { + // Use a 33-bit string to check that word boundaries are dealt with correctly. + String fromString = "111010101110101100010100101000101"; + BitString bitString = new BitString(fromString); + String toString = bitString.toString(); + assert toString.equals(fromString) : "Failed parsing: String representations do not match."; + } + + + /** + * Checks that integer conversion is correct. + */ + @Test(dependsOnMethods = "testSetBits") + public void testToNumber() + { + BitString bitString = new BitString(10); + bitString.setBit(0, true); + bitString.setBit(9, true); + BigInteger number = bitString.toNumber(); + assert number.intValue() == 513 : "Incorrect number conversion: " + number.intValue(); + } + + + /** + * Checks that the bit string can correctly count its number of set bits. + */ + @Test(dependsOnMethods = "testSetBits") + public void testCountSetBits() + { + BitString bitString = new BitString(64); + assert bitString.countSetBits() == 0 : "Initial string should have no 1s."; + // The bits to set have been chosen because they deal with boundary cases. + bitString.setBit(0, true); + bitString.setBit(31, true); + bitString.setBit(32, true); + bitString.setBit(33, true); + bitString.setBit(63, true); + int setBits = bitString.countSetBits(); + assert setBits == 5 : "No. set bits should be 5, is " + setBits; + } + + + /** + * Checks that the bit string can correctly count its number of unset bits. + */ + @Test(dependsOnMethods = "testSetBits") + public void testCountUnsetBits() + { + BitString bitString = new BitString(12); + assert bitString.countUnsetBits() == 12 : "Initial string should have no 1s."; + bitString.setBit(0, true); + bitString.setBit(5, true); + bitString.setBit(6, true); + bitString.setBit(9, true); + bitString.setBit(10, true); + int setBits = bitString.countUnsetBits(); + assert setBits == 7 : "No. set bits should be 7, is " + setBits; + } + + + @Test(dependsOnMethods = {"testSetBits", "testFlipBits"}) + public void testClone() + { + BitString bitString = new BitString(10); + bitString.setBit(3, true); + bitString.setBit(7, true); + bitString.setBit(8, true); + BitString clone = bitString.clone(); + // Check the clone is a bit-for-bit duplicate. + for (int i = 0; i < bitString.getLength(); i++) + { + assert bitString.getBit(i) == clone.getBit(i) : "Cloned bit string does not match in position " + i; + } + // Check that clone is distinct from original (i.e. it does not change + // if the original is modified). + assert clone != bitString : "Clone is same object."; + bitString.flipBit(2); + assert !clone.getBit(2) : "Clone is not independent from original."; + } + + + @Test(dependsOnMethods = "testClone") + public void testEquals() + { + BitString bitString = new BitString(10); + bitString.setBit(2, true); + bitString.setBit(5, true); + bitString.setBit(8, true); + assert bitString.equals(bitString) : "Object should always equal itself."; + assert !bitString.equals(null) : "Object should never equal null."; + assert !bitString.equals(new Object()) : "BitString should not equal object of different type."; + + BitString clone = bitString.clone(); + assert clone.equals(bitString) : "Equals comparison failed on equivalent bit strings."; + // Equivalent objects must have the same hash code. + assert bitString.hashCode() == clone.hashCode() : "Hash codes do not match."; + // Changing one of the objects should result in them no longer being considered equal. + clone.flipBit(0); + assert !clone.equals(bitString) : "Equals comparison failed on different bit strings."; + // Bit strings of different lengths but with the same bits set should not be + // considered equal. + BitString shortBitString = new BitString(9); + shortBitString.setBit(2, true); + shortBitString.setBit(5, true); + shortBitString.setBit(8, true); + assert !shortBitString.equals(bitString) : "Equals comparison failed on different length bit strings."; + } + + + /** + * The length of a bit string must be non-negative. If an attempt is made + * to create a bit string with a negative length, an appropriate exception + * must be thrown. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidLength() + { + new BitString(-1); + } + + + /** + * Checks to ensure that invalid characters in a String used to construct + * the bit string results in an appropriate exception. Not throwing an + * exception is an error since bugs in programs that use bit strings will + * be hard to detect. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidChars() + { + new BitString("0010201"); // Invalid. + } + + + /** + * The index of an individual bit must be non-negative. If an attempt is made + * to set a negative bit position, an appropriate exception must be thrown. + */ + @Test(expectedExceptions = IndexOutOfBoundsException.class) + public void testNegativeIndex() + { + BitString bitString = new BitString(1); + bitString.setBit(-1, false); + } + + + /** + * The index of an individual bit must be within the range 0 to length-1. + * If an attempt is made to set a negative bit position, an appropriate + * exception must be thrown. + */ + @Test(expectedExceptions = IndexOutOfBoundsException.class) + public void testIndexTooHigh() + { + BitString bitString = new BitString(1); + bitString.setBit(1, false); + } + + + @Test(dependsOnMethods = "testEquals") + public void testSerialisation() throws IOException, ClassNotFoundException + { + BitString bitString = new BitString("0101010"); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + ObjectOutputStream objectStream = new ObjectOutputStream(byteStream); + objectStream.writeObject(bitString); + objectStream.flush(); + byte[] bytes = byteStream.toByteArray(); + ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes)); + BitString bitString2 = (BitString) inputStream.readObject(); + assert bitString2.equals(bitString) : "Deserialized object is not the same as original."; + } + + + @Test + public void testSwapSubstringWordAligned() + { + // Bit indices are little-endian, so position 0 is the rightmost bit. + BitString ones = new BitString("1111111111111111111111111111111111111111111111111111111111111111"); + BitString zeros = new BitString("0000000000000000000000000000000000000000000000000000000000000000"); + ones.swapSubstring(zeros, 0, 32); + assert ones.toString().equals("1111111111111111111111111111111100000000000000000000000000000000") + : "Substring swap failed: " + ones; + assert zeros.toString().equals("0000000000000000000000000000000011111111111111111111111111111111") + : "Substring swap failed: " + zeros; + } + + + @Test + public void testSwapSubstringNonAlignedStart() + { + // Bit indices are little-endian, so position 0 is the rightmost bit. + BitString ones = new BitString("1111111111111111111111111111111111111111"); + BitString zeros = new BitString("0000000000000000000000000000000000000000"); + ones.swapSubstring(zeros, 2, 30); + assert ones.toString().equals("1111111100000000000000000000000000000011") : "Substring swap failed: " + ones; + assert zeros.toString().equals("0000000011111111111111111111111111111100") : "Substring swap failed: " + zeros; + } + + + @Test + public void testSwapSubstringNonAlignedEnd() + { + // Bit indices are little-endian, so position 0 is the rightmost bit. + BitString ones = new BitString("1111111111"); + BitString zeros = new BitString("0000000000"); + ones.swapSubstring(zeros, 0, 3); + assert ones.toString().equals("1111111000") : "Substring swap failed: " + ones; + assert zeros.toString().equals("0000000111") : "Substring swap failed: " + zeros; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/combinatorics/CombinationGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/combinatorics/CombinationGeneratorTest.java new file mode 100644 index 00000000..ee830343 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/combinatorics/CombinationGeneratorTest.java @@ -0,0 +1,207 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.combinatorics; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import org.testng.annotations.Test; + +/** + * Unit test for the {@link CombinationGenerator} class. + * @author Daniel Dyer + */ +public class CombinationGeneratorTest +{ + private final String[] elements = new String[]{"1", "2", "3"}; + + /** + * This is the main test case and ensures that the combination generator + * produces the correct output. + */ + @Test + public void testCombinations() + { + CombinationGenerator<String> generator = new CombinationGenerator<String>(elements, 2); + assert generator.hasMore() : "Generator should have more combinations available."; + assert generator.getTotalCombinations() == 3 : "Possible combinations should be 3."; + assert generator.getRemainingCombinations() == 3: "Remaining combinations should be 3."; + + String[] combination1 = generator.nextCombinationAsArray(); + assert generator.hasMore() : "Generator should have more combinations available."; + assert combination1.length == 2 : "Combination length should be 2."; + assert generator.getRemainingCombinations() == 2: "Remaining combinations should be 2."; + assert !combination1[0].equals(combination1[1]) : "Combination elements should be different."; + + List<String> combination2 = generator.nextCombinationAsList(); // Use different "next" method to exercise other options. + assert generator.hasMore() : "Generator should have more combinations available."; + assert combination2.size() == 2 : "Combination length should be 2."; + assert generator.getRemainingCombinations() == 1: "Remaining combinations should be 1."; + // Make sure this combination is different from the previous one. + assert !combination2.get(0).equals(combination2.get(1)) : "Combination elements should be different."; + assert !(combination1[0] + combination1[1]).equals(combination2.get(0) + combination2.get(1)) + : "Combination should be different from previous one."; + + List<String> combination3 = new ArrayList<String>(2); + generator.nextCombinationAsList(combination3); // Use different "next" method to exercise other options. + assert !generator.hasMore() : "Generator should have no more combinations available."; + assert combination3.size() == 2 : "Combination length should be 2."; + assert generator.getRemainingCombinations() == 0: "Remaining combinations should be 0."; + // Make sure this combination is different from the others generated. + assert !combination3.get(0).equals(combination3.get(1)) : "Combination elements should be different."; + assert !(combination2.get(0) + combination2.get(1)).equals(combination3.get(0) + combination3.get(1)) + : "Combination should be different from previous one."; + assert !(combination1[0] + combination1[1]).equals(combination3.get(0) + combination3.get(1)) + : "Combination should be different from previous one."; + } + + + /** + * Ensures that the combination generator works correctly with the "for-each" style + * loop. + */ + @Test + public void testIterable() + { + CombinationGenerator<String> generator = new CombinationGenerator<String>(elements, 2); + Set<String> distinctCombinations = new HashSet<String>(); + for (List<String> combination : generator) + { + assert combination.size() == 2 : "Wrong combination length: " + combination.size(); + // Flatten to a single string for easier comparison. + distinctCombinations.add(combination.get(0) + combination.get(1)); + } + assert distinctCombinations.size() == 3 : "Wrong number of combinations: " + distinctCombinations.size(); + } + + + /** + * Ensures that the iterator is read-only. + */ + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testIteratorRemove() + { + CombinationGenerator<String> generator = new CombinationGenerator<String>(elements, 2); + Iterator<List<String>> iterator = generator.iterator(); + iterator.next(); + iterator.remove(); // Should throw UnsupportedOperationException. + } + + + /** + * When generating a combination into an existing array, that + * array must be big enough to hold the combination. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testDestinationArrayTooShort() + { + CombinationGenerator<String> generator = new CombinationGenerator<String>(elements, 2); + generator.nextCombinationAsArray(new String[1]); // Should throw an exception. + } + + + /** + * When generating a combination into an existing array, that array should + * not be bigger than required. Otherwise subtle bugs may occur in programs + * that use the combination generator when the end of the array contains nulls + * or zeros. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testDestinationArrayTooLong() + { + CombinationGenerator<String> generator = new CombinationGenerator<String>(elements, 2); + generator.nextCombinationAsArray(new String[3]); // Should throw an exception. + } + + + /** + * Combinations cannot contain duplicates, therefore the maximum length of a combination + * is the size of the set of elements from which the combination is formed. This test + * ensures that an appropriate exception is thrown if an attempt is made to create a + * generator that does not observe this constraint. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testCombinationLengthTooLong() + { + new CombinationGenerator<String>(Arrays.asList(elements), 4); + } + + + /** + * Zero-length combinations for an emtpy set of elements is not an error. + * It should not return any combinations other than a single empty one. + */ + @Test + public void testZeroLength() + { + CombinationGenerator<String> generator = new CombinationGenerator<String>(Collections.<String>emptyList(), 0); + assert generator.getTotalCombinations() == 1 : "Should be only one combination."; + List<String> combination = generator.nextCombinationAsList(); + assert combination.isEmpty() : "Combination should be zero-length."; + assert !generator.hasMore() : "Should be no more combinations."; + } + + + /** + * If the initial parameters result in more than 2^63 combinations, then an + * exception should be thrown as this is outside the range of a long. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testTooManyCombinations() + { + Integer [] elements = new Integer[100]; + for (int i = 0; i < elements.length; i++) + { + elements[i] = i; + } + new CombinationGenerator<Integer>(elements, 40); // Should throw an exception. + } + + + @Test(dependsOnMethods = "testZeroLength", + expectedExceptions = IllegalStateException.class) + public void testExhaustion() + { + CombinationGenerator<String> generator = new CombinationGenerator<String>(Collections.<String>emptyList(), 0); + generator.nextCombinationAsList(); // First one should succeed. + generator.nextCombinationAsList(); // Second one should throw an exception. + } + + + /** + * If the original set of elements is sorted, then all of the combinations should + * be sorted as well since the algorithm does not re-order them, it just omits some when + * creating each combination. + */ + @Test + public void testOrdering() + { + Integer[] elements = new Integer[]{1, 2, 3, 4, 5, 6}; + CombinationGenerator<Integer> generator = new CombinationGenerator<Integer>(elements, 4); + for (List<Integer> combination : generator) + { + for (int i = 1; i < combination.size(); i++) + { + assert combination.get(i - 1) < combination.get(i) : "Combination is not ordered correctly."; + } + } + } + +} diff --git a/maths/core/src/java/test/org/uncommons/maths/combinatorics/PermutationGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/combinatorics/PermutationGeneratorTest.java new file mode 100644 index 00000000..a2870d41 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/combinatorics/PermutationGeneratorTest.java @@ -0,0 +1,181 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.combinatorics; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import org.testng.annotations.Test; + +/** + * Unit test for the {@link PermutationGenerator} class. + * @author Daniel Dyer + */ +public class PermutationGeneratorTest +{ + private final String[] elements = {"1", "2", "3"}; + + + /** + * This is the main test case and ensures that the permutation generator + * produces the correct output. + */ + @Test + public void testPermutations() + { + PermutationGenerator<String> generator = new PermutationGenerator<String>(elements); + assert generator.getTotalPermutations() == 6 : "Possible permutations should be 6."; + assert generator.getRemainingPermutations() == 6 : "Remaining permutations should be 6."; + + String[] permutation1 = generator.nextPermutationAsArray(); + assert permutation1.length == 3 : "Permutation length should be 3."; + assert generator.getRemainingPermutations() == 5 : "Remaining permutations should be 5."; + assert !permutation1[0].equals(permutation1[1]) : "Permutation elements should be different."; + assert !permutation1[0].equals(permutation1[2]) : "Permutation elements should be different."; + assert !permutation1[1].equals(permutation1[2]) : "Permutation elements should be different."; + + List<String> permutation2 = generator.nextPermutationAsList(); + assert permutation2.size() == 3 : "Permutation length should be 3."; + assert generator.getRemainingPermutations() == 4: "Remaining permutations should be 4."; + // Make sure this combination is different from the previous one. + assert !permutation2.get(0).equals(permutation2.get(1)) : "Permutation elements should be different."; + assert !permutation2.get(0).equals(permutation2.get(2)) : "Permutation elements should be different."; + assert !permutation2.get(1).equals(permutation2.get(2)) : "Permutation elements should be different."; + + String perm1String = permutation1[0] + permutation1[1] + permutation1[2]; + String perm2String = permutation2.get(0) + permutation2.get(1) + permutation2.get(2); + assert !(perm1String).equals(perm2String) : "Permutation should be different from previous one."; + } + + + /** + * Make sure that the permutation generator correctly calculates how many + * permutations are remaining. + */ + @Test + public void testPermutationCount() + { + PermutationGenerator<String> generator = new PermutationGenerator<String>(elements); + assert generator.getTotalPermutations() == 6 : "Possible permutations should be 6."; + assert generator.hasMore() : "Generator should have more permutations available."; + assert generator.getRemainingPermutations() == 6 : "Remaining permutations should be 6."; + // Generate all of the permutations. + List<String> temp = new LinkedList<String>(); + for (int i = 6; i > 0; i--) + { + generator.nextPermutationAsList(temp); + } + assert generator.getTotalPermutations() == 6 : "Total permutations should be unchanged."; + assert generator.getRemainingPermutations() == 0 : "Remaining permutations should be zero."; + assert !generator.hasMore() : "Should be no more permutations."; + } + + + /** + * Ensures that the permutation generator works correctly with the "for-each" style + * loop. + */ + @Test + public void testIterable() + { + PermutationGenerator<String> generator = new PermutationGenerator<String>(elements); + Set<String> distinctPermutations = new HashSet<String>(); + for (List<String> permutation : generator) + { + assert permutation.size() == 3 : "Wrong permutation length: " + permutation.size(); + // Flatten to a single string for easier comparison. + distinctPermutations.add(permutation.get(0) + permutation.get(1) + permutation.get(2)); + } + assert distinctPermutations.size() == 6 : "Wrong number of permutations: " + distinctPermutations.size(); + } + + + /** + * Ensures that the iterator is read-only. + */ + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testIteratorRemove() + { + PermutationGenerator<String> generator = new PermutationGenerator<String>(elements); + Iterator<List<String>> iterator = generator.iterator(); + iterator.next(); + iterator.remove(); // Should throw UnsupportedOperationException. + } + + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testTooManyElements() + { + // 21 elements is too many to be able to process using 64-bit values. + new PermutationGenerator<Integer>(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21}); + // Should throw an IllegalArgumentException. + } + + + /** + * When generating a permutation into an existing array, that + * array must be big enough to hold the permutation. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testDestinationArrayTooShort() + { + PermutationGenerator<String> generator = new PermutationGenerator<String>(elements); + generator.nextPermutationAsArray(new String[2]); // Should throw an exception. + } + + + /** + * When generating a permutation into an existing array, that array should + * not be bigger than required. Otherwise subtle bugs may occur in programs + * that use the permutation generator when the end of the array contains nulls + * or zeros. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testDestinationArrayTooLong() + { + PermutationGenerator<String> generator = new PermutationGenerator<String>(elements); + generator.nextPermutationAsArray(new String[4]); // Should throw an exception. + } + + + /** + * Zero-length permutations for an emtpy set of elements is not an error. + * It should not return any permutations other than a single empty one. + */ + @Test + public void testZeroLength() + { + PermutationGenerator<String> generator = new PermutationGenerator<String>(Collections.<String>emptyList()); + assert generator.getTotalPermutations() == 1 : "Should be only one permutation."; + List<String> permutation = generator.nextPermutationAsList(); + assert permutation.isEmpty() : "Permutation should be zero-length."; + assert !generator.hasMore() : "Should be no more permutations."; + } + + + @Test(dependsOnMethods = "testZeroLength", + expectedExceptions = IllegalStateException.class) + public void testExhaustion() + { + PermutationGenerator<String> generator = new PermutationGenerator<String>(Collections.<String>emptyList()); + generator.nextPermutationAsList(); // First one should succeed. + generator.nextPermutationAsList(); // Second one should throw an exception. + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/number/RationalTest.java b/maths/core/src/java/test/org/uncommons/maths/number/RationalTest.java new file mode 100644 index 00000000..96ec7820 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/number/RationalTest.java @@ -0,0 +1,211 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.number; + +import java.math.BigDecimal; +import org.testng.annotations.Test; + +/** + * Unit test for the {@link Rational} numeric type. + * @author Daniel Dyer + */ +public class RationalTest +{ + @Test + public void testEquality() + { + Rational r1 = new Rational(3, 4); + Rational r2 = new Rational(6, 8); + assert r1.equals(r2) : "Numerically equivalent rationals should be considered equal."; + assert r2.equals(r1) : "Equality must be reflective."; + assert r1.hashCode() == r2.hashCode() : "Equal values must have identical hash codes."; + assert !r1.equals(Double.valueOf(0.75)) : "Objects of different types should not be considered equal."; + + assert !Rational.ONE.equals(Rational.HALF) : "Numerically distinct rationals should not be considered equal."; + assert !Rational.QUARTER.equals(Rational.THREE_QUARTERS) : "Rationals with the same denominator but different numerators should not be considered equal."; + assert Rational.ONE.equals(Rational.ONE) : "Equality must be reflexive."; + assert !Rational.ONE.equals(null) : "No object should be considered equal to null."; + } + + + @Test(dependsOnMethods = "testEquality") + public void testComparisons() + { + Rational r1 = new Rational(3, 4); + Rational r2 = new Rational(3, 4); + Rational r3 = new Rational(9, 10); + assert r1.compareTo(r1) == 0 : "Equality must be reflexive."; + assert r1.compareTo(r2) == 0 : "equals() must be consitent with compareTo()"; + assert r1.compareTo(r3) < 0 : "First argument should be less than second."; + assert r3.compareTo(r1) > 0 : "First argument should be greater than second."; + } + + + /** + * Fractions should always be stored in their simplest form (so 9/12 + * should be converted to 3/4). + */ + @Test + public void testSimplification() + { + Rational rational = new Rational(3, 6); + assert rational.getNumerator() == 1 : "Numerator should be 1, is " + rational.getNumerator(); + assert rational.getDenominator() == 2 : "Denominator should be 2, is " + rational.getDenominator(); + } + + + @Test + public void testMultiply() + { + Rational a = new Rational(2, 3); + Rational b = new Rational(1, 2); + Rational result = a.multiply(b); + assert result.getNumerator() == 1 : "Numerator should be 1, is " + result.getNumerator(); + assert result.getDenominator() == 3 : "Denominator should be 3, is " + result.getDenominator(); + } + + + @Test + public void testDivide() + { + Rational a = new Rational(2, 3); + Rational b = new Rational(1, 2); + Rational result = a.divide(b); + assert result.getNumerator() == 4 : "Numerator should be 4, is " + result.getNumerator(); + assert result.getDenominator() == 3 : "Denominator should be 3, is " + result.getDenominator(); + } + + + @Test + public void testAddSameDenominator() + { + Rational a = new Rational(2, 5); + Rational b = new Rational(1, 5); + Rational result = a.add(b); + assert result.getNumerator() == 3 : "Numerator should be 3, is " + result.getNumerator(); + assert result.getDenominator() == 5 : "Denominator should be 5, is " + result.getDenominator(); + } + + + @Test + public void testAddDifferentDenominators() + { + Rational a = new Rational(1, 3); + Rational b = new Rational(1, 6); + Rational result = a.add(b); + assert result.getNumerator() == 1 : "Numerator should be 1, is " + result.getNumerator(); + assert result.getDenominator() == 2 : "Denominator should be 2, is " + result.getDenominator(); + } + + + @Test + public void testSubtractSameDenominator() + { + Rational a = new Rational(3, 5); + Rational b = new Rational(1, 5); + Rational result = a.subtract(b); + assert result.getNumerator() == 2 : "Numerator should be 2, is " + result.getNumerator(); + assert result.getDenominator() == 5 : "Denominator should be 5, is " + result.getDenominator(); + } + + + @Test + public void testSubtractDifferentDenominators() + { + Rational a = new Rational(11, 20); + Rational b = new Rational(1, 7); + Rational result = a.subtract(b); + assert result.getNumerator() == 57 : "Numerator should be 57, is " + result.getNumerator(); + assert result.getDenominator() == 140 : "Denominator should be 140, is " + result.getDenominator(); + } + + + @Test + public void testToStringProperFraction() + { + String string = new Rational(4, 5).toString(); + assert string.equals("4/5") : "String value should be \"4/5\", is \"" + string + "\""; + } + + + @Test + public void testToStringTopHeavyFraction() + { + String string = new Rational(3, 2).toString(); + assert string.equals("3/2") : "String value should be \"3/2\", is \"" + string + "\""; + } + + + @Test + public void testToStringInteger() + { + String string = new Rational(8, 2).toString(); + assert string.equals("4") : "String value should be \"4\", is \"" + string + "\""; + } + + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testZeroDenominator() + { + new Rational(2, 0); // Division by zero not permitted, should throw IllegalArgumentException. + } + + + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNegativeDenominator() + { + new Rational(1, -2); // Negative denominator not permitted, should throw IllegalArgumentException. + } + + + /** + * Tests that a rational is correctly constructed from a BigDecimal value. + */ + @Test + public void testBigDecimalConversion() + { + Rational rational = new Rational(new BigDecimal("1.2")); + assert rational.getNumerator() == 6 : "Numerator should be 6, is " + rational.getNumerator(); + assert rational.getDenominator() == 5 : "Denominator should be 5, is " + rational.getDenominator(); + } + + + @Test + public void testBigDecimalConversionExcessPrecision() + { + Rational rational = new Rational(new BigDecimal("1.20000000000000000000000000")); + assert rational.getNumerator() == 6 : "Numerator should be 6, is " + rational.getNumerator(); + assert rational.getDenominator() == 5 : "Denominator should be 5, is " + rational.getDenominator(); + } + + + @Test + public void testIntValue() + { + Rational rational = new Rational(5, 2); + int intValue = rational.intValue(); + assert intValue == 2 : "Integer value should be 2, is " + intValue; + } + + + @Test + public void testFloatValue() + { + Rational rational = new Rational(5, 2); + float floatValue = rational.floatValue(); + assert floatValue == 2.5f : "Floating point value should be 2.5, is " + floatValue; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/AESCounterRNGTest.java b/maths/core/src/java/test/org/uncommons/maths/random/AESCounterRNGTest.java new file mode 100644 index 00000000..82c0770d --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/AESCounterRNGTest.java @@ -0,0 +1,101 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.GeneralSecurityException; +import org.testng.Reporter; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; + +/** + * Unit test for the AES RNG. + * @author Daniel Dyer + */ +public class AESCounterRNGTest +{ + /** + * Test to ensure that two distinct RNGs with the same seed return the + * same sequence of numbers. + */ + @Test + public void testRepeatability() throws GeneralSecurityException + { + AESCounterRNG rng = new AESCounterRNG(); + // Create second RNG using same seed. + AESCounterRNG duplicateRNG = new AESCounterRNG(rng.getSeed()); + assert RNGTestUtils.testEquivalence(rng, duplicateRNG, 1000) : "Generated sequences do not match."; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testDistribution() throws GeneralSecurityException, SeedException + { + AESCounterRNG rng = new AESCounterRNG(DefaultSeedGenerator.getInstance()); + double pi = RNGTestUtils.calculateMonteCarloValueForPi(rng, 100000); + Reporter.log("Monte Carlo value for Pi: " + pi); + assert Maths.approxEquals(pi, Math.PI, 0.01) : "Monte Carlo value for Pi is outside acceptable range:" + pi; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testStandardDeviation() throws GeneralSecurityException + { + AESCounterRNG rng = new AESCounterRNG(); + // Expected standard deviation for a uniformly distributed population of values in the range 0..n + // approaches n/sqrt(12). + int n = 100; + double observedSD = RNGTestUtils.calculateSampleStandardDeviation(rng, n, 10000); + double expectedSD = 100 / Math.sqrt(12); + Reporter.log("Expected SD: " + expectedSD + ", observed SD: " + observedSD); + assert Maths.approxEquals(observedSD, expectedSD, 0.02) : "Standard deviation is outside acceptable range: " + observedSD; + } + + + @Test(expectedExceptions = GeneralSecurityException.class) + public void testSeedTooShort() throws GeneralSecurityException + { + new AESCounterRNG(new byte[]{1, 2, 3}); // Should throw an exception. + } + + + @Test(expectedExceptions = GeneralSecurityException.class) + public void testSeedTooLong() throws GeneralSecurityException + { + new AESCounterRNG(40); // Should throw an exception. + } + + + /** + * RNG must not accept a null seed otherwise it will not be properly initialised. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullSeed() throws GeneralSecurityException + { + new AESCounterRNG((byte[]) null); // Should throw an exception. + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/BinomialGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/BinomialGeneratorTest.java new file mode 100644 index 00000000..687f4444 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/BinomialGeneratorTest.java @@ -0,0 +1,131 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; +import org.uncommons.maths.number.AdjustableNumberGenerator; +import org.uncommons.maths.number.NumberGenerator; +import org.uncommons.maths.statistics.DataSet; + +/** + * Unit test for the Binomial number generator. + * @author Daniel Dyer + */ +public class BinomialGeneratorTest +{ + private final Random rng = new MersenneTwisterRNG(); + + /** + * Check that the observed mean and standard deviation are consistent + * with the specified distribution parameters. + */ + @Test(groups = "non-deterministic") + public void testDistribution() + { + final int n = 20; + final double p = 0.163d; + NumberGenerator<Integer> generator = new BinomialGenerator(n, // Number of trials. + p, // Probability of success in each. + rng); + checkDistribution(generator, n, p); + } + + + @Test(groups = "non-deterministic") + public void testDynamicParameters() + { + final int initialN = 20; + final double initialP = 0.163d; + AdjustableNumberGenerator<Integer> nGenerator = new AdjustableNumberGenerator<Integer>(initialN); + AdjustableNumberGenerator<Double> pGenerator = new AdjustableNumberGenerator<Double>(initialP); + NumberGenerator<Integer> generator = new BinomialGenerator(nGenerator, + pGenerator, + rng); + checkDistribution(generator, initialN, initialP); + + // Adjust parameters and ensure that the generator output conforms to this new distribution. + final int adjustedN = 14; + nGenerator.setValue(adjustedN); + final double adjustedP = 0.32d; + pGenerator.setValue(adjustedP); + + checkDistribution(generator, adjustedN, adjustedP); + } + + + /** + * The probability of succes in any single trial is invalid if it is greater than 1. + * Further, it needs to be less than 1 to be useful. This test ensures that an + * appropriate exception is thrown if the probability is greater than or equal to 1. + * Not throwing an exception is an error because it permits undetected bugs in + * programs that use {@link BinomialGenerator}. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testProbabilityTooHigh() + { + new BinomialGenerator(5, 1d, rng); + } + + + /** + * The probability of succes in any single trial must be greater than zero to + * be useful. This test ensures that an appropriate exception is thrown if the + * probability is not positive. Not throwing an exception is an error because it + * permits undetected bugs in programs that use {@link BinomialGenerator}. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testProbabilityTooLow() + { + new BinomialGenerator(5, 0d, rng); + } + + + /** + * The number of trials must be greater than zero to be useful. This test ensures + * that an appropriate exception is thrown if the number is not positive. Not + * throwing an exception is an error because it permits undetected bugs in + * programs that use {@link BinomialGenerator}. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testTrialsTooLow() + { + new BinomialGenerator(0, 0.5d, rng); + } + + + private void checkDistribution(NumberGenerator<Integer> generator, + int n, + double p) + { + final double expectedMean = n * p; + final double expectedStandardDeviation = Math.sqrt(n * p * (1 - p)); + + final int iterations = 10000; + DataSet data = new DataSet(iterations); + for (int i = 0; i < iterations; i++) + { + int value = generator.nextValue(); + assert value >= 0 && value <= n : "Value out-of-range: " + value; + data.addValue(value); + } + assert Maths.approxEquals(data.getArithmeticMean(), expectedMean, 0.02d) + : "Observed mean outside acceptable range: " + data.getArithmeticMean(); + assert Maths.approxEquals(data.getSampleStandardDeviation(), expectedStandardDeviation, 0.02) + : "Observed standard deviation outside acceptable range: " + data.getSampleStandardDeviation(); + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/CMWC4096RNGTest.java b/maths/core/src/java/test/org/uncommons/maths/random/CMWC4096RNGTest.java new file mode 100644 index 00000000..6881a901 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/CMWC4096RNGTest.java @@ -0,0 +1,124 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.GeneralSecurityException; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.ByteArrayInputStream; +import org.testng.Reporter; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; + +/** + * Unit test for the Complementary Multiply With Carry (CMWC) RNG. + * @author Daniel Dyer + */ +public class CMWC4096RNGTest +{ + private final SeedGenerator seedGenerator = new RandomDotOrgSeedGenerator(); + + /** + * Test to ensure that two distinct RNGs with the same seed return the + * same sequence of numbers. + */ + @Test + public void testRepeatability() throws SeedException + { + CMWC4096RNG rng = new CMWC4096RNG(seedGenerator); + // Create second RNG using same seed. + CMWC4096RNG duplicateRNG = new CMWC4096RNG(rng.getSeed()); + assert RNGTestUtils.testEquivalence(rng, duplicateRNG, 1000) : "Generated sequences do not match."; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testDistribution() throws SeedException + { + CMWC4096RNG rng = new CMWC4096RNG(seedGenerator); + double pi = RNGTestUtils.calculateMonteCarloValueForPi(rng, 100000); + Reporter.log("Monte Carlo value for Pi: " + pi); + assert Maths.approxEquals(pi, Math.PI, 0.01) : "Monte Carlo value for Pi is outside acceptable range:" + pi; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testStandardDeviation() throws SeedException + { + CMWC4096RNG rng = new CMWC4096RNG(seedGenerator); + // Expected standard deviation for a uniformly distributed population of values in the range 0..n + // approaches n/sqrt(12). + int n = 100; + double observedSD = RNGTestUtils.calculateSampleStandardDeviation(rng, n, 10000); + double expectedSD = n / Math.sqrt(12); + Reporter.log("Expected SD: " + expectedSD + ", observed SD: " + observedSD); + assert Maths.approxEquals(observedSD, expectedSD, 0.02) : "Standard deviation is outside acceptable range: " + observedSD; + } + + + /** + * Make sure that the RNG does not accept seeds that are too small since + * this could affect the distribution of the output. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidSeedSize() + { + new CMWC4096RNG(new byte[]{1, 2, 3}); // Not enough bytes, should cause an IllegalArgumentException. + } + + + /** + * RNG must not accept a null seed otherwise it will not be properly initialised. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullSeed() throws GeneralSecurityException + { + new CMWC4096RNG((byte[]) null); + } + + + @Test + public void testSerializable() throws IOException, ClassNotFoundException + { + // Serialise an RNG. + CMWC4096RNG rng = new CMWC4096RNG(); + ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutStream = new ObjectOutputStream(byteOutStream); + objectOutStream.writeObject(rng); + + // Read the RNG back-in. + ObjectInputStream objectInStream = new ObjectInputStream(new ByteArrayInputStream(byteOutStream.toByteArray())); + CMWC4096RNG rng2 = (CMWC4096RNG) objectInStream.readObject(); + assert rng != rng2 : "Deserialised RNG should be distinct object."; + + // Both RNGs should generate the same sequence. + assert RNGTestUtils.testEquivalence(rng, rng2, 20) : "Output mismatch after serialisation."; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/CellularAutomatonRNGTest.java b/maths/core/src/java/test/org/uncommons/maths/random/CellularAutomatonRNGTest.java new file mode 100644 index 00000000..a0ef480d --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/CellularAutomatonRNGTest.java @@ -0,0 +1,122 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.GeneralSecurityException; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.ByteArrayInputStream; +import org.testng.Reporter; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; + +/** + * Unit test for the cellular automaton RNG. + * @author Daniel Dyer + */ +public class CellularAutomatonRNGTest +{ + /** + * Test to ensure that two distinct RNGs with the same seed return the + * same sequence of numbers. + */ + @Test + public void testRepeatability() + { + CellularAutomatonRNG rng = new CellularAutomatonRNG(); + // Create second RNG using same seed. + CellularAutomatonRNG duplicateRNG = new CellularAutomatonRNG(rng.getSeed()); + assert RNGTestUtils.testEquivalence(rng, duplicateRNG, 1000) : "Generated sequences do not match."; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testDistribution() throws SeedException + { + CellularAutomatonRNG rng = new CellularAutomatonRNG(DefaultSeedGenerator.getInstance()); + double pi = RNGTestUtils.calculateMonteCarloValueForPi(rng, 100000); + Reporter.log("Monte Carlo value for Pi: " + pi); + assert Maths.approxEquals(pi, Math.PI, 0.01) : "Monte Carlo value for Pi is outside acceptable range:" + pi; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testStandardDeviation() + { + CellularAutomatonRNG rng = new CellularAutomatonRNG(); + // Expected standard deviation for a uniformly distributed population of values in the range 0..n + // approaches n/sqrt(12). + int n = 100; + double observedSD = RNGTestUtils.calculateSampleStandardDeviation(rng, n, 10000); + double expectedSD = n / Math.sqrt(12); + Reporter.log("Expected SD: " + expectedSD + ", observed SD: " + observedSD); + assert Maths.approxEquals(observedSD, expectedSD, 0.02) : "Standard deviation is outside acceptable range: " + observedSD; + } + + + /** + * Make sure that the RNG does not accept seeds that are too small since + * this could affect the distribution of the output. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidSeedSize() + { + new CellularAutomatonRNG(new byte[]{1, 2, 3}); // One byte too few, should cause an IllegalArgumentException. + } + + + /** + * RNG must not accept a null seed otherwise it will not be properly initialised. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullSeed() throws GeneralSecurityException + { + new CellularAutomatonRNG((byte[]) null); + } + + + @Test + public void testSerializable() throws IOException, ClassNotFoundException + { + // Serialise an RNG. + CellularAutomatonRNG rng = new CellularAutomatonRNG(); + ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutStream = new ObjectOutputStream(byteOutStream); + objectOutStream.writeObject(rng); + + // Read the RNG back-in. + ObjectInputStream objectInStream = new ObjectInputStream(new ByteArrayInputStream(byteOutStream.toByteArray())); + CellularAutomatonRNG rng2 = (CellularAutomatonRNG) objectInStream.readObject(); + assert rng != rng2 : "Deserialised RNG should be distinct object."; + + // Both RNGs should generate the same sequence. + assert RNGTestUtils.testEquivalence(rng, rng2, 20) : "Output mismatch after serialisation."; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/ContinuousUniformGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/ContinuousUniformGeneratorTest.java new file mode 100644 index 00000000..5e0111d6 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/ContinuousUniformGeneratorTest.java @@ -0,0 +1,63 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; +import org.uncommons.maths.number.NumberGenerator; +import org.uncommons.maths.statistics.DataSet; + +/** + * Unit test for the uniform floating point number generator. + * @author Daniel Dyer + */ +public class ContinuousUniformGeneratorTest +{ + /** + * Check that the observed mean and standard deviation are consistent + * with the specified distribution parameters. + */ + @Test(groups = "non-deterministic") + public void testDistribution() + { + final double min = 150; + final double max = 500; + final double range = max - min; + final double expectedMean = (range / 2) + min; + // Expected standard deviation for a uniformly distributed population of values in the range 0..n + // approaches n/sqrt(12). + final double standardDeviation = range / Math.sqrt(12); + NumberGenerator<Double> generator = new ContinuousUniformGenerator(min, + max, + new MersenneTwisterRNG()); + final int iterations = 10000; + DataSet data = new DataSet(iterations); + for (int i = 0; i < iterations; i++) + { + double value = generator.nextValue(); + assert value >= min && value <= max : "Value out-out-of-range: " + value; + data.addValue(value); + } + assert Maths.approxEquals(data.getArithmeticMean(), expectedMean, 0.02) + : "Observed mean outside acceptable range: " + data.getArithmeticMean(); + assert Maths.approxEquals(data.getSampleStandardDeviation(), standardDeviation, 0.02) + : "Observed standard deviation outside acceptable range: " + data.getSampleStandardDeviation(); + // Expected median is the same as expected mean. + assert Maths.approxEquals(data.getMedian(), expectedMean, 0.02) + : "Observed mean outside acceptable range: " + data.getMedian(); + } + +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/DefaultSeedGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/DefaultSeedGeneratorTest.java new file mode 100644 index 00000000..84118cc2 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/DefaultSeedGeneratorTest.java @@ -0,0 +1,82 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.Permission; +import org.testng.annotations.Test; + +/** + * Unit test for {@link DefaultSeedGenerator}. + * @author Daniel Dyer + */ +public class DefaultSeedGeneratorTest +{ + /** + * Check that the default seed generator gracefully falls + * back to an alternative generation strategy when the security + * manager prevents it from using its first choice. + */ + @Test + public void testRestrictedEnvironment() + { + SecurityManager securityManager = System.getSecurityManager(); + try + { + // Don't allow file system or network access. + System.setSecurityManager(new RestrictedSecurityManager()); + DefaultSeedGenerator.getInstance().generateSeed(4); + // Should get to here without exceptions. + } + finally + { + // Restore the original security manager so that we don't + // interfere with the running of other tests. + System.setSecurityManager(securityManager); + } + } + + + /** + * This security manager allows everything except for some operations that are + * explicitly blocked. These operations are accessing /dev/random and opening + * a socket connection. + */ + private static final class RestrictedSecurityManager extends SecurityManager + { + @Override + public void checkRead(String file) + { + if (file.equals("/dev/random")) + { + throw new SecurityException("Test not permitted to access /dev/random"); + } + } + + + @Override + public void checkConnect(String host, int port) + { + throw new SecurityException("Test not permitted to connect to " + host + ":" + port); + } + + + @Override + public void checkPermission(Permission permission) + { + // Allow everything. + } + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/DevRandomSeedGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/DevRandomSeedGeneratorTest.java new file mode 100644 index 00000000..1b4b3ba6 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/DevRandomSeedGeneratorTest.java @@ -0,0 +1,46 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.io.File; +import org.testng.Reporter; +import org.testng.annotations.Test; + +/** + * Unit test for the seed generator that reads data from /dev/random (on + * platforms that provide it). + * @author Daniel Dyer + */ +public class DevRandomSeedGeneratorTest +{ + @Test + public void testGenerator() + { + SeedGenerator generator = new DevRandomSeedGenerator(); + try + { + byte[] seed = generator.generateSeed(32); + assert seed.length == 32 : "Failed to generate seed of correct length"; + } + catch (SeedException ex) + { + // This exception is OK, but only if we are running on a platform that + // does not provide /dev/random. + assert !new File("/dev/random").exists() : "Seed generator failed even though /dev/random exists."; + Reporter.log("/dev/random does not exist on this platform."); + } + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/DiehardInputGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/DiehardInputGeneratorTest.java new file mode 100644 index 00000000..df7c064f --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/DiehardInputGeneratorTest.java @@ -0,0 +1,47 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.io.File; +import org.testng.annotations.Test; + +/** + * Unit test for the {@link DiehardInputGenerator} class. + * @author Daniel Dyer + */ +public class DiehardInputGeneratorTest +{ + /** + * Make sure that the input file is created and that it is the correct size. + */ + @Test + public void testFileCreation() throws Exception + { + File tempFile = File.createTempFile("diehard-input", null); + try + { + DiehardInputGenerator.main(new String[]{"java.util.Random", tempFile.getAbsolutePath()}); + assert tempFile.length() == 12000000 : "Generated file should be 12Mb, is " + tempFile.length() + " bytes."; + } + finally + { + if (!tempFile.delete()) + { + tempFile.deleteOnExit(); + } + } + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/DiscreteUniformGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/DiscreteUniformGeneratorTest.java new file mode 100644 index 00000000..c2ceccdb --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/DiscreteUniformGeneratorTest.java @@ -0,0 +1,63 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; +import org.uncommons.maths.number.NumberGenerator; +import org.uncommons.maths.statistics.DataSet; + +/** + * Unit test for the uniform integer number generator. + * @author Daniel Dyer + */ +public class DiscreteUniformGeneratorTest +{ + /** + * Check that the observed mean and standard deviation are consistent + * with the specified distribution parameters. + */ + @Test(groups = "non-deterministic") + public void testDistribution() + { + final int min = 150; + final int max = 500; + final int range = max - min; + final double expectedMean = (range / 2) + min; + // Expected standard deviation for a uniformly distributed population of values in the range 0..n + // approaches n/sqrt(12). + final double standardDeviation = range / Math.sqrt(12); + NumberGenerator<Integer> generator = new DiscreteUniformGenerator(min, + max, + new MersenneTwisterRNG()); + final int iterations = 10000; + DataSet data = new DataSet(iterations); + for (int i = 0; i < iterations; i++) + { + int value = generator.nextValue(); + assert value >= min && value <= max : "Value out-of-range: " + value; + data.addValue(value); + } + assert Maths.approxEquals(data.getArithmeticMean(), expectedMean, 0.02) + : "Observed mean outside acceptable range: " + data.getArithmeticMean(); + assert Maths.approxEquals(data.getSampleStandardDeviation(), standardDeviation, 0.02) + : "Observed standard deviation outside acceptable range: " + data.getSampleStandardDeviation(); + // Expected median is the same as expected mean. + assert Maths.approxEquals(data.getMedian(), expectedMean, 0.02) + : "Observed mean outside acceptable range: " + data.getMedian(); + } + +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/ExponentialGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/ExponentialGeneratorTest.java new file mode 100644 index 00000000..3dd1d3d6 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/ExponentialGeneratorTest.java @@ -0,0 +1,83 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; +import org.uncommons.maths.number.AdjustableNumberGenerator; +import org.uncommons.maths.number.NumberGenerator; +import org.uncommons.maths.statistics.DataSet; + +/** + * Unit test for the exponential random generator. + * @author Daniel Dyer + */ +public class ExponentialGeneratorTest +{ + private final Random rng = new MersenneTwisterRNG(); + + @Test(groups = "non-deterministic") + public void testDistribution() + { + final double rate = 3.2d; + NumberGenerator<Double> generator = new ExponentialGenerator(rate, rng); + checkDistribution(generator, rate); + } + + + @Test(groups = "non-deterministic") + public void testDynamicParameters() + { + final double initialRate = 0.75d; + AdjustableNumberGenerator<Double> rateGenerator = new AdjustableNumberGenerator<Double>(initialRate); + NumberGenerator<Double> generator = new ExponentialGenerator(rateGenerator, rng); + checkDistribution(generator, initialRate); + + // Adjust parameters and ensure that the generator output conforms to this + // new distribution. + final double adjustedRate = 1.05d; + rateGenerator.setValue(adjustedRate); + + checkDistribution(generator, adjustedRate); + } + + + + private void checkDistribution(NumberGenerator<Double> generator, + double rate) + { + final double expectedMean = 1 / rate; + final double expectedStandardDeviation = Math.sqrt(1 / (rate * rate)); + final double expectedMedian = Math.log(2) / rate; + + final int iterations = 10000; + DataSet data = new DataSet(iterations); + for (int i = 0; i < iterations; i++) + { + data.addValue(generator.nextValue()); + } + // Exponential distribution appears to be a bit more volatile than the others in + // terms of conforming to expectations, so use a 4% tolerance here, instead of the 2% + // used for other distributions, to avoid too many false positives. + assert Maths.approxEquals(data.getArithmeticMean(), expectedMean, 0.04d) + : "Observed mean outside acceptable range: " + data.getArithmeticMean(); + assert Maths.approxEquals(data.getSampleStandardDeviation(), expectedStandardDeviation, 0.04d) + : "Observed standard deviation outside acceptable range: " + data.getSampleStandardDeviation(); + assert Maths.approxEquals(data.getMedian(), expectedMedian, 0.04d) + : "Observed median outside acceptable range: " + data.getMedian(); + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/GaussianGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/GaussianGeneratorTest.java new file mode 100644 index 00000000..61d0c23d --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/GaussianGeneratorTest.java @@ -0,0 +1,91 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; +import org.uncommons.maths.number.AdjustableNumberGenerator; +import org.uncommons.maths.number.NumberGenerator; +import org.uncommons.maths.statistics.DataSet; + +/** + * Unit test for the normally-distributed number generator. + * @author Daniel Dyer + */ +public class GaussianGeneratorTest +{ + private final Random rng = new MersenneTwisterRNG(); + + + /** + * Check that the observed mean and standard deviation are consistent + * with the specified distribution parameters. + */ + @Test(groups = "non-deterministic") + public void testDistribution() + { + final double mean = 147; + final double standardDeviation = 17; + NumberGenerator<Double> generator = new GaussianGenerator(mean, + standardDeviation, + rng); + checkDistribution(generator, mean, standardDeviation); + } + + + @Test(groups = "non-deterministic") + public void testDynamicParameters() + { + final double initialMean = 147; + final double initialStandardDeviation = 17; + AdjustableNumberGenerator<Double> meanGenerator = new AdjustableNumberGenerator<Double>(initialMean); + AdjustableNumberGenerator<Double> standardDeviationGenerator = new AdjustableNumberGenerator<Double>(initialStandardDeviation); + NumberGenerator<Double> generator = new GaussianGenerator(meanGenerator, + standardDeviationGenerator, + rng); + checkDistribution(generator, initialMean, initialStandardDeviation); + + // Adjust parameters and ensure that the generator output conforms to this new + // distribution. + final double adjustedMean = 73; + final double adjustedStandardDeviation = 9; + meanGenerator.setValue(adjustedMean); + standardDeviationGenerator.setValue(adjustedStandardDeviation); + + checkDistribution(generator, adjustedMean, adjustedStandardDeviation); + } + + + private void checkDistribution(NumberGenerator<Double> generator, + double expectedMean, + double expectedStandardDeviation) + { + final int iterations = 10000; + DataSet data = new DataSet(iterations); + for (int i = 0; i < iterations; i++) + { + data.addValue(generator.nextValue()); + } + assert Maths.approxEquals(data.getArithmeticMean(), expectedMean, 0.02) + : "Observed mean outside acceptable range: " + data.getArithmeticMean(); + assert Maths.approxEquals(data.getSampleStandardDeviation(), expectedStandardDeviation, 0.02) + : "Observed standard deviation outside acceptable range: " + data.getSampleStandardDeviation(); + // Expected median is the same as expected mean. + assert Maths.approxEquals(data.getMedian(), expectedMean, 0.02) + : "Observed median outside acceptable range: " + data.getMedian(); + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/JavaRNGTest.java b/maths/core/src/java/test/org/uncommons/maths/random/JavaRNGTest.java new file mode 100644 index 00000000..74d1e4ce --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/JavaRNGTest.java @@ -0,0 +1,70 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.GeneralSecurityException; +import org.testng.annotations.Test; + +/** + * Unit test for the JDK RNG. + * @author Daniel Dyer + */ +public class JavaRNGTest +{ + /** + * Test to ensure that two distinct RNGs with the same seed return the + * same sequence of numbers. + */ + @Test + public void testRepeatability() + { + // Create an RNG using the default seeding strategy. + JavaRNG rng = new JavaRNG(); + // Create second RNG using same seed. + JavaRNG duplicateRNG = new JavaRNG(rng.getSeed()); + assert RNGTestUtils.testEquivalence(rng, duplicateRNG, 1000) : "Generated sequences do not match."; + } + + + /** + * Make sure that the RNG does not accept seeds that are too small since + * this could affect the distribution of the output. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidSeedSize() + { + new JavaRNG(new byte[]{1, 2, 3, 4, 5, 6, 7}); // One byte too few, should cause an IllegalArgumentException. + } + + + /** + * RNG must not accept a null seed otherwise it will not be properly initialised. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullSeed() throws GeneralSecurityException, SeedException + { + new JavaRNG(new SeedGenerator() + { + public byte[] generateSeed(int length) + { + return null; + } + }); + } + + + // Don't bother testing the distribution of the output for this RNG, it's beyond our control. +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/MersenneTwisterRNGTest.java b/maths/core/src/java/test/org/uncommons/maths/random/MersenneTwisterRNGTest.java new file mode 100644 index 00000000..d7544412 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/MersenneTwisterRNGTest.java @@ -0,0 +1,122 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.GeneralSecurityException; +import org.testng.Reporter; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; + +/** + * Unit test for the cellular automaton RNG. + * @author Daniel Dyer + */ +public class MersenneTwisterRNGTest +{ + /** + * Test to ensure that two distinct RNGs with the same seed return the + * same sequence of numbers. + */ + @Test + public void testRepeatability() + { + MersenneTwisterRNG rng = new MersenneTwisterRNG(); + // Create second RNG using same seed. + MersenneTwisterRNG duplicateRNG = new MersenneTwisterRNG(rng.getSeed()); + assert RNGTestUtils.testEquivalence(rng, duplicateRNG, 1000) : "Generated sequences do not match."; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testDistribution() throws SeedException + { + MersenneTwisterRNG rng = new MersenneTwisterRNG(DefaultSeedGenerator.getInstance()); + double pi = RNGTestUtils.calculateMonteCarloValueForPi(rng, 100000); + Reporter.log("Monte Carlo value for Pi: " + pi); + assert Maths.approxEquals(pi, Math.PI, 0.01) : "Monte Carlo value for Pi is outside acceptable range: " + pi; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testStandardDeviation() + { + MersenneTwisterRNG rng = new MersenneTwisterRNG(); + // Expected standard deviation for a uniformly distributed population of values in the range 0..n + // approaches n/sqrt(12). + int n = 100; + double observedSD = RNGTestUtils.calculateSampleStandardDeviation(rng, n, 10000); + double expectedSD = 100 / Math.sqrt(12); + Reporter.log("Expected SD: " + expectedSD + ", observed SD: " + observedSD); + assert Maths.approxEquals(observedSD, expectedSD, 0.02) : "Standard deviation is outside acceptable range: " + observedSD; + } + + + /** + * Make sure that the RNG does not accept seeds that are too small since + * this could affect the distribution of the output. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidSeedSize() + { + new MersenneTwisterRNG(new byte[]{1, 2, 3, 4, 5, 6, 7, 8}); // Need 16 bytes, should cause an IllegalArgumentException. + } + + + /** + * RNG must not accept a null seed otherwise it will not be properly initialised. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullSeed() throws GeneralSecurityException + { + new MersenneTwisterRNG((byte[]) null); + } + + + @Test + public void testSerializable() throws IOException, ClassNotFoundException + { + // Serialise an RNG. + MersenneTwisterRNG rng = new MersenneTwisterRNG(); + ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutStream = new ObjectOutputStream(byteOutStream); + objectOutStream.writeObject(rng); + + // Read the RNG back-in. + ObjectInputStream objectInStream = new ObjectInputStream(new ByteArrayInputStream(byteOutStream.toByteArray())); + MersenneTwisterRNG rng2 = (MersenneTwisterRNG) objectInStream.readObject(); + assert rng != rng2 : "Deserialised RNG should be distinct object."; + + // Both RNGs should generate the same sequence. + assert RNGTestUtils.testEquivalence(rng, rng2, 20) : "Output mismatch after serialisation."; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/PoissonGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/PoissonGeneratorTest.java new file mode 100644 index 00000000..3931bffc --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/PoissonGeneratorTest.java @@ -0,0 +1,98 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; +import org.uncommons.maths.number.AdjustableNumberGenerator; +import org.uncommons.maths.number.NumberGenerator; +import org.uncommons.maths.statistics.DataSet; + +/** + * Unit test for the Poisson number generator. + * @author Daniel Dyer + */ +public class PoissonGeneratorTest +{ + private final Random rng = new MersenneTwisterRNG(); + + + /** + * Check that the observed mean and standard deviation are consistent + * with the specified distribution parameters. + */ + @Test(groups = "non-deterministic") + public void testDistribution() + { + final double mean = 19; + NumberGenerator<Integer> generator = new PoissonGenerator(mean, rng); + checkDistribution(generator, mean); + } + + + @Test(groups = "non-deterministic") + public void testDynamicParameters() + { + final double initialMean = 19; + AdjustableNumberGenerator<Double> meanGenerator = new AdjustableNumberGenerator<Double>(initialMean); + NumberGenerator<Integer> generator = new PoissonGenerator(meanGenerator, + rng); + checkDistribution(generator, initialMean); + + // Adjust parameters and ensure that the generator output conforms to this new + // distribution. + final double adjustedMean = 13; + meanGenerator.setValue(adjustedMean); + + checkDistribution(generator, adjustedMean); + } + + + + /** + * The mean must be greater than zero to be useful. This test ensures + * that an appropriate exception is thrown if the mean is not positive. Not + * throwing an exception is an error because it permits undetected bugs in + * programs that use {@link PoissonGenerator}. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testMeanTooLow() + { + new PoissonGenerator(0d, rng); + } + + + private void checkDistribution(NumberGenerator<Integer> generator, + double expectedMean) + { + // Variance of a Possion distribution equals its mean. + final double expectedStandardDeviation = Math.sqrt(expectedMean); + + final int iterations = 10000; + DataSet data = new DataSet(iterations); + for (int i = 0; i < iterations; i++) + { + int value = generator.nextValue(); + assert value >= 0 : "Value must be non-negative: " + value; + data.addValue(value); + } + assert Maths.approxEquals(data.getArithmeticMean(), expectedMean, 0.02) + : "Observed mean outside acceptable range: " + data.getArithmeticMean(); + assert Maths.approxEquals(data.getSampleStandardDeviation(), expectedStandardDeviation, 0.02) + : "Observed standard deviation outside acceptable range: " + data.getSampleStandardDeviation(); + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/ProbabilityTest.java b/maths/core/src/java/test/org/uncommons/maths/random/ProbabilityTest.java new file mode 100644 index 00000000..37db9500 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/ProbabilityTest.java @@ -0,0 +1,169 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.testng.Reporter; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; + +/** + * Unit test for {@link Probability} value type. + * @author Daniel Dyer + */ +public class ProbabilityTest +{ + private final Random rng = new MersenneTwisterRNG(); + + /** + * Negative probabilities are invalid. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testProbabilityTooLow() + { + new Probability(-0.01); // Should throw an IllegalArgumentException. + } + + + /** + * Probabilities greater than one are invalid. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testProbabilityTooHigh() + { + new Probability(1.01); // Should throw an IllegalArgumentException. + } + + + @Test + public void testZeroIntegerConversion() + { + assert Probability.ZERO.longValue() == 0 : "Invalid integer conversion of zero probability."; + } + + + @Test + public void testOneIntegerConversion() + { + assert Probability.ONE.longValue() == 1 : "Invalid integer conversion of one probability."; + } + + + @Test(expectedExceptions = ArithmeticException.class) + public void testFractionalIntegerConversion() + { + Probability.EVENS.longValue(); // Should throw an ArithmeticException. + } + + + @Test + public void testEquality() + { + Probability p1 = new Probability(0.75d); + Probability p2 = new Probability(0.75d); + assert p1.equals(p2) : "Numerically equivalent probabilities should be considered equal."; + assert p2.equals(p1) : "Equality must be reflective."; + assert p1.hashCode() == p2.hashCode() : "Equal probabilities must have identical hash codes."; + assert !p1.equals(Double.valueOf(0.75)) : "Objects of different types should not be considered equal."; + + assert !Probability.ONE.equals(Probability.EVENS) : "Numerically distinct probabilities should not be considered equal."; + assert Probability.ONE.equals(Probability.ONE) : "Equality must be reflexive."; + assert !Probability.ONE.equals(null) : "No object should be considered equal to null."; + } + + + @Test(dependsOnMethods = "testEquality") + public void testComparisons() + { + Probability p1 = new Probability(0.75d); + Probability p2 = new Probability(0.75d); + Probability p3 = new Probability(0.9d); + assert p1.compareTo(p1) == 0 : "Equality must be reflexive."; + assert p1.compareTo(p2) == 0 : "equals() must be consitent with compareTo()"; + assert p1.compareTo(p3) < 0 : "First argument should be less than second."; + assert p3.compareTo(p1) > 0 : "First argument should be greater than second."; + } + + + @Test(dependsOnMethods = "testEquality") + public void testIntegerComplements() + { + Probability complement = Probability.ZERO.getComplement(); + assert complement.equals(Probability.ONE) : "Incorrect complement for zero: " + complement; + assert complement.getComplement().equals(Probability.ZERO) : "Complement must be symmetrical."; + } + + + @Test(dependsOnMethods = "testEquality") + public void testFractionalComplements() + { + Probability probability = new Probability(0.75d); + Probability complement = probability.getComplement(); + assert complement.equals(new Probability(0.25d)) : "Incorrect complement: " + complement; + assert complement.getComplement().equals(probability) : "Complement must be symmetrical."; + } + + + /** + * If the probability is zero, the {@link Probability#nextEvent(java.util.Random)} method + * should never return true. + */ + @Test + public void testImpossibleEvents() + { + for (int i = 0; i < 1000; i++) + { + assert !Probability.ZERO.nextEvent(rng) : "Impossible event occurred."; + } + } + + + /** + * If the probability is one, the {@link Probability#nextEvent(java.util.Random)} method + * should always return true. + */ + @Test + public void testCertainties() + { + for (int i = 0; i < 1000; i++) + { + assert Probability.ONE.nextEvent(rng) : "Certainty failed to happen."; + } + } + + + /** + * Check that the observed event outcomes are in line with what we would expect. + */ + @Test + public void testPossibleEvents() + { + final int iterations = 1000; + int count = 0; + for (int i = 0; i < iterations; i++) + { + if (Probability.EVENS.nextEvent(rng)) + { + ++count; + } + } + double observedProbability = (double) count / iterations; + // If we get between 450 and 550 successful outcomes (the expected 500 +/- 10%), + // we will assume that that the distribution is correct. + Reporter.log("Observed probability: " + observedProbability); + assert Maths.approxEquals(observedProbability, 0.5d, 0.1d) : "Observed probability outside tolerance."; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/RNGBenchmark.java b/maths/core/src/java/test/org/uncommons/maths/random/RNGBenchmark.java new file mode 100644 index 00000000..545e1b8b --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/RNGBenchmark.java @@ -0,0 +1,71 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.Random; + +/** + * Performs timings on the various random number generator (RNG) implementations + * available for use. This class does not perform any + * statistical tests on the output of the RNGs, it simply measures throughput. + * @author Daniel Dyer + */ +public class RNGBenchmark implements Runnable +{ + private static final int ITERATIONS = 1000000; + + private final Random rng; + private final int iterations; + + public RNGBenchmark(Random rng, + int iterations) + { + this.rng = rng; + this.iterations = iterations; + } + + + public void run() + { + System.out.println("Testing " + rng.getClass().getName() + "..."); + long startTime = System.currentTimeMillis(); + for (int i = 1; i <= iterations; i++) + { + rng.nextInt(i); + rng.nextDouble(); + rng.nextGaussian(); + } + long elapsedTime = System.currentTimeMillis() - startTime; + double seconds = ((double) elapsedTime) / 1000; + System.out.println("Completed " + iterations + " iterations in " + seconds + " seconds.\n"); + } + + + public static void main(String[] args) throws GeneralSecurityException + { + System.out.println("------------------------------------------------------------"); + new RNGBenchmark(new JavaRNG(), ITERATIONS).run(); + new RNGBenchmark(new SecureRandom(), ITERATIONS).run(); + new RNGBenchmark(new AESCounterRNG(), ITERATIONS).run(); + new RNGBenchmark(new MersenneTwisterRNG(), ITERATIONS).run(); + new RNGBenchmark(new CellularAutomatonRNG(), ITERATIONS).run(); + new RNGBenchmark(new CMWC4096RNG(), ITERATIONS).run(); + new RNGBenchmark(new XORShiftRNG(), ITERATIONS).run(); + System.out.println("------------------------------------------------------------"); + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/RNGTestUtils.java b/maths/core/src/java/test/org/uncommons/maths/random/RNGTestUtils.java new file mode 100644 index 00000000..3aee0a54 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/RNGTestUtils.java @@ -0,0 +1,130 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.util.Random; +import org.uncommons.maths.statistics.DataSet; + +/** + * Provides methods used for testing the operation of RNG implementations. + * @author Daniel Dyer + */ +final class RNGTestUtils +{ + private RNGTestUtils() + { + // Prevents instantiation of utility class. + } + + /** + * Test to ensure that two distinct RNGs with the same seed return the + * same sequence of numbers. + * @param rng1 The first RNG. Its output is compared to that of {@code rng2}. + * @param rng2 The second RNG. Its output is compared to that of {@code rng1}. + * @param iterations The number of values to generate from each RNG and + * compare. + * @return true if the two RNGs produce the same sequence of values, false + * otherwise. + */ + public static boolean testEquivalence(Random rng1, + Random rng2, + int iterations) + { + for (int i = 0; i < iterations; i++) + { + if (rng1.nextInt() != rng2.nextInt()) + { + return false; + } + } + return true; + } + + + /** + * This is a rudimentary check to ensure that the output of a given RNG + * is approximately uniformly distributed. If the RNG output is not + * uniformly distributed, this method will return a poor estimate for the + * value of pi. + * @param rng The RNG to test. + * @param iterations The number of random points to generate for use in the + * calculation. This value needs to be sufficiently large in order to + * produce a reasonably accurate result (assuming the RNG is uniform). + * Less than 10,000 is not particularly useful. 100,000 should be sufficient. + * @return An approximation of pi generated using the provided RNG. + */ + public static double calculateMonteCarloValueForPi(Random rng, + int iterations) + { + // Assumes a quadrant of a circle of radius 1, bounded by a box with + // sides of length 1. The area of the square is therefore 1 square unit + // and the area of the quadrant is (pi * r^2) / 4. + int totalInsideQuadrant = 0; + // Generate the specified number of random points and count how many fall + // within the quadrant and how many do not. We expect the number of points + // in the quadrant (expressed as a fraction of the total number of points) + // to be pi/4. Therefore pi = 4 * ratio. + for (int i = 0; i < iterations; i++) + { + double x = rng.nextDouble(); + double y = rng.nextDouble(); + if (isInQuadrant(x, y)) + { + ++totalInsideQuadrant; + } + } + // From these figures we can deduce an approximate value for Pi. + return 4 * ((double) totalInsideQuadrant / iterations); + } + + + /** + * Uses Pythagoras' theorem to determine whether the specified coordinates + * fall within the area of the quadrant of a circle of radius 1 that is + * centered on the origin. + * @param x The x-coordinate of the point (must be between 0 and 1). + * @param y The y-coordinate of the point (must be between 0 and 1). + * @return True if the point is within the quadrant, false otherwise. + */ + private static boolean isInQuadrant(double x, double y) + { + double distance = Math.sqrt((x * x) + (y * y)); + return distance <= 1; + } + + + /** + * Generates a sequence of values from a given random number generator and + * then calculates the standard deviation of the sample. + * @param rng The RNG to use. + * @param maxValue The maximum value for generated integers (values will be + * in the range [0, maxValue)). + * @param iterations The number of values to generate and use in the standard + * deviation calculation. + * @return The standard deviation of the generated sample. + */ + public static double calculateSampleStandardDeviation(Random rng, + int maxValue, + int iterations) + { + DataSet dataSet = new DataSet(iterations); + for (int i = 0; i < iterations; i++) + { + dataSet.addValue(rng.nextInt(maxValue)); + } + return dataSet.getSampleStandardDeviation(); + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/RandomDotOrgSeedGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/RandomDotOrgSeedGeneratorTest.java new file mode 100644 index 00000000..721973aa --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/RandomDotOrgSeedGeneratorTest.java @@ -0,0 +1,48 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import org.testng.annotations.Test; + +/** + * Unit test for the seed generator that connects to random.org to get seed + * data. + * @author Daniel Dyer + */ +public class RandomDotOrgSeedGeneratorTest +{ + @Test + public void testGenerator() throws SeedException + { + SeedGenerator generator = new RandomDotOrgSeedGenerator(); + byte[] seed = generator.generateSeed(32); + assert seed.length == 32 : "Failed to generate seed of correct length"; + } + + + /** + * Try to acquire a large number of bytes, more than are cached internally + * by the seed generator implementation. + */ + @Test + public void testLargeRequest() throws SeedException + { + SeedGenerator generator = new RandomDotOrgSeedGenerator(); + // 1024 bytes are cached internally, so request more than that. + byte[] seed = generator.generateSeed(2560); + assert seed.length == 2560 : "Failed to generate seed of correct length"; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/SecureRandomSeedGeneratorTest.java b/maths/core/src/java/test/org/uncommons/maths/random/SecureRandomSeedGeneratorTest.java new file mode 100644 index 00000000..e5a1c489 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/SecureRandomSeedGeneratorTest.java @@ -0,0 +1,34 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import org.testng.annotations.Test; + +/** + * Unit test for the seed generator that uses {@link java.security.SecureRandom} + * to produce seed data. + * @author Daniel Dyer + */ +public class SecureRandomSeedGeneratorTest +{ + @Test + public void testGenerator() throws SeedException + { + SeedGenerator generator = new SecureRandomSeedGenerator(); + byte[] seed = generator.generateSeed(32); + assert seed.length == 32 : "Failed to generate seed of correct length"; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/random/XORShiftRNGTest.java b/maths/core/src/java/test/org/uncommons/maths/random/XORShiftRNGTest.java new file mode 100644 index 00000000..a754562d --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/random/XORShiftRNGTest.java @@ -0,0 +1,124 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.random; + +import java.security.GeneralSecurityException; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectInputStream; +import java.io.ByteArrayInputStream; +import org.testng.Reporter; +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; + +/** + * Unit test for the cellular automaton RNG. + * @author Daniel Dyer + */ +public class XORShiftRNGTest +{ + /** + * Test to ensure that two distinct RNGs with the same seed return the + * same sequence of numbers. This method must be run before any of the + * other tests otherwise the state of the RNG will not be the same in the + * duplicate RNG. + */ + @Test + public void testRepeatability() + { + XORShiftRNG rng = new XORShiftRNG(); + // Create second RNG using same seed. + XORShiftRNG duplicateRNG = new XORShiftRNG(rng.getSeed()); + assert RNGTestUtils.testEquivalence(rng, duplicateRNG, 1000) : "Generated sequences do not match."; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testDistribution() throws SeedException + { + XORShiftRNG rng = new XORShiftRNG(DefaultSeedGenerator.getInstance()); + double pi = RNGTestUtils.calculateMonteCarloValueForPi(rng, 100000); + Reporter.log("Monte Carlo value for Pi: " + pi); + assert Maths.approxEquals(pi, Math.PI, 0.01) : "Monte Carlo value for Pi is outside acceptable range:" + pi; + } + + + /** + * Test to ensure that the output from the RNG is broadly as expected. This will not + * detect the subtle statistical anomalies that would be picked up by Diehard, but it + * provides a simple check for major problems with the output. + */ + @Test(groups = "non-deterministic", + dependsOnMethods = "testRepeatability") + public void testStandardDeviation() + { + XORShiftRNG rng = new XORShiftRNG(); + // Expected standard deviation for a uniformly distributed population of values in the range 0..n + // approaches n/sqrt(12). + int n = 100; + double observedSD = RNGTestUtils.calculateSampleStandardDeviation(rng, n, 10000); + double expectedSD = n / Math.sqrt(12); + Reporter.log("Expected SD: " + expectedSD + ", observed SD: " + observedSD); + assert Maths.approxEquals(observedSD, expectedSD, 0.02) : "Standard deviation is outside acceptable range: " + observedSD; + } + + + /** + * Make sure that the RNG does not accept seeds that are too small since + * this could affect the distribution of the output. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testInvalidSeedSize() + { + new XORShiftRNG(new byte[]{1, 2, 3}); // Not enough bytes, should cause an IllegalArgumentException. + } + + + /** + * RNG must not accept a null seed otherwise it will not be properly initialised. + */ + @Test(expectedExceptions = IllegalArgumentException.class) + public void testNullSeed() throws GeneralSecurityException + { + new XORShiftRNG((byte[]) null); + } + + + @Test + public void testSerializable() throws IOException, ClassNotFoundException + { + // Serialise an RNG. + XORShiftRNG rng = new XORShiftRNG(); + ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutStream = new ObjectOutputStream(byteOutStream); + objectOutStream.writeObject(rng); + + // Read the RNG back-in. + ObjectInputStream objectInStream = new ObjectInputStream(new ByteArrayInputStream(byteOutStream.toByteArray())); + XORShiftRNG rng2 = (XORShiftRNG) objectInStream.readObject(); + assert rng != rng2 : "Deserialised RNG should be distinct object."; + + // Both RNGs should generate the same sequence. + assert RNGTestUtils.testEquivalence(rng, rng2, 20) : "Output mismatch after serialisation."; + } +} diff --git a/maths/core/src/java/test/org/uncommons/maths/statistics/DataSetTest.java b/maths/core/src/java/test/org/uncommons/maths/statistics/DataSetTest.java new file mode 100644 index 00000000..220f5b41 --- /dev/null +++ b/maths/core/src/java/test/org/uncommons/maths/statistics/DataSetTest.java @@ -0,0 +1,194 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.statistics; + +import org.testng.annotations.Test; +import org.uncommons.maths.Maths; + +/** + * Unit test for statistics class. + * @author Daniel Dyer + */ +public class DataSetTest +{ + private static final double[] DATA_SET = new double[]{1, 2, 3, 4, 5}; + + /** + * Make sure that the data set's capacity grows correctly as + * more values are added. + */ + @Test + public void testCapacityIncrease() + { + DataSet data = new DataSet(3); + assert data.getSize() == 0 : "Initial size should be 0."; + data.addValue(1); + data.addValue(2); + data.addValue(3); + assert data.getSize() == 3 : "Size should be 3."; + // Add a value to take the size beyond the initial capacity. + data.addValue(4); + assert data.getSize() == 4 : "Size should be 4."; + } + + + @Test + public void testAggregate() + { + DataSet data = new DataSet(DATA_SET); + assert Math.round(data.getAggregate()) == 15 + : "Incorrect aggregate: " + data.getAggregate(); + } + + + @Test + public void testProduct() + { + DataSet data = new DataSet(DATA_SET); + long product = Maths.factorial(5); + assert Math.round(data.getProduct()) == product + : "Incorrect product: " + data.getProduct(); + } + + + @Test + public void testMinimum() + { + DataSet data = new DataSet(); + data.addValue(4); + assert data.getMinimum() == 4 : "Minimum should be 4, is " + data.getMinimum(); + data.addValue(7); + assert data.getMinimum() == 4 : "Minimum should be 4, is " + data.getMinimum(); + data.addValue(2); + assert data.getMinimum() == 2 : "Minimum should be 2, is " + data.getMinimum(); + data.addValue(-9); + assert data.getMinimum() == -9 : "Minimum should be -9, is " + data.getMinimum(); + } + + + @Test + public void testMaximum() + { + DataSet data = new DataSet(); + data.addValue(9); + assert data.getMaximum() == 9 : "Maximum should be 9, is " + data.getMaximum(); + data.addValue(8); + assert data.getMaximum() == 9 : "Maximum should be 9, is " + data.getMaximum(); + data.addValue(-15); + assert data.getMaximum() == 9 : "Maximum should be 9, is " + data.getMaximum(); + data.addValue(12); + assert data.getMaximum() == 12 : "Maximum should be 12, is " + data.getMaximum(); + } + + + @Test + public void testMedian() + { + DataSet data = new DataSet(); + data.addValue(15); + assert data.getMedian() == 15 : "Median should be 15, is " + data.getMedian(); + data.addValue(17); + assert Math.round(data.getMedian()) == 16 : "Median should be 16, is " + data.getMedian(); + data.addValue(102); + assert Math.round(data.getMedian()) == 17 : "Median should be 17, is " + data.getMedian(); + } + + + @Test + public void testArithmeticMean() + { + DataSet data = new DataSet(DATA_SET); + assert Math.round(data.getArithmeticMean()) == 3 + : "Incorrect average: " + data.getArithmeticMean(); + } + + + @Test + public void testGeometricMean() + { + DataSet data = new DataSet(DATA_SET); + long product = Maths.factorial(5); + assert data.getGeometricMean() == Math.pow(product, 0.2d) + : "Incorrect geometric mean: " + data.getGeometricMean(); + } + + + @Test + public void testHarmonicMean() + { + DataSet data = new DataSet(new double[]{1, 2, 4, 4}); + // Reciprocals are 1, 1/2, 1/4 and 1/4. + // Sum of reciprocals is 2. Therefore, harmonic mean is 4/2 = 2. + assert data.getHarmonicMean() == 2d : "Incorrect harmonic mean: " + data.getHarmonicMean(); + } + + + @Test + public void testMeanDeviation() + { + DataSet data = new DataSet(DATA_SET); + assert data.getMeanDeviation() == 1.2d + : "Incorrect mean deviation: " + data.getMeanDeviation(); + } + + + @Test + public void testPopulationVariance() + { + DataSet data = new DataSet(DATA_SET); + assert Math.round(data.getVariance()) == 2 + : "Incorrect population variance: " + data.getVariance(); + } + + + @Test + public void testSampleVariance() + { + DataSet data = new DataSet(DATA_SET); + assert data.getSampleVariance() == 2.5d + : "Incorrect sample variance: " + data.getSampleVariance(); + } + + + @Test + public void testPopulationStandardDeviation() + { + DataSet data = new DataSet(DATA_SET); + assert data.getStandardDeviation() == Math.sqrt(2d) + : "Incorrect sample variance: " + data.getStandardDeviation(); + } + + + @Test + public void testSampleStandardDeviation() + { + DataSet data = new DataSet(DATA_SET); + assert data.getSampleStandardDeviation() == Math.sqrt(2.5d) + : "Incorrect sample variance: " + data.getSampleStandardDeviation(); + } + + + /** + * Check that an appropriate exception is thrown when attempting to + * calculate stats without any data. + */ + @Test(expectedExceptions = EmptyDataSetException.class) + public void testEmptyDataSet() + { + DataSet data = new DataSet(); + data.getArithmeticMean(); // Should throw EmptyDataSetException. + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/BinomialDistribution.java b/maths/demo/src/java/main/org/uncommons/maths/demo/BinomialDistribution.java new file mode 100644 index 00000000..5f064e0d --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/BinomialDistribution.java @@ -0,0 +1,108 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.uncommons.maths.Maths; +import org.uncommons.maths.random.BinomialGenerator; + +/** + * @author Daniel Dyer + */ +class BinomialDistribution extends ProbabilityDistribution +{ + private final int n; + private final double p; + + + public BinomialDistribution(int n, double p) + { + this.n = n; + this.p = p; + } + + + public Map<Double, Double> getExpectedValues() + { + Map<Double, Double> values = new HashMap<Double, Double>(); + for (int i = 0; i <= n; i++) + { + values.put((double) i, getExpectedProbability(i)); + } + return values; + } + + + /** + * This is the probability mass function + * (http://en.wikipedia.org/wiki/Probability_mass_function) of + * the Binomial distribution represented by this number generator. + * @param successes The number of successful trials to determine + * the probability for. + * @return The probability of obtaining the specified number of + * successful trials given the current values of n and p. + */ + private double getExpectedProbability(int successes) + { + double prob = Math.pow(p, successes) * Math.pow(1 - p, n - successes); + BigDecimal coefficient = new BigDecimal(binomialCoefficient(n, successes)); + return coefficient.multiply(new BigDecimal(prob)).doubleValue(); + } + + + private BigInteger binomialCoefficient(int n, int k) + { + BigInteger nFactorial = Maths.bigFactorial(n); + BigInteger kFactorial = Maths.bigFactorial(k); + BigInteger nMinusKFactorial = Maths.bigFactorial(n - k); + BigInteger divisor = kFactorial.multiply(nMinusKFactorial); + return nFactorial.divide(divisor); + } + + + protected BinomialGenerator createValueGenerator(Random rng) + { + return new BinomialGenerator(n, p, rng); + } + + + public double getExpectedMean() + { + return n * p; + } + + + public double getExpectedStandardDeviation() + { + return Math.sqrt(n * p * (1 - p)); + } + + + public String getDescription() + { + return "Binomial Distribution (n = " + n + ", p = " + p + ")"; + } + + + public boolean isDiscrete() + { + return true; + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/BinomialParametersPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/BinomialParametersPanel.java new file mode 100644 index 00000000..92ba99be --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/BinomialParametersPanel.java @@ -0,0 +1,51 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.SpringLayout; +import org.uncommons.swing.SpringUtilities; + +/** + * @author Daniel Dyer + */ +class BinomialParametersPanel extends ParametersPanel +{ + private final SpinnerNumberModel trialsNumberModel = new SpinnerNumberModel(50, 1, 100, 1); + private final SpinnerNumberModel probabilityNumberModel = new SpinnerNumberModel(0.5d, 0.0d, 1.0d, 0.01d); + + public BinomialParametersPanel() + { + JPanel wrapper = new JPanel(new SpringLayout()); + wrapper.add(new JLabel("No. Trials: ")); + wrapper.add(new JSpinner(trialsNumberModel)); + wrapper.add(new JLabel("Probability: ")); + wrapper.add(new JSpinner(probabilityNumberModel)); + SpringUtilities.makeCompactGrid(wrapper, 4, 1, 6, 6, 6, 6); + add(wrapper, BorderLayout.NORTH); + } + + + public BinomialDistribution createProbabilityDistribution() + { + return new BinomialDistribution(trialsNumberModel.getNumber().intValue(), + probabilityNumberModel.getNumber().doubleValue()); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/DistributionPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/DistributionPanel.java new file mode 100644 index 00000000..e143acc9 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/DistributionPanel.java @@ -0,0 +1,80 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import javax.swing.BorderFactory; +import javax.swing.JComboBox; +import javax.swing.JPanel; + +/** + * GUI component for selecting a probability distribution. Displays appropriate + * configuration options for each distribution. + * @author Daniel Dyer + */ +class DistributionPanel extends JPanel +{ + private final SortedMap<String, ParametersPanel> parameterPanels = new TreeMap<String, ParametersPanel>(); + private final JComboBox distributionCombo = new JComboBox(); + + { + parameterPanels.put("Binomial", new BinomialParametersPanel()); + parameterPanels.put("Exponential", new ExponentialParametersPanel()); + parameterPanels.put("Gaussian", new GaussianParametersPanel()); + parameterPanels.put("Poisson", new PoissonParametersPanel()); + parameterPanels.put("Uniform", new UniformParametersPanel()); + } + + + public DistributionPanel() + { + super(new BorderLayout()); + final CardLayout parametersLayout = new CardLayout(); + final JPanel parametersPanel = new JPanel(parametersLayout); + for (Map.Entry<String, ParametersPanel> entry : parameterPanels.entrySet()) + { + distributionCombo.addItem(entry.getKey()); + parametersPanel.add(entry.getValue(), entry.getKey()); + } + parametersLayout.first(parametersPanel); + + distributionCombo.addItemListener(new ItemListener() + { + public void itemStateChanged(ItemEvent itemEvent) + { + parametersLayout.show(parametersPanel, + (String) distributionCombo.getSelectedItem()); + } + }); + + add(distributionCombo, BorderLayout.NORTH); + add(parametersPanel, BorderLayout.CENTER); + setBorder(BorderFactory.createTitledBorder("Probability Distribution")); + } + + + public ProbabilityDistribution createProbabilityDistribution() + { + ParametersPanel panel = parameterPanels.get(distributionCombo.getSelectedItem().toString()); + return panel.createProbabilityDistribution(); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/ExponentialDistribution.java b/maths/demo/src/java/main/org/uncommons/maths/demo/ExponentialDistribution.java new file mode 100644 index 00000000..1e34c65f --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/ExponentialDistribution.java @@ -0,0 +1,97 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.uncommons.maths.random.ExponentialGenerator; + +/** + * @author Daniel Dyer + */ +class ExponentialDistribution extends ProbabilityDistribution +{ + private final double rate; + + + public ExponentialDistribution(double rate) + { + this.rate = rate; + } + + + protected ExponentialGenerator createValueGenerator(Random rng) + { + return new ExponentialGenerator(rate, rng); + } + + + public Map<Double, Double> getExpectedValues() + { + Map<Double, Double> values = new HashMap<Double, Double>(); + double p; + double x = 0; + do + { + p = getExpectedProbability(x); + values.put(x, p); + x += (1 / (2 * rate)); + } while (p > 0.001); + return values; + } + + + /** + * This is the probability density function for the Exponential + * distribution. + */ + private double getExpectedProbability(double x) + { + if (x < 0) + { + return 0; + } + else + { + return rate * Math.exp(-rate * x); + } + } + + + public double getExpectedMean() + { + return Math.pow(rate, -1); + } + + + public double getExpectedStandardDeviation() + { + return Math.sqrt(Math.pow(rate, -2)); + } + + + public String getDescription() + { + return "Exponential Distribution (\u03bb = " + rate + ")"; + } + + + public boolean isDiscrete() + { + return false; + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/ExponentialParametersPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/ExponentialParametersPanel.java new file mode 100644 index 00000000..587ff074 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/ExponentialParametersPanel.java @@ -0,0 +1,47 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.SpringLayout; +import org.uncommons.swing.SpringUtilities; + +/** + * @author Daniel Dyer + */ +class ExponentialParametersPanel extends ParametersPanel +{ + private final SpinnerNumberModel rateNumberModel = new SpinnerNumberModel(2.0d, 0.1d, 100.0d, 0.1d); + + public ExponentialParametersPanel() + { + JPanel wrapper = new JPanel(new SpringLayout()); + wrapper.add(new JLabel("Rate: ")); + wrapper.add(new JSpinner(rateNumberModel)); + SpringUtilities.makeCompactGrid(wrapper, 2, 1, 6, 6, 6, 6); + add(wrapper, BorderLayout.NORTH); + } + + + public ExponentialDistribution createProbabilityDistribution() + { + return new ExponentialDistribution(rateNumberModel.getNumber().doubleValue()); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/GaussianDistribution.java b/maths/demo/src/java/main/org/uncommons/maths/demo/GaussianDistribution.java new file mode 100644 index 00000000..0bc64227 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/GaussianDistribution.java @@ -0,0 +1,95 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.uncommons.maths.random.GaussianGenerator; + +/** + * @author Daniel Dyer + */ +class GaussianDistribution extends ProbabilityDistribution +{ + private final double mean; + private final double standardDeviation; + + + public GaussianDistribution(double mean, double standardDeviation) + { + this.mean = mean; + this.standardDeviation = standardDeviation; + } + + + protected GaussianGenerator createValueGenerator(Random rng) + { + return new GaussianGenerator(mean, standardDeviation, rng); + } + + + public Map<Double, Double> getExpectedValues() + { + Map<Double, Double> values = new HashMap<Double, Double>(); + double p; + double x = 0; + do + { + p = getExpectedProbability(mean + x); + values.put(mean + x, p); + values.put(mean - x, p); + x += (3 * standardDeviation / 10); // 99.7% of values are within 3 standard deviations of the mean. + } while (p > 0.001); + return values; + } + + + /** + * This is the probability density function for the Gaussian + * distribution. + */ + private double getExpectedProbability(double x) + { + double y = 1 / (standardDeviation * Math.sqrt(Math.PI * 2)); + double z = -(Math.pow(x - mean, 2) / (2 * Math.pow(standardDeviation, 2))); + return y * Math.exp(z); + } + + + public double getExpectedMean() + { + return mean; + } + + + public double getExpectedStandardDeviation() + { + return standardDeviation; + } + + + public String getDescription() + { + return "Gaussian Distribution (\u03bc = " + mean + ", \u03c3 = " + standardDeviation +")"; + } + + + public boolean isDiscrete() + { + return false; + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/GaussianParametersPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/GaussianParametersPanel.java new file mode 100644 index 00000000..3b9571a0 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/GaussianParametersPanel.java @@ -0,0 +1,51 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.SpringLayout; +import org.uncommons.swing.SpringUtilities; + +/** + * @author Daniel Dyer + */ +class GaussianParametersPanel extends ParametersPanel +{ + private final SpinnerNumberModel meanNumberModel = new SpinnerNumberModel(0, -1000, 1000, 1); + private final SpinnerNumberModel deviationNumberModel = new SpinnerNumberModel(1d, 0.01d, 1000d, 1d); + + public GaussianParametersPanel() + { + JPanel wrapper = new JPanel(new SpringLayout()); + wrapper.add(new JLabel("Mean: ")); + wrapper.add(new JSpinner(meanNumberModel)); + wrapper.add(new JLabel("Standard Deviation: ")); + wrapper.add(new JSpinner(deviationNumberModel)); + SpringUtilities.makeCompactGrid(wrapper, 4, 1, 6, 6, 6, 6); + add(wrapper, BorderLayout.NORTH); + } + + + public GaussianDistribution createProbabilityDistribution() + { + return new GaussianDistribution(meanNumberModel.getNumber().doubleValue(), + deviationNumberModel.getNumber().doubleValue()); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/GraphPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/GraphPanel.java new file mode 100644 index 00000000..330d32a8 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/GraphPanel.java @@ -0,0 +1,94 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import java.util.Map; +import javax.swing.JPanel; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; +import org.jfree.chart.renderer.xy.XYSplineRenderer; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +/** + * @author Daniel Dyer + */ +class GraphPanel extends JPanel +{ + private final ChartPanel chartPanel = new ChartPanel(null); + + public GraphPanel() + { + super(new BorderLayout()); + add(chartPanel, BorderLayout.CENTER); + } + + + public void generateGraph(String title, + Map<Double, Double> observedValues, + Map<Double, Double> expectedValues, + double expectedMean, + double expectedStandardDeviation, + boolean discrete) + { + XYSeriesCollection dataSet = new XYSeriesCollection(); + XYSeries observedSeries = new XYSeries("Observed"); + dataSet.addSeries(observedSeries); + XYSeries expectedSeries = new XYSeries("Expected"); + dataSet.addSeries(expectedSeries); + + for (Map.Entry<Double, Double> entry : observedValues.entrySet()) + { + observedSeries.add(entry.getKey(), entry.getValue()); + } + + for (Map.Entry<Double, Double> entry : expectedValues.entrySet()) + { + expectedSeries.add(entry.getKey(), entry.getValue()); + } + + + JFreeChart chart = ChartFactory.createXYLineChart(title, + "Value", + "Probability", + dataSet, + PlotOrientation.VERTICAL, + true, + false, + false); + XYPlot plot = (XYPlot) chart.getPlot(); + if (discrete) + { + // Render markers at each data point (these discrete points are the + // distibution, not the lines between them). + plot.setRenderer(new XYLineAndShapeRenderer()); + } + else + { + // Render smooth lines between points for a continuous distribution. + XYSplineRenderer renderer = new XYSplineRenderer(); + renderer.setBaseShapesVisible(false); + plot.setRenderer(renderer); + } + + chartPanel.setChart(chart); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/ParametersPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/ParametersPanel.java new file mode 100644 index 00000000..e47ba12a --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/ParametersPanel.java @@ -0,0 +1,33 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import javax.swing.JPanel; + +/** + * @author Daniel Dyer + */ +abstract class ParametersPanel extends JPanel +{ + protected ParametersPanel() + { + super(new BorderLayout()); + } + + + public abstract ProbabilityDistribution createProbabilityDistribution(); +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/PoissonDistribution.java b/maths/demo/src/java/main/org/uncommons/maths/demo/PoissonDistribution.java new file mode 100644 index 00000000..edf638f6 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/PoissonDistribution.java @@ -0,0 +1,100 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.uncommons.maths.Maths; +import org.uncommons.maths.random.PoissonGenerator; + +/** + * @author Daniel Dyer + */ +class PoissonDistribution extends ProbabilityDistribution +{ + private final double mean; + + public PoissonDistribution(double mean) + { + this.mean = mean; + } + + + public Map<Double, Double> getExpectedValues() + { + Map<Double, Double> values = new HashMap<Double, Double>(); + int index = 0; + double p; + do + { + p = getExpectedProbability(index); + values.put((double) index, p); + ++index; + } while (p > 0.001); + return values; + } + + + /** + * This is the probability mass function + * (http://en.wikipedia.org/wiki/Probability_mass_function) of + * the Poisson distribution represented by this number generator. + * @param events The number of occurrences to determine the + * probability for. + * @return The probability of the specified number of events + * occurring given the current value of lamda. + */ + private double getExpectedProbability(int events) + { + BigDecimal kFactorial = new BigDecimal(Maths.bigFactorial(events)); + double numerator = Math.exp(-mean) * Math.pow(mean, events); + return new BigDecimal(numerator).divide(kFactorial, RoundingMode.HALF_UP).doubleValue(); + } + + + + protected PoissonGenerator createValueGenerator(Random rng) + { + return new PoissonGenerator(mean, rng); + } + + + public double getExpectedMean() + { + return mean; + } + + + public double getExpectedStandardDeviation() + { + return Math.sqrt(mean); + } + + + public String getDescription() + { + return "Poisson Distribution (\u03bb = " + mean + ")"; + } + + + public boolean isDiscrete() + { + return true; + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/PoissonParametersPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/PoissonParametersPanel.java new file mode 100644 index 00000000..83484fb5 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/PoissonParametersPanel.java @@ -0,0 +1,47 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.SpringLayout; +import org.uncommons.swing.SpringUtilities; + +/** + * @author Daniel Dyer + */ +class PoissonParametersPanel extends ParametersPanel +{ + private final SpinnerNumberModel meanNumberModel = new SpinnerNumberModel(5.0d, 0.1d, 100.0d, 0.1d); + + public PoissonParametersPanel() + { + JPanel wrapper = new JPanel(new SpringLayout()); + wrapper.add(new JLabel("Mean: ")); + wrapper.add(new JSpinner(meanNumberModel)); + SpringUtilities.makeCompactGrid(wrapper, 2, 1, 6, 6, 6, 6); + add(wrapper, BorderLayout.NORTH); + } + + + public PoissonDistribution createProbabilityDistribution() + { + return new PoissonDistribution(meanNumberModel.getNumber().doubleValue()); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/ProbabilityDistribution.java b/maths/demo/src/java/main/org/uncommons/maths/demo/ProbabilityDistribution.java new file mode 100644 index 00000000..f9339ef9 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/ProbabilityDistribution.java @@ -0,0 +1,125 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.uncommons.maths.number.NumberGenerator; + +/** + * Encapsulates a probability distribution. Provides both theoretical + * values for the distribution as well as a way of randomly generating + * values that follow this distribution. + * @author Daniel Dyer + */ +abstract class ProbabilityDistribution +{ + protected abstract NumberGenerator<?> createValueGenerator(Random rng); + + public Map<Double, Double> generateValues(int count, + Random rng) + { + Map<Double, Double> values = isDiscrete() + ? generateDiscreteValues(count, rng) + : generateContinuousValues(count, rng); + + double sum = 0; + for (Double key : values.keySet()) + { + Double value = values.get(key); + values.put(key, value / count); + sum += value; + } + assert Math.round(sum) == count : "Wrong total: " + sum; + return values; + } + + + private Map<Double, Double> generateDiscreteValues(int count, + Random rng) + { + NumberGenerator<?> generator = createValueGenerator(rng); + Map<Double, Double> values = new HashMap<Double, Double>(); + for (int i = 0; i < count; i++) + { + double value = generator.nextValue().doubleValue(); + Double aggregate = values.get(value); + aggregate = aggregate == null ? 0 : aggregate; + values.put(value, ++aggregate); + } + return values; + } + + + private Map<Double, Double> generateContinuousValues(int count, + Random rng) + { + NumberGenerator<?> generator = createValueGenerator(rng); + double[] values = new double[count]; + double min = Double.MAX_VALUE; + double max = Double.MIN_VALUE; + for (int i = 0; i < count; i++) + { + double value = generator.nextValue().doubleValue(); + min = Math.min(value, min); + max = Math.max(value, max); + values[i] = value; + } + return doQuantization(max, min, values); + } + + + /** + * Convert the continuous values into discrete values by chopping up + * the distribution into several equally-sized intervals. + */ + protected static Map<Double, Double> doQuantization(double max, + double min, + double[] values) + { + double range = max - min; + int noIntervals = 20; + double intervalSize = range / noIntervals; + int[] intervals = new int[noIntervals]; + for (double value : values) + { + int interval = Math.min(noIntervals - 1, + (int) Math.floor((value - min) / intervalSize)); + assert interval >= 0 && interval < noIntervals : "Invalid interval: " + interval; + ++intervals[interval]; + } + Map<Double, Double> discretisedValues = new HashMap<Double, Double>(); + for (int i = 0; i < intervals.length; i++) + { + // Correct the value to take into account the size of the interval. + double value = (1 / intervalSize) * (double) intervals[i]; + discretisedValues.put(min + ((i + 0.5) * intervalSize), value); + } + return discretisedValues; + } + + + public abstract Map<Double, Double> getExpectedValues(); + + public abstract double getExpectedMean(); + + public abstract double getExpectedStandardDeviation(); + + public abstract String getDescription(); + + public abstract boolean isDiscrete(); +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/RNGPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/RNGPanel.java new file mode 100644 index 00000000..44d72358 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/RNGPanel.java @@ -0,0 +1,96 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.SortedMap; +import java.util.TreeMap; +import javax.swing.BorderFactory; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.SpringLayout; +import org.uncommons.maths.random.AESCounterRNG; +import org.uncommons.maths.random.CMWC4096RNG; +import org.uncommons.maths.random.CellularAutomatonRNG; +import org.uncommons.maths.random.JavaRNG; +import org.uncommons.maths.random.MersenneTwisterRNG; +import org.uncommons.maths.random.XORShiftRNG; +import org.uncommons.swing.SpringUtilities; + +/** + * Controls for selecing a random number generator and a number of values + * to generate. + * @author Daniel Dyer + */ +class RNGPanel extends JPanel +{ + private final JComboBox rngCombo = new JComboBox(); + private final SpinnerNumberModel iterationsNumberModel = new SpinnerNumberModel(10000, 10, 1000000, 100); + + private final SortedMap<String, Random> rngs = new TreeMap<String, Random>(); + { + try + { + rngs.put("AES", new AESCounterRNG()); + rngs.put("Cellular Automaton", new CellularAutomatonRNG()); + rngs.put("CMWC 4096", new CMWC4096RNG()); + rngs.put("JDK RNG", new JavaRNG()); + rngs.put("Mersenne Twister", new MersenneTwisterRNG()); + rngs.put("SecureRandom", new SecureRandom()); + rngs.put("XOR Shift", new XORShiftRNG()); + } + catch (GeneralSecurityException ex) + { + throw new IllegalStateException("Failed to initialise RNGs.", ex); + } + } + + + public RNGPanel() + { + super(new SpringLayout()); + for (String name : rngs.keySet()) + { + rngCombo.addItem(name); + } + rngCombo.setSelectedIndex(3); // Mersenne Twister. + add(rngCombo); + add(new JLabel("No. Values: ")); + add(new JSpinner(iterationsNumberModel)); + setBorder(BorderFactory.createTitledBorder("RNG")); + SpringUtilities.makeCompactGrid(this, 3, 1, 6, 6, 6, 6); + } + + + public Random getRNG() + { + return rngs.get((String) rngCombo.getSelectedItem()); + } + + + /** + * Returns the number of values to be generated, as specified by the user. + */ + public int getIterations() + { + return iterationsNumberModel.getNumber().intValue(); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/RandomDemo.java b/maths/demo/src/java/main/org/uncommons/maths/demo/RandomDemo.java new file mode 100644 index 00000000..e4a69655 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/RandomDemo.java @@ -0,0 +1,157 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Map; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import org.uncommons.swing.SwingBackgroundTask; + +/** + * Demo application that demonstrates the generation of random values using + * different probability distributions. + * @author Daniel Dyer + */ +public class RandomDemo extends JFrame +{ + private final DistributionPanel distributionPanel = new DistributionPanel(); + private final RNGPanel rngPanel = new RNGPanel(); + private final GraphPanel graphPanel = new GraphPanel(); + + public RandomDemo() + { + super("Uncommons Maths - Random Numbers Demo"); + setLayout(new BorderLayout()); + add(createControls(), BorderLayout.WEST); + add(graphPanel, BorderLayout.CENTER); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(700, 500); + setMinimumSize(new Dimension(500, 250)); + validate(); + } + + + private JComponent createControls() + { + Box controls = new Box(BoxLayout.Y_AXIS); + controls.add(distributionPanel); + controls.add(rngPanel); + + JButton executeButton = new JButton("Go"); + executeButton.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent actionEvent) + { + RandomDemo.this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + new SwingBackgroundTask<GraphData>() + { + private ProbabilityDistribution distribution; + + protected GraphData performTask() + { + distribution = distributionPanel.createProbabilityDistribution(); + + Map<Double, Double> observedValues = distribution.generateValues(rngPanel.getIterations(), + rngPanel.getRNG()); + Map<Double, Double> expectedValues = distribution.getExpectedValues(); + return new GraphData(observedValues, + expectedValues, + distribution.getExpectedMean(), + distribution.getExpectedStandardDeviation()); + } + + protected void postProcessing(GraphData data) + { + graphPanel.generateGraph(distribution.getDescription(), + data.getObservedValues(), + data.getExpectedValues(), + data.getExpectedMean(), + data.getExpectedStandardDeviation(), + distribution.isDiscrete()); + RandomDemo.this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + }.execute(); + } + }); + controls.add(executeButton); + return controls; + } + + + public static void main(String[] args) + { + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + new RandomDemo().setVisible(true); + } + }); + } + + + private static class GraphData + { + private final Map<Double, Double> observedValues; + private final Map<Double, Double> expectedValues; + private final double expectedMean; + private final double expectedStandardDeviation; + + + public GraphData(Map<Double, Double> observedValues, + Map<Double, Double> expectedValues, + double expectedMean, + double expectedStandardDeviation) + { + this.observedValues = observedValues; + this.expectedValues = expectedValues; + this.expectedMean = expectedMean; + this.expectedStandardDeviation = expectedStandardDeviation; + } + + + public Map<Double, Double> getObservedValues() + { + return observedValues; + } + + + public Map<Double, Double> getExpectedValues() + { + return expectedValues; + } + + + public double getExpectedMean() + { + return expectedMean; + } + + public double getExpectedStandardDeviation() + { + return expectedStandardDeviation; + } + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/UniformDistribution.java b/maths/demo/src/java/main/org/uncommons/maths/demo/UniformDistribution.java new file mode 100644 index 00000000..489bd85c --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/UniformDistribution.java @@ -0,0 +1,77 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.uncommons.maths.random.DiscreteUniformGenerator; + +/** + * @author Daniel Dyer + */ +class UniformDistribution extends ProbabilityDistribution +{ + private final int min; + private final int max; + + public UniformDistribution(int min, int max) + { + this.min = min; + this.max = max; + } + + + public Map<Double, Double> getExpectedValues() + { + Map<Double, Double> values = new HashMap<Double, Double>(); + for (int i = min; i <= max; i++) + { + values.put((double) i, 1d / ((max - min) + 1)); + } + return values; + } + + + protected DiscreteUniformGenerator createValueGenerator(Random rng) + { + return new DiscreteUniformGenerator(min, max, rng); + } + + + public double getExpectedMean() + { + return (max - min) / 2 + min; + } + + + public double getExpectedStandardDeviation() + { + return (max - min) / Math.sqrt(12); + } + + + public String getDescription() + { + return "Uniform Distribution (Range = " + min + "..." + max + ")"; + } + + + public boolean isDiscrete() + { + return true; + } +} diff --git a/maths/demo/src/java/main/org/uncommons/maths/demo/UniformParametersPanel.java b/maths/demo/src/java/main/org/uncommons/maths/demo/UniformParametersPanel.java new file mode 100644 index 00000000..58c1c3ec --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/maths/demo/UniformParametersPanel.java @@ -0,0 +1,51 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.maths.demo; + +import java.awt.BorderLayout; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.SpringLayout; +import org.uncommons.swing.SpringUtilities; + +/** + * @author Daniel Dyer + */ +class UniformParametersPanel extends ParametersPanel +{ + private final SpinnerNumberModel minNumberModel = new SpinnerNumberModel(1, 0, 100, 1); + private final SpinnerNumberModel maxNumberModel = new SpinnerNumberModel(10, 1, 100, 1); + + public UniformParametersPanel() + { + JPanel wrapper = new JPanel(new SpringLayout()); + wrapper.add(new JLabel("Minimum: ")); + wrapper.add(new JSpinner(minNumberModel)); + wrapper.add(new JLabel("Maximum: ")); + wrapper.add(new JSpinner(maxNumberModel)); + SpringUtilities.makeCompactGrid(wrapper, 4, 1, 6, 6, 6, 6); + add(wrapper, BorderLayout.NORTH); + } + + + public UniformDistribution createProbabilityDistribution() + { + return new UniformDistribution(minNumberModel.getNumber().intValue(), + maxNumberModel.getNumber().intValue()); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/swing/SpringUtilities.java b/maths/demo/src/java/main/org/uncommons/swing/SpringUtilities.java new file mode 100644 index 00000000..221d4f1a --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/swing/SpringUtilities.java @@ -0,0 +1,213 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.swing; + +import java.awt.Component; +import java.awt.Container; +import javax.swing.Spring; +import javax.swing.SpringLayout; + +/** + * Utility methods for creating form-style or grid-style layouts with SpringLayout. + * Modified version of the class presented in the Sun Swing tutorial + * (http://java.sun.com/docs/books/tutorial/uiswing/layout/examples/SpringUtilities.java). + */ +public final class SpringUtilities +{ + private SpringUtilities() + { + // Private constructor prevents instantiation of utility class. + } + + + /** + * Aligns the first {@code rows} * {@code cols} components of {@code parent} + * in a grid. Each component is as big as the maximum preferred width and + * height of the components. The parent is made just big enough to fit them + * all. + * @param parent The container to layout. + * @param rows Number of rows + * @param cols Number of columns + * @param initialX x location to start the grid at + * @param initialY y location to start the grid at + * @param xPad x padding between cells + * @param yPad y padding between cells + */ + public static void makeGrid(Container parent, + int rows, + int cols, + int initialX, + int initialY, + int xPad, int yPad) + { + if (!(parent.getLayout() instanceof SpringLayout)) + { + throw new IllegalArgumentException("The first argument to makeGrid must use SpringLayout."); + } + SpringLayout layout = (SpringLayout) parent.getLayout(); + + Spring xPadSpring = Spring.constant(xPad); + Spring yPadSpring = Spring.constant(yPad); + Spring initialXSpring = Spring.constant(initialX); + Spring initialYSpring = Spring.constant(initialY); + int max = rows * cols; + + // Calculate Springs that are the max of the width/height so that all + // cells have the same size. + Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0)).getWidth(); + Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0)).getWidth(); + for (int i = 1; i < max; i++) + { + SpringLayout.Constraints cons = layout.getConstraints(parent.getComponent(i)); + maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth()); + maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight()); + } + + // Apply the new width/height Spring. This forces all the + // components to have the same size. + for (int i = 0; i < max; i++) + { + SpringLayout.Constraints cons = layout.getConstraints(parent.getComponent(i)); + cons.setWidth(maxWidthSpring); + cons.setHeight(maxHeightSpring); + } + + // Then adjust the x/y constraints of all the cells so that they + // are aligned in a grid. + SpringLayout.Constraints lastConstraints = null; + SpringLayout.Constraints lastRowConstraints = null; + for (int i = 0; i < max; i++) + { + SpringLayout.Constraints constraints = layout.getConstraints(parent.getComponent(i)); + if (i % cols == 0) // Start of new row. + { + lastRowConstraints = lastConstraints; + constraints.setX(initialXSpring); + } + else // X position depends on previous component. + { + constraints.setX(Spring.sum(lastConstraints.getConstraint(SpringLayout.EAST), + xPadSpring)); + } + + if (i / cols == 0) // First row. + { + constraints.setY(initialYSpring); + } + else // Y position depends on previous row. + { + constraints.setY(Spring.sum(lastRowConstraints.getConstraint(SpringLayout.SOUTH), + yPadSpring)); + } + lastConstraints = constraints; + } + + // Set the parent's size. + SpringLayout.Constraints pCons = layout.getConstraints(parent); + pCons.setConstraint(SpringLayout.SOUTH, + Spring.sum(Spring.constant(yPad), + lastConstraints.getConstraint(SpringLayout.SOUTH))); + pCons.setConstraint(SpringLayout.EAST, + Spring.sum(Spring.constant(xPad), + lastConstraints.getConstraint(SpringLayout.EAST))); + } + + + /** + * Aligns the first {@code rows} * {@code cols} components of {@code parent} + * in a grid. Each component in a column is as wide as the maximum preferred + * width of the components in that column; height is similarly determined for + * each row. The parent is made just big enough to fit them all. + * @param parent The container to layout. + * @param rows number of rows + * @param columns number of columns + * @param initialX x location to start the grid at + * @param initialY y location to start the grid at + * @param xPad x padding between cells + * @param yPad y padding between cells + */ + public static void makeCompactGrid(Container parent, + int rows, + int columns, + int initialX, + int initialY, + int xPad, + int yPad) + { + if (!(parent.getLayout() instanceof SpringLayout)) + { + throw new IllegalArgumentException("The first argument to makeCompactGrid must use SpringLayout."); + } + SpringLayout layout = (SpringLayout) parent.getLayout(); + + // Align all cells in each column and make them the same width. + Spring x = Spring.constant(initialX); + for (int c = 0; c < columns; c++) + { + Spring width = Spring.constant(0); + for (int r = 0; r < rows; r++) + { + width = Spring.max(width, + getConstraintsForCell(r, c, parent, columns).getWidth()); + } + for (int r = 0; r < rows; r++) + { + SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, columns); + constraints.setX(x); + constraints.setWidth(width); + } + x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad))); + } + + // Align all cells in each row and make them the same height. + Spring y = Spring.constant(initialY); + for (int r = 0; r < rows; r++) + { + Spring height = Spring.constant(0); + for (int c = 0; c < columns; c++) + { + height = Spring.max(height, + getConstraintsForCell(r, c, parent, columns).getHeight()); + } + for (int c = 0; c < columns; c++) + { + SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, columns); + constraints.setY(y); + constraints.setHeight(height); + } + y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad))); + } + + // Set the parent's size. + SpringLayout.Constraints parentConstraints = layout.getConstraints(parent); + parentConstraints.setConstraint(SpringLayout.SOUTH, y); + parentConstraints.setConstraint(SpringLayout.EAST, x); + } + + + /** + * Helper method for {@link #makeCompactGrid(Container, int, int, int, int, int, int)}. + */ + private static SpringLayout.Constraints getConstraintsForCell(int row, + int col, + Container parent, + int cols) + { + SpringLayout layout = (SpringLayout) parent.getLayout(); + Component c = parent.getComponent(row * cols + col); + return layout.getConstraints(c); + } +} diff --git a/maths/demo/src/java/main/org/uncommons/swing/SwingBackgroundTask.java b/maths/demo/src/java/main/org/uncommons/swing/SwingBackgroundTask.java new file mode 100644 index 00000000..728a4413 --- /dev/null +++ b/maths/demo/src/java/main/org/uncommons/swing/SwingBackgroundTask.java @@ -0,0 +1,100 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.swing; + +import java.util.concurrent.CountDownLatch; +import javax.swing.SwingUtilities; + +/** + * A task that is executed on a background thread and then updates + * a Swing GUI. A task may only be executed once. + * @author Daniel Dyer + * @param <V> Type of result generated by the task. + */ +public abstract class SwingBackgroundTask<V> +{ + // Used to assign thread IDs to make threads easier to identify when debugging. + private static int instanceCount = 0; + + private final CountDownLatch latch = new CountDownLatch(1); + private final int id; + + protected SwingBackgroundTask() + { + synchronized (SwingBackgroundTask.class) + { + this.id = instanceCount; + ++instanceCount; + } + } + + + /** + * Asynchronous call that begins execution of the task + * and returns immediately. + */ + public void execute() + { + Runnable task = new Runnable() + { + public void run() + { + final V result = performTask(); + SwingUtilities.invokeLater(new Runnable() + { + public void run() + { + postProcessing(result); + latch.countDown(); + } + }); + } + }; + new Thread(task, "SwingBackgroundTask-" + id).start(); + } + + + /** + * Waits for the execution of this task to complete. If the {@link #execute()} + * method has not yet been invoked, this method will block indefinitely. + * @throws InterruptedException If the thread executing the task + * is interrupted. + */ + public void waitForCompletion() throws InterruptedException + { + latch.await(); + } + + + /** + * Performs the processing of the task and returns a result. + * Implement in sub-classes to provide the task logic. This method will + * run on a background thread and not on the Event Dispatch Thread and + * therefore should not manipulate any Swing components. + * @return The result of executing this task. + */ + protected abstract V performTask(); + + + /** + * This method is invoked, on the Event Dispatch Thread, after the task + * has been executed. + * This should be implemented in sub-classes in order to provide GUI + * updates that should occur following task completion. + * @param result The result from the {@link #performTask()} method. + */ + protected abstract void postProcessing(V result); +} diff --git a/maths/demo/src/java/test/org/uncommons/swing/SwingBackgroundTaskTest.java b/maths/demo/src/java/test/org/uncommons/swing/SwingBackgroundTaskTest.java new file mode 100644 index 00000000..899eabe1 --- /dev/null +++ b/maths/demo/src/java/test/org/uncommons/swing/SwingBackgroundTaskTest.java @@ -0,0 +1,58 @@ +// ============================================================================ +// Copyright 2006-2012 Daniel W. Dyer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================ +package org.uncommons.swing; + +import javax.swing.SwingUtilities; +import org.testng.annotations.Test; + +/** + * Unit test for {@link SwingBackgroundTask}. Ensures code is + * executed on correct threads. + * @author Daniel Dyer + */ +public class SwingBackgroundTaskTest +{ + private boolean taskExecuted; + private boolean taskOnEDT; + private boolean postProcessingExecuted; + private boolean postProcessingOnEDT; + + @Test + public void testExecutionThreads() throws InterruptedException + { + SwingBackgroundTask<Object> testTask = new SwingBackgroundTask<Object>() + { + protected Object performTask() + { + taskExecuted = true; + taskOnEDT = SwingUtilities.isEventDispatchThread(); + return null; + } + + protected void postProcessing(Object result) + { + postProcessingExecuted = true; + postProcessingOnEDT = SwingUtilities.isEventDispatchThread(); + } + }; + testTask.execute(); + testTask.waitForCompletion(); + assert taskExecuted : "Task was not executed."; + assert postProcessingExecuted : "Post-processing was not executed."; + assert !taskOnEDT : "Task was executed on EDT."; + assert postProcessingOnEDT : "Post-processing was not executed on EDT."; + } +} diff --git a/maths/etc/intellij/Core.iml b/maths/etc/intellij/Core.iml new file mode 100644 index 00000000..98cfedba --- /dev/null +++ b/maths/etc/intellij/Core.iml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module relativePaths="true" type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/../../core/build/classes/main" /> + <output-test url="file://$MODULE_DIR$/../../core/build/classes/test" /> + <exclude-output /> + <content url="file://$MODULE_DIR$/../../core"> + <sourceFolder url="file://$MODULE_DIR$/../../core/src/java/main" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/../../core/src/java/test" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/../../core/build" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="TestNG" level="project" /> + </component> +</module> + diff --git a/maths/etc/intellij/Demo.iml b/maths/etc/intellij/Demo.iml new file mode 100644 index 00000000..eff553f0 --- /dev/null +++ b/maths/etc/intellij/Demo.iml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module relativePaths="true" type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="false"> + <output url="file://$MODULE_DIR$/../../demo/build/classes/main" /> + <output-test url="file://$MODULE_DIR$/../../demo/build/classes/test" /> + <exclude-output /> + <content url="file://$MODULE_DIR$/../../demo"> + <sourceFolder url="file://$MODULE_DIR$/../../demo/src/java/main" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/../../demo/src/java/test" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/../../demo/build" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="Core" /> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../../lib/runtime/jfreechart-1.0.8.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="module-library"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../../lib/runtime/jcommon-1.0.12.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </orderEntry> + <orderEntry type="library" name="TestNG" level="project" /> + </component> +</module> + diff --git a/maths/etc/intellij/Global.iml b/maths/etc/intellij/Global.iml new file mode 100644 index 00000000..b83166dd --- /dev/null +++ b/maths/etc/intellij/Global.iml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module relativePaths="true" type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <content url="file://$MODULE_DIR$/../.."> + <excludeFolder url="file://$MODULE_DIR$/../../core" /> + <excludeFolder url="file://$MODULE_DIR$/../../demo" /> + <excludeFolder url="file://$MODULE_DIR$/../../dist" /> + <excludeFolder url="file://$MODULE_DIR$/../../docs" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> + diff --git a/maths/etc/intellij/Uncommons Maths.ipr b/maths/etc/intellij/Uncommons Maths.ipr new file mode 100644 index 00000000..e99b7d17 --- /dev/null +++ b/maths/etc/intellij/Uncommons Maths.ipr @@ -0,0 +1,515 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="AntConfiguration"> + <defaultAnt bundledAnt="true" /> + <buildFile url="file://$PROJECT_DIR$/../../build.xml"> + <additionalClassPath /> + <antReference projectDefault="true" /> + <customJdkName value="" /> + <maximumHeapSize value="128" /> + <maximumStackSize value="32" /> + <properties /> + </buildFile> + </component> + <component name="BuildJarProjectSettings"> + <option name="BUILD_JARS_ON_MAKE" value="false" /> + </component> + <component name="CodeStyleProjectProfileManger"> + <option name="PROJECT_PROFILE" /> + <option name="USE_PROJECT_LEVEL_SETTINGS" value="false" /> + </component> + <component name="CodeStyleSettingsManager"> + <option name="PER_PROJECT_SETTINGS" /> + <option name="USE_PER_PROJECT_SETTINGS" value="false" /> + </component> + <component name="CompilerConfiguration"> + <option name="DEFAULT_COMPILER" value="Javac" /> + <resourceExtensions> + <entry name=".+\.(properties|xml|html|dtd|tld)" /> + <entry name=".+\.(gif|png|jpeg|jpg)" /> + </resourceExtensions> + <wildcardResourcePatterns> + <entry name="?*.properties" /> + <entry name="?*.xml" /> + <entry name="?*.gif" /> + <entry name="?*.png" /> + <entry name="?*.jpeg" /> + <entry name="?*.jpg" /> + <entry name="?*.html" /> + <entry name="?*.dtd" /> + <entry name="?*.tld" /> + </wildcardResourcePatterns> + <annotationProcessing> + <profile default="true" name="Default" enabled="false"> + <processorPath useClasspath="true" /> + </profile> + </annotationProcessing> + </component> + <component name="CopyrightManager" default=""> + <module2copyright /> + </component> + <component name="CppTools.Loader" reportImplicitCastToBool="false" warnedAboutFileOutOfSourceRoot="true" version="1" /> + <component name="DependenciesAnalyzeManager"> + <option name="myForwardDirection" value="false" /> + </component> + <component name="DependencyValidationManager"> + <option name="SKIP_IMPORT_STATEMENTS" value="false" /> + </component> + <component name="EclipseCompilerSettings"> + <option name="GENERATE_NO_WARNINGS" value="true" /> + <option name="DEPRECATION" value="false" /> + </component> + <component name="EclipseEmbeddedCompilerSettings"> + <option name="DEBUGGING_INFO" value="true" /> + <option name="GENERATE_NO_WARNINGS" value="true" /> + <option name="DEPRECATION" value="false" /> + <option name="ADDITIONAL_OPTIONS_STRING" value="" /> + <option name="MAXIMUM_HEAP_SIZE" value="128" /> + </component> + <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> + <component name="EntryPointsManager"> + <entry_points version="2.0" /> + </component> + <component name="ExportToHTMLSettings"> + <option name="PRINT_LINE_NUMBERS" value="false" /> + <option name="OPEN_IN_BROWSER" value="false" /> + <option name="OUTPUT_DIRECTORY" /> + </component> + <component name="IdProvider" IDEtalkID="0D0AA9419EF81876CD8BD46BC2DFC45D" /> + <component name="InspectionProjectProfileManager"> + <profiles> + <profile version="1.0" is_locked="false"> + <option name="myName" value="Project Default" /> + <option name="myLocal" value="false" /> + <inspection_tool class="AbstractMethodCallInConstructor" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="AntMissingPropertiesFileInspection" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ArithmeticOnVolatileField" level="WARNING" enabled="true" /> + <inspection_tool class="AssertAsName" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreTransformationOfOriginalParameter" value="false" /> + </inspection_tool> + <inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="BadExceptionCaught" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="exceptionsString" value="java.lang.NullPointerException,java.lang.IllegalMonitorStateException,java.lang.ArrayIndexOutOfBoundsException" /> + <option name="exceptions"> + <value /> + </option> + </inspection_tool> + <inspection_tool class="BusyWait" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CachedNumberConstructorCall" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CastThatLosesPrecision" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreIntegerCharCasts" value="false" /> + </inspection_tool> + <inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="insideTryAllowed" value="false" /> + </inspection_tool> + <inspection_tool class="ClassNameSameAsAncestorName" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="m_regex" value="[A-Z][A-Za-z\d]*" /> + <option name="m_minLength" value="3" /> + <option name="m_maxLength" value="64" /> + </inspection_tool> + <inspection_tool class="CloneCallsConstructors" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CloneInNonCloneableClass" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CloneableImplementsClone" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="m_ignoreCloneableDueToInheritance" value="false" /> + </inspection_tool> + <inspection_tool class="CollectionAddedToSelf" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CollectionsMustHaveInitialCapacity" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ConditionalExpressionWithIdenticalBranches" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ConfusingElse" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="reportWhenNoStatementFollow" value="false" /> + </inspection_tool> + <inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CovariantCompareTo" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="CovariantEquals" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="DeclareCollectionAsInterface" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreLocalVariables" value="false" /> + <option name="ignorePrivateMethodsAndFields" value="false" /> + </inspection_tool> + <inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="DivideByZero" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="DollarSignInName" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreOnVolatileVariables" value="false" /> + </inspection_tool> + <inspection_tool class="DuplicateCondition" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreMethodCalls" value="false" /> + </inspection_tool> + <inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="EnumAsName" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="EnumeratedClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="m_regex" value="[A-Z][A-Za-z\d]*" /> + <option name="m_minLength" value="3" /> + <option name="m_maxLength" value="64" /> + </inspection_tool> + <inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ExceptionFromCatchWhichDoesntWrap" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreGetMessage" value="false" /> + <option name="ignoreCantWrap" value="false" /> + </inspection_tool> + <inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="countGettersAndSetters" value="false" /> + </inspection_tool> + <inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" /> + <option name="insideTryAllowed" value="false" /> + </inspection_tool> + <inspection_tool class="InstanceofChain" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreInstanceofOnLibraryClasses" value="false" /> + </inspection_tool> + <inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="InstantiationOfUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="InterfaceNamingConvention" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="m_regex" value="[A-Z][A-Za-z\d]*" /> + <option name="m_minLength" value="3" /> + <option name="m_maxLength" value="64" /> + </inspection_tool> + <inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="insideTryAllowed" value="false" /> + </inspection_tool> + <inspection_tool class="JavaLangImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="LongLiteralsEndingWithLowercaseL" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="MultipleTypedDeclaration" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="NonProtectedConstructorInAbstractClass" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="m_ignoreNonPublicClasses" value="false" /> + </inspection_tool> + <inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreRequiredObsoleteCollectionTypes" value="false" /> + </inspection_tool> + <inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="OnDemandImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="OverlyStrongTypeCast" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreInMatchingInstanceof" value="false" /> + </inspection_tool> + <inspection_tool class="OverriddenMethodCallInConstructor" level="WARNING" enabled="true" /> + <inspection_tool class="PackageVisibleField" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="PublicField" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreEnums" value="false" /> + <option name="ignorableAnnotations"> + <value /> + </option> + </inspection_tool> + <inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="RandomDoubleForRandomInteger" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="RawUseOfParameterizedType" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="RedundantImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="RedundantMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreLazyOperators" value="true" /> + <option name="ignoreObscureOperators" value="false" /> + </inspection_tool> + <inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="SamePackageImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="insideTryAllowed" value="false" /> + </inspection_tool> + <inspection_tool class="StaticInheritance" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ThreadRun" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ThreadWithDefaultRunMethod" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreRethrownExceptions" value="false" /> + </inspection_tool> + <inspection_tool class="TooBroadScope" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="m_allowConstructorAsInitializer" value="false" /> + <option name="m_onlyLookAtBlocks" value="false" /> + </inspection_tool> + <inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="UnusedImport" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="UseOfAnotherObjectsPrivateField" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreSameClass" value="false" /> + <option name="ignoreEquals" value="false" /> + </inspection_tool> + <inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreClassesWithOnlyMain" value="false" /> + <option name="ignorableAnnotations"> + <value /> + </option> + </inspection_tool> + <inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="VolatileLongOrDoubleField" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="WaitNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreNonEmtpyLoops" value="false" /> + </inspection_tool> + <inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" /> + </profile> + </profiles> + <option name="PROJECT_PROFILE" value="Project Default" /> + <option name="USE_PROJECT_PROFILE" value="true" /> + <version value="1.0" /> + </component> + <component name="JavadocGenerationManager"> + <option name="OUTPUT_DIRECTORY" /> + <option name="OPTION_SCOPE" value="protected" /> + <option name="OPTION_HIERARCHY" value="true" /> + <option name="OPTION_NAVIGATOR" value="true" /> + <option name="OPTION_INDEX" value="true" /> + <option name="OPTION_SEPARATE_INDEX" value="true" /> + <option name="OPTION_DOCUMENT_TAG_USE" value="false" /> + <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" /> + <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" /> + <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" /> + <option name="OPTION_DEPRECATED_LIST" value="true" /> + <option name="OTHER_OPTIONS" value="" /> + <option name="HEAP_SIZE" /> + <option name="LOCALE" /> + <option name="OPEN_IN_BROWSER" value="true" /> + </component> + <component name="LogConsolePreferences"> + <option name="FILTER_ERRORS" value="false" /> + <option name="FILTER_WARNINGS" value="false" /> + <option name="FILTER_INFO" value="true" /> + <option name="CUSTOM_FILTER" /> + </component> + <component name="MavenBuildProjectComponent"> + <option name="mavenExecutable" value="" /> + <option name="Settings File" value="" /> + <option name="mavenCommandLineParams" value="" /> + <option name="vmOptions" value="" /> + <option name="useMavenEmbedder" value="false" /> + <option name="useFilter" value="false" /> + <option name="Batch Mode" value="false" /> + <option name="Check Plugin Updates" value="false" /> + <option name="Debug" value="false" /> + <option name="Errors" value="false" /> + <option name="Fail At End" value="false" /> + <option name="Fail Fast" value="false" /> + <option name="Fail Never" value="false" /> + <option name="Lax Checksums" value="false" /> + <option name="No Plugin Registry" value="false" /> + <option name="No Plugin Updates" value="false" /> + <option name="Non Recursive" value="false" /> + <option name="Offline" value="false" /> + <option name="Reactor" value="false" /> + <option name="Strict Checksums" value="false" /> + <option name="Update Plugins" value="false" /> + <option name="Update Snapshots" value="false" /> + <option name="Skip Tests" value="false" /> + <pom-list /> + </component> + <component name="Palette2"> + <group name="Swing"> + <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> + </item> + <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true"> + <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> + <initial-values> + <property name="text" value="Button" /> + </initial-values> + </item> + <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="RadioButton" /> + </initial-values> + </item> + <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="CheckBox" /> + </initial-values> + </item> + <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="Label" /> + </initial-values> + </item> + <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> + <preferred-size width="-1" height="20" /> + </default-constraints> + </item> + <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> + </item> + </group> + </component> + <component name="ProjectDetails"> + <option name="projectName" value="Uncommons Maths" /> + </component> + <component name="ProjectKey"> + <option name="state" value="https://uncommons-maths.dev.java.net/svn/uncommons-maths/trunk/etc/intellij/Uncommons%20Maths.ipr" /> + </component> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/Core.iml" filepath="$PROJECT_DIR$/Core.iml" group="Modules" /> + <module fileurl="file://$PROJECT_DIR$/Demo.iml" filepath="$PROJECT_DIR$/Demo.iml" group="Modules" /> + <module fileurl="file://$PROJECT_DIR$/Global.iml" filepath="$PROJECT_DIR$/Global.iml" /> + </modules> + </component> + <component name="ProjectResources"> + <default-html-doctype>http://www.w3.org/1999/xhtml</default-html-doctype> + </component> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.6" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/../../build/classes/main" /> + </component> + <component name="SvnBranchConfigurationManager"> + <option name="myConfigurationMap"> + <map> + <entry key="$PROJECT_DIR$/../.."> + <value> + <SvnBranchConfiguration> + <option name="branchUrls"> + <list> + <option value="https://uncommons-maths.dev.java.net/svn/uncommons-maths/branches" /> + <option value="https://uncommons-maths.dev.java.net/svn/uncommons-maths/tags" /> + </list> + </option> + <option name="trunkUrl" value="https://uncommons-maths.dev.java.net/svn/uncommons-maths/trunk" /> + </SvnBranchConfiguration> + </value> + </entry> + </map> + </option> + <option name="mySupportsUserInfoFilter" value="true" /> + </component> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> + </component> + <component name="WebServicesPlugin" addRequiredLibraries="true" /> + <component name="com.intellij.jsf.UserDefinedFacesConfigs"> + <option name="USER_DEFINED_CONFIGS"> + <value> + <list size="0" /> + </value> + </option> + </component> + <component name="com.sixrr.metrics.MetricsReloaded"> + <option name="selectedProfile" value="Default" /> + <option name="autoscroll" value="false" /> + <option name="calculateMetrics" value="true" /> + <option name="includeTestClasses" value="false" /> + <option name="flattenInnerClasses" value="true" /> + <option name="cycleTableSpecificationString" value="" /> + <option name="shortCycleTableSpecificationString" value="" /> + </component> + <component name="libraryTable"> + <library name="TestNG"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../lib/compiletime/testng/testng-5.13.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES /> + </library> + </component> +</project> + diff --git a/maths/etc/testng.xml b/maths/etc/testng.xml new file mode 100644 index 00000000..3322cc3e --- /dev/null +++ b/maths/etc/testng.xml @@ -0,0 +1,36 @@ +<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > + +<suite name="Unit Tests" verbose="1" > + + <test name="Random" > + <packages> + <package name="org.uncommons.maths.random" /> + </packages> + </test> + + <test name="Combinatorics" > + <packages> + <package name="org.uncommons.maths.combinatorics" /> + </packages> + </test> + + <test name="Statistics" > + <packages> + <package name="org.uncommons.maths.statistics" /> + </packages> + </test> + + <test name="Binary" > + <packages> + <package name="org.uncommons.maths.binary" /> + </packages> + </test> + + <test name="Number" > + <packages> + <package name="org.uncommons.maths" /> + <package name="org.uncommons.maths.number" /> + </packages> + </test> + +</suite> diff --git a/maths/uncommons-maths.bnd b/maths/uncommons-maths.bnd new file mode 100644 index 00000000..cf99ba32 --- /dev/null +++ b/maths/uncommons-maths.bnd @@ -0,0 +1,5 @@ +Bundle-Name: Uncommons Maths +Bundle-SymbolicName: org.uncommons.maths +Bundle-Version: ${version} +Export-Package: *;version=${version} + |
