├── .codeclimate.yml ├── .document ├── .gitignore ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── VERSION ├── lib ├── simple-random.rb └── simple-random │ ├── multi_threaded_simple_random.rb │ └── simple_random.rb ├── simple-random.gemspec └── test ├── helper.rb └── test_simple_random.rb /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | brakeman: 4 | enabled: true 5 | bundler-audit: 6 | enabled: true 7 | fixme: 8 | enabled: true 9 | rubocop: 10 | enabled: true 11 | ratings: 12 | paths: 13 | - Gemfile.lock 14 | - "**.rb" 15 | exclude_paths: 16 | - pkg/**/* 17 | - vendor/**/* 18 | - test/**/* 19 | - tmp/**/* 20 | - coverage/**/* 21 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PROJECT::GENERAL 17 | coverage 18 | rdoc 19 | pkg 20 | 21 | ## PROJECT::SPECIFIC 22 | .rbenv-version 23 | .rvmrc 24 | .ruby-version 25 | .ruby-gemset 26 | 27 | tmp 28 | 29 | Gemfile.lock 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # single test suite, non-parallel build. 2 | 3 | env: 4 | global: 5 | - CC_TEST_REPORTER_ID=fc1330064eb82caf0f39ecd509e3e1c5b4fc5ca8b02fb9fa63d6e8a92da5ad18 6 | language: ruby 7 | rvm: 8 | - 2.6 9 | - 2.5 10 | - 2.4 11 | - 2.3.6 12 | - 2.2.0 13 | before_install: 14 | - gem install bundler --version 1.17.3 15 | before_script: 16 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter 17 | - chmod +x ./cc-test-reporter 18 | - ./cc-test-reporter before-build 19 | script: 20 | - bundle exec rake test 21 | after_script: 22 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT 23 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | group :development do 4 | gem 'awesome_print', '>= 1.6' 5 | gem 'minitest' 6 | gem 'shoulda' 7 | gem 'rdoc', '~> 3.12' 8 | gem 'jeweler', '~> 2.0.1' 9 | gem 'simplecov' 10 | gem 'nokogiri', '>= 1.8.5' 11 | gem 'codeclimate-test-reporter', require: nil 12 | end 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ## See http://opensource.org/licenses/CDDL-1.0 2 | 3 | COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 (CDDL-1.0) 4 | 1. Definitions. 5 | 6 | 1.1. Contributor means each individual or entity that creates or contributes to the creation of Modifications. 7 | 8 | 1.2. Contributor Version means the combination of the Original Software, prior Modifications used by a Contributor (if any), and the Modifications made by that particular Contributor. 9 | 10 | 1.3. Covered Software means (a) the Original Software, or (b) Modifications, or (c) the combination of files containing Original Software with files containing Modifications, in each case including portions thereof. 11 | 12 | 1.4. Executable means the Covered Software in any form other than Source Code. 13 | 14 | 1.5. Initial Developer means the individual or entity that first makes Original Software available under this License. 15 | 16 | 1.6. Larger Work means a work which combines Covered Software or portions thereof with code not governed by the terms of this License. 17 | 18 | 1.7. License means this document. 19 | 20 | 1.8. Licensable means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently acquired, any and all of the rights conveyed herein. 21 | 22 | 1.9. Modifications means the Source Code and Executable form of any of the following: 23 | 24 | A. Any file that results from an addition to, deletion from or modification of the contents of a file containing Original Software or previous Modifications; 25 | 26 | B. Any new file that contains any part of the Original Software or previous Modification; or 27 | 28 | C. Any new file that is contributed or otherwise made available under the terms of this License. 29 | 30 | 1.10. Original Software means the Source Code and Executable form of computer software code that is originally released under this License. 31 | 32 | 1.11. Patent Claims means any patent claim(s), now owned or hereafter acquired, including without limitation, method, process, and apparatus claims, in any patent Licensable by grantor. 33 | 34 | 1.12. Source Code means (a) the common form of computer software code in which modifications are made and (b) associated documentation included in or with such code. 35 | 36 | 1.13. You (or Your) means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, You includes any entity which controls, is controlled by, or is under common control with You. For purposes of this definition, control means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 37 | 38 | 2. License Grants. 39 | 40 | 2.1. The Initial Developer Grant. 41 | 42 | Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, the Initial Developer hereby grants You a world-wide, royalty-free, non-exclusive license: 43 | 44 | (a) under intellectual property rights (other than patent or trademark) Licensable by Initial Developer, to use, reproduce, modify, display, perform, sublicense and distribute the Original Software (or portions thereof), with or without Modifications, and/or as part of a Larger Work; and 45 | 46 | (b) under Patent Claims infringed by the making, using or selling of Original Software, to make, have made, use, practice, sell, and offer for sale, and/or otherwise dispose of the Original Software (or portions thereof). 47 | 48 | (c) The licenses granted in Sections 2.1(a) and (b) are effective on the date Initial Developer first distributes or otherwise makes the Original Software available to a third party under the terms of this License. 49 | 50 | (d) Notwithstanding Section 2.1(b) above, no patent license is granted: (1) for code that You delete from the Original Software, or (2) for infringements caused by: (i) the modification of the Original Software, or (ii) the combination of the Original Software with other software or devices. 51 | 52 | 2.2. Contributor Grant. 53 | 54 | Conditioned upon Your compliance with Section 3.1 below and subject to third party intellectual property claims, each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: 55 | 56 | (a) under intellectual property rights (other than patent or trademark) Licensable by Contributor to use, reproduce, modify, display, perform, sublicense and distribute the Modifications created by such Contributor (or portions thereof), either on an unmodified basis, with other Modifications, as Covered Software and/or as part of a Larger Work; and 57 | 58 | (b) under Patent Claims infringed by the making, using, or selling of Modifications made by that Contributor either alone and/or in combination with its Contributor Version (or portions of such combination), to make, use, sell, offer for sale, have made, and/or otherwise dispose of: (1) Modifications made by that Contributor (or portions thereof); and (2) the combination of Modifications made by that Contributor with its Contributor Version (or portions of such combination). 59 | 60 | (c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective on the date Contributor first distributes or otherwise makes the Modifications available to a third party. 61 | 62 | (d) Notwithstanding Section 2.2(b) above, no patent license is granted: (1) for any code that Contributor has deleted from the Contributor Version; (2) for infringements caused by: (i) third party modifications of Contributor Version, or (ii) the combination of Modifications made by that Contributor with other software (except as part of the Contributor Version) or other devices; or (3) under Patent Claims infringed by Covered Software in the absence of Modifications made by that Contributor. 63 | 64 | 3. Distribution Obligations. 65 | 66 | 3.1. Availability of Source Code. 67 | 68 | Any Covered Software that You distribute or otherwise make available in Executable form must also be made available in Source Code form and that Source Code form must be distributed only under the terms of this License. You must include a copy of this License with every copy of the Source Code form of the Covered Software You distribute or otherwise make available. You must inform recipients of any such Covered Software in Executable form as to how they can obtain such Covered Software in Source Code form in a reasonable manner on or through a medium customarily used for software exchange. 69 | 70 | 3.2. Modifications. 71 | 72 | The Modifications that You create or to which You contribute are governed by the terms of this License. You represent that You believe Your Modifications are Your original creation(s) and/or You have sufficient rights to grant the rights conveyed by this License. 73 | 74 | 3.3. Required Notices. 75 | 76 | You must include a notice in each of Your Modifications that identifies You as the Contributor of the Modification. You may not remove or alter any copyright, patent or trademark notices contained within the Covered Software, or any notices of licensing or any descriptive text giving attribution to any Contributor or the Initial Developer. 77 | 78 | 3.4. Application of Additional Terms. 79 | 80 | You may not offer or impose any terms on any Covered Software in Source Code form that alters or restricts the applicable version of this License or the recipients rights hereunder. You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, you may do so only on Your own behalf, and not on behalf of the Initial Developer or any Contributor. You must make it absolutely clear that any such warranty, support, indemnity or liability obligation is offered by You alone, and You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of warranty, support, indemnity or liability terms You offer. 81 | 82 | 3.5. Distribution of Executable Versions. 83 | 84 | You may distribute the Executable form of the Covered Software under the terms of this License or under the terms of a license of Your choice, which may contain terms different from this License, provided that You are in compliance with the terms of this License and that the license for the Executable form does not attempt to limit or alter the recipients rights in the Source Code form from the rights set forth in this License. If You distribute the Covered Software in Executable form under a different license, You must make it absolutely clear that any terms which differ from this License are offered by You alone, not by the Initial Developer or Contributor. You hereby agree to indemnify the Initial Developer and every Contributor for any liability incurred by the Initial Developer or such Contributor as a result of any such terms You offer. 85 | 86 | 3.6. Larger Works. 87 | 88 | You may create a Larger Work by combining Covered Software with other code not governed by the terms of this License and distribute the Larger Work as a single product. In such a case, You must make sure the requirements of this License are fulfilled for the Covered Software. 89 | 90 | 4. Versions of the License. 91 | 92 | 4.1. New Versions. 93 | 94 | Sun Microsystems, Inc. is the initial license steward and may publish revised and/or new versions of this License from time to time. Each version will be given a distinguishing version number. Except as provided in Section 4.3, no one other than the license steward has the right to modify this License. 95 | 96 | 4.2. Effect of New Versions. 97 | 98 | You may always continue to use, distribute or otherwise make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. If the Initial Developer includes a notice in the Original Software prohibiting it from being distributed or otherwise made available under any subsequent version of the License, You must distribute and make the Covered Software available under the terms of the version of the License under which You originally received the Covered Software. Otherwise, You may also choose to use, distribute or otherwise make the Covered Software available under the terms of any subsequent version of the License published by the license steward. 99 | 100 | 4.3. Modified Versions. 101 | 102 | When You are an Initial Developer and You want to create a new license for Your Original Software, You may create and use a modified version of this License if You: (a) rename the license and remove any references to the name of the license steward (except to note that the license differs from this License); and (b) otherwise make it clear that the license contains terms which differ from this License. 103 | 104 | 5. DISCLAIMER OF WARRANTY. 105 | 106 | COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN AS IS BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. 107 | 108 | 6. TERMINATION. 109 | 110 | 6.1. This License and the rights granted hereunder will terminate automatically if You fail to comply with terms herein and fail to cure such breach within 30 days of becoming aware of the breach. Provisions which, by their nature, must remain in effect beyond the termination of this License shall survive. 111 | 112 | 6.2. If You assert a patent infringement claim (excluding declaratory judgment actions) against Initial Developer or a Contributor (the Initial Developer or Contributor against whom You assert such claim is referred to as Participant) alleging that the Participant Software (meaning the Contributor Version where the Participant is a Contributor or the Original Software where the Participant is the Initial Developer) directly or indirectly infringes any patent, then any and all rights granted directly or indirectly to You by such Participant, the Initial Developer (if the Initial Developer is not the Participant) and all Contributors under Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice from Participant terminate prospectively and automatically at the expiration of such 60 day notice period, unless if within such 60 day period You withdraw Your claim with respect to the Participant Software against such Participant either unilaterally or pursuant to a written agreement with Participant. 113 | 114 | 6.3. In the event of termination under Sections 6.1 or 6.2 above, all end user licenses that have been validly granted by You or any distributor hereunder prior to termination (excluding licenses granted to You by any distributor) shall survive termination. 115 | 116 | 7. LIMITATION OF LIABILITY. 117 | 118 | UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOST PROFITS, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH PARTYS NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. 119 | 120 | 8. U.S. GOVERNMENT END USERS. 121 | 122 | The Covered Software is a commercial item, as that term is defined in 48 C.F.R. 2.101 (Oct. 1995), consisting of commercial computer software (as that term is defined at 48 C.F.R. 252.227-7014(a)(1)) and commercial computer software documentation as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users acquire Covered Software with only those rights set forth herein. This U.S. Government Rights clause is in lieu of, and supersedes, any other FAR, DFAR, or other clause or provision that addresses Government rights in computer software under this License. 123 | 124 | 9. MISCELLANEOUS. 125 | 126 | This License represents the complete agreement concerning subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. This License shall be governed by the law of the jurisdiction specified in a notice contained within the Original Software (except to the extent applicable law, if any, provides otherwise), excluding such jurisdictions conflict-of-law provisions. Any litigation relating to this License shall be subject to the jurisdiction of the courts located in the jurisdiction and venue specified in a notice contained within the Original Software, with the losing party responsible for costs, including, without limitation, court costs and reasonable attorneys fees and expenses. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not apply to this License. You agree that You alone are responsible for compliance with the United States export administration regulations (and the export control laws and regulation of any other countries) when You use, distribute or otherwise make available any Covered Software. 127 | 128 | 10. RESPONSIBILITY FOR CLAIMS. 129 | 130 | As between Initial Developer and the Contributors, each party is responsible for claims and damages arising, directly or indirectly, out of its utilization of rights under this License and You agree to work with Initial Developer and Contributors to distribute such responsibility on an equitable basis. Nothing herein is intended or shall be deemed to constitute any admission of liability. 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-random ![Build status](https://travis-ci.com/ealdent/simple-random.svg?branch=master) 2 | 3 | Generate random numbers sampled from the following distributions: 4 | 5 | * Beta 6 | * Cauchy 7 | * Chi square 8 | * Dirichlet 9 | * Exponential 10 | * Gamma 11 | * Inverse gamma 12 | * Laplace (double exponential) 13 | * Normal 14 | * Student t 15 | * Triangular 16 | * Uniform 17 | * Weibull 18 | 19 | Based on [John D. Cook's SimpleRNG](http://www.codeproject.com/KB/recipes/SimpleRNG.aspx) C# library. 20 | 21 | ## Installation 22 | 23 | ### Plain Ruby 24 | 25 | Run `gem install simple-random` in your terminal. 26 | 27 | ### Ruby on Rails 28 | 29 | Add `gem 'simple-random', '~> 1.0.0'` to your Gemfile and run `bundle install`. 30 | 31 | 32 | ## Usage 33 | 34 | Some of the methods available: 35 | 36 | ``` ruby 37 | > r = SimpleRandom.new # Initialize a SimpleRandom instance 38 | => # 39 | > r.set_seed # By default the same random seed is used, so we change it 40 | > r.uniform(0, 5) # Produce a uniform random sample from the open interval (lower, upper). 41 | => 0.6353204359766096 42 | > r.normal(1000, 200) # Sample normal distribution with given mean and standard deviation 43 | => 862.5447157384566 44 | > r.exponential(2) # Get exponential random sample with specified mean 45 | => 0.9386480625062965 46 | > r.triangular(0, 2.5, 10) # Get triangular random sample with specified lower limit, mode, upper limit 47 | => 3.1083306054169277 48 | ``` 49 | 50 | Note that by default the same seed is used every time to generate the random numbers. This means that repeated runs should yield the same results. If you would like it to always initialize with a different seed, or if you are using multiple SimpleRandom objects, you should call `#set_seed` on the instance. 51 | 52 | See [lib/simple-random.rb](lib/simple-random/simple_random.rb) for all available methods and options. 53 | 54 | 55 | ## Note on Patches/Pull Requests 56 | 57 | * Fork the project. 58 | * Make your feature addition or bug fix. 59 | * Add tests for it. This is important so I don't break it in a future version unintentionally. 60 | * Commit, but please do not mess with the gemspec, `Rakefile`, `VERSION`, `LICENSE`, or `.travis.yml`. 61 | (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 62 | * Send me a pull request. 63 | 64 | HT: This list was modified from the default instructions that ship with [jeweler](https://github.com/technicalpickles/jeweler). 65 | 66 | ## Copyright 67 | 68 | Distributed under the Code Project Open License, which is similar to MIT or BSD. See LICENSE for full details (don't just take my word for it that it's similar to those licenses). 69 | 70 | ## History 71 | 72 | ### 1.0.3 - 2015-11-25 73 | * Attempt to reduce code complexity and improve readability 74 | * Change error handling somewhat to throw specific errors and improve messages 75 | 76 | ### 1.0.2 - 2015-11-24 77 | * Merge pull request from [cunchem](https://github.com/cunchem) to fix Laplace method 78 | 79 | ### 1.0.1 - 2015-07-31 80 | * Merge [purcell](https://github.com/purcell)'s changes to fix numeric seeds 81 | 82 | ### 1.0.0 - 2014-07-08 83 | * Migrate to new version of Jeweler for gem packaging 84 | * Merge [jwroblewski](https://github.com/jwroblewski)'s changes into a new multi-threaded simple random class 85 | * Change from Code Project Open License to [CDDL-1.0](http://opensource.org/licenses/CDDL-1.0) 86 | 87 | ### 0.10.0 - 2014-03-31 88 | * Sample from triangular distribution (thanks to [benedictleejh](https://github.com/benedictleejh)) 89 | 90 | ### 0.9.3 - 2011-09-16 91 | * Sample from Dirichlet distribution with given set of parameters 92 | 93 | ### 0.9.2 - 2011-09-06 94 | * Use microseconds for random seed 95 | 96 | ### 0.9.1 - 2010-07-27 97 | * First stable release 98 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options 17 | gem.name = "simple-random" 18 | gem.homepage = "http://github.com/ealdent/simple-random" 19 | gem.licenses = "CDDL-1.0" 20 | gem.summary = %Q{Simple Random Number Generator} 21 | gem.description = %Q{Simple Random Number Generator including Beta, Cauchy, Chi square, Exponential, Gamma, Inverse Gamma, Laplace (double exponential), Normal, Student t, Uniform, and Weibull. Ported from John D. Cook's C# Code.} 22 | gem.email = "jasonmadams@gmail.com" 23 | gem.authors = ["John D. Cook", "Jason Adams"] 24 | # dependencies defined in Gemfile 25 | end 26 | Jeweler::RubygemsDotOrgTasks.new 27 | 28 | require 'rake/testtask' 29 | Rake::TestTask.new(:test) do |test| 30 | test.libs << 'lib' << 'test' 31 | test.pattern = 'test/**/test_*.rb' 32 | test.verbose = true 33 | end 34 | 35 | desc "Code coverage detail" 36 | task :simplecov do 37 | ENV['COVERAGE'] = "true" 38 | Rake::Task['test'].execute 39 | end 40 | 41 | task :default => :test 42 | 43 | require 'rdoc/task' 44 | Rake::RDocTask.new do |rdoc| 45 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 46 | 47 | rdoc.rdoc_dir = 'rdoc' 48 | rdoc.title = "simple-random #{version}" 49 | rdoc.rdoc_files.include('README*') 50 | rdoc.rdoc_files.include('lib/**/*.rb') 51 | end 52 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.3 -------------------------------------------------------------------------------- /lib/simple-random.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'simple-random', 'simple_random') 2 | require File.join(File.dirname(__FILE__), 'simple-random', 'multi_threaded_simple_random') 3 | -------------------------------------------------------------------------------- /lib/simple-random/multi_threaded_simple_random.rb: -------------------------------------------------------------------------------- 1 | require 'monitor' 2 | 3 | class MultiThreadedSimpleRandom < SimpleRandom 4 | class << self 5 | @instances = nil 6 | 7 | def instance 8 | unless instance_variable_defined?('@instances') && @instances 9 | extend MonitorMixin 10 | 11 | self.synchronize do 12 | @instances ||= {} 13 | end 14 | end 15 | 16 | instance_id = Thread.current.object_id 17 | 18 | unless @instances[instance_id] 19 | self.synchronize do 20 | @instances[instance_id] ||= new 21 | end 22 | end 23 | 24 | @instances[instance_id] 25 | end 26 | end 27 | 28 | private_class_method :new 29 | end 30 | -------------------------------------------------------------------------------- /lib/simple-random/simple_random.rb: -------------------------------------------------------------------------------- 1 | class SimpleRandom 2 | class InvalidSeedArgument < StandardError; end 3 | 4 | I_32_BIT = 4294967296 5 | F_32_BIT = 4294967296.0 6 | DEFAULT_SEEDS = [521288629, 362436069] 7 | 8 | def initialize(*args) 9 | if args.empty? 10 | set_seed(*DEFAULT_SEEDS) 11 | else 12 | set_seed(*args) 13 | end 14 | end 15 | 16 | def set_seed(*args) 17 | validate_seeds!(*args) 18 | 19 | @m_w, @m_z = determine_seeds(*args) 20 | 21 | ensure_32bit_seeds! 22 | end 23 | 24 | def seeds=(value) 25 | set_seed(*[value].flatten.compact) 26 | end 27 | 28 | def seeds 29 | [@m_w, @m_z] 30 | end 31 | 32 | # Produce a uniform random sample from the open interval (lower, upper). 33 | def uniform(lower = 0, upper = 1) 34 | fail ArgumentError, 'Upper bound must be greater than lower bound.' unless lower < upper 35 | 36 | ((get_unsigned_int + 1) * (upper - lower) / F_32_BIT) + lower 37 | end 38 | 39 | # Sample normal distribution with given mean and standard deviation 40 | def normal(mean = 0.0, standard_deviation = 1.0) 41 | fail ArgumentError, 'Standard deviation must be strictly positive' unless standard_deviation > 0 42 | 43 | mean + standard_deviation * ((-2.0 * Math.log(uniform)) ** 0.5) * Math.sin(2.0 * Math::PI * uniform) 44 | end 45 | 46 | # Get exponential random sample with specified mean 47 | def exponential(mean = 1) 48 | fail ArgumentError, "Mean must be strictly positive" unless mean > 0 49 | 50 | -1.0 * mean * Math.log(uniform) 51 | end 52 | 53 | # Get triangular random sample with specified lower limit, mode, upper limit 54 | def triangular(lower, mode, upper) 55 | fail ArgumentError, 'Upper bound must be greater than lower bound.' unless lower < upper 56 | fail ArgumentError, 'Mode must lie between the upper and lower limits' if mode > upper || mode < lower 57 | 58 | r = (upper - lower).to_f 59 | u = uniform 60 | 61 | if u < ((mode - lower) / r) 62 | lower + Math.sqrt(u * r * (mode - lower)) 63 | else 64 | upper - Math.sqrt((1.0 - u) * r * (upper - mode)) 65 | end 66 | end 67 | 68 | # Implementation based on "A Simple Method for Generating Gamma Variables" 69 | # by George Marsaglia and Wai Wan Tsang. ACM Transactions on Mathematical Software 70 | # Vol 26, No 3, September 2000, pages 363-372. 71 | def gamma(shape, scale) 72 | fail ArgumentError, 'Shape must be strictly positive' unless shape > 0 73 | return scale * gamma(shape + 1.0, 1.0) * uniform ** -shape if shape < 1 74 | 75 | d = shape - 1 / 3.0 76 | c = (9 * d) ** -0.5 77 | 78 | begin 79 | z = normal 80 | 81 | condition1 = z > (-1.0 / c) 82 | condition2 = false 83 | 84 | if condition1 85 | u = uniform 86 | v = (1 + c * z) ** 3 87 | condition2 = Math.log(u) < (0.5 * (z ** 2) + d * (1.0 - v + Math.log(v))) 88 | end 89 | end while !condition2 90 | 91 | scale * d * v 92 | end 93 | 94 | def chi_square(degrees_of_freedom) 95 | gamma(0.5 * degrees_of_freedom, 2.0) 96 | end 97 | 98 | def inverse_gamma(shape, scale) 99 | 1.0 / gamma(shape, 1.0 / scale) 100 | end 101 | 102 | def beta(a, b) 103 | fail ArgumentError, "Parameters must be strictly positive" unless a > 0 && b > 0 104 | u = gamma(a, 1) 105 | v = gamma(b, 1) 106 | u / (u + v) 107 | end 108 | 109 | def weibull(shape, scale) 110 | fail ArgumentError, 'Shape and scale must be positive' unless shape > 0 && scale > 0 111 | 112 | scale * ((-Math.log(uniform)) ** (1.0 / shape)) 113 | end 114 | 115 | def cauchy(median, scale) 116 | fail ArgumentError, 'Scale must be positive' unless scale > 0 117 | 118 | median + scale * Math.tan(Math::PI * (uniform - 0.5)) 119 | end 120 | 121 | def student_t(degrees_of_freedom) 122 | fail ArgumentError, 'Degrees of freedom must be strictly positive' unless degrees_of_freedom > 0 123 | 124 | normal / ((chi_square(degrees_of_freedom) / degrees_of_freedom) ** 0.5) 125 | end 126 | 127 | def laplace(mean, scale) 128 | u_1 = uniform(-0.5, 0.5) 129 | u_2 = uniform 130 | 131 | sign = u_1 / u_1.abs 132 | mean + sign * scale * Math.log(1 - u_2) 133 | end 134 | 135 | def log_normal(mu, sigma) 136 | Math.exp(normal(mu, sigma)) 137 | end 138 | 139 | def dirichlet(*parameters) 140 | sample = parameters.map { |a| gamma(a, 1) } 141 | sum = sample.inject(0.0) { |sum, g| sum + g } 142 | sample.map { |g| g / sum } 143 | end 144 | 145 | private 146 | 147 | # This is the heart of the generator. 148 | # It uses George Marsaglia's MWC algorithm to produce an unsigned integer. 149 | # See http://www.bobwheeler.com/statistics/Password/MarsagliaPost.txt 150 | def get_unsigned_int 151 | @m_z = 36969 * (@m_z & 65535) + (@m_z >> 16); 152 | @m_w = 18000 * (@m_w & 65535) + (@m_w >> 16); 153 | ((@m_z << 16) + (@m_w & 65535)) % 4294967296 154 | end 155 | 156 | def validate_seeds!(*args) 157 | return true if args.compact.empty? 158 | 159 | unless args[0].to_f > 0 160 | fail InvalidSeedArgument, 'Seeds must be strictly positive' 161 | end 162 | 163 | unless args[1].nil? || args[1].to_f > 0 164 | fail InvalidSeedArgument, 'Seeds must be strictly positive' 165 | end 166 | 167 | true 168 | end 169 | 170 | def generate_temporal_seed(timestamp = Time.now) 171 | x = (timestamp.to_f * 1000000).to_i 172 | 173 | [x >> 16, x % 4294967296] 174 | end 175 | 176 | def gamma_function(x) 177 | return 1e308 if x > 171.0 178 | 179 | if x.to_f == x.to_i 180 | return unless x > 0 181 | return 1 if x.to_i == 1 182 | 183 | (1...x).inject(&:*) 184 | else 185 | z = if x.abs > 1.0 186 | x.abs - x.abs.to_i 187 | else 188 | x 189 | end 190 | 191 | gr = GAMMA_VALUES.inject(GAMMA_NAUGHT) do |sum, g| 192 | sum * z + g 193 | end 194 | 195 | r = if x.abs > 1 196 | (1..(x.abs.to_i)).inject(1.0) { |prod, i| prod * (x.abs - i) } 197 | else 198 | 1.0 199 | end 200 | 201 | if x < 0 && x.abs > 1 202 | -Math::PI * gr * z / (x * r * Math.sin(Math::PI * x)) 203 | else 204 | r / (gr * z) 205 | end 206 | end 207 | end 208 | 209 | def ensure_32bit_seeds! 210 | @m_w = @m_w.to_i % I_32_BIT 211 | @m_z = @m_z.to_i % I_32_BIT 212 | end 213 | 214 | def determine_seeds(*args) 215 | return generate_temporal_seed(args.first || Time.now) if args.empty? || args.first.respond_to?(:iso8601) 216 | return [DEFAULT_SEEDS.first, args.first] if args.size < 2 217 | args[0..1] 218 | end 219 | 220 | GAMMA_NAUGHT = 0.14e-14 221 | 222 | GAMMA_VALUES = [ 223 | -5.4e-15, 224 | -2.06e-14, 225 | 5.1e-13, 226 | -3.6968e-12, 227 | 7.7823e-12, 228 | 1.043427e-10, 229 | -1.1812746e-09, 230 | 5.0020075e-09, 231 | 6.116095e-09, 232 | -2.056338417e-07, 233 | 1.133027232e-06, 234 | -1.2504934821e-06, 235 | -2.01348547807e-05, 236 | 0.0001280502823882, 237 | -0.0002152416741149, 238 | -0.0011651675918591, 239 | 0.007218943246663, 240 | -0.009621971527877, 241 | -0.0421977345555443, 242 | 0.1665386113822915, 243 | -0.0420026350340952, 244 | -0.6558780715202538, 245 | 0.5772156649015329, 246 | 1.0 247 | ] 248 | end 249 | -------------------------------------------------------------------------------- /simple-random.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | # stub: simple-random 1.0.1 ruby lib 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "simple-random" 9 | s.version = "1.0.2" 10 | 11 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 12 | s.require_paths = ["lib"] 13 | s.authors = ["John D. Cook", "Jason Adams"] 14 | s.date = "2015-11-24" 15 | s.description = "Simple Random Number Generator including Beta, Cauchy, Chi square, Exponential, Gamma, Inverse Gamma, Laplace (double exponential), Normal, Student t, Uniform, and Weibull. Ported from John D. Cook's C# Code." 16 | s.email = "jasonmadams@gmail.com" 17 | s.extra_rdoc_files = [ 18 | "LICENSE", 19 | "README.md" 20 | ] 21 | s.files = [ 22 | ".document", 23 | ".travis.yml", 24 | "Gemfile", 25 | "Gemfile.lock", 26 | "LICENSE", 27 | "README.md", 28 | "Rakefile", 29 | "VERSION", 30 | "lib/simple-random.rb", 31 | "lib/simple-random/multi_threaded_simple_random.rb", 32 | "lib/simple-random/simple_random.rb", 33 | "simple-random.gemspec", 34 | "test/helper.rb", 35 | "test/test_simple_random.rb" 36 | ] 37 | s.homepage = "http://github.com/ealdent/simple-random" 38 | s.licenses = ["CDDL-1.0"] 39 | s.rubygems_version = "2.2.2" 40 | s.summary = "Simple Random Number Generator" 41 | 42 | if s.respond_to? :specification_version then 43 | s.specification_version = 4 44 | 45 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 46 | s.add_development_dependency(%q, [">= 0"]) 47 | s.add_development_dependency(%q, [">= 0"]) 48 | s.add_development_dependency(%q, ["~> 3.12"]) 49 | s.add_development_dependency(%q, ["~> 1.0"]) 50 | s.add_development_dependency(%q, ["~> 2.0.1"]) 51 | s.add_development_dependency(%q, [">= 0"]) 52 | else 53 | s.add_dependency(%q, [">= 0"]) 54 | s.add_dependency(%q, [">= 0"]) 55 | s.add_dependency(%q, ["~> 3.12"]) 56 | s.add_dependency(%q, ["~> 1.0"]) 57 | s.add_dependency(%q, ["~> 2.0.1"]) 58 | s.add_dependency(%q, [">= 0"]) 59 | end 60 | else 61 | s.add_dependency(%q, [">= 0"]) 62 | s.add_dependency(%q, [">= 0"]) 63 | s.add_dependency(%q, ["~> 3.12"]) 64 | s.add_dependency(%q, ["~> 1.0"]) 65 | s.add_dependency(%q, ["~> 2.0.1"]) 66 | s.add_dependency(%q, [">= 0"]) 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | require 'awesome_print' 3 | 4 | module SimpleCov::Configuration 5 | def clean_filters 6 | @filters = [] 7 | end 8 | end 9 | 10 | SimpleCov.configure do 11 | clean_filters 12 | load_profile 'test_frameworks' 13 | end 14 | 15 | ENV["COVERAGE"] && SimpleCov.start do 16 | add_filter "/.rvm/" 17 | end 18 | require 'rubygems' 19 | require 'bundler' 20 | begin 21 | Bundler.setup(:default, :development) 22 | rescue Bundler::BundlerError => e 23 | $stderr.puts e.message 24 | $stderr.puts "Run `bundle install` to install missing gems" 25 | exit e.status_code 26 | end 27 | require 'minitest/autorun' 28 | require 'shoulda' 29 | 30 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 31 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 32 | require 'simple-random' 33 | 34 | class MiniTest::Test 35 | end 36 | 37 | MiniTest.autorun 38 | -------------------------------------------------------------------------------- /test/test_simple_random.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | # TODO: use Kolmogorov-Smirnov test instead: http://en.wikipedia.org/wiki/Kolmogorov_Smirnov 4 | 5 | SAMPLE_SIZE = 10000 6 | MAXIMUM_EPSILON = 0.01 7 | 8 | def generate_numbers(generator, distribution, *args) 9 | (1..SAMPLE_SIZE).map { generator.send(distribution.to_sym, *args) } 10 | end 11 | 12 | class Array 13 | def mean 14 | if size > 0 15 | inject(&:+) / size.to_f 16 | else 17 | 0.0 18 | end 19 | end 20 | 21 | def standard_deviation 22 | if size > 1 23 | m = mean 24 | (inject(0.0) { |sum, i| sum + ((i - m) ** 2) } / (size - 1).to_f) ** 0.5 25 | else 26 | 1.0 27 | end 28 | end 29 | end 30 | 31 | def Time.now 32 | new(2015, 11, 26, 12, 1, 15, '-05:00') 33 | end 34 | 35 | class TestSimpleRandom < MiniTest::Test 36 | context "Setting the seeds for a simple random number generator" do 37 | context "on initialization" do 38 | should "assign default seeds when none are specified" do 39 | r = SimpleRandom.new 40 | assert r.seeds == SimpleRandom::DEFAULT_SEEDS 41 | end 42 | 43 | should "assign the seeds specified in the initializer" do 44 | r = SimpleRandom.new(1, 2) 45 | assert r.seeds == [1, 2] 46 | end 47 | 48 | should "reject negative seed values" do 49 | assert_raises SimpleRandom::InvalidSeedArgument do 50 | SimpleRandom.new(-1, 3) 51 | end 52 | end 53 | end 54 | 55 | context 'after initialization' do 56 | setup do 57 | @r = SimpleRandom.new 58 | end 59 | 60 | should "accept a single value and leave the first seed the same" do 61 | @r.seeds = 1 62 | assert @r.seeds == [521288629, 1] 63 | end 64 | 65 | should "update the seeds when given an array of values" do 66 | @r.seeds = [1, 2] 67 | assert @r.seeds == [1, 2] 68 | end 69 | 70 | should "accept a timestamp instead of an numeric value" do 71 | @r.seeds = Time.parse('2015-01-01T00:00:00.000-0500') 72 | assert @r.seeds == [193992865, 413250560] 73 | end 74 | 75 | should "use the current timestamp when nothing is specified" do 76 | @r.seeds = nil 77 | assert @r.seeds == [628393424, 2245012672] 78 | end 79 | end 80 | 81 | should "provide different results with different integer seeds" do 82 | r1 = SimpleRandom.new 83 | r1.set_seed(2) 84 | r2 = SimpleRandom.new 85 | r2.set_seed(1234512343214134) 86 | 87 | r1_randoms = 100.times.map { r1.uniform(0, 10).floor } 88 | r2_randoms = 100.times.map { r2.uniform(0, 10).floor } 89 | 90 | assert r1_randoms != r2_randoms 91 | end 92 | end 93 | 94 | context "A simple random number generator" do 95 | setup do 96 | @r = SimpleRandom.new 97 | end 98 | 99 | should "generate random numbers from a uniform distribution in the interval (0, 1)" do 100 | SAMPLE_SIZE.times do 101 | u = @r.uniform 102 | assert u < 1 103 | assert u > 0 104 | end 105 | end 106 | 107 | should "generate uniformly random numbers with mean approximately 0.5" do 108 | numbers = generate_numbers(@r, :uniform) 109 | epsilon = (0.5 - numbers.mean).abs 110 | 111 | assert epsilon < MAXIMUM_EPSILON 112 | end 113 | 114 | should "generate random numbers from a normal distribution with mean approximately 0" do 115 | numbers = generate_numbers(@r, :normal) 116 | epsilon = (0.0 - numbers.mean).abs 117 | 118 | assert epsilon < MAXIMUM_EPSILON 119 | end 120 | 121 | should "generate random numbers from a normal distribution with sample standard deviation approximately 1" do 122 | numbers = generate_numbers(@r, :normal) 123 | epsilon = (1.0 - numbers.standard_deviation).abs 124 | 125 | assert epsilon < MAXIMUM_EPSILON 126 | end 127 | 128 | should "generate random numbers from an exponential distribution with mean approximately 1" do 129 | numbers = generate_numbers(@r, :exponential) 130 | epsilon = (1.0 - numbers.mean).abs 131 | 132 | assert epsilon < MAXIMUM_EPSILON 133 | end 134 | 135 | should "generate random numbers from triangular(0, 1, 1) in the range [0, 1]" do 136 | SAMPLE_SIZE.times do 137 | t = @r.triangular(0.0, 1.0, 1.0) 138 | assert t <= 1.0 139 | assert t >= 0.0 140 | end 141 | end 142 | 143 | should "generate random numbers from triangular(0, 1, 1) with mean approximately 0.66" do 144 | a = 0.0 145 | c = 1.0 146 | b = 1.0 147 | numbers = generate_numbers(@r, :triangular, a, c, b) 148 | mean = (a + b + c) / 3 149 | epsilon = (mean - numbers.mean).abs 150 | 151 | assert epsilon < MAXIMUM_EPSILON 152 | end 153 | 154 | should "generate random numbers from triangular(0, 1, 1) with standard deviation approximately 0.23" do 155 | a = 0.0 156 | b = 1.0 157 | c = 1.0 158 | numbers = generate_numbers(@r, :triangular, a, b, c) 159 | std_dev = Math.sqrt((a ** 2 + b ** 2 + c ** 2 - a * b - a * c - b * c) / 18) 160 | epsilon = (std_dev - numbers.standard_deviation).abs 161 | 162 | assert epsilon < MAXIMUM_EPSILON 163 | end 164 | 165 | should "generate random numbers from triangular(0, 0.5, 1) with mean approximately 0.5" do 166 | a = 0.0 167 | b = 0.5 168 | c = 1.0 169 | 170 | numbers = generate_numbers(@r, :triangular, a, b, c) 171 | mean = (a + b + c) / 3 172 | epsilon = (mean - numbers.mean).abs 173 | 174 | assert epsilon < MAXIMUM_EPSILON 175 | end 176 | 177 | should "generate a random number sampled from a gamma distribution" do 178 | assert @r.gamma(5, 2.3) 179 | assert @r.gamma(5.3, 2.7) 180 | assert @r.gamma(2.3, 2) 181 | end 182 | 183 | should "generate a random number sampled from an inverse gamma distribution" do 184 | assert @r.inverse_gamma(5, 2.3) 185 | assert @r.inverse_gamma(5.7, 2.8) 186 | assert @r.inverse_gamma(3.2, 2) 187 | end 188 | 189 | should "generate a random number sampled from a beta distribution" do 190 | assert @r.beta(5, 2.3) 191 | end 192 | 193 | should "generate a random number sampled from a chi-square distribution" do 194 | assert @r.chi_square(10) 195 | end 196 | 197 | should "generate a random number using weibull" do 198 | assert @r.weibull(5, 2.3) 199 | end 200 | 201 | should "generate random number from a dirichlet distribution" do 202 | assert @r.dirichlet(5.3, 2.7) 203 | end 204 | 205 | should "generate random numbers from laplace(0, 1) with mean approximately 0" do 206 | mean = 0.0 207 | scale = 0.1 208 | numbers = generate_numbers(@r, :laplace, mean, scale) 209 | epsilon = (mean - numbers.mean).abs 210 | 211 | assert epsilon < MAXIMUM_EPSILON 212 | end 213 | end 214 | 215 | context "A multi-threaded simple random number generator" do 216 | setup do 217 | @r = MultiThreadedSimpleRandom.instance 218 | end 219 | 220 | should "work independently in every thread" do 221 | sample_count = 10 222 | thread_count = 10 223 | 224 | samples = Hash.new { |hash, key| hash[key] = [] } 225 | 226 | threads = Array.new(thread_count) do 227 | Thread.new do 228 | sample_count.times do 229 | samples[Thread.current.object_id] << MultiThreadedSimpleRandom.instance.uniform 230 | end 231 | end 232 | end 233 | 234 | threads.map(&:join) 235 | 236 | samples = samples.values 237 | assert samples.size == thread_count 238 | assert samples.uniq.size == 1 239 | end 240 | end 241 | end 242 | --------------------------------------------------------------------------------