Stéphane Caronhttps://scaron.info/2017-09-24T00:00:00+02:00Dynamic Walking over Rough Terrains by Nonlinear Predictive Control of the Floating-base Inverted Pendulum2017-09-24T00:00:00+02:00Stéphane Carontag:scaron.info,2017-09-24:research/iros-2017.html<p class="authors"><strong>Stéphane Caron</strong>, <strong>Abderrahmane Kheddar</strong>. To be presented at <a class="reference external" href="http://www.iros2017.org/">2017 IEEE/RSJ
International Conference on Intelligent Robots and Systems (IROS)</a>, September 2017.</p>
<div class="section" id="abstract">
<h2>Abstract</h2>
<p>We present a real-time rough-terrain dynamic walking pattern generator. Our
method automatically finds step durations, which is a critical issue over rough
terrains where they depend on terrain topology. To achieve this level of
generality, we introduce the Floating-base Inverted Pendulum (FIP) model where
the center of mass can translate freely and the zero-tilting moment point is
allowed to leave the contact surface. We show that this model is equivalent to
the linear-inverted pendulum mode with variable center of mass height, aside
from the fact that its equations of motion remain linear. Our design then
follows three steps: (i) we characterize the FIP contact-stability condition;
(ii) we compute feedforward controls by solving a nonlinear optimization over
receding-horizon FIP trajectories. Despite running at 30 Hz in a
model-predictive fashion, simulations show that the latter is too slow to
stabilize dynamic motions. To remedy this, we (iii) linearize FIP feedback
control computations into a quadratic program, resulting in a constrained
linear-quadratic regulator that runs at 300 Hz. We finally demonstrate our
solution in simulations with a model of the HRP-4 humanoid robot, including
noise and delays over both state estimation and foot force control.</p>
<img alt="HRP-4 walking down an elliptic staircase using our dynamic walking pattern generator" class="noborder max-height-400px align-center" src="https://scaron.info/figures/dynamic-walking.png" />
</div>
<div class="section" id="content">
<h2>Content</h2>
<table border="1" class="colwidths-given files docutils">
<colgroup>
<col width="10%" />
<col width="90%" />
</colgroup>
<tbody valign="top">
<tr><td><img alt="pdf" class="icon" src="https://scaron.info/images/icons/pdf.png" /></td>
<td><a class="reference external" href="https://hal.archives-ouvertes.fr/hal-01481052/document">Pre-print on HAL</a></td>
</tr>
<tr><td><img alt="github" class="icon" src="https://scaron.info/images/icons/github.png" /></td>
<td><a class="reference external" href="https://github.com/stephane-caron/dynamic-walking">Source code</a></td>
</tr>
<tr><td><img alt="mp4" class="icon" src="https://scaron.info/images/icons/video.png" /></td>
<td><a class="reference external" href="https://scaron.info/videos/dynamic-walking.mp4">Video</a></td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="cite">
<h2>Cite</h2>
<div class="highlight"><pre><span></span>@inproceedings{caron2017iros,
title = {Dynamic Walking over Rough Terrains by Nonlinear Predictive Control of the Floating-base Inverted Pendulum},
author = {Caron, St{\'e}phane and Kheddar, Abderrahmane},
booktitle = {Intelligent Robots and Systems (IROS), 2017 IEEE/RSJ International Conference on},
year = {2017},
month = {September},
note = {to be presented},
hal_id = {hal-01481052},
hal_version = {v1},
url = {https://hal.archives-ouvertes.fr/hal-01481052},
}
</pre></div>
</div>
<div class="section" id="q-a">
<h2>Q & A</h2>
<p>Feel free to write me directly about any question you have on this work.</p>
</div>
Adaptive Compliance in Post-Impact Humanoid Falls Using Preview Control of a Reduce Model2017-07-25T00:00:00+02:00Stéphane Carontag:scaron.info,2017-07-25:research/adaptive-compliance.html<p class="authors"><strong>Vincent Samy</strong>, <strong>Stéphane Caron</strong>, <strong>Karim Bouyarmane</strong> and <strong>Abderrahmane
Kheddar</strong>.</p>
<div class="section" id="abstract">
<h2>Abstract</h2>
<p>We present a novel approach to control a hu-manoid robot in active compliance
just after an impact consecutive to a fall. Using linear model predictive
control (LMPC), the momentum accumulated by the robot during the falling phase
is absorbed, by driving it to zero, until the robot comes to safe rest. The
LMPC is written for a reduced center-of-mass model of the robot subject to
external contact forces applied on the impact bodies of the robot, each body
belonging to an impact limb (arm, leg). Distributing optimally the initial
momentum at impact and the total gravity force among all the impacting limbs,
we write one LMPC per such limb, each contributing to its own share of the
momentum absorption problem. The control vector of each MPC is the contact
force applied at the impact body of the limb. We propose a method that allows
to encode in a contact polytope both the friction limitations and the actuation
torque limits of the impact limb's actuated joints, this polytope models in a
synthetic way the linear constraint on the control vector of the LMPC. The
approach is validated in full-body dynamics simulation of a humanoid robot
falling on a wall.</p>
</div>
<div class="section" id="content">
<h2>Content</h2>
<table border="1" class="colwidths-given files docutils">
<colgroup>
<col width="10%" />
<col width="90%" />
</colgroup>
<tbody valign="top">
<tr><td><img alt="pdf" class="icon" src="https://scaron.info/images/icons/pdf.png" /></td>
<td><a class="reference external" href="https://hal.archives-ouvertes.fr/hal-01569819/document">Pre-print on HAL</a></td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="cite">
<h2>Cite</h2>
<div class="highlight"><pre><span></span>@unpublished{samy2017hal,
title = {{Adaptive Compliance in Post-Impact Humanoid Falls Using Preview Control of a Reduce Model}},
author = {Samy, Vincent and Caron, St{\'e}phane and Bouyarmane, Karim and Kheddar, Abderrahmane},
year = {2017},
month = {July},
hal_id = {hal-01569819},
hal_version = {v1},
url = {https://hal.archives-ouvertes.fr/hal-01569819}
}
</pre></div>
</div>
When to make a step? Tackling the timing problem in multi-contact locomotion by TOPP-MPC2017-07-25T00:00:00+02:00Stéphane Carontag:scaron.info,2017-07-25:research/topp-mpc.html<p class="authors"><strong>Stéphane Caron</strong>, <strong>Quang-Cuong Pham</strong>.</p>
<div class="section" id="abstract">
<h2>Abstract</h2>
<p>We present a model predictive controller (MPC) for multi-contact locomotion
where predictive optimizations are realized by time-optimal path
parameterization (TOPP). A key feature of this solution is that, contrary to
existing planners where step timings are provided as inputs, here the timing
between contact switches is computed as <em>output</em> of a fast nonlinear
optimization. This is particularly appealing to multi-contact locomotion, where
proper timings depend on terrain topology and suitable heuristics are unknown.
We show how to formulate legged locomotion as a TOPP problem and demonstrate
the behavior of the resulting TOPP-MPC controller in simulations with a model
of the HRP-4 humanoid robot.</p>
<img alt="HRP-4 walking a series of hills using the TOPP-MPC controller" class="noborder padtop align-center" src="https://scaron.info/images/topp-mpc.png" />
</div>
<div class="section" id="content">
<h2>Content</h2>
<table border="1" class="colwidths-given files docutils">
<colgroup>
<col width="10%" />
<col width="90%" />
</colgroup>
<tbody valign="top">
<tr><td><img alt="pdf" class="icon" src="https://scaron.info/images/icons/pdf.png" /></td>
<td><a class="reference external" href="https://hal.archives-ouvertes.fr/hal-01363757/document">Pre-print on HAL</a></td>
</tr>
<tr><td><img alt="github" class="icon" src="https://scaron.info/images/icons/github.png" /></td>
<td><a class="reference external" href="https://github.com/stephane-caron/topp-mpc">Source code</a></td>
</tr>
<tr><td><img alt="mp4" class="icon" src="https://scaron.info/images/icons/video.png" /></td>
<td><a class="reference external" href="https://scaron.info/videos/topp-mpc.mp4">Video</a></td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="cite">
<h2>Cite</h2>
<div class="highlight"><pre><span></span>@unpublished{caron2017hal,
title = {When to make a step? Tackling the timing problem in multi-contact locomotion by TOPP-MPC},
author = {Caron, St{\'e}phane and Pham, Quang-Cuong},
year = {2017},
month = {September},
hal_id = {hal-01363757},
hal_version = {v1},
url = {https://hal.archives-ouvertes.fr/hal-01363757}
}
</pre></div>
</div>
Multi-Contact Planning and Control2017-07-10T00:00:00+02:00Stéphane Carontag:scaron.info,2017-07-10:research/mcpc.html<p class="authors"><a class="reference external" href="https://members.loria.fr/kbouyarmane/">Karim Bouyarmane</a>,
<strong>Stéphane Caron</strong>,
<a class="reference external" href="https://sites.google.com/site/adrienescandehomepage/">Adrien Escande</a> and
<strong>Abderrahmane Kheddar</strong>.</p>
<div class="section" id="abstract">
<h2>Abstract</h2>
<p>The essence of humanoid robots is their ability to reproduce human skills in
locomotion and manipulation. Early efforts in humanoid research were dedicated
to bipedal walking, first on flat terrains and recently on uneven ones, while
the manipulation capabilities inherit from the literature in bimanual and
dexterous-hand manipulation. In practice, the two problems interact largely.
Locomotion in cluttered spaces benefits from extra contacts between any part of
the robot and the environment, such as when grippers grasp a handrail during
stair climbing, while legs can conversely enhance manipulation capabilities,
such as when arching the whole-body to augment contact pressure at an
end-effector. The two problems share the same background: they are governed by
non-smooth dynamics (friction and impacts at contacts) under viability
constraints including dynamic stability. Consequently, they are now solved
jointly. This chapter highlights the state-of-the-art techniques used for this
purpose in multi-contact planning and control.</p>
</div>
<div class="section" id="content">
<h2>Content</h2>
<table border="1" class="colwidths-given files docutils">
<colgroup>
<col width="10%" />
<col width="90%" />
</colgroup>
<tbody valign="top">
<tr><td><img alt="pdf" class="icon" src="https://scaron.info/images/icons/pdf.png" /></td>
<td><a class="reference external" href="https://members.loria.fr/kbouyarmane/mcpc.pdf">Pre-print</a></td>
</tr>
</tbody>
</table>
</div>
Writing a scientific paper2017-06-29T19:42:00+02:00Stéphane Carontag:scaron.info,2017-06-29:teaching/writing-a-scientific-paper.html<p>Like software engineering, or playing a game of the <em>Dark Souls</em> series,
writing scientific papers is a challenging activity that blends multiple goals
and pitfalls. As in software engineering, one way to learn the tricks of the
trade is to look and comment upon harmful behaviors, known as <em>anti-patterns</em>.</p>
<h2>Anti-patterns</h2>
<h3>The We-Need-To</h3>
<p>This one applies to both scientific writing and presentations.</p>
<p><strong>Description:</strong> the solution to a problem is presented as necessary.</p>
<ul>
<li>Oral: "And so, to do this, we <em>needed to</em> measure..."</li>
<li>Writen: "To achieve this, one needs to update..."</li>
<li>Writen: "The next step is to compute X such that..."</li>
</ul>
<p>Necessity has a precise meaning: a solution is necessary when <em>there is no
other way</em>. While it is reassuring to think that your method emerged as "the"
logical response to your problem, truth is, it bundles a natural chain of
contingencies, and there are most likely other ways to solve the same problem.</p>
<p>Doubt is a crucial practice in science. The mistake that appears in this
anti-pattern is to shun it out.</p>
<p><strong>Remedy:</strong> cut to the point.</p>
<ul>
<li>Oral:" And so, to do this, we measured..."</li>
<li>Writen: "To achieve this, we updated..."</li>
<li>Writen: "We then compute X such that..."</li>
</ul>
<h3>The manuscript is organized as follows</h3>
<p><strong>Description:</strong> the paper digresses describing its own structure.</p>
<blockquote>
<p>"The rest of the manuscript is organized as follows. In Section
II, we do this. In Section III, we show that..."</p>
</blockquote>
<p>Keep in mind that reading, like watching a movie, is not a fully conscious
process. To be receptive to the ideas conveyed by your writing, you want to
keep your reader in a <a href="https://en.wikipedia.org/wiki/Flow_(psychology)">state of
flow</a>. Linear storytelling is
a common strategy to realize this, using temporal or logical connectives to
stitch paragraphs one after the other. Breaking the storyline to describe the
structure of yet-to-come points is a way to achieve the opposite.</p>
<p><strong>Remedy:</strong> use logical connectors to keep the reading flow linear.</p>
<h2>General writing advice</h2>
<h3>Thesaurus and dictionary</h3>
<p>Two advices especially targetted to non-native speakers like me:</p>
<ul>
<li>Beware of <a href="https://en.wikipedia.org/wiki/False_friend">false friends</a>:
whenever unsure, check the definition in a
<a href="https://en.wiktionary.org/">dictionary</a></li>
<li>Use a <a href="http://www.thesaurus.com/">thesaurus</a> to avoid repetitions, and to
improve your phrasing by selecting words that express best what you want to
say.</li>
</ul>
<h3>Active or passive voice</h3>
<p>There is a bit of debate surrounding this question, as it certainly boils down
to a personal choice of writing style. Some scientists argue that, the
objective of scientific papers being the communication of <em>facts</em> and
observations, scientists themselves should be grammatically excluded as
subjects. Neither "we" as in "we developed...", nor "our" as in "our
method...". This line of thought yields to an intensive use of the passive
voice.</p>
<blockquote>
<p><em>Science is built up of facts, as a house is with stones. But a collection of
facts is no more a science than a heap of stones is a house.
(<a href="https://fr.wikipedia.org/wiki/Henri_Poincaré">Henri Poincaré</a>)</em></p>
</blockquote>
<p>However, passive-voice sentences are a slippery path: they use more words, and
have a tendency to produce more complex and <em>vague</em> sentence structures.
Vagueness is the main matter of concern here, the nemesis of scientific
writers. For this reason, unless there is a clear motivation to use the passive
voice (to emphasize the action over its actor, to describe actions from
anonymous actors, ...), try to <strong>use the active voice whenever possible</strong>.</p>
<h2>To go further</h2>
<p>Have you identified other anti-patterns in scientific papers? Any positive
pattern in your quiver? Feel free to let me know about it.</p>
<p>Related reads:</p>
<ul>
<li><a href="http://ccr.sigcomm.org/online/files/p83-keshavA.pdf">How to Read a Paper</a> by
S. Keshav</li>
</ul>Pendular models for walking over rough terrains2017-06-20T00:00:00+02:00Stéphane Carontag:scaron.info,2017-06-20:research/jnrh-2017.html<p class="authors">Talk given at the <a class="reference external" href="https://jnrh2017.sciencesconf.org/">Journées Nationales de la Robotique Humanoïde (JNRH)</a>, June 2017.</p>
<div class="section" id="abstract">
<h2>Abstract</h2>
<p>The Newton-Euler equations that govern multi-body systems are not integrable in
general. However, they become so in the pendular mode, a specific way of moving
where conservation of the angular momentum is enforced. This property was
successfully showcased for locomotion over horizontal floors (2D locomotion) by
walking-pattern generators based on the LIPM and CART-table models. In this
talk, we will see how to generalize these two models to 3D locomotion while
taking into account both friction and tilted contacts, resulting into the FIP
(3D version of LIPM) and COM-accel (3D version of CART-table) models. We will
demonstrate both approaches in live simulations with the HRP-4 humanoid model.</p>
<img alt="HRP-4 descendant un escalier elliptique (motif généré avec un modèle pendulaire)" class="noborder max-height-400px align-center" src="https://scaron.info/figures/dynamic-walking.png" />
</div>
<div class="section" id="resume">
<h2>Résumé</h2>
<p>Dans cet exposé, nous revenons sur les modèles pendulaires pour la marche en
terrain accidenté. Ceux-ci présentent deux composantes (à vue de nez)
distinctes : l'équation différentielle qui régit la dynamique du système, et
les contraintes d'inégalité qui limitent son actuation. Deux angles d'attaque
pointent ici le bout de leur nez. Le premier procède directement des équations
de Newton-Euler(Z) : la dynamique du système est non-linéaire tandis que ses
contraintes sont linéaires. Une des difficultés est alors d'intégrer le système
entre deux points de collocation (par exemple avec la méthode de Runge-Kutta).
La seconde approche change de commande pour rendre la dynamique linéaire et
invariante, rendant l'intégration exacte au prix de nouvelles contraintes
non-linéaires. Fourrant notre nez dans cette direction, nous présenterons le
modèle du pendule inversé flottant (PIF) utilisé dans notre générateur de
marche tout-terrain. Nous lancerons enfin quelques simulations de l'humanoïde
HRP-4 traversant un escalier circulaire et accidenté, normalement sans se
casser le nez.</p>
</div>
<div class="section" id="content">
<h2>Content</h2>
<table border="1" class="colwidths-given files docutils">
<colgroup>
<col width="10%" />
<col width="90%" />
</colgroup>
<tbody valign="top">
<tr><td><img alt="pdf" class="icon" src="https://scaron.info/images/icons/pdf.png" /></td>
<td><a class="reference external" href="https://scaron.info/files/jnrh-2017/slides.pdf">Slides</a></td>
</tr>
<tr><td><img alt="pdf" class="icon" src="https://scaron.info/images/icons/pdf.png" /></td>
<td><a class="reference external" href="https://hal.archives-ouvertes.fr/hal-01349880/document">Paper on the COM-accel model</a></td>
</tr>
<tr><td><img alt="pdf" class="icon" src="https://scaron.info/images/icons/pdf.png" /></td>
<td><a class="reference external" href="https://hal.archives-ouvertes.fr/hal-01481052/document">Paper on the FIP model</a></td>
</tr>
</tbody>
</table>
</div>
Solving an Ordinary Differential Inequality2017-05-31T00:00:00+02:00Stéphane Carontag:scaron.info,2017-05-31:blog/solving-an-ordinary-differential-inequality.html<p>In his 2008 paper <a class="reference external" href="https://hal.archives-ouvertes.fr/inria-00390555/document">Viability and Predictive Control for Safe Locomotion</a>, Pierre-Brice
Wieber discusses what happens when the center-of-mass of a humanoid reaches the
boundary of its supporting contact area. Mathematically, this gives rise to the
Ordinary Differential Inequality (ODI):</p>
<div class="math">
\begin{equation*}
\frac{\ddot{x}}{\omega^2} \ \geq \ x + b
\end{equation*}
</div>
<p>Under the boundary condition:</p>
<div class="math">
\begin{equation*}
x(t_0) + b \ = \ 0
\end{equation*}
</div>
<p>The paper provides an analytical solution to the ODI as follows:</p>
<div class="math">
\begin{equation*}
x(t) \ \geq \ \frac{\xd(t_0)}{\omega} \sinh\left(\frac{t}{\omega}\right) - b
\end{equation*}
</div>
<p>Let us now detail how to get there, that is to say, how to solve an ordinary
differential <em>inequality</em>.</p>
<div class="section" id="upper-bound-from-an-ode-solution">
<h2>Upper-bound from an ODE solution</h2>
<p>The method to bound ODI solutions relies on the parallel solution to the
Ordinary Differential Equation (ODE), where the inequality sign has been
replaced by an equality. One of the earliest theorems formalizing this was
given by Petrovitsch in 1901:</p>
<blockquote>
<strong>Theorem (Petrovitsch, 1901):</strong> let <span class="math">\(\dot{\gamma} \geq f(\gamma, t)\)</span>
denote an ODI, and let <span class="math">\(\eta\)</span> denote a solution to the ODE
<span class="math">\(\dot{\eta} = f(\eta, t)\)</span> subject to the boundary condition
<span class="math">\(\eta(t_0) = \gamma(t_0)\)</span>. Then,</blockquote>
<div class="math">
\begin{equation*}
\begin{array}{r}
\forall t < t_0, \ \gamma(t) \leq \eta(t) \\
\forall t > t_0, \ \gamma(t) \geq \eta(t)
\end{array}
\end{equation*}
</div>
<p>An equivalent formulation, perhaps more famous today, is called <a class="reference external" href="https://en.wikipedia.org/wiki/Grönwall's_inequality">Gronwall's
inequality</a>:</p>
<blockquote>
<strong>Theorem (Gronwall, 1919):</strong> let <span class="math">\(\beta\)</span> and <span class="math">\(u\)</span> be
real-valued continuous functions defined over the interval <span class="math">\([a, b]\)</span>.
If <span class="math">\(u\)</span> satisfies the differential inequality <span class="math">\(u'(t) \leq
\beta(t) u(t)\)</span>, then it is bounded by the solution of the corresponding
differential equation <span class="math">\(y'(t) = \beta(t) y(t)\)</span>:</blockquote>
<div class="math">
\begin{equation*}
u(t) \ \leq \ u(a) \exp\left( \int_a^t \beta(s) {\rm d} s \right)
\end{equation*}
</div>
<p>In our case, a solution to the ODE associated with our second-order ODI
satisfies:</p>
<div class="math">
\begin{equation*}
\frac{\ydd}{\omega^2} \ = \ y + b
\end{equation*}
</div>
<p>It is well known that the set of solutions to such second-order differential
equations, whose characteristic polynomial has real roots, are linear
combinations of <a class="reference external" href="https://en.wikipedia.org/wiki/Hyperbolic_function">hyperbolic functions</a>, plus the non-homogeneous
part of the solution:</p>
<div class="math">
\begin{equation*}
y = \alpha \cosh(\omega (t - t_0)) + \beta \sinh(\omega (t - t_0)) - b
\end{equation*}
</div>
<p>We now choose the boundary conditions such that <span class="math">\(y(t_0) = x(t_0) = -b\)</span> and
<span class="math">\(\yd(t_0) = \xd(t_0)\)</span>, resulting in:</p>
<div class="math">
\begin{equation*}
y \ = \ \frac{\xd(t_0)}{\omega} \sinh\left(\frac{t}{\omega}\right) - b
\end{equation*}
</div>
<p>We cannot apply Petrovitsch's theorem directly as our variable <span class="math">\(\gamma =
(x, \xd)\)</span> is two-dimensional, but the underlying idea is the same. Consider the
difference <span class="math">\(\delta\)</span> between <span class="math">\(x\)</span> (ODI solution) and <span class="math">\(y\)</span> (ODE
solution). Then:</p>
<div class="math">
\begin{equation*}
\begin{array}{r}
\ddot{\delta} \geq \delta \\
\delta(t_0) = 0 \\
\dot{\delta}(t_0) = 0
\end{array}
\end{equation*}
</div>
<p>Due to the initial condition, either <span class="math">\(\delta\)</span> is uniformly <span class="math">\(0\)</span> for
all <span class="math">\(t \geq t_0\)</span> (in which case the bound is tight), or <span class="math">\(\delta\)</span> is
increasing for <span class="math">\(t \geq t_0\)</span> and thus positive. In both cases, this
difference ends up being positive, so that <span class="math">\(\forall t \geq t_0, x(t) \geq
y(t)\)</span> and <span class="math">\(y\)</span> is indeed an analytical lower-bound to all solutions of the
ODI.</p>
<div class="section" id="references">
<h3>References</h3>
<ul class="simple">
<li>M. Petrovitsch, "Sur une manière d'étendre le théorème de la moyence aux
équations différentielles du premier ordre", <em>Ann. of Math.</em>, <strong>54</strong> : 3 (1901)
pp. 417–436</li>
<li>T. H. Gronwall, "Note on the derivatives with respect to a parameter of the
solutions of a system of differential equations", <em>Ann. of Math.</em>, <strong>20</strong> : 2
(1919) pp. 292–296</li>
</ul>
</div>
</div>
<script type='text/javascript'>if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
var mathjaxscript = document.createElement('script');
mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
mathjaxscript.type = 'text/javascript';
mathjaxscript.src = '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
mathjaxscript[(window.opera ? "innerHTML" : "text")] =
"MathJax.Hub.Config({" +
" config: ['MMLorHTML.js']," +
" TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'AMS' } }," +
" jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
" extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
" displayAlign: 'center'," +
" displayIndent: '0em'," +
" showMathMenu: true," +
" tex2jax: { " +
" inlineMath: [ ['\\\\(','\\\\)'] ], " +
" displayMath: [ ['$$','$$'] ]," +
" processEscapes: true," +
" preview: 'TeX'," +
" }, " +
" 'HTML-CSS': { " +
" styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#333 ! important'} }, linebreaks: { automatic: false, width: 'container' }" +
" }, " +
" 'TeX': { " +
" Macros: { " +
" defeq: \"{\\\\stackrel{\\\\mathrm{def}}{=}}\"," +
" Ld: \"{\\\\dot{L}}\"," +
" LdG: \"{\\\\dot{L}_G}\"," +
" bfA: \"{\\\\boldsymbol{A}}\"," +
" bfB: \"{\\\\boldsymbol{B}}\"," +
" bfC: \"{\\\\boldsymbol{C}}\"," +
" bfD: \"{\\\\boldsymbol{D}}\"," +
" bfE: \"{\\\\boldsymbol{E}}\"," +
" bfF: \"{\\\\boldsymbol{F}}\"," +
" bfG: \"{\\\\boldsymbol{G}}\"," +
" bfH: \"{\\\\boldsymbol{H}}\"," +
" bfI: \"{\\\\boldsymbol{I}}\"," +
" bfJ: \"{\\\\boldsymbol{J}}\"," +
" bfK: \"{\\\\boldsymbol{K}}\"," +
" bfL: \"{\\\\boldsymbol{L}}\"," +
" bfM: \"{\\\\boldsymbol{M}}\"," +
" bfN: \"{\\\\boldsymbol{N}}\"," +
" bfO: \"{\\\\boldsymbol{O}}\"," +
" bfP: \"{\\\\boldsymbol{P}}\"," +
" bfQ: \"{\\\\boldsymbol{Q}}\"," +
" bfR: \"{\\\\boldsymbol{R}}\"," +
" bfS: \"{\\\\boldsymbol{S}}\"," +
" bfT: \"{\\\\boldsymbol{T}}\"," +
" bfU: \"{\\\\boldsymbol{U}}\"," +
" bfV: \"{\\\\boldsymbol{V}}\"," +
" bfW: \"{\\\\boldsymbol{W}}\"," +
" bfX: \"{\\\\boldsymbol{X}}\"," +
" bfY: \"{\\\\boldsymbol{Y}}\"," +
" bfZ: \"{\\\\boldsymbol{Z}}\"," +
" bfa: \"{\\\\boldsymbol{a}}\"," +
" bfb: \"{\\\\boldsymbol{b}}\"," +
" bfc: \"{\\\\boldsymbol{c}}\"," +
" bfd: \"{\\\\boldsymbol{d}}\"," +
" bfe: \"{\\\\boldsymbol{e}}\"," +
" bff: \"{\\\\boldsymbol{f}}\"," +
" bfg: \"{\\\\boldsymbol{g}}\"," +
" bfh: \"{\\\\boldsymbol{h}}\"," +
" bfi: \"{\\\\boldsymbol{i}}\"," +
" bfj: \"{\\\\boldsymbol{j}}\"," +
" bfk: \"{\\\\boldsymbol{k}}\"," +
" bfl: \"{\\\\boldsymbol{l}}\"," +
" bfm: \"{\\\\boldsymbol{m}}\"," +
" bfn: \"{\\\\boldsymbol{n}}\"," +
" bfo: \"{\\\\boldsymbol{o}}\"," +
" bfp: \"{\\\\boldsymbol{p}}\"," +
" bfq: \"{\\\\boldsymbol{q}}\"," +
" bfr: \"{\\\\boldsymbol{r}}\"," +
" bfs: \"{\\\\boldsymbol{s}}\"," +
" bft: \"{\\\\boldsymbol{t}}\"," +
" bfu: \"{\\\\boldsymbol{u}}\"," +
" bfv: \"{\\\\boldsymbol{v}}\"," +
" bfw: \"{\\\\boldsymbol{w}}\"," +
" bfx: \"{\\\\boldsymbol{x}}\"," +
" bfy: \"{\\\\boldsymbol{y}}\"," +
" bfz: \"{\\\\boldsymbol{z}}\"," +
" bfalpha: \"{\\\\boldsymbol{\\\\alpha}}\"," +
" bfbeta: \"{\\\\boldsymbol{\\\\beta}}\"," +
" bfgamma: \"{\\\\boldsymbol{\\\\gamma}}\"," +
" bftau: \"{\\\\boldsymbol{\\\\tau}}\"," +
" bfomega: \"{\\\\boldsymbol{\\\\omega}}\"," +
" calA: \"{\\\\cal A}\"," +
" calB: \"{\\\\cal B}\"," +
" calC: \"{\\\\cal C}\"," +
" calD: \"{\\\\cal D}\"," +
" calE: \"{\\\\cal E}\"," +
" calF: \"{\\\\cal F}\"," +
" calG: \"{\\\\cal G}\"," +
" calH: \"{\\\\cal H}\"," +
" calI: \"{\\\\cal I}\"," +
" calJ: \"{\\\\cal J}\"," +
" calK: \"{\\\\cal K}\"," +
" calL: \"{\\\\cal L}\"," +
" calM: \"{\\\\cal M}\"," +
" calN: \"{\\\\cal N}\"," +
" calO: \"{\\\\cal O}\"," +
" calP: \"{\\\\cal P}\"," +
" calQ: \"{\\\\cal Q}\"," +
" calR: \"{\\\\cal R}\"," +
" calS: \"{\\\\cal S}\"," +
" calT: \"{\\\\cal T}\"," +
" calU: \"{\\\\cal U}\"," +
" calV: \"{\\\\cal V}\"," +
" calW: \"{\\\\cal W}\"," +
" calX: \"{\\\\cal X}\"," +
" calY: \"{\\\\cal Y}\"," +
" calZ: \"{\\\\cal Z}\"," +
" d: [\"{\\\\rm d}{#1}\", 1]," +
" dim: \"{\\\\rm dim}\"," +
" p: \"{\\\\boldsymbol{p}}\"," +
" pd: \"{\\\\dot{\\\\bfp}}\"," +
" pdd: \"{\\\\ddot{\\\\bfp}}\"," +
" q: \"{\\\\boldsymbol{q}}\"," +
" qd: \"{\\\\dot{\\\\bfq}}\"," +
" qdd: \"{\\\\ddot{\\\\bfq}}\"," +
" xd: \"{\\\\dot{x}}\"," +
" xdd: \"{\\\\ddot{x}}\"," +
" yd: \"{\\\\dot{y}}\"," +
" ydd: \"{\\\\ddot{y}}\"," +
" zd: \"{\\\\dot{z}}\"," +
" zdd: \"{\\\\ddot{z}}\"," +
" defeq: \"{\\\\stackrel{\\\\mathrm{def}}{\\\\ =\\\\ }}\"," +
" } " +
" } " +
"}); ";
(document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
</script>Quadratic Programming2017-04-24T00:00:00+02:00Stéphane Carontag:scaron.info,2017-04-24:teaching/quadratic-programming.html<p>Quadratic programs are a particular kind of mathematical optimization problems
that can be applied to solve a variety of problems, for instance: in statistics
for curve fitting, in machine learning to compute <a class="reference external" href="https://en.wikipedia.org/wiki/Support_vector_machine">support vector machines
(SVMs)</a>, in robotics to
solve <a class="reference external" href="/teaching/inverse-kinematics.html">inverse kinematics</a>, etc. They are
the first step beyond linear programming (LP) in convex optimization.</p>
<p>A quadratic program (QP) is written in standard form as:</p>
<div class="math">
\begin{equation*}
\begin{array}{rl}
\mathrm{minimize} & (1/2) x^T P x + q^T x \\
\mathrm{subject\ to} & G x \leq h \\
& A x = b
\end{array}
\end{equation*}
</div>
<p>Here, <span class="math">\(x\)</span> is the vector of optimization variables <span class="math">\(x_1, \ldots,
x_n\)</span>. The matrix <span class="math">\(P\)</span> and vector <span class="math">\(q\)</span> are used to define any
<em>quadratic</em> objective function on these variables, while the matrix-vector
couples <span class="math">\((G, h)\)</span> and <span class="math">\((A, b)\)</span> are used to define inequality and
equality constraints, respectively. Vector inequalities apply coordinate by
coordinate.</p>
<p>This mathematical construction means the following: a QP finds the minimum of a
quadratic function over a linear set:</p>
<img alt="QP is about finding the minimum of a quadratic function (circles) over a linear set (polygon)" class="center" src="https://scaron.info/images/quadratic-programming.jpg" />
<p>In the 2D illustration above, the level sets of the quadratic function are
represented by ellipses, and the linear set is the blue polygon. Since the
global optimal of the objective function is outside of the polygon, the
solution <span class="math">\(x^*\)</span> of the QP lies at the boundary of the linear set. The set
of linear constraints that are <em>saturated</em> at <span class="math">\(x^*\)</span> is called the <a class="reference external" href="https://en.wikipedia.org/wiki/Active_set_method">active
set</a>, but that's a story for
another post...</p>
<p>Back to the standard form, notice that there is no constant term in the
objective function. Indeed, it would have no effect on the result of the
optimization, which is the location of the solution <span class="math">\(x^*\)</span>. Expressions
like <span class="math">\(\| A x - b \|^2\)</span> are written in standard form as <span class="math">\(x^T (A^T A)
x - 2 (A^T b)^T x\)</span>.</p>
<p>The standard form also assumes without loss of generality that the matrix
<span class="math">\(P\)</span> is symmetric. Any matrix <span class="math">\(M\)</span> can be decomposed as sum of its
symmetric part <span class="math">\(M^+\)</span> and antisymmetric part <span class="math">\(M^-\)</span>, and the latter
yields zero in <span class="math">\(x^T M^- x\)</span>. Note that some QP solvers assume that you
feed them a symmetric cost matrix: they won't check this, and will return wrong
results if you don't.</p>
<div class="section" id="solving-qps-in-python">
<h2>Solving QPs in Python</h2>
<p>The most readily-available QP solver in Python is <a class="reference external" href="https://pypi.python.org/pypi/quadprog/">quadprog</a>. It can be installed by simply
running <tt class="docutils literal">sudo pip install quadprog</tt> if you have the <em>pip</em> package manager on
your system. To keep the same matrix names as above, let us wrap it with the
following function:</p>
<pre class="code python literal-block">
<span class="kn">import</span> <span class="nn">quadprog</span>
<span class="k">def</span> <span class="nf">solve_qp</span><span class="p">(</span><span class="n">P</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">G</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">h</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">A</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">qp_G</span> <span class="o">=</span> <span class="o">.</span><span class="mi">5</span> <span class="o">*</span> <span class="p">(</span><span class="n">P</span> <span class="o">+</span> <span class="n">P</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="c1"># make sure P is symmetric</span>
<span class="n">qp_a</span> <span class="o">=</span> <span class="o">-</span><span class="n">q</span>
<span class="k">if</span> <span class="n">A</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
<span class="n">qp_C</span> <span class="o">=</span> <span class="o">-</span><span class="n">numpy</span><span class="o">.</span><span class="n">vstack</span><span class="p">([</span><span class="n">A</span><span class="p">,</span> <span class="n">G</span><span class="p">])</span><span class="o">.</span><span class="n">T</span>
<span class="n">qp_b</span> <span class="o">=</span> <span class="o">-</span><span class="n">numpy</span><span class="o">.</span><span class="n">hstack</span><span class="p">([</span><span class="n">b</span><span class="p">,</span> <span class="n">h</span><span class="p">])</span>
<span class="n">meq</span> <span class="o">=</span> <span class="n">A</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span> <span class="c1"># no equality constraint</span>
<span class="n">qp_C</span> <span class="o">=</span> <span class="o">-</span><span class="n">G</span><span class="o">.</span><span class="n">T</span>
<span class="n">qp_b</span> <span class="o">=</span> <span class="o">-</span><span class="n">h</span>
<span class="n">meq</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">return</span> <span class="n">quadprog</span><span class="o">.</span><span class="n">solve_qp</span><span class="p">(</span><span class="n">qp_G</span><span class="p">,</span> <span class="n">qp_a</span><span class="p">,</span> <span class="n">qp_C</span><span class="p">,</span> <span class="n">qp_b</span><span class="p">,</span> <span class="n">meq</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</pre>
<p>For a small example, let us see how to solve the following QP:</p>
<div class="math">
\begin{align*}
\begin{array}{rl}
\mathrm{minimize} & \left\| \left[\begin{array}{ccc}
1 & 2 & 0 \\
-8 & 3 & 2 \\
0 & 1 & 1 \end{array}\right] \left[\begin{array}{c} x_1 \\ x_2 \\
x_3\end{array}\right] - \left[\begin{array}{c} 3 \\ 2 \\
3\end{array}\right] \right\|^2 \\
\mathrm{subject\ to} & \left[\begin{array}{ccc}
1 & 2 & 1 \\
2 & 0 & 1 \\
-1 & 2 & -1 \end{array}\right] \left[\begin{array}{c} x_1 \\ x_2 \\
x_3\end{array}\right] \leq \left[\begin{array}{c}
3 \\ 2 \\ -2 \end{array} \right]
\end{array}
\end{align*}
</div>
<p>First, we write our QP matrices in proper format:</p>
<pre class="code python literal-block">
<span class="n">M</span> <span class="o">=</span> <span class="n">array</span><span class="p">([[</span><span class="mf">1.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="mf">0.</span><span class="p">],</span> <span class="p">[</span><span class="o">-</span><span class="mf">8.</span><span class="p">,</span> <span class="mf">3.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">],</span> <span class="p">[</span><span class="mf">0.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">]])</span>
<span class="n">P</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">M</span><span class="o">.</span><span class="n">T</span><span class="p">,</span> <span class="n">M</span><span class="p">)</span>
<span class="n">q</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">array</span><span class="p">([</span><span class="mf">3.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="mf">3.</span><span class="p">]),</span> <span class="n">M</span><span class="p">)</span><span class="o">.</span><span class="n">reshape</span><span class="p">((</span><span class="mi">3</span><span class="p">,))</span>
<span class="n">G</span> <span class="o">=</span> <span class="n">array</span><span class="p">([[</span><span class="mf">1.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">],</span> <span class="p">[</span><span class="mf">2.</span><span class="p">,</span> <span class="mf">0.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">],</span> <span class="p">[</span><span class="o">-</span><span class="mf">1.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.</span><span class="p">]])</span>
<span class="n">h</span> <span class="o">=</span> <span class="n">array</span><span class="p">([</span><span class="mf">3.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="o">-</span><span class="mf">2.</span><span class="p">])</span><span class="o">.</span><span class="n">reshape</span><span class="p">((</span><span class="mi">3</span><span class="p">,))</span>
</pre>
<p>Finally, we compute the solution using the function above:</p>
<pre class="code python literal-block">
<span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">solve_qp</span><span class="p">(</span><span class="n">P</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">G</span><span class="p">,</span> <span class="n">h</span><span class="p">)</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">array</span><span class="p">([</span><span class="o">-</span><span class="mf">0.49025721</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.57755261</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.66484801</span><span class="p">])</span>
</pre>
<p>To go further, you can check out this blog post on <a class="reference external" href="/blog/quadratic-programming-in-python.html">Quadratic Programming in
Python</a>, as well as the
<a class="reference external" href="https://github.com/stephane-caron/qpsolvers">qpsolvers</a> which implements the
<tt class="docutils literal">solve_qp</tt> function for a variety of QP solvers.</p>
</div>
<script type='text/javascript'>if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
var mathjaxscript = document.createElement('script');
mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
mathjaxscript.type = 'text/javascript';
mathjaxscript.src = '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
mathjaxscript[(window.opera ? "innerHTML" : "text")] =
"MathJax.Hub.Config({" +
" config: ['MMLorHTML.js']," +
" TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'AMS' } }," +
" jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
" extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
" displayAlign: 'center'," +
" displayIndent: '0em'," +
" showMathMenu: true," +
" tex2jax: { " +
" inlineMath: [ ['\\\\(','\\\\)'] ], " +
" displayMath: [ ['$$','$$'] ]," +
" processEscapes: true," +
" preview: 'TeX'," +
" }, " +
" 'HTML-CSS': { " +
" styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#333 ! important'} }, linebreaks: { automatic: false, width: 'container' }" +
" }, " +
" 'TeX': { " +
" Macros: { " +
" defeq: \"{\\\\stackrel{\\\\mathrm{def}}{=}}\"," +
" Ld: \"{\\\\dot{L}}\"," +
" LdG: \"{\\\\dot{L}_G}\"," +
" bfA: \"{\\\\boldsymbol{A}}\"," +
" bfB: \"{\\\\boldsymbol{B}}\"," +
" bfC: \"{\\\\boldsymbol{C}}\"," +
" bfD: \"{\\\\boldsymbol{D}}\"," +
" bfE: \"{\\\\boldsymbol{E}}\"," +
" bfF: \"{\\\\boldsymbol{F}}\"," +
" bfG: \"{\\\\boldsymbol{G}}\"," +
" bfH: \"{\\\\boldsymbol{H}}\"," +
" bfI: \"{\\\\boldsymbol{I}}\"," +
" bfJ: \"{\\\\boldsymbol{J}}\"," +
" bfK: \"{\\\\boldsymbol{K}}\"," +
" bfL: \"{\\\\boldsymbol{L}}\"," +
" bfM: \"{\\\\boldsymbol{M}}\"," +
" bfN: \"{\\\\boldsymbol{N}}\"," +
" bfO: \"{\\\\boldsymbol{O}}\"," +
" bfP: \"{\\\\boldsymbol{P}}\"," +
" bfQ: \"{\\\\boldsymbol{Q}}\"," +
" bfR: \"{\\\\boldsymbol{R}}\"," +
" bfS: \"{\\\\boldsymbol{S}}\"," +
" bfT: \"{\\\\boldsymbol{T}}\"," +
" bfU: \"{\\\\boldsymbol{U}}\"," +
" bfV: \"{\\\\boldsymbol{V}}\"," +
" bfW: \"{\\\\boldsymbol{W}}\"," +
" bfX: \"{\\\\boldsymbol{X}}\"," +
" bfY: \"{\\\\boldsymbol{Y}}\"," +
" bfZ: \"{\\\\boldsymbol{Z}}\"," +
" bfa: \"{\\\\boldsymbol{a}}\"," +
" bfb: \"{\\\\boldsymbol{b}}\"," +
" bfc: \"{\\\\boldsymbol{c}}\"," +
" bfd: \"{\\\\boldsymbol{d}}\"," +
" bfe: \"{\\\\boldsymbol{e}}\"," +
" bff: \"{\\\\boldsymbol{f}}\"," +
" bfg: \"{\\\\boldsymbol{g}}\"," +
" bfh: \"{\\\\boldsymbol{h}}\"," +
" bfi: \"{\\\\boldsymbol{i}}\"," +
" bfj: \"{\\\\boldsymbol{j}}\"," +
" bfk: \"{\\\\boldsymbol{k}}\"," +
" bfl: \"{\\\\boldsymbol{l}}\"," +
" bfm: \"{\\\\boldsymbol{m}}\"," +
" bfn: \"{\\\\boldsymbol{n}}\"," +
" bfo: \"{\\\\boldsymbol{o}}\"," +
" bfp: \"{\\\\boldsymbol{p}}\"," +
" bfq: \"{\\\\boldsymbol{q}}\"," +
" bfr: \"{\\\\boldsymbol{r}}\"," +
" bfs: \"{\\\\boldsymbol{s}}\"," +
" bft: \"{\\\\boldsymbol{t}}\"," +
" bfu: \"{\\\\boldsymbol{u}}\"," +
" bfv: \"{\\\\boldsymbol{v}}\"," +
" bfw: \"{\\\\boldsymbol{w}}\"," +
" bfx: \"{\\\\boldsymbol{x}}\"," +
" bfy: \"{\\\\boldsymbol{y}}\"," +
" bfz: \"{\\\\boldsymbol{z}}\"," +
" bfalpha: \"{\\\\boldsymbol{\\\\alpha}}\"," +
" bfbeta: \"{\\\\boldsymbol{\\\\beta}}\"," +
" bfgamma: \"{\\\\boldsymbol{\\\\gamma}}\"," +
" bftau: \"{\\\\boldsymbol{\\\\tau}}\"," +
" bfomega: \"{\\\\boldsymbol{\\\\omega}}\"," +
" calA: \"{\\\\cal A}\"," +
" calB: \"{\\\\cal B}\"," +
" calC: \"{\\\\cal C}\"," +
" calD: \"{\\\\cal D}\"," +
" calE: \"{\\\\cal E}\"," +
" calF: \"{\\\\cal F}\"," +
" calG: \"{\\\\cal G}\"," +
" calH: \"{\\\\cal H}\"," +
" calI: \"{\\\\cal I}\"," +
" calJ: \"{\\\\cal J}\"," +
" calK: \"{\\\\cal K}\"," +
" calL: \"{\\\\cal L}\"," +
" calM: \"{\\\\cal M}\"," +
" calN: \"{\\\\cal N}\"," +
" calO: \"{\\\\cal O}\"," +
" calP: \"{\\\\cal P}\"," +
" calQ: \"{\\\\cal Q}\"," +
" calR: \"{\\\\cal R}\"," +
" calS: \"{\\\\cal S}\"," +
" calT: \"{\\\\cal T}\"," +
" calU: \"{\\\\cal U}\"," +
" calV: \"{\\\\cal V}\"," +
" calW: \"{\\\\cal W}\"," +
" calX: \"{\\\\cal X}\"," +
" calY: \"{\\\\cal Y}\"," +
" calZ: \"{\\\\cal Z}\"," +
" d: [\"{\\\\rm d}{#1}\", 1]," +
" dim: \"{\\\\rm dim}\"," +
" p: \"{\\\\boldsymbol{p}}\"," +
" pd: \"{\\\\dot{\\\\bfp}}\"," +
" pdd: \"{\\\\ddot{\\\\bfp}}\"," +
" q: \"{\\\\boldsymbol{q}}\"," +
" qd: \"{\\\\dot{\\\\bfq}}\"," +
" qdd: \"{\\\\ddot{\\\\bfq}}\"," +
" xd: \"{\\\\dot{x}}\"," +
" xdd: \"{\\\\ddot{x}}\"," +
" yd: \"{\\\\dot{y}}\"," +
" ydd: \"{\\\\ddot{y}}\"," +
" zd: \"{\\\\dot{z}}\"," +
" zdd: \"{\\\\ddot{z}}\"," +
" defeq: \"{\\\\stackrel{\\\\mathrm{def}}{\\\\ =\\\\ }}\"," +
" } " +
" } " +
"}); ";
(document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
</script>Quadratic Programming in Python2017-04-24T00:00:00+02:00Stéphane Carontag:scaron.info,2017-04-24:blog/quadratic-programming-in-python.html<p>Quadratic programs are a particular kind of mathematical optimization problems
that can be applied to solve a variety of problems, for instance: in statistics
for curve fitting, in machine learning to compute <a class="reference external" href="https://en.wikipedia.org/wiki/Support_vector_machine">support vector machines
(SVMs)</a>, in robotics to
solve <a class="reference external" href="/teaching/inverse-kinematics.html">inverse kinematics</a>, etc. They are
the first step beyond linear programming (LP) in convex optimization. We will
now see how to solve quadratic programs in Python using a number of available
solvers: CVXOPT, CVXPY, Gurobi, MOSEK, qpOASES and quadprog.</p>
<div class="section" id="standard-form-of-quadratic-programs">
<h2>Standard form of quadratic programs</h2>
<p>A quadratic program (QP) is written in standard form as:</p>
<div class="math">
\begin{equation*}
\begin{array}{rl}
\mathrm{minimize} & (1/2) x^T P x + q^T x \\
\mathrm{subject\ to} & G x \leq h \\
& A x = b
\end{array}
\end{equation*}
</div>
<p>Here, <span class="math">\(x\)</span> is the vector of optimization variables <span class="math">\(x_1, \ldots,
x_n\)</span>. The matrix <span class="math">\(P\)</span> and vector <span class="math">\(q\)</span> are used to define any
<em>quadratic</em> objective function on these variables, while the matrix-vector
couples <span class="math">\((G, h)\)</span> and <span class="math">\((A, b)\)</span> are used to define inequality and
equality constraints, respectively. Vector inequalities apply coordinate by
coordinate.</p>
<p>This mathematical construction means the following: a QP finds the minimum of a
quadratic function over a linear set:</p>
<img alt="QP is about finding the minimum of a quadratic function (circles) over a linear set (polygon)" class="center" src="https://scaron.info/images/quadratic-programming.jpg" />
<p>In the 2D illustration above, the level sets of the quadratic function are
represented by ellipses, and the linear set is the blue polygon. Since the
global optimal of the objective function is outside of the polygon, the
solution <span class="math">\(x^*\)</span> of the QP lies at the boundary of the linear set. The set
of linear constraints that are <em>saturated</em> at <span class="math">\(x^*\)</span> is called the <a class="reference external" href="https://en.wikipedia.org/wiki/Active_set_method">active
set</a>, but that's a story for
another post...</p>
<p>Back to the standard form, notice that there is no constant term in the
objective function. Indeed, it would have no effect on the result of the
optimization, which is the location of the solution <span class="math">\(x^*\)</span>. Expressions
like <span class="math">\(\| A x - b \|^2\)</span> are written in standard form as <span class="math">\(x^T (A^T A)
x - 2 (A^T b)^T x\)</span>.</p>
<p>The standard form also assumes without loss of generality that the matrix
<span class="math">\(P\)</span> is symmetric. Any matrix <span class="math">\(M\)</span> can be decomposed as sum of its
symmetric part <span class="math">\(M^+\)</span> and antisymmetric part <span class="math">\(M^-\)</span>, and the latter
yields zero in <span class="math">\(x^T M^- x\)</span>. Note that some QP solvers assume that you
feed them a symmetric cost matrix: they won't check this, and will return wrong
results if you don't.</p>
</div>
<div class="section" id="setting-up-qp-solvers">
<h2>Setting up QP solvers</h2>
<p>The two readily-available QP solvers in Python are CVXOPT and quadprog. They
can be installed by:</p>
<pre class="code bash literal-block">
$ sudo <span class="nv">CVXOPT_BUILD_GLPK</span><span class="o">=</span><span class="m">1</span> pip install cvxopt
$ sudo pip install quadprog
</pre>
<p>CVXOPT uses its own matrix type, and it requires the matrix <span class="math">\(P\)</span> of the
objective function to be symmetric. To be on the safe side, you can wrap it as
follows:</p>
<pre class="code python literal-block">
<span class="k">def</span> <span class="nf">cvxopt_solve_qp</span><span class="p">(</span><span class="n">P</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">G</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">h</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">A</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">P</span> <span class="o">=</span> <span class="o">.</span><span class="mi">5</span> <span class="o">*</span> <span class="p">(</span><span class="n">P</span> <span class="o">+</span> <span class="n">P</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="c1"># make sure P is symmetric</span>
<span class="n">args</span> <span class="o">=</span> <span class="p">[</span><span class="n">matrix</span><span class="p">(</span><span class="n">P</span><span class="p">),</span> <span class="n">matrix</span><span class="p">(</span><span class="n">q</span><span class="p">)]</span>
<span class="k">if</span> <span class="n">G</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
<span class="n">args</span><span class="o">.</span><span class="n">extend</span><span class="p">([</span><span class="n">matrix</span><span class="p">(</span><span class="n">G</span><span class="p">),</span> <span class="n">matrix</span><span class="p">(</span><span class="n">h</span><span class="p">)])</span>
<span class="k">if</span> <span class="n">A</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
<span class="n">args</span><span class="o">.</span><span class="n">extend</span><span class="p">([</span><span class="n">matrix</span><span class="p">(</span><span class="n">A</span><span class="p">),</span> <span class="n">matrix</span><span class="p">(</span><span class="n">b</span><span class="p">)])</span>
<span class="n">sol</span> <span class="o">=</span> <span class="n">cvxopt</span><span class="o">.</span><span class="n">solvers</span><span class="o">.</span><span class="n">qp</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="k">if</span> <span class="s1">'optimal'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">sol</span><span class="p">[</span><span class="s1">'status'</span><span class="p">]:</span>
<span class="k">return</span> <span class="bp">None</span>
<span class="k">return</span> <span class="n">numpy</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="n">sol</span><span class="p">[</span><span class="s1">'x'</span><span class="p">])</span><span class="o">.</span><span class="n">reshape</span><span class="p">((</span><span class="n">P</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">],))</span>
</pre>
<p>The quadprog module works directly on NumPy arrays so there is no need for
type conversion. Its matrix representation is equivalent but with different
names:</p>
<pre class="code python literal-block">
<span class="k">def</span> <span class="nf">quadprog_solve_qp</span><span class="p">(</span><span class="n">P</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">G</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">h</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">A</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">qp_G</span> <span class="o">=</span> <span class="o">.</span><span class="mi">5</span> <span class="o">*</span> <span class="p">(</span><span class="n">P</span> <span class="o">+</span> <span class="n">P</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="c1"># make sure P is symmetric</span>
<span class="n">qp_a</span> <span class="o">=</span> <span class="o">-</span><span class="n">q</span>
<span class="k">if</span> <span class="n">A</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
<span class="n">qp_C</span> <span class="o">=</span> <span class="o">-</span><span class="n">numpy</span><span class="o">.</span><span class="n">vstack</span><span class="p">([</span><span class="n">A</span><span class="p">,</span> <span class="n">G</span><span class="p">])</span><span class="o">.</span><span class="n">T</span>
<span class="n">qp_b</span> <span class="o">=</span> <span class="o">-</span><span class="n">numpy</span><span class="o">.</span><span class="n">hstack</span><span class="p">([</span><span class="n">b</span><span class="p">,</span> <span class="n">h</span><span class="p">])</span>
<span class="n">meq</span> <span class="o">=</span> <span class="n">A</span><span class="o">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span> <span class="c1"># no equality constraint</span>
<span class="n">qp_C</span> <span class="o">=</span> <span class="o">-</span><span class="n">G</span><span class="o">.</span><span class="n">T</span>
<span class="n">qp_b</span> <span class="o">=</span> <span class="o">-</span><span class="n">h</span>
<span class="n">meq</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">return</span> <span class="n">quadprog</span><span class="o">.</span><span class="n">solve_qp</span><span class="p">(</span><span class="n">qp_G</span><span class="p">,</span> <span class="n">qp_a</span><span class="p">,</span> <span class="n">qp_C</span><span class="p">,</span> <span class="n">qp_b</span><span class="p">,</span> <span class="n">meq</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</pre>
<p>In case you want to try out other solvers, I implemented similar wrappers for
those I could get my hands on (like Gurobi or MOSEK) in the <a class="reference external" href="https://github.com/stephane-caron/qpsolvers">qpsolvers</a> module.</p>
</div>
<div class="section" id="example">
<h2>Example</h2>
<p>For a small example, let us see how to solve the following QP:</p>
<div class="math">
\begin{align*}
\begin{array}{rl}
\mathrm{minimize} & \left\| \left[\begin{array}{ccc}
1 & 2 & 0 \\
-8 & 3 & 2 \\
0 & 1 & 1 \end{array}\right] \left[\begin{array}{c} x_1 \\ x_2 \\
x_3\end{array}\right] - \left[\begin{array}{c} 3 \\ 2 \\
3\end{array}\right] \right\|^2 \\
\mathrm{subject\ to} & \left[\begin{array}{ccc}
1 & 2 & 1 \\
2 & 0 & 1 \\
-1 & 2 & -1 \end{array}\right] \left[\begin{array}{c} x_1 \\ x_2 \\
x_3\end{array}\right] \leq \left[\begin{array}{c}
3 \\ 2 \\ -2 \end{array} \right]
\end{array}
\end{align*}
</div>
<p>First, we write our QP matrices in proper format:</p>
<pre class="code python literal-block">
<span class="n">M</span> <span class="o">=</span> <span class="n">array</span><span class="p">([[</span><span class="mf">1.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="mf">0.</span><span class="p">],</span> <span class="p">[</span><span class="o">-</span><span class="mf">8.</span><span class="p">,</span> <span class="mf">3.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">],</span> <span class="p">[</span><span class="mf">0.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">]])</span>
<span class="n">P</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">M</span><span class="o">.</span><span class="n">T</span><span class="p">,</span> <span class="n">M</span><span class="p">)</span>
<span class="n">q</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">array</span><span class="p">([</span><span class="mf">3.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="mf">3.</span><span class="p">]),</span> <span class="n">M</span><span class="p">)</span><span class="o">.</span><span class="n">reshape</span><span class="p">((</span><span class="mi">3</span><span class="p">,))</span>
<span class="n">G</span> <span class="o">=</span> <span class="n">array</span><span class="p">([[</span><span class="mf">1.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">],</span> <span class="p">[</span><span class="mf">2.</span><span class="p">,</span> <span class="mf">0.</span><span class="p">,</span> <span class="mf">1.</span><span class="p">],</span> <span class="p">[</span><span class="o">-</span><span class="mf">1.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.</span><span class="p">]])</span>
<span class="n">h</span> <span class="o">=</span> <span class="n">array</span><span class="p">([</span><span class="mf">3.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="o">-</span><span class="mf">2.</span><span class="p">])</span><span class="o">.</span><span class="n">reshape</span><span class="p">((</span><span class="mi">3</span><span class="p">,))</span>
</pre>
<p>Finally, we compute the solution using one of the available QP solvers:</p>
<pre class="code python literal-block">
<span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">cvxopt_solve_qp</span><span class="p">(</span><span class="n">P</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">G</span><span class="p">,</span> <span class="n">h</span><span class="p">)</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">array</span><span class="p">([</span><span class="o">-</span><span class="mf">0.49025721</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.57755278</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.66484775</span><span class="p">])</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="n">quadprog_solve_qp</span><span class="p">(</span><span class="n">P</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">G</span><span class="p">,</span> <span class="n">h</span><span class="p">)</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="n">array</span><span class="p">([</span><span class="o">-</span><span class="mf">0.49025721</span><span class="p">,</span> <span class="o">-</span><span class="mf">1.57755261</span><span class="p">,</span> <span class="o">-</span><span class="mf">0.66484801</span><span class="p">])</span>
</pre>
</div>
<div class="section" id="comparing-solver-performances">
<h2>Comparing solver performances</h2>
<p>In the following benchmark, I compared six different solvers. Three of them are
numerical, which is the approach we have seen so far:</p>
<ul class="simple">
<li><a class="reference external" href="http://cvxopt.org">CVXOPT</a></li>
<li><a class="reference external" href="https://projects.coin-or.org/qpOASES">qpOASES</a></li>
<li><a class="reference external" href="https://pypi.python.org/pypi/quadprog/">quadprog</a></li>
</ul>
<p>The three others are symbolic, meaning that if you dig into their API they
allow you to construct your problem formally (with variable names) rather than
using the matrix-vector representation. This is convenient for big sparse
problems, but slower and small problems such as the one we are looking at here.
The three symbolic solvers I tested are:</p>
<ul class="simple">
<li><a class="reference external" href="http://www.cvxpy.org/en/latest/">CVXPY</a></li>
<li><a class="reference external" href="https://www.gurobi.com/">Gurobi</a></li>
<li><a class="reference external" href="https://mosek.com/">MOSEK</a> (as wrapped by CVXOPT)</li>
</ul>
<p>Here is a sample of computation times on my machine:</p>
<ul class="simple">
<li>CVXOPT: 1000 loops, best of 3: 559 µs per loop</li>
<li>CVXPY: 100 loops, best of 3: 2.81 ms per loop</li>
<li>Gurobi: 1000 loops, best of 3: 865 µs per loop</li>
<li>MOSEK: 100 loops, best of 3: 7.24 ms per loop</li>
<li>qpOASES: 10000 loops, best of 3: 31.5 µs per loop</li>
<li>quadprog: 10000 loops, best of 3: 34.1 µs per loop</li>
</ul>
<p>For further investigation, let us generate random problems of arbitrary size as
follows:</p>
<pre class="code python literal-block">
<span class="k">def</span> <span class="nf">solve_random_qp</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">solver</span><span class="p">):</span>
<span class="n">M</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">((</span><span class="n">n</span><span class="p">,</span> <span class="n">n</span><span class="p">)),</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="n">P</span><span class="p">,</span> <span class="n">q</span> <span class="o">=</span> <span class="n">dot</span><span class="p">(</span><span class="n">M</span><span class="o">.</span><span class="n">T</span><span class="p">,</span> <span class="n">M</span><span class="p">),</span> <span class="n">dot</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="n">M</span><span class="p">)</span><span class="o">.</span><span class="n">reshape</span><span class="p">((</span><span class="n">n</span><span class="p">,))</span>
<span class="n">G</span> <span class="o">=</span> <span class="n">toeplitz</span><span class="p">([</span><span class="mf">1.</span><span class="p">,</span> <span class="mf">0.</span><span class="p">,</span> <span class="mf">0.</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span><span class="mf">0.</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">3</span><span class="p">),</span> <span class="p">[</span><span class="mf">1.</span><span class="p">,</span> <span class="mf">2.</span><span class="p">,</span> <span class="mf">3.</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span><span class="mf">0.</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">3</span><span class="p">))</span>
<span class="n">h</span> <span class="o">=</span> <span class="n">ones</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="k">return</span> <span class="n">solve_qp</span><span class="p">(</span><span class="n">P</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">G</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">solver</span><span class="o">=</span><span class="n">solver</span><span class="p">)</span>
</pre>
<p>The Toeplitz matrix used to generate inequalities is just an upper-tridiagonal
matrix with coefficients 1, 2, 3, all other coefficients being zero. This
matrix is sparse but represented by (dense) NumPy arrays here. Using the
function above, I generated a benchmark for problem sizes ranging from 10 to
2,000, averaging computation times over 10 runs for each point. Here are the
results:</p>
<img alt="Results of the benchmark of QP solvers in Python" class="noborder align-center" src="https://scaron.info/images/qp-benchmark.png" style="width: 750px;" />
<p>The bottom line of this small comparison is that <strong>quadprog</strong>, which implements
the Goldfarb-Idnani dual algorithm, <strong>simply rocks</strong>. More generally,
active-set solvers (quadprog and qpOASES) perform best on these dense problems.
To see the benefit of symbolic solvers (CVXPY or Gurobi), one would have to use
sparse matrix representation, which I didn't do here.</p>
<p>You can try for yourself on your own machine, all scripts and solver wrapping
functions are in the <a class="reference external" href="https://github.com/stephane-caron/qpsolvers">qpsolvers</a>
repository.</p>
</div>
<script type='text/javascript'>if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
var mathjaxscript = document.createElement('script');
mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
mathjaxscript.type = 'text/javascript';
mathjaxscript.src = '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
mathjaxscript[(window.opera ? "innerHTML" : "text")] =
"MathJax.Hub.Config({" +
" config: ['MMLorHTML.js']," +
" TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'AMS' } }," +
" jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
" extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
" displayAlign: 'center'," +
" displayIndent: '0em'," +
" showMathMenu: true," +
" tex2jax: { " +
" inlineMath: [ ['\\\\(','\\\\)'] ], " +
" displayMath: [ ['$$','$$'] ]," +
" processEscapes: true," +
" preview: 'TeX'," +
" }, " +
" 'HTML-CSS': { " +
" styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#333 ! important'} }, linebreaks: { automatic: false, width: 'container' }" +
" }, " +
" 'TeX': { " +
" Macros: { " +
" defeq: \"{\\\\stackrel{\\\\mathrm{def}}{=}}\"," +
" Ld: \"{\\\\dot{L}}\"," +
" LdG: \"{\\\\dot{L}_G}\"," +
" bfA: \"{\\\\boldsymbol{A}}\"," +
" bfB: \"{\\\\boldsymbol{B}}\"," +
" bfC: \"{\\\\boldsymbol{C}}\"," +
" bfD: \"{\\\\boldsymbol{D}}\"," +
" bfE: \"{\\\\boldsymbol{E}}\"," +
" bfF: \"{\\\\boldsymbol{F}}\"," +
" bfG: \"{\\\\boldsymbol{G}}\"," +
" bfH: \"{\\\\boldsymbol{H}}\"," +
" bfI: \"{\\\\boldsymbol{I}}\"," +
" bfJ: \"{\\\\boldsymbol{J}}\"," +
" bfK: \"{\\\\boldsymbol{K}}\"," +
" bfL: \"{\\\\boldsymbol{L}}\"," +
" bfM: \"{\\\\boldsymbol{M}}\"," +
" bfN: \"{\\\\boldsymbol{N}}\"," +
" bfO: \"{\\\\boldsymbol{O}}\"," +
" bfP: \"{\\\\boldsymbol{P}}\"," +
" bfQ: \"{\\\\boldsymbol{Q}}\"," +
" bfR: \"{\\\\boldsymbol{R}}\"," +
" bfS: \"{\\\\boldsymbol{S}}\"," +
" bfT: \"{\\\\boldsymbol{T}}\"," +
" bfU: \"{\\\\boldsymbol{U}}\"," +
" bfV: \"{\\\\boldsymbol{V}}\"," +
" bfW: \"{\\\\boldsymbol{W}}\"," +
" bfX: \"{\\\\boldsymbol{X}}\"," +
" bfY: \"{\\\\boldsymbol{Y}}\"," +
" bfZ: \"{\\\\boldsymbol{Z}}\"," +
" bfa: \"{\\\\boldsymbol{a}}\"," +
" bfb: \"{\\\\boldsymbol{b}}\"," +
" bfc: \"{\\\\boldsymbol{c}}\"," +
" bfd: \"{\\\\boldsymbol{d}}\"," +
" bfe: \"{\\\\boldsymbol{e}}\"," +
" bff: \"{\\\\boldsymbol{f}}\"," +
" bfg: \"{\\\\boldsymbol{g}}\"," +
" bfh: \"{\\\\boldsymbol{h}}\"," +
" bfi: \"{\\\\boldsymbol{i}}\"," +
" bfj: \"{\\\\boldsymbol{j}}\"," +
" bfk: \"{\\\\boldsymbol{k}}\"," +
" bfl: \"{\\\\boldsymbol{l}}\"," +
" bfm: \"{\\\\boldsymbol{m}}\"," +
" bfn: \"{\\\\boldsymbol{n}}\"," +
" bfo: \"{\\\\boldsymbol{o}}\"," +
" bfp: \"{\\\\boldsymbol{p}}\"," +
" bfq: \"{\\\\boldsymbol{q}}\"," +
" bfr: \"{\\\\boldsymbol{r}}\"," +
" bfs: \"{\\\\boldsymbol{s}}\"," +
" bft: \"{\\\\boldsymbol{t}}\"," +
" bfu: \"{\\\\boldsymbol{u}}\"," +
" bfv: \"{\\\\boldsymbol{v}}\"," +
" bfw: \"{\\\\boldsymbol{w}}\"," +
" bfx: \"{\\\\boldsymbol{x}}\"," +
" bfy: \"{\\\\boldsymbol{y}}\"," +
" bfz: \"{\\\\boldsymbol{z}}\"," +
" bfalpha: \"{\\\\boldsymbol{\\\\alpha}}\"," +
" bfbeta: \"{\\\\boldsymbol{\\\\beta}}\"," +
" bfgamma: \"{\\\\boldsymbol{\\\\gamma}}\"," +
" bftau: \"{\\\\boldsymbol{\\\\tau}}\"," +
" bfomega: \"{\\\\boldsymbol{\\\\omega}}\"," +
" calA: \"{\\\\cal A}\"," +
" calB: \"{\\\\cal B}\"," +
" calC: \"{\\\\cal C}\"," +
" calD: \"{\\\\cal D}\"," +
" calE: \"{\\\\cal E}\"," +
" calF: \"{\\\\cal F}\"," +
" calG: \"{\\\\cal G}\"," +
" calH: \"{\\\\cal H}\"," +
" calI: \"{\\\\cal I}\"," +
" calJ: \"{\\\\cal J}\"," +
" calK: \"{\\\\cal K}\"," +
" calL: \"{\\\\cal L}\"," +
" calM: \"{\\\\cal M}\"," +
" calN: \"{\\\\cal N}\"," +
" calO: \"{\\\\cal O}\"," +
" calP: \"{\\\\cal P}\"," +
" calQ: \"{\\\\cal Q}\"," +
" calR: \"{\\\\cal R}\"," +
" calS: \"{\\\\cal S}\"," +
" calT: \"{\\\\cal T}\"," +
" calU: \"{\\\\cal U}\"," +
" calV: \"{\\\\cal V}\"," +
" calW: \"{\\\\cal W}\"," +
" calX: \"{\\\\cal X}\"," +
" calY: \"{\\\\cal Y}\"," +
" calZ: \"{\\\\cal Z}\"," +
" d: [\"{\\\\rm d}{#1}\", 1]," +
" dim: \"{\\\\rm dim}\"," +
" p: \"{\\\\boldsymbol{p}}\"," +
" pd: \"{\\\\dot{\\\\bfp}}\"," +
" pdd: \"{\\\\ddot{\\\\bfp}}\"," +
" q: \"{\\\\boldsymbol{q}}\"," +
" qd: \"{\\\\dot{\\\\bfq}}\"," +
" qdd: \"{\\\\ddot{\\\\bfq}}\"," +
" xd: \"{\\\\dot{x}}\"," +
" xdd: \"{\\\\ddot{x}}\"," +
" yd: \"{\\\\dot{y}}\"," +
" ydd: \"{\\\\ddot{y}}\"," +
" zd: \"{\\\\dot{z}}\"," +
" zdd: \"{\\\\ddot{z}}\"," +
" defeq: \"{\\\\stackrel{\\\\mathrm{def}}{\\\\ =\\\\ }}\"," +
" } " +
" } " +
"}); ";
(document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
</script>Contact modes2017-03-25T19:42:00+01:00Stéphane Carontag:scaron.info,2017-03-25:teaching/contact-modes.html<p>Contacts are temporary degrees of constraints (DOCs) used by mobile robots to
control their motions. They stem from the <em>non-penetrability condition</em>: if
<span class="math">\(d_{ij}\)</span> is the distance between the closest pair of points, one belonging to a
body <span class="math">\(i\)</span> and the other to a body <span class="math">\(j \neq i\)</span>, then
</p>
<div class="math">$$
d_{ij} \geq 0.
$$</div>
<p>
When <span class="math">\(d_{ij} > 0\)</span>, there is no contact and the two bodies may move freely. But
as soon as <span class="math">\(d_{ij} = 0\)</span>, contact appears and constraints the respective motions
of the two bodies.</p>
<h2>Definition from degrees of constraints</h2>
<p>In rigid body dynamics, a body has six degrees of freedom, three for its
translation and three for its orientation. Therefore, a contact can create
between one and six degrees of constraints, depending on the number and
arrangement of contact points between the contacting bodies.</p>
<blockquote>
<p><strong>Definition (<a href="http://doai.io/10.1177%2F0278364902021012003">Balkcom and
Trinkle</a>):</strong>
the <em>mode</em> of a contact is the set of degrees of constraints that it
introduces between the two contacting bodies.</p>
</blockquote>
<p>Common contact modes include:</p>
<ul>
<li><em>Broken</em>: when there is no contact (DOC: 0)</li>
<li><em>Sliding</em>: when there is a relative translation between contact surfaces,
accompanied or not by a rotation along the contact normal (DOC: 3 or 4)</li>
<li><em>Rolling</em>: when one body in contact is rotating around a line on the other
body, accompanied or not by a translation along that line (DOC: 2 or 3)</li>
<li><em>Fixed</em>: when the contact is fully constrained (DOC: 6)</li>
</ul>
<h2>Complementarity in contact modes</h2>
<p>Owing to the complementary relationship between motion and force vectors in
<a href="/teaching/screw-theory.html">screw theory</a>, each degree of constraint on
motions springs a degree of freedom on forces, and <em>vice versa</em>. For instance,</p>
<ul>
<li>A <em>broken</em> contact allows for free motions in the space <span class="math">\(d_{ij} \geq 0\)</span>
delimited by the non-penetration condition, and fully constraints the contact
wrench to <span class="math">\(\boldsymbol{0}\)</span>.</li>
<li>A <em>fixed</em> contact fully constraints motion vectors (velocities,
accelerations, etc.) to <span class="math">\(\boldsymbol{0}\)</span>, and allows for free forces in the
space <span class="math">\(\bfF \bff \leq \boldsymbol{0}\)</span> delimited by the <a href="/teaching/friction-model.html">friction
model</a>.</li>
</ul>
<p>More generally, contact modes can be seen as the states of the discrete system
of contact dynamics. Transitions between modes, known as <a href="/teaching/contact-stability.html">contact
switches</a>, occur when certain guard
conditions are crossed. For instance, when the contact force reaches the
boundary of its friction cone, or when the <a href="/teaching/zero-tilting-moment-point.html">center of
pressure</a> hits the boundary of the
contact area. Here is an example of a subgraph with transitions between contact
modes:</p>
<p><img src="/figures/contact-modes.png" class="center"></p>
<p>(To view full screen: right-click on the image, <em>View Image</em>.) The complete
automaton of the discrete system is symmetric and dense.</p>
<h2>To go further</h2>
<p>The article by <a href="http://doai.io/10.1177/0278364902021012003">(Balkcom and Trinkle,
2002)</a> introduced several
important ideas, including contact modes and the representation of contact
conditions by polyhedral convex sets.</p>
<script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
var mathjaxscript = document.createElement('script');
mathjaxscript.id = 'mathjaxscript_pelican_#%@#$@#';
mathjaxscript.type = 'text/javascript';
mathjaxscript.src = '//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML';
mathjaxscript[(window.opera ? "innerHTML" : "text")] =
"MathJax.Hub.Config({" +
" config: ['MMLorHTML.js']," +
" TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'AMS' } }," +
" jax: ['input/TeX','input/MathML','output/HTML-CSS']," +
" extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," +
" displayAlign: 'center'," +
" displayIndent: '0em'," +
" showMathMenu: true," +
" tex2jax: { " +
" inlineMath: [ ['\\\\(','\\\\)'] ], " +
" displayMath: [ ['$$','$$'] ]," +
" processEscapes: true," +
" preview: 'TeX'," +
" }, " +
" 'HTML-CSS': { " +
" styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: '#333 ! important'} }, linebreaks: { automatic: false, width: 'container' }" +
" }, " +
" 'TeX': { " +
" Macros: { " +
" defeq: \"{\\\\stackrel{\\\\mathrm{def}}{=}}\"," +
" Ld: \"{\\\\dot{L}}\"," +
" LdG: \"{\\\\dot{L}_G}\"," +
" bfA: \"{\\\\boldsymbol{A}}\"," +
" bfB: \"{\\\\boldsymbol{B}}\"," +
" bfC: \"{\\\\boldsymbol{C}}\"," +
" bfD: \"{\\\\boldsymbol{D}}\"," +
" bfE: \"{\\\\boldsymbol{E}}\"," +
" bfF: \"{\\\\boldsymbol{F}}\"," +
" bfG: \"{\\\\boldsymbol{G}}\"," +
" bfH: \"{\\\\boldsymbol{H}}\"," +
" bfI: \"{\\\\boldsymbol{I}}\"," +
" bfJ: \"{\\\\boldsymbol{J}}\"," +
" bfK: \"{\\\\boldsymbol{K}}\"," +
" bfL: \"{\\\\boldsymbol{L}}\"," +
" bfM: \"{\\\\boldsymbol{M}}\"," +
" bfN: \"{\\\\boldsymbol{N}}\"," +
" bfO: \"{\\\\boldsymbol{O}}\"," +
" bfP: \"{\\\\boldsymbol{P}}\"," +
" bfQ: \"{\\\\boldsymbol{Q}}\"," +
" bfR: \"{\\\\boldsymbol{R}}\"," +
" bfS: \"{\\\\boldsymbol{S}}\"," +
" bfT: \"{\\\\boldsymbol{T}}\"," +
" bfU: \"{\\\\boldsymbol{U}}\"," +
" bfV: \"{\\\\boldsymbol{V}}\"," +
" bfW: \"{\\\\boldsymbol{W}}\"," +
" bfX: \"{\\\\boldsymbol{X}}\"," +
" bfY: \"{\\\\boldsymbol{Y}}\"," +
" bfZ: \"{\\\\boldsymbol{Z}}\"," +
" bfa: \"{\\\\boldsymbol{a}}\"," +
" bfb: \"{\\\\boldsymbol{b}}\"," +
" bfc: \"{\\\\boldsymbol{c}}\"," +
" bfd: \"{\\\\boldsymbol{d}}\"," +
" bfe: \"{\\\\boldsymbol{e}}\"," +
" bff: \"{\\\\boldsymbol{f}}\"," +
" bfg: \"{\\\\boldsymbol{g}}\"," +
" bfh: \"{\\\\boldsymbol{h}}\"," +
" bfi: \"{\\\\boldsymbol{i}}\"," +
" bfj: \"{\\\\boldsymbol{j}}\"," +
" bfk: \"{\\\\boldsymbol{k}}\"," +
" bfl: \"{\\\\boldsymbol{l}}\"," +
" bfm: \"{\\\\boldsymbol{m}}\"," +
" bfn: \"{\\\\boldsymbol{n}}\"," +
" bfo: \"{\\\\boldsymbol{o}}\"," +
" bfp: \"{\\\\boldsymbol{p}}\"," +
" bfq: \"{\\\\boldsymbol{q}}\"," +
" bfr: \"{\\\\boldsymbol{r}}\"," +
" bfs: \"{\\\\boldsymbol{s}}\"," +
" bft: \"{\\\\boldsymbol{t}}\"," +
" bfu: \"{\\\\boldsymbol{u}}\"," +
" bfv: \"{\\\\boldsymbol{v}}\"," +
" bfw: \"{\\\\boldsymbol{w}}\"," +
" bfx: \"{\\\\boldsymbol{x}}\"," +
" bfy: \"{\\\\boldsymbol{y}}\"," +
" bfz: \"{\\\\boldsymbol{z}}\"," +
" bfalpha: \"{\\\\boldsymbol{\\\\alpha}}\"," +
" bfbeta: \"{\\\\boldsymbol{\\\\beta}}\"," +
" bfgamma: \"{\\\\boldsymbol{\\\\gamma}}\"," +
" bftau: \"{\\\\boldsymbol{\\\\tau}}\"," +
" bfomega: \"{\\\\boldsymbol{\\\\omega}}\"," +
" calA: \"{\\\\cal A}\"," +
" calB: \"{\\\\cal B}\"," +
" calC: \"{\\\\cal C}\"," +
" calD: \"{\\\\cal D}\"," +
" calE: \"{\\\\cal E}\"," +
" calF: \"{\\\\cal F}\"," +
" calG: \"{\\\\cal G}\"," +
" calH: \"{\\\\cal H}\"," +
" calI: \"{\\\\cal I}\"," +
" calJ: \"{\\\\cal J}\"," +
" calK: \"{\\\\cal K}\"," +
" calL: \"{\\\\cal L}\"," +
" calM: \"{\\\\cal M}\"," +
" calN: \"{\\\\cal N}\"," +
" calO: \"{\\\\cal O}\"," +
" calP: \"{\\\\cal P}\"," +
" calQ: \"{\\\\cal Q}\"," +
" calR: \"{\\\\cal R}\"," +
" calS: \"{\\\\cal S}\"," +
" calT: \"{\\\\cal T}\"," +
" calU: \"{\\\\cal U}\"," +
" calV: \"{\\\\cal V}\"," +
" calW: \"{\\\\cal W}\"," +
" calX: \"{\\\\cal X}\"," +
" calY: \"{\\\\cal Y}\"," +
" calZ: \"{\\\\cal Z}\"," +
" d: [\"{\\\\rm d}{#1}\", 1]," +
" dim: \"{\\\\rm dim}\"," +
" p: \"{\\\\boldsymbol{p}}\"," +
" pd: \"{\\\\dot{\\\\bfp}}\"," +
" pdd: \"{\\\\ddot{\\\\bfp}}\"," +
" q: \"{\\\\boldsymbol{q}}\"," +
" qd: \"{\\\\dot{\\\\bfq}}\"," +
" qdd: \"{\\\\ddot{\\\\bfq}}\"," +
" xd: \"{\\\\dot{x}}\"," +
" xdd: \"{\\\\ddot{x}}\"," +
" yd: \"{\\\\dot{y}}\"," +
" ydd: \"{\\\\ddot{y}}\"," +
" zd: \"{\\\\dot{z}}\"," +
" zdd: \"{\\\\ddot{z}}\"," +
" defeq: \"{\\\\stackrel{\\\\mathrm{def}}{\\\\ =\\\\ }}\"," +
" } " +
" } " +
"}); ";
(document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
</script>