Skip to content

Commit

Permalink
merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbynum committed Mar 6, 2024
2 parents 96e2b03 + e72ca14 commit c1d0324
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 69 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/test_branches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
TARGET: linux
PYENV: pip

- os: macos-latest
- os: macos-13
python: '3.10'
TARGET: osx
PYENV: pip
Expand All @@ -86,7 +86,7 @@ jobs:
PACKAGES: pytest-qt

- os: ubuntu-latest
python: 3.9
python: '3.10'
other: /mpi
mpi: 3
skip_doctest: 1
Expand Down Expand Up @@ -361,12 +361,13 @@ jobs:
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG"
fi
done
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES metis"
PYPI_DEPENDENCIES="$PYPI_DEPENDENCIES metis"
echo ""
echo "*** Install Pyomo dependencies ***"
# Note: this will fail the build if any installation fails (or
# possibly if it outputs messages to stderr)
conda install --update-deps -q -y $CONDA_DEPENDENCIES
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES metis"
PYPI_DEPENDENCIES="$PYPI_DEPENDENCIES metis"
conda install --update-deps -y $CONDA_DEPENDENCIES
if test -z "${{matrix.slim}}"; then
PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g')
echo "Installing for $PYVER"
Expand Down Expand Up @@ -674,7 +675,7 @@ jobs:
$PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \
parse_data_commands; parse_data_commands(data='')"
# Note: if we are testing with openmpi, add '--oversubscribe'
mpirun -np ${{matrix.mpi}} pytest -v \
mpirun -np ${{matrix.mpi}} -oversubscribe pytest -v \
--junit-xml=TEST-pyomo-mpi.xml \
-m "mpi" -W ignore::Warning \
pyomo `pwd`/pyomo-model-libraries
Expand Down Expand Up @@ -751,12 +752,12 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-13, windows-latest]

include:
- os: ubuntu-latest
TARGET: linux
- os: macos-latest
- os: macos-13
TARGET: osx
- os: windows-latest
TARGET: win
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/test_pr_and_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-13, windows-latest]
python: [ 3.8, 3.9, '3.10', '3.11', '3.12' ]
other: [""]
category: [""]
Expand All @@ -69,7 +69,7 @@ jobs:
TARGET: linux
PYENV: pip

- os: macos-latest
- os: macos-13
TARGET: osx
PYENV: pip

Expand All @@ -87,7 +87,7 @@ jobs:
PACKAGES: pytest-qt

- os: ubuntu-latest
python: 3.9
python: '3.10'
other: /mpi
mpi: 3
skip_doctest: 1
Expand Down Expand Up @@ -383,11 +383,11 @@ jobs:
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG"
fi
done
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES metis"
PYPI_DEPENDENCIES="$PYPI_DEPENDENCIES metis"
echo "*** Install Pyomo dependencies ***"
# Note: this will fail the build if any installation fails (or
# possibly if it outputs messages to stderr)
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES metis"
PYPI_DEPENDENCIES="$PYPI_DEPENDENCIES metis"
conda install --update-deps -q -y $CONDA_DEPENDENCIES
if test -z "${{matrix.slim}}"; then
PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g')
Expand Down Expand Up @@ -696,7 +696,7 @@ jobs:
$PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \
parse_data_commands; parse_data_commands(data='')"
# Note: if we are testing with openmpi, add '--oversubscribe'
mpirun -np ${{matrix.mpi}} pytest -v \
mpirun -np ${{matrix.mpi}} -oversubscribe pytest -v \
--junit-xml=TEST-pyomo-mpi.xml \
-m "mpi" -W ignore::Warning \
pyomo `pwd`/pyomo-model-libraries
Expand Down Expand Up @@ -774,12 +774,12 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-13, windows-latest]

include:
- os: ubuntu-latest
TARGET: linux
- os: macos-latest
- os: macos-13
TARGET: osx
- os: windows-latest
TARGET: win
Expand Down
4 changes: 2 additions & 2 deletions pyomo/common/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,10 +902,10 @@ def __exit__(self, exc_type, exc_value, traceback):
sys.modules[_global_name + name] = sys.modules[name]
while deferred:
name, mod = deferred.popitem()
mod.__path__ = None
mod.__spec__ = None
sys.modules[_global_name + name] = mod
if isinstance(mod, DeferredImportModule):
mod.__path__ = None
mod.__spec__ = None
deferred.update(
(name + '.' + k, v)
for k, v in mod.__dict__.items()
Expand Down
148 changes: 111 additions & 37 deletions pyomo/common/numeric_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
native_integer_types = {int}
native_logical_types = {bool}
native_complex_types = {complex}
pyomo_constant_types = set() # includes NumericConstant

_native_boolean_types = {int, bool, str, bytes}
relocated_module_attribute(
Expand All @@ -61,6 +60,16 @@
"be treated as if they were bool (as was the case for the other "
"native_*_types sets). Users likely should use native_logical_types.",
)
_pyomo_constant_types = set() # includes NumericConstant, _PythonCallbackFunctionID
relocated_module_attribute(
'pyomo_constant_types',
'pyomo.common.numeric_types._pyomo_constant_types',
version='6.7.2.dev0',
msg="The pyomo_constant_types set will be removed in the future: the set "
"contained only NumericConstant and _PythonCallbackFunctionID, and provided "
"no meaningful value to clients or walkers. Users should likely handle "
"these types in the same manner as immutable Params.",
)


#: Python set used to identify numeric constants and related native
Expand Down Expand Up @@ -193,6 +202,67 @@ def RegisterLogicalType(new_type: type):
nonpyomo_leaf_types.add(new_type)


def check_if_native_type(obj):
if isinstance(obj, (str, bytes)):
native_types.add(obj.__class__)
return True
if check_if_logical_type(obj):
return True
if check_if_numeric_type(obj):
return True
return False


def check_if_logical_type(obj):
"""Test if the argument behaves like a logical type.
We check for "logical types" by checking if the type returns sane
results for Boolean operators (``^``, ``|``, ``&``) and if it maps
``1`` and ``2`` both to the same equivalent instance. If that
works, then we register the type in :py:attr:`native_logical_types`.
"""
obj_class = obj.__class__
# Do not re-evaluate known native types
if obj_class in native_types:
return obj_class in native_logical_types

try:
# It is not an error if you can't initialize the type from an
# int, but if you can, it should map !0 to True
if obj_class(1) != obj_class(2):
return False
except:
pass

try:
# Native logical types *must* be hashable
hash(obj)
# Native logical types must honor standard Boolean operators
if all(
(
obj_class(False) != obj_class(True),
obj_class(False) ^ obj_class(False) == obj_class(False),
obj_class(False) ^ obj_class(True) == obj_class(True),
obj_class(True) ^ obj_class(False) == obj_class(True),
obj_class(True) ^ obj_class(True) == obj_class(False),
obj_class(False) | obj_class(False) == obj_class(False),
obj_class(False) | obj_class(True) == obj_class(True),
obj_class(True) | obj_class(False) == obj_class(True),
obj_class(True) | obj_class(True) == obj_class(True),
obj_class(False) & obj_class(False) == obj_class(False),
obj_class(False) & obj_class(True) == obj_class(False),
obj_class(True) & obj_class(False) == obj_class(False),
obj_class(True) & obj_class(True) == obj_class(True),
)
):
RegisterLogicalType(obj_class)
return True
except:
pass
return False


def check_if_numeric_type(obj):
"""Test if the argument behaves like a numeric type.
Expand All @@ -210,36 +280,52 @@ def check_if_numeric_type(obj):
try:
obj_plus_0 = obj + 0
obj_p0_class = obj_plus_0.__class__
# ensure that the object is comparable to 0 in a meaningful way
# (among other things, this prevents numpy.ndarray objects from
# being added to native_numeric_types)
# Native numeric types *must* be hashable
hash(obj)
except:
return False
if obj_p0_class is not obj_class and obj_p0_class not in native_numeric_types:
return False
#
# Check if the numeric type behaves like a complex type
#
try:
if 1.41 < abs(obj_class(1j + 1)) < 1.42:
RegisterComplexType(obj_class)
return False
except:
pass
#
# Ensure that the object is comparable to 0 in a meaningful way
#
try:
if not ((obj < 0) ^ (obj >= 0)):
return False
# Native types *must* be hashable
hash(obj)
except:
return False
if obj_p0_class is obj_class or obj_p0_class in native_numeric_types:
#
# If we get here, this is a reasonably well-behaving
# numeric type: add it to the native numeric types
# so that future lookups will be faster.
#
RegisterNumericType(obj_class)
#
# Generate a warning, since Pyomo's management of third-party
# numeric types is more robust when registering explicitly.
#
logger.warning(
f"""Dynamically registering the following numeric type:
#
# If we get here, this is a reasonably well-behaving
# numeric type: add it to the native numeric types
# so that future lookups will be faster.
#
RegisterNumericType(obj_class)
try:
if obj_class(0.4) == obj_class(0):
RegisterIntegerType(obj_class)
except:
pass
#
# Generate a warning, since Pyomo's management of third-party
# numeric types is more robust when registering explicitly.
#
logger.warning(
f"""Dynamically registering the following numeric type:
{obj_class.__module__}.{obj_class.__name__}
Dynamic registration is supported for convenience, but there are known
limitations to this approach. We recommend explicitly registering
numeric types using RegisterNumericType() or RegisterIntegerType()."""
)
return True
else:
return False
)
return True


def value(obj, exception=True):
Expand All @@ -266,22 +352,10 @@ def value(obj, exception=True):
"""
if obj.__class__ in native_types:
return obj
if obj.__class__ in pyomo_constant_types:
#
# I'm commenting this out for now, but I think we should never expect
# to see a numeric constant with value None.
#
# if exception and obj.value is None:
# raise ValueError(
# "No value for uninitialized NumericConstant object %s"
# % (obj.name,))
return obj.value
#
# Test if we have a duck typed Pyomo expression
#
try:
obj.is_numeric_type()
except AttributeError:
if not hasattr(obj, 'is_numeric_type'):
#
# TODO: Historically we checked for new *numeric* types and
# raised exceptions for anything else. That is inconsistent
Expand All @@ -296,7 +370,7 @@ def value(obj, exception=True):
return None
raise TypeError(
"Cannot evaluate object with unknown type: %s" % obj.__class__.__name__
) from None
)
#
# Evaluate the expression object
#
Expand Down
Loading

0 comments on commit c1d0324

Please sign in to comment.