random_variates 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +22 -0
  3. data/README.md +13 -0
  4. data/lib/random_variates.rb +352 -0
  5. metadata +46 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a634c9ce4183624b1b56fa3c4c753e971c8304a92cb130de598a93fa14668303
4
+ data.tar.gz: 7f6819ecdbe4e3ad308e78b69f41be9b2ccd93aaecf67f2df783fb121d4f1867
5
+ SHA512:
6
+ metadata.gz: 14aa27bd3e6c82808cbbe309031f61f567683c1ce2bf078cc3523d637403e2f0988f909a2733683b2616c890bc84d445c5d75668d4b381bcfb059f7d77780b20
7
+ data.tar.gz: 72b9a7eebad20ff548519c28e868d97c8e6363abb66669e79d3dda84bc1567a0e43dd2ee116479155487fe94607efe9e8292eff021731fff87fe86c20f220509
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+
2
+ The MIT License (MIT)
3
+
4
+ Copyright (c) 2017-2018 Paul J. Sanchez
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,13 @@
1
+ This gem implements random variate generation for several
2
+ common statistical distributions. Each distribution is implemented
3
+ in its own class, and different parameterizations are created as
4
+ instances of the class using the constructor to specify the
5
+ parameterization. All constructors use named parameters for clarity,
6
+ so the order of parameters does not matter. All random variate classes
7
+ provide an optional argument `rng`, with which the user can specify an
8
+ enumerable U(0,1) generator to use as the core source of randomness.
9
+ If `rng` is not specified, it defaults to Ruby's `Kernel#rand`.
10
+
11
+ Once a random variate class has been instantiated, values can either be
12
+ generated on demand using the `next` method or by using the instance as
13
+ a generator in any iterable context.
@@ -0,0 +1,352 @@
1
+ # This library implements random variate generation for several
2
+ # common statistical distributions. Each distribution is implemented
3
+ # in its own class, and different parameterizations are created as
4
+ # instances of the class using the constructor to specify the
5
+ # parameterization. All constructors use named parameters for clarity,
6
+ # so the order of parameters does not matter. All RV classes provide an
7
+ # optional argument +rng+, with which the user can specify an enumerable
8
+ # U(0,1) generator to use as the core source of randomness. If +rng+ is
9
+ # not specified, it defaults to +Kernel#rand+.
10
+ #
11
+ # Once a random variate class has been instantiated, values can either be
12
+ # generated on demand using the +next+ method or by using the instance as
13
+ # a generator in any iterable context.
14
+
15
+ # Provide access to +Kernel#rand+ as an +Enumerator+ object.
16
+ U_GENERATOR = Enumerator.new { |yielder| loop { yielder << rand } }
17
+
18
+ # The +RV_Generator+ module provides a common core of methods to make
19
+ # all of the RV classes +Enumerable+.
20
+ module RV_Generator
21
+ include Enumerable
22
+
23
+ attr_reader :generator
24
+
25
+ def next
26
+ @generator.next
27
+ end
28
+
29
+ def each
30
+ loop { yield @generator.next }
31
+ end
32
+ end
33
+
34
+ # Generate values uniformly distributed between +min+ and +max+.
35
+ #
36
+ # *Arguments*::
37
+ # - +min+ -> the lower bound for the range (default: 0).
38
+ # - +max+ -> the upper bound for the range (default: 1).
39
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
40
+ #
41
+ class Uniform
42
+ include RV_Generator
43
+
44
+ attr_reader :min, :max
45
+
46
+ def initialize(min: 0.0, max: 1.0, rng: U_GENERATOR)
47
+ fail 'Max must be greater than min.' if max <= min
48
+ @min = min
49
+ @max = max
50
+ range = max - min
51
+ @generator = Enumerator.new do |yielder|
52
+ loop { yielder << (min + range * rng.next) }
53
+ end
54
+ end
55
+ end
56
+
57
+ # Triangular random variate generator with specified +min+, +mode+, and +max+.
58
+ #
59
+ # *Arguments*::
60
+ # - +min+ -> the lower bound for the range (default: 0).
61
+ # - +max+ -> the upper bound for the range (default: 1).
62
+ # - +mode+ -> the highest likelihood value (+min+ ≤ +mode+ ≤ +max+; default: 1).
63
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
64
+ #
65
+ class Triangle
66
+ include RV_Generator
67
+
68
+ attr_reader :min, :max, :mode, :range
69
+
70
+ def initialize(min: 0.0, max: 1.0, mode: 1.0, rng: U_GENERATOR)
71
+ @range = max - min
72
+ fail 'Min must be less than Max.' if @range <= 0
73
+ fail 'Mode must be between Min and Max.' unless (min..max).include? mode
74
+ @min = min
75
+ @max = max
76
+ @mode = mode
77
+ crossover_p = (mode - min).to_f / @range
78
+ @generator = Enumerator.new do |yielder|
79
+ loop do
80
+ u = rng.next
81
+ if u < crossover_p
82
+ yielder << min + Math.sqrt(range * (mode - min) * u)
83
+ else
84
+ yielder << max - Math.sqrt(range * (max - mode) * (1.0 - u))
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ # Exponential random variate generator with specified +rate+ or +mean+.
92
+ # One and only one of +rate+ or +mean+ should be specified.
93
+ #
94
+ # *Arguments*::
95
+ # - +rate+ -> the rate of occurrences per unit time (default: +nil+).
96
+ # - +mean+ -> the expected value of the distribution (default: +nil+).
97
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
98
+ #
99
+ class Exponential
100
+ include RV_Generator
101
+
102
+ attr_reader :rate
103
+
104
+ def initialize(rate: nil, mean: nil, rng: U_GENERATOR)
105
+ fail 'Rate must be positive.' if rate && rate <= 0
106
+ fail 'Mean must be positive.' if mean && mean <= 0
107
+ fail 'Supply one and only one of mean or rate' unless rate.nil? ^ mean.nil?
108
+ @mean = rate.nil? ? mean : 1.0 / rate
109
+ @generator = Enumerator.new do |yielder|
110
+ loop { yielder << (-mean * Math.log(rng.next)) }
111
+ end
112
+ end
113
+ end
114
+
115
+ # Gaussian/normal random variate generator with specified
116
+ # +mean+ and +standard deviation+. Defaults to a standard normal.
117
+ #
118
+ # *Arguments*::
119
+ # - +mean+ -> the expected value (default: 0).
120
+ # - +sd+ -> the standard deviation (default: 1).
121
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
122
+ #
123
+ class Gaussian
124
+ include RV_Generator
125
+
126
+ attr_reader :mean, :sd
127
+
128
+ def initialize(mean: 0.0, sd: 1.0, rng: U_GENERATOR)
129
+ fail 'Standard deviation must be positive.' if sd <= 0
130
+ @mean = mean
131
+ @sd = sd
132
+ @generator = Enumerator.new do |yielder|
133
+ # Ratio of Uniforms
134
+ bound = 2.0 * Math.sqrt(2.0 / Math::E)
135
+ loop do
136
+ u = rng.next
137
+ next if u == 0.0
138
+ v = bound * (rng.next - 0.5)
139
+ x = v / u
140
+ x_sqr = x * x
141
+ u_sqr = u * u
142
+ if 6.0 * x_sqr <= 44.0 - 72.0 * u + 36.0 * u_sqr - 8.0 * u * u_sqr
143
+ yielder << sd * x + mean
144
+ elsif x_sqr * u >= 2.0 - 2.0 * u_sqr
145
+ next
146
+ elsif x_sqr <= -4.0 * Math.log(u)
147
+ yielder << sd * x + mean
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ # Alternate Gaussian/normal random variate generator with specified
155
+ # +mean+ and +standard deviation+. Defaults to a standard normal.
156
+ #
157
+ # *Arguments*::
158
+ # - +mean+ -> the expected value (default: 0).
159
+ # - +sd+ -> the standard deviation (default: 1).
160
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
161
+ #
162
+ class BoxMuller
163
+ include RV_Generator
164
+
165
+ attr_reader :mean, :sd
166
+
167
+ def initialize(mean: 0.0, sd: 1.0, rng: U_GENERATOR)
168
+ fail 'Standard deviation must be positive.' if sd <= 0
169
+ @mean = mean
170
+ @sd = sd
171
+ @generator = Enumerator.new do |yielder|
172
+ # Box-Muller
173
+ next_z = 0.0
174
+ need_new_pair = false
175
+ loop do
176
+ need_new_pair ^= true
177
+ if need_new_pair
178
+ theta = 2.0 * Math::PI * rng.next
179
+ d = sd * Math.sqrt(-2.0 * Math.log(rng.next))
180
+ next_z = mean + d * Math.sin(theta)
181
+ yielder << mean + d * Math.cos(theta)
182
+ else
183
+ yielder << next_z
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ # Gamma generator based on Marsaglia and Tsang method Algorithm 4.33
191
+ #
192
+ # *Arguments*::
193
+ # - +alpha+ -> the shape parameter (+alpha+ > 0; default: 1).
194
+ # - +beta+ -> the rate parameter (+beta+ > 0; default: 1).
195
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
196
+ #
197
+ class Gamma
198
+ include RV_Generator
199
+
200
+ attr_reader :alpha, :beta
201
+
202
+ def initialize(alpha: 1.0, beta: 1.0, rng: U_GENERATOR)
203
+ fail 'Alpha and beta must be positive.' if alpha <= 0 || beta <= 0
204
+ @alpha = alpha
205
+ @beta = beta
206
+ std_normal = Gaussian.new(mean: 0.0, sd: 1.0, rng: rng)
207
+ @generator = Enumerator.new do |yielder|
208
+ loop { yielder << __gen__(alpha, beta, std_normal, rng) }
209
+ end
210
+ end
211
+
212
+ private
213
+
214
+ def __gen__(alpha, beta, std_normal, rng)
215
+ if alpha > 1
216
+ z = v = 0.0
217
+ d = alpha - 1.0 / 3.0
218
+ c = (1.0 / 3.0) / Math.sqrt(d)
219
+ loop do
220
+ loop do
221
+ z = std_normal.next
222
+ v = 1.0 + c * z
223
+ break if v > 0
224
+ end
225
+ z2 = z * z
226
+ v = v * v * v
227
+ u = rng.next
228
+ break if u < 1.0 - 0.0331 * z2 * z2
229
+ break if Math.log(u) < (0.5 * z2 + d * (1.0 - v + Math.log(v)))
230
+ end
231
+ d * v * beta
232
+ else
233
+ __gen__(alpha + 1.0, beta, std_normal, rng) * (rng.next**(1.0 / alpha))
234
+ end
235
+ end
236
+ end
237
+
238
+ # Weibull generator based on Devroye
239
+ #
240
+ # *Arguments*::
241
+ # - +rate+ -> the scale parameter (+rate+ > 0; default: 1).
242
+ # - +k+ -> the shape parameter (+k+ > 0; default: 1).
243
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
244
+ #
245
+ class Weibull
246
+ include RV_Generator
247
+
248
+ attr_reader :rate, :k
249
+
250
+ def initialize(rate: 1.0, k: 1.0, rng: U_GENERATOR)
251
+ fail 'Rate and k must be positive.' if rate <= 0 || k <= 0
252
+ @rate = rate
253
+ @k = k
254
+ power = 1.0 / k
255
+ @generator = Enumerator.new do |yielder|
256
+ loop { yielder << (-Math.log(rng.next))**power / rate }
257
+ end
258
+ end
259
+ end
260
+
261
+ # Erlang generator - Weibull restricted to integer +k+
262
+ #
263
+ # *Arguments*::
264
+ # - +rate+ -> the scale parameter (+rate+ > 0; default: 1).
265
+ # - +k+ -> the shape parameter (+k+ > 0; default: 1).
266
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
267
+ #
268
+ class Erlang < Weibull
269
+ def initialize(rate: 1.0, k: 1, rng: U_GENERATOR)
270
+ fail 'K must be integer.' unless k.integer?
271
+ super(rate: rate, k: k, rng: rng)
272
+ end
273
+ end
274
+
275
+ # Poisson generator.
276
+ #
277
+ # *Arguments*::
278
+ # - +rate+ -> expected number per unit time/distance (+rate+ > 0; default: 1).
279
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
280
+ #
281
+ class Poisson
282
+ include RV_Generator
283
+
284
+ attr_reader :rate
285
+
286
+ def initialize(rate: 1.0, rng: U_GENERATOR)
287
+ fail 'rate must be positive.' if rate <= 0
288
+ @rate = rate
289
+ threshold = Math.exp(-rate)
290
+ @generator = Enumerator.new do |yielder|
291
+ loop do
292
+ count = 0
293
+ product = 1.0
294
+ count += 1 until (product *= rng.next) < threshold
295
+ yielder << count
296
+ end
297
+ end
298
+ end
299
+ end
300
+
301
+ # Geometric generator. Number of trials until first "success".
302
+ #
303
+ # *Arguments*::
304
+ # - +p+ -> the probability of success (0 < +p+ < 1; default: 0.5).
305
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
306
+ #
307
+ class Geometric
308
+ include RV_Generator
309
+
310
+ attr_reader :p
311
+
312
+ def initialize(p: 0.5, rng: U_GENERATOR)
313
+ fail 'Require 0 < p < 1.' if p <= 0 || p >= 1
314
+ @p = p
315
+ log_q = Math.log(1 - p)
316
+ @generator = Enumerator.new do |yielder|
317
+ loop { yielder << (Math.log(1.0 - rng.next) / log_q).ceil }
318
+ end
319
+ end
320
+ end
321
+
322
+ # Binomial generator. Number of "successes" in +n+ independent trials.
323
+ #
324
+ # *Arguments*::
325
+ # - +n+ -> the number of trials (+p+ > 0, integer; default: 1).
326
+ # - +p+ -> the probability of success (0 < +p+ < 1; default: 0.5).
327
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
328
+ #
329
+ class Binomial
330
+ include RV_Generator
331
+
332
+ attr_reader :n, :p
333
+
334
+ def initialize(n: 1, p: 0.5, rng: U_GENERATOR)
335
+ fail 'N must be a positive integer.' if n.to_i != n || n <= 0
336
+ fail 'Require 0 < p < 1.' if p <= 0 || p >= 1
337
+ @n = n.to_i
338
+ @p = p
339
+ log_q = Math.log(1 - p)
340
+ @generator = Enumerator.new do |yielder|
341
+ loop do
342
+ result = sum = 0
343
+ loop do
344
+ sum += Math.log(rng.next) / (n - result)
345
+ break if sum < log_q
346
+ result += 1
347
+ end
348
+ yielder << result
349
+ end
350
+ end
351
+ end
352
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: random_variates
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul J Sanchez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Random variate generators implemented as enumerator/enumerable.
14
+ email: pjs@alum.mit.edu
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE.md
20
+ - README.md
21
+ - lib/random_variates.rb
22
+ homepage:
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '2.0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 2.7.7
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: Random variate generator classes.
46
+ test_files: []