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.
@@ -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.
@@ -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
@@ -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,3 @@
1
+ # drto
2
+
3
+ Agent instructions for this repo live in [AGENTS.md](AGENTS.md). Read them there.
@@ -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__"]
@@ -0,0 +1,8 @@
1
+ # Copyright (c) 2026 Devin Griffith
2
+ # SPDX-License-Identifier: BSD-3-Clause
3
+ """Phase 0 smoke test: the package imports and exposes a version string."""
4
+ import drto
5
+
6
+
7
+ def test_version_is_string():
8
+ assert isinstance(drto.__version__, str)