Skip to content

Commit

Permalink
Merge pull request #82 from nextmv-io/feature/pyomo-apps-support-scip
Browse files Browse the repository at this point in the history
Update pyomo apps to support SCIP
  • Loading branch information
sebastian-quintero authored Oct 3, 2024
2 parents 7beac1a + 89e914b commit ea2fcfa
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 39 deletions.
3 changes: 2 additions & 1 deletion python-pyomo-knapsack/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
SUPPORTED_PROVIDER_DURATIONS = {
"cbc": "sec",
"glpk": "tmlim",
"scip": "limits/time",
}


Expand Down Expand Up @@ -84,7 +85,7 @@ def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
model.objective = pyo.Objective(expr=values, sense=pyo.maximize)

# Solves the problem.
results = solver.solve(model)
results = solver.solve(model, tee=True)

# Convert to solution format.
value = pyo.value(model.objective, exception=False)
Expand Down
2 changes: 1 addition & 1 deletion python-pyomo-knapsack/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pyomo==6.7.2
pyomo==6.8.0
nextmv==0.12.0
9 changes: 3 additions & 6 deletions python-pyomo-shiftassignment/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import datetime
import logging
import time
from typing import Any

Expand All @@ -10,6 +9,7 @@
SUPPORTED_PROVIDER_DURATIONS = {
"cbc": "sec",
"glpk": "tmlim",
"scip": "limits/time",
}

# Status of the solver after optimizing.
Expand Down Expand Up @@ -56,9 +56,6 @@ def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
f"{', '.join(SUPPORTED_PROVIDER_DURATIONS.keys())}"
)

# Silence all Pyomo logging.
logging.getLogger("pyomo.core").setLevel(logging.ERROR)

# Prepare data
workers, shifts, rules_per_worker = convert_input(input.data)

Expand Down Expand Up @@ -146,11 +143,11 @@ def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
)

# Creates the solver.
solver = pyo.SolverFactory(provider) # Use an appropriate solver name
solver = pyo.SolverFactory(provider)
solver.options[SUPPORTED_PROVIDER_DURATIONS[provider]] = options.duration

# Solve the model.
results = solver.solve(model)
results = solver.solve(model, tee=True)

# Convert to solution format.
schedule = {}
Expand Down
2 changes: 1 addition & 1 deletion python-pyomo-shiftassignment/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pyomo==6.7.2
pyomo==6.8.0
nextmv==0.12.0
66 changes: 37 additions & 29 deletions python-pyomo-shiftplanning/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,21 @@
from typing import Any

import nextmv
from pyomo.environ import (
ConcreteModel,
Constraint,
NonNegativeIntegers,
Objective,
SolverFactory,
TerminationCondition,
Var,
minimize,
value,
)
import pyomo.environ as pyo

# Duration parameter for the solver.
SUPPORTED_PROVIDER_DURATIONS = {
"cbc": "sec",
"glpk": "tmlim",
"scip": "limits/time",
}

# Status of the solver after optimizing.
STATUS = {
TerminationCondition.feasible: "suboptimal",
TerminationCondition.infeasible: "infeasible",
TerminationCondition.optimal: "optimal",
TerminationCondition.unbounded: "unbounded",
pyo.TerminationCondition.feasible: "suboptimal",
pyo.TerminationCondition.infeasible: "infeasible",
pyo.TerminationCondition.optimal: "optimal",
pyo.TerminationCondition.unbounded: "unbounded",
}


Expand Down Expand Up @@ -50,8 +47,16 @@ def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
start_time = time.time()
nextmv.redirect_stdout() # Solver chatter is logged to stderr.

# Make sure the provider is supported.
provider = options.provider
if provider not in SUPPORTED_PROVIDER_DURATIONS:
raise ValueError(
f"Unsupported provider: {provider}. The supported providers are: "
f"{', '.join(SUPPORTED_PROVIDER_DURATIONS.keys())}"
)

# Create the Pyomo model
model = ConcreteModel()
model = pyo.ConcreteModel()

# Prepare data
shifts, demands = convert_data(input.data)
Expand All @@ -67,7 +72,7 @@ def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
required_hours = sum((p.end_time - p.start_time).seconds for p in periods) / 3600

# Create integer variables indicating how many times a shift is planned.
model.x_assign = Var([(s["id"],) for s in concrete_shifts], within=NonNegativeIntegers)
model.x_assign = pyo.Var([(s["id"],) for s in concrete_shifts], within=pyo.NonNegativeIntegers)

# Bound assignment variables by the minimum and maximum number of workers.
for s in concrete_shifts:
Expand All @@ -77,11 +82,11 @@ def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:

# Create variables for tracking various costs.
if "under_supply_cost" in input_options:
model.x_under = Var([(p,) for p in periods], within=NonNegativeIntegers)
model.underSupply = Var(within=NonNegativeIntegers)
model.x_under = pyo.Var([(p,) for p in periods], within=pyo.NonNegativeIntegers)
model.underSupply = pyo.Var(within=pyo.NonNegativeIntegers)
if "over_supply_cost" in input_options:
model.overSupply = Var(within=NonNegativeIntegers)
model.shift_cost = Var(within=NonNegativeIntegers)
model.overSupply = pyo.Var(within=pyo.NonNegativeIntegers)
model.shift_cost = pyo.Var(within=pyo.NonNegativeIntegers)

# Objective function: minimize the cost of the planned shifts
obj_expr = 0
Expand All @@ -90,7 +95,7 @@ def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
if "over_supply_cost" in input_options:
obj_expr += model.overSupply * input_options["over_supply_cost"]
obj_expr += model.shift_cost
model.objective = Objective(expr=obj_expr, sense=minimize)
model.objective = pyo.Objective(expr=obj_expr, sense=pyo.minimize)

# Constraints

Expand All @@ -100,37 +105,40 @@ def solve(input: nextmv.Input, options: nextmv.Options) -> nextmv.Output:
# Add the new constraint
model.add_component(
constraint_name,
Constraint(
pyo.Constraint(
expr=sum([model.x_assign[s["id"]] for s in p.covering_shifts]) == sum(d["count"] for d in p.demands)
),
)

# Track under supply
if "under_supply_cost" in input_options:
model.under_supply = Constraint(
model.under_supply = pyo.Constraint(
expr=model.underSupply
== sum(model.x_under[p] * (p.end_time - p.start_time).seconds / 3600 for p in periods)
)

# Track over supply
if "over_supply_cost" in input_options:
model.over_supply = Constraint(
model.over_supply = pyo.Constraint(
expr=model.overSupply
== sum(model.x_assign[s["id"]] * (s["end_time"] - s["start_time"]).seconds / 3600 for s in concrete_shifts)
- required_hours
)

# Track shift cost
model.shift_cost_track = Constraint(
model.shift_cost_track = pyo.Constraint(
expr=model.shift_cost == sum(model.x_assign[s["id"]] * s["cost"] for s in concrete_shifts)
)

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

# Solve the model.
solver = SolverFactory(options.provider)
results = solver.solve(model, tee=False, timelimit=options.duration)
results = solver.solve(model, tee=True)

# Convert to solution format.
val = value(model.objective, exception=False)
val = pyo.value(model.objective, exception=False)
schedule = {
"planned_shifts": [
{
Expand Down
2 changes: 1 addition & 1 deletion python-pyomo-shiftplanning/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pyomo==6.7.2
pyomo==6.8.0
nextmv==0.12.0

0 comments on commit ea2fcfa

Please sign in to comment.