I am trying to find an algorithm for the NP-hard 2D Variable Size Bin Packing Problem (2DVSBPP) in (Swi-)Prolog using Constraint Logic Programming (CLP).
The problem could be explained like this: some ordered Products need to be packed as efficiently as possible into some Boxes (bins). The products have some given Width and Length (squares or rectangles, eg 2x3). There are four different size of boxes, each with a given cost for the shipper (eg $4 for the 5x5 box, $5 for 5x7 box). The goal is to minimize the total cost from the boxes.
I've been looking for an answer to this problem for a while now and have read numerous papers and similar examples in other languages. However, I can't find any working solution. I'm especially struggling with how to handle the unknown number of Boxes (bins).
To be able to find a solution to this problem I've tried to adapt a similar problem but really have no idea how to handle the variable amount of boxes. The following code can choose the cheapest possible box to fit all the products as long as there is only one box needed to fit them all. From the moment we need multiple boxes, the program just fails.
The boxes and products:
:- use_module(library(clpfd)).
:- use_module(library(clpr)).
:- expects_dialect(sicstus).
%% These are the possible productsizes that could need packing
% product (id, width, length)
product(1, 2, 2).
product(2, 1, 2).
product(2, 2, 1). % repeating product n2 because it can lay horizontal or vertical
product(3, 1, 3).
product(3, 3, 1). % idem
product(4, 3, 3). % is square so does not need it
product(5, 2, 3).
product(5, 3, 2). % iden
product(6, 4, 2).
product(6, 2, 4). % idem
% because it can lay virtically or horizontally in a box
product_either_way(Number, Width, Length) :-
product(Number, Width, Length).
product_either_way(Number, Width, Length) :-
product(Number, Length, Width).
%% These are the so called bins from the 2DVSBPP problem
%% There are 4 sizes, but there is an unlimited supply
% box(Width, Length, Cost)
box(4,4,4).
box(4,6,6).
box(5,5,7).
box(9,9,9).
The constraints:
area_box_pos_combined(W_total*H_total,prod(N),X+Y,f(X,Width,Y,Height)) :-
product_either_way(N, Width, Height), % Getting the width and height (length) of a product
% Constraint: the product should 'fit' inside the choosen box
% thus limiting its coordinates (XY)
X #>= 1,
X #=< W_total-Width+1,
Y #>= 1,
Y #=< H_total-Height+1.
positions_vars([],[]).
positions_vars([X+Y|XYs],[X,Y|Zs]) :-
positions_vars(XYs,Zs).
area_boxes_positions_(ProductList,Ps,Zs) :-
box(W, H, Cost), % finding a suitable box with a W & H
%% minimize(Cost),
maplist(area_box_pos_combined(W*H),ProductList,Ps,Cs), % Setting up constraints for each product
disjoint2(Cs), % making sure they dont overlap with other product inside the box
positions_vars(Ps,Zs).
A possible query which asks to pack 4 products (numbers 2, 1, 3 and 5)
area_boxes_positions_([prod(2),prod(1),prod(3),prod(5)],Positions,Zs),
labeling([ffc],Zs).
Gives the following as output, one possible way to pack the products:
Positions = [3+1, 1+1, 4+1, 1+3],
Zs = [3, 1, 1, 1, 4, 1, 1, 3] .
But how do I model multiple boxes, when we would have an order with more products that would not fit inside one box?
Any help or examples are really appreciated!
There are some redundancies in your partial solution, maybe caused by premature optimization.
First, since you have a product_either_way/3, you should not change your input specification, adding products with same id and dimensions swapped. After all, width and height are properties you cannot swap arbitrarly in the real world, and you already have produced a predicate that takes care of this, so I have started removing such duplicates.
Second, the purpose of disjoint/2 is to compute a placement of a set of rectangles, so your area_box_pos_combined/4 and positions_vars/2 are pretty much useless.
Here is how I would approach this problem. First, write a predicate that given a list of products and a box, puts as many as possible into it, and 'returns' those that didn't fit. For instance
It's somewhat buggy, because it will stop at the first product it cannot place, but there could be more placeable after this. But what's important, now we can start to test if it's working, given the interaction with key concepts of CLP(FD). disjoint/2 works on bounded variables, so the domain declaration of X_i and Y_i is needed.
Now we can provide a driver, maybe as simple as
and then let Prolog generate as many solutions as it can, just order them by cost, by means of library(solution_sequences):
But we don't know which placements have been generated. We have to add arguments that carry back the information. Then
To be true, library(clpfd) is used just as commodity, but mixed with the searching capabilities of (pure) Prolog gives us a short and declarative solution.
See the specific documentation of library(clpBNR) for a better approach.