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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a373b6d772167ba3c442980aa0ebef57fe3f06c686eb52022d7b7f52b46f3da2
4
- data.tar.gz: 48cbe39fa60eeb16323437717816563b4f95c16827251c3b7fa76a3f59931d34
3
+ metadata.gz: 8d221c7c71ecf7cf3f2b329491efdea64b682744d106dfa43aa725be90c26b64
4
+ data.tar.gz: c8dcefee02e7a9c0bafb19ca1186e37acb138d90be8098076224f7ebd6c5dee8
5
5
  SHA512:
6
- metadata.gz: fd33198c89901219af7bdfef38383cc2d3d4f9ce47f43ce27a2b33231d6ae4f9901c78ffd1cd67a6a2a25e863f6dd9d9f2b8efcc6221a663c11efd7cac34e641
7
- data.tar.gz: 2a4618d56f415822b4a67f3b21e87fa19bb28281d88836f0f63a5e14adc3caca4f6b4c64fff62a4b6897ff49e97303e4f5888c95f318981b7bf25da6733f426e
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-2017 Mauro Tortonesi
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
+ [![Gem Version](https://badge.fury.io/rb/erv.svg)](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. ruby-erv was built from code that I extracted out of
11
- several scientific software I wrote for my research projects.
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
@@ -0,0 +1,2 @@
1
+ # Load main file of the framework
2
+ require 'erv'
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 'bundler', '~> 1.14'
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
- raise ArgumentError unless opts[:max_value]
14
- @max = opts[:max_value].to_i
15
- @min = opts[:min_value].try(:to_i) || 0
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
@@ -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].try(:to_f)
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[:location].try(:to_f) || 0.0
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
 
@@ -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/support/try'
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].try(:to_s)
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
- alias_method :sample, :next
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, "First value must be provided!" if first.nil?
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
- alias_method :sample, :next
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
- raise ArgumentError unless opts[:max_value]
12
- max = opts[:max_value].to_f
13
- @min = opts[:min_value].try(:to_f) || 0.0
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
@@ -1,3 +1,3 @@
1
1
  module ERV
2
- VERSION = '0.3.5'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -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
- lambda do
9
- ERV::ConstantDistribution.new
10
- end.must_raise ArgumentError
3
+ expect { ERV::ConstantDistribution.new }.to raise_exception(ArgumentError)
11
4
  end
12
5
 
13
- context 'reference value' do
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.must_equal val
14
+ expect(crv.sample).to be == val
23
15
  end
24
-
25
16
  end
26
-
27
17
  end
@@ -1,81 +1,29 @@
1
- require 'test_helper'
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
- lambda do
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
- context 'a (default) 1-parameter distribution' do
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
- it 'should have the expected mean' do
36
- sample_mean = samples.inject(0.0) {|s,x| s += x } / num_samples.to_f
37
- sample_mean.must_be_within_epsilon zero_to_sixty.mean, 0.05
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
- it 'should have the expected variance' do
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
- end
20
+ with 'a 2-parameter distribution' do
21
+ let(:distribution) { ERV::DiscreteUniformDistribution.new(min_value: 10, max_value: 90) }
61
22
 
62
- context 'moments' do
23
+ let(:num_samples) { 200_000 }
24
+ let(:samples) { num_samples.times.map { distribution.sample } }
25
+ let(:epsilon) { 1E-2 }
63
26
 
64
- let :samples do
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).must_equal 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