From 4c13589c08a87f62c3362f30467854447338ae21 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 18 Oct 2023 13:30:42 -0600 Subject: [PATCH 01/12] Track change in general expression API (use arg(0) and not expr). Fixes #2986 --- pyomo/core/expr/calculus/diff_with_pyomo.py | 2 +- pyomo/core/tests/unit/test_derivs.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pyomo/core/expr/calculus/diff_with_pyomo.py b/pyomo/core/expr/calculus/diff_with_pyomo.py index 952e8ec6dd3..0e3ba3cc2b2 100644 --- a/pyomo/core/expr/calculus/diff_with_pyomo.py +++ b/pyomo/core/expr/calculus/diff_with_pyomo.py @@ -328,7 +328,7 @@ def _diff_GeneralExpression(node, val_dict, der_dict): val_dict: ComponentMap der_dict: ComponentMap """ - der_dict[node.expr] += der_dict[node] + der_dict[node.arg(0)] += der_dict[node] def _diff_ExternalFunctionExpression(node, val_dict, der_dict): diff --git a/pyomo/core/tests/unit/test_derivs.py b/pyomo/core/tests/unit/test_derivs.py index 9e89f2beac9..23a5a8bc7d1 100644 --- a/pyomo/core/tests/unit/test_derivs.py +++ b/pyomo/core/tests/unit/test_derivs.py @@ -230,6 +230,17 @@ def e2(m, i): symbolic = reverse_sd(m.o.expr) self.assertAlmostEqual(derivs[m.x], pyo.value(symbolic[m.x]), tol) + def test_constant_named_expressions(self): + m = pyo.ConcreteModel() + m.x = pyo.Var(initialize=3) + m.e = pyo.Expression(expr=2) + + e = m.x * m.e + derivs = reverse_ad(e) + symbolic = reverse_sd(e) + self.assertAlmostEqual(derivs[m.x], pyo.value(symbolic[m.x]), tol + 3) + self.assertAlmostEqual(derivs[m.x], approx_deriv(e, m.x), tol) + def test_multiple_named_expressions(self): m = pyo.ConcreteModel() m.x = pyo.Var() From 10edc68f71161e198c1d382be019da604c4af3d5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 18 Oct 2023 16:02:39 -0600 Subject: [PATCH 02/12] Add debugging for when conda env setup fails --- .github/workflows/test_branches.yml | 17 +++++++++++++++-- .github/workflows/test_pr_and_main.yml | 15 ++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index ab91bf86ca1..540436f8781 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -318,7 +318,20 @@ jobs: fi done echo "*** Install Pyomo dependencies ***" - conda install --update-deps -q -y $CONDA_DEPENDENCIES + conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 + if test -n "$CONDA_INSTALL_ERR"; then + CONDAENV=$(conda list) + CONDA_INSTALL_ERR= + for PKG in $CONDA_DEPENDENCIES; do + if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then + echo "Conda dependency $PKG failed to install" + CONDA_INSTALL_ERR=1 + fi + done + if test -n "$CONDA_INSTALL_ERR"; then + exit 1 + fi + fi if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" @@ -334,7 +347,7 @@ jobs: echo "$_PKGLIST" _BASE=$(echo "$PKG" | sed 's/[=<>].*//') _BUILDS=$(echo "$_PKGLIST" | grep "^$_BASE " \ - | sed -r 's/\s+/ /g' | cut -d\ -f3) || echo ""x + | sed -r 's/\s+/ /g' | cut -d\ -f3) || echo "" if test -n "$_BUILDS"; then _ISPY=$(echo "$_BUILDS" | grep "^py") \ || echo "No python build detected" diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 5c27474d9d9..c6b5ef37535 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -348,7 +348,20 @@ jobs: fi done echo "*** Install Pyomo dependencies ***" - conda install --update-deps -q -y $CONDA_DEPENDENCIES + conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 + if test -n "$CONDA_INSTALL_ERR"; then + CONDAENV=$(conda list) + CONDA_INSTALL_ERR= + for PKG in $CONDA_DEPENDENCIES; do + if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then + echo "Conda dependency $PKG failed to install" + CONDA_INSTALL_ERR=1 + fi + done + if test -n "$CONDA_INSTALL_ERR"; then + exit 1 + fi + fi if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" From bbfcd680d76e6c004b3e97d4bf97cc5714c4cdcf Mon Sep 17 00:00:00 2001 From: John Siirola Date: Wed, 18 Oct 2023 16:09:52 -0600 Subject: [PATCH 03/12] Adding more debugging --- .github/workflows/test_branches.yml | 6 +++++- .github/workflows/test_pr_and_main.yml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 540436f8781..78b4ccd0868 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -320,12 +320,16 @@ jobs: echo "*** Install Pyomo dependencies ***" conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 if test -n "$CONDA_INSTALL_ERR"; then + echo "WARNING: 'conda install' returned nonzero return code." + echo "Verifying packages:" CONDAENV=$(conda list) CONDA_INSTALL_ERR= for PKG in $CONDA_DEPENDENCIES; do if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then - echo "Conda dependency $PKG failed to install" + echo "[FAIL] $PKG" CONDA_INSTALL_ERR=1 + else + echo "[ OK ] $PKG" fi done if test -n "$CONDA_INSTALL_ERR"; then diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index c6b5ef37535..6e8db628524 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -350,12 +350,16 @@ jobs: echo "*** Install Pyomo dependencies ***" conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 if test -n "$CONDA_INSTALL_ERR"; then + echo "WARNING: 'conda install' returned nonzero return code." + echo "Verifying packages:" CONDAENV=$(conda list) CONDA_INSTALL_ERR= for PKG in $CONDA_DEPENDENCIES; do if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then - echo "Conda dependency $PKG failed to install" + echo "[FAIL] $PKG" CONDA_INSTALL_ERR=1 + else + echo "[ OK ] $PKG" fi done if test -n "$CONDA_INSTALL_ERR"; then From a86acd8c6547f7f129ec84b21b12715ac554b1b4 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 11:37:04 -0600 Subject: [PATCH 04/12] Fix guards for mpi4py in tests --- .../pynumero/examples/tests/test_mpi_examples.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py b/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py index 3c47d58754e..68fe907a8ef 100644 --- a/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py +++ b/pyomo/contrib/pynumero/examples/tests/test_mpi_examples.py @@ -7,26 +7,28 @@ ) SKIPTESTS = [] -if numpy_available and scipy_available: - pass -else: - SKIPTESTS.append("Pynumero needs scipy and numpy>=1.13.0 to run BlockMatrix tests") +if not numpy_available: + SKIPTESTS.append("Pynumero needs numpy>=1.13.0 to run BlockMatrix tests") +if not scipy_available: + SKIPTESTS.append("Pynumero needs scipy to run BlockMatrix tests") try: from mpi4py import MPI comm = MPI.COMM_WORLD if comm.Get_size() != 3: - SKIPTESTS.append("Pynumero MPI examples require exactly 3 processes") + SKIPTESTS.append( + f"Pynumero MPI examples require 3 MPI processes (got {comm.Get_size()})" + ) except ImportError: - SKIPTESTS.append("Pynumero MPI examples require exactly 3 processes") + SKIPTESTS.append("Pynumero MPI examples require mpi4py") if not SKIPTESTS: from pyomo.contrib.pynumero.examples import parallel_vector_ops, parallel_matvec @unittest.pytest.mark.mpi -@unittest.skipIf(SKIPTESTS, SKIPTESTS) +@unittest.skipIf(SKIPTESTS, "\n".join(SKIPTESTS)) class TestExamples(unittest.TestCase): def test_parallel_vector_ops(self): z1_local, z2, z3 = parallel_vector_ops.main() From aae8bb3266243d494ea395c0b6bad091326d96e5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 12:34:09 -0600 Subject: [PATCH 05/12] Remove explicit installation of openmpi --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 78b4ccd0868..67ba7b156c7 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -92,7 +92,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: conda - PACKAGES: mpi4py openmpi + PACKAGES: mpi4py - os: ubuntu-latest python: '3.10' diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 6e8db628524..a352822c53c 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -93,7 +93,7 @@ jobs: skip_doctest: 1 TARGET: linux PYENV: conda - PACKAGES: mpi4py openmpi + PACKAGES: mpi4py - os: ubuntu-latest python: 3.11 From 67009063495fabafa0af00c00cf7a875615e1923 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 12:52:41 -0600 Subject: [PATCH 06/12] Remove '--oversubscribe' from mpirun (mpich compatibility) --- .github/workflows/test_branches.yml | 2 +- .github/workflows/test_pr_and_main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 67ba7b156c7..892525a953e 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -640,7 +640,7 @@ jobs: # is fully generated by a single process before invoking MPI $PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \ parse_data_commands; parse_data_commands(data='')" - mpirun -np ${{matrix.mpi}} --oversubscribe pytest -v \ + mpirun -np ${{matrix.mpi}} pytest -v \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ pyomo `pwd`/pyomo-model-libraries diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index a352822c53c..18e0a1565cc 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -670,7 +670,7 @@ jobs: # is fully generated by a single process before invoking MPI $PYTHON_EXE -c "from pyomo.dataportal.parse_datacmds import \ parse_data_commands; parse_data_commands(data='')" - mpirun -np ${{matrix.mpi}} --oversubscribe pytest -v \ + mpirun -np ${{matrix.mpi}} pytest -v \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ pyomo `pwd`/pyomo-model-libraries From b1a1e097b4846ec5ab38069395b994b43e83b84b Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 17:46:57 -0600 Subject: [PATCH 07/12] Track relocation of GamsExceptionExecution --- pyomo/solvers/plugins/solvers/GAMS.py | 5 ++++- pyomo/solvers/tests/checks/test_GAMS.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index ae0b12cdad4..c8224bbd999 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -250,7 +250,10 @@ def solve(self, *args, **kwds): self.available() from gams import GamsWorkspace, DebugLevel - from gams.workspace import GamsExceptionExecution + try: + from gams import GamsExceptionExecution + except ImportError: + from gams.workspace import GamsExceptionExecution if len(args) != 1: raise ValueError( diff --git a/pyomo/solvers/tests/checks/test_GAMS.py b/pyomo/solvers/tests/checks/test_GAMS.py index 5260f2bd195..c4913672694 100644 --- a/pyomo/solvers/tests/checks/test_GAMS.py +++ b/pyomo/solvers/tests/checks/test_GAMS.py @@ -31,7 +31,10 @@ opt_py = SolverFactory('gams', solver_io='python') gamspy_available = opt_py.available(exception_flag=False) if gamspy_available: - from gams.workspace import GamsExceptionExecution + try: + from gams import GamsExceptionExecution + except: + from gams.workspace import GamsExceptionExecution opt_gms = SolverFactory('gams', solver_io='gms') gamsgms_available = opt_gms.available(exception_flag=False) From 2c187e3f46943ecd007dac57d9d9254df07412be Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 17:47:41 -0600 Subject: [PATCH 08/12] Remove special handling of conda install failures --- .github/workflows/test_branches.yml | 22 ++++------------------ .github/workflows/test_pr_and_main.yml | 22 ++++------------------ 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml index 892525a953e..e773587ec85 100644 --- a/.github/workflows/test_branches.yml +++ b/.github/workflows/test_branches.yml @@ -318,24 +318,9 @@ jobs: fi done echo "*** Install Pyomo dependencies ***" - conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 - if test -n "$CONDA_INSTALL_ERR"; then - echo "WARNING: 'conda install' returned nonzero return code." - echo "Verifying packages:" - CONDAENV=$(conda list) - CONDA_INSTALL_ERR= - for PKG in $CONDA_DEPENDENCIES; do - if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then - echo "[FAIL] $PKG" - CONDA_INSTALL_ERR=1 - else - echo "[ OK ] $PKG" - fi - done - if test -n "$CONDA_INSTALL_ERR"; then - exit 1 - fi - 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 if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" @@ -640,6 +625,7 @@ jobs: # is fully generated by a single process before invoking MPI $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 \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml index 18e0a1565cc..2885fd107a8 100644 --- a/.github/workflows/test_pr_and_main.yml +++ b/.github/workflows/test_pr_and_main.yml @@ -348,24 +348,9 @@ jobs: fi done echo "*** Install Pyomo dependencies ***" - conda install --update-deps -q -y $CONDA_DEPENDENCIES || CONDA_INSTALL_ERR=1 - if test -n "$CONDA_INSTALL_ERR"; then - echo "WARNING: 'conda install' returned nonzero return code." - echo "Verifying packages:" - CONDAENV=$(conda list) - CONDA_INSTALL_ERR= - for PKG in $CONDA_DEPENDENCIES; do - if test `echo "$CONDAENV" | grep "^$PKG " | wc -l` -eq 0; then - echo "[FAIL] $PKG" - CONDA_INSTALL_ERR=1 - else - echo "[ OK ] $PKG" - fi - done - if test -n "$CONDA_INSTALL_ERR"; then - exit 1 - fi - 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 if test -z "${{matrix.slim}}"; then PYVER=$(echo "py${{matrix.python}}" | sed 's/\.//g') echo "Installing for $PYVER" @@ -670,6 +655,7 @@ jobs: # is fully generated by a single process before invoking MPI $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 \ --junit-xml=TEST-pyomo-mpi.xml \ -m "mpi" -W ignore::Warning \ From d0a8c6e7577f409196d6370214faf347292834d3 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 17:50:46 -0600 Subject: [PATCH 09/12] AApply black --- pyomo/solvers/plugins/solvers/GAMS.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index c8224bbd999..16a126b9af4 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -250,6 +250,7 @@ def solve(self, *args, **kwds): self.available() from gams import GamsWorkspace, DebugLevel + try: from gams import GamsExceptionExecution except ImportError: From e154fbc318c00503e3a4086f4874099440cb3e21 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 21:26:48 -0600 Subject: [PATCH 10/12] Explicitly disallow networkx=3.2 on python3.8 --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fe73782136c..6f68eb56db5 100644 --- a/setup.py +++ b/setup.py @@ -242,7 +242,12 @@ def _print_deps(self, deplist): # Note: matplotlib 3.6.1 has bug #24127, which breaks # seaborn's histplot (triggering parmest failures) 'matplotlib!=3.6.1', - 'networkx', # network, incidence_analysis, community_detection + # network, incidence_analysis, community_detection + # Note: networkx 3.2 is Python>-3.9, but there is a broken + # 3.2 package on conda-forgethat will get implicitly + # installed on python 3.8 + 'networkx<3.2;python_version<3.9', + 'networkx;python_version>=3.9', 'numpy', 'openpyxl', # dataportals #'pathos', # requested for #963, but PR currently closed From 9cc018a85c234d9a9076d0a192687c96b0d954c5 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 21:53:05 -0600 Subject: [PATCH 11/12] Improve version comparison in setup.py to handle comparisons with 3.10+ --- setup.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 6f68eb56db5..0fd6c939e39 100644 --- a/setup.py +++ b/setup.py @@ -166,9 +166,23 @@ def run(self): print(' '.join(deps)) def _print_deps(self, deplist): + class version_cmp(object): + ver = tuple(map(int, platform.python_version_tuple()[:2])) + def __lt__(self, other): + return self.ver < tuple(map(int, other.split('.'))) + def __le__(self, other): + return self.ver <= tuple(map(int, other.split('.'))) + def __gt__(self, other): + return not self.__le__(other) + def __ge__(self, other): + return not self.__lt__(other) + def __eq__(self, other): + return self.ver == tuple(map(int, other.split('.'))) + def __ne__(self, other): + return not self.__eq__(other) implementation_name = sys.implementation.name platform_system = platform.system() - python_version = '.'.join(platform.python_version_tuple()[:2]) + python_version = version_cmp() for entry in deplist: dep, _, condition = (_.strip() for _ in entry.partition(';')) if condition and not eval(condition): @@ -246,8 +260,8 @@ def _print_deps(self, deplist): # Note: networkx 3.2 is Python>-3.9, but there is a broken # 3.2 package on conda-forgethat will get implicitly # installed on python 3.8 - 'networkx<3.2;python_version<3.9', - 'networkx;python_version>=3.9', + 'networkx<3.2; python_version<"3.9"', + 'networkx; python_version>="3.9"', 'numpy', 'openpyxl', # dataportals #'pathos', # requested for #963, but PR currently closed From 0593891b1a6b46c07c0313d91b00797b1a1f15c6 Mon Sep 17 00:00:00 2001 From: John Siirola Date: Thu, 19 Oct 2023 21:55:29 -0600 Subject: [PATCH 12/12] Apply black --- setup.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/setup.py b/setup.py index 0fd6c939e39..252ef2d063e 100644 --- a/setup.py +++ b/setup.py @@ -168,18 +168,25 @@ def run(self): def _print_deps(self, deplist): class version_cmp(object): ver = tuple(map(int, platform.python_version_tuple()[:2])) + def __lt__(self, other): return self.ver < tuple(map(int, other.split('.'))) + def __le__(self, other): return self.ver <= tuple(map(int, other.split('.'))) + def __gt__(self, other): return not self.__le__(other) + def __ge__(self, other): return not self.__lt__(other) + def __eq__(self, other): return self.ver == tuple(map(int, other.split('.'))) + def __ne__(self, other): return not self.__eq__(other) + implementation_name = sys.implementation.name platform_system = platform.system() python_version = version_cmp()