From 89e914b5a8bbe26d5064b9ed4c7f2ea1ef3726c4 Mon Sep 17 00:00:00 2001 From: Sebastian Quintero Date: Thu, 3 Oct 2024 13:15:55 -0500 Subject: [PATCH] Update pyomo apps to support SCIP --- python-pyomo-knapsack/main.py | 3 +- python-pyomo-knapsack/requirements.txt | 2 +- python-pyomo-shiftassignment/main.py | 9 +-- python-pyomo-shiftassignment/requirements.txt | 2 +- python-pyomo-shiftplanning/main.py | 66 +++++++++++-------- python-pyomo-shiftplanning/requirements.txt | 2 +- 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/python-pyomo-knapsack/main.py b/python-pyomo-knapsack/main.py index 31b4fe9..124bf5b 100644 --- a/python-pyomo-knapsack/main.py +++ b/python-pyomo-knapsack/main.py @@ -7,6 +7,7 @@ SUPPORTED_PROVIDER_DURATIONS = { "cbc": "sec", "glpk": "tmlim", + "scip": "limits/time", } @@ -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) diff --git a/python-pyomo-knapsack/requirements.txt b/python-pyomo-knapsack/requirements.txt index 84dff76..6151d99 100644 --- a/python-pyomo-knapsack/requirements.txt +++ b/python-pyomo-knapsack/requirements.txt @@ -1,2 +1,2 @@ -pyomo==6.7.2 +pyomo==6.8.0 nextmv==0.12.0 diff --git a/python-pyomo-shiftassignment/main.py b/python-pyomo-shiftassignment/main.py index 891a244..9b4db76 100644 --- a/python-pyomo-shiftassignment/main.py +++ b/python-pyomo-shiftassignment/main.py @@ -1,5 +1,4 @@ import datetime -import logging import time from typing import Any @@ -10,6 +9,7 @@ SUPPORTED_PROVIDER_DURATIONS = { "cbc": "sec", "glpk": "tmlim", + "scip": "limits/time", } # Status of the solver after optimizing. @@ -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) @@ -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 = {} diff --git a/python-pyomo-shiftassignment/requirements.txt b/python-pyomo-shiftassignment/requirements.txt index 84dff76..6151d99 100644 --- a/python-pyomo-shiftassignment/requirements.txt +++ b/python-pyomo-shiftassignment/requirements.txt @@ -1,2 +1,2 @@ -pyomo==6.7.2 +pyomo==6.8.0 nextmv==0.12.0 diff --git a/python-pyomo-shiftplanning/main.py b/python-pyomo-shiftplanning/main.py index 0c97f83..5aaabc1 100644 --- a/python-pyomo-shiftplanning/main.py +++ b/python-pyomo-shiftplanning/main.py @@ -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", } @@ -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) @@ -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: @@ -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 @@ -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 @@ -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": [ { diff --git a/python-pyomo-shiftplanning/requirements.txt b/python-pyomo-shiftplanning/requirements.txt index 84dff76..6151d99 100644 --- a/python-pyomo-shiftplanning/requirements.txt +++ b/python-pyomo-shiftplanning/requirements.txt @@ -1,2 +1,2 @@ -pyomo==6.7.2 +pyomo==6.8.0 nextmv==0.12.0