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