Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
nonlinear_solve.cpp
6 
7 namespace smith {
8 
10 void applyBoundaryConditions(double time, const smith::BoundaryConditionManager* bc_manager,
11  smith::FEFieldPtr& primal_field, const smith::FEFieldPtr& bc_field_ptr)
12 {
13  if (!bc_manager) {
14  return;
15  }
16  if (bc_field_ptr) {
17  auto constrained_dofs = bc_manager->allEssentialTrueDofs();
18  for (int i = 0; i < constrained_dofs.Size(); i++) {
19  int j = constrained_dofs[i];
20  (*primal_field)[j] = (*bc_field_ptr)(j);
21  }
22  } else {
23  for (auto& bc : bc_manager->essentials()) {
24  bc.setDofs(*primal_field, time);
25  }
26  }
27 }
28 
29 std::vector<FieldState> block_solve(const std::vector<WeakForm*>& residual_evals,
30  const std::vector<std::vector<size_t>> block_indices, const FieldState& shape_disp,
31  const std::vector<std::vector<FieldState>>& states,
32  const std::vector<std::vector<FieldState>>& params, const TimeInfo& time_info,
33  const NonlinearBlockSolverBase* solver,
34  const std::vector<const BoundaryConditionManager*>& bc_managers)
35 {
37  size_t num_rows_ = residual_evals.size();
38 
39  SLIC_ERROR_IF(num_rows_ != block_indices.size(), "Block indices size not consistent with number of residual rows");
40  SLIC_ERROR_IF(num_rows_ != states.size(),
41  "Number of state input vectors not consistent with number of residual rows");
42  SLIC_ERROR_IF(num_rows_ != params.size(),
43  "Number of parameter input vectors not consistent with number of residual rows");
44  SLIC_ERROR_IF(num_rows_ != bc_managers.size(),
45  "Number of boundary condition manager not consistent with number of residual rows");
46 
47  for (size_t r = 0; r < num_rows_; ++r) {
48  SLIC_ERROR_IF(num_rows_ != block_indices[r].size(), "All block index rows must have the same number of columns");
49  }
50 
51  // Validate block_indices bounds against states sizes
52  for (size_t row = 0; row < num_rows_; ++row) {
53  for (size_t col = 0; col < num_rows_; ++col) {
54  size_t idx = block_indices[row][col];
55  if (idx != invalid_block_index) {
56  SLIC_ERROR_IF(idx >= states[row].size(),
57  axom::fmt::format("block_indices[{}][{}] = {} is out of bounds (states[{}].size() = {})", row,
58  col, idx, row, states[row].size()));
59  }
60  }
61  // Validate that diagonal entry is not invalid
62  SLIC_ERROR_IF(
63  block_indices[row][row] == invalid_block_index,
64  axom::fmt::format("block_indices[{}][{}] (diagonal entry) must not be invalid_block_index", row, row));
65  }
66 
67  std::vector<size_t> num_state_inputs;
68  std::vector<gretl::StateBase> allFields;
69  for (auto& ss : states) {
70  num_state_inputs.push_back(ss.size());
71  for (auto& s : ss) {
72  allFields.push_back(s);
73  }
74  }
75  std::vector<size_t> num_param_inputs;
76  for (auto& ps : params) {
77  num_param_inputs.push_back(ps.size());
78  for (auto& p : ps) {
79  allFields.push_back(p);
80  }
81  }
82  allFields.push_back(shape_disp);
83  struct ZeroDualVectors {
84  std::vector<FEDualPtr> operator()(const std::vector<FEFieldPtr>& fs)
85  {
86  std::vector<FEDualPtr> ds(fs.size());
87  for (size_t i = 0; i < fs.size(); ++i) {
88  ds[i] = std::make_shared<FiniteElementDual>(fs[i]->space(), fs[i]->name() + "_dual");
89  }
90  return ds;
91  }
92  };
93 
94  FieldVecState sol =
95  shape_disp.create_state<std::vector<FEFieldPtr>, std::vector<FEDualPtr>>(allFields, ZeroDualVectors());
96  sol.set_eval([=](const gretl::UpstreamStates& upstreams, gretl::DownstreamState& downstream) {
97  SMITH_MARK_BEGIN("solve forward");
98  const size_t num_rows = num_state_inputs.size();
99  std::vector<std::vector<FEFieldPtr>> input_fields(num_rows);
100  SLIC_ERROR_IF(num_rows != num_param_inputs.size(), "row count for params and states are inconsistent");
101 
102  // The order of inputs in upstreams is:
103  // states of residual 0, states of residual 1, ... , params of residual 0, params of residual 1, ...
104  size_t field_count = 0;
105  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
106  for (size_t state_i = 0; state_i < num_state_inputs[row_i]; ++state_i) {
107  input_fields[row_i].push_back(upstreams[field_count++].get<FEFieldPtr>());
108  }
109  }
110  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
111  for (size_t param_i = 0; param_i < num_param_inputs[row_i]; ++param_i) {
112  input_fields[row_i].push_back(upstreams[field_count++].get<FEFieldPtr>());
113  }
114  }
115 
116  std::vector<FEFieldPtr> diagonal_fields(num_rows);
117  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
118  size_t prime_unknown_row_i = block_indices[row_i][row_i];
119  SLIC_ERROR_IF(prime_unknown_row_i == invalid_block_index,
120  "The primary unknown field (field index for block_index[n][n], must not be invalid)");
121  diagonal_fields[row_i] = std::make_shared<FiniteElementState>(*input_fields[row_i][prime_unknown_row_i]);
122  }
123 
124  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
125  FEFieldPtr primal_field_row_i = diagonal_fields[row_i];
126  applyBoundaryConditions(time_info.time(), bc_managers[row_i], primal_field_row_i, nullptr);
127  }
128 
129  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
130  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
131  size_t prime_unknown_ij = block_indices[row_i][col_j];
132  if (prime_unknown_ij != invalid_block_index) {
133  input_fields[row_i][block_indices[row_i][col_j]] = diagonal_fields[col_j];
134  }
135  }
136  }
137 
138  const FEFieldPtr shape_disp_ptr = upstreams[field_count].get<FEFieldPtr>();
139 
140  auto eval_residuals = [=](const std::vector<FEFieldPtr>& unknowns) {
141  SLIC_ERROR_IF(unknowns.size() != num_rows,
142  "block solver unknowns size must match the number or residuals in block_solve");
143  std::vector<mfem::Vector> residuals(num_rows);
144 
145  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
146  FEFieldPtr primal_field_row_i = diagonal_fields[row_i];
147  *primal_field_row_i = *unknowns[row_i];
148  applyBoundaryConditions(time_info.time(), bc_managers[row_i], primal_field_row_i, nullptr);
149  }
150  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
151  residuals[row_i] = residual_evals[row_i]->residual(time_info, shape_disp_ptr.get(),
152  getConstFieldPointers(input_fields[row_i]));
153  if (bc_managers[row_i]) {
154  residuals[row_i].SetSubVector(bc_managers[row_i]->allEssentialTrueDofs(), 0.0);
155  }
156  }
157  return residuals;
158  };
159 
160  auto eval_jacobians = [=](const std::vector<FEFieldPtr>& unknowns) {
161  SLIC_ERROR_IF(unknowns.size() != num_rows,
162  "block solver unknown size must match the number or residuals in block_solve");
163  std::vector<std::vector<std::unique_ptr<mfem::HypreParMatrix>>> jacobians(num_rows);
164 
165  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
166  FEFieldPtr primal_field_row_i = diagonal_fields[row_i];
167  *primal_field_row_i = *unknowns[row_i];
168  applyBoundaryConditions(time_info.time(), bc_managers[row_i], primal_field_row_i, nullptr);
169  }
170 
171  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
172  std::vector<FEFieldPtr> row_field_inputs = input_fields[row_i];
173  std::vector<double> tangent_weights(row_field_inputs.size(), 0.0);
174  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
175  size_t field_index_to_diff = block_indices[row_i][col_j];
176  if (field_index_to_diff != invalid_block_index) {
177  tangent_weights[field_index_to_diff] = 1.0;
178  auto jac_ij = residual_evals[row_i]->jacobian(time_info, shape_disp_ptr.get(),
179  getConstFieldPointers(row_field_inputs), tangent_weights);
180  jacobians[row_i].emplace_back(std::move(jac_ij));
181  tangent_weights[field_index_to_diff] = 0.0;
182  } else {
183  jacobians[row_i].emplace_back(nullptr);
184  }
185  }
186  }
187 
188  // Apply BCs to the block system
189  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
190  if (!bc_managers[row_i]) {
191  continue;
192  }
193  if (jacobians[row_i][row_i]) {
194  jacobians[row_i][row_i]->EliminateBC(bc_managers[row_i]->allEssentialTrueDofs(),
195  mfem::Operator::DiagonalPolicy::DIAG_ONE);
196  }
197  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
198  if (col_j != row_i) {
199  if (jacobians[row_i][col_j]) {
200  jacobians[row_i][col_j]->EliminateRows(bc_managers[row_i]->allEssentialTrueDofs());
201  }
202  if (jacobians[col_j][row_i]) {
203  mfem::HypreParMatrix* Jji =
204  jacobians[col_j][row_i]->EliminateCols(bc_managers[row_i]->allEssentialTrueDofs());
205  delete Jji;
206  }
207  }
208  }
209  }
210  return jacobians;
211  };
212 
213  diagonal_fields = solver->solve(diagonal_fields, eval_residuals, eval_jacobians);
214 
215  downstream.set<std::vector<FEFieldPtr>, std::vector<FEDualPtr>>(diagonal_fields);
216 
217  SMITH_MARK_END("solve forward");
218  });
219 
220  sol.set_vjp([=](gretl::UpstreamStates& upstreams, const gretl::DownstreamState& downstream) {
221  SMITH_MARK_BEGIN("solve reverse");
222  const std::vector<FEFieldPtr> s = downstream.get<std::vector<FEFieldPtr>>(); // get the final solution
223  const std::vector<FEDualPtr> s_dual =
224  downstream.get_dual<std::vector<FEDualPtr>, std::vector<FEFieldPtr>>(); // get the dual load
225 
226  const size_t num_rows = num_state_inputs.size();
227  SLIC_ERROR_IF(s_dual.size() != num_rows,
228  "block solver vjp downstream size must match the number or residuals in block_solve");
229 
230  std::vector<std::vector<FEFieldPtr>> input_fields(num_rows);
231  size_t field_count = 0;
232  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
233  for (size_t state_i = 0; state_i < num_state_inputs[row_i]; ++state_i) {
234  input_fields[row_i].push_back(upstreams[field_count++].get<FEFieldPtr>());
235  }
236  }
237  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
238  for (size_t param_i = 0; param_i < num_param_inputs[row_i]; ++param_i) {
239  input_fields[row_i].push_back(upstreams[field_count++].get<FEFieldPtr>());
240  }
241  }
242 
243  // if the field is a primal variable we solved before,
244  // make a copy so we don't accidentally override the original copy
245  std::vector<FEFieldPtr> diagonal_fields(num_rows);
246  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
247  diagonal_fields[row_i] = std::make_shared<FiniteElementState>(*input_fields[row_i][block_indices[row_i][row_i]]);
248  *diagonal_fields[row_i] = *s[row_i];
249  }
250 
251  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
252  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
253  input_fields[row_i][block_indices[row_i][col_j]] = diagonal_fields[col_j];
254  }
255  }
256 
257  const FEFieldPtr shape_disp_ptr = upstreams[field_count].get<FEFieldPtr>();
258 
259  // I'm not sure this will be the right timestamp to apply boundary condition during backward propagation
260  // Need to double check for time-dependent boundary conditions
261  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
262  FEFieldPtr primal_field_row_i = diagonal_fields[row_i];
263  applyBoundaryConditions(time_info.time(), bc_managers[row_i], primal_field_row_i, nullptr);
264  }
265 
266  solver->clearMemory();
267 
268  std::vector<std::vector<std::unique_ptr<mfem::HypreParMatrix>>> jacobians(num_rows);
269  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
270  std::vector<FEFieldPtr> row_field_inputs = input_fields[row_i];
271  std::vector<double> tangent_weights(row_field_inputs.size(), 0.0);
272  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
273  size_t field_index_to_diff = block_indices[row_i][col_j];
274  if (field_index_to_diff != invalid_block_index) {
275  tangent_weights[field_index_to_diff] = 1.0;
276  auto jac_ij = residual_evals[row_i]->jacobian(time_info, shape_disp_ptr.get(),
277  getConstFieldPointers(row_field_inputs), tangent_weights);
278  jacobians[row_i].emplace_back(std::move(jac_ij));
279  tangent_weights[field_index_to_diff] = 0.0;
280  } else {
281  jacobians[row_i].emplace_back(nullptr);
282  }
283  }
284  }
285 
286  // Apply BCs to the block system
287  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
288  if (!bc_managers[row_i]) {
289  continue;
290  }
291  s_dual[row_i]->SetSubVector(bc_managers[row_i]->allEssentialTrueDofs(), 0.0);
292 
293  mfem::HypreParMatrix* Jii =
294  jacobians[row_i][row_i]->EliminateRowsCols(bc_managers[row_i]->allEssentialTrueDofs());
295  delete Jii;
296  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
297  if (col_j != row_i) {
298  if (jacobians[row_i][col_j]) {
299  jacobians[row_i][col_j]->EliminateRows(bc_managers[row_i]->allEssentialTrueDofs());
300  }
301  if (jacobians[col_j][row_i]) {
302  mfem::HypreParMatrix* Jji =
303  jacobians[col_j][row_i]->EliminateCols(bc_managers[row_i]->allEssentialTrueDofs());
304  delete Jji;
305  }
306  }
307  }
308  }
309 
310  // Take the transpose of the block system
311  std::vector<std::vector<std::unique_ptr<mfem::HypreParMatrix>>> jacobians_T(num_rows);
312  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
313  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
314  if (jacobians[row_i][col_j]) {
315  jacobians_T[col_j].emplace_back(std::unique_ptr<mfem::HypreParMatrix>(jacobians[row_i][col_j]->Transpose()));
316  } else {
317  jacobians_T[col_j].emplace_back(nullptr);
318  }
319  }
320  }
321  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
322  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
323  jacobians[row_i][col_j].reset();
324  }
325  }
326 
327  std::vector<FEFieldPtr> adjoint_fields(num_rows);
328  adjoint_fields = solver->solveAdjoint(s_dual, jacobians_T);
329  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
330  *adjoint_fields[row_i] *= -1.0;
331  }
332 
333  // Update sensitivities
334  std::vector<std::vector<FEDualPtr>> field_sensitivities(num_rows);
335  FEDualPtr shape_disp_sensitivity = upstreams[field_count].get_dual<FEDualPtr, FEFieldPtr>();
336  size_t dual_index = 0;
337  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
338  for (size_t state_i = 0; state_i < num_state_inputs[row_i]; ++state_i) {
339  field_sensitivities[row_i].push_back(upstreams[dual_index++].get_dual<FEDualPtr, FEFieldPtr>());
340  }
341  }
342  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
343  for (size_t param_i = 0; param_i < num_param_inputs[row_i]; ++param_i) {
344  field_sensitivities[row_i].push_back(upstreams[dual_index++].get_dual<FEDualPtr, FEFieldPtr>());
345  }
346  }
347  SLIC_ERROR_IF(field_count != dual_index, "Number of sensitivities must equal to number of upstreams");
348 
349  // No sensitivity needed for primal fields
350  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
351  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
352  field_sensitivities[row_i][block_indices[row_i][col_j]] = nullptr;
353  }
354  }
355 
356  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
357  residual_evals[row_i]->vjp(time_info, shape_disp_ptr.get(), getConstFieldPointers(input_fields[row_i]), {},
358  adjoint_fields[row_i].get(), shape_disp_sensitivity.get(),
359  getFieldPointers(field_sensitivities[row_i]), {});
360  }
361 
362  SMITH_MARK_END("solve reverse");
363  });
364 
365  sol.finalize();
366 
367  std::vector<FieldState> results;
368  for (size_t i = 0; i < num_rows_; ++i) {
369  FieldState s = gretl::create_state<FEFieldPtr, FEDualPtr>(
371  [i](const std::vector<FEFieldPtr>& sols) {
372  auto state_copy = std::make_shared<FiniteElementState>(sols[i]->space(), sols[i]->name());
373  *state_copy = *sols[i];
374  return state_copy;
375  },
376  [i](const std::vector<FEFieldPtr>&, const FEFieldPtr&, std::vector<FEDualPtr>& sols_,
377  const FEDualPtr& output_) { *sols_[i] += *output_; },
378  sol);
379 
380  results.emplace_back(s);
381  }
382 
383  return results;
384 }
385 
386 } // namespace smith
This file contains the declaration of the boundary condition manager class.
A container for the boundary condition information relating to a specific physics module.
const mfem::Array< int > & allEssentialTrueDofs() const
Returns all the true degrees of freedom associated with all the essential BCs.
std::vector< BoundaryCondition > & essentials()
Accessor for the essential BC objects.
Abstract interface for nonlinear block solvers that provide both forward and adjoint solves.
virtual std::vector< FieldPtr > solveAdjoint(const std::vector< DualPtr > &u_bars, std::vector< std::vector< MatrixPtr >> &jacobian_transposed) const =0
Solve the (linear) adjoint set of equations with a vector of FiniteElementState as unknown.
virtual void clearMemory() const
Interface option to clear memory between solves to avoid high-water mark memory usage.
virtual std::vector< FieldPtr > solve(const std::vector< FieldPtr > &u_guesses, std::function< std::vector< mfem::Vector >(const std::vector< FieldPtr > &)> residuals, std::function< std::vector< std::vector< MatrixPtr >>(const std::vector< FieldPtr > &)> jacobians) const =0
Solve a set of equations with a vector of FiniteElementState as unknown.
Defines common types and helper functions for using the residual and scalar_objective classes.
Accelerator functionality.
Definition: smith.cpp:36
std::shared_ptr< FiniteElementState > FEFieldPtr
typedef
Definition: field_state.hpp:20
gretl::State< std::vector< FEFieldPtr >, std::vector< FEDualPtr > > FieldVecState
typedef
Definition: field_state.hpp:24
void applyBoundaryConditions(double time, const smith::BoundaryConditionManager *bc_manager, smith::FEFieldPtr &primal_field, const smith::FEFieldPtr &bc_field_ptr)
apply boundary conditions
std::vector< FiniteElementState * > getFieldPointers(std::vector< FieldState > &states, std::vector< FieldState > params={})
Get a vector of FieldPtr or DualFieldPtr from a vector of FieldState.
gretl::State< FEFieldPtr, FEDualPtr > FieldState
typedef
Definition: field_state.hpp:22
constexpr SMITH_HOST_DEVICE int size(const tensor< T, n... > &)
returns the total number of stored values in a tensor
Definition: tensor.hpp:1939
std::shared_ptr< FiniteElementDual > FEDualPtr
typedef
Definition: field_state.hpp:21
std::vector< const FiniteElementState * > getConstFieldPointers(const std::vector< FieldState > &states, const std::vector< FieldState > &params={})
Get a vector of ConstFieldPtr or ConstDualFieldPtr from a vector of FieldState.
mfem::ParFiniteElementSpace & space(FieldState field)
Get the space from the primal field of a field states.
std::vector< FieldState > block_solve(const std::vector< WeakForm * > &residual_evals, const std::vector< std::vector< size_t >> block_indices, const FieldState &shape_disp, const std::vector< std::vector< FieldState >> &states, const std::vector< std::vector< FieldState >> &params, const TimeInfo &time_info, const NonlinearBlockSolverBase *solver, const std::vector< const BoundaryConditionManager * > &bc_managers)
Solve a block nonlinear system of equations as defined by the vector of weak form.
This file contains nonlinear block solver interfaces and helpers.
Methods for solving systems of equations as given by WeakForms. Tracks these operations on the gretl ...
#define SMITH_MARK_FUNCTION
Definition: profiling.hpp:90
#define SMITH_MARK_BEGIN(name)
Definition: profiling.hpp:94
#define SMITH_MARK_END(name)
Definition: profiling.hpp:95
struct storing time and timestep information
Definition: common.hpp:18
double time() const
accessor for the current time
Definition: common.hpp:26
functor which takes a std::shared_ptr<FiniteElementState>, and returns a zero-valued std::shared_ptr<...
Definition: field_state.hpp:29
Specifies interface for evaluating weak form residuals and their gradients.