Python integer programming: looking for constraint on set partitioning - python

I have a set partitioning problem that requires each available period to be used only once.
def solve (slots, reqs, periods):
# binary variable to id if combination is used
x = pulp.LpVariable.dicts('slot', slots, lowBound=0, upBound=1, cat=pulp.LpInteger)
# linear program problem
sked_model = pulp.LpProblem("NLE Specials Scheduling Model", pulp.LpMaximize)
# objective function
sked_model += sum([x[slot] for slot in slots]), "Slot selection" # all combinations considered equal
# Supply maximum number of slots
sked_model += sum([x[slot] for slot in slots]) == len(reqs), "Number_of_slots to sked"
ERROR OCCURS HERE
# A period can only be used once
for period in periods:
sked_model += sum([x[slot] for slot in slots if period == slot[0:2]]) <= 1, "Period_used_no_more_than_once"
# The problem is solved using PuLP’s choice of Solver
sked_model.solve()
The error received is "pulp.constants.PulpError: overlapping constraint names:" on the # period can only be used once constraint.

Solved this.
The error came from the constraint name, "Period_used_no_more_than_once", being used more than once when the constraints were built. By replacing that name with "", the constraint worked as desired.
New constraint:
for period in periods:
sked_model += sum([x[slot] for slot in slots if period == slot[0:2]]) <= 1, ""

Related

Can you create an array of unknown values with CVXPY?

I am attempting to find an array of optimum values to help with selecting the best runtime for some motors in a factory.
At the moment I have created a simple version of what I would like to do, and it works for one.
# Create variables
running_time = cp.Variable(name='running_time')
max_running_time = 240 # in minutes
pump_flow_per_minute = 9.6*3600
required_volume = 2073600 # in litres
# Find the optimal solution
objective = cp.Minimize(running_time)
# Constraints
runtime_constraint = running_time <= max_running_time
volume_contraint = pump_flow_per_minute * running_time >= required_volume
constraints = [runtime_constraint, volume_contraint]
prob = cp.Problem(objective, constraints)
prob.solve()
print("The optimal solution is:", prob.value)
The issue I am having is translating this to multiple areas, where I would like to look at an array of max_running_times which could equate to a 24 hour period, and likewise with the required volumes.
Is there such a way to complete this trick with the use of CVXPY, I ultimately think the use of one problem is best as in the future adding onto this problem, the solution will directly cause effect on the other times of day selected. Is the solution best solved with the liked of loops?

Minimise set size in task assignation problem

I have to create a solution for assigning tasks to users according to some rules and I wanted to give linear programming a try.
I have a list of tasks that require a certain skill and belong to a specific team, and I have a list of available users, their assigned team for the day, and their skill set:
# Creating dummies
task = pd.DataFrame({
'id': [n for n in range(25)],
'skill': [random.randint(0,3) for _ in range(25)]
})
task['team'] = task.apply(lambda row: 'A' for row.skill in (1, 2) else 'B', axis=1)
user_list = pd.DataFrame({
'user': [''.join(random.sample(string.ascii_lowercase, 4)) for _ in range(10)],
'team': [random.choice(['A', 'B']) for _ in range(10)]
})
user_skill = {user_list['user'][k]: random.sample(range(5), 3) for k in range(len(user_list))}
The constraints I have to implement are the following:
All tasks must be assigned
A task can only be assigned to one user
A user can not do a task for which he or she isn't skilled
A user can not do a task for another team than his or hers
The amount of tasks per user should be as low as possible inside a team
I struggled a lot to write this in PuLP but thanks to this post I managed to get some results.
# Create the problem
task_assignment = pulp.LpProblem('task_assignment', pulp.LpMaximize)
# Create model vars
pair = pulp.LpVariable.dicts("Pair", (user_list.user, task.id), cat=pulp.LpBinary)
task_covered = pulp.LpVariable.dicts('Covered', task.id, cat=pulp.LpBinary)
# Set objective
task_assignment += pulp.lpSum(task_covered[t] for t in task.id) + \
0.05 * pulp.lpSum(pair[u][t] for u in user_list.user for t in task.id)
# Constraint
# A task can only be done by one user
for t in task.id:
task_assignment+= pulp.lpSum([pair[u][t] for u in user_list.user]) <= 1
# A user must be skilled for the task
for u in user_list.user:
for t in task.id:
if not task[task.id == t].skill.values[0] in user_skill[u]:
task_assignment += pair[u][t] == 0
# A user can not do a task for another team
for u in user_list.user:
for t in task.id:
if not (task[task.id == t].team.values[0] == user_list[user_list.user == u].team.values[0]):
task_assignment+= pair[u][t] == 0
task_assignment.solve()
My problem is that I have absolutely no idea on how to implement the last constraint (i.e. the amount of tasks per user should be as low as possible inside a team)
Does someone have any idea how to do this ?
First of all, your dummy data set isn't valid python code since it misses some brackets.
One way to minimize the number of tasks per user inside a team is to minimize the maximal number of tasks per user inside a team. For this end, we just include a non-negative variable eps for each team and add the following constraints:
teams = user_list.team.unique()
# Create the problem
task_assignment = pulp.LpProblem('task_assignment', pulp.LpMaximize)
# Create model vars
pair = pulp.LpVariable.dicts("Pair", (user_list.user, task.id), cat=pulp.LpBinary)
task_covered = pulp.LpVariable.dicts('Covered', task.id, cat=pulp.LpBinary)
eps = pulp.LpVariable.dicts("eps", teams, cat=pulp.LpContinuous, lowBound=0.0)
# Set objective
task_assignment += pulp.lpSum(task_covered[t] for t in task.id) + \
0.05 * pulp.lpSum(pair[u][t] for u in user_list.user for t in task.id) - \
0.01 * pulp.lpSum(eps[team] for team in teams)
# Constraint
# ... your other constraints here ...
# the amount of tasks per user should be as low as possible inside a time
for team in teams:
# for all users in the current team
for u in user_list[user_list["team"] == team].user:
task_assignment += pulp.lpSum(pair[u][t] for t in task.id) <= eps[team]
task_assignment.solve()
Because you have a maximization problem, we need to subtract the sum of the eps in the objective.

Pulp Integer Programming Constraint ignored

I am trying to solve a LpProblem with only boolean variables and Pulp seems to be ignoring some constraints. To give some context about the problem:
I want to find an optimal solution to the problem schools face when trying to create classroom groups. In this case, students are given a paper to write at most 5 other students and the school guarantees them that they will be together with at least one of those students. To see how I modeled this problem into an integer programming problem please refer to this question.
In that link you will see that my variables are defined as x_ij = 1 if student i will be together with student j, and x_i_j = 0 otherwise. Also, in that link I ask about the constraint that I am having trouble implementing with Pulp: if x_i_j = 1 and x_j_k = 1, then by transitive property, x_i_k = 1. In other words, if student i is with student j, and student j is with student k, then, student i will inherently be together with student k.
My objective is to maximize the sum of all the elements of the matrix obtained when performing a Hadamard product between the input matrix and the variables matrix. In other words, I want to contemplate as many of the student's requests as possible.
I will now provide some code snippets and screen captures that should help visualize the problem:
Inputs (just a sample: the real matrix is 37x37)
Output
As you can see in this last image, x_27 = 1 and x_37 = 1 but x_23 = 0 which doesn't make sense.
Here is how I define my variables
def define_variables():
variables = []
for i in range(AMOUNT_OF_STUDENTS):
row = []
for j in range(AMOUNT_OF_STUDENTS):
row.append(LpVariable(f"x_{i}_{j}", lowBound=0, upBound=1, cat='Integer'))
variables.append(row)
return variables
Here is how I define the transitive constraints
for i in range(len(variables)):
for j in range(i, len(variables)):
if i != j:
problem += variables[i][j] == variables[j][i] # Symmetry
for k in range(j, len(variables)):
if i < j < k < len(variables):
problem += variables[i][j] + variables[j][k] - variables[i][k] <= 1 # Transitive
problem += variables[i][j] + variables[i][k] - variables[j][k] <= 1
problem += variables[j][k] + variables[i][k] - variables[i][j] <= 1
When printing the LpProblem I see the constraint that is apparently not working:
As you can see in the output: x_2_7 = 1 and x_3_7 = 1. Therefore, to satisfy this constraint, x_2_3 should also be 1, but as you can also see in the output, it is 0.
Any ideas about what could be happening? I've been stuck for days and the problem seems to be modeled fine and it worked when I only had 8 students (64 variables). Now that I have 37 students (1369 variables) it seems to be behaving oddly. The solver arrives to a solution but it seems to be ignoring some constraints.
Any help is very much appreciated! Thank you in advance.
The constraint is working correctly. Find below the analysis:
(crossposted from github: https://github.com/coin-or/pulp/issues/377)
import pulp as pl
import pytups as pt
path = 'debugSolution.txt'
# import model
_vars, prob = pl.LpProblem.from_json(path)
# get all variables with non-zero value
vars_value = pt.SuperDict(_vars).vfilter(pl.value)
# export the lp
prob.writeLP('debugSolution.lp')
# the constraint you show in the SO problem is:
# _C3833: - x_2_3 + x_2_7 + x_3_7 <= 1
'x_2_7' in vars_value
# True, so x_2_7 has value 1
'x_3_7' in vars_value
# False, so x_3_7 has value 0
'x_2_3' in vars_value
# False, so x_2_3 has value 0
So -0 + 1 + 0 <= 1 means the constraint is respected. There must be a problem with bringing back the value of x_3_7 somewhere because you think is 1 when in pulp it's 0.
This is called a set partitioning problem and PuLP has an example in their documentation here.
In essence, instead of modeling your variables as indicators of whether student A is in the same class as student B, you'll define a mapping between a set of students and a set of classrooms. You can then apply your student preferences as either constraints or part of a maximization objective.

Activity selection using Greedy Algorithm in Python

Given the problem, I have the following approach however, I am not able to to get all the test cases
Problem Statement: A club has planned to organize several event. The volunteers are given a list of activities and the starting time and ending time of those activities.
Write a python function that accepts the activity list, start_time list and finish_time list. The function should find out and return the list of maximum number of activities that can be performed by a single person.
Assume that a person can work only on a single activity at a time. If an activity performed by a person ends at x unit time then he/she can take up the next activity which is starting at any time greater than or equal to x+1.
def find_maximum_activities(activity_list,start_time_list, finish_time_list):
activities = list(zip(activity_list, start_time_list, finish_time_list))
activities.sort(key = lambda x: x[2])
finish = 0
result = []
for i in activities:
if finish <= i[1]:
result.append(i[0])
finish = i[2]
return result
activity_list=[1,2,3,4,5,6,7]
start_time_list=[1,4,2,3,6,8,6]
finish_time_list=[2,6,4,5,7,10,9]
result=find_maximum_activities(activity_list,start_time_list, finish_time_list)
print("The maximum set of activities that can be completed:",result)
You are missing to update the finish variable.
activities.sort(key=lambda x: x[1])
finish = -1
result = []
for i in activities:
if finish <= i[0]:
result.append(d[i])
finish = i[1]
Try the above snippet.
I don't believe this is a greedy problem.
IMO, it is a DP problem.
Given an Activity you should've computed the answer for each activity that starts after this activity.
So process the activities in decreasing order of start time.
Therefore answer for a given activity will be 1 + max(Answer for all activity that start after this ends).
Make max(Answer for all activity that start after this ends) an O(1) | O(log(n)) operation.

PuLP: Stuck with objective function definition

I am a first time user of PuLP and I the last time I did linear programming, Python did not exist.
I can solve this problem with LibreOffice's Solve extension (which does LP)
But I need to do it in code.
I want to optimise a stock picking problem.
We need to pick a certain quantity of screws, say 98.
Screws are packed in packs of 25 and 100. I name those pack sizes '25' and '100'.
The cost of the pick needs to be minimised.
There is a cost to pick each pack, and there is a cost to the excess quantity picked.
The constraint is that the quantity picked >= target_qty
For example, if the cost to each unit of excess was 0.1 and the cost to pick the '25' pack was 1 and the cost to pack the '100' pack is 1.1., the cost of picking is 1 x 100 pack is
(100 - 98) *.1 + 0*1 + 1*1.1
This is cheaper than picking 4*'25' pack.
Assuming that there are dicts pack_cost{} and pack_capacity{} which both have the key pack_name,
e.g. pack_cost = {'25':1,'100':1.1} and therefore list_of_pack_names = ['25','100']
I try this:
lp_prob = pulp.LpProblem('PackSizes', pulp.LpMinimize)
packs_used = pulp.LpVariable.dicts("Packs",list_of_pack_names,lowBound=0,cat="Integer")
pack_cost = [pack_costs[pack_name]*packs_used[pack_name] for pack_name in list_of_pack_names]
excess_cost = cost_per_unit * ( sum([pack_sizes[pack_name]*packs_used[pack_name] for pack_name in list_of_pack_names])- original_qty)
lp_prob += pulp.lpSum(pack_cost) + pulp.lpSum(excess_cost) #objective function
# and constraint: total picked >= needed
lp_prob += pulp.lpSum(sum([pack_sizes[pack_name]*packs_used[pack_name] for pack_name in list_of_pack_names]) >= target_qty)
Results:
print("Status:",pulp.LpStatus[lp_prob.status])
shows Optimal
lp_prob.objective is 10*Packs_10 + 15*Packs_15 + 30*Packs_30 - 16.5
but the solution is 0 of each pack size
You may check your problem with
print(lp_prob)
You do not add any essential constraint that prevents all vars from becoming zero.
Probably, you misprinted in the constraint statement. This constraint makes the problem not trivial (check brackets):
lp_prob += pulp.lpSum(sum([pack_sizes[pack_name]*packs_used[pack_name] for pack_name in list_of_pack_names])) >= target_qty

Categories

Resources