random_variates 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.md +1 -1
- data/README.md +16 -4
- data/lib/random_variates.rb +376 -326
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43a85c7f9cd47a418101215d490e1089ab8b5867f860c6200359c2d1f08079a2
|
4
|
+
data.tar.gz: 6bb665672cbbfcd7b281739acc3e78437ad8a2632e21208559d9a5b4dc380645
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5741f020d080a4e2a2e2dec67e3654eeb6e8cd5ea4725881ccb94f7b2b1b9efbfc38548bcadae625f2b68e36d868209259fc485c6d30007eed11b2154bb65df8
|
7
|
+
data.tar.gz: f5f8821ecf11b76639c8e1c63391dbbfc5f490617da946a49d157dee1c9f749a6d41755a5ae1abcc4b4000743c8db358654e0ff7e7840b7af2740c7afd7fa1b3
|
data/LICENSE.md
CHANGED
data/README.md
CHANGED
@@ -1,13 +1,25 @@
|
|
1
|
+
### DESCRIPTION
|
2
|
+
|
1
3
|
This gem 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
|
4
6
|
instances of the class using the constructor to specify the
|
5
7
|
parameterization. All constructors use named parameters for clarity,
|
6
8
|
so the order of parameters does not matter. All random variate classes
|
7
|
-
provide an optional argument `rng`, with which the user can specify
|
8
|
-
|
9
|
-
If `rng` is not specified, it
|
9
|
+
provide an optional argument `rng`, with which the user can specify a
|
10
|
+
U(0,1) generator as an `Enumerator` to use as the core source of randomness.
|
11
|
+
If `rng` is not specified, it is based on Ruby's `Kernel#rand`.
|
10
12
|
|
11
13
|
Once a random variate class has been instantiated, values can either be
|
12
14
|
generated on demand using the `next` method or by using the instance as
|
13
|
-
a generator in any iterable context.
|
15
|
+
a generator in any iterable context. Since these are "infinite" generators, enumerative Ruby methods such as `.first` or `.take` will require a `.each` first to yield an iterator for lazy access. Example: `my_exp = RV::Exponential.new(rate: 3); my_exp.each.take(50)` will yield an array of fifty exponentially distributed values having rate 3.
|
16
|
+
|
17
|
+
---
|
18
|
+
|
19
|
+
### RELEASE NOTES
|
20
|
+
|
21
|
+
**v0.4**
|
22
|
+
- All distribution classes have been placed in a module called `RV` to try to avoid namespace conflicts.
|
23
|
+
- The `Gaussian` class has been renamed, it is now the `Normal` class.
|
24
|
+
- Parameters for `Gaussian` and `BoxMuller` have been renamed. They are now `mu:` and `sigma:`.
|
25
|
+
- `Exponential`, `Normal`, and `BoxMuller` distribution parameters can now be set to new values without having to instantiate an entirely new generator object.
|
data/lib/random_variates.rb
CHANGED
@@ -14,421 +14,471 @@
|
|
14
14
|
# generated on demand using the +next+ method or by using the instance as
|
15
15
|
# a generator in any iterable context.
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def next
|
28
|
-
@generator.next
|
29
|
-
end
|
17
|
+
module RV
|
18
|
+
# Provide access to +Kernel#rand+ as an +Enumerator+.
|
19
|
+
U_GENERATOR = Enumerator.new { |yielder| loop { yielder << rand } }
|
20
|
+
|
21
|
+
# The +RV_Generator+ module provides a common core of methods to make
|
22
|
+
# all of the RV classes iterable.
|
23
|
+
module RV_Generator
|
24
|
+
def next
|
25
|
+
raise 'next not implemented!'
|
26
|
+
end
|
30
27
|
|
31
|
-
|
32
|
-
|
28
|
+
def each
|
29
|
+
Enumerator.new { |y| loop { y << self.next } }
|
30
|
+
end
|
33
31
|
end
|
34
|
-
end
|
35
32
|
|
36
|
-
# Generate values uniformly distributed between +min+ and +max+.
|
37
|
-
#
|
38
|
-
# *Arguments*::
|
39
|
-
# - +min+ -> the lower bound for the range (default: 0).
|
40
|
-
# - +max+ -> the upper bound for the range (default: 1).
|
41
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
42
|
-
#
|
43
|
-
class Uniform
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
33
|
+
# Generate values uniformly distributed between +min+ and +max+.
|
34
|
+
#
|
35
|
+
# *Arguments*::
|
36
|
+
# - +min+ -> the lower bound for the range (default: 0).
|
37
|
+
# - +max+ -> the upper bound for the range (default: 1).
|
38
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
39
|
+
#
|
40
|
+
class Uniform
|
41
|
+
include RV_Generator
|
42
|
+
|
43
|
+
attr_reader :min, :max, :range
|
44
|
+
|
45
|
+
def initialize(min: 0.0, max: 1.0, rng: U_GENERATOR)
|
46
|
+
raise 'Max must be greater than min.' if max <= min
|
47
|
+
@min = min
|
48
|
+
@max = max
|
49
|
+
@range = max - min
|
50
|
+
@rng = rng
|
51
|
+
end
|
50
52
|
|
51
|
-
|
52
|
-
|
53
|
-
range = max - min
|
54
|
-
@generator = Enumerator.new do |yielder|
|
55
|
-
loop { yielder << (min + range * rng.next) }
|
53
|
+
def next
|
54
|
+
@min + @range * @rng.next
|
56
55
|
end
|
57
56
|
end
|
58
|
-
end
|
59
57
|
|
60
|
-
# Triangular random variate generator with specified +min+, +mode+, and +max+.
|
61
|
-
#
|
62
|
-
# *Arguments*::
|
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.
|
68
|
-
#
|
69
|
-
class Triangle
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
58
|
+
# Triangular random variate generator with specified +min+, +mode+, and +max+.
|
59
|
+
#
|
60
|
+
# *Arguments*::
|
61
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
62
|
+
# - +min+ -> the lower bound for the range.
|
63
|
+
# - +max+ -> the upper bound for the range.
|
64
|
+
# - +mode+ -> the highest likelihood value (+min+ ≤ +mode+ ≤ +max+).
|
65
|
+
# - +mean+ -> the expected value of the distribution.
|
66
|
+
#
|
67
|
+
class Triangle
|
68
|
+
include RV_Generator
|
69
|
+
|
70
|
+
attr_reader :min, :max, :mode, :range, :mean
|
71
|
+
|
72
|
+
def initialize(rng: U_GENERATOR, **args)
|
73
|
+
param_names = %i[mean min max mode]
|
74
|
+
unless args.size > 2 && args.keys.all? { |k| param_names.include? k }
|
75
|
+
raise "invalid args - can only be #{param_names.join ', '}, or rng."
|
76
|
+
end
|
79
77
|
|
80
|
-
|
78
|
+
param_names.each { |k| args[k] ||= nil } if args.size < param_names.size
|
81
79
|
|
82
|
-
|
83
|
-
|
84
|
-
|
80
|
+
nil_args = args.select { |_, v| v.nil? }.keys
|
81
|
+
nil_args_count = nil_args.count
|
82
|
+
raise 'too many nil args' if nil_args_count > 1
|
85
83
|
|
86
|
-
|
84
|
+
args.transform_values! &:to_f
|
87
85
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
else
|
93
|
-
key = nil_args.shift
|
94
|
-
case key
|
95
|
-
when :mean
|
96
|
-
args[key] = (args[:min] + args[:max] + args[:mode]) / 3.0
|
86
|
+
if nil_args_count == 0
|
87
|
+
if args[:mean] != (args[:min] + args[:max] + args[:mode]) / 3.0
|
88
|
+
raise 'inconsistent args'
|
89
|
+
end
|
97
90
|
else
|
98
|
-
|
99
|
-
|
91
|
+
key = nil_args.shift
|
92
|
+
case key
|
93
|
+
when :mean
|
94
|
+
args[key] = (args[:min] + args[:max] + args[:mode]) / 3.0
|
95
|
+
else
|
96
|
+
others = param_names - [key, :mean]
|
97
|
+
args[key] = 3 * args[:mean] - args.values_at(*others).sum
|
98
|
+
end
|
100
99
|
end
|
101
|
-
end
|
102
100
|
|
103
|
-
|
101
|
+
param_names.each { |parm| instance_variable_set("@#{parm}", args[parm]) }
|
104
102
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
@generator = Enumerator.new do |yielder|
|
111
|
-
loop do
|
112
|
-
u = rng.next
|
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
|
-
)
|
103
|
+
@rng = rng
|
104
|
+
@range = @max - @min
|
105
|
+
raise 'Min must be less than Max.' if @range <= 0
|
106
|
+
unless (@min..@max).include? @mode
|
107
|
+
raise 'Mode must be between Min and Max.'
|
118
108
|
end
|
109
|
+
@crossover_p = (@mode - @min).to_f / @range
|
110
|
+
end
|
111
|
+
|
112
|
+
def next
|
113
|
+
u = @rng.next
|
114
|
+
u < @crossover_p ?
|
115
|
+
@min + Math.sqrt(@range * (@mode - @min) * u) :
|
116
|
+
@max - Math.sqrt(@range * (@max - @mode) * (1.0 - u))
|
119
117
|
end
|
120
118
|
end
|
121
|
-
end
|
122
119
|
|
123
|
-
# Exponential random variate generator with specified +rate+ or +mean+.
|
124
|
-
# One and only one of +rate+ or +mean+ should be specified.
|
125
|
-
#
|
126
|
-
# *Arguments*::
|
127
|
-
# - +rate+ -> the rate of occurrences per unit time (default: +nil+).
|
128
|
-
# - +mean+ -> the expected value of the distribution (default: +nil+).
|
129
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
130
|
-
#
|
131
|
-
class Exponential
|
132
|
-
|
120
|
+
# Exponential random variate generator with specified +rate+ or +mean+.
|
121
|
+
# One and only one of +rate+ or +mean+ should be specified.
|
122
|
+
#
|
123
|
+
# *Arguments*::
|
124
|
+
# - +rate+ -> the rate of occurrences per unit time (default: +nil+).
|
125
|
+
# - +mean+ -> the expected value of the distribution (default: +nil+).
|
126
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
127
|
+
#
|
128
|
+
class Exponential
|
129
|
+
include RV_Generator
|
130
|
+
|
131
|
+
attr_reader :rate, :mean
|
132
|
+
|
133
|
+
def initialize(rate: nil, mean: nil, rng: U_GENERATOR)
|
134
|
+
raise 'Rate must be positive.' if rate && rate <= 0
|
135
|
+
raise 'Mean must be positive.' if mean && mean <= 0
|
136
|
+
unless rate.nil? ^ mean.nil?
|
137
|
+
raise 'Supply one and only one of mean or rate'
|
138
|
+
end
|
133
139
|
|
134
|
-
|
140
|
+
if rate.nil?
|
141
|
+
@mean = mean
|
142
|
+
@rate = 1.0 / mean
|
143
|
+
else
|
144
|
+
@mean = 1.0 / rate
|
145
|
+
@rate = rate
|
146
|
+
end
|
147
|
+
@rng = rng
|
148
|
+
end
|
135
149
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
raise 'Supply one and only one of mean or rate' unless rate.nil? ^ mean.nil?
|
150
|
+
def next
|
151
|
+
-@mean * Math.log(@rng.next)
|
152
|
+
end
|
140
153
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
else
|
154
|
+
def rate=(rate)
|
155
|
+
raise 'Rate must be a number.' unless rate.is_a? Number
|
156
|
+
raise 'Rate must be positive.' if rate <= 0
|
145
157
|
@mean = 1.0 / rate
|
146
158
|
@rate = rate
|
147
159
|
end
|
148
|
-
|
149
|
-
|
160
|
+
|
161
|
+
def mean=(mean)
|
162
|
+
raise 'Mean must be a number.' unless mean.is_a? Number
|
163
|
+
raise 'Mean must be positive.' if mean <= 0
|
164
|
+
@mean = mean
|
165
|
+
@rate = 1.0 / mean
|
150
166
|
end
|
151
167
|
end
|
152
|
-
end
|
153
|
-
|
154
|
-
# 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 Gaussian
|
163
|
-
include RV_Generator
|
164
168
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
+
# Normal/Gaussian random variate generator with specified
|
170
|
+
# +mean+ and +standard deviation+. Defaults to a standard normal.
|
171
|
+
#
|
172
|
+
# *Arguments*::
|
173
|
+
# - +mu+ -> the expected value (default: 0).
|
174
|
+
# - +sigma+ -> the standard deviation (default: 1).
|
175
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
176
|
+
#
|
177
|
+
class Normal
|
178
|
+
include RV_Generator
|
179
|
+
BOUND = 2.0 * Math.sqrt(2.0 / Math::E)
|
180
|
+
|
181
|
+
attr_reader :mu, :sigma
|
182
|
+
|
183
|
+
def initialize(mu: 0.0, sigma: 1.0, rng: U_GENERATOR)
|
184
|
+
raise 'Standard deviation must be positive.' if sigma <= 0
|
185
|
+
|
186
|
+
@mu = mu
|
187
|
+
@sigma = sigma
|
188
|
+
@rng = rng
|
189
|
+
end
|
169
190
|
|
170
|
-
|
171
|
-
@sd = sd
|
172
|
-
@generator = Enumerator.new do |yielder|
|
173
|
-
# Ratio of Uniforms
|
174
|
-
bound = 2.0 * Math.sqrt(2.0 / Math::E)
|
191
|
+
def next
|
175
192
|
loop do
|
176
|
-
u = rng.next
|
193
|
+
u = @rng.next
|
177
194
|
next if u == 0.0
|
178
|
-
|
179
|
-
v = bound * (rng.next - 0.5)
|
195
|
+
v = BOUND * (@rng.next - 0.5)
|
180
196
|
x = v / u
|
181
197
|
x_sqr = x * x
|
182
198
|
u_sqr = u * u
|
183
199
|
if 6.0 * x_sqr <= 44.0 - 72.0 * u + 36.0 * u_sqr - 8.0 * u * u_sqr
|
184
|
-
|
200
|
+
return @sigma * x + @mu
|
185
201
|
elsif x_sqr * u >= 2.0 - 2.0 * u_sqr
|
186
202
|
next
|
187
203
|
elsif x_sqr <= -4.0 * Math.log(u)
|
188
|
-
|
204
|
+
return @sigma * x + @mu
|
189
205
|
end
|
190
206
|
end
|
191
207
|
end
|
192
|
-
end
|
193
|
-
end
|
194
208
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
# - +mean+ -> the expected value (default: 0).
|
200
|
-
# - +sd+ -> the standard deviation (default: 1).
|
201
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
202
|
-
#
|
203
|
-
class BoxMuller
|
204
|
-
include RV_Generator
|
209
|
+
def sigma=(sigma)
|
210
|
+
raise 'sigma must be a number.' unless sigma.is_a? Number
|
211
|
+
@sigma = sigma
|
212
|
+
end
|
205
213
|
|
206
|
-
|
214
|
+
def mu=(mu)
|
215
|
+
raise 'mu must be a number.' unless mu.is_a? Number
|
216
|
+
@mu = mu
|
217
|
+
end
|
218
|
+
end
|
207
219
|
|
208
|
-
|
209
|
-
|
220
|
+
# Alternate normal/Gaussian random variate generator with specified
|
221
|
+
# +mean+ and +standard deviation+. Defaults to a standard normal.
|
222
|
+
#
|
223
|
+
# *Arguments*::
|
224
|
+
# - +mu+ -> the expected value (default: 0).
|
225
|
+
# - +sigma+ -> the standard deviation (default: 1).
|
226
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
227
|
+
#
|
228
|
+
class BoxMuller
|
229
|
+
include RV_Generator
|
230
|
+
TWO_PI = 2.0 * Math::PI
|
231
|
+
|
232
|
+
attr_reader :mu, :sigma
|
233
|
+
|
234
|
+
def initialize(mu: 0.0, sigma: 1.0, rng: U_GENERATOR)
|
235
|
+
raise 'Standard deviation must be positive.' if sigma <= 0
|
236
|
+
|
237
|
+
@mu = mu
|
238
|
+
@sigma = sigma
|
239
|
+
@rng = rng
|
240
|
+
@need_new_pair = false
|
241
|
+
# @next_norm = 0.0
|
242
|
+
end
|
210
243
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
if need_new_pair
|
220
|
-
theta = 2.0 * Math::PI * rng.next
|
221
|
-
d = sd * Math.sqrt(-2.0 * Math.log(rng.next))
|
222
|
-
next_z = mean + d * Math.sin(theta)
|
223
|
-
yielder << mean + d * Math.cos(theta)
|
224
|
-
else
|
225
|
-
yielder << next_z
|
226
|
-
end
|
244
|
+
def next
|
245
|
+
if @need_new_pair ^= true
|
246
|
+
theta = TWO_PI * @rng.next
|
247
|
+
d = @sigma * Math.sqrt(-2.0 * Math.log(@rng.next))
|
248
|
+
@next_norm = @mu + d * Math.sin(theta)
|
249
|
+
@mu + d * Math.cos(theta)
|
250
|
+
else
|
251
|
+
@next_norm
|
227
252
|
end
|
228
253
|
end
|
229
|
-
end
|
230
|
-
end
|
231
254
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
# *Arguments*::
|
237
|
-
# - +alpha+ -> the shape parameter (+alpha+ > 0; default: 1).
|
238
|
-
# - +beta+ -> the rate parameter (+beta+ > 0; default: 1).
|
239
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
240
|
-
#
|
241
|
-
class Gamma
|
242
|
-
include RV_Generator
|
255
|
+
def sigma=(sigma)
|
256
|
+
raise 'sigma must be a number.' unless sigma.is_a? Number
|
257
|
+
@sigma = sigma
|
258
|
+
end
|
243
259
|
|
244
|
-
|
260
|
+
def mu=(mu)
|
261
|
+
raise 'mu must be a number.' unless mu.is_a? Number
|
262
|
+
@mu = mu
|
263
|
+
end
|
264
|
+
end
|
245
265
|
|
246
|
-
|
247
|
-
|
266
|
+
# Gamma generator based on Marsaglia and Tsang method Algorithm 4.33
|
267
|
+
#
|
268
|
+
# Produces gamma RVs with expected value +alpha+ * +beta+.
|
269
|
+
#
|
270
|
+
# *Arguments*::
|
271
|
+
# - +alpha+ -> the shape parameter (+alpha+ > 0; default: 1).
|
272
|
+
# - +beta+ -> the rate parameter (+beta+ > 0; default: 1).
|
273
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
274
|
+
#
|
275
|
+
class Gamma
|
276
|
+
include RV_Generator
|
277
|
+
|
278
|
+
attr_reader :alpha, :beta
|
279
|
+
|
280
|
+
def initialize(alpha: 1.0, beta: 1.0, rng: U_GENERATOR)
|
281
|
+
raise 'Alpha and beta must be positive.' if alpha <= 0 || beta <= 0
|
282
|
+
|
283
|
+
@alpha = alpha
|
284
|
+
@beta = beta
|
285
|
+
@rng = rng
|
286
|
+
@std_normal = Normal.new(rng: rng)
|
287
|
+
end
|
248
288
|
|
249
|
-
|
250
|
-
|
251
|
-
std_normal = Gaussian.new(mean: 0.0, sd: 1.0, rng: rng)
|
252
|
-
@generator = Enumerator.new do |yielder|
|
253
|
-
loop { yielder << __gen__(alpha, beta, std_normal, rng) }
|
289
|
+
def next
|
290
|
+
__gen__(@alpha, @beta)
|
254
291
|
end
|
255
|
-
end
|
256
292
|
|
257
|
-
|
293
|
+
private
|
258
294
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
loop do
|
295
|
+
def __gen__(alpha, beta)
|
296
|
+
if alpha > 1
|
297
|
+
z = v = 0.0
|
298
|
+
d = alpha - 1.0 / 3.0
|
299
|
+
c = (1.0 / 3.0) / Math.sqrt(d)
|
265
300
|
loop do
|
266
|
-
|
267
|
-
|
268
|
-
|
301
|
+
loop do
|
302
|
+
z = @std_normal.next
|
303
|
+
v = 1.0 + c * z
|
304
|
+
break if v > 0
|
305
|
+
end
|
306
|
+
z2 = z * z
|
307
|
+
v = v * v * v
|
308
|
+
u = @rng.next
|
309
|
+
break if u < 1.0 - 0.0331 * z2 * z2
|
310
|
+
break if Math.log(u) < (0.5 * z2 + d * (1.0 - v + Math.log(v)))
|
269
311
|
end
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
break if u < 1.0 - 0.0331 * z2 * z2
|
274
|
-
break if Math.log(u) < (0.5 * z2 + d * (1.0 - v + Math.log(v)))
|
312
|
+
d * v * beta
|
313
|
+
else
|
314
|
+
__gen__(alpha + 1.0, beta) * (@rng.next**(1.0 / alpha))
|
275
315
|
end
|
276
|
-
d * v * beta
|
277
|
-
else
|
278
|
-
__gen__(alpha + 1.0, beta, std_normal, rng) * (rng.next**(1.0 / alpha))
|
279
316
|
end
|
280
317
|
end
|
281
|
-
end
|
282
318
|
|
283
|
-
# Weibull generator based on Devroye
|
284
|
-
#
|
285
|
-
# *Arguments*::
|
286
|
-
# - +rate+ -> the scale parameter (+rate+ > 0; default: 1).
|
287
|
-
# - +k+ -> the shape parameter (+k+ > 0; default: 1).
|
288
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
289
|
-
#
|
290
|
-
class Weibull
|
291
|
-
|
319
|
+
# Weibull generator based on Devroye
|
320
|
+
#
|
321
|
+
# *Arguments*::
|
322
|
+
# - +rate+ -> the scale parameter (+rate+ > 0; default: 1).
|
323
|
+
# - +k+ -> the shape parameter (+k+ > 0; default: 1).
|
324
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
325
|
+
#
|
326
|
+
class Weibull
|
327
|
+
include RV_Generator
|
292
328
|
|
293
|
-
|
329
|
+
attr_reader :rate, :k
|
294
330
|
|
295
|
-
|
296
|
-
|
331
|
+
def initialize(rate: 1.0, k: 1.0, rng: U_GENERATOR)
|
332
|
+
raise 'Rate and k must be positive.' if rate <= 0 || k <= 0
|
297
333
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
loop { yielder << (-Math.log(rng.next))**power / rate }
|
334
|
+
@rate = rate
|
335
|
+
@k = k
|
336
|
+
@rng = rng
|
337
|
+
@power = 1.0 / k
|
303
338
|
end
|
304
|
-
end
|
305
|
-
end
|
306
339
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
# - +rate+ -> the scale parameter (+rate+ > 0; default: 1).
|
311
|
-
# - +k+ -> the shape parameter (+k+ > 0; default: 1).
|
312
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
313
|
-
#
|
314
|
-
class Erlang < Weibull
|
315
|
-
def initialize(rate: 1.0, k: 1, rng: U_GENERATOR)
|
316
|
-
raise 'K must be integer.' unless k.integer?
|
317
|
-
|
318
|
-
super(rate: rate, k: k, rng: rng)
|
340
|
+
def next
|
341
|
+
(-Math.log(@rng.next))**@power / @rate
|
342
|
+
end
|
319
343
|
end
|
320
|
-
end
|
321
344
|
|
322
|
-
#
|
323
|
-
#
|
324
|
-
#
|
325
|
-
#
|
326
|
-
#
|
327
|
-
#
|
328
|
-
#
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
345
|
+
# Erlang generator - Weibull restricted to integer +k+
|
346
|
+
#
|
347
|
+
# *Arguments*::
|
348
|
+
# - +rate+ -> the scale parameter (+rate+ > 0; default: 1).
|
349
|
+
# - +k+ -> the shape parameter (+k+ > 0; default: 1).
|
350
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
351
|
+
#
|
352
|
+
class Erlang < Weibull
|
353
|
+
def initialize(rate: 1.0, k: 1, rng: U_GENERATOR)
|
354
|
+
raise 'K must be integer.' unless k.integer?
|
355
|
+
|
356
|
+
super(rate: rate, k: k, rng: rng)
|
357
|
+
end
|
358
|
+
end
|
335
359
|
|
336
|
-
|
360
|
+
# von Mises generator.
|
361
|
+
#
|
362
|
+
# This von Mises distribution generator is based on the VML algorithm by
|
363
|
+
# L. Barabesis: "Generating von Mises variates by the Ratio-of-Uniforms Method"
|
364
|
+
# Statistica Applicata Vol. 7, #4, 1995
|
365
|
+
# http://sa-ijas.stat.unipd.it/sites/sa-ijas.stat.unipd.it/files/417-426.pdf
|
366
|
+
#
|
367
|
+
# *Arguments*::
|
368
|
+
# - +kappa+ -> concentration coefficient (+kappa+ ≥ 0).
|
369
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
370
|
+
#
|
371
|
+
class VonMises
|
372
|
+
include RV_Generator
|
373
|
+
|
374
|
+
attr_reader :kappa
|
375
|
+
|
376
|
+
def initialize(kappa:, rng: U_GENERATOR)
|
377
|
+
raise 'kappa must be positive.' if kappa < 0
|
378
|
+
@kappa = kappa
|
379
|
+
@rng = rng
|
380
|
+
@s = (kappa > 1.3 ? 1.0 / Math.sqrt(kappa) : Math::PI * Math.exp(-kappa))
|
381
|
+
end
|
337
382
|
|
338
|
-
|
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|
|
383
|
+
def next
|
342
384
|
loop do
|
343
|
-
r1 = rng.next
|
344
|
-
theta = s * (2.0 * rng.next - 1.0) / r1
|
345
|
-
next if
|
346
|
-
|
347
|
-
|
385
|
+
r1 = @rng.next
|
386
|
+
theta = @s * (2.0 * @rng.next - 1.0) / r1
|
387
|
+
next if theta.abs > Math::PI
|
388
|
+
return theta if (0.25 * @kappa * theta * theta < 1.0 - r1) ||
|
389
|
+
(0.5 * @kappa * (Math.cos(theta) - 1.0) >= Math.log(r1))
|
348
390
|
end
|
349
391
|
end
|
350
392
|
end
|
351
|
-
end
|
352
393
|
|
353
|
-
# Poisson generator.
|
354
|
-
#
|
355
|
-
# *Arguments*::
|
356
|
-
# - +rate+ -> expected number per unit time/distance (+rate+ > 0; default: 1).
|
357
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
358
|
-
#
|
359
|
-
class Poisson
|
360
|
-
|
394
|
+
# Poisson generator.
|
395
|
+
#
|
396
|
+
# *Arguments*::
|
397
|
+
# - +rate+ -> expected number per unit time/distance (+rate+ > 0; default: 1).
|
398
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
399
|
+
#
|
400
|
+
class Poisson
|
401
|
+
include RV_Generator
|
361
402
|
|
362
|
-
|
403
|
+
attr_reader :rate
|
363
404
|
|
364
|
-
|
365
|
-
|
405
|
+
def initialize(rate: 1.0, rng: U_GENERATOR)
|
406
|
+
raise 'rate must be positive.' if rate <= 0
|
366
407
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
408
|
+
@rate = rate
|
409
|
+
@threshold = Math.exp(-rate)
|
410
|
+
@rng = rng
|
411
|
+
end
|
412
|
+
|
413
|
+
def next
|
414
|
+
count = 0
|
415
|
+
product = 1.0
|
416
|
+
count += 1 until (product *= @rng.next) < @threshold
|
417
|
+
count
|
376
418
|
end
|
377
419
|
end
|
378
|
-
end
|
379
420
|
|
380
|
-
# Geometric generator. Number of trials until first "success".
|
381
|
-
#
|
382
|
-
# *Arguments*::
|
383
|
-
# - +p+ -> the probability of success (0 < +p+ < 1; default: 0.5).
|
384
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
385
|
-
#
|
386
|
-
class Geometric
|
387
|
-
|
421
|
+
# Geometric generator. Number of trials until first "success".
|
422
|
+
#
|
423
|
+
# *Arguments*::
|
424
|
+
# - +p+ -> the probability of success (0 < +p+ < 1; default: 0.5).
|
425
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
426
|
+
#
|
427
|
+
class Geometric
|
428
|
+
include RV_Generator
|
388
429
|
|
389
|
-
|
430
|
+
attr_reader :p
|
390
431
|
|
391
|
-
|
392
|
-
|
432
|
+
def initialize(p: 0.5, rng: U_GENERATOR)
|
433
|
+
raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
|
393
434
|
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
loop { yielder << (Math.log(1.0 - rng.next) / log_q).ceil }
|
435
|
+
@p = p
|
436
|
+
@log_q = Math.log(1 - p)
|
437
|
+
@rng = rng
|
398
438
|
end
|
399
|
-
end
|
400
|
-
end
|
401
439
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
# - +p+ -> the probability of success (0 < +p+ < 1; default: 0.5).
|
407
|
-
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
408
|
-
#
|
409
|
-
class Binomial
|
410
|
-
include RV_Generator
|
411
|
-
|
412
|
-
attr_reader :n, :p
|
440
|
+
def next
|
441
|
+
(Math.log(1.0 - @rng.next) / @log_q).ceil
|
442
|
+
end
|
443
|
+
end
|
413
444
|
|
414
|
-
|
415
|
-
|
416
|
-
|
445
|
+
# Binomial generator. Number of "successes" in +n+ independent trials.
|
446
|
+
#
|
447
|
+
# *Arguments*::
|
448
|
+
# - +n+ -> the number of trials (+p+ > 0, integer; default: 1).
|
449
|
+
# - +p+ -> the probability of success (0 < +p+ < 1; default: 0.5).
|
450
|
+
# - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
|
451
|
+
#
|
452
|
+
class Binomial
|
453
|
+
include RV_Generator
|
454
|
+
|
455
|
+
attr_reader :n, :p
|
456
|
+
|
457
|
+
def initialize(n: 1, p: 0.5, rng: U_GENERATOR)
|
458
|
+
raise 'N must be an integer.' unless n.is_a? Integer
|
459
|
+
raise 'N must be positive.' if n <= 0
|
460
|
+
raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
|
461
|
+
|
462
|
+
@n = n.to_i
|
463
|
+
@p = p
|
464
|
+
@complement = false
|
465
|
+
if @p <= 0.5
|
466
|
+
@log_q = Math.log(1 - p)
|
467
|
+
else
|
468
|
+
@log_q = Math.log(@p)
|
469
|
+
@complement = true
|
470
|
+
end
|
471
|
+
@rng = rng
|
472
|
+
end
|
417
473
|
|
418
|
-
|
419
|
-
|
420
|
-
log_q = Math.log(1 - p)
|
421
|
-
@generator = Enumerator.new do |yielder|
|
474
|
+
def next
|
475
|
+
result = sum = 0
|
422
476
|
loop do
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
break if sum < log_q
|
427
|
-
|
428
|
-
result += 1
|
429
|
-
end
|
430
|
-
yielder << result
|
477
|
+
sum += Math.log(@rng.next) / (@n - result)
|
478
|
+
break if sum < @log_q
|
479
|
+
result += 1
|
431
480
|
end
|
481
|
+
@complement ? @n - result : result
|
432
482
|
end
|
433
483
|
end
|
434
484
|
end
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: random_variates
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.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: 2020-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: Random variate generators implemented as
|
13
|
+
description: Random variate generators implemented as enumerators.
|
14
14
|
email: pjs@alum.mit.edu
|
15
15
|
executables: []
|
16
16
|
extensions: []
|
@@ -19,7 +19,7 @@ files:
|
|
19
19
|
- LICENSE.md
|
20
20
|
- README.md
|
21
21
|
- lib/random_variates.rb
|
22
|
-
homepage:
|
22
|
+
homepage: https://bitbucket.org/paul_j_sanchez/random_variates.git
|
23
23
|
licenses:
|
24
24
|
- MIT
|
25
25
|
metadata: {}
|
@@ -31,14 +31,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '2.
|
34
|
+
version: '2.5'
|
35
35
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '0'
|
40
40
|
requirements: []
|
41
|
-
rubygems_version: 3.
|
41
|
+
rubygems_version: 3.1.2
|
42
42
|
signing_key:
|
43
43
|
specification_version: 4
|
44
44
|
summary: Random variate generator classes.
|