Skip to content

Commit d96283e

Browse files
authored
Merge branch 'main' into observer
2 parents 4cea685 + 74cd43e commit d96283e

File tree

9 files changed

+76
-25
lines changed

9 files changed

+76
-25
lines changed

.github/workflows/test_branches.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,10 @@ jobs:
375375
echo ""
376376
echo "*** Install Pyomo dependencies ***"
377377
# For windows, cannot use newer setuptools because of APPSI compilation issues
378+
# There seems to be some specific problem with platformdirs 4.5.0
379+
# on win 3.13/3.14 as of 2025-10-23
378380
if test "${{matrix.TARGET}}" == 'win'; then
379-
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES setuptools<74.0.0"
381+
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES setuptools<74.0.0 platformdirs!=4.5.0"
380382
fi
381383
# Note: this will fail the build if any installation fails (or
382384
# possibly if it outputs messages to stderr)

.github/workflows/test_pr_and_main.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,10 @@ jobs:
427427
echo ""
428428
echo "*** Install Pyomo dependencies ***"
429429
# For windows, cannot use newer setuptools because of APPSI compilation issues
430+
# There seems to be some specific problem with platformdirs 4.5.0
431+
# on win 3.13/3.14 as of 2025-10-23
430432
if test "${{matrix.TARGET}}" == 'win'; then
431-
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES setuptools<74.0.0"
433+
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES setuptools<74.0.0 platformdirs!=4.5.0"
432434
fi
433435
# Note: this will fail the build if any installation fails (or
434436
# possibly if it outputs messages to stderr)

pyomo/contrib/piecewise/tests/test_nonlinear_to_pwl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from pyomo.contrib.piecewise.transform.nonlinear_to_pwl import (
2020
NonlinearToPWL,
2121
DomainPartitioningMethod,
22+
lineartree_available,
23+
sklearn_available,
2224
)
2325
from pyomo.core.base.expression import _ExpressionData
2426
from pyomo.core.expr.compare import (
@@ -45,8 +47,6 @@
4547
SolverFactory('gurobi').available(exception_flag=False)
4648
and SolverFactory('gurobi').license_is_valid()
4749
)
48-
lineartree_available = attempt_import('lineartree')[1]
49-
sklearn_available = attempt_import('sklearn.linear_model')[1]
5050

5151

5252
class TestNonlinearToPWL_1D(unittest.TestCase):

pyomo/contrib/piecewise/transform/nonlinear_to_pwl.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from pyomo.common.collections import ComponentMap, ComponentSet
4040
from pyomo.common.config import ConfigDict, ConfigValue, PositiveInt, InEnum
4141
from pyomo.common.dependencies import attempt_import
42-
from pyomo.common.dependencies import numpy as np
42+
from pyomo.common.dependencies import numpy as np, packaging
4343
from pyomo.common.enums import IntEnum
4444
from pyomo.common.modeling import unique_component_name
4545
from pyomo.core.expr.numeric_expr import SumExpression
@@ -53,7 +53,28 @@
5353
from pyomo.repn.util import ExprType, OrderedVarRecorder
5454

5555

56-
lineartree, lineartree_available = attempt_import('lineartree')
56+
def _lt_importer():
57+
import lineartree
58+
import importlib.metadata
59+
60+
# linear-tree through version 0.3.5 relies on a private method from
61+
# scikit-learn. That method was removed in scikit-learn version
62+
# 1.7.0. We will report linear-tree as "unavailable" if
63+
# scikit-learn is "too new."
64+
lt_ver = packaging.version.parse(importlib.metadata.version('linear-tree'))
65+
if lt_ver <= packaging.version.Version('0.3.5'):
66+
import sklearn
67+
68+
skl_ver = packaging.version.parse(sklearn.__version__)
69+
if skl_ver >= packaging.version.Version('1.7.0'):
70+
raise ImportError(
71+
f"linear-tree<=0.3.5 (found {lt_ver}) is incompatible with "
72+
f"scikit-learn>=1.7.0 (found {skl_ver})"
73+
)
74+
return lineartree
75+
76+
77+
lineartree, lineartree_available = attempt_import('lineartree', importer=_lt_importer)
5778
sklearn_lm, sklearn_available = attempt_import('sklearn.linear_model')
5879

5980
logger = logging.getLogger(__name__)

pyomo/contrib/solver/solvers/knitro/base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,4 +268,6 @@ def _get_error_type(
268268
return NoReducedCostsError
269269
elif item_type is ConstraintData and value_type == ValueType.DUAL:
270270
return NoDualsError
271-
raise DeveloperError()
271+
raise DeveloperError(
272+
f"Unsupported KNITRO item type {item_type} and value type {value_type}."
273+
)

pyomo/contrib/solver/solvers/knitro/config.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ def __init__(
3535
ConfigValue(
3636
domain=Bool,
3737
default=False,
38-
doc="KNITRO solver does not allow variable removal. We can either make the variable a continuous free variable or rebuild the whole model when variable removal is attempted. When `rebuild_model_on_remove_var` is set to True, the model will be rebuilt.",
38+
doc=(
39+
"KNITRO solver does not allow variable removal. We can "
40+
"either make the variable a continuous free variable or "
41+
"rebuild the whole model when variable removal is "
42+
"attempted. When `rebuild_model_on_remove_var` is set to "
43+
"True, the model will be rebuilt."
44+
),
3945
),
4046
)
4147

@@ -44,6 +50,11 @@ def __init__(
4450
ConfigValue(
4551
domain=Bool,
4652
default=False,
47-
doc="To evaluate non-linear constraints, KNITRO solver sets explicit values on variables. This option controls whether to restore the original variable values after solving.",
53+
doc=(
54+
"To evaluate non-linear constraints, KNITRO solver sets "
55+
"explicit values on variables. This option controls "
56+
"whether to restore the original variable values after "
57+
"solving."
58+
),
4859
),
4960
)

pyomo/contrib/solver/solvers/knitro/engine.py

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,20 @@ def parse_bounds(
4444
if item.fixed:
4545
bounds_map[BoundType.EQ][i] = value(item.value)
4646
continue
47-
if item.has_lb():
48-
bounds_map[BoundType.LO][i] = value(item.lb)
49-
if item.has_ub():
50-
bounds_map[BoundType.UP][i] = value(item.ub)
47+
lb, ub = item.bounds
48+
if lb is not None:
49+
bounds_map[BoundType.LO][i] = lb
50+
if ub is not None:
51+
bounds_map[BoundType.UP][i] = ub
5152
elif isinstance(item, ConstraintData):
53+
lb, _, ub = item.to_bounded_expression(evaluate_bounds=True)
5254
if item.equality:
53-
bounds_map[BoundType.EQ][i] = value(item.lower)
55+
bounds_map[BoundType.EQ][i] = lb
5456
continue
55-
if item.has_lb():
56-
bounds_map[BoundType.LO][i] = value(item.lower)
57-
if item.has_ub():
58-
bounds_map[BoundType.UP][i] = value(item.upper)
57+
if lb is not None:
58+
bounds_map[BoundType.LO][i] = lb
59+
if ub is not None:
60+
bounds_map[BoundType.UP][i] = ub
5961
return bounds_map
6062

6163

@@ -85,7 +87,7 @@ def api_set_param(param_type: int) -> Callable[..., None]:
8587
return knitro.KN_set_double_param
8688
elif param_type == knitro.KN_PARAMTYPE_STRING:
8789
return knitro.KN_set_char_param
88-
raise DeveloperError()
90+
raise DeveloperError(f"Unsupported KNITRO parameter type: {param_type}")
8991

9092

9193
def api_get_values(
@@ -101,15 +103,17 @@ def api_get_values(
101103
return knitro.KN_get_con_dual_values
102104
elif value_type == ValueType.PRIMAL:
103105
return knitro.KN_get_con_values
104-
raise DeveloperError()
106+
raise DeveloperError(
107+
f"Unsupported KNITRO item type or value type: {item_type}, {value_type}"
108+
)
105109

106110

107111
def api_add_items(item_type: type[ItemType]) -> Callable[..., Optional[list[int]]]:
108112
if item_type is VarData:
109113
return knitro.KN_add_vars
110114
elif item_type is ConstraintData:
111115
return knitro.KN_add_cons
112-
raise DeveloperError()
116+
raise DeveloperError(f"Unsupported KNITRO item type: {item_type}")
113117

114118

115119
def api_set_bnds(
@@ -129,13 +133,15 @@ def api_set_bnds(
129133
return knitro.KN_set_con_lobnds
130134
elif bound_type == BoundType.UP:
131135
return knitro.KN_set_con_upbnds
132-
raise DeveloperError()
136+
raise DeveloperError(
137+
f"Unsupported KNITRO item type or bound type: {item_type}, {bound_type}"
138+
)
133139

134140

135141
def api_set_types(item_type: type[ItemType]) -> Callable[..., None]:
136142
if item_type is VarData:
137143
return knitro.KN_set_var_types
138-
raise DeveloperError()
144+
raise DeveloperError(f"Unsupported KNITRO item type: {item_type}")
139145

140146

141147
def api_add_struct(is_obj: bool, structure_type: StructureType) -> Callable[..., None]:
@@ -153,7 +159,9 @@ def api_add_struct(is_obj: bool, structure_type: StructureType) -> Callable[...,
153159
return knitro.KN_add_con_linear_struct
154160
elif structure_type == StructureType.QUADRATIC:
155161
return knitro.KN_add_con_quadratic_struct
156-
raise DeveloperError()
162+
raise DeveloperError(
163+
f"Unsupported KNITRO structure type: is_obj={is_obj}, structure_type={structure_type}"
164+
)
157165

158166

159167
class Engine:

pyomo/contrib/solver/solvers/knitro/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from pyomo.contrib.solver.solvers.knitro.typing import Function
1919
from pyomo.core.base.block import BlockData
2020
from pyomo.core.base.constraint import Constraint, ConstraintData
21+
from pyomo.core.base.enums import SortComponents
2122
from pyomo.core.base.expression import Expression
2223
from pyomo.core.base.objective import Objective, ObjectiveData
2324
from pyomo.core.base.var import VarData
@@ -51,7 +52,7 @@ def get_active_constraints(block: BlockData) -> list[ConstraintData]:
5152
5253
"""
5354
generator = block.component_data_objects(
54-
Constraint, descend_into=True, active=True, sort=True
55+
Constraint, descend_into=True, active=True, sort=SortComponents.deterministic
5556
)
5657
return list(generator)
5758

pyomo/solvers/tests/checks/test_xpress_persistent.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,10 @@ def test_add_column_exceptions(self):
320320
self.assertRaises(RuntimeError, opt.add_column, m, m.y, -2, [m.c], [1])
321321

322322
@unittest.skipIf(not xpress_available, "xpress is not available")
323+
@unittest.skipIf(
324+
xpd.xpress_available and xpd.xpress.__version__ == '9.8.0',
325+
"Xpress 9.8 always runs global optimizer",
326+
)
323327
def test_nonconvexqp_locally_optimal(self):
324328
"""Test non-convex QP for which xpress_direct should find a locally
325329
optimal solution."""

0 commit comments

Comments
 (0)