libMesh
unsteady_solver.C
Go to the documentation of this file.
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 
38  : TimeSolver(s),
39  old_local_nonlinear_solution (NumericVector<Number>::build(s.comm()).release()),
40  first_solve (true),
41  first_adjoint_step (true)
42 {
43  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  for(auto j : make_range(s.n_qois()))
49  {
50  old_adjoints[j] = nullptr;
51  }
52 }
53 
54 
55 
57 
58 
59 
61 {
63 
64  _system.add_vector("_old_nonlinear_solution");
65 }
66 
67 
69 {
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  for(auto i : make_range(_system.n_qois()))
76  {
77  std::string old_adjoint_solution_name = "_old_adjoint_solution";
78  old_adjoint_solution_name+= std::to_string(i);
79  _system.add_vector(old_adjoint_solution_name, false, GHOSTED);
80 
81  std::string adjoint_rhs_name = "adjoint_rhs";
82  adjoint_rhs_name+= std::to_string(i);
83  _system.add_vector(adjoint_rhs_name, false, GHOSTED);
84  }
85 
86 }
87 
88 
90 {
92 
93 #ifdef LIBMESH_ENABLE_GHOSTED
96  GHOSTED);
97 #else
99 #endif
100 }
101 
102 
103 
105 {
107 
108 #ifdef LIBMESH_ENABLE_GHOSTED
110  _system.get_dof_map().get_send_list(), false,
111  GHOSTED);
112 #else
114 #endif
115 
116  // localize the old solution
117  NumericVector<Number> & old_nonlinear_soln =
118  _system.get_vector("_old_nonlinear_solution");
119 
120  old_nonlinear_soln.localize
123 }
124 
125 
126 
128 {
129  if (first_solve)
130  {
132  first_solve = false;
133  }
134 
135  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.
140  {
141  bool backtracking_failed =
143 
144  bool max_iterations =
146 
147  if (backtracking_failed || max_iterations)
148  {
149  // Cut timestep in half
150  for (unsigned int nr=0; nr<reduce_deltat_on_diffsolver_failure; ++nr)
151  {
152  _system.deltat *= 0.5;
153  libMesh::out << "Newton backtracking failed. Trying with smaller timestep, dt="
154  << _system.deltat << std::endl;
155 
156  solve_result = _diff_solver->solve();
157 
158  // Check solve results with reduced timestep
159  bool backtracking_still_failed =
161 
162  bool backtracking_max_iterations =
164 
165  if (!backtracking_still_failed && !backtracking_max_iterations)
166  {
167  // Set the successful deltat as the last deltat
169 
170  if (!quiet)
171  libMesh::out << "Reduced dt solve succeeded." << std::endl;
172  return;
173  }
174  }
175 
176  // If we made it here, we still couldn't converge the solve after
177  // reducing deltat
178  libMesh::out << "DiffSolver::solve() did not succeed after "
180  << " attempts." << std::endl;
181  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
188 }
189 
190 
191 
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  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
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  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  solution_history->store(false, _system.time);
215 
216  NumericVector<Number> & old_nonlinear_soln =
217  _system.get_vector("_old_nonlinear_solution");
218  NumericVector<Number> & nonlinear_solution =
219  *(_system.solution);
220 
221  old_nonlinear_soln = nonlinear_solution;
222 
223  old_nonlinear_soln.localize
226 }
227 
228 std::pair<unsigned int, Real> UnsteadySolver::adjoint_solve(const QoISet & qoi_indices)
229 {
230  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.
235 
236  return adjoint_output;
237 }
238 
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  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  for(auto i : make_range(_system.n_qois()))
248  {
249  std::string old_adjoint_solution_name = "_old_adjoint_solution";
250  old_adjoint_solution_name+= std::to_string(i);
251  NumericVector<Number> & old_adjoint_solution_i = _system.get_vector(old_adjoint_solution_name);
252  NumericVector<Number> & adjoint_solution_i = _system.get_adjoint_solution(i);
253  old_adjoint_solution_i = adjoint_solution_i;
254  }
255 
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  solution_history->retrieve(true, _system.time);
262 
263  // Dont forget to localize the old_nonlinear_solution !
264  _system.get_vector("_old_nonlinear_solution").localize
267 }
268 
270 {
271  // Retrieve all the stored vectors at the current time
272  solution_history->retrieve(false, _system.time);
273 
274  // Dont forget to localize the old_nonlinear_solution !
275  _system.get_vector("_old_nonlinear_solution").localize
278 }
279 
280 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  Real time_left = _system.time;
290 
291  // Left side sensitivities to hold f(t_j)
292  SensitivityData sensitivities_left(qois, _system, parameter_vector);
293 
294  // Get f(t_j)
295  _system.adjoint_qoi_parameter_sensitivity(qois, parameter_vector, sensitivities_left);
296 
297  // Advance to t_j+1
299 
300  // Get t_j+1
301  Real time_right = _system.time;
302 
303  // Right side sensitivities f(t_j+1)
304  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  _system.remove_vector("sensitivity_rhs0");
308 
309  // Retrieve the primal and adjoint solutions at the current timestep
311 
312  // Get f(t_j+1)
313  _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  _system.remove_vector("sensitivity_rhs0");
317 
318  // Get the contributions for each sensitivity from this timestep
319  const auto pv_size = parameter_vector.size();
320  for (auto i : make_range(qois.size(_system)))
321  for (auto j : make_range(pv_size))
322  sensitivities[i][j] = ( (sensitivities_left[i][j] + sensitivities_right[i][j])/2. )*(time_right - time_left);
323 }
324 
326 {
327  libmesh_not_implemented();
328 }
329 
330 #ifdef LIBMESH_ENABLE_AMR
331 void UnsteadySolver::integrate_adjoint_refinement_error_estimate(AdjointRefinementEstimator & /*adjoint_refinement_error_estimator*/, ErrorVector & /*QoI_elementwise_error*/)
332 {
333  libmesh_not_implemented();
334 }
335 #endif // LIBMESH_ENABLE_AMR
336 
338  const
339 {
340  libmesh_assert_less (global_dof_number, _system.get_dof_map().n_dofs());
341  libmesh_assert_less (global_dof_number, old_local_nonlinear_solution->size());
342 
343  return (*old_local_nonlinear_solution)(global_dof_number);
344 }
345 
346 
347 
349 {
350 
351  std::unique_ptr<NumericVector<Number>> solution_copy =
352  _system.solution->clone();
353 
354  solution_copy->add(-1., _system.get_vector("_old_nonlinear_solution"));
355 
356  solution_copy->close();
357 
358  return _system.calculate_norm(*solution_copy, norm);
359 }
360 
362 {
363  // Dont forget to localize the old_nonlinear_solution !
364  _system.get_vector("_old_nonlinear_solution").localize
367 }
368 
369 } // namespace libMesh
bool quiet
Print extra debugging information if quiet == false.
Definition: time_solver.h:230
virtual void reinit() override
The reinitialization function.
Real time
For time-dependent problems, this is the time t at the beginning of the current timestep.
Definition: system.h:1615
virtual void integrate_adjoint_refinement_error_estimate(AdjointRefinementEstimator &, ErrorVector &) 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...
std::size_t size() const
std::unique_ptr< DiffSolver > _diff_solver
An implicit linear or nonlinear solver to use at each timestep.
Definition: time_solver.h:302
UnsteadySolver(sys_type &s)
Constructor.
Data structure for specifying which Parameters should be independent variables in a parameter sensiti...
This is a generic class that defines a solver to handle time integration of DifferentiableSystems.
Definition: time_solver.h:63
This class implements a "brute force" goal-oriented error estimator which computes an estimate of err...
virtual void init_data()
The data initialization function.
Definition: time_solver.C:97
Data structure for specifying which Quantities of Interest should be calculated in an adjoint or a pa...
Definition: qoi_set.h:45
unsigned int n_qois() const
Number of currently active quantities of interest.
Definition: system.h:2621
The ErrorVector is a specialization of the StatisticsVector for error data computed on a finite eleme...
Definition: error_vector.h:50
virtual void init_data() override
The data initialization function.
std::unique_ptr< SolutionHistory > solution_history
A std::unique_ptr to a SolutionHistory object.
Definition: time_solver.h:319
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.
Number old_nonlinear_solution(const dof_id_type global_dof_number) const
virtual void reinit()
The reinitialization function.
Definition: time_solver.C:54
Provides a uniform interface to vector storage schemes for different linear algebra libraries...
Definition: vector_fe_ex5.C:44
NumericVector< Number > & add_vector(std::string_view vec_name, const bool projections=true, const ParallelType type=PARALLEL)
Adds the additional vector vec_name to this system.
Definition: system.C:768
This class defines a norm/seminorm to be applied to a NumericVector which contains coefficients in a ...
Definition: system_norm.h:49
dof_id_type n_dofs(const unsigned int vn) const
Definition: dof_map.h:668
The libMesh namespace provides an interface to certain functionality in the library.
dof_id_type n_local_dofs() const
Definition: system.C:158
dof_id_type n_dofs() const
Definition: system.C:121
This class provides a specific system class.
Definition: diff_system.h:54
The DiffSolver reached the maximum allowed number of nonlinear iterations before satisfying any conve...
Definition: diff_solver.h:268
sys_type & _system
A reference to the system we are solving.
Definition: time_solver.h:312
std::vector< std::unique_ptr< NumericVector< Number > > > old_adjoints
A vector of pointers to vectors holding the adjoint solution at the last time step.
virtual void adjoint_advance_timestep() override
This method advances the adjoint solution to the previous timestep, after an adjoint_solve() has been...
void remove_vector(std::string_view vec_name)
Removes the additional vector vec_name from this system.
Definition: system.C:873
virtual ~UnsteadySolver()
Destructor.
std::shared_ptr< NumericVector< Number > > old_local_nonlinear_solution
Serial vector of _system.get_vector("_old_nonlinear_solution") This is a shared_ptr so that it can be...
Data structure for holding completed parameter sensitivity calculations.
virtual void init()
The initialization function.
Definition: time_solver.C:72
virtual void retrieve_timestep() override
This method retrieves all the stored solutions at the current system.time.
virtual void adjoint_qoi_parameter_sensitivity(const QoISet &qoi_indices, const ParameterVector &parameters, SensitivityData &sensitivities) override
Solves for the derivative of each of the system&#39;s quantities of interest q in qoi[qoi_indices] with r...
Real deltat
For time-dependent problems, this is the amount delta t to advance the solution in time...
Definition: diff_system.h:280
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition: system.h:1593
Real calculate_norm(const NumericVector< Number > &v, unsigned int var, FEMNormType norm_type, std::set< unsigned int > *skip_dimensions=nullptr) const
Definition: system.C:1724
auto norm(const T &a) -> decltype(std::abs(a))
Definition: tensor_tools.h:74
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
std::size_t size(const System &sys) const
Definition: qoi_set.C:35
virtual void integrate_qoi_timestep() override
A method to integrate the system::QoI functionals.
virtual void solve() override
This method solves for the solution at the next timestep.
virtual void advance_timestep() override
This method advances the solution to the next timestep, after a solve() has been performed.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
OStreamProxy out
virtual void init_adjoints() override
Add adjoint vectors and old_adjoint_vectors as per the indices of QoISet.
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:140
NumericVector< Number > & get_adjoint_solution(unsigned int i=0)
Definition: system.C:1245
virtual Real du(const SystemNorm &norm) const override
Computes the size of ||u^{n+1} - u^{n}|| in some norm.
The DiffSolver failed to find a descent direction by backtracking (See newton_solver.C)
Definition: diff_solver.h:274
Real last_deltat
The deltat for the last completed timestep before the current one.
Definition: time_solver.h:332
virtual void init() override
The initialization function.
const DofMap & get_dof_map() const
Definition: system.h:2374
virtual void init_adjoints()
Initialize any adjoint related data structures, based on the number of qois.
Definition: time_solver.C:83
const std::vector< dof_id_type > & get_send_list() const
Definition: dof_map.h:526
const NumericVector< Number > & get_vector(std::string_view vec_name) const
Definition: system.C:943
uint8_t dof_id_type
Definition: id_types.h:67
bool first_solve
A bool that will be true the first time solve() is called, and false thereafter.
virtual void localize(std::vector< T > &v_local) const =0
Creates a copy of the global vector in the local vector v_local.