$title Box Packing Problem (boxpacking,SEQ=434) $onText This model tries to find the optimal configuration of boxes in a container given the 3-dimensional measures of both objects while maintaining the geometrical restrictions. The model uses different methods to determine the order in which the boxes should be packed. GAMS Connect is used to read the data from the br1.csv file. The data is from Bischoff/Ratcliff paper on container packing and is available at the ORLIB instance collection, http://people.brunel.ac.uk/~mastjjb/jeb/orlib/thpackinfo.html This formulation is described in detail in: Ocloo, Valentina & Fügenschuh, Armin & Pamen, Olivier. (2020). A New Mathematical Model for a 3D Container Packing Problem. 10.26127/btuopen-5088. Keywords: mixed integer linear programming, container packing problem, 3D-bin packing problem, box packing problem $offText $eolCom !! $if not set DEBUG $set DEBUG 0 !! DEBUG=1 activates additional output $if not set METHOD $set METHOD batch !! [standard, greedy, batch] $if not set FIXLOCDIM $set FIXLOCDIM 0 !! FIXLOCDIM=1 fixes location of placed boxes in the container $if not set MAXCOPIES $set MAXCOPIES 50 !! maximum number of copies per box type $if not set BATCH_SIZE $set BATCH_SIZE 5 !! number of boxes per batch $if not set TIMELIMIT $set TIMELIMIT 30 !! total time limit * define the 3D measure of the container $if not set X_CONT $set X_CONT 587 $if not set Y_CONT $set Y_CONT 233 $if not set Z_CONT $set Z_CONT 220 * raw input data Set box_id 'input data box ids' box_id_hdr 'input data header' / x, rot_x, y, rot_y, z, rot_z, nb, weight/ mult_box 'input data box copy id index for multiple boxes of the same type' / cp1*cp%MAXCOPIES% /; Parameter box_data(box_id<, box_id_hdr) dim_raw(box_id,box_id_hdr); * Reading the data $onEmbeddedCode Connect: - CSVReader: file: br1.csv name: box_data header: True indexColumns: 1 valueColumns: "2:lastCol" - GAMSWriter: symbols: all $offEmbeddedCode * reduce the number of boxes, * the full scale problem becomes computationally challenging box_data(box_id, 'nb') = ceil(box_data(box_id, 'nb')/2.5); display$%DEBUG% box_data $eval MAXB card(box_id)*%MAXCOPIES% * raw data consistency checks abort$[smax(box_id, box_data(box_id, 'nb')) > %MAXCOPIES%] 'Input data contains more copies of a box than supported. Increase MAXCOPIES'; * internal data structures Set bb super set of boxes / b1*b%MAXB%, vbox 'virtual box' / b_exist(bb) set of existing boxes b(bb) dynamic subset of boxes bmap(box_id,mult_box,bb) mapping of input data box ids with copy id to internal box ids bmap_red(bb,box_id) mapping of input data box ids to internal box ids d(box_id_hdr) dimensions / x, y, z / r(box_id_hdr) possibility of rotating a box / rot_x, rot_y, rot_z / o six possible orientations of a box / o1*o6 / rmap(o,d,d) rotations that define mapping of input dimensions to orientation dimensions / o1.(x.x,y.y,z.z) 'input x->x y->y z->z' o2.(x.x,y.z,z.y) 'rotate input 90 degrees along x axis x->x y->z z->y (not possible if rot_x = 0)' o3.(x.z,y.y,z.x) 'rotate input 90 degrees along y axis x->z y->y z->x (not possible if rot_y = 0)' o4.(x.y,y.x,z.z) 'rotate input 90 degrees along z axis x->y y->x z->z (not possible if rot_z = 0)' o5.(x.z,y.x,z.y) 'rotate input 90 degrees along x+z axis x->z y->x z->y (not possible if rot_x = 0 or rot_z = 0)' o6.(x.y,y.z,z.x) 'rotate input 90 degrees along x+y axis x->y y->z z->x (not possible if rot_x = 0 or rot_y = 0)' / ro(r,o) rotation restriction / rot_x.(o2,o5,o6), rot_y.(o3,o6), rot_z.(o4,o5) / bo(bb,o) available orientations / #bb.#o /; Alias (bb,bb1,bb2), (b,b1,b2), (d,d1); Parameter dim_cont(d) 'container dimesnions' / x %X_CONT%, y %Y_CONT%, z %Z_CONT% / dim_o(bb,o,d) 'dimensions of box bb for orientation o' rot(bb, r) 'allowed rotations' Box_vol(bb) 'volume of each box'; * map raw box ids to internal box ids b('b1') = yes; loop((box_id,mult_box)$[ord(mult_box) <= box_data(box_id,'nb')], bmap(box_id,mult_box,b) = yes; b(bb) = b(bb-1); ); option clear=b; option bmap_red cplex.opt solvefinal 0 mipstart 1 $$offEcho * use option file cpp.optfile = 1; $elseIfI %system.mip%==gurobi * create gurobi option file $$onEcho > gurobi.opt solvefixed 0 mipstart 1 norelheurtime %TIMELIMIT% $$offEcho * use option file cpp.optfile = 1; $endIf Scalar num_of_bat; num_of_bat = ceil(card(b_exist)/%BATCH_SIZE%); option limrow=0, limcol=0, solprint=off, solvelink=5; $ifE %DEBUG%==1 option limrow=1e6, limcol=1e6, solprint=on; * solve for all existing boxes at once $ifThenI.method %METHOD%==standard b(b_exist) = yes; solve cpp max VOL use MIP; * iteratively solve for each box $elseIfI.method %METHOD%==greedy loop(bb$b_exist(bb), b(bb) = yes; solve cpp max VOL use MIP; OMEGA.fx(bb) = round(OMEGA.l(bb)); $$ifE %FIXLOCDIM%=1 ALPHA.fx(bb,o) = round(ALPHA.l(bb,o)); LOC.fx(bb,d) = LOC.l(bb,d); DIM.fx(bb,d) = DIM.l(bb,d); ); * put n boxes in a batch and solve model $elseIfI.method %METHOD%==batch Scalar i; for(i = 1 to num_of_bat, b(bb)$(ord(bb) <= %BATCH_SIZE%*i) = yes; solve cpp max VOL use MIP; OMEGA.fx(b) = round(OMEGA.l(b)); $$ifE %FIXLOCDIM%=1 ALPHA.fx(b,o) = round(ALPHA.l(b,o)); LOC.fx(b,d) = LOC.l(b,d); DIM.fx(b,d) = DIM.l(b,d); ); $endIf.method Parameter rep(*) report; rep('total number of boxes') = card(b_exist); rep('number of boxes in container') = sum(b_exist, OMEGA.l(b_exist)); rep('volume of container [m3]') = prod(d, dim_cont(d)/100); rep('volume smallest box [m3]') = smin(b_exist, Box_vol(b_exist)); rep('volume biggest box [m3]') = smax(b_exist, Box_vol(b_exist)); rep('total volume of all boxes [m3]') = sum(b_exist, Box_vol(b_exist)); rep('total volume of boxes in container [m3]') = sum(b_exist, Box_vol(b_exist)*OMEGA.L(b_exist)); rep('ratio of boxes in container') = (rep('number of boxes in container') / rep('total number of boxes')); rep('ratio of total volume of boxes in container') = (rep('total volume of boxes in container [m3]') / rep('total volume of all boxes [m3]')); rep('ratio of container volume utilized') = (rep('total volume of boxes in container [m3]') / rep('volume of container [m3]')); option rep:2:0:1; display rep;