I am a newbie with PuLP and trying to solve a potentially trivial problem.
I have some projects that required employees with a certain skillset per project, defined in a matrix project_skill_demand
. The projects demand certain (wo)manpower to be maintained, eg. 1.5 (150%) of a FTE. Developers may work no more than .8 across all projects they are assigned to, and the total project demand must be fulfilled, defined in project_utilization_demand
.
I hard coded 5 developers assuming that not all are assigned to a project if possible.
Here are my two problems: Distributing employees (here developers) across projects looked straight forward, however I fail to define a minimization function that maximizes a developers utilization while at the same time minimizing the total number of assigned workers (which should be a consequence of the former).
What I additionally can't figure out is how to have the solver minimize the required skill sets for a given developer working on a project. I want to optimize for the smallest skill set possible while fulfilling all demands of a project they are assigned to.
Here is what I have so far:
import pulp as plp
Projects = ["P1", "P2", "P3"]
Devs = ["D1", "D2", "D3", "D4", "D5", "D6"]
Skills = ["S1", "S2", "S3", "S4"]
# Define demand for each project in pct FTE
project_utilization_demand = {"P1": 2.0, "P2": 0.5, "P3": 1.5,}
# Define sets of required skills for each project, unused so far
_project_skill_demand = [ # Skills
# S1 S2 S3 S4
[1, 1, 1, 1], # P1
[1, 1, 0, 0], # P2 Projects
[0, 1, 0, 1], # P3
]
project_skill_demand = plp.makeDict([Projects, Skills], _project_skill_demand)
DevSkills = [(d, s) for d in Devs for s in Skills]
Assignments = [(p, d) for p in Projects for d in Devs]
# These variables I'd like to have optimized
# Skill requirements for devs should be as little as possible
dev_skills = plp.LpVariable.dicts("DevSkills", (Devs, Skills), 0, 1, cat=plp.LpInteger)
# dev_skills = plp.LpVariable.dicts("DevSkills", (Devs, Skills), cat=plp.LpBinary) # Is there any difference using a binary variable here?
# Devs should be assigned to as few projects as possible
# Can I use information from `project_utilization_demand` as the upper limit here, currently None?
assignments = plp.LpVariable.dicts("pct_assignment", (Projects, Devs), 0, None, plp.LpContinuous)
prob = plp.LpProblem("LinearProgramming", plp.LpMinimize)
# minimizing assignments has no effect since the constraints set them to > sum(project_demand), which is 4
# and there is no reason to go beyond that
#
prob += (
plp.lpSum([assignments[p][d] for p, d in Assignments]) # plp.lpSum(assignements) is just the same
+ plp.lpSum(dev_skills) # Without a constraint, this will remain all 0
)
# Constraints
# Devs may only be utilized to max .8 (80%)
for d in Devs:
prob += (
plp.lpSum([assignments[p][d] for p in Projects]) <= .8,
f"Sum of projects for Dev {d}",
)
# total assignments of devs to projects must fulfill demand
for p in Projects:
prob += (
plp.lpSum([assignments[p][d] for d in Devs]) >= project_utilization_demand[p],
f"Sum of Devs in P {p}",
)
# Skills of devs _eventually assigned_ to a project must satisfy the project's requirements
#
# HOW?
#
prob.solve()
status = plp.LpStatus[prob.status]
for v in prob.variables():
print(f"{v.name} = {v.varValue}")
print(f"Total Costs = {prob.objective.value()}")