polymath 1.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.
@@ -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: []