Gecoder C0 Coverage Information - RCov

lib/gecoder/interface/constraints.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/gecoder/interface/constraints.rb 479 259
100.00%
100.00%

Key

Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.

Coverage Details

1 module Gecode
2   # An error signaling that the constraint specified is missing (e.g. one tried
3   # to negate a constraint, but no negated form is implemented).
4   class MissingConstraintError < StandardError
5   end
6   
7   # Describes an operand, something that a constraint can be placed
8   # on. Constraints are placed by calling #must or #must_not (the
9   # latter negates the constraint). This produces a
10   # ConstraintReceiver, which defines methods that places constraints
11   # on the operand.
12   #
13   # In general this produces something like the following.
14   # 
15   #   operand.must.constraint_method(params)
16   #
17   # See e.g. Gecode::Int::IntOperand for concrete examples.
18   # 
19   # Classes that mix in Operand must define the methods #model
20   # and #construct_receiver. They should also define a method that converts
21   # the operand into a variable of the operand's type (e.g. int var
22   # operands should define a method #to_int_var that returns an
23   # instance of Gecode::IntVar that represents the operand). The
24   # latter method should be used by constraints to fetch variables
25   # needed when posting constraints. The presence of the method should
26   # also be used for type checking (rather than e.g. checking whether
27   # a parameter is of type IntOperand). 
28   module Operand 
29     # Specifies that a constraint must hold for the left hand side.
30     def must
31       construct_receiver :lhs => self, :negate => false
32     end
33     alias_method :must_be, :must
34     
35     # Specifies that the negation of a constraint must hold for the left hand 
36     # side.
37     def must_not
38       construct_receiver :lhs => self, :negate => true
39     end
40     alias_method :must_not_be, :must_not
41 
42     # Fetches the model that the operand belongs to.
43     def model
44       raise NotImplementedError, 'Abstract method has not been implemented.'
45     end
46     
47     private
48 
49     # Constructs the appropriate constraint receiver given the
50     # specified parameters.
51     def construct_receiver(params)
52       raise NotImplementedError, 'Abstract method has not been implemented.'
53     end
54   end
55   
56   # Describes a constraint receiver, something that receives and 
57   # places constraints on various Operand. Constraint receivers 
58   # are created by calling #must or #must_not (the latter negates 
59   # the constraint) on something that mixes in Operand.
60   #
61   # A constraint is placed on an Operand +operand+ as follows:
62   #
63   #   operand.must.constraint_method(params)
64   #
65   # The constraint receiver is created by the call to #must and the
66   # constraint is then placed by the call to #constraint_method. 
67   # See e.g. Gecode::Int::IntConstraintReceiver for 
68   # concrete examples.
69   #
70   # The following options can be specified in a hash with symbols as
71   # keys when placing a constraint:
72   #
73   # [:strength] The propagation strength suggests how much effort the 
74   #             solver should put into trying to prune the domains of 
75   #             variables using the constraint. 
76   #
77   #             The allowed values are:
78   #             [:value] Value consistency (naive).
79   #             [:bounds] Bounds consistency. The bounds of the operand 
80   #                       will always be constrained as much as possible 
81   #                       (but pruning may not be done inside the
82   #                       bounds, even though it may be possible).
83   #             [:domain] Domain consistency. All values that can be pruned 
84   #                       away, given the current amount of information,
85   #                       are pruned away.
86   #             [:default] Uses the default consistency of the constraint.
87   #             
88   #             The strength generally progresses as 
89   #             :value < :bounds < :domain (:value being the weakest, 
90   #             :domain being the strongest). A higher strength can 
91   #             reduce the search space quicker, but at the cost of 
92   #             making each propagation more costly.
93   #
94   # [:kind]     The propagation kind option suggests the implementation
95   #             that should be preferred if there are multiple 
96   #             implementations of a constraint. 
97   #
98   #             The different kinds are:
99   #             [:speed] Prefer speed over memory consumption.
100   #             [:memory] Prefer low memory consumption over speed.
101   #             [:default] Uses the constraint's default propagation kind.
102   #
103   # [:reify]    Reification is used to link a constraint to a boolean 
104   #             operand in such a way that the variable is true if and
105   #             only if the constraint is satisfied. The propagation
106   #             goes both ways, so if the variable is constrained to be
107   #             false then the constraint is not allowed to be
108   #             satisfied.
109   #
110   #             Reification can be thought of as a last resort glue which 
111   #             can be used to combine constraints so that e.g. exactly
112   #             3 out of 17 constraints must be satisfied.
113   #
114   # Not all constraints accept all options. Constraints that have sets
115   # as operands (e.g. SetConstraintReceiver and
116   # SetEnumConstraintReceiver) do not accept the :strength and :kind
117   # options, all other do. Some constraints do not accept the :reify
118   # option.
119   #
120   # See e.g. Gecode::Int::IntConstraintReceiver for 
121   # concrete examples of options being specified.
122   class ConstraintReceiver
123     # A list that keeps track of all constraint methods used in the
124     # program, essentially providing unique ids for each.
125     @@constraint_names_list ||= []
126 
127     # Constructs a new expression with the specified parameters. The 
128     # parameters should at least contain the keys :lhs, and :negate.
129     #
130     # Raises ArgumentError if any of those keys are missing or if :lhs
131     # is not an operand.
132     def initialize(model, params)
133       unless params.has_key?(:lhs) and params.has_key?(:negate)
134         raise ArgumentError, 'Expression requires at least :lhs, ' + 
135           "and :negate as parameter keys, got #{params.keys.join(', ')}."
136       end
137       unless params[:lhs].kind_of? Operand
138         raise ArgumentError, 'Expected :lhs to be an operand, received ' + 
139           "#{params[:lhs].class}."
140       end
141       
142       @model = model
143       @params = params
144     end
145 
146     private
147 
148     # Provides commutativity for the constraint with the specified
149     # method name. If the method with the specified method name is
150     # called with something that, when given to the block, evaluates
151     # to true, then the constraint will be called on the right hand
152     # side with the left hand side as argument.
153     #
154     # The original constraint method is assumed to take two arguments:
155     # a right hand side and a hash of options.
156     def self.provide_commutativity(constraint_name, &block)
157       unless @@constraint_names_list.include? constraint_name
158         @@constraint_names_list << constraint_name 
159       end
160       unique_id = @@constraint_names_list.index(constraint_name)
161 
162       pre_alias_method_name = "pre_commutivity_#{unique_id}"
163       if method_defined? constraint_name
164         alias_method pre_alias_method_name, constraint_name
165       end
166       
167       module_eval <<-end_code
168         @@commutivity_check_#{unique_id} = block
169         def #{constraint_name}(rhs, options = {})
170           if @@commutivity_check_#{unique_id}.call(rhs, options)
171             if @params[:negate]
172               rhs.must_not.method(:#{constraint_name}).call(
173                 @params[:lhs], options)
174             else
175               rhs.must.method(:#{constraint_name}).call(
176                 @params[:lhs], options)
177             end
178           else
179             if self.class.method_defined? :#{pre_alias_method_name}
180               #{pre_alias_method_name}(rhs, options)
181             else
182               raise TypeError, \"Unexpected argument type \#{rhs.class}.\" 
183             end
184           end
185         end
186       end_code
187     end
188     
189     # Creates aliases for any defined comparison methods.
190     def self.alias_comparison_methods
191       Gecode::Util::COMPARISON_ALIASES.each_pair do |orig, aliases|
192         if instance_methods.include?(orig.to_s)
193           aliases.each do |name|
194             alias_method(name, orig)
195           end
196         end
197       end
198     end
199     
200     # Creates aliases for any defined set methods.
201     def self.alias_set_methods
202       Gecode::Util::SET_ALIASES.each_pair do |orig, aliases|
203         if instance_methods.include?(orig.to_s)
204           aliases.each do |name|
205             alias_method(name, orig)
206           end
207         end
208       end
209     end
210   end
211   
212   # Base class for all constraints.
213   class Constraint #:nodoc:
214     # Creates a constraint with the specified parameters, bound to the 
215     # specified model. 
216     def initialize(model, params)
217       @model = model
218       @params = params.clone
219     end
220     
221     # Posts the constraint, adding it to the model. This is an abstract 
222     # method and should be overridden by all sub-classes.
223     def post
224       raise NotImplementedError, 'Abstract method has not been implemented.'
225     end
226     
227     private
228     
229     # Gives an array of the values selected for the standard propagation 
230     # options (propagation strength and propagation kind) in the order that
231     # they are given when posting constraints to Gecode.
232     def propagation_options
233       Gecode::Util::extract_propagation_options(@params)
234     end
235   end
236 
237   # A constraint that can be specified by providing a block containing the
238   # post method.
239   class BlockConstraint < Constraint #:nodoc:
240     def initialize(model, params, &block)
241       super(model, params)
242       @proc = block
243     end
244 
245     def post
246       @proc.call
247     end
248   end
249 
250   # A module that provides some utility-methods for constraints.
251   module Util #:nodoc:
252     # Maps the name used in options to the value used in Gecode for 
253     # propagation strengths.
254     PROPAGATION_STRENGTHS = {
255       :default  => Gecode::Raw::ICL_DEF,
256       :value    => Gecode::Raw::ICL_VAL,
257       :bounds   => Gecode::Raw::ICL_BND,
258       :domain   => Gecode::Raw::ICL_DOM
259     }
260     
261     # Maps the name used in options to the value used in Gecode for 
262     # propagation kinds.
263     PROPAGATION_KINDS = {
264       :default  => Gecode::Raw::PK_DEF,
265       :speed    => Gecode::Raw::PK_SPEED,
266       :memory   => Gecode::Raw::PK_MEMORY,
267     } 
268     
269     # Maps the names of the methods to the corresponding integer relation 
270     # type in Gecode.
271     RELATION_TYPES = { 
272       :== => Gecode::Raw::IRT_EQ,
273       :<= => Gecode::Raw::IRT_LQ,
274       :<  => Gecode::Raw::IRT_LE,
275       :>= => Gecode::Raw::IRT_GQ,
276       :>  => Gecode::Raw::IRT_GR
277     }
278     # The same as above, but negated.
279     NEGATED_RELATION_TYPES = {
280       :== => Gecode::Raw::IRT_NQ,
281       :<= => Gecode::Raw::IRT_GR,
282       :<  => Gecode::Raw::IRT_GQ,
283       :>= => Gecode::Raw::IRT_LE,
284       :>  => Gecode::Raw::IRT_LQ
285     }
286 
287     # Maps the names of the methods to the corresponding set relation type in 
288     # Gecode.
289     SET_RELATION_TYPES = { 
290       :==         => Gecode::Raw::SRT_EQ,
291       :superset   => Gecode::Raw::SRT_SUP,
292       :subset     => Gecode::Raw::SRT_SUB,
293       :disjoint   => Gecode::Raw::SRT_DISJ,
294       :complement => Gecode::Raw::SRT_CMPL
295     }
296     # The same as above, but negated.
297     NEGATED_SET_RELATION_TYPES = {
298       :== => Gecode::Raw::SRT_NQ
299     }
300     # Maps the names of the methods to the corresponding set operation type in 
301     # Gecode.
302     SET_OPERATION_TYPES = { 
303       :union          => Gecode::Raw::SOT_UNION,
304       :disjoint_union => Gecode::Raw::SOT_DUNION,
305       :intersection   => Gecode::Raw::SOT_INTER,
306       :minus          => Gecode::Raw::SOT_MINUS
307     }
308     
309     # Various method aliases for comparison methods. Maps the original 
310     # (symbol) name to an array of aliases.
311     COMPARISON_ALIASES = { 
312       :== => [:equal, :equal_to],
313       :>  => [:greater, :greater_than],
314       :>= => [:greater_or_equal, :greater_than_or_equal_to],
315       :<  => [:less, :less_than],
316       :<= => [:less_or_equal, :less_than_or_equal_to]
317     }
318     SET_ALIASES = { 
319       :==         => [:equal, :equal_to],
320       :superset   => [:superset_of],
321       :subset     => [:subset_of],
322       :disjoint   => [:disjoint_with],
323       :complement => [:complement_of]
324     }
325     
326     module_function
327     
328     # Decodes the common options to constraints: strength, kind and 
329     # reification. Returns a hash with up to three values. :strength is the 
330     # strength that should be used for the constraint, :kind is the 
331     # propagation kind that should be used, and :reif is the (bound) boolean 
332     # operand that should be used for reification. The decoded options are 
333     # removed from the hash (so in general the hash will be consumed in the 
334     # process).
335     # 
336     # Raises ArgumentError if an unrecognized option is found in the specified
337     # hash. Or if an unrecognized strength is given. Raises TypeError if the
338     # reification operand is not a boolean operand.
339     def decode_options(options)
340       # Propagation strength.
341       strength = options.delete(:strength) || :default
342       unless PROPAGATION_STRENGTHS.include? strength
343         raise ArgumentError, "Unrecognized propagation strength #{strength}."
344       end
345       
346       # Propagation kind.
347       kind = options.delete(:kind) || :default
348       unless PROPAGATION_KINDS.include? kind
349         raise ArgumentError, "Unrecognized propagation kind #{kind}."
350       end  
351                     
352       # Reification.
353       reif_var = options.delete(:reify)
354       unless reif_var.nil? or reif_var.respond_to? :to_bool_var
355         raise TypeError, 'Only boolean operands may be used for reification.'
356       end
357       
358       # Check for unrecognized options.
359       unless options.empty?
360         raise ArgumentError, 'Unrecognized constraint option: ' + 
361           options.keys.first.to_s
362       end
363       return {
364         :strength => PROPAGATION_STRENGTHS[strength], 
365         :kind => PROPAGATION_KINDS[kind],
366         :reif => reif_var
367       }
368     end
369     
370     # Converts the different ways to specify constant sets in the interface
371     # to the form that the set should be represented in Gecode (possibly 
372     # multiple paramters. The different forms accepted are:
373     # * Single instance of Fixnum (singleton set).
374     # * Range (set containing all numbers in range), treated differently from
375     #   other enumerations.
376     # * Enumeration of integers (set contaning all numbers in set).
377     def constant_set_to_params(constant_set)
378       unless constant_set?(constant_set)
379         raise TypeError, "Expected a constant set, got: #{constant_set}."
380       end
381     
382       if constant_set.kind_of? Range
383         return constant_set.first, constant_set.last
384       elsif constant_set.kind_of? Fixnum
385         return constant_set
386       else
387         constant_set = constant_set.to_a
388         return Gecode::Raw::IntSet.new(constant_set, constant_set.size)
389       end
390     end
391     
392     # Converts the different ways to specify constant sets in the interface
393     # to an instance of Gecode::Raw::IntSet. The different forms accepted are:
394     # * Single instance of Fixnum (singleton set).
395     # * Range (set containing all numbers in range), treated differently from
396     #   other enumerations.
397     # * Enumeration of integers (set contaning all numbers in set).
398     def constant_set_to_int_set(constant_set)
399       unless constant_set?(constant_set)
400         raise TypeError, "Expected a constant set, got: #{constant_set}."
401       end
402       
403       if constant_set.kind_of? Range
404         return Gecode::Raw::IntSet.new(constant_set.first, constant_set.last)
405       elsif constant_set.kind_of? Fixnum
406         return Gecode::Raw::IntSet.new([constant_set], 1)
407       else
408         constant_set = constant_set.to_a
409         return Gecode::Raw::IntSet.new(constant_set, constant_set.size)
410       end
411     end
412     
413     # Checks whether the specified expression is regarded as a constant set.
414     # Returns true if it is, false otherwise.
415     def constant_set?(expression)
416       return (
417          expression.kind_of?(Range) &&        # It's a range.
418          expression.first.kind_of?(Fixnum) &&
419          expression.last.kind_of?(Fixnum)) ||
420         expression.kind_of?(Fixnum) ||        # It's a single fixnum.
421         (expression.kind_of?(Enumerable) &&   # It's an enum of fixnums.
422          expression.all?{ |e| e.kind_of? Fixnum })
423     end
424     
425     # Extracts an array of the values selected for the standard propagation 
426     # options (propagation strength and propagation kind) from the hash of
427     # parameters given. The options are returned in the order that they are 
428     # given when posting constraints to Gecode.      
429     def extract_propagation_options(params)
430       params.values_at(:strength, :kind)
431     end
432   end
433   
434   # A module that contains utility-methods for extensional constraints.
435   module Util::Extensional #:nodoc:
436     module_function
437     
438     # Checks that the specified enumeration is an enumeration containing 
439     # one or more tuples of the specified size. It also allows the caller
440     # to define additional tests by providing a block, which is given each
441     # tuple. If a test fails then an appropriate error is raised.
442     def perform_tuple_checks(tuples, expected_size, &additional_test)
443       unless tuples.respond_to?(:each)
444         raise TypeError, 'Expected an enumeration with tuples, got ' + 
445           "#{tuples.class}."
446       end
447       
448       if tuples.empty?
449         raise ArgumentError, 'One or more tuples must be specified.'
450       end
451       
452       tuples.each do |tuple|
453         unless tuple.respond_to?(:each)
454           raise TypeError, 'Expected an enumeration containing enumeraions, ' +
455             "got #{tuple.class}."
456         end
457         
458         unless tuple.size == expected_size
459           raise ArgumentError, 'All tuples must be of the same size as the ' + 
460             'number of operands in the array.'
461         end
462         
463         yield tuple
464       end
465     end
466   end
467 end
468 
469 require 'gecoder/interface/constraints/reifiable_constraints'
470 require 'gecoder/interface/constraints/int_var_constraints'
471 require 'gecoder/interface/constraints/int_enum_constraints'
472 require 'gecoder/interface/constraints/bool_var_constraints'
473 require 'gecoder/interface/constraints/bool_enum_constraints'
474 require 'gecoder/interface/constraints/set_var_constraints'
475 require 'gecoder/interface/constraints/set_enum_constraints'
476 require 'gecoder/interface/constraints/selected_set_constraints'
477 require 'gecoder/interface/constraints/set_elements_constraints'
478 require 'gecoder/interface/constraints/fixnum_enum_constraints'
479 require 'gecoder/interface/constraints/extensional_regexp'

Generated on Thu Jan 08 13:27:03 +0100 2015 with rcov 1.0.0