statistical 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +111 -0
- data/.travis.yml +7 -0
- data/CONTRIBUTING.md +73 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +37 -0
- data/Rakefile +43 -0
- data/bin/console +11 -0
- data/bin/distribution +53 -0
- data/bin/setup +8 -0
- data/data/template/distribution.erb +84 -0
- data/data/template/rng.erb +53 -0
- data/data/template/spec.erb +142 -0
- data/lib/core_extensions.rb +35 -0
- data/lib/statistical.rb +7 -0
- data/lib/statistical/distribution.rb +36 -0
- data/lib/statistical/distribution/bernoulli.rb +29 -0
- data/lib/statistical/distribution/exponential.rb +85 -0
- data/lib/statistical/distribution/laplace.rb +101 -0
- data/lib/statistical/distribution/two_point.rb +144 -0
- data/lib/statistical/distribution/uniform.rb +98 -0
- data/lib/statistical/distribution/uniform_discrete.rb +133 -0
- data/lib/statistical/distribution/weibull.rb +99 -0
- data/lib/statistical/helpers.rb +132 -0
- data/lib/statistical/rng.rb +37 -0
- data/lib/statistical/rng/bernoulli.rb +29 -0
- data/lib/statistical/rng/exponential.rb +56 -0
- data/lib/statistical/rng/laplace.rb +57 -0
- data/lib/statistical/rng/two_point.rb +70 -0
- data/lib/statistical/rng/uniform.rb +62 -0
- data/lib/statistical/rng/uniform_discrete.rb +78 -0
- data/lib/statistical/rng/weibull.rb +58 -0
- data/lib/statistical/version.rb +3 -0
- data/statistical.gemspec +28 -0
- metadata +165 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
module Statistical
|
2
|
+
# This class models a mathematical domain by basing it on Ruby's Range
|
3
|
+
# Does not allow for enumeration of the domain unless. The primary addition
|
4
|
+
# here is the addtion of an exclusion list which allows us to exclude specific
|
5
|
+
# points and ranges from within the domain.
|
6
|
+
#
|
7
|
+
# @note If the exclusion list contains points outside of the base range these
|
8
|
+
# points are not validated. The user is expect to supply valid input, since
|
9
|
+
# this is a helper class
|
10
|
+
# @note All instances of this class are returned frozen
|
11
|
+
#
|
12
|
+
# @author Vaibhav Yenamandra
|
13
|
+
#
|
14
|
+
# @attr_reader :exclusions The exclusion list of points and ranges to not
|
15
|
+
# be included in the domain. The list must be homogenous
|
16
|
+
class Domain < Range
|
17
|
+
include Comparable
|
18
|
+
|
19
|
+
attr_reader :start, :finish, :exclusions, :domain_type
|
20
|
+
|
21
|
+
TYPES = [
|
22
|
+
:left_open,
|
23
|
+
:right_open,
|
24
|
+
:full_open,
|
25
|
+
:closed
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
# Creates a new domain instance which can be one of the following types
|
29
|
+
# :left_open, :right_open, :full_open, :closed
|
30
|
+
# An exclusion list is also maintained to capture undesired points, ranges
|
31
|
+
#
|
32
|
+
# @param [Fixnum, Bignum] start number where the range starts
|
33
|
+
# @param [Fixnum, Bignum] finish number where the range ends
|
34
|
+
# @param [Symbol] domain_type kind of domain to represent
|
35
|
+
# @param exclusions homogenous list of exclusions
|
36
|
+
def initialize(start, finish, domain_type, *exclusions)
|
37
|
+
exclusions ||= []
|
38
|
+
exclude_end = false
|
39
|
+
|
40
|
+
case domain_type
|
41
|
+
when :left_open
|
42
|
+
@exclusions = [start, exclusions].flatten
|
43
|
+
exclude_end = false
|
44
|
+
when :right_open
|
45
|
+
@exclusions = [exclusions, finish].flatten
|
46
|
+
exclude_end = true
|
47
|
+
when :full_open
|
48
|
+
@exclusions = [start, exclusions, finish].flatten
|
49
|
+
exclude_end = true
|
50
|
+
when :closed
|
51
|
+
@exclusions = [exclusions].flatten
|
52
|
+
exclude_end = false
|
53
|
+
else
|
54
|
+
raise ArgumentError,
|
55
|
+
"Invalid domain type, must be one of #{DOMAIN_TYPES}"
|
56
|
+
end
|
57
|
+
@start = start
|
58
|
+
@finish = finish
|
59
|
+
@domain_type = domain_type
|
60
|
+
|
61
|
+
super(@start, @finish, exclude_end)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a frozen new instance, overrides Range::new
|
65
|
+
# @return [Domain] a new instance of the Domain class
|
66
|
+
def new(*args)
|
67
|
+
super(*args).freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns a frozen new instance
|
71
|
+
# @see #new
|
72
|
+
# @return [Domain] a new instance of the Domain class
|
73
|
+
def self.[](*args)
|
74
|
+
new(*args)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Find if a point value is part of the instance's exclusion list
|
78
|
+
#
|
79
|
+
# @param val The numeric value to test for exclusion
|
80
|
+
# @return [Boolean] if the value is excluded from the current domain
|
81
|
+
def exclude?(val)
|
82
|
+
has_val = false
|
83
|
+
@exclusions.each do |e|
|
84
|
+
case e
|
85
|
+
when Fixnum, Bignum, Float
|
86
|
+
has_val = has_val || (e == val)
|
87
|
+
when Range
|
88
|
+
has_val ||= e.include?(val)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
return has_val
|
92
|
+
end
|
93
|
+
|
94
|
+
# Find if a point value is part of the domain
|
95
|
+
#
|
96
|
+
# @param val The value to test for inclusion
|
97
|
+
# @return [Boolean] if the value is included in the current domain
|
98
|
+
def include?(val)
|
99
|
+
super(val) && !exclude?(val)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Serialize the instance
|
103
|
+
#
|
104
|
+
# @see #to_s
|
105
|
+
# @return [String] string representation of the Domain object
|
106
|
+
def inspect
|
107
|
+
return "[#{super}] - #{@exclusions}" unless @exclusions.empty?
|
108
|
+
return super
|
109
|
+
end
|
110
|
+
|
111
|
+
# Compares Domain objects with Real numeric types (Fixnum, Bignum, Float)
|
112
|
+
#
|
113
|
+
# @param other the point to compare
|
114
|
+
# @return -1 if the value lies to the left of the domain
|
115
|
+
# 1 if the value lies to the right of domain
|
116
|
+
# 0 if the value is included in the range
|
117
|
+
# nil if the two are not deemed comparable
|
118
|
+
def <=>(other)
|
119
|
+
case other
|
120
|
+
when Fixnum, Bignum, Float
|
121
|
+
return -1 if other <= @start && !include?(other)
|
122
|
+
return 1 if other >= @finish && !include?(other)
|
123
|
+
return 0 if include?(other)
|
124
|
+
else
|
125
|
+
# Not comparable
|
126
|
+
return nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
alias :to_s :inspect
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'statistical/rng/uniform'
|
2
|
+
require 'statistical/rng/uniform_discrete'
|
3
|
+
require 'statistical/rng/two_point'
|
4
|
+
require 'statistical/rng/bernoulli'
|
5
|
+
require 'statistical/rng/exponential'
|
6
|
+
require 'statistical/rng/laplace'
|
7
|
+
require 'statistical/rng/weibull'
|
8
|
+
|
9
|
+
module Statistical
|
10
|
+
# Factory module to create instances of the various classes
|
11
|
+
# nested under itself
|
12
|
+
module Rng
|
13
|
+
# @private
|
14
|
+
# No need to document this
|
15
|
+
# Dynamically add constants when called
|
16
|
+
def self.const_missing(cname)
|
17
|
+
const_set(cname, make_classmap) if cname == :RNG_TYPES
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates a new instance of the give type if the type was found.
|
21
|
+
#
|
22
|
+
# @raise ArgumentError If the give type parameter was not found
|
23
|
+
def self.create(type = :uniform, *args, &block)
|
24
|
+
raise ArgumentError unless RNG_TYPES.include?(type)
|
25
|
+
RNG_TYPES[type].new(*args, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.make_classmap
|
29
|
+
rng_klasses = constants.select { |k| const_get(k).is_a?(Class)}
|
30
|
+
keylist = rng_klasses.map { |k| k.to_s.snakecase.to_sym}
|
31
|
+
klasses = rng_klasses.map { |k| const_get(k)}
|
32
|
+
return Hash[keylist.zip(klasses)].freeze
|
33
|
+
end
|
34
|
+
|
35
|
+
private_class_method :make_classmap
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'statistical/distribution/bernoulli'
|
2
|
+
require 'statistical/rng/two_point'
|
3
|
+
|
4
|
+
module Statistical
|
5
|
+
module Rng
|
6
|
+
# This class models a bernoulli trial using the TwoPoint distribution as
|
7
|
+
# as base distribution / trial system.
|
8
|
+
class Bernoulli < TwoPoint
|
9
|
+
# Companion RNG class for the Bernoulli distribution. Requires a
|
10
|
+
# distrbution object of the same type. Defaults to standard bernoulli
|
11
|
+
# if arguments are unspecified
|
12
|
+
#
|
13
|
+
# @author Vaibhav Yenamandra
|
14
|
+
#
|
15
|
+
# @attr_reader [Float] p Probability of success state
|
16
|
+
# @attr_reader [Float] q Probability of failure state
|
17
|
+
# @attr_reader [Hash] states Possible states that the RNG can take up
|
18
|
+
# @attr_reader [Random] generator The PRNG being used for randomness
|
19
|
+
def initialize(dobj = nil, seed = Random.new_seed)
|
20
|
+
unless dobj.nil? || dobj.is_a?(Statistical::Distribution::Bernoulli)
|
21
|
+
raise TypeError,
|
22
|
+
"Expected Distribution object or nil, found #{dobj.class}"
|
23
|
+
end
|
24
|
+
dobj = Statistical::Distribution::Bernoulli.new if dobj.nil?
|
25
|
+
super(dobj, seed)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'statistical/distribution/exponential'
|
2
|
+
require 'statistical/distribution/uniform'
|
3
|
+
require 'statistical/rng/uniform'
|
4
|
+
|
5
|
+
module Statistical
|
6
|
+
module Rng
|
7
|
+
# Companion RNG class for the exponential distribution. Requires a
|
8
|
+
# distrbution object of the corresponding distribution
|
9
|
+
#
|
10
|
+
# @author Vaibhav Yenamandra
|
11
|
+
#
|
12
|
+
# @attr_reader [Numeric] rate Rate parameter of the exponential distribution
|
13
|
+
# @attr_reader [Numeric] upper The upper bound of the uniform distribution
|
14
|
+
class Exponential
|
15
|
+
attr_reader :rate, :generator
|
16
|
+
|
17
|
+
def initialize(dobj = nil, seed = Random.new_seed)
|
18
|
+
unless dobj.nil? || dobj.is_a?(Statistical::Distribution::Exponential)
|
19
|
+
raise TypeError,
|
20
|
+
"Expected Distribution object or nil, found #{dobj.class}"
|
21
|
+
end
|
22
|
+
dobj = Statistical::Distribution::Exponential.new if dobj.nil?
|
23
|
+
@generator = Random.new(seed)
|
24
|
+
@rate = dobj.rate
|
25
|
+
@sdist = dobj
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return the next random number from the sequence
|
29
|
+
#
|
30
|
+
# @return [Numeric] next random number in the sequence
|
31
|
+
def rand
|
32
|
+
return @sdist.quantile(@generator.rand)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Compare against another rng to see if they are the same
|
36
|
+
#
|
37
|
+
# @return true if and only if, source distributions are the same and the
|
38
|
+
# prng has the same initial state
|
39
|
+
def eql?(other)
|
40
|
+
return other.is_a?(self.class) &&
|
41
|
+
@generator == other.generator &&
|
42
|
+
@rate = other.rate
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return the type of the source distribution
|
46
|
+
#
|
47
|
+
# @return source distribution's type
|
48
|
+
def type
|
49
|
+
@sdist.class
|
50
|
+
end
|
51
|
+
|
52
|
+
alias :== :eql?
|
53
|
+
private :eql?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'statistical/distribution/laplace'
|
2
|
+
|
3
|
+
module Statistical
|
4
|
+
module Rng
|
5
|
+
# Companion RNG class for the continuous uniform distribution. Requires a
|
6
|
+
# distrbution object of the corresponding distribution
|
7
|
+
#
|
8
|
+
# @author Vaibhav Yenamandra
|
9
|
+
#
|
10
|
+
# @attr_reader [Numeric] scale Scale parameter of the Laplace distribution.
|
11
|
+
# @attr_reader [Numeric] location Location parameter to determine where the
|
12
|
+
# distribution is centered / where the mean lies at
|
13
|
+
class Laplace
|
14
|
+
attr_reader :generator, :scale, :location
|
15
|
+
|
16
|
+
def initialize(dobj = nil, seed = Random.new_seed)
|
17
|
+
unless dobj.nil? || dobj.is_a?(Statistical::Distribution::Laplace)
|
18
|
+
raise TypeError,
|
19
|
+
"Expected Distribution object or nil, found #{dobj.class}"
|
20
|
+
end
|
21
|
+
dobj = Statistical::Distribution::Laplace.new if dobj.nil?
|
22
|
+
@generator = Random.new(seed)
|
23
|
+
@scale = dobj.scale
|
24
|
+
@location = dobj.location
|
25
|
+
@sdist = dobj
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return the next random number from the sequence
|
29
|
+
#
|
30
|
+
# @return next random number in the sequence
|
31
|
+
def rand
|
32
|
+
return @sdist.quantile(@generator.rand)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Compare against another rng to see if they are the same
|
36
|
+
#
|
37
|
+
# @return true if and only if, source distributions are the same and the
|
38
|
+
# prng has the same initial state
|
39
|
+
def eql?(other)
|
40
|
+
return other.is_a?(self.class) &&
|
41
|
+
other.generator == @generator &&
|
42
|
+
other.location == @location &&
|
43
|
+
other.scale == @scale
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return the type of the source distribution
|
47
|
+
#
|
48
|
+
# @return source distribution's type
|
49
|
+
def type
|
50
|
+
@sdist.class
|
51
|
+
end
|
52
|
+
|
53
|
+
alias :== :eql?
|
54
|
+
private :eql?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'statistical/distribution/two_point'
|
2
|
+
require 'statistical/distribution/uniform'
|
3
|
+
|
4
|
+
module Statistical
|
5
|
+
module Rng
|
6
|
+
# Random number generator to model the two point distribution used for
|
7
|
+
# working with problem where the state space has only two points with
|
8
|
+
# distinct probabilities
|
9
|
+
class TwoPoint
|
10
|
+
attr_reader :generator, :p, :q, :states
|
11
|
+
|
12
|
+
# Companion RNG class for the two-point distribution. Requires a
|
13
|
+
# distrbution object of the corresponding type. Defaults to the standard
|
14
|
+
# bernoulli if no arguments are given
|
15
|
+
#
|
16
|
+
# @author Vaibhav Yenamandra
|
17
|
+
#
|
18
|
+
# @attr_reader [Float] p Probability of success state
|
19
|
+
# @attr_reader [Float] q Probability of failure state
|
20
|
+
# @attr_reader [Hash] states Possible states that the RNG can take up
|
21
|
+
# @attr_reader [Object] generator The PRNG being used for randomness
|
22
|
+
def initialize(dobj = nil, seed = Random.new_seed)
|
23
|
+
unless dobj.nil? || dobj.is_a?(Statistical::Distribution::TwoPoint)
|
24
|
+
raise TypeError,
|
25
|
+
"Expected Distribution object or nil, found #{dobj.class}"
|
26
|
+
end
|
27
|
+
dobj = Statistical::Distribution::TwoPoint.new if dobj.nil?
|
28
|
+
@generator = Random.new(seed)
|
29
|
+
@sdist = dobj
|
30
|
+
@p = dobj.p
|
31
|
+
@q = dobj.q
|
32
|
+
@states = dobj.states
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return the next random number from the sequence following the given
|
36
|
+
# distribution
|
37
|
+
#
|
38
|
+
# @return next random number in the sequence
|
39
|
+
def rand
|
40
|
+
return @sdist.quantile(@generator.rand)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Compare against another rng to see if they are the same
|
44
|
+
#
|
45
|
+
# @return [Boolean] true if and only if, source distributions are the same
|
46
|
+
# and the prng has the same initial state
|
47
|
+
def eql?(other)
|
48
|
+
return other.is_a?(self.class) &&
|
49
|
+
@p = other.p &&
|
50
|
+
@states == other.states &&
|
51
|
+
@generator == other.generator
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return the type of the source distribution
|
55
|
+
#
|
56
|
+
# @return source distribution's type
|
57
|
+
def type
|
58
|
+
@sdist.class
|
59
|
+
end
|
60
|
+
|
61
|
+
# The support set over which the distribution exists
|
62
|
+
def support
|
63
|
+
@sdist.support
|
64
|
+
end
|
65
|
+
|
66
|
+
alias :== :eql?
|
67
|
+
private :eql?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'statistical/distribution/uniform'
|
2
|
+
|
3
|
+
module Statistical
|
4
|
+
module Rng
|
5
|
+
# Companion RNG class for the continuous uniform distribution. Requires a
|
6
|
+
# distrbution object of the corresponding distribution
|
7
|
+
#
|
8
|
+
# @author Vaibhav Yenamandra
|
9
|
+
#
|
10
|
+
# @attr_reader [Numeric] lower The lower bound of the uniform distribution.
|
11
|
+
# @attr_reader [Numeric] upper The upper bound of the uniform distribution.
|
12
|
+
class Uniform
|
13
|
+
attr_reader :lower, :upper, :generator
|
14
|
+
|
15
|
+
def initialize(dobj = nil, seed = Random.new_seed)
|
16
|
+
unless dobj.nil? || dobj.is_a?(Statistical::Distribution::Uniform)
|
17
|
+
raise TypeError,
|
18
|
+
"Expected Distribution object or nil, found #{dobj.class}"
|
19
|
+
end
|
20
|
+
dobj = Statistical::Distribution::Uniform.new if dobj.nil?
|
21
|
+
@generator = Random.new(seed)
|
22
|
+
@lower = dobj.lower
|
23
|
+
@upper = dobj.upper
|
24
|
+
@sdist = dobj
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return the next random number from the sequence
|
28
|
+
#
|
29
|
+
# @author Vaibhav Yenamandra
|
30
|
+
#
|
31
|
+
# @return next random number in the sequence
|
32
|
+
def rand
|
33
|
+
@lower + @generator.rand * (@upper - @lower)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Compare against another rng to see if they are the same
|
37
|
+
#
|
38
|
+
# @author Vaibhav Yenamandra
|
39
|
+
#
|
40
|
+
# @return [Boolean] true if and only if, source distributions are the
|
41
|
+
# same and the prng has the same initial state
|
42
|
+
def eql?(other)
|
43
|
+
return other.is_a?(self.class) &&
|
44
|
+
@lower == other.lower &&
|
45
|
+
@upper == other.upper &&
|
46
|
+
@generator == other.generator
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return the type of the source distribution
|
50
|
+
#
|
51
|
+
# @author Vaibhav Yenamandra
|
52
|
+
#
|
53
|
+
# @return [Statistical::Distribution::Uniform] source distribution's type
|
54
|
+
def type
|
55
|
+
@sdist.class
|
56
|
+
end
|
57
|
+
|
58
|
+
alias :== :eql?
|
59
|
+
private :eql?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|