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  if (block_indices[row_i][col_j] != invalid_block_index) {
254  input_fields[row_i][block_indices[row_i][col_j]] = diagonal_fields[col_j];
255  }
256  }
257  }
258 
259  const FEFieldPtr shape_disp_ptr = upstreams[field_count].get<FEFieldPtr>();
260 
261  // I'm not sure this will be the right timestamp to apply boundary condition during backward propagation
262  // Need to double check for time-dependent boundary conditions
263  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
264  FEFieldPtr primal_field_row_i = diagonal_fields[row_i];
265  applyBoundaryConditions(time_info.time(), bc_managers[row_i], primal_field_row_i, nullptr);
266  }
267 
268  solver->clearMemory();
269 
270  std::vector<std::vector<std::unique_ptr<mfem::HypreParMatrix>>> jacobians(num_rows);
271  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
272  std::vector<FEFieldPtr> row_field_inputs = input_fields[row_i];
273  std::vector<double> tangent_weights(row_field_inputs.size(), 0.0);
274  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
275  size_t field_index_to_diff = block_indices[row_i][col_j];
276  if (field_index_to_diff != invalid_block_index) {
277  tangent_weights[field_index_to_diff] = 1.0;
278  auto jac_ij = residual_evals[row_i]->jacobian(time_info, shape_disp_ptr.get(),
279  getConstFieldPointers(row_field_inputs), tangent_weights);
280  jacobians[row_i].emplace_back(std::move(jac_ij));
281  tangent_weights[field_index_to_diff] = 0.0;
282  } else {
283  jacobians[row_i].emplace_back(nullptr);
284  }
285  }
286  }
287 
288  // Apply BCs to the block system
289  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
290  if (!bc_managers[row_i]) {
291  continue;
292  }
293  s_dual[row_i]->SetSubVector(bc_managers[row_i]->allEssentialTrueDofs(), 0.0);
294 
295  mfem::HypreParMatrix* Jii =
296  jacobians[row_i][row_i]->EliminateRowsCols(bc_managers[row_i]->allEssentialTrueDofs());
297  delete Jii;
298  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
299  if (col_j != row_i) {
300  if (jacobians[row_i][col_j]) {
301  jacobians[row_i][col_j]->EliminateRows(bc_managers[row_i]->allEssentialTrueDofs());
302  }
303  if (jacobians[col_j][row_i]) {
304  mfem::HypreParMatrix* Jji =
305  jacobians[col_j][row_i]->EliminateCols(bc_managers[row_i]->allEssentialTrueDofs());
306  delete Jji;
307  }
308  }
309  }
310  }
311 
312  // Take the transpose of the block system
313  std::vector<std::vector<std::unique_ptr<mfem::HypreParMatrix>>> jacobians_T(num_rows);
314  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
315  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
316  if (jacobians[row_i][col_j]) {
317  jacobians_T[col_j].emplace_back(std::unique_ptr<mfem::HypreParMatrix>(jacobians[row_i][col_j]->Transpose()));
318  } else {
319  jacobians_T[col_j].emplace_back(nullptr);
320  }
321  }
322  }
323  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
324  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
325  jacobians[row_i][col_j].reset();
326  }
327  }
328 
329  std::vector<FEFieldPtr> adjoint_fields(num_rows);
330  adjoint_fields = solver->solveAdjoint(s_dual, jacobians_T);
331  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
332  *adjoint_fields[row_i] *= -1.0;
333  }
334 
335  // Update sensitivities
336  std::vector<std::vector<FEDualPtr>> field_sensitivities(num_rows);
337  FEDualPtr shape_disp_sensitivity = upstreams[field_count].get_dual<FEDualPtr, FEFieldPtr>();
338  size_t dual_index = 0;
339  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
340  for (size_t state_i = 0; state_i < num_state_inputs[row_i]; ++state_i) {
341  field_sensitivities[row_i].push_back(upstreams[dual_index++].get_dual<FEDualPtr, FEFieldPtr>());
342  }
343  }
344  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
345  for (size_t param_i = 0; param_i < num_param_inputs[row_i]; ++param_i) {
346  field_sensitivities[row_i].push_back(upstreams[dual_index++].get_dual<FEDualPtr, FEFieldPtr>());
347  }
348  }
349  SLIC_ERROR_IF(field_count != dual_index, "Number of sensitivities must equal to number of upstreams");
350 
351  // No sensitivity needed for primal fields
352  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
353  for (size_t col_j = 0; col_j < num_rows; ++col_j) {
354  if (block_indices[row_i][col_j] != invalid_block_index) {
355  field_sensitivities[row_i][block_indices[row_i][col_j]] = nullptr;
356  }
357  }
358  }
359 
360  for (size_t row_i = 0; row_i < num_rows; ++row_i) {
361  residual_evals[row_i]->vjp(time_info, shape_disp_ptr.get(), getConstFieldPointers(input_fields[row_i]), {},
362  adjoint_fields[row_i].get(), shape_disp_sensitivity.get(),
363  getFieldPointers(field_sensitivities[row_i]), {});
364  }
365 
366  SMITH_MARK_END("solve reverse");
367  });
368 
369  sol.finalize();
370 
371  std::vector<FieldState> results;
372  for (size_t i = 0; i < num_rows_; ++i) {
373  FieldState s = gretl::create_state<FEFieldPtr, FEDualPtr>(
375  [i](const std::vector<FEFieldPtr>& sols) {
376  auto state_copy = std::make_shared<FiniteElementState>(sols[i]->space(), sols[i]->name());
377  *state_copy = *sols[i];
378  return state_copy;
379  },
380  [i](const std::vector<FEFieldPtr>&, const FEFieldPtr&, std::vector<FEDualPtr>& sols_,
381  const FEDualPtr& output_) { *sols_[i] += *output_; },
382  sol);
383 
384  results.emplace_back(s);
385  }
386 
387  return results;
388 }
389 
390 } // 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:33
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.