Serac  0.1
Serac is an implicit thermal strucural mechanics simulation code.
odes.cpp
1 // Copyright (c) 2019-2023, Lawrence Livermore National Security, LLC and
2 // other Serac Project Developers. See the top-level LICENSE file for
3 // details.
4 //
5 // SPDX-License-Identifier: (BSD-3-Clause)
6 
8 
9 namespace serac::mfem_ext {
10 
12  : mfem::SecondOrderTimeDependentOperator(n, 0.0), state_(std::move(state)), solver_(solver), bcs_(bcs), zero_(n)
13 {
14  zero_ = 0.0;
15  U_minus_.SetSize(n);
16  U_.SetSize(n);
17  U_plus_.SetSize(n);
18  dU_dt_.SetSize(n);
19  d2U_dt2_.SetSize(n);
20 }
21 
23 {
24  timestepper_ = timestepper;
25 
26  switch (timestepper) {
28  second_order_ode_solver_ = std::make_unique<mfem::NewmarkSolver>();
29  break;
31  second_order_ode_solver_ = std::make_unique<mfem::HHTAlphaSolver>();
32  break;
34  second_order_ode_solver_ = std::make_unique<mfem::WBZAlphaSolver>();
35  break;
37  // WARNING: apparently mfem's implementation of AverageAccelerationSolver
38  // is NOT equivalent to Newmark (beta = 0.25, gamma = 0.5), so the
39  // stability analysis of using DirichletEnforcementMethod::DirectControl
40  // with Newmark methods does not apply.
41  //
42  // TODO: do a more thorough stability analysis for mfem::GeneralizedAlpha2Solver
43  // to characterize which parameter combinations work with time-varying
44  // dirichlet constraints
45  SLIC_WARNING_ROOT(
46  "Cannot guarantee stability for AverageAcceleration with time-dependent Dirichlet Boundary Conditions");
47  second_order_ode_solver_ = std::make_unique<mfem::AverageAccelerationSolver>();
48  break;
50  second_order_ode_solver_ = std::make_unique<mfem::LinearAccelerationSolver>();
51  break;
53  second_order_ode_solver_ = std::make_unique<mfem::CentralDifferenceSolver>();
54  break;
56  second_order_ode_solver_ = std::make_unique<mfem::FoxGoodwinSolver>();
57  break;
59  first_order_system_ode_solver_ = std::make_unique<mfem::BackwardEulerSolver>();
60  break;
61  default:
62  SLIC_ERROR_ROOT("Timestep method was not a supported second-order ODE method");
63  }
64 
65  if (second_order_ode_solver_) {
66  second_order_ode_solver_->Init(*this);
67  } else if (first_order_system_ode_solver_) {
68  // we need to adjust the width of this operator
69  width *= 2;
70  first_order_system_ode_solver_->Init(*this);
71  } else {
72  // there is no ode_solver
73  SLIC_ERROR("Neither second_order_ode_solver_ nor first_order_system_ode_solver_ specified");
74  }
75 }
76 
77 void SecondOrderODE::Step(mfem::Vector& x, mfem::Vector& dxdt, double& time, double& dt)
78 {
79  if (second_order_ode_solver_) {
80  // if we used a 2nd order method
81  second_order_ode_solver_->Step(x, dxdt, time, dt);
82 
83  if (enforcement_method_ == DirichletEnforcementMethod::FullControl) {
84  U_minus_ = 0.0;
85  U_ = 0.0;
86  U_plus_ = 0.0;
87  for (const auto& bc : bcs_.essentials()) {
88  bc.setDofs(U_minus_, t - epsilon);
89  bc.setDofs(U_, t);
90  bc.setDofs(U_plus_, t + epsilon);
91  }
92 
93  auto constrained_dofs = bcs_.allEssentialTrueDofs();
94  for (int i = 0; i < constrained_dofs.Size(); i++) {
95  x[i] = U_[i];
96  dxdt[i] = (U_plus_[i] - U_minus_[i]) / (2.0 * epsilon);
97  }
98  }
99 
100  } else if (first_order_system_ode_solver_) {
101  // Would be better if displacement and velocity were from a block vector?
102  mfem::Array<int> boffsets(3);
103  boffsets[0] = 0;
104  boffsets[1] = x.Size();
105  boffsets[2] = x.Size() + dxdt.Size();
106  mfem::BlockVector bx(boffsets);
107  bx.GetBlock(0) = x;
108  bx.GetBlock(1) = dxdt;
109 
110  first_order_system_ode_solver_->Step(bx, time, dt);
111 
112  // Copy back
113  x = bx.GetBlock(0);
114  dxdt = bx.GetBlock(1);
115  } else {
116  SLIC_ERROR_ROOT("Neither second_order_ode_solver_ nor first_order_system_ode_solver_ specified");
117  }
118 }
119 
120 void SecondOrderODE::ImplicitSolve(const double dt, const mfem::Vector& u, mfem::Vector& du_dt)
121 {
122  /* A second order o.d.e can be recast as a first order system
123  u_next = u_prev + dt * v_next
124  v_next = v_prev + dt * a_next
125 
126  This means:
127  u_next = u_prev + dt * (v_prev + dt * a_next);
128  u_next = (u_prev + dt * v_prev) + dt*dt*a_next
129  */
130 
131  // Split u in half and du_dt in half
132  mfem::Array<int> boffsets(3);
133  boffsets[0] = 0;
134  boffsets[1] = u.Size() / 2;
135  boffsets[2] = u.Size();
136 
137  const mfem::BlockVector bu(u.GetData(), boffsets);
138 
139  mfem::BlockVector bdu_dt(du_dt.GetData(), boffsets);
140 
141  mfem::Vector u_next(bu.GetBlock(0));
142  u_next.Add(dt, bu.GetBlock(1));
143 
144  Solve(t, dt * dt, dt, u_next,
145  bu.GetBlock(1), // v_next
146  bdu_dt.GetBlock(1)); // a_next
147 
148  bdu_dt.GetBlock(0) = bu.GetBlock(1);
149  bdu_dt.GetBlock(0).Add(dt, bdu_dt.GetBlock(1));
150 }
151 
152 void SecondOrderODE::Solve(const double time, const double c0, const double c1, const mfem::Vector& u,
153  const mfem::Vector& du_dt, mfem::Vector& d2u_dt2) const
154 {
155  // assign these values to variables with greater scope,
156  // so that the residual operator can see them
157  state_.time = time;
158  state_.c0 = c0;
159  state_.c1 = c1;
160  state_.u = u;
161  state_.du_dt = du_dt;
162 
163  // evaluate the constraint functions at a 3-point
164  // stencil of times centered on the time of interest
165  // in order to compute finite-difference approximations
166  // to the time derivatives that appear in the residual
167  U_minus_ = 0.0;
168  U_ = 0.0;
169  U_plus_ = 0.0;
170  for (const auto& bc : bcs_.essentials()) {
171  bc.setDofs(U_minus_, time - epsilon);
172  bc.setDofs(U_, time);
173  bc.setDofs(U_plus_, time + epsilon);
174  }
175 
176  bool implicit = (c0 != 0.0 || c1 != 0.0);
177  if (implicit) {
178  if (enforcement_method_ == DirichletEnforcementMethod::DirectControl) {
179  // TODO: MFEM PR#3064 explicitly deleted the const vector operator-. This
180  // is in active discusion and may be un-deleted. Original line commented.
181  // d2U_dt2_ = (U_ - u) / c0;
182  subtract(1.0 / c0, U_, u, d2U_dt2_);
183  dU_dt_ = du_dt;
184  U_ = u;
185  }
186 
187  if (enforcement_method_ == DirichletEnforcementMethod::RateControl) {
188  // d2U_dt2_ = ((U_plus_ - U_minus_) / (2.0 * epsilon) - du_dt) / c1;
189  subtract(U_plus_, U_minus_, d2U_dt2_);
190  d2U_dt2_ /= 2.0 * epsilon;
191  d2U_dt2_ -= du_dt;
192  d2U_dt2_ /= c1;
193 
194  dU_dt_ = du_dt;
195  U_ = u;
196  }
197 
198  if (enforcement_method_ == DirichletEnforcementMethod::FullControl) {
199  // d2U_dt2_ = (U_minus_ - 2.0 * U_ + U_plus_) / (epsilon * epsilon);
200  add(1.0, U_minus_, -2.0, U_, d2U_dt2_);
201  d2U_dt2_ += U_plus_;
202  d2U_dt2_ /= epsilon * epsilon;
203 
204  // d2U_dt2_ = ((U_plus_ - U_minus_) / (2.0 * epsilon) - du_dt) / c1;
205  subtract(U_plus_, U_minus_, d2U_dt2_);
206  d2U_dt2_ /= 2.0 * epsilon;
207  d2U_dt2_ -= du_dt;
208  d2U_dt2_ /= c1;
209 
210  // dU_dt_ = (U_plus_ - U_minus_) / (2.0 * epsilon) - c1 * d2U_dt2_;
211  subtract(U_plus_, U_minus_, dU_dt_);
212  dU_dt_ /= 2.0 * epsilon;
213  dU_dt_.Add(-1.0 * c1, d2U_dt2_);
214 
215  // U_ = U_ - c0 * d2U_dt2_;
216  U_.Add(-1.0 * c0, d2U_dt2_);
217  }
218  } else {
219  // d2U_dt2_ = (U_minus_ - 2.0 * U_ + U_plus_) / (epsilon * epsilon);
220  add(1.0, U_minus_, -2.0, U_, d2U_dt2_);
221  d2U_dt2_ += U_plus_;
222  d2U_dt2_ /= epsilon * epsilon;
223 
224  // dU_dt_ = (U_plus_ - U_minus_) / (2.0 * epsilon);
225  subtract(U_plus_, U_minus_, dU_dt_);
226  dU_dt_ /= 2.0 * epsilon;
227  }
228 
229  auto constrained_dofs = bcs_.allEssentialTrueDofs();
230  state_.u.SetSubVector(constrained_dofs, 0.0);
231  U_.SetSubVectorComplement(constrained_dofs, 0.0);
232  state_.u += U_;
233 
234  state_.du_dt.SetSubVector(constrained_dofs, 0.0);
235  dU_dt_.SetSubVectorComplement(constrained_dofs, 0.0);
236  state_.du_dt += dU_dt_;
237 
238  // use the previous solution as our starting guess
239  d2u_dt2 = state_.d2u_dt2;
240  d2u_dt2.SetSubVector(constrained_dofs, 0.0);
241  d2U_dt2_.SetSubVectorComplement(constrained_dofs, 0.0);
242  d2u_dt2 += d2U_dt2_;
243 
244  solver_.solve(d2u_dt2);
245  SLIC_WARNING_ROOT_IF(!solver_.nonlinearSolver().GetConverged(), "Newton Solver did not converge.");
246 
247  state_.d2u_dt2 = d2u_dt2;
248 }
249 
251  const BoundaryConditionManager& bcs)
252  : mfem::TimeDependentOperator(n, 0.0), state_(std::move(state)), solver_(solver), bcs_(bcs), zero_(n)
253 {
254  zero_ = 0.0;
255  U_minus_.SetSize(n);
256  U_.SetSize(n);
257  U_plus_.SetSize(n);
258  dU_dt_.SetSize(n);
259 }
260 
262 {
263  timestepper_ = timestepper;
264 
265  switch (timestepper) {
267  ode_solver_ = std::make_unique<mfem::BackwardEulerSolver>();
268  break;
270  ode_solver_ = std::make_unique<mfem::SDIRK33Solver>();
271  break;
273  ode_solver_ = std::make_unique<mfem::ForwardEulerSolver>();
274  break;
276  ode_solver_ = std::make_unique<mfem::RK2Solver>(0.5);
277  break;
279  ode_solver_ = std::make_unique<mfem::RK3SSPSolver>();
280  break;
282  ode_solver_ = std::make_unique<mfem::RK4Solver>();
283  break;
285  ode_solver_ = std::make_unique<mfem::GeneralizedAlphaSolver>(0.5);
286  break;
288  ode_solver_ = std::make_unique<mfem::ImplicitMidpointSolver>();
289  break;
291  ode_solver_ = std::make_unique<mfem::SDIRK23Solver>();
292  break;
294  ode_solver_ = std::make_unique<mfem::SDIRK34Solver>();
295  break;
296  default:
297  SLIC_ERROR_ROOT("Timestep method was not a supported first-order ODE method");
298  }
299  ode_solver_->Init(*this);
300 }
301 
302 void FirstOrderODE::Solve(const double time, const double dt, const mfem::Vector& u, mfem::Vector& du_dt) const
303 {
304  // assign these values to variables with greater scope,
305  // so that the residual operator can see them
306  state_.time = time;
307  state_.dt = dt;
308  state_.u = u;
309 
310  // evaluate the constraint functions at a 3-point
311  // stencil of times centered on the time of interest
312  // in order to compute finite-difference approximations
313  // to the time derivatives that appear in the residual
314  U_minus_ = 0.0;
315  U_ = 0.0;
316  U_plus_ = 0.0;
317  for (const auto& bc : bcs_.essentials()) {
318  bc.setDofs(U_minus_, time - epsilon);
319  bc.setDofs(U_, time);
320  bc.setDofs(U_plus_, time + epsilon);
321  }
322 
323  bool implicit = (dt != 0.0);
324  if (implicit) {
325  if (enforcement_method_ == DirichletEnforcementMethod::DirectControl) {
326  // TODO: MFEM PR#3064 explicitly deleted the const vector operator-. This
327  // is in active discusion and may be un-deleted. Original line commented.
328  // dU_dt_ = (U_ - u) / dt;
329  subtract(1.0 / dt, U_, u, dU_dt_);
330  U_ = u;
331  }
332 
333  if (enforcement_method_ == DirichletEnforcementMethod::RateControl) {
334  // dU_dt_ = (U_plus_ - U_minus_) / (2.0 * epsilon);
335  subtract(1.0 / (2.0 * epsilon), U_plus_, U_minus_, dU_dt_);
336  U_ = u;
337  }
338 
339  if (enforcement_method_ == DirichletEnforcementMethod::FullControl) {
340  // dU_dt_ = (U_plus_ - U_minus_) / (2.0 * epsilon);
341  subtract(1.0 / (2.0 * epsilon), U_plus_, U_minus_, dU_dt_);
342  // U_ = U_ - dt * dU_dt_;
343  U_.Add(-1.0 * dt, dU_dt_);
344  }
345  } else {
346  // dU_dt_ = (U_plus_ - U_minus_) / (2.0 * epsilon);
347  subtract(1.0 / (2.0 * epsilon), U_plus_, U_minus_, dU_dt_);
348  }
349 
350  auto constrained_dofs = bcs_.allEssentialTrueDofs();
351  state_.u.SetSubVector(constrained_dofs, 0.0);
352  U_.SetSubVectorComplement(constrained_dofs, 0.0);
353  state_.u += U_;
354 
355  du_dt = state_.du_dt;
356  du_dt.SetSubVector(constrained_dofs, 0.0);
357  dU_dt_.SetSubVectorComplement(constrained_dofs, 0.0);
358  du_dt += dU_dt_;
359 
360  solver_.solve(du_dt);
361  SLIC_WARNING_ROOT_IF(!solver_.nonlinearSolver().GetConverged(), "Newton Solver did not converge.");
362 
363  state_.du_dt = du_dt;
364  state_.previous_dt = dt;
365 }
366 
367 } // namespace serac::mfem_ext
A container for the boundary condition information relating to a specific physics module.
std::vector< BoundaryCondition > & essentials()
Accessor for the essential BC objects.
const mfem::Array< int > & allEssentialTrueDofs() const
Returns all the true degrees of freedom associated with all the essential BCs.
This class manages the objects typically required to solve a nonlinear set of equations arising from ...
mfem::NewtonSolver & nonlinearSolver()
void solve(mfem::Vector &x) const
void SetTimestepper(const serac::TimestepMethod timestepper)
Set the time integration method.
Definition: odes.cpp:261
FirstOrderODE(int n, FirstOrderODE::State &&state, const EquationSolver &solver, const BoundaryConditionManager &bcs)
Constructor defining the size and specific system of ordinary differential equations to be solved.
Definition: odes.cpp:250
static constexpr double epsilon
a small number used to compute finite difference approximations to time derivatives of boundary condi...
Definition: odes.hpp:249
void Step(mfem::Vector &x, mfem::Vector &dxdt, double &time, double &dt)
Performs a time step.
Definition: odes.cpp:77
void ImplicitSolve(const double c0, const double c1, const mfem::Vector &u, const mfem::Vector &du_dt, mfem::Vector &d2u_dt2) override
Solves the equation d2u_dt2 = f(u + c0 * d2u_dt2, du_dt + c1 * d2u_dt2, t)
Definition: odes.hpp:125
static constexpr double epsilon
a small number used to compute finite difference approximations to time derivatives of boundary condi...
Definition: odes.hpp:45
void SetTimestepper(const serac::TimestepMethod timestepper)
Set the time integration method.
Definition: odes.cpp:22
SecondOrderODE(int n, State &&state, const EquationSolver &solver, const BoundaryConditionManager &bcs)
Constructor defining the size and specific system of ordinary differential equations to be solved.
Definition: odes.cpp:11
TimestepMethod
Timestep method of a solver.
Wrapper classes for using MFEM's ODE solver objects.
A set of references to physics-module-owned variables used by the residual operator.
Definition: odes.hpp:255
double & dt
Current time step.
Definition: odes.hpp:269
mfem::Vector & u
Predicted true DOFs.
Definition: odes.hpp:264
double & time
Time value at which the ODE solver wants to compute a residual.
Definition: odes.hpp:259
mfem::Vector & du_dt
Previous value of du_dt.
Definition: odes.hpp:274
double & previous_dt
Previous value of dt.
Definition: odes.hpp:279
A set of references to physics-module-owned variables used by the residual operator.
Definition: odes.hpp:51
double & time
Time value at which the ODE solver wants to compute a residual.
Definition: odes.hpp:55
double & c0
coefficient used to calculate updated displacement: u_{n + 1} := u + c0 * d2u_dt2
Definition: odes.hpp:60
mfem::Vector & du_dt
Predicted du_dt.
Definition: odes.hpp:75
double & c1
coefficient used to calculate updated velocity: du_dt_{n+1} := du_dt + c1 * d2u_dt2
Definition: odes.hpp:65
mfem::Vector & u
Predicted true DOFs.
Definition: odes.hpp:70
mfem::Vector & d2u_dt2
Previous value of d^2u_dt^2.
Definition: odes.hpp:80