LCOV - code coverage report
Current view: top level - src/solvers - euler2_solver.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4232 (290bfc) with base 82cc40 Lines: 75 161 46.6 %
Date: 2025-08-27 15:46:53 Functions: 6 10 60.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
       3             : 
       4             : // This library is free software; you can redistribute it and/or
       5             : // modify it under the terms of the GNU Lesser General Public
       6             : // License as published by the Free Software Foundation; either
       7             : // version 2.1 of the License, or (at your option) any later version.
       8             : 
       9             : // This library is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12             : // Lesser General Public License for more details.
      13             : 
      14             : // You should have received a copy of the GNU Lesser General Public
      15             : // License along with this library; if not, write to the Free Software
      16             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      17             : 
      18             : 
      19             : // Local includes
      20             : #include "libmesh/euler2_solver.h"
      21             : 
      22             : #include "libmesh/adjoint_refinement_estimator.h"
      23             : #include "libmesh/diff_system.h"
      24             : #include "libmesh/error_vector.h"
      25             : #include "libmesh/int_range.h"
      26             : #include "libmesh/numeric_vector.h"
      27             : 
      28             : namespace libMesh
      29             : {
      30             : 
      31             : 
      32             : 
      33         426 : Euler2Solver::Euler2Solver (sys_type & s)
      34         426 :   : FirstOrderUnsteadySolver(s), theta(1.)
      35             : {
      36         426 : }
      37             : 
      38             : 
      39             : 
      40         804 : Euler2Solver::~Euler2Solver () = default;
      41             : 
      42             : 
      43             : 
      44           0 : Real Euler2Solver::error_order() const
      45             : {
      46           0 :   if (theta == 0.5)
      47           0 :     return 2.;
      48           0 :   return 1.;
      49             : }
      50             : 
      51             : 
      52             : 
      53             : 
      54      327632 : bool Euler2Solver::element_residual (bool request_jacobian,
      55             :                                      DiffContext & context)
      56             : {
      57      327632 :   bool compute_second_order_eqns = !this->_system.get_physics()->get_second_order_vars().empty();
      58             : 
      59      357744 :   return this->_general_residual(request_jacobian,
      60             :                                  context,
      61             :                                  &DifferentiablePhysics::mass_residual,
      62             :                                  &DifferentiablePhysics::damping_residual,
      63             :                                  &DifferentiablePhysics::_eulerian_time_deriv,
      64             :                                  &DifferentiablePhysics::element_constraint,
      65             :                                  &DiffContext::elem_reinit,
      66      327632 :                                  compute_second_order_eqns);
      67             : }
      68             : 
      69             : 
      70             : 
      71      185600 : bool Euler2Solver::side_residual (bool request_jacobian,
      72             :                                   DiffContext & context)
      73             : {
      74      201856 :   return this->_general_residual(request_jacobian,
      75             :                                  context,
      76             :                                  &DifferentiablePhysics::side_mass_residual,
      77             :                                  &DifferentiablePhysics::side_damping_residual,
      78             :                                  &DifferentiablePhysics::side_time_derivative,
      79             :                                  &DifferentiablePhysics::side_constraint,
      80             :                                  &DiffContext::elem_side_reinit,
      81      185600 :                                  false);
      82             : }
      83             : 
      84             : 
      85             : 
      86           0 : bool Euler2Solver::nonlocal_residual (bool request_jacobian,
      87             :                                       DiffContext & context)
      88             : {
      89           0 :   bool compute_second_order_eqns = this->_system.have_second_order_scalar_vars();
      90             : 
      91           0 :   return this->_general_residual(request_jacobian,
      92             :                                  context,
      93             :                                  &DifferentiablePhysics::nonlocal_mass_residual,
      94             :                                  &DifferentiablePhysics::nonlocal_damping_residual,
      95             :                                  &DifferentiablePhysics::nonlocal_time_derivative,
      96             :                                  &DifferentiablePhysics::nonlocal_constraint,
      97             :                                  &DiffContext::nonlocal_reinit,
      98           0 :                                  compute_second_order_eqns);
      99             : }
     100             : 
     101             : 
     102             : 
     103      513232 : bool Euler2Solver::_general_residual (bool request_jacobian,
     104             :                                       DiffContext & context,
     105             :                                       ResFuncType mass,
     106             :                                       ResFuncType damping,
     107             :                                       ResFuncType time_deriv,
     108             :                                       ResFuncType constraint,
     109             :                                       ReinitFuncType reinit_func,
     110             :                                       bool compute_second_order_eqns)
     111             : {
     112       46368 :   unsigned int n_dofs = context.get_elem_solution().size();
     113             : 
     114             :   // Local nonlinear solution at old timestep
     115      513232 :   DenseVector<Number> old_elem_solution(n_dofs);
     116    21556672 :   for (unsigned int i=0; i != n_dofs; ++i)
     117    21043440 :     old_elem_solution(i) =
     118    22903444 :       old_nonlinear_solution(context.get_dof_indices()[i]);
     119             : 
     120             :   // Local time derivative of solution
     121       46368 :   context.get_elem_solution_rate() = context.get_elem_solution();
     122      466864 :   context.get_elem_solution_rate() -= old_elem_solution;
     123      513232 :   context.elem_solution_rate_derivative = 1 / _system.deltat;
     124       46368 :   context.get_elem_solution_rate() *=
     125       71448 :     context.elem_solution_rate_derivative;
     126             : 
     127             :   // Our first evaluations are at the final elem_solution
     128      513232 :   context.elem_solution_derivative = 1.0;
     129             : 
     130             :   // If a fixed solution is requested, we'll use the elem_solution
     131             :   // at the new timestep
     132             :   // FIXME - should this be the theta solution instead?
     133      513232 :   if (_system.use_fixed_solution)
     134           0 :     context.get_elem_fixed_solution() = context.get_elem_solution();
     135             : 
     136      513232 :   context.fixed_solution_derivative = 1.0;
     137             : 
     138             :   // We need to save the old jacobian and old residual since we'll be
     139             :   // multiplying some of the new contributions by theta or 1-theta
     140      605968 :   DenseMatrix<Number> old_elem_jacobian(n_dofs, n_dofs);
     141      513232 :   DenseVector<Number> old_elem_residual(n_dofs);
     142       46368 :   old_elem_residual.swap(context.get_elem_residual());
     143      513232 :   if (request_jacobian)
     144      233432 :     old_elem_jacobian.swap(context.get_elem_jacobian());
     145             : 
     146             :   // Local time derivative of solution
     147       46368 :   context.get_elem_solution_rate() = context.get_elem_solution();
     148      466864 :   context.get_elem_solution_rate() -= old_elem_solution;
     149      513232 :   context.elem_solution_rate_derivative = 1 / _system.deltat;
     150       46368 :   context.get_elem_solution_rate() *=
     151       71448 :     context.elem_solution_rate_derivative;
     152             : 
     153             :   // If we are asked to compute residuals for second order variables,
     154             :   // we also populate the acceleration part so the user can use that.
     155      513232 :   if (compute_second_order_eqns)
     156      326912 :     this->prepare_accel(context);
     157             : 
     158             :   // Move the mesh into place first if necessary, set t = t_{n+1}
     159      513232 :   (context.*reinit_func)(1.);
     160             : 
     161             :   // First, evaluate time derivative at the new timestep.
     162             :   // The element should already be in the proper place
     163             :   // even for a moving mesh problem.
     164             :   bool jacobian_computed =
     165      980096 :     (_system.get_physics()->*time_deriv)(request_jacobian, context);
     166             : 
     167             :   // Next, evaluate the mass residual at the new timestep
     168             : 
     169      980096 :   jacobian_computed = (_system.get_physics()->*mass)(jacobian_computed, context) &&
     170             :     jacobian_computed;
     171             : 
     172             :   // If we have second-order variables, we need to get damping terms
     173             :   // and the velocity equations
     174      513232 :   if (compute_second_order_eqns)
     175             :     {
     176      623772 :       jacobian_computed = (_system.get_physics()->*damping)(jacobian_computed, context) &&
     177             :         jacobian_computed;
     178             : 
     179      326912 :       jacobian_computed = this->compute_second_order_eqns(jacobian_computed, context) &&
     180             :         jacobian_computed;
     181             :     }
     182             : 
     183             :   // Add the constraint term
     184      980096 :   jacobian_computed = (_system.get_physics()->*constraint)(jacobian_computed, context) &&
     185             :     jacobian_computed;
     186             : 
     187             :   // The new solution's contribution is scaled by theta
     188      513232 :   context.get_elem_residual() *= theta;
     189      513232 :   context.get_elem_jacobian() *= theta;
     190             : 
     191             :   // Save the new solution's term
     192      605968 :   DenseMatrix<Number> elem_jacobian_newterm(n_dofs, n_dofs);
     193      466864 :   DenseVector<Number> elem_residual_newterm(n_dofs);
     194       46368 :   elem_residual_newterm.swap(context.get_elem_residual());
     195      466864 :   if (request_jacobian)
     196      233432 :     elem_jacobian_newterm.swap(context.get_elem_jacobian());
     197             : 
     198             :   // Add the time-dependent term for the old solution
     199             : 
     200             :   // Make sure elem_solution is set up for elem_reinit to use
     201             :   // Move elem_->old_, old_->elem_
     202       46368 :   context.get_elem_solution().swap(old_elem_solution);
     203      513232 :   context.elem_solution_derivative = 0.0;
     204             : 
     205             :   // Move the mesh into place if necessary, set t = t_{n}
     206      513232 :   (context.*reinit_func)(0.);
     207             : 
     208       46368 :   jacobian_computed =
     209      980096 :     (_system.get_physics()->*time_deriv)(jacobian_computed, context) &&
     210             :     jacobian_computed;
     211             : 
     212             :   // Add the mass residual term for the old solution
     213             : 
     214             :   // Evaluating the mass residual at both old and new timesteps will be
     215             :   // redundant in most problems but may be necessary for time accuracy
     216             :   // or stability in moving mesh problems or problems with user-overridden
     217             :   // mass_residual functions
     218             : 
     219       46368 :   jacobian_computed =
     220      980096 :     (_system.get_physics()->*mass)(jacobian_computed, context) &&
     221             :     jacobian_computed;
     222             : 
     223             :   // If we have second-order variables, we need to get damping terms
     224             :   // and the velocity equations
     225      513232 :   if (compute_second_order_eqns)
     226             :     {
     227      623772 :       jacobian_computed = (_system.get_physics()->*damping)(jacobian_computed, context) &&
     228             :         jacobian_computed;
     229             : 
     230      326912 :       jacobian_computed = this->compute_second_order_eqns(jacobian_computed, context) &&
     231             :         jacobian_computed;
     232             :     }
     233             : 
     234             :   // The old solution's contribution is scaled by (1-theta)
     235      513232 :   context.get_elem_residual() *= (1-theta);
     236      513232 :   context.get_elem_jacobian() *= (1-theta);
     237             : 
     238             :   // Restore the elem_solution
     239             :   // Move elem_->elem_, old_->old_
     240       46368 :   context.get_elem_solution().swap(old_elem_solution);
     241      513232 :   context.elem_solution_derivative = 1;
     242             : 
     243             :   // Restore the elem position if necessary, set t = t_{n+1}
     244      513232 :   (context.*reinit_func)(1.);
     245             : 
     246             :   // Add back (or restore) the old residual/jacobian
     247      466864 :   context.get_elem_residual() += old_elem_residual;
     248      513232 :   if (request_jacobian)
     249             :     {
     250      256616 :       if (jacobian_computed)
     251      233432 :         context.get_elem_jacobian() += old_elem_jacobian;
     252             :       else
     253           0 :         context.get_elem_jacobian().swap(old_elem_jacobian);
     254             :     }
     255             : 
     256             :   // Add the saved new-solution terms
     257      466864 :   context.get_elem_residual() += elem_residual_newterm;
     258      513232 :   if (jacobian_computed)
     259      233432 :     context.get_elem_jacobian() += elem_jacobian_newterm;
     260             : 
     261      559600 :   return jacobian_computed;
     262      840992 : }
     263             : 
     264           0 : void Euler2Solver::integrate_qoi_timestep()
     265             : {
     266             :   // We use a numerical integration scheme consistent with the theta used for the timesolver.
     267             : 
     268             :   // Zero out the system.qoi vector
     269           0 :   for (auto j : make_range(_system.n_qois()))
     270             :   {
     271           0 :     _system.set_qoi(j, 0.0);
     272             :   }
     273             : 
     274             :   // Left and right side contributions
     275           0 :   std::vector<Number> left_contribution(_system.n_qois(), 0.0);
     276           0 :   Number time_left = 0.0;
     277           0 :   std::vector<Number> right_contribution(_system.n_qois(), 0.0);
     278           0 :   Number time_right = 0.0;
     279             : 
     280           0 :   time_left = _system.time;
     281             : 
     282             :   // Base class assumes a direct steady evaluation
     283           0 :   this->_system.assemble_qoi();
     284             : 
     285             :   // Also get the spatially integrated errors for all the QoIs in the QoI set
     286           0 :   for (auto j : make_range(_system.n_qois()))
     287             :   {
     288           0 :     left_contribution[j] = _system.get_qoi_value(j);
     289             :   }
     290             : 
     291             :   // Advance to t_j+1
     292           0 :   _system.time = _system.time + _system.deltat;
     293             : 
     294           0 :   time_right = _system.time;
     295             : 
     296             :   // Load the solution at the next timestep
     297           0 :   retrieve_timestep();
     298             : 
     299             :   // Zero out the system.qoi vector
     300           0 :   for (auto j : make_range(_system.n_qois()))
     301             :   {
     302           0 :     _system.set_qoi(j, 0.0);
     303             :   }
     304             : 
     305             :   // Base class assumes a direct steady evaluation
     306           0 :   this->_system.assemble_qoi();
     307             : 
     308           0 :   for(auto j : make_range(_system.n_qois()))
     309             :   {
     310           0 :     right_contribution[j] = _system.get_qoi_value(j);
     311             :   }
     312             : 
     313             :   // Combine the left and right side contributions as per the specified theta
     314             :   // theta = 0.5 (Crank-Nicholson) gives the trapezoidal rule.
     315           0 :   for (auto j : make_range(_system.n_qois()))
     316             :   {
     317           0 :     _system.set_qoi(j, ( ( ((1.0 - theta)*left_contribution[j]) + (theta*right_contribution[j]) )/2.0 )*(time_right - time_left));
     318             :   }
     319           0 : }
     320             : 
     321             : #ifdef LIBMESH_ENABLE_AMR
     322           0 : void Euler2Solver::integrate_adjoint_refinement_error_estimate(AdjointRefinementEstimator & adjoint_refinement_error_estimator, ErrorVector & QoI_elementwise_error)
     323             : {
     324             :   // Currently, we only support this functionality when Backward-Euler time integration is used.
     325           0 :   if (theta != 1.0)
     326           0 :   libmesh_not_implemented();
     327             : 
     328             :   // There are two possibilities regarding the integration rule we need to use for time integration.
     329             :   // If we have a instantaneous QoI, then we need to use a left sided Riemann sum, otherwise the trapezoidal rule for temporally smooth QoIs.
     330             : 
     331             :   // Create left and right error estimate vectors of the right size
     332           0 :   std::vector<Number> qoi_error_estimates_left(_system.n_qois());
     333           0 :   std::vector<Number> qoi_error_estimates_right(_system.n_qois());
     334             : 
     335             :   // Get t_j
     336           0 :   Real time_left = _system.time;
     337             : 
     338             :   // Get f(t_j)
     339           0 :   ErrorVector QoI_elementwise_error_left;
     340             : 
     341             :   // If we are at the very initial step, the error contribution is zero,
     342             :   // otherwise the old ajoint vector has been filled and we are the left end
     343             :   // of a subsequent timestep or sub-timestep
     344           0 :   if(old_adjoints[0] != nullptr)
     345             :   {
     346             :     // For evaluating the residual, we need to use the deltat that was used
     347             :     // to get us to this solution, so we save the current deltat as next_step_deltat
     348             :     // and set _system.deltat to the last completed deltat.
     349           0 :     next_step_deltat = _system.deltat;
     350           0 :     _system.deltat = last_step_deltat;
     351             : 
     352             :     // The adjoint error estimate expression for a backwards facing step
     353             :     // scheme needs the adjoint for the last time instant, so save the current adjoint for future use
     354           0 :     for (auto j : make_range(_system.n_qois()))
     355             :     {
     356             :       // Swap for residual weighting
     357           0 :       _system.get_adjoint_solution(j).swap(*old_adjoints[j]);
     358             :     }
     359             : 
     360           0 :     _system.update();
     361             : 
     362             :     // The residual has to be evaluated at the last time
     363           0 :     _system.time = _system.time - last_step_deltat;
     364             : 
     365           0 :     adjoint_refinement_error_estimator.estimate_error(_system, QoI_elementwise_error_left);
     366             : 
     367             :     // Shift the time back
     368           0 :     _system.time = _system.time + last_step_deltat;
     369             : 
     370             :     // Swap back the current and old adjoints
     371           0 :     for (auto j : make_range(_system.n_qois()))
     372             :     {
     373           0 :       _system.get_adjoint_solution(j).swap(*old_adjoints[j]);
     374             :     }
     375             : 
     376             :     // Set the system deltat back to what it should be to march to the next time
     377           0 :     _system.deltat = next_step_deltat;
     378             : 
     379             :   }
     380             :   else
     381             :   {
     382           0 :     for(auto i : index_range(QoI_elementwise_error))
     383           0 :       QoI_elementwise_error_left[i] = 0.0;
     384             :   }
     385             : 
     386             :   // Also get the left side contributions for the spatially integrated errors for all the QoIs in the QoI set
     387           0 :   for (auto j : make_range(_system.n_qois()))
     388             :   {
     389             :     // Skip this QoI if not in the QoI Set
     390           0 :     if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
     391             :     {
     392             :       // If we are at the initial time, the error contribution is zero
     393           0 :       if(std::abs(_system.time) > TOLERANCE*sqrt(TOLERANCE))
     394             :       {
     395           0 :         qoi_error_estimates_left[j] = adjoint_refinement_error_estimator.get_global_QoI_error_estimate(j);
     396             :       }
     397             :       else
     398             :       {
     399           0 :         qoi_error_estimates_left[j] = 0.0;
     400             :       }
     401             :     }
     402             :   }
     403             : 
     404             :   // Advance to t_j+1
     405           0 :   _system.time = _system.time + _system.deltat;
     406             : 
     407             :   // Get t_j+1
     408           0 :   Real time_right = _system.time;
     409             : 
     410             :   // We will need to use the last step deltat for the weighted residual evaluation
     411           0 :   last_step_deltat = _system.deltat;
     412             : 
     413             :   // The adjoint error estimate expression for a backwards facing step
     414             :   // scheme needs the adjoint for the last time instant, so save the current adjoint for future use
     415           0 :   for (auto j : make_range(_system.n_qois()))
     416             :   {
     417           0 :     old_adjoints[j] = _system.get_adjoint_solution(j).clone();
     418             :   }
     419             : 
     420             :   // Retrieve the state and adjoint vectors for the next time instant
     421           0 :   retrieve_timestep();
     422             : 
     423             :   // Swap for residual weighting
     424           0 :   for (auto j : make_range(_system.n_qois()))
     425             :   {
     426           0 :    _system.get_adjoint_solution(j).swap(*old_adjoints[j]);
     427             :   }
     428             : 
     429             :   // Swap out the deltats as we did for the left side
     430           0 :   next_step_deltat = _system.deltat;
     431           0 :   _system.deltat = last_step_deltat;
     432             : 
     433             :   // Get f(t_j+1)
     434           0 :   ErrorVector QoI_elementwise_error_right;
     435             : 
     436           0 :   _system.update();
     437             : 
     438             :   // The residual has to be evaluated at the last time
     439           0 :   _system.time = _system.time - last_step_deltat;
     440             : 
     441           0 :   adjoint_refinement_error_estimator.estimate_error(_system, QoI_elementwise_error_right);
     442             : 
     443             :   // Shift the time back
     444           0 :   _system.time = _system.time + last_step_deltat;
     445             : 
     446             :   // Set the system deltat back to what it needs to be able to march to the next time
     447           0 :   _system.deltat = next_step_deltat;
     448             : 
     449             :   // Swap back now that the residual weighting is done
     450           0 :   for (auto j : make_range(_system.n_qois()))
     451             :   {
     452           0 :    _system.get_adjoint_solution(j).swap(*old_adjoints[j]);
     453             :   }
     454             : 
     455             :   // Also get the right side contributions for the spatially integrated errors for all the QoIs in the QoI set
     456           0 :   for (auto j : make_range(_system.n_qois()))
     457             :   {
     458             :     // Skip this QoI if not in the QoI Set
     459           0 :     if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
     460             :     {
     461           0 :       qoi_error_estimates_right[j] = adjoint_refinement_error_estimator.get_global_QoI_error_estimate(j);
     462             :     }
     463             :   }
     464             : 
     465             :   // Error contribution from this timestep
     466           0 :   for (auto i : index_range(QoI_elementwise_error))
     467           0 :     QoI_elementwise_error[i] = float(((QoI_elementwise_error_right[i] + QoI_elementwise_error_left[i])/2)
     468           0 :                                      * (time_right - time_left));
     469             : 
     470             :   // QoI set spatially integrated errors contribution from this timestep
     471           0 :   for (auto j : make_range(_system.n_qois()))
     472             :   {
     473             :     // Skip this QoI if not in the QoI Set
     474           0 :     if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
     475             :     {
     476           0 :       _system.set_qoi_error_estimate(j, ( (1.0 - theta)*qoi_error_estimates_left[j] + theta*qoi_error_estimates_right[j] )*last_step_deltat);
     477             :     }
     478             :   }
     479             : 
     480           0 : }
     481             : #endif // LIBMESH_ENABLE_AMR
     482             : 
     483             : } // namespace libMesh

Generated by: LCOV version 1.14