Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Pyomo for NumPy 2 #3292

Merged
merged 7 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/test_branches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,9 @@ jobs:
token: ${{ secrets.PYOMO_CODECOV_TOKEN }}
name: ${{ matrix.TARGET }}
flags: ${{ matrix.TARGET }}
# downgrading after v0.7.0 broke tokenless upload
# see codecov/codecov-action#1487
version: v0.6.0
fail_ci_if_error: true

- name: Upload other coverage reports
Expand All @@ -867,4 +870,7 @@ jobs:
token: ${{ secrets.PYOMO_CODECOV_TOKEN }}
name: ${{ matrix.TARGET }}/other
flags: ${{ matrix.TARGET }},other
# downgrading after v0.7.0 broke tokenless upload
# see codecov/codecov-action#1487
version: v0.6.0
fail_ci_if_error: true
6 changes: 6 additions & 0 deletions .github/workflows/test_pr_and_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,9 @@ jobs:
token: ${{ secrets.PYOMO_CODECOV_TOKEN }}
name: ${{ matrix.TARGET }}
flags: ${{ matrix.TARGET }}
# downgrading after v0.7.0 broke tokenless upload
# see codecov/codecov-action#1487
version: v0.6.0
fail_ci_if_error: true

- name: Upload other coverage reports
Expand All @@ -911,4 +914,7 @@ jobs:
token: ${{ secrets.PYOMO_CODECOV_TOKEN }}
name: ${{ matrix.TARGET }}/other
flags: ${{ matrix.TARGET }},other
# downgrading after v0.7.0 broke tokenless upload
# see codecov/codecov-action#1487
version: v0.6.0
fail_ci_if_error: true
10 changes: 8 additions & 2 deletions pyomo/common/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -999,10 +999,13 @@ def _finalize_numpy(np, available):
# registration here (to bypass the deprecation warning) until we
# finally remove all support for it
numeric_types._native_boolean_types.add(t)
_floats = [np.float_, np.float16, np.float32, np.float64]
_floats = [np.float16, np.float32, np.float64]
# float96 and float128 may or may not be defined in this particular
# numpy build (it depends on platform and version).
# Register them only if they are present
if hasattr(np, 'float_'):
# Prepend to preserve previous functionality

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that float_/complex_ were simple aliases to float64/complex128, so it is not necessary to handle them for numpy 1.x, just dropping them is fully backwards compatible.

_floats.insert(0, np.float_)
if hasattr(np, 'float96'):
_floats.append(np.float96)
if hasattr(np, 'float128'):
Expand All @@ -1013,10 +1016,13 @@ def _finalize_numpy(np, available):
# registration here (to bypass the deprecation warning) until we
# finally remove all support for it
numeric_types._native_boolean_types.add(t)
_complex = [np.complex_, np.complex64, np.complex128]
_complex = [np.complex64, np.complex128]
# complex192 and complex256 may or may not be defined in this
# particular numpy build (it depends on platform and version).
# Register them only if they are present
if hasattr(np, 'np.complex_'):
# Prepend to preserve functionality
_complex.insert(0, np.complex_)
if hasattr(np, 'complex192'):
_complex.append(np.complex192)
if hasattr(np, 'complex256'):
Expand Down
35 changes: 28 additions & 7 deletions pyomo/common/unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@ def filter_fcn(self, line):
return False

def filter_file_contents(self, lines, abstol=None):
_numpy_scalar_re = re.compile(r'np.(int|float)\d+\(([^\)]+)\)')
filtered = []
deprecated = None
for line in lines:
Expand All @@ -807,29 +808,49 @@ def filter_file_contents(self, lines, abstol=None):
item_list = []
items = line.strip().split()
for i in items:
# Split up lists, dicts, and sets
while i and i[0] in '[{':
item_list.append(i[0])
i = i[1:]
tail = []
while i and i[-1] in ',:]}':
tail.append(i[-1])
i = i[:-1]

# A few substitutions to get tests passing on pypy3
if ".inf" in i:
i = i.replace(".inf", "inf")
if "null" in i:
i = i.replace("null", "None")

try:
item_list.append(float(i))
# Numpy 2.x changed the repr for scalars. Convert
# the new scalar reprs back to the original (which
# were indistinguishable from python floats/ints)
np_match = _numpy_scalar_re.match(i)
if np_match:
item_list.append(float(np_match.group(2)))
else:
item_list.append(float(i))
except:
item_list.append(i)
if tail:
tail.reverse()
item_list.extend(tail)

# We can get printed results objects where the baseline is
# exactly 0 (and omitted) and the test is slightly non-zero.
# We will look for the pattern of values printed from
# results objects and remote them if they are within
# tolerance of 0
if (
len(item_list) == 2
and item_list[0] == 'Value:'
and type(item_list[1]) is float
and abs(item_list[1]) < (abstol or 0)
and len(filtered[-1]) == 1
and filtered[-1][0][-1] == ':'
len(item_list) == 3
and item_list[0] == 'Value'
and item_list[1] == ':'
and type(item_list[2]) is float
and abs(item_list[2]) < (abstol or 0)
and len(filtered[-1]) == 2
and filtered[-1][1] == ':'
):
filtered.pop()
else:
Expand Down
10 changes: 6 additions & 4 deletions pyomo/core/kernel/register_numpy_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@
# Historically, the lists included several numpy aliases
numpy_int_names.extend(('int_', 'intc', 'intp'))
numpy_int.extend((numpy.int_, numpy.intc, numpy.intp))
numpy_float_names.append('float_')
numpy_float.append(numpy.float_)
numpy_complex_names.append('complex_')
numpy_complex.append(numpy.complex_)
if hasattr(numpy, 'float_'):
numpy_float_names.append('float_')
numpy_float.append(numpy.float_)
if hasattr(numpy, 'complex_'):
numpy_complex_names.append('complex_')
numpy_complex.append(numpy.complex_)

# Re-build the old numpy_* lists
for t in native_boolean_types:
Expand Down
11 changes: 8 additions & 3 deletions pyomo/core/tests/unit/test_kernel_register_numpy_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
# Boolean
numpy_bool_names = []
if numpy_available:
numpy_bool_names.append('bool_')
if numpy.__version__[0] == '2':
numpy_bool_names.append('bool')
else:
numpy_bool_names.append('bool_')
Comment on lines -19 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you check version here, but everywhere else you use hasattr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because John added that one...

Copy link
Contributor Author

@mrmundt mrmundt Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh but also, I can answer - this is a forwards-compatible one. bool and bool_ are both valid in NumPy 2, whereas bool does not exist in older NumPys. The other ones are forcing backwards compatibility.

# Integers
numpy_int_names = []
if numpy_available:
Expand All @@ -34,7 +37,8 @@
# Reals
numpy_float_names = []
if numpy_available:
numpy_float_names.append('float_')
if hasattr(numpy, 'float_'):
numpy_float_names.append('float_')
numpy_float_names.append('float16')
numpy_float_names.append('float32')
numpy_float_names.append('float64')
Expand All @@ -46,7 +50,8 @@
# Complex
numpy_complex_names = []
if numpy_available:
numpy_complex_names.append('complex_')
if hasattr(numpy, 'complex_'):
numpy_complex_names.append('complex_')
numpy_complex_names.append('complex64')
numpy_complex_names.append('complex128')
if hasattr(numpy, 'complex192'):
Expand Down
8 changes: 4 additions & 4 deletions pyomo/core/tests/unit/test_numvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,10 +552,10 @@ def test_unknownNumericType(self):

@unittest.skipUnless(numpy_available, "This test requires NumPy")
def test_numpy_basic_float_registration(self):
self.assertIn(numpy.float_, native_numeric_types)
self.assertNotIn(numpy.float_, native_integer_types)
self.assertIn(numpy.float_, _native_boolean_types)
self.assertIn(numpy.float_, native_types)
self.assertIn(numpy.float64, native_numeric_types)
self.assertNotIn(numpy.float64, native_integer_types)
self.assertIn(numpy.float64, _native_boolean_types)
self.assertIn(numpy.float64, native_types)

@unittest.skipUnless(numpy_available, "This test requires NumPy")
def test_numpy_basic_int_registration(self):
Expand Down
12 changes: 6 additions & 6 deletions pyomo/core/tests/unit/test_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ def setUp(self):
self.instance = self.model.create_instance(currdir + "setA.dat")
self.e1 = numpy.bool_(1)
self.e2 = numpy.int_(2)
self.e3 = numpy.float_(3.0)
self.e3 = numpy.float64(3.0)
self.e4 = numpy.int_(4)
self.e5 = numpy.int_(5)
self.e6 = numpy.int_(6)
Expand All @@ -1068,7 +1068,7 @@ def test_numpy_int(self):

def test_numpy_float(self):
model = ConcreteModel()
model.A = Set(initialize=[numpy.float_(1.0), numpy.float_(0.0)])
model.A = Set(initialize=[numpy.float64(1.0), numpy.float64(0.0)])
self.assertEqual(model.A.bounds(), (0, 1))


Expand Down Expand Up @@ -3213,7 +3213,7 @@ def test_numpy_membership(self):
self.assertEqual(numpy.int_(1) in Boolean, True)
self.assertEqual(numpy.bool_(True) in Boolean, True)
self.assertEqual(numpy.bool_(False) in Boolean, True)
self.assertEqual(numpy.float_(1.1) in Boolean, False)
self.assertEqual(numpy.float64(1.1) in Boolean, False)
self.assertEqual(numpy.int_(2) in Boolean, False)

self.assertEqual(numpy.int_(0) in Integers, True)
Expand All @@ -3222,7 +3222,7 @@ def test_numpy_membership(self):
# identically to 1
self.assertEqual(numpy.bool_(True) in Integers, True)
self.assertEqual(numpy.bool_(False) in Integers, True)
self.assertEqual(numpy.float_(1.1) in Integers, False)
self.assertEqual(numpy.float64(1.1) in Integers, False)
self.assertEqual(numpy.int_(2) in Integers, True)

self.assertEqual(numpy.int_(0) in Reals, True)
Expand All @@ -3231,14 +3231,14 @@ def test_numpy_membership(self):
# identically to 1
self.assertEqual(numpy.bool_(True) in Reals, True)
self.assertEqual(numpy.bool_(False) in Reals, True)
self.assertEqual(numpy.float_(1.1) in Reals, True)
self.assertEqual(numpy.float64(1.1) in Reals, True)
self.assertEqual(numpy.int_(2) in Reals, True)

self.assertEqual(numpy.int_(0) in Any, True)
self.assertEqual(numpy.int_(1) in Any, True)
self.assertEqual(numpy.bool_(True) in Any, True)
self.assertEqual(numpy.bool_(False) in Any, True)
self.assertEqual(numpy.float_(1.1) in Any, True)
self.assertEqual(numpy.float64(1.1) in Any, True)
self.assertEqual(numpy.int_(2) in Any, True)

def test_setargs1(self):
Expand Down
24 changes: 12 additions & 12 deletions pyomo/repn/plugins/lp_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,13 +458,13 @@ def write(self, model):
addSymbol(con, label)
ostream.write(f'\n{label}:\n')
self.write_expression(ostream, repn, False)
ostream.write(f'>= {(lb - offset)!r}\n')
ostream.write(f'>= {(lb - offset)!s}\n')
elif lb == ub:
label = f'c_e_{symbol}_'
addSymbol(con, label)
ostream.write(f'\n{label}:\n')
self.write_expression(ostream, repn, False)
ostream.write(f'= {(lb - offset)!r}\n')
ostream.write(f'= {(lb - offset)!s}\n')
else:
# We will need the constraint body twice. Generate
# in a buffer so we only have to do that once.
Expand All @@ -476,18 +476,18 @@ def write(self, model):
addSymbol(con, label)
ostream.write(f'\n{label}:\n')
ostream.write(buf)
ostream.write(f'>= {(lb - offset)!r}\n')
ostream.write(f'>= {(lb - offset)!s}\n')
label = f'r_u_{symbol}_'
aliasSymbol(con, label)
ostream.write(f'\n{label}:\n')
ostream.write(buf)
ostream.write(f'<= {(ub - offset)!r}\n')
ostream.write(f'<= {(ub - offset)!s}\n')
elif ub is not None:
label = f'c_u_{symbol}_'
addSymbol(con, label)
ostream.write(f'\n{label}:\n')
self.write_expression(ostream, repn, False)
ostream.write(f'<= {(ub - offset)!r}\n')
ostream.write(f'<= {(ub - offset)!s}\n')

if with_debug_timing:
# report the last constraint
Expand Down Expand Up @@ -527,8 +527,8 @@ def write(self, model):
# Note: Var.bounds guarantees the values are either (finite)
# native_numeric_types or None
lb, ub = v.bounds
lb = '-inf' if lb is None else repr(lb)
ub = '+inf' if ub is None else repr(ub)
lb = '-inf' if lb is None else str(lb)
ub = '+inf' if ub is None else str(ub)
ostream.write(f"\n {lb} <= {v_symbol} <= {ub}")

if integer_vars:
Expand Down Expand Up @@ -565,7 +565,7 @@ def write(self, model):
for v, w in getattr(soscon, 'get_items', soscon.items)():
if w.__class__ not in int_float:
w = float(f)
ostream.write(f" {getSymbol(v)}:{w!r}\n")
ostream.write(f" {getSymbol(v)}:{w!s}\n")

ostream.write("\nend\n")

Expand All @@ -584,9 +584,9 @@ def write_expression(self, ostream, expr, is_objective):
expr.linear.items(), key=lambda x: getVarOrder(x[0])
):
if coef < 0:
ostream.write(f'{coef!r} {getSymbol(getVar(vid))}\n')
ostream.write(f'{coef!s} {getSymbol(getVar(vid))}\n')
else:
ostream.write(f'+{coef!r} {getSymbol(getVar(vid))}\n')
ostream.write(f'+{coef!s} {getSymbol(getVar(vid))}\n')

quadratic = getattr(expr, 'quadratic', None)
if quadratic:
Expand All @@ -605,9 +605,9 @@ def _normalize_constraint(data):
col = c1, c2
sym = f' {getSymbol(getVar(vid1))} * {getSymbol(getVar(vid2))}\n'
if coef < 0:
return col, repr(coef) + sym
return col, str(coef) + sym
else:
return col, '+' + repr(coef) + sym
return col, f'+{coef!s}{sym}'

if is_objective:
#
Expand Down
Loading
Loading