Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
heat_transfer.hpp
Go to the documentation of this file.
1 // Copyright (c) Lawrence Livermore National Security, LLC and
2 // other Smith 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 
17 #include "smith/physics/common.hpp"
20 #include "smith/numerics/odes.hpp"
24 #include "smith/physics/mesh.hpp"
26 
27 namespace smith {
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 smith::TimesteppingOptions timestepping_opts, const std::string& physics_name,
119  std::shared_ptr<smith::Mesh> smith_mesh, std::vector<std::string> parameter_names = {}, int cycle = 0,
120  double time = 0.0, bool checkpoint_to_disk = false)
121  : HeatTransfer(std::make_unique<EquationSolver>(nonlinear_opts, lin_opts, smith_mesh->getComm()),
122  timestepping_opts, physics_name, smith_mesh, parameter_names, cycle, time, checkpoint_to_disk)
123  {
124  }
125 
142  HeatTransfer(std::unique_ptr<smith::EquationSolver> solver, const smith::TimesteppingOptions timestepping_opts,
143  const std::string& physics_name, std::shared_ptr<smith::Mesh> smith_mesh,
144  std::vector<std::string> parameter_names = {}, int cycle = 0, double time = 0.0,
145  bool checkpoint_to_disk = false)
146  : BasePhysics(physics_name, smith_mesh, cycle, time, checkpoint_to_disk),
147  temperature_(StateManager::newState(H1<order>{}, detail::addPrefix(physics_name, "temperature"), mesh_->tag())),
148  temperature_rate_(
149  StateManager::newState(H1<order>{}, detail::addPrefix(physics_name, "temperature_rate"), mesh_->tag())),
150  adjoint_temperature_(
151  StateManager::newState(H1<order>{}, detail::addPrefix(physics_name, "adjoint_temperature"), mesh_->tag())),
152  implicit_sensitivity_temperature_start_of_step_(adjoint_temperature_.space(),
153  detail::addPrefix(physics_name, "total_deriv_wrt_temperature")),
154  temperature_adjoint_load_(temperature_.space(), detail::addPrefix(physics_name, "temperature_adjoint_load")),
155  temperature_rate_adjoint_load_(temperature_.space(),
156  detail::addPrefix(physics_name, "temperature_rate_adjoint_load")),
157  residual_with_bcs_(temperature_.space().TrueVSize()),
158  nonlin_solver_(std::move(solver)),
159  ode_(temperature_.space().TrueVSize(),
160  {.time = time_, .u = u_, .dt = dt_, .du_dt = temperature_rate_, .previous_dt = previous_dt_},
161  *nonlin_solver_, bcs_)
162  {
163  SLIC_ERROR_ROOT_IF(
164  mfemParMesh().Dimension() != dim,
165  axom::fmt::format("Compile time class dimension template parameter and runtime mesh dimension do not match"));
166 
167  SLIC_ERROR_ROOT_IF(
168  !nonlin_solver_,
169  "EquationSolver argument is nullptr in HeatTransfer constructor. It is possible that it was previously moved.");
170 
171  // Check for dynamic mode
172  if (timestepping_opts.timestepper != TimestepMethod::QuasiStatic) {
173  ode_.SetTimestepper(timestepping_opts.timestepper);
174  ode_.SetEnforcementMethod(timestepping_opts.enforcement_method);
175  is_quasistatic_ = false;
176  } else {
177  is_quasistatic_ = true;
178  }
179 
180  states_.push_back(&temperature_);
181  if (!is_quasistatic_) {
182  states_.push_back(&temperature_rate_);
183  }
184 
185  adjoints_.push_back(&adjoint_temperature_);
186 
187  // Create a pack of the primal field and parameter finite element spaces
188  const mfem::ParFiniteElementSpace* test_space = &temperature_.space();
189  const mfem::ParFiniteElementSpace* shape_space = &mesh_->shapeDisplacementSpace();
190 
191  std::array<const mfem::ParFiniteElementSpace*, sizeof...(parameter_space) + NUM_STATE_VARS> trial_spaces;
192  trial_spaces[0] = &temperature_.space();
193  trial_spaces[1] = &temperature_.space();
194 
195  SLIC_ERROR_ROOT_IF(
196  sizeof...(parameter_space) != parameter_names.size(),
197  axom::fmt::format("{} parameter spaces given in the template argument but {} parameter names were supplied.",
198  sizeof...(parameter_space), parameter_names.size()));
199 
200  if constexpr (sizeof...(parameter_space) > 0) {
201  tuple<parameter_space...> types{};
202  for_constexpr<sizeof...(parameter_space)>([&](auto i) {
203  parameters_.emplace_back(mfemParMesh(), get<i>(types), detail::addPrefix(name_, parameter_names[i]));
204 
205  trial_spaces[i + NUM_STATE_VARS] = &(parameters_[i].state->space());
206  });
207  }
208 
209  residual_ =
210  std::make_unique<ShapeAwareFunctional<shape_trial, test(scalar_trial, scalar_trial, parameter_space...)>>(
211  shape_space, test_space, trial_spaces);
212 
213  nonlin_solver_->setOperator(residual_with_bcs_);
214 
215  int true_size = temperature_.space().TrueVSize();
216  u_.SetSize(true_size);
217  u_predicted_.SetSize(true_size);
218 
219  initializeThermalStates();
220  }
221 
231  HeatTransfer(const HeatTransferInputOptions& input_options, const std::string& physics_name,
232  std::shared_ptr<smith::Mesh> smith_mesh, 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, smith_mesh, {}, cycle, time)
235  {
236  for (const auto& mat : input_options.materials) {
237  if (std::holds_alternative<smith::heat_transfer::LinearIsotropicConductor>(mat)) {
238  setMaterial(std::get<smith::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 
277  {
278  dt_ = 0.0;
279  previous_dt_ = -1.0;
280  time_end_step_ = 0.0;
281 
282  u_ = 0.0;
283  temperature_ = 0.0;
284  temperature_rate_ = 0.0;
285  adjoint_temperature_ = 0.0;
286  implicit_sensitivity_temperature_start_of_step_ = 0.0;
287  temperature_adjoint_load_ = 0.0;
288  temperature_rate_adjoint_load_ = 0.0;
289  }
290 
297  void resetStates(int cycle = 0, double time = 0.0) override
298  {
300  initializeThermalStates();
301 
302  if (!checkpoint_to_disk_) {
303  checkpoint_states_.clear();
304  auto state_names = stateNames();
305  for (const auto& state_name : state_names) {
306  checkpoint_states_[state_name].push_back(state(state_name));
307  }
308  }
309  }
310 
319  void setTemperatureBCs(const std::set<int>& temp_bdr, std::function<double(const mfem::Vector& x, double t)> temp)
320  {
321  // Project the coefficient onto the grid function
322  temp_bdr_coef_ = std::make_shared<mfem::FunctionCoefficient>(temp);
323 
324  bcs_.addEssential(temp_bdr, temp_bdr_coef_, temperature_.space());
325  }
326 
334  void advanceTimestep(double dt) override
335  {
336  if (is_quasistatic_) {
337  time_ += dt;
338 
339  // Project the essential boundary coefficients
340  for (auto& bc : bcs_.essentials()) {
341  bc.setDofs(temperature_, time_);
342  }
343  nonlin_solver_->solve(temperature_);
344  } else {
345  // Step the time integrator
346  // Note that the ODE solver handles the essential boundary condition application itself
347 
348  // The current ode interface tracks 2 times, one internally which we have a handle to via time_,
349  // and one here via the step interface.
350  // We are ignoring this one, and just using the internal version for now.
351  // This may need to be revisited when more complex time integrators are required,
352  // but at the moment, the double times creates a lot of confusion, so
353  // we short circuit the extra time here by passing a dummy time and ignoring it.
354  double time_tmp = time_;
355  ode_.Step(temperature_, time_tmp, dt);
356  }
357 
358  cycle_ += 1;
359 
360  if (checkpoint_to_disk_) {
361  outputStateToDisk();
362  } else {
363  auto state_names = stateNames();
364  for (const auto& state_name : state_names) {
365  checkpoint_states_[state_name].push_back(state(state_name));
366  }
367  }
368 
369  if (cycle_ > max_cycle_) {
370  timesteps_.push_back(dt);
371  max_cycle_ = cycle_;
372  max_time_ = time_;
373  }
374  }
375 
380  template <typename MaterialType>
381  struct ThermalMaterialIntegrand {
387  ThermalMaterialIntegrand(MaterialType material) : material_(material) {}
388 
392  template <typename X, typename T, typename dT_dt, typename... Params>
393  auto operator()(double /*time*/, X x, T temperature, dT_dt dtemp_dt, Params... params) const
394  {
395  // Get the value and the gradient from the input tuple
396  auto [u, du_dX] = temperature;
397  auto du_dt = get<VALUE>(dtemp_dt);
398 
399  auto [heat_capacity, heat_flux] = material_(x, u, du_dX, params...);
400 
401  return smith::tuple{heat_capacity * du_dt, -1.0 * heat_flux};
402  }
403 
404  private:
405  MaterialType material_;
406  };
407 
432  template <int... active_parameters, typename MaterialType>
433  void setMaterial(DependsOn<active_parameters...>, const MaterialType& material, Domain& domain)
434  {
435  residual_->AddDomainIntegral(Dimension<dim>{}, DependsOn<0, 1, NUM_STATE_VARS + active_parameters...>{},
436  ThermalMaterialIntegrand<MaterialType>(material), domain);
437  }
438 
440  template <typename MaterialType>
441  void setMaterial(const MaterialType& material, Domain& domain)
442  {
443  setMaterial(DependsOn<>{}, material, domain);
444  }
445 
453  void setTemperature(std::function<double(const mfem::Vector& x, double t)> temp)
454  {
455  // Project the coefficient onto the grid function
456  mfem::FunctionCoefficient temp_coef(temp);
457 
458  temp_coef.SetTime(time_);
459  temperature_.project(temp_coef);
460  }
461 
463  void setTemperature(const FiniteElementState temp) { temperature_ = temp; }
464 
487  template <int... active_parameters, typename SourceType>
488  void setSource(DependsOn<active_parameters...>, SourceType source_function, Domain& domain)
489  {
490  residual_->AddDomainIntegral(
491  Dimension<dim>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
492  [source_function](double t, auto x, auto temperature, auto /* dtemp_dt */, auto... params) {
493  // Get the value and the gradient from the input tuple
494  auto [u, du_dX] = temperature;
495 
496  auto source = source_function(x, t, u, du_dX, params...);
497 
498  // Return the source and the flux as a tuple
499  return smith::tuple{-1.0 * source, smith::zero{}};
500  },
501  domain);
502  }
503 
505  template <typename SourceType>
506  void setSource(SourceType source_function, Domain& domain)
507  {
508  setSource(DependsOn<>{}, source_function, domain);
509  }
510 
533  template <int... active_parameters, typename FluxType>
534  void setFluxBCs(DependsOn<active_parameters...>, FluxType flux_function, Domain& domain)
535  {
536  residual_->AddBoundaryIntegral(
537  Dimension<dim - 1>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
538  [flux_function](double t, auto X, auto u, auto /* dtemp_dt */, auto... params) {
539  auto temp = get<VALUE>(u);
540  auto n = cross(get<DERIVATIVE>(X));
541 
542  return flux_function(X, normalize(n), t, temp, params...);
543  },
544  domain);
545  }
546 
548  template <typename FluxType>
549  void setFluxBCs(FluxType flux_function, Domain& domain)
550  {
551  setFluxBCs(DependsOn<>{}, flux_function, domain);
552  }
553 
559  const smith::FiniteElementState& temperature() const { return temperature_; };
560 
566  const smith::FiniteElementState& temperatureRate() const { return temperature_rate_; };
567 
574  const FiniteElementState& state(const std::string& state_name) const override
575  {
576  if (state_name == "temperature") {
577  return temperature_;
578  } else if (state_name == "temperature_rate") {
579  return temperature_rate_;
580  }
581 
582  SLIC_ERROR_ROOT(axom::fmt::format("State '{}' requested from solid mechanics module '{}', but it doesn't exist",
583  state_name, name_));
584  return temperature_;
585  }
586 
596  void setState(const std::string& state_name, const FiniteElementState& state) override
597  {
598  if (state_name == "temperature") {
599  temperature_ = state;
600  if (!checkpoint_to_disk_) {
601  checkpoint_states_["temperature"][static_cast<size_t>(cycle_)] = temperature_;
602  }
603  return;
604  }
605 
606  SLIC_ERROR_ROOT(axom::fmt::format(
607  "setState for state named '{}' requested from heat transfer module '{}', but it doesn't exist", state_name,
608  name_));
609  }
610 
616  virtual std::vector<std::string> stateNames() const override
617  {
618  if (is_quasistatic_) {
619  return std::vector<std::string>{"temperature"};
620  } else {
621  return std::vector<std::string>{"temperature", "temperature_rate"};
622  }
623  }
624 
648  template <int... active_parameters, typename callable>
650  {
651  residual_->AddBoundaryIntegral(Dimension<dim - 1>{}, DependsOn<0, 1, active_parameters + NUM_STATE_VARS...>{},
652  qfunction, domain);
653  }
654 
661  const FiniteElementState& adjoint(const std::string& state_name) const override
662  {
663  if (state_name == "temperature") {
664  return adjoint_temperature_;
665  }
666 
667  SLIC_ERROR_ROOT(axom::fmt::format("Adjoint '{}' requested from solid mechanics module '{}', but it doesn't exist",
668  state_name, name_));
669  return adjoint_temperature_;
670  }
671 
677  void completeSetup() override
678  {
679  if (is_quasistatic_) {
680  residual_with_bcs_ = mfem_ext::StdFunctionOperator(
681  temperature_.space().TrueVSize(),
682 
683  [this](const mfem::Vector& u, mfem::Vector& r) {
684  const mfem::Vector res = (*residual_)(time_, shapeDisplacement(), u, temperature_rate_,
685  *parameters_[parameter_indices].state...);
686 
687  // TODO this copy is required as the sundials solvers do not allow move assignments because of their memory
688  // tracking strategy
689  // See https://github.com/mfem/mfem/issues/3531
690  r = res;
691  r.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
692  },
693 
694  [this](const mfem::Vector& u) -> mfem::Operator& {
695  auto [r, drdu] = (*residual_)(time_, shapeDisplacement(), differentiate_wrt(u), temperature_rate_,
696  *parameters_[parameter_indices].state...);
697  J_ = assemble(drdu);
698  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
699  return *J_;
700  });
701  } else {
702  residual_with_bcs_ = mfem_ext::StdFunctionOperator(
703  temperature_.space().TrueVSize(),
704 
705  [this](const mfem::Vector& du_dt, mfem::Vector& r) {
706  add(1.0, u_, dt_, du_dt, u_predicted_);
707  const mfem::Vector res =
708  (*residual_)(time_, shapeDisplacement(), u_predicted_, du_dt, *parameters_[parameter_indices].state...);
709 
710  // TODO this copy is required as the sundials solvers do not allow move assignments because of their memory
711  // tracking strategy
712  // See https://github.com/mfem/mfem/issues/3531
713  r = res;
714  r.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
715  },
716 
717  [this](const mfem::Vector& du_dt) -> mfem::Operator& {
718  add(1.0, u_, dt_, du_dt, u_predicted_);
719 
720  // K := dR/du
721  auto K = smith::get<DERIVATIVE>((*residual_)(time_, shapeDisplacement(), differentiate_wrt(u_predicted_),
722  du_dt, *parameters_[parameter_indices].state...));
723  std::unique_ptr<mfem::HypreParMatrix> k_mat(assemble(K));
724 
725  // M := dR/du_dot
726  auto M =
727  smith::get<DERIVATIVE>((*residual_)(time_, shapeDisplacement(), u_predicted_, differentiate_wrt(du_dt),
728  *parameters_[parameter_indices].state...));
729  std::unique_ptr<mfem::HypreParMatrix> m_mat(assemble(M));
730 
731  // J := M + dt K
732  J_.reset(mfem::Add(1.0, *m_mat, dt_, *k_mat));
733  J_e_ = bcs_.eliminateAllEssentialDofsFromMatrix(*J_);
734 
735  return *J_;
736  });
737  }
738 
739  if (checkpoint_to_disk_) {
740  outputStateToDisk();
741  } else {
742  checkpoint_states_.clear();
743  auto state_names = stateNames();
744  for (const auto& state_name : state_names) {
745  checkpoint_states_[state_name].push_back(state(state_name));
746  }
747  }
748  }
749 
764  virtual void setAdjointLoad(std::unordered_map<std::string, const smith::FiniteElementDual&> loads) override
765  {
766  SLIC_ERROR_ROOT_IF(loads.size() == 0,
767  "Adjoint load container size must be greater than 0 in the heat transfer module.");
768 
769  auto temp_adjoint_load = loads.find("temperature");
770  auto temp_rate_adjoint_load = loads.find("temperature_rate"); // does not need to be specified
771 
772  SLIC_ERROR_ROOT_IF(temp_adjoint_load == loads.end(), "Adjoint load for \"temperature\" not found.");
773 
774  temperature_adjoint_load_ = temp_adjoint_load->second;
775  // Add the sign correction to move the term to the RHS
776  temperature_adjoint_load_ *= -1.0;
777 
778  if (temp_rate_adjoint_load != loads.end()) {
779  temperature_rate_adjoint_load_ = temp_rate_adjoint_load->second;
780  temperature_rate_adjoint_load_ *= -1.0;
781  }
782  }
783 
814  void reverseAdjointTimestep() override
815  {
816  auto& lin_solver = nonlin_solver_->linearSolver();
817 
818  mfem::HypreParVector adjoint_essential(temperature_adjoint_load_);
819  adjoint_essential = 0.0;
820 
821  SLIC_ERROR_ROOT_IF(cycle_ <= min_cycle_,
822  "Maximum number of adjoint timesteps exceeded! The number of adjoint timesteps must equal the "
823  "number of forward timesteps");
824 
825  cycle_--; // cycle is now at n \in [0,N-1]
826 
827  double dt = getCheckpointedTimestep(cycle_);
828  auto end_step_solution = getCheckpointedStates(cycle_ + 1);
829 
830  temperature_ = end_step_solution.at("temperature");
831 
832  if (is_quasistatic_) {
833  // We store the previous timestep's temperature as the current temperature for use in the lambdas computing the
834  // sensitivities.
835 
836  auto [_, drdu] = (*residual_)(time_, shapeDisplacement(), differentiate_wrt(temperature_), temperature_rate_,
837  *parameters_[parameter_indices].state...);
838  auto jacobian = assemble(drdu);
839  auto J_T = std::unique_ptr<mfem::HypreParMatrix>(jacobian->Transpose());
840 
841  for (const auto& bc : bcs_.essentials()) {
842  bc.apply(*J_T, temperature_adjoint_load_, adjoint_essential);
843  }
844 
845  lin_solver.SetOperator(*J_T);
846  lin_solver.Mult(temperature_adjoint_load_, adjoint_temperature_);
847  } else {
848  SLIC_ERROR_ROOT_IF(ode_.GetTimestepper() != TimestepMethod::BackwardEuler,
849  "Only backward Euler implemented for transient adjoint heat conduction.");
850 
851  temperature_rate_ = end_step_solution.at("temperature_rate");
852 
853  // K := dR/du
854  auto K = smith::get<DERIVATIVE>((*residual_)(time_, shapeDisplacement(), differentiate_wrt(temperature_),
855  temperature_rate_, *parameters_[parameter_indices].state...));
856  std::unique_ptr<mfem::HypreParMatrix> k_mat(assemble(K));
857 
858  // M := dR/du_dot
859  auto M = smith::get<DERIVATIVE>((*residual_)(time_, shapeDisplacement(), temperature_,
860  differentiate_wrt(temperature_rate_),
861  *parameters_[parameter_indices].state...));
862  std::unique_ptr<mfem::HypreParMatrix> m_mat(assemble(M));
863 
864  J_.reset(mfem::Add(1.0, *m_mat, dt, *k_mat));
865  auto J_T = std::unique_ptr<mfem::HypreParMatrix>(J_->Transpose());
866 
867  // recall that temperature_adjoint_load_vector and d_temperature_dt_adjoint_load_vector were already multiplied by
868  // -1 above
869  mfem::HypreParVector modified_RHS(temperature_adjoint_load_);
870  modified_RHS *= dt;
871  modified_RHS.Add(1.0, temperature_rate_adjoint_load_);
872  modified_RHS.Add(-dt, implicit_sensitivity_temperature_start_of_step_);
873 
874  for (const auto& bc : bcs_.essentials()) {
875  bc.apply(*J_T, modified_RHS, adjoint_essential);
876  }
877 
878  lin_solver.SetOperator(*J_T);
879  lin_solver.Mult(modified_RHS, adjoint_temperature_);
880 
881  // This multiply is technically on M transposed. However, this matrix should be symmetric unless
882  // the thermal capacity is a function of the temperature rate of change, which is thermodynamically
883  // impossible, and fortunately not possible with our material interface.
884  // Not doing the transpose here to avoid doing unnecessary work.
885  m_mat->Mult(adjoint_temperature_, implicit_sensitivity_temperature_start_of_step_);
886  implicit_sensitivity_temperature_start_of_step_ *= -1.0 / dt;
887  implicit_sensitivity_temperature_start_of_step_.Add(1.0 / dt,
888  temperature_rate_adjoint_load_); // already multiplied by -1
889  }
890 
891  time_end_step_ = time_;
892  time_ -= dt;
893  }
894 
904  FiniteElementDual computeTimestepSensitivity(size_t parameter_field) override
905  {
906  // TODO: the time is likely not being handled correctly on the reverse pass, but we don't
907  // have tests to confirm.
908  auto drdparam = smith::get<DERIVATIVE>(d_residual_d_[parameter_field](time_end_step_));
909  auto drdparam_mat = assemble(drdparam);
910 
911  drdparam_mat->MultTranspose(adjoint_temperature_, *parameters_[parameter_field].sensitivity);
912 
913  return *parameters_[parameter_field].sensitivity;
914  }
915 
925  {
926  auto drdshape =
927  smith::get<DERIVATIVE>((*residual_)(time_end_step_, differentiate_wrt(shapeDisplacement()), temperature_,
928  temperature_rate_, *parameters_[parameter_indices].state...));
929 
930  auto drdshape_mat = assemble(drdshape);
931 
932  drdshape_mat->MultTranspose(adjoint_temperature_, shape_displacement_dual_);
933 
934  return shapeDisplacementSensitivity();
935  }
936 
944  const std::unordered_map<std::string, const smith::FiniteElementDual&> computeInitialConditionSensitivity()
945  const override
946  {
947  return {{"temperature", implicit_sensitivity_temperature_start_of_step_}};
948  }
949 
951  virtual ~HeatTransfer() = default;
952 
953  protected:
956 
959 
961  using test = H1<order>;
962 
965 
968 
971 
974 
977 
980 
982  std::unique_ptr<ShapeAwareFunctional<shape_trial, test(scalar_trial, scalar_trial, parameter_space...)>> residual_;
983 
985  std::unique_ptr<mfem::HypreParMatrix> M_;
986 
988  std::shared_ptr<mfem::Coefficient> temp_bdr_coef_;
989 
995 
997  std::unique_ptr<EquationSolver> nonlin_solver_;
998 
1005 
1007  std::unique_ptr<mfem::HypreParMatrix> J_;
1008 
1011  std::unique_ptr<mfem::HypreParMatrix> J_e_;
1012 
1014  double dt_;
1015 
1018 
1022 
1024  mfem::Vector u_;
1025 
1027  mfem::Vector u_predicted_;
1028 
1032  std::array<std::function<decltype((*residual_)(DifferentiateWRT<1>{}, 0.0, shape_displacement_, temperature_,
1033  temperature_rate_, *parameters_[parameter_indices].state...))(double)>,
1034  sizeof...(parameter_indices)>
1035  d_residual_d_ = {[&](double TIME) {
1036  return (*residual_)(DifferentiateWRT<NUM_STATE_VARS + 1 + parameter_indices>{}, TIME, shape_displacement_,
1037  temperature_, temperature_rate_, *parameters_[parameter_indices].state...);
1038  }...};
1039 };
1040 
1041 } // namespace smith
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.
void setMaterial(DependsOn< active_parameters... >, const MaterialType &material, Domain &domain)
Set the thermal material model for the physics solver.
void setFluxBCs(FluxType flux_function, Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
smith::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...
const FiniteElementState & adjoint(const std::string &state_name) const override
Accessor for getting named finite element state adjoint solution from the physics modules.
void setSource(DependsOn< active_parameters... >, SourceType source_function, Domain &domain)
Set the thermal source function.
const FiniteElementDual & computeTimestepShapeSensitivity() override
Compute the implicit sensitivity of the quantity of interest used in defining the load for the adjoin...
double time_end_step_
End of step time used in reverse mode so that the time can be decremented on reverse steps.
const std::unordered_map< std::string, const smith::FiniteElementDual & > computeInitialConditionSensitivity() const override
Compute the implicit sensitivity of the quantity of interest with respect to the initial temperature.
smith::FiniteElementDual temperature_rate_adjoint_load_
The downstream derivative of the qoi with repect to the primal temperature rate variable.
const smith::FiniteElementState & temperatureRate() const
Get the temperature rate of change state.
FiniteElementDual computeTimestepSensitivity(size_t parameter_field) override
Compute the implicit sensitivity of the quantity of interest used in defining the load for the adjoin...
std::unique_ptr< ShapeAwareFunctional< shape_trial, test(scalar_trial, scalar_trial, parameter_space...)> > residual_
smith::Functional that is used to calculate the residual and its derivatives
void addCustomBoundaryIntegral(DependsOn< active_parameters... >, callable qfunction, Domain &domain)
register a custom boundary integral calculation as part of the residual
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)
void setState(const std::string &state_name, const FiniteElementState &state) override
Set the primal solution field (temperature) for the underlying heat transfer solver.
HeatTransfer(std::unique_ptr< smith::EquationSolver > solver, const smith::TimesteppingOptions timestepping_opts, const std::string &physics_name, std::shared_ptr< smith::Mesh > smith_mesh, std::vector< std::string > parameter_names={}, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Construct a new heat transfer object.
mfem_ext::FirstOrderODE ode_
the ordinary differential equation that describes how to solve for the time derivative of temperature...
void initializeThermalStates()
Non virtual method to reset thermal states to zero. This does not reset design parameters or shape.
HeatTransfer(const NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions lin_opts, const smith::TimesteppingOptions timestepping_opts, const std::string &physics_name, std::shared_ptr< smith::Mesh > smith_mesh, std::vector< std::string > parameter_names={}, int cycle=0, double time=0.0, bool checkpoint_to_disk=false)
Construct a new heat transfer object.
std::shared_ptr< mfem::Coefficient > temp_bdr_coef_
Coefficient containing the essential boundary values.
void setMaterial(const MaterialType &material, Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
virtual void setAdjointLoad(std::unordered_map< std::string, const smith::FiniteElementDual & > loads) override
Set the loads for the adjoint reverse timestep solve.
void setFluxBCs(DependsOn< active_parameters... >, FluxType flux_function, Domain &domain)
Set the thermal flux boundary condition.
mfem_ext::StdFunctionOperator residual_with_bcs_
mfem::Operator that describes the weight residual and its gradient with respect to temperature
const FiniteElementState & state(const std::string &state_name) const override
Accessor for getting named finite element state primal solution from the physics modules.
void completeSetup() override
Complete the initialization and allocation of the data structures.
smith::FiniteElementState adjoint_temperature_
The adjoint temperature finite element states, the multiplier on the residual for a given timestep.
void setSource(SourceType source_function, Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
HeatTransfer(const HeatTransferInputOptions &input_options, const std::string &physics_name, std::shared_ptr< smith::Mesh > smith_mesh, int cycle=0, double time=0.0)
Construct a new Nonlinear HeatTransfer Solver object.
std::unique_ptr< EquationSolver > nonlin_solver_
the specific methods and tolerances specified to solve the nonlinear residual equations
FiniteElementState temperature_rate_
Rate of change in temperature at the current adjoint timestep.
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.
void setTemperature(std::function< double(const mfem::Vector &x, double t)> temp)
Set the underlying finite element state to a prescribed temperature.
void setTemperature(const FiniteElementState temp)
This is an overloaded member function, provided for convenience. It differs from the above function o...
smith::FiniteElementDual temperature_adjoint_load_
The downstream derivative of the qoi with repect to the primal temperature variable.
virtual std::vector< std::string > stateNames() const override
Get a vector of the finite element state primal solution names.
An object containing the solver for a heat transfer PDE.
static FiniteElementState newState(FunctionSpace space, const std::string &state_name, const std::string &mesh_tag)
Factory method for creating a new FEState object.
FirstOrderODE is a class wrapping mfem::TimeDependentOperator so that the user can use std::function ...
Definition: odes.hpp:240
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.
Smith mesh class which assists in constructing the appropriate parallel mfem meshes and registering a...
constexpr SMITH_HOST_DEVICE void for_constexpr(const 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 direct_linear_options
the default direct solver option for solving the linear stiffness equations
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.
Accelerator functionality.
Definition: smith.cpp:36
tuple(T...) -> tuple< T... >
Class template argument deduction rule for tuples.
auto differentiate_wrt(const mfem::Vector &v)
this function is intended to only be used in combination with smith::Functional::operator(),...
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:959
SMITH_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:1115
bool holds_alternative(const variant< T0, T1 > &v)
Checks whether a variant's active member is of a certain type.
Definition: variant.hpp:381
Wrapper classes for using MFEM's ODE solver objects.
Wrapper of smith::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:17
a class for representing a geometric region that can be used for integration
Definition: domain.hpp:33
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:21
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:122
The material and load types for the thermal functional physics module.