erv 0.3.1 → 0.3.2

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
  SHA1:
3
- metadata.gz: a94686dfda6162e93eb5b19063ef7fd28842d434
4
- data.tar.gz: 22b0835f5343f735f5e2f970ce32acc7dac56206
3
+ metadata.gz: f73f2bc514e8d23844ca2e1652bd0801f661679a
4
+ data.tar.gz: a772550f0c889fcbf411038c830add3fa144876c
5
5
  SHA512:
6
- metadata.gz: 61a34d96497ca307867a72d410d1274562808eba25f5fa19ff4c53533f248537cf17df7a0354c62ae10836e01f03edf3fb6eedd844b005ddd7e1fdb8625a2ff5
7
- data.tar.gz: b7294f4b04076fff19998ed58c20ad77eb69ae139a80d1821403eb1d60e61fb9aba460fa281c83fb140065818f20a31dcfe15e3bd2d42af93b5a9c0d331cb982
6
+ metadata.gz: 90c953b59a635bb02e4ee5e079147804b14a7d54033a0230f6d749bd9c3d67750ac6b3d722dfbb1d4a453c1158d1b51d311c348d0e5a3f67bcf3330715ae7d27
7
+ data.tar.gz: 0420a2bc62b45cf9453070a57b24dc7b7731de3fac657df3500f7404e5cf78de4d621d3c1043e00d9a6e6c1b8cdfc35fc0be7d7bbc207eb65ac4bb8b52188d97
@@ -1,8 +1,3 @@
1
- if RUBY_PLATFORM == 'java'
2
- require 'java'
3
- java_import org.apache.commons.math3.distribution.UniformIntegerDistribution
4
- end
5
-
6
1
  require 'erv/distribution'
7
2
  require 'erv/support/try'
8
3
 
@@ -10,23 +5,23 @@ require 'erv/support/try'
10
5
  module ERV
11
6
 
12
7
  class DiscreteUniformDistribution < Distribution
8
+ attr_reader :mean, :variance
9
+
13
10
  def initialize(opts)
14
11
  super(opts)
15
12
 
16
13
  raise ArgumentError unless opts[:max_value]
17
- max = opts[:max_value].to_i
18
- min = opts[:min_value].try(:to_i) || 0
19
-
20
- if RUBY_PLATFORM == 'java'
21
- # create distribution
22
- d = UniformIntegerDistribution.new(@rng, min, max)
23
- # setup sampling function
24
- @func = Proc.new { d.sample }
25
- else
26
- # setup sampling function
27
- @func = Proc.new { min + @rng.uniform_int(max-min) }
28
- end
14
+ @max = opts[:max_value].to_i
15
+ @min = opts[:min_value].try(:to_i) || 0
16
+ @mean = (@max + @min) / 2.0
17
+ # See https://en.wikipedia.org/wiki/Discrete_uniform_distribution
18
+ @variance = ((@max - @min + 1) ** 2 - 1) / 12.0
29
19
  end
20
+
21
+ def sample
22
+ @min + @rng.rand(@max - @min)
23
+ end
24
+
30
25
  end
31
26
 
32
27
  end
@@ -21,7 +21,7 @@ module ERV
21
21
  # starting from a random variable X ~ U(0,1), which is provided by the
22
22
  # RNG, we can obtain a random variable Y ~ Exp(\lambda), with mean = 1 /
23
23
  # \lambda, through the transformation: Y = - (1 / \lambda) ln X. see
24
- # [GROESE11], section 4.2.3.
24
+ # [KROESE11], section 4.2.3.
25
25
  - @mean * Math.log(@rng.rand)
26
26
  end
27
27
  end
@@ -1,32 +1,65 @@
1
- if RUBY_PLATFORM == 'java'
2
- require 'java'
3
- JGammaDistribution = org.apache.commons.math3.distribution.GammaDistribution
4
- end
5
-
6
1
  require 'erv/distribution'
7
2
 
8
3
 
9
4
  module ERV
10
-
5
+ # We refer to the formulation of the Gamma distribution adopted by [KROESE11]:
6
+ #
7
+ # f(x) = \frac{\lambda^{\alpha} x^{\alpha - 1} e^{-\lambda x}}{\Gamma(\alpha)}
8
+ #
9
+ # with \alpha > 0 being the shape parameter, \lambda > 0 being the scale
10
+ # parameter, and x > 0.
11
+ #
12
+ # (Note that, unlike [KROESE11] Wikipedia refers to the \beta parameter as the "rate".)
11
13
  class GammaDistribution < Distribution
14
+ attr_accessor :mean, :variance
15
+
12
16
  def initialize(opts)
13
17
  super(opts)
14
18
 
15
- raise ArgumentError unless opts[:rate] and opts[:shape]
16
- scale = 1 / opts[:rate].to_f
17
- shape = opts[:shape].to_f
18
-
19
- if RUBY_PLATFORM == 'java'
20
- # create distribution
21
- d = JGammaDistribution.new(@rng, shape, scale,
22
- JGammaDistribution::DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
23
- # setup sampling function
24
- @func = Proc.new { d.sample }
25
- else
26
- # setup sampling function
27
- @func = Proc.new { @rng.gamma(shape, scale) }
28
- end
19
+ raise ArgumentError unless opts[:scale] and opts[:shape]
20
+ @scale = opts[:scale].to_f
21
+ @shape = opts[:shape].to_f
22
+
23
+ raise ArgumentError, "scale parameter must be greater than zero!" unless @scale > 0.0
24
+ raise ArgumentError, "shape parameter must be greater than zero!" unless @shape > 0.0
25
+
26
+ @mean = @shape / @scale
27
+ @variance = @shape / (@scale ** 2)
29
28
  end
29
+
30
+ # We use Marsaglia and Tsang's sampling algorithm
31
+ #
32
+ # For more details, see [KROESE11], section 4.2.6, algorithm 4.33 and
33
+ # http://www.hongliangjie.com/2012/12/19/how-to-generate-gamma-random-variables/
34
+ def sample
35
+ gamrand(@shape, @scale)
36
+ end
37
+
38
+ private
39
+
40
+ def gamrand(shape, scale)
41
+ if shape >= 1.0
42
+ d = shape - 1.0 / 3
43
+ c = 1.0 / Math::sqrt(9 * d)
44
+ ok = false
45
+ until ok do
46
+ # Use box-muller algorithm (see [KROESE11], section 4.2.11, algorithm
47
+ # 4.47) to obtain z ~ N(0,1).
48
+ u_1 = @rng.rand
49
+ u_2 = @rng.rand
50
+ z = Math::sqrt(-2.0 * Math::log(u_1)) * Math::cos(2.0 * Math::PI * u_2)
51
+ if z > -1.0 / c
52
+ v = (1.0 + c * z) ** 3
53
+ u = @rng.rand
54
+ ok = Math::log(u) <= (0.5 * (z**2) + d - d * v + d * Math::log(v))
55
+ end
56
+ end
57
+ d * v / scale;
58
+ else # 0 < @shape < 1
59
+ gamrand(shape + 1.0, scale) * (@rng.rand ** (1 / shape))
60
+ end
61
+ end
62
+
30
63
  end
31
64
 
32
65
  end
@@ -13,14 +13,14 @@ module ERV
13
13
  end
14
14
 
15
15
  def sample
16
- # use box-muller algorithm (see [GROESE11], section 4.2.11, algorithm
16
+ # Use Box-Muller algorithm (see [KROESE11], section 4.2.11, algorithm
17
17
  # 4.47) to obtain x ~ N(0,1).
18
18
  u_1 = @rng.rand
19
19
  u_2 = @rng.rand
20
20
  x = Math.sqrt(-2.0 * Math.log(u_1)) * Math.cos(2.0 * Math::PI * u_2)
21
21
 
22
- # use location-scale transformation to obtain a N(\mu, \sigma^2)
23
- # distribution. see [GROESE11], section 3.1.2.2.
22
+ # Use location-scale transformation to obtain a N(\mu, \sigma^2)
23
+ # distribution. See [KROESE11], section 3.1.2.2.
24
24
  @mu + @sigma * x
25
25
  end
26
26
 
@@ -1,42 +1,43 @@
1
- if RUBY_PLATFORM == 'java'
2
- require 'java'
3
- java_import org.apache.commons.math3.distribution.UniformRealDistribution
4
- end
5
-
6
1
  require 'erv/distribution'
7
2
  require 'erv/support/try'
8
3
 
9
4
 
10
5
  module ERV
11
6
 
7
+ # See https://en.wikipedia.org/wiki/Generalized_Pareto_distribution
12
8
  class GpdDistribution < Distribution
9
+ attr_accessor :mean, :variance
10
+
13
11
  def initialize(opts)
14
12
  super(opts)
15
13
 
16
14
  raise ArgumentError unless opts[:scale] and opts[:shape]
17
- scale = opts[:scale].to_f
18
- shape = opts[:shape].to_f
19
- location = opts[:location].try(:to_f) || 0.0
20
-
21
- if RUBY_PLATFORM == 'java'
22
- # create uniform distribution
23
- d = UniformRealDistribution.new(@rng, 0.0, 1.0,
24
- UniformRealDistribution::DEFAULT_INVERSE_ABSOLUTE_ACCURACY)
25
- # setup sampling function
26
- @func = Proc.new {
27
- # this algorithm was taken from wikipedia
28
- u = 1.0 - d.sample
29
- location + (scale * ((u ** (-shape)) - 1.0) / shape)
30
- }
15
+ @scale = opts[:scale].to_f
16
+ @shape = opts[:shape].to_f
17
+ @location = opts[:location].try(:to_f) || 0.0
18
+
19
+ @mean = if @shape < 1.0
20
+ @location + @scale / (1.0 - @shape)
21
+ else
22
+ Infinity
23
+ end
24
+
25
+ @variance = if @shape < 0.5
26
+ (@scale ** 2.0) / (((1.0 - @shape) ** 2) * (1.0 - 2 * @shape))
27
+ else
28
+ Infinity
29
+ end
30
+ end
31
+
32
+ def sample
33
+ u = 1.0 - @rng.rand
34
+ if @shape == 0.0
35
+ @location - @scale * Math::log(u)
31
36
  else
32
- # setup sampling function
33
- @func = Proc.new {
34
- # this algorithm was taken from wikipedia
35
- u = 1.0 - @rng.uniform
36
- location + (scale * ((u ** (-shape)) - 1.0) / shape)
37
- }
37
+ @location + (@scale * ((u ** (- @shape)) - 1.0) / @shape)
38
38
  end
39
39
  end
40
+
40
41
  end
41
42
 
42
43
  end
@@ -1,32 +1,33 @@
1
- if RUBY_PLATFORM == 'java'
2
- require 'java'
3
- JGeometricDistribution = org.apache.commons.math3.distribution.GeometricDistribution
4
- end
1
+ require 'erv/distribution'
5
2
 
6
3
 
7
4
  module ERV
8
5
 
6
+ # We adopt the following version of the geometric distribution:
7
+ #
8
+ # Pr(X = k) = (1 - p) ^ {k-1} * p
9
+ #
10
+ # See: https://en.wikipedia.org/wiki/Geometric_distribution
9
11
  class GeometricDistribution < Distribution
12
+ attr_accessor :mean, :variance
13
+
10
14
  def initialize(opts)
11
15
  super(opts)
12
16
 
13
17
  raise ArgumentError unless opts[:probability_of_success]
14
- p = opts[:probability_of_success].to_f
15
-
16
- if RUBY_PLATFORM == 'java'
17
- # create distribution
18
- d = JGeometricDistribution.new(@rng, p)
19
- # setup sampling function
20
- @func = Proc.new { d.sample }
21
- else
22
- # setup sampling function
23
- #
24
- # WARNING: I HAVEN'T TRIED THIS CODE!!!
25
- # Note that GSL implements the shifted version of the geometric
26
- # distribution, so we might need to change the result (removing 1?)
27
- @func = Proc.new { @rng.geometric(p) }
28
- end
18
+ @p = opts[:probability_of_success].to_f
19
+
20
+ @mean = 1.0 / @p
21
+ @variance = (1.0 - @p) / (@p ** 2)
22
+
23
+ @ln_1_minus_p = Math::log(1 - @p)
29
24
  end
25
+
26
+ def sample
27
+ u = @rng.rand
28
+ (Math::log(u) / @ln_1_minus_p).ceil
29
+ end
30
+
30
31
  end
31
32
 
32
33
  end
@@ -17,7 +17,7 @@ module ERV
17
17
  def sample
18
18
  # starting from a random variable X ~ U(0,1), which is provided by the
19
19
  # RNG, we can obtain a random variable Y ~ U(a,b) through location-scale
20
- # transformation: Y = a + (b-a) X. see [GROESE11], section 3.1.2.2.
20
+ # transformation: Y = a + (b-a) X. see [KROESE11], section 3.1.2.2.
21
21
  @min + @range * @rng.rand
22
22
  end
23
23
  end
data/lib/erv/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ERV
2
- VERSION = '0.3.1'
2
+ VERSION = '0.3.2'
3
3
  end
@@ -0,0 +1,81 @@
1
+ require 'test_helper'
2
+
3
+ require 'erv/discrete_uniform_distribution'
4
+
5
+ describe ERV::DiscreteUniformDistribution do
6
+
7
+ NUM_SAMPLES = 10000
8
+
9
+ it 'should require at least a maximum parameter' do
10
+ lambda do
11
+ ERV::DiscreteUniformDistribution.new()
12
+ end.must_raise ArgumentError
13
+ end
14
+
15
+
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
34
+
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
39
+
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
46
+ end
47
+
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
+
60
+ end
61
+
62
+ context 'moments' do
63
+
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
79
+ end
80
+
81
+ end
@@ -0,0 +1,47 @@
1
+ require 'test_helper'
2
+
3
+ require 'erv/gamma_distribution'
4
+
5
+ describe ERV::GammaDistribution do
6
+
7
+ NUM_SAMPLES = 20000
8
+
9
+ it 'should require the scale and shape parameters' do
10
+ lambda do
11
+ ERV::GammaDistribution.new()
12
+ end.must_raise ArgumentError
13
+ end
14
+
15
+ context 'with a shape greater than one' do
16
+
17
+ let :gsgtone do
18
+ ERV::GammaDistribution.new(shape: 2.0, scale: 0.5)
19
+ end
20
+
21
+ context 'sampling' do
22
+
23
+ it 'should allow sampling' do
24
+ gsgtone.sample
25
+ end
26
+
27
+ end
28
+
29
+ context 'moments' do
30
+ let :samples do
31
+ 0.upto(NUM_SAMPLES).collect { gsgtone.sample }
32
+ end
33
+
34
+ it 'should have the expected mean' do
35
+ sample_mean = samples.inject(0.0) {|s,x| s += x } / NUM_SAMPLES.to_f
36
+ sample_mean.must_be_within_epsilon gsgtone.mean, 0.05
37
+ end
38
+
39
+ it 'should have the expected variance' do
40
+ sample_variance = samples.inject(0.0) {|s,x| s += (x - gsgtone.mean) ** 2 } / NUM_SAMPLES.to_f
41
+ sample_variance.must_be_within_epsilon gsgtone.variance, 0.05
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,82 @@
1
+ require 'test_helper'
2
+
3
+ require 'erv/general_pareto_distribution'
4
+
5
+ describe ERV::GpdDistribution do
6
+
7
+ NUM_SAMPLES = 50000
8
+
9
+ it 'should require scale and shape parameters' do
10
+ lambda do
11
+ ERV::GpdDistribution.new()
12
+ end.must_raise ArgumentError
13
+ end
14
+
15
+
16
+ context 'a (default) 2-parameter distribution' do
17
+
18
+ let :gpd_2params do
19
+ ERV::GpdDistribution.new(scale: 1.0, shape: 0.3)
20
+ end
21
+
22
+ context 'sampling' do
23
+
24
+ it 'should allow sampling' do
25
+ gpd_2params.sample
26
+ end
27
+
28
+ end
29
+
30
+ context 'moments' do
31
+ let :samples do
32
+ 0.upto(NUM_SAMPLES).collect { gpd_2params.sample }
33
+ end
34
+
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 gpd_2params.mean, 0.1
38
+ end
39
+
40
+ it 'should have the expected variance' do
41
+ sample_variance = samples.inject(0.0) {|s,x| s += (x - gpd_2params.mean) ** 2 } / NUM_SAMPLES.to_f
42
+ sample_variance.must_be_within_epsilon gpd_2params.variance, 0.1
43
+ end
44
+
45
+ end
46
+ end
47
+
48
+
49
+ context 'a 3-parameter distribution' do
50
+
51
+ let :gpd_3params do
52
+ ERV::GpdDistribution.new(scale: 1.0,
53
+ shape: 0.3,
54
+ location: 1.0)
55
+ end
56
+
57
+ context 'sampling' do
58
+
59
+ it 'should allow sampling' do
60
+ gpd_3params.sample
61
+ end
62
+
63
+ end
64
+
65
+ context 'moments' do
66
+ let :samples do
67
+ 0.upto(NUM_SAMPLES).collect { gpd_3params.sample }
68
+ end
69
+
70
+ it 'should have the expected mean' do
71
+ sample_mean = samples.inject(0.0) {|s,x| s += x } / NUM_SAMPLES.to_f
72
+ sample_mean.must_be_within_epsilon gpd_3params.mean, 0.1
73
+ end
74
+
75
+ it 'should have the expected variance' do
76
+ sample_variance = samples.inject(0.0) {|s,x| s += (x - gpd_3params.mean) ** 2 } / NUM_SAMPLES.to_f
77
+ sample_variance.must_be_within_epsilon gpd_3params.variance, 0.1
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+
3
+ require 'erv/geometric_distribution'
4
+
5
+ describe ERV::GeometricDistribution do
6
+
7
+ NUM_SAMPLES = 10000
8
+
9
+ it 'should require at least a probability of success parameter' do
10
+ lambda do
11
+ ERV::GeometricDistribution.new()
12
+ end.must_raise ArgumentError
13
+ end
14
+
15
+ let :gd do
16
+ ERV::GeometricDistribution.new(probability_of_success: 0.5)
17
+ end
18
+
19
+ context 'sampling' do
20
+
21
+ it 'should allow sampling' do
22
+ gd.sample
23
+ end
24
+
25
+ end
26
+
27
+ context 'moments' do
28
+ let :samples do
29
+ 0.upto(NUM_SAMPLES).collect { gd.sample }
30
+ end
31
+
32
+ it 'should have the expected mean' do
33
+ sample_mean = samples.inject(0.0) {|s,x| s += x } / NUM_SAMPLES.to_f
34
+ sample_mean.must_be_within_epsilon gd.mean, 0.05
35
+ end
36
+
37
+ it 'should have the expected variance' do
38
+ sample_variance = samples.inject(0.0) {|s,x| s += (x - gd.mean) ** 2 } / NUM_SAMPLES.to_f
39
+ sample_variance.must_be_within_epsilon gd.variance, 0.05
40
+ end
41
+
42
+ end
43
+
44
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: erv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mauro Tortonesi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-01 00:00:00.000000000 Z
11
+ date: 2017-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,7 +80,7 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.0.3
83
- description: erv-0.3.1
83
+ description: erv-0.3.2
84
84
  email:
85
85
  - mauro.tortonesi@unife.it
86
86
  executables: []
@@ -108,7 +108,11 @@ files:
108
108
  - lib/erv/uniform_distribution.rb
109
109
  - lib/erv/version.rb
110
110
  - test/erv/constant_distribution_test.rb
111
+ - test/erv/discrete_uniform_distribution_test.rb
111
112
  - test/erv/distribution_test.rb
113
+ - test/erv/gamma_distribution_test.rb
114
+ - test/erv/general_pareto_distribution_test.rb
115
+ - test/erv/geometric_distribution_test.rb
112
116
  - test/erv/mixture_distribution_test.rb
113
117
  - test/erv/random_variable_test.rb
114
118
  - test/test_helper.rb
@@ -132,13 +136,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
136
  version: '0'
133
137
  requirements: []
134
138
  rubyforge_project:
135
- rubygems_version: 2.6.8
139
+ rubygems_version: 2.6.11
136
140
  signing_key:
137
141
  specification_version: 4
138
142
  summary: Easy/elegant Ruby library providing support for random variable generation
139
143
  test_files:
140
144
  - test/erv/constant_distribution_test.rb
145
+ - test/erv/discrete_uniform_distribution_test.rb
141
146
  - test/erv/distribution_test.rb
147
+ - test/erv/gamma_distribution_test.rb
148
+ - test/erv/general_pareto_distribution_test.rb
149
+ - test/erv/geometric_distribution_test.rb
142
150
  - test/erv/mixture_distribution_test.rb
143
151
  - test/erv/random_variable_test.rb
144
152
  - test/test_helper.rb