random_variates 0.3.0 → 0.4.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/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.
|