diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 92c47b2d64b..c1029ff3d7b 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -75,13 +75,19 @@ jobs: other: [""] category: [""] + # win/3.8 conda builds no longer work due to environment not being able + # to resolve. We are skipping it now. + exclude: + - os: windows-latest + python: 3.8 + include: - os: ubuntu-latest python: '3.12' TARGET: linux PYENV: pip - - os: macos-13 + - os: macos-latest python: '3.10' TARGET: osx PYENV: pip @@ -215,6 +221,7 @@ jobs: if: matrix.TARGET == 'win' run: | echo "SETUPTOOLS_USE_DISTUTILS=local" >> $GITHUB_ENV + choco install pkgconfiglite - name: Set up Python ${{ matrix.python }} if: matrix.PYENV == 'pip' @@ -352,6 +359,10 @@ jobs: done echo "" echo "*** Install Pyomo dependencies ***" + # For windows, cannot use newer setuptools because of APPSI compilation issues + if test "${{matrix.TARGET}}" == 'win'; then + CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES setuptools<74.0.0" + fi # 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 diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 34efa4a029b..33aacaa9e35 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-13, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] python: [ 3.8, 3.9, '3.10', '3.11', '3.12' ] other: [""] category: [""] @@ -77,12 +77,13 @@ jobs: exclude: - os: windows-latest python: 3.8 + include: - os: ubuntu-latest TARGET: linux PYENV: pip - - os: macos-13 + - os: macos-latest TARGET: osx PYENV: pip @@ -108,14 +109,6 @@ jobs: PYENV: conda PACKAGES: openmpi mpi4py - - os: ubuntu-latest - python: '3.11' - other: /singletest - category: "-m 'neos or importtest'" - skip_doctest: 1 - TARGET: linux - PYENV: pip - - os: ubuntu-latest python: '3.10' other: /cython @@ -132,6 +125,14 @@ jobs: TARGET: win PYENV: pip + - os: ubuntu-latest + python: '3.11' + other: /singletest + category: "-m 'neos or importtest'" + skip_doctest: 1 + TARGET: linux + PYENV: pip + - os: ubuntu-latest python: 3.8 other: /slim @@ -243,6 +244,7 @@ jobs: if: matrix.TARGET == 'win' run: | echo "SETUPTOOLS_USE_DISTUTILS=local" >> $GITHUB_ENV + choco install pkgconfiglite - name: Set up Python ${{ matrix.python }} if: matrix.PYENV == 'pip' @@ -380,6 +382,10 @@ jobs: done echo "" echo "*** Install Pyomo dependencies ***" + # For windows, cannot use newer setuptools because of APPSI compilation issues + if test "${{matrix.TARGET}}" == 'win'; then + CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES setuptools<74.0.0" + fi # 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 diff --git a/README.md b/README.md index 707f1a06c5a..83934153361 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Jenkins Status](https://github.com/Pyomo/jenkins-status/blob/main/pyomo_main.svg)](https://pyomo-jenkins.sandia.gov/) [![codecov](https://codecov.io/gh/Pyomo/pyomo/branch/main/graph/badge.svg)](https://codecov.io/gh/Pyomo/pyomo) [![Documentation Status](https://readthedocs.org/projects/pyomo/badge/?version=latest)](http://pyomo.readthedocs.org/en/latest/) +[![URL Validation](https://img.shields.io/github/actions/workflow/status/Pyomo/pyomo/url_check.yml?branch=main&label=doc%20urls)](https://github.com/Pyomo/pyomo/actions/workflows/url_check.yml) [![Build services](https://github.com/Pyomo/jenkins-status/blob/main/pyomo_services.svg)](https://pyomo-jenkins.sandia.gov/) [![GitHub contributors](https://img.shields.io/github/contributors/pyomo/pyomo.svg)](https://github.com/pyomo/pyomo/graphs/contributors) [![Merged PRs](https://img.shields.io/github/issues-pr-closed-raw/pyomo/pyomo.svg?label=merged+PRs)](https://github.com/pyomo/pyomo/pulls?q=is:pr+is:merged) diff --git a/examples/pyomobook/gdp-ch/gdp_uc.py b/examples/pyomobook/gdp-ch/gdp_uc.py index 6268bcce068..ff3c554c039 100644 --- a/examples/pyomobook/gdp-ch/gdp_uc.py +++ b/examples/pyomobook/gdp-ch/gdp_uc.py @@ -116,3 +116,9 @@ def obj(m): @model.Constraint(model.GENERATORS) def nontrivial(m, g): return sum(m.Power[g, t] for t in m.TIME) >= len(m.TIME) / 2 * m.MinPower[g] + + +@model.ConstraintList() +def nondegenerate(m): + for i, g in enumerate(m.GENERATORS): + yield m.Power[g, i + 1] == 0 diff --git a/examples/pyomobook/gdp-ch/pyomo.gdp_uc.txt b/examples/pyomobook/gdp-ch/pyomo.gdp_uc.txt index 477336d48ba..f0b5a5c4795 100644 --- a/examples/pyomobook/gdp-ch/pyomo.gdp_uc.txt +++ b/examples/pyomobook/gdp-ch/pyomo.gdp_uc.txt @@ -22,9 +22,9 @@ Problem: Lower bound: 45.0 Upper bound: 45.0 Number of objectives: 1 - Number of constraints: 58 + Number of constraints: 60 Number of variables: 24 - Number of nonzeros: 124 + Number of nonzeros: 126 Sense: minimize # ---------------------------------------------------------- # Solver Information @@ -34,8 +34,8 @@ Solver: Termination condition: optimal Statistics: Branch and bound: - Number of bounded subproblems: 15 - Number of created subproblems: 15 + Number of bounded subproblems: 9 + Number of created subproblems: 9 Error rc: 0 Time: 0.007754325866699219 # ---------------------------------------------------------- @@ -51,24 +51,24 @@ Solution: obj: Value: 45 Variable: - GenOff[g1,2].binary_indicator_var: + GenOff[g1,1].binary_indicator_var: Value: 1 - GenOff[g2,1].binary_indicator_var: + GenOff[g2,2].binary_indicator_var: Value: 1 - GenOn[g1,1].binary_indicator_var: + GenOn[g1,3].binary_indicator_var: Value: 1 - GenOn[g2,3].binary_indicator_var: + GenOn[g2,1].binary_indicator_var: Value: 1 - GenStartup[g1,3].binary_indicator_var: + GenStartup[g1,2].binary_indicator_var: Value: 1 - GenStartup[g2,2].binary_indicator_var: + GenStartup[g2,3].binary_indicator_var: Value: 1 - Power[g1,1]: - Value: 10 - Power[g1,3]: + Power[g1,2]: Value: 5 - Power[g2,2]: + Power[g1,3]: Value: 10 - Power[g2,3]: + Power[g2,1]: Value: 20 + Power[g2,3]: + Value: 10 Constraint: No values diff --git a/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py b/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py index dc721488f74..961d34a68c7 100644 --- a/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py +++ b/pyomo/contrib/fme/tests/test_fourier_motzkin_elimination.py @@ -562,7 +562,7 @@ def test_post_processing(self): fme = TransformationFactory('contrib.fourier_motzkin_elimination') fme.apply_to(m, vars_to_eliminate=disaggregatedVars, do_integer_arithmetic=True) # post-process - fme.post_process_fme_constraints(m, SolverFactory('glpk')) + fme.post_process_fme_constraints(m, SolverFactory('glpk'), tolerance=-1e-6) constraints = m._pyomo_contrib_fme_transformation.projected_constraints self.assertEqual(len(constraints), 11) diff --git a/pyomo/contrib/pynumero/sparse/block_vector.py b/pyomo/contrib/pynumero/sparse/block_vector.py index b636dd74203..bcb5e786d08 100644 --- a/pyomo/contrib/pynumero/sparse/block_vector.py +++ b/pyomo/contrib/pynumero/sparse/block_vector.py @@ -38,7 +38,7 @@ def assert_block_structure(vec): raise NotFullyDefinedBlockVectorError(msg) -class BlockVector(np.ndarray, BaseBlockVector): +class BlockVector(BaseBlockVector, np.ndarray): """ Structured vector interface. This interface can be used to perform operations on vectors composed by vectors. For example, @@ -1592,86 +1592,3 @@ def toMPIBlockVector(self, rank_ownership, mpi_comm, assert_correct_owners=False mpi_bv.set_block(bid, self.get_block(bid)) return mpi_bv - - # the following methods are not supported by blockvector - - def argpartition(self, kth, axis=-1, kind='introselect', order=None): - BaseBlockVector.argpartition(self, kth, axis=axis, kind=kind, order=order) - - def argsort(self, axis=-1, kind='quicksort', order=None): - BaseBlockVector.argsort(self, axis=axis, kind=kind, order=order) - - def byteswap(self, inplace=False): - BaseBlockVector.byteswap(self, inplace=inplace) - - def choose(self, choices, out=None, mode='raise'): - BaseBlockVector.choose(self, choices, out=out, mode=mode) - - def diagonal(self, offset=0, axis1=0, axis2=1): - BaseBlockVector.diagonal(self, offset=offset, axis1=axis1, axis2=axis2) - - def dump(self, file): - BaseBlockVector.dump(self, file) - - def dumps(self): - BaseBlockVector.dumps(self) - - def getfield(self, dtype, offset=0): - BaseBlockVector.getfield(self, dtype, offset=offset) - - def item(self, *args): - BaseBlockVector.item(self, *args) - - def itemset(self, *args): - BaseBlockVector.itemset(self, *args) - - def newbyteorder(self, new_order='S'): - BaseBlockVector.newbyteorder(self, new_order=new_order) - - def put(self, indices, values, mode='raise'): - BaseBlockVector.put(self, indices, values, mode=mode) - - def partition(self, kth, axis=-1, kind='introselect', order=None): - BaseBlockVector.partition(self, kth, axis=axis, kind=kind, order=order) - - def repeat(self, repeats, axis=None): - BaseBlockVector.repeat(self, repeats, axis=axis) - - def reshape(self, shape, order='C'): - BaseBlockVector.reshape(self, shape, order=order) - - def resize(self, new_shape, refcheck=True): - BaseBlockVector.resize(self, new_shape, refcheck=refcheck) - - def searchsorted(self, v, side='left', sorter=None): - BaseBlockVector.searchsorted(self, v, side=side, sorter=sorter) - - def setfield(self, val, dtype, offset=0): - BaseBlockVector.setfield(self, val, dtype, offset=offset) - - def setflags(self, write=None, align=None, uic=None): - BaseBlockVector.setflags(self, write=write, align=align, uic=uic) - - def sort(self, axis=-1, kind='quicksort', order=None): - BaseBlockVector.sort(self, axis=axis, kind=kind, order=order) - - def squeeze(self, axis=None): - BaseBlockVector.squeeze(self, axis=axis) - - def swapaxes(self, axis1, axis2): - BaseBlockVector.swapaxes(self, axis1, axis2) - - def tobytes(self, order='C'): - BaseBlockVector.tobytes(self, order=order) - - def take(self, indices, axis=None, out=None, mode='raise'): - BaseBlockVector.take(self, indices, axis=axis, out=out, mode=mode) - - def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): - raise NotImplementedError('trace not implemented for BlockVector') - - def transpose(*axes): - BaseBlockVector.transpose(*axes) - - def tostring(order='C'): - BaseBlockVector.tostring(order=order) diff --git a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py index 89cf136a5f7..f86d450a73e 100644 --- a/pyomo/contrib/pynumero/sparse/mpi_block_vector.py +++ b/pyomo/contrib/pynumero/sparse/mpi_block_vector.py @@ -24,7 +24,7 @@ def assert_block_structure(vec): raise NotFullyDefinedBlockVectorError(msg) -class MPIBlockVector(np.ndarray, BaseBlockVector): +class MPIBlockVector(BaseBlockVector, np.ndarray): """ Parallel structured vector interface. This interface can be used to perform parallel operations on vectors composed by vectors. The main @@ -1447,81 +1447,3 @@ def flatten(self, order='C'): def ravel(self, order='C'): raise RuntimeError('Operation not supported by MPIBlockVector') - - def argpartition(self, kth, axis=-1, kind='introselect', order=None): - BaseBlockVector.argpartition(self, kth, axis=axis, kind=kind, order=order) - - def argsort(self, axis=-1, kind='quicksort', order=None): - BaseBlockVector.argsort(self, axis=axis, kind=kind, order=order) - - def byteswap(self, inplace=False): - BaseBlockVector.byteswap(self, inplace=inplace) - - def choose(self, choices, out=None, mode='raise'): - BaseBlockVector.choose(self, choices, out=out, mode=mode) - - def diagonal(self, offset=0, axis1=0, axis2=1): - BaseBlockVector.diagonal(self, offset=offset, axis1=axis1, axis2=axis2) - - def dump(self, file): - BaseBlockVector.dump(self, file) - - def dumps(self): - BaseBlockVector.dumps(self) - - def getfield(self, dtype, offset=0): - BaseBlockVector.getfield(self, dtype, offset=offset) - - def item(self, *args): - BaseBlockVector.item(self, *args) - - def itemset(self, *args): - BaseBlockVector.itemset(self, *args) - - def newbyteorder(self, new_order='S'): - BaseBlockVector.newbyteorder(self, new_order=new_order) - - def put(self, indices, values, mode='raise'): - BaseBlockVector.put(self, indices, values, mode=mode) - - def partition(self, kth, axis=-1, kind='introselect', order=None): - BaseBlockVector.partition(self, kth, axis=axis, kind=kind, order=order) - - def repeat(self, repeats, axis=None): - BaseBlockVector.repeat(self, repeats, axis=axis) - - def reshape(self, shape, order='C'): - BaseBlockVector.reshape(self, shape, order=order) - - def resize(self, new_shape, refcheck=True): - BaseBlockVector.resize(self, new_shape, refcheck=refcheck) - - def searchsorted(self, v, side='left', sorter=None): - BaseBlockVector.searchsorted(self, v, side=side, sorter=sorter) - - def setfield(self, val, dtype, offset=0): - BaseBlockVector.setfield(self, val, dtype, offset=offset) - - def setflags(self, write=None, align=None, uic=None): - BaseBlockVector.setflags(self, write=write, align=align, uic=uic) - - def sort(self, axis=-1, kind='quicksort', order=None): - BaseBlockVector.sort(self, axis=axis, kind=kind, order=order) - - def squeeze(self, axis=None): - BaseBlockVector.squeeze(self, axis=axis) - - def swapaxes(self, axis1, axis2): - BaseBlockVector.swapaxes(self, axis1, axis2) - - def tobytes(self, order='C'): - BaseBlockVector.tobytes(self, order=order) - - def argmax(self, axis=None, out=None): - BaseBlockVector.argmax(self, axis=axis, out=out) - - def argmin(self, axis=None, out=None): - BaseBlockVector.argmax(self, axis=axis, out=out) - - def take(self, indices, axis=None, out=None, mode='raise'): - BaseBlockVector.take(self, indices, axis=axis, out=out, mode=mode) diff --git a/pyomo/core/kernel/base.py b/pyomo/core/kernel/base.py index d599c76f6a1..0653868e109 100644 --- a/pyomo/core/kernel/base.py +++ b/pyomo/core/kernel/base.py @@ -156,7 +156,7 @@ def getname( Args: fully_qualified (bool): Generate a full name by - iterating through all anscestor containers. + iterating through all ancestor containers. Default is :const:`False`. convert (function): A function that converts a storage key into a string diff --git a/pyomo/core/tests/unit/kernel/test_piecewise.py b/pyomo/core/tests/unit/kernel/test_piecewise.py index 3d9cf66e39c..e376bdce8b3 100644 --- a/pyomo/core/tests/unit/kernel/test_piecewise.py +++ b/pyomo/core/tests/unit/kernel/test_piecewise.py @@ -209,19 +209,17 @@ def test_generate_delaunay(self): vlist.append(variable(lb=0, ub=1)) vlist.append(variable(lb=1, ub=2)) vlist.append(variable(lb=2, ub=3)) - if not (util.numpy_available and util.scipy_available): - with self.assertRaises(ImportError): - util.generate_delaunay(vlist) - else: - tri = util.generate_delaunay(vlist, num=2) - self.assertTrue(isinstance(tri, util.scipy.spatial.Delaunay)) - self.assertEqual(len(tri.simplices), 6) - self.assertEqual(len(tri.points), 8) - - tri = util.generate_delaunay(vlist, num=3) - self.assertTrue(isinstance(tri, util.scipy.spatial.Delaunay)) - self.assertEqual(len(tri.simplices), 62) - self.assertEqual(len(tri.points), 27) + tri = util.generate_delaunay(vlist, num=2) + self.assertTrue(isinstance(tri, util.scipy.spatial.Delaunay)) + self.assertEqual(len(tri.simplices), 6) + self.assertEqual(len(tri.points), 8) + + tri = util.generate_delaunay(vlist, num=3) + self.assertTrue(isinstance(tri, util.scipy.spatial.Delaunay)) + # we got some simplices + self.assertTrue(len(tri.simplices) > 1) + # all the given points are accounted for + self.assertEqual(len(tri.points) + len(tri.coplanar), 27) # # Check cases where not all variables are bounded diff --git a/pyomo/core/tests/unit/test_sets.py b/pyomo/core/tests/unit/test_sets.py index 52c4523eaba..e9f96a417f4 100644 --- a/pyomo/core/tests/unit/test_sets.py +++ b/pyomo/core/tests/unit/test_sets.py @@ -2979,7 +2979,7 @@ def test_initialize_and_clone_from_dict_keys(self): # # While deepcopying a model is generally not supported, this is # an easy way to ensure that this simple model is cleanly - # clonable. + # able to be cloned. ref = """1 Set Declarations INDEX : Size=1, Index=None, Ordered=Insertion Key : Dimen : Domain : Size : Members diff --git a/pyomo/neos/tests/test_neos.py b/pyomo/neos/tests/test_neos.py index 681856781be..f14600fdb84 100644 --- a/pyomo/neos/tests/test_neos.py +++ b/pyomo/neos/tests/test_neos.py @@ -79,9 +79,6 @@ def test_doc(self): doc = pyomo.neos.doc dockeys = set(doc.keys()) - # Octeract interface is disabled, see #3321 - amplsolvers.remove('octeract') - self.assertEqual(amplsolvers, dockeys) # gamssolvers = set(v[0].lower() for v in tmp if v[1]=='GAMS') @@ -152,8 +149,9 @@ def test_minto(self): def test_mosek(self): self._run('mosek') - # [16 Jul 24] Octeract is erroring. We will disable the interface + # [16 Jul 24]: Octeract is erroring. We will disable the interface # (and testing) until we have time to resolve #3321 + # [20 Sep 24]: and appears to have been removed from NEOS # # def test_octeract(self): # self._run('octeract')