144 lines
3.9 KiB
Python
144 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright 2010-2025 Google LLC
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Solve a simple bin packing problem using a MIP solver."""
|
|
# [START program]
|
|
# [START import]
|
|
import io
|
|
|
|
import pandas as pd
|
|
|
|
from ortools.linear_solver.python import model_builder
|
|
|
|
# [END import]
|
|
|
|
|
|
# [START program_part1]
|
|
# [START data_model]
|
|
def create_data_model() -> tuple[pd.DataFrame, pd.DataFrame]:
|
|
"""Create the data for the example."""
|
|
|
|
items_str = """
|
|
item weight
|
|
i1 48
|
|
i2 30
|
|
i3 19
|
|
i4 36
|
|
i5 36
|
|
i6 27
|
|
i7 42
|
|
i8 42
|
|
i9 36
|
|
i10 24
|
|
i11 30
|
|
"""
|
|
|
|
bins_str = """
|
|
bin capacity
|
|
b1 100
|
|
b2 100
|
|
b3 100
|
|
b4 100
|
|
b5 100
|
|
b6 100
|
|
b7 100
|
|
"""
|
|
|
|
items = pd.read_table(io.StringIO(items_str), index_col=0, sep=r"\s+")
|
|
bins = pd.read_table(io.StringIO(bins_str), index_col=0, sep=r"\s+")
|
|
return items, bins
|
|
# [END data_model]
|
|
|
|
|
|
def main():
|
|
# [START data]
|
|
items, bins = create_data_model()
|
|
# [END data]
|
|
# [END program_part1]
|
|
|
|
# [START model]
|
|
# Create the model.
|
|
model = model_builder.Model()
|
|
# [END model]
|
|
|
|
# [START program_part2]
|
|
# [START variables]
|
|
# Variables
|
|
# x[i, j] = 1 if item i is packed in bin j.
|
|
items_x_bins = pd.MultiIndex.from_product(
|
|
[items.index, bins.index], names=["item", "bin"]
|
|
)
|
|
x = model.new_bool_var_series(name="x", index=items_x_bins)
|
|
|
|
# y[j] = 1 if bin j is used.
|
|
y = model.new_bool_var_series(name="y", index=bins.index)
|
|
# [END variables]
|
|
|
|
# [START constraints]
|
|
# Constraints
|
|
# Each item must be in exactly one bin.
|
|
for unused_name, all_copies in x.groupby("item"):
|
|
model.add(x[all_copies.index].sum() == 1)
|
|
|
|
# The amount packed in each bin cannot exceed its capacity.
|
|
for selected_bin in bins.index:
|
|
items_in_bin = x.xs(selected_bin, level="bin")
|
|
model.add(
|
|
items_in_bin.dot(items.weight)
|
|
<= bins.loc[selected_bin].capacity * y[selected_bin]
|
|
)
|
|
# [END constraints]
|
|
|
|
# [START objective]
|
|
# Objective: minimize the number of bins used.
|
|
model.minimize(y.sum())
|
|
# [END objective]
|
|
|
|
# [START solve]
|
|
# Create the solver with the CP-SAT backend, and solve the model.
|
|
solver = model_builder.Solver("sat")
|
|
if not solver.solver_is_supported():
|
|
return
|
|
status = solver.solve(model)
|
|
# [END solve]
|
|
|
|
# [START print_solution]
|
|
if status == model_builder.SolveStatus.OPTIMAL:
|
|
print(f"Number of bins used = {solver.objective_value}")
|
|
|
|
x_values = solver.values(x)
|
|
y_values = solver.values(y)
|
|
active_bins = y_values.loc[lambda x: x == 1].index
|
|
|
|
for b in active_bins:
|
|
print(f"Bin {b}")
|
|
items_in_bin = x_values.xs(b, level="bin").loc[lambda x: x == 1].index
|
|
for item in items_in_bin:
|
|
print(f" Item {item} - weight {items.loc[item].weight}")
|
|
print(f" Packed items weight: {items.loc[items_in_bin].sum().to_string()}")
|
|
print()
|
|
|
|
print(f"Total packed weight: {items.weight.sum()}")
|
|
print()
|
|
print(f"Time = {solver.wall_time} seconds")
|
|
else:
|
|
print("The problem does not have an optimal solution.")
|
|
# [END print_solution]
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
# [END program_part2]
|
|
# [END program]
|