Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/pyomo/pyomo into cyipopt-ex…
Browse files Browse the repository at this point in the history
…tend-callback
  • Loading branch information
Robbybp committed Aug 13, 2024
2 parents 58b5df8 + 404fd6d commit 671d8c6
Show file tree
Hide file tree
Showing 49 changed files with 1,236 additions and 851 deletions.
6 changes: 3 additions & 3 deletions doc/OnlineDocs/contributed_packages/gdpopt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ An example that includes the modeling approach may be found below.
Variables:
x : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : -1.2 : 0.0 : 2 : False : False : Reals
None : -1.2 : 0 : 2 : False : False : Reals
y : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : -10 : 1.0 : 10 : False : False : Reals
None : -10 : 1 : 10 : False : False : Reals
<BLANKLINE>
Objectives:
objective : Size=1, Index=None, Active=True
Expand All @@ -106,7 +106,7 @@ An example that includes the modeling approach may be found below.
Constraints:
c : Size=1
Key : Lower : Body : Upper
None : 1.0 : 1.0 : 1.0
None : 1.0 : 1 : 1.0

.. note::

Expand Down
9 changes: 8 additions & 1 deletion pyomo/common/fileutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,17 @@ def find_dir(
)


_exeExt = {'linux': None, 'windows': '.exe', 'cygwin': '.exe', 'darwin': None}
_exeExt = {
'linux': None,
'freebsd': None,
'windows': '.exe',
'cygwin': '.exe',
'darwin': None,
}

_libExt = {
'linux': ('.so', '.so.*'),
'freebsd': ('.so', '.so.*'),
'windows': ('.dll', '.pyd'),
'cygwin': ('.dll', '.so', '.so.*'),
'darwin': ('.dylib', '.so', '.so.*'),
Expand Down
7 changes: 4 additions & 3 deletions pyomo/common/tests/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import pyomo.common.envvar as envvar

from pyomo.common import DeveloperError
from pyomo.common.fileutils import this_file
from pyomo.common.fileutils import this_file, Executable
from pyomo.common.download import FileDownloader, distro_available
from pyomo.common.log import LoggingIntercept
from pyomo.common.tee import capture_output
Expand Down Expand Up @@ -173,7 +173,8 @@ def test_get_os_version(self):
self.assertTrue(v.replace('.', '').startswith(dist_ver))

if (
subprocess.run(
Executable('lsb_release').available()
and subprocess.run(
['lsb_release'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
Expand Down Expand Up @@ -206,7 +207,7 @@ def test_get_os_version(self):
self.assertEqual(_os, 'win')
self.assertEqual(_norm, _os + ''.join(_ver.split('.')[:2]))
else:
self.assertEqual(ans, '')
self.assertEqual(_os, '')

self.assertEqual((_os, _ver), FileDownloader._os_version)
# Exercise the fetch from CACHE
Expand Down
2 changes: 1 addition & 1 deletion pyomo/common/unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ def filter_file_contents(self, lines, abstol=None):

return filtered

def compare_baseline(self, test_output, baseline, abstol=1e-6, reltol=None):
def compare_baseline(self, test_output, baseline, abstol=1e-6, reltol=1e-8):
# Filter files independently and then compare filtered contents
out_filtered = self.filter_file_contents(
test_output.strip().split('\n'), abstol
Expand Down
35 changes: 4 additions & 31 deletions pyomo/contrib/appsi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ def add_constraints(self, cons: List[ConstraintData]):
raise ValueError(
'constraint {name} has already been added'.format(name=con.name)
)
self._active_constraints[con] = (con.lower, con.body, con.upper)
self._active_constraints[con] = con.expr
if self.use_extensions and cmodel_available:
tmp = cmodel.prep_for_repn(con.body, self._expr_types)
else:
Expand Down Expand Up @@ -1363,40 +1363,13 @@ def update(self, timer: HierarchicalTimer = None):
cons_to_remove_and_add = dict()
need_to_set_objective = False
if config.update_constraints:
cons_to_update = list()
sos_to_update = list()
for c in current_cons_dict.keys():
if c not in new_cons_set:
cons_to_update.append(c)
if c not in new_cons_set and c.expr is not self._active_constraints[c]:
cons_to_remove_and_add[c] = None
sos_to_update = []
for c in current_sos_dict.keys():
if c not in new_sos_set:
sos_to_update.append(c)
for c in cons_to_update:
lower, body, upper = self._active_constraints[c]
new_lower, new_body, new_upper = c.lower, c.body, c.upper
if new_body is not body:
cons_to_remove_and_add[c] = None
continue
if new_lower is not lower:
if (
type(new_lower) is NumericConstant
and type(lower) is NumericConstant
and new_lower.value == lower.value
):
pass
else:
cons_to_remove_and_add[c] = None
continue
if new_upper is not upper:
if (
type(new_upper) is NumericConstant
and type(upper) is NumericConstant
and new_upper.value == upper.value
):
pass
else:
cons_to_remove_and_add[c] = None
continue
self.remove_sos_constraints(sos_to_update)
self.add_sos_constraints(sos_to_update)
timer.stop('cons')
Expand Down
2 changes: 1 addition & 1 deletion pyomo/contrib/appsi/cmodel/src/fbbt_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ void process_fbbt_constraints(FBBTModel *model, PyomoExprTypes &expr_types,
py::handle con_body;

for (py::handle c : cons) {
lower_body_upper = active_constraints[c];
lower_body_upper = c.attr("to_bounded_expression")();
con_lb = lower_body_upper[0];
con_body = lower_body_upper[1];
con_ub = lower_body_upper[2];
Expand Down
2 changes: 1 addition & 1 deletion pyomo/contrib/appsi/cmodel/src/lp_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ void process_lp_constraints(py::list cons, py::object writer) {
py::object nonlinear_expr;
PyomoExprTypes expr_types = PyomoExprTypes();
for (py::handle c : cons) {
lower_body_upper = active_constraints[c];
lower_body_upper = c.attr("to_bounded_expression")();
cname = getSymbol(c, labeler);
repn = generate_standard_repn(
lower_body_upper[1], "compute_values"_a = false, "quadratic"_a = true);
Expand Down
2 changes: 1 addition & 1 deletion pyomo/contrib/appsi/cmodel/src/nl_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ void process_nl_constraints(NLWriter *nl_writer, PyomoExprTypes &expr_types,
py::handle repn_nonlinear_expr;

for (py::handle c : cons) {
lower_body_upper = active_constraints[c];
lower_body_upper = c.attr("to_bounded_expression")();
repn = generate_standard_repn(
lower_body_upper[1], "compute_values"_a = false, "quadratic"_a = false);
_const = appsi_expr_from_pyomo_expr(repn.attr("constant"), var_map,
Expand Down
21 changes: 21 additions & 0 deletions pyomo/contrib/appsi/solvers/ipopt.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,3 +567,24 @@ def get_reduced_costs(
return ComponentMap((k, v) for k, v in self._reduced_costs.items())
else:
return ComponentMap((v, self._reduced_costs[v]) for v in vars_to_load)

def has_linear_solver(self, linear_solver):
import pyomo.core as AML
from pyomo.common.tee import capture_output

m = AML.ConcreteModel()
m.x = AML.Var()
m.o = AML.Objective(expr=(m.x - 2) ** 2)
with capture_output() as OUT:
solver = self.__class__()
solver.config.stream_solver = True
solver.config.load_solution = False
solver.ipopt_options['linear_solver'] = linear_solver
try:
solver.solve(m)
except FileNotFoundError:
# The APPSI interface always tries to open the SOL file,
# and will generate a FileNotFoundError if ipopt didn't
# generate one
return False
return 'running with linear solver' in OUT.getvalue()
42 changes: 42 additions & 0 deletions pyomo/contrib/appsi/tests/test_ipopt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# ___________________________________________________________________________
#
# Pyomo: Python Optimization Modeling Objects
# Copyright (c) 2008-2024
# 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.
# ___________________________________________________________________________

from pyomo.common import unittest
from pyomo.contrib.appsi.solvers import ipopt


ipopt_available = ipopt.Ipopt().available()


@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available")
class TestIpoptInterface(unittest.TestCase):
def test_has_linear_solver(self):
opt = ipopt.Ipopt()
self.assertTrue(
any(
map(
opt.has_linear_solver,
[
'mumps',
'ma27',
'ma57',
'ma77',
'ma86',
'ma97',
'pardiso',
'pardisomkl',
'spral',
'wsmp',
],
)
)
)
self.assertFalse(opt.has_linear_solver('bogus_linear_solver'))
2 changes: 1 addition & 1 deletion pyomo/contrib/community_detection/community_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def generate_model_graph(
# Create a list of the variable numbers that occur in the given constraint equation
numbered_variables_in_constraint_equation = [
component_number_map[constraint_variable]
for constraint_variable in identify_variables(model_constraint.body)
for constraint_variable in identify_variables(model_constraint.expr)
]

# Update constraint_variable_map
Expand Down
37 changes: 35 additions & 2 deletions pyomo/contrib/cp/tests/test_logical_to_disjunctive.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,41 @@ def test_equivalence(self):

assertExpressionsEqual(self, m.cons[10].expr, m.z[5] >= 1)

def test_equivalent_to_True(self):
m = self.make_model()
e = m.a.equivalent_to(True)

visitor = LogicalToDisjunctiveVisitor()
m.cons = visitor.constraints
m.z = visitor.z_vars

visitor.walk_expression(e)

self.assertIs(m.a.get_associated_binary(), m.z[1])
self.assertEqual(len(m.z), 4)
self.assertEqual(len(m.cons), 10)

# z[2] == !a v True
assertExpressionsEqual(
self, m.cons[1].expr, (1 - m.z[2]) + (1 - m.z[1]) + 1 >= 1
)
assertExpressionsEqual(self, m.cons[2].expr, 1 - (1 - m.z[1]) + m.z[2] >= 1)
assertExpressionsEqual(self, m.cons[3].expr, m.z[2] + (1 - 1) >= 1)

# z[3] == a v ! c
assertExpressionsEqual(self, m.cons[4].expr, (1 - m.z[3]) + m.z[1] >= 1)
assertExpressionsEqual(self, m.cons[5].expr, m.z[3] + (1 - m.z[1]) >= 1)
assertExpressionsEqual(self, m.cons[6].expr, m.z[3] + 1 >= 1)

# z[4] == z[2] ^ z[3]
assertExpressionsEqual(self, m.cons[7].expr, m.z[4] <= m.z[2])
assertExpressionsEqual(self, m.cons[8].expr, m.z[4] <= m.z[3])
assertExpressionsEqual(
self, m.cons[9].expr, 1 - m.z[4] <= 2 - (m.z[2] + m.z[3])
)

assertExpressionsEqual(self, m.cons[10].expr, m.z[4] >= 1)

def test_xor(self):
m = self.make_model()
e = m.a.xor(m.b)
Expand Down Expand Up @@ -263,8 +298,6 @@ def test_at_most(self):
# z3 = a ^ b
assertExpressionsEqual(self, m.cons[1].expr, m.z[3] <= a)
assertExpressionsEqual(self, m.cons[2].expr, m.z[3] <= b)
m.cons.pprint()
print(m.cons[3].expr)
assertExpressionsEqual(self, m.cons[3].expr, 1 - m.z[3] <= 2 - sum([a, b]))

# atmost in disjunctive form
Expand Down
15 changes: 7 additions & 8 deletions pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,7 @@ def _dispatch_var(visitor, node):


def _dispatch_param(visitor, node):
if int(value(node)) == value(node):
return False, node
else:
raise ValueError(
"Found non-integer valued Param '%s' in a logical "
"expression. This cannot be written to a disjunctive "
"form." % node.name
)
return False, node


def _dispatch_expression(visitor, node):
Expand Down Expand Up @@ -244,6 +237,12 @@ def initializeWalker(self, expr):

def beforeChild(self, node, child, child_idx):
if child.__class__ in EXPR.native_types:
if child.__class__ is bool:
# If we encounter a bool, we are going to need to treat it as
# binary explicitly because we are finally pedantic enough in the
# expression system to not allow some of the mixing we will need
# (like summing a LinearExpression with a bool)
return False, int(child)
return False, child

if child.is_numeric_type():
Expand Down
9 changes: 6 additions & 3 deletions pyomo/contrib/doe/doe.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def __init__(
A Python ``function`` that returns a Concrete Pyomo model, similar to the interface for ``parmest``
solver:
A ``solver`` object that User specified, default=None.
If not specified, default solver is IPOPT MA57.
If not specified, default solver is IPOPT (with MA57, if available).
prior_FIM:
A 2D numpy array containing Fisher information matrix (FIM) for prior experiments.
The default None means there is no prior information.
Expand Down Expand Up @@ -995,7 +995,7 @@ def run_grid_search(
)
count += 1
failed_count += 1
self.logger.warning("failed count:", failed_count)
self.logger.warning("failed count: %s", failed_count)
result_combine[tuple(design_set_iter)] = None

# For user's access
Expand Down Expand Up @@ -1387,7 +1387,10 @@ def _fix_design(self, m, design_val, fix_opt=True, optimize_option=None):
def _get_default_ipopt_solver(self):
"""Default solver"""
solver = SolverFactory("ipopt")
solver.options["linear_solver"] = "ma57"
for linear_solver in ('ma57', 'ma27', 'ma97'):
if solver.has_linear_solver(linear_solver):
solver.options["linear_solver"] = linear_solver
break
solver.options["halt_on_ampl_error"] = "yes"
solver.options["max_iter"] = 3000
return solver
Expand Down
2 changes: 2 additions & 0 deletions pyomo/contrib/doe/tests/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from pyomo.opt import SolverFactory

ipopt_available = SolverFactory("ipopt").available()
k_aug_available = SolverFactory("k_aug").available(exception_flag=False)


class TestReactorExamples(unittest.TestCase):
Expand All @@ -57,6 +58,7 @@ def test_reactor_optimize_doe(self):

reactor_optimize_doe.main()

@unittest.skipIf(not k_aug_available, "The 'k_aug' command is not available")
@unittest.skipIf(not ipopt_available, "The 'ipopt' command is not available")
@unittest.skipIf(not pandas_available, "pandas is not available")
@unittest.skipIf(not numpy_available, "Numpy is not available")
Expand Down
4 changes: 4 additions & 0 deletions pyomo/contrib/doe/tests/test_fim_doe.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
VariablesWithIndices,
)
from pyomo.contrib.doe.examples.reactor_kinetics import create_model, disc_for_measure
from pyomo.environ import SolverFactory

ipopt_available = SolverFactory("ipopt").available()


class TestMeasurementError(unittest.TestCase):
Expand Down Expand Up @@ -196,6 +199,7 @@ def test(self):


@unittest.skipIf(not numpy_available, "Numpy is not available")
@unittest.skipIf(not ipopt_available, "ipopt is not available")
class TestPriorFIMError(unittest.TestCase):
def test(self):
# Control time set [h]
Expand Down
2 changes: 2 additions & 0 deletions pyomo/contrib/doe/tests/test_reactor_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from pyomo.opt import SolverFactory

ipopt_available = SolverFactory("ipopt").available()
k_aug_available = SolverFactory("k_aug").available(exception_flag=False)


class Test_Reaction_Kinetics_Example(unittest.TestCase):
Expand Down Expand Up @@ -133,6 +134,7 @@ def test_kinetics_example_sequential_finite_then_optimize(self):
# self.assertAlmostEqual(value(optimize_result.model.T[0.5]), 300, places=2)
self.assertAlmostEqual(np.log10(optimize_result.trace), 3.340, places=2)

@unittest.skipIf(not k_aug_available, "The 'k_aug' solver is not available")
@unittest.skipIf(not ipopt_available, "The 'ipopt' solver is not available")
@unittest.skipIf(not numpy_available, "Numpy is not available")
@unittest.skipIf(not pandas_available, "Pandas is not available")
Expand Down
Loading

0 comments on commit 671d8c6

Please sign in to comment.