Skip to content

Commit 0821122

Browse files
authored
Merge branch 'main' into conic-geomean-fix
2 parents a41efb5 + 25d5910 commit 0821122

File tree

25 files changed

+1597
-195
lines changed

25 files changed

+1597
-195
lines changed

.jenkins.sh

+20-5
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,28 @@ if test -z "$MODE" -o "$MODE" == test; then
208208
coverage report -i || exit 1
209209
coverage xml -i || exit 1
210210
export OS=`uname`
211-
if test -n "$CODECOV_TOKEN"; then
212-
CODECOV_JOB_NAME=`echo ${JOB_NAME} | sed -r 's/^(.*autotest_)?Pyomo_([^\/]+).*/\2/'`.$BUILD_NUMBER.$python
211+
if test -z "$PYOMO_SOURCE_SHA"; then
212+
PYOMO_SOURCE_SHA=$GIT_COMMIT
213+
fi
214+
if test -n "$CODECOV_TOKEN" -a -n "$PYOMO_SOURCE_SHA"; then
215+
CODECOV_JOB_NAME=$(echo ${JOB_NAME} \
216+
| sed -r 's/^(.*autotest_)?Pyomo_([^\/]+).*/\2/').$BUILD_NUMBER.$python
213217
if test -z "$CODECOV_REPO_OWNER"; then
214-
CODECOV_REPO_OWNER="pyomo"
218+
if test -n "$PYOMO_SOURCE_REPO"; then
219+
CODECOV_REPO_OWNER=$(echo "$PYOMO_SOURCE_REPO" | cut -d '/' -f 4)
220+
elif test -n "$GIT_URL"; then
221+
CODECOV_REPO_OWNER=$(echo "$GIT_URL" | cut -d '/' -f 4)
222+
else
223+
CODECOV_REPO_OWNER=""
224+
fi
215225
fi
216-
if test -z "CODECOV_SOURCE_BRANCH"; then
217-
CODECOV_SOURCE_BRANCH="main"
226+
if test -z "$CODECOV_SOURCE_BRANCH"; then
227+
CODECOV_SOURCE_BRANCH=$(git branch -av --contains "$PYOMO_SOURCE_SHA" \
228+
| grep "${PYOMO_SOURCE_SHA:0:7}" | grep "/origin/" \
229+
| cut -d '/' -f 3 | cut -d' ' -f 1)
230+
if test -z "$CODECOV_SOURCE_BRANCH"; then
231+
CODECOV_SOURCE_BRANCH=main
232+
fi
218233
fi
219234
i=0
220235
while /bin/true; do

examples/pyomo/tutorials/set.dat

+3
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ set S[5] := 2 3;
1616

1717
set T[2] := 1 3;
1818
set T[5] := 2 3;
19+
20+
set X[2] := 1;
21+
set X[5] := 2 3;

examples/pyomo/tutorials/set.out

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
23 Set Declarations
1+
24 Set Declarations
22
A : Size=1, Index=None, Ordered=Insertion
33
Key : Dimen : Domain : Size : Members
44
None : 1 : Any : 3 : {1, 2, 3}
@@ -89,5 +89,9 @@
8989
2 : 1 : Any : 5 : {1, 3, 5, 7, 9}
9090
3 : 1 : Any : 5 : {1, 4, 7, 10, 13}
9191
4 : 1 : Any : 5 : {1, 5, 9, 13, 17}
92+
X : Size=2, Index=B, Ordered=Insertion
93+
Key : Dimen : Domain : Size : Members
94+
2 : 1 : S[2] : 1 : {1,}
95+
5 : 1 : S[5] : 2 : {2, 3}
9296

93-
23 Declarations: A B C D E F G H Hsub I J K K_2 L M N O P R S T U V
97+
24 Declarations: A B C D E F G H Hsub I J K K_2 L M N O P R S X T U V

examples/pyomo/tutorials/set.py

+7
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,13 @@ def P_init(model, i, j):
171171
#
172172
model.S = Set(model.B, within=model.A)
173173

174+
#
175+
# Validation of a set array can also be linked to another set array. If so, the
176+
# elements under each index must also be found under the corresponding index in
177+
# the validation set array:
178+
#
179+
model.X = Set(model.B, within=model.S)
180+
174181

175182
#
176183
# Validation of set arrays can also be performed with the _validate_ option.

pyomo/contrib/appsi/solvers/highs.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ def _remove_constraints(self, cons: List[ConstraintData]):
481481
indices_to_remove.append(con_ndx)
482482
self._mutable_helpers.pop(con, None)
483483
self._solver_model.deleteRows(
484-
len(indices_to_remove), np.array(indices_to_remove)
484+
len(indices_to_remove), np.sort(np.array(indices_to_remove))
485485
)
486486
con_ndx = 0
487487
new_con_map = dict()

pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py

+37
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,43 @@ def test_mutable_params_with_remove_vars(self):
8080
res = opt.solve(m)
8181
self.assertAlmostEqual(res.best_feasible_objective, -9)
8282

83+
def test_fix_and_unfix(self):
84+
# Tests issue https://github.com/Pyomo/pyomo/issues/3127
85+
86+
m = pe.ConcreteModel()
87+
m.x = pe.Var(domain=pe.Binary)
88+
m.y = pe.Var(domain=pe.Binary)
89+
m.fx = pe.Var(domain=pe.NonNegativeReals)
90+
m.fy = pe.Var(domain=pe.NonNegativeReals)
91+
m.c1 = pe.Constraint(expr=m.fx <= m.x)
92+
m.c2 = pe.Constraint(expr=m.fy <= m.y)
93+
m.c3 = pe.Constraint(expr=m.x + m.y <= 1)
94+
95+
m.obj = pe.Objective(expr=m.fx * 0.5 + m.fy * 0.4, sense=pe.maximize)
96+
97+
opt = Highs()
98+
99+
# solution 1 has m.x == 1 and m.y == 0
100+
r = opt.solve(m)
101+
self.assertAlmostEqual(m.fx.value, 1, places=5)
102+
self.assertAlmostEqual(m.fy.value, 0, places=5)
103+
self.assertAlmostEqual(r.best_feasible_objective, 0.5, places=5)
104+
105+
# solution 2 has m.x == 0 and m.y == 1
106+
m.y.fix(1)
107+
r = opt.solve(m)
108+
self.assertAlmostEqual(m.fx.value, 0, places=5)
109+
self.assertAlmostEqual(m.fy.value, 1, places=5)
110+
self.assertAlmostEqual(r.best_feasible_objective, 0.4, places=5)
111+
112+
# solution 3 should be equal solution 1
113+
m.y.unfix()
114+
m.x.fix(1)
115+
r = opt.solve(m)
116+
self.assertAlmostEqual(m.fx.value, 1, places=5)
117+
self.assertAlmostEqual(m.fy.value, 0, places=5)
118+
self.assertAlmostEqual(r.best_feasible_objective, 0.5, places=5)
119+
83120
def test_capture_highs_output(self):
84121
# tests issue #3003
85122
#

pyomo/contrib/pyros/tests/test_grcs.py

+72-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from pyomo.contrib.pyros.util import get_vars_from_component
4343
from pyomo.contrib.pyros.util import identify_objective_functions
4444
from pyomo.common.collections import Bunch
45+
from pyomo.repn.plugins import nl_writer as pyomo_nl_writer
4546
import time
4647
import math
4748
from pyomo.contrib.pyros.util import time_code
@@ -68,7 +69,7 @@
6869
from pyomo.common.dependencies import numpy as np, numpy_available
6970
from pyomo.common.dependencies import scipy as sp, scipy_available
7071
from pyomo.environ import maximize as pyo_max
71-
from pyomo.common.errors import ApplicationError
72+
from pyomo.common.errors import ApplicationError, InfeasibleConstraintException
7273
from pyomo.opt import (
7374
SolverResults,
7475
SolverStatus,
@@ -4616,6 +4617,76 @@ def test_discrete_separation_subsolver_error(self):
46164617
),
46174618
)
46184619

4620+
@unittest.skipUnless(ipopt_available, "IPOPT is not available.")
4621+
def test_pyros_nl_writer_tol(self):
4622+
"""
4623+
Test PyROS subsolver call routine behavior
4624+
with respect to the NL writer tolerance is as
4625+
expected.
4626+
"""
4627+
m = ConcreteModel()
4628+
m.q = Param(initialize=1, mutable=True)
4629+
m.x1 = Var(initialize=1, bounds=(0, 1))
4630+
m.x2 = Var(initialize=2, bounds=(0, m.q))
4631+
m.obj = Objective(expr=m.x1 + m.x2)
4632+
4633+
# fixed just inside the PyROS-specified NL writer tolerance.
4634+
m.x1.fix(m.x1.upper + 9.9e-5)
4635+
4636+
current_nl_writer_tol = pyomo_nl_writer.TOL
4637+
ipopt_solver = SolverFactory("ipopt")
4638+
pyros_solver = SolverFactory("pyros")
4639+
4640+
pyros_solver.solve(
4641+
model=m,
4642+
first_stage_variables=[m.x1],
4643+
second_stage_variables=[m.x2],
4644+
uncertain_params=[m.q],
4645+
uncertainty_set=BoxSet([[0, 1]]),
4646+
local_solver=ipopt_solver,
4647+
global_solver=ipopt_solver,
4648+
decision_rule_order=0,
4649+
solve_master_globally=False,
4650+
bypass_global_separation=True,
4651+
)
4652+
4653+
self.assertEqual(
4654+
pyomo_nl_writer.TOL,
4655+
current_nl_writer_tol,
4656+
msg="Pyomo NL writer tolerance not restored as expected.",
4657+
)
4658+
4659+
# fixed just outside the PyROS-specified NL writer tolerance.
4660+
# this should be exceptional.
4661+
m.x1.fix(m.x1.upper + 1.01e-4)
4662+
4663+
err_msg = (
4664+
"model contains a trivially infeasible variable.*x1"
4665+
".*fixed.*outside bounds"
4666+
)
4667+
with self.assertRaisesRegex(InfeasibleConstraintException, err_msg):
4668+
pyros_solver.solve(
4669+
model=m,
4670+
first_stage_variables=[m.x1],
4671+
second_stage_variables=[m.x2],
4672+
uncertain_params=[m.q],
4673+
uncertainty_set=BoxSet([[0, 1]]),
4674+
local_solver=ipopt_solver,
4675+
global_solver=ipopt_solver,
4676+
decision_rule_order=0,
4677+
solve_master_globally=False,
4678+
bypass_global_separation=True,
4679+
)
4680+
4681+
self.assertEqual(
4682+
pyomo_nl_writer.TOL,
4683+
current_nl_writer_tol,
4684+
msg=(
4685+
"Pyomo NL writer tolerance not restored as expected "
4686+
"after exceptional test."
4687+
),
4688+
)
4689+
46194690
@unittest.skipUnless(
46204691
baron_license_is_valid, "Global NLP solver is not available and licensed."
46214692
)

pyomo/contrib/pyros/util.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from pyomo.core.expr import value
3939
from pyomo.core.expr.numeric_expr import NPV_MaxExpression, NPV_MinExpression
4040
from pyomo.repn.standard_repn import generate_standard_repn
41+
from pyomo.repn.plugins import nl_writer as pyomo_nl_writer
4142
from pyomo.core.expr.visitor import (
4243
identify_variables,
4344
identify_mutable_parameters,
@@ -377,7 +378,14 @@ def revert_solver_max_time_adjustment(
377378
elif isinstance(solver, SolverFactory.get_class("baron")):
378379
options_key = "MaxTime"
379380
elif isinstance(solver, SolverFactory.get_class("ipopt")):
380-
options_key = "max_cpu_time"
381+
options_key = (
382+
# IPOPT 3.14.0+ added support for specifying
383+
# wall time limit explicitly; this is preferred
384+
# over CPU time limit
385+
"max_wall_time"
386+
if solver.version() >= (3, 14, 0, 0)
387+
else "max_cpu_time"
388+
)
381389
elif isinstance(solver, SolverFactory.get_class("scip")):
382390
options_key = "limits/time"
383391
else:
@@ -1809,6 +1817,16 @@ def call_solver(model, solver, config, timing_obj, timer_name, err_msg):
18091817
timing_obj.start_timer(timer_name)
18101818
tt_timer.tic(msg=None)
18111819

1820+
# tentative: reduce risk of InfeasibleConstraintException
1821+
# occurring due to discrepancies between Pyomo NL writer
1822+
# tolerance and (default) subordinate solver (e.g. IPOPT)
1823+
# feasibility tolerances.
1824+
# e.g., a Var fixed outside bounds beyond the Pyomo NL writer
1825+
# tolerance, but still within the default IPOPT feasibility
1826+
# tolerance
1827+
current_nl_writer_tol = pyomo_nl_writer.TOL
1828+
pyomo_nl_writer.TOL = 1e-4
1829+
18121830
try:
18131831
results = solver.solve(
18141832
model,
@@ -1827,6 +1845,8 @@ def call_solver(model, solver, config, timing_obj, timer_name, err_msg):
18271845
results.solver, TIC_TOC_SOLVE_TIME_ATTR, tt_timer.toc(msg=None, delta=True)
18281846
)
18291847
finally:
1848+
pyomo_nl_writer.TOL = current_nl_writer_tol
1849+
18301850
timing_obj.stop_timer(timer_name)
18311851
revert_solver_max_time_adjustment(
18321852
solver, orig_setting, custom_setting_present, config

pyomo/core/base/set.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1932,7 +1932,8 @@ class Set(IndexedComponent):
19321932
19331933
within : initialiser(set), optional
19341934
A set that defines the valid values that can be contained
1935-
in this set
1935+
in this set. If the latter is indexed, the former can be indexed or
1936+
non-indexed, in which case it applies to all indices.
19361937
domain : initializer(set), optional
19371938
A set that defines the valid values that can be contained
19381939
in this set
@@ -2218,7 +2219,7 @@ def _getitem_when_not_present(self, index):
22182219

22192220
domain = self._init_domain(_block, index, self)
22202221
if domain is not None:
2221-
domain.construct()
2222+
domain.parent_component().construct()
22222223
if _d is UnknownSetDimen and domain is not None and domain.dimen is not None:
22232224
_d = domain.dimen
22242225

pyomo/core/expr/compare.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,14 @@ def assertExpressionsEqual(test, a, b, include_named_exprs=True, places=None):
230230
test.assertEqual(len(prefix_a), len(prefix_b))
231231
for _a, _b in zip(prefix_a, prefix_b):
232232
test.assertIs(_a.__class__, _b.__class__)
233-
if places is None:
234-
test.assertEqual(_a, _b)
233+
# If _a is nan, check _b is nan
234+
if _a != _a:
235+
test.assertTrue(_b != _b)
235236
else:
236-
test.assertAlmostEqual(_a, _b, places=places)
237+
if places is None:
238+
test.assertEqual(_a, _b)
239+
else:
240+
test.assertAlmostEqual(_a, _b, places=places)
237241
except (PyomoException, AssertionError):
238242
test.fail(
239243
f"Expressions not equal:\n\t"
@@ -292,10 +296,13 @@ def assertExpressionsStructurallyEqual(
292296
for _a, _b in zip(prefix_a, prefix_b):
293297
if _a.__class__ not in native_types and _b.__class__ not in native_types:
294298
test.assertIs(_a.__class__, _b.__class__)
295-
if places is None:
296-
test.assertEqual(_a, _b)
299+
if _a != _a:
300+
test.assertTrue(_b != _b)
297301
else:
298-
test.assertAlmostEqual(_a, _b, places=places)
302+
if places is None:
303+
test.assertEqual(_a, _b)
304+
else:
305+
test.assertAlmostEqual(_a, _b, places=places)
299306
except (PyomoException, AssertionError):
300307
test.fail(
301308
f"Expressions not structurally equal:\n\t"

0 commit comments

Comments
 (0)