lib/dither/ipog.rb in dither-0.0.4 vs lib/dither/ipog.rb in dither-0.0.5
- old
+ new
@@ -1,112 +1,177 @@
+# coding: utf-8
module Dither
class IPOG
- attr_reader :params, :t, :constraints
- private :params, :t, :constraints
+ attr_reader :params, :t, :constraints, :test_set, :orig_params, :unbound_param_pool
+ private :params, :t, :constraints, :test_set, :orig_params
def initialize(params, t, opts = {})
- @params = params
+ init_params(params)
@t = t
- @constraints = opts[:constraints]
- unless constraints.nil?
- @constraints = constraints.map(&:to_a)
- .map { |a| a.map { |b| Param.new(*b) } }
+ unless opts[:constraints].nil?
+ @constraints = opts[:constraints].map(&:to_a)
+ .map { |a| a.map { |b| @params[@map_to_orig_index.key(b[0])][b[1]] } }
.map(&:to_set)
end
+
raise 't must be >= 2' if t < 2
raise 't must be <= params.length' if t > params.length
params.each do |param|
raise 'param length must be > 1' if param.length < 2
end
end
+ def init_params(user_params)
+ tmp = []
+ user_params.each_with_index { |e, i| tmp << [i, e] }
+ @orig_params = tmp.sort_by { |a| a[1].length }
+ .reverse!
+
+ @map_to_orig_index = {}
+ @orig_params.each_with_index do |e, i|
+ @map_to_orig_index[i] = e[0]
+ end
+
+ @params = []
+ @unbound_param_pool = []
+ orig_params.each_with_index do |e, i|
+ @params << (0...e[1].length).map { |j| Param.new(i, j) }
+ @unbound_param_pool << UnboundParam.new(i)
+ end
+ params
+ end
+
+ # return nil if unable to satisfy constraints
+ def maximize_coverage(i, test_case, pi)
+ current_max = 0
+ current_max_j = 0
+ current_matches = []
+
+ (0...params[i].length).each do |j|
+ current_param = params[i][j]
+ test_case << current_param
+ unless violates_constraints?(test_case)
+ matches = pi.select { |a| a.subset?(test_case) }
+ count = matches.count
+
+ if count > current_max
+ current_max = count
+ current_max_j = j
+ current_matches = matches
+ end
+ end
+ test_case.delete(current_param)
+ end
+
+ test_case << params[i][current_max_j]
+ return nil if violates_constraints?(test_case)
+
+ current_matches
+ end
+
def run
- ts = comb
+ # add into test set a test for each combination of values
+ # of the first t parameter
+ test_set = comb
+
(t...params.length).each do |i|
- ts = ts.zip((0...params[i].length).cycle)
- .map { |a| a[0] << Param.new(i, a[1]) }
- .delete_if { |a| violates_constraints?(a) }
+ # let pi
+ # be the set of t-way combinations of values involving
+ # parameter Pi and t -1 parameters among the first i – 1
+ # parameters
+ pi = comb_i(i)
- comb_i(i).each do |a|
- next if violates_constraints?(a)
- next if ts.any? { |test| a.subset?(test) }
+ # horizontal extension for parameter i
+ test_set.each do |test_case|
+ cover = maximize_coverage(i, test_case, pi)
- existing_test = false
- ts.select { |c| c.length <= i }
- .each do |b|
+ if cover.nil?
+ test_set.delete(test_case)
+ else
+ pi -= cover
+ end
+ end
- unbound = find_unbound(a, b)
+ # vertical extension for parameter i
+ pi.each do |a|
+ if test_set.any? { |test_case| a.subset?(test_case) }
+ pi.delete(a)
+ else
- if unbound
- unbound.each { |c| b << c }
- existing_test = true
- break
+ test_case = nil
+ test_set.each do |b|
+ test_case = b.merge_without_conflict(i, a) do |a|
+ violates_constraints?(a)
+ end
+ break unless test_case.nil?
end
- end
- ts << a unless existing_test
+ if test_case.nil?
+ test_set << a.create_unbound(i)
+ end
+ pi.delete(a)
+ end
end
end
- ts.map { |a| fill_unbound(a) }
+ test_set.map { |a| fill_unbound(a) }
.delete_if(&:nil?)
- .to_set
.to_a
end
def violates_constraints?(params)
return false if constraints.nil?
constraints.any? { |b| b.subset?(params) }
end
def comb
ranges = (0...t).to_a.inject([]) do |a, i|
- a << (0...params[i].length).map { |j| Param.new(i, j) }
+ a << (0...params[i].length).map { |j| params[i][j] }
end
products = ranges[1..-1].inject(ranges[0]) do |a, b|
a = a.product(b)
end
products.map(&:flatten)
- .map(&:to_set)
+ .map { |a| TestCase.create(params, unbound_param_pool, a) }
end
def comb_i(param_i)
values = (0...param_i).to_a.combination((t-1)).to_a
values.each do |a|
a << param_i
end
result = []
values.each do |a|
result += a[1..-1]
- .inject((0...params[a[0]].length).map { |b| Param.new(0, b) }) { |p, i| p.product((0...params[i].length).to_a.map { |b| Param.new(i, b) }) }
+ .inject((0...params[a[0]].length).map { |b| params[0][b] }) { |p, i| p.product((0...params[i].length).to_a.map { |b| params[i][b] }) }
.map(&:flatten)
- .map(&:to_set)
+ .map { |a| TestCase.create(params, unbound_param_pool, a) }
end
result
end
private
def fill_unbound(data)
- @bound_sets ||= []
- return nil if @bound_sets.any? { |a| data.subset?(a) }
-
arr = Array.new(params.length)
data.each do |param|
- arr[param.i] = params[param.i][param.j]
+ unless param.unbound?
+ orig_param = orig_params[param.i]
+ arr[orig_param[0]] = orig_param[1][param.j]
+ end
end
arr.each_with_index do |e, i|
if e.nil?
j = 0
- arr[i] = params[i][j]
- data << Param.new(i, j)
+ orig_param = orig_params.find { |a| a[0] = i }
+ arr[i] = orig_param[1][j]
end
end
- @bound_sets << data
+
return nil if violates_constraints?(data)
arr
end
def find_unbound(param_array, stuff)