LCOV - code coverage report
Current view: top level - src/solvers - twostep_time_solver.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 141 166 84.9 %
Date: 2025-08-19 19:27:09 Functions: 8 8 100.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             : #include "libmesh/twostep_time_solver.h"
      20             : 
      21             : #include "libmesh/adjoint_refinement_estimator.h"
      22             : #include "libmesh/diff_system.h"
      23             : #include "libmesh/enum_norm_type.h"
      24             : #include "libmesh/error_vector.h"
      25             : #include "libmesh/euler_solver.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             : // C++ includes
      33             : #include <memory>
      34             : 
      35             : namespace libMesh
      36             : {
      37             : 
      38             : 
      39             : 
      40         420 : TwostepTimeSolver::TwostepTimeSolver (sys_type & s)
      41         420 :   : AdaptiveTimeSolver(s)
      42             : 
      43             : {
      44             :   // We start with a reasonable time solver: implicit Euler
      45         420 :   core_time_solver = std::make_unique<EulerSolver>(s);
      46         420 : }
      47             : 
      48             : 
      49             : 
      50         792 : TwostepTimeSolver::~TwostepTimeSolver () = default;
      51             : 
      52             : 
      53             : 
      54        3780 : void TwostepTimeSolver::solve()
      55             : {
      56         108 :   libmesh_assert(core_time_solver.get());
      57             : 
      58             :   // All actual solution history operations are handled by the outer
      59             :   // solver, so the outer solver has to call advance_timestep and
      60             :   // call solution_history->store to store the initial conditions
      61        3780 :   if (first_solve)
      62             :     {
      63         420 :       advance_timestep();
      64         420 :       first_solve = false;
      65             :     }
      66             : 
      67             :   // We may have to repeat timesteps entirely if our error is bad
      68             :   // enough
      69         108 :   bool max_tolerance_met = false;
      70             : 
      71             :   // Calculating error values each time
      72        3780 :   Real single_norm(0.), double_norm(0.), error_norm(0.),
      73         108 :     relative_error(0.);
      74             : 
      75             :   // The loop below will change system time and deltat based on calculations.
      76             :   // We will need to save these for calculating the deltat for the next timestep
      77             :   // after the while loop has converged.
      78             :   Real old_time;
      79             :   Real old_deltat;
      80             : 
      81        7560 :   while (!max_tolerance_met)
      82             :     {
      83             :       // If we've been asked to reduce deltat if necessary, make sure
      84             :       // the core timesolver does so
      85        3888 :       core_time_solver->reduce_deltat_on_diffsolver_failure =
      86        3780 :         this->reduce_deltat_on_diffsolver_failure;
      87             : 
      88        3780 :       if (!quiet)
      89             :         {
      90         108 :           libMesh::out << "\n === Computing adaptive timestep === "
      91         108 :                        << std::endl;
      92             :         }
      93             : 
      94             :       // Use the double-length timestep first (so the
      95             :       // old_nonlinear_solution won't have to change)
      96        3780 :       core_time_solver->solve();
      97             : 
      98             :       // Save a copy of the double-length nonlinear solution
      99             :       // and the old nonlinear solution
     100             :       std::unique_ptr<NumericVector<Number>> double_solution =
     101        3888 :         _system.solution->clone();
     102             :       std::unique_ptr<NumericVector<Number>> old_solution =
     103        3780 :         _system.get_vector("_old_nonlinear_solution").clone();
     104             : 
     105        3888 :       double_norm = calculate_norm(_system, *double_solution);
     106        3780 :       if (!quiet)
     107             :         {
     108         108 :           libMesh::out << "Double norm = " << double_norm << std::endl;
     109             :         }
     110             : 
     111             :       // Then reset the initial guess for our single-length calcs
     112        3888 :       *(_system.solution) = _system.get_vector("_old_nonlinear_solution");
     113             : 
     114             :       // Call two single-length timesteps
     115             :       // Be sure that the core_time_solver does not change the
     116             :       // timestep here.  (This is unlikely because it just succeeded
     117             :       // with a timestep twice as large!)
     118             :       // FIXME: even if diffsolver failure is unlikely, we ought to
     119             :       // do *something* if it happens
     120        3780 :       core_time_solver->reduce_deltat_on_diffsolver_failure = 0;
     121             : 
     122        3780 :       old_time = _system.time;
     123        3780 :       old_deltat = _system.deltat;
     124        3780 :       _system.deltat *= 0.5;
     125             : 
     126             :       // Attempt the 'half timestep solve'
     127        3780 :       core_time_solver->solve();
     128             : 
     129             :       // If we successfully completed the solve, let the time solver know the deltat used
     130        3780 :       this->last_deltat = _system.deltat;
     131             : 
     132             :       // Increment system.time, and save the half solution to solution history
     133        3780 :       core_time_solver->advance_timestep();
     134             : 
     135        3780 :       core_time_solver->solve();
     136             : 
     137        3888 :       single_norm = calculate_norm(_system, *_system.solution);
     138        3780 :       if (!quiet)
     139             :         {
     140         108 :           libMesh::out << "Single norm = " << single_norm << std::endl;
     141             :         }
     142             : 
     143             :       // Reset the core_time_solver's reduce_deltat... value.
     144        3888 :       core_time_solver->reduce_deltat_on_diffsolver_failure =
     145        3780 :         this->reduce_deltat_on_diffsolver_failure;
     146             : 
     147             :       // Find the relative error
     148        3888 :       *double_solution -= *(_system.solution);
     149        3888 :       error_norm  = calculate_norm(_system, *double_solution);
     150        3888 :       relative_error = error_norm / old_deltat /
     151        3780 :         std::max(double_norm, single_norm);
     152             : 
     153             :       // If the relative error makes no sense, we're done
     154        3780 :       if (!double_norm && !single_norm)
     155           0 :         return;
     156             : 
     157        3780 :       if (!quiet)
     158             :         {
     159         108 :           libMesh::out << "Error norm = " << error_norm << std::endl;
     160         108 :           libMesh::out << "Local relative error = "
     161        3672 :                        << (error_norm /
     162         216 :                            std::max(double_norm, single_norm))
     163         108 :                        << std::endl;
     164         108 :           libMesh::out << "Global relative error = "
     165         108 :                        << (error_norm / old_deltat /
     166         216 :                            std::max(double_norm, single_norm))
     167         108 :                        << std::endl;
     168         108 :           libMesh::out << "old delta t = " << old_deltat << std::endl;
     169             :         }
     170             : 
     171             :       // If our upper tolerance is negative, that means we want to set
     172             :       // it based on the first successful time step
     173        3780 :       if (this->upper_tolerance < 0)
     174           0 :         this->upper_tolerance = -this->upper_tolerance * relative_error;
     175             : 
     176             :       // If we haven't met our upper error tolerance, we'll have to
     177             :       // repeat this timestep entirely
     178        3780 :       if (this->upper_tolerance && relative_error > this->upper_tolerance)
     179             :         {
     180             :           // If we are saving solution histories, the core time solver
     181             :           // will save half solutions, and these solves can be attempted
     182             :           // repeatedly. If we failed to meet the tolerance, erase the
     183             :           // half solution from solution history.
     184           0 :           core_time_solver->get_solution_history().erase(_system.time);
     185             : 
     186             :           // We will be retrying this timestep with deltat/2, so restore
     187             :           // all the necessary state.
     188             :           // FIXME: this probably doesn't work with multistep methods
     189           0 :           _system.get_vector("_old_nonlinear_solution") = *old_solution;
     190           0 :           _system.time = old_time;
     191           0 :           _system.deltat = old_deltat;
     192             : 
     193             :           // Update to localize the old nonlinear solution
     194           0 :           core_time_solver->update();
     195             : 
     196             :           // Reset the initial guess for our next try
     197           0 :           *(_system.solution) =
     198           0 :             _system.get_vector("_old_nonlinear_solution");
     199             : 
     200             :           // Chop delta t in half
     201           0 :           _system.deltat /= 2.;
     202             : 
     203           0 :           if (!quiet)
     204             :             {
     205           0 :               libMesh::out << "Failed to meet upper error tolerance"
     206           0 :                            << std::endl;
     207           0 :               libMesh::out << "Retrying with delta t = "
     208           0 :                            << _system.deltat << std::endl;
     209             :             }
     210             :         }
     211             :       else
     212         108 :         max_tolerance_met = true;
     213             : 
     214        3564 :     }
     215             : 
     216             :   // We ended up taking two half steps of size system.deltat to
     217             :   // march our last time step.
     218        3780 :   this->last_deltat = _system.deltat;
     219        3780 :   this->completed_timestep_size = 2.0*_system.deltat;
     220             : 
     221             :   // TimeSolver::solve methods should leave system.time unchanged
     222        3780 :   _system.time = old_time;
     223             : 
     224             :   // Compare the relative error to the tolerance and adjust deltat
     225        3780 :   _system.deltat = old_deltat;
     226             : 
     227             :   // If our target tolerance is negative, that means we want to set
     228             :   // it based on the first successful time step
     229        3780 :   if (this->target_tolerance < 0)
     230           0 :     this->target_tolerance = -this->target_tolerance * relative_error;
     231             : 
     232             :   const Real global_shrink_or_growth_factor =
     233        3780 :     std::pow(this->target_tolerance / relative_error,
     234        3780 :              static_cast<Real>(1. / core_time_solver->error_order()));
     235             : 
     236             :   const Real local_shrink_or_growth_factor =
     237        7560 :     std::pow(this->target_tolerance /
     238        3888 :              (error_norm/std::max(double_norm, single_norm)),
     239        3780 :              static_cast<Real>(1. / (core_time_solver->error_order()+1.)));
     240             : 
     241        3780 :   if (!quiet)
     242             :     {
     243         108 :       libMesh::out << "The global growth/shrink factor is: "
     244         108 :                    << global_shrink_or_growth_factor << std::endl;
     245         108 :       libMesh::out << "The local growth/shrink factor is: "
     246         108 :                    << local_shrink_or_growth_factor << std::endl;
     247             :     }
     248             : 
     249             :   // The local s.o.g. factor is based on the expected **local**
     250             :   // truncation error for the timestepping method, the global
     251             :   // s.o.g. factor is based on the method's **global** truncation
     252             :   // error.  You can shrink/grow the timestep to attempt to satisfy
     253             :   // either a global or local time-discretization error tolerance.
     254             : 
     255         108 :   Real shrink_or_growth_factor =
     256        3780 :     this->global_tolerance ? global_shrink_or_growth_factor :
     257             :     local_shrink_or_growth_factor;
     258             : 
     259        3780 :   if (this->max_growth && this->max_growth < shrink_or_growth_factor)
     260             :     {
     261        3780 :       if (!quiet && this->global_tolerance)
     262             :         {
     263         108 :           libMesh::out << "delta t is constrained by max_growth" << std::endl;
     264             :         }
     265        3780 :       shrink_or_growth_factor = this->max_growth;
     266             :     }
     267             : 
     268        3780 :   _system.deltat *= shrink_or_growth_factor;
     269             : 
     270             :   // Restrict deltat to max-allowable value if necessary
     271        3780 :   if ((this->max_deltat != 0.0) && (_system.deltat > this->max_deltat))
     272             :     {
     273           0 :       if (!quiet)
     274             :         {
     275           0 :           libMesh::out << "delta t is constrained by maximum-allowable delta t."
     276           0 :                        << std::endl;
     277             :         }
     278           0 :       _system.deltat = this->max_deltat;
     279             :     }
     280             : 
     281             :   // Restrict deltat to min-allowable value if necessary
     282        3780 :   if ((this->min_deltat != 0.0) && (_system.deltat < this->min_deltat))
     283             :     {
     284           0 :       if (!quiet)
     285             :         {
     286           0 :           libMesh::out << "delta t is constrained by minimum-allowable delta t."
     287           0 :                        << std::endl;
     288             :         }
     289           0 :       _system.deltat = this->min_deltat;
     290             :     }
     291             : 
     292        3780 :   if (!quiet)
     293             :     {
     294        3780 :       libMesh::out << "new delta t = " << _system.deltat << std::endl;
     295             :     }
     296             : }
     297             : 
     298        3780 : std::pair<unsigned int, Real> TwostepTimeSolver::adjoint_solve (const QoISet & qoi_indices)
     299             : {
     300             :   // Take the first adjoint 'half timestep'
     301        3780 :   core_time_solver->adjoint_solve(qoi_indices);
     302             : 
     303             :   // We print the forward 'half solution' norms and we will do so for the adjoints if running in dbg.
     304             :   #ifdef DEBUG
     305         296 :     for(auto i : make_range(_system.n_qois()))
     306             :     {
     307         376 :       for(auto j : make_range(_system.n_vars()))
     308         188 :         libMesh::out<<"||Z_"<<"("<<_system.time<<";"<<i<<","<<j<<")||_H1: "<<_system.calculate_norm(_system.get_adjoint_solution(i), j,H1)<<std::endl;
     309             :     }
     310             :   #endif
     311             : 
     312             :   // Record the sub step deltat we used for the last adjoint solve.
     313        3780 :   last_deltat = _system.deltat;
     314             : 
     315             :   // Adjoint advance the timestep
     316        3780 :   core_time_solver->adjoint_advance_timestep();
     317             : 
     318             :  // We have to contend with the fact that the delta_t set by SolutionHistory will not be the
     319             :  // delta_t for the adjoint solve. At time t_i, the adjoint solve uses the same delta_t
     320             :  // as the primal solve, pulling the adjoint solution from t_i+1 to t_i.
     321             :  // FSH however sets delta_t to the value which takes us from t_i to t_i-1.
     322             :  // Therefore use the last_deltat for the solve and reset system delta_t after the solve.
     323        3780 :   Real temp_deltat = _system.deltat;
     324        3780 :   _system.deltat = last_deltat;
     325             : 
     326             :   // The second half timestep
     327        3780 :   std::pair<unsigned int, Real> full_adjoint_output = core_time_solver->adjoint_solve(qoi_indices);
     328             : 
     329             :   // Record the sub step deltat we used for the last adjoint solve and reset the system deltat to the
     330             :   // value set by SolutionHistory.
     331        3780 :   last_deltat = _system.deltat;
     332        3780 :   _system.deltat = temp_deltat;
     333             : 
     334             :   // Record the total size of the last timestep, for a 2StepTS, this is
     335             :   // simply twice the deltat for each sub(half) step.
     336        3780 :   this->completed_timestep_size = 2.0*_system.deltat;
     337             : 
     338        3780 :   return full_adjoint_output;
     339             : }
     340             : 
     341        2800 : void TwostepTimeSolver::integrate_qoi_timestep()
     342             : {
     343             :   // Vectors to hold qoi contributions from the first and second half timesteps
     344        2960 :   std::vector<Number> qois_first_half(_system.n_qois(), 0.0);
     345        2960 :   std::vector<Number> qois_second_half(_system.n_qois(), 0.0);
     346             : 
     347             :   // First half contribution
     348        2800 :   core_time_solver->integrate_qoi_timestep();
     349             : 
     350        8400 :   for (auto j : make_range(_system.n_qois()))
     351             :   {
     352        5600 :     qois_first_half[j] = _system.get_qoi_value(j);
     353             :   }
     354             : 
     355             :   // Second half contribution
     356        2800 :   core_time_solver->integrate_qoi_timestep();
     357             : 
     358        8400 :   for (auto j : make_range(_system.n_qois()))
     359             :   {
     360        5600 :     qois_second_half[j] = _system.get_qoi_value(j);
     361             :   }
     362             : 
     363             :   // Zero out the system.qoi vector
     364        8400 :   for (auto j : make_range(_system.n_qois()))
     365             :   {
     366        5600 :     _system.set_qoi(j, 0.0);
     367             :   }
     368             : 
     369             :   // Add the contributions from the two halftimesteps to get the full QoI
     370             :   // contribution from this timestep
     371        8400 :   for (auto j : make_range(_system.n_qois()))
     372             :   {
     373        5920 :     _system.set_qoi(j, qois_first_half[j] + qois_second_half[j]);
     374             :   }
     375        2800 : }
     376             : 
     377         980 : void TwostepTimeSolver::integrate_adjoint_sensitivity(const QoISet & qois, const ParameterVector & parameter_vector, SensitivityData & sensitivities)
     378             : {
     379             :   // We are using the trapezoidal rule to integrate each timestep, and then pooling the contributions here.
     380             :   // (f(t_j) + f(t_j+1/2))/2 (t_j+1/2 - t_j) + (f(t_j+1/2) + f(t_j+1))/2 (t_j+1 - t_j+1/2)
     381             : 
     382             :   // First half timestep
     383        1008 :   SensitivityData sensitivities_first_half(qois, _system, parameter_vector);
     384             : 
     385         980 :   core_time_solver->integrate_adjoint_sensitivity(qois, parameter_vector, sensitivities_first_half);
     386             : 
     387             :   // Second half timestep
     388        1008 :   SensitivityData sensitivities_second_half(qois, _system, parameter_vector);
     389             : 
     390         980 :   core_time_solver->integrate_adjoint_sensitivity(qois, parameter_vector, sensitivities_second_half);
     391             : 
     392             :   // Get the contributions for each sensitivity from this timestep
     393          28 :   const auto pv_size = parameter_vector.size();
     394        2912 :   for (auto i : make_range(qois.size(_system)))
     395        1960 :     for (auto j : make_range(pv_size))
     396        1064 :       sensitivities[i][j] = sensitivities_first_half[i][j] + sensitivities_second_half[i][j];
     397         980 : }
     398             : 
     399             : #ifdef LIBMESH_ENABLE_AMR
     400        2800 : void TwostepTimeSolver::integrate_adjoint_refinement_error_estimate(AdjointRefinementEstimator & adjoint_refinement_error_estimator, ErrorVector & QoI_elementwise_error)
     401             : {
     402             :   // We use a numerical integration scheme consistent with the theta used for the timesolver.
     403             : 
     404             :   // Create first and second half error estimate vectors of the right size
     405        2960 :   std::vector<Number> qoi_error_estimates_first_half(_system.n_qois());
     406        2960 :   std::vector<Number> qoi_error_estimates_second_half(_system.n_qois());
     407             : 
     408             :   // First half timestep
     409         160 :   ErrorVector QoI_elementwise_error_first_half;
     410             : 
     411        2800 :   core_time_solver->integrate_adjoint_refinement_error_estimate(adjoint_refinement_error_estimator, QoI_elementwise_error_first_half);
     412             : 
     413             :   // Also get the first 'half step' spatially integrated errors for all the QoIs in the QoI set
     414        8400 :   for (auto j : make_range(_system.n_qois()))
     415             :   {
     416             :     // Skip this QoI if not in the QoI Set
     417        5600 :     if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
     418             :     {
     419        5600 :       qoi_error_estimates_first_half[j] = _system.get_qoi_error_estimate_value(j);
     420             :     }
     421             :   }
     422             : 
     423             :   // Second half timestep
     424         160 :   ErrorVector QoI_elementwise_error_second_half;
     425             : 
     426        2800 :   core_time_solver->integrate_adjoint_refinement_error_estimate(adjoint_refinement_error_estimator, QoI_elementwise_error_second_half);
     427             : 
     428             :   // Also get the second 'half step' spatially integrated errors for all the QoIs in the QoI set
     429        8400 :   for (auto j : make_range(_system.n_qois()))
     430             :   {
     431             :     // Skip this QoI if not in the QoI Set
     432        5600 :     if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
     433             :     {
     434        5600 :       qoi_error_estimates_second_half[j] = _system.get_qoi_error_estimate_value(j);
     435             :     }
     436             :   }
     437             : 
     438             :   // Error contribution from this timestep
     439        2800 :   for (auto i : index_range(QoI_elementwise_error))
     440           0 :     QoI_elementwise_error[i] = QoI_elementwise_error_first_half[i] + QoI_elementwise_error_second_half[i];
     441             : 
     442        8400 :   for (auto j : make_range(_system.n_qois()))
     443             :   {
     444             :     // Skip this QoI if not in the QoI Set
     445        5600 :     if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
     446             :     {
     447        5920 :       _system.set_qoi_error_estimate(j, qoi_error_estimates_first_half[j] + qoi_error_estimates_second_half[j]);
     448             :     }
     449             :   }
     450        2800 : }
     451             : #endif // LIBMESH_ENABLE_AMR
     452             : 
     453             : } // namespace libMesh

Generated by: LCOV version 1.14