Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
differentiable_test_utils.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 "gretl/double_state.hpp"
19 
20 namespace smith {
21 
23 template <typename DispSpace, typename DensitySpace>
24 auto createKineticEnergyIntegrator(smith::Domain& domain, const mfem::ParFiniteElementSpace& velocity_space,
25  const mfem::ParFiniteElementSpace& density_space)
26 {
27  static constexpr int dim = DispSpace::components;
28  auto ke_integrator = std::make_shared<smith::Functional<double(DispSpace, DispSpace, DensitySpace)>>(
29  std::array<const mfem::ParFiniteElementSpace*, 3>{&velocity_space, &velocity_space, &density_space});
30  ke_integrator->AddDomainIntegral(
32  [&](auto /*t*/, auto /*X*/, auto U, auto V, auto Rho) {
33  auto rho = get<VALUE>(Rho);
34  auto v = get<VALUE>(V);
35  auto ke = 0.5 * rho * inner(v, v);
36  auto dx_dX = get<DERIVATIVE>(U) + Identity<dim>();
37  auto J = det(dx_dX);
38  return ke * J;
39  },
40  domain);
41  return ke_integrator;
42 }
43 
45 template <typename DispSpace, typename DensitySpace>
46 gretl::State<double> computeKineticEnergy(
47  const std::shared_ptr<smith::Functional<double(DispSpace, DispSpace, DensitySpace)>>& energy_func,
48  smith::FieldState disp, smith::FieldState velo, smith::FieldState density, double scaling)
49 {
50  return gretl::create_state<double, double>(
51  // specify how to zero the dual
52  [](double forwardVal) { return 0 * forwardVal; },
53  // define how to (re)evaluate the output
54  [=](const smith::FEFieldPtr& Disp, const smith::FEFieldPtr& Velo, const smith::FEFieldPtr& Density) -> double {
55  return (*energy_func)(0.0, *Disp, *Velo, *Density) * scaling;
56  },
57  // define how to backpropagate the vjp
58  [=](const smith::FEFieldPtr& Disp, const smith::FEFieldPtr& Velo, const smith::FEFieldPtr& Density,
59  const double& /*ke*/, smith::FEDualPtr& Disp_dual, smith::FEDualPtr& Velo_dual,
60  smith::FEDualPtr& Density_dual, const double& ke_dual) -> void {
61  auto ddisp = (*energy_func)(0.0, smith::differentiate_wrt(*Disp), *Velo, *Density);
62  auto de_ddisp = assemble(smith::get<smith::DERIVATIVE>(ddisp));
63 
64  auto dvelo = (*energy_func)(0.0, *Disp, smith::differentiate_wrt(*Velo), *Density);
65  auto de_dvelo = assemble(smith::get<smith::DERIVATIVE>(dvelo));
66 
67  auto ddens = (*energy_func)(0.0, *Disp, *Velo, smith::differentiate_wrt(*Density));
68  auto de_ddensity = assemble(smith::get<smith::DERIVATIVE>(ddens));
69 
70  Disp_dual->Add(scaling * ke_dual, *de_ddisp);
71  Velo_dual->Add(scaling * ke_dual, *de_dvelo);
72  Density_dual->Add(scaling * ke_dual, *de_ddensity);
73  },
74  // give the input values
75  disp, velo, density);
76 }
77 
79 inline auto checkGradients(const gretl::State<double>& objectiveState, FieldState& inputState,
80  FiniteElementDual& inputDual, double objectiveBase, gretl::DataStore& dataStore, double eps)
81 {
82  smith::FiniteElementState inputSave(*inputState.get());
83  dataStore.reset();
84  smith::FiniteElementState& input = *inputState.get();
85  smith::FiniteElementState pert(input.space(), input.name() + "_pert");
86 
87  int sz = pert.Size();
88  for (int i = 0; i < sz; ++i) {
89  pert[i] = -1.2 + 2.02 * (double(i) / sz);
90  input[i] += eps * pert[i];
91  }
92 
93  double objectivePlus = objectiveState.get();
94 
95  double directionDeriv = 0.0;
96  for (int i = 0; i < sz; ++i) {
97  directionDeriv += pert[i] * inputDual[i];
98  }
99 
100  *inputState.get() = inputSave;
101 
102  return std::make_pair(directionDeriv, (objectivePlus - objectiveBase) / eps);
103 }
104 
106 inline auto checkGradients(const gretl::State<double>& objectiveState, gretl::State<double, double>& inputState,
107  double& inputDual, double objectiveBase, gretl::DataStore& dataStore, double eps)
108 {
109  double inputSave = inputState.get();
110  dataStore.reset();
111  inputState.set(inputSave + eps);
112  double objectivePlus = objectiveState.get();
113  inputState.set(inputSave);
114  return std::make_pair(inputDual, (objectivePlus - objectiveBase) / eps);
115 }
116 
120 inline double checkGradWrt(const gretl::State<double>& objective, smith::FieldState& input, double eps,
121  size_t num_fd_steps = 4, bool printmore = false)
122 {
123  auto& graph = objective.data_store();
124 
125  // reset each time, just to be sure
126  graph.reset();
127 
128  // re-evaluate the final objective value
129  double objectiveBase = objective.get();
130 
131  // back-propagate to get sensitivity wrt input states
132  gretl::set_as_objective(objective);
133  graph.back_prop();
134 
135  auto dual_vec = input.get_dual();
136 
137  std::vector<double> grad_errors;
138  auto [grad, grad_fd] = checkGradients(objective, input, *dual_vec, objectiveBase, graph, eps);
139  grad_errors.push_back(std::abs(grad - grad_fd));
140 
141  for (size_t step = 1; step < num_fd_steps; ++step) {
142  eps /= 2;
143  std::tie(grad, grad_fd) = checkGradients(objective, input, *dual_vec, objectiveBase, graph, eps);
144  if (printmore) std::cout << "grad = " << grad << "\ngrad fd = " << grad_fd << std::endl;
145  grad_errors.push_back(std::abs(grad - grad_fd));
146  }
147 
148  for (size_t step = 0; step < num_fd_steps; ++step) {
149  std::cout << "grad error " << step << " = " << grad_errors[step] << std::endl;
150  }
151 
152  if (num_fd_steps >= 2) {
153  return std::log2(grad_errors[0] / grad_errors[num_fd_steps - 1]) / static_cast<double>(num_fd_steps - 1);
154  }
155 
156  return 0;
157 };
158 
162 inline double checkGradWrt(const gretl::State<double>& objective, gretl::State<double, double>& input, double eps,
163  size_t num_fd_steps = 4, bool printmore = false)
164 {
165  auto& graph = objective.data_store();
166 
167  // reset each time, just to be sure
168  graph.reset();
169 
170  // re-evaluate the final objective value
171  double objectiveBase = objective.get();
172 
173  // back-propagate to get sensitivity wrt input states
174  gretl::set_as_objective(objective);
175  graph.back_prop();
176 
177  auto dual = input.get_dual();
178 
179  std::vector<double> grad_errors;
180  auto [grad, grad_fd] = checkGradients(objective, input, dual, objectiveBase, graph, eps);
181  grad_errors.push_back(std::abs(grad - grad_fd));
182 
183  for (size_t step = 1; step < num_fd_steps; ++step) {
184  eps /= 2;
185  std::tie(grad, grad_fd) = checkGradients(objective, input, dual, objectiveBase, graph, eps);
186  if (printmore) std::cout << "grad = " << grad << "\ngrad fd = " << grad_fd << std::endl;
187  grad_errors.push_back(std::abs(grad - grad_fd));
188  }
189 
190  for (size_t step = 0; step < num_fd_steps; ++step) {
191  std::cout << "grad error " << step << " = " << grad_errors[step] << std::endl;
192  }
193 
194  if (num_fd_steps >= 2) {
195  return std::log2(grad_errors[0] / grad_errors[num_fd_steps - 1]) / static_cast<double>(num_fd_steps - 1);
196  }
197 
198  return 0;
199 };
200 
201 } // namespace smith
This file contains the declaration of the boundary condition manager class.
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::ParFiniteElementSpace & space()
Returns a non-owning reference to the internal FESpace.
std::string name() const
Returns the name of the FEState (field)
Accelerator functionality.
Definition: smith.cpp:36
auto checkGradients(const gretl::State< double > &objectiveState, FieldState &inputState, FiniteElementDual &inputDual, double objectiveBase, gretl::DataStore &dataStore, double eps)
testing utility to confirm order of convergence of the finite differences relative to the backprop gr...
std::shared_ptr< FiniteElementState > FEFieldPtr
typedef
Definition: field_state.hpp:20
gretl::State< double > computeKineticEnergy(const std::shared_ptr< smith::Functional< double(DispSpace, DispSpace, DensitySpace)>> &energy_func, smith::FieldState disp, smith::FieldState velo, smith::FieldState density, double scaling)
Utility function which computes the kinetic energy and returns it as a gretl state (with its vjp defi...
double checkGradWrt(const gretl::State< double > &objective, smith::FieldState &input, double eps, size_t num_fd_steps=4, bool printmore=false)
Testing utility function which runs a gretl graph num_fd_steps (with increasingly smaller finite diff...
gretl::State< FEFieldPtr, FEDualPtr > FieldState
typedef
Definition: field_state.hpp:22
auto differentiate_wrt(const mfem::Vector &v)
this function is intended to only be used in combination with smith::Functional::operator(),...
auto createKineticEnergyIntegrator(smith::Domain &domain, const mfem::ParFiniteElementSpace &velocity_space, const mfem::ParFiniteElementSpace &density_space)
Utility function to construct a smith::functional which evaluates the total kinetic energy.
std::shared_ptr< FiniteElementDual > FEDualPtr
typedef
Definition: field_state.hpp:21
SMITH_HOST_DEVICE auto abs(dual< gradient_type > x)
Implementation of absolute value function for dual numbers.
Definition: dual.hpp:219
constexpr SMITH_HOST_DEVICE auto det(const isotropic_tensor< T, m, m > &I)
compute the determinant of an isotropic tensor
constexpr SMITH_HOST_DEVICE auto inner(const dual< S > &A, const dual< T > &B)
Definition: dual.hpp:281
Specifies interface for evaluating scalar objective from fields and their field gradients.
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
Dual number struct (value plus gradient)
Definition: dual.hpp:28