Skip to content

Commit

Permalink
Merge pull request Pyomo#2200 from jsiirola/resolve-regressions
Browse files Browse the repository at this point in the history
Resolve regressions in `assertStructuredAlmostEqual` and `Initializer`
  • Loading branch information
blnicho authored Nov 17, 2021
2 parents 5e836be + 1cae497 commit bcc260f
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 13 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release_wheel_creation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
path: dist

manylinuxaarch64:
if: ${{ false }}
name: ${{ matrix.TARGET }}/wheel_creation
runs-on: ${{ matrix.os }}
strategy:
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Pyomo 6.2 17 Nov 2021
- Fix bugs with open NumericRanges (#2170, #2179)
- Fix bugs with References (#2158)
- Fix Initializer by treating pandas.Series as sequences (#2151)
- Fix Initializer support for ConfigList/ConfigDict (#2200)
- Add a DataFrameInitializer (#2150)
- Add a public API for retrieving variable bound expressions (#2172)
- Rework Var component to leverage Initializer (#2184)
Expand All @@ -48,6 +49,7 @@ Pyomo 6.2 17 Nov 2021
- Improve GAMS writer performance (#2191)
- Testing
- Resolve test failures when no solvers are available (#2146)
- Resolve NumericValue support in assertStructuredAlmostEqual (#2200)
- Fix typo in booktest skip declaration (#2186)
- DAE Updates
- Utility function to slice individual components in flatten module (#2141)
Expand Down
14 changes: 8 additions & 6 deletions pyomo/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from textwrap import wrap
import types

from pyomo.common.collections import Sequence, Mapping
from pyomo.common.deprecation import deprecated, relocated_module_attribute
from pyomo.common.fileutils import import_file
from pyomo.common.modeling import NoArgumentGiven
Expand Down Expand Up @@ -1150,18 +1151,19 @@ def __getstate__(self):
# state[i] = getattr(self,i)
# return state
#
# Hoewever, in this case, the (nominal) parent class is
# However, in this case, the (nominal) parent class is
# 'object', and object does not implement __getstate__. Since
# super() doesn't actually return a class, we are going to check
# the *derived class*'s MRO and see if this is the second to
# last class (the last is always 'object'). If it is, then we
# can allocate the state dictionary. If it is not, then we call
# the super-class's __getstate__ (since that class is NOT
# 'object').
if self.__class__.__mro__[-2] is ConfigBase:
state = {}
_base = super()
if hasattr(_base, '__getstate__'):
state = _base.__getstate__()
else:
state = super(ConfigBase, self).__getstate__()
state = {}
state.update((key, getattr(self, key)) for key in ConfigBase.__slots__)
state['_domain'] = _picklable(state['_domain'], self)
state['_parent'] = None
Expand Down Expand Up @@ -1721,7 +1723,7 @@ def __exit__(self, t, v, tb):
self.release_lock()


class ConfigList(ConfigBase):
class ConfigList(ConfigBase, Sequence):
"""Store and manipulate a list of configuration values.
Parameters
Expand Down Expand Up @@ -1896,7 +1898,7 @@ def _data_collector(self, level, prefix, visibility=None, docMode=False):
yield v


class ConfigDict(ConfigBase):
class ConfigDict(ConfigBase, Mapping):
"""Store and manipulate a dictionary of configuration values.
Parameters
Expand Down
15 changes: 15 additions & 0 deletions pyomo/common/tests/test_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import pyomo.common.unittest as unittest
from pyomo.common.log import LoggingIntercept
from pyomo.environ import ConcreteModel, Var, Param

@unittest.timeout(10)
def short_sleep():
Expand Down Expand Up @@ -150,6 +151,20 @@ def test_assertStructuredAlmostEqual_nested(self):
'3 !~= 2.999'):
self.assertStructuredAlmostEqual(a, b)

def test_assertStructuredAlmostEqual_numericvalue(self):
m = ConcreteModel()
m.v = Var(initialize=2.)
m.p = Param(initialize=2.)
a = {1.1: [1,m.p,3], 'a': 'hi', 3: {1:2, 3:4}}
b = {1.1: [1,m.v,3], 'a': 'hi', 3: {1:2, 3:4}}
self.assertStructuredAlmostEqual(a, b)
m.v.set_value(m.v.value - 1.999e-7)
self.assertStructuredAlmostEqual(a, b)
m.v.set_value(m.v.value - 1.999e-7)
with self.assertRaisesRegex(self.failureException,
'2.0 !~= 1.999'):
self.assertStructuredAlmostEqual(a, b)

def test_timeout_fcn_call(self):
self.assertEqual(short_sleep(), 42)
with self.assertRaisesRegex(
Expand Down
37 changes: 30 additions & 7 deletions pyomo/common/unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,27 @@
def _defaultFormatter(msg, default):
return msg or default

def _floatOrCall(val):
"""Cast the value to float, if that fails call it and then cast.
This is an "augmented" version of float() to better support
integration with Pyomo NumericValue objects: if the initial cast to
float fails by throwing a TypeError (as non-constant NumericValue
objects will), then it falls back on calling the object and
returning that value cast to float.
"""
try:
return float(val)
except TypeError:
return float(val())

def assertStructuredAlmostEqual(first, second,
places=None, msg=None, delta=None,
reltol=None, abstol=None,
allow_second_superset=False,
item_callback=float, exception=ValueError,
item_callback=_floatOrCall,
exception=ValueError,
formatter=_defaultFormatter):
"""Test that first and second are equal up to a tolerance
Expand Down Expand Up @@ -157,6 +173,7 @@ def _assertStructuredAlmostEqual(first, second,
"""Recursive implementation of assertStructuredAlmostEqual"""

args = (first, second)
f, s = args
if all(isinstance(_, Mapping) for _ in args):
if exact and len(first) != len(second):
raise exception(
Expand Down Expand Up @@ -226,11 +243,17 @@ def _assertStructuredAlmostEqual(first, second,
except:
pass

raise exception(
"%s !~= %s" % (
_unittest.case.safe_repr(first),
_unittest.case.safe_repr(second),
))
msg = "%s !~= %s" % (
_unittest.case.safe_repr(first),
_unittest.case.safe_repr(second),
)
if f is not first or s is not second:
msg = "%s !~= %s (%s)" % (
_unittest.case.safe_repr(f),
_unittest.case.safe_repr(s),
msg,
)
raise exception(msg)


def _category_to_tuple(_cat):
Expand Down Expand Up @@ -509,7 +532,7 @@ def assertStructuredAlmostEqual(self, first, second,
places=None, msg=None, delta=None,
reltol=None, abstol=None,
allow_second_superset=False,
item_callback=float):
item_callback=_floatOrCall):
assertStructuredAlmostEqual(
first=first,
second=second,
Expand Down
26 changes: 26 additions & 0 deletions pyomo/core/tests/unit/test_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import pickle

import pyomo.common.unittest as unittest
from pyomo.common.config import ConfigValue, ConfigList, ConfigDict
from pyomo.common.dependencies import pandas as pd, pandas_available

from pyomo.core.base.util import flatten_tuple
Expand Down Expand Up @@ -534,3 +535,28 @@ def rule(m, i):
with self.assertRaisesRegex(TypeError, 'type'):
d(None, 2)
self.assertEqual(d(None, 3), 100)

def test_config_integration(self):
c = ConfigList()
c.add(1)
c.add(3)
c.add(5)
a = Initializer(c)
self.assertIs(type(a), ItemInitializer)
self.assertTrue(a.contains_indices())
self.assertEqual(list(a.indices()), [0, 1, 2])
self.assertEqual(a(None, 0), 1)
self.assertEqual(a(None, 1), 3)
self.assertEqual(a(None, 2), 5)

c = ConfigDict()
c.declare('opt_1', ConfigValue(default=1))
c.declare('opt_3', ConfigValue(default=3))
c.declare('opt_5', ConfigValue(default=5))
a = Initializer(c)
self.assertIs(type(a), ItemInitializer)
self.assertTrue(a.contains_indices())
self.assertEqual(list(a.indices()), ['opt_1', 'opt_3', 'opt_5'])
self.assertEqual(a(None, 'opt_1'), 1)
self.assertEqual(a(None, 'opt_3'), 3)
self.assertEqual(a(None, 'opt_5'), 5)

0 comments on commit bcc260f

Please sign in to comment.