switch model_builder python from numpy to pandas

This commit is contained in:
Laurent Perron
2023-07-09 13:54:23 +02:00
parent 1ff59b03c0
commit 3acda0a0e9
11 changed files with 2399 additions and 4227 deletions

View File

@@ -37,7 +37,6 @@ py_test(
"//ortools/linear_solver/testdata:large_model.mps.gz",
"//ortools/linear_solver/testdata:maximization.mps",
],
python_version = "PY3",
deps = [
":model_builder_helper",
":model_builder_numbers",
@@ -65,6 +64,7 @@ py_library(
":model_builder_helper",
":model_builder_numbers",
requirement("numpy"),
requirement("pandas"),
"//ortools/linear_solver:linear_solver_py_pb2",
],
)
@@ -76,32 +76,8 @@ py_test(
"//ortools/linear_solver/testdata:maximization.mps",
"//ortools/linear_solver/testdata:small_model.lp",
],
python_version = "PY3",
deps = [
":model_builder",
":model_builder_helper",
":model_builder_numbers",
requirement("absl-py"),
requirement("numpy"),
],
)
py_library(
name = "pandas_model",
srcs = ["pandas_model.py"],
deps = [
":model_builder_helper",
requirement("numpy"),
requirement("pandas"),
"//ortools/linear_solver:linear_solver_py_pb2",
],
)
py_test(
name = "pandas_model_test",
srcs = ["pandas_model_test.py"],
deps = [
":pandas_model",
requirement("absl-py"),
requirement("pandas"),
"//ortools/linear_solver:linear_solver_py_pb2",

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@
#include "absl/log/check.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "ortools/base/logging.h"
#include "ortools/linear_solver/linear_solver.pb.h"
#include "ortools/linear_solver/model_exporter.h"
#include "pybind11/eigen.h"
@@ -177,10 +178,19 @@ PYBIND11_MODULE(model_builder_helper, m) {
arg("mps_string"))
.def("import_from_mps_file", &ModelBuilderHelper::ImportFromMpsFile,
arg("mps_file"))
#if defined(USE_LP_PARSER)
.def("import_from_lp_string", &ModelBuilderHelper::ImportFromLpString,
arg("lp_string"))
.def("import_from_lp_file", &ModelBuilderHelper::ImportFromLpFile,
arg("lp_file"))
#else
.def("import_from_lp_string", [](const std::string& lp_string) {
LOG(INFO) << "Parsing LP string is not compiled in";
})
.def("import_from_lp_file", [](const std::string& lp_file) {
LOG(INFO) << "Parsing LP file is not compiled in";
})
#endif
.def(
"fill_model_from_sparse_data",
[](ModelBuilderHelper* helper,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,9 @@
"""MIP example that solves an assignment problem."""
# [START program]
# [START import]
import numpy as np
import io
import pandas as pd
from ortools.linear_solver.python import model_builder
# [END import]
@@ -24,20 +26,35 @@ from ortools.linear_solver.python import model_builder
def main():
# Data
# [START data_model]
costs = np.array(
[
[90, 80, 75, 70],
[35, 85, 55, 65],
[125, 95, 90, 95],
[45, 110, 95, 115],
[50, 100, 90, 100],
]
)
num_workers, num_tasks = costs.shape
data_str = """
worker task cost
w1 t1 90
w1 t2 80
w1 t3 75
w1 t4 70
w2 t1 35
w2 t2 85
w2 t3 55
w2 t4 65
w3 t1 125
w3 t2 95
w3 t3 90
w3 t4 95
w4 t1 45
w4 t2 110
w4 t3 95
w4 t4 115
w5 t1 50
w5 t2 110
w5 t3 90
w5 t4 100
"""
data = pd.read_table(io.StringIO(data_str), sep=r"\s+")
# [END data_model]
# Solver
# Create the model.
# [START model]
model = model_builder.ModelBuilder()
# [END model]
@@ -45,25 +62,23 @@ def main():
# [START variables]
# x[i, j] is an array of 0-1 variables, which will be 1
# if worker i is assigned to task j.
x = model.new_bool_var_array(
shape=[num_workers, num_tasks], name="x"
) # pytype: disable=wrong-arg-types # numpy-scalars
x = model.new_bool_var_series(name="x", index=data.index)
# [END variables]
# Constraints
# [START constraints]
# Each worker is assigned to at most 1 task.
for i in range(num_workers):
model.add(np.sum(x[i, :]) <= 1)
for unused_name, tasks in data.groupby("worker"):
model.add(x[tasks.index].sum() <= 1)
# Each task is assigned to exactly one worker.
for j in range(num_tasks):
model.add(np.sum(x[:, j]) == 1)
for unused_name, workers in data.groupby("task"):
model.add(x[workers.index].sum() == 1)
# [END constraints]
# Objective
# [START objective]
model.minimize(np.dot(x.flatten(), costs.flatten()))
model.minimize(data.cost.dot(x))
# [END objective]
# [START solve]
@@ -79,11 +94,9 @@ def main():
or status == model_builder.SolveStatus.FEASIBLE
):
print(f"Total cost = {solver.objective_value}\n")
for i in range(num_workers):
for j in range(num_tasks):
# Test if x[i,j] is 1 (with tolerance for floating point arithmetic).
if solver.value(x[i, j]) > 0.5:
print(f"Worker {i} assigned to task {j}." + f" Cost: {costs[i][j]}")
selected = data.loc[solver.values(x).loc[lambda x: x == 1].index]
for unused_index, row in selected.iterrows():
print(f"{row.task} assigned to {row.worker} with a cost of {row.cost}")
else:
print("No solution found.")
# [END print_solution]

View File

@@ -15,7 +15,9 @@
"""Solve a simple bin packing problem using a MIP solver."""
# [START program]
# [START import]
import numpy as np
import io
import pandas as pd
from ortools.linear_solver.python import model_builder
# [END import]
@@ -25,58 +27,81 @@ from ortools.linear_solver.python import model_builder
# [START data_model]
def create_data_model():
"""Create the data for the example."""
data = {}
weights = [48, 30, 19, 36, 36, 27, 42, 42, 36, 24, 30]
data["weights"] = weights
data["items"] = list(range(len(weights)))
data["bins"] = data["items"]
data["bin_capacity"] = 100
return data
# [END data_model]
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]
data = create_data_model()
num_items = len(data["items"])
num_bins = len(data["bins"])
items, bins = create_data_model()
# [END data]
# [END program_part1]
# [START solver]
# [START model]
# Create the model.
model = model_builder.ModelBuilder()
# [END solver]
# [END model]
# [START program_part2]
# [START variables]
# Variables
# x[i, j] = 1 if item i is packed in bin j.
x = model.new_bool_var_array(
shape=[num_items, num_bins], name="x"
) # pytype: disable=wrong-arg-types # numpy-scalars
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_array(
shape=[num_bins], name="y"
) # pytype: disable=wrong-arg-types # numpy-scalars
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 i in data["items"]:
model.add(np.sum(x[i, :]) == 1)
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 j in data["bins"]:
model.add(np.dot(x[:, j], data["weights"]) <= data["bin_capacity"] * y[j])
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(np.sum(y))
model.minimize(y.sum())
# [END objective]
# [START solve]
@@ -87,24 +112,23 @@ def main():
# [START print_solution]
if status == model_builder.SolveStatus.OPTIMAL:
num_bins = 0.0
for j in data["bins"]:
if solver.value(y[j]) == 1:
bin_items = []
bin_weight = 0
for i in data["items"]:
if solver.value(x[i, j]) > 0:
bin_items.append(i)
bin_weight += data["weights"][i]
if bin_weight > 0:
num_bins += 1
print("Bin number", j)
print(" Items packed:", bin_items)
print(" Total weight:", bin_weight)
print()
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("Number of bins used:", num_bins)
print("Time = ", solver.wall_time, " seconds")
print(f"Time = {solver.wall_time} seconds")
else:
print("The problem does not have an optimal solution.")
# [END print_solution]

View File

@@ -46,6 +46,7 @@ def code_sample_py(name):
deps = [
requirement("absl-py"),
requirement("numpy"),
requirement("pandas"),
"//ortools/linear_solver/python:model_builder",
],
python_version = "PY3",
@@ -63,6 +64,7 @@ def code_sample_py(name):
deps = [
requirement("absl-py"),
requirement("numpy"),
requirement("pandas"),
],
python_version = "PY3",
srcs_version = "PY3",

View File

@@ -29,12 +29,8 @@ def main():
# [START variables]
# Create the variables x and y.
x = model.new_num_var(
0.0, math.inf, "x"
) # pytype: disable=wrong-arg-types # numpy-scalars
y = model.new_num_var(
0.0, math.inf, "y"
) # pytype: disable=wrong-arg-types # numpy-scalars
x = model.new_num_var(0.0, math.inf, "x")
y = model.new_num_var(0.0, math.inf, "y")
print("Number of variables =", model.num_variables)
# [END variables]

View File

@@ -29,12 +29,8 @@ def main():
# [START variables]
# x and y are integer non-negative variables.
x = model.new_int_var(
0.0, math.inf, "x"
) # pytype: disable=wrong-arg-types # numpy-scalars
y = model.new_int_var(
0.0, math.inf, "y"
) # pytype: disable=wrong-arg-types # numpy-scalars
x = model.new_int_var(0.0, math.inf, "x")
y = model.new_int_var(0.0, math.inf, "y")
print("Number of variables =", model.num_variables)
# [END variables]