random_variates 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.md +22 -0
- data/README.md +13 -0
- data/lib/random_variates.rb +352 -0
- 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: []
|