/** * \copyright * Copyright (c) 2012-2018, OpenGeoSys Community (http://www.opengeosys.org) * Distributed under a Modified BSD License. * See accompanying file LICENSE.txt or * http://www.opengeosys.org/project/license * */ #include "CentralDifferencesJacobianAssembler.h" #include "BaseLib/Error.h" #include "MathLib/LinAlg/Eigen/EigenMapTools.h" #include "LocalAssemblerInterface.h" namespace ProcessLib { CentralDifferencesJacobianAssembler::CentralDifferencesJacobianAssembler( std::vector<double>&& absolute_epsilons) : _absolute_epsilons(std::move(absolute_epsilons)) { if (_absolute_epsilons.empty()) OGS_FATAL("No values for the absolute epsilons have been given."); } void CentralDifferencesJacobianAssembler::assembleWithJacobian( LocalAssemblerInterface& local_assembler, const double t, const std::vector<double>& local_x_data, const std::vector<double>& local_xdot_data, const double dxdot_dx, const double dx_dx, std::vector<double>& local_M_data, std::vector<double>& local_K_data, std::vector<double>& local_b_data, std::vector<double>& local_Jac_data) { // TODO do not check in every call. if (local_x_data.size() % _absolute_epsilons.size() != 0) { OGS_FATAL( "The number of specified epsilons (%u) and the number of local " "d.o.f.s (%u) do not match, i.e., the latter is not divisable by " "the former.", _absolute_epsilons.size(), local_x_data.size()); } auto const num_r_c = static_cast<Eigen::MatrixXd::Index>(local_x_data.size()); auto const local_x = MathLib::toVector<Eigen::VectorXd>(local_x_data, num_r_c); auto const local_xdot = MathLib::toVector<Eigen::VectorXd>(local_xdot_data, num_r_c); auto local_Jac = MathLib::createZeroedMatrix(local_Jac_data, num_r_c, num_r_c); _local_x_perturbed_data = local_x_data; auto const num_dofs_per_component = local_x_data.size() / _absolute_epsilons.size(); // Residual res := M xdot + K x - b // Computing Jac := dres/dx // = M dxdot/dx + dM/dx xdot + K dx/dx + dK/dx x - db/dx // (Note: dM/dx and dK/dx actually have the second and // third index transposed.) // The loop computes the dM/dx, dK/dx and db/dx terms, the rest is computed // afterwards. for (Eigen::MatrixXd::Index i = 0; i < num_r_c; ++i) { // assume that local_x_data is ordered by component. auto const component = i / num_dofs_per_component; auto const eps = _absolute_epsilons[component]; _local_x_perturbed_data[i] += eps; local_assembler.assemble(t, _local_x_perturbed_data, local_M_data, local_K_data, local_b_data); _local_x_perturbed_data[i] = local_x_data[i] - eps; local_assembler.assemble(t, _local_x_perturbed_data, _local_M_data, _local_K_data, _local_b_data); _local_x_perturbed_data[i] = local_x_data[i]; if (!local_M_data.empty()) { auto const local_M_p = MathLib::toMatrix(local_M_data, num_r_c, num_r_c); auto const local_M_m = MathLib::toMatrix(_local_M_data, num_r_c, num_r_c); local_Jac.col(i).noalias() += // dM/dxi * x_dot (local_M_p - local_M_m) * local_xdot / (2.0 * eps); local_M_data.clear(); _local_M_data.clear(); } if (!local_K_data.empty()) { auto const local_K_p = MathLib::toMatrix(local_K_data, num_r_c, num_r_c); auto const local_K_m = MathLib::toMatrix(_local_K_data, num_r_c, num_r_c); local_Jac.col(i).noalias() += // dK/dxi * x (local_K_p - local_K_m) * local_x / (2.0 * eps); local_K_data.clear(); _local_K_data.clear(); } if (!local_b_data.empty()) { auto const local_b_p = MathLib::toVector<Eigen::VectorXd>(local_b_data, num_r_c); auto const local_b_m = MathLib::toVector<Eigen::VectorXd>(_local_b_data, num_r_c); local_Jac.col(i).noalias() -= // db/dxi (local_b_p - local_b_m) / (2.0 * eps); local_b_data.clear(); _local_b_data.clear(); } } // Assemble with unperturbed local x. local_assembler.assemble(t, local_x_data, local_M_data, local_K_data, local_b_data); // Compute remaining terms of the Jacobian. if (dxdot_dx != 0.0 && !local_M_data.empty()) { auto local_M = MathLib::toMatrix(local_M_data, num_r_c, num_r_c); local_Jac.noalias() += local_M * dxdot_dx; } if (dx_dx != 0.0 && !local_K_data.empty()) { auto local_K = MathLib::toMatrix(local_K_data, num_r_c, num_r_c); local_Jac.noalias() += local_K * dx_dx; } } std::unique_ptr<CentralDifferencesJacobianAssembler> createCentralDifferencesJacobianAssembler(BaseLib::ConfigTree const& config) { //! \ogs_file_param{prj__processes__process__jacobian_assembler__type} config.checkConfigParameter("type", "CentralDifferences"); // TODO make non-optional. //! \ogs_file_param{prj__processes__process__jacobian_assembler__relative_epsilons} auto rel_eps = config.getConfigParameterOptional<std::vector<double>>( "relative_epsilons"); //! \ogs_file_param{prj__processes__process__jacobian_assembler__component_magnitudes} auto comp_mag = config.getConfigParameterOptional<std::vector<double>>( "component_magnitudes"); if (!!rel_eps != !!comp_mag) { OGS_FATAL( "Either both or none of <relative_epsilons> and " "<component_magnitudes> have to be specified."); } std::vector<double> abs_eps; if (rel_eps) { if (rel_eps->size() != comp_mag->size()) { OGS_FATAL( "The numbers of components of <relative_epsilons> and " "<component_magnitudes> do not match."); } abs_eps.resize(rel_eps->size()); for (std::size_t i=0; i<rel_eps->size(); ++i) { abs_eps[i] = (*rel_eps)[i] * (*comp_mag)[i]; } } else { // By default 1e-8 is used as epsilon for all components. // TODO: remove this default value. abs_eps.emplace_back(1e-8); } return std::make_unique<CentralDifferencesJacobianAssembler>( std::move(abs_eps)); } } // ProcessLib