├── .gitignore
├── .idea
├── .gitignore
└── dictionaries
│ └── marco.xml
├── LICENSE.md
├── README.md
├── README_PI.md
├── doc
├── latex
│ ├── main.pdf
│ ├── main.tex
│ ├── pyMPC_formula_1.txt
│ └── pyMPC_formula_2.txt
└── sphinx
│ ├── Makefile
│ └── source
│ ├── code.rst
│ ├── conf.py
│ ├── index.rst
│ ├── math.rst
│ ├── project.rst
│ └── sphyinx_install
├── examples
├── example_accelerate_brake.py
├── example_inverted_pendulum.ipynb
├── example_inverted_pendulum.py
├── example_inverted_pendulum_kalman.ipynb
├── example_inverted_pendulum_kalman.py
├── example_point_mass.ipynb
├── example_point_mass.py
└── img
│ ├── cart_pole.png
│ └── cart_pole.svg
├── pyMPC
├── .idea
│ ├── misc.xml
│ ├── modules.xml
│ ├── pyMPC.iml
│ ├── vcs.xml
│ └── workspace.xml
├── __init__.py
├── kalman.py
├── mpc.py
└── mpc_no_slack.py
├── setup.py
└── test_scripts
├── alternative
├── example_unconstrained_MPC.py
└── unconstrained.py
├── cvx_mpc_reference_governor.py
├── cvx_mpc_reference_governor_du.py
├── cvx_mpc_reference_governor_du_mimo.py
├── cvx_mpc_simple.py
├── disturbance.ipynb
├── example_inverted_pendulum.py
├── example_inverted_pendulum_1ms.py
├── example_inverted_pendulum_disturbance.py
├── example_inverted_pendulum_ode.py
├── example_inverted_pendulum_reference.py
├── example_mpc_function.py
├── kalman
├── MPC_observer.py
├── example_estimator_save_predictions.py
├── example_inverted_pendulum_disturbance.py
├── example_inverted_pendulum_disturbance_test.py
├── example_inverted_pendulum_estimator.py
├── img
│ ├── cart_pole.png
│ └── cart_pole.svg
└── kalman.py
├── main.py
├── main_cvxpy.py
├── main_cvxpy_du.py
├── main_cvxpy_du_Nc.py
├── main_cvxpy_du_nocnst.py
├── main_cvxpy_simple.py
├── main_du.py
├── main_du_Nc.py
├── main_matlab_yalmip.m
├── main_simple.py
├── reference_governor
├── cvx_mpc_reference_governor_du_mimo_slack.py
├── oscillating_dynamics.py
└── system_dynamics.py
└── verify_MPC.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /.ipynb_checkpoints/
2 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /workspace.xml
--------------------------------------------------------------------------------
/.idea/dictionaries/marco.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | kalman
5 |
6 |
7 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Marco Forgione
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pyMPC
2 |
3 | Linear Constrained Model Predictive Control (MPC) in Python:
4 |
5 |
6 |
7 | where
8 |
9 | ## Requirements
10 |
11 | pyMPC requires the following packages:
12 | * numpy
13 | * scipy
14 | * [OSQP](https://osqp.org/)
15 | * matplotlib
16 |
17 | ## Installation
18 |
19 | ### Stable version from PyPI
20 |
21 | Run the command
22 |
23 | ```
24 | pip install python-mpc
25 | ```
26 | This will install the [stable version](https://pypi.org/project/python-mpc/0.1.1/) of pyMPC from the PyPI package repository.
27 |
28 | ### Latest version from GitHub
29 | 1. Get a local copy the pyMPC project. For instance, run
30 | ```
31 | git clone https://github.com/forgi86/pyMPC.git
32 | ```
33 | in a terminal to clone the project using git. Alternatively, download the zipped pyMPC project from [this link](https://github.com/forgi86/pyMPC/zipball/master) and extract it in a local folder
34 |
35 | 2. Install pyMPC by running
36 | ```
37 | pip install -e .
38 | ```
39 | in the pyMPC project root folder (where the file setup.py is located).
40 |
41 | ## Supported platforms
42 |
43 | We successfully tested pyMPC on the following platforms:
44 | * Windows 10 on a PC with x86-64 CPU
45 | * Ubuntu 18.04 LTS on a PC with x86-64 CPU
46 | * Raspbian Buster on a Raspberry PI 3 rev B
47 |
48 | Detailed instructions for the Raspberry PI platform are available [here](README_PI.md).
49 |
50 | ## Usage
51 |
52 | This code snippets illustrates the use of the MPCController class:
53 |
54 | ```
55 | from pyMPC.mpc import MPCController
56 |
57 | K = MPCController(Ad,Bd,Np=20, x0=x0,xref=xref,uminus1=uminus1,
58 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
59 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax)
60 | K.setup()
61 |
62 | ...
63 |
64 | xstep = x0
65 | for i in range(nsim):
66 | uMPC = K.output()
67 | xstep = Ad.dot(xstep) + Bd.dot(uMPC) # system simulation steps
68 | K.update(xstep) # update with measurement
69 | ```
70 | Full working examples are given in the [examples](examples) folder:
71 | * [Point mass with input force and friction](examples/example_point_mass.ipynb)
72 | * [Inverted pendulum on a cart](examples/example_inverted_pendulum.ipynb)
73 | * [Inverted pendulum on a cart with kalman filter](examples/example_inverted_pendulum_kalman.ipynb)
74 |
75 | ## Contributing
76 |
77 | I am slowly adding new functionalities to pyMPC according to my research needs. If you also wanna contribute, feel free to write me an email: marco.forgione@idsia.ch
78 |
79 | ## Citing
80 |
81 | If you find this project useful, we encourage you to
82 |
83 | * Star this repository :star:
84 | * Cite the [paper](https://arxiv.org/pdf/1911.13021)
85 | ```
86 | @inproceedings{forgione2020efficient,
87 | title={Efficient Calibration of Embedded {MPC}},
88 | author={Forgione, Marco and Piga, Dario and Bemporad, Alberto},
89 | booktitle={Proc. of the 21st IFAC World Congress 2020, Berlin, Germany, July 12-17 2020},
90 | year={2020}
91 | }
92 | ```
93 |
--------------------------------------------------------------------------------
/README_PI.md:
--------------------------------------------------------------------------------
1 | ## pyMPC on Raspberry PI
2 |
3 | pyMPC can run on Raspberry PI (we tested model 3B). This procedure may also work on other versions and maybe other ARM machines, but we haven't tested yet.
4 |
5 | ## Installation procedure
6 |
7 | We assume you already have Raspbian installed on the Raspberry PI.
8 |
9 | 1. Install the following packages via apt
10 | ```
11 | apt-get install git
12 | apt-get install cmake
13 | ```
14 |
15 | 2. Install the berryconda conda distribution
16 | ```
17 | wget https://github.com/jjhelmus/berryconda/releases/download/v2.0.0/Berryconda3-2.0.0-Linux-armv7l.sh
18 | chmod +x Berryconda3-2.0.0-Linux-armv7l.sh
19 | ./Berryconda3-2.0.0-Linux-armv7l.sh
20 | ```
21 | In the installation process, you will be asked for an installation folder. The default /home/pi/berryconda3 is fine
22 |
23 | 3. Install required packages in your new (berry)conda distribtion
24 | ```
25 | cd /home/pi/berryconda3
26 | ./conda install numpy scipy matplotlib
27 | ./conda install -c numba numba
28 | ./pip install control
29 | apt-get install cmake
30 | ./pip install osqp
31 | ```
32 |
33 | 4. Get a local copy the pyMPC project
34 | ```
35 | git clone https://github.com/forgi86/pyMPC.git
36 | ```
37 |
38 | 5. Install pyMPC. In the folder where you cloned the project, run
39 |
40 | ```
41 | pip install -e .
42 | ```
43 |
--------------------------------------------------------------------------------
/doc/latex/main.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forgi86/pyMPC/291db149554767a035fcb01df3fed7a6b3fe60e4/doc/latex/main.pdf
--------------------------------------------------------------------------------
/doc/latex/pyMPC_formula_1.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | \begin{align*} &\min_{u_0, \dots, u_{N_{p}-1},x_0,\dots,x_{N_p}} \sum_{k=0}^{N_p-1} \bigg[ \big(x_k - x_{ref}\big)^\top Q_x\big(x_k - x_{ref}\big) + \big(u_k - u_{ref}\big)^\top Q_u \big(u_k - u_{ref}\big) + \Delta u_k^\top Q_{\Delta} \Delta u_k \bigg ] + \big(x_{N_p} - x_{ref}\big)^\top Q_{x_{N_p}}\big(x_{N_p} - x_{ref}\big) \\ &\text{subject to} \nonumber\\ &x_{k+1} = Ax_k + B u_k \label{eq:linear_dynamics} \\ &u_{min} \leq u_k \leq u_{max}\\ &x_{min} \leq x_k \leq x_{max}\\ &\Delta u_{min} \leq \Delta u_k \leq \Delta u_{max}\\ &u_{-1} = \bar u \\ &x_0 = \bar x \end{align*}
6 |
--------------------------------------------------------------------------------
/doc/latex/pyMPC_formula_2.txt:
--------------------------------------------------------------------------------
1 |
.
2 |
3 |
4 | \Delta u_k = u_k - u_{k-1}
5 |
--------------------------------------------------------------------------------
/doc/sphinx/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | SOURCEDIR = source
8 | BUILDDIR = build
9 |
10 | # Put it first so that "make" without argument is like "make help".
11 | help:
12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
13 |
14 | .PHONY: help Makefile
15 |
16 | # Catch-all target: route all unknown targets to Sphinx using the new
17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18 | %: Makefile
19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--------------------------------------------------------------------------------
/doc/sphinx/source/code.rst:
--------------------------------------------------------------------------------
1 | .. pyMPC documentation master file, created by
2 | sphinx-quickstart on Tue May 28 18:47:14 2019.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Code documentation
7 | =================================
8 |
9 |
10 | .. automodule:: mpc
11 | :members:
12 |
13 | .. automodule:: kalman
14 | :members:
15 |
16 |
--------------------------------------------------------------------------------
/doc/sphinx/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Configuration file for the Sphinx documentation builder.
4 | #
5 | # This file does only contain a selection of the most common options. For a
6 | # full list see the documentation:
7 | # http://www.sphinx-doc.org/en/master/config
8 |
9 | # -- Path setup --------------------------------------------------------------
10 |
11 | # If extensions (or modules to document with autodoc) are in another directory,
12 | # add these directories to sys.path here. If the directory is relative to the
13 | # documentation root, use os.path.abspath to make it absolute, like shown here.
14 | #
15 | import os
16 | import sys
17 | sys.path.insert(0, os.path.abspath('../../../pyMPC'))
18 |
19 |
20 | # -- Project information -----------------------------------------------------
21 |
22 | project = 'pyMPC'
23 | copyright = '2019, Marco Forgione'
24 | author = 'Marco Forgione'
25 |
26 | # The short X.Y version
27 | version = ''
28 | # The full version, including alpha/beta/rc tags
29 | release = '0.1'
30 |
31 |
32 | # -- General configuration ---------------------------------------------------
33 |
34 | # If your documentation needs a minimal Sphinx version, state it here.
35 | #
36 | # needs_sphinx = '1.0'
37 |
38 | # Add any Sphinx extension module names here, as strings. They can be
39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
40 | # ones.
41 | extensions = [
42 | 'sphinx.ext.autodoc',
43 | 'sphinx.ext.mathjax',
44 | 'numpydoc'
45 | ]
46 |
47 | # Add any paths that contain templates here, relative to this directory.
48 | templates_path = ['_templates']
49 |
50 | # The suffix(es) of source filenames.
51 | # You can specify multiple suffix as a list of string:
52 | #
53 | # source_suffix = ['.rst', '.md']
54 | source_suffix = '.rst'
55 |
56 | # The master toctree document.
57 | master_doc = 'index'
58 |
59 | # The language for content autogenerated by Sphinx. Refer to documentation
60 | # for a list of supported languages.
61 | #
62 | # This is also used if you do content translation via gettext catalogs.
63 | # Usually you set "language" from the command line for these cases.
64 | language = None
65 |
66 | # List of patterns, relative to source directory, that match files and
67 | # directories to ignore when looking for source files.
68 | # This pattern also affects html_static_path and html_extra_path.
69 | exclude_patterns = []
70 |
71 | # The name of the Pygments (syntax highlighting) style to use.
72 | pygments_style = None
73 |
74 |
75 | # -- Options for HTML output -------------------------------------------------
76 |
77 | # The theme to use for HTML and HTML Help pages. See the documentation for
78 | # a list of builtin themes.
79 | #
80 | html_theme = 'alabaster'
81 |
82 | # Theme options are theme-specific and customize the look and feel of a theme
83 | # further. For a list of options available for each theme, see the
84 | # documentation.
85 | #
86 | # html_theme_options = {}
87 |
88 | # Add any paths that contain custom static files (such as style sheets) here,
89 | # relative to this directory. They are copied after the builtin static files,
90 | # so a file named "default.css" will overwrite the builtin "default.css".
91 | html_static_path = ['_static']
92 |
93 | # Custom sidebar templates, must be a dictionary that maps document names
94 | # to template names.
95 | #
96 | # The default sidebars (for documents that don't match any pattern) are
97 | # defined by theme itself. Builtin themes are using these templates by
98 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
99 | # 'searchbox.html']``.
100 | #
101 | # html_sidebars = {}
102 |
103 |
104 | # -- Options for HTMLHelp output ---------------------------------------------
105 |
106 | # Output file base name for HTML help builder.
107 | htmlhelp_basename = 'pyMPCdoc'
108 |
109 |
110 | # -- Options for LaTeX output ------------------------------------------------
111 |
112 | latex_elements = {
113 | # The paper size ('letterpaper' or 'a4paper').
114 | #
115 | # 'papersize': 'letterpaper',
116 |
117 | # The font size ('10pt', '11pt' or '12pt').
118 | #
119 | # 'pointsize': '10pt',
120 |
121 | # Additional stuff for the LaTeX preamble.
122 | #
123 | # 'preamble': '',
124 |
125 | # Latex figure (float) alignment
126 | #
127 | # 'figure_align': 'htbp',
128 | }
129 |
130 | # Grouping the document tree into LaTeX files. List of tuples
131 | # (source start file, target name, title,
132 | # author, documentclass [howto, manual, or own class]).
133 | latex_documents = [
134 | (master_doc, 'pyMPC.tex', 'pyMPC Documentation',
135 | 'Marco Forgione', 'manual'),
136 | ]
137 |
138 |
139 | # -- Options for manual page output ------------------------------------------
140 |
141 | # One entry per manual page. List of tuples
142 | # (source start file, name, description, authors, manual section).
143 | man_pages = [
144 | (master_doc, 'pympc', 'pyMPC Documentation',
145 | [author], 1)
146 | ]
147 |
148 |
149 | # -- Options for Texinfo output ----------------------------------------------
150 |
151 | # Grouping the document tree into Texinfo files. List of tuples
152 | # (source start file, target name, title, author,
153 | # dir menu entry, description, category)
154 | texinfo_documents = [
155 | (master_doc, 'pyMPC', 'pyMPC Documentation',
156 | author, 'pyMPC', 'One line description of project.',
157 | 'Miscellaneous'),
158 | ]
159 |
160 |
161 | # -- Options for Epub output -------------------------------------------------
162 |
163 | # Bibliographic Dublin Core info.
164 | epub_title = project
165 |
166 | # The unique identifier of the text. This can be a ISBN number
167 | # or the project homepage.
168 | #
169 | # epub_identifier = ''
170 |
171 | # A unique identification for the text.
172 | #
173 | # epub_uid = ''
174 |
175 | # A list of files that should not be packed into the epub file.
176 | epub_exclude_files = ['search.html']
177 |
178 | numpydoc_show_class_members = False
179 | # -- Extension configuration -------------------------------------------------
180 |
--------------------------------------------------------------------------------
/doc/sphinx/source/index.rst:
--------------------------------------------------------------------------------
1 | .. pyMPC documentation master file, created by
2 | sphinx-quickstart on Tue May 28 18:47:14 2019.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | pyMPC
7 | =====
8 | ---------------------------------------------
9 | A python library for Model Predictive Control
10 | ---------------------------------------------
11 |
12 | pyMPC is an open-source python library for Model Predictive Control (MPC).
13 | The project is hosted on this `GitHub repository `_.
14 |
15 | Requirements
16 | ------------
17 |
18 | In order to run pyMPC, you need a python 3.x environment and the following packages:
19 |
20 | * `numpy `_
21 | * `scipy `_
22 | * `OSQP `_
23 | * `matplotlib `_
24 |
25 | The installation procedure below should take case of installing pyMPC with all its dependencies.
26 |
27 | Installation
28 | ------------
29 | 1. Copy or clone the pyMPC project into a local folder. For instance, run
30 |
31 | .. code-block:: bash
32 |
33 | git clone https://github.com/forgi86/pyMPC.git
34 |
35 | from the command line
36 |
37 | 2. Navigate to your local pyMPC folder
38 |
39 | .. code-block:: bash
40 |
41 | cd PYMPC_LOCAL_FOLDER
42 |
43 | where PYMPC_LOCAL_FOLDER is the folder where you have just downloaded the code in step 2
44 |
45 | 3. Install pyMPC in your python environment: run
46 |
47 | .. code-block:: bash
48 |
49 | pip install -e .
50 |
51 | from the command line, in the working folder PYMPC_LOCAL_FOLDER
52 |
53 | Usage
54 | -----
55 |
56 | This code snippets illustrates the use of the MPCController class:
57 |
58 | .. code-block:: python
59 |
60 | from pyMPC.mpc import MPCController
61 |
62 | ...
63 |
64 | K = MPCController(Ad,Bd,Np=20, x0=x0,xref=xref,uminus1=uminus1,
65 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
66 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax)
67 | K.setup()
68 |
69 | ...
70 |
71 | xstep = x0
72 | for i in range(nsim):
73 | uMPC = K.output()
74 | xstep = Ad.dot(xstep) + Bd.dot(uMPC) # system simulation steps
75 | K.update(xstep) # update with measurement
76 |
77 |
78 | Examples
79 | --------
80 |
81 | Full working examples are given in the `examples `_ folder on git:
82 | * `Point mass, full state feedback `_
83 | * `Cart-pole system, full state feedback `_
84 | * `Cart-pole system, with Kalman Filter `_
85 |
86 |
87 | Content
88 | -------
89 | .. toctree::
90 | :maxdepth: 2
91 |
92 | code
93 | math
94 |
95 |
96 |
97 | Indices and tables
98 | ------------------
99 |
100 | * :ref:`genindex`
101 | * :ref:`modindex`
102 | * :ref:`search`
103 |
--------------------------------------------------------------------------------
/doc/sphinx/source/math.rst:
--------------------------------------------------------------------------------
1 | The Math
2 | =================================
3 |
4 | pyMPC solves the following optimization problem:
5 |
6 | .. math::
7 | \newcommand{\MPC}{\mathrm{MPC}}
8 | \newcommand{\varx}{\mathbf{x}}
9 | \newcommand{\varu}{\mathbf{u}}
10 | \newcommand{\slack}{\epsilon}
11 | \newcommand{\QxN}{Q_{x_N}}
12 | \newcommand{\Qx}{Q_{x}}
13 | \newcommand{\Qu}{Q_{u}}
14 | \newcommand{\Qdu}{Q_{\Delta u}}
15 | \newcommand{\Np}{{N_p}}
16 | \newcommand{\Nc}{{N_c}}
17 | \newcommand{\blkdiag}{\text{blkdiag}}
18 |
19 | .. math::
20 |
21 | \begin{multline}
22 | \arg \min_{\mathbf{x},\mathbf{u}}
23 | \big(x_\Np - x_{ref}\big)^\top Q_x \big(x_\Np - x_{ref}\big) +
24 | \bigg [
25 | \sum_{k=0}^{\Np-1} \big(x_k - x_{ref}\big)^\top Q_x \big(x_k - x_{ref}\big) +
26 | \big(u_k - u_{ref}\big)^\top Q_u \big(u_k - u_{ref}\big) +
27 | \Delta u_k^\top Q_u \Delta u_k
28 | \bigg ]
29 | \end{multline}.
30 |
31 | Under the hood, pyMPC transforms the MPC optimization problem above in a form that can be solved using a standard QP
32 | solver:
33 |
34 | .. math::
35 |
36 | \begin{align}
37 | &\min \frac{1}{2} x_{q}^\top P + q^\top x_{q} \\
38 | &\text{subject to} \nonumber \\
39 | &l_{b} \leq Ax \leq u_{b}
40 | \end{align}
41 |
42 |
--------------------------------------------------------------------------------
/doc/sphinx/source/project.rst:
--------------------------------------------------------------------------------
1 | The pyMPC Project
2 | =================================
3 |
4 | The aim of the pyMPC project is to provide a Model Predictive Control (MPC) library in python.
5 |
--------------------------------------------------------------------------------
/doc/sphinx/source/sphyinx_install:
--------------------------------------------------------------------------------
1 | sudo add-apt-repository ppa:builds/sphinxsearch-stable
2 | sudo apt-get install sphinxsearch
3 |
--------------------------------------------------------------------------------
/examples/example_accelerate_brake.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 | from scipy.integrate import ode
7 |
8 |
9 | if __name__ == '__main__':
10 | # Constants #
11 | Ts = 0.2 # sampling time (s)
12 | M = 2 # mass (Kg)
13 | b = 0.3 # friction coefficient (N*s/m)
14 |
15 | # Continuous-time matrices (just for reference)
16 | Ac = np.array([
17 | [0.0, 1.0],
18 | [0, -b/M]]
19 | )
20 | Bc = np.array([
21 | [0.0],
22 | [1/M]
23 | ])
24 |
25 | Bc = np.c_[Bc, Bc] # Acceleration and Brake input
26 |
27 | def f_ODE(t,x,u):
28 | der = Ac @ x + Bc @ u
29 | return der
30 |
31 | [nx, nu] = Bc.shape # number of states and number or inputs
32 |
33 | # Simple forward euler discretization
34 | Ad = np.eye(nx) + Ac*Ts
35 | Bd = Bc*Ts
36 |
37 |
38 | # Reference input and states
39 | pref = 7.0
40 | vref = 0.0
41 | xref = np.array([pref, vref]) # reference state
42 | uref = np.array([0.0]) # reference input
43 | uminus1 = np.array([0.0, 0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
44 |
45 | # Constraints
46 | xmin = np.array([-100.0, -0.8])
47 | xmax = np.array([100.0, 0.8])
48 |
49 | umin = np.array([0, -1.0]) # Accelerator is positive, brake is negative
50 | umax = np.array([0.5, 0])
51 |
52 | Dumin = np.array([-np.inf, -np.inf])
53 | Dumax = np.array([np.inf, np.inf])
54 |
55 | # Objective function
56 | Qx = sparse.diags([10.0, 0.0]) # Quadratic cost for states x0, x1, ..., x_N-1
57 | QxN = sparse.diags([10.0, 0.0]) # Quadratic cost for xN
58 | Qu = sparse.diags([0.5, 0.2]) # Quadratic cost for u0, u1, ...., u_N-1
59 | QDu = sparse.diags([0.5, 0.2]) # Quadratic cost for Du0, Du1, ...., Du_N-1
60 |
61 | # Initial state
62 | x0 = np.array([0.1, 0.2]) # initial state
63 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
64 | system_dyn.set_initial_value(x0, 0)
65 | system_dyn.set_f_params(0.0)
66 |
67 | # Prediction horizon
68 | Np = 20
69 |
70 | K = MPCController(Ad, Bd, Np=Np, x0=x0, xref=xref, uminus1=uminus1,
71 | Qx=Qx, QxN=QxN, Qu=Qu, QDu=QDu,
72 | xmin=xmin, xmax=xmax, umin=umin, umax=umax, Dumin=Dumin, Dumax=Dumax)
73 | K.setup()
74 |
75 | # Simulate in closed loop
76 | [nx, nu] = Bd.shape # number of states and number or inputs
77 | len_sim = 15 # simulation length (s)
78 | nsim = int(len_sim/Ts) # simulation length(timesteps)
79 | xsim = np.zeros((nsim,nx))
80 | usim = np.zeros((nsim,nu))
81 | tcalc = np.zeros((nsim,1))
82 | tsim = np.arange(0,nsim)*Ts
83 |
84 |
85 |
86 | xstep = x0
87 | uMPC = uminus1
88 |
89 | time_start = time.time()
90 | for i in range(nsim):
91 | xsim[i, :] = xstep
92 |
93 | # MPC update and step. Could be in just one function call
94 | time_start = time.time()
95 | K.update(xstep, uMPC) # update with measurement
96 | uMPC = K.output() # MPC step (u_k value)
97 | tcalc[i,:] = time.time() - time_start
98 | usim[i,:] = uMPC
99 |
100 | #xstep = Ad.dot(xstep) + Bd.dot(uMPC) # Real system step (x_k+1 value)
101 | system_dyn.set_f_params(uMPC) # set current input value to uMPC
102 | system_dyn.integrate(system_dyn.t + Ts)
103 | xstep = system_dyn.y
104 |
105 |
106 | time_sim = time.time() - time_start
107 | fig,axes = plt.subplots(3,1, figsize=(10,10))
108 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
109 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
110 | axes[0].set_title("Position (m)")
111 |
112 | axes[1].plot(tsim, xsim[:,1], label="v")
113 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
114 | axes[1].set_title("Velocity (m/s)")
115 |
116 | axes[2].plot(tsim, usim[:, 0], label="u1")
117 | axes[2].plot(tsim, usim[:, 1], label="u2")
118 |
119 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
120 | axes[2].set_title("Force (N)")
121 |
122 | for ax in axes:
123 | ax.grid(True)
124 | ax.legend()
125 |
126 | plt.figure()
127 | plt.hist(tcalc*1000)
128 | plt.grid(True)
129 |
130 |
--------------------------------------------------------------------------------
/examples/example_inverted_pendulum.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 |
7 | if __name__ == '__main__':
8 |
9 | # Constants #
10 | M = 0.5
11 | m = 0.2
12 | b = 0.1
13 | ftheta = 0.1
14 | l = 0.3
15 | g = 9.81
16 |
17 | Ts = 50e-3
18 |
19 | Ac =np.array([[0, 1, 0, 0],
20 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
21 | [0, 0, 0, 1],
22 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
23 |
24 | Bc = np.array([
25 | [0.0],
26 | [1.0/M],
27 | [0.0],
28 | [-1/(M*l)]
29 | ])
30 |
31 | [nx, nu] = Bc.shape # number of states and number or inputs
32 |
33 | # Simple forward euler discretization
34 | Ad = np.eye(nx) + Ac*Ts
35 | Bd = Bc*Ts
36 |
37 | # Reference input and states
38 | xref = np.array([0.3, 0.0, 0.0, 0.0]) # reference state
39 | uref = np.array([0.0]) # reference input
40 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
41 |
42 | # Constraints
43 | xmin = np.array([-1.0, -100, -100, -100])
44 | xmax = np.array([0.3, 100.0, 100, 100])
45 |
46 | umin = np.array([-20])
47 | umax = np.array([20])
48 |
49 | Dumin = np.array([-5])
50 | Dumax = np.array([5])
51 |
52 | # Objective function weights
53 | Qx = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
54 | QxN = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for xN
55 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
56 | QDu = 0.01 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
57 |
58 | # Initial state
59 | phi0 = 15*2*np.pi/360
60 | x0 = np.array([0, 0, phi0, 0]) # initial state
61 |
62 | # Prediction horizon
63 | Np = 20
64 |
65 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
66 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
67 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
68 | eps_feas=1e3)
69 | K.setup()
70 |
71 | # Simulate in closed loop
72 | [nx, nu] = Bd.shape # number of states and number or inputs
73 | len_sim = 20 # simulation length (s)
74 | nsim = int(len_sim/Ts) # simulation length(timesteps)
75 | xsim = np.zeros((nsim,nx))
76 | usim = np.zeros((nsim,nu))
77 | tsim = np.arange(0,nsim)*Ts
78 |
79 | time_start = time.time()
80 |
81 | xstep = x0
82 | uMPC = uminus1
83 | for i in range(nsim):
84 | xsim[i,:] = xstep
85 |
86 | # MPC update and step. Could be in just one function call
87 | K.update(xstep, uMPC) # update with measurement
88 | uMPC = K.output() # MPC step (u_k value)
89 | usim[i,:] = uMPC
90 |
91 | # System simulation step
92 | F = uMPC
93 | v = xstep[1]
94 | theta = xstep[2]
95 | omega = xstep[3]
96 | der = np.zeros(nx)
97 | der[0] = v
98 | der[1] = (m*l*np.sin(theta)*omega**2 -m*g*np.sin(theta)*np.cos(theta) + m*ftheta*np.cos(theta)*omega + F - b*v)/(M+m*(1-np.cos(theta)**2));
99 | der[2] = omega
100 | der[3] = ((M+m)*(g*np.sin(theta) - ftheta*omega) - m*l*omega**2*np.sin(theta)*np.cos(theta) -(F-b*v)*np.cos(theta))/(l*(M + m*(1-np.cos(theta)**2)) );
101 | # Forward euler step
102 | xstep = xstep + der*Ts
103 |
104 | time_sim = time.time() - time_start
105 |
106 | fig,axes = plt.subplots(3, 1, figsize=(10, 10))
107 | axes[0].plot(tsim, xsim[:, 0], "k", label='p')
108 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="p_ref")
109 | axes[0].set_title("Position (m)")
110 |
111 | axes[1].plot(tsim, xsim[:, 2]*360/2/np.pi, label="phi")
112 | axes[1].plot(tsim, xref[2]*360/2/np.pi*np.ones(np.shape(tsim)), "r--", label="phi_ref")
113 | axes[1].set_title("Angle (deg)")
114 |
115 | axes[2].plot(tsim, usim[:, 0], label="u")
116 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="u_ref")
117 | axes[2].set_title("Force (N)")
118 |
119 | for ax in axes:
120 | ax.grid(True)
121 | ax.legend()
122 |
123 |
124 |
--------------------------------------------------------------------------------
/examples/example_inverted_pendulum_kalman.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 | from scipy.integrate import ode
7 | from pyMPC.kalman import kalman_design_simple, LinearStateEstimator
8 |
9 | if __name__ == '__main__':
10 |
11 | # Constants #
12 | M = 0.5
13 | m = 0.2
14 | b = 0.1
15 | ftheta = 0.1
16 | l = 0.3
17 | g = 9.81
18 |
19 | Ts = 5e-3
20 |
21 | Ac =np.array([[0, 1, 0, 0],
22 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
23 | [0, 0, 0, 1],
24 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
25 |
26 | Bc = np.array([
27 | [0.0],
28 | [1.0/M],
29 | [0.0],
30 | [-1/(M*l)]
31 | ])
32 |
33 | Cc = np.array([[1., 0., 0., 0.],
34 | [0., 0., 1., 0.]])
35 |
36 | Dc = np.zeros((2, 1))
37 |
38 | [nx, nu] = Bc.shape # number of states and number or inputs
39 | ny = np.shape(Cc)[0]
40 |
41 | # Nonlinear dynamics ODE
42 | def f_ODE(t,x,u):
43 | #print(x)
44 | F = u
45 | v = x[1]
46 | theta = x[2]
47 | omega = x[3]
48 | der = np.zeros(nx)
49 | der[0] = v
50 | der[1] = (m * l * np.sin(theta) * omega ** 2 - m * g * np.sin(theta) * np.cos(theta) + m * ftheta * np.cos(
51 | theta) * omega + F - b * v) / (M + m * (1 - np.cos(theta) ** 2))
52 | der[2] = omega
53 | der[3] = ((M + m) * (g * np.sin(theta) - ftheta * omega) - m * l * omega ** 2 * np.sin(theta) * np.cos(
54 | theta) - (
55 | F - b * v) * np.cos(theta)) / (l * (M + m * (1 - np.cos(theta) ** 2)))
56 | return der
57 |
58 | # Simple forward euler discretization
59 | Ad = np.eye(nx) + Ac*Ts
60 | Bd = Bc*Ts
61 | Cd = Cc
62 | Dd = Dc
63 |
64 | # Standard deviation of the measurement noise on position and angle
65 | std_npos = 0*0.005
66 | std_nphi = 0*0.005
67 |
68 | # Reference input and states
69 | xref = np.array([0.3, 0.0, 0.0, 0.0]) # reference state
70 | uref = np.array([0.0]) # reference input
71 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
72 |
73 | # Constraints
74 | xmin = np.array([-1.0, -100, -100, -100])
75 | xmax = np.array([1.0, 100.0, 100, 100])
76 |
77 | umin = np.array([-20])
78 | umax = np.array([20])
79 |
80 | Dumin = np.array([-5])
81 | Dumax = np.array([5])
82 |
83 | # Objective function weights
84 | Qx = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
85 | QxN = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for xN
86 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
87 | QDu = 0.01 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
88 |
89 | # Initialize simulation system
90 | phi0 = 15*2*np.pi/360
91 | x0 = np.array([0, 0, phi0, 0]) # initial state
92 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
93 | system_dyn.set_initial_value(x0, 0)
94 | system_dyn.set_f_params(0.0)
95 |
96 | # Basic Kalman filter design
97 | Q_kal = 10 * np.eye(nx)
98 | R_kal = np.eye(ny)
99 | L, P, W = kalman_design_simple(Ad, Bd, Cd, Dd, Q_kal, R_kal, type='filter')
100 | x0_est = x0
101 | KF = LinearStateEstimator(x0_est, Ad, Bd, Cd, Dd, L)
102 |
103 | # Prediction horizon
104 | Np = 200
105 |
106 | # Initialize controller
107 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
108 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
109 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
110 | eps_feas = 1e3)
111 | K.setup()
112 |
113 | # Simulate in closed loop
114 | [nx, nu] = Bd.shape # number of states and number or inputs
115 | len_sim = 10 # simulation length (s)
116 | nsim = int(len_sim / Ts) # simulation length(timesteps)
117 | x_vec = np.zeros((nsim, nx))
118 | #x_vec_EA = np.zeros((nsim, nx))
119 |
120 | y_vec = np.zeros((nsim, ny))
121 | y_meas_vec = np.zeros((nsim, ny))
122 | y_est_vec = np.zeros((nsim, ny))
123 | x_est_vec = np.zeros((nsim, nx))
124 | u_vec = np.zeros((nsim, nu))
125 | t_vec = np.arange(0, nsim) * Ts
126 | t_MPC_CPU = np.arange(0, nsim) * Ts
127 | x_MPC_pred = np.zeros((nsim, Np+1, nx)) # on-line predictions from the Kalman Filter
128 |
129 | time_start = time.time()
130 |
131 | uMPC = uminus1
132 | y_step = None
133 | ymeas_step = None
134 | #x_step = x0
135 | for i in range(nsim):
136 | # Output for step i
137 | # System
138 | y_step = Cd.dot(system_dyn.y) # y[i] from the system
139 | ymeas_step = y_step
140 | ymeas_step[0] += std_npos * np.random.randn()
141 | ymeas_step[1] += std_nphi * np.random.randn()
142 | # Estimator
143 |
144 | # time_MPC_start = time.time()
145 | uMPC, infoMPC = K.output(return_x_seq=True) # u[i] = k(\hat x[i]) possibly computed at time instant -1
146 | # t_MPC_CPU[i] = time.time() - time_MPC_start
147 |
148 | x_MPC_pred[i, :, :] = infoMPC['x_seq'] # x_MPC_pred[i,i+1,...| possibly computed at time instant -1]
149 |
150 | # Save output for step i
151 | y_vec[i, :] = y_step # y[i]
152 | y_meas_vec[i,:] = ymeas_step # y_meas[i]
153 | x_vec[i, :] = system_dyn.y # x[i]
154 | y_est_vec[i,:] = KF.y # \hat y[i|i-1]
155 | x_est_vec[i, :] = KF.x # \hat x[i|i-1]
156 | u_vec[i, :] = uMPC # u[i]
157 | # x_vec_EA[i,:] = x_step
158 |
159 | # Update i+1
160 | # System
161 | system_dyn.set_f_params(uMPC) # set current input value to uMPC
162 | system_dyn.integrate(system_dyn.t + Ts)
163 | #x_step = system_dyn.y
164 | # der = f_ODE(0,x_step,uMPC)
165 | #x_step = x_step + der * Ts # true system evolves to x[i+1]
166 | #system_dyn.set_initial_value(x_step, 0)
167 |
168 | # Kalman filter: update and predict
169 | KF.update(ymeas_step) # update \hat x[i|i-1] to \hat x[i|i] updated using ymeas[i]
170 | KF.predict(uMPC) # predict \hat x[i+1|i] using u[i]
171 |
172 | # MPC update for step i+1
173 | time_MPC_start = time.time()
174 | K.update(KF.x, uMPC) # update with measurement (and possibly pre-compute u[i+1])
175 | t_MPC_CPU[i] = time.time() - time_MPC_start
176 |
177 | time_sim = time.time() - time_start
178 |
179 | fig,axes = plt.subplots(3,1, figsize=(10,10))
180 | axes[0].plot(t_vec, x_vec[:,0], "k", label='p')
181 | axes[0].plot(t_vec, xref[0]*np.ones(np.shape(t_vec)), "r--", label="p_ref")
182 | #axes[0].plot(t_vec, x_vec_EA[:,0]*np.ones(np.shape(t_vec)), "r--", label="p_EA")
183 | axes[0].set_title("Position (m)")
184 |
185 | axes[1].plot(t_vec, x_vec[:,2]*360/2/np.pi, label="phi")
186 | axes[1].plot(t_vec, xref[2]*360/2/np.pi*np.ones(np.shape(t_vec)), "r--", label="phi_ref")
187 | #axes[1].plot(t_vec, x_vec_EA[:,2]*np.ones(np.shape(t_vec)), "r--", label="p_EA")
188 | axes[1].set_title("Angle (deg)")
189 |
190 | axes[2].plot(t_vec, u_vec[:,0], label="u")
191 | axes[2].plot(t_vec, uref*np.ones(np.shape(t_vec)), "r--", label="u_ref")
192 | axes[2].set_title("Force (N)")
193 |
194 | for ax in axes:
195 | ax.grid(True)
196 | ax.legend()
197 |
198 |
199 | fig,ax = plt.subplots(1,1, figsize=(5,5))
200 | ax.hist(t_MPC_CPU*1000, bins=100)
201 | ax.grid(True)
202 | ax.set_xlabel('MPC computation CPU time (ms)')
203 |
--------------------------------------------------------------------------------
/examples/example_point_mass.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 | from scipy.integrate import ode
7 |
8 |
9 | if __name__ == '__main__':
10 | # Constants #
11 | Ts = 0.2 # sampling time (s)
12 | M = 2 # mass (Kg)
13 | b = 0.3 # friction coefficient (N*s/m)
14 |
15 | # Continuous-time matrices (just for reference)
16 | Ac = np.array([
17 | [0.0, 1.0],
18 | [0, -b/M]]
19 | )
20 | Bc = np.array([
21 | [0.0],
22 | [1/M]
23 | ])
24 |
25 | def f_ODE(t,x,u):
26 | der = Ac @ x + Bc @ u
27 | return der
28 |
29 | [nx, nu] = Bc.shape # number of states and number or inputs
30 |
31 | # Simple forward euler discretization
32 | Ad = np.eye(nx) + Ac*Ts
33 | Bd = Bc*Ts
34 |
35 |
36 | # Reference input and states
37 | pref = 7.0
38 | vref = 0.0
39 | xref = np.array([pref, vref]) # reference state
40 | uref = np.array([0.0]) # reference input
41 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
42 |
43 | # Constraints
44 | xmin = np.array([-100.0, -100.0])
45 | xmax = np.array([100.0, 100.0])
46 |
47 | umin = np.array([-1.2])
48 | umax = np.array([1.2])
49 |
50 | Dumin = np.array([-2e-1])
51 | Dumax = np.array([2e-1])
52 |
53 | # Objective function
54 | Qx = sparse.diags([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
55 | QxN = sparse.diags([0.5, 0.1]) # Quadratic cost for xN
56 | Qu = 2.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
57 | QDu = 10.0 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
58 |
59 | # Initial state
60 | x0 = np.array([0.1, 0.2]) # initial state
61 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
62 | system_dyn.set_initial_value(x0, 0)
63 | system_dyn.set_f_params(0.0)
64 |
65 | # Prediction horizon
66 | Np = 20
67 |
68 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
69 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
70 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax)
71 | K.setup()
72 |
73 | # Simulate in closed loop
74 | [nx, nu] = Bd.shape # number of states and number or inputs
75 | len_sim = 15 # simulation length (s)
76 | nsim = int(len_sim/Ts) # simulation length(timesteps)
77 | xsim = np.zeros((nsim,nx))
78 | usim = np.zeros((nsim,nu))
79 | tcalc = np.zeros((nsim,1))
80 | tsim = np.arange(0,nsim)*Ts
81 |
82 |
83 |
84 | xstep = x0
85 | uMPC = uminus1
86 |
87 | time_start = time.time()
88 | for i in range(nsim):
89 | xsim[i,:] = xstep
90 |
91 | # MPC update and step. Could be in just one function call
92 | time_start = time.time()
93 | K.update(xstep, uMPC) # update with measurement
94 | uMPC = K.output() # MPC step (u_k value)
95 | tcalc[i,:] = time.time() - time_start
96 | usim[i,:] = uMPC
97 |
98 | #xstep = Ad.dot(xstep) + Bd.dot(uMPC) # Real system step (x_k+1 value)
99 | system_dyn.set_f_params(uMPC) # set current input value to uMPC
100 | system_dyn.integrate(system_dyn.t + Ts)
101 | xstep = system_dyn.y
102 |
103 |
104 | time_sim = time.time() - time_start
105 | fig,axes = plt.subplots(3,1, figsize=(10,10))
106 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
107 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
108 | axes[0].set_title("Position (m)")
109 |
110 | axes[1].plot(tsim, xsim[:,1], label="v")
111 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
112 | axes[1].set_title("Velocity (m/s)")
113 |
114 | axes[2].plot(tsim, usim[:,0], label="u")
115 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
116 | axes[2].set_title("Force (N)")
117 |
118 | for ax in axes:
119 | ax.grid(True)
120 | ax.legend()
121 |
122 | plt.figure()
123 | plt.hist(tcalc*1000)
124 | plt.grid(True)
125 |
126 |
--------------------------------------------------------------------------------
/examples/img/cart_pole.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forgi86/pyMPC/291db149554767a035fcb01df3fed7a6b3fe60e4/examples/img/cart_pole.png
--------------------------------------------------------------------------------
/pyMPC/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/pyMPC/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/pyMPC/.idea/pyMPC.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/pyMPC/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/pyMPC/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | 1559891914583
93 |
94 |
95 | 1559891914583
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/pyMPC/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forgi86/pyMPC/291db149554767a035fcb01df3fed7a6b3fe60e4/pyMPC/__init__.py
--------------------------------------------------------------------------------
/pyMPC/kalman.py:
--------------------------------------------------------------------------------
1 | import control
2 | import numpy as np
3 | import scipy as sp
4 |
5 | # conda install -c conda-forge slycot
6 |
7 |
8 | def __first_dim__(X):
9 | if sp.size(X) == 1:
10 | m = 1
11 | else:
12 | m = sp.size(X,0)
13 | return m
14 |
15 |
16 | def __second_dim__(X):
17 | if sp.size(X) == 1:
18 | m = 1
19 | else:
20 | m = sp.size(X,1)
21 | return m
22 |
23 |
24 | def kalman_design(A, B, C, D, Qn, Rn, Nn=None):
25 | """ General design a Kalman filter for the discrete-time system
26 | x_{k+1} = Ax_{k} + Bu_{k} + Gw_{k}
27 | y_{k} = Cx_{k} + Du_{k} + Hw_{k} + v_{k}
28 | with known inputs u and stochastic disturbances v, w.
29 | In particular, v and w are zero mean, white Gaussian noise sources with
30 | E[vv'] = Qn, E[ww'] = Rn, E[wv'] = Nn
31 |
32 | The Kalman filter has structure
33 | \hat x_{k+1|k+1} = A\hat x_{k|k} + Bu_{k} + L[y_{k+1} - C(A \hat x{k|k} + Bu_{k})]
34 | \hat y_{k|k} = Cx_{k|k}
35 |
36 | where L is the Kalman filter gain
37 |
38 | The Kalman predictor has structure
39 | \hat x_{k+1|k} = Ax_{k|k-1} + Bu_{k} + L[y_{k} - C\hat x{k|k-1}]
40 | \hat y_{k|k-1} = Cx_{k|k-1}
41 |
42 | where L is the Kalman predictor gain
43 | """
44 | nx = np.shape(A)[0]
45 | nw = np.shape(Qn)[0] # number of uncontrolled inputs
46 | nu = np.shape(B)[1] - nw # number of controlled inputs
47 | ny = np.shape(C)[0]
48 |
49 | if Nn is None:
50 | Nn = np.zeros((nw, ny))
51 |
52 | E = np.eye(nx)
53 | Bu = B[:, 0:nu]
54 | Du = D[:, 0:nu]
55 | Bw = B[:, nu:]
56 | Dw = D[:, nu:]
57 |
58 | Hn = Dw @ Nn
59 | Rb = Rn + Hn + np.transpose(Hn) + Dw @ Qn @ np.transpose(Dw)
60 | Qb = Bw @ Qn @ np.transpose(Bw)
61 | Nb = Bw @ (Qn @ np.transpose(Dw) + Nn)
62 |
63 | # Enforce symmetry
64 | Qb = (Qb + np.transpose(Qb))/2
65 | Rb = (Rb+np.transpose(Rb))/2
66 |
67 | P,W,K, = control.dare(np.transpose(A), np.transpose(C), Qb, Rb, Nb, np.transpose(E))
68 |
69 | L = np.transpose(K) # Kalman gain
70 | return L,P,W
71 |
72 |
73 | def kalman_design_simple(A, B, C, D, Qn, Rn, type='filter'):
74 | """ Simplified design a Kalman predictor or a Kalman filter for the discrete-time system
75 |
76 | x_{k+1} = Ax_{k} + Bu_{k} + Iw_{k}
77 | y_{k} = Cx_{k} + Du_{k} + I v_{k}
78 |
79 | with known inputs u and stochastic disturbances v, w.
80 | In particular, v and w are zero mean, white Gaussian noise sources with
81 | E[vv'] = Qn, E[ww'] = Rn, E[wv'] = 0
82 |
83 | The Kalman filter has structure
84 | \hat x_{k+1|k+1} = A\hat x_{k|k} + Bu_{k} + L[y_{k+1} - C(A \hat x{k|k} + Bu_{k})]
85 | \hat y_{k|k} = Cx_{k|k}
86 |
87 | where L is the Kalman filter gain
88 |
89 | The Kalman predictor has structure
90 | \hat x_{k+1|k} = Ax_{k|k-1} + Bu_{k} + L[y_{k} - C\hat x{k|k-1}]
91 | \hat y_{k|k-1} = Cx_{k|k-1}
92 |
93 | where L is the Kalman predictor gain
94 | """
95 |
96 | P,W,K, = control.dare(np.transpose(A), np.transpose(C), Qn, Rn)
97 | # L = np.transpose(K) # Kalman gain
98 |
99 | if type == 'filter':
100 | L = P@np.transpose(C) @ sp.linalg.basic.inv(C@P@np.transpose(C)+Rn)
101 | elif type == 'predictor':
102 | L = A@P@np.transpose(C) @ sp.linalg.basic.inv(C@P@np.transpose(C)+Rn)
103 | else:
104 | raise ValueError("Unknown Kalman design type. Specify either filter or predictor!")
105 |
106 | return L,P,W
107 |
108 |
109 | class LinearStateEstimator:
110 | def __init__(self, x0, A, B, C, D, L):
111 |
112 | self.x = np.copy(x0)
113 | self.y = C @ self.x
114 | self.A = A
115 | self.B = B
116 | self.C = C
117 | self.D = D
118 | self.L = L
119 |
120 | self.nx = __first_dim__(A)
121 | self.nu = __second_dim__(B) # number of controlled inputs
122 | self.ny = __first_dim__(C)
123 |
124 | def out_y(self,u):
125 | return self.y
126 |
127 | def predict(self, u):
128 | self.x = self.A @ self.x + self.B @u # x[k|k] -> x[k+1|k]
129 | self.y = self.C @ self.x # y[k|k] -> y[k+1|k]
130 | return self.x
131 |
132 | def update(self, y_meas):
133 | self.x = self.x + self.L @ (y_meas - self.y) # x[k+1|k] -> x[k+1|k+1]
134 | return self.x
135 |
136 | def sim(self, u_seq, x=None):
137 |
138 | if x is None:
139 | x = self.x
140 | Np = __first_dim__(u_seq)
141 | nu = __second_dim__(u_seq)
142 | assert(nu == self.nu)
143 |
144 | y = np.zeros((Np,self.ny))
145 | x_tmp = x
146 | for i in range(Np):
147 | u_tmp = u_seq[i]
148 | y[i,:] = self.C @ x_tmp + self.D @ u_tmp
149 | x_tmp = self.A @x_tmp + self.B @ u_tmp
150 |
151 | #y[Np] = self.C @ x_tmp + self.D @ u_tmp # not really true for D. Here it is 0 anyways
152 | return y
153 |
154 |
155 | if __name__ == '__main__':
156 |
157 | # Constants #
158 | Ts = 0.2 # sampling time (s)
159 | M = 2 # mass (Kg)
160 | b = 0.3 # friction coefficient (N*s/m)
161 |
162 | Ad = np.array([
163 | [1.0, Ts],
164 | [0, 1.0 -b/M*Ts]
165 | ])
166 |
167 | Bd = np.array([
168 | [0.0],
169 | [Ts/M]])
170 |
171 | Cd = np.array([[1, 0]])
172 | Dd = np.array([[0]])
173 |
174 | [nx, nu] = Bd.shape # number of states and number or inputs
175 | ny = np.shape(Cd)[0]
176 |
177 | ## General design ##
178 | Bd_kal = np.hstack([Bd, Bd])
179 | Dd_kal = np.array([[0, 0]])
180 | Q_kal = np.array([[100]]) # nw x nw matrix, w general (here, nw = nu)
181 | R_kal = np.eye(ny) # ny x ny)
182 | L_general,P_general,W_general = kalman_design(Ad, Bd_kal, Cd, Dd_kal, Q_kal, R_kal)
183 |
184 | # Simple design
185 | Q_kal = 10 * np.eye(nx)
186 | R_kal = np.eye(ny)
187 | L_simple,P_simple,W_simple = kalman_design_simple(Ad, Bd, Cd, Dd, Q_kal, R_kal)
188 |
189 | # Simple design written in general form
190 | Bd_kal = np.hstack([Bd, np.eye(nx)])
191 | Dd_kal = np.hstack([Dd, np.zeros((ny, nx))])
192 | Q_kal = 10 * np.eye(nx)#np.eye(nx) * 100
193 | R_kal = np.eye(ny) * 1
194 | L_gensim,P_gensim,W_gensim = kalman_design_simple(Ad, Bd, Cd, Dd, Q_kal, R_kal)
195 |
196 | assert(np.isclose(L_gensim[0], L_simple[0]))
197 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | setup(
4 | name='python-mpc',
5 | version='0.1.1',
6 | url='https://github.com/forgi86/pyMPC.git',
7 | author='Marco Forgione',
8 | author_email='marco.forgione1986@gmail.com',
9 | description='MPC package for python',
10 | packages=find_packages(),
11 | install_requires=['numpy', 'scipy', 'matplotlib', 'osqp'], # to be checked
12 | extras_require={
13 | 'cvx experiments': ["cvxpy"]
14 | }
15 | )
16 |
--------------------------------------------------------------------------------
/test_scripts/alternative/example_unconstrained_MPC.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy as sp
3 | import scipy.linalg
4 |
5 | if __name__ == '__main__':
6 |
7 | Ts = 0.2 # sampling time (s)
8 | M = 2 # mass (Kg)
9 | b = 0.3 # friction coefficient (N*s/m)
10 |
11 | Ad = np.array([
12 | [1.0, Ts],
13 | [0, 1.0 -b/M*Ts]
14 | ])
15 | Bd = np.array([
16 | [0.0],
17 | [Ts/M]])
18 |
19 | [nx, nu] = Bd.shape # number of states and number or inputs
20 | [nx1, nx2] = Ad.shape
21 |
22 | # Objective function
23 | Qx = np.diag([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
24 | QxN = np.diag([0.5, 0.1]) # Quadratic cost for xN
25 | Qu = 2.0 * np.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
26 | QDu = 10.0 * np.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
27 |
28 | assert(nx1 == nx2)
29 | assert(nx1 == nx)
30 |
31 | Np = 4
32 |
33 | # Initial state
34 | x0 = np.array([0.1, 0.2]) # initial state
35 | x0 = x0.reshape(-1,1)
36 | xref = np.ones(nx)
37 | Xref = np.kron(np.ones((Np,1)), xref.reshape(-1,1))
38 |
39 | uref = np.ones(nu)
40 | Uref = np.kron(np.ones((Np,1)), uref.reshape(-1,1))
41 |
42 | uminus1 = np.ones(nu)
43 | uminus1 = uminus1.reshape(-1,1)
44 |
45 | A_cal = np.empty((Np*nx, nx))
46 | B_cal = np.zeros((Np*nx, Np*nu))
47 |
48 | for k in range(0, Np):
49 | if k==0:
50 | A_km1 = np.eye(nx)
51 | else:
52 | A_km1 = A_cal[(k - 1) * nx:(k) * nx, 0:nx]
53 | A_cal[k * nx:(k + 1) * nx, 0:nx] = Ad @ A_km1
54 |
55 | for k in range(Np):
56 | if k == 0:
57 | A_k = np.eye(nx)
58 | else:
59 | A_k = A_cal[(k - 1) * nx:(k) * nx, 0:nx]
60 | A_kB = A_k @ Bd
61 | for p in range(Np-k):
62 | B_cal[(k+p)*nx:(k+p+1)*nx, p*nu:(p+1)*nu] = A_kB
63 |
64 |
65 | Q_cal_x = scipy.linalg.block_diag(np.kron(np.eye(Np-1), Qx), # x0...x_N-1
66 | QxN) # xN
67 | Q_cal_u = scipy.linalg.block_diag(np.kron(np.eye(Np), Qu)) # x0...x_N-1
68 |
69 | iDu = 2 * np.eye(Np) - np.eye(Np, k=1) - np.eye(Np, k=-1)
70 | iDu[Np - 1, Np - 1] = 1
71 | Q_cal_Du = np.kron(iDu, QDu)
72 |
73 |
74 | P = np.transpose(B_cal) @ Q_cal_x @ B_cal
75 | P_inv = np.linalg.inv(P)
76 | p_ubar = np.vstack([-QDu, # u0
77 | np.zeros(((Np - 1)*nu, nu))]) # u1..uN-1
78 |
79 | p_x0 = np.transpose(B_cal) @ Q_cal_x @ A_cal
80 | p_Xref = -np.transpose(B_cal) @ Q_cal_x
81 | p_Uref = - Q_cal_u
82 |
83 | k_x0 = - P_inv @ p_x0
84 | k_Xref = -P_inv @ p_Xref
85 | k_ubar = -P_inv @ p_ubar
86 | k_Uref = -P_inv @ p_Uref
87 |
88 | p_tot = p_x0 @ x0 + p_Xref @ Xref + p_ubar @ uminus1 + p_Uref @ Uref
89 |
90 | u_MPC_all = -np.linalg.solve(P,p_tot)
91 |
92 | u_MPC_all = u_MPC_all.reshape(-1,nu)
93 | u_MPC = u_MPC_all[0,:]
94 |
95 | #u_MPC_all_2 = k_x0 @ x0 + k_Xref @ Xref + k_Uref @ Uref + k_ubar @ uminus1
96 |
--------------------------------------------------------------------------------
/test_scripts/cvx_mpc_reference_governor.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import scipy.sparse as sparse
4 | import time
5 | import control
6 | from cvxpy import Variable, Parameter, Minimize, Problem, OSQP, quad_form
7 |
8 |
9 | if __name__ == "__main__":
10 |
11 | len_sim = 120 # simulation length (s)
12 |
13 | # Discrete time model of a frictionless mass (pure integrator)
14 | Ts = 1.0
15 | r_den = 0.9 # magnitude of poles
16 | wo_den = 0.2 # phase of poles (approx 2.26 kHz)
17 |
18 | # Build a second-order discrete-time dynamics with dcgain=1 (inner loop model)
19 | H_noise = control.TransferFunction([1], [1, -2 * r_den * np.cos(wo_den), r_den ** 2], Ts)
20 | H_noise = H_noise / control.dcgain(H_noise)
21 | H_ss = control.ss(H_noise)
22 |
23 | Ad = np.array(H_ss.A)
24 | Bd = np.array(H_ss.B)
25 | Cd = np.array(H_ss.C)
26 | Dd = np.array(H_ss.D)
27 | [nx, nu] = Bd.shape # number of states and number or inputs
28 | [ny, _] = Cd.shape # number of outputs
29 |
30 | # Constraints
31 | uref = 0
32 | uinit = 0 # not used here
33 | umin = np.array([-1000.0]) - uref
34 | umax = np.array([1000.0]) - uref
35 |
36 | ymin = np.array([-100.0])
37 | ymax = np.array([100.0])
38 |
39 | # Objective function
40 | Qy = np.diag([20]) # or sparse.diags([])
41 | QyN = np.diag([20]) # final cost
42 | Qu = 0.1 * np.eye(1)
43 |
44 | # Initial and reference
45 | x0 = np.array([0.0, 0.0]) # initial state
46 | r = 1.0 # Reference output
47 |
48 | # Prediction horizon
49 | Np = 40
50 |
51 | # Define problem
52 | u = Variable((nu, Np))
53 | x = Variable((nx, Np + 1))
54 | x_init = Parameter(nx)
55 | objective = 0
56 | constraints = [x[:, 0] == x_init]
57 | y = Cd @ x
58 | for k in range(Np):
59 | objective += quad_form(y[:, k] - r, Qy) \
60 | + quad_form(u[:, k], Qu) # objective function
61 | constraints += [x[:, k+1] == Ad@x[:, k] + Bd@u[:, k]] # system dynamics constraint
62 | constraints += [ymin <= x[:, k], x[:, k] <= ymax] # state interval constraint
63 | constraints += [umin <= u[:, k], u[:, k] <= umax] # input interval constraint
64 | objective += quad_form(y[:, Np] - r, QyN)
65 | prob = Problem(Minimize(objective), constraints)
66 |
67 | # Simulate in closed loop
68 | nsim = int(len_sim/Ts) # simulation length(timesteps)
69 | xsim = np.zeros((nsim, nx))
70 | ysim = np.zeros((nsim, ny))
71 | usim = np.zeros((nsim, nu))
72 | tsol = np.zeros((nsim, 1))
73 | tsim = np.arange(0, nsim)*Ts
74 |
75 | # uminus1_val = uinit # initial previous measured input is the input at time instant -1.
76 | time_start = time.time()
77 | for i in range(nsim):
78 |
79 | ysim[i, :] = Cd @ x0
80 | x_init.value = x0 # set value to the x_init cvx parameter to x0
81 |
82 | time_start = time.time()
83 | prob.solve(solver=OSQP, warm_start=True)
84 | tsol[i] = 1000*(time.time() - time_start)
85 |
86 | uMPC = u[:, 0].value
87 | usim[i, :] = uMPC
88 | x0 = Ad.dot(x0) + Bd.dot(uMPC)
89 | xsim[i, :] = x0
90 |
91 | # uminus1_val = uMPC # or a measurement if the input is affected by noise
92 | time_sim = time.time() - time_start
93 |
94 | # In[Plot time traces]
95 | fig, axes = plt.subplots(3, 1, figsize=(10, 10))
96 | axes[0].plot(tsim, ysim[:, 0], "k", label='p')
97 | axes[0].plot(tsim, r * np.ones(np.shape(tsim)), "r--", label="pref")
98 | axes[0].set_title("Output (-)")
99 |
100 |
101 | axes[2].plot(tsim, usim[:, 0], label="u")
102 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
103 | axes[2].set_title("Input (N)")
104 |
105 | for ax in axes:
106 | ax.grid(True)
107 | ax.legend()
108 |
109 | # In[Timing]
110 | plt.figure()
111 | plt.hist(tsol[1:])
112 | plt.xlabel("MPC solution time (ms)")
113 |
114 | print(f"First MPC execution takes {tsol[0, 0]:.0f} ms")
--------------------------------------------------------------------------------
/test_scripts/cvx_mpc_reference_governor_du.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import scipy
4 | import scipy.sparse as sparse
5 | import time
6 | import control
7 | from cvxpy import Variable, Parameter, Minimize, Problem, OSQP, quad_form
8 |
9 |
10 | if __name__ == "__main__":
11 |
12 | len_sim = 120 # simulation length (s)
13 |
14 | # Discrete time model of a frictionless mass (pure integrator)
15 | Ts = 1.0
16 | r_den = 0.9 # magnitude of poles
17 | wo_den = 0.2 # phase of poles (approx 2.26 kHz)
18 |
19 | # Build a second-order discrete-time dynamics with dcgain=1 (inner loop model)
20 | H_sys = control.TransferFunction([1], [1, -2 * r_den * np.cos(wo_den), r_den ** 2], Ts)
21 | H_sys = H_sys / control.dcgain(H_sys)
22 | H_ss = control.ss(H_sys)
23 |
24 | Ad = np.array(H_ss.A)
25 | Bd = np.c_[Bd, 1 / 2 * Bd]
26 | Cd = np.array(H_ss.C)
27 | Dd = np.array(H_ss.D)
28 |
29 | Ad = scipy.linalg.block_diag(Ad, Ad)
30 | Bd = scipy.linalg.block_diag(Bd, Bd)
31 |
32 | [nx, nu] = Bd.shape # number of states and number or inputs
33 | [ny, _] = Cd.shape # number of outputs
34 |
35 | # Constraints
36 | gref = 0
37 | ginit = np.array([0.0]) #
38 | gmin = np.array([-1000.0]) - gref
39 | gmax = np.array([1000.0]) - gref
40 |
41 | ymin = np.array([-100.0])
42 | ymax = np.array([100.0])
43 |
44 | Dgmin = np.array([-2e-1])
45 | Dgmax = np.array([2e-1])
46 |
47 |
48 | # Objective function
49 | Qy = np.diag([20]) # or sparse.diags([])
50 | QyN = np.diag([20]) # final cost
51 | #Qg = 0.01 * np.eye(1)
52 | QDg = 0.5 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
53 |
54 | # Initial and reference
55 | x0 = np.array([0.0, 0.0]) # initial state
56 | r = 1.0 # Reference output
57 |
58 | # Prediction horizon
59 | Np = 40
60 |
61 | # Define problem
62 | u = Variable((nu, Np))
63 | x = Variable((nx, Np + 1))
64 | x_init = Parameter(nx)
65 | uminus1 = Parameter(nu) # input at time instant negative one (from previous MPC window or uinit in the first MPC window)
66 |
67 | objective = 0
68 | constraints = [x[:, 0] == x_init]
69 | y = Cd @ x
70 | for k in range(Np):
71 | if k > 0:
72 | objective += quad_form(u[:, k] - u[:, k - 1], QDg) # \sum_{k=1}^{N_p-1} (uk - u_k-1)'QDu(uk - u_k-1)
73 | else: # at k = 0...
74 | # if uminus1[0] is not np.nan: # if there is an uold to be considered
75 | objective += quad_form(u[:, k] - uminus1, QDg) # ... penalize the variation of u0 with respect to uold
76 |
77 | objective += quad_form(y[:, k] - r, Qy)
78 | #objective += quad_form(u[:, k], Qg) # objective function
79 |
80 | constraints += [x[:, k+1] == Ad@x[:, k] + Bd@u[:, k]] # system dynamics constraint
81 | constraints += [ymin <= x[:, k], x[:, k] <= ymax] # state interval constraint
82 | constraints += [gmin <= u[:, k], u[:, k] <= gmax] # input interval constraint
83 |
84 | if k > 0:
85 | constraints += [Dgmin <= u[:, k] - u[:, k - 1], u[:, k] - u[:, k - 1] <= Dgmax]
86 | else: # at k = 0...
87 | # if uminus1[0] is not np.nan:
88 | constraints += [Dgmin <= u[:, k] - uminus1, u[:, k] - uminus1 <= Dgmax]
89 |
90 |
91 | objective += quad_form(y[:, Np] - r, QyN)
92 | prob = Problem(Minimize(objective), constraints)
93 |
94 | # Simulate in closed loop
95 | nsim = int(len_sim/Ts) # simulation length(timesteps)
96 | xsim = np.zeros((nsim, nx))
97 | ysim = np.zeros((nsim, ny))
98 | usim = np.zeros((nsim, nu))
99 | tsol = np.zeros((nsim, 1))
100 | tsim = np.arange(0, nsim)*Ts
101 |
102 | uMPC = ginit # initial previous measured input is the input at time instant -1.
103 | time_start = time.time()
104 | for i in range(nsim):
105 |
106 | ysim[i, :] = Cd @ x0
107 | x_init.value = x0 # set value to the x_init cvx parameter to x0
108 | uminus1.value = uMPC
109 |
110 | time_start = time.time()
111 | prob.solve(solver=OSQP, warm_start=True)
112 | tsol[i] = 1000*(time.time() - time_start)
113 |
114 | uMPC = u[:, 0].value
115 | usim[i, :] = uMPC
116 | x0 = Ad.dot(x0) + Bd.dot(uMPC)
117 | xsim[i, :] = x0
118 |
119 | time_sim = time.time() - time_start
120 |
121 | # In[Plot time traces]
122 | fig, axes = plt.subplots(3, 1, figsize=(10, 10))
123 | axes[0].plot(tsim, ysim[:, 0], "k", label='p')
124 | axes[0].plot(tsim, r * np.ones(np.shape(tsim)), "r--", label="pref")
125 | axes[0].set_title("Output (-)")
126 |
127 |
128 | axes[2].plot(tsim, usim[:, 0], label="u")
129 | axes[2].plot(tsim, gref * np.ones(np.shape(tsim)), "r--", label="uref")
130 | axes[2].set_title("Input (N)")
131 |
132 | for ax in axes:
133 | ax.grid(True)
134 | ax.legend()
135 |
136 | # In[Timing]
137 | plt.figure()
138 | plt.hist(tsol[1:])
139 | plt.xlabel("MPC solution time (ms)")
140 |
141 | print(f"First MPC execution takes {tsol[0, 0]:.0f} ms")
--------------------------------------------------------------------------------
/test_scripts/cvx_mpc_reference_governor_du_mimo.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import scipy
4 | import scipy.sparse as sparse
5 | import time
6 | import control
7 | from cvxpy import Variable, Parameter, Minimize, Problem, OSQP, quad_form
8 |
9 |
10 | if __name__ == "__main__":
11 |
12 | len_sim = 120 # simulation length (s)
13 |
14 | # Discrete time model of a frictionless mass (pure integrator)
15 | Ts = 1.0
16 | r_den = 0.9 # magnitude of poles
17 | wo_den = 0.2 # phase of poles (approx 2.26 kHz)
18 |
19 | # Build a second-order discrete-time dynamics with dcgain=1 (inner loop model)
20 | H_sys = control.TransferFunction([1], [1, -2 * r_den * np.cos(wo_den), r_den ** 2], Ts)
21 | H_sys = H_sys / control.dcgain(H_sys)
22 | H_ss = control.ss(H_sys)
23 |
24 | # SISO ABCD
25 | Ad = np.array(H_ss.A)
26 | Bd = np.array(H_ss.B)
27 | Cd = np.array(H_ss.C)
28 | Dd = np.array(H_ss.D)
29 |
30 | # MIMO ABCD
31 | Ad = scipy.linalg.block_diag(Ad, Ad)
32 | Bd = scipy.linalg.block_diag(Bd, Bd)
33 | Cd = scipy.linalg.block_diag(Cd, 1.5*Cd)
34 | Dd = scipy.linalg.block_diag(Dd, Dd)
35 |
36 | [nx, ng] = Bd.shape # number of states and number or inputs
37 | [ny, _] = Cd.shape # number of outputs
38 |
39 | # Constraints
40 | ginit = np.array(2*[0.0]) #
41 | gmin = np.array(2*[-1000.0]) #- gref
42 | gmax = np.array(2*[1000.0]) #- gref
43 |
44 | ymin = np.array(2*[-100.0])
45 | ymax = np.array(2*[100.0])
46 |
47 | Dgmin = np.array(2*[-2e-1])
48 | Dgmax = np.array(2*[2e-1])
49 |
50 |
51 | # Objective function
52 | Qy = np.diag(2*[20]) # or sparse.diags([])
53 | #QyN = np.diag(2*[20]) # final cost
54 | QDy = np.eye(ny)
55 | Qrg = 100*np.eye(ny)
56 | QDg = 0.5 * sparse.eye(ny) # Quadratic cost for Du0, Du1, ...., Du_N-1
57 |
58 | # Initial and reference
59 | x0 = np.array(2*[0.0, 0.0]) # initial state
60 |
61 | # Prediction horizon
62 | Np = 40
63 |
64 | # Define problem
65 | g = Variable((ng, Np))
66 | x = Variable((nx, Np))
67 | x_init = Parameter(nx)
68 | gminus1 = Parameter(ny) # input at time instant negative one (from previous MPC window or uinit in the first MPC window)
69 | yminus1 = Parameter(ny) # input at time instant negative one (from previous MPC window or uinit in the first MPC window)
70 | r = Parameter(ny)
71 |
72 | objective = 0.0
73 | constraints = [x[:, 0] == x_init]
74 | y = Cd @ x + Dd @g
75 |
76 | for k in range(Np):
77 | objective += quad_form(y[:, k] - r, Qy) # tracking cost
78 | objective += quad_form(g[:, k] - r, Qrg) # reference governor cost
79 | if k > 0:
80 | objective += quad_form(g[:, k] - g[:, k - 1], QDg)
81 | objective += quad_form(y[:, k] - y[:, k - 1], QDy)
82 | else: # at k = 0...
83 | objective += quad_form(g[:, k] - gminus1, QDg) # ... penalize the variation of u0 with respect to uold
84 | objective += quad_form(y[:, k] - yminus1, QDy) # ... penalize the variation of u0 with respect to uold
85 |
86 | #objective += quad_form(u[:, k], Qg) # objective function
87 |
88 | if k < Np - 1:
89 | constraints += [x[:, k+1] == Ad @x[:, k] + Bd @ g[:, k]] # system dynamics constraint
90 | constraints += [ymin <= y[:, k], y[:, k] <= ymax] # state interval constraint
91 | constraints += [gmin <= g[:, k], g[:, k] <= gmax] # input interval constraint
92 |
93 | if k > 0:
94 | constraints += [Dgmin <= g[:, k] - g[:, k - 1], g[:, k] - g[:, k - 1] <= Dgmax]
95 | else: # at k = 0...
96 | # if uminus1[0] is not np.nan:
97 | constraints += [Dgmin <= g[:, k] - gminus1, g[:, k] - gminus1 <= Dgmax]
98 |
99 | #objective += quad_form(y[:, Np] - r, QyN)
100 |
101 | prob = Problem(Minimize(objective), constraints)
102 |
103 | # Simulate in closed loop
104 | nsim = int(len_sim/Ts) # simulation length(timesteps)
105 | xsim = np.zeros((nsim, nx))
106 | ysim = np.zeros((nsim, ny))
107 | gsim = np.zeros((nsim, ng))
108 | tsol = np.zeros((nsim, 1))
109 | tsim = np.arange(0, nsim)*Ts
110 |
111 | gMPC = ginit # initial previous measured input is the input at time instant -1.
112 | time_start = time.time()
113 | for i in range(nsim):
114 |
115 | yold = Cd @ x0 + Dd @ gMPC
116 | ysim[i, :] = yold
117 |
118 | x_init.value = x0 # set value to the x_init cvx parameter to x0
119 | gminus1.value = gMPC
120 | yminus1.value = yold
121 | r.value = np.array(2*[1.0]) # Reference output
122 |
123 | time_start = time.time()
124 | prob.solve(solver=OSQP, warm_start=True)
125 | tsol[i] = 1000*(time.time() - time_start)
126 |
127 | gMPC = g[:, 0].value
128 | gsim[i, :] = gMPC
129 | x0 = Ad.dot(x0) + Bd.dot(gMPC)
130 | xsim[i, :] = x0
131 |
132 | time_sim = time.time() - time_start
133 |
134 | # In[Plot time traces]
135 | fig, axes = plt.subplots(3, 1, figsize=(10, 10))
136 | axes[0].plot(tsim, ysim[:, 0], "k", label='p')
137 | #axes[0].plot(tsim, r * np.ones(np.shape(tsim)), "r--", label="pref")
138 | axes[0].set_title("Output (-)")
139 |
140 | axes[1].plot(tsim, ysim[:, 1], "k", label='p')
141 | #axes[0].plot(tsim, r * np.ones(np.shape(tsim)), "r--", label="pref")
142 | axes[1].set_title("Output (-)")
143 |
144 | axes[2].plot(tsim, gsim[:, 0], label="u")
145 | axes[2].plot(tsim, gsim[:, 1], label="u")
146 | #axes[2].plot(tsim, gref * np.ones(np.shape(tsim)), "r--", label="uref")
147 | axes[2].set_title("Input (N)")
148 |
149 | for ax in axes:
150 | ax.grid(True)
151 | ax.legend()
152 |
153 | # In[Timing]
154 | plt.figure()
155 | plt.hist(tsol[1:])
156 | plt.xlabel("MPC solution time (ms)")
157 |
158 | print(f"First MPC execution takes {tsol[0, 0]:.0f} ms")
--------------------------------------------------------------------------------
/test_scripts/cvx_mpc_simple.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import scipy.sparse as sparse
4 | import time
5 | from cvxpy import Variable, Parameter, Minimize, Problem, OSQP, quad_form
6 |
7 |
8 | if __name__ == "__main__":
9 |
10 | # Discrete time model of a frictionless mass (pure integrator)
11 | Ts = 0.2
12 | M = 2.0
13 |
14 | Ad = sparse.csc_matrix([
15 | [1.0, Ts],
16 | [0, 1.0]
17 | ])
18 | Bd = sparse.csc_matrix([
19 | [0.0],
20 | [Ts/M]])
21 |
22 | [nx, nu] = Bd.shape # number of states and number or inputs
23 |
24 | # Constraints
25 | uref = 0
26 | uinit = 0 # not used here
27 | umin = np.array([-1000.0]) - uref
28 | umax = np.array([1000.0]) - uref
29 |
30 | xmin = np.array([-100.0, -100.0])
31 | xmax = np.array([100.0, 100.0])
32 |
33 | # Objective function
34 | Q = sparse.diags([0.2, 0.3])
35 | QN = sparse.diags([0.4, 0.5]) # final cost
36 | R = 0.1*sparse.eye(1)
37 |
38 | # Initial and reference states
39 | x0 = np.array([0.1, 0.2]) # initial state
40 | # Reference input and states
41 | pref = 7.0
42 | vref = 0
43 | xref = np.array([pref, vref]) # reference state
44 |
45 | # Prediction horizon
46 | Np = 20
47 |
48 | # Define problem
49 | u = Variable((nu, Np))
50 | x = Variable((nx, Np + 1))
51 | x_init = Parameter(nx)
52 | objective = 0
53 | constraints = [x[:, 0] == x_init]
54 | for k in range(Np):
55 | objective += quad_form(x[:, k] - xref, Q) + quad_form(u[:, k], R) # objective function
56 | constraints += [x[:, k+1] == Ad@x[:, k] + Bd@u[:, k]] # system dynamics constraint
57 | constraints += [xmin <= x[:, k], x[:, k] <= xmax] # state interval constraint
58 | constraints += [umin <= u[:, k], u[:, k] <= umax] # input interval constraint
59 | objective += quad_form(x[:, Np] - xref, QN)
60 | prob = Problem(Minimize(objective), constraints)
61 |
62 | # Simulate in closed loop
63 | len_sim = 15 # simulation length (s)
64 | nsim = int(len_sim/Ts) # simulation length(timesteps)
65 | xsim = np.zeros((nsim, nx))
66 | usim = np.zeros((nsim, nu))
67 | tsol = np.zeros((nsim, 1))
68 | tsim = np.arange(0, nsim)*Ts
69 |
70 | # uminus1_val = uinit # initial previous measured input is the input at time instant -1.
71 | time_start = time.time()
72 | for i in range(nsim):
73 | x_init.value = x0 # set value to the x_init cvx parameter to x0
74 |
75 | time_start = time.time()
76 | prob.solve(solver=OSQP, warm_start=True)
77 | tsol[i] = 1000*(time.time() - time_start)
78 |
79 | uMPC = u[:, 0].value
80 | usim[i, :] = uMPC
81 | x0 = Ad.dot(x0) + Bd.dot(uMPC)
82 | xsim[i, :] = x0
83 |
84 | # uminus1_val = uMPC # or a measurement if the input is affected by noise
85 | time_sim = time.time() - time_start
86 |
87 | # In[Plot time traces]
88 | fig, axes = plt.subplots(3, 1, figsize=(10, 10))
89 | axes[0].plot(tsim, xsim[:, 0], "k", label='p')
90 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
91 | axes[0].set_title("Position (m)")
92 |
93 | axes[1].plot(tsim, xsim[:, 1], label="v")
94 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
95 | axes[1].set_title("Velocity (m/s)")
96 |
97 | axes[2].plot(tsim, usim[:, 0], label="u")
98 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
99 | axes[2].set_title("Force (N)")
100 |
101 | for ax in axes:
102 | ax.grid(True)
103 | ax.legend()
104 |
105 | # In[Timing]
106 | plt.figure()
107 | plt.hist(tsol[1:])
108 | plt.xlabel("MPC solution time (ms)")
109 |
110 | print(f"First MPC execution takes {tsol[0, 0]:.0f} ms")
--------------------------------------------------------------------------------
/test_scripts/example_inverted_pendulum.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 | import warnings
7 |
8 | if __name__ == '__main__':
9 |
10 | warnings.filterwarnings('error')
11 |
12 | # Constants #
13 | M = 0.5 # big mass
14 | m = 0.2 # small mass
15 | b = 0.1 # friction big mass
16 | ftheta = 0.1 # friction joint
17 | l = 0.3 # rod length
18 | g = 9.81 # gravity
19 |
20 | Ts = 50e-3
21 |
22 | Ac = np.array([[0, 1, 0, 0],
23 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
24 | [0, 0, 0, 1],
25 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
26 |
27 | Bc = np.array([
28 | [0.0],
29 | [1.0/M],
30 | [0.0],
31 | [-1/(M*l)]
32 | ])
33 |
34 | [nx, nu] = Bc.shape # number of states and number or inputs
35 |
36 | # Simple forward euler discretization
37 | Ad = np.eye(nx) + Ac*Ts
38 | Bd = Bc*Ts
39 |
40 | # Reference input and states
41 | xref = np.array([0.3, 0.0, 0.0, 0.0]) # reference state
42 | uref = np.array([0.0]) # reference input
43 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
44 |
45 | # Constraints
46 | xmin = np.array([-1.0, -100.0, -100.0, -100.0])
47 | xmax = np.array([1.0, 100.0, 100.0, 100.0])
48 |
49 | umin = np.array([-20.0])
50 | umax = np.array([20.0])
51 |
52 | Dumin = np.array([-5.0])
53 | Dumax = np.array([5.0])
54 |
55 | # Objective function weights
56 | Qx = sparse.diags([.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
57 | QxN = sparse.diags([.3, 0, 1.0, 0]) # Quadratic cost for xN
58 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
59 | QDu = 0.01 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
60 |
61 | # Initial state
62 | phi0 = 15*2*np.pi/360
63 | x0 = np.array([0, 0, phi0, 0]) # initial state
64 |
65 | # Prediction horizon
66 | Np = 20
67 | Nc = 20
68 |
69 | K = MPCController(Ad,Bd,Np=Np, Nc=Nc, x0=x0,xref=xref,uminus1=uminus1,
70 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
71 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
72 | eps_feas = 1e3)
73 | K.setup()
74 |
75 | # Simulate in closed loop
76 | [nx, nu] = Bd.shape # number of states and number or inputs
77 | len_sim = 20 # simulation length (s)
78 | nsim = int(len_sim/Ts) # simulation length(timesteps)
79 | xsim = np.zeros((nsim,nx))
80 | usim = np.zeros((nsim,nu))
81 | tsim = np.arange(0,nsim)*Ts
82 |
83 | time_start = time.time()
84 |
85 | xstep = x0
86 | uMPC = uminus1
87 | for i in range(nsim):
88 | xsim[i, :] = xstep
89 |
90 | # MPC update and step. Could be in just one function call
91 | K.update(xstep, uMPC) # update with measurement
92 | uMPC = K.output() # MPC step (u_k value)
93 | usim[i,:] = uMPC
94 |
95 | # System simulation step
96 | F = uMPC
97 | v = xstep[1]
98 | theta = xstep[2]
99 | omega = xstep[3]
100 | der = np.zeros(nx)
101 | der[0] = v
102 | der[1] = (m*l*np.sin(theta)*omega**2 -m*g*np.sin(theta)*np.cos(theta) + m*ftheta*np.cos(theta)*omega + F - b*v)/(M+m*(1-np.cos(theta)**2));
103 | der[2] = omega
104 | der[3] = ((M+m)*(g*np.sin(theta) - ftheta*omega) - m*l*omega**2*np.sin(theta)*np.cos(theta) -(F-b*v)*np.cos(theta))/(l*(M + m*(1-np.cos(theta)**2)) );
105 | # Forward euler step
106 | xstep = xstep + der*Ts
107 |
108 | time_sim = time.time() - time_start
109 |
110 | fig,axes = plt.subplots(3,1, figsize=(10,10))
111 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
112 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="p_ref")
113 | axes[0].set_title("Position (m)")
114 |
115 | axes[1].plot(tsim, xsim[:,2]*360/2/np.pi, label="phi")
116 | axes[1].plot(tsim, xref[2]*360/2/np.pi*np.ones(np.shape(tsim)), "r--", label="phi_ref")
117 | axes[1].set_title("Angle (deg)")
118 |
119 | axes[2].plot(tsim, usim[:,0], label="u")
120 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="u_ref")
121 | axes[2].set_title("Force (N)")
122 |
123 | for ax in axes:
124 | ax.grid(True)
125 | ax.legend()
126 |
127 |
128 |
--------------------------------------------------------------------------------
/test_scripts/example_inverted_pendulum_1ms.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from scipy.integrate import ode
6 | from scipy.interpolate import interp1d
7 | from pyMPC.mpc import MPCController
8 |
9 | if __name__ == '__main__':
10 |
11 | # Constants #
12 | M = 0.5
13 | m = 0.2
14 | b = 0.1
15 | ftheta = 0.1
16 | l = 0.3
17 | g = 9.81
18 |
19 | Ts_MPC = 25e-3
20 | Ts_sim = 1e-3
21 | ratio_Ts = int(Ts_MPC//Ts_sim)
22 |
23 | Ac =np.array([[0, 1, 0, 0],
24 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
25 | [0, 0, 0, 1],
26 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
27 |
28 | Bc = np.array([
29 | [0.0],
30 | [1.0/M],
31 | [0.0],
32 | [-1/(M*l)]
33 | ])
34 |
35 | [nx, nu] = Bc.shape # number of states and number or inputs
36 |
37 | # Nonlinear dynamics ODE
38 | def f_ODE(t,x,u):
39 | #print(x)
40 | F = u
41 | v = x[1]
42 | theta = x[2]
43 | omega = x[3]
44 | der = np.zeros(4)
45 | der[0] = v
46 | der[1] = (m * l * np.sin(theta) * omega ** 2 - m * g * np.sin(theta) * np.cos(theta) + m * ftheta * np.cos(
47 | theta) * omega + F - b * v) / (M + m * (1 - np.cos(theta) ** 2))
48 | der[2] = omega
49 | der[3] = ((M + m) * (g * np.sin(theta) - ftheta * omega) - m * l * omega ** 2 * np.sin(theta) * np.cos(
50 | theta) - (
51 | F - b * v) * np.cos(theta)) / (l * (M + m * (1 - np.cos(theta) ** 2)))
52 | return der
53 |
54 | # Brutal forward euler discretization
55 | Ad = np.eye(nx) + Ac * Ts_MPC
56 | Bd = Bc * Ts_MPC
57 |
58 | # Reference input and states
59 | t_ref_vec = np.array([0.0, 10.0, 20.0, 30.0, 40.0])
60 | p_ref_vec = np.array([0.0, 0.3, 0.3, 0.0, 0.0])
61 | rp_fun = interp1d(t_ref_vec, p_ref_vec, kind='zero')
62 | r_fun = lambda t: np.array([rp_fun(t), 0.0, 0.0, 0.0])
63 |
64 | xref = np.array([rp_fun(0), 0.0, 0.0, 0.0]) # reference state
65 | uref = np.array([0.0]) # reference input
66 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
67 |
68 | # Constraints
69 | xmin = np.array([-1.0, -100, -100, -100])
70 | xmax = np.array([0.3, 100.0, 100, 100])
71 |
72 | umin = np.array([-20])
73 | umax = np.array([20])
74 |
75 | Dumin = np.array([-5])
76 | Dumax = np.array([5])
77 |
78 | # Objective function weights
79 | Qx = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
80 | QxN = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for xN
81 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
82 | QDu = 0.01 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
83 |
84 | # Initial state
85 | phi0 = 15*2*np.pi/360
86 | x0 = np.array([0, 0, phi0, 0]) # initial state
87 | t0 = 0
88 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
89 | system_dyn.set_initial_value(x0, t0)
90 | system_dyn.set_f_params(0.0)
91 |
92 | # Prediction horizon
93 | Np = 30
94 |
95 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
96 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
97 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
98 | eps_feas = 1e3)
99 | K.setup()
100 |
101 | # Simulate in closed loop
102 | [nx, nu] = Bd.shape # number of states and number or inputs
103 | len_sim = 40 # simulation length (s)
104 | nsim = int(len_sim / Ts_MPC) # simulation length(timesteps)
105 | x_vec = np.zeros((nsim, nx))
106 | xref_vec = np.zeros((nsim, nx))
107 | u_vec = np.zeros((nsim, nu))
108 | t_vec = np.zeros((nsim,1))
109 |
110 | nsim_fast = int(len_sim / Ts_sim)
111 | xsim_fast = np.zeros((nsim_fast, nx)) # finer integration grid for performance evaluation
112 | xref_fast = np.zeros((nsim_fast, nx)) # finer integration grid for performance evaluatio
113 | t_vec_fast = np.zeros((nsim_fast, 1))
114 | time_start = time.time()
115 |
116 | t_step = t0
117 | uMPC = None
118 | idx_MPC = 0 # slow index increasing for the multiples of Ts_MPC
119 | for idx_fast in range(nsim_fast):
120 | idx_MPC = idx_fast // ratio_Ts
121 | run_MPC = (idx_fast % ratio_Ts) == 0
122 |
123 | xref_fast[idx_fast, :] = r_fun(t_step)
124 | xsim_fast[idx_fast, :] = system_dyn.y
125 | t_vec_fast[idx_fast, :] = t_step
126 | if run_MPC: # it is also a step of the simulation at rate Ts_MPC
127 | x_vec[idx_MPC, :] = system_dyn.y
128 | t_vec[idx_MPC, :] = t_step
129 |
130 | # MPC update and step. Could be in just one function call
131 | if run_MPC:
132 | xref = r_fun(t_step) # reference state
133 | xref_vec[idx_MPC,:] = xref
134 | K.update(system_dyn.y, uMPC, xref=xref) # update with measurement
135 | uMPC = K.output() # MPC step (u_k value)
136 | u_vec[idx_MPC, :] = uMPC
137 |
138 | # System simulation step
139 | if run_MPC:
140 | system_dyn.set_f_params(uMPC) # set current input value
141 |
142 | system_dyn.integrate(t_step + Ts_sim)
143 |
144 | # Update simulation time
145 | t_step += Ts_sim
146 |
147 | idx_MPC += 1
148 |
149 | time_sim = time.time() - time_start
150 |
151 | fig,axes = plt.subplots(3,1, figsize=(10,10))
152 | axes[0].plot(t_vec, x_vec[:, 0], "k", label='p')
153 | axes[0].plot(t_vec, xref_vec[:,0], "r--", label="p_ref")
154 | axes[0].set_title("Position (m)")
155 |
156 | axes[1].plot(t_vec, x_vec[:, 2] * 360 / 2 / np.pi, label="phi")
157 | axes[1].plot(t_vec, xref_vec[:,2] * 360 / 2 / np.pi, "r--", label="phi_ref")
158 | axes[1].set_title("Angle (deg)")
159 |
160 | axes[2].plot(t_vec, u_vec[:, 0], label="u")
161 | axes[2].plot(t_vec, uref * np.ones(np.shape(t_vec)), "r--", label="u_ref")
162 | axes[2].set_title("Force (N)")
163 |
164 | for ax in axes:
165 | ax.grid(True)
166 | ax.legend()
167 |
--------------------------------------------------------------------------------
/test_scripts/example_inverted_pendulum_disturbance.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from scipy.integrate import ode
6 | from scipy.interpolate import interp1d
7 | from pyMPC.mpc import MPCController
8 | import control
9 | import control.matlab
10 |
11 |
12 | if __name__ == '__main__':
13 |
14 | # Constants #
15 | M = 0.5
16 | m = 0.2
17 | b = 0.1
18 | ftheta = 0.1
19 | l = 0.3
20 | g = 9.81
21 |
22 | Ts_MPC = 25e-3
23 | Ts_sim = 1e-3
24 | ratio_Ts = int(Ts_MPC//Ts_sim)
25 |
26 | Ac =np.array([[0, 1, 0, 0],
27 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
28 | [0, 0, 0, 1],
29 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
30 |
31 | Bc = np.array([
32 | [0.0],
33 | [1.0/M],
34 | [0.0],
35 | [-1/(M*l)]
36 | ])
37 |
38 | [nx, nu] = Bc.shape # number of states and number or inputs
39 |
40 | # Nonlinear dynamics ODE
41 | def f_ODE(t,x,u):
42 | #print(x)
43 | F = u
44 | v = x[1]
45 | theta = x[2]
46 | omega = x[3]
47 | der = np.zeros(4)
48 | der[0] = v
49 | der[1] = (m * l * np.sin(theta) * omega ** 2 - m * g * np.sin(theta) * np.cos(theta) + m * ftheta * np.cos(
50 | theta) * omega + F - b * v) / (M + m * (1 - np.cos(theta) ** 2))
51 | der[2] = omega
52 | der[3] = ((M + m) * (g * np.sin(theta) - ftheta * omega) - m * l * omega ** 2 * np.sin(theta) * np.cos(
53 | theta) - (
54 | F - b * v) * np.cos(theta)) / (l * (M + m * (1 - np.cos(theta) ** 2)))
55 | return der
56 |
57 | # Brutal forward euler discretization
58 | Ad = np.eye(nx) + Ac * Ts_MPC
59 | Bd = Bc * Ts_MPC
60 |
61 | # Force disturbance
62 | wu = 10 # bandwidth of the force disturbance
63 | std_du = 0.3
64 | Ts = 1e-3
65 | Hu = control.TransferFunction([1], [1 / wu, 1])
66 | Hu = Hu * Hu
67 | Hud = control.matlab.c2d(Hu, Ts)
68 | t_imp = np.arange(5000) * Ts
69 | t, y = control.impulse_response(Hud, t_imp)
70 | y = y[0]
71 | std_tmp = np.sqrt(np.sum(y ** 2)) # np.sqrt(trapz(y**2,t))
72 | Hu = Hu / (std_tmp) * std_du
73 | N_sim = 100000
74 | e = np.random.randn(N_sim)
75 | te = np.arange(N_sim) * Ts
76 | _, d_fast, _ = control.forced_response(Hu, te, e)
77 | d_fast = d_fast[1000:]
78 |
79 | # Reference input and states
80 | t_ref_vec = np.array([0.0, 10.0, 20.0, 30.0, 40.0])
81 | p_ref_vec = np.array([0.0, 0.3, 0.3, 0.0, 0.0])
82 | rp_fun = interp1d(t_ref_vec, p_ref_vec, kind='zero')
83 | r_fun = lambda t: np.array([rp_fun(t), 0.0, 0.0, 0.0])
84 |
85 | xref = np.array([rp_fun(0), 0.0, 0.0, 0.0]) # reference state
86 | uref = np.array([0.0]) # reference input
87 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
88 |
89 | # Constraints
90 | xmin = np.array([-1.0, -100, -100, -100])
91 | xmax = np.array([0.5, 100.0, 100, 100])
92 |
93 | umin = np.array([-20])
94 | umax = np.array([20])
95 |
96 | Dumin = np.array([-5])
97 | Dumax = np.array([5])
98 |
99 | # Objective function weights
100 | Qx = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
101 | QxN = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for xN
102 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
103 | QDu = 0.001 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
104 |
105 | # Initial state
106 | phi0 = 15*2*np.pi/360
107 | x0 = np.array([0, 0, phi0, 0]) # initial state
108 | t0 = 0
109 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
110 | system_dyn.set_initial_value(x0, t0)
111 | system_dyn.set_f_params(0.0)
112 |
113 | # Prediction horizon
114 | Np = 30
115 |
116 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
117 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
118 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
119 | eps_feas = 1e3)
120 | K.setup()
121 |
122 | # Simulate in closed loop
123 | [nx, nu] = Bd.shape # number of states and number or inputs
124 | len_sim = 40 # simulation length (s)
125 | nsim = int(len_sim / Ts_MPC) # simulation length(timesteps)
126 | x_vec = np.zeros((nsim, nx))
127 | xref_vec = np.zeros((nsim, nx))
128 | u_vec = np.zeros((nsim, nu))
129 | t_vec = np.zeros((nsim,1))
130 |
131 | nsim_fast = int(len_sim / Ts_sim)
132 | x_vec_fast = np.zeros((nsim_fast, nx)) # finer integration grid for performance evaluation
133 | xref_vec_fast = np.zeros((nsim_fast, nx)) # finer integration grid for performance evaluatio
134 | u_vec_fast = np.zeros((nsim_fast, nu))
135 | t_vec_fast = np.zeros((nsim_fast, 1))
136 | time_start = time.time()
137 |
138 | t_step = t0
139 | uMPC = None
140 | idx_MPC = 0 # slow index increasing for the multiples of Ts_MPC
141 | for idx_fast in range(nsim_fast):
142 | idx_MPC = idx_fast // ratio_Ts
143 | run_MPC = (idx_fast % ratio_Ts) == 0
144 |
145 | xref_vec_fast[idx_fast, :] = r_fun(t_step)
146 | x_vec_fast[idx_fast, :] = system_dyn.y
147 | if run_MPC: # it is also a step of the simulation at rate Ts_MPC
148 | x_vec[idx_MPC, :] = system_dyn.y
149 | t_vec[idx_MPC, :] = t_step
150 |
151 | # MPC update and step. Could be in just one function call
152 | if run_MPC:
153 | xref = r_fun(t_step) # reference state
154 | xref_vec[idx_MPC,:] = xref
155 | K.update(system_dyn.y, uMPC, xref=xref) # update with measurement
156 | uMPC = K.output() # MPC step (u_k value)
157 | u_vec[idx_MPC, :] = uMPC
158 |
159 | u_fast = uMPC + d_fast[idx_fast]
160 | u_vec_fast[idx_fast,:] = u_fast
161 | t_vec_fast[idx_fast,:] = t_step
162 |
163 |
164 | # System simulation step
165 | if run_MPC:
166 | pass #system_dyn.set_f_params(uMPC)# + d_fast[idx_fast]/10) # set current input value
167 | system_dyn.set_f_params(u_fast)
168 | system_dyn.integrate(t_step + Ts_sim)
169 |
170 | # Update simulation time
171 | t_step += Ts_sim
172 |
173 | idx_MPC += 1
174 |
175 | time_sim = time.perf_counter() - time_start
176 |
177 | fig,axes = plt.subplots(3,1, figsize=(10,10))
178 | axes[0].plot(t_vec_fast, x_vec_fast[:, 0], "k", label='p')
179 | axes[0].plot(t_vec_fast, xref_vec_fast[:,0], "r--", label="p_ref")
180 | axes[0].set_title("Position (m)")
181 |
182 | axes[1].plot(t_vec_fast, x_vec_fast[:, 2] * 360 / 2 / np.pi, label="phi")
183 | axes[1].plot(t_vec_fast, xref_vec_fast[:,2] * 360 / 2 / np.pi, "r--", label="phi_ref")
184 | axes[1].set_title("Angle (deg)")
185 |
186 | axes[2].step(t_vec_fast, u_vec_fast[:, 0], label="F")
187 | axes[2].step(t_vec, u_vec[:, 0], label="F_MPC")
188 | axes[2].plot(t_vec, uref * np.ones(np.shape(t_vec)), "r--", label="u_ref")
189 | axes[2].set_title("Force (N)")
190 |
191 | for ax in axes:
192 | ax.grid(True)
193 | ax.legend()
194 |
--------------------------------------------------------------------------------
/test_scripts/example_inverted_pendulum_ode.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from scipy.integrate import ode
6 | from pyMPC.mpc import MPCController
7 |
8 | if __name__ == '__main__':
9 |
10 | # Constants #
11 | M = 0.5
12 | m = 0.2
13 | b = 0.1
14 | ftheta = 0.1
15 | l = 0.3
16 | g = 9.81
17 |
18 | Ts = 5e-3
19 |
20 | Ac =np.array([[0, 1, 0, 0],
21 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
22 | [0, 0, 0, 1],
23 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
24 |
25 | Bc = np.array([
26 | [0.0],
27 | [1.0/M],
28 | [0.0],
29 | [-1/(M*l)]
30 | ])
31 |
32 | [nx, nu] = Bc.shape # number of states and number or inputs
33 |
34 | # Nonlinear dynamics ODE
35 | def f_ODE(t,x,u):
36 | #print(x)
37 | F = u
38 | v = x[1]
39 | theta = x[2]
40 | omega = x[3]
41 | der = np.zeros(nx)
42 | der[0] = v
43 | der[1] = (m * l * np.sin(theta) * omega ** 2 - m * g * np.sin(theta) * np.cos(theta) + m * ftheta * np.cos(
44 | theta) * omega + F - b * v) / (M + m * (1 - np.cos(theta) ** 2))
45 | der[2] = omega
46 | der[3] = ((M + m) * (g * np.sin(theta) - ftheta * omega) - m * l * omega ** 2 * np.sin(theta) * np.cos(
47 | theta) - (
48 | F - b * v) * np.cos(theta)) / (l * (M + m * (1 - np.cos(theta) ** 2)))
49 | return der
50 |
51 | # Brutal forward euler discretization
52 | Ad = np.eye(nx) + Ac*Ts
53 | Bd = Bc*Ts
54 |
55 | # Reference input and states
56 | xref = np.array([0.3, 0.0, 0.0, 0.0]) # reference state
57 | uref = np.array([0.0]) # reference input
58 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
59 |
60 | # Constraints
61 | xmin = np.array([-1.0, -100, -100, -100])
62 | xmax = np.array([0.3, 100.0, 100, 100])
63 |
64 | umin = np.array([-20])
65 | umax = np.array([20])
66 |
67 | Dumin = np.array([-5])
68 | Dumax = np.array([5])
69 |
70 | # Objective function weights
71 | Qx = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
72 | QxN = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for xN
73 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
74 | QDu = 0.01 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
75 |
76 | # Initial state
77 | phi0 = 15*2*np.pi/360
78 | x0 = np.array([0, 0, phi0, 0]) # initial state
79 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
80 | system_dyn.set_initial_value(x0, 0)
81 | system_dyn.set_f_params(0.0)
82 |
83 | # Prediction horizon
84 | Np = 20
85 |
86 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
87 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
88 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
89 | eps_feas = 1e3)
90 | K.setup()
91 |
92 | # Simulate in closed loop
93 | [nx, nu] = Bd.shape # number of states and number or inputs
94 | len_sim = 20 # simulation length (s)
95 | nsim = int(len_sim/Ts) # simulation length(timesteps)
96 | xsim = np.zeros((nsim,nx))
97 | usim = np.zeros((nsim,nu))
98 | tsim = np.arange(0,nsim)*Ts
99 |
100 | time_start = time.time()
101 |
102 | xstep = x0
103 | uMPC = uminus1
104 | for i in range(nsim):
105 | xsim[i,:] = xstep
106 |
107 | # MPC update and step. Could be in just one function call
108 | K.update(xstep, uMPC) # update with measurement
109 | uMPC = K.output() # MPC step (u_k value)
110 | usim[i,:] = uMPC
111 |
112 | # System simulation step
113 | system_dyn.set_f_params(uMPC) # set current input value
114 | system_dyn.integrate((i+1)*Ts)
115 | xstep = system_dyn.y
116 |
117 | time_sim = time.time() - time_start
118 |
119 | fig,axes = plt.subplots(3,1, figsize=(10,10))
120 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
121 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="p_ref")
122 | axes[0].set_title("Position (m)")
123 |
124 | axes[1].plot(tsim, xsim[:,2]*360/2/np.pi, label="phi")
125 | axes[1].plot(tsim, xref[2]*360/2/np.pi*np.ones(np.shape(tsim)), "r--", label="phi_ref")
126 | axes[1].set_title("Angle (deg)")
127 |
128 | axes[2].plot(tsim, usim[:,0], label="u")
129 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="u_ref")
130 | axes[2].set_title("Force (N)")
131 |
132 | for ax in axes:
133 | ax.grid(True)
134 | ax.legend()
135 |
136 |
137 |
--------------------------------------------------------------------------------
/test_scripts/example_inverted_pendulum_reference.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from scipy.integrate import ode
6 | from scipy.interpolate import interp1d
7 | from pyMPC.mpc import MPCController
8 |
9 | if __name__ == '__main__':
10 |
11 | # Constants #
12 | M = 0.5
13 | m = 0.2
14 | b = 0.1
15 | ftheta = 0.1
16 | l = 0.3
17 | g = 9.81
18 |
19 | Ts = 25e-3
20 |
21 | Ac =np.array([[0, 1, 0, 0],
22 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
23 | [0, 0, 0, 1],
24 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
25 |
26 | Bc = np.array([
27 | [0.0],
28 | [1.0/M],
29 | [0.0],
30 | [-1/(M*l)]
31 | ])
32 |
33 | [nx, nu] = Bc.shape # number of states and number or inputs
34 |
35 | # Nonlinear dynamics ODE
36 | def f_ODE(t,x,u):
37 | #print(x)
38 | F = u
39 | v = x[1]
40 | theta = x[2]
41 | omega = x[3]
42 | der = np.zeros(4)
43 | der[0] = v
44 | der[1] = (m * l * np.sin(theta) * omega ** 2 - m * g * np.sin(theta) * np.cos(theta) + m * ftheta * np.cos(
45 | theta) * omega + F - b * v) / (M + m * (1 - np.cos(theta) ** 2))
46 | der[2] = omega
47 | der[3] = ((M + m) * (g * np.sin(theta) - ftheta * omega) - m * l * omega ** 2 * np.sin(theta) * np.cos(
48 | theta) - (
49 | F - b * v) * np.cos(theta)) / (l * (M + m * (1 - np.cos(theta) ** 2)))
50 | return der
51 |
52 | # Brutal forward euler discretization
53 | Ad = np.eye(nx) + Ac*Ts
54 | Bd = Bc*Ts
55 |
56 | # Reference input and states
57 | t_ref_vec = np.array([0.0, 10.0, 20.0, 30.0, 40.0])
58 | p_ref_vec = np.array([0.0, 0.3, 0.3, 0.0, 0.0])
59 | rp_fun = interp1d(t_ref_vec, p_ref_vec, kind='zero')
60 | r_fun = lambda t: np.array([rp_fun(t), 0.0, 0.0, 0.0])
61 |
62 | xref = np.array([rp_fun(0), 0.0, 0.0, 0.0]) # reference state
63 | uref = np.array([0.0]) # reference input
64 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
65 |
66 | # Constraints
67 | xmin = np.array([-1.0, -100, -100, -100])
68 | xmax = np.array([0.3, 100.0, 100, 100])
69 |
70 | umin = np.array([-20])
71 | umax = np.array([20])
72 |
73 | Dumin = np.array([-5])
74 | Dumax = np.array([5])
75 |
76 | # Objective function weights
77 | Qx = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
78 | QxN = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for xN
79 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
80 | QDu = 0.01 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
81 |
82 | # Initial state
83 | phi0 = 15*2*np.pi/360
84 | x0 = np.array([0, 0, phi0, 0]) # initial state
85 | t0 = 0
86 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
87 | system_dyn.set_initial_value(x0, t0)
88 | system_dyn.set_f_params(0.0)
89 |
90 | # Prediction horizon
91 | Np = 30
92 |
93 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
94 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
95 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
96 | eps_feas = 1e3)
97 | K.setup()
98 |
99 | # Simulate in closed loop
100 | [nx, nu] = Bd.shape # number of states and number or inputs
101 | len_sim = 40 # simulation length (s)
102 | nsim = int(len_sim/Ts) # simulation length(timesteps)
103 | xsim = np.zeros((nsim,nx))
104 | usim = np.zeros((nsim,nu))
105 | tsim = np.arange(0,nsim)*Ts
106 |
107 | time_start = time.time()
108 |
109 | t_step = t0
110 | uMPC = uminus1
111 | for i in range(nsim):
112 | xsim[i,:] = system_dyn.y
113 |
114 | # MPC update and step. Could be in just one function call
115 | xref = r_fun(t_step) # reference state
116 | K.update(system_dyn.y, uMPC, xref=xref) # update with measurement
117 | uMPC = K.output() # MPC step (u_k value)
118 | usim[i,:] = uMPC
119 |
120 | # System simulation step
121 | system_dyn.set_f_params(uMPC) # set current input value
122 | system_dyn.integrate(t_step + Ts)
123 |
124 | # Update simulation time
125 | t_step += Ts
126 |
127 | time_sim = time.time() - time_start
128 |
129 | fig,axes = plt.subplots(3,1, figsize=(10,10))
130 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
131 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="p_ref")
132 | axes[0].set_title("Position (m)")
133 |
134 | axes[1].plot(tsim, xsim[:,2]*360/2/np.pi, label="phi")
135 | axes[1].plot(tsim, xref[2]*360/2/np.pi*np.ones(np.shape(tsim)), "r--", label="phi_ref")
136 | axes[1].set_title("Angle (deg)")
137 |
138 | axes[2].plot(tsim, usim[:,0], label="u")
139 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="u_ref")
140 | axes[2].set_title("Force (N)")
141 |
142 | for ax in axes:
143 | ax.grid(True)
144 | ax.legend()
145 |
--------------------------------------------------------------------------------
/test_scripts/example_mpc_function.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 |
7 |
8 |
9 | if __name__ == '__main__':
10 | # Constants #
11 | Ts = 0.2 # sampling time (s)
12 | M = 2 # mass (Kg)
13 | b = 0.3 # friction coefficient (N*s/m)
14 |
15 | Ad = sparse.csc_matrix([
16 | [1.0, Ts],
17 | [0, 1.0 -b/M*Ts]
18 | ])
19 | Bd = sparse.csc_matrix([
20 | [0.0],
21 | [Ts/M]])
22 |
23 | # Continous-time matrices (just for reference)
24 | Ac = np.array([
25 | [0.0, 1.0],
26 | [0, -b/M]]
27 | )
28 | Bc = np.array([
29 | [0.0],
30 | [1/M]
31 | ])
32 |
33 | # Reference input and states
34 | pref = 7.0
35 | vref = 0.0
36 | xref = np.array([pref, vref]) # reference state
37 | uref = np.array([0.0]) # reference input
38 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
39 |
40 | # Constraints
41 | xmin = np.array([-100.0, -100.0])
42 | xmax = np.array([100.0, 100.0])
43 |
44 | umin = np.array([-1.2])
45 | umax = np.array([1.2])
46 |
47 | Dumin = np.array([-2e-1])
48 | Dumax = np.array([2e-1])
49 |
50 | # Objective function
51 | Qx = sparse.diags([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
52 | QxN = sparse.diags([0.5, 0.1]) # Quadratic cost for xN
53 | Qu = 2.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
54 | QDu = 10.0 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
55 |
56 | # Initial state
57 | x0 = np.array([0.1, 0.2]) # initial state
58 |
59 | # Prediction horizon
60 | Np = 20
61 |
62 | K = MPCController(Ad,Bd,Np=20, x0=x0,xref=xref,uminus1=uminus1,
63 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
64 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax)
65 | K.setup()
66 |
67 | # Simulate in closed loop
68 | [nx, nu] = Bd.shape # number of states and number or inputs
69 | len_sim = 15 # simulation length (s)
70 | nsim = int(len_sim/Ts) # simulation length(timesteps)
71 | xsim = np.zeros((nsim,nx))
72 | usim = np.zeros((nsim,nu))
73 | tsim = np.arange(0,nsim)*Ts
74 |
75 | time_start = time.time()
76 |
77 | xtmp = x0
78 | utmp = uminus1
79 | for i in range(nsim):
80 | uMPC = K.__controller_function__(xtmp, utmp)
81 | xtmp = Ad.dot(xtmp) + Bd.dot(uMPC) # system step
82 | utmp = uMPC
83 | xsim[i,:] = xtmp
84 | usim[i,:] = uMPC
85 |
86 | time_sim = time.time() - time_start
87 |
88 | fig,axes = plt.subplots(3,1, figsize=(10,10))
89 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
90 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
91 | axes[0].set_title("Position (m)")
92 |
93 | axes[1].plot(tsim, xsim[:,1], label="v")
94 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
95 | axes[1].set_title("Velocity (m/s)")
96 |
97 | axes[2].plot(tsim, usim[:,0], label="u")
98 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
99 | axes[2].set_title("Force (N)")
100 |
101 | for ax in axes:
102 | ax.grid(True)
103 | ax.legend()
104 |
105 | N_mc = 10000
106 | data = np.empty((N_mc, nx + nu + nu))
107 | for i in range(N_mc):
108 | x = np.random.random(nx)
109 | uminus1 = np.random.random(nu)
110 | uMPC = K.__controller_function__(x,uminus1)
111 | data[i,:] = np.hstack((x,uminus1, uMPC))
112 |
--------------------------------------------------------------------------------
/test_scripts/kalman/MPC_observer.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 | from kalman import kalman_filter
7 |
8 | if __name__ == '__main__':
9 |
10 | # Constants #
11 | M = 0.5
12 | m = 0.2
13 | b = 0.1
14 | ftheta = 0.1
15 | l = 0.3
16 | g = 9.81
17 |
18 | Ts = 25e-3
19 |
20 | Ac =np.array([[0, 1, 0, 0],
21 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
22 | [0, 0, 0, 1],
23 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
24 |
25 | Bc = np.array([
26 | [0.0],
27 | [1.0/M],
28 | [0.0],
29 | [-1/(M*l)]
30 | ])
31 |
32 | Cc = np.array([[1., 0., 0., 0.],
33 | [0., 0., 1., 0.]])
34 |
35 | Dc = np.zeros((2,1))
36 |
37 | [nx, nu] = Bc.shape # number of states and number or inputs
38 |
39 | # Brutal forward euler discretization
40 | Ad = np.eye(nx) + Ac*Ts
41 | Bd = Bc*Ts
42 | Cd = Cc
43 | Dd = Dc
44 |
45 | std_npos = 0.01
46 | std_ntheta = 0.01
47 |
48 | # Reference input and states
49 | xref = np.array([0.3, 0.0, 0.0, 0.0]) # reference state
50 | uref = np.array([0.0]) # reference input
51 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
52 |
53 | # Constraints
54 | xmin = np.array([-10.0, -10.0, -100, -100])
55 | xmax = np.array([10.0, 10.0, 100, 100])
56 |
57 | umin = np.array([-20])
58 | umax = np.array([20])
59 |
60 | Dumin = np.array([-5])
61 | Dumax = np.array([5])
62 |
63 | # Objective function weights
64 | Qx = sparse.diags([1.0, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
65 | QxN = sparse.diags([1.0, 0, 1.0, 0]) # Quadratic cost for xN
66 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
67 | QDu = 0.1 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
68 |
69 | # Initial state
70 | phi0 = 15*2*np.pi/360
71 | x0 = np.array([0, 0, phi0, 0]) # initial state
72 |
73 | # Prediction horizon
74 | Np = 40
75 |
76 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
77 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
78 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax)
79 | K.setup()
80 |
81 | # Kalman filter setup
82 | Cd = Cc
83 | Dd = Dc
84 | [nx, nu] = Bd.shape # number of states and number or inputs
85 | ny = np.shape(Cc)[0]
86 |
87 | # Kalman filter extended matrices
88 | Bd_kal = np.hstack([Bd, np.eye(nx)])
89 | Dd_kal = np.hstack([Dd, np.zeros((ny, nx))])
90 | Q_kal = np.diag([10,10,10,10])#np.eye(nx) * 100
91 | R_kal = np.eye(ny) * 1
92 |
93 | #Bd_kal = np.hstack([Bd, Bd])
94 | #Dd_kal = np.hstack([Dd, Dd])
95 | #Q_kal = np.eye(nu) * 1
96 | #R_kal = np.eye(ny) * 1
97 |
98 | L,P,W = kalman_filter(Ad, Bd_kal, Cd, Dd_kal, Q_kal, R_kal)
99 |
100 |
101 | # Simulate in closed loop
102 | [nx, nu] = Bd.shape # number of states and number or inputs
103 | len_sim = 100 # simulation length (s)
104 | nsim = int(len_sim/Ts) # simulation length(timesteps)
105 | x_vec = np.zeros((nsim, nx))
106 | y_vec = np.zeros((nsim, ny))
107 | x_est_vec = np.zeros((nsim, nx))
108 | u_vec = np.zeros((nsim, nu))
109 | t_vec = np.arange(0, nsim) * Ts
110 |
111 | time_start = time.time()
112 |
113 | x_step = x0
114 | x_step_est = x0
115 |
116 | uMPC = uminus1
117 | for i in range(nsim):
118 |
119 | # Output for step i
120 | # System
121 | y_step = Cd.dot(x_step) # y[k+1]
122 | ymeas_step = y_step
123 | ymeas_step[0] += std_npos * np.random.randn()
124 | ymeas_step[1] += std_ntheta * np.random.randn()
125 | # Estimator
126 | yest_step = Cd.dot(x_step_est)
127 | # MPC
128 | uMPC = K.output() # MPC step (u_k value)
129 |
130 | # Save output for step i
131 | y_vec[i,:] = y_step
132 | u_vec[i,:] = uMPC
133 | x_vec[i,:] = x_step
134 | x_est_vec[i, :] = x_step_est
135 |
136 | # Update i+1
137 | # System
138 | F = uMPC
139 | v = x_step[1]
140 | theta = x_step[2]
141 | omega = x_step[3]
142 | der = np.zeros(nx)
143 | der[0] = v
144 | der[1] = (m*l*np.sin(theta)*omega**2 -m*g*np.sin(theta)*np.cos(theta) + m*ftheta*np.cos(theta)*omega + F - b*v)/(M+m*(1-np.cos(theta)**2))
145 | der[2] = omega
146 | der[3] = ((M+m)*(g*np.sin(theta) - ftheta*omega) - m*l*omega**2*np.sin(theta)*np.cos(theta) -(F-b*v)*np.cos(theta))/(l*(M + m*(1-np.cos(theta)**2)) )
147 | x_step = x_step + der * Ts # x[k+1] #x_step = Ad.dot(x_step) + Bd.dot(uMPC)
148 |
149 | # Estimator
150 | x_step_est = Ad.dot(x_step_est) + Bd.dot(uMPC) # x[k+1|k]
151 | x_step_est = x_step_est + L @ (ymeas_step - yest_step) # x[k+1|k+1]
152 |
153 | # MPC update
154 | K.update(x_step_est, uMPC) # update with measurement #
155 |
156 | time_sim = time.time() - time_start
157 |
158 | fig,axes = plt.subplots(5,1, figsize=(10,10), sharex=True)
159 | axes[0].plot(t_vec, x_vec[:, 0], "k", label='p')
160 | axes[0].plot(t_vec, xref[0] * np.ones(np.shape(t_vec)), "r--", label="p_ref")
161 | axes[0].plot(t_vec, x_est_vec[:, 0], "b", label="p_est")
162 | axes[0].set_ylabel("Position (m)")
163 |
164 | axes[1].plot(t_vec, x_vec[:, 1], "k", label='v')
165 | axes[1].plot(t_vec, x_est_vec[:, 1], "b", label="v_est")
166 | axes[1].set_ylabel("Velocity (m/s)")
167 |
168 | axes[2].plot(t_vec, x_vec[:, 2] * 360 / 2 / np.pi, label="phi")
169 | axes[2].plot(t_vec, x_est_vec[:, 2] * 360 / 2 / np.pi, "b", label="phi_est")
170 | axes[2].set_ylabel("Angle (deg)")
171 |
172 | axes[3].plot(t_vec, x_vec[:, 3], label="omega")
173 | axes[3].plot(t_vec, x_est_vec[:, 3], "b", label="omega_est")
174 | axes[3].set_ylabel("Anglular speed (rad/sec)")
175 |
176 | axes[4].plot(t_vec, u_vec[:, 0], label="u")
177 | axes[4].plot(t_vec, uref * np.ones(np.shape(t_vec)), "r--", label="u_ref")
178 | axes[4].set_ylabel("Force (N)")
179 |
180 | for ax in axes:
181 | ax.grid(True)
182 | ax.legend()
183 |
--------------------------------------------------------------------------------
/test_scripts/kalman/example_estimator_save_predictions.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 | from kalman import kalman_filter
7 |
8 | if __name__ == '__main__':
9 |
10 | # Constants #
11 | M = 0.5
12 | m = 0.2
13 | b = 0.1
14 | ftheta = 0.1
15 | l = 0.3
16 | g = 9.81
17 |
18 | Ts = 50e-3
19 |
20 | Ac =np.array([[0, 1, 0, 0],
21 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
22 | [0, 0, 0, 1],
23 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
24 |
25 | Bc = np.array([
26 | [0.0],
27 | [1.0/M],
28 | [0.0],
29 | [-1/(M*l)]
30 | ])
31 |
32 | Cc = np.array([[1., 0., 0., 0.],
33 | [0., 0., 1., 0.]])
34 |
35 | Dc = np.zeros((2,1))
36 |
37 | [nx, nu] = Bc.shape # number of states and number or inputs
38 |
39 | # Brutal forward euler discretization
40 | Ad = np.eye(nx) + Ac*Ts
41 | Bd = Bc*Ts
42 | Cd = Cc
43 | Dd = Dc
44 |
45 | # Reference input and states
46 | xref = np.array([0.3, 0.0, 0.0, 0.0]) # reference state
47 | uref = np.array([0.0]) # reference input
48 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
49 |
50 | # Constraints
51 | xmin = np.array([-100.0, -100, -100, -100])
52 | xmax = np.array([100.0, 100.0, 100, 100])
53 |
54 | umin = np.array([-20])
55 | umax = np.array([20])
56 |
57 | Dumin = np.array([-5])
58 | Dumax = np.array([5])
59 |
60 | # Objective function weights
61 | Qx = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
62 | QxN = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for xN
63 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
64 | QDu = 0.01 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
65 |
66 | # Initial state
67 | phi0 = 15*2*np.pi/360
68 | x0 = np.array([0, 0, phi0, 0]) # initial state
69 |
70 | # Prediction horizon
71 | Np = 20
72 |
73 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
74 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
75 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax)
76 | K.setup()
77 |
78 | # Kalman filter setup
79 | Cd = Cc
80 | Dd = Dc
81 | [nx, nu] = Bc.shape # number of states and number or inputs
82 | ny = np.shape(Cc)[0]
83 |
84 | # Kalman filter extended matrices
85 | #Bd_kal = np.hstack([Bd, np.eye(nx)])
86 | #Dd_kal = np.hstack([Dd, np.zeros((ny, nx))])
87 | #Q_kal = np.eye(nx) * 10
88 | #R_kal = np.eye(ny) * 1
89 |
90 | Bd_kal = np.hstack([Bd, Bd])
91 | Dd_kal = np.hstack([Dd, Dd])
92 | Q_kal = np.eye(nu) * 100
93 | R_kal = np.eye(ny) * 0.1
94 |
95 | L,P,W = kalman_filter(Ad, Bd_kal, Cd, Dd_kal, Q_kal, R_kal)
96 |
97 |
98 | # Simulate in closed loop
99 | [nx, nu] = Bd.shape # number of states and number or inputs
100 | len_sim = 40 # simulation length (s)
101 | nsim = int(len_sim/Ts) # simulation length(timesteps)
102 | xsim = np.zeros((nsim,nx))
103 | xpred = np.zeros((nsim, Np+1, nx)) # save on-line estimator predictions
104 | ysim = np.zeros((nsim, ny))
105 | xest = np.zeros((nsim,nx))
106 | usim = np.zeros((nsim,nu))
107 | tsim = np.arange(0,nsim)*Ts
108 |
109 | time_start = time.time()
110 |
111 | xstep = x0
112 | ystep = None
113 |
114 | xstep_est = x0
115 | ystep_est = None
116 |
117 | uMPC = uminus1
118 | for i in range(nsim):
119 |
120 | # System output
121 |
122 | # Save system data
123 | xsim[i,:] = xstep
124 | ystep = Cd.dot(x0) # + noise ?
125 | ysim[i,:] = ystep
126 |
127 | # Save estimator data
128 | xest[i,:] = xstep_est
129 |
130 | # MPC update and step. Could be in just one function call
131 | K.update(xstep_est, uMPC) # update with measurement
132 | uMPC,infoMPC = K.output(return_x_seq=True) # MPC step (u_k value)
133 | usim[i,:] = uMPC
134 | xpred[i, :, :] = infoMPC['x_seq'] # save predictions for further use
135 |
136 | # System simulation step
137 | F = uMPC
138 | v = xstep[1]
139 | theta = xstep[2]
140 | omega = xstep[3]
141 | der = np.zeros(nx)
142 | der[0] = v
143 | der[1] = (m*l*np.sin(theta)*omega**2 -m*g*np.sin(theta)*np.cos(theta) + m*ftheta*np.cos(theta)*omega + F - b*v)/(M+m*(1-np.cos(theta)**2))
144 | der[2] = omega
145 | der[3] = ((M+m)*(g*np.sin(theta) - ftheta*omega) - m*l*omega**2*np.sin(theta)*np.cos(theta) -(F-b*v)*np.cos(theta))/(l*(M + m*(1-np.cos(theta)**2)) )
146 | # Forward euler step
147 | #xstep = Ad.dot(xstep) + Bd.dot(uMPC)
148 | xstep = xstep + der*Ts # x[k+1]
149 | ystep = Cd.dot(xstep) # y[k+1]
150 | ymeas = ystep + 0.01*np.random.random(ny)
151 |
152 | # Estimator step
153 | xstep_est = Ad.dot(xstep_est) + Bd.dot(uMPC) # x[k+1|k]
154 | ystep_est = Cd.dot(xstep_est) # y[k+1|k]
155 | xstep_est = xstep_est + L @ (ymeas-ystep_est) # x[k+1|k+1]
156 |
157 |
158 | time_sim = time.time() - time_start
159 |
160 | fig,axes = plt.subplots(5,1, figsize=(10,10))
161 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
162 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="p_ref")
163 | axes[0].plot(tsim, xest[:,0], "b", label="p_est")
164 | axes[0].set_ylabel("Position (m)")
165 |
166 | axes[1].plot(tsim, xsim[:,1], "k", label='v')
167 | axes[1].plot(tsim, xest[:,1], "b", label="v_est")
168 | axes[1].set_ylabel("Velocity (m/s)")
169 |
170 | axes[2].plot(tsim, xsim[:,2]*360/2/np.pi, label="phi")
171 | axes[2].plot(tsim, xest[:,2]*360/2/np.pi, "b", label="phi_est")
172 | axes[2].set_ylabel("Angle (deg)")
173 |
174 | axes[3].plot(tsim, xsim[:,3], label="omega")
175 | axes[3].plot(tsim, xest[:,3], "b", label="omega_est")
176 | axes[3].set_ylabel("Anglular speed (rad/sec)")
177 |
178 | axes[4].plot(tsim, usim[:,0], label="u")
179 | axes[4].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="u_ref")
180 | axes[4].set_ylabel("Force (N)")
181 |
182 | for ax in axes:
183 | ax.grid(True)
184 | ax.legend()
185 |
--------------------------------------------------------------------------------
/test_scripts/kalman/example_inverted_pendulum_disturbance.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from scipy.integrate import ode
6 | from scipy.interpolate import interp1d
7 | from kalman import kalman_filter_simple, kalman_filter, LinearStateEstimator
8 | from pyMPC.mpc import MPCController
9 | import control
10 | import control.matlab
11 |
12 |
13 | RAD_TO_DEG = 180.0/np.pi
14 |
15 | if __name__ == '__main__':
16 |
17 | # Constants #
18 | M = 0.5
19 | m = 0.2
20 | b = 0.1
21 | ftheta = 0.1
22 | l = 0.3
23 | g = 9.81
24 |
25 | Ts_MPC = 10e-3
26 | Ts_sim = 1e-3
27 | ratio_Ts = int(Ts_MPC // Ts_sim)
28 |
29 | Ac =np.array([[0, 1, 0, 0],
30 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
31 | [0, 0, 0, 1],
32 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
33 |
34 | Bc = np.array([
35 | [0.0],
36 | [1.0/M],
37 | [0.0],
38 | [-1/(M*l)]
39 | ])
40 |
41 | Cc = np.array([[1., 0., 0., 0.],
42 | [0., 0., 1., 0.]])
43 |
44 | Dc = np.zeros((2, 1))
45 |
46 | [nx, nu] = Bc.shape # number of states and number or inputs
47 | ny = np.shape(Cc)[0]
48 |
49 |
50 | # Nonlinear dynamics ODE
51 | def f_ODE(t,x,u):
52 | #print(x)
53 | F = u
54 | v = x[1]
55 | theta = x[2]
56 | omega = x[3]
57 | der = np.zeros(4)
58 | der[0] = v
59 | der[1] = (m * l * np.sin(theta) * omega ** 2 - m * g * np.sin(theta) * np.cos(theta) + m * ftheta * np.cos(
60 | theta) * omega + F - b * v) / (M + m * (1 - np.cos(theta) ** 2))
61 | der[2] = omega
62 | der[3] = ((M + m) * (g * np.sin(theta) - ftheta * omega) - m * l * omega ** 2 * np.sin(theta) * np.cos(
63 | theta) - (
64 | F - b * v) * np.cos(theta)) / (l * (M + m * (1 - np.cos(theta) ** 2)))
65 | return der
66 |
67 | # Brutal forward euler discretization
68 | Ad = np.eye(nx) + Ac * Ts_MPC
69 | Bd = Bc * Ts_MPC
70 | Cd = Cc
71 | Dd = Dc
72 |
73 | std_npos = 1*0.005
74 | std_nphi = 1*0.005
75 |
76 | # Force disturbance
77 | wu = 10 # bandwidth of the force disturbance
78 | std_du = 0.1
79 | Ts = 1e-3
80 | Hu = control.TransferFunction([1], [1 / wu, 1])
81 | Hu = Hu * Hu
82 | Hud = control.matlab.c2d(Hu, Ts)
83 | t_imp = np.arange(5000) * Ts
84 | t, y = control.impulse_response(Hud, t_imp)
85 | y = y[0]
86 | std_tmp = np.sqrt(np.sum(y ** 2)) # np.sqrt(trapz(y**2,t))
87 | Hu = Hu / (std_tmp) * std_du
88 | N_sim = 100000
89 | e = np.random.randn(N_sim)
90 | te = np.arange(N_sim) * Ts
91 | _, d_fast, _ = control.forced_response(Hu, te, e)
92 | d_fast = d_fast[1000:]
93 |
94 | # Reference input and states
95 | t_ref_vec = np.array([0.0, 10.0, 20.0, 30.0, 40.0])
96 | p_ref_vec = np.array([0.0, 0.3, 0.3, 0.0, 0.0])
97 | rp_fun = interp1d(t_ref_vec, p_ref_vec, kind='zero')
98 |
99 | def xref_fun(t):
100 | return np.array([rp_fun(t), 0.0, 0.0, 0.0])
101 |
102 |
103 | xref = xref_fun(0) # reference state
104 | uref = np.array([0.0]) # reference input
105 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
106 |
107 | # Constraints
108 | xmin = np.array([-1.0, -100, -100, -100])
109 | xmax = np.array([0.5, 100.0, 100, 100])
110 |
111 | umin = np.array([-20])
112 | umax = np.array([20])
113 |
114 | Dumin = np.array([-100*Ts_MPC]) # 100 N/s
115 | Dumax = np.array([100*Ts_MPC])
116 |
117 | # Objective function weights
118 | Qx = sparse.diags([1.0, 0, 10.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
119 | QxN = sparse.diags([1.0, 0, 10.0, 0]) # Quadratic cost for xN
120 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
121 | QDu = 0.1 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
122 |
123 | # Initial state
124 | phi0 = 15*2*np.pi/360
125 | x0 = np.array([0, 0, phi0, 0]) # initial state
126 | t0 = 0
127 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
128 | system_dyn.set_initial_value(x0, t0)
129 | system_dyn.set_f_params(0.0)
130 |
131 | # Prediction horizon
132 | Np = 100
133 | Nc = 100
134 |
135 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
136 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
137 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
138 | eps_feas = 1e3)
139 | K.setup()
140 |
141 | # Basic Kalman filter design
142 | Q_kal = np.diag([0.0001, 100, 0.0001, 100])
143 | #Q_kal = np.diag([100, 100, 100, 100])
144 | R_kal = 1*np.eye(ny)
145 | L, P, W = kalman_filter_simple(Ad, Bd, Cd, Dd, Q_kal, R_kal)
146 | #Bd_kal = np.hstack([Bd, Bd])
147 | #Dd_kal = np.array([[0, 0]])
148 | #Q_kal = np.array([[1e4]]) # nw x nw matrix, w general (here, nw = nu)
149 | #R_kal = np.eye(ny) # ny x ny)
150 | #L,P,W = kalman_filter(Ad, Bd_kal, Cd, Dd_kal, Q_kal, R_kal)
151 |
152 | x0_est = x0
153 | KF = LinearStateEstimator(x0_est, Ad, Bd, Cd, Dd,L)
154 |
155 |
156 | # Simulate in closed loop
157 | [nx, nu] = Bd.shape # number of states and number or inputs
158 | len_sim = 40 # simulation length (s)
159 | nsim = int(np.ceil(len_sim / Ts_MPC)) # simulation length(timesteps) # watch out! +1 added, is it correct?
160 | t_vec = np.zeros((nsim, 1))
161 | t_calc_vec = np.zeros((nsim,1)) # computational time to get MPC solution (+ estimator)
162 | status_vec = np.zeros((nsim,1))
163 | x_vec = np.zeros((nsim, nx))
164 | x_ref_vec = np.zeros((nsim, nx))
165 | y_vec = np.zeros((nsim, ny))
166 | y_meas_vec = np.zeros((nsim, ny))
167 | y_est_vec = np.zeros((nsim, ny))
168 | x_est_vec = np.zeros((nsim, nx))
169 | u_vec = np.zeros((nsim, nu))
170 | x_MPC_pred = np.zeros((nsim, Np+1, nx)) # on-line predictions from the Kalman Filter
171 |
172 | nsim_fast = int(len_sim // Ts_sim)
173 | t_vec_fast = np.zeros((nsim_fast, 1))
174 | x_vec_fast = np.zeros((nsim_fast, nx)) # finer integration grid for performance evaluation
175 | x_ref_vec_fast = np.zeros((nsim_fast, nx)) # finer integration grid for performance evaluatio
176 | u_vec_fast = np.zeros((nsim_fast, nu)) # finer integration grid for performance evaluatio
177 | Fd_vec_fast = np.zeros((nsim_fast, nu)) #
178 |
179 | t_step = t0
180 | u_MPC = None
181 | for idx_fast in range(nsim_fast):
182 |
183 | ## Determine step type: fast simulation only or MPC step
184 | idx_MPC = idx_fast // ratio_Ts
185 | run_MPC = (idx_fast % ratio_Ts) == 0
186 |
187 | # Output for step i
188 | # Ts_MPC outputs
189 | if run_MPC: # it is also a step of the simulation at rate Ts_MPC
190 | t_vec[idx_MPC, :] = t_step
191 | x_vec[idx_MPC, :] = system_dyn.y
192 | xref_MPC = xref_fun(t_step) # reference state
193 | x_ref_vec[idx_MPC,:] = xref_MPC
194 |
195 | u_MPC, info_MPC = K.output(return_x_seq=True, return_status=True) # u[i] = k(\hat x[i]) possibly computed at time instant -1
196 | x_MPC_pred[idx_MPC, :, :] = info_MPC['x_seq'] # x_MPC_pred[i,i+1,...| possibly computed at time instant -1]
197 | u_vec[idx_MPC, :] = u_MPC
198 |
199 | y_step = Cd.dot(system_dyn.y) # y[i] measured from the system
200 | ymeas_step = y_step
201 | ymeas_step[0] += std_npos * np.random.randn()
202 | ymeas_step[1] += std_nphi * np.random.randn()
203 | y_meas_vec[idx_MPC,:] = ymeas_step
204 | y_vec[idx_MPC,:] = y_step
205 | status_vec[idx_MPC,:] = (info_MPC['status'] != 'solved')
206 |
207 | # Ts_fast outputs
208 | t_vec_fast[idx_fast,:] = t_step
209 | x_vec_fast[idx_fast, :] = system_dyn.y
210 | x_ref_vec_fast[idx_fast, :] = xref_MPC
211 | u_fast = u_MPC + d_fast[idx_fast]
212 | u_vec_fast[idx_fast,:] = u_fast
213 | Fd_vec_fast[idx_fast,:] = d_fast[idx_fast]
214 |
215 | ## Update to step i+1
216 |
217 | # Controller simulation step at rate Ts_MPC
218 | if run_MPC:
219 | time_calc_start = time.time()
220 | # Kalman filter: update and predict
221 | KF.update(ymeas_step) # \hat x[i|i]
222 | KF.predict(u_MPC) # \hat x[i+1|i]
223 | # MPC update
224 | #K.update(system_dyn.y, u_MPC, xref=xref_MPC) # update with measurement
225 | K.update(KF.x, u_MPC, xref=xref_MPC) # update with measurement
226 | t_calc_vec[idx_MPC,:] = time.time() - time_calc_start
227 |
228 | # System simulation step at rate Ts_fast
229 | system_dyn.set_f_params(u_fast)
230 | system_dyn.integrate(t_step + Ts_sim)
231 |
232 | # Time update
233 | t_step += Ts_sim
234 |
235 |
236 | y_OL_pred = np.zeros((nsim-Np-1, Np+1, ny)) # on-line predictions from the Kalman Filter
237 | y_MPC_pred = x_MPC_pred[:, :, [0, 2]] # how to vectorize C * x_MPC_pred??
238 | y_MPC_err = np.zeros(np.shape(y_OL_pred))
239 | y_OL_err = np.zeros(np.shape(y_OL_pred))
240 | for i in range(nsim-Np-1):
241 | u_init = u_vec[i:i+Np+1, :]
242 | x_init = x_vec[i,:]
243 | y_OL_pred[i,:,:] = KF.sim(u_init,x_init)
244 | y_OL_err[i, :, :] = y_OL_pred[i, :, :] - y_meas_vec[i:i + Np + 1]
245 | y_MPC_err[i, :, :] = y_MPC_pred[i, :, :] - y_meas_vec[i:i + Np + 1]
246 |
247 |
248 | fig,axes = plt.subplots(3,1, figsize=(10,10), sharex=True)
249 | axes[0].plot(t_vec, y_meas_vec[:, 0], "b", label='p_meas')
250 | axes[0].plot(t_vec_fast, x_vec_fast[:, 0], "k", label='p')
251 | axes[0].plot(t_vec_fast, x_ref_vec_fast[:,0], "r--", label="p_ref")
252 | idx_pred = 0
253 | axes[0].plot(t_vec[idx_pred:idx_pred+Np+1], y_OL_pred[idx_pred, :, 0], 'r', label='Off-line k-step prediction')
254 | axes[0].plot(t_vec[idx_pred:idx_pred+Np+1], y_MPC_pred[idx_pred, :, 0], 'c', label='MPC k-step prediction' )
255 | axes[0].set_title("Position (m)")
256 |
257 | axes[1].plot(t_vec, y_meas_vec[:, 1]*RAD_TO_DEG, "b", label='phi_meas')
258 | axes[1].plot(t_vec_fast, x_vec_fast[:, 2]*RAD_TO_DEG, 'k', label="phi")
259 | axes[1].plot(t_vec_fast, x_ref_vec_fast[:,2]*RAD_TO_DEG, "r--", label="phi_ref")
260 | idx_pred = 0
261 | axes[1].plot(t_vec[idx_pred:idx_pred+Np+1], y_OL_pred[idx_pred, :, 1]*RAD_TO_DEG, 'r', label='Off-line k-step prediction')
262 | axes[1].plot(t_vec[idx_pred:idx_pred+Np+1], y_MPC_pred[idx_pred, :, 1]*RAD_TO_DEG, 'c', label='MPC k-step prediction' )
263 | axes[1].set_title("Angle (deg)")
264 |
265 | axes[2].step(t_vec_fast, u_vec_fast[:, 0], where='post', label="F")
266 | axes[2].step(t_vec, u_vec[:, 0], where='post', label="F_MPC")
267 | axes[2].step(t_vec_fast, Fd_vec_fast[:, 0], where='post', label="F_d")
268 | axes[2].plot(t_vec, uref * np.ones(np.shape(t_vec)), "r--", label="u_ref")
269 | axes[2].set_title("Force (N)")
270 |
271 | for ax in axes:
272 | ax.grid(True)
273 | ax.legend()
274 |
275 | # In[CPU time]
276 | fig, ax = plt.subplots(1, 1, figsize=(5, 5))
277 | ax.hist(t_calc_vec*1000, bins=100)
278 | ax.grid(True)
279 | ax.set_title('CPU time (ms)')
280 |
--------------------------------------------------------------------------------
/test_scripts/kalman/example_inverted_pendulum_disturbance_test.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from scipy.integrate import ode
6 | from scipy.interpolate import interp1d
7 | from pyMPC.kalman import kalman_design_simple, LinearStateEstimator
8 | from pyMPC.mpc import MPCController
9 | import control
10 | import control.matlab
11 |
12 |
13 | RAD_TO_DEG = 180.0/np.pi
14 |
15 | if __name__ == '__main__':
16 |
17 | # Constants #
18 | M = 0.5
19 | m = 0.2
20 | b = 0.1
21 | ftheta = 0.1
22 | l = 0.3
23 | g = 9.81
24 |
25 | Ts_MPC = 10e-3
26 | Ts_sim = 1e-3
27 | ratio_Ts = int(Ts_MPC // Ts_sim)
28 |
29 | Ac =np.array([[0, 1, 0, 0],
30 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
31 | [0, 0, 0, 1],
32 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
33 |
34 | Bc = np.array([
35 | [0.0],
36 | [1.0/M],
37 | [0.0],
38 | [-1/(M*l)]
39 | ])
40 |
41 | Cc = np.array([[1., 0., 0., 0.],
42 | [0., 0., 1., 0.]])
43 |
44 | Dc = np.zeros((2, 1))
45 |
46 | [nx, nu] = Bc.shape # number of states and number or inputs
47 | ny = np.shape(Cc)[0]
48 |
49 |
50 | # Nonlinear dynamics ODE
51 | def f_ODE(t,x,u):
52 | #print(x)
53 | F = u
54 | v = x[1]
55 | theta = x[2]
56 | omega = x[3]
57 | der = np.zeros(4)
58 | der[0] = v
59 | der[1] = (m * l * np.sin(theta) * omega ** 2 - m * g * np.sin(theta) * np.cos(theta) + m * ftheta * np.cos(
60 | theta) * omega + F - b * v) / (M + m * (1 - np.cos(theta) ** 2))
61 | der[2] = omega
62 | der[3] = ((M + m) * (g * np.sin(theta) - ftheta * omega) - m * l * omega ** 2 * np.sin(theta) * np.cos(
63 | theta) - (
64 | F - b * v) * np.cos(theta)) / (l * (M + m * (1 - np.cos(theta) ** 2)))
65 | return der
66 |
67 | # Brutal forward euler discretization
68 | Ad = np.eye(nx) + Ac * Ts_MPC
69 | Bd = Bc * Ts_MPC
70 | Cd = Cc
71 | Dd = Dc
72 |
73 | std_npos = 1*0.004
74 | std_nphi = 1*0.004
75 |
76 | # Force disturbance
77 | wu = 10 # bandwidth of the force disturbance
78 | #std_du = 0.1
79 | std_du = 0.0001
80 | Ts = 1e-3
81 | Hu = control.TransferFunction([1], [1 / wu, 1])
82 | Hu = Hu * Hu
83 | Hud = control.matlab.c2d(Hu, Ts)
84 | t_imp = np.arange(5000) * Ts
85 | t, y = control.impulse_response(Hud, t_imp)
86 | y = y[0]
87 | std_tmp = np.sqrt(np.sum(y ** 2)) # np.sqrt(trapz(y**2,t))
88 | Hu = Hu / (std_tmp) * std_du
89 | N_sim = 100000
90 | e = np.random.randn(N_sim)
91 | te = np.arange(N_sim) * Ts
92 | _, d_fast, _ = control.forced_response(Hu, te, e)
93 | d_fast = d_fast[1000:]
94 |
95 | # Reference input and states
96 | t_ref_vec = np.array([0.0, 10.0, 20.0, 30.0, 40.0])
97 | p_ref_vec = np.array([0.0, 0.3, 0.3, 0.0, 0.0])
98 | rp_fun = interp1d(t_ref_vec, p_ref_vec, kind='zero')
99 |
100 | def xref_fun(t):
101 | return np.array([rp_fun(t), 0.0, 0.0, 0.0])
102 |
103 |
104 | xref = xref_fun(0) # reference state
105 | uref = np.array([0.0]) # reference input
106 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
107 |
108 | # Constraints
109 | xmin = np.array([-1.0, -100, -100, -100])
110 | xmax = np.array([0.5, 100.0, 100, 100])
111 |
112 | umin = np.array([-20])
113 | umax = np.array([20])
114 |
115 | Dumin = np.array([-100*Ts_MPC]) # 100 N/s
116 | Dumax = np.array([100*Ts_MPC])
117 |
118 | # Objective function weights
119 | Qx = sparse.diags([1.0, 0, 5.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
120 | QxN = sparse.diags([1.0, 0, 5.0, 0]) # Quadratic cost for xN
121 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
122 | QDu = 1e-5/(Ts_MPC**2) * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
123 |
124 | # Initial state
125 | phi0 = 15*2*np.pi/360
126 | x0 = np.array([0, 0, phi0, 0]) # initial state
127 | t0 = 0
128 | system_dyn = ode(f_ODE).set_integrator('vode', method='bdf')
129 | system_dyn.set_initial_value(x0, t0)
130 | system_dyn.set_f_params(0.0)
131 |
132 | # Prediction horizon
133 | Np = 100
134 | Nc = 50
135 |
136 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
137 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
138 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax,
139 | eps_feas = 1e3)
140 | K.setup()
141 |
142 | # Basic Kalman filter design
143 | # Q_kal = np.diag([0.0001, 100, 0.0001, 100]) # setting for force disturbance
144 | Q_kal = np.diag([0.1, 10, 0.1, 10])
145 | #Q_kal = np.diag([100, 100, 100, 100])
146 | R_kal = 1*np.eye(ny)
147 | L, P, W = kalman_design_simple(Ad, Bd, Cd, Dd, Q_kal, R_kal)
148 | #Bd_kal = np.hstack([Bd, Bd])
149 | #Dd_kal = np.array([[0, 0]])
150 | #Q_kal = np.array([[1e4]]) # nw x nw matrix, w general (here, nw = nu)
151 | #R_kal = np.eye(ny) # ny x ny)
152 | #L,P,W = kalman_filter(Ad, Bd_kal, Cd, Dd_kal, Q_kal, R_kal)
153 |
154 | x0_est = x0
155 | KF = LinearStateEstimator(x0_est, Ad, Bd, Cd, Dd,L)
156 |
157 |
158 | # Simulate in closed loop
159 | [nx, nu] = Bd.shape # number of states and number or inputs
160 | len_sim = 40 # simulation length (s)
161 | nsim = int(np.ceil(len_sim / Ts_MPC)) # simulation length(timesteps) # watch out! +1 added, is it correct?
162 | t_vec = np.zeros((nsim, 1))
163 | t_calc_vec = np.zeros((nsim,1)) # computational time to get MPC solution (+ estimator)
164 | status_vec = np.zeros((nsim,1))
165 | x_vec = np.zeros((nsim, nx))
166 | x_ref_vec = np.zeros((nsim, nx))
167 | y_vec = np.zeros((nsim, ny))
168 | y_meas_vec = np.zeros((nsim, ny))
169 | y_est_vec = np.zeros((nsim, ny))
170 | x_est_vec = np.zeros((nsim, nx))
171 | u_vec = np.zeros((nsim, nu))
172 | x_MPC_pred = np.zeros((nsim, Np+1, nx)) # on-line predictions from the Kalman Filter
173 |
174 | nsim_fast = int(len_sim // Ts_sim)
175 | t_vec_fast = np.zeros((nsim_fast, 1))
176 | x_vec_fast = np.zeros((nsim_fast, nx)) # finer integration grid for performance evaluation
177 | x_ref_vec_fast = np.zeros((nsim_fast, nx)) # finer integration grid for performance evaluatio
178 | u_vec_fast = np.zeros((nsim_fast, nu)) # finer integration grid for performance evaluatio
179 | Fd_vec_fast = np.zeros((nsim_fast, nu)) #
180 |
181 | t_step = t0
182 | u_MPC = None
183 | for idx_fast in range(nsim_fast):
184 |
185 | ## Determine step type: fast simulation only or MPC step
186 | idx_MPC = idx_fast // ratio_Ts
187 | run_MPC = (idx_fast % ratio_Ts) == 0
188 |
189 | # Output for step i
190 | # Ts_MPC outputs
191 | if run_MPC: # it is also a step of the simulation at rate Ts_MPC
192 | t_vec[idx_MPC, :] = t_step
193 | x_vec[idx_MPC, :] = system_dyn.y
194 | xref_MPC = xref_fun(t_step) # reference state
195 | x_ref_vec[idx_MPC,:] = xref_MPC
196 |
197 | u_MPC, info_MPC = K.output(return_x_seq=True, return_status=True) # u[i] = k(\hat x[i]) possibly computed at time instant -1
198 | x_MPC_pred[idx_MPC, :, :] = info_MPC['x_seq'] # x_MPC_pred[i,i+1,...| possibly computed at time instant -1]
199 | u_vec[idx_MPC, :] = u_MPC
200 | x_est_vec[idx_MPC,:] = KF.x
201 |
202 | y_step = Cd.dot(system_dyn.y) # y[i] measured from the system
203 | ymeas_step = y_step
204 | ymeas_step[0] += std_npos * np.random.randn()
205 | ymeas_step[1] += std_nphi * np.random.randn()
206 | y_meas_vec[idx_MPC,:] = ymeas_step
207 | y_vec[idx_MPC,:] = y_step
208 | status_vec[idx_MPC,:] = (info_MPC['status'] != 'solved')
209 |
210 | # Ts_fast outputs
211 | t_vec_fast[idx_fast,:] = t_step
212 | x_vec_fast[idx_fast, :] = system_dyn.y
213 | x_ref_vec_fast[idx_fast, :] = xref_MPC
214 | u_fast = u_MPC + d_fast[idx_fast]
215 | u_vec_fast[idx_fast,:] = u_fast
216 | Fd_vec_fast[idx_fast,:] = d_fast[idx_fast]
217 |
218 | ## Update to step i+1
219 |
220 | # Controller simulation step at rate Ts_MPC
221 | if run_MPC:
222 | time_calc_start = time.perf_counter()
223 | # Kalman filter: update and predict
224 | KF.update(ymeas_step) # \hat x[i|i]
225 | KF.predict(u_MPC) # \hat x[i+1|i]
226 | # MPC update
227 | #K.update(system_dyn.y, u_MPC, xref=xref_MPC) # update with measurement
228 | K.update(KF.x, u_MPC, xref=xref_MPC) # update with measurement
229 | t_calc_vec[idx_MPC,:] = time.perf_counter() - time_calc_start
230 |
231 | # System simulation step at rate Ts_fast
232 | system_dyn.set_f_params(u_fast)
233 | system_dyn.integrate(t_step + Ts_sim)
234 |
235 | # Time update
236 | t_step += Ts_sim
237 |
238 |
239 | y_OL_pred = np.zeros((nsim-Np-1, Np+1, ny)) # on-line predictions from the Kalman Filter
240 | y_MPC_pred = x_MPC_pred[:, :, [0, 2]] # how to vectorize C * x_MPC_pred??
241 | y_MPC_err = np.zeros(np.shape(y_OL_pred))
242 | y_OL_err = np.zeros(np.shape(y_OL_pred))
243 | for i in range(nsim-Np-1):
244 | u_init = u_vec[i:i+Np+1, :]
245 | x_init = x_vec[i,:]
246 | y_OL_pred[i,:,:] = KF.sim(u_init,x_init)
247 | y_OL_err[i, :, :] = y_OL_pred[i, :, :] - y_meas_vec[i:i + Np + 1]
248 | y_MPC_err[i, :, :] = y_MPC_pred[i, :, :] - y_meas_vec[i:i + Np + 1]
249 |
250 |
251 | fig,axes = plt.subplots(3,1, figsize=(10,10), sharex=True)
252 | #axes[0].plot(t_vec, y_meas_vec[:, 0], "b", label='p_meas')
253 | axes[0].plot(t_vec_fast, x_vec_fast[:, 0], "k", label='p')
254 | axes[0].plot(t_vec, x_est_vec[:, 0], "k--", label='p_est')
255 | axes[0].plot(t_vec_fast, x_ref_vec_fast[:,0], "r--", label="p_ref")
256 | idx_pred = 0
257 | axes[0].plot(t_vec[idx_pred:idx_pred+Np+1], y_OL_pred[idx_pred, :, 0], 'r', label='Off-line k-step prediction')
258 | axes[0].plot(t_vec[idx_pred:idx_pred+Np+1], y_MPC_pred[idx_pred, :, 0], 'c', label='MPC k-step prediction' )
259 | axes[0].set_title("Position (m)")
260 |
261 | #axes[1].plot(t_vec, y_meas_vec[:, 1]*RAD_TO_DEG, "b", label='phi_meas')
262 | axes[1].plot(t_vec_fast, x_vec_fast[:, 2]*RAD_TO_DEG, 'k', label="phi")
263 | axes[1].plot(t_vec, x_est_vec[:, 2]*RAD_TO_DEG, "k--", label='phi_est')
264 | axes[1].plot(t_vec_fast, x_ref_vec_fast[:,2]*RAD_TO_DEG, "r--", label="phi_ref")
265 | idx_pred = 0
266 | axes[1].plot(t_vec[idx_pred:idx_pred+Np+1], y_OL_pred[idx_pred, :, 1]*RAD_TO_DEG, 'r', label='Off-line k-step prediction')
267 | axes[1].plot(t_vec[idx_pred:idx_pred+Np+1], y_MPC_pred[idx_pred, :, 1]*RAD_TO_DEG, 'c', label='MPC k-step prediction' )
268 | axes[1].set_title("Angle (deg)")
269 |
270 | axes[2].step(t_vec_fast, u_vec_fast[:, 0], where='post', label="F")
271 | axes[2].step(t_vec, u_vec[:, 0], where='post', label="F_MPC")
272 | axes[2].step(t_vec_fast, Fd_vec_fast[:, 0], where='post', label="F_d")
273 | axes[2].plot(t_vec, uref * np.ones(np.shape(t_vec)), "r--", label="u_ref")
274 | axes[2].set_title("Force (N)")
275 |
276 | for ax in axes:
277 | ax.grid(True)
278 | ax.legend()
279 |
280 |
281 | fig,axes = plt.subplots(2,1, figsize=(10,10), sharex=True)
282 | #axes[0].plot(t_vec, y_meas_vec[:, 0], "b", label='p_meas')
283 | axes[0].plot(t_vec_fast, x_vec_fast[:, 1], "k", label='v')
284 | axes[0].plot(t_vec, x_est_vec[:, 1], "k--", label='v_est')
285 | axes[0].set_title("Linear velocity (m/s)")
286 | axes[0].grid(True)
287 |
288 | #axes[1].plot(t_vec, y_meas_vec[:, 1]*RAD_TO_DEG, "b", label='phi_meas')
289 | axes[1].plot(t_vec_fast, x_vec_fast[:, 3], 'k', label="w")
290 | axes[1].plot(t_vec, x_est_vec[:, 3], "k--", label='w_est')
291 | axes[1].set_title("Angular velocity (rad/s)")
292 | axes[1].grid(True)
293 |
294 | for ax in axes:
295 | ax.grid(True)
296 | ax.legend()
297 |
298 | fig, ax = plt.subplots(1, 1, figsize=(5, 5))
299 | ax.hist(t_calc_vec*1000)
300 | ax.grid(True)
301 | ax.set_title('CPU time (ms)')
302 |
--------------------------------------------------------------------------------
/test_scripts/kalman/example_inverted_pendulum_estimator.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 | from kalman import kalman_filter
7 |
8 | if __name__ == '__main__':
9 |
10 | # Constants #
11 | M = 0.5
12 | m = 0.2
13 | b = 0.1
14 | ftheta = 0.1
15 | l = 0.3
16 | g = 9.81
17 |
18 | Ts = 50e-3
19 |
20 | Ac =np.array([[0, 1, 0, 0],
21 | [0, -b/M, -(g*m)/M, (ftheta*m)/M],
22 | [0, 0, 0, 1],
23 | [0, b/(M*l), (M*g + g*m)/(M*l), -(M*ftheta + ftheta*m)/(M*l)]])
24 |
25 | Bc = np.array([
26 | [0.0],
27 | [1.0/M],
28 | [0.0],
29 | [-1/(M*l)]
30 | ])
31 |
32 | Cc = np.array([[1., 0., 0., 0.],
33 | [0., 0., 1., 0.]])
34 |
35 | Dc = np.zeros((2,1))
36 |
37 | [nx, nu] = Bc.shape # number of states and number or inputs
38 |
39 | # Brutal forward euler discretization
40 | Ad = np.eye(nx) + Ac*Ts
41 | Bd = Bc*Ts
42 | Cd = Cc
43 | Dd = Dc
44 |
45 | # Reference input and states
46 | xref = np.array([0.3, 0.0, 0.0, 0.0]) # reference state
47 | uref = np.array([0.0]) # reference input
48 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
49 |
50 | # Constraints
51 | xmin = np.array([-100.0, -100, -100, -100])
52 | xmax = np.array([100.0, 100.0, 100, 100])
53 |
54 | umin = np.array([-20])
55 | umax = np.array([20])
56 |
57 | Dumin = np.array([-5])
58 | Dumax = np.array([5])
59 |
60 | # Objective function weights
61 | Qx = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for states x0, x1, ..., x_N-1
62 | QxN = sparse.diags([0.3, 0, 1.0, 0]) # Quadratic cost for xN
63 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
64 | QDu = 0.01 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
65 |
66 | # Initial state
67 | phi0 = 15*2*np.pi/360
68 | x0 = np.array([0, 0, phi0, 0]) # initial state
69 |
70 | # Prediction horizon
71 | Np = 20
72 |
73 | K = MPCController(Ad,Bd,Np=Np, x0=x0,xref=xref,uminus1=uminus1,
74 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
75 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax)
76 | K.setup()
77 |
78 | # Kalman filter setup
79 | Cd = Cc
80 | Dd = Dc
81 | [nx, nu] = Bc.shape # number of states and number or inputs
82 | ny = np.shape(Cc)[0]
83 |
84 | # Kalman filter extended matrices
85 | #Bd_kal = np.hstack([Bd, np.eye(nx)])
86 | #Dd_kal = np.hstack([Dd, np.zeros((ny, nx))])
87 | #Q_kal = np.eye(nx) * 10
88 | #R_kal = np.eye(ny) * 1
89 |
90 | Bd_kal = np.hstack([Bd, Bd])
91 | Dd_kal = np.hstack([Dd, Dd])
92 | Q_kal = np.eye(nu) * 100
93 | R_kal = np.eye(ny) * 0.1
94 |
95 | L,P,W = kalman_filter(Ad, Bd_kal, Cd, Dd_kal, Q_kal, R_kal)
96 |
97 |
98 | # Simulate in closed loop
99 | [nx, nu] = Bd.shape # number of states and number or inputs
100 | len_sim = 40 # simulation length (s)
101 | nsim = int(len_sim/Ts) # simulation length(timesteps)
102 | xsim = np.zeros((nsim,nx))
103 | ysim = np.zeros((nsim, ny))
104 | xest = np.zeros((nsim,nx))
105 | usim = np.zeros((nsim,nu))
106 | tsim = np.arange(0,nsim)*Ts
107 |
108 | time_start = time.time()
109 |
110 | xstep = x0
111 | ystep = None
112 |
113 | xstep_est = x0
114 | ystep_est = None
115 |
116 | uMPC = uminus1
117 | for i in range(nsim):
118 |
119 | # System output
120 |
121 | # Save system data
122 | xsim[i,:] = xstep
123 | ystep = Cd.dot(x0) # + noise ?
124 | ysim[i,:] = ystep
125 |
126 | # Save estimator data
127 | xest[i,:] = xstep_est
128 |
129 | # MPC update and step. Could be in just one function call
130 | K.update(xstep_est, uMPC) # update with measurement
131 | uMPC = K.output() # MPC step (u_k value)
132 | usim[i,:] = uMPC
133 |
134 | # System simulation step
135 | F = uMPC
136 | v = xstep[1]
137 | theta = xstep[2]
138 | omega = xstep[3]
139 | der = np.zeros(nx)
140 | der[0] = v
141 | der[1] = (m*l*np.sin(theta)*omega**2 -m*g*np.sin(theta)*np.cos(theta) + m*ftheta*np.cos(theta)*omega + F - b*v)/(M+m*(1-np.cos(theta)**2))
142 | der[2] = omega
143 | der[3] = ((M+m)*(g*np.sin(theta) - ftheta*omega) - m*l*omega**2*np.sin(theta)*np.cos(theta) -(F-b*v)*np.cos(theta))/(l*(M + m*(1-np.cos(theta)**2)) )
144 | # Forward euler step
145 | #xstep = Ad.dot(xstep) + Bd.dot(uMPC)
146 | xstep = xstep + der*Ts # x[k+1]
147 | ystep = Cd.dot(xstep) # y[k+1]
148 | ymeas = ystep + 0.01*np.random.random(ny)
149 |
150 | # Estimator step
151 | xstep_est = Ad.dot(xstep_est) + Bd.dot(uMPC) # x[k+1|k]
152 | ystep_est = Cd.dot(xstep_est) # y[k+1|k]
153 | xstep_est = xstep_est + L @ (ymeas-ystep_est) # x[k+1|k+1]
154 |
155 |
156 | time_sim = time.time() - time_start
157 |
158 | fig,axes = plt.subplots(5,1, figsize=(10,10))
159 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
160 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="p_ref")
161 | axes[0].plot(tsim, xest[:,0], "b", label="p_est")
162 | axes[0].set_ylabel("Position (m)")
163 |
164 | axes[1].plot(tsim, xsim[:,1], "k", label='v')
165 | axes[1].plot(tsim, xest[:,1], "b", label="v_est")
166 | axes[1].set_ylabel("Velocity (m/s)")
167 |
168 | axes[2].plot(tsim, xsim[:,2]*360/2/np.pi, label="phi")
169 | axes[2].plot(tsim, xest[:,2]*360/2/np.pi, "b", label="phi_est")
170 | axes[2].set_ylabel("Angle (deg)")
171 |
172 | axes[3].plot(tsim, xsim[:,3], label="omega")
173 | axes[3].plot(tsim, xest[:,3], "b", label="omega_est")
174 | axes[3].set_ylabel("Anglular speed (rad/sec)")
175 |
176 | axes[4].plot(tsim, usim[:,0], label="u")
177 | axes[4].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="u_ref")
178 | axes[4].set_ylabel("Force (N)")
179 |
180 | for ax in axes:
181 | ax.grid(True)
182 | ax.legend()
183 |
--------------------------------------------------------------------------------
/test_scripts/kalman/img/cart_pole.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forgi86/pyMPC/291db149554767a035fcb01df3fed7a6b3fe60e4/test_scripts/kalman/img/cart_pole.png
--------------------------------------------------------------------------------
/test_scripts/kalman/kalman.py:
--------------------------------------------------------------------------------
1 | import control
2 | import numpy as np
3 | import scipy as sp
4 |
5 | # conda install -c conda-forge slycot
6 |
7 |
8 | def __first_dim__(X):
9 | if sp.size(X) == 1:
10 | m = 1
11 | else:
12 | m = sp.size(X,0)
13 | return m
14 |
15 |
16 | def __second_dim__(X):
17 | if sp.size(X) == 1:
18 | m = 1
19 | else:
20 | m = sp.size(X,1)
21 | return m
22 |
23 | def kalman_filter(A, B, C, D, Qn, Rn, Nn=None):
24 | """ Design a Kalman filter for the discrete-time system
25 | x_{k+1} = Ax_{k} + Bu_{k} + Gw_{k}
26 | y_{k} = Cx_{k} + Du_{k} + Hw_{k} + v_{k}
27 | with known inputs u and sctochastic disturbances v, w.
28 | In particular, v and w are zero mean, white Gaussian noise sources with
29 | E[vv'] = Qn, E[ww'] = Rn, E['wv'] = Nn
30 |
31 | The Kalman filter has structure
32 | \hat x_{k+1} = Ax_{k} + Bu_{k} + L(y_{k} - C\hat x{k} - Du_{k})
33 | \hat y_{k} = Cx_k + Du_k
34 | """
35 | nx = np.shape(A)[0]
36 | nw = np.shape(Qn)[0] # number of uncontrolled inputs
37 | nu = np.shape(B)[1] - nw # number of controlled inputs
38 | ny = np.shape(C)[0]
39 |
40 | if Nn is None:
41 | Nn = np.zeros((nw, ny))
42 |
43 | E = np.eye(nx)
44 | Bu = B[:, 0:nu]
45 | Du = D[:, 0:nu]
46 | Bw = B[:, nu:]
47 | Dw = D[:, nu:]
48 |
49 | Hn = Dw @ Nn
50 | Rb = Rn + Hn + np.transpose(Hn) + Dw @ Qn @ np.transpose(Dw)
51 | Qb = Bw @ Qn @ np.transpose(Bw)
52 | Nb = Bw @ (Qn @ np.transpose(Dw) + Nn)
53 |
54 | # Enforce symmetry
55 | Qb = (Qb + np.transpose(Qb))/2
56 | Rb = (Rb+np.transpose(Rb))/2
57 |
58 | P,W,K, = control.dare(np.transpose(A), np.transpose(C), Qb, Rb, Nb, np.transpose(E))
59 |
60 | L = np.transpose(K) # Kalman gain
61 | return L,P,W
62 |
63 | def kalman_filter_simple(A, B, C, D, Qn, Rn):
64 | r"""Design a Kalman filter for the discrete-time system
65 |
66 | .. math::
67 | \begin{split}
68 | x_{k+1} &= Ax_{k} + Bu_{k} + Iw_{k}\\
69 | y_{k} &= Cx_{k} + Du_{k} + I v_{k}
70 | \end{split}
71 |
72 | with known inputs u and stochastic disturbances w and v.
73 | In particular, w and v are zero mean, white Gaussian noise sources with
74 | E[vv'] = Qn, E[ww'] = Rn, E['wv'] = 0
75 |
76 | The Kalman filter has structure
77 |
78 | .. math::
79 | \begin{split}
80 | \hat x_{k+1} &= Ax_{k} + Bu_{k} + L(y_{k} - C\hat x{k} - Du_{k})\\
81 | \hat y_{k} &= Cx_k + Du_k
82 | \end{split}
83 | """
84 |
85 | nx = __first_dim__(A)
86 | nw = nx # number of uncontrolled inputs
87 | nu = __second_dim__(B) # number of controlled inputs
88 | ny = __first_dim__(C)
89 |
90 | P,W,K, = control.dare(np.transpose(A), np.transpose(C), Qn, Rn)
91 | L = np.transpose(K) # Kalman gain
92 | return L,P,W
93 |
94 |
95 | class LinearStateEstimator:
96 | def __init__(self, x0, A, B, C, D, L):
97 |
98 | self.x = x0
99 | self.y = C @ x0
100 | self.A = A
101 | self.B = B
102 | self.C = C
103 | self.D = D
104 | self.L = L
105 |
106 | self.nx = __first_dim__(A)
107 | self.nu = __second_dim__(B) # number of controlled inputs
108 | self.ny = __first_dim__(C)
109 |
110 | def out_y(self,u):
111 | return self.y
112 |
113 | def predict(self, u):
114 | self.x = self.A @ self.x + self.B @u # x[k+1|k]
115 | self.y = self.C @ self.x + self.D @u
116 | return self.x
117 |
118 | def update(self, y_meas):
119 | self.x = self.x + self.L @ (y_meas - self.y) # x[k+1|k+1]
120 | return self.x
121 |
122 | def sim(self, u_seq, x=None):
123 |
124 | if x is None:
125 | x = self.x
126 | Np = __first_dim__(u_seq)
127 | nu = __second_dim__(u_seq)
128 | assert(nu == self.nu)
129 |
130 | y = np.zeros((Np,self.ny))
131 | x_tmp = x
132 | for i in range(Np):
133 | u_tmp = u_seq[i]
134 | y[i,:] = self.C @ x_tmp + self.D @ u_tmp
135 | x_tmp = self.A @x_tmp + self.B @ u_tmp
136 |
137 | #y[Np] = self.C @ x_tmp + self.D @ u_tmp # not really true for D. Here it is 0 anyways
138 | return y
139 |
140 | if __name__ == '__main__':
141 |
142 | # Constants #
143 | Ts = 0.2 # sampling time (s)
144 | M = 2 # mass (Kg)
145 | b = 0.3 # friction coefficient (N*s/m)
146 |
147 | Ad = np.array([
148 | [1.0, Ts],
149 | [0, 1.0 -b/M*Ts]
150 | ])
151 |
152 | Bd = np.array([
153 | [0.0],
154 | [Ts/M]])
155 |
156 | Cd = np.array([[1, 0]])
157 | Dd = np.array([[0]])
158 |
159 | [nx, nu] = Bd.shape # number of states and number or inputs
160 | ny = np.shape(Cd)[0]
161 |
162 | ## General design ##
163 | Bd_kal = np.hstack([Bd, Bd])
164 | Dd_kal = np.array([[0, 0]])
165 | Q_kal = np.array([[100]]) # nw x nw matrix, w general (here, nw = nu)
166 | R_kal = np.eye(ny) # ny x ny)
167 | L_general,P_general,W_general = kalman_filter(Ad, Bd_kal, Cd, Dd_kal, Q_kal, R_kal)
168 |
169 | # Simple design
170 | Q_kal = 10 * np.eye(nx)
171 | R_kal = np.eye(ny)
172 | L_simple,P_simple,W_simple = kalman_filter_simple(Ad, Bd, Cd, Dd, Q_kal, R_kal)
173 |
174 | # Simple design written in general form
175 | Bd_kal = np.hstack([Bd, np.eye(nx)])
176 | Dd_kal = np.hstack([Dd, np.zeros((ny, nx))])
177 | Q_kal = 10 * np.eye(nx)#np.eye(nx) * 100
178 | R_kal = np.eye(ny) * 1
179 | L_gensim,P_gensim,W_gensim = kalman_filter_simple(Ad, Bd, Cd, Dd, Q_kal, R_kal)
180 |
181 | assert(np.isclose(L_gensim[0], L_simple[0]))
182 |
--------------------------------------------------------------------------------
/test_scripts/main.py:
--------------------------------------------------------------------------------
1 | import osqp
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 |
6 | # Discrete time model of a quadcopter
7 | Ad = sparse.csc_matrix([
8 | [1., 0., 0., 0., 0., 0., 0.1, 0., 0., 0., 0., 0. ],
9 | [0., 1., 0., 0., 0., 0., 0., 0.1, 0., 0., 0., 0. ],
10 | [0., 0., 1., 0., 0., 0., 0., 0., 0.1, 0., 0., 0. ],
11 | [0.0488, 0., 0., 1., 0., 0., 0.0016, 0., 0., 0.0992, 0., 0. ],
12 | [0., -0.0488, 0., 0., 1., 0., 0., -0.0016, 0., 0., 0.0992, 0. ],
13 | [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.0992],
14 | [0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0. ],
15 | [0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0. ],
16 | [0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0. ],
17 | [0.9734, 0., 0., 0., 0., 0., 0.0488, 0., 0., 0.9846, 0., 0. ],
18 | [0., -0.9734, 0., 0., 0., 0., 0., -0.0488, 0., 0., 0.9846, 0. ],
19 | [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.9846]
20 | ])
21 | Bd = sparse.csc_matrix([
22 | [0., -0.0726, 0., 0.0726],
23 | [-0.0726, 0., 0.0726, 0. ],
24 | [-0.0152, 0.0152, -0.0152, 0.0152],
25 | [-0., -0.0006, -0., 0.0006],
26 | [0.0006, 0., -0.0006, 0.0000],
27 | [0.0106, 0.0106, 0.0106, 0.0106],
28 | [0, -1.4512, 0., 1.4512],
29 | [-1.4512, 0., 1.4512, 0. ],
30 | [-0.3049, 0.3049, -0.3049, 0.3049],
31 | [-0., -0.0236, 0., 0.0236],
32 | [0.0236, 0., -0.0236, 0. ],
33 | [0.2107, 0.2107, 0.2107, 0.2107]])
34 |
35 | [nx, nu] = Bd.shape # number of states and number or inputs
36 |
37 | # Constraints
38 | u0 = 10.5916
39 | umin = np.array([9.6, 9.6, 9.6, 9.6]) - u0
40 | umax = np.array([13., 13., 13., 13.]) - u0
41 | xmin = np.array([-np.pi/6,-np.pi/6,-np.inf,-np.inf,-np.inf,-1.,
42 | -np.inf,-np.inf,-np.inf,-np.inf,-np.inf,-np.inf])
43 | xmax = np.array([ np.pi/6, np.pi/6, np.inf, np.inf, np.inf, np.inf,
44 | np.inf, np.inf, np.inf, np.inf, np.inf, np.inf])
45 |
46 | # Objective function
47 | Q = sparse.diags([0., 0., 10., 10., 10., 10., 0., 0., 0., 5., 5., 5.])
48 | QN = Q
49 | R = 0.1*sparse.eye(4)
50 |
51 | # Initial and reference states
52 | x0 = np.zeros(12) # initial state
53 | xr = np.array([0.,0.,1.,0.,0.,0.,0.,0.,0.,0.,0.,0.]) # reference state
54 |
55 | # Prediction horizon
56 | Np = 10
57 |
58 | # Cast MPC problem to a QP: x = (x(0),x(1),...,x(N),u(0),...,u(N-1))
59 | # - quadratic objective
60 | P = sparse.block_diag([sparse.kron(sparse.eye(Np), Q), QN,
61 | sparse.kron(sparse.eye(Np), R)]).tocsc()
62 | # - linear objective
63 | q = np.hstack([np.kron(np.ones(Np), -Q.dot(xr)), -QN.dot(xr),
64 | np.zeros(Np * nu)])
65 | # - linear dynamics
66 | Ax = sparse.kron(sparse.eye(Np + 1), -sparse.eye(nx)) + sparse.kron(sparse.eye(Np + 1, k=-1), Ad)
67 | Bu = sparse.kron(sparse.vstack([sparse.csc_matrix((1, Np)), sparse.eye(Np)]), Bd)
68 | Aeq = sparse.hstack([Ax, Bu])
69 | leq = np.hstack([-x0, np.zeros(Np * nx)])
70 | ueq = leq
71 | # - input and state constraints
72 | Aineq = sparse.eye((Np + 1) * nx + Np * nu)
73 | lineq = np.hstack([np.kron(np.ones(Np + 1), xmin), np.kron(np.ones(Np), umin)])
74 | uineq = np.hstack([np.kron(np.ones(Np + 1), xmax), np.kron(np.ones(Np), umax)])
75 | # - OSQP constraints
76 | A = sparse.vstack([Aeq, Aineq]).tocsc()
77 | l = np.hstack([leq, lineq])
78 | u = np.hstack([ueq, uineq])
79 |
80 | # Create an OSQP object
81 | prob = osqp.OSQP()
82 |
83 | # Setup workspace
84 | prob.setup(P, q, A, l, u, warm_start=True)
85 |
86 | # Simulate in closed loop
87 | nsim = 15
88 | for i in range(nsim):
89 | # Solve
90 | res = prob.solve()
91 |
92 | # Check solver status
93 | if res.info.status != 'solved':
94 | raise ValueError('OSQP did not solve the problem!')
95 |
96 | # Apply first control input to the plant
97 | ctrl = res.x[-Np * nu:-(Np - 1) * nu]
98 | x0 = Ad.dot(x0) + Bd.dot(ctrl)
99 |
100 | # Update initial state
101 | l[:nx] = -x0
102 | u[:nx] = -x0
103 | prob.update(l=l, u=u)
104 |
--------------------------------------------------------------------------------
/test_scripts/main_cvxpy.py:
--------------------------------------------------------------------------------
1 | from cvxpy import *
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 |
6 | # Discrete time model of a quadcopter
7 | Ad = sparse.csc_matrix([
8 | [1., 0., 0., 0., 0., 0., 0.1, 0., 0., 0., 0., 0. ],
9 | [0., 1., 0., 0., 0., 0., 0., 0.1, 0., 0., 0., 0. ],
10 | [0., 0., 1., 0., 0., 0., 0., 0., 0.1, 0., 0., 0. ],
11 | [0.0488, 0., 0., 1., 0., 0., 0.0016, 0., 0., 0.0992, 0., 0. ],
12 | [0., -0.0488, 0., 0., 1., 0., 0., -0.0016, 0., 0., 0.0992, 0. ],
13 | [0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0., 0.0992],
14 | [0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0., 0. ],
15 | [0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 0. ],
16 | [0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0. ],
17 | [0.9734, 0., 0., 0., 0., 0., 0.0488, 0., 0., 0.9846, 0., 0. ],
18 | [0., -0.9734, 0., 0., 0., 0., 0., -0.0488, 0., 0., 0.9846, 0. ],
19 | [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.9846]
20 | ])
21 |
22 | Bd = sparse.csc_matrix([
23 | [0., -0.0726, 0., 0.0726],
24 | [-0.0726, 0., 0.0726, 0. ],
25 | [-0.0152, 0.0152, -0.0152, 0.0152],
26 | [-0., -0.0006, -0., 0.0006],
27 | [0.0006, 0., -0.0006, 0.0000],
28 | [0.0106, 0.0106, 0.0106, 0.0106],
29 | [0, -1.4512, 0., 1.4512],
30 | [-1.4512, 0., 1.4512, 0. ],
31 | [-0.3049, 0.3049, -0.3049, 0.3049],
32 | [-0., -0.0236, 0., 0.0236],
33 | [0.0236, 0., -0.0236, 0. ],
34 | [0.2107, 0.2107, 0.2107, 0.2107]])
35 | [nx, nu] = Bd.shape
36 |
37 | # Constraints
38 | u0 = 10.5916
39 | umin = np.array([9.6, 9.6, 9.6, 9.6]) - u0
40 | umax = np.array([13., 13., 13., 13.]) - u0
41 | xmin = np.array([-np.pi/6,-np.pi/6,-np.inf,-np.inf,-np.inf,-1.,
42 | -np.inf,-np.inf,-np.inf,-np.inf,-np.inf,-np.inf])
43 | xmax = np.array([ np.pi/6, np.pi/6, np.inf, np.inf, np.inf, np.inf,
44 | np.inf, np.inf, np.inf, np.inf, np.inf, np.inf])
45 |
46 | # Objective function
47 | Q = sparse.diags([0., 0., 10., 10., 10., 10., 0., 0., 0., 5., 5., 5.])
48 | QN = Q
49 | R = 0.1*sparse.eye(4)
50 |
51 | # Initial and reference states
52 | x0 = np.zeros(12)
53 | xr = np.array([0.,0.,1.,0.,0.,0.,0.,0.,0.,0.,0.,0.])
54 |
55 | # Prediction horizon
56 | N = 10
57 |
58 | # Define problem
59 | u = Variable((nu, N))
60 | x = Variable((nx, N+1))
61 | x_init = Parameter(nx) # parameter of the problem (changes from time to time)
62 | objective = 0
63 | constraints = [x[:,0] == x_init]
64 | for k in range(N):
65 | objective += quad_form(x[:,k] - xr, Q) + quad_form(u[:,k], R)
66 | constraints += [x[:,k+1] == Ad*x[:,k] + Bd*u[:,k]]
67 | constraints += [xmin <= x[:,k], x[:,k] <= xmax]
68 | constraints += [umin <= u[:,k], u[:,k] <= umax]
69 | objective += quad_form(x[:,N] - xr, QN)
70 | prob = Problem(Minimize(objective), constraints)
71 |
72 | # Simulate in closed loop
73 | nsim = 15
74 | for i in range(nsim):
75 | x_init.value = x0
76 | prob.solve(solver=OSQP, warm_start=True)
77 | x0 = Ad.dot(x0) + Bd.dot(u[:,0].value)
78 |
--------------------------------------------------------------------------------
/test_scripts/main_cvxpy_du.py:
--------------------------------------------------------------------------------
1 | from cvxpy import *
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 |
6 | # Discrete time model of the system (mass point with input force and friction)
7 |
8 | # Constants #
9 | Ts = 0.2 # sampling time (s)
10 | M = 2 # mass (Kg)
11 | b = 0.3 # friction coefficient (N*s/m)
12 |
13 | Ad = sparse.csc_matrix([
14 | [1.0, Ts],
15 | [0, 1.0 -b/M*Ts]
16 | ])
17 | Bd = sparse.csc_matrix([
18 | [0.0],
19 | [Ts/M]])
20 |
21 | # Continous-time matrices (just for reference)
22 | Ac = np.array([
23 | [0.0, 1.0],
24 | [0, -b/M]]
25 | )
26 | Bc = np.array([
27 | [0.0],
28 | [1/M]
29 | ])
30 |
31 | [nx, nu] = Bd.shape # number of states and number or inputs
32 |
33 | # Reference input and states
34 | pref = 7.0
35 | vref = 0.0
36 | xref = np.array([pref, vref]) # reference state
37 | uref = np.array([0.0]) # reference input
38 | uinit = np.array([0.0]) # input at time step negative one - used to penalize the first delta 0. Could be the same as uref.
39 |
40 | # Constraints
41 | xmin = np.array([-100.0, -100.0])
42 | xmax = np.array([100.0, 100.0])
43 |
44 | umin = np.array([-1.5])*100
45 | umax = np.array([1.5])*100
46 |
47 | Dumin = np.array([-2e-1])
48 | Dumax = np.array([2e-1])
49 |
50 | # Objective function
51 | Qx = sparse.diags([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
52 | QxN = sparse.diags([0.5, 0.1]) # Quadratic cost for xN
53 | Qu = 2.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
54 | QDu = 10.0 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
55 |
56 | # Initial state
57 | x0 = np.array([0.1, 0.2]) # initial state
58 |
59 | # Prediction horizon
60 | Np = 20
61 |
62 | # Define the optimization problem
63 | u = Variable((nu, Np)) # u0, u1, ... u_N-1
64 | x = Variable((nx, Np + 1)) # x0, x1, ... x_N
65 | x_init = Parameter(nx)
66 | uminus1 = Parameter(nu) # input at time instant negative one (from previous MPC window or uinit in the first MPC window)
67 |
68 | objective = 0
69 | constraints = [x[:,0] == x_init]
70 | for k in range(Np):
71 | objective += quad_form(x[:,k] - xref, Qx) + quad_form(u[:, k] - uref, Qu) # cost function J: \sum_{k=0}^{N_p-1} (xk - x_r)'Qx(xk - x_r) + (u_k)'Qx(u_k)
72 | if k > 0:
73 | objective += quad_form(u[:,k] - u[:,k-1], QDu) # \sum_{k=1}^{N_p-1} (uk - u_k-1)'QDu(uk - u_k-1)
74 | else:
75 | objective += quad_form(u[:,k] - uminus1, QDu) # ... penalize the variation of u0 with respect to uold
76 |
77 | constraints += [x[:,k+1] == Ad*x[:,k] + Bd*u[:,k]] # model dynamics constraints
78 |
79 | constraints += [xmin <= x[:,k], x[:,k] <= xmax] # state constraints
80 | constraints += [umin <= u[:,k], u[:,k] <= umax] # input constraints
81 |
82 |
83 | if k > 0:
84 | constraints += [Dumin <= u[:,k] - u[:,k-1] , u[:,k] - u[:,k-1] <= Dumax]
85 | else: # at k = 0...
86 | constraints += [Dumin <= u[:,k] - uminus1 , u[:, k] - uminus1 <= Dumax]
87 |
88 | objective += quad_form(x[:, Np] - xref, QxN) # add final cost for xN
89 | prob = Problem(Minimize(objective), constraints)
90 |
91 | # Simulate in closed loop
92 | len_sim = 15 # simulation length (s)
93 | nsim = int(len_sim/Ts) # simulation length(timesteps)
94 | xsim = np.zeros((nsim,nx))
95 | usim = np.zeros((nsim,nu))
96 | tsim = np.arange(0,nsim)*Ts
97 |
98 | uminus1_val = uinit # initial previous measured input is the input at time instant -1.
99 | for i in range(nsim):
100 | x_init.value = x0
101 | uminus1.value = uminus1_val
102 | #xminus1_val = xminus1
103 | prob.solve(solver=OSQP, warm_start=True, verbose=False, eps_abs=1e-10, eps_rel=1e-10) # solve MPC problem
104 | uMPC = u[:,0].value
105 | usim[i,:] = uMPC
106 | x0 = Ad.dot(x0) + Bd.dot(uMPC)
107 | xsim[i,:] = x0
108 |
109 | uminus1_val = uMPC # or a measurement if the input is affected by noise
110 |
111 | if i == 1:
112 | print(u.value)
113 | #print(u0)
114 |
115 | # In [1]
116 |
117 | import matplotlib.pyplot as plt
118 | fig,axes = plt.subplots(3,1, figsize=(10,10))
119 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
120 | axes[0].plot(tsim, pref*np.ones(np.shape(tsim)), "r--", label="pref")
121 | axes[0].set_title("Position (m)")
122 |
123 | axes[1].plot(tsim, xsim[:,1], label="v")
124 | axes[1].plot(tsim, vref*np.ones(np.shape(tsim)), "r--", label="vref")
125 | axes[1].set_title("Velocity (m/s)")
126 |
127 | axes[2].plot(tsim, usim[:,0], label="u")
128 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
129 | axes[2].set_title("Force (N)")
130 |
131 |
132 | for ax in axes:
133 | ax.grid(True)
134 | ax.legend()
135 |
--------------------------------------------------------------------------------
/test_scripts/main_cvxpy_du_Nc.py:
--------------------------------------------------------------------------------
1 | from cvxpy import *
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 |
6 | # Discrete time model of the system (mass point with input force and friction)
7 |
8 | # Constants #
9 | Ts = 0.2 # sampling time (s)
10 | M = 2 # mass (Kg)
11 | b = 0.3 # friction coefficient (N*s/m)
12 |
13 | Ad = sparse.csc_matrix([
14 | [1.0, Ts],
15 | [0, 1.0 -b/M*Ts]
16 | ])
17 | Bd = sparse.csc_matrix([
18 | [0.0],
19 | [Ts/M]])
20 |
21 | # Continous-time matrices (just for reference)
22 | Ac = np.array([
23 | [0.0, 1.0],
24 | [0, -b/M]]
25 | )
26 | Bc = np.array([
27 | [0.0],
28 | [1/M]
29 | ])
30 |
31 | [nx, nu] = Bd.shape # number of states and number or inputs
32 |
33 | # Reference input and states
34 | pref = 7.0
35 | vref = 0.0
36 | xref = np.array([pref, vref]) # reference state
37 | uref = np.array([0.0]) # reference input
38 | uinit = np.array([0.0]) # input at time step negative one - used to penalize the first delta 0. Could be the same as uref.
39 |
40 | # Constraints
41 | xmin = np.array([-100.0, -100.0])
42 | xmax = np.array([100.0, 100.0])
43 |
44 | umin = np.array([-1.5])*100
45 | umax = np.array([1.5])*100
46 |
47 | Dumin = np.array([-2e-1])
48 | Dumax = np.array([2e-1])
49 |
50 | # Objective function
51 | Qx = sparse.diags([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
52 | QxN = sparse.diags([0.5, 0.1]) # Quadratic cost for xN
53 | Qu = 2.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
54 | QDu = 10.0 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
55 |
56 | # Initial state
57 | x0 = np.array([0.1, 0.2]) # initial state
58 |
59 | # Prediction horizon
60 | Np = 20
61 | Nc = 15
62 | # Define the optimization problem
63 | u = Variable((nu, Np)) # u0, u1, ... u_N-1
64 | x = Variable((nx, Np + 1)) # x0, x1, ... x_N
65 | x_init = Parameter(nx)
66 | uminus1 = Parameter(nu) # input at time instant negative one (from previous MPC window or uinit in the first MPC window)
67 |
68 | objective = 0
69 | constraints = [x[:,0] == x_init]
70 | for k in range(Np):
71 | objective += quad_form(x[:,k] - xref, Qx) + quad_form(u[:, k] - uref, Qu) # cost function J: \sum_{k=0}^{N_p-1} (xk - x_r)'Qx(xk - x_r) + (u_k)'Qx(u_k)
72 | if k > 0:
73 | objective += quad_form(u[:,k] - u[:,k-1], QDu) # \sum_{k=1}^{N_p-1} (uk - u_k-1)'QDu(uk - u_k-1)
74 | else:
75 | objective += quad_form(u[:,k] - uminus1, QDu) # ... penalize the variation of u0 with respect to uold
76 |
77 | constraints += [x[:,k+1] == Ad*x[:,k] + Bd*u[:,k]] # model dynamics constraints
78 |
79 | constraints += [xmin <= x[:,k], x[:,k] <= xmax] # state constraints
80 | constraints += [umin <= u[:,k], u[:,k] <= umax] # input constraints
81 |
82 |
83 | if k > 0:
84 | constraints += [Dumin <= u[:,k] - u[:,k-1] , u[:,k] - u[:,k-1] <= Dumax]
85 | else: # at k = 0...
86 | constraints += [Dumin <= u[:,k] - uminus1 , u[:, k] - uminus1 <= Dumax]
87 |
88 | objective += quad_form(x[:, Np] - xref, QxN) # add final cost for xN
89 | prob = Problem(Minimize(objective), constraints)
90 |
91 | # Simulate in closed loop
92 | len_sim = 15 # simulation length (s)
93 | nsim = int(len_sim/Ts) # simulation length(timesteps)
94 | xsim = np.zeros((nsim,nx))
95 | usim = np.zeros((nsim,nu))
96 | tsim = np.arange(0,nsim)*Ts
97 |
98 | uminus1_val = uinit # initial previous measured input is the input at time instant -1.
99 | for i in range(nsim):
100 | x_init.value = x0
101 | uminus1.value = uminus1_val
102 | #xminus1_val = xminus1
103 | prob.solve(solver=OSQP, warm_start=True, verbose=False, eps_abs=1e-10, eps_rel=1e-10) # solve MPC problem
104 | uMPC = u[:,0].value
105 | usim[i,:] = uMPC
106 | x0 = Ad.dot(x0) + Bd.dot(uMPC)
107 | xsim[i,:] = x0
108 |
109 | uminus1_val = uMPC # or a measurement if the input is affected by noise
110 |
111 | if i == 1:
112 | print(u.value)
113 | #print(u0)
114 |
115 | # In [1]
116 |
117 | import matplotlib.pyplot as plt
118 | fig,axes = plt.subplots(3,1, figsize=(10,10))
119 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
120 | axes[0].plot(tsim, pref*np.ones(np.shape(tsim)), "r--", label="pref")
121 | axes[0].set_title("Position (m)")
122 |
123 | axes[1].plot(tsim, xsim[:,1], label="v")
124 | axes[1].plot(tsim, vref*np.ones(np.shape(tsim)), "r--", label="vref")
125 | axes[1].set_title("Velocity (m/s)")
126 |
127 | axes[2].plot(tsim, usim[:,0], label="u")
128 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
129 | axes[2].set_title("Force (N)")
130 |
131 |
132 | for ax in axes:
133 | ax.grid(True)
134 | ax.legend()
135 |
--------------------------------------------------------------------------------
/test_scripts/main_cvxpy_du_nocnst.py:
--------------------------------------------------------------------------------
1 | from cvxpy import *
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 |
6 | # Discrete time model of the system (mass point with input force and friction)
7 |
8 | # Constants #
9 | Ts = 0.2 # sampling time (s)
10 | M = 2 # mass (Kg)
11 | b = 0.3 # friction coefficient (N*s/m)
12 |
13 | Ad = sparse.csc_matrix([
14 | [1.0, Ts],
15 | [0, 1.0 -b/M*Ts]
16 | ])
17 | Bd = sparse.csc_matrix([
18 | [0.0],
19 | [Ts/M]])
20 |
21 | # Continous-time matrices (just for reference)
22 | Ac = np.array([
23 | [0.0, 1.0],
24 | [0, -b/M]]
25 | )
26 | Bc = np.array([
27 | [0.0],
28 | [1/M]
29 | ])
30 |
31 | [nx, nu] = Bd.shape # number of states and number or inputs
32 |
33 | # Reference input and states
34 | pref = 7.0
35 | vref = 0
36 | xref = np.array([pref, vref]) # reference state
37 | uref = 0 # reference input
38 | uinit = np.array([0.0]) # input at time step negative one - used to penalize the first delta 0. Could be the same as uref.
39 |
40 | # Constraints
41 | xmin = np.array([-100.0, -100.0])
42 | xmax = np.array([100.0, 100.0])
43 |
44 | umin = np.array([-1.5])
45 | umax = np.array([1.5])
46 |
47 | Dumin = np.array([-2e-1])
48 | Dumax = np.array([2e-1])
49 |
50 | # Objective function
51 | Qx = sparse.diags([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
52 | QxN = sparse.diags([0.5, 0.1]) # Quadratic cost for xN
53 | Qu = 0.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
54 | QDu = 2.0 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
55 |
56 | # Initial state
57 | x0 = np.array([0.1, 0.2]) # initial state
58 |
59 | # Prediction horizon
60 | Np = 20
61 |
62 | # Define the optimization problem
63 | u = Variable((nu, Np))
64 | x = Variable((nx, Np + 1))
65 | x_init = Parameter(nx)
66 | uminus1 = Parameter(nu) # input at time instant negative one (from previous MPC window or uinit in the first MPC window)
67 |
68 | objective = 0
69 | constraints = [x[:,0] == x_init]
70 | for k in range(Np):
71 | objective += quad_form(x[:,k] - xref, Qx) + quad_form(u[:, k] - uref, Qu) # cost function J: \sum_{k=0}^{N_p-1} (xk - x_r)'Qx(xk - x_r) + (u_k)'Qx(u_k)
72 | if k > 0:
73 | objective += quad_form(u[:,k] - u[:,k-1], QDu) # \sum_{k=1}^{N_p-1} (uk - u_k-1)'QDu(uk - u_k-1)
74 | else: # at k = 0...
75 | if uminus1 is not np.nan: # if there is an uold to be considered
76 | objective += quad_form(u[:,k] - uminus1, QDu) # ... penalize the variation of u0 with respect to uold
77 |
78 | constraints += [x[:,k+1] == Ad*x[:,k] + Bd*u[:,k]] # model dynamics constraints
79 |
80 | constraints += [xmin <= x[:,k], x[:,k] <= xmax] # state constraints
81 | constraints += [umin <= u[:,k], u[:,k] <= umax] # input constraints
82 |
83 | if k > 0:
84 | constraints += [Dumin <= u[:,k] - u[:,k-1] , u[:,k] - u[:,k-1] <= Dumax]
85 | else: # at k = 0...
86 | if uminus1 is not np.nan: # if there is an uold to be considered
87 | constraints += [Dumin <= u[:,k] - uminus1 , u[:, k] - uminus1 <= Dumax]
88 |
89 | objective += quad_form(x[:, Np] - xref, QxN) # add final cost for xN
90 | prob = Problem(Minimize(objective), constraints)
91 |
92 | # Simulate in closed loop
93 | len_sim = 15 # simulation length (s)
94 | nsim = int(len_sim/Ts) # simulation length(timesteps)
95 | xsim = np.zeros((nsim,nx))
96 | usim = np.zeros((nsim,nu))
97 | tsim = np.arange(0,nsim)*Ts
98 |
99 | uminus1_val = uinit # initial previous measured input is the input at time instant -1.
100 | for i in range(nsim):
101 | x_init.value = x0
102 | uminus1.value = uminus1_val
103 | #xminus1_val = xminus1
104 | prob.solve(solver=OSQP, warm_start=True) # solve MPC problem
105 | uMPC = u[:,0].value
106 | usim[i,:] = uMPC
107 | x0 = Ad.dot(x0) + Bd.dot(uMPC)
108 | xsim[i,:] = x0
109 |
110 | uminus1_val = uMPC # or a measurement if the input is affected by noise
111 | #print(u0)
112 |
113 | # In [1]
114 | import matplotlib.pyplot as plt
115 | fig,axes = plt.subplots(3,1, figsize=(10,10))
116 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
117 | axes[0].plot(tsim, pref*np.ones(np.shape(tsim)), "r--", label="pref")
118 | axes[0].set_title("Position (m)")
119 |
120 | axes[1].plot(tsim, xsim[:,1], label="v")
121 | axes[1].plot(tsim, vref*np.ones(np.shape(tsim)), "r--", label="vref")
122 | axes[1].set_title("Velocity (m/s)")
123 |
124 | axes[2].plot(tsim, usim[:,0], label="u")
125 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
126 | axes[2].set_title("Force (N)")
127 |
128 |
129 | for ax in axes:
130 | ax.grid(True)
131 | ax.legend()
132 |
--------------------------------------------------------------------------------
/test_scripts/main_cvxpy_simple.py:
--------------------------------------------------------------------------------
1 | from cvxpy import Variable, Parameter, Minimize, Problem, OSQP, quad_form
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 | import time
6 |
7 |
8 | if __name__ == "__main__":
9 |
10 | # Discrete time model of a quadcopter
11 | Ts = 0.2
12 | M = 2.0
13 |
14 | Ad = sparse.csc_matrix([
15 | [1.0, Ts],
16 | [0, 1.0]
17 | ])
18 | Bd = sparse.csc_matrix([
19 | [0.0],
20 | [Ts/M]])
21 |
22 | [nx, nu] = Bd.shape # number of states and number or inputs
23 |
24 | # Constraints
25 | uref = 0
26 | uinit = 0 # not used here
27 | umin = np.array([-1000.0]) - uref
28 | umax = np.array([1000.0]) - uref
29 |
30 | xmin = np.array([-100.0, -100.0])
31 | xmax = np.array([100.0, 100.0])
32 |
33 | # Objective function
34 | Q = sparse.diags([0.2, 0.3])
35 | QN = sparse.diags([0.4, 0.5]) # final cost
36 | R = 0.1*sparse.eye(1)
37 |
38 | # Initial and reference states
39 | x0 = np.array([0.1, 0.2]) # initial state
40 | # Reference input and states
41 | pref = 7.0
42 | vref = 0
43 | xref = np.array([pref, vref]) # reference state
44 |
45 | # Prediction horizon
46 | Np = 20
47 |
48 | # Define problem
49 | u = Variable((nu, Np))
50 | x = Variable((nx, Np + 1))
51 | x_init = Parameter(nx)
52 | objective = 0
53 | constraints = [x[:,0] == x_init]
54 | for k in range(Np):
55 | objective += quad_form(x[:, k] - xref, Q) + quad_form(u[:, k], R)
56 | constraints += [x[:, k+1] == Ad*x[:, k] + Bd*u[:, k]]
57 | constraints += [xmin <= x[:, k], x[:, k] <= xmax]
58 | constraints += [umin <= u[:, k], u[:, k] <= umax]
59 | objective += quad_form(x[:, Np] - xref, QN)
60 | prob = Problem(Minimize(objective), constraints)
61 |
62 |
63 | # Simulate in closed loop
64 | # Simulate in closed loop
65 | len_sim = 15 # simulation length (s)
66 | nsim = int(len_sim/Ts) # simulation length(timesteps)
67 | xsim = np.zeros((nsim,nx))
68 | usim = np.zeros((nsim,nu))
69 | tsim = np.arange(0,nsim)*Ts
70 |
71 | uminus1_val = uinit # initial previous measured input is the input at time instant -1.
72 | time_start = time.time()
73 | for i in range(nsim):
74 | x_init.value = x0
75 | #uminus1.value = uminus1_val
76 | prob.solve(solver=OSQP, warm_start=True)
77 | uMPC = u[:,0].value
78 | usim[i,:] = uMPC
79 | x0 = Ad.dot(x0) + Bd.dot(uMPC)
80 | xsim[i,:] = x0
81 |
82 | uminus1_val = uMPC # or a measurement if the input is affected by noise
83 | time_sim = time.time() - time_start
84 |
85 | # In [1]
86 | import matplotlib.pyplot as plt
87 | fig,axes = plt.subplots(3,1, figsize=(10,10))
88 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
89 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
90 | axes[0].set_title("Position (m)")
91 |
92 | axes[1].plot(tsim, xsim[:,1], label="v")
93 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
94 | axes[1].set_title("Velocity (m/s)")
95 |
96 | axes[2].plot(tsim, usim[:,0], label="u")
97 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
98 | axes[2].set_title("Force (N)")
99 |
100 |
101 | for ax in axes:
102 | ax.grid(True)
103 | ax.legend()
104 |
--------------------------------------------------------------------------------
/test_scripts/main_du.py:
--------------------------------------------------------------------------------
1 | import osqp
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 | import time
6 |
7 | # Discrete time model of the system (mass point with input force and friction)
8 |
9 | # Constants #
10 | Ts = 0.2 # sampling time (s)
11 | M = 2 # mass (Kg)
12 | b = 0.3 # friction coefficient (N*s/m)
13 |
14 | Ad = sparse.csc_matrix([
15 | [1.0, Ts],
16 | [0, 1.0 -b/M*Ts]
17 | ])
18 | Bd = sparse.csc_matrix([
19 | [0.0],
20 | [Ts/M]])
21 |
22 | # Continous-time matrices (just for reference)
23 | Ac = np.array([
24 | [0.0, 1.0],
25 | [0, -b/M]]
26 | )
27 | Bc = np.array([
28 | [0.0],
29 | [1/M]
30 | ])
31 |
32 | [nx, nu] = Bd.shape # number of states and number or inputs
33 |
34 | # Reference input and states
35 | pref = 7.0
36 | vref = 0.0
37 | xref = np.array([pref, vref]) # reference state
38 | uref = np.array([0.0]) # reference input
39 | uinit = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
40 |
41 | # Constraints
42 | xmin = np.array([-100.0, -100.0])
43 | xmax = np.array([100.0, 100.0])
44 |
45 | umin = np.array([-1.2])
46 | umax = np.array([1.2])
47 |
48 | Dumin = np.array([-2e-1])
49 | Dumax = np.array([2e-1])
50 |
51 | # Objective function
52 | Qx = sparse.diags([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
53 | QxN = sparse.diags([0.5, 0.1]) # Quadratic cost for xN
54 | Qu = 2.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
55 | QDu = 10.0 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
56 |
57 | # Initial state
58 | x0 = np.array([0.1, 0.2]) # initial state
59 |
60 | # Prediction horizon
61 | Np = 20
62 |
63 | # Cast MPC problem to a QP: x = (x(0),x(1),...,x(N),u(0),...,u(N-1))
64 | # - quadratic objective
65 | P_xu = sparse.block_diag([sparse.kron(sparse.eye(Np), Qx), # x0... x_N-1
66 | QxN, # x_N
67 | sparse.kron(sparse.eye(Np), Qu)] # u0... u_N-1
68 | ).tocsc()
69 |
70 | iDu = 2 * np.eye(Np) - np.eye(Np,k=1) - np.eye(Np, k=-1)
71 | iDu[Np-1,Np-1] = 1
72 | P_du = sparse.block_diag([sparse.kron(sparse.eye((Np+1)*nx), 0), # zeros for x
73 | sparse.kron(iDu, QDu)]
74 | ).tocsc()
75 |
76 | P = P_xu + P_du
77 |
78 | # - linear objective
79 | q_x = np.hstack([np.kron(np.ones(Np), -Qx.dot(xref)), # x0... x_N-1
80 | -QxN.dot(xref), # x_N
81 | np.zeros(Np * nu)]) # u0... u_N-1
82 |
83 | q_u = np.hstack([np.zeros((Np+1) * nx),
84 | np.kron(np.ones(Np), -Qu.dot(uref))]
85 | )
86 |
87 | q_du = np.hstack([np.zeros((Np+1) * nx), # x0... x_N
88 | -QDu.dot(uinit), # u0
89 | np.zeros((Np-1) * nu)]) # u1... u_N-1
90 |
91 | q = q_x + q_u + q_du
92 |
93 | # - linear dynamics
94 | Ax = sparse.kron(sparse.eye(Np + 1), -sparse.eye(nx)) + sparse.kron(sparse.eye(Np + 1, k=-1), Ad)
95 | Bu = sparse.kron(sparse.vstack([sparse.csc_matrix((1, Np)), sparse.eye(Np)]), Bd)
96 | Aeq_dyn = sparse.hstack([Ax, Bu])
97 | leq_dyn = np.hstack([-x0, np.zeros(Np * nx)])
98 | ueq_dyn = leq_dyn # for equality constraints -> upper bound = lower bound!
99 |
100 | # - input and state constraints
101 | Aineq_xu = sparse.eye((Np + 1) * nx + Np * nu)
102 | lineq_xu = np.hstack([np.kron(np.ones(Np + 1), xmin), np.kron(np.ones(Np), umin)]) # lower bound of inequalities
103 | uineq_xu = np.hstack([np.kron(np.ones(Np + 1), xmax), np.kron(np.ones(Np), umax)]) # upper bound of inequalities
104 |
105 | Aineq_du = sparse.vstack([sparse.hstack([np.zeros((Np + 1) * nx), np.ones(nu), np.zeros((Np - 1) * nu)]), # for u0 - u-1
106 | sparse.hstack([np.zeros((Np * nu, (Np+1) * nx)), -sparse.eye(Np * nu) + sparse.eye(Np * nu, k=1)]) # for uk - uk-1, k=1...Np
107 | ]
108 | )
109 |
110 | uineq_du = np.ones((Np+1) * nu)*Dumax
111 | uineq_du[0:nu] += uinit[0:nu]
112 |
113 | lineq_du = np.ones((Np+1) * nu)*Dumin
114 | lineq_du[0:nu] += uinit[0:nu] # works for nonscalar u?
115 |
116 | # - OSQP constraints
117 | A = sparse.vstack([Aeq_dyn, Aineq_xu, Aineq_du]).tocsc()
118 | l = np.hstack([leq_dyn, lineq_xu, lineq_du])
119 | u = np.hstack([ueq_dyn, uineq_xu, uineq_du])
120 |
121 | # Create an OSQP object
122 | prob = osqp.OSQP()
123 |
124 | # Setup workspace
125 | prob.setup(P, q, A, l, u, warm_start=True, verbose=False, eps_abs=1e-10, eps_rel=1e-10)
126 |
127 | # Simulate in closed loop
128 | len_sim = 15 # simulation length (s)
129 | nsim = int(len_sim/Ts) # simulation length(timesteps)
130 | xsim = np.zeros((nsim,nx))
131 | usim = np.zeros((nsim,nu))
132 | tsim = np.arange(0,nsim)*Ts
133 |
134 |
135 | time_start = time.time()
136 | uminus1_val = uinit # initial previous measured input is the input at time instant -1.
137 |
138 | for i in range(nsim):
139 |
140 | # Update optimization problem
141 | l[:nx] = -x0
142 | u[:nx] = -x0
143 |
144 | l[(Np+1)*nx + (Np+1)*nx + (Np)*nu:(Np+1)*nx + (Np+1)*nx + (Np)*nu + nu] = Dumin + uminus1_val[0:nu]
145 | u[(Np+1)*nx + (Np+1)*nx + (Np)*nu:(Np+1)*nx + (Np+1)*nx + (Np)*nu + nu] = Dumax + uminus1_val[0:nu]
146 |
147 | q_du = np.hstack([np.zeros((Np+1) * nx), # x0... x_N
148 | -QDu.dot(uminus1_val), # u0
149 | np.zeros((Np-1) * nu)]) # u1... u_N-1
150 | q = q_x + q_u + q_du
151 | prob.update(l=l, u=u, q=q)
152 |
153 | # Solve
154 | res = prob.solve()
155 |
156 | # Check solver status
157 | if res.info.status != 'solved':
158 | raise ValueError('OSQP did not solve the problem!')
159 |
160 | # Apply first control input to the plant
161 | uMPC = res.x[-Np * nu:-(Np - 1) * nu]
162 | x0 = Ad.dot(x0) + Bd.dot(uMPC) # system step
163 | xsim[i,:] = x0
164 | usim[i,:] = uMPC
165 | uminus1_val = uMPC
166 |
167 | if i == 1:
168 | print(res.x[-Np * nu:])
169 | time_sim = time.time() - time_start
170 |
171 | # In [1]
172 | import matplotlib.pyplot as plt
173 | fig,axes = plt.subplots(3,1, figsize=(10,10))
174 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
175 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
176 | axes[0].set_title("Position (m)")
177 |
178 | axes[1].plot(tsim, xsim[:,1], label="v")
179 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
180 | axes[1].set_title("Velocity (m/s)")
181 |
182 | axes[2].plot(tsim, usim[:,0], label="u")
183 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
184 | axes[2].set_title("Force (N)")
185 |
186 |
187 | for ax in axes:
188 | ax.grid(True)
189 | ax.legend()
190 |
--------------------------------------------------------------------------------
/test_scripts/main_du_Nc.py:
--------------------------------------------------------------------------------
1 | import osqp
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 | import time
6 |
7 | # Discrete time model of the system (mass point with input force and friction)
8 |
9 | # Constants #
10 | Ts = 0.2 # sampling time (s)
11 | M = 2 # mass (Kg)
12 | b = 0.3 # friction coefficient (N*s/m)
13 |
14 | Ad = sparse.csc_matrix([
15 | [1.0, Ts],
16 | [0, 1.0 -b/M*Ts]
17 | ])
18 | Bd = sparse.csc_matrix([
19 | [0.0],
20 | [Ts/M]])
21 |
22 | # Continous-time matrices (just for reference)
23 | Ac = np.array([
24 | [0.0, 1.0],
25 | [0, -b/M]]
26 | )
27 | Bc = np.array([
28 | [0.0],
29 | [1/M]
30 | ])
31 |
32 | [nx, nu] = Bd.shape # number of states and number or inputs
33 |
34 | # Reference input and states
35 | pref = 7.0
36 | vref = 0.0
37 | xref = np.array([pref, vref]) # reference state
38 | uref = np.array([0.0]) # reference input
39 | uinit = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
40 |
41 | # Constraints
42 | xmin = np.array([-100.0, -100.0])
43 | xmax = np.array([100.0, 100.0])
44 |
45 | umin = np.array([-1.2])
46 | umax = np.array([1.2])
47 |
48 | Dumin = np.array([-2e-1])
49 | Dumax = np.array([2e-1])
50 |
51 | # Objective function
52 | Qx = sparse.diags([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
53 | QxN = sparse.diags([0.5, 0.1]) # Quadratic cost for xN
54 | Qu = 2.0 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
55 | QDu = 10.0 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
56 |
57 | # Initial state
58 | x0 = np.array([0.1, 0.2]) # initial state
59 |
60 | # Prediction horizon
61 | Np = 20
62 | Nc = 15
63 |
64 | # Cast MPC problem to a QP: x = (x(0),x(1),...,x(N),u(0),...,u(N-1))
65 | # - quadratic objective
66 | P_xu = sparse.block_diag([sparse.kron(sparse.eye(Np), Qx), # x0... x_N-1
67 | QxN, # x_N
68 | sparse.kron(sparse.eye(Np), Qu)] # u0... u_N-1
69 | ).tocsc()
70 |
71 | iDu = 2 * np.eye(Np) - np.eye(Np,k=1) - np.eye(Np, k=-1)
72 | iDu[Np-1,Np-1] = 1
73 | P_du = sparse.block_diag([sparse.kron(sparse.eye((Np+1)*nx), 0), # zeros for x
74 | sparse.kron(iDu, QDu)]
75 | ).tocsc()
76 |
77 | P = P_xu + P_du
78 |
79 | # - linear objective
80 | q_x = np.hstack([np.kron(np.ones(Np), -Qx.dot(xref)), # x0... x_N-1
81 | -QxN.dot(xref), # x_N
82 | np.zeros(Np * nu)]) # u0... u_N-1
83 |
84 | q_u = np.hstack([np.zeros((Np+1) * nx),
85 | np.kron(np.ones(Np), -Qu.dot(uref))]
86 | )
87 |
88 | q_du = np.hstack([np.zeros((Np+1) * nx), # x0... x_N
89 | -QDu.dot(uinit), # u0
90 | np.zeros((Np-1) * nu)]) # u1... u_N-1
91 |
92 | q = q_x + q_u + q_du
93 |
94 | # - linear dynamics
95 | Ax = sparse.kron(sparse.eye(Np + 1), -sparse.eye(nx)) + sparse.kron(sparse.eye(Np + 1, k=-1), Ad)
96 | Bu = sparse.kron(sparse.vstack([sparse.csc_matrix((1, Np)), sparse.eye(Np)]), Bd)
97 | Aeq_dyn = sparse.hstack([Ax, Bu])
98 | leq_dyn = np.hstack([-x0, np.zeros(Np * nx)])
99 | ueq_dyn = leq_dyn # for equality constraints -> upper bound = lower bound!
100 |
101 | # - input and state constraints
102 | Aineq_xu = sparse.eye((Np + 1) * nx + Np * nu)
103 | lineq_xu = np.hstack([np.kron(np.ones(Np + 1), xmin), np.kron(np.ones(Np), umin)]) # lower bound of inequalities
104 | uineq_xu = np.hstack([np.kron(np.ones(Np + 1), xmax), np.kron(np.ones(Np), umax)]) # upper bound of inequalities
105 |
106 | Aineq_du = sparse.vstack([sparse.hstack([np.zeros((Np + 1) * nx), np.ones(nu), np.zeros((Np - 1) * nu)]), # for u0 - u-1
107 | sparse.hstack([np.zeros((Np * nu, (Np+1) * nx)), -sparse.eye(Np * nu) + sparse.eye(Np * nu, k=1)]) # for uk - uk-1, k=1...Np
108 | ]
109 | )
110 |
111 | uineq_du = np.ones((Np+1) * nu)*Dumax
112 | uineq_du[0:nu] += uinit[0:nu]
113 |
114 | lineq_du = np.ones((Np+1) * nu)*Dumin
115 | lineq_du[0:nu] += uinit[0:nu] # works for nonscalar u?
116 |
117 | # - OSQP constraints
118 | A = sparse.vstack([Aeq_dyn, Aineq_xu, Aineq_du]).tocsc()
119 | l = np.hstack([leq_dyn, lineq_xu, lineq_du])
120 | u = np.hstack([ueq_dyn, uineq_xu, uineq_du])
121 |
122 | # Create an OSQP object
123 | prob = osqp.OSQP()
124 |
125 | # Setup workspace
126 | prob.setup(P, q, A, l, u, warm_start=True, verbose=False, eps_abs=1e-10, eps_rel=1e-10)
127 |
128 | # Simulate in closed loop
129 | len_sim = 15 # simulation length (s)
130 | nsim = int(len_sim/Ts) # simulation length(timesteps)
131 | xsim = np.zeros((nsim,nx))
132 | usim = np.zeros((nsim,nu))
133 | tsim = np.arange(0,nsim)*Ts
134 |
135 |
136 | time_start = time.time()
137 | uminus1_val = uinit # initial previous measured input is the input at time instant -1.
138 |
139 | for i in range(nsim):
140 |
141 | # Update optimization problem
142 | l[:nx] = -x0
143 | u[:nx] = -x0
144 |
145 | l[(Np+1)*nx + (Np+1)*nx + (Np)*nu:(Np+1)*nx + (Np+1)*nx + (Np)*nu + nu] = Dumin + uminus1_val[0:nu]
146 | u[(Np+1)*nx + (Np+1)*nx + (Np)*nu:(Np+1)*nx + (Np+1)*nx + (Np)*nu + nu] = Dumax + uminus1_val[0:nu]
147 |
148 | q_du = np.hstack([np.zeros((Np+1) * nx), # x0... x_N
149 | -QDu.dot(uminus1_val), # u0
150 | np.zeros((Np-1) * nu)]) # u1... u_N-1
151 | q = q_x + q_u + q_du
152 | prob.update(l=l, u=u, q=q)
153 |
154 | # Solve
155 | res = prob.solve()
156 |
157 | # Check solver status
158 | if res.info.status != 'solved':
159 | raise ValueError('OSQP did not solve the problem!')
160 |
161 | # Apply first control input to the plant
162 | uMPC = res.x[-Np * nu:-(Np - 1) * nu]
163 | x0 = Ad.dot(x0) + Bd.dot(uMPC) # system step
164 | xsim[i,:] = x0
165 | usim[i,:] = uMPC
166 | uminus1_val = uMPC
167 |
168 | if i == 1:
169 | print(res.x[-Np * nu:])
170 | time_sim = time.time() - time_start
171 |
172 | # In [1]
173 | import matplotlib.pyplot as plt
174 | fig,axes = plt.subplots(3,1, figsize=(10,10))
175 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
176 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
177 | axes[0].set_title("Position (m)")
178 |
179 | axes[1].plot(tsim, xsim[:,1], label="v")
180 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
181 | axes[1].set_title("Velocity (m/s)")
182 |
183 | axes[2].plot(tsim, usim[:,0], label="u")
184 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
185 | axes[2].set_title("Force (N)")
186 |
187 |
188 | for ax in axes:
189 | ax.grid(True)
190 | ax.legend()
191 |
--------------------------------------------------------------------------------
/test_scripts/main_matlab_yalmip.m:
--------------------------------------------------------------------------------
1 | PATH_YALMIP = "/home/marco/Documents/MATLAB/Add-Ons/Toolboxes/YALMIP";
2 |
3 | addpath(genpath(PATH_YALMIP))
4 |
5 | % Discrete time model of a quadcopter
6 | Ad = [1 0 0 0 0 0 0.1 0 0 0 0 0;
7 | 0 1 0 0 0 0 0 0.1 0 0 0 0;
8 | 0 0 1 0 0 0 0 0 0.1 0 0 0;
9 | 0.0488 0 0 1 0 0 0.0016 0 0 0.0992 0 0;
10 | 0 -0.0488 0 0 1 0 0 -0.0016 0 0 0.0992 0;
11 | 0 0 0 0 0 1 0 0 0 0 0 0.0992;
12 | 0 0 0 0 0 0 1 0 0 0 0 0;
13 | 0 0 0 0 0 0 0 1 0 0 0 0;
14 | 0 0 0 0 0 0 0 0 1 0 0 0;
15 | 0.9734 0 0 0 0 0 0.0488 0 0 0.9846 0 0;
16 | 0 -0.9734 0 0 0 0 0 -0.0488 0 0 0.9846 0;
17 | 0 0 0 0 0 0 0 0 0 0 0 0.9846];
18 | Bd = [0 -0.0726 0 0.0726;
19 | -0.0726 0 0.0726 0;
20 | -0.0152 0.0152 -0.0152 0.0152;
21 | 0 -0.0006 -0.0000 0.0006;
22 | 0.0006 0 -0.0006 0;
23 | 0.0106 0.0106 0.0106 0.0106;
24 | 0 -1.4512 0 1.4512;
25 | -1.4512 0 1.4512 0;
26 | -0.3049 0.3049 -0.3049 0.3049;
27 | 0 -0.0236 0 0.0236;
28 | 0.0236 0 -0.0236 0;
29 | 0.2107 0.2107 0.2107 0.2107];
30 | [nx, nu] = size(Bd);
31 |
32 | % Constraints
33 | u0 = 10.5916;
34 | umin = [9.6; 9.6; 9.6; 9.6] - u0;
35 | umax = [13; 13; 13; 13] - u0;
36 | xmin = [-pi/6; -pi/6; -Inf; -Inf; -Inf; -1; -Inf(6,1)];
37 | xmax = [ pi/6; pi/6; Inf; Inf; Inf; Inf; Inf(6,1)];
38 |
39 | % Objective function
40 | Q = diag([0 0 10 10 10 10 0 0 0 5 5 5]);
41 | QN = Q;
42 | R = 0.1*eye(4);
43 |
44 | % Initial and reference states
45 | x0 = zeros(12,1);
46 | xr = [0; 0; 1; 0; 0; 0; 0; 0; 0; 0; 0; 0];
47 |
48 | % Prediction horizon
49 | N = 10;
50 |
51 | % Define problem
52 | u = sdpvar(repmat(nu,1,N), repmat(1,1,N));
53 | x = sdpvar(repmat(nx,1,N+1), repmat(1,1,N+1));
54 | constraints = [xmin <= x{1} <= xmax];
55 | objective = 0;
56 | for k = 1 : N
57 | objective = objective + (x{k}-xr)'*Q*(x{k}-xr) + u{k}'*R*u{k};
58 | constraints = [constraints, x{k+1} == Ad*x{k} + Bd*u{k}];
59 | constraints = [constraints, umin <= u{k}<= umax, xmin <= x{k+1} <= xmax];
60 | end
61 | objective = objective + (x{N+1}-xr)'*QN*(x{N+1}-xr);
62 | options = sdpsettings('solver', 'gurobi');
63 | controller = optimizer(constraints, objective, options, x{1}, [u{:}]);
64 |
65 | % Simulate in closed loop
66 | nsim = 15;
67 | for i = 1 : nsim
68 | U = controller{x0};
69 | x0 = Ad*x0 + Bd*U(:,1);
70 | end
71 |
--------------------------------------------------------------------------------
/test_scripts/main_simple.py:
--------------------------------------------------------------------------------
1 | import osqp
2 | import numpy as np
3 | import scipy as sp
4 | import scipy.sparse as sparse
5 | import time
6 |
7 | # Discrete time model of a quadcopter
8 | Ts = 0.2
9 | M = 2
10 | Ad = sparse.csc_matrix([
11 | [1.0, Ts],
12 | [0, 1.0]
13 | ])
14 | Bd = sparse.csc_matrix([
15 | [0.0],
16 | [Ts/M]])
17 |
18 | [nx, nu] = Bd.shape # number of states and number or inputs
19 |
20 | # Constraints
21 | uref = 0
22 | uinit = 0 # not used here
23 | umin = np.array([-1000.0]) - uref
24 | umax = np.array([1000.0]) - uref
25 |
26 | xmin = np.array([-100.0, -100.0])
27 | xmax = np.array([100.0, 100.0])
28 |
29 | # Objective function
30 | Q = sparse.diags([0.2, 0.3])
31 | QN = sparse.diags([0.4, 0.5]) # final cost
32 | R = 0.1*sparse.eye(1)
33 |
34 | # Initial and reference states
35 | x0 = np.array([0.1, 0.2]) # initial state
36 | # Reference input and states
37 | pref = 7.0
38 | vref = 0
39 | xref = np.array([pref, vref]) # reference state
40 |
41 | # Prediction horizon
42 | Np = 20
43 |
44 | # Cast MPC problem to a QP: x = (x(0),x(1),...,x(N),u(0),...,u(N-1))
45 | # - quadratic objective
46 | P = sparse.block_diag([sparse.kron(sparse.eye(Np), Q), QN,
47 | sparse.kron(sparse.eye(Np), R)]).tocsc()
48 | # - linear objective
49 | q = np.hstack([np.kron(np.ones(Np), -Q.dot(xref)), -QN.dot(xref),
50 | np.zeros(Np * nu)])
51 |
52 | # - linear dynamics
53 | Ax = sparse.kron(sparse.eye(Np + 1), -sparse.eye(nx)) + sparse.kron(sparse.eye(Np + 1, k=-1), Ad)
54 | Bu = sparse.kron(sparse.vstack([sparse.csc_matrix((1, Np)), sparse.eye(Np)]), Bd)
55 | Aeq = sparse.hstack([Ax, Bu])
56 | leq = np.hstack([-x0, np.zeros(Np * nx)])
57 | ueq = leq # for equality constraints -> upper bound = lower bound!
58 | # - input and state constraints
59 | Aineq = sparse.eye((Np + 1) * nx + Np * nu)
60 | lineq = np.hstack([np.kron(np.ones(Np + 1), xmin), np.kron(np.ones(Np), umin)]) # lower bound of inequalities
61 | uineq = np.hstack([np.kron(np.ones(Np + 1), xmax), np.kron(np.ones(Np), umax)]) # upper bound of inequalities
62 | # - OSQP constraints
63 | A = sparse.vstack([Aeq, Aineq]).tocsc()
64 | l = np.hstack([leq, lineq])
65 | u = np.hstack([ueq, uineq])
66 |
67 | # Create an OSQP object
68 | prob = osqp.OSQP()
69 |
70 | # Setup workspace
71 | prob.setup(P, q, A, l, u, warm_start=True)
72 |
73 | # Simulate in closed loop
74 | len_sim = 15 # simulation length (s)
75 | nsim = int(len_sim/Ts) # simulation length(timesteps)
76 | xsim = np.zeros((nsim,nx))
77 | usim = np.zeros((nsim,nu))
78 | tsim = np.arange(0,nsim)*Ts
79 |
80 | #uminus1_val = uinit # initial previous measured input is the input at time instant -1.
81 | time_start = time.time()
82 | for i in range(nsim):
83 | # Solve
84 | res = prob.solve()
85 |
86 | # Check solver status
87 | if res.info.status != 'solved':
88 | raise ValueError('OSQP did not solve the problem!')
89 |
90 | # Apply first control input to the plant
91 | uMPC = res.x[-Np * nu:-(Np - 1) * nu]
92 | x0 = Ad.dot(x0) + Bd.dot(uMPC)
93 | xsim[i,:] = x0
94 | usim[i,:] = uMPC
95 |
96 | # Update initial state
97 | l[:nx] = -x0
98 | u[:nx] = -x0
99 | prob.update(l=l, u=u)
100 | time_sim = time.time() - time_start
101 |
102 | # In [1]
103 | import matplotlib.pyplot as plt
104 | fig,axes = plt.subplots(3,1, figsize=(10,10))
105 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
106 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
107 | axes[0].set_title("Position (m)")
108 |
109 | axes[1].plot(tsim, xsim[:,1], label="v")
110 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
111 | axes[1].set_title("Velocity (m/s)")
112 |
113 | axes[2].plot(tsim, usim[:,0], label="u")
114 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
115 | axes[2].set_title("Force (N)")
116 |
117 |
118 | for ax in axes:
119 | ax.grid(True)
120 | ax.legend()
121 |
--------------------------------------------------------------------------------
/test_scripts/reference_governor/cvx_mpc_reference_governor_du_mimo_slack.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | import scipy.sparse as sparse
4 | import time
5 | from cvxpy import Variable, Parameter, Minimize, Problem, OSQP, quad_form
6 | from system_dynamics import Ad, Bd, Cd, Dd
7 |
8 |
9 | if __name__ == "__main__":
10 |
11 | # In[Constants]
12 | Ts = 1.0 # MPC sampling time
13 | len_sim = 120 # simulation length (s)
14 |
15 | [nx, ng] = Bd.shape # number of states and number or inputs
16 | [ny, nx_2] = Cd.shape # number of outputs
17 | assert(nx == nx_2)
18 |
19 | # In[MPC Settings]
20 |
21 | # Prediction horizon
22 | Np = 40
23 |
24 | # Constraints
25 | gmin = np.array(2*[-1000.0])
26 | gmax = np.array(2*[1000.0])
27 |
28 | ymin = np.array(2*[-100.0])
29 | ymax = np.array(2*[100.0])
30 |
31 | Dgmin = np.array(2*[-2e-1])
32 | Dgmax = np.array(2*[2e-1])
33 |
34 | # Objective function
35 | Qy = np.diag(2*[20]) # penalty on y - r
36 | QDy = 10 * np.eye(ny) # penalty on Delta y
37 | Qrg = 10 * np.eye(ny)
38 | QDg = 100 * sparse.eye(ny) # Quadratic cost for Du0, Du1, ...., Du_N-1
39 |
40 | # Initial state, reference, command
41 | x0 = np.array(2*[0.0, 0.0]) # initial state
42 | y0 = Cd @ x0 # initial state
43 | gm1 = np.array(2 * [0.0]) # g at time -1, used for the constraint on Delta g
44 |
45 | # In[MPC Problem setup]
46 | g = Variable((ng, Np))
47 | x = Variable((nx, Np))
48 | eps_slack = Variable(ny)
49 |
50 | x_init = Parameter(nx)
51 | gminus1 = Parameter(ny) # MPC command at time -1 (from previous MPC window or g_step_old for the first instant)
52 | yminus1 = Parameter(ny) # system output at time -1 (from previous MPC window or y_step_old for the first instant)
53 | r = Parameter(ny)
54 |
55 | y = Cd @ x + Dd @ g # system output definition
56 | objective = 0.0
57 | objective += quad_form(eps_slack, 1e4*np.eye(ny)) # constraint violation penalty on slack
58 | constraints = [x[:, 0] == x_init] # initial state constraint
59 | constraints += [eps_slack >= 0.0] # slack positive constraint
60 |
61 | for k in range(Np):
62 |
63 | # Objective function
64 | objective += quad_form(r - y[:, k], Qy) # tracking cost
65 | objective += quad_form(r - g[:, k], Qrg) # reference governor cost
66 | if k > 0:
67 | objective += quad_form(g[:, k] - g[:, k - 1], QDg) # MPC command variation cost
68 | objective += quad_form(y[:, k] - y[:, k - 1], QDy) # system output variation cost
69 | else: # at k = 0...
70 | objective += quad_form(g[:, k] - gminus1, QDg) # MPC command variation cost k=0
71 | objective += quad_form(y[:, k] - yminus1, QDy) # system output variation cost k=0
72 |
73 | # Constraints
74 | if k < Np - 1:
75 | constraints += [x[:, k+1] == Ad @ x[:, k] + Bd @ g[:, k]] # system dynamics constraint
76 |
77 | constraints += [ymin - eps_slack <= y[:, k], y[:, k] <= ymax + eps_slack] # system output interval constraint
78 | constraints += [gmin <= g[:, k], g[:, k] <= gmax] # MPC command interval constraint
79 |
80 | # MPC command variation constraint
81 | if k > 0:
82 | constraints += [Dgmin <= g[:, k] - g[:, k - 1], g[:, k] - g[:, k - 1] <= Dgmax]
83 | else: # at k = 0...
84 | constraints += [Dgmin <= g[:, k] - gminus1, g[:, k] - gminus1 <= Dgmax]
85 |
86 | prob = Problem(Minimize(objective), constraints)
87 |
88 | # Simulate in closed loop
89 | n_sim = int(len_sim / Ts) # simulation length(timesteps)
90 | x_sim = np.zeros((n_sim, nx))
91 | y_sim = np.zeros((n_sim, ny))
92 | g_sim = np.zeros((n_sim, ng))
93 | t_MPC = np.zeros((n_sim, 1))
94 | t_sim = np.arange(0, n_sim) * Ts
95 |
96 | g_step_old = gm1 # initial previous measured input is the input at time instant -1.
97 | x_step = x0
98 | y_step_old = y0
99 | time_start = time.time()
100 | for i in range(n_sim):
101 |
102 | x_sim[i, :] = x_step
103 |
104 | # MPC Control law computation
105 | time_start = time.time()
106 | x_init.value = x_step # set value to the x_init cvx parameter to x0
107 | gminus1.value = g_step_old
108 | yminus1.value = y_step_old
109 | r.value = np.array(2*[1.0]) # Reference output
110 | prob.solve(solver=OSQP, warm_start=True)
111 | g_step = g[:, 0].value
112 | time_MPC = 1000*(time.time() - time_start)
113 | t_MPC[i] = time_MPC # MPC control law computation time
114 |
115 | y_step = Cd @ x_step + Dd @ g_step
116 | y_sim[i, :] = y_step
117 | g_sim[i, :] = g_step
118 |
119 | # System update
120 | x_step = Ad @ x_step + Bd @ g_step
121 | y_step_old = y_step # like an additional state
122 | g_step_old = g_step # like an additional state
123 |
124 | time_sim = time.time() - time_start
125 |
126 | # In[Plot time traces]
127 | fig, axes = plt.subplots(2, 1, figsize=(10, 10))
128 | axes[0].plot(t_sim, y_sim[:, 0], "k", label='y1')
129 | axes[0].plot(t_sim, y_sim[:, 1], "b", label='y2')
130 | axes[0].set_title("Output (-)")
131 |
132 | axes[1].plot(t_sim, g_sim[:, 0], "k", label="g1")
133 | axes[1].plot(t_sim, g_sim[:, 1], "b", label="g2")
134 | axes[1].set_title("Input (-)")
135 |
136 | for ax in axes:
137 | ax.grid(True)
138 | ax.legend()
139 |
140 | # In[Timing]
141 | plt.figure()
142 | plt.hist(t_MPC[1:])
143 | plt.xlabel("MPC solution time (ms)")
144 |
145 | print(f"First MPC execution takes {t_MPC[0, 0]:.0f} ms")
146 | print(f"Following MPC execution take {np.max(t_MPC[1:, 0]):.0f} ms in the worst case")
--------------------------------------------------------------------------------
/test_scripts/reference_governor/oscillating_dynamics.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 | import control
3 | import numpy as np
4 |
5 | ts = 1.0
6 | r_den = 0.9 # magnitude of poles
7 | wo_den = 0.2 # phase of poles (approx 2.26 kHz)
8 |
9 | H_noise = control.TransferFunction([1], [1, -2 * r_den * np.cos(wo_den), r_den ** 2], ts)
10 | H_noise = H_noise/control.dcgain(H_noise)
11 | H_ss = control.ss(H_noise)
12 | t, val = control.step_response(H_ss, np.arange(100))
13 |
14 |
15 | plt.plot(val[0, :])
--------------------------------------------------------------------------------
/test_scripts/reference_governor/system_dynamics.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy
3 | import control
4 |
5 |
6 | # In[System dynamics]
7 |
8 | Ts = 1.0
9 | r_den_1 = 0.9 # magnitude of poles
10 | wo_den_1 = 0.2 # phase of poles (approx 2.26 kHz)
11 |
12 | # Build a second-order discrete-time dynamics with dcgain=1 (inner loop model)
13 | G_1 = control.TransferFunction([1], [1, -2 * r_den_1 * np.cos(wo_den_1), r_den_1 ** 2], Ts)
14 | G_1 = G_1 / control.dcgain(G_1)
15 | G_1_ss = control.ss(G_1)
16 |
17 | # SISO state-space matrices subsystem 11
18 | A_1 = np.array(G_1_ss.A)
19 | B_1 = np.array(G_1_ss.B)
20 | C_1 = np.array(G_1_ss.C)
21 | D_1 = np.array(G_1_ss.D)
22 |
23 | r_den_2 = 0.9 # magnitude of poles
24 | wo_den_2 = 0.4 # phase of poles (approx 2.26 kHz)
25 |
26 | # Build a second-order discrete-time dynamics with dcgain=1 (inner loop model)
27 | G_2 = control.TransferFunction([1], [1, -2 * r_den_2 * np.cos(wo_den_2), r_den_2 ** 2], Ts)
28 | G_2 = G_2 / control.dcgain(G_2)
29 | G_2_ss = control.ss(G_2)
30 |
31 | # SISO state-space matrices subsystem 22
32 | A_2 = np.array(G_2_ss.A)
33 | B_2 = np.array(G_2_ss.B)
34 | C_2 = np.array(G_2_ss.C)
35 | D_2 = np.array(G_2_ss.D)
36 |
37 | # MIMO state-space matrices
38 | Ad = scipy.linalg.block_diag(A_1, A_2)
39 | Bd = scipy.linalg.block_diag(B_1, B_2)
40 | Cd = scipy.linalg.block_diag(C_1, C_2)
41 | Dd = scipy.linalg.block_diag(D_1, D_2)
--------------------------------------------------------------------------------
/test_scripts/verify_MPC.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.sparse as sparse
3 | import time
4 | import matplotlib.pyplot as plt
5 | from pyMPC.mpc import MPCController
6 |
7 |
8 | if __name__ == '__main__':
9 | import time
10 | import matplotlib.pyplot as plt
11 | # Constants #
12 | Ts = 0.2 # sampling time (s)
13 | M = 2 # mass (Kg)
14 | b = 0.3 # friction coefficient (N*s/m)
15 |
16 | Ad = sparse.csc_matrix([
17 | [1.0, Ts],
18 | [0, 1.0 -b/M*Ts]
19 | ])
20 | Bd = sparse.csc_matrix([
21 | [0.0],
22 | [Ts/M]])
23 |
24 | # Continous-time matrices (just for reference)
25 | Ac = np.array([
26 | [0.0, 1.0],
27 | [0, -b/M]]
28 | )
29 | Bc = np.array([
30 | [0.0],
31 | [1/M]
32 | ])
33 |
34 | # Reference input and states
35 | pref = 7.0
36 | vref = 0.0
37 | xref = np.array([pref, vref]) # reference state
38 | uref = np.array([5]) # reference input
39 | uminus1 = np.array([0.0]) # input at time step negative one - used to penalize the first delta u at time instant 0. Could be the same as uref.
40 |
41 | # Constraints
42 | xmin = np.array([-10, -10.0])
43 | xmax = np.array([100.0, 100.0])
44 |
45 | umin = np.array([-1.2])
46 | umax = np.array([1.2])
47 |
48 | Dumin = np.array([-2e-1])
49 | Dumax = np.array([2e-1])
50 |
51 | # Objective function
52 | Qx = sparse.diags([0.5, 0.1]) # Quadratic cost for states x0, x1, ..., x_N-1
53 | QxN = sparse.diags([0.5, 0.1]) # Quadratic cost for xN
54 | Qu = 0.1 * sparse.eye(1) # Quadratic cost for u0, u1, ...., u_N-1
55 | QDu = 0.0 * sparse.eye(1) # Quadratic cost for Du0, Du1, ...., Du_N-1
56 |
57 | # Initial state
58 | x0 = np.array([0.1, 0.2]) # initial state
59 |
60 | # Prediction horizon
61 | Np = 25
62 | Nc = 25
63 |
64 | K = MPCController(Ad,Bd,Np=Np,Nc=Nc,x0=x0,xref=xref,uref=uref,uminus1=uminus1,
65 | Qx=Qx, QxN=QxN, Qu=Qu,QDu=QDu,
66 | xmin=xmin,xmax=xmax,umin=umin,umax=umax,Dumin=Dumin,Dumax=Dumax)
67 | K.setup()
68 |
69 | # Simulate in closed loop
70 | [nx, nu] = Bd.shape # number of states and number or inputs
71 | len_sim = 30 # simulation length (s)
72 | nsim = int(len_sim/Ts) # simulation length(timesteps)
73 | xsim = np.zeros((nsim,nx))
74 | usim = np.zeros((nsim,nu))
75 | tsim = np.arange(0,nsim)*Ts
76 | J_opt = np.zeros((nsim,1))
77 |
78 | time_start = time.time()
79 | xstep = x0
80 | for i in range(nsim):
81 | uMPC, info = K.output(return_u_seq=True, return_x_seq=True, return_eps_seq=True, return_status=True, return_obj_val=True)
82 | xstep = Ad.dot(xstep) + Bd.dot(uMPC) # system step
83 | J_opt[i,:] = info['obj_val']
84 | K.update(xstep) # update with measurement
85 | K.solve()
86 | xsim[i,:] = xstep
87 | usim[i,:] = uMPC
88 |
89 | time_sim = time.time() - time_start
90 |
91 | #K.__controller_function__(np.array([0,0]), np.array([0]))
92 |
93 | fig,axes = plt.subplots(4,1, figsize=(10,10))
94 | axes[0].plot(tsim, xsim[:,0], "k", label='p')
95 | axes[0].plot(tsim, xref[0]*np.ones(np.shape(tsim)), "r--", label="pref")
96 | axes[0].set_title("Position (m)")
97 |
98 | axes[1].plot(tsim, xsim[:,1], label="v")
99 | axes[1].plot(tsim, xref[1]*np.ones(np.shape(tsim)), "r--", label="vref")
100 | axes[1].set_title("Velocity (m/s)")
101 |
102 | axes[2].plot(tsim, usim[:,0], label="u")
103 | axes[2].plot(tsim, uref*np.ones(np.shape(tsim)), "r--", label="uref")
104 | axes[2].set_title("Force (N)")
105 |
106 | axes[3].plot(tsim, J_opt, "k", label="J_opt")
107 |
108 | for ax in axes:
109 | ax.grid(True)
110 | ax.legend()
111 |
112 |
113 | x_seq = info['x_seq']
114 | eps_seq = info['eps_seq']
115 | u_seq = info['u_seq']
116 | eps_recalc_seq = np.zeros(eps_seq.shape)
117 | u_old = K.uminus1_rh
118 | x_new_dyn = x_seq[0,:]
119 | J_recalc_x = 0
120 | J_recalc_u = 0
121 | J_recalc_Du = 0
122 | J_recalc_eps = 0
123 |
124 |
125 | for i in range(Np):
126 | x_i = x_seq[i,:] # x[i]
127 | eps_i = eps_seq[i]
128 | if i < Nc:
129 | u_i = u_seq[i,:] # u[i]
130 | else:
131 | u_i = u_seq[Nc-1,:]
132 | eps_i_recalc = x_new_dyn - x_i # eps[i]
133 | J_recalc_x += 1/2*(x_i -xref).dot(K.Qx.dot((x_i -xref)))
134 | J_recalc_u += 1/2*(u_i -uref).dot(K.Qu.dot((u_i -uref)))
135 | J_recalc_Du += 1/2*(u_i -u_old).dot(K.QDu.dot((u_i -u_old)))
136 | J_recalc_eps += 1/2*(eps_i_recalc).dot(K.Qeps.dot((eps_i_recalc)))
137 | x_new_dyn = K.Ad.dot(x_i) + K.Bd.dot(u_i)
138 | u_old = u_i
139 |
140 | x_i = x_seq[Np,:]
141 | eps_i_recalc = x_new_dyn - x_i
142 | J_recalc_x += 1/2*(x_i -xref).dot(K.QxN.dot((x_i -xref)))
143 | J_recalc_eps += 1/2*(eps_i_recalc).dot(K.Qeps.dot((eps_i_recalc)))
144 |
145 | J_recalc = J_recalc_x + J_recalc_u + J_recalc_Du + J_recalc_eps
146 |
--------------------------------------------------------------------------------