LCOV - code coverage report
Current view: top level - src/materials/abaqus - AbaqusUMATStress.C (source / functions) Hit Total Coverage
Test: idaholab/moose solid_mechanics: f45d79 Lines: 183 184 99.5 %
Date: 2025-07-25 05:00:39 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "AbaqusUMATStress.h"
      11             : #include "AnalysisStepUserObject.h"
      12             : #include "Factory.h"
      13             : #include "MooseMesh.h"
      14             : #include "RankTwoTensor.h"
      15             : #include "RankFourTensor.h"
      16             : #include "libmesh/int_range.h"
      17             : #include <string.h>
      18             : #include <algorithm>
      19             : 
      20             : #define QUOTE(macro) stringifyName(macro)
      21             : 
      22             : registerMooseObject("SolidMechanicsApp", AbaqusUMATStress);
      23             : 
      24             : InputParameters
      25        1312 : AbaqusUMATStress::validParams()
      26             : {
      27        1312 :   InputParameters params = ComputeGeneralStressBase::validParams();
      28        1312 :   params.addClassDescription("Coupling material to use Abaqus UMAT models in MOOSE");
      29        2624 :   params.addRequiredParam<FileName>(
      30             :       "plugin", "The path to the compiled dynamic library for the plugin you want to use");
      31        2624 :   params.addRequiredParam<bool>(
      32             :       "use_one_based_indexing",
      33             :       "Parameter to control whether indexing for element and integration points as presented to "
      34             :       "UMAT models is based on 1 (true) or 0 (false). This does not affect internal MOOSE "
      35             :       "numbering. The option to use 0-based numbering is deprecated and will be removed soon.");
      36        2624 :   params.addRequiredParam<std::vector<Real>>(
      37             :       "constant_properties", "Constant mechanical and thermal material properties (PROPS)");
      38        2624 :   params.addRequiredParam<unsigned int>("num_state_vars",
      39             :                                         "The number of state variables this UMAT is going to use");
      40        2624 :   params.addCoupledVar("temperature", 0.0, "Coupled temperature");
      41        2624 :   params.addCoupledVar("external_fields",
      42             :                        "The external fields that can be used in the UMAT subroutine");
      43        2624 :   params.addParam<std::vector<MaterialPropertyName>>("external_properties", {}, "");
      44        2624 :   params.addParam<MooseEnum>("decomposition_method",
      45        2624 :                              ComputeFiniteStrain::decompositionType(),
      46             :                              "Method to calculate the strain kinematics.");
      47        2624 :   params.addParam<bool>(
      48             :       "use_displaced_mesh",
      49        2624 :       false,
      50             :       "Whether or not this object should use the "
      51             :       "displaced mesh for computing displacements and quantities based on the deformed state.");
      52        2624 :   params.addParam<UserObjectName>(
      53             :       "analysis_step_user_object",
      54             :       "The AnalysisStepUserObject that provides times from simulation loading steps.");
      55        2624 :   params.addParam<RealVectorValue>(
      56             :       "orientation",
      57             :       "Euler angles that describe the orientation of the local material coordinate system.");
      58        1312 :   return params;
      59           0 : }
      60             : 
      61             : #ifndef METHOD
      62             : #error "The METHOD preprocessor symbol must be supplied by the build system."
      63             : #endif
      64             : 
      65         984 : AbaqusUMATStress::AbaqusUMATStress(const InputParameters & parameters)
      66             :   : ComputeGeneralStressBase(parameters),
      67        1968 :     _plugin(getParam<FileName>("plugin")),
      68        1968 :     _library(_plugin + std::string("-") + QUOTE(METHOD) + ".plugin"),
      69         984 :     _umat(_library.getFunction<umat_t>("umat_")),
      70        1968 :     _aqNSTATV(getParam<unsigned int>("num_state_vars")),
      71         984 :     _aqSTATEV(_aqNSTATV),
      72        1968 :     _aqPROPS(getParam<std::vector<Real>>("constant_properties")),
      73         984 :     _aqNPROPS(_aqPROPS.size()),
      74        1968 :     _stress_old(getMaterialPropertyOld<RankTwoTensor>(_base_name + "stress")),
      75        1968 :     _total_strain_old(getMaterialPropertyOld<RankTwoTensor>(_base_name + "total_strain")),
      76        1968 :     _strain_increment(getOptionalMaterialProperty<RankTwoTensor>(_base_name + "strain_increment")),
      77         984 :     _jacobian_mult(declareProperty<RankFourTensor>(_base_name + "Jacobian_mult")),
      78        1968 :     _Fbar(getOptionalMaterialProperty<RankTwoTensor>(_base_name + "deformation_gradient")),
      79        1968 :     _Fbar_old(getOptionalMaterialPropertyOld<RankTwoTensor>(_base_name + "deformation_gradient")),
      80         984 :     _state_var(declareProperty<std::vector<Real>>(_base_name + "state_var")),
      81        1968 :     _state_var_old(getMaterialPropertyOld<std::vector<Real>>(_base_name + "state_var")),
      82         984 :     _elastic_strain_energy(declareProperty<Real>(_base_name + "elastic_strain_energy")),
      83         984 :     _plastic_dissipation(declareProperty<Real>(_base_name + "plastic_dissipation")),
      84         984 :     _creep_dissipation(declareProperty<Real>(_base_name + "creep_dissipation")),
      85         984 :     _material_timestep(declareProperty<Real>(_base_name + "material_timestep_limit")),
      86         984 :     _rotation_increment(
      87         984 :         getOptionalMaterialProperty<RankTwoTensor>(_base_name + "rotation_increment")),
      88         984 :     _rotation_increment_old(
      89         984 :         getOptionalMaterialPropertyOld<RankTwoTensor>(_base_name + "rotation_increment")),
      90         984 :     _temperature(coupledValue("temperature")),
      91         984 :     _temperature_old(coupledValueOld("temperature")),
      92        1968 :     _external_fields(isCoupled("external_fields") ? coupledValues("external_fields")
      93             :                                                   : std::vector<const VariableValue *>{}),
      94        1968 :     _external_fields_old(isCoupled("external_fields") ? coupledValuesOld("external_fields")
      95             :                                                       : std::vector<const VariableValue *>{}),
      96         984 :     _number_external_fields(_external_fields.size()),
      97        2952 :     _external_property_names(getParam<std::vector<MaterialPropertyName>>("external_properties")),
      98         984 :     _number_external_properties(_external_property_names.size()),
      99         984 :     _external_properties(_number_external_properties),
     100         984 :     _external_properties_old(_number_external_properties),
     101        1968 :     _use_one_based_indexing(getParam<bool>("use_one_based_indexing")),
     102        1968 :     _use_orientation(isParamValid("orientation")),
     103        1968 :     _R(_use_orientation ? getParam<RealVectorValue>("orientation") : RealVectorValue(0.0)),
     104         984 :     _total_rotation(declareProperty<RankTwoTensor>("total_rotation")),
     105        1968 :     _total_rotation_old(getMaterialPropertyOld<RankTwoTensor>("total_rotation")),
     106         984 :     _decomposition_method(
     107        4920 :         getParam<MooseEnum>("decomposition_method").getEnum<ComputeFiniteStrain::DecompMethod>())
     108             : {
     109         984 :   if (!_use_one_based_indexing)
     110          36 :     mooseDeprecated(
     111             :         "AbaqusUMATStress has transitioned to 1-based indexing in the element (NOEL) and "
     112             :         "integration point (NPT) numbers to ensure maximum compatibility with legacy UMAT files. "
     113             :         "Please ensure that any new UMAT plugins using these quantities are using the correct "
     114             :         "indexing. 0-based indexing will be deprecated soon.");
     115             : 
     116             :   // get material properties
     117        1020 :   for (std::size_t i = 0; i < _number_external_properties; ++i)
     118             :   {
     119          36 :     _external_properties[i] = &getMaterialProperty<Real>(_external_property_names[i]);
     120          36 :     _external_properties_old[i] = &getMaterialPropertyOld<Real>(_external_property_names[i]);
     121             :   }
     122             : 
     123             :   // Read mesh dimension and size UMAT arrays (we always size for full 3D)
     124         984 :   _aqNTENS = 6; // Size of the stress or strain component array (NDI+NSHR)
     125         984 :   _aqNSHR = 3;  // Number of engineering shear stress components
     126         984 :   _aqNDI = 3;   // Number of direct stress components (always 3)
     127             : 
     128         984 :   _aqDDSDDT.resize(_aqNTENS);
     129         984 :   _aqDRPLDE.resize(_aqNTENS);
     130         984 :   _aqSTRAN.resize(_aqNTENS);
     131         984 :   _aqDFGRD0.resize(9);
     132         984 :   _aqDFGRD1.resize(9);
     133         984 :   _aqDROT.resize(9);
     134         984 :   _aqSTRESS.resize(_aqNTENS);
     135         984 :   _aqDDSDDE.resize(_aqNTENS * _aqNTENS);
     136         984 :   _aqDSTRAN.resize(_aqNTENS);
     137         984 :   _aqPREDEF.resize(_number_external_fields + _number_external_properties);
     138         984 :   _aqDPRED.resize(_number_external_fields + _number_external_properties);
     139         984 : }
     140             : 
     141             : void
     142         980 : AbaqusUMATStress::initialSetup()
     143             : {
     144             :   // The _Fbar, _Fbar_old, and _rotation_increment optional properties are only available when an
     145             :   // incremental strain formulation is used. If they are not avaliable we advide the user to
     146             :   // select an incremental formulation.
     147         980 :   if (!_strain_increment || !_Fbar || !_Fbar_old || !_rotation_increment)
     148           2 :     mooseError("AbaqusUMATStress '",
     149           2 :                name(),
     150             :                "': Incremental strain quantities are not available. You likely are using a total "
     151             :                "strain formulation. Specify `incremental = true` in the tensor mechanics action, "
     152             :                "or use ComputeIncrementalStrain in your input file.");
     153             : 
     154             :   // Let's automatically detect uos and identify the one we are interested in.
     155             :   // If there is more than one, we assume something is off and error out.
     156        1956 :   if (!isParamSetByUser("analysis_step_user_object"))
     157         960 :     getAnalysisStepUserObject(_fe_problem, _step_user_object, name());
     158             :   else
     159          18 :     _step_user_object = &getUserObject<AnalysisStepUserObject>("analysis_step_user_object");
     160         978 : }
     161             : 
     162             : void
     163       48864 : AbaqusUMATStress::initQpStatefulProperties()
     164             : {
     165       48864 :   ComputeGeneralStressBase::initQpStatefulProperties();
     166             : 
     167             :   // Initialize state variable vector
     168       48864 :   _state_var[_qp].resize(_aqNSTATV);
     169      228608 :   for (const auto i : make_range(_aqNSTATV))
     170      179744 :     _state_var[_qp][i] = 0.0;
     171             : 
     172             :   // Initialize total rotation tensor
     173       48864 :   _total_rotation[_qp] = _R;
     174       48864 : }
     175             : 
     176             : void
     177      458328 : AbaqusUMATStress::computeProperties()
     178             : {
     179             :   // current element "number"
     180      458328 :   _aqNOEL = _current_elem->id() + (_use_one_based_indexing ? 1 : 0);
     181             : 
     182             :   // characteristic element length
     183      458328 :   _aqCELENT = std::pow(_current_elem->volume(), 1.0 / _current_elem->dim());
     184             : 
     185      458328 :   if (!_step_user_object)
     186             :   {
     187             :     // Value of total time at the beginning of the current increment
     188      456768 :     _aqTIME[0] = _t - _dt;
     189             :   }
     190             :   else
     191             :   {
     192        1560 :     const unsigned int start_time_step = _step_user_object->getStep(_t - _dt);
     193        1560 :     const Real step_time = _step_user_object->getStartTime(start_time_step);
     194             :     // Value of step time at the beginning of the current increment
     195        1560 :     _aqTIME[0] = step_time;
     196             :   }
     197             :   // Value of total time at the beginning of the current increment
     198      458328 :   _aqTIME[1] = _t - _dt;
     199             : 
     200             :   // Time increment
     201      458328 :   _aqDTIME = _dt;
     202             : 
     203             :   // Fill unused characters with spaces (Fortran)
     204      458328 :   std::fill(_aqCMNAME, _aqCMNAME + 80, ' ');
     205      458328 :   std::memcpy(_aqCMNAME, name().c_str(), name().size());
     206             : 
     207      458328 :   ComputeGeneralStressBase::computeProperties();
     208      458328 : }
     209             : 
     210             : void
     211     2112116 : AbaqusUMATStress::computeQpStress()
     212             : {
     213             :   // C uses row-major, whereas Fortran uses column major
     214             :   // therefore, all unsymmetric matrices must be transposed before passing them to Fortran
     215     2112116 :   RankTwoTensor FBar_old_fortran = _Fbar_old[_qp].transpose();
     216     2112116 :   RankTwoTensor FBar_fortran = _Fbar[_qp].transpose();
     217             : 
     218             :   // DROT needed by UMAT will depend on kinematics and whether or not an intermediate configuration
     219             :   // is used
     220     2112116 :   RankTwoTensor DROT_fortran;
     221     2112116 :   if (_use_orientation)
     222             :   {
     223       32400 :     DROT_fortran = RankTwoTensor::Identity();
     224             :   }
     225             :   else
     226             :   {
     227     2079716 :     if (_decomposition_method == ComputeFiniteStrain::DecompMethod::HughesWinget)
     228       14368 :       DROT_fortran = _rotation_increment[_qp].transpose();
     229             :     else
     230     2065348 :       DROT_fortran = _rotation_increment_old[_qp].transpose();
     231             :   }
     232             : 
     233             :   const Real * myDFGRD0 = &(FBar_old_fortran(0, 0));
     234             :   const Real * myDFGRD1 = &(FBar_fortran(0, 0));
     235             :   const Real * myDROT = &(DROT_fortran(0, 0));
     236             : 
     237             :   // More local copies of materials so we can (optionally) rotate
     238     2112116 :   RankTwoTensor stress_old = _stress_old[_qp];
     239     2112116 :   RankTwoTensor total_strain_old = _total_strain_old[_qp];
     240     2112116 :   RankTwoTensor strain_increment = _strain_increment[_qp];
     241             : 
     242             :   // check if we need to rotate to intermediate configuration
     243     2112116 :   if (_use_orientation)
     244             :   {
     245             :     // keep track of total rotation
     246       32400 :     _total_rotation[_qp] = _rotation_increment[_qp] * _total_rotation_old[_qp];
     247             :     // rotate stress/strain/increment from reference configuration to intermediate configuration
     248       32400 :     stress_old.rotate(_total_rotation_old[_qp].transpose());
     249       32400 :     total_strain_old.rotate(_total_rotation_old[_qp].transpose());
     250             : 
     251       32400 :     if (_decomposition_method == ComputeFiniteStrain::DecompMethod::HughesWinget)
     252       25920 :       strain_increment.rotate(_total_rotation[_qp].transpose());
     253             :     else
     254        6480 :       strain_increment.rotate(_total_rotation_old[_qp].transpose());
     255             :   }
     256     2079716 :   else if (_decomposition_method == ComputeFiniteStrain::DecompMethod::HughesWinget)
     257             :     // rotate old stress to reference configuration
     258       14368 :     stress_old.rotate(_rotation_increment[_qp]);
     259             : 
     260             :   // copy because UMAT does not guarantee constness
     261    21121160 :   for (const auto i : make_range(9))
     262             :   {
     263    19009044 :     _aqDFGRD0[i] = myDFGRD0[i];
     264    19009044 :     _aqDFGRD1[i] = myDFGRD1[i];
     265    19009044 :     _aqDROT[i] = myDROT[i];
     266             :   }
     267             : 
     268             :   // Recover "old" state variables
     269     8062452 :   for (const auto i : make_range(_aqNSTATV))
     270     5950336 :     _aqSTATEV[i] = _state_var_old[_qp][i];
     271             : 
     272             :   // Pass through updated stress, total strain, and strain increment arrays
     273             :   static const std::array<Real, 6> strain_factor{{1, 1, 1, 2, 2, 2}};
     274             :   // Account for difference in vector order convention: yz, xz, xy (MOOSE)  vs xy, xz, yz
     275             :   // (commercial software)
     276             :   static const std::array<std::pair<unsigned int, unsigned int>, 6> component{
     277             :       {{0, 0}, {1, 1}, {2, 2}, {0, 1}, {0, 2}, {1, 2}}};
     278             : 
     279    14784812 :   for (const auto i : make_range(_aqNTENS))
     280             :   {
     281    12672696 :     const auto a = component[i].first;
     282    12672696 :     const auto b = component[i].second;
     283    12672696 :     _aqSTRESS[i] = stress_old(a, b);
     284    12672696 :     _aqSTRAN[i] = total_strain_old(a, b) * strain_factor[i];
     285    12672696 :     _aqDSTRAN[i] = strain_increment(a, b) * strain_factor[i];
     286             :   }
     287             : 
     288             :   // current coordinates
     289     8448464 :   for (const auto i : make_range(Moose::dim))
     290     6336348 :     _aqCOORDS[i] = _q_point[_qp](i);
     291             : 
     292             :   // zero out Jacobian contribution
     293    78148292 :   for (const auto i : make_range(_aqNTENS * _aqNTENS))
     294    76036176 :     _aqDDSDDE[i] = 0.0;
     295             : 
     296             :   // Set PNEWDT initially to a large value
     297     2112116 :   _aqPNEWDT = std::numeric_limits<Real>::max();
     298             : 
     299             :   // Temperature
     300     2112116 :   _aqTEMP = _temperature_old[_qp];
     301             : 
     302             :   // Temperature increment
     303     2112116 :   _aqDTEMP = _temperature[_qp] - _temperature_old[_qp];
     304             : 
     305     2800580 :   for (const auto i : make_range(_number_external_fields))
     306             :   {
     307             :     // External field at this step
     308      688464 :     _aqPREDEF[i] = (*_external_fields_old[i])[_qp];
     309             : 
     310             :     // External field increments
     311      688464 :     _aqDPRED[i] = (*_external_fields[i])[_qp] - (*_external_fields_old[i])[_qp];
     312             :   }
     313             : 
     314     2242740 :   for (const auto i : make_range(_number_external_properties))
     315             :   {
     316             :     // External property at this step
     317      130624 :     _aqPREDEF[i + _number_external_fields] = (*_external_properties_old[i])[_qp];
     318             : 
     319             :     // External property increments
     320      130624 :     _aqDPRED[i + _number_external_fields] =
     321      130624 :         (*_external_properties[i])[_qp] - (*_external_properties_old[i])[_qp];
     322             :   }
     323             : 
     324             :   // Layer number (not supported)
     325     2112116 :   _aqLAYER = -1;
     326             : 
     327             :   // Section point number within the layer (not supported)
     328     2112116 :   _aqKSPT = -1;
     329             : 
     330             :   // Increment number
     331     2112116 :   _aqKINC = _t_step;
     332     2112116 :   _aqKSTEP = 1;
     333             : 
     334             :   // integration point number
     335     2112116 :   _aqNPT = _qp + (_use_one_based_indexing ? 1 : 0);
     336             : 
     337             :   // Connection to extern statement
     338     2112116 :   _umat(_aqSTRESS.data(),
     339             :         _aqSTATEV.data(),
     340             :         _aqDDSDDE.data(),
     341     2112116 :         &_elastic_strain_energy[_qp],
     342     2112116 :         &_plastic_dissipation[_qp],
     343     2112116 :         &_creep_dissipation[_qp],
     344             :         &_aqRPL,
     345             :         _aqDDSDDT.data(),
     346             :         _aqDRPLDE.data(),
     347             :         &_aqDRPLDT,
     348             :         _aqSTRAN.data(),
     349             :         _aqDSTRAN.data(),
     350             :         _aqTIME.data(),
     351             :         &_aqDTIME,
     352             :         &_aqTEMP,
     353             :         &_aqDTEMP,
     354             :         _aqPREDEF.data(),
     355             :         _aqDPRED.data(),
     356     2112116 :         _aqCMNAME,
     357             :         &_aqNDI,
     358             :         &_aqNSHR,
     359             :         &_aqNTENS,
     360             :         &_aqNSTATV,
     361             :         _aqPROPS.data(),
     362             :         &_aqNPROPS,
     363             :         _aqCOORDS.data(),
     364             :         _aqDROT.data(),
     365             :         &_aqPNEWDT,
     366             :         &_aqCELENT,
     367             :         _aqDFGRD0.data(),
     368             :         _aqDFGRD1.data(),
     369             :         &_aqNOEL,
     370             :         &_aqNPT,
     371             :         &_aqLAYER,
     372             :         &_aqKSPT,
     373             :         &_aqKSTEP,
     374             :         &_aqKINC);
     375             : 
     376             :   // Update state variables
     377     8062452 :   for (int i = 0; i < _aqNSTATV; ++i)
     378     5950336 :     _state_var[_qp][i] = _aqSTATEV[i];
     379             : 
     380             :   // Here, we apply UMAT convention: Always multiply _dt by PNEWDT to determine the material time
     381             :   // step MOOSE time stepper will choose the most limiting of all material time step increments
     382             :   // provided
     383     2112116 :   _material_timestep[_qp] = _aqPNEWDT * _dt;
     384             : 
     385             :   // Get new stress tensor - UMAT should update stress
     386             :   // Account for difference in vector order convention: yz, xz, xy (MOOSE)  vs xy, xz, yz
     387             :   // (commercial software)
     388     2112116 :   _stress[_qp] = RankTwoTensor(
     389             :       _aqSTRESS[0], _aqSTRESS[1], _aqSTRESS[2], _aqSTRESS[5], _aqSTRESS[4], _aqSTRESS[3]);
     390             : 
     391             :   // Build Jacobian matrix from UMAT's Voigt non-standard order to fourth order tensor.
     392             :   const unsigned int N = Moose::dim;
     393             :   const unsigned int ntens = N * (N + 1) / 2;
     394             :   const int nskip = N - 1;
     395             : 
     396     8448464 :   for (const auto i : make_range(N))
     397    25345392 :     for (const auto j : make_range(N))
     398    76036176 :       for (const auto k : make_range(N))
     399   228108528 :         for (const auto l : make_range(N))
     400             :         {
     401   171081396 :           if (i == j)
     402    57027132 :             _jacobian_mult[_qp](i, j, k, l) =
     403    57027132 :                 k == l ? _aqDDSDDE[i * ntens + k] : _aqDDSDDE[i * ntens + k + nskip + l];
     404             :           else
     405             :             // i!=j
     406   114054264 :             _jacobian_mult[_qp](i, j, k, l) =
     407   114054264 :                 k == l ? _aqDDSDDE[(nskip + i + j) * ntens + k]
     408    76036176 :                        : _aqDDSDDE[(nskip + i + j) * ntens + k + nskip + l];
     409             :         }
     410             : 
     411             :   // check if we need to rotate from intermediate reference frame
     412     2112116 :   if (_use_orientation)
     413             :   {
     414             :     // rotate to current configuration
     415       32400 :     _stress[_qp].rotate(_total_rotation[_qp]);
     416       32400 :     _jacobian_mult[_qp].rotate(_total_rotation[_qp]);
     417             :   }
     418     2079716 :   else if (_decomposition_method != ComputeFiniteStrain::DecompMethod::HughesWinget)
     419     2065348 :     _stress[_qp].rotate(_rotation_increment[_qp]);
     420     2112116 : }

Generated by: LCOV version 1.14