diff --git a/src/dfpm/io/configuration.cpp b/src/dfpm/io/configuration.cpp index 67ef004..1205eff 100644 --- a/src/dfpm/io/configuration.cpp +++ b/src/dfpm/io/configuration.cpp @@ -1,390 +1,390 @@ /* ============================================================================= Copyright (c) 2014 - 2016 F. Georget Princeton University All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ============================================================================= */ #include "configuration.hpp" #include "specmicp_common/io/yaml.hpp" #include "specmicp_common/io/safe_config.hpp" #include "dfpm/meshes/generic_mesh1d.hpp" #include "dfpm/meshes/axisymmetric_mesh1d.hpp" #include "dfpm/solver/parabolic_structs.hpp" #include "specmicp_common/io/config_yaml_sections.h" #include "specmicp_common/log.hpp" #include namespace specmicp { namespace io { // new interface // ------------- //! \brief Configure the mesh mesh::Mesh1DPtr SPECMICP_DLL_PUBLIC configure_mesh(YAMLConfigHandle&& mesh_section) { auto type = mesh_section.get_required_attribute(SPC_CF_S_MESH_A_TYPE); mesh::Mesh1DPtr the_mesh {nullptr}; if (type == SPC_CF_S_MESH_SS_UNIFMESH) { SPC_CONF_LOG << "Uniform mesh"; the_mesh = configure_uniform_mesh1D( mesh_section.get_section(SPC_CF_S_MESH_SS_UNIFMESH)); } else if (type == SPC_CF_S_MESH_SS_RAMPMESH) { SPC_CONF_LOG << "Ramp mesh"; the_mesh = configure_ramp_mesh1D( mesh_section.get_section(SPC_CF_S_MESH_SS_RAMPMESH)); } else if (type == SPC_CF_S_MESH_SS_UNIFAXISYMESH) { SPC_CONF_LOG << "Uniform axisymmetric mesh"; the_mesh = configure_uniform_axisymmetric_mesh1D( mesh_section.get_section(SPC_CF_S_MESH_SS_UNIFAXISYMESH)); } else mesh_section.report_error(YAMLConfigError::InvalidArgument, "'"+type+"' is not a valid type of mesh."); uindex_t nb_nodes = the_mesh->nb_nodes(); SPC_CONF_LOG << "Coordinates : " << "\n - 0 : " << the_mesh->get_position(0) << "\n - 1 : " << the_mesh->get_position(1) << "\n - 2 : " << the_mesh->get_position(2) << "\n - ---- " << "\n - " << nb_nodes-3 << " : " << the_mesh->get_position(nb_nodes - 3) << "\n - " << nb_nodes-2 << " : " << the_mesh->get_position(nb_nodes - 2) << "\n - " << nb_nodes-1 << " : " << the_mesh->get_position(nb_nodes - 1) ; return the_mesh; } //! \brief Configure an uniform mesh mesh::Mesh1DPtr configure_uniform_mesh1D(YAMLConfigHandle&& uniform_section) { mesh::Uniform1DMeshGeometry geometry; geometry.dx = uniform_section.get_required_attribute( SPC_CF_S_MESH_SS_UNIFMESH_A_DX, 0.0); geometry.nb_nodes = uniform_section.get_required_attribute( SPC_CF_S_MESH_SS_UNIFMESH_A_NBNODES, 0); geometry.section = uniform_section.get_required_attribute( SPC_CF_S_MESH_SS_UNIFMESH_A_SECTION, 0.0); return mesh::uniform_mesh1d(geometry); } //! \brief Configure a 'ramp' mesh mesh::Mesh1DPtr configure_ramp_mesh1D(YAMLConfigHandle&& ramp_section) { mesh::Ramp1DMeshGeometry geometry; geometry.dx_min = ramp_section.get_required_attribute( SPC_CF_S_MESH_SS_RAMPMESH_A_MIN_DX, 0.0); geometry.dx_max = ramp_section.get_required_attribute( SPC_CF_S_MESH_SS_RAMPMESH_A_MAX_DX, 0.0); geometry.length_ramp = ramp_section.get_required_attribute( SPC_CF_S_MESH_SS_RAMPMESH_A_RAMP_LENGTH, 0.0); geometry.length_plateau = ramp_section.get_required_attribute( SPC_CF_S_MESH_SS_RAMPMESH_A_PLATEAU_LENGTH, 0.0); geometry.section = ramp_section.get_required_attribute( SPC_CF_S_MESH_SS_RAMPMESH_A_SECTION, 0.0); return mesh::ramp_mesh1d(geometry); } //! \brief Configure an uniform axisymmetric mesh mesh::Mesh1DPtr configure_uniform_axisymmetric_mesh1D(YAMLConfigHandle&& axisymmetric_section) { mesh::UniformAxisymmetricMesh1DGeometry geometry; geometry.dx = axisymmetric_section.get_optional_attribute( SPC_CF_S_MESH_SS_UNIFAXISYMESH_A_DX, -1.0); geometry.nb_nodes = axisymmetric_section.get_optional_attribute( SPC_CF_S_MESH_SS_UNIFAXISYMESH_A_NBNODES, -1.0); geometry.height = axisymmetric_section.get_required_attribute( SPC_CF_S_MESH_SS_UNIFAXISYMESH_A_HEIGHT, 0.0); geometry.radius = axisymmetric_section.get_required_attribute( SPC_CF_S_MESH_SS_UNIFAXISYMESH_A_RADIUS, 0.0); return mesh::uniform_axisymmetric_mesh1d(geometry); } // old interface // ------------- mesh::Mesh1DPtr configure_mesh(const YAML::Node& conf) { check_mandatory_yaml_node(conf, SPC_CF_S_MESH, "__main__"); const YAML::Node& subconf = conf[SPC_CF_S_MESH]; std::string type = subconf[SPC_CF_S_MESH_A_TYPE].as(); if (type == SPC_CF_S_MESH_SS_UNIFMESH) return configure_uniform_mesh1D(subconf); else if (type == SPC_CF_S_MESH_SS_RAMPMESH) return configure_ramp_mesh1D(subconf); else if (type == SPC_CF_S_MESH_SS_UNIFAXISYMESH) return configure_uniform_axisymmetric_mesh1D(subconf); else throw std::invalid_argument("Not a valid type of mesh : '"+type+"'."); } mesh::Mesh1DPtr configure_uniform_mesh1D(const YAML::Node& conf) { check_mandatory_yaml_node(conf, SPC_CF_S_MESH_SS_UNIFMESH, SPC_CF_S_MESH); const YAML::Node& subconf = conf[SPC_CF_S_MESH_SS_UNIFMESH]; mesh::Uniform1DMeshGeometry geometry; geometry.dx = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_UNIFMESH_A_DX, SPC_CF_S_MESH_SS_UNIFMESH); geometry.nb_nodes = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_UNIFMESH_A_NBNODES, SPC_CF_S_MESH_SS_UNIFMESH); geometry.section = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_UNIFMESH_A_SECTION, SPC_CF_S_MESH_SS_UNIFMESH); return mesh::uniform_mesh1d(geometry); } mesh::Mesh1DPtr configure_ramp_mesh1D(const YAML::Node& conf) { check_mandatory_yaml_node(conf, SPC_CF_S_MESH_SS_RAMPMESH, SPC_CF_S_MESH); const YAML::Node& subconf = conf[SPC_CF_S_MESH_SS_RAMPMESH]; mesh::Ramp1DMeshGeometry geometry; geometry.dx_min = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_RAMPMESH_A_MIN_DX, SPC_CF_S_MESH_SS_RAMPMESH); geometry.dx_max = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_RAMPMESH_A_MAX_DX, SPC_CF_S_MESH_SS_RAMPMESH); geometry.length_ramp = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_RAMPMESH_A_RAMP_LENGTH, SPC_CF_S_MESH_SS_RAMPMESH); geometry.length_plateau = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_RAMPMESH_A_PLATEAU_LENGTH, SPC_CF_S_MESH_SS_RAMPMESH); geometry.section = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_RAMPMESH_A_SECTION, SPC_CF_S_MESH_SS_RAMPMESH); return mesh::ramp_mesh1d(geometry); } mesh::Mesh1DPtr configure_uniform_axisymmetric_mesh1D(const YAML::Node& conf) { const YAML::Node& subconf = conf[SPC_CF_S_MESH_SS_UNIFAXISYMESH]; mesh::UniformAxisymmetricMesh1DGeometry geometry; geometry.dx = get_yaml_optional( subconf, SPC_CF_S_MESH_SS_UNIFAXISYMESH_A_DX, SPC_CF_S_MESH_SS_UNIFAXISYMESH, -1.0); geometry.nb_nodes = get_yaml_optional( subconf, SPC_CF_S_MESH_SS_UNIFAXISYMESH_A_NBNODES, SPC_CF_S_MESH_SS_UNIFAXISYMESH, -1); geometry.height = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_UNIFAXISYMESH_A_HEIGHT, SPC_CF_S_MESH_SS_UNIFAXISYMESH); geometry.radius = get_yaml_mandatory( subconf, SPC_CF_S_MESH_SS_UNIFAXISYMESH_A_RADIUS, SPC_CF_S_MESH_SS_UNIFAXISYMESH); return mesh::uniform_axisymmetric_mesh1d(geometry); } // transport options // ================= void configure_transport_options( dfpmsolver::ParabolicDriverOptions& options, const YAML::Node& configuration ) { check_mandatory_yaml_node(configuration, SPC_CF_S_DFPM, "__main__"); const YAML::Node& conf = configuration[SPC_CF_S_DFPM]; options.absolute_tolerance = get_yaml_optional( conf, SPC_CF_S_DFPM_A_ABS_TOL, SPC_CF_S_DFPM, DFPM_DEFAULT_ABS_TOL); options.residuals_tolerance = get_yaml_optional( conf, SPC_CF_S_DFPM_A_RES_TOL, SPC_CF_S_DFPM, DFPM_DEFAULT_RES_TOL); options.step_tolerance = get_yaml_optional( conf, SPC_CF_S_DFPM_A_STEP_TOL, SPC_CF_S_DFPM, DFPM_DEFAULT_STEP_TOL); options.maximum_iterations = get_yaml_optional( conf, SPC_CF_S_DFPM_A_MAX_ITER, SPC_CF_S_DFPM, DFPM_DEFAULT_MAX_ITER); options.maximum_step_length = get_yaml_optional( conf, SPC_CF_S_DFPM_A_MAX_STEP_LENGTH, SPC_CF_S_DFPM, DFPM_DEFAULT_MAX_STEP_LENGTH); options.max_iterations_at_max_length = get_yaml_optional(conf, SPC_CF_S_DFPM_A_MAX_STEP_MAX_ITER, SPC_CF_S_DFPM, DFPM_DEFAULT_MAX_ITER_MAX_LENGTH); options.threshold_stationary_point = get_yaml_optional(conf, SPC_CF_S_DFPM_A_TRSHOLD_STATIONARY, SPC_CF_S_DFPM, DFPM_DEFAULT_TRSHOLD_STATIONARY); options.quasi_newton = get_yaml_optional(conf, SPC_CF_S_DFPM_A_QUASI_NEWTON, SPC_CF_S_DFPM, DFPM_PARABOLIC_DEFAULT_QUASI_NEWTON); if (conf[SPC_CF_S_DFPM_A_SPARSE_SOLVER]) { std::string sparse_solver = conf[SPC_CF_S_DFPM_A_SPARSE_SOLVER].as(); if (sparse_solver == "LU") options.sparse_solver = sparse_solvers::SparseSolver::SparseLU; else if (sparse_solver == "QR") options.sparse_solver = sparse_solvers::SparseSolver::SparseQR; else if (sparse_solver == "GMRES") options.sparse_solver = sparse_solvers::SparseSolver::GMRES; else if (sparse_solver == "BiCGSTAB") options.sparse_solver = sparse_solvers::SparseSolver::BiCGSTAB; else throw std::runtime_error("Invalid argument for the sparse solver : '"+sparse_solver+"'.\n" +"Available solvers : 'LU' / 'QR' / 'GMRES' / 'BiCGSTAB'."); } if (conf[SPC_CF_S_DFPM_A_LINESEARCH]) { std::string linesearch = conf[SPC_CF_S_DFPM_A_LINESEARCH].as(); if (linesearch == "backtracking") - options.linesearch = dfpmsolver::ParabolicLinesearch::Bactracking; + options.linesearch = dfpmsolver::ParabolicLinesearch::Backtracking; else if (linesearch == "strang") options.linesearch = dfpmsolver::ParabolicLinesearch::Strang; else throw std::runtime_error("Invalid argument for the linesearch : '"+linesearch+"'.\n" - +"Available options : 'bactracking' / 'strang'."); + +"Available options : 'backtracking' / 'strang'."); } } void configure_transport_options( dfpmsolver::ParabolicDriverOptions& options, io::YAMLConfigHandle&& conf ) { conf.set_if_attribute_exists( options.absolute_tolerance, SPC_CF_S_DFPM_A_ABS_TOL, 0.0); conf.set_if_attribute_exists( options.residuals_tolerance, SPC_CF_S_DFPM_A_RES_TOL, 0.0, 1.0); conf.set_if_attribute_exists( options.step_tolerance, SPC_CF_S_DFPM_A_STEP_TOL, 0.0, 1.0); conf.set_if_attribute_exists( options.maximum_iterations, SPC_CF_S_DFPM_A_MAX_ITER, 0); conf.set_if_attribute_exists( options.maximum_step_length, SPC_CF_S_DFPM_A_MAX_STEP_LENGTH, 0.0); conf.set_if_attribute_exists( options.max_iterations_at_max_length, SPC_CF_S_DFPM_A_MAX_STEP_MAX_ITER, 0); conf.set_if_attribute_exists( options.threshold_stationary_point, SPC_CF_S_DFPM_A_TRSHOLD_STATIONARY, 0.0, 1.0); conf.set_if_attribute_exists( options.quasi_newton, SPC_CF_S_DFPM_A_QUASI_NEWTON); if (conf.has_attribute(SPC_CF_S_DFPM_A_SPARSE_SOLVER)) { std::string sparse_solver = conf.get_attribute( SPC_CF_S_DFPM_A_SPARSE_SOLVER); if (sparse_solver == "LU") options.sparse_solver = sparse_solvers::SparseSolver::SparseLU; else if (sparse_solver == "QR") options.sparse_solver = sparse_solvers::SparseSolver::SparseQR; #ifdef EIGEN3_UNSUPPORTED_FOUND else if (sparse_solver == "GMRES") options.sparse_solver = sparse_solvers::SparseSolver::GMRES; #endif else if (sparse_solver == "BiCGSTAB") options.sparse_solver = sparse_solvers::SparseSolver::BiCGSTAB; else conf.report_error( YAMLConfigError::InvalidArgument, "Invalid argument for the sparse solver : '" + sparse_solver + "'.\n" "Available solvers :" "'LU' / 'QR' / 'GMRES' / 'BiCGSTAB'." ); } if (conf.has_attribute(SPC_CF_S_DFPM_A_LINESEARCH)) { std::string linesearch = conf.get_attribute(SPC_CF_S_DFPM_A_LINESEARCH); if (linesearch == "backtracking") - options.linesearch = dfpmsolver::ParabolicLinesearch::Bactracking; + options.linesearch = dfpmsolver::ParabolicLinesearch::Backtracking; else if (linesearch == "strang") options.linesearch = dfpmsolver::ParabolicLinesearch::Strang; else conf.report_error( YAMLConfigError::InvalidArgument, "Invalid argument for the linesearch : '" + linesearch + "'.\n" - "Available options : 'bactracking' / 'strang'." + "Available options : 'backtracking' / 'strang'." ); } } void print_transport_options( dfpmsolver::ParabolicDriverOptions& options, std::ostream& output ) { output << " - residual tolerance " << options.residuals_tolerance << "\n - absolute tolerance " << options.absolute_tolerance << "\n - step tolerance " << options.step_tolerance << "\n - max. iterations " << options.maximum_iterations << "\n - max. step length " << options.maximum_iterations << "\n - # iter. at max length " << options.max_iterations_at_max_length << "\n - threshold stationary " << options.threshold_stationary_point << "\n - quasi newton iter. " << options.quasi_newton << "\n - coeff. Newton step " << options.coeff_accept_newton_step << std::endl; } } //end namespace io } //end namespace specmicp diff --git a/src/dfpm/solver/parabolic_driver.inl b/src/dfpm/solver/parabolic_driver.inl index bbbaa65..cbda003 100644 --- a/src/dfpm/solver/parabolic_driver.inl +++ b/src/dfpm/solver/parabolic_driver.inl @@ -1,535 +1,535 @@ /* ============================================================================= Copyright (c) 2014 - 2016 F. Georget Princeton University All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ============================================================================= */ #ifndef SPECMICP_DFPMSOLVER_PARABOLICDRIVER_HPP #include "parabolic_driver.hpp" // for syntaxic coloration only #endif #include "specmicp_common/log.hpp" namespace specmicp { namespace dfpmsolver { template void ParabolicDriver::compute_jacobian(Vector& displacement, Vector& velocity, Eigen::SparseMatrix& jacobian ) { program().compute_jacobian(displacement, velocity, jacobian, get_options().alpha*m_current_dt); jacobian = jacobian*scaling().asDiagonal(); jacobian.makeCompressed(); get_perfs().nb_call_jacobian += 1; } template ParabolicDriverReturnCode ParabolicDriver::solve_timestep(scalar_t dt, Eigen::VectorXd& displacement) { initialize_timestep(dt, displacement); return restart_timestep(displacement); } template void ParabolicDriver::initialize_timestep(scalar_t dt, Eigen::VectorXd& displacement) { // 1) scaling initialize_scaling(); // 2) predictor, velocity m_residuals = Eigen::VectorXd::Zero(get_neq()); m_current_dt = dt; set_predictor(displacement); reset_velocity(); program().apply_bc(dt, displacement, m_velocity); // 3) initial residuals compute_residuals(displacement, m_velocity, m_residuals); m_norm_0 = norm_residuals(); // - If the norm is too low, we set it to 1 // This is important in reactive transport when // an equation can be driven out of equilibrium // during a timestep if (m_norm_0 < get_options().absolute_tolerance) m_norm_0 = 1.0; // 4) other initialisations get_perfs().nb_iterations = 0; } template void ParabolicDriver::set_predictor(Vector& displacement) { if (get_options().alpha < 1) m_predictor = displacement + (1.0-get_options().alpha)*m_current_dt*m_velocity; else m_predictor = displacement; } template scalar_t ParabolicDriver::update_norm(const Vector& update) { // l-∞ scaled norm scalar_t norm = 0.0; for (index_t dof=0; dof ParabolicDriverReturnCode ParabolicDriver::restart_timestep(Vector& displacement) { ParabolicDriverReturnCode return_code = ParabolicDriverReturnCode::NotConvergedYet; //m_solver.reset(nullptr); 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().absolute_residual = m_residuals.norm(); 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 (m_solver == nullptr) { m_solver = sparse_solvers::get_sparse_solver< Eigen::SparseMatrix, Vector, Vector>(get_options().sparse_solver); compute_jacobian(displacement, m_velocity, m_jacobian); m_solver->analyse_pattern(m_jacobian); sparse_solvers::SparseSolverReturnCode retcode = m_solver->decompose(m_jacobian); if (retcode != sparse_solvers::SparseSolverReturnCode::Success) { ERROR << "Error when factorizing jacobian : " << (int) retcode << std::endl; return ParabolicDriverReturnCode::ErrorLinearSystem; } force_recompute_jacobian =false; } else if (force_recompute_jacobian or get_perfs().nb_iterations % get_options().quasi_newton == 0) { compute_jacobian(displacement, m_velocity, m_jacobian); sparse_solvers::SparseSolverReturnCode retcode = m_solver->decompose(m_jacobian); if (retcode != sparse_solvers::SparseSolverReturnCode::Success) { ERROR << "Error when factorizing jacobian : " << (int) retcode << std::endl; return ParabolicDriverReturnCode::ErrorLinearSystem; } } get_perfs().nb_iterations += 1; m_gradient = m_jacobian.transpose()*m_residuals; 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 ParabolicDriverReturnCode ParabolicDriver::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 (get_perfs().absolute_residual < get_options().absolute_tolerance) { termcode = ParabolicDriverReturnCode::ResidualMinimized; } else if (nb_iterations > 0 and norm_update > 0.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 double ParabolicDriver::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 double ParabolicDriver::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 void ParabolicDriver::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 ParabolicDriverReturnCode ParabolicDriver::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: + case ParabolicLinesearch::Backtracking: 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 ParabolicLinesearchReturnCode ParabolicDriver::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= 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 ParabolicLinesearchReturnCode ParabolicDriver::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 : " < 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 { // 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; } 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 \n" << "Proposed update : \n ---- \n" << update << "\n ---- \n" << "Current displacements \n ---- \n" << displacements << "\n ---- \n"; return ParabolicLinesearchReturnCode::Divergence; } fcp = compute_residuals_linesearch(update, lambda, xp); ++cnt; } while(retcode == 2 and cnt < 50); 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 void ParabolicDriver::set_velocity(Vector& velocity_vector) { m_velocity.resize(program().get_tot_ndf()); m_velocity.setZero(); for (index_t dof=0; dof void ParabolicDriver::reset_velocity() { for (index_t dof=0; dof Princeton University All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ============================================================================= */ #ifndef SPECMICP_DFPMSOLVER_PARABOLICSTRUCTS_HPP #define SPECMICP_DFPMSOLVER_PARABOLICSTRUCTS_HPP #include "driver_structs.hpp" #define DFPM_PARABOLIC_DEFAULT_ALPHA 1.0 -#define DFPM_PARABOLIC_DEFAULT_LINESEARCH specmicp::dfpmsolver::ParabolicLinesearch::Bactracking +#define DFPM_PARABOLIC_DEFAULT_LINESEARCH specmicp::dfpmsolver::ParabolicLinesearch::Backtracking #define DFPM_PARABOLIC_DEFAULT_QUASI_NEWTON 1 namespace specmicp { namespace dfpmsolver { //! \brief The linesearch to use in the parabolic driver enum class ParabolicLinesearch { - Bactracking, //!< Bactracking linesearch + Backtracking, //!< Backtracking linesearch Strang //!< Strang Linesearch }; //! \brief The return code of the parabolic linesearch enum class ParabolicLinesearchReturnCode { NotSupposedToHappen, //!< For debugging purpose MaximumIterations, //!< Error : maximum iteration reached LambdaTooSmall, //!< Error : step tolerance reached Divergence, //!< Error : divergence Success //!< Success ! }; //! \brief Options of a parabolic driver //! struct ParabolicDriverOptions: public DriverOptions { //! The linesearch to use ParabolicLinesearch linesearch {DFPM_PARABOLIC_DEFAULT_LINESEARCH}; //! Number of iterations without reforming the jacobian int quasi_newton {DFPM_PARABOLIC_DEFAULT_QUASI_NEWTON}; //! Implicit/Cranck-Nicholson parameter (between 0 and 1) scalar_t alpha {DFPM_PARABOLIC_DEFAULT_ALPHA}; ParabolicDriverOptions() {} }; //! \brief Return codes of the parabolic solver //! //! A value greater than NotconvergedYet indicates a succes enum class ParabolicDriverReturnCode { LinesearchFailed = -5, //!< Linesearch has failed (usually indicates a bad system) MaxIterations = -4, //!< Maximum number of iterations reached MaxStepTakenTooManyTimes = -3, //!< Maximum step taken too many times (divergence) ErrorLinearSystem = -2, //!< Error when solving the linear system StationaryPoint = -1, //!< Stationnary points are detected NotConvergedYet = 0, //!< Problem is not converged ResidualMinimized = 1, //!< The residual is minimized (Success) ErrorMinimized = 2 //!< Error is minimized (may be good) }; //! \brief Return true if 'retcode' corresponds to a failure inline bool has_failed(ParabolicDriverReturnCode retcode) { return (retcode <= ParabolicDriverReturnCode::NotConvergedYet); } //! \brief Performance of the parabolic driver struct ParabolicDriverPerformance: public DriverPerformance { ParabolicDriverReturnCode return_code; ParabolicDriverPerformance(): DriverPerformance(), return_code(ParabolicDriverReturnCode::NotConvergedYet) {} }; } // end namespace dfpmsolver } // end namespace specmicp #endif // SPECMICP_DFPMSOLVER_PARABOLICSTRUCTS_HPP