polynomials 0.2.0 → 0.3.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/Gemfile +5 -3
- data/Gemfile.lock +8 -2
- data/README.markdown +73 -0
- data/Rakefile +8 -8
- data/VERSION +1 -1
- data/examples/plot_only_mutual_data.rb +47 -0
- data/lib/polynomials/exceptions.rb +3 -0
- data/lib/polynomials/formulas.rb +64 -0
- data/lib/polynomials/point.rb +71 -0
- data/lib/polynomials/polynomial.rb +144 -0
- data/lib/polynomials/term.rb +48 -0
- data/lib/polynomials.rb +13 -139
- data/polynomials.gemspec +21 -11
- data/test/{math_test.rb → formulas_test.rb} +16 -14
- data/test/point_test.rb +51 -0
- data/test/polynomial_test.rb +17 -11
- data/test/term_test.rb +8 -1
- data/test/test_helper.rb +19 -1
- metadata +41 -11
- data/README.rdoc +0 -19
- data/lib/core_ext/math.rb +0 -60
- data/lib/term.rb +0 -47
- data/test/helper.rb +0 -18
data/Gemfile
CHANGED
@@ -8,8 +8,10 @@ source "http://rubygems.org"
|
|
8
8
|
group :development do
|
9
9
|
gem "bundler", "~> 1.0.0"
|
10
10
|
gem "jeweler", "~> 1.6.0"
|
11
|
-
gem "
|
12
|
-
end
|
13
|
-
group :development, :tests do
|
11
|
+
gem "simplecov", ">= 0"
|
14
12
|
gem 'awesome_print'
|
15
13
|
end
|
14
|
+
group :production, :development, :tests do
|
15
|
+
gem "activesupport"
|
16
|
+
gem 'i18n'
|
17
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -1,20 +1,26 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
activesupport (3.0.7)
|
4
5
|
awesome_print (0.3.2)
|
5
6
|
git (1.2.5)
|
7
|
+
i18n (0.5.0)
|
6
8
|
jeweler (1.6.0)
|
7
9
|
bundler (~> 1.0.0)
|
8
10
|
git (>= 1.2.5)
|
9
11
|
rake
|
10
12
|
rake (0.8.7)
|
11
|
-
|
13
|
+
simplecov (0.4.2)
|
14
|
+
simplecov-html (~> 0.4.4)
|
15
|
+
simplecov-html (0.4.5)
|
12
16
|
|
13
17
|
PLATFORMS
|
14
18
|
ruby
|
15
19
|
|
16
20
|
DEPENDENCIES
|
21
|
+
activesupport
|
17
22
|
awesome_print
|
18
23
|
bundler (~> 1.0.0)
|
24
|
+
i18n
|
19
25
|
jeweler (~> 1.6.0)
|
20
|
-
|
26
|
+
simplecov
|
data/README.markdown
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# Polynomials
|
2
|
+
|
3
|
+
## Usage
|
4
|
+
|
5
|
+
<pre><code>
|
6
|
+
require 'polynomials'
|
7
|
+
require 'gnuplot'
|
8
|
+
include Polynomials
|
9
|
+
|
10
|
+
|
11
|
+
polynomial = Polynomial.parse(ARGV[0])
|
12
|
+
|
13
|
+
points = polynomial.roots | polynomial.local_extrema | polynomial.inflection_points
|
14
|
+
|
15
|
+
max_x = points.max_by(&:x).x
|
16
|
+
min_x = points.min_by(&:x).x
|
17
|
+
|
18
|
+
start = (min_x - 0.1)
|
19
|
+
stop = (max_x + 0.1)
|
20
|
+
|
21
|
+
x = start
|
22
|
+
data_x,data_y = [],[]
|
23
|
+
while x < stop
|
24
|
+
data_x << x
|
25
|
+
data_y << polynomial.(x)
|
26
|
+
x += 0.005
|
27
|
+
end
|
28
|
+
|
29
|
+
Gnuplot.open do |gp|
|
30
|
+
Gnuplot::Plot.new(gp) do |plot|
|
31
|
+
plot.xrange "[#{start}:#{stop}]"
|
32
|
+
plot.title "Polynomial"
|
33
|
+
plot.ylabel "f(x)"
|
34
|
+
plot.xlabel "x"
|
35
|
+
plot.grid
|
36
|
+
plot.data << Gnuplot::DataSet.new( [data_x,data_y] ) do |ds|
|
37
|
+
ds.with = "lines"
|
38
|
+
ds.linewidth = 0.1
|
39
|
+
ds.title = "f(x) = #{polynomial}"
|
40
|
+
end
|
41
|
+
|
42
|
+
[:inflection_point,:root, :maximum, :minimum].each do |kind_of_point|
|
43
|
+
selected_points = points.select(&:"#{kind_of_point}?")
|
44
|
+
plot.data << Gnuplot::DataSet.new([selected_points.map(&:x), selected_points.map(&:y)]) do |ds|
|
45
|
+
ds.with = "points"
|
46
|
+
ds.linewidth = 2
|
47
|
+
ds.title = kind_of_point.to_s.pluralize.titleize
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
</code></pre>
|
53
|
+
|
54
|
+
### Bash Command
|
55
|
+
<pre><code> % ruby examples/plot_only_mutual_data.rb '20.432 x^4 - 17.75 x^3 - 20x^2 + 5 x -50'</code></pre>
|
56
|
+
|
57
|
+
### Output:
|
58
|
+
<img src="https://img.skitch.com/20110515-cwgqkt2mnbeit3c9y5djsp1cjh.jpg">
|
59
|
+
|
60
|
+
## Contributing to polynomials
|
61
|
+
|
62
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
63
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
64
|
+
* Fork the project
|
65
|
+
* Commit and push until you are happy with your contribution
|
66
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
67
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
68
|
+
|
69
|
+
## Copyright
|
70
|
+
|
71
|
+
Copyright (c) 2011 Manuel Korfmann. See LICENSE.txt for
|
72
|
+
further details.
|
73
|
+
|
data/Rakefile
CHANGED
@@ -35,14 +35,14 @@ Rake::TestTask.new(:test) do |test|
|
|
35
35
|
test.verbose = true
|
36
36
|
end
|
37
37
|
|
38
|
-
require 'rcov/rcovtask'
|
39
|
-
Rcov::RcovTask.new do |test|
|
40
|
-
test.libs << 'test'
|
41
|
-
test.pattern = 'test/**/*_test.rb'
|
42
|
-
test.verbose = true
|
43
|
-
test.rcov_opts << '--exclude "gems/*"'
|
44
|
-
end
|
45
|
-
|
38
|
+
#require 'rcov/rcovtask'
|
39
|
+
#Rcov::RcovTask.new do |test|
|
40
|
+
# test.libs << 'test'
|
41
|
+
# test.pattern = 'test/**/*_test.rb'
|
42
|
+
# test.verbose = true
|
43
|
+
# test.rcov_opts << '--exclude "gems/*"'
|
44
|
+
#end
|
45
|
+
#
|
46
46
|
task :default => :test
|
47
47
|
|
48
48
|
require 'rake/rdoctask'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'polynomials'
|
2
|
+
require 'gnuplot'
|
3
|
+
include Polynomials
|
4
|
+
|
5
|
+
|
6
|
+
polynomial = Polynomial.parse(ARGV[0])
|
7
|
+
|
8
|
+
points = polynomial.roots | polynomial.local_extrema | polynomial.inflection_points
|
9
|
+
|
10
|
+
max_x = points.max_by(&:x).x
|
11
|
+
min_x = points.min_by(&:x).x
|
12
|
+
|
13
|
+
start = (min_x - 0.1)
|
14
|
+
stop = (max_x + 0.1)
|
15
|
+
|
16
|
+
x = start
|
17
|
+
data_x,data_y = [],[]
|
18
|
+
while x < stop
|
19
|
+
data_x << x
|
20
|
+
data_y << polynomial.(x)
|
21
|
+
x += 0.005
|
22
|
+
end
|
23
|
+
|
24
|
+
Gnuplot.open do |gp|
|
25
|
+
Gnuplot::Plot.new(gp) do |plot|
|
26
|
+
plot.xrange "[#{start}:#{stop}]"
|
27
|
+
plot.title "Polynomial"
|
28
|
+
plot.ylabel "f(x)"
|
29
|
+
plot.xlabel "x"
|
30
|
+
plot.grid
|
31
|
+
plot.data << Gnuplot::DataSet.new( [data_x,data_y] ) do |ds|
|
32
|
+
ds.with = "lines"
|
33
|
+
ds.linewidth = 0.1
|
34
|
+
ds.title = "f(x) = #{polynomial}"
|
35
|
+
end
|
36
|
+
|
37
|
+
[:inflection_point,:root, :maximum, :minimum].each do |kind_of_point|
|
38
|
+
selected_points = points.select(&:"#{kind_of_point}?")
|
39
|
+
plot.data << Gnuplot::DataSet.new([selected_points.map(&:x), selected_points.map(&:y)]) do |ds|
|
40
|
+
ds.with = "points"
|
41
|
+
ds.linewidth = 2
|
42
|
+
ds.title = kind_of_point.to_s.pluralize.titleize
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'mathn'
|
2
|
+
module Polynomials
|
3
|
+
module Formulas
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def roots_of_cubic_function(a,b,c,d,with_complex = false)
|
7
|
+
f = ((3*c/a) - (b**2/a**2))/3
|
8
|
+
g = (((2*b**3)/a**3) - (9*b*c/a**2) + (27*d/a))/27
|
9
|
+
h = (g**2/4) + (f**3/27)
|
10
|
+
if f == 0 && g == 0 && h == 0
|
11
|
+
f = (3*c/a) - (b**2/a**2)
|
12
|
+
g = (2*b**3/a**3) - (9*b*c/a**2) + (27*d/a)
|
13
|
+
h = (g**2/4) + (f**3/27)
|
14
|
+
[-Math.cbrt(d/a)]
|
15
|
+
elsif h <= 0
|
16
|
+
i = Math.sqrt((g**2/4) - h)
|
17
|
+
j = Math.cbrt(i)
|
18
|
+
k = Math.acos(-(g / (2*i)))
|
19
|
+
l = j * -1
|
20
|
+
m = Math.cos(k/3)
|
21
|
+
n = Math.sqrt(3) * Math.sin(k/3)
|
22
|
+
p = (b/(3*a))*-1
|
23
|
+
[2*j*Math.cos(k/3) - (b/(3*a)), l*(m+n)+p, l*(m-n)+p]
|
24
|
+
else
|
25
|
+
f = (((3*c)/a) - (b**2/a**2))/3
|
26
|
+
g = ((2*b**3/a**3) - (9*b*c/a**2) + ((27*d)/a))/27
|
27
|
+
h = (g**2/4) + (f**3/27)
|
28
|
+
r = -(g/2) + Math.sqrt(h)
|
29
|
+
s = Math.cbrt(r)
|
30
|
+
t = -(g/2) - Math.sqrt(h)
|
31
|
+
u = Math.cbrt(t)
|
32
|
+
roots = [(s + u) - (b/(3*a))]
|
33
|
+
roots |= [1,-1].map do |algebraic_sign|
|
34
|
+
Complex((-(s + u)/2) - (b/(3*a)),algebraic_sign * (s-u)*Math.sqrt(3)/2)
|
35
|
+
end if with_complex
|
36
|
+
roots
|
37
|
+
end.map { |n| n.respond_to?(:round) ? n.round(10) : n }.to_set
|
38
|
+
end
|
39
|
+
|
40
|
+
def roots_of_quadratic_function(a,b,c)
|
41
|
+
p = b/a
|
42
|
+
q = c/a
|
43
|
+
denom = (p/2.0)**2 - q
|
44
|
+
return Set[] if denom < 0
|
45
|
+
root = Math.sqrt(denom)
|
46
|
+
Set.new([:+,:-].map{ |operator| (-(p/2.0)).send(operator, root)}.map { |n| n.round(10) })
|
47
|
+
end
|
48
|
+
|
49
|
+
def roots_of_quartic_function(*args)
|
50
|
+
a,b,c,d,e = args.map { |n| n/args.first }
|
51
|
+
f = c - (3*b**2/8)
|
52
|
+
g = d + (b**3/8) - (b*c/2)
|
53
|
+
h = e - (3*b**4/256) + (b**2 * c/16) - ( b*d/4)
|
54
|
+
roots = self.roots_of_cubic_function(1,(f/2),(((f**2)-(4*h))/16),-(g**2)/64,true)
|
55
|
+
combs = roots.reject{ |n| n == 0}.combination(2)
|
56
|
+
only_complex = combs.select { |comb| comb.all? { |n| n.is_a? Complex }}
|
57
|
+
p,q = (only_complex.empty? ? combs.first : only_complex.first).map { |n| Math.sqrt(n) }
|
58
|
+
r = (-g/(8*p*q))
|
59
|
+
s = b/(4*a)
|
60
|
+
roots = Set.new([p + q + r -s, p - q - r -s, -p + q - r -s, -p - q + r -s].map { |n| n.is_a?(Complex) ? (n.real if n.imaginary == 0) : n }.compact )
|
61
|
+
roots.map { |n| n.respond_to?(:round) ? n.round(10) : n }.to_set
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Polynomials
|
2
|
+
class Point
|
3
|
+
include Comparable
|
4
|
+
attr_accessor :x, :y
|
5
|
+
|
6
|
+
def self.inherited(subclass)
|
7
|
+
self.class_eval do
|
8
|
+
define_method :"#{subclass.name.demodulize.underscore}?" do
|
9
|
+
self.is_a? subclass
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
def initialize(x,y)
|
14
|
+
@x,@y = x,y
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"(#{self.x.inspect},#{self.y.inspect})"
|
19
|
+
end
|
20
|
+
|
21
|
+
def <=>(other)
|
22
|
+
if self.x < other.x
|
23
|
+
-1
|
24
|
+
elsif self.x > other.x
|
25
|
+
1
|
26
|
+
elsif self.x == other.x && self.y == other.y
|
27
|
+
0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def eql?(other)
|
32
|
+
self.x == other.x &&
|
33
|
+
self.y == other.y &&
|
34
|
+
other.class == self.class
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Extremum < Point
|
39
|
+
attr_accessor :kind_of_extremum
|
40
|
+
|
41
|
+
[:maximum,:minimum].each do |extremum|
|
42
|
+
self.superclass.class_eval do
|
43
|
+
define_method :"#{extremum}?" do
|
44
|
+
self.extremum? && self.kind_of_extremum == extremum
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(*args,kind_of_extremum)
|
50
|
+
@kind_of_extremum = kind_of_extremum
|
51
|
+
super(*args)
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s
|
55
|
+
super + kind_of_extremum.to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
def eql?(other)
|
59
|
+
super && self.kind_of_extremum == other.kind_of_extremum
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Root < Point
|
64
|
+
def initialize(x)
|
65
|
+
super(x,0)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class InflectionPoint < Point
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Polynomials
|
2
|
+
class Polynomial
|
3
|
+
|
4
|
+
MinMaxMapping = { 1.0 => :maximum, -1.0 => :minimum }
|
5
|
+
AfterExtremaCurvatureMapping = { maximum: :right, minimum: :left }
|
6
|
+
NegPosMinMaxExtremumMapping = {[1.0,-1.0] => :maximum,[-1.0,1.0] => :minimum}
|
7
|
+
|
8
|
+
attr_accessor :terms
|
9
|
+
|
10
|
+
def self.parse(string)
|
11
|
+
polynomial = self.new
|
12
|
+
string.split(/(?=[-+])/).each do |term|
|
13
|
+
parsed = Term.parse(term)
|
14
|
+
polynomial.terms[parsed.exponent].coefficient += parsed.coefficient
|
15
|
+
end
|
16
|
+
return polynomial
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(*args)
|
20
|
+
self.terms = Hash.new { |hash, key| hash[key] = Term.new(key) }
|
21
|
+
unless args.empty?
|
22
|
+
args.reverse.each.with_index do |coefficient,exponent|
|
23
|
+
self.terms[exponent].coefficient += coefficient
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def calculate(x)
|
29
|
+
self.terms.values.inject(0.0) do |acc,t|
|
30
|
+
acc + (t.coefficient.to_f * (x**t.exponent))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias call calculate
|
34
|
+
|
35
|
+
def derivative
|
36
|
+
new_function = self.alter do |nf, term|
|
37
|
+
nf.terms[term.exponent - 1].coefficient += term.exponent * term.coefficient
|
38
|
+
end
|
39
|
+
new_function.terms.reject! { |_,t| t.coefficient == 0 }
|
40
|
+
return new_function
|
41
|
+
end
|
42
|
+
|
43
|
+
def roots
|
44
|
+
if !terms.empty? and terms.keys.none?(&:zero?)
|
45
|
+
self.alter { |nf, term| nf.terms[term.exponent-self.lt.exponent].coefficient = term.coefficient }.roots << Root.new(0.0)
|
46
|
+
else
|
47
|
+
case self.degree
|
48
|
+
when 1
|
49
|
+
Set[-self.terms[0].coefficient / self.terms[1].coefficient]
|
50
|
+
when 2
|
51
|
+
Formulas.roots_of_quadratic_function(*coefficients_till(2))
|
52
|
+
when 3
|
53
|
+
Formulas.roots_of_cubic_function(*coefficients_till(3))
|
54
|
+
when 4
|
55
|
+
Formulas.roots_of_quartic_function(*coefficients_till(4))
|
56
|
+
else
|
57
|
+
Set[]
|
58
|
+
end.map { |x| Root.new(x) }.to_set
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def local_extrema
|
63
|
+
derivative = self.derivative
|
64
|
+
possible_extrema = derivative.roots.sort.map(&:x)
|
65
|
+
return Set[] if possible_extrema.empty?
|
66
|
+
samples = ([possible_extrema.first - 1] + possible_extrema.sort + [possible_extrema.last + 1]).each_cons(2).map do |before,after|
|
67
|
+
(before + after)/2
|
68
|
+
end
|
69
|
+
possible_extrema.zip(samples.each_cons(2)).inject(Set[]) do |set,(pe,(after,before))|
|
70
|
+
yafter = derivative.calculate(after)
|
71
|
+
ybefore = derivative.calculate(before)
|
72
|
+
kind_of_extremum = NegPosMinMaxExtremumMapping[[yafter/yafter.abs,ybefore/ybefore.abs]]
|
73
|
+
set << Extremum.new(pe,self.calculate(pe), kind_of_extremum) if kind_of_extremum
|
74
|
+
set
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def curvature_behaviour
|
79
|
+
hash = Hash.new {|h,k|h[k]=Set.new}
|
80
|
+
return hash if self.degree > 5
|
81
|
+
self.derivative.extrema.sort.each_cons(2).group_by do |start,_|
|
82
|
+
kind_of_curvature = AfterExtremaCurvatureMapping[start.kind_of_extremum]
|
83
|
+
end.tap { |h| h.values.each { |v| v.map! { |s,e| Range.new(s.x,e.x) }}}
|
84
|
+
end
|
85
|
+
|
86
|
+
def degree
|
87
|
+
self.terms.keys.max || 0
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
terms.sort_by { |_,t| -t.exponent }.inject("") do |string,(_,term)|
|
92
|
+
string << term.to_s unless term.coefficient.zero? && !term.exponent.zero?
|
93
|
+
string
|
94
|
+
end.strip.sub(/\A\+\s/, '')
|
95
|
+
end
|
96
|
+
|
97
|
+
def ==(other)
|
98
|
+
delete_zeros = proc{ |_,t| t.coefficient.zero? }
|
99
|
+
self.terms.delete_if(&delete_zeros) == other.terms.delete_if(&delete_zeros)
|
100
|
+
end
|
101
|
+
|
102
|
+
def extrema
|
103
|
+
extrema = local_extrema
|
104
|
+
unless self.degree == 0
|
105
|
+
a = self.gt.coefficient
|
106
|
+
max_or_min = (self.degree.even? ? [1.0,1.0] : [-1.0,1.0]).map { |n| (n * a)/a.abs }
|
107
|
+
max_or_min.zip([-Infinity,Infinity]).each do |kind_of_extremum, x|
|
108
|
+
extrema << Extremum.new(x,nil,MinMaxMapping[kind_of_extremum])
|
109
|
+
end
|
110
|
+
end
|
111
|
+
return extrema
|
112
|
+
end
|
113
|
+
|
114
|
+
def inflection_points
|
115
|
+
self.derivative.local_extrema.map { |p| InflectionPoint.new(p.x,self.calculate(p.x)) }.to_set
|
116
|
+
end
|
117
|
+
|
118
|
+
def gt
|
119
|
+
self.terms[self.degree]
|
120
|
+
end
|
121
|
+
|
122
|
+
def lt
|
123
|
+
self.terms[self.terms.min_by{ |_,t| t.exponent}.first]
|
124
|
+
end
|
125
|
+
|
126
|
+
def coefficients_till(n)
|
127
|
+
coefficients = []
|
128
|
+
(0..n).each do |e|
|
129
|
+
coefficients << self.terms[e].coefficient
|
130
|
+
end
|
131
|
+
return coefficients.reverse
|
132
|
+
end
|
133
|
+
private :coefficients_till
|
134
|
+
|
135
|
+
def alter
|
136
|
+
new_function = self.class.new
|
137
|
+
self.terms.values.each do |term|
|
138
|
+
yield new_function, term
|
139
|
+
end
|
140
|
+
return new_function
|
141
|
+
end
|
142
|
+
protected :alter
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Polynomials
|
2
|
+
class Term
|
3
|
+
attr_accessor :coefficient, :exponent
|
4
|
+
|
5
|
+
def initialize(exponent = 0, coefficient = 0)
|
6
|
+
@coefficient, @exponent = coefficient, exponent
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.parse(string)
|
10
|
+
term = self.new
|
11
|
+
float = /\d+(?:\.\d+)?/
|
12
|
+
integer = /\d+/
|
13
|
+
polynomial_regex = /
|
14
|
+
\A\s*
|
15
|
+
(?<algebraic_sign>[-+]?)
|
16
|
+
\s*
|
17
|
+
(?<coefficient>#{float})?
|
18
|
+
\s*
|
19
|
+
(?<power>x(?:\^(?<exponent>#{integer}))?)?
|
20
|
+
\s*\Z
|
21
|
+
/x
|
22
|
+
|
23
|
+
raw_data = string.match(polynomial_regex)
|
24
|
+
raise NotParsableError unless raw_data
|
25
|
+
term.coefficient = (raw_data[:algebraic_sign] + ( raw_data[:coefficient] || "1")).to_f
|
26
|
+
term.exponent = raw_data[:power] ? (raw_data[:exponent] || 1).to_i : 0
|
27
|
+
term
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
pretty_coeffiecent = coefficient.denominator == 1 ? coefficient.abs.to_i : coefficient.abs
|
32
|
+
algebraic_sign = coefficient < 0 ? '-' : '+'
|
33
|
+
power = "x#{"^#{exponent}" unless exponent == 1}"
|
34
|
+
"#{algebraic_sign} #{pretty_coeffiecent}#{ power unless exponent.zero?} "
|
35
|
+
end
|
36
|
+
|
37
|
+
def dup
|
38
|
+
duplicate = self.class.new
|
39
|
+
duplicate.coefficient = self.coefficient
|
40
|
+
duplicate.exponent = self.exponent
|
41
|
+
duplicate
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
self.coefficient == other.coefficient && self.exponent == other.exponent
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/polynomials.rb
CHANGED
@@ -1,145 +1,19 @@
|
|
1
|
-
|
2
|
-
require_relative 'term'
|
3
|
-
require_relative 'core_ext/math'
|
4
|
-
|
5
|
-
class Polynomial
|
6
|
-
|
7
|
-
MinMaxMapping = { 1.0 => :max, -1.0 => :min }
|
8
|
-
AfterextremaCurvatureMapping = { max: :right, min: :left }
|
9
|
-
NegPosMinMaxExtremumMapping = {[1.0,-1.0] => :max,[-1.0,1.0] => :min}
|
10
|
-
|
11
|
-
attr_accessor :terms
|
12
|
-
|
13
|
-
def self.parse(string)
|
14
|
-
polynomial = self.new
|
15
|
-
string.split(/(?=[-+])/).each do |term|
|
16
|
-
parsed = Term.parse(term)
|
17
|
-
polynomial.terms[parsed.exponent].coefficient += parsed.coefficient
|
18
|
-
end
|
19
|
-
return polynomial
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(*args)
|
23
|
-
self.terms = Hash.new { |hash, key| hash[key] = Term.new(key) }
|
24
|
-
unless args.empty?
|
25
|
-
args.reverse.each.with_index do |coefficient,exponent|
|
26
|
-
self.terms[exponent].coefficient += coefficient
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def calculate(x)
|
32
|
-
self.terms.values.inject(0.0) do |acc,t|
|
33
|
-
acc + (t.coefficient.to_f * (x**t.exponent))
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def derivative
|
38
|
-
new_function = self.alter do |nf, term|
|
39
|
-
nf.terms[term.exponent - 1].coefficient += term.exponent * term.coefficient
|
40
|
-
end
|
41
|
-
new_function.terms.reject! { |_,t| t.coefficient == 0 }
|
42
|
-
return new_function
|
43
|
-
end
|
44
|
-
|
45
|
-
def roots
|
46
|
-
if !terms.empty? and terms.keys.none?(&:zero?)
|
47
|
-
self.alter { |nf, term| nf.terms[term.exponent-self.lt.exponent].coefficient = term.coefficient }.roots << 0.0
|
48
|
-
else
|
49
|
-
case self.degree
|
50
|
-
when 1
|
51
|
-
Set[-self.terms[0].coefficient / self.terms[1].coefficient]
|
52
|
-
when 2
|
53
|
-
Math.roots_of_quadratic_function(*coefficients_till(2))
|
54
|
-
when 3
|
55
|
-
Math.roots_of_cubic_function(*coefficients_till(3))
|
56
|
-
when 4
|
57
|
-
Math.roots_of_quartic_function(*coefficients_till(4))
|
58
|
-
else
|
59
|
-
Set[]
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
1
|
+
$LOAD_PATH << File.join(File.dirname(File.expand_path(__FILE__)), 'polynomials')
|
63
2
|
|
64
|
-
|
65
|
-
derivative = self.derivative
|
66
|
-
max_min_extremum = Hash.new { |hash,key| hash[key] = Set.new }
|
67
|
-
possible_extrema = derivative.roots.sort
|
68
|
-
unless possible_extrema.empty?
|
69
|
-
samples = ([possible_extrema.first - 1] + possible_extrema.sort + [possible_extrema.last + 1]).each_cons(2).map do |before,after|
|
70
|
-
(before + after)/2
|
71
|
-
end
|
3
|
+
require 'active_support/inflector'
|
72
4
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
max_min_extremum[kind_of_extremum] << pe if kind_of_extremum
|
78
|
-
end
|
79
|
-
end
|
80
|
-
return max_min_extremum
|
81
|
-
end
|
82
|
-
|
83
|
-
def curvature_behaviour
|
84
|
-
hash = Hash.new {|h,k|h[k]=Set.new}
|
85
|
-
return hash if self.degree > 5
|
86
|
-
extrema = self.derivative.extrema
|
87
|
-
all_extremas = extrema.values.inject(Set[],&:|).sort
|
88
|
-
all_extremas.each_cons(2).map { |s,e| Range.new(s,e) }.group_by do |range|
|
89
|
-
kind_of_curvature = AfterextremaCurvatureMapping[extrema.find { |k,v| v.include?(range.begin) }.first]
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def degree
|
94
|
-
self.terms.keys.max || 0
|
95
|
-
end
|
96
|
-
|
97
|
-
def to_s
|
98
|
-
terms.sort_by { |_,t| -t.exponent }.inject("") do |string,(_,term)|
|
99
|
-
string << term.to_s unless term.coefficient.zero? && !term.exponent.zero?
|
100
|
-
string
|
101
|
-
end.strip.sub(/\A\+\s/, '')
|
102
|
-
end
|
103
|
-
|
104
|
-
def ==(other)
|
105
|
-
delete_zeros = proc{ |_,t| t.coefficient.zero? }
|
106
|
-
self.terms.delete_if(&delete_zeros) == other.terms.delete_if(&delete_zeros)
|
107
|
-
end
|
108
|
-
|
109
|
-
def extrema
|
110
|
-
extrema = local_extrema
|
111
|
-
unless self.degree == 0
|
112
|
-
a = self.gt.coefficient
|
113
|
-
max_or_min = (self.degree.even? ? [1.0,1.0] : [-1.0,1.0]).map { |n| (n * a)/a.abs }
|
114
|
-
extrema[MinMaxMapping[max_or_min.first]] << -1.0/0
|
115
|
-
extrema[MinMaxMapping[max_or_min.last]] << 1.0/0
|
116
|
-
end
|
117
|
-
return extrema
|
118
|
-
end
|
119
|
-
|
120
|
-
def gt
|
121
|
-
self.terms[self.degree]
|
122
|
-
end
|
5
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
6
|
+
inflect.plural /mum$/, '\1ma'
|
7
|
+
inflect.singular /ma$/, '\1mum'
|
8
|
+
end
|
123
9
|
|
124
|
-
|
125
|
-
|
126
|
-
|
10
|
+
require 'polynomial'
|
11
|
+
require 'set'
|
12
|
+
require 'term'
|
13
|
+
require 'point'
|
14
|
+
require 'formulas'
|
15
|
+
require 'exceptions'
|
127
16
|
|
128
|
-
def coefficients_till(n)
|
129
|
-
coefficients = []
|
130
|
-
(0..n).each do |e|
|
131
|
-
coefficients << self.terms[e].coefficient
|
132
|
-
end
|
133
|
-
return coefficients.reverse
|
134
|
-
end
|
135
|
-
private :coefficients_till
|
136
17
|
|
137
|
-
|
138
|
-
new_function = self.class.new
|
139
|
-
self.terms.values.each do |term|
|
140
|
-
yield new_function, term
|
141
|
-
end
|
142
|
-
return new_function
|
143
|
-
end
|
144
|
-
protected :alter
|
18
|
+
module Polynomials
|
145
19
|
end
|
data/polynomials.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{polynomials}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Manuel Korfmann"]
|
12
|
-
s.date = %q{2011-05-
|
12
|
+
s.date = %q{2011-05-15}
|
13
13
|
s.description = %q{
|
14
14
|
This is a gem dedicated to parsing, manipulating and finding roots,extrema and inflection points of polynomials.
|
15
15
|
It can solve polynomial equations with a degree up to 4 by implementing formulas for solving quadratic, cubic and quartic functions.
|
@@ -17,22 +17,26 @@ It can solve polynomial equations with a degree up to 4 by implementing formulas
|
|
17
17
|
s.email = %q{manu@korfmann.info}
|
18
18
|
s.extra_rdoc_files = [
|
19
19
|
"LICENSE.txt",
|
20
|
-
"README.
|
20
|
+
"README.markdown"
|
21
21
|
]
|
22
22
|
s.files = [
|
23
23
|
".document",
|
24
24
|
"Gemfile",
|
25
25
|
"Gemfile.lock",
|
26
26
|
"LICENSE.txt",
|
27
|
-
"README.
|
27
|
+
"README.markdown",
|
28
28
|
"Rakefile",
|
29
29
|
"VERSION",
|
30
|
-
"
|
30
|
+
"examples/plot_only_mutual_data.rb",
|
31
31
|
"lib/polynomials.rb",
|
32
|
-
"lib/
|
32
|
+
"lib/polynomials/exceptions.rb",
|
33
|
+
"lib/polynomials/formulas.rb",
|
34
|
+
"lib/polynomials/point.rb",
|
35
|
+
"lib/polynomials/polynomial.rb",
|
36
|
+
"lib/polynomials/term.rb",
|
33
37
|
"polynomials.gemspec",
|
34
|
-
"test/
|
35
|
-
"test/
|
38
|
+
"test/formulas_test.rb",
|
39
|
+
"test/point_test.rb",
|
36
40
|
"test/polynomial_test.rb",
|
37
41
|
"test/term_test.rb",
|
38
42
|
"test/test_helper.rb"
|
@@ -50,19 +54,25 @@ It can solve polynomial equations with a degree up to 4 by implementing formulas
|
|
50
54
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
55
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
52
56
|
s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
|
53
|
-
s.add_development_dependency(%q<
|
57
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
54
58
|
s.add_development_dependency(%q<awesome_print>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<activesupport>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<i18n>, [">= 0"])
|
55
61
|
else
|
56
62
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
63
|
s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
|
58
|
-
s.add_dependency(%q<
|
64
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
59
65
|
s.add_dependency(%q<awesome_print>, [">= 0"])
|
66
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
67
|
+
s.add_dependency(%q<i18n>, [">= 0"])
|
60
68
|
end
|
61
69
|
else
|
62
70
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
63
71
|
s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
|
64
|
-
s.add_dependency(%q<
|
72
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
65
73
|
s.add_dependency(%q<awesome_print>, [">= 0"])
|
74
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
75
|
+
s.add_dependency(%q<i18n>, [">= 0"])
|
66
76
|
end
|
67
77
|
end
|
68
78
|
|
@@ -1,13 +1,15 @@
|
|
1
|
-
|
1
|
+
require "#{File.dirname(File.expand_path(__FILE__))}/test_helper"
|
2
|
+
|
2
3
|
class TestMath < MiniTest::Unit::TestCase
|
4
|
+
include Polynomials
|
3
5
|
def test_roots_for_constant_functions
|
4
6
|
polynomial = Polynomial.parse('5')
|
5
|
-
|
7
|
+
assert_set_eql Set[],polynomial.roots
|
6
8
|
end
|
7
9
|
|
8
10
|
def test_roots_for_linear_functions
|
9
11
|
polynomial = Polynomial.parse('5x + 2')
|
10
|
-
|
12
|
+
assert_set_eql Set[Root.new(-2.0/5.0)],polynomial.roots
|
11
13
|
end
|
12
14
|
|
13
15
|
def test_roots_quadratic_functions
|
@@ -16,50 +18,50 @@ class TestMath < MiniTest::Unit::TestCase
|
|
16
18
|
q = -40.0/3.0
|
17
19
|
root = Math.sqrt((p/2)**2 - q)
|
18
20
|
fraction = -(p/2)
|
19
|
-
|
21
|
+
assert_set_eql Set[Root.new((fraction - root).round(10)), Root.new((fraction + root).round(10))],polynomial.roots
|
20
22
|
|
21
23
|
polynomial = Polynomial.parse('3x^2 - 40')
|
22
|
-
|
24
|
+
assert_set_eql Set[Root.new(Math.sqrt(40.0/3).round(10)), Root.new(-Math.sqrt(40.0/3).round(10))], polynomial.roots
|
23
25
|
end
|
24
26
|
|
25
27
|
def test_roots_for_cubic_functions
|
26
28
|
polynomial = Polynomial.parse('1x^3 + 1x^2 + 2x - 1')
|
27
29
|
assert_equal(1, polynomial.roots.size)
|
28
|
-
|
30
|
+
assert_set_eql Set[Root.new(0.3926467817)], polynomial.roots
|
29
31
|
|
30
32
|
polynomial = Polynomial.parse('2x^3 - 4x^2 - 22x + 24')
|
31
|
-
|
33
|
+
assert_set_eql Set[Root.new(4.0),Root.new(-3.0),Root.new(1.0)], polynomial.roots
|
32
34
|
end
|
33
35
|
|
34
36
|
def test_roots_for_cubic_functions_with_complex
|
35
37
|
coefficients = Polynomial.parse('3x^3 - 10x^2 + 14x + 27').terms.first(4).map{ |t| t.last.coefficient }
|
36
|
-
assert_equal Set[-1.0, Complex(2.1666666666666634,2.0749832663314605), Complex(2.1666666666666634,-2.0749832663314605)],
|
38
|
+
assert_equal Set[-1.0, Complex(2.1666666666666634,2.0749832663314605), Complex(2.1666666666666634,-2.0749832663314605)],Formulas.roots_of_cubic_function(*coefficients, true)
|
37
39
|
end
|
38
40
|
|
39
41
|
def test_roots_for_cubic_functions_one_real_all_equal
|
40
42
|
polynomial = Polynomial.parse('1x^3 + 6x^2 + 12x + 8')
|
41
43
|
assert_equal(1, polynomial.roots.size)
|
42
|
-
assert_equal(-2, polynomial.roots.first)
|
44
|
+
assert_equal(Root.new(-2), polynomial.roots.first)
|
43
45
|
end
|
44
46
|
|
45
47
|
def test_roots_for_quartic_functions
|
46
48
|
polynomial = Polynomial.parse('3x^4 + 6x^3 - 123x^2 - 126x + 1080')
|
47
|
-
|
49
|
+
assert_set_eql Set[Root.new(5.0), Root.new(3.0), Root.new(-4.0), Root.new(-6.0)], polynomial.roots
|
48
50
|
|
49
51
|
polynomial = Polynomial.parse('-20x^4 + 5x^3 + 17x^2 - 29x + 87')
|
50
|
-
|
52
|
+
assert_set_eql Set[Root.new(-1.6820039266),Root.new(1.4875831103)], polynomial.roots
|
51
53
|
end
|
52
54
|
|
53
55
|
def test_roots_without_term_with_exponent_of_zero
|
54
56
|
polynomial = Polynomial.parse('3x^3 + 3x^2')
|
55
|
-
|
57
|
+
assert_set_eql Set[Root.new(0.0),Root.new(-1.0)], polynomial.roots
|
56
58
|
|
57
59
|
polynomial = Polynomial.parse('1 x^4')
|
58
|
-
|
60
|
+
assert_set_eql Set[Root.new(0.0)], polynomial.roots
|
59
61
|
end
|
60
62
|
|
61
63
|
def test_roots_biquadratic_equation
|
62
64
|
polynomial = Polynomial.parse('4x^4 + 2x^2 - 1')
|
63
|
-
|
65
|
+
assert_set_eql Set[Root.new(0.5558929703), Root.new(-0.5558929703)], polynomial.roots
|
64
66
|
end
|
65
67
|
end
|
data/test/point_test.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "#{File.dirname(File.expand_path(__FILE__))}/test_helper"
|
2
|
+
class PointTest < MiniTest::Unit::TestCase
|
3
|
+
def test_initalizer
|
4
|
+
point = Point.new(2,Polynomial.new(7).calculate(2))
|
5
|
+
assert_equal 2, point.x
|
6
|
+
assert_equal 7, point.y
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_to_s
|
10
|
+
point = Point.new(1,Polynomial.new(1).calculate(1))
|
11
|
+
assert_equal "(1,1.0)", point.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_equality
|
15
|
+
point1 = Extremum.new(5,Polynomial.new(0.0).calculate(0), :maximum)
|
16
|
+
point2 = Extremum.new(5,Polynomial.new(0).calculate(0), :maximum)
|
17
|
+
point3 = Point.new(5,Polynomial.new(0).calculate(0))
|
18
|
+
assert point1.eql?(point2)
|
19
|
+
refute point3.eql?(point2)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_comparison
|
23
|
+
point1 = Point.new(1,Polynomial.new(5).calculate(1))
|
24
|
+
point2 = Point.new(3,Polynomial.new(5).calculate(3))
|
25
|
+
point3 = Point.new(0.5,Polynomial.new(5).calculate(0.5))
|
26
|
+
point4 = Point.new(0.5,Polynomial.new(5).calculate(0.5))
|
27
|
+
assert point1 < point2
|
28
|
+
assert point1 > point3
|
29
|
+
assert point4 <= point3
|
30
|
+
assert point4 <= point2
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_root
|
34
|
+
assert_equal '(50,0)', Root.new(50).to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_boolean_max_min_meths
|
38
|
+
assert_equal true,Extremum.new(2,3,:maximum).maximum?
|
39
|
+
assert_equal true,Extremum.new(2,3,:minimum).minimum?
|
40
|
+
assert_equal false,Extremum.new(2,3,:maximum).minimum?
|
41
|
+
assert_equal false, Root.new(2).minimum?
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_bolean_check_methods
|
45
|
+
assert_equal true, InflectionPoint.new(2,3).inflection_point?
|
46
|
+
assert_equal true, Extremum.new(2,3, :maximum).extremum?
|
47
|
+
assert_equal true, Root.new(2).root?
|
48
|
+
assert_equal false, Root.new(3).extremum?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
data/test/polynomial_test.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
1
|
+
require "#{File.dirname(File.expand_path(__FILE__))}/test_helper"
|
2
2
|
|
3
3
|
Infinity = 1.0/0
|
4
4
|
class TestPolynomial < MiniTest::Unit::TestCase
|
5
|
+
include Polynomials
|
5
6
|
def test_to_s
|
6
7
|
polynomial = Polynomial.parse('5x + 2x^2 + 20')
|
7
8
|
assert_equal '2x^2 + 5x + 20', polynomial.to_s
|
@@ -45,23 +46,23 @@ class TestPolynomial < MiniTest::Unit::TestCase
|
|
45
46
|
|
46
47
|
def test_extrema
|
47
48
|
polynomial = Polynomial.parse('3x^2 + 2x + 1')
|
48
|
-
|
49
|
+
assert_set_eql(Set[ Extremum.new(-2.0/6.0,polynomial.calculate(-2.0/6.0), :minimum) ], polynomial.local_extrema)
|
49
50
|
polynomial = Polynomial.parse('5x^3 - 5x^2 + 2x - 2')
|
50
51
|
end
|
51
52
|
|
52
53
|
def test_extrema_with_slope_of_derivative_equal_to_zero
|
53
54
|
polynomial = Polynomial.parse('1x^4')
|
54
|
-
|
55
|
-
|
55
|
+
assert_set_eql(Set[ Extremum.new(0.0,polynomial.calculate(0.0), :minimum) ], polynomial.local_extrema)
|
56
|
+
assert_set_eql(Set[*[Infinity,-Infinity].map { |x| Extremum.new(x,nil, :maximum)}, Extremum.new(0.0,polynomial.calculate(0.0), :minimum) ], polynomial.extrema)
|
56
57
|
end
|
57
58
|
|
58
59
|
def test_no_local_extrema
|
59
60
|
polynomial = Polynomial.parse('6x^6 - 5x + 50')
|
60
|
-
assert_equal(
|
61
|
-
|
61
|
+
assert_equal(Set[], polynomial.local_extrema)
|
62
|
+
assert_set_eql(Set[*[ -1.0/0, 1.0/0 ].map { |x| Extremum.new(x, nil, :maximum)}], polynomial.extrema)
|
62
63
|
end
|
63
64
|
|
64
|
-
def
|
65
|
+
def test_curvature_behaviour_no_inflection_points
|
65
66
|
polynomial = Polynomial.parse('1 x^4')
|
66
67
|
assert_equal({ left: [-1.0/0..+1.0/0] }, polynomial.curvature_behaviour)
|
67
68
|
|
@@ -74,7 +75,7 @@ class TestPolynomial < MiniTest::Unit::TestCase
|
|
74
75
|
|
75
76
|
def test_no_extremums
|
76
77
|
polynomial = Polynomial.parse('5')
|
77
|
-
assert_equal(
|
78
|
+
assert_equal(Set[], polynomial.extrema)
|
78
79
|
end
|
79
80
|
|
80
81
|
def test_no_curvature
|
@@ -91,12 +92,12 @@ class TestPolynomial < MiniTest::Unit::TestCase
|
|
91
92
|
assert_equal({}, polynomial.curvature_behaviour)
|
92
93
|
end
|
93
94
|
|
94
|
-
def
|
95
|
+
def test_curvature_behaviour_two_inflection_points
|
95
96
|
polynomial = Polynomial.parse('+ 1.0 x^4 + 5.0 x^3 - 1.0 x^2 + 3.0 x + 5.0')
|
96
97
|
assert_equal({left:[-1.0/0..-2.5649778198, 0.0649778198..1.0/0], right: [-2.5649778198..0.0649778198] } , polynomial.curvature_behaviour)
|
97
98
|
end
|
98
99
|
|
99
|
-
def
|
100
|
+
def test_curvature_behaviour_three_inflection_points
|
100
101
|
polynomial = Polynomial.new(20,4,0,-1,-200)
|
101
102
|
assert_equal( {:right=>[(-1/10)..0.0], :left=>[-Infinity..(-1/10), 0.0..Infinity]}, polynomial.curvature_behaviour)
|
102
103
|
end
|
@@ -104,7 +105,7 @@ class TestPolynomial < MiniTest::Unit::TestCase
|
|
104
105
|
|
105
106
|
def test_efficient_roots_calculation
|
106
107
|
polynomial = Polynomial.parse('200x^2342435 + 6x^20')
|
107
|
-
|
108
|
+
assert_set_eql(Set[Root.new(0.0)], polynomial.roots)
|
108
109
|
end
|
109
110
|
|
110
111
|
def test_lt
|
@@ -121,4 +122,9 @@ class TestPolynomial < MiniTest::Unit::TestCase
|
|
121
122
|
polynomial = Polynomial.new(5,0,2,-1)
|
122
123
|
assert_equal '5x^3 + 2x - 1', polynomial.to_s
|
123
124
|
end
|
125
|
+
|
126
|
+
def test_inflection_points
|
127
|
+
polynomial = Polynomial.new(20,4,0,-1,-200)
|
128
|
+
assert_set_eql Set[InflectionPoint.new(-1/10,polynomial.(-1/10)),InflectionPoint.new(0,polynomial.(0))], polynomial.inflection_points
|
129
|
+
end
|
124
130
|
end
|
data/test/term_test.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
require "#{File.dirname(File.expand_path(__FILE__))}/test_helper"
|
2
|
+
|
2
3
|
class TestTerm < MiniTest::Unit::TestCase
|
3
4
|
def test_initializer
|
4
5
|
assert_equal 0, Term.new.coefficient
|
@@ -25,6 +26,12 @@ class TestTerm < MiniTest::Unit::TestCase
|
|
25
26
|
assert_equal 0, Term.parse('6').exponent
|
26
27
|
end
|
27
28
|
|
29
|
+
def test_dup
|
30
|
+
term = Term.new(2,10)
|
31
|
+
assert_equal term.coefficient, term.dup.coefficient
|
32
|
+
assert_equal term.exponent, term.dup.exponent
|
33
|
+
end
|
34
|
+
|
28
35
|
def test_invalid_string_raises_not_parsable_error
|
29
36
|
['6x^ -5', '6^20', '2 2', 'xx', '2 ^ x'].each do |not_parsable_string|
|
30
37
|
assert_raises NotParsableError do
|
data/test/test_helper.rb
CHANGED
@@ -1,3 +1,21 @@
|
|
1
1
|
require 'awesome_print'
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start
|
2
4
|
require 'minitest/autorun'
|
3
|
-
|
5
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
|
+
require 'polynomials'
|
8
|
+
|
9
|
+
class MiniTest::Unit::TestCase
|
10
|
+
include Polynomials
|
11
|
+
def assert_set_eql(actual,computed)
|
12
|
+
test = lambda do
|
13
|
+
actual.all? do |a|
|
14
|
+
computed.any? do |c|
|
15
|
+
a.eql?(c) && c.eql?(a)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
assert test.call, "Expected #{actual.inspect}, not #{computed.inspect}"
|
20
|
+
end
|
21
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 3
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Manuel Korfmann
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-05-
|
17
|
+
date: 2011-05-15 00:00:00 +02:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -48,7 +48,7 @@ dependencies:
|
|
48
48
|
prerelease: false
|
49
49
|
version_requirements: *id002
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
|
-
name:
|
51
|
+
name: simplecov
|
52
52
|
requirement: &id003 !ruby/object:Gem::Requirement
|
53
53
|
none: false
|
54
54
|
requirements:
|
@@ -73,6 +73,32 @@ dependencies:
|
|
73
73
|
type: :development
|
74
74
|
prerelease: false
|
75
75
|
version_requirements: *id004
|
76
|
+
- !ruby/object:Gem::Dependency
|
77
|
+
name: activesupport
|
78
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: *id005
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: i18n
|
91
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *id006
|
76
102
|
description: "\n\
|
77
103
|
This is a gem dedicated to parsing, manipulating and finding roots,extrema and inflection points of polynomials.\n\
|
78
104
|
It can solve polynomial equations with a degree up to 4 by implementing formulas for solving quadratic, cubic and quartic functions.\n "
|
@@ -83,21 +109,25 @@ extensions: []
|
|
83
109
|
|
84
110
|
extra_rdoc_files:
|
85
111
|
- LICENSE.txt
|
86
|
-
- README.
|
112
|
+
- README.markdown
|
87
113
|
files:
|
88
114
|
- .document
|
89
115
|
- Gemfile
|
90
116
|
- Gemfile.lock
|
91
117
|
- LICENSE.txt
|
92
|
-
- README.
|
118
|
+
- README.markdown
|
93
119
|
- Rakefile
|
94
120
|
- VERSION
|
95
|
-
-
|
121
|
+
- examples/plot_only_mutual_data.rb
|
96
122
|
- lib/polynomials.rb
|
97
|
-
- lib/
|
123
|
+
- lib/polynomials/exceptions.rb
|
124
|
+
- lib/polynomials/formulas.rb
|
125
|
+
- lib/polynomials/point.rb
|
126
|
+
- lib/polynomials/polynomial.rb
|
127
|
+
- lib/polynomials/term.rb
|
98
128
|
- polynomials.gemspec
|
99
|
-
- test/
|
100
|
-
- test/
|
129
|
+
- test/formulas_test.rb
|
130
|
+
- test/point_test.rb
|
101
131
|
- test/polynomial_test.rb
|
102
132
|
- test/term_test.rb
|
103
133
|
- test/test_helper.rb
|
@@ -115,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
115
145
|
requirements:
|
116
146
|
- - ">="
|
117
147
|
- !ruby/object:Gem::Version
|
118
|
-
hash: -
|
148
|
+
hash: -1053346155644578569
|
119
149
|
segments:
|
120
150
|
- 0
|
121
151
|
version: "0"
|
data/README.rdoc
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
= polynomials
|
2
|
-
|
3
|
-
Description goes here.
|
4
|
-
|
5
|
-
== Contributing to polynomials
|
6
|
-
|
7
|
-
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
-
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
-
* Fork the project
|
10
|
-
* Start a feature/bugfix branch
|
11
|
-
* Commit and push until you are happy with your contribution
|
12
|
-
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
-
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
-
|
15
|
-
== Copyright
|
16
|
-
|
17
|
-
Copyright (c) 2011 Manuel Korfmann. See LICENSE.txt for
|
18
|
-
further details.
|
19
|
-
|
data/lib/core_ext/math.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'mathn'
|
2
|
-
module Math
|
3
|
-
def self.roots_of_cubic_function(a,b,c,d,with_complex = false)
|
4
|
-
f = ((3*c/a) - (b**2/a**2))/3
|
5
|
-
g = (((2*b**3)/a**3) - (9*b*c/a**2) + (27*d/a))/27
|
6
|
-
h = (g**2/4) + (f**3/27)
|
7
|
-
if f == 0 && g == 0 && h == 0
|
8
|
-
f = (3*c/a) - (b**2/a**2)
|
9
|
-
g = (2*b**3/a**3) - (9*b*c/a**2) + (27*d/a)
|
10
|
-
h = (g**2/4) + (f**3/27)
|
11
|
-
[-Math.cbrt(d/a)]
|
12
|
-
elsif h <= 0
|
13
|
-
i = Math.sqrt((g**2/4) - h)
|
14
|
-
j = Math.cbrt(i)
|
15
|
-
k = Math.acos(-(g / (2*i)))
|
16
|
-
l = j * -1
|
17
|
-
m = Math.cos(k/3)
|
18
|
-
n = Math.sqrt(3) * Math.sin(k/3)
|
19
|
-
p = (b/(3*a))*-1
|
20
|
-
[2*j*Math.cos(k/3) - (b/(3*a)), l*(m+n)+p, l*(m-n)+p]
|
21
|
-
else
|
22
|
-
f = (((3*c)/a) - (b**2/a**2))/3
|
23
|
-
g = ((2*b**3/a**3) - (9*b*c/a**2) + ((27*d)/a))/27
|
24
|
-
h = (g**2/4) + (f**3/27)
|
25
|
-
r = -(g/2) + Math.sqrt(h)
|
26
|
-
s = Math.cbrt(r)
|
27
|
-
t = -(g/2) - Math.sqrt(h)
|
28
|
-
u = Math.cbrt(t)
|
29
|
-
roots = [(s + u) - (b/(3*a))]
|
30
|
-
roots |= [1,-1].map do |algebraic_sign|
|
31
|
-
Complex((-(s + u)/2) - (b/(3*a)),algebraic_sign * (s-u)*Math.sqrt(3)/2)
|
32
|
-
end if with_complex
|
33
|
-
roots
|
34
|
-
end.map { |n| n.respond_to?(:round) ? n.round(10) : n }.to_set
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.roots_of_quadratic_function(a,b,c)
|
38
|
-
p = b/a
|
39
|
-
q = c/a
|
40
|
-
denom = (p/2.0)**2 - q
|
41
|
-
return Set[] if denom < 0
|
42
|
-
root = Math.sqrt(denom)
|
43
|
-
Set.new([:+,:-].map{ |operator| (-(p/2.0)).send(operator, root)}.map { |n| n.round(10) })
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.roots_of_quartic_function(*args)
|
47
|
-
a,b,c,d,e = args.map { |n| n/args.first }
|
48
|
-
f = c - (3*b**2/8)
|
49
|
-
g = d + (b**3/8) - (b*c/2)
|
50
|
-
h = e - (3*b**4/256) + (b**2 * c/16) - ( b*d/4)
|
51
|
-
roots = self.roots_of_cubic_function(1,(f/2),(((f**2)-(4*h))/16),-(g**2)/64,true)
|
52
|
-
combs = roots.reject{ |n| n == 0}.combination(2)
|
53
|
-
only_complex = combs.select { |comb| comb.all? { |n| n.is_a? Complex }}
|
54
|
-
p,q = (only_complex.empty? ? combs.first : only_complex.first).map { |n| Math.sqrt(n) }
|
55
|
-
r = (-g/(8*p*q))
|
56
|
-
s = b/(4*a)
|
57
|
-
roots = Set.new([p + q + r -s, p - q - r -s, -p + q - r -s, -p - q + r -s].map { |n| n.is_a?(Complex) ? (n.real if n.imaginary == 0) : n }.compact )
|
58
|
-
roots.map { |n| n.respond_to?(:round) ? n.round(10) : n }.to_set
|
59
|
-
end
|
60
|
-
end
|
data/lib/term.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
class NotParsableError < ArgumentError;end
|
2
|
-
class Term
|
3
|
-
attr_accessor :coefficient, :exponent
|
4
|
-
|
5
|
-
def initialize(exponent = 0, coefficient = 0)
|
6
|
-
@coefficient, @exponent = coefficient, exponent
|
7
|
-
end
|
8
|
-
|
9
|
-
def self.parse(string)
|
10
|
-
term = self.new
|
11
|
-
float = /\d+(?:\.\d+)?/
|
12
|
-
integer = /\d+/
|
13
|
-
polynomial_regex = /
|
14
|
-
\A\s*
|
15
|
-
(?<algebraic_sign>[-+]?)
|
16
|
-
\s*
|
17
|
-
(?<coefficient>#{float})?
|
18
|
-
\s*
|
19
|
-
(?<power>x(?:\^(?<exponent>#{integer}))?)?
|
20
|
-
\s*\Z
|
21
|
-
/x
|
22
|
-
|
23
|
-
raw_data = string.match(polynomial_regex)
|
24
|
-
raise NotParsableError unless raw_data
|
25
|
-
term.coefficient = (raw_data[:algebraic_sign] + ( raw_data[:coefficient] || "1")).to_f
|
26
|
-
term.exponent = raw_data[:power] ? (raw_data[:exponent] || 1).to_i : 0
|
27
|
-
term
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_s
|
31
|
-
pretty_coeffiecent = coefficient.denominator == 1 ? coefficient.abs.to_i : coefficient.abs
|
32
|
-
algebraic_sign = coefficient < 0 ? '-' : '+'
|
33
|
-
power = "x#{"^#{exponent}" unless exponent == 1}"
|
34
|
-
"#{algebraic_sign} #{pretty_coeffiecent}#{ power unless exponent.zero?} "
|
35
|
-
end
|
36
|
-
|
37
|
-
def dup
|
38
|
-
duplicate = self.class.new
|
39
|
-
duplicate.coefficient = self.coefficient
|
40
|
-
duplicate.exponent = self.exponent
|
41
|
-
duplicate
|
42
|
-
end
|
43
|
-
|
44
|
-
def ==(other)
|
45
|
-
self.coefficient == other.coefficient && self.exponent == other.exponent
|
46
|
-
end
|
47
|
-
end
|
data/test/helper.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
begin
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
10
|
-
require 'test/unit'
|
11
|
-
require 'shoulda'
|
12
|
-
|
13
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
-
require 'polynomials'
|
16
|
-
|
17
|
-
class Test::Unit::TestCase
|
18
|
-
end
|