random_variates 0.2.2 → 0.3.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 +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.
|