congruence_solver 0.2.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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/README.md +57 -0
- data/Rakefile +53 -0
- data/bench/bench_tools.rb +33 -0
- data/bench/solve_congruence_bm.rb +71 -0
- data/bin/csolve.rb +54 -0
- data/bin/setup +7 -0
- data/congruence_solver.gemspec +33 -0
- data/ext/congruence_solver/extconf.rb +4 -0
- data/lib/congruence_solver/version.rb +3 -0
- data/lib/congruence_solver.rb +6 -0
- data/lib/polynomial_interpreter.rb +116 -0
- data/spec/congruence_solver_spec.rb +38 -0
- data/spec/csolve_spec.rb +89 -0
- data/spec/spec_helper.rb +96 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b8cdf5634b888651240db1d379330c26e0915815
|
4
|
+
data.tar.gz: cb6e241768d62d727196d40600aed3da6c8d8cbd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c656aba94e133852ff5a6cb03cb4e4d35e7df80bc60205254caf6ffb1b8bb486121c939a2f8c63dce9dc3881b7ad48840d7a63619b1839faa18acca2148a72bb
|
7
|
+
data.tar.gz: 84930366fe58c6f75167c673b180b5d1dcc89e38540160f0281b1c10ba100eb7603d23f63ea6346d8ea7117030ae2274d27f82433cc9c5542aa9496d3ee4f146
|
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# CongruenceSolver
|
2
|
+
|
3
|
+
CongruenceSolver is a gem for solving polynomial congruences. Should you ever need to solve polynomial congruences and have Ruby installed, this is the gem for you!
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'congruence_solver'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install congruence_solver
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
To solve a polynomial congruence at the command line, simply invoke `csolve` and then enter the congruence at the prompt.
|
24
|
+
|
25
|
+
```
|
26
|
+
csolve
|
27
|
+
Congruence to solve:
|
28
|
+
x^2 + 2x + 1 = x^3 + 3x^5 mod 49
|
29
|
+
(0) 1
|
30
|
+
(1) 8
|
31
|
+
(2) 15
|
32
|
+
(3) 22
|
33
|
+
(4) 26
|
34
|
+
(5) 29
|
35
|
+
(6) 36
|
36
|
+
(7) 43
|
37
|
+
```
|
38
|
+
|
39
|
+
To use the CongruenceSolver in a Ruby program, use CongruenceSolve::solve_congruence(coeffs, mod), where coeffs is the ascending list of coefficients of the polynomial (congruent to 0) and mod is the modulus of the congruence.
|
40
|
+
|
41
|
+
```
|
42
|
+
#solve -3x^5 - x^3 + x^2 + 2x + 1 = 0 mod 49
|
43
|
+
coeffs = [1, 2, 1, 1, 0, 3]
|
44
|
+
mod = 49
|
45
|
+
CongruenceSolver.solve_congruence(coeffs, mod).sort #=> [1, 8, 15, 22, 26, 29, 36, 43]
|
46
|
+
```
|
47
|
+
|
48
|
+
## Development
|
49
|
+
|
50
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests, or rake bench to run the benchmark.
|
51
|
+
|
52
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/laneb/congruence_solver.
|
57
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require 'rake/extensiontask'
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
=begin
|
9
|
+
namespace :test do
|
10
|
+
desc "Compile and unit test C extensions"
|
11
|
+
Rspec::Core::RakeTask.new :c => :compile_c do |t|
|
12
|
+
t.rspec_opts = C_SPEC_FILE
|
13
|
+
end
|
14
|
+
end
|
15
|
+
=end
|
16
|
+
|
17
|
+
#exe runs the csolve binary
|
18
|
+
task :exe do
|
19
|
+
$LOAD_PATH << "#{Dir.pwd}/lib/"
|
20
|
+
require_relative "bin/csolve.rb"
|
21
|
+
end
|
22
|
+
|
23
|
+
#spec runs all RSpec examples
|
24
|
+
RSpec::Core::RakeTask.new :spec
|
25
|
+
|
26
|
+
#uses task template provided by rake-compiler to run the extension compilation
|
27
|
+
#workflow. Task name: compile (do not use task name: ext)
|
28
|
+
Rake::ExtensionTask.new 'congruence_solver' do |ext|
|
29
|
+
ext.lib_dir = "lib/congruence_solver"
|
30
|
+
end
|
31
|
+
|
32
|
+
task :bench do
|
33
|
+
$LOAD_PATH << "#{Dir.pwd}/lib/"
|
34
|
+
Dir.foreach("bench") do |bm_file|
|
35
|
+
path = "bench/#{bm_file}"
|
36
|
+
if File.file? path and path =~ "_bm.rb^"
|
37
|
+
require_relative path
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#executes compile task defined above, then cleans up the tmp directory that
|
43
|
+
#rake-compiler leaves behind for some reason
|
44
|
+
task :compile_c => :compile do
|
45
|
+
CLEAN_CMD = "rmdir /s /q tmp"
|
46
|
+
system CLEAN_CMD
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
def polynomial_to_s(coeffs)
|
3
|
+
polynomial = ""
|
4
|
+
is_first_term = true
|
5
|
+
|
6
|
+
coeffs.reverse.each_with_index do |coe, idx|
|
7
|
+
exp = coeffs.length - idx - 1
|
8
|
+
if coe != 0
|
9
|
+
if is_first_term
|
10
|
+
is_first_term = false
|
11
|
+
else
|
12
|
+
if coe < 0
|
13
|
+
polynomial << " - "
|
14
|
+
else
|
15
|
+
polynomial << " + "
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if coe.abs > 1 or (coe.abs == 1 and exp < 2)
|
20
|
+
polynomial << coe.abs.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
if exp > 0
|
24
|
+
polynomial << "x"
|
25
|
+
if exp > 1
|
26
|
+
polynomial << "^#{exp}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
polynomial
|
33
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "congruence_solver"
|
2
|
+
require "benchmark"
|
3
|
+
require_relative "./bench_tools.rb"
|
4
|
+
|
5
|
+
|
6
|
+
SMALL_DEG_POLYNOMIAL_COEFFS = [1, -4, 4]
|
7
|
+
LARGE_DEG_POLYNOMIAL_COEFFS = [-11, 0, 0, 3, 0, 0, 0, 0, 0, 10]
|
8
|
+
SMALL_MOD = 49
|
9
|
+
MED_MOD = 5104
|
10
|
+
LARGE_MOD = 94122
|
11
|
+
XTRA_LARGE_MOD = 401249
|
12
|
+
XTRA_LARGE_PRIME_MOD = 306893
|
13
|
+
SMALL_FACTORED_LARGE_MOD = 510510
|
14
|
+
|
15
|
+
|
16
|
+
def solve_congruence_brute_force(coeffs, mod)
|
17
|
+
solutions = []
|
18
|
+
|
19
|
+
0.upto(mod) do |x|
|
20
|
+
sum = 0
|
21
|
+
|
22
|
+
coeffs.each_with_index do |coe, exp|
|
23
|
+
sum = (sum + coe*x**exp) % mod
|
24
|
+
end
|
25
|
+
|
26
|
+
if sum == 0 then solutions << x end
|
27
|
+
end
|
28
|
+
|
29
|
+
solutions
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def bm_solve_congruence(coeffs, mod)
|
34
|
+
puts "Solving #{polynomial_to_s(coeffs)} = 0 mod #{mod}"
|
35
|
+
|
36
|
+
rb_bf_solutions = solve_congruence_brute_force(coeffs, mod).sort
|
37
|
+
#c_bf_solutions = CongruenceSolver.brute_force(coeffs, mod).sort
|
38
|
+
c_lifting_solutions = CongruenceSolver.lift(coeffs, mod).sort
|
39
|
+
|
40
|
+
unless rb_bf_solutions == c_lifting_solutions #and c_bf_solutions c_lift_solutions
|
41
|
+
puts "Solutions do not match:"
|
42
|
+
puts "Ruby/force solutions #{rb_bf_solutions.inspect}"
|
43
|
+
#puts "C/force solutions #{c_bf_solutions}"
|
44
|
+
puts "C/lifting solutions: #{c_lifting_solutions.inspect}"
|
45
|
+
end
|
46
|
+
|
47
|
+
puts "Time measurements:"
|
48
|
+
|
49
|
+
Benchmark.bmbm do |bm|
|
50
|
+
bm.report("Ruby/force") do
|
51
|
+
solve_congruence_brute_force(coeffs, mod)
|
52
|
+
end
|
53
|
+
=begin
|
54
|
+
bm.report("C/force") do
|
55
|
+
CongruenceSolver.brute_force(coeffs, mod)
|
56
|
+
end
|
57
|
+
=end
|
58
|
+
bm.report("C/lifting") do
|
59
|
+
CongruenceSolver.lift(coeffs, mod)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
print "\n\n"
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
[SMALL_DEG_POLYNOMIAL_COEFFS, LARGE_DEG_POLYNOMIAL_COEFFS].each do |coeffs|
|
68
|
+
[SMALL_MOD, MED_MOD, LARGE_MOD, XTRA_LARGE_MOD, XTRA_LARGE_PRIME_MOD, SMALL_FACTORED_LARGE_MOD].each do |mod|
|
69
|
+
bm_solve_congruence(coeffs, mod)
|
70
|
+
end
|
71
|
+
end
|
data/bin/csolve.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'congruence_solver'
|
3
|
+
require "polynomial_interpreter"
|
4
|
+
|
5
|
+
|
6
|
+
SOLVE_CONGRUENCE_BENCH_FILE = "../bench/solve_congruence_bm.rb"
|
7
|
+
|
8
|
+
|
9
|
+
if ARGV.pop == "bench"
|
10
|
+
require_relative SOLVE_CONGRUENCE_BENCH_FILE
|
11
|
+
exit(0)
|
12
|
+
end
|
13
|
+
|
14
|
+
CONGRUENCE_FORMAT = "(lhs polynomial) = (rhs polynomial) mod (modulus)"
|
15
|
+
CONGRUENCE_INVALID_MSG = "Congruence invalid: congruences must be of form:\n#{CONGRUENCE_FORMAT}"
|
16
|
+
POLYNOMIAL_FORMAT = "ax^b+cx^d...\n(integer coefficients, positive integer exponents, order irrelevant)"
|
17
|
+
LHS_INVALID_MSG = "Left hand polynomial invalid: polynomials must be of form: #{POLYNOMIAL_FORMAT}"
|
18
|
+
RHS_INVALID_MSG = "Right hand polynomial invalid: polynomials must be of form: #{POLYNOMIAL_FORMAT}"
|
19
|
+
MOD_INVALID_MSG = "Mod invalid: modulus must be an integer greater than 2"
|
20
|
+
|
21
|
+
puts "Congruence to solve:"
|
22
|
+
|
23
|
+
begin
|
24
|
+
coeffs, mod = PolynomialInterpreter.read_congruence(STDIN.gets)
|
25
|
+
rescue ArgumentError => e
|
26
|
+
if(e == PolynomialInterpreter::Errors::CONGRUENCE_INVALID)
|
27
|
+
STDERR.puts CONGRUENCE_INVALID_MSG
|
28
|
+
exit(1)
|
29
|
+
|
30
|
+
elsif(e == PolynomialInterpreter::Errors::LHS_POLYNOMIAL_INVALID)
|
31
|
+
STDERR.puts LHS_INVALID_MSG
|
32
|
+
exit(1)
|
33
|
+
|
34
|
+
elsif(e == PolynomialInterpreter::Errors::RHS_POLYNOMIAL_INVALID)
|
35
|
+
STDERR.puts RHS_INVALID_MSG
|
36
|
+
exit(1)
|
37
|
+
|
38
|
+
elsif(e == PolynomialInterpreter::Errors::MOD_INVALID)
|
39
|
+
STDERR.puts MOD_INVALID_MSG
|
40
|
+
exit(1)
|
41
|
+
|
42
|
+
else
|
43
|
+
raise e
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
solutions = CongruenceSolver.lift(coeffs, mod).sort
|
48
|
+
|
49
|
+
if solutions.empty?
|
50
|
+
puts "No solution."
|
51
|
+
else
|
52
|
+
puts "Solutions:"
|
53
|
+
solutions.each_with_index {|sol, i| puts "(#{i}) #{sol}"}
|
54
|
+
end
|
data/bin/setup
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'congruence_solver/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "congruence_solver"
|
8
|
+
spec.version = CongruenceSolver::VERSION
|
9
|
+
spec.authors = ["lane"]
|
10
|
+
spec.email = ["lane.barlow@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "A gem for solving polynomial congruences."
|
13
|
+
spec.description = "Provides a class (CongruenceSolver) for finding the modular zeros of a
|
14
|
+
polynomial (given the coefficients and modulus) and a binary (csolve) to
|
15
|
+
to solve your congruences at the command line."
|
16
|
+
spec.homepage = "https://github.com/laneb/congruence_solver"
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split("\n")
|
19
|
+
spec.files += `git submodule --quiet foreach pwd`.split("\n").map do |abs_dir|
|
20
|
+
rel_dir = abs_dir.gsub(Dir::pwd, "")
|
21
|
+
Dir::entries(rel_dir).select {|f| File::file? f}
|
22
|
+
end.flatten
|
23
|
+
spec.bindir = "bin"
|
24
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
|
27
|
+
spec.extensions << "ext/congruence_solver/extconf.rb"
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
30
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
31
|
+
spec.add_development_dependency "rspec", "~> 2.4"
|
32
|
+
spec.add_development_dependency "rake-compiler", "~>0.9"
|
33
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
class PolynomialInterpreter
|
2
|
+
module Errors
|
3
|
+
POLYNOMIAL_INVALID = ArgumentError.new "polynomial invalid"
|
4
|
+
CONGRUENCE_INVALID = ArgumentError.new "congruence invalid"
|
5
|
+
LHS_POLYNOMIAL_INVALID = ArgumentError.new "lhs polynomial invalid"
|
6
|
+
RHS_POLYNOMIAL_INVALID = ArgumentError.new "rhs polynomial invalid"
|
7
|
+
MOD_INVALID = ArgumentError.new "mod invalid"
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def self.read_congruence(input_congruence)
|
12
|
+
match_data = input_congruence.match(/^(.*)=(.*) +(?:mod +(.*)|\(mod +(.*)\))$/)
|
13
|
+
|
14
|
+
if match_data.nil?
|
15
|
+
raise Errors::CONGRUENCE_INVALID
|
16
|
+
end
|
17
|
+
|
18
|
+
lhs = match_data[1]
|
19
|
+
rhs = match_data[2]
|
20
|
+
mod = match_data[3] || match_data[4]
|
21
|
+
|
22
|
+
begin
|
23
|
+
lh_coeffs = read_coeffs(lhs.gsub(" ", ""))
|
24
|
+
rescue ArgumentError => e
|
25
|
+
if(e == Errors::POLYNOMIAL_INVALID)
|
26
|
+
raise Errors::LHS_POLYNOMIAL_INVALID
|
27
|
+
else
|
28
|
+
raise e
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
rh_coeffs = read_coeffs(rhs.gsub(" ", ""))
|
34
|
+
rescue ArgumentError => e
|
35
|
+
if e == Errors::POLYNOMIAL_INVALID
|
36
|
+
raise Errors::RHS_POLYNOMIAL_INVALID
|
37
|
+
else
|
38
|
+
raise e
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if mod !~ /\d+/ or mod.to_i < 2
|
43
|
+
raise Errors::MOD_INVALID
|
44
|
+
end
|
45
|
+
|
46
|
+
0.upto rh_coeffs.length-1 do |idx|
|
47
|
+
unless rh_coeffs[idx].nil?
|
48
|
+
lh_coeffs[idx] ||= 0
|
49
|
+
lh_coeffs[idx] -= rh_coeffs[idx]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
[lh_coeffs, mod.to_i]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.read_coeffs(input_polynomial)
|
57
|
+
if input_polynomial == ""
|
58
|
+
raise Errors::POLYNOMIAL_INVALID
|
59
|
+
end
|
60
|
+
|
61
|
+
last_var = nil
|
62
|
+
coeffs = Array.new
|
63
|
+
|
64
|
+
loop do
|
65
|
+
input_polynomial.slice!(/^(\d+)\*?/)
|
66
|
+
match_data_coe = Regexp.last_match
|
67
|
+
|
68
|
+
input_polynomial.slice!(/^([a-zA-Z])(?:\^(\d+))?/)
|
69
|
+
match_data_exp = Regexp.last_match
|
70
|
+
|
71
|
+
if match_data_coe.nil? and match_data_exp.nil?
|
72
|
+
raise ArgumentError, INVALID_POLYNOMIAL_MSG
|
73
|
+
else
|
74
|
+
if match_data_exp.nil?
|
75
|
+
coe = match_data_coe[1].to_i
|
76
|
+
exp = 0
|
77
|
+
else
|
78
|
+
unless last_var.nil? or last_var == match_data_exp[1]
|
79
|
+
raise Errors::POLYNOMIAL_INVALID
|
80
|
+
end
|
81
|
+
|
82
|
+
last_var = match_data_exp[1]
|
83
|
+
|
84
|
+
if match_data_coe.nil?
|
85
|
+
coe = 1
|
86
|
+
else
|
87
|
+
coe = match_data_coe[1].to_i
|
88
|
+
end
|
89
|
+
|
90
|
+
if match_data_exp[2].nil?
|
91
|
+
exp = 1
|
92
|
+
else
|
93
|
+
exp = match_data_exp[2].to_i
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
coeffs[exp] ||= 0
|
99
|
+
coeffs[exp] += coe.to_i
|
100
|
+
|
101
|
+
break if input_polynomial.length == 0
|
102
|
+
|
103
|
+
op = input_polynomial.slice!(0)
|
104
|
+
|
105
|
+
unless op.match /[-+]/
|
106
|
+
raise Errors::POLYNOMIAL_INVALID
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
0.upto(coeffs.length-1) do |idx|
|
111
|
+
coeffs[idx] ||= 0
|
112
|
+
end
|
113
|
+
|
114
|
+
coeffs
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "congruence_solver"
|
2
|
+
|
3
|
+
RSpec.describe CongruenceSolver do
|
4
|
+
describe "::lift" do
|
5
|
+
it "expects 3 arguments" do
|
6
|
+
expect {CongruenceSolver.lift()}.to raise_error ArgumentError
|
7
|
+
expect {CongruenceSolver.lift(0)}.to raise_error ArgumentError
|
8
|
+
expect {CongruenceSolver.lift([1,2], 3)}.not_to raise_error
|
9
|
+
expect {CongruenceSolver.lift([1], [1], nil)}.to raise_error ArgumentError
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
it "solves individual polynomial congruences defined by their coefficients and mod" do
|
14
|
+
coeffs = [-1, 0, 4]
|
15
|
+
mod = 5
|
16
|
+
expect(CongruenceSolver.lift(coeffs, mod).sort).to eq [2, 3]
|
17
|
+
|
18
|
+
coeffs = [-3, 4, 9]
|
19
|
+
mod = 49
|
20
|
+
expect(CongruenceSolver.lift(coeffs, mod).sort).to eq []
|
21
|
+
|
22
|
+
coeffs = [1, -4, 4]
|
23
|
+
mod = 5104
|
24
|
+
expect(CongruenceSolver.lift(coeffs, mod).sort).to eq []
|
25
|
+
|
26
|
+
coeffs = [4, -4, 1]
|
27
|
+
mod = 5104
|
28
|
+
expect(CongruenceSolver.lift(coeffs, mod).sort).to eq [2, 1278, 2554, 3830]
|
29
|
+
|
30
|
+
coeffs = Array.new(500, 0)
|
31
|
+
coeffs[0] = -1
|
32
|
+
coeffs[500] = 1
|
33
|
+
mod = 15
|
34
|
+
expect(CongruenceSolver.lift(coeffs, mod).sort).to eq [1, 2, 4, 7, 8, 11, 13, 14]
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/spec/csolve_spec.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require "polynomial_interpreter"
|
2
|
+
|
3
|
+
RSpec.describe PolynomialInterpreter do
|
4
|
+
describe "::read_congruence" do
|
5
|
+
context "when input contains no '\='" do
|
6
|
+
it "raises an ArgumentError" do
|
7
|
+
not_congruence = "x^2 (mod 5)"
|
8
|
+
expect {PolynomialInterpreter.read_congruence not_congruence}.to raise_error ArgumentError, "congruence invalid"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "when input contains no mod" do
|
13
|
+
it "raises an ArgumentError" do
|
14
|
+
not_congruence = "x^2 = 3"
|
15
|
+
expect {PolynomialInterpreter.read_congruence not_congruence}.to raise_error ArgumentError, "congruence invalid"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when input contains an invalid mod" do
|
20
|
+
it "raises an ArgumentError" do
|
21
|
+
not_congruence = "x = 5 (mod)"
|
22
|
+
expect {PolynomialInterpreter.read_congruence not_congruence}.to raise_error ArgumentError, "congruence invalid"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when lhs is empty" do
|
27
|
+
it "raises an ArgumentError" do
|
28
|
+
congruence = "= 3 (mod 4)"
|
29
|
+
expect {PolynomialInterpreter.read_congruence congruence}.to raise_error ArgumentError, "lhs polynomial invalid"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when lhs contains invalid characters" do
|
34
|
+
it "raises an ArgumentError" do
|
35
|
+
congruence = "x! = 3 (mod 5)"
|
36
|
+
expect {PolynomialInterpreter.read_congruence congruence}.to raise_error ArgumentError, "lhs polynomial invalid"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "when lhs contains more than one distinct variables" do
|
41
|
+
it "raises an ArgumentError" do
|
42
|
+
congruence = "x^2 + y = 0 (mod 6)"
|
43
|
+
expect {PolynomialInterpreter.read_congruence congruence}.to raise_error ArgumentError, "lhs polynomial invalid"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when lhs contains negative power" do
|
48
|
+
it "raises an ArgumentError" do
|
49
|
+
congruence = "x^-1 = 2 (mod 35)"
|
50
|
+
expect {PolynomialInterpreter.read_congruence congruence}.to raise_error ArgumentError, "lhs polynomial invalid"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when rhs is empty" do
|
55
|
+
it "raises an ArgumentError" do
|
56
|
+
congruence = "x^3 = (mod 4)"
|
57
|
+
expect {PolynomialInterpreter.read_congruence congruence}.to raise_error ArgumentError, "rhs polynomial invalid"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when rhs contains invalid characters" do
|
62
|
+
it "raises an ArgumentError" do
|
63
|
+
congruence = "3 = x! (mod 5)"
|
64
|
+
expect {PolynomialInterpreter.read_congruence congruence}.to raise_error ArgumentError, "rhs polynomial invalid"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when rhs contains more than one distinct variables" do
|
69
|
+
it "raises an ArgumentError" do
|
70
|
+
congruence = "0 = x^2 + y (mod 6)"
|
71
|
+
expect {PolynomialInterpreter.read_congruence congruence}.to raise_error ArgumentError, "rhs polynomial invalid"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when rhs contains negative power" do
|
76
|
+
it "raises an ArgumentError" do
|
77
|
+
congruence = "2 = x^-1 (mod 35)"
|
78
|
+
expect {PolynomialInterpreter.read_congruence congruence}.to raise_error ArgumentError, "rhs polynomial invalid"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when input is a valid polynomial congruence" do
|
83
|
+
it "formats congruence as a single polynomial congruent to 0" do
|
84
|
+
congruence = "45x^5 + 5 + 3x ^6 + 5x^2 + x + 3x^5=x^9 + 9 x (mod 16)"
|
85
|
+
expect( PolynomialInterpreter.read_congruence(congruence)).to eq [[-1, 0, 0, 3, 48, 0, 0, 5, -8, 5].reverse, 16]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
4
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
5
|
+
# files.
|
6
|
+
#
|
7
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
8
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
9
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
10
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
11
|
+
# a separate helper file that requires the additional dependencies and performs
|
12
|
+
# the additional setup, and require it from the spec files that actually need
|
13
|
+
# it.
|
14
|
+
#
|
15
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
16
|
+
# users commonly want.
|
17
|
+
#
|
18
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
19
|
+
RSpec.configure do |config|
|
20
|
+
# rspec-expectations config goes here. You can use an alternate
|
21
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
22
|
+
# assertions if you prefer.
|
23
|
+
config.expect_with :rspec do |expectations|
|
24
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
25
|
+
# and `failure_message` of custom matchers include text for helper methods
|
26
|
+
# defined using `chain`, e.g.:
|
27
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
28
|
+
# # => "be bigger than 2 and smaller than 4"
|
29
|
+
# ...rather than:
|
30
|
+
# # => "be bigger than 2"
|
31
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
35
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
36
|
+
config.mock_with :rspec do |mocks|
|
37
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
38
|
+
# a real object. This is generally recommended, and will default to
|
39
|
+
# `true` in RSpec 4.
|
40
|
+
mocks.verify_partial_doubles = true
|
41
|
+
end
|
42
|
+
|
43
|
+
# The settings below are suggested to provide a good initial experience
|
44
|
+
# with RSpec, but feel free to customize to your heart's content.
|
45
|
+
=begin
|
46
|
+
# These two settings work together to allow you to limit a spec run
|
47
|
+
# to individual examples or groups you care about by tagging them with
|
48
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
49
|
+
# get run.
|
50
|
+
config.filter_run :focus
|
51
|
+
config.run_all_when_everything_filtered = true
|
52
|
+
|
53
|
+
# Allows RSpec to persist some state between runs in order to support
|
54
|
+
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
55
|
+
# you configure your source control system to ignore this file.
|
56
|
+
config.example_status_persistence_file_path = "spec/examples.txt"
|
57
|
+
|
58
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
59
|
+
# recommended. For more details, see:
|
60
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
61
|
+
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
62
|
+
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
63
|
+
config.disable_monkey_patching!
|
64
|
+
|
65
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
66
|
+
# be too noisy due to issues in dependencies.
|
67
|
+
config.warnings = true
|
68
|
+
|
69
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
70
|
+
# file, and it's useful to allow more verbose output when running an
|
71
|
+
# individual spec file.
|
72
|
+
if config.files_to_run.one?
|
73
|
+
# Use the documentation formatter for detailed output,
|
74
|
+
# unless a formatter has already been configured
|
75
|
+
# (e.g. via a command-line flag).
|
76
|
+
config.default_formatter = 'doc'
|
77
|
+
end
|
78
|
+
|
79
|
+
# Print the 10 slowest examples and example groups at the
|
80
|
+
# end of the spec run, to help surface which specs are running
|
81
|
+
# particularly slow.
|
82
|
+
config.profile_examples = 10
|
83
|
+
|
84
|
+
# Run specs in random order to surface order dependencies. If you find an
|
85
|
+
# order dependency and want to debug it, you can fix the order by providing
|
86
|
+
# the seed, which is printed after each run.
|
87
|
+
# --seed 1234
|
88
|
+
config.order = :random
|
89
|
+
|
90
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
91
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
92
|
+
# test failures related to randomization by passing the same `--seed` value
|
93
|
+
# as the one that triggered the failure.
|
94
|
+
Kernel.srand config.seed
|
95
|
+
=end
|
96
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: congruence_solver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- lane
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake-compiler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.9'
|
69
|
+
description: "Provides a class (CongruenceSolver) for finding the modular zeros of
|
70
|
+
a \n polynomial (given the coefficients and modulus) and
|
71
|
+
a binary (csolve) to \n to solve your congruences at the
|
72
|
+
command line."
|
73
|
+
email:
|
74
|
+
- lane.barlow@gmail.com
|
75
|
+
executables:
|
76
|
+
- csolve.rb
|
77
|
+
- setup
|
78
|
+
extensions:
|
79
|
+
- ext/congruence_solver/extconf.rb
|
80
|
+
extra_rdoc_files: []
|
81
|
+
files:
|
82
|
+
- ".gitignore"
|
83
|
+
- ".gitmodules"
|
84
|
+
- ".rspec"
|
85
|
+
- ".travis.yml"
|
86
|
+
- README.md
|
87
|
+
- Rakefile
|
88
|
+
- bench/bench_tools.rb
|
89
|
+
- bench/solve_congruence_bm.rb
|
90
|
+
- bin/csolve.rb
|
91
|
+
- bin/setup
|
92
|
+
- congruence_solver.gemspec
|
93
|
+
- ext/congruence_solver/extconf.rb
|
94
|
+
- lib/congruence_solver.rb
|
95
|
+
- lib/congruence_solver/version.rb
|
96
|
+
- lib/polynomial_interpreter.rb
|
97
|
+
- spec/congruence_solver_spec.rb
|
98
|
+
- spec/csolve_spec.rb
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
homepage: https://github.com/laneb/congruence_solver
|
101
|
+
licenses: []
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 2.4.5
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: A gem for solving polynomial congruences.
|
123
|
+
test_files: []
|