-
Notifications
You must be signed in to change notification settings - Fork 557
add cuopt direct solver #3620
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
add cuopt direct solver #3620
Changes from all commits
90adbf4
b4b1502
6f64094
d9e42ad
e2e9159
d4a5d07
61e19bf
432d8c0
5d8ec53
66b9aa8
46c5466
1a61334
17d8cad
f064d64
b978841
af1d91e
645abe3
caea3bd
5540913
1b65acd
19236fe
9670f41
6cfa3e4
4c87a9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,360 @@ | ||||||||||||||||||||||||||||||||||||||
# ___________________________________________________________________________ | ||||||||||||||||||||||||||||||||||||||
# | ||||||||||||||||||||||||||||||||||||||
# Pyomo: Python Optimization Modeling Objects | ||||||||||||||||||||||||||||||||||||||
# Copyright (c) 2008-2025 | ||||||||||||||||||||||||||||||||||||||
# National Technology and Engineering Solutions of Sandia, LLC | ||||||||||||||||||||||||||||||||||||||
# Under the terms of Contract DE-NA0003525 with National Technology and | ||||||||||||||||||||||||||||||||||||||
# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain | ||||||||||||||||||||||||||||||||||||||
# rights in this software. | ||||||||||||||||||||||||||||||||||||||
# This software is distributed under the 3-clause BSD License. | ||||||||||||||||||||||||||||||||||||||
# ___________________________________________________________________________ | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
import logging | ||||||||||||||||||||||||||||||||||||||
import re | ||||||||||||||||||||||||||||||||||||||
import sys | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
from pyomo.common.collections import ComponentSet, ComponentMap, Bunch | ||||||||||||||||||||||||||||||||||||||
from pyomo.common.dependencies import attempt_import | ||||||||||||||||||||||||||||||||||||||
from pyomo.common.dependencies import numpy as np | ||||||||||||||||||||||||||||||||||||||
from pyomo.core.base import Suffix, Var, Constraint, SOSConstraint, Objective | ||||||||||||||||||||||||||||||||||||||
from pyomo.common.errors import ApplicationError | ||||||||||||||||||||||||||||||||||||||
from pyomo.common.tempfiles import TempfileManager | ||||||||||||||||||||||||||||||||||||||
from pyomo.common.tee import capture_output | ||||||||||||||||||||||||||||||||||||||
from pyomo.core.expr.numvalue import is_fixed | ||||||||||||||||||||||||||||||||||||||
from pyomo.core.expr.numvalue import value | ||||||||||||||||||||||||||||||||||||||
from pyomo.core.staleflag import StaleFlagManager | ||||||||||||||||||||||||||||||||||||||
from pyomo.repn import generate_standard_repn | ||||||||||||||||||||||||||||||||||||||
from pyomo.solvers.plugins.solvers.direct_solver import DirectSolver | ||||||||||||||||||||||||||||||||||||||
from pyomo.solvers.plugins.solvers.direct_or_persistent_solver import ( | ||||||||||||||||||||||||||||||||||||||
DirectOrPersistentSolver, | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
from pyomo.core.kernel.objective import minimize, maximize | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please do not import from kernel.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
from pyomo.opt.results.results_ import SolverResults | ||||||||||||||||||||||||||||||||||||||
from pyomo.opt.results.solution import Solution, SolutionStatus | ||||||||||||||||||||||||||||||||||||||
from pyomo.opt.results.solver import TerminationCondition, SolverStatus | ||||||||||||||||||||||||||||||||||||||
from pyomo.opt.base import SolverFactory | ||||||||||||||||||||||||||||||||||||||
from pyomo.core.base.suffix import Suffix | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You import |
||||||||||||||||||||||||||||||||||||||
import time | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
logger = logging.getLogger("pyomo.solvers") | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use the logger more liberally! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, we are really trying to move to a paradigm where we log using logger = logging.getLogger(__name__) |
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
cuopt, cuopt_available = attempt_import("cuopt") | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@SolverFactory.register("cuopt", doc="Direct python interface to CUOPT") | ||||||||||||||||||||||||||||||||||||||
class CUOPTDirect(DirectSolver): | ||||||||||||||||||||||||||||||||||||||
def __init__(self, **kwds): | ||||||||||||||||||||||||||||||||||||||
kwds["type"] = "cuoptdirect" | ||||||||||||||||||||||||||||||||||||||
super(CUOPTDirect, self).__init__(**kwds) | ||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||
import cuopt | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
self._version = tuple(int(k) for k in cuopt.__version__.split('.')) | ||||||||||||||||||||||||||||||||||||||
self._python_api_exists = True | ||||||||||||||||||||||||||||||||||||||
except ImportError: | ||||||||||||||||||||||||||||||||||||||
self._python_api_exists = False | ||||||||||||||||||||||||||||||||||||||
except Exception as e: | ||||||||||||||||||||||||||||||||||||||
print("Import of cuopt failed - cuopt message=" + str(e) + "\n") | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We generally log/raise rather than print. Not a rule, but generally a suggestion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will argue that it is a rule: as Pyomo is a library and not an application (with the exception of the
Comment on lines
+50
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This entire block of code is unnecessary and can run afoul of the self._version = cuopt.__version__.split('.')
self._python_api_exists = cuopt_available (Note that |
||||||||||||||||||||||||||||||||||||||
self._python_api_exists = False | ||||||||||||||||||||||||||||||||||||||
# Note: Undefined capabilities default to None | ||||||||||||||||||||||||||||||||||||||
self._capabilities.linear = True | ||||||||||||||||||||||||||||||||||||||
self._capabilities.integer = True | ||||||||||||||||||||||||||||||||||||||
self.referenced_vars = ComponentSet() | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _apply_solver(self): | ||||||||||||||||||||||||||||||||||||||
StaleFlagManager.mark_all_as_stale() | ||||||||||||||||||||||||||||||||||||||
log_file = None | ||||||||||||||||||||||||||||||||||||||
if self._log_file: | ||||||||||||||||||||||||||||||||||||||
log_file = self._log_file | ||||||||||||||||||||||||||||||||||||||
t0 = time.time() | ||||||||||||||||||||||||||||||||||||||
self.solution = cuopt.linear_programming.solver.Solve(self._solver_model) | ||||||||||||||||||||||||||||||||||||||
t1 = time.time() | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+70
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine, but just so you're aware, we have this lovely little utility called |
||||||||||||||||||||||||||||||||||||||
self._wallclock_time = t1 - t0 | ||||||||||||||||||||||||||||||||||||||
return Bunch(rc=None, log=None) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _add_constraint(self, constraints): | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is confusing: the method implies it is for a single constraint, but the implementation and usage below are for a generator of constraints. I would recommend renaming to make it clear that you are expecting multiple constraints.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
c_lb, c_ub = [], [] | ||||||||||||||||||||||||||||||||||||||
matrix_data = [] | ||||||||||||||||||||||||||||||||||||||
matrix_indptr = [0] | ||||||||||||||||||||||||||||||||||||||
matrix_indices = [] | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
con_idx = 0 | ||||||||||||||||||||||||||||||||||||||
for con in constraints: | ||||||||||||||||||||||||||||||||||||||
if not con.active: | ||||||||||||||||||||||||||||||||||||||
return None | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+84
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there are multiple constraints, one that is active and all others that aren't, and this hits an inactive one first, wouldn't that cause the loop to immediately exit / it wouldn't get to the active constraint? I think you probably want |
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if self._skip_trivial_constraints and is_fixed(con.body): | ||||||||||||||||||||||||||||||||||||||
return None | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+87
to
+88
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should check if the trivial constraint is actually feasible |
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if not con.has_lb() and not con.has_ub(): | ||||||||||||||||||||||||||||||||||||||
assert not con.equality | ||||||||||||||||||||||||||||||||||||||
continue # non-binding, so skip | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
conname = self._symbol_map.getSymbol(con, self._labeler) | ||||||||||||||||||||||||||||||||||||||
self._pyomo_con_to_solver_con_map[con] = con_idx | ||||||||||||||||||||||||||||||||||||||
con_idx += 0 | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't seem to actually be incrementing. |
||||||||||||||||||||||||||||||||||||||
repn = generate_standard_repn(con.body, quadratic=False) | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would strongly recommend using the |
||||||||||||||||||||||||||||||||||||||
matrix_data.extend(repn.linear_coefs) | ||||||||||||||||||||||||||||||||||||||
matrix_indices.extend( | ||||||||||||||||||||||||||||||||||||||
[self.var_name_dict[str(i)] for i in repn.linear_vars] | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
(the explicit formation of the list is unnecessary and adds unnecessary overhead) |
||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
self.referenced_vars.update(repn.linear_vars) | ||||||||||||||||||||||||||||||||||||||
matrix_indptr.append(len(matrix_data)) | ||||||||||||||||||||||||||||||||||||||
c_lb.append( | ||||||||||||||||||||||||||||||||||||||
value(con.lower) - repn.constant if con.lower is not None else -np.inf | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
c_ub.append( | ||||||||||||||||||||||||||||||||||||||
value(con.upper) - repn.constant if con.upper is not None else np.inf | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+104
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is doing a bunch of unnecessary work
I would recommend at the beginning of the loop doing: for con in constraints:
lb, body, ub = con.to_bounded_expression(evaluate_bounds=True) |
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if len(matrix_data) == 0: | ||||||||||||||||||||||||||||||||||||||
matrix_data = [0] | ||||||||||||||||||||||||||||||||||||||
matrix_indices = [0] | ||||||||||||||||||||||||||||||||||||||
matrix_indptr = [0, 1] | ||||||||||||||||||||||||||||||||||||||
c_lb = [0] | ||||||||||||||||||||||||||||||||||||||
c_ub = [0] | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
self._solver_model.set_csr_constraint_matrix( | ||||||||||||||||||||||||||||||||||||||
np.array(matrix_data), np.array(matrix_indices), np.array(matrix_indptr) | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
self._solver_model.set_constraint_lower_bounds(np.array(c_lb)) | ||||||||||||||||||||||||||||||||||||||
self._solver_model.set_constraint_upper_bounds(np.array(c_ub)) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _add_var(self, variables): | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to
Suggested change
|
||||||||||||||||||||||||||||||||||||||
# Map variable to index and get var bounds | ||||||||||||||||||||||||||||||||||||||
self.var_name_dict = {} | ||||||||||||||||||||||||||||||||||||||
v_lb, v_ub, v_type = [], [], [] | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
for i, v in enumerate(variables): | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that each call to lb, ub = v.bounds |
||||||||||||||||||||||||||||||||||||||
if v.is_binary(): | ||||||||||||||||||||||||||||||||||||||
v_type.append("I") | ||||||||||||||||||||||||||||||||||||||
v_lb.append(v.lb if v.lb is not None else 0) | ||||||||||||||||||||||||||||||||||||||
v_ub.append(v.ub if v.ub is not None else 1) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+132
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||
if v.is_integer(): | ||||||||||||||||||||||||||||||||||||||
v_type.append("I") | ||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||
v_type.append("C") | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+135
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good form to verify that
Suggested change
|
||||||||||||||||||||||||||||||||||||||
v_lb.append(v.lb if v.lb is not None else -np.inf) | ||||||||||||||||||||||||||||||||||||||
v_ub.append(v.ub if v.ub is not None else np.inf) | ||||||||||||||||||||||||||||||||||||||
self.var_name_dict[str(v)] = i | ||||||||||||||||||||||||||||||||||||||
self._pyomo_var_to_ndx_map[v] = self._ndx_count | ||||||||||||||||||||||||||||||||||||||
self._ndx_count += 1 | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
self._solver_model.set_variable_lower_bounds(np.array(v_lb)) | ||||||||||||||||||||||||||||||||||||||
self._solver_model.set_variable_upper_bounds(np.array(v_ub)) | ||||||||||||||||||||||||||||||||||||||
self._solver_model.set_variable_types(np.array(v_type)) | ||||||||||||||||||||||||||||||||||||||
self._solver_model.set_variable_names(np.array(list(self.var_name_dict.keys()))) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _set_objective(self, objective): | ||||||||||||||||||||||||||||||||||||||
repn = generate_standard_repn(objective.expr, quadratic=False) | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, I would recommend switching to |
||||||||||||||||||||||||||||||||||||||
self.referenced_vars.update(repn.linear_vars) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
obj_coeffs = [0] * len(self.var_name_dict) | ||||||||||||||||||||||||||||||||||||||
for i, coeff in enumerate(repn.linear_coefs): | ||||||||||||||||||||||||||||||||||||||
obj_coeffs[self.var_name_dict[str(repn.linear_vars[i])]] = coeff | ||||||||||||||||||||||||||||||||||||||
self._solver_model.set_objective_coefficients(np.array(obj_coeffs)) | ||||||||||||||||||||||||||||||||||||||
if objective.sense == maximize: | ||||||||||||||||||||||||||||||||||||||
self._solver_model.set_maximize(True) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+158
to
+159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This relies on the cuopt default being
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _set_instance(self, model, kwds={}): | ||||||||||||||||||||||||||||||||||||||
DirectOrPersistentSolver._set_instance(self, model, kwds) | ||||||||||||||||||||||||||||||||||||||
self.var_name_dict = None | ||||||||||||||||||||||||||||||||||||||
self._pyomo_var_to_ndx_map = ComponentMap() | ||||||||||||||||||||||||||||||||||||||
self._ndx_count = 0 | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||
self._solver_model = cuopt.linear_programming.DataModel() | ||||||||||||||||||||||||||||||||||||||
except Exception: | ||||||||||||||||||||||||||||||||||||||
e = sys.exc_info()[1] | ||||||||||||||||||||||||||||||||||||||
msg = ( | ||||||||||||||||||||||||||||||||||||||
"Unable to create CUOPT model. " | ||||||||||||||||||||||||||||||||||||||
"Have you installed the Python " | ||||||||||||||||||||||||||||||||||||||
"SDK for CUOPT?\n\n\t" + "Error message: {0}".format(e) | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+169
to
+175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is never raised or logged, so a user will never see this message. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worse, the user will get a strange exception later ... probably from |
||||||||||||||||||||||||||||||||||||||
self._add_block(model) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _add_block(self, block): | ||||||||||||||||||||||||||||||||||||||
self._add_var( | ||||||||||||||||||||||||||||||||||||||
block.component_data_objects( | ||||||||||||||||||||||||||||||||||||||
ctype=Var, descend_into=True, active=True, sort=True | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
self._add_constraint( | ||||||||||||||||||||||||||||||||||||||
block.component_data_objects( | ||||||||||||||||||||||||||||||||||||||
ctype=Constraint, descend_into=True, active=True, sort=True | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
for sub_block in block.block_data_objects(descend_into=True, active=True): | ||||||||||||||||||||||||||||||||||||||
obj_counter = 0 | ||||||||||||||||||||||||||||||||||||||
for obj in sub_block.component_data_objects( | ||||||||||||||||||||||||||||||||||||||
ctype=Objective, descend_into=False, active=True | ||||||||||||||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||||||||||||||
obj_counter += 1 | ||||||||||||||||||||||||||||||||||||||
if obj_counter > 1: | ||||||||||||||||||||||||||||||||||||||
raise ValueError( | ||||||||||||||||||||||||||||||||||||||
"Solver interface does not support multiple objectives." | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
self._set_objective(obj) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+189
to
+199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be made simpler / more efficient:
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _postsolve(self): | ||||||||||||||||||||||||||||||||||||||
extract_duals = False | ||||||||||||||||||||||||||||||||||||||
extract_slacks = False | ||||||||||||||||||||||||||||||||||||||
extract_reduced_costs = False | ||||||||||||||||||||||||||||||||||||||
for suffix in self._suffixes: | ||||||||||||||||||||||||||||||||||||||
flag = False | ||||||||||||||||||||||||||||||||||||||
if re.match(suffix, "dual"): | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could very well be wrong, but isn't this backwards? I thought it worked like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic is fragile, and is being reworked in the That said, this logic was copied from other direct solvers (I saw it in CPLEX.py and GUROBI.py). It would be good to rework this, but given that it is consistent with the (questionable) behavior in other solvers, I will not complain if we leave it as is. |
||||||||||||||||||||||||||||||||||||||
extract_duals = True | ||||||||||||||||||||||||||||||||||||||
flag = True | ||||||||||||||||||||||||||||||||||||||
if re.match(suffix, "rc"): | ||||||||||||||||||||||||||||||||||||||
extract_reduced_costs = True | ||||||||||||||||||||||||||||||||||||||
flag = True | ||||||||||||||||||||||||||||||||||||||
if not flag: | ||||||||||||||||||||||||||||||||||||||
raise RuntimeError( | ||||||||||||||||||||||||||||||||||||||
"***The cuopt_direct solver plugin cannot extract solution suffix=" | ||||||||||||||||||||||||||||||||||||||
+ suffix | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+213
to
+217
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be better to log loudly than to raise an error here (no strong feelings either way, just wanted to offer an alternative option). |
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
solution = self.solution | ||||||||||||||||||||||||||||||||||||||
status = solution.get_termination_status() | ||||||||||||||||||||||||||||||||||||||
self.results = SolverResults() | ||||||||||||||||||||||||||||||||||||||
soln = Solution() | ||||||||||||||||||||||||||||||||||||||
self.results.solver.name = "CUOPT" | ||||||||||||||||||||||||||||||||||||||
self.results.solver.wallclock_time = self._wallclock_time | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
prob_type = solution.problem_category | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if status in [1]: | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not create lists / use
Suggested change
(here and all the subsequent tests) |
||||||||||||||||||||||||||||||||||||||
self.results.solver.status = SolverStatus.ok | ||||||||||||||||||||||||||||||||||||||
self.results.solver.termination_condition = TerminationCondition.optimal | ||||||||||||||||||||||||||||||||||||||
soln.status = SolutionStatus.optimal | ||||||||||||||||||||||||||||||||||||||
elif status in [3]: | ||||||||||||||||||||||||||||||||||||||
self.results.solver.status = SolverStatus.warning | ||||||||||||||||||||||||||||||||||||||
self.results.solver.termination_condition = TerminationCondition.unbounded | ||||||||||||||||||||||||||||||||||||||
soln.status = SolutionStatus.unbounded | ||||||||||||||||||||||||||||||||||||||
elif status in [8]: | ||||||||||||||||||||||||||||||||||||||
self.results.solver.status = SolverStatus.ok | ||||||||||||||||||||||||||||||||||||||
self.results.solver.termination_condition = TerminationCondition.feasible | ||||||||||||||||||||||||||||||||||||||
soln.status = SolutionStatus.feasible | ||||||||||||||||||||||||||||||||||||||
elif status in [2]: | ||||||||||||||||||||||||||||||||||||||
self.results.solver.status = SolverStatus.warning | ||||||||||||||||||||||||||||||||||||||
self.results.solver.termination_condition = TerminationCondition.infeasible | ||||||||||||||||||||||||||||||||||||||
soln.status = SolutionStatus.infeasible | ||||||||||||||||||||||||||||||||||||||
elif status in [4]: | ||||||||||||||||||||||||||||||||||||||
self.results.solver.status = SolverStatus.aborted | ||||||||||||||||||||||||||||||||||||||
self.results.solver.termination_condition = ( | ||||||||||||||||||||||||||||||||||||||
TerminationCondition.maxIterations | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
soln.status = SolutionStatus.stoppedByLimit | ||||||||||||||||||||||||||||||||||||||
elif status in [5]: | ||||||||||||||||||||||||||||||||||||||
self.results.solver.status = SolverStatus.aborted | ||||||||||||||||||||||||||||||||||||||
self.results.solver.termination_condition = ( | ||||||||||||||||||||||||||||||||||||||
TerminationCondition.maxTimeLimit | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
soln.status = SolutionStatus.stoppedByLimit | ||||||||||||||||||||||||||||||||||||||
elif status in [7]: | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+228
to
+256
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a lot of "magic numbers" here - what is 1? What is 3? What is 7? Can you give them names or at least add comments/explanation? |
||||||||||||||||||||||||||||||||||||||
self.results.solver.status = SolverStatus.ok | ||||||||||||||||||||||||||||||||||||||
self.results.solver.termination_condition = TerminationCondition.other | ||||||||||||||||||||||||||||||||||||||
soln.status = SolutionStatus.other | ||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||
self.results.solver.status = SolverStatus.error | ||||||||||||||||||||||||||||||||||||||
self.results.solver.termination_condition = TerminationCondition.error | ||||||||||||||||||||||||||||||||||||||
soln.status = SolutionStatus.error | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if self._solver_model.maximize: | ||||||||||||||||||||||||||||||||||||||
self.results.problem.sense = maximize | ||||||||||||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||||||||||||
self.results.problem.sense = minimize | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
self.results.problem.upper_bound = None | ||||||||||||||||||||||||||||||||||||||
self.results.problem.lower_bound = None | ||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||
self.results.problem.upper_bound = solution.get_primal_objective() | ||||||||||||||||||||||||||||||||||||||
self.results.problem.lower_bound = solution.get_primal_objective() | ||||||||||||||||||||||||||||||||||||||
Comment on lines
+273
to
+274
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implies convergence to a MIP gap of 0... I think this needs to be something like (assuming
Suggested change
|
||||||||||||||||||||||||||||||||||||||
except Exception as e: | ||||||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
var_map = self._pyomo_var_to_ndx_map | ||||||||||||||||||||||||||||||||||||||
con_map = self._pyomo_con_to_solver_con_map | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
primal_solution = solution.get_primal_solution().tolist() | ||||||||||||||||||||||||||||||||||||||
reduced_costs = None | ||||||||||||||||||||||||||||||||||||||
dual_solution = None | ||||||||||||||||||||||||||||||||||||||
if extract_reduced_costs and solution.get_problem_category() == 0: | ||||||||||||||||||||||||||||||||||||||
reduced_costs = solution.get_reduced_cost() | ||||||||||||||||||||||||||||||||||||||
if extract_duals and solution.get_problem_category() == 0: | ||||||||||||||||||||||||||||||||||||||
dual_solution = solution.get_dual_solution() | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if self._save_results: | ||||||||||||||||||||||||||||||||||||||
soln_variables = soln.variable | ||||||||||||||||||||||||||||||||||||||
soln_constraints = soln.constraint | ||||||||||||||||||||||||||||||||||||||
for i, pyomo_var in enumerate(var_map.keys()): | ||||||||||||||||||||||||||||||||||||||
if len(primal_solution) > 0 and pyomo_var in self.referenced_vars: | ||||||||||||||||||||||||||||||||||||||
name = self._symbol_map.getSymbol(pyomo_var, self._labeler) | ||||||||||||||||||||||||||||||||||||||
soln_variables[name] = {"Value": primal_solution[i]} | ||||||||||||||||||||||||||||||||||||||
if reduced_costs is not None: | ||||||||||||||||||||||||||||||||||||||
soln_variables[name]["Rc"] = reduced_costs[i] | ||||||||||||||||||||||||||||||||||||||
for i, pyomo_con in enumerate(con_map.keys()): | ||||||||||||||||||||||||||||||||||||||
if dual_solution is not None: | ||||||||||||||||||||||||||||||||||||||
con_name = self._symbol_map.getSymbol(pyomo_con, self._labeler) | ||||||||||||||||||||||||||||||||||||||
soln_constraints[con_name] = {"Dual": dual_solution[i]} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
elif self._load_solutions: | ||||||||||||||||||||||||||||||||||||||
if len(primal_solution) > 0: | ||||||||||||||||||||||||||||||||||||||
for i, pyomo_var in enumerate(var_map.keys()): | ||||||||||||||||||||||||||||||||||||||
pyomo_var.set_value(primal_solution[i], skip_validation=True) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if reduced_costs is not None: | ||||||||||||||||||||||||||||||||||||||
self._load_rc() | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if dual_solution is not None: | ||||||||||||||||||||||||||||||||||||||
self._load_duals() | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
self.results.solution.insert(soln) | ||||||||||||||||||||||||||||||||||||||
return DirectOrPersistentSolver._postsolve(self) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def warm_start_capable(self): | ||||||||||||||||||||||||||||||||||||||
return False | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _load_rc(self, vars_to_load=None): | ||||||||||||||||||||||||||||||||||||||
if not hasattr(self._pyomo_model, "rc"): | ||||||||||||||||||||||||||||||||||||||
self._pyomo_model.rc = Suffix(direction=Suffix.IMPORT) | ||||||||||||||||||||||||||||||||||||||
rc = self._pyomo_model.rc | ||||||||||||||||||||||||||||||||||||||
var_map = self._pyomo_var_to_ndx_map | ||||||||||||||||||||||||||||||||||||||
if vars_to_load is None: | ||||||||||||||||||||||||||||||||||||||
vars_to_load = var_map.keys() | ||||||||||||||||||||||||||||||||||||||
reduced_costs = self.solution.get_reduced_cost() | ||||||||||||||||||||||||||||||||||||||
for pyomo_var in vars_to_load: | ||||||||||||||||||||||||||||||||||||||
rc[pyomo_var] = reduced_costs[var_map[pyomo_var]] | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def load_rc(self, vars_to_load=None): | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
Load the reduced costs into the 'rc' suffix. The 'rc' suffix must live on the parent model. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Parameters | ||||||||||||||||||||||||||||||||||||||
---------- | ||||||||||||||||||||||||||||||||||||||
vars_to_load: list of Var | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
self._load_rc(vars_to_load) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def _load_duals(self, cons_to_load=None): | ||||||||||||||||||||||||||||||||||||||
if not hasattr(self._pyomo_model, 'dual'): | ||||||||||||||||||||||||||||||||||||||
self._pyomo_model.dual = Suffix(direction=Suffix.IMPORT) | ||||||||||||||||||||||||||||||||||||||
dual = self._pyomo_model.dual | ||||||||||||||||||||||||||||||||||||||
con_map = self._pyomo_con_to_solver_con_map | ||||||||||||||||||||||||||||||||||||||
if cons_to_load is None: | ||||||||||||||||||||||||||||||||||||||
cons_to_load = con_map.keys() | ||||||||||||||||||||||||||||||||||||||
dual_solution = self.solution.get_reduced_cost() | ||||||||||||||||||||||||||||||||||||||
for pyomo_con in cons_to_load: | ||||||||||||||||||||||||||||||||||||||
dual[pyomo_con] = dual_solution[con_map[pyomo_con]] | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
def load_duals(self, cons_to_load=None): | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
Load the duals into the 'dual' suffix. The 'dual' suffix must live on the parent model. | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Parameters | ||||||||||||||||||||||||||||||||||||||
---------- | ||||||||||||||||||||||||||||||||||||||
cons_to_load: list of Constraint | ||||||||||||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||||||||||||
self._load_duals(cons_to_load) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider alphabetizing the imports