Code owners
Assign users and groups as approvers for specific file changes. Learn more.
newton.hh 27.10 KiB
// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:
#ifndef PACXX_PROJECTSEMINAR_2019_SRC_NEWTON_HH
#define PACXX_PROJECTSEMINAR_2019_SRC_NEWTON_HH
// Shameless modified copy of PDELab::Newton
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <memory>
#include <dune/common/exceptions.hh>
#include <dune/common/ios_state.hh>
#include <dune/common/parametertree.hh>
#include <dune/common/timer.hh>
#include <dune/istl/operators.hh>
#include <dune/istl/solver.hh>
#include "linearsolver.hh"
namespace PPS
{
// Exception classes used in NewtonSolver
class NewtonError : public Dune::Exception {};
class NewtonDefectError : public NewtonError {};
class NewtonLinearSolverError : public NewtonError {};
class NewtonLineSearchError : public NewtonError {};
class NewtonNotConverged : public NewtonError {};
// Status information of Newton's method
struct NewtonResult : Dune::InverseOperatorResult
{
double first_defect; // the first defect
double defect; // the final defect
double assembler_time; // Cumulative time for matrix assembly
double linear_solver_time; // Cumulative time for linear solver
int linear_solver_iterations; // Total number of linear iterations
NewtonResult() :
first_defect(0.0), defect(0.0), assembler_time(0.0), linear_solver_time(0.0),
linear_solver_iterations(0) {}
};
template<class GOS, class TrlV, class TstV>
class NewtonBase
{
typedef GOS GridOperator;
typedef TrlV TrialVector;
typedef TstV TestVector;
typedef typename TestVector::field_type RFType;
using Operator = Dune::LinearOperator<TrlV, TstV>;
public:
// export result type
typedef NewtonResult Result;
void setVerbosityLevel(unsigned int verbosity_level)
{
verbosity_level_ = verbosity_level;
}
//! Set whether the jacobian matrix should be kept across calls to apply().
void setKeepMatrix(bool b)
{
keep_matrix_ = b;
}
//! Return whether the jacobian matrix is kept across calls to apply().
bool keepMatrix() const
{
return keep_matrix_;
}
//! Discard the stored Jacobian matrix.
void discardMatrix()
{
if(op_)
op_.reset();
}
protected:
const GridOperator& gridoperator_;
TrialVector *u_;
std::shared_ptr<TrialVector> z_;
std::shared_ptr<TestVector> r_;
std::shared_ptr<Operator> op_;
Result res_;
unsigned int verbosity_level_;
RFType prev_defect_;
RFType linear_reduction_;
bool reassembled_;
RFType reduction_;
RFType abs_limit_;
bool keep_matrix_;
Dune::ParameterTree operator_params_;
NewtonBase(const GridOperator& go, TrialVector& u)
: gridoperator_(go)
, u_(&u)
, verbosity_level_(1)
, keep_matrix_(true)
{ }
NewtonBase(const GridOperator& go)
: gridoperator_(go)
, u_(0)
, verbosity_level_(1)
, keep_matrix_(true)
{ }
virtual ~NewtonBase() { }
virtual bool terminate() = 0;
virtual void prepare_step(Operator& op, TestVector& r) = 0;
virtual void line_search(TrialVector& z, TestVector& r) = 0;
virtual void defect(TestVector& r) = 0;
}; // end class NewtonBase
template<class GOS, class S, class TrlV, class TstV>
class NewtonSolver : public virtual NewtonBase<GOS,TrlV,TstV>
{
typedef S Solver;
typedef GOS GridOperator;
typedef TrlV TrialVector;
typedef TstV TestVector;
typedef typename TestVector::field_type RFType;
using Operator = Dune::LinearOperator<TrlV, TstV>;
public:
typedef NewtonResult Result;
NewtonSolver(const GridOperator& go, TrialVector& u_, Solver& solver)
: NewtonBase<GOS,TrlV,TstV>(go,u_)
, solver_(solver)
, result_valid_(false)
{}
NewtonSolver(const GridOperator& go, Solver& solver)
: NewtonBase<GOS,TrlV,TstV>(go)
, solver_(solver)
, result_valid_(false)
{}
void apply();
void apply(TrialVector& u_);
const Result& result() const
{
if (!result_valid_)
DUNE_THROW(NewtonError,
"NewtonSolver::result() called before NewtonSolver::solve()");
return this->res_;
}
protected:
virtual void defect(TestVector& r) override
{
r = 0.0;
this->gridoperator_.residual(*this->u_, r);
this->res_.defect = this->solver_.norm(r);
if (!std::isfinite(this->res_.defect))
DUNE_THROW(NewtonDefectError,
"NewtonSolver::defect(): Non-linear defect is NaN or Inf");
}
private:
void linearSolve(Operator& op, TrialVector& z, TestVector& r) const
{
if (this->verbosity_level_ >= 4)
std::cout << " Solving linear system..." << std::endl;
z = 0.0;
// If possible tell solver backend to reuse linear system when we did not reassemble.
assert(this->reassembled_);
this->solver_.apply(op, z, r, this->linear_reduction_);
Dune::ios_base_all_saver restorer(std::cout); // store old ios flags
if (!this->solver_.result().converged)
DUNE_THROW(NewtonLinearSolverError,
"NewtonSolver::linearSolve(): Linear solver did not converge "
"in " << this->solver_.result().iterations << " iterations");
if (this->verbosity_level_ >= 4)
std::cout << " linear solver iterations: "
<< std::setw(12) << solver_.result().iterations << std::endl
<< " linear defect reduction: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< solver_.result().reduction << std::endl;
}
Solver& solver_;
bool result_valid_;
}; // end class NewtonSolver
template<class GOS, class S, class TrlV, class TstV>
void NewtonSolver<GOS,S,TrlV,TstV>::apply(TrialVector& u)
{
this->u_ = &u;
apply();
}
template<class GOS, class S, class TrlV, class TstV>
void NewtonSolver<GOS,S,TrlV,TstV>::apply()
{
this->res_.iterations = 0;
this->res_.converged = false;
this->res_.reduction = 1.0;
this->res_.conv_rate = 1.0;
this->res_.elapsed = 0.0;
this->res_.assembler_time = 0.0;
this->res_.linear_solver_time = 0.0;
this->res_.linear_solver_iterations = 0;
result_valid_ = true;
Dune::Timer timer;
try
{
if(!this->r_) {
// std::cout << "=== Setting up residual vector ..." << std::endl;
this->r_ = std::make_shared<TstV>(this->gridoperator_.rangeSize());
}
// residual calculation in member function "defect":
//--------------------------------------------------
// - set residual vector to zero
// - calculate new residual
// - store norm of residual in "this->res_.defect"
this->defect(*this->r_);
this->res_.first_defect = this->res_.defect;
this->prev_defect_ = this->res_.defect;
if (this->verbosity_level_ >= 2)
{
// store old ios flags
Dune::ios_base_all_saver restorer(std::cout);
std::cout << " Initial defect: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->res_.defect << std::endl;
}
if(!this->op_) {
// std::cout << "==== Setting up jacobian matrix ... " << std::endl;
this->op_ = makeOperator<TrlV, TstV>(&this->gridoperator_,
this->operator_params_);
}
if(!this->z_) {
// std::cout << "==== Setting up correction vector ... " << std::endl;
this->z_ = std::make_shared<TrlV>(this->gridoperator_.domainSize());
}
while (!this->terminate())
{
if (this->verbosity_level_ >= 3)
std::cout << " Newton iteration " << this->res_.iterations
<< " --------------------------------" << std::endl;
Dune::Timer assembler_timer;
try
{
// jacobian calculation in member function "prepare_step"
//-------------------------------------------------------
// - if above reassemble threshold
// - set jacobian to zero
// - calculate new jacobian
// - set linear reduction
this->prepare_step(*this->op_,*this->r_);
}
catch (...)
{
this->res_.assembler_time += assembler_timer.elapsed();
throw;
}
double assembler_time = assembler_timer.elapsed();
this->res_.assembler_time += assembler_time;
if (this->verbosity_level_ >= 3)
std::cout << " matrix assembly time: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< assembler_time << std::endl;
Dune::Timer linear_solver_timer;
try
{
// solution of linear system in member function "linearSolve"
//-----------------------------------------------------------
// - set initial guess for correction z to zero
// - call linear solver
this->linearSolve(*this->op_, *this->z_, *this->r_);
}
catch (...)
{
this->res_.linear_solver_time += linear_solver_timer.elapsed();
this->res_.linear_solver_iterations += this->solver_.result().iterations;
throw;
}
double linear_solver_time = linear_solver_timer.elapsed();
this->res_.linear_solver_time += linear_solver_time;
this->res_.linear_solver_iterations += this->solver_.result().iterations;
try
{
// line search with correction z
// the undamped version is also integrated in here
this->line_search(*this->z_, *this->r_);
}
catch (NewtonLineSearchError&)
{
if (this->reassembled_)
throw;
if (this->verbosity_level_ >= 3)
std::cout << " line search failed - trying again with reassembled matrix" << std::endl;
continue;
}
this->res_.reduction = this->res_.defect/this->res_.first_defect;
this->res_.iterations++;
this->res_.conv_rate = std::pow(this->res_.reduction, 1.0/this->res_.iterations);
// store old ios flags
Dune::ios_base_all_saver restorer(std::cout);
if (this->verbosity_level_ >= 3)
std::cout << " linear solver time: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< linear_solver_time << std::endl
<< " defect reduction (this iteration):"
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->res_.defect/this->prev_defect_ << std::endl
<< " defect reduction (total): "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->res_.reduction << std::endl
<< " new defect: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->res_.defect << std::endl;
if (this->verbosity_level_ == 2)
std::cout << " Newton iteration " << std::setw(2) << this->res_.iterations
<< ". New defect: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->res_.defect
<< ". Reduction (this): "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->res_.defect/this->prev_defect_
<< ". Reduction (total): "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->res_.reduction << std::endl;
} // end while
} // end try
catch(...)
{
this->res_.elapsed = timer.elapsed();
throw;
}
this->res_.elapsed = timer.elapsed();
Dune::ios_base_all_saver restorer(std::cout); // store old ios flags
if (this->verbosity_level_ == 1)
std::cout << " Newton converged after " << std::setw(2) << this->res_.iterations
<< " iterations. Reduction: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->res_.reduction
<< " (" << std::setprecision(4) << this->res_.elapsed << "s)"
<< std::endl;
if(!this->keep_matrix_)
this->op_.reset();
} // end apply
template<class GOS, class TrlV, class TstV>
class NewtonTerminate : public virtual NewtonBase<GOS,TrlV,TstV>
{
typedef GOS GridOperator;
typedef TrlV TrialVector;
typedef typename TstV::field_type RFType;
public:
NewtonTerminate(const GridOperator& go, TrialVector& u_)
: NewtonBase<GOS,TrlV,TstV>(go,u_)
, maxit_(40)
, force_iteration_(false)
{
this->reduction_ = 1e-8;
this->abs_limit_ = 1e-12;
}
NewtonTerminate(const GridOperator& go)
: NewtonBase<GOS,TrlV,TstV>(go)
, maxit_(40)
, force_iteration_(false)
{
this->reduction_ = 1e-8;
this->abs_limit_ = 1e-12;
}
void setReduction(RFType reduction)
{
this->reduction_ = reduction;
}
void setMaxIterations(unsigned int maxit)
{
maxit_ = maxit;
}
void setForceIteration(bool force_iteration)
{
force_iteration_ = force_iteration;
}
void setAbsoluteLimit(RFType abs_limit_)
{
this->abs_limit_ = abs_limit_;
}
void setOperatorParams(Dune::ParameterTree operator_params)
{
this->operator_params_ = std::move(operator_params);
}
virtual bool terminate() override
{
if (force_iteration_ && this->res_.iterations == 0)
return false;
this->res_.converged = this->res_.defect < this->abs_limit_
|| this->res_.defect < this->res_.first_defect * this->reduction_;
if (this->res_.iterations >= int(maxit_) && !this->res_.converged)
DUNE_THROW(NewtonNotConverged,
"NewtonTerminate::terminate(): Maximum iteration count reached");
return this->res_.converged;
}
private:
unsigned int maxit_;
bool force_iteration_;
}; // end class NewtonTerminate
template<class GOS, class TrlV, class TstV>
class NewtonPrepareStep : public virtual NewtonBase<GOS,TrlV,TstV>
{
typedef GOS GridOperator;
typedef TrlV TrialVector;
typedef typename TstV::field_type RFType;
using Operator = Dune::LinearOperator<TrlV, TstV>;
public:
NewtonPrepareStep(const GridOperator& go, TrialVector& u_)
: NewtonBase<GOS,TrlV,TstV>(go,u_)
, min_linear_reduction_(1e-3)
, fixed_linear_reduction_(0.0)
, reassemble_threshold_(0.0)
{}
NewtonPrepareStep(const GridOperator& go)
: NewtonBase<GOS,TrlV,TstV>(go)
, min_linear_reduction_(1e-3)
, fixed_linear_reduction_(0.0)
, reassemble_threshold_(0.0)
{}
/**\brief set the minimal reduction in the linear solver
\note with min_linear_reduction > 0, the linear reduction will be
determined as mininum of the min_linear_reduction and the
linear_reduction needed to achieve second order
Newton convergence. */
void setMinLinearReduction(RFType min_linear_reduction)
{
min_linear_reduction_ = min_linear_reduction;
}
/**\brief set a fixed reduction in the linear solver (overwrites setMinLinearReduction)
\note with fixed_linear_reduction > 0, the linear reduction
rate will always be fixed to min_linear_reduction. */
void setFixedLinearReduction(bool fixed_linear_reduction)
{
fixed_linear_reduction_ = fixed_linear_reduction;
}
/**\brief set a threshold, when the linear operator is reassembled
We allow to keep the linear operator over several newton
iterations. If the reduction in the newton drops below a
given threshold the linear operator is reassembled to ensure
convergence.
*/
void setReassembleThreshold(RFType reassemble_threshold)
{
reassemble_threshold_ = reassemble_threshold;
}
virtual void prepare_step(Operator& op, TstV& ) override
{
this->reassembled_ = false;
if (this->res_.defect/this->prev_defect_ > reassemble_threshold_)
{
if (this->verbosity_level_ >= 3)
std::cout << " Reassembling matrix..." << std::endl;
dynamic_cast<LinearizedOperatorMixin<TrlV>&>(*this->op_).
linearizeAt(*this->u_);
this->reassembled_ = true;
}
if (fixed_linear_reduction_ == true)
this->linear_reduction_ = min_linear_reduction_;
else {
// determine maximum defect, where Newton is converged.
RFType stop_defect =
std::max(this->res_.first_defect * this->reduction_,
this->abs_limit_);
/*
To achieve second order convergence of newton
we need a linear reduction of at least
current_defect^2/prev_defect^2.
For the last newton step a linear reduction of
1/10*end_defect/current_defect
is sufficient for convergence.
*/
if ( stop_defect/(10*this->res_.defect) >
this->res_.defect*this->res_.defect/(this->prev_defect_*this->prev_defect_) )
this->linear_reduction_ =
stop_defect/(10*this->res_.defect);
else
this->linear_reduction_ =
std::min(min_linear_reduction_,this->res_.defect*this->res_.defect/(this->prev_defect_*this->prev_defect_));
}
this->prev_defect_ = this->res_.defect;
Dune::ios_base_all_saver restorer(std::cout); // store old ios flags
if (this->verbosity_level_ >= 3)
std::cout << " requested linear reduction: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< this->linear_reduction_ << std::endl;
}
private:
RFType min_linear_reduction_;
bool fixed_linear_reduction_;
RFType reassemble_threshold_;
}; // end class NewtonPrepareStep
template<class GOS, class TrlV, class TstV>
class NewtonLineSearch : public virtual NewtonBase<GOS,TrlV,TstV>
{
typedef GOS GridOperator;
typedef TrlV TrialVector;
typedef TstV TestVector;
typedef typename TestVector::field_type RFType;
public:
enum Strategy {
/** \brief don't do any line search or damping */
noLineSearch,
/** \brief perform a linear search for the optimal damping parameter with multiples of damping
the strategy was described in <a href="http://dx.doi.org/10.1007/BF01406516">[Hackbusch and Reusken, 1989]</a> */
hackbuschReusken,
/** \brief same as hackbuschReusken, but doesn't fail if the best update is still not good enough */
hackbuschReuskenAcceptBest };
NewtonLineSearch(const GridOperator& go, TrialVector& u_)
: NewtonBase<GOS,TrlV,TstV>(go,u_)
, strategy_(hackbuschReusken)
, maxit_(10)
, damping_factor_(0.5)
{}
NewtonLineSearch(const GridOperator& go)
: NewtonBase<GOS,TrlV,TstV>(go)
, strategy_(hackbuschReusken)
, maxit_(10)
, damping_factor_(0.5)
{}
void setLineSearchStrategy(Strategy strategy)
{
strategy_ = strategy;
}
void setLineSearchStrategy(std::string strategy)
{
strategy_ = strategyFromName(strategy);
}
void setLineSearchMaxIterations(unsigned int maxit)
{
maxit_ = maxit;
}
void setLineSearchDampingFactor(RFType damping_factor)
{
damping_factor_ = damping_factor;
}
virtual void line_search(TrialVector& z, TestVector& r) override
{
if ((strategy_ == noLineSearch) || (this->res_.defect < this->abs_limit_))
{
this->u_->axpy(-1.0, z);
this->defect(r);
return;
}
if (this->verbosity_level_ >= 4)
std::cout << " Performing line search..." << std::endl;
RFType lambda = 1.0;
RFType best_lambda = 0.0;
RFType best_defect = this->res_.defect;
TrialVector prev_u(*this->u_);
unsigned int i = 0;
Dune::ios_base_all_saver restorer(std::cout); // store old ios flags
while (1)
{
if (this->verbosity_level_ >= 4)
std::cout << " trying line search damping factor: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< lambda
<< std::endl;
this->u_->axpy(-lambda, z);
try {
this->defect(r);
}
catch (NewtonDefectError&)
{
if (this->verbosity_level_ >= 4)
std::cout << " NaNs detected" << std::endl;
} // ignore NaNs and try again with lower lambda
if (this->res_.defect <= (1.0 - lambda/4) * this->prev_defect_)
{
if (this->verbosity_level_ >= 4)
std::cout << " line search converged" << std::endl;
break;
}
if (this->res_.defect < best_defect)
{
best_defect = this->res_.defect;
best_lambda = lambda;
}
if (++i >= maxit_)
{
if (this->verbosity_level_ >= 4)
std::cout << " max line search iterations exceeded" << std::endl;
switch (strategy_)
{
case hackbuschReusken:
*this->u_ = prev_u;
this->defect(r);
DUNE_THROW(NewtonLineSearchError,
"NewtonLineSearch::line_search(): line search failed, "
"max iteration count reached, "
"defect did not improve enough");
case hackbuschReuskenAcceptBest:
if (best_lambda == 0.0)
{
*this->u_ = prev_u;
this->defect(r);
DUNE_THROW(NewtonLineSearchError,
"NewtonLineSearch::line_search(): line search failed, "
"max iteration count reached, "
"defect did not improve in any of the iterations");
}
if (best_lambda != lambda)
{
*this->u_ = prev_u;
this->u_->axpy(-best_lambda, z);
this->defect(r);
}
break;
case noLineSearch:
break;
}
break;
}
lambda *= damping_factor_;
*this->u_ = prev_u;
}
if (this->verbosity_level_ >= 4)
std::cout << " line search damping factor: "
<< std::setw(12) << std::setprecision(4) << std::scientific
<< lambda << std::endl;
} // end line_search
protected:
/** helper function to get the different strategies from their name */
Strategy strategyFromName(const std::string & s) {
if (s == "noLineSearch")
return noLineSearch;
if (s == "hackbuschReusken")
return hackbuschReusken;
if (s == "hackbuschReuskenAcceptBest")
return hackbuschReuskenAcceptBest;
DUNE_THROW(Dune::Exception, "unknown line search strategy" << s);
}
private:
Strategy strategy_;
unsigned int maxit_;
RFType damping_factor_;
}; // end class NewtonLineSearch
template<class GOS, class S, class TrlV, class TstV = TrlV>
class Newton : public NewtonSolver<GOS,S,TrlV,TstV>
, public NewtonTerminate<GOS,TrlV,TstV>
, public NewtonLineSearch<GOS,TrlV,TstV>
, public NewtonPrepareStep<GOS,TrlV,TstV>
{
typedef GOS GridOperator;
typedef S Solver;
typedef TrlV TrialVector;
public:
Newton(const GridOperator& go, TrialVector& u_, Solver& solver_)
: NewtonBase<GOS,TrlV,TstV>(go,u_)
, NewtonSolver<GOS,S,TrlV,TstV>(go,u_,solver_)
, NewtonTerminate<GOS,TrlV,TstV>(go,u_)
, NewtonLineSearch<GOS,TrlV,TstV>(go,u_)
, NewtonPrepareStep<GOS,TrlV,TstV>(go,u_)
{}
Newton(const GridOperator& go, Solver& solver_)
: NewtonBase<GOS,TrlV,TstV>(go)
, NewtonSolver<GOS,S,TrlV,TstV>(go,solver_)
, NewtonTerminate<GOS,TrlV,TstV>(go)
, NewtonLineSearch<GOS,TrlV,TstV>(go)
, NewtonPrepareStep<GOS,TrlV,TstV>(go)
{}
//! interpret a parameter tree as a set of options for the newton solver
/**
example configuration:
\code
[NewtonParameters]
ReassembleThreshold = 0.1
LineSearchMaxIterations = 10
MaxIterations = 7
AbsoluteLimit = 1e-6
Reduction = 1e-4
MinLinearReduction = 1e-3
LineSearchDamping = 0.9
\endcode
and invocation in the code:
\code
newton.setParameters(param.sub("NewtonParameters"));
\endcode
*/
void setParameters(const Dune::ParameterTree & param)
{
typedef typename TstV::field_type RFType;
if (param.hasKey("VerbosityLevel"))
this->setVerbosityLevel(
param.get<unsigned int>("VerbosityLevel"));
if (param.hasKey("Reduction"))
this->setReduction(
param.get<RFType>("Reduction"));
if (param.hasKey("MaxIterations"))
this->setMaxIterations(
param.get<unsigned int>("MaxIterations"));
if (param.hasKey("ForceIteration"))
this->setForceIteration(
param.get<bool>("ForceIteration"));
if (param.hasKey("AbsoluteLimit"))
this->setAbsoluteLimit(
param.get<RFType>("AbsoluteLimit"));
if (param.hasKey("MinLinearReduction"))
this->setMinLinearReduction(
param.get<RFType>("MinLinearReduction"));
if (param.hasKey("FixedLinearReduction"))
this->setFixedLinearReduction(
param.get<bool>("FixedLinearReduction"));
if (param.hasKey("ReassembleThreshold"))
this->setReassembleThreshold(
param.get<RFType>("ReassembleThreshold"));
if (param.hasKey("LineSearchStrategy"))
this->setLineSearchStrategy(
param.get<std::string>("LineSearchStrategy"));
if (param.hasKey("LineSearchMaxIterations"))
this->setLineSearchMaxIterations(
param.get<unsigned int>("LineSearchMaxIterations"));
if (param.hasKey("LineSearchDampingFactor"))
this->setLineSearchDampingFactor(
param.get<RFType>("LineSearchDampingFactor"));
if (param.hasKey("KeepMatrix"))
this->setKeepMatrix(
param.get<bool>("KeepMatrix"));
if (param.hasSub("Operator"))
this->setOperatorParams(param.sub("Operator"));
}
}; // end class Newton
} // end namespace PPS
#endif // PACXX_PROJECTSEMINAR_2019_SRC_NEWTON_HH