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

Interface to GiNaC for Simplification #3088

Merged
merged 116 commits into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
8d1e68e
working on simplification contrib package
michaelbynum Nov 8, 2023
8015d7e
working on simplification contrib package
michaelbynum Nov 8, 2023
56e8ac8
working on ginac interface for simplification
michaelbynum Nov 20, 2023
932d3d6
ginac interface improvements
michaelbynum Nov 20, 2023
208a5da
simplification interface
michaelbynum Nov 20, 2023
6ef8914
bugs
michaelbynum Nov 21, 2023
af47ef7
simplification tests
michaelbynum Nov 21, 2023
475ec06
simplification tests
michaelbynum Jan 10, 2024
491db9f
update GHA to install ginac
michaelbynum Jan 10, 2024
b3a1ff9
run black
michaelbynum Jan 10, 2024
de8743a
syntax
michaelbynum Jan 10, 2024
ee9f830
install ginac in GHA
michaelbynum Jan 10, 2024
36cfd63
install ginac in GHA
michaelbynum Jan 10, 2024
8269539
install ginac in GHA
michaelbynum Jan 10, 2024
7bb0ff5
install ginac in GHA
michaelbynum Jan 10, 2024
546dad1
install ginac in GHA
michaelbynum Jan 10, 2024
37a955b
install ginac in GHA
michaelbynum Jan 10, 2024
05134ce
install ginac in GHA
michaelbynum Jan 10, 2024
1151927
install ginac in GHA
michaelbynum Jan 11, 2024
2c4fdbe
skip tests when dependencies are not available
michaelbynum Jan 11, 2024
9ebd79b
install ginac in GHA
michaelbynum Jan 11, 2024
0bd1563
update simplification tests
michaelbynum Jan 11, 2024
26007ac
run black
michaelbynum Jan 11, 2024
fc36411
add pytest marker for simplification
michaelbynum Jan 11, 2024
2007de4
Merge branch 'main' into ginac
michaelbynum Jan 11, 2024
5ba03e2
update GHA
michaelbynum Jan 11, 2024
90cdeba
update GHA
michaelbynum Jan 11, 2024
17cb11d
debugging GHA
michaelbynum Jan 11, 2024
d1fe244
test simplification with ginac and sympy
michaelbynum Jan 11, 2024
9159c3c
test simplification with ginac and sympy
michaelbynum Jan 11, 2024
457f2b3
fixing simplification tests
michaelbynum Jan 11, 2024
1e7c3f1
fixing simplification tests
michaelbynum Jan 11, 2024
b2d969f
fixing simplification tests
michaelbynum Jan 11, 2024
da2fe3d
fixing simplification tests
michaelbynum Jan 11, 2024
d37b304
Merge remote-tracking branch 'main_fork/main' into ginac
michaelbynum Jan 22, 2024
2b1596b
fixing simplification tests
michaelbynum Jan 22, 2024
d75c5f8
fixing simplification tests
michaelbynum Jan 22, 2024
dae0205
run black
michaelbynum Jan 22, 2024
313108d
fixing simplification tests
michaelbynum Jan 22, 2024
5ff4d4d
fixing simplification tests
michaelbynum Jan 22, 2024
010a997
fixing simplification tests
michaelbynum Jan 22, 2024
2c59b29
fixing simplification tests
michaelbynum Jan 22, 2024
d67d90d
fixing simplification tests
michaelbynum Jan 22, 2024
3fcec35
fixing simplification tests
michaelbynum Jan 22, 2024
b5550fe
fixing simplification tests
michaelbynum Jan 22, 2024
8984389
fixing simplification tests
michaelbynum Jan 22, 2024
125b7c7
Merge branch 'main' into ginac
michaelbynum Jan 24, 2024
92163f2
cleanup
michaelbynum Feb 21, 2024
0dff80f
cleanup
michaelbynum Feb 21, 2024
c49c2df
cleanup
michaelbynum Feb 21, 2024
2c42772
Merge remote-tracking branch 'michaelbynum/ginac' into ginac
michaelbynum Feb 21, 2024
ce308d9
Merge branch 'main' into ginac
michaelbynum Feb 21, 2024
29d6a19
cleanup
michaelbynum Feb 21, 2024
350f3d0
Merge remote-tracking branch 'main_fork/main' into ginac
michaelbynum Feb 21, 2024
9cb26c7
NFC: Fix year in copyright assertion comment
blnicho Feb 28, 2024
1e609c0
run black
michaelbynum Mar 4, 2024
e076cbf
Merge remote-tracking branch 'michaelbynum/ginac' into ginac
michaelbynum Mar 4, 2024
3dcaa1d
Merge branch 'main' into ginac
michaelbynum Mar 5, 2024
637e277
Merge branch 'main' into ginac
michaelbynum Apr 23, 2024
79f7189
Merge remote-tracking branch 'michaelbynum/ginac' into ginac
michaelbynum Apr 23, 2024
f4e989f
Add fileutils patch so find_library returns absolute path on Linux
jsiirola Apr 29, 2024
ebb4d07
bugfix: add missing import
jsiirola Apr 29, 2024
16d49e1
NFC: clarify ginac builder exception message
jsiirola Apr 29, 2024
bd3299d
Register the GiNaC interface builder with the ExtensionBuilder
jsiirola Apr 29, 2024
29b2072
Rework GiNaC interface builder (in development - testing several things)
jsiirola Apr 29, 2024
0e42033
Merge branch 'main' into ginac
michaelbynum Apr 29, 2024
1b1f944
bugfix
michaelbynum Apr 29, 2024
77fff61
Merge remote-tracking branch 'michael/ginac' into ginac
jsiirola Apr 30, 2024
74052ee
Disable local build of GiNaC
jsiirola Apr 30, 2024
6bb0f7f
NFC: apply black
jsiirola Apr 30, 2024
39643b3
remove repeated code
jsiirola Apr 30, 2024
89ace9b
Add support for download tar archives to theFileDownloader
jsiirola Apr 30, 2024
bb274ff
Switch GiNaC interface builder to use TempfileManager
jsiirola Apr 30, 2024
adaefbc
Add function for downloading and installing GiNaC and CLN
jsiirola Apr 30, 2024
c7a8f8e
Hook GiNaC builder into pyomo command
jsiirola Apr 30, 2024
a29bb3e
Remove simplification test marker
jsiirola Apr 30, 2024
c475fe7
Switching output to sys.stdout, adding debugging
jsiirola Apr 30, 2024
7053690
Support walking up the directory tree looking for ginac headers (this
jsiirola Apr 30, 2024
be69224
Fix several typos / include search logic
jsiirola Apr 30, 2024
4ac04a6
NFC: apply black
jsiirola Apr 30, 2024
abe5f8b
Resync GHA workflows, remove ginac build code
jsiirola Apr 30, 2024
54bd3d3
Move the ginac interface sources to a subdirectory
jsiirola May 5, 2024
8a37c8b
Update builder to use argparse, clean up output
jsiirola May 5, 2024
c2c63bc
Run sympy tests any time sympy is installed
jsiirola May 5, 2024
b53bbfc
Define NamedIntEnum
jsiirola May 5, 2024
e7540f2
Rework Simplifier so we can force the backend mode
jsiirola May 5, 2024
9c220b0
Improve robustness of tar filter
jsiirola May 5, 2024
e597bb5
Ensure tar file is closed
jsiirola May 5, 2024
70166ff
test get_tar_archive()
jsiirola May 5, 2024
591514d
Merge pull request #10 from jsiirola/ginac
michaelbynum May 7, 2024
4446d36
fix tests
michaelbynum May 7, 2024
90b1783
Update tar filter to handle ValueError from commonpath()
jsiirola May 7, 2024
5aae459
keep mutable parameters in sympy conversion
michaelbynum May 7, 2024
fa84fb7
Merge remote-tracking branch 'michaelbynum/ginac' into ginac
michaelbynum May 7, 2024
ae5ebd3
update defaults for mutable parameters when using sympy
michaelbynum May 7, 2024
2b3bd4e
update tests
michaelbynum May 7, 2024
82dfda1
Ensure the same output is logged on Windows and other platforms
jsiirola May 8, 2024
e132fb0
Merge remote-tracking branch 'main_fork/main' into ginac
michaelbynum May 8, 2024
3572c44
ginac cleanup
michaelbynum May 8, 2024
4b340bf
Merge remote-tracking branch 'michaelbynum/ginac' into ginac
michaelbynum May 8, 2024
a53c6f7
Add 'builders' marker for testing custom library builds (currently ju…
jsiirola May 8, 2024
8f33eed
Support multiple markers (categories) in jenkins driver
jsiirola May 8, 2024
4dc4e89
Additional (debugging) output in ginac_interface builder
jsiirola May 8, 2024
27ac6dc
Merge remote-tracking branch 'refs/remotes/michael/ginac' into ginac
jsiirola May 8, 2024
e847f10
NFC: fix typo
jsiirola May 8, 2024
8b11a1c
NFC: resyncing test_branches and test_pr_and_main
jsiirola May 8, 2024
fa2cc31
improve handling of category quotation
jsiirola May 8, 2024
bd5f10c
Improve conftest.py efficiency
jsiirola May 8, 2024
354feb2
Ensure that all unmarked tests are marked with the implicit markers
jsiirola May 8, 2024
92edcc5
NFC: apply black
jsiirola May 8, 2024
7ae7275
Prevent APPSI / GiNaC interfaces from exposing module symbols globally
jsiirola May 8, 2024
1409aa2
Fix bug in Jenkins driver
jsiirola May 9, 2024
aa75601
Fix typo in Jenkins driver
jsiirola May 9, 2024
cdaff17
Add info to the build log
jsiirola May 9, 2024
76d53de
Explicitly call out CWD when running configure
jsiirola May 9, 2024
35b71f8
Removing singletest from test_branches
jsiirola May 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/test_branches.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ jobs:
# Notes:
# - install glpk
# - pyodbc needs: gcc pkg-config unixodbc freetds
for pkg in bash pkg-config unixodbc freetds glpk; do
for pkg in bash pkg-config unixodbc freetds glpk ginac; do
brew list $pkg || brew install $pkg
done

Expand All @@ -193,7 +193,8 @@ jobs:
# - install glpk
# - ipopt needs: libopenblas-dev gfortran liblapack-dev
sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \
install libopenblas-dev gfortran liblapack-dev glpk-utils
install libopenblas-dev gfortran liblapack-dev glpk-utils \
libginac-dev
sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os

- name: Update Windows
Expand Down Expand Up @@ -264,7 +265,7 @@ jobs:
if test -z "${{matrix.slim}}"; then
python -m pip install --cache-dir cache/pip cplex docplex \
|| echo "WARNING: CPLEX Community Edition is not available"
python -m pip install --cache-dir cache/pip gurobipy==10.0.3\
python -m pip install --cache-dir cache/pip gurobipy==10.0.3 \
|| echo "WARNING: Gurobi is not available"
python -m pip install --cache-dir cache/pip xpress \
|| echo "WARNING: Xpress Community Edition is not available"
Expand Down Expand Up @@ -339,7 +340,7 @@ jobs:
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 -y $CONDA_DEPENDENCIES
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"
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/test_pr_and_main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ jobs:
# Notes:
# - install glpk
# - pyodbc needs: gcc pkg-config unixodbc freetds
for pkg in bash pkg-config unixodbc freetds glpk; do
for pkg in bash pkg-config unixodbc freetds glpk ginac; do
brew list $pkg || brew install $pkg
done

Expand All @@ -230,7 +230,8 @@ jobs:
# - install glpk
# - ipopt needs: libopenblas-dev gfortran liblapack-dev
sudo apt-get -o Dir::Cache=${GITHUB_WORKSPACE}/cache/os \
install libopenblas-dev gfortran liblapack-dev glpk-utils
install libopenblas-dev gfortran liblapack-dev glpk-utils \
libginac-dev
sudo chmod -R 777 ${GITHUB_WORKSPACE}/cache/os

- name: Update Windows
Expand Down Expand Up @@ -372,6 +373,7 @@ jobs:
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG"
fi
done
echo ""
echo "*** Install Pyomo dependencies ***"
# Note: this will fail the build if any installation fails (or
# possibly if it outputs messages to stderr)
Expand Down
18 changes: 14 additions & 4 deletions .jenkins.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ fi
if test -z "$SLIM"; then
export VENV_SYSTEM_PACKAGES='--system-site-packages'
fi
if test ! -z "$CATEGORY"; then
export PY_CAT="-m $CATEGORY"
fi

if test "$WORKSPACE" != "`pwd`"; then
echo "ERROR: pwd is not WORKSPACE"
Expand Down Expand Up @@ -122,10 +119,23 @@ if test -z "$MODE" -o "$MODE" == setup; then
echo "PYOMO_CONFIG_DIR=$PYOMO_CONFIG_DIR"
echo ""

# Call Pyomo build scripts to build TPLs that would normally be
# skipped by the pyomo download-extensions / build-extensions
# actions below
if [[ " $CATEGORY " == *" builders "* ]]; then
echo ""
echo "Running local build scripts..."
echo ""
set -x
python pyomo/contrib/simplification/build.py --build-deps || exit 1
set +x
fi

# Use Pyomo to download & compile binary extensions
i=0
while /bin/true; do
i=$[$i+1]
echo ""
echo "Downloading pyomo extensions (attempt $i)"
pyomo download-extensions $PYOMO_DOWNLOAD_ARGS
if test $? == 0; then
Expand Down Expand Up @@ -178,7 +188,7 @@ if test -z "$MODE" -o "$MODE" == test; then
python -m pytest -v \
-W ignore::Warning \
--junitxml="TEST-pyomo.xml" \
$PY_CAT $TEST_SUITES $PYTEST_EXTRA_ARGS
-m "$CATEGORY" $TEST_SUITES $PYTEST_EXTRA_ARGS

# Combine the coverage results and upload
if test -z "$DISABLE_COVERAGE"; then
Expand Down
27 changes: 20 additions & 7 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@

import pytest

_implicit_markers = {'default'}
_extended_implicit_markers = _implicit_markers.union({'solver'})


def pytest_collection_modifyitems(items):
"""
This method will mark any unmarked tests with the implicit marker ('default')

"""
for item in items:
try:
next(item.iter_markers())
except StopIteration:
for marker in _implicit_markers:
item.add_marker(getattr(pytest.mark, marker))


def pytest_runtest_setup(item):
"""
Expand All @@ -32,23 +48,20 @@ def pytest_runtest_setup(item):
the default mode; but if solver tests are also marked with an explicit
category (e.g., "expensive"), we will skip them.
"""
marker = item.iter_markers()
solvernames = [mark.args[0] for mark in item.iter_markers(name="solver")]
solveroption = item.config.getoption("--solver")
markeroption = item.config.getoption("-m")
implicit_markers = ['default']
extended_implicit_markers = implicit_markers + ['solver']
item_markers = set(mark.name for mark in marker)
item_markers = set(mark.name for mark in item.iter_markers())
if solveroption:
if solveroption not in solvernames:
pytest.skip("SKIPPED: Test not marked {!r}".format(solveroption))
return
elif markeroption:
return
elif item_markers:
if not set(implicit_markers).issubset(
item_markers
) and not item_markers.issubset(set(extended_implicit_markers)):
if not _implicit_markers.issubset(item_markers) and not item_markers.issubset(
_extended_implicit_markers
):
pytest.skip('SKIPPED: Only running default, solver, and unmarked tests.')


Expand Down
58 changes: 57 additions & 1 deletion pyomo/common/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
urllib_error = attempt_import('urllib.error')[0]
ssl = attempt_import('ssl')[0]
zipfile = attempt_import('zipfile')[0]
tarfile = attempt_import('tarfile')[0]
gzip = attempt_import('gzip')[0]
distro, distro_available = attempt_import('distro')

Expand Down Expand Up @@ -371,7 +372,7 @@ def get_zip_archive(self, url, dirOffset=0):
# Simple sanity checks
for info in zip_file.infolist():
f = info.filename
if f[0] in '\\/' or '..' in f:
if f[0] in '\\/' or '..' in f or os.path.isabs(f):
logger.error(
"malformed (potentially insecure) filename (%s) "
"found in zip archive. Skipping file." % (f,)
Expand All @@ -387,6 +388,61 @@ def get_zip_archive(self, url, dirOffset=0):
info.filename = target[-1] + '/' if f[-1] == '/' else target[-1]
zip_file.extract(f, os.path.join(self._fname, *tuple(target[dirOffset:-1])))

def get_tar_archive(self, url, dirOffset=0):
if self._fname is None:
raise DeveloperError(
"target file name has not been initialized "
"with set_destination_filename"
)
if os.path.exists(self._fname) and not os.path.isdir(self._fname):
raise RuntimeError(
"Target directory (%s) exists, but is not a directory" % (self._fname,)
)

def filter_fcn(info):
# this mocks up the `tarfile` filter introduced in Python
# 3.12 and backported to later releases of Python (e.g.,
# 3.8.17, 3.9.17, 3.10.12, and 3.11.4)
f = info.name
if os.path.isabs(f) or '..' in f or f.startswith(('/', os.sep)):
logger.error(
"malformed or potentially insecure filename (%s). "
"Skipping file." % (f,)
)
return False
target = self._splitpath(f)
if len(target) <= dirOffset:
if not info.isdir():
logger.warning(
"Skipping file (%s) in tar archive due to dirOffset." % (f,)
)
return False
info.name = f = '/'.join(target[dirOffset:])
target = os.path.realpath(os.path.join(dest, f))
try:
if os.path.commonpath([target, dest]) != dest:
logger.error(
"potentially insecure filename (%s) resolves outside target "
"directory. Skipping file." % (f,)
)
return False
except ValueError:
# commonpath() will raise ValueError for paths that
# don't have anything in common (notably, when files are
# on different drives on Windows)
logger.error(
"potentially insecure filename (%s) resolves outside target "
"directory. Skipping file." % (f,)
)
return False
# Strip high bits & group/other write bits
info.mode &= 0o755
return True

with tarfile.open(fileobj=io.BytesIO(self.retrieve_url(url))) as TAR:
dest = os.path.realpath(self._fname)
TAR.extractall(dest, filter(filter_fcn, TAR.getmembers()))

def get_gzipped_binary_file(self, url):
if self._fname is None:
raise DeveloperError(
Expand Down
24 changes: 16 additions & 8 deletions pyomo/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
.. autosummary::

ExtendedEnumType
NamedIntEnum

Standard Enums:

Expand Down Expand Up @@ -130,7 +131,21 @@ def __new__(metacls, cls, bases, classdict, **kwds):
return super().__new__(metacls, cls, bases, classdict, **kwds)


class ObjectiveSense(enum.IntEnum):
class NamedIntEnum(enum.IntEnum):
"""An extended version of :py:class:`enum.IntEnum` that supports
creating members by name as well as value.

"""

@classmethod
def _missing_(cls, value):
for member in cls:
if member.name == value:
return member
return None


class ObjectiveSense(NamedIntEnum):
"""Flag indicating if an objective is minimizing (1) or maximizing (-1).

While the numeric values are arbitrary, there are parts of Pyomo
Expand All @@ -150,13 +165,6 @@ class ObjectiveSense(enum.IntEnum):
def __str__(self):
return self.name

@classmethod
def _missing_(cls, value):
for member in cls:
if member.name == value:
return member
return None


minimize = ObjectiveSense.minimize
maximize = ObjectiveSense.maximize
23 changes: 21 additions & 2 deletions pyomo/common/fileutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import os
import platform
import importlib.util
import subprocess
import sys

from . import envvar
Expand Down Expand Up @@ -375,9 +376,27 @@ def find_library(libname, cwd=True, include_PATH=True, pathlist=None):
if libname_base.startswith('lib') and _system() != 'windows':
libname_base = libname_base[3:]
if ext.lower().startswith(('.so', '.dll', '.dylib')):
return ctypes.util.find_library(libname_base)
lib = ctypes.util.find_library(libname_base)
else:
return ctypes.util.find_library(libname)
lib = ctypes.util.find_library(libname)
if lib and os.path.sep not in lib:
# work around https://github.com/python/cpython/issues/65241,
# where python does not return the absolute path on *nix
try:
libname = lib + ' '
with subprocess.Popen(
['/sbin/ldconfig', '-p'],
stdin=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
stdout=subprocess.PIPE,
env={'LC_ALL': 'C', 'LANG': 'C'},
) as p:
for line in os.fsdecode(p.stdout.read()).splitlines():
if line.lstrip().startswith(libname):
return os.path.realpath(line.split()[-1])
except:
pass
return lib


def find_executable(exename, cwd=True, include_PATH=True, pathlist=None):
Expand Down