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.
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.