libMesh
Loading...
Searching...
No Matches
twostep_time_solver.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 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
35namespace 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
298std::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
377void 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
400void 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
This class wraps another UnsteadySolver derived class, and compares the results of timestepping with ...
virtual Real calculate_norm(System &, NumericVector< Number > &)
A helper function to calculate error norms.
Real completed_timestep_size
The adaptive time solver's have two notions of deltat.
Real min_deltat
Do not allow the adaptive time solver to select deltat < min_deltat.
Real upper_tolerance
This tolerance is the maximum relative error between an exact time integration and a single time step...
std::unique_ptr< UnsteadySolver > core_time_solver
This object is used to take timesteps.
virtual void advance_timestep() override
This method advances the solution to the next timestep, after a solve() has been performed.
Real max_deltat
Do not allow the adaptive time solver to select deltat > max_deltat.
Real target_tolerance
This tolerance is the target relative error between an exact time integration and a single time step ...
bool global_tolerance
This flag, which is true by default, grows (shrinks) the timestep based on the expected global accura...
Real max_growth
Do not allow the adaptive time solver to select a new deltat greater than max_growth times the old de...
This class implements a "brute force" goal-oriented error estimator which computes an estimate of err...
QoISet & qoi_set()
Access to the QoISet (default: weight all QoIs equally) to use when computing errors.
This class provides a specific system class.
Definition diff_system.h:57
Real deltat
For time-dependent problems, this is the amount delta t to advance the solution in time.
The ErrorVector is a specialization of the StatisticsVector for error data computed on a finite eleme...
virtual std::unique_ptr< NumericVector< T > > clone() const =0
Data structure for specifying which Parameters should be independent variables in a parameter sensiti...
std::size_t size() const
Data structure for specifying which Quantities of Interest should be calculated in an adjoint or a pa...
Definition qoi_set.h:46
std::size_t size(const System &sys) const
Definition qoi_set.C:35
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
Data structure for holding completed parameter sensitivity calculations.
void set_qoi(unsigned int qoi_index, Number qoi_value)
Definition system.C:2176
Number get_qoi_value(unsigned int qoi_index) const
Definition system.C:2184
Real time
For time-dependent problems, this is the time t at the beginning of the current timestep.
Definition system.h:1677
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition system.h:1655
unsigned int n_qois() const
Number of currently active quantities of interest.
Definition system.h:2562
void set_qoi_error_estimate(unsigned int qoi_index, Number qoi_error_estimate)
Definition system.C:2204
Real calculate_norm(const NumericVector< Number > &v, unsigned int var, FEMNormType norm_type, std::set< unsigned int > *skip_dimensions=nullptr) const
Definition system.C:1511
Number get_qoi_error_estimate_value(unsigned int qoi_index) const
Definition system.C:2211
unsigned int n_vars() const
Definition system.C:2674
NumericVector< Number > & get_adjoint_solution(unsigned int i=0)
Definition system.C:1232
const NumericVector< Number > & get_vector(std::string_view vec_name) const
Definition system.C:931
sys_type & _system
A reference to the system we are solving.
Real last_deltat
The deltat for the last completed timestep before the current one.
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 ...
bool quiet
Print extra debugging information if quiet == false.
~TwostepTimeSolver()
Destructor.
virtual void solve() override
This method solves for the solution at the next timestep.
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.
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...
TwostepTimeSolver(sys_type &s)
Constructor.
virtual void integrate_qoi_timestep() override
A method to integrate the system::QoI functionals.
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.
bool first_solve
A bool that will be true the first time solve() is called, and false thereafter.
The libMesh namespace provides an interface to certain functionality in the library.
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:153
libmesh_assert(ctx)
OStreamProxy out
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
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:176