croupier 1.6.0 → 2.0.0.rc1

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.
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