7 #include "smith/differentiable_numerics/coupled_system_solver.hpp"
16 #include <axom/slic.hpp>
17 #include <axom/fmt.hpp>
22 : max_staggered_iterations_(1), exact_staggered_steps_(false)
28 : max_staggered_iterations_(max_staggered_iterations), exact_staggered_steps_(exact_staggered_steps)
30 SLIC_ERROR_IF(max_staggered_iterations <= 0, "max_staggered_iterations must be > 0
");
33 void CoupledSystemSolver::addSubsystemSolver(const std::vector<size_t>& block_indices,
34 std::shared_ptr<NonlinearBlockSolverBase> solver, double relaxation_factor)
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));
40 stages_.push_back(Stage{block_indices, std::move(solver), relaxation_factor});
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
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);
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);
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();
72 std::vector<NonlinearConvergenceContext> stage_convergence_contexts(active_stages.size());
74 // Working copy of states, updated in-place as stages solve
75 std::vector<std::vector<FieldState>> current_states = states;
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());
83 for (const auto& param_state : params[global_row]) {
84 input_ptrs.push_back(param_state.get().get());
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);
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]));
101 stage.solver->primeConvergenceContext(stage_init_residuals, stage_convergence_contexts[stage_idx]);
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();
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;
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]);
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];
128 stage_block_indices.push_back(row_indices);
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);
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];
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);
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;
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]));
166 auto stage_status = stage.solver->convergenceStatus(1.0, stage_residuals, stage_convergence_contexts[s]);
168 if (!stage_status.converged) {
169 all_converged = false;
174 SLIC_INFO_ROOT(axom::fmt::format("Staggered iteration converged after {} iteration(s)
", iter + 1));
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]);
188 return final_solutions;
191 std::shared_ptr<CoupledSystemSolver> CoupledSystemSolver::singleBlockSolver(size_t block_index) const
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;
203 Stage single_stage{{0}, stage_solver, stage.relaxation_factor};
204 result->addSubsystemSolver(single_stage.block_indices, single_stage.solver, single_stage.relaxation_factor);
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;
217 Stage single_stage{{0}, stage_solver, stage.relaxation_factor};
218 result->addSubsystemSolver(single_stage.block_indices, single_stage.solver, single_stage.relaxation_factor);
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.
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.