Skip to content

Commit

Permalink
Pyomo solver changes.
Browse files Browse the repository at this point in the history
Refactored the pyomo solvers to be classes. They work as follows now:

```
solver = PyomoSolver(problem)
results = solver.solve("f_1")
```
  • Loading branch information
gialmisi committed Apr 25, 2024
1 parent 2ec0ac6 commit ca1da4a
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 163 deletions.
24 changes: 12 additions & 12 deletions desdeo/mcdm/nimbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def solve_intermediate_solutions(
msg = f"The given number of desired intermediate ({num_desired=}) solutions must be at least 1."
raise NimbusError(msg)

_create_solver = guess_best_solver(problem) if create_solver is None else create_solver
init_solver = guess_best_solver(problem) if create_solver is None else create_solver
_solver_options = None if solver_options is None else solver_options

# compute the element-wise difference between each solution (in the decision space)
Expand Down Expand Up @@ -104,10 +104,10 @@ def solve_intermediate_solutions(
# depending on problem properties (either diff or non-diff)
asf_problem, target = add_asf_diff(problem, "target", rp)

solver = _create_solver(asf_problem, _solver_options)
solver = init_solver(asf_problem, _solver_options)

# solve and store results
result: SolverResults = solver(target)
result: SolverResults = solver.solve(target)

intermediate_solutions.append(result)

Expand Down Expand Up @@ -279,7 +279,7 @@ def solve_sub_problems(
msg = f"The current point {reference_point} is missing entries " "for one or more of the objective functions."
raise NimbusError(msg)

_create_solver = create_solver if create_solver is not None else guess_best_solver(problem)
init_solver = create_solver if create_solver is not None else guess_best_solver(problem)
_solver_options = solver_options if solver_options is not None else None

# derive the classifications based on the reference point and and previous
Expand All @@ -295,35 +295,35 @@ def solve_sub_problems(
add_nimbus_sf = add_nimbus_sf_diff if is_smooth else add_nimbus_sf_nondiff

problem_w_nimbus, nimbus_target = add_nimbus_sf(problem, "nimbus_sf", classifications, current_objectives)
nimbus_solver = _create_solver(problem_w_nimbus, _solver_options)
nimbus_solver = init_solver(problem_w_nimbus, _solver_options)

solutions.append(nimbus_solver(nimbus_target))
solutions.append(nimbus_solver.solve(nimbus_target))

if num_desired > 1:
# solve STOM
add_stom_sf = add_stom_sf_diff if is_smooth else add_stom_sf_nondiff

problem_w_stom, stom_target = add_stom_sf(problem, "stom_sf", reference_point)
stom_solver = _create_solver(problem_w_stom, _solver_options)
stom_solver = init_solver(problem_w_stom, _solver_options)

solutions.append(stom_solver(stom_target))
solutions.append(stom_solver.solve(stom_target))

if num_desired > 2:
# solve ASF
add_asf = add_asf_diff if is_smooth else add_asf_nondiff

problem_w_asf, asf_target = add_asf(problem, "asf", reference_point)
asf_solver = _create_solver(problem_w_asf, _solver_options)
asf_solver = init_solver(problem_w_asf, _solver_options)

solutions.append(asf_solver(asf_target))
solutions.append(asf_solver.solve(asf_target))

if num_desired > 3:
# solve GUESS
add_guess_sf = add_guess_sf_diff if is_smooth else add_guess_sf_nondiff

problem_w_guess, guess_target = add_guess_sf(problem, "guess_sf", reference_point)
guess_solver = _create_solver(problem_w_guess, _solver_options)
guess_solver = init_solver(problem_w_guess, _solver_options)

solutions.append(guess_solver(guess_target))
solutions.append(guess_solver.solve(guess_target))

return solutions
9 changes: 6 additions & 3 deletions desdeo/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"NevergradGenericSolver",
"PersistentGurobipySolver",
"ProximalSolver",
"PyomoBonminSolver",
"PyomoGurobiSolver",
"PyomoIpoptSolver",
"SolverOptions",
"SolverResults",
"ScalarizationError",
Expand Down Expand Up @@ -49,9 +52,9 @@
from desdeo.tools.pyomo_solver_interfaces import (
BonminOptions,
IpoptOptions,
create_pyomo_bonmin_solver,
create_pyomo_gurobi_solver,
create_pyomo_ipopt_solver,
PyomoBonminSolver,
PyomoGurobiSolver,
PyomoIpoptSolver,
)
from desdeo.tools.scalarization import (
ScalarizationError,
Expand Down
207 changes: 106 additions & 101 deletions desdeo/tools/pyomo_solver_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@
from pyomo.opt import TerminationCondition as _pyomo_TerminationCondition

from desdeo.problem import Problem, PyomoEvaluator
from desdeo.tools.generics import CreateSolverType, SolverResults

# forward typehints
create_pyomo_bonmin_solver: CreateSolverType
create_pyomo_gurobi_solver: CreateSolverType
from desdeo.tools.generics import SolverResults


class BonminOptions(BaseModel):
Expand Down Expand Up @@ -94,12 +90,12 @@ class IpoptOptions(BaseModel):
def parse_pyomo_optimizer_results(
opt_res: _pyomo_SolverResults, problem: Problem, evaluator: PyomoEvaluator
) -> SolverResults:
"""Parses pyomo SolverResults into DESDEO SolverResutls.
"""Parses pyomo SolverResults into DESDEO SolverResults.
Args:
opt_res (SolverResults): the pyomo solver results.
problem (Problem): the problem being solved.
evaluator (PyomoEvaluator): the evalutor utilized to get the pyomo solver results.
evaluator (PyomoEvaluator): the evaluator utilized to get the pyomo solver results.
Returns:
SolverResults: DESDEO solver results.
Expand Down Expand Up @@ -129,135 +125,144 @@ def parse_pyomo_optimizer_results(
)


def create_pyomo_bonmin_solver(
problem: Problem, options: BonminOptions | None = _default_bonmin_options
) -> Callable[[str], SolverResults]:
"""Creates a pyomo solver that utilizes bonmin.
class PyomoBonminSolver:
"""Creates pyomo solvers that utilize bonmin."""

Suitable for mixed-integer problems. The objective function being minimized
(target) and the constraint functions must be twice continuously
differentiable. When the objective functions and constraints are convex, the
solution is exact. When the objective or any of the constraints, or both,
are non-convex, then the solution is based on heuristics.
def __init__(self, problem: Problem, options: BonminOptions | None = _default_bonmin_options):
"""The solver is initialized with a problem and solver options.
For more info about bonmin, see: https://www.coin-or.org/Bonmin/
Suitable for mixed-integer problems. The objective function being minimized
(target) and the constraint functions must be twice continuously
differentiable. When the objective functions and constraints are convex, the
solution is exact. When the objective or any of the constraints, or both,
are non-convex, then the solution is based on heuristics.
Note:
Bonmin must be installed on the system running DESDEO, and its executable
must be defined in the PATH.
For more info about bonmin, see: https://www.coin-or.org/Bonmin/
Args:
problem (Problem): the problem to be solved.
options (BonminOptions, optional): options to be passed to the Bonmin solver.
If `None` is passed, defaults to `_default_bonmin_options` defined in
this source file. Defaults to `None`.
Note:
Bonmin must be installed on the system running DESDEO, and its executable
must be defined in the PATH.
Returns:
Callable[[str], SolverResults]: a callable function that takes
as its argument one of the symbols defined for a function expression in
problem.
"""
if options is None:
options = _default_bonmin_options
Args:
problem (Problem): the problem to be solved.
options (BonminOptions, optional): options to be passed to the Bonmin solver.
If `None` is passed, defaults to `_default_bonmin_options` defined in
this source file. Defaults to `None`.
"""
self.problem = problem
self.evaluator = PyomoEvaluator(problem)

evaluator = PyomoEvaluator(problem)
if options is None:
self.options = _default_bonmin_options
else:
self.options = options

def solver(target: str) -> SolverResults:
evaluator.set_optimization_target(target)
def solve(self, target: str) -> SolverResults:
"""Solve the problem for a given target.
Args:
target (str): the symbol of the objective function to be optimized.
Returns:
SolverResults: the results of the optimization.
"""
self.evaluator.set_optimization_target(target)

opt = pyomo.SolverFactory("bonmin", tee=True)

# set solver options
for key, value in options.asdict().items():
for key, value in self.options.asdict().items():
opt.options[key] = value
opt_res = opt.solve(evaluator.model)
opt_res = opt.solve(self.evaluator.model)

return parse_pyomo_optimizer_results(opt_res, problem, evaluator)
return parse_pyomo_optimizer_results(opt_res, self.problem, self.evaluator)

return solver

class PyomoIpoptSolver:
"""Create a pyomo solver that utilizes Ipopt."""

def create_pyomo_ipopt_solver(
problem: Problem, options: IpoptOptions | None = _default_ipopt_options
) -> Callable[[str], SolverResults]:
"""Creates a pyomo solver that utilizes Ipopt.
def __init__(self, problem: Problem, options: IpoptOptions | None = _default_ipopt_options):
"""The solver is initialized with a problem and solver options.
Suitable for non-linear, twice differentiable constrained problems.
The problem may be convex or non-convex.
Suitable for non-linear, twice differentiable constrained problems.
The problem may be convex or non-convex.
For more information, see https://coin-or.github.io/Ipopt/
For more information, see https://coin-or.github.io/Ipopt/
Note:
Ipopt must be installed on the system running DESDEO, and its executable
must be defined in the PATH.
Note:
Ipopt must be installed on the system running DESDEO, and its executable
must be defined in the PATH.
Args:
problem (Problem): the problem being solved.
options (IpoptOptions, optional): options to be passed to the Ipopt solver.
If `None` is passed, defaults to `_default_ipopt_options` defined in
this source file. Defaults to `None`.
Args:
problem (Problem): the problem being solved.
options (IpoptOptions, optional): options to be passed to the Ipopt solver.
If `None` is passed, defaults to `_default_ipopt_options` defined in
this source file. Defaults to `None`.
"""
self.problem = problem
self.evaluator = PyomoEvaluator(problem)

Returns:
Callable[[str], SolverResults]: a callable function that takes
as its argument one of the symbols defined for a function expression in
problem.
"""
if options is None:
options = _default_ipopt_options
if options is None:
self.options = _default_ipopt_options
else:
self.options = options

evaluator = PyomoEvaluator(problem)
if options is None:
options = {}
def solve(self, target: str) -> SolverResults:
"""Solve the problem for a given target.
def solver(target: str) -> SolverResults:
evaluator.set_optimization_target(target)
Args:
target (str): the symbol of the objective function to be optimized.
Returns:
SolverResults: results of the Optimization.
"""
self.evaluator.set_optimization_target(target)

opt = pyomo.SolverFactory("ipopt", tee=True)
opt_res = opt.solve(evaluator.model)
return parse_pyomo_optimizer_results(opt_res, problem, evaluator)
opt_res = opt.solve(self.evaluator.model)
return parse_pyomo_optimizer_results(opt_res, self.problem, self.evaluator)

return solver

class PyomoGurobiSolver:
"""Creates a pyomo solver that utilized Gurobi."""

def create_pyomo_gurobi_solver(
problem: Problem, options: dict[str, any] | None = None
) -> Callable[[str], SolverResults]:
"""Creates a pyomo solver that utilizes gurobi.
def __init__(self, problem: Problem, options: dict[str, any] | None = None):
"""Creates a pyomo solver that utilizes gurobi.
You need to have gurobi installed on your system for this to work.
You need to have gurobi installed on your system for this to work.
Suitable for solving mixed-integer linear and quadratic optimization
problems.
Suitable for solving mixed-integer linear and quadratic optimization
problems.
Args:
problem (Problem): the problem to be solved.
options (GurobiOptions): Dictionary of Gurobi parameters to set.
This is passed to pyomo as is, so it works the same as options
would for calling pyomo SolverFactory directly.
See https://www.gurobi.com/documentation/current/refman/parameters.html
for information on the available options
Args:
problem (Problem): the problem to be solved.
options (GurobiOptions): Dictionary of Gurobi parameters to set.
This is passed to pyomo as is, so it works the same as options
would for calling pyomo SolverFactory directly.
See https://www.gurobi.com/documentation/current/refman/parameters.html
for information on the available options
"""
self.problem = problem
self.evaluator = PyomoEvaluator(problem)

Returns:
Callable[[str], SolverResults]: a callable function that takes
as its argument one of the symbols defined for a function expression in
problem.
"""
evaluator = PyomoEvaluator(problem)
if options is None:
self.options = {}
else:
self.options = options

if options is None:
options = {}
def solve(self, target: str) -> SolverResults:
"""Solve the problem for a given target.
def solver(target: str) -> SolverResults:
evaluator.set_optimization_target(target)
Args:
target (str): the symbol of the objective function to be optimized.
opt = pyomo.SolverFactory("gurobi", solver_io="python", options=options)
Returns:
SolverResults: the results of the optimization.
"""
self.evaluator.set_optimization_target(target)

# set solver options
# for key, value in options.asdict().items():
# opt.options[key] = value
opt = pyomo.SolverFactory("gurobi", solver_io="python", options=self.options)

with pyomo.SolverFactory("gurobi", solver_io="python") as opt:
opt_res = opt.solve(evaluator.model)
return parse_pyomo_optimizer_results(opt_res, problem, evaluator)

return solver
opt_res = opt.solve(self.evaluator.model)
return parse_pyomo_optimizer_results(opt_res, self.problem, self.evaluator)

0 comments on commit ca1da4a

Please sign in to comment.