Skip to content

Commit cae9d7a

Browse files
Improve documentation and integrate Sphinx with GitHub Pages (#39)
* Use IEEE citation and links for papers in `README.md` Replace arXiv links and BibTex entries with IEEE versions for referenced papers to ensure proper attribution. * Update docstrings to correctly render with Sphinx - Update function and class docstrings in several modules. - Add missing module-level docstrings. This change ensures the project is correctly rendered in documentation pages built with Sphinx, and improves the overall documentation presentation. * Improve readability and structure of `README.md` - Standardize letter casing for "nonlinear" terminology. - Fix minor punctuation and formatting inconsistencies. - Improve structure of the "Controller Creation" section in "Code Structure". * Add Sphinx documentation * Convert README links to absolute URLs on release for PyPI Add a step to the "build" job in `release.yml` to convert source file links in `README.md` from relative paths to absolute GitHub URLs. This ensures the links point to their corresponding files on GitHub when rendered on PyPI. * Add workflow for building & deploying docs to GitHub Pages
1 parent a30f915 commit cae9d7a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1562
-327
lines changed

.github/workflows/docs.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
name: Build & Deploy Docs
3+
4+
concurrency:
5+
group: ${{ github.workflow }}-${{ github.ref_name }}
6+
cancel-in-progress: true
7+
8+
on:
9+
push:
10+
branches: ["main"]
11+
12+
env:
13+
PYTHON_VERSION: '3.12'
14+
15+
jobs:
16+
build_docs:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: ${{ env.PYTHON_VERSION }}
26+
27+
- name: Upgrade pip and install docs requirements
28+
working-directory: ./docs
29+
run: |
30+
python -m pip install --upgrade pip
31+
pip install -r requirements.txt
32+
33+
- name: Build docs
34+
working-directory: ./docs
35+
run: make html
36+
37+
- name: Upload docs artifact
38+
uses: actions/upload-artifact@v4
39+
with:
40+
name: docs-html
41+
path: ./docs/build/html
42+
43+
deploy_docs:
44+
runs-on: ubuntu-latest
45+
needs: [build_docs]
46+
steps:
47+
- name: Download artifacts
48+
uses: actions/download-artifact@v4
49+
with:
50+
name: docs-html
51+
path: ./docs/build/html
52+
53+
- name: Deploy to gh-pages
54+
uses: peaceiris/actions-gh-pages@v4
55+
with:
56+
github_token: ${{ secrets.GITHUB_TOKEN }}
57+
publish_dir: ./docs/build/html

.github/workflows/release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ jobs:
9494
run: |
9595
pip install -e .
9696
97+
- name: Convert README source file links to absolute GitHub URLs
98+
run: |
99+
export REPO_URL="https://github.com/pavelacamposp/direct-data-driven-mpc/blob/main/"
100+
perl -i -pe 's{\[([^\]]+)\]\((?!https?://|#)([^)]+)\)}{"[$1]($ENV{REPO_URL}$2)"}ge' README.md
101+
97102
- name: Build source and wheel distribution
98103
run: |
99104
python -m build

README.md

Lines changed: 50 additions & 55 deletions
Large diffs are not rendered by default.

direct_data_driven_mpc/lti_data_driven_mpc_controller.py

Lines changed: 108 additions & 70 deletions
Large diffs are not rendered by default.

direct_data_driven_mpc/nonlinear_data_driven_mpc_controller.py

Lines changed: 84 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,34 @@
1212
# Define the regularization types of `alpha`, considering
1313
# with respect to what variable it is regularized
1414
class AlphaRegType(Enum):
15-
# Alpha regularized w.r.t. an approximation of alpha_Lin^sr(D_t),
16-
# based on Remark 1 of [2].
15+
"""
16+
Regularization types for the `alpha` variable used in the formulation of
17+
Data-Driven MPC controllers for nonlinear systems.
18+
19+
Attributes:
20+
APPROXIMATED: Regularizes `alpha` with respect to an approximation of
21+
`alpha_Lin^sr(D_t)`. Based on Remark 1 of [2].
22+
PREVIOUS: Regularizes `alpha` with respect to the previous optimal
23+
alpha value to encourage stationary behavior. Refer to Section V of
24+
[2].
25+
ZERO: Regularizes `alpha` with respect to zero.
26+
27+
References:
28+
[2] J. Berberich, J. Köhler, M. A. Müller and F. Allgöwer, "Linear
29+
Tracking MPC for Nonlinear Systems—Part II: The Data-Driven Case," in
30+
IEEE Transactions on Automatic Control, vol. 67, no. 9, pp. 4406-4421,
31+
Sept. 2022, doi: 10.1109/TAC.2022.3166851.
32+
"""
33+
1734
APPROXIMATED = 0
18-
# Alpha regularized w.r.t. the previous optimal alpha value to
19-
# encourage stationary behavior. Refer to Section V of [2].
2035
PREVIOUS = 1
21-
# Alpha regularized w.r.t. zero.
2236
ZERO = 2
2337

2438

2539
class NonlinearDataDrivenMPCController:
2640
"""
2741
A class that implements a Data-Driven Model Predictive Control (MPC)
28-
controller for Nonlinear systems. The implementation is based on research
42+
controller for nonlinear systems. The implementation is based on research
2943
by J. Berberich et al., as described in [2].
3044
3145
Attributes:
@@ -60,10 +74,15 @@ class NonlinearDataDrivenMPCController:
6074
lamb_sigma_s (float): The ridge regularization weight for
6175
`sigma_s` for a controller that uses an approximation of
6276
`alpha_Lin^sr(D_t)` for the regularization of `alpha`.
63-
ext_out_incr_in (bool): The controller structure (uses an extended
64-
output representation and input increments, or operates as a
65-
standard controller with direct control inputs and without system
66-
state extensions).
77+
ext_out_incr_in (bool): The controller structure:
78+
79+
- If `True`, the controller uses an extended output representation
80+
(y_ext[k] = [y[k], u[k]]) and input increments (u[k] = u[k-1] +
81+
du[k-1]).
82+
- If `False`, the controller operates as a standard controller with
83+
direct control inputs and without system state extensions.
84+
85+
Defaults to `False`.
6786
update_cost_threshold (float): The tracking cost value threshold.
6887
Online input-output data updates are disabled when the tracking
6988
cost value is less than this value.
@@ -137,9 +156,9 @@ class NonlinearDataDrivenMPCController:
137156
138157
References:
139158
[2] J. Berberich, J. Köhler, M. A. Müller and F. Allgöwer, "Linear
140-
Tracking MPC for Nonlinear Systems—Part II: The Data-Driven Case,"
141-
in IEEE Transactions on Automatic Control, vol. 67, no. 9, pp.
142-
4406-4421, Sept. 2022, doi: 10.1109/TAC.2022.3166851.
159+
Tracking MPC for Nonlinear Systems—Part II: The Data-Driven Case," in
160+
IEEE Transactions on Automatic Control, vol. 67, no. 9, pp. 4406-4421,
161+
Sept. 2022, doi: 10.1109/TAC.2022.3166851.
143162
"""
144163

145164
def __init__(
@@ -202,18 +221,22 @@ def __init__(
202221
single input.
203222
alpha_reg_type (AlphaRegType): The `alpha` regularization type
204223
for the Nonlinear Data-Driven MPC formulation.
205-
lamb_alpha_s (float): The ridge regularization weight for
224+
lamb_alpha_s (float | None): The ridge regularization weight for
206225
`alpha_s` for a controller that uses an approximation of
207226
`alpha_Lin^sr(D_t)` for the regularization of `alpha`.
208-
lamb_sigma_s (float): The ridge regularization weight for
227+
lamb_sigma_s (float | None): The ridge regularization weight for
209228
`sigma_s` for a controller that uses an approximation of
210229
`alpha_Lin^sr(D_t)` for the regularization of `alpha`.
211230
ext_out_incr_in (bool): The controller structure:
212-
If `True`, the controller uses an extended output
213-
representation (y_ext[k] = [y[k], u[k]]) and input increments
214-
(u[k] = u[k-1] + du[k-1]). If `False`, the controller operates
215-
as a standard controller with direct control inputs and
216-
without system state extensions. Defaults to `False`.
231+
232+
- If `True`, the controller uses an extended output
233+
representation (y_ext[k] = [y[k], u[k]]) and input increments
234+
(u[k] = u[k-1] + du[k-1]).
235+
- If `False`, the controller operates as a standard controller
236+
with direct control inputs and without system state
237+
extensions.
238+
239+
Defaults to `False`.
217240
update_cost_threshold (float | None): The tracking cost value
218241
threshold. Online input-output data updates are disabled when
219242
the tracking cost value is less than this value. If `None`,
@@ -225,9 +248,9 @@ def __init__(
225248
226249
References:
227250
[2] J. Berberich, J. Köhler, M. A. Müller and F. Allgöwer, "Linear
228-
Tracking MPC for Nonlinear Systems—Part II: The Data-Driven
229-
Case," in IEEE Transactions on Automatic Control, vol. 67, no.
230-
9, pp. 4406-4421, Sept. 2022, doi: 10.1109/TAC.2022.3166851.
251+
Tracking MPC for Nonlinear Systems—Part II: The Data-Driven Case,"
252+
in IEEE Transactions on Automatic Control, vol. 67, no. 9, pp.
253+
4406-4421, Sept. 2022, doi: 10.1109/TAC.2022.3166851.
231254
"""
232255
# Define controller structure:
233256
# - If `True`: The controller uses an extended output representation
@@ -274,9 +297,10 @@ def __init__(
274297
self.Us = Us # Bounds for the predicted input setpoint
275298
# Note: Us must be a subset of U.
276299

277-
# Alpha regularization type for Nonlinear MPC
300+
# Alpha regularization type
278301
self.alpha_reg_type = alpha_reg_type
279-
# Nonlinear MPC parameters for the approximation of alpha_Lin^sr(D_t).
302+
303+
# Parameters for the approximation of alpha_Lin^sr(D_t).
280304
# Alpha is regularized w.r.t. this parameter, based on Remark 1
281305
# of [2].
282306
if alpha_reg_type == AlphaRegType.APPROXIMATED:
@@ -438,6 +462,7 @@ def initialize_data_driven_mpc(self) -> None:
438462
Initialize the Data-Driven MPC controller.
439463
440464
This method performs the following tasks:
465+
441466
1. Constructs Hankel matrices from the initial input-output trajectory
442467
data (`u`, `y`). These matrices are used for the data-driven
443468
characterization of the unknown system, as defined by the system
@@ -502,6 +527,7 @@ def update_and_solve_data_driven_mpc(
502527
and store the optimal control input.
503528
504529
This method performs the following tasks:
530+
505531
1. Constructs Hankel matrices using the latest measured input-output
506532
data. If the tracking cost value from the previous solution is
507533
small enough (less than `update_cost_threshold`), omits this step
@@ -809,23 +835,23 @@ def define_mpc_constraints(self) -> None:
809835
810836
This method defines the following constraints, as described in the
811837
Nonlinear Data-Driven MPC formulation in [2]:
838+
812839
- **System dynamics**: Ensures input-output predictions are possible
813-
trajectories of the system based on a data-driven characterization
814-
of all its input-output trajectories. Defined by Equation (22b).
840+
trajectories of the system based on a data-driven characterization of
841+
all its input-output trajectories. Defined by Equation (22b).
815842
- **Internal state**: Ensures predictions align with the internal
816-
state of the system's trajectory. This constrains the first `n`
817-
input-output predictions to match the past `n` input-output
818-
measurements of the system, guaranteeing that the predictions
819-
consider the initial state of the system. Defined by Equation
820-
(22c).
843+
state of the system's trajectory. This constrains the first `n`
844+
input-output predictions to match the past `n` input-output
845+
measurements of the system, guaranteeing that the predictions
846+
consider the initial state of the system. Defined by Equation (22c).
821847
- **Terminal state**: Aims to stabilize the internal state of the
822-
system so it aligns with the steady-state that corresponds to the
823-
input-output equilibrium pair (predicted equilibrium setpoints
824-
`u_s`, `y_s`) in any minimal realization (last `n` input-output
825-
predictions, as considered in [2]). Defined by Equation (22d).
848+
system so it aligns with the steady-state that corresponds to the
849+
input-output equilibrium pair (predicted equilibrium setpoints `u_s`,
850+
`y_s`) in any minimal realization (last `n` input-output predictions,
851+
as considered in [2]). Defined by Equation (22d).
826852
- **Input**: Constrains both the equilibrium input (predicted input
827-
setpoint `u_s`) and the input trajectory (`ubar`). Defined by
828-
Equation (22e).
853+
setpoint `u_s`) and the input trajectory (`ubar`). Defined by
854+
Equation (22e).
829855
830856
Note:
831857
This method initializes the `dynamics_constraints`,
@@ -867,8 +893,8 @@ def define_system_dynamic_constraints(self) -> list[cp.Constraint]:
867893
868894
Returns:
869895
list[cp.Constraint]: A list containing the CVXPY system dynamic
870-
constraints for the Data-Driven MPC controller, corresponding
871-
to the specified MPC controller type.
896+
constraints for the Data-Driven MPC controller, corresponding to
897+
the specified MPC controller type.
872898
873899
References:
874900
[2]: See class-level docstring for full reference details.
@@ -904,7 +930,7 @@ def define_internal_state_constraints(self) -> list[cp.Constraint]:
904930
905931
Returns:
906932
list[cp.Constraint]: A list containing the CVXPY internal state
907-
constraints for the Data-Driven MPC controller.
933+
constraints for the Data-Driven MPC controller.
908934
909935
Note:
910936
It is essential to update the system's input-output measurements,
@@ -941,7 +967,7 @@ def define_terminal_state_constraints(self) -> list[cp.Constraint]:
941967
942968
Returns:
943969
list[cp.Constraint]: A list containing the CVXPY terminal state
944-
constraints for the Data-Driven MPC controller.
970+
constraints for the Data-Driven MPC controller.
945971
946972
References:
947973
[2]: See class-level docstring for full reference details.
@@ -968,7 +994,7 @@ def define_input_constraints(self) -> list[cp.Constraint]:
968994
969995
Returns:
970996
list[cp.Constraint]: A list containing the CVXPY input constraints
971-
for the Data-Driven MPC controller.
997+
for the Data-Driven MPC controller.
972998
"""
973999
# Define input constraints
9741000
if self.ext_out_incr_in:
@@ -1063,8 +1089,8 @@ def solve_mpc_problem(self, warm_start: bool = False) -> str:
10631089
10641090
Returns:
10651091
str: The status of the optimization problem after attempting to
1066-
solve it (e.g., "optimal", "optimal_inaccurate", "infeasible",
1067-
"unbounded").
1092+
solve it (e.g., "optimal", "optimal_inaccurate", "infeasible",
1093+
"unbounded").
10681094
10691095
Note:
10701096
This method assumes that the MPC problem has already been defined.
@@ -1082,8 +1108,8 @@ def get_problem_solve_status(self) -> str:
10821108
10831109
Returns:
10841110
str: The status of the optimization problem after attempting to
1085-
solve it (e.g., "optimal", "optimal_inaccurate", "infeasible",
1086-
"unbounded").
1111+
solve it (e.g., "optimal", "optimal_inaccurate", "infeasible",
1112+
"unbounded").
10871113
"""
10881114
return self.problem.status
10891115

@@ -1094,7 +1120,7 @@ def get_optimal_cost_value(self) -> float:
10941120
10951121
Returns:
10961122
float: The optimal cost value of the solved MPC optimization
1097-
problem.
1123+
problem.
10981124
"""
10991125
return self.problem.value
11001126

@@ -1105,9 +1131,9 @@ def get_optimal_control_input(self) -> np.ndarray:
11051131
11061132
Returns:
11071133
np.ndarray: The predicted optimal control input from time step 0
1108-
to L. If the controller uses an extended output representation
1109-
and input increments, returns the predicted optimal control
1110-
input increments instead.
1134+
to L. If the controller uses an extended output representation and
1135+
input increments, returns the predicted optimal control input
1136+
increments instead.
11111137
11121138
Raises:
11131139
ValueError: If the MPC problem solution status was not "optimal"
@@ -1152,7 +1178,7 @@ def get_optimal_control_input_at_step(self, n_step: int = 0) -> np.ndarray:
11521178
11531179
Returns:
11541180
np.ndarray: An array containing the optimal control input for the
1155-
specified prediction time step.
1181+
specified prediction time step.
11561182
11571183
Note:
11581184
This method assumes that the optimal control input from the MPC
@@ -1214,9 +1240,9 @@ def get_du_value_at_step(self, n_step: int = 0) -> np.ndarray | None:
12141240
12151241
Returns:
12161242
np.ndarray | None: An array containing the optimal control
1217-
input increment for the specified prediction time step if the
1218-
controller uses an extended output representation and input
1219-
increments. Otherwise, returns `None`.
1243+
input increment for the specified prediction time step if the
1244+
controller uses an extended output representation and input
1245+
increments. Otherwise, returns `None`.
12201246
12211247
Note:
12221248
This method assumes that the `optimal_du` attribute contains the
@@ -1250,9 +1276,9 @@ def store_input_output_measurement(
12501276
time step, expected to match the dimensions of prior outputs.
12511277
This output should correspond to the system's response to
12521278
`u_current`, as both represent a trajectory of the system.
1253-
du_current (np.ndarray): The control input increment (du[k] =
1254-
u[k+1] - u[k]) for the current time step, expected to match
1255-
the dimensions of prior inputs.
1279+
du_current (np.ndarray | None): The control input increment
1280+
(du[k] = u[k+1] - u[k]) for the current time step, expected to
1281+
match the dimensions of prior inputs.
12561282
12571283
Raises:
12581284
ValueError: If `u_current`, `y_current`, or `du_current` do not

0 commit comments

Comments
 (0)