more_math 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +2 -0
- data/LICENSE +18 -0
- data/README +20 -0
- data/Rakefile +84 -0
- data/VERSION +1 -0
- data/install.rb +19 -0
- data/lib/more_math/cantor_pairing_funtion.rb +49 -0
- data/lib/more_math/constants/functions_constants.rb +29 -0
- data/lib/more_math/continued_fraction.rb +140 -0
- data/lib/more_math/distributions.rb +134 -0
- data/lib/more_math/exceptions.rb +6 -0
- data/lib/more_math/functions.rb +151 -0
- data/lib/more_math/histogram.rb +62 -0
- data/lib/more_math/linear_regression.rb +78 -0
- data/lib/more_math/newton_bisection.rb +66 -0
- data/lib/more_math/sequence.rb +337 -0
- data/lib/more_math/version.rb +8 -0
- data/lib/more_math.rb +9 -0
- data/make_doc.rb +5 -0
- data/tests/test_analysis.rb +321 -0
- data/tests/test_cantor_pairing_function.rb +23 -0
- data/tests/test_continued_fraction.rb +40 -0
- data/tests/test_distribution.rb +69 -0
- data/tests/test_functions.rb +33 -0
- data/tests/test_histogram.rb +29 -0
- data/tests/test_newton_bisection.rb +28 -0
- metadata +108 -0
data/CHANGES
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Copyright (c) 2010 Florian Frank
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE X
|
16
|
+
CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
17
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
18
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
= MoreMath - More mathematics in Ruby
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
Ruby library that contains various mathematical functions and algorithms.
|
6
|
+
|
7
|
+
== Download
|
8
|
+
|
9
|
+
The homepage of this library is located at
|
10
|
+
|
11
|
+
* http://flori.github.com/more_math
|
12
|
+
|
13
|
+
== Author
|
14
|
+
|
15
|
+
Florian Frank mailto:flori@ping.de
|
16
|
+
|
17
|
+
== License
|
18
|
+
|
19
|
+
This software is licensed under the X11 (or MIT) license:
|
20
|
+
http://www.xfree86.org/3.3.6/COPYRIGHT2.html#3
|
data/Rakefile
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
begin
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rbconfig'
|
7
|
+
include Config
|
8
|
+
|
9
|
+
PKG_NAME = 'more_math'
|
10
|
+
PKG_VERSION = File.read('VERSION').chomp
|
11
|
+
PKG_FILES = FileList['**/*'].exclude(/^(doc|CVS|pkg|coverage)/)
|
12
|
+
CLEAN.include 'coverage', 'doc'
|
13
|
+
CLOBBER.include FileList['data/*']
|
14
|
+
|
15
|
+
desc "Run unit tests"
|
16
|
+
task :test do
|
17
|
+
sh %{RUBYOPT="-Ilib $RUBYOPT" testrb tests/*.rb}
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Testing library with coverage"
|
21
|
+
task :coverage do
|
22
|
+
sh 'rcov -x tests -Ilib tests/*.rb'
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Installing library"
|
26
|
+
task :install do
|
27
|
+
ruby 'install.rb'
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Creating documentation"
|
31
|
+
task :doc do
|
32
|
+
ruby 'make_doc.rb'
|
33
|
+
end
|
34
|
+
|
35
|
+
if defined? Gem
|
36
|
+
spec = Gem::Specification.new do |s|
|
37
|
+
s.name = PKG_NAME
|
38
|
+
s.version = PKG_VERSION
|
39
|
+
s.summary = "Library that provides more mathematics."
|
40
|
+
s.description = "Library that provides more mathematical functions/algorithms than standard Ruby."
|
41
|
+
s.add_dependency('dslkit', '~> 0.2')
|
42
|
+
|
43
|
+
s.files = PKG_FILES
|
44
|
+
|
45
|
+
s.require_path = 'lib'
|
46
|
+
|
47
|
+
s.has_rdoc = true
|
48
|
+
s.rdoc_options <<
|
49
|
+
'--title' << 'MoreMath -- More Math in Ruby' << '--main' << 'README'
|
50
|
+
s.extra_rdoc_files << 'README'
|
51
|
+
s.test_files = Dir['tests/*.rb']
|
52
|
+
|
53
|
+
s.author = "Florian Frank"
|
54
|
+
s.email = "flori@ping.de"
|
55
|
+
s.homepage = "http://flori.github.com/#{PKG_NAME}"
|
56
|
+
s.rubyforge_project = PKG_NAME
|
57
|
+
end
|
58
|
+
|
59
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
60
|
+
pkg.need_tar = true
|
61
|
+
pkg.package_files += PKG_FILES
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc m = "Writing version information for #{PKG_VERSION}"
|
66
|
+
task :version do
|
67
|
+
puts m
|
68
|
+
File.open(File.join('lib', PKG_NAME, 'version.rb'), 'w') do |v|
|
69
|
+
v.puts <<EOT
|
70
|
+
module MoreMath
|
71
|
+
# MoreMath version
|
72
|
+
VERSION = '#{PKG_VERSION}'
|
73
|
+
VERSION_ARRAY = VERSION.split(/\\./).map { |x| x.to_i } # :nodoc:
|
74
|
+
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
|
75
|
+
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
|
76
|
+
VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
|
77
|
+
end
|
78
|
+
EOT
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
task :default => [ :version, :test ]
|
83
|
+
|
84
|
+
task :release => [ :clobber, :version, :package ]
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/install.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
require 'fileutils'
|
5
|
+
include FileUtils::Verbose
|
6
|
+
|
7
|
+
include Config
|
8
|
+
|
9
|
+
file = 'lib/more_math.rb'
|
10
|
+
libdir = CONFIG["sitelibdir"]
|
11
|
+
install(file, libdir, :mode => 0755)
|
12
|
+
mkdir_p subdir = File.join(libdir, 'more_math')
|
13
|
+
for f in Dir['lib/more_math/*.rb']
|
14
|
+
install(f, subdir)
|
15
|
+
end
|
16
|
+
mkdir_p subdir = File.join(libdir, 'more_math', 'constants')
|
17
|
+
for f in Dir['lib/more_math/constants/*.rb']
|
18
|
+
install(f, subdir)
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'more_math'
|
2
|
+
|
3
|
+
module MoreMath
|
4
|
+
module CantorPairingFunction
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def cantor_pairing(*xs)
|
9
|
+
if xs.size == 1 and xs.first.respond_to?(:to_ary)
|
10
|
+
xs = xs.first.to_ary
|
11
|
+
end
|
12
|
+
case xs.size
|
13
|
+
when 0, 1
|
14
|
+
raise ArgumentError, "at least two arguments are required"
|
15
|
+
when 2
|
16
|
+
x, y, = *xs
|
17
|
+
(x + y) * (x + y + 1) / 2 + y
|
18
|
+
else
|
19
|
+
cantor_pairing(cantor_pairing(*xs[0..1]), *xs[2..-1])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.cantor_pairing_inv_f(z)
|
24
|
+
z * (z + 1) / 2
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.cantor_pairing_inv_q(z)
|
28
|
+
v = 0
|
29
|
+
while cantor_pairing_inv_f(v) <= z
|
30
|
+
v += 1
|
31
|
+
end
|
32
|
+
v - 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def cantor_pairing_inv(c, n = 2)
|
36
|
+
raise ArgumentError, "n is required to be >= 2" unless n >= 2
|
37
|
+
result = []
|
38
|
+
begin
|
39
|
+
q = CantorPairingFunction.cantor_pairing_inv_q(c)
|
40
|
+
y = c - CantorPairingFunction.cantor_pairing_inv_f(q)
|
41
|
+
x = q - y
|
42
|
+
result.unshift y
|
43
|
+
c = x
|
44
|
+
n -= 1
|
45
|
+
end until n <= 1
|
46
|
+
result.unshift x
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MoreMath
|
2
|
+
module Constants
|
3
|
+
module FunctionsConstants
|
4
|
+
LANCZOS_COEFFICIENTS = [
|
5
|
+
0.99999999999999709182,
|
6
|
+
57.156235665862923517,
|
7
|
+
-59.597960355475491248,
|
8
|
+
14.136097974741747174,
|
9
|
+
-0.49191381609762019978,
|
10
|
+
0.33994649984811888699e-4,
|
11
|
+
0.46523628927048575665e-4,
|
12
|
+
-0.98374475304879564677e-4,
|
13
|
+
0.15808870322491248884e-3,
|
14
|
+
-0.21026444172410488319e-3,
|
15
|
+
0.21743961811521264320e-3,
|
16
|
+
-0.16431810653676389022e-3,
|
17
|
+
0.84418223983852743293e-4,
|
18
|
+
-0.26190838401581408670e-4,
|
19
|
+
0.36899182659531622704e-5,
|
20
|
+
]
|
21
|
+
|
22
|
+
HALF_LOG_2_PI = 0.5 * Math.log(2 * Math::PI)
|
23
|
+
|
24
|
+
ERF_A = -8 * (Math::PI - 3) / (3 * Math::PI * (Math::PI - 4))
|
25
|
+
|
26
|
+
ROOT2 = Math.sqrt(2)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'more_math'
|
2
|
+
|
3
|
+
module MoreMath
|
4
|
+
# This class implements a continued fraction of the form:
|
5
|
+
#
|
6
|
+
# b_1
|
7
|
+
# a_0 + -------------------------
|
8
|
+
# b_2
|
9
|
+
# a_1 + --------------------
|
10
|
+
# b_3
|
11
|
+
# a_2 + ---------------
|
12
|
+
# b_4
|
13
|
+
# a_3 + ----------
|
14
|
+
# b_5
|
15
|
+
# a_4 + -----
|
16
|
+
# ...
|
17
|
+
#
|
18
|
+
class ContinuedFraction
|
19
|
+
# Creates a continued fraction instance. With the defaults for_a { 1 } and
|
20
|
+
# for_b { 1 } it approximates the golden ration phi if evaluated.
|
21
|
+
def initialize
|
22
|
+
@a = proc { 1.0 }
|
23
|
+
@b = proc { 1.0 }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates a ContinuedFraction instances and passes its arguments to a call
|
27
|
+
# to for_a.
|
28
|
+
def self.for_a(arg = nil, &block)
|
29
|
+
new.for_a(arg, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Creates a ContinuedFraction instances and passes its arguments to a call
|
33
|
+
# to for_b.
|
34
|
+
def self.for_b(arg = nil, &block)
|
35
|
+
new.for_b(arg, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# This method either takes a block or an argument +arg+. The argument +arg+
|
39
|
+
# has to respond to an integer index n >= 0 and return the value a_n. The
|
40
|
+
# block has to return the value for a_n when +n+ is passed as the first
|
41
|
+
# argument to the block. If a_n is dependent on an +x+ value (see the call
|
42
|
+
# method) the +x+ will be the second argument of the block.
|
43
|
+
def for_a(arg = nil, &block)
|
44
|
+
if arg and !block
|
45
|
+
@a = arg
|
46
|
+
elsif block and !arg
|
47
|
+
@a = block
|
48
|
+
else
|
49
|
+
raise ArgumentError, "exactly one argument or one block required"
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# This method either takes a block or an argument +arg+. The argument +arg+
|
55
|
+
# has to respond to an integer index n >= 1 and return the value b_n. The
|
56
|
+
# block has to return the value for b_n when +n+ is passed as the first
|
57
|
+
# argument to the block. If b_n is dependent on an +x+ value (see the call
|
58
|
+
# method) the +x+ will be the second argument of the block.
|
59
|
+
def for_b(arg = nil, &block)
|
60
|
+
if arg and !block
|
61
|
+
@b = arg
|
62
|
+
elsif block and !arg
|
63
|
+
@b = block
|
64
|
+
else
|
65
|
+
raise ArgumentError, "exactly one argument or one block required"
|
66
|
+
end
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the value for a_n or a_n(x).
|
71
|
+
def a(n, x = nil)
|
72
|
+
result = if x
|
73
|
+
@a[n, x]
|
74
|
+
else
|
75
|
+
@a[n]
|
76
|
+
end and result.to_f
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the value for b_n or b_n(x).
|
80
|
+
def b(n, x = nil)
|
81
|
+
result = if x
|
82
|
+
@b[n, x]
|
83
|
+
else
|
84
|
+
@b[n]
|
85
|
+
end and result.to_f
|
86
|
+
end
|
87
|
+
|
88
|
+
# Evaluates the continued fraction for the value +x+ (if any) with the
|
89
|
+
# accuracy +epsilon+ and +max_iterations+ as the maximum number of
|
90
|
+
# iterations using the Wallis-method with scaling.
|
91
|
+
def call(x = nil, epsilon = 1E-16, max_iterations = 1 << 31)
|
92
|
+
c_0, c_1 = 1.0, a(0, x)
|
93
|
+
c_1 == nil and return 0 / 0.0
|
94
|
+
d_0, d_1 = 0.0, 1.0
|
95
|
+
result = c_1 / d_1
|
96
|
+
n = 0
|
97
|
+
error = 1 / 0.0
|
98
|
+
$DEBUG and warn "n=%u, a=%f, b=nil, c=%f, d=%f result=%f, error=nil" %
|
99
|
+
[ n, c_1, c_1, d_1, result ]
|
100
|
+
while n < max_iterations and error > epsilon
|
101
|
+
n += 1
|
102
|
+
a_n, b_n = a(n, x), b(n, x)
|
103
|
+
a_n and b_n or break
|
104
|
+
c_2 = a_n * c_1 + b_n * c_0
|
105
|
+
d_2 = a_n * d_1 + b_n * d_0
|
106
|
+
if c_2.infinite? or d_2.infinite?
|
107
|
+
if a_n != 0
|
108
|
+
c_2 = c_1 + (b_n / a_n * c_0)
|
109
|
+
d_2 = d_1 + (b_n / a_n * d_0)
|
110
|
+
elsif b_n != 0
|
111
|
+
c_2 = (a_n / b_n * c_1) + c_0
|
112
|
+
d_2 = (a_n / b_n * d_1) + d_0
|
113
|
+
else
|
114
|
+
raise Errno::ERANGE
|
115
|
+
end
|
116
|
+
end
|
117
|
+
r = c_2 / d_2
|
118
|
+
error = (r / result - 1).abs
|
119
|
+
|
120
|
+
result = r
|
121
|
+
|
122
|
+
$DEBUG and warn "n=%u, a=%f, b=%f, c=%f, d=%f, result=%f, error=%.16f" %
|
123
|
+
[ n, a_n, b_n, c_1, d_1, result, error ]
|
124
|
+
|
125
|
+
c_0, c_1 = c_1, c_2
|
126
|
+
d_0, d_1 = d_1, d_2
|
127
|
+
end
|
128
|
+
n >= max_iterations and raise Errno::ERANGE
|
129
|
+
result
|
130
|
+
end
|
131
|
+
|
132
|
+
alias [] call
|
133
|
+
|
134
|
+
# Returns this continued fraction as a Proc object which takes the same
|
135
|
+
# arguments like its call method does.
|
136
|
+
def to_proc
|
137
|
+
proc { |*a| call(*a) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'more_math'
|
2
|
+
require 'more_math/functions'
|
3
|
+
require 'more_math/constants/functions_constants'
|
4
|
+
|
5
|
+
module MoreMath
|
6
|
+
# This class is used to compute the Normal Distribution.
|
7
|
+
class NormalDistribution
|
8
|
+
include Functions
|
9
|
+
include Constants::FunctionsConstants
|
10
|
+
|
11
|
+
# Creates a NormalDistribution instance for the values +mu+ and +sigma+.
|
12
|
+
def initialize(mu = 0.0, sigma = 1.0)
|
13
|
+
@mu, @sigma = mu.to_f, sigma.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :mu
|
17
|
+
|
18
|
+
attr_reader :sigma
|
19
|
+
|
20
|
+
# Returns the cumulative probability (p-value) of the NormalDistribution
|
21
|
+
# for the value +x+.
|
22
|
+
def probability(x)
|
23
|
+
0.5 * (1 + erf((x - @mu) / (@sigma * ROOT2)))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the inverse cumulative probability value of the
|
27
|
+
# NormalDistribution for the probability +p+.
|
28
|
+
def inverse_probability(p)
|
29
|
+
case
|
30
|
+
when p <= 0
|
31
|
+
-1 / 0.0
|
32
|
+
when p >= 1
|
33
|
+
1 / 0.0
|
34
|
+
when p == 0.5 # This is a bit sloppy, maybe improve this later.
|
35
|
+
@mu
|
36
|
+
else
|
37
|
+
begin
|
38
|
+
NewtonBisection.new { |x| probability(x) - p }.solve(nil, 1_000_000)
|
39
|
+
rescue
|
40
|
+
0 / 0.0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
STD_NORMAL_DISTRIBUTION = NormalDistribution.new
|
47
|
+
|
48
|
+
# This class is used to compute the Chi-Square Distribution.
|
49
|
+
class ChiSquareDistribution
|
50
|
+
include Functions
|
51
|
+
|
52
|
+
# Creates a ChiSquareDistribution for +df+ degrees of freedom.
|
53
|
+
def initialize(df)
|
54
|
+
@df = df
|
55
|
+
@df_half = @df / 2.0
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :df
|
59
|
+
|
60
|
+
# Returns the cumulative probability (p-value) of the ChiSquareDistribution
|
61
|
+
# for the value +x+.
|
62
|
+
def probability(x)
|
63
|
+
if x < 0
|
64
|
+
0.0
|
65
|
+
else
|
66
|
+
gammaP_regularized(x / 2, @df_half)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns the inverse cumulative probability value of the
|
71
|
+
# NormalDistribution for the probability +p+.
|
72
|
+
def inverse_probability(p)
|
73
|
+
case
|
74
|
+
when p <= 0, p >= 1
|
75
|
+
0.0
|
76
|
+
else
|
77
|
+
begin
|
78
|
+
bisect = NewtonBisection.new { |x| probability(x) - p }
|
79
|
+
range = bisect.bracket 0.5..10
|
80
|
+
bisect.solve(range, 1_000_000)
|
81
|
+
rescue
|
82
|
+
0 / 0.0
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# This class is used to compute the T-Distribution.
|
89
|
+
class TDistribution
|
90
|
+
include Functions
|
91
|
+
|
92
|
+
# Returns a TDistribution instance for the degrees of freedom +df+.
|
93
|
+
def initialize(df)
|
94
|
+
@df = df
|
95
|
+
end
|
96
|
+
|
97
|
+
# Degrees of freedom.
|
98
|
+
attr_reader :df
|
99
|
+
|
100
|
+
# Returns the cumulative probability (p-value) of the TDistribution for the
|
101
|
+
# t-value +x+.
|
102
|
+
def probability(x)
|
103
|
+
if x == 0
|
104
|
+
0.5
|
105
|
+
else
|
106
|
+
t = beta_regularized(@df / (@df + x ** 2.0), 0.5 * @df, 0.5)
|
107
|
+
if x < 0.0
|
108
|
+
0.5 * t
|
109
|
+
else
|
110
|
+
1 - 0.5 * t
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Returns the inverse cumulative probability (t-value) of the TDistribution
|
116
|
+
# for the probability +p+.
|
117
|
+
def inverse_probability(p)
|
118
|
+
case
|
119
|
+
when p <= 0
|
120
|
+
-1 / 0.0
|
121
|
+
when p >= 1
|
122
|
+
1 / 0.0
|
123
|
+
else
|
124
|
+
begin
|
125
|
+
bisect = NewtonBisection.new { |x| probability(x) - p }
|
126
|
+
range = bisect.bracket(-10..10)
|
127
|
+
bisect.solve(range, 1_000_000)
|
128
|
+
rescue
|
129
|
+
0 / 0.0
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'more_math'
|
2
|
+
|
3
|
+
module MoreMath
|
4
|
+
module Functions
|
5
|
+
module_function
|
6
|
+
|
7
|
+
include Math
|
8
|
+
extend Math
|
9
|
+
|
10
|
+
# Returns the natural logarithm of Euler gamma function value for +x+ using
|
11
|
+
# the Lanczos approximation.
|
12
|
+
if Math.respond_to?(:lgamma)
|
13
|
+
def log_gamma(x)
|
14
|
+
lgamma(x).first
|
15
|
+
end
|
16
|
+
else
|
17
|
+
def log_gamma(x)
|
18
|
+
if x.nan? || x <= 0
|
19
|
+
0 / 0.0
|
20
|
+
else
|
21
|
+
sum = 0.0
|
22
|
+
lc = Constants::FunctionsConstants::LANCZOS_COEFFICIENTS
|
23
|
+
half_log_2_pi = Constants::FunctionsConstants::HALF_LOG_2_PI
|
24
|
+
(lc.size - 1).downto(1) do |i|
|
25
|
+
sum += lc[i] / (x + i)
|
26
|
+
end
|
27
|
+
sum += lc[0]
|
28
|
+
tmp = x + 607.0 / 128 + 0.5
|
29
|
+
(x + 0.5) * log(tmp) - tmp + half_log_2_pi + log(sum / x)
|
30
|
+
end
|
31
|
+
rescue Errno::ERANGE, Errno::EDOM
|
32
|
+
0 / 0.0
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the natural logarithm of the beta function value for +(a, b)+.
|
37
|
+
def log_beta(a, b)
|
38
|
+
log_gamma(a) + log_gamma(b) - log_gamma(a + b)
|
39
|
+
rescue Errno::ERANGE, Errno::EDOM
|
40
|
+
0 / 0.0
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return an approximation value of Euler's regularized beta function for
|
44
|
+
# +x+, +a+, and +b+ with an error <= +epsilon+, but only iterate
|
45
|
+
# +max_iterations+-times.
|
46
|
+
def beta_regularized(x, a, b, epsilon = 1E-16, max_iterations = 1 << 16)
|
47
|
+
x, a, b = x.to_f, a.to_f, b.to_f
|
48
|
+
case
|
49
|
+
when a.nan? || b.nan? || x.nan? || a <= 0 || b <= 0 || x < 0 || x > 1
|
50
|
+
0 / 0.0
|
51
|
+
when x > (a + 1) / (a + b + 2)
|
52
|
+
1 - beta_regularized(1 - x, b, a, epsilon, max_iterations)
|
53
|
+
else
|
54
|
+
fraction = ContinuedFraction.for_b do |n, x|
|
55
|
+
if n % 2 == 0
|
56
|
+
m = n / 2.0
|
57
|
+
(m * (b - m) * x) / ((a + (2 * m) - 1) * (a + (2 * m)))
|
58
|
+
else
|
59
|
+
m = (n - 1) / 2.0
|
60
|
+
-((a + m) * (a + b + m) * x) / ((a + 2 * m) * (a + 2 * m + 1))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
exp(a * log(x) + b * log(1.0 - x) - log(a) - log_beta(a, b)) /
|
64
|
+
fraction[x, epsilon, max_iterations]
|
65
|
+
end
|
66
|
+
rescue Errno::ERANGE, Errno::EDOM
|
67
|
+
0 / 0.0
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return an approximation of the regularized gammaP function for +x+ and
|
71
|
+
# +a+ with an error of <= +epsilon+, but only iterate
|
72
|
+
# +max_iterations+-times.
|
73
|
+
def gammaP_regularized(x, a, epsilon = 1E-16, max_iterations = 1 << 16)
|
74
|
+
x, a = x.to_f, a.to_f
|
75
|
+
case
|
76
|
+
when a.nan? || x.nan? || a <= 0 || x < 0
|
77
|
+
0 / 0.0
|
78
|
+
when x == 0
|
79
|
+
0.0
|
80
|
+
when 1 <= a && a < x
|
81
|
+
1 - gammaQ_regularized(x, a, epsilon, max_iterations)
|
82
|
+
else
|
83
|
+
n = 0
|
84
|
+
an = 1 / a
|
85
|
+
sum = an
|
86
|
+
while an.abs > epsilon && n < max_iterations
|
87
|
+
n += 1
|
88
|
+
an *= x / (a + n)
|
89
|
+
sum += an
|
90
|
+
end
|
91
|
+
if n >= max_iterations
|
92
|
+
raise Errno::ERANGE
|
93
|
+
else
|
94
|
+
exp(-x + a * log(x) - log_gamma(a)) * sum
|
95
|
+
end
|
96
|
+
end
|
97
|
+
rescue Errno::ERANGE, Errno::EDOM
|
98
|
+
0 / 0.0
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return an approximation of the regularized gammaQ function for +x+ and
|
102
|
+
# +a+ with an error of <= +epsilon+, but only iterate
|
103
|
+
# +max_iterations+-times.
|
104
|
+
def gammaQ_regularized(x, a, epsilon = 1E-16, max_iterations = 1 << 16)
|
105
|
+
x, a = x.to_f, a.to_f
|
106
|
+
case
|
107
|
+
when a.nan? || x.nan? || a <= 0 || x < 0
|
108
|
+
0 / 0.0
|
109
|
+
when x == 0
|
110
|
+
1.0
|
111
|
+
when a > x || a < 1
|
112
|
+
1 - gammaP_regularized(x, a, epsilon, max_iterations)
|
113
|
+
else
|
114
|
+
fraction = ContinuedFraction.for_a do |n, x|
|
115
|
+
(2 * n + 1) - a + x
|
116
|
+
end.for_b do |n, x|
|
117
|
+
n * (a - n)
|
118
|
+
end
|
119
|
+
exp(-x + a * log(x) - log_gamma(a)) *
|
120
|
+
fraction[x, epsilon, max_iterations] ** -1
|
121
|
+
end
|
122
|
+
rescue Errno::ERANGE, Errno::EDOM
|
123
|
+
0 / 0.0
|
124
|
+
end
|
125
|
+
|
126
|
+
unless Math.respond_to?(:erf)
|
127
|
+
# Returns an approximate value for the error function's value for +x+.
|
128
|
+
def erf(x)
|
129
|
+
erf_a = MoreMath::Constants::FunctionsConstants::ERF_A
|
130
|
+
r = sqrt(1 - exp(-x ** 2 * (4 / Math::PI + erf_a * x ** 2) / (1 + erf_a * x ** 2)))
|
131
|
+
x < 0 ? -r : r
|
132
|
+
end
|
133
|
+
else
|
134
|
+
def erf(x)
|
135
|
+
Math.erf(x)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns Cantor's tuple function for the tuple +*xs+ (the size must be at
|
140
|
+
# least 2).
|
141
|
+
def cantor_pairing(*xs)
|
142
|
+
CantorPairingFunction.cantor_pairing(*xs)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns the inverse of Cantor's tuple function for the value +c+. +n+ is
|
146
|
+
# the length of the tuple (defaults to 2, a pair).
|
147
|
+
def cantor_pairing_inv(c, n = 2)
|
148
|
+
CantorPairingFunction.cantor_pairing_inv(c, n)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|