croupier 1.6.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +2 -2
  3. data/Rakefile +7 -0
  4. data/lib/croupier/distribution.rb +115 -36
  5. data/lib/croupier/distribution_generator.rb +57 -0
  6. data/lib/croupier/distribution_generators/enumerator_block_generator.rb +23 -0
  7. data/lib/croupier/distribution_generators/enumerator_generator.rb +21 -0
  8. data/lib/croupier/distribution_generators/inverse_cdf_generator.rb +21 -0
  9. data/lib/croupier/distribution_generators/minimum_sample_generator.rb +24 -0
  10. data/lib/croupier/distribution_generators.rb +18 -0
  11. data/lib/croupier/distributions/bernoulli.rb +18 -25
  12. data/lib/croupier/distributions/binomial.rb +30 -29
  13. data/lib/croupier/distributions/cauchy.rb +22 -19
  14. data/lib/croupier/distributions/credit_card.rb +39 -29
  15. data/lib/croupier/distributions/degenerate.rb +16 -19
  16. data/lib/croupier/distributions/exponential.rb +18 -19
  17. data/lib/croupier/distributions/gamma.rb +66 -37
  18. data/lib/croupier/distributions/geometric.rb +19 -20
  19. data/lib/croupier/distributions/nbinomial.rb +22 -33
  20. data/lib/croupier/distributions/normal.rb +30 -34
  21. data/lib/croupier/distributions/poisson.rb +26 -25
  22. data/lib/croupier/distributions/triangular.rb +39 -25
  23. data/lib/croupier/distributions/uniform.rb +43 -19
  24. data/lib/croupier/distributions.rb +3 -4
  25. data/lib/croupier/version.rb +1 -1
  26. data/lib/croupier.rb +43 -6
  27. data/test/minitest/distribution_class/test_class_methods.rb +142 -0
  28. data/test/{test_distribution_class.rb → minitest/distribution_class/test_instance_methods.rb} +26 -28
  29. data/test/minitest/distribution_generator_class/test_class_methods.rb +24 -0
  30. data/test/minitest/distribution_generator_class/test_instance_methods.rb +35 -0
  31. data/test/minitest/distribution_generators/test_enumerator_block_generator.rb +31 -0
  32. data/test/minitest/distribution_generators/test_enumerator_generator.rb +33 -0
  33. data/test/minitest/distribution_generators/test_inverse_cdf_generator.rb +36 -0
  34. data/test/minitest/distribution_generators/test_minimum_sample_generator.rb +33 -0
  35. data/test/minitest/distributions/test_bernoulli_distribution.rb +13 -0
  36. data/test/minitest/distributions/test_binomial_distribution.rb +18 -0
  37. data/test/minitest/distributions/test_cauchy_distribution.rb +18 -0
  38. data/test/{distributions/minitest/test_credit_card.rb → minitest/distributions/test_credit_card_distribution.rb} +5 -0
  39. data/test/{distributions/minitest → minitest/distributions}/test_degenerate_distribution.rb +2 -2
  40. data/test/minitest/distributions/test_exponential_distribution.rb +13 -0
  41. data/test/minitest/distributions/test_gamma_distribution.rb +23 -0
  42. data/test/minitest/distributions/test_geometric_distribution.rb +13 -0
  43. data/test/minitest/distributions/test_normal_distribution.rb +17 -0
  44. data/test/minitest/distributions/test_poisson_distribution.rb +13 -0
  45. data/test/minitest/distributions/test_triangular_distribution.rb +26 -0
  46. data/test/minitest/distributions/test_uniform_distribution.rb +54 -0
  47. data/test/{test_croupier_module.rb → minitest/test_croupier_module.rb} +0 -0
  48. data/test/minitest/test_distribution_generators_module.rb +37 -0
  49. data/test/{test_distributions_module.rb → minitest/test_distributions_module.rb} +0 -0
  50. data/test/rtests.R +1 -0
  51. metadata +62 -44
  52. data/test/distributions/R_tests/test_bernoulli.R +0 -17
  53. data/test/distributions/R_tests/test_binomial.R +0 -17
  54. data/test/distributions/R_tests/test_cauchy.R +0 -28
  55. data/test/distributions/R_tests/test_exponential.R +0 -28
  56. data/test/distributions/R_tests/test_gamma.R +0 -27
  57. data/test/distributions/R_tests/test_geometric.R +0 -23
  58. data/test/distributions/R_tests/test_nbinomial.R +0 -17
  59. data/test/distributions/R_tests/test_normal.R +0 -52
  60. data/test/distributions/R_tests/test_poisson.R +0 -17
  61. data/test/distributions/R_tests/test_triangular.R +0 -28
  62. data/test/distributions/R_tests/test_uniform.R +0 -30
  63. data/test/testsuite.R +0 -37
@@ -6,30 +6,27 @@ module Croupier
6
6
  # Discrete probability distribution that returns the same value.
7
7
  class Degenerate < ::Croupier::Distribution
8
8
 
9
- def initialize(options={})
10
- @name = "Degenerate distribution"
11
- @description = "Discrete probability distribution that returns the same value each time."
12
- configure(options)
13
- end
9
+ distribution_name "Degenerate distribution"
14
10
 
15
- def generate_number
16
- params[:constant]
17
- end
11
+ distribution_description "Discrete probability distribution that returns the same value each time."
18
12
 
19
- def default_parameters
20
- {:constant => 42.0}
21
- end
13
+ cli_name "degenerate"
14
+
15
+ cli_options({
16
+ options: [
17
+ [:constant, 'value to be returned', {type: :float, default: 42.0}]
18
+ ],
19
+ banner: "Degenerate distribution. Discrete probability distribution that returns the same value each time."
20
+ })
22
21
 
23
- def self.cli_name
24
- "degenerate"
22
+ enumerator_block do |y|
23
+ loop do
24
+ y << params[:constant]
25
+ end
25
26
  end
26
27
 
27
- def self.cli_options
28
- {:options => [
29
- [:constant, 'value to be returned', {:type=>:float, :default => 42.0}]
30
- ],
31
- :banner => "Degenerate distribution. Discrete probability distribution that returns the same value each time."
32
- }
28
+ def initialize(options={})
29
+ super(options)
33
30
  end
34
31
  end
35
32
  end
@@ -8,31 +8,30 @@ module Croupier
8
8
  #
9
9
  class Exponential < ::Croupier::Distribution
10
10
 
11
- def initialize(options={})
12
- @name = "Exponential distribution"
13
- @description = "Continuous probability distribution with a lambda param rate describing the time between events in a Poisson process"
14
- configure(options)
15
- raise Croupier::InputParamsError, "Invalid interval values" if params[:lambda] <= 0
16
- end
11
+ distribution_name "Exponential distribution"
17
12
 
18
- def inv_cdf n
19
- (-1/params[:lambda]) * Math.log(n)
20
- end
13
+ distribution_description "Continuous probability distribution with a lambda param rate describing the time between events in a Poisson process"
21
14
 
22
- def default_parameters
23
- {:lambda => 1.0}
15
+ cli_name "exponential"
16
+
17
+ cli_options({
18
+ options: [
19
+ [:lambda, 'rate param', {type: :float, default: 1.0}]
20
+ ],
21
+ banner: "Exponential distribution. Generate numbers following a exponential distribution for a given lambda rate"
22
+ })
23
+
24
+ inv_cdf do |n|
25
+ (-1 / lambda) * Math.log(1 - n)
24
26
  end
25
27
 
26
- def self.cli_name
27
- "exponential"
28
+ def initialize(options={})
29
+ super(options)
30
+ raise Croupier::InputParamsError, "lambda cannot be negative" if params[:lambda] <= 0
28
31
  end
29
32
 
30
- def self.cli_options
31
- {:options => [
32
- [:lambda, 'rate param', {:type=>:float, :default => 1.0}]
33
- ],
34
- :banner => "Exponential distribution. Generate numbers following a exponential distribution for a given lambda rate"
35
- }
33
+ def lambda
34
+ params[:lambda]
36
35
  end
37
36
  end
38
37
  end
@@ -7,58 +7,87 @@ module Croupier
7
7
  # (defaults to 1) and scale (defaults to 1).
8
8
  class Gamma < ::Croupier::Distribution
9
9
 
10
+ distribution_name "Gamma distribution"
11
+
12
+ distribution_description "Family of continuous distributions with two parameters, shape and scale"
13
+
14
+ cli_name "gamma"
15
+
16
+ cli_options({
17
+ options: [
18
+ [:shape, 'shape of the distribution', {type: :float, default: 1.0}],
19
+ [:scale, 'scale of the distribution', {type: :float, default: 1.0}]
20
+ ],
21
+ banner: "Family of continuous distributions with two parameters, shape and scale."
22
+ })
23
+
24
+ enumerator do |c|
25
+ c.degenerate(constant: scale).to_enum.lazy.zip(xi_enum, adjust_enum).map do |s,x,a|
26
+ s * (x - a)
27
+ end
28
+ end
29
+
10
30
  def initialize(options={})
11
- @name = "Gamma distribution"
12
- @description = "Family of continuous distributions with two parameters, shape and scale"
13
- configure(options)
31
+ super(options)
14
32
  end
15
33
 
16
- def generate_number
17
- params[:scale] * (gen_xi - (1..params[:shape].floor).map { Math.log(1 - rand) }.inject(&:+) )
34
+ def shape
35
+ params[:shape]
18
36
  end
19
37
 
20
- def default_parameters
21
- {:shape => 1.0, :std => 1.0}
38
+ def scale
39
+ params[:scale]
22
40
  end
23
41
 
24
- def self.cli_name
25
- "gamma"
42
+ def delta
43
+ @delta ||= shape - shape.floor
26
44
  end
27
45
 
28
- def self.cli_options
29
- {:options => [
30
- [:shape, 'shape of the distribution', {:type=>:float, :default => 1.0}],
31
- [:scale, 'scale of the distribution', {:type=>:float, :default => 1.0}]
32
- ],
33
- :banner => "Family of continuous distributions with two parameters, shape and scale."
34
- }
46
+ def v_0
47
+ @v_0 ||= Math::E / (delta + Math::E)
35
48
  end
36
49
 
37
50
  protected
51
+
52
+ def adjust_enum
53
+ uniform.map{|n| Math.log(n) }.each_slice(shape.floor).map do |slice|
54
+ slice.inject &:+
55
+ end
56
+ end
57
+
38
58
  # Based on Ahrens-Dieter acceptance-rejection method
39
59
  # as described on Wikipedia:
40
60
  # http://en.wikipedia.org/wiki/Gamma_distribution#Generating_gamma-distributed_random_variables
41
- def gen_xi
42
- delta = params[:shape] - params[:shape].floor
43
- v_0 = Math::E / ( delta + Math::E )
44
- eta = 1; xi = 0;
45
-
46
- begin
47
- # Generate thre independent uniformly distributed
48
- # on interval (0,1]random variables
49
- v_1 = 1 - rand; v_2 = 1 - rand; v_3 = 1 - rand;
50
-
51
- # Generate a new xi.
52
- if v_1 < v_0
53
- xi = (v_2 ** (1/delta))
54
- eta = v_3 * (xi ** (delta - 1))
55
- else
56
- xi = 1 - Math.log(v_2)
57
- eta = v_3 * (Math::E ** (-xi))
58
- end
59
- end while eta > (xi ** (delta - 1)) * (Math::E ** -xi)
60
-
61
- xi
61
+
62
+ def xi_enum
63
+ uniform.zip(uniform, uniform).map do |v1, v2, v3|
64
+ x = xi v1, v2, v3
65
+ [x, eta(x, v1, v2, v3)]
66
+ end.reject do |x,e|
67
+ e > (x** (delta - 1)) * (Math.exp(-x))
68
+ end.map do |x,e|
69
+ x
70
+ end
71
+ end
72
+
73
+ def xi v1, v2, v3
74
+ if v1 < v_0
75
+ v2** (1/delta)
76
+ else
77
+ 1 - Math.log(v2)
78
+ end
79
+ end
80
+
81
+ def eta xi, v1, v2, v3
82
+ if v1 < v_0
83
+ v3 * (xi** (delta - 1))
84
+ else
85
+ v3 * Math.exp(-xi)
86
+ end
87
+ end
88
+
89
+ def uniform
90
+ ::Croupier::Distributions.uniform(included: 1, excluded: 0).to_enum.lazy
62
91
  end
63
92
  end
64
93
  end
@@ -12,33 +12,32 @@ module Croupier
12
12
  # (The Art of Computer Programming, Volume 2, 3.4.1.F )
13
13
  class Geometric < ::Croupier::Distribution
14
14
 
15
- def initialize(options={})
16
- @name = "Geometric distribution"
17
- @description = "Discrete probability distribution that expresses the number of X Bernoulli trials needed to get one success, supported on the set { 1, 2, 3, ...}"
18
- configure(options)
19
- raise Croupier::InputParamsError, "Probability of success must be in the interval [0,1]" if params[:success] > 1 || params[:success] < 0
20
- end
15
+ distribution_name "Geometric distribution"
16
+
17
+ distribution_description "Discrete probability distribution that expresses the number of X Bernoulli trials needed to get one success, supported on the set { 1, 2, 3, ...}"
18
+
19
+ cli_name "geometric"
20
+
21
+ cli_options({
22
+ options: [
23
+ [:success, 'success probability of each trial', {type: :float, short: "-p", default: 0.5}]
24
+ ],
25
+ banner: "Geometric distribution. Discrete probability distribution that expresses the number of X Bernoulli trials needed to get one success, supported on the set { 1, 2, 3, ...} }"
26
+ })
21
27
 
22
28
  # Fair point: it is not the inverse of the cdf,
23
29
  # but it generates the distribution from an uniform.
24
- def inv_cdf n
25
- (Math.log(1-n) / Math.log(1-params[:success])).ceil
26
- end
27
-
28
- def default_parameters
29
- {:success => 0.5}
30
+ inv_cdf do |n|
31
+ (Math.log(1.0-n) / Math.log(1.0-success)).ceil
30
32
  end
31
33
 
32
- def self.cli_name
33
- "geometric"
34
+ def initialize(options={})
35
+ super(options)
36
+ raise Croupier::InputParamsError, "Probability of success must be in the interval [0,1]" if params[:success] > 1 || params[:success] < 0
34
37
  end
35
38
 
36
- def self.cli_options
37
- {:options => [
38
- [:success, 'success probability of each trial', {:type=>:float, :short => "-p", :default => 0.5}]
39
- ],
40
- :banner => "Geometric distribution. Discrete probability distribution that expresses the number of X Bernoulli trials needed to get one success, supported on the set { 1, 2, 3, ...} }"
41
- }
39
+ def success
40
+ params[:success]
42
41
  end
43
42
  end
44
43
  end
@@ -10,49 +10,38 @@ module Croupier
10
10
  # Wikipedia -- http://en.wikipedia.org/wiki/Negative_binomial_distribution
11
11
  class Nbinomial < ::Croupier::Distribution
12
12
 
13
- def initialize(options={})
14
- @name = "Negative binomial distribution"
15
- @description = "Discrete probability distribution of the number of successes in a sequence of Bernoulli trials before a specified (non-random) number of failures (denoted size) occur."
16
- configure(options)
17
- raise Croupier::InputParamsError, "Probability of success must be in the interval [0,1]" if params[:success] > 1 || params[:success] < 0
18
- end
13
+ distribution_name "Negative binomial distribution"
19
14
 
20
- # Fair point: it is not the inverse of the cdf,
21
- # but it generates the distribution from an uniform.
22
- def generate_sample n=1
23
- generate_geometrics(n).each_slice(params[:size]).map do |sample|
24
- sample.inject(-params[:size], &:+) # Inject starts on -size because
25
- # this way it is equivalent to:
26
- # sample.map{|x| x - 1}.inject(&:+)
27
- end
28
- end
15
+ distribution_description "Discrete probability distribution of the number of successes in a sequence of Bernoulli trials before a specified (non-random) number of failures (denoted size) occur."
29
16
 
30
- def default_parameters
31
- {:success => 0.5, :size => 1}
32
- end
17
+ cli_name "nbinomial"
33
18
 
34
- def self.cli_name
35
- "nbinomial"
36
- end
19
+ cli_options({
20
+ options: [
21
+ [:size, 'number of errors', {type: :integer, default: 1}],
22
+ [:success, 'success probability of each trial', {type: :float, short: "-p", default: 0.5}]
23
+ ],
24
+ banner: "Negative binomial distribution. Discrete probability distribution of the number of successes in a sequence of Bernoulli trials before a specified (non-random) number of failures (denoted size) occur."
25
+ })
37
26
 
38
- def self.cli_options
39
- {:options => [
40
- [:size, 'number of errors', {:type => :integer, :default => 1}],
41
- [:success, 'success probability of each trial', {:type=>:float, :short => "-p", :default => 0.5}]
42
- ],
43
- :banner => "Negative binomial distribution. Discrete probability distribution of the number of successes in a sequence of Bernoulli trials before a specified (non-random) number of failures (denoted size) occur."
44
- }
27
+ enumerator do |c|
28
+ c.geometric(success: success).to_enum.lazy.each_slice(size).map do |x|
29
+ x.inject(-size,&:+)
30
+ end
45
31
  end
46
32
 
47
- private
48
- def base_geometric
49
- ::Croupier::Distributions::Geometric.new(success: params[:success])
33
+ def initialize(options={})
34
+ super(options)
35
+ raise Croupier::InputParamsError, "Probability of success must be in the interval [0,1]" if params[:success] > 1 || params[:success] < 0
50
36
  end
51
37
 
52
- def generate_geometrics(n)
53
- base_geometric.generate_sample(params[:size]*n)
38
+ def success
39
+ params[:success]
54
40
  end
55
41
 
42
+ def size
43
+ params[:size]
44
+ end
56
45
  end
57
46
  end
58
47
  end
@@ -8,48 +8,44 @@ module Croupier
8
8
  #
9
9
  class Normal < ::Croupier::Distribution
10
10
 
11
- def initialize(options={})
12
- @name = "Normal distribution"
13
- @description = "Continuous distribution (mu,sigma) (defaults to (0,1) ) where mu is the mean and sigma the standard deviation."
14
- configure(options)
11
+ distribution_name "Normal distribution"
12
+
13
+ distribution_description "Continuous distribution (mu,sigma) (defaults to (0,1) ) where mu is the mean and sigma the standard deviation."
14
+
15
+ cli_name "normal"
16
+
17
+ cli_options({
18
+ options: [
19
+ [:mean, 'mean of the distribution', {type: :float, default: 0.0}],
20
+ [:std, 'standard deviation of the distribution', {type: :float, default: 1.0}]
21
+ ],
22
+ banner: "Normal distribution. Generate numbers following a continuous distribution in the real line with mean :mean and standard deviation :std."
23
+ })
24
+
25
+ minimum_sample do
26
+ x, y = 1 - ::Croupier.rand, 1 - ::Croupier.rand
27
+ [
28
+ Math.sqrt(-2*Math.log(x)) * Math.cos(2*Math::PI*y),
29
+ Math.sqrt(-2*Math.log(x)) * Math.sin(2*Math::PI*y)
30
+ ]
15
31
  end
16
32
 
17
- def generate_sample(n=1)
18
- sample = n.odd? ? n+1 : n
19
-
20
- # Generate
21
- gen = (1..sample).map do |x|
22
- 1 - rand # because uniform need to be in (0,1]
23
- end.each_slice(2).flat_map do |x, y|
24
- [
25
- Math.sqrt(-2*Math.log(x)) * Math.cos(2*Math::PI*y),
26
- Math.sqrt(-2*Math.log(x)) * Math.sin(2*Math::PI*y)
27
- ]
28
- end
29
-
30
- # Adjust parameters.
31
- gen.map!{ |x| x * params[:std] } if params[:std] != 1
32
- gen.map!{ |x| x + params[:mean] } if params[:mean] != 0
33
-
34
- # Adjust length
35
- n.odd? ? gen[0..-2] : gen
33
+ def initialize(options={})
34
+ super(options)
36
35
  end
37
36
 
38
- def default_parameters
39
- {:mean => 0, :std => 1}
37
+ def std
38
+ params[:std]
40
39
  end
41
40
 
42
- def self.cli_name
43
- "normal"
41
+ def mean
42
+ params[:mean]
44
43
  end
45
44
 
46
- def self.cli_options
47
- {:options => [
48
- [:mean, 'mean of the distribution', {:type=>:float, :default => 0.0}],
49
- [:std, 'standard deviation of the distribution', {:type=>:float, :default => 1.0}]
50
- ],
51
- :banner => "Normal distribution. Generate numbers following a continuous distribution in the real line with mean :mean and standard deviation :std."
52
- }
45
+ def to_enum
46
+ @generator.to_enum.
47
+ map {|x| x * std}. # Adjust standard deviation
48
+ map {|x| x + mean} # Adjust mean
53
49
  end
54
50
  end
55
51
  end
@@ -4,43 +4,44 @@ module Croupier
4
4
  #####################################################################
5
5
  # Poisson Distribution
6
6
  # Discrete probability distribution that expresses the probability
7
- # of a given number of event occurrin in a fixed interval of time
7
+ # of a given number of event occurring in a fixed interval of time
8
8
  # and/or space if these events occur with a known average rate
9
9
  # and independently of the time since the las event.
10
10
  #
11
11
  # Wikipedia http://en.wikipedia.org/wiki/Poisson_distribution
12
12
  class Poisson < ::Croupier::Distribution
13
13
 
14
- def initialize(options={})
15
- @name = "Poisson distribution"
16
- @description = "Discrete probability distribution that expresses the probability of a given number of events occurring in a fixed interval of time."
17
- configure(options)
18
- end
14
+ distribution_name "Poisson distribution"
19
15
 
20
- def generate_number
21
- l = Math.exp(-params[:lambda])
22
- k = 0; p = 1;
23
- while p > l
24
- p *= rand
25
- k += 1;
26
- end
27
- k-1
28
- end
16
+ distribution_description "Discrete probability distribution that expresses the probability of a given number of events occurring in a fixed interval of time."
29
17
 
30
- def default_parameters
31
- {:lambda => 50}
18
+ cli_name "poisson"
19
+
20
+ cli_options({
21
+ options: [
22
+ [:lambda, 'rate parameter (equal to the mean of the distribution)', {type: :integer, default: 50}]
23
+ ],
24
+ banner: "Poisson distribution. Discrete probability distribution that expresses the probability of a given number of events occurring in a fixed interval of time."
25
+ })
26
+
27
+ enumerator_block do |y|
28
+ l = Math.exp(-lambda)
29
+ loop do
30
+ k = 0; p = 1
31
+ while p > l
32
+ p *= ::Croupier.rand
33
+ k += 1
34
+ end
35
+ y << (k-1)
36
+ end
32
37
  end
33
38
 
34
- def self.cli_name
35
- "poisson"
39
+ def initialize(options={})
40
+ super(options)
36
41
  end
37
42
 
38
- def self.cli_options
39
- {:options => [
40
- [:lambda, 'rate parameter (equal to the mean of the distribution)', {:type=>:integer, :default => 50}]
41
- ],
42
- :banner => "Poisson distribution. Discrete probability distribution that expresses the probability of a given number of events occurring in a fixed interval of time."
43
- }
43
+ def lambda
44
+ params[:lambda]
44
45
  end
45
46
  end
46
47
  end
@@ -7,42 +7,56 @@ module Croupier
7
7
  # is a, upper limit b and mode c (a <= c <= b).
8
8
  class Triangular < ::Croupier::Distribution
9
9
 
10
+ distribution_name "Triangular distribution"
11
+
12
+ distribution_description "Continuous probability distribution whose lower limit is a, upper limit b and mode c (a <= c <= b)"
13
+
14
+ cli_name "triangular"
15
+
16
+ cli_options({
17
+ options: [
18
+ [:lower, 'lower limit', {type: :float, short: '-a', default: 0.0}],
19
+ [:upper, 'upper limit', {type: :float, short: '-b', default: 1.0}],
20
+ [:mode, 'mode', {type: :float, short: '-c', default: 0.5}]
21
+ ],
22
+ banner: "Triangular distribution. Continuous distribution whose support is the interval (a,b), with mode c."
23
+ })
24
+
25
+ inv_cdf do |n|
26
+ if n < @F_c
27
+ lower + Math.sqrt( n * range * (mode - lower) )
28
+ else
29
+ upper - Math.sqrt( (1-n) * range * (upper - mode) )
30
+ end
31
+ end
32
+
10
33
  def initialize(options={})
11
- @name = "Triangular distribution"
12
- @description = "Continuous probability distribution whose lower limit is a, upper limit b and mode c (a <= c <= b)"
13
- configure(options)
14
- raise Croupier::InputParamsError, "Invalid interval values" if params[:a] >= params[:b]
15
- if params[:c] < params[:a] || params[:b] < params[:c]
34
+ super(options)
35
+ if params[:lower] >= params[:upper]
36
+ warn("Lower limit is greater than upper limit. Changing their values.")
37
+ params[:lower], params[:upper] = params[:upper], params[:lower]
38
+ end
39
+ if params[:mode] < params[:lower] || params[:upper] < params[:mode]
16
40
  warn("Mode is not in the support. Mode value will be change to median.")
17
- params[:c] = (params[:a]+params[:b])/2;
41
+ params[:mode] = (params[:lower]+params[:upper])/2.0;
18
42
  end
19
- @F_c = (params[:c]-params[:a])/(params[:b]-params[:a])
43
+ @F_c = (params[:mode]-params[:lower]).to_f/(params[:upper]-params[:lower])
20
44
  end
21
45
 
22
- def inv_cdf n
23
- if n < @F_c
24
- params[:a] + Math.sqrt( n * (params[:b] - params[:a]) * (params[:c] - params[:a]) )
25
- else
26
- params[:b] - Math.sqrt( (1-n) * (params[:b] - params[:a]) * (params[:b] - params[:c]) )
27
- end
46
+ def lower
47
+ params[:lower]
28
48
  end
29
49
 
30
- def default_parameters
31
- {:a => 0.0, :b => 1.0, :c => 0.5}
50
+ def upper
51
+ params[:upper]
32
52
  end
33
53
 
34
- def self.cli_name
35
- "triangular"
54
+ def mode
55
+ params[:mode]
36
56
  end
37
57
 
38
- def self.cli_options
39
- {:options => [
40
- [:a, 'lower limit', {:type=>:float, :default => 0.0}],
41
- [:b, 'upper limit', {:type=>:float, :default => 1.0}],
42
- [:c, 'mode' , {:type=>:float, :default => 0.5}]
43
- ],
44
- :banner => "Triangular distribution. Continuous distribution whose support is the interval (a,b), with mode c."
45
- }
58
+ def range
59
+ @range ||= upper - lower
46
60
  end
47
61
  end
48
62
  end
@@ -3,37 +3,61 @@ module Croupier
3
3
 
4
4
  #####################################################################
5
5
  # Uniform Distribution
6
- # Continous distribution where all points in an interval have
6
+ # Continuous distribution where all points in an interval have
7
7
  # the same probability.
8
8
  #
9
9
  class Uniform < ::Croupier::Distribution
10
10
 
11
+ distribution_name "Uniform distribution"
12
+
13
+ distribution_description "Continuous distribution on [a,b] (defaults to [0,1]) where all points in the interval are equally likely"
14
+
15
+ cli_name "uniform"
16
+
17
+ cli_options({
18
+ options: [
19
+ [:included, 'interval included value', {type: :float, short: "-i", default: 0.0}],
20
+ [:excluded, 'interval excluded value', {type: :float, short: "-e", default: 1.0}]
21
+ ],
22
+ banner: "Uniform distribution. Generate numbers following a continuous distribution on [a,b] (given a=min(included, excluded) and b=max(included,excluded)) where all points in the interval are equally likely."
23
+ })
24
+
11
25
  def initialize(options={})
12
- @name = "Uniform distribution"
13
- @description = "Continuous distribution on [a,b] (defaults to [0,1]) where all points in the interval are equally likely"
14
- configure(options)
26
+ super options
27
+ @exclude_value = if self.inverted?
28
+ ->(n) { 1-n }
29
+ else
30
+ ->(n) { n }
31
+ end
32
+ @min, @max = if self.inverted?
33
+ [params[:excluded], params[:included]]
34
+ else
35
+ [params[:included], params[:excluded]]
36
+ end
37
+ @range = @max - @min
15
38
  end
16
39
 
17
- def generate_number
18
- raise Croupier::InputParamsError, "Invalid interval values" if params[:b] < params[:a]
19
- rand Range.new(params[:a], params[:b])
20
- end
40
+ attr_reader :exclude_value, :min, :max, :range
21
41
 
22
- def default_parameters
23
- {:a => 0.0, :b => 1.0}
42
+ def inverted?
43
+ params[:included] > params[:excluded]
24
44
  end
25
45
 
26
- def self.cli_name
27
- "uniform"
46
+ def to_enum
47
+ @enum ||= base_enum.lazy.
48
+ map(&exclude_value).
49
+ map do |n|
50
+ min + range * n
51
+ end
28
52
  end
29
53
 
30
- def self.cli_options
31
- {:options => [
32
- [:a, 'interval start value', {:type=>:float, :default => 0.0}],
33
- [:b, 'interval end value', {:type=>:float, :default => 1.0}]
34
- ],
35
- :banner => "Uniform distribution. Generate numbers following a continuous distribution on [a,b] (defaults to [0,1]) where all points in the interval are equally likely"
36
- }
54
+ protected
55
+ def base_enum
56
+ Enumerator.new do |y|
57
+ loop do
58
+ y << Croupier.rand
59
+ end
60
+ end
37
61
  end
38
62
  end
39
63
  end