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"
18 
19 namespace smith {
20 
22 template <typename DispSpace, typename DensitySpace>
23 auto createKineticEnergyIntegrator(smith::Domain& domain, const mfem::ParFiniteElementSpace& velocity_space,
24  const mfem::ParFiniteElementSpace& density_space)
25 {
26  static constexpr int dim = DispSpace::components;
27  auto ke_integrator = std::make_shared<smith::Functional<double(DispSpace, DispSpace, DensitySpace)>>(
28  std::array<const mfem::ParFiniteElementSpace*, 3>{&velocity_space, &velocity_space, &density_space});
29  ke_integrator->AddDomainIntegral(
31  [&](auto /*t*/, auto /*X*/, auto U, auto V, auto Rho) {
32  auto rho = get<VALUE>(Rho);
33  auto v = get<VALUE>(V);
34  auto ke = 0.5 * rho * inner(v, v);
35  auto dx_dX = get<DERIVATIVE>(U) + Identity<dim>();
36  auto J = det(dx_dX);
37  return ke * J;
38  },
39  domain);
40  return ke_integrator;
41 }
42 
44 template <typename DispSpace, typename DensitySpace>
45 gretl::State<double> computeKineticEnergy(
46  const std::shared_ptr<smith::Functional<double(DispSpace, DispSpace, DensitySpace)>>& energy_func,
47  smith::FieldState disp, smith::FieldState velo, smith::FieldState density, double scaling)
48 {
49  return gretl::create_state<double, double>(
50  // specify how to zero the dual
51  [](double forwardVal) { return 0 * forwardVal; },
52  // define how to (re)evaluate the output
53  [=](const smith::FEFieldPtr& Disp, const smith::FEFieldPtr& Velo, const smith::FEFieldPtr& Density) -> double {
54  return (*energy_func)(0.0, *Disp, *Velo, *Density) * scaling;
55  },
56  // define how to backpropagate the vjp
57  [=](const smith::FEFieldPtr& Disp, const smith::FEFieldPtr& Velo, const smith::FEFieldPtr& Density,
58  const double& /*ke*/, smith::FEDualPtr& Disp_dual, smith::FEDualPtr& Velo_dual,
59  smith::FEDualPtr& Density_dual, const double& ke_dual) -> void {
60  auto ddisp = (*energy_func)(0.0, smith::differentiate_wrt(*Disp), *Velo, *Density);
61  auto de_ddisp = assemble(smith::get<smith::DERIVATIVE>(ddisp));
62 
63  auto dvelo = (*energy_func)(0.0, *Disp, smith::differentiate_wrt(*Velo), *Density);
64  auto de_dvelo = assemble(smith::get<smith::DERIVATIVE>(dvelo));
65 
66  auto ddens = (*energy_func)(0.0, *Disp, *Velo, smith::differentiate_wrt(*Density));
67  auto de_ddensity = assemble(smith::get<smith::DERIVATIVE>(ddens));
68 
69  Disp_dual->Add(scaling * ke_dual, *de_ddisp);
70  Velo_dual->Add(scaling * ke_dual, *de_dvelo);
71  Density_dual->Add(scaling * ke_dual, *de_ddensity);
72  },
73  // give the input values
74  disp, velo, density);
75 }
76 
78 inline auto checkGradients(const gretl::State<double>& objectiveState, FieldState& inputState,
79  FiniteElementDual& inputDual, double objectiveBase, gretl::DataStore& dataStore, double eps)
80 {
81  smith::FiniteElementState inputSave(*inputState.get());
82  dataStore.reset();
83  smith::FiniteElementState& input = *inputState.get();
84  smith::FiniteElementState pert(input.space(), input.name() + "_pert");
85 
86  int sz = pert.Size();
87  for (int i = 0; i < sz; ++i) {
88  pert[i] = -1.2 + 2.02 * (double(i) / sz);
89  input[i] += eps * pert[i];
90  }
91 
92  double objectivePlus = objectiveState.get();
93 
94  double directionDeriv = 0.0;
95  for (int i = 0; i < sz; ++i) {
96  directionDeriv += pert[i] * inputDual[i];
97  }
98 
99  *inputState.get() = inputSave;
100 
101  return std::make_pair(directionDeriv, (objectivePlus - objectiveBase) / eps);
102 }
103 
105 inline auto checkGradients(const gretl::State<double>& objectiveState, gretl::State<double, double>& inputState,
106  double& inputDual, double objectiveBase, gretl::DataStore& dataStore, double eps)
107 {
108  double inputSave = inputState.get();
109  dataStore.reset();
110  inputState.set(inputSave + eps);
111  double objectivePlus = objectiveState.get();
112  inputState.set(inputSave);
113  return std::make_pair(inputDual, (objectivePlus - objectiveBase) / eps);
114 }
115 
119 inline double checkGradWrt(const gretl::State<double>& objective, smith::FieldState& input, double eps,
120  size_t num_fd_steps = 4, bool printmore = false)
121 {
122  auto& graph = objective.data_store();
123 
124  // reset each time, just to be sure
125  graph.reset();
126 
127  // re-evaluate the final objective value
128  double objectiveBase = objective.get();
129 
130  // back-propagate to get sensitivity wrt input states
131  gretl::set_as_objective(objective);
132  graph.back_prop();
133 
134  auto dual_vec = *input.get_dual();
135 
136  std::vector<double> grad_errors;
137  auto [grad, grad_fd] = checkGradients(objective, input, dual_vec, objectiveBase, graph, eps);
138  grad_errors.push_back(std::abs(grad - grad_fd));
139 
140  for (size_t step = 1; step < num_fd_steps; ++step) {
141  eps /= 2;
142  std::tie(grad, grad_fd) = checkGradients(objective, input, dual_vec, objectiveBase, graph, eps);
143  if (printmore) std::cout << "grad = " << grad << "\ngrad fd = " << grad_fd << std::endl;
144  grad_errors.push_back(std::abs(grad - grad_fd));
145  }
146 
147  for (size_t step = 0; step < num_fd_steps; ++step) {
148  std::cout << "grad error " << step << " = " << grad_errors[step] << std::endl;
149  }
150 
151  if (num_fd_steps >= 2) {
152  return std::log2(grad_errors[0] / grad_errors[num_fd_steps - 1]) / static_cast<double>(num_fd_steps - 1);
153  }
154 
155  return 0;
156 };
157 
161 inline double checkGradWrt(const gretl::State<double>& objective, gretl::State<double, double>& input, double eps,
162  size_t num_fd_steps = 4, bool printmore = false)
163 {
164  auto& graph = objective.data_store();
165 
166  // reset each time, just to be sure
167  graph.reset();
168 
169  // re-evaluate the final objective value
170  double objectiveBase = objective.get();
171 
172  // back-propagate to get sensitivity wrt input states
173  gretl::set_as_objective(objective);
174  graph.back_prop();
175 
176  auto dual = input.get_dual();
177 
178  std::vector<double> grad_errors;
179  auto [grad, grad_fd] = checkGradients(objective, input, dual, objectiveBase, graph, eps);
180  grad_errors.push_back(std::abs(grad - grad_fd));
181 
182  for (size_t step = 1; step < num_fd_steps; ++step) {
183  eps /= 2;
184  std::tie(grad, grad_fd) = checkGradients(objective, input, dual, objectiveBase, graph, eps);
185  if (printmore) std::cout << "grad = " << grad << "\ngrad fd = " << grad_fd << std::endl;
186  grad_errors.push_back(std::abs(grad - grad_fd));
187  }
188 
189  for (size_t step = 0; step < num_fd_steps; ++step) {
190  std::cout << "grad error " << step << " = " << grad_errors[step] << std::endl;
191  }
192 
193  if (num_fd_steps >= 2) {
194  return std::log2(grad_errors[0] / grad_errors[num_fd_steps - 1]) / static_cast<double>(num_fd_steps - 1);
195  }
196 
197  return 0;
198 };
199 
200 } // namespace smith
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