LCOV - code coverage report
Current view: top level - src/problems - SubChannel1PhaseProblem.C (source / functions) Hit Total Coverage
Test: idaholab/moose subchannel: #32971 (54bef8) with base c6cf66 Lines: 1448 1545 93.7 %
Date: 2026-05-29 20:40:47 Functions: 40 41 97.6 %
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 "SubChannel1PhaseProblem.h"
      11             : #include "SystemBase.h"
      12             : #include "libmesh/petsc_vector.h"
      13             : #include "libmesh/dense_matrix.h"
      14             : #include "libmesh/dense_vector.h"
      15             : #include <iostream>
      16             : #include <cmath>
      17             : #include "AuxiliarySystem.h"
      18             : #include "SCM.h"
      19             : #include "SinglePhaseFluidProperties.h"
      20             : #include "SCMFrictionClosureBase.h"
      21             : #include "SCMHTCClosureBase.h"
      22             : #include "SCMMixingClosureBase.h"
      23             : 
      24             : struct Ctx
      25             : {
      26             :   int iblock;
      27             :   SubChannel1PhaseProblem * schp;
      28             : };
      29             : 
      30             : PetscErrorCode
      31       63443 : formFunction(SNES, Vec x, Vec f, void * ctx)
      32             : {
      33             :   const PetscScalar * xx;
      34             :   PetscScalar * ff;
      35             :   PetscInt size;
      36             : 
      37             :   PetscFunctionBegin;
      38             :   Ctx * cc = static_cast<Ctx *>(ctx);
      39       63443 :   LibmeshPetscCallQ(VecGetSize(x, &size));
      40             : 
      41       63443 :   libMesh::DenseVector<Real> solution_seed(size, 0.0);
      42       63443 :   LibmeshPetscCallQ(VecGetArrayRead(x, &xx));
      43    36902003 :   for (PetscInt i = 0; i < size; i++)
      44    36838560 :     solution_seed(i) = xx[i];
      45             : 
      46       63443 :   LibmeshPetscCallQ(VecRestoreArrayRead(x, &xx));
      47             : 
      48             :   libMesh::DenseVector<Real> Wij_residual_vector =
      49       63443 :       cc->schp->residualFunction(cc->iblock, solution_seed);
      50             : 
      51       63443 :   LibmeshPetscCallQ(VecGetArray(f, &ff));
      52    36902003 :   for (int i = 0; i < size; i++)
      53    36838560 :     ff[i] = Wij_residual_vector(i);
      54             : 
      55       63443 :   LibmeshPetscCallQ(VecRestoreArray(f, &ff));
      56       63443 :   PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
      57             : }
      58             : 
      59             : InputParameters
      60         602 : SubChannel1PhaseProblem::validParams()
      61             : {
      62             :   // Enumerations
      63        1204 :   MooseEnum schemes("upwind downwind central_difference exponential", "central_difference");
      64        1204 :   MooseEnum gravity_direction("counter_flow co_flow none", "counter_flow");
      65             : 
      66             :   // Inputs
      67         602 :   InputParameters params = ExternalProblem::validParams();
      68         602 :   params += PostprocessorInterface::validParams();
      69         602 :   params.addClassDescription("Base class of the subchannel solvers");
      70        1204 :   params.addRequiredParam<unsigned int>("n_blocks", "The number of blocks in the axial direction");
      71        1204 :   params.addParam<Real>("P_tol", 1e-6, "Pressure tolerance");
      72        1204 :   params.addParam<Real>("T_tol", 1e-6, "Temperature tolerance");
      73        1204 :   params.addParam<int>("T_maxit", 100, "Maximum number of iterations for inner temperature loop");
      74        1204 :   params.addParam<PetscReal>("rtol", 1e-6, "Relative tolerance for ksp solver");
      75        1204 :   params.addParam<PetscReal>("atol", 1e-6, "Absolute tolerance for ksp solver");
      76        1204 :   params.addParam<PetscReal>("dtol", 1e5, "Divergence tolerance or ksp solver");
      77        1204 :   params.addParam<PetscInt>("maxit", 1e4, "Maximum number of iterations for ksp solver");
      78        1204 :   params.addParam<MooseEnum>(
      79             :       "interpolation_scheme",
      80             :       schemes,
      81             :       "Interpolation scheme used for the method. Default is central_difference");
      82        1204 :   params.addParam<MooseEnum>(
      83             :       "gravity", gravity_direction, "Direction of gravity. Default is counter_flow");
      84        1204 :   params.addParam<bool>(
      85        1204 :       "implicit", false, "Boolean to define the use of explicit or implicit solution.");
      86        1204 :   params.addParam<bool>("staggered_pressure",
      87        1204 :                         false,
      88             :                         "Boolean to define the use of staggered or collocated pressure.");
      89        1204 :   params.addParam<bool>(
      90        1204 :       "segregated", true, "Boolean to define whether to use a segregated solution.");
      91        1204 :   params.addParam<bool>(
      92        1204 :       "verbose_subchannel", false, "Boolean to print out information related to subchannel solve.");
      93        1204 :   params.addRequiredParam<bool>("compute_density", "Flag that enables the calculation of density");
      94        1204 :   params.addRequiredParam<bool>("compute_viscosity",
      95             :                                 "Flag that enables the calculation of viscosity");
      96        1204 :   params.addRequiredParam<bool>(
      97             :       "compute_power",
      98             :       "Flag that informs whether we solve the Enthalpy/Temperature equations or not");
      99        1204 :   params.addRequiredParam<PostprocessorName>(
     100             :       "P_out",
     101             :       "The postprocessor (or scalar) that provides the absolute outlet pressure [Pa]. The solved "
     102             :       "pressure variable P is relative to this value.");
     103        1204 :   params.addRequiredParam<UserObjectName>("fp", "Fluid properties user object name");
     104        1204 :   params.addRequiredParam<UserObjectName>("friction_closure",
     105             :                                           "Closure computing the friction factor");
     106        1204 :   params.addRequiredParam<UserObjectName>(
     107             :       "mixing_closure",
     108             :       "Closure computing the turbulent mixing, wire-induced "
     109             :       "mixing and sweep flow mixing parameter where applicable");
     110        1204 :   params.addParam<UserObjectName>(
     111             :       "pin_HTC_closure", "Closure computing HTC on fuel pin (required if pin mesh exists).");
     112        1204 :   params.addParam<UserObjectName>("duct_HTC_closure",
     113             :                                   "Closure computing HTC on duct (required if duct mesh exists).");
     114        1204 :   params.addParam<bool>(
     115        1204 :       "full_output", false, "Flag that enables the output of the maximum number of variables.");
     116        1204 :   params.addDeprecatedParam<Real>("beta",
     117             :                                   "Thermal diffusion coefficient used in turbulent crossflow.",
     118             :                                   "Use closure system instead.");
     119        1806 :   params.addDeprecatedParam<bool>(
     120             :       "constant_beta",
     121        1204 :       true,
     122             :       "Boolean to define the use of a constant beta or beta correlation (Kim and Chung, 2001)",
     123             :       "Use closure system instead.");
     124         602 :   return params;
     125         602 : }
     126             : 
     127         301 : SubChannel1PhaseProblem::SubChannel1PhaseProblem(const InputParameters & params)
     128             :   : ExternalProblem(params),
     129             :     PostprocessorInterface(this),
     130             :     _friction_args(/*i_ch=*/0, /*Re=*/1.0, /*S=*/0.0, /*w_perim=*/0.0),
     131             :     _nusselt_args(
     132             :         /*Re=*/1.0, /*Pr=*/1.0, std::numeric_limits<unsigned int>::max(), /*iz=*/0, /*i_ch=*/0),
     133         301 :     _P_out(getPostprocessorValue("P_out")),
     134         301 :     _fp(nullptr),
     135         301 :     _subchannel_mesh(SCM::getMesh<SubChannelMesh>(_mesh)),
     136         602 :     _n_blocks(getParam<unsigned int>("n_blocks")),
     137         602 :     _Wij(declareRestartableData<libMesh::DenseMatrix<Real>>("Wij")),
     138         301 :     _g_grav(9.81),
     139         301 :     _kij(_subchannel_mesh.getKij()),
     140         301 :     _one(1.0),
     141         602 :     _compute_density(getParam<bool>("compute_density")),
     142         602 :     _compute_viscosity(getParam<bool>("compute_viscosity")),
     143         602 :     _compute_power(getParam<bool>("compute_power")),
     144         301 :     _pin_mesh_exist(_subchannel_mesh.pinMeshExist()),
     145         301 :     _duct_mesh_exist(_subchannel_mesh.ductMeshExist()),
     146         602 :     _P_tol(getParam<Real>("P_tol")),
     147         602 :     _T_tol(getParam<Real>("T_tol")),
     148         602 :     _T_maxit(getParam<int>("T_maxit")),
     149         602 :     _rtol(getParam<PetscReal>("rtol")),
     150         602 :     _atol(getParam<PetscReal>("atol")),
     151         602 :     _dtol(getParam<PetscReal>("dtol")),
     152         602 :     _maxit(getParam<PetscInt>("maxit")),
     153         602 :     _interpolation_scheme(getParam<MooseEnum>("interpolation_scheme")),
     154         602 :     _gravity_direction(getParam<MooseEnum>("gravity")),
     155         301 :     _dir_grav(computeGravityDir(_gravity_direction)),
     156         602 :     _implicit_bool(getParam<bool>("implicit")),
     157         602 :     _staggered_pressure_bool(getParam<bool>("staggered_pressure")),
     158         602 :     _segregated_bool(getParam<bool>("segregated")),
     159         602 :     _verbose_subchannel(getParam<bool>("verbose_subchannel")),
     160             :     _Tpin_soln(nullptr),
     161             :     _duct_heat_flux_soln(nullptr),
     162             :     _Tduct_soln(nullptr),
     163        1204 :     _HTC_soln(nullptr)
     164             : {
     165         602 :   if (params.isParamSetByUser("beta") || params.isParamSetByUser("constant_beta"))
     166           0 :     paramError("beta",
     167             :                "You are using a deprecated parameter. Please use the mixing_closure system.");
     168         802 :   if (_pin_mesh_exist && !isParamValid("pin_HTC_closure"))
     169           0 :     paramError("pin_HTC_closure", "required when a pin mesh exists.");
     170         502 :   if (_duct_mesh_exist && !isParamValid("duct_HTC_closure"))
     171           0 :     paramError("duct_HTC_closure", "required when a duct mesh exists.");
     172             :   // NOTE: The four quantities below are 0 for processor_id != 0
     173         301 :   _n_cells = _subchannel_mesh.getNumOfAxialCells();
     174         301 :   _n_gaps = _subchannel_mesh.getNumOfGapsPerLayer();
     175         301 :   _n_pins = _subchannel_mesh.getNumOfPins();
     176         301 :   _n_channels = _subchannel_mesh.getNumOfChannels();
     177             :   // NOTE: The four quantities above are 0 for processor_id != 0
     178         301 :   _z_grid = _subchannel_mesh.getZGrid();
     179         301 :   _block_size = _n_cells / _n_blocks;
     180             :   // Pressure drop (lives on subchannel nodes)
     181         301 :   _DP.resize(_n_channels, _n_cells + 1);
     182             :   _DP.zero();
     183             :   // Turbulent crossflow (stuff that live on the gaps)
     184         301 :   if (!_app.isRestarting() && !_app.isRecovering())
     185             :   {
     186         295 :     _Wij.resize(_n_gaps, _n_cells + 1);
     187         295 :     _Wij.zero();
     188             :   }
     189         301 :   _Wij_old.resize(_n_gaps, _n_cells + 1);
     190             :   _Wij_old.zero();
     191         301 :   _WijPrime.resize(_n_gaps, _n_cells + 1);
     192             :   _WijPrime.zero();
     193         301 :   _Wij_residual_matrix.resize(_n_gaps, _block_size);
     194             :   _Wij_residual_matrix.zero();
     195         301 :   _converged = true;
     196             : 
     197             :   // Mass conservation components
     198         301 :   LibmeshPetscCall(
     199             :       createPetscMatrix(_mc_sumWij_mat, _block_size * _n_channels, _block_size * _n_gaps));
     200         301 :   LibmeshPetscCall(createPetscVector(_Wij_vec, _block_size * _n_gaps));
     201         301 :   LibmeshPetscCall(createPetscVector(_prod, _block_size * _n_channels));
     202         301 :   LibmeshPetscCall(createPetscVector(_prodp, _block_size * _n_channels));
     203         301 :   LibmeshPetscCall(createPetscMatrix(
     204             :       _mc_axial_convection_mat, _block_size * _n_channels, _block_size * _n_channels));
     205         301 :   LibmeshPetscCall(createPetscVector(_mc_axial_convection_rhs, _block_size * _n_channels));
     206             : 
     207             :   // Axial momentum conservation components
     208         301 :   LibmeshPetscCall(createPetscMatrix(
     209             :       _amc_turbulent_cross_flows_mat, _block_size * _n_gaps, _block_size * _n_channels));
     210         301 :   LibmeshPetscCall(createPetscVector(_amc_turbulent_cross_flows_rhs, _block_size * _n_gaps));
     211         301 :   LibmeshPetscCall(createPetscMatrix(
     212             :       _amc_time_derivative_mat, _block_size * _n_channels, _block_size * _n_channels));
     213         301 :   LibmeshPetscCall(createPetscVector(_amc_time_derivative_rhs, _block_size * _n_channels));
     214         301 :   LibmeshPetscCall(createPetscMatrix(
     215             :       _amc_advective_derivative_mat, _block_size * _n_channels, _block_size * _n_channels));
     216         301 :   LibmeshPetscCall(createPetscVector(_amc_advective_derivative_rhs, _block_size * _n_channels));
     217         301 :   LibmeshPetscCall(createPetscMatrix(
     218             :       _amc_cross_derivative_mat, _block_size * _n_channels, _block_size * _n_channels));
     219         301 :   LibmeshPetscCall(createPetscVector(_amc_cross_derivative_rhs, _block_size * _n_channels));
     220         301 :   LibmeshPetscCall(createPetscMatrix(
     221             :       _amc_friction_force_mat, _block_size * _n_channels, _block_size * _n_channels));
     222         301 :   LibmeshPetscCall(createPetscVector(_amc_friction_force_rhs, _block_size * _n_channels));
     223         301 :   LibmeshPetscCall(createPetscVector(_amc_gravity_rhs, _block_size * _n_channels));
     224         301 :   LibmeshPetscCall(createPetscMatrix(
     225             :       _amc_pressure_force_mat, _block_size * _n_channels, _block_size * _n_channels));
     226         301 :   LibmeshPetscCall(createPetscVector(_amc_pressure_force_rhs, _block_size * _n_channels));
     227         301 :   LibmeshPetscCall(
     228             :       createPetscMatrix(_amc_sys_mdot_mat, _block_size * _n_channels, _block_size * _n_channels));
     229         301 :   LibmeshPetscCall(createPetscVector(_amc_sys_mdot_rhs, _block_size * _n_channels));
     230             : 
     231             :   // Lateral momentum conservation components
     232         301 :   LibmeshPetscCall(
     233             :       createPetscMatrix(_cmc_time_derivative_mat, _block_size * _n_gaps, _block_size * _n_gaps));
     234         301 :   LibmeshPetscCall(createPetscVector(_cmc_time_derivative_rhs, _block_size * _n_gaps));
     235         301 :   LibmeshPetscCall(createPetscMatrix(
     236             :       _cmc_advective_derivative_mat, _block_size * _n_gaps, _block_size * _n_gaps));
     237         301 :   LibmeshPetscCall(createPetscVector(_cmc_advective_derivative_rhs, _block_size * _n_gaps));
     238         301 :   LibmeshPetscCall(
     239             :       createPetscMatrix(_cmc_friction_force_mat, _block_size * _n_gaps, _block_size * _n_gaps));
     240         301 :   LibmeshPetscCall(createPetscVector(_cmc_friction_force_rhs, _block_size * _n_gaps));
     241         301 :   LibmeshPetscCall(
     242             :       createPetscMatrix(_cmc_pressure_force_mat, _block_size * _n_gaps, _block_size * _n_channels));
     243         301 :   LibmeshPetscCall(createPetscVector(_cmc_pressure_force_rhs, _block_size * _n_gaps));
     244         301 :   LibmeshPetscCall(
     245             :       createPetscMatrix(_cmc_sys_Wij_mat, _block_size * _n_gaps, _block_size * _n_gaps));
     246         301 :   LibmeshPetscCall(createPetscVector(_cmc_sys_Wij_rhs, _block_size * _n_gaps));
     247             : 
     248             :   // Energy conservation components
     249         301 :   LibmeshPetscCall(createPetscMatrix(
     250             :       _hc_time_derivative_mat, _block_size * _n_channels, _block_size * _n_channels));
     251         301 :   LibmeshPetscCall(createPetscVector(_hc_time_derivative_rhs, _block_size * _n_channels));
     252         301 :   LibmeshPetscCall(createPetscMatrix(
     253             :       _hc_advective_derivative_mat, _block_size * _n_channels, _block_size * _n_channels));
     254         301 :   LibmeshPetscCall(createPetscVector(_hc_advective_derivative_rhs, _block_size * _n_channels));
     255         301 :   LibmeshPetscCall(createPetscMatrix(
     256             :       _hc_cross_derivative_mat, _block_size * _n_channels, _block_size * _n_channels));
     257         301 :   LibmeshPetscCall(createPetscVector(_hc_cross_derivative_rhs, _block_size * _n_channels));
     258         301 :   LibmeshPetscCall(createPetscVector(_hc_added_heat_rhs, _block_size * _n_channels));
     259         301 :   LibmeshPetscCall(
     260             :       createPetscMatrix(_hc_sys_h_mat, _block_size * _n_channels, _block_size * _n_channels));
     261         301 :   LibmeshPetscCall(createPetscVector(_hc_sys_h_rhs, _block_size * _n_channels));
     262             : 
     263         301 :   if ((_n_blocks == _n_cells) && _implicit_bool)
     264             :   {
     265           0 :     mooseError(name(),
     266             :                ": When implicit number of blocks can't be equal to number of cells. This will "
     267             :                "cause problems with the subchannel interpolation scheme.");
     268             :   }
     269         301 : }
     270             : 
     271             : void
     272         288 : SubChannel1PhaseProblem::initialSetup()
     273             : {
     274         288 :   ExternalProblem::initialSetup();
     275             : 
     276         576 :   _fp = &getUserObject<SinglePhaseFluidProperties>(getParam<UserObjectName>("fp"));
     277         288 :   _friction_closure =
     278         576 :       &getUserObject<SCMFrictionClosureBase>(getParam<UserObjectName>("friction_closure"));
     279         288 :   _mixing_closure =
     280         576 :       &getUserObject<SCMMixingClosureBase>(getParam<UserObjectName>("mixing_closure"));
     281             : 
     282             :   /// Set value for turbulent momentum modeling parameter CT
     283         288 :   _CT = _mixing_closure->getCT();
     284             : 
     285             :   // Create variables for output and storage
     286         576 :   _mdot_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::MASS_FLOW_RATE));
     287         576 :   _SumWij_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::SUM_CROSSFLOW));
     288         576 :   _P_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::PRESSURE));
     289         576 :   if (getParam<bool>("full_output"))
     290             :   {
     291         554 :     _DP_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::PRESSURE_DROP));
     292         554 :     _ff_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::FRICTION_FACTOR));
     293             :   }
     294         576 :   _h_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::ENTHALPY));
     295         576 :   _T_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::TEMPERATURE));
     296         288 :   if (_pin_mesh_exist)
     297             :   {
     298         324 :     _Tpin_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::PIN_TEMPERATURE));
     299         324 :     _Dpin_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::PIN_DIAMETER));
     300             :     _HTC_soln =
     301         324 :         std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::HEAT_TRANSFER_COEFFICIENT));
     302         162 :     _pin_HTC_closure =
     303         486 :         &getUserObject<SCMHTCClosureBase>(getParam<UserObjectName>("pin_HTC_closure"));
     304             :   }
     305         576 :   _rho_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::DENSITY));
     306         576 :   _mu_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::VISCOSITY));
     307         576 :   _S_flow_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::SURFACE_AREA));
     308         576 :   _w_perim_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::WETTED_PERIMETER));
     309         576 :   _q_prime_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::LINEAR_HEAT_RATE));
     310             :   _displacement_soln =
     311         576 :       std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::DISPLACEMENT));
     312         288 :   if (_duct_mesh_exist)
     313             :   {
     314             :     _duct_heat_flux_soln =
     315         134 :         std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::DUCT_HEAT_FLUX));
     316         134 :     _Tduct_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::DUCT_TEMPERATURE));
     317          67 :     _duct_HTC_closure =
     318         201 :         &getUserObject<SCMHTCClosureBase>(getParam<UserObjectName>("duct_HTC_closure"));
     319             :   }
     320         288 : }
     321             : 
     322             : void
     323         351 : SubChannel1PhaseProblem::detectDeformation()
     324             : {
     325             :   const Real tol = libMesh::TOLERANCE;
     326         351 :   const auto pin_diameter = _subchannel_mesh.getPinDiameter();
     327             : 
     328         351 :   if (_pin_mesh_exist)
     329             :   {
     330        2663 :     for (unsigned int iz = 0; iz < _n_cells + 1; iz++)
     331       87614 :       for (unsigned int i_pin = 0; i_pin < _n_pins; i_pin++)
     332             :       {
     333       85114 :         auto * node = _subchannel_mesh.getPinNode(i_pin, iz);
     334       85114 :         const Real Dpin = (*_Dpin_soln)(node);
     335       85114 :         if (std::abs(Dpin) <= tol)
     336           0 :           mooseError("Dpin is zero at node ",
     337           0 :                      node->id(),
     338             :                      ". You must initialize Dpin to a non-zero value.");
     339       85114 :         if (std::abs(Dpin - pin_diameter) > tol)
     340         690 :           _deformation = true;
     341             :       }
     342             :   }
     343             : 
     344        8509 :   for (unsigned int iz = 0; iz < _n_cells + 1 && !_deformation; iz++)
     345      319157 :     for (unsigned int i_ch = 0; i_ch < _n_channels && !_deformation; i_ch++)
     346             :     {
     347      310999 :       auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
     348      310999 :       auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
     349             : 
     350      437959 :       if ((subch_type == EChannelType::CORNER || subch_type == EChannelType::EDGE) &&
     351      126960 :           std::abs((*_displacement_soln)(node)) > tol)
     352           0 :         _deformation = true;
     353             :     }
     354         351 : }
     355             : 
     356         894 : SubChannel1PhaseProblem::~SubChannel1PhaseProblem()
     357             : {
     358         298 :   PetscErrorCode ierr = cleanUp();
     359         298 :   if (ierr)
     360           0 :     mooseError(name(), ": Error in memory cleanup");
     361         298 : }
     362             : 
     363             : PetscErrorCode
     364         298 : SubChannel1PhaseProblem::cleanUp()
     365             : {
     366             :   PetscFunctionBegin;
     367             :   // We need to clean up the petsc matrices/vectors
     368             :   // Mass conservation components
     369         298 :   LibmeshPetscCall(MatDestroy(&_mc_sumWij_mat));
     370         298 :   LibmeshPetscCall(VecDestroy(&_Wij_vec));
     371         298 :   LibmeshPetscCall(VecDestroy(&_prod));
     372         298 :   LibmeshPetscCall(VecDestroy(&_prodp));
     373         298 :   LibmeshPetscCall(MatDestroy(&_mc_axial_convection_mat));
     374         298 :   LibmeshPetscCall(VecDestroy(&_mc_axial_convection_rhs));
     375             : 
     376             :   // Axial momentum conservation components
     377         298 :   LibmeshPetscCall(MatDestroy(&_amc_turbulent_cross_flows_mat));
     378         298 :   LibmeshPetscCall(VecDestroy(&_amc_turbulent_cross_flows_rhs));
     379         298 :   LibmeshPetscCall(MatDestroy(&_amc_time_derivative_mat));
     380         298 :   LibmeshPetscCall(VecDestroy(&_amc_time_derivative_rhs));
     381         298 :   LibmeshPetscCall(MatDestroy(&_amc_advective_derivative_mat));
     382         298 :   LibmeshPetscCall(VecDestroy(&_amc_advective_derivative_rhs));
     383         298 :   LibmeshPetscCall(MatDestroy(&_amc_cross_derivative_mat));
     384         298 :   LibmeshPetscCall(VecDestroy(&_amc_cross_derivative_rhs));
     385         298 :   LibmeshPetscCall(MatDestroy(&_amc_friction_force_mat));
     386         298 :   LibmeshPetscCall(VecDestroy(&_amc_friction_force_rhs));
     387         298 :   LibmeshPetscCall(VecDestroy(&_amc_gravity_rhs));
     388         298 :   LibmeshPetscCall(MatDestroy(&_amc_pressure_force_mat));
     389         298 :   LibmeshPetscCall(VecDestroy(&_amc_pressure_force_rhs));
     390         298 :   LibmeshPetscCall(MatDestroy(&_amc_sys_mdot_mat));
     391         298 :   LibmeshPetscCall(VecDestroy(&_amc_sys_mdot_rhs));
     392             : 
     393             :   // Lateral momentum conservation components
     394         298 :   LibmeshPetscCall(MatDestroy(&_cmc_time_derivative_mat));
     395         298 :   LibmeshPetscCall(VecDestroy(&_cmc_time_derivative_rhs));
     396         298 :   LibmeshPetscCall(MatDestroy(&_cmc_advective_derivative_mat));
     397         298 :   LibmeshPetscCall(VecDestroy(&_cmc_advective_derivative_rhs));
     398         298 :   LibmeshPetscCall(MatDestroy(&_cmc_friction_force_mat));
     399         298 :   LibmeshPetscCall(VecDestroy(&_cmc_friction_force_rhs));
     400         298 :   LibmeshPetscCall(MatDestroy(&_cmc_pressure_force_mat));
     401         298 :   LibmeshPetscCall(VecDestroy(&_cmc_pressure_force_rhs));
     402         298 :   LibmeshPetscCall(MatDestroy(&_cmc_sys_Wij_mat));
     403         298 :   LibmeshPetscCall(VecDestroy(&_cmc_sys_Wij_rhs));
     404             : 
     405             :   // Energy conservation components
     406         298 :   LibmeshPetscCall(MatDestroy(&_hc_time_derivative_mat));
     407         298 :   LibmeshPetscCall(VecDestroy(&_hc_time_derivative_rhs));
     408         298 :   LibmeshPetscCall(MatDestroy(&_hc_advective_derivative_mat));
     409         298 :   LibmeshPetscCall(VecDestroy(&_hc_advective_derivative_rhs));
     410         298 :   LibmeshPetscCall(MatDestroy(&_hc_cross_derivative_mat));
     411         298 :   LibmeshPetscCall(VecDestroy(&_hc_cross_derivative_rhs));
     412         298 :   LibmeshPetscCall(VecDestroy(&_hc_added_heat_rhs));
     413         298 :   LibmeshPetscCall(MatDestroy(&_hc_sys_h_mat));
     414         298 :   LibmeshPetscCall(VecDestroy(&_hc_sys_h_rhs));
     415             : 
     416         298 :   PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
     417             : }
     418             : 
     419             : bool
     420         348 : SubChannel1PhaseProblem::solverSystemConverged(const unsigned int)
     421             : {
     422         348 :   return _converged;
     423             : }
     424             : 
     425             : PetscScalar
     426   577065852 : SubChannel1PhaseProblem::computeInterpolationCoefficients(PetscScalar Peclet)
     427             : {
     428   577065852 :   switch (_interpolation_scheme)
     429             :   {
     430             :     case 0: // upwind interpolation
     431             :       return 1.0;
     432           0 :     case 1: // downwind interpolation
     433           0 :       return 0.0;
     434   470047452 :     case 2: // central_difference interpolation
     435   470047452 :       return 0.5;
     436    33092280 :     case 3: // exponential interpolation (Peclet limited)
     437    33092280 :       return ((Peclet - 1.0) * std::exp(Peclet) + 1) / (Peclet * (std::exp(Peclet) - 1.) + 1e-10);
     438           0 :     default:
     439           0 :       mooseError(name(),
     440             :                  ": Interpolation scheme should be a string: upwind, downwind, central_difference, "
     441             :                  "exponential");
     442             :   }
     443             : }
     444             : 
     445             : PetscScalar
     446   490911462 : SubChannel1PhaseProblem::computeInterpolatedValue(PetscScalar topValue,
     447             :                                                   PetscScalar botValue,
     448             :                                                   PetscScalar Peclet)
     449             : {
     450   490911462 :   PetscScalar alpha = computeInterpolationCoefficients(Peclet);
     451   490911462 :   return alpha * botValue + (1.0 - alpha) * topValue;
     452             : }
     453             : 
     454             : void
     455         303 : SubChannel1PhaseProblem::computeWijFromSolve(int iblock)
     456             : {
     457         303 :   const unsigned int last_node = (iblock + 1) * _block_size;
     458         303 :   const unsigned int first_node = iblock * _block_size + 1;
     459             :   // Initial guess, port crossflow of block (iblock) into a vector that will act as my initial guess
     460         303 :   libMesh::DenseVector<Real> solution_seed(_n_gaps * _block_size, 0.0);
     461       10453 :   for (unsigned int iz = first_node; iz < last_node + 1; iz++)
     462             :   {
     463      228490 :     for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
     464             :     {
     465      218340 :       int i = _n_gaps * (iz - first_node) + i_gap; // column wise transfer
     466      218340 :       solution_seed(i) = _Wij(i_gap, iz);
     467             :     }
     468             :   }
     469             : 
     470             :   // Solving the combined lateral momentum equation for Wij using a PETSc solver and update vector
     471             :   // root
     472         303 :   libMesh::DenseVector<Real> root(_n_gaps * _block_size, 0.0);
     473         303 :   LibmeshPetscCall(petscSnesSolver(iblock, solution_seed, root));
     474             : 
     475             :   // Assign the solution to the cross-flow matrix
     476             :   int i = 0;
     477       10453 :   for (unsigned int iz = first_node; iz < last_node + 1; iz++)
     478             :   {
     479      228490 :     for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
     480             :     {
     481      218340 :       _Wij(i_gap, iz) = root(i);
     482      218340 :       i++;
     483             :     }
     484             :   }
     485         303 : }
     486             : 
     487             : void
     488       65330 : SubChannel1PhaseProblem::computeSumWij(int iblock)
     489             : {
     490       65330 :   const unsigned int last_node = (iblock + 1) * _block_size;
     491       65330 :   const unsigned int first_node = iblock * _block_size + 1;
     492             :   // Add to solution vector if explicit
     493       65330 :   if (!_implicit_bool)
     494             :   {
     495      318071 :     for (unsigned int iz = first_node; iz < last_node + 1; iz++)
     496             :     {
     497     7044710 :       for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
     498             :       {
     499     6744420 :         auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
     500             :         Real sumWij = 0.0;
     501             :         // Calculate sum of crossflow into channel i from channels j around i
     502             :         unsigned int counter = 0;
     503    28069140 :         for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
     504             :         {
     505    21324720 :           sumWij += _subchannel_mesh.getCrossflowSign(i_ch, counter) * _Wij(i_gap, iz);
     506    21324720 :           counter++;
     507             :         }
     508             :         // The net crossflow coming out of cell i [kg/sec]
     509     6744420 :         _SumWij_soln->set(node_out, sumWij);
     510             :       }
     511             :     }
     512             :   }
     513             :   // Add to matrix if implicit
     514             :   else
     515             :   {
     516      541754 :     for (unsigned int iz = first_node; iz < last_node + 1; iz++)
     517             :     {
     518      494205 :       unsigned int iz_ind = iz - first_node;
     519    19735695 :       for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
     520             :       {
     521             :         // Calculate sum of crossflow into channel i from channels j around i
     522             :         unsigned int counter = 0;
     523    81546090 :         for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
     524             :         {
     525    62304600 :           PetscInt row = i_ch + _n_channels * iz_ind;
     526    62304600 :           PetscInt col = i_gap + _n_gaps * iz_ind;
     527    62304600 :           PetscScalar value = _subchannel_mesh.getCrossflowSign(i_ch, counter);
     528    62304600 :           LibmeshPetscCall(MatSetValues(_mc_sumWij_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
     529    62304600 :           counter++;
     530             :         }
     531             :       }
     532             :     }
     533       47549 :     LibmeshPetscCall(MatAssemblyBegin(_mc_sumWij_mat, MAT_FINAL_ASSEMBLY));
     534       47549 :     LibmeshPetscCall(MatAssemblyEnd(_mc_sumWij_mat, MAT_FINAL_ASSEMBLY));
     535       47549 :     if (_segregated_bool)
     536             :     {
     537             :       Vec loc_prod;
     538             :       Vec loc_Wij;
     539       45662 :       LibmeshPetscCall(VecDuplicate(_amc_sys_mdot_rhs, &loc_prod));
     540       45662 :       LibmeshPetscCall(VecDuplicate(_Wij_vec, &loc_Wij));
     541       45662 :       LibmeshPetscCall(populateVectorFromDense<libMesh::DenseMatrix<Real>>(
     542             :           loc_Wij, _Wij, first_node, last_node, _n_gaps));
     543       45662 :       LibmeshPetscCall(MatMult(_mc_sumWij_mat, loc_Wij, loc_prod));
     544       45662 :       LibmeshPetscCall(populateSolutionChan<SolutionHandle>(
     545             :           loc_prod, *_SumWij_soln, first_node, last_node, _n_channels));
     546       45662 :       LibmeshPetscCall(VecDestroy(&loc_prod));
     547       45662 :       LibmeshPetscCall(VecDestroy(&loc_Wij));
     548             :     }
     549             :   }
     550       65330 : }
     551             : 
     552             : void
     553       65330 : SubChannel1PhaseProblem::computeMdot(int iblock)
     554             : {
     555       65330 :   const unsigned int last_node = (iblock + 1) * _block_size;
     556       65330 :   const unsigned int first_node = iblock * _block_size + 1;
     557       65330 :   if (!_implicit_bool)
     558             :   {
     559      318071 :     for (unsigned int iz = first_node; iz < last_node + 1; iz++)
     560             :     {
     561      300290 :       auto dz = _z_grid[iz] - _z_grid[iz - 1];
     562     7044710 :       for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
     563             :       {
     564     6744420 :         auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
     565     6744420 :         auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
     566     6744420 :         auto volume = dz * (*_S_flow_soln)(node_in);
     567     6744420 :         auto time_term = _TR * ((*_rho_soln)(node_out)-_rho_soln->old(node_out)) * volume / _dt;
     568             :         // Wij positive out of i into j;
     569     6744420 :         auto mdot_out = (*_mdot_soln)(node_in) - (*_SumWij_soln)(node_out)-time_term;
     570     6744420 :         if (mdot_out < 0)
     571             :         {
     572           0 :           _console << "Wij = : " << _Wij << "\n";
     573           0 :           mooseError(name(),
     574             :                      " : Calculation of negative mass flow mdot_out = : ",
     575             :                      mdot_out,
     576             :                      " Axial Level= : ",
     577             :                      iz,
     578             :                      " - Implicit solves are required for recirculating flow.");
     579             :         }
     580     6744420 :         _mdot_soln->set(node_out, mdot_out); // kg/sec
     581             :       }
     582             :     }
     583             :   }
     584             :   else
     585             :   {
     586      541754 :     for (unsigned int iz = first_node; iz < last_node + 1; iz++)
     587             :     {
     588      494205 :       auto dz = _z_grid[iz] - _z_grid[iz - 1];
     589      494205 :       auto iz_ind = iz - first_node;
     590    19735695 :       for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
     591             :       {
     592    19241490 :         auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
     593    19241490 :         auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
     594    19241490 :         auto volume = dz * (*_S_flow_soln)(node_in);
     595             : 
     596             :         // Adding time derivative to the RHS
     597    19241490 :         auto time_term = _TR * ((*_rho_soln)(node_out)-_rho_soln->old(node_out)) * volume / _dt;
     598    19241490 :         PetscInt row_vec = i_ch + _n_channels * iz_ind;
     599    19241490 :         PetscScalar value_vec = -1.0 * time_term;
     600    19241490 :         LibmeshPetscCall(
     601             :             VecSetValues(_mc_axial_convection_rhs, 1, &row_vec, &value_vec, INSERT_VALUES));
     602             : 
     603             :         // Imposing bottom boundary condition or adding of diagonal elements
     604    19241490 :         if (iz == first_node)
     605             :         {
     606     1792230 :           PetscScalar value_vec = (*_mdot_soln)(node_in);
     607     1792230 :           PetscInt row_vec = i_ch + _n_channels * iz_ind;
     608     1792230 :           LibmeshPetscCall(
     609             :               VecSetValues(_mc_axial_convection_rhs, 1, &row_vec, &value_vec, ADD_VALUES));
     610             :         }
     611             :         else
     612             :         {
     613    17449260 :           PetscInt row = i_ch + _n_channels * iz_ind;
     614    17449260 :           PetscInt col = i_ch + _n_channels * (iz_ind - 1);
     615    17449260 :           PetscScalar value = -1.0;
     616    17449260 :           LibmeshPetscCall(
     617             :               MatSetValues(_mc_axial_convection_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
     618             :         }
     619             : 
     620             :         // Adding diagonal elements
     621    19241490 :         PetscInt row = i_ch + _n_channels * iz_ind;
     622    19241490 :         PetscInt col = i_ch + _n_channels * iz_ind;
     623    19241490 :         PetscScalar value = 1.0;
     624    19241490 :         LibmeshPetscCall(
     625             :             MatSetValues(_mc_axial_convection_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
     626             : 
     627             :         // Adding cross flows RHS
     628    19241490 :         if (_segregated_bool)
     629             :         {
     630    15827820 :           PetscScalar value_vec_2 = -1.0 * (*_SumWij_soln)(node_out);
     631    15827820 :           PetscInt row_vec_2 = i_ch + _n_channels * iz_ind;
     632    15827820 :           LibmeshPetscCall(
     633             :               VecSetValues(_mc_axial_convection_rhs, 1, &row_vec_2, &value_vec_2, ADD_VALUES));
     634             :         }
     635             :       }
     636             :     }
     637       47549 :     LibmeshPetscCall(MatAssemblyBegin(_mc_axial_convection_mat, MAT_FINAL_ASSEMBLY));
     638       47549 :     LibmeshPetscCall(MatAssemblyEnd(_mc_axial_convection_mat, MAT_FINAL_ASSEMBLY));
     639             : 
     640       47549 :     if (_segregated_bool)
     641             :     {
     642             :       KSP ksploc;
     643             :       PC pc;
     644             :       Vec sol;
     645       45662 :       LibmeshPetscCall(VecDuplicate(_mc_axial_convection_rhs, &sol));
     646       45662 :       LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksploc));
     647       45662 :       LibmeshPetscCall(KSPSetOperators(ksploc, _mc_axial_convection_mat, _mc_axial_convection_mat));
     648       45662 :       LibmeshPetscCall(KSPGetPC(ksploc, &pc));
     649       45662 :       LibmeshPetscCall(PCSetType(pc, PCJACOBI));
     650       45662 :       LibmeshPetscCall(KSPSetTolerances(ksploc, _rtol, _atol, _dtol, _maxit));
     651       45662 :       LibmeshPetscCall(KSPSetFromOptions(ksploc));
     652       45662 :       LibmeshPetscCall(KSPSolve(ksploc, _mc_axial_convection_rhs, sol));
     653       45662 :       LibmeshPetscCall(populateSolutionChan<SolutionHandle>(
     654             :           sol, *_mdot_soln, first_node, last_node, _n_channels));
     655       45662 :       LibmeshPetscCall(VecZeroEntries(_mc_axial_convection_rhs));
     656       45662 :       LibmeshPetscCall(KSPDestroy(&ksploc));
     657       45662 :       LibmeshPetscCall(VecDestroy(&sol));
     658             :     }
     659             :   }
     660       65330 : }
     661             : 
     662             : void
     663       65330 : SubChannel1PhaseProblem::computeDP(int iblock)
     664             : {
     665       65330 :   const unsigned int last_node = (iblock + 1) * _block_size;
     666       65330 :   const unsigned int first_node = iblock * _block_size + 1;
     667       65330 :   if (!_implicit_bool)
     668             :   {
     669      318071 :     for (unsigned int iz = first_node; iz < last_node + 1; iz++)
     670             :     {
     671      300290 :       auto k_grid = _subchannel_mesh.getKGrid();
     672      300290 :       auto dz = _z_grid[iz] - _z_grid[iz - 1];
     673     7044710 :       for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
     674             :       {
     675     6744420 :         auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
     676     6744420 :         auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
     677     6744420 :         auto rho_in = (*_rho_soln)(node_in);
     678     6744420 :         auto rho_out = (*_rho_soln)(node_out);
     679     6744420 :         auto mu_in = (*_mu_soln)(node_in);
     680     6744420 :         auto S = (*_S_flow_soln)(node_in);
     681     6744420 :         auto w_perim = (*_w_perim_soln)(node_in);
     682             :         // hydraulic diameter in the i direction
     683     6744420 :         auto Dh_i = 4.0 * S / w_perim;
     684     6744420 :         auto time_term = _TR * ((*_mdot_soln)(node_out)-_mdot_soln->old(node_out)) * dz / _dt -
     685     6744420 :                          dz * 2.0 * (*_mdot_soln)(node_out) * (rho_out - _rho_soln->old(node_out)) /
     686     6744420 :                              rho_in / _dt;
     687             :         auto mass_term1 =
     688     6744420 :             Utility::pow<2>((*_mdot_soln)(node_out)) * (1.0 / S / rho_out - 1.0 / S / rho_in);
     689     6744420 :         auto mass_term2 = -2.0 * (*_mdot_soln)(node_out) * (*_SumWij_soln)(node_out) / S / rho_in;
     690             :         auto crossflow_term = 0.0;
     691             :         auto turbulent_term = 0.0;
     692             :         unsigned int counter = 0;
     693    28069140 :         for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
     694             :         {
     695    21324720 :           auto chans = _subchannel_mesh.getGapChannels(i_gap);
     696             :           unsigned int ii_ch = chans.first;
     697             :           unsigned int jj_ch = chans.second;
     698    21324720 :           auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
     699    21324720 :           auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
     700    21324720 :           auto * node_out_i = _subchannel_mesh.getChannelNode(ii_ch, iz);
     701    21324720 :           auto * node_out_j = _subchannel_mesh.getChannelNode(jj_ch, iz);
     702    21324720 :           auto rho_i = (*_rho_soln)(node_in_i);
     703    21324720 :           auto rho_j = (*_rho_soln)(node_in_j);
     704    21324720 :           auto Si = (*_S_flow_soln)(node_in_i);
     705    21324720 :           auto Sj = (*_S_flow_soln)(node_in_j);
     706             :           Real u_star = 0.0;
     707             :           // figure out donor axial velocity
     708    21324720 :           if (_Wij(i_gap, iz) > 0.0)
     709    10214926 :             u_star = (*_mdot_soln)(node_out_i) / Si / rho_i;
     710             :           else
     711    11109794 :             u_star = (*_mdot_soln)(node_out_j) / Sj / rho_j;
     712             : 
     713    21324720 :           crossflow_term +=
     714    21324720 :               _subchannel_mesh.getCrossflowSign(i_ch, counter) * _Wij(i_gap, iz) * u_star;
     715             : 
     716    21324720 :           turbulent_term += _WijPrime(i_gap, iz) * (2 * (*_mdot_soln)(node_out) / rho_in / S -
     717    21324720 :                                                     (*_mdot_soln)(node_out_j) / Sj / rho_j -
     718    21324720 :                                                     (*_mdot_soln)(node_out_i) / Si / rho_i);
     719    21324720 :           counter++;
     720             :         }
     721     6744420 :         turbulent_term *= _CT;
     722     6744420 :         auto Re = (((*_mdot_soln)(node_in) / S) * Dh_i / mu_in);
     723     6744420 :         _friction_args = FrictionStruct(i_ch, Re, S, w_perim);
     724     6744420 :         Real ff = _friction_closure->computeFrictionFactor(_friction_args);
     725     6744420 :         if (_ff_soln)
     726     6744420 :           _ff_soln->set(node_out, ff);
     727             :         /// Upwind local form loss
     728             :         auto ki = 0.0;
     729     6744420 :         if ((*_mdot_soln)(node_out) >= 0)
     730     6744420 :           ki = k_grid[i_ch][iz - 1];
     731             :         else
     732           0 :           ki = k_grid[i_ch][iz];
     733     6744420 :         auto friction_term = (ff * dz / Dh_i + ki) * 0.5 *
     734     6744420 :                              (*_mdot_soln)(node_out)*std::abs((*_mdot_soln)(node_out)) /
     735     6744420 :                              (S * (*_rho_soln)(node_out));
     736     6744420 :         auto gravity_term = _dir_grav * _g_grav * (*_rho_soln)(node_out)*dz * S;
     737     6744420 :         auto DP = (1 / S) * (time_term + mass_term1 + mass_term2 + crossflow_term + turbulent_term +
     738     6744420 :                              friction_term + gravity_term); // Pa
     739     6744420 :         _DP(i_ch, iz) = DP;
     740     6744420 :         if (_DP_soln)
     741     6744420 :           _DP_soln->set(node_out, DP);
     742             :       }
     743      300290 :     }
     744             :   }
     745             :   else
     746             :   {
     747       47549 :     LibmeshPetscCall(MatZeroEntries(_amc_time_derivative_mat));
     748       47549 :     LibmeshPetscCall(MatZeroEntries(_amc_advective_derivative_mat));
     749       47549 :     LibmeshPetscCall(MatZeroEntries(_amc_cross_derivative_mat));
     750       47549 :     LibmeshPetscCall(MatZeroEntries(_amc_friction_force_mat));
     751       47549 :     LibmeshPetscCall(VecZeroEntries(_amc_time_derivative_rhs));
     752       47549 :     LibmeshPetscCall(VecZeroEntries(_amc_advective_derivative_rhs));
     753       47549 :     LibmeshPetscCall(VecZeroEntries(_amc_cross_derivative_rhs));
     754       47549 :     LibmeshPetscCall(VecZeroEntries(_amc_friction_force_rhs));
     755       47549 :     LibmeshPetscCall(VecZeroEntries(_amc_gravity_rhs));
     756       47549 :     LibmeshPetscCall(MatZeroEntries(_amc_sys_mdot_mat));
     757       47549 :     LibmeshPetscCall(VecZeroEntries(_amc_sys_mdot_rhs));
     758      541754 :     for (unsigned int iz = first_node; iz < last_node + 1; iz++)
     759             :     {
     760      494205 :       auto k_grid = _subchannel_mesh.getKGrid();
     761      494205 :       auto dz = _z_grid[iz] - _z_grid[iz - 1];
     762      494205 :       auto iz_ind = iz - first_node;
     763    19735695 :       for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
     764             :       {
     765             :         // inlet and outlet nodes
     766    19241490 :         auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
     767    19241490 :         auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
     768             : 
     769             :         // interpolation weight coefficient
     770             :         PetscScalar Pe = 0.5;
     771    19241490 :         if (_interpolation_scheme == 3)
     772             :         {
     773             :           // Compute the Peclet number
     774      808860 :           auto S_in = (*_S_flow_soln)(node_in);
     775      808860 :           auto S_out = (*_S_flow_soln)(node_out);
     776      808860 :           auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
     777      808860 :           auto w_perim_in = (*_w_perim_soln)(node_in);
     778      808860 :           auto w_perim_out = (*_w_perim_soln)(node_out);
     779      808860 :           auto w_perim_interp = this->computeInterpolatedValue(w_perim_out, w_perim_in, 0.5);
     780             :           auto mdot_loc =
     781      808860 :               this->computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), 0.5);
     782      808860 :           auto mu_in = (*_mu_soln)(node_in);
     783      808860 :           auto mu_out = (*_mu_soln)(node_out);
     784      808860 :           auto mu_interp = this->computeInterpolatedValue(mu_out, mu_in, 0.5);
     785      808860 :           auto Dh_i = 4.0 * S_interp / w_perim_interp;
     786             :           // Compute friction factor
     787      808860 :           auto Re = ((mdot_loc / S_interp) * Dh_i / mu_interp);
     788      808860 :           _friction_args = FrictionStruct(i_ch, Re, S_interp, w_perim_interp);
     789      808860 :           Real ff = _friction_closure->computeFrictionFactor(_friction_args);
     790      808860 :           if (_ff_soln)
     791      808860 :             _ff_soln->set(node_out, ff);
     792             :           /// Upwind local form loss
     793             :           auto ki = 0.0;
     794      808860 :           if ((*_mdot_soln)(node_out) >= 0)
     795      808860 :             ki = k_grid[i_ch][iz - 1];
     796             :           else
     797           0 :             ki = k_grid[i_ch][iz];
     798      808860 :           Pe = 1.0 / ((ff * dz / Dh_i + ki) * 0.5) * mdot_loc / std::abs(mdot_loc);
     799             :         }
     800    19241490 :         auto alpha = computeInterpolationCoefficients(Pe);
     801             : 
     802             :         // inlet, outlet, and interpolated density
     803    19241490 :         auto rho_in = (*_rho_soln)(node_in);
     804    19241490 :         auto rho_out = (*_rho_soln)(node_out);
     805    19241490 :         auto rho_interp = computeInterpolatedValue(rho_out, rho_in, Pe);
     806             : 
     807             :         // inlet, outlet, and interpolated viscosity
     808    19241490 :         auto mu_in = (*_mu_soln)(node_in);
     809    19241490 :         auto mu_out = (*_mu_soln)(node_out);
     810    19241490 :         auto mu_interp = computeInterpolatedValue(mu_out, mu_in, Pe);
     811             : 
     812             :         // inlet, outlet, and interpolated axial surface area
     813    19241490 :         auto S_in = (*_S_flow_soln)(node_in);
     814    19241490 :         auto S_out = (*_S_flow_soln)(node_out);
     815    19241490 :         auto S_interp = computeInterpolatedValue(S_out, S_in, Pe);
     816             : 
     817             :         // inlet, outlet, and interpolated wetted perimeter
     818    19241490 :         auto w_perim_in = (*_w_perim_soln)(node_in);
     819    19241490 :         auto w_perim_out = (*_w_perim_soln)(node_out);
     820    19241490 :         auto w_perim_interp = computeInterpolatedValue(w_perim_out, w_perim_in, Pe);
     821             : 
     822             :         // hydraulic diameter in the i direction
     823    19241490 :         auto Dh_i = 4.0 * S_interp / w_perim_interp;
     824             : 
     825             :         /// Time derivative term
     826    19241490 :         if (iz == first_node)
     827             :         {
     828     1792230 :           PetscScalar value_vec_tt = -1.0 * _TR * alpha * (*_mdot_soln)(node_in)*dz / _dt;
     829     1792230 :           PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
     830     1792230 :           LibmeshPetscCall(
     831             :               VecSetValues(_amc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
     832             :         }
     833             :         else
     834             :         {
     835    17449260 :           PetscInt row_tt = i_ch + _n_channels * iz_ind;
     836    17449260 :           PetscInt col_tt = i_ch + _n_channels * (iz_ind - 1);
     837    17449260 :           PetscScalar value_tt = _TR * alpha * dz / _dt;
     838    17449260 :           LibmeshPetscCall(MatSetValues(
     839             :               _amc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
     840             :         }
     841             : 
     842             :         // Adding diagonal elements
     843    19241490 :         PetscInt row_tt = i_ch + _n_channels * iz_ind;
     844    19241490 :         PetscInt col_tt = i_ch + _n_channels * iz_ind;
     845    19241490 :         PetscScalar value_tt = _TR * (1.0 - alpha) * dz / _dt;
     846    19241490 :         LibmeshPetscCall(MatSetValues(
     847             :             _amc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
     848             : 
     849             :         // Adding RHS elements
     850             :         PetscScalar mdot_old_interp =
     851    19241490 :             computeInterpolatedValue(_mdot_soln->old(node_out), _mdot_soln->old(node_in), Pe);
     852    19241490 :         PetscScalar value_vec_tt = _TR * mdot_old_interp * dz / _dt;
     853    19241490 :         PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
     854    19241490 :         LibmeshPetscCall(
     855             :             VecSetValues(_amc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
     856             : 
     857             :         /// Advective derivative term
     858    19241490 :         if (iz == first_node)
     859             :         {
     860     1792230 :           PetscScalar value_vec_at = Utility::pow<2>((*_mdot_soln)(node_in)) / (S_in * rho_in);
     861     1792230 :           PetscInt row_vec_at = i_ch + _n_channels * iz_ind;
     862     1792230 :           LibmeshPetscCall(VecSetValues(
     863             :               _amc_advective_derivative_rhs, 1, &row_vec_at, &value_vec_at, ADD_VALUES));
     864             :         }
     865             :         else
     866             :         {
     867    17449260 :           PetscInt row_at = i_ch + _n_channels * iz_ind;
     868    17449260 :           PetscInt col_at = i_ch + _n_channels * (iz_ind - 1);
     869    17449260 :           PetscScalar value_at = -1.0 * std::abs((*_mdot_soln)(node_in)) / (S_in * rho_in);
     870    17449260 :           LibmeshPetscCall(MatSetValues(
     871             :               _amc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
     872             :         }
     873             : 
     874             :         // Adding diagonal elements
     875    19241490 :         PetscInt row_at = i_ch + _n_channels * iz_ind;
     876    19241490 :         PetscInt col_at = i_ch + _n_channels * iz_ind;
     877    19241490 :         PetscScalar value_at = std::abs((*_mdot_soln)(node_out)) / (S_out * rho_out);
     878    19241490 :         LibmeshPetscCall(MatSetValues(
     879             :             _amc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
     880             : 
     881             :         /// Cross derivative term
     882             :         unsigned int counter = 0;
     883             :         unsigned int cross_index = iz; // iz-1;
     884    81546090 :         for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
     885             :         {
     886    62304600 :           auto chans = _subchannel_mesh.getGapChannels(i_gap);
     887             :           unsigned int ii_ch = chans.first;
     888             :           unsigned int jj_ch = chans.second;
     889    62304600 :           auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
     890    62304600 :           auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
     891    62304600 :           auto * node_out_i = _subchannel_mesh.getChannelNode(ii_ch, iz);
     892    62304600 :           auto * node_out_j = _subchannel_mesh.getChannelNode(jj_ch, iz);
     893             :           auto rho_i =
     894    62304600 :               computeInterpolatedValue((*_rho_soln)(node_out_i), (*_rho_soln)(node_in_i), Pe);
     895             :           auto rho_j =
     896    62304600 :               computeInterpolatedValue((*_rho_soln)(node_out_j), (*_rho_soln)(node_in_j), Pe);
     897             :           auto S_i =
     898    62304600 :               computeInterpolatedValue((*_S_flow_soln)(node_out_i), (*_S_flow_soln)(node_in_i), Pe);
     899             :           auto S_j =
     900    62304600 :               computeInterpolatedValue((*_S_flow_soln)(node_out_j), (*_S_flow_soln)(node_in_j), Pe);
     901             :           auto u_star = 0.0;
     902             :           // figure out donor axial velocity
     903    62304600 :           if (_Wij(i_gap, cross_index) > 0.0)
     904             :           {
     905    33203602 :             if (iz == first_node)
     906             :             {
     907     3126014 :               u_star = (*_mdot_soln)(node_in_i) / S_i / rho_i;
     908     6252028 :               PetscScalar value_vec_ct = -1.0 * alpha *
     909     3126014 :                                          _subchannel_mesh.getCrossflowSign(i_ch, counter) *
     910     3126014 :                                          _Wij(i_gap, cross_index) * u_star;
     911     3126014 :               PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
     912     3126014 :               LibmeshPetscCall(VecSetValues(
     913             :                   _amc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
     914             :             }
     915             :             else
     916             :             {
     917    30077588 :               PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
     918    30077588 :                                      _Wij(i_gap, cross_index) / S_i / rho_i;
     919    30077588 :               PetscInt row_ct = i_ch + _n_channels * iz_ind;
     920    30077588 :               PetscInt col_ct = ii_ch + _n_channels * (iz_ind - 1);
     921    30077588 :               LibmeshPetscCall(MatSetValues(
     922             :                   _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
     923             :             }
     924    33203602 :             PetscScalar value_ct = (1.0 - alpha) *
     925    33203602 :                                    _subchannel_mesh.getCrossflowSign(i_ch, counter) *
     926    33203602 :                                    _Wij(i_gap, cross_index) / S_i / rho_i;
     927    33203602 :             PetscInt row_ct = i_ch + _n_channels * iz_ind;
     928    33203602 :             PetscInt col_ct = ii_ch + _n_channels * iz_ind;
     929    33203602 :             LibmeshPetscCall(MatSetValues(
     930             :                 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
     931             :           }
     932    29100998 :           else if (_Wij(i_gap, cross_index) < 0.0) // _Wij=0 operations not necessary
     933             :           {
     934    28330986 :             if (iz == first_node)
     935             :             {
     936     2681034 :               u_star = (*_mdot_soln)(node_in_j) / S_j / rho_j;
     937     5362068 :               PetscScalar value_vec_ct = -1.0 * alpha *
     938     2681034 :                                          _subchannel_mesh.getCrossflowSign(i_ch, counter) *
     939     2681034 :                                          _Wij(i_gap, cross_index) * u_star;
     940     2681034 :               PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
     941     2681034 :               LibmeshPetscCall(VecSetValues(
     942             :                   _amc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
     943             :             }
     944             :             else
     945             :             {
     946    25649952 :               PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
     947    25649952 :                                      _Wij(i_gap, cross_index) / S_j / rho_j;
     948    25649952 :               PetscInt row_ct = i_ch + _n_channels * iz_ind;
     949    25649952 :               PetscInt col_ct = jj_ch + _n_channels * (iz_ind - 1);
     950    25649952 :               LibmeshPetscCall(MatSetValues(
     951             :                   _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
     952             :             }
     953    28330986 :             PetscScalar value_ct = (1.0 - alpha) *
     954    28330986 :                                    _subchannel_mesh.getCrossflowSign(i_ch, counter) *
     955    28330986 :                                    _Wij(i_gap, cross_index) / S_j / rho_j;
     956    28330986 :             PetscInt row_ct = i_ch + _n_channels * iz_ind;
     957    28330986 :             PetscInt col_ct = jj_ch + _n_channels * iz_ind;
     958    28330986 :             LibmeshPetscCall(MatSetValues(
     959             :                 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
     960             :           }
     961             : 
     962    62304600 :           if (iz == first_node)
     963             :           {
     964     5842140 :             PetscScalar value_vec_ct = -2.0 * alpha * (*_mdot_soln)(node_in)*_CT *
     965     5842140 :                                        _WijPrime(i_gap, cross_index) / (rho_interp * S_interp);
     966     5842140 :             value_vec_ct += alpha * (*_mdot_soln)(node_in_j)*_CT * _WijPrime(i_gap, cross_index) /
     967     5842140 :                             (rho_j * S_j);
     968     5842140 :             value_vec_ct += alpha * (*_mdot_soln)(node_in_i)*_CT * _WijPrime(i_gap, cross_index) /
     969     5842140 :                             (rho_i * S_i);
     970     5842140 :             PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
     971     5842140 :             LibmeshPetscCall(
     972             :                 VecSetValues(_amc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
     973             :           }
     974             :           else
     975             :           {
     976             :             PetscScalar value_center_ct =
     977   112924920 :                 2.0 * alpha * _CT * _WijPrime(i_gap, cross_index) / (rho_interp * S_interp);
     978    56462460 :             PetscInt row_ct = i_ch + _n_channels * iz_ind;
     979    56462460 :             PetscInt col_ct = i_ch + _n_channels * (iz_ind - 1);
     980    56462460 :             LibmeshPetscCall(MatSetValues(
     981             :                 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
     982             : 
     983             :             PetscScalar value_left_ct =
     984    56462460 :                 -1.0 * alpha * _CT * _WijPrime(i_gap, cross_index) / (rho_j * S_j);
     985    56462460 :             row_ct = i_ch + _n_channels * iz_ind;
     986    56462460 :             col_ct = jj_ch + _n_channels * (iz_ind - 1);
     987    56462460 :             LibmeshPetscCall(MatSetValues(
     988             :                 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
     989             : 
     990             :             PetscScalar value_right_ct =
     991    56462460 :                 -1.0 * alpha * _CT * _WijPrime(i_gap, cross_index) / (rho_i * S_i);
     992    56462460 :             row_ct = i_ch + _n_channels * iz_ind;
     993    56462460 :             col_ct = ii_ch + _n_channels * (iz_ind - 1);
     994    56462460 :             LibmeshPetscCall(MatSetValues(
     995             :                 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
     996             :           }
     997             : 
     998             :           PetscScalar value_center_ct =
     999    62304600 :               2.0 * (1.0 - alpha) * _CT * _WijPrime(i_gap, cross_index) / (rho_interp * S_interp);
    1000    62304600 :           PetscInt row_ct = i_ch + _n_channels * iz_ind;
    1001    62304600 :           PetscInt col_ct = i_ch + _n_channels * iz_ind;
    1002    62304600 :           LibmeshPetscCall(MatSetValues(
    1003             :               _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
    1004             : 
    1005             :           PetscScalar value_left_ct =
    1006    62304600 :               -1.0 * (1.0 - alpha) * _CT * _WijPrime(i_gap, cross_index) / (rho_j * S_j);
    1007    62304600 :           row_ct = i_ch + _n_channels * iz_ind;
    1008    62304600 :           col_ct = jj_ch + _n_channels * iz_ind;
    1009    62304600 :           LibmeshPetscCall(MatSetValues(
    1010             :               _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
    1011             : 
    1012             :           PetscScalar value_right_ct =
    1013    62304600 :               -1.0 * (1.0 - alpha) * _CT * _WijPrime(i_gap, cross_index) / (rho_i * S_i);
    1014    62304600 :           row_ct = i_ch + _n_channels * iz_ind;
    1015    62304600 :           col_ct = ii_ch + _n_channels * iz_ind;
    1016    62304600 :           LibmeshPetscCall(MatSetValues(
    1017             :               _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
    1018    62304600 :           counter++;
    1019             :         }
    1020             : 
    1021             :         /// Friction term
    1022             :         PetscScalar mdot_interp =
    1023    19241490 :             computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), Pe);
    1024    19241490 :         auto Re = ((mdot_interp / S_interp) * Dh_i / mu_interp);
    1025    19241490 :         _friction_args = FrictionStruct(i_ch, Re, S_interp, w_perim_interp);
    1026    19241490 :         Real ff = _friction_closure->computeFrictionFactor(_friction_args);
    1027    19241490 :         if (_ff_soln)
    1028    19219440 :           _ff_soln->set(node_out, ff);
    1029             :         /// Upwind local form loss
    1030             :         auto ki = 0.0;
    1031    19241490 :         if ((*_mdot_soln)(node_out) >= 0)
    1032    19241490 :           ki = k_grid[i_ch][iz - 1];
    1033             :         else
    1034           0 :           ki = k_grid[i_ch][iz];
    1035    19241490 :         auto coef = (ff * dz / Dh_i + ki) * 0.5 * std::abs((*_mdot_soln)(node_out)) /
    1036    19241490 :                     (S_interp * rho_interp);
    1037    19241490 :         if (iz == first_node)
    1038             :         {
    1039     1792230 :           PetscScalar value_vec = -1.0 * alpha * coef * (*_mdot_soln)(node_in);
    1040     1792230 :           PetscInt row_vec = i_ch + _n_channels * iz_ind;
    1041     1792230 :           LibmeshPetscCall(
    1042             :               VecSetValues(_amc_friction_force_rhs, 1, &row_vec, &value_vec, ADD_VALUES));
    1043             :         }
    1044             :         else
    1045             :         {
    1046    17449260 :           PetscInt row = i_ch + _n_channels * iz_ind;
    1047    17449260 :           PetscInt col = i_ch + _n_channels * (iz_ind - 1);
    1048    17449260 :           PetscScalar value = alpha * coef;
    1049    17449260 :           LibmeshPetscCall(
    1050             :               MatSetValues(_amc_friction_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
    1051             :         }
    1052             : 
    1053             :         // Adding diagonal elements
    1054    19241490 :         PetscInt row = i_ch + _n_channels * iz_ind;
    1055    19241490 :         PetscInt col = i_ch + _n_channels * iz_ind;
    1056    19241490 :         PetscScalar value = (1.0 - alpha) * coef;
    1057    19241490 :         LibmeshPetscCall(
    1058             :             MatSetValues(_amc_friction_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
    1059             : 
    1060             :         /// Gravity force
    1061    19241490 :         PetscScalar value_vec = _dir_grav * -1.0 * _g_grav * rho_interp * dz * S_interp;
    1062    19241490 :         PetscInt row_vec = i_ch + _n_channels * iz_ind;
    1063    19241490 :         LibmeshPetscCall(VecSetValues(_amc_gravity_rhs, 1, &row_vec, &value_vec, ADD_VALUES));
    1064             :       }
    1065      494205 :     }
    1066             :     /// Assembling system
    1067       47549 :     LibmeshPetscCall(MatZeroEntries(_amc_sys_mdot_mat));
    1068       47549 :     LibmeshPetscCall(VecZeroEntries(_amc_sys_mdot_rhs));
    1069       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
    1070       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
    1071       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
    1072       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
    1073       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
    1074       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
    1075       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_friction_force_mat, MAT_FINAL_ASSEMBLY));
    1076       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_friction_force_mat, MAT_FINAL_ASSEMBLY));
    1077       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1078       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1079             :     // Matrix
    1080             : #if !PETSC_VERSION_LESS_THAN(3, 15, 0)
    1081       47549 :     LibmeshPetscCall(
    1082             :         MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_time_derivative_mat, UNKNOWN_NONZERO_PATTERN));
    1083       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1084       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1085       47549 :     LibmeshPetscCall(
    1086             :         MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_advective_derivative_mat, UNKNOWN_NONZERO_PATTERN));
    1087       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1088       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1089       47549 :     LibmeshPetscCall(
    1090             :         MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_cross_derivative_mat, UNKNOWN_NONZERO_PATTERN));
    1091       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1092       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1093       47549 :     LibmeshPetscCall(
    1094             :         MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_friction_force_mat, UNKNOWN_NONZERO_PATTERN));
    1095             : #else
    1096             :     LibmeshPetscCall(
    1097             :         MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_time_derivative_mat, DIFFERENT_NONZERO_PATTERN));
    1098             :     LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1099             :     LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1100             :     LibmeshPetscCall(
    1101             :         MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_advective_derivative_mat, DIFFERENT_NONZERO_PATTERN));
    1102             :     LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1103             :     LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1104             :     LibmeshPetscCall(
    1105             :         MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_cross_derivative_mat, DIFFERENT_NONZERO_PATTERN));
    1106             :     LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1107             :     LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1108             :     LibmeshPetscCall(
    1109             :         MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_friction_force_mat, DIFFERENT_NONZERO_PATTERN));
    1110             : #endif
    1111       47549 :     LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1112       47549 :     LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
    1113             :     // RHS
    1114       47549 :     LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_time_derivative_rhs));
    1115       47549 :     LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_advective_derivative_rhs));
    1116       47549 :     LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_cross_derivative_rhs));
    1117       47549 :     LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_friction_force_rhs));
    1118       47549 :     LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_gravity_rhs));
    1119       47549 :     if (_segregated_bool)
    1120             :     {
    1121             :       // Assembly the matrix system
    1122       45662 :       LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
    1123             :           _prod, *_mdot_soln, first_node, last_node, _n_channels));
    1124             :       Vec ls;
    1125       45662 :       LibmeshPetscCall(VecDuplicate(_amc_sys_mdot_rhs, &ls));
    1126       45662 :       LibmeshPetscCall(MatMult(_amc_sys_mdot_mat, _prod, ls));
    1127       45662 :       LibmeshPetscCall(VecAXPY(ls, -1.0, _amc_sys_mdot_rhs));
    1128             :       PetscScalar * xx;
    1129       45662 :       LibmeshPetscCall(VecGetArray(ls, &xx));
    1130      481932 :       for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1131             :       {
    1132      436270 :         auto iz_ind = iz - first_node;
    1133    16264090 :         for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1134             :         {
    1135             :           // Setting nodes
    1136    15827820 :           auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1137    15827820 :           auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
    1138             : 
    1139             :           // inlet, outlet, and interpolated axial surface area
    1140    15827820 :           auto S_in = (*_S_flow_soln)(node_in);
    1141    15827820 :           auto S_out = (*_S_flow_soln)(node_out);
    1142    15827820 :           auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
    1143             : 
    1144             :           // Setting solutions
    1145    15827820 :           if (S_interp != 0)
    1146             :           {
    1147    15827820 :             auto DP = (1 / S_interp) * xx[iz_ind * _n_channels + i_ch];
    1148    15827820 :             _DP(i_ch, iz) = DP;
    1149    15827820 :             if (_DP_soln)
    1150    15827820 :               _DP_soln->set(node_out, DP);
    1151             :           }
    1152             :           else
    1153             :           {
    1154             :             auto DP = 0.0;
    1155           0 :             _DP(i_ch, iz) = DP;
    1156           0 :             if (_DP_soln)
    1157           0 :               _DP_soln->set(node_out, DP);
    1158             :           }
    1159             :         }
    1160             :       }
    1161       45662 :       LibmeshPetscCall(VecDestroy(&ls));
    1162             :     }
    1163             :   }
    1164       65330 : }
    1165             : 
    1166             : void
    1167       65330 : SubChannel1PhaseProblem::computeP(int iblock)
    1168             : {
    1169       65330 :   const unsigned int last_node = (iblock + 1) * _block_size;
    1170       65330 :   const unsigned int first_node = iblock * _block_size + 1;
    1171       65330 :   if (!_implicit_bool)
    1172             :   {
    1173       17781 :     if (!_staggered_pressure_bool)
    1174             :     {
    1175      280275 :       for (unsigned int iz = last_node; iz > first_node - 1; iz--)
    1176             :       {
    1177             :         // Calculate pressure in the inlet of the cell assuming known outlet
    1178     5773390 :         for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1179             :         {
    1180     5507460 :           auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
    1181     5507460 :           auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1182             :           // update Pressure solution
    1183     5507460 :           _P_soln->set(node_in, (*_P_soln)(node_out) + _DP(i_ch, iz));
    1184             :         }
    1185             :       }
    1186             :     }
    1187             :     else
    1188             :     {
    1189       37796 :       for (unsigned int iz = last_node; iz > first_node - 1; iz--)
    1190             :       {
    1191             :         // Calculate pressure in the inlet of the cell assuming known outlet
    1192     1271320 :         for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1193             :         {
    1194     1236960 :           auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
    1195     1236960 :           auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1196             :           // update Pressure solution
    1197             :           // Note: assuming uniform axial discretization in the curren code
    1198             :           // We will need to update this later if we allow non-uniform refinements in the axial
    1199             :           // direction
    1200             :           PetscScalar Pe = 0.5;
    1201     1236960 :           auto alpha = computeInterpolationCoefficients(Pe);
    1202     1236960 :           if (iz == last_node)
    1203             :           {
    1204      123696 :             _P_soln->set(node_in, (*_P_soln)(node_out) + _DP(i_ch, iz) / 2.0);
    1205             :           }
    1206             :           else
    1207             :           {
    1208     1113264 :             _P_soln->set(node_in,
    1209     1113264 :                          (*_P_soln)(node_out) + (1.0 - alpha) * _DP(i_ch, iz) +
    1210     1113264 :                              alpha * _DP(i_ch, iz - 1));
    1211             :           }
    1212             :         }
    1213             :       }
    1214             :     }
    1215             :   }
    1216             :   else
    1217             :   {
    1218       47549 :     if (!_staggered_pressure_bool)
    1219             :     {
    1220       47549 :       LibmeshPetscCall(VecZeroEntries(_amc_pressure_force_rhs));
    1221      541754 :       for (unsigned int iz = last_node; iz > first_node - 1; iz--)
    1222             :       {
    1223      494205 :         auto iz_ind = iz - first_node;
    1224             :         // Calculate pressure in the inlet of the cell assuming known outlet
    1225    19735695 :         for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1226             :         {
    1227    19241490 :           auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
    1228    19241490 :           auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1229             : 
    1230             :           // inlet, outlet, and interpolated axial surface area
    1231    19241490 :           auto S_in = (*_S_flow_soln)(node_in);
    1232    19241490 :           auto S_out = (*_S_flow_soln)(node_out);
    1233    19241490 :           auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
    1234             : 
    1235             :           // Creating matrix of coefficients
    1236    19241490 :           PetscInt row = i_ch + _n_channels * iz_ind;
    1237    19241490 :           PetscInt col = i_ch + _n_channels * iz_ind;
    1238    19241490 :           PetscScalar value = -1.0 * S_interp;
    1239    19241490 :           LibmeshPetscCall(
    1240             :               MatSetValues(_amc_pressure_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
    1241             : 
    1242    19241490 :           if (iz == last_node)
    1243             :           {
    1244     1792230 :             PetscScalar value = -1.0 * (*_P_soln)(node_out)*S_interp;
    1245     1792230 :             PetscInt row = i_ch + _n_channels * iz_ind;
    1246     1792230 :             LibmeshPetscCall(VecSetValues(_amc_pressure_force_rhs, 1, &row, &value, ADD_VALUES));
    1247             :           }
    1248             :           else
    1249             :           {
    1250    17449260 :             PetscInt row = i_ch + _n_channels * iz_ind;
    1251    17449260 :             PetscInt col = i_ch + _n_channels * (iz_ind + 1);
    1252    17449260 :             PetscScalar value = 1.0 * S_interp;
    1253    17449260 :             LibmeshPetscCall(
    1254             :                 MatSetValues(_amc_pressure_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
    1255             :           }
    1256             : 
    1257    19241490 :           if (_segregated_bool)
    1258             :           {
    1259    15827820 :             auto dp_out = _DP(i_ch, iz);
    1260    15827820 :             PetscScalar value_v = -1.0 * dp_out * S_interp;
    1261    15827820 :             PetscInt row_v = i_ch + _n_channels * iz_ind;
    1262    15827820 :             LibmeshPetscCall(
    1263             :                 VecSetValues(_amc_pressure_force_rhs, 1, &row_v, &value_v, ADD_VALUES));
    1264             :           }
    1265             :         }
    1266             :       }
    1267             :       // Solving pressure problem
    1268       47549 :       LibmeshPetscCall(MatAssemblyBegin(_amc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
    1269       47549 :       LibmeshPetscCall(MatAssemblyEnd(_amc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
    1270       47549 :       if (_segregated_bool)
    1271             :       {
    1272             :         KSP ksploc;
    1273             :         PC pc;
    1274             :         Vec sol;
    1275       45662 :         LibmeshPetscCall(VecDuplicate(_amc_pressure_force_rhs, &sol));
    1276       45662 :         LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksploc));
    1277       45662 :         LibmeshPetscCall(KSPSetOperators(ksploc, _amc_pressure_force_mat, _amc_pressure_force_mat));
    1278       45662 :         LibmeshPetscCall(KSPGetPC(ksploc, &pc));
    1279       45662 :         LibmeshPetscCall(PCSetType(pc, PCJACOBI));
    1280       45662 :         LibmeshPetscCall(KSPSetTolerances(ksploc, _rtol, _atol, _dtol, _maxit));
    1281       45662 :         LibmeshPetscCall(KSPSetFromOptions(ksploc));
    1282       45662 :         LibmeshPetscCall(KSPSolve(ksploc, _amc_pressure_force_rhs, sol));
    1283             :         PetscScalar * xx;
    1284       45662 :         LibmeshPetscCall(VecGetArray(sol, &xx));
    1285             :         // update Pressure solution
    1286      481932 :         for (unsigned int iz = last_node; iz > first_node - 1; iz--)
    1287             :         {
    1288      436270 :           auto iz_ind = iz - first_node;
    1289    16264090 :           for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1290             :           {
    1291    15827820 :             auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1292    15827820 :             PetscScalar value = xx[iz_ind * _n_channels + i_ch];
    1293    15827820 :             _P_soln->set(node_in, value);
    1294             :           }
    1295             :         }
    1296       45662 :         LibmeshPetscCall(VecZeroEntries(_amc_pressure_force_rhs));
    1297       45662 :         LibmeshPetscCall(KSPDestroy(&ksploc));
    1298       45662 :         LibmeshPetscCall(VecDestroy(&sol));
    1299             :       }
    1300             :     }
    1301             :     else
    1302             :     {
    1303           0 :       LibmeshPetscCall(VecZeroEntries(_amc_pressure_force_rhs));
    1304           0 :       for (unsigned int iz = last_node; iz > first_node - 1; iz--)
    1305             :       {
    1306           0 :         auto iz_ind = iz - first_node;
    1307             :         // Calculate pressure in the inlet of the cell assuming known outlet
    1308           0 :         for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1309             :         {
    1310           0 :           auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
    1311           0 :           auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1312             : 
    1313             :           // inlet, outlet, and interpolated axial surface area
    1314           0 :           auto S_in = (*_S_flow_soln)(node_in);
    1315           0 :           auto S_out = (*_S_flow_soln)(node_out);
    1316           0 :           auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
    1317             : 
    1318             :           // Creating matrix of coefficients
    1319           0 :           PetscInt row = i_ch + _n_channels * iz_ind;
    1320           0 :           PetscInt col = i_ch + _n_channels * iz_ind;
    1321           0 :           PetscScalar value = -1.0 * S_interp;
    1322           0 :           LibmeshPetscCall(
    1323             :               MatSetValues(_amc_pressure_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
    1324             : 
    1325           0 :           if (iz == last_node)
    1326             :           {
    1327           0 :             PetscScalar value = -1.0 * (*_P_soln)(node_out)*S_interp;
    1328           0 :             PetscInt row = i_ch + _n_channels * iz_ind;
    1329           0 :             LibmeshPetscCall(VecSetValues(_amc_pressure_force_rhs, 1, &row, &value, ADD_VALUES));
    1330             : 
    1331           0 :             auto dp_out = _DP(i_ch, iz);
    1332           0 :             PetscScalar value_v = -1.0 * dp_out / 2.0 * S_interp;
    1333           0 :             PetscInt row_v = i_ch + _n_channels * iz_ind;
    1334           0 :             LibmeshPetscCall(
    1335             :                 VecSetValues(_amc_pressure_force_rhs, 1, &row_v, &value_v, ADD_VALUES));
    1336             :           }
    1337             :           else
    1338             :           {
    1339           0 :             PetscInt row = i_ch + _n_channels * iz_ind;
    1340           0 :             PetscInt col = i_ch + _n_channels * (iz_ind + 1);
    1341           0 :             PetscScalar value = 1.0 * S_interp;
    1342           0 :             LibmeshPetscCall(
    1343             :                 MatSetValues(_amc_pressure_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
    1344             : 
    1345           0 :             if (_segregated_bool)
    1346             :             {
    1347           0 :               auto dp_in = _DP(i_ch, iz - 1);
    1348           0 :               auto dp_out = _DP(i_ch, iz);
    1349           0 :               auto dp_interp = computeInterpolatedValue(dp_out, dp_in, 0.5);
    1350           0 :               PetscScalar value_v = -1.0 * dp_interp * S_interp;
    1351           0 :               PetscInt row_v = i_ch + _n_channels * iz_ind;
    1352           0 :               LibmeshPetscCall(
    1353             :                   VecSetValues(_amc_pressure_force_rhs, 1, &row_v, &value_v, ADD_VALUES));
    1354             :             }
    1355             :           }
    1356             :         }
    1357             :       }
    1358             :       // Solving pressure problem
    1359           0 :       LibmeshPetscCall(MatAssemblyBegin(_amc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
    1360           0 :       LibmeshPetscCall(MatAssemblyEnd(_amc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
    1361           0 :       if (_verbose_subchannel)
    1362           0 :         _console << "Block: " << iblock << " - Axial momentum pressure force matrix assembled"
    1363           0 :                  << std::endl;
    1364             : 
    1365           0 :       if (_segregated_bool)
    1366             :       {
    1367             :         KSP ksploc;
    1368             :         PC pc;
    1369             :         Vec sol;
    1370           0 :         LibmeshPetscCall(VecDuplicate(_amc_pressure_force_rhs, &sol));
    1371           0 :         LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksploc));
    1372           0 :         LibmeshPetscCall(KSPSetOperators(ksploc, _amc_pressure_force_mat, _amc_pressure_force_mat));
    1373           0 :         LibmeshPetscCall(KSPGetPC(ksploc, &pc));
    1374           0 :         LibmeshPetscCall(PCSetType(pc, PCJACOBI));
    1375           0 :         LibmeshPetscCall(KSPSetTolerances(ksploc, _rtol, _atol, _dtol, _maxit));
    1376           0 :         LibmeshPetscCall(KSPSetFromOptions(ksploc));
    1377           0 :         LibmeshPetscCall(KSPSolve(ksploc, _amc_pressure_force_rhs, sol));
    1378             :         PetscScalar * xx;
    1379           0 :         LibmeshPetscCall(VecGetArray(sol, &xx));
    1380             :         // update Pressure solution
    1381           0 :         for (unsigned int iz = last_node; iz > first_node - 1; iz--)
    1382             :         {
    1383           0 :           auto iz_ind = iz - first_node;
    1384           0 :           for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1385             :           {
    1386           0 :             auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1387           0 :             PetscScalar value = xx[iz_ind * _n_channels + i_ch];
    1388           0 :             _P_soln->set(node_in, value);
    1389             :           }
    1390             :         }
    1391           0 :         LibmeshPetscCall(VecZeroEntries(_amc_pressure_force_rhs));
    1392           0 :         LibmeshPetscCall(KSPDestroy(&ksploc));
    1393           0 :         LibmeshPetscCall(VecDestroy(&sol));
    1394             :       }
    1395             :     }
    1396             :   }
    1397       65330 : }
    1398             : 
    1399             : void
    1400        2004 : SubChannel1PhaseProblem::computeT(int iblock)
    1401             : {
    1402        2004 :   const unsigned int last_node = (iblock + 1) * _block_size;
    1403        2004 :   const unsigned int first_node = iblock * _block_size + 1;
    1404       64379 :   for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1405             :   {
    1406     3465545 :     for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1407             :     {
    1408     3403170 :       auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
    1409     3403170 :       _T_soln->set(node, _fp->T_from_p_h((*_P_soln)(node) + _P_out, (*_h_soln)(node)));
    1410             :     }
    1411             :   }
    1412        2004 : }
    1413             : 
    1414             : void
    1415        2180 : SubChannel1PhaseProblem::computeRho(int iblock)
    1416             : {
    1417        2180 :   const unsigned int last_node = (iblock + 1) * _block_size;
    1418        2180 :   const unsigned int first_node = iblock * _block_size + 1;
    1419        2180 :   if (iblock == 0)
    1420             :   {
    1421      128357 :     for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1422             :     {
    1423      126177 :       auto * node = _subchannel_mesh.getChannelNode(i_ch, 0);
    1424      126177 :       _rho_soln->set(node, _fp->rho_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node)));
    1425             :     }
    1426             :   }
    1427       70165 :   for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1428             :   {
    1429     3595115 :     for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1430             :     {
    1431     3527130 :       auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
    1432     3527130 :       _rho_soln->set(node, _fp->rho_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node)));
    1433             :     }
    1434             :   }
    1435        2180 : }
    1436             : 
    1437             : void
    1438        2180 : SubChannel1PhaseProblem::computeMu(int iblock)
    1439             : {
    1440        2180 :   const unsigned int last_node = (iblock + 1) * _block_size;
    1441        2180 :   const unsigned int first_node = iblock * _block_size + 1;
    1442        2180 :   if (iblock == 0)
    1443             :   {
    1444      128357 :     for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1445             :     {
    1446      126177 :       auto * node = _subchannel_mesh.getChannelNode(i_ch, 0);
    1447      126177 :       _mu_soln->set(node, _fp->mu_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node)));
    1448             :     }
    1449             :   }
    1450       70165 :   for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1451             :   {
    1452     3595115 :     for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    1453             :     {
    1454     3527130 :       auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
    1455     3527130 :       _mu_soln->set(node, _fp->mu_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node)));
    1456             :     }
    1457             :   }
    1458        2180 : }
    1459             : 
    1460             : void
    1461       65330 : SubChannel1PhaseProblem::computeWijResidual(int iblock)
    1462             : {
    1463       65330 :   const unsigned int last_node = (iblock + 1) * _block_size;
    1464       65330 :   const unsigned int first_node = iblock * _block_size + 1;
    1465             :   // Cross flow residual
    1466       65330 :   if (!_implicit_bool)
    1467             :   {
    1468       17781 :     const Real & pitch = _subchannel_mesh.getPitch();
    1469      318071 :     for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1470             :     {
    1471      300290 :       auto dz = _z_grid[iz] - _z_grid[iz - 1];
    1472    10962650 :       for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
    1473             :       {
    1474    10662360 :         auto chans = _subchannel_mesh.getGapChannels(i_gap);
    1475             :         unsigned int i_ch = chans.first;
    1476             :         unsigned int j_ch = chans.second;
    1477    10662360 :         auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1478    10662360 :         auto * node_out_i = _subchannel_mesh.getChannelNode(i_ch, iz);
    1479    10662360 :         auto * node_in_j = _subchannel_mesh.getChannelNode(j_ch, iz - 1);
    1480    10662360 :         auto * node_out_j = _subchannel_mesh.getChannelNode(j_ch, iz);
    1481    10662360 :         auto rho_i = (*_rho_soln)(node_in_i);
    1482    10662360 :         auto rho_j = (*_rho_soln)(node_in_j);
    1483    10662360 :         auto Si = (*_S_flow_soln)(node_in_i);
    1484    10662360 :         auto Sj = (*_S_flow_soln)(node_in_j);
    1485    10662360 :         auto Sij = dz * _subchannel_mesh.getGapWidth(iz, i_gap);
    1486    10662360 :         auto Lij = pitch;
    1487             :         // total local form loss in the ij direction
    1488    10662360 :         auto friction_term = _kij * _Wij(i_gap, iz) * std::abs(_Wij(i_gap, iz));
    1489    10662360 :         auto DPij = (*_P_soln)(node_in_i) - (*_P_soln)(node_in_j);
    1490             :         // Figure out donor cell density
    1491             :         auto rho_star = 0.0;
    1492    10662360 :         if (_Wij(i_gap, iz) > 0.0)
    1493             :           rho_star = rho_i;
    1494     5554897 :         else if (_Wij(i_gap, iz) < 0.0)
    1495             :           rho_star = rho_j;
    1496             :         else
    1497      891384 :           rho_star = (rho_i + rho_j) / 2.0;
    1498             :         auto mass_term_out =
    1499    10662360 :             (*_mdot_soln)(node_out_i) / (*_S_flow_soln)(node_out_i) / (*_rho_soln)(node_out_i) +
    1500    10662360 :             (*_mdot_soln)(node_out_j) / (*_S_flow_soln)(node_out_j) / (*_rho_soln)(node_out_j);
    1501             :         auto mass_term_in =
    1502    10662360 :             (*_mdot_soln)(node_in_i) / Si / rho_i + (*_mdot_soln)(node_in_j) / Sj / rho_j;
    1503    10662360 :         auto term_out = Sij * rho_star * (Lij / dz) * mass_term_out * _Wij(i_gap, iz);
    1504    10662360 :         auto term_in = Sij * rho_star * (Lij / dz) * mass_term_in * _Wij(i_gap, iz - 1);
    1505    10662360 :         auto inertia_term = term_out - term_in;
    1506    10662360 :         auto pressure_term = 2 * Utility::pow<2>(Sij) * DPij * rho_star;
    1507             :         auto time_term =
    1508    10662360 :             _TR * 2.0 * (_Wij(i_gap, iz) - _Wij_old(i_gap, iz)) * Lij * Sij * rho_star / _dt;
    1509             : 
    1510    10662360 :         _Wij_residual_matrix(i_gap, iz - 1 - iblock * _block_size) =
    1511    10662360 :             time_term + friction_term + inertia_term - pressure_term;
    1512             :       }
    1513             :     }
    1514             :   }
    1515             :   else
    1516             :   {
    1517             :     // Initializing to zero the elements of the lateral momentum assembly
    1518       47549 :     LibmeshPetscCall(MatZeroEntries(_cmc_time_derivative_mat));
    1519       47549 :     LibmeshPetscCall(MatZeroEntries(_cmc_advective_derivative_mat));
    1520       47549 :     LibmeshPetscCall(MatZeroEntries(_cmc_friction_force_mat));
    1521       47549 :     LibmeshPetscCall(MatZeroEntries(_cmc_pressure_force_mat));
    1522       47549 :     LibmeshPetscCall(VecZeroEntries(_cmc_time_derivative_rhs));
    1523       47549 :     LibmeshPetscCall(VecZeroEntries(_cmc_advective_derivative_rhs));
    1524       47549 :     LibmeshPetscCall(VecZeroEntries(_cmc_friction_force_rhs));
    1525       47549 :     LibmeshPetscCall(VecZeroEntries(_cmc_pressure_force_rhs));
    1526       47549 :     LibmeshPetscCall(MatZeroEntries(_cmc_sys_Wij_mat));
    1527       47549 :     LibmeshPetscCall(VecZeroEntries(_cmc_sys_Wij_rhs));
    1528       47549 :     const Real & pitch = _subchannel_mesh.getPitch();
    1529      541754 :     for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1530             :     {
    1531      494205 :       auto dz = _z_grid[iz] - _z_grid[iz - 1];
    1532      494205 :       auto iz_ind = iz - first_node;
    1533    31646505 :       for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
    1534             :       {
    1535    31152300 :         auto chans = _subchannel_mesh.getGapChannels(i_gap);
    1536             :         unsigned int i_ch = chans.first;
    1537             :         unsigned int j_ch = chans.second;
    1538    31152300 :         auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1539    31152300 :         auto * node_out_i = _subchannel_mesh.getChannelNode(i_ch, iz);
    1540    31152300 :         auto * node_in_j = _subchannel_mesh.getChannelNode(j_ch, iz - 1);
    1541    31152300 :         auto * node_out_j = _subchannel_mesh.getChannelNode(j_ch, iz);
    1542             : 
    1543             :         // inlet, outlet, and interpolated densities
    1544    31152300 :         auto rho_i_in = (*_rho_soln)(node_in_i);
    1545    31152300 :         auto rho_i_out = (*_rho_soln)(node_out_i);
    1546    31152300 :         auto rho_i_interp = computeInterpolatedValue(rho_i_out, rho_i_in, 0.5);
    1547    31152300 :         auto rho_j_in = (*_rho_soln)(node_in_j);
    1548    31152300 :         auto rho_j_out = (*_rho_soln)(node_out_j);
    1549    31152300 :         auto rho_j_interp = computeInterpolatedValue(rho_j_out, rho_j_in, 0.5);
    1550             : 
    1551             :         // inlet, outlet, and interpolated areas
    1552    31152300 :         auto S_i_in = (*_S_flow_soln)(node_in_i);
    1553    31152300 :         auto S_i_out = (*_S_flow_soln)(node_out_i);
    1554    31152300 :         auto S_j_in = (*_S_flow_soln)(node_in_j);
    1555    31152300 :         auto S_j_out = (*_S_flow_soln)(node_out_j);
    1556             : 
    1557             :         // Cross-sectional gap area
    1558    31152300 :         auto Sij = dz * _subchannel_mesh.getGapWidth(iz, i_gap);
    1559    31152300 :         auto Lij = pitch;
    1560             : 
    1561             :         // Figure out donor cell density
    1562             :         auto rho_star = 0.0;
    1563    31152300 :         if (_Wij(i_gap, iz) > 0.0)
    1564             :           rho_star = rho_i_interp;
    1565    14550499 :         else if (_Wij(i_gap, iz) < 0.0)
    1566             :           rho_star = rho_j_interp;
    1567             :         else
    1568      385006 :           rho_star = (rho_i_interp + rho_j_interp) / 2.0;
    1569             : 
    1570             :         // Assembling time derivative
    1571    31152300 :         PetscScalar time_factor = _TR * Lij * Sij * rho_star / _dt;
    1572    31152300 :         PetscInt row_td = i_gap + _n_gaps * iz_ind;
    1573    31152300 :         PetscInt col_td = i_gap + _n_gaps * iz_ind;
    1574    31152300 :         PetscScalar value_td = time_factor;
    1575    31152300 :         LibmeshPetscCall(MatSetValues(
    1576             :             _cmc_time_derivative_mat, 1, &row_td, 1, &col_td, &value_td, INSERT_VALUES));
    1577    31152300 :         PetscScalar value_td_rhs = time_factor * _Wij_old(i_gap, iz);
    1578    31152300 :         LibmeshPetscCall(
    1579             :             VecSetValues(_cmc_time_derivative_rhs, 1, &row_td, &value_td_rhs, INSERT_VALUES));
    1580             : 
    1581             :         // Assembling inertial term
    1582             :         PetscScalar Pe = 0.5;
    1583    31152300 :         auto alpha = computeInterpolationCoefficients(Pe);
    1584    31152300 :         auto mass_term_out = (*_mdot_soln)(node_out_i) / S_i_out / rho_i_out +
    1585    31152300 :                              (*_mdot_soln)(node_out_j) / S_j_out / rho_j_out;
    1586    31152300 :         auto mass_term_in = (*_mdot_soln)(node_in_i) / S_i_in / rho_i_in +
    1587    31152300 :                             (*_mdot_soln)(node_in_j) / S_j_in / rho_j_in;
    1588    31152300 :         auto term_out = Sij * rho_star * (Lij / dz) * mass_term_out / 2.0;
    1589    31152300 :         auto term_in = Sij * rho_star * (Lij / dz) * mass_term_in / 2.0;
    1590    31152300 :         if (iz == first_node)
    1591             :         {
    1592     2921070 :           PetscInt row_ad = i_gap + _n_gaps * iz_ind;
    1593     2921070 :           PetscScalar value_ad = term_in * alpha * _Wij(i_gap, iz - 1);
    1594     2921070 :           LibmeshPetscCall(
    1595             :               VecSetValues(_cmc_advective_derivative_rhs, 1, &row_ad, &value_ad, ADD_VALUES));
    1596             : 
    1597     2921070 :           PetscInt col_ad = i_gap + _n_gaps * iz_ind;
    1598     2921070 :           value_ad = -1.0 * term_in * (1.0 - alpha) + term_out * alpha;
    1599     2921070 :           LibmeshPetscCall(MatSetValues(
    1600             :               _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
    1601             : 
    1602     2921070 :           col_ad = i_gap + _n_gaps * (iz_ind + 1);
    1603     2921070 :           value_ad = term_out * (1.0 - alpha);
    1604     2921070 :           LibmeshPetscCall(MatSetValues(
    1605             :               _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
    1606             :         }
    1607    28231230 :         else if (iz == last_node)
    1608             :         {
    1609     2921070 :           PetscInt row_ad = i_gap + _n_gaps * iz_ind;
    1610     2921070 :           PetscInt col_ad = i_gap + _n_gaps * (iz_ind - 1);
    1611     2921070 :           PetscScalar value_ad = -1.0 * term_in * alpha;
    1612     2921070 :           LibmeshPetscCall(MatSetValues(
    1613             :               _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
    1614             : 
    1615     2921070 :           col_ad = i_gap + _n_gaps * iz_ind;
    1616     2921070 :           value_ad = -1.0 * term_in * (1.0 - alpha) + term_out * alpha;
    1617     2921070 :           LibmeshPetscCall(MatSetValues(
    1618             :               _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
    1619             : 
    1620     2921070 :           value_ad = -1.0 * term_out * (1.0 - alpha) * _Wij(i_gap, iz);
    1621     2921070 :           LibmeshPetscCall(
    1622             :               VecSetValues(_cmc_advective_derivative_rhs, 1, &row_ad, &value_ad, ADD_VALUES));
    1623             :         }
    1624             :         else
    1625             :         {
    1626    25310160 :           PetscInt row_ad = i_gap + _n_gaps * iz_ind;
    1627    25310160 :           PetscInt col_ad = i_gap + _n_gaps * (iz_ind - 1);
    1628    25310160 :           PetscScalar value_ad = -1.0 * term_in * alpha;
    1629    25310160 :           LibmeshPetscCall(MatSetValues(
    1630             :               _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
    1631             : 
    1632    25310160 :           col_ad = i_gap + _n_gaps * iz_ind;
    1633    25310160 :           value_ad = -1.0 * term_in * (1.0 - alpha) + term_out * alpha;
    1634    25310160 :           LibmeshPetscCall(MatSetValues(
    1635             :               _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
    1636             : 
    1637    25310160 :           col_ad = i_gap + _n_gaps * (iz_ind + 1);
    1638    25310160 :           value_ad = term_out * (1.0 - alpha);
    1639    25310160 :           LibmeshPetscCall(MatSetValues(
    1640             :               _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
    1641             :         }
    1642             :         // Assembling friction force
    1643    31152300 :         PetscInt row_ff = i_gap + _n_gaps * iz_ind;
    1644    31152300 :         PetscInt col_ff = i_gap + _n_gaps * iz_ind;
    1645    31152300 :         PetscScalar value_ff = _kij * std::abs(_Wij(i_gap, iz)) / 2.0;
    1646    31152300 :         LibmeshPetscCall(MatSetValues(
    1647             :             _cmc_friction_force_mat, 1, &row_ff, 1, &col_ff, &value_ff, INSERT_VALUES));
    1648             : 
    1649             :         // Assembling pressure force
    1650    31152300 :         alpha = computeInterpolationCoefficients(Pe);
    1651             : 
    1652    31152300 :         if (!_staggered_pressure_bool)
    1653             :         {
    1654    31152300 :           PetscScalar pressure_factor = Utility::pow<2>(Sij) * rho_star;
    1655    31152300 :           PetscInt row_pf = i_gap + _n_gaps * iz_ind;
    1656    31152300 :           PetscInt col_pf = i_ch + _n_channels * iz_ind;
    1657    31152300 :           PetscScalar value_pf = -1.0 * alpha * pressure_factor;
    1658    31152300 :           LibmeshPetscCall(
    1659             :               MatSetValues(_cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
    1660    31152300 :           col_pf = j_ch + _n_channels * iz_ind;
    1661    31152300 :           value_pf = alpha * pressure_factor;
    1662    31152300 :           LibmeshPetscCall(
    1663             :               MatSetValues(_cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
    1664             : 
    1665    31152300 :           if (iz == last_node)
    1666             :           {
    1667     2921070 :             PetscInt row_pf = i_gap + _n_gaps * iz_ind;
    1668     2921070 :             PetscScalar value_pf = (1.0 - alpha) * pressure_factor * (*_P_soln)(node_out_i);
    1669     2921070 :             LibmeshPetscCall(
    1670             :                 VecSetValues(_cmc_pressure_force_rhs, 1, &row_pf, &value_pf, ADD_VALUES));
    1671     2921070 :             value_pf = -1.0 * (1.0 - alpha) * pressure_factor * (*_P_soln)(node_out_j);
    1672     2921070 :             LibmeshPetscCall(
    1673             :                 VecSetValues(_cmc_pressure_force_rhs, 1, &row_pf, &value_pf, ADD_VALUES));
    1674             :           }
    1675             :           else
    1676             :           {
    1677    28231230 :             row_pf = i_gap + _n_gaps * iz_ind;
    1678    28231230 :             col_pf = i_ch + _n_channels * (iz_ind + 1);
    1679    28231230 :             value_pf = -1.0 * (1.0 - alpha) * pressure_factor;
    1680    28231230 :             LibmeshPetscCall(MatSetValues(
    1681             :                 _cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
    1682    28231230 :             col_pf = j_ch + _n_channels * (iz_ind + 1);
    1683    28231230 :             value_pf = (1.0 - alpha) * pressure_factor;
    1684    28231230 :             LibmeshPetscCall(MatSetValues(
    1685             :                 _cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
    1686             :           }
    1687             :         }
    1688             :         else
    1689             :         {
    1690           0 :           PetscScalar pressure_factor = Utility::pow<2>(Sij) * rho_star;
    1691           0 :           PetscInt row_pf = i_gap + _n_gaps * iz_ind;
    1692           0 :           PetscInt col_pf = i_ch + _n_channels * iz_ind;
    1693           0 :           PetscScalar value_pf = -1.0 * pressure_factor;
    1694           0 :           LibmeshPetscCall(
    1695             :               MatSetValues(_cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
    1696           0 :           col_pf = j_ch + _n_channels * iz_ind;
    1697           0 :           value_pf = pressure_factor;
    1698           0 :           LibmeshPetscCall(
    1699             :               MatSetValues(_cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
    1700             :         }
    1701             :       }
    1702             :     }
    1703             :     /// Assembling system
    1704       47549 :     LibmeshPetscCall(MatZeroEntries(_cmc_sys_Wij_mat));
    1705       47549 :     LibmeshPetscCall(VecZeroEntries(_cmc_sys_Wij_rhs));
    1706       47549 :     LibmeshPetscCall(MatAssemblyBegin(_cmc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
    1707       47549 :     LibmeshPetscCall(MatAssemblyEnd(_cmc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
    1708       47549 :     LibmeshPetscCall(MatAssemblyBegin(_cmc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
    1709       47549 :     LibmeshPetscCall(MatAssemblyEnd(_cmc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
    1710       47549 :     LibmeshPetscCall(MatAssemblyBegin(_cmc_friction_force_mat, MAT_FINAL_ASSEMBLY));
    1711       47549 :     LibmeshPetscCall(MatAssemblyEnd(_cmc_friction_force_mat, MAT_FINAL_ASSEMBLY));
    1712       47549 :     LibmeshPetscCall(MatAssemblyBegin(_cmc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
    1713       47549 :     LibmeshPetscCall(MatAssemblyEnd(_cmc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
    1714       47549 :     LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1715       47549 :     LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1716             :     // Matrix
    1717             : #if !PETSC_VERSION_LESS_THAN(3, 15, 0)
    1718       47549 :     LibmeshPetscCall(
    1719             :         MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_time_derivative_mat, UNKNOWN_NONZERO_PATTERN));
    1720       47549 :     LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1721       47549 :     LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1722       47549 :     LibmeshPetscCall(
    1723             :         MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_advective_derivative_mat, UNKNOWN_NONZERO_PATTERN));
    1724       47549 :     LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1725       47549 :     LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1726       47549 :     LibmeshPetscCall(
    1727             :         MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_friction_force_mat, UNKNOWN_NONZERO_PATTERN));
    1728             : #else
    1729             :     LibmeshPetscCall(
    1730             :         MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_time_derivative_mat, DIFFERENT_NONZERO_PATTERN));
    1731             :     LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1732             :     LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1733             :     LibmeshPetscCall(
    1734             :         MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_advective_derivative_mat, DIFFERENT_NONZERO_PATTERN));
    1735             :     LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1736             :     LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1737             :     LibmeshPetscCall(
    1738             :         MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_friction_force_mat, DIFFERENT_NONZERO_PATTERN));
    1739             : #endif
    1740       47549 :     LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1741       47549 :     LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
    1742             :     // RHS
    1743       47549 :     LibmeshPetscCall(VecAXPY(_cmc_sys_Wij_rhs, 1.0, _cmc_time_derivative_rhs));
    1744       47549 :     LibmeshPetscCall(VecAXPY(_cmc_sys_Wij_rhs, 1.0, _cmc_advective_derivative_rhs));
    1745       47549 :     LibmeshPetscCall(VecAXPY(_cmc_sys_Wij_rhs, 1.0, _cmc_friction_force_rhs));
    1746             : 
    1747       47549 :     if (_segregated_bool)
    1748             :     {
    1749             :       // Assembly the matrix system
    1750             :       Vec sol_holder_P;
    1751       45662 :       LibmeshPetscCall(createPetscVector(sol_holder_P, _block_size * _n_gaps));
    1752             :       Vec sol_holder_W;
    1753       45662 :       LibmeshPetscCall(createPetscVector(sol_holder_W, _block_size * _n_gaps));
    1754       45662 :       LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
    1755             :           _prodp, *_P_soln, first_node - 1, last_node - 1, _n_channels));
    1756       45662 :       LibmeshPetscCall(populateVectorFromDense<libMesh::DenseMatrix<Real>>(
    1757             :           _Wij_vec, _Wij, first_node, last_node, _n_gaps));
    1758       45662 :       LibmeshPetscCall(MatMult(_cmc_sys_Wij_mat, _Wij_vec, sol_holder_W));
    1759       45662 :       LibmeshPetscCall(VecAXPY(sol_holder_W, -1.0, _cmc_sys_Wij_rhs));
    1760       45662 :       LibmeshPetscCall(MatMult(_cmc_pressure_force_mat, _prodp, sol_holder_P));
    1761       45662 :       LibmeshPetscCall(VecAXPY(sol_holder_P, -1.0, _cmc_pressure_force_rhs));
    1762       45662 :       LibmeshPetscCall(VecAXPY(sol_holder_W, 1.0, sol_holder_P));
    1763             :       PetscScalar * xx;
    1764       45662 :       LibmeshPetscCall(VecGetArray(sol_holder_W, &xx));
    1765      481932 :       for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1766             :       {
    1767      436270 :         auto iz_ind = iz - first_node;
    1768    26612470 :         for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
    1769             :         {
    1770    26176200 :           _Wij_residual_matrix(i_gap, iz - 1 - iblock * _block_size) = xx[iz_ind * _n_gaps + i_gap];
    1771             :         }
    1772             :       }
    1773       45662 :       LibmeshPetscCall(VecDestroy(&sol_holder_P));
    1774       45662 :       LibmeshPetscCall(VecDestroy(&sol_holder_W));
    1775             :     }
    1776             :   }
    1777       65330 : }
    1778             : 
    1779             : void
    1780       67217 : SubChannel1PhaseProblem::computeWijPrime(int iblock)
    1781             : {
    1782       67217 :   const unsigned int last_node = (iblock + 1) * _block_size;
    1783       67217 :   const unsigned int first_node = iblock * _block_size + 1;
    1784      919647 :   for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1785             :   {
    1786      852430 :     auto dz = _z_grid[iz] - _z_grid[iz - 1];
    1787    47643190 :     for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
    1788             :     {
    1789    46790760 :       auto chans = _subchannel_mesh.getGapChannels(i_gap);
    1790             :       unsigned int i_ch = chans.first;
    1791             :       unsigned int j_ch = chans.second;
    1792    46790760 :       auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    1793    46790760 :       auto * node_out_i = _subchannel_mesh.getChannelNode(i_ch, iz);
    1794    46790760 :       auto * node_in_j = _subchannel_mesh.getChannelNode(j_ch, iz - 1);
    1795    46790760 :       auto * node_out_j = _subchannel_mesh.getChannelNode(j_ch, iz);
    1796    46790760 :       auto Si_in = (*_S_flow_soln)(node_in_i);
    1797    46790760 :       auto Sj_in = (*_S_flow_soln)(node_in_j);
    1798    46790760 :       auto Si_out = (*_S_flow_soln)(node_out_i);
    1799    46790760 :       auto Sj_out = (*_S_flow_soln)(node_out_j);
    1800    46790760 :       auto gap = _subchannel_mesh.getGapWidth(iz, i_gap);
    1801    46790760 :       auto Sij = dz * gap;
    1802             :       auto avg_massflux =
    1803    46790760 :           0.5 * (((*_mdot_soln)(node_in_i) + (*_mdot_soln)(node_in_j)) / (Si_in + Sj_in) +
    1804    46790760 :                  ((*_mdot_soln)(node_out_i) + (*_mdot_soln)(node_out_j)) / (Si_out + Sj_out));
    1805    46790760 :       auto beta = computeMixingParameter(i_gap, iz);
    1806             : 
    1807    46790760 :       if (!_implicit_bool)
    1808             :       {
    1809    10662360 :         _WijPrime(i_gap, iz) = beta * avg_massflux * Sij;
    1810             :       }
    1811             :       else
    1812             :       {
    1813    36128400 :         auto iz_ind = iz - first_node;
    1814    36128400 :         PetscScalar base_value = beta * 0.5 * Sij;
    1815             : 
    1816             :         // Bottom values
    1817    36128400 :         if (iz == first_node)
    1818             :         {
    1819     3102420 :           PetscScalar value_tl = -1.0 * base_value / (Si_in + Sj_in) *
    1820     3102420 :                                  ((*_mdot_soln)(node_in_i) + (*_mdot_soln)(node_in_j));
    1821     3102420 :           PetscInt row = i_gap + _n_gaps * iz_ind;
    1822     3102420 :           LibmeshPetscCall(
    1823             :               VecSetValues(_amc_turbulent_cross_flows_rhs, 1, &row, &value_tl, INSERT_VALUES));
    1824             :         }
    1825             :         else
    1826             :         {
    1827    33025980 :           PetscScalar value_tl = base_value / (Si_in + Sj_in);
    1828    33025980 :           PetscInt row = i_gap + _n_gaps * iz_ind;
    1829             : 
    1830    33025980 :           PetscInt col_ich = i_ch + _n_channels * (iz_ind - 1);
    1831    33025980 :           LibmeshPetscCall(MatSetValues(
    1832             :               _amc_turbulent_cross_flows_mat, 1, &row, 1, &col_ich, &value_tl, INSERT_VALUES));
    1833             : 
    1834    33025980 :           PetscInt col_jch = j_ch + _n_channels * (iz_ind - 1);
    1835    33025980 :           LibmeshPetscCall(MatSetValues(
    1836             :               _amc_turbulent_cross_flows_mat, 1, &row, 1, &col_jch, &value_tl, INSERT_VALUES));
    1837             :         }
    1838             : 
    1839             :         // Top values
    1840    36128400 :         PetscScalar value_bl = base_value / (Si_out + Sj_out);
    1841    36128400 :         PetscInt row = i_gap + _n_gaps * iz_ind;
    1842             : 
    1843    36128400 :         PetscInt col_ich = i_ch + _n_channels * iz_ind;
    1844    36128400 :         LibmeshPetscCall(MatSetValues(
    1845             :             _amc_turbulent_cross_flows_mat, 1, &row, 1, &col_ich, &value_bl, INSERT_VALUES));
    1846             : 
    1847    36128400 :         PetscInt col_jch = j_ch + _n_channels * iz_ind;
    1848    36128400 :         LibmeshPetscCall(MatSetValues(
    1849             :             _amc_turbulent_cross_flows_mat, 1, &row, 1, &col_jch, &value_bl, INSERT_VALUES));
    1850             :       }
    1851             :     }
    1852             :   }
    1853             : 
    1854       67217 :   if (_implicit_bool)
    1855             :   {
    1856       49436 :     LibmeshPetscCall(MatAssemblyBegin(_amc_turbulent_cross_flows_mat, MAT_FINAL_ASSEMBLY));
    1857       49436 :     LibmeshPetscCall(MatAssemblyEnd(_amc_turbulent_cross_flows_mat, MAT_FINAL_ASSEMBLY));
    1858             : 
    1859             :     /// Update turbulent crossflow
    1860             :     Vec loc_prod;
    1861             :     Vec loc_Wij;
    1862       49436 :     LibmeshPetscCall(VecDuplicate(_amc_sys_mdot_rhs, &loc_prod));
    1863       49436 :     LibmeshPetscCall(VecDuplicate(_Wij_vec, &loc_Wij));
    1864       49436 :     LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
    1865             :         loc_prod, *_mdot_soln, first_node, last_node, _n_channels));
    1866       49436 :     LibmeshPetscCall(MatMult(_amc_turbulent_cross_flows_mat, loc_prod, loc_Wij));
    1867       49436 :     LibmeshPetscCall(VecAXPY(loc_Wij, -1.0, _amc_turbulent_cross_flows_rhs));
    1868       49436 :     LibmeshPetscCall(populateDenseFromVector<libMesh::DenseMatrix<Real>>(
    1869             :         loc_Wij, _WijPrime, first_node, last_node, _n_gaps));
    1870       49436 :     LibmeshPetscCall(VecDestroy(&loc_prod));
    1871       49436 :     LibmeshPetscCall(VecDestroy(&loc_Wij));
    1872             :   }
    1873       67217 : }
    1874             : 
    1875             : Real
    1876    46790760 : SubChannel1PhaseProblem::computeMixingParameter(unsigned int i_gap, unsigned int iz) const
    1877             : {
    1878    46790760 :   auto beta = _mixing_closure->computeMixingParameter(i_gap, iz);
    1879    46790760 :   if (!std::isfinite(beta) || beta < 0.0)
    1880           0 :     mooseError(name(),
    1881             :                ": Mixing closure returned invalid beta = ",
    1882             :                beta,
    1883             :                " for gap ",
    1884             :                i_gap,
    1885             :                " at axial index ",
    1886             :                iz,
    1887             :                ". Beta must be finite and non-negative.");
    1888             : 
    1889    46790760 :   return beta;
    1890             : }
    1891             : 
    1892             : Real
    1893     1944300 : SubChannel1PhaseProblem::computeSweepFlowMixingParameter(unsigned int i_gap, unsigned int iz) const
    1894             : {
    1895     1944300 :   auto beta = _mixing_closure->computeSweepFlowMixingParameter(i_gap, iz);
    1896     1944300 :   if (!std::isfinite(beta) || beta < 0.0)
    1897           0 :     mooseError(name(),
    1898             :                ": Mixing closure returned invalid sweep-flow coefficient = ",
    1899             :                beta,
    1900             :                " for gap ",
    1901             :                i_gap,
    1902             :                " at axial index ",
    1903             :                iz,
    1904             :                ". sweep-flow coefficient must be finite and non-negative.");
    1905             : 
    1906     1944300 :   return beta;
    1907             : }
    1908             : 
    1909             : libMesh::DenseVector<Real>
    1910       63443 : SubChannel1PhaseProblem::residualFunction(int iblock, libMesh::DenseVector<Real> solution)
    1911             : {
    1912       63443 :   const unsigned int last_node = (iblock + 1) * _block_size;
    1913       63443 :   const unsigned int first_node = iblock * _block_size + 1;
    1914       63443 :   libMesh::DenseVector<Real> Wij_residual_vector(_n_gaps * _block_size, 0.0);
    1915             :   // Assign the solution to the cross-flow matrix
    1916             :   int i = 0;
    1917      800003 :   for (unsigned int iz = first_node; iz < last_node + 1; iz++)
    1918             :   {
    1919    37575120 :     for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
    1920             :     {
    1921    36838560 :       _Wij(i_gap, iz) = solution(i);
    1922    36838560 :       i++;
    1923             :     }
    1924             :   }
    1925             : 
    1926             :   // Calculating sum of crossflows
    1927       63443 :   computeSumWij(iblock);
    1928             :   // Solving axial flux
    1929       63443 :   computeMdot(iblock);
    1930             :   // Calculation of turbulent Crossflow
    1931       63443 :   computeWijPrime(iblock);
    1932             :   // Solving for Pressure Drop
    1933       63443 :   computeDP(iblock);
    1934             :   // Solving for pressure
    1935       63443 :   computeP(iblock);
    1936             :   // Populating lateral crossflow residual matrix
    1937       63443 :   computeWijResidual(iblock);
    1938             : 
    1939             :   // Turn the residual matrix into a residual vector
    1940      800003 :   for (unsigned int iz = 0; iz < _block_size; iz++)
    1941             :   {
    1942    37575120 :     for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
    1943             :     {
    1944    36838560 :       int i = _n_gaps * iz + i_gap; // column wise transfer
    1945    36838560 :       Wij_residual_vector(i) = _Wij_residual_matrix(i_gap, iz);
    1946             :     }
    1947             :   }
    1948       63443 :   return Wij_residual_vector;
    1949             : }
    1950             : 
    1951             : PetscErrorCode
    1952         303 : SubChannel1PhaseProblem::petscSnesSolver(int iblock,
    1953             :                                          const libMesh::DenseVector<Real> & solution,
    1954             :                                          libMesh::DenseVector<Real> & root)
    1955             : {
    1956             :   SNES snes;
    1957             :   KSP ksp;
    1958             :   PC pc;
    1959             :   Vec x, r;
    1960             :   PetscScalar * xx;
    1961             : 
    1962             :   PetscFunctionBegin;
    1963         303 :   LibmeshPetscCall(SNESCreate(PETSC_COMM_SELF, &snes));
    1964         303 :   LibmeshPetscCall(VecCreate(PETSC_COMM_SELF, &x));
    1965         303 :   LibmeshPetscCall(VecSetSizes(x, PETSC_DECIDE, _block_size * _n_gaps));
    1966         303 :   LibmeshPetscCall(VecSetFromOptions(x));
    1967         303 :   LibmeshPetscCall(VecDuplicate(x, &r));
    1968             : 
    1969             : #if PETSC_VERSION_LESS_THAN(3, 13, 0)
    1970             :   LibmeshPetscCall(PetscOptionsSetValue(PETSC_NULL, "-snes_mf", PETSC_NULL));
    1971             : #else
    1972         303 :   LibmeshPetscCall(SNESSetUseMatrixFree(snes, PETSC_FALSE, PETSC_TRUE));
    1973             : #endif
    1974             :   Ctx ctx;
    1975         303 :   ctx.iblock = iblock;
    1976         303 :   ctx.schp = this;
    1977         303 :   LibmeshPetscCall(SNESSetFunction(snes, r, formFunction, &ctx));
    1978         303 :   LibmeshPetscCall(SNESGetKSP(snes, &ksp));
    1979         303 :   LibmeshPetscCall(KSPGetPC(ksp, &pc));
    1980         303 :   LibmeshPetscCall(PCSetType(pc, PCNONE));
    1981         303 :   LibmeshPetscCall(KSPSetTolerances(ksp, _rtol, _atol, _dtol, _maxit));
    1982         303 :   LibmeshPetscCall(SNESSetFromOptions(snes));
    1983         303 :   LibmeshPetscCall(VecGetArray(x, &xx));
    1984      218643 :   for (unsigned int i = 0; i < _block_size * _n_gaps; i++)
    1985             :   {
    1986      218340 :     xx[i] = solution(i);
    1987             :   }
    1988         303 :   LibmeshPetscCall(VecRestoreArray(x, &xx));
    1989             : 
    1990         303 :   LibmeshPetscCall(SNESSolve(snes, NULL, x));
    1991         303 :   LibmeshPetscCall(VecGetArray(x, &xx));
    1992      218643 :   for (unsigned int i = 0; i < _block_size * _n_gaps; i++)
    1993      218340 :     root(i) = xx[i];
    1994             : 
    1995         303 :   LibmeshPetscCall(VecRestoreArray(x, &xx));
    1996         303 :   LibmeshPetscCall(VecDestroy(&x));
    1997         303 :   LibmeshPetscCall(VecDestroy(&r));
    1998         303 :   LibmeshPetscCall(SNESDestroy(&snes));
    1999         303 :   PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
    2000             : }
    2001             : 
    2002             : PetscErrorCode
    2003        1814 : SubChannel1PhaseProblem::solveAndPopulateEnthalpy(
    2004             :     Mat A, Vec rhs, unsigned int first_node, unsigned int last_node, const char * ksp_prefix)
    2005             : {
    2006             :   PetscFunctionBegin;
    2007             : 
    2008             :   // Create solution vector with rhs layout
    2009        1814 :   Vec x = nullptr;
    2010        1814 :   LibmeshPetscCall(VecDuplicate(rhs, &x));
    2011             : 
    2012             :   // KSP setup
    2013        1814 :   KSP ksp = nullptr;
    2014        1814 :   PC pc = nullptr;
    2015        1814 :   LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksp));
    2016        1814 :   LibmeshPetscCall(KSPSetOperators(ksp, A, A));
    2017        1814 :   LibmeshPetscCall(KSPGetPC(ksp, &pc));
    2018        1814 :   LibmeshPetscCall(PCSetType(pc, PCJACOBI));
    2019        1814 :   LibmeshPetscCall(KSPSetTolerances(ksp, _rtol, _atol, _dtol, _maxit));
    2020        1814 :   if (ksp_prefix && *ksp_prefix)
    2021        1814 :     LibmeshPetscCall(KSPSetOptionsPrefix(ksp, ksp_prefix));
    2022        1814 :   LibmeshPetscCall(KSPSetFromOptions(ksp));
    2023             : 
    2024             :   // Solve
    2025        1814 :   LibmeshPetscCall(KSPSolve(ksp, rhs, x));
    2026             : 
    2027             :   // Scatter to _h_soln with sanity checks
    2028        1814 :   PetscScalar * xx = nullptr;
    2029        1814 :   LibmeshPetscCall(VecGetArray(x, &xx));
    2030       59394 :   for (unsigned int iz = first_node; iz <= last_node; ++iz)
    2031             :   {
    2032       57580 :     const unsigned int iz_ind = iz - first_node;
    2033     3428920 :     for (unsigned int i_ch = 0; i_ch < _n_channels; ++i_ch)
    2034             :     {
    2035     3371340 :       auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
    2036     3371340 :       const PetscScalar h_out = xx[iz_ind * _n_channels + i_ch];
    2037     3371340 :       if (h_out < 0.0)
    2038           0 :         mooseError(
    2039             :             name(), " : Calculation of negative Enthalpy h_out = ", h_out, " Axial Level = ", iz);
    2040     3371340 :       _h_soln->set(node_out, h_out);
    2041             :     }
    2042             :   }
    2043        1814 :   LibmeshPetscCall(VecRestoreArray(x, &xx));
    2044             : 
    2045             :   // Cleanup
    2046        1814 :   LibmeshPetscCall(KSPDestroy(&ksp));
    2047        1814 :   LibmeshPetscCall(VecDestroy(&x));
    2048             : 
    2049        1814 :   PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
    2050             : }
    2051             : 
    2052             : Real
    2053     3211560 : SubChannel1PhaseProblem::computeAddedHeatDuct(unsigned int i_ch, unsigned int iz) const
    2054             : {
    2055             :   mooseAssert(iz > 0, "Trapezoidal rule requires starting at index 1 at least");
    2056     3211560 :   if (_duct_mesh_exist)
    2057             :   {
    2058     1150800 :     auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
    2059     1150800 :     if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
    2060             :     {
    2061      279600 :       auto dz = _z_grid[iz] - _z_grid[iz - 1];
    2062      279600 :       auto * node_in_chan = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    2063      279600 :       auto * node_out_chan = _subchannel_mesh.getChannelNode(i_ch, iz);
    2064      279600 :       auto * node_in_duct = _subchannel_mesh.getDuctNodeFromChannel(node_in_chan);
    2065      279600 :       auto * node_out_duct = _subchannel_mesh.getDuctNodeFromChannel(node_out_chan);
    2066      279600 :       auto heat_rate_in = (*_duct_heat_flux_soln)(node_in_duct);
    2067      279600 :       auto heat_rate_out = (*_duct_heat_flux_soln)(node_out_duct);
    2068      279600 :       auto width = getSubChannelPeripheralDuctWidth(i_ch);
    2069      279600 :       return 0.5 * (heat_rate_in + heat_rate_out) * dz * width;
    2070             :     }
    2071             :     else
    2072             :     {
    2073             :       return 0.0;
    2074             :     }
    2075             :   }
    2076             :   else
    2077             :   {
    2078             :     return 0.0;
    2079             :   }
    2080             : }
    2081             : 
    2082             : PetscErrorCode
    2083        1887 : SubChannel1PhaseProblem::implicitPetscSolve(int iblock)
    2084             : {
    2085             :   PetscFunctionBegin;
    2086             :   // ---------- helper functions -----------------------------
    2087       49062 :   auto V = [&](const std::string & s)
    2088             :   {
    2089       49062 :     if (_verbose_subchannel)
    2090       37648 :       _console << s << std::endl;
    2091       50949 :   };
    2092             : 
    2093       16983 :   auto DupMatAssembled = [&](Mat src, Mat * dst)
    2094             :   {
    2095       16983 :     if (src)
    2096             :     {
    2097       11322 :       LibmeshPetscCall(MatDuplicate(src, MAT_COPY_VALUES, dst));
    2098       11322 :       LibmeshPetscCall(MatAssemblyBegin(*dst, MAT_FINAL_ASSEMBLY));
    2099       11322 :       LibmeshPetscCall(MatAssemblyEnd(*dst, MAT_FINAL_ASSEMBLY));
    2100             :     }
    2101             :     else
    2102        5661 :       *dst = NULL;
    2103       16983 :   };
    2104             : 
    2105        5661 :   auto DupVecCopy = [&](Vec src, Vec * dst)
    2106             :   {
    2107        5661 :     LibmeshPetscCall(VecDuplicate(src, dst));
    2108        5661 :     LibmeshPetscCall(VecCopy(src, *dst));
    2109        5661 :   };
    2110             : 
    2111             :   const PetscInt Q = 3; // [mass conservation, axial momentum, cross momentum]
    2112             : 
    2113             :   // small indexer
    2114       16983 :   auto Idx = [&](PetscInt r, PetscInt c) { return Q * r + c; };
    2115             : 
    2116             :   // arrays that MUST be declared before lambdas use them
    2117        1887 :   std::vector<Mat> mat_array(Q * Q, NULL);
    2118        1887 :   std::vector<Vec> vec_array(Q, NULL);
    2119             : 
    2120             :   // generic assembler for one governing equation row in the nested matrix
    2121        5661 :   auto AssembleEquation = [&](PetscInt f,
    2122             :                               Mat A0,
    2123             :                               Mat A1,
    2124             :                               Mat A2,             // three blocks in row f (can be nullptr)
    2125             :                               Vec rhs,            // base RHS for equation f
    2126             :                               Vec rhs_add,        // optional extra RHS to add (can be nullptr)
    2127             :                               const char * label) // e.g. "Mass", "Lin mom", "Cross mom"
    2128             :   {
    2129        5661 :     DupMatAssembled(A0, &mat_array[Idx(f, 0)]);
    2130        5661 :     DupMatAssembled(A1, &mat_array[Idx(f, 1)]);
    2131        5661 :     DupMatAssembled(A2, &mat_array[Idx(f, 2)]);
    2132        5661 :     DupVecCopy(rhs, &vec_array[f]);
    2133        5661 :     if (rhs_add)
    2134        3774 :       LibmeshPetscCall(VecAXPY(vec_array[f], 1.0, rhs_add));
    2135       11322 :     V(std::string(label) + " system assembled");
    2136        5661 :   };
    2137             : 
    2138             :   // -----------------------------------------------------------------------------
    2139             :   // Helper lambda that applies per-equation under-relaxation by modifying BOTH the
    2140             :   // matrix block and the RHS for that equation.
    2141             :   //
    2142             :   // Specifically, with A_ff for the equation and D = diag(A_ff):
    2143             :   //   1) Matrix diagonal scaling: D <- D / alpha, then A_ff's diagonal is replaced
    2144             :   //      with D. For alpha < 1 this increases diagonal dominance (more damping).
    2145             :   //   2) RHS blending with the previous solution x_old:
    2146             :   //        rhs_f <- rhs_f + (1 - alpha) * (D / alpha) * x_old
    2147             :   //      where x_old is provided by the caller via `populate(work)`.
    2148             :   //
    2149             :   // Net effect: the solved x satisfies
    2150             :   //        A_ff x = rhs_f_original + ((1 - alpha)/alpha) * D * (x_old - x),
    2151             :   // which damps updates toward x_old without changing the converged solution.
    2152             :   // -----------------------------------------------------------------------------
    2153             :   auto RelaxEquation =
    2154        5661 :       [&](Mat A_ff, Vec rhs_f, Vec like_vec, Vec work, PetscScalar alpha, auto && populate)
    2155             :   {
    2156        5661 :     Vec d = nullptr;
    2157        5661 :     LibmeshPetscCall(VecDuplicate(like_vec, &d));
    2158             : 
    2159             :     // 1) A_ff: diag <- diag / alpha
    2160        5661 :     LibmeshPetscCall(MatGetDiagonal(A_ff, d));
    2161        5661 :     LibmeshPetscCall(VecScale(d, 1.0 / alpha));
    2162        5661 :     LibmeshPetscCall(MatDiagonalSet(A_ff, d, INSERT_VALUES));
    2163             : 
    2164             :     // 2) work <- x_old (caller-provided populator)
    2165        5661 :     LibmeshPetscCall(populate(work));
    2166             : 
    2167             :     // 3) rhs_f += (1 - alpha) * (diag .* work)
    2168        5661 :     LibmeshPetscCall(VecScale(d, (1.0 - alpha)));
    2169        5661 :     LibmeshPetscCall(VecPointwiseMult(work, work, d));
    2170        5661 :     LibmeshPetscCall(VecAXPY(rhs_f, 1.0, work));
    2171             : 
    2172        5661 :     LibmeshPetscCall(VecDestroy(&d));
    2173        7548 :   };
    2174             : 
    2175             :   // indices
    2176        1887 :   const unsigned int first_node = iblock * _block_size + 1;
    2177        1887 :   const unsigned int last_node = (iblock + 1) * _block_size;
    2178             : 
    2179             :   // ---------- assemble per-block operators -----------------
    2180        1887 :   computeSumWij(iblock);
    2181        1887 :   computeMdot(iblock);
    2182        1887 :   computeWijPrime(iblock);
    2183        1887 :   computeDP(iblock);
    2184        1887 :   computeP(iblock);
    2185        1887 :   computeWijResidual(iblock);
    2186             : 
    2187        1887 :   V("Starting nested system.");
    2188             : 
    2189             :   // Populate nested matrix with the individual physics
    2190             :   // equation 0: Mass conservation
    2191        1887 :   AssembleEquation(/*f=*/0,
    2192             :                    /*A0=*/_mc_axial_convection_mat,
    2193             :                    /*A1=*/nullptr,
    2194             :                    /*A2=*/_mc_sumWij_mat,
    2195             :                    /*rhs=*/_mc_axial_convection_rhs,
    2196             :                    /*rhs_add=*/nullptr,
    2197             :                    /*label=*/"Mass");
    2198             : 
    2199             :   // equation 1: Axial momentum conservation
    2200        1887 :   AssembleEquation(/*f=*/1,
    2201             :                    /*A0=*/_amc_sys_mdot_mat,
    2202             :                    /*A1=*/_amc_pressure_force_mat,
    2203             :                    /*A2=*/nullptr,
    2204             :                    /*rhs=*/_amc_pressure_force_rhs,
    2205             :                    /*rhs_add=*/_amc_sys_mdot_rhs,
    2206             :                    /*label=*/"Lin mom");
    2207             : 
    2208             :   // equation 2: Cross momentum conservation
    2209        1887 :   AssembleEquation(/*f=*/2,
    2210             :                    /*A0=*/nullptr,
    2211             :                    /*A1=*/_cmc_pressure_force_mat,
    2212             :                    /*A2=*/_cmc_sys_Wij_mat,
    2213             :                    /*rhs=*/_cmc_sys_Wij_rhs,
    2214             :                    /*rhs_add=*/_cmc_pressure_force_rhs,
    2215             :                    /*label=*/"Cross mom");
    2216             : 
    2217             :   // ========================== Relaxation  ====================
    2218             :   if (true)
    2219             :   {
    2220        1887 :     LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
    2221             :         _prod, *_mdot_soln, first_node, last_node, _n_channels));
    2222             : 
    2223             :     Vec mdot_estimate;
    2224        1887 :     LibmeshPetscCall(createPetscVector(mdot_estimate, _block_size * _n_channels));
    2225             :     Vec pmat_diag;
    2226        1887 :     LibmeshPetscCall(createPetscVector(pmat_diag, _block_size * _n_channels));
    2227             :     Vec p_estimate;
    2228        1887 :     LibmeshPetscCall(createPetscVector(p_estimate, _block_size * _n_channels));
    2229             :     Vec unity_vec;
    2230        1887 :     LibmeshPetscCall(createPetscVector(unity_vec, _block_size * _n_channels));
    2231        1887 :     LibmeshPetscCall(VecSet(unity_vec, 1.0));
    2232             :     Vec sol_holder_P;
    2233        1887 :     LibmeshPetscCall(createPetscVector(sol_holder_P, _block_size * _n_gaps));
    2234             :     Vec unity_vec_Wij;
    2235        1887 :     LibmeshPetscCall(createPetscVector(unity_vec_Wij, _block_size * _n_gaps));
    2236        1887 :     LibmeshPetscCall(VecSet(unity_vec_Wij, 1.0));
    2237             :     Vec _Wij_loc_vec;
    2238        1887 :     LibmeshPetscCall(createPetscVector(_Wij_loc_vec, _block_size * _n_gaps));
    2239             :     Vec _Wij_old_loc_vec;
    2240        1887 :     LibmeshPetscCall(createPetscVector(_Wij_old_loc_vec, _block_size * _n_gaps));
    2241             : 
    2242             :     // ---- scale estimates ----
    2243             :     // mdot_estimate = A(1,0) * mdot
    2244        1887 :     LibmeshPetscCall(MatMult(mat_array[Q /* (1,0) */], _prod, mdot_estimate));
    2245             : 
    2246             :     // p_estimate = mdot_est / (diag(A(1,1)) + eps)
    2247        1887 :     LibmeshPetscCall(MatGetDiagonal(mat_array[Q + 1], pmat_diag));
    2248        1887 :     LibmeshPetscCall(VecAXPY(pmat_diag, 1e-10, unity_vec));
    2249        1887 :     LibmeshPetscCall(VecPointwiseDivide(p_estimate, mdot_estimate, pmat_diag));
    2250             : 
    2251             :     // sol_holder_P = A(2,1) * p_estimate - rhs_cmc_pressure
    2252        1887 :     LibmeshPetscCall(MatMult(mat_array[2 * Q + 1], p_estimate, sol_holder_P));
    2253        1887 :     LibmeshPetscCall(VecAXPY(sol_holder_P, -1.0, _cmc_pressure_force_rhs));
    2254             : 
    2255             :     // sumWij_loc from sol_holder_P (accumulate)
    2256             :     Vec sumWij_loc;
    2257        1887 :     LibmeshPetscCall(createPetscVector(sumWij_loc, _block_size * _n_channels));
    2258       59822 :     for (unsigned int iz = first_node; iz <= last_node; ++iz)
    2259             :     {
    2260       57935 :       const auto iz_ind = iz - first_node;
    2261     3471605 :       for (unsigned int i_ch = 0; i_ch < _n_channels; ++i_ch)
    2262             :       {
    2263     3413670 :         PetscScalar sumWij = 0.0;
    2264             :         unsigned int counter = 0;
    2265    13365870 :         for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
    2266             :         {
    2267     9952200 :           auto chans = _subchannel_mesh.getGapChannels(i_gap);
    2268             :           unsigned int i_ch_loc = chans.first;
    2269     9952200 :           PetscInt row_vec = i_ch_loc + _n_channels * iz_ind;
    2270             :           PetscScalar loc_Wij_value;
    2271     9952200 :           LibmeshPetscCall(VecGetValues(sol_holder_P, 1, &row_vec, &loc_Wij_value));
    2272     9952200 :           sumWij += _subchannel_mesh.getCrossflowSign(i_ch, counter) * loc_Wij_value;
    2273     9952200 :           counter++;
    2274             :         }
    2275     3413670 :         PetscInt row_vec = i_ch + _n_channels * iz_ind;
    2276     3413670 :         LibmeshPetscCall(VecSetValues(sumWij_loc, 1, &row_vec, &sumWij, INSERT_VALUES));
    2277             :       }
    2278             :     }
    2279        1887 :     LibmeshPetscCall(VecAssemblyBegin(sumWij_loc));
    2280        1887 :     LibmeshPetscCall(VecAssemblyEnd(sumWij_loc));
    2281             : 
    2282             :     // ---- robust scale measurements ----
    2283             :     PetscScalar min_mdot;
    2284        1887 :     LibmeshPetscCall(VecAbs(_prod));
    2285        1887 :     LibmeshPetscCall(VecMin(_prod, NULL, &min_mdot));
    2286        1887 :     V("Minimum estimated mdot: " + std::to_string(min_mdot));
    2287             : 
    2288        1887 :     LibmeshPetscCall(VecAbs(sumWij_loc));
    2289        1887 :     LibmeshPetscCall(VecMax(sumWij_loc, NULL, &_max_sumWij));
    2290        3768 :     _max_sumWij = std::max(1e-10, _max_sumWij);
    2291        1887 :     V("Maximum estimated Wij: " + std::to_string(_max_sumWij));
    2292             : 
    2293        1887 :     LibmeshPetscCall(populateVectorFromDense<libMesh::DenseMatrix<Real>>(
    2294             :         _Wij_loc_vec, _Wij, first_node, last_node, _n_gaps));
    2295        1887 :     LibmeshPetscCall(VecAbs(_Wij_loc_vec));
    2296        1887 :     LibmeshPetscCall(populateVectorFromDense<libMesh::DenseMatrix<Real>>(
    2297             :         _Wij_old_loc_vec, _Wij_old, first_node, last_node, _n_gaps));
    2298        1887 :     LibmeshPetscCall(VecAbs(_Wij_old_loc_vec));
    2299        1887 :     LibmeshPetscCall(VecAXPY(_Wij_loc_vec, -1.0, _Wij_old_loc_vec));
    2300             : 
    2301             :     PetscScalar relax_factor;
    2302        1887 :     LibmeshPetscCall(VecAbs(_Wij_loc_vec));
    2303             : #if !PETSC_VERSION_LESS_THAN(3, 16, 0)
    2304        1887 :     LibmeshPetscCall(VecMean(_Wij_loc_vec, &relax_factor));
    2305             : #else
    2306             :     VecSum(_Wij_loc_vec, &relax_factor);
    2307             :     relax_factor /= _block_size * _n_gaps;
    2308             : #endif
    2309        1887 :     relax_factor = relax_factor / _max_sumWij + 0.5;
    2310        1887 :     V("Relax base value: " + std::to_string(relax_factor));
    2311             : 
    2312             :     // ---- crossflow resistance inflation ----
    2313             :     const PetscScalar resistance_relaxation = 0.9;
    2314        1887 :     _added_K = _max_sumWij / min_mdot;
    2315        1887 :     V("New cross resistance: " + std::to_string(_added_K));
    2316        1887 :     _added_K = (_added_K * resistance_relaxation + (1.0 - resistance_relaxation) * _added_K_old) *
    2317             :                relax_factor;
    2318        1887 :     V("Relaxed cross resistance: " + std::to_string(_added_K));
    2319             : 
    2320             :     // Snap-up lower-bounding
    2321        1887 :     if (_added_K < 10 && _added_K >= 1.0)
    2322         335 :       _added_K = 1.0;
    2323        1887 :     if (_added_K < 1.0 && _added_K >= 0.1)
    2324         828 :       _added_K = 0.5;
    2325        1887 :     if (_added_K < 0.1 && _added_K >= 0.01)
    2326         344 :       _added_K = 1. / 3.;
    2327        1887 :     if (_added_K < 1e-2 && _added_K >= 1e-3)
    2328           0 :       _added_K = 0.1;
    2329        1887 :     V("Actual added cross resistance: " + std::to_string(_added_K));
    2330        1887 :     LibmeshPetscCall(VecScale(unity_vec_Wij, _added_K));
    2331        1887 :     _added_K_old = _added_K;
    2332             : 
    2333        1887 :     LibmeshPetscCall(MatDiagonalSet(mat_array[2 * Q + 2], unity_vec_Wij, ADD_VALUES));
    2334             : 
    2335             :     // ---- cleanup temp vectors used above ----
    2336        1887 :     LibmeshPetscCall(VecDestroy(&mdot_estimate));
    2337        1887 :     LibmeshPetscCall(VecDestroy(&pmat_diag));
    2338        1887 :     LibmeshPetscCall(VecDestroy(&unity_vec));
    2339        1887 :     LibmeshPetscCall(VecDestroy(&p_estimate));
    2340        1887 :     LibmeshPetscCall(VecDestroy(&sol_holder_P));
    2341        1887 :     LibmeshPetscCall(VecDestroy(&unity_vec_Wij));
    2342        1887 :     LibmeshPetscCall(VecDestroy(&sumWij_loc));
    2343        1887 :     LibmeshPetscCall(VecDestroy(&_Wij_loc_vec));
    2344        1887 :     LibmeshPetscCall(VecDestroy(&_Wij_old_loc_vec));
    2345             : 
    2346             :     // ---- per-equation under-relaxation ----
    2347             :     const PetscScalar relaxation_factor_mdot = 1.0;
    2348             :     const PetscScalar relaxation_factor_P = 1.0;
    2349             :     const PetscScalar relaxation_factor_Wij = 0.1;
    2350             : 
    2351        3774 :     V("Relax mdot: " + std::to_string(relaxation_factor_mdot));
    2352        3774 :     V("Relax P: " + std::to_string(relaxation_factor_P));
    2353        1887 :     V("Relax Wij: " + std::to_string(relaxation_factor_Wij));
    2354             : 
    2355             :     // mdot
    2356        1887 :     RelaxEquation(mat_array[Idx(0, 0)],
    2357             :                   vec_array[0],
    2358             :                   vec_array[0],
    2359             :                   _prod,
    2360             :                   relaxation_factor_mdot,
    2361        1887 :                   [&](Vec dst)
    2362             :                   {
    2363        1887 :                     return populateVectorFromHandle<SolutionHandle>(
    2364        1887 :                         dst, *_mdot_soln, first_node, last_node, _n_channels);
    2365             :                   });
    2366        1887 :     V("mdot relaxed");
    2367             : 
    2368             :     // pressure
    2369        1887 :     RelaxEquation(mat_array[Idx(1, 1)],
    2370             :                   vec_array[1],
    2371             :                   vec_array[1],
    2372             :                   _prod,
    2373             :                   relaxation_factor_P,
    2374        1887 :                   [&](Vec dst)
    2375             :                   {
    2376        1887 :                     return populateVectorFromHandle<SolutionHandle>(
    2377        1887 :                         dst, *_P_soln, first_node, last_node, _n_channels);
    2378             :                   });
    2379        1887 :     V("P relaxed");
    2380             : 
    2381             :     // crossflow
    2382        1887 :     RelaxEquation(mat_array[Idx(2, 2)],
    2383             :                   vec_array[2],
    2384             :                   vec_array[2],
    2385             :                   _Wij_vec,
    2386             :                   relaxation_factor_Wij,
    2387        1887 :                   [&](Vec dst)
    2388             :                   {
    2389        1887 :                     return populateVectorFromDense<libMesh::DenseMatrix<Real>>(
    2390        1887 :                         dst, _Wij, first_node, last_node, _n_gaps);
    2391             :                   });
    2392        1887 :     V("Wij relaxed");
    2393             :   }
    2394        3774 :   V("Linear solver relaxed");
    2395             : 
    2396             :   // ======================== Create and configure KSP =========================
    2397             :   Mat A_nest;
    2398             :   Vec b_nest;
    2399             :   Vec x_nest;
    2400        1887 :   LibmeshPetscCall(MatCreateNest(PETSC_COMM_SELF, Q, NULL, Q, NULL, mat_array.data(), &A_nest));
    2401        1887 :   LibmeshPetscCall(VecCreateNest(PETSC_COMM_SELF, Q, NULL, vec_array.data(), &b_nest));
    2402        1887 :   V("Nested system created");
    2403             : 
    2404             :   KSP ksp;
    2405             :   PC pc;
    2406        1887 :   LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksp));
    2407        1887 :   LibmeshPetscCall(KSPSetType(ksp, KSPFGMRES));
    2408        1887 :   LibmeshPetscCall(KSPSetOperators(ksp, A_nest, A_nest));
    2409        1887 :   LibmeshPetscCall(KSPGetPC(ksp, &pc));
    2410        1887 :   LibmeshPetscCall(PCSetType(pc, PCFIELDSPLIT));
    2411        1887 :   LibmeshPetscCall(KSPSetTolerances(ksp, _rtol, _atol, _dtol, _maxit));
    2412             : 
    2413             :   // split equations
    2414        1887 :   std::vector<IS> rows(Q);
    2415        1887 :   LibmeshPetscCall(MatNestGetISs(A_nest, rows.data(), NULL));
    2416        7548 :   for (PetscInt j = 0; j < Q; ++j)
    2417             :   {
    2418             :     IS part;
    2419        5661 :     LibmeshPetscCall(ISDuplicate(rows[j], &part));
    2420        5661 :     LibmeshPetscCall(PCFieldSplitSetIS(pc, NULL, part));
    2421        5661 :     LibmeshPetscCall(ISDestroy(&part));
    2422             :   }
    2423        1887 :   V("Linear solver assembled");
    2424             : 
    2425             :   // ============================== Solve =====================================
    2426        1887 :   LibmeshPetscCall(VecDuplicate(b_nest, &x_nest));
    2427        1887 :   LibmeshPetscCall(VecSet(x_nest, 0.0));
    2428        1887 :   LibmeshPetscCall(KSPSolve(ksp, b_nest, x_nest));
    2429             : 
    2430             :   // destroy solver containers first
    2431        1887 :   LibmeshPetscCall(VecDestroy(&b_nest));
    2432        1887 :   LibmeshPetscCall(MatDestroy(&A_nest));
    2433        1887 :   LibmeshPetscCall(KSPDestroy(&ksp));
    2434       18870 :   for (PetscInt i = 0; i < Q * Q; i++)
    2435       16983 :     LibmeshPetscCall(MatDestroy(&mat_array[i]));
    2436        7548 :   for (PetscInt i = 0; i < Q; i++)
    2437        5661 :     LibmeshPetscCall(VecDestroy(&vec_array[i]));
    2438        1887 :   V("Solver elements destroyed");
    2439             : 
    2440             :   // ====================== Extract & scatter the solution =====================
    2441             :   Vec sol_mdot, sol_p, sol_Wij;
    2442        1887 :   V("Vectors to hold solution created");
    2443             :   PetscInt num_vecs;
    2444             :   Vec * loc_vecs;
    2445        1887 :   LibmeshPetscCall(VecNestGetSubVecs(x_nest, &num_vecs, &loc_vecs));
    2446        1887 :   LibmeshPetscCall(VecDuplicate(_mc_axial_convection_rhs, &sol_mdot));
    2447        1887 :   LibmeshPetscCall(VecCopy(loc_vecs[0], sol_mdot));
    2448        1887 :   LibmeshPetscCall(VecDuplicate(_amc_sys_mdot_rhs, &sol_p));
    2449        1887 :   LibmeshPetscCall(VecCopy(loc_vecs[1], sol_p));
    2450        1887 :   LibmeshPetscCall(VecDuplicate(_cmc_sys_Wij_rhs, &sol_Wij));
    2451        1887 :   LibmeshPetscCall(VecCopy(loc_vecs[2], sol_Wij));
    2452        1887 :   V("Solution from coupled solver copied to solution vectors");
    2453             : 
    2454             :   // mass flow
    2455        1887 :   LibmeshPetscCall(populateSolutionChan<SolutionHandle>(
    2456             :       sol_mdot, *_mdot_soln, first_node, last_node, _n_channels));
    2457             : 
    2458             :   // pressure
    2459             :   {
    2460             :     PetscScalar * sol_p_array;
    2461        1887 :     LibmeshPetscCall(VecGetArray(sol_p, &sol_p_array));
    2462       59822 :     for (unsigned int iz = last_node; iz > first_node - 1; iz--)
    2463             :     {
    2464       57935 :       const auto iz_ind = iz - first_node;
    2465     3471605 :       for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    2466             :       {
    2467     3413670 :         auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
    2468     3413670 :         PetscScalar value = sol_p_array[iz_ind * _n_channels + i_ch];
    2469     3413670 :         _P_soln->set(node_in, value);
    2470             :       }
    2471             :     }
    2472        1887 :     LibmeshPetscCall(VecRestoreArray(sol_p, &sol_p_array));
    2473             :   }
    2474             : 
    2475             :   // crossflow dense + sumWij + correction factor
    2476        1887 :   LibmeshPetscCall(populateDenseFromVector<libMesh::DenseMatrix<Real>>(
    2477             :       sol_Wij, _Wij, first_node, last_node, _n_gaps));
    2478             : 
    2479        1887 :   LibmeshPetscCall(MatMult(_mc_sumWij_mat, sol_Wij, _prod));
    2480        1887 :   LibmeshPetscCall(populateSolutionChan<SolutionHandle>(
    2481             :       _prod, *_SumWij_soln, first_node, last_node, _n_channels));
    2482             : 
    2483        1887 :   LibmeshPetscCall(VecAbs(_prod));
    2484        1887 :   LibmeshPetscCall(VecMax(_prod, NULL, &_max_sumWij_new));
    2485        1887 :   V("Maximum estimated Wij new: " + std::to_string(_max_sumWij_new));
    2486        1887 :   _correction_factor = _max_sumWij_new / _max_sumWij;
    2487        1887 :   V("Correction factor: " + std::to_string(_correction_factor));
    2488        1887 :   V("Solutions assigned to MOOSE variables.");
    2489             : 
    2490             :   // cleanup solution objects
    2491        1887 :   LibmeshPetscCall(VecDestroy(&x_nest));
    2492        1887 :   LibmeshPetscCall(VecDestroy(&sol_mdot));
    2493        1887 :   LibmeshPetscCall(VecDestroy(&sol_p));
    2494        1887 :   LibmeshPetscCall(VecDestroy(&sol_Wij));
    2495        1887 :   V("Solutions destroyed.");
    2496             : 
    2497        1887 :   PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
    2498        1887 : }
    2499             : 
    2500             : void
    2501         351 : SubChannel1PhaseProblem::externalSolve()
    2502             : {
    2503         351 :   _console << "Executing subchannel solver\n";
    2504         351 :   _dt = (isTransient() ? dt() : _one);
    2505         351 :   _TR = isTransient();
    2506         351 :   initializeSolution();
    2507             :   // Small helper functions to reduce repetition
    2508             :   // Verbose print helper (no-op unless _verbose_subchannel is true)
    2509       11935 :   auto V = [&](const std::string & s)
    2510             :   {
    2511       11935 :     if (_verbose_subchannel)
    2512        8432 :       _console << s << std::endl;
    2513       12286 :   };
    2514         351 :   V("Solution initialized");
    2515         351 :   Real P_error = 1.0;
    2516         351 :   unsigned int P_it = 0;
    2517             :   unsigned int P_it_max;
    2518             : 
    2519         351 :   if (_segregated_bool)
    2520         161 :     P_it_max = 20 * _n_blocks;
    2521             :   else
    2522             :     P_it_max = 100;
    2523             : 
    2524         351 :   if ((_n_blocks == 1) && (_segregated_bool))
    2525             :     P_it_max = 5;
    2526             : 
    2527        2066 :   while ((P_error > _P_tol && P_it < P_it_max))
    2528             :   {
    2529        1715 :     P_it += 1;
    2530        1715 :     if (P_it == P_it_max && _n_blocks != 1)
    2531             :     {
    2532           0 :       _console << "Reached maximum number of axial pressure iterations" << std::endl;
    2533           0 :       _converged = false;
    2534             :     }
    2535        1715 :     _console << "Solving Outer Iteration : " << P_it << std::endl;
    2536        1715 :     auto P_L2norm_old_axial = _P_soln->L2norm();
    2537        3430 :     for (unsigned int iblock = 0; iblock < _n_blocks; iblock++)
    2538             :     {
    2539        1715 :       int last_level = (iblock + 1) * _block_size;
    2540        1715 :       int first_level = iblock * _block_size + 1;
    2541        1715 :       Real T_block_error = 1.0;
    2542             :       auto T_it = 0;
    2543        1715 :       _console << "Solving Block: " << iblock << " From first level: " << first_level
    2544        1715 :                << " to last level: " << last_level << std::endl;
    2545        4639 :       while (T_block_error > _T_tol && T_it < _T_maxit)
    2546             :       {
    2547        2924 :         T_it += 1;
    2548        2924 :         if (T_it == _T_maxit)
    2549             :         {
    2550           0 :           _console << "Reached maximum number of temperature iterations for block: " << iblock
    2551           0 :                    << std::endl;
    2552           0 :           _converged = false;
    2553             :         }
    2554        2924 :         auto T_L2norm_old_block = _T_soln->L2norm();
    2555             :         // We are only computing quantities on rank 0
    2556        2924 :         if (processor_id() > 0)
    2557         734 :           goto aux_close;
    2558             : 
    2559        2190 :         if (_segregated_bool)
    2560             :         {
    2561         303 :           computeWijFromSolve(iblock);
    2562         303 :           if (_compute_power)
    2563             :           {
    2564         217 :             computeh(iblock);
    2565         217 :             computeT(iblock);
    2566             :           }
    2567             :         }
    2568             :         else
    2569             :         {
    2570        1887 :           LibmeshPetscCall(implicitPetscSolve(iblock));
    2571        1887 :           computeWijPrime(iblock);
    2572        1887 :           V("Done with main solve.");
    2573        1887 :           if (_compute_power)
    2574             :           {
    2575        1787 :             computeh(iblock);
    2576        1787 :             computeT(iblock);
    2577             :           }
    2578        3774 :           V("Done with thermal solve.");
    2579             :         }
    2580             : 
    2581        2190 :         V("Start updating thermophysical properties.");
    2582        2190 :         if (_compute_density)
    2583        2180 :           computeRho(iblock);
    2584        2190 :         if (_compute_viscosity)
    2585        2180 :           computeMu(iblock);
    2586        4380 :         V("Done updating thermophysical properties.");
    2587             : 
    2588             :         // We must do a global assembly to make sure data is parallel consistent before we do things
    2589             :         // like compute L2 norms
    2590        2924 :       aux_close:
    2591        2924 :         _aux->solution().close();
    2592             : 
    2593        2924 :         auto T_L2norm_new = _T_soln->L2norm();
    2594        2924 :         T_block_error =
    2595        2924 :             std::abs((T_L2norm_new - T_L2norm_old_block) / (T_L2norm_old_block + 1E-14));
    2596        2924 :         _console << "T_block_error: " << T_block_error << std::endl;
    2597             : 
    2598             :         // All processes must have the same iteration count
    2599        2924 :         comm().max(T_block_error);
    2600             :       }
    2601             :     }
    2602        1715 :     auto P_L2norm_new_axial = _P_soln->L2norm();
    2603        1715 :     P_error =
    2604        1715 :         std::abs((P_L2norm_new_axial - P_L2norm_old_axial) / (P_L2norm_old_axial + _P_out + 1E-14));
    2605        1715 :     _console << "P_error :" << P_error << std::endl;
    2606        1715 :     V("Iteration:  " + std::to_string(P_it));
    2607        3430 :     V("Maximum iterations: " + std::to_string(P_it_max));
    2608             :   }
    2609             :   // update old crossflow matrix
    2610         351 :   _Wij_old = _Wij;
    2611         351 :   _console << "Finished executing subchannel solver\n";
    2612             : 
    2613             :   // set SumWij at the inlet equal to the one on the first axial level  (for visualization purposes)
    2614       12473 :   for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    2615             :   {
    2616       12122 :     auto * node_in = _subchannel_mesh.getChannelNode(i_ch, 0);
    2617       12122 :     auto * node_out = _subchannel_mesh.getChannelNode(i_ch, 1);
    2618       12122 :     _SumWij_soln->set(node_in, (*_SumWij_soln)(node_out)); // kg/sec
    2619             :   }
    2620             : 
    2621         351 :   if (_pin_mesh_exist)
    2622             :   {
    2623             :     // Assign average HTC to subchannels. This is exact if all pins have the same diameter
    2624        2600 :     for (unsigned int iz = 0; iz < _n_cells + 1; ++iz)
    2625             :     {
    2626      166418 :       for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    2627             :       {
    2628      163981 :         const auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
    2629      163981 :         auto mu = (*_mu_soln)(node);
    2630      163981 :         auto S = (*_S_flow_soln)(node);
    2631      163981 :         auto w_perim = (*_w_perim_soln)(node);
    2632      163981 :         auto Dh_i = 4.0 * S / w_perim;
    2633      163981 :         auto Re = (((*_mdot_soln)(node) / S) * Dh_i / mu);
    2634      163981 :         auto k = _fp->k_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
    2635      163981 :         auto cp = _fp->cp_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
    2636      163981 :         auto Pr = (*_mu_soln)(node)*cp / k;
    2637             :         // Create Friction structure
    2638      163981 :         _friction_args = FrictionStruct(i_ch, Re, S, w_perim);
    2639             : 
    2640             :         Real sumhw = 0.0;
    2641      601463 :         for (auto i_pin : _subchannel_mesh.getChannelPins(i_ch))
    2642             :         {
    2643             :           // Create nusselt number structure
    2644      437485 :           _nusselt_args = NusseltStruct(Re, Pr, i_pin, iz, i_ch);
    2645             : 
    2646             :           // Compute HTC
    2647      437485 :           sumhw += _pin_HTC_closure->computeHTC(_friction_args, _nusselt_args, k);
    2648             :         }
    2649             : 
    2650             :         // Set HTC
    2651      163978 :         _HTC_soln->set(node, sumhw / _subchannel_mesh.getChannelPins(i_ch).size());
    2652             :       }
    2653             :     }
    2654             :     _HTC_soln->close();
    2655             : 
    2656         160 :     _console << "Commencing calculation of Pin surface temperature \n";
    2657        4217 :     for (unsigned int i_pin = 0; i_pin < _n_pins; i_pin++)
    2658             :     {
    2659       85328 :       for (unsigned int iz = 0; iz < _n_cells + 1; ++iz)
    2660             :       {
    2661       81271 :         const auto * pin_node = _subchannel_mesh.getPinNode(i_pin, iz);
    2662             :         Real sumTemp = 0.0;
    2663             :         // Calculate sum of pin surface temperatures that the channels around the pin see
    2664      518753 :         for (auto i_ch : _subchannel_mesh.getPinChannels(i_pin))
    2665             :         {
    2666      437482 :           const auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
    2667      437482 :           auto mu = (*_mu_soln)(node);
    2668      437482 :           auto S = (*_S_flow_soln)(node);
    2669      437482 :           auto w_perim = (*_w_perim_soln)(node);
    2670      437482 :           auto Dh_i = 4.0 * S / w_perim;
    2671      437482 :           auto Re = (((*_mdot_soln)(node) / S) * Dh_i / mu);
    2672      437482 :           auto k = _fp->k_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
    2673      437482 :           auto cp = _fp->cp_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
    2674      437482 :           auto Pr = (*_mu_soln)(node)*cp / k;
    2675             :           // Create Friction structure
    2676      437482 :           _friction_args = FrictionStruct(i_ch, Re, S, w_perim);
    2677             :           // Create nusselt number structure
    2678      437482 :           _nusselt_args = NusseltStruct(Re, Pr, i_pin, iz, i_ch);
    2679             :           // Compute HTC
    2680      437482 :           auto hw = _pin_HTC_closure->computeHTC(_friction_args, _nusselt_args, k);
    2681             :           // Compute surface temperature contribution from subchannel side
    2682      437482 :           sumTemp +=
    2683      437482 :               (*_q_prime_soln)(pin_node) / ((*_Dpin_soln)(pin_node)*M_PI * hw) + (*_T_soln)(node);
    2684             :         }
    2685       81271 :         if (_subchannel_mesh.getPinChannels(i_pin).size() > 0)
    2686       81271 :           _Tpin_soln->set(pin_node, sumTemp / _subchannel_mesh.getPinChannels(i_pin).size());
    2687             :         else
    2688           0 :           mooseError("Pin was not found for pin index:  " + std::to_string(i_pin));
    2689             :       }
    2690             :     }
    2691             :   }
    2692             : 
    2693             :   /// Assigning temperatures to duct
    2694         348 :   if (_duct_mesh_exist && processor_id() == 0)
    2695             :   {
    2696          52 :     _console << "Commencing calculation of duct surface temperature " << std::endl;
    2697          52 :     auto duct_nodes = _subchannel_mesh.getDuctNodes();
    2698       24472 :     for (Node * dn : duct_nodes)
    2699             :     {
    2700       24420 :       auto * node_chan = _subchannel_mesh.getChannelNodeFromDuct(dn);
    2701       24420 :       auto mu = (*_mu_soln)(node_chan);
    2702       24420 :       auto S = (*_S_flow_soln)(node_chan);
    2703       24420 :       auto w_perim = (*_w_perim_soln)(node_chan);
    2704       24420 :       auto Dh_i = 4.0 * S / w_perim;
    2705       24420 :       auto Re = (((*_mdot_soln)(node_chan) / S) * Dh_i / mu);
    2706       24420 :       auto k = _fp->k_from_p_T((*_P_soln)(node_chan) + _P_out, (*_T_soln)(node_chan));
    2707       24420 :       auto cp = _fp->cp_from_p_T((*_P_soln)(node_chan) + _P_out, (*_T_soln)(node_chan));
    2708       24420 :       auto Pr = (*_mu_soln)(node_chan)*cp / k;
    2709             : 
    2710             :       // Create nusselt number structure (consistent with pin case)
    2711       24420 :       const libMesh::Point & node_point = *_subchannel_mesh.getChannelNodeFromDuct(dn);
    2712       24420 :       const unsigned int iz = _subchannel_mesh.getZIndex(node_point);
    2713       24420 :       const unsigned int i_ch = _subchannel_mesh.channelIndex(node_point);
    2714             : 
    2715             :       // Create nusselt number structure
    2716       24420 :       _nusselt_args = NusseltStruct(Re, Pr, std::numeric_limits<unsigned int>::max(), iz, i_ch);
    2717             : 
    2718             :       // Create Friction structure
    2719       24420 :       _friction_args = FrictionStruct(i_ch, Re, S, w_perim);
    2720             : 
    2721             :       // Compute HTC
    2722       24420 :       auto hw = _duct_HTC_closure->computeHTC(_friction_args, _nusselt_args, k);
    2723             : 
    2724             :       // Compute Channel Temperature
    2725       24420 :       auto T_chan = (*_duct_heat_flux_soln)(dn) / hw + (*_T_soln)(node_chan);
    2726       24420 :       _Tduct_soln->set(dn, T_chan);
    2727             :     }
    2728          52 :   }
    2729         348 :   _aux->solution().close();
    2730         348 :   _aux->update();
    2731             : 
    2732         348 :   if (processor_id() != 0)
    2733          92 :     return;
    2734             :   Real power_in = 0.0;
    2735             :   Real power_out = 0.0;
    2736             :   Real viscosity_in = 0.0;
    2737         256 :   Real mass_flow_in = 0.0;
    2738             :   Real mass_flow_out = 0.0;
    2739       12000 :   for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
    2740             :   {
    2741       11744 :     auto * node_in = _subchannel_mesh.getChannelNode(i_ch, 0);
    2742       11744 :     auto * node_out = _subchannel_mesh.getChannelNode(i_ch, _n_cells);
    2743       11744 :     const Real mdot_in = (*_mdot_soln)(node_in);
    2744       11744 :     power_in += mdot_in * (*_h_soln)(node_in);
    2745       11744 :     power_out += (*_mdot_soln)(node_out) * (*_h_soln)(node_out);
    2746       11744 :     viscosity_in += mdot_in * (*_mu_soln)(node_in);
    2747       11744 :     mass_flow_in += mdot_in;
    2748       11744 :     mass_flow_out += (*_mdot_soln)(node_out);
    2749             :   }
    2750         256 :   auto h_bulk_out = power_out / mass_flow_out;
    2751         256 :   auto T_bulk_out = _fp->T_from_p_h(_P_out, h_bulk_out);
    2752             : 
    2753         256 :   Real bulk_Dh = _subchannel_mesh.getAssemblyHydraulicDiameter();
    2754         256 :   Real inlet_mu = viscosity_in / mass_flow_in;
    2755         256 :   Real bulk_Re = mass_flow_in * bulk_Dh / (inlet_mu * _subchannel_mesh.getAssemblyFlowArea());
    2756         256 :   if (_verbose_subchannel)
    2757             :   {
    2758         159 :     _console << " ======================================= " << std::endl;
    2759         159 :     _console << " ======== Subchannel Print Outs ======== " << std::endl;
    2760         159 :     _console << " ======================================= " << std::endl;
    2761         159 :     _console << "Total flow area :" << _subchannel_mesh.getAssemblyFlowArea() << " m^2"
    2762         159 :              << std::endl;
    2763         159 :     _console << "Assembly hydraulic diameter :" << bulk_Dh << " m" << std::endl;
    2764         159 :     _console << "Assembly Re number :" << bulk_Re << " [-]" << std::endl;
    2765         159 :     _console << "Bulk coolant temperature at outlet :" << T_bulk_out << " K" << std::endl;
    2766         159 :     _console << "Power added to coolant is : " << power_out - power_in << " Watt" << std::endl;
    2767         159 :     _console << "Mass flow rate in is : " << mass_flow_in << " kg/sec" << std::endl;
    2768         159 :     _console << "Mass balance is : " << mass_flow_out - mass_flow_in << " kg/sec" << std::endl;
    2769         159 :     _console << "User defined outlet pressure is : " << _P_out << " Pa" << std::endl;
    2770         159 :     _console << " ======================================= " << std::endl;
    2771             :   }
    2772             : 
    2773         256 :   if (MooseUtils::absoluteFuzzyLessEqual((power_out - power_in), -1.0))
    2774           0 :     mooseWarning(
    2775           0 :         "Energy conservation equation might not be solved correctly, Power added to coolant:  " +
    2776           0 :         std::to_string(power_out - power_in) + " Watt ");
    2777             : }
    2778             : 
    2779             : void
    2780         707 : SubChannel1PhaseProblem::syncSolutions(Direction /*direction*/)
    2781             : {
    2782         707 : }

Generated by: LCOV version 1.14