/******************************************************************************/
/* LYNX, a MOOSE-based application */
/* */
/* Copyright (C) 2017 by Antoine B. Jacquey and Mauro Cacace */
/* GFZ Potsdam, German Research Centre for Geosciences */
/* */
/* This program is free software: you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation, either version 3 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see */
/******************************************************************************/
#include "LynxSolidMomentum.h"
#include "MooseMesh.h"
#include "MooseVariable.h"
#include "Assembly.h"
#include "SystemBase.h"
#include "libmesh/quadrature.h"
registerMooseObject("LynxApp", LynxSolidMomentum);
template <>
InputParameters
validParams()
{
InputParameters params = validParams();
params.addClassDescription("Solid momentum kernel.");
params.addRequiredCoupledVar(
"displacements", "The string of displacements variables suitable for the problem statement.");
params.addCoupledVar("temperature", "The temperature variable.");
params.addCoupledVar("fluid_pressure", "The fluid pressure variable.");
params.addCoupledVar("lithostatic_pressure", "The lithostatic pressure variable.");
params.addCoupledVar("dynamic_pressure", "The dynamic pressure variable.");
params.addCoupledVar("damage", "The damage intensity variable.");
params.set("use_displaced_mesh") = false;
params.addRequiredParam("component",
"An integer corresponding to the direction "
"the variable this kernel acts in (0 for x, "
"1 for y, 2 for z).");
params.addParam(
"volumetric_locking_correction", false, "Flag to correct volumetric locking");
return params;
}
LynxSolidMomentum::LynxSolidMomentum(const InputParameters & parameters)
: DerivativeMaterialInterface(parameters),
_ndisp(coupledComponents("displacements")),
_coupled_temp(isCoupled("temperature")),
_coupled_pf(isCoupled("fluid_pressure")),
_pf(_coupled_pf ? coupledValue("fluid_pressure") : _zero),
_coupled_plith(isCoupled("lithostatic_pressure")),
_coupled_pdyn(isCoupled("dynamic_pressure")),
_coupled_dam(isCoupled("damage")),
_component(getParam("component")),
_vol_locking_correction(getParam("volumetric_locking_correction")),
_stress(getMaterialProperty("stress")),
_biot(getDefaultMaterialProperty("biot_coefficient")),
_tangent_modulus(getMaterialProperty("tangent_modulus")),
_dthermal_strain_dtemp(getDefaultMaterialProperty("dthermal_strain_dtemp")),
_gravity(getDefaultMaterialProperty("gravity_vector")),
_rho_b(getDefaultMaterialProperty("bulk_density")),
_drho_dtemp(getDefaultMaterialProperty("drho_dtemp")),
_drho_dev(getDefaultMaterialProperty("drho_dev")),
_dstress_ddamage(getDefaultMaterialProperty("dstress_ddamage")),
_avg_grad_test(_test.size(), std::vector(3, 0.0)),
_avg_grad_phi(_phi.size(), std::vector(3, 0.0)),
_disp_var(_ndisp),
_temp_var(_coupled_temp ? coupled("temperature") : 0),
_pf_var(_coupled_pf ? coupled("fluid_pressure") : 0),
_plith_var(_coupled_plith ? coupled("lithostatic_pressure") : 0),
_pdyn_var(_coupled_pdyn ? coupled("dynamic_pressure") : 0),
_damage_var(_coupled_dam ? coupled("damage") : 0)
{
// Checking for consistency between mesh size and length of the provided displacements vector
if (_ndisp != _mesh.dimension())
mooseError("The number of displacement variables supplied must match the mesh dimension.");
for (unsigned int i = 0; i < _ndisp; ++i)
_disp_var[i] = coupled("displacements", i);
}
/******************************************************************************/
/* RESIDUALS */
/******************************************************************************/
void
LynxSolidMomentum::computeResidual()
{
DenseVector & re = _assembly.residualBlock(_var.number());
_local_re.resize(re.size());
_local_re.zero();
if (_vol_locking_correction)
computeAverageGradientTest();
precalculateResidual();
for (_i = 0; _i < _test.size(); ++_i)
for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
_local_re(_i) += _JxW[_qp] * _coord[_qp] * computeQpResidual();
re += _local_re;
if (_has_save_in)
{
Threads::spin_mutex::scoped_lock lock(Threads::spin_mtx);
for (const auto & var : _save_in)
var->sys().solution().add_vector(_local_re, var->dofIndices());
}
}
Real
LynxSolidMomentum::computeQpResidual()
{
RealVectorValue stress_row = _stress[_qp].row(_component);
stress_row(_component) -= _biot[_qp] * _pf[_qp];
RealVectorValue grav_term = -_rho_b[_qp] * _gravity[_qp];
Real residual = stress_row * _grad_test[_i][_qp] + grav_term(_component) * _test[_i][_qp];
if (_vol_locking_correction)
residual += (_stress[_qp].trace() / 3.0 + _biot[_qp] * _pf[_qp]) *
(_avg_grad_test[_i][_component] - _grad_test[_i][_qp](_component));
return residual;
}
/******************************************************************************/
/* JACOBIAN */
/******************************************************************************/
void
LynxSolidMomentum::computeJacobian()
{
if (_vol_locking_correction)
{
computeAverageGradientTest();
computeAverageGradientPhi();
}
Kernel::computeJacobian();
}
Real
LynxSolidMomentum::computeQpJacobian()
{
Real jacobian = 0.0;
jacobian += elasticJacobian(
_tangent_modulus[_qp], _component, _component, _grad_test[_i][_qp], _grad_phi[_j][_qp]);
RealVectorValue dgrav_term = -_drho_dev[_qp] * _gravity[_qp];
jacobian += dgrav_term(_component) * _test[_i][_qp] * _grad_phi[_j][_qp](_component);
if (_vol_locking_correction)
{
Real sum_C3x3 = _tangent_modulus[_qp].sum3x3();
RealGradient sum_C3x1 = _tangent_modulus[_qp].sum3x1();
// jacobian = Bbar^T_i * C * Bbar_j where Bbar = B + Bvol
// jacobian = B^T_i * C * B_j + Bvol^T_i * C * Bvol_j + Bvol^T_i * C * B_j + B^T_i * C * Bvol_j
// Bvol^T_i * C * Bvol_j
jacobian += sum_C3x3 * (_avg_grad_test[_i][_component] - _grad_test[_i][_qp](_component)) *
(_avg_grad_phi[_j][_component] - _grad_phi[_j][_qp](_component)) / 9.0;
// B^T_i * C * Bvol_j
jacobian += sum_C3x1(_component) * _grad_test[_i][_qp](_component) *
(_avg_grad_phi[_j][_component] - _grad_phi[_j][_qp](_component)) / 3.0;
// Bvol^T_i * C * B_j
RankTwoTensor phi;
if (_component == 0)
{
phi(0, 0) = _grad_phi[_j][_qp](0);
phi(0, 1) = phi(1, 0) = _grad_phi[_j][_qp](1);
phi(0, 2) = phi(2, 0) = _grad_phi[_j][_qp](2);
}
else if (_component == 1)
{
phi(1, 1) = _grad_phi[_j][_qp](1);
phi(0, 1) = phi(1, 0) = _grad_phi[_j][_qp](0);
phi(1, 2) = phi(2, 1) = _grad_phi[_j][_qp](2);
}
else if (_component == 2)
{
phi(2, 2) = _grad_phi[_j][_qp](2);
phi(0, 2) = phi(2, 0) = _grad_phi[_j][_qp](0);
phi(1, 2) = phi(2, 1) = _grad_phi[_j][_qp](1);
}
jacobian += (_tangent_modulus[_qp] * phi).trace() *
(_avg_grad_test[_i][_component] - _grad_test[_i][_qp](_component)) / 3.0;
}
return jacobian;
}
/******************************************************************************/
/* OFF-DIAG JACOBIAN */
/******************************************************************************/
void
LynxSolidMomentum::computeOffDiagJacobian(MooseVariableFEBase & jvar)
{
if (_vol_locking_correction)
{
computeAverageGradientPhi();
computeAverageGradientTest();
}
Kernel::computeOffDiagJacobian(jvar);
}
Real
LynxSolidMomentum::computeQpOffDiagJacobian(unsigned int jvar)
{
// // off-diagonal Jacobian with respect to a coupled displacement component
for (unsigned int coupled_component = 0; coupled_component < _ndisp; ++coupled_component)
if (jvar == _disp_var[coupled_component])
{
Real jacobian = 0.0;
jacobian += elasticJacobian(_tangent_modulus[_qp],
_component,
coupled_component,
_grad_test[_i][_qp],
_grad_phi[_j][_qp]);
RealVectorValue dgrav_term = -_drho_dev[_qp] * _gravity[_qp];
jacobian += dgrav_term(_component) * _test[_i][_qp] * _grad_phi[_j][_qp](coupled_component);
if (_vol_locking_correction)
{
const Real sum_C3x3 = _tangent_modulus[_qp].sum3x3();
const RealGradient sum_C3x1 = _tangent_modulus[_qp].sum3x1();
// jacobian = Bbar^T_i * C * Bbar_j where Bbar = B + Bvol
// jacobian = B^T_i * C * B_j + Bvol^T_i * C * Bvol_j + Bvol^T_i * C * B_j + B^T_i * C *
// Bvol_j
// Bvol^T_i * C * Bvol_j
jacobian += sum_C3x3 * (_avg_grad_test[_i][_component] - _grad_test[_i][_qp](_component)) *
(_avg_grad_phi[_j][coupled_component] - _grad_phi[_j][_qp](coupled_component)) /
9.0;
// B^T_i * C * Bvol_j
jacobian += sum_C3x1(_component) * _grad_test[_i][_qp](_component) *
(_avg_grad_phi[_j][coupled_component] - _grad_phi[_j][_qp](coupled_component)) /
3.0;
// Bvol^T_i * C * B_i
RankTwoTensor phi;
for (unsigned int i = 0; i < 3; ++i)
phi(coupled_component, i) = _grad_phi[_j][_qp](i);
jacobian += (_tangent_modulus[_qp] * phi).trace() *
(_avg_grad_test[_i][_component] - _grad_test[_i][_qp](_component)) / 3.0;
}
return jacobian;
}
// if (_coupled_temp && jvar == _temp_var)
// return -(_elasticity_tensor[_qp] * _dthermal_strain_dtemp[_qp] *
// _grad_test[_i][_qp])(_component)*_phi[_j][_qp];
if (_coupled_pf && jvar == _pf_var)
return -_biot[_qp] * _phi[_j][_qp] * _grad_test[_i][_qp](_component);
if (_coupled_plith && jvar == _plith_var)
return -_phi[_j][_qp] * _grad_test[_i][_qp](_component);
if (_coupled_pdyn && jvar == _pdyn_var)
return -_phi[_j][_qp] * _grad_test[_i][_qp](_component);
if (_coupled_dam && jvar == _damage_var)
return _dstress_ddamage[_qp].row(_component) * _phi[_j][_qp] * _grad_test[_i][_qp];
return 0.0;
}
Real
LynxSolidMomentum::elasticJacobian(const RankFourTensor & jacobian_r4t,
unsigned int i,
unsigned int k,
const RealGradient & grad_test,
const RealGradient & grad_phi)
{
Real sum = 0.0;
for (unsigned int j = 0; j < LIBMESH_DIM; ++j)
for (unsigned int l = 0; l < LIBMESH_DIM; ++l)
sum += jacobian_r4t(i, j, k, l) * grad_phi(l) * grad_test(j);
return sum;
}
void
LynxSolidMomentum::computeAverageGradientTest()
{
// Calculate volume averaged value of shape function derivative
_avg_grad_test.resize(_test.size());
for (_i = 0; _i < _test.size(); ++_i)
{
_avg_grad_test[_i].resize(3);
_avg_grad_test[_i][_component] = 0.0;
for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
_avg_grad_test[_i][_component] += _grad_test[_i][_qp](_component) * _JxW[_qp] * _coord[_qp];
_avg_grad_test[_i][_component] /= _current_elem_volume;
}
}
void
LynxSolidMomentum::computeAverageGradientPhi()
{
// Calculate volume average derivatives for phi
_avg_grad_phi.resize(_phi.size());
for (_i = 0; _i < _phi.size(); ++_i)
{
_avg_grad_phi[_i].resize(3);
for (unsigned int component = 0; component < _mesh.dimension(); ++component)
{
_avg_grad_phi[_i][component] = 0.0;
for (_qp = 0; _qp < _qrule->n_points(); ++_qp)
_avg_grad_phi[_i][component] += _grad_phi[_i][_qp](component) * _JxW[_qp] * _coord[_qp];
_avg_grad_phi[_i][component] /= _current_elem_volume;
}
}
}