From d1feb884905648cedca93156192a74dfdece597a Mon Sep 17 00:00:00 2001 From: Wenqing Wang <wenqing.wang@ufz.de> Date: Fri, 16 Oct 2020 15:18:06 +0200 Subject: [PATCH] [Process/TRM] Added local assembler --- .../IntegrationPointData.h | 182 +++ .../ThermoRichardsMechanicsFEM-impl.h | 1443 +++++++++++++++++ .../ThermoRichardsMechanicsFEM.h | 258 +++ 3 files changed, 1883 insertions(+) create mode 100644 ProcessLib/ThermoRichardsMechanics/IntegrationPointData.h create mode 100644 ProcessLib/ThermoRichardsMechanics/ThermoRichardsMechanicsFEM-impl.h create mode 100644 ProcessLib/ThermoRichardsMechanics/ThermoRichardsMechanicsFEM.h diff --git a/ProcessLib/ThermoRichardsMechanics/IntegrationPointData.h b/ProcessLib/ThermoRichardsMechanics/IntegrationPointData.h new file mode 100644 index 00000000000..caad48f6f7c --- /dev/null +++ b/ProcessLib/ThermoRichardsMechanics/IntegrationPointData.h @@ -0,0 +1,182 @@ +/** + * \file + * \copyright + * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#pragma once + +#include <limits> +#include <memory> + +#include "MaterialLib/MPL/VariableType.h" +#include "MaterialLib/SolidModels/LinearElasticIsotropic.h" +#include "MathLib/KelvinVector.h" +#include "MathLib/LinAlg/Eigen/EigenMapTools.h" + +namespace ProcessLib +{ +namespace ThermoRichardsMechanics +{ +template <typename BMatricesType, typename ShapeMatrixTypeDisplacement, + typename ShapeMatricesTypePressure, int DisplacementDim, int NPoints> +struct IntegrationPointData final +{ + explicit IntegrationPointData( + MaterialLib::Solids::MechanicsBase<DisplacementDim> const& + solid_material) + : solid_material(solid_material), + material_state_variables( + solid_material.createMaterialStateVariables()) + { + // Initialize current time step values + static const int kelvin_vector_size = + MathLib::KelvinVector::KelvinVectorDimensions< + DisplacementDim>::value; + sigma_eff.setZero(kelvin_vector_size); + sigma_sw.setZero(kelvin_vector_size); + eps.setZero(kelvin_vector_size); + eps_m.setZero(kelvin_vector_size); + + // Previous time step values are not initialized and are set later. + eps_prev.resize(kelvin_vector_size); + eps_m_prev.resize(kelvin_vector_size); + sigma_eff_prev.resize(kelvin_vector_size); + } + + typename ShapeMatrixTypeDisplacement::template MatrixType< + DisplacementDim, NPoints * DisplacementDim> + N_u_op; + typename BMatricesType::KelvinVectorType sigma_eff, sigma_eff_prev; + typename BMatricesType::KelvinVectorType sigma_sw, sigma_sw_prev; + typename BMatricesType::KelvinVectorType eps, eps_prev; + typename BMatricesType::KelvinVectorType eps_m, eps_m_prev; + + typename ShapeMatrixTypeDisplacement::NodalRowVectorType N_u; + typename ShapeMatrixTypeDisplacement::GlobalDimNodalMatrixType dNdx_u; + + typename ShapeMatricesTypePressure::NodalRowVectorType N_p; + typename ShapeMatricesTypePressure::GlobalDimNodalMatrixType dNdx_p; + + typename ShapeMatricesTypePressure::GlobalDimVectorType v_darcy; + + double saturation = std::numeric_limits<double>::quiet_NaN(); + double saturation_prev = std::numeric_limits<double>::quiet_NaN(); + double porosity = std::numeric_limits<double>::quiet_NaN(); + double porosity_prev = std::numeric_limits<double>::quiet_NaN(); + double transport_porosity = std::numeric_limits<double>::quiet_NaN(); + double transport_porosity_prev = std::numeric_limits<double>::quiet_NaN(); + double dry_density_solid = std::numeric_limits<double>::quiet_NaN(); + double dry_density_pellet_saturated = + std::numeric_limits<double>::quiet_NaN(); + double dry_density_pellet_unsaturated = + std::numeric_limits<double>::quiet_NaN(); + + MaterialLib::Solids::MechanicsBase<DisplacementDim> const& solid_material; + std::unique_ptr<typename MaterialLib::Solids::MechanicsBase< + DisplacementDim>::MaterialStateVariables> + material_state_variables; + + double integration_weight = std::numeric_limits<double>::quiet_NaN(); + + void pushBackState() + { + eps_prev = eps; + eps_m_prev = eps_m; + sigma_eff_prev = sigma_eff; + sigma_sw_prev = sigma_sw; + saturation_prev = saturation; + porosity_prev = porosity; + transport_porosity_prev = transport_porosity; + material_state_variables->pushBackState(); + } + + MathLib::KelvinVector::KelvinMatrixType<DisplacementDim> + computeElasticTangentStiffness( + double const t, + ParameterLib::SpatialPosition const& x_position, + double const dt, + double const temperature) + { + namespace MPL = MaterialPropertyLib; + + MPL::VariableArray variable_array; + MPL::VariableArray variable_array_prev; + + auto const null_state = solid_material.createMaterialStateVariables(); + + using KV = MathLib::KelvinVector::KelvinVectorType<DisplacementDim>; + + variable_array[static_cast<int>(MPL::Variable::stress)].emplace<KV>( + KV::Zero()); + variable_array[static_cast<int>(MPL::Variable::mechanical_strain)] + .emplace<KV>(KV::Zero()); + variable_array[static_cast<int>(MPL::Variable::temperature)] + .emplace<double>(temperature); + + variable_array_prev[static_cast<int>(MPL::Variable::stress)] + .emplace<KV>(KV::Zero()); + variable_array_prev[static_cast<int>(MPL::Variable::mechanical_strain)] + .emplace<KV>(KV::Zero()); + variable_array_prev[static_cast<int>(MPL::Variable::temperature)] + .emplace<double>(temperature); + + auto&& solution = + solid_material.integrateStress(variable_array_prev, variable_array, + t, x_position, dt, *null_state); + + if (!solution) + { + OGS_FATAL("Computation of elastic tangent stiffness failed."); + } + + MathLib::KelvinVector::KelvinMatrixType<DisplacementDim> C = + std::move(std::get<2>(*solution)); + + return C; + } + + typename BMatricesType::KelvinMatrixType updateConstitutiveRelation( + MaterialPropertyLib::VariableArray const& variable_array, + double const t, + ParameterLib::SpatialPosition const& x_position, + double const dt, + double const temperature) + { + MaterialPropertyLib::VariableArray variable_array_prev; + variable_array_prev[static_cast<int>( + MaterialPropertyLib::Variable::stress)] + .emplace<MathLib::KelvinVector::KelvinVectorType<DisplacementDim>>( + sigma_eff_prev); + variable_array_prev[static_cast<int>(MaterialPropertyLib::Variable:: + mechanical_strain)] + .emplace<MathLib::KelvinVector::KelvinVectorType<DisplacementDim>>( + eps_m_prev); + variable_array_prev[static_cast<int>( + MaterialPropertyLib::Variable::temperature)] + .emplace<double>(temperature); + + auto&& solution = solid_material.integrateStress( + variable_array_prev, variable_array, t, x_position, dt, + *material_state_variables); + + if (!solution) + { + OGS_FATAL("Computation of local constitutive relation failed."); + } + + MathLib::KelvinVector::KelvinMatrixType<DisplacementDim> C; + std::tie(sigma_eff, material_state_variables, C) = std::move(*solution); + + return C; + } + + EIGEN_MAKE_ALIGNED_OPERATOR_NEW; +}; + +} // namespace ThermoRichardsMechanics +} // namespace ProcessLib diff --git a/ProcessLib/ThermoRichardsMechanics/ThermoRichardsMechanicsFEM-impl.h b/ProcessLib/ThermoRichardsMechanics/ThermoRichardsMechanicsFEM-impl.h new file mode 100644 index 00000000000..2e6df6bcf8f --- /dev/null +++ b/ProcessLib/ThermoRichardsMechanics/ThermoRichardsMechanicsFEM-impl.h @@ -0,0 +1,1443 @@ +/** + * \copyright + * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + * \file + * Created on November 29, 2017, 2:03 PM + */ + +#pragma once + +#include <cassert> + +#include "MaterialLib/MPL/Medium.h" +#include "MaterialLib/MPL/Utils/FormEffectiveThermalConductivity.h" +#include "MaterialLib/MPL/Utils/FormEigenTensor.h" +#include "MaterialLib/MPL/Utils/GetLiquidThermalExpansivity.h" +#include "MaterialLib/SolidModels/SelectSolidConstitutiveRelation.h" +#include "MathLib/KelvinVector.h" +#include "NumLib/Function/Interpolation.h" +#include "ProcessLib/Utils/SetOrGetIntegrationPointData.h" + +namespace ProcessLib +{ +namespace ThermoRichardsMechanics +{ +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +ThermoRichardsMechanicsLocalAssembler<ShapeFunctionDisplacement, + ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + ThermoRichardsMechanicsLocalAssembler( + MeshLib::Element const& e, + std::size_t const /*local_matrix_size*/, + bool const is_axially_symmetric, + unsigned const integration_order, + ThermoRichardsMechanicsProcessData<DisplacementDim>& process_data) + : _process_data(process_data), + _integration_method(integration_order), + _element(e), + _is_axially_symmetric(is_axially_symmetric) +{ + unsigned const n_integration_points = + _integration_method.getNumberOfPoints(); + + _ip_data.reserve(n_integration_points); + _secondary_data.N_u.resize(n_integration_points); + + auto const shape_matrices_u = + NumLib::initShapeMatrices<ShapeFunctionDisplacement, + ShapeMatricesTypeDisplacement, + DisplacementDim>(e, is_axially_symmetric, + _integration_method); + + auto const shape_matrices_p = + NumLib::initShapeMatrices<ShapeFunctionPressure, + ShapeMatricesTypePressure, DisplacementDim>( + e, is_axially_symmetric, _integration_method); + + auto const& solid_material = + MaterialLib::Solids::selectSolidConstitutiveRelation( + _process_data.solid_materials, _process_data.material_ids, + e.getID()); + + auto const& medium = _process_data.media_map->getMedium(_element.getID()); + + ParameterLib::SpatialPosition x_position; + x_position.setElementID(_element.getID()); + for (unsigned ip = 0; ip < n_integration_points; ip++) + { + x_position.setIntegrationPoint(ip); + _ip_data.emplace_back(solid_material); + auto& ip_data = _ip_data[ip]; + auto const& sm_u = shape_matrices_u[ip]; + _ip_data[ip].integration_weight = + _integration_method.getWeightedPoint(ip).getWeight() * + sm_u.integralMeasure * sm_u.detJ; + + ip_data.N_u_op = ShapeMatricesTypeDisplacement::template MatrixType< + DisplacementDim, displacement_size>::Zero(DisplacementDim, + displacement_size); + for (int i = 0; i < DisplacementDim; ++i) + { + ip_data.N_u_op + .template block<1, displacement_size / DisplacementDim>( + i, i * displacement_size / DisplacementDim) + .noalias() = sm_u.N; + } + + ip_data.N_u = sm_u.N; + ip_data.dNdx_u = sm_u.dNdx; + + ip_data.N_p = shape_matrices_p[ip].N; + ip_data.dNdx_p = shape_matrices_p[ip].dNdx; + + // Initial porosity. Could be read from intergration point data or mesh. + ip_data.porosity = + medium->property(MPL::porosity) + .template initialValue<double>( + x_position, + std::numeric_limits< + double>::quiet_NaN() /* t independent */); + + ip_data.transport_porosity = ip_data.porosity; + if (medium->hasProperty(MPL::PropertyType::transport_porosity)) + { + ip_data.transport_porosity = + medium->property(MPL::transport_porosity) + .template initialValue<double>( + x_position, + std::numeric_limits< + double>::quiet_NaN() /* t independent */); + } + + _secondary_data.N_u[ip] = shape_matrices_u[ip].N; + } +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::size_t ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::setIPDataInitialConditions(std::string const& name, + double const* values, + int const integration_order) +{ + if (integration_order != + static_cast<int>(_integration_method.getIntegrationOrder())) + { + OGS_FATAL( + "Setting integration point initial conditions; The integration " + "order of the local assembler for element {:d} is different " + "from the integration order in the initial condition.", + _element.getID()); + } + + if (name == "sigma_ip") + { + if (_process_data.initial_stress != nullptr) + { + OGS_FATAL( + "Setting initial conditions for stress from integration " + "point data and from a parameter '{:s}' is not possible " + "simultaneously.", + _process_data.initial_stress->name); + } + return ProcessLib::setIntegrationPointKelvinVectorData<DisplacementDim>( + values, _ip_data, &IpData::sigma_eff); + } + + if (name == "saturation_ip") + { + return ProcessLib::setIntegrationPointScalarData(values, _ip_data, + &IpData::saturation); + } + if (name == "porosity_ip") + { + return ProcessLib::setIntegrationPointScalarData(values, _ip_data, + &IpData::porosity); + } + if (name == "transport_porosity_ip") + { + return ProcessLib::setIntegrationPointScalarData( + values, _ip_data, &IpData::transport_porosity); + } + if (name == "swelling_stress_ip") + { + return ProcessLib::setIntegrationPointKelvinVectorData<DisplacementDim>( + values, _ip_data, &IpData::sigma_sw); + } + if (name == "epsilon_ip") + { + return ProcessLib::setIntegrationPointKelvinVectorData<DisplacementDim>( + values, _ip_data, &IpData::eps); + } + return 0; +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +void ThermoRichardsMechanicsLocalAssembler<ShapeFunctionDisplacement, + ShapeFunctionPressure, + IntegrationMethod, DisplacementDim>:: + setInitialConditionsConcrete(std::vector<double> const& local_x, + double const t, + bool const /*use_monolithic_scheme*/, + int const /*process_id*/) +{ + assert(local_x.size() == + temperature_size + pressure_size + displacement_size); + + auto p_L = + Eigen::Map<typename ShapeMatricesTypePressure::template VectorType< + pressure_size> const>(local_x.data() + pressure_index, + pressure_size); + + auto const& medium = _process_data.media_map->getMedium(_element.getID()); + MPL::VariableArray variables; + + ParameterLib::SpatialPosition x_position; + x_position.setElementID(_element.getID()); + + unsigned const n_integration_points = + _integration_method.getNumberOfPoints(); + for (unsigned ip = 0; ip < n_integration_points; ip++) + { + x_position.setIntegrationPoint(ip); + + auto const& N_p = _ip_data[ip].N_p; + + double p_cap_ip; + NumLib::shapeFunctionInterpolate(-p_L, N_p, p_cap_ip); + + variables[static_cast<int>(MPL::Variable::capillary_pressure)] = + p_cap_ip; + variables[static_cast<int>(MPL::Variable::phase_pressure)] = -p_cap_ip; + + // Note: temperature dependent saturation model is not considered so + // far. + _ip_data[ip].saturation_prev = + medium->property(MPL::PropertyType::saturation) + .template value<double>( + variables, x_position, t, + std::numeric_limits<double>::quiet_NaN()); + } +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +void ThermoRichardsMechanicsLocalAssembler<ShapeFunctionDisplacement, + ShapeFunctionPressure, + IntegrationMethod, DisplacementDim>:: + assembleWithJacobian(double const t, double const dt, + std::vector<double> const& local_x, + std::vector<double> const& local_xdot, + const double /*dxdot_dx*/, const double /*dx_dx*/, + std::vector<double>& /*local_M_data*/, + std::vector<double>& /*local_K_data*/, + std::vector<double>& local_rhs_data, + std::vector<double>& local_Jac_data) +{ + auto const local_matrix_dim = + displacement_size + pressure_size + temperature_size; + assert(local_x.size() == local_matrix_dim); + + auto const T = + Eigen::Map<typename ShapeMatricesTypePressure::template VectorType< + temperature_size> const>(local_x.data() + temperature_index, + temperature_size); + auto const p_L = + Eigen::Map<typename ShapeMatricesTypePressure::template VectorType< + pressure_size> const>(local_x.data() + pressure_index, + pressure_size); + + auto const u = + Eigen::Map<typename ShapeMatricesTypeDisplacement::template VectorType< + displacement_size> const>(local_x.data() + displacement_index, + displacement_size); + + auto const T_dot = + Eigen::Map<typename ShapeMatricesTypePressure::template VectorType< + temperature_size> const>(local_xdot.data() + temperature_index, + temperature_size); + auto const p_L_dot = + Eigen::Map<typename ShapeMatricesTypePressure::template VectorType< + pressure_size> const>(local_xdot.data() + pressure_index, + pressure_size); + auto const u_dot = + Eigen::Map<typename ShapeMatricesTypeDisplacement::template VectorType< + displacement_size> const>(local_xdot.data() + displacement_index, + displacement_size); + + auto local_Jac = MathLib::createZeroedMatrix< + typename ShapeMatricesTypeDisplacement::template MatrixType< + local_matrix_dim, local_matrix_dim>>( + local_Jac_data, local_matrix_dim, local_matrix_dim); + + auto local_rhs = + MathLib::createZeroedVector<typename ShapeMatricesTypeDisplacement:: + template VectorType<local_matrix_dim>>( + local_rhs_data, local_matrix_dim); + + auto const& identity2 = MathLib::KelvinVector::Invariants< + MathLib::KelvinVector::KelvinVectorDimensions<DisplacementDim>::value>:: + identity2; + + typename ShapeMatricesTypePressure::NodalMatrixType M_TT = + ShapeMatricesTypePressure::NodalMatrixType::Zero(temperature_size, + temperature_size); + typename ShapeMatricesTypePressure::NodalMatrixType K_TT = + ShapeMatricesTypePressure::NodalMatrixType::Zero(temperature_size, + temperature_size); + typename ShapeMatricesTypePressure::NodalMatrixType K_Tp = + ShapeMatricesTypePressure::NodalMatrixType::Zero(temperature_size, + pressure_size); + typename ShapeMatricesTypePressure::NodalMatrixType M_pT = + ShapeMatricesTypePressure::NodalMatrixType::Zero(pressure_size, + temperature_size); + typename ShapeMatricesTypePressure::NodalMatrixType laplace_p = + ShapeMatricesTypePressure::NodalMatrixType::Zero(pressure_size, + pressure_size); + + typename ShapeMatricesTypePressure::NodalMatrixType storage_p_a_p = + ShapeMatricesTypePressure::NodalMatrixType::Zero(pressure_size, + pressure_size); + + typename ShapeMatricesTypePressure::NodalMatrixType storage_p_a_S_Jpp = + ShapeMatricesTypePressure::NodalMatrixType::Zero(pressure_size, + pressure_size); + + typename ShapeMatricesTypePressure::NodalMatrixType storage_p_a_S = + ShapeMatricesTypePressure::NodalMatrixType::Zero(pressure_size, + pressure_size); + + typename ShapeMatricesTypeDisplacement::template MatrixType< + displacement_size, pressure_size> + Kup = ShapeMatricesTypeDisplacement::template MatrixType< + displacement_size, pressure_size>::Zero(displacement_size, + pressure_size); + + typename ShapeMatricesTypeDisplacement::template MatrixType< + pressure_size, displacement_size> + Kpu = ShapeMatricesTypeDisplacement::template MatrixType< + pressure_size, displacement_size>::Zero(pressure_size, + displacement_size); + + auto const& medium = _process_data.media_map->getMedium(_element.getID()); + auto const& liquid_phase = medium->phase("AqueousLiquid"); + auto const& solid_phase = medium->phase("Solid"); + MPL::VariableArray variables; + MPL::VariableArray variables_prev; + + ParameterLib::SpatialPosition x_position; + x_position.setElementID(_element.getID()); + + unsigned const n_integration_points = + _integration_method.getNumberOfPoints(); + for (unsigned ip = 0; ip < n_integration_points; ip++) + { + x_position.setIntegrationPoint(ip); + auto const& w = _ip_data[ip].integration_weight; + + auto const& N_u_op = _ip_data[ip].N_u_op; + + auto const& N_u = _ip_data[ip].N_u; + auto const& dNdx_u = _ip_data[ip].dNdx_u; + + auto const& N_p = _ip_data[ip].N_p; + auto const& dNdx_p = _ip_data[ip].dNdx_p; + + auto const x_coord = + NumLib::interpolateXCoordinate<ShapeFunctionDisplacement, + ShapeMatricesTypeDisplacement>( + _element, N_u); + auto const B = + LinearBMatrix::computeBMatrix<DisplacementDim, + ShapeFunctionDisplacement::NPOINTS, + typename BMatricesType::BMatrixType>( + dNdx_u, N_u, x_coord, _is_axially_symmetric); + + double T_ip; + NumLib::shapeFunctionInterpolate(T, N_p, T_ip); + double T_dot_ip; + NumLib::shapeFunctionInterpolate(T_dot, N_p, T_dot_ip); + + double p_cap_ip; + NumLib::shapeFunctionInterpolate(-p_L, N_p, p_cap_ip); + + double p_cap_dot_ip; + NumLib::shapeFunctionInterpolate(-p_L_dot, N_p, p_cap_dot_ip); + + variables[static_cast<int>(MPL::Variable::capillary_pressure)] = + p_cap_ip; + variables[static_cast<int>(MPL::Variable::phase_pressure)] = -p_cap_ip; + variables[static_cast<int>(MPL::Variable::temperature)] = T_ip; + + auto& eps = _ip_data[ip].eps; + auto const& sigma_eff = _ip_data[ip].sigma_eff; + auto& S_L = _ip_data[ip].saturation; + auto const S_L_prev = _ip_data[ip].saturation_prev; + auto const alpha = + medium->property(MPL::PropertyType::biot_coefficient) + .template value<double>(variables, x_position, t, dt); + + auto const C_el = _ip_data[ip].computeElasticTangentStiffness( + t, x_position, dt, T_ip); + + auto const beta_SR = + (1 - alpha) / + _ip_data[ip].solid_material.getBulkModulus(t, x_position, &C_el); + variables[static_cast<int>(MPL::Variable::grain_compressibility)] = + beta_SR; + + auto const K_LR = + liquid_phase.property(MPL::PropertyType::bulk_modulus) + .template value<double>(variables, x_position, t, dt); + + auto const rho_LR = + liquid_phase.property(MPL::PropertyType::density) + .template value<double>(variables, x_position, t, dt); + auto const& b = _process_data.specific_body_force; + + S_L = medium->property(MPL::PropertyType::saturation) + .template value<double>(variables, x_position, t, dt); + variables[static_cast<int>(MPL::Variable::liquid_saturation)] = S_L; + variables_prev[static_cast<int>(MPL::Variable::liquid_saturation)] = + S_L_prev; + + double const dS_L_dp_cap = + medium->property(MPL::PropertyType::saturation) + .template dValue<double>(variables, + MPL::Variable::capillary_pressure, + x_position, t, dt); + + auto const chi = [medium, x_position, t, dt](double const S_L) { + MPL::VariableArray variables; + variables[static_cast<int>(MPL::Variable::liquid_saturation)] = S_L; + return medium->property(MPL::PropertyType::bishops_effective_stress) + .template value<double>(variables, x_position, t, dt); + }; + double const chi_S_L = chi(S_L); + double const chi_S_L_prev = chi(S_L_prev); + + variables[static_cast<int>(MPL::Variable::effective_pore_pressure)] = + -chi_S_L * p_cap_ip; + variables_prev[static_cast<int>( + MPL::Variable::effective_pore_pressure)] = + -chi_S_L_prev * (p_cap_ip - p_cap_dot_ip * dt); + + // Set volumetric strain rate for the general case without swelling. + variables[static_cast<int>(MPL::Variable::volumetric_strain)] + .emplace<double>(identity2.transpose() * B * u); + variables_prev[static_cast<int>(MPL::Variable::volumetric_strain)] + .emplace<double>(identity2.transpose() * B * (u - u_dot * dt)); + + auto& phi = _ip_data[ip].porosity; + { // Porosity update + + variables_prev[static_cast<int>(MPL::Variable::porosity)] = + _ip_data[ip].porosity_prev; + phi = medium->property(MPL::PropertyType::porosity) + .template value<double>(variables, variables_prev, + x_position, t, dt); + variables[static_cast<int>(MPL::Variable::porosity)] = phi; + } + + if (alpha < phi) + { + OGS_FATAL( + "ThermoRichardsMechanics: Biot-coefficient {} is smaller than " + "porosity {} in element/integration point {}/{}.", + alpha, phi, _element.getID(), ip); + } + + // Swelling and possibly volumetric strain rate update. + auto& sigma_sw = _ip_data[ip].sigma_sw; + { + auto const& sigma_sw_prev = _ip_data[ip].sigma_sw_prev; + + // If there is swelling, compute it. Update volumetric strain rate, + // s.t. it corresponds to the mechanical part only. + sigma_sw = sigma_sw_prev; + if (solid_phase.hasProperty( + MPL::PropertyType::swelling_stress_rate)) + { + using DimMatrix = Eigen::Matrix<double, 3, 3>; + auto const sigma_sw_dot = + MathLib::KelvinVector::tensorToKelvin<DisplacementDim>( + solid_phase + .property(MPL::PropertyType::swelling_stress_rate) + .template value<DimMatrix>( + variables, variables_prev, x_position, t, dt)); + sigma_sw += sigma_sw_dot * dt; + + // !!! Misusing volumetric strain for mechanical volumetric + // strain just to update the transport porosity !!! + std::get<double>(variables[static_cast<int>( + MPL::Variable::volumetric_strain)]) += + identity2.transpose() * C_el.inverse() * sigma_sw; + std::get<double>(variables_prev[static_cast<int>( + MPL::Variable::volumetric_strain)]) += + identity2.transpose() * C_el.inverse() * sigma_sw_prev; + } + + if (solid_phase.hasProperty(MPL::PropertyType::transport_porosity)) + { + variables_prev[static_cast<int>( + MPL::Variable::transport_porosity)] = + _ip_data[ip].transport_porosity_prev; + + _ip_data[ip].transport_porosity = + solid_phase.property(MPL::PropertyType::transport_porosity) + .template value<double>(variables, variables_prev, + x_position, t, dt); + variables[static_cast<int>(MPL::Variable::transport_porosity)] = + _ip_data[ip].transport_porosity; + } + else + { + variables[static_cast<int>(MPL::Variable::transport_porosity)] = + phi; + } + } + + double const k_rel = + medium->property(MPL::PropertyType::relative_permeability) + .template value<double>(variables, x_position, t, dt); + auto const mu = + liquid_phase.property(MPL::PropertyType::viscosity) + .template value<double>(variables, x_position, t, dt); + + // For stress dependent permeability. + variables[static_cast<int>(MPL::Variable::stress)] + .emplace<SymmetricTensor>( + MathLib::KelvinVector::kelvinVectorToSymmetricTensor( + (_ip_data[ip].sigma_eff + + alpha * chi_S_L * identity2 * p_cap_ip) + .eval())); + auto const K_intrinsic = MPL::formEigenTensor<DisplacementDim>( + medium->property(MPL::PropertyType::permeability) + .value(variables, x_position, t, dt)); + + GlobalDimMatrixType const Ki_over_mu = K_intrinsic / mu; + GlobalDimMatrixType const rho_Ki_over_mu = rho_LR * Ki_over_mu; + + // + // displacement equation, displacement part + // + eps.noalias() = B * u; + + // Consider anisotropic thermal expansion. + // Read in 3x3 tensor. 2D case also requires expansion coeff. for z- + // component. + Eigen::Matrix<double, 3, + 3> const solid_linear_thermal_expansion_coefficient = + MaterialPropertyLib::formEigenTensor<3>( + solid_phase + .property( + MaterialPropertyLib::PropertyType::thermal_expansivity) + .value(variables, x_position, t, dt)); + + MathLib::KelvinVector::KelvinVectorType<DisplacementDim> const + dthermal_strain = + MathLib::KelvinVector::tensorToKelvin<DisplacementDim>( + solid_linear_thermal_expansion_coefficient) * + T_dot_ip * dt; + + variables[static_cast<int>(MPL::Variable::mechanical_strain)] + .emplace<MathLib::KelvinVector::KelvinVectorType<DisplacementDim>>( + eps - dthermal_strain); + + auto C = _ip_data[ip].updateConstitutiveRelation(variables, t, + x_position, dt, T_ip); + + local_Jac + .template block<displacement_size, displacement_size>( + displacement_index, displacement_index) + .noalias() += B.transpose() * C * B * w; + + double const p_FR = -chi_S_L * p_cap_ip; + // p_SR + variables[static_cast<int>(MPL::Variable::solid_grain_pressure)] = + p_FR - (sigma_eff + sigma_sw).dot(identity2) / (3 * (1 - phi)); + auto const rho_SR = + solid_phase.property(MPL::PropertyType::density) + .template value<double>(variables, x_position, t, dt); + + double const rho = rho_SR * (1 - phi) + S_L * phi * rho_LR; + local_rhs.template segment<displacement_size>(displacement_index) + .noalias() -= (B.transpose() * (sigma_eff + sigma_sw) - + N_u_op.transpose() * rho * b) * + w; + + // + // displacement equation, pressure part + // + Kup.noalias() += B.transpose() * alpha * chi_S_L * identity2 * N_p * w; + + auto const dchi_dS_L = + medium->property(MPL::PropertyType::bishops_effective_stress) + .template dValue<double>(variables, + MPL::Variable::liquid_saturation, + x_position, t, dt); + local_Jac + .template block<displacement_size, pressure_size>( + displacement_index, pressure_index) + .noalias() -= B.transpose() * alpha * + (chi_S_L + dchi_dS_L * p_cap_ip * dS_L_dp_cap) * + identity2 * N_p * w; + + local_Jac + .template block<displacement_size, pressure_size>( + displacement_index, pressure_index) + .noalias() += + N_u_op.transpose() * phi * rho_LR * dS_L_dp_cap * b * N_p * w; + + if (solid_phase.hasProperty(MPL::PropertyType::swelling_stress_rate)) + { + using DimMatrix = Eigen::Matrix<double, 3, 3>; + auto const dsigma_sw_dS_L = + MathLib::KelvinVector::tensorToKelvin<DisplacementDim>( + solid_phase + .property(MPL::PropertyType::swelling_stress_rate) + .template dValue<DimMatrix>( + variables, variables_prev, + MPL::Variable::liquid_saturation, x_position, t, + dt)); + local_Jac + .template block<displacement_size, pressure_size>( + displacement_index, pressure_index) + .noalias() += + B.transpose() * dsigma_sw_dS_L * dS_L_dp_cap * N_p * w; + } + // + // pressure equation, displacement part. + // + Kpu.noalias() += N_p.transpose() * S_L * rho_LR * alpha * + identity2.transpose() * B * w; + + // + // pressure equation, pressure part. + // + laplace_p.noalias() += + dNdx_p.transpose() * k_rel * rho_Ki_over_mu * dNdx_p * w; + + const double alphaB_minu_phi = alpha - phi; + double const a0 = alphaB_minu_phi * beta_SR; + double const specific_storage_a_p = S_L * (phi / K_LR + S_L * a0); + double const specific_storage_a_S = phi - p_cap_ip * S_L * a0; + + double const dspecific_storage_a_p_dp_cap = + dS_L_dp_cap * (phi / K_LR + 2 * S_L * a0); + double const dspecific_storage_a_S_dp_cap = + -a0 * (S_L + p_cap_ip * dS_L_dp_cap); + + storage_p_a_p.noalias() += + N_p.transpose() * rho_LR * specific_storage_a_p * N_p * w; + if (p_cap_dot_ip != 0) // prevent division by zero. + { + storage_p_a_S.noalias() -= N_p.transpose() * rho_LR * + specific_storage_a_S * (S_L - S_L_prev) / + (dt * p_cap_dot_ip) * N_p * w; + } + + local_Jac + .template block<pressure_size, pressure_size>(pressure_index, + pressure_index) + .noalias() += N_p.transpose() * p_cap_dot_ip * rho_LR * + dspecific_storage_a_p_dp_cap * N_p * w; + + storage_p_a_S_Jpp.noalias() -= + N_p.transpose() * rho_LR * + ((S_L - S_L_prev) * dspecific_storage_a_S_dp_cap + + specific_storage_a_S * dS_L_dp_cap) / + dt * N_p * w; + + local_Jac + .template block<pressure_size, pressure_size>(pressure_index, + pressure_index) + .noalias() -= N_p.transpose() * rho_LR * dS_L_dp_cap * alpha * + identity2.transpose() * B * u_dot * N_p * w; + + double const dk_rel_dS_l = + medium->property(MPL::PropertyType::relative_permeability) + .template dValue<double>(variables, + MPL::Variable::liquid_saturation, + x_position, t, dt); + GlobalDimVectorType const grad_p_cap = -dNdx_p * p_L; + local_Jac + .template block<pressure_size, pressure_size>(pressure_index, + pressure_index) + .noalias() += dNdx_p.transpose() * rho_Ki_over_mu * grad_p_cap * + dk_rel_dS_l * dS_L_dp_cap * N_p * w; + + local_Jac + .template block<pressure_size, pressure_size>(pressure_index, + pressure_index) + .noalias() += dNdx_p.transpose() * rho_LR * rho_Ki_over_mu * b * + dk_rel_dS_l * dS_L_dp_cap * N_p * w; + + local_rhs.template segment<pressure_size>(pressure_index).noalias() += + dNdx_p.transpose() * rho_LR * k_rel * rho_Ki_over_mu * b * w; + + // + // pressure equation, temperature part. + // + // Note: d (rho_l * beta _T)/dp * dotT + // Add the thermal expansion term is the point is in the fully + // saturated zone. + if (p_cap_ip <= 0.0) // p_l>0.0 + { + double const fluid_volumetric_thermal_expansion_coefficient = + MPL::getLiquidThermalExpansivity(liquid_phase, variables, + rho_LR, x_position, t, dt); + const double eff_thermal_expansion = + alphaB_minu_phi * + solid_linear_thermal_expansion_coefficient.trace() + + phi * fluid_volumetric_thermal_expansion_coefficient; + + M_pT.noalias() -= + N_p.transpose() * rho_LR * eff_thermal_expansion * N_p * w; + } + + // + // temperature equation. + // + { + auto const specific_heat_capacity_fluid = + liquid_phase + .property(MaterialPropertyLib::specific_heat_capacity) + .template value<double>(variables, x_position, t, dt); + + auto const specific_heat_capacity_solid = + solid_phase + .property(MaterialPropertyLib::PropertyType:: + specific_heat_capacity) + .template value<double>(variables, x_position, t, dt); + + M_TT.noalias() += + w * + (rho_SR * specific_heat_capacity_solid * (1 - phi) + + (S_L * rho_LR * specific_heat_capacity_fluid) * phi) * + N_p.transpose() * N_p; + + auto const thermal_conductivity_solid = + solid_phase + .property( + MaterialPropertyLib::PropertyType::thermal_conductivity) + .value(variables, x_position, t, dt); + + auto const thermal_conductivity_fluid = + liquid_phase + .property( + MaterialPropertyLib::PropertyType::thermal_conductivity) + .template value<double>(variables, x_position, t, dt) * + S_L; + + GlobalDimMatrixType const thermal_conductivity = + MaterialPropertyLib::formEffectiveThermalConductivity< + DisplacementDim>(thermal_conductivity_solid, + thermal_conductivity_fluid, phi); + + GlobalDimVectorType const velocity_l = + GlobalDimVectorType(-Ki_over_mu * (dNdx_p * p_L - rho_LR * b)); + + K_TT.noalias() += + (dNdx_p.transpose() * thermal_conductivity * dNdx_p + + N_p.transpose() * velocity_l.transpose() * dNdx_p * rho_LR * + specific_heat_capacity_fluid) * + w; + + // + // temperature equation, pressure part + // + K_Tp.noalias() -= rho_LR * specific_heat_capacity_fluid * + N_p.transpose() * (dNdx_p * T).transpose() * + Ki_over_mu * dNdx_p * w; + } + } + + if (_process_data.apply_mass_lumping) + { + storage_p_a_p = storage_p_a_p.colwise().sum().eval().asDiagonal(); + storage_p_a_S = storage_p_a_S.colwise().sum().eval().asDiagonal(); + storage_p_a_S_Jpp = + storage_p_a_S_Jpp.colwise().sum().eval().asDiagonal(); + } + + // + // -- Jacobian + // + // temperature equation. + local_Jac + .template block<temperature_size, temperature_size>(temperature_index, + temperature_index) + .noalias() += M_TT / dt + K_TT; + // temperature equation, pressure part + local_Jac + .template block<temperature_size, pressure_size>(temperature_index, + pressure_index) + .noalias() += K_Tp; + + // pressure equation, pressure part. + local_Jac + .template block<pressure_size, pressure_size>(pressure_index, + pressure_index) + .noalias() += laplace_p + storage_p_a_p / dt + storage_p_a_S_Jpp; + + // pressure equation, temperature part (contributed by thermal expansion). + local_Jac + .template block<pressure_size, temperature_size>(pressure_index, + temperature_index) + .noalias() += M_pT / dt; + + // pressure equation, displacement part. + local_Jac + .template block<pressure_size, displacement_size>(pressure_index, + displacement_index) + .noalias() = Kpu / dt; + + // + // -- Residual + // + // temperature equation + local_rhs.template segment<temperature_size>(temperature_index).noalias() -= + M_TT * T_dot + K_TT * T; + + // pressure equation + local_rhs.template segment<pressure_size>(pressure_index).noalias() -= + laplace_p * p_L + (storage_p_a_p + storage_p_a_S) * p_L_dot + + Kpu * u_dot + M_pT * T_dot; + + // displacement equation + local_rhs.template segment<displacement_size>(displacement_index) + .noalias() += Kup * p_L; +} + +template <int Components, typename StoreValuesFunction> +std::vector<double> transposeInPlace( + StoreValuesFunction const& store_values_function) +{ + std::vector<double> result; + store_values_function(result); + // Transpose. For time being Eigen's transposeInPlace doesn't work for + // non-square mapped matrices. + MathLib::toMatrix< + Eigen::Matrix<double, Eigen::Dynamic, Components, Eigen::RowMajor>>( + result, result.size() / Components, Components) = + MathLib::toMatrix< + Eigen::Matrix<double, Components, Eigen::Dynamic, Eigen::RowMajor>>( + result, Components, result.size() / Components) + .transpose() + .eval(); + + return result; +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::getSigma() const +{ + constexpr int kelvin_vector_size = + MathLib::KelvinVector::KelvinVectorDimensions<DisplacementDim>::value; + + return transposeInPlace<kelvin_vector_size>( + [this](std::vector<double>& values) { + return getIntPtSigma(0, {}, {}, values); + }); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> const& ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + getIntPtSigma( + const double /*t*/, + std::vector<GlobalVector*> const& /*x*/, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/, + std::vector<double>& cache) const +{ + return ProcessLib::getIntegrationPointKelvinVectorData<DisplacementDim>( + _ip_data, &IpData::sigma_eff, cache); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::getSwellingStress() const +{ + constexpr int kelvin_vector_size = + MathLib::KelvinVector::KelvinVectorDimensions<DisplacementDim>::value; + + return transposeInPlace<kelvin_vector_size>( + [this](std::vector<double>& values) { + return getIntPtSwellingStress(0, {}, {}, values); + }); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> const& ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + getIntPtSwellingStress( + const double /*t*/, + std::vector<GlobalVector*> const& /*x*/, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/, + std::vector<double>& cache) const +{ + constexpr int kelvin_vector_size = + MathLib::KelvinVector::KelvinVectorDimensions<DisplacementDim>::value; + auto const n_integration_points = _ip_data.size(); + + cache.clear(); + auto cache_mat = MathLib::createZeroedMatrix<Eigen::Matrix< + double, kelvin_vector_size, Eigen::Dynamic, Eigen::RowMajor>>( + cache, kelvin_vector_size, n_integration_points); + + for (unsigned ip = 0; ip < n_integration_points; ++ip) + { + auto const& sigma_sw = _ip_data[ip].sigma_sw; + cache_mat.col(ip) = + MathLib::KelvinVector::kelvinVectorToSymmetricTensor(sigma_sw); + } + + return cache; +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::getEpsilon() const +{ + constexpr int kelvin_vector_size = + MathLib::KelvinVector::KelvinVectorDimensions<DisplacementDim>::value; + + return transposeInPlace<kelvin_vector_size>( + [this](std::vector<double>& values) { + return getIntPtEpsilon(0, {}, {}, values); + }); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> const& ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + getIntPtEpsilon( + const double /*t*/, + std::vector<GlobalVector*> const& /*x*/, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/, + std::vector<double>& cache) const +{ + return ProcessLib::getIntegrationPointKelvinVectorData<DisplacementDim>( + _ip_data, &IpData::eps, cache); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> const& ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + getIntPtDarcyVelocity( + const double /*t*/, + std::vector<GlobalVector*> const& /*x*/, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/, + std::vector<double>& cache) const +{ + unsigned const n_integration_points = + _integration_method.getNumberOfPoints(); + + cache.clear(); + auto cache_matrix = MathLib::createZeroedMatrix<Eigen::Matrix< + double, DisplacementDim, Eigen::Dynamic, Eigen::RowMajor>>( + cache, DisplacementDim, n_integration_points); + + for (unsigned ip = 0; ip < n_integration_points; ip++) + { + cache_matrix.col(ip).noalias() = _ip_data[ip].v_darcy; + } + + return cache; +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::getSaturation() const +{ + std::vector<double> result; + getIntPtSaturation(0, {}, {}, result); + return result; +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> const& ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + getIntPtSaturation( + const double /*t*/, + std::vector<GlobalVector*> const& /*x*/, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/, + std::vector<double>& cache) const +{ + return ProcessLib::getIntegrationPointScalarData( + _ip_data, &IpData::saturation, cache); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::getPorosity() const +{ + std::vector<double> result; + getIntPtPorosity(0, {}, {}, result); + return result; +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> const& ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + getIntPtPorosity( + const double /*t*/, + std::vector<GlobalVector*> const& /*x*/, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/, + std::vector<double>& cache) const +{ + return ProcessLib::getIntegrationPointScalarData(_ip_data, + &IpData::porosity, cache); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::getTransportPorosity() const +{ + std::vector<double> result; + getIntPtTransportPorosity(0, {}, {}, result); + return result; +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> const& ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + getIntPtTransportPorosity( + const double /*t*/, + std::vector<GlobalVector*> const& /*x*/, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/, + std::vector<double>& cache) const +{ + return ProcessLib::getIntegrationPointScalarData( + _ip_data, &IpData::transport_porosity, cache); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +std::vector<double> const& ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>:: + getIntPtDryDensitySolid( + const double /*t*/, + std::vector<GlobalVector*> const& /*x*/, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& /*dof_table*/, + std::vector<double>& cache) const +{ + return ProcessLib::getIntegrationPointScalarData( + _ip_data, &IpData::dry_density_solid, cache); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +void ThermoRichardsMechanicsLocalAssembler<ShapeFunctionDisplacement, + ShapeFunctionPressure, + IntegrationMethod, DisplacementDim>:: + postNonLinearSolverConcrete(std::vector<double> const& local_x, + std::vector<double> const& local_xdot, + double const t, double const dt, + bool const use_monolithic_scheme, + int const /*process_id*/) +{ + const int displacement_offset = + use_monolithic_scheme ? displacement_index : 0; + + auto const T = + Eigen::Map<typename ShapeMatricesTypePressure::template VectorType< + temperature_size> const>(local_x.data() + temperature_index, + temperature_size); + auto const T_dot = + Eigen::Map<typename ShapeMatricesTypePressure::template VectorType< + temperature_size> const>(local_xdot.data() + temperature_index, + temperature_size); + + auto const u = + Eigen::Map<typename ShapeMatricesTypeDisplacement::template VectorType< + displacement_size> const>(local_x.data() + displacement_offset, + displacement_size); + auto const& medium = _process_data.media_map->getMedium(_element.getID()); + MPL::VariableArray variables; + ParameterLib::SpatialPosition x_position; + x_position.setElementID(_element.getID()); + auto const& solid_phase = medium->phase("Solid"); + + int const n_integration_points = _integration_method.getNumberOfPoints(); + for (int ip = 0; ip < n_integration_points; ip++) + { + x_position.setIntegrationPoint(ip); + auto const& N_p = _ip_data[ip].N_p; + auto const& N_u = _ip_data[ip].N_u; + auto const& dNdx_u = _ip_data[ip].dNdx_u; + double T_ip; + NumLib::shapeFunctionInterpolate(T, N_p, T_ip); + double T_dot_ip; + NumLib::shapeFunctionInterpolate(T_dot, N_p, T_dot_ip); + variables[static_cast<int>(MPL::Variable::temperature)] = T_ip; + + auto const x_coord = + NumLib::interpolateXCoordinate<ShapeFunctionDisplacement, + ShapeMatricesTypeDisplacement>( + _element, N_u); + auto const B = + LinearBMatrix::computeBMatrix<DisplacementDim, + ShapeFunctionDisplacement::NPOINTS, + typename BMatricesType::BMatrixType>( + dNdx_u, N_u, x_coord, _is_axially_symmetric); + + auto& eps = _ip_data[ip].eps; + eps.noalias() = B * u; + // Consider anisotropic thermal expansion. + // Read in 3x3 tensor. 2D case also requires expansion coeff. for z- + // component. + Eigen::Matrix<double, 3, + 3> const solid_linear_thermal_expansion_coefficient = + MaterialPropertyLib::formEigenTensor<3>( + solid_phase + .property( + MaterialPropertyLib::PropertyType::thermal_expansivity) + .value(variables, x_position, t, dt)); + + MathLib::KelvinVector::KelvinVectorType<DisplacementDim> const + dthermal_strain = + MathLib::KelvinVector::tensorToKelvin<DisplacementDim>( + solid_linear_thermal_expansion_coefficient) * + T_dot_ip * dt; + + variables[static_cast<int>(MPL::Variable::mechanical_strain)] + .emplace<MathLib::KelvinVector::KelvinVectorType<DisplacementDim>>( + eps - dthermal_strain); + + _ip_data[ip].updateConstitutiveRelation(variables, t, x_position, dt, + T_ip); + } +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +void ThermoRichardsMechanicsLocalAssembler<ShapeFunctionDisplacement, + ShapeFunctionPressure, + IntegrationMethod, DisplacementDim>:: + computeSecondaryVariableConcrete(double const t, double const dt, + Eigen::VectorXd const& local_x, + Eigen::VectorXd const& local_x_dot) +{ + auto const T = + local_x.template segment<temperature_size>(temperature_index); + auto const p_L = local_x.template segment<pressure_size>(pressure_index); + auto const u = + local_x.template segment<displacement_size>(displacement_index); + + auto const T_dot = + local_x_dot.template segment<temperature_size>(temperature_index); + auto const p_L_dot = + local_x_dot.template segment<pressure_size>(pressure_index); + auto const u_dot = + local_x_dot.template segment<displacement_size>(displacement_index); + + auto const& identity2 = MathLib::KelvinVector::Invariants< + MathLib::KelvinVector::KelvinVectorDimensions<DisplacementDim>::value>:: + identity2; + + auto const& medium = _process_data.media_map->getMedium(_element.getID()); + auto const& liquid_phase = medium->phase("AqueousLiquid"); + auto const& solid_phase = medium->phase("Solid"); + MPL::VariableArray variables; + MPL::VariableArray variables_prev; + + ParameterLib::SpatialPosition x_position; + x_position.setElementID(_element.getID()); + + unsigned const n_integration_points = + _integration_method.getNumberOfPoints(); + + double saturation_avg = 0; + double porosity_avg = 0; + + using KV = MathLib::KelvinVector::KelvinVectorType<DisplacementDim>; + KV sigma_avg = KV::Zero(); + + for (unsigned ip = 0; ip < n_integration_points; ip++) + { + x_position.setIntegrationPoint(ip); + auto const& N_p = _ip_data[ip].N_p; + auto const& N_u = _ip_data[ip].N_u; + auto const& dNdx_u = _ip_data[ip].dNdx_u; + + auto const x_coord = + NumLib::interpolateXCoordinate<ShapeFunctionDisplacement, + ShapeMatricesTypeDisplacement>( + _element, N_u); + auto const B = + LinearBMatrix::computeBMatrix<DisplacementDim, + ShapeFunctionDisplacement::NPOINTS, + typename BMatricesType::BMatrixType>( + dNdx_u, N_u, x_coord, _is_axially_symmetric); + + double T_ip; + NumLib::shapeFunctionInterpolate(T, N_p, T_ip); + double T_dot_ip; + NumLib::shapeFunctionInterpolate(T_dot, N_p, T_dot_ip); + + double p_cap_ip; + NumLib::shapeFunctionInterpolate(-p_L, N_p, p_cap_ip); + + double p_cap_dot_ip; + NumLib::shapeFunctionInterpolate(-p_L_dot, N_p, p_cap_dot_ip); + + variables[static_cast<int>(MPL::Variable::capillary_pressure)] = + p_cap_ip; + variables[static_cast<int>(MPL::Variable::phase_pressure)] = -p_cap_ip; + + variables[static_cast<int>(MPL::Variable::temperature)] = T_ip; + + auto& eps = _ip_data[ip].eps; + auto& S_L = _ip_data[ip].saturation; + auto const S_L_prev = _ip_data[ip].saturation_prev; + S_L = medium->property(MPL::PropertyType::saturation) + .template value<double>(variables, x_position, t, dt); + variables[static_cast<int>(MPL::Variable::liquid_saturation)] = S_L; + variables_prev[static_cast<int>(MPL::Variable::liquid_saturation)] = + S_L_prev; + + auto const chi = [medium, x_position, t, dt](double const S_L) { + MPL::VariableArray variables; + variables.fill(std::numeric_limits<double>::quiet_NaN()); + variables[static_cast<int>(MPL::Variable::liquid_saturation)] = S_L; + return medium->property(MPL::PropertyType::bishops_effective_stress) + .template value<double>(variables, x_position, t, dt); + }; + double const chi_S_L = chi(S_L); + double const chi_S_L_prev = chi(S_L_prev); + + auto const alpha = + medium->property(MPL::PropertyType::biot_coefficient) + .template value<double>(variables, x_position, t, dt); + + auto const C_el = _ip_data[ip].computeElasticTangentStiffness( + t, x_position, dt, T_ip); + + auto const beta_SR = + (1 - alpha) / + _ip_data[ip].solid_material.getBulkModulus(t, x_position, &C_el); + variables[static_cast<int>(MPL::Variable::grain_compressibility)] = + beta_SR; + + variables[static_cast<int>(MPL::Variable::effective_pore_pressure)] = + -chi_S_L * p_cap_ip; + variables_prev[static_cast<int>( + MPL::Variable::effective_pore_pressure)] = + -chi_S_L_prev * (p_cap_ip - p_cap_dot_ip * dt); + + // Set volumetric strain rate for the general case without swelling. + variables[static_cast<int>(MPL::Variable::volumetric_strain)] + .emplace<double>(identity2.transpose() * B * u); + variables_prev[static_cast<int>(MPL::Variable::volumetric_strain)] + .emplace<double>(identity2.transpose() * B * (u - u_dot * dt)); + + auto& phi = _ip_data[ip].porosity; + { // Porosity update + variables_prev[static_cast<int>(MPL::Variable::porosity)] = + _ip_data[ip].porosity_prev; + phi = medium->property(MPL::PropertyType::porosity) + .template value<double>(variables, variables_prev, + x_position, t, dt); + variables[static_cast<int>(MPL::Variable::porosity)] = phi; + } + + // Swelling and possibly volumetric strain rate update. + auto& sigma_sw = _ip_data[ip].sigma_sw; + { + auto const& sigma_sw_prev = _ip_data[ip].sigma_sw_prev; + + // If there is swelling, compute it. Update volumetric strain rate, + // s.t. it corresponds to the mechanical part only. + sigma_sw = sigma_sw_prev; + if (solid_phase.hasProperty( + MPL::PropertyType::swelling_stress_rate)) + { + using DimMatrix = Eigen::Matrix<double, 3, 3>; + auto const sigma_sw_dot = + MathLib::KelvinVector::tensorToKelvin<DisplacementDim>( + solid_phase + .property(MPL::PropertyType::swelling_stress_rate) + .template value<DimMatrix>( + variables, variables_prev, x_position, t, dt)); + sigma_sw += sigma_sw_dot * dt; + + // !!! Misusing volumetric strain for mechanical volumetric + // strain just to update the transport porosity !!! + std::get<double>(variables[static_cast<int>( + MPL::Variable::volumetric_strain)]) += + identity2.transpose() * C_el.inverse() * sigma_sw; + std::get<double>(variables_prev[static_cast<int>( + MPL::Variable::volumetric_strain)]) += + identity2.transpose() * C_el.inverse() * sigma_sw_prev; + } + + if (solid_phase.hasProperty(MPL::PropertyType::transport_porosity)) + { + variables_prev[static_cast<int>( + MPL::Variable::transport_porosity)] = + _ip_data[ip].transport_porosity_prev; + + _ip_data[ip].transport_porosity = + solid_phase.property(MPL::PropertyType::transport_porosity) + .template value<double>(variables, variables_prev, + x_position, t, dt); + variables[static_cast<int>(MPL::Variable::transport_porosity)] = + _ip_data[ip].transport_porosity; + } + else + { + variables[static_cast<int>(MPL::Variable::transport_porosity)] = + phi; + } + } + auto const mu = + liquid_phase.property(MPL::PropertyType::viscosity) + .template value<double>(variables, x_position, t, dt); + auto const rho_LR = + liquid_phase.property(MPL::PropertyType::density) + .template value<double>(variables, x_position, t, dt); + + // For stress dependent permeability. + variables[static_cast<int>(MPL::Variable::stress)] + .emplace<SymmetricTensor>( + MathLib::KelvinVector::kelvinVectorToSymmetricTensor( + (_ip_data[ip].sigma_eff + + alpha * chi_S_L * identity2 * p_cap_ip) + .eval())); + auto const K_intrinsic = MPL::formEigenTensor<DisplacementDim>( + medium->property(MPL::PropertyType::permeability) + .value(variables, x_position, t, dt)); + + double const k_rel = + medium->property(MPL::PropertyType::relative_permeability) + .template value<double>(variables, x_position, t, dt); + + GlobalDimMatrixType const K_over_mu = k_rel * K_intrinsic / mu; + + auto const& sigma_eff = _ip_data[ip].sigma_eff; + double const p_FR = -chi_S_L * p_cap_ip; + // p_SR + variables[static_cast<int>(MPL::Variable::solid_grain_pressure)] = + p_FR - (sigma_eff + sigma_sw).dot(identity2) / (3 * (1 - phi)); + auto const rho_SR = + solid_phase.property(MPL::PropertyType::density) + .template value<double>(variables, x_position, t, dt); + _ip_data[ip].dry_density_solid = (1 - phi) * rho_SR; + + eps.noalias() = B * u; + + // Consider anisotropic thermal expansion. + // Read in 3x3 tensor. 2D case also requires expansion coeff. for z- + // component. + Eigen::Matrix<double, 3, + 3> const solid_linear_thermal_expansion_coefficient = + MaterialPropertyLib::formEigenTensor<3>( + solid_phase + .property( + MaterialPropertyLib::PropertyType::thermal_expansivity) + .value(variables, x_position, t, dt)); + + MathLib::KelvinVector::KelvinVectorType<DisplacementDim> const + dthermal_strain = + MathLib::KelvinVector::tensorToKelvin<DisplacementDim>( + solid_linear_thermal_expansion_coefficient) * + T_dot_ip * dt; + + variables[static_cast<int>(MPL::Variable::mechanical_strain)] + .emplace<MathLib::KelvinVector::KelvinVectorType<DisplacementDim>>( + eps - dthermal_strain); + + _ip_data[ip].updateConstitutiveRelation(variables, t, x_position, dt, + T_ip); + + auto const& b = _process_data.specific_body_force; + + // Compute the velocity + auto const& dNdx_p = _ip_data[ip].dNdx_p; + _ip_data[ip].v_darcy.noalias() = + -K_over_mu * dNdx_p * p_L + rho_LR * K_over_mu * b; + + saturation_avg += S_L; + porosity_avg += phi; + sigma_avg += sigma_eff; + } + saturation_avg /= n_integration_points; + porosity_avg /= n_integration_points; + sigma_avg /= n_integration_points; + + (*_process_data.element_saturation)[_element.getID()] = saturation_avg; + (*_process_data.element_porosity)[_element.getID()] = porosity_avg; + + Eigen::Map<KV>(&(*_process_data.element_stresses)[_element.getID() * + KV::RowsAtCompileTime]) = + MathLib::KelvinVector::kelvinVectorToSymmetricTensor(sigma_avg); + + NumLib::interpolateToHigherOrderNodes< + ShapeFunctionPressure, typename ShapeFunctionDisplacement::MeshElement, + DisplacementDim>(_element, _is_axially_symmetric, p_L, + *_process_data.pressure_interpolated); + NumLib::interpolateToHigherOrderNodes< + ShapeFunctionPressure, typename ShapeFunctionDisplacement::MeshElement, + DisplacementDim>(_element, _is_axially_symmetric, T, + *_process_data.temperature_interpolated); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +unsigned ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::getNumberOfIntegrationPoints() const +{ + return _integration_method.getNumberOfPoints(); +} + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +typename MaterialLib::Solids::MechanicsBase< + DisplacementDim>::MaterialStateVariables const& +ThermoRichardsMechanicsLocalAssembler< + ShapeFunctionDisplacement, ShapeFunctionPressure, IntegrationMethod, + DisplacementDim>::getMaterialStateVariablesAt(unsigned integration_point) + const +{ + return *_ip_data[integration_point].material_state_variables; +} +} // namespace ThermoRichardsMechanics +} // namespace ProcessLib diff --git a/ProcessLib/ThermoRichardsMechanics/ThermoRichardsMechanicsFEM.h b/ProcessLib/ThermoRichardsMechanics/ThermoRichardsMechanicsFEM.h new file mode 100644 index 00000000000..a0f1cfddc24 --- /dev/null +++ b/ProcessLib/ThermoRichardsMechanics/ThermoRichardsMechanicsFEM.h @@ -0,0 +1,258 @@ +/** + * \file + * \copyright + * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#pragma once + +#include <memory> +#include <vector> + +#include "IntegrationPointData.h" +#include "MaterialLib/SolidModels/LinearElasticIsotropic.h" +#include "MathLib/KelvinVector.h" +#include "MathLib/LinAlg/Eigen/EigenMapTools.h" +#include "NumLib/DOF/DOFTableUtil.h" +#include "NumLib/Fem/InitShapeMatrices.h" +#include "NumLib/Fem/ShapeMatrixPolicy.h" +#include "ParameterLib/Parameter.h" +#include "ProcessLib/Deformation/BMatrixPolicy.h" +#include "ProcessLib/Deformation/LinearBMatrix.h" +#include "ProcessLib/LocalAssemblerTraits.h" +#include "ProcessLib/RichardsMechanics/LocalAssemblerInterface.h" +#include "ThermoRichardsMechanicsProcessData.h" + +namespace ProcessLib +{ +namespace ThermoRichardsMechanics +{ +namespace MPL = MaterialPropertyLib; + +/// Used for the extrapolation of the integration point values. It is ordered +/// (and stored) by integration points. +template <typename ShapeMatrixType> +struct SecondaryData +{ + std::vector<ShapeMatrixType, Eigen::aligned_allocator<ShapeMatrixType>> N_u; +}; + +template <typename ShapeFunctionDisplacement, typename ShapeFunctionPressure, + typename IntegrationMethod, int DisplacementDim> +class ThermoRichardsMechanicsLocalAssembler + : public RichardsMechanics::LocalAssemblerInterface<DisplacementDim> +{ +public: + using ShapeMatricesTypeDisplacement = + ShapeMatrixPolicyType<ShapeFunctionDisplacement, DisplacementDim>; + // Note: temperature variable uses the same shape functions as that are used + // by pressure variable. + using ShapeMatricesTypePressure = + ShapeMatrixPolicyType<ShapeFunctionPressure, DisplacementDim>; + + using GlobalDimMatrixType = + typename ShapeMatricesTypePressure::GlobalDimMatrixType; + using GlobalDimVectorType = + typename ShapeMatricesTypePressure::GlobalDimVectorType; + + using BMatricesType = + BMatrixPolicyType<ShapeFunctionDisplacement, DisplacementDim>; + using KelvinVectorType = typename BMatricesType::KelvinVectorType; + + using IpData = + IntegrationPointData<BMatricesType, ShapeMatricesTypeDisplacement, + ShapeMatricesTypePressure, DisplacementDim, + ShapeFunctionDisplacement::NPOINTS>; + + static int const KelvinVectorSize = + MathLib::KelvinVector::KelvinVectorDimensions<DisplacementDim>::value; + using Invariants = MathLib::KelvinVector::Invariants<KelvinVectorSize>; + + using SymmetricTensor = Eigen::Matrix<double, KelvinVectorSize, 1>; + + ThermoRichardsMechanicsLocalAssembler( + ThermoRichardsMechanicsLocalAssembler const&) = delete; + ThermoRichardsMechanicsLocalAssembler( + ThermoRichardsMechanicsLocalAssembler&&) = delete; + + ThermoRichardsMechanicsLocalAssembler( + MeshLib::Element const& e, + std::size_t const /*local_matrix_size*/, + bool const is_axially_symmetric, + unsigned const integration_order, + ThermoRichardsMechanicsProcessData<DisplacementDim>& process_data); + + /// \return the number of read integration points. + std::size_t setIPDataInitialConditions( + std::string const& name, + double const* values, + int const integration_order) override; + + void setInitialConditionsConcrete(std::vector<double> const& local_x, + double const t, + bool const use_monolithic_scheme, + int const process_id) override; + + void assembleWithJacobian(double const t, double const dt, + std::vector<double> const& local_x, + std::vector<double> const& local_xdot, + const double /*dxdot_dx*/, const double /*dx_dx*/, + std::vector<double>& /*local_M_data*/, + std::vector<double>& /*local_K_data*/, + std::vector<double>& local_rhs_data, + std::vector<double>& local_Jac_data) override; + + void initializeConcrete() override + { + unsigned const n_integration_points = + _integration_method.getNumberOfPoints(); + + for (unsigned ip = 0; ip < n_integration_points; ip++) + { + auto& ip_data = _ip_data[ip]; + + /// Set initial stress from parameter. + if (_process_data.initial_stress != nullptr) + { + ParameterLib::SpatialPosition const x_position{ + boost::none, _element.getID(), ip, + MathLib::Point3d(NumLib::interpolateCoordinates< + ShapeFunctionDisplacement, + ShapeMatricesTypeDisplacement>( + _element, ip_data.N_u))}; + + ip_data.sigma_eff = + MathLib::KelvinVector::symmetricTensorToKelvinVector< + DisplacementDim>((*_process_data.initial_stress)( + std::numeric_limits< + double>::quiet_NaN() /* time independent */, + x_position)); + } + + ip_data.pushBackState(); + } + } + + void postTimestepConcrete(Eigen::VectorXd const& /*local_x*/, + double const /*t*/, + double const /*dt*/) override + { + unsigned const n_integration_points = + _integration_method.getNumberOfPoints(); + + for (unsigned ip = 0; ip < n_integration_points; ip++) + { + _ip_data[ip].pushBackState(); + } + } + + void computeSecondaryVariableConcrete( + double const t, double const dt, Eigen::VectorXd const& local_x, + Eigen::VectorXd const& local_x_dot) override; + + void postNonLinearSolverConcrete(std::vector<double> const& local_x, + std::vector<double> const& local_xdot, + double const t, double const dt, + bool const use_monolithic_scheme, + int const process_id) override; + + Eigen::Map<const Eigen::RowVectorXd> getShapeMatrix( + const unsigned integration_point) const override + { + auto const& N_u = _secondary_data.N_u[integration_point]; + + // assumes N is stored contiguously in memory + return Eigen::Map<const Eigen::RowVectorXd>(N_u.data(), N_u.size()); + } + + std::vector<double> getSigma() const override; + + std::vector<double> const& getIntPtDarcyVelocity( + const double t, + std::vector<GlobalVector*> const& x, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table, + std::vector<double>& cache) const override; + + std::vector<double> getSaturation() const override; + std::vector<double> const& getIntPtSaturation( + const double t, + std::vector<GlobalVector*> const& x, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table, + std::vector<double>& cache) const override; + + std::vector<double> getPorosity() const override; + std::vector<double> const& getIntPtPorosity( + const double t, + std::vector<GlobalVector*> const& x, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table, + std::vector<double>& cache) const override; + + std::vector<double> getTransportPorosity() const override; + std::vector<double> const& getIntPtTransportPorosity( + const double t, + std::vector<GlobalVector*> const& x, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table, + std::vector<double>& cache) const override; + + std::vector<double> const& getIntPtSigma( + const double t, + std::vector<GlobalVector*> const& x, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table, + std::vector<double>& cache) const override; + + std::vector<double> getSwellingStress() const override; + std::vector<double> const& getIntPtSwellingStress( + const double t, + std::vector<GlobalVector*> const& x, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table, + std::vector<double>& cache) const override; + + std::vector<double> getEpsilon() const override; + std::vector<double> const& getIntPtEpsilon( + const double t, + std::vector<GlobalVector*> const& x, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table, + std::vector<double>& cache) const override; + + std::vector<double> const& getIntPtDryDensitySolid( + const double t, + std::vector<GlobalVector*> const& x, + std::vector<NumLib::LocalToGlobalIndexMap const*> const& dof_table, + std::vector<double>& cache) const override; + +private: + unsigned getNumberOfIntegrationPoints() const override; + + typename MaterialLib::Solids::MechanicsBase< + DisplacementDim>::MaterialStateVariables const& + getMaterialStateVariablesAt(unsigned integration_point) const override; + +private: + ThermoRichardsMechanicsProcessData<DisplacementDim>& _process_data; + + std::vector<IpData, Eigen::aligned_allocator<IpData>> _ip_data; + + IntegrationMethod _integration_method; + MeshLib::Element const& _element; + bool const _is_axially_symmetric; + SecondaryData< + typename ShapeMatricesTypeDisplacement::ShapeMatrices::ShapeType> + _secondary_data; + + static const int temperature_index = 0; + static const int temperature_size = ShapeFunctionPressure::NPOINTS; + static const int pressure_index = temperature_size; + static const int pressure_size = ShapeFunctionPressure::NPOINTS; + static const int displacement_index = 2 * ShapeFunctionPressure::NPOINTS; + static const int displacement_size = + ShapeFunctionDisplacement::NPOINTS * DisplacementDim; +}; + +} // namespace ThermoRichardsMechanics +} // namespace ProcessLib + +#include "ThermoRichardsMechanicsFEM-impl.h" -- GitLab