random_variates 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/random_variates.rb +124 -46
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d30e6b62a53350bfd18a1deb7d5338c98d7fcd479a9f5020ff7bc6e8358c256
|
4
|
+
data.tar.gz: 33a02922156dc7b17b9aada61693562aca6a12cf059f2e5fa775f52b7de10bc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2d6f766aa38c6647c4f184fb279854597f7bee784b007ca1da35a3791200645872e5cc433270ce14a094408a360afac313170e155bb451653a43be03996cae4
|
7
|
+
data.tar.gz: 82ea2362281c4eb8fe9ee0168f88df8a77333530d8a693cafd7a5ecc5490efea05167561a2458c3b65d21033a35ac791afecb667185cdaa72e1c681907959904
|
data/lib/random_variates.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This library implements random variate generation for several
|
2
4
|
# common statistical distributions. Each distribution is implemented
|
3
5
|
# in its own class, and different parameterizations are created as
|
@@ -44,7 +46,8 @@ class Uniform
|
|
44
46
|
attr_reader :min, :max
|
45
47
|
|
46
48
|
def initialize(min: 0.0, max: 1.0, rng: U_GENERATOR)
|
47
|
-
|
49
|
+
raise 'Max must be greater than min.' if max <= min
|
50
|
+
|
48
51
|
@min = min
|
49
52
|
@max = max
|
50
53
|
range = max - min
|
@@ -57,32 +60,61 @@ end
|
|
57
60
|
# Triangular random variate generator with specified +min+, +mode+, and +max+.
|
58
61
|
#
|
59
62
|
# *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
63
|
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
64
|
+
# - +min+ -> the lower bound for the range.
|
65
|
+
# - +max+ -> the upper bound for the range.
|
66
|
+
# - +mode+ -> the highest likelihood value (+min+ ≤ +mode+ ≤ +max+).
|
67
|
+
# - +mean+ -> the expected value of the distribution.
|
64
68
|
#
|
65
69
|
class Triangle
|
66
70
|
include RV_Generator
|
67
71
|
|
68
|
-
attr_reader :min, :max, :mode, :range
|
72
|
+
attr_reader :min, :max, :mode, :range, :mean
|
69
73
|
|
70
|
-
def initialize(
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
74
|
+
def initialize(rng: U_GENERATOR, **args)
|
75
|
+
param_names = %i[mean min max mode]
|
76
|
+
unless args.size > 2 && args.keys.all? { |k| param_names.include? k }
|
77
|
+
raise "invalid args - can only be #{param_names.join ', '}, or rng."
|
78
|
+
end
|
79
|
+
|
80
|
+
param_names.each { |k| args[k] ||= nil } if args.size < param_names.size
|
81
|
+
|
82
|
+
nil_args = args.select { |_, v| v.nil? }.keys
|
83
|
+
nil_args_count = nil_args.count
|
84
|
+
raise 'too many nil args' if nil_args_count > 1
|
85
|
+
|
86
|
+
args.transform_values! &:to_f
|
87
|
+
|
88
|
+
if nil_args_count == 0
|
89
|
+
if args[:mean] != (args[:min] + args[:max] + args[:mode]) / 3.0
|
90
|
+
raise 'inconsistent args'
|
91
|
+
end
|
92
|
+
else
|
93
|
+
key = nil_args.shift
|
94
|
+
case key
|
95
|
+
when :mean
|
96
|
+
args[key] = (args[:min] + args[:max] + args[:mode]) / 3.0
|
97
|
+
else
|
98
|
+
others = param_names - [key, :mean]
|
99
|
+
args[key] = 3 * args[:mean] - args.values_at(*others).sum
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
param_names.each { |parm| instance_variable_set("@#{parm}", args[parm]) }
|
104
|
+
|
105
|
+
@range = @max - @min
|
106
|
+
raise 'Min must be less than Max.' if @range <= 0
|
107
|
+
raise 'Mode must be between Min and Max.' unless (@min..@max).include? @mode
|
108
|
+
|
109
|
+
crossover_p = (@mode - @min).to_f / @range
|
78
110
|
@generator = Enumerator.new do |yielder|
|
79
111
|
loop do
|
80
112
|
u = rng.next
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
113
|
+
yielder << (
|
114
|
+
u < crossover_p ?
|
115
|
+
@min + Math.sqrt(@range * (@mode - @min) * u) :
|
116
|
+
@max - Math.sqrt(@range * (@max - @mode) * (1.0 - u))
|
117
|
+
)
|
86
118
|
end
|
87
119
|
end
|
88
120
|
end
|
@@ -99,16 +131,19 @@ end
|
|
99
131
|
class Exponential
|
100
132
|
include RV_Generator
|
101
133
|
|
102
|
-
attr_reader :rate
|
134
|
+
attr_reader :rate, :mean
|
103
135
|
|
104
136
|
def initialize(rate: nil, mean: nil, rng: U_GENERATOR)
|
105
|
-
|
106
|
-
|
107
|
-
|
137
|
+
raise 'Rate must be positive.' if rate && rate <= 0
|
138
|
+
raise 'Mean must be positive.' if mean && mean <= 0
|
139
|
+
raise 'Supply one and only one of mean or rate' unless rate.nil? ^ mean.nil?
|
140
|
+
|
108
141
|
if rate.nil?
|
109
|
-
@mean
|
142
|
+
@mean = mean
|
143
|
+
@rate = 1.0 / mean
|
110
144
|
else
|
111
|
-
@mean
|
145
|
+
@mean = 1.0 / rate
|
146
|
+
@rate = rate
|
112
147
|
end
|
113
148
|
@generator = Enumerator.new do |yielder|
|
114
149
|
loop { yielder << (-@mean * Math.log(rng.next)) }
|
@@ -130,27 +165,29 @@ class Gaussian
|
|
130
165
|
attr_reader :mean, :sd
|
131
166
|
|
132
167
|
def initialize(mean: 0.0, sd: 1.0, rng: U_GENERATOR)
|
133
|
-
|
168
|
+
raise 'Standard deviation must be positive.' if sd <= 0
|
169
|
+
|
134
170
|
@mean = mean
|
135
171
|
@sd = sd
|
136
172
|
@generator = Enumerator.new do |yielder|
|
137
173
|
# Ratio of Uniforms
|
138
174
|
bound = 2.0 * Math.sqrt(2.0 / Math::E)
|
139
175
|
loop do
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
176
|
+
u = rng.next
|
177
|
+
next if u == 0.0
|
178
|
+
|
179
|
+
v = bound * (rng.next - 0.5)
|
180
|
+
x = v / u
|
181
|
+
x_sqr = x * x
|
182
|
+
u_sqr = u * u
|
183
|
+
if 6.0 * x_sqr <= 44.0 - 72.0 * u + 36.0 * u_sqr - 8.0 * u * u_sqr
|
184
|
+
yielder << sd * x + mean
|
185
|
+
elsif x_sqr * u >= 2.0 - 2.0 * u_sqr
|
186
|
+
next
|
187
|
+
elsif x_sqr <= -4.0 * Math.log(u)
|
188
|
+
yielder << sd * x + mean
|
153
189
|
end
|
190
|
+
end
|
154
191
|
end
|
155
192
|
end
|
156
193
|
end
|
@@ -169,7 +206,8 @@ class BoxMuller
|
|
169
206
|
attr_reader :mean, :sd
|
170
207
|
|
171
208
|
def initialize(mean: 0.0, sd: 1.0, rng: U_GENERATOR)
|
172
|
-
|
209
|
+
raise 'Standard deviation must be positive.' if sd <= 0
|
210
|
+
|
173
211
|
@mean = mean
|
174
212
|
@sd = sd
|
175
213
|
@generator = Enumerator.new do |yielder|
|
@@ -193,6 +231,8 @@ end
|
|
193
231
|
|
194
232
|
# Gamma generator based on Marsaglia and Tsang method Algorithm 4.33
|
195
233
|
#
|
234
|
+
# Produces gamma RVs with expected value +alpha+ * +beta+.
|
235
|
+
#
|
196
236
|
# *Arguments*::
|
197
237
|
# - +alpha+ -> the shape parameter (+alpha+ > 0; default: 1).
|
198
238
|
# - +beta+ -> the rate parameter (+beta+ > 0; default: 1).
|
@@ -204,7 +244,8 @@ class Gamma
|
|
204
244
|
attr_reader :alpha, :beta
|
205
245
|
|
206
246
|
def initialize(alpha: 1.0, beta: 1.0, rng: U_GENERATOR)
|
207
|
-
|
247
|
+
raise 'Alpha and beta must be positive.' if alpha <= 0 || beta <= 0
|
248
|
+
|
208
249
|
@alpha = alpha
|
209
250
|
@beta = beta
|
210
251
|
std_normal = Gaussian.new(mean: 0.0, sd: 1.0, rng: rng)
|
@@ -252,7 +293,8 @@ class Weibull
|
|
252
293
|
attr_reader :rate, :k
|
253
294
|
|
254
295
|
def initialize(rate: 1.0, k: 1.0, rng: U_GENERATOR)
|
255
|
-
|
296
|
+
raise 'Rate and k must be positive.' if rate <= 0 || k <= 0
|
297
|
+
|
256
298
|
@rate = rate
|
257
299
|
@k = k
|
258
300
|
power = 1.0 / k
|
@@ -271,11 +313,43 @@ end
|
|
271
313
|
#
|
272
314
|
class Erlang < Weibull
|
273
315
|
def initialize(rate: 1.0, k: 1, rng: U_GENERATOR)
|
274
|
-
|
316
|
+
raise 'K must be integer.' unless k.integer?
|
317
|
+
|
275
318
|
super(rate: rate, k: k, rng: rng)
|
276
319
|
end
|
277
320
|
end
|
278
321
|
|
322
|
+
# von Mises generator.
|
323
|
+
#
|
324
|
+
# This von Mises distribution generator is based on the VML algorithm by
|
325
|
+
# L. Barabesis: "Generating von Mises variates by the Ratio-of-Uniforms Method"
|
326
|
+
# Statistica Applicata Vol. 7, #4, 1995
|
327
|
+
# http://sa-ijas.stat.unipd.it/sites/sa-ijas.stat.unipd.it/files/417-426.pdf
|
328
|
+
#
|
329
|
+
# *Arguments*::
|
330
|
+
# - +kappa+ -> concentration coefficient (+kappa+ ≥ 0).
|
331
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
332
|
+
#
|
333
|
+
class VonMises
|
334
|
+
include RV_Generator
|
335
|
+
|
336
|
+
attr_reader :kappa
|
337
|
+
|
338
|
+
def initialize(kappa:, rng: U_GENERATOR)
|
339
|
+
raise 'kappa must be positive.' if kappa < 0
|
340
|
+
s = (kappa > 1.3 ? 1.0 / Math.sqrt(kappa) : Math::PI * Math.exp(-kappa))
|
341
|
+
@generator = Enumerator.new do |yielder|
|
342
|
+
loop do
|
343
|
+
r1 = rng.next
|
344
|
+
theta = s * (2.0 * rng.next - 1.0) / r1
|
345
|
+
next if (theta.abs > Math::PI)
|
346
|
+
yielder << theta if (0.25 * kappa * theta * theta < 1.0 - r1) ||
|
347
|
+
(0.5 * kappa * (Math.cos(theta) - 1.0) >= Math.log(r1))
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
279
353
|
# Poisson generator.
|
280
354
|
#
|
281
355
|
# *Arguments*::
|
@@ -288,7 +362,8 @@ class Poisson
|
|
288
362
|
attr_reader :rate
|
289
363
|
|
290
364
|
def initialize(rate: 1.0, rng: U_GENERATOR)
|
291
|
-
|
365
|
+
raise 'rate must be positive.' if rate <= 0
|
366
|
+
|
292
367
|
@rate = rate
|
293
368
|
threshold = Math.exp(-rate)
|
294
369
|
@generator = Enumerator.new do |yielder|
|
@@ -314,7 +389,8 @@ class Geometric
|
|
314
389
|
attr_reader :p
|
315
390
|
|
316
391
|
def initialize(p: 0.5, rng: U_GENERATOR)
|
317
|
-
|
392
|
+
raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
|
393
|
+
|
318
394
|
@p = p
|
319
395
|
log_q = Math.log(1 - p)
|
320
396
|
@generator = Enumerator.new do |yielder|
|
@@ -336,8 +412,9 @@ class Binomial
|
|
336
412
|
attr_reader :n, :p
|
337
413
|
|
338
414
|
def initialize(n: 1, p: 0.5, rng: U_GENERATOR)
|
339
|
-
|
340
|
-
|
415
|
+
raise 'N must be a positive integer.' if n.to_i != n || n <= 0
|
416
|
+
raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
|
417
|
+
|
341
418
|
@n = n.to_i
|
342
419
|
@p = p
|
343
420
|
log_q = Math.log(1 - p)
|
@@ -347,6 +424,7 @@ class Binomial
|
|
347
424
|
loop do
|
348
425
|
sum += Math.log(rng.next) / (n - result)
|
349
426
|
break if sum < log_q
|
427
|
+
|
350
428
|
result += 1
|
351
429
|
end
|
352
430
|
yielder << result
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: random_variates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul J Sanchez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Random variate generators implemented as enumerator/enumerable.
|
14
14
|
email: pjs@alum.mit.edu
|
@@ -38,8 +38,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '0'
|
40
40
|
requirements: []
|
41
|
-
|
42
|
-
rubygems_version: 2.7.7
|
41
|
+
rubygems_version: 3.0.3
|
43
42
|
signing_key:
|
44
43
|
specification_version: 4
|
45
44
|
summary: Random variate generator classes.
|