knapsack_solver 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|