#!/usr/bin/ruby require 'AbstractConstraint' require 'extensions' module ConstraintSolver # Represents a constraint which is specified by the allowed or disallowed # tuples of arbitrary arity. class TupleConstraint < AbstractConstraint attr_reader :variables, :tuples, :allowedTuples, :violationCost # Initialises a new tuple constraint. # The first argument is the list of variables involved in the # constraint, the second argument is an array of tuples. Each element of # the array is an array with exactly as many elements as the array of # variables. The third argument specifies whether the array of tuples # lists the allowed or disallowed tuples. Default is allowed tuples. # Optionally, a cost for violating the constraint can be specified. def initialize(variables, tuples, allowedTuples=true, violationCost=1) @variables = variables unless tuples.inject(true) { |bool,tuple| bool & (tuple.size == variables.size) } raise ArgumentError, "All tuples must have " + variables.size.to_s + " elements!" end @tuples = tuples @violationCost = violationCost @allowedTuples = allowedTuples end # Checks whether the assignments to the variables are allowed tuples. def holds? tups = @tuples @variables.each_with_index { |var,i| next unless var.assigned? tups = tups.find_all { |tup| tup[i] == var.value } if @allowedTuples return false if tups.empty? end } if not @allowedTuples and allAssigned? and tuples.include?(@variables.collect { |var| var.value }) return false end return true end def allAssigned? @variables.each { |var| return false if not var.assigned? } return true end def include?(variable) @variables.include?(variable) end def to_s @variables.collect { |var| var.name }.join(", ") + ": " + tuples.join(", ") end def to_s_full @variables.collect { |var| var.to_s }.join(", ") + ": " + tuples.join(", ") end def ==(constraint) return false unless constraint.kind_of?(TupleConstraint) (@variables == constraint.variables) and (@tuples == constraint.tuples) and (@allowedTuples == constraint.allowedTuples) and (@violationCost == constraint.violationCost) end def each @variables.each { |var| yield var } end def revise revisedVariables = [] checks = 0 wipeout = false assignedIndices, unassignedIndices = (0..(@variables.size - 1)).partition { |i| @variables[i].assigned? } pruneTuples = (@allowedTuples ? @tuples : []) assignedIndices.each { |i| if @allowedTuples pruneTuples = pruneTuples.find_all { |tup| tup[i] == @variables[i].value } else (@tuples - pruneTuples).each { |tup| if @variables[i].value == tup[i] pruneTuples << tup end } end } unassignedIndices.each { |i| var = @variables[i] pruneList = pruneTuples.collect { |tup| tup[i] }.to_set if @allowedTuples pruneList = var.domain.values - pruneList end if var.domain.include_any?(pruneList) begin var.domain.prune(pruneList) rescue DomainWipeoutException wipeout = true end revisedVariables << var break if wipeout end } return revisedVariables, checks, wipeout end end end