diff --git a/.coin-or/projDesc.xml b/.coin-or/projDesc.xml
index dd009f994bf..0c8942ea955 100644
--- a/.coin-or/projDesc.xml
+++ b/.coin-or/projDesc.xml
@@ -107,7 +107,7 @@ Carl D. Laird, Chair, Pyomo Management Committee, claird at andrew dot cmu dot e
license file directly.
-->
- https://github.com/Pyomo/pyomo/blob/main/LICENSE.txt
+ https://github.com/Pyomo/pyomo/blob/main/LICENSE.md
@@ -227,8 +227,8 @@ Carl D. Laird, Chair, Pyomo Management Committee, claird at andrew dot cmu dot e
Use explicit overrides to disable use of automated
version reporting.
-->
- 6.4.2
- 6.4.2
+ 6.6.1
+ 6.6.1
@@ -287,7 +287,7 @@ Carl D. Laird, Chair, Pyomo Management Committee, claird at andrew dot cmu dot e
Any
- Python 3.7, 3.8, 3.9, 3.10
+ Python 3.7, 3.8, 3.9, 3.10, 3.11
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 00000000000..6d3e6401c5b
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,46 @@
+# PURPOSE: This file will ignore the included SHAs when
+# running `git blame`.
+# USAGE: To use this file, you can do one of the following:
+# 1) git blame --ignore-revs-file .git-blame-ignore-revs
+# 2) git config blame.ignoreRevsFile .git-blame-ignore-revs
+
+# Most of these will be ignoring application
+# of black to the repository
+49fb44e8acb90b3019dbc21241515ae9789b9798
+ce67d33762114de1bd31033b791ba38aca0360a4
+6e8e1e879aa33e8917ab9ecb46371031e39c49ee
+01d84aad72719f77270bc5fb15984663cc2692d3
+38d7587c51c518ebf77948de43f1e7e4714b4b70
+3fa9f893fc98e9c48de7652095ab815bb2a51310
+bbc492a8f88a09868ba24ef99b8550cfb700d563
+b991efa2916b8f2b1cdadde0f8bcc848b715db8f
+7302ab9b2cbbe0792b7227c1a65298935021b874
+c79e1e94f7ad28f6ddf83c3eec4db1071898b613
+d0f5081da6621e45df1bed1543ebcca2fee7d2cf
+bf37b8701ada7517193d9f270cc14235326ff95d
+8d69faf286c623f02f48ab3bf0aa76a39bed158c
+09687ab6297cb8cc2577030a8751d3b39470ef69
+3acc0f41b699b57d0020fdbe44078bf36631a093
+733289d6f80e2c7ea4d500be20919ffca80c452a
+7102497c843e0687c4817b78f3939b1e1fff18d1
+72b2dbfe34e59f60b2668e2b65bc1a439fb50b3e
+3d8a9a058a548cee7716fea47d9a0e0712acb52f
+ec2101738756789aa82a547013e9a345310409a7
+8f53d6c3d82a42bd2324e3def60f54802f6394c1
+2895da9ccb6248f41dcfe34ff23b9c5243ca2ff1
+5a58691b11b6265af22bf83c4c9a88d6e76ec079
+7c30d21fee6d305f30d3bc21fd7dff067951f662
+d1338fa2e9015f59a0505c1561d8e12924187fd1
+6ef8840eeb88978fa244c4837057e7a058756dda
+597df3cf410d84f9a9f2aa91b905ef02d60d3013
+f74ee5e4488e5df1dc6033226441c26e1d4a6d08
+765b2fd671952bccf7f3ea78821a9267436429e0
+4e811287b5695e92f8b860497f7f3336d10de787
+ab66ddc816fbc7748b8ff7f5ccf29ed248e8f808
+0b7b099f4578250b65cdba874dfd3a491e6007fe
+69aaac0180bd4bb2088086d412f31f4a592298dd
+ed13c8c65d6c3f56973887744be1c62a5d4756de
+0d93f98aa608f892df404ad8015885d26e09bb55
+63a3c602a00a2b747fc308c0571bbe33e55a3731
+363a16a609f519b3edfdfcf40c66d6de7ac135af
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 56271b333d2..66aff69938d 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -4,6 +4,9 @@ about: Suggest adding an enhancement of a current feature or a new feature in Py
labels: enhancement
---
+
+
+
## Summary
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f6da4169dc5..4bd8e88bfed 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,3 +1,12 @@
+
+
+
+
+
+
+
+
+
## Fixes # .
## Summary/Motivation:
diff --git a/.github/workflows/release_wheel_creation.yml b/.github/workflows/release_wheel_creation.yml
index a3a2f6f138b..2c71584c5b8 100644
--- a/.github/workflows/release_wheel_creation.yml
+++ b/.github/workflows/release_wheel_creation.yml
@@ -10,23 +10,28 @@ on:
description: Git Hash (Optional)
required: false
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
env:
PYOMO_SETUP_ARGS: --with-distributable-extensions
jobs:
manylinux:
- name: ${{ matrix.TARGET }}/wheel_creation
+ name: ${{ matrix.TARGET }}/${{ matrix.wheel-version }}_wheel_creation
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
+ wheel-version: ['cp37-cp37m', 'cp38-cp38', 'cp39-cp39', 'cp310-cp310', 'cp311-cp311']
os: [ubuntu-latest]
include:
- os: ubuntu-latest
TARGET: manylinux
- python-version: [3.7]
+ python-version: [3.8]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
@@ -35,10 +40,11 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install twine wheel setuptools pybind11
+ # TODO: Update the manylinux builder to next tagged release
- name: Build manylinux Python wheels
- uses: RalfG/python-wheels-manylinux-build@v0.4.0-manylinux2010_x86_64
+ uses: RalfG/python-wheels-manylinux-build@a1e012c58ed3960f81b7ed2759a037fb0ad28e2d
with:
- python-versions: 'cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310'
+ python-versions: ${{ matrix.wheel-version }}
build-requirements: 'cython pybind11'
package-path: ''
pip-wheel-args: ''
@@ -51,56 +57,11 @@ jobs:
run: |
sudo rm -rfv dist/*-linux_x86_64.whl
- name: Upload artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: manylinux-wheels
path: dist
- manylinuxaarch64:
- if: ${{ false }}
- name: ${{ matrix.TARGET }}/wheel_creation
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest]
- include:
- - os: ubuntu-latest
- TARGET: manylinuxaarch64
- python-version: [3.7]
- steps:
- - uses: actions/checkout@v2
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v3
- with:
- python-version: ${{ matrix.python-version }}
- - uses: docker/setup-qemu-action@v1
- name: Set up QEMU
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install twine wheel setuptools pybind11
- - name: Build manylinux Python wheels
- uses: RalfG/python-wheels-manylinux-build@v0.4.0-manylinux2014_aarch64
- with:
- python-versions: 'cp37-cp37m cp38-cp38 cp39-cp39'
- build-requirements: 'cython'
- package-path: ''
- pip-wheel-args: ''
- # When locally testing, --no-deps flag is necessary (PyUtilib dependency will trigger an error otherwise)
- - name: Consolidate wheels
- run: |
- sudo test -d dist || mkdir -v dist
- sudo find . -name \*.whl | grep -v /dist/ | xargs -n1 -i mv -v "{}" dist/
- - name: Delete linux wheels
- run: |
- sudo rm -rfv dist/*-linux_aarch64.whl
- - name: Upload artifact
- uses: actions/upload-artifact@v2
- with:
- name: manylinux-aarch64-wheels
- path: dist
-
generictarball:
name: ${{ matrix.TARGET }}
runs-on: ${{ matrix.os }}
@@ -111,9 +72,9 @@ jobs:
include:
- os: ubuntu-latest
TARGET: generic_tarball
- python-version: [3.7]
+ python-version: [3.8]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
@@ -126,7 +87,7 @@ jobs:
run: |
python setup.py --without-cython sdist --format=gztar
- name: Upload artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: generictarball
path: dist
@@ -141,9 +102,9 @@ jobs:
include:
- os: macos-latest
TARGET: osx
- python-version: [ 3.7, 3.8, 3.9, '3.10' ]
+ python-version: [ 3.7, 3.8, 3.9, '3.10', '3.11' ]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
@@ -157,7 +118,7 @@ jobs:
python setup.py --with-cython --with-distributable-extensions sdist --format=gztar bdist_wheel
- name: Upload artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: osx-wheels
path: dist
@@ -172,9 +133,9 @@ jobs:
include:
- os: windows-latest
TARGET: win
- python-version: [ 3.7, 3.8, 3.9, '3.10' ]
+ python-version: [ 3.7, 3.8, 3.9, '3.10', '3.11' ]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
@@ -191,7 +152,7 @@ jobs:
$env:PYTHONWARNINGS="ignore::UserWarning"
Invoke-Expression "python setup.py --with-cython --with-distributable-extensions sdist --format=gztar bdist_wheel"
- name: Upload artifact
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: win-wheels
path: dist
diff --git a/.github/workflows/test_branches.yml b/.github/workflows/test_branches.yml
index f986f6f93a5..6fd56708520 100644
--- a/.github/workflows/test_branches.yml
+++ b/.github/workflows/test_branches.yml
@@ -10,6 +10,10 @@ on:
description: Git Hash (Optional)
required: false
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
defaults:
run:
shell: bash -l {0}
@@ -19,10 +23,31 @@ env:
PYTHON_CORE_PKGS: wheel
PYPI_ONLY: z3-solver
PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels
- CACHE_VER: v210812.0
+ CACHE_VER: v221013.1
NEOS_EMAIL: tests@pyomo.org
+ SRC_REF: ${{ github.head_ref || github.ref }}
jobs:
+ lint:
+ name: lint/style-and-typos
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Pyomo source
+ uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+ - name: Black Formatting Check
+ run: |
+ pip install black
+ black . -S -C --check --diff --exclude examples/pyomobook/python-ch/BadIndent.py
+ - name: Spell Check
+ uses: crate-ci/typos@master
+ with:
+ config: ./.github/workflows/typos.toml
+
+
build:
name: ${{ matrix.TARGET }}/${{ matrix.python }}${{ matrix.other }}
runs-on: ${{ matrix.os }}
@@ -31,23 +56,23 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
- python: ['3.10']
+ python: ['3.11']
other: [""]
category: [""]
include:
- os: ubuntu-latest
- python: '3.10'
+ python: '3.11'
TARGET: linux
PYENV: pip
- os: macos-latest
- python: 3.8
+ python: '3.10'
TARGET: osx
PYENV: pip
- os: windows-latest
- python: 3.7
+ python: 3.9
TARGET: win
PYENV: conda
PACKAGES: glpk
@@ -70,15 +95,7 @@ jobs:
PACKAGES: mpi4py openmpi
- os: ubuntu-latest
- python: 3.7
- other: /slim
- slim: 1
- skip_doctest: 1
- TARGET: linux
- PYENV: pip
-
- - os: ubuntu-latest
- python: 3.8
+ python: '3.10'
other: /cython
setup_options: --with-cython
skip_doctest: 1
@@ -121,31 +138,32 @@ jobs:
# the 5 GB GitHub allows.
#
#- name: Conda package cache
- # uses: actions/cache@v2
+ # uses: actions/cache@v3
# if: matrix.PYENV == 'conda'
# id: conda-cache
# with:
# path: cache/conda
# key: conda-${{env.CACHE_VER}}.0-${{runner.os}}-${{matrix.python}}
- - name: Pip package cache
- uses: actions/cache@v2
- if: matrix.PYENV == 'pip'
- id: pip-cache
- with:
- path: cache/pip
- key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-${{matrix.python}}
+ #- name: Pip package cache
+ # uses: actions/cache@v3
+ # if: matrix.PYENV == 'pip'
+ # id: pip-cache
+ # with:
+ # path: cache/pip
+ # key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-${{matrix.python}}
- - name: OS package cache
- uses: actions/cache@v2
- if: matrix.TARGET != 'osx'
- id: os-cache
- with:
- path: cache/os
- key: pkg-${{env.CACHE_VER}}.0-${{runner.os}}
+ #- name: OS package cache
+ # uses: actions/cache@v3
+ # if: matrix.TARGET != 'osx'
+ # id: os-cache
+ # with:
+ # path: cache/os
+ # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}}
- name: TPL package download cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
+ if: ${{ ! matrix.slim }}
id: download-cache
with:
path: cache/download
@@ -196,7 +214,7 @@ jobs:
- name: Set up Python ${{ matrix.python }}
if: matrix.PYENV == 'pip'
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
@@ -206,7 +224,7 @@ jobs:
with:
auto-update-conda: true
python-version: ${{ matrix.python }}
- channels: conda-forge
+ channels: conda-forge,gurobi,ibmdecisionoptimization,fico-xpress
channel-priority: strict
# GitHub actions is very fragile when it comes to setting up various
@@ -247,7 +265,7 @@ jobs:
python -m pip install --cache-dir cache/pip pymysql || \
python -m pip install --cache-dir cache/pip pymysql
if test -z "${{matrix.slim}}"; then
- python -m pip install --cache-dir cache/pip cplex \
+ 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 \
-i https://pypi.gurobi.com gurobipy \
@@ -264,10 +282,19 @@ jobs:
- name: Install Python packages (conda)
if: matrix.PYENV == 'conda'
run: |
+ # Set up environment
mkdir -p $GITHUB_WORKSPACE/cache/conda
conda config --set always_yes yes
conda config --set auto_update_conda false
conda config --prepend pkgs_dirs $GITHUB_WORKSPACE/cache/conda
+ # Try to install mamba
+ conda install -q -y -n base conda-libmamba-solver || MAMBA_FAILED=1
+ if test -z "$MAMBA_FAILED"; then
+ echo "*** Activating the mamba environment solver ***"
+ conda config --set solver libmamba
+ fi
+ # Print environment info
+ echo "*** CONDA environment: ***"
conda info
conda config --show-sources
conda config --show channels
@@ -298,22 +325,34 @@ jobs:
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG"
fi
done
- conda install -q -y -c conda-forge $CONDA_DEPENDENCIES
+ echo "*** Install Pyomo dependencies ***"
+ conda install -q -y $CONDA_DEPENDENCIES
if test -z "${{matrix.slim}}"; then
- conda install -q -y -c ibmdecisionoptimization 'cplex>=12.10' \
+ echo "*** Install CPLEX ***"
+ conda install -q -y 'cplex>=12.10' docplex \
|| echo "WARNING: CPLEX Community Edition is not available"
- conda install -q -y -c gurobi gurobi \
+ echo "*** Install Gurobi ***"
+ conda install -q -y gurobi \
|| echo "WARNING: Gurobi is not available"
- conda install -q -y -c fico-xpress xpress \
+ echo "*** Install Xpress ***"
+ conda install -q -y xpress \
|| echo "WARNING: Xpress Community Edition is not available"
- for PKG in cyipopt pymumps; do
- conda install -q -y -c conda-forge $PKG \
+ for PKG in cyipopt pymumps scip; do
+ echo "*** Install $PKG ***"
+ conda install -q -y $PKG \
|| echo "WARNING: $PKG is not available"
done
# TODO: This is a hack to stop test_qt.py from running until we
# can better troubleshoot why it fails on GHA
for QTPACKAGE in qt pyqt; do
- conda remove $QTPACKAGE || echo "$QTPACKAGE not in this environment"
+ # Because conda is insane, removing packages can cause
+ # unrelated packages to be updated (breaking version
+ # specifications specified previously, e.g., in
+ # setup.py). There doesn't appear to be a good
+ # workaround, so we will just force-remove (recognizing
+ # that it may break other conda cruft).
+ conda remove --force-remove $QTPACKAGE \
+ || echo "$QTPACKAGE not in this environment"
done
fi
# Re-try Pyomo (optional) dependencies with pip
@@ -398,6 +437,8 @@ jobs:
echo "DYLD_LIBRARY_PATH=${env:DYLD_LIBRARY_PATH}:$GAMS_DIR" `
Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
$INSTALLER = "${env:DOWNLOAD_DIR}/gams_install.exe"
+ # We are pinning to 29.1.0 because a license is required for
+ # versions after this in order to run in demo mode.
$URL = "https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0"
if ( "${{matrix.TARGET}}" -eq "win" ) {
$URL = "$URL/windows/windows_x64_64.exe"
@@ -503,7 +544,8 @@ jobs:
run: |
echo ""
echo "Clone Pyomo-model-libraries..."
- git clone -b main https://github.com/Pyomo/pyomo-model-libraries.git
+ URL=https://github.com/Pyomo/pyomo-model-libraries.git
+ git clone -b ${SRC_REF##*/} $URL || git clone -b main $URL
echo ""
echo "Install Pyomo..."
echo ""
@@ -513,6 +555,15 @@ jobs:
echo ""
echo "PYOMO_CONFIG_DIR=${GITHUB_WORKSPACE}/config" >> $GITHUB_ENV
+ # this has to be done after Pyomo is installed because highspy
+ # depends on pyomo's find_library function
+ - name: Install HiGHS
+ if: ${{ ! matrix.slim }}
+ shell: bash
+ run: |
+ $PYTHON_EXE -m pip install --cache-dir cache/pip highspy \
+ || echo "WARNING: highspy is not available"
+
- name: Set up coverage tracking
run: |
if test "${{matrix.TARGET}}" == win; then
@@ -537,7 +588,7 @@ jobs:
echo ""
echo "Pyomo download-extensions"
echo ""
- pyomo download-extensions
+ pyomo download-extensions || exit 1
echo ""
echo "Pyomo build-extensions"
echo ""
@@ -553,12 +604,8 @@ jobs:
- name: Run Pyomo tests
if: matrix.mpi == 0
run: |
- CATEGORY=
- for cat in ${{matrix.category}}; do
- CATEGORY+=" -m $cat"
- done
$PYTHON_EXE -m pytest -v \
- -W ignore::Warning $CATEGORY \
+ -W ignore::Warning ${{matrix.category}} \
pyomo `pwd`/pyomo-model-libraries \
`pwd`/examples/pyomobook --junitxml="TEST-pyomo.xml"
@@ -586,7 +633,7 @@ jobs:
coverage xml -i
- name: Record build artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: ${{github.job}}_${{env.GHA_JOBGROUP}}-${{env.GHA_JOBNAME}}
path: |
@@ -606,7 +653,7 @@ jobs:
uses: actions/checkout@v3
- name: Set up Python 3.8
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: 3.8
@@ -640,7 +687,7 @@ jobs:
cover:
name: process-coverage-${{ matrix.TARGET }}
needs: build
- if: always() # run even if a build job fails
+ if: ${{ false }} # turn off for branches
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
@@ -661,20 +708,20 @@ jobs:
uses: actions/checkout@v3
# We need the source for .codecov.yml and running "coverage xml"
- - name: Pip package cache
- uses: actions/cache@v2
- id: pip-cache
- with:
- path: cache/pip
- key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-3.8
+ #- name: Pip package cache
+ # uses: actions/cache@v3
+ # id: pip-cache
+ # with:
+ # path: cache/pip
+ # key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-3.8
- name: Download build artifacts
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
path: artifacts
- name: Set up Python 3.8
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: 3.8
@@ -769,9 +816,10 @@ jobs:
- name: Upload codecov reports
if: github.repository_owner == 'Pyomo' || github.ref != 'refs/heads/main'
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3
with:
files: coverage.xml
+ token: ${{ secrets.PYOMO_CODECOV_TOKEN }}
name: ${{ matrix.TARGET }}
flags: ${{ matrix.TARGET }}
fail_ci_if_error: true
@@ -780,9 +828,10 @@ jobs:
if: |
hashFiles('coverage-other.xml') != '' &&
(github.repository_owner == 'Pyomo' || github.ref != 'refs/heads/main')
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3
with:
files: coverage-other.xml
+ token: ${{ secrets.PYOMO_CODECOV_TOKEN }}
name: ${{ matrix.TARGET }}/other
flags: ${{ matrix.TARGET }},other
fail_ci_if_error: true
diff --git a/.github/workflows/test_pr_and_main.yml b/.github/workflows/test_pr_and_main.yml
index beb874ebc69..9a8888cef1c 100644
--- a/.github/workflows/test_pr_and_main.yml
+++ b/.github/workflows/test_pr_and_main.yml
@@ -13,6 +13,10 @@ on:
description: Git Hash (Optional)
required: false
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
defaults:
run:
shell: bash -l {0}
@@ -22,19 +26,37 @@ env:
PYTHON_CORE_PKGS: wheel
PYPI_ONLY: z3-solver
PYPY_EXCLUDE: scipy numdifftools seaborn statsmodels
- CACHE_VER: v210812.0
+ CACHE_VER: v221013.1
NEOS_EMAIL: tests@pyomo.org
+ SRC_REF: ${{ github.head_ref || github.ref }}
jobs:
+ lint:
+ name: lint/style-and-typos
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Pyomo source
+ uses: actions/checkout@v3
+ - name: Black Formatting Check
+ run: |
+ pip install black
+ black . -S -C --check --diff --exclude examples/pyomobook/python-ch/BadIndent.py
+ - name: Spell Check
+ uses: crate-ci/typos@master
+ with:
+ config: ./.github/workflows/typos.toml
+
+
build:
name: ${{ matrix.TARGET }}/${{ matrix.python }}${{ matrix.other }}
+ needs: lint # the linter job is a prerequisite for PRs
runs-on: ${{ matrix.os }}
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
- python: [3.7, 3.8, 3.9, '3.10']
+ python: [3.7, 3.8, 3.9, '3.10', '3.11']
other: [""]
category: [""]
@@ -70,15 +92,15 @@ jobs:
PACKAGES: mpi4py openmpi
- os: ubuntu-latest
- python: 3.7
- other: /slim
- slim: 1
+ python: 3.11
+ other: /singletest
+ category: "-m 'neos or importtest'"
skip_doctest: 1
TARGET: linux
PYENV: pip
- os: ubuntu-latest
- python: 3.8
+ python: '3.10'
other: /cython
setup_options: --with-cython
skip_doctest: 1
@@ -86,10 +108,17 @@ jobs:
PYENV: pip
PACKAGES: cython
- - os: ubuntu-latest
+ - os: windows-latest
python: 3.8
- other: /singletest
- category: neos
+ other: /pip
+ skip_doctest: 1
+ TARGET: win
+ PYENV: pip
+
+ - os: ubuntu-latest
+ python: 3.7
+ other: /slim
+ slim: 1
skip_doctest: 1
TARGET: linux
PYENV: pip
@@ -107,13 +136,6 @@ jobs:
TARGET: linux
PYENV: pip
- - os: windows-latest
- python: 3.8
- other: /pip
- skip_doctest: 1
- TARGET: win
- PYENV: pip
-
steps:
- name: Checkout Pyomo source
uses: actions/checkout@v3
@@ -142,31 +164,32 @@ jobs:
# the 5 GB GitHub allows.
#
#- name: Conda package cache
- # uses: actions/cache@v2
+ # uses: actions/cache@v3
# if: matrix.PYENV == 'conda'
# id: conda-cache
# with:
# path: cache/conda
# key: conda-${{env.CACHE_VER}}.0-${{runner.os}}-${{matrix.python}}
- - name: Pip package cache
- uses: actions/cache@v2
- if: matrix.PYENV == 'pip'
- id: pip-cache
- with:
- path: cache/pip
- key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-${{matrix.python}}
+ #- name: Pip package cache
+ # uses: actions/cache@v3
+ # if: matrix.PYENV == 'pip'
+ # id: pip-cache
+ # with:
+ # path: cache/pip
+ # key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-${{matrix.python}}
- - name: OS package cache
- uses: actions/cache@v2
- if: matrix.TARGET != 'osx'
- id: os-cache
- with:
- path: cache/os
- key: pkg-${{env.CACHE_VER}}.0-${{runner.os}}
+ #- name: OS package cache
+ # uses: actions/cache@v3
+ # if: matrix.TARGET != 'osx'
+ # id: os-cache
+ # with:
+ # path: cache/os
+ # key: pkg-${{env.CACHE_VER}}.0-${{runner.os}}
- name: TPL package download cache
- uses: actions/cache@v2
+ uses: actions/cache@v3
+ if: ${{ ! matrix.slim }}
id: download-cache
with:
path: cache/download
@@ -217,7 +240,7 @@ jobs:
- name: Set up Python ${{ matrix.python }}
if: matrix.PYENV == 'pip'
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
@@ -227,7 +250,7 @@ jobs:
with:
auto-update-conda: true
python-version: ${{ matrix.python }}
- channels: conda-forge
+ channels: conda-forge,gurobi,ibmdecisionoptimization,fico-xpress
channel-priority: strict
# GitHub actions is very fragile when it comes to setting up various
@@ -268,7 +291,7 @@ jobs:
python -m pip install --cache-dir cache/pip pymysql || \
python -m pip install --cache-dir cache/pip pymysql
if test -z "${{matrix.slim}}"; then
- python -m pip install --cache-dir cache/pip cplex \
+ 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 \
-i https://pypi.gurobi.com gurobipy \
@@ -285,10 +308,19 @@ jobs:
- name: Install Python packages (conda)
if: matrix.PYENV == 'conda'
run: |
+ # Set up environment
mkdir -p $GITHUB_WORKSPACE/cache/conda
conda config --set always_yes yes
conda config --set auto_update_conda false
conda config --prepend pkgs_dirs $GITHUB_WORKSPACE/cache/conda
+ # Try to install mamba
+ conda install -q -y -n base conda-libmamba-solver || MAMBA_FAILED=1
+ if test -z "$MAMBA_FAILED"; then
+ echo "*** Activating the mamba environment solver ***"
+ conda config --set solver libmamba
+ fi
+ # Print environment info
+ echo "*** CONDA environment: ***"
conda info
conda config --show-sources
conda config --show channels
@@ -319,22 +351,34 @@ jobs:
CONDA_DEPENDENCIES="$CONDA_DEPENDENCIES $PKG"
fi
done
- conda install -q -y -c conda-forge $CONDA_DEPENDENCIES
+ echo "*** Install Pyomo dependencies ***"
+ conda install -q -y $CONDA_DEPENDENCIES
if test -z "${{matrix.slim}}"; then
- conda install -q -y -c ibmdecisionoptimization 'cplex>=12.10' \
+ echo "*** Install CPLEX ***"
+ conda install -q -y 'cplex>=12.10' docplex \
|| echo "WARNING: CPLEX Community Edition is not available"
- conda install -q -y -c gurobi gurobi \
+ echo "*** Install Gurobi ***"
+ conda install -q -y gurobi \
|| echo "WARNING: Gurobi is not available"
- conda install -q -y -c fico-xpress xpress \
+ echo "*** Install Xpress ***"
+ conda install -q -y xpress \
|| echo "WARNING: Xpress Community Edition is not available"
- for PKG in cyipopt pymumps; do
- conda install -q -y -c conda-forge $PKG \
+ for PKG in cyipopt pymumps scip; do
+ echo "*** Install $PKG ***"
+ conda install -q -y $PKG \
|| echo "WARNING: $PKG is not available"
done
# TODO: This is a hack to stop test_qt.py from running until we
# can better troubleshoot why it fails on GHA
for QTPACKAGE in qt pyqt; do
- conda remove $QTPACKAGE || echo "$QTPACKAGE not in this environment"
+ # Because conda is insane, removing packages can cause
+ # unrelated packages to be updated (breaking version
+ # specifications specified previously, e.g., in
+ # setup.py). There doesn't appear to be a good
+ # workaround, so we will just force-remove (recognizing
+ # that it may break other conda cruft).
+ conda remove --force-remove $QTPACKAGE \
+ || echo "$QTPACKAGE not in this environment"
done
fi
# Re-try Pyomo (optional) dependencies with pip
@@ -419,6 +463,8 @@ jobs:
echo "DYLD_LIBRARY_PATH=${env:DYLD_LIBRARY_PATH}:$GAMS_DIR" `
Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
$INSTALLER = "${env:DOWNLOAD_DIR}/gams_install.exe"
+ # We are pinning to 29.1.0 because a license is required for
+ # versions after this in order to run in demo mode.
$URL = "https://d37drm4t2jghv5.cloudfront.net/distributions/29.1.0"
if ( "${{matrix.TARGET}}" -eq "win" ) {
$URL = "$URL/windows/windows_x64_64.exe"
@@ -524,7 +570,8 @@ jobs:
run: |
echo ""
echo "Clone Pyomo-model-libraries..."
- git clone -b main https://github.com/Pyomo/pyomo-model-libraries.git
+ URL=https://github.com/Pyomo/pyomo-model-libraries.git
+ git clone -b ${SRC_REF##*/} $URL || git clone -b main $URL
echo ""
echo "Install Pyomo..."
echo ""
@@ -534,6 +581,15 @@ jobs:
echo ""
echo "PYOMO_CONFIG_DIR=${GITHUB_WORKSPACE}/config" >> $GITHUB_ENV
+ # this has to be done after Pyomo is installed because highspy
+ # depends on pyomo's find_library function
+ - name: Install HiGHS
+ if: ${{ ! matrix.slim }}
+ shell: bash
+ run: |
+ $PYTHON_EXE -m pip install --cache-dir cache/pip highspy \
+ || echo "WARNING: highspy is not available"
+
- name: Set up coverage tracking
run: |
if test "${{matrix.TARGET}}" == win; then
@@ -558,7 +614,7 @@ jobs:
echo ""
echo "Pyomo download-extensions"
echo ""
- pyomo download-extensions
+ pyomo download-extensions || exit 1
echo ""
echo "Pyomo build-extensions"
echo ""
@@ -574,12 +630,8 @@ jobs:
- name: Run Pyomo tests
if: matrix.mpi == 0
run: |
- CATEGORY=
- for cat in ${{matrix.category}}; do
- CATEGORY+=" -m $cat"
- done
$PYTHON_EXE -m pytest -v \
- -W ignore::Warning $CATEGORY \
+ -W ignore::Warning ${{matrix.category}} \
pyomo `pwd`/pyomo-model-libraries \
`pwd`/examples/pyomobook --junitxml="TEST-pyomo.xml"
@@ -607,7 +659,7 @@ jobs:
coverage xml -i
- name: Record build artifacts
- uses: actions/upload-artifact@v2
+ uses: actions/upload-artifact@v3
with:
name: ${{github.job}}_${{env.GHA_JOBGROUP}}-${{env.GHA_JOBNAME}}
path: |
@@ -620,6 +672,7 @@ jobs:
bare-python-env:
name: linux/3.8/bare-env
+ needs: lint # the linter job is a prerequisite for PRs
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
@@ -627,7 +680,7 @@ jobs:
uses: actions/checkout@v3
- name: Set up Python 3.8
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: 3.8
@@ -682,20 +735,20 @@ jobs:
uses: actions/checkout@v3
# We need the source for .codecov.yml and running "coverage xml"
- - name: Pip package cache
- uses: actions/cache@v2
- id: pip-cache
- with:
- path: cache/pip
- key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-3.8
+ #- name: Pip package cache
+ # uses: actions/cache@v3
+ # id: pip-cache
+ # with:
+ # path: cache/pip
+ # key: pip-${{env.CACHE_VER}}.0-${{runner.os}}-3.8
- name: Download build artifacts
- uses: actions/download-artifact@v2
+ uses: actions/download-artifact@v3
with:
path: artifacts
- name: Set up Python 3.8
- uses: actions/setup-python@v3
+ uses: actions/setup-python@v4
with:
python-version: 3.8
@@ -790,9 +843,10 @@ jobs:
- name: Upload codecov reports
if: github.repository_owner == 'Pyomo' || github.ref != 'refs/heads/main'
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3
with:
files: coverage.xml
+ token: ${{ secrets.PYOMO_CODECOV_TOKEN }}
name: ${{ matrix.TARGET }}
flags: ${{ matrix.TARGET }}
fail_ci_if_error: true
@@ -801,9 +855,10 @@ jobs:
if: |
hashFiles('coverage-other.xml') != '' &&
(github.repository_owner == 'Pyomo' || github.ref != 'refs/heads/main')
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3
with:
files: coverage-other.xml
+ token: ${{ secrets.PYOMO_CODECOV_TOKEN }}
name: ${{ matrix.TARGET }}/other
flags: ${{ matrix.TARGET }},other
fail_ci_if_error: true
diff --git a/.github/workflows/typos.toml b/.github/workflows/typos.toml
new file mode 100644
index 00000000000..23f94fc8afd
--- /dev/null
+++ b/.github/workflows/typos.toml
@@ -0,0 +1,43 @@
+[files]
+extend-exclude = ["*.eps"]
+
+[default.extend-words]
+# Ignore IDAES
+IDAES = "IDAES"
+idaes = "idaes"
+# Ignore datas
+Datas = "Datas"
+datas = "datas"
+# Ignore ND
+ND = "ND"
+nd = "nd"
+# Ignore INOUT
+inout = "inout"
+INOUT= "INOUT"
+# Ignore MAYBEE from examples
+MAYBEE = "MAYBEE"
+# Ignore conext for constraint-external
+conext = "conext"
+# Ignore AFE (affine expression) from MOSEK vernacular
+afe = "afe"
+# Ignore Fo from PyNumero tests
+Fo = "Fo"
+# Ignore ba from contrib
+ba = "ba"
+# Ignore complimentarity from interior_point
+complimentarity = "complimentarity"
+# Ignore from "number of inputs" from core
+nin = "nin"
+# sisser CUTEr instance
+sisser = "sisser"
+# LAF
+LAF = "LAF"
+# caf
+caf = "caf"
+# WRONLY
+WRONLY = "WRONLY"
+# Ignore the name Hax
+Hax = "Hax"
+# Big Sur
+Sur = "Sur"
+# AS NEEDED: Add More Words Below
diff --git a/.gitignore b/.gitignore
index cea795d481b..09069552990 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,7 @@ doc/OnlineDocs/**/*.spy
pyomo/dataportal/parse_table_datacmds.py
gurobi.log
-# Results from nosetests --with-coverage
+# Results from pytest --with-coverage
.coverage
*.cover
diff --git a/.jenkins.sh b/.jenkins.sh
index 36f950b9e9c..f31fef99377 100644
--- a/.jenkins.sh
+++ b/.jenkins.sh
@@ -11,7 +11,7 @@
#
# CATEGORY: the category to pass to pytest
#
-# TEST_SUITES: Paths (module or directory) to be passed to nosetests to
+# TEST_SUITES: Paths (module or directory) to be passed to pytest to
# run. (defaults to "pyomo '$WORKSPACE/pyomo-model-libraries'")
#
# SLIM: If nonempty, then the virtualenv will only have pip, setuptools,
@@ -86,9 +86,13 @@ if test -z "$MODE" -o "$MODE" == setup; then
echo "#"
echo "# Installing pyomo modules"
echo "#"
- pushd "$WORKSPACE/pyutilib" || echo "PyUtilib not found"
- python setup.py develop || echo "PyUtilib failed - skipping."
- popd
+ if test -d "$WORKSPACE/pyutilib"; then
+ pushd "$WORKSPACE/pyutilib"
+ python setup.py develop || echo "PyUtilib failed - skipping."
+ popd
+ else
+ echo "PyUtilib not found; skipping"
+ fi
pushd "$WORKSPACE/pyomo" || exit 1
python setup.py develop $PYOMO_SETUP_ARGS || exit 1
popd
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index ffcba32f400..dc77164f866 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -5,6 +5,11 @@
# Required
version: 2
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.8"
+
sphinx:
configuration: doc/OnlineDocs/conf.py
@@ -12,7 +17,6 @@ formats: all
# Set the version of Python and requirements required to build the docs
python:
- version: 3
install:
- method: pip
path: .
diff --git a/AUTHORS.txt b/AUTHORS.txt
deleted file mode 100644
index 789467f30a5..00000000000
--- a/AUTHORS.txt
+++ /dev/null
@@ -1 +0,0 @@
-See www.pyomo.org
diff --git a/CHANGELOG.txt b/CHANGELOG.md
similarity index 87%
rename from CHANGELOG.txt
rename to CHANGELOG.md
index 0cae7c774fd..1bf751268fa 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.md
@@ -1,9 +1,263 @@
-===============
Pyomo CHANGELOG
===============
+
+-------------------------------------------------------------------------------
+Pyomo 6.6.1 (30 May 2023)
+-------------------------------------------------------------------------------
+
+- General
+ - Update cmake builder for recent setuptools (#2847)
+ - Fixing minor formatting for 6.6.0 release changes (#2842)
+ - Silence deprecation warnings (#2854)
+- Core
+ - Update indentation handling in `config.StringFormatter` (#2853)
+ - Restore slice API broken by #2829 (#2849)
+ - Resolve handling of {}**0 in `LinearRepn`/`QuadraticRepn` (#2857)
+- Solver Interfaces
+ - NL writer: resolve error identifying vars in indexed SOS (#2852)
+ - Manage Gurobi environments in GurobiDirect (#2680)
+- Contributed Packages
+ - cp: fix handling fixed BooleanVars in logical-to-disjunctive walker (#2850)
+ - FBBT: Fix typo when handling GeneralExpression objects (#2848)
+ - MindtPy: add support for cyipopt (#2830)
+
+-------------------------------------------------------------------------------
+Pyomo 6.6.0 (24 May 2023)
+-------------------------------------------------------------------------------
+
+- General
+ - Remove `pyomo check`/`pyomo.checker` module (#2753)
+ - Improve formatting of docstrings generated from `ConfigDict` (#2754)
+ - Deprecate `add_docstring_list` (#2755)
+ - Reapply `black` to previously completed directories (#2775)
+ - Improve formatting for `DeveloperError`, `MouseTrap` messages (#2805)
+- Core
+ - Bugfix: component indexes specified as lists (#2765)
+ - Remove the use of weakrefs in `SymbolMap` (#2791)
+ - Improve conversions between Pyomo and Sympy expressions (#2806)
+ - Rework expression generation to leverage multiple dispatch (#2722)
+ - Improve robustness of `calculate_variable_from_constraint()` (#2812)
+ - Add support for infix Boolean logical operators (#2835)
+ - Improvements to Pyomo component iteration (#2829)
+- Documentation
+ - Copyright and Book Updates (#2750)
+ - Link documentation in incidence_analysis README (#2759)
+ - Update ReadtheDocs Configuration (#2780)
+ - Correct import in community.rst (#2792)
+ - Remove instructions for python <= 3.0 (#2822)
+- Solvers Interfaces
+ - NEOS: fix typo in `kestrelAMPL.kill()` argument (#2758)
+ - Better handling of mutable parameters in HiGHS interface (#2763)
+ - Improve linear data structure in NL writer (#2769)
+ - Bugfix for shared named expressions in NL writer (#2790)
+ - Resolve NPV constants in `LinearExpressions` in NL writer (#2811)
+ - GAMS/Baron: ensure negative numbers are parenthesized (#2833)
+ - Release LP version 2 (LPv2) writer (#2823, #2840)
+- Testing
+ - Rework Upload of Coverage Reports (#2761)
+ - Update constant for visitor tests for python 3.11.2 (#2799)
+ - Auto-Linting: Spelling Black Style Checker (#2800, #2818)
+ - Skip MOSEK tests on NEOS (due to unknown NEOS error) (#2839)
+- GDP
+ - Add `gdp.bound_pretransformation` (#2824)
+- Contributed Packages
+ - APPSI: Improve logging consistency across solvers (#2787)
+ - APPSI: Update `available` method in APPSI-Gurobi interface (#2828)
+ - DoE: Release version 2 (#2794)
+ - incidence_analysis: Remove strict usage of PyomoNLP (#2752)
+ - incidence_analysis: Test `IndexedBlock` (#2789)
+ - incidence_analysis: Use standard repn for incidence graph generation (#2834)
+ - Parmest: Update for pandas 2.0.0 release (#2795)
+ - piecewise: Add contrib.piecewise package (#2708, #2768, #2766, #2797, #2798,
+ #2826)
+ - PyNumero: Refactor CyIpopt interface to subclass `cyipopt.Problem` (#2760)
+ - PyNumero: Fix CyIpopt interface when `load_solutions=False` (#2820)
+ - PyROS: Fixes to PyROS Separation Routine (#2815)
+ - PyROS: Fixes to Coefficient Matching and Timing Functionalities (#2837)
+
+-------------------------------------------------------------------------------
+Pyomo 6.5.0 (16 Feb 2023)
+-------------------------------------------------------------------------------
+
+- General
+ - Apply `black` to enforce PEP8 standards in certain modules (#2737, #2738,
+ #2733, #2732, #2731, #2728, #2730, #2729, #2720, #2721, #2719, #2718)
+ - Add Developers' call information to README (#2665)
+ - Deprecate `pyomo.checker` module (#2734)
+ - Warn when infeasibility tools will not log output (#2666)
+ - Separate identification from logging in `pyomo.util.infeasible.log_*` (#2669)
+ - Convert subprocess timeout parameters to module attributes (#2672)
+ - Resolve consistency issues in the Bunch class (#2685)
+ - Remove GSL downloader from `download-extensions` (#2725)
+ - Update enhancement GitHub issue template to link to wiki (#2739)
+ - Add deprecation warning to `pyomo` command (#2740)
+ - Require `version=` for all deprecation utilities (#2744)
+ - Fix `pyomo --version` version string (#2743)
+- Core
+ - Fix minor typo in set.py (#2679)
+ - Fix bugs in scaling transformation (#2678)
+ - Rework handling of 'dimensionless' units in Pyomo (#2691)
+- Solver Interfaces
+ - Switch default NL writer to nlv2 and bug fixes (#2676, #2710, #2726)
+ - Enable MOSEK10 warm-start flag and relocate var initialization (#2647)
+ - Fix handling of POW in Baron writer (#2693)
+ - Update GAMS license check to avoid exception when not available (#2697)
+- Documentation
+ - Fix incorrect documentation for sending options to solvers (#2688)
+ - Fix Sphinx warnings (#2712)
+ - Document Python Version Support policy (#2735)
+ - Document deprecation and removal of functionality (#2741)
+ - Document docstring formatting requirements (#2742)
+- Testing
+ - Skip failing Baron tests (#2694)
+ - Remove residual `nose` references (#2736)
+ - Update GHA setup-python version (#2705)
+ - Improve GHA conda setup performance (#2701)
+ - Add unit test for QCQO problems with MOSEK (#2682)
+- DAE
+ - Fix typo in `__init__.py` (#2683)
+ - Add `active` filter to flattener (#2643)
+- GDP
+ - Add GDP-to-MIP transformation base class (#2687)
+- Contributed Packages
+ - DoE: New module for model-based design of experiments (#2294, #2711, #2527)
+ - FBBT: Add tolerances to tests (#2675)
+ - GDPopt: Switch a LBB test to use Gurobi as MINLP solver (#2686)
+ - incidence_analysis: Add `plot` method to `IncidenceGraphInterface` (#2716)
+ - incidence_analysis: Refactor to cache a graph instead of a matrix (#2715)
+ - incidence_analysis: Add documentation and update API (#2727, #2745)
+ - incidence_analysis: Add logging solve_strongly_connected_components (#2723)
+ - MindtPy: Refactor to improve extensibility and maintainability (#2654)
+ - Parmest: Suppress mpi-sppy output in import (#2692)
+ - PyNumero: Add tee argument to Pyomo-SciPy square solvers (#2668)
+ - PyNumero: Support implicit function solvers in ExternalPyomoModel (#2652)
+ - PyROS: Fix user_time and wallclock_time bug (#2670)
+ - PyROS: More judicious enforcement of PyROS Solver time limit (#2660, #2706)
+ - PyROS: Update documentation (#2698, #2707)
+ - PyROS: Adjust routine for loading DR polishing model solutions (#2700)
+ - Viewer: Update to support PySide6 and display units and domain (#2689)
+
+-------------------------------------------------------------------------------
+Pyomo 6.4.4 (9 Dec 2022)
+-------------------------------------------------------------------------------
+
+- General
+ - Convert `txt` to `md` files (`CHANGELOG`, `LICENSE`, `RELEASE`) (#2635)
+ - Parallelize build of manylinux wheels (#2636)
+ - Update source for Jenkins status badge (#2639, #2640)
+ - Update relocated_module_attribute to work with cythonized modules (#2644)
+ - Add utility methods to HierarchicalTimer (#2651)
+- Core
+ - Fix preservation of stale flags through clone/pickle (#2633)
+ - Add support for local suffixes in scaling transformation (#2619)
+- Solver Interfaces
+ - Fix handling of nonconvex MIQCP problems in Xpress (#2625)
+- Testing
+ - Update GitHub actions to cancel jobs when new changes are pushed (#2634)
+ - Remove requirement for a `pyutilib` directory in Jenkins driver (#2637)
+ - Enable GitHub actions build on Windows Python 3.11 (#2638)
+ - Add build services infrastructure status badge (#2646)
+ - Add version upper bound on MOSEK warmstart test skip (#2649)
+ - Improve compare.py handling of nosetests/pytest output (#2661)
+- GDP
+ - Add option to use multiple-bigm only on bound constraints (#2624)
+ - Add logical_to_disjunctive and replace uses of logical_to_linear (#2627)
+- Contributed Packages
+ - FBBT: Fix bug with ExternalFunction expressions (#2657)
+ - PyROS: Fix uncertain param bounds evaluation for FactorModelSet (#2620)
+ - PyROS: Add origin attribute to BudgetSet (#2645)
+ - PyROS: Fix UncertaintySet.bounding_problem method (#2659)
+
-------------------------------------------------------------------------------
-Pyomo 6.4.2 17 Aug 2022
+Pyomo 6.4.3 (28 Nov 2022)
+-------------------------------------------------------------------------------
+
+- General
+ - Update PauseGC to work in nested contexts (#2507)
+ - Simplify deepcopy/pickle logic to speed up model clone (#2510)
+ - Fix generate_standard_repn to handle unexpected NPV expressions (#2511)
+ - Add thread safe proxies for PauseGC, TempFileManager singletons (#2514)
+ - Fix ConstructionTimer bug for components indexed by nonfinite sets (#2518)
+ - Add calculate_variable_from_constraint differentiation mode option (#2549)
+ - Update URL for downloading GSL and GJH (#2556, #2588)
+ - Update logic for retrying failed downloads (#2569)
+ - Add support and testing for Python 3.11 (#2596, #2618)
+ - Update deprecation utilities to improve user messages (#2606)
+- Core
+ - Refactor expression hierarchy, add RelationalExpression base class (#2499)
+ - Support cloning individual blocks (#2504)
+ - Block performance improvements (#2508)
+ - Add support for creating a slice to a single ComponentData object (#2509)
+ - Fix missing import of value in pyomo.core.base.external (#2525)
+ - Improve handling of restricted words on Blocks (#2535)
+ - Improve Reference() performance (#2537)
+ - Fix mapping gradient/hessian for external functions with string args (#2539)
+ - Fix bug for sum_product(Var, Param, Param) (#2551)
+ - Add deprecation path for expression objects moved to relational_expr (#2554)
+ - Exception when setting value of Expression to non-numeric expression (#2567)
+ - Improve deepcopy performance (#2628)
+- Documentation
+ - Fix examples in working_models.rst (#2502)
+- Solver Interfaces
+ - Improve SCIP results object (#2462)
+ - Improve warning message when LP writer raises KeyError (#2497)
+ - Fix Gurobi work limit bug (#2530)
+ - Updates and fixes for the NLv2 writer (#2540, #2622, #2568)
+ - Fix Xpress when stopped due to MAXTIME or MAXNODES (#2553)
+ - Add support for MOSEK 10 affine conic constraints (#2557)
+ - Fix missing explicit space in GAMS put command (#2578)
+ - Fix GAMS logfile storage location (#2580)
+ - LP writer performance improvements (#2583, #2585)
+ - Update handling of MOSEK Env and Python module (#2591)
+ - Release MOSEK license when optimize raises a mosek.Error (#2593)
+ - Update list of allowable characters in CPLEX filenames (#2597)
+- Testing
+ - Update performance driver to be usable outside of Pyomo (#2505)
+ - Update the performance test driver (#2538)
+ - Reduce amount of environment code cached in GitHub actions (#2565)
+ - Update GitHub actions versions from v2 to v3 (#2566)
+ - Allow nan to compare equal in assertStructuredAlmostEqual() (#2582)
+ - Add test utilities for comparing expressions (#2590)
+ - Skip a test in MOSEK 10 due to a bug in warm starting MIQPs (#2614)
+ - Update skipped CBC test that works with CBC 2.10.6 (#2615)
+ - Add SCIP to GitHub actions environment (#2602)
+- GDP
+ - Use OrderedSet instead of list in GDPTree to improve performance (#2516)
+ - Reduce calls to logical_to_linear in GDP transformations (#2519)
+ - Add utility for gathering BigM values after transformation (#2520)
+ - Add tighter logical constraints in transformations of nested GDPs (#2550)
+ - Fix pickling of transformed GDP models (#2576)
+ - Add multiple-bigM transformation (#2592)
+ - Improve performance of BigM transformation (#2605)
+ - Remove weakref mapping Disjunctions to their algebraic_constraint (#2617)
+- Contributed Packages
+ - APPSI: Fix exception raised by appsi_gurobi during Python shutdown (#2498)
+ - APPSI: Improve handling of Gurobi results (#2517)
+ - APPSI: Add interface to HiGHS solver (#2561)
+ - APPSI: Only release Gurobi license after deleting all instances (#2599)
+ - APPSI: Patch IntEnum to preserve pre-3.11 formatting (#2607)
+ - CP: New package for constraint programming (#2570, #2612)
+ - GDPopt: Add warning when reporting results from LBB (#2534)
+ - GDPopt: Delete dummy objective when we're done using it (#2552)
+ - GDPopt: Add enumerate solution approach (#2559, #2575)
+ - IIS: Add package for computing the IIS of an infeasible Pyomo model (#2512)
+ - MindtPy: Fix bug in termination condition (#2587)
+ - MindtPy: Fix bug in checking absolute and relative gap (#2608)
+ - MPC: Data structures/utils for rolling horizon dynamic optimization (#2477)
+ - Parmest: Solve square problem to initialize regression problem (#2438)
+ - Parmest: Return ContinuousSet values from theta_est() (#2464)
+ - PyNumero: Fix NumPy deprecation warnings (#2521)
+ - PyNumero: Add interfaces to SciPy square solvers (#2523)
+ - PyNumero: Check AmplInterface availability in SciPy solver tests (#2594)
+ - PyNumero: Add ProjectedExtendedNLP class (#2601)
+ - PyNumero: Add interface to SciPy scalar Newton solver (#2603)
+ - PyROS: Rewrite UncertaintySet docstrings/improve validation (#2488)
+ - PyROS: Updates to subproblem initialization and solver call routines (#2515)
+ - PyROS: Fix collection of sub-solver solve times (#2543)
+
+-------------------------------------------------------------------------------
+Pyomo 6.4.2 (17 Aug 2022)
-------------------------------------------------------------------------------
- General
@@ -62,7 +316,7 @@ Pyomo 6.4.2 17 Aug 2022
- PyROS: Update exception handling parsing BARON lower/upper bounds (#2486)
-------------------------------------------------------------------------------
-Pyomo 6.4.1 13 May 2022
+Pyomo 6.4.1 (13 May 2022)
-------------------------------------------------------------------------------
- General
@@ -100,7 +354,7 @@ Pyomo 6.4.1 13 May 2022
(#2353, #2371)
-------------------------------------------------------------------------------
-Pyomo 6.4.0 16 Mar 2022
+Pyomo 6.4.0 (16 Mar 2022)
-------------------------------------------------------------------------------
- General
@@ -124,11 +378,11 @@ Pyomo 6.4.0 16 Mar 2022
constant expressions (#2324)
- PyNumero: Improve coverage of mpi block matrix tests (#2318)
- PyNumero: Skip bound/domain validation in ExternalPyomoModel (#2323)
- - PyNumero: Remove deprecated useage of numpy.bool (#2339)
+ - PyNumero: Remove deprecated usage of numpy.bool (#2339)
- PyROS: Fix variable default initialization (#2331)
-------------------------------------------------------------------------------
-Pyomo 6.3.0 23 Feb 2022
+Pyomo 6.3.0 (23 Feb 2022)
-------------------------------------------------------------------------------
ADVANCE CHANGE NOTICE
@@ -208,7 +462,7 @@ ADVANCE CHANGE NOTICE
- TrustRegion: New implementation of Trust Region Framework (#2238, #2279)
-------------------------------------------------------------------------------
-Pyomo 6.2 17 Nov 2021
+Pyomo 6.2 (17 Nov 2021)
-------------------------------------------------------------------------------
- General
@@ -280,7 +534,7 @@ Pyomo 6.2 17 Nov 2021
- PyROS: Add uncertain variable bounds detection (#2159)
-------------------------------------------------------------------------------
-Pyomo 6.1.2 20 Aug 2021
+Pyomo 6.1.2 (20 Aug 2021)
-------------------------------------------------------------------------------
- General
@@ -295,14 +549,14 @@ Pyomo 6.1.2 20 Aug 2021
- MindtPy: Support gurobi_persistent in LP/NLP-based B&B algorithm (#2071)
-------------------------------------------------------------------------------
-Pyomo 6.1.1 17 Aug 2021
+Pyomo 6.1.1 (17 Aug 2021)
-------------------------------------------------------------------------------
- General
- Adding missing __init__.py files across Pyomo (#2086)
-------------------------------------------------------------------------------
-Pyomo 6.1 17 Aug 2021
+Pyomo 6.1 (17 Aug 2021)
-------------------------------------------------------------------------------
- General
@@ -364,7 +618,7 @@ Pyomo 6.1 17 Aug 2021
-------------------------------------------------------------------------------
-Pyomo 6.0.1 4 Jun 2021
+Pyomo 6.0.1 (4 Jun 2021)
-------------------------------------------------------------------------------
- General
@@ -379,7 +633,7 @@ Pyomo 6.0.1 4 Jun 2021
- GDPopt: Fix implicit conversion warnings (#2002)
-------------------------------------------------------------------------------
-Pyomo 6.0 20 May 2021
+Pyomo 6.0 (20 May 2021)
-------------------------------------------------------------------------------
BACKWARDS COMPATIBILITY WARNINGS
@@ -508,7 +762,7 @@ BACKWARDS COMPATIBILITY WARNINGS
- MindtPy: general improvements and add feasibility pump (#1847)
-------------------------------------------------------------------------------
-Pyomo 5.7.3 29 Jan 2021
+Pyomo 5.7.3 (29 Jan 2021)
-------------------------------------------------------------------------------
ADVANCE CHANGE NOTICE:
@@ -555,7 +809,7 @@ ADVANCE CHANGE NOTICE:
- Parmest: update pairwise plot to use the covariance matrix (#1774)
-------------------------------------------------------------------------------
-Pyomo 5.7.2 17 Dec 2020
+Pyomo 5.7.2 (17 Dec 2020)
-------------------------------------------------------------------------------
- General
@@ -645,7 +899,7 @@ Pyomo 5.7.2 17 Dec 2020
misc updates (#1632, #1653, #1610, #1667, #1681, #1705, #1724)
-------------------------------------------------------------------------------
-Pyomo 5.7.1 15 Sep 2020
+Pyomo 5.7.1 (15 Sep 2020)
-------------------------------------------------------------------------------
- General
@@ -707,7 +961,7 @@ Pyomo 5.7.1 15 Sep 2020
- Add integer arithmetic option to FME transformation (#1594)
-------------------------------------------------------------------------------
-Pyomo 5.7.0 19 Jun 2020
+Pyomo 5.7.0 (19 Jun 2020)
-------------------------------------------------------------------------------
- General
@@ -793,13 +1047,13 @@ Pyomo 5.7.0 19 Jun 2020
- Add basic interior point algorithm based on PyNumero (#1450, #1505, #1495)
-------------------------------------------------------------------------------
-Pyomo 5.6.9 18 Mar 2020
+Pyomo 5.6.9 (18 Mar 2020)
-------------------------------------------------------------------------------
- General
- Fix bug and improve output formatting in pyomo.util.infeasible (#1226, #1234)
- Add 'version' and 'remove_in' arguments to deprecation_warning (#1231)
- - Change NoArgumentGiven to a class and standardize useage (#1236)
+ - Change NoArgumentGiven to a class and standardize usage (#1236)
- Update GSL URL to track change in AMPL SSL certificate (#1245)
- Clean up setup.py (#1227)
- Remove legacy build/test/distribution scripts (#1263)
@@ -819,7 +1073,7 @@ Pyomo 5.6.9 18 Mar 2020
- Raise error on failed Param validation (#1272)
- Fix return value for component decorator (#1296)
- Change mult. order in taylor_series_expansion for numpy compatibility (#1329)
- - Deprecate 'Any' being the defalt Param domain (#1266)
+ - Deprecate 'Any' being the default Param domain (#1266)
- Solver Interfaces
- Update CPLEX direct interface to support CPLEX 12.10 (#1276)
- Shorten GAMS ShortNameLabeler symbols (#1338)
@@ -863,7 +1117,7 @@ Pyomo 5.6.9 18 Mar 2020
- Fix Benders MPI logic bug and expand parallel test coverage (#1278)
-------------------------------------------------------------------------------
-Pyomo 5.6.8 13 Dec 2019
+Pyomo 5.6.8 (13 Dec 2019)
-------------------------------------------------------------------------------
- General
@@ -896,7 +1150,7 @@ Pyomo 5.6.8 13 Dec 2019
- Add test skipping to trust region tests requiring IPOPT (#1220)
-------------------------------------------------------------------------------
-Pyomo 5.6.7 7 Nov 2019
+Pyomo 5.6.7 (7 Nov 2019)
-------------------------------------------------------------------------------
- General
@@ -942,7 +1196,7 @@ Pyomo 5.6.7 7 Nov 2019
- Add documentation for user interface to LinearExpression (#1120)
-------------------------------------------------------------------------------
-Pyomo 5.6.6 21 Jun 2019
+Pyomo 5.6.6 (21 Jun 2019)
-------------------------------------------------------------------------------
- Core
@@ -959,7 +1213,7 @@ Pyomo 5.6.6 21 Jun 2019
support for AbstractModels (#955, #1054, #1066)
-------------------------------------------------------------------------------
-Pyomo 5.6.5 10 Jun 2019
+Pyomo 5.6.5 (10 Jun 2019)
-------------------------------------------------------------------------------
- General
@@ -972,14 +1226,14 @@ Pyomo 5.6.5 10 Jun 2019
- Benders cut generator component (#1028)
-------------------------------------------------------------------------------
-Pyomo 5.6.4 24 May 2019
+Pyomo 5.6.4 (24 May 2019)
-------------------------------------------------------------------------------
- General
- Resolve project description rendering on PyPI
-------------------------------------------------------------------------------
-Pyomo 5.6.3 24 May 2019
+Pyomo 5.6.3 (24 May 2019)
-------------------------------------------------------------------------------
- General
@@ -1005,7 +1259,7 @@ Pyomo 5.6.3 24 May 2019
- GDPbb improvements and cleanup (#982)
-------------------------------------------------------------------------------
-Pyomo 5.6.2 1 May 2019
+Pyomo 5.6.2 (1 May 2019)
-------------------------------------------------------------------------------
- General
@@ -1062,7 +1316,7 @@ Pyomo 5.6.2 1 May 2019
- Network updates
- Fix sequential decomposition when ports contain References (#975)
- Contributed Packages
- - ParmEst updates to make API more flexible, updated examples, documentation
+ - Parmest updates to make API more flexible, updated examples, documentation
and tests (#814)
- GDPopt algorithm enhancements, cut generation bug fix, add example
to tests, time limit option support (#805, #826, #852, #970)
@@ -1090,7 +1344,7 @@ Pyomo 5.6.2 1 May 2019
- Show how to activate/deactivate constraints (#932)
-------------------------------------------------------------------------------
-Pyomo 5.6.1 18 Jan 2019
+Pyomo 5.6.1 (18 Jan 2019)
-------------------------------------------------------------------------------
- General
@@ -1106,7 +1360,7 @@ Pyomo 5.6.1 18 Jan 2019
- Add support for RangeSet in GDP transformations (#803)
-------------------------------------------------------------------------------
-Pyomo 5.6 19 Dec 2018
+Pyomo 5.6 (19 Dec 2018)
-------------------------------------------------------------------------------
- General
@@ -1189,7 +1443,7 @@ Pyomo 5.6 19 Dec 2018
- PySP updates
- Python 3.7 support (#463)
- Fix bugs in finance example (#564, #578)
- - Added a wrapper for PySP to create a scripting interace (#689, #727, #737)
+ - Added a wrapper for PySP to create a scripting interface (#689, #727, #737)
- Bug fixes (#736, #788)
- New packages:
- DataPortal:
@@ -1206,7 +1460,7 @@ Pyomo 5.6 19 Dec 2018
- New packages:
- Preprocessing transformation for variable aggregation (#533, #617)
- Compute disjunctive variable bounds (#481)
- - ParmEst package for parameter estimation (#706, #733, #769, #781)
+ - Parmest package for parameter estimation (#706, #733, #769, #781)
- PyNumero package for numerical optimization (#725, #775)
- sensitivity_toolbox for interfacing with sIPOPT (#766)
- PETSc AMPL wrapper (#774)
@@ -1231,14 +1485,14 @@ Pyomo 5.6 19 Dec 2018
- Update examples (#436)
-------------------------------------------------------------------------------
-Pyomo 5.5.1 26 Oct 2018
+Pyomo 5.5.1 (26 Oct 2018)
-------------------------------------------------------------------------------
- General
- Adding support for Python 3.7
-------------------------------------------------------------------------------
-Pyomo 5.5 14 Apr 2018
+Pyomo 5.5 (14 Apr 2018)
-------------------------------------------------------------------------------
- Move preprocessing transformations to contrib (#426)
@@ -1273,25 +1527,25 @@ Pyomo 5.5 14 Apr 2018
- Documentation updates (#425)
-------------------------------------------------------------------------------
-Pyomo 5.4.3 2 Mar 2018
+Pyomo 5.4.3 (2 Mar 2018)
-------------------------------------------------------------------------------
- Another fix in the release process.
-------------------------------------------------------------------------------
-Pyomo 5.4.2 2 Mar 2018
+Pyomo 5.4.2 (2 Mar 2018)
-------------------------------------------------------------------------------
- Misc fix in the release process.
-------------------------------------------------------------------------------
-Pyomo 5.4.1 28 Feb 2018
+Pyomo 5.4.1 (28 Feb 2018)
-------------------------------------------------------------------------------
- Misc version increment to support pypi idiosyncrasies.
-------------------------------------------------------------------------------
-Pyomo 5.4 28 Feb 2018
+Pyomo 5.4 (28 Feb 2018)
-------------------------------------------------------------------------------
=======
@@ -1337,7 +1591,7 @@ Pyomo 5.4 28 Feb 2018
- Logging overhaul and support for timing concrete models (#245)
-------------------------------------------------------------------------------
-Pyomo 5.3 21 Oct 2017
+Pyomo 5.3 (21 Oct 2017)
-------------------------------------------------------------------------------
- Removed testing for Python 3.4
@@ -1361,7 +1615,7 @@ Pyomo 5.3 21 Oct 2017
- Tracking changes in pyutilib.th
-------------------------------------------------------------------------------
-Pyomo 5.2 14 May 2017
+Pyomo 5.2 (14 May 2017)
-------------------------------------------------------------------------------
- Resolved timeout issues running NEOS solvers
@@ -1406,13 +1660,13 @@ Pyomo 5.2 14 May 2017
- Updating the bilinear transform to avoid creating a Set `index`.
-------------------------------------------------------------------------------
-Pyomo 5.1.1 8 Jan 2017
+Pyomo 5.1.1 (8 Jan 2017)
-------------------------------------------------------------------------------
- Monkeypatch to resolve (#95)
-------------------------------------------------------------------------------
-Pyomo 5.1 4 Jan 2017
+Pyomo 5.1 (4 Jan 2017)
-------------------------------------------------------------------------------
- Added a CONOPT plugin to handle a custom SOL file output (#88)
@@ -1436,13 +1690,13 @@ Pyomo 5.1 4 Jan 2017
- Removed support for OpenOpt
-------------------------------------------------------------------------------
-Pyomo 5.0.1 16 Nov 2016
+Pyomo 5.0.1 (16 Nov 2016)
-------------------------------------------------------------------------------
- Updating PyUtilib dependency
-------------------------------------------------------------------------------
-Pyomo 5.0 15 Nov 2016
+Pyomo 5.0 (15 Nov 2016)
-------------------------------------------------------------------------------
- Added examples used in the Pyomo book to the Pyomo software repos
@@ -2107,7 +2361,7 @@ Pyomo 2.4.3199
- ASL solver interface can now be specified with the form
--solver=asl:PICO
-- Usability enchancements
+- Usability enhancements
- Numerous bug fixes.
- Updated messages to provide clearer indication of modeling errors
@@ -2330,7 +2584,7 @@ Pyomo 1.1
indexed by one or more sets.
- A revision to Pyomo semantics. Now, expressions are not evaluated
- when performing arithemetic operations (plus, times, etc).
+ when performing arithmetic operations (plus, times, etc).
- A major rework of how component attributes are managed for
NumericValue objects and subclasses of this class. This was driven
@@ -2390,11 +2644,11 @@ Pyomo 1.1
the validation function: we want to allow the validation function to
refer to the value as if it were set.
- - Depricating the use of the expression factory for algebraic expression
+ - Deprecating the use of the expression factory for algebraic expression
types. These are now launched directly from the generate_expression()
function.
- - Adding support for specifing options when launching solvers. For example:
+ - Adding support for specifying options when launching solvers. For example:
results = self.pico.solve(currdir+"bell3a.mps", options="maxCPUMinutes=0.1")
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6c7b8fdb3a1..5907c0d36ff 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,8 @@ Contributing to Pyomo
Online Documentation
--------------------
-Detailed contribution guidelines may be found in our [online documentation](http://pyomo.readthedocs.io/en/latest/contribution_guide.html).
+Detailed contribution guidelines may be found in our
+[online documentation](http://pyomo.readthedocs.io/en/latest/contribution_guide.html).
Pull Requests
-------------
diff --git a/LICENSE.txt b/LICENSE.md
similarity index 99%
rename from LICENSE.txt
rename to LICENSE.md
index 103764561df..192d315e4b5 100644
--- a/LICENSE.txt
+++ b/LICENSE.md
@@ -1,3 +1,6 @@
+LICENSE
+=======
+
Copyright (c) 2008-2022 National Technology and Engineering Solutions of
Sandia, LLC . Under the terms of Contract DE-NA0003525 with National
Technology and Engineering Solutions of Sandia, LLC , the U.S.
diff --git a/MANIFEST.in b/MANIFEST.in
index 3e677d0574e..c28ab72d11a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,2 +1,2 @@
include README.md
-include LICENSE.txt
+include LICENSE.md
diff --git a/README.md b/README.md
index 655523d8741..b35ac20ed98 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
[](https://github.com/Pyomo/pyomo/actions?query=event%3Apush+workflow%3A%22GitHub+CI%22)
-[](https://jenkins-srn.sandia.gov/job/Pyomo_trunk)
+[](https://pyomo-jenkins.sandia.gov/)
[](https://codecov.io/gh/Pyomo/pyomo)
[](http://pyomo.readthedocs.org/en/latest/)
+[](https://pyomo-jenkins.sandia.gov/)
[](https://github.com/pyomo/pyomo/graphs/contributors)
[](https://github.com/pyomo/pyomo/pulls?q=is:pr+is:merged)
@@ -26,6 +27,7 @@ including:
- Generalized disjunctive programming
- Differential algebraic equations
- Mathematical programming with equilibrium constraints
+ - Constraint programming
Pyomo supports analysis and scripting within a full-featured programming
language. Further, Pyomo has also proven an effective framework for
@@ -44,13 +46,19 @@ subproblems using Python parallel communication libraries.
Pyomo was formerly released as the Coopr software library.
-Pyomo is available under the BSD License, see the LICENSE.txt file.
+Pyomo is available under the BSD License - see the
+[`LICENSE.md`](https://github.com/Pyomo/pyomo/blob/main/LICENSE.md) file.
Pyomo is currently tested with the following Python implementations:
-* CPython: 3.7, 3.8, 3.9, 3.10
+* CPython: 3.7, 3.8, 3.9, 3.10, 3.11
* PyPy: 3.7, 3.8, 3.9
+_Testing and support policy_:
+
+At the time of the first Pyomo release after the end-of-life of a minor Python
+version, we will remove testing for that Python version.
+
### Installation
#### PyPI [](https://pypi.org/project/Pyomo/) [](https://pypistats.org/packages/pyomo)
@@ -77,7 +85,11 @@ To get help from the Pyomo community ask a question on one of the following:
Pyomo development moved to this repository in June, 2016 from
Sandia National Laboratories. Developer discussions are hosted by
-[google groups](https://groups.google.com/forum/#!forum/pyomo-developers).
+[Google Groups](https://groups.google.com/forum/#!forum/pyomo-developers).
+
+The Pyomo Development team holds weekly coordination meetings on
+Tuesdays 12:30 - 14:00 (MT). Please contact wg-pyomo@sandia.gov to
+request call-in information.
By contributing to this software project, you are agreeing to the
following terms and conditions for your contributions:
diff --git a/RELEASE.txt b/RELEASE.md
similarity index 52%
rename from RELEASE.txt
rename to RELEASE.md
index 10def0707e9..53de39654c9 100644
--- a/RELEASE.txt
+++ b/RELEASE.md
@@ -1,4 +1,4 @@
-We are pleased to announce the release of Pyomo 6.4.2.
+We are pleased to announce the release of Pyomo 6.6.1.
Pyomo is a collection of Python software packages that supports a
diverse set of optimization capabilities for formulating and analyzing
@@ -9,16 +9,29 @@ The following are highlights of the 6.0 release series:
- Improved stability and robustness of core Pyomo code and solver interfaces
- Integration of Boolean variables into GDP
- Integration of NumPy support into the Pyomo expression system
- - Added support for Python 3.10
+ - Implemented a more performant and robust expression generation system
+ - Implemented a more performant NL file writer (NLv2)
+ - Implemented a more performant LP file writer (LPv2)
+ - Applied [PEP8 standards](https://peps.python.org/pep-0008/) throughout the
+ codebase
+ - Added support for Python 3.10, 3.11
- Removed support for Python 3.6
+ - Removed the `pyomo check` command
- New packages:
- APPSI (Auto-Persistent Pyomo Solver Interfaces)
+ - CP (Constraint programming models and solver interfaces)
+ - DoE (Model based design of experiments)
- External grey box models
+ - IIS (Standard interface to solver IIS capabilities)
+ - MPC (Data structures/utils for rolling horizon dynamic optimization)
+ - piecewise (Modeling with and reformulating multivariate piecewise linear
+ functions)
- PyROS (Pyomo Robust Optimization Solver)
- Structural model analysis
- Rewrite of the TrustRegion Solver
-A full list of updates and changes is available in the CHANGELOG.txt
+A full list of updates and changes is available in the
+[`CHANGELOG.md`](https://github.com/Pyomo/pyomo/blob/main/CHANGELOG.md).
Enjoy!
@@ -27,7 +40,6 @@ Enjoy!
- https://www.pyomo.org
------------
About Pyomo
-----------
@@ -35,11 +47,15 @@ The Pyomo home page provides resources for Pyomo users:
* https://www.pyomo.org
+Detailed documentation is hosted on Read the Docs:
+
+ * https://pyomo.readthedocs.io/en/stable/
+
Pyomo development is hosted at GitHub:
* https://github.com/Pyomo
-Get help at
+Get help at:
* StackOverflow: https://stackoverflow.com/questions/tagged/pyomo
* Pyomo Forum: https://groups.google.com/group/pyomo-forum/
diff --git a/conftest.py b/conftest.py
index 70a3ccebec5..df5b0f31e59 100644
--- a/conftest.py
+++ b/conftest.py
@@ -11,6 +11,7 @@
import pytest
+
def pytest_runtest_setup(item):
"""
This method overrides pytest's default behavior for marked tests.
@@ -45,8 +46,9 @@ def pytest_runtest_setup(item):
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 set(implicit_markers).issubset(
+ item_markers
+ ) and not item_markers.issubset(set(extended_implicit_markers)):
pytest.skip('SKIPPED: Only running default, solver, and unmarked tests.')
diff --git a/doc/OnlineDocs/README.md b/doc/OnlineDocs/README.md
new file mode 100644
index 00000000000..a2d4e5997dc
--- /dev/null
+++ b/doc/OnlineDocs/README.md
@@ -0,0 +1,27 @@
+Preview Changes Locally
+------------------------
+
+1. Install Sphinx
+
+ ```bash
+ $ pip install sphinx sphinx_rtd_theme sphinx_copybutton
+ ```
+
+ **NOTE**: You may get a warning about the `dot` command if you do not have
+ `graphviz` installed.
+
+1. Build the documentation
+
+ ```bash
+ $ make html # Option 1
+ $ make latexpdf # Option 2
+ ```
+
+1. View `_build/html/index.html` in your browser
+
+Test Changes Locally
+--------------------
+
+ ```bash
+ $ make -C doc/OnlineDocs doctest -d # from the pyomo root folder
+ ```
diff --git a/doc/OnlineDocs/README.txt b/doc/OnlineDocs/README.txt
deleted file mode 100644
index 237dc8d3fcf..00000000000
--- a/doc/OnlineDocs/README.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-GETTING STARTED
----------------
-
-0. Install Sphinx
-
- pip install sphinx sphinx_rtd_theme
-
-1. Edit documentation
-
- vi *.rst
-
-2. Build the documentation
-
- make html
-
-or
-
- make latexpdf
-
-NOTE: If the local python is not on your path, then you may need to
-invoke 'make' differently. For example, using the PyUtilib 'lbin' command:
-
- lbin make html
-
-3. Admire your work
-
- cd _build/html
- open index.html
-
-4. Repeat
-
- GOTO STEP 1
diff --git a/doc/OnlineDocs/_static/theme_overrides.css b/doc/OnlineDocs/_static/theme_overrides.css
index faadd069f12..43d48693e03 100644
--- a/doc/OnlineDocs/_static/theme_overrides.css
+++ b/doc/OnlineDocs/_static/theme_overrides.css
@@ -13,7 +13,7 @@ code.descname {
font-weight: bold !important;
color: black;
}
-/* method argument lists shoult *not* be bold, argument names in black */
+/* method argument lists should *not* be bold, argument names in black */
dl.py.method dt {
font-weight: normal;
}
diff --git a/doc/OnlineDocs/advanced_topics/flattener/index.rst b/doc/OnlineDocs/advanced_topics/flattener/index.rst
new file mode 100644
index 00000000000..377de5233ec
--- /dev/null
+++ b/doc/OnlineDocs/advanced_topics/flattener/index.rst
@@ -0,0 +1,44 @@
+"Flattening" a Pyomo model
+==========================
+
+.. autosummary::
+
+ pyomo.dae.flatten
+
+.. toctree::
+ :maxdepth: 1
+
+ motivation.rst
+ reference.rst
+
+What does it mean to flatten a model?
+-------------------------------------
+When accessing components in a block-structured model, we use
+``component_objects`` or ``component_data_objects`` to access all objects
+of a specific ``Component`` or ``ComponentData`` type.
+The generated objects may be thought of as a "flattened" representation
+of the model, as they may be accessed without any knowledge of the model's
+block structure.
+These methods are very useful, but it is still challenging to use them
+to access specific components.
+Specifically, we often want to access "all components indexed by some set,"
+or "all component data at a particular index of this set."
+In addition, we often want to generate the components in a block that
+is indexed by our particular set, as these components may be thought of as
+"implicitly indexed" by this set.
+The ``pyomo.dae.flatten`` module aims to address this use case by providing
+utilities to generate all components indexed, explicitly or implicitly, by
+user-provided sets.
+
+**When we say "flatten a model," we mean "generate all components in the model,
+preserving all user-specified indexing sets."**
+
+Data structures
+---------------
+The components returned are either ``ComponentData`` objects, for components
+not indexed by any of the provided sets, or references-to-slices, for
+components indexed, explicitly or implicitly, by the provided sets.
+Slices are necessary as they can encode "implicit indexing" -- where a
+component is contained in an indexed block. It is natural to return references
+to these slices, so they may be accessed and manipulated like any other
+component.
diff --git a/doc/OnlineDocs/advanced_topics/flattener/motivation.rst b/doc/OnlineDocs/advanced_topics/flattener/motivation.rst
new file mode 100644
index 00000000000..046d888a215
--- /dev/null
+++ b/doc/OnlineDocs/advanced_topics/flattener/motivation.rst
@@ -0,0 +1,26 @@
+Motivation
+==========
+
+The ``pyomo.dae.flatten`` module was originally developed to assist with
+dynamic optimization. A very common operation in dynamic or multi-period
+optimization is to initialize all time-indexed variables to their values
+at a specific time point. However, for variables indexed by time and
+arbitrary other indexing sets, this is difficult to do in a way that does
+does not depend on the variable we are initializing. Things get worse
+when we consider that a time index can exist on a parent block rather
+than the component itself.
+
+By "reshaping" time-indexed variables in a model into references indexed
+only by time, the ``flatten_dae_components`` function allows us to perform
+operations that depend on knowledge of time indices without knowing
+anything about the variables that we are operating on.
+
+This "flattened representation" of a model turns out to be useful for
+dynamic optimization in a variety of other contexts. Examples include
+constructing a tracking objective function and plotting results.
+This representation is also useful in cases where we want to preserve
+indexing along more than one set, as in PDE-constrained optimization.
+The ``flatten_components_along_sets`` function allows partitioning
+components while preserving multiple indexing sets.
+In such a case, time and space-indexed data for a given variable is useful
+for purposes such as initialization, visualization, and stability analysis.
diff --git a/doc/OnlineDocs/advanced_topics/flattener/reference.rst b/doc/OnlineDocs/advanced_topics/flattener/reference.rst
new file mode 100644
index 00000000000..22c7b67e1f6
--- /dev/null
+++ b/doc/OnlineDocs/advanced_topics/flattener/reference.rst
@@ -0,0 +1,14 @@
+API reference
+=============
+
+.. autosummary::
+
+ pyomo.dae.flatten.slice_component_along_sets
+ pyomo.dae.flatten.flatten_components_along_sets
+ pyomo.dae.flatten.flatten_dae_components
+
+.. autofunction:: pyomo.dae.flatten.slice_component_along_sets
+
+.. autofunction:: pyomo.dae.flatten.flatten_components_along_sets
+
+.. autofunction:: pyomo.dae.flatten.flatten_dae_components
diff --git a/doc/OnlineDocs/advanced_topics/index.rst b/doc/OnlineDocs/advanced_topics/index.rst
index e727dfc0d67..d5293bfa40c 100644
--- a/doc/OnlineDocs/advanced_topics/index.rst
+++ b/doc/OnlineDocs/advanced_topics/index.rst
@@ -7,3 +7,5 @@ Advanced Topics
persistent_solvers.rst
units_container.rst
linearexpression.rst
+ flattener/index.rst
+ sos_constraints.rst
diff --git a/doc/OnlineDocs/advanced_topics/linearexpression.rst b/doc/OnlineDocs/advanced_topics/linearexpression.rst
index abadc7869da..a320b66590f 100644
--- a/doc/OnlineDocs/advanced_topics/linearexpression.rst
+++ b/doc/OnlineDocs/advanced_topics/linearexpression.rst
@@ -39,4 +39,4 @@ syntax. This example creates two constraints that are the same:
.. warning::
The lists that are passed to ``LinearModel`` are not copied, so caution must
- be excercised if they are modified after the component is constructed.
+ be exercised if they are modified after the component is constructed.
diff --git a/doc/OnlineDocs/advanced_topics/sos_constraints.rst b/doc/OnlineDocs/advanced_topics/sos_constraints.rst
new file mode 100644
index 00000000000..b536b3f0b26
--- /dev/null
+++ b/doc/OnlineDocs/advanced_topics/sos_constraints.rst
@@ -0,0 +1,288 @@
+Special Ordered Sets (SOS)
+==========================
+
+Pyomo allows users to declare special ordered sets (SOS) within their problems.
+These are sets of variables among which only a certain number of variables can
+be non-zero, and those that are must be adjacent according to a given order.
+
+Special ordered sets of types 1 (SOS1) and 2 (SOS2) are the classic ones, but
+the concept can be generalised: a SOS of type N cannot have more than N of its
+members taking non-zero values, and those that do must be adjacent in the set.
+These can be useful for modelling and computational performance purposes.
+
+By explicitly declaring these, users can keep their formulations and respective
+solving times shorter than they would otherwise, since the logical constraints
+that enforce the SOS do not need to be implemented within the model and are
+instead (ideally) handled algorithmically by the solver.
+
+Special ordered sets can be declared one by one or indexed via other sets.
+
+Non-indexed Special Ordered Sets
+--------------------------------
+
+A single SOS of type **N** involving all members of a pyomo Var component can
+be declared in one line:
+
+.. currentmodule:: pyomo.environ
+
+.. testcode::
+
+ # import pyomo
+ import pyomo.environ as pyo
+ # declare the model
+ model = pyo.AbstractModel()
+ # the type of SOS
+ N = 1 # or 2, 3, ...
+ # the set that indexes the variables
+ model.A = pyo.Set()
+ # the variables under consideration
+ model.x = pyo.Var(model.A)
+ # the sos constraint
+ model.mysos = pyo.SOSConstraint(var=model.x, sos=N)
+
+In the example above, the weight of each variable is determined automatically
+based on their position/order in the pyomo Var component (``model.x``).
+
+Alternatively, the weights can be specified through a pyomo Param component
+(``model.mysosweights``) indexed by the set also indexing the variables
+(``model.A``):
+
+.. doctest::
+ :hide:
+
+ >>> model = pyo.AbstractModel()
+
+.. testcode::
+
+ # the set that indexes the variables
+ model.A = pyo.Set()
+ # the variables under consideration
+ model.x = pyo.Var(model.A)
+ # the weights for each variable used in the sos constraints
+ model.mysosweights = pyo.Param(model.A)
+ # the sos constraint
+ model.mysos = pyo.SOSConstraint(
+ var=model.x,
+ sos=N,
+ weights=model.mysosweights
+ )
+
+Indexed Special Ordered Sets
+----------------------------
+
+Multiple SOS of type **N** involving members of a pyomo Var component
+(``model.x``) can be created using two additional sets (``model.A`` and
+``model.mysosvarindexset``):
+
+.. doctest::
+ :hide:
+
+ >>> model = pyo.AbstractModel()
+
+.. testcode::
+
+ # the set that indexes the variables
+ model.A = pyo.Set()
+ # the variables under consideration
+ model.x = pyo.Var(model.A)
+ # the set indexing the sos constraints
+ model.B = pyo.Set()
+ # the sets containing the variable indexes for each constraint
+ model.mysosvarindexset = pyo.Set(model.B)
+ # the sos constraints
+ model.mysos = pyo.SOSConstraint(
+ model.B,
+ var=model.x,
+ sos=N,
+ index=model.mysosvarindexset
+ )
+
+In the example above, the weights are determined automatically from the
+position of the variables. Alternatively, they can be specified through a pyomo
+Param component (``model.mysosweights``) and an additional set (``model.C``):
+
+.. doctest::
+ :hide:
+
+ >>> model = pyo.AbstractModel()
+
+.. testcode::
+
+ # the set that indexes the variables
+ model.A = pyo.Set()
+ # the variables under consideration
+ model.x = pyo.Var(model.A)
+ # the set indexing the sos constraints
+ model.B = pyo.Set()
+ # the sets containing the variable indexes for each constraint
+ model.mysosvarindexset = pyo.Set(model.B)
+ # the set that indexes the variables used in the sos constraints
+ model.C = pyo.Set(within=model.A)
+ # the weights for each variable used in the sos constraints
+ model.mysosweights = pyo.Param(model.C)
+ # the sos constraints
+ model.mysos = pyo.SOSConstraint(
+ model.B,
+ var=model.x,
+ sos=N,
+ index=model.mysosvarindexset,
+ weights=model.mysosweights,
+ )
+
+Declaring Special Ordered Sets using rules
+------------------------------------------
+
+Arguably the best way to declare an SOS is through rules. This option allows
+users to specify the variables and weights through a method provided via the
+``rule`` parameter. If this parameter is used, users must specify a method that
+returns one of the following options:
+
+- a list of the variables in the SOS, whose respective weights are then determined based on their position;
+
+- a tuple of two lists, the first for the variables in the SOS and the second for the respective weights;
+
+- or, pyomo.environ.SOSConstraint.Skip, if the SOS is not to be declared.
+
+If one is content on having the weights determined based on the position of the
+variables, then the following example using the ``rule`` parameter is sufficient:
+
+.. doctest::
+ :hide:
+
+ >>> model = pyo.AbstractModel()
+
+.. testcode::
+
+ # the set that indexes the variables
+ model.A = pyo.Set()
+ # the variables under consideration
+ model.x = pyo.Var(model.A, domain=pyo.NonNegativeReals)
+ # the rule method creating the constraint
+ def rule_mysos(m):
+ return [m.x[a] for a in m.x]
+ # the sos constraint(s)
+ model.mysos = pyo.SOSConstraint(rule=rule_mysos, sos=N)
+
+
+If the weights must be determined in some other way, then the following example
+illustrates how they can be specified for each member of the SOS using the ``rule`` parameter:
+
+.. doctest::
+ :hide:
+
+ >>> model = pyo.AbstractModel()
+
+.. testcode::
+
+ # the set that indexes the variables
+ model.A = pyo.Set()
+ # the variables under consideration
+ model.x = pyo.Var(model.A, domain=pyo.NonNegativeReals)
+ # the rule method creating the constraint
+ def rule_mysos(m):
+ var_list = [m.x[a] for a in m.x]
+ weight_list = [i+1 for i in range(len(var_list))]
+ return (var_list, weight_list)
+ # the sos constraint(s)
+ model.mysos = pyo.SOSConstraint(rule=rule_mysos, sos=N)
+
+The ``rule`` parameter also allows users to create SOS comprising variables
+from different pyomo Var components, as shown below:
+
+.. doctest::
+ :hide:
+
+ >>> model = pyo.AbstractModel()
+
+.. testcode::
+
+ # the set that indexes the x variables
+ model.A = pyo.Set()
+ # the set that indexes the y variables
+ model.B = pyo.Set()
+ # the set that indexes the SOS constraints
+ model.C = pyo.Set()
+ # the x variables, which will be used in the constraints
+ model.x = pyo.Var(model.A, domain=pyo.NonNegativeReals)
+ # the y variables, which will be used in the constraints
+ model.y = pyo.Var(model.B, domain=pyo.NonNegativeReals)
+ # the x variable indices for each constraint
+ model.mysosindex_x = pyo.Set(model.C)
+ # the y variable indices for each constraint
+ model.mysosindex_y = pyo.Set(model.C)
+ # the weights for the x variable indices
+ model.mysosweights_x = pyo.Param(model.A)
+ # the weights for the y variable indices
+ model.mysosweights_y = pyo.Param(model.B)
+ # the rule method with which each constraint c is built
+ def rule_mysos(m, c):
+ var_list = [m.x[a] for a in m.mysosindex_x[c]]
+ var_list.extend([m.y[b] for b in m.mysosindex_y[c]])
+ weight_list = [m.mysosweights_x[a] for a in m.mysosindex_x[c]]
+ weight_list.extend([m.mysosweights_y[b] for b in m.mysosindex_y[c]])
+ return (var_list, weight_list)
+ # the sos constraint(s)
+ model.mysos = pyo.SOSConstraint(
+ model.C,
+ rule=rule_mysos,
+ sos=N
+ )
+
+Compatible solvers
+------------------
+
+Not all LP/MILP solvers are compatible with SOS declarations and Pyomo might
+not be ready to interact with all those that are. The following is a list of
+solvers known to be compatible with special ordered sets through Pyomo:
+
+- CBC
+- SCIP
+- Gurobi
+- CPLEX
+
+Please note that declaring an SOS is no guarantee that a solver will use it as
+such in the end. Some solvers, namely Gurobi and CPLEX, might reformulate
+problems with explicit SOS declarations, if they perceive that to be useful.
+
+Full example with non-indexed SOS constraint
+--------------------------------------------
+
+.. doctest::
+ :hide:
+
+ >>> model = pyo.AbstractModel()
+
+.. testcode::
+
+ import pyomo.environ as pyo
+ from pyomo.opt import check_available_solvers
+ from math import isclose
+ N = 1
+ model = pyo.ConcreteModel()
+ model.x = pyo.Var([1], domain=pyo.NonNegativeReals, bounds=(0,40))
+ model.A = pyo.Set(initialize=[1,2,4,6])
+ model.y = pyo.Var(model.A, domain=pyo.NonNegativeReals, bounds=(0,2))
+ model.OBJ = pyo.Objective(
+ expr=(1*model.x[1]+
+ 2*model.y[1]+
+ 3*model.y[2]+
+ -0.1*model.y[4]+
+ 0.5*model.y[6])
+ )
+ model.ConstraintYmin = pyo.Constraint(
+ expr = (model.x[1]+
+ model.y[1]+
+ model.y[2]+
+ model.y[6] >= 0.25
+ )
+ )
+ model.mysos = pyo.SOSConstraint(
+ var=model.y,
+ sos=N
+ )
+ solver_name = 'scip'
+ solver_available = bool(check_available_solvers(solver_name))
+ if solver_available:
+ opt = pyo.SolverFactory(solver_name)
+ opt.solve(model, tee=False)
+ assert isclose(pyo.value(model.OBJ), 0.05, abs_tol=1e-3)
diff --git a/doc/OnlineDocs/bibliography.rst b/doc/OnlineDocs/bibliography.rst
index e4e76f66adb..6cbb96d3bfb 100644
--- a/doc/OnlineDocs/bibliography.rst
+++ b/doc/OnlineDocs/bibliography.rst
@@ -9,7 +9,7 @@ Bibliography
.. [GAMS] http://www.gams.com
-.. [GRCSPaper] Isenberg, NM, Akula, P, Eslick, JC, Bhattacharyya, D,
+.. [Isenberg_et_al] Isenberg, NM, Akula, P, Eslick, JC, Bhattacharyya, D,
Miller, DC, Gounaris, CE. A generalized cuttingâ€set approach for
nonlinear robust optimization in process systems
engineering. AIChE J. 2021; 67:e17175. DOI `10.1002/aic.17175
@@ -34,6 +34,12 @@ Bibliography
2nd Edition. Springer Optimization and Its
Applications, Vol 67. Springer, 2017.
+.. [PyomoBookIII] Bynum, Michael L., Gabriel A. Hackebeil,
+ William E. Hart, Carl D. Laird, Bethany L. Nicholson,
+ John D. Siirola, Jean-Paul Watson, and David L. Woodruff.
+ Pyomo - Optimization Modeling in Python, 3rd Edition.
+ Vol. 67. Springer, 2021.
+
.. [PyomoJournal] William E. Hart, Jean-Paul Watson, David L. Woodruff.
"Pyomo: modeling and solving mathematical programs in
Python," Mathematical Programming Computation, Volume
diff --git a/doc/OnlineDocs/citing_pyomo.rst b/doc/OnlineDocs/citing_pyomo.rst
index 8064e56770b..458a1fe6ab7 100644
--- a/doc/OnlineDocs/citing_pyomo.rst
+++ b/doc/OnlineDocs/citing_pyomo.rst
@@ -4,9 +4,10 @@ Citing Pyomo
Pyomo
-----
+Bynum, Michael L., Gabriel A. Hackebeil, William E. Hart, Carl D. Laird, Bethany L. Nicholson, John D. Siirola, Jean-Paul Watson, and David L. Woodruff. Pyomo - Optimization Modeling in Python, 3rd Edition. Springer, 2021.
+
Hart, William E., Jean-Paul Watson, and David L. Woodruff. "Pyomo: modeling and solving mathematical programs in Python." Mathematical Programming Computation 3, no. 3 (2011): 219-260.
-Hart, William E., Carl Laird, Jean-Paul Watson, David L. Woodruff, Gabriel A. Hackebeil, Bethany L. Nicholson, and John D. Siirola. Pyomo – Optimization Modeling in Python. Springer, 2017.
PySP
----
diff --git a/doc/OnlineDocs/conf.py b/doc/OnlineDocs/conf.py
index 03da0c471e4..d8939cf61dd 100644
--- a/doc/OnlineDocs/conf.py
+++ b/doc/OnlineDocs/conf.py
@@ -30,9 +30,11 @@
try:
print("Regenerating SPY files...")
from strip_examples import generate_spy_files
+
generate_spy_files(os.path.abspath('tests'))
- generate_spy_files(os.path.abspath(os.path.join(
- 'library_reference','kernel','examples')))
+ generate_spy_files(
+ os.path.abspath(os.path.join('library_reference', 'kernel', 'examples'))
+ )
finally:
sys.path.pop(0)
@@ -68,12 +70,13 @@
'sphinx.ext.inheritance_diagram',
'sphinx.ext.autosummary',
'sphinx.ext.doctest',
+ 'sphinx.ext.todo',
'sphinx_copybutton',
#'sphinx.ext.githubpages',
]
viewcode_follow_imported_members = True
-#napoleon_include_private_with_doc = True
+# napoleon_include_private_with_doc = True
copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: "
copybutton_prompt_is_regexp = True
@@ -92,7 +95,7 @@
# General information about the project.
project = u'Pyomo'
-copyright = u'2017, Sandia National Laboratories'
+copyright = u'2008-2023, Sandia National Laboratories'
author = u'Pyomo Developers'
# The version info for the project you're documenting, acts as replacement for
@@ -101,6 +104,7 @@
#
# The short X.Y version.
import pyomo.version
+
version = pyomo.version.__version__
# The full version, including alpha/beta/rc tags.
release = pyomo.version.__version__
@@ -121,7 +125,7 @@
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
-todo_include_todos = False
+todo_include_todos = True
# If true, doctest flags (comments looking like # doctest: FLAG, ...) at
# the ends of lines and markers are removed for all code
@@ -137,20 +141,14 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
-#html_theme = 'alabaster'
+# html_theme = 'alabaster'
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
html_theme = 'sphinx_rtd_theme'
-# Force HTML4: If we don't explicitly force HTML4, then the background
-# of the Parameters/Returns/Return type headers is shaded the same as the
-# method prototype (tested 15 April 21 with Sphinx=3.5.4 and
-# sphinx-rtd-theme=0.5.2).
-html4_writer = True
-#html5_writer = True
-
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
+
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
@@ -163,9 +161,7 @@
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
-html_css_files = [
- 'theme_overrides.css',
-]
+html_css_files = ['theme_overrides.css']
html_favicon = "../logos/pyomo/favicon.ico"
@@ -182,15 +178,12 @@
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
-
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
-
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
-
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
@@ -199,20 +192,14 @@
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
-latex_documents = [
- (master_doc, 'pyomo.tex', 'Pyomo Documentation',
- 'Pyomo', 'manual'),
-]
+latex_documents = [(master_doc, 'pyomo.tex', 'Pyomo Documentation', 'Pyomo', 'manual')]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
-man_pages = [
- (master_doc, 'pyomo', 'Pyomo Documentation',
- [author], 1)
-]
+man_pages = [(master_doc, 'pyomo', 'Pyomo Documentation', [author], 1)]
# -- Options for Texinfo output -------------------------------------------
@@ -221,27 +208,41 @@
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
- (master_doc, 'pyomo', 'Pyomo Documentation',
- author, 'Pyomo', 'One line description of project.',
- 'Miscellaneous'),
+ (
+ master_doc,
+ 'pyomo',
+ 'Pyomo Documentation',
+ author,
+ 'Pyomo',
+ 'One line description of project.',
+ 'Miscellaneous',
+ )
]
-#autodoc_member_order = 'bysource'
-#autodoc_member_order = 'groupwise'
+# autodoc_member_order = 'bysource'
+# autodoc_member_order = 'groupwise'
# -- Check which conditional dependencies are available ------------------
# Used for skipping certain doctests
from sphinx.ext.doctest import doctest
+
doctest_default_flags = (
- doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE +
- doctest.IGNORE_EXCEPTION_DETAIL + doctest.DONT_ACCEPT_TRUE_FOR_1
+ doctest.ELLIPSIS
+ + doctest.NORMALIZE_WHITESPACE
+ + doctest.IGNORE_EXCEPTION_DETAIL
+ + doctest.DONT_ACCEPT_TRUE_FOR_1
)
+
+
class IgnoreResultOutputChecker(doctest.OutputChecker):
IGNORE_RESULT = doctest.register_optionflag('IGNORE_RESULT')
+
def check_output(self, want, got, optionflags):
if optionflags & self.IGNORE_RESULT:
return True
return super().check_output(want, got, optionflags)
+
+
doctest.OutputChecker = IgnoreResultOutputChecker
doctest_global_setup = '''
@@ -265,7 +266,7 @@ def check_output(self, want, got, optionflags):
import pyomo.opt as _opt
# Not using SolverFactory to check solver availability because
-# as of June 2020 there is no way to supress warnings when
+# as of June 2020 there is no way to suppress warnings when
# solvers are not available
ipopt_available = bool(_opt.check_available_solvers('ipopt'))
sipopt_available = bool(_opt.check_available_solvers('ipopt_sens'))
diff --git a/doc/OnlineDocs/contributed_packages/community.rst b/doc/OnlineDocs/contributed_packages/community.rst
index 08d3bb36a3c..b110107e604 100644
--- a/doc/OnlineDocs/contributed_packages/community.rst
+++ b/doc/OnlineDocs/contributed_packages/community.rst
@@ -101,9 +101,10 @@ We'll first use a model from `Allman et al, 2019`_ :
.. _Allman et al, 2019: https://doi.org/10.1007/s11081-019-09450-5
.. doctest::
+ :skipif: not networkx_available
Required Imports
- >>> from pyomo.contrib.community_detection.detection import detect_communities, detect_communities, CommunityMap, generate_model_graph
+ >>> from pyomo.contrib.community_detection.detection import detect_communities, CommunityMap, generate_model_graph
>>> from pyomo.contrib.mindtpy.tests.eight_process_problem import EightProcessFlowsheet
>>> from pyomo.core import ConcreteModel, Var, Constraint
>>> import networkx as nx
@@ -143,26 +144,33 @@ have unintended consequences): ``new_community_map = copy.deepcopy(community_map
Let's take a closer look at the actual community map object generated by `detect_communities`:
.. doctest::
+ :skipif: not networkx_available
:hide:
- >>> if community_map_object[0] == (['c3', 'c4', 'c5'], ['x3', 'x4']):
- ... community_map_object[0], community_map_object[1] = community_map_object[1], community_map_object[0]
+ >>> from pyomo.common.formatting import tostr
+ >>> if tostr(community_map_object[0]) == "([c3, c4, c5], [x3, x4])":
+ ... _ = community_map_object.community_map
+ ... _[0], _[1] = _[1], _[0]
.. doctest::
+ :skipif: not networkx_available
- >>> print(community_map_object) #doctest:+SKIP
+ >>> print(community_map_object)
{0: (['c1', 'c2'], ['x1', 'x2']), 1: (['c3', 'c4', 'c5'], ['x3', 'x4'])}
Printing a community map object is made to be user-friendly (by showing the community map with components
replaced by their strings). However, if the default Pyomo representation of components is desired, then the
-community_map attribute or the repr() function can be used:
+community_map attribute or the `repr()` function can be used:
.. doctest::
+ :skipif: not networkx_available
- >>> print(community_map_object.community_map) # or print(repr(community_map_object)) # doctest: +SKIP
- {0: ([, ], [, ]), 1: ([, , ], [, ])}
+ >>> print(community_map_object.community_map)
+ {0: ([, ], [, ]), 1: ([, , ], [, ])}
+ >>> print(repr(community_map_object))
+ {0: ([, ], [, ]), 1: ([, , ], [, ])}
`generate_structured_model` method of CommunityMap objects
It may be useful to create a new model based on the communities found in the model - we can use the
@@ -171,6 +179,7 @@ community_map attribute or the repr() function can be used:
take a look at the example below:
.. doctest::
+ :skipif: not networkx_available
Use the CommunityMap object made from the first code example
>>> structured_model = community_map_object.generate_structured_model() # doctest: +SKIP
@@ -262,7 +271,7 @@ community_map attribute or the repr() function can be used:
so. Let's take a look at how this can be done in the following example:
.. doctest::
- :skipif: not matplotlib_available
+ :skipif: not matplotlib_available or not networkx_available
Create a CommunityMap object (so we can demonstrate the visualize_model_graph method)
>>> community_map_object = cmo = detect_communities(model, type_of_community_map='bipartite', random_seed=seed)
@@ -295,7 +304,7 @@ An example of the two separate graphs created for these two function calls is sh
.. _Duran & Grossmann, 1986: https://dx.doi.org/10.1007/BF02592064
.. doctest::
- :skipif: not matplotlib_available
+ :skipif: not matplotlib_available or not networkx_available
Define the model
>>> model = EightProcessFlowsheet()
@@ -337,6 +346,7 @@ We can see an example for the three separate graphs created by these three funct
For this example, we will only need the NetworkX graph of the model and the number-to-component mapping.
.. doctest::
+ :skipif: not networkx_available
Define the model
>>> model = decode_model_1()
diff --git a/doc/OnlineDocs/contributed_packages/doe/CCSI-license.txt b/doc/OnlineDocs/contributed_packages/doe/CCSI-license.txt
new file mode 100644
index 00000000000..4b0dadd9e06
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/doe/CCSI-license.txt
@@ -0,0 +1,43 @@
+# Pyomo.DoE was originally developed as part of the Carbon Capture Simulation for Industry
+# Impact (CCSI2) project under the following license:
+#
+# *** License Agreement ***
+#
+# Pyomo.DoE Copyright (c) 2022, by the software owners: TRIAD National Security, LLC., Lawrence
+# Livermore National Security, LLC., Lawrence Berkeley National Laboratory,
+# Pacific Northwest National Laboratory, Battelle Memorial Institute, University of Notre Dame,
+# The University of Pittsburgh, The University of Texas at Austin, University of Toledo,
+# West Virginia University, et al. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are permitted provided
+# that the following conditions are met:
+# (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the
+# following disclaimer.
+# (2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+# the following disclaimer in the documentation and/or other materials provided with the distribution.
+# (3) Neither the name of the Carbon Capture Simulation for Industry Impact,
+# TRIAD National Security, LLC., Lawrence Livermore National Security, LLC.,
+# Lawrence Berkeley National Laboratory, Pacific Northwest National Laboratory,
+# Battelle Memorial Institute, University of Notre Dame, The University of Pittsburgh,
+# U.S. Dept. of Energy nor the names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# You are under no obligation whatsoever to provide any bug fixes, patches, or upgrades to the features,
+# functionality or performance of the source code ("Enhancements") to anyone; however, if you choose to
+# make your Enhancements available either publicly, or directly to Lawrence Berkeley National Laboratory,
+# without imposing a separate written license agreement for such Enhancements, then you hereby grant
+# the following license: a non-exclusive, royalty-free perpetual license to install, use, modify, prepare
+# derivative works, incorporate into other computer software, distribute, and sublicense such
+# enhancements or derivative works thereof, in binary and source code form.
+#
+# Lead Developers: Jialu Wang and Alexander Dowling, University of Notre Dame
diff --git a/doc/OnlineDocs/contributed_packages/doe/doe.rst b/doc/OnlineDocs/contributed_packages/doe/doe.rst
new file mode 100644
index 00000000000..354a9916e9b
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/doe/doe.rst
@@ -0,0 +1,291 @@
+Pyomo.DoE
+=========
+
+**Pyomo.DoE** (Pyomo Design of Experiments) is a Python library for model-based design of experiments using science-based models.
+
+Pyomo.DoE was developed by **Jialu Wang** and **Alexander W. Dowling** at the University of Notre Dame as part of the `Carbon Capture Simulation for Industry Impact (CCSI2) `_.
+project, funded through the U.S. Department Of Energy Office of Fossil Energy.
+
+If you use Pyomo.DoE, please cite:
+
+[Wang and Dowling, 2022] Wang, Jialu, and Alexander W. Dowling.
+"Pyomo.DOE: An openâ€source package for modelâ€based design of experiments in Python."
+AIChE Journal 68.12 (2022): e17813. `https://doi.org/10.1002/aic.17813`
+
+Methodology Overview
+---------------------
+
+Model-based Design of Experiments (MBDoE) is a technique to maximize the information gain of experiments by directly using science-based models with physically meaningful parameters. It is one key component in the model calibration and uncertainty quantification workflow shown below:
+
+.. figure:: flowchart.png
+ :scale: 25 %
+
+ The exploratory analysis, parameter estimation, uncertainty analysis, and MBDoE are combined into an iterative framework to select, refine, and calibrate science-based mathematical models with quantified uncertainty. Currently, Pyomo.DoE focuses on increasing parameter precision.
+
+Pyomo.DoE provides the exploratory analysis and MBDoE capabilities to the Pyomo ecosystem. The user provides one Pyomo model, a set of parameter nominal values,
+the allowable design spaces for design variables, and the assumed observation error model.
+During exploratory analysis, Pyomo.DoE checks if the model parameters can be inferred from the postulated measurements or preliminary data.
+MBDoE then recommends optimized experimental conditions for collecting more data.
+Parameter estimation packages such as `Parmest `_ can perform parameter estimation using the available data to infer values for parameters,
+and facilitate an uncertainty analysis to approximate the parameter covariance matrix.
+If the parameter uncertainties are sufficiently small, the workflow terminates and returns the final model with quantified parametric uncertainty.
+If not, MBDoE recommends optimized experimental conditions to generate new data.
+
+Below is an overview of the type of optimization models Pyomo.DoE can accommodate:
+
+* Pyomo.DoE is suitable for optimization models of **continuous** variables
+* Pyomo.DoE can handle **equality constraints** defining state variables
+* Pyomo.DoE supports (Partial) Differential-Algebraic Equations (PDAE) models via Pyomo.DAE
+* Pyomo.DoE also supports models with only algebraic constraints
+
+The general form of a DAE problem that can be passed into Pyomo.DoE is shown below:
+
+.. math::
+ \begin{align*}
+ & \dot{\mathbf{x}}(t) = \mathbf{f}(\mathbf{x}(t), \mathbf{z}(t), \mathbf{y}(t), \mathbf{u}(t), \overline{\mathbf{w}}, \boldsymbol{\theta}) \\
+ & \mathbf{g}(\mathbf{x}(t), \mathbf{z}(t), \mathbf{y}(t), \mathbf{u}(t), \overline{\mathbf{w}},\boldsymbol{\theta})=\mathbf{0} \\
+ & \mathbf{y} =\mathbf{h}(\mathbf{x}(t), \mathbf{z}(t), \mathbf{u}(t), \overline{\mathbf{w}},\boldsymbol{\theta}) \\
+ & \mathbf{f}^{\mathbf{0}}\left(\dot{\mathbf{x}}\left(t_{0}\right), \mathbf{x}\left(t_{0}\right), \mathbf{z}(t_0), \mathbf{y}(t_0), \mathbf{u}\left(t_{0}\right), \overline{\mathbf{w}}, \boldsymbol{\theta})\right)=\mathbf{0} \\
+ & \mathbf{g}^{\mathbf{0}}\left( \mathbf{x}\left(t_{0}\right),\mathbf{z}(t_0), \mathbf{y}(t_0), \mathbf{u}\left(t_{0}\right), \overline{\mathbf{w}}, \boldsymbol{\theta}\right)=\mathbf{0}\\
+ &\mathbf{y}^{\mathbf{0}}\left(t_{0}\right)=\mathbf{h}\left(\mathbf{x}\left(t_{0}\right),\mathbf{z}(t_0), \mathbf{u}\left(t_{0}\right), \overline{\mathbf{w}}, \boldsymbol{\theta}\right)
+ \end{align*}
+
+where:
+
+* :math:`\boldsymbol{\theta} \in \mathbb{R}^{N_p}` are unknown model parameters.
+* :math:`\mathbf{x} \subseteq \mathcal{X}` are dynamic state variables which characterize trajectory of the system, :math:`\mathcal{X} \in \mathbb{R}^{N_x \times N_t}`.
+* :math:`\mathbf{z} \subseteq \mathcal{Z}` are algebraic state variables, :math:`\mathcal{Z} \in \mathbb{R}^{N_z \times N_t}`.
+* :math:`\mathbf{u} \subseteq \mathcal{U}` are time-varying decision variables, :math:`\mathcal{U} \in \mathbb{R}^{N_u \times N_t}`.
+* :math:`\overline{\mathbf{w}} \in \mathbb{R}^{N_w}` are time-invariant decision variables.
+* :math:`\mathbf{y} \subseteq \mathcal{Y}` are measurement response variables, :math:`\mathcal{Y} \in \mathbb{R}^{N_r \times N_t}`.
+* :math:`\mathbf{f}(\cdot)` are differential equations.
+* :math:`\mathbf{g}(\cdot)` are algebraic equations.
+* :math:`\mathbf{h}(\cdot)` are measurement functions.
+* :math:`\mathbf{t} \in \mathbb{R}^{N_t \times 1}` is a union of all time sets.
+
+.. note::
+ * Parameters and design variables should be defined as Pyomo ``Var`` components on the model to use ``direct_kaug`` mode, and can be defined as Pyomo ``Param`` object if not using ``direct_kaug``.
+
+Based on the above notation, the form of the MBDoE problem addressed in Pyomo.DoE is shown below:
+
+.. math::
+ \begin{equation}
+ \begin{aligned}
+ \underset{\boldsymbol{\varphi}}{\max} \quad & \Psi (\mathbf{M}(\mathbf{\hat{y}}, \boldsymbol{\varphi})) \\
+ \text{s.t.} \quad & \mathbf{M}(\boldsymbol{\hat{\theta}}, \boldsymbol{\varphi}) = \sum_r^{N_r} \sum_{r'}^{N_r} \tilde{\sigma}_{(r,r')}\mathbf{Q}_r^\mathbf{T} \mathbf{Q}_{r'} + \mathbf{V}^{-1}_{\boldsymbol{\theta}}(\boldsymbol{\hat{\theta}}) \\
+ & \dot{\mathbf{x}}(t) = \mathbf{f}(\mathbf{x}(t), \mathbf{z}(t), \mathbf{y}(t), \mathbf{u}(t), \overline{\mathbf{w}}, \boldsymbol{\theta}) \\
+ & \mathbf{g}(\mathbf{x}(t), \mathbf{z}(t), \mathbf{y}(t), \mathbf{u}(t), \overline{\mathbf{w}},\boldsymbol{\theta})=\mathbf{0} \\
+ & \mathbf{y} =\mathbf{h}(\mathbf{x}(t), \mathbf{z}(t), \mathbf{u}(t), \overline{\mathbf{w}},\boldsymbol{\theta}) \\
+ & \mathbf{f}^{\mathbf{0}}\left(\dot{\mathbf{x}}\left(t_{0}\right), \mathbf{x}\left(t_{0}\right), \mathbf{z}(t_0), \mathbf{y}(t_0), \mathbf{u}\left(t_{0}\right), \overline{\mathbf{w}}, \boldsymbol{\theta})\right)=\mathbf{0} \\
+ & \mathbf{g}^{\mathbf{0}}\left( \mathbf{x}\left(t_{0}\right),\mathbf{z}(t_0), \mathbf{y}(t_0), \mathbf{u}\left(t_{0}\right), \overline{\mathbf{w}}, \boldsymbol{\theta}\right)=\mathbf{0}\\
+ &\mathbf{y}^{\mathbf{0}}\left(t_{0}\right)=\mathbf{h}\left(\mathbf{x}\left(t_{0}\right),\mathbf{z}(t_0), \mathbf{u}\left(t_{0}\right), \overline{\mathbf{w}}, \boldsymbol{\theta}\right)
+ \end{aligned}
+ \end{equation}
+
+where:
+
+* :math:`\boldsymbol{\varphi}` are design variables, which are manipulated to maximize the information content of experiments. It should consist of one or more of :math:`\mathbf{u}(t), \mathbf{y}^{\mathbf{0}}({t_0}),\overline{\mathbf{w}}`. With a proper model formulation, the timepoints for control or measurements :math:`\mathbf{t}` can also be degrees of freedom.
+* :math:`\mathbf{M}` is the Fisher information matrix (FIM), estimated as the inverse of the covariance matrix of parameter estimates :math:`\boldsymbol{\hat{\theta}}`. A large FIM indicates more information contained in the experiment for parameter estimation.
+* :math:`\mathbf{Q}` is the dynamic sensitivity matrix, containing the partial derivatives of :math:`\mathbf{y}` with respect to :math:`\boldsymbol{\theta}`.
+* :math:`\Psi` is the design criteria to measure FIM.
+* :math:`\mathbf{V}_{\boldsymbol{\theta}}(\boldsymbol{\hat{\theta}})^{-1}` is the FIM of previous experiments.
+
+Pyomo.DoE provides four design criteria :math:`\Psi` to measure the size of FIM:
+
+.. list-table:: Pyomo.DoE design criteria
+ :header-rows: 1
+ :class: tight-table
+
+ * - Design criterion
+ - Computation
+ - Geometrical meaning
+ * - A-optimality
+ - :math:`\text{trace}({\mathbf{M}})`
+ - Dimensions of the enclosing box of the confidence ellipse
+ * - D-optimality
+ - :math:`\text{det}({\mathbf{M}})`
+ - Volume of the confidence ellipse
+ * - E-optimality
+ - :math:`\text{min eig}({\mathbf{M}})`
+ - Size of the longest axis of the confidence ellipse
+ * - Modified E-optimality
+ - :math:`\text{cond}({\mathbf{M}})`
+ - Ratio of the longest axis to the shortest axis of the confidence ellipse
+
+In order to solve problems of the above, Pyomo.DoE implements the 2-stage stochastic program. Please see Wang and Dowling (2022) for details.
+
+Pyomo.DoE Required Inputs
+--------------------------------
+The required inputs to the Pyomo.DoE solver are the following:
+
+* A function that creates the process model
+* Dictionary of parameters and their nominal value
+* A measurement object
+* A design variables object
+* A Numpy ``array`` containing the Prior FIM
+* Optimization solver
+
+Below is a list of arguments that Pyomo.DoE expects the user to provide.
+
+parameter_dict : ``dictionary``
+ A ``dictionary`` of parameter names and values. If they are an indexed variable, put the variable name and index in a nested ``Dictionary``.
+
+design_variables: ``DesignVariables``
+ A ``DesignVariables`` of design variables, provided by the DesignVariables class.
+ If this design var is independent of time (constant), set the time to [0]
+
+measurement_variables : ``MeasurementVariables``
+ A ``MeasurementVariables`` of the measurements, provided by the MeasurementVariables class.
+
+create_model : ``function``
+ A ``function`` returning a deterministic process model.
+
+prior_FIM : ``array``
+ An ``array`` defining the Fisher information matrix (FIM) for prior experiments, default is a zero matrix.
+
+Pyomo.DoE Solver Interface
+---------------------------
+
+.. figure:: uml.png
+ :scale: 25 %
+
+
+.. autoclass:: pyomo.contrib.doe.doe.DesignOfExperiments
+ :members: __init__, stochastic_program, compute_FIM, run_grid_search
+
+.. Note::
+ ``stochastic_program()`` includes the following steps:
+ #. Build two-stage stochastic programming optimization model where scenarios correspond to finite difference approximations for the Jacobian of the response variables with respect to calibrated model parameters
+ #. Fix the experiment design decisions and solve a square (i.e., zero degrees of freedom) instance of the two-stage DOE problem. This step is for initialization.
+ #. Unfix the experiment design decisions and solve the two-stage DOE problem.
+
+.. autoclass:: pyomo.contrib.doe.measurements.MeasurementVariables
+ :members: __init__, add_variables
+
+.. autoclass:: pyomo.contrib.doe.measurements.DesignVariables
+ :members: __init__, add_variables
+
+.. autoclass:: pyomo.contrib.doe.scenario.ScenarioGenerator
+ :special-members: __init__
+
+.. autoclass:: pyomo.contrib.doe.result.FisherResults
+ :members: __init__, result_analysis
+
+.. autoclass:: pyomo.contrib.doe.result.GridSearchResult
+ :special-members: __init__
+
+
+Pyomo.DoE Usage Example
+-----------------------
+
+We illustrate the use of Pyomo.DoE using a reaction kinetics example (Wang and Dowling, 2022).
+The Arrhenius equations model the temperature dependence of the reaction rate coefficient :math:`k_1, k_2`. Assuming a first-order reaction mechanism gives the reaction rate model. Further, we assume only species A is fed to the reactor.
+
+
+.. math::
+ \begin{equation}
+ \begin{aligned}
+ k_1 & = A_1 e^{-\frac{E_1}{RT}} \\
+ k_2 & = A_2 e^{-\frac{E_2}{RT}} \\
+ \frac{d{C_A}}{dt} & = -k_1{C_A} \\
+ \frac{d{C_B}}{dt} & = k_1{C_A} - k_2{C_B} \\
+ C_{A0}& = C_A + C_B + C_C \\
+ C_B(t_0) & = 0 \\
+ C_C(t_0) & = 0 \\
+ \end{aligned}
+ \end{equation}
+
+
+
+:math:`C_A(t), C_B(t), C_C(t)` are the time-varying concentrations of the species A, B, C, respectively.
+:math:`k_1, k_2` are the rates for the two chemical reactions using an Arrhenius equation with activation energies :math:`E_1, E_2` and pre-exponential factors :math:`A_1, A_2`.
+The goal of MBDoE is to optimize the experiment design variables :math:`\boldsymbol{\varphi} = (C_{A0}, T(t))`, where :math:`C_{A0},T(t)` are the initial concentration of species A and the time-varying reactor temperature, to maximize the precision of unknown model parameters :math:`\boldsymbol{\theta} = (A_1, E_1, A_2, E_2)` by measuring :math:`\mathbf{y}(t)=(C_A(t), C_B(t), C_C(t))`.
+The observation errors are assumed to be independent both in time and across measurements with a constant standard deviation of 1 M for each species.
+
+
+Step 0: Import Pyomo and the Pyomo.DoE module
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. doctest::
+
+ >>> # === Required import ===
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.dae import ContinuousSet, DerivativeVar
+ >>> from pyomo.contrib.doe import DesignOfExperiments, MeasurementVariables, DesignVariables
+ >>> import numpy as np
+
+Step 1: Define the Pyomo process model
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The process model for the reaction kinetics problem is shown below.
+
+.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_kinetics.py
+ :language: python
+ :pyobject: create_model
+
+.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_kinetics.py
+ :language: python
+ :pyobject: disc_for_measure
+
+.. note::
+ The model requires at least two options: "block" and "global". Both options requires the pass of a created empty Pyomo model.
+ With "global" option, only design variables and their time sets need to be defined;
+ With "block" option, a full model needs to be defined.
+
+
+Step 2: Define the inputs for Pyomo.DoE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_compute_FIM.py
+ :language: python
+ :start-at: # Control time set
+ :end-before: ### Compute
+
+
+Step 3: Compute the FIM of a square MBDoE problem
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This method computes an MBDoE optimization problem with no degree of freedom.
+
+This method can be accomplished by two modes, ``direct_kaug`` and ``sequential_finite``.
+``direct_kaug`` mode requires the installation of the solver `k_aug `_.
+
+.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_compute_FIM.py
+ :language: python
+ :start-after: ### Compute the FIM
+ :end-before: # test result
+
+Step 4: Exploratory analysis (Enumeration)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Exploratory analysis is suggested to enumerate the design space to check if the problem is identifiable,
+i.e., ensure that D-, E-optimality metrics are not small numbers near zero, and Modified E-optimality is not a big number.
+
+Pyomo.DoE accomplishes the exploratory analysis with the ``run_grid_search`` function.
+It allows users to define any number of design decisions. Heatmaps can be drawn by two design variables, fixing other design variables.
+1D curve can be drawn by one design variable, fixing all other variables.
+The function ``run_grid_search`` enumerates over the design space, each MBDoE problem accomplished by ``compute_FIM`` method.
+Therefore, ``run_grid_search`` supports only two modes: ``sequential_finite`` and ``direct_kaug``.
+
+.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_compute_FIM.py
+ :language: python
+ :pyobject: main
+
+Successful run of the above code shows the following figure:
+
+.. figure:: grid-1.png
+ :scale: 35 %
+
+A heatmap shows the change of the objective function, a.k.a. the experimental information content, in the design region. Horizontal and vertical axes are two design variables, while the color of each grid shows the experimental information content. Taking the Fig. Reactor case - A optimality as example, A-optimality shows that the most informative region is around $C_{A0}=5.0$ M, $T=300.0$ K, while the least informative region is around $C_{A0}=1.0$ M, $T=700.0$ K.
+
+Step 5: Gradient-based optimization
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Pyomo.DoE accomplishes gradient-based optimization with the ``stochastic_program`` function for A- and D-optimality design.
+
+This function solves twice: It solves the square version of the MBDoE problem first, and then unfixes the design variables as degree of freedoms and solves again. In this way the optimization problem can be well initialized.
+
+.. literalinclude:: ../../../../pyomo/contrib/doe/examples/reactor_compute_FIM.py
+ :language: python
+ :pyobject: main
+
+
diff --git a/doc/OnlineDocs/contributed_packages/doe/flowchart.png b/doc/OnlineDocs/contributed_packages/doe/flowchart.png
new file mode 100644
index 00000000000..2e66566d2f6
Binary files /dev/null and b/doc/OnlineDocs/contributed_packages/doe/flowchart.png differ
diff --git a/doc/OnlineDocs/contributed_packages/doe/grid-1.png b/doc/OnlineDocs/contributed_packages/doe/grid-1.png
new file mode 100644
index 00000000000..6c96bbab7e0
Binary files /dev/null and b/doc/OnlineDocs/contributed_packages/doe/grid-1.png differ
diff --git a/doc/OnlineDocs/contributed_packages/doe/reactor.png b/doc/OnlineDocs/contributed_packages/doe/reactor.png
new file mode 100644
index 00000000000..7664493fd81
Binary files /dev/null and b/doc/OnlineDocs/contributed_packages/doe/reactor.png differ
diff --git a/doc/OnlineDocs/contributed_packages/doe/uml.png b/doc/OnlineDocs/contributed_packages/doe/uml.png
new file mode 100644
index 00000000000..e5280987722
Binary files /dev/null and b/doc/OnlineDocs/contributed_packages/doe/uml.png differ
diff --git a/doc/OnlineDocs/contributed_packages/incidence/api.rst b/doc/OnlineDocs/contributed_packages/incidence/api.rst
new file mode 100644
index 00000000000..38bf0be125b
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/api.rst
@@ -0,0 +1,14 @@
+.. _incidence_api:
+
+API Reference
+=============
+
+.. toctree::
+ incidence.rst
+ config.rst
+ interface.rst
+ matching.rst
+ connected.rst
+ triangularize.rst
+ dulmage_mendelsohn.rst
+ scc_solver.rst
diff --git a/doc/OnlineDocs/contributed_packages/incidence/config.rst b/doc/OnlineDocs/contributed_packages/incidence/config.rst
new file mode 100644
index 00000000000..06e4f5c5626
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/config.rst
@@ -0,0 +1,5 @@
+Incidence Options
+=================
+
+.. automodule:: pyomo.contrib.incidence_analysis.config
+ :members:
diff --git a/doc/OnlineDocs/contributed_packages/incidence/connected.rst b/doc/OnlineDocs/contributed_packages/incidence/connected.rst
new file mode 100644
index 00000000000..4cf60f62eba
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/connected.rst
@@ -0,0 +1,5 @@
+Weakly Connected Components
+===========================
+
+.. automodule:: pyomo.contrib.incidence_analysis.connected
+ :members:
diff --git a/doc/OnlineDocs/contributed_packages/incidence/dulmage_mendelsohn.rst b/doc/OnlineDocs/contributed_packages/incidence/dulmage_mendelsohn.rst
new file mode 100644
index 00000000000..6fe2bd59324
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/dulmage_mendelsohn.rst
@@ -0,0 +1,5 @@
+Dulmage-Mendelsohn Partition
+============================
+
+.. automodule:: pyomo.contrib.incidence_analysis.dulmage_mendelsohn
+ :members:
diff --git a/doc/OnlineDocs/contributed_packages/incidence/incidence.rst b/doc/OnlineDocs/contributed_packages/incidence/incidence.rst
new file mode 100644
index 00000000000..ebf481c00a7
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/incidence.rst
@@ -0,0 +1,5 @@
+Incident Variables
+==================
+
+.. automodule:: pyomo.contrib.incidence_analysis.incidence
+ :members:
diff --git a/doc/OnlineDocs/contributed_packages/incidence/index.rst b/doc/OnlineDocs/contributed_packages/incidence/index.rst
new file mode 100644
index 00000000000..ab0e07f6abc
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/index.rst
@@ -0,0 +1,19 @@
+Incidence Analysis
+==================
+
+Tools for constructing and analyzing the incidence graph of variables
+and constraints.
+
+This documentation contains the following resources:
+
+.. toctree::
+ :maxdepth: 1
+
+ overview.rst
+ tutorial.rst
+ api.rst
+
+If you are wondering what Incidence Analysis is and would like to learn more,
+please see :ref:`incidence_overview`. If you already know what
+Incidence Analysis is and are here for reference, see :ref:`incidence_tutorial`
+or :ref:`incidence_api` as needed.
diff --git a/doc/OnlineDocs/contributed_packages/incidence/interface.rst b/doc/OnlineDocs/contributed_packages/incidence/interface.rst
new file mode 100644
index 00000000000..29c92d8193c
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/interface.rst
@@ -0,0 +1,5 @@
+Pyomo Interfaces
+================
+
+.. automodule:: pyomo.contrib.incidence_analysis.interface
+ :members:
diff --git a/doc/OnlineDocs/contributed_packages/incidence/matching.rst b/doc/OnlineDocs/contributed_packages/incidence/matching.rst
new file mode 100644
index 00000000000..1941c7116cd
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/matching.rst
@@ -0,0 +1,5 @@
+Maximum Matching
+================
+
+.. automodule:: pyomo.contrib.incidence_analysis.matching
+ :members:
diff --git a/doc/OnlineDocs/contributed_packages/incidence/overview.rst b/doc/OnlineDocs/contributed_packages/incidence/overview.rst
new file mode 100644
index 00000000000..544740cae57
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/overview.rst
@@ -0,0 +1,46 @@
+.. _incidence_overview:
+
+Overview
+========
+
+What is Incidence Analysis?
+---------------------------
+
+A Pyomo extension for constructing the bipartite incidence graph of variables
+and constraints, and an interface to useful algorithms for analyzing or
+decomposing this graph.
+
+Why is Incidence Analysis useful?
+---------------------------------
+
+It can identify the source of certain types of singularities in a system of
+variables and constraints. These singularities often violate assumptions made
+while modeling a physical system or assumptions required for an optimization
+solver to guarantee convergence. In particular, interior point methods used for
+nonlinear local optimization require the Jacobian of equality constraints (and
+active inequalities) to be full row rank, and this package implements the
+Dulmage-Mendelsohn partition, which can be used to determine if this Jacobian
+is structurally rank-deficient.
+
+Who develops and maintains Incidence Analysis?
+----------------------------------------------
+
+This extension was developed by Robert Parker while a PhD student in
+Professor Biegler's lab at Carnegie Mellon University, with guidance
+from Bethany Nicholson and John Siirola at Sandia.
+
+How can I cite Incidence Analysis?
+----------------------------------
+
+We are working on a journal article about Incidence Analysis and the underlying
+methods. In the meantime, if you use Incidence Analysis in your research, you
+may cite the following conference paper:
+
+.. code-block:: bibtex
+
+ @inproceedings{Parker2023Dulmage,
+ title={{An application of the Dulmage-Mendelsohn partition to the analysis of a discretized dynamic chemical looping combustion reactor model}},
+ author={Robert Parker and Chinedu Okoli and Bethany Nicholson and John Siirola and Lorenz Biegler},
+ booktitle={Proceedings of FOCAPO/CPC 2023},
+ year={2023}
+ }
diff --git a/doc/OnlineDocs/contributed_packages/incidence/scc_solver.rst b/doc/OnlineDocs/contributed_packages/incidence/scc_solver.rst
new file mode 100644
index 00000000000..35f494af1a1
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/scc_solver.rst
@@ -0,0 +1,5 @@
+Block Triangular Decomposition Solver
+=====================================
+
+.. automodule:: pyomo.contrib.incidence_analysis.scc_solver
+ :members:
diff --git a/doc/OnlineDocs/contributed_packages/incidence/triangularize.rst b/doc/OnlineDocs/contributed_packages/incidence/triangularize.rst
new file mode 100644
index 00000000000..a051086a859
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/triangularize.rst
@@ -0,0 +1,5 @@
+Block Triangularization
+=======================
+
+.. automodule:: pyomo.contrib.incidence_analysis.triangularize
+ :members:
diff --git a/doc/OnlineDocs/contributed_packages/incidence/tutorial.bt.rst b/doc/OnlineDocs/contributed_packages/incidence/tutorial.bt.rst
new file mode 100644
index 00000000000..6710c0dbb50
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/tutorial.bt.rst
@@ -0,0 +1,107 @@
+Debugging a numeric singularity using block triangularization
+=============================================================
+
+We start with some imports. To debug a *numeric* singularity, we will need
+``PyomoNLP`` from :ref:`pynumero` to get the constraint Jacobian,
+and will need NumPy to compute condition numbers.
+
+.. doctest::
+ :skipif: not scipy_available or not asl_available or not networkx_available
+
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.contrib.pynumero.interfaces.pyomo_nlp import PyomoNLP
+ >>> from pyomo.contrib.incidence_analysis import IncidenceGraphInterface
+ >>> import numpy as np
+
+We now build the model we would like to debug. Compared to the model in
+:ref:`incidence_tutorial_dm`, we have converted the sum equation to use a sum
+over component flow rates rather than a sum over mass fractions.
+
+.. doctest::
+ :skipif: not scipy_available or not asl_available or not networkx_available
+
+ >>> m = pyo.ConcreteModel()
+ >>> m.components = pyo.Set(initialize=[1, 2, 3])
+ >>> m.x = pyo.Var(m.components, initialize=1.0/3.0)
+ >>> m.flow_comp = pyo.Var(m.components, initialize=10.0)
+ >>> m.flow = pyo.Var(initialize=30.0)
+ >>> m.density = pyo.Var(initialize=1.0)
+ >>> # This equation is new!
+ >>> m.sum_flow_eqn = pyo.Constraint(
+ ... expr=sum(m.flow_comp[j] for j in m.components) == m.flow
+ ... )
+ >>> m.holdup_eqn = pyo.Constraint(m.components, expr={
+ ... j: m.x[j]*m.density - 1 == 0 for j in m.components
+ ... })
+ >>> m.density_eqn = pyo.Constraint(
+ ... expr=1/m.density - sum(1/m.x[j] for j in m.components) == 0
+ ... )
+ >>> m.flow_eqn = pyo.Constraint(m.components, expr={
+ ... j: m.x[j]*m.flow - m.flow_comp[j] == 0 for j in m.components
+ ... })
+
+We now construct the incidence graph and check unmatched variables and
+constraints to validate structural nonsingularity.
+
+.. doctest::
+ :skipif: not scipy_available or not asl_available or not networkx_available
+
+ >>> igraph = IncidenceGraphInterface(m, include_inequality=False)
+ >>> var_dmp, con_dmp = igraph.dulmage_mendelsohn()
+ >>> print(len(var_dmp.unmatched))
+ 0
+ >>> print(len(con_dmp.unmatched))
+ 0
+
+Our system is structurally nonsingular. Now we check whether we are numerically
+nonsingular (well-conditioned) by checking the condition number.
+Admittedly, deciding if a matrix is "singular" by looking at its condition
+number is somewhat of an art. We might define "numerically singular" as having a
+condition number greater than the inverse of machine precision (approximately
+``1e16``), but poorly conditioned matrices can cause problems even if they don't
+meet this definition. Here we use ``1e10`` as a somewhat arbitrary condition
+number threshold to indicate a problem in our system.
+
+.. doctest::
+ :skipif: not scipy_available or not asl_available or not networkx_available
+
+ >>> # PyomoNLP requires exactly one objective function
+ >>> m._obj = pyo.Objective(expr=0.0)
+ >>> nlp = PyomoNLP(m)
+ >>> cond_threshold = 1e10
+ >>> cond = np.linalg.cond(nlp.evaluate_jacobian_eq().toarray())
+ >>> print(cond > cond_threshold)
+ True
+
+The system is poorly conditioned. Now we can check diagonal blocks of a block
+triangularization to determine which blocks are causing the poor conditioning.
+
+.. code-block:: python
+
+ >>> var_blocks, con_blocks = igraph.block_triangularize()
+ >>> for i, (vblock, cblock) in enumerate(zip(var_blocks, con_blocks)):
+ ... submatrix = nlp.extract_submatrix_jacobian(vblock, cblock)
+ ... cond = np.linalg.cond(submatrix.toarray())
+ ... print(f"block {i}: {cond}")
+ ... if cond > cond_threshold:
+ ... for var in vblock:
+ ... print(f" {var.name}")
+ ... for con in cblock:
+ ... print(f" {con.name}")
+ block 0: 24.492504515710433
+ block 1: 1.2480741394486336e+17
+ flow
+ flow_comp[1]
+ flow_comp[2]
+ flow_comp[3]
+ sum_flow_eqn
+ flow_eqn[1]
+ flow_eqn[2]
+ flow_eqn[3]
+
+We see that the second block is causing the singularity, and that this block
+contains the sum equation that we modified for this example. This suggests that
+converting this equation to sum over flow rates rather than mass fractions just
+converted a structural singularity to a numeric singularity, and didn't really
+solve our problem. To see a fix that *does* resolve the singularity, see
+:ref:`incidence_tutorial_dm`.
diff --git a/doc/OnlineDocs/contributed_packages/incidence/tutorial.btsolve.rst b/doc/OnlineDocs/contributed_packages/incidence/tutorial.btsolve.rst
new file mode 100644
index 00000000000..1ff0b6c5afe
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/tutorial.btsolve.rst
@@ -0,0 +1,72 @@
+Solving a square system with a block triangular decomposition
+=============================================================
+
+We start with imports. The key function from Incidence Analysis we will use is
+``solve_strongly_connected_components``.
+
+.. doctest::
+ :skipif: not networkx_available or not scipy_available or not asl_available
+
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.contrib.incidence_analysis import (
+ ... solve_strongly_connected_components
+ ... )
+
+Now we construct the model we would like to solve. This is a model with the
+same structure as the "fixed model" in :ref:`incidence_tutorial_dm`.
+
+.. doctest::
+ :skipif: not networkx_available or not scipy_available or not asl_available
+
+ >>> m = pyo.ConcreteModel()
+ >>> m.components = pyo.Set(initialize=[1, 2, 3])
+ >>> m.x = pyo.Var(m.components, initialize=1.0/3.0)
+ >>> m.flow_comp = pyo.Var(m.components, initialize=10.0)
+ >>> m.flow = pyo.Var(initialize=30.0)
+ >>> m.dens_bulk = pyo.Var(initialize=1.0)
+ >>> m.dens_skel = pyo.Var(initialize=1.0)
+ >>> m.porosity = pyo.Var(initialize=0.25)
+ >>> m.velocity = pyo.Param(initialize=1.0)
+ >>> m.holdup = pyo.Param(
+ ... m.components, initialize={j: 1.0+j/10.0 for j in m.components}
+ ... )
+ >>> m.sum_eqn = pyo.Constraint(
+ ... expr=sum(m.x[j] for j in m.components) - 1 == 0
+ ... )
+ >>> m.holdup_eqn = pyo.Constraint(m.components, expr={
+ ... j: m.x[j]*m.dens_bulk - m.holdup[j] == 0 for j in m.components
+ ... })
+ >>> m.dens_skel_eqn = pyo.Constraint(
+ ... expr=1/m.dens_skel - sum(1e-3/m.x[j] for j in m.components) == 0
+ ... )
+ >>> m.dens_bulk_eqn = pyo.Constraint(
+ ... expr=m.dens_bulk == (1 - m.porosity)*m.dens_skel
+ ... )
+ >>> m.flow_eqn = pyo.Constraint(m.components, expr={
+ ... j: m.x[j]*m.flow - m.flow_comp[j] == 0 for j in m.components
+ ... })
+ >>> m.flow_dens_eqn = pyo.Constraint(
+ ... expr=m.flow == m.velocity*m.dens_bulk
+ ... )
+
+Solving via a block triangular decomposition is useful in cases where the full
+model does not converge when considered simultaneously by a Newton solver.
+In this case, we specify a solver to use for the diagonal blocks and call
+``solve_strongly_connected_components``.
+
+.. doctest::
+ :skipif: not networkx_available or not scipy_available or not asl_available
+
+ >>> # Suppose a solve like this does not converge
+ >>> # pyo.SolverFactory("scipy.fsolve").solve(m)
+
+ >>> # We solve via block-triangular decomposition
+ >>> solver = pyo.SolverFactory("scipy.fsolve")
+ >>> res_list = solve_strongly_connected_components(m, solver=solver)
+
+We can now display the variable values at the solution:
+
+.. code-block:: python
+
+ for var in m.component_objects(pyo.Var):
+ var.pprint()
diff --git a/doc/OnlineDocs/contributed_packages/incidence/tutorial.dm.rst b/doc/OnlineDocs/contributed_packages/incidence/tutorial.dm.rst
new file mode 100644
index 00000000000..c14861e9fc8
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/tutorial.dm.rst
@@ -0,0 +1,191 @@
+.. _incidence_tutorial_dm:
+
+Debugging a structural singularity with the Dulmage-Mendelsohn partition
+========================================================================
+
+We start with some imports and by creating a Pyomo model we would like
+to debug. Usually the model is much larger and more complicated than this.
+This particular system appeared when debugging a dynamic 1-D partial
+differential-algebraic equation (PDAE) model representing a chemical looping
+combustion reactor.
+
+.. doctest::
+ :skipif: not scipy_available or not networkx_available or not asl_available
+
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.contrib.incidence_analysis import IncidenceGraphInterface
+
+ >>> m = pyo.ConcreteModel()
+ >>> m.components = pyo.Set(initialize=[1, 2, 3])
+ >>> m.x = pyo.Var(m.components, initialize=1.0/3.0)
+ >>> m.flow_comp = pyo.Var(m.components, initialize=10.0)
+ >>> m.flow = pyo.Var(initialize=30.0)
+ >>> m.density = pyo.Var(initialize=1.0)
+ >>> m.sum_eqn = pyo.Constraint(
+ ... expr=sum(m.x[j] for j in m.components) - 1 == 0
+ ... )
+ >>> m.holdup_eqn = pyo.Constraint(m.components, expr={
+ ... j: m.x[j]*m.density - 1 == 0 for j in m.components
+ ... })
+ >>> m.density_eqn = pyo.Constraint(
+ ... expr=1/m.density - sum(1/m.x[j] for j in m.components) == 0
+ ... )
+ >>> m.flow_eqn = pyo.Constraint(m.components, expr={
+ ... j: m.x[j]*m.flow - m.flow_comp[j] == 0 for j in m.components
+ ... })
+
+To check this model for structural singularity, we apply the Dulmage-Mendelsohn
+partition. ``var_dm_partition`` and ``con_dm_partition`` are named tuples
+with fields for each of the four subsets defined by the partition:
+``unmatched``, ``overconstrained``, ``square``, and ``underconstrained``.
+
+.. doctest::
+ :skipif: not scipy_available or not networkx_available or not asl_available
+
+ >>> igraph = IncidenceGraphInterface(m)
+ >>> # Make sure we have a square system
+ >>> print(len(igraph.variables))
+ 8
+ >>> print(len(igraph.constraints))
+ 8
+ >>> var_dm_partition, con_dm_partition = igraph.dulmage_mendelsohn()
+
+If any variables or constraints are unmatched, the (Jacobian of the) model
+is structurally singular.
+
+.. code-block:: python
+
+ >>> # Note that the unmatched variables/constraints are not mathematically
+ >>> # unique and could change with implementation!
+ >>> for var in var_dm_partition.unmatched:
+ ... print(var.name)
+ flow_comp[1]
+ >>> for con in con_dm_partition.unmatched:
+ ... print(con.name)
+ density_eqn
+
+This model has one unmatched constraint and one unmatched variable, so it is
+structurally singular. However, the unmatched variable and constraint are not
+unique. For example, ``flow_comp[2]`` could have been unmatched instead of
+``flow_comp[1]``. The exact variables and constraints that are unmatched depends
+on both the order in which variables are identified in Pyomo expressions and
+the implementation of the matching algorithm. For a given implementation,
+however, these variables and constraints should be deterministic.
+
+Unique subsets of variables and constraints that are useful when debugging a
+structural singularity are the underconstrained and overconstrained subsystems.
+The variables in the underconstrained subsystem are contained in the
+``unmatched`` and ``underconstrained`` fields of the ``var_dm_partition`` named tuple,
+while the constraints are contained in the ``underconstrained`` field of the
+``con_dm_partition`` named tuple.
+The variables in the overconstrained subsystem are contained in the
+``overconstrained`` field of the ``var_dm_partition`` named tuple, while the constraints
+are contained in the ``overconstrained`` and ``unmatched`` fields of the
+``con_dm_partition`` named tuple.
+
+We now construct the underconstrained and overconstrained subsystems:
+
+.. doctest::
+ :skipif: not scipy_available or not networkx_available or not asl_available
+
+ >>> uc_var = var_dm_partition.unmatched + var_dm_partition.underconstrained
+ >>> uc_con = con_dm_partition.underconstrained
+ >>> oc_var = var_dm_partition.overconstrained
+ >>> oc_con = con_dm_partition.overconstrained + con_dm_partition.unmatched
+
+And display the variables and constraints contained in each:
+
+.. code-block:: python
+
+ >>> # Note that while these variables/constraints are uniquely determined,
+ >>> # their order is not!
+
+ >>> # Overconstrained subsystem
+ >>> for var in oc_var:
+ >>> print(var.name)
+ x[1]
+ density
+ x[2]
+ x[3]
+ >>> for con in oc_con:
+ >>> print(con.name)
+ sum_eqn
+ holdup_eqn[1]
+ holdup_eqn[2]
+ holdup_eqn[3]
+ density_eqn
+
+ >>> # Underconstrained subsystem
+ >>> for var in uc_var:
+ >>> print(var.name)
+ flow_comp[1]
+ flow
+ flow_comp[2]
+ flow_comp[3]
+ >>> for con in uc_con:
+ >>> print(con.name)
+ flow_eqn[1]
+ flow_eqn[2]
+ flow_eqn[3]
+
+At this point we must use our intuition about the system being modeled to
+identify "what is causing" the singularity. Looking at the under and over-
+constrained systems, it appears that we are missing an equation to calculate
+``flow``, the total flow rate, and that ``density`` is over-specified as it
+is computed by both the bulk density equation and one of the component density
+equations.
+
+With this knowledge, we can eventually figure out (a) that we need an equation
+to calculate ``flow`` from density and (b) that our "bulk density equation"
+is actually a *skeletal* density equation. Admittedly, this is difficult to
+figure out without the full context behind this particular system.
+
+The following code constructs a new version of the model and verifies that it
+is structurally nonsingular:
+
+.. doctest::
+ :skipif: not scipy_available or not networkx_available or not asl_available
+
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.contrib.incidence_analysis import IncidenceGraphInterface
+
+ >>> m = pyo.ConcreteModel()
+ >>> m.components = pyo.Set(initialize=[1, 2, 3])
+ >>> m.x = pyo.Var(m.components, initialize=1.0/3.0)
+ >>> m.flow_comp = pyo.Var(m.components, initialize=10.0)
+ >>> m.flow = pyo.Var(initialize=30.0)
+ >>> m.dens_bulk = pyo.Var(initialize=1.0)
+ >>> m.dens_skel = pyo.Var(initialize=1.0)
+ >>> m.porosity = pyo.Var(initialize=0.25)
+ >>> m.velocity = pyo.Param(initialize=1.0)
+ >>> m.sum_eqn = pyo.Constraint(
+ ... expr=sum(m.x[j] for j in m.components) - 1 == 0
+ ... )
+ >>> m.holdup_eqn = pyo.Constraint(m.components, expr={
+ ... j: m.x[j]*m.dens_bulk - 1 == 0 for j in m.components
+ ... })
+ >>> m.dens_skel_eqn = pyo.Constraint(
+ ... expr=1/m.dens_skel - sum(1/m.x[j] for j in m.components) == 0
+ ... )
+ >>> m.dens_bulk_eqn = pyo.Constraint(
+ ... expr=m.dens_bulk == (1 - m.porosity)*m.dens_skel
+ ... )
+ >>> m.flow_eqn = pyo.Constraint(m.components, expr={
+ ... j: m.x[j]*m.flow - m.flow_comp[j] == 0 for j in m.components
+ ... })
+ >>> m.flow_dens_eqn = pyo.Constraint(
+ ... expr=m.flow == m.velocity*m.dens_bulk
+ ... )
+
+ >>> igraph = IncidenceGraphInterface(m, include_inequality=False)
+ >>> print(len(igraph.variables))
+ 10
+ >>> print(len(igraph.constraints))
+ 10
+ >>> var_dm_partition, con_dm_partition = igraph.dulmage_mendelsohn()
+
+ >>> # There are now no unmatched variables or equations
+ >>> print(len(var_dm_partition.unmatched))
+ 0
+ >>> print(len(con_dm_partition.unmatched))
+ 0
diff --git a/doc/OnlineDocs/contributed_packages/incidence/tutorial.rst b/doc/OnlineDocs/contributed_packages/incidence/tutorial.rst
new file mode 100644
index 00000000000..4b22fc16c53
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/incidence/tutorial.rst
@@ -0,0 +1,14 @@
+.. _incidence_tutorial:
+
+Incidence Analysis Tutorial
+===========================
+
+This tutorial walks through examples of the most common use cases for
+Incidence Analysis:
+
+.. toctree::
+ :maxdepth: 1
+
+ tutorial.dm.rst
+ tutorial.bt.rst
+ tutorial.btsolve.rst
diff --git a/doc/OnlineDocs/contributed_packages/index.rst b/doc/OnlineDocs/contributed_packages/index.rst
index 997fe589425..f893753780e 100644
--- a/doc/OnlineDocs/contributed_packages/index.rst
+++ b/doc/OnlineDocs/contributed_packages/index.rst
@@ -16,9 +16,12 @@ Contributed packages distributed with Pyomo:
:maxdepth: 1
community.rst
+ doe/doe.rst
gdpopt.rst
iis.rst
+ incidence/index.rst
mindtpy.rst
+ mpc/index.rst
multistart.rst
preprocessing.rst
parmest/index.rst
diff --git a/doc/OnlineDocs/contributed_packages/mindtpy.rst b/doc/OnlineDocs/contributed_packages/mindtpy.rst
index 507216045ed..a850a42c740 100644
--- a/doc/OnlineDocs/contributed_packages/mindtpy.rst
+++ b/doc/OnlineDocs/contributed_packages/mindtpy.rst
@@ -3,7 +3,7 @@ MindtPy Solver
The Mixed-Integer Nonlinear Decomposition Toolbox in Pyomo (MindtPy) solver
allows users to solve Mixed-Integer Nonlinear Programs (MINLP) using decomposition algorithms.
-These decomposition algorithms usually rely on the solution of Mixed-Intger Linear Programs
+These decomposition algorithms usually rely on the solution of Mixed-Integer Linear Programs
(MILP) and Nonlinear Programs (NLP).
The following algorithms are currently available in MindtPy:
@@ -18,6 +18,10 @@ The following algorithms are currently available in MindtPy:
Usage and early implementation details for MindtPy can be found in the PSE 2018 paper Bernal et al.,
(`ref `_,
`preprint `_).
+This solver implementation has been developed by `David Bernal `_
+and `Zedong Peng `_ as part of research efforts at the `Bernal Research Group
+`_ and the `Grossmann Research Group `_
+at Purdue University and Carnegie Mellon University.
.. _Duran & Grossmann, 1986: https://dx.doi.org/10.1007/BF02592064
.. _Westerlund & Petterson, 1995: http://dx.doi.org/10.1016/0098-1354(95)87027-X
@@ -120,7 +124,7 @@ LP/NLP Based Branch-and-Bound
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
MindtPy also supports single-tree implementation of Outer-Approximation (OA) algorithm, which is known as LP/NLP based branch-and-bound algorithm originally described in [`Quesada & Grossmann, 1992`_].
-The LP/NLP based branch-and-bound algorithm in MindtPy is implemeted based on the LazyConstraintCallback function in commercial solvers.
+The LP/NLP based branch-and-bound algorithm in MindtPy is implemented based on the LazyConstraintCallback function in commercial solvers.
.. _Quesada & Grossmann, 1992: https://www.sciencedirect.com/science/article/abs/pii/0098135492800288
@@ -181,7 +185,7 @@ A usage example for OA with solution pool is as follows:
>>> pyo.SolverFactory('mindtpy').solve(model,
... strategy='OA',
- ... mip_solver='cplex_peristent',
+ ... mip_solver='cplex_persistent',
... nlp_solver='ipopt',
... solution_pool=True,
... num_solution_iteration=10, # default=5
@@ -310,6 +314,6 @@ Report a Bug
If you find a bug in MindtPy, we will be grateful if you could
- submit an `issue`_ in Pyomo repository
-- directly contact David Bernal and Zedong Peng .
+- directly contact David Bernal and Zedong Peng .
.. _issue: https://github.com/Pyomo/pyomo/issues
diff --git a/doc/OnlineDocs/contributed_packages/mpc/examples.rst b/doc/OnlineDocs/contributed_packages/mpc/examples.rst
new file mode 100644
index 00000000000..95204192358
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/mpc/examples.rst
@@ -0,0 +1,6 @@
+Examples
+========
+
+Please see ``pyomo/contrib/mpc/examples/cstr/run_openloop.py`` and
+``pyomo/contrib/mpc/examples/cstr/run_mpc.py`` for examples of some simple
+use cases.
diff --git a/doc/OnlineDocs/contributed_packages/mpc/faq.rst b/doc/OnlineDocs/contributed_packages/mpc/faq.rst
new file mode 100644
index 00000000000..e42e7184696
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/mpc/faq.rst
@@ -0,0 +1,16 @@
+Frequently asked questions
+==========================
+
+#. Why not use Pandas DataFrames?
+
+Pandas DataFrames are a natural data structure for storing "columns" of
+time series data. These columns, or individual time series, could each represent
+the data for a single variable. This is very similar to the TimeSeriesData
+class introduced in this package.
+The reason a new data structure is introduced is primarily that a DataFrame
+does not provide any utility for converting labels into a consistent format,
+as TimeSeriesData does by accepting variables, strings, slices, etc.
+as keys and converting them into the form of a time-indexed ComponentUID.
+Also, DataFrames do not have convenient analogs for scalar data and
+time interval data, which this package provides as the ScalarData
+and IntervalData classes with very similar APIs to TimeSeriesData.
diff --git a/doc/OnlineDocs/contributed_packages/mpc/index.rst b/doc/OnlineDocs/contributed_packages/mpc/index.rst
new file mode 100644
index 00000000000..b93abf223e2
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/mpc/index.rst
@@ -0,0 +1,12 @@
+MPC
+===
+
+This package contains data structures and utilities for dynamic optimization
+and rolling horizon applications, e.g. model predictive control.
+
+.. toctree::
+ :maxdepth: 1
+
+ overview.rst
+ examples.rst
+ faq.rst
diff --git a/doc/OnlineDocs/contributed_packages/mpc/overview.rst b/doc/OnlineDocs/contributed_packages/mpc/overview.rst
new file mode 100644
index 00000000000..f5dbe85e523
--- /dev/null
+++ b/doc/OnlineDocs/contributed_packages/mpc/overview.rst
@@ -0,0 +1,210 @@
+Overview
+========
+
+What does this package contain?
+-------------------------------
+
+#. Data structures for values and time series data associated with time-indexed variables (or parameters, or named expressions). Examples are setpoint values associated with a subset of state variables or time series data from a simulation
+
+#. Utilities for loading and extracting this data into and from variables in a model
+
+#. Utilities for constructing components from this data (expressions, constraints, and objectives) that are useful for dynamic optimization
+
+What is the goal of this package?
+---------------------------------
+
+This package was written to help developers of Pyomo-based dynamic optimization
+case studies, especially rolling horizon dynamic optimization case studies,
+write scripts that are small, legible, and maintainable.
+It does this by providing utilities for mundane data-management and model
+construction tasks, allowing the developer to focus on their application.
+
+Why is this package useful?
+---------------------------
+
+First, it is not normally easy to extract "flattened" time series data,
+in which all indexing structure other than time-indexing has been
+flattened to yield a set of one-dimensional arrays, from a Pyomo model.
+This is an extremely convenient data structure to have for plotting,
+analysis, initialization, and manipulation of dynamic models.
+If all variables are indexed by time and only time, this data is relatively
+easy to obtain.
+The first issue comes up when dealing with components that are indexed by
+time in addition to some other set(s). For example:
+
+.. doctest::
+
+ >>> import pyomo.environ as pyo
+
+ >>> m = pyo.ConcreteModel()
+ >>> m.time = pyo.Set(initialize=[0, 1, 2])
+ >>> m.comp = pyo.Set(initialize=["A", "B"])
+ >>> m.var = pyo.Var(m.time, m.comp, initialize=1.0)
+
+ >>> t0 = m.time.first()
+ >>> data = {
+ ... m.var[t0, j].name: [m.var[i, j].value for i in m.time]
+ ... for j in m.comp
+ ... }
+ >>> data
+ {'var[0,A]': [1.0, 1.0, 1.0], 'var[0,B]': [1.0, 1.0, 1.0]}
+
+To generate data in this form, we need to (a) know that our variable is indexed
+by time and ``m.comp`` and (b) arbitrarily select a time index ``t0`` to
+generate a unique key for each time series.
+This gets more difficult when blocks and time-indexed blocks are used as well.
+The first difficulty can be alleviated using
+``flatten_dae_components`` from ``pyomo.dae.flatten``:
+
+.. doctest::
+
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.dae.flatten import flatten_dae_components
+
+ >>> m = pyo.ConcreteModel()
+ >>> m.time = pyo.Set(initialize=[0, 1, 2])
+ >>> m.comp = pyo.Set(initialize=["A", "B"])
+ >>> m.var = pyo.Var(m.time, m.comp, initialize=1.0)
+
+ >>> t0 = m.time.first()
+ >>> scalar_vars, dae_vars = flatten_dae_components(m, m.time, pyo.Var)
+ >>> data = {var[t0].name: list(var[:].value) for var in dae_vars}
+ >>> data
+ {'var[0,A]': [1.0, 1.0, 1.0], 'var[0,B]': [1.0, 1.0, 1.0]}
+
+Addressing the arbitrary ``t0`` index requires us to ask what key we
+would like to use to identify each time series in our data structure.
+The key should uniquely correspond to a component, or "sub-component"
+that is indexed only by time. A slice, e.g. ``m.var[:, "A"]`` seems
+natural. However, Pyomo provides a better data structure that can
+be constructed from a component, slice, or string, called
+``ComponentUID``. Being constructable from a string is important as
+we may want to store or serialize this data in a form that is agnostic
+of any particular ``ConcreteModel`` object.
+We can now generate our data structure as:
+
+.. doctest::
+
+ >>> data = {
+ ... pyo.ComponentUID(var.referent): list(var[:].value)
+ ... for var in dae_vars
+ ... }
+ >>> data
+ {var[*,A]: [1.0, 1.0, 1.0], var[*,B]: [1.0, 1.0, 1.0]}
+
+This is the structure of the underlying dictionary in the ``TimeSeriesData``
+class provided by this package. We can generate this data using this package
+as:
+
+.. doctest::
+
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.contrib.mpc import DynamicModelInterface
+
+ >>> m = pyo.ConcreteModel()
+ >>> m.time = pyo.Set(initialize=[0, 1, 2])
+ >>> m.comp = pyo.Set(initialize=["A", "B"])
+ >>> m.var = pyo.Var(m.time, m.comp, initialize=1.0)
+
+ >>> # Construct a helper class for interfacing model with data
+ >>> helper = DynamicModelInterface(m, m.time)
+
+ >>> # Generates a TimeSeriesData object
+ >>> series_data = helper.get_data_at_time()
+
+ >>> # Get the underlying dictionary
+ >>> data = series_data.get_data()
+ >>> data
+ {var[*,A]: [1.0, 1.0, 1.0], var[*,B]: [1.0, 1.0, 1.0]}
+
+The first value proposition of this package is that ``DynamicModelInterface``
+and ``TimeSeriesData`` provide wrappers to ease loading and extraction of data
+via ``flatten_dae_components`` and ``ComponentUID``.
+
+The second difficulty addressed by this package is that of extracting and
+loading data between (potentially) different models.
+For instance, in model predictive control, we often want to extract data from
+a particular time point in a plant model and load it into a controller model
+as initial conditions. This can be done as follows:
+
+.. doctest::
+
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.contrib.mpc import DynamicModelInterface
+
+ >>> m1 = pyo.ConcreteModel()
+ >>> m1.time = pyo.Set(initialize=[0, 1, 2])
+ >>> m1.comp = pyo.Set(initialize=["A", "B"])
+ >>> m1.var = pyo.Var(m1.time, m1.comp, initialize=1.0)
+
+ >>> m2 = pyo.ConcreteModel()
+ >>> m2.time = pyo.Set(initialize=[0, 1, 2])
+ >>> m2.comp = pyo.Set(initialize=["A", "B"])
+ >>> m2.var = pyo.Var(m2.time, m2.comp, initialize=2.0)
+
+ >>> # Construct helper objects
+ >>> m1_helper = DynamicModelInterface(m1, m1.time)
+ >>> m2_helper = DynamicModelInterface(m2, m2.time)
+
+ >>> # Extract data from final time point of m2
+ >>> tf = m2.time.last()
+ >>> tf_data = m2_helper.get_data_at_time(tf)
+
+ >>> # Load data into initial time point of m1
+ >>> t0 = m1.time.first()
+ >>> m1_helper.load_data(tf_data, time_points=t0)
+
+ >>> # Get TimeSeriesData object
+ >>> series_data = m1_helper.get_data_at_time()
+ >>> # Get underlying dictionary
+ >>> series_data.get_data()
+ {var[*,A]: [2.0, 1.0, 1.0], var[*,B]: [2.0, 1.0, 1.0]}
+
+.. note::
+
+ Here we rely on the fact that our variable has the same name in
+ both models.
+
+Finally, this package provides methods for constructing components like
+tracking cost expressions and piecewise-constant constraints from the
+provided data structures. For example, the following code constructs
+a tracking cost expression.
+
+.. doctest::
+
+ >>> import pyomo.environ as pyo
+ >>> from pyomo.contrib.mpc import DynamicModelInterface
+
+ >>> m = pyo.ConcreteModel()
+ >>> m.time = pyo.Set(initialize=[0, 1, 2])
+ >>> m.comp = pyo.Set(initialize=["A", "B"])
+ >>> m.var = pyo.Var(m.time, m.comp, initialize=1.0)
+
+ >>> # Construct helper object
+ >>> helper = DynamicModelInterface(m, m.time)
+
+ >>> # Construct data structure for setpoints
+ >>> setpoint = {m.var[:, "A"]: 0.5, m.var[:, "B"]: 2.0}
+ >>> var_set, tr_cost = helper.get_penalty_from_target(setpoint)
+ >>> m.setpoint_idx = var_set
+ >>> m.tracking_cost = tr_cost
+ >>> m.tracking_cost.pprint()
+ tracking_cost : Size=6, Index=tracking_cost_index
+ Key : Expression
+ (0, 0) : (var[0,A] - 0.5)**2
+ (0, 1) : (var[1,A] - 0.5)**2
+ (0, 2) : (var[2,A] - 0.5)**2
+ (1, 0) : (var[0,B] - 2.0)**2
+ (1, 1) : (var[1,B] - 2.0)**2
+ (1, 2) : (var[2,B] - 2.0)**2
+
+
+These methods will hopefully allow developers to declutter dynamic optimization
+scripts and pay more attention to the application of the optimization problem
+rather than the setup of the optimization problem.
+
+Who develops and maintains this package?
+----------------------------------------
+
+This package was developed by Robert Parker while a PhD student in Larry
+Biegler's group at CMU, with guidance from Bethany Nicholson and John Siirola.
diff --git a/doc/OnlineDocs/contributed_packages/parmest/driver.rst b/doc/OnlineDocs/contributed_packages/parmest/driver.rst
index 79db50603a4..28238928b83 100644
--- a/doc/OnlineDocs/contributed_packages/parmest/driver.rst
+++ b/doc/OnlineDocs/contributed_packages/parmest/driver.rst
@@ -156,3 +156,14 @@ expression which is used to define "SecondStageCost". The objective
function can be used to customize data points and weights that are used
in parameter estimation.
+Suggested initialization procedure for parameter estimation problems
+--------------------------------------------------------------------
+
+To check the quality of initial guess values provided for the fitted parameters, we suggest solving a
+square instance of the problem prior to solving the parameter estimation problem using the following steps:
+
+1. Create :class:`~pyomo.contrib.parmest.parmest.Estimator` object. To initialize the parameter estimation solve from the square problem solution, set optional argument ``solver_options = {bound_push: 1e-8}``.
+
+2. Call :class:`~pyomo.contrib.parmest.parmest.Estimator.objective_at_theta` with optional argument ``(initialize_parmest_model=True)``. Different initial guess values for the fitted parameters can be provided using optional argument `theta_values` (**Pandas Dataframe**)
+
+3. Solve parameter estimation problem by calling :class:`~pyomo.contrib.parmest.parmest.Estimator.theta_est`
diff --git a/doc/OnlineDocs/contributed_packages/parmest/index.rst b/doc/OnlineDocs/contributed_packages/parmest/index.rst
index 0d53d80fe2c..2bf4942e632 100644
--- a/doc/OnlineDocs/contributed_packages/parmest/index.rst
+++ b/doc/OnlineDocs/contributed_packages/parmest/index.rst
@@ -10,8 +10,8 @@ Citation for parmest
If you use parmest, please cite [ParmestPaper]_
-Index of parmest documenation
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Index of parmest documentation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. toctree::
:maxdepth: 2
diff --git a/doc/OnlineDocs/contributed_packages/preprocessing.rst b/doc/OnlineDocs/contributed_packages/preprocessing.rst
index e6802886468..fd26f2bf6db 100644
--- a/doc/OnlineDocs/contributed_packages/preprocessing.rst
+++ b/doc/OnlineDocs/contributed_packages/preprocessing.rst
@@ -20,7 +20,7 @@ later be deprecated or combined, depending on their usefulness.
var_aggregator.VariableAggregator
bounds_to_vars.ConstraintToVarBoundTransform
induced_linearity.InducedLinearity
- constraint_tightener.TightenContraintFromVars
+ constraint_tightener.TightenConstraintFromVars
deactivate_trivial_constraints.TrivialConstraintDeactivator
detect_fixed_vars.FixedVarDetector
equality_propagate.FixedVarPropagator
@@ -93,7 +93,7 @@ Constraint Bounds Tightener
This transformation was developed by `Sunjeev Kale
`_ at Carnegie Mellon University.
-.. autoclass:: pyomo.contrib.preprocessing.plugins.constraint_tightener.TightenContraintFromVars
+.. autoclass:: pyomo.contrib.preprocessing.plugins.constraint_tightener.TightenConstraintFromVars
:members: apply_to, create_using
Trivial Constraint Deactivation
diff --git a/doc/OnlineDocs/contributed_packages/pynumero/index.rst b/doc/OnlineDocs/contributed_packages/pynumero/index.rst
index be82d843091..6ff8b29f812 100644
--- a/doc/OnlineDocs/contributed_packages/pynumero/index.rst
+++ b/doc/OnlineDocs/contributed_packages/pynumero/index.rst
@@ -1,3 +1,5 @@
+.. _pynumero:
+
PyNumero
========
diff --git a/doc/OnlineDocs/contributed_packages/pynumero/installation.rst b/doc/OnlineDocs/contributed_packages/pynumero/installation.rst
index 983d4565aa3..9ac6961d2de 100644
--- a/doc/OnlineDocs/contributed_packages/pynumero/installation.rst
+++ b/doc/OnlineDocs/contributed_packages/pynumero/installation.rst
@@ -10,11 +10,14 @@ https://github.com/Pyomo/pyomo/blob/main/pyomo/contrib/pynumero/build.py
and
https://github.com/Pyomo/pyomo/blob/main/pyomo/contrib/pynumero/src/CMakeLists.txt.
+Note that you will need a C++ compiler and CMake installed to build the
+PyNumero libraries.
+
Method 1
--------
One way to build PyNumero extensions is with the pyomo
-download-extensions and build-extensions subcommands. Note that
+`download-extensions` and `build-extensions` subcommands. Note that
this approach will build PyNumero without support for the HSL linear
solvers. ::
@@ -27,6 +30,18 @@ Method 2
If you want PyNumero support for the HSL solvers and you have an IPOPT compilation
for your machine, you can build PyNumero using the build script ::
- cd pyomo/contrib/pynumero/
- python build.py -DBUILD_ASL=ON -DBUILD_MA27=ON -DIPOPT_DIR=
+ python -m pyomo.contrib.pynumero.build -DBUILD_ASL=ON -DBUILD_MA27=ON -DIPOPT_DIR=
+
+Method 3
+--------
+
+You can build the PyNumero libraries from source using `cmake`. This
+generally works best when building from a source distribution of Pyomo.
+Assuming that you are starting in the root of the Pyomo source
+distribution, you can follow the normal CMake build process ::
+ mkdir build
+ cd build
+ ccmake ../pyomo/contrib/pynumero/src
+ make
+ make install
diff --git a/doc/OnlineDocs/contributed_packages/pynumero/tutorial.mpi_blocks.rst b/doc/OnlineDocs/contributed_packages/pynumero/tutorial.mpi_blocks.rst
index 2c97320be4e..b9cb1d5db7a 100644
--- a/doc/OnlineDocs/contributed_packages/pynumero/tutorial.mpi_blocks.rst
+++ b/doc/OnlineDocs/contributed_packages/pynumero/tutorial.mpi_blocks.rst
@@ -40,7 +40,7 @@ Note that blocks should only be set if the process/rank owns that
block.
The operations performed with `MPIBlockVector` are identical to the
-same operations peformed with `BlockVector` (or even NumPy arrays),
+same operations performed with `BlockVector` (or even NumPy arrays),
except that the operations are now performed in parallel.
`MPIBlockMatrix` construction is very similar. Consider the following
diff --git a/doc/OnlineDocs/contributed_packages/pyros.rst b/doc/OnlineDocs/contributed_packages/pyros.rst
index aeb7a293997..4ef57fbf26c 100644
--- a/doc/OnlineDocs/contributed_packages/pyros.rst
+++ b/doc/OnlineDocs/contributed_packages/pyros.rst
@@ -2,10 +2,11 @@
PyROS Solver
############
-PyROS (Pyomo Robust Optimization Solver) is a metasolver capability within Pyomo for solving non-convex,
-two-stage optimization models using adjustable robust optimization.
+PyROS (Pyomo Robust Optimization Solver) is a Pyomo-based meta-solver
+for non-convex, two-stage adjustable robust optimization problems.
-It was developed by **Natalie M. Isenberg** and **Chrysanthos E. Gounaris** of Carnegie Mellon University,
+It was developed by **Natalie M. Isenberg**, **Jason A. F. Sherman**,
+and **Chrysanthos E. Gounaris** of Carnegie Mellon University,
in collaboration with **John D. Siirola** of Sandia National Labs.
The developers gratefully acknowledge support from the U.S. Department of Energy's
`Institute for the Design of Advanced Energy Systems (IDAES) `_.
@@ -13,88 +14,116 @@ The developers gratefully acknowledge support from the U.S. Department of Energy
Methodology Overview
-----------------------------
-Below is an overview of the type of optimization models PyROS can accomodate.
+Below is an overview of the type of optimization models PyROS can accommodate.
-* PyROS is suitable for optimization models of **continuous variables** that may feature non-linearities (including **non-convexities**) in both the variables and uncertain parameters.
-* PyROS can handle **equality constraints** defining state variables, including implicit state variables that cannot be eliminated via reformulation.
-* PyROS allows for **two-stage** optimization problems that may feature both first-stage and second-stage degrees of freedom.
+* PyROS is suitable for optimization models of **continuous variables**
+ that may feature non-linearities (including **non-convexities**) in
+ both the variables and uncertain parameters.
+* PyROS can handle **equality constraints** defining state variables,
+ including implicit state variables that cannot be eliminated via
+ reformulation.
+* PyROS allows for **two-stage** optimization problems that may
+ feature both first-stage and second-stage degrees of freedom.
-The general form of a deterministic optimization problem that can be passed into PyROS is shown below:
+PyROS is designed to operate on deterministic models of the general form
+
+.. _deterministic-model:
.. math::
- \begin{align*}
- \displaystyle \min_{\substack{x \in \mathcal{X}, \\ z \in \mathbb{R}^n, y\in\mathbb{R}^a}} & ~~ f_1\left(x\right) + f_2\left(x,z,y; q^0\right) & \\
- \displaystyle \text{s.t.} \quad \: & ~~ g_i\left(x, z, y; q^0\right) \leq 0 & \forall i \in \mathcal{I} \\
- & ~~ h_j\left(x,z,y; q^0\right) = 0 & \forall j \in \mathcal{J} \\
- \end{align*}
+ \begin{array}{clll}
+ \displaystyle \min_{\substack{x \in \mathcal{X}, \\ z \in \mathbb{R}^{n_z}, y\in\mathbb{R}^{n_y}}} & ~~ f_1\left(x\right) + f_2(x,z,y; q^{\text{nom}}) & \\
+ \displaystyle \text{s.t.} & ~~ g_i(x, z, y; q^{\text{nom}}) \leq 0 & \forall\,i \in \mathcal{I} \\
+ & ~~ h_j(x,z,y; q^{\text{nom}}) = 0 & \forall\,j \in \mathcal{J} \\
+ \end{array}
where:
-* :math:`x \in \mathcal{X}` are the "design" variables (i.e., first-stage degrees of freedom), where :math:`\mathcal{X} \subseteq \mathbb{R}^m` is the feasible space defined by the model constraints that only reference these variables
-* :math:`z \in \mathbb{R}^n` are the "control" variables (i.e., second-stage degrees of freedom)
-* :math:`y \in \mathbb{R}^a` are the "state" variables
-* :math:`q \in \mathbb{R}^w` is the vector of parameters that we shall later consider to be uncertain, and :math:`q^0` is the vector of nominal values associated with those.
-* :math:`f_1\left(x\right)` are the terms of the objective function that depend only on design variables
-* :math:`f_2\left(x, z, y; q\right)` are the terms of the objective function that depend on control and/or state variables
-* :math:`g_i\left(x, z, y; q\right)` is the :math:`i^\text{th}` inequality constraint in set :math:`\mathcal{I}` (see Note)
-* :math:`h_j\left(x, z, y; q\right)` is the :math:`j^\text{th}` equality constraint in set :math:`\mathcal{J}` (see Note)
+* :math:`x \in \mathcal{X}` are the "design" variables
+ (i.e., first-stage degrees of freedom),
+ where :math:`\mathcal{X} \subseteq \mathbb{R}^{n_x}` is the feasible space defined by the model constraints
+ (including variable bounds specifications) referencing :math:`x` only.
+* :math:`z \in \mathbb{R}^{n_z}` are the "control" variables
+ (i.e., second-stage degrees of freedom)
+* :math:`y \in \mathbb{R}^{n_y}` are the "state" variables
+* :math:`q \in \mathbb{R}^{n_q}` is the vector of model parameters considered
+ uncertain, and :math:`q^{\text{nom}}` is the vector of nominal values
+ associated with those.
+* :math:`f_1\left(x\right)` are the terms of the objective function that depend
+ only on design variables
+* :math:`f_2\left(x, z, y; q\right)` are the terms of the objective function
+ that depend on all variables and the uncertain parameters
+* :math:`g_i\left(x, z, y; q\right)` is the :math:`i^\text{th}`
+ inequality constraint function in set :math:`\mathcal{I}`
+ (see :ref:`Note `)
+* :math:`h_j\left(x, z, y; q\right)` is the :math:`j^\text{th}`
+ equality constraint function in set :math:`\mathcal{J}`
+ (see :ref:`Note `)
+
+.. _var-bounds-to-ineqs:
.. note::
- * Applicable bounds on variables :math:`z` and/or :math:`y` are assumed to have been incorporated in the set of inequality constraints :math:`\mathcal{I}`.
- * A key requirement of PyROS is that each value of :math:`\left(x, z, q \right)` maps to a unique value of :math:`y`, a property that is assumed to be properly enforced by the system of equality constraints :math:`\mathcal{J}`. If such unique mapping does not hold, then the selection of 'state' (i.e., not degree of freedom) variables :math:`y` is incorrect, and one or more of the :math:`y` variables should be appropriately redesignated to be part of either :math:`x` or :math:`z`.
+ PyROS accepts models in which bounds are directly imposed on
+ ``Var`` objects representing components of the variables :math:`z`
+ and :math:`y`. These models are cast to
+ :ref:`the form above `
+ by reformulating the bounds as inequality constraints.
-In order to cast the robust optimization counterpart formulation of the above model, we shall now assume that the uncertain parameters may attain
-any realization from within an uncertainty set :math:`\mathcal{Q} \subseteq \mathbb{R}^w`, such that :math:`q^0 \in \mathcal{Q}`.
-The set :math:`\mathcal{Q}` is assumed to be closed and bounded, while it can be **either continuous or discrete**.
+.. _unique-mapping:
-Based on the above notation, the form of the robust counterpart addressed in PyROS is shown below:
+.. note::
+ A key requirement of PyROS is that each value of :math:`\left(x, z, q \right)`
+ maps to a unique value of :math:`y`, a property that is assumed to
+ be properly enforced by the system of equality constraints
+ :math:`\mathcal{J}`.
+ If the mapping is not unique, then the selection of 'state'
+ (i.e., not degree of freedom) variables :math:`y` is incorrect,
+ and one or more of the :math:`y` variables should be appropriately
+ redesignated to be part of either :math:`x` or :math:`z`.
+
+In order to cast the robust optimization counterpart of the
+:ref:`deterministic model `,
+we now assume that the uncertain parameters may attain
+any realization in a compact uncertainty set
+:math:`\mathcal{Q} \subseteq \mathbb{R}^{n_q}` containing
+the nominal value :math:`q^{\text{nom}}`.
+The set :math:`\mathcal{Q}` may be **either continuous or discrete**.
+
+Based on the above notation, the form of the robust counterpart addressed by PyROS is
.. math::
- \begin{align*}
+ \begin{array}{ccclll}
\displaystyle \min_{x \in \mathcal{X}}
& \displaystyle \max_{q \in \mathcal{Q}}
- & \displaystyle \min_{z \in \mathbb{R}^n, y \in \mathbb{R}^a} \ \ & \displaystyle ~~ f_1\left(x\right) + f_2\left(x, z, y, q\right) & & \\
- & & \text{s.t.} \quad \:& \displaystyle ~~ g_i\left(x, z, y, q\right) \leq 0 & & \forall i \in \mathcal{I}\\
- & & & \displaystyle ~~ h_j\left(x, z, y, q\right) = 0 & & \forall j \in \mathcal{J}
- \end{align*}
+ & \displaystyle \min_{\substack{z \in \mathbb{R}^{n_z},\\y \in \mathbb{R}^{n_y}}} \ \ & \displaystyle ~~ f_1\left(x\right) + f_2\left(x, z, y, q\right) \\
+ & & \text{s.t.}~ & \displaystyle ~~ g_i\left(x, z, y, q\right) \leq 0 & & \forall\, i \in \mathcal{I}\\
+ & & & \displaystyle ~~ h_j\left(x, z, y, q\right) = 0 & & \forall\,j \in \mathcal{J}
+ \end{array}
-In order to solve problems of the above type, PyROS implements the
-Generalized Robust Cutting-Set algorithm developed in [GRCSPaper]_.
+PyROS solves problems of this form using the
+Generalized Robust Cutting-Set algorithm developed in [Isenberg_et_al]_.
When using PyROS, please consider citing the above paper.
PyROS Required Inputs
-----------------------------
-The required inputs to the PyROS solver are the following:
+The required inputs to the PyROS solver are:
-* The determinisitic optimization model
+* The deterministic optimization model
* List of first-stage ("design") variables
* List of second-stage ("control") variables
-* List of parameters to be considered uncertain
+* List of parameters considered uncertain
* The uncertainty set
-* Subordinate local and global NLP optimization solvers
-
-Below is a list of arguments that PyROS expects the user to provide when calling the ``solve`` command.
-Note how all but the ``model`` argument **must** be specified as ``kwargs``.
-
-model : ``ConcreteModel``
- A ``ConcreteModel`` object representing the deterministic model.
-first_stage_variables : ``list(Var)``
- A list of Pyomo ``Var`` objects representing the first-stage degrees of freedom (design variables) in ``model``.
-second_stage_variables : ``list(Var)``
- A list of Pyomo ``Var`` objects representing second-stage degrees of freedom (control variables) in ``model``.
-uncertain_params : ``list(Param)``
- A list of Pyomo ``Param`` objects in ``deterministic_model`` to be considered uncertain. These specified ``Param`` objects must have the property ``mutable=True``.
-uncertainty_set : ``UncertaintySet``
- A PyROS ``UncertaintySet`` object representing uncertainty in the space of those parameters listed in the ``uncertain_params`` object.
-local_solver : ``Solver``
- A Pyomo ``Solver`` instance for a local NLP optimization solver.
-global_solver : ``Solver``
- A Pyomo ``Solver`` instance for a global NLP optimization solver.
+* Subordinate local and global nonlinear programming (NLP) solvers
+
+These are more elaborately presented in the
+:ref:`Solver Interface ` section.
.. note::
- Any variables in the model not specified to be first- or second-stage variables are automatically considered to be state variables.
+ Any variables in the model not specified to be first-stage or second-stage
+ variables are automatically considered to be state variables.
+
+.. _solver-interface:
PyROS Solver Interface
-----------------------------
@@ -103,107 +132,161 @@ PyROS Solver Interface
:members: solve
.. note::
- Solving the master problems globally (via option ``solve_masters_globally=True``) is one of the requirements to guarantee robust optimality;
- solving the master problems locally can only lead to a robust feasible solution.
+ Upon successful convergence of PyROS, the solution returned is
+ certified to be robust optimal only if:
-.. note::
- Selecting worst-case objective (via option ``objective_focus=ObjectiveType.worst_case``) is one of the requirements to guarantee robust optimality;
- selecting nominal objective can only lead to a robust feasible solution,
- albeit one that has optimized the sum of first- and (nominal) second-stage objectives.
+ 1. master problems are solved to global optimality
+ (by specifying ``solve_master_globally=True``)
+ 2. a worst-case objective focus is chosen
+ (by specifying ``objective_focus=ObjectiveType.worst_case``)
-.. note::
- To utilize option ``p_robustness``, a dictionary of the following form must be supplied via the ``kwarg``:
- There must be a key (``str``) called 'rho', which maps to a non-negative value, where '1+rho' defines a bound
- for the ratio of the objective that any scenario may exhibit compared to the nominal objective.
+ Otherwise, the solution returned is certified to only be robust feasible.
PyROS Uncertainty Sets
-----------------------------
-PyROS contains pre-implemented ``UncertaintySet`` specializations for many types of commonly used uncertainty sets.
-Additional capabilities for intersecting multiple PyROS ``UncertaintySet`` objects so as to create custom sets are also provided
-via the ``IntersectionSet`` class. Custom user-specified sets can also be defined via the base ``UncertaintySet`` class.
-
-Mathematical representations of the sets are shown below, followed by the class descriptions.
-
-.. list-table:: PyROS Uncertainty Sets
+Uncertainty sets are represented by subclasses of
+the :class:`~pyomo.contrib.pyros.uncertainty_sets.UncertaintySet`
+abstract base class.
+PyROS provides a suite of pre-implemented subclasses representing
+commonly used uncertainty sets.
+Custom user-defined uncertainty set types may be implemented by
+subclassing the
+:class:`~pyomo.contrib.pyros.uncertainty_sets.UncertaintySet` class.
+The intersection of a sequence of concrete
+:class:`~pyomo.contrib.pyros.uncertainty_sets.UncertaintySet`
+instances can be easily constructed by instantiating the pre-implemented
+:class:`~pyomo.contrib.pyros.uncertainty_sets.IntersectionSet`
+subclass.
+
+The table that follows provides mathematical definitions of
+the various abstract and pre-implemented
+:class:`~pyomo.contrib.pyros.uncertainty_sets.UncertaintySet` subclasses.
+
+.. _table-uncertsets:
+
+.. list-table:: Mathematical definitions of PyROS uncertainty sets of dimension :math:`n`.
:header-rows: 1
:class: tight-table
* - Uncertainty Set Type
- - Set Representation
- * - ``BoxSet``
- - :math:`Q_X = \left\{q \in \mathbb{R}^n : q^\ell \leq q \leq q^u\right\} \\ q^\ell \in \mathbb{R}^n \\ q^u \in \mathbb{R}^n : \left\{q^\ell \leq q^u\right\}`
- * - ``CardinalitySet``
- - :math:`Q_C = \left\{q \in \mathbb{R}^n : q = q^0 + (\hat{q} \circ \xi) \text{ for some } \xi \in \Xi_C\right\}\\ \Xi_C = \left\{\xi \in [0, 1]^n : \displaystyle\sum_{i=1}^{n} \xi_i \leq \Gamma\right\} \\ \Gamma \in [0, n] \\ \hat{q} \in \mathbb{R}^{n}_{+} \\ q^0 \in \mathbb{R}^n`
- * - ``BudgetSet``
- - :math:`Q_B = \left\{q \in \mathbb{R}^n_+: \displaystyle\sum_{i \in B_\ell} q_i \leq b_\ell \ \forall \ell \in \left\{1,\ldots,L\right\} \right\} \\ b_\ell \in \mathbb{R}^{L}_+`
- * - ``FactorModelSet``
- - :math:`Q_F = \left\{q \in \mathbb{R}^n: \displaystyle q = q^0 + \Psi \xi \text{ for some }\xi \in \Xi_F\right\} \\ \Xi_F = \left\{ \xi \in \left[-1, 1\right]^F, \left\lvert \displaystyle \sum_{f=1}^{F} \xi_f\right\rvert \leq \beta F \right\} \\ \beta \in [0,1] \\ \Psi \in \mathbb{R}^{n \times F}_+ \\ q^0 \in \mathbb{R}^n`
- * - ``PolyhedralSet``
- - :math:`Q_P = \left\{q \in \mathbb{R}^n: \displaystyle A q \leq b \right\} \\ A \in \mathbb{R}^{m \times n} \\ b \in \mathbb{R}^{m} \\ q^0 \in \mathbb{R}^n: {Aq^0 \leq b}`
- * - ``AxisAlignedEllipsoidalSet``
- - :math:`Q_A = \left\{q \in \mathbb{R}^n: \displaystyle \sum\limits_{i=1 : \atop \left\{ \alpha_i > 0 \right\} } \left(\frac{q_i - q_i^0}{\alpha_i} \right)^2 \leq 1 , \quad q_i = q^0_i \quad \forall i : \left\{\alpha_i=0\right\}\right\} \\ \alpha \in \mathbb{R}^n_+, \\ q^0 \in \mathbb{R}^n`
- * - ``EllipsoidalSet``
- - :math:`Q_E = \left\{q \in \mathbb{R}^n: \displaystyle q = q^0 + P^{1/2} \xi \text{ for some } \xi \in \Xi_E \right\} \\ \Xi_E = \left\{\xi \in \mathbb{R} : \xi^T\xi \leq s \right\} \\ P \in \mathbb{S}^{n\times n}_+ \\ s \in \mathbb{R}_+ \\ q^0 \in \mathbb{R}^n`
- * - ``UncertaintySet``
- - :math:`Q_U = \left\{q \in \mathbb{R}^n: \displaystyle g_i(q) \leq 0 \quad \forall i \in \left\{1,\ldots,m \right\}\right\} \\ m \in \mathbb{N}_+ \\ g_i : \mathbb{R}^n \mapsto \mathbb{R} \, \forall i \in \left\{1,\ldots,m\right\}, \\ q^0 \in \mathbb{R}^n : \left\{g_i(q^0) \leq 0 \ \forall i \in \left\{1,\ldots,m\right\}\right\}`
- * - ``DiscreteScenariosSet``
- - :math:`Q_D = \left\{q^s : s = 0,\ldots,D \right\} \\ D \in \mathbb{N} \\ q^s \in \mathbb{R}^n \forall s \in \left\{ 0,\ldots,D\right\}`
- * - ``IntersectionSet``
- - :math:`Q_I = \left\{q \in \mathbb{R}^n: \displaystyle q \in \bigcap_{i \in \left\{1,\ldots,m\right\}} Q_i\right\} \\ Q_i \subset \mathbb{R}^n \quad \forall i \in \left\{1,\ldots,m\right\}`
+ - Input Data
+ - Mathematical Definition
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.BoxSet`
+ - :math:`\begin{array}{l} q ^{\text{L}} \in \mathbb{R}^{n}, \\ q^{\text{U}} \in \mathbb{R}^{n} \end{array}`
+ - :math:`\{q \in \mathbb{R}^n \mid q^\mathrm{L} \leq q \leq q^\mathrm{U}\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.CardinalitySet`
+ - :math:`\begin{array}{l} q^{0} \in \mathbb{R}^{n}, \\ \hat{q} \in \mathbb{R}_{+}^{n}, \\ \Gamma \in [0, n] \end{array}`
+ - :math:`\left\{ q \in \mathbb{R}^{n} \middle| \begin{array}{l} q = q^{0} + \hat{q} \circ \xi \\ \displaystyle \sum_{i=1}^{n} \xi_{i} \leq \Gamma \\ \xi \in [0, 1]^{n} \end{array} \right\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.BudgetSet`
+ - :math:`\begin{array}{l} q^{0} \in \mathbb{R}^{n}, \\ b \in \mathbb{R}_{+}^{L}, \\ B \in \{0, 1\}^{L \times n} \end{array}`
+ - :math:`\left\{ q \in \mathbb{R}^{n} \middle| \begin{array}{l} \begin{pmatrix} B \\ -I \end{pmatrix} q \leq \begin{pmatrix} b + Bq^{0} \\ -q^{0} \end{pmatrix} \end{array} \right\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.FactorModelSet`
+ - :math:`\begin{array}{l} q^{0} \in \mathbb{R}^{n}, \\ \Psi \in \mathbb{R}^{n \times F}, \\ \beta \in [0, 1] \end{array}`
+ - :math:`\left\{ q \in \mathbb{R}^{n} \middle| \begin{array}{l} q = q^{0} + \Psi \xi \\ \displaystyle\bigg| \sum_{j=1}^{F} \xi_{j} \bigg| \leq \beta F \\ \xi \in [-1, 1]^{F} \\ \end{array} \right\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.PolyhedralSet`
+ - :math:`\begin{array}{l} A \in \mathbb{R}^{m \times n}, \\ b \in \mathbb{R}^{m}\end{array}`
+ - :math:`\{q \in \mathbb{R}^{n} \mid A q \leq b\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.AxisAlignedEllipsoidalSet`
+ - :math:`\begin{array}{l} q^0 \in \mathbb{R}^{n}, \\ \alpha \in \mathbb{R}_{+}^{n} \end{array}`
+ - :math:`\left\{ q \in \mathbb{R}^{n} \middle| \begin{array}{l} \displaystyle\sum_{\substack{i = 1: \\ \alpha_{i} > 0}}^{n} \left(\frac{q_{i} - q_{i}^{0}}{\alpha_{i}}\right)^2 \leq 1 \\ q_{i} = q_{i}^{0} \,\forall\,i : \alpha_{i} = 0 \end{array} \right\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.EllipsoidalSet`
+ - :math:`\begin{array}{l} q^0 \in \mathbb{R}^n, \\ P \in \mathbb{S}_{++}^{n}, \\ s \in \mathbb{R}_{+} \end{array}`
+ - :math:`\{q \in \mathbb{R}^{n} \mid (q - q^{0})^{\intercal} P^{-1} (q - q^{0}) \leq s\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.UncertaintySet`
+ - :math:`g: \mathbb{R}^{n} \to \mathbb{R}^{m}`
+ - :math:`\{q \in \mathbb{R}^{n} \mid g(q) \leq 0\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.DiscreteScenarioSet`
+ - :math:`q^{1}, q^{2},\dots , q^{S} \in \mathbb{R}^{n}`
+ - :math:`\{q^{1}, q^{2}, \dots , q^{S}\}`
+ * - :class:`~pyomo.contrib.pyros.uncertainty_sets.IntersectionSet`
+ - :math:`\mathcal{Q}_{1}, \mathcal{Q}_{2}, \dots , \mathcal{Q}_{m} \subset \mathbb{R}^{n}`
+ - :math:`\displaystyle \bigcap_{i=1}^{m} \mathcal{Q}_{i}`
.. note::
- Each of the PyROS uncertainty set classes inherits from the ``UncertaintySet`` base class.
+ Each of the PyROS uncertainty set classes inherits from the
+ :class:`~pyomo.contrib.pyros.uncertainty_sets.UncertaintySet`
+ abstract base class.
PyROS Uncertainty Set Classes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.BoxSet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: bounds, type, parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.CardinalitySet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: origin, positive_deviation, gamma, type, parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.BudgetSet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: coefficients_mat, rhs_vec, origin, budget_membership_mat, budget_rhs_vec, type, parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.FactorModelSet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: origin, number_of_factors, psi_mat, beta, type, parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.PolyhedralSet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: coefficients_mat, rhs_vec, type, parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.AxisAlignedEllipsoidalSet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: center, half_lengths, type, parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.EllipsoidalSet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: center, shape_matrix, scale, type, parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.UncertaintySet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.DiscreteScenarioSet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: scenarios, type, parameter_bounds, dim, point_in_set
.. autoclass:: pyomo.contrib.pyros.uncertainty_sets.IntersectionSet
- :special-members: __init__, parameter_bounds, dim, point_in_set
+ :show-inheritance:
+ :special-members: all_sets, type, parameter_bounds, dim, point_in_set
PyROS Usage Example
-----------------------------
-We will use an example to illustrate the usage of PyROS. The problem we will use is called *hydro* and comes from the GAMS example problem database in `The GAMS Model Library `_. The model was converted to Pyomo format via the `GAMS Convert tool `_.
-
-This model is a QCQP with 31 variables. Of these variables, 13 represent degrees of freedom, with the additional 18 being state variables.
-The model features 6 linear inequality constraints, 6 linear equality constraints, 6 non-linear (quadratic) equalities, and a quadratic objective.
-We have augmented this model by converting one objective coefficient, two constraint coefficients, and one constraint right-hand side into Param objects so that they can be considered uncertain later on.
+In this section, we illustrate the usage of PyROS with a modeling example.
+The deterministic problem of interest is called *hydro*
+(available `here `_),
+a QCQP taken from the
+`GAMS Model Library `_.
+We have converted the model to Pyomo format using the
+`GAMS Convert tool `_.
+
+The *hydro* model features 31 variables,
+of which 13 are degrees of freedom and 18 are state variables.
+Moreover, there are
+6 linear inequality constraints,
+12 linear equality constraints,
+6 non-linear (quadratic) equality constraints,
+and a quadratic objective.
+We have extended this model by converting one objective coefficient,
+two constraint coefficients, and one constraint right-hand side
+into ``Param`` objects so that they can be considered uncertain later on.
.. note::
- Per our analysis, the *hydro* problem satisfies the requirement that each value of :math:`\left(x, z, q \right)` maps to a unique value of :math:`y`, which indicates a proper partition of variables between (first- or second-stage) degrees of freedom and state variables.
+ Per our analysis, the *hydro* problem satisfies the requirement that
+ each value of :math:`\left(x, z, q \right)` maps to a unique
+ value of :math:`y`, which, in accordance with
+ :ref:`our earlier note `,
+ indicates a proper partitioning of the model variables
+ into (first-stage and second-stage) degrees of freedom and
+ state variables.
Step 0: Import Pyomo and the PyROS Module
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-In anticipation of using the PyROS solver and building the deterministic Pyomo model:
+In anticipation of using the PyROS solver and building the deterministic Pyomo
+model:
.. doctest::
@@ -220,14 +303,24 @@ Step 1: Define the Deterministic Problem
The deterministic Pyomo model for *hydro* is shown below.
.. note::
- Primitive data (Python literals) that have been hard-coded within a deterministic model cannot be later considered uncertain, unless they are first converted to ``Param`` objects within the ``ConcreteModel`` object.
- Furthermore, any ``Param`` object that is to be later considered uncertain must have the property ``mutable=True``.
+ Primitive data (Python literals) that have been hard-coded within a
+ deterministic model cannot be later considered uncertain,
+ unless they are first converted to ``Param`` objects within
+ the ``ConcreteModel`` object.
+ Furthermore, any ``Param`` object that is to be later considered
+ uncertain must have the property ``mutable=True``.
.. note::
- In case modifying the ``mutable`` property inside the deterministic model object itself is not straight-forward in your context,
- you may consider adding the following statement **after** ``import pyomo.environ as pyo`` but **before** defining the model object:
- ``pyo.Param.DefaultMutable = True``. Note how this sets the default ``mutable`` property in all ``Param`` objects in the ensuing model instance to ``True``;
- consequently, this solution will not work with ``Param`` objects for which the ``mutable=False`` property was explicitly enabled inside the model object.
+ In case modifying the ``mutable`` property inside the deterministic
+ model object itself is not straightforward in your context,
+ you may consider adding the following statement **after**
+ ``import pyomo.environ as pyo`` but **before** defining the model
+ object: ``pyo.Param.DefaultMutable = True``.
+ For all ``Param`` objects declared after this statement,
+ the attribute ``mutable`` is set to ``True`` by default.
+ Hence, non-mutable ``Param`` objects are now declared by
+ explicitly passing the argument ``mutable=False`` to the
+ ``Param`` constructor.
.. doctest::
@@ -310,25 +403,46 @@ The deterministic Pyomo model for *hydro* is shown below.
Step 2: Define the Uncertainty
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-First, we need to collect into a list those ``Param`` objects of our model that represent potentially uncertain parameters. For purposes of our example, we shall assume uncertainty in the model parameters ``(m.p[0], m.p[1], m.p[2], m.p[3])``, for which we can conveniently utilize the ``m.p`` object (itself an indexed ``Param`` object).
+First, we need to collect into a list those ``Param`` objects of our model
+that represent potentially uncertain parameters.
+For the purposes of our example, we shall assume uncertainty in the model
+parameters ``[m.p[0], m.p[1], m.p[2], m.p[3]]``, for which we can
+conveniently utilize the object ``m.p`` (itself an indexed ``Param`` object).
.. doctest::
>>> # === Specify which parameters are uncertain ===
- >>> uncertain_parameters = [m.p] # We can pass IndexedParams this way to PyROS, or as an expanded list per index
+ >>> # We can pass IndexedParams this way to PyROS,
+ >>> # or as an expanded list per index
+ >>> uncertain_parameters = [m.p]
.. note::
- Any ``Param`` object that is to be considered uncertain by PyROS must have the property ``mutable=True``.
-
-PyROS will seek to identify solutions that remain feasible for any realization of these parameters included in an uncertainty set. To that end, we need to construct an ``UncertaintySet`` object. In our example, let us utilize the ``BoxSet`` constructor to specify an uncertainty set of simple hyper-rectangular geometry. For this, we will assume each parameter value is uncertain within a percentage of its nominal value. Constructing this specific ``UncertaintySet`` object can be done as follows.
+ Any ``Param`` object that is to be considered uncertain by PyROS
+ must have the property ``mutable=True``.
+
+PyROS will seek to identify solutions that remain feasible for any
+realization of these parameters included in an uncertainty set.
+To that end, we need to construct an
+:class:`~pyomo.contrib.pyros.uncertainty_sets.UncertaintySet`
+object.
+In our example, let us utilize the
+:class:`~pyomo.contrib.pyros.uncertainty_sets.BoxSet`
+constructor to specify
+an uncertainty set of simple hyper-rectangular geometry.
+For this, we will assume each parameter value is uncertain within a
+percentage of its nominal value. Constructing this specific
+:class:`~pyomo.contrib.pyros.uncertainty_sets.UncertaintySet`
+object can be done as follows:
.. doctest::
>>> # === Define the pertinent data ===
>>> relative_deviation = 0.15
- >>> bounds = [(nominal_values[i] - relative_deviation*nominal_values[i],
- ... nominal_values[i] + relative_deviation*nominal_values[i])
- ... for i in range(4)]
+ >>> bounds = [
+ ... (nominal_values[i] - relative_deviation*nominal_values[i],
+ ... nominal_values[i] + relative_deviation*nominal_values[i])
+ ... for i in range(4)
+ ... ]
>>> # === Construct the desirable uncertainty set ===
>>> box_uncertainty_set = pyros.BoxSet(bounds=bounds)
@@ -336,7 +450,10 @@ PyROS will seek to identify solutions that remain feasible for any realization o
Step 3: Solve with PyROS
^^^^^^^^^^^^^^^^^^^^^^^^^^
-PyROS requires the user to supply one local and one global NLP solver to be used for solving sub-problems. For convenience, we shall have PyROS invoke BARON as both the local and the global NLP solver.
+PyROS requires the user to supply one local and one global NLP solver to use
+for solving sub-problems.
+For convenience, we shall have PyROS invoke BARON as both the local and the
+global NLP solver:
.. doctest::
:skipif: not (baron.available() and baron.license_is_valid())
@@ -346,60 +463,81 @@ PyROS requires the user to supply one local and one global NLP solver to be used
>>> global_solver = pyo.SolverFactory('baron')
.. note::
- Additional solvers to be used as backup can be designated during the ``solve`` statement via the config options ``backup_local_solvers`` and ``backup_global_solvers`` presented above.
-
-The final step in solving a model with PyROS is to designate the remaining required inputs, namely ``first_stage_variables`` and ``second_stage_variables``. Below, we present two separate cases.
+ Additional NLP optimizers can be automatically used in the event the primary
+ subordinate local or global optimizer passed
+ to the PyROS :meth:`~pyomo.contrib.pyros.PyROS.solve` method
+ does not successfully solve a subproblem to an appropriate termination
+ condition. These alternative solvers are provided through the optional
+ keyword arguments ``backup_local_solvers`` and ``backup_global_solvers``.
+
+The final step in solving a model with PyROS is to construct the
+remaining required inputs, namely
+``first_stage_variables`` and ``second_stage_variables``.
+Below, we present two separate cases.
PyROS Termination Conditions
"""""""""""""""""""""""""""""
-PyROS will return one of six termination conditions upon completion. These termination conditions are tabulated below.
-
-.. tabularcolumns:: |c|c|c|
-
-+---------------------------------------------------+----------------------------------------------------------------+
-| **Termination Condition** | **Description** |
-+---------------------------------------------------+----------------------------------------------------------------+
-| ``pyrosTerminationCondition.robust_optimal`` | The final solution is robust optimal |
-+---------------------------------------------------+----------------------------------------------------------------+
-| ``pyrosTerminationCondition.robust_feasible`` | The final solution is robust feasible |
-+---------------------------------------------------+----------------------------------------------------------------+
-| ``pyrosTerminationCondition.robust_infeasible`` | The posed problem is robust infeasible |
-+---------------------------------------------------+----------------------------------------------------------------+
-| ``pyrosTerminationCondition.max_iter`` | Maximum number of GRCS iteration reached |
-+---------------------------------------------------+----------------------------------------------------------------+
-| ``pyrosTerminationCondition.time_out`` | Maximum number of time reached |
-+---------------------------------------------------+----------------------------------------------------------------+
-| ``pyrosTerminationCondition.subsolver_error`` | Unacceptable return status(es) from a user-supplied sub-solver|
-+---------------------------------------------------+----------------------------------------------------------------+
+PyROS will return one of six termination conditions upon completion.
+These termination conditions are defined through the
+:class:`~pyomo.contrib.pyros.util.pyrosTerminationCondition` enumeration
+and tabulated below.
+
+.. table:: PyROS termination conditions.
+
+ +----------------------------------------------------------------------------------+----------------------------------------------------------------+
+ | Termination Condition | Description |
+ +==================================================================================+================================================================+
+ | :attr:`~pyomo.contrib.pyros.util.pyrosTerminationCondition.robust_optimal` | The final solution is robust optimal |
+ +----------------------------------------------------------------------------------+----------------------------------------------------------------+
+ | :attr:`~pyomo.contrib.pyros.util.pyrosTerminationCondition.robust_feasible` | The final solution is robust feasible |
+ +----------------------------------------------------------------------------------+----------------------------------------------------------------+
+ | :attr:`~pyomo.contrib.pyros.util.pyrosTerminationCondition.robust_infeasible` | The posed problem is robust infeasible |
+ +----------------------------------------------------------------------------------+----------------------------------------------------------------+
+ | :attr:`~pyomo.contrib.pyros.util.pyrosTerminationCondition.max_iter` | Maximum number of GRCS iteration reached |
+ +----------------------------------------------------------------------------------+----------------------------------------------------------------+
+ | :attr:`~pyomo.contrib.pyros.util.pyrosTerminationCondition.time_out` | Maximum number of time reached |
+ +----------------------------------------------------------------------------------+----------------------------------------------------------------+
+ | :attr:`~pyomo.contrib.pyros.util.pyrosTerminationCondition.subsolver_error` | Unacceptable return status(es) from a user-supplied sub-solver|
+ +----------------------------------------------------------------------------------+----------------------------------------------------------------+
A Single-Stage Problem
"""""""""""""""""""""""""
-If we choose to designate all variables as either design or state variables, without any control variables (i.e., all degrees of freedom are first-stage), we can use PyROS to solve the single-stage problem as shown below. In particular, let us instruct PyROS that variables ``m.x1`` through ``m.x6``, ``m.x19`` through ``m.x24``, and ``m.x31`` correspond to first-stage degrees of freedom.
+If we choose to designate all variables as either design or state variables,
+without any control variables (i.e., all degrees of freedom are first-stage),
+we can use PyROS to solve the single-stage problem as shown below.
+In particular, let us instruct PyROS that variables
+``m.x1`` through ``m.x6``, ``m.x19`` through ``m.x24``, and ``m.x31``
+correspond to first-stage degrees of freedom.
+
+.. _single-stage-problem:
.. doctest::
:skipif: not (baron.available() and baron.license_is_valid())
- >>> # === Designate which variables correspond to first- and second-stage degrees of freedom ===
- >>> first_stage_variables =[m.x1, m.x2, m.x3, m.x4, m.x5, m.x6,
- ... m.x19, m.x20, m.x21, m.x22, m.x23, m.x24, m.x31]
+ >>> # === Designate which variables correspond to first-stage
+ >>> # and second-stage degrees of freedom ===
+ >>> first_stage_variables =[
+ ... m.x1, m.x2, m.x3, m.x4, m.x5, m.x6,
+ ... m.x19, m.x20, m.x21, m.x22, m.x23, m.x24, m.x31,
+ ... ]
>>> second_stage_variables = []
>>> # The remaining variables are implicitly designated to be state variables
>>> # === Call PyROS to solve the robust optimization problem ===
- >>> results_1 = pyros_solver.solve(model = m,
- ... first_stage_variables = first_stage_variables,
- ... second_stage_variables = second_stage_variables,
- ... uncertain_params = uncertain_parameters,
- ... uncertainty_set = box_uncertainty_set,
- ... local_solver = local_solver,
- ... global_solver= global_solver,
- ... options = {
- ... "objective_focus": pyros.ObjectiveType.worst_case,
- ... "solve_master_globally": True,
- ... "load_solution":False
- ... })
+ >>> results_1 = pyros_solver.solve(
+ ... model=m,
+ ... first_stage_variables=first_stage_variables,
+ ... second_stage_variables=second_stage_variables,
+ ... uncertain_params=uncertain_parameters,
+ ... uncertainty_set=box_uncertainty_set,
+ ... local_solver=local_solver,
+ ... global_solver=global_solver,
+ ... objective_focus=pyros.ObjectiveType.worst_case,
+ ... solve_master_globally=True,
+ ... load_solution=False,
+ ... )
===========================================================================================
PyROS: Pyomo Robust Optimization Solver ...
===========================================================================================
@@ -413,32 +551,58 @@ If we choose to designate all variables as either design or state variables, wit
>>> objective = results_1.final_objective_value
>>> # === Print some results ===
>>> single_stage_final_objective = round(objective,-1)
- >>> print("Final objective value: %s" % single_stage_final_objective)
+ >>> print(f"Final objective value: {single_stage_final_objective}")
Final objective value: 48367380.0
- >>> print("PyROS termination condition: %s" % termination_condition)
+ >>> print(f"PyROS termination condition: {termination_condition}")
PyROS termination condition: pyrosTerminationCondition.robust_optimal
PyROS Results Object
"""""""""""""""""""""""""""
-The results object returned by PyROS allows you to query the following information from the solve call:
-total iterations of the algorithm ``iterations``, CPU time ``time``, the GRCS algorithm termination condition ``pyros_termination_condition``,
-and the final objective function value ``final_objective_value``. If the option ``load_solution`` = ``True`` (default), the variables in the model will be
-loaded to the solution determined by PyROS and can be obtained by querying the model variables. Note that in the results obtained above, we set ``load_solution`` = ``False``.
-This is to ensure that the next set of runs shown here can utilize the original deterministic model, as the initial point can affect the performance of sub-solvers.
+The results object returned by PyROS allows you to query the following information
+from the solve call:
+
+* ``iterations``: total iterations of the algorithm
+* ``time``: total wallclock time (or elapsed time) in seconds
+* ``pyros_termination_condition``: the GRCS algorithm termination condition
+* ``final_objective_value``: the final objective function value.
+
+The :ref:`preceding code snippet `
+demonstrates how to retrieve this information.
+
+If we pass ``load_solution=True`` (the default setting)
+to the :meth:`~pyomo.contrib.pyros.PyROS.solve` method,
+then the solution at which PyROS terminates will be loaded to
+the variables of the original deterministic model.
+Note that in the :ref:`preceding code snippet `,
+we set ``load_solution=False`` to ensure the next set of runs shown here can
+utilize the initial point loaded to the original deterministic model,
+as the initial point may affect the performance of sub-solvers.
.. note::
- The reported ``final_objective_value`` and final model variable values depend on the selection of the option ``objective_focus``.
- The ``final_objective_value`` is the sum of first-stage and second-stage objective functions.
- If ``objective_focus = ObjectiveType.nominal``, second-stage objective and variables are evaluated at the nominal realization of the uncertain parameters, :math:`q^0`.
- If ``objective_focus = ObjectiveType.worst_case``, second-stage objective and variables are evaluated at the worst-case realization of the uncertain parameters, :math:`q^{k^\ast}` where :math:`k^\ast = argmax_{k \in \mathcal{K}} f_2(x,z^k,y^k,q^k)` .
-
-An example of how to query these values on the previously obtained results is shown in the code above.
-
+ The reported ``final_objective_value`` and final model variable values
+ depend on the selection of the option ``objective_focus``.
+ The ``final_objective_value`` is the sum of first-stage
+ and second-stage objective functions.
+ If ``objective_focus = ObjectiveType.nominal``,
+ second-stage objective and variables are evaluated at
+ the nominal realization of the uncertain parameters, :math:`q^{\text{nom}}`.
+ If ``objective_focus = ObjectiveType.worst_case``, second-stage objective
+ and variables are evaluated at the worst-case realization
+ of the uncertain parameters, :math:`q^{k^\ast}`
+ where :math:`k^\ast = \mathrm{argmax}_{k \in \mathcal{K}}~f_2(x,z^k,y^k,q^k)`.
A Two-Stage Problem
""""""""""""""""""""""
-For this next set of runs, we will assume that some of the previously designated first-stage degrees of freedom are in fact second-stage ones. PyROS handles second-stage degrees of freedom via the use of decision rules, which is controlled with the config option ``decision_rule_order`` presented above. Here, we shall select affine decision rules by setting ``decision_rule_order`` to the value of `1`.
+For this next set of runs, we will
+assume that some of the previously designated first-stage degrees of
+freedom are in fact second-stage degrees of freedom.
+PyROS handles second-stage degrees of freedom via the use of polynomial
+decision rules, of which the degree is controlled through the
+optional keyword argument ``decision_rule_order`` to the PyROS
+:meth:`~pyomo.contrib.pyros.PyROS.solve` method.
+In this example, we select affine decision rules by setting
+``decision_rule_order=1``:
.. doctest::
:skipif: not (baron.available() and baron.license_is_valid())
@@ -449,97 +613,127 @@ For this next set of runs, we will assume that some of the previously designated
>>> # The remaining variables are implicitly designated to be state variables
>>> # === Call PyROS to solve the robust optimization problem ===
- >>> results_2 = pyros_solver.solve(model = m,
- ... first_stage_variables = first_stage_variables,
- ... second_stage_variables = second_stage_variables,
- ... uncertain_params = uncertain_parameters,
- ... uncertainty_set = box_uncertainty_set,
- ... local_solver = local_solver,
- ... global_solver = global_solver,
- ... options = {
- ... "objective_focus": pyros.ObjectiveType.worst_case,
- ... "solve_master_globally": True,
- ... "decision_rule_order": 1
- ... })
+ >>> results_2 = pyros_solver.solve(
+ ... model=m,
+ ... first_stage_variables=first_stage_variables,
+ ... second_stage_variables=second_stage_variables,
+ ... uncertain_params=uncertain_parameters,
+ ... uncertainty_set=box_uncertainty_set,
+ ... local_solver=local_solver,
+ ... global_solver=global_solver,
+ ... objective_focus=pyros.ObjectiveType.worst_case,
+ ... solve_master_globally=True,
+ ... decision_rule_order=1,
+ ... )
===========================================================================================
PyROS: Pyomo Robust Optimization Solver ...
...
INFO: Robust optimal solution identified. Exiting PyROS.
- >>> # === Compare final objective to the singe-stage solution
- >>> two_stage_final_objective = round(pyo.value(results_2.final_objective_value),-1)
- >>> percent_difference = 100 * (two_stage_final_objective - single_stage_final_objective)/(single_stage_final_objective)
- >>> print("Percent objective change relative to constant decision rules objective: %.2f %%" % percent_difference)
+ >>> # === Compare final objective to the single-stage solution
+ >>> two_stage_final_objective = round(
+ ... pyo.value(results_2.final_objective_value),
+ ... -1,
+ ... )
+ >>> percent_difference = 100 * (
+ ... two_stage_final_objective - single_stage_final_objective
+ ... ) / (single_stage_final_objective)
+ >>> print("Percent objective change relative to constant decision rules "
+ ... f"objective: {percent_difference:.2f}")
Percent objective change relative to constant decision rules objective: -24...
-In this example, when we compare the final objective value in the case of constant decision rules (no second-stage recourse)
-and affine decision rules, we see there is a ~25% decrease in total objective value.
+For this example, we notice a ~25% decrease in the final objective
+value when switching from a static decision rule (no second-stage recourse)
+to an affine decision rule.
The Price of Robustness
""""""""""""""""""""""""
-Using appropriately constructed hierarchies, PyROS allows for the facile comparison of robust optimal objectives across sets to determine the "price of robustness."
-For the set we considered here, the ``BoxSet``, we can create such a hierarchy via an array of ``relative_deviation`` parameters to define the size of these uncertainty sets.
-We can then loop through this array and call PyROS within a loop to identify robust solutions in light of each of the specified ``BoxSet`` objects.
+In conjunction with standard Python control flow tools,
+PyROS facilitates a "price of robustness" analysis for a model of interest
+through the evaluation and comparison of the robust optimal
+objective function value across any appropriately constructed hierarchy
+of uncertainty sets.
+In this example, we consider a sequence of
+box uncertainty sets centered on the nominal uncertain
+parameter realization, such that each box is parameterized
+by a real value specifying a relative box size.
+To this end, we construct an iterable called ``relative_deviation_list``
+whose entries are ``float`` values representing the relative sizes.
+We then loop through ``relative_deviation_list`` so that for each relative
+size, the corresponding robust optimal objective value
+can be evaluated by creating an appropriate
+:class:`~pyomo.contrib.pyros.uncertainty_sets.BoxSet`
+instance and invoking the PyROS solver:
.. code::
>>> # This takes a long time to run and therefore is not a doctest
- >>> # === An array of maximum relative deviations from the nominal uncertain parameter values to utilize in constructing box sets
+ >>> # === An array of maximum relative deviations from the nominal uncertain
+ >>> # parameter values to utilize in constructing box sets
>>> relative_deviation_list = [0.00, 0.10, 0.20, 0.30, 0.40]
>>> # === Final robust optimal objectives
>>> robust_optimal_objectives = []
>>> for relative_deviation in relative_deviation_list: # doctest: +SKIP
- ... bounds = [(nominal_values[i] - relative_deviation*nominal_values[i],
- ... nominal_values[i] + relative_deviation*nominal_values[i])
- ... for i in range(4)]
- ... box_uncertainty_set = pyros.BoxSet(bounds = bounds)
- ... results = pyros_solver.solve(model = m,
- ... first_stage_variables = first_stage_variables,
- ... second_stage_variables = second_stage_variables,
- ... uncertain_params = uncertain_parameters,
- ... uncertainty_set = box_uncertainty_set,
- ... local_solver = local_solver,
- ... global_solver = global_solver,
- ... options = {
- ... "objective_focus": pyros.ObjectiveType.worst_case,
- ... "solve_master_globally": True,
- ... "decision_rule_order": 1
- ... })
- ... if results.pyros_termination_condition != pyros.pyrosTerminationCondition.robust_optimal:
- ... print("This instance didn't solve to robust optimality.")
- ... robust_optimal_objective.append("-----")
- ... else:
- ... robust_optimal_objectives.append(str(results.final_objective_value))
+ ... bounds = [
+ ... (nominal_values[i] - relative_deviation*nominal_values[i],
+ ... nominal_values[i] + relative_deviation*nominal_values[i])
+ ... for i in range(4)
+ ... ]
+ ... box_uncertainty_set = pyros.BoxSet(bounds = bounds)
+ ... results = pyros_solver.solve(
+ ... model=m,
+ ... first_stage_variables=first_stage_variables,
+ ... second_stage_variables=second_stage_variables,
+ ... uncertain_params=uncertain_parameters,
+ ... uncertainty_set= box_uncertainty_set,
+ ... local_solver=local_solver,
+ ... global_solver=global_solver,
+ ... objective_focus=pyros.ObjectiveType.worst_case,
+ ... solve_master_globally=True,
+ ... decision_rule_order=1,
+ ... )
+ ... is_robust_optimal = (
+ ... results.pyros_termination_condition
+ ... == pyros.pyrosTerminationCondition.robust_optimal
+ ... )
+ ... if not is_robust_optimal:
+ ... print(f"Instance for relative deviation: {relative_deviation} "
+ ... "not solved to robust optimality.")
+ ... robust_optimal_objectives.append("-----")
+ ... else:
+ ... robust_optimal_objectives.append(str(results.final_objective_value))
For this example, we obtain the following price of robustness results:
-.. tabularcolumns:: |c|c|c|
-
-+------------------------------------------+------------------------------+-----------------------------+
-| **Uncertainty Set Size (+/-)** :sup:`o` | **Robust Optimal Objective** | **% Increase** :sup:`x` |
-+------------------------------------------+------------------------------+-----------------------------+
-| 0.00 | 35,837,659.18 | 0.00 % |
-+------------------------------------------+------------------------------+-----------------------------+
-| 0.10 | 36,135,191.59 | 0.82 % |
-+------------------------------------------+------------------------------+-----------------------------+
-| 0.20 | 36,437,979.81 | 1.64 % |
-+------------------------------------------+------------------------------+-----------------------------+
-| 0.30 | 43,478,190.92 | 17.57 % |
-+------------------------------------------+------------------------------+-----------------------------+
-| 0.40 | ``robust_infeasible`` | :math:`\text{-----}` |
-+------------------------------------------+------------------------------+-----------------------------+
-
-Note how, in the case of the last uncertainty set, we were able to utilize PyROS to show the robust infeasibility of the problem.
+.. table:: Price of robustness results.
+
+ +------------------------------------------+------------------------------+-----------------------------+
+ | Uncertainty Set Size (+/-) :sup:`o` | Robust Optimal Objective | % Increase :sup:`x` |
+ +==========================================+==============================+=============================+
+ | 0.00 | 35,837,659.18 | 0.00 % |
+ +------------------------------------------+------------------------------+-----------------------------+
+ | 0.10 | 36,135,191.59 | 0.82 % |
+ +------------------------------------------+------------------------------+-----------------------------+
+ | 0.20 | 36,437,979.81 | 1.64 % |
+ +------------------------------------------+------------------------------+-----------------------------+
+ | 0.30 | 43,478,190.92 | 17.57 % |
+ +------------------------------------------+------------------------------+-----------------------------+
+ | 0.40 | ``robust_infeasible`` | :math:`\text{-----}` |
+ +------------------------------------------+------------------------------+-----------------------------+
+
+Notice that PyROS was successfully able to determine the robust
+infeasibility of the problem under the largest uncertainty set.
:sup:`o` **Relative Deviation from Nominal Realization**
:sup:`x` **Relative to Deterministic Optimal Objective**
-This clearly illustrates the impact that the uncertainty set size can have on the robust optimal objective values.
-Price of robustness studies like this are easily implemented using PyROS.
+This example clearly illustrates the potential impact of the uncertainty
+set size on the robust optimal objective function value
+and demonstrates the ease of implementing a price of robustness study
+for a given optimization problem under uncertainty.
-.. warning::
+.. note::
- PyROS is still under a beta release. Please provide feedback and/or
- report any problems by opening an issue on the Pyomo
- `GitHub page `_.
+ Please provide feedback and/or report any problems by opening an issue on
+ the `Pyomo GitHub page `_.
diff --git a/doc/OnlineDocs/contribution_guide.rst b/doc/OnlineDocs/contribution_guide.rst
index 2ebb3e1ce70..10670627546 100644
--- a/doc/OnlineDocs/contribution_guide.rst
+++ b/doc/OnlineDocs/contribution_guide.rst
@@ -21,18 +21,38 @@ we encourage modular/targeted commits with descriptive commit messages.
Coding Standards
++++++++++++++++
- * Required: 4 space indentation (no tabs)
- * Desired: PEP8
- * No use of __author__
+ * Required: `black `_
+ * No use of ``__author__``
* Inside ``pyomo.contrib``: Contact information for the contribution
maintainer (such as a Github ID) should be included in the Sphinx
documentation
-Sphinx-compliant documentation is required for:
+The first step of Pyomo's GitHub Actions workflow is to run
+`black `_ and a
+`spell-checker `_ to ensure style
+guide compliance and minimize typos. Before opening a pull request, please
+run:
+
+::
+
+ # Auto-apply correct formatting
+ pip install black
+ black -S -C --exclude examples/pyomobook/python-ch/BadIndent.py
+ # Find typos in files
+ conda install typos
+ typos --config .github/workflows/typos.toml
+
+If the spell-checker returns a failure for a word that is spelled correctly,
+please add the word to the ``.github/workflows/typos.toml`` file.
+
+Online Pyomo documentation is generated using `Sphinx `_
+with the ``napoleon`` extension enabled. For API documentation we use of one of these
+`supported styles for docstrings `_,
+but we prefer the NumPy standard. Whichever you choose, we require compliant docstrings for:
* Modules
* Public and Private Classes
- * Public and Private Functions
+ * Public and Private Functions
We also encourage you to include examples, especially for new features
and contributions to ``pyomo.contrib``.
@@ -41,7 +61,7 @@ Testing
+++++++
Pyomo uses `unittest `_,
-`nose `_,
+`pytest `_,
`GitHub Actions `_,
and Jenkins
for testing and continuous integration. Submitted code should include
@@ -54,7 +74,7 @@ merged.
The Pyomo main branch provides a Github Actions workflow (configured
in the ``.github/`` directory) that will test any changes pushed to
a branch with a subset of the complete test harness that includes
-multiple virtual machines (ubuntu, mac-os, windows)
+multiple virtual machines (``ubuntu``, ``mac-os``, ``windows``)
and multiple Python versions. For existing forks, fetch and merge
your fork (and branches) with Pyomo's main. For new forks, you will
need to enable GitHub Actions in the 'Actions' tab on your fork.
@@ -65,10 +85,27 @@ may be opened by including '[WIP]' at the beginning of the PR
title. This allows your code changes to be tested by the full suite of
Pyomo's automatic
testing infrastructure. Any pull requests marked '[WIP]' will not be
-reviewed or merged by the core development team. In addition, any
+reviewed or merged by the core development team. However, any
'[WIP]' pull request left open for an extended period of time without
active development may be marked 'stale' and closed.
+Python Version Support
+++++++++++++++++++++++
+
+By policy, Pyomo supports and tests the currently supported Python versions,
+as can be seen on `Status of Python Versions `_.
+It is expected that tests will pass for all of the supported and tested
+versions of Python, unless otherwise stated.
+
+At the time of the first Pyomo release after the end-of-life of a minor Python
+version, we will remove testing and support for that Python version.
+
+This will also result in a bump in the minor Pyomo version.
+
+For example, assume Python 3.A is declared end-of-life while Pyomo is on
+version 6.3.Y. After the release of Pyomo 6.3.(Y+1), Python 3.A will be removed,
+and the next Pyomo release will be 6.4.0.
+
Working on Forks and Branches
-----------------------------
@@ -251,13 +288,14 @@ Setting up your development environment
After cloning your fork, you will want to install Pyomo from source.
-Step 1 (recommended): Create a new conda environment.
+Step 1 (recommended): Create a new ``conda`` environment.
::
conda create --name pyomodev
-You may change the environment name from ``pyomodev`` as you see fit. Then activate the environment:
+You may change the environment name from ``pyomodev`` as you see fit.
+Then activate the environment:
::
@@ -265,9 +303,11 @@ You may change the environment name from ``pyomodev`` as you see fit. Then activ
Step 2 (optional): Install PyUtilib
-The hard dependency on PyUtilib was removed in Pyomo 6.0.0. There is still a soft dependency for any code related to ``pyomo.dataportal.plugins.sheet``.
+The hard dependency on PyUtilib was removed in Pyomo 6.0.0. There is still a
+soft dependency for any code related to ``pyomo.dataportal.plugins.sheet``.
-If your contribution requires PyUtilib, you will likely need the main branch of PyUtilib to contribute. Clone a copy of the repository in a new directory:
+If your contribution requires PyUtilib, you will likely need the main branch of
+PyUtilib to contribute. Clone a copy of the repository in a new directory:
::
@@ -287,7 +327,10 @@ Finally, move to the directory containing the clone of your Pyomo fork and run:
python setup.py develop
-These commands register the cloned code with the active python environment (``pyomodev``). This way, your changes to the source code for ``pyomo`` are automatically used by the active environment. You can create another conda environment to switch to alternate versions of pyomo (e.g., stable).
+These commands register the cloned code with the active python environment
+(``pyomodev``). This way, your changes to the source code for ``pyomo`` are
+automatically used by the active environment. You can create another conda
+environment to switch to alternate versions of pyomo (e.g., stable).
Review Process
--------------
@@ -331,9 +374,11 @@ of third-party contributions that enhance Pyomo's core functionality.
The are two ways that ``pyomo.contrib`` can be used to integrate
third-party packages:
-* ``pyomo.contrib`` can provide wrappers for separate Python packages, thereby allowing these packages to be imported as subpackages of pyomo.
+* ``pyomo.contrib`` can provide wrappers for separate Python packages, thereby
+ allowing these packages to be imported as subpackages of pyomo.
-* ``pyomo.contrib`` can include contributed packages that are developed and maintained outside of the Pyomo developer team.
+* ``pyomo.contrib`` can include contributed packages that are developed and
+ maintained outside of the Pyomo developer team.
Including contrib packages in the Pyomo source tree provides a
convenient mechanism for defining new functionality that can be
diff --git a/doc/OnlineDocs/developer_reference/deprecation.rst b/doc/OnlineDocs/developer_reference/deprecation.rst
new file mode 100644
index 00000000000..7fc5ec2b0ff
--- /dev/null
+++ b/doc/OnlineDocs/developer_reference/deprecation.rst
@@ -0,0 +1,62 @@
+Deprecation and Removal of Functionality
+========================================
+
+During the course of development, there may be cases where it becomes
+necessary to deprecate or remove functionality from the standard Pyomo
+offering.
+
+Deprecation
+-----------
+
+We offer a set of tools to help with deprecation in
+``pyomo.common.deprecation``.
+
+By policy, when deprecating or moving an existing capability, one of the
+following utilities should be leveraged. Each has a required
+``version`` argument that should be set to current development version (e.g.,
+``"6.6.2.dev0"``). This version will be updated to the next actual
+release as part of the Pyomo release process. The current development version
+can be found by running ``pyomo --version`` on your local fork/branch.
+
+.. currentmodule:: pyomo.common.deprecation
+
+.. autosummary::
+
+ deprecated
+ deprecation_warning
+ relocated_module
+ relocated_module_attribute
+ RenamedClass
+
+.. autodecorator:: pyomo.common.deprecation.deprecated
+ :noindex:
+
+.. autofunction:: pyomo.common.deprecation.deprecation_warning
+ :noindex:
+
+.. autofunction:: pyomo.common.deprecation.relocated_module
+ :noindex:
+
+.. autofunction:: pyomo.common.deprecation.relocated_module_attribute
+ :noindex:
+
+.. autoclass:: pyomo.common.deprecation.RenamedClass
+ :noindex:
+
+
+Removal
+-------
+
+By policy, functionality should be deprecated with reasonable
+warning, pending extenuating circumstances. The functionality should
+be deprecated, following the information above.
+
+If the functionality is documented in the most recent
+edition of [`Pyomo - Optimization Modeling in Python`_], it may not be removed
+until the next major version release.
+
+.. _Pyomo - Optimization Modeling in Python: https://doi.org/10.1007/978-3-030-68928-5
+
+For other functionality, it is preferred that ample time is given
+before removing the functionality. At minimum, significant functionality
+removal will result in a minor version bump.
diff --git a/doc/OnlineDocs/developer_reference/expressions/design.rst b/doc/OnlineDocs/developer_reference/expressions/design.rst
index f21e677cfc2..0d90b09953c 100644
--- a/doc/OnlineDocs/developer_reference/expressions/design.rst
+++ b/doc/OnlineDocs/developer_reference/expressions/design.rst
@@ -15,7 +15,7 @@ Most Pyomo expression trees have the following form
1. Interior nodes are objects that inherit from the :class:`ExpressionBase ` class. These objects typically have one or more child nodes. Linear expression nodes do not have child nodes, but they are treated as interior nodes in the expression tree because they references other leaf nodes.
-2. Leaf nodes are numeric values, parameter components and variable components, which represent the *inputs* to the expresion.
+2. Leaf nodes are numeric values, parameter components and variable components, which represent the *inputs* to the expression.
Expression Classes
------------------
@@ -41,7 +41,7 @@ logical relationships, which are summarized in the following table:
==================== ==================================== ========================================================================================
Operation Example Pyomo Class
==================== ==================================== ========================================================================================
-exernal function ``myfunc(x,y,z)`` :class:`ExternalFunctionExpression `
+external function ``myfunc(x,y,z)`` :class:`ExternalFunctionExpression `
logical if-then-else ``Expr_if(IF=x, THEN=y, ELSE=z)`` :class:`Expr_ifExpression `
intrinsic function ``sin(x)`` :class:`UnaryFunctionExpression `
absolute function ``abs(x)`` :class:`AbsExpression `
@@ -119,7 +119,7 @@ the named expression.
.. note::
The named expression classes are not implemented as sub-classes
- of :class:`ExpressionBase `.
+ of :class:`NumericExpression `.
This reflects design constraints related to the fact that these
are modeling components that belong to class hierarchies other
than the expression class hierarchy, and Pyomo's design prohibits
@@ -149,7 +149,7 @@ Pyomo does not have a binary sum expression class. Instead,
it has an ``n``-ary summation class, :class:`SumExpression
`. This expression class
treats sums as ``n``-ary sums for efficiency reasons; many large
-optimization models contain large sums. But note tht this class
+optimization models contain large sums. But note that this class
maintains the immutability property described above. This class
shares an underlying list of arguments with other :class:`SumExpression
` objects. A particular
diff --git a/doc/OnlineDocs/developer_reference/expressions/index.rst b/doc/OnlineDocs/developer_reference/expressions/index.rst
index 62220d300b0..769639d50eb 100644
--- a/doc/OnlineDocs/developer_reference/expressions/index.rst
+++ b/doc/OnlineDocs/developer_reference/expressions/index.rst
@@ -10,7 +10,7 @@ Pyomo Expressions
This documentation does not explicitly reference objects in
pyomo.core.kernel. While the Pyomo5 expression system works
with pyomo.core.kernel objects, the documentation of these
- documents was not sufficient to appropriately descibe the use
+ documents was not sufficient to appropriately describe the use
of kernel objects in expressions.
Pyomo supports the declaration of symbolic expressions that represent
diff --git a/doc/OnlineDocs/developer_reference/expressions/performance.rst b/doc/OnlineDocs/developer_reference/expressions/performance.rst
index e8d7fa2679f..2ed5dd0744b 100644
--- a/doc/OnlineDocs/developer_reference/expressions/performance.rst
+++ b/doc/OnlineDocs/developer_reference/expressions/performance.rst
@@ -50,7 +50,7 @@ For example, consider the following quadratic polynomial:
.. literalinclude:: ../../tests/expr/performance_loop3.spy
This quadratic polynomial is treated as a nonlinear expression
-unless the expression is explicilty processed to identify quadratic
+unless the expression is explicitly processed to identify quadratic
terms. This *lazy* identification of of quadratic terms allows
Pyomo to tailor the search for quadratic terms only when they are
explicitly needed.
diff --git a/doc/OnlineDocs/developer_reference/index.rst b/doc/OnlineDocs/developer_reference/index.rst
index e8a0e29d122..8c29150015c 100644
--- a/doc/OnlineDocs/developer_reference/index.rst
+++ b/doc/OnlineDocs/developer_reference/index.rst
@@ -10,4 +10,5 @@ scripts using Pyomo.
:maxdepth: 1
config.rst
+ deprecation.rst
expressions/index.rst
diff --git a/doc/OnlineDocs/errors.rst b/doc/OnlineDocs/errors.rst
index 784754797b3..162c2e10257 100644
--- a/doc/OnlineDocs/errors.rst
+++ b/doc/OnlineDocs/errors.rst
@@ -177,7 +177,7 @@ this warning (and an exception from the converter):
...
TypeError: Cannot create a Set from data that does not support __contains__...
ERROR (E2001): 5 is not a valid domain. Variable domains must be an instance
- of a Pyomo Set or convertable to a Pyomo Set.
+ of a Pyomo Set or convertible to a Pyomo Set.
See also https://pyomo.readthedocs.io/en/stable/errors.html#e2001
diff --git a/doc/OnlineDocs/index.rst b/doc/OnlineDocs/index.rst
index b3d6e754de0..ef986a3429f 100644
--- a/doc/OnlineDocs/index.rst
+++ b/doc/OnlineDocs/index.rst
@@ -18,6 +18,7 @@ with a diverse set of optimization capabilities.
solving_pyomo_models.rst
working_models.rst
working_abstractmodels/index.rst
+ model_transformations/index.rst
modeling_extensions/index.rst
tutorial_examples.rst
model_debugging/index.rst
diff --git a/doc/OnlineDocs/installation.rst b/doc/OnlineDocs/installation.rst
index 700cc7011fe..f833f1c4db5 100644
--- a/doc/OnlineDocs/installation.rst
+++ b/doc/OnlineDocs/installation.rst
@@ -3,9 +3,11 @@ Installation
Pyomo currently supports the following versions of Python:
-* CPython: 3.7, 3.8, 3.9, 3.10
+* CPython: 3.7, 3.8, 3.9, 3.10, 3.11
* PyPy: 3
+At the time of the first Pyomo release after the end-of-life of a minor Python
+version, Pyomo will remove testing for that Python version.
Using CONDA
~~~~~~~~~~~
diff --git a/doc/OnlineDocs/library_reference/aml/index.rst b/doc/OnlineDocs/library_reference/aml/index.rst
index d104e3dceb8..f06ca35b087 100644
--- a/doc/OnlineDocs/library_reference/aml/index.rst
+++ b/doc/OnlineDocs/library_reference/aml/index.rst
@@ -20,6 +20,7 @@ through the `pyomo.environ` namespace.
Constraint
ExternalFunction
Reference
+ SOSConstraint
AML Component Documentation
@@ -77,3 +78,8 @@ AML Component Documentation
:members:
:inherited-members:
+.. autoclass:: SOSConstraint
+ :show-inheritance:
+ :members:
+ :inherited-members:
+
diff --git a/doc/OnlineDocs/library_reference/appsi/appsi.rst b/doc/OnlineDocs/library_reference/appsi/appsi.rst
index 1881a8db67c..e26e4b0e82a 100644
--- a/doc/OnlineDocs/library_reference/appsi/appsi.rst
+++ b/doc/OnlineDocs/library_reference/appsi/appsi.rst
@@ -81,8 +81,26 @@ attribute. For example:
Installation
------------
+There are a few ways to install Appsi listed below.
+
+Option1:
+
+.. code-block::
+
+ pyomo build-extensions
+
+Option2:
.. code-block::
cd pyomo/contrib/appsi/
python build.py
+
+Option3:
+
+.. code-block::
+
+ python
+ >>> from pyomo.contrib.appsi.build import build_appsi
+ >>> build_appsi()
+
diff --git a/doc/OnlineDocs/library_reference/appsi/appsi.solvers.gurobi.rst b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.gurobi.rst
index cd83eed6e89..9e0af041410 100644
--- a/doc/OnlineDocs/library_reference/appsi/appsi.solvers.gurobi.rst
+++ b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.gurobi.rst
@@ -1,6 +1,47 @@
Gurobi
======
+
+Handling Gurobi licenses through the APPSI interface
+----------------------------------------------------
+
+In order to obtain performance benefits when re-solving a Pyomo model
+with Gurobi repeatedly, Pyomo has to keep a reference to a gurobipy
+model between calls to
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.solve()`. Depending
+on the Gurobi license type, this may "consume" a license as long as
+any APPSI-Gurobi interface exists (i.e., has not been garbage
+collected). To release a Gurobi license for other processes, use the
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.release_license()`
+method as shown below. Note that
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.release_license()`
+must be called on every instance for this to actually release the
+license. However, releasing the license will delete the gurobipy model
+which will have to be reconstructed from scratch the next time
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.solve()` is
+called, negating any performance benefit of the persistent solver
+interface.
+
+.. code-block:: python
+
+ >>> opt = appsi.solvers.Gurobi() # doctest: +SKIP
+ >>> results = opt.solve(model) # doctest: +SKIP
+ >>> opt.release_license() # doctest: +SKIP
+
+
+Also note that both the
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.available()` and
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.solve()` methods
+will construct a gurobipy model, thereby (depending on the type of
+license) "consuming" a license. The
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.available()`
+method has to do this so that the availability does not change between
+calls to
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.available()` and
+:py:meth:`~pyomo.contrib.appsi.solvers.gurobi.Gurobi.solve()`, leading
+to unexpected errors.
+
+
.. autoclass:: pyomo.contrib.appsi.solvers.gurobi.GurobiResults
:members:
:inherited-members:
diff --git a/doc/OnlineDocs/library_reference/appsi/appsi.solvers.highs.rst b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.highs.rst
new file mode 100644
index 00000000000..f2f72d0ad85
--- /dev/null
+++ b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.highs.rst
@@ -0,0 +1,14 @@
+HiGHS
+=====
+
+.. autoclass:: pyomo.contrib.appsi.solvers.highs.HighsResults
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
+
+.. autoclass:: pyomo.contrib.appsi.solvers.highs.Highs
+ :members:
+ :inherited-members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst
index 5b5664290bf..1c598d95628 100644
--- a/doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst
+++ b/doc/OnlineDocs/library_reference/appsi/appsi.solvers.rst
@@ -12,3 +12,4 @@ Solvers
appsi.solvers.ipopt
appsi.solvers.cplex
appsi.solvers.cbc
+ appsi.solvers.highs
diff --git a/doc/OnlineDocs/library_reference/common/config.rst b/doc/OnlineDocs/library_reference/common/config.rst
index bca74a76694..7a400b26ce3 100644
--- a/doc/OnlineDocs/library_reference/common/config.rst
+++ b/doc/OnlineDocs/library_reference/common/config.rst
@@ -12,11 +12,21 @@ Core classes
ConfigList
ConfigValue
+Utilities
+~~~~~~~~~
+
+.. autosummary::
+
+ document_kwargs_from_configdict
+
+
Domain validators
~~~~~~~~~~~~~~~~~
.. autosummary::
+ Bool
+ Integer
PositiveInt
NegativeInt
NonNegativeInt
@@ -27,6 +37,8 @@ Domain validators
NonNegativeFloat
In
InEnum
+ ListOf
+ Module
Path
PathList
DynamicImplicitDomain
@@ -50,10 +62,10 @@ Domain validators
:members:
:undoc-members:
-.. autoclass:: DynamicImplicitDomain
- :members:
- :undoc-members:
+.. autodecorator:: document_kwargs_from_configdict
+.. autofunction:: Bool
+.. autofunction:: Integer
.. autofunction:: PositiveInt
.. autofunction:: NegativeInt
.. autofunction:: NonNegativeInt
@@ -64,5 +76,8 @@ Domain validators
.. autofunction:: NonNegativeFloat
.. autoclass:: In
.. autoclass:: InEnum
+.. autoclass:: ListOf
+.. autoclass:: Module
.. autoclass:: Path
.. autoclass:: PathList
+.. autoclass:: DynamicImplicitDomain
diff --git a/doc/OnlineDocs/library_reference/common/errors.rst b/doc/OnlineDocs/library_reference/common/errors.rst
new file mode 100644
index 00000000000..7b2bd01fe32
--- /dev/null
+++ b/doc/OnlineDocs/library_reference/common/errors.rst
@@ -0,0 +1,6 @@
+pyomo.common.errors
+===================
+
+.. automodule:: pyomo.common.errors
+ :members:
+ :member-order: bysource
diff --git a/doc/OnlineDocs/library_reference/common/index.rst b/doc/OnlineDocs/library_reference/common/index.rst
index bb14ff84660..c9c99008250 100644
--- a/doc/OnlineDocs/library_reference/common/index.rst
+++ b/doc/OnlineDocs/library_reference/common/index.rst
@@ -1,7 +1,7 @@
Common Utilities
================
-Pyomo provides a set of general-purpose utilites through
+Pyomo provides a set of general-purpose utilities through
``pyomo.common``. These utilities are self-contained and do not import
or rely on any other parts of Pyomo.
@@ -11,6 +11,7 @@ or rely on any other parts of Pyomo.
config.rst
dependencies.rst
deprecation.rst
+ errors.rst
fileutils.rst
formatting.rst
tempfiles.rst
diff --git a/doc/OnlineDocs/library_reference/expressions/classes.rst b/doc/OnlineDocs/library_reference/expressions/classes.rst
index 0402bbe465c..9374272f501 100644
--- a/doc/OnlineDocs/library_reference/expressions/classes.rst
+++ b/doc/OnlineDocs/library_reference/expressions/classes.rst
@@ -4,12 +4,12 @@ Core Classes
The following are the two core classes documented here:
* :class:`NumericValue`
- * :class:`ExpressionBase`
+ * :class:`NumericExpression`
The remaining classes are the public classes for expressions, which
developers may need to know about. The methods for these classes are not
documented because they are described in the
-:class:`ExpressionBase` class.
+:class:`NumericExpression` class.
Sets with Expression Types
--------------------------
@@ -21,15 +21,15 @@ Pyomo expressions.
.. autodata:: pyomo.core.expr.numvalue.native_types
.. autodata:: pyomo.core.expr.numvalue.nonpyomo_leaf_types
-NumericValue and ExpressionBase
--------------------------------
+NumericValue and NumericExpression
+----------------------------------
.. autoclass:: pyomo.core.expr.numvalue.NumericValue
:members:
:special-members:
:private-members:
-.. autoclass:: pyomo.core.expr.current.ExpressionBase
+.. autoclass:: pyomo.core.expr.current.NumericExpression
:members:
:show-inheritance:
:special-members:
diff --git a/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py b/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py
index fb8fd2748c8..146048a6046 100644
--- a/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py
+++ b/doc/OnlineDocs/library_reference/kernel/examples/aml_example.py
@@ -1,5 +1,6 @@
# @Import_Syntax
import pyomo.environ as aml
+
# @Import_Syntax
datafile = None
@@ -7,8 +8,7 @@
# @AbstractModels
m = aml.AbstractModel()
# ... define model ...
-instance = \
- m.create_instance(datafile)
+instance = m.create_instance(datafile)
# @AbstractModels
del datafile
@@ -19,29 +19,27 @@
# @ConcreteModels
-
# @Sets_1
-m.s = aml.Set(initialize=[1,2],
- ordered=True)
+m.s = aml.Set(initialize=[1, 2], ordered=True)
# @Sets_1
# @Sets_2
# [1,2,3]
-m.q = aml.RangeSet(1,3)
+m.q = aml.RangeSet(1, 3)
# @Sets_2
-
# @Parameters_single
-m.p = aml.Param(mutable=True,
- initialize=0)
+m.p = aml.Param(mutable=True, initialize=0)
+
+
# @Parameters_single
# @Parameters_dict
# pd[1] = 0, pd[2] = 1
def pd_(m, i):
return m.s.ord(i) - 1
-m.pd = aml.Param(m.s,
- mutable=True,
- rule=pd_)
+
+
+m.pd = aml.Param(m.s, mutable=True, rule=pd_)
# @Parameters_dict
# @Parameters_list
@@ -53,36 +51,37 @@ def pd_(m, i):
# @Parameters_list
-
# @Variables_single
-m.v = aml.Var(initialize=1.0,
- bounds=(1,4))
+m.v = aml.Var(initialize=1.0, bounds=(1, 4))
# @Variables_single
# @Variables_dict
-m.vd = aml.Var(m.s,
- bounds=(None,9))
+m.vd = aml.Var(m.s, bounds=(None, 9))
+
# @Variables_dict
# @Variables_list
# used 1-based indexing
def vl_(m, i):
return (i, None)
+
+
m.vl = aml.VarList(bounds=vl_)
for j in m.q:
m.vl.add()
# @Variables_list
# @Constraints_single
-m.c = aml.Constraint(expr=\
- sum(m.vd.values()) <= 9)
+m.c = aml.Constraint(expr=sum(m.vd.values()) <= 9)
+
+
# @Constraints_single
# @Constraints_dict
-def cd_(m,i,j):
+def cd_(m, i, j):
return m.vd[i] == j
-m.cd = aml.Constraint(m.s,
- m.q,
- rule=cd_)
+
+
+m.cd = aml.Constraint(m.s, m.q, rule=cd_)
# @Constraints_dict
@@ -90,23 +89,21 @@ def cd_(m,i,j):
# uses 1-based indexing
m.cl = aml.ConstraintList()
for j in m.q:
- m.cl.add(
- aml.inequality(
- -5,
- m.vl[j]-m.v,
- 5))
+ m.cl.add(aml.inequality(-5, m.vl[j] - m.v, 5))
# @Constraints_list
-
# @Expressions_single
m.e = aml.Expression(expr=-m.v)
+
+
# @Expressions_single
# @Expressions_dict
def ed_(m, i):
return -m.vd[i]
-m.ed = aml.Expression(m.s,
- rule=ed_)
+
+
+m.ed = aml.Expression(m.s, rule=ed_)
# @Expressions_dict
# @Expressions_list
@@ -117,15 +114,17 @@ def ed_(m, i):
# @Expressions_list
-
# @Objectives_single
m.o = aml.Objective(expr=-m.v)
+
+
# @Objectives_single
# @Objectives_dict
def od_(m, i):
return -m.vd[i]
-m.od = aml.Objective(m.s,
- rule=od_)
+
+
+m.od = aml.Objective(m.s, rule=od_)
# @Objectives_dict
# @Objectives_list
# uses 1-based indexing
@@ -136,14 +135,11 @@ def od_(m, i):
# @Objectives_list
-
# @SOS_single
-m.sos1 = aml.SOSConstraint(
- var=m.vl,
- level=1)
-m.sos2 = aml.SOSConstraint(
- var=m.vd,
- level=2)
+m.sos1 = aml.SOSConstraint(var=m.vl, level=1)
+m.sos2 = aml.SOSConstraint(var=m.vd, level=2)
+
+
# @SOS_single
# @SOS_dict
def sd_(m, i):
@@ -152,10 +148,9 @@ def sd_(m, i):
elif i == 2:
t = list(m.vl.values())
return t
-m.sd = aml.SOSConstraint(
- [1,2],
- rule=sd_,
- level=1)
+
+
+m.sd = aml.SOSConstraint([1, 2], rule=sd_, level=1)
# @SOS_dict
# @SOS_list
@@ -166,10 +161,8 @@ def sd_(m, i):
# @SOS_list
-
# @Suffix_single
-m.dual = aml.Suffix(
- direction=aml.Suffix.IMPORT)
+m.dual = aml.Suffix(direction=aml.Suffix.IMPORT)
# @Suffix_single
# @Suffix_dict
#
@@ -178,20 +171,12 @@ def sd_(m, i):
# @Suffix_dict
-
# @Piecewise_1d
-breakpoints = [1,2,3,4]
-values = [1,2,1,2]
+breakpoints = [1, 2, 3, 4]
+values = [1, 2, 1, 2]
m.f = aml.Var()
-m.pw = aml.Piecewise(
- m.f,
- m.v,
- pw_pts=breakpoints,
- f_rule=values,
- pw_constr_type='EQ')
+m.pw = aml.Piecewise(m.f, m.v, pw_pts=breakpoints, f_rule=values, pw_constr_type='EQ')
# @Piecewise_1d
-
m.pprint()
-
diff --git a/doc/OnlineDocs/library_reference/kernel/examples/conic.py b/doc/OnlineDocs/library_reference/kernel/examples/conic.py
index be3fcb4d7df..9282bc67f9a 100644
--- a/doc/OnlineDocs/library_reference/kernel/examples/conic.py
+++ b/doc/OnlineDocs/library_reference/kernel/examples/conic.py
@@ -1,24 +1,22 @@
# @Class
import pyomo.kernel as pmo
+
m = pmo.block()
m.x1 = pmo.variable(lb=0)
m.x2 = pmo.variable()
m.r = pmo.variable(lb=0)
-m.q = pmo.conic.primal_exponential(
- x1=m.x1,
- x2=m.x2,
- r=m.r)
+m.q = pmo.conic.primal_exponential(x1=m.x1, x2=m.x2, r=m.r)
# @Class
del m
# @Domain
import pyomo.kernel as pmo
import math
+
m = pmo.block()
m.x = pmo.variable(lb=0)
m.y = pmo.variable(lb=0)
m.b = pmo.conic.primal_exponential.as_domain(
- x1=math.sqrt(2)*m.x,
- x2=2.0,
- r=2*(m.x + m.y))
+ x1=math.sqrt(2) * m.x, x2=2.0, r=2 * (m.x + m.y)
+)
# @Domain
diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py
index 997ffeafcad..1caf064bb2a 100644
--- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py
+++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_example.py
@@ -1,14 +1,18 @@
# @Import_Syntax
import pyomo.kernel as pmo
+
# @Import_Syntax
data = None
+
# @AbstractModels
def create(data):
instance = pmo.block()
# ... define instance ...
return instance
+
+
instance = create(data)
# @AbstractModels
del data
@@ -19,9 +23,8 @@ def create(data):
# @ConcreteModels
-
# @Sets_1
-m.s = [1,2]
+m.s = [1, 2]
# @Sets_1
# @Sets_2
@@ -30,7 +33,6 @@ def create(data):
# @Sets_2
-
# @Parameters_single
m.p = pmo.parameter(0)
@@ -38,7 +40,7 @@ def create(data):
# @Parameters_dict
# pd[1] = 0, pd[2] = 1
m.pd = pmo.parameter_dict()
-for k,i in enumerate(m.s):
+for k, i in enumerate(m.s):
m.pd[i] = pmo.parameter(k)
@@ -48,16 +50,12 @@ def create(data):
# pl[0] = 0, pl[0] = 1, ...
m.pl = pmo.parameter_list()
for j in m.q:
- m.pl.append(
- pmo.parameter(j))
+ m.pl.append(pmo.parameter(j))
# @Parameters_list
-
# @Variables_single
-m.v = pmo.variable(value=1,
- lb=1,
- ub=4)
+m.v = pmo.variable(value=1, lb=1, ub=4)
# @Variables_single
# @Variables_dict
m.vd = pmo.variable_dict()
@@ -68,77 +66,60 @@ def create(data):
# used 0-based indexing
m.vl = pmo.variable_list()
for j in m.q:
- m.vl.append(
- pmo.variable(lb=i))
+ m.vl.append(pmo.variable(lb=i))
# @Variables_list
-
# @Constraints_single
-m.c = pmo.constraint(
- sum(m.vd.values()) <= 9)
+m.c = pmo.constraint(sum(m.vd.values()) <= 9)
# @Constraints_single
# @Constraints_dict
m.cd = pmo.constraint_dict()
for i in m.s:
for j in m.q:
- m.cd[i,j] = \
- pmo.constraint(
- body=m.vd[i],
- rhs=j)
+ m.cd[i, j] = pmo.constraint(body=m.vd[i], rhs=j)
# @Constraints_dict
# @Constraints_list
# uses 0-based indexing
m.cl = pmo.constraint_list()
for j in m.q:
- m.cl.append(
- pmo.constraint(
- lb=-5,
- body=m.vl[j]-m.v,
- ub=5))
+ m.cl.append(pmo.constraint(lb=-5, body=m.vl[j] - m.v, ub=5))
# @Constraints_list
-
# @Expressions_single
m.e = pmo.expression(-m.v)
# @Expressions_single
# @Expressions_dict
m.ed = pmo.expression_dict()
for i in m.s:
- m.ed[i] = \
- pmo.expression(-m.vd[i])
+ m.ed[i] = pmo.expression(-m.vd[i])
# @Expressions_dict
# @Expressions_list
# uses 0-based indexed
m.el = pmo.expression_list()
for j in m.q:
- m.el.append(
- pmo.expression(-m.vl[j]))
+ m.el.append(pmo.expression(-m.vl[j]))
# @Expressions_list
-
# @Objectives_single
m.o = pmo.objective(-m.v)
# @Objectives_single
# @Objectives_dict
m.od = pmo.objective_dict()
for i in m.s:
- m.od[i] = \
- pmo.objective(-m.vd[i])
+ m.od[i] = pmo.objective(-m.vd[i])
# @Objectives_dict
# @Objectives_list
# uses 0-based indexing
m.ol = pmo.objective_list()
for j in m.q:
- m.ol.append(
- pmo.objective(-m.vl[j]))
+ m.ol.append(pmo.objective(-m.vl[j]))
# @Objectives_list
-
# @SOS_single
m.sos1 = pmo.sos1(m.vd.values())
@@ -153,46 +134,30 @@ def create(data):
m.sd[2] = pmo.sos1(m.vl)
-
-
-
-
-
# @SOS_dict
# @SOS_list
# uses 0-based indexing
m.sl = pmo.sos_list()
for i in m.s:
- m.sl.append(pmo.sos1(
- [m.vl[i], m.vd[i]]))
+ m.sl.append(pmo.sos1([m.vl[i], m.vd[i]]))
# @SOS_list
-
# @Suffix_single
-m.dual = pmo.suffix(
- direction=pmo.suffix.IMPORT)
+m.dual = pmo.suffix(direction=pmo.suffix.IMPORT)
# @Suffix_single
# @Suffix_dict
m.suffixes = pmo.suffix_dict()
-m.suffixes['dual'] = pmo.suffix(
- direction=pmo.suffix.IMPORT)
+m.suffixes['dual'] = pmo.suffix(direction=pmo.suffix.IMPORT)
# @Suffix_dict
-
# @Piecewise_1d
-breakpoints = [1,2,3,4]
-values = [1,2,1,2]
+breakpoints = [1, 2, 3, 4]
+values = [1, 2, 1, 2]
m.f = pmo.variable()
-m.pw = pmo.piecewise(
- breakpoints,
- values,
- input=m.v,
- output=m.f,
- bound='eq')
+m.pw = pmo.piecewise(breakpoints, values, input=m.v, output=m.f, bound='eq')
# @Piecewise_1d
-
pmo.pprint(m)
diff --git a/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py b/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py
index 5333fa7faff..c21c6dc890b 100644
--- a/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py
+++ b/doc/OnlineDocs/library_reference/kernel/examples/kernel_subclassing.py
@@ -1,8 +1,10 @@
import pyomo.kernel
+
# @Nonnegative
class NonNegativeVariable(pyomo.kernel.variable):
"""A non-negative variable."""
+
__slots__ = ()
def __init__(self, **kwds):
@@ -19,45 +21,57 @@ def __init__(self, **kwds):
def lb(self):
# calls the base class property getter
return pyomo.kernel.variable.lb.fget(self)
+
@lb.setter
def lb(self, lb):
if lb < 0:
raise ValueError("lower bound must be non-negative")
# calls the base class property setter
pyomo.kernel.variable.lb.fset(self, lb)
+
+
# @Nonnegative
+
# @Point
class Point(pyomo.kernel.variable_tuple):
"""A 3-dimensional point in Cartesian space with the
z coordinate restricted to non-negative values."""
+
__slots__ = ()
def __init__(self):
super(Point, self).__init__(
- (pyomo.kernel.variable(),
- pyomo.kernel.variable(),
- NonNegativeVariable()))
+ (pyomo.kernel.variable(), pyomo.kernel.variable(), NonNegativeVariable())
+ )
+
@property
def x(self):
return self[0]
+
@property
def y(self):
return self[1]
+
@property
def z(self):
return self[2]
+
+
# @Point
+
# @SOC
class SOC(pyomo.kernel.constraint):
"""A convex second-order cone constraint"""
+
__slots__ = ()
def __init__(self, point):
assert isinstance(point.z, NonNegativeVariable)
- super(SOC, self).__init__(
- point.x**2 + point.y**2 <= point.z**2)
+ super(SOC, self).__init__(point.x**2 + point.y**2 <= point.z**2)
+
+
# @SOC
# @Usage
diff --git a/doc/OnlineDocs/library_reference/kernel/examples/transformer.py b/doc/OnlineDocs/library_reference/kernel/examples/transformer.py
index 63f5e851213..3d8449a191d 100644
--- a/doc/OnlineDocs/library_reference/kernel/examples/transformer.py
+++ b/doc/OnlineDocs/library_reference/kernel/examples/transformer.py
@@ -3,45 +3,53 @@
import pympler.asizeof
+
def _fmt(num, suffix='B'):
"""format memory output"""
if num is None:
return ""
- for unit in ['','K','M','G','T','P','E','Z']:
+ for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
if abs(num) < 1000.0:
return "%3.1f %s%s" % (num, unit, suffix)
num /= 1000.0
return "%.1f %s%s" % (num, 'Yi', suffix)
+
# @kernel
class Transformer(pyomo.kernel.block):
def __init__(self):
- super(Transformer,self).__init__()
+ super(Transformer, self).__init__()
self._a = pyomo.kernel.parameter()
self._v_in = pyomo.kernel.expression()
self._v_out = pyomo.kernel.expression()
- self._c = pyomo.kernel.constraint(
- self._a * self._v_out == self._v_in)
+ self._c = pyomo.kernel.constraint(self._a * self._v_out == self._v_in)
+
def set_ratio(self, a):
assert a > 0
self._a.value = a
+
def connect_v_in(self, v_in):
self._v_in.expr = v_in
+
def connect_v_out(self, v_out):
self._v_out.expr = v_out
+
+
# @kernel
print(_fmt(pympler.asizeof.asizeof(Transformer())))
+
# @aml
def Transformer():
b = pyomo.environ.Block(concrete=True)
b._a = pyomo.environ.Param(mutable=True)
b._v_in = pyomo.environ.Expression()
b._v_out = pyomo.environ.Expression()
- b._c = pyomo.environ.Constraint(expr=\
- b._a * b._v_out == b._v_in)
+ b._c = pyomo.environ.Constraint(expr=b._a * b._v_out == b._v_in)
return b
+
+
# @aml
print(_fmt(pympler.asizeof.asizeof(Transformer())))
diff --git a/doc/OnlineDocs/library_reference/solvers/gurobi_direct.rst b/doc/OnlineDocs/library_reference/solvers/gurobi_direct.rst
new file mode 100644
index 00000000000..21cb79e5531
--- /dev/null
+++ b/doc/OnlineDocs/library_reference/solvers/gurobi_direct.rst
@@ -0,0 +1,18 @@
+GurobiDirect
+============
+
+.. currentmodule:: pyomo.solvers.plugins.solvers.gurobi_direct
+
+Methods
+-------
+
+.. autosummary::
+
+ GurobiDirect.available
+ GurobiDirect.close
+ GurobiDirect.close_global
+ GurobiDirect.solve
+ GurobiDirect.version
+
+.. autoclass:: GurobiDirect
+ :members: available, close, close_global, solve, version
diff --git a/doc/OnlineDocs/library_reference/solvers/index.rst b/doc/OnlineDocs/library_reference/solvers/index.rst
index b2e215c3070..400032df076 100644
--- a/doc/OnlineDocs/library_reference/solvers/index.rst
+++ b/doc/OnlineDocs/library_reference/solvers/index.rst
@@ -6,5 +6,6 @@ Solver Interfaces
gams.rst
cplex_persistent.rst
+ gurobi_direct.rst
gurobi_persistent.rst
xpress_persistent.rst
diff --git a/doc/OnlineDocs/model_transformations/index.rst b/doc/OnlineDocs/model_transformations/index.rst
new file mode 100644
index 00000000000..462538128e7
--- /dev/null
+++ b/doc/OnlineDocs/model_transformations/index.rst
@@ -0,0 +1,7 @@
+Model Transformations
+=====================
+
+.. toctree::
+ :maxdepth: 1
+
+ scaling.rst
diff --git a/doc/OnlineDocs/model_transformations/scaling.rst b/doc/OnlineDocs/model_transformations/scaling.rst
new file mode 100644
index 00000000000..180f1e0205b
--- /dev/null
+++ b/doc/OnlineDocs/model_transformations/scaling.rst
@@ -0,0 +1,41 @@
+Model Scaling Transformation
+============================
+
+Good scaling of models can greatly improve the numerical properties of a problem and thus increase reliability and convergence. The ``core.scale_model`` transformation allows users to separate scaling of a model from the declaration of the model variables and constraints which allows for models to be written in more natural forms and to be scaled and rescaled as required without having to rewrite the model code.
+
+.. autoclass:: pyomo.core.plugins.transform.scaling.ScaleModel
+ :members:
+
+Setting Scaling Factors
+-----------------------
+
+Scaling factors for components in a model are declared using :ref:`Suffixes`, as shown in the example above. In order to define a scaling factor for a component, a ``Suffix`` named ``scaling_factor`` must first be created to hold the scaling factor(s). Scaling factor suffixes can be declared at any level of the model hierarchy, but scaling factors declared on the higher-level ``models`` or ``Blocks`` take precedence over those declared at lower levels.
+
+Scaling suffixes are dict-like where each key is a Pyomo component and the value is the scaling factor to be applied to that component.
+
+In the case of indexed components, scaling factors can either be declared for an individual index or for the indexed component as a whole (with scaling factors for individual indices taking precedence over overall scaling factors).
+
+.. note::
+
+ In the case that a scaling factor is declared for a component on at multiple levels of the hierarchy, the highest level scaling factor will be applied.
+
+.. note::
+
+ It is also possible (but not encouraged) to define a "default" scaling factor to be applied to any component for which a specific scaling factor has not been declared by setting a entry in a Suffix with a key of ``None``. In this case, the default value declared closest to the component to be scaled will be used (i.e., the first default value found when walking up the model hierarchy).
+
+Applying Model Scaling
+----------------------
+
+The ``core.scale_model`` transformation provides two approaches for creating a scaled model.
+
+In-Place Scaling
+****************
+
+The ``apply_to(model)`` method can be used to apply scaling directly to an existing model. When using this method, all the variables, constraints and objectives within the target model are replaced with new scaled components and the appropriate scaling factors applied. The model can then be sent to a solver as usual, however the results will be in terms of the scaled components and must be un-scaled by the user.
+
+Creating a New Scaled Model
+***************************
+
+Alternatively, the ``create_using(model)`` method can be used to create a new, scaled version of the model which can be solved. In this case, a clone of the original model is generated with the variables, constraints and objectives replaced by scaled equivalents. Users can then send the scaled model to a solver after which the ``propagate_solution`` method can be used to map the scaled solution back onto the original model for further analysis.
+
+The advantage of this approach is that the original model is maintained separately from the scaled model, which facilitates rescaling and other manipulation of the original model after a solution has been found. The disadvantage of this approach is that cloning the model may result in memory issues when dealing with larger models.
diff --git a/doc/OnlineDocs/modeling_extensions/dae.rst b/doc/OnlineDocs/modeling_extensions/dae.rst
index 19ac3c15593..703e83f4f14 100644
--- a/doc/OnlineDocs/modeling_extensions/dae.rst
+++ b/doc/OnlineDocs/modeling_extensions/dae.rst
@@ -446,7 +446,7 @@ a number of finite element points which is less than the number of points
already included in the :py:class:`ContinuousSet` then
the transformation will ignore the specified number and proceed with the larger
set of points. Discretization points will never be removed from a
-:py:class:`ContinousSet` during the discretization.
+:py:class:`ContinuousSet` during the discretization.
The following code is a Python script applying the backward difference
method. The code also shows how to add a constraint to a discretized model.
@@ -583,7 +583,7 @@ In the above example, the ``reduce_collocation_points`` function restricts
the variable ``model.u`` to have only **1** free collocation point per
finite element, thereby enforcing a piecewise constant profile.
:numref:`Fig. %s ` shows the solution profile before and
-after appling
+after applying
the ``reduce_collocation_points`` function.
.. _reduce_points_fig:
@@ -837,8 +837,8 @@ the corresponding values for the dynamic variable profiles.
- Need to provide initial conditions for dynamic states by setting the
value or using fix()
-Specifying Time-Varing Inputs
-*****************************
+Specifying Time-Varying Inputs
+******************************
The :py:class:`Simulator` supports simulation of a system
of ODE's or DAE's with time-varying parameters or control inputs. Time-varying
inputs can be specified using a Pyomo ``Suffix``. We currently only support
diff --git a/doc/OnlineDocs/modeling_extensions/gdp/concepts.rst b/doc/OnlineDocs/modeling_extensions/gdp/concepts.rst
index 3f571f988d2..95629bc48fd 100644
--- a/doc/OnlineDocs/modeling_extensions/gdp/concepts.rst
+++ b/doc/OnlineDocs/modeling_extensions/gdp/concepts.rst
@@ -115,7 +115,7 @@ These logical propositions can include:
.. |equiv| replace:: :math:`Y_1 \Leftrightarrow Y_2`
.. |land| replace:: :math:`Y_1 \land Y_2`
.. |lor| replace:: :math:`Y_1 \lor Y_2`
-.. |xor| replace:: :math:`Y_1 \underline{\lor} Y_2`
+.. |xor| replace:: :math:`Y_1 \veebar Y_2`
.. |impl| replace:: :math:`Y_1 \Rightarrow Y_2`
+-----------------+---------+-------------+-------------+-------------+
diff --git a/doc/OnlineDocs/modeling_extensions/gdp/index.rst b/doc/OnlineDocs/modeling_extensions/gdp/index.rst
index a5e47333377..0c8529c60cb 100644
--- a/doc/OnlineDocs/modeling_extensions/gdp/index.rst
+++ b/doc/OnlineDocs/modeling_extensions/gdp/index.rst
@@ -54,7 +54,7 @@ These can be expressed as a disjunction as follows:
\text{constraints} \\
\text{for }\textit{else}
\end{gathered}\right] \\
- Y_1 \underline{\vee} Y_2
+ Y_1 \veebar Y_2
\end{gather*}
Here, if the Boolean :math:`Y_1` is ``True``, then the constraints in the first disjunct are enforced; otherwise, the constraints in the second disjunct are enforced.
diff --git a/doc/OnlineDocs/modeling_extensions/gdp/modeling.rst b/doc/OnlineDocs/modeling_extensions/gdp/modeling.rst
index 9627698634e..b70e37d5935 100644
--- a/doc/OnlineDocs/modeling_extensions/gdp/modeling.rst
+++ b/doc/OnlineDocs/modeling_extensions/gdp/modeling.rst
@@ -86,7 +86,7 @@ When the ``Disjunction`` object constructor is passed a list of lists, the outer
By default, Pyomo.GDP ``Disjunction`` objects enforce an implicit "exactly one" relationship among the selection of the disjuncts (generalization of exclusive-OR).
That is, exactly one of the ``Disjunct`` indicator variables should take a ``True`` value.
- This can be seen as an implicit logical proposition, in our example, :math:`Y_1 \underline{\lor} Y_2`.
+ This can be seen as an implicit logical proposition, in our example, :math:`Y_1 \veebar Y_2`.
Logical Propositions
====================
@@ -112,7 +112,7 @@ Using these Boolean variables, we can define ``LogicalConstraint`` objects, anal
.. doctest::
- >>> m.p = LogicalConstraint(expr=m.Y[1].implies(land(m.Y[2], m.Y[3])).lor(m.Y[4]))
+ >>> m.p = LogicalConstraint(expr=m.Y[1].implies(m.Y[2] & m.Y[3]) | m.Y[4])
>>> m.p.pprint()
p : Size=1, Index=None, Active=True
Key : Body : Active
@@ -126,19 +126,24 @@ Pyomo.GDP logical expression system supported operators and their usage are list
+--------------+------------------------+-----------------------------------+--------------------------------+
| Operator | Operator | Method | Function |
+==============+========================+===================================+================================+
-| Conjunction | | :code:`Y[1].land(Y[2])` | :code:`land(Y[1],Y[2])` |
+| Negation | :code:`~Y[1]` | | :code:`lnot(Y[1])` |
+--------------+------------------------+-----------------------------------+--------------------------------+
-| Disjunction | | :code:`Y[1].lor(Y[2])` | :code:`lor(Y[1],Y[2])` |
+| Conjunction | :code:`Y[1] & Y[2]` | :code:`Y[1].land(Y[2])` | :code:`land(Y[1],Y[2])` |
+--------------+------------------------+-----------------------------------+--------------------------------+
-| Negation | :code:`~Y[1]` | | :code:`lnot(Y[1])` |
+| Disjunction | :code:`Y[1] | Y[2]` | :code:`Y[1].lor(Y[2])` | :code:`lor(Y[1],Y[2])` |
+--------------+------------------------+-----------------------------------+--------------------------------+
-| Exclusive OR | | :code:`Y[1].xor(Y[2])` | :code:`xor(Y[1], Y[2])` |
+| Exclusive OR | :code:`Y[1] ^ Y[2]` | :code:`Y[1].xor(Y[2])` | :code:`xor(Y[1], Y[2])` |
+--------------+------------------------+-----------------------------------+--------------------------------+
| Implication | | :code:`Y[1].implies(Y[2])` | :code:`implies(Y[1], Y[2])` |
+--------------+------------------------+-----------------------------------+--------------------------------+
| Equivalence | | :code:`Y[1].equivalent_to(Y[2])` | :code:`equivalent(Y[1], Y[2])` |
+--------------+------------------------+-----------------------------------+--------------------------------+
+.. note::
+
+ We omit support for some infix operators, e.g. :code:`Y[1] >> Y[2]`, due to concerns about non-intuitive Python operator precedence.
+ That is :code:`Y[1] | Y[2] >> Y[3]` would translate to :math:`Y_1 \lor (Y_2 \Rightarrow Y_3)` rather than :math:`(Y_1 \lor Y_2) \Rightarrow Y_3`
+
In addition, the following constraint-programming-inspired operators are provided: ``exactly``, ``atmost``, and ``atleast``.
These predicates enforce, respectively, that exactly, at most, or at least N of their ``BooleanVar`` arguments are ``True``.
@@ -148,26 +153,22 @@ Usage:
- :code:`atmost(3, Y)`
- :code:`exactly(3, Y)`
-.. note::
-
- We omit support for most infix operators, e.g. :code:`Y[1] >> Y[2]`, due to concerns about non-intuitive Python operator precedence.
- That is :code:`Y[1] | Y[2] >> Y[3]` would translate to :math:`Y_1 \lor (Y_2 \Rightarrow Y_3)` rather than :math:`(Y_1 \lor Y_2) \Rightarrow Y_3`
-
.. doctest::
>>> m = ConcreteModel()
>>> m.my_set = RangeSet(4)
>>> m.Y = BooleanVar(m.my_set)
>>> m.p = LogicalConstraint(expr=atleast(3, m.Y))
+ >>> m.p.pprint()
+ p : Size=1, Index=None, Active=True
+ Key : Body : Active
+ None : atleast(3: [Y[1], Y[2], Y[3], Y[4]]) : True
>>> TransformationFactory('core.logical_to_linear').apply_to(m)
- >>> m.logic_to_linear.transformed_constraints.pprint() # constraint auto-generated by transformation
+ >>> # constraint auto-generated by transformation
+ >>> m.logic_to_linear.transformed_constraints.pprint()
transformed_constraints : Size=1, Index=logic_to_linear.transformed_constraints_index, Active=True
Key : Lower : Body : Upper : Active
1 : 3.0 : Y_asbinary[1] + Y_asbinary[2] + Y_asbinary[3] + Y_asbinary[4] : +Inf : True
- >>> m.p.pprint()
- p : Size=1, Index=None, Active=False
- Key : Body : Active
- None : atleast(3: [Y[1], Y[2], Y[3], Y[4]]) : False
We elaborate on the ``logical_to_linear`` transformation :ref:`on the next page `.
@@ -223,8 +224,8 @@ Here, we demonstrate this capability with a toy example:
\min~&x\\
\text{s.t.}~&\left[\begin{gathered}Y_1\\x \geq 2\end{gathered}\right] \vee \left[\begin{gathered}Y_2\\x \geq 3\end{gathered}\right]\\
&\left[\begin{gathered}Y_3\\x \leq 8\end{gathered}\right] \vee \left[\begin{gathered}Y_4\\x = 2.5\end{gathered}\right] \\
- &Y_1 \underline{\vee} Y_2\\
- &Y_3 \underline{\vee} Y_4\\
+ &Y_1 \veebar Y_2\\
+ &Y_3 \veebar Y_4\\
&Y_1 \Rightarrow Y_4
.. doctest::
@@ -288,8 +289,8 @@ Composition of standard operators
.. code::
- m.p = LogicalConstraint(expr=lor(m.Y[1], m.Y[2]).implies(
- land(m.Y[3], ~m.Y[4], m.Y[5].lor(m.Y[6])))
+ m.p = LogicalConstraint(expr=(m.Y[1] | m.Y[2]).implies(
+ m.Y[3] & ~m.Y[4] & (m.Y[5] | m.Y[6]))
)
Expressions within CP-type operators
@@ -359,7 +360,7 @@ In the ``logical_to_linear`` transformation, we automatically convert these spec
Additional Examples
===================
-The following models all work and are equivalent for :math:`\left[x = 0\right] \underline{\lor} \left[y = 0\right]`:
+The following models all work and are equivalent for :math:`\left[x = 0\right] \veebar \left[y = 0\right]`:
.. doctest::
diff --git a/doc/OnlineDocs/modeling_extensions/gdp/solving.rst b/doc/OnlineDocs/modeling_extensions/gdp/solving.rst
index 8f5e1ccd250..2f3076862e6 100644
--- a/doc/OnlineDocs/modeling_extensions/gdp/solving.rst
+++ b/doc/OnlineDocs/modeling_extensions/gdp/solving.rst
@@ -29,15 +29,25 @@ Logical constraints
.. note::
- Historically it was required to convert logical propositions to
- algebraic form prior to use of the MI(N)LP reformulations and the
- GDPopt solver. However, this is mathematically incorrect since these
- reformulations convert logical formulations to algebraic formulations.
- It is therefore recommended to use both the MI(N)LP reformulations
- and GDPopt directly to transform or solve GDPs that include logical
- propositions.
+ Historically users needed to explicitly convert logical propositions
+ to algebraic form prior to invoking the GDP MI(N)LP reformulations
+ or the GDPopt solver. However, this is mathematically incorrect
+ since the GDP MI(N)LP reformulations themselves convert logical
+ formulations to algebraic formulations. The current recommended
+ practice is to pass the entire (mixed logical / algebraic) model to
+ the MI(N)LP reformulations or GDPopt directly.
+
+There are several approaches to convert logical constraints into
+algebraic form.
+
+Conjunctive Normal Form
+^^^^^^^^^^^^^^^^^^^^^^^
-The following transforms logical propositions on the model to algebraic form:
+The first transformation (`core.logical_to_linear`) leverages the
+`sympy` package to generate the conjunctive normal form of the logical
+constraints and then adds the equivalent as a list algebraic
+constraints. The following transforms logical propositions on the model
+to algebraic form:
.. code::
@@ -61,6 +71,29 @@ Following solution of the GDP model, values of the Boolean variables may be upda
.. autofunction:: pyomo.core.plugins.transform.logical_to_linear.update_boolean_vars_from_binary
+Factorable Programming
+^^^^^^^^^^^^^^^^^^^^^^
+
+The second transformation (`contrib.logical_to_disjunctive`) leverages
+ideas from factorable programming to first generate an equivalent set of
+"factored" logical constraints form by traversing each logical
+proposition and replacing each logical operator with an additional
+Boolean variable and then adding the "simple" logical constraint that
+equates the new Boolean variable with the single logical operator.
+
+The resulting "simple" logical constraints are converted to either MIP
+or GDP form: if the constraint contains only Boolean variables, then
+then MIP representation is emitted. Logical constraints with mixed
+integer-Boolean arguments (e.g., `atmost`, `atleast`, `exactly`, etc.)
+are converted to a disjunctive representation.
+
+As this transformation both avoids the conversion into `sympy` and only
+requires a single traversal of each logical constraint,
+`contrib.logical_to_disjunctive` is significantly faster than
+`core.logical_to_linear` at the cost of a larger model. In practice,
+the cost of the larger model is negated by the effectiveness of the MIP
+presolve in most solvers.
+
Reformulation to MI(N)LP
------------------------
@@ -77,7 +110,6 @@ By default, the BM transformation will estimate reasonably tight M values for yo
For nonlinear models where finite expression bounds may be inferred from variable bounds, the BM transformation may also be able to automatically compute M values for you.
For all other models, you will need to provide the M values through a "BigM" Suffix, or through the `bigM` argument to the transformation.
We will raise a ``GDP_Error`` for missing M values.
-We implement the multiple-parameter Big-M (MBM) approach described in literature\ [#gdp-mbm]_.
To apply the BM reformulation within a python script, use:
@@ -87,6 +119,27 @@ To apply the BM reformulation within a python script, use:
From the Pyomo command line, include the ``--transform pyomo.gdp.bigm`` option.
+Multiple Big-M (MBM) Reformulation
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+We also implement the multiple-parameter Big-M (MBM) approach described in literature\ [#gdp-mbm]_.
+By default, the MBM transformation will solve continuous subproblems in order to calculate M values.
+This process can be time-consuming, so the transformation also provides a method to export the M values used as a dictionary and allows for M values to be provided through the `bigM` argument.
+
+For example, to apply the transformation and store the M values, use:
+
+.. code::
+
+ mbigm = TransformationFactory('gdp.mbigm')
+ mbigm.apply_to(model)
+
+ # These can be stored...
+ M_values = mbigm.get_all_M_values(model)
+ # ...so that in future runs, you can write:
+ mbigm.apply_to(m, bigM=M_values)
+
+From the Pyomo command line, include the ``--transform pyomo.gdp.mbigm`` option.
+
Hull Reformulation (HR)
^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/doc/OnlineDocs/pyomo_modeling_components/Expressions.rst b/doc/OnlineDocs/pyomo_modeling_components/Expressions.rst
index 0e5b5200b5d..f0558621316 100644
--- a/doc/OnlineDocs/pyomo_modeling_components/Expressions.rst
+++ b/doc/OnlineDocs/pyomo_modeling_components/Expressions.rst
@@ -2,7 +2,7 @@ Expressions
===========
In this section, we use the word "expression" in two ways: first in the
-general sense of the word and second to desribe a class of Pyomo objects
+general sense of the word and second to describe a class of Pyomo objects
that have the name ``Expression`` as described in the subsection on
expression objects.
diff --git a/doc/OnlineDocs/pyomo_modeling_components/Parameters.rst b/doc/OnlineDocs/pyomo_modeling_components/Parameters.rst
index 194555b74dc..90bcff4dc27 100644
--- a/doc/OnlineDocs/pyomo_modeling_components/Parameters.rst
+++ b/doc/OnlineDocs/pyomo_modeling_components/Parameters.rst
@@ -78,7 +78,7 @@ Parameter values can be checked by a validation function. In the
following example, the every value of the parameter ``T`` (indexed by
``model.A``) is checked
to be greater than 3.14159. If a value is provided that is less than
-that, the model instantation will be terminated and an error message
+that, the model instantiation will be terminated and an error message
issued. The validation function should be written so as to return
``True`` if the data is valid and ``False`` otherwise.
diff --git a/doc/OnlineDocs/pyomo_modeling_components/Sets.rst b/doc/OnlineDocs/pyomo_modeling_components/Sets.rst
index 2f71401c2b4..f9a692fcb10 100644
--- a/doc/OnlineDocs/pyomo_modeling_components/Sets.rst
+++ b/doc/OnlineDocs/pyomo_modeling_components/Sets.rst
@@ -125,7 +125,7 @@ Note that the element number starts with 1 and not 0:
None : 1 : Any : 10 : {3, 5, 7, 9, 11, 13, 15, 17, 19, 21}
Additional information about iterators for set initialization is in the
-[PyomoBookII]_ book.
+[PyomoBookIII]_ book.
.. note::
diff --git a/doc/OnlineDocs/pyomo_modeling_components/Suffixes.rst b/doc/OnlineDocs/pyomo_modeling_components/Suffixes.rst
index 58c4de9fbc2..e45fe2d74b7 100644
--- a/doc/OnlineDocs/pyomo_modeling_components/Suffixes.rst
+++ b/doc/OnlineDocs/pyomo_modeling_components/Suffixes.rst
@@ -388,6 +388,7 @@ Suffix component with an IMPORT_EXPORT direction.
ipopt = pyo.SolverFactory('ipopt')
+
The difference in performance can be seen by examining Ipopt's iteration
log with and without warm starting:
@@ -405,13 +406,9 @@ log with and without warm starting:
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
0 1.6109693e+01 1.12e+01 5.28e-01 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0
1 1.6982239e+01 7.30e-01 1.02e+01 -1.0 6.11e-01 - 7.19e-02 1.00e+00f 1
- 2 1.7318411e+01 3.60e-02 5.05e-01 -1.0 1.61e-01 - 1.00e+00 1.00e+00h 1
- 3 1.6849424e+01 2.78e-01 6.68e-02 -1.7 2.85e-01 - 7.94e-01 1.00e+00h 1
- 4 1.7051199e+01 4.71e-03 2.78e-03 -1.7 6.06e-02 - 1.00e+00 1.00e+00h 1
- 5 1.7011979e+01 7.19e-03 8.50e-03 -3.8 3.66e-02 - 9.45e-01 9.98e-01h 1
- 6 1.7014271e+01 1.74e-05 9.78e-06 -3.8 3.33e-03 - 1.00e+00 1.00e+00h 1
- 7 1.7014021e+01 1.23e-07 1.82e-07 -5.7 2.69e-04 - 1.00e+00 1.00e+00h 1
- 8 1.7014017e+01 1.77e-11 2.52e-11 -8.6 3.32e-06 - 1.00e+00 1.00e+00h 1
+ 2 1.7318411e+01 ...
+ ...
+ 8 1.7014017e+01 ...
Number of Iterations....: 8
...
@@ -441,7 +438,7 @@ log with and without warm starting:
iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls
0 1.7014032e+01 2.00e-06 4.07e-06 -6.0 0.00e+00 - 0.00e+00 0.00e+00 0
1 1.7014019e+01 3.65e-12 1.00e-11 -6.0 2.50e-01 - 1.00e+00 1.00e+00h 1
- 2 1.7014017e+01 4.48e-12 6.42e-12 -9.0 1.92e-06 - 1.00e+00 1.00e+00h 1
+ 2 1.7014017e+01 ...
Number of Iterations....: 2
...
diff --git a/doc/OnlineDocs/pyomo_overview/math_modeling.rst b/doc/OnlineDocs/pyomo_overview/math_modeling.rst
index 20769524863..ccacca8d58d 100644
--- a/doc/OnlineDocs/pyomo_overview/math_modeling.rst
+++ b/doc/OnlineDocs/pyomo_overview/math_modeling.rst
@@ -3,7 +3,7 @@ Mathematical Modeling
This section provides an introduction to Pyomo: Python Optimization
Modeling Objects. A more complete description is contained in the
-[PyomoBookII]_ book. Pyomo supports the formulation and analysis of
+[PyomoBookIII]_ book. Pyomo supports the formulation and analysis of
mathematical models for complex optimization applications. This
capability is commonly associated with commercially available algebraic
modeling languages (AMLs) such as [AMPL]_, [AIMMS]_, and [GAMS]_.
@@ -72,8 +72,8 @@ solvers to analyze a model introduces additional complexities.
Pyomo is an AML that extends Python to include objects for mathematical
-modeling. [PyomoBookI]_, [PyomoBookII]_, and [PyomoJournal]_ compare
-Pyomo with other AMLs. Although many good AMLs have been developed for
+modeling. [PyomoBookI]_, [PyomoBookII]_, [PyomoBookIII]_, and [PyomoJournal]_
+compare Pyomo with other AMLs. Although many good AMLs have been developed for
optimization models, the following are motivating factors for the
development of Pyomo:
diff --git a/doc/OnlineDocs/pyomo_overview/simple_examples.rst b/doc/OnlineDocs/pyomo_overview/simple_examples.rst
index 3806d573ac3..4358c87b678 100644
--- a/doc/OnlineDocs/pyomo_overview/simple_examples.rst
+++ b/doc/OnlineDocs/pyomo_overview/simple_examples.rst
@@ -58,7 +58,6 @@ One way to implement this in Pyomo is as shown as follows:
.. testcode::
- from __future__ import division
import pyomo.environ as pyo
model = pyo.AbstractModel()
@@ -103,21 +102,8 @@ One way to implement this in Pyomo is as shown as follows:
indented and the end of the indentation is used by Python to signal
the end of the definition.
-We will now examine the lines in this example. The first import line is
-used to ensure that ``int`` or ``long`` division arguments are converted
-to floating point values before division is performed.
-
-.. testcode::
-
- from __future__ import division
-
-In Python versions before 3.0, division returns the floor of the
-mathematical result of division if arguments are ``int`` or ``long``.
-This import line avoids unexpected behavior when developing mathematical
-models with integer values in Python 2.x (and is not necessary in Python
-3.x).
-
-The next import line that is required in every Pyomo model. Its purpose
+We will now examine the lines in this example.
+The first import line is required in every Pyomo model. Its purpose
is to make the symbols used by Pyomo known to Python.
.. testcode::
diff --git a/doc/OnlineDocs/tests/data/ABCD2.py b/doc/OnlineDocs/tests/data/ABCD2.py
index 15b4e46b04e..65a46415368 100644
--- a/doc/OnlineDocs/tests/data/ABCD2.py
+++ b/doc/OnlineDocs/tests/data/ABCD2.py
@@ -2,13 +2,13 @@
model = AbstractModel()
-model.Z = Set(initialize=[('A1','B1',1), ('A2','B2',2), ('A3','B3',3)])
-#model.Z = Set(dimen=3)
+model.Z = Set(initialize=[('A1', 'B1', 1), ('A2', 'B2', 2), ('A3', 'B3', 3)])
+# model.Z = Set(dimen=3)
model.D = Param(model.Z)
instance = model.create_instance('ABCD2.dat')
-print('Z '+str(sorted(list(instance.Z.data()))))
+print('Z ' + str(sorted(list(instance.Z.data()))))
print('D')
for key in sorted(instance.D.keys()):
- print(name(instance.D,key)+" "+str(value(instance.D[key])))
+ print(name(instance.D, key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/ABCD3.py b/doc/OnlineDocs/tests/data/ABCD3.py
index b3b63af6c16..48797ced5bb 100644
--- a/doc/OnlineDocs/tests/data/ABCD3.py
+++ b/doc/OnlineDocs/tests/data/ABCD3.py
@@ -7,7 +7,7 @@
instance = model.create_instance('ABCD3.dat')
-print('Z '+str(sorted(list(instance.Z.data()))))
+print('Z ' + str(sorted(list(instance.Z.data()))))
print('D')
for key in sorted(instance.D.keys()):
- print(name(instance.D,key)+" "+str(value(instance.D[key])))
+ print(name(instance.D, key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/ABCD4.py b/doc/OnlineDocs/tests/data/ABCD4.py
index 6ab89a84188..20f6a21c011 100644
--- a/doc/OnlineDocs/tests/data/ABCD4.py
+++ b/doc/OnlineDocs/tests/data/ABCD4.py
@@ -7,7 +7,7 @@
instance = model.create_instance('ABCD4.dat')
-print('Z '+str(sorted(list(instance.Z.data()))))
+print('Z ' + str(sorted(list(instance.Z.data()))))
print('Y')
for key in sorted(instance.Y.keys()):
- print(name(instance.Y,key)+" "+str(value(instance.Y[key])))
+ print(name(instance.Y, key) + " " + str(value(instance.Y[key])))
diff --git a/doc/OnlineDocs/tests/data/ABCD5.py b/doc/OnlineDocs/tests/data/ABCD5.py
index 39b3ff667fa..58461af056b 100644
--- a/doc/OnlineDocs/tests/data/ABCD5.py
+++ b/doc/OnlineDocs/tests/data/ABCD5.py
@@ -10,10 +10,10 @@
instance = model.create_instance('ABCD5.dat')
-print('Z '+str(sorted(list(instance.Z.data()))))
+print('Z ' + str(sorted(list(instance.Z.data()))))
print('Y')
for key in sorted(instance.Y.keys()):
- print(name(instance.Y,key)+" "+str(value(instance.Y[key])))
+ print(name(instance.Y, key) + " " + str(value(instance.Y[key])))
print('W')
for key in sorted(instance.W.keys()):
- print(name(instance.W,key)+" "+str(value(instance.W[key])))
+ print(name(instance.W, key) + " " + str(value(instance.W[key])))
diff --git a/doc/OnlineDocs/tests/data/ABCD6.py b/doc/OnlineDocs/tests/data/ABCD6.py
index dc9c156458e..961408dbc7e 100644
--- a/doc/OnlineDocs/tests/data/ABCD6.py
+++ b/doc/OnlineDocs/tests/data/ABCD6.py
@@ -7,7 +7,7 @@
instance = model.create_instance('ABCD6.dat')
-print('Z '+str(sorted(list(instance.Z.data()))))
+print('Z ' + str(sorted(list(instance.Z.data()))))
print('D')
for key in sorted(instance.D.keys()):
- print(name(instance.D,key)+" "+str(value(instance.D[key])))
+ print(name(instance.D, key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/ABCD7.py b/doc/OnlineDocs/tests/data/ABCD7.py
index 0f9f8967210..a97e764fa5a 100644
--- a/doc/OnlineDocs/tests/data/ABCD7.py
+++ b/doc/OnlineDocs/tests/data/ABCD7.py
@@ -10,10 +10,10 @@
try:
instance = model.create_instance('ABCD7.dat')
except pyomo.common.errors.ApplicationError as e:
- print("ERROR "+str(e))
+ print("ERROR " + str(e))
sys.exit(1)
-print('Z '+str(sorted(list(instance.Z.data()))))
+print('Z ' + str(sorted(list(instance.Z.data()))))
print('Y')
for key in sorted(instance.Y.keys()):
- print(name(instance.Y,key)+" "+str(value(instance.Y[key])))
+ print(name(instance.Y, key) + " " + str(value(instance.Y[key])))
diff --git a/doc/OnlineDocs/tests/data/ABCD8.py b/doc/OnlineDocs/tests/data/ABCD8.py
index d9d00f0275a..9bcd950c681 100644
--- a/doc/OnlineDocs/tests/data/ABCD8.py
+++ b/doc/OnlineDocs/tests/data/ABCD8.py
@@ -10,10 +10,10 @@
try:
instance = model.create_instance('ABCD8.dat')
except pyomo.common.errors.ApplicationError as e:
- print("ERROR "+str(e))
+ print("ERROR " + str(e))
sys.exit(1)
-print('Z '+str(sorted(list(instance.Z.data()))))
+print('Z ' + str(sorted(list(instance.Z.data()))))
print('Y')
for key in sorted(instance.Y.keys()):
- print(name(instance.Y,key)+" "+str(value(instance.Y[key])))
+ print(name(instance.Y, key) + " " + str(value(instance.Y[key])))
diff --git a/doc/OnlineDocs/tests/data/ABCD9.py b/doc/OnlineDocs/tests/data/ABCD9.py
index 1322432ee59..29fcb6426db 100644
--- a/doc/OnlineDocs/tests/data/ABCD9.py
+++ b/doc/OnlineDocs/tests/data/ABCD9.py
@@ -10,10 +10,10 @@
try:
instance = model.create_instance('ABCD9.dat')
except pyomo.common.errors.ApplicationError as e:
- print("ERROR "+str(e))
+ print("ERROR " + str(e))
sys.exit(1)
-print('Z '+str(sorted(list(instance.Z.data()))))
+print('Z ' + str(sorted(list(instance.Z.data()))))
print('Y')
for key in sorted(instance.Y.keys()):
- print(instance.Y[key]+" "+str(value(instance.Y[key])))
+ print(instance.Y[key] + " " + str(value(instance.Y[key])))
diff --git a/doc/OnlineDocs/tests/data/diet1.py b/doc/OnlineDocs/tests/data/diet1.py
index b7eea483daa..ef0d8096350 100644
--- a/doc/OnlineDocs/tests/data/diet1.py
+++ b/doc/OnlineDocs/tests/data/diet1.py
@@ -2,7 +2,7 @@
from pyomo.environ import *
infinity = float('inf')
-MAX_FOOD_SUPPLY = 20.0 # There is a finite food supply
+MAX_FOOD_SUPPLY = 20.0 # There is a finite food supply
model = AbstractModel()
@@ -11,8 +11,12 @@
model.FOOD = Set()
model.cost = Param(model.FOOD, within=PositiveReals)
model.f_min = Param(model.FOOD, within=NonNegativeReals, default=0.0)
-def f_max_validate (model, value, j):
+
+
+def f_max_validate(model, value, j):
return model.f_max[j] > model.f_min[j]
+
+
model.f_max = Param(model.FOOD, validate=f_max_validate, default=MAX_FOOD_SUPPLY)
model.NUTR = Set()
@@ -22,29 +26,50 @@ def f_max_validate (model, value, j):
# --------------------------------------------------------
+
def Buy_bounds(model, i):
return (model.f_min[i], model.f_max[i])
+
+
model.Buy = Var(model.FOOD, bounds=Buy_bounds, within=NonNegativeIntegers)
# --------------------------------------------------------
+
def Total_Cost_rule(model):
return sum(model.cost[j] * model.Buy[j] for j in model.FOOD)
+
+
model.Total_Cost = Objective(rule=Total_Cost_rule, sense=minimize)
# --------------------------------------------------------
+
def Entree_rule(model):
- entrees = ['Cheeseburger', 'Ham Sandwich', 'Hamburger', 'Fish Sandwich', 'Chicken Sandwich']
+ entrees = [
+ 'Cheeseburger',
+ 'Ham Sandwich',
+ 'Hamburger',
+ 'Fish Sandwich',
+ 'Chicken Sandwich',
+ ]
return sum(model.Buy[e] for e in entrees) >= 1
+
+
model.Entree = Constraint(rule=Entree_rule)
+
def Side_rule(model):
sides = ['Fries', 'Sausage Biscuit']
return sum(model.Buy[s] for s in sides) >= 1
+
+
model.Side = Constraint(rule=Side_rule)
+
def Drink_rule(model):
drinks = ['Lowfat Milk', 'Orange Juice']
return sum(model.Buy[d] for d in drinks) >= 1
+
+
model.Drink = Constraint(rule=Drink_rule)
diff --git a/doc/OnlineDocs/tests/data/import1.tab.py b/doc/OnlineDocs/tests/data/import1.tab.py
index aef3dae30fc..c9164ab73ec 100644
--- a/doc/OnlineDocs/tests/data/import1.tab.py
+++ b/doc/OnlineDocs/tests/data/import1.tab.py
@@ -10,4 +10,4 @@
print('Y')
keys = instance.Y.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.Y[key])))
+ print(str(key) + " " + str(value(instance.Y[key])))
diff --git a/doc/OnlineDocs/tests/data/import2.tab.py b/doc/OnlineDocs/tests/data/import2.tab.py
index 9ede9049f75..d03f053d090 100644
--- a/doc/OnlineDocs/tests/data/import2.tab.py
+++ b/doc/OnlineDocs/tests/data/import2.tab.py
@@ -7,8 +7,8 @@
instance = model.create_instance('import2.tab.dat')
-print('A '+str(sorted(list(instance.A.data()))))
+print('A ' + str(sorted(list(instance.A.data()))))
print('Y')
keys = instance.Y.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.Y[key])))
+ print(str(key) + " " + str(value(instance.Y[key])))
diff --git a/doc/OnlineDocs/tests/data/import3.tab.py b/doc/OnlineDocs/tests/data/import3.tab.py
index f5be9b19393..e86557677ee 100644
--- a/doc/OnlineDocs/tests/data/import3.tab.py
+++ b/doc/OnlineDocs/tests/data/import3.tab.py
@@ -6,4 +6,4 @@
instance = model.create_instance('import3.tab.dat')
-print('A '+str(sorted(list(instance.A.data()))))
+print('A ' + str(sorted(list(instance.A.data()))))
diff --git a/doc/OnlineDocs/tests/data/import4.tab.py b/doc/OnlineDocs/tests/data/import4.tab.py
index fd84afdccc9..93df9c761ab 100644
--- a/doc/OnlineDocs/tests/data/import4.tab.py
+++ b/doc/OnlineDocs/tests/data/import4.tab.py
@@ -6,4 +6,4 @@
instance = model.create_instance('import4.tab.dat')
-print('C '+str(sorted(list(instance.C.data()))))
+print('C ' + str(sorted(list(instance.C.data()))))
diff --git a/doc/OnlineDocs/tests/data/import5.tab.py b/doc/OnlineDocs/tests/data/import5.tab.py
index 413c741d8f4..1d20476a16f 100644
--- a/doc/OnlineDocs/tests/data/import5.tab.py
+++ b/doc/OnlineDocs/tests/data/import5.tab.py
@@ -6,4 +6,4 @@
instance = model.create_instance('import5.tab.dat')
-print('B '+str(list(sorted(instance.B.data()))))
+print('B ' + str(list(sorted(instance.B.data()))))
diff --git a/doc/OnlineDocs/tests/data/import6.tab.py b/doc/OnlineDocs/tests/data/import6.tab.py
index 8823047aa49..8a1ab232f86 100644
--- a/doc/OnlineDocs/tests/data/import6.tab.py
+++ b/doc/OnlineDocs/tests/data/import6.tab.py
@@ -6,4 +6,4 @@
instance = model.create_instance('import6.tab.dat')
-print('p '+str(value(instance.p)))
+print('p ' + str(value(instance.p)))
diff --git a/doc/OnlineDocs/tests/data/import7.tab.py b/doc/OnlineDocs/tests/data/import7.tab.py
index 7887266f9f4..747d884be31 100644
--- a/doc/OnlineDocs/tests/data/import7.tab.py
+++ b/doc/OnlineDocs/tests/data/import7.tab.py
@@ -4,14 +4,14 @@
model.I = Set(initialize=['I1', 'I2', 'I3', 'I4'])
model.A = Set(initialize=['A1', 'A2', 'A3'])
-model.U = Param(model.I,model.A)
+model.U = Param(model.I, model.A)
# BUG: This should cause an error
-#model.U = Param(model.A,model.I)
+# model.U = Param(model.A,model.I)
instance = model.create_instance('import7.tab.dat')
-print('I '+str(sorted(list(instance.I.data()))))
-print('A '+str(sorted(list(instance.A.data()))))
+print('I ' + str(sorted(list(instance.I.data()))))
+print('A ' + str(sorted(list(instance.A.data()))))
print('U')
for key in sorted(instance.U.keys()):
- print(name(instance.U,key)+" "+str(value(instance.U[key])))
+ print(name(instance.U, key) + " " + str(value(instance.U[key])))
diff --git a/doc/OnlineDocs/tests/data/import8.tab.py b/doc/OnlineDocs/tests/data/import8.tab.py
index 4790a2f1053..b7866d7a3e5 100644
--- a/doc/OnlineDocs/tests/data/import8.tab.py
+++ b/doc/OnlineDocs/tests/data/import8.tab.py
@@ -4,12 +4,12 @@
model.I = Set(initialize=['I1', 'I2', 'I3', 'I4'])
model.A = Set(initialize=['A1', 'A2', 'A3'])
-model.U = Param(model.A,model.I)
+model.U = Param(model.A, model.I)
instance = model.create_instance('import8.tab.dat')
-print('A '+str(sorted(list(instance.A.data()))))
-print('I '+str(sorted(list(instance.I.data()))))
+print('A ' + str(sorted(list(instance.A.data()))))
+print('I ' + str(sorted(list(instance.I.data()))))
print('U')
for key in sorted(instance.U.keys()):
- print(name(instance.U,key)+" "+str(value(instance.U[key])))
+ print(name(instance.U, key) + " " + str(value(instance.U[key])))
diff --git a/doc/OnlineDocs/tests/data/param2.py b/doc/OnlineDocs/tests/data/param2.py
index ce5fa62b89b..f46f05ceebc 100644
--- a/doc/OnlineDocs/tests/data/param2.py
+++ b/doc/OnlineDocs/tests/data/param2.py
@@ -11,4 +11,4 @@
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
diff --git a/doc/OnlineDocs/tests/data/param2a.py b/doc/OnlineDocs/tests/data/param2a.py
index 62261118a2e..4557f63d841 100644
--- a/doc/OnlineDocs/tests/data/param2a.py
+++ b/doc/OnlineDocs/tests/data/param2a.py
@@ -11,4 +11,4 @@
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
diff --git a/doc/OnlineDocs/tests/data/param3.py b/doc/OnlineDocs/tests/data/param3.py
index f3339743888..149155ce67d 100644
--- a/doc/OnlineDocs/tests/data/param3.py
+++ b/doc/OnlineDocs/tests/data/param3.py
@@ -14,12 +14,12 @@
print('B')
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
print('C')
keys = instance.C.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.C[key])))
+ print(str(key) + " " + str(value(instance.C[key])))
print('D')
keys = instance.D.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.D[key])))
+ print(str(key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/param3a.py b/doc/OnlineDocs/tests/data/param3a.py
index c3e03d7884a..0e99cad0c7a 100644
--- a/doc/OnlineDocs/tests/data/param3a.py
+++ b/doc/OnlineDocs/tests/data/param3a.py
@@ -14,12 +14,12 @@
print('B')
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
print('C')
keys = instance.C.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.C[key])))
+ print(str(key) + " " + str(value(instance.C[key])))
print('D')
keys = instance.D.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.D[key])))
+ print(str(key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/param3b.py b/doc/OnlineDocs/tests/data/param3b.py
index 0482e834b12..deda175ea12 100644
--- a/doc/OnlineDocs/tests/data/param3b.py
+++ b/doc/OnlineDocs/tests/data/param3b.py
@@ -14,12 +14,12 @@
print('B')
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
print('C')
keys = instance.C.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.C[key])))
+ print(str(key) + " " + str(value(instance.C[key])))
print('D')
keys = instance.D.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.D[key])))
+ print(str(key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/param3c.py b/doc/OnlineDocs/tests/data/param3c.py
index 30ef74eb713..4056dc8107d 100644
--- a/doc/OnlineDocs/tests/data/param3c.py
+++ b/doc/OnlineDocs/tests/data/param3c.py
@@ -14,12 +14,12 @@
print('B')
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
print('C')
keys = instance.C.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.C[key])))
+ print(str(key) + " " + str(value(instance.C[key])))
print('D')
keys = instance.D.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.D[key])))
+ print(str(key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/param4.py b/doc/OnlineDocs/tests/data/param4.py
index d02f4b5af7b..1190dae8dec 100644
--- a/doc/OnlineDocs/tests/data/param4.py
+++ b/doc/OnlineDocs/tests/data/param4.py
@@ -12,4 +12,4 @@
print('B')
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
diff --git a/doc/OnlineDocs/tests/data/param5.py b/doc/OnlineDocs/tests/data/param5.py
index 2243f8aff57..69f6cc46552 100644
--- a/doc/OnlineDocs/tests/data/param5.py
+++ b/doc/OnlineDocs/tests/data/param5.py
@@ -11,4 +11,4 @@
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
diff --git a/doc/OnlineDocs/tests/data/param5a.py b/doc/OnlineDocs/tests/data/param5a.py
index 3695d42500c..303b92f9f2e 100644
--- a/doc/OnlineDocs/tests/data/param5a.py
+++ b/doc/OnlineDocs/tests/data/param5a.py
@@ -11,4 +11,4 @@
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
diff --git a/doc/OnlineDocs/tests/data/param6.py b/doc/OnlineDocs/tests/data/param6.py
index 4b3dd053341..c3e4b25d144 100644
--- a/doc/OnlineDocs/tests/data/param6.py
+++ b/doc/OnlineDocs/tests/data/param6.py
@@ -14,12 +14,12 @@
keys = instance.B.keys()
print('B')
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
print('C')
keys = instance.C.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.C[key])))
+ print(str(key) + " " + str(value(instance.C[key])))
print('D')
keys = instance.D.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.D[key])))
+ print(str(key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/param6a.py b/doc/OnlineDocs/tests/data/param6a.py
index be68eaa7947..07e8280cc18 100644
--- a/doc/OnlineDocs/tests/data/param6a.py
+++ b/doc/OnlineDocs/tests/data/param6a.py
@@ -14,12 +14,12 @@
keys = instance.B.keys()
print('B')
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
print('C')
keys = instance.C.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.C[key])))
+ print(str(key) + " " + str(value(instance.C[key])))
print('D')
keys = instance.D.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.D[key])))
+ print(str(key) + " " + str(value(instance.D[key])))
diff --git a/doc/OnlineDocs/tests/data/param7a.py b/doc/OnlineDocs/tests/data/param7a.py
index b5edaf1d58e..3bb68b3f3b7 100644
--- a/doc/OnlineDocs/tests/data/param7a.py
+++ b/doc/OnlineDocs/tests/data/param7a.py
@@ -11,4 +11,4 @@
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
diff --git a/doc/OnlineDocs/tests/data/param7b.py b/doc/OnlineDocs/tests/data/param7b.py
index 60956f69737..6e5c857851f 100644
--- a/doc/OnlineDocs/tests/data/param7b.py
+++ b/doc/OnlineDocs/tests/data/param7b.py
@@ -11,4 +11,4 @@
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
diff --git a/doc/OnlineDocs/tests/data/param8a.py b/doc/OnlineDocs/tests/data/param8a.py
index c9c63f5807b..57c9b08ca43 100644
--- a/doc/OnlineDocs/tests/data/param8a.py
+++ b/doc/OnlineDocs/tests/data/param8a.py
@@ -11,4 +11,4 @@
keys = instance.B.keys()
for key in sorted(keys):
- print(str(key)+" "+str(value(instance.B[key])))
+ print(str(key) + " " + str(value(instance.B[key])))
diff --git a/doc/OnlineDocs/tests/data/set1.py b/doc/OnlineDocs/tests/data/set1.py
index 6d40f7b6746..5248e9d5dc9 100644
--- a/doc/OnlineDocs/tests/data/set1.py
+++ b/doc/OnlineDocs/tests/data/set1.py
@@ -10,4 +10,4 @@
print(sorted(list(instance.A.data())))
print(sorted((instance.B.data())))
-print(sorted(list((instance.C.data())), key=lambda x:x if type(x) is str else str(x)))
+print(sorted(list((instance.C.data())), key=lambda x: x if type(x) is str else str(x)))
diff --git a/doc/OnlineDocs/tests/data/set3.py b/doc/OnlineDocs/tests/data/set3.py
index a94e881c205..d58e0c0dd43 100644
--- a/doc/OnlineDocs/tests/data/set3.py
+++ b/doc/OnlineDocs/tests/data/set3.py
@@ -6,10 +6,14 @@
model.A = Set()
model.B = Set(model.A)
# @decl
-#model.C = Set(model.A,model.A)
+# model.C = Set(model.A,model.A)
instance = model.create_instance('set3.dat')
-print(sorted(list(instance.A.data()), key=lambda x:x if type(x) is str else str(x)))
-print(sorted(list(instance.B[1].data()), key=lambda x:x if type(x) is str else str(x)))
-print(sorted(list(instance.B['aaa'].data()), key=lambda x:x if type(x) is str else str(x)))
+print(sorted(list(instance.A.data()), key=lambda x: x if type(x) is str else str(x)))
+print(sorted(list(instance.B[1].data()), key=lambda x: x if type(x) is str else str(x)))
+print(
+ sorted(
+ list(instance.B['aaa'].data()), key=lambda x: x if type(x) is str else str(x)
+ )
+)
diff --git a/doc/OnlineDocs/tests/data/set5.py b/doc/OnlineDocs/tests/data/set5.py
index bfbe8594a94..35acd4e4317 100644
--- a/doc/OnlineDocs/tests/data/set5.py
+++ b/doc/OnlineDocs/tests/data/set5.py
@@ -9,5 +9,5 @@
instance = model.create_instance('set5.dat')
-for tpl in sorted(list(instance.A.data()), key=lambda x:tuple(map(str,x))):
+for tpl in sorted(list(instance.A.data()), key=lambda x: tuple(map(str, x))):
print(tpl)
diff --git a/doc/OnlineDocs/tests/data/table0.py b/doc/OnlineDocs/tests/data/table0.py
index 8d4af8f066b..af7f634bd34 100644
--- a/doc/OnlineDocs/tests/data/table0.py
+++ b/doc/OnlineDocs/tests/data/table0.py
@@ -2,7 +2,7 @@
model = AbstractModel()
-model.A = Set(initialize=['A1','A2','A3'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
model.M = Param(model.A)
instance = model.create_instance('table0.dat')
diff --git a/doc/OnlineDocs/tests/data/table0.ul.py b/doc/OnlineDocs/tests/data/table0.ul.py
index 5b2c88e17cf..213407b071c 100644
--- a/doc/OnlineDocs/tests/data/table0.ul.py
+++ b/doc/OnlineDocs/tests/data/table0.ul.py
@@ -2,7 +2,7 @@
model = AbstractModel()
-model.A = Set(initialize=['A1','A2','A3'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
model.M = Param(model.A)
instance = model.create_instance('table0.ul.dat')
diff --git a/doc/OnlineDocs/tests/data/table1.py b/doc/OnlineDocs/tests/data/table1.py
index 1ca81708a8b..1f86508c60a 100644
--- a/doc/OnlineDocs/tests/data/table1.py
+++ b/doc/OnlineDocs/tests/data/table1.py
@@ -2,7 +2,7 @@
model = AbstractModel()
-model.A = Set(initialize=['A1','A2','A3'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
model.M = Param(model.A)
instance = model.create_instance('table1.dat')
diff --git a/doc/OnlineDocs/tests/data/table2.py b/doc/OnlineDocs/tests/data/table2.py
index 32170e0d6aa..d7708b9277f 100644
--- a/doc/OnlineDocs/tests/data/table2.py
+++ b/doc/OnlineDocs/tests/data/table2.py
@@ -2,8 +2,8 @@
model = AbstractModel()
-model.A = Set(initialize=['A1','A2','A3'])
-model.B = Set(initialize=['B1','B2','B3'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
+model.B = Set(initialize=['B1', 'B2', 'B3'])
model.M = Param(model.A)
model.N = Param(model.A, model.B)
diff --git a/doc/OnlineDocs/tests/data/table3.py b/doc/OnlineDocs/tests/data/table3.py
index bb5c6620657..fa871a4f79c 100644
--- a/doc/OnlineDocs/tests/data/table3.py
+++ b/doc/OnlineDocs/tests/data/table3.py
@@ -3,7 +3,7 @@
model = AbstractModel()
model.A = Set()
-model.B = Set(initialize=['B1','B2','B3'])
+model.B = Set(initialize=['B1', 'B2', 'B3'])
model.Z = Set(dimen=2)
model.M = Param(model.A)
diff --git a/doc/OnlineDocs/tests/data/table3.ul.py b/doc/OnlineDocs/tests/data/table3.ul.py
index 5af0de791be..713d36b9f3a 100644
--- a/doc/OnlineDocs/tests/data/table3.ul.py
+++ b/doc/OnlineDocs/tests/data/table3.ul.py
@@ -3,7 +3,7 @@
model = AbstractModel()
model.A = Set()
-model.B = Set(initialize=['B1','B2','B3'])
+model.B = Set(initialize=['B1', 'B2', 'B3'])
model.Z = Set(dimen=2)
model.M = Param(model.A)
diff --git a/doc/OnlineDocs/tests/data/table7.py b/doc/OnlineDocs/tests/data/table7.py
index 3ba276c34a9..f8f8e769b2e 100644
--- a/doc/OnlineDocs/tests/data/table7.py
+++ b/doc/OnlineDocs/tests/data/table7.py
@@ -2,7 +2,7 @@
model = AbstractModel()
-model.A = Set(initialize=['A1','A2','A3'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
model.M = Param(model.A)
model.Z = Set(dimen=2)
diff --git a/doc/OnlineDocs/tests/dataportal/PP_sqlite.py b/doc/OnlineDocs/tests/dataportal/PP_sqlite.py
index 8d178681b84..9c6fc5ddc0b 100644
--- a/doc/OnlineDocs/tests/dataportal/PP_sqlite.py
+++ b/doc/OnlineDocs/tests/dataportal/PP_sqlite.py
@@ -23,20 +23,18 @@
c.execute('DROP TABLE IF EXISTS ' + table)
conn.commit()
-c.execute('''
+c.execute(
+ '''
CREATE TABLE PPtable (
A text not null,
B text not null,
PP float not null
)
-''')
+'''
+)
conn.commit()
-data = [
- ("A1", "B1", 4.3),
- ("A2", "B2", 4.4),
- ("A3", "B3", 4.5)
-]
+data = [("A1", "B1", 4.3), ("A2", "B2", 4.4), ("A3", "B3", 4.5)]
for row in data:
c.execute('''INSERT INTO PPtable VALUES (?,?,?)''', row)
conn.commit()
diff --git a/doc/OnlineDocs/tests/dataportal/dataportal_tab.py b/doc/OnlineDocs/tests/dataportal/dataportal_tab.py
index 49610e7396b..d1a75196c99 100644
--- a/doc/OnlineDocs/tests/dataportal/dataportal_tab.py
+++ b/doc/OnlineDocs/tests/dataportal/dataportal_tab.py
@@ -50,7 +50,7 @@
# @param2
model = AbstractModel()
data = DataPortal()
-model.A = Set(initialize=['A1','A2','A3'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
model.y = Param(model.A)
data.load(filename='Y.tab', param=model.y)
instance = model.create_instance(data)
@@ -60,10 +60,10 @@
# @param4
model = AbstractModel()
data = DataPortal()
-model.A = Set(initialize=['A1','A2','A3'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
model.x = Param(model.A)
model.w = Param(model.A)
-data.load(filename='XW.tab', param=(model.x,model.w))
+data.load(filename='XW.tab', param=(model.x, model.w))
instance = model.create_instance(data)
# @param4
instance.pprint()
@@ -83,8 +83,7 @@
data = DataPortal()
model.A = Set()
model.w = Param(model.A)
-data.load(filename='XW.tab', select=('A','W'),
- param=model.w, index=model.A)
+data.load(filename='XW.tab', select=('A', 'W'), param=model.w, index=model.A)
instance = model.create_instance(data)
# @param5
instance.pprint()
@@ -92,11 +91,10 @@
# @param6
model = AbstractModel()
data = DataPortal()
-model.A = Set(initialize=['A1','A2','A3'])
-model.I = Set(initialize=['I1','I2','I3','I4'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
+model.I = Set(initialize=['I1', 'I2', 'I3', 'I4'])
model.u = Param(model.I, model.A)
-data.load(filename='U.tab', param=model.u,
- format='array')
+data.load(filename='U.tab', param=model.u, format='array')
instance = model.create_instance(data)
# @param6
instance.pprint()
@@ -104,11 +102,10 @@
# @param7
model = AbstractModel()
data = DataPortal()
-model.A = Set(initialize=['A1','A2','A3'])
-model.I = Set(initialize=['I1','I2','I3','I4'])
+model.A = Set(initialize=['A1', 'A2', 'A3'])
+model.I = Set(initialize=['I1', 'I2', 'I3', 'I4'])
model.t = Param(model.A, model.I)
-data.load(filename='U.tab', param=model.t,
- format='transposed_array')
+data.load(filename='U.tab', param=model.t, format='transposed_array')
instance = model.create_instance(data)
# @param7
instance.pprint()
@@ -126,7 +123,7 @@
# @param9
model = AbstractModel()
data = DataPortal()
-model.A = Set(initialize=['A1','A2','A3','A4'])
+model.A = Set(initialize=['A1', 'A2', 'A3', 'A4'])
model.y = Param(model.A)
data.load(filename='Y.tab', param=model.y)
instance = model.create_instance(data)
@@ -149,8 +146,8 @@
model.A = Set()
model.B = Set()
model.q = Param(model.A, model.B)
-data.load(filename='PP.tab', param=model.q, index=(model.A,model.B))
-#instance = model.create_instance(data)
+data.load(filename='PP.tab', param=model.q, index=(model.A, model.B))
+# instance = model.create_instance(data)
# @param11
# --------------------------------------------------
# @concrete1
@@ -169,17 +166,17 @@
model = ConcreteModel()
model.z = Param(initialize=data['z'])
-model.y = Param(['A1','A2','A3'], initialize=data['y'])
+model.y = Param(['A1', 'A2', 'A3'], initialize=data['y'])
# @concrete2
model.pprint()
# --------------------------------------------------
# @getitem
data = DataPortal()
data.load(filename='A.tab', set="A", format="set")
-print(data['A']) #['A1', 'A2', 'A3']
+print(data['A']) # ['A1', 'A2', 'A3']
data.load(filename='Z.tab', param="z", format="param")
-print(data['z']) #1.1
+print(data['z']) # 1.1
data.load(filename='Y.tab', param="y", format="table")
for key in sorted(data['y']):
@@ -191,8 +188,7 @@
data = DataPortal()
model.A = Set(dimen=2)
model.p = Param(model.A)
-data.load(filename='excel.xls', range='PPtable',
- param=model.p, index=model.A)
+data.load(filename='excel.xls', range='PPtable', param=model.p, index=model.A)
instance = model.create_instance(data)
# @excel1
instance.pprint()
@@ -202,7 +198,7 @@
data = DataPortal()
model.A = Set(dimen=2)
model.p = Param(model.A)
-#data.load(filename='excel.xls', range='AX2:AZ5',
+# data.load(filename='excel.xls', range='AX2:AZ5',
# param=model.p, index=model.A)
instance = model.create_instance(data)
# @excel2
@@ -213,15 +209,20 @@
data = DataPortal()
model.A = Set(dimen=2)
model.p = Param(model.A)
-data.load(filename='PP.sqlite', using='sqlite3',
- table='PPtable',
- param=model.p, index=model.A)
+data.load(
+ filename='PP.sqlite', using='sqlite3', table='PPtable', param=model.p, index=model.A
+)
instance = model.create_instance(data)
# @db1
data = DataPortal()
-data.load(filename='PP.sqlite', using='sqlite3',
- table='PPtable',
- param=model.p, index=model.A, text_factory=str)
+data.load(
+ filename='PP.sqlite',
+ using='sqlite3',
+ table='PPtable',
+ param=model.p,
+ index=model.A,
+ text_factory=str,
+)
instance = model.create_instance(data)
instance.pprint()
# --------------------------------------------------
@@ -230,15 +231,24 @@
data = DataPortal()
model.A = Set()
model.p = Param(model.A)
-data.load(filename='PP.sqlite', using='sqlite3',
- query="SELECT A,PP FROM PPtable",
- param=model.p, index=model.A)
+data.load(
+ filename='PP.sqlite',
+ using='sqlite3',
+ query="SELECT A,PP FROM PPtable",
+ param=model.p,
+ index=model.A,
+)
instance = model.create_instance(data)
# @db2
data = DataPortal()
-data.load(filename='PP.sqlite', using='sqlite3',
- query="SELECT A,PP FROM PPtable",
- param=model.p, index=model.A, text_factory=str)
+data.load(
+ filename='PP.sqlite',
+ using='sqlite3',
+ query="SELECT A,PP FROM PPtable",
+ param=model.p,
+ index=model.A,
+ text_factory=str,
+)
instance = model.create_instance(data)
instance.pprint()
# --------------------------------------------------
@@ -248,17 +258,24 @@
data = DataPortal()
model.A = Set()
model.p = Param(model.A)
- data.load(filename="Driver={MySQL ODBC 5.2 UNICODE Driver}; Database=Pyomo; Server=localhost; User=pyomo;",
- using='pypyodbc',
- query="SELECT A,PP FROM PPtable",
- param=model.p, index=model.A)
+ data.load(
+ filename="Driver={MySQL ODBC 5.2 UNICODE Driver}; Database=Pyomo; Server=localhost; User=pyomo;",
+ using='pypyodbc',
+ query="SELECT A,PP FROM PPtable",
+ param=model.p,
+ index=model.A,
+ )
instance = model.create_instance(data)
# @db3
data = DataPortal()
- data.load(filename="Driver={MySQL ODBC 5.2 UNICODE Driver}; Database=Pyomo; Server=localhost; User=pyomo;",
- using='pypyodbc',
- query="SELECT A,PP FROM PPtable",
- param=model.p, index=model.A, text_factory=str)
+ data.load(
+ filename="Driver={MySQL ODBC 5.2 UNICODE Driver}; Database=Pyomo; Server=localhost; User=pyomo;",
+ using='pypyodbc',
+ query="SELECT A,PP FROM PPtable",
+ param=model.p,
+ index=model.A,
+ text_factory=str,
+ )
instance = model.create_instance(data)
instance.pprint()
# --------------------------------------------------
@@ -298,11 +315,9 @@
model.C = Set(dimen=2)
data = DataPortal()
data.load(filename='C.tab', set=model.C, namespace='ns1')
-data.load(filename='D.tab', set=model.C, namespace='ns2',
- format='set_array')
+data.load(filename='D.tab', set=model.C, namespace='ns2', format='set_array')
instance1 = model.create_instance(data, namespaces=['ns1'])
instance2 = model.create_instance(data, namespaces=['ns2'])
# @namespaces1
instance1.pprint()
instance2.pprint()
-
diff --git a/doc/OnlineDocs/tests/dataportal/param_initialization.py b/doc/OnlineDocs/tests/dataportal/param_initialization.py
index e46b641c43b..5567b01f284 100644
--- a/doc/OnlineDocs/tests/dataportal/param_initialization.py
+++ b/doc/OnlineDocs/tests/dataportal/param_initialization.py
@@ -9,14 +9,17 @@
# Initialize with a dictionary
# @decl2
-model.b = Param([1,2,3], initialize={1:1, 2:2, 3:3})
+model.b = Param([1, 2, 3], initialize={1: 1, 2: 2, 3: 3})
# @decl2
+
# Initialize with a function that returns native Python data
# @decl3
def c(model):
- return {1:1, 2:2, 3:3}
-model.c = Param([1,2,3], initialize=c)
+ return {1: 1, 2: 2, 3: 3}
+
+
+model.c = Param([1, 2, 3], initialize=c)
# @decl3
model.pprint(verbose=True)
diff --git a/doc/OnlineDocs/tests/dataportal/set_initialization.py b/doc/OnlineDocs/tests/dataportal/set_initialization.py
index 14b034a0119..aa7b426fa82 100644
--- a/doc/OnlineDocs/tests/dataportal/set_initialization.py
+++ b/doc/OnlineDocs/tests/dataportal/set_initialization.py
@@ -5,15 +5,15 @@
# Initialize with a list, tuple or set
# @decl2
-model.A = Set(initialize=[2,3,5])
-model.B = Set(initialize=set([2,3,5]))
-model.C = Set(initialize=(2,3,5))
+model.A = Set(initialize=[2, 3, 5])
+model.B = Set(initialize=set([2, 3, 5]))
+model.C = Set(initialize=(2, 3, 5))
# @decl2
# Initialize with a generator
# @decl3
model.D = Set(initialize=range(9))
-model.E = Set(initialize=(i for i in model.B if i%2 == 0))
+model.E = Set(initialize=(i for i in model.B if i % 2 == 0))
# @decl3
# Initialize with a numpy
@@ -22,20 +22,23 @@
model.F = Set(initialize=f)
# @decl4
+
# Initialize with a function that returns native Python data
# @decl5
def g(model):
- return [2,3,5]
+ return [2, 3, 5]
+
+
model.G = Set(initialize=g)
# @decl5
# Initialize an indexed set with a dictionary
# @decl6
H_init = {}
-H_init[2] = [1,3,5]
-H_init[3] = [2,4,6]
-H_init[4] = [3,5,7]
-model.H = Set([2,3,4],initialize=H_init)
+H_init[2] = [1, 3, 5]
+H_init[3] = [2, 4, 6]
+H_init[4] = [3, 5, 7]
+model.H = Set([2, 3, 4], initialize=H_init)
# @decl6
model.pprint(verbose=True)
diff --git a/doc/OnlineDocs/tests/expr/design.py b/doc/OnlineDocs/tests/expr/design.py
index 7cc8d9698c3..b122a5f2bf3 100644
--- a/doc/OnlineDocs/tests/expr/design.py
+++ b/doc/OnlineDocs/tests/expr/design.py
@@ -1,6 +1,6 @@
from pyomo.environ import *
-#---------------------------------------------
+# ---------------------------------------------
# @categories
m = ConcreteModel()
m.p = Param(default=10, mutable=False)
@@ -11,18 +11,18 @@
# @categories
m.pprint()
-#---------------------------------------------
+# ---------------------------------------------
# @named_expression
M = ConcreteModel()
M.v = Var()
M.w = Var()
-M.e = Expression(expr=2*M.v)
-f = M.e + 3 # f == 2*v + 3
-M.e += M.w # f == 2*v + 3 + w
+M.e = Expression(expr=2 * M.v)
+f = M.e + 3 # f == 2*v + 3
+M.e += M.w # f == 2*v + 3 + w
# @named_expression
-#---------------------------------------------
+# ---------------------------------------------
# @cm1
M = ConcreteModel()
M.x = Var(range(5))
@@ -39,7 +39,7 @@
print(e)
-#---------------------------------------------
+# ---------------------------------------------
# @cm2
M = ConcreteModel()
M.x = Var(range(5))
@@ -51,4 +51,3 @@
# @cm2
print("cm2")
print(e)
-
diff --git a/doc/OnlineDocs/tests/expr/index.py b/doc/OnlineDocs/tests/expr/index.py
index 1cfc954aa52..9c9c79bf7be 100644
--- a/doc/OnlineDocs/tests/expr/index.py
+++ b/doc/OnlineDocs/tests/expr/index.py
@@ -1,11 +1,10 @@
from pyomo.environ import *
-#---------------------------------------------
+# ---------------------------------------------
# @simple
M = ConcreteModel()
M.v = Var()
-e = M.v*2
+e = M.v * 2
# @simple
print(e)
-
diff --git a/doc/OnlineDocs/tests/expr/managing.py b/doc/OnlineDocs/tests/expr/managing.py
index 99faba46721..0a2709fe96f 100644
--- a/doc/OnlineDocs/tests/expr/managing.py
+++ b/doc/OnlineDocs/tests/expr/managing.py
@@ -3,14 +3,14 @@
import math
import copy
-#---------------------------------------------
+# ---------------------------------------------
# @ex1
from pyomo.core.expr import current as EXPR
M = ConcreteModel()
M.x = Var()
-e = sin(M.x) + 2*M.x
+e = sin(M.x) + 2 * M.x
# sin(x) + 2*x
print(EXPR.expression_to_string(e))
@@ -19,7 +19,7 @@
print(EXPR.expression_to_string(e, verbose=True))
# @ex1
-#---------------------------------------------
+# ---------------------------------------------
# @ex2
from pyomo.core.expr import current as EXPR
@@ -27,13 +27,13 @@
M.x = Var()
M.y = Var()
-e = sin(M.x) + 2*M.y
+e = sin(M.x) + 2 * M.y
# sin(x1) + 2*x2
print(EXPR.expression_to_string(e, labeler=NumericLabeler('x')))
# @ex2
-#---------------------------------------------
+# ---------------------------------------------
# @ex3
from pyomo.core.expr import current as EXPR
@@ -41,13 +41,13 @@
M.x = Var()
M.y = Var()
-e = sin(M.x) + 2*M.y + M.x*M.y - 3
+e = sin(M.x) + 2 * M.y + M.x * M.y - 3
# -3 + 2*y + sin(x) + x*y
print(EXPR.expression_to_string(e, standardize=True))
# @ex3
-#---------------------------------------------
+# ---------------------------------------------
# @ex4
from pyomo.core.expr import current as EXPR
@@ -59,31 +59,31 @@
e1 = sin(M.x)
e2 = e1.clone()
total = counter.count - start
- assert(total == 1)
+ assert total == 1
# @ex4
-#---------------------------------------------
+# ---------------------------------------------
# @ex5
M = ConcreteModel()
M.x = Var()
-M.x.value = math.pi/2.0
+M.x.value = math.pi / 2.0
val = value(M.x)
-assert(isclose(val, math.pi/2.0))
+assert isclose(val, math.pi / 2.0)
# @ex5
# @ex6
val = M.x()
-assert(isclose(val, math.pi/2.0))
+assert isclose(val, math.pi / 2.0)
# @ex6
-#---------------------------------------------
+# ---------------------------------------------
# @ex7
M = ConcreteModel()
M.x = Var()
val = value(M.x, exception=False)
-assert(val is None)
+assert val is None
# @ex7
-#---------------------------------------------
+# ---------------------------------------------
# @ex8
from pyomo.core.expr import current as EXPR
@@ -91,12 +91,12 @@
M.x = Var()
M.p = Param(mutable=True)
-e = M.p+M.x
+e = M.p + M.x
s = set([type(M.p)])
-assert(list(EXPR.identify_components(e, s)) == [M.p])
+assert list(EXPR.identify_components(e, s)) == [M.p]
# @ex8
-#---------------------------------------------
+# ---------------------------------------------
# @ex9
from pyomo.core.expr import current as EXPR
@@ -104,20 +104,22 @@
M.x = Var()
M.y = Var()
-e = M.x+M.y
+e = M.x + M.y
M.y.value = 1
M.y.fixed = True
-assert(set(id(v) for v in EXPR.identify_variables(e)) == set([id(M.x), id(M.y)]))
-assert(set(id(v) for v in EXPR.identify_variables(e, include_fixed=False)) == set([id(M.x)]))
+assert set(id(v) for v in EXPR.identify_variables(e)) == set([id(M.x), id(M.y)])
+assert set(id(v) for v in EXPR.identify_variables(e, include_fixed=False)) == set(
+ [id(M.x)]
+)
# @ex9
-#---------------------------------------------
+# ---------------------------------------------
# @visitor1
from pyomo.core.expr import current as EXPR
-class SizeofVisitor(EXPR.SimpleExpressionVisitor):
+class SizeofVisitor(EXPR.SimpleExpressionVisitor):
def __init__(self):
self.counter = 0
@@ -126,9 +128,12 @@ def visit(self, node):
def finalize(self):
return self.counter
+
+
# @visitor1
-#---------------------------------------------
+
+# ---------------------------------------------
# @visitor2
def sizeof_expression(expr):
#
@@ -139,16 +144,18 @@ def sizeof_expression(expr):
# Compute the value using the :func:`xbfs` search method.
#
return visitor.xbfs(expr)
+
+
# @visitor2
-#---------------------------------------------
+# ---------------------------------------------
# @visitor3
from pyomo.core.expr import current as EXPR
-class CloneVisitor(EXPR.ExpressionValueVisitor):
+class CloneVisitor(EXPR.ExpressionValueVisitor):
def __init__(self):
- self.memo = {'__block_scope__': { id(None): False }}
+ self.memo = {'__block_scope__': {id(None): False}}
def visit(self, node, values):
#
@@ -160,14 +167,19 @@ def visiting_potential_leaf(self, node):
#
# Clone leaf nodes in the expression tree
#
- if node.__class__ in native_numeric_types or\
- node.__class__ not in pyomo5_expression_types:\
+ if (
+ node.__class__ in native_numeric_types
+ or node.__class__ not in pyomo5_expression_types
+ ):
return True, copy.deepcopy(node, self.memo)
return False, None
+
+
# @visitor3
-#---------------------------------------------
+
+# ---------------------------------------------
# @visitor4
def clone_expression(expr):
#
@@ -175,18 +187,20 @@ def clone_expression(expr):
#
visitor = CloneVisitor()
#
- # Clone the expression using the :func:`dfs_postorder_stack`
+ # Clone the expression using the :func:`dfs_postorder_stack`
# search method.
#
return visitor.dfs_postorder_stack(expr)
+
+
# @visitor4
-#---------------------------------------------
+# ---------------------------------------------
# @visitor5
from pyomo.core.expr import current as EXPR
-class ScalingVisitor(EXPR.ExpressionReplacementVisitor):
+class ScalingVisitor(EXPR.ExpressionReplacementVisitor):
def __init__(self, scale):
super(ScalingVisitor, self).__init__()
self.scale = scale
@@ -199,21 +213,24 @@ def visiting_potential_leaf(self, node):
return True, node
if node.is_variable_type():
- return True, self.scale[id(node)]*node
+ return True, self.scale[id(node)] * node
if isinstance(node, EXPR.LinearExpression):
node_ = copy.deepcopy(node)
node_.constant = node.constant
node_.linear_vars = copy.copy(node.linear_vars)
node_.linear_coefs = []
- for i,v in enumerate(node.linear_vars):
- node_.linear_coefs.append( node.linear_coefs[i]*self.scale[id(v)] )
+ for i, v in enumerate(node.linear_vars):
+ node_.linear_coefs.append(node.linear_coefs[i] * self.scale[id(v)])
return True, node_
return False, None
+
+
# @visitor5
-#---------------------------------------------
+
+# ---------------------------------------------
# @visitor6
def scale_expression(expr, scale):
#
@@ -221,26 +238,27 @@ def scale_expression(expr, scale):
#
visitor = ScalingVisitor(scale)
#
- # Scale the expression using the :func:`dfs_postorder_stack`
+ # Scale the expression using the :func:`dfs_postorder_stack`
# search method.
#
return visitor.dfs_postorder_stack(expr)
+
+
# @visitor6
-#---------------------------------------------
+# ---------------------------------------------
# @visitor7
M = ConcreteModel()
M.x = Var(range(5))
M.p = Param(range(5), mutable=True)
-scale={}
+scale = {}
for i in M.x:
- scale[id(M.x[i])] = M.p[i]
+ scale[id(M.x[i])] = M.p[i]
e = quicksum(M.x[i] for i in M.x)
-f = scale_expression(e,scale)
+f = scale_expression(e, scale)
# p[0]*x[0] + p[1]*x[1] + p[2]*x[2] + p[3]*x[3] + p[4]*x[4]
print(f)
# @visitor7
-
diff --git a/doc/OnlineDocs/tests/expr/overview.py b/doc/OnlineDocs/tests/expr/overview.py
index 7cd33925c6c..6207a4c4288 100644
--- a/doc/OnlineDocs/tests/expr/overview.py
+++ b/doc/OnlineDocs/tests/expr/overview.py
@@ -1,6 +1,6 @@
from pyomo.environ import *
-#---------------------------------------------
+# ---------------------------------------------
# @example1
M = ConcreteModel()
M.x = Var(range(100))
@@ -17,46 +17,46 @@
# @example1
print(e)
-#---------------------------------------------
+# ---------------------------------------------
# @example2
M = ConcreteModel()
M.p = Param(initialize=3)
-M.q = 1/M.p
+M.q = 1 / M.p
M.x = Var(range(100))
# The value M.q is cloned every time it is used.
e = 0
for i in range(100):
- e = e + M.x[i]*M.q
+ e = e + M.x[i] * M.q
# @example2
print(e)
-#---------------------------------------------
+# ---------------------------------------------
# @tree1
M = ConcreteModel()
M.v = Var()
-e = f = 2*M.v
+e = f = 2 * M.v
# @tree1
print(e)
-#---------------------------------------------
+# ---------------------------------------------
# @tree2
M = ConcreteModel()
M.v = Var()
-e = 2*M.v
+e = 2 * M.v
f = e + 3
# @tree2
print(e)
print(f)
-#---------------------------------------------
+# ---------------------------------------------
# @tree3
M = ConcreteModel()
M.v = Var()
-e = 2*M.v
+e = 2 * M.v
f = e + 3
g = e + 4
# @tree3
@@ -64,13 +64,13 @@
print(f)
print(g)
-#---------------------------------------------
+# ---------------------------------------------
# @tree4
M = ConcreteModel()
M.v = Var()
M.w = Var()
-e = 2*M.v
+e = 2 * M.v
f = e + 3
e += M.w
@@ -78,16 +78,15 @@
print(e)
print(f)
-#---------------------------------------------
+# ---------------------------------------------
# @tree5
M = ConcreteModel()
M.v = Var()
M.w = Var()
-M.e = Expression(expr=2*M.v)
+M.e = Expression(expr=2 * M.v)
f = M.e + 3
M.e += M.w
# @tree5
print(M.e)
-
diff --git a/doc/OnlineDocs/tests/expr/performance.py b/doc/OnlineDocs/tests/expr/performance.py
index 7ece93c195e..53ac5bb4f9e 100644
--- a/doc/OnlineDocs/tests/expr/performance.py
+++ b/doc/OnlineDocs/tests/expr/performance.py
@@ -1,6 +1,6 @@
from pyomo.environ import *
-#---------------------------------------------
+# ---------------------------------------------
# @loop1
M = ConcreteModel()
M.x = Var(range(5))
@@ -11,19 +11,19 @@
# @loop1
print(s)
-#---------------------------------------------
+# ---------------------------------------------
# @loop2
s = sum(M.x[i] for i in range(5))
# @loop2
print(s)
-#---------------------------------------------
+# ---------------------------------------------
# @loop3
-s = sum(M.x[i] for i in range(5))**2
+s = sum(M.x[i] for i in range(5)) ** 2
# @loop3
print(s)
-#---------------------------------------------
+# ---------------------------------------------
# @prod
M = ConcreteModel()
M.x = Var(range(5))
@@ -42,30 +42,30 @@
print(e2)
print(e3)
-#---------------------------------------------
+# ---------------------------------------------
# @quicksum
M = ConcreteModel()
M.x = Var(range(5))
# Summation using the Python sum() function
-e1 = sum(M.x[i]**2 for i in M.x)
+e1 = sum(M.x[i] ** 2 for i in M.x)
# Summation using the Pyomo quicksum function
-e2 = quicksum(M.x[i]**2 for i in M.x)
+e2 = quicksum(M.x[i] ** 2 for i in M.x)
# @quicksum
print(e1)
print(e2)
-#---------------------------------------------
+# ---------------------------------------------
# @warning
M = ConcreteModel()
M.x = Var(range(5))
-e = quicksum(M.x[i]**2 if i > 0 else M.x[i] for i in range(5))
+e = quicksum(M.x[i] ** 2 if i > 0 else M.x[i] for i in range(5))
# @warning
print(e)
-#---------------------------------------------
+# ---------------------------------------------
# @sum_product1
M = ConcreteModel()
M.z = RangeSet(5)
@@ -85,7 +85,7 @@
print(e2)
print(e3)
-#---------------------------------------------
+# ---------------------------------------------
# @sum_product2
# Sum the product of x_i/y_i
e1 = sum_product(M.x, denom=M.y)
@@ -95,4 +95,3 @@
# @sum_product2
print(e1)
print(e2)
-
diff --git a/doc/OnlineDocs/tests/expr/quicksum.py b/doc/OnlineDocs/tests/expr/quicksum.py
index 23b6c3473e3..a1ad9660664 100644
--- a/doc/OnlineDocs/tests/expr/quicksum.py
+++ b/doc/OnlineDocs/tests/expr/quicksum.py
@@ -9,7 +9,7 @@
M.x = Var(M.A)
start = time.time()
-e = sum( (M.x[i] - 1)**M.p[i] for i in M.A)
+e = sum((M.x[i] - 1) ** M.p[i] for i in M.A)
print("sum: %f" % (time.time() - start))
start = time.time()
@@ -17,7 +17,7 @@
print("repn: %f" % (time.time() - start))
start = time.time()
-e = quicksum( (M.x[i] - 1)**M.p[i] for i in M.A)
+e = quicksum((M.x[i] - 1) ** M.p[i] for i in M.A)
print("quicksum: %f" % (time.time() - start))
start = time.time()
diff --git a/doc/OnlineDocs/tests/scripting/AbstractSuffixes.py b/doc/OnlineDocs/tests/scripting/AbstractSuffixes.py
index fa7d4fae584..20a4cc20581 100644
--- a/doc/OnlineDocs/tests/scripting/AbstractSuffixes.py
+++ b/doc/OnlineDocs/tests/scripting/AbstractSuffixes.py
@@ -1,17 +1,24 @@
from pyomo.environ import *
model = AbstractModel()
-model.I = RangeSet(1,4)
+model.I = RangeSet(1, 4)
model.x = Var(model.I)
+
+
def c_rule(m, i):
return m.x[i] >= i
+
+
model.c = Constraint(model.I, rule=c_rule)
+
def foo_rule(m):
- return ((m.x[i], 3.0*i) for i in m.I)
+ return ((m.x[i], 3.0 * i) for i in m.I)
+
+
model.foo = Suffix(rule=foo_rule)
# instantiate the model
inst = model.create_instance()
for i in inst.I:
- print (i, inst.foo[inst.x[i]])
+ print(i, inst.foo[inst.x[i]])
diff --git a/doc/OnlineDocs/tests/scripting/Isinglebuild.py b/doc/OnlineDocs/tests/scripting/Isinglebuild.py
index bee8d105c9b..00f79c9a750 100644
--- a/doc/OnlineDocs/tests/scripting/Isinglebuild.py
+++ b/doc/OnlineDocs/tests/scripting/Isinglebuild.py
@@ -10,13 +10,15 @@
model.NodesOut = Set(model.Nodes, within=model.Nodes, initialize=[])
model.NodesIn = Set(model.Nodes, within=model.Nodes, initialize=[])
+
def Populate_In_and_Out(model):
# loop over the arcs and put the end points in the appropriate places
- for (i,j) in model.Arcs:
+ for i, j in model.Arcs:
model.NodesIn[j].add(i)
model.NodesOut[i].add(j)
-model.In_n_Out = BuildAction(rule = Populate_In_and_Out)
+
+model.In_n_Out = BuildAction(rule=Populate_In_and_Out)
model.Flow = Var(model.Arcs, domain=NonNegativeReals)
model.FlowCost = Param(model.Arcs)
@@ -24,14 +26,22 @@ def Populate_In_and_Out(model):
model.Demand = Param(model.Nodes)
model.Supply = Param(model.Nodes)
+
def Obj_rule(model):
return summation(model.FlowCost, model.Flow)
+
+
model.Obj = Objective(rule=Obj_rule, sense=minimize)
+
def FlowBalance_rule(model, node):
- return model.Supply[node] \
- + sum(model.Flow[i, node] for i in model.NodesIn[node]) \
- - model.Demand[node] \
- - sum(model.Flow[node, j] for j in model.NodesOut[node]) \
- == 0
+ return (
+ model.Supply[node]
+ + sum(model.Flow[i, node] for i in model.NodesIn[node])
+ - model.Demand[node]
+ - sum(model.Flow[node, j] for j in model.NodesOut[node])
+ == 0
+ )
+
+
model.FlowBalance = Constraint(model.Nodes, rule=FlowBalance_rule)
diff --git a/doc/OnlineDocs/tests/scripting/NodesIn_init.py b/doc/OnlineDocs/tests/scripting/NodesIn_init.py
index a91f690d840..4a90029baa3 100644
--- a/doc/OnlineDocs/tests/scripting/NodesIn_init.py
+++ b/doc/OnlineDocs/tests/scripting/NodesIn_init.py
@@ -1,7 +1,9 @@
def NodesIn_init(model, node):
retval = []
- for (i,j) in model.Arcs:
+ for i, j in model.Arcs:
if j == node:
retval.append(i)
return retval
+
+
model.NodesIn = Set(model.Nodes, initialize=NodesIn_init)
diff --git a/doc/OnlineDocs/tests/scripting/Z_init.py b/doc/OnlineDocs/tests/scripting/Z_init.py
index 6bcf99cd35a..426de6f7d08 100644
--- a/doc/OnlineDocs/tests/scripting/Z_init.py
+++ b/doc/OnlineDocs/tests/scripting/Z_init.py
@@ -1,5 +1,7 @@
def Z_init(model, i):
if i > 10:
return Set.End
- return 2*i+1
+ return 2 * i + 1
+
+
model.Z = Set(initialize=Z_init)
diff --git a/doc/OnlineDocs/tests/scripting/abstract2.py b/doc/OnlineDocs/tests/scripting/abstract2.py
index 9bf0ab6cf38..7eb444914db 100644
--- a/doc/OnlineDocs/tests/scripting/abstract2.py
+++ b/doc/OnlineDocs/tests/scripting/abstract2.py
@@ -15,14 +15,18 @@
# the next line declares a variable indexed by the set J
model.x = Var(model.J, domain=NonNegativeReals)
+
def obj_expression(model):
return summation(model.c, model.x)
+
model.OBJ = Objective(rule=obj_expression)
+
def ax_constraint_rule(model, i):
# return the expression for the constraint for i
- return sum(model.a[i,j] * model.x[j] for j in model.J) >= model.b[i]
+ return sum(model.a[i, j] * model.x[j] for j in model.J) >= model.b[i]
+
# the next line creates one constraint for each member of the set model.I
model.AxbConstraint = Constraint(model.I, rule=ax_constraint_rule)
diff --git a/doc/OnlineDocs/tests/scripting/abstract2piece.py b/doc/OnlineDocs/tests/scripting/abstract2piece.py
index 132d5ce060d..225ec0d1a64 100644
--- a/doc/OnlineDocs/tests/scripting/abstract2piece.py
+++ b/doc/OnlineDocs/tests/scripting/abstract2piece.py
@@ -8,7 +8,7 @@
model.I = Set()
model.J = Set()
-Topx = 6.1 # range of x variables
+Topx = 6.1 # range of x variables
model.a = Param(model.I, model.J)
model.b = Param(model.I)
@@ -21,23 +21,31 @@
# to avoid warnings, we set breakpoints at or beyond the bounds
PieceCnt = 100
bpts = []
-for i in range(PieceCnt+2):
- bpts.append(float((i*Topx)/PieceCnt))
+for i in range(PieceCnt + 2):
+ bpts.append(float((i * Topx) / PieceCnt))
+
def f4(model, j, xp):
# we not need j, but it is passed as the index for the constraint
return xp**4
-model.ComputeObj = Piecewise(model.J, model.y, model.x, pw_pts=bpts, pw_constr_type='EQ', f_rule=f4)
+
+model.ComputeObj = Piecewise(
+ model.J, model.y, model.x, pw_pts=bpts, pw_constr_type='EQ', f_rule=f4
+)
+
def obj_expression(model):
return summation(model.c, model.y)
+
model.OBJ = Objective(rule=obj_expression)
+
def ax_constraint_rule(model, i):
# return the expression for the constraint for i
- return sum(model.a[i,j] * model.x[j] for j in model.J) >= model.b[i]
+ return sum(model.a[i, j] * model.x[j] for j in model.J) >= model.b[i]
+
# the next line creates one constraint for each member of the set model.I
model.AxbConstraint = Constraint(model.I, rule=ax_constraint_rule)
diff --git a/doc/OnlineDocs/tests/scripting/abstract2piecebuild.py b/doc/OnlineDocs/tests/scripting/abstract2piecebuild.py
index 85480c8ac0b..1f00cdb0265 100644
--- a/doc/OnlineDocs/tests/scripting/abstract2piecebuild.py
+++ b/doc/OnlineDocs/tests/scripting/abstract2piecebuild.py
@@ -12,43 +12,55 @@
model.b = Param(model.I)
model.c = Param(model.J)
-model.Topx = Param(default=6.1) # range of x variables
+model.Topx = Param(default=6.1) # range of x variables
model.PieceCnt = Param(default=100)
# the next line declares a variable indexed by the set J
-model.x = Var(model.J, domain=NonNegativeReals, bounds=(0,model.Topx))
+model.x = Var(model.J, domain=NonNegativeReals, bounds=(0, model.Topx))
model.y = Var(model.J, domain=NonNegativeReals)
# to avoid warnings, we set breakpoints beyond the bounds
# we are using a dictionary so that we can have different
# breakpoints for each index. But we won't.
model.bpts = {}
+
+
# @Function_valid_declaration
def bpts_build(model, j):
-# @Function_valid_declaration
+ # @Function_valid_declaration
model.bpts[j] = []
- for i in range(model.PieceCnt+2):
- model.bpts[j].append(float((i*model.Topx)/model.PieceCnt))
-# The object model.BuildBpts is not refered to again;
+ for i in range(model.PieceCnt + 2):
+ model.bpts[j].append(float((i * model.Topx) / model.PieceCnt))
+
+
+# The object model.BuildBpts is not referred to again;
# the only goal is to trigger the action at build time
# @BuildAction_example
model.BuildBpts = BuildAction(model.J, rule=bpts_build)
# @BuildAction_example
+
def f4(model, j, xp):
# we not need j in this example, but it is passed as the index for the constraint
return xp**4
-model.ComputePieces = Piecewise(model.J, model.y, model.x, pw_pts=model.bpts, pw_constr_type='EQ', f_rule=f4)
+
+model.ComputePieces = Piecewise(
+ model.J, model.y, model.x, pw_pts=model.bpts, pw_constr_type='EQ', f_rule=f4
+)
+
def obj_expression(model):
return summation(model.c, model.y)
+
model.OBJ = Objective(rule=obj_expression)
+
def ax_constraint_rule(model, i):
# return the expression for the constraint for i
- return sum(model.a[i,j] * model.x[j] for j in model.J) >= model.b[i]
+ return sum(model.a[i, j] * model.x[j] for j in model.J) >= model.b[i]
+
# the next line creates one constraint for each member of the set model.I
model.AxbConstraint = Constraint(model.I, rule=ax_constraint_rule)
diff --git a/doc/OnlineDocs/tests/scripting/block_iter_example.py b/doc/OnlineDocs/tests/scripting/block_iter_example.py
index 89cfa77b835..680e0d1728b 100644
--- a/doc/OnlineDocs/tests/scripting/block_iter_example.py
+++ b/doc/OnlineDocs/tests/scripting/block_iter_example.py
@@ -1,13 +1,16 @@
# written by jds, adapted for doc by dlw
from pyomo.environ import *
-# simple way to get arbitrary, unique values for each thing
+# simple way to get arbitrary, unique values for each thing
val_iter = 0
+
+
def get_val(*args, **kwds):
global val_iter
val_iter += 1
return val_iter
+
model = ConcreteModel()
model.I = RangeSet(3)
model.x = Var(initialize=get_val)
@@ -17,10 +20,13 @@ def get_val(*args, **kwds):
model.b.a = Var(initialize=get_val)
model.b.b = Var(model.I, initialize=get_val)
-def c_rule(b,i):
+
+def c_rule(b, i):
b.c = Var(initialize=get_val)
b.d = Var(b.model().I, initialize=get_val)
-model.c = Block([1,2], rule=c_rule)
+
+
+model.c = Block([1, 2], rule=c_rule)
model.pprint()
@@ -30,5 +36,5 @@ def c_rule(b,i):
v.pprint()
for v_data in model.component_data_objects(Var, descend_into=True):
- print("Found: "+v_data.name+", value = "+str(value(v_data)))
+ print("Found: " + v_data.name + ", value = " + str(value(v_data)))
# @compprintloop
diff --git a/doc/OnlineDocs/tests/scripting/concrete1.py b/doc/OnlineDocs/tests/scripting/concrete1.py
index 5da68c14e38..1c1f1517e17 100644
--- a/doc/OnlineDocs/tests/scripting/concrete1.py
+++ b/doc/OnlineDocs/tests/scripting/concrete1.py
@@ -3,8 +3,8 @@
model = ConcreteModel()
-model.x = Var([1,2], domain=NonNegativeReals)
+model.x = Var([1, 2], domain=NonNegativeReals)
-model.OBJ = Objective(expr = 2*model.x[1] + 3*model.x[2])
+model.OBJ = Objective(expr=2 * model.x[1] + 3 * model.x[2])
-model.Constraint1 = Constraint(expr = 3*model.x[1] + 4*model.x[2] >= 1)
+model.Constraint1 = Constraint(expr=3 * model.x[1] + 4 * model.x[2] >= 1)
diff --git a/doc/OnlineDocs/tests/scripting/doubleA.py b/doc/OnlineDocs/tests/scripting/doubleA.py
index 77970841cdc..12a07944db3 100644
--- a/doc/OnlineDocs/tests/scripting/doubleA.py
+++ b/doc/OnlineDocs/tests/scripting/doubleA.py
@@ -1,3 +1,5 @@
def doubleA_init(model):
- return (i*2 for i in model.A)
+ return (i * 2 for i in model.A)
+
+
model.C = Set(initialize=DoubleA_init)
diff --git a/doc/OnlineDocs/tests/scripting/driveabs2.py b/doc/OnlineDocs/tests/scripting/driveabs2.py
index 257ea669480..67ab7468864 100644
--- a/doc/OnlineDocs/tests/scripting/driveabs2.py
+++ b/doc/OnlineDocs/tests/scripting/driveabs2.py
@@ -23,14 +23,14 @@
# @Access_all_dual
# display all duals
-print ("Duals")
+print("Duals")
for c in instance.component_objects(pyo.Constraint, active=True):
- print (" Constraint",c)
+ print(" Constraint", c)
for index in c:
- print (" ", index, instance.dual[c[index]])
+ print(" ", index, instance.dual[c[index]])
# @Access_all_dual
# @Access_one_dual
# access one dual
-print ("Dual for Film=", instance.dual[instance.AxbConstraint['Film']])
+print("Dual for Film=", instance.dual[instance.AxbConstraint['Film']])
# @Access_one_dual
diff --git a/doc/OnlineDocs/tests/scripting/driveconc1.py b/doc/OnlineDocs/tests/scripting/driveconc1.py
index 74acd353084..ca5d6fc1593 100644
--- a/doc/OnlineDocs/tests/scripting/driveconc1.py
+++ b/doc/OnlineDocs/tests/scripting/driveconc1.py
@@ -13,14 +13,11 @@
# so the solver plugin will know which suffixes to collect
model.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT)
-results = opt.solve(model) # also load results to model
+results = opt.solve(model) # also load results to model
# display all duals
-print ("Duals")
+print("Duals")
for c in model.component_objects(pyo.Constraint, active=True):
- print (" Constraint",c)
+ print(" Constraint", c)
for index in c:
- print (" ", index, model.dual[c[index]])
-
-
-
+ print(" ", index, model.dual[c[index]])
diff --git a/doc/OnlineDocs/tests/scripting/iterative1.py b/doc/OnlineDocs/tests/scripting/iterative1.py
index 6bdddb5ffd9..61b0fd3828e 100644
--- a/doc/OnlineDocs/tests/scripting/iterative1.py
+++ b/doc/OnlineDocs/tests/scripting/iterative1.py
@@ -2,6 +2,7 @@
# iterative1.py
import pyomo.environ as pyo
from pyomo.opt import SolverFactory
+
# @Import_symbols_for_pyomo
# @Call_SolverFactory_with_argument
@@ -17,8 +18,12 @@
model = pyo.AbstractModel()
model.n = pyo.Param(default=4)
model.x = pyo.Var(pyo.RangeSet(model.n), within=pyo.Binary)
+
+
def o_rule(model):
return pyo.summation(model.x)
+
+
model.o = pyo.Objective(rule=o_rule)
# @Create_base_model
# @Create_empty_constraint_list
@@ -39,20 +44,20 @@ def o_rule(model):
# Iterate to eliminate the previously found solution
# @Assign_integers
for i in range(5):
-# @Assign_integers
-# @Iteratively_assign_and_test
+ # @Assign_integers
+ # @Iteratively_assign_and_test
expr = 0
for j in instance.x:
if pyo.value(instance.x[j]) == 0:
expr += instance.x[j]
else:
- expr += (1-instance.x[j])
-# @Iteratively_assign_and_test
-# @Add_expression_constraint
- instance.c.add( expr >= 1 )
-# @Add_expression_constraint
-# @Find_and_display_solution
+ expr += 1 - instance.x[j]
+ # @Iteratively_assign_and_test
+ # @Add_expression_constraint
+ instance.c.add(expr >= 1)
+ # @Add_expression_constraint
+ # @Find_and_display_solution
results = opt.solve(instance)
- print ("\n===== iteration",i)
+ print("\n===== iteration", i)
instance.display()
# @Find_and_display_solution
diff --git a/doc/OnlineDocs/tests/scripting/iterative2.py b/doc/OnlineDocs/tests/scripting/iterative2.py
index 6758de13221..e559a2c8400 100644
--- a/doc/OnlineDocs/tests/scripting/iterative2.py
+++ b/doc/OnlineDocs/tests/scripting/iterative2.py
@@ -13,8 +13,12 @@
model = pyo.AbstractModel()
model.n = pyo.Param(default=4)
model.x = pyo.Var(pyo.RangeSet(model.n), within=pyo.Binary)
+
+
def o_rule(model):
return pyo.summation(model.x)
+
+
model.o = pyo.Objective(rule=o_rule)
model.c = pyo.ConstraintList()
diff --git a/doc/OnlineDocs/tests/scripting/noiteration1.py b/doc/OnlineDocs/tests/scripting/noiteration1.py
index 47e2b5769e4..be9fb529855 100644
--- a/doc/OnlineDocs/tests/scripting/noiteration1.py
+++ b/doc/OnlineDocs/tests/scripting/noiteration1.py
@@ -13,8 +13,12 @@
model = pyo.ConcreteModel()
model.n = pyo.Param(default=4)
model.x = pyo.Var(pyo.RangeSet(model.n), within=pyo.Binary)
+
+
def o_rule(model):
return pyo.summation(model.x)
+
+
model.o = pyo.Objective(rule=o_rule)
model.c = pyo.ConstraintList()
@@ -23,5 +27,4 @@ def o_rule(model):
if pyo.value(model.x[2]) == 0:
print("The second index has a zero")
else:
- print("x[2]=",pyo.value(model.x[2]))
-
+ print("x[2]=", pyo.value(model.x[2]))
diff --git a/doc/OnlineDocs/tests/scripting/parallel.py b/doc/OnlineDocs/tests/scripting/parallel.py
index 85ecda6824b..cf9b55d9605 100644
--- a/doc/OnlineDocs/tests/scripting/parallel.py
+++ b/doc/OnlineDocs/tests/scripting/parallel.py
@@ -5,7 +5,9 @@
rank = MPI.COMM_WORLD.Get_rank()
size = MPI.COMM_WORLD.Get_size()
-assert size == 2, 'This example only works with 2 processes; please us mpirun -np 2 python -m mpi4py parallel.py'
+assert (
+ size == 2
+), 'This example only works with 2 processes; please us mpirun -np 2 python -m mpi4py parallel.py'
# Create a solver
opt = pyo.SolverFactory('cplex_direct')
diff --git a/doc/OnlineDocs/tests/scripting/spy4Constraints.py b/doc/OnlineDocs/tests/scripting/spy4Constraints.py
index dfcca5d8375..ac42b4d38b3 100644
--- a/doc/OnlineDocs/tests/scripting/spy4Constraints.py
+++ b/doc/OnlineDocs/tests/scripting/spy4Constraints.py
@@ -3,33 +3,48 @@
Code snippets for Constraints.rst in testable form
"""
from pyomo.environ import *
+
model = ConcreteModel()
# @Inequality_constraints_2expressions
model.x = Var()
+
def aRule(model):
- return model.x >= 2
+ return model.x >= 2
+
+
model.Boundx = Constraint(rule=aRule)
+
def bRule(model):
- return (2, model.x, None)
+ return (2, model.x, None)
+
+
model.boundx = Constraint(rule=bRule)
# @Inequality_constraints_2expressions
model = ConcreteModel()
-model.J = Set(initialize=['butter','scones'])
+model.J = Set(initialize=['butter', 'scones'])
model.x = Var(model.J)
+
+
# @Constraint_example
def teaOKrule(model):
- return(model.x['butter'] + model.x['scones'] == 3)
+ return model.x['butter'] + model.x['scones'] == 3
+
+
model.TeaConst = Constraint(rule=teaOKrule)
# @Constraint_example
# @Passing_elements_crossproduct
-model.A = RangeSet(1,10)
+model.A = RangeSet(1, 10)
model.a = Param(model.A, within=PositiveReals)
model.ToBuy = Var(model.A)
+
+
def bud_rule(model, i):
- return model.a[i]*model.ToBuy[i] <= i
+ return model.a[i] * model.ToBuy[i] <= i
+
+
aBudget = Constraint(model.A, rule=bud_rule)
# @Passing_elements_crossproduct
diff --git a/doc/OnlineDocs/tests/scripting/spy4Expressions.py b/doc/OnlineDocs/tests/scripting/spy4Expressions.py
index 5a1ab565d59..d4a5cad321a 100644
--- a/doc/OnlineDocs/tests/scripting/spy4Expressions.py
+++ b/doc/OnlineDocs/tests/scripting/spy4Expressions.py
@@ -3,6 +3,7 @@
Code snippets for Expressions.rst in testable form
"""
from pyomo.environ import *
+
model = ConcreteModel()
# @Buildup_expression_switch
@@ -13,11 +14,14 @@
model.d = Param()
model.x = Var(model.A, domain=Boolean)
+
def pi_rule(model):
accexpr = summation(model.c, model.x)
if switch >= 2:
accexpr = accexpr - model.d
return accexpr >= 0.5
+
+
PieSlice = Constraint(rule=pi_rule)
# @Buildup_expression_switch
@@ -27,64 +31,76 @@ def pi_rule(model):
model.d = Param()
model.x = Var(model.A, domain=Boolean)
+
def pi_rule(model):
accexpr = summation(model.c, model.x)
if model.d >= 2: # NOT in an abstract model!!
accexpr = accexpr - model.d
return accexpr >= 0.5
+
+
PieSlice = Constraint(rule=pi_rule)
# @Abstract_wrong_usage
# @Declare_piecewise_constraints
-#model.pwconst = Piecewise(indexes, yvar, xvar, **Keywords)
-#model.pwconst = Piecewise(yvar,xvar,**Keywords)
+# model.pwconst = Piecewise(indexes, yvar, xvar, **Keywords)
+# model.pwconst = Piecewise(yvar,xvar,**Keywords)
# @Declare_piecewise_constraints
+
# @f_rule_Function_examples
# A function that changes with index
-def f(model,j,x):
- if (j == 2):
- return x**2 + 1.0
- else:
- return x**2 + 5.0
+def f(model, j, x):
+ if j == 2:
+ return x**2 + 1.0
+ else:
+ return x**2 + 5.0
+
# A nonlinear function
-f = lambda model,x : exp(x) + value(model.p)
+f = lambda model, x: exp(x) + value(model.p)
# A step function
-f = [0,0,1,1,2,2]
+f = [0, 0, 1, 1, 2, 2]
# @f_rule_Function_examples
# @Keyword_assignment_example
-kwds = {'pw_constr_type':'EQ','pw_repn':'SOS2','sense':maximize,'force_pw':True}
+kwds = {'pw_constr_type': 'EQ', 'pw_repn': 'SOS2', 'sense': maximize, 'force_pw': True}
# @Keyword_assignment_example
# @Expression_objects_illustration
model = ConcreteModel()
model.x = Var(initialize=1.0)
-def _e(m,i):
- return m.x*i
-model.e = Expression([1,2,3], rule=_e)
+
+
+def _e(m, i):
+ return m.x * i
+
+
+model.e = Expression([1, 2, 3], rule=_e)
instance = model.create_instance()
-print (value(instance.e[1])) # -> 1.0
-print (instance.e[1]()) # -> 1.0
-print (instance.e[1].value) # -> a pyomo expression object
+print(value(instance.e[1])) # -> 1.0
+print(instance.e[1]()) # -> 1.0
+print(instance.e[1].value) # -> a pyomo expression object
# Change the underlying expression
instance.e[1].value = instance.x**2
-#... solve
-#... load results
+# ... solve
+# ... load results
# print the value of the expression given the loaded optimal solution
-print (value(instance.e[1]))
+print(value(instance.e[1]))
# @Expression_objects_illustration
+
# @Define_python_function
def f(x, p):
return x + p
+
+
# @Define_python_function
# @Generate_new_expression
diff --git a/doc/OnlineDocs/tests/scripting/spy4PyomoCommand.py b/doc/OnlineDocs/tests/scripting/spy4PyomoCommand.py
index fff7b1d777b..c03ee1e5039 100644
--- a/doc/OnlineDocs/tests/scripting/spy4PyomoCommand.py
+++ b/doc/OnlineDocs/tests/scripting/spy4PyomoCommand.py
@@ -3,6 +3,7 @@
Code snippets for PyomoCommand.rst in testable form
"""
from pyomo.environ import *
+
model = ConcreteModel()
model.I = RangeSet(3)
model.J = RangeSet(3)
@@ -10,11 +11,13 @@
model.x = Var(model.J)
model.b = Param(model.I, default=1.0)
+
# @Troubleshooting_printed_command
def ax_constraint_rule(model, i):
- # return the expression for the constraint for i
- print ("ax_constraint_rule was called for i=",str(i))
- return sum(model.a[i,j] * model.x[j] for j in model.J) >= model.b[i]
+ # return the expression for the constraint for i
+ print("ax_constraint_rule was called for i=", str(i))
+ return sum(model.a[i, j] * model.x[j] for j in model.J) >= model.b[i]
+
# the next line creates one constraint for each member of the set model.I
model.AxbConstraint = Constraint(model.I, rule=ax_constraint_rule)
diff --git a/doc/OnlineDocs/tests/scripting/spy4Variables.py b/doc/OnlineDocs/tests/scripting/spy4Variables.py
index 4a302d87ea8..802226247c5 100644
--- a/doc/OnlineDocs/tests/scripting/spy4Variables.py
+++ b/doc/OnlineDocs/tests/scripting/spy4Variables.py
@@ -3,9 +3,10 @@
Code snippets for Variables.rst in testable form
"""
from pyomo.environ import *
+
model = ConcreteModel()
# @Declare_singleton_variable
-model.LumberJack = Var(within=NonNegativeReals, bounds=(0,6), initialize=1.5)
+model.LumberJack = Var(within=NonNegativeReals, bounds=(0, 6), initialize=1.5)
# @Declare_singleton_variable
# @Assign_value
@@ -14,9 +15,13 @@
# @Declare_bounds
model.A = Set(initialize=['Scones', 'Tea'])
-lb = {'Scones':2, 'Tea':4}
-ub = {'Scones':5, 'Tea':7}
+lb = {'Scones': 2, 'Tea': 4}
+ub = {'Scones': 5, 'Tea': 7}
+
+
def fb(model, i):
- return (lb[i], ub[i])
+ return (lb[i], ub[i])
+
+
model.PriceToCharge = Var(model.A, domain=PositiveIntegers, bounds=fb)
# @Declare_bounds
diff --git a/doc/OnlineDocs/tests/scripting/spy4scripts.py b/doc/OnlineDocs/tests/scripting/spy4scripts.py
index 40c69a6364c..48ba923d09c 100644
--- a/doc/OnlineDocs/tests/scripting/spy4scripts.py
+++ b/doc/OnlineDocs/tests/scripting/spy4scripts.py
@@ -9,7 +9,7 @@
import pyomo.environ as pyo
instance = pyo.ConcreteModel()
-instance.I = pyo.Set(initialize=[1,2,3])
+instance.I = pyo.Set(initialize=[1, 2, 3])
instance.sigma = pyo.Param(mutable=True, initialize=2.3)
instance.Theta = pyo.Param(instance.I, mutable=True)
for i in instance.I:
@@ -28,7 +28,7 @@
instance.ParamName.value = NewVal
# @Assign_value_to_unindexed_parametername_2
-instance.x = pyo.Var([1,2,3], initialize=0)
+instance.x = pyo.Var([1, 2, 3], initialize=0)
instance.y = pyo.Var()
# @Set_upper&lower_bound
@@ -45,43 +45,51 @@
instance.y.fixed = True
# @Equivalent_form_of_instance.x.fix(2)
-model=ConcreteModel()
-model.obj1 = pyo.Objective(expr = 0)
-model.obj2 = pyo.Objective(expr = 0)
+model = ConcreteModel()
+model.obj1 = pyo.Objective(expr=0)
+model.obj2 = pyo.Objective(expr=0)
# @Pass_multiple_objectives_to_solver
model.obj1.deactivate()
model.obj2.activate()
# @Pass_multiple_objectives_to_solver
+
# @Listing_arguments
def pyomo_preprocess(options=None):
- if options == None:
- print ("No command line options were given.")
- else:
- print ("Command line arguments were: %s" % options)
+ if options == None:
+ print("No command line options were given.")
+ else:
+ print("Command line arguments were: %s" % options)
+
+
# @Listing_arguments
# @Provide_dictionary_for_arbitrary_keywords
def pyomo_preprocess(**kwds):
- options = kwds.get('options',None)
- if options == None:
- print ("No command line options were given.")
- else:
- print ("Command line arguments were: %s" % options)
+ options = kwds.get('options', None)
+ if options == None:
+ print("No command line options were given.")
+ else:
+ print("Command line arguments were: %s" % options)
+
+
# @Provide_dictionary_for_arbitrary_keywords
+
# @Pyomo_preprocess_argument
def pyomo_preprocess(options=None):
pass
+
+
# @Pyomo_preprocess_argument
# @Display_all_variables&values
for v in instance.component_objects(pyo.Var, active=True):
- print("Variable",v)
+ print("Variable", v)
for index in v:
- print (" ",index, pyo.value(v[index]))
+ print(" ", index, pyo.value(v[index]))
# @Display_all_variables&values
# @Display_all_variables&values_data
@@ -90,41 +98,47 @@ def pyomo_preprocess(options=None):
# @Display_all_variables&values_data
-instance.iVar = pyo.Var([1,2,3], initialize=1, domain=pyo.Boolean)
+instance.iVar = pyo.Var([1, 2, 3], initialize=1, domain=pyo.Boolean)
instance.sVar = pyo.Var(initialize=1, domain=pyo.Boolean)
# dlw may 2018: the next snippet does not trigger any fixing ("active?")
# @Fix_all_integers&values
for var in instance.component_data_objects(pyo.Var, active=True):
if var.domain is pyo.IntegerSet or var.domain is pyo.BooleanSet:
- print ("fixing "+str(v))
- var.fixed = True # fix the current value
+ print("fixing " + str(v))
+ var.fixed = True # fix the current value
# @Fix_all_integers&values
+
# @Include_definition_in_modelfile
def pyomo_print_results(options, instance, results):
for v in instance.component_objects(pyo.Var, active=True):
- print ("Variable "+str(v))
+ print("Variable " + str(v))
varobject = getattr(instance, v)
for index in varobject:
- print (" ",index, varobject[index].value)
+ print(" ", index, varobject[index].value)
+
+
# @Include_definition_in_modelfile
# @Print_parameter_name&value
for parmobject in instance.component_objects(pyo.Param, active=True):
- print ("Parameter "+str(parmobject.name))
+ print("Parameter " + str(parmobject.name))
for index in parmobject:
- print (" ",index, parmobject[index].value)
+ print(" ", index, parmobject[index].value)
# @Print_parameter_name&value
+
# @Include_definition_output_constraints&duals
def pyomo_print_results(options, instance, results):
# display all duals
- print ("Duals")
+ print("Duals")
for c in instance.component_objects(pyo.Constraint, active=True):
- print (" Constraint",c)
+ print(" Constraint", c)
cobject = getattr(instance, c)
for index in cobject:
- print (" ", index, instance.dual[cobject[index]])
+ print(" ", index, instance.dual[cobject[index]])
+
+
# @Include_definition_output_constraints&duals
"""
@@ -171,7 +185,7 @@ def pyomo_print_results(options, instance, results):
# @Add_option_to_solver
# @Add_multiple_options_to_solver
-results = optimizer.solve(instance, options="threads=4", tee=True)
+results = optimizer.solve(instance, options={'threads' : 4}, tee=True)
# @Add_multiple_options_to_solver
# @Set_path_to_solver_executable
diff --git a/doc/OnlineDocs/tests/strip_examples.py b/doc/OnlineDocs/tests/strip_examples.py
index aaadfc4632c..045af6b87cc 100644
--- a/doc/OnlineDocs/tests/strip_examples.py
+++ b/doc/OnlineDocs/tests/strip_examples.py
@@ -10,7 +10,7 @@
# # @block
# print("END HERE")
#
-# If this file was foo.py, then a file foo_block.spy is created, which
+# If this file was foo.py, then a file foo_block.spy is created, which
# contains the lines between the lines starting with "# @".
#
# Additionally, the file foo.spy is created, which strips all lines
@@ -24,13 +24,14 @@
import os
import os.path
+
def f(root, file):
if not file.endswith('.py'):
return
prefix = os.path.splitext(file)[0]
- #print([root, file, prefix])
- OUTPUT = open(root+'/'+prefix+'.spy','w')
- INPUT = open(root+'/'+file,'r')
+ # print([root, file, prefix])
+ OUTPUT = open(root + '/' + prefix + '.spy', 'w')
+ INPUT = open(root + '/' + file, 'r')
flag = False
block_name = None
for line in INPUT:
@@ -39,10 +40,13 @@ def f(root, file):
if flag is False:
block_name = tmp[3:]
flag = True
- OUTPUT_ = open(root+'/'+prefix+'_%s.spy' % block_name,'w')
+ OUTPUT_ = open(root + '/' + prefix + '_%s.spy' % block_name, 'w')
else:
if block_name != tmp[3:]:
- print("ERROR parsing file '%s': Started block '%s' but ended with '%s'" % (root+'/'+file, block_name, tmp[3:]))
+ print(
+ "ERROR parsing file '%s': Started block '%s' but ended with '%s'"
+ % (root + '/' + file, block_name, tmp[3:])
+ )
sys.exit(1)
flag = False
block_name is None
diff --git a/doc/OnlineDocs/tests/test_examples.py b/doc/OnlineDocs/tests/test_examples.py
index f41db8644ac..0ee6a249c38 100644
--- a/doc/OnlineDocs/tests/test_examples.py
+++ b/doc/OnlineDocs/tests/test_examples.py
@@ -8,16 +8,17 @@
try:
import yaml
- yaml_available=True
+
+ yaml_available = True
except:
- yaml_available=False
+ yaml_available = False
# Find all *.txt files, and use them to define baseline tests
currdir = os.path.dirname(os.path.abspath(__file__))
datadir = currdir
-testdirs = [currdir, ]
+testdirs = [currdir]
-solver_dependencies = {
+solver_dependencies = {
'Test_nonlinear_ch': {
'test_rosen_pyomo_rosen': 'ipopt',
'test_react_design_run_pyomo_reactor_table': 'ipopt',
@@ -28,18 +29,14 @@
'test_disease_est_run_disease_callback': 'ipopt',
'test_deer_run_deer': 'ipopt',
},
- 'Test_mpec_ch': {
- 'test_mpec_ch_path1': 'path',
- },
- 'Test_dae_ch': {
- 'test_run_path_constraint_tester': 'ipopt',
- },
+ 'Test_mpec_ch': {'test_mpec_ch_path1': 'path'},
+ 'Test_dae_ch': {'test_run_path_constraint_tester': 'ipopt'},
}
-package_dependencies = {
+package_dependencies = {
'Test_data': {
- 'test_data_ABCD9': ['pyodbc',],
- 'test_data_ABCD8': ['pyodbc',],
- 'test_data_ABCD7': ['win32com',],
+ 'test_data_ABCD9': ['pyodbc'],
+ 'test_data_ABCD8': ['pyodbc'],
+ 'test_data_ABCD7': ['win32com'],
},
'Test_dataportal': {
'test_dataportal_dataportal_tab': ['xlrd'],
@@ -52,10 +49,13 @@
only_book_tests = set(['Test_nonlinear_ch', 'Test_scripts_ch'])
+
def _check_available(name):
from pyomo.opt.base.solvers import check_available_solvers
+
return bool(check_available_solvers(name))
+
def check_skip(tfname_, name):
#
# Skip if YAML isn't installed
@@ -85,12 +85,15 @@ def check_skip(tfname_, name):
# Return a boolean if the test should be skipped
#
if tfname_ in solver_dependencies:
- if name in solver_dependencies[tfname_] and \
- not solver_available[solver_dependencies[tfname_][name]]:
+ if (
+ name in solver_dependencies[tfname_]
+ and not solver_available[solver_dependencies[tfname_][name]]
+ ):
# Skip the test because a solver is not available
- # print('Skipping %s because of missing solver' %(name))
+ # print('Skipping %s because of missing solver' %(name))
return 'Solver "%s" is not available' % (
- solver_dependencies[tfname_][name], )
+ solver_dependencies[tfname_][name],
+ )
if tfname_ in package_dependencies:
if name in package_dependencies[tfname_]:
packages_ = package_dependencies[tfname_][name]
@@ -104,7 +107,8 @@ def check_skip(tfname_, name):
return "Package%s %s %s not available" % (
's' if len(_missing) > 1 else '',
", ".join(_missing),
- 'are' if len(_missing) > 1 else 'is',)
+ 'are' if len(_missing) > 1 else 'is',
+ )
return False
@@ -112,135 +116,154 @@ def filter(line):
# Ignore certain text when comparing output with baseline
# Ipopt 3.12.4 puts BACKSPACE (chr(8) / ^H) into the output.
- line = line.strip(" \n\t"+chr(8))
+ line = line.strip(" \n\t" + chr(8))
if not line:
return True
- for field in ( '[',
- 'password:',
- 'http:',
- 'Job ',
- 'Importing module',
- 'Function',
- 'File',):
+ for field in (
+ '[',
+ 'password:',
+ 'http:',
+ 'Job ',
+ 'Importing module',
+ 'Function',
+ 'File',
+ ):
if line.startswith(field):
return True
- for field in ( 'Total CPU',
- 'Ipopt',
- 'Status: optimal',
- 'Status: feasible',
- 'time:',
- 'Time:',
- 'with format cpxlp',
- 'usermodel = >> for var in instance.component_data_objects(pyo.Var, active=True):
@@ -479,7 +479,7 @@ blocks) is as follows (this particular snippet assumes that instead of
.. literalinclude:: tests/scripting/block_iter_example_compprintloop.spy
:language: python
-.. _ParmAccess:
+.. _ParamAccess:
Accessing Parameter Values
--------------------------
diff --git a/doc/logos/doe/PyomoDoE-lg.png b/doc/logos/doe/PyomoDoE-lg.png
new file mode 100644
index 00000000000..88bc36ce617
Binary files /dev/null and b/doc/logos/doe/PyomoDoE-lg.png differ
diff --git a/doc/logos/doe/PyomoDoE-md.png b/doc/logos/doe/PyomoDoE-md.png
new file mode 100644
index 00000000000..2612d31653a
Binary files /dev/null and b/doc/logos/doe/PyomoDoE-md.png differ
diff --git a/doc/logos/doe/PyomoDoE-sm.png b/doc/logos/doe/PyomoDoE-sm.png
new file mode 100644
index 00000000000..9649ef80dda
Binary files /dev/null and b/doc/logos/doe/PyomoDoE-sm.png differ
diff --git a/doc/logos/doe/PyomoDoE.ai b/doc/logos/doe/PyomoDoE.ai
new file mode 100644
index 00000000000..ec3708ccfdb
--- /dev/null
+++ b/doc/logos/doe/PyomoDoE.ai
@@ -0,0 +1,1608 @@
+%PDF-1.6
%âãÏÓ
+1 0 obj
<>/OCGs[22 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
+
+
+
+
+ application/pdf
+
+
+ PyomoDOE
+
+
+ 2022-09-15T15:11:38-04:00
+ 2022-09-15T15:11:38-04:00
+ 2022-09-15T15:11:38-04:00
+ Adobe Illustrator 26.5 (Macintosh)
+
+
+
+ 256
+ 116
+ JPEG
+ /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAdAEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9Na5otprOmy2N0PhfdJB
9pHH2WHyynPhjkiYlu0+eWKYkHiGqWGpaPfy2FwWjkiOxUkKwPR19jnLZccscjEvXYckMsRIdVJN
W1RDVLydT4iRx/HIjLMdT82RwwPQfJWTzF5gQUTU7tR4CeQf8bZIajIP4pfMsTpsR/hj8grr5v8A
M6mo1O4r7uSPuOTGsy/ziwOiw/zQrJ5682KKDUpKe4Q/rU5Ia7N/OYHs/B/NZB5W89eYJr1n1G5+
s2kYAeP041PxdwUVTUUyyPauSEgZG49eThavs/EI+kVL4vTYZopolliYPG4qrDoQc6SExIAjcF5+
USDRX5JDsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirG/O3lRNd
0/nCAuo24Jt36ch1MbHwPbwOYWt0nix2+ocnP0Gs8GW/0nn+t4vJHJHI0cilJEJV0YUIINCCDnME
UaL1QIIsLcCXYq7FWQeW46QTSfzMF/4EV/42zHz9HD1R3AZv5X136nKLS4b/AEWQ/Cx/YY/wObLs
rX+EeCX0H7C6jV6fiHEObN86t1DsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi
rsVdirsVefecV/MTQueoaJftf6WvxSWssUcksI+fEO6e9ajv45gZ/GhvE3F22k/L5fTOPDLv3osS
tfzw8zx0FxaWk6+IWRG+8OR+GYse0Z9QHYS7FxHkZBKdc84Wev6gt19QFhdSCkzLJzSRh0NCq8TT
bqa5g6uYyHiAo9XM0umlijw8XEOiFzBch2KuxVk2gRlNPDf78dmH/Ef+Ncxs3NwNQfUmWUtDMPKm
veqq6fct+9UfuHP7QH7J9x2zpuye0OIeHM79P1Or1mnr1Dl1ZNm+de7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXnfnv8qLTVfU1HRAttqRq0lv9mKY96fyOfHoe/jmv
1OiEt47F3Gi7UMPTPeP2h4teWd3ZXUlrdxNBcwnjJE4owOaiUSDRekhMSFg2Ez0689ZODn96v4jx
zFnCkEIzK0OxVlukJw06AeK8v+CJP8cxMn1F12Y3IovK2ptHZHDoSrKaqw2IIwgkGwpFs/8AL2tr
qVtxkIF3EP3q/wAw/nH8c7Hs7XDPGj9Y5/rdLqdP4Z2+kptmycV2KuxV2KuxV2KpV5p8x2Plry9f
a7fJLJaafEZpo4ArSFQQPhDMi137sMVeS/8AQ3f5bf8AVt1n/kRa/wDZTimkXpv/ADlb+V15crDO
mo6ejED17m3jMYr3PoSzPt/q4rSe+e/z48peS7mxj1Gy1C7ttTtxdWGoWKW8ttNGf5HeeMkioJ26
EeOKGdaLrGn61pFnq2nSiaxvoknt5B3VxUVHYjoR2OKsC8+fn95I8l+Yf0Bfw319qIjSSVLCOGQR
mTdI39SWI8ytGoB0IxVNvOH5u+TvJ+n2Vz5hkms7y/iWaHSeAkvACBUOiMyLxOxJelQaE4q8+l/5
y98giSkek6q8f87Jbqff4fWb9eKaZj5D/Pn8vvOmoppmnTz2mpygmCyvYxG8nEFm4MjSRkgCtOVc
UIfz9/zkB5N8keYX0LVrPUZ7tIo5jJaxQPHxkFRvJNE1f9jirI/y7/Mfy/5+0WXVtFWeKKCdreaC
6VEmV1VWqVR5BxYNsa4qnHmLXtP8v6Ffa3qLFbLT4Xnm40LEIK8UBKgsx+FRXriryvSf+cqfy91P
VLPTbfT9WWe+njtomkhtggeZwiliLhjSrb0GKaZ757/Mryh5HtIrjzBeGF7jl9VtYlMk0vCnLig7
CvViB74oeZz/APOXnkFXpBpOqyJ/MyW6b+wEzYpplfkf/nIH8u/OGpxaVZTXFjqU5It7a/jWMyEV
PFHjeWPltsC1T2xQ9JxV2Kse83+R9H8zW3G6X0r2MUgvUA5r7N/MvsfopmPn08cg35uZpNbPCduX
c8I8x+Vtb8sagIr2Oikk290m8cgH8p/Wp3zR59PKBqT1Om1UM0bj8lW1uUuIg42PRl8DmBKNFuIV
cihmdqnC2hT+VFH3DMKXMurkbJVcixdiqM0tr6O8ilswfWDAL4Gu3E/PM/QYcxmJYxy69HH1E4CN
SZxrWvWWi2KXeoBlR2EdIxz+MqWp2/lO+d3p8E8poc3nNRqI4hxS5MG1H88/LtvO1vCsYnGxjuLi
KJxUVFUBc5sYdlH+KQcCXaZIuEJEfj3pBqv546lCsdxbmzMSyIXhRuTNGDVxyLNSqilaZXrtHHDi
uAlOdj8bOb2NknqdQIZSMeOibO3Tbc+dfC3pFp+YnkW5tYbhdf0+NZkVxHLdQJIvIVo6l6qw7jNe
McyLo/JypgRkRY2ZBHJHLGskbB43AZHUgqVIqCCOoOQQuxVhH53f+Sn8z/8AMG3/ABJcVeT/APOL
HlHyprXk/VrjWdFsNTnj1Axxy3ltDO6p6EZ4q0isQKmtMUlr/nKPyB5M0Tyfp2raNpFrpl82pJbO
1pGsCtFJBM7BkQKpo0S022xUI/QfIZ/ML/nGfRbUgPrVkl1LpEx6iS3upo1iqf2ZI14eHQ9sVYd+
R/52R+S/LWv6Dr1f9xscl3o1vISrG45cHtPEcpGD9NvjOKt/849+Sb/zz56vPPnmGtzbWFybjm4+
GfUHPNQB/LDUPQdPhHTFUN+bEMfmj/nJW30S8Pq2Au9N09kqy/uGWOSZARuPilfpir6Rh/Kr8s4o
FhXyppJRRxBeygd6e7shYn3JxQ+Y/wA2NF0ryJ+eemS6DAtjaJJYahHbxV4I3q0cKvZWMZPHpv8A
RilX/wCch7W3u/z0trS5qbe4SwimC/a4O3Fqe9DiqN/5xu1288pfmhqfkvUz6Yv2ltHQn4VvbIsV
I7fEodff4cVLNv8AnLXzgbHyvp/le3kpPq8vr3ag/wDHtbEFVYf5cpUj/VOKh4Tp/lyfQfPfku2u
AVubptMvpVIoR9auBIgI8RGVBr3xVnP/ADkG36d/PbT9EuWYWqDT7Dbslw4dyN+v7/FX0da/lR+W
VtAkEflXSmRBQGWzgmf6XkV3P0nFD5j/AOcgvLuleRvzQ0u78u2iWEBtrfUY4YfgRbiKeQHgKnj/
AHSnbFL7GxQ7FXYqhdU0rT9VspLLUIFuLaUfFGw79iD1BHYjIzgJCjybMWWUJcUTReK+b/y51Lyz
M+oafzvNH6yGlZIl/wCLAOoH8w+mmaPV6ExFjeL02i7Sjl9Mtp/ek1vxnMfA1EhABHuaZqTs58jQ
ZvmvdUqRW8sv2Rt4npmXptDlzfSNu/o05c8IcyjIrGNd3+M+HbOh0vY2OG8/Ufsdbl10pbR2DI/L
VvbmZpWZfVTaOLv7tTNwAAKDh3bH/wA6J7tdCtoLLgbySR3gEponJE4gtQE0HPNr2VE8UiO51Pa0
o1ES5W8R0vyDo8ETyaiv6Q1Cdi9xdSFgSzbniAdhm7hjA97qcuumT6fTEcgzS4/I7yLe6LDcgXdp
JJAjv6E1QWdRX+9WXuc1s9TMTMel07bHllwCXWr+xLLX8nPy4s7y2tLiGa5uLosLdZ5nHIoKt/de
mOhyE8wiQCdzyTCefJCU4j0wq/K9g+jookiiSKMUjjUKg8AooM0hLtV2KsI/O7/yU/mf/mDb/iS4
q+Zfyg8v/nRqeiXsvkPUvqWnJc8LqP11irP6amtGVq/ARillGrfkT+f3m24gi8z6xDNbwmqNdXbS
Rx12YpFGjDkR7D54q+j/ACX5XtvKvlXTPL9tIZotOhERmI4l3JLSPxFacnYmmKHyj/zlH5X03Rfz
GW7sV9P9NWwvrqIAcROZHjdl/wBfgGP+VU98Uh9XeS/Kem+U/LNhoGnL/o9lGFaQijSSH4pJW/yn
ck4ofMHm4fUv+croGuDwRtX01g3akscHE1/2W+KX1zih8jf85GgX354afZwGswhsLdgNyHklZgKD
fpIMUhr8/v8Ayflj/wBu7/iYxVGf85K6Dd+VPzL0rztpg9P6+0dyHHRb2yZa1p2ZAh9zyxUJPqt2
v5yfnvaR2wY6M7QxANUFLG1T1J6ntybnT3YYqmn50Iif85GaIiKFRZdJVVUUAAlUAADFVP8AN6f6
h/zkxp17NRYorzSJ6saDhGYqknsPgOKvrbFD5K/5y1m+s/mNpVlCOcsemRAgHfnLcTUWnyofpxSH
1rih2KuxV2KuIBBBFQdiDirzjzN5D0q21GO+0qVLYtIr3Gn/ALHWpeOgPD3U7eFOh1Os7M494bF2
+n7VIiYz3259V0VjEm7fG3v0+7BpuxsWPeXqP2fJxMutnLYbBdeXAtbKe548xBG8nAbV4KTT8M3M
I7gBwiUi8jedrTzdp099a2728cExgKyEMSQoaop/rZfqMHhmrtjGVs58v2kE9yzyGrQ0ZE8ff6Mx
2TG/zcmBu9Ohrukcj0/12Uf8aZu+yBtI+50PbMvVEe9gGbh0qPi1D80bKB0i0ee/0uVVNn+4eTim
3AqYvip02P4Zxms1mWOonUbje3w93zfTezeyNDl0eMTycOTh9Rvv3r1dw2Ydrt75unvYr3Ubee1l
tjygrC8IjIPKoqPbqc1Gp1WXJISltXJ63s3s3R6fGceKpCf1WQbfRH5f+b4fNHlyG+qFvY/3N9EP
2ZlG5A/lf7Q+7tmwwZeON9Xju09CdNmMf4eY937GSZc69jP5maBqHmHyHrei6cFa+vrYxW6u3BSx
IO7Hp0xViX/OPn5eeZPI/lnUdP15Ikubm9+sRCGQSDh6SJuR7qcVep4q7FXg3/OQf5O+dPPHmbTt
Q0GKB7a2svq8pmmEZ5+q77AjpRhil7zih4r+d/5BXnnbV4vMWgXsVpq6RLDcQXHJY5RGSUdZEDsr
gHj0odundSw2L8uv+cqIIDbR+YZDENgTqBZqDpR2Bf8AHFU0/Lj/AJxx802/nK182eeNUiu7i1mW
7WBJJLmaW4j3jaaaQL9hgG25VpTFbRP5r/kx538y/mra+ZdLit20uL6nzaSYI/7hgX+EjFXov50f
l/L558i3Ok2gT9KQyJdaa0h4qJozQgt25Rsy4oYb/wA49/kvrXke81XVvMSQjUriNLWyWFxIFhrz
lJIH7bKn3e+KUF+ZP5O+dPMH5xad5q06KBtJtpLB5WeYLJS2kDSUQjwG2Kpx+ef5E3Pn27tNZ0a8
hs9ZtYvq8iXHIRTRKxdPjQMyspZv2TWvamKsEg/Lf/nKa0hNrB5hkaEDgpOolzxGw4tIC4/DFUb5
K/5xt85T+brTzL591iO7a2mjuZIBLLd3E7xEFEllkCgKCBWhbbbbritvo/FDsVdirsVQ2prKbCb0
mKOF5AqaH4dz+GKsMJJNTuT1OFDsVRulWFtfXD21yvOF42DLUitaDt88bpUD+WP5daf5d8sNZS2j
288l7ezOjSFyUNy6W7VDEb2yR/x3rl2oymcr8vx9qgUjNKuvq19HITRCeD/6rf065SrFvzSlD+ZU
Qf7qto1PzLO3/G2dD2UKxH3/AKnm+1zeX/N/WxawtHvL63tI/t3EiRL83YD+ObDJPhiSejrscOKQ
iOpfQMUSRRJFGOMcahUHgFFBnHE2bL2oFCguwJWrFGjMyoFZ92IABPzxpJJL5I/6G7/Mn/q26N/y
Iuv+ynFad/0N3+ZP/Vt0b/kRdf8AZTitO/6G7/Mn/q26N/yIuv8AspxWnf8AQ3f5k/8AVt0b/kRd
f9lOK0m+h/8AOYOtJMi67oFtPCTSSSxkkhZR3ISUzBvlyHzxWn0H5J89eXPOmiJq+hXBltyeE0Tj
jNDIBUxypU8W+mh7EjFCf4q7FXYq7FXYq8c/PP8AOvX/AMvdW0yz0yxtbuO+geaRrn1KqVfiAvB0
xS8y/wChvvO//Vm0z7rj/qritO/6G+87/wDVm0z7rj/qritO/wChvvO//Vm0z7rj/qritO/6G+87
/wDVm0z7rj/qritJh5d/5yt85ap5g0zTJtI05Ir67gtpHQT8lWaRUJWshFQGxWn1Bih2KuxV2KuI
BBB3B2IxVg9zCYbiSI/sMV+44UKeKo3Sb2KzujLKGK8StFoTUkeJGKpz/iaw/wB9y/cv/NWBNsUu
bq3tYTNO4jjXqT+oeJycIGRqIsteTJGAuRoMPv31DzNrkktnbySyyBFVBuQqIE5Mei1413OdLgAw
YgJkPLZzLUZSYA7s68o/l3+jLqLUdRmD3cVWigj+wpIIqzH7R37fjms1naPGDGI2dtouzPDInI+r
uZvmqdu7FXYq/NbFk9o/KL/nH2z8/wDlR9dm1qTT3W6ktvQSBZRSNUblyLp15+GKGbf9Cc6b/wBT
RN/0iL/1VxW1O4/5w4tTH/o/mp1k7epZBlO3TadafPFbeG/mN+Xus+Q/Mj6JqbpMxjWe2uoq8JYX
JAYBtweSkEeI+nFWef8AOLPmifS/zIXSC5+p67BJDJGT8PrQI00T/MBXUf62Kl9i4odirsVdirsV
fLH/ADmF/wApJ5e/5g5f+TuKQ8P8t+XdV8x63a6LpMQm1C8YrbxsyoCVUufiYhRspxS9C/6Fl/OD
/q1Q/wDSXb/814ot3/Qsv5wf9WqH/pLt/wDmvFbd/wBCy/nB/wBWqH/pLt/+a8VtNPKv/OOn5r6f
5n0e/utMhS2tL22nncXUDERxSq7Ggep2GKvsHFDsVdirsVdirz+fW7bUPMeuWkRHPTbiOBwO/K3j
ev8AwbMv0ZbPGYiJ7x+liCqZWl2KoO+1KO2KxIjXF3J/c2sYLOx+Qrtl+DTyyHbYDmegcbUamOPz
keQHMqun+QdU1WZbzzBN6EQ3SyiI5AeBO4X8T8szfzePCOHELP8AOLgjRZM54sxofzQznTtL0/Tb
cW9jAkEQ6hRuT4sTux9zmuyZZTNyNu0xYo4xURQRWVtjsVdirsVfmtiyfYf/ADif/wCSvl/7adx/
ybixQXs2KHYq+af+cxtPQSeV9QWnqMLu3kPchTE6du3JsUh4v+U921p+ZvlaZdq6paRE1p8Msyxt
19nxV97ahqFlp1jPf306W1nbI0txPIQqIiipZicUPD/MX/OXPk+yneHRNKutWCEgTyMtpE/ulRLJ
T/WQfLFNMbk/5zHvi5MflWJU7K16zH7xCv6sVpNNJ/5zE0eWVV1fy3cWsXR5bW4S4Pz4Olv/AMSx
WnuHlTzb5f8ANejRaxoV2t3ZSkqWAKsjr9pJEajKw8D8+mKHzh/zmF/yknl7/mDl/wCTuKQ8+/ID
/wAm/wCW/wDjNL/1DyYq+6MUOxV2KuxV2KuxV2KuxVZNPDBGZJpFjjHVmIA/HITnGIuRoK+Zvy08
2fX/AMy/MRZ6xa3JPcw77Fo5WZAPlE7fdm812GsMf6Lj45eovYM07esgt9R1S0E+jCO4gZmj+trJ
GyKyMUcActyrAjKs85w5QMj8vv8A0fYwlxEen5qdv5S8w2kjSwxt60n95MJl5t8zyGaLVR1+U77R
6CJAA+F/abPmwx4IwNj6j16qrp5xt/iY3Zp1ozSD8C2YRjrYfz/tLcti81a/bNxkkD0/YlQV/AK2
CPauogaJv3hbTWz89ISFvLYr4vEa/wDCt/XM/D26P44/JbZBY6vp18P9GnV27odnH+xO+bfBq8eX
6Tf3pReZKuxV+a2LJ9h/84n/APkr5f8Atp3H/JuLFBezYodir59/5zC/5Rvy9/zGS/8AJrFIfOnk
X/lN/L3/AG07P/qITFX1B/zllrs9j+XtppkLlDq16iTgGnKGBWlKn/noEOKh84flh+XWoef/ADQN
Ds7hLQJC9zdXUilxHChVSQgK8iWkUAVGKvc4/wDnDrSBCRJ5muGm2o62qKvv8JkY/wDDYrbyv84v
ySvvy5FjdDUV1PTb93jjm9IwyRyIA3F15SA1U7EN2OwxVmn/ADiDr80PmbWtBZz9XvLRbxEPQS28
ipt4Flm3+WKld/zmF/yknl7/AJg5f+TuKh8+4pdirsVdir6V/wCcNf8Apr/+3d/2NYoL6UxQ7FXE
gCp2A6nFUluddnuJGttGh+tSg0e4O0Kf7Lv/AJ9c1mTXSmeHAOI9/wDCFY/5y0ySx8qazrWrXj3F
xa2c8sUanigkCHgB82p0Aw6bsk5csTmkZG+XT8fJjLYPlHyfrA0bzPpupM3GO3nUzN4RN8EnT/IY
53uox8cDHycWJos189/mxqOvSto/l1ZIbGU+kZEB9e5LGnFQPiVW/l6nv4ZhaXQCHqnz+5nPJewe
2fkR5T8zeWvJz2uuhYWuZzdW1nWskKuigrJ2BPHlxHTvvsNfr80JzuLbjiQN3o+YLY7FVOe2t514
TxJKv8rqGH45CeOMxUgCqS33k3S7irW/K2kP8vxJ/wACf4HNXn7GxT+n0n7EUxjUfLuq6afVK84l
NRPETt8+65o9R2flw78x3hUbpPnG8tysd7W4h6c/92D6f2vpzK0nbE4bT9Uft/atsxs761vIRNbS
CSM9x1B8COxzpMOeGSPFE2Evzdy1k+w/+cT/APyV8v8A207j/k3FigvZsUOxV88/85iTquieW7c/
akubiQfJI0B/4mMUh89/l9E83n3y1ClOcmq2SLXpVrhAMVe8/wDOZMzCDylDQcXa/cnvVBbgf8Tx
UJJ/zh/AG83a7PXePT1QD/XmU/8AGmKl9WYoeHf85eRofy70uUj411eJVPs1tcE/8RGKQ8l/5xZZ
l/NeEAkBrK5DAHqKKaH6RipZJ/zmF/yknl7/AJg5f+TuKh5V+VHlvTPMv5haNoeqK7WF7JIk6xtw
YhYXcUYdN1GKvp//AKFa/Kf/AJZ7z/pJb+mKLd/0K1+U/wDyz3n/AEkt/TFbd/0K1+U//LPef9JL
f0xW2X+QPyt8p+Q/r/8Ah+OZP0j6X1n1pTLX0OfClRt/etirLsVWzTRQxNLKwSNBVmORnMRFnkqW
yWtzqZ/0nlBYfs2w+F5PeQjoP8n78wpYpZ/q9OPu6n3/AKvmqYwwxQxrHEgjjXZUUUA+7M2EBEUB
QV5t/wA5E6qLH8s7qCtG1G4gtVPfZ/XP/CwkZsOzoXlHk15Ts+XvLvlvWvMeqxaXo9s11dy/sr9l
VHV3Y7Ko7k5v8mWMBcjs4wBL6n/K78mNF8mxJfXfC/8AMLD47sj4IajdYFPTwLn4j7DbOf1Wtll2
G0XJhjAejZhNjsVdirsVdirsVY/rXlG1uw01nS3uOpXpGx9wOh+WafW9kwyeqHpl9hRTE4LjU9Gv
jTlDMu0kbfZYe47jOfhky6bJ3S7lfEWdwzfYf/OJ/wD5K+X/ALadx/ybixQXs2KHYq+SP+csPNth
q3m7TdGsp0uE0WCT6y8bBlW4uGHOOo/aVIkr86dRikMH/I7TG1H82fLUAFfTuxcn2FqjT13/AOMe
KvYP+cyLZ2tPKlyK8I5L6NttqyCBhv8A88zioY9/ziDeInnXWbMkBp9O9RQep9KdAaf8jMVL6wxQ
8H/5y+vETyPo1kSOc2piZV7kQ28qkj2/fDFIeY/84p2zzfmmZFrS20+4lagrsWjj38N5MVKf/wDO
YX/KSeXv+YOX/k7ioeafkpqum6V+aOg6hqVzHZ2NvLIZrmZgkaAwSKCzHYbkDFX2J/yt78r/APqa
dM/6SY/64od/yt78r/8AqadM/wCkmP8Arirv+Vvflf8A9TTpn/STH/XFVS2/NX8trq5itrfzLp0t
xO6xwxJcRlmdzxVVAO5JNMVZViqk1uJJhJJ8Sx7xJ2B/mPv4eGVnHcrPTkqrliuxV5n+cXkbXfO9
3oWi2JFtpsLzXWpX8gqkdAqRqq1BdyGeg+8jM7R544hKR59GvJEnZlvkzyN5e8n6WNP0e34cqG5u
noZpmH7Uj0367DoO2Y+bPLIbkyjEBP8AKWTsVdirsVdirsVdirsVS/WNFtdTg4SjjMo/dTDqp/iP
bMPWaKGeNHn0KvzmzMZJlp/mbzJptubfTtVvLK3LFzDb3EsScjsW4oyiu2Kor/HXnf8A6mHU/wDp
MuP+a8UKN15u813cZiutav7iM1BSW5mdd9jszHFKU4q+m/8AnFv8rdW0+7uPOWtWr2okhNvpEMyl
ZGWQgyT8Tuo4rxSvUE9qHFBZ3/zkd5Hv/NX5esdMga41LSZ1vYYIxWSSMKySog7ni/OnU8aDFAfH
Gi65rOganFqWkXcthqFuSI54jxYVFGB8QRsQcWTPo/8AnJL840hMf6dVz0WRrS0LD/klQ/SDiimH
ea/O/mvzbdx3fmLUpdQmhBEPPiqRhqFuEaBEWtBXiuKvoL/nEnyTqFnBqvmy9gaGK9jSz00uCpkj
DepLIK/sFggU96HFSkf/ADmF/wApJ5e/5g5f+TuKh8+4pdirsVdiqeeRf+U38vf9tOz/AOohMUP0
NxQ7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq+ff+sQP+XP/uYYpd/1iB/y5/8AcwxV3/WIH/Ln
/wBzDFUZZ/8AQpPpH0v0Xx5b+t9Z5VoP9+/FT8MVZf5O/wCVBfW4v8MfoD9If7p9P6v9a6/sep+9
+7FD0fFXYq8w/MD/AKF8+vTf4v8A0P8ApOv+kdPrdf8Aiz6v++r/AK2KvO2/6Ey5GtK13p+nKfRT
bFLLvKn/AEK59aT9D/oX6zyHp/XeXLltTh9e7+FO+KvZF48Rwpxp8NOlO1MUMM8+/wDKpfrdr/jj
9FfW/Tb6n+kvT5+ny+Lhz7csVYt/1i9/37X/AE74pd/1i9/37X/Tvirv+sXv+/a/6d8Vd/1i9/37
X/TviqJ03/oW39I2v6O/w7+kPWj+p+l6HqetyHp8Kb8uVKYof//Z
+
+
+
+ uuid:648047da-72d6-b74b-a5aa-806a33643ff0
+ xmp.did:26665edd-e4a0-4a03-aa39-7964ff2d7809
+ uuid:5D20892493BFDB11914A8590D31508C8
+ proof:pdf
+
+ xmp.iid:77568db9-71f9-43f7-85eb-4960f6a3a2ff
+ xmp.did:77568db9-71f9-43f7-85eb-4960f6a3a2ff
+ uuid:5D20892493BFDB11914A8590D31508C8
+ default
+
+
+
+
+ converted
+ from application/pdf to <unknown>
+
+
+ saved
+ xmp.iid:D27F11740720681191099C3B601C4548
+ 2008-04-17T14:19:15+05:30
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ converted
+ from application/pdf to <unknown>
+
+
+ converted
+ from application/pdf to <unknown>
+
+
+ saved
+ xmp.iid:F97F1174072068118D4ED246B3ADB1C6
+ 2008-05-15T16:23:06-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:FA7F1174072068118D4ED246B3ADB1C6
+ 2008-05-15T17:10:45-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:EF7F117407206811A46CA4519D24356B
+ 2008-05-15T22:53:33-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:F07F117407206811A46CA4519D24356B
+ 2008-05-15T23:07:07-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:F77F117407206811BDDDFD38D0CF24DD
+ 2008-05-16T10:35:43-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ converted
+ from application/pdf to <unknown>
+
+
+ saved
+ xmp.iid:F97F117407206811BDDDFD38D0CF24DD
+ 2008-05-16T10:40:59-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ converted
+ from application/vnd.adobe.illustrator to <unknown>
+
+
+ saved
+ xmp.iid:FA7F117407206811BDDDFD38D0CF24DD
+ 2008-05-16T11:26:55-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:FB7F117407206811BDDDFD38D0CF24DD
+ 2008-05-16T11:29:01-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:FC7F117407206811BDDDFD38D0CF24DD
+ 2008-05-16T11:29:20-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:FD7F117407206811BDDDFD38D0CF24DD
+ 2008-05-16T11:30:54-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:FE7F117407206811BDDDFD38D0CF24DD
+ 2008-05-16T11:31:22-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:B233668C16206811BDDDFD38D0CF24DD
+ 2008-05-16T12:23:46-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:B333668C16206811BDDDFD38D0CF24DD
+ 2008-05-16T13:27:54-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:B433668C16206811BDDDFD38D0CF24DD
+ 2008-05-16T13:46:13-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:F77F11740720681197C1BF14D1759E83
+ 2008-05-16T15:47:57-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:F87F11740720681197C1BF14D1759E83
+ 2008-05-16T15:51:06-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:F97F11740720681197C1BF14D1759E83
+ 2008-05-16T15:52:22-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ converted
+ from application/vnd.adobe.illustrator to application/vnd.adobe.illustrator
+
+
+ saved
+ xmp.iid:FA7F117407206811B628E3BF27C8C41B
+ 2008-05-22T13:28:01-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ converted
+ from application/vnd.adobe.illustrator to application/vnd.adobe.illustrator
+
+
+ saved
+ xmp.iid:FF7F117407206811B628E3BF27C8C41B
+ 2008-05-22T16:23:53-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ converted
+ from application/vnd.adobe.illustrator to application/vnd.adobe.illustrator
+
+
+ saved
+ xmp.iid:07C3BD25102DDD1181B594070CEB88D9
+ 2008-05-28T16:45:26-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ converted
+ from application/vnd.adobe.illustrator to application/vnd.adobe.illustrator
+
+
+ saved
+ xmp.iid:F87F1174072068119098B097FDA39BEF
+ 2008-06-02T13:25:25-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:F77F117407206811BB1DBF8F242B6F84
+ 2008-06-09T14:58:36-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:F97F117407206811ACAFB8DA80854E76
+ 2008-06-11T14:31:27-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:0180117407206811834383CD3A8D2303
+ 2008-06-11T22:37:35-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:F77F117407206811818C85DF6A1A75C3
+ 2008-06-27T14:40:42-07:00
+ Adobe Illustrator CS4
+
+
+ /
+
+
+
+
+ saved
+ xmp.iid:00801174072068118F62EC69E7D04E31
+ 2010-03-30T08:48:15-06:00
+ Adobe Illustrator CS4
+ /
+
+
+ saved
+ xmp.iid:26665edd-e4a0-4a03-aa39-7964ff2d7809
+ 2022-09-06T15:17:39-04:00
+ Adobe Illustrator 24.3 (Macintosh)
+ /
+
+
+
+ Document
+ Print
+ AIRobin
+ False
+ False
+ 1
+
+ 500.000000
+ 300.000000
+ Pixels
+
+
+
+ Cyan
+ Magenta
+ Yellow
+ Black
+
+
+
+
+
+ Default Swatch Group
+ 0
+
+
+
+ White
+ RGB
+ PROCESS
+ 255
+ 255
+ 255
+
+
+ Black
+ RGB
+ PROCESS
+ 35
+ 31
+ 32
+
+
+ CMYK Red
+ RGB
+ PROCESS
+ 236
+ 28
+ 36
+
+
+ CMYK Yellow
+ RGB
+ PROCESS
+ 255
+ 241
+ 0
+
+
+ CMYK Green
+ RGB
+ PROCESS
+ 0
+ 165
+ 81
+
+
+ CMYK Cyan
+ RGB
+ PROCESS
+ 0
+ 173
+ 238
+
+
+ CMYK Blue
+ RGB
+ PROCESS
+ 46
+ 49
+ 145
+
+
+ CMYK Magenta
+ RGB
+ PROCESS
+ 235
+ 0
+ 139
+
+
+ C=15 M=100 Y=90 K=10
+ RGB
+ PROCESS
+ 190
+ 30
+ 45
+
+
+ C=0 M=90 Y=85 K=0
+ RGB
+ PROCESS
+ 238
+ 64
+ 54
+
+
+ C=0 M=80 Y=95 K=0
+ RGB
+ PROCESS
+ 240
+ 90
+ 40
+
+
+ C=0 M=50 Y=100 K=0
+ RGB
+ PROCESS
+ 246
+ 146
+ 30
+
+
+ C=0 M=35 Y=85 K=0
+ RGB
+ PROCESS
+ 250
+ 175
+ 64
+
+
+ C=5 M=0 Y=90 K=0
+ RGB
+ PROCESS
+ 249
+ 236
+ 49
+
+
+ C=20 M=0 Y=100 K=0
+ RGB
+ PROCESS
+ 214
+ 222
+ 35
+
+
+ C=50 M=0 Y=100 K=0
+ RGB
+ PROCESS
+ 139
+ 197
+ 63
+
+
+ C=75 M=0 Y=100 K=0
+ RGB
+ PROCESS
+ 55
+ 179
+ 74
+
+
+ C=85 M=10 Y=100 K=10
+ RGB
+ PROCESS
+ 0
+ 147
+ 69
+
+
+ C=90 M=30 Y=95 K=30
+ RGB
+ PROCESS
+ 0
+ 104
+ 56
+
+
+ C=75 M=0 Y=75 K=0
+ RGB
+ PROCESS
+ 41
+ 180
+ 115
+
+
+ C=80 M=10 Y=45 K=0
+ RGB
+ PROCESS
+ 0
+ 166
+ 156
+
+
+ C=70 M=15 Y=0 K=0
+ RGB
+ PROCESS
+ 38
+ 169
+ 224
+
+
+ C=85 M=50 Y=0 K=0
+ RGB
+ PROCESS
+ 27
+ 117
+ 187
+
+
+ C=100 M=95 Y=5 K=0
+ RGB
+ PROCESS
+ 43
+ 56
+ 143
+
+
+ C=100 M=100 Y=25 K=25
+ RGB
+ PROCESS
+ 38
+ 34
+ 97
+
+
+ C=75 M=100 Y=0 K=0
+ RGB
+ PROCESS
+ 101
+ 45
+ 144
+
+
+ C=50 M=100 Y=0 K=0
+ RGB
+ PROCESS
+ 144
+ 39
+ 142
+
+
+ C=35 M=100 Y=35 K=10
+ RGB
+ PROCESS
+ 158
+ 31
+ 99
+
+
+ C=10 M=100 Y=50 K=0
+ RGB
+ PROCESS
+ 217
+ 28
+ 92
+
+
+ C=0 M=95 Y=20 K=0
+ RGB
+ PROCESS
+ 236
+ 41
+ 123
+
+
+ C=25 M=25 Y=40 K=0
+ RGB
+ PROCESS
+ 193
+ 180
+ 154
+
+
+ C=40 M=45 Y=50 K=5
+ RGB
+ PROCESS
+ 154
+ 132
+ 121
+
+
+ C=50 M=50 Y=60 K=25
+ RGB
+ PROCESS
+ 113
+ 101
+ 88
+
+
+ C=55 M=60 Y=65 K=40
+ RGB
+ PROCESS
+ 90
+ 74
+ 66
+
+
+ C=25 M=40 Y=65 K=0
+ RGB
+ PROCESS
+ 195
+ 153
+ 107
+
+
+ C=30 M=50 Y=75 K=10
+ RGB
+ PROCESS
+ 168
+ 124
+ 79
+
+
+ C=35 M=60 Y=80 K=25
+ RGB
+ PROCESS
+ 138
+ 93
+ 59
+
+
+ C=40 M=65 Y=90 K=35
+ RGB
+ PROCESS
+ 117
+ 76
+ 40
+
+
+ C=40 M=70 Y=100 K=50
+ RGB
+ PROCESS
+ 96
+ 56
+ 19
+
+
+ C=50 M=70 Y=80 K=70
+ RGB
+ PROCESS
+ 59
+ 35
+ 20
+
+
+ R=245 G=187 B=58
+ PROCESS
+ 100.000000
+ RGB
+ 245
+ 187
+ 58
+
+
+ R=236 G=123 B=49
+ PROCESS
+ 100.000000
+ RGB
+ 236
+ 123
+ 49
+
+
+ R=213 G=86 B=43
+ PROCESS
+ 100.000000
+ RGB
+ 213
+ 86
+ 43
+
+
+ R=127 G=214 B=246
+ PROCESS
+ 100.000000
+ RGB
+ 127
+ 214
+ 246
+
+
+ R=191 G=234 B=251
+ PROCESS
+ 100.000000
+ RGB
+ 191
+ 234
+ 251
+
+
+
+
+
+ Grays
+ 1
+
+
+
+ C=0 M=0 Y=0 K=100
+ RGB
+ PROCESS
+ 35
+ 31
+ 32
+
+
+ C=0 M=0 Y=0 K=90
+ RGB
+ PROCESS
+ 64
+ 64
+ 65
+
+
+ C=0 M=0 Y=0 K=80
+ RGB
+ PROCESS
+ 88
+ 89
+ 91
+
+
+ C=0 M=0 Y=0 K=70
+ RGB
+ PROCESS
+ 109
+ 110
+ 112
+
+
+ C=0 M=0 Y=0 K=60
+ RGB
+ PROCESS
+ 128
+ 129
+ 132
+
+
+ C=0 M=0 Y=0 K=50
+ RGB
+ PROCESS
+ 146
+ 148
+ 151
+
+
+ C=0 M=0 Y=0 K=40
+ RGB
+ PROCESS
+ 166
+ 168
+ 171
+
+
+ C=0 M=0 Y=0 K=30
+ RGB
+ PROCESS
+ 187
+ 189
+ 191
+
+
+ C=0 M=0 Y=0 K=20
+ RGB
+ PROCESS
+ 208
+ 210
+ 211
+
+
+ C=0 M=0 Y=0 K=10
+ RGB
+ PROCESS
+ 230
+ 231
+ 232
+
+
+ C=0 M=0 Y=0 K=5
+ RGB
+ PROCESS
+ 241
+ 241
+ 242
+
+
+
+
+
+ Brights
+ 1
+
+
+
+ C=0 M=100 Y=100 K=0
+ RGB
+ PROCESS
+ 236
+ 28
+ 36
+
+
+ C=0 M=75 Y=100 K=0
+ RGB
+ PROCESS
+ 241
+ 101
+ 34
+
+
+ C=0 M=10 Y=95 K=0
+ RGB
+ PROCESS
+ 255
+ 221
+ 21
+
+
+ C=85 M=10 Y=100 K=0
+ RGB
+ PROCESS
+ 0
+ 161
+ 75
+
+
+ C=100 M=90 Y=0 K=0
+ RGB
+ PROCESS
+ 34
+ 64
+ 153
+
+
+ C=60 M=90 Y=0 K=0
+ RGB
+ PROCESS
+ 127
+ 63
+ 151
+
+
+
+
+
+
+ Adobe PDF library 16.07
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
endstream
endobj
3 0 obj
<>
endobj
5 0 obj
<>/Resources<>/ExtGState<>/Properties<>>>/Thumb 27 0 R/TrimBox[0.0 0.0 500.0 300.0]/Type/Page>>
endobj
24 0 obj
<>stream
+H‰´WÍŽ$·
¾÷SÔ´V"EJºfläd†y€†<œ} ßG©ºª»Ç“݃±Øé"«Ä~¤¾üãmûòÓ[ÞþöÃÛvùã’7Íy3ü¿òá¿¿^þ¹ýçòå헼ݾn9ùèøÛ¥â¯ä¾m_oxýw¼þ××Ë[Ù2þ•MJçV¶>’>¶Ûû…oÞ/ת©¨mꩱ]Knxh›–Ô‚®5µáx(Iºm·>©©Vݤ'¯ÃFªÝIÊ U;>—”
z.¿]~¾ÀB¨ÈIµáoñ1í<ì3ˆPÓŒ°³ö¹'¡Ð+~¿xIpãZÜ5Ðÿ^
+†PAíBCž¸¥QÚ&Â/ûI¼&/i(Å™¥Œ÷×6ݸËg¨C=TŸe—Q“–ë›%órQ{§ýWëi xWï°–q¸¶]
ö‡JáŸZžu
+wiP^‘8-vÖÙ"¤&Q>²ÏS=‰gP2L~q‰qª&[1bäñç‘!t",hrjÐtrÁi}CĨ©½(q$[JmÍrV¢=©ÛVYt^zGËH½9*ª¡Wx‚òè,•ÝŒ^üÞ#ý3òH:2Ò‡RïßÍ0‡ó}ÚIÈ „Å™Å84d°Œ¨ž–Z¦•°)Ê ]0¢X{;S¨ô5–zï
ÁJ?ƒRël)§˜kI™»K^*ˆ¢|EÖ ƒzÑ +hc(`°±¹Sÿö"ˆ g߀è†QN&IbCGÐóÑîšUNä‹ÇÅjÊn*rÞ"TŽF[?À·ßhW¥]%
+GûGvåR)càS„äTœxÓ§a Š¤#¸ØêAî!¨@•
+*î¹Ï ‡j•
jØC`ÅÔ;Ú®lõõ¬‰yc0VšÚ`Þ‚`ÔdÀT$Øԧ͊^£¢Õae‹€ŒÔëKD:D2_l³Y¬Dþ“59ô4ß^-_zù(K˜˜nÏgHevfE¤àmoôyš;`5£o>І^+ôÈ({ðê«ÇÈÈ•øô 훼8åøÚ (ïÒêg>Ì‘!+òZ? þF™ƒ¬Œ§cô ⧮؆ÏÑŸ‹‚º•Ä‘BÆ™ ˜ƒšfÙc÷M‘Ç;åW“è)£ªx¤m x: ub .äÁ=èÂO†ì
+¤01«q·KÞé4Âœ‡CôÌûÎ@ÊkäšÓHCNÆA²à¼î4ñ‹ˆÇNO´&,©`›€‰`9,wøà¯8 o°œ,Ó]‚/' Ðãßž±ý25ÏñnÜ] ª;1ŶAváèÍ\Ûõ|Cߟ¯šÍf9=.ï_kK† Ëh~-ûã&Ð{0¯u:X´Z‹.)³W?à‡û[™ý|Š±º #
+Åš¤m,†bá £W‹®˜› ª–=H.Ñ<ˆ—:³€©±7 ƒ"QT&;§qò?pro§ y¡aJž€È©{åäžSl6LçPÃl]†ß8ìŠN(}®æŽRV
+¼u9ã~’Ï„ŠØq Ô•EÔ‚-á=p)ôsà{¹?Ó´ùÝÎ`†˜6V< f„(ñ¹ü_z”ѣ氌ØáâvQäAï´ºªH‡ÇÒG¹•ó5èÍC¶èjî;LÝý“k0}‹F!¦Åþ/)Åc^pòs«¾0ïž
+46Ñÿnœ…¨&CÄ<ÐIàò¤Q·Š¢m‹†uR&Ž`îDq…¥¨áòLË*œ%Ѧ;YÀÎGi§¤‰D+¶U•¥°»• ø°¾cà_… ÕHo9ÈÈ€Öã5ñ,„9>î.-Êƨ¸E•ZYº£~ñ†Kxaê‰æŒ‹Q×íçµT÷)`#±ÁÇ)édÀ*$DÎHžc|{.ogÜúGà‘Ï @À}¥¹•›h
µ¤j -¼±+ q³ -*Œ·,=°NͱsÐØ ®¸}þñ`Þžð„:Œý…ƒ¬±¯lµ<÷kbï 0m]$ç¥ìäÁ9=jš‡ÆXŒá–raRyûœÒå.´ˆp%`ô¹,•C4|*cÑœIgÚçK}ÐÙE#܃N
k5°S#±=sr»Ü9‡_Ú¢^9Ú[ì_yŤ³¸‰¾eR3¸EÕçØ$ØWÂÛh‰žÇVš#x2 +OÎ…uÞ ‚:Î}qœBO{8ù¶äª®EÏ[›šû+%enQgaºµ½5ÊøÞQöÊq÷}ðþçèn't·gt·Oѽ>£ûx@w}@÷òïò×Ã{;û}ïõx—3¼—3¾û'øÞð½=ã{ûßë3¾—€ïßðõàóø¿ßŸ¾?#|ÿáÛ—ïFxÜ<¸—³/aõ Þ©-À§jrî;mè¬úJçÓ3MmÝÏ!ØÙ™¹6afçC)''?>g÷/_9‡Æ‹Ã+ã°r?óÂùÓ¥¯Åå0×|<Ü
È”ÙámM›;®À¢+W}k6a1šª÷ƒDÛõ>ë`q „¥Ì&´:¢Ô:ÇGf©O4QosÍ [æÊ5î„ô}Q·‹!íNódT´ÍDÆ>ûCÃ
@žÇ-f(ê¹ÈAÞG×çÁ :
+1Fè´Â‡e20©·@“:$ÜYµzƈ¦ü|@ä;¯¥6$¯™ Å™ÈÉÐزÿíB¤„7XZN+‚ P¾Sl.c´v†XØÉ£ŒJl1r¸Üà-„÷5å€Qlx1ϵ:ÂFlQÈĈãý5m&pópL)ÀèÐ)½- ÅÜ-¨=j–XÐfÈÃÈE£8,"wç0êPÞ`L=_©©u]©FÄŒoR¿Góçgî
+•„qؽœ¦ 丕°K`¡O x„eÖ°vqÓA
³@XøðŠiAÝ×MjØÄPË‹};Y4ÂÄýÇœË+JÂhð±z8Šü
î¨[…«h]6¹)^j8 ïªidW_'„yÃß/˜z³‹†GìO9hUÀÔ†mÄ ë\¶
+1€Œ&2-°µö¾
+<‡~AØj)¿¨»³û[4•ÓIsl ;Ý£1”›Œýâ
+ƒÜ'€(´ÂÞîíNF´2G·³ßwÆ„¶zÛðó? çø
endstream
endobj
27 0 obj
<>stream
+8;Z\r0lFl_$q8W?4bu$g(W^$RaX>.I3ogA,Ub86JYWr2L]mqK4s#MGT`E#nf:7\L@
+#ss`Un&K>R=C[?*L@>'ClJBPibN+YL%*]EqpQ6K5Bn>'?>hKY7Rh.nB4>USZ%(Kh*
+M!MEqmmm(pG?"6I,"qP=hk*4XJsB/UA[IP"iKp"jo>$1$Yg4=1&fZBA@:EO.S^4nt29nU&aZA5`5@\3C0oK7
endstream
endobj
28 0 obj
[/Indexed/DeviceRGB 255 29 0 R]
endobj
29 0 obj
<>stream
+8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0
+b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup`
+E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn
+6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O(
+l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~>
endstream
endobj
22 0 obj
<>
endobj
30 0 obj
[/View/Design]
endobj
31 0 obj
<>>>
endobj
26 0 obj
<>
endobj
25 0 obj
[/ICCBased 32 0 R]
endobj
32 0 obj
<>stream
+H‰œ–yTSwÇoÉž•°Ãc
[€°5la‘QIBHØADED„ª•2ÖmtFOE.®cÖ}êÒõ0êè8´×Ž8GNg¦Óïï÷9÷wïïÝß½÷ó '¥ªµÕ0 Ö ÏJŒÅb¤
+ 2y.-;!à’ÆK°ZÜ ü‹ž^i½"LÊÀ0ðÿ‰-×é
@8(”µrœ;q®ª7èLöœy¥•&†Qëñq¶4±jž½ç|æ9ÚÄ
+V³)gB£0ñiœWו8#©8wÕ©•õ8_Å٥ʨQãüÜ«QÊj@é&»A)/ÇÙgº>'K‚ó ÈtÕ;\ú”
Ó¥$ÕºF½ZUnÀÜå˜(4TŒ%)ë«”ƒ0C&¯”阤Z£“i˜¿óœ8¦Úbx‘ƒE¡ÁÁBÑ;…ú¯›¿P¦ÞÎӓ̹žAüom?çW=
+€x¯Íú·¶Ò- Œ¯Àòæ[›Ëû 0ñ¾¾øÎ}ø¦y)7ta¾¾õõõ>j¥ÜÇTÐ7úŸ¿@ï¼ÏÇtÜ›ò`qÊ2™±Ê€™ê&¯®ª6ê±ZL®Ä„?â_øóyxg)Ë”z¥ÈçLUáíÖ*ÔuµSkÿSeØO4?׸¸c¯¯Ø°.ò ò· åÒ R´
ßÞô-•’2ð5ßáÞüÜÏ ú÷Sá>Ó£Vš‹“då`r£¾n~ÏôY &à+`œ;ÂA4ˆÉ 䀰ÈA9Ð =¨- t°lÃ`;»Á~pŒƒÁ ðGp| ®[`Lƒ‡`<¯ "AˆYA+äùCb(Š‡R¡,¨ *T2B-Ð
+¨ê‡†¡Ðnè÷ÐQètº}MA ï —0Óal»Á¾°ŽSàx ¬‚kà&¸^Á£ð>ø0|>_ƒ'á‡ð,ÂG!"F$H:Rˆ”!z¤éF‘Qd?r9‹\A&‘GÈ”ˆrQ¢áhš‹ÊÑ´íE‡Ñ]èaô4zBgÐ×Á–àE#H ‹*B=¡‹0HØIøˆp†p0MxJ$ùD1„˜D, V›‰½ÄÄÄãÄKÄ»ÄY‰dEò"EÒI2’ÔEÚBÚGúŒt™4MzN¦‘Èþär!YKî ’÷?%_&ß#¿¢°(®”0J:EAi¤ôQÆ(Ç()Ó”WT6U@ æP+¨íÔ!ê~êêmêæD¥eÒÔ´å´!ÚïhŸÓ¦h/èº']B/¢éëèÒÓ¿¢?a0nŒhF!ÃÀXÇØÍ8ÅøšñÜŒkæc&5S˜µ™˜6»lö˜Iaº2c˜K™MÌAæ!æEæ#…åÆ’°d¬VÖë(ëk–Íe‹Øél
»—½‡}Ž}ŸCâ¸qâ9
+N'çÎ)Î].ÂuæJ¸rî
+î÷wšGä xR^¯‡÷[ÞoÆœchžgÞ`>bþ‰ù$á»ñ¥ü*~ÿ ÿ:ÿ¥…EŒ…ÒbÅ~‹ËÏ,m,£-•–Ý–,¯Y¾´Â¬â*6X[ݱF=3ë·YŸ±~dó ·‘ÛtÛ´¹iÛzÚfÙ6Û~`{ÁvÖÎÞ.ÑNg·Åî”Ý#{¾}´}…ý€ý§ö¸‘j‡‡ÏþŠ™c1X6„Æfm“Ž;'_9 œr:œ8Ýq¦:‹ËœœO:ϸ8¸¤¹´¸ìu¹éJq»–»nv=ëúÌMà–ï¶ÊmÜí¾ÀR 4 ö
+n»3Ü£ÜkÜGݯz=Ä•[=¾ô„=ƒ<Ë=GTB(É/ÙSòƒ,]6*›-•–¾W:#—È7Ë*¢ŠÊe¿ò^YDYÙ}U„j£êAyTù`ù#µD=¬þ¶"©b{ųÊôÊ+¬Ê¯: !kJ4Gµm¥ötµ}uCõ%—®K7YV³©fFŸ¢ßYÕ.©=bàá?SŒîÆ•Æ©ºÈº‘ºçõyõ‡Ø
Ú†žkï5%4ý¦m–7Ÿlqlio™Z³lG+ÔZÚz²Í¹³mzyâò]íÔöÊö?uøuôw|¿"űN»ÎåwW&®ÜÛe֥ﺱ*|ÕöÕèjõê‰5k¶¬yÝèþ¢Ç¯g°ç‡^yïkEk‡Öþ¸®lÝD_p߶õÄõÚõ×7DmØÕÏîoê¿»1mãál {àûMÅ›Î
nßLÝlÜ<9”úO ¤[þ˜¸™$™™üšhšÕ›B›¯œœ‰œ÷dÒž@ž®ŸŸ‹Ÿú i Ø¡G¡¶¢&¢–££v£æ¤V¤Ç¥8¥©¦¦‹¦ý§n§à¨R¨Ä©7©©ªª««u«é¬\¬ÐD¸®-®¡¯¯‹° °u°ê±`±Ö²K²Â³8³®´%´œµµŠ¶¶y¶ð·h·à¸Y¸Ñ¹J¹Âº;ºµ».»§¼!¼›½½¾
+¾„¾ÿ¿z¿õÀpÀìÁgÁãÂ_ÂÛÃXÃÔÄQÄÎÅKÅÈÆFÆÃÇAÇ¿È=ȼÉ:ɹÊ8Ê·Ë6˶Ì5̵Í5͵Î6ζÏ7ϸÐ9кÑ<ѾÒ?ÒÁÓDÓÆÔIÔËÕNÕÑÖUÖØ×\×àØdØèÙlÙñÚvÚûÛ€ÜÜŠÝÝ–ÞÞ¢ß)߯à6à½áDáÌâSâÛãcãëäsäüå„æ
æ–çç©è2è¼éFéÐê[êåëpëûì†ííœî(î´ï@ïÌðXðåñrñÿòŒóó§ô4ôÂõPõÞömöû÷Šøø¨ù8ùÇúWúçûwüü˜ý)ýºþKþÜÿmÿÿ ÷„óû
endstream
endobj
7 0 obj
<>
endobj
16 0 obj
<>
endobj
17 0 obj
<>stream
+%!PS-Adobe-3.0
%%Creator: Adobe Illustrator(R) 24.0
%%AI8_CreatorVersion: 26.5.0
%%For: (Kristina Davis) ()
%%Title: (PyomoDOE.ai)
%%CreationDate: 9/15/22 3:11 PM
%%Canvassize: 16383
%%BoundingBox: 59 70 397 221
%%HiResBoundingBox: 59.4353000000001 70.9548995583682 396.623100000001 220.928
%%DocumentProcessColors: Cyan Magenta Yellow Black
%AI5_FileFormat 14.0
%AI12_BuildNumber: 223
%AI3_ColorUsage: Color
%AI7_ImageSettings: 0
%%RGBProcessColor: 0.498039215803146 0.839215695858002 0.964705884456635 (R=127 G=214 B=246)
%%+ 0.749019622802734 0.917647063732147 0.984313726425171 (R=191 G=234 B=251)
%%+ 0.835294127464294 0.337254911661148 0.168627455830574 (R=213 G=86 B=43)
%%+ 0.925490200519562 0.482352942228317 0.192156866192818 (R=236 G=123 B=49)
%%+ 0.960784316062927 0.733333349227905 0.227450981736183 (R=245 G=187 B=58)
%%+ 0 0 0 ([Registration])
%AI3_Cropmarks: 0 0 500 300
%AI3_TemplateBox: 250.5 149.5 250.5 149.5
%AI3_TileBox: -38 -206 538 528
%AI3_DocumentPreview: None
%AI5_ArtSize: 14400 14400
%AI5_RulerUnits: 6
%AI24_LargeCanvasScale: 1
%AI9_ColorModel: 1
%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0
%AI5_TargetResolution: 800
%AI5_NumLayers: 1
%AI17_Begin_Content_if_version_gt:24 4
%AI10_OpenToVie: -1213.02976175998 764.38503815438 0.751786916543069 0 8033.52634516619 8344.11967269269 2547 1303 18 1 0 6 45 0 0 0 1 1 0 1 1 0 1
%AI17_Alternate_Content
%AI9_OpenToView: -1213.02976175998 764.38503815438 0.751786916543069 2547 1303 18 1 0 6 45 0 0 0 1 1 0 1 1 0 1
%AI17_End_Versioned_Content
%AI5_OpenViewLayers: 7
%AI17_Begin_Content_if_version_gt:24 4
%AI17_Alternate_Content
%AI17_End_Versioned_Content
%%PageOrigin:0 0
%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142
%AI9_Flatten: 1
%AI12_CMSettings: 00.MS
%%EndComments
endstream
endobj
18 0 obj
<>stream
+%AI24_ZStandard_Data(µ/ý Xü¼
+@®T.ÐŒhҊ꼸 ¤èØë Žôå%×DÄï†'’ì&ÛàSu>(K=Œš¥ÞgùÔDI$FÒ£?o¾|‘ÿ÷ïçß×ÏÇ{ûäÉ/Nܽ]=ýÜ<5êÓ¦K“ööîæ¾6Ã… :[–ô2®!îÝ^%Mj›4™Ò¬«•Š—N!›‰’b(‰aÁÐr Þ‰7~¼'·¿ÇÏ×ßçï÷ÿ'_ÞüyôéA’DÉ&Mœ\¸0ággæeådäcã´hÍš-û뻫››{k›)R£E‰¾Öï©):Y’prjf"B„
$èظȈˆH=Z´ˆ¾ºº¶²®ªª¦¢š¦L‰ò¤I“%ß³sS3óÒ2aB„
,HªÑ‘ºã׌vÈ’!cÅŠ¡¨ÇÆÆbd\\TTLL