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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.md +1 -1
  3. data/README.md +16 -4
  4. data/lib/random_variates.rb +376 -326
  5. metadata +6 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d30e6b62a53350bfd18a1deb7d5338c98d7fcd479a9f5020ff7bc6e8358c256
4
- data.tar.gz: 33a02922156dc7b17b9aada61693562aca6a12cf059f2e5fa775f52b7de10bc0
3
+ metadata.gz: 43a85c7f9cd47a418101215d490e1089ab8b5867f860c6200359c2d1f08079a2
4
+ data.tar.gz: 6bb665672cbbfcd7b281739acc3e78437ad8a2632e21208559d9a5b4dc380645
5
5
  SHA512:
6
- metadata.gz: d2d6f766aa38c6647c4f184fb279854597f7bee784b007ca1da35a3791200645872e5cc433270ce14a094408a360afac313170e155bb451653a43be03996cae4
7
- data.tar.gz: 82ea2362281c4eb8fe9ee0168f88df8a77333530d8a693cafd7a5ecc5490efea05167561a2458c3b65d21033a35ac791afecb667185cdaa72e1c681907959904
6
+ metadata.gz: 5741f020d080a4e2a2e2dec67e3654eeb6e8cd5ea4725881ccb94f7b2b1b9efbfc38548bcadae625f2b68e36d868209259fc485c6d30007eed11b2154bb65df8
7
+ data.tar.gz: f5f8821ecf11b76639c8e1c63391dbbfc5f490617da946a49d157dee1c9f749a6d41755a5ae1abcc4b4000743c8db358654e0ff7e7840b7af2740c7afd7fa1b3
data/LICENSE.md CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  The MIT License (MIT)
3
3
 
4
- Copyright (c) 2017-2018 Paul J. Sanchez
4
+ Copyright (c) 2020 Paul J. Sanchez
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
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 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`.
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.
@@ -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
- # Provide access to +Kernel#rand+ as an +Enumerator+ object.
18
- U_GENERATOR = Enumerator.new { |yielder| loop { yielder << rand } }
19
-
20
- # The +RV_Generator+ module provides a common core of methods to make
21
- # all of the RV classes +Enumerable+.
22
- module RV_Generator
23
- include Enumerable
24
-
25
- attr_reader :generator
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
- def each
32
- loop { yield @generator.next }
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
- include RV_Generator
45
-
46
- attr_reader :min, :max
47
-
48
- def initialize(min: 0.0, max: 1.0, rng: U_GENERATOR)
49
- raise 'Max must be greater than min.' if max <= min
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
- @min = min
52
- @max = max
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
- include RV_Generator
71
-
72
- attr_reader :min, :max, :mode, :range, :mean
73
-
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
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
- param_names.each { |k| args[k] ||= nil } if args.size < param_names.size
78
+ param_names.each { |k| args[k] ||= nil } if args.size < param_names.size
81
79
 
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
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
- args.transform_values! &:to_f
84
+ args.transform_values! &:to_f
87
85
 
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
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
- others = param_names - [key, :mean]
99
- args[key] = 3 * args[:mean] - args.values_at(*others).sum
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
- param_names.each { |parm| instance_variable_set("@#{parm}", args[parm]) }
101
+ param_names.each { |parm| instance_variable_set("@#{parm}", args[parm]) }
104
102
 
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
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
- include RV_Generator
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
- attr_reader :rate, :mean
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
- def initialize(rate: nil, mean: nil, rng: U_GENERATOR)
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?
150
+ def next
151
+ -@mean * Math.log(@rng.next)
152
+ end
140
153
 
141
- if rate.nil?
142
- @mean = mean
143
- @rate = 1.0 / mean
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
- @generator = Enumerator.new do |yielder|
149
- loop { yielder << (-@mean * Math.log(rng.next)) }
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
- attr_reader :mean, :sd
166
-
167
- def initialize(mean: 0.0, sd: 1.0, rng: U_GENERATOR)
168
- raise 'Standard deviation must be positive.' if sd <= 0
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
- @mean = mean
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
- yielder << sd * x + mean
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
- yielder << sd * x + mean
204
+ return @sigma * x + @mu
189
205
  end
190
206
  end
191
207
  end
192
- end
193
- end
194
208
 
195
- # Alternate Gaussian/normal random variate generator with specified
196
- # +mean+ and +standard deviation+. Defaults to a standard normal.
197
- #
198
- # *Arguments*::
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
- attr_reader :mean, :sd
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
- def initialize(mean: 0.0, sd: 1.0, rng: U_GENERATOR)
209
- raise 'Standard deviation must be positive.' if sd <= 0
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
- @mean = mean
212
- @sd = sd
213
- @generator = Enumerator.new do |yielder|
214
- # Box-Muller
215
- next_z = 0.0
216
- need_new_pair = false
217
- loop do
218
- need_new_pair ^= true
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
- # Gamma generator based on Marsaglia and Tsang method Algorithm 4.33
233
- #
234
- # Produces gamma RVs with expected value +alpha+ * +beta+.
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
- attr_reader :alpha, :beta
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
- def initialize(alpha: 1.0, beta: 1.0, rng: U_GENERATOR)
247
- raise 'Alpha and beta must be positive.' if alpha <= 0 || beta <= 0
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
- @alpha = alpha
250
- @beta = beta
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
- private
293
+ private
258
294
 
259
- def __gen__(alpha, beta, std_normal, rng)
260
- if alpha > 1
261
- z = v = 0.0
262
- d = alpha - 1.0 / 3.0
263
- c = (1.0 / 3.0) / Math.sqrt(d)
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
- z = std_normal.next
267
- v = 1.0 + c * z
268
- break if v > 0
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
- z2 = z * z
271
- v = v * v * v
272
- u = rng.next
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
- include RV_Generator
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
- attr_reader :rate, :k
329
+ attr_reader :rate, :k
294
330
 
295
- def initialize(rate: 1.0, k: 1.0, rng: U_GENERATOR)
296
- raise 'Rate and k must be positive.' if rate <= 0 || k <= 0
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
- @rate = rate
299
- @k = k
300
- power = 1.0 / k
301
- @generator = Enumerator.new do |yielder|
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
- # Erlang generator - Weibull restricted to integer +k+
308
- #
309
- # *Arguments*::
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
- # 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
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
- attr_reader :kappa
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
- 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|
383
+ def next
342
384
  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))
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
- include RV_Generator
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
- attr_reader :rate
403
+ attr_reader :rate
363
404
 
364
- def initialize(rate: 1.0, rng: U_GENERATOR)
365
- raise 'rate must be positive.' if rate <= 0
405
+ def initialize(rate: 1.0, rng: U_GENERATOR)
406
+ raise 'rate must be positive.' if rate <= 0
366
407
 
367
- @rate = rate
368
- threshold = Math.exp(-rate)
369
- @generator = Enumerator.new do |yielder|
370
- loop do
371
- count = 0
372
- product = 1.0
373
- count += 1 until (product *= rng.next) < threshold
374
- yielder << count
375
- end
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
- include RV_Generator
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
- attr_reader :p
430
+ attr_reader :p
390
431
 
391
- def initialize(p: 0.5, rng: U_GENERATOR)
392
- raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
432
+ def initialize(p: 0.5, rng: U_GENERATOR)
433
+ raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
393
434
 
394
- @p = p
395
- log_q = Math.log(1 - p)
396
- @generator = Enumerator.new do |yielder|
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
- # Binomial generator. Number of "successes" in +n+ independent trials.
403
- #
404
- # *Arguments*::
405
- # - +n+ -> the number of trials (+p+ > 0, integer; default: 1).
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
- def initialize(n: 1, p: 0.5, rng: U_GENERATOR)
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
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
- @n = n.to_i
419
- @p = p
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
- result = sum = 0
424
- loop do
425
- sum += Math.log(rng.next) / (n - result)
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.3.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: 2019-08-28 00:00:00.000000000 Z
11
+ date: 2020-05-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Random variate generators implemented as enumerator/enumerable.
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.0'
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.0.3
41
+ rubygems_version: 3.1.2
42
42
  signing_key:
43
43
  specification_version: 4
44
44
  summary: Random variate generator classes.