libMesh
twostep_time_solver.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2024 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 
42 
43 {
44  // We start with a reasonable time solver: implicit Euler
45  core_time_solver = std::make_unique<EulerSolver>(s);
46 }
47 
48 
49 
51 
52 
53 
55 {
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  if (first_solve)
62  {
64  first_solve = false;
65  }
66 
67  // We may have to repeat timesteps entirely if our error is bad
68  // enough
69  bool max_tolerance_met = false;
70 
71  // Calculating error values each time
72  Real single_norm(0.), double_norm(0.), error_norm(0.),
73  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  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  core_time_solver->reduce_deltat_on_diffsolver_failure =
87 
88  if (!quiet)
89  {
90  libMesh::out << "\n === Computing adaptive timestep === "
91  << std::endl;
92  }
93 
94  // Use the double-length timestep first (so the
95  // old_nonlinear_solution won't have to change)
96  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  _system.solution->clone();
102  std::unique_ptr<NumericVector<Number>> old_solution =
103  _system.get_vector("_old_nonlinear_solution").clone();
104 
105  double_norm = calculate_norm(_system, *double_solution);
106  if (!quiet)
107  {
108  libMesh::out << "Double norm = " << double_norm << std::endl;
109  }
110 
111  // Then reset the initial guess for our single-length calcs
112  *(_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  core_time_solver->reduce_deltat_on_diffsolver_failure = 0;
121 
122  old_time = _system.time;
123  old_deltat = _system.deltat;
124  _system.deltat *= 0.5;
125 
126  // Attempt the 'half timestep solve'
127  core_time_solver->solve();
128 
129  // If we successfully completed the solve, let the time solver know the deltat used
130  this->last_deltat = _system.deltat;
131 
132  // Increment system.time, and save the half solution to solution history
133  core_time_solver->advance_timestep();
134 
135  core_time_solver->solve();
136 
137  single_norm = calculate_norm(_system, *_system.solution);
138  if (!quiet)
139  {
140  libMesh::out << "Single norm = " << single_norm << std::endl;
141  }
142 
143  // Reset the core_time_solver's reduce_deltat... value.
144  core_time_solver->reduce_deltat_on_diffsolver_failure =
146 
147  // Find the relative error
148  *double_solution -= *(_system.solution);
149  error_norm = calculate_norm(_system, *double_solution);
150  relative_error = error_norm / old_deltat /
151  std::max(double_norm, single_norm);
152 
153  // If the relative error makes no sense, we're done
154  if (!double_norm && !single_norm)
155  return;
156 
157  if (!quiet)
158  {
159  libMesh::out << "Error norm = " << error_norm << std::endl;
160  libMesh::out << "Local relative error = "
161  << (error_norm /
162  std::max(double_norm, single_norm))
163  << std::endl;
164  libMesh::out << "Global relative error = "
165  << (error_norm / old_deltat /
166  std::max(double_norm, single_norm))
167  << std::endl;
168  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  if (this->upper_tolerance < 0)
174  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  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  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  _system.get_vector("_old_nonlinear_solution") = *old_solution;
190  _system.time = old_time;
191  _system.deltat = old_deltat;
192 
193  // Update to localize the old nonlinear solution
194  core_time_solver->update();
195 
196  // Reset the initial guess for our next try
197  *(_system.solution) =
198  _system.get_vector("_old_nonlinear_solution");
199 
200  // Chop delta t in half
201  _system.deltat /= 2.;
202 
203  if (!quiet)
204  {
205  libMesh::out << "Failed to meet upper error tolerance"
206  << std::endl;
207  libMesh::out << "Retrying with delta t = "
208  << _system.deltat << std::endl;
209  }
210  }
211  else
212  max_tolerance_met = true;
213 
214  }
215 
216  // We ended up taking two half steps of size system.deltat to
217  // march our last time step.
218  this->last_deltat = _system.deltat;
220 
221  // TimeSolver::solve methods should leave system.time unchanged
222  _system.time = old_time;
223 
224  // Compare the relative error to the tolerance and adjust deltat
225  _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  if (this->target_tolerance < 0)
230  this->target_tolerance = -this->target_tolerance * relative_error;
231 
232  const Real global_shrink_or_growth_factor =
233  std::pow(this->target_tolerance / relative_error,
234  static_cast<Real>(1. / core_time_solver->error_order()));
235 
236  const Real local_shrink_or_growth_factor =
237  std::pow(this->target_tolerance /
238  (error_norm/std::max(double_norm, single_norm)),
239  static_cast<Real>(1. / (core_time_solver->error_order()+1.)));
240 
241  if (!quiet)
242  {
243  libMesh::out << "The global growth/shrink factor is: "
244  << global_shrink_or_growth_factor << std::endl;
245  libMesh::out << "The local growth/shrink factor is: "
246  << 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  Real shrink_or_growth_factor =
256  this->global_tolerance ? global_shrink_or_growth_factor :
257  local_shrink_or_growth_factor;
258 
259  if (this->max_growth && this->max_growth < shrink_or_growth_factor)
260  {
261  if (!quiet && this->global_tolerance)
262  {
263  libMesh::out << "delta t is constrained by max_growth" << std::endl;
264  }
265  shrink_or_growth_factor = this->max_growth;
266  }
267 
268  _system.deltat *= shrink_or_growth_factor;
269 
270  // Restrict deltat to max-allowable value if necessary
271  if ((this->max_deltat != 0.0) && (_system.deltat > this->max_deltat))
272  {
273  if (!quiet)
274  {
275  libMesh::out << "delta t is constrained by maximum-allowable delta t."
276  << std::endl;
277  }
278  _system.deltat = this->max_deltat;
279  }
280 
281  // Restrict deltat to min-allowable value if necessary
282  if ((this->min_deltat != 0.0) && (_system.deltat < this->min_deltat))
283  {
284  if (!quiet)
285  {
286  libMesh::out << "delta t is constrained by minimum-allowable delta t."
287  << std::endl;
288  }
289  _system.deltat = this->min_deltat;
290  }
291 
292  if (!quiet)
293  {
294  libMesh::out << "new delta t = " << _system.deltat << std::endl;
295  }
296 }
297 
298 std::pair<unsigned int, Real> TwostepTimeSolver::adjoint_solve (const QoISet & qoi_indices)
299 {
300  // Take the first adjoint 'half timestep'
301  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  for(auto i : make_range(_system.n_qois()))
306  {
307  for(auto j : make_range(_system.n_vars()))
308  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.
314 
315  // Adjoint advance the timestep
316  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  Real temp_deltat = _system.deltat;
325 
326  // The second half timestep
327  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.
332  _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.
337 
338  return full_adjoint_output;
339 }
340 
342 {
343  // Vectors to hold qoi contributions from the first and second half timesteps
344  std::vector<Number> qois_first_half(_system.n_qois(), 0.0);
345  std::vector<Number> qois_second_half(_system.n_qois(), 0.0);
346 
347  // First half contribution
348  core_time_solver->integrate_qoi_timestep();
349 
350  for (auto j : make_range(_system.n_qois()))
351  {
352  qois_first_half[j] = _system.get_qoi_value(j);
353  }
354 
355  // Second half contribution
356  core_time_solver->integrate_qoi_timestep();
357 
358  for (auto j : make_range(_system.n_qois()))
359  {
360  qois_second_half[j] = _system.get_qoi_value(j);
361  }
362 
363  // Zero out the system.qoi vector
364  for (auto j : make_range(_system.n_qois()))
365  {
366  _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  for (auto j : make_range(_system.n_qois()))
372  {
373  _system.set_qoi(j, qois_first_half[j] + qois_second_half[j]);
374  }
375 }
376 
377 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  SensitivityData sensitivities_first_half(qois, _system, parameter_vector);
384 
385  core_time_solver->integrate_adjoint_sensitivity(qois, parameter_vector, sensitivities_first_half);
386 
387  // Second half timestep
388  SensitivityData sensitivities_second_half(qois, _system, parameter_vector);
389 
390  core_time_solver->integrate_adjoint_sensitivity(qois, parameter_vector, sensitivities_second_half);
391 
392  // Get the contributions for each sensitivity from this timestep
393  const auto pv_size = parameter_vector.size();
394  for (auto i : make_range(qois.size(_system)))
395  for (auto j : make_range(pv_size))
396  sensitivities[i][j] = sensitivities_first_half[i][j] + sensitivities_second_half[i][j];
397 }
398 
399 #ifdef LIBMESH_ENABLE_AMR
400 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  std::vector<Number> qoi_error_estimates_first_half(_system.n_qois());
406  std::vector<Number> qoi_error_estimates_second_half(_system.n_qois());
407 
408  // First half timestep
409  ErrorVector QoI_elementwise_error_first_half;
410 
411  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  for (auto j : make_range(_system.n_qois()))
415  {
416  // Skip this QoI if not in the QoI Set
417  if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
418  {
419  qoi_error_estimates_first_half[j] = _system.get_qoi_error_estimate_value(j);
420  }
421  }
422 
423  // Second half timestep
424  ErrorVector QoI_elementwise_error_second_half;
425 
426  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  for (auto j : make_range(_system.n_qois()))
430  {
431  // Skip this QoI if not in the QoI Set
432  if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
433  {
434  qoi_error_estimates_second_half[j] = _system.get_qoi_error_estimate_value(j);
435  }
436  }
437 
438  // Error contribution from this timestep
439  for (auto i : index_range(QoI_elementwise_error))
440  QoI_elementwise_error[i] = QoI_elementwise_error_first_half[i] + QoI_elementwise_error_second_half[i];
441 
442  for (auto j : make_range(_system.n_qois()))
443  {
444  // Skip this QoI if not in the QoI Set
445  if (adjoint_refinement_error_estimator.qoi_set().has_index(j))
446  {
447  _system.set_qoi_error_estimate(j, qoi_error_estimates_first_half[j] + qoi_error_estimates_second_half[j]);
448  }
449  }
450 }
451 #endif // LIBMESH_ENABLE_AMR
452 
453 } // namespace libMesh
bool quiet
Print extra debugging information if quiet == false.
Definition: time_solver.h:230
virtual Real calculate_norm(System &, NumericVector< Number > &)
A helper function to calculate error norms.
Real time
For time-dependent problems, this is the time t at the beginning of the current timestep.
Definition: system.h:1595
QoISet & qoi_set()
Access to the QoISet (default: weight all QoIs equally) to use when computing errors.
Real completed_timestep_size
The adaptive time solver&#39;s have two notions of deltat.
std::size_t size() const
Data structure for specifying which Parameters should be independent variables in a parameter sensiti...
This class implements a "brute force" goal-oriented error estimator which computes an estimate of err...
Data structure for specifying which Quantities of Interest should be calculated in an adjoint or a pa...
Definition: qoi_set.h:45
Number get_qoi_value(unsigned int qoi_index) const
Definition: system.C:2334
unsigned int n_qois() const
Number of currently active quantities of interest.
Definition: system.h:2516
The ErrorVector is a specialization of the StatisticsVector for error data computed on a finite eleme...
Definition: error_vector.h:50
Real target_tolerance
This tolerance is the target relative error between an exact time integration and a single time step ...
virtual std::unique_ptr< NumericVector< T > > clone() const =0
The libMesh namespace provides an interface to certain functionality in the library.
virtual void integrate_qoi_timestep() override
A method to integrate the system::QoI functionals.
virtual void integrate_adjoint_refinement_error_estimate(AdjointRefinementEstimator &adjoint_refinement_error_estimator, ErrorVector &QoI_elementwise_error) override
A method to compute the adjoint refinement error estimate at the current timestep.
This class provides a specific system class.
Definition: diff_system.h:54
bool has_index(std::size_t) const
Return whether or not this index is in the set to be calculated.
Definition: qoi_set.h:224
bool global_tolerance
This flag, which is true by default, grows (shrinks) the timestep based on the expected global accura...
sys_type & _system
A reference to the system we are solving.
Definition: time_solver.h:312
Real max_deltat
Do not allow the adaptive time solver to select deltat > max_deltat.
T pow(const T &x)
Definition: utility.h:328
virtual std::pair< unsigned int, Real > adjoint_solve(const QoISet &qoi_indices) override
This method solves for the adjoint solution at the next adjoint timestep (or a steady state adjoint s...
Data structure for holding completed parameter sensitivity calculations.
Real deltat
For time-dependent problems, this is the amount delta t to advance the solution in time...
Definition: diff_system.h:278
Real upper_tolerance
This tolerance is the maximum relative error between an exact time integration and a single time step...
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition: system.h:1573
Real calculate_norm(const NumericVector< Number > &v, unsigned int var, FEMNormType norm_type, std::set< unsigned int > *skip_dimensions=nullptr) const
Definition: system.C:1672
libmesh_assert(ctx)
This class wraps another UnsteadySolver derived class, and compares the results of timestepping with ...
unsigned int reduce_deltat_on_diffsolver_failure
This value (which defaults to zero) is the number of times the TimeSolver is allowed to halve deltat ...
Definition: time_solver.h:259
virtual void integrate_adjoint_sensitivity(const QoISet &qois, const ParameterVector &parameter_vector, SensitivityData &sensitivities) override
A method to integrate the adjoint sensitivity w.r.t a given parameter vector.
std::size_t size(const System &sys) const
Definition: qoi_set.C:35
TwostepTimeSolver(sys_type &s)
Constructor.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
void set_qoi_error_estimate(unsigned int qoi_index, Number qoi_error_estimate)
Definition: system.C:2354
OStreamProxy out
~TwostepTimeSolver()
Destructor.
Real min_deltat
Do not allow the adaptive time solver to select deltat < min_deltat.
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:134
NumericVector< Number > & get_adjoint_solution(unsigned int i=0)
Definition: system.C:1193
void set_qoi(unsigned int qoi_index, Number qoi_value)
Definition: system.C:2326
Real last_deltat
The deltat for the last completed timestep before the current one.
Definition: time_solver.h:332
virtual void advance_timestep() override
This method advances the solution to the next timestep, after a solve() has been performed.
Number get_qoi_error_estimate_value(unsigned int qoi_index) const
Definition: system.C:2361
virtual void solve() override
This method solves for the solution at the next timestep.
unsigned int n_vars() const
Definition: system.h:2349
std::unique_ptr< UnsteadySolver > core_time_solver
This object is used to take timesteps.
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:111
const NumericVector< Number > & get_vector(std::string_view vec_name) const
Definition: system.C:918
bool first_solve
A bool that will be true the first time solve() is called, and false thereafter.
Real max_growth
Do not allow the adaptive time solver to select a new deltat greater than max_growth times the old de...