random_variates 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/random_variates.rb +124 -46
  3. metadata +3 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67576ececc07bebc1fdca7b6f25d4c2bfc9b719765e88a63530d4bf6dcb9e982
4
- data.tar.gz: 271706b932ed05415196da7836d421e2be95c7d8a887cedf715101aa50802f03
3
+ metadata.gz: 7d30e6b62a53350bfd18a1deb7d5338c98d7fcd479a9f5020ff7bc6e8358c256
4
+ data.tar.gz: 33a02922156dc7b17b9aada61693562aca6a12cf059f2e5fa775f52b7de10bc0
5
5
  SHA512:
6
- metadata.gz: 7f30374ac11869a77187be0532678bb8a3f3d00afcad1a894cf341d488c563ef5ced758a5a8641b584c42f51805af78b3a8eae95486b9839cc73e7c680714601
7
- data.tar.gz: d1aa14d4af1113bf220c6e02aa516cbf66f30dd5c0932e4766d11984b5656a3deb9fa08e482cca194282c3e76c3a7e2c1c49249fa34fbecdb71f96d9d5507c73
6
+ metadata.gz: d2d6f766aa38c6647c4f184fb279854597f7bee784b007ca1da35a3791200645872e5cc433270ce14a094408a360afac313170e155bb451653a43be03996cae4
7
+ data.tar.gz: 82ea2362281c4eb8fe9ee0168f88df8a77333530d8a693cafd7a5ecc5490efea05167561a2458c3b65d21033a35ac791afecb667185cdaa72e1c681907959904
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This library implements random variate generation for several
2
4
  # common statistical distributions. Each distribution is implemented
3
5
  # in its own class, and different parameterizations are created as
@@ -44,7 +46,8 @@ class Uniform
44
46
  attr_reader :min, :max
45
47
 
46
48
  def initialize(min: 0.0, max: 1.0, rng: U_GENERATOR)
47
- fail 'Max must be greater than min.' if max <= min
49
+ raise 'Max must be greater than min.' if max <= min
50
+
48
51
  @min = min
49
52
  @max = max
50
53
  range = max - min
@@ -57,32 +60,61 @@ end
57
60
  # Triangular random variate generator with specified +min+, +mode+, and +max+.
58
61
  #
59
62
  # *Arguments*::
60
- # - +min+ -> the lower bound for the range (default: 0).
61
- # - +max+ -> the upper bound for the range (default: 1).
62
- # - +mode+ -> the highest likelihood value (+min+ ≤ +mode+ ≤ +max+; default: 1).
63
63
  # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
64
+ # - +min+ -> the lower bound for the range.
65
+ # - +max+ -> the upper bound for the range.
66
+ # - +mode+ -> the highest likelihood value (+min+ ≤ +mode+ ≤ +max+).
67
+ # - +mean+ -> the expected value of the distribution.
64
68
  #
65
69
  class Triangle
66
70
  include RV_Generator
67
71
 
68
- attr_reader :min, :max, :mode, :range
72
+ attr_reader :min, :max, :mode, :range, :mean
69
73
 
70
- def initialize(min: 0.0, max: 1.0, mode: 1.0, rng: U_GENERATOR)
71
- @range = max - min
72
- fail 'Min must be less than Max.' if @range <= 0
73
- fail 'Mode must be between Min and Max.' unless (min..max).include? mode
74
- @min = min
75
- @max = max
76
- @mode = mode
77
- crossover_p = (mode - min).to_f / @range
74
+ def initialize(rng: U_GENERATOR, **args)
75
+ param_names = %i[mean min max mode]
76
+ unless args.size > 2 && args.keys.all? { |k| param_names.include? k }
77
+ raise "invalid args - can only be #{param_names.join ', '}, or rng."
78
+ end
79
+
80
+ param_names.each { |k| args[k] ||= nil } if args.size < param_names.size
81
+
82
+ nil_args = args.select { |_, v| v.nil? }.keys
83
+ nil_args_count = nil_args.count
84
+ raise 'too many nil args' if nil_args_count > 1
85
+
86
+ args.transform_values! &:to_f
87
+
88
+ if nil_args_count == 0
89
+ if args[:mean] != (args[:min] + args[:max] + args[:mode]) / 3.0
90
+ raise 'inconsistent args'
91
+ end
92
+ else
93
+ key = nil_args.shift
94
+ case key
95
+ when :mean
96
+ args[key] = (args[:min] + args[:max] + args[:mode]) / 3.0
97
+ else
98
+ others = param_names - [key, :mean]
99
+ args[key] = 3 * args[:mean] - args.values_at(*others).sum
100
+ end
101
+ end
102
+
103
+ param_names.each { |parm| instance_variable_set("@#{parm}", args[parm]) }
104
+
105
+ @range = @max - @min
106
+ raise 'Min must be less than Max.' if @range <= 0
107
+ raise 'Mode must be between Min and Max.' unless (@min..@max).include? @mode
108
+
109
+ crossover_p = (@mode - @min).to_f / @range
78
110
  @generator = Enumerator.new do |yielder|
79
111
  loop do
80
112
  u = rng.next
81
- if u < crossover_p
82
- yielder << min + Math.sqrt(@range * (mode - min) * u)
83
- else
84
- yielder << max - Math.sqrt(@range * (max - mode) * (1.0 - u))
85
- end
113
+ yielder << (
114
+ u < crossover_p ?
115
+ @min + Math.sqrt(@range * (@mode - @min) * u) :
116
+ @max - Math.sqrt(@range * (@max - @mode) * (1.0 - u))
117
+ )
86
118
  end
87
119
  end
88
120
  end
@@ -99,16 +131,19 @@ end
99
131
  class Exponential
100
132
  include RV_Generator
101
133
 
102
- attr_reader :rate
134
+ attr_reader :rate, :mean
103
135
 
104
136
  def initialize(rate: nil, mean: nil, rng: U_GENERATOR)
105
- fail 'Rate must be positive.' if rate && rate <= 0
106
- fail 'Mean must be positive.' if mean && mean <= 0
107
- fail 'Supply one and only one of mean or rate' unless rate.nil? ^ mean.nil?
137
+ raise 'Rate must be positive.' if rate && rate <= 0
138
+ raise 'Mean must be positive.' if mean && mean <= 0
139
+ raise 'Supply one and only one of mean or rate' unless rate.nil? ^ mean.nil?
140
+
108
141
  if rate.nil?
109
- @mean, @rate = mean, 1.0 / mean
142
+ @mean = mean
143
+ @rate = 1.0 / mean
110
144
  else
111
- @mean, @rate = 1.0 / rate, rate
145
+ @mean = 1.0 / rate
146
+ @rate = rate
112
147
  end
113
148
  @generator = Enumerator.new do |yielder|
114
149
  loop { yielder << (-@mean * Math.log(rng.next)) }
@@ -130,27 +165,29 @@ class Gaussian
130
165
  attr_reader :mean, :sd
131
166
 
132
167
  def initialize(mean: 0.0, sd: 1.0, rng: U_GENERATOR)
133
- fail 'Standard deviation must be positive.' if sd <= 0
168
+ raise 'Standard deviation must be positive.' if sd <= 0
169
+
134
170
  @mean = mean
135
171
  @sd = sd
136
172
  @generator = Enumerator.new do |yielder|
137
173
  # Ratio of Uniforms
138
174
  bound = 2.0 * Math.sqrt(2.0 / Math::E)
139
175
  loop do
140
- u = rng.next
141
- next if u == 0.0
142
- v = bound * (rng.next - 0.5)
143
- x = v / u
144
- x_sqr = x * x
145
- u_sqr = u * u
146
- if 6.0 * x_sqr <= 44.0 - 72.0 * u + 36.0 * u_sqr - 8.0 * u * u_sqr
147
- yielder << sd * x + mean
148
- elsif x_sqr * u >= 2.0 - 2.0 * u_sqr
149
- next
150
- elsif x_sqr <= -4.0 * Math.log(u)
151
- yielder << sd * x + mean
152
- end
176
+ u = rng.next
177
+ next if u == 0.0
178
+
179
+ v = bound * (rng.next - 0.5)
180
+ x = v / u
181
+ x_sqr = x * x
182
+ u_sqr = u * u
183
+ if 6.0 * x_sqr <= 44.0 - 72.0 * u + 36.0 * u_sqr - 8.0 * u * u_sqr
184
+ yielder << sd * x + mean
185
+ elsif x_sqr * u >= 2.0 - 2.0 * u_sqr
186
+ next
187
+ elsif x_sqr <= -4.0 * Math.log(u)
188
+ yielder << sd * x + mean
153
189
  end
190
+ end
154
191
  end
155
192
  end
156
193
  end
@@ -169,7 +206,8 @@ class BoxMuller
169
206
  attr_reader :mean, :sd
170
207
 
171
208
  def initialize(mean: 0.0, sd: 1.0, rng: U_GENERATOR)
172
- fail 'Standard deviation must be positive.' if sd <= 0
209
+ raise 'Standard deviation must be positive.' if sd <= 0
210
+
173
211
  @mean = mean
174
212
  @sd = sd
175
213
  @generator = Enumerator.new do |yielder|
@@ -193,6 +231,8 @@ end
193
231
 
194
232
  # Gamma generator based on Marsaglia and Tsang method Algorithm 4.33
195
233
  #
234
+ # Produces gamma RVs with expected value +alpha+ * +beta+.
235
+ #
196
236
  # *Arguments*::
197
237
  # - +alpha+ -> the shape parameter (+alpha+ > 0; default: 1).
198
238
  # - +beta+ -> the rate parameter (+beta+ > 0; default: 1).
@@ -204,7 +244,8 @@ class Gamma
204
244
  attr_reader :alpha, :beta
205
245
 
206
246
  def initialize(alpha: 1.0, beta: 1.0, rng: U_GENERATOR)
207
- fail 'Alpha and beta must be positive.' if alpha <= 0 || beta <= 0
247
+ raise 'Alpha and beta must be positive.' if alpha <= 0 || beta <= 0
248
+
208
249
  @alpha = alpha
209
250
  @beta = beta
210
251
  std_normal = Gaussian.new(mean: 0.0, sd: 1.0, rng: rng)
@@ -252,7 +293,8 @@ class Weibull
252
293
  attr_reader :rate, :k
253
294
 
254
295
  def initialize(rate: 1.0, k: 1.0, rng: U_GENERATOR)
255
- fail 'Rate and k must be positive.' if rate <= 0 || k <= 0
296
+ raise 'Rate and k must be positive.' if rate <= 0 || k <= 0
297
+
256
298
  @rate = rate
257
299
  @k = k
258
300
  power = 1.0 / k
@@ -271,11 +313,43 @@ end
271
313
  #
272
314
  class Erlang < Weibull
273
315
  def initialize(rate: 1.0, k: 1, rng: U_GENERATOR)
274
- fail 'K must be integer.' unless k.integer?
316
+ raise 'K must be integer.' unless k.integer?
317
+
275
318
  super(rate: rate, k: k, rng: rng)
276
319
  end
277
320
  end
278
321
 
322
+ # von Mises generator.
323
+ #
324
+ # This von Mises distribution generator is based on the VML algorithm by
325
+ # L. Barabesis: "Generating von Mises variates by the Ratio-of-Uniforms Method"
326
+ # Statistica Applicata Vol. 7, #4, 1995
327
+ # http://sa-ijas.stat.unipd.it/sites/sa-ijas.stat.unipd.it/files/417-426.pdf
328
+ #
329
+ # *Arguments*::
330
+ # - +kappa+ -> concentration coefficient (+kappa+ ≥ 0).
331
+ # - +rng+ -> the (+Enumerable+) source of U(0, 1)'s (default: U_GENERATOR)
332
+ #
333
+ class VonMises
334
+ include RV_Generator
335
+
336
+ attr_reader :kappa
337
+
338
+ def initialize(kappa:, rng: U_GENERATOR)
339
+ raise 'kappa must be positive.' if kappa < 0
340
+ s = (kappa > 1.3 ? 1.0 / Math.sqrt(kappa) : Math::PI * Math.exp(-kappa))
341
+ @generator = Enumerator.new do |yielder|
342
+ loop do
343
+ r1 = rng.next
344
+ theta = s * (2.0 * rng.next - 1.0) / r1
345
+ next if (theta.abs > Math::PI)
346
+ yielder << theta if (0.25 * kappa * theta * theta < 1.0 - r1) ||
347
+ (0.5 * kappa * (Math.cos(theta) - 1.0) >= Math.log(r1))
348
+ end
349
+ end
350
+ end
351
+ end
352
+
279
353
  # Poisson generator.
280
354
  #
281
355
  # *Arguments*::
@@ -288,7 +362,8 @@ class Poisson
288
362
  attr_reader :rate
289
363
 
290
364
  def initialize(rate: 1.0, rng: U_GENERATOR)
291
- fail 'rate must be positive.' if rate <= 0
365
+ raise 'rate must be positive.' if rate <= 0
366
+
292
367
  @rate = rate
293
368
  threshold = Math.exp(-rate)
294
369
  @generator = Enumerator.new do |yielder|
@@ -314,7 +389,8 @@ class Geometric
314
389
  attr_reader :p
315
390
 
316
391
  def initialize(p: 0.5, rng: U_GENERATOR)
317
- fail 'Require 0 < p < 1.' if p <= 0 || p >= 1
392
+ raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
393
+
318
394
  @p = p
319
395
  log_q = Math.log(1 - p)
320
396
  @generator = Enumerator.new do |yielder|
@@ -336,8 +412,9 @@ class Binomial
336
412
  attr_reader :n, :p
337
413
 
338
414
  def initialize(n: 1, p: 0.5, rng: U_GENERATOR)
339
- fail 'N must be a positive integer.' if n.to_i != n || n <= 0
340
- fail 'Require 0 < p < 1.' if p <= 0 || p >= 1
415
+ raise 'N must be a positive integer.' if n.to_i != n || n <= 0
416
+ raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
417
+
341
418
  @n = n.to_i
342
419
  @p = p
343
420
  log_q = Math.log(1 - p)
@@ -347,6 +424,7 @@ class Binomial
347
424
  loop do
348
425
  sum += Math.log(rng.next) / (n - result)
349
426
  break if sum < log_q
427
+
350
428
  result += 1
351
429
  end
352
430
  yielder << result
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: random_variates
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul J Sanchez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-15 00:00:00.000000000 Z
11
+ date: 2019-08-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Random variate generators implemented as enumerator/enumerable.
14
14
  email: pjs@alum.mit.edu
@@ -38,8 +38,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
40
  requirements: []
41
- rubyforge_project:
42
- rubygems_version: 2.7.7
41
+ rubygems_version: 3.0.3
43
42
  signing_key:
44
43
  specification_version: 4
45
44
  summary: Random variate generator classes.