drto 0.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- drto-0.0.0/.githooks/README.md +25 -0
- drto-0.0.0/.githooks/pre-commit +19 -0
- drto-0.0.0/.github/workflows/ci.yml +56 -0
- drto-0.0.0/.github/workflows/publish.yml +50 -0
- drto-0.0.0/.gitignore +29 -0
- drto-0.0.0/AGENTS.md +81 -0
- drto-0.0.0/CHANGELOG.md +18 -0
- drto-0.0.0/CITATION.cff +22 -0
- drto-0.0.0/CLAUDE.md +3 -0
- drto-0.0.0/CONTRIBUTING.md +40 -0
- drto-0.0.0/DESIGN.md +251 -0
- drto-0.0.0/LICENSE +28 -0
- drto-0.0.0/Makefile +41 -0
- drto-0.0.0/PKG-INFO +122 -0
- drto-0.0.0/README.md +95 -0
- drto-0.0.0/_typos.toml +10 -0
- drto-0.0.0/dev-notes/README.md +27 -0
- drto-0.0.0/dev-notes/research/roadmap.md +25 -0
- drto-0.0.0/pyproject.toml +51 -0
- drto-0.0.0/src/drto/__init__.py +16 -0
- drto-0.0.0/tests/test_import.py +8 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Git hooks
|
|
2
|
+
|
|
3
|
+
Tracked hooks for this repo. They are **not active until you opt in**:
|
|
4
|
+
`core.hooksPath` is local git config and does not travel with a clone.
|
|
5
|
+
|
|
6
|
+
Enable once per clone:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
git config core.hooksPath .githooks
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## `pre-commit`
|
|
13
|
+
|
|
14
|
+
Runs `black --check src/ tests/`, the same gate CI enforces, and rejects the
|
|
15
|
+
commit if any file is not black-clean. This keeps formatting drift from
|
|
16
|
+
reaching CI.
|
|
17
|
+
|
|
18
|
+
Fix a rejection with:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
make fmt
|
|
22
|
+
git add -u
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Bypass for a single commit with `git commit --no-verify` (discouraged).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Reject commits that are not black-clean, mirroring CI's `black --check`
|
|
3
|
+
# gate so formatting drift never reaches CI. Enable with:
|
|
4
|
+
#
|
|
5
|
+
# git config core.hooksPath .githooks
|
|
6
|
+
#
|
|
7
|
+
# Bypass for a single commit with `git commit --no-verify` (discouraged).
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
if ! command -v black >/dev/null 2>&1; then
|
|
11
|
+
echo "pre-commit: black not found on PATH; skipping format check" >&2
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
if ! black --check src/ tests/; then
|
|
16
|
+
echo >&2
|
|
17
|
+
echo "pre-commit: black check failed; run 'make fmt', re-stage, and commit again." >&2
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
lint:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v7
|
|
13
|
+
- uses: actions/setup-python@v6
|
|
14
|
+
with:
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
# Pinned so CI formatting matches what the code was formatted with.
|
|
17
|
+
- run: python -m pip install black==24.8.0
|
|
18
|
+
- run: black --check --diff src/ tests/
|
|
19
|
+
- uses: crate-ci/typos@v1.48.0
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v7
|
|
29
|
+
- uses: actions/setup-python@v6
|
|
30
|
+
with:
|
|
31
|
+
python-version: ${{ matrix.python-version }}
|
|
32
|
+
- run: python -m pip install -e ".[dev]"
|
|
33
|
+
- run: python -m pytest -q --cov=drto --cov-report=term-missing
|
|
34
|
+
|
|
35
|
+
import-base:
|
|
36
|
+
# The package must import with only its base (non-dev) dependencies.
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v7
|
|
40
|
+
- uses: actions/setup-python@v6
|
|
41
|
+
with:
|
|
42
|
+
python-version: "3.12"
|
|
43
|
+
- run: python -m pip install -e .
|
|
44
|
+
- run: python -c "import drto; print('drto', drto.__version__)"
|
|
45
|
+
|
|
46
|
+
min-deps:
|
|
47
|
+
# Exercise the declared floor: oldest supported Python and minimum Pyomo.
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v7
|
|
51
|
+
- uses: actions/setup-python@v6
|
|
52
|
+
with:
|
|
53
|
+
python-version: "3.10"
|
|
54
|
+
- run: python -m pip install -e ".[dev]"
|
|
55
|
+
- run: python -m pip install "pyomo==6.8.1"
|
|
56
|
+
- run: python -m pytest -q
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI on a version tag (e.g. `git tag v0.1.0 && git push --tags`).
|
|
4
|
+
# Uses PyPI Trusted Publishing (OIDC) -- no API token needed, but the project
|
|
5
|
+
# must be configured for trusted publishing on PyPI first (one-time setup).
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build-and-publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write # required for trusted publishing
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- uses: actions/setup-python@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- name: Build sdist and wheel
|
|
22
|
+
run: |
|
|
23
|
+
python -m pip install --upgrade build
|
|
24
|
+
python -m build
|
|
25
|
+
- name: Publish to PyPI
|
|
26
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
27
|
+
|
|
28
|
+
# Cut a GitHub Release for the same tag, with notes lifted from the matching
|
|
29
|
+
# CHANGELOG section, so the repo's Releases page tracks the PyPI versions.
|
|
30
|
+
github-release:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
permissions:
|
|
33
|
+
contents: write # required to create the release
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v7
|
|
36
|
+
- name: Extract release notes from CHANGELOG
|
|
37
|
+
run: |
|
|
38
|
+
version="${GITHUB_REF_NAME#v}"
|
|
39
|
+
awk -v ver="$version" 'BEGIN{s="## [" ver "]"} index($0,s)==1{f=1;next} f && index($0,"## [")==1{exit} f{print}' CHANGELOG.md | grep -vE '^\[[^]]+\]: ' | sed '/./,$!d' > release_notes.md
|
|
40
|
+
if [ ! -s release_notes.md ]; then
|
|
41
|
+
echo "${GITHUB_REF_NAME}. See CHANGELOG.md for details." > release_notes.md
|
|
42
|
+
fi
|
|
43
|
+
- name: Create GitHub Release
|
|
44
|
+
env:
|
|
45
|
+
GH_TOKEN: ${{ github.token }}
|
|
46
|
+
run: |
|
|
47
|
+
gh release create "$GITHUB_REF_NAME" \
|
|
48
|
+
--title "$GITHUB_REF_NAME" \
|
|
49
|
+
--notes-file release_notes.md \
|
|
50
|
+
--verify-tag
|
drto-0.0.0/.gitignore
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
|
|
12
|
+
# Coverage
|
|
13
|
+
.coverage
|
|
14
|
+
.coverage.*
|
|
15
|
+
htmlcov/
|
|
16
|
+
coverage.xml
|
|
17
|
+
|
|
18
|
+
# Environments
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
env/
|
|
22
|
+
|
|
23
|
+
# Notebooks
|
|
24
|
+
.ipynb_checkpoints/
|
|
25
|
+
|
|
26
|
+
# Editors / OS
|
|
27
|
+
.vscode/
|
|
28
|
+
.idea/
|
|
29
|
+
.DS_Store
|
drto-0.0.0/AGENTS.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Agent guide for drto
|
|
2
|
+
|
|
3
|
+
drto is a Python package: receding-horizon NMPC and moving horizon estimation
|
|
4
|
+
for `pyomo.dae` models. This file is the entry point for coding agents. Read
|
|
5
|
+
it before working in this repo.
|
|
6
|
+
|
|
7
|
+
## Status: design phase
|
|
8
|
+
|
|
9
|
+
There is no implementation yet. The design is settled and recorded:
|
|
10
|
+
|
|
11
|
+
- **DESIGN.md** is the authoritative design record: the six-mode framework,
|
|
12
|
+
the declaration surface, and every locked decision.
|
|
13
|
+
- **README.md** is the user-facing overview of the same.
|
|
14
|
+
|
|
15
|
+
Treat both as the source of truth. Before touching anything on the
|
|
16
|
+
declaration or API surface, read DESIGN.md. A decision logged there as
|
|
17
|
+
`USER DECISION <date>` is authoritative: do not silently reverse or
|
|
18
|
+
reinterpret it. If new work seems to require changing one, surface it and
|
|
19
|
+
ask, do not just diverge.
|
|
20
|
+
|
|
21
|
+
## Repo conventions
|
|
22
|
+
|
|
23
|
+
Canonical commands (they mirror CI, so local green means CI green; do not
|
|
24
|
+
hand-roll black or pytest flags):
|
|
25
|
+
|
|
26
|
+
- `make dev` -- editable install with dev extras.
|
|
27
|
+
- `make lint` -- `black --check` plus `typos`.
|
|
28
|
+
- `make test` -- `pytest` with coverage.
|
|
29
|
+
- `make check-imports` -- import drto with only base deps.
|
|
30
|
+
|
|
31
|
+
If `make` is not on PATH (a bare Windows shell), the targets are thin
|
|
32
|
+
wrappers: run `black --check --diff src/ tests/`, `typos`, and
|
|
33
|
+
`python -m pytest` directly. `make help` (or reading the Makefile) lists the
|
|
34
|
+
exact command behind each target.
|
|
35
|
+
|
|
36
|
+
This is a single pure-Python package that matches its siblings pyomo-cvp and
|
|
37
|
+
pyomo-cp. When adding a file, copy the shape of the nearest sibling rather
|
|
38
|
+
than inventing a new one.
|
|
39
|
+
|
|
40
|
+
- **License:** BSD-3-Clause. Every source file carries the two-line header
|
|
41
|
+
`# Copyright (c) 2026 Devin Griffith` /
|
|
42
|
+
`# SPDX-License-Identifier: BSD-3-Clause`.
|
|
43
|
+
- **Layout:** hatchling build, `src/drto/` package.
|
|
44
|
+
- **Formatting:** Black, line length 88, skip-string-normalization,
|
|
45
|
+
skip-magic-trailing-comma (Pyomo's own settings). Spell-check with `typos`.
|
|
46
|
+
- **Versioning:** Keep a Changelog plus SemVer in `CHANGELOG.md`.
|
|
47
|
+
- **Optional dependencies** go through
|
|
48
|
+
`pyomo.common.dependencies.attempt_import` so the package imports cleanly
|
|
49
|
+
when a backend (the pounce solver, pyomo-cvp) is absent. Prefer explicit
|
|
50
|
+
declaration over introspection throughout.
|
|
51
|
+
- **Do not defer tech debt:** fix deprecated deps, outdated action versions,
|
|
52
|
+
and floating refs in the same pass you notice them.
|
|
53
|
+
|
|
54
|
+
## Definition of done
|
|
55
|
+
|
|
56
|
+
A user-facing change is not done until code plus a pinning `pytest`, a bullet
|
|
57
|
+
under `## [Unreleased]` in `CHANGELOG.md`, and the relevant
|
|
58
|
+
README/docstring/notebook update all land in the same change. See
|
|
59
|
+
CONTRIBUTING.md.
|
|
60
|
+
|
|
61
|
+
## House style
|
|
62
|
+
|
|
63
|
+
No em dashes anywhere: code, comments, docs, commit messages, changelog.
|
|
64
|
+
Short plain sentences. Comments state present-tense constraints and
|
|
65
|
+
rationale, not development history. Design history lives in `dev-notes/` and
|
|
66
|
+
`DESIGN.md`, not in code comments.
|
|
67
|
+
|
|
68
|
+
## Intended module map (aspirational, grows from DESIGN.md)
|
|
69
|
+
|
|
70
|
+
Not built yet; recorded so the first code lands in the right shape:
|
|
71
|
+
|
|
72
|
+
- The declaration surface (`declare_state`, `declare_control`, the cost and
|
|
73
|
+
boundary declarations, the estimation declarations) is the public API.
|
|
74
|
+
- One receding-horizon loop underlies the six modes (steady-state / dynamic
|
|
75
|
+
by simulation / optimization / estimation); the ideal / real-time /
|
|
76
|
+
advanced-step execution variants are variants of dynamic optimization, not
|
|
77
|
+
separate modes.
|
|
78
|
+
- The sensitivity fast update rides on pyomo-pounce; control parameterization
|
|
79
|
+
on pyomo-cvp. Both are dependencies, not vendored.
|
|
80
|
+
|
|
81
|
+
Grow this into a "how to drive drto" table once there is a runnable loop.
|
drto-0.0.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and the project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.0.0] - 2026-07-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Repository scaffolding and the PyPI name reservation. Design phase: the
|
|
14
|
+
declaration framework and the six modes are recorded in DESIGN.md and the
|
|
15
|
+
README. No functionality yet.
|
|
16
|
+
|
|
17
|
+
[Unreleased]: https://github.com/devin-griff/drto/compare/v0.0.0...HEAD
|
|
18
|
+
[0.0.0]: https://github.com/devin-griff/drto/releases/tag/v0.0.0
|
drto-0.0.0/CITATION.cff
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use drto in academic work, please cite it as below."
|
|
3
|
+
title: "DRTO: A Unified Framework for Dynamic Real-Time Optimization"
|
|
4
|
+
abstract: >
|
|
5
|
+
DRTO is a framework that provides steady-state and dynamic estimation,
|
|
6
|
+
simulation, and optimization from a core Pyomo model, as well as the
|
|
7
|
+
moving-horizon machinery for implementations of dynamic real-time optimization.
|
|
8
|
+
type: software
|
|
9
|
+
license: BSD-3-Clause
|
|
10
|
+
repository-code: "https://github.com/devin-griff/drto"
|
|
11
|
+
url: "https://github.com/devin-griff/drto"
|
|
12
|
+
authors:
|
|
13
|
+
- family-names: Griffith
|
|
14
|
+
given-names: Devin
|
|
15
|
+
keywords:
|
|
16
|
+
- nmpc
|
|
17
|
+
- moving horizon estimation
|
|
18
|
+
- pyomo
|
|
19
|
+
- dynamic optimization
|
|
20
|
+
- receding horizon
|
|
21
|
+
# TODO: add ORCID, the Zenodo DOI, and date-released once the first tagged
|
|
22
|
+
# release is archived.
|
drto-0.0.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Contributing to drto
|
|
2
|
+
|
|
3
|
+
This file is about getting a change *merge-ready*. The agent-facing
|
|
4
|
+
conventions and the repo map live in [AGENTS.md](AGENTS.md).
|
|
5
|
+
|
|
6
|
+
## Enable the git hooks (one-time)
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
git config core.hooksPath .githooks
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The `pre-commit` hook runs `black --check`, mirroring CI so formatting drift
|
|
13
|
+
never reaches `main`.
|
|
14
|
+
|
|
15
|
+
## Definition of done for a user-facing change
|
|
16
|
+
|
|
17
|
+
A change that adds or changes user-visible behavior is not done until **all
|
|
18
|
+
three** of these land in the same PR:
|
|
19
|
+
|
|
20
|
+
1. **Code + test.** The behavior, with a `pytest` that pins it.
|
|
21
|
+
2. **CHANGELOG entry.** A bullet under the `## [Unreleased]` section of
|
|
22
|
+
`CHANGELOG.md`, in the user's terms. At release time the section is renamed
|
|
23
|
+
to the version and dated.
|
|
24
|
+
3. **Docs.** The relevant README, docstring, or example-notebook update, so
|
|
25
|
+
the feature is documented where a user looks.
|
|
26
|
+
|
|
27
|
+
## Run the CI guards locally before pushing
|
|
28
|
+
|
|
29
|
+
These mirror the CI jobs; run them for fast feedback:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
make lint # black --check + typos
|
|
33
|
+
make test # pytest with coverage
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## House style
|
|
37
|
+
|
|
38
|
+
No em dashes anywhere: code, comments, docs, commits, changelog. Short plain
|
|
39
|
+
sentences. Comments state present-tense rationale, not history; design history
|
|
40
|
+
lives in `dev-notes/` and `DESIGN.md`.
|
drto-0.0.0/DESIGN.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
## What it is
|
|
2
|
+
|
|
3
|
+
DRTO is a framework that provides steady-state and dynamic estimation,
|
|
4
|
+
simulation, and optimization from a core Pyomo model, as well as the
|
|
5
|
+
moving-horizon machinery for implementations of dynamic real-time optimization.
|
|
6
|
+
The intent is to apply to a broad range of model and problem types, with functionality
|
|
7
|
+
being built incrementally.
|
|
8
|
+
|
|
9
|
+
## The mode framework (six modes)
|
|
10
|
+
|
|
11
|
+
drto is one framework over one declared model, run in any of six modes:
|
|
12
|
+
the 2x3 grid of {steady-state, dynamic} by {simulation, optimization,
|
|
13
|
+
estimation}. The mode fixes what is free and what the objective is; the
|
|
14
|
+
model and (mostly) the declarations are shared.
|
|
15
|
+
|
|
16
|
+
| | Simulation | Optimization | Estimation |
|
|
17
|
+
| --- | --- | --- | --- |
|
|
18
|
+
| Steady-state | solve f(z,u)=0 for z at fixed u | economic RTO: optimize phi(z_ss, u_ss) | data reconciliation: fit z to steady data |
|
|
19
|
+
| Dynamic | integrate the ODE forward (IVP) | NMPC / D-RTO over the horizon | moving horizon estimation (MHE) |
|
|
20
|
+
|
|
21
|
+
- Columns are what the mode does with the degrees of freedom. Simulation
|
|
22
|
+
frees nothing and solves the square model. Optimization frees the
|
|
23
|
+
controls and adds a cost. Estimation frees the states (and parameters)
|
|
24
|
+
and fits them to measurements.
|
|
25
|
+
- Rows are the time treatment. Steady-state collapses the model to one
|
|
26
|
+
point with every dz/dt = 0. Dynamic keeps the horizon and the
|
|
27
|
+
discretized dynamics.
|
|
28
|
+
- The optimization and estimation columns are duals: one frees inputs to
|
|
29
|
+
minimize a cost, the other frees states to fit data. MHE is the dual of
|
|
30
|
+
NMPC, and steady-state reconciliation the dual of RTO. This is why one
|
|
31
|
+
declaration surface can cover both halves; the seams are the initial
|
|
32
|
+
anchor (a hard condition for control, a soft arrival cost for
|
|
33
|
+
estimation) and the measurement.
|
|
34
|
+
|
|
35
|
+
## Steady-state reduction (setpoint consistency and economic RTO)
|
|
36
|
+
|
|
37
|
+
A first-class feature: drto reduces the dynamic model to its
|
|
38
|
+
steady-state problem, the same equations with every dz/dt = 0 at a
|
|
39
|
+
single time point, solving f(z,u)=0 and g(z,u)=0 for an equilibrium
|
|
40
|
+
(z_ss, u_ss). Two uses:
|
|
41
|
+
|
|
42
|
+
1. **Setpoint consistency.** The tracking target is DERIVED from the
|
|
43
|
+
model, not hand-specified, so the state target and the control
|
|
44
|
+
target are a true fixed point of the dynamics by construction.
|
|
45
|
+
Motivating case (2026-07-13): a hand-typed Hicks CSTR tracking pair
|
|
46
|
+
(xss, uss) that was not an exact equilibrium: the control that
|
|
47
|
+
actually holds xss was v1 = 0.578, not the declared 0.583. The
|
|
48
|
+
controller could not zero both the state-tracking and the
|
|
49
|
+
control-tracking terms at once, so the controls never settled: they
|
|
50
|
+
drifted ~5e-4 at the tail hunting a compromise, at any horizon
|
|
51
|
+
(with a model-consistent uss the tail spread dropped ~100x to
|
|
52
|
+
4e-6). A model-derived setpoint removes the whole failure mode.
|
|
53
|
+
|
|
54
|
+
2. **Economic RTO.** The same reduced problem plus an economic
|
|
55
|
+
objective phi(z_ss, u_ss) over the steady-state variables, subject
|
|
56
|
+
to constraints: the RTO layer that computes the economically-
|
|
57
|
+
optimal operating point, which the NMPC then tracks (or uses as an
|
|
58
|
+
economic-NMPC terminal reference). This is what makes the D-RTO
|
|
59
|
+
name literal: steady-state RTO is the dz/dt -> 0 limit of the
|
|
60
|
+
dynamic problem, so the dynamic controller and its setpoint
|
|
61
|
+
optimizer are one package.
|
|
62
|
+
|
|
63
|
+
## Dependency stack
|
|
64
|
+
|
|
65
|
+
- pyomo + pyomo.dae for modeling and discretization.
|
|
66
|
+
- pyomo-cvp for piecewise-constant control parameterization
|
|
67
|
+
(`declare_profile`); the controls' declaration already lives there.
|
|
68
|
+
pyomo-cvp STAYS a standalone package (independently useful for
|
|
69
|
+
offline dynamic optimization, and a Pyomo upstream candidate); drto
|
|
70
|
+
depends on it and re-exports the declarations so users get one
|
|
71
|
+
import surface. Revisit only if the warm shift needs to reach inside
|
|
72
|
+
cvp's substitution machinery.
|
|
73
|
+
- pyomo-pounce (pounce >= 0.8.0) for the in-process solve session,
|
|
74
|
+
sensitivity gradients, and the `estimate()` fast update (merged as
|
|
75
|
+
jkitchin/pounce#199). The estimation-side machinery (covariance with
|
|
76
|
+
hessian="lagrangian"|"gauss-newton" and active-bound projection)
|
|
77
|
+
merged as #203 on 2026-07-12; it ships in the release after 0.8.0.
|
|
78
|
+
- pounce coupling is a hard dependency for v1: simpler and honest about
|
|
79
|
+
what works today. A sensitivity-backend interface (with k_aug as a
|
|
80
|
+
legacy alternative) was considered and deferred.
|
|
81
|
+
|
|
82
|
+
## Declarations
|
|
83
|
+
|
|
84
|
+
Explicit declarations throughout, matching the pyomo-cvp / pyomo-pounce
|
|
85
|
+
family. USER DECISION: no DerivativeVar introspection for states. The
|
|
86
|
+
divergence cases that killed auto-detection: quadrature/cost
|
|
87
|
+
accumulators carry DerivativeVars but are not plant states; spatial
|
|
88
|
+
derivatives are not temporal states; quasi-steady treatments; discrete
|
|
89
|
+
time or hand-discretized models have states but no DerivativeVars; and
|
|
90
|
+
no structure implies the measured vs unmeasured distinction MHE will
|
|
91
|
+
need.
|
|
92
|
+
|
|
93
|
+
Each declaration tags a Pyomo component the user already wrote, a Var, a
|
|
94
|
+
Constraint, or a Param (USER DECISION 2026-07-14): the point is to bolt
|
|
95
|
+
onto an existing pyomo.dae model, not to introduce a new modeling
|
|
96
|
+
framework. State and control tag Vars; the cost and boundary declarations
|
|
97
|
+
tag Constraints; the measurement and steady-state targets tag Params. The
|
|
98
|
+
control-scope declaration surface (the estimation-side surface follows
|
|
99
|
+
below):
|
|
100
|
+
|
|
101
|
+
- `declare_state(m.z1, m.z2, ...)`: tags the differential-state Vars. Varargs,
|
|
102
|
+
indexed-container-aware (one call declares all members). drto then picks
|
|
103
|
+
up each state's dynamics automatically from its DerivativeVar (USER
|
|
104
|
+
DECISION 2026-07-14, good-enough starting point; no `declare_dynamics`).
|
|
105
|
+
This is NOT the rejected state auto-detection: the state role is still
|
|
106
|
+
declared, and the DerivativeVar only locates the ODE of an
|
|
107
|
+
already-declared state.
|
|
108
|
+
- `declare_control(m.u, ..., wrt=m.t, profile=...)`: tags the
|
|
109
|
+
manipulated-input Vars, the free decision variables. The `profile` flag folds in the
|
|
110
|
+
control's parameterization: `declare_control` calls pyomo-cvp's
|
|
111
|
+
`declare_profile` automatically (USER DECISION 2026-07-14). One call
|
|
112
|
+
declares the control and its parameterization; cvp stays the dependency
|
|
113
|
+
that implements the parameterization underneath.
|
|
114
|
+
- `declare_tracking_stage_cost(m.tracking_stage_con)`: tags the equality
|
|
115
|
+
Constraint defining the setpoint-tracking running cost (the
|
|
116
|
+
||z - z_ss|| + ||u - u_ss|| regulation penalty). LHS a lone scalar Var;
|
|
117
|
+
drto adds it to the objective it assembles. The tracking targets are the
|
|
118
|
+
declared steady-state Params (`declare_steady_state` /
|
|
119
|
+
`declare_steady_state_control` below), populated by the steady-state/RTO
|
|
120
|
+
solve. The RHS is however the user expressed the accumulated cost (a
|
|
121
|
+
finite-element sum, a pyomo.dae Integral, an accumulator's terminal
|
|
122
|
+
value): drto does not care as long as it resolves to the LHS scalar.
|
|
123
|
+
- `declare_economic_stage_cost(m.economic_stage_con)`: tags the equality
|
|
124
|
+
Constraint defining the economic running cost phi(z, u). Same LHS-scalar
|
|
125
|
+
convention. This is the same economic objective the steady-state RTO
|
|
126
|
+
mode optimizes at its single point, so one declaration serves economic
|
|
127
|
+
NMPC and RTO both (economic NMPC itself is post-v1; RTO uses it in v1).
|
|
128
|
+
USER DECISION 2026-07-14: split the running cost into tracking and
|
|
129
|
+
economic terms so a mode selects which is live.
|
|
130
|
+
- `declare_tracking_terminal_cost(m.tracking_terminal_con)`: tags the
|
|
131
|
+
equality Constraint defining the terminal (Mayer) tracking cost
|
|
132
|
+
V_f(z(tN)), the terminal regulation penalty. Same LHS-scalar convention;
|
|
133
|
+
drto adds the terminal-cost Var to the objective. Dropped in
|
|
134
|
+
steady-state (see the objective note below). Renamed from
|
|
135
|
+
`declare_terminal_cost` for clarity against the economic term (USER
|
|
136
|
+
DECISION 2026-07-14).
|
|
137
|
+
- `declare_initial_condition(m.init_con)`: tags the equality Constraint
|
|
138
|
+
anchoring the initial state, LHS the anchored state at t0. If the RHS is
|
|
139
|
+
a mutable Param, that Param is the feedback-injection point drto updates
|
|
140
|
+
each step, so this convention doubles as the state-feedback hook (z_hat)
|
|
141
|
+
for the online loop. Named "condition" deliberately: it pins the state
|
|
142
|
+
(an equality), a different job from a boundary set. This is the exact
|
|
143
|
+
seam where MHE will diverge, since its initial anchor is the soft
|
|
144
|
+
arrival cost, not a hard equality, so a soft mode or a separate
|
|
145
|
+
`declare_arrival_cost` is the estimation follow-on, not this.
|
|
146
|
+
- `declare_terminal_constraint(m.terminal_con)`: tags the Constraint
|
|
147
|
+
(equality or inequality) defining the terminal set/region z(tN) in X_f.
|
|
148
|
+
Requirement (USER DECISION 2026-07-14): every Var it references is a
|
|
149
|
+
declared state at tN, the final time; a control at tN is excluded, the
|
|
150
|
+
standard OCP convention (X_f lives in state space). No LHS convention,
|
|
151
|
+
which is what separates it from a path constraint (present at every t).
|
|
152
|
+
"Constraint" not "condition" because it restricts to a set rather than
|
|
153
|
+
pinning a value.
|
|
154
|
+
- `declare_steady_state(m.z_ss, ...)`: tags the Params holding the
|
|
155
|
+
steady-state state target z_ss. The tracking costs drive toward these
|
|
156
|
+
(z - z_ss); the steady-state/RTO mode populates them from its solve (or
|
|
157
|
+
they are set directly, since they are Params), so the target is
|
|
158
|
+
model-derived rather than hand-typed (the Hicks CSTR lesson above).
|
|
159
|
+
- `declare_steady_state_control(m.u_ss, ...)`: tags the Params holding the
|
|
160
|
+
steady-state control target u_ss, driven toward the same way (u - u_ss)
|
|
161
|
+
and populated the same way (USER DECISION 2026-07-14).
|
|
162
|
+
|
|
163
|
+
Convention on the declared constraints (verified
|
|
164
|
+
against Pyomo 6.10):
|
|
165
|
+
|
|
166
|
+
- The cost and initial-condition constraints must be equalities; an
|
|
167
|
+
inequality is rejected with a clear error (a cost term or anchor written
|
|
168
|
+
as an inequality is a user mistake).
|
|
169
|
+
- Their LHS is the appropriate scalar, read as `con.expr.args[0]`. Pyomo
|
|
170
|
+
canonicalizes the constraint body to `LHS - RHS` (lower=upper=0), but
|
|
171
|
+
`con.expr` preserves the as-written relational expression, so the LHS is
|
|
172
|
+
a stable read and a misplaced scalar (a non-Var LHS) is detectable and
|
|
173
|
+
rejected. For a cost the scalar is the cost-term Var drto puts in the
|
|
174
|
+
objective; for the initial condition it is the anchored state at t0.
|
|
175
|
+
|
|
176
|
+
The objective is drto's, not the user's: it assembles `min` over the
|
|
177
|
+
declared cost-term Vars that are live in the current mode. Modes add or
|
|
178
|
+
drop terms by including or excluding a cost's (Var, defining constraint)
|
|
179
|
+
pair; in steady-state the reduced model never instantiates the terminal
|
|
180
|
+
cost, so that term is simply absent, nothing to zero-weight. This is the
|
|
181
|
+
practical payoff of representing each cost as a Var defined by a
|
|
182
|
+
constraint rather than a bare expression: the pair is one handle drto can
|
|
183
|
+
find and drop.
|
|
184
|
+
|
|
185
|
+
Naming: fully-written-out, not abbreviated
|
|
186
|
+
(`declare_terminal_cost` not `declare_term_cost`,
|
|
187
|
+
`declare_initial_condition` not `declare_init_con`). These are setup-time
|
|
188
|
+
calls, never hot-path, so brevity buys nothing; `con` is ambiguous
|
|
189
|
+
between condition and constraint, the exact distinction that matters; the
|
|
190
|
+
full forms are the OCP literature's own vocabulary, so a reader who knows
|
|
191
|
+
the theory maps straight on; and spelling them out forces the precise
|
|
192
|
+
concept (condition vs constraint vs set) rather than hiding behind an
|
|
193
|
+
abbreviation.
|
|
194
|
+
|
|
195
|
+
Not declared, by design:
|
|
196
|
+
|
|
197
|
+
- Path constraints: the states' upper and lower bounds, i.e. the Var
|
|
198
|
+
bounds, which drto reads off the model. Not
|
|
199
|
+
a separate declaration.
|
|
200
|
+
|
|
201
|
+
Moving-horizon data hooks now have homes: the state anchor z_hat is the
|
|
202
|
+
mutable Param on the RHS of the initial-condition constraint; the tracking
|
|
203
|
+
setpoint is the declared steady-state Params z_ss/u_ss; the measurements
|
|
204
|
+
are the mutable Param stream in `declare_measurement` below. Each is
|
|
205
|
+
updated each step. Dynamics source is decided too: picked up from each
|
|
206
|
+
state's DerivativeVar, above.
|
|
207
|
+
|
|
208
|
+
Shared conventions:
|
|
209
|
+
|
|
210
|
+
- Each declaration has an explicit call-time form as well (the
|
|
211
|
+
declared/explicit duality established in pyomo-cvp and pyomo-pounce).
|
|
212
|
+
- Family conventions locked in pounce#203: varargs on every declaration,
|
|
213
|
+
keyword options (e.g. `group=`) apply to every component in the call.
|
|
214
|
+
|
|
215
|
+
Estimation-side surface (surface designed now,
|
|
216
|
+
built with the MHE follow-on). MHE is the dual of the control problem, so
|
|
217
|
+
the same conventions hold (each tags a Var or a Constraint; cost
|
|
218
|
+
constraints are equalities with the scalar on the LHS; drto assembles the
|
|
219
|
+
estimation objective from the live cost-term Vars):
|
|
220
|
+
|
|
221
|
+
- `declare_estimated_parameter(m.theta, ...)`: tags the Vars for unknown
|
|
222
|
+
model parameters to estimate, constant over the window. Shared with the
|
|
223
|
+
steady-state data-reconciliation mode.
|
|
224
|
+
- `declare_disturbance(m.w, ...)`: tags the process-noise Vars w in
|
|
225
|
+
dz/dt = f + w, the free variables the estimator adjusts to reconcile the
|
|
226
|
+
model with the data, penalized by their inverse covariance in the
|
|
227
|
+
estimation stage cost. It is noise, not a manipulated input: unrelated to
|
|
228
|
+
`declare_control`, with no profile parameterization (USER DECISION
|
|
229
|
+
2026-07-14).
|
|
230
|
+
- `declare_measurement(m.y_meas, ...)`: tags the measurement Param(s), the
|
|
231
|
+
measured values y_meas that appear in the estimation cost residuals
|
|
232
|
+
(||y_meas - h(z)||). Like the z_hat feedback hook, it is a mutable Param
|
|
233
|
+
drto refreshes each step, here the incoming measurements over the window.
|
|
234
|
+
Nothing else to tag: h(z) is written inline in the cost, so there is no
|
|
235
|
+
output Var or defining constraint (USER DECISION 2026-07-14).
|
|
236
|
+
- `declare_estimation_stage_cost(m.est_stage_con)`: tags the equality
|
|
237
|
+
Constraint for the running estimation cost over the window, the
|
|
238
|
+
measurement residual ||y_meas - h(z)|| plus the process-noise penalty
|
|
239
|
+
||w||, weighted by inverse covariances. LHS-scalar convention.
|
|
240
|
+
- `declare_estimation_terminal_cost(m.est_terminal_con)`: tags the equality
|
|
241
|
+
Constraint for the current-time (window-present) term, the current-state
|
|
242
|
+
measurement residual ||y_meas(tN) - h(z(tN))|| with no process noise
|
|
243
|
+
(nothing leads out of the last point), which is why it is a distinct
|
|
244
|
+
terminal term rather than part of the stage sum. This IS a standard MHE
|
|
245
|
+
term (USER correction 2026-07-14).
|
|
246
|
+
- `declare_arrival_cost(m.arrival_con)`: tags the equality Constraint for
|
|
247
|
+
the soft prior on the window's initial state, ||z(t0) - z_prior||
|
|
248
|
+
weighted by the arrival-cost inverse covariance. The dual of the
|
|
249
|
+
control-side initial condition, but SOFT (a cost, not a hard equality).
|
|
250
|
+
Its weight is the piece the covariance propagation updates each step
|
|
251
|
+
(Gauss-Newton, the pounce#203 machinery in Follow-on).
|
drto-0.0.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Devin Griffith
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
drto-0.0.0/Makefile
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# drto -- Makefile wrapper around the Python toolchain.
|
|
2
|
+
# Each target mirrors one CI job, so local green means CI green.
|
|
3
|
+
#
|
|
4
|
+
# make help # list targets
|
|
5
|
+
# make dev # editable install with dev extras
|
|
6
|
+
# make fmt # format with black
|
|
7
|
+
# make fmt-check # check formatting (CI gate)
|
|
8
|
+
# make typos # spell-check with typos
|
|
9
|
+
# make lint # fmt-check + typos
|
|
10
|
+
# make test # run pytest with coverage
|
|
11
|
+
# make check-imports # import drto with only base deps present
|
|
12
|
+
# make min-deps # install the pyomo floor, then test
|
|
13
|
+
|
|
14
|
+
.PHONY: help dev fmt fmt-check typos lint test check-imports min-deps
|
|
15
|
+
|
|
16
|
+
help:
|
|
17
|
+
@sed -n 's/^# //p' Makefile
|
|
18
|
+
|
|
19
|
+
dev:
|
|
20
|
+
python -m pip install -e ".[dev]"
|
|
21
|
+
|
|
22
|
+
fmt:
|
|
23
|
+
black src/ tests/
|
|
24
|
+
|
|
25
|
+
fmt-check:
|
|
26
|
+
black --check --diff src/ tests/
|
|
27
|
+
|
|
28
|
+
typos:
|
|
29
|
+
typos
|
|
30
|
+
|
|
31
|
+
lint: fmt-check typos
|
|
32
|
+
|
|
33
|
+
test:
|
|
34
|
+
python -m pytest -q --cov=drto --cov-report=term-missing
|
|
35
|
+
|
|
36
|
+
check-imports:
|
|
37
|
+
python -c "import drto; print('drto', drto.__version__)"
|
|
38
|
+
|
|
39
|
+
min-deps:
|
|
40
|
+
python -m pip install "pyomo==6.8.1"
|
|
41
|
+
python -m pytest -q
|
drto-0.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: drto
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Dynamic real-time optimization: receding-horizon NMPC and moving horizon estimation for Pyomo models.
|
|
5
|
+
Project-URL: Homepage, https://github.com/devin-griff/drto
|
|
6
|
+
Project-URL: Repository, https://github.com/devin-griff/drto
|
|
7
|
+
Project-URL: Issues, https://github.com/devin-griff/drto/issues
|
|
8
|
+
Author-email: Devin Griffith <dwg176@gmail.com>
|
|
9
|
+
License-Expression: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: dae,dynamic-optimization,mhe,moving-horizon-estimation,nmpc,pyomo,receding-horizon
|
|
12
|
+
Classifier: Development Status :: 1 - Planning
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: pyomo>=6.8.1
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
22
|
+
Provides-Extra: examples
|
|
23
|
+
Requires-Dist: jupyter; extra == 'examples'
|
|
24
|
+
Requires-Dist: matplotlib; extra == 'examples'
|
|
25
|
+
Requires-Dist: numpy; extra == 'examples'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# drto
|
|
29
|
+
|
|
30
|
+
Dynamic real-time optimization: receding-horizon optimization and
|
|
31
|
+
estimation for Pyomo models, with advanced-step NMPC as the headline
|
|
32
|
+
capability and moving horizon estimation as the planned follow-on.
|
|
33
|
+
|
|
34
|
+
## The six modes
|
|
35
|
+
|
|
36
|
+
drto runs one declared model in any of six modes, the 2x3 grid of
|
|
37
|
+
{steady-state, dynamic} by {simulation, optimization, estimation}. You
|
|
38
|
+
write the model once; the mode fixes what is free and what the objective
|
|
39
|
+
is.
|
|
40
|
+
|
|
41
|
+
| | Simulation | Optimization | Estimation |
|
|
42
|
+
| --- | --- | --- | --- |
|
|
43
|
+
| **Steady-state** | solve the model at equilibrium | economic RTO | data reconciliation |
|
|
44
|
+
| **Dynamic** | integrate the model forward | NMPC / D-RTO | moving horizon estimation |
|
|
45
|
+
|
|
46
|
+
Down the columns: simulation frees nothing and solves the model as given;
|
|
47
|
+
optimization frees the controls and adds a cost; estimation frees the
|
|
48
|
+
states and fits them to measurements. Across the rows: steady-state
|
|
49
|
+
collapses the model to a single equilibrium point, dynamic keeps the time
|
|
50
|
+
horizon. The optimization and estimation columns are duals (NMPC with MHE,
|
|
51
|
+
RTO with reconciliation), so one declaration surface serves both.
|
|
52
|
+
|
|
53
|
+
The near-term focus is the optimization column: dynamic NMPC/D-RTO, whose
|
|
54
|
+
ideal, real-time, and advanced-step execution variants are the headline,
|
|
55
|
+
plus steady-state RTO. Estimation is the planned follow-on.
|
|
56
|
+
|
|
57
|
+
## Declaring a control problem
|
|
58
|
+
|
|
59
|
+
drto is declaration-first, and each declaration tags a Pyomo component you
|
|
60
|
+
already wrote: a Variable, a Constraint, or a Parameter. You build your dynamic model as
|
|
61
|
+
an ordinary `pyomo.dae` model, then point the declarations at its pieces;
|
|
62
|
+
drto assembles the horizon problem and runs the loop. It bolts onto an
|
|
63
|
+
existing model rather than replacing how you build one. The pieces are the
|
|
64
|
+
object types of an optimal control problem (the dynamic-optimization
|
|
65
|
+
mode):
|
|
66
|
+
|
|
67
|
+
| DRTO object type | Pyomo object type | Declaration | What it is |
|
|
68
|
+
| --- | --- | --- | --- |
|
|
69
|
+
| State | Variable | `declare_state(m.z, ...)` | A differential state. drto reads its dynamics from the state's `DerivativeVar`. |
|
|
70
|
+
| Control | Variable | `declare_control(m.u, ..., wrt=m.t, profile=...)` | A manipulated input, the decision variable. The `profile` flag sets its parameterization (piecewise-constant, ...) via pyomo-cvp. |
|
|
71
|
+
| Tracking stage cost | Constraint | `declare_tracking_stage_cost(m.tracking_stage_con)` | Equality defining the setpoint-tracking running cost; its left-hand-side scalar goes in the objective. The setpoint it references is the declared steady-state Param (below). |
|
|
72
|
+
| Economic stage cost | Constraint | `declare_economic_stage_cost(m.economic_stage_con)` | Equality defining the economic running cost; the same objective the steady-state RTO mode uses. |
|
|
73
|
+
| Tracking terminal cost | Constraint | `declare_tracking_terminal_cost(m.tracking_terminal_con)` | Equality defining the terminal tracking cost; its left-hand-side scalar goes in the objective. |
|
|
74
|
+
| Initial condition | Constraint | `declare_initial_condition(m.init_con)` | Equality anchoring the initial state; left-hand side is the state at t0, right-hand side the feedback. |
|
|
75
|
+
| Terminal constraint | Constraint | `declare_terminal_constraint(m.terminal_con)` | Constraint on the states at the final time; the terminal set the final state must lie in. |
|
|
76
|
+
| Steady-state target | Parameter | `declare_steady_state(m.z_ss)` | The state setpoint the tracking costs drive toward; populated by the steady-state/RTO solve. |
|
|
77
|
+
| Steady-state control target | Parameter | `declare_steady_state_control(m.u_ss)` | The control setpoint the tracking costs drive toward. |
|
|
78
|
+
|
|
79
|
+
Conventions drto enforces on those constraints: the cost and
|
|
80
|
+
initial-condition constraints are equalities whose left-hand side is the
|
|
81
|
+
scalar the declaration is about (the cost term, or the anchored state); a
|
|
82
|
+
terminal constraint may reference only states at the final time, which is
|
|
83
|
+
what separates it from a path constraint. The objective is drto's own: it
|
|
84
|
+
sums the declared cost terms that are live in the current mode, so a mode
|
|
85
|
+
drops a term just by leaving out its constraint.
|
|
86
|
+
|
|
87
|
+
Two things you never declare, because they already live in the model: the
|
|
88
|
+
**dynamics** are read from the `pyomo.dae` `DerivativeVar`s of the
|
|
89
|
+
declared states, and the **path constraints** are the state variables'
|
|
90
|
+
own upper and lower bounds.
|
|
91
|
+
|
|
92
|
+
The vocabulary is the optimal-control literature's own (stage cost,
|
|
93
|
+
terminal cost, terminal constraint), so a model reads the way the theory
|
|
94
|
+
does. The other modes reuse the same model: simulation drops the cost, and
|
|
95
|
+
estimation swaps the initial condition for a soft arrival cost and adds the
|
|
96
|
+
estimation pieces below.
|
|
97
|
+
|
|
98
|
+
## Declaring an estimation problem
|
|
99
|
+
|
|
100
|
+
Estimation is the dual half (moving horizon estimation, the planned
|
|
101
|
+
follow-on), and it declares its own pieces the same way. MHE fits the model
|
|
102
|
+
to a moving window of measurements, so the free variables and the objective
|
|
103
|
+
terms differ, but the conventions carry over: each declaration tags a Var,
|
|
104
|
+
a Constraint, or a Param, and drto assembles the estimation objective from
|
|
105
|
+
the live cost terms.
|
|
106
|
+
|
|
107
|
+
| DRTO object type | Pyomo object type | Declaration | What it is |
|
|
108
|
+
| --- | --- | --- | --- |
|
|
109
|
+
| Estimated parameter | Variable | `declare_estimated_parameter(m.theta, ...)` | Unknown model parameters to estimate, constant over the window. Shared with steady-state data reconciliation. |
|
|
110
|
+
| Disturbance | Variable | `declare_disturbance(m.w, ...)` | Process-noise variables (`dz/dt = f + w`) the estimator adjusts to fit the data, penalized by their covariance. |
|
|
111
|
+
| Measurement | Parameter | `declare_measurement(m.y_meas, ...)` | The measured values in the estimation cost residuals; a mutable Param drto refreshes each step. |
|
|
112
|
+
| Estimation stage cost | Constraint | `declare_estimation_stage_cost(m.est_stage_con)` | Equality defining the running estimation cost: measurement residual plus process-noise penalty over the window. |
|
|
113
|
+
| Estimation terminal cost | Constraint | `declare_estimation_terminal_cost(m.est_terminal_con)` | Equality for the current-time measurement residual (no process noise leads out of the last point). |
|
|
114
|
+
| Arrival cost | Constraint | `declare_arrival_cost(m.arrival_con)` | Equality for the soft prior on the window's initial state; its weight is updated by covariance propagation. |
|
|
115
|
+
|
|
116
|
+
The arrival cost is the soft dual of the control side's initial condition,
|
|
117
|
+
and the estimation stage and terminal costs are the measurement-fitting
|
|
118
|
+
counterparts of the tracking costs.
|
|
119
|
+
|
|
120
|
+
## Status
|
|
121
|
+
|
|
122
|
+
Design phase: see [DESIGN.md](DESIGN.md). No code yet.
|
drto-0.0.0/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# drto
|
|
2
|
+
|
|
3
|
+
Dynamic real-time optimization: receding-horizon optimization and
|
|
4
|
+
estimation for Pyomo models, with advanced-step NMPC as the headline
|
|
5
|
+
capability and moving horizon estimation as the planned follow-on.
|
|
6
|
+
|
|
7
|
+
## The six modes
|
|
8
|
+
|
|
9
|
+
drto runs one declared model in any of six modes, the 2x3 grid of
|
|
10
|
+
{steady-state, dynamic} by {simulation, optimization, estimation}. You
|
|
11
|
+
write the model once; the mode fixes what is free and what the objective
|
|
12
|
+
is.
|
|
13
|
+
|
|
14
|
+
| | Simulation | Optimization | Estimation |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| **Steady-state** | solve the model at equilibrium | economic RTO | data reconciliation |
|
|
17
|
+
| **Dynamic** | integrate the model forward | NMPC / D-RTO | moving horizon estimation |
|
|
18
|
+
|
|
19
|
+
Down the columns: simulation frees nothing and solves the model as given;
|
|
20
|
+
optimization frees the controls and adds a cost; estimation frees the
|
|
21
|
+
states and fits them to measurements. Across the rows: steady-state
|
|
22
|
+
collapses the model to a single equilibrium point, dynamic keeps the time
|
|
23
|
+
horizon. The optimization and estimation columns are duals (NMPC with MHE,
|
|
24
|
+
RTO with reconciliation), so one declaration surface serves both.
|
|
25
|
+
|
|
26
|
+
The near-term focus is the optimization column: dynamic NMPC/D-RTO, whose
|
|
27
|
+
ideal, real-time, and advanced-step execution variants are the headline,
|
|
28
|
+
plus steady-state RTO. Estimation is the planned follow-on.
|
|
29
|
+
|
|
30
|
+
## Declaring a control problem
|
|
31
|
+
|
|
32
|
+
drto is declaration-first, and each declaration tags a Pyomo component you
|
|
33
|
+
already wrote: a Variable, a Constraint, or a Parameter. You build your dynamic model as
|
|
34
|
+
an ordinary `pyomo.dae` model, then point the declarations at its pieces;
|
|
35
|
+
drto assembles the horizon problem and runs the loop. It bolts onto an
|
|
36
|
+
existing model rather than replacing how you build one. The pieces are the
|
|
37
|
+
object types of an optimal control problem (the dynamic-optimization
|
|
38
|
+
mode):
|
|
39
|
+
|
|
40
|
+
| DRTO object type | Pyomo object type | Declaration | What it is |
|
|
41
|
+
| --- | --- | --- | --- |
|
|
42
|
+
| State | Variable | `declare_state(m.z, ...)` | A differential state. drto reads its dynamics from the state's `DerivativeVar`. |
|
|
43
|
+
| Control | Variable | `declare_control(m.u, ..., wrt=m.t, profile=...)` | A manipulated input, the decision variable. The `profile` flag sets its parameterization (piecewise-constant, ...) via pyomo-cvp. |
|
|
44
|
+
| Tracking stage cost | Constraint | `declare_tracking_stage_cost(m.tracking_stage_con)` | Equality defining the setpoint-tracking running cost; its left-hand-side scalar goes in the objective. The setpoint it references is the declared steady-state Param (below). |
|
|
45
|
+
| Economic stage cost | Constraint | `declare_economic_stage_cost(m.economic_stage_con)` | Equality defining the economic running cost; the same objective the steady-state RTO mode uses. |
|
|
46
|
+
| Tracking terminal cost | Constraint | `declare_tracking_terminal_cost(m.tracking_terminal_con)` | Equality defining the terminal tracking cost; its left-hand-side scalar goes in the objective. |
|
|
47
|
+
| Initial condition | Constraint | `declare_initial_condition(m.init_con)` | Equality anchoring the initial state; left-hand side is the state at t0, right-hand side the feedback. |
|
|
48
|
+
| Terminal constraint | Constraint | `declare_terminal_constraint(m.terminal_con)` | Constraint on the states at the final time; the terminal set the final state must lie in. |
|
|
49
|
+
| Steady-state target | Parameter | `declare_steady_state(m.z_ss)` | The state setpoint the tracking costs drive toward; populated by the steady-state/RTO solve. |
|
|
50
|
+
| Steady-state control target | Parameter | `declare_steady_state_control(m.u_ss)` | The control setpoint the tracking costs drive toward. |
|
|
51
|
+
|
|
52
|
+
Conventions drto enforces on those constraints: the cost and
|
|
53
|
+
initial-condition constraints are equalities whose left-hand side is the
|
|
54
|
+
scalar the declaration is about (the cost term, or the anchored state); a
|
|
55
|
+
terminal constraint may reference only states at the final time, which is
|
|
56
|
+
what separates it from a path constraint. The objective is drto's own: it
|
|
57
|
+
sums the declared cost terms that are live in the current mode, so a mode
|
|
58
|
+
drops a term just by leaving out its constraint.
|
|
59
|
+
|
|
60
|
+
Two things you never declare, because they already live in the model: the
|
|
61
|
+
**dynamics** are read from the `pyomo.dae` `DerivativeVar`s of the
|
|
62
|
+
declared states, and the **path constraints** are the state variables'
|
|
63
|
+
own upper and lower bounds.
|
|
64
|
+
|
|
65
|
+
The vocabulary is the optimal-control literature's own (stage cost,
|
|
66
|
+
terminal cost, terminal constraint), so a model reads the way the theory
|
|
67
|
+
does. The other modes reuse the same model: simulation drops the cost, and
|
|
68
|
+
estimation swaps the initial condition for a soft arrival cost and adds the
|
|
69
|
+
estimation pieces below.
|
|
70
|
+
|
|
71
|
+
## Declaring an estimation problem
|
|
72
|
+
|
|
73
|
+
Estimation is the dual half (moving horizon estimation, the planned
|
|
74
|
+
follow-on), and it declares its own pieces the same way. MHE fits the model
|
|
75
|
+
to a moving window of measurements, so the free variables and the objective
|
|
76
|
+
terms differ, but the conventions carry over: each declaration tags a Var,
|
|
77
|
+
a Constraint, or a Param, and drto assembles the estimation objective from
|
|
78
|
+
the live cost terms.
|
|
79
|
+
|
|
80
|
+
| DRTO object type | Pyomo object type | Declaration | What it is |
|
|
81
|
+
| --- | --- | --- | --- |
|
|
82
|
+
| Estimated parameter | Variable | `declare_estimated_parameter(m.theta, ...)` | Unknown model parameters to estimate, constant over the window. Shared with steady-state data reconciliation. |
|
|
83
|
+
| Disturbance | Variable | `declare_disturbance(m.w, ...)` | Process-noise variables (`dz/dt = f + w`) the estimator adjusts to fit the data, penalized by their covariance. |
|
|
84
|
+
| Measurement | Parameter | `declare_measurement(m.y_meas, ...)` | The measured values in the estimation cost residuals; a mutable Param drto refreshes each step. |
|
|
85
|
+
| Estimation stage cost | Constraint | `declare_estimation_stage_cost(m.est_stage_con)` | Equality defining the running estimation cost: measurement residual plus process-noise penalty over the window. |
|
|
86
|
+
| Estimation terminal cost | Constraint | `declare_estimation_terminal_cost(m.est_terminal_con)` | Equality for the current-time measurement residual (no process noise leads out of the last point). |
|
|
87
|
+
| Arrival cost | Constraint | `declare_arrival_cost(m.arrival_con)` | Equality for the soft prior on the window's initial state; its weight is updated by covariance propagation. |
|
|
88
|
+
|
|
89
|
+
The arrival cost is the soft dual of the control side's initial condition,
|
|
90
|
+
and the estimation stage and terminal costs are the measurement-fitting
|
|
91
|
+
counterparts of the tracking costs.
|
|
92
|
+
|
|
93
|
+
## Status
|
|
94
|
+
|
|
95
|
+
Design phase: see [DESIGN.md](DESIGN.md). No code yet.
|
drto-0.0.0/_typos.toml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Configuration for the `typos` spell-checker (crate-ci/typos), matching the
|
|
2
|
+
# Pyomo contribution guide's spell-check step.
|
|
3
|
+
[files]
|
|
4
|
+
# Executed-notebook outputs carry base64 blobs and verbatim solver log text;
|
|
5
|
+
# the notebook prose is reviewed by eye instead.
|
|
6
|
+
extend-exclude = ["*.ipynb"]
|
|
7
|
+
|
|
8
|
+
[default.extend-words]
|
|
9
|
+
# Domain terms, not misspellings.
|
|
10
|
+
RTO = "RTO" # real-time optimization
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# dev-notes
|
|
2
|
+
|
|
3
|
+
drto's durable engineering memory: design records, code-review logs, progress
|
|
4
|
+
trackers, and forward-looking research. This is the one place development
|
|
5
|
+
history is allowed to accrete, which keeps code comments clean (they state
|
|
6
|
+
present-tense rationale only).
|
|
7
|
+
|
|
8
|
+
`DESIGN.md` at the repo root stays the top-level design record. These notes
|
|
9
|
+
are the working layer beneath it.
|
|
10
|
+
|
|
11
|
+
## Naming so notes are findable
|
|
12
|
+
|
|
13
|
+
Use these patterns so an agent can Glob for a genre:
|
|
14
|
+
|
|
15
|
+
- `code-review-YYYY-MM.md` -- a dated, severity-ranked review, read-only once
|
|
16
|
+
written; follow-ups go in `code-review-YYYY-MM-followups.md`.
|
|
17
|
+
- `issue-NNN-<slug>.md` -- work notes for a specific issue.
|
|
18
|
+
- `<feature>-progress.md` -- live state for a multi-session or loop task, with
|
|
19
|
+
`[ ]` / `[x]` checkboxes.
|
|
20
|
+
- `release.md` -- the release runbook, once there is one.
|
|
21
|
+
- `research/` -- forward-looking work not tied to a single change.
|
|
22
|
+
|
|
23
|
+
## House style for a note
|
|
24
|
+
|
|
25
|
+
Open with a one-line **Status**. When a note's framing is overtaken by later
|
|
26
|
+
findings, append a "Progress / current state" section that corrects the
|
|
27
|
+
original rather than editing it away, so the correction stays visible.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
Status: design complete, no code. Seeded from DESIGN.md (Delivery plan and
|
|
4
|
+
Follow-on). DESIGN.md stays authoritative for decisions; this note tracks
|
|
5
|
+
sequence.
|
|
6
|
+
|
|
7
|
+
## Near term
|
|
8
|
+
|
|
9
|
+
1. Quad-tank spike: the ideal-NMPC mode end-to-end on the quad tank (build on
|
|
10
|
+
the Quad_tank_cvp model), then flip on the advanced-step mode with the
|
|
11
|
+
existing pounce `estimate()`. The three-variant comparison plot falls out.
|
|
12
|
+
2. v1 package: the loop, the three execution variants, the control-side
|
|
13
|
+
declarations, tests, docs, executed notebooks.
|
|
14
|
+
|
|
15
|
+
## Follow-on
|
|
16
|
+
|
|
17
|
+
- Moving horizon estimation (the estimation half): the six estimation
|
|
18
|
+
declarations, the soft arrival cost, and covariance propagation for the
|
|
19
|
+
arrival-cost weight (pounce covariance machinery).
|
|
20
|
+
- Steady-state reduction wiring (setpoint consistency and economic RTO) via
|
|
21
|
+
the reusable-object mechanism.
|
|
22
|
+
|
|
23
|
+
## Stretch
|
|
24
|
+
|
|
25
|
+
- One large-scale dynamic flowsheet as a credibility example.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "drto"
|
|
7
|
+
version = "0.0.0"
|
|
8
|
+
description = "Dynamic real-time optimization: receding-horizon NMPC and moving horizon estimation for Pyomo models."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "BSD-3-Clause"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Devin Griffith", email = "dwg176@gmail.com" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"nmpc",
|
|
16
|
+
"mhe",
|
|
17
|
+
"moving-horizon-estimation",
|
|
18
|
+
"pyomo",
|
|
19
|
+
"dynamic-optimization",
|
|
20
|
+
"receding-horizon",
|
|
21
|
+
"dae",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 1 - Planning",
|
|
25
|
+
"Intended Audience :: Science/Research",
|
|
26
|
+
"License :: OSI Approved :: BSD License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
29
|
+
]
|
|
30
|
+
dependencies = ["pyomo>=6.8.1"]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = ["pytest", "pytest-cov"]
|
|
34
|
+
examples = ["numpy", "matplotlib", "jupyter"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/devin-griff/drto"
|
|
38
|
+
Repository = "https://github.com/devin-griff/drto"
|
|
39
|
+
Issues = "https://github.com/devin-griff/drto/issues"
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/drto"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
|
|
47
|
+
# Mirrors Pyomo's own Black settings so formatting matches upstream exactly.
|
|
48
|
+
[tool.black]
|
|
49
|
+
line-length = 88
|
|
50
|
+
skip-string-normalization = true
|
|
51
|
+
skip-magic-trailing-comma = true
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright (c) 2026 Devin Griffith
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause
|
|
3
|
+
"""drto: dynamic real-time optimization for Pyomo models.
|
|
4
|
+
|
|
5
|
+
Receding-horizon NMPC and moving horizon estimation for ``pyomo.dae`` models.
|
|
6
|
+
Design phase: the framework is recorded in DESIGN.md and AGENTS.md. No
|
|
7
|
+
functionality yet.
|
|
8
|
+
"""
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version("drto")
|
|
13
|
+
except PackageNotFoundError: # not installed (e.g. running from a source tree)
|
|
14
|
+
__version__ = "0.0.0"
|
|
15
|
+
|
|
16
|
+
__all__ = ["__version__"]
|