Skip to content

Commit

Permalink
Merge branch 'main' into lp-dual
Browse files Browse the repository at this point in the history
  • Loading branch information
emma58 authored Nov 8, 2024
2 parents 317802c + 364e7f1 commit 52e7207
Show file tree
Hide file tree
Showing 38 changed files with 11,536 additions and 8,725 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/test_pr_and_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@ jobs:
TARGET: linux
PYENV: pip

- os: ubuntu-latest
python: 3.12
other: /numpy2
slim: 1
skip_doctest: 1
TARGET: linux
PYENV: pip
PACKAGES: "gurobipy dill numpy>2.0 scipy networkx"

- os: ubuntu-latest
python: 3.9
other: /pyutilib
Expand Down
3 changes: 2 additions & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ build:
sphinx:
configuration: doc/OnlineDocs/conf.py

formats: all
formats:
- pdf

# Set the version of Python and requirements required to build the docs
python:
Expand Down
229 changes: 143 additions & 86 deletions doc/OnlineDocs/explanation/solvers/pyros.rst

Large diffs are not rendered by default.

60 changes: 48 additions & 12 deletions pyomo/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1693,22 +1693,53 @@ class UninitializedMixin(object):
@property
def _data(self):
#
# This is a possibly dangerous construct: falling back on
# calling the _default can mask a real problem in the default
# type/value.
# We assume that _default is usually a concrete value. But, we
# also accept a types (classes) and initialization functions as
# defaults, in which case we will construct an instance of that
# class and use that as the default. If they both raise
# exceptions, we will let the original exception propagate up.
#
try:
self._setter(self._default)
except:
if hasattr(self._default, '__call__'):
self._setter(self._default())
else:
raise
_default_val = self._default()
try:
self._setter(_default_val)
return self._data
except:
pass
raise
return self._data

@_data.setter
def _data(self, value):
self.__class__ = self.__class__.__mro__[2]
_mro = self.__class__.__mro__
# There is an edge case in multithreaded environments where this
# function could actually be called more than once for a single
# ConfigValue. We want to make sure that only the first of the
# calls actually updates the __class__ (the others will
# recursively lookup the _data attribute and the second lookup
# will resolve to normal attribute assignment).
#
# We first encountered this issue for Config objects stores as
# class attributes (i.e., the default Config for something like
# a solver or writer) and multiple threads were simultaneously
# creating instances of the class (each of which was resolving
# the default values for the class attribute).
#
# Note that this explicitly assumes that the uninitialized
# Config object was defined as:
#
# class UninitializedConfig(UninitializedMixin, Config)
#
# and that the resulting class was never inherited from. If
# this assumption is ever violated, attempts to use the
# uninitialized config object will generate infinite recursion
# (and that is OK, as the developer should immediately be
# informed of their error)
if _mro[1] is UninitializedMixin:
self.__class__ = _mro[2]
self._data = value


Expand Down Expand Up @@ -2680,14 +2711,19 @@ def __len__(self):
def __iter__(self):
return map(attrgetter('_name'), self._data.values())

def __getattr__(self, name):
def __getattr__(self, attr):
# Note: __getattr__ is only called after all "usual" attribute
# lookup methods have failed. So, if we get here, we already
# know that key is not a __slot__ or a method, etc...
_name = name.replace(' ', '_')
if _name not in self._data:
raise AttributeError("Unknown attribute '%s'" % name)
return ConfigDict.__getitem__(self, _name)
_attr = attr.replace(' ', '_')
# Note: we test for "_data" because finding attributes on a
# partially constructed ConfigDict (before the _data attribute
# was declared) can lead to infinite recursion.
if _attr == "_data" or _attr not in self._data:
raise AttributeError(
f"'{type(self).__name__}' object has no attribute '{attr}'"
)
return ConfigDict.__getitem__(self, _attr)

def __setattr__(self, name, value):
if name in ConfigDict._reserved_words:
Expand Down
17 changes: 15 additions & 2 deletions pyomo/common/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1863,7 +1863,18 @@ def test_default_function(self):
c.value()

c = ConfigValue('a', domain=int)
with self.assertRaisesRegex(ValueError, 'invalid value for configuration'):
with self.assertRaisesRegex(
ValueError, '(?s)invalid value for configuration.*casting a'
):
c.value()

# Test that if both the default and the result from calling the
# default raise exceptions, the propagated exception is from
# castig the original default:
c = ConfigValue(default=lambda: 'a', domain=int)
with self.assertRaisesRegex(
ValueError, "(?s)invalid value for configuration.*lambda"
):
c.value()

def test_set_default(self):
Expand Down Expand Up @@ -2736,7 +2747,9 @@ def test_getattr_setattr(self):
):
config.baz = 10

with self.assertRaisesRegex(AttributeError, "Unknown attribute 'baz'"):
with self.assertRaisesRegex(
AttributeError, "'ConfigDict' object has no attribute 'baz'"
):
a = config.baz

def test_nonString_keys(self):
Expand Down
15 changes: 15 additions & 0 deletions pyomo/common/tests/test_tee.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

import gc
import os
import time
import sys
Expand All @@ -23,6 +24,20 @@


class TestTeeStream(unittest.TestCase):
def setUp(self):
self.reenable_gc = gc.isenabled()
gc.disable()
# Set a short switch interval so that the threading tests behave
# as expected
self.switchinterval = sys.getswitchinterval()
sys.setswitchinterval(tee._poll_interval / 100)

def tearDown(self):
sys.setswitchinterval(self.switchinterval)
if self.reenable_gc:
gc.enable()
gc.collect()

def test_stdout(self):
a = StringIO()
b = StringIO()
Expand Down
3 changes: 2 additions & 1 deletion pyomo/common/unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,10 +876,11 @@ def filter_fcn(self, line):
# next 6 patterns ignore entries in pstats reports:
'function calls',
'List reduced',
'.py:',
'.py:', # timing/profiling output
' {built-in method',
' {method',
' {pyomo.core.expr.numvalue.as_numeric}',
' {gurobipy.',
):
if field in line:
return True
Expand Down
2 changes: 1 addition & 1 deletion pyomo/contrib/iis/mis.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ def _constraint_generator():
except:
results = None

if pyo.check_optimal_termination(results):
if (results is not None) and pyo.check_optimal_termination(results):
msg += "Could not determine Minimal Intractable System\n"
else:
deletion_filter = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
from pyomo.core.expr.visitor import identify_variables
import pyomo.environ as pyo

from pyomo.common.dependencies import networkx_available as nx_available
from pyomo.contrib.pynumero.dependencies import (
numpy as np,
numpy_available,
scipy,
scipy_available,
)

Expand Down Expand Up @@ -151,6 +151,7 @@ def flow_out_eqn(m, t):


class TestExternalGreyBoxBlock(unittest.TestCase):
@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_construct_scalar(self):
m = pyo.ConcreteModel()
m.ex_block = ExternalGreyBoxBlock(concrete=True)
Expand All @@ -171,6 +172,7 @@ def test_construct_scalar(self):
self.assertEqual(len(block.outputs), 0)
self.assertEqual(len(block._equality_constraint_names), 2)

@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_construct_indexed(self):
block = ExternalGreyBoxBlock([0, 1, 2], concrete=True)
self.assertIs(type(block), IndexedExternalGreyBoxBlock)
Expand All @@ -192,6 +194,7 @@ def test_construct_indexed(self):
self.assertEqual(len(b._equality_constraint_names), 2)

@unittest.skipUnless(cyipopt_available, "cyipopt is not available")
@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_solve_square(self):
m = pyo.ConcreteModel()
m.ex_block = ExternalGreyBoxBlock(concrete=True)
Expand Down Expand Up @@ -234,6 +237,7 @@ def test_solve_square(self):
self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8)

@unittest.skipUnless(cyipopt_available, "cyipopt is not available")
@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_optimize(self):
m = pyo.ConcreteModel()
m.ex_block = ExternalGreyBoxBlock(concrete=True)
Expand Down Expand Up @@ -292,6 +296,7 @@ def test_optimize(self):
self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8)

@unittest.skipUnless(cyipopt_available, "cyipopt is not available")
@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_optimize_with_cyipopt_for_inner_problem(self):
# Use CyIpopt, rather than the default SciPy solvers,
# for the inner problem
Expand Down Expand Up @@ -427,6 +432,7 @@ def test_optimize_no_decomposition(self):
self.assertAlmostEqual(m_ex.x.value, x.value, delta=1e-8)
self.assertAlmostEqual(m_ex.y.value, y.value, delta=1e-8)

@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_construct_dynamic(self):
m = make_dynamic_model()
time = m.time
Expand Down Expand Up @@ -504,6 +510,7 @@ def test_construct_dynamic(self):
)

@unittest.skipUnless(cyipopt_available, "cyipopt is not available")
@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_solve_square_dynamic(self):
# Create the "external model"
m = make_dynamic_model()
Expand Down Expand Up @@ -571,6 +578,7 @@ def linking_constraint_rule(m, i, t):
self.assertStructuredAlmostEqual(values, target_values, delta=1e-5)

@unittest.skipUnless(cyipopt_available, "cyipopt is not available")
@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_optimize_dynamic(self):
# Create the "external model"
m = make_dynamic_model()
Expand Down Expand Up @@ -653,6 +661,7 @@ def linking_constraint_rule(m, i, t):
self.assertStructuredAlmostEqual(values, target_values, delta=1e-5)

@unittest.skipUnless(cyipopt_available, "cyipopt is not available")
@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
def test_optimize_dynamic_references(self):
"""
When when pre-existing variables are attached to the EGBB
Expand Down Expand Up @@ -717,7 +726,8 @@ def test_optimize_dynamic_references(self):
self.assertStructuredAlmostEqual(values, target_values, delta=1e-5)


class TestPyomoNLPWithGreyBoxBLocks(unittest.TestCase):
@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
class TestPyomoNLPWithGreyBoxBlocks(unittest.TestCase):
def test_set_and_evaluate(self):
m = pyo.ConcreteModel()
m.ex_block = ExternalGreyBoxBlock(concrete=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import pyomo.common.unittest as unittest
import pyomo.environ as pyo

from pyomo.common.dependencies import networkx_available as nx_available
from pyomo.contrib.pynumero.dependencies import (
numpy as np,
numpy_available,
Expand Down Expand Up @@ -513,6 +514,7 @@ def test_explicit_zeros(self):
np.testing.assert_allclose(hess.data, data, rtol=1e-8)


@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
class TestExternalPyomoModel(unittest.TestCase):
def test_evaluate_SimpleModel1(self):
model = SimpleModel1()
Expand Down Expand Up @@ -838,6 +840,7 @@ def test_evaluate_hessian_lagrangian_SimpleModel2x2_1(self):
np.testing.assert_allclose(hess_lag, expected_hess_lag, rtol=1e-8)


@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
class TestUpdatedHessianCalculationMethods(unittest.TestCase):
"""
These tests exercise the methods for fast Hessian-of-Lagrangian
Expand Down Expand Up @@ -1021,6 +1024,7 @@ def test_evaluate_hessian_equality_constraints_order(self):
)


@unittest.skipUnless(nx_available, "SCCImplicitFunctionSolver requires networkx")
class TestScaling(unittest.TestCase):
def con_3_body(self, x, y, u, v):
return 1e5 * x**2 + 1e4 * y**2 + 1e1 * u**2 + 1e0 * v**2
Expand Down
Loading

0 comments on commit 52e7207

Please sign in to comment.