statistical 0.1.0
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 +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
|