erv 0.3.5 → 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.
- checksums.yaml +4 -4
- data/LICENSE +2 -1
- data/README.md +6 -2
- data/config/sus.rb +2 -0
- data/erv.gemspec +2 -5
- data/fixtures/erv/distribution_behaviour.rb +23 -0
- data/lib/erv/beta_distribution.rb +36 -0
- data/lib/erv/discrete_uniform_distribution.rb +5 -7
- data/lib/erv/distribution.rb +3 -1
- data/lib/erv/expnorm_distribution.rb +40 -0
- data/lib/erv/exponential_distribution.rb +1 -2
- data/lib/erv/generalized_pareto_distribution.rb +4 -8
- data/lib/erv/lognorm_distribution.rb +32 -0
- data/lib/erv/mixture_distribution.rb +4 -0
- data/lib/erv/random_variable.rb +12 -14
- data/lib/erv/uniform_distribution.rb +3 -4
- data/lib/erv/version.rb +1 -1
- data/lib/erv/weibull_distribution.rb +23 -0
- data/test/erv/beta_distribution_test.rb +22 -0
- data/test/erv/constant_distribution_test.rb +4 -14
- data/test/erv/discrete_uniform_distribution_test.rb +14 -66
- data/test/erv/distribution_test.rb +2 -10
- data/test/erv/expnorm_distribution_test.rb +27 -0
- data/test/erv/exponential_distribution_test.rb +8 -34
- data/test/erv/gamma_distribution_test.rb +8 -38
- data/test/erv/gaussian_distribution_test.rb +8 -35
- data/test/erv/generalized_pareto_distribution_test.rb +21 -70
- data/test/erv/geometric_distribution_test.rb +8 -35
- data/test/erv/log_normal_distribution_test.rb +17 -0
- data/test/erv/mixture_distribution_test.rb +36 -64
- data/test/erv/random_variable_test.rb +3 -9
- metadata +20 -73
- data/Rakefile +0 -11
- data/TODO +0 -2
- data/lib/erv/support/try.rb +0 -15
- data/test/test_helper.rb +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d221c7c71ecf7cf3f2b329491efdea64b682744d106dfa43aa725be90c26b64
|
|
4
|
+
data.tar.gz: c8dcefee02e7a9c0bafb19ca1186e37acb138d90be8098076224f7ebd6c5dee8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cd8301d624202ef0d4230d581dcfd4c0e046974c27af67e25d4e68a4db3f152e1f7f5aaa4af677e6ba58f2afb93f6e744b6505587b5a28280f6c8aecf2b8db24
|
|
7
|
+
data.tar.gz: 7aca0079037734d9ab7efb88ac31fe3e2981067e73646326317dde1fcec90aef2c291f93ba3c0cf7d5f8a1d197e3b01c03a4fbb21916d8b86953e0eb76ab86a1
|
data/LICENSE
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2014-
|
|
3
|
+
Copyright (c) 2014-2025 Mauro Tortonesi
|
|
4
|
+
Copyright (c) 2023-2025 Filippo Poltronieri
|
|
4
5
|
|
|
5
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
7
|
this software and associated documentation files (the "Software"), to deal in
|
data/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ruby-erv
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/erv)
|
|
4
|
+
|
|
3
5
|
Easy/elegant random variable library
|
|
4
6
|
|
|
5
7
|
|
|
@@ -7,8 +9,10 @@ Easy/elegant random variable library
|
|
|
7
9
|
|
|
8
10
|
ruby-erv is a library that enables to create objects representing random
|
|
9
11
|
variables with a given probability distribution (gaussian, uniform, etc.) and
|
|
10
|
-
to sample from them.
|
|
11
|
-
several scientific software
|
|
12
|
+
to sample from them. It originated as a bunch of RNG-related code extracted out
|
|
13
|
+
of several scientific software tools that we wrote for our research projects at
|
|
14
|
+
the University of Ferrara, Italy, that we later reshaped in a more coherent and
|
|
15
|
+
systematic fashion.
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
## Installation
|
data/config/sus.rb
ADDED
data/erv.gemspec
CHANGED
|
@@ -12,15 +12,12 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.summary = %q{Easy/elegant Ruby library providing support for random variable generation}
|
|
13
13
|
spec.homepage = 'https://github.com/mtortonesi/ruby-erv'
|
|
14
14
|
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 2.3.0'
|
|
15
16
|
|
|
16
17
|
spec.files = `git ls-files`.split($/).reject{|x| x == '.gitignore' or x == '.projections.json' }
|
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
19
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
20
|
spec.require_paths = ['lib']
|
|
20
21
|
|
|
21
|
-
spec.add_development_dependency '
|
|
22
|
-
spec.add_development_dependency 'rake', '~> 12.0'
|
|
23
|
-
spec.add_development_dependency 'minitest', '~> 5.10'
|
|
24
|
-
spec.add_development_dependency 'minitest-reporters', '~> 1.1'
|
|
25
|
-
spec.add_development_dependency 'minitest-spec-context', '~> 0.0.3'
|
|
22
|
+
spec.add_development_dependency 'sus', '~> 0.35.0'
|
|
26
23
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module ERV
|
|
2
|
+
module DistributionBehavior
|
|
3
|
+
def self.included(base)
|
|
4
|
+
base.it 'should allow sampling' do
|
|
5
|
+
expect{ distribution.sample }.not.to raise_exception(ArgumentError).and(! be_nil)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
base.it 'should have the expected mean and variance' do
|
|
9
|
+
# sample_mean = samples.inject(0.0) { |s, x| s + x } / num_samples.to_f
|
|
10
|
+
sample_mean = samples.sum / num_samples.to_f
|
|
11
|
+
distribution_mean = distribution.mean
|
|
12
|
+
expect(sample_mean).to be_within(epsilon * distribution_mean).of(distribution_mean)
|
|
13
|
+
|
|
14
|
+
sample_variance = samples.map{ |x| (x - sample_mean) **2 }.sum / (num_samples - 1).to_f
|
|
15
|
+
distribution_variance = distribution.variance
|
|
16
|
+
expect(sample_variance).to be_within(epsilon * distribution_variance).of(distribution_variance)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
# def samples { num_samples.times.map { distribution.sample } }
|
|
21
|
+
# def epsilon { 5E-3 }
|
|
22
|
+
# def num_samples { 1_000_000 }
|
|
23
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'erv/distribution'
|
|
2
|
+
|
|
3
|
+
module ERV
|
|
4
|
+
class BetaDistribution < Distribution
|
|
5
|
+
def initialize(opts = {})
|
|
6
|
+
raise ArgumentError, 'alpha is required' unless opts[:alpha]
|
|
7
|
+
raise ArgumentError, 'beta is required' unless opts[:beta]
|
|
8
|
+
raise ArgumentError, 'alpha must be greater than zero' unless opts[:alpha].to_f > 0.0
|
|
9
|
+
raise ArgumentError, 'beta must be greater than zero' unless opts[:beta].to_f > 0.0
|
|
10
|
+
|
|
11
|
+
# Parameters alpha and beta must be greater than zero
|
|
12
|
+
@alpha = opts[:alpha]
|
|
13
|
+
@beta = opts[:beta]
|
|
14
|
+
|
|
15
|
+
@gamma_alpha = GammaDistribution.new(shape: @alpha, scale: 1.0)
|
|
16
|
+
@gamma_beta = GammaDistribution.new(shape: @beta, scale: 1.0)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The handbook describes many method to sample from a Beta distribution.
|
|
20
|
+
# We could use Gamma RVs or event sampling u ~ N(0,1) and then X = U ** (1 / alpha)
|
|
21
|
+
def sample
|
|
22
|
+
# let's use gamma random variables
|
|
23
|
+
x = @gamma_alpha.sample
|
|
24
|
+
y = @gamma_beta.sample
|
|
25
|
+
x / (x + y)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mean
|
|
29
|
+
@alpha.to_f / (@alpha + @beta)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def variance
|
|
33
|
+
(@alpha.to_f * @beta) / (((@alpha + @beta)**2) * (@alpha + @beta + 1))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require 'erv/distribution'
|
|
2
|
-
require 'erv/support/try'
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
module ERV
|
|
@@ -10,18 +9,17 @@ module ERV
|
|
|
10
9
|
def initialize(opts={})
|
|
11
10
|
super(opts)
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
@max
|
|
15
|
-
@min = opts
|
|
12
|
+
@max = opts[:max_value]&.to_i
|
|
13
|
+
raise ArgumentError unless @max
|
|
14
|
+
@min = opts.fetch(:min_value, 0).to_i
|
|
16
15
|
@mean = (@max + @min) / 2.0
|
|
17
16
|
# See https://en.wikipedia.org/wiki/Discrete_uniform_distribution
|
|
18
|
-
@variance = ((@max - @min + 1) ** 2 - 1) / 12.0
|
|
17
|
+
@variance = ((@max - @min + 1).to_f ** 2 - 1.0) / 12.0
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def sample
|
|
22
|
-
@min + @rng.rand(@max - @min)
|
|
21
|
+
(@min.to_f + @rng.rand * (@max - @min + 1).to_f).floor
|
|
23
22
|
end
|
|
24
|
-
|
|
25
23
|
end
|
|
26
24
|
|
|
27
25
|
end
|
data/lib/erv/distribution.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module ERV
|
|
2
2
|
|
|
3
3
|
class Distribution
|
|
4
|
+
|
|
5
|
+
DEFAULT_SEED = 12345
|
|
4
6
|
def initialize(opts={})
|
|
5
7
|
# use provided RNG or create a new one
|
|
6
8
|
if opts[:rng]
|
|
@@ -8,7 +10,7 @@ module ERV
|
|
|
8
10
|
elsif opts[:seed]
|
|
9
11
|
@rng = Random.new(opts[:seed])
|
|
10
12
|
else
|
|
11
|
-
@rng = Random.new
|
|
13
|
+
@rng = Random.new(DEFAULT_SEED)
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'erv/distribution'
|
|
2
|
+
|
|
3
|
+
module ERV
|
|
4
|
+
class ExpNormDistribution < Distribution
|
|
5
|
+
def initialize(opts = {})
|
|
6
|
+
super(opts)
|
|
7
|
+
|
|
8
|
+
raise ArgumentError, 'mu is required' unless opts[:mu]
|
|
9
|
+
raise ArgumentError, 'sigma is required' unless opts[:sigma]
|
|
10
|
+
raise ArgumentError, 'lambda is required' unless opts[:lambda]
|
|
11
|
+
raise ArgumentError, 'sigma must be > 0' if opts[:sigma].to_f <= 0
|
|
12
|
+
raise ArgumentError, 'lambda must be > 0' if opts[:lambda].to_f <= 0
|
|
13
|
+
|
|
14
|
+
@mu = opts[:mu].to_f
|
|
15
|
+
@sigma = opts[:sigma].to_f
|
|
16
|
+
@lambda = opts[:lambda].to_f # rate = 1/mean_exponential
|
|
17
|
+
|
|
18
|
+
@gaussian = GaussianDistribution.new(mean: @mu, sd: @sigma)
|
|
19
|
+
@exponential = ExponentialDistribution.new(rate: @lambda)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def sample
|
|
23
|
+
@gaussian.sample + @exponential.sample
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Theoretical Mean E[X]: μ + 1/λ
|
|
27
|
+
def mean
|
|
28
|
+
@mu + 1.0 / @lambda
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Theoretical Variance: σ² + 1/λ²
|
|
32
|
+
def variance
|
|
33
|
+
@sigma**2 + 1.0 / (@lambda**2)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def k
|
|
37
|
+
@lambda / @sigma
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require 'erv/distribution'
|
|
2
|
-
require 'erv/support/try'
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
module ERV
|
|
@@ -10,7 +9,7 @@ module ERV
|
|
|
10
9
|
def initialize(opts={})
|
|
11
10
|
super(opts)
|
|
12
11
|
|
|
13
|
-
@rate = opts[:rate]
|
|
12
|
+
@rate = opts[:rate]&.to_f
|
|
14
13
|
raise ArgumentError unless @rate and @rate > 0.0
|
|
15
14
|
|
|
16
15
|
@mean = 1 / @rate
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
require 'erv/distribution'
|
|
2
|
-
require 'erv/support/try'
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
module ERV
|
|
6
|
-
|
|
7
5
|
# See https://en.wikipedia.org/wiki/Generalized_Pareto_distribution
|
|
8
6
|
class GpdDistribution < Distribution
|
|
9
7
|
attr_accessor :mean, :variance
|
|
@@ -14,30 +12,28 @@ module ERV
|
|
|
14
12
|
raise ArgumentError unless opts[:scale] and opts[:shape]
|
|
15
13
|
@scale = opts[:scale].to_f
|
|
16
14
|
@shape = opts[:shape].to_f
|
|
17
|
-
@location = opts
|
|
15
|
+
@location = opts.fetch(:location, 0.0).to_f
|
|
18
16
|
|
|
19
17
|
@mean = if @shape < 1.0
|
|
20
18
|
@location + @scale / (1.0 - @shape)
|
|
21
19
|
else
|
|
22
|
-
Infinity
|
|
20
|
+
Float::Infinity
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
@variance = if @shape < 0.5
|
|
26
24
|
(@scale ** 2.0) / (((1.0 - @shape) ** 2) * (1.0 - 2 * @shape))
|
|
27
25
|
else
|
|
28
|
-
Infinity
|
|
26
|
+
Float::Infinity
|
|
29
27
|
end
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
def sample
|
|
33
31
|
u = 1.0 - @rng.rand
|
|
34
|
-
if @shape == 0.0
|
|
32
|
+
if @shape.abs < 1E-15 # numerically stable check if @shape == 0.0
|
|
35
33
|
@location - @scale * Math::log(u)
|
|
36
34
|
else
|
|
37
35
|
@location + (@scale * ((u ** (- @shape)) - 1.0) / @shape)
|
|
38
36
|
end
|
|
39
37
|
end
|
|
40
|
-
|
|
41
38
|
end
|
|
42
|
-
|
|
43
39
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'erv/distribution'
|
|
2
|
+
|
|
3
|
+
module ERV
|
|
4
|
+
class LogNormDistribution < Distribution
|
|
5
|
+
def initialize(opts = {})
|
|
6
|
+
super(opts)
|
|
7
|
+
|
|
8
|
+
raise ArgumentError, 'mu is required' unless opts[:mu]
|
|
9
|
+
raise ArgumentError, 'sigma is required' unless opts[:sigma]
|
|
10
|
+
raise ArgumentError, 'sigma must be greater than zero' unless opts[:sigma].to_f > 0.0
|
|
11
|
+
|
|
12
|
+
@mu = opts[:mu].to_f
|
|
13
|
+
@sigma = opts[:sigma].to_f
|
|
14
|
+
|
|
15
|
+
# From Handbook of Monte Carlo Methods [KROESE11], section 4.2.10, pag 121
|
|
16
|
+
@gaussian_dist = GaussianDistribution.new(mean: @mu, sd: @sigma)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def sample
|
|
20
|
+
# just sample from @gaussian_dist and exponentiate the result
|
|
21
|
+
Math.exp(@gaussian_dist.sample) # just sample from @gaussian_dist and exponentiate the res Math.exp(@gaussian_dist.sample)))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def mean
|
|
25
|
+
Math.exp(@mu + 0.5 * @sigma**2)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def variance
|
|
29
|
+
Math.exp(2 * @mu + @sigma**2) * (Math.exp(@sigma**2) - 1)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -10,6 +10,9 @@ require 'erv/uniform_distribution'
|
|
|
10
10
|
module ERV
|
|
11
11
|
|
|
12
12
|
class MixtureDistribution
|
|
13
|
+
|
|
14
|
+
DEFAULT_SEED = 12345
|
|
15
|
+
|
|
13
16
|
def initialize(confs, opts={})
|
|
14
17
|
raise ArgumentError, "Please, provide at least 2 distributions!" if confs.count < 2
|
|
15
18
|
|
|
@@ -42,6 +45,7 @@ module ERV
|
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
seed = opts[:seed]
|
|
48
|
+
seed = DEFAULT_SEED if seed.nil?
|
|
45
49
|
@mixture_sampler = (seed ? Random.new(seed) : Random.new)
|
|
46
50
|
end
|
|
47
51
|
|
data/lib/erv/random_variable.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'forwardable'
|
|
1
2
|
require 'erv/constant_distribution'
|
|
2
3
|
require 'erv/discrete_uniform_distribution'
|
|
3
4
|
require 'erv/exponential_distribution'
|
|
@@ -7,20 +8,21 @@ require 'erv/generalized_pareto_distribution'
|
|
|
7
8
|
require 'erv/geometric_distribution'
|
|
8
9
|
require 'erv/mixture_distribution'
|
|
9
10
|
require 'erv/uniform_distribution'
|
|
10
|
-
require 'erv/
|
|
11
|
+
require 'erv/weibull_distribution'
|
|
12
|
+
require 'erv/lognorm_distribution'
|
|
13
|
+
require 'erv/beta_distribution'
|
|
14
|
+
require 'erv/expnorm_distribution'
|
|
11
15
|
|
|
12
16
|
|
|
13
17
|
module ERV
|
|
14
|
-
|
|
15
18
|
class RandomVariable
|
|
16
|
-
|
|
17
19
|
extend Forwardable
|
|
18
20
|
|
|
19
21
|
def_delegators :@dist, :mean, :variance
|
|
20
22
|
|
|
21
|
-
def initialize(args={})
|
|
23
|
+
def initialize(args = {})
|
|
22
24
|
# get distribution name
|
|
23
|
-
dist_name = args[:distribution]
|
|
25
|
+
dist_name = args[:distribution]&.to_s
|
|
24
26
|
|
|
25
27
|
# get class name that corresponds to the requested distribution
|
|
26
28
|
klass_name = dist_name.split('_').push('distribution').map(&:capitalize).join
|
|
@@ -33,17 +35,15 @@ module ERV
|
|
|
33
35
|
@dist.sample
|
|
34
36
|
end
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
alias sample next
|
|
38
39
|
end
|
|
39
40
|
|
|
40
|
-
|
|
41
41
|
class SequentialRandomVariable
|
|
42
|
-
|
|
43
|
-
def initialize(opts={})
|
|
42
|
+
def initialize(opts = {})
|
|
44
43
|
args = opts.dup
|
|
45
44
|
first = args.delete(:first_value)
|
|
46
|
-
raise ArgumentError,
|
|
45
|
+
raise ArgumentError, 'First value must be provided!' if first.nil?
|
|
46
|
+
|
|
47
47
|
@most_recent = first.to_f
|
|
48
48
|
@var = RandomVariable.new(args)
|
|
49
49
|
end
|
|
@@ -52,8 +52,6 @@ module ERV
|
|
|
52
52
|
@most_recent += @var.next
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
alias sample next
|
|
57
56
|
end
|
|
58
|
-
|
|
59
57
|
end
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
require 'erv/distribution'
|
|
2
|
-
require 'erv/support/try'
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
module ERV
|
|
@@ -8,9 +7,9 @@ module ERV
|
|
|
8
7
|
def initialize(opts={})
|
|
9
8
|
super(opts)
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@min = opts
|
|
10
|
+
max = opts[:max_value]&.to_f
|
|
11
|
+
raise ArgumentError unless max
|
|
12
|
+
@min = opts.fetch(:min_value, 0.0).to_f
|
|
14
13
|
@range = max - @min
|
|
15
14
|
end
|
|
16
15
|
|
data/lib/erv/version.rb
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'erv/distribution'
|
|
2
|
+
|
|
3
|
+
module ERV
|
|
4
|
+
class WeibullDistribution < Distribution
|
|
5
|
+
def initialize(opts = {})
|
|
6
|
+
super(opts)
|
|
7
|
+
|
|
8
|
+
raise ArgumentError unless opts[:scale] and opts[:shape]
|
|
9
|
+
|
|
10
|
+
@scale = opts[:scale].to_f
|
|
11
|
+
@shape = opts[:shape].to_f
|
|
12
|
+
|
|
13
|
+
raise ArgumentError, 'scale parameter must be greater than zero!' unless @scale > 0.0
|
|
14
|
+
raise ArgumentError, 'shape parameter must be greater than zero!' unless @shape > 0.0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# For more details, see [KROESE11], section 4.2.18, algorithm 4.66 and
|
|
18
|
+
def sample
|
|
19
|
+
u = @rng.rand
|
|
20
|
+
@scale * (-Math.log(1 - u))**(1.0 / @shape)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'erv/distribution_behaviour'
|
|
2
|
+
|
|
3
|
+
describe ERV::BetaDistribution do
|
|
4
|
+
with 'initialization' do
|
|
5
|
+
it 'should require the alpha and beta parameters' do
|
|
6
|
+
expect{ ERV::BetaDistribution.new(alpha: 0.0) }.to raise_exception(ArgumentError)
|
|
7
|
+
expect{ ERV::BetaDistribution.new(beta: 0.0) }.to raise_exception(ArgumentError)
|
|
8
|
+
expect{ ERV::BetaDistribution.new(alpha: -1.0, beta: 0.0) }.to raise_exception(ArgumentError)
|
|
9
|
+
expect{ ERV::BetaDistribution.new(alpha: 0.0, beta: -1.0) }.to raise_exception(ArgumentError)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
with 'sampling' do
|
|
14
|
+
let(:distribution) { ERV::BetaDistribution.new(alpha: 2.0, beta: 5.0) }
|
|
15
|
+
|
|
16
|
+
let(:num_samples) { 200_000 }
|
|
17
|
+
let(:samples) { num_samples.times.map { distribution.sample } }
|
|
18
|
+
let(:epsilon) { 1E-2 }
|
|
19
|
+
|
|
20
|
+
include ERV::DistributionBehavior
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -1,27 +1,17 @@
|
|
|
1
|
-
require 'test_helper'
|
|
2
|
-
|
|
3
|
-
require 'erv/constant_distribution'
|
|
4
|
-
|
|
5
1
|
describe ERV::ConstantDistribution do
|
|
6
|
-
|
|
7
2
|
it 'should require a reference value' do
|
|
8
|
-
|
|
9
|
-
ERV::ConstantDistribution.new
|
|
10
|
-
end.must_raise ArgumentError
|
|
3
|
+
expect { ERV::ConstantDistribution.new }.to raise_exception(ArgumentError)
|
|
11
4
|
end
|
|
12
5
|
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
with 'reference value' do
|
|
15
7
|
it 'should be accepted at initialization time' do
|
|
16
|
-
ERV::ConstantDistribution.new(value: 10)
|
|
8
|
+
expect { ERV::ConstantDistribution.new(value: 10) }.not.to raise_exception(ArgumentError)
|
|
17
9
|
end
|
|
18
10
|
|
|
19
11
|
it 'should match the value returned by sampling' do
|
|
20
12
|
val = rand(100)
|
|
21
13
|
crv = ERV::ConstantDistribution.new(value: val)
|
|
22
|
-
crv.sample.
|
|
14
|
+
expect(crv.sample).to be == val
|
|
23
15
|
end
|
|
24
|
-
|
|
25
16
|
end
|
|
26
|
-
|
|
27
17
|
end
|
|
@@ -1,81 +1,29 @@
|
|
|
1
|
-
require '
|
|
2
|
-
|
|
3
|
-
require 'erv/discrete_uniform_distribution'
|
|
1
|
+
require 'erv/distribution_behaviour'
|
|
4
2
|
|
|
5
3
|
describe ERV::DiscreteUniformDistribution do
|
|
6
|
-
|
|
7
|
-
let (:num_samples) { 10000 }
|
|
8
|
-
|
|
9
4
|
it 'should require at least a maximum parameter' do
|
|
10
|
-
|
|
11
|
-
ERV::DiscreteUniformDistribution.new()
|
|
12
|
-
end.must_raise ArgumentError
|
|
5
|
+
expect{ ERV::DiscreteUniformDistribution.new }.to raise_exception(ArgumentError)
|
|
13
6
|
end
|
|
14
7
|
|
|
15
8
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let :zero_to_sixty do
|
|
19
|
-
ERV::DiscreteUniformDistribution.new(max_value: 60)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
context 'sampling' do
|
|
23
|
-
|
|
24
|
-
it 'should allow sampling' do
|
|
25
|
-
zero_to_sixty.sample
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
context 'moments' do
|
|
31
|
-
let :samples do
|
|
32
|
-
0.upto(num_samples).collect { zero_to_sixty.sample }
|
|
33
|
-
end
|
|
9
|
+
with 'a (default) 1-parameter distribution' do
|
|
10
|
+
let(:distribution) { ERV::DiscreteUniformDistribution.new(max_value: 60) }
|
|
34
11
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
end
|
|
12
|
+
let(:num_samples) { 300_000 }
|
|
13
|
+
let(:samples) { num_samples.times.map { distribution.sample } }
|
|
14
|
+
let(:epsilon) { 1E-2 }
|
|
39
15
|
|
|
40
|
-
|
|
41
|
-
sample_variance = samples.inject(0.0) {|s,x| s += (x - zero_to_sixty.mean) ** 2 } / num_samples.to_f
|
|
42
|
-
sample_variance.must_be_within_epsilon zero_to_sixty.variance, 0.05
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
end
|
|
16
|
+
include ERV::DistributionBehavior
|
|
46
17
|
end
|
|
47
18
|
|
|
48
|
-
context 'a 2-parameter distribution' do
|
|
49
|
-
|
|
50
|
-
let :ten_to_ninety do
|
|
51
|
-
ERV::DiscreteUniformDistribution.new(min_value: 10, max_value: 90)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
context 'sampling' do
|
|
55
|
-
|
|
56
|
-
it 'should allow sampling' do
|
|
57
|
-
ten_to_ninety.sample
|
|
58
|
-
end
|
|
59
19
|
|
|
60
|
-
|
|
20
|
+
with 'a 2-parameter distribution' do
|
|
21
|
+
let(:distribution) { ERV::DiscreteUniformDistribution.new(min_value: 10, max_value: 90) }
|
|
61
22
|
|
|
62
|
-
|
|
23
|
+
let(:num_samples) { 200_000 }
|
|
24
|
+
let(:samples) { num_samples.times.map { distribution.sample } }
|
|
25
|
+
let(:epsilon) { 1E-2 }
|
|
63
26
|
|
|
64
|
-
|
|
65
|
-
0.upto(num_samples).collect { ten_to_ninety.sample }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
it 'should have the expected mean' do
|
|
69
|
-
sample_mean = samples.inject(0.0) {|s,x| s += x } / num_samples.to_f
|
|
70
|
-
sample_mean.must_be_within_epsilon ten_to_ninety.mean, 0.05
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
it 'should have the expected variance' do
|
|
74
|
-
sample_variance = samples.inject(0.0) {|s,x| s += (x - ten_to_ninety.mean) ** 2 } / num_samples.to_f
|
|
75
|
-
sample_variance.must_be_within_epsilon ten_to_ninety.variance, 0.05
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
end
|
|
27
|
+
include ERV::DistributionBehavior
|
|
79
28
|
end
|
|
80
|
-
|
|
81
29
|
end
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
require 'test_helper'
|
|
2
|
-
|
|
3
|
-
require 'erv/distribution'
|
|
4
|
-
|
|
5
1
|
describe ERV::Distribution do
|
|
6
|
-
|
|
7
|
-
context 'when explicitly given an RNG' do
|
|
8
|
-
|
|
2
|
+
with 'when explicitly given an RNG' do
|
|
9
3
|
it 'should use the given RNG' do
|
|
10
4
|
rng = Random.new
|
|
11
5
|
d = ERV::Distribution.new(rng: rng)
|
|
12
|
-
d.instance_variable_get(:@rng).
|
|
6
|
+
expect(d.instance_variable_get(:@rng)).to be == rng
|
|
13
7
|
end
|
|
14
|
-
|
|
15
8
|
end
|
|
16
|
-
|
|
17
9
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'erv/distribution_behaviour'
|
|
2
|
+
|
|
3
|
+
describe ERV::ExpNormDistribution do
|
|
4
|
+
let(:num_samples) { 100_000 }
|
|
5
|
+
|
|
6
|
+
with 'initialization' do
|
|
7
|
+
it 'requires mu, sigma and lambda' do
|
|
8
|
+
expect{ ERV::ExpNormDistribution.new }.to raise_exception(ArgumentError)
|
|
9
|
+
expect{ ERV::ExpNormDistribution.new(mu: 0, sigma: 1) }.to raise_exception(ArgumentError)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'raises on invalid parameters' do
|
|
13
|
+
expect{ ERV::ExpNormDistribution.new(mu: 0, sigma: 0, lambda: 1) }.to raise_exception(ArgumentError)
|
|
14
|
+
expect{ ERV::ExpNormDistribution.new(mu: 0, sigma: 1, lambda: -1) }.to raise_exception(ArgumentError)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
with 'sampling' do
|
|
19
|
+
let(:distribution) { ERV::ExpNormDistribution.new(mu: 10.0, sigma: 5.0, lambda: 0.2) }
|
|
20
|
+
|
|
21
|
+
let(:num_samples) { 200_000 }
|
|
22
|
+
let(:samples) { num_samples.times.map { distribution.sample } }
|
|
23
|
+
let(:epsilon) { 1E-2 }
|
|
24
|
+
|
|
25
|
+
include ERV::DistributionBehavior
|
|
26
|
+
end
|
|
27
|
+
end
|