LCOV - code coverage report
Current view: top level - src/solvers - unsteady_solver.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 100 138 72.5 %
Date: 2025-08-19 19:27:09 Functions: 14 19 73.7 %
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             : #include "libmesh/unsteady_solver.h"
      20             : 
      21             : #include "libmesh/adjoint_refinement_estimator.h"
      22             : #include "libmesh/diff_solver.h"
      23             : #include "libmesh/diff_system.h"
      24             : #include "libmesh/dof_map.h"
      25             : #include "libmesh/error_vector.h"
      26             : #include "libmesh/int_range.h"
      27             : #include "libmesh/numeric_vector.h"
      28             : #include "libmesh/parameter_vector.h"
      29             : #include "libmesh/sensitivity_data.h"
      30             : #include "libmesh/solution_history.h"
      31             : 
      32             : namespace libMesh
      33             : {
      34             : 
      35             : 
      36             : 
      37        3099 : UnsteadySolver::UnsteadySolver (sys_type & s)
      38             :   : TimeSolver(s),
      39        6110 :     old_local_nonlinear_solution (NumericVector<Number>::build(s.comm()).release()),
      40        2923 :     first_solve                  (true),
      41        6286 :     first_adjoint_step (true)
      42             : {
      43        3099 :   old_adjoints.resize(s.n_qois());
      44             : 
      45             :   // Set the old adjoint pointers to nullptrs
      46             :   // We will use this nullness to skip the initial time instant,
      47             :   // when there is no older adjoint.
      48        5899 :   for(auto j : make_range(s.n_qois()))
      49             :   {
      50        2800 :     old_adjoints[j] = nullptr;
      51             :   }
      52        3099 : }
      53             : 
      54             : 
      55             : 
      56        5846 : UnsteadySolver::~UnsteadySolver () = default;
      57             : 
      58             : 
      59             : 
      60        2259 : void UnsteadySolver::init ()
      61             : {
      62        2259 :   TimeSolver::init();
      63             : 
      64        2259 :   _system.add_vector("_old_nonlinear_solution");
      65        2259 : }
      66             : 
      67             : 
      68         840 : void UnsteadySolver::init_adjoints ()
      69             : {
      70         840 :   TimeSolver::init_adjoints();
      71             : 
      72             :   // Add old adjoint solutions
      73             :   // To keep the number of vectors consistent between the primal and adjoint
      74             :   // time loops, we will also add the adjoint rhs vector during initialization
      75        2240 :   for(auto i : make_range(_system.n_qois()))
      76             :   {
      77        1440 :     std::string old_adjoint_solution_name = "_old_adjoint_solution";
      78        1400 :     old_adjoint_solution_name+= std::to_string(i);
      79        1440 :     _system.add_vector(old_adjoint_solution_name, false, GHOSTED);
      80             : 
      81        1440 :     std::string adjoint_rhs_name = "adjoint_rhs";
      82        1360 :     adjoint_rhs_name+= std::to_string(i);
      83        1440 :     _system.add_vector(adjoint_rhs_name, false, GHOSTED);
      84             :   }
      85             : 
      86         840 : }
      87             : 
      88             : 
      89        2259 : void UnsteadySolver::init_data()
      90             : {
      91        2259 :   TimeSolver::init_data();
      92             : 
      93             : #ifdef LIBMESH_ENABLE_GHOSTED
      94        2323 :   old_local_nonlinear_solution->init (_system.n_dofs(), _system.n_local_dofs(),
      95        2259 :                                       _system.get_dof_map().get_send_list(), false,
      96         128 :                                       GHOSTED);
      97             : #else
      98             :   old_local_nonlinear_solution->init (_system.n_dofs(), false, SERIAL);
      99             : #endif
     100        2259 : }
     101             : 
     102             : 
     103             : 
     104        1120 : void UnsteadySolver::reinit ()
     105             : {
     106        1120 :   TimeSolver::reinit();
     107             : 
     108             : #ifdef LIBMESH_ENABLE_GHOSTED
     109        1152 :   old_local_nonlinear_solution->init (_system.n_dofs(), _system.n_local_dofs(),
     110        1120 :                                       _system.get_dof_map().get_send_list(), false,
     111          64 :                                       GHOSTED);
     112             : #else
     113             :   old_local_nonlinear_solution->init (_system.n_dofs(), false, SERIAL);
     114             : #endif
     115             : 
     116             :   // localize the old solution
     117             :   NumericVector<Number> & old_nonlinear_soln =
     118        1120 :     _system.get_vector("_old_nonlinear_solution");
     119             : 
     120             :   old_nonlinear_soln.localize
     121        1120 :     (*old_local_nonlinear_solution,
     122        1120 :      _system.get_dof_map().get_send_list());
     123        1120 : }
     124             : 
     125             : 
     126             : 
     127       27465 : void UnsteadySolver::solve ()
     128             : {
     129       27465 :   if (first_solve)
     130             :     {
     131        1839 :       advance_timestep();
     132        1839 :       first_solve = false;
     133             :     }
     134             : 
     135       27465 :   unsigned int solve_result = _diff_solver->solve();
     136             : 
     137             :   // If we requested the UnsteadySolver to attempt reducing dt after a
     138             :   // failed DiffSolver solve, check the results of the solve now.
     139       27465 :   if (reduce_deltat_on_diffsolver_failure)
     140             :     {
     141           0 :       bool backtracking_failed =
     142           0 :         solve_result & DiffSolver::DIVERGED_BACKTRACKING_FAILURE;
     143             : 
     144           0 :       bool max_iterations =
     145           0 :         solve_result & DiffSolver::DIVERGED_MAX_NONLINEAR_ITERATIONS;
     146             : 
     147           0 :       if (backtracking_failed || max_iterations)
     148             :         {
     149             :           // Cut timestep in half
     150           0 :           for (unsigned int nr=0; nr<reduce_deltat_on_diffsolver_failure; ++nr)
     151             :             {
     152           0 :               _system.deltat *= 0.5;
     153           0 :               libMesh::out << "Newton backtracking failed.  Trying with smaller timestep, dt="
     154           0 :                            << _system.deltat << std::endl;
     155             : 
     156           0 :               solve_result = _diff_solver->solve();
     157             : 
     158             :               // Check solve results with reduced timestep
     159           0 :               bool backtracking_still_failed =
     160           0 :                 solve_result & DiffSolver::DIVERGED_BACKTRACKING_FAILURE;
     161             : 
     162           0 :               bool backtracking_max_iterations =
     163           0 :                 solve_result & DiffSolver::DIVERGED_MAX_NONLINEAR_ITERATIONS;
     164             : 
     165           0 :               if (!backtracking_still_failed && !backtracking_max_iterations)
     166             :                 {
     167             :                   // Set the successful deltat as the last deltat
     168           0 :                   last_deltat = _system.deltat;
     169             : 
     170           0 :                   if (!quiet)
     171           0 :                     libMesh::out << "Reduced dt solve succeeded." << std::endl;
     172           0 :                   return;
     173             :                 }
     174             :             }
     175             : 
     176             :           // If we made it here, we still couldn't converge the solve after
     177             :           // reducing deltat
     178           0 :           libMesh::out << "DiffSolver::solve() did not succeed after "
     179           0 :                        << reduce_deltat_on_diffsolver_failure
     180           0 :                        << " attempts." << std::endl;
     181           0 :           libmesh_convergence_failure();
     182             : 
     183             :         } // end if (backtracking_failed || max_iterations)
     184             :     } // end if (reduce_deltat_on_diffsolver_failure)
     185             : 
     186             :   // Set the successful deltat as the last deltat
     187       27465 :   last_deltat = _system.deltat;
     188             : }
     189             : 
     190             : 
     191             : 
     192       21744 : void UnsteadySolver::advance_timestep ()
     193             : {
     194             :   // The first access of advance_timestep happens via solve, not user code
     195             :   // It is used here to store any initial conditions data
     196       21744 :   if (!first_solve)
     197             :     {
     198             :       // We call advance_timestep in user code after solve, so any solutions
     199             :       // we will be storing will be for the next time instance
     200       19905 :       _system.time += _system.deltat;
     201             :     }
     202             :     else
     203             :     {
     204             :       // We are here because of a call to advance_timestep that happens
     205             :       // via solve, the very first solve. All we are doing here is storing
     206             :       // the initial condition. The actual solution computed via this solve
     207             :       // will be stored when we call advance_timestep in the user's timestep loop
     208        1839 :       first_solve = false;
     209             :     }
     210             : 
     211             :   // If the user has attached a memory or file solution history object
     212             :   // to the solver, this will store the current solution indexed with
     213             :   // the current time
     214       21744 :   solution_history->store(false, _system.time);
     215             : 
     216             :   NumericVector<Number> & old_nonlinear_soln =
     217       21744 :     _system.get_vector("_old_nonlinear_solution");
     218             :   NumericVector<Number> & nonlinear_solution =
     219       21744 :     *(_system.solution);
     220             : 
     221       21744 :   old_nonlinear_soln = nonlinear_solution;
     222             : 
     223             :   old_nonlinear_soln.localize
     224       21744 :     (*old_local_nonlinear_solution,
     225       21744 :      _system.get_dof_map().get_send_list());
     226       21744 : }
     227             : 
     228       11760 : std::pair<unsigned int, Real> UnsteadySolver::adjoint_solve(const QoISet & qoi_indices)
     229             : {
     230       11760 :   std::pair<unsigned int, Real> adjoint_output = _system.ImplicitSystem::adjoint_solve(qoi_indices);
     231             : 
     232             :   // Record the deltat we used for this adjoint timestep. This was determined completely
     233             :   // by SolutionHistory::retrieve methods. The adjoint_solve methods should never change deltat.
     234       11760 :   last_deltat = _system.deltat;
     235             : 
     236       11760 :   return adjoint_output;
     237             : }
     238             : 
     239        8400 : void UnsteadySolver::adjoint_advance_timestep ()
     240             : {
     241             :   // Call the store function to store the adjoint we have computed (or
     242             :   // for first_adjoint_step, the adjoint initial condition) in this
     243             :   // time step for the time instance.
     244        8400 :   solution_history->store(true, _system.time);
     245             : 
     246             :   // Before moving to the next time instant, copy over the current adjoint solutions into _old_adjoint_solutions
     247       22680 :   for(auto i : make_range(_system.n_qois()))
     248             :   {
     249       14688 :    std::string old_adjoint_solution_name = "_old_adjoint_solution";
     250       13872 :    old_adjoint_solution_name+= std::to_string(i);
     251       14688 :    NumericVector<Number> & old_adjoint_solution_i = _system.get_vector(old_adjoint_solution_name);
     252       14280 :    NumericVector<Number> & adjoint_solution_i = _system.get_adjoint_solution(i);
     253       14280 :    old_adjoint_solution_i = adjoint_solution_i;
     254             :   }
     255             : 
     256        8400 :   _system.time -= _system.deltat;
     257             : 
     258             :   // Retrieve the primal solution vectors at this new (or for
     259             :   // first_adjoint_step, initial) time instance. These provide the
     260             :   // data to solve the adjoint problem for the next time instance.
     261        8400 :   solution_history->retrieve(true, _system.time);
     262             : 
     263             :   // Dont forget to localize the old_nonlinear_solution !
     264        8400 :   _system.get_vector("_old_nonlinear_solution").localize
     265        8400 :     (*old_local_nonlinear_solution,
     266        8400 :      _system.get_dof_map().get_send_list());
     267        8400 : }
     268             : 
     269       21560 : void UnsteadySolver::retrieve_timestep()
     270             : {
     271             :   // Retrieve all the stored vectors at the current time
     272       21560 :   solution_history->retrieve(false, _system.time);
     273             : 
     274             :   // Dont forget to localize the old_nonlinear_solution !
     275       21560 :   _system.get_vector("_old_nonlinear_solution").localize
     276       21560 :     (*old_local_nonlinear_solution,
     277       21560 :      _system.get_dof_map().get_send_list());
     278       21560 : }
     279             : 
     280        3360 : void UnsteadySolver::integrate_adjoint_sensitivity(const QoISet & qois, const ParameterVector & parameter_vector, SensitivityData & sensitivities)
     281             : {
     282             :   // CURRENTLY using the trapezoidal rule to integrate each timestep
     283             :   // (f(t_j) + f(t_j+1))/2 (t_j+1 - t_j)
     284             :   // Fix me: This function needs to be moved to the EulerSolver classes like the
     285             :   // other integrate_timestep functions, and use an integration rule consistent with
     286             :   // the theta method used for the time integration.
     287             : 
     288             :   // Get t_j
     289        3360 :   Real time_left = _system.time;
     290             : 
     291             :   // Left side sensitivities to hold f(t_j)
     292        3456 :   SensitivityData sensitivities_left(qois, _system, parameter_vector);
     293             : 
     294             :   // Get f(t_j)
     295        3360 :   _system.adjoint_qoi_parameter_sensitivity(qois, parameter_vector, sensitivities_left);
     296             : 
     297             :   // Advance to t_j+1
     298        3360 :   _system.time = _system.time + _system.deltat;
     299             : 
     300             :   // Get t_j+1
     301          96 :   Real time_right = _system.time;
     302             : 
     303             :   // Right side sensitivities f(t_j+1)
     304        3456 :   SensitivityData sensitivities_right(qois, _system, parameter_vector);
     305             : 
     306             :   // Remove the sensitivity rhs vector from system since we did not write it to file and it cannot be retrieved
     307        3360 :   _system.remove_vector("sensitivity_rhs0");
     308             : 
     309             :   // Retrieve the primal and adjoint solutions at the current timestep
     310        3360 :   retrieve_timestep();
     311             : 
     312             :   // Get f(t_j+1)
     313        3360 :   _system.adjoint_qoi_parameter_sensitivity(qois, parameter_vector, sensitivities_right);
     314             : 
     315             :   // Remove the sensitivity rhs vector from system since we did not write it to file and it cannot be retrieved
     316        3360 :   _system.remove_vector("sensitivity_rhs0");
     317             : 
     318             :   // Get the contributions for each sensitivity from this timestep
     319          96 :   const auto pv_size = parameter_vector.size();
     320        9888 :   for (auto i : make_range(qois.size(_system)))
     321        6720 :     for (auto j : make_range(pv_size))
     322        3648 :       sensitivities[i][j] = ( (sensitivities_left[i][j] + sensitivities_right[i][j])/2. )*(time_right - time_left);
     323        3360 : }
     324             : 
     325           0 : void UnsteadySolver::integrate_qoi_timestep()
     326             : {
     327           0 :   libmesh_not_implemented();
     328             : }
     329             : 
     330             : #ifdef LIBMESH_ENABLE_AMR
     331           0 : void UnsteadySolver::integrate_adjoint_refinement_error_estimate(AdjointRefinementEstimator & /*adjoint_refinement_error_estimator*/, ErrorVector & /*QoI_elementwise_error*/)
     332             : {
     333           0 :   libmesh_not_implemented();
     334             : }
     335             : #endif // LIBMESH_ENABLE_AMR
     336             : 
     337   176326912 : Number UnsteadySolver::old_nonlinear_solution(const dof_id_type global_dof_number)
     338             :   const
     339             : {
     340    15400200 :   libmesh_assert_less (global_dof_number, _system.get_dof_map().n_dofs());
     341    15400200 :   libmesh_assert_less (global_dof_number, old_local_nonlinear_solution->size());
     342             : 
     343   176326912 :   return (*old_local_nonlinear_solution)(global_dof_number);
     344             : }
     345             : 
     346             : 
     347             : 
     348           0 : Real UnsteadySolver::du(const SystemNorm & norm) const
     349             : {
     350             : 
     351             :   std::unique_ptr<NumericVector<Number>> solution_copy =
     352           0 :     _system.solution->clone();
     353             : 
     354           0 :   solution_copy->add(-1., _system.get_vector("_old_nonlinear_solution"));
     355             : 
     356           0 :   solution_copy->close();
     357             : 
     358           0 :   return _system.calculate_norm(*solution_copy, norm);
     359           0 : }
     360             : 
     361           0 : void UnsteadySolver::update()
     362             : {
     363             :   // Dont forget to localize the old_nonlinear_solution !
     364           0 :   _system.get_vector("_old_nonlinear_solution").localize
     365           0 :     (*old_local_nonlinear_solution,
     366           0 :      _system.get_dof_map().get_send_list());
     367           0 : }
     368             : 
     369             : } // namespace libMesh

Generated by: LCOV version 1.14