Serac  0.1
Serac is an implicit thermal strucural mechanics simulation code.
heat_transfer.hpp
Go to the documentation of this file.
1 // Copyright (c) 2019-2024, 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 
13 #pragma once
14 
15 #include "mfem.hpp"
16 
18 #include "serac/physics/common.hpp"
21 #include "serac/numerics/odes.hpp"
26 
27 namespace serac {
28 
29 namespace heat_transfer {
30 
35  .preconditioner = Preconditioner::HypreL1Jacobi,
36  .relative_tol = 1.0e-6,
37  .absolute_tol = 1.0e-12,
38  .max_iterations = 200};
39 
41 #ifdef MFEM_USE_STRUMPACK
43 #else
45 #endif
46 
51  .relative_tol = 1.0e-4,
52  .absolute_tol = 1.0e-8,
53  .max_iterations = 500,
54  .print_level = 1};
55 
61 
66 
67 } // namespace heat_transfer
68 
81 template <int order, int dim, typename parameters = Parameters<>,
82  typename parameter_indices = std::make_integer_sequence<int, parameters::n>>
84 
86 template <int order, int dim, typename... parameter_space, int... parameter_indices>
87 class HeatTransfer<order, dim, Parameters<parameter_space...>, std::integer_sequence<int, parameter_indices...>>
88  : public BasePhysics {
89 public:
91  static constexpr int VALUE = 0, DERIVATIVE = 1;
92  static constexpr int SHAPE = 0;
93  static constexpr auto I = Identity<dim>();
95 
98  static constexpr auto NUM_STATE_VARS = 2;
99 
117  HeatTransfer(const NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions lin_opts,
118  const serac::TimesteppingOptions timestepping_opts, const std::string& physics_name,
119  std::string mesh_tag, std::vector<std::string> parameter_names = {}, int cycle = 0, double time = 0.0,
120  bool checkpoint_to_disk = false)
121  : HeatTransfer(std::make_unique<EquationSolver>(nonlinear_opts, lin_opts, StateManager::mesh(mesh_tag).GetComm()),
122  timestepping_opts, physics_name, mesh_tag, parameter_names, cycle, time, checkpoint_to_disk)
123  {
124  }
125 
142  HeatTransfer(std::unique_ptr<serac::EquationSolver> solver, const serac::TimesteppingOptions timestepping_opts,
143  const std::string& physics_name, std::string mesh_tag, std::vector<std::string> parameter_names = {},
144  int cycle = 0, double time = 0.0, bool checkpoint_to_disk = false)
145  : BasePhysics(physics_name, mesh_tag, cycle, time, checkpoint_to_disk),
146  temperature_(StateManager::newState(H1<order>{}, detail::addPrefix(physics_name, "temperature"), mesh_tag_)),
147  temperature_rate_(
148  StateManager::newState(H1<order>{}, detail::addPrefix(physics_name, "temperature_rate"), mesh_tag_)),
149  adjoint_temperature_(
150  StateManager::newState(H1<order>{}, detail::addPrefix(physics_name, "adjoint_temperature"), mesh_tag_)),
151  implicit_sensitivity_temperature_start_of_step_(adjoint_temperature_.space(),
152  detail::addPrefix(physics_name, "total_deriv_wrt_temperature")),
153  temperature_adjoint_load_(temperature_.space(), detail::addPrefix(physics_name, "temperature_adjoint_load")),
154  temperature_rate_adjoint_load_(temperature_.space(),
155  detail::addPrefix(physics_name, "temperature_rate_adjoint_load")),
156  residual_with_bcs_(temperature_.space().TrueVSize()),
157  nonlin_solver_(std::move(solver)),
158  ode_(temperature_.space().TrueVSize(),
159  {.time = ode_time_point_, .u = u_, .dt = dt_, .du_dt = temperature_rate_, .previous_dt = previous_dt_},
160  *nonlin_solver_, bcs_)
161  {
162  SLIC_ERROR_ROOT_IF(
163  mesh_.Dimension() != dim,
164  axom::fmt::format("Compile time class dimension template parameter and runtime mesh dimension do not match"));
165 
166  SLIC_ERROR_ROOT_IF(
167  !nonlin_solver_,
168  "EquationSolver argument is nullptr in HeatTransfer constructor. It is possible that it was previously moved.");
169 
170  // Check for dynamic mode
171  if (timestepping_opts.timestepper != TimestepMethod::QuasiStatic) {
172  ode_.SetTimestepper(timestepping_opts.timestepper);
173  ode_.SetEnforcementMethod(timestepping_opts.enforcement_method);
174  is_quasistatic_ = false;
175  } else {
176  is_quasistatic_ = true;
177  }
178 
179  states_.push_back(&temperature_);
180  if (!is_quasistatic_) {
181  states_.push_back(&temperature_rate_);
182  }
183 
184  adjoints_.push_back(&adjoint_temperature_);
185 
186  // Create a pack of the primal field and parameter finite element spaces
187  mfem::ParFiniteElementSpace* test_space = &temperature_.space();
188  mfem::ParFiniteElementSpace* shape_space = &shape_displacement_.space();
189 
190  std::array<const mfem::ParFiniteElementSpace*, sizeof...(parameter_space) + NUM_STATE_VARS> trial_spaces;
191  trial_spaces[0] = &temperature_.space();
192  trial_spaces[1] = &temperature_.space();
193 
194  SLIC_ERROR_ROOT_IF(
195  sizeof...(parameter_space) != parameter_names.size(),
196  axom::fmt::format("{} parameter spaces given in the template argument but {} parameter names were supplied.",
197  sizeof...(parameter_space), parameter_names.size()));
198 
199  if constexpr (sizeof...(parameter_space) > 0) {
200  tuple<parameter_space...> types{};
201  for_constexpr<sizeof...(parameter_space)>([&](auto i) {
202  parameters_.emplace_back(mesh_, get<i>(types), detail::addPrefix(name_, parameter_names[i]));
203 
204  trial_spaces[i + NUM_STATE_VARS] = &(parameters_[i].state->space());
205  });
206  }
207 
208  residual_ =
209  std::make_unique<ShapeAwareFunctional<shape_trial, test(scalar_trial, scalar_trial, parameter_space...)>>(
210  shape_space, test_space, trial_spaces);
211 
212  nonlin_solver_->setOperator(residual_with_bcs_);
213 
214  int true_size = temperature_.space().TrueVSize();
215  u_.SetSize(true_size);
216  u_predicted_.SetSize(true_size);
217 
218  shape_displacement_ = 0.0;
219  initializeThermalStates();
220  }
221 
231  HeatTransfer(const HeatTransferInputOptions& input_options, const std::string& physics_name,
232  const std::string& mesh_tag, int cycle = 0, double time = 0.0)
233  : HeatTransfer(input_options.nonlin_solver_options, input_options.lin_solver_options,
234  input_options.timestepping_options, physics_name, mesh_tag, {}, cycle, time)
235  {
236  for (const auto& mat : input_options.materials) {
237  if (std::holds_alternative<serac::heat_transfer::LinearIsotropicConductor>(mat)) {
238  setMaterial(std::get<serac::heat_transfer::LinearIsotropicConductor>(mat));
241  }
242  }
243 
244  if (input_options.initial_temperature) {
245  auto temp = input_options.initial_temperature->constructScalar();
246  temperature_.project(*temp);
247  }
248 
249  if (input_options.source_coef) {
250  // TODO: Not implemented yet in input files
251  // NOTE: cannot use std::functions that use mfem::vector
252  SLIC_ERROR("'source' is not implemented yet in input files.");
253  }
254 
255  // Process the BCs in sorted order for correct behavior with repeated attributes
256  std::map<std::string, input::BoundaryConditionInputOptions> sorted_bcs(input_options.boundary_conditions.begin(),
257  input_options.boundary_conditions.end());
258  for (const auto& [bc_name, bc] : sorted_bcs) {
259  // FIXME: Better naming for boundary conditions?
260  if (bc_name.find("temperature") != std::string::npos) {
261  std::shared_ptr<mfem::Coefficient> temp_coef(bc.coef_opts.constructScalar());
262  bcs_.addEssential(bc.attrs, temp_coef, temperature_.space(), *bc.coef_opts.component);
263  } else if (bc_name.find("flux") != std::string::npos) {
264  // TODO: Not implemented yet in input files
265  // NOTE: cannot use std::functions that use mfem::vector
266  SLIC_ERROR("'flux' is not implemented yet in input files.");
267  } else {
268  SLIC_WARNING_ROOT("Ignoring boundary condition with unknown name: " << physics_name);
269  }
270  }
271  }
272 
280  {
281  dt_ = 0.0;
282  previous_dt_ = -1.0;
283 
284  u_ = 0.0;
285  temperature_ = 0.0;
286  temperature_rate_ = 0.0;
287  adjoint_temperature_ = 0.0;
288  implicit_sensitivity_temperature_start_of_step_ = 0.0;
289  temperature_adjoint_load_ = 0.0;
290  temperature_rate_adjoint_load_ = 0.0;
291 
292  if (!checkpoint_to_disk_) {
293  checkpoint_states_.clear();
294 
295  checkpoint_states_["temperature"].push_back(temperature_);
296  checkpoint_states_["temperature_rate"].push_back(temperature_rate_);
297  }
298  }
299 
306  void resetStates(int cycle = 0, double time = 0.0) override
307  {
309  initializeThermalStates();
310  }
311 
320  void setTemperatureBCs(const std::set<int>& temp_bdr, std::function<double(const mfem::Vector& x, double t)> temp)
321  {
322  // Project the coefficient onto the grid function
323  temp_bdr_coef_ = std::make_shared<mfem::FunctionCoefficient>(temp);
324 
325  bcs_.addEssential(temp_bdr, temp_bdr_coef_, temperature_.space());
326  }
327 
335  void advanceTimestep(double dt) override
336  {
337  if (is_quasistatic_) {
338  time_ += dt;
339 
340  // Set the ODE time point for the time-varying loads in quasi-static problems
341  ode_time_point_ = time_;
342 
343  // Project the essential boundary coefficients
344  for (auto& bc : bcs_.essentials()) {
345  bc.setDofs(temperature_, time_);
346  }
347  nonlin_solver_->solve(temperature_);
348 
349  cycle_ += 1;
350 
351  } else {
352  // Step the time integrator
353  // Note that the ODE solver handles the essential boundary condition application itself
354  ode_.Step(temperature_, time_, dt);
355 
356  cycle_ += 1;
357 
358  if (checkpoint_to_disk_) {
359  outputStateToDisk();
360  } else {
361  checkpoint_states_["temperature"].push_back(temperature_);
362  checkpoint_states_["temperature_rate"].push_back(temperature_rate_);
363  }
364  }
365 
366  if (cycle_ > max_cycle_) {
367  timesteps_.push_back(dt);
368  max_cycle_ = cycle_;
369  max_time_ = time_;
370  }
371  }
372 
377  template <typename MaterialType>
378  struct ThermalMaterialIntegrand {
384  ThermalMaterialIntegrand(MaterialType material) : material_(material) {}
385 
386  // Due to nvcc's lack of support for extended generic lambdas (i.e. functions of the form
387  // auto lambda = [] __host__ __device__ (auto) {}; ), MaterialType cannot be an extended
388  // generic lambda. The static asserts below check this.
389  private:
390  class DummyArgumentType {};
391  static_assert(!std::is_invocable<MaterialType, DummyArgumentType&>::value);
392  static_assert(!std::is_invocable<MaterialType, DummyArgumentType*>::value);
393  static_assert(!std::is_invocable<MaterialType, DummyArgumentType>::value);
394 
395  public:
399  template <typename X, typename T, typename dT_dt, typename... Params>
400  auto operator()(double /*time*/, X x, T temperature, dT_dt dtemp_dt, Params... params) const
401  {
402  // Get the value and the gradient from the input tuple
403  auto [u, du_dX] = temperature;
404  auto du_dt = get<VALUE>(dtemp_dt);
405 
406  auto [heat_capacity, heat_flux] = material_(x, u, du_dX, params...);
407 
408  return serac::tuple{heat_capacity * du_dt, -1.0 * heat_flux};
409  }
410 
411  private:
412  MaterialType material_;
413  };
414 
438  template <int... active_parameters, typename MaterialType>
439  void setMaterial(DependsOn<active_parameters...>, MaterialType material)
440  {
441  ThermalMaterialIntegrand<MaterialType> integrand(material);
442 
443  residual_->AddDomainIntegral(Dimension<dim>{}, DependsOn<0, 1, NUM_STATE_VARS + active_parameters...>{}, integrand,
444  mesh_);
445  }
446 
448  template <typename MaterialType>
449  void setMaterial(MaterialType material)
450  {
451  setMaterial(DependsOn<>{}, material);
452  }
453 
461  void setTemperature(std::function<double(const mfem::Vector& x, double t)> temp)
462  {
463  // Project the coefficient onto the grid function
464  mfem::FunctionCoefficient temp_coef(temp);
465 
466  temp_coef.SetTime(time_);
467  temperature_.project(temp_coef);
468  }
469 
471  void setTemperature(const FiniteElementState temp) { temperature_ = temp; }
472 
496  template <int... active_parameters, typename SourceType>
497  void setSource(DependsOn<active_parameters...>, SourceType source_function,
498  const std::optional<Domain>& optional_domain = std::nullopt)
499  {
500  Domain domain = (optional_domain) ? *optional_domain : EntireDomain(mesh_);
501 
502  residual_->AddDomainIntegral(
503  Dimension<dim>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
504  [source_function](double t, auto x, auto temperature, auto /* dtemp_dt */, auto... params) {
505  // Get the value and the gradient from the input tuple
506  auto [u, du_dX] = temperature;
507 
508  auto source = source_function(x, t, u, du_dX, params...);
509 
510  // Return the source and the flux as a tuple
511  return serac::tuple{-1.0 * source, serac::zero{}};
512  },
513  domain);
514  }
515 
517  template <typename SourceType>
518  void setSource(SourceType source_function, const std::optional<Domain>& optional_domain = std::nullopt)
519  {
520  setSource(DependsOn<>{}, source_function, optional_domain);
521  }
522 
546  template <int... active_parameters, typename FluxType>
547  void setFluxBCs(DependsOn<active_parameters...>, FluxType flux_function,
548  const std::optional<Domain>& optional_domain = std::nullopt)
549  {
550  Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_);
551 
552  residual_->AddBoundaryIntegral(
553  Dimension<dim - 1>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
554  [flux_function](double t, auto X, auto u, auto /* dtemp_dt */, auto... params) {
555  auto temp = get<VALUE>(u);
556  auto n = cross(get<DERIVATIVE>(X));
557 
558  return flux_function(X, normalize(n), t, temp, params...);
559  },
560  domain);
561  }
562 
564  template <typename FluxType>
565  void setFluxBCs(FluxType flux_function, const std::optional<Domain>& optional_domain = std::nullopt)
566  {
567  setFluxBCs(DependsOn<>{}, flux_function, optional_domain);
568  }
569 
577  void setShapeDisplacement(std::function<void(const mfem::Vector& x, mfem::Vector& shape_disp)> shape_disp)
578  {
579  // Project the coefficient onto the grid function
580  mfem::VectorFunctionCoefficient shape_disp_coef(dim, shape_disp);
581  shape_displacement_.project(shape_disp_coef);
582  }
583 
585  void setShapeDisplacement(FiniteElementState& shape_disp) { shape_displacement_ = shape_disp; }
586 
592  const serac::FiniteElementState& temperature() const { return temperature_; };
593 
599  const serac::FiniteElementState& temperatureRate() const { return temperature_rate_; };
600 
607  const FiniteElementState& state(const std::string& state_name) const override
608  {
609  if (state_name == "temperature") {
610  return temperature_;
611  } else if (state_name == "temperature_rate") {
612  return temperature_rate_;
613  }
614 
615  SLIC_ERROR_ROOT(axom::fmt::format("State '{}' requested from solid mechanics module '{}', but it doesn't exist",
616  state_name, name_));
617  return temperature_;
618  }
619 
629  void setState(const std::string& state_name, const FiniteElementState& state) override
630  {
631  if (state_name == "temperature") {
632  temperature_ = state;
633  if (!checkpoint_to_disk_) {
634  checkpoint_states_["temperature"][static_cast<size_t>(cycle_)] = temperature_;
635  }
636  return;
637  }
638 
639  SLIC_ERROR_ROOT(axom::fmt::format(
640  "setState for state named '{}' requested from heat transfer module '{}', but it doesn't exist", state_name,
641  name_));
642  }
643 
649  virtual std::vector<std::string> stateNames() const override
650  {
651  if (is_quasistatic_) {
652  return std::vector<std::string>{"temperature"};
653  } else {
654  return std::vector<std::string>{"temperature", "temperature_rate"};
655  }
656  }
657 
679  template <int... active_parameters, typename callable>
681  const std::optional<Domain>& optional_domain = std::nullopt)
682  {
683  Domain domain = (optional_domain) ? *optional_domain : EntireBoundary(mesh_);
684 
685  residual_->AddBoundaryIntegral(Dimension<dim - 1>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
686  qfunction, domain);
687  }
688 
695  const FiniteElementState& adjoint(const std::string& state_name) const override
696  {
697  if (state_name == "temperature") {
698  return adjoint_temperature_;
699  }
700 
701  SLIC_ERROR_ROOT(axom::fmt::format("Adjoint '{}' requested from solid mechanics module '{}', but it doesn't exist",
702  state_name, name_));
703  return adjoint_temperature_;
704  }
705 
711  void completeSetup() override
712  {
713  // Build the dof array lookup tables
714  temperature_.space().BuildDofToArrays();
715 
716  if (is_quasistatic_) {
717  residual_with_bcs_ = mfem_ext::StdFunctionOperator(
718  temperature_.space().TrueVSize(),
719 
720  [this](const mfem::Vector& u, mfem::Vector& r) {
721  const mfem::Vector res = (*residual_)(ode_time_point_, shape_displacement_, u, temperature_rate_,
722  *parameters_[parameter_indices].state...);
723 
724  // TODO this copy is required as the sundials solvers do not allow move assignments because of their memory
725  // tracking strategy
726  // See https://github.com/mfem/mfem/issues/3531
727  r = res;
728  r.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
729  },
730 
731  [this](const mfem::Vector& u) -> mfem::Operator& {
732  auto [r, drdu] = (*residual_)(ode_time_point_, shape_displacement_, differentiate_wrt(u), temperature_rate_,
733  *parameters_[parameter_indices].state...);
734  J_ = assemble(drdu);
735  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
736  return *J_;
737  });
738  } else {
739  residual_with_bcs_ = mfem_ext::StdFunctionOperator(
740  temperature_.space().TrueVSize(),
741 
742  [this](const mfem::Vector& du_dt, mfem::Vector& r) {
743  add(1.0, u_, dt_, du_dt, u_predicted_);
744  const mfem::Vector res = (*residual_)(ode_time_point_, shape_displacement_, u_predicted_, du_dt,
745  *parameters_[parameter_indices].state...);
746 
747  // TODO this copy is required as the sundials solvers do not allow move assignments because of their memory
748  // tracking strategy
749  // See https://github.com/mfem/mfem/issues/3531
750  r = res;
751  r.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
752  },
753 
754  [this](const mfem::Vector& du_dt) -> mfem::Operator& {
755  add(1.0, u_, dt_, du_dt, u_predicted_);
756 
757  // K := dR/du
758  auto K = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_,
759  differentiate_wrt(u_predicted_), du_dt,
760  *parameters_[parameter_indices].state...));
761  std::unique_ptr<mfem::HypreParMatrix> k_mat(assemble(K));
762 
763  // M := dR/du_dot
764  auto M = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_, u_predicted_,
765  differentiate_wrt(du_dt),
766  *parameters_[parameter_indices].state...));
767  std::unique_ptr<mfem::HypreParMatrix> m_mat(assemble(M));
768 
769  // J := M + dt K
770  J_.reset(mfem::Add(1.0, *m_mat, dt_, *k_mat));
771  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
772 
773  return *J_;
774  });
775  }
776 
777  if (checkpoint_to_disk_) {
778  outputStateToDisk();
779  } else {
780  checkpoint_states_.clear();
781 
782  checkpoint_states_["temperature"].push_back(temperature_);
783  checkpoint_states_["temperature_rate"].push_back(temperature_rate_);
784  }
785  }
786 
801  virtual void setAdjointLoad(std::unordered_map<std::string, const serac::FiniteElementDual&> loads) override
802  {
803  SLIC_ERROR_ROOT_IF(loads.size() == 0,
804  "Adjoint load container size must be greater than 0 in the heat transfer module.");
805 
806  auto temp_adjoint_load = loads.find("temperature");
807  auto temp_rate_adjoint_load = loads.find("temperature_rate"); // does not need to be specified
808 
809  SLIC_ERROR_ROOT_IF(temp_adjoint_load == loads.end(), "Adjoint load for \"temperature\" not found.");
810 
811  temperature_adjoint_load_ = temp_adjoint_load->second;
812  // Add the sign correction to move the term to the RHS
813  temperature_adjoint_load_ *= -1.0;
814 
815  if (temp_rate_adjoint_load != loads.end()) {
816  temperature_rate_adjoint_load_ = temp_rate_adjoint_load->second;
817  temperature_rate_adjoint_load_ *= -1.0;
818  }
819  }
820 
851  void reverseAdjointTimestep() override
852  {
853  auto& lin_solver = nonlin_solver_->linearSolver();
854 
855  mfem::HypreParVector adjoint_essential(temperature_adjoint_load_);
856  adjoint_essential = 0.0;
857 
858  if (is_quasistatic_) {
859  // We store the previous timestep's temperature as the current temperature for use in the lambdas computing the
860  // sensitivities.
861 
862  auto [_, drdu] = (*residual_)(ode_time_point_, shape_displacement_, differentiate_wrt(temperature_),
863  temperature_rate_, *parameters_[parameter_indices].state...);
864  auto jacobian = assemble(drdu);
865  auto J_T = std::unique_ptr<mfem::HypreParMatrix>(jacobian->Transpose());
866 
867  for (const auto& bc : bcs_.essentials()) {
868  bc.apply(*J_T, temperature_adjoint_load_, adjoint_essential);
869  }
870 
871  lin_solver.SetOperator(*J_T);
872  lin_solver.Mult(temperature_adjoint_load_, adjoint_temperature_);
873 
874  return;
875  }
876 
877  SLIC_ERROR_ROOT_IF(ode_.GetTimestepper() != TimestepMethod::BackwardEuler,
878  "Only backward Euler implemented for transient adjoint heat conduction.");
879 
880  SLIC_ERROR_ROOT_IF(cycle_ <= min_cycle_,
881  "Maximum number of adjoint timesteps exceeded! The number of adjoint timesteps must equal the "
882  "number of forward timesteps");
883 
884  // Load the temperature from the previous cycle from disk
885  serac::FiniteElementState temperature_n_minus_1(temperature_);
886 
887  {
888  auto previous_states_n = getCheckpointedStates(cycle_);
889  auto previous_states_n_minus_1 = getCheckpointedStates(cycle_ - 1);
890 
891  temperature_ = previous_states_n.at("temperature");
892  temperature_rate_ = previous_states_n.at("temperature_rate");
893  temperature_n_minus_1 = previous_states_n_minus_1.at("temperature");
894  }
895 
896  double dt = getCheckpointedTimestep(cycle_ - 1);
897 
898  // K := dR/du
899  auto K = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_, differentiate_wrt(temperature_),
900  temperature_rate_, *parameters_[parameter_indices].state...));
901  std::unique_ptr<mfem::HypreParMatrix> k_mat(assemble(K));
902 
903  // M := dR/du_dot
904  auto M = serac::get<DERIVATIVE>((*residual_)(ode_time_point_, shape_displacement_, temperature_,
905  differentiate_wrt(temperature_rate_),
906  *parameters_[parameter_indices].state...));
907  std::unique_ptr<mfem::HypreParMatrix> m_mat(assemble(M));
908 
909  J_.reset(mfem::Add(1.0, *m_mat, dt, *k_mat));
910  auto J_T = std::unique_ptr<mfem::HypreParMatrix>(J_->Transpose());
911 
912  // recall that temperature_adjoint_load_vector and d_temperature_dt_adjoint_load_vector were already multiplied by
913  // -1 above
914  mfem::HypreParVector modified_RHS(temperature_adjoint_load_);
915  modified_RHS *= dt;
916  modified_RHS.Add(1.0, temperature_rate_adjoint_load_);
917  modified_RHS.Add(-dt, implicit_sensitivity_temperature_start_of_step_);
918 
919  for (const auto& bc : bcs_.essentials()) {
920  bc.apply(*J_T, modified_RHS, adjoint_essential);
921  }
922 
923  lin_solver.SetOperator(*J_T);
924  lin_solver.Mult(modified_RHS, adjoint_temperature_);
925 
926  // This multiply is technically on M transposed. However, this matrix should be symmetric unless
927  // the thermal capacity is a function of the temperature rate of change, which is thermodynamically
928  // impossible, and fortunately not possible with our material interface.
929  // Not doing the transpose here to avoid doing unnecessary work.
930  m_mat->Mult(adjoint_temperature_, implicit_sensitivity_temperature_start_of_step_);
931  implicit_sensitivity_temperature_start_of_step_ *= -1.0 / dt;
932  implicit_sensitivity_temperature_start_of_step_.Add(1.0 / dt,
933  temperature_rate_adjoint_load_); // already multiplied by -1
934 
935  time_ -= dt;
936  cycle_--;
937  }
938 
946  std::unordered_map<std::string, FiniteElementState> getCheckpointedStates(int cycle_to_load) const override
947  {
948  std::unordered_map<std::string, FiniteElementState> previous_states;
949 
950  if (checkpoint_to_disk_) {
951  previous_states.emplace("temperature", temperature_);
952  previous_states.emplace("temperature_rate", temperature_rate_);
954  {previous_states.at("temperature"), previous_states.at("temperature_rate")});
955  return previous_states;
956  } else {
957  previous_states.emplace("temperature", checkpoint_states_.at("temperature")[static_cast<size_t>(cycle_to_load)]);
958  previous_states.emplace("temperature_rate",
959  checkpoint_states_.at("temperature_rate")[static_cast<size_t>(cycle_to_load)]);
960  }
961 
962  return previous_states;
963  }
964 
974  FiniteElementDual& computeTimestepSensitivity(size_t parameter_field) override
975  {
976  // TODO: the time is likely not being handled correctly on the reverse pass, but we don't
977  // have tests to confirm.
978  auto drdparam = serac::get<DERIVATIVE>(d_residual_d_[parameter_field](ode_time_point_));
979  auto drdparam_mat = assemble(drdparam);
980 
981  drdparam_mat->MultTranspose(adjoint_temperature_, *parameters_[parameter_field].sensitivity);
982 
983  return *parameters_[parameter_field].sensitivity;
984  }
985 
995  {
996  auto drdshape =
997  serac::get<DERIVATIVE>((*residual_)(ode_time_point_, differentiate_wrt(shape_displacement_), temperature_,
998  temperature_rate_, *parameters_[parameter_indices].state...));
999 
1000  auto drdshape_mat = assemble(drdshape);
1001 
1002  drdshape_mat->MultTranspose(adjoint_temperature_, *shape_displacement_sensitivity_);
1003 
1004  return *shape_displacement_sensitivity_;
1005  }
1006 
1014  const std::unordered_map<std::string, const serac::FiniteElementDual&> computeInitialConditionSensitivity() override
1015  {
1016  return {{"temperature", implicit_sensitivity_temperature_start_of_step_}};
1017  }
1018 
1020  virtual ~HeatTransfer() = default;
1021 
1022 protected:
1025 
1028 
1030  using test = H1<order>;
1031 
1034 
1037 
1040 
1043 
1046 
1049 
1051  std::unique_ptr<ShapeAwareFunctional<shape_trial, test(scalar_trial, scalar_trial, parameter_space...)>> residual_;
1052 
1054  std::unique_ptr<mfem::HypreParMatrix> M_;
1055 
1057  std::shared_ptr<mfem::Coefficient> temp_bdr_coef_;
1058 
1064 
1066  std::unique_ptr<EquationSolver> nonlin_solver_;
1067 
1074 
1076  std::unique_ptr<mfem::HypreParMatrix> J_;
1077 
1080  std::unique_ptr<mfem::HypreParMatrix> J_e_;
1081 
1083  double dt_;
1084 
1087 
1089  mfem::Vector u_;
1090 
1092  mfem::Vector u_predicted_;
1093 
1097  std::array<std::function<decltype((*residual_)(DifferentiateWRT<1>{}, 0.0, shape_displacement_, temperature_,
1098  temperature_rate_, *parameters_[parameter_indices].state...))(double)>,
1099  sizeof...(parameter_indices)>
1100  d_residual_d_ = {[&](double TIME) {
1101  return (*residual_)(DifferentiateWRT<NUM_STATE_VARS + 1 + parameter_indices>{}, TIME, shape_displacement_,
1102  temperature_, temperature_rate_, *parameters_[parameter_indices].state...);
1103  }...};
1104 };
1105 
1106 } // namespace serac
The base interface class for a generic PDE solver.
This is the abstract base class for a generic forward solver.
void initializeBasePhysicsStates(int cycle, double time)
Protected, non-virtual method to reset physics states to zero. This does not reset design parameters ...
Class for encapsulating the dual vector space of a finite element space (i.e. the space of linear for...
Class for encapsulating the critical MFEM components of a primal finite element field.
mfem_ext::StdFunctionOperator residual_with_bcs_
mfem::Operator that describes the weight residual and its gradient with respect to temperature
void setTemperature(const FiniteElementState temp)
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::unordered_map< std::string, FiniteElementState > getCheckpointedStates(int cycle_to_load) const override
Accessor for getting named finite element state primal solution from the physics modules at a given c...
const FiniteElementState & state(const std::string &state_name) const override
Accessor for getting named finite element state primal solution from the physics modules.
void setMaterial(MaterialType material)
This is an overloaded member function, provided for convenience. It differs from the above function o...
serac::FiniteElementDual temperature_rate_adjoint_load_
The downstream derivative of the qoi with repect to the primal temperature rate variable.
FiniteElementDual & computeTimestepShapeSensitivity() override
Compute the implicit sensitivity of the quantity of interest used in defining the load for the adjoin...
HeatTransfer(std::unique_ptr< serac::EquationSolver > solver, const serac::TimesteppingOptions timestepping_opts, const std::string &physics_name, std::string mesh_tag, std::vector< std::string > parameter_names={}, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Construct a new heat transfer object.
void resetStates(int cycle=0, double time=0.0) override
Method to reset physics states to zero. This does not reset design parameters or shape.
serac::FiniteElementState adjoint_temperature_
The adjoint temperature finite element states, the multiplier on the residual for a given timestep.
virtual void setAdjointLoad(std::unordered_map< std::string, const serac::FiniteElementDual & > loads) override
Set the loads for the adjoint reverse timestep solve.
mfem_ext::FirstOrderODE ode_
the ordinary differential equation that describes how to solve for the time derivative of temperature...
void setFluxBCs(FluxType flux_function, const std::optional< Domain > &optional_domain=std::nullopt)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setTemperature(std::function< double(const mfem::Vector &x, double t)> temp)
Set the underlying finite element state to a prescribed temperature.
std::unique_ptr< EquationSolver > nonlin_solver_
the specific methods and tolerances specified to solve the nonlinear residual equations
void setShapeDisplacement(std::function< void(const mfem::Vector &x, mfem::Vector &shape_disp)> shape_disp)
Set the underlying finite element state to a prescribed shape displacement.
void setSource(DependsOn< active_parameters... >, SourceType source_function, const std::optional< Domain > &optional_domain=std::nullopt)
Set the thermal source function.
void setMaterial(DependsOn< active_parameters... >, MaterialType material)
Set the thermal material model for the physics solver.
void setShapeDisplacement(FiniteElementState &shape_disp)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setTemperatureBCs(const std::set< int > &temp_bdr, std::function< double(const mfem::Vector &x, double t)> temp)
Set essential temperature boundary conditions (strongly enforced)
std::unique_ptr< ShapeAwareFunctional< shape_trial, test(scalar_trial, scalar_trial, parameter_space...)> > residual_
serac::Functional that is used to calculate the residual and its derivatives
FiniteElementDual & computeTimestepSensitivity(size_t parameter_field) override
Compute the implicit sensitivity of the quantity of interest used in defining the load for the adjoin...
void initializeThermalStates()
Non virtual method to reset thermal states to zero. This does not reset design parameters or shape.
virtual std::vector< std::string > stateNames() const override
Get a vector of the finite element state primal solution names.
const serac::FiniteElementState & temperatureRate() const
Get the temperature rate of change state.
serac::FiniteElementDual temperature_adjoint_load_
The downstream derivative of the qoi with repect to the primal temperature variable.
HeatTransfer(const NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions lin_opts, const serac::TimesteppingOptions timestepping_opts, const std::string &physics_name, std::string mesh_tag, std::vector< std::string > parameter_names={}, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Construct a new heat transfer object.
serac::FiniteElementDual implicit_sensitivity_temperature_start_of_step_
The total/implicit sensitivity of the qoi with respect to the start of the previous timestep's temper...
void completeSetup() override
Complete the initialization and allocation of the data structures.
void setFluxBCs(DependsOn< active_parameters... >, FluxType flux_function, const std::optional< Domain > &optional_domain=std::nullopt)
Set the thermal flux boundary condition.
const FiniteElementState & adjoint(const std::string &state_name) const override
Accessor for getting named finite element state adjoint solution from the physics modules.
FiniteElementState temperature_rate_
Rate of change in temperature at the current adjoint timestep.
void addCustomBoundaryIntegral(DependsOn< active_parameters... >, callable qfunction, const std::optional< Domain > &optional_domain=std::nullopt)
register a custom boundary integral calculation as part of the residual
const std::unordered_map< std::string, const serac::FiniteElementDual & > computeInitialConditionSensitivity() override
Compute the implicit sensitivity of the quantity of interest with respect to the initial temperature.
void setSource(SourceType source_function, const std::optional< Domain > &optional_domain=std::nullopt)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setState(const std::string &state_name, const FiniteElementState &state) override
Set the primal solution field (temperature) for the underlying heat transfer solver.
std::shared_ptr< mfem::Coefficient > temp_bdr_coef_
Coefficient containing the essential boundary values.
HeatTransfer(const HeatTransferInputOptions &input_options, const std::string &physics_name, const std::string &mesh_tag, int cycle=0, double time=0.0)
Construct a new Nonlinear HeatTransfer Solver object.
An object containing the solver for a heat transfer PDE.
static void loadCheckpointedStates(int cycle_to_load, std::vector< std::reference_wrapper< FiniteElementState >> states_to_load)
loads the finite element states from a previously checkpointed cycle
static FiniteElementState newState(FunctionSpace space, const std::string &state_name, const std::string &mesh_tag)
Factory method for creating a new FEState object.
static mfem::ParMesh & mesh(const std::string &mesh_tag)
Returns a non-owning reference to mesh held by StateManager.
FirstOrderODE is a class wrapping mfem::TimeDependentOperator so that the user can use std::function ...
Definition: odes.hpp:238
StdFunctionOperator is a class wrapping mfem::Operator so that the user can use std::function to defi...
A file defining some enums and structs that are used by the different physics modules.
An object containing all input file options for the solver for a heat transfer PDE.
A function intended to be used as part of a driver to initialize common libraries.
constexpr SERAC_HOST_DEVICE void for_constexpr(lambda &&f)
multidimensional loop tool that evaluates the lambda body inside the innermost loop.
constexpr auto get(std::integer_sequence< int, n... >)
return the Ith integer in {n...}
const TimesteppingOptions default_timestepping_options
Reasonable defaults for dynamic heat transfer simulations.
const TimesteppingOptions default_static_options
Reasonable defaults for static heat transfer simulations.
const LinearSolverOptions default_linear_options
Reasonable defaults for most thermal linear solver options.
const NonlinearSolverOptions default_nonlinear_options
Reasonable defaults for most thermal nonlinear solver options.
const LinearSolverOptions direct_linear_options
the default direct solver option for solving the linear stiffness equations
Accelerator functionality.
Definition: serac.cpp:38
Domain EntireBoundary(const mfem::Mesh &mesh)
constructs a domain from all the boundary elements in a mesh
Definition: domain.cpp:418
auto differentiate_wrt(const mfem::Vector &v)
this function is intended to only be used in combination with serac::Functional::operator(),...
tuple(T...) -> tuple< T... >
Class template argument deduction rule for tuples.
auto cross(const tensor< T, 3, 2 > &A)
compute the cross product of the columns of A: A(:,1) x A(:,2)
Definition: tensor.hpp:906
SERAC_HOST_DEVICE auto normalize(const tensor< T, n... > &A)
Normalizes the tensor Each element is divided by the Frobenius norm of the tensor,...
Definition: tensor.hpp:1062
bool holds_alternative(const variant< T0, T1 > &v)
Checks whether a variant's active member is of a certain type.
Definition: variant.hpp:381
Domain EntireDomain(const mfem::Mesh &mesh)
constructs a domain from all the elements in a mesh
Definition: domain.cpp:382
Wrapper classes for using MFEM's ODE solver objects.
Wrapper of serac::Functional for evaluating integrals and derivatives of quantities with shape displa...
This file contains the declaration of the StateManager class.
Classes for defining an mfem::Operator with std::functions.
Compile-time alias for a dimension.
Definition: geometry.hpp:11
a class for representing a geometric region that can be used for integration
Definition: domain.hpp:21
H1 elements of order p.
Stores all information held in the input file that is used to configure the solver.
ThermalMaterialIntegrand(MaterialType material)
Construct a ThermalMaterialIntegrand functor with material model of type MaterialType.
Parameters for an iterative linear solution scheme.
LinearSolver linear_solver
Linear solver selection.
Nonlinear solution scheme parameters.
NonlinearSolver nonlin_solver
Nonlinear solver selection.
a struct that is used in the physics modules to clarify which template arguments are user-controlled ...
Definition: common.hpp:23
A timestep and boundary condition enforcement method for a dynamic solver.
TimestepMethod timestepper
The timestepping method to be applied.
DirichletEnforcementMethod enforcement_method
The essential boundary enforcement method to use.
Linear anisotropic thermal material model.
This is a class that mimics most of std::tuple's interface, except that it is usable in CUDA kernels ...
Definition: tuple.hpp:28
A sentinel struct for eliding no-op tensor operations.
Definition: tensor.hpp:123
The material and load types for the thermal functional physics module.