random_variates 0.4.0 → 0.5.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 +25 -11
  4. data/lib/random_variates.rb +46 -36
  5. metadata +28 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43a85c7f9cd47a418101215d490e1089ab8b5867f860c6200359c2d1f08079a2
4
- data.tar.gz: 6bb665672cbbfcd7b281739acc3e78437ad8a2632e21208559d9a5b4dc380645
3
+ metadata.gz: 6ae5af59fb1609e227a42bc3fe9b93f1cb0d561513526044de0f74bc95fe9040
4
+ data.tar.gz: 5a18534afb749cf8b0d829b64f1f67daec0809ee0669073872dcf477a24f4d99
5
5
  SHA512:
6
- metadata.gz: 5741f020d080a4e2a2e2dec67e3654eeb6e8cd5ea4725881ccb94f7b2b1b9efbfc38548bcadae625f2b68e36d868209259fc485c6d30007eed11b2154bb65df8
7
- data.tar.gz: f5f8821ecf11b76639c8e1c63391dbbfc5f490617da946a49d157dee1c9f749a6d41755a5ae1abcc4b4000743c8db358654e0ff7e7840b7af2740c7afd7fa1b3
6
+ metadata.gz: a22d4223b928d7013deeb938fa497b231821d28939a75521b1e30f0bea1e20e95e8bb730c0a0dc8a2a0513b9174821abe36174a068dcc1cd81fafad941620af4
7
+ data.tar.gz: b4651ab9d4a17cb250a35a819658f92b6212c39f6cac9670ea3d069ab98e2335ea5b345e9ff138c7d642741a33441d0d29faa6c5eb41c545b65845bbf0257222
data/LICENSE.md CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  The MIT License (MIT)
3
3
 
4
- Copyright (c) 2020 Paul J. Sanchez
4
+ Copyright (c) 2023 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,4 +1,4 @@
1
- ### DESCRIPTION
1
+ #### DESCRIPTION
2
2
 
3
3
  This gem implements random variate generation for several
4
4
  common statistical distributions. Each distribution is implemented
@@ -7,19 +7,33 @@ instances of the class using the constructor to specify the
7
7
  parameterization. All constructors use named parameters for clarity,
8
8
  so the order of parameters does not matter. All random variate classes
9
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
+ pseudo-random number generator that has a `rand` method that provides
11
+ U(0,1) values to use as the core source of randomness.
12
+ If `rng` is not specified, it defaults to an instance of
13
+ class `Xoroshiro::Random`.
12
14
 
13
15
  Once a random variate class has been instantiated, values can either be
14
- generated on demand using the `next` method or by using the instance as
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
+ generated on demand using the `next` method (preferred for speed) or by
17
+ creating an `Enumerator` for use in any iterable context. For example,
18
+ the following will create an array of fifty exponentially distributed
19
+ values having rate 3.
20
+
21
+ my_exp = RV::Exponential.new(rate: 3)
22
+ my_exp.each.take(50)
16
23
 
17
24
  ---
18
25
 
19
- ### RELEASE NOTES
26
+ #### RELEASE NOTES
27
+
28
+ **v0.5**
29
+
30
+ Prior releases focused on statistical correctness and adding distributions. This
31
+ release is about speed improvements. Substantial speedups have been accomplished by:
20
32
 
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.
33
+ - replacing the prior U(0,1) `Enumerator` architecture with direct calls to the PRNG;
34
+ - replacing `loop do` with the measurably faster `while true` in
35
+ acceptance/rejection based algorithms; and
36
+ - replacing ruby's built-in `Random` with `Xoroshiro::Random` as the default
37
+ PRNG. Xoshiro256** is faster than ruby's MT19937 implementation, and passes
38
+ all tests in the TestU01 suite. See [xoshiro / xoroshiro generators and the
39
+ PRNG shootout](https://prng.di.unimi.it) for more information.
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'xoroshiro'
4
+
3
5
  # This library implements random variate generation for several
4
6
  # common statistical distributions. Each distribution is implemented
5
7
  # in its own class, and different parameterizations are created as
6
8
  # instances of the class using the constructor to specify the
7
9
  # parameterization. All constructors use named parameters for clarity,
8
10
  # so the order of parameters does not matter. All RV classes provide an
9
- # optional argument +rng+, with which the user can specify an enumerable
10
- # U(0,1) generator to use as the core source of randomness. If +rng+ is
11
+ # optional argument +rng+, with which the user can specify a U(0,1)
12
+ # PRNG object to use as the core source of randomness. If +rng+ is
11
13
  # not specified, it defaults to +Kernel#rand+.
12
14
  #
13
15
  # Once a random variate class has been instantiated, values can either be
@@ -15,8 +17,8 @@
15
17
  # a generator in any iterable context.
16
18
 
17
19
  module RV
18
- # Provide access to +Kernel#rand+ as an +Enumerator+.
19
- U_GENERATOR = Enumerator.new { |yielder| loop { yielder << rand } }
20
+ # Set default PRNG.
21
+ U_GENERATOR = Xoroshiro::Random.new
20
22
 
21
23
  # The +RV_Generator+ module provides a common core of methods to make
22
24
  # all of the RV classes iterable.
@@ -26,7 +28,11 @@ module RV
26
28
  end
27
29
 
28
30
  def each
29
- Enumerator.new { |y| loop { y << self.next } }
31
+ Enumerator.new do |y|
32
+ while true
33
+ y << self.next
34
+ end
35
+ end
30
36
  end
31
37
  end
32
38
 
@@ -40,18 +46,17 @@ module RV
40
46
  class Uniform
41
47
  include RV_Generator
42
48
 
43
- attr_reader :min, :max, :range
49
+ attr_reader :min, :max
44
50
 
45
51
  def initialize(min: 0.0, max: 1.0, rng: U_GENERATOR)
46
- raise 'Max must be greater than min.' if max <= min
52
+ raise 'Max must be greater than min.' unless max > min
47
53
  @min = min
48
54
  @max = max
49
- @range = max - min
50
55
  @rng = rng
51
56
  end
52
57
 
53
58
  def next
54
- @min + @range * @rng.next
59
+ @rng.rand(@min..@max)
55
60
  end
56
61
  end
57
62
 
@@ -110,7 +115,7 @@ module RV
110
115
  end
111
116
 
112
117
  def next
113
- u = @rng.next
118
+ u = @rng.rand
114
119
  u < @crossover_p ?
115
120
  @min + Math.sqrt(@range * (@mode - @min) * u) :
116
121
  @max - Math.sqrt(@range * (@max - @mode) * (1.0 - u))
@@ -148,18 +153,18 @@ module RV
148
153
  end
149
154
 
150
155
  def next
151
- -@mean * Math.log(@rng.next)
156
+ -@mean * Math.log(@rng.rand)
152
157
  end
153
158
 
154
159
  def rate=(rate)
155
- raise 'Rate must be a number.' unless rate.is_a? Number
160
+ raise 'Rate must be a number.' unless rate.is_a? Numeric
156
161
  raise 'Rate must be positive.' if rate <= 0
157
162
  @mean = 1.0 / rate
158
163
  @rate = rate
159
164
  end
160
165
 
161
166
  def mean=(mean)
162
- raise 'Mean must be a number.' unless mean.is_a? Number
167
+ raise 'Mean must be a number.' unless mean.is_a? Numeric
163
168
  raise 'Mean must be positive.' if mean <= 0
164
169
  @mean = mean
165
170
  @rate = 1.0 / mean
@@ -189,10 +194,10 @@ module RV
189
194
  end
190
195
 
191
196
  def next
192
- loop do
193
- u = @rng.next
197
+ while true
198
+ u = @rng.rand
194
199
  next if u == 0.0
195
- v = BOUND * (@rng.next - 0.5)
200
+ v = BOUND * (@rng.rand - 0.5)
196
201
  x = v / u
197
202
  x_sqr = x * x
198
203
  u_sqr = u * u
@@ -207,12 +212,12 @@ module RV
207
212
  end
208
213
 
209
214
  def sigma=(sigma)
210
- raise 'sigma must be a number.' unless sigma.is_a? Number
215
+ raise 'sigma must be a number.' unless sigma.is_a? Numeric
211
216
  @sigma = sigma
212
217
  end
213
218
 
214
219
  def mu=(mu)
215
- raise 'mu must be a number.' unless mu.is_a? Number
220
+ raise 'mu must be a number.' unless mu.is_a? Numeric
216
221
  @mu = mu
217
222
  end
218
223
  end
@@ -238,13 +243,12 @@ module RV
238
243
  @sigma = sigma
239
244
  @rng = rng
240
245
  @need_new_pair = false
241
- # @next_norm = 0.0
242
246
  end
243
247
 
244
248
  def next
245
249
  if @need_new_pair ^= true
246
- theta = TWO_PI * @rng.next
247
- d = @sigma * Math.sqrt(-2.0 * Math.log(@rng.next))
250
+ theta = TWO_PI * @rng.rand
251
+ d = @sigma * Math.sqrt(-2.0 * Math.log(@rng.rand))
248
252
  @next_norm = @mu + d * Math.sin(theta)
249
253
  @mu + d * Math.cos(theta)
250
254
  else
@@ -253,12 +257,12 @@ module RV
253
257
  end
254
258
 
255
259
  def sigma=(sigma)
256
- raise 'sigma must be a number.' unless sigma.is_a? Number
260
+ raise 'sigma must be a number.' unless sigma.is_a? Numeric
257
261
  @sigma = sigma
258
262
  end
259
263
 
260
264
  def mu=(mu)
261
- raise 'mu must be a number.' unless mu.is_a? Number
265
+ raise 'mu must be a number.' unless mu.is_a? Numeric
262
266
  @mu = mu
263
267
  end
264
268
  end
@@ -297,21 +301,21 @@ module RV
297
301
  z = v = 0.0
298
302
  d = alpha - 1.0 / 3.0
299
303
  c = (1.0 / 3.0) / Math.sqrt(d)
300
- loop do
301
- loop do
304
+ while true
305
+ while true
302
306
  z = @std_normal.next
303
307
  v = 1.0 + c * z
304
308
  break if v > 0
305
309
  end
306
310
  z2 = z * z
307
311
  v = v * v * v
308
- u = @rng.next
312
+ u = @rng.rand
309
313
  break if u < 1.0 - 0.0331 * z2 * z2
310
314
  break if Math.log(u) < (0.5 * z2 + d * (1.0 - v + Math.log(v)))
311
315
  end
312
316
  d * v * beta
313
317
  else
314
- __gen__(alpha + 1.0, beta) * (@rng.next**(1.0 / alpha))
318
+ __gen__(alpha + 1.0, beta) * (@rng.rand**(1.0 / alpha))
315
319
  end
316
320
  end
317
321
  end
@@ -338,7 +342,7 @@ module RV
338
342
  end
339
343
 
340
344
  def next
341
- (-Math.log(@rng.next))**@power / @rate
345
+ (-Math.log(@rng.rand))**@power / @rate
342
346
  end
343
347
  end
344
348
 
@@ -381,9 +385,9 @@ module RV
381
385
  end
382
386
 
383
387
  def next
384
- loop do
385
- r1 = @rng.next
386
- theta = @s * (2.0 * @rng.next - 1.0) / r1
388
+ while true
389
+ r1 = @rng.rand
390
+ theta = @s * (2.0 * @rng.rand - 1.0) / r1
387
391
  next if theta.abs > Math::PI
388
392
  return theta if (0.25 * @kappa * theta * theta < 1.0 - r1) ||
389
393
  (0.5 * @kappa * (Math.cos(theta) - 1.0) >= Math.log(r1))
@@ -410,10 +414,17 @@ module RV
410
414
  @rng = rng
411
415
  end
412
416
 
417
+ def rate=(rate)
418
+ raise 'Rate must be a number.' unless rate.is_a? Numeric
419
+ raise 'Rate must be positive.' if rate <= 0
420
+ @rate = rate
421
+ @threshold = Math.exp(-rate)
422
+ end
423
+
413
424
  def next
414
425
  count = 0
415
426
  product = 1.0
416
- count += 1 until (product *= @rng.next) < @threshold
427
+ count += 1 until (product *= @rng.rand) < @threshold
417
428
  count
418
429
  end
419
430
  end
@@ -432,13 +443,12 @@ module RV
432
443
  def initialize(p: 0.5, rng: U_GENERATOR)
433
444
  raise 'Require 0 < p < 1.' if p <= 0 || p >= 1
434
445
 
435
- @p = p
436
446
  @log_q = Math.log(1 - p)
437
447
  @rng = rng
438
448
  end
439
449
 
440
450
  def next
441
- (Math.log(1.0 - @rng.next) / @log_q).ceil
451
+ (Math.log(1.0 - @rng.rand) / @log_q).ceil
442
452
  end
443
453
  end
444
454
 
@@ -473,8 +483,8 @@ module RV
473
483
 
474
484
  def next
475
485
  result = sum = 0
476
- loop do
477
- sum += Math.log(@rng.next) / (@n - result)
486
+ while true
487
+ sum += Math.log(@rng.rand) / (@n - result)
478
488
  break if sum < @log_q
479
489
  result += 1
480
490
  end
metadata CHANGED
@@ -1,16 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: random_variates
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul J Sanchez
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-02 00:00:00.000000000 Z
12
- dependencies: []
13
- description: Random variate generators implemented as enumerators.
11
+ date: 2023-05-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: xoroshiro
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
27
+ description: Random variate generators implemented with Enumerator.
14
28
  email: pjs@alum.mit.edu
15
29
  executables: []
16
30
  extensions: []
@@ -22,8 +36,11 @@ files:
22
36
  homepage: https://bitbucket.org/paul_j_sanchez/random_variates.git
23
37
  licenses:
24
38
  - MIT
25
- metadata: {}
26
- post_install_message:
39
+ metadata:
40
+ rubygems_mfa_required: 'true'
41
+ homepage_uri: https://bitbucket.org/paul_j_sanchez/random_variates.git
42
+ documentation_uri: https://rubydoc.info/gems/random_variates
43
+ post_install_message:
27
44
  rdoc_options: []
28
45
  require_paths:
29
46
  - lib
@@ -31,15 +48,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
31
48
  requirements:
32
49
  - - ">="
33
50
  - !ruby/object:Gem::Version
34
- version: '2.5'
51
+ version: '3.0'
35
52
  required_rubygems_version: !ruby/object:Gem::Requirement
36
53
  requirements:
37
54
  - - ">="
38
55
  - !ruby/object:Gem::Version
39
56
  version: '0'
40
57
  requirements: []
41
- rubygems_version: 3.1.2
42
- signing_key:
58
+ rubygems_version: 3.4.13
59
+ signing_key:
43
60
  specification_version: 4
44
- summary: Random variate generator classes.
61
+ summary: Random variate generator classes for popular distributions.
45
62
  test_files: []