diff --git a/pyomo/core/base/units_container.py b/pyomo/core/base/units_container.py index 1e9685188c7..dd6bb75aec9 100644 --- a/pyomo/core/base/units_container.py +++ b/pyomo/core/base/units_container.py @@ -1514,6 +1514,19 @@ def __getattribute__(self, attr): # present, at which point this instance __class__ will fall back # to PyomoUnitsContainer (where this method is not declared, OR # pint is not available and an ImportError will be raised. + # + # We need special case handling for __class__: gurobipy + # interrogates things by looking at their __class__ during + # python shutdown. Unfortunately, interrogating this + # singleton's __class__ evaluates `pint_available`, which - if + # DASK is installed - imports dask. Importing dask creates + # threading objects. Unfortunately, creating threading objects + # during interpreter shutdown generates a RuntimeError. So, our + # solution is to special-case the resolution of __class__ here + # to avoid accidentally triggering the imports. + if attr == "__class__": + return _DeferredUnitsSingleton + # if pint_available: # If the first thing that is being called is # "units.set_pint_registry(...)", then we will call __init__ diff --git a/pyomo/solvers/plugins/solvers/SCIPAMPL.py b/pyomo/solvers/plugins/solvers/SCIPAMPL.py index 69a24455706..9898b9cdd90 100644 --- a/pyomo/solvers/plugins/solvers/SCIPAMPL.py +++ b/pyomo/solvers/plugins/solvers/SCIPAMPL.py @@ -288,7 +288,7 @@ def _postsolve(self): # UNKNOWN # unknown='unknown' # An uninitialized value - if results.solver.message == "unknown": + if "unknown" in results.solver.message: results.solver.status = SolverStatus.unknown results.solver.termination_condition = TerminationCondition.unknown if len(results.solution) > 0: @@ -296,7 +296,7 @@ def _postsolve(self): # ABORTED # userInterrupt='userInterrupt' # Interrupt signal generated by user - elif results.solver.message == "user interrupt": + elif "user interrupt" in results.solver.message: results.solver.status = SolverStatus.aborted results.solver.termination_condition = TerminationCondition.userInterrupt if len(results.solution) > 0: @@ -304,7 +304,7 @@ def _postsolve(self): # OK # maxEvaluations='maxEvaluations' # Exceeded maximum number of problem evaluations - elif results.solver.message == "node limit reached": + elif "node limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations if len(results.solution) > 0: @@ -312,7 +312,7 @@ def _postsolve(self): # OK # maxEvaluations='maxEvaluations' # Exceeded maximum number of problem evaluations - elif results.solver.message == "total node limit reached": + elif "total node limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations if len(results.solution) > 0: @@ -320,7 +320,7 @@ def _postsolve(self): # OK # maxEvaluations='maxEvaluations' # Exceeded maximum number of problem evaluations - elif results.solver.message == "stall node limit reached": + elif "stall node limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxEvaluations if len(results.solution) > 0: @@ -328,7 +328,7 @@ def _postsolve(self): # OK # maxTimeLimit='maxTimeLimit' # Exceeded maximum time limited allowed by user but having return a feasible solution - elif results.solver.message == "time limit reached": + elif "time limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.maxTimeLimit if len(results.solution) > 0: @@ -336,7 +336,7 @@ def _postsolve(self): # OK # other='other' # Other, uncategorized normal termination - elif results.solver.message == "memory limit reached": + elif "memory limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.other if len(results.solution) > 0: @@ -344,7 +344,7 @@ def _postsolve(self): # OK # other='other' # Other, uncategorized normal termination - elif results.solver.message == "gap limit reached": + elif "gap limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.other if len(results.solution) > 0: @@ -352,7 +352,7 @@ def _postsolve(self): # OK # other='other' # Other, uncategorized normal termination - elif results.solver.message == "solution limit reached": + elif "solution limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.other if len(results.solution) > 0: @@ -360,7 +360,7 @@ def _postsolve(self): # OK # other='other' # Other, uncategorized normal termination - elif results.solver.message == "solution improvement limit reached": + elif "solution improvement limit reached" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.other if len(results.solution) > 0: @@ -368,19 +368,27 @@ def _postsolve(self): # OK # optimal='optimal' # Found an optimal solution - elif results.solver.message == "optimal solution found": + elif "optimal solution" in results.solver.message: results.solver.status = SolverStatus.ok results.solver.termination_condition = TerminationCondition.optimal if len(results.solution) > 0: results.solution(0).status = SolutionStatus.optimal - if results.problem.sense == ProblemSense.minimize: - results.problem.lower_bound = results.solver.primal_bound - else: - results.problem.upper_bound = results.solver.primal_bound + try: + if results.problem.sense == ProblemSense.minimize: + results.problem.lower_bound = results.solver.primal_bound + else: + results.problem.upper_bound = results.solver.primal_bound + except AttributeError: + """ + This may occur if SCIP solves the problem during presolve. In that case, + the log file may not get parsed correctly (self.read_scip_log), and + results.solver.primal_bound will not be populated. + """ + pass # WARNING # infeasible='infeasible' # Demonstrated that the problem is infeasible - elif results.solver.message == "infeasible": + elif "infeasible" in results.solver.message: results.solver.status = SolverStatus.warning results.solver.termination_condition = TerminationCondition.infeasible if len(results.solution) > 0: @@ -388,7 +396,7 @@ def _postsolve(self): # WARNING # unbounded='unbounded' # Demonstrated that problem is unbounded - elif results.solver.message == "unbounded": + elif "unbounded" in results.solver.message: results.solver.status = SolverStatus.warning results.solver.termination_condition = TerminationCondition.unbounded if len(results.solution) > 0: @@ -396,7 +404,7 @@ def _postsolve(self): # WARNING # infeasibleOrUnbounded='infeasibleOrUnbounded' # Problem is either infeasible or unbounded - elif results.solver.message == "infeasible or unbounded": + elif "infeasible or unbounded" in results.solver.message: results.solver.status = SolverStatus.warning results.solver.termination_condition = ( TerminationCondition.infeasibleOrUnbounded