polymath 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a894f774d02bd9b398ece719980e9ed6dd6e5911
4
+ data.tar.gz: 8b6581fa9da8d754368e8f8b5ba7c6507d0f433a
5
+ SHA512:
6
+ metadata.gz: 62c2c0ae2ee2bfe42acec1cee9ad48a186d5f4fc4a58bb20614025725dcfe4d78628f44024f556c5b314252e0015f439157d8758f4af679c829dd655c5ac6dff
7
+ data.tar.gz: 24e54d6c6f213b6016c8839f4dce86a4b76a9b9cff10f23341c4052ffd25e2e0cef0b2ff76c33f53ce70a892d3576c1a4d6732d6ccd79d6b66243c535f0ea245
@@ -0,0 +1,28 @@
1
+ # Polymath
2
+ [![Gem Version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=rb&type=6&v=1.0.0&x2=0)](https://rubygems.org/gems/polymath)
3
+ A library for polynomials with integer coefficients
4
+
5
+ ## install:
6
+ gem install polymath
7
+
8
+ ## usage:
9
+
10
+ ### lib:
11
+ ```ruby
12
+ require 'polymath'
13
+ polynomial = Polymath.polynomial("x^2 - 3x + 2")
14
+ polynomial.cleanup!
15
+ polynomial.to_s #=> "x^2-3x+2"
16
+ ```
17
+
18
+ ### command line:
19
+ ```
20
+ $ polymath -af "x^2 - 3x + 2"
21
+ x^2-3x+2
22
+ deg: 2
23
+ class: normal quadratic trinomial
24
+ zeroes: [(1/1), (2/1)]
25
+ ```
26
+
27
+ ## docs:
28
+ http://www.rubydoc.info/gems/polymath
@@ -0,0 +1,106 @@
1
+ require 'optparse'
2
+ require 'json'
3
+
4
+ require_relative 'polymath/nomial/polynomial'
5
+ require_relative 'polymath/math/math'
6
+
7
+ module Polymath
8
+ ::Version = [1, 0, 0]
9
+
10
+ ##
11
+ ## @brief parses command line arguments
12
+ ##
13
+ ## @param args The arguments
14
+ ##
15
+ ## @return a hash of parsed options
16
+ ##
17
+ def self.parse_options(args)
18
+
19
+ options = {
20
+ :polynomial => nil,
21
+ :verbose => true,
22
+ :analyze => false,
23
+ :factor => false
24
+ }
25
+
26
+ parser = OptionParser.new { |opts|
27
+ opts.banner = "Usage: polymath [options] <polynomial>"
28
+
29
+ opts.on("-f", "--factor", "factor the polynomial expression") { |f|
30
+ options[:factor] = f
31
+ }
32
+
33
+ opts.on("-a", "--analyze", "analyze the polynomial expression") { |a|
34
+ options[:analyze] = a
35
+ }
36
+
37
+ opts.on("-q", "--quiet", "only output what is specified") { |q|
38
+ options[:verbose] = ! q
39
+ }
40
+
41
+ opts.on("-r", "--random [N]", "generate a random polynomial") { |n|
42
+ options[:polynomial] = if n
43
+ Nomial::random_polynomial(Integer(n))
44
+ else
45
+ Nomial::random_polynomial
46
+ end
47
+ }
48
+
49
+ opts.on_tail("-h", "--help", "Show this message") {
50
+ puts opts
51
+ exit
52
+ }
53
+
54
+ opts.on_tail("--version", "Show version") {
55
+ puts self::Version.join('.')
56
+ exit
57
+ }
58
+ }
59
+
60
+ parser.parse!(args)
61
+ options[:polynomial] = args.pop unless options[:polynomial]
62
+ options
63
+ end
64
+
65
+ def self.polynomial(exp)
66
+ Polymath::Nomial::Polynomial.new(exp)
67
+ end
68
+
69
+ ##
70
+ ## @brief preforms actions specified by command line arguments
71
+ ##
72
+ ## @param options The parsed options
73
+ ##
74
+ ## @return nil
75
+ ##
76
+ def self.command_line(options)
77
+ raise "No polynomial given" unless options[:polynomial]
78
+
79
+ polynomial = Polymath.polynomial(options[:polynomial])
80
+ math = Polymath::Math.new
81
+
82
+ polynomial.cleanup!
83
+
84
+ if options[:factor]
85
+ zeroes = math.factor_rational_zeroes(polynomial: polynomial)
86
+ end
87
+
88
+ #output
89
+ puts options.map { |opt, value|
90
+ next unless value
91
+ case opt
92
+ when :verbose
93
+ polynomial.to_s
94
+ when :factor
95
+ "zeroes: #{zeroes}" if options[:factor]
96
+ when :analyze
97
+ {
98
+ deg: polynomial.deg,
99
+ class: polynomial.classification.map { |t, c| c }.compact.join(" ")
100
+ }.map { |a,b| "#{a}: #{b}" }.join("\n")
101
+ end
102
+ }.compact.join("\n")
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,8 @@
1
+ module Polymath
2
+ class TooManyVariablesError < StandardError
3
+ def initialize(msg="There are too many variables in this polynomial")
4
+ super
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,92 @@
1
+ require 'prime'
2
+
3
+ module Polymath
4
+
5
+ class Math
6
+
7
+ ::ZeroRoot = Rational(0)
8
+
9
+ ##
10
+ ## @brief calculates possible rational zeroes for a given polynomial
11
+ ##
12
+ ## @param polynomial The polynomial
13
+ ##
14
+ ## @return an array of Rational numbers
15
+ ##
16
+ def rational_zeroes(polynomial:)
17
+ cnf = factors_of(polynomial.constant)
18
+ lcf = factors_of(polynomial.leading_coefficient)
19
+
20
+ cnf.map { |x|
21
+ lcf.map { |y| [ Rational(x, y), -Rational(x, y) ] }
22
+ }.flatten.uniq
23
+ end
24
+
25
+ ##
26
+ ## @brief Determines if a rational number is a zero for a given
27
+ ## polynomial
28
+ ##
29
+ ## @param polynomial The polynomial
30
+ ## @param test_value The test value
31
+ ##
32
+ ## @return True if a zero, False otherwise.
33
+ ##
34
+ def is_a_zero?(test_value:, polynomial:)
35
+ synthetic_remainder(polynomial: polynomial, divisor: test_value) == 0
36
+ end
37
+
38
+ ##
39
+ ## @brief determines the zeroes of a polynomial
40
+ ##
41
+ ## @param polynomial The polynomial
42
+ ##
43
+ ## @return an array of Rational numbers
44
+ ##
45
+ def factor_rational_zeroes(polynomial:)
46
+ rational_zeroes(polynomial: polynomial).select { |test_value|
47
+ is_a_zero?(test_value: test_value, polynomial: polynomial)
48
+ }
49
+ end
50
+
51
+ def factor_zeroes(polynomial:)
52
+ case polynomial.classification[:len]
53
+ when :monomial
54
+ case polynomial.classification[:special]
55
+ when :zero
56
+ [Float::INFINITY]
57
+ else
58
+ [::ZeroRoot]
59
+ end
60
+ else
61
+ factor_rational_zeroes(polynomial: polynomial)
62
+ end
63
+ end
64
+
65
+ ##
66
+ ## @brief calculates the remainder of the synthetic quotient of a
67
+ ## polynomial and a test value
68
+ ##
69
+ ## @param polynomial The polynomial
70
+ ## @param value The value
71
+ ##
72
+ ## @return a Rational number
73
+ ##
74
+ def synthetic_remainder(polynomial:, divisor:)
75
+ polynomial.coefficients.reduce { |carry, next_cof|
76
+ (carry * divisor) + next_cof
77
+ }
78
+ end
79
+
80
+ ##
81
+ ## @brief determine the prime factors of x
82
+ ##
83
+ ## @param x the integer to factor
84
+ ##
85
+ ## @return an array of integers
86
+ ##
87
+ def factors_of(x)
88
+ (x.prime_division.map { |f| f[0] } + [1, x]).uniq.sort
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,58 @@
1
+ module Polymath
2
+ module Nomial
3
+ class Monomial
4
+
5
+ attr_accessor :cof, :deg, :var
6
+
7
+ ::PlaceholderVar = "x"
8
+
9
+ def initialize(cof:1, deg:0, var: ::PlaceholderVar)
10
+ @cof, @deg, @var = cof, deg, var
11
+ end
12
+
13
+ def homogenize!(new_var)
14
+ @cof = 1 unless cof
15
+ @deg = 0 unless deg
16
+ @var = new_var
17
+ end
18
+
19
+ def merge!(other)
20
+ @cof *= other.cof if other.cof
21
+ @deg += other.deg if other.deg
22
+ @var = other.var if other.var != ::PlaceholderVar
23
+ self
24
+ end
25
+
26
+ def gcd(other)
27
+ Monomial.new(
28
+ cof: cof.gcd(other.cof),
29
+ deg: [deg, other.deg].min,
30
+ var: var
31
+ )
32
+ end
33
+
34
+ def /(other)
35
+ Monomial.new(
36
+ cof: cof / other.cof,
37
+ deg: deg - other.deg,
38
+ var: var
39
+ )
40
+ end
41
+
42
+ def +(other)
43
+ raise "error" unless deg == other.deg
44
+ raise "error" unless var == other.var
45
+ Monomial.new(cof:@cof + other.cof, deg:deg, var:var)
46
+ end
47
+
48
+ def to_s
49
+ if deg > 0
50
+ "#{cof == 1 ? "" : cof}" + "#{var}" + (deg > 1 ? "^#{deg}" : "")
51
+ else
52
+ "#{cof}"
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,69 @@
1
+ require_relative 'monomial'
2
+ module Polymath
3
+ module Nomial
4
+ class Parser
5
+
6
+ ::AllowedCharacters = / [[:alnum:]] | \^ | \s | \+ | \- /x
7
+
8
+ def self.sanitize(exp)
9
+ exp.gsub(/(?!#{AllowedCharacters})./, '')
10
+ end
11
+
12
+ def self.set_variable(exp, variable)
13
+ exp.gsub(/[[:alpha:]]/, variable)
14
+ end
15
+
16
+ def self.strip(exp)
17
+ exp.gsub(/\s/, '')
18
+ end
19
+
20
+ ##
21
+ ## @brief parses a string polynomial expression
22
+ ##
23
+ ## @param exp the string polynomial expression
24
+ ##
25
+ ## @return an array of monomials
26
+ ##
27
+ def self.parse(exp)
28
+ self.strip(exp).split(/\+|(?=-)/).map { |monomial|
29
+ monomial.split(/(?=[[:alpha:]])/).map { |token|
30
+ if /[[:alpha:]]/.match?(token)
31
+ Monomial.new(
32
+ var: token.scan(/[[:alpha:]]/).join,
33
+ deg: /\^/.match?(token) ? Integer(token.scan(/\^(.*)/).join) : 1,
34
+ cof: /\-/.match?(token) ? -1 : nil
35
+ )
36
+ elsif /\d/.match?(token)
37
+ Monomial.new(cof: Integer(token))
38
+ elsif token == "-"
39
+ Monomial.new(cof: -1)
40
+ end
41
+ }.compact.reduce(:merge!)
42
+ }
43
+ end
44
+
45
+ ##
46
+ ## @brief guesses which variable the user meant by frequency
47
+ ##
48
+ ## @return string of length 1
49
+ ##
50
+ def self.guess_variable(polynomial)
51
+ variables = polynomial.monomials.select { |monomial|
52
+ monomial.var != "?"
53
+ }.map { |monomial| monomial.var }
54
+
55
+ if variables.length == 0
56
+ "x"
57
+ else
58
+ variables.uniq.map { |char|
59
+ {
60
+ count: variables.count(char),
61
+ char: char
62
+ }
63
+ }.sort_by { |c| -c[:count] }.first[:char]
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,231 @@
1
+ require_relative 'monomial'
2
+ require_relative 'parser'
3
+
4
+ module Polymath
5
+ module Nomial
6
+ def self.random_polynomial(n=3, cof_max: 100, skip_chance: 0.25)
7
+ degree = Integer(Random.rand() * n) + 1
8
+ (0..degree).map { |deg|
9
+ if Random.rand <= 1.0 - skip_chance
10
+ "#{Integer(Random.rand * cof_max) - (cof_max / 2)}x^#{deg}"
11
+ else
12
+ "0"
13
+ end
14
+ }.compact.shuffle.join("+")
15
+ end
16
+
17
+ ##
18
+ ## @brief Class for a polynomial with integer coefficients and degrees
19
+ ##
20
+ class Polynomial
21
+
22
+ attr_reader :exp, :monomials, :homogenized_exp
23
+ attr_accessor :variable
24
+
25
+ ::DefaultVar = "x"
26
+ ::ZeroMonomial = Monomial.new(cof: 0, var: ::DefaultVar)
27
+ ::UnitMonomial = Monomial.new(var: ::DefaultVar)
28
+
29
+ ##
30
+ ## @brief Constructs a Polynomial object
31
+ ##
32
+ ## @param exp the string polynomial expression
33
+ ##
34
+ ## @return a new Polynomial object
35
+ ##
36
+ def initialize(exp)
37
+ @exp = Parser.sanitize(exp)
38
+ @monomials = Parser.parse(@exp)
39
+ @monomials << ::ZeroMonomial if @monomials.length == 0
40
+
41
+ @variable = Parser.guess_variable(self)
42
+ @homogenized_exp = Parser.set_variable(exp, @variable)
43
+
44
+ @gcd = ::UnitMonomial
45
+ end
46
+
47
+ ##
48
+ ## @brief checks if the supplied expression string was auto-corrected
49
+ ##
50
+ ## @return boolean
51
+ ##
52
+ def modified_expression?
53
+ homogenized_exp != exp
54
+ end
55
+
56
+ ##
57
+ ## @brief homogenizes each monomial in place
58
+ ##
59
+ ## @return an array of monomials
60
+ ##
61
+ def homogenize!
62
+ monomials.each { |monomial| monomial.homogenize!(variable) }
63
+ end
64
+
65
+ ##
66
+ ## @brief orders a polynomial expression in descending order by degree
67
+ ##
68
+ ## @return an array of monomials in descending order
69
+ ##
70
+ def order
71
+ monomials.sort_by { |monomial| -monomial.deg }
72
+ end
73
+
74
+ ##
75
+ ## @brief collects terms of the same degree
76
+ ##
77
+ ## @return an array of monomials where all like degrees are merged
78
+ ##
79
+ def collect_terms
80
+ c = (0..deg).map { |n|
81
+ collected = monomials_where_deg(n).reduce(:+)
82
+ collected.cof == 0 ? nil : collected if collected
83
+ }.compact.reverse
84
+
85
+ c.empty? ? [::ZeroMonomial] : c
86
+ end
87
+
88
+ ##
89
+ ## @brief homogenizes, collects terms, and orders a polynomial in place
90
+ ##
91
+ ## @return nil
92
+ ##
93
+ def cleanup!
94
+ homogenize!
95
+
96
+ @monomials = order
97
+
98
+ @monomials = collect_terms
99
+
100
+ @monomials = factor_gcd
101
+ end
102
+
103
+ ##
104
+ ## @brief returns all monomials with the specified degree
105
+ ##
106
+ ## @param n the specified degree
107
+ ##
108
+ ## @return an array of monomials with degree == n
109
+ ##
110
+ def monomials_where_deg(n)
111
+ monomials.select { |monomial| monomial.deg == n }
112
+ end
113
+
114
+ ##
115
+ ## @brief returns the degree of the polynomial
116
+ ##
117
+ ## @return an integer degree
118
+ ##
119
+ def deg
120
+ order.first.deg
121
+ end
122
+
123
+ ##
124
+ ## @brief returns the value of the polynomial's constant
125
+ ##
126
+ ## @param cof The cof
127
+ ##
128
+ ## @return an integer
129
+ ##
130
+ def constant
131
+ (monomials_where_deg(0).first || Monomial.new(cof: 0)).cof
132
+ end
133
+
134
+ ##
135
+ ## @brief returns the value of the polynomial's leading coefficient
136
+ ##
137
+ ## @return an integer
138
+ ##
139
+ def leading_coefficient
140
+ monomials_where_deg(deg).first.cof
141
+ end
142
+
143
+ ##
144
+ ## @brief returns an array of the polynomial's coefficients
145
+ ##
146
+ ## @return an array of integers
147
+ ##
148
+ def coefficients
149
+ monomials.map { |monomial| monomial.cof }
150
+ end
151
+
152
+ ##
153
+ ## @brief facotrs the gcd out of the polynomial
154
+ ##
155
+ ## @return nil
156
+ ##
157
+ def factor_gcd
158
+ cls = classification
159
+ if cls[:special] == :zero or cls[:len] == :monomial
160
+ return monomials
161
+ end
162
+ @gcd = monomials.reduce(:gcd)
163
+ monomials.map { |monomial|
164
+ monomial / @gcd
165
+ }
166
+ end
167
+
168
+ ##
169
+ ## @brief uses a symbol classification method to classify a polynomial
170
+ ##
171
+ ## @return a hash
172
+ ##
173
+ def classification
174
+ if (1..3).include?(monomials.length)
175
+ basic = [
176
+ :monomial,
177
+ :binomial,
178
+ :trinomial
179
+ ][monomials.length - 1]
180
+
181
+ if basic == :monomial
182
+ if monomials.first.cof == 0
183
+ special = :zero
184
+ elsif monomials.first.deg == 0
185
+ special = :constant
186
+ end
187
+ end
188
+ end
189
+
190
+ if (0..10).include?(deg)
191
+ degree = [
192
+ :undefinded,
193
+ :linear,
194
+ :quadratic,
195
+ :qubic,
196
+ :quartic,
197
+ :quintic,
198
+ :hexic,
199
+ :heptic,
200
+ :octic,
201
+ :nonic,
202
+ :decic
203
+ ][deg]
204
+ end
205
+
206
+ basic ||= :polynomial
207
+ special ||= :normal
208
+ degree ||= :"#{deg}th_degree"
209
+
210
+ { :special => special, :deg => degree, :len => basic }
211
+ end
212
+
213
+ ##
214
+ ## @brief displys a string form of the polynomial
215
+ ##
216
+ ## @return a string
217
+ ##
218
+ def to_s
219
+ expression = monomials.collect { |monomial| monomial.to_s }.reduce { |m, t|
220
+ joiner = t[0] == "-" ? "" : "+"
221
+ m += joiner + t
222
+ }
223
+ if @gcd.cof == 1 and @gcd.deg == 0
224
+ expression
225
+ else
226
+ "#{@gcd}(#{expression})"
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,18 @@
1
+ require 'test/unit'
2
+ require_relative '../lib/polymath/polynomial'
3
+
4
+ class TestPolynomial < Test::Unit::TestCase
5
+ include Polymath
6
+
7
+ # the parser will guess the polynomial's variable
8
+ #
9
+ # @return { description_of_the_return_value }
10
+ #
11
+ def test_parsing
12
+ polynomial = Polynomial.new("15x^3+15x^332-4xx^2-3y-2")
13
+ assert_equal polynomial.to_s, "15x^332+11x^3-3x^1-2"
14
+ assert_equal polynomial.deg, 332
15
+ assert_equal polynomial.variable, "x"
16
+ end
17
+
18
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: polymath
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - annacrombie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: polynomial library
14
+ email: stone.tickle@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - "./README.md"
20
+ - "./lib/polymath.rb"
21
+ - "./lib/polymath/error.rb"
22
+ - "./lib/polymath/math/math.rb"
23
+ - "./lib/polymath/nomial/monomial.rb"
24
+ - "./lib/polymath/nomial/parser.rb"
25
+ - "./lib/polymath/nomial/polynomial.rb"
26
+ - "./test/test_polynomial.rb"
27
+ homepage: https://github.com/uab-cs/polymath/
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.6.11
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: polynomial library
51
+ test_files: []