IPOAlgorithm 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/IPOAlgorithm.rb +21 -0
- data/lib/IPOAlgorithm.rb +393 -0
- data/lib/IPOAlgorithm/Parameter.rb +12 -0
- data/lib/IPOAlgorithm/Run.rb +8 -0
- data/test/incorrect_file.csv +4 -0
- data/test/infeasible_pairs.csv +20 -0
- data/test/infeasible_pairs_impossible_combination_HG.csv +6 -0
- data/test/infeasible_pairs_impossible_combination_VG.csv +4 -0
- data/test/infeasible_pairs_not_all_pairs.csv +20 -0
- data/test/infeasible_pairs_unspecified_level.csv +20 -0
- data/test/input_file.csv +3 -0
- data/test/input_file_impossible_combination_HG.csv +3 -0
- data/test/input_file_impossible_combination_VG.csv +3 -0
- data/test/test_IPOAlgorithm.rb +120 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ab7632425eddb21f2030c19dfaca5f7f83957d62
|
4
|
+
data.tar.gz: 242b2c3342e31338d7a4797cf22767f6d6a684f2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b3d92e4fe19edda9ef2137f717c4cf3dd000aaadb27c75a0ee4d952569bd8ef98239c63954fd26efd33378ecb3150711ae37147b3706f06a5c23b14e272ad0a7
|
7
|
+
data.tar.gz: 2a672ea546523b5e2e622c47e3bcda38cc1171e4f3d601a5f39cfb52b175098bd464bc0f1ad83c1218b50f96dfc97c70fc0e31c557b3d346c2f983419ab85c59
|
data/bin/IPOAlgorithm.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative '../lib/IPOAlgorithm.rb'
|
2
|
+
|
3
|
+
#
|
4
|
+
#Exception handling for the arguments passed to the algorithm
|
5
|
+
#
|
6
|
+
|
7
|
+
#The algorithm raises an error if the number of arguments is not 2
|
8
|
+
raise ArgumentError, "The IPO algorithm needs at least 2 arguments and a maximum of 4." unless 2 <= ARGV.size && ARGV.size <=4
|
9
|
+
|
10
|
+
#The first argument must be an integer indicating the number of factors/parameters
|
11
|
+
raise ArgumentError, "The first parameter must be an Integer i s.t. 2 <= i <= 26 indicating the number of factors." unless (no_parameters = Integer(ARGV.first)) && no_parameters >= 2 && no_parameters < 27
|
12
|
+
|
13
|
+
#The second argument must be an array that is matched against a regular expression to see if it is of the correct form
|
14
|
+
raise ArgumentError, "The second parameter must be an array of form [val1, val2, ... valn] where each vali indicates the number of levels for factor i and n is the number of factors already specified as the first parameter to the algorithm." unless /\[(\d+,)+\d+\]/.match ARGV[1]
|
15
|
+
|
16
|
+
#The 'levels' array will contain the number of values (levels) for each parameter
|
17
|
+
levels = eval ARGV[1]
|
18
|
+
raise ArgumentError, "The second parameter must be an array of form [val1, val2, ... valn] where each vali indicates the number of levels for factor i and n is the number of factors already specified as the first parameter to the algorithm." if levels.size != no_parameters
|
19
|
+
|
20
|
+
puts IPOAlgorithm.new(no_parameters, levels,ARGV[2],ARGV[3]).output_table
|
21
|
+
|
data/lib/IPOAlgorithm.rb
ADDED
@@ -0,0 +1,393 @@
|
|
1
|
+
|
2
|
+
require 'terminal-table'
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
class IPOAlgorithm
|
6
|
+
|
7
|
+
attr_reader :no_parameters
|
8
|
+
attr_reader :levels
|
9
|
+
attr_reader :parameters
|
10
|
+
attr_reader :runs
|
11
|
+
attr_reader :actual_parameters
|
12
|
+
attr_reader :infeasible_pairs
|
13
|
+
attr_reader :output_table
|
14
|
+
def initialize(no_parameters,levels,csv=nil,infeasible_csv=nil)
|
15
|
+
#
|
16
|
+
#preliminary checks of the arguments passed to the algorithm and initialization of the '@no_parameters' instance_variable
|
17
|
+
#
|
18
|
+
raise ArgumentError, "The number of parameters passed as an argument must be an Integer." unless no_parameters.is_a? Integer
|
19
|
+
@no_parameters = no_parameters
|
20
|
+
raise ArgumentError, "The IPOAlgorithm can process at most 26 parameters (at least 2 parameters must be provided)" if @no_parameters >26 || @no_parameters < 2
|
21
|
+
raise ArgumentError, "The number of parameters doesn't match the size of the array argument representing the number of levels for each parameter." unless levels.size == @no_parameters
|
22
|
+
raise ArgumentError, "The number of levels for each argument must be an Integer." unless levels.all? {|l| l.is_a? Integer}
|
23
|
+
|
24
|
+
|
25
|
+
#initialization of the remaining instance_variables
|
26
|
+
@levels = levels
|
27
|
+
@runs = Array.new
|
28
|
+
@parameters = Array.new
|
29
|
+
@infeasible_pairs = Array.new
|
30
|
+
|
31
|
+
#to differentiate values of different factors, each factor is named with uppercase letters in the range [A-Z]; thus the number of factors is limited to a maximum of 26 factors
|
32
|
+
0.upto(@no_parameters-1) do |i|
|
33
|
+
@parameters << Parameter.new((65 + i).chr, @levels[i])
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
#reads the parameters and relative values from the csv file passed as an argument and checks if the number of factors and relative levels correspond to the specified values
|
38
|
+
#if a .csv file containing the infeasible pairs is passed parses this file in order to check if the specified levels are valid and represents these values by means of the format used by the internal logic of the algorithm
|
39
|
+
unless csv.nil?
|
40
|
+
@actual_parameters = Array.new
|
41
|
+
#parses the .csv file containing the factors and relative levels and saves the values in a tabular format
|
42
|
+
read_parameters_from_csv(csv)
|
43
|
+
#performs checks for inconsistencies
|
44
|
+
raise ArgumentError, "The number of factors in the CSV file doesn't correspond to the specified value" unless @actual_parameters.size == @parameters.size
|
45
|
+
@actual_parameters.each_with_index {|t,i| raise ArgumentError, "The number of levels doesn't correspond to the specified values" unless t.size - 1 == @levels[i]}
|
46
|
+
#in case an additional file for the infeasible pairs is passed parses this file
|
47
|
+
unless infeasible_csv.nil?
|
48
|
+
#reads the infeasible pairs and stores them in the '@infeasible_pairs' instance variable
|
49
|
+
read_infeasible_pairs(infeasible_csv)
|
50
|
+
|
51
|
+
@infeasible_pairs.each_with_index do |p,i|
|
52
|
+
#checks if the levels belonging to the pair exist and saves the 'a1', 'b3', ... internal representation using temporary variables
|
53
|
+
temp1 = find_value p.first
|
54
|
+
temp2 = find_value p.last
|
55
|
+
|
56
|
+
#raises ArgumentError if the levels weren't specified in the factors' .csv file
|
57
|
+
raise ArgumentError,"#{p.first} level specified in the #{infeasible_csv} file doesn't belong to any parameter in the #{csv} file" if temp1.nil?
|
58
|
+
raise ArgumentError,"#{p.last} level specified in the #{infeasible_csv} file doesn't belong to any parameter in the #{csv} file" if temp2.nil?
|
59
|
+
#overwrites the levels using the internal representation
|
60
|
+
@infeasible_pairs[i] = [temp1,temp2]
|
61
|
+
#orders the levels in alphabetically ascending order
|
62
|
+
@infeasible_pairs[i].sort_by {|p| p}
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
#generate all pairs of the first two parameters F1 and F2 and add them to the runs
|
69
|
+
|
70
|
+
@parameters[0].values.each do |value1|
|
71
|
+
@parameters[1].values.each do |value2|
|
72
|
+
@runs << (Run.new.elements << value1 << value2)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
#removes the infeasible pairs
|
77
|
+
@runs -= @infeasible_pairs
|
78
|
+
|
79
|
+
|
80
|
+
#possible termination if there are 2 factors
|
81
|
+
return nil if no_parameters == 2
|
82
|
+
|
83
|
+
#given that there are more than 2 factors there's need to repeat the next steps for all the remaining factors
|
84
|
+
2.upto(no_parameters-1) do |current_F|
|
85
|
+
#replace all runs with an extended version containing an appropriate value of the current parameter; keep track of the uncovered pairs
|
86
|
+
temp = horizontal_growth(current_F)
|
87
|
+
@runs = temp[:runs]
|
88
|
+
uncovered_pairs = temp[:uncovered_pairs]
|
89
|
+
|
90
|
+
#if there aren't any uncovered pairs end the current step else apply the vertical growth procedure
|
91
|
+
@runs += vertical_growth(uncovered_pairs,current_F) unless uncovered_pairs.empty?
|
92
|
+
end
|
93
|
+
|
94
|
+
#displays the runs obtained by the algorithm in a generic form and, if a .csv file was provided, with real values
|
95
|
+
format_output_without_CSV if csv.nil?
|
96
|
+
format_output_with_CSV unless csv.nil?
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def horizontal_growth(f)
|
103
|
+
|
104
|
+
#calculate all the pairs formed by the values of already processed factors and those of the current factor
|
105
|
+
ap = Array.new
|
106
|
+
0.upto(f-1) {|i| ap += pairs(i,f)}
|
107
|
+
|
108
|
+
#removes the infeasible pairs
|
109
|
+
|
110
|
+
ap -= @infeasible_pairs
|
111
|
+
|
112
|
+
#calculate the minimum between the number of tests already obtained and the number of levels of the current factor
|
113
|
+
c = [@parameters[f].no_levels, @runs.size].min
|
114
|
+
|
115
|
+
#the runs already obtained are extended with values of the current factor; the pairs contained by the runs thus extended, are removed from the ap (all pairs) set; given the fact that values of the factor can give rise to infeasible pairs the original IPO algorithm is thus modified:
|
116
|
+
#for each run an attempt is made to extend the run with a different level of the factor under observation; the first level which hasn't been already used and that doesn't form infeasible pairs with the values contained by the run is added to the run; the 'loop_counter' variable serves as a flag to indicate if such a value is available; if it reaches 2 then it means that all the values have been examined and there isn't such a level
|
117
|
+
#an already used level is used to extend the run; if all levels give rise to unfeasible pairs an error is raised indicating that the input to the algorithm cannot produce valid runs
|
118
|
+
|
119
|
+
#saves the already used levels in order to minimize the number of tests
|
120
|
+
already_used_level = []
|
121
|
+
tPrime = Array.new
|
122
|
+
0.upto(c-1) do |i|
|
123
|
+
#the evaluation of levels of the current factor under observation starts at a specific index in accordance with the original IPO Algorithm, so that a minimal set of runs is obtained
|
124
|
+
j=i
|
125
|
+
loop_counter=0
|
126
|
+
loop do
|
127
|
+
#attempt to extend the run with the next level of the factor
|
128
|
+
temp = ext(@runs[i].dup,@parameters[f].values[j])
|
129
|
+
#if it doesn't form infeasible pairs and it hasn't been already used the run is extended with this level
|
130
|
+
if (!pairs_for_run(temp).any? {|t| @infeasible_pairs.include? t} && !already_used_level.include?(j))
|
131
|
+
tPrime << temp
|
132
|
+
already_used_level << j
|
133
|
+
break
|
134
|
+
end
|
135
|
+
#the parsing of the levels of the factor restarts with the first level in a circular fashion; the loop_counter flag is increased
|
136
|
+
if j==@parameters[f].no_levels-1
|
137
|
+
j=0
|
138
|
+
loop_counter+=1
|
139
|
+
else
|
140
|
+
#increases the index that points at the next level of the factor
|
141
|
+
j+=1
|
142
|
+
end
|
143
|
+
#if the domain of the factor has been parsed and no valid level was encountered stop the search
|
144
|
+
break if loop_counter == 2
|
145
|
+
end
|
146
|
+
#search an already used level to extend the run in a similar fashion to the steps commented above; this time raise an error if no such level is found meaning that there is no valid run that respects the contraints of infeasible pairs
|
147
|
+
j=i
|
148
|
+
loop_counter_2 = 0
|
149
|
+
if loop_counter==2
|
150
|
+
loop do
|
151
|
+
temp = ext(@runs[i].dup,@parameters[f].values[j])
|
152
|
+
if (!pairs_for_run(temp).any? {|t| @infeasible_pairs.include? t})
|
153
|
+
tPrime << temp
|
154
|
+
break
|
155
|
+
end
|
156
|
+
if j==@parameters[f].no_levels-1
|
157
|
+
j=0
|
158
|
+
loop_counter_2+=1
|
159
|
+
else
|
160
|
+
j+=1
|
161
|
+
end
|
162
|
+
loop_counter_2
|
163
|
+
raise ArgumentError,"The input provided doesn't allow the creation of valid runs(raised in horizontal_growth)" if loop_counter_2 == 2
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
#remove the covered pairs by the run produced
|
168
|
+
ap -= pairs_for_run(tPrime[i])
|
169
|
+
end
|
170
|
+
|
171
|
+
#if the minimum is the preexistent number of runs than the method return the extended runs
|
172
|
+
return {runs: tPrime, uncovered_pairs: ap} if c == @runs.size
|
173
|
+
|
174
|
+
#
|
175
|
+
#otherwise extend the remaining runs by values of the current factor under observation that cover the maximum number of pairs in ap
|
176
|
+
#
|
177
|
+
|
178
|
+
valuePrime = nil
|
179
|
+
apPrime = []
|
180
|
+
|
181
|
+
#scan every run already produced
|
182
|
+
c.upto(@runs.size-1) do |j|
|
183
|
+
apSecond = []
|
184
|
+
#for every value of the current factor under observation
|
185
|
+
@parameters[f].values.each_with_index do |value,index|
|
186
|
+
#extend the run with the value
|
187
|
+
tempa = ext(@runs[j].dup,value)
|
188
|
+
#check if any infeasible pairs have been obtained
|
189
|
+
unless pairs_for_run(tempa).any? {|p| @infeasible_pairs.include? p}
|
190
|
+
#calculate the pairs contained by the run thus obtained
|
191
|
+
apSecond = pairs_for_run(tempa)
|
192
|
+
#find the value that adds the maximum pairwise coverage
|
193
|
+
if apSecond.find_all {|e| ap.include? e}.size > apPrime.find_all {|e| ap.include? e}.size
|
194
|
+
apPrime = apSecond
|
195
|
+
valuePrime = value
|
196
|
+
end
|
197
|
+
end
|
198
|
+
raise ArgumentError,"The input provided doesn't allow the creation of valid runs(raised in horizontal_growth)" if apSecond.nil? && index == @parameters[f].no_levels-1
|
199
|
+
end
|
200
|
+
#extend the run with the value found in the previous step
|
201
|
+
tPrime << ext(@runs[j].dup,valuePrime)
|
202
|
+
#remove the pairs covered by the newly extended run
|
203
|
+
ap -= apPrime
|
204
|
+
end
|
205
|
+
{runs: tPrime, uncovered_pairs: ap}
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
#permits to extend a run with a value
|
210
|
+
#
|
211
|
+
def ext(run, val)
|
212
|
+
run << val
|
213
|
+
end
|
214
|
+
|
215
|
+
#
|
216
|
+
#given the indexes for two parameters produces the cartesian product of their respective values
|
217
|
+
#
|
218
|
+
def pairs(f1, f2)
|
219
|
+
temp = Array.new
|
220
|
+
@parameters[f1].values.each do |value1|
|
221
|
+
@parameters[f2].values.each do |value2|
|
222
|
+
temp << (Run.new.elements << value1 << value2)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
temp
|
226
|
+
end
|
227
|
+
|
228
|
+
#
|
229
|
+
#produces all the pairs of values given a run
|
230
|
+
#
|
231
|
+
def pairs_for_run(run)
|
232
|
+
temp = Array.new
|
233
|
+
0.upto(run.size-2) do |index1|
|
234
|
+
(index1+1).upto(run.size-1) do |index2|
|
235
|
+
temp << ([] << run[index1] << run[index2])
|
236
|
+
end
|
237
|
+
end
|
238
|
+
temp
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
#Vertical Growth procedure to produce additional runs that cover the remaining uncovered pairs
|
243
|
+
#
|
244
|
+
def vertical_growth(uncovered_pairs,current_F)
|
245
|
+
current_F = current_F
|
246
|
+
tPrime = []
|
247
|
+
#scan every run
|
248
|
+
uncovered_pairs.each do |u_pair|
|
249
|
+
#calculate the index of the factor whose value doesn't form any pair with the factor under observation within the preexistent runs
|
250
|
+
uncovered_F = u_pair[0][0].ord - 97
|
251
|
+
#use the flag to indicate if the pair has been covered
|
252
|
+
flag = false
|
253
|
+
#scan every preexistent run
|
254
|
+
@runs.each do |run|
|
255
|
+
#if the run contains a placeholder to indicate the absence of a value where a value of the factor with index uncovered_F should be, overwrite the placeholder with the uncovered value
|
256
|
+
temp = run.dup
|
257
|
+
temp[uncovered_F] = u_pair[0]
|
258
|
+
if run[uncovered_F].nil? && !pairs_for_run(temp).any? {|p| @infeasible_pairs.include? p}
|
259
|
+
run[uncovered_F] = u_pair[0]
|
260
|
+
#indicate that the pair is covered and break the loop
|
261
|
+
flag = true
|
262
|
+
break
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
#if no placeholder was found create a new run
|
267
|
+
if !flag
|
268
|
+
temp = []
|
269
|
+
0.upto(current_F-1) do |j|
|
270
|
+
#use placeholders for values belonging to factors different from the one whose value isn't covered (uncovered_F)
|
271
|
+
if j != uncovered_F
|
272
|
+
temp << nil
|
273
|
+
#save the value needing coverage
|
274
|
+
else
|
275
|
+
temp << u_pair[0]
|
276
|
+
end
|
277
|
+
end
|
278
|
+
#append the second value of the uncovered pair, that belongs to the factor under observation (current_F)
|
279
|
+
temp << u_pair[1]
|
280
|
+
#add the run obtained to the set of runs
|
281
|
+
tPrime << temp
|
282
|
+
end
|
283
|
+
end
|
284
|
+
#
|
285
|
+
#replace each placeholder with a value of the corresponding factor selected in random fashion but respecting the constraints on infeasible pairs
|
286
|
+
#
|
287
|
+
#for each run obtained in the previous step
|
288
|
+
tPrime.each do |run|
|
289
|
+
#for each level of the run
|
290
|
+
run.each_with_index do |val,index|
|
291
|
+
#if the value is nil
|
292
|
+
if run[index].nil?
|
293
|
+
#create a copy of the run
|
294
|
+
temp = run.dup
|
295
|
+
#generate a random index in the range that goes from zero to the number of levels of the factor - 1
|
296
|
+
i = rand(0...@parameters[index].no_levels)
|
297
|
+
#store this index
|
298
|
+
j = i
|
299
|
+
loop do
|
300
|
+
#check if the level at index j is a valid one in the sense that doesn't form infeasible pairs
|
301
|
+
temp[index] = @parameters[index].values[j]
|
302
|
+
#if it's valid stop the search
|
303
|
+
break unless pairs_for_run(temp).any? {|p| @infeasible_pairs.include? p}
|
304
|
+
#tries next level
|
305
|
+
j+=1
|
306
|
+
#if it reaches the last level of the factor restart from the first level in a circular fashion
|
307
|
+
j = 0 if j == @parameters[index].no_levels
|
308
|
+
#if no level is valid raise an exception
|
309
|
+
raise ArgumentError,"The input provided doesn't allow the creation of valid runs(raised in vertical_growth)" if j == i
|
310
|
+
end
|
311
|
+
#save the level thus obtained
|
312
|
+
run[index] = temp[index]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
#return the runs obtained by the Vertical_Growth method
|
317
|
+
tPrime
|
318
|
+
end
|
319
|
+
|
320
|
+
#
|
321
|
+
#formats the runs obtained by the algorithm in a table with an appropriate structure
|
322
|
+
#
|
323
|
+
def format_output_without_CSV
|
324
|
+
headings = [] << "Run"
|
325
|
+
rows = []
|
326
|
+
@parameters.each_with_index {|p,i| headings << "F#{i+1}"}
|
327
|
+
@runs.each_with_index {|r,i| temp = ["#{i+1}"]; temp += r; rows << temp}
|
328
|
+
@output_table = Terminal::Table.new :title => "IPO Algorithm tests output", :headings => headings, :rows => rows
|
329
|
+
end
|
330
|
+
|
331
|
+
#
|
332
|
+
#formats the runs obtained by the algorithm substituting real values passed by means of the .csv file
|
333
|
+
#
|
334
|
+
|
335
|
+
def format_output_with_CSV()
|
336
|
+
headings = [] << "Run"
|
337
|
+
rows = []
|
338
|
+
@actual_parameters.each {|t| headings << t[0]}
|
339
|
+
@runs.each_with_index do |run,i|
|
340
|
+
temp = ["#{i+1}"];
|
341
|
+
run.each_with_index do |r,i|
|
342
|
+
temp << @actual_parameters[i][Integer(run[i][1])]
|
343
|
+
end
|
344
|
+
rows << temp
|
345
|
+
end
|
346
|
+
@output_table = Terminal::Table.new :title => "IPO Algorithm tests output", :headings => headings, :rows => rows
|
347
|
+
end
|
348
|
+
|
349
|
+
#
|
350
|
+
#the procedure reads the .csv file and produces a tabular representation
|
351
|
+
#
|
352
|
+
def read_parameters_from_csv(csv)
|
353
|
+
begin
|
354
|
+
temp = CSV.read(csv)
|
355
|
+
rescue Exception
|
356
|
+
raise ArgumentError, "It wasn't possible to read from #{csv} file."
|
357
|
+
end
|
358
|
+
temp.each_with_index do |row,index|
|
359
|
+
@actual_parameters[index] = row[0].split(';')
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
#
|
364
|
+
#reads the infeasible pairs from the .csv file
|
365
|
+
#
|
366
|
+
def read_infeasible_pairs(infeasible_csv)
|
367
|
+
begin
|
368
|
+
temp = CSV.read(infeasible_csv)
|
369
|
+
rescue Exception
|
370
|
+
raise ArgumentError, "It wasn't possible to read from #{infeasible_csv} file."
|
371
|
+
end
|
372
|
+
temp.each_with_index do |row,index|
|
373
|
+
@infeasible_pairs[index] = row[0].split(';')
|
374
|
+
end
|
375
|
+
raise ArgumentError,"Every line of the .csv file containing infeasible pairs must contain exactly 2 elements" unless @infeasible_pairs.all? {|pair| pair.size == 2}
|
376
|
+
end
|
377
|
+
|
378
|
+
#permits to retrieve a level from the tabular representation of the .csv file of factors; if it exists the internal representation of the level is returned, consisting of a lowercase letter from a to z and an index
|
379
|
+
def find_value(val)
|
380
|
+
temp = nil
|
381
|
+
@actual_parameters.each_with_index do |row,index|
|
382
|
+
row.each_with_index do |element, index1|
|
383
|
+
if (element.downcase.delete(' ') == val.downcase.delete(' ') && index1 != 0)
|
384
|
+
return (index + 97).chr + index1.to_s
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
return nil
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
require_relative 'IPOAlgorithm/Parameter'
|
393
|
+
require_relative 'IPOAlgorithm/Run'
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#the class 'Parameter' models a Factor/Parameter
|
2
|
+
class IPOAlgorithm::Parameter
|
3
|
+
attr_reader :name, :no_levels, :values
|
4
|
+
|
5
|
+
def initialize(name, levels)
|
6
|
+
@values = Array.new
|
7
|
+
@name = name
|
8
|
+
@no_levels = levels
|
9
|
+
#each value is represented using the lowercase version of the factor's name and an index subscript starting from 1 up to the number of levels of the factor
|
10
|
+
1.upto(levels) {|i| values << name.downcase + i.to_s}
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,4 @@
|
|
1
|
+
Hardware;Dell Dimension Series;Apple iMac;Apple MacBook Pro;;
|
2
|
+
OS;Windows Server 2008: Web Edition;Windows Server 2008: R2 standard edition;Windows 7 Enterprise;OS 10.6;OS 10.7
|
3
|
+
Browser;Internet Explorer 9;Internet Explorer;Chrome;Safari 5.1.6;Firefox
|
4
|
+
AnotherFactor;f1;f2;;;
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Dell Dimension Series;OS 10.6
|
2
|
+
Dell Dimension Series;OS 10.7
|
3
|
+
Apple iMac;Windows Server 2008: Web Edition
|
4
|
+
Apple iMac;Windows Server 2008: R2 standard edition
|
5
|
+
Apple iMac;Windows 7 Enterprise
|
6
|
+
Apple MacBook Pro;Windows Server 2008: Web Edition
|
7
|
+
Apple MacBook Pro;Windows Server 2008: R2 standard edition
|
8
|
+
Apple MacBook Pro;Windows 7 Enterprise
|
9
|
+
Dell Dimension Series;Safari 5.1.6
|
10
|
+
Apple iMac;Internet Explorer 9
|
11
|
+
Apple iMac;Internet Explorer
|
12
|
+
Apple MacBook Pro;Internet Explorer 9
|
13
|
+
Apple MacBook Pro;Internet Explorer
|
14
|
+
OS 10.6;Internet Explorer 9
|
15
|
+
OS 10.6;Internet Explorer
|
16
|
+
OS 10.7;Internet Explorer 9
|
17
|
+
OS 10.7;Internet Explorer
|
18
|
+
Windows Server 2008: Web Edition;Safari 5.1.6
|
19
|
+
Windows Server 2008: R2 standard edition;Safari 5.1.6
|
20
|
+
Windows 7 Enterprise;Safari 5.1.6
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Dell Dimension Series;OS 10.6
|
2
|
+
Dell Dimension Series;OS 10.7
|
3
|
+
Apple iMac;Windows Server 2008: Web Edition
|
4
|
+
Apple iMac;Windows Server 2008: R2 standard edition
|
5
|
+
Apple iMac;Windows 7 Enterprise
|
6
|
+
Apple MacBook Pro;Windows Server 2008: Web Edition
|
7
|
+
Apple MacBook Pro;Windows Server 2008: R2 standard edition
|
8
|
+
Apple MacBook Pro;Windows 7 Enterprise
|
9
|
+
Dell Dimension Series;Safari 5.1.6
|
10
|
+
Apple iMac;Internet Explorer 9
|
11
|
+
Apple iMac;
|
12
|
+
Apple MacBook Pro;Internet Explorer 9
|
13
|
+
Apple MacBook Pro;Internet Explorer
|
14
|
+
OS 10.6;Internet Explorer 9
|
15
|
+
OS 10.6;Internet Explorer
|
16
|
+
OS 10.7;Internet Explorer 9
|
17
|
+
OS 10.7;Internet Explorer
|
18
|
+
Windows Server 2008: Web Edition;Safari 5.1.6
|
19
|
+
Windows Server 2008: R2 standard edition;Safari 5.1.6
|
20
|
+
Windows 7 Enterprise;Safari 5.1.6
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Dell Dimension Series;OS 10.6
|
2
|
+
Dell Dimension Series;OS 10.7
|
3
|
+
Apple iMac;Windows Server 2008: Web Edition
|
4
|
+
Apple iMac;Windows Server 2008: R2 standard edition
|
5
|
+
Apple iMac;Windows 7 Enterprise
|
6
|
+
Apple MacBook Pro;Windows Server 2008: Web Edition
|
7
|
+
Apple MacBook Pro;Windows Server 2008: R2 standard edition
|
8
|
+
Apple MacBook Pro;Windows 10 Enterprise
|
9
|
+
Dell Dimension Series;Safari 5.1.6
|
10
|
+
Apple iMac;Internet Explorer 9
|
11
|
+
Apple iMac;Internet Explorer
|
12
|
+
Apple MacBook Pro;Internet Explorer 9
|
13
|
+
Apple MacBook Pro;Internet Explorer
|
14
|
+
OS 10.6;Internet Explorer 9
|
15
|
+
OS 10.6;Internet Explorer
|
16
|
+
OS 10.7;Internet Explorer 9
|
17
|
+
OS 10.7;Internet Explorer
|
18
|
+
Windows Server 2008: Web Edition;Safari 5.1.6
|
19
|
+
Windows Server 2008: R2 standard edition;Safari 5.1.6
|
20
|
+
Windows 7 Enterprise;Safari 5.1.6
|
data/test/input_file.csv
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../lib/IPOAlgorithm.rb'
|
3
|
+
|
4
|
+
#tests the response of the algorithm with only the first 2 arguments provided
|
5
|
+
class TestIpoWithoutCSV < Test::Unit::TestCase
|
6
|
+
|
7
|
+
#it specifies the maximum number of parameters to be tested
|
8
|
+
def setup
|
9
|
+
@no_tests = 10
|
10
|
+
end
|
11
|
+
|
12
|
+
#tests if the algorithm spots invalid inputs
|
13
|
+
def test_invalid_number_of_parameters
|
14
|
+
assert_raise(ArgumentError.new("The number of parameters passed as an argument must be an Integer.")) {IPOAlgorithm.new(3.5,[3,5,5])}
|
15
|
+
assert_raise(ArgumentError.new("The IPOAlgorithm can process at most 26 parameters (at least 2 parameters must be provided)")) {IPOAlgorithm.new(27,[3,5,5])}
|
16
|
+
assert_raise(ArgumentError.new("The IPOAlgorithm can process at most 26 parameters (at least 2 parameters must be provided)")) {IPOAlgorithm.new(1,[3,5,5])}
|
17
|
+
end
|
18
|
+
|
19
|
+
#tests if the algorithm recognizes when the specified number of parameters doesn't correspond to the size of the array representing the levels for each parameter
|
20
|
+
def test_different_no_parameters
|
21
|
+
assert_raise(ArgumentError.new("The number of parameters doesn't match the size of the array argument representing the number of levels for each parameter.")) {IPOAlgorithm.new(3,[3,5,5,4])}
|
22
|
+
end
|
23
|
+
|
24
|
+
#tests if the algorithm recognizes when the specified number of levels for a parameter isn't a valid value
|
25
|
+
def test_invalid_levels
|
26
|
+
assert_raise(ArgumentError.new("The number of levels for each argument must be an Integer.")) {IPOAlgorithm.new(3,[3,5,"a"])}
|
27
|
+
end
|
28
|
+
|
29
|
+
#test if all the pairs have been covered; to do so the Kernel#eval method is used to create objects of IPO class dynamically and to test the pairwise coverage
|
30
|
+
def test_all_pairs_covered
|
31
|
+
puts "\n"
|
32
|
+
2.upto(@no_tests) do |no_factors|
|
33
|
+
levels="["
|
34
|
+
1.upto(no_factors) {|factor| levels += rand(2...10 ).to_s + ","}
|
35
|
+
levels[levels.size-1] = "]"
|
36
|
+
declaration = "@ipo_#{no_factors}_factors = IPOAlgorithm.new(#{no_factors}, #{levels})"
|
37
|
+
puts declaration + " ----->I'm testing #{no_factors} factors"
|
38
|
+
eval declaration
|
39
|
+
eval "assert_not_nil @ipo_#{no_factors}_factors"
|
40
|
+
eval "assert_equal @ipo_#{no_factors}_factors.class, IPOAlgorithm"
|
41
|
+
eval "assert all_pairs_covered @ipo_#{no_factors}_factors"
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
#method that given an IPO object outputs a boolean value indicating the pairwise coverage
|
47
|
+
def all_pairs_covered(ipo)
|
48
|
+
all_pairs = []
|
49
|
+
0.upto(ipo.no_parameters-2) do |index1|
|
50
|
+
(index1+1).upto(ipo.no_parameters-1) do |index2|
|
51
|
+
all_pairs += ipo.instance_eval {pairs(index1,index2)}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
ipo.runs.each {|r| all_pairs -= ipo.instance_eval{pairs_for_run(r)}}
|
55
|
+
(all_pairs-ipo.infeasible_pairs).empty?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#tests the algorithm response with the first two mandatory arguments and the third optional one containing the path to the .csv file with the actual values of the parameters; the pairwise coverage isn't tested because it is the same as for the TestIpoWithoutCSV tests.
|
60
|
+
class TestIpoWithCSV < Test::Unit::TestCase
|
61
|
+
|
62
|
+
#tests if the .csv file has been correctly read and the tabular representation created
|
63
|
+
def setup
|
64
|
+
@path = File.expand_path File.dirname(__FILE__)
|
65
|
+
end
|
66
|
+
def test_table_created
|
67
|
+
@ipo = IPOAlgorithm.new(3,[3,5,5],"#{@path}/input_file.csv")
|
68
|
+
assert_not_nil @ipo.actual_parameters
|
69
|
+
end
|
70
|
+
|
71
|
+
#tests if the algorithm spots invalid .csv files
|
72
|
+
def test_invalid_file
|
73
|
+
assert_raise(ArgumentError.new("It wasn't possible to read from #{@path}/invalid_file.csv file.")) {IPOAlgorithm.new(3,[3,5,5],"#{@path}/invalid_file.csv")}
|
74
|
+
end
|
75
|
+
|
76
|
+
#test if the algorithm recognizes a discrepancy between the values of the first two arguments and the values contained within the .csv file
|
77
|
+
def test_incorrect_csv
|
78
|
+
assert_raise(ArgumentError.new("The number of factors in the CSV file doesn't correspond to the specified value")) {IPOAlgorithm.new(3,[3,5,5],"#{@path}/incorrect_file.csv")}
|
79
|
+
assert_raise(ArgumentError.new("The number of levels doesn't correspond to the specified values")) {IPOAlgorithm.new(3,[3,4,5],"#{@path}/input_file.csv")}
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
#tests the algorithm response when all four arguments are provided
|
85
|
+
class TestIpoWithCSVandInfeasiblePairs < Test::Unit::TestCase
|
86
|
+
def setup
|
87
|
+
@path = File.expand_path File.dirname(__FILE__)
|
88
|
+
end
|
89
|
+
|
90
|
+
#tests if the algorithm recognizes invalid files for the infeasible pairs
|
91
|
+
def test_invalid_file
|
92
|
+
assert_raise(ArgumentError.new("It wasn't possible to read from #{@path}/invalid_infeasible_file.csv file.")) {IPOAlgorithm.new(3,[3,5,5],"#{@path}/input_file.csv","#{@path}/invalid_infeasible_file.csv")}
|
93
|
+
end
|
94
|
+
|
95
|
+
#tests if the algorithm spots rows of the .csv file containing infeasible pairs with less or more than 2 values
|
96
|
+
def test_not_all_lines_are_pairs
|
97
|
+
assert_raise(ArgumentError.new("Every line of the .csv file containing infeasible pairs must contain exactly 2 elements")) {IPOAlgorithm.new(3,[3,5,5],"#{@path}/input_file.csv","#{@path}/infeasible_pairs_not_all_pairs.csv")}
|
98
|
+
end
|
99
|
+
|
100
|
+
#tests if the algorithm recognizes values specified by the .csv file containing infeasible pairs that have not been declared by the .csv file containing the actual values of the paramenters
|
101
|
+
def test_unspecified_level
|
102
|
+
assert_raise(ArgumentError.new("Windows 10 Enterprise level specified in the #{@path}/infeasible_pairs_unspecified_level.csv file doesn't belong to any parameter in the #{@path}/input_file.csv file")) {IPOAlgorithm.new(3,[3,5,5],"#{@path}/input_file.csv","#{@path}/infeasible_pairs_unspecified_level.csv")}
|
103
|
+
end
|
104
|
+
|
105
|
+
#tests if the algorithm spots infeasible pairs for which it is impossible to obtain valid runs; more precisely this impossibility is spotted within the Vertical Growth step of the algorithm.
|
106
|
+
def test_impossible_combination_VG
|
107
|
+
assert_raise(ArgumentError.new("The input provided doesn't allow the creation of valid runs(raised in vertical_growth)")) {IPOAlgorithm.new(3,[2,2,2],"#{@path}/input_file_impossible_combination_VG.csv","#{@path}/infeasible_pairs_impossible_combination_VG.csv")}
|
108
|
+
end
|
109
|
+
|
110
|
+
#tests if the algorithm spots infeasible pairs for which it is impossible to obtain valid runs; more precisely this impossibility is spotted within the Horizontal Growth step of the algorithm.
|
111
|
+
def test_impossible_combination_HG
|
112
|
+
assert_raise(ArgumentError.new("The input provided doesn't allow the creation of valid runs(raised in horizontal_growth)")) {IPOAlgorithm.new(3,[2,3,2],"#{@path}/input_file_impossible_combination_HG.csv","#{@path}/infeasible_pairs_impossible_combination_HG.csv")}
|
113
|
+
end
|
114
|
+
|
115
|
+
#test if indeed no infeasible pair has been covered
|
116
|
+
def test_no_infeasible_pair_covered
|
117
|
+
assert TestIpoWithoutCSV.new(5).all_pairs_covered(IPOAlgorithm.new(3,[3,5,5],"#{@path}/input_file.csv","#{@path}/infeasible_pairs.csv"))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: IPOAlgorithm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mihai Soldan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: terminal-table
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.6.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.6.0
|
27
|
+
description: IPO (In-Parameter-Order) procedure for the generation of mixed level
|
28
|
+
covering arrays with constraints of infeasible pairs.
|
29
|
+
email: catalinmihai.soldan@studenti.unicam.it
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- bin/IPOAlgorithm.rb
|
35
|
+
- lib/IPOAlgorithm.rb
|
36
|
+
- lib/IPOAlgorithm/Parameter.rb
|
37
|
+
- lib/IPOAlgorithm/Run.rb
|
38
|
+
- test/incorrect_file.csv
|
39
|
+
- test/infeasible_pairs.csv
|
40
|
+
- test/infeasible_pairs_impossible_combination_HG.csv
|
41
|
+
- test/infeasible_pairs_impossible_combination_VG.csv
|
42
|
+
- test/infeasible_pairs_not_all_pairs.csv
|
43
|
+
- test/infeasible_pairs_unspecified_level.csv
|
44
|
+
- test/input_file.csv
|
45
|
+
- test/input_file_impossible_combination_HG.csv
|
46
|
+
- test/input_file_impossible_combination_VG.csv
|
47
|
+
- test/test_IPOAlgorithm.rb
|
48
|
+
homepage: http://rubygems.org/gems/IPOAlgorithm
|
49
|
+
licenses:
|
50
|
+
- GNU GPLv3
|
51
|
+
metadata: {}
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.4.5.1
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: IPO procedure
|
72
|
+
test_files:
|
73
|
+
- test/test_IPOAlgorithm.rb
|