From eccf0d0bfdfdbd7201539c6262f951611f06f1bc Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sun, 25 Jun 2023 22:40:05 +0100 Subject: [PATCH] proof of concept knapsack optimizer --- model_data/optimiser/Optimiser.py | 73 +++++++++++++++++++++++++++++++ model_data/requirements.txt | 3 +- 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 model_data/optimiser/Optimiser.py diff --git a/model_data/optimiser/Optimiser.py b/model_data/optimiser/Optimiser.py new file mode 100644 index 00000000..5adce91d --- /dev/null +++ b/model_data/optimiser/Optimiser.py @@ -0,0 +1,73 @@ +from mip import Model, xsum, maximize, BINARY + +# Example parts +wall = [ + {"id": 1, "cost": 2000, "gain": 5, "type": "wall"}, + {"id": 2, "cost": 2300, "gain": 6, "type": "wall"} +] + +floor = [ + {"id": 1, "cost": 1500, "gain": 3, "type": "floor"}, + {"id": 2, "cost": 1600, "gain": 3.1, "type": "floor"} +] + +roof = [ + {"id": 1, "cost": 1000, "gain": 2, "type": "roof"}, + {"id": 2, "cost": 1100, "gain": 2.3, "type": "roof"} +] + +# To solve this, we are solving a constrained Knapsack problem +# Maximize sum(gain_g . x_g) for g in groups +# subject to sum(cost_g . x_g) <= C +# subject to sum(x_g) <= 1 for g in groups +# x_g in {0, 1} for g in groups +# +# The first sum, which is the objective of the optimisation provlem, ensures that we are maximising the gain +# for the selected parts +# The second sum (and the first constraint) ensures that the cost of the selected parts is less than or equal to C +# The third sum (and the second constraint) ensures that at most one part from each group is selected +# The last constraint ensures that the decision variables are binary + +C = 4000 + +# group all the parts +groups = [wall, floor, roof] + +# Initialize Model +m = Model("knapsack") + +# Create variables +vars = [[m.add_var(var_type=BINARY, name=str(component["id"])) for component in group] for group in groups] + +# Set objective +# This objective is the sum +# gain_ig * x_ig, where gain_ig represents the gain for ith part in group g +# and x_ig is the binary decision variable for the ith part in group g +m.objective = maximize( + xsum( + component['gain'] * var for group, group_vars in zip(groups, vars) for component, var in zip(group, group_vars) + ) +) + +# Add constraints +# This constrain ensures that sum of cost_ig * x_ig <= C, where cost_ig represents the cost for the ith component +# in group g, and x_ig is the binary decision variable for the ith component in group g +m += xsum(item['cost'] * var for group, group_vars in zip(groups, vars) for item, var in zip(group, group_vars)) <= C + +# At most one item from each group +# This constraint ensures that at most one item from each group is selected +# This is expressed by summing up the decision variables for each group and ensuring that the sum is <= 1 +for group_vars in vars: + m += xsum(var for var in group_vars) <= 1 + +# Solve the problem +m.optimize() + +# Get the selected items +selected_items = [ + item for group, group_vars in zip(groups, vars) for item, var in zip(group, group_vars) if var.x >= 0.99 +] + +total_gain = m.objective.x +actual_cost = sum([component['cost'] for component in selected_items]) +print("Selected items:", selected_items) diff --git a/model_data/requirements.txt b/model_data/requirements.txt index bd8368ce..a0efa457 100644 --- a/model_data/requirements.txt +++ b/model_data/requirements.txt @@ -12,4 +12,5 @@ python-Levenshtein dbfread pyproj pint -geopandas \ No newline at end of file +geopandas +mip \ No newline at end of file