Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
---|---|---|---|---|
lib/gecoder/interface/mixin.rb | 486 | 227 | 100.00%
|
100.00%
|
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.
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