erv 0.0.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -56
- data/Rakefile +8 -52
- data/TODO +2 -0
- data/erv.gemspec +5 -12
- data/lib/erv/constant_distribution.rb +13 -2
- data/lib/erv/distribution.rb +8 -8
- data/lib/erv/exponential_distribution.rb +16 -18
- data/lib/erv/gaussian_distribution.rb +22 -18
- data/lib/erv/mixture_distribution.rb +81 -0
- data/lib/erv/random_variable.rb +4 -3
- data/lib/erv/uniform_distribution.rb +8 -16
- data/lib/erv/version.rb +1 -1
- data/lib/erv.rb +0 -8
- data/{spec/erv/constant_distribution_spec.rb → test/erv/constant_distribution_test.rb} +4 -6
- data/test/erv/distribution_test.rb +17 -0
- data/test/erv/mixture_distribution_test.rb +55 -0
- data/test/erv/random_variable_test.rb +30 -0
- data/test/test_helper.rb +6 -0
- metadata +51 -30
- data/lib/erv/rng.rb +0 -29
- data/spec/spec_helper.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4df7f1fe74e08a93b0011dbce05db2862db6f742
|
4
|
+
data.tar.gz: bef0cb26a91b1353971c3d2f1de8cc071832b60b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85bc816dcaf649c8410138ddcb3496d449c701e9f847d842139c59c8e17a5f0e97b857ca362845c7365138afe0b524902bbdfd1eb060aa4151b03246582b1298
|
7
|
+
data.tar.gz: 4308c0e5862acb574b6adb771998e95459f19dc929749716f55118ac068246f319be8bcd3a801ee36d6aaa2a812a6c94ed08f41efcb7568773624c4a0a1c95b5
|
data/README.md
CHANGED
@@ -10,31 +10,13 @@ variables with a given probability distribution (gaussian, uniform, etc.) and
|
|
10
10
|
to sample from them. ruby-erv was built from code that I extracted out of
|
11
11
|
several scientific software I wrote for my research projects.
|
12
12
|
|
13
|
-
ruby-erv is designed to work on both YARV/MRI and JRuby. I have not tested it
|
14
|
-
on Rubinius yet.
|
15
|
-
|
16
13
|
|
17
14
|
## Installation
|
18
15
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
```ruby
|
23
|
-
gem 'erv', git: 'https://github.com/mtortonesi/ruby-erv.git'
|
24
|
-
```
|
25
|
-
|
26
|
-
in your Gemfile and run:
|
27
|
-
|
28
|
-
bundle install
|
29
|
-
|
30
|
-
If using JRuby, you'll also need to run:
|
31
|
-
|
32
|
-
rake get_latest_commons_math_snapshot
|
16
|
+
You can get the stable version of ruby-erv by installing the erv gem from
|
17
|
+
RubyGems:
|
33
18
|
|
34
|
-
|
35
|
-
[implementation notes below](#implementation-notes) for more information. (Note
|
36
|
-
that the rake-based installation of Apache Commons Math 3.3-SNAPSHOT requires
|
37
|
-
[nokogiri](http://nokogiri.org/).)
|
19
|
+
gem install erv
|
38
20
|
|
39
21
|
|
40
22
|
## Examples
|
@@ -45,49 +27,39 @@ variables using ruby-erv, and how to sample from them:
|
|
45
27
|
```ruby
|
46
28
|
require 'erv'
|
47
29
|
|
30
|
+
# Gaussian random variable with mean 10 and standard deviation 2
|
48
31
|
gaussian_rv = ERV::RandomVariable.new(distribution: :gaussian,
|
49
32
|
mean: 10, sd: 2)
|
50
33
|
s1 = gaussian_rv.sample
|
34
|
+
|
35
|
+
# Geometric random variable with probability of success 0.3
|
36
|
+
geometric_rv = ERV::RandomVariable.new(distribution: :geometric,
|
37
|
+
probability_of_success: 0.3)
|
38
|
+
s2 = geometric_rv.sample
|
39
|
+
```
|
40
|
+
|
41
|
+
Starting from version 0.2.0, ruby-erv also supports mixture models. Here is a
|
42
|
+
simple example of how to create a random variable with an exponential mixture
|
43
|
+
distribution:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
require 'erv'
|
47
|
+
|
48
|
+
emd = ERV::MixtureDistribution.new([ { distribution: :exponential, rate: 1.0, weight: 100.0 },
|
49
|
+
{ distribution: :exponential, rate: 2.0, weight: 200.0 },
|
50
|
+
{ distribution: :exponential, rate: 3.0, weight: 300.0 } ])
|
51
|
+
s3 = emd.sample
|
51
52
|
```
|
52
53
|
|
53
54
|
|
54
55
|
## Implementation notes
|
55
56
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
In JRuby, ruby-erv leverages the [Apache Commons
|
64
|
-
Math](http://commons.apache.org/proper/commons-math/) library to access the
|
65
|
-
same random number generation functions. Unfortunately, at the time of this
|
66
|
-
writing the Apache Commons Math maintainers have not shipped the 3.3 release
|
67
|
-
yet. Commons Math 3.3 should include support for geometric distribution (that I
|
68
|
-
need for several of my projects), thanks to [a
|
69
|
-
patch](https://issues.apache.org/jira/browse/MATH-973) that I submitted and
|
70
|
-
that was merged some time ago. So, for the moment ruby-env builds on top of the
|
71
|
-
3.3-SNAPSHOT version of Apache Commons Math.
|
72
|
-
|
73
|
-
To facilitate the installation of ruby-env under JRuby, I have decided to
|
74
|
-
bundle the jar archive of Apache Commons Math 3.3-SNAPSHOT in (the jars
|
75
|
-
directory of) the ruby-env gem package. This is a rather dirty but not uncommon
|
76
|
-
approach, as many other gems (including the awesome
|
77
|
-
[Nokogiri](https://github.com/sparklemotion/nokogiri/tree/master/lib)) bundle
|
78
|
-
external jar dependencies in their JRuby version. However, in future I might
|
79
|
-
decide to switch to a more powerful, Maven-based automated installation of
|
80
|
-
Apache Commons Math 3.3-SNAPSHOT. For instance, based on Christian Meier's
|
81
|
-
[jar-dependencies](https://github.com/mkristian/jar-dependencies).
|
82
|
-
|
83
|
-
|
84
|
-
## Acknowledgment
|
85
|
-
|
86
|
-
I would like to thank [Christian Meier](https://github.com/mkristian) for his
|
87
|
-
very valuable suggestions on how to package this gem for JRuby. If you're
|
88
|
-
interested in building serious applications for JRuby, I strongly recommend you
|
89
|
-
to check out Christian's [jbundler](https://github.com/mkristian/jbundler) and
|
90
|
-
[jar-dependencies](https://github.com/mkristian/jar-dependencies).
|
57
|
+
Starting from version 0.2.0, ruby-erv makes use of the standard (Pseudo) Random
|
58
|
+
Number Generator provided by Ruby VMs, which is a variant of the
|
59
|
+
Mersenne-Twister algorithm modified to have a period of 2<sup>19937</sup>-1.
|
60
|
+
The randomness provided by Ruby's PRNG, whose adoption significantly improved
|
61
|
+
ruby-erv's portability to different Ruby VMs (MRI, JRuby, etc.), should be more
|
62
|
+
than enough for most purposes.
|
91
63
|
|
92
64
|
|
93
65
|
## License
|
data/Rakefile
CHANGED
@@ -1,55 +1,11 @@
|
|
1
|
-
|
1
|
+
require 'bundler/gem_tasks'
|
2
2
|
|
3
|
-
|
4
|
-
require 'bundler/gem_helper'
|
5
|
-
|
6
|
-
require 'nokogiri'
|
7
|
-
require 'open-uri'
|
8
|
-
|
9
|
-
# setup absolute path for jars directory
|
10
|
-
JAR_DIR = File.expand_path(File.join(File.dirname(__FILE__), 'jars'))
|
11
|
-
|
12
|
-
directory JAR_DIR
|
13
|
-
|
14
|
-
desc 'Get latest snapshot of Apache Commons Math 3.3'
|
15
|
-
task :get_latest_commons_math_snapshot => [ JAR_DIR ] do
|
16
|
-
# base URL of the apache commons math 3 snapshot repository
|
17
|
-
BASE_URL = 'http://repository.apache.org/content/groups/snapshots/org/apache/commons/commons-math3/3.3-SNAPSHOT/'
|
18
|
-
|
19
|
-
# retrieve timestamp and build number of latest snapshot
|
20
|
-
puts 'Retrieving metadata information on latest Apache Commons Math 3.3 snapshot.'
|
21
|
-
doc = Nokogiri::HTML(open(BASE_URL + 'maven-metadata.xml'))
|
22
|
-
timestamp = doc.xpath('//timestamp/text()')
|
23
|
-
buildnumber = doc.xpath('//buildnumber/text()')
|
24
|
-
|
25
|
-
# get archive name of latest snapshot
|
26
|
-
filename = "commons-math3-3.3-#{timestamp}-#{buildnumber}.jar"
|
27
|
-
|
28
|
-
# get destination file name
|
29
|
-
jar_path = File.join(JAR_DIR, filename)
|
30
|
-
|
31
|
-
# don't download if file already exists
|
32
|
-
if File.exists?(jar_path)
|
33
|
-
puts 'Archive already present in jars directory.'
|
34
|
-
else
|
35
|
-
puts 'Retrieving latest Apache Commons Math 3.3 snapshot.'
|
36
|
-
file_url = BASE_URL + filename
|
37
|
-
File.write(jar_path, open(file_url).read)
|
38
|
-
end
|
39
|
-
|
40
|
-
# clean JAR_DIR of every file except filename
|
41
|
-
puts 'Removing obsolete archives from jars directory.'
|
42
|
-
FileUtils.rm(Dir.glob("#{JAR_DIR}/*").reject!{|file| file == jar_path })
|
43
|
-
end
|
44
|
-
|
45
|
-
desc "Build erv-#{ERV::VERSION}-java.gem into the pkg directory."
|
46
|
-
task :build => [ :get_latest_commons_math_snapshot ] do
|
47
|
-
Bundler::GemHelper.instance = Bundler::GemHelper.new
|
48
|
-
Bundler::GemHelper.instance.build_gem
|
49
|
-
end
|
50
|
-
|
51
|
-
else # RUBY_PLATFORM !~ /java/
|
52
|
-
|
53
|
-
require 'bundler/gem_tasks'
|
3
|
+
require 'rake/testtask'
|
54
4
|
|
5
|
+
Rake::TestTask.new do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.test_files = Dir.glob('test/**/*_test.rb').sort
|
8
|
+
t.verbose = true
|
55
9
|
end
|
10
|
+
|
11
|
+
task :default => :test
|
data/erv.gemspec
CHANGED
@@ -18,16 +18,9 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler', '~> 1.
|
22
|
-
spec.add_development_dependency 'rake'
|
23
|
-
spec.add_development_dependency '
|
24
|
-
|
25
|
-
|
26
|
-
# we need to release a JRuby-specific gem
|
27
|
-
spec.platform = 'java'
|
28
|
-
# include Apache Commons Math 3.3 jar archive
|
29
|
-
spec.files.concat(Dir['jars/*.jar'])
|
30
|
-
else
|
31
|
-
spec.add_dependency 'gsl-nmatrix'
|
32
|
-
end
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
22
|
+
spec.add_development_dependency 'rake', '~> 12.0'
|
23
|
+
spec.add_development_dependency 'minitest', '~> 5.10'
|
24
|
+
spec.add_development_dependency 'minitest-reporters', '~> 1.1'
|
25
|
+
spec.add_development_dependency 'minitest-spec-context', '~> 0.0.3'
|
33
26
|
end
|
@@ -7,8 +7,19 @@ module ERV
|
|
7
7
|
super(opts)
|
8
8
|
|
9
9
|
raise ArgumentError unless opts[:value]
|
10
|
-
val = opts[:value].to_f
|
11
|
-
|
10
|
+
@val = opts[:value].to_f
|
11
|
+
end
|
12
|
+
|
13
|
+
def mean
|
14
|
+
@val
|
15
|
+
end
|
16
|
+
|
17
|
+
def variance
|
18
|
+
0.0
|
19
|
+
end
|
20
|
+
|
21
|
+
def sample
|
22
|
+
@val
|
12
23
|
end
|
13
24
|
end
|
14
25
|
|
data/lib/erv/distribution.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require 'erv/rng'
|
2
|
-
|
3
1
|
module ERV
|
4
2
|
|
5
3
|
class Distribution
|
6
4
|
def initialize(opts)
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
5
|
+
# use provided RNG or create a new one
|
6
|
+
if opts[:rng]
|
7
|
+
@rng = opts[:rng]
|
8
|
+
elsif opts[:seed]
|
9
|
+
@rng = Random.new(opts[:seed])
|
10
|
+
else
|
11
|
+
@rng = Random.new
|
12
|
+
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -1,30 +1,28 @@
|
|
1
|
-
if RUBY_PLATFORM == 'java'
|
2
|
-
require 'java'
|
3
|
-
JExponentialDistribution = org.apache.commons.math3.distribution.ExponentialDistribution
|
4
|
-
end
|
5
|
-
|
6
1
|
require 'erv/distribution'
|
2
|
+
require 'erv/support/try'
|
7
3
|
|
8
4
|
|
9
5
|
module ERV
|
10
6
|
|
11
7
|
class ExponentialDistribution < Distribution
|
8
|
+
attr_reader :mean, :variance
|
9
|
+
|
12
10
|
def initialize(opts)
|
13
11
|
super(opts)
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
13
|
+
@rate = opts[:rate].try(:to_f)
|
14
|
+
raise ArgumentError unless @rate and @rate > 0.0
|
15
|
+
|
16
|
+
@mean = 1 / @rate
|
17
|
+
@variance = @mean ** 2
|
18
|
+
end
|
19
|
+
|
20
|
+
def sample
|
21
|
+
# starting from a random variable X ~ U(0,1), which is provided by the
|
22
|
+
# RNG, we can obtain a random variable Y ~ Exp(\lambda), with mean = 1 /
|
23
|
+
# \lambda, through the transformation: Y = - (1 / \lambda) ln X. see
|
24
|
+
# [GROESE11], section 4.2.3.
|
25
|
+
- @mean * Math.log(@rng.rand)
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
@@ -1,8 +1,3 @@
|
|
1
|
-
if RUBY_PLATFORM == 'java'
|
2
|
-
require 'java'
|
3
|
-
java_import org.apache.commons.math3.distribution.NormalDistribution
|
4
|
-
end
|
5
|
-
|
6
1
|
require 'erv/distribution'
|
7
2
|
|
8
3
|
|
@@ -13,19 +8,28 @@ module ERV
|
|
13
8
|
super(opts)
|
14
9
|
|
15
10
|
raise ArgumentError unless opts[:mean] and opts[:sd]
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
11
|
+
@mu = opts[:mean].to_f
|
12
|
+
@sigma = opts[:sd].to_f
|
13
|
+
end
|
14
|
+
|
15
|
+
def sample
|
16
|
+
# use box-muller algorithm (see [GROESE11], section 4.2.11, algorithm
|
17
|
+
# 4.47) to obtain x ~ N(0,1).
|
18
|
+
u_1 = @rng.rand
|
19
|
+
u_2 = @rng.rand
|
20
|
+
x = Math.sqrt(-2.0 * Math.log(u_1)) * Math.cos(2.0 * Math.PI * u_2)
|
21
|
+
|
22
|
+
# use location-scale transformation to obtain a N(\mu, \sigma^2)
|
23
|
+
# distribution. see [GROESE11], section 3.1.2.2.
|
24
|
+
@mu + @sigma * x
|
25
|
+
end
|
26
|
+
|
27
|
+
def mean
|
28
|
+
@mu
|
29
|
+
end
|
30
|
+
|
31
|
+
def variance
|
32
|
+
@sigma ** 2
|
29
33
|
end
|
30
34
|
end
|
31
35
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'erv/constant_distribution'
|
2
|
+
require 'erv/discrete_uniform_distribution'
|
3
|
+
require 'erv/exponential_distribution'
|
4
|
+
require 'erv/gamma_distribution'
|
5
|
+
require 'erv/gaussian_distribution'
|
6
|
+
require 'erv/general_pareto_distribution'
|
7
|
+
require 'erv/geometric_distribution'
|
8
|
+
require 'erv/uniform_distribution'
|
9
|
+
|
10
|
+
module ERV
|
11
|
+
|
12
|
+
class MixtureDistribution
|
13
|
+
def initialize(confs, opts={})
|
14
|
+
raise ArgumentError, "Please, provide at least 2 distributions!" unless confs.length >= 2
|
15
|
+
|
16
|
+
@mixture = []
|
17
|
+
@weight_sum = 0.0
|
18
|
+
while dist_conf = confs.shift
|
19
|
+
# get weight ...
|
20
|
+
weight = dist_conf.delete(:weight).to_f
|
21
|
+
|
22
|
+
# ... and keep track of it
|
23
|
+
@weight_sum += weight
|
24
|
+
|
25
|
+
# get distribution name
|
26
|
+
dist_name = dist_conf.delete(:distribution).to_s
|
27
|
+
|
28
|
+
# get class name that corresponds to the requested distribution
|
29
|
+
klass_name = dist_name.split('_').push('distribution').map(&:capitalize).join
|
30
|
+
|
31
|
+
# create distribution object
|
32
|
+
distribution = ERV.const_get(klass_name).new(dist_conf)
|
33
|
+
|
34
|
+
# add distribution to mixture
|
35
|
+
@mixture << { weight: weight, distribution: distribution }
|
36
|
+
end
|
37
|
+
|
38
|
+
seed = opts[:seed]
|
39
|
+
@mixture_sampler = (seed ? Random.new(seed) : Random.new)
|
40
|
+
end
|
41
|
+
|
42
|
+
def sample
|
43
|
+
x = @mixture_sampler.rand
|
44
|
+
|
45
|
+
# find index of distribution we are supposed to sample from
|
46
|
+
i = 0
|
47
|
+
while x > (@mixture[i][:weight] / @weight_sum)
|
48
|
+
x -= (@mixture[i][:weight] / @weight_sum)
|
49
|
+
i += 1
|
50
|
+
end
|
51
|
+
|
52
|
+
# return sample
|
53
|
+
@mixture[i][:distribution].sample
|
54
|
+
end
|
55
|
+
|
56
|
+
def mean
|
57
|
+
@mean ||= calculate_mean
|
58
|
+
end
|
59
|
+
|
60
|
+
def variance
|
61
|
+
@variance ||= calculate_variance
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def calculate_mean
|
67
|
+
@mixture.inject(0.0) do |s,x|
|
68
|
+
s += (x[:weight] / @weight_sum * x[:distribution].mean)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def calculate_variance
|
73
|
+
@mixture.inject(0.0) do |s,x|
|
74
|
+
s += (x[:weight] / @weight_sum *
|
75
|
+
((x[:distribution].mean - self.mean) ** 2 +
|
76
|
+
x[:distribution].variance))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
data/lib/erv/random_variable.rb
CHANGED
@@ -18,6 +18,7 @@ module ERV
|
|
18
18
|
|
19
19
|
# get class name that corresponds to the requested distribution
|
20
20
|
klass_name = dist_name.split('_').push('distribution').map(&:capitalize).join
|
21
|
+
|
21
22
|
# create distribution object
|
22
23
|
@dist = ERV.const_get(klass_name).new(opts)
|
23
24
|
end
|
@@ -30,9 +31,9 @@ module ERV
|
|
30
31
|
|
31
32
|
class SequentialRandomVariable
|
32
33
|
def initialize(args)
|
33
|
-
|
34
|
-
@most_recent =
|
35
|
-
@var = RandomVariable.new(args)
|
34
|
+
first = args[:first_value]
|
35
|
+
@most_recent = first.nil? ? 0.0 : first
|
36
|
+
@var = RandomVariable.new(args.reject{|k,v| k == :first_value })
|
36
37
|
end
|
37
38
|
|
38
39
|
def next
|
@@ -1,8 +1,3 @@
|
|
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
|
|
@@ -15,18 +10,15 @@ module ERV
|
|
15
10
|
|
16
11
|
raise ArgumentError unless opts[:max_value]
|
17
12
|
max = opts[:max_value].to_f
|
18
|
-
min = opts[:min_value].try(:to_f) || 0.0
|
13
|
+
@min = opts[:min_value].try(:to_f) || 0.0
|
14
|
+
@range = max - @min
|
15
|
+
end
|
19
16
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@func = Proc.new { d.sample }
|
26
|
-
else
|
27
|
-
# setup sampling function
|
28
|
-
@func = Proc.new { @rng.flat(min, max) }
|
29
|
-
end
|
17
|
+
def sample
|
18
|
+
# starting from a random variable X ~ U(0,1), which is provided by the
|
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.
|
21
|
+
@min + @range * @rng.rand
|
30
22
|
end
|
31
23
|
end
|
32
24
|
|
data/lib/erv/version.rb
CHANGED
data/lib/erv.rb
CHANGED
@@ -1,10 +1,2 @@
|
|
1
|
-
# Load Apache Commons Math 3.3 jar if we're using JRuby
|
2
|
-
if RUBY_PLATFORM =~ /java/
|
3
|
-
JARS_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'jars'))
|
4
|
-
Dir["#{JARS_DIR}/*.jar"].each do |jar|
|
5
|
-
$CLASSPATH << jar unless $CLASSPATH.include?(jar)
|
6
|
-
end
|
7
|
-
end
|
8
|
-
|
9
1
|
require 'erv/version'
|
10
2
|
require 'erv/random_variable'
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'test_helper'
|
2
2
|
|
3
3
|
require 'erv/constant_distribution'
|
4
4
|
|
@@ -7,21 +7,19 @@ describe ERV::ConstantDistribution do
|
|
7
7
|
it 'should require a reference value' do
|
8
8
|
lambda do
|
9
9
|
ERV::ConstantDistribution.new
|
10
|
-
end.
|
10
|
+
end.must_raise ArgumentError
|
11
11
|
end
|
12
12
|
|
13
13
|
context 'reference value' do
|
14
14
|
|
15
15
|
it 'should be accepted at initialization time' do
|
16
|
-
|
17
|
-
ERV::ConstantDistribution.new(value: 10)
|
18
|
-
end.should_not raise_error
|
16
|
+
ERV::ConstantDistribution.new(value: 10)
|
19
17
|
end
|
20
18
|
|
21
19
|
it 'should match the value returned by sampling' do
|
22
20
|
val = rand(100)
|
23
21
|
crv = ERV::ConstantDistribution.new(value: val)
|
24
|
-
crv.sample.
|
22
|
+
crv.sample.must_equal val
|
25
23
|
end
|
26
24
|
|
27
25
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'erv/distribution'
|
4
|
+
|
5
|
+
describe ERV::Distribution do
|
6
|
+
|
7
|
+
context 'when explicitly given an RNG' do
|
8
|
+
|
9
|
+
it 'should use the given RNG' do
|
10
|
+
rng = Random.new
|
11
|
+
d = ERV::Distribution.new(rng: rng)
|
12
|
+
d.instance_variable_get(:@rng).must_equal rng
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'erv/mixture_distribution'
|
4
|
+
|
5
|
+
describe ERV::MixtureDistribution do
|
6
|
+
it 'should require at least two distributions' do
|
7
|
+
lambda do
|
8
|
+
ERV::MixtureDistribution.new([ { distribution: :exponential, rate: 1.0, weight: 0.5 } ])
|
9
|
+
end.must_raise ArgumentError
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should keep track of distribution weights (for normalization)' do
|
13
|
+
# create a mixture distribution with unnormalized weights
|
14
|
+
uw_md = ERV::MixtureDistribution.new([ { distribution: :exponential, rate: 1.0, weight: 100.0 },
|
15
|
+
{ distribution: :exponential, rate: 2.0, weight: 200.0 },
|
16
|
+
{ distribution: :exponential, rate: 3.0, weight: 300.0 } ])
|
17
|
+
uw_md.instance_variable_get("@weight_sum").must_equal 600.0
|
18
|
+
end
|
19
|
+
|
20
|
+
let :md do
|
21
|
+
ERV::MixtureDistribution.new([ { distribution: :exponential, rate: 1.0, weight: 0.3 },
|
22
|
+
{ distribution: :exponential, rate: 2.0, weight: 0.2 },
|
23
|
+
{ distribution: :exponential, rate: 3.0, weight: 0.5 } ])
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'sampling' do
|
27
|
+
|
28
|
+
it 'should allow sampling from the mixture' do
|
29
|
+
md.sample
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'moments' do
|
35
|
+
|
36
|
+
let :expected_mean do
|
37
|
+
0.3 * 1/1.0 + 0.2 * 1/2.0 + 0.5 * 1/3.0
|
38
|
+
end
|
39
|
+
|
40
|
+
let :expected_variance do
|
41
|
+
0.3 * (1/1.0 - expected_mean) ** 2 + (1/1.0) ** 2 +
|
42
|
+
0.2 * (1/2.0 - expected_mean) ** 2 + (1/2.0) ** 2 +
|
43
|
+
0.5 * (1/3.0 - expected_mean) ** 2 + (1/3.0) ** 2
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should correctly calculate the mean of the mixture' do
|
47
|
+
md.mean.must_equal expected_mean
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should correctly calculate the variance of the mixture' do
|
51
|
+
md.mean.must_equal expected_mean
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'erv/random_variable'
|
4
|
+
|
5
|
+
describe ERV::SequentialRandomVariable do
|
6
|
+
|
7
|
+
it 'should require the :first_value parameter' do
|
8
|
+
lambda do
|
9
|
+
ERV::SequentialRandomVariable.new
|
10
|
+
end.must_raise ArgumentError
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should consider starting value' do
|
14
|
+
first = 1.0
|
15
|
+
srv = ERV::SequentialRandomVariable.new(first_value: first, distribution: :exponential, rate: 2.0)
|
16
|
+
srv.next.must_be :>, first
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should consider previous sample' do
|
20
|
+
first = 1.0
|
21
|
+
srv = ERV::SequentialRandomVariable.new(first_value: first, distribution: :exponential, rate: 2.0)
|
22
|
+
previous_sample = srv.next
|
23
|
+
10.times do
|
24
|
+
new_sample = srv.next
|
25
|
+
new_sample.must_be :>, previous_sample
|
26
|
+
previous_sample = new_sample
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,72 +1,86 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: erv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mauro Tortonesi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ~>
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.14'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - ~>
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.14'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
33
|
+
version: '12.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
40
|
+
version: '12.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '5.10'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '5.10'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: minitest-reporters
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
62
|
-
type: :
|
61
|
+
version: '1.1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.1'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-spec-context
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.0.3
|
76
|
+
type: :development
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
|
-
- -
|
80
|
+
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
69
|
-
description: erv-0.0
|
82
|
+
version: 0.0.3
|
83
|
+
description: erv-0.2.0
|
70
84
|
email:
|
71
85
|
- mauro.tortonesi@unife.it
|
72
86
|
executables: []
|
@@ -77,6 +91,7 @@ files:
|
|
77
91
|
- LICENSE
|
78
92
|
- README.md
|
79
93
|
- Rakefile
|
94
|
+
- TODO
|
80
95
|
- erv.gemspec
|
81
96
|
- lib/erv.rb
|
82
97
|
- lib/erv/constant_distribution.rb
|
@@ -87,13 +102,16 @@ files:
|
|
87
102
|
- lib/erv/gaussian_distribution.rb
|
88
103
|
- lib/erv/general_pareto_distribution.rb
|
89
104
|
- lib/erv/geometric_distribution.rb
|
105
|
+
- lib/erv/mixture_distribution.rb
|
90
106
|
- lib/erv/random_variable.rb
|
91
|
-
- lib/erv/rng.rb
|
92
107
|
- lib/erv/support/try.rb
|
93
108
|
- lib/erv/uniform_distribution.rb
|
94
109
|
- lib/erv/version.rb
|
95
|
-
-
|
96
|
-
-
|
110
|
+
- test/erv/constant_distribution_test.rb
|
111
|
+
- test/erv/distribution_test.rb
|
112
|
+
- test/erv/mixture_distribution_test.rb
|
113
|
+
- test/erv/random_variable_test.rb
|
114
|
+
- test/test_helper.rb
|
97
115
|
homepage: https://github.com/mtortonesi/ruby-erv
|
98
116
|
licenses:
|
99
117
|
- MIT
|
@@ -104,20 +122,23 @@ require_paths:
|
|
104
122
|
- lib
|
105
123
|
required_ruby_version: !ruby/object:Gem::Requirement
|
106
124
|
requirements:
|
107
|
-
- -
|
125
|
+
- - ">="
|
108
126
|
- !ruby/object:Gem::Version
|
109
127
|
version: '0'
|
110
128
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
129
|
requirements:
|
112
|
-
- -
|
130
|
+
- - ">="
|
113
131
|
- !ruby/object:Gem::Version
|
114
132
|
version: '0'
|
115
133
|
requirements: []
|
116
134
|
rubyforge_project:
|
117
|
-
rubygems_version: 2.
|
135
|
+
rubygems_version: 2.6.8
|
118
136
|
signing_key:
|
119
137
|
specification_version: 4
|
120
138
|
summary: Easy/elegant Ruby library providing support for random variable generation
|
121
139
|
test_files:
|
122
|
-
-
|
123
|
-
-
|
140
|
+
- test/erv/constant_distribution_test.rb
|
141
|
+
- test/erv/distribution_test.rb
|
142
|
+
- test/erv/mixture_distribution_test.rb
|
143
|
+
- test/erv/random_variable_test.rb
|
144
|
+
- test/test_helper.rb
|
data/lib/erv/rng.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
if RUBY_PLATFORM == 'java'
|
2
|
-
require 'java'
|
3
|
-
java_import org.apache.commons.math3.random.MersenneTwister
|
4
|
-
else
|
5
|
-
require 'gsl'
|
6
|
-
end
|
7
|
-
|
8
|
-
|
9
|
-
module ERV
|
10
|
-
|
11
|
-
class RNG
|
12
|
-
def self.make_rng(seed = nil)
|
13
|
-
# if not explicitly provided, seed is taken from the (lower quality)
|
14
|
-
# pseudo-random Kernel::rand generator
|
15
|
-
if RUBY_PLATFORM == 'java'
|
16
|
-
# this is a somewhat ugly workaround in order to avoid ambibuities in
|
17
|
-
# the constructor method that will be called (we basically explicit the
|
18
|
-
# signature of the constructor that we want to use)
|
19
|
-
constructor = org.apache.commons.math3.random.MersenneTwister.java_class.constructor(Java::long)
|
20
|
-
rng = constructor.new_instance(seed || Kernel::rand(2**31 - 1))
|
21
|
-
else
|
22
|
-
rng = seed ?
|
23
|
-
GSL::Rng.alloc(GSL::Rng::MT19937, seed) :
|
24
|
-
GSL::Rng.alloc(GSL::Rng::MT19937, Kernel::rand(2**31 - 1))
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
-
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
-
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
-
# loaded once.
|
5
|
-
|
6
|
-
# Required to setup classpath for Apache Commons Math 3.3-SNAPSHOT in JRuby
|
7
|
-
require 'erv'
|
8
|
-
|
9
|
-
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
10
|
-
RSpec.configure do |config|
|
11
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
|
-
config.run_all_when_everything_filtered = true
|
13
|
-
config.filter_run :focus
|
14
|
-
|
15
|
-
# Run specs in random order to surface order dependencies. If you find an
|
16
|
-
# order dependency and want to debug it, you can fix the order by providing
|
17
|
-
# the seed, which is printed after each run.
|
18
|
-
# --seed 1234
|
19
|
-
config.order = 'random'
|
20
|
-
end
|