-
Elias Pipping authoredElias Pipping authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ellipticenergy.hh 9.17 KiB
#ifndef ELLIPTIC_ENERGY_HH
#define ELLIPTIC_ENERGY_HH
#include <dune/common/fmatrix.hh>
#include <dune/common/fvector.hh>
#include <dune/common/stdstreams.hh>
#include <dune/fufem/interval.hh>
#include <dune/tnnmg/problem-classes/bisection.hh>
#include "mydirectionalconvexfunction.hh"
#include "localnonlinearity.hh"
#include "circularconvexfunction.hh"
namespace Dune {
template <int dim> class EllipticEnergy {
public:
typedef FieldVector<double, dim> SmallVector;
typedef FieldMatrix<double, dim, dim> SmallMatrix;
typedef LocalNonlinearity<dim> NonlinearityType;
EllipticEnergy(SmallMatrix const &A, SmallVector const &b,
shared_ptr<NonlinearityType const> phi, int ignore = dim)
: A(A), b(b), phi(phi), ignore(ignore) {}
double operator()(SmallVector const &v) const {
SmallVector y(0);
A.usmv(0.5, v, y); // 1/2 Av
y -= b; // 1/2 Av - b
return y * v + (*phi)(v); // <1/2 Av - b,v> + H(|v|)
}
void descentAtZero(SmallVector &ret) const {
SmallVector const zero(0);
// If there is a direction of descent, this is it
SmallVector d;
smoothGradient(zero, d);
d *= -1;
Interval<double> D;
phi->directionalSubDiff(zero, d, D);
double const nonlinearDecline = D[1];
double const smoothDecline = -(d * d);
double const combinedDecline = smoothDecline + nonlinearDecline;
if (combinedDecline < 0) {
ret = d;
} else {
ret = 0;
}
}
// returns false if the direction is tangential
void descentDirectionNew(SmallVector const &x, SmallVector &ret) const {
SmallVector pg;
upperGradient(x, pg);
SmallVector mg;
lowerGradient(x, mg);
double const pgx = pg * x;
double const mgx = mg * x;
if (pgx >= 0 && mgx >= 0) {
ret = pg;
ret *= -1;
} else if (pgx <= 0 && mgx <= 0) {
ret = mg;
ret *= -1;
} else {
assert(false);
}
}
bool descentDirection(SmallVector const &x, SmallVector &ret) const {
// Check the squared norm rather than each component because
// complementaryProjection() divides by it
if (x.two_norm2() == 0.0) {
descentAtZero(ret);
return true;
}
SmallVector pg;
upperGradient(x, pg);
SmallVector mg;
lowerGradient(x, mg);
double const pgx = pg * x;
double const mgx = mg * x;
if (pgx >= 0 && mgx >= 0) {
ret = pg;
ret *= -1;
return true;
} else if (pgx <= 0 && mgx <= 0) {
ret = mg;
ret *= -1;
return true;
} else {
// Includes the case that pg points in direction x and mg
// points in direction -x. The projection will then be zero.
SmallVector d;
smoothGradient(x, d);
complementaryProjection(d, x, ret);
ret *= -1;
return false;
}
}
SmallMatrix const &A;
SmallVector const &b;
shared_ptr<NonlinearityType const> const phi;
int const ignore; // Dimension that should be ignored; goes from 0
// to dim-1; the special value dim means that no
// dimension should be ignored
private:
// Gradient of the smooth part
void smoothGradient(SmallVector const &x, SmallVector &y) const {
A.mv(x, y); // y = Av
y -= b; // y = Av - b
if (ignore != dim)
y[ignore] = 0;
}
void upperGradient(SmallVector const &x, SmallVector &y) const {
phi->upperGradient(x, y);
SmallVector z;
smoothGradient(x, z);
y += z;
if (ignore != dim)
y[ignore] = 0;
}
void lowerGradient(SmallVector const &x, SmallVector &y) const {
phi->lowerGradient(x, y);
SmallVector z;
smoothGradient(x, z);
y += z;
if (ignore != dim)
y[ignore] = 0;
}
// y = (id - P)(d) where P is the projection onto the line t*x
void complementaryProjection(SmallVector const &d, SmallVector const &x,
SmallVector &y) const {
double const dx = d * x;
double const xx = x.two_norm2();
y = d;
y.axpy(-dx / xx, x);
}
};
template <class Functional>
void descentMinimisation(Functional const &J,
typename Functional::SmallVector &x,
typename Functional::SmallVector const &descDir,
Bisection const &bisection) {
typedef typename Functional::SmallVector SmallVector;
typedef typename Functional::NonlinearityType LocalNonlinearityType;
// {{{ Construct a restriction of J to the line x + t * descDir
/* We have
1/2 <A(u+xv),u+xv>-<b,u+xv> = 1/2 <Av,v> x^2 - <b-Au,v> x + <1/2 Au-b,u>
since A is symmetric.
*/
SmallVector tmp = J.b; // b
J.A.mmv(x, tmp); // b-Au
double const JRestb = tmp * descDir; // <b-Au,v>
J.A.mv(descDir, tmp); // Av
double const JRestA = tmp * descDir; // <Av,v>
MyDirectionalConvexFunction<LocalNonlinearityType> const JRest(
JRestA, JRestb, *J.phi, x, descDir);
// }}}
{ // Debug
Interval<double> D;
JRest.subDiff(0, D);
dverb
<< "## Directional derivative (as per subdifferential of restriction): "
<< D[1] << " (coordinates of the restriction)" << std::endl;
/*
It is possible that this differs quite a lot from the
directional derivative computed in the descentDirection()
method:
If phi is nonsmooth at x, so that the directional
derivatives jump, and |x| is computed to be too small or too
large globally or locally, the locally computed
subdifferential and the globally computed subdifferential
will no longer coincide!
The assertion D[1] <= 0 may thus fail.
*/
}
int count;
double const stepsize = bisection.minimize(JRest, 0.0, 0.0, count);
dverb << "Number of iterations in the bisection method: " << count
<< std::endl;
;
x.axpy(stepsize, descDir);
}
template <class Functional>
void tangentialMinimisation(Functional const &J,
typename Functional::SmallVector &x,
typename Functional::SmallVector const &descDir,
Bisection const &bisection) {
typedef typename Functional::SmallMatrix SmallMatrix;
typedef typename Functional::SmallVector SmallVector;
// We completely ignore the nonlinearity here -- when restricted
// to a circle, it just enters as a constant!
CircularConvexFunction<SmallMatrix, SmallVector> const JRest(J.A, J.b, x,
descDir);
int count;
double const stepsize = bisection.minimize(JRest, 0.0, 1.0, count);
dverb << "Number of iterations in the bisection method: " << count
<< std::endl;
;
// Since x is used in the computation of the rhs, do not write to it directly
SmallVector tmp;
JRest.cartesian(stepsize, tmp);
x = tmp;
}
template <class Functional>
void minimise(Functional const &J, typename Functional::SmallVector &x,
size_t steps, Bisection const &bisection) {
typedef typename Functional::SmallVector SmallVector;
for (size_t step = 0; step < steps; ++step) {
SmallVector descDir;
bool const linesearchp = J.descentDirection(x, descDir);
if (descDir == SmallVector(0.0))
return;
if (linesearchp) {
descentMinimisation(J, x, descDir, bisection);
} else {
Bisection slowBisection(bisection);
slowBisection.setFastQuadratic(false);
tangentialMinimisation(J, x, descDir, slowBisection);
}
}
}
// NOTE: only works for the 2D case
template <class Functional>
void minimise2(Functional const &J, typename Functional::SmallVector &x,
size_t steps, Bisection const &bisection) {
typedef typename Functional::SmallVector SmallVector;
minimisationInitialiser(J, bisection, x);
{ // Make a single step where we already know we're not differentiable
SmallVector descDir;
if (x == SmallVector(0))
J.descentAtZero(descDir);
else
J.descentDirectionNew(x, descDir);
descentMinimisation(J, x, descDir, bisection);
}
// From here on, we're in a C1 region and stay there.
for (size_t i = 1; i < steps; ++i) {
SmallVector descDir;
J.descentDirectionNew(x, descDir);
descentMinimisation(J, x, descDir, bisection);
}
}
template <class Functional>
void minimisationInitialiser(Functional const &J, Bisection const &bisection,
typename Functional::SmallVector &startingPoint) {
typedef typename Functional::SmallVector SmallVector;
std::vector<double> const kinks = { 5, 10,
15 }; // FIXME: We happen to know these
SmallVector x_old(0);
double Jx_old = J(x_old);
for (auto &kink : kinks) {
SmallVector x1 = { kink, 0 }; // Random vector that has the right norm
SmallVector const descDir1 = { x1[1], -x1[0] };
tangentialMinimisation(J, x1, descDir1, bisection);
double const Jx1 = J(x1);
SmallVector x2 = { -x1[0], -x1[1] };
SmallVector const descDir2 = { x2[1], -x2[0] };
tangentialMinimisation(J, x2, descDir2, bisection);
double const Jx2 = J(x2);
double const Jx = (Jx2 < Jx1 ? Jx2 : Jx1);
SmallVector const x = (Jx2 < Jx1 ? x2 : x1);
if (Jx >= Jx_old) {
startingPoint = x_old;
return;
}
Jx_old = Jx;
x_old = x;
}
startingPoint = x_old;
}
}
#endif