LCOV - code coverage report
Current view: top level - src/userobjects - EulerAngleUpdater.C (source / functions) Hit Total Coverage
Test: idaholab/moose phase_field: #32971 (54bef8) with base c6cf66 Lines: 62 84 73.8 %
Date: 2026-05-29 20:38:39 Functions: 4 4 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 "EulerAngleUpdater.h"
      11             : #include "GrainTrackerInterface.h"
      12             : #include "GrainForceAndTorqueInterface.h"
      13             : #include "RotationTensor.h"
      14             : #include "SystemBase.h"
      15             : 
      16             : registerMooseObject("PhaseFieldApp", EulerAngleUpdater);
      17             : 
      18             : InputParameters
      19          28 : EulerAngleUpdater::validParams()
      20             : {
      21          28 :   InputParameters params = EulerAngleProvider::validParams();
      22          28 :   params.addClassDescription(
      23             :       "Provide updated euler angles after rigid body rotation of the grains.");
      24          56 :   params.addRequiredParam<UserObjectName>("grain_tracker_object",
      25             :                                           "The FeatureFloodCount UserObject to get values from.");
      26          56 :   params.addRequiredParam<UserObjectName>("euler_angle_provider",
      27             :                                           "Name of Euler angle provider user object");
      28          56 :   params.addRequiredParam<UserObjectName>("grain_torques_object",
      29             :                                           "Name of Euler angle provider user object");
      30          56 :   params.addRequiredParam<VectorPostprocessorName>("grain_volumes",
      31             :                                                    "The feature volume VectorPostprocessorValue.");
      32          56 :   params.addParam<Real>("rotation_constant", 1.0, "Constant value characterizing grain rotation");
      33          28 :   return params;
      34           0 : }
      35             : 
      36          14 : EulerAngleUpdater::EulerAngleUpdater(const InputParameters & params)
      37             :   : EulerAngleProvider(params),
      38          14 :     _grain_tracker(getUserObject<GrainTrackerInterface>("grain_tracker_object")),
      39          14 :     _euler(getUserObject<EulerAngleProvider>("euler_angle_provider")),
      40          14 :     _grain_torque(getUserObject<GrainForceAndTorqueInterface>("grain_torques_object")),
      41          14 :     _grain_volumes(getVectorPostprocessorValue("grain_volumes", "feature_volumes")),
      42          28 :     _mr(getParam<Real>("rotation_constant")),
      43          28 :     _first_time(declareRestartableData<bool>("first_time_euler_update", true)),
      44          14 :     _first_time_recovered(_app.isRecovering()),
      45          28 :     _t_step_old(declareRestartableData<int>("euler_update_tstep_old", -1)),
      46          42 :     _angles_old(declareRestartableData<std::vector<EulerAngles>>("euler_angles_old"))
      47             : {
      48          14 : }
      49             : 
      50             : void
      51          44 : EulerAngleUpdater::initialize()
      52             : {
      53          44 :   const auto grain_num = _grain_tracker.getTotalFeatureCount();
      54             : 
      55          44 :   if (_first_time)
      56             :   {
      57          12 :     _angles.resize(grain_num);
      58          12 :     _angles_old.resize(grain_num);
      59          24 :     for (unsigned int i = 0; i < grain_num; ++i)
      60          12 :       _angles[i] = _euler.getEulerAngles(i); // Read initial euler angles
      61             :   }
      62             : 
      63          44 :   unsigned int angle_size = _angles.size();
      64          44 :   for (unsigned int i = angle_size; i < grain_num; ++i) // if new grains are created
      65           0 :     _angles.push_back(_euler.getEulerAngles(i));        // Assign initial euler angles
      66             : 
      67          88 :   for (unsigned int i = 0; i < grain_num; ++i)
      68             :   {
      69             :     // We dont want to use old angles on recovery
      70          44 :     if (!_first_time && !_first_time_recovered && !_fe_problem.converged(_sys.number()))
      71           0 :       _angles[i] = _angles_old[i];
      72          44 :     _first_time_recovered = false;
      73             : 
      74          44 :     RealGradient torque = _grain_torque.getTorqueValues()[i];
      75             : 
      76          44 :     if (i <= angle_size && _t_step > _t_step_old) // if new grains are created
      77          42 :       _angles_old[i] = _angles[i];
      78           2 :     else if (i > angle_size)
      79           0 :       _angles_old.push_back(_angles[i]);
      80             : 
      81          44 :     RotationTensor R0(_angles_old[i]); // RotationTensor as per old euler angles
      82             :     RealVectorValue torque_rotated =
      83          44 :         R0 * torque; // Applied torque is rotated to allign with old grain axes
      84             :     RealVectorValue omega =
      85          44 :         _mr / _grain_volumes[i] * torque_rotated; // Angular velocity as per old grain axes
      86             :     /**
      87             :      * Change in euler angles are obtained from the torque & angular velocities about the material
      88             :      * axes.
      89             :      * Change in phi1, Phi and phi2 are caused by rotation about z axis, x' axis & z'' axis,
      90             :      * respectively.
      91             :      * Components of the angular velocities across z, x' and z'' axes are obtained from the torque
      92             :      * values.
      93             :      * This yields change in euler angles due to grain rotation.
      94             :      */
      95             :     RealVectorValue angle_change;
      96          44 :     angle_change(0) = omega(2) * _dt;
      97          44 :     angle_change(1) =
      98          44 :         (omega(0) * std::cos(angle_change(0)) + omega(1) * std::sin(angle_change(0))) * _dt;
      99          44 :     angle_change(2) = (omega(0) * std::sin(angle_change(0)) * std::sin(angle_change(1)) -
     100          44 :                        omega(1) * std::cos(angle_change(0)) * std::sin(angle_change(1)) +
     101          44 :                        omega(2) * std::cos(angle_change(1))) *
     102          44 :                       _dt;
     103             :     angle_change *= (180.0 / libMesh::pi);
     104             : 
     105          44 :     RotationTensor R1(angle_change); // Rotation matrix due to torque
     106             :     /**
     107             :      * Final RotationMatrix = RotationMatrix due to applied torque X old RotationMatrix
     108             :      * Updated Euler angles are obtained by back-tracking the angles from the rotation matrix
     109             :      * For details about the componenets of the rotation matrix please refer to RotationTensor.C
     110             :      * Phi = acos(R33); phi1 = atan2(R31,-R32); phi2 = atan2(R13,R23) for phi != 0.0 por 180.0
     111             :      */
     112          44 :     RealTensorValue R = R1 * R0;
     113             : 
     114          44 :     if (R(2, 2) != 1.0 && R(2, 2) != -1.0) // checks if cos(Phi) = 1 or -1
     115             :     {
     116          44 :       _angles[i].phi1 = std::atan2(R(2, 0), -R(2, 1)) * (180.0 / libMesh::pi);
     117          44 :       _angles[i].Phi = std::acos(R(2, 2)) * (180.0 / libMesh::pi);
     118          44 :       _angles[i].phi2 = std::atan2(R(0, 2), R(1, 2)) * (180.0 / libMesh::pi);
     119             :     }
     120           0 :     else if (R(2, 2) == 1.0) // special case for Phi = 0.0
     121             :     {
     122           0 :       if (R0(2, 2) == 1.0)
     123             :         // when Phi_old = 0.0; all the rotations are about z axis and angles accumulates after each
     124             :         // rotation
     125           0 :         _angles[i].phi1 = _angles_old[i].phi1 + _angles_old[i].phi2 + angle_change(0);
     126             :       else
     127           0 :         _angles[i].phi1 = angle_change(0);
     128             :       // Comply with bunge euler angle definitions, 0.0 <= phi1 <= 360.0
     129           0 :       if (std::abs(_angles[i].phi1) > 360.0)
     130             :       {
     131           0 :         int laps = _angles[i].phi1 / 360.0;
     132           0 :         _angles[i].phi1 -= laps * 360.0;
     133             :       }
     134           0 :       _angles[i].Phi = 0.0;
     135           0 :       _angles[i].phi2 = -_angles[i].phi1 + std::atan2(R(0, 1), R(1, 1)) * (180.0 / libMesh::pi);
     136             :     }
     137             :     else
     138             :     {
     139           0 :       if (R0(2, 2) == 1.0)
     140           0 :         _angles[i].phi1 = _angles_old[i].phi1 + _angles_old[i].phi2 + angle_change(0);
     141             :       else
     142           0 :         _angles[i].phi1 = angle_change(0);
     143             :       // Comply with bunge euler angle definitions, 0.0 <= phi1 <= 360.0
     144           0 :       if (std::abs(_angles[i].phi1) > 360.0)
     145             :       {
     146           0 :         int laps = _angles[i].phi1 / 360.0;
     147           0 :         _angles[i].phi1 -= laps * 360.0;
     148             :       }
     149           0 :       _angles[i].Phi = 180.0;
     150           0 :       _angles[i].phi2 = _angles[i].phi1 - std::atan2(-R(0, 1), -R(1, 1)) * (180.0 / libMesh::pi);
     151             :     }
     152             : 
     153             :     // Following checks and updates are done only to comply with bunge euler angle definitions, 0.0
     154             :     // <= phi1/phi2 <= 360.0
     155          44 :     if (_angles[i].phi1 < 0.0)
     156          31 :       _angles[i].phi1 += 360.0;
     157          44 :     if (_angles[i].phi2 < 0.0)
     158          44 :       _angles[i].phi2 += 360.0;
     159          44 :     if (_angles[i].Phi < 0.0)
     160           0 :       mooseError("Euler angle out of range.");
     161             :   }
     162             : 
     163          44 :   _first_time = false;
     164          44 :   _t_step_old = _t_step;
     165          44 : }
     166             : 
     167             : const EulerAngles &
     168          13 : EulerAngleUpdater::getEulerAnglesOld(unsigned int i) const
     169             : {
     170             :   mooseAssert(i < getGrainNum(), "Requesting Euler angles for an invalid grain id");
     171          13 :   return _angles_old[i];
     172             : }

Generated by: LCOV version 1.14