random_variates 0.2.2 → 0.3.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 (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.