knapsack_solver 0.1.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/LICENSE +19 -0
- data/README.md +2 -0
- data/bin/knapsack_solver +12 -0
- data/knapsack_solver.gemspec +33 -0
- data/lib/knapsack_solver.rb +2 -0
- data/lib/knapsack_solver/cli.rb +49 -0
- data/lib/knapsack_solver/cli_option_checker.rb +83 -0
- data/lib/knapsack_solver/cli_option_parser.rb +68 -0
- data/lib/knapsack_solver/dataset.rb +44 -0
- data/lib/knapsack_solver/graph_printer.rb +115 -0
- data/lib/knapsack_solver/instance.rb +48 -0
- data/lib/knapsack_solver/output_printer.rb +97 -0
- data/lib/knapsack_solver/solver.rb +118 -0
- data/lib/knapsack_solver/solving_methods/branch_and_bound.rb +111 -0
- data/lib/knapsack_solver/solving_methods/dynamic_programming.rb +116 -0
- data/lib/knapsack_solver/solving_methods/fptas.rb +47 -0
- data/lib/knapsack_solver/solving_methods/heuristic_price_weight.rb +56 -0
- data/lib/knapsack_solver/version.rb +5 -0
- data/test/knapsack_solver_matchers.rb +103 -0
- data/test/knapsack_solver_spec.rb +261 -0
- data/test/spec_helper.rb +5 -0
- metadata +141 -0
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'knapsack_solver/solving_methods/dynamic_programming'
|
2
|
+
|
3
|
+
module KnapsackSolver
|
4
|
+
# This class implements methods for solving 0/1 knapsack problem using Fully
|
5
|
+
# Polynomial Time Approximation Scheme.
|
6
|
+
class Fptas
|
7
|
+
# Initializes 0/1 knapsack FPTAS solver.
|
8
|
+
#
|
9
|
+
# @param instance [Instance] Instance of a 0/1 knapsack problem.
|
10
|
+
# @param epsilon [Instances] Maximum allowed relative error of the resulting price.
|
11
|
+
def initialize(instance, epsilon)
|
12
|
+
@instance = instance
|
13
|
+
@epsilon = epsilon.to_f
|
14
|
+
@orig_prices = @instance.things.map(&:price)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Solve the instance of 0/1 knapsack problem using FPTAS.
|
18
|
+
#
|
19
|
+
# @return [Hash] resulting price and thing configuration (0 = thing is not in the knapsack, 1 = thing is there)
|
20
|
+
def run
|
21
|
+
modify_prices_for_epsilon!
|
22
|
+
r = DynamicProgramming.new(@instance).run
|
23
|
+
p = get_normal_price_from_fptas(r[:config])
|
24
|
+
{ price: p, config: r[:config] }
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
# Modifies prices of the things according to the supplied epsilon constant
|
30
|
+
# to achieve max. allowed relative error.
|
31
|
+
def modify_prices_for_epsilon!
|
32
|
+
m = @instance.things.max_by(&:price).price
|
33
|
+
k = (@epsilon * m) / @instance.things.size
|
34
|
+
@instance.things.each { |t| t.price = (t.price.to_f / k).floor }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Computes resulting price using original unmodified prices of things.
|
38
|
+
#
|
39
|
+
# @param presenve [Array] configuration variables vector
|
40
|
+
# @return [Integer] total price of things in the knapsack
|
41
|
+
def get_normal_price_from_fptas(presence)
|
42
|
+
@instance.things.reduce(0) do |price, t|
|
43
|
+
price + ((presence[t.index] != 0 ? @orig_prices[t.index] : 0))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module KnapsackSolver
|
2
|
+
# This class implements methods for solving 0/1 knapsack problem using
|
3
|
+
# simple heuristic by price to weight ratio. Things with the best price to
|
4
|
+
# weight ratio are selected first.
|
5
|
+
class HeuristicPriceToWeight
|
6
|
+
# Initializes instance of 0/1 knapsack problem solver using simple
|
7
|
+
# heuristic by price to weight ratio.
|
8
|
+
#
|
9
|
+
# @param instance [Instance] 0/1 knapsack problem instance
|
10
|
+
def initialize(instance)
|
11
|
+
@instance = instance
|
12
|
+
@config = Array.new(instance.things.size) { 0 }
|
13
|
+
@sorted_things = instance.things.sort do |a, b|
|
14
|
+
(b.price.to_f / b.weight) <=> (a.price.to_f / a.weight)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Solve the instance of 0/1 knapsack problem.
|
19
|
+
#
|
20
|
+
# @return [Hash] resulting price and thing configuration (0 = thing is not in the knapsack, 1 = thing is there)
|
21
|
+
def run
|
22
|
+
solve
|
23
|
+
{ price: @best_price, config: @best_config }
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
# Solve the instance of 0/1 knapsack problem.
|
29
|
+
def solve
|
30
|
+
@sorted_things.each do |thing|
|
31
|
+
break if (config_weight + thing.weight) > @instance.weight_capacity
|
32
|
+
@config[thing.index] = 1
|
33
|
+
end
|
34
|
+
@best_price = config_price
|
35
|
+
@best_config = @config.dup
|
36
|
+
end
|
37
|
+
|
38
|
+
# Gets total weight of things present in the knapsack.
|
39
|
+
#
|
40
|
+
# @return [Integer] total weight
|
41
|
+
def config_weight
|
42
|
+
@config.each_with_index.reduce(0) do |weight, (presence, index)|
|
43
|
+
weight + presence * @instance.things[index].weight
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Gets total price of things present in the knapsack.
|
48
|
+
#
|
49
|
+
# @return [Integer] total price
|
50
|
+
def config_price
|
51
|
+
@config.each_with_index.reduce(0) do |price, (presence, index)|
|
52
|
+
price + presence * @instance.things[index].price
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'rspec/expectations'
|
2
|
+
|
3
|
+
def comment_lines(file_path)
|
4
|
+
File.open(file_path, 'r').each_line.select { |l| l.chars.first == '#' }
|
5
|
+
end
|
6
|
+
|
7
|
+
def data_lines(file_path)
|
8
|
+
File.open(file_path, 'r').each_line.select { |l| l.chars.first != '#' }
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
RSpec::Matchers.define :be_a_regular_file do
|
13
|
+
match do |actual|
|
14
|
+
File.file?(actual)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec::Matchers.define :be_an_empty_directory do
|
19
|
+
match do |actual|
|
20
|
+
Dir.empty?(actual)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec::Matchers.define :be_a_valid_results_file do
|
25
|
+
match do |actual|
|
26
|
+
begin
|
27
|
+
comment_lines = comment_lines(actual)
|
28
|
+
data_lines = data_lines(actual)
|
29
|
+
# It must have 3 lines: 2 comments and >= 1 data line
|
30
|
+
return false if comment_lines.size != 2 || data_lines.size < 1
|
31
|
+
# Data line must consist of non-negative numbers and array 1 and 0
|
32
|
+
data_lines.each do |l|
|
33
|
+
l.scan(/\[[^\]]*\]/).first.tr('[]', '').split(',').map { |n| n.to_i }.each do |i|
|
34
|
+
return false if i != 0 && i != 1
|
35
|
+
end
|
36
|
+
l.scan(/[0-9\.]+/).map { |n| n.to_f }.each do |i|
|
37
|
+
return false if i < 0
|
38
|
+
end
|
39
|
+
end
|
40
|
+
true
|
41
|
+
rescue
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
RSpec::Matchers.define :be_a_equal_to_results_file do |good|
|
48
|
+
match do |actual|
|
49
|
+
begin
|
50
|
+
lines = data_lines(actual)
|
51
|
+
good_lines = data_lines(good)
|
52
|
+
return false if lines.size != good_lines.size
|
53
|
+
lines.each_with_index do |l, i|
|
54
|
+
# Check price and configuration
|
55
|
+
return false if l.split(']').first != good_lines[i].split(']').first
|
56
|
+
# Check relative error
|
57
|
+
return false if l.split().last != good_lines[i].split().last
|
58
|
+
return false if l.split().size != good_lines[i].split().size
|
59
|
+
end
|
60
|
+
true
|
61
|
+
rescue
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
RSpec::Matchers.define :be_a_equal_to_stats_file do |good|
|
68
|
+
match do |actual|
|
69
|
+
begin
|
70
|
+
lines = data_lines(actual)
|
71
|
+
good_lines = data_lines(good)
|
72
|
+
return false if lines.size != good_lines.size
|
73
|
+
lines.each_with_index do |l, i|
|
74
|
+
# Check average price
|
75
|
+
return false if l.split().first != good_lines[i].split().first
|
76
|
+
# Check average relative error
|
77
|
+
return false if l.split().last != good_lines[i].split().last
|
78
|
+
return false if l.split().size != good_lines[i].split().size
|
79
|
+
end
|
80
|
+
true
|
81
|
+
rescue
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
RSpec::Matchers.define :be_a_valid_stats_file do
|
88
|
+
match do |actual|
|
89
|
+
begin
|
90
|
+
comment_lines = comment_lines(actual)
|
91
|
+
data_lines = data_lines(actual)
|
92
|
+
# It must have 3 lines: 2 comments and 1 data line
|
93
|
+
return false if comment_lines.size != 2 || data_lines.size != 1
|
94
|
+
# Data line must consist of non-negative numbers
|
95
|
+
data_lines.first.split.map { |i| i.to_f }.each do |n|
|
96
|
+
return false if n < 0
|
97
|
+
end
|
98
|
+
true
|
99
|
+
rescue
|
100
|
+
false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'tmpdir'
|
3
|
+
require_relative 'spec_helper'
|
4
|
+
require_relative 'knapsack_solver_matchers'
|
5
|
+
require_relative '../lib/knapsack_solver/cli.rb'
|
6
|
+
require_relative '../lib/knapsack_solver/version.rb'
|
7
|
+
|
8
|
+
module FileHelper
|
9
|
+
def file_list(directory, files)
|
10
|
+
files.map { |f| File.join(directory, f) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ArgumentHelper
|
15
|
+
def args(args_string = nil)
|
16
|
+
return [] if args_string.nil?
|
17
|
+
args_string.split
|
18
|
+
end
|
19
|
+
|
20
|
+
def args_in_dataset_out_file(args_string, tmpdir)
|
21
|
+
args(args_string) + ['-o', tmpdir, 'test/datasets/size_4.dataset', 'test/datasets/size_10.dataset']
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe KnapsackSolver::CLI do
|
26
|
+
include FileHelper
|
27
|
+
include ArgumentHelper
|
28
|
+
|
29
|
+
subject(:cli) { KnapsackSolver::CLI }
|
30
|
+
|
31
|
+
context 'options' do
|
32
|
+
it 'recognizes invalid options' do
|
33
|
+
expect { cli.run(args('-a')) }.to raise_error(OptionParser::InvalidOption)
|
34
|
+
expect { cli.run(args('-k -h')) }.to raise_error(OptionParser::InvalidOption)
|
35
|
+
expect { cli.run(args('-x -b')) }.to raise_error(OptionParser::InvalidOption)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'detects missing arguments' do
|
39
|
+
expect { cli.run(args('-o')) }.to raise_error(OptionParser::MissingArgument)
|
40
|
+
expect { cli.run(args('-g')) }.to raise_error(OptionParser::MissingArgument)
|
41
|
+
end
|
42
|
+
|
43
|
+
it '-h has the top priority among valid options' do
|
44
|
+
expect { cli.run(args('-b -v -h')) }.to output(/Usage:/).to_stdout
|
45
|
+
expect { cli.run(args('-b -h -v')) }.to output(/Usage:/).to_stdout
|
46
|
+
expect { cli.run(args('-h -b -v')) }.to output(/Usage:/).to_stdout
|
47
|
+
end
|
48
|
+
|
49
|
+
it '-v has a second from the top priority among valid options' do
|
50
|
+
expect { cli.run(args('-v -h')) }.to output(/Usage:/).to_stdout
|
51
|
+
expect { cli.run(args('-v -b')) }.to output(/knapsack_solver #{KnapsackSolver::VERSION}/).to_stdout
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'at least one method of solving must be selected' do
|
55
|
+
Dir.mktmpdir() do |tmpdir|
|
56
|
+
expect { cli.run(args()) }.to raise_error(/At least one method of solving must be requested/)
|
57
|
+
expect { cli.run(args_in_dataset_out_file('-b', tmpdir)) }.not_to raise_error
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'FPTAS must have epsilon constant provided' do
|
62
|
+
Dir.mktmpdir() do |tmpdir|
|
63
|
+
expect { cli.run(args_in_dataset_out_file('-f', tmpdir)) }.to raise_error(/Missing FPTAS epsilon constant/)
|
64
|
+
expect { cli.run(args_in_dataset_out_file('-f -e 0.5', tmpdir)) }.not_to raise_error
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'FPTAS epsilon constant must be number from range (0,1)' do
|
69
|
+
Dir.mktmpdir() do |tmpdir|
|
70
|
+
expect { cli.run(args_in_dataset_out_file('-f -e asdf', tmpdir)) }.to raise_error(/FPTAS epsilon must be number from range \(0,1\)/)
|
71
|
+
expect { cli.run(args_in_dataset_out_file('-f -e 0.5x', tmpdir)) }.to raise_error(/FPTAS epsilon must be number from range \(0,1\)/)
|
72
|
+
expect { cli.run(args_in_dataset_out_file('-f -e -0.3', tmpdir)) }.to raise_error(/FPTAS epsilon must be number from range \(0,1\)/)
|
73
|
+
expect { cli.run(args_in_dataset_out_file('-f -e 0', tmpdir)) }.to raise_error(/FPTAS epsilon must be number from range \(0,1\)/)
|
74
|
+
expect { cli.run(args_in_dataset_out_file('-f -e 0.01', tmpdir)) }.not_to raise_error
|
75
|
+
expect { cli.run(args_in_dataset_out_file('-f -e 0.99', tmpdir)) }.not_to raise_error
|
76
|
+
expect { cli.run(args_in_dataset_out_file('-f -e 1', tmpdir)) }.to raise_error(/FPTAS epsilon must be number from range \(0,1\)/)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'epsilon constant must not be provided when FPTAS is not selected' do
|
81
|
+
Dir.mktmpdir() do |tmpdir|
|
82
|
+
expect { cli.run(args_in_dataset_out_file('-b -e 0.5', tmpdir)) }.to raise_error(/epsilon constant must not be provided when FPTAS is not selected/)
|
83
|
+
expect { cli.run(args_in_dataset_out_file('-b -f -e 0.5', tmpdir)) }.not_to raise_error
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'positional arguments' do
|
90
|
+
it 'must have at least one dataset provided' do
|
91
|
+
expect { cli.run(args('-b')) }.to raise_error(/Missing datset file\(s\)/)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'dataset path must be a path to a regular file' do
|
95
|
+
Dir.mktmpdir do |tmpdir|
|
96
|
+
expect { cli.run(args('-b ' + tmpdir)) }.to raise_error(/is not a regular file/)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'dataset file must exist' do
|
101
|
+
Dir.mktmpdir do |tmpdir|
|
102
|
+
not_existent_file = File.join(tmpdir, 'size_4.dataset')
|
103
|
+
expect { cli.run(args('-b ' + not_existent_file)) }.to raise_error(/does not exists/)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'dataset file must be readable' do
|
108
|
+
Dir.mktmpdir do |tmpdir|
|
109
|
+
FileUtils.cp('test/datasets/size_4.dataset', tmpdir)
|
110
|
+
not_readable_file = File.join(tmpdir, 'size_4.dataset')
|
111
|
+
FileUtils.chmod('a-r', not_readable_file)
|
112
|
+
expect { cli.run(args('-b ' + not_readable_file)) }.to raise_error(/is not readable/)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'dataset file must have correct format' do
|
117
|
+
Dir.mktmpdir do |tmpdir|
|
118
|
+
invalid_files = %w(invalid_1.dataset
|
119
|
+
invalid_2.dataset
|
120
|
+
invalid_3.dataset
|
121
|
+
invalid_4.dataset
|
122
|
+
invalid_5.dataset
|
123
|
+
invalid_6.dataset
|
124
|
+
invalid_7.dataset
|
125
|
+
invalid_8.dataset)
|
126
|
+
error_messages = ['missing ID',
|
127
|
+
'first line does not contain ID',
|
128
|
+
'ID is negative',
|
129
|
+
'ID is not an integer',
|
130
|
+
'missing knapsack capacity',
|
131
|
+
'missing pairs \(price, weight\)',
|
132
|
+
'instance desctiption contains negative number',
|
133
|
+
'instance desctiption does not contain only integers']
|
134
|
+
file_list('test/invalid_datasets', invalid_files).each_with_index do |f, i|
|
135
|
+
expect { cli.run(args('-d ' + f)) }.to raise_error(/#{error_messages[i]}/)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'output of results' do
|
142
|
+
it 'directory for the output logs must exist' do
|
143
|
+
Dir.mktmpdir do |tmpdir|
|
144
|
+
not_existent_file = File.join(tmpdir, 'size_4.dataset')
|
145
|
+
expect { cli.run(args('-b -o ' + not_existent_file)) }.to raise_error(/does not exists/)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'path to a directory for the output logs must point to a directory' do
|
150
|
+
Dir.mktmpdir do |tmpdir|
|
151
|
+
FileUtils.cp('test/datasets/size_4.dataset', tmpdir)
|
152
|
+
file = File.join(tmpdir, 'size_4.dataset')
|
153
|
+
expect { cli.run(args('-b -o ' + file)) }.to raise_error(/is not a directory/)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'directory for the output logs must be writable' do
|
158
|
+
Dir.mktmpdir do |tmpdir|
|
159
|
+
not_writable_dir = File.join(tmpdir, 'dir')
|
160
|
+
Dir.mkdir(not_writable_dir)
|
161
|
+
FileUtils.chmod('a-w', not_writable_dir)
|
162
|
+
expect { cli.run(args('-b -o ' + not_writable_dir)) }.to raise_error(/is not writable/)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'directory for the graph files must exist' do
|
167
|
+
Dir.mktmpdir do |tmpdir|
|
168
|
+
not_existent_file = File.join(tmpdir, 'size_4.dataset')
|
169
|
+
expect { cli.run(args('-b -g ' + not_existent_file)) }.to raise_error(/does not exists/)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'path to a directory for the graph files must point to a directory' do
|
174
|
+
Dir.mktmpdir do |tmpdir|
|
175
|
+
FileUtils.cp('test/datasets/size_4.dataset', tmpdir)
|
176
|
+
file = File.join(tmpdir, 'size_4.dataset')
|
177
|
+
expect { cli.run(args('-b -g ' + file)) }.to raise_error(/is not a directory/)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'directory for the graph files must be writable' do
|
182
|
+
Dir.mktmpdir do |tmpdir|
|
183
|
+
not_writable_dir = File.join(tmpdir, 'dir')
|
184
|
+
Dir.mkdir(not_writable_dir)
|
185
|
+
FileUtils.chmod('a-w', not_writable_dir)
|
186
|
+
expect { cli.run(args('-b -g ' + not_writable_dir)) }.to raise_error(/is not writable/)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'writes graph files' do
|
191
|
+
Dir.mktmpdir do |tmpdir|
|
192
|
+
png_files = %w(avg_price.png avg_cpu_time.png avg_wall_clock_time.png avg_relative_error.png)
|
193
|
+
gnuplot_files = %w(avg_price.gnuplot avg_cpu_time.gnuplot avg_wall_clock_time.gnuplot avg_relative_error.gnuplot)
|
194
|
+
files = png_files + gnuplot_files
|
195
|
+
cli.run(args_in_dataset_out_file('-b -g ' + tmpdir, tmpdir))
|
196
|
+
expect(file_list(tmpdir, files)).to all be_a_regular_file
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'writes results and stats to files' do
|
201
|
+
Dir.mktmpdir do |tmpdir|
|
202
|
+
results_files = %w(size_4_branch_and_bound.results size_10_branch_and_bound.results)
|
203
|
+
stats_files = %w(size_4_branch_and_bound.stats size_10_branch_and_bound.stats)
|
204
|
+
files = results_files + stats_files
|
205
|
+
cli.run(args_in_dataset_out_file('-b', tmpdir))
|
206
|
+
expect(file_list(tmpdir, files)).to all be_a_regular_file
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'writes results and stats to stdout' do
|
211
|
+
results_files = %w(size_4_branch_and_bound.results size_10_branch_and_bound.results)
|
212
|
+
stats_files = %w(size_4_branch_and_bound.stats size_10_branch_and_bound.stats)
|
213
|
+
files = results_files + stats_files
|
214
|
+
files.each do |f|
|
215
|
+
expect { cli.run(args('-b test/datasets/size_4.dataset test/datasets/size_10.dataset')) }.to output(/#{f}/).to_stdout
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'adds relative error if an exact solving method is selected' do
|
220
|
+
expect { cli.run(args('-r test/datasets/size_4.dataset test/datasets/size_10.dataset')) }.not_to output(/avg_relative_error/).to_stdout
|
221
|
+
expect { cli.run(args('-f -e 0.5 -r test/datasets/size_4.dataset test/datasets/size_10.dataset')) }.not_to output(/avg_relative_error/).to_stdout
|
222
|
+
expect { cli.run(args('-b -r test/datasets/size_4.dataset test/datasets/size_10.dataset')) }.to output(/avg_relative_error/).to_stdout
|
223
|
+
expect { cli.run(args('-d -r test/datasets/size_4.dataset test/datasets/size_10.dataset')) }.to output(/avg_relative_error/).to_stdout
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'produces correct results' do
|
227
|
+
Dir.mktmpdir do |tmpdir|
|
228
|
+
results_files = %w(size_10_dynamic_programming.results
|
229
|
+
size_10_fptas.results
|
230
|
+
size_10_heuristic.results
|
231
|
+
size_4_dynamic_programming.results
|
232
|
+
size_4_fptas.results
|
233
|
+
size_4_heuristic.results)
|
234
|
+
stats_files = %w(size_10_dynamic_programming.stats
|
235
|
+
size_10_fptas.stats
|
236
|
+
size_10_heuristic.stats
|
237
|
+
size_4_dynamic_programming.stats
|
238
|
+
size_4_fptas.stats
|
239
|
+
size_4_heuristic.stats)
|
240
|
+
good_results_files = results_files.map { |f| File.join('test/output_logs', f) }
|
241
|
+
good_stats_files = stats_files.map { |f| File.join('test/output_logs', f) }
|
242
|
+
files = results_files + stats_files
|
243
|
+
cli.run(args_in_dataset_out_file('-b -d -r -f -e 0.5', tmpdir))
|
244
|
+
expect(file_list(tmpdir, files)).to all be_a_regular_file
|
245
|
+
expect(file_list(tmpdir, results_files)).to all be_a_valid_results_file
|
246
|
+
expect(file_list(tmpdir, stats_files)).to all be_a_valid_stats_file
|
247
|
+
|
248
|
+
file_list(tmpdir, results_files).each_with_index do |f, i|
|
249
|
+
expect(f).to be_a_equal_to_results_file(good_results_files[i])
|
250
|
+
end
|
251
|
+
|
252
|
+
file_list(tmpdir, stats_files).each_with_index do |f, i|
|
253
|
+
expect(f).to be_a_equal_to_stats_file(good_stats_files[i])
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|