:- local struct(contents(glass, plastic, steel, wood, copper)). |
:- local struct(colour(red, blue, green)). |
:- local struct(bin(colour, capacity, contents:contents)). |
|
solve_bin/2
is the general predicate
that takes an amount of components packed into a contents
structure and returns the solution.
?- Demand = contents{glass:1, plastic:2, steel:1, wood:3, copper:2}, solve_bin(Demand, Bins). |
solve_bin/2
calls bin_setup/2
to
generate a list Bins.
It adds redundant constraints to remove symmetries (two
solutions are considered symmetrical if they are
the same, but with the bins in a different order).
Finally it labels all decision variables in the problem.
solve_bin(Demand, Bins) :- bin_setup(Demand, Bins), remove_symmetry(Bins), bin_label(Bins). |
bin_setup/2
, to generate a list of bins with appropriate
constraints, works as follows.
First it tries to match the (remaining) demand with zero,
and use no (further) bins.
If this fails, a new bin is added to the bin list;
appropriate constraints are imposed on all the new bin's
variables;
its contents are subtracted from the demand;
and the bin_setup/2
predicate calls itself recursively:
bin_setup(Demand,[]) :- all_zeroes(Demand). bin_setup(Demand, [Bin | Bins]) :- constrain_bin(Bin), reduce_demand(Demand, Bin, RemainingDemand), bin_setup(RemainingDemand, Bins). all_zeroes( contents{glass:0, plastic:0, wood:0, steel:0, copper:0} ). reduce_demand( contents{glass:G, plastic:P, wood:W, steel:S, copper:C}, bin{glass:BG, plastic:BP, wood:BW, steel:BS, copper:BC}, contents{glass:RG, plastic:RP, wood:RW, steel:RS, copper:RC} ) :- RG #= G - BG, RP #= P - BP, RW #= W - BW, RS #= S - BS, RC #= C - BC. |
constrain_bin(bin{colour:Col, capacity:Cap, contents:C}) :- colour_capacity_constraint(Col, Cap), capacity_constraint(Cap, C), contents_constraints(C), colour_constraints(Col, C). |
relates/4
predicate (defined
in section 8.6.3):
colour_capacity_constraint(Col, Cap) :- relates(Col, [red of colour, blue of colour, green of colour], Cap, [3, 1, 4]). |
capacity_constraint(Cap, contents{glass:G, plastic:P, steel:S, wood:W, copper:C}) :- G #>= 0, P #>= 0, S #>= 0, W #>= 0, C #>= 0, NumItems #= G + P + W + S + C, Cap #>= NumItems, NumItems #> 0. |
contents_constraints(contents{glass:G, plastic:P, wood:W, copper:C}) :- requires(W, P), exclusive(G, C), exclusive(C, P). |
=>
.
`Wood requires paper' is expressed in logic as `If the number of wood
items is greater than zero, then the number of paper items
is also greater than zero':
requires(W,P) :- W #> 0 => P #> 0. |
or
.
`X and Y are exclusive' is expressed as `Either the number of items of
kind X is zero, or the number of items of kind Y is zero':
exclusive(X,Y) :- X #= 0 or Y #= 0. |
relates/4
predicate.
The number of wooden items is then constrained not to exceed the capacity:
colour_constraints(Col, contents{wood:W}) :- relates(Col, [red of colour, blue of colour, green of colour], WCap, [1, 1, 2]), W #=< WCap. |
remove_symmetry(Bins) :- ( fromto(Bins, [B1, B2 | Rest], [B2 | Rest], [_Last]) do lex_ord(B1, B2) ). |
lex_ord(bin{colour:Col1, contents:Conts1}, bin{colour:Col2, contents:Conts2}) :- % Use `=..' to extract the contents of the bin as a list Conts1 =.. [_ | Vars1], Conts2 =.. [_ | Vars2], lexico_le([Col1 | Vars1], [Col2 | Vars2]). |
bin_label(Bins) :- ( foreach(bin{colour:C} Bins) do indomain(C) ), term_variables(Bins, Vars), search(Vars, 0, first_fail, indomain, complete, []). |
search/6
predicate of the ic library).