dynare/dynare++/src/nlsolve.cc

278 lines
7.5 KiB
C++

/*
* Copyright © 2006 Ondra Kamenik
* Copyright © 2019 Dynare Team
*
* This file is part of Dynare.
*
* Dynare is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Dynare is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Dynare. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nlsolve.hh"
#include "dynare_exception.hh"
#include <sstream>
#include <iomanip>
using namespace ogu;
double GoldenSectionSearch::golden = (3.-std::sqrt(5.))/2;
double
GoldenSectionSearch::search(OneDFunction &f, double x1, double x2)
{
double b;
if (init_bracket(f, x1, x2, b))
{
double fb = f.eval(b);
double f1 = f.eval(x1);
f.eval(x2);
double dx;
do
{
double w = (b-x1)/(x2-x1);
dx = std::abs((1-2*w)*(x2-x1));
double x;
if (b-x1 > x2-b)
x = b - dx;
else
x = b + dx;
double fx = f.eval(x);
if (!std::isfinite(fx))
return x1;
if (b-x1 > x2-b)
{
// x is on the left from b
if (f1 > fx && fx < fb)
{
// pickup bracket [f1,fx,fb]
x2 = b;
fb = fx;
b = x;
}
else
{
// pickup bracket [fx,fb,fx2]
f1 = fx;
x1 = x;
}
}
else
{
// x is on the right from b
if (f1 > fb && fb < fx)
// pickup bracket [f1,fb,fx]
x2 = x;
else
{
// pickup bracket [fb,fx,f2]
f1 = fb;
x1 = b;
fb = fx;
b = x;
}
}
}
while (dx > tol);
}
return b;
}
bool
GoldenSectionSearch::init_bracket(OneDFunction &f, double x1, double &x2, double &b)
{
double f1 = f.eval(x1);
if (!std::isfinite(f1))
throw DynareException(__FILE__, __LINE__,
"Safer point not finite in GoldenSectionSearch::init_bracket");
int cnt = 0;
bool bracket_found = false;
do
{
bool finite_found = search_for_finite(f, x1, x2, b);
if (!finite_found)
{
b = x1;
return false;
}
double f2 = f.eval(x2);
double fb = f.eval(b);
double bsym = 2*x2 - b;
double fbsym = f.eval(bsym);
// now we know that f1, f2, and fb are finite
if (std::isfinite(fbsym))
{
/* we have four numbers f1, fb, f2, fbsym, we test for the following
combinations to find the bracket: [f1,f2,fbsym], [f1,fb,fbsym] and
[f1,fb,fbsym] */
if (f1 > f2 && f2 < fbsym)
{
bracket_found = true;
b = x2;
x2 = bsym;
}
else if (f1 > fb && fb < fbsym)
{
bracket_found = true;
x2 = bsym;
}
else if (f1 > fb && fb < f2)
bracket_found = true;
else
{
double newx2 = b;
// choose the smallest value in case we end
if (f1 > fbsym)
{
/* the smallest value is on the other end, we do not want to
continue */
b = bsym;
return false;
}
else
b = x1;
// move x2 to b in case we continue
x2 = newx2;
}
}
else
{
/* we have only three numbers, we test for the bracket, and if not
found, we set b as potential result and shorten x2 as potential
init value for next cycle */
if (f1 > fb && fb < f2)
bracket_found = true;
else
{
double newx2 = b;
// choose the smaller value in case we end
if (f1 > f2)
b = x2;
else
b = x1;
// move x2 to b in case we continue
x2 = newx2;
}
}
cnt++;
}
while (!bracket_found && cnt < 5);
return bracket_found;
}
/* This moves x2 toward to x1 until the function at x2 is finite and b as a
golden section between x1 and x2 yields also finite f. */
bool
GoldenSectionSearch::search_for_finite(OneDFunction &f, double x1, double &x2, double &b)
{
int cnt = 0;
bool found = false;
do
{
double f2 = f.eval(x2);
b = (1-golden)*x1 + golden*x2;
double fb = f.eval(b);
found = std::isfinite(f2) && std::isfinite(fb);
if (!found)
x2 = b;
cnt++;
}
while (!found && cnt < 5);
return found;
}
void
VectorFunction::check_for_eval(const ConstVector &in, Vector &out) const
{
if (inDim() != in.length() || outDim() != out.length())
throw DynareException(__FILE__, __LINE__,
"Wrong dimensions in VectorFunction::check_for_eval");
}
double
NLSolver::eval(double lambda)
{
Vector xx(const_cast<const Vector &>(x));
xx.add(1-lambda, xcauchy);
xx.add(lambda, xnewton);
Vector ff(func.outDim());
func.eval(xx, ff);
return ff.dot(ff);
}
bool
NLSolver::solve(Vector &xx, int &iter)
{
JournalRecord rec(journal);
rec << "Iter lambda residual" << endrec;
JournalRecord rec1(journal);
rec1 << u8"───────────────────────────" << endrec;
x = const_cast<const Vector &>(xx);
iter = 0;
// setup fx
Vector fx(func.outDim());
func.eval(x, fx);
if (!fx.isFinite())
throw DynareException(__FILE__, __LINE__,
"Initial guess does not yield finite residual in NLSolver::solve");
bool converged = fx.getMax() < tol;
JournalRecord rec2(journal);
auto format_double = [](double v)
{
std::ostringstream buf;
buf << std::setw(11) << v;
return buf.str();
};
rec2 << iter << " N/A " << format_double(fx.getMax()) << endrec;
while (!converged && iter < max_iter)
{
// setup Jacobian
jacob.eval(x);
// calculate cauchy step
Vector g(func.inDim());
g.zeros();
ConstTwoDMatrix(jacob).multaVecTrans(g, fx);
Vector Jg(func.inDim());
Jg.zeros();
ConstTwoDMatrix(jacob).multaVec(Jg, g);
double m = -g.dot(g)/Jg.dot(Jg);
xcauchy = const_cast<const Vector &>(g);
xcauchy.mult(m);
// calculate newton step
xnewton = const_cast<const Vector &>(fx);
ConstTwoDMatrix(jacob).multInvLeft(xnewton);
xnewton.mult(-1);
// line search
double lambda = GoldenSectionSearch::search(*this, 0, 1);
x.add(1-lambda, xcauchy);
x.add(lambda, xnewton);
// evaluate func
func.eval(x, fx);
converged = fx.getMax() < tol;
// iter
iter++;
JournalRecord rec3(journal);
rec3 << iter << " " << lambda << " " << format_double(fx.getMax()) << endrec;
}
xx = const_cast<const Vector &>(x);
return converged;
}