Skip to content

Commit

Permalink
Merge pull request #13 from nextmv-io/feature/change-objective-function
Browse files Browse the repository at this point in the history
Change the objective function to minimize workers with assignments
  • Loading branch information
sebastian-quintero authored Jan 25, 2024
2 parents 102cc2e + a8605a2 commit 7a5716f
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 11 deletions.
28 changes: 26 additions & 2 deletions shift-assignment/acceptance_test_metrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,35 @@
}
},
{
"field": "result.custom.availability_usage",
"field": "result.custom.worker_assignment_rate",
"statistic": "mean",
"metric_type": "direct-comparison",
"params": {
"operator": "ge"
"operator": "le"
}
},
{
"field": "result.custom.merged_availabilities_used",
"statistic": "mean",
"metric_type": "direct-comparison",
"params": {
"operator": "le"
}
},
{
"field": "result.custom.merged_total_availabilities",
"statistic": "mean",
"metric_type": "direct-comparison",
"params": {
"operator": "eq"
}
},
{
"field": "result.custom.merged_availability_usage",
"statistic": "mean",
"metric_type": "direct-comparison",
"params": {
"operator": "le"
}
}
]
Expand Down
37 changes: 28 additions & 9 deletions shift-assignment/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def solve(
)

# Creates the solver.
model = pyo.ConcreteModel()
solver = pyo.SolverFactory(provider)
solver.options[SUPPORTED_PROVIDER_DURATIONS[provider]] = duration

Expand All @@ -90,28 +91,46 @@ def solve(
# >>> Variables

# Create binary variables indicating whether a worker is assigned to a shift
model = pyo.ConcreteModel()
model.x_assign = pyo.Var(
[(worker["id"], shift["id"]) for worker in workers for shift in shifts],
within=pyo.Binary,
)

# Create binary variable indicating whether a worker has an assignment.
model.y_worker = pyo.Var(
[(worker["id"]) for worker in workers],
within=pyo.Binary,
)

# >>> Objective

# Maximize the sum of the preferences of the assigned shifts.
preferences = sum(
worker["preferences"].get(shift["id"], 0)
* model.x_assign[(worker["id"], shift["id"])]
for worker in workers
for shift in shifts
)
# Minimize the number of workers with assignments.
preferences = sum(model.y_worker[(worker["id"])] for worker in workers)
model.objective = pyo.Objective(
expr=preferences,
sense=pyo.maximize,
sense=pyo.minimize,
)

# >>> Constraints

# Relationship between x_assign and y_worker. If a worker is assigned to at
# least one shift, then y_worker is 1. On the other hand, if a worker is
# not assigned to any shift, then y_worker is 0.
for worker in workers:
shifts_assigned = sum(
model.x_assign[(worker["id"], shift["id"])] for shift in shifts
)
model.add_component(
f"worker_{worker['id']}_no_shifts",
pyo.Constraint(
expr=shifts_assigned <= model.y_worker[(worker["id"])] * len(shifts)
),
)
model.add_component(
f"worker_{worker['id']}_at_least_one_shift",
pyo.Constraint(expr=shifts_assigned >= model.y_worker[(worker["id"])]),
)

# Each shift must have the required number of workers.
for shift in shifts:
num_workers = sum(model.x_assign[(e["id"], shift["id"])] for e in workers)
Expand Down

0 comments on commit 7a5716f

Please sign in to comment.