diff --git a/src/dfpmsolver/driver_structs.hpp b/src/dfpmsolver/driver_structs.hpp index 2464af7..104e2a9 100644 --- a/src/dfpmsolver/driver_structs.hpp +++ b/src/dfpmsolver/driver_structs.hpp @@ -1,52 +1,52 @@ #ifndef SPECMICP_DFPMSOLVER_DRIVERSTRUCTS_HPP #define SPECMICP_DFPMSOLVER_DRIVERSTRUCTS_HPP #include "common.hpp" -#include "utils/sparse_solver_structs.hpp" +#include "utils/sparse_solvers/sparse_solver_structs.hpp" namespace specmicp { namespace dfpmsolver { //! \brief Options of a driver //! struct DriverOptions { scalar_t residuals_tolerance; //!< Tolerance for the residual scalar_t step_tolerance; //!< Tolerance for the minimum step length scalar_t threshold_stationary_point; //!< if ||R||>threshold, the point is classified as stationary int maximum_iterations; //!< Maximum iterations allowed scalar_t maximum_step_length; //!< Maximum step length allowed int max_iterations_at_max_length; //!< Maximum number of iterations at maximum step length scalar_t coeff_accept_newton_step; //!< Accept Newton step if enough progress is made - SparseSolver sparse_solver; //!< The sparse solver to use + sparse_solvers::SparseSolver sparse_solver; //!< The sparse solver to use DriverOptions(): residuals_tolerance(5e-5), step_tolerance(1e-10), threshold_stationary_point(1e-4), maximum_iterations(200), maximum_step_length(1e3), max_iterations_at_max_length(50), coeff_accept_newton_step(0.9), - sparse_solver(SparseSolver::SparseQR) + sparse_solver(sparse_solvers::SparseSolver::SparseQR) {} }; //! \brief Performance of a driver struct DriverPerformance { int nb_call_residuals; int nb_call_jacobian; int nb_iterations; int nb_consecutive_max_step_taken; int nb_max_step_taken; bool maximum_step_taken; scalar_t current_residual; scalar_t current_update; }; } // end namespace dfpmsolver } // end namespace specmicp #endif // SPECMICP_DFPMSOLVER_DRIVERSTRUCTS_HPP diff --git a/src/dfpmsolver/parabolic_driver.hpp b/src/dfpmsolver/parabolic_driver.hpp index 2b5fd80..81a9d2f 100644 --- a/src/dfpmsolver/parabolic_driver.hpp +++ b/src/dfpmsolver/parabolic_driver.hpp @@ -1,147 +1,146 @@ #ifndef SPECMICP_DFPMSOLVER_PARABOLICDRIVER_HPP #define SPECMICP_DFPMSOLVER_PARABOLICDRIVER_HPP #include "common.hpp" -#include <Eigen/Sparse> +#include "utils/sparse_solvers/sparse_solver.hpp" #include "driver.hpp" #include "parabolic_structs.hpp" namespace specmicp { namespace dfpmsolver { //! \brief The parabolic driver //! //! Parabolic driver for finite element programs template <class Program> class ParabolicDriver: public Driver<Program, ParabolicDriverOptions, ParabolicDriverPerformance> { public: using base=Driver<Program, ParabolicDriverOptions, ParabolicDriverPerformance>; using base::program; using base::get_neq; using base::get_options; using base::get_perfs; using base::scaling; using base::initialize_scaling; ParabolicDriver(Program& the_program): Driver<Program, ParabolicDriverOptions, ParabolicDriverPerformance>(the_program), m_velocity(Vector::Zero(the_program.get_tot_ndf())) {} //! \brief Solve a timestep of length dt ParabolicDriverReturnCode solve_timestep(scalar_t dt, Vector& displacement); //! \brief Restart the current timestep ParabolicDriverReturnCode restart_timestep(Vector& displacement); //! \brief Check if the solution has been found ParabolicDriverReturnCode check_convergence(); //! \brief Compute the residuals void compute_residuals(const Vector& displacement, Vector& residual) { compute_residuals(displacement, m_velocity, residual); } //! \brief Compute the residuals void compute_residuals(const Vector& displacement, const Vector& velocity, Vector& residual) { program().compute_residuals(displacement, velocity, residual); get_perfs().nb_call_residuals += 1; } //! \brief Compute the jacobian void compute_jacobian(Vector& displacement, Vector& velocity, Eigen::SparseMatrix<scalar_t>& jacobian ); //! \brief Return the norm of the current residuals double norm_residuals() {return m_residuals.norm();} //! \brief Read/Write reference to the velocity vector const Vector& get_velocity() const {return m_velocity;} Vector& velocity() {return m_velocity;} void set_velocity(Vector& velocity_vector); //! \brief Initialize the computation void initialize_timestep(scalar_t dt, Eigen::VectorXd& displacement); //! \brief Strang Linesearch //! //! ref : //! - Matthies et al. (1979) //! - JHP course notes ParabolicLinesearchReturnCode strang_linesearch( Vector& update, Vector& displacements, scalar_t& lambda ); scalar_t residuals_0() {return m_norm_0;} private: void reset_velocity(); //! \brief Backtracking Linesearch //! //! ref : //! - Algo A6.3.1 : Dennis and Schnabel (1983) //! - Nocedal & Wrigth (2006) ParabolicLinesearchReturnCode backtracking_linesearch( Vector& update, Vector& displacements, scalar_t& lambda_out ); //! Update the variables in displacement void update_variable( const Vector& update, scalar_t lambda, Vector& displacement ); //! \brief Set the predictor void set_predictor(Vector& displacement); - //! \brief solve the linear system - SparseSolverReturnCode solve_linear_system(Eigen::VectorXd& update); //! \brief perform the linesearch ParabolicDriverReturnCode linesearch( Vector &update, Vector &displacements ); //! Compute the residuals for the linesearch double compute_residuals_linesearch( Vector& update, scalar_t lambda, Vector& displacement ); double compute_residuals_strang_linesearch( Vector &update, scalar_t lambda, Vector &displacement ); scalar_t update_norm(const Vector& update); double m_norm_0; double m_current_dt; Eigen::VectorXd m_gradient; Eigen::VectorXd m_velocity; Eigen::VectorXd m_residuals; Eigen::VectorXd m_predictor; Eigen::SparseMatrix<double> m_jacobian; + sparse_solvers::SparseSolverPtr<Eigen::SparseMatrix<double>, Vector, Vector> m_solver; bool m_velocity_is_initialized; }; } // end namespace dfpmsolver } // end namespace specmicp // implementation // ============== #include "parabolic_driver.inl" #endif // SPECMICP_DFPMSOLVER_PARABOLICDRIVER_HPP diff --git a/src/dfpmsolver/parabolic_driver.inl b/src/dfpmsolver/parabolic_driver.inl index 6f629c8..0079a30 100644 --- a/src/dfpmsolver/parabolic_driver.inl +++ b/src/dfpmsolver/parabolic_driver.inl @@ -1,471 +1,466 @@ #include "parabolic_driver.hpp" // for syntaxic coloration only #include "utils/log.hpp" -#include "utils/sparse_solver.hpp" +//#include "utils/sparse_solver.hpp" namespace specmicp { namespace dfpmsolver { template <class Program> void ParabolicDriver<Program>::compute_jacobian(Vector& displacement, Vector& velocity, Eigen::SparseMatrix<scalar_t>& jacobian ) { program().compute_jacobian(displacement, velocity, jacobian, get_options().alpha*m_current_dt); jacobian = jacobian*scaling().asDiagonal(); get_perfs().nb_call_jacobian += 1; } template <class Program> ParabolicDriverReturnCode ParabolicDriver<Program>::solve_timestep(scalar_t dt, Eigen::VectorXd& displacement) { initialize_timestep(dt, displacement); return restart_timestep(displacement); } template <class Program> void ParabolicDriver<Program>::initialize_timestep(scalar_t dt, Eigen::VectorXd& displacement) { initialize_scaling(); m_residuals = Eigen::VectorXd::Zero(get_neq()); m_current_dt = dt; set_predictor(displacement); reset_velocity(); program().apply_bc(dt, displacement, m_velocity); compute_residuals(displacement, m_velocity, m_residuals); m_norm_0 = norm_residuals(); get_perfs().nb_iterations = 0; } template <class Program> void ParabolicDriver<Program>::set_predictor(Vector& displacement) { if (get_options().alpha < 1) m_predictor = displacement + (1-get_options().alpha)*m_current_dt*m_velocity; else m_predictor = displacement; } template <class Program> scalar_t ParabolicDriver<Program>::update_norm(const Vector& update) { // l-∞ scaled norm scalar_t norm = 0.0; for (index_t dof=0; dof<program().get_tot_ndf(); ++dof) { const index_t id_eq = program().id_equation(dof); if (id_eq == no_species or update(id_eq) == 0.0) continue; norm = std::max(norm, std::abs(update(id_eq))/(std::max(std::abs(m_velocity(dof)), 1.0/scaling(id_eq)))); } return norm; } template <class Program> ParabolicDriverReturnCode ParabolicDriver<Program>::restart_timestep(Vector& displacement) { ParabolicDriverReturnCode return_code = ParabolicDriverReturnCode::NotConvergedYet; + m_solver = sparse_solvers::get_sparse_solver< + Eigen::SparseMatrix<double>, Vector, Vector>(get_options().sparse_solver); Eigen::VectorXd update(get_neq()); update.setZero(); get_perfs().current_update = 0; + bool force_recompute_jacobian = true; while (return_code == ParabolicDriverReturnCode::NotConvergedYet) { compute_residuals(displacement, m_velocity, m_residuals); get_perfs().current_residual = m_residuals.norm()/m_norm_0; get_perfs().current_update = update_norm(update); DEBUG << " NB iterations : " << get_perfs().nb_iterations << " - res : " << get_perfs().current_residual << " - update : " << get_perfs().current_update; return_code = check_convergence(); if (return_code != ParabolicDriverReturnCode::NotConvergedYet) break; - if (get_perfs().nb_iterations % get_options().quasi_newton == 0) + if (force_recompute_jacobian + or get_perfs().nb_iterations % get_options().quasi_newton == 0) { - // Todo : avoid computation of decomposition if it is the same matrix compute_jacobian(displacement, m_velocity, m_jacobian); + m_solver->decompose(m_jacobian); } get_perfs().nb_iterations += 1; m_gradient = m_jacobian.transpose()*m_residuals; - SparseSolverReturnCode retcode = solve_linear_system(update); - if (retcode != SparseSolverReturnCode::Success) + sparse_solvers::SparseSolverReturnCode retcode = m_solver->solve_scaling(m_residuals, scaling(), update); + if (retcode != sparse_solvers::SparseSolverReturnCode::Success) { ERROR << "Error when solving linear system : " << (int) retcode << std::endl; return ParabolicDriverReturnCode::ErrorLinearSystem; } //if (update.norm() < get_options().step_tolerance) return_code = ParabolicDriverReturnCode::ErrorMinimized; //else return_code = linesearch(update, displacement); } return return_code; } template <class Program> ParabolicDriverReturnCode ParabolicDriver<Program>::check_convergence() { ParabolicDriverReturnCode termcode = ParabolicDriverReturnCode::NotConvergedYet; const int nb_iterations = get_perfs().nb_iterations; const scalar_t norm_residuals = get_perfs().current_residual; const scalar_t norm_update = get_perfs().current_update; //std::cout << "Residuals : " << nb_iterations << " - " << norm_residuals/m_norm_0 << std::endl; DEBUG << "Residuals : " << nb_iterations << " - " << norm_residuals/m_norm_0; if (norm_residuals < get_options().residuals_tolerance) { termcode = ParabolicDriverReturnCode::ResidualMinimized; } else if (nb_iterations > 0 and norm_update > 0 and norm_update < 1.01*get_options().step_tolerance) { if (norm_residuals > get_options().threshold_stationary_point) { ERROR << "Stationary point detected !"; termcode = ParabolicDriverReturnCode::StationaryPoint; } WARNING << "Error is minimized - may indicate a stationnary point"; termcode = ParabolicDriverReturnCode::ErrorMinimized; } else if (nb_iterations > get_options().maximum_iterations) { ERROR << "Maximum number of iteration reached (" << get_options().maximum_iterations << ")"; termcode = ParabolicDriverReturnCode::MaxIterations; } else if (get_perfs().maximum_step_taken) { get_perfs().nb_consecutive_max_step_taken += 1; get_perfs().nb_max_step_taken += 1; if (get_perfs().nb_consecutive_max_step_taken == get_options().max_iterations_at_max_length) { ERROR << "Divergence detected - Maximum step length taken two many times"; termcode = ParabolicDriverReturnCode::MaxStepTakenTooManyTimes; } } else { get_perfs().nb_consecutive_max_step_taken = 0; } get_perfs().return_code = termcode; return termcode; } -template <class Program> -SparseSolverReturnCode ParabolicDriver<Program>::solve_linear_system(Eigen::VectorXd& update) -{ - return solve_sparse_linear_system(scaling().asDiagonal()*m_residuals, - m_jacobian, - update, - get_options().sparse_solver); -} - template <class Program> double ParabolicDriver<Program>::compute_residuals_linesearch( Vector &update, scalar_t lambda, Vector &displacement ) { Eigen::VectorXd velocity(m_velocity); Eigen::VectorXd residual = Eigen::VectorXd::Zero(get_neq()); program().update_solution(update, lambda, get_options().alpha*m_current_dt, m_predictor, displacement, velocity); compute_residuals(displacement, velocity, residual); return 0.5*residual.squaredNorm(); } template <class Program> double ParabolicDriver<Program>::compute_residuals_strang_linesearch( Vector &update, scalar_t lambda, Vector &displacement ) { Eigen::VectorXd velocity(m_velocity); Eigen::VectorXd residual = Eigen::VectorXd::Zero(get_neq()); program().update_solution(update, lambda, get_options().alpha*m_current_dt, m_predictor, displacement, velocity); compute_residuals(displacement, velocity, residual); return update.dot(residual); } template <class Program> void ParabolicDriver<Program>::update_variable( const Vector& update, scalar_t lambda, Vector& displacement ) { program().update_solution(update, lambda, get_options().alpha*m_current_dt, m_predictor, displacement, m_velocity); } template <class Program> ParabolicDriverReturnCode ParabolicDriver<Program>::linesearch( Vector &update, Vector &displacements ) { base::is_step_too_long(update); get_perfs().maximum_step_taken = false; scalar_t lambda; ParabolicLinesearchReturnCode retcode; switch (get_options().linesearch) { case ParabolicLinesearch::Bactracking: retcode = backtracking_linesearch(update, displacements, lambda); break; case ParabolicLinesearch::Strang: retcode = strang_linesearch(update, displacements, lambda); break; default: throw std::runtime_error("Linesearch type for Parabolic driver is not recognized"); break; } if (retcode != ParabolicLinesearchReturnCode::Success) { return ParabolicDriverReturnCode::LinesearchFailed; } update_variable(update, lambda, displacements); update *= lambda; return ParabolicDriverReturnCode::NotConvergedYet; } namespace internal { //! \brief Return true if a and b have the same sign inline bool have_same_sign(double a, double b) { return (std::copysign(1., a) * std::copysign(1., b) > 0); } } // end namespace internal template <class Program> ParabolicLinesearchReturnCode ParabolicDriver<Program>::strang_linesearch( Vector& update, Vector& displacements, scalar_t& lambda ) { DEBUG << "Strang linesearch"; Eigen::VectorXd xp(displacements); const scalar_t s_tol = 0.5; const scalar_t s_max = 2.0; const int lin_max = 10; scalar_t s_b = 0.0; scalar_t g_b = 0.5*m_residuals.squaredNorm(); scalar_t s_a = 1.0; scalar_t g_a = compute_residuals_strang_linesearch(update, 1.0, xp); scalar_t newtlen = (scaling().asDiagonal()*update).norm(); // const scalar_t s_r = s_a; const scalar_t g_r = g_a; if (std::abs(g_a) <= s_tol*std::abs(g_b)) { DEBUG << "Skip linesearch "; lambda = 1.0; if (lambda == 1.0 and (newtlen > 0.99 * get_options().maximum_step_length)) { get_perfs().maximum_step_taken = true; } return ParabolicLinesearchReturnCode::Success; } while (internal::have_same_sign(g_a, g_b) and s_a < s_max) { s_b = s_a; s_a = 2*s_a; g_b = g_a; g_a = compute_residuals_strang_linesearch(update, s_a, xp); } scalar_t g_ = g_a; scalar_t g_0 = g_a; scalar_t s = s_a; int l; for (l=0; l<lin_max; ++l) { if (internal::have_same_sign(g_a, g_b) or std::abs(g_) < s_tol*std::abs(g_0) or std::abs(s_a - s_b) < s_tol*(s_a + s_b)/2 ) break; s = s_a - g_a * ( s_a - s_b)/(g_a - g_b); g_ = compute_residuals_strang_linesearch(update, s, xp); if (internal::have_same_sign(g_, g_a)) g_b = g_b/2.; else { s_b = s_a; g_b = g_a; } s_a = s; g_a = g_; } if (l == lin_max) return ParabolicLinesearchReturnCode::MaximumIterations; if (g_a >= g_r) { WARNING << "Failed to find better update in Strang linesearch"; //lambda = 0.1*s_r; return backtracking_linesearch(update, displacements, lambda); } lambda = s_a; if (lambda == 1.0 and (newtlen > 0.99 * get_options().maximum_step_length)) { get_perfs().maximum_step_taken = true; } return ParabolicLinesearchReturnCode::Success; } template <class Program> ParabolicLinesearchReturnCode ParabolicDriver<Program>::backtracking_linesearch( Vector& update, Vector& displacements, scalar_t& lambda_out ) { // References // ---------- // - Algo A6.3.1 : Dennis and Schnabel (1983) // - Nocedal & Wrigth (2006) DEBUG << "Linesearch"; Eigen::VectorXd xp(displacements); double fcp; int retcode = 2; // 2 not converged, 1 problem, 0 success const scalar_t alpha = 1e-4; scalar_t newtlen = (scaling().asDiagonal()*update).norm(); scalar_t init_slope = m_gradient.dot(update); const scalar_t rellength = update_norm(update); const scalar_t minlambda = get_options().step_tolerance / rellength; scalar_t lambda = 1.0; scalar_t lambda_prev = lambda; scalar_t merit_value = 0.5*m_residuals.squaredNorm(); // new residual fcp = compute_residuals_linesearch(update, lambda, xp); // Skip linesearch if enough progress is done // ------------------------------------------ if (fcp < get_options().coeff_accept_newton_step *merit_value) { DEBUG << "Skip linesearch "; lambda_out = 1.0; return ParabolicLinesearchReturnCode::Success; } // The linesearch // -------------- scalar_t fc = merit_value; scalar_t fcp_prev; int cnt = 0; do { SPAM << "cnt : " <<cnt << " - lambda : " << lambda << " # " << fc << " # " << fcp << " # " << init_slope; if (fcp <= fc + alpha*lambda*init_slope) //pg760 Fachinei2003 { retcode = 0; if (lambda == 1.0 and (newtlen > 0.99 * get_options().maximum_step_length)) { get_perfs().maximum_step_taken = true; } break; } else if (lambda < minlambda) { retcode = 0; lambda = minlambda; //retcode = 1; break; } else { //WARNING << "denom : " << fcp - fc -init_slope; // Select a new step length // - - - - - - - - - - - - double lambdatmp; if (cnt == 0) { // only a quadratic at the first lambdatmp = - init_slope / (2*(fcp - fc -init_slope)); } else { const scalar_t factor = 1.0 /(lambda - lambda_prev); const scalar_t x1 = fcp - fc - lambda*init_slope; const scalar_t x2 = fcp_prev - fc - lambda_prev*init_slope; const scalar_t a = factor * ( x1/(lambda*lambda) - x2/(lambda_prev*lambda_prev)); const scalar_t b = factor * ( -x1*lambda_prev/(lambda*lambda) + x2*lambda/(lambda_prev*lambda_prev)); if (a == 0) { // cubic interpolation is in fact a quadratic interpolation lambdatmp = - init_slope/(2*b); } else { const scalar_t disc = b*b-3*a*init_slope; lambdatmp = (-b+std::sqrt(disc))/(3*a); } if (lambdatmp > 0.5*lambda ) lambdatmp = 0.5*lambda; } //WARNING << "lambdatmp : " << lambdatmp; lambda_prev = lambda; fcp_prev = fcp; if (lambdatmp < 0.1*lambda) { lambda = 0.1 * lambda; } else { lambda = lambdatmp; } } if (not std::isfinite(lambda)) { ERROR << "Lambda is non finite - we stop"; return ParabolicLinesearchReturnCode::Divergence; } fcp = compute_residuals_linesearch(update, lambda, xp); //xp.velocity = x.velocity; ++cnt; } while(retcode == 2 and cnt < 50); //WARNING << "Lambda : " << lambda << " - iterations : " << cnt; if (cnt == 50) { ERROR << "Too much linesearch iterations ! We stop"; return ParabolicLinesearchReturnCode::MaximumIterations; } lambda_out = lambda; switch (retcode) { case 0: return ParabolicLinesearchReturnCode::Success; break; case 1: return ParabolicLinesearchReturnCode::LambdaTooSmall; default: return ParabolicLinesearchReturnCode::NotSupposedToHappen; break; } } template <class Program> void ParabolicDriver<Program>::set_velocity(Vector& velocity_vector) { m_velocity.resize(program().get_tot_ndf()); m_velocity.setZero(); for (index_t dof=0; dof<program().get_tot_ndf(); ++dof) { if (program().id_equation(dof) == no_equation) { m_velocity(dof) = velocity_vector(dof); } } } template <class Program> void ParabolicDriver<Program>::reset_velocity() { for (index_t dof=0; dof<program().get_tot_ndf(); ++dof) { if (program().id_equation(dof) == no_equation) continue; m_velocity(dof) = 0.0; } } } // end namespace dfpmsolver } // end namespace specmicp