erv 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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