Search code examples
constraint-programmingminizinc

Understanding the input format of Minizincs geost constraint


I'm trying to understand MiniZincs geost constraint, which is described in the packing constraint section of the docs. I'm trying to implement 2D packing of rectangles with rotation: So I'd like to fit rectangles on a plate of given length and width, but I'm having a hard time understanding the expected input format.

I have the following model, where I read the number of parts or rectangles to be placed into nParts. nShapes is the number of shapes those rectangles can take.

include "globals.mzn";

int: nParts;
set of int: PARTS = 1..nParts;

int: nShapes; 
set of int: SHAPES = 1..nShapes;

int: plateLength;
int: plateWidth;

set of int: LEN = 0..plateLength;
set of int: WID = 0..plateWidth;

int: k = 2;
set of int: DIM = 1..k;

array[SHAPES,DIM] of int: rect_size;
array[SHAPES,DIM] of 0..0: rect_offset;
array[PARTS] of set of SHAPES: shape;

array[PARTS,DIM] of var int: xy;
array[PARTS] of var SHAPES: kind;

constraint geost(k, rect_size, rect_offset, shape, xy, kind);

constraint forall(i in PARTS)(kind[i] in shape[i]);

constraint forall(i in PARTS)(xy[i,1] in LEN);
constraint forall(i in PARTS)(xy[i,1] + rect_size[kind[i], 1] in LEN);

constraint forall(i in PARTS)(xy[i,2] in WID);
constraint forall(i in PARTS)(xy[i,2] + rect_size[kind[i], 2] in WID);

Given model seems to work for this extremly simple data (placing only one rectangle, with two possible shapes):

plateLength = 4000;
plateWidth = 4000;

nParts = 1;
nShapes = 2;

rect_size = [|4000, 2000|
2000, 4000|];
rect_offset = [|0, 0|
0, 0|];

shape = [{1,2}];

Yet for more complex data, I'm running into errors, leading to the conclusion my understanding of the input format might be wrong. In the following example, I want to place 5 parts on a plate and I'd expect a result like this: The first rectangle can take the shape [4000, 2000] or [2000, 4000] and is therefor indexed via {1,2}, the second rectangle can take the shape [2000, 2000] and is indexed via {3}.

enter image description here

plateLength = 4000;
plateWidth = 4000;

nParts = 5;
nShapes = 7;

rect_size = [|4000, 2000|
2000, 4000|
2000, 2000|
1000, 2000|
2000, 1000|
500, 1000|
1000, 500|];
rect_offset = [|0, 0|
0, 0|
0, 0|
0, 0|
0, 0|
0, 0|
0, 0|];

shape = [{1,2}, {3}, {4,5}, {6,7}, {6,7}];

Is this specification correct? If not, how can I correctly specify the input for the geost constraint? A simple example would also help, I have only found rather complex ones here and here.


Solution

  • The global non-overlap constraint geost for k dimensional objects enforces that no two objects overlap. Its cousin geost_bb further constraints the objects to fit within a global k dimensional bounding box.


    Let's say that we want to find a suitable placement on a 2D plane for the following three objects:

    enter image description here

    Let's say that we can rotate each object by 90° (or its multiples), then our problem is to find a suitable placement on a 2D plane for 3 objects that can take one of the following 10 shapes:

    enter image description here

    (Notice that we need to take into account only two possible rotations for the second object.)

    Now, let's take a look at the definition of the geost_bb constraint:

    constraint
        geost_bb(
            k,              % the number of dimensions
            rect_size,      % the size of each box in k dimensions
            rect_offset,    % the offset of each box from the base position in k dimensions
            shape,          % the set of rectangles defining the i-th shape
            x,              % the base position of each object.
                            % (var) x[i,j] is the position of object i in dimension j
            kind,           % (var) the shape used by each object
            l,              % array of lower bounds
            u               % array of upper bounds
        );
    

    Everything, except for x, kind, l, u, is an input for the constraint. The matrix x specifies the bottom-left coordinate of the (smallest) rectangular convex-hull wrapping each object i. The vector kind specifies the shape of a given object i. The vectors l and u specify the lower and upper bound constraints for each k dimension of the N-dimensional rectangular convex-hull enveloping all of the objects in the problem.

    A shape is given by the composition of a set of rectangles. Each rectangle is uniquely identified by its size and its offset wrt. the bottom-left coordinate in the corresponding shape.

    Notice that there can be multiple ways to decompose a collection of shapes into a collection of rectangles. As a rule of thumb, it is always a good idea to use the least number of rectangles as possible.

    This is a possible decomposition for our running example (rectangles with the same size and offset have the same color):

    enter image description here

    Let's encode this example into Minizinc.

    We start by declaring the input parameters and the output variables of our problem:

    int: k;
    int: nObjects;
    int: nRectangles;
    int: nShapes; 
    
    set of int: DIMENSIONS = 1..k;
    set of int: OBJECTS    = 1..nObjects;
    set of int: RECTANGLES = 1..nRectangles;
    set of int: SHAPES     = 1..nShapes;
    
    array[DIMENSIONS] of int:             l;
    array[DIMENSIONS] of int:             u;
    array[RECTANGLES,DIMENSIONS] of int:  rect_size;
    array[RECTANGLES,DIMENSIONS] of int:  rect_offset;
    array[SHAPES] of set of RECTANGLES:   shape;
    array[OBJECTS,DIMENSIONS] of var int: x;
    array[OBJECTS] of var SHAPES:         kind;
    
    • The number of dimensions for a 2D plane is 2, so k is equal 2;
    • The number of objects is 3, so nObjects is equal 3;
    • The number of unique rectangles that are necessary to design all shapes in the problem is 13, so nRectangles is equal 13;
    • The number of shapes is 10, so nShapes is equal 10;

    Hence, we write:

    k = 2;                  % Number of dimensions for a 2D Plane
    nObjects = 3;           % Number of objects
    nRectangles = 13;       % Number of rectangles
    nShapes = 10;           % Number of shapes
    

    Starting from the top-right corner, we define the size and offset of the 13 rectangles we need as follows:

    rect_size = [|
         4, 2|
         2, 4|
         4, 2|
         2, 4|
    
         1, 2|
         2, 1|
         1, 2|
         2, 1|
    
         2, 1|
         1, 2|
    
         1, 1|
         1, 1|
         1, 1|];
    
    rect_offset = [|
         0, 0|
         0, 0|
         0, 2|
         2, 0|
    
         2, 2|
         2, 1|
         1, 0|
         0, 2|
    
         0, 0|
         0, 0|
    
         0, 1|
         1, 1|
         0, 0|];
    

    Now we can define the 10 shapes we need in terms of the given list or rectangles:

    shape = [
        { 1, 5 },
        { 2, 6 },
        { 3, 7 },
        { 4, 8 },
    
        { 9 },
        { 10 },
    
        { 9, 11 },
        { 10, 12 },
        { 7, 11 },
        { 7, 13 }
    ];
    

    We require that the solution to the problem must place all objects within 4x4 square placed at the origin:

    l = [0, 0];
    u = [4, 4];
    

    In order to complete this example, we need an additional constraint to ensure that each object is assigned a valid shape among those available:

    array[OBJECTS] of set of SHAPES: valid_shapes;
    
    valid_shapes = [{1, 2, 3, 4}, {5, 6}, {7, 8, 9, 10}];
    
    constraint forall (obj in OBJECTS) (
        kind[obj] in valid_shapes[obj]
    );
    
    ...
    
    solve satisfy;
    

    A possible solution to this trivial problem is:

    x = array2d(1..3, 1..2, [0, 0, 0, 3, 0, 0]);
    kind = array1d(1..3, [4, 5, 7]);
    ----------
    

    Graphically, the solution is:

    enter image description here


    You ask:

    Is this specification correct?

    No. The apparent source of confusion is the definition of a shape. The above explanation should clarify that aspect.