diff --git a/pyomo/contrib/piecewise/tests/test_nonlinear_to_pwl.py b/pyomo/contrib/piecewise/tests/test_nonlinear_to_pwl.py index 29bfd8723a7..48c36c017fb 100644 --- a/pyomo/contrib/piecewise/tests/test_nonlinear_to_pwl.py +++ b/pyomo/contrib/piecewise/tests/test_nonlinear_to_pwl.py @@ -19,6 +19,8 @@ from pyomo.contrib.piecewise.transform.nonlinear_to_pwl import ( NonlinearToPWL, DomainPartitioningMethod, + lineartree_available, + sklearn_available, ) from pyomo.core.base.expression import _ExpressionData from pyomo.core.expr.compare import ( @@ -45,8 +47,6 @@ SolverFactory('gurobi').available(exception_flag=False) and SolverFactory('gurobi').license_is_valid() ) -lineartree_available = attempt_import('lineartree')[1] -sklearn_available = attempt_import('sklearn.linear_model')[1] class TestNonlinearToPWL_1D(unittest.TestCase): diff --git a/pyomo/contrib/piecewise/transform/nonlinear_to_pwl.py b/pyomo/contrib/piecewise/transform/nonlinear_to_pwl.py index 6794a2428b6..26b4d533cbb 100644 --- a/pyomo/contrib/piecewise/transform/nonlinear_to_pwl.py +++ b/pyomo/contrib/piecewise/transform/nonlinear_to_pwl.py @@ -39,7 +39,7 @@ from pyomo.common.collections import ComponentMap, ComponentSet from pyomo.common.config import ConfigDict, ConfigValue, PositiveInt, InEnum from pyomo.common.dependencies import attempt_import -from pyomo.common.dependencies import numpy as np +from pyomo.common.dependencies import numpy as np, packaging from pyomo.common.enums import IntEnum from pyomo.common.modeling import unique_component_name from pyomo.core.expr.numeric_expr import SumExpression @@ -53,7 +53,28 @@ from pyomo.repn.util import ExprType, OrderedVarRecorder -lineartree, lineartree_available = attempt_import('lineartree') +def _lt_importer(): + import lineartree + import importlib.metadata + + # linear-tree through version 0.3.5 relies on a private method from + # scikit-learn. That method was removed in scikit-learn version + # 1.7.0. We will report linear-tree as "unavailable" if + # scikit-learn is "too new." + lt_ver = packaging.version.parse(importlib.metadata.version('linear-tree')) + if lt_ver <= packaging.version.Version('0.3.5'): + import sklearn + + skl_ver = packaging.version.parse(sklearn.__version__) + if skl_ver >= packaging.version.Version('1.7.0'): + raise ImportError( + f"linear-tree<=0.3.5 (found {lt_ver}) is incompatible with " + f"scikit-learn>=1.7.0 (found {skl_ver})" + ) + return lineartree + + +lineartree, lineartree_available = attempt_import('lineartree', importer=_lt_importer) sklearn_lm, sklearn_available = attempt_import('sklearn.linear_model') logger = logging.getLogger(__name__) diff --git a/pyomo/solvers/tests/checks/test_xpress_persistent.py b/pyomo/solvers/tests/checks/test_xpress_persistent.py index d5ec3a78d4a..65f7f18f45f 100644 --- a/pyomo/solvers/tests/checks/test_xpress_persistent.py +++ b/pyomo/solvers/tests/checks/test_xpress_persistent.py @@ -320,6 +320,10 @@ def test_add_column_exceptions(self): self.assertRaises(RuntimeError, opt.add_column, m, m.y, -2, [m.c], [1]) @unittest.skipIf(not xpress_available, "xpress is not available") + @unittest.skipIf( + xpd.xpress_available and xpd.xpress.__version__ == '9.8.0', + "Xpress 9.8 always runs global optimizer", + ) def test_nonconvexqp_locally_optimal(self): """Test non-convex QP for which xpress_direct should find a locally optimal solution."""