Smith  0.1
Smith is an implicit thermal structural mechanics simulation code.
coupled_system_solver.cpp
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 
7 #include "smith/differentiable_numerics/coupled_system_solver.hpp"
12 #include "mfem.hpp"
13 
14 #include <algorithm>
15 #include <numeric>
16 #include <axom/slic.hpp>
17 #include <axom/fmt.hpp>
18 
19 namespace smith {
20 
21 CoupledSystemSolver::CoupledSystemSolver(std::shared_ptr<NonlinearBlockSolverBase> single_solver)
22  : max_staggered_iterations_(1), exact_staggered_steps_(false)
23 {
24  addSubsystemSolver({}, std::move(single_solver));
25 }
26 
27 CoupledSystemSolver::CoupledSystemSolver(int max_staggered_iterations, bool exact_staggered_steps)
28  : max_staggered_iterations_(max_staggered_iterations), exact_staggered_steps_(exact_staggered_steps)
29 {
30  SLIC_ERROR_IF(max_staggered_iterations <= 0, "max_staggered_iterations must be > 0");
31 }
32 
33 void CoupledSystemSolver::addSubsystemSolver(const std::vector<size_t>& block_indices,
34  std::shared_ptr<NonlinearBlockSolverBase> solver, double relaxation_factor)
35 {
36  SLIC_ERROR_IF(!solver, "CoupledSystemSolver stage solver must be non-null");
37  SLIC_ERROR_IF(relaxation_factor <= 0.0 || relaxation_factor > 1.0,
38  axom::fmt::format("Stage relaxation_factor {} must be in (0, 1]", relaxation_factor));
39 
40  stages_.push_back(Stage{block_indices, std::move(solver), relaxation_factor});
41 }
42 
43 std::vector<FieldState> CoupledSystemSolver::solve(
44  const std::vector<WeakForm*>& residual_evals, const std::vector<std::vector<size_t>>& block_indices,
45  const FieldState& shape_disp, const std::vector<std::vector<FieldState>>& states,
46  const std::vector<std::vector<FieldState>>& params, const TimeInfo& time_info,
47  const std::vector<const BoundaryConditionManager*>& bc_managers) const
48 {
49  SLIC_ERROR_IF(stages_.empty(), "CoupledSystemSolver has no stages defined.");
50 
51  size_t num_residuals = residual_evals.size();
52  std::vector<Stage> active_stages = stages_;
53  for (auto& stage : active_stages) {
54  if (stage.block_indices.empty()) {
55  stage.block_indices.resize(num_residuals);
56  std::iota(stage.block_indices.begin(), stage.block_indices.end(), 0);
57  }
58  }
59  // Set the inner tolerance factor based on the number of stages. For single-stage
60  // solves, we don't want to reduce the tolerances as that's pointless and
61  // unintuitive. For multi-stage solves, we want a tighter inner solve to
62  // ensure outer staggered convergence.
63  const double inner_tol_factor = (active_stages.size() == 1) ? 1.0 : 0.6;
64  for (auto& stage : active_stages) {
65  stage.solver->setInnerToleranceMultiplier(inner_tol_factor);
66  }
67 
68  // Reset each stage solver's convergence tracking (e.g. initial residual norm for rel-tol)
69  for (const auto& stage : active_stages) {
70  stage.solver->resetConvergenceState();
71  }
72  std::vector<NonlinearConvergenceContext> stage_convergence_contexts(active_stages.size());
73 
74  // Working copy of states, updated in-place as stages solve
75  std::vector<std::vector<FieldState>> current_states = states;
76 
77  // Helper lambda to assemble input pointers, evaluate residual, and zero essential BCs
78  auto eval_residual_and_zero_bcs = [&](size_t global_row) {
79  std::vector<const FiniteElementState*> input_ptrs;
80  for (const auto& field_state : current_states[global_row]) {
81  input_ptrs.push_back(field_state.get().get());
82  }
83  for (const auto& param_state : params[global_row]) {
84  input_ptrs.push_back(param_state.get().get());
85  }
86  mfem::Vector res = residual_evals[global_row]->residual(time_info, shape_disp.get().get(), input_ptrs);
87  if (bc_managers[global_row]) {
88  res.SetSubVector(bc_managers[global_row]->allEssentialTrueDofs(), 0.0);
89  }
90  return res;
91  };
92 
93  // Evaluate and register true initial residuals before block sweeps mutate the state.
94  for (size_t stage_idx = 0; stage_idx < active_stages.size(); ++stage_idx) {
95  const auto& stage = active_stages[stage_idx];
96  size_t num_stage_blocks = stage.block_indices.size();
97  std::vector<mfem::Vector> stage_init_residuals;
98  for (size_t i = 0; i < num_stage_blocks; ++i) {
99  stage_init_residuals.push_back(eval_residual_and_zero_bcs(stage.block_indices[i]));
100  }
101  stage.solver->primeConvergenceContext(stage_init_residuals, stage_convergence_contexts[stage_idx]);
102  }
103 
104  for (int iter = 0; iter < max_staggered_iterations_; ++iter) {
105  // --- Run each stage ---
106  for (size_t stage_idx = 0; stage_idx < active_stages.size(); ++stage_idx) {
107  const auto& stage = active_stages[stage_idx];
108  size_t num_stage_blocks = stage.block_indices.size();
109 
110  std::vector<WeakForm*> stage_residuals;
111  std::vector<std::vector<size_t>> stage_block_indices;
112  std::vector<std::vector<FieldState>> stage_states;
113  std::vector<std::vector<FieldState>> stage_params;
114  std::vector<const BoundaryConditionManager*> stage_bc_managers;
115 
116  for (size_t i = 0; i < num_stage_blocks; ++i) {
117  size_t global_row = stage.block_indices[i];
118  stage_residuals.push_back(residual_evals[global_row]);
119  stage_bc_managers.push_back(bc_managers[global_row]);
120  stage_states.push_back(current_states[global_row]);
121  stage_params.push_back(params[global_row]);
122 
123  std::vector<size_t> row_indices(num_stage_blocks, invalid_block_index);
124  for (size_t col_idx = 0; col_idx < num_stage_blocks; ++col_idx) {
125  size_t global_col = stage.block_indices[col_idx];
126  row_indices[col_idx] = block_indices[global_row][global_col];
127  }
128  stage_block_indices.push_back(row_indices);
129  }
130 
131  std::vector<FieldState> stage_solutions =
132  block_solve(stage_residuals, stage_block_indices, shape_disp, stage_states, stage_params, time_info,
133  stage.solver.get(), stage_bc_managers);
134 
135  // Propagate updated fields to all residuals that reference them.
136  // Apply relaxation: x_new = omega * x_solved + (1 - omega) * x_k.
137  for (size_t i = 0; i < num_stage_blocks; ++i) {
138  size_t global_col = stage.block_indices[i];
139  FieldState new_state = stage_solutions[i];
140 
141  if (stage.relaxation_factor != 1.0) {
142  FieldState old_state = current_states[global_col][block_indices[global_col][global_col]];
143  new_state = weighted_average(new_state, old_state, stage.relaxation_factor);
144  }
145 
146  for (size_t r = 0; r < num_residuals; ++r) {
147  size_t c = block_indices[r][global_col];
148  if (c != invalid_block_index) {
149  current_states[r][c] = new_state;
150  }
151  }
152  }
153  }
154 
155  // --- Convergence check (skipped in exact-steps mode, single-iteration mode,
156  // or on the last iteration where a break has no effect) ---
157  if (!exact_staggered_steps_ && max_staggered_iterations_ > 1 && iter < max_staggered_iterations_ - 1) {
158  bool all_converged = true;
159  for (size_t s = 0; s < active_stages.size(); ++s) {
160  const auto& stage = active_stages[s];
161  size_t num_stage_blocks = stage.block_indices.size();
162  std::vector<mfem::Vector> stage_residuals;
163  for (size_t i = 0; i < num_stage_blocks; ++i) {
164  stage_residuals.push_back(eval_residual_and_zero_bcs(stage.block_indices[i]));
165  }
166  auto stage_status = stage.solver->convergenceStatus(1.0, stage_residuals, stage_convergence_contexts[s]);
167 
168  if (!stage_status.converged) {
169  all_converged = false;
170  break;
171  }
172  }
173  if (all_converged) {
174  SLIC_INFO_ROOT(axom::fmt::format("Staggered iteration converged after {} iteration(s)", iter + 1));
175  break;
176  }
177  }
178  }
179 
180  // Return the diagonal (unknown) states as the final solution
181  std::vector<FieldState> final_solutions;
182  final_solutions.reserve(num_residuals);
183  for (size_t r = 0; r < num_residuals; ++r) {
184  size_t s_idx = block_indices[r][r];
185  final_solutions.push_back(current_states[r][s_idx]);
186  }
187 
188  return final_solutions;
189 }
190 
191 std::shared_ptr<CoupledSystemSolver> CoupledSystemSolver::singleBlockSolver(size_t block_index) const
192 {
193  constexpr bool exact_staggered_steps = true;
194  for (const auto& stage : stages_) {
195  if (stage.block_indices.empty()) {
196  auto result = std::make_shared<CoupledSystemSolver>(1, exact_staggered_steps);
197  std::shared_ptr<NonlinearBlockSolverBase> stage_solver = stage.solver;
198  if (const auto* equation_solver = dynamic_cast<const NonlinearBlockSolver*>(stage.solver.get())) {
199  if (auto cloned_solver = equation_solver->cloneFresh()) {
200  stage_solver = cloned_solver;
201  }
202  }
203  Stage single_stage{{0}, stage_solver, stage.relaxation_factor};
204  result->addSubsystemSolver(single_stage.block_indices, single_stage.solver, single_stage.relaxation_factor);
205  return result;
206  }
207 
208  auto found = std::find(stage.block_indices.begin(), stage.block_indices.end(), block_index);
209  if (found != stage.block_indices.end()) {
210  auto result = std::make_shared<CoupledSystemSolver>(1, exact_staggered_steps);
211  std::shared_ptr<NonlinearBlockSolverBase> stage_solver = stage.solver;
212  if (const auto* equation_solver = dynamic_cast<const NonlinearBlockSolver*>(stage.solver.get())) {
213  if (auto cloned_solver = equation_solver->cloneFresh()) {
214  stage_solver = cloned_solver;
215  }
216  }
217  Stage single_stage{{0}, stage_solver, stage.relaxation_factor};
218  result->addSubsystemSolver(single_stage.block_indices, single_stage.solver, single_stage.relaxation_factor);
219  return result;
220  }
221  }
222 
223  return nullptr;
224 }
225 
226 } // namespace smith
This file contains the declaration of the boundary condition manager class.
Orchestrates staggered solution for multiphysics systems.
CoupledSystemSolver(std::shared_ptr< NonlinearBlockSolverBase > single_solver)
Construct a monolithic CoupledSystemSolver from a single block solver.
void addSubsystemSolver(const std::vector< size_t > &block_indices, std::shared_ptr< NonlinearBlockSolverBase > solver, double relaxation_factor=1.0)
Convenience method to add a solver stage.
Accelerator functionality.
Definition: smith.cpp:36
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 ...
Represents a single stage in a staggered iteration.
Specifies interface for evaluating weak form residuals and their gradients.