KASKADE 7 development version
qpLinesearch.hh
Go to the documentation of this file.
1/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2/* */
3/* This file is part of the library KASKADE 7 */
4/* https://www.zib.de/research/projects/kaskade7-finite-element-toolbox */
5/* */
6/* Copyright (C) 2019-2020 Zuse Institute Berlin */
7/* */
8/* KASKADE 7 is distributed under the terms of the ZIB Academic License. */
9/* see $KASKADE/academic.txt */
10/* */
11/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
12
13#ifndef QPLINESEARCH_HH
14#define QPLINESEARCH_HH
15
16#include <memory>
17#include <tuple>
18
19#include "dune/istl/bvector.hh"
20
23
24namespace Kaskade
25{
43 template <class MatrixA, class MatrixB, class VectorX, class VectorB, class Real>
44 Real qpLinesearch(MatrixA const& A, MatrixB const& B, Real gamma,
45 VectorX const& c, VectorB const& b, VectorX const& dx)
46 {
47 // Capture the trivial and useless cases:
48 if (dx.two_norm2() == 0)
49 return 1;
50
51 if (c.two_norm2() == 0)
52 return 0;
53
54
55 // Select step size by minimizing J(t) = t^2/2 dx^T Adx + t c^T dx + gamma/2 |t Bdx - b|_+^2
56 // over t. This is a piecewise quadratic function in t. Switching points can be induced only by
57 // constraints i with (Bdx)_i>0 and b_i>0 (activation for growing t) or (Bdx)_i<0
58 // and b_i<0 (inactivation for growing t).
59
60 // J(t) = alpha t^2 + beta t + sum_j [eta_j (t-r_j)]_+^2.
61 // Gather all quadratic contributions into alpha and all linear terms into beta. First the objective.
62 VectorX Adx(dx.N());
63 A.mv(dx,Adx);
64 double alpha = dx*Adx / 2;
65 double beta = dx*c;
66
67 // TODO: RECONSIDER. EQUALITY MAY BE OK, IF UNIQUENESS AND CONVEXITY IS ENSURED BY ACTIVE CONSTRAINTS...
68 // if (alpha <= 0)
69 // {
70 // std::cout << "A = " << A << "\n";
71 // throw NonpositiveMatrixException("nonconvex direction encountered in QP linesearch: dx^T A dx = "
72 // +std::to_string(alpha),__FILE__,__LINE__);
73 // }
74 if (alpha <= 0)
75 {
76 std::cout << "Nonconvex direction encountered in QP linesearch: dx^T A dx = " +std::to_string(alpha) << "\n";
77 std::cout << "set steplength to 0.\n";
78 return 0;
79 }
80
81 // Compute the unconstrained minimizer satisfying 2 alpha t + beta = 0.
82 Real t = -beta/alpha/2;
83
84 // If gamma==0, the constraints play no role at all. Then we're done here.
85 // (Setting gamma to 0 can be used as a quick hack for improving performance when
86 // one knows for sure that the given direction dx does not interfere with the constraints.)
87 if (gamma == (Real)0)
88 return t;
89
90 // Now consider constraints and distinguish between those that can lead to switching
91 // and those that are always active or always inactive (for t>=0). The ones that remain
92 // inactive can be ignored altogether.
93 VectorB Bdx(b.N());
94 B.mv(dx,Bdx);
95
96 // For strict constraints (gamma = inf), we need not minimize after we hit the first
97 // constraint - this simplifies things (and we avoid numerical difficulties) in a
98 // special case implementation.
99 if (gamma == std::numeric_limits<Real>::infinity())
100 {
101 // For each constraint, limit the stepsize to be feasible, i.e. t B dx <= b.
102 for (int i=0; i<b.N(); ++i)
103 if (Bdx[i] > 0) // only consider constraints that might become (more) active
104 t = std::min(t,b[i]/Bdx[i]);
105
106 // If t<0, then either dx is no unconstrained descent direction, or we are already
107 // infeasible and would violate a constraint even more when moving on (so becoming feasible
108 // requires us to go backwards). This is definitely a bad situation.
109 if (t < 0)
110 throw InfeasibleProblemException("Infeasible initial starting point for linesearch (step "
111 + std::to_string(t) + ").",
112 __FILE__,__LINE__);
113 return t; // And here, we're done.
114 }
115
116 // Now comes the tedious case: We have to look through all the segments
117 // and find the minimizer.
118
119 std::vector<std::pair<Real,Real>> rEtas;
120
121 for (int i=0; i<b.N(); ++i)
122 if (Bdx[i]>=0 && b[i]<=0) // this constraint will remain active for all t>=0,
123 { // thus we may simply include it in the quadratic objective
124 alpha += gamma/2 * Bdx[i]*Bdx[i];
125 beta -= gamma/2 * 2*Bdx[i]*b[i];
126 }
127 else if (Bdx[i]*b[i]>0) // this constraint can switch
128 {
129 double r = b[i]/Bdx[i]; // intercept of t*dx where this constraint switches
130 double eta = Bdx[i]; // change of constraint along dx
131
132 rEtas.push_back(std::make_pair(r,eta));
133
134 if (b[i]<0) // This is already active at t=0 -> include its coefficients into the quadratic objective
135 { // We may skip the constant terms, though, as we don't compare objective values
136 alpha += gamma/2 * eta*eta;
137 beta -= gamma/2 * 2*eta*eta*r;
138 }
139 }
140
141 // Sort the constraints such that we can inspect one segment after the other
142 // Sort it in reverse order, such that we can easily pop the encountered switches off the back
143 std::sort(begin(rEtas),end(rEtas),FirstGreater());
144
145 // Now look for a minimizer along t>=0.
146 t = std::max(0.0,-beta / (2*alpha)); // minimizer of currently active quadratic objective on [0,oo[
147
148 if (t==0)
149 {
150 // std::cout << "*** no descent in PQP linesearch -> t = 0 (|dx|=" << dx.two_norm() << ")\n";
151 return 0;
152 }
153
154 while (!rEtas.empty())
155 {
156 if (t < rEtas.back().first) // The minimizer comes before the first switch and is therefore the
157 break; // global minimizer. We're done.
158
159 // We ran into a constraint switch before reaching the minimizer. This means that for the remaining part,
160 // the quadratic objective is changed.
161 auto [r,Bdx] = rEtas.back();
162 rEtas.pop_back();
163
164 if (Bdx > 0) // Constraint becomes active
165 { // -> add new quadratic contribution [Bdx*(t-r)]^2 to the objective
166 alpha += gamma/2 * Bdx*Bdx; // (as before, we can omit the constant term)
167 beta -= gamma/2 * 2*Bdx*Bdx*r;
168 }
169 else // Constraint becomes inactive
170 { // -> subtract quadratic contribution [Bdx*(t-r)]^2 that had been
171 alpha -= gamma/2 * Bdx*Bdx; // included in the objective up to here
172 beta += gamma/2 * 2*Bdx*Bdx*r;
173 if (alpha <= 0)
174 throw NonpositiveMatrixException("nonconvex direction encountered in QP linesearch",__FILE__,__LINE__);
175 }
176
177 t = std::max(r,-beta / (2*alpha)); // Continue linesearch from the constraint switching point.
178 }
179
180 return t;
181 }
182
195 template <class MatrixA, class MatrixB, class VectorX, class VectorB, class Real>
196 Real qpLinesearch(MatrixA const& A, MatrixB const& B, Real gamma,
197 VectorX const& c, VectorB const& b, VectorX const& x, VectorX const& dx)
198 {
199 // Shift problem such that it is centered around x.
200 VectorX c0 = c;
201 A.umv(x,c0);
202 VectorB b0 = b;
203 B.usmv(-1.0,x,b0);
204
205
206 Real t = 0;
207 try
208 {
209 t = qpLinesearch(A,B,gamma,c0,b0,dx);
210 }
211 catch (LinesearchException const& ex)
212 {
213 VectorB con(b.N()); // Bx-b
214 B.mv(x,con);
215 con -= b;
216 for (int i=0; i<b.N(); ++i)
217 con[i] = std::max(0.0,con[i][0]);
218
219 B.usmtv(gamma,con,c0); // Ax+c+gamma*B'*(Bx-b)_+, the derivative at x
220
221 if (c0.two_norm() > 1e3* (c.two_norm()+con.two_norm()) * std::numeric_limits<Real>::epsilon())
222 {
223 std::cout << "|grad| = " << c0.two_norm() << ", |c| = " << c.two_norm() << ", |Bx-b|_+ = " << con.two_norm() << "\n";
224 throw LinesearchException(ex.what() + std::string("Does not appear to be due to roundoff."),__FILE__,__LINE__);
225 }
226 }
227
228 return t;
229 }
230}
231
232#endif
To be raised if an iterative solver detects the problem to be infeasible (has no valid solution).
To be raised if a linesearch fails.
To be raised if the matrix is not positive definite.
Dune::FieldVector< T, n > max(Dune::FieldVector< T, n > x, Dune::FieldVector< T, n > const &y)
Componentwise maximum.
Definition: fixdune.hh:110
Dune::FieldVector< T, n > min(Dune::FieldVector< T, n > x, Dune::FieldVector< T, n > const &y)
Componentwise minimum.
Definition: fixdune.hh:122
Real qpLinesearch(MatrixA const &A, MatrixB const &B, Real gamma, VectorX const &c, VectorB const &b, VectorX const &dx)
Performs linesearch for penalized QPs.
Definition: qpLinesearch.hh:44
A comparator functor that supports sorting std::pair by their first component.
Definition: firstless.hh:34