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.
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: []