Gecoder C0 Coverage Information - RCov

lib/gecoder/interface/mixin.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/gecoder/interface/mixin.rb 486 227
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   # Mixin contains the base functionality needed to formulate problems.
3   #
4   # == Formulating problems
5   #
6   # Problems are formulated by building a model that represents the
7   # problem. A model is a class that mixes in Mixin and uses its
8   # functionality to define variables and constraints that describe the
9   # problem. Below is an example of a model that formulates the problem
10   # of finding a solution to the following equation system.
11   #
12   # Equation system:
13   #   x + y = z
14   #   x = y - 3
15   #   0 <= x,y,z <= 9
16   #
17   # Model:
18   #   class EquationProblem 
19   #     include Gecode::Mixin
20   #
21   #     attr :vars
22   #
23   #     def initialize
24   #       x, y, z = @vars = int_var_array(3, 0..9)
25   #
26   #       (x + y).must == z
27   #       x.must == y - 3
28   #
29   #       branch_on @vars
30   #     end
31   #   end
32   # 
33   # A model typically consists of three main parts:
34   # [Variables] Variables specify how to view the problem. A solution is an 
35   #             assignment of the variables. In the example above we created 
36   #             an array of three integer variables with domains 0..9 and gave 
37   #             it the name +variables+.
38   #
39   #             There are three types of variables: integer variables
40   #             (Gecode::IntVar, can be assigned one of many
41   #             possible integer values), boolean variables
42   #             (Gecode::BoolVar, can be assigned either true or
43   #             false) and set variables (Gecode::SetVar, can be
44   #             assigned a set of integers).  Variables of the different
45   #             types are constructed using #int_var, #int_var_array,
46   #             #int_var_matrix, #bool_var, #bool_var_array,
47   #             #bool_var_matrix, #set_var, #set_var_array and
48   #             #set_var_matrix .
49   #
50   #             The various variables all have the functionality of Operand 
51   #             and have many properties depending on their type. For 
52   #             instance integer variables have the properties defined
53   #             in Gecode::Int::IntOperand and
54   #             enumerations of integer variables (such as the array
55   #             +variables+ we used) have the properties defined in  
56   #             Gecode::IntEnum::IntEnumOperand .
57   #             
58   # [Constraints] Constraints are placed on the variables to ensure that a 
59   #               valid assignment of the variables must also be a solution.
60   #               In the example above we constrained the variables so
61   #               that all equations were satisfied (which is exactly when
62   #               we have found a solution).
63   #
64   #               The various constraints that can be placed on the various
65   #               kinds of operands are found in the respective
66   #               constraint receivers. For instance, the constraints
67   #               that can be placed on integer operands are found in 
68   #               Gecode::Int::IntConstraintReceiver and
69   #               the constraints that can be placed on enumerations of
70   #               integer operands are found in 
71   #               Gecode::IntEnum::IntEnumConstraintReceiver .
72   #
73   # [Branching] "branch_on variables" in the example tells Gecode that
74   #             it should explore the search space until it has assigned
75   #             +variables+ (or exhausted the search space). It also
76   #             tells Gecode in what order the search space should be
77   #             explore, which can have a big effect on the search
78   #             performance. See #branch_on for details.
79   #
80   # == Finding solutions
81   #
82   # Solutions to a formulated problem are found are found by using
83   # methods such as #solve!, #solution, #each_solution . If one wants to
84   # find a solution that optimizes a certain quantity (i.e. maximizes a
85   # certain variable) then one should have a look at #maximize!,
86   # #minimize! and #optimize! .
87   #
88   # The first solution to the example above could for instance be found
89   # using
90   #
91   #   puts EquationProblem.new.solve!.vars.values.join(', ')
92   #
93   # which would find the first solution to the problem, access the
94   # assigned values of +variables+ and print them (in order x, y, z).
95   #
96   # == Shorter ways of formulating problems
97   #
98   # Problems can also be formulated without defining a new class by
99   # using Gecode#solve et al.
100   #
101   # Additionally one can use "foo_is_an ..." to create an accessor of 
102   # name foo, without having to use instance variables. The above
103   # problem becomes
104   #   class EquationProblem 
105   #     include Gecode::Mixin
106   #
107   #     def initialize
108   #       x, y, z = vars_is_an int_var_array(3, 0..9)
109   #
110   #       (x + y).must == z
111   #       x.must == y - 3
112   #
113   #       branch_on vars
114   #     end
115   #   end
116   #
117   module Mixin
118     # The largest integer allowed in the domain of an integer variable.
119     MAX_INT = Gecode::Raw::IntLimits::MAX
120     # The smallest integer allowed in the domain of an integer variable.
121     MIN_INT = Gecode::Raw::IntLimits::MIN
122 
123     # The largest integer allowed in the domain of a set variable.
124     SET_MAX_INT = Gecode::Raw::SetLimits::MAX
125     # The smallest integer allowed in the domain of a set variable.
126     SET_MIN_INT = Gecode::Raw::SetLimits::MIN
127 
128     # The largest possible domain for an integer variable.
129     LARGEST_INT_DOMAIN = MIN_INT..MAX_INT
130     # The largest possible domain, without negative integers, for an
131     # integer variable.
132     NON_NEGATIVE_INT_DOMAIN = 0..MAX_INT
133 
134     # The largest possible bound for a set variable.
135     LARGEST_SET_BOUND = SET_MIN_INT..SET_MAX_INT
136 
137     # Creates a new integer variable with the specified domain. The domain can
138     # either be a range, a single element, or an enumeration of elements. If no
139     # domain is specified then the largest possible domain is used.
140     def int_var(domain = LARGEST_INT_DOMAIN)
141       args = domain_arguments(domain)
142       IntVar.new(self, variable_creation_space.new_int_var(*args))
143     end
144     
145     # Creates an array containing the specified number of integer variables 
146     # with the specified domain. The domain can either be a range, a single 
147     # element, or an enumeration of elements. If no domain is specified then 
148     # the largest possible domain is used.
149     def int_var_array(count, domain = LARGEST_INT_DOMAIN)
150       args = domain_arguments(domain)
151       build_var_array(count) do
152         IntVar.new(self, variable_creation_space.new_int_var(*args))
153       end
154     end
155     
156     # Creates a matrix containing the specified number rows and columns of 
157     # integer variables with the specified domain. The domain can either be a 
158     # range, a single element, or an enumeration of elements. If no domain 
159     # is specified then the largest possible domain is used.
160     def int_var_matrix(row_count, col_count, domain = LARGEST_INT_DOMAIN)
161       args = domain_arguments(domain)
162       build_var_matrix(row_count, col_count) do
163         IntVar.new(self, variable_creation_space.new_int_var(*args))
164       end
165     end
166     
167     # Creates a new boolean variable.
168     def bool_var
169       BoolVar.new(self, variable_creation_space.new_bool_var)
170     end
171     
172     # Creates an array containing the specified number of boolean variables.
173     def bool_var_array(count)
174       build_var_array(count) do
175         BoolVar.new(self, variable_creation_space.new_bool_var)
176       end
177     end
178     
179     # Creates a matrix containing the specified number rows and columns of 
180     # boolean variables.
181     def bool_var_matrix(row_count, col_count)
182       build_var_matrix(row_count, col_count) do
183         BoolVar.new(self, variable_creation_space.new_bool_var)
184       end
185     end
186     
187     # Creates a set variable with the specified domain for greatest lower bound
188     # and least upper bound (specified as either a fixnum, range or enum). If 
189     # no bounds are specified then the empty set is used as greatest lower 
190     # bound and the largest possible set as least upper bound. 
191     #
192     # A range for the allowed cardinality of the set can also be
193     # specified, if none is specified, or nil is given, then the default
194     # range (anything) will be used. If only a single Fixnum is
195     # specified as cardinality_range then it's used as lower bound.
196     def set_var(glb_domain = [], lub_domain = LARGEST_SET_BOUND,
197         cardinality_range = nil)
198       args = set_bounds_to_parameters(glb_domain, lub_domain, cardinality_range)
199       SetVar.new(self, variable_creation_space.new_set_var(*args))
200     end
201     
202     # Creates an array containing the specified number of set variables. The
203     # parameters beyond count are the same as for #set_var .
204     def set_var_array(count, glb_domain = [], lub_domain = LARGEST_SET_BOUND, 
205         cardinality_range = nil)
206       args = set_bounds_to_parameters(glb_domain, lub_domain, cardinality_range)
207       build_var_array(count) do
208         SetVar.new(self, variable_creation_space.new_set_var(*args))
209       end
210     end
211     
212     # Creates a matrix containing the specified number of rows and columns 
213     # filled with set variables. The parameters beyond row and column counts are
214     # the same as for #set_var .
215     def set_var_matrix(row_count, col_count, glb_domain = [], 
216         lub_domain = LARGEST_SET_BOUND, cardinality_range = nil)
217       args = set_bounds_to_parameters(glb_domain, lub_domain, cardinality_range)
218       build_var_matrix(row_count, col_count) do
219         SetVar.new(self, variable_creation_space.new_set_var(*args))
220       end
221     end
222     
223     # Retrieves the currently used space. Calling this method is only allowed 
224     # when sanctioned by the model beforehand, e.g. when the model asks a 
225     # constraint to post itself. Otherwise an RuntimeError is raised.
226     #
227     # The space returned by this method should never be stored, it should be
228     # rerequested from the model every time that it's needed.
229     def active_space #:nodoc:
230       unless @gecoder_mixin_allow_space_access
231         raise 'Space access is restricted and the permission to access the ' + 
232           'space has not been given.'
233       end
234       selected_space
235     end
236     
237     # Adds the specified constraint to the model. Returns the newly added 
238     # constraint.
239     def add_constraint(constraint) #:nodoc:
240       add_interaction do
241         constraint.post
242       end
243       return constraint
244     end
245     
246     # Adds a block containing something that interacts with Gecode to a queue
247     # where it is potentially executed.
248     def add_interaction(&block) #:nodoc:
249       gecode_interaction_queue << block
250     end
251     
252     # Allows the model's active space to be accessed while the block is 
253     # executed. Don't use this unless you know what you're doing. Anything that
254     # the space is used for (such as bound variables) must be released before
255     # the block ends.
256     #
257     # Returns the result of the block.
258     def allow_space_access(&block) #:nodoc:
259       # We store the old value so that nested calls don't become a problem, i.e.
260       # access is allowed as long as one call to this method is still on the 
261       # stack.
262       old = @gecoder_mixin_allow_space_access
263       @gecoder_mixin_allow_space_access = true
264       res = yield
265       @gecoder_mixin_allow_space_access = old
266       return res
267     end
268     
269     # Starts tracking a variable that depends on the space. All variables 
270     # created should call this method for their respective models.
271     def track_variable(variable) #:nodoc:
272       (@gecoder_mixin_variables ||= []) << variable
273     end
274 
275     def self.included(mod)
276       mod.class_eval do
277         alias_method :pre_gecoder_method_missing, :method_missing
278         # Wraps method missing to handle #foo_is_a and #foo_is_an . 
279         #
280         # "<variable_name>_is_a <variable>" or "<variable_name>_is_an
281         # <variable>", # replacing "<variable_name>" with the variable's
282         # name and "<variable>" with the variable, adds an instance
283         # variable and accessor with the specified name.
284         #
285         # The method also returns the variable given.
286         #
287         # ==== Example
288         #
289         #   # Add an instance variable and accessor named "foo" that return
290         #   # the integer variable.
291         #   foo_is_an int_var(0..9)
292         #
293         #   # Add an instance variable and accessor named "bar" that return
294         #   # the boolean variable array.
295         #   bar_is_a bool_var_array(2)
296         def method_missing(method, *args)
297           name = method.to_s
298           if name =~ /._is_an?$/
299             name.sub!(/_is_an?$/, '')
300             unless args.size == 1
301               raise ArgumentError, 
302                 "Wrong number of argmuments (#{args.size} for 1)."
303             end 
304             if respond_to? name
305               raise ArgumentError, "Method with name #{name} already exists."
306             end
307             if instance_variable_defined? "@#{name}"
308               raise ArgumentError, 
309                 "Instance variable with name @#{name} already exists."
310             end
311 
312             # We use the meta class to avoid defining the variable in all
313             # other instances of the class.
314             eval <<-"end_eval"
315               @#{name} = args.first
316               class <<self
317                 attr :#{name}
318               end
319             end_eval
320             return args.first
321           else
322             pre_gecoder_method_missing(method, *args)
323           end
324         end
325         alias_method :mixin_method_missing, :method_missing
326 
327         def self.method_added(method)
328           if method == :method_missing && !@redefining_method_missing
329             # The class that is mixing in the mixin redefined method
330             # missing. Redefine method missing again to combine the two
331             # definitions.
332             @redefining_method_missing = true
333             class_eval do 
334               alias_method :mixee_method_missing, :method_missing
335               def combined_method_missing(*args)
336                 begin
337                   mixin_method_missing(*args)
338                 rescue NoMethodError => e
339                   mixee_method_missing(*args)
340                 end
341               end
342               alias_method :method_missing, :combined_method_missing
343             end
344           end
345         end
346       end
347     end
348 
349     protected
350     
351     # Gets a queue of objects that can be posted to the model's active_space 
352     # (by calling their post method).
353     def gecode_interaction_queue #:nodoc:
354       @gecode_interaction_queue ||= []
355     end
356     
357     private
358     
359     # Creates an array containing the specified number of variables, all
360     # constructed using the provided block..
361     def build_var_array(count, &block)
362       variables = []
363       count.times do 
364         variables << yield
365       end
366       return wrap_enum(variables)
367     end
368     
369     # Creates a matrix containing the specified number rows and columns of 
370     # variables, all constructed using the provided block. 
371     def build_var_matrix(row_count, col_count, &block)
372       rows = []
373       row_count.times do |i|
374         row = []
375         col_count.times do |j|
376           row << yield
377         end
378         rows << row
379       end
380       return wrap_enum(Util::EnumMatrix.rows(rows, false))
381     end
382 
383     # Returns the array of arguments that correspond to the specified 
384     # domain when given to Gecode. The domain can be given as a range, 
385     # a single number, or an enumerable of elements. 
386     def domain_arguments(domain)
387       if domain.respond_to?(:first) and domain.respond_to?(:last) and
388             domain.respond_to?(:exclude_end?)
389         if domain.exclude_end?
390           return [domain.first, (domain.last - 1)]
391         else
392           return [domain.first, domain.last]
393         end
394       elsif domain.kind_of? Enumerable
395         array = domain.to_a
396         return [Gecode::Raw::IntSet.new(array, array.size)]
397       elsif domain.kind_of? Fixnum
398         return [domain, domain]
399       else
400         raise TypeError, 'The domain must be given as an instance of ' +
401           "Enumerable or Fixnum, but #{domain.class} was given."
402       end
403     end
404     
405     # Transforms the argument to a set cardinality range, returns nil if the
406     # default range should be used. If arg is a range then that's used, 
407     # otherwise if the argument is a fixnum it's used as lower bound.
408     def to_set_cardinality_range(arg)
409       if arg.kind_of? Fixnum
410         arg..Gecode::Raw::SetLimits::MAX
411       else
412         arg
413       end
414     end
415     
416     # Converts the specified set var domain to parameters accepted by
417     # Gecode. The bounds can be specified as a fixnum, range or # enum. 
418     # The parameters are returned as an array.
419     def set_bounds_to_parameters(glb_domain, lub_domain, cardinality_range)
420       check_set_bounds(glb_domain, lub_domain)
421       args = []
422       args << Gecode::Util.constant_set_to_int_set(glb_domain)
423       args << Gecode::Util.constant_set_to_int_set(lub_domain)
424       card_range = to_set_cardinality_range(cardinality_range)
425       if card_range.nil?
426         card_range = 0..Gecode::Raw::SetLimits::CARD
427       end
428       args << card_range.first << card_range.last
429     end
430     
431     # Checks whether the specified greatest lower bound is a subset of least 
432     # upper bound. Raises ArgumentError if that is not the case.
433     def check_set_bounds(glb, lub)
434       unless valid_set_bounds?(glb, lub)
435         raise ArgumentError, 
436           "Invalid set bounds: #{glb} is not a subset of #{lub}."
437       end
438     end
439     
440     # Returns whether the greatest lower bound is a subset of least upper 
441     # bound.
442     def valid_set_bounds?(glb, lub)
443       return true if glb.respond_to?(:empty?) and glb.empty? 
444       if glb.kind_of?(Range) and lub.kind_of?(Range)
445         glb.first >= lub.first and glb.last <= lub.last
446       else
447         glb = [glb] if glb.kind_of?(Fixnum)
448         lub = [lub] if lub.kind_of?(Fixnum)
449         (glb.to_a - lub.to_a).empty?
450       end
451     end
452     
453     # Retrieves the base from which searches are made. 
454     def base_space
455       @gecoder_mixin_base_space ||= Gecode::Raw::Space.new
456     end
457     
458     # Retrieves the currently selected space, the one which constraints and 
459     # variables should be bound to.
460     def selected_space
461       return @gecoder_mixin_active_space unless @gecoder_mixin_active_space.nil?
462       self.active_space = base_space
463     end
464     
465     # Retrieves the space that should be used for variable creation.
466     def variable_creation_space
467       @gecoder_mixin_variable_creation_space || selected_space
468     end
469     
470     # Executes any interactions with Gecode still waiting in the queue 
471     # (emptying the queue) in the process.
472     def perform_queued_gecode_interactions
473       allow_space_access do
474         gecode_interaction_queue.each{ |con| con.call }
475         gecode_interaction_queue.clear # Empty the queue.
476       end
477     end
478     
479     # Switches the active space used (the space from which variables are read
480     # and to which constraints are posted). @gecoder_mixin_active_space 
481     # should never be assigned directly.
482     def active_space=(new_space)
483       @gecoder_mixin_active_space = new_space
484     end
485   end
486 end

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