Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
thermomechanics_monolithic.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 
14 #pragma once
15 
16 #include "mfem.hpp"
17 
20 #include "smith/physics/boundary_conditions/components.hpp"
24 #include "smith/physics/mesh.hpp"
25 
26 namespace smith {
27 
28 namespace thermomechanics {
29 
31 #ifdef MFEM_USE_STRUMPACK
33 #else
35 #endif
36 
41  .relative_tol = 1.0e-4,
42  .absolute_tol = 1.0e-8,
43  .max_iterations = 500,
44  .print_level = 1};
45 
46 } // namespace thermomechanics
47 
48 template <int order, int dim, typename parameters = Parameters<>,
49  typename parameter_indices = std::make_integer_sequence<int, parameters::n>>
51 
57 template <int order, int dim, typename... parameter_space, int... parameter_indices>
58 class ThermomechanicsMonolithic<order, dim, Parameters<parameter_space...>,
59  std::integer_sequence<int, parameter_indices...>> : public BasePhysics {
60  public:
62  static constexpr int VALUE = 0, DERIVATIVE = 1;
63  static constexpr int SHAPE = 0;
64  static constexpr auto I = Identity<dim>();
66 
69  static constexpr auto NUM_STATE_VARS = 2;
70 
73  template <typename T>
74  using qdata_type = std::shared_ptr<QuadratureData<T>>;
75 
90  const std::string& physics_name, std::shared_ptr<smith::Mesh> smith_mesh,
91  std::vector<std::string> parameter_names = {}, int cycle = 0, double time = 0.0,
92  bool checkpoint_to_disk = false)
93  : ThermomechanicsMonolithic(std::make_unique<EquationSolver>(nonlinear_opts, lin_opts, smith_mesh->getComm()),
94  physics_name, smith_mesh, parameter_names, cycle, time, checkpoint_to_disk)
95  {
96  }
97 
110  ThermomechanicsMonolithic(std::unique_ptr<EquationSolver> solver, const std::string& physics_name,
111  std::shared_ptr<smith::Mesh> smith_mesh, std::vector<std::string> parameter_names = {},
112  int cycle = 0, double time = 0.0, bool checkpoint_to_disk = false)
113  : BasePhysics(physics_name, smith_mesh, cycle, time, checkpoint_to_disk),
114  temperature_(StateManager::newState(H1<order>{}, detail::addPrefix(physics_name, "temperature"), mesh_->tag())),
115  displacement_(
116  StateManager::newState(H1<order, dim>{}, detail::addPrefix(physics_name, "displacement"), mesh_->tag())),
117  temperature_adjoint_(
118  StateManager::newState(H1<order>{}, detail::addPrefix(physics_name, "temperature_adjoint"), mesh_->tag())),
119  displacement_adjoint_(StateManager::newState(
120  H1<order, dim>{}, detail::addPrefix(physics_name, "dispacement_adjoint"), mesh_->tag())),
121  temperature_adjoint_load_(StateManager::newDual(
122  H1<order>{}, detail::addPrefix(physics_name, "temperature_adjoint_load"), mesh_->tag())),
123  displacement_adjoint_load_(StateManager::newDual(
124  H1<order, dim>{}, detail::addPrefix(physics_name, "displacement_adjoint_load"), mesh_->tag())),
125  bcs_displacement_(mfemParMesh()),
126  block_residual_with_bcs_(temperature_.space().TrueVSize() + displacement_.space().TrueVSize()),
127  nonlin_solver_(std::move(solver))
128  {
130  SLIC_ERROR_ROOT_IF(mfemParMesh().Dimension() != dim,
131  std::format("Compile time dimension, {0}, and runtime mesh dimension, {1}, mismatch", dim,
132  mfemParMesh().Dimension()));
133  SLIC_ERROR_ROOT_IF(!nonlin_solver_,
134  "EquationSolver argument is nullptr in ThermoMechanics constructor. It is possible that it was "
135  "previously moved.");
136 
137  is_quasistatic_ = true;
138 
139  states_.push_back(&temperature_);
140  states_.push_back(&displacement_);
141 
142  adjoints_.push_back(&temperature_adjoint_);
143  adjoints_.push_back(&displacement_adjoint_);
144 
145  const mfem::ParFiniteElementSpace* test_space_1 = &temperature_.space();
146  const mfem::ParFiniteElementSpace* test_space_2 = &displacement_.space();
147  const mfem::ParFiniteElementSpace* shape_space = &mesh_->shapeDisplacementSpace();
148 
149  std::array<const mfem::ParFiniteElementSpace*, NUM_STATE_VARS + sizeof...(parameter_space)> trial_spaces;
150  trial_spaces[0] = &temperature_.space();
151  trial_spaces[1] = &displacement_.space();
152 
153  SLIC_ERROR_ROOT_IF(
154  sizeof...(parameter_space) != parameter_names.size(),
155  std::format("{} parameter spaces given in the template argument but {} parameter names were supplied.",
156  sizeof...(parameter_space), parameter_names.size()));
157 
158  if constexpr (sizeof...(parameter_space) > 0) {
159  tuple<parameter_space...> types{};
160  for_constexpr<sizeof...(parameter_space)>([&](auto i) {
161  parameters_.emplace_back(mfemParMesh(), get<i>(types), detail::addPrefix(name_, parameter_names[i]));
162  trial_spaces[i + NUM_STATE_VARS] = &(parameters_[i].state->space());
163  });
164  }
165 
166  residual_T_ = std::make_unique<
167  ShapeAwareFunctional<shape_trial, scalar_test(scalar_trial, vector_trial, parameter_space...)>>(
168  shape_space, test_space_1, trial_spaces);
169  residual_u_ = std::make_unique<
170  ShapeAwareFunctional<shape_trial, vector_test(scalar_trial, vector_trial, parameter_space...)>>(
171  shape_space, test_space_2, trial_spaces);
172 
173  block_thermomech_offsets_.SetSize(NUM_STATE_VARS + 1);
174  block_thermomech_offsets_[0] = 0;
175  block_thermomech_offsets_[1] = temperature_.space().TrueVSize();
176  block_thermomech_offsets_[2] = displacement_.space().TrueVSize();
177  block_thermomech_offsets_.PartialSum();
178 
179  block_thermomech_ = std::make_unique<mfem::BlockVector>(block_thermomech_offsets_);
180 
181  block_thermomech_->GetBlock(0) = temperature_;
182  block_thermomech_->GetBlock(1) = displacement_;
183 
184  block_thermomech_adjoint_ = std::make_unique<mfem::BlockVector>(block_thermomech_offsets_);
185  block_thermomech_adjoint_->GetBlock(0) = temperature_adjoint_;
186  block_thermomech_adjoint_->GetBlock(1) = displacement_adjoint_;
187 
188  nonlin_solver_->setOperator(block_residual_with_bcs_);
189 
190  block_nonlinear_oper_ = std::make_unique<mfem::BlockOperator>(block_thermomech_offsets_);
191  block_nonlinear_oper_transpose_ = std::make_unique<mfem::BlockOperator>(block_thermomech_offsets_);
192 
193  initializeThermoMechanicsStates();
194  }
195 
198 
205  {
206  dt_ = 0.0;
207  time_end_step_ = 0.0;
208 
209  temperature_ = 0.0;
210  displacement_ = 0.0;
211 
212  temperature_adjoint_ = 0.0;
213  displacement_adjoint_ = 0.0;
214 
215  temperature_adjoint_load_ = 0.0;
216  displacement_adjoint_load_ = 0.0;
217  }
218 
223  void resetStates(int cycle = 0, double time = 0.0) override
224  {
226  initializeThermoMechanicsStates();
227 
228  if (!checkpoint_to_disk_) {
229  checkpoint_states_.clear();
230  auto state_names = stateNames();
231  for (const auto& state_name : state_names) {
232  checkpoint_states_[state_name].push_back(state(state_name));
233  }
234  }
235  }
236 
245  void setTemperatureBCs(const std::set<int>& temp_bdr, std::function<double(const mfem::Vector& x, double t)> temp)
246  {
247  // Project the coefficient onto the grid function
248  temp_bdr_coef_ = std::make_shared<mfem::FunctionCoefficient>(temp);
249 
250  bcs_.addEssential(temp_bdr, temp_bdr_coef_, temperature_.space());
251  }
252 
261  template <typename AppliedTemperatureFunction>
262  void setTemperatureBCs(AppliedTemperatureFunction applied_temperature, Domain& domain)
263  {
264  auto mfem_coefficient_function = [applied_temperature](const mfem::Vector X_mfem, double t) {
265  auto X = make_tensor<dim>([&X_mfem](int i) { return X_mfem[i]; });
266  return applied_temperature(X, t);
267  };
268 
269  temp_bdr_coef_ = std::make_shared<mfem::FunctionCoefficient>(mfem_coefficient_function);
270 
271  auto dof_list = domain.dof_list(&temperature_.space());
272 
273  bcs_.addEssential(dof_list, temp_bdr_coef_, temperature_.space(), 0);
274  }
275 
298  template <int... active_parameters, typename FluxType>
299  void setFluxBCs(DependsOn<active_parameters...>, FluxType flux_function, Domain& domain)
300  {
301  residual_T_->AddBoundaryIntegral(
302  Dimension<dim - 1>{}, DependsOn<0, active_parameters + NUM_STATE_VARS...>{},
303  [flux_function](double t, auto X, auto u, auto... params) {
304  auto temp = get<VALUE>(u);
305  auto n = cross(get<DERIVATIVE>(X));
306 
307  return flux_function(X, normalize(n), t, temp, params...);
308  },
309  domain);
310  }
311 
313  template <typename FluxType>
314  void setFluxBCs(FluxType flux_function, Domain& domain)
315  {
316  setFluxBCs(DependsOn<>{}, flux_function, domain);
317  }
318 
348  template <typename AppliedDisplacementFunction>
349  void setDisplacementBCs(AppliedDisplacementFunction applied_displacement, Domain& domain,
350  Components components = Component::ALL)
351  {
352  for (int i = 0; i < dim; ++i) {
353  if (components[size_t(i)]) {
354  auto mfem_coefficient_function = [applied_displacement, i](const mfem::Vector& X_mfem, double t) {
355  auto X = make_tensor<dim>([&X_mfem](int k) { return X_mfem[k]; });
356  return applied_displacement(X, t)[i];
357  };
358 
359  component_disp_bdr_coef_ = std::make_shared<mfem::FunctionCoefficient>(mfem_coefficient_function);
360 
361  auto dof_list = domain.dof_list(&displacement_.space());
362 
363  // scalar ldofs -> vector ldofs
364  displacement_.space().DofsToVDofs(i, dof_list);
365 
366  bcs_displacement_.addEssential(dof_list, component_disp_bdr_coef_, displacement_.space(), i);
367  }
368  }
369  }
370 
380  void setFixedBCs(Domain& domain, Components components = Component::ALL)
381  {
382  auto zero_vector_function = [](tensor<double, dim>, double) { return tensor<double, dim>{}; };
383  setDisplacementBCs(zero_vector_function, domain, components);
384  }
385 
409  template <int... active_parameters, typename TractionType>
410  void setTraction(DependsOn<active_parameters...>, TractionType traction_function, Domain& domain)
411  {
412  residual_u_->AddBoundaryIntegral(
413  Dimension<dim - 1>{}, DependsOn<NUM_STATE_VARS + active_parameters...>{},
414  [traction_function](double t, auto X, auto... params) {
415  auto n = cross(get<DERIVATIVE>(X));
416 
417  return -1.0 * traction_function(get<VALUE>(X), normalize(n), t, params...);
418  },
419  domain);
420  }
421 
423  template <typename TractionType>
424  void setTraction(TractionType traction_function, Domain& domain)
425  {
426  setTraction(DependsOn<>{}, traction_function, domain);
427  }
428 
436  void setTemperature(std::function<double(const mfem::Vector& x, double t)> temp)
437  {
438  // Project the coefficient onto the grid function
439  mfem::FunctionCoefficient temp_coef(temp);
440 
441  temp_coef.SetTime(time_);
442  temperature_.project(temp_coef);
443  }
444 
446  void setTemperature(const FiniteElementState temp) { temperature_ = temp; }
447 
453  void setDisplacement(std::function<void(const mfem::Vector& x, mfem::Vector& disp)> disp)
454  {
455  // Project the coefficient onto the grid function
456  mfem::VectorFunctionCoefficient disp_coef(dim, disp);
457  displacement_.project(disp_coef);
458  }
459 
461  void setDisplacement(const FiniteElementState& disp) { displacement_ = disp; }
462 
467  template <typename MaterialType>
468  struct ThermalMaterialInterface {
474  ThermalMaterialInterface(MaterialType material) : material_(material) {}
475 
479  // template <typename X, typename T, typename dT_dt, typename... Params>
480  template <typename X, typename T, typename U, typename... Params>
481  auto operator()(double /*time*/, X /* x */, T temperature, U displacement, Params... params) const
482  {
483  typename MaterialType::State state{};
484 
485  // Get the value and the gradient from the input tuple
486  auto [theta, dtheta_dX] = temperature;
487  auto du_dX = get<DERIVATIVE>(displacement);
488  // auto du_dt = get<VALUE>(dtemp_dt);
489 
490  auto [stress, heat_accumulation, internal_heat_source, heat_flux] =
491  material_(state, du_dX, theta, dtheta_dX, params...);
492 
493  // return smith::tuple{heat_capacity * du_dt, -1.0 * heat_flux};
494  return smith::tuple{-1.0 * internal_heat_source, -1.0 * heat_flux};
495  }
496 
497  private:
498  MaterialType material_;
499  };
500 
505  template <typename MaterialType>
506  struct SolidMaterialInterface {
508  SolidMaterialInterface(MaterialType material) : material_(material) {}
509 
513  template <typename X, typename T, typename U, typename... Params>
514  auto operator()(double /* time */, X /* x */, T temperature, U displacement, Params... params) const
515  {
516  typename MaterialType::State state{};
517 
518  auto [theta, dtheta_dX] = temperature;
519  auto du_dX = get<DERIVATIVE>(displacement);
520 
521  auto [stress, heat_accumulation, internal_heat_source, heat_flux] =
522  material_(state, du_dX, theta, dtheta_dX, params...);
523 
524  return smith::tuple{smith::zero{}, stress};
525  }
526 
527  private:
528  MaterialType material_;
529  };
530 
557  template <int... active_parameters, typename MaterialType>
558  void setMaterial(DependsOn<active_parameters...>, const MaterialType& material, Domain& domain)
559  {
560  residual_T_->AddDomainIntegral(Dimension<dim>{}, DependsOn<0, 1, NUM_STATE_VARS + active_parameters...>{},
561  ThermalMaterialInterface<MaterialType>(material), domain);
562  residual_u_->AddDomainIntegral(Dimension<dim>{}, DependsOn<0, 1, NUM_STATE_VARS + active_parameters...>{},
563  SolidMaterialInterface<MaterialType>(material), domain);
564  }
565 
567  template <typename MaterialType>
568  void setMaterial(const MaterialType& material, Domain& domain)
569  {
570  setMaterial(DependsOn<>{}, material, domain);
571  }
572 
595  template <int... active_parameters, typename SourceType>
596  void setSource(DependsOn<active_parameters...>, SourceType source_function, Domain& domain)
597  {
598  residual_T_->AddDomainIntegral(
599  Dimension<dim>{}, DependsOn<0, active_parameters + NUM_STATE_VARS...>{},
600  [source_function](double t, auto x, auto temperature, auto... params) {
601  // Get the value and the gradient from the input tuple
602  auto [T, dT_dX] = temperature;
603 
604  auto source = source_function(x, t, T, dT_dX, params...);
605 
606  // Return the source and the flux as a tuple
607  return smith::tuple{-1.0 * source, smith::zero{}};
608  },
609  domain);
610  }
611 
613  template <typename SourceType>
614  void setSource(SourceType source_function, Domain& domain)
615  {
616  setSource(DependsOn<>{}, source_function, domain);
617  }
618 
638  template <int... active_parameters, typename BodyForceType>
639  void addBodyForce(DependsOn<active_parameters...>, BodyForceType body_force, Domain& domain)
640  {
641  residual_u_->AddDomainIntegral(
642  Dimension<dim>{}, DependsOn<active_parameters + NUM_STATE_VARS...>{},
643  [body_force](double t, auto x, auto... params) {
644  auto bf = body_force(get<VALUE>(x), t, params...);
645  return smith::tuple{-1.0 * bf, smith::zero{}};
646  },
647  domain);
648  }
649 
651  template <typename BodyForceType>
652  void addBodyForce(BodyForceType body_force, Domain& domain)
653  {
654  addBodyForce(DependsOn<>{}, body_force, domain);
655  }
656 
658  void completeSetup() override
659  {
660  // Block operator representing the nonlinear system of equations
661  block_residual_with_bcs_ = mfem_ext::StdFunctionOperator(
662  temperature_.space().TrueVSize() + displacement_.space().TrueVSize(),
663 
664  // A lambda representing the residual R(T, u, params...)
665  // The input is the current state (T, u) in block vector form and the output is the block residual vector (r1,
666  // r2)
667  [this](const mfem::Vector& u, mfem::Vector& r) {
668  mfem::BlockVector block_u(const_cast<mfem::Vector&>(u), block_thermomech_offsets_);
669  mfem::BlockVector block_r(r, block_thermomech_offsets_);
670 
671  auto temperature = block_u.GetBlock(0);
672  auto displacement = block_u.GetBlock(1);
673 
674  auto r_1 = (*residual_T_)(time_, shapeDisplacement(), temperature, displacement,
675  *parameters_[parameter_indices].state...);
676  r_1.SetSubVector(bcs_.allEssentialTrueDofs(), 0.0);
677 
678  auto r_2 = (*residual_u_)(time_, shapeDisplacement(), temperature, displacement,
679  *parameters_[parameter_indices].state...);
680  r_2.SetSubVector(bcs_displacement_.allEssentialTrueDofs(), 0.0);
681 
682  block_r.GetBlock(0) = r_1;
683  block_r.GetBlock(1) = r_2;
684  },
685 
686  [this](const mfem::Vector& u) -> mfem::Operator& {
687  mfem::BlockVector block_u(const_cast<mfem::Vector&>(u), block_thermomech_offsets_);
688 
689  auto temperature = block_u.GetBlock(0);
690  auto displacement = block_u.GetBlock(1);
691 
692  // Get the components of the block Jacobian via auto differentiation
693  auto [r1, dr1_dT] = (*residual_T_)(time_, shapeDisplacement(), differentiate_wrt(temperature), displacement,
694  *parameters_[parameter_indices].state...);
695  auto [_1, dr1_du] = (*residual_T_)(time_, shapeDisplacement(), temperature, differentiate_wrt(displacement),
696  *parameters_[parameter_indices].state...);
697 
698  auto [r2, dr2_dT] = (*residual_u_)(time_, shapeDisplacement(), differentiate_wrt(temperature), displacement,
699  *parameters_[parameter_indices].state...);
700  auto [_2, dr2_du] = (*residual_u_)(time_, shapeDisplacement(), temperature, differentiate_wrt(displacement),
701  *parameters_[parameter_indices].state...);
702 
703  // Assemble the matrix-free Jacobian operators into hypre matrices
704  J_11_ = assemble(dr1_dT);
705  J_12_ = assemble(dr1_du);
706  J_21_ = assemble(dr2_dT);
707  J_22_ = assemble(dr2_du);
708 
709  // Eliminate the essential DoFs from the matrix
710  auto ess_tdofs_T = bcs_.allEssentialTrueDofs();
711 
712  mfem::HypreParMatrix* JTempTemp = J_11_->EliminateRowsCols(ess_tdofs_T);
713  mfem::HypreParMatrix* JDispTemp = J_21_->EliminateCols(ess_tdofs_T);
714  J_12_->EliminateRows(ess_tdofs_T);
715 
716  delete JTempTemp;
717  delete JDispTemp;
718 
719  auto ess_tdofs_u = bcs_displacement_.allEssentialTrueDofs();
720 
721  mfem::HypreParMatrix* JDispDisp = J_22_->EliminateRowsCols(ess_tdofs_u);
722  mfem::HypreParMatrix* JTempDisp = J_12_->EliminateCols(ess_tdofs_u);
723  J_21_->EliminateRows(ess_tdofs_u);
724 
725  delete JDispDisp;
726  delete JTempDisp;
727 
728  // Fill the block operator with the individual Jacobian blocks
729  block_nonlinear_oper_->SetBlock(0, 0, J_11_.get());
730  block_nonlinear_oper_->SetBlock(0, 1, J_12_.get());
731  block_nonlinear_oper_->SetBlock(1, 0, J_21_.get());
732  block_nonlinear_oper_->SetBlock(1, 1, J_22_.get());
733 
734  return *block_nonlinear_oper_;
735  });
736 
737  if (checkpoint_to_disk_) {
738  outputStateToDisk();
739  } else {
740  checkpoint_states_.clear();
741  auto state_names = stateNames();
742  for (const auto& state_name : state_names) {
743  checkpoint_states_[state_name].push_back(state(state_name));
744  }
745  }
746  }
747 
749  void advanceTimestep(double dt) override
750  {
752  SLIC_ERROR_ROOT_IF(!residual_T_ || !residual_u_,
753  "completeSetup() must be called prior to advanceTimestep(dt) in ThermoMechanics.");
754 
755  // If this is the first call, initialize the previous parameter values as the initial values
756  if (cycle_ == 0) {
757  for (auto& parameter : parameters_) {
758  *parameter.previous_state = *parameter.state;
759  }
760  }
761 
762  time_ += dt;
763 
764  for (auto& bc : bcs_.essentials()) {
765  bc.setDofs(temperature_, time_);
766  }
767 
768  for (auto& bc : bcs_displacement_.essentials()) {
769  bc.setDofs(displacement_, time_);
770  }
771 
772  // Update the block vector representation with the current temperature and displacement
773  block_thermomech_->GetBlock(0) = temperature_;
774  block_thermomech_->GetBlock(1) = displacement_;
775 
776  // Perform a nonlinear solve using Newton's method
777  nonlin_solver_->solve(*block_thermomech_);
778 
779  // Fill the independent temperature and displacement vectors from the block vector
780  static_cast<mfem::Vector&>(temperature_) = block_thermomech_->GetBlock(0);
781  static_cast<mfem::Vector&>(displacement_) = block_thermomech_->GetBlock(1);
782 
783  cycle_ += 1;
784 
785  if (checkpoint_to_disk_) {
786  outputStateToDisk();
787  } else {
788  auto state_names = stateNames();
789  for (const auto& state_name : state_names) {
790  checkpoint_states_[state_name].push_back(state(state_name));
791  }
792  }
793 
794  if (cycle_ > max_cycle_) {
795  timesteps_.push_back(dt);
796  max_cycle_ = cycle_;
797  max_time_ = time_;
798  }
799  }
800 
813  void setAdjointLoad(std::unordered_map<std::string, const smith::FiniteElementDual&> loads) override
814  {
815  SLIC_ERROR_ROOT_IF(loads.size() != 2,
816  "Adjoint load container is not the expected size of 2 in the thermomechanics module");
817  auto temperature_adjoint_load = loads.find("temperature");
818 
819  SLIC_ERROR_ROOT_IF(temperature_adjoint_load == loads.end(), "Adjoint load for \"temperature\" not found.");
820 
821  temperature_adjoint_load_ = temperature_adjoint_load->second;
822  // Add sign correction
823  temperature_adjoint_load_ *= -1.0;
824 
825  auto displacement_adjoint_load = loads.find("displacement");
826 
827  SLIC_ERROR_ROOT_IF(displacement_adjoint_load == loads.end(), "Adjoint load for \"displacement\" not found.");
828 
829  displacement_adjoint_load_ = displacement_adjoint_load->second;
830  // Add sign correction
831  displacement_adjoint_load_ *= -1.0;
832  }
833 
835  void reverseAdjointTimestep() override
836  {
838  auto& linear_solver = nonlin_solver_->linearSolver();
839 
840  cycle_--; // cycle is now at n \in [0,N-1]
841 
842  double dt = getCheckpointedTimestep(cycle_ + 1);
843 
844  auto end_step_solution = getCheckpointedStates(cycle_ + 1);
845  temperature_ = end_step_solution.at("temperature");
846  displacement_ = end_step_solution.at("displacement");
847 
848  auto [r1, dr1_dT] = (*residual_T_)(time_, shapeDisplacement(), differentiate_wrt(temperature_), displacement_,
849  *parameters_[parameter_indices].state...);
850  auto [_1, dr1_du] = (*residual_T_)(time_, shapeDisplacement(), temperature_, differentiate_wrt(displacement_),
851  *parameters_[parameter_indices].state...);
852 
853  auto [r2, dr2_dT] = (*residual_u_)(time_, shapeDisplacement(), differentiate_wrt(temperature_), displacement_,
854  *parameters_[parameter_indices].state...);
855  auto [_2, dr2_du] = (*residual_u_)(time_, shapeDisplacement(), temperature_, differentiate_wrt(displacement_),
856  *parameters_[parameter_indices].state...);
857 
858  J_11_ = assemble(dr1_dT);
859  J_12_ = assemble(dr1_du);
860  J_21_ = assemble(dr2_dT);
861  J_22_ = assemble(dr2_du);
862 
863  auto ess_tdofs_T = bcs_.allEssentialTrueDofs();
864  temperature_adjoint_load_.SetSubVector(ess_tdofs_T, 0.0);
865 
866  mfem::HypreParMatrix* JTempTemp = J_11_->EliminateRowsCols(ess_tdofs_T);
867  mfem::HypreParMatrix* JDispTemp = J_21_->EliminateCols(ess_tdofs_T);
868  J_12_->EliminateRows(ess_tdofs_T);
869 
870  delete JTempTemp;
871  delete JDispTemp;
872 
873  auto ess_tdofs_u = bcs_displacement_.allEssentialTrueDofs();
874  displacement_adjoint_load_.SetSubVector(ess_tdofs_u, 0.0);
875 
876  mfem::HypreParMatrix* JDispDisp = J_22_->EliminateRowsCols(ess_tdofs_u);
877  mfem::HypreParMatrix* JTempDisp = J_12_->EliminateCols(ess_tdofs_u);
878  J_21_->EliminateRows(ess_tdofs_u);
879 
880  delete JDispDisp;
881  delete JTempDisp;
882 
883  // Adjoint problem uses the tranpose of the Jacobian operator
884  J_11_transpose_ = std::unique_ptr<mfem::HypreParMatrix>(J_11_->Transpose());
885  J_12_transpose_ = std::unique_ptr<mfem::HypreParMatrix>(J_12_->Transpose());
886  J_21_transpose_ = std::unique_ptr<mfem::HypreParMatrix>(J_21_->Transpose());
887  J_22_transpose_ = std::unique_ptr<mfem::HypreParMatrix>(J_22_->Transpose());
888 
889  block_nonlinear_oper_transpose_->SetBlock(0, 0, J_11_transpose_.get());
890  block_nonlinear_oper_transpose_->SetBlock(0, 1, J_21_transpose_.get());
891  block_nonlinear_oper_transpose_->SetBlock(1, 0, J_12_transpose_.get());
892  block_nonlinear_oper_transpose_->SetBlock(1, 1, J_22_transpose_.get());
893 
894  linear_solver.SetOperator(*block_nonlinear_oper_transpose_);
895 
896  mfem::BlockVector block_thermomech_adjoint_load(block_thermomech_offsets_);
897  block_thermomech_adjoint_load.GetBlock(0) = temperature_adjoint_load_;
898  block_thermomech_adjoint_load.GetBlock(1) = displacement_adjoint_load_;
899 
900  linear_solver.Mult(block_thermomech_adjoint_load, *block_thermomech_adjoint_);
901 
902  // Fill the adjoint vectors
903  static_cast<mfem::Vector&>(temperature_adjoint_) = block_thermomech_adjoint_->GetBlock(0);
904  static_cast<mfem::Vector&>(displacement_adjoint_) = block_thermomech_adjoint_->GetBlock(1);
905 
906  // Reset the equation solver
907  nonlin_solver_->setOperator(block_residual_with_bcs_);
908 
909  time_end_step_ = time_;
910  time_ -= dt;
911  }
912 
914  FiniteElementDual computeTimestepSensitivity(size_t parameter_field) override
915  {
916  SLIC_ASSERT_MSG(parameter_field < sizeof...(parameter_indices),
917  std::format("Invalid parameter index '{}' requested for sensitivity.", parameter_field));
918 
919  auto dr1_dparam = smith::get<DERIVATIVE>(d_residual_T_d_[parameter_field](time_end_step_));
920  auto dr2_dparam = smith::get<DERIVATIVE>(d_residual_u_d_[parameter_field](time_end_step_));
921 
922  auto dr1_dparam_mat = assemble(dr1_dparam);
923  auto dr2_dparam_mat = assemble(dr2_dparam);
924 
925  dr1_dparam_mat->MultTranspose(temperature_adjoint_, *parameters_[parameter_field].sensitivity);
926  dr2_dparam_mat->AddMultTranspose(displacement_adjoint_, *parameters_[parameter_field].sensitivity);
927 
928  return *parameters_[parameter_field].sensitivity;
929  }
930 
933  {
934  auto dr1_dshape =
935  smith::get<DERIVATIVE>((*residual_T_)(time_end_step_, differentiate_wrt(shapeDisplacement()), temperature_,
936  displacement_, *parameters_[parameter_indices].state...));
937  auto dr2_dshape =
938  smith::get<DERIVATIVE>((*residual_u_)(time_end_step_, differentiate_wrt(shapeDisplacement()), temperature_,
939  displacement_, *parameters_[parameter_indices].state...));
940 
941  auto dr1_dshape_mat = assemble(dr1_dshape);
942  auto dr2_dshape_mat = assemble(dr2_dshape);
943 
944  dr1_dshape_mat->MultTranspose(temperature_adjoint_, shape_displacement_dual_);
945  dr2_dshape_mat->AddMultTranspose(displacement_adjoint_, shape_displacement_dual_);
946 
947  return shapeDisplacementSensitivity();
948  }
949 
951  const FiniteElementState& state(const std::string& state_name) const override
952  {
953  if (state_name == "temperature") {
954  return temperature_;
955  } else if (state_name == "displacement") {
956  return displacement_;
957  } else {
958  SLIC_ERROR_ROOT(std::format("State '{}' requested from thermomechanics solver '{}', but it doesn't exist",
959  state_name, name_));
960  }
961 
962  return temperature_;
963  }
964 
965  void setState(const std::string& state_name, const FiniteElementState& state) override
966  {
967  if (state_name == "temperature") {
968  temperature_ = state;
969  return;
970  } else if (state_name == "displacement") {
971  displacement_ = state;
972  return;
973  }
974 
975  SLIC_ERROR_ROOT(
976  std::format("setState for state name '{}' requested from thermomechanics module '{}', but it doesn't exist",
977  state_name, name_));
978  }
979 
980  std::vector<std::string> stateNames() const override
981  {
982  return std::vector<std::string>{{"temperature"}, {"displacement"}};
983  }
984 
985  const FiniteElementState& adjoint(const std::string& state_name) const override
986  {
987  if (state_name == "temperature") {
988  return temperature_adjoint_;
989  } else if (state_name == "displacement") {
990  return displacement_adjoint_;
991  } else {
992  SLIC_ERROR_ROOT(std::format("adjoint '{}' requested from thermomechanics solver '{}', but it doesn't exist",
993  state_name, name_));
994  }
995 
996  return temperature_adjoint_;
997  }
998 
1004  const smith::FiniteElementState& temperature() const { return temperature_; };
1005 
1011  const smith::FiniteElementState& displacement() const { return displacement_; };
1012 
1013  private:
1015  using vector_trial = H1<order, dim>;
1016 
1018  using scalar_trial = H1<order>;
1019 
1021  using vector_test = H1<order, dim>;
1022 
1024  using scalar_test = H1<order>;
1025 
1028  using shape_trial = smith::H1<smith::SHAPE_ORDER, dim>;
1029 
1031  FiniteElementState temperature_;
1032 
1034  FiniteElementState displacement_;
1035 
1037  FiniteElementState temperature_adjoint_;
1038 
1040  FiniteElementState displacement_adjoint_;
1041 
1043  smith::FiniteElementDual temperature_adjoint_load_;
1044 
1046  smith::FiniteElementDual displacement_adjoint_load_;
1047 
1049  smith::BoundaryConditionManager bcs_displacement_;
1050 
1052  std::unique_ptr<smith::ShapeAwareFunctional<shape_trial, scalar_test(scalar_trial, vector_trial, parameter_space...)>>
1053  residual_T_;
1054 
1056  std::unique_ptr<smith::ShapeAwareFunctional<shape_trial, vector_test(scalar_trial, vector_trial, parameter_space...)>>
1057  residual_u_;
1058 
1063  mfem_ext::StdFunctionOperator block_residual_with_bcs_;
1064 
1066  std::unique_ptr<smith::EquationSolver> nonlin_solver_;
1067 
1069  mfem::Array<int> block_thermomech_offsets_;
1070 
1072  std::unique_ptr<mfem::BlockVector> block_thermomech_;
1073 
1075  std::unique_ptr<mfem::BlockVector> block_thermomech_adjoint_;
1076 
1078  std::unique_ptr<mfem::BlockOperator> block_nonlinear_oper_;
1079 
1081  std::unique_ptr<mfem::BlockOperator> block_nonlinear_oper_transpose_;
1082 
1084  std::unique_ptr<mfem::HypreParMatrix> J_11_;
1085 
1087  std::unique_ptr<mfem::HypreParMatrix> J_12_;
1088 
1090  std::unique_ptr<mfem::HypreParMatrix> J_21_;
1091 
1093  std::unique_ptr<mfem::HypreParMatrix> J_22_;
1094 
1096  std::unique_ptr<mfem::HypreParMatrix> J_11_transpose_;
1097 
1099  std::unique_ptr<mfem::HypreParMatrix> J_12_transpose_;
1100 
1102  std::unique_ptr<mfem::HypreParMatrix> J_21_transpose_;
1103 
1105  std::unique_ptr<mfem::HypreParMatrix> J_22_transpose_;
1106 
1109  double time_end_step_;
1110 
1112  std::shared_ptr<mfem::Coefficient> temp_bdr_coef_;
1113 
1115  std::shared_ptr<mfem::Coefficient> component_disp_bdr_coef_;
1116 
1120  std::array<std::function<decltype((*residual_T_)(DifferentiateWRT<1>{}, 0.0, shape_displacement_, temperature_,
1121  displacement_, *parameters_[parameter_indices].state...))(double)>,
1122  sizeof...(parameter_indices)>
1123  d_residual_T_d_ = {[&](double _t) {
1124  return (*residual_T_)(DifferentiateWRT<NUM_STATE_VARS + 1 + parameter_indices>{}, _t, shape_displacement_,
1125  temperature_, displacement_, *parameters_[parameter_indices].state...);
1126  }...};
1127 
1128  std::array<std::function<decltype((*residual_u_)(DifferentiateWRT<1>{}, 0.0, shape_displacement_, temperature_,
1129  displacement_, *parameters_[parameter_indices].state...))(double)>,
1130  sizeof...(parameter_indices)>
1131  d_residual_u_d_ = {[&](double _t) {
1132  return (*residual_u_)(DifferentiateWRT<NUM_STATE_VARS + 1 + parameter_indices>{}, _t, shape_displacement_,
1133  temperature_, displacement_, *parameters_[parameter_indices].state...);
1134  }...};
1135 };
1136 
1137 } // 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 ...
A container for the boundary condition information relating to a specific physics module.
A set to flag components of a vector field.
Definition: components.hpp:29
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.
static FiniteElementDual newDual(FunctionSpace space, const std::string &dual_name, const std::string &mesh_tag)
Factory method for creating a new FEDual object.
static FiniteElementState newState(FunctionSpace space, const std::string &state_name, const std::string &mesh_tag)
Factory method for creating a new FEState object.
void setFluxBCs(FluxType flux_function, Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
ThermomechanicsMonolithic(std::unique_ptr< EquationSolver > solver, 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 Thermal-SolidMechanics object.
void setAdjointLoad(std::unordered_map< std::string, const smith::FiniteElementDual & > loads) override
Set the loads for the adjoint reverse timestep solve.
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 setTemperature(std::function< double(const mfem::Vector &x, double t)> temp)
Set the underlying finite element state to a prescribed temperature.
void addBodyForce(DependsOn< active_parameters... >, BodyForceType body_force, Domain &domain)
Set the body force function.
void setTraction(TractionType traction_function, Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::shared_ptr< QuadratureData< T > > qdata_type
a container holding quadrature point data of the specified type
void completeSetup() override
This is an overloaded member function, provided for convenience. It differs from the above function o...
ThermomechanicsMonolithic(const NonlinearSolverOptions nonlinear_opts, const LinearSolverOptions lin_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 Thermomechanics object.
void setState(const std::string &state_name, const FiniteElementState &state) override
Set the primal solution field values of the underlying physics solver.
const FiniteElementState & state(const std::string &state_name) const override
This is an overloaded member function, provided for convenience. It differs from the above function o...
const FiniteElementState & adjoint(const std::string &state_name) const override
Accessor for getting named finite element state adjoint solution from the physics modules.
const FiniteElementDual & computeTimestepShapeSensitivity() override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setFluxBCs(DependsOn< active_parameters... >, FluxType flux_function, Domain &domain)
Set the thermal flux boundary condition.
std::vector< std::string > stateNames() const override
Get a vector of the finite element state primal solution names.
void addBodyForce(BodyForceType body_force, Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void reverseAdjointTimestep() override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setTemperature(const FiniteElementState temp)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setSource(SourceType source_function, Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setDisplacement(std::function< void(const mfem::Vector &x, mfem::Vector &disp)> disp)
Set the underlying finite element state to a prescribed displacement.
void setTraction(DependsOn< active_parameters... >, TractionType traction_function, Domain &domain)
Set the traction boundary condition.
void setFixedBCs(Domain &domain, Components components=Component::ALL)
Shortcut to set selected components of displacements to zero for all time.
void setTemperatureBCs(AppliedTemperatureFunction applied_temperature, Domain &domain)
Set essential temperature boundary conditions (strongly enforced) matching setDisplacementBCs signatu...
void setSource(DependsOn< active_parameters... >, SourceType source_function, Domain &domain)
Set the thermal source function.
void setMaterial(const MaterialType &material, Domain &domain)
This is an overloaded member function, provided for convenience. It differs from the above function o...
void setMaterial(DependsOn< active_parameters... >, const MaterialType &material, Domain &domain)
Set the thermomechanical material model for the physics solver.
void setDisplacement(const FiniteElementState &disp)
This is an overloaded member function, provided for convenience. It differs from the above function o...
FiniteElementDual computeTimestepSensitivity(size_t parameter_field) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
void resetStates(int cycle=0, double time=0.0) override
Virtual implementation of required state reset method.
void setDisplacementBCs(AppliedDisplacementFunction applied_displacement, Domain &domain, Components components=Component::ALL)
Set essential displacement boundary conditions on selected components.
void initializeThermoMechanicsStates()
Non virtual method to reset temperature and displacement states to zero. This does not reset design p...
void advanceTimestep(double dt) override
This is an overloaded member function, provided for convenience. It differs from the above function o...
StdFunctionOperator is a class wrapping mfem::Operator so that the user can use std::function to defi...
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.
const smith::LinearSolverOptions direct_linear_options
the default direct solver option for solving the linear stiffness equations
const smith::NonlinearSolverOptions default_nonlinear_options
Reasonable defaults for most thermomechanics nonlinear solver options.
Accelerator functionality.
Definition: smith.cpp:36
tuple(T...) -> tuple< T... >
Class template argument deduction rule for tuples.
SMITH_HOST_DEVICE 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:966
auto differentiate_wrt(const mfem::Vector &v)
this function is intended to only be used in combination with smith::Functional::operator(),...
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:1122
#define SMITH_MARK_FUNCTION
Definition: profiling.hpp:90
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
mfem::Array< int > dof_list(const fes_t *fes) const
get mfem degree of freedom list for a given FiniteElementSpace
Definition: domain.cpp:466
H1 elements of order p.
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:45
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
An object containing all input file options for the solver for thermal structural solver.