random_variates 0.4.0 → 0.5.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 +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: []