Ejemplo n.º 1
0
 def remove(self, superitem):
     """
     Return a new layer without the given superitem
     """
     new_spool = superitems.SuperitemPool(
         superitems=[s for s in self.superitems_pool if s != superitem])
     new_scoords = [
         c for i, c in enumerate(self.superitems_coords)
         if i != self.superitems_pool.get_index(superitem)
     ]
     return Layer(new_spool, new_scoords, self.pallet_dims)
Ejemplo n.º 2
0
 def _get_new_layer(to_place):
     """
     Place the maximum amount of items that can fit in
     a new layer, starting from the given pool
     """
     assert len(
         to_place) > 0, "The number of superitems to place must be > 0"
     spool = superitems.SuperitemPool(superitems=to_place)
     layer = maxrects.maxrects_single_layer_online(
         spool, self.pallet_dims)
     return layer
Ejemplo n.º 3
0
    def not_covered_superitems(self):
        """
        Return a list of superitems which are not present in any layer
        """
        covered_spool = superitems.SuperitemPool(superitems=None)
        for l in self.layers:
            covered_spool.extend(l.superitems_pool)

        return [
            s for s in self.superitems_pool
            if covered_spool.get_index(s) is None
        ]
Ejemplo n.º 4
0
 def _add_single_layers(self):
     """
     Add one layer for each superitem that only
     contains that superitem
     """
     for superitem in self.superitems_pool:
         self.add(
             Layer(
                 superitems.SuperitemPool([superitem]),
                 [utils.Coordinate(x=0, y=0)],
                 self.pallet_dims,
             ))
Ejemplo n.º 5
0
def maxrects_single_layer_offline(superitems_pool,
                                  pallet_dims,
                                  superitems_in_layer=None):
    """
    Given a superitems pool and the maximum dimensions to pack them into,
    try to fit each superitem in a single layer (if not possible, return an error)
    """
    logger.debug("MR-SL-Offline starting")

    # Set all superitems in layer
    if superitems_in_layer is None:
        superitems_in_layer = np.arange(len(superitems_pool))

    logger.debug(
        f"MR-SL-Offline {superitems_in_layer}/{len(superitems_pool)} superitems to place"
    )

    # Iterate over each placement strategy
    ws, ds, _ = superitems_pool.get_superitems_dims()
    for strategy in MAXRECTS_PACKING_STRATEGIES:
        # Create the maxrects packing algorithm
        packer = newPacker(
            mode=PackingMode.Offline,
            bin_algo=PackingBin.Global,
            pack_algo=strategy,
            sort_algo=SORT_AREA,
            rotation=False,
        )

        # Add one bin representing one layer
        packer.add_bin(pallet_dims.width, pallet_dims.depth, count=1)

        # Add superitems to be packed
        for i in superitems_in_layer:
            packer.add_rect(ws[i], ds[i], rid=i)

        # Start the packing procedure
        packer.pack()

        # Feasible packing with a single layer
        if len(packer) == 1 and len(packer[0]) == len(superitems_in_layer):
            spool = superitems.SuperitemPool(
                superitems=[superitems_pool[s.rid] for s in packer[0]])
            layer = layers.Layer(
                spool, [utils.Coordinate(s.x, s.y) for s in packer[0]],
                pallet_dims)
            logger.debug(
                f"MR-SL-Offline generated a new layer with {len(layer)} superitems "
                f"and {layer.get_density(two_dims=False)} 3D density")
            return layer

    return None
Ejemplo n.º 6
0
def build_layer_from_model_output(superitems_pool, superitems_in_layer,
                                  solution, pallet_dims):
    """
    Return a single layer from the given model solution (either baseline or column generation).
    The 'solution' parameter should be a dictionary of the form
    {
        'c_{s}_x': ...,
        'c_{s}_y: ...,
        ...
    }
    """
    spool, scoords = [], []
    for s in superitems_in_layer:
        spool += [superitems_pool[s]]
        scoords += [Coordinate(x=solution[f"c_{s}_x"], y=solution[f"c_{s}_y"])]
    spool = superitems.SuperitemPool(superitems=spool)
    return layers.Layer(spool, scoords, pallet_dims)
Ejemplo n.º 7
0
def get_height_groups(superitems_pool,
                      pallet_dims,
                      height_tol=0,
                      density_tol=0.5):
    """
    Divide the whole pool of superitems into groups having either
    the exact same height or an height within the given tolerance
    """
    assert height_tol >= 0 and density_tol >= 0.0, "Tolerance parameters must be non-negative"

    # Get unique heights
    unique_heights = sorted(set(s.height for s in superitems_pool))
    height_sets = {
        h: {k
            for k in unique_heights[i:] if k - h <= height_tol}
        for i, h in enumerate(unique_heights)
    }
    for (i, hi), (j, hj) in zip(
            list(height_sets.items())[:-1],
            list(height_sets.items())[1:]):
        if hj.issubset(hi):
            unique_heights.remove(j)

    # Generate one group of superitems for each similar height
    groups = []
    for height in unique_heights:
        spool = [
            s for s in superitems_pool
            if s.height >= height and s.height <= height + height_tol
        ]
        spool = superitems.SuperitemPool(superitems=spool)
        if (sum(s.volume
                for s in spool) >= density_tol * spool.get_max_height() *
                pallet_dims.width * pallet_dims.depth):
            groups += [spool]

    return groups
Ejemplo n.º 8
0
def maxrects_single_layer_online(superitems_pool,
                                 pallet_dims,
                                 superitems_duals=None):
    """
    Given a superitems pool and the maximum dimensions to pack them into, try to fit
    the greatest number of superitems in a single layer following the given order
    """
    logger.debug("MR-SL-Online starting")

    # If no duals are given use superitems' heights as a fallback
    ws, ds, hs = superitems_pool.get_superitems_dims()
    if superitems_duals is None:
        superitems_duals = np.array(hs)

    # Sort rectangles by duals
    indexes = utils.argsort(list(zip(superitems_duals, hs)), reverse=True)
    logger.debug(
        f"MR-SL-Online {sum(superitems_duals[i] > 0 for i in indexes)} non-zero duals to place"
    )

    # Iterate over each placement strategy
    generated_layers, num_duals = [], []
    for strategy in MAXRECTS_PACKING_STRATEGIES:
        # Create the maxrects packing algorithm
        packer = newPacker(
            mode=PackingMode.Online,
            pack_algo=strategy,
            rotation=False,
        )

        # Add one bin representing one layer
        packer.add_bin(pallet_dims.width, pallet_dims.depth, count=1)

        # Online packing procedure
        n_packed, non_zero_packed, layer_height = 0, 0, 0
        for i in indexes:
            if superitems_duals[i] > 0 or hs[i] <= layer_height:
                packer.add_rect(ws[i], ds[i], i)
                if len(packer[0]) > n_packed:
                    n_packed = len(packer[0])
                    if superitems_duals[i] > 0:
                        non_zero_packed += 1
                    if hs[i] > layer_height:
                        layer_height = hs[i]
        num_duals += [non_zero_packed]

        # Build layer after packing
        spool, coords = [], []
        for s in packer[0]:
            spool += [superitems_pool[s.rid]]
            coords += [utils.Coordinate(s.x, s.y)]
        layer = layers.Layer(superitems.SuperitemPool(spool), coords,
                             pallet_dims)
        generated_layers += [layer]

    # Find the best layer by taking into account the number of
    # placed superitems with non-zero duals and density
    layer_indexes = utils.argsort(
        [(duals, layer.get_density(two_dims=False))
         for duals, layer in zip(num_duals, generated_layers)],
        reverse=True,
    )
    layer = generated_layers[layer_indexes[0]]

    logger.debug(
        f"MR-SL-Online generated a new layer with {len(layer)} superitems "
        f"(of which {num_duals[layer_indexes[0]]} with non-zero dual) "
        f"and {layer.get_density(two_dims=False)} 3D density")
    return layer
Ejemplo n.º 9
0
def maxrects_multiple_layers(superitems_pool, pallet_dims, add_single=True):
    """
    Given a superitems pool and the maximum dimensions to pack them into,
    return a layer pool with warm start placements
    """
    logger.debug("MR-ML-Offline starting")
    logger.debug(
        f"MR-ML-Offline {'used' if add_single else 'not_used'} as warm_start")
    logger.debug(f"MR-ML-Offline {len(superitems_pool)} superitems to place")

    # Return a layer with a single item if only one is present in the superitems pool
    if len(superitems_pool) == 1:
        layer_pool = layers.LayerPool(superitems_pool,
                                      pallet_dims,
                                      add_single=True)
        uncovered = 0
    else:
        generated_pools = []
        for strategy in MAXRECTS_PACKING_STRATEGIES:
            # Build initial layer pool
            layer_pool = layers.LayerPool(superitems_pool,
                                          pallet_dims,
                                          add_single=add_single)

            # Create the maxrects packing algorithm
            packer = newPacker(
                mode=PackingMode.Offline,
                bin_algo=PackingBin.Global,
                pack_algo=strategy,
                sort_algo=SORT_AREA,
                rotation=False,
            )

            # Add an infinite number of layers (no upper bound)
            packer.add_bin(pallet_dims.width,
                           pallet_dims.depth,
                           count=float("inf"))

            # Add superitems to be packed
            ws, ds, _ = superitems_pool.get_superitems_dims()
            for i, (w, d) in enumerate(zip(ws, ds)):
                packer.add_rect(w, d, rid=i)

            # Start the packing procedure
            packer.pack()

            # Build a layer pool
            for layer in packer:
                spool, scoords = [], []
                for superitem in layer:
                    spool += [superitems_pool[superitem.rid]]
                    scoords += [utils.Coordinate(superitem.x, superitem.y)]

                spool = superitems.SuperitemPool(superitems=spool)
                layer_pool.add(layers.Layer(spool, scoords, pallet_dims))
                layer_pool.sort_by_densities(two_dims=False)

            # Add the layer pool to the list of generated pools
            generated_pools += [layer_pool]

        # Find the best layer pool by considering the number of placed superitems,
        # the number of generated layers and the density of each layer dense
        uncovered = [
            len(pool.not_covered_superitems()) for pool in generated_pools
        ]
        n_layers = [len(pool) for pool in generated_pools]
        densities = [
            pool[0].get_density(two_dims=False) for pool in generated_pools
        ]
        pool_indexes = utils.argsort(list(zip(uncovered, n_layers, densities)),
                                     reverse=True)
        layer_pool = generated_pools[pool_indexes[0]]
        uncovered = uncovered[pool_indexes[0]]

    logger.debug(
        f"MR-ML-Offline generated {len(layer_pool)} layers with 3D densities {layer_pool.get_densities(two_dims=False)}"
    )
    logger.debug(
        f"MR-ML-Offline placed {len(superitems_pool) - uncovered}/{len(superitems_pool)} superitems"
    )
    return layer_pool
Ejemplo n.º 10
0
    def _place_not_covered(self, singles_removed=None, area_tol=1.0):
        """
        Place the remaining items (not superitems) either on top
        of existing bins or in a whole new bin, if they do not fit
        """
        def _get_unplaceable_items(superitems_list, max_spare_height):
            """
            Return items that must be placed in a new bin
            """
            index = len(superitems_list)
            for i, s in enumerate(superitems_list):
                if s.height > max_spare_height:
                    index = i
                    break
            return superitems_list[:index], superitems_list[index:]

        def _get_placeable_items(superitems_list, working_bin):
            """
            Return items that can be placed in a new layer
            in the given bin
            """
            to_place = []
            for s in superitems_list:
                last_layer_area = working_bin.layer_pool[-1].area
                max_area = np.clip(area_tol * last_layer_area, 0,
                                   self.pallet_dims.area)
                area = sum(s.area for s in to_place)
                if area < max_area and s.height < working_bin.remaining_height:
                    to_place += [s]
                else:
                    break
            return to_place

        def _get_new_layer(to_place):
            """
            Place the maximum amount of items that can fit in
            a new layer, starting from the given pool
            """
            assert len(
                to_place) > 0, "The number of superitems to place must be > 0"
            spool = superitems.SuperitemPool(superitems=to_place)
            layer = maxrects.maxrects_single_layer_online(
                spool, self.pallet_dims)
            return layer

        def _place_new_layers(superitems_list, remaining_heights):
            """
            Try to place items in the bin with the least spare height
            and fallback to the other open bins, if the layer doesn't fit
            """
            sorted_indices = utils.argsort(remaining_heights)
            working_index = 0
            while len(superitems_list) > 0 and working_index < len(self.bins):
                working_bin = self.bins[sorted_indices[working_index]]
                to_place = _get_placeable_items(superitems_list, working_bin)
                if len(to_place) > 0:
                    layer = _get_new_layer(to_place)
                    self.layer_pool.add(layer)
                    working_bin.add(layer)
                    for s in layer.superitems_pool:
                        superitems_list.remove(s)
                else:
                    working_index = working_index + 1
            return superitems_list

        # Get single superitems that are not yet covered
        # (assuming that the superitems pool in the layer pool contains all single superitems)
        superitems_list = self.layer_pool.not_covered_single_superitems(
            singles_removed=singles_removed)

        # Sort superitems by ascending height
        superitems_list = [
            superitems_list[i]
            for i in utils.argsort([s.height for s in superitems_list])
        ]

        # Get placeable and unplaceable items
        remaining_heights = self.get_remaining_heights()
        max_remaining_height = 0 if len(remaining_heights) == 0 else max(
            remaining_heights)
        superitems_list, remaining_items = _get_unplaceable_items(
            superitems_list, max_remaining_height)
        superitems_list = _place_new_layers(superitems_list, remaining_heights)

        # Place unplaceable items in a new bin
        remaining_items += superitems_list
        if len(remaining_items) > 0:
            spool = superitems.SuperitemPool(superitems=remaining_items)
            lpool = maxrects.maxrects_multiple_layers(spool,
                                                      self.pallet_dims,
                                                      add_single=False)
            self.layer_pool.extend(lpool)
            self.bins += self._build(lpool)
Ejemplo n.º 11
0
def main(
    order,
    procedure="cg",
    max_iters=1,
    superitems_horizontal=True,
    superitems_horizontal_type="two-width",
    superitems_max_vstacked=4,
    density_tol=0.5,
    filtering_two_dims=False,
    filtering_max_coverage_all=3,
    filtering_max_coverage_single=3,
    tlim=None,
    enable_solver_output=False,
    height_tol=0,
    cg_use_height_groups=True,
    cg_mr_warm_start=True,
    cg_max_iters=100,
    cg_max_stag_iters=20,
    cg_sp_mr=False,
    cg_sp_np_type="mip",
    cg_sp_p_type="cp",
    cg_return_only_last=False,
):
    """
    External interface to all the implemented solutions to solve 3D-BPP
    """
    assert max_iters > 0, "The number of maximum iteration must be > 0"
    assert procedure in ("mr", "bl", "cg"), "Unsupported procedure"

    logger.info(f"{procedure.upper()} procedure starting")

    # Create the final superitems pool and a copy of the order
    final_layer_pool = layers.LayerPool(superitems.SuperitemPool(),
                                        config.PALLET_DIMS)
    working_order = order.copy()

    # Iterate the specified number of times in order to reduce
    # the number of uncovered items at each iteration
    not_covered, all_singles_removed = [], []
    for iter in range(max_iters):
        logger.info(f"{procedure.upper()} iteration {iter + 1}/{max_iters}")

        # Create the superitems pool and call the baseline procedure
        superitems_list, singles_removed = superitems.SuperitemPool.gen_superitems(
            order=working_order,
            pallet_dims=config.PALLET_DIMS,
            max_vstacked=superitems_max_vstacked,
            horizontal=superitems_horizontal,
            horizontal_type=superitems_horizontal_type,
        )
        superitems_pool = superitems.SuperitemPool(superitems=superitems_list)
        all_singles_removed += singles_removed

        # Call the right packing procedure
        if procedure == "bl":
            layer_pool = baseline.baseline(superitems_pool,
                                           config.PALLET_DIMS,
                                           tlim=tlim)
        elif procedure == "mr":
            layer_pool = maxrects_warm_start(superitems_pool,
                                             height_tol=height_tol,
                                             density_tol=density_tol,
                                             add_single=False)
        elif procedure == "cg":
            layer_pool = cg(
                superitems_pool,
                height_tol=height_tol,
                density_tol=density_tol,
                use_height_groups=cg_use_height_groups,
                mr_warm_start=cg_mr_warm_start,
                max_iters=cg_max_iters,
                max_stag_iters=cg_max_stag_iters,
                tlim=tlim,
                sp_mr=cg_sp_mr,
                sp_np_type=cg_sp_np_type,
                sp_p_type=cg_sp_p_type,
                return_only_last=cg_return_only_last,
                enable_solver_output=enable_solver_output,
            )

        # Filter layers based on the given parameters
        layer_pool = layer_pool.filter_layers(
            min_density=density_tol,
            two_dims=filtering_two_dims,
            max_coverage_all=filtering_max_coverage_all,
            max_coverage_single=filtering_max_coverage_single,
        )

        # Add only the filtered layers
        final_layer_pool.extend(layer_pool)

        # Compute the number of uncovered Items
        prev_not_covered = len(not_covered)
        item_coverage = final_layer_pool.item_coverage()
        not_covered = [k for k, v in item_coverage.items() if not v]
        logger.info(
            f"Items not covered: {len(not_covered)}/{len(item_coverage)}")
        if len(not_covered) == prev_not_covered:
            logger.info(
                "Stop iterating, no improvement from the previous iteration")
            break

        # Compute a new order composed of only not covered items
        working_order = order.iloc[not_covered].copy()

    # Build a pool of bins from the layer pool and compact
    # all layers in each bin to avoid having "flying" products
    bin_pool = bins.BinPool(final_layer_pool,
                            config.PALLET_DIMS,
                            singles_removed=set(all_singles_removed))
    return bins.CompactBinPool(bin_pool)