summaryrefslogtreecommitdiffstats
path: root/maths
diff options
context:
space:
mode:
authorYohann Roussel <yroussel@google.com>2014-03-19 16:25:37 +0100
committerYohann Roussel <yroussel@google.com>2014-03-20 15:13:33 +0100
commit4eceb95409e844fdc33c9c706e1dc307bfd40303 (patch)
treeee9f4f3fc79f757c79081c336bce4f1782c6ccd8 /maths
parent3d2402901b1a6462e2cf47a6fd09711f327961c3 (diff)
downloadtoolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.gz
toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.bz2
toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.zip
Initial Jack import.
Change-Id: I953cf0a520195a7187d791b2885848ad0d5a9b43
Diffstat (limited to 'maths')
-rw-r--r--maths/Android.mk30
-rw-r--r--maths/CHANGELOG.txt110
-rw-r--r--maths/LICENCE.txt202
-rw-r--r--maths/NOTICE.txt19
-rw-r--r--maths/README.android7
-rw-r--r--maths/build.xml292
-rw-r--r--maths/core/pom.xml36
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/Maths.java230
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/binary/BinaryUtils.java168
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/binary/BitString.java354
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/binary/package-info.java20
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/combinatorics/CombinationGenerator.java288
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/combinatorics/PermutationGenerator.java304
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/combinatorics/package-info.java23
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/number/AdjustableNumberGenerator.java81
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/number/ConstantGenerator.java46
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/number/NumberGenerator.java34
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/number/Rational.java342
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/number/package-info.java20
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/package-info.java20
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/AESCounterRNG.java216
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/BinomialGenerator.java146
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/CMWC4096RNG.java129
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/CellularAutomatonRNG.java199
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/ContinuousUniformGenerator.java49
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/DefaultSeedGenerator.java98
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/DevRandomSeedGenerator.java90
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/DiehardInputGenerator.java91
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/DiscreteUniformGenerator.java49
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/ExponentialGenerator.java83
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/GaussianGenerator.java86
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/JavaRNG.java96
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/MersenneTwisterRNG.java204
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/PoissonGenerator.java90
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/Probability.java214
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/RandomDotOrgSeedGenerator.java140
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/RepeatableRNG.java32
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/SecureRandomSeedGenerator.java52
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/SeedException.java42
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/SeedGenerator.java31
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/XORShiftRNG.java128
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/random/package-info.java22
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/statistics/DataSet.java362
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/statistics/EmptyDataSetException.java30
-rw-r--r--maths/core/src/java/main/org/uncommons/maths/statistics/package-info.java20
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/MathsTest.java213
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/binary/BinaryUtilsTest.java171
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/binary/BitStringTest.java327
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/combinatorics/CombinationGeneratorTest.java207
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/combinatorics/PermutationGeneratorTest.java181
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/number/RationalTest.java211
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/AESCounterRNGTest.java101
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/BinomialGeneratorTest.java131
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/CMWC4096RNGTest.java124
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/CellularAutomatonRNGTest.java122
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/ContinuousUniformGeneratorTest.java63
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/DefaultSeedGeneratorTest.java82
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/DevRandomSeedGeneratorTest.java46
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/DiehardInputGeneratorTest.java47
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/DiscreteUniformGeneratorTest.java63
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/ExponentialGeneratorTest.java83
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/GaussianGeneratorTest.java91
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/JavaRNGTest.java70
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/MersenneTwisterRNGTest.java122
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/PoissonGeneratorTest.java98
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/ProbabilityTest.java169
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/RNGBenchmark.java71
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/RNGTestUtils.java130
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/RandomDotOrgSeedGeneratorTest.java48
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/SecureRandomSeedGeneratorTest.java34
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/random/XORShiftRNGTest.java124
-rw-r--r--maths/core/src/java/test/org/uncommons/maths/statistics/DataSetTest.java194
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/BinomialDistribution.java108
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/BinomialParametersPanel.java51
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/DistributionPanel.java80
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/ExponentialDistribution.java97
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/ExponentialParametersPanel.java47
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/GaussianDistribution.java95
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/GaussianParametersPanel.java51
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/GraphPanel.java94
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/ParametersPanel.java33
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/PoissonDistribution.java100
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/PoissonParametersPanel.java47
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/ProbabilityDistribution.java125
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/RNGPanel.java96
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/RandomDemo.java157
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/UniformDistribution.java77
-rw-r--r--maths/demo/src/java/main/org/uncommons/maths/demo/UniformParametersPanel.java51
-rw-r--r--maths/demo/src/java/main/org/uncommons/swing/SpringUtilities.java213
-rw-r--r--maths/demo/src/java/main/org/uncommons/swing/SwingBackgroundTask.java100
-rw-r--r--maths/demo/src/java/test/org/uncommons/swing/SwingBackgroundTaskTest.java58
-rw-r--r--maths/etc/intellij/Core.iml17
-rw-r--r--maths/etc/intellij/Demo.iml36
-rw-r--r--maths/etc/intellij/Global.iml14
-rw-r--r--maths/etc/intellij/Uncommons Maths.ipr515
-rw-r--r--maths/etc/testng.xml36
-rw-r--r--maths/uncommons-maths.bnd5
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&lt;Integer&gt; elements = Arrays.asList(1, 2, 3);
+ * CombinationGenerator&lt;Integer&gt; combinations = new CombinationGenerator(elements, 2);
+ * for (List&lt;Integer&gt; 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&lt;Integer&gt; elements = Arrays.asList(1, 2, 3);
+ * PermutationGenerator&lt;Integer&gt; permutations = new PermutationGenerator(elements);
+ * for (List&lt;Integer&gt; 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}
+