LCOV - code coverage report
Current view: top level - src/executioners - LinearAssemblySegregatedSolve.C (source / functions) Hit Total Coverage
Test: idaholab/moose navier_stokes: #32971 (54bef8) with base c6cf66 Lines: 388 426 91.1 %
Date: 2026-05-29 20:37:52 Functions: 11 11 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "LinearAssemblySegregatedSolve.h"
      11             : #include "FEProblem.h"
      12             : #include "SegregatedSolverUtils.h"
      13             : #include "LinearSystem.h"
      14             : #include "Executioner.h"
      15             : 
      16             : using namespace libMesh;
      17             : 
      18             : InputParameters
      19        1344 : LinearAssemblySegregatedSolve::validParams()
      20             : {
      21        1344 :   InputParameters params = SIMPLESolveBase::validParams();
      22             : 
      23        2688 :   params.addParam<std::vector<SolverSystemName>>(
      24             :       "active_scalar_systems", {}, "The solver system for each active scalar advection equation.");
      25             : 
      26             :   /*
      27             :    * Parameters to control the solution of each scalar advection system
      28             :    */
      29        1344 :   params.addParam<std::vector<Real>>("active_scalar_equation_relaxation",
      30        1344 :                                      std::vector<Real>(),
      31             :                                      "The relaxation which should be used for the active scalar "
      32             :                                      "equations. (=1 for no relaxation, "
      33             :                                      "diagonal dominance will still be enforced)");
      34             : 
      35        2688 :   params.addParam<MultiMooseEnum>("active_scalar_petsc_options",
      36        2688 :                                   Moose::PetscSupport::getCommonPetscFlags(),
      37             :                                   "Singleton PETSc options for the active scalar equation(s)");
      38        2688 :   params.addParam<MultiMooseEnum>(
      39             :       "active_scalar_petsc_options_iname",
      40        2688 :       Moose::PetscSupport::getCommonPetscKeys(),
      41             :       "Names of PETSc name/value pairs for the active scalar equation(s)");
      42        2688 :   params.addParam<std::vector<std::string>>(
      43             :       "active_scalar_petsc_options_value",
      44             :       "Values of PETSc name/value pairs (must correspond with \"petsc_options_iname\" for the "
      45             :       "active scalar equation(s)");
      46        1344 :   params.addParam<std::vector<Real>>(
      47             :       "active_scalar_absolute_tolerance",
      48        1344 :       std::vector<Real>(),
      49             :       "The absolute tolerance(s) on the normalized residual(s) of the active scalar equation(s).");
      50        4032 :   params.addRangeCheckedParam<Real>("active_scalar_l_tol",
      51        2688 :                                     1e-5,
      52             :                                     "0.0<=active_scalar_l_tol & active_scalar_l_tol<1.0",
      53             :                                     "The relative tolerance on the normalized residual in the "
      54             :                                     "linear solver of the active scalar equation(s).");
      55        4032 :   params.addRangeCheckedParam<Real>("active_scalar_l_abs_tol",
      56        2688 :                                     1e-10,
      57             :                                     "0.0<active_scalar_l_abs_tol",
      58             :                                     "The absolute tolerance on the normalized residual in the "
      59             :                                     "linear solver of the active scalar equation(s).");
      60        2688 :   params.addParam<unsigned int>(
      61             :       "active_scalar_l_max_its",
      62        2688 :       10000,
      63             :       "The maximum allowed iterations in the linear solver of the turbulence equation.");
      64             : 
      65        2688 :   params.addParamNamesToGroup(
      66             :       "active_scalar_systems active_scalar_equation_relaxation active_scalar_petsc_options "
      67             :       "active_scalar_petsc_options_iname "
      68             :       "active_scalar_petsc_options_value active_scalar_petsc_options_value "
      69             :       "active_scalar_absolute_tolerance "
      70             :       "active_scalar_l_tol active_scalar_l_abs_tol active_scalar_l_max_its",
      71             :       "Active Scalars Equations");
      72             : 
      73             :   /*
      74             :    * Flags to optionally skip solving subsets of the thermal-hydraulics system (useful when
      75             :    * recovering a converged solution and only advancing scalar transport for example).
      76             :    */
      77        2688 :   params.addParam<bool>(
      78        2688 :       "should_solve_momentum", true, "Whether we should solve the momentum predictor/corrector.");
      79        2688 :   params.addParam<bool>(
      80        2688 :       "should_solve_pressure", true, "Whether we should solve the pressure corrector.");
      81        2688 :   params.addParam<bool>(
      82        2688 :       "should_solve_energy", true, "Whether we should solve the fluid energy equation.");
      83        2688 :   params.addParam<bool>(
      84        2688 :       "should_solve_solid_energy", true, "Whether we should solve the solid energy equation.");
      85        2688 :   params.addParam<bool>("should_solve_turbulence",
      86        2688 :                         true,
      87             :                         "Whether we should solve the turbulence surrogate equations.");
      88        2688 :   params.addParam<bool>(
      89        2688 :       "should_solve_passive_scalars", true, "Whether we should solve passive scalar equations.");
      90        2688 :   params.addParam<bool>(
      91        2688 :       "should_solve_active_scalars", true, "Whether we should solve active scalar equations.");
      92        2688 :   params.addParam<bool>("should_solve_pm_radiation",
      93        2688 :                         true,
      94             :                         "Whether we should solve participating media radiation equations.");
      95        2688 :   params.addParamNamesToGroup("should_solve_momentum should_solve_pressure should_solve_energy "
      96             :                               "should_solve_solid_energy should_solve_turbulence "
      97             :                               "should_solve_passive_scalars should_solve_active_scalars",
      98             :                               "Solve control");
      99             : 
     100             :   /*
     101             :    * Parameters to control the conjugate heat transfer
     102             :    */
     103        1344 :   params += NS::FV::CHTHandler::validParams();
     104             : 
     105        1344 :   return params;
     106           0 : }
     107             : 
     108         672 : LinearAssemblySegregatedSolve::LinearAssemblySegregatedSolve(Executioner & ex)
     109             :   : SIMPLESolveBase(ex),
     110        1344 :     _pressure_sys_number(_problem.linearSysNum(getParam<SolverSystemName>("pressure_system"))),
     111         672 :     _pressure_system(_problem.getLinearSystem(_pressure_sys_number)),
     112        1344 :     _energy_sys_number(_has_energy_system
     113        1116 :                            ? _problem.linearSysNum(getParam<SolverSystemName>("energy_system"))
     114             :                            : libMesh::invalid_uint),
     115         672 :     _energy_system(_has_energy_system ? &_problem.getLinearSystem(_energy_sys_number) : nullptr),
     116         672 :     _solid_energy_sys_number(
     117         672 :         _has_solid_energy_system
     118         776 :             ? _problem.linearSysNum(getParam<SolverSystemName>("solid_energy_system"))
     119             :             : libMesh::invalid_uint),
     120         672 :     _solid_energy_system(
     121         672 :         _has_solid_energy_system ? &_problem.getLinearSystem(_solid_energy_sys_number) : nullptr),
     122        1344 :     _should_solve_momentum(getParam<bool>("should_solve_momentum")),
     123        1344 :     _should_solve_pressure(getParam<bool>("should_solve_pressure")),
     124        1344 :     _should_solve_energy(getParam<bool>("should_solve_energy")),
     125        1344 :     _should_solve_solid_energy(getParam<bool>("should_solve_solid_energy")),
     126        1344 :     _should_solve_turbulence(getParam<bool>("should_solve_turbulence")),
     127        1344 :     _should_solve_passive_scalars(getParam<bool>("should_solve_passive_scalars")),
     128        1344 :     _should_solve_active_scalars(getParam<bool>("should_solve_active_scalars")),
     129        1344 :     _should_solve_pm_radiation(getParam<bool>("should_solve_pm_radiation")),
     130        1344 :     _active_scalar_system_names(getParam<std::vector<SolverSystemName>>("active_scalar_systems")),
     131         672 :     _has_active_scalar_systems(!_active_scalar_system_names.empty()),
     132        1344 :     _active_scalar_equation_relaxation(
     133             :         getParam<std::vector<Real>>("active_scalar_equation_relaxation")),
     134        1344 :     _active_scalar_l_abs_tol(getParam<Real>("active_scalar_l_abs_tol")),
     135        1344 :     _active_scalar_absolute_tolerance(
     136             :         getParam<std::vector<Real>>("active_scalar_absolute_tolerance")),
     137        2016 :     _cht(ex.parameters())
     138             : {
     139         672 :   if (!_should_solve_momentum && _should_solve_pressure)
     140           0 :     paramError("should_solve_momentum",
     141             :                "Pressure correction requires solving the momentum equations.");
     142         672 :   if (_should_solve_momentum && !_should_solve_pressure)
     143           0 :     paramError("should_solve_pressure",
     144             :                "Solving momentum without a pressure corrector is not supported.");
     145         672 :   if (_has_solid_energy_system && !_should_solve_energy && _should_solve_solid_energy)
     146           0 :     paramError("should_solve_solid_energy",
     147             :                "Solid energy solve cannot be enabled when the fluid energy solve is disabled.");
     148             : 
     149             :   // We fetch the systems and their numbers for the momentum equations only if we solve them
     150         672 :   if (_should_solve_momentum)
     151        1970 :     for (auto system_i : index_range(_momentum_system_names))
     152             :     {
     153        1298 :       _momentum_system_numbers.push_back(_problem.linearSysNum(_momentum_system_names[system_i]));
     154        1298 :       _momentum_systems.push_back(&_problem.getLinearSystem(_momentum_system_numbers[system_i]));
     155        1298 :       _systems_to_solve.push_back(_momentum_systems.back());
     156             :     }
     157             : 
     158         672 :   if (_should_solve_pressure)
     159         672 :     _systems_to_solve.push_back(&_pressure_system);
     160             : 
     161         672 :   if (_has_energy_system && _should_solve_energy)
     162         222 :     _systems_to_solve.push_back(_energy_system);
     163             : 
     164         672 :   if (_has_solid_energy_system && _should_solve_solid_energy)
     165          52 :     _systems_to_solve.push_back(_solid_energy_system);
     166             :   // and for the turbulence surrogate equations
     167         672 :   if (_has_turbulence_systems && _should_solve_turbulence)
     168         213 :     for (auto system_i : index_range(_turbulence_system_names))
     169             :     {
     170         142 :       _turbulence_system_numbers.push_back(
     171         142 :           _problem.linearSysNum(_turbulence_system_names[system_i]));
     172         142 :       _turbulence_systems.push_back(
     173         142 :           &_problem.getLinearSystem(_turbulence_system_numbers[system_i]));
     174             :     }
     175             : 
     176             :   // and for the passive scalar equations
     177         672 :   if (_has_passive_scalar_systems && _should_solve_passive_scalars)
     178         114 :     for (auto system_i : index_range(_passive_scalar_system_names))
     179             :     {
     180          70 :       _passive_scalar_system_numbers.push_back(
     181          70 :           _problem.linearSysNum(_passive_scalar_system_names[system_i]));
     182          70 :       _passive_scalar_systems.push_back(
     183          70 :           &_problem.getLinearSystem(_passive_scalar_system_numbers[system_i]));
     184          70 :       if (_should_solve_passive_scalars)
     185          70 :         _systems_to_solve.push_back(_passive_scalar_systems.back());
     186             :     }
     187             : 
     188             :   // and for the participating media radiation equations
     189         672 :   if (_has_pm_radiation_systems && _should_solve_pm_radiation)
     190          32 :     for (auto system_i : index_range(_pm_radiation_system_names))
     191             :     {
     192          16 :       _pm_radiation_system_numbers.push_back(
     193          16 :           _problem.linearSysNum(_pm_radiation_system_names[system_i]));
     194          16 :       _pm_radiation_systems.push_back(
     195          16 :           &_problem.getLinearSystem(_pm_radiation_system_numbers[system_i]));
     196          16 :       _systems_to_solve.push_back(_pm_radiation_systems.back());
     197             :     }
     198             : 
     199             :   // and for the active scalar equations
     200         672 :   if (_has_active_scalar_systems && _should_solve_active_scalars)
     201          50 :     for (auto system_i : index_range(_active_scalar_system_names))
     202             :     {
     203          25 :       _active_scalar_system_numbers.push_back(
     204          25 :           _problem.linearSysNum(_active_scalar_system_names[system_i]));
     205          25 :       _active_scalar_systems.push_back(
     206          25 :           &_problem.getLinearSystem(_active_scalar_system_numbers[system_i]));
     207          25 :       _systems_to_solve.push_back(_active_scalar_systems.back());
     208             : 
     209             :       const auto & active_scalar_petsc_options =
     210          25 :           getParam<MultiMooseEnum>("active_scalar_petsc_options");
     211             :       const auto & active_scalar_petsc_pair_options = getParam<MooseEnumItem, std::string>(
     212          50 :           "active_scalar_petsc_options_iname", "active_scalar_petsc_options_value");
     213          25 :       Moose::PetscSupport::addPetscFlagsToPetscOptions(
     214             :           active_scalar_petsc_options, "", *this, _active_scalar_petsc_options);
     215          50 :       Moose::PetscSupport::addPetscPairsToPetscOptions(active_scalar_petsc_pair_options,
     216          25 :                                                        _problem.mesh().dimension(),
     217             :                                                        "",
     218             :                                                        *this,
     219             :                                                        _active_scalar_petsc_options);
     220             : 
     221          25 :       _active_scalar_linear_control.real_valued_data["rel_tol"] =
     222          50 :           getParam<Real>("active_scalar_l_tol");
     223          25 :       _active_scalar_linear_control.real_valued_data["abs_tol"] =
     224          50 :           getParam<Real>("active_scalar_l_abs_tol");
     225          25 :       _active_scalar_linear_control.int_valued_data["max_its"] =
     226          50 :           getParam<unsigned int>("active_scalar_l_max_its");
     227          25 :     }
     228             : 
     229         672 :   if (_active_scalar_equation_relaxation.size() != _active_scalar_system_names.size())
     230           0 :     paramError("active_scalar_equation_relaxation",
     231             :                "Should be the same size as the number of systems");
     232             : 
     233             :   // We disable the prefix here for the time being, the segregated solvers use a different approach
     234             :   // for setting the petsc parameters
     235        3027 :   for (auto & system : _systems_to_solve)
     236        2355 :     system->system().prefix_with_name(false);
     237             : 
     238             :   // Link CHT objects, this will also do some error checking
     239             :   // Make a copy for compatibility. These could change in the future
     240             :   // Convert _pm_radiation_systems to std::vector<SystemBase *>
     241         672 :   if (_cht.enabled())
     242             :   {
     243          40 :     if (!_should_solve_energy)
     244           0 :       paramError("should_solve_energy",
     245             :                  "Conjugate heat transfer requires solving the fluid energy equation.");
     246          40 :     if (_has_solid_energy_system && !_should_solve_solid_energy)
     247           0 :       paramError("should_solve_solid_energy",
     248             :                  "Conjugate heat transfer requires solving the solid energy equation.");
     249             : 
     250             :     std::vector<SystemBase *> pm_radiation_systems_base(_pm_radiation_systems.begin(),
     251          40 :                                                         _pm_radiation_systems.end());
     252             : 
     253          40 :     _cht.linkEnergySystems(_solid_energy_system, _energy_system, pm_radiation_systems_base);
     254          40 :   }
     255         672 : }
     256             : 
     257             : void
     258         671 : LinearAssemblySegregatedSolve::linkRhieChowUserObject()
     259             : {
     260         671 :   if (!_should_solve_momentum)
     261             :     return;
     262             : 
     263         671 :   _rc_uo =
     264         671 :       const_cast<RhieChowMassFlux *>(&getUserObject<RhieChowMassFlux>("rhie_chow_user_object"));
     265         671 :   _rc_uo->linkMomentumPressureSystems(
     266         671 :       _momentum_systems, _pressure_system, _momentum_system_numbers);
     267             : 
     268             :   // Initialize the face velocities in the RC object
     269         671 :   if (!_app.isRecovering())
     270         652 :     _rc_uo->initFaceMassFlux();
     271         671 :   _rc_uo->initCouplingField();
     272             : }
     273             : 
     274             : std::vector<std::pair<unsigned int, Real>>
     275      119163 : LinearAssemblySegregatedSolve::solveMomentumPredictor()
     276             : {
     277             :   // Temporary storage for the (flux-normalized) residuals from
     278             :   // different momentum components
     279             :   std::vector<std::pair<unsigned int, Real>> its_normalized_residuals;
     280             : 
     281             :   LinearImplicitSystem & momentum_system_0 =
     282      119163 :       libMesh::cast_ref<LinearImplicitSystem &>(_momentum_systems[0]->system());
     283             : 
     284             :   PetscLinearSolver<Real> & momentum_solver =
     285      119163 :       libMesh::cast_ref<PetscLinearSolver<Real> &>(*momentum_system_0.get_linear_solver());
     286             : 
     287             :   // Solve the momentum equations.
     288             :   // TO DO: These equations are VERY similar. If we can store the differences (things coming from
     289             :   // BCs for example) separately, it is enough to construct one matrix.
     290      321006 :   for (const auto system_i : index_range(_momentum_systems))
     291             :   {
     292      201843 :     _problem.setCurrentLinearSystem(_momentum_system_numbers[system_i]);
     293             : 
     294             :     // We will need the right hand side and the solution of the next component
     295             :     LinearImplicitSystem & momentum_system =
     296      201843 :         libMesh::cast_ref<LinearImplicitSystem &>(_momentum_systems[system_i]->system());
     297             : 
     298             :     NumericVector<Number> & solution = *(momentum_system.solution);
     299      201843 :     NumericVector<Number> & rhs = *(momentum_system.rhs);
     300      201843 :     SparseMatrix<Number> & mmat = *(momentum_system.matrix);
     301             : 
     302      201843 :     auto diff_diagonal = solution.zero_clone();
     303             : 
     304             :     // We assemble the matrix and the right hand side
     305      201843 :     _problem.computeLinearSystemSys(momentum_system, mmat, rhs, /*compute_grads*/ true);
     306             : 
     307             :     // Still need to relax the right hand side with the same vector
     308      201843 :     NS::FV::relaxMatrix(mmat, _momentum_equation_relaxation, *diff_diagonal);
     309      201843 :     NS::FV::relaxRightHandSide(rhs, solution, *diff_diagonal);
     310             : 
     311             :     // The normalization factor depends on the right hand side so we need to recompute it for this
     312             :     // component
     313      201843 :     Real norm_factor = NS::FV::computeNormalizationFactor(solution, mmat, rhs);
     314             : 
     315             :     // Very important, for deciding the convergence, we need the unpreconditioned
     316             :     // norms in the linear solve
     317      201843 :     LibmeshPetscCall(KSPSetNormType(momentum_solver.ksp(), KSP_NORM_UNPRECONDITIONED));
     318             :     // Solve this component. We don't update the ghosted solution yet, that will come at the end
     319             :     // of the corrector step. Also setting the linear tolerances and maximum iteration counts.
     320      201843 :     _momentum_linear_control.real_valued_data["abs_tol"] = _momentum_l_abs_tol * norm_factor;
     321      201843 :     momentum_solver.set_solver_configuration(_momentum_linear_control);
     322             : 
     323             :     // We solve the equation
     324      201843 :     auto its_resid_pair = momentum_solver.solve(mmat, mmat, solution, rhs);
     325      201843 :     momentum_system.update();
     326             : 
     327             :     // We will reuse the preconditioner for every momentum system
     328      201843 :     if (system_i == 0)
     329      119163 :       momentum_solver.reuse_preconditioner(true);
     330             : 
     331             :     // Save the normalized residual
     332             :     its_normalized_residuals.push_back(
     333      201843 :         std::make_pair(its_resid_pair.first, momentum_solver.get_initial_residual() / norm_factor));
     334             : 
     335      201843 :     if (_print_fields)
     336             :     {
     337           0 :       _console << " matrix when we solve " << std::endl;
     338           0 :       mmat.print();
     339           0 :       _console << " rhs when we solve " << std::endl;
     340           0 :       rhs.print();
     341           0 :       _console << " velocity solution component " << system_i << std::endl;
     342           0 :       solution.print();
     343           0 :       _console << "Norm factor " << norm_factor << std::endl;
     344           0 :       _console << Moose::stringify(momentum_solver.get_initial_residual()) << std::endl;
     345             :     }
     346             : 
     347             :     // Printing residuals
     348      201843 :     _console << " Momentum equation:"
     349             :              << (_momentum_systems.size() > 1
     350      897516 :                      ? std::string(" Component ") + std::to_string(system_i + 1) + std::string(" ")
     351      201843 :                      : std::string(" "))
     352      201843 :              << COLOR_GREEN << its_normalized_residuals[system_i].second << COLOR_DEFAULT
     353      201843 :              << " Linear its: " << its_normalized_residuals[system_i].first << std::endl;
     354      201843 :   }
     355             : 
     356      321006 :   for (const auto system_i : index_range(_momentum_systems))
     357             :   {
     358             :     LinearImplicitSystem & momentum_system =
     359      201843 :         libMesh::cast_ref<LinearImplicitSystem &>(_momentum_systems[system_i]->system());
     360      201843 :     _momentum_systems[system_i]->setSolution(*(momentum_system.current_local_solution));
     361      201843 :     _momentum_systems[system_i]->copyPreviousNonlinearSolutions();
     362             :   }
     363             : 
     364             :   // We reset this to ensure the preconditioner is recomputed new time we go to the momentum
     365             :   // predictor
     366      119163 :   momentum_solver.reuse_preconditioner(false);
     367             : 
     368      119163 :   return its_normalized_residuals;
     369           0 : }
     370             : 
     371             : void
     372         671 : LinearAssemblySegregatedSolve::initialSetup()
     373             : {
     374         671 :   if (_cht.enabled())
     375             :   {
     376          40 :     _cht.deduceCHTBoundaryCoupling();
     377          40 :     _cht.setupConjugateHeatTransferContainers();
     378             :   }
     379         671 : }
     380             : 
     381             : std::pair<unsigned int, Real>
     382      132163 : LinearAssemblySegregatedSolve::solvePressureCorrector()
     383             : {
     384      132163 :   _problem.setCurrentLinearSystem(_pressure_sys_number);
     385             : 
     386             :   // We will need some members from the linear system
     387             :   LinearImplicitSystem & pressure_system =
     388      132163 :       libMesh::cast_ref<LinearImplicitSystem &>(_pressure_system.system());
     389             : 
     390             :   // We will need the solution, the right hand side and the matrix
     391             :   NumericVector<Number> & current_local_solution = *(pressure_system.current_local_solution);
     392             :   NumericVector<Number> & solution = *(pressure_system.solution);
     393      132163 :   SparseMatrix<Number> & mmat = *(pressure_system.matrix);
     394      132163 :   NumericVector<Number> & rhs = *(pressure_system.rhs);
     395             : 
     396             :   // Fetch the linear solver from the system
     397             :   PetscLinearSolver<Real> & pressure_solver =
     398      132163 :       libMesh::cast_ref<PetscLinearSolver<Real> &>(*pressure_system.get_linear_solver());
     399             : 
     400      132163 :   _problem.computeLinearSystemSys(pressure_system, mmat, rhs, false);
     401             : 
     402      132163 :   if (_print_fields)
     403             :   {
     404           0 :     _console << "Pressure matrix" << std::endl;
     405           0 :     mmat.print();
     406             :   }
     407             : 
     408             :   // We compute the normalization factors based on the fluxes
     409      132163 :   Real norm_factor = NS::FV::computeNormalizationFactor(solution, mmat, rhs);
     410             : 
     411             :   // We need the non-preconditioned norm to be consistent with the norm factor
     412      132163 :   LibmeshPetscCall(KSPSetNormType(pressure_solver.ksp(), KSP_NORM_UNPRECONDITIONED));
     413             : 
     414             :   // Setting the linear tolerances and maximum iteration counts
     415      132163 :   _pressure_linear_control.real_valued_data["abs_tol"] = _pressure_l_abs_tol * norm_factor;
     416      132163 :   pressure_solver.set_solver_configuration(_pressure_linear_control);
     417             : 
     418      132163 :   if (_pin_pressure)
     419       27901 :     NS::FV::constrainSystem(mmat, rhs, _pressure_pin_value, _pressure_pin_dof);
     420      132163 :   pressure_system.update();
     421             : 
     422      132163 :   auto its_res_pair = pressure_solver.solve(mmat, mmat, solution, rhs);
     423      132163 :   pressure_system.update();
     424             : 
     425      132163 :   if (_print_fields)
     426             :   {
     427           0 :     _console << " rhs when we solve pressure " << std::endl;
     428           0 :     rhs.print();
     429           0 :     _console << " Pressure " << std::endl;
     430           0 :     solution.print();
     431           0 :     _console << "Norm factor " << norm_factor << std::endl;
     432             :   }
     433             : 
     434      132163 :   _pressure_system.setSolution(current_local_solution);
     435             : 
     436             :   const auto residuals =
     437      132163 :       std::make_pair(its_res_pair.first, pressure_solver.get_initial_residual() / norm_factor);
     438             : 
     439      132163 :   _console << " Pressure equation: " << COLOR_GREEN << residuals.second << COLOR_DEFAULT
     440      132163 :            << " Linear its: " << residuals.first << std::endl;
     441             : 
     442      132163 :   return residuals;
     443             : }
     444             : 
     445             : std::pair<unsigned int, Real>
     446       10642 : LinearAssemblySegregatedSolve::solveSolidEnergy()
     447             : {
     448       10642 :   _problem.setCurrentLinearSystem(_solid_energy_sys_number);
     449             : 
     450             :   // We will need some members from the linear system
     451             :   LinearImplicitSystem & system =
     452       10642 :       libMesh::cast_ref<LinearImplicitSystem &>(_solid_energy_system->system());
     453             : 
     454             :   // We will need the solution, the right hand side and the matrix
     455             :   NumericVector<Number> & current_local_solution = *(system.current_local_solution);
     456             :   NumericVector<Number> & solution = *(system.solution);
     457       10642 :   SparseMatrix<Number> & mmat = *(system.matrix);
     458       10642 :   NumericVector<Number> & rhs = *(system.rhs);
     459             : 
     460             :   // Fetch the linear solver from the system
     461             :   PetscLinearSolver<Real> & solver =
     462       10642 :       libMesh::cast_ref<PetscLinearSolver<Real> &>(*system.get_linear_solver());
     463             : 
     464       10642 :   _problem.computeLinearSystemSys(system, mmat, rhs, false);
     465             : 
     466       10642 :   if (_print_fields)
     467             :   {
     468           0 :     _console << "Solid energy matrix" << std::endl;
     469           0 :     mmat.print();
     470             :   }
     471             : 
     472             :   // We compute the normalization factors based on the fluxes
     473       10642 :   Real norm_factor = NS::FV::computeNormalizationFactor(solution, mmat, rhs);
     474             : 
     475             :   // We need the non-preconditioned norm to be consistent with the norm factor
     476       10642 :   LibmeshPetscCall(KSPSetNormType(solver.ksp(), KSP_NORM_UNPRECONDITIONED));
     477             : 
     478             :   // Setting the linear tolerances and maximum iteration counts
     479       10642 :   _solid_energy_linear_control.real_valued_data["abs_tol"] = _solid_energy_l_abs_tol * norm_factor;
     480       10642 :   solver.set_solver_configuration(_solid_energy_linear_control);
     481             : 
     482       10642 :   auto its_res_pair = solver.solve(mmat, mmat, solution, rhs);
     483       10642 :   system.update();
     484             : 
     485       10642 :   if (_print_fields)
     486             :   {
     487           0 :     _console << " rhs when we solve solid energy " << std::endl;
     488           0 :     rhs.print();
     489           0 :     _console << " Solid energy " << std::endl;
     490           0 :     solution.print();
     491           0 :     _console << "Norm factor " << norm_factor << std::endl;
     492             :   }
     493             : 
     494       10642 :   _solid_energy_system->setSolution(current_local_solution);
     495             : 
     496             :   const auto residuals =
     497       10642 :       std::make_pair(its_res_pair.first, solver.get_initial_residual() / norm_factor);
     498             : 
     499       10642 :   _console << " Solid energy equation: " << COLOR_GREEN << residuals.second << COLOR_DEFAULT
     500       10642 :            << " Linear its: " << residuals.first << std::endl;
     501             : 
     502       10642 :   return residuals;
     503             : }
     504             : 
     505             : std::pair<unsigned int, Real>
     506      132163 : LinearAssemblySegregatedSolve::correctVelocity(const bool subtract_updated_pressure,
     507             :                                                const bool recompute_face_mass_flux,
     508             :                                                const SolverParams & solver_params)
     509             : {
     510             :   // Compute the coupling fields between the momentum and pressure equations.
     511             :   // The first argument makes sure the pressure gradient is staged at the first
     512             :   // iteration
     513      132163 :   _rc_uo->computeHbyA(subtract_updated_pressure, _print_fields);
     514             : 
     515             :   // We set the preconditioner/controllable parameters for the pressure equations through
     516             :   // petsc options. Linear tolerances will be overridden within the solver.
     517      132163 :   Moose::PetscSupport::petscSetOptions(_pressure_petsc_options, solver_params);
     518             : 
     519             :   // Solve the pressure corrector
     520      132163 :   const auto residuals = solvePressureCorrector();
     521             : 
     522             :   // Compute the face velocity which is used in the advection terms. In certain
     523             :   // segregated solver algorithms (like PISO) this is only done on the last iteration.
     524      132163 :   if (recompute_face_mass_flux)
     525      119163 :     _rc_uo->computeFaceMassFlux();
     526             : 
     527      132163 :   auto & pressure_current_solution = *(_pressure_system.system().current_local_solution.get());
     528      132163 :   auto & pressure_old_solution = *(_pressure_system.solutionPreviousNewton());
     529             : 
     530             :   // Relax the pressure update for the next momentum predictor
     531      132163 :   NS::FV::relaxSolutionUpdate(
     532      132163 :       pressure_current_solution, pressure_old_solution, _pressure_variable_relaxation);
     533             : 
     534             :   // Overwrite old solution
     535      132163 :   pressure_old_solution = pressure_current_solution;
     536      132163 :   _pressure_system.setSolution(pressure_current_solution);
     537             : 
     538             :   // We recompute the updated pressure gradient
     539      132163 :   _pressure_system.computeGradients();
     540             : 
     541             :   // Reconstruct the cell velocity as well to accelerate convergence
     542      132163 :   _rc_uo->computeCellVelocity();
     543             : 
     544      132163 :   return residuals;
     545             : }
     546             : 
     547             : std::pair<unsigned int, Real>
     548      107388 : LinearAssemblySegregatedSolve::solveAdvectedSystem(const unsigned int system_num,
     549             :                                                    LinearSystem & system,
     550             :                                                    const Real relaxation_factor,
     551             :                                                    SolverConfiguration & solver_config,
     552             :                                                    const Real absolute_tol,
     553             :                                                    const Real field_relaxation,
     554             :                                                    const Real min_value_limiter)
     555             : {
     556      107388 :   _problem.setCurrentLinearSystem(system_num);
     557             : 
     558             :   // We will need some members from the implicit linear system
     559      107388 :   LinearImplicitSystem & li_system = libMesh::cast_ref<LinearImplicitSystem &>(system.system());
     560             : 
     561             :   // We will need the solution, the right hand side and the matrix
     562             :   NumericVector<Number> & current_local_solution = *(li_system.current_local_solution);
     563             :   NumericVector<Number> & solution = *(li_system.solution);
     564      107388 :   SparseMatrix<Number> & mmat = *(li_system.matrix);
     565      107388 :   NumericVector<Number> & rhs = *(li_system.rhs);
     566             : 
     567             :   // We need a vector that stores the (diagonal_relaxed-original_diagonal) vector
     568      107388 :   auto diff_diagonal = solution.zero_clone();
     569             : 
     570             :   // Fetch the linear solver from the system
     571             :   PetscLinearSolver<Real> & linear_solver =
     572      107388 :       libMesh::cast_ref<PetscLinearSolver<Real> &>(*li_system.get_linear_solver());
     573             : 
     574      107388 :   _problem.computeLinearSystemSys(li_system, mmat, rhs, true);
     575             : 
     576             :   // Go and relax the system matrix and the right hand side
     577      107388 :   NS::FV::relaxMatrix(mmat, relaxation_factor, *diff_diagonal);
     578      107388 :   NS::FV::relaxRightHandSide(rhs, solution, *diff_diagonal);
     579             : 
     580      107388 :   if (_print_fields)
     581             :   {
     582           0 :     _console << system.name() << " system matrix" << std::endl;
     583           0 :     mmat.print();
     584             :   }
     585             : 
     586             :   // We compute the normalization factors based on the fluxes
     587      107388 :   Real norm_factor = NS::FV::computeNormalizationFactor(solution, mmat, rhs);
     588             : 
     589             :   // We need the non-preconditioned norm to be consistent with the norm factor
     590      107388 :   LibmeshPetscCall(KSPSetNormType(linear_solver.ksp(), KSP_NORM_UNPRECONDITIONED));
     591             : 
     592             :   // Setting the linear tolerances and maximum iteration counts
     593      107388 :   solver_config.real_valued_data["abs_tol"] = absolute_tol * norm_factor;
     594      107388 :   linear_solver.set_solver_configuration(solver_config);
     595             : 
     596             :   // Solve the system and update current local solution
     597      107388 :   auto its_res_pair = linear_solver.solve(mmat, mmat, solution, rhs);
     598      107388 :   li_system.update();
     599             : 
     600      107388 :   if (_print_fields)
     601             :   {
     602           0 :     _console << " rhs when we solve " << system.name() << std::endl;
     603           0 :     rhs.print();
     604           0 :     _console << system.name() << " solution " << std::endl;
     605           0 :     solution.print();
     606           0 :     _console << " Norm factor " << norm_factor << std::endl;
     607             :   }
     608             : 
     609             :   // Limiting scalar solution
     610      107388 :   if (min_value_limiter != std::numeric_limits<Real>::min())
     611       52996 :     NS::FV::limitSolutionUpdate(current_local_solution, min_value_limiter);
     612             : 
     613             :   // Relax the field update for the next momentum predictor
     614      107388 :   if (field_relaxation != 1.0)
     615             :   {
     616       35652 :     auto & old_local_solution = *(system.solutionPreviousNewton());
     617       35652 :     NS::FV::relaxSolutionUpdate(current_local_solution, old_local_solution, field_relaxation);
     618             : 
     619             :     // Update old solution, only needed if relaxing the field
     620       35652 :     old_local_solution = current_local_solution;
     621             :   }
     622             : 
     623      107388 :   system.setSolution(current_local_solution);
     624             : 
     625             :   const auto residuals =
     626      107388 :       std::make_pair(its_res_pair.first, linear_solver.get_initial_residual() / norm_factor);
     627             : 
     628      107388 :   _console << " Advected system: " << system.name() << " " << COLOR_GREEN << residuals.second
     629      107388 :            << COLOR_DEFAULT << " Linear its: " << residuals.first << std::endl;
     630             : 
     631      107388 :   return residuals;
     632      107388 : }
     633             : 
     634             : bool
     635        1183 : LinearAssemblySegregatedSolve::solve()
     636             : {
     637             :   // Do not solve if problem is set not to
     638        1183 :   if (!_problem.shouldSolve())
     639             :     return true;
     640             : 
     641             :   // Dummy solver parameter file which is needed for switching petsc options
     642        1183 :   SolverParams solver_params;
     643        1183 :   solver_params._type = Moose::SolveType::ST_LINEAR;
     644        1183 :   solver_params._line_search = Moose::LineSearchType::LS_NONE;
     645             : 
     646             :   // Initialize the SIMPLE iteration counter
     647        1183 :   unsigned int simple_iteration_counter = 0;
     648             : 
     649             :   // We set up the residual storage and the corresponding tolerances.
     650        1183 :   ResidualStorage residual_storage = setupResidualStorage();
     651             :   auto & ns_residuals = residual_storage.ns_residuals;
     652             :   auto & ns_abs_tols = residual_storage.ns_abs_tols;
     653             :   const auto & momentum_indices = residual_storage.momentum_indices;
     654        1183 :   const auto pressure_index = residual_storage.pressure_index;
     655        1183 :   const auto energy_index = residual_storage.energy_index;
     656        1183 :   const auto solid_energy_index = residual_storage.solid_energy_index;
     657             :   const auto & active_scalar_indices = residual_storage.active_scalar_indices;
     658             :   const auto & turbulence_indices = residual_storage.turbulence_indices;
     659             :   const auto & pm_radiation_indices = residual_storage.pm_radiation_indices;
     660             : 
     661        1183 :   bool converged = residual_storage.converged;
     662             : 
     663             :   // Loop until converged or hit the maximum allowed iteration number
     664        1183 :   if (_cht.enabled() && _should_solve_energy)
     665          38 :     _cht.initializeCHTCouplingFields();
     666             : 
     667      120346 :   while (simple_iteration_counter < _num_iterations && !converged)
     668             :   {
     669      119163 :     simple_iteration_counter++;
     670             : 
     671             :     // We set the preconditioner/controllable parameters through petsc options. Linear
     672             :     // tolerances will be overridden within the solver. In case of a segregated momentum
     673             :     // solver, we assume that every velocity component uses the same preconditioner
     674      119163 :     if (_should_solve_momentum)
     675      119163 :       Moose::PetscSupport::petscSetOptions(_momentum_petsc_options, solver_params);
     676             : 
     677             :     // Initialize pressure gradients, after this we just reuse the last ones from each
     678             :     // iteration
     679      119163 :     if (_should_solve_pressure && simple_iteration_counter == 1)
     680        1183 :       _pressure_system.computeGradients();
     681             : 
     682      119163 :     _console << "Iteration " << simple_iteration_counter << " Initial residual norms:" << std::endl;
     683             : 
     684             :     // Solve the momentum predictor step
     685      119163 :     if (_should_solve_momentum)
     686             :     {
     687      119163 :       auto momentum_residual = solveMomentumPredictor();
     688      321006 :       for (const auto system_i : index_range(momentum_residual))
     689      201843 :         ns_residuals[momentum_indices[system_i]] = momentum_residual[system_i];
     690      119163 :     }
     691             : 
     692             :     // Now we correct the velocity, this function depends on the method, it differs for
     693             :     // SIMPLE/PIMPLE, this returns the pressure errors
     694      119163 :     if (_should_solve_pressure)
     695      119163 :       ns_residuals[pressure_index] = correctVelocity(true, true, solver_params);
     696             : 
     697             :     // If we have an energy equation, solve it here.We assume the material properties in the
     698             :     // Navier-Stokes equations depend on temperature, therefore we can not solve for temperature
     699             :     // outside of the velocity-pressure loop
     700      119163 :     if (_has_energy_system && _should_solve_energy)
     701             :     {
     702             :       // If there is no CHT specified this will just do go once through this block
     703             :       _cht.resetCHTConvergence();
     704       56908 :       while (!_cht.converged())
     705             :       {
     706       29806 :         if (_cht.enabled())
     707        7276 :           _cht.updateCHTBoundaryCouplingFields(NS::CHTSide::FLUID);
     708             : 
     709             :         // We set the preconditioner/controllable parameters through petsc options. Linear
     710             :         // tolerances will be overridden within the solver.
     711       29806 :         Moose::PetscSupport::petscSetOptions(_energy_petsc_options, solver_params);
     712       59612 :         ns_residuals[energy_index] = solveAdvectedSystem(_energy_sys_number,
     713       29806 :                                                          *_energy_system,
     714       29806 :                                                          _energy_equation_relaxation,
     715             :                                                          _energy_linear_control,
     716       29806 :                                                          _energy_l_abs_tol);
     717             : 
     718       29806 :         if (_has_pm_radiation_systems && _should_solve_pm_radiation)
     719             :         {
     720             :           // We set the preconditioner/controllable parameters through petsc options. Linear
     721             :           // tolerances will be overridden within the solver.
     722        2212 :           Moose::PetscSupport::petscSetOptions(_pm_radiation_petsc_options, solver_params);
     723        4424 :           for (const auto i : index_range(_pm_radiation_system_names))
     724             :           {
     725        2212 :             ns_residuals[pm_radiation_indices[i]] =
     726        2212 :                 solveAdvectedSystem(_pm_radiation_system_numbers[i],
     727             :                                     *_pm_radiation_systems[i],
     728             :                                     _pm_radiation_equation_relaxation[i],
     729             :                                     _pm_radiation_linear_control,
     730        2212 :                                     _pm_radiation_l_abs_tol);
     731             :           }
     732             :         }
     733             : 
     734       29806 :         if (_has_solid_energy_system && _should_solve_solid_energy)
     735             :         {
     736             :           // For now we only update gradients if cht is needed, might change in the future
     737       10642 :           if (_cht.enabled())
     738             :           {
     739        7276 :             _energy_system->computeGradients();
     740        7276 :             _cht.updateCHTBoundaryCouplingFields(NS::CHTSide::SOLID);
     741             :           }
     742             : 
     743             :           // We set the preconditioner/controllable parameters through petsc options. Linear
     744             :           // tolerances will be overridden within the solver.
     745       10642 :           Moose::PetscSupport::petscSetOptions(_solid_energy_petsc_options, solver_params);
     746       10642 :           ns_residuals[solid_energy_index] = solveSolidEnergy();
     747             : 
     748             :           // For now we only update gradients if cht is needed, might change in the future
     749       10642 :           if (_cht.enabled())
     750        7276 :             _solid_energy_system->computeGradients();
     751             :         }
     752             : 
     753       29806 :         if (_cht.enabled())
     754             :         {
     755        7276 :           _cht.sumIntegratedFluxes();
     756        7276 :           _cht.printIntegratedFluxes();
     757             :         }
     758             : 
     759             :         _cht.incrementCHTIterators();
     760             :       }
     761       27102 :       if (_cht.enabled())
     762        4572 :         _cht.resetIntegratedFluxes();
     763             :     }
     764             : 
     765             :     // If we have active scalar equations, solve them here in case they depend on temperature
     766             :     // or they affect the fluid properties such that they must be solved concurrently with
     767             :     // pressure and velocity
     768      119163 :     if (_has_active_scalar_systems && _should_solve_active_scalars)
     769             :     {
     770       12088 :       _problem.execute(EXEC_NONLINEAR);
     771             : 
     772             :       // We set the preconditioner/controllable parameters through petsc options. Linear
     773             :       // tolerances will be overridden within the solver.
     774       12088 :       Moose::PetscSupport::petscSetOptions(_active_scalar_petsc_options, solver_params);
     775       24176 :       for (const auto i : index_range(_active_scalar_system_names))
     776       12088 :         ns_residuals[active_scalar_indices[i]] =
     777       12088 :             solveAdvectedSystem(_active_scalar_system_numbers[i],
     778             :                                 *_active_scalar_systems[i],
     779             :                                 _active_scalar_equation_relaxation[i],
     780             :                                 _active_scalar_linear_control,
     781       12088 :                                 _active_scalar_l_abs_tol);
     782             :     }
     783             : 
     784             :     // If we have turbulence equations, solve them here.
     785             :     // The turbulent viscosity depends on the value of the turbulence surrogate variables
     786      119163 :     if (_has_turbulence_systems && _should_solve_turbulence)
     787             :     {
     788             :       // We set the preconditioner/controllable parameters through petsc options. Linear
     789             :       // tolerances will be overridden within the solver.
     790       26498 :       Moose::PetscSupport::petscSetOptions(_turbulence_petsc_options, solver_params);
     791       79494 :       for (const auto i : index_range(_turbulence_system_names))
     792             :       {
     793       52996 :         ns_residuals[turbulence_indices[i]] =
     794       52996 :             solveAdvectedSystem(_turbulence_system_numbers[i],
     795             :                                 *_turbulence_systems[i],
     796             :                                 _turbulence_equation_relaxation[i],
     797             :                                 _turbulence_linear_control,
     798       52996 :                                 _turbulence_l_abs_tol,
     799             :                                 _turbulence_field_relaxation[i],
     800             :                                 _turbulence_field_min_limit[i]);
     801             :       }
     802             :     }
     803             : 
     804      119163 :     _problem.execute(EXEC_NONLINEAR);
     805             : 
     806      119163 :     converged = NS::FV::converged(ns_residuals, ns_abs_tols);
     807             :   }
     808             : 
     809             :   // If we have passive scalar equations, solve them here. We assume the material properties in
     810             :   // the Navier-Stokes equations do not depend on passive scalars, as they are passive, therefore
     811             :   // we solve outside of the velocity-pressure loop
     812        1183 :   if (_has_passive_scalar_systems && _should_solve_passive_scalars &&
     813          47 :       (converged || _continue_on_max_its))
     814             :   {
     815             :     // The reason why we need more than one iteration is due to the matrix relaxation
     816             :     // which can be used to stabilize the equations
     817             :     bool passive_scalar_converged = false;
     818          72 :     unsigned int ps_iteration_counter = 0;
     819             : 
     820          72 :     _console << "Passive scalar iteration " << ps_iteration_counter
     821          72 :              << " Initial residual norms:" << std::endl;
     822             : 
     823        5299 :     while (ps_iteration_counter < _num_iterations && !passive_scalar_converged)
     824             :     {
     825        5227 :       ps_iteration_counter++;
     826             :       std::vector<std::pair<unsigned int, Real>> scalar_residuals(
     827        5227 :           _passive_scalar_system_names.size(), std::make_pair(0, 1.0));
     828             :       std::vector<Real> scalar_abs_tols;
     829       15513 :       for (const auto scalar_tol : _passive_scalar_absolute_tolerance)
     830       10286 :         scalar_abs_tols.push_back(scalar_tol);
     831             : 
     832             :       // We set the preconditioner/controllable parameters through petsc options. Linear
     833             :       // tolerances will be overridden within the solver.
     834        5227 :       Moose::PetscSupport::petscSetOptions(_passive_scalar_petsc_options, solver_params);
     835       15513 :       for (const auto i : index_range(_passive_scalar_system_names))
     836       10286 :         scalar_residuals[i] = solveAdvectedSystem(_passive_scalar_system_numbers[i],
     837             :                                                   *_passive_scalar_systems[i],
     838             :                                                   _passive_scalar_equation_relaxation[i],
     839             :                                                   _passive_scalar_linear_control,
     840       10286 :                                                   _passive_scalar_l_abs_tol);
     841             : 
     842        5227 :       passive_scalar_converged = NS::FV::converged(scalar_residuals, scalar_abs_tols);
     843        5227 :     }
     844             : 
     845             :     // Both flow and scalars must converge
     846          72 :     converged = passive_scalar_converged && converged;
     847             :   }
     848             : 
     849        1183 :   converged = _continue_on_max_its ? true : converged;
     850             : 
     851             :   return converged;
     852        1183 : }
     853             : 
     854             : LinearAssemblySegregatedSolve::ResidualStorage
     855        1183 : LinearAssemblySegregatedSolve::setupResidualStorage() const
     856             : {
     857        1183 :   ResidualStorage storage;
     858             : 
     859             :   // Residual store: position in this vector defines the ordering used by NS::FV::converged()
     860             :   // Each entry holds (linear its, normalized residual) for one system
     861        1183 :   if (_should_solve_momentum)
     862        3479 :     for ([[maybe_unused]] const auto system_i : index_range(_momentum_systems))
     863             :     {
     864        2296 :       storage.momentum_indices.push_back(storage.ns_residuals.size());
     865        2296 :       storage.ns_residuals.push_back(std::make_pair(0, 1.0));
     866        2296 :       storage.ns_abs_tols.push_back(_momentum_absolute_tolerance);
     867             :     }
     868             : 
     869        1183 :   if (_should_solve_pressure)
     870             :   {
     871        1183 :     storage.pressure_index = storage.ns_residuals.size();
     872        1183 :     storage.ns_residuals.push_back(std::make_pair(0, 1.0));
     873        1183 :     storage.ns_abs_tols.push_back(_pressure_absolute_tolerance);
     874             :   }
     875             : 
     876        1183 :   if (_has_energy_system && _should_solve_energy)
     877             :   {
     878         590 :     storage.energy_index = storage.ns_residuals.size();
     879         590 :     storage.ns_residuals.push_back(std::make_pair(0, 1.0));
     880         590 :     storage.ns_abs_tols.push_back(_energy_absolute_tolerance);
     881             :   }
     882             : 
     883        1183 :   if (_has_solid_energy_system && _should_solve_solid_energy)
     884             :   {
     885          50 :     storage.solid_energy_index = storage.ns_residuals.size();
     886          50 :     storage.ns_residuals.push_back(std::make_pair(0, 1.0));
     887          50 :     storage.ns_abs_tols.push_back(_solid_energy_absolute_tolerance);
     888             :   }
     889             : 
     890        1183 :   if (_has_active_scalar_systems && _should_solve_active_scalars)
     891         170 :     for (const auto i : index_range(_active_scalar_system_names))
     892             :     {
     893          85 :       storage.active_scalar_indices.push_back(storage.ns_residuals.size());
     894          85 :       storage.ns_residuals.push_back(std::make_pair(0, 1.0));
     895          85 :       storage.ns_abs_tols.push_back(_active_scalar_absolute_tolerance[i]);
     896             :     }
     897             : 
     898        1183 :   if (_has_turbulence_systems && _should_solve_turbulence)
     899         369 :     for (const auto i : index_range(_turbulence_system_names))
     900             :     {
     901         246 :       storage.turbulence_indices.push_back(storage.ns_residuals.size());
     902         246 :       storage.ns_residuals.push_back(std::make_pair(0, 1.0));
     903         246 :       storage.ns_abs_tols.push_back(_turbulence_absolute_tolerance[i]);
     904             :     }
     905             : 
     906        1183 :   if (_has_pm_radiation_systems && _should_solve_pm_radiation)
     907          28 :     for (const auto i : index_range(_pm_radiation_system_names))
     908             :     {
     909          14 :       storage.pm_radiation_indices.push_back(storage.ns_residuals.size());
     910          14 :       storage.ns_residuals.push_back(std::make_pair(0, 1.0));
     911          14 :       storage.ns_abs_tols.push_back(_pm_radiation_absolute_tolerance[i]);
     912             :     }
     913             : 
     914        1183 :   storage.converged = storage.ns_residuals.empty();
     915        1183 :   return storage;
     916           0 : }

Generated by: LCOV version 1.14