https://mooseframework.inl.gov
Loading...
Searching...
No Matches
SubChannel1PhaseProblem.C
Go to the documentation of this file.
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
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"
21#include "SCMHTCClosureBase.h"
23#include "TransientBase.h"
24#include "ImplicitEuler.h"
25
31
32PetscErrorCode
33formFunction(SNES, Vec x, Vec f, void * ctx)
34{
35 const PetscScalar * xx;
36 PetscScalar * ff;
37 PetscInt size;
38
40 Ctx * cc = static_cast<Ctx *>(ctx);
41 LibmeshPetscCallQ(VecGetSize(x, &size));
42
43 libMesh::DenseVector<Real> solution_seed(size, 0.0);
44 LibmeshPetscCallQ(VecGetArrayRead(x, &xx));
45 for (PetscInt i = 0; i < size; i++)
46 solution_seed(i) = xx[i];
47
48 LibmeshPetscCallQ(VecRestoreArrayRead(x, &xx));
49
50 libMesh::DenseVector<Real> Wij_residual_vector =
51 cc->schp->residualFunction(cc->iblock, solution_seed);
52
53 LibmeshPetscCallQ(VecGetArray(f, &ff));
54 for (int i = 0; i < size; i++)
55 ff[i] = Wij_residual_vector(i);
56
57 LibmeshPetscCallQ(VecRestoreArray(f, &ff));
58 PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
59}
60
63{
64 // Enumerations
65 MooseEnum schemes("upwind downwind central_difference exponential", "central_difference");
66 MooseEnum gravity_direction("counter_flow co_flow none", "counter_flow");
67
68 // Inputs
71 params.addClassDescription("Base class of the subchannel solvers");
72 params.addParam<unsigned int>("n_blocks", 1, "The number of blocks in the axial direction");
73 params.addParam<Real>("P_tol", 1e-6, "Pressure tolerance");
74 params.addRangeCheckedParam<int>(
75 "P_maxit",
76 0,
77 "P_maxit >= 0",
78 "Maximum number of pressure iterations; zero selects the solver's automatic limit");
79 params.addParam<Real>("T_tol", 1e-6, "Temperature tolerance");
80 params.addParam<int>("T_maxit", 100, "Maximum number of iterations for inner temperature loop");
81 params.addRangeCheckedParam<Real>(
82 "T_relaxation",
83 1.0,
84 "T_relaxation > 0 & T_relaxation <= 1",
85 "Relaxation factor for temperature updates in the inner thermal-hydraulic iteration");
86 params.addRangeCheckedParam<unsigned int>(
87 "enthalpy_subcycles",
88 1,
89 "enthalpy_subcycles > 0",
90 "Number of enthalpy, temperature, and property updates performed per flow solve");
91 params.addRangeCheckedParam<Real>(
92 "mass_flow_equation_relaxation",
93 1.0,
94 "mass_flow_equation_relaxation > 0 & mass_flow_equation_relaxation <= 1",
95 "Equation relaxation factor for mass flow rate in the implicit non-segregated solve");
96 params.addRangeCheckedParam<Real>(
97 "pressure_equation_relaxation",
98 1.0,
99 "pressure_equation_relaxation > 0 & pressure_equation_relaxation <= 1",
100 "Equation relaxation factor for pressure in the implicit non-segregated solve");
101 params.addRangeCheckedParam<Real>(
102 "crossflow_equation_relaxation",
103 0.1,
104 "crossflow_equation_relaxation > 0 & crossflow_equation_relaxation <= 1",
105 "Equation relaxation factor for crossflow in the implicit non-segregated solve");
106 params.addRangeCheckedParam<Real>(
107 "mass_flow_relaxation",
108 1.0,
109 "mass_flow_relaxation > 0 & mass_flow_relaxation <= 1",
110 "Post-solve relaxation factor for mass flow rate updates in the implicit non-segregated "
111 "solve");
112 params.addRangeCheckedParam<Real>(
113 "pressure_relaxation",
114 1.0,
115 "pressure_relaxation > 0 & pressure_relaxation <= 1",
116 "Post-solve relaxation factor for pressure updates in the implicit non-segregated solve");
117 params.addRangeCheckedParam<Real>(
118 "crossflow_relaxation",
119 1.0,
120 "crossflow_relaxation > 0 & crossflow_relaxation <= 1",
121 "Post-solve relaxation factor for crossflow updates in the implicit non-segregated solve");
122 params.addParam<PetscReal>("rtol", 1e-6, "Relative tolerance for ksp solver");
123 params.addParam<PetscReal>("atol", 1e-6, "Absolute tolerance for ksp solver");
124 params.addParam<PetscReal>("dtol", 1e5, "Divergence tolerance or ksp solver");
125 params.addParam<PetscInt>("maxit", 1e4, "Maximum number of iterations for ksp solver");
126 params.addParam<MooseEnum>(
127 "interpolation_scheme",
128 schemes,
129 "Interpolation scheme used to evaluate values at the cell center. Default is "
130 "central_difference");
131 params.addParam<MooseEnum>(
132 "gravity", gravity_direction, "Direction of gravity. Default is counter_flow");
133 params.addParam<bool>(
134 "implicit", false, "Boolean to define the use of explicit or implicit solution.");
135 params.addDeprecatedParam<bool>(
136 "staggered_pressure",
137 false,
138 "Boolean to define the use of staggered or collocated pressure.",
139 "Pressure is a face value and the pressure drop is a cell value, so the collocated "
140 "formulation is always used. This parameter is ignored.");
141 params.addParam<bool>(
142 "segregated", true, "Boolean to define whether to use a segregated solution.");
143 params.addParam<bool>(
144 "verbose_subchannel", false, "Boolean to print out information related to subchannel solve.");
145 params.addRequiredParam<bool>("compute_density", "Flag that enables the calculation of density");
146 params.addRequiredParam<bool>("compute_viscosity",
147 "Flag that enables the calculation of viscosity");
148 params.addRequiredParam<bool>(
149 "compute_power",
150 "Flag that informs whether we solve the Enthalpy/Temperature equations or not");
151 params.addRequiredParam<PostprocessorName>(
152 "P_out",
153 "The postprocessor (or scalar) that provides the absolute outlet pressure [Pa]. The solved "
154 "pressure variable P is relative to this value.");
155 params.addRequiredParam<UserObjectName>("fp", "Fluid properties user object name");
156 params.addRequiredParam<UserObjectName>("friction_closure",
157 "Closure computing the friction factor");
158 params.addRequiredParam<UserObjectName>(
159 "mixing_closure",
160 "Closure computing the turbulent mixing, wire-induced "
161 "mixing and sweep flow mixing parameter where applicable");
162 params.addParam<UserObjectName>(
163 "pin_HTC_closure", "Closure computing HTC on fuel pin (required if pin mesh exists).");
164 params.addParam<UserObjectName>("duct_HTC_closure",
165 "Closure computing HTC on duct (required if duct mesh exists).");
166 params.addParam<bool>(
167 "full_output", false, "Flag that enables the output of the maximum number of variables.");
168 params.addDeprecatedParam<Real>("beta",
169 "Thermal diffusion coefficient used in turbulent crossflow.",
170 "Use closure system instead.");
171 params.addDeprecatedParam<bool>(
172 "constant_beta",
173 true,
174 "Boolean to define the use of a constant beta or beta correlation (Kim and Chung, 2001)",
175 "Use closure system instead.");
176
178 "P_tol P_maxit T_tol T_maxit T_relaxation enthalpy_subcycles "
179 "mass_flow_equation_relaxation pressure_equation_relaxation crossflow_equation_relaxation "
180 "mass_flow_relaxation pressure_relaxation crossflow_relaxation rtol atol dtol maxit",
181 "Solver tolerances and iterations");
182 params.addParamNamesToGroup("implicit segregated interpolation_scheme", "Solution method");
183 params.addParamNamesToGroup("fp friction_closure mixing_closure pin_HTC_closure duct_HTC_closure",
184 "Closures");
185 params.addParamNamesToGroup("compute_density compute_viscosity compute_power gravity",
186 "Physics models");
187 params.addParamNamesToGroup("verbose_subchannel full_output", "Output");
188
189 return params;
190}
191
193 : ExternalProblem(params),
195 _friction_args(/*i_ch=*/0, /*Re=*/1.0, /*S=*/0.0, /*w_perim=*/0.0),
196 _nusselt_args(
197 /*Re=*/1.0, /*Pr=*/1.0, std::numeric_limits<unsigned int>::max(), /*iz=*/0, /*i_ch=*/0),
198 _P_out(getPostprocessorValue("P_out")),
199 _fp(nullptr),
200 _subchannel_mesh(SCM::getMesh<SubChannelMesh>(_mesh)),
201 _n_blocks(getParam<unsigned int>("n_blocks")),
202 _Wij(declareRestartableData<libMesh::DenseMatrix<Real>>("Wij")),
203 _Wij_old(declareRestartableData<libMesh::DenseMatrix<Real>>("Wij_old")),
204 _g_grav(9.81),
205 _kij(_subchannel_mesh.getKij()),
206 _one(1.0),
207 _compute_density(getParam<bool>("compute_density")),
208 _compute_viscosity(getParam<bool>("compute_viscosity")),
209 _compute_power(getParam<bool>("compute_power")),
210 _pin_mesh_exist(_subchannel_mesh.pinMeshExist()),
211 _duct_mesh_exist(_subchannel_mesh.ductMeshExist()),
212 _bulk_Re(1.0),
213 _bulk_V(1.0),
214 _P_tol(getParam<Real>("P_tol")),
215 _P_maxit(getParam<int>("P_maxit")),
216 _T_tol(getParam<Real>("T_tol")),
217 _T_maxit(getParam<int>("T_maxit")),
218 _T_relaxation(getParam<Real>("T_relaxation")),
219 _enthalpy_subcycles(getParam<unsigned int>("enthalpy_subcycles")),
220 _mass_flow_equation_relaxation(getParam<Real>("mass_flow_equation_relaxation")),
221 _pressure_equation_relaxation(getParam<Real>("pressure_equation_relaxation")),
222 _crossflow_equation_relaxation(getParam<Real>("crossflow_equation_relaxation")),
223 _mass_flow_relaxation(getParam<Real>("mass_flow_relaxation")),
224 _pressure_relaxation(getParam<Real>("pressure_relaxation")),
225 _crossflow_relaxation(getParam<Real>("crossflow_relaxation")),
226 _rtol(getParam<PetscReal>("rtol")),
227 _atol(getParam<PetscReal>("atol")),
228 _dtol(getParam<PetscReal>("dtol")),
229 _maxit(getParam<PetscInt>("maxit")),
230 _interpolation_scheme(getParam<MooseEnum>("interpolation_scheme")),
231 _gravity_direction(getParam<MooseEnum>("gravity")),
232 _dir_grav(computeGravityDir(_gravity_direction)),
233 _implicit_bool(getParam<bool>("implicit")),
234 _segregated_bool(getParam<bool>("segregated")),
235 _verbose_subchannel(getParam<bool>("verbose_subchannel")),
236 _friction_closure(nullptr),
237 _mixing_closure(nullptr),
238 _pin_HTC_closure(nullptr),
239 _duct_HTC_closure(nullptr),
240 _Tpin_soln(nullptr),
241 _duct_heat_flux_soln(nullptr),
242 _Tduct_soln(nullptr),
243 _HTC_soln(nullptr)
244{
245 if (params.isParamSetByUser("beta") || params.isParamSetByUser("constant_beta"))
246 paramError("beta",
247 "You are using a deprecated parameter. Please use the mixing_closure system.");
248 if (_pin_mesh_exist && !isParamValid("pin_HTC_closure"))
249 paramError("pin_HTC_closure", "required when a pin mesh exists.");
250 if (_duct_mesh_exist && !isParamValid("duct_HTC_closure"))
251 paramError("duct_HTC_closure", "required when a duct mesh exists.");
253 paramError("segregated", "A non-segregated solve requires 'implicit = true'.");
255 for (const auto * relaxation_param : {"mass_flow_equation_relaxation",
256 "pressure_equation_relaxation",
257 "crossflow_equation_relaxation",
258 "mass_flow_relaxation",
259 "pressure_relaxation",
260 "crossflow_relaxation"})
261 if (params.isParamSetByUser(relaxation_param))
262 paramError(relaxation_param,
263 "The '",
264 relaxation_param,
265 "' parameter is only used by the implicit non-segregated solve. Set "
266 "'implicit = true' and 'segregated = false' to use it.");
267 // NOTE: The four quantities below are 0 for processor_id != 0
272 // NOTE: The four quantities above are 0 for processor_id != 0
275 // Pressure drop (lives on subchannel nodes)
277 _DP.zero();
278 // Turbulent crossflow (stuff that live on the gaps)
279 if (!_app.isRestarting() && !_app.isRecovering())
280 {
282 _Wij.zero();
284 _Wij_old.zero();
285 }
286
288 _WijPrime.zero();
291 _converged = true;
292
293 // Mass conservation components
294 LibmeshPetscCall(
296 LibmeshPetscCall(createPetscVector(_Wij_vec, _block_size * _n_gaps));
297 LibmeshPetscCall(createPetscVector(_prod, _block_size * _n_channels));
298 LibmeshPetscCall(createPetscVector(_prodp, _block_size * _n_channels));
299 LibmeshPetscCall(createPetscMatrix(
302 LibmeshPetscCall(createPetscMatrix(
304
305 // Axial momentum conservation components
306 LibmeshPetscCall(createPetscMatrix(
309 LibmeshPetscCall(createPetscMatrix(
312 LibmeshPetscCall(createPetscMatrix(
315 LibmeshPetscCall(createPetscMatrix(
318 LibmeshPetscCall(createPetscMatrix(
322 LibmeshPetscCall(createPetscMatrix(
325 LibmeshPetscCall(
328
329 // Lateral momentum conservation components
330 LibmeshPetscCall(
333 LibmeshPetscCall(createPetscMatrix(
336 LibmeshPetscCall(
339 LibmeshPetscCall(
342 LibmeshPetscCall(
345
346 // Energy conservation components
347 LibmeshPetscCall(createPetscMatrix(
350 LibmeshPetscCall(createPetscMatrix(
353 LibmeshPetscCall(createPetscMatrix(
357 LibmeshPetscCall(
360
362 {
364 ": When implicit number of blocks can't be equal to number of cells. This will "
365 "cause problems with the subchannel interpolation scheme.");
366 }
367}
368
369void
371{
372 // Create variables for output and storage. This comes before the base class setup, which runs
373 // the user objects executed on initial, such as SCMPinPowerPostprocessor, that read them.
374 _mdot_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::MASS_FLOW_RATE));
375 _SumWij_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::SUM_CROSSFLOW));
376 _P_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::PRESSURE));
377 if (getParam<bool>("full_output"))
378 {
379 _DP_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::PRESSURE_DROP));
380 _ff_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::FRICTION_FACTOR));
381 }
382 _h_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::ENTHALPY));
383 _T_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::TEMPERATURE));
384 if (_pin_mesh_exist)
385 {
386 _Tpin_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::PIN_TEMPERATURE));
387 _Dpin_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::PIN_DIAMETER));
388 _HTC_soln =
389 std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::HEAT_TRANSFER_COEFFICIENT));
390 }
391 _rho_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::DENSITY));
392 _mu_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::VISCOSITY));
393 _S_flow_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::SURFACE_AREA));
394 _w_perim_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::WETTED_PERIMETER));
395 _q_prime_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::LINEAR_HEAT_RATE));
397 std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::DISPLACEMENT));
399 {
401 std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::DUCT_HEAT_FLUX));
402 _Tduct_soln = std::make_unique<SolutionHandle>(getVariable(0, SubChannelApp::DUCT_TEMPERATURE));
403 }
404
406
407 _fp = &getUserObject<SinglePhaseFluidProperties>(getParam<UserObjectName>("fp"));
409 &getUserObject<SCMFrictionClosureBase>(getParam<UserObjectName>("friction_closure"));
411 &getUserObject<SCMMixingClosureBase>(getParam<UserObjectName>("mixing_closure"));
412
415
416 if (_pin_mesh_exist)
418 &getUserObject<SCMHTCClosureBase>(getParam<UserObjectName>("pin_HTC_closure"));
421 &getUserObject<SCMHTCClosureBase>(getParam<UserObjectName>("duct_HTC_closure"));
422}
423
424void
426{
427 if (processor_id() == 0)
428 {
429 Real viscosity_in = 0.0;
430 Real volumetric_flow_in = 0.0;
431 Real mass_flow_in = 0.0;
432 for (const auto i_ch : make_range(_n_channels))
433 {
434 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, 0);
435 const Real mdot_in = (*_mdot_soln)(node_in);
436 const Real rho_in = (*_rho_soln)(node_in);
437 viscosity_in += mdot_in * (*_mu_soln)(node_in);
438 volumetric_flow_in += mdot_in / rho_in;
439 mass_flow_in += mdot_in;
440 }
441 const Real bulk_Dh = _subchannel_mesh.getAssemblyHydraulicDiameter();
442 const Real flow_area = _subchannel_mesh.getAssemblyFlowArea();
443 const Real inlet_mu = viscosity_in / mass_flow_in;
444 _bulk_Re = mass_flow_in * bulk_Dh / (inlet_mu * flow_area);
445 _bulk_V = volumetric_flow_in / flow_area;
446 }
447 // Only rank 0 owns the subchannel DOFs (SingleRankPartitioner), so share the result
450
451 if (MooseUtils::absoluteFuzzyEqual(_bulk_Re, 0.0))
452 mooseError("The computed bulk Reynolds number is zero.");
453
454 if (MooseUtils::absoluteFuzzyEqual(_bulk_V, 0.0))
455 mooseError("The computed bulk velocity is zero.");
456
458}
459
460void
462{
463 const Real tol = libMesh::TOLERANCE;
464 const auto pin_diameter = _subchannel_mesh.getPinDiameter();
465
466 if (_pin_mesh_exist)
467 {
468 for (unsigned int iz = 0; iz < _n_cells + 1; iz++)
469 for (unsigned int i_pin = 0; i_pin < _n_pins; i_pin++)
470 {
471 auto * node = _subchannel_mesh.getPinNode(i_pin, iz);
472 const Real Dpin = (*_Dpin_soln)(node);
473 if (std::abs(Dpin) <= tol)
474 mooseError("Dpin is zero at node ",
475 node->id(),
476 ". You must initialize Dpin to a non-zero value.");
477 if (std::abs(Dpin - pin_diameter) > tol)
478 _deformation = true;
479 }
480 }
481
482 for (unsigned int iz = 0; iz < _n_cells + 1 && !_deformation; iz++)
483 for (unsigned int i_ch = 0; i_ch < _n_channels && !_deformation; i_ch++)
484 {
485 auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
486 auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
487
488 if ((subch_type == EChannelType::CORNER || subch_type == EChannelType::EDGE) &&
489 std::abs((*_displacement_soln)(node)) > tol)
490 _deformation = true;
491 }
492}
493
495{
496 PetscErrorCode ierr = cleanUp();
497 if (ierr)
498 mooseError(name(), ": Error in memory cleanup");
499}
500
501PetscErrorCode
503{
505 // We need to clean up the petsc matrices/vectors
506 // Mass conservation components
507 LibmeshPetscCall(MatDestroy(&_mc_sumWij_mat));
508 LibmeshPetscCall(VecDestroy(&_Wij_vec));
509 LibmeshPetscCall(VecDestroy(&_prod));
510 LibmeshPetscCall(VecDestroy(&_prodp));
511 LibmeshPetscCall(MatDestroy(&_mc_axial_convection_mat));
512 LibmeshPetscCall(VecDestroy(&_mc_axial_convection_rhs));
513 LibmeshPetscCall(MatDestroy(&_mc_density_pressure_mat));
514
515 // Axial momentum conservation components
516 LibmeshPetscCall(MatDestroy(&_amc_turbulent_cross_flows_mat));
517 LibmeshPetscCall(VecDestroy(&_amc_turbulent_cross_flows_rhs));
518 LibmeshPetscCall(MatDestroy(&_amc_time_derivative_mat));
519 LibmeshPetscCall(VecDestroy(&_amc_time_derivative_rhs));
520 LibmeshPetscCall(MatDestroy(&_amc_advective_derivative_mat));
521 LibmeshPetscCall(VecDestroy(&_amc_advective_derivative_rhs));
522 LibmeshPetscCall(MatDestroy(&_amc_cross_derivative_mat));
523 LibmeshPetscCall(VecDestroy(&_amc_cross_derivative_rhs));
524 LibmeshPetscCall(MatDestroy(&_amc_friction_force_mat));
525 LibmeshPetscCall(VecDestroy(&_amc_friction_force_rhs));
526 LibmeshPetscCall(VecDestroy(&_amc_gravity_rhs));
527 LibmeshPetscCall(MatDestroy(&_amc_pressure_force_mat));
528 LibmeshPetscCall(VecDestroy(&_amc_pressure_force_rhs));
529 LibmeshPetscCall(MatDestroy(&_amc_sys_mdot_mat));
530 LibmeshPetscCall(VecDestroy(&_amc_sys_mdot_rhs));
531
532 // Lateral momentum conservation components
533 LibmeshPetscCall(MatDestroy(&_cmc_time_derivative_mat));
534 LibmeshPetscCall(VecDestroy(&_cmc_time_derivative_rhs));
535 LibmeshPetscCall(MatDestroy(&_cmc_advective_derivative_mat));
536 LibmeshPetscCall(VecDestroy(&_cmc_advective_derivative_rhs));
537 LibmeshPetscCall(MatDestroy(&_cmc_friction_force_mat));
538 LibmeshPetscCall(VecDestroy(&_cmc_friction_force_rhs));
539 LibmeshPetscCall(MatDestroy(&_cmc_pressure_force_mat));
540 LibmeshPetscCall(VecDestroy(&_cmc_pressure_force_rhs));
541 LibmeshPetscCall(MatDestroy(&_cmc_sys_Wij_mat));
542 LibmeshPetscCall(VecDestroy(&_cmc_sys_Wij_rhs));
543
544 // Energy conservation components
545 LibmeshPetscCall(MatDestroy(&_hc_time_derivative_mat));
546 LibmeshPetscCall(VecDestroy(&_hc_time_derivative_rhs));
547 LibmeshPetscCall(MatDestroy(&_hc_advective_derivative_mat));
548 LibmeshPetscCall(VecDestroy(&_hc_advective_derivative_rhs));
549 LibmeshPetscCall(MatDestroy(&_hc_cross_derivative_mat));
550 LibmeshPetscCall(VecDestroy(&_hc_cross_derivative_rhs));
551 LibmeshPetscCall(VecDestroy(&_hc_added_heat_rhs));
552 LibmeshPetscCall(MatDestroy(&_hc_sys_h_mat));
553 LibmeshPetscCall(VecDestroy(&_hc_sys_h_rhs));
554
555 PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
556}
557
558bool
560{
561 return _converged;
562}
563
564PetscScalar
566{
567 switch (_interpolation_scheme)
568 {
569 case 0: // upwind interpolation
570 return 1.0;
571 case 1: // downwind interpolation
572 return 0.0;
573 case 2: // central_difference interpolation
574 return 0.5;
575 case 3: // exponential interpolation (Peclet limited)
576 {
577 // Weight of the bottom value, alpha = 1 - 1 / Pe + 1 / (exp(Pe) - 1), from the exponential
578 // profile of the steady 1D advection-diffusion equation. It is evaluated with exp(-|Pe|)
579 // so that it stays finite for large |Pe|; the original form overflows to NaN once exp(Pe)
580 // exceeds the double range. For |Pe| below 1e-6 the series limit 0.5 + Pe / 12 is used
581 const auto Pe = Peclet;
582 if (std::abs(Pe) < 1e-6)
583 return 0.5 + Pe / 12.0;
584 const auto e = std::exp(-std::abs(Pe));
585 const auto alpha_pos = 1.0 - 1.0 / std::abs(Pe) + e / (1.0 - e);
586 return Pe > 0 ? alpha_pos : 1.0 - alpha_pos;
587 }
588 default:
590 ": Interpolation scheme should be a string: upwind, downwind, central_difference, "
591 "exponential");
592 }
593}
594
595PetscScalar
597 PetscScalar botValue,
598 PetscScalar Peclet)
599{
600 PetscScalar alpha = computeInterpolationCoefficients(Peclet);
601 return alpha * botValue + (1.0 - alpha) * topValue;
602}
603
604void
606{
607 const unsigned int last_node = (iblock + 1) * _block_size;
608 const unsigned int first_node = iblock * _block_size + 1;
609 // Initial guess, port crossflow of block (iblock) into a vector that will act as my initial guess
610 libMesh::DenseVector<Real> solution_seed(_n_gaps * _block_size, 0.0);
611 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
612 {
613 for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
614 {
615 int i = _n_gaps * (iz - first_node) + i_gap; // column wise transfer
616 solution_seed(i) = _Wij(i_gap, iz);
617 }
618 }
619
620 // Solving the combined lateral momentum equation for Wij using a PETSc solver and update vector
621 // root
623 LibmeshPetscCall(petscSnesSolver(iblock, solution_seed, root));
624
625 // Assign the solution to the cross-flow matrix
626 int i = 0;
627 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
628 {
629 for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
630 {
631 _Wij(i_gap, iz) = root(i);
632 i++;
633 }
634 }
635}
636
637void
639{
640 const unsigned int last_node = (iblock + 1) * _block_size;
641 const unsigned int first_node = iblock * _block_size + 1;
642 // Add to solution vector if explicit
643 if (!_implicit_bool)
644 {
645 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
646 {
647 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
648 {
649 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
650 Real sumWij = 0.0;
651 // Calculate sum of crossflow into channel i from channels j around i
652 unsigned int counter = 0;
653 for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
654 {
655 sumWij += _subchannel_mesh.getCrossflowSign(i_ch, counter) * _Wij(i_gap, iz);
656 counter++;
657 }
658 // The net crossflow coming out of cell i [kg/sec]
659 _SumWij_soln->set(node_out, sumWij);
660 }
661 }
662 }
663 // Add to matrix if implicit
664 else
665 {
666 LibmeshPetscCall(MatZeroEntries(_mc_density_pressure_mat));
667 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
668 {
669 unsigned int iz_ind = iz - first_node;
670 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
671 {
672 // Calculate sum of crossflow into channel i from channels j around i
673 unsigned int counter = 0;
674 for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
675 {
676 PetscInt row = i_ch + _n_channels * iz_ind;
677 PetscInt col = i_gap + _n_gaps * iz_ind;
678 PetscScalar value = _subchannel_mesh.getCrossflowSign(i_ch, counter);
679 LibmeshPetscCall(MatSetValues(_mc_sumWij_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
680 counter++;
681 }
682 }
683 }
684 LibmeshPetscCall(MatAssemblyBegin(_mc_sumWij_mat, MAT_FINAL_ASSEMBLY));
685 LibmeshPetscCall(MatAssemblyEnd(_mc_sumWij_mat, MAT_FINAL_ASSEMBLY));
687 {
688 Vec loc_prod;
689 Vec loc_Wij;
690 LibmeshPetscCall(VecDuplicate(_amc_sys_mdot_rhs, &loc_prod));
691 LibmeshPetscCall(VecDuplicate(_Wij_vec, &loc_Wij));
693 loc_Wij, _Wij, first_node, last_node, _n_gaps));
694 LibmeshPetscCall(MatMult(_mc_sumWij_mat, loc_Wij, loc_prod));
695 LibmeshPetscCall(populateSolutionChan<SolutionHandle>(
696 loc_prod, *_SumWij_soln, first_node, last_node, _n_channels));
697 LibmeshPetscCall(VecDestroy(&loc_prod));
698 LibmeshPetscCall(VecDestroy(&loc_Wij));
699 }
700 }
701}
702
703void
705{
706 const unsigned int last_node = (iblock + 1) * _block_size;
707 const unsigned int first_node = iblock * _block_size + 1;
708 if (!_implicit_bool)
709 {
710 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
711 {
712 auto dz = _z_grid[iz] - _z_grid[iz - 1];
713 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
714 {
715 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
716 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
717 auto volume = dz * (*_S_flow_soln)(node_in);
718 auto time_term = _TR * ((*_rho_soln)(node_out)-_rho_soln->old(node_out)) * volume / _dt;
719 // Wij positive out of i into j;
720 auto mdot_out = (*_mdot_soln)(node_in) - (*_SumWij_soln)(node_out)-time_term;
721 if (mdot_out < 0)
722 {
723 _console << "Wij = : " << _Wij << "\n";
725 " : Calculation of negative mass flow mdot_out = : ",
726 mdot_out,
727 " Axial Level= : ",
728 iz,
729 " - Implicit solves are required for recirculating flow.");
730 }
731 _mdot_soln->set(node_out, mdot_out); // kg/sec
732 }
733 }
734 }
735 else
736 {
737 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
738 {
739 auto dz = _z_grid[iz] - _z_grid[iz - 1];
740 auto iz_ind = iz - first_node;
741 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
742 {
743 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
744 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
745 auto volume = dz * (*_S_flow_soln)(node_in);
746
747 // Adding time derivative to the RHS
748 auto time_term = _TR * ((*_rho_soln)(node_out)-_rho_soln->old(node_out)) * volume / _dt;
749 PetscInt row_vec = i_ch + _n_channels * iz_ind;
750 PetscScalar value_vec = -1.0 * time_term;
751 LibmeshPetscCall(
752 VecSetValues(_mc_axial_convection_rhs, 1, &row_vec, &value_vec, INSERT_VALUES));
753
754 // Linearize transient density with respect to pressure at constant enthalpy. The
755 // temperature response at constant h is required even for a rho(T)-only liquid model:
756 //
757 // (d rho / d p)_h = (d rho / d p)_T
758 // - (d rho / d T)_p (d h / d p)_T / (d h / d T)_p.
759 //
760 // Without this Jacobian contribution, pressure-density coupling remains in the outer
761 // fixed-point iteration and scales as O(1 / dt). As the time step decreases, the iteration
762 // can become increasingly slow or noncontractive and may require stronger relaxation.
763 if (_TR && _compute_density && _compute_power && iz < last_node)
764 {
765 Real rho, drho_dp_T, drho_dT;
766 _fp->rho_from_p_T(
767 (*_P_soln)(node_out) + _P_out, (*_T_soln)(node_out), rho, drho_dp_T, drho_dT);
768 Real h, dh_dp_T, dh_dT;
769 _fp->h_from_p_T((*_P_soln)(node_out) + _P_out, (*_T_soln)(node_out), h, dh_dp_T, dh_dT);
770 const Real drho_dp_h = drho_dp_T - drho_dT * dh_dp_T / dh_dT;
771 const PetscScalar pressure_coefficient = volume / _dt * drho_dp_h;
772 const PetscInt pressure_col = i_ch + _n_channels * (iz_ind + 1);
773 LibmeshPetscCall(MatSetValues(_mc_density_pressure_mat,
774 1,
775 &row_vec,
776 1,
777 &pressure_col,
778 &pressure_coefficient,
779 INSERT_VALUES));
780 const PetscScalar linearization_rhs = pressure_coefficient * (*_P_soln)(node_out);
781 LibmeshPetscCall(
782 VecSetValues(_mc_axial_convection_rhs, 1, &row_vec, &linearization_rhs, ADD_VALUES));
783 }
784
785 // Imposing bottom boundary condition or adding of diagonal elements
786 if (iz == first_node)
787 {
788 PetscScalar value_vec = (*_mdot_soln)(node_in);
789 PetscInt row_vec = i_ch + _n_channels * iz_ind;
790 LibmeshPetscCall(
791 VecSetValues(_mc_axial_convection_rhs, 1, &row_vec, &value_vec, ADD_VALUES));
792 }
793 else
794 {
795 PetscInt row = i_ch + _n_channels * iz_ind;
796 PetscInt col = i_ch + _n_channels * (iz_ind - 1);
797 PetscScalar value = -1.0;
798 LibmeshPetscCall(
799 MatSetValues(_mc_axial_convection_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
800 }
801
802 // Adding diagonal elements
803 PetscInt row = i_ch + _n_channels * iz_ind;
804 PetscInt col = i_ch + _n_channels * iz_ind;
805 PetscScalar value = 1.0;
806 LibmeshPetscCall(
807 MatSetValues(_mc_axial_convection_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
808
809 // Adding cross flows RHS
811 {
812 PetscScalar value_vec_2 = -1.0 * (*_SumWij_soln)(node_out);
813 PetscInt row_vec_2 = i_ch + _n_channels * iz_ind;
814 LibmeshPetscCall(
815 VecSetValues(_mc_axial_convection_rhs, 1, &row_vec_2, &value_vec_2, ADD_VALUES));
816 }
817 }
818 }
819 LibmeshPetscCall(MatAssemblyBegin(_mc_axial_convection_mat, MAT_FINAL_ASSEMBLY));
820 LibmeshPetscCall(MatAssemblyEnd(_mc_axial_convection_mat, MAT_FINAL_ASSEMBLY));
821 LibmeshPetscCall(MatAssemblyBegin(_mc_density_pressure_mat, MAT_FINAL_ASSEMBLY));
822 LibmeshPetscCall(MatAssemblyEnd(_mc_density_pressure_mat, MAT_FINAL_ASSEMBLY));
823
825 {
826 KSP ksploc;
827 PC pc;
828 Vec sol;
829 LibmeshPetscCall(VecDuplicate(_mc_axial_convection_rhs, &sol));
830 LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksploc));
831 LibmeshPetscCall(KSPSetOperators(ksploc, _mc_axial_convection_mat, _mc_axial_convection_mat));
832 LibmeshPetscCall(KSPGetPC(ksploc, &pc));
833 LibmeshPetscCall(PCSetType(pc, PCJACOBI));
834 LibmeshPetscCall(KSPSetTolerances(ksploc, _rtol, _atol, _dtol, _maxit));
835 LibmeshPetscCall(KSPSetFromOptions(ksploc));
836 LibmeshPetscCall(KSPSolve(ksploc, _mc_axial_convection_rhs, sol));
837 LibmeshPetscCall(populateSolutionChan<SolutionHandle>(
838 sol, *_mdot_soln, first_node, last_node, _n_channels));
839 LibmeshPetscCall(VecZeroEntries(_mc_axial_convection_rhs));
840 LibmeshPetscCall(KSPDestroy(&ksploc));
841 LibmeshPetscCall(VecDestroy(&sol));
842 }
843 }
844}
845
846void
848{
849 const unsigned int last_node = (iblock + 1) * _block_size;
850 const unsigned int first_node = iblock * _block_size + 1;
851 if (!_implicit_bool)
852 {
853 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
854 {
855 auto k_grid = _subchannel_mesh.getKGrid();
856 auto dz = _z_grid[iz] - _z_grid[iz - 1];
857 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
858 {
859 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
860 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
861 auto rho_in = (*_rho_soln)(node_in);
862 auto rho_out = (*_rho_soln)(node_out);
863 auto mu_in = (*_mu_soln)(node_in);
864 auto S = (*_S_flow_soln)(node_in);
865 auto w_perim = (*_w_perim_soln)(node_in);
866 auto time_term = _TR * ((*_mdot_soln)(node_out)-_mdot_soln->old(node_out)) * dz / _dt -
867 dz * 2.0 * (*_mdot_soln)(node_out) * (rho_out - _rho_soln->old(node_out)) /
868 rho_in / _dt;
869 auto mass_term1 =
870 Utility::pow<2>((*_mdot_soln)(node_out)) * (1.0 / S / rho_out - 1.0 / S / rho_in);
871 auto mass_term2 = -2.0 * (*_mdot_soln)(node_out) * (*_SumWij_soln)(node_out) / S / rho_in;
872 auto crossflow_term = 0.0;
873 auto turbulent_term = 0.0;
874 unsigned int counter = 0;
875 for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
876 {
877 auto chans = _subchannel_mesh.getGapChannels(i_gap);
878 unsigned int ii_ch = chans.first;
879 unsigned int jj_ch = chans.second;
880 auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
881 auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
882 auto * node_out_i = _subchannel_mesh.getChannelNode(ii_ch, iz);
883 auto * node_out_j = _subchannel_mesh.getChannelNode(jj_ch, iz);
884 auto rho_i = (*_rho_soln)(node_in_i);
885 auto rho_j = (*_rho_soln)(node_in_j);
886 auto Si = (*_S_flow_soln)(node_in_i);
887 auto Sj = (*_S_flow_soln)(node_in_j);
888 Real u_star = 0.0;
889 // figure out donor axial velocity
890 if (_Wij(i_gap, iz) > 0.0)
891 u_star = (*_mdot_soln)(node_out_i) / Si / rho_i;
892 else
893 u_star = (*_mdot_soln)(node_out_j) / Sj / rho_j;
894
895 crossflow_term +=
896 _subchannel_mesh.getCrossflowSign(i_ch, counter) * _Wij(i_gap, iz) * u_star;
897
898 turbulent_term += _WijPrime(i_gap, iz) * (2 * (*_mdot_soln)(node_out) / rho_in / S -
899 (*_mdot_soln)(node_out_j) / Sj / rho_j -
900 (*_mdot_soln)(node_out_i) / Si / rho_i);
901 counter++;
902 }
903 turbulent_term *= _CT;
905 auto ki = 0.0;
906 if ((*_mdot_soln)(node_out) >= 0)
907 ki = k_grid[i_ch][iz - 1];
908 else
909 ki = k_grid[i_ch][iz];
910 // Cell Peclet number of the exponential scheme, from the friction of the cell evaluated at
911 // the arithmetic midpoint, as in the implicit branch
912 PetscScalar Pe = 0.5;
913 if (_interpolation_scheme == 3)
914 {
915 const auto S_mid = computeInterpolatedValue((*_S_flow_soln)(node_out), S, 0.5);
916 const auto w_perim_mid =
917 computeInterpolatedValue((*_w_perim_soln)(node_out), w_perim, 0.5);
918 const auto mdot_mid =
919 computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), 0.5);
920 const auto mu_mid = computeInterpolatedValue((*_mu_soln)(node_out), mu_in, 0.5);
921 const auto Dh_mid = 4.0 * S_mid / w_perim_mid;
923 i_ch, std::abs(mdot_mid) / S_mid * Dh_mid / mu_mid, S_mid, w_perim_mid);
925 Pe = 1.0 / ((ff_mid * dz / Dh_mid + ki) * 0.5) * mdot_mid / std::abs(mdot_mid);
926 }
927 // The friction factor and the friction force are cell-center quantities
928 const auto S_c = computeInterpolatedValue((*_S_flow_soln)(node_out), S, Pe);
929 const auto w_perim_c = computeInterpolatedValue((*_w_perim_soln)(node_out), w_perim, Pe);
930 const auto mu_c = computeInterpolatedValue((*_mu_soln)(node_out), mu_in, Pe);
931 const auto rho_c = computeInterpolatedValue(rho_out, rho_in, Pe);
932 const auto mdot_c =
933 computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), Pe);
934 const auto Dh_c = 4.0 * S_c / w_perim_c;
935 auto Re = std::abs(mdot_c) / S_c * Dh_c / mu_c;
936 _friction_args = FrictionStruct(i_ch, Re, S_c, w_perim_c);
938 if (_ff_soln)
939 _ff_soln->set(node_out, ff);
940 auto friction_term =
941 (ff * dz / Dh_c + ki) * 0.5 * mdot_c * std::abs(mdot_c) / (S_c * rho_c);
942 auto gravity_term = _dir_grav * _g_grav * (*_rho_soln)(node_out)*dz * S;
943 auto DP = (1 / S) * (time_term + mass_term1 + mass_term2 + crossflow_term + turbulent_term +
944 friction_term + gravity_term); // Pa
945 _DP(i_ch, iz) = DP;
946 if (_DP_soln)
947 _DP_soln->set(node_out, DP);
948 }
949 }
950 }
951 else
952 {
953 LibmeshPetscCall(MatZeroEntries(_amc_time_derivative_mat));
954 LibmeshPetscCall(MatZeroEntries(_amc_advective_derivative_mat));
955 LibmeshPetscCall(MatZeroEntries(_amc_cross_derivative_mat));
956 LibmeshPetscCall(MatZeroEntries(_amc_friction_force_mat));
957 LibmeshPetscCall(VecZeroEntries(_amc_time_derivative_rhs));
958 LibmeshPetscCall(VecZeroEntries(_amc_advective_derivative_rhs));
959 LibmeshPetscCall(VecZeroEntries(_amc_cross_derivative_rhs));
960 LibmeshPetscCall(VecZeroEntries(_amc_friction_force_rhs));
961 LibmeshPetscCall(VecZeroEntries(_amc_gravity_rhs));
962 LibmeshPetscCall(MatZeroEntries(_amc_sys_mdot_mat));
963 LibmeshPetscCall(VecZeroEntries(_amc_sys_mdot_rhs));
964 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
965 {
966 auto k_grid = _subchannel_mesh.getKGrid();
967 auto dz = _z_grid[iz] - _z_grid[iz - 1];
968 auto iz_ind = iz - first_node;
969 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
970 {
971 // inlet and outlet nodes
972 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
973 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
974
975 // interpolation weight coefficient
976 PetscScalar Pe = 0.5;
977 if (_interpolation_scheme == 3)
978 {
979 // Compute the Peclet number
980 auto S_in = (*_S_flow_soln)(node_in);
981 auto S_out = (*_S_flow_soln)(node_out);
982 auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
983 auto w_perim_in = (*_w_perim_soln)(node_in);
984 auto w_perim_out = (*_w_perim_soln)(node_out);
985 auto w_perim_interp = this->computeInterpolatedValue(w_perim_out, w_perim_in, 0.5);
986 auto mdot_loc =
987 this->computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), 0.5);
988 auto mu_in = (*_mu_soln)(node_in);
989 auto mu_out = (*_mu_soln)(node_out);
990 auto mu_interp = this->computeInterpolatedValue(mu_out, mu_in, 0.5);
991 auto Dh_c = 4.0 * S_interp / w_perim_interp;
992 // Compute friction factor
993 auto Re = ((std::abs(mdot_loc) / S_interp) * Dh_c / mu_interp);
994 _friction_args = FrictionStruct(i_ch, Re, S_interp, w_perim_interp);
996 if (_ff_soln)
997 _ff_soln->set(node_out, ff);
999 auto ki = 0.0;
1000 if ((*_mdot_soln)(node_out) >= 0)
1001 ki = k_grid[i_ch][iz - 1];
1002 else
1003 ki = k_grid[i_ch][iz];
1004 Pe = 1.0 / ((ff * dz / Dh_c + ki) * 0.5) * mdot_loc / std::abs(mdot_loc);
1005 }
1006 auto alpha = computeInterpolationCoefficients(Pe);
1007
1008 // inlet, outlet, and interpolated density
1009 auto rho_in = (*_rho_soln)(node_in);
1010 auto rho_out = (*_rho_soln)(node_out);
1011 auto rho_interp = computeInterpolatedValue(rho_out, rho_in, Pe);
1012
1013 // inlet, outlet, and interpolated viscosity
1014 auto mu_in = (*_mu_soln)(node_in);
1015 auto mu_out = (*_mu_soln)(node_out);
1016 auto mu_interp = computeInterpolatedValue(mu_out, mu_in, Pe);
1017
1018 // inlet, outlet, and interpolated axial surface area
1019 auto S_in = (*_S_flow_soln)(node_in);
1020 auto S_out = (*_S_flow_soln)(node_out);
1021 auto S_interp = computeInterpolatedValue(S_out, S_in, Pe);
1022
1023 // inlet, outlet, and interpolated wetted perimeter
1024 auto w_perim_in = (*_w_perim_soln)(node_in);
1025 auto w_perim_out = (*_w_perim_soln)(node_out);
1026 auto w_perim_interp = computeInterpolatedValue(w_perim_out, w_perim_in, Pe);
1027
1028 // cell-center hydraulic diameter
1029 auto Dh_c = 4.0 * S_interp / w_perim_interp;
1030
1031 // Keep temporal storage independent of the spatial interpolation scheme. Applying the
1032 // upwind coefficient to this term puts the storage on the upstream node and leaves the
1033 // first axial row without transient inertia in the upwind limit.
1034 PetscInt row_tt = i_ch + _n_channels * iz_ind;
1035 PetscInt col_tt = i_ch + _n_channels * iz_ind;
1036 PetscScalar value_tt = _TR * dz / _dt;
1037 LibmeshPetscCall(MatSetValues(
1038 _amc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
1039
1040 PetscScalar value_vec_tt = _TR * _mdot_soln->old(node_out) * dz / _dt;
1041 PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
1042 LibmeshPetscCall(
1043 VecSetValues(_amc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
1044
1047 if (iz == first_node)
1048 {
1049 PetscScalar value_vec_at = Utility::pow<2>((*_mdot_soln)(node_in)) / (S_in * rho_in);
1050 PetscInt row_vec_at = i_ch + _n_channels * iz_ind;
1051 LibmeshPetscCall(VecSetValues(
1052 _amc_advective_derivative_rhs, 1, &row_vec_at, &value_vec_at, ADD_VALUES));
1053 }
1054 else
1055 {
1056 PetscInt row_at = i_ch + _n_channels * iz_ind;
1057 PetscInt col_at = i_ch + _n_channels * (iz_ind - 1);
1058 PetscScalar value_at = -1.0 * (*_mdot_soln)(node_in) / (S_in * rho_in);
1059 LibmeshPetscCall(MatSetValues(
1060 _amc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1061 }
1062
1063 // Adding diagonal elements
1064 PetscInt row_at = i_ch + _n_channels * iz_ind;
1065 PetscInt col_at = i_ch + _n_channels * iz_ind;
1066 PetscScalar value_at = (*_mdot_soln)(node_out) / (S_out * rho_out);
1067 LibmeshPetscCall(MatSetValues(
1068 _amc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1069
1071 unsigned int counter = 0;
1072 unsigned int cross_index = iz; // iz-1;
1073 for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
1074 {
1075 auto chans = _subchannel_mesh.getGapChannels(i_gap);
1076 unsigned int ii_ch = chans.first;
1077 unsigned int jj_ch = chans.second;
1078 auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
1079 auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
1080 auto * node_out_i = _subchannel_mesh.getChannelNode(ii_ch, iz);
1081 auto * node_out_j = _subchannel_mesh.getChannelNode(jj_ch, iz);
1082 auto rho_i =
1083 computeInterpolatedValue((*_rho_soln)(node_out_i), (*_rho_soln)(node_in_i), Pe);
1084 auto rho_j =
1085 computeInterpolatedValue((*_rho_soln)(node_out_j), (*_rho_soln)(node_in_j), Pe);
1086 auto S_i =
1087 computeInterpolatedValue((*_S_flow_soln)(node_out_i), (*_S_flow_soln)(node_in_i), Pe);
1088 auto S_j =
1089 computeInterpolatedValue((*_S_flow_soln)(node_out_j), (*_S_flow_soln)(node_in_j), Pe);
1090 auto u_star = 0.0;
1091 // figure out donor axial velocity
1092 if (_Wij(i_gap, cross_index) > 0.0)
1093 {
1094 if (iz == first_node)
1095 {
1096 u_star = (*_mdot_soln)(node_in_i) / S_i / rho_i;
1097 PetscScalar value_vec_ct = -1.0 * alpha *
1098 _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1099 _Wij(i_gap, cross_index) * u_star;
1100 PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1101 LibmeshPetscCall(VecSetValues(
1102 _amc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1103 }
1104 else
1105 {
1106 PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1107 _Wij(i_gap, cross_index) / S_i / rho_i;
1108 PetscInt row_ct = i_ch + _n_channels * iz_ind;
1109 PetscInt col_ct = ii_ch + _n_channels * (iz_ind - 1);
1110 LibmeshPetscCall(MatSetValues(
1111 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1112 }
1113 PetscScalar value_ct = (1.0 - alpha) *
1114 _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1115 _Wij(i_gap, cross_index) / S_i / rho_i;
1116 PetscInt row_ct = i_ch + _n_channels * iz_ind;
1117 PetscInt col_ct = ii_ch + _n_channels * iz_ind;
1118 LibmeshPetscCall(MatSetValues(
1119 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1120 }
1121 else if (_Wij(i_gap, cross_index) < 0.0) // _Wij=0 operations not necessary
1122 {
1123 if (iz == first_node)
1124 {
1125 u_star = (*_mdot_soln)(node_in_j) / S_j / rho_j;
1126 PetscScalar value_vec_ct = -1.0 * alpha *
1127 _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1128 _Wij(i_gap, cross_index) * u_star;
1129 PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1130 LibmeshPetscCall(VecSetValues(
1131 _amc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1132 }
1133 else
1134 {
1135 PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1136 _Wij(i_gap, cross_index) / S_j / rho_j;
1137 PetscInt row_ct = i_ch + _n_channels * iz_ind;
1138 PetscInt col_ct = jj_ch + _n_channels * (iz_ind - 1);
1139 LibmeshPetscCall(MatSetValues(
1140 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1141 }
1142 PetscScalar value_ct = (1.0 - alpha) *
1143 _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1144 _Wij(i_gap, cross_index) / S_j / rho_j;
1145 PetscInt row_ct = i_ch + _n_channels * iz_ind;
1146 PetscInt col_ct = jj_ch + _n_channels * iz_ind;
1147 LibmeshPetscCall(MatSetValues(
1148 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1149 }
1150
1151 if (iz == first_node)
1152 {
1153 PetscScalar value_vec_ct = -2.0 * alpha * (*_mdot_soln)(node_in)*_CT *
1154 _WijPrime(i_gap, cross_index) / (rho_interp * S_interp);
1155 value_vec_ct += alpha * (*_mdot_soln)(node_in_j)*_CT * _WijPrime(i_gap, cross_index) /
1156 (rho_j * S_j);
1157 value_vec_ct += alpha * (*_mdot_soln)(node_in_i)*_CT * _WijPrime(i_gap, cross_index) /
1158 (rho_i * S_i);
1159 PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1160 LibmeshPetscCall(
1161 VecSetValues(_amc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1162 }
1163 else
1164 {
1165 PetscScalar value_center_ct =
1166 2.0 * alpha * _CT * _WijPrime(i_gap, cross_index) / (rho_interp * S_interp);
1167 PetscInt row_ct = i_ch + _n_channels * iz_ind;
1168 PetscInt col_ct = i_ch + _n_channels * (iz_ind - 1);
1169 LibmeshPetscCall(MatSetValues(
1170 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
1171
1172 PetscScalar value_left_ct =
1173 -1.0 * alpha * _CT * _WijPrime(i_gap, cross_index) / (rho_j * S_j);
1174 row_ct = i_ch + _n_channels * iz_ind;
1175 col_ct = jj_ch + _n_channels * (iz_ind - 1);
1176 LibmeshPetscCall(MatSetValues(
1177 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
1178
1179 PetscScalar value_right_ct =
1180 -1.0 * alpha * _CT * _WijPrime(i_gap, cross_index) / (rho_i * S_i);
1181 row_ct = i_ch + _n_channels * iz_ind;
1182 col_ct = ii_ch + _n_channels * (iz_ind - 1);
1183 LibmeshPetscCall(MatSetValues(
1184 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
1185 }
1186
1187 PetscScalar value_center_ct =
1188 2.0 * (1.0 - alpha) * _CT * _WijPrime(i_gap, cross_index) / (rho_interp * S_interp);
1189 PetscInt row_ct = i_ch + _n_channels * iz_ind;
1190 PetscInt col_ct = i_ch + _n_channels * iz_ind;
1191 LibmeshPetscCall(MatSetValues(
1192 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
1193
1194 PetscScalar value_left_ct =
1195 -1.0 * (1.0 - alpha) * _CT * _WijPrime(i_gap, cross_index) / (rho_j * S_j);
1196 row_ct = i_ch + _n_channels * iz_ind;
1197 col_ct = jj_ch + _n_channels * iz_ind;
1198 LibmeshPetscCall(MatSetValues(
1199 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
1200
1201 PetscScalar value_right_ct =
1202 -1.0 * (1.0 - alpha) * _CT * _WijPrime(i_gap, cross_index) / (rho_i * S_i);
1203 row_ct = i_ch + _n_channels * iz_ind;
1204 col_ct = ii_ch + _n_channels * iz_ind;
1205 LibmeshPetscCall(MatSetValues(
1206 _amc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
1207 counter++;
1208 }
1209
1211 PetscScalar mdot_interp =
1212 computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), Pe);
1213 auto Re = ((std::abs(mdot_interp) / S_interp) * Dh_c / mu_interp);
1214 _friction_args = FrictionStruct(i_ch, Re, S_interp, w_perim_interp);
1216 if (_ff_soln)
1217 _ff_soln->set(node_out, ff);
1219 auto ki = 0.0;
1220 if ((*_mdot_soln)(node_out) >= 0)
1221 ki = k_grid[i_ch][iz - 1];
1222 else
1223 ki = k_grid[i_ch][iz];
1224 auto coef = (ff * dz / Dh_c + ki) * 0.5 * std::abs(mdot_interp) / (S_interp * rho_interp);
1225 if (iz == first_node)
1226 {
1227 PetscScalar value_vec = -1.0 * alpha * coef * (*_mdot_soln)(node_in);
1228 PetscInt row_vec = i_ch + _n_channels * iz_ind;
1229 LibmeshPetscCall(
1230 VecSetValues(_amc_friction_force_rhs, 1, &row_vec, &value_vec, ADD_VALUES));
1231 }
1232 else
1233 {
1234 PetscInt row = i_ch + _n_channels * iz_ind;
1235 PetscInt col = i_ch + _n_channels * (iz_ind - 1);
1236 PetscScalar value = alpha * coef;
1237 LibmeshPetscCall(
1238 MatSetValues(_amc_friction_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
1239 }
1240
1241 // Adding diagonal elements
1242 PetscInt row = i_ch + _n_channels * iz_ind;
1243 PetscInt col = i_ch + _n_channels * iz_ind;
1244 PetscScalar value = (1.0 - alpha) * coef;
1245 LibmeshPetscCall(
1246 MatSetValues(_amc_friction_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
1247
1249 PetscScalar value_vec = _dir_grav * -1.0 * _g_grav * rho_interp * dz * S_interp;
1250 PetscInt row_vec = i_ch + _n_channels * iz_ind;
1251 LibmeshPetscCall(VecSetValues(_amc_gravity_rhs, 1, &row_vec, &value_vec, ADD_VALUES));
1252 }
1253 }
1255 LibmeshPetscCall(MatZeroEntries(_amc_sys_mdot_mat));
1256 LibmeshPetscCall(VecZeroEntries(_amc_sys_mdot_rhs));
1257 LibmeshPetscCall(MatAssemblyBegin(_amc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1258 LibmeshPetscCall(MatAssemblyEnd(_amc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1259 LibmeshPetscCall(MatAssemblyBegin(_amc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1260 LibmeshPetscCall(MatAssemblyEnd(_amc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1261 LibmeshPetscCall(MatAssemblyBegin(_amc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1262 LibmeshPetscCall(MatAssemblyEnd(_amc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1263 LibmeshPetscCall(MatAssemblyBegin(_amc_friction_force_mat, MAT_FINAL_ASSEMBLY));
1264 LibmeshPetscCall(MatAssemblyEnd(_amc_friction_force_mat, MAT_FINAL_ASSEMBLY));
1265 LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1266 LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1267 // Matrix
1268#if !PETSC_VERSION_LESS_THAN(3, 15, 0)
1269 LibmeshPetscCall(
1270 MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_time_derivative_mat, UNKNOWN_NONZERO_PATTERN));
1271 LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1272 LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1273 LibmeshPetscCall(
1274 MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_advective_derivative_mat, UNKNOWN_NONZERO_PATTERN));
1275 LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1276 LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1277 LibmeshPetscCall(
1278 MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_cross_derivative_mat, UNKNOWN_NONZERO_PATTERN));
1279 LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1280 LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1281 LibmeshPetscCall(
1282 MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_friction_force_mat, UNKNOWN_NONZERO_PATTERN));
1283#else
1284 LibmeshPetscCall(
1285 MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_time_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1286 LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1287 LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1288 LibmeshPetscCall(
1289 MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_advective_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1290 LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1291 LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1292 LibmeshPetscCall(
1293 MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_cross_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1294 LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1295 LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1296 LibmeshPetscCall(
1297 MatAXPY(_amc_sys_mdot_mat, 1.0, _amc_friction_force_mat, DIFFERENT_NONZERO_PATTERN));
1298#endif
1299 LibmeshPetscCall(MatAssemblyBegin(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1300 LibmeshPetscCall(MatAssemblyEnd(_amc_sys_mdot_mat, MAT_FINAL_ASSEMBLY));
1301 // RHS
1302 LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_time_derivative_rhs));
1303 LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_advective_derivative_rhs));
1304 LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_cross_derivative_rhs));
1305 LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_friction_force_rhs));
1306 LibmeshPetscCall(VecAXPY(_amc_sys_mdot_rhs, 1.0, _amc_gravity_rhs));
1307 if (_segregated_bool)
1308 {
1309 // Assembly the matrix system
1310 LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
1311 _prod, *_mdot_soln, first_node, last_node, _n_channels));
1312 Vec ls;
1313 LibmeshPetscCall(VecDuplicate(_amc_sys_mdot_rhs, &ls));
1314 LibmeshPetscCall(MatMult(_amc_sys_mdot_mat, _prod, ls));
1315 LibmeshPetscCall(VecAXPY(ls, -1.0, _amc_sys_mdot_rhs));
1316 PetscScalar * xx;
1317 LibmeshPetscCall(VecGetArray(ls, &xx));
1318 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1319 {
1320 auto iz_ind = iz - first_node;
1321 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1322 {
1323 // Setting nodes
1324 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1325 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
1326
1327 // inlet, outlet, and interpolated axial surface area
1328 auto S_in = (*_S_flow_soln)(node_in);
1329 auto S_out = (*_S_flow_soln)(node_out);
1330 auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
1331
1332 // Setting solutions
1333 if (S_interp != 0)
1334 {
1335 auto DP = (1 / S_interp) * xx[iz_ind * _n_channels + i_ch];
1336 _DP(i_ch, iz) = DP;
1337 if (_DP_soln)
1338 _DP_soln->set(node_out, DP);
1339 }
1340 else
1341 {
1342 auto DP = 0.0;
1343 _DP(i_ch, iz) = DP;
1344 if (_DP_soln)
1345 _DP_soln->set(node_out, DP);
1346 }
1347 }
1348 }
1349 LibmeshPetscCall(VecDestroy(&ls));
1350 }
1351 }
1352}
1353
1354void
1356{
1357 const unsigned int last_node = (iblock + 1) * _block_size;
1358 const unsigned int first_node = iblock * _block_size + 1;
1359 if (!_implicit_bool)
1360 {
1361 for (unsigned int iz = last_node; iz > first_node - 1; iz--)
1362 {
1363 // Calculate pressure in the inlet of the cell assuming known outlet
1364 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1365 {
1366 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
1367 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1368 // update Pressure solution
1369 _P_soln->set(node_in, (*_P_soln)(node_out) + _DP(i_ch, iz));
1370 }
1371 }
1372 }
1373 else
1374 {
1375 LibmeshPetscCall(VecZeroEntries(_amc_pressure_force_rhs));
1376 for (unsigned int iz = last_node; iz > first_node - 1; iz--)
1377 {
1378 auto iz_ind = iz - first_node;
1379 // Calculate pressure in the inlet of the cell assuming known outlet
1380 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1381 {
1382 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
1383 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1384
1385 // inlet, outlet, and interpolated axial surface area
1386 auto S_in = (*_S_flow_soln)(node_in);
1387 auto S_out = (*_S_flow_soln)(node_out);
1388 auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
1389
1390 // Creating matrix of coefficients
1391 PetscInt row = i_ch + _n_channels * iz_ind;
1392 PetscInt col = i_ch + _n_channels * iz_ind;
1393 PetscScalar value = -1.0 * S_interp;
1394 LibmeshPetscCall(
1395 MatSetValues(_amc_pressure_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
1396
1397 // Row iz_ind is the pressure force on cell iz, S_c * (P(iz) - P(iz - 1)), and its unknown
1398 // is the inlet pressure P(iz - 1). The outlet pressure of the top cell of the block is
1399 // known (the outlet boundary condition or the block above), so it goes to the right-hand
1400 // side. For the other cells, P(iz) is the unknown of the next row.
1401 if (iz == last_node)
1402 {
1403 PetscScalar value = -1.0 * (*_P_soln)(node_out)*S_interp;
1404 PetscInt row = i_ch + _n_channels * iz_ind;
1405 LibmeshPetscCall(VecSetValues(_amc_pressure_force_rhs, 1, &row, &value, ADD_VALUES));
1406 }
1407 else
1408 {
1409 PetscInt row = i_ch + _n_channels * iz_ind;
1410 PetscInt col = i_ch + _n_channels * (iz_ind + 1);
1411 PetscScalar value = 1.0 * S_interp;
1412 LibmeshPetscCall(
1413 MatSetValues(_amc_pressure_force_mat, 1, &row, 1, &col, &value, INSERT_VALUES));
1414 }
1415
1416 // The segregated solver takes the cell pressure drop DP from the axial momentum solve
1417 // (computeDP), so each row reduces to P(iz - 1) = P(iz) + DP(iz). The monolithic solver
1418 // omits this term because the same matrix is the pressure force in its axial momentum
1419 // equation, which determines P directly.
1420 if (_segregated_bool)
1421 {
1422 auto dp_out = _DP(i_ch, iz);
1423 PetscScalar value_v = -1.0 * dp_out * S_interp;
1424 PetscInt row_v = i_ch + _n_channels * iz_ind;
1425 LibmeshPetscCall(VecSetValues(_amc_pressure_force_rhs, 1, &row_v, &value_v, ADD_VALUES));
1426 }
1427 }
1428 }
1429 // Solving pressure problem
1430 LibmeshPetscCall(MatAssemblyBegin(_amc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
1431 LibmeshPetscCall(MatAssemblyEnd(_amc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
1432 if (_segregated_bool)
1433 {
1434 KSP ksploc;
1435 PC pc;
1436 Vec sol;
1437 LibmeshPetscCall(VecDuplicate(_amc_pressure_force_rhs, &sol));
1438 LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksploc));
1439 LibmeshPetscCall(KSPSetOperators(ksploc, _amc_pressure_force_mat, _amc_pressure_force_mat));
1440 LibmeshPetscCall(KSPGetPC(ksploc, &pc));
1441 LibmeshPetscCall(PCSetType(pc, PCJACOBI));
1442 LibmeshPetscCall(KSPSetTolerances(ksploc, _rtol, _atol, _dtol, _maxit));
1443 LibmeshPetscCall(KSPSetFromOptions(ksploc));
1444 LibmeshPetscCall(KSPSolve(ksploc, _amc_pressure_force_rhs, sol));
1445 PetscScalar * xx;
1446 LibmeshPetscCall(VecGetArray(sol, &xx));
1447 // update Pressure solution
1448 for (unsigned int iz = last_node; iz > first_node - 1; iz--)
1449 {
1450 auto iz_ind = iz - first_node;
1451 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1452 {
1453 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1454 PetscScalar value = xx[iz_ind * _n_channels + i_ch];
1455 _P_soln->set(node_in, value);
1456 }
1457 }
1458 LibmeshPetscCall(VecZeroEntries(_amc_pressure_force_rhs));
1459 LibmeshPetscCall(KSPDestroy(&ksploc));
1460 LibmeshPetscCall(VecDestroy(&sol));
1461 }
1462 }
1463}
1464
1465Real
1467{
1468 const unsigned int last_node = (iblock + 1) * _block_size;
1469 const unsigned int first_node = iblock * _block_size + 1;
1470 std::vector<Real> residual;
1471 residual.reserve(_block_size * _n_channels);
1472 Real residual_norm_sq = 0.0;
1473 Real temperature_norm_sq = 0.0;
1474 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1475 {
1476 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1477 {
1478 auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
1479 const Real T = (*_T_soln)(node);
1480 const Real T_from_ph = _fp->T_from_p_h((*_P_soln)(node) + _P_out, (*_h_soln)(node));
1481 residual.push_back(T_from_ph - T);
1482 residual_norm_sq += Utility::pow<2>(residual.back());
1483 temperature_norm_sq += Utility::pow<2>(T);
1484 }
1485 }
1486
1487 // Set the temperature solution with relaxation if needed
1488 std::size_t i = 0;
1489 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1490 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1491 {
1492 auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
1493 _T_soln->set(node, (*_T_soln)(node) + _T_relaxation * residual[i]);
1494 ++i;
1495 }
1496
1497 return std::sqrt(residual_norm_sq) / (std::sqrt(temperature_norm_sq) + 1e-14);
1498}
1499
1500void
1502{
1503 const unsigned int last_node = (iblock + 1) * _block_size;
1504 const unsigned int first_node = iblock * _block_size + 1;
1505 if (iblock == 0)
1506 {
1507 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1508 {
1509 auto * node = _subchannel_mesh.getChannelNode(i_ch, 0);
1510 _rho_soln->set(node, _fp->rho_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node)));
1511 }
1512 }
1513 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1514 {
1515 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1516 {
1517 auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
1518 _rho_soln->set(node, _fp->rho_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node)));
1519 }
1520 }
1521}
1522
1523void
1525{
1526 const unsigned int last_node = (iblock + 1) * _block_size;
1527 const unsigned int first_node = iblock * _block_size + 1;
1528 if (iblock == 0)
1529 {
1530 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1531 {
1532 auto * node = _subchannel_mesh.getChannelNode(i_ch, 0);
1533 _mu_soln->set(node, _fp->mu_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node)));
1534 }
1535 }
1536 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1537 {
1538 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1539 {
1540 auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
1541 _mu_soln->set(node, _fp->mu_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node)));
1542 }
1543 }
1544}
1545
1546void
1548{
1549 const unsigned int last_node = (iblock + 1) * _block_size;
1550 const unsigned int first_node = iblock * _block_size + 1;
1551 // Cross flow residual
1552 if (!_implicit_bool)
1553 {
1554 const Real & pitch = _subchannel_mesh.getPitch();
1555 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1556 {
1557 auto dz = _z_grid[iz] - _z_grid[iz - 1];
1558 for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
1559 {
1560 auto chans = _subchannel_mesh.getGapChannels(i_gap);
1561 unsigned int i_ch = chans.first;
1562 unsigned int j_ch = chans.second;
1563 auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1564 auto * node_out_i = _subchannel_mesh.getChannelNode(i_ch, iz);
1565 auto * node_in_j = _subchannel_mesh.getChannelNode(j_ch, iz - 1);
1566 auto * node_out_j = _subchannel_mesh.getChannelNode(j_ch, iz);
1567 auto rho_i = (*_rho_soln)(node_in_i);
1568 auto rho_j = (*_rho_soln)(node_in_j);
1569 auto Si = (*_S_flow_soln)(node_in_i);
1570 auto Sj = (*_S_flow_soln)(node_in_j);
1571 auto Sij = dz * _subchannel_mesh.getGapWidth(iz, i_gap);
1572 auto Lij = pitch;
1573 // total local form loss in the ij direction
1574 auto friction_term = _kij * _Wij(i_gap, iz) * std::abs(_Wij(i_gap, iz));
1575 auto DPij = (*_P_soln)(node_in_i) - (*_P_soln)(node_in_j);
1576 // Figure out donor cell density
1577 auto rho_star = 0.0;
1578 if (_Wij(i_gap, iz) > 0.0)
1579 rho_star = rho_i;
1580 else if (_Wij(i_gap, iz) < 0.0)
1581 rho_star = rho_j;
1582 else
1583 rho_star = (rho_i + rho_j) / 2.0;
1584 auto mass_term_out =
1585 (*_mdot_soln)(node_out_i) / (*_S_flow_soln)(node_out_i) / (*_rho_soln)(node_out_i) +
1586 (*_mdot_soln)(node_out_j) / (*_S_flow_soln)(node_out_j) / (*_rho_soln)(node_out_j);
1587 auto mass_term_in =
1588 (*_mdot_soln)(node_in_i) / Si / rho_i + (*_mdot_soln)(node_in_j) / Sj / rho_j;
1589 auto term_out = Sij * rho_star * (Lij / dz) * mass_term_out * _Wij(i_gap, iz);
1590 auto term_in = Sij * rho_star * (Lij / dz) * mass_term_in * _Wij(i_gap, iz - 1);
1591 auto inertia_term = term_out - term_in;
1592 auto pressure_term = 2 * Utility::pow<2>(Sij) * DPij * rho_star;
1593 auto time_term =
1594 _TR * 2.0 * (_Wij(i_gap, iz) - _Wij_old(i_gap, iz)) * Lij * Sij * rho_star / _dt;
1595
1596 _Wij_residual_matrix(i_gap, iz - 1 - iblock * _block_size) =
1597 time_term + friction_term + inertia_term - pressure_term;
1598 }
1599 }
1600 }
1601 else
1602 {
1603 // Initializing to zero the elements of the lateral momentum assembly
1604 LibmeshPetscCall(MatZeroEntries(_cmc_time_derivative_mat));
1605 LibmeshPetscCall(MatZeroEntries(_cmc_advective_derivative_mat));
1606 LibmeshPetscCall(MatZeroEntries(_cmc_friction_force_mat));
1607 LibmeshPetscCall(MatZeroEntries(_cmc_pressure_force_mat));
1608 LibmeshPetscCall(VecZeroEntries(_cmc_time_derivative_rhs));
1609 LibmeshPetscCall(VecZeroEntries(_cmc_advective_derivative_rhs));
1610 LibmeshPetscCall(VecZeroEntries(_cmc_friction_force_rhs));
1611 LibmeshPetscCall(VecZeroEntries(_cmc_pressure_force_rhs));
1612 LibmeshPetscCall(MatZeroEntries(_cmc_sys_Wij_mat));
1613 LibmeshPetscCall(VecZeroEntries(_cmc_sys_Wij_rhs));
1614 const Real & pitch = _subchannel_mesh.getPitch();
1615 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1616 {
1617 auto dz = _z_grid[iz] - _z_grid[iz - 1];
1618 auto iz_ind = iz - first_node;
1619 for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
1620 {
1621 auto chans = _subchannel_mesh.getGapChannels(i_gap);
1622 unsigned int i_ch = chans.first;
1623 unsigned int j_ch = chans.second;
1624 auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1625 auto * node_out_i = _subchannel_mesh.getChannelNode(i_ch, iz);
1626 auto * node_in_j = _subchannel_mesh.getChannelNode(j_ch, iz - 1);
1627 auto * node_out_j = _subchannel_mesh.getChannelNode(j_ch, iz);
1628
1629 // inlet, outlet, and interpolated densities
1630 auto rho_i_in = (*_rho_soln)(node_in_i);
1631 auto rho_i_out = (*_rho_soln)(node_out_i);
1632 auto rho_i_interp = computeInterpolatedValue(rho_i_out, rho_i_in, 0.5);
1633 auto rho_j_in = (*_rho_soln)(node_in_j);
1634 auto rho_j_out = (*_rho_soln)(node_out_j);
1635 auto rho_j_interp = computeInterpolatedValue(rho_j_out, rho_j_in, 0.5);
1636
1637 // inlet, outlet, and interpolated areas
1638 auto S_i_in = (*_S_flow_soln)(node_in_i);
1639 auto S_i_out = (*_S_flow_soln)(node_out_i);
1640 auto S_j_in = (*_S_flow_soln)(node_in_j);
1641 auto S_j_out = (*_S_flow_soln)(node_out_j);
1642
1643 // Cross-sectional gap area
1644 auto Sij = dz * _subchannel_mesh.getGapWidth(iz, i_gap);
1645 auto Lij = pitch;
1646
1647 // Figure out donor cell density
1648 auto rho_star = 0.0;
1649 if (_Wij(i_gap, iz) > 0.0)
1650 rho_star = rho_i_interp;
1651 else if (_Wij(i_gap, iz) < 0.0)
1652 rho_star = rho_j_interp;
1653 else
1654 rho_star = (rho_i_interp + rho_j_interp) / 2.0;
1655
1656 // Assembling time derivative
1657 PetscScalar time_factor = _TR * Lij * Sij * rho_star / _dt;
1658 PetscInt row_td = i_gap + _n_gaps * iz_ind;
1659 PetscInt col_td = i_gap + _n_gaps * iz_ind;
1660 PetscScalar value_td = time_factor;
1661 LibmeshPetscCall(MatSetValues(
1662 _cmc_time_derivative_mat, 1, &row_td, 1, &col_td, &value_td, INSERT_VALUES));
1663 PetscScalar value_td_rhs = time_factor * _Wij_old(i_gap, iz);
1664 LibmeshPetscCall(
1665 VecSetValues(_cmc_time_derivative_rhs, 1, &row_td, &value_td_rhs, INSERT_VALUES));
1666
1667 // Assembling inertial term
1668 PetscScalar Pe = 0.5;
1669 auto alpha = computeInterpolationCoefficients(Pe);
1670 auto mass_term_out = (*_mdot_soln)(node_out_i) / S_i_out / rho_i_out +
1671 (*_mdot_soln)(node_out_j) / S_j_out / rho_j_out;
1672 auto mass_term_in = (*_mdot_soln)(node_in_i) / S_i_in / rho_i_in +
1673 (*_mdot_soln)(node_in_j) / S_j_in / rho_j_in;
1674 auto term_out = Sij * rho_star * (Lij / dz) * mass_term_out / 2.0;
1675 auto term_in = Sij * rho_star * (Lij / dz) * mass_term_in / 2.0;
1676 if (iz == first_node)
1677 {
1678 PetscInt row_ad = i_gap + _n_gaps * iz_ind;
1679 PetscScalar value_ad = term_in * alpha * _Wij(i_gap, iz - 1);
1680 LibmeshPetscCall(
1681 VecSetValues(_cmc_advective_derivative_rhs, 1, &row_ad, &value_ad, ADD_VALUES));
1682
1683 PetscInt col_ad = i_gap + _n_gaps * iz_ind;
1684 value_ad = -1.0 * term_in * (1.0 - alpha) + term_out * alpha;
1685 LibmeshPetscCall(MatSetValues(
1686 _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
1687
1688 col_ad = i_gap + _n_gaps * (iz_ind + 1);
1689 value_ad = term_out * (1.0 - alpha);
1690 LibmeshPetscCall(MatSetValues(
1691 _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
1692 }
1693 else if (iz == last_node)
1694 {
1695 PetscInt row_ad = i_gap + _n_gaps * iz_ind;
1696 PetscInt col_ad = i_gap + _n_gaps * (iz_ind - 1);
1697 PetscScalar value_ad = -1.0 * term_in * alpha;
1698 LibmeshPetscCall(MatSetValues(
1699 _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
1700
1701 col_ad = i_gap + _n_gaps * iz_ind;
1702 value_ad = -1.0 * term_in * (1.0 - alpha) + term_out * alpha;
1703 LibmeshPetscCall(MatSetValues(
1704 _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
1705
1706 value_ad = -1.0 * term_out * (1.0 - alpha) * _Wij(i_gap, iz);
1707 LibmeshPetscCall(
1708 VecSetValues(_cmc_advective_derivative_rhs, 1, &row_ad, &value_ad, ADD_VALUES));
1709 }
1710 else
1711 {
1712 PetscInt row_ad = i_gap + _n_gaps * iz_ind;
1713 PetscInt col_ad = i_gap + _n_gaps * (iz_ind - 1);
1714 PetscScalar value_ad = -1.0 * term_in * alpha;
1715 LibmeshPetscCall(MatSetValues(
1716 _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
1717
1718 col_ad = i_gap + _n_gaps * iz_ind;
1719 value_ad = -1.0 * term_in * (1.0 - alpha) + term_out * alpha;
1720 LibmeshPetscCall(MatSetValues(
1721 _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
1722
1723 col_ad = i_gap + _n_gaps * (iz_ind + 1);
1724 value_ad = term_out * (1.0 - alpha);
1725 LibmeshPetscCall(MatSetValues(
1726 _cmc_advective_derivative_mat, 1, &row_ad, 1, &col_ad, &value_ad, INSERT_VALUES));
1727 }
1728 // Assembling friction force
1729 PetscInt row_ff = i_gap + _n_gaps * iz_ind;
1730 PetscInt col_ff = i_gap + _n_gaps * iz_ind;
1731 PetscScalar value_ff = _kij * std::abs(_Wij(i_gap, iz)) / 2.0;
1732 LibmeshPetscCall(MatSetValues(
1733 _cmc_friction_force_mat, 1, &row_ff, 1, &col_ff, &value_ff, INSERT_VALUES));
1734
1735 // Assembling pressure force
1737
1738 PetscScalar pressure_factor = Utility::pow<2>(Sij) * rho_star;
1739 PetscInt row_pf = i_gap + _n_gaps * iz_ind;
1740 PetscInt col_pf = i_ch + _n_channels * iz_ind;
1741 PetscScalar value_pf = -1.0 * alpha * pressure_factor;
1742 LibmeshPetscCall(
1743 MatSetValues(_cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
1744 col_pf = j_ch + _n_channels * iz_ind;
1745 value_pf = alpha * pressure_factor;
1746 LibmeshPetscCall(
1747 MatSetValues(_cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
1748
1749 if (iz == last_node)
1750 {
1751 PetscInt row_pf = i_gap + _n_gaps * iz_ind;
1752 PetscScalar value_pf = (1.0 - alpha) * pressure_factor * (*_P_soln)(node_out_i);
1753 LibmeshPetscCall(
1754 VecSetValues(_cmc_pressure_force_rhs, 1, &row_pf, &value_pf, ADD_VALUES));
1755 value_pf = -1.0 * (1.0 - alpha) * pressure_factor * (*_P_soln)(node_out_j);
1756 LibmeshPetscCall(
1757 VecSetValues(_cmc_pressure_force_rhs, 1, &row_pf, &value_pf, ADD_VALUES));
1758 }
1759 else
1760 {
1761 row_pf = i_gap + _n_gaps * iz_ind;
1762 col_pf = i_ch + _n_channels * (iz_ind + 1);
1763 value_pf = -1.0 * (1.0 - alpha) * pressure_factor;
1764 LibmeshPetscCall(
1765 MatSetValues(_cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
1766 col_pf = j_ch + _n_channels * (iz_ind + 1);
1767 value_pf = (1.0 - alpha) * pressure_factor;
1768 LibmeshPetscCall(
1769 MatSetValues(_cmc_pressure_force_mat, 1, &row_pf, 1, &col_pf, &value_pf, ADD_VALUES));
1770 }
1771 }
1772 }
1774 LibmeshPetscCall(MatZeroEntries(_cmc_sys_Wij_mat));
1775 LibmeshPetscCall(VecZeroEntries(_cmc_sys_Wij_rhs));
1776 LibmeshPetscCall(MatAssemblyBegin(_cmc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1777 LibmeshPetscCall(MatAssemblyEnd(_cmc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1778 LibmeshPetscCall(MatAssemblyBegin(_cmc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1779 LibmeshPetscCall(MatAssemblyEnd(_cmc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1780 LibmeshPetscCall(MatAssemblyBegin(_cmc_friction_force_mat, MAT_FINAL_ASSEMBLY));
1781 LibmeshPetscCall(MatAssemblyEnd(_cmc_friction_force_mat, MAT_FINAL_ASSEMBLY));
1782 LibmeshPetscCall(MatAssemblyBegin(_cmc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
1783 LibmeshPetscCall(MatAssemblyEnd(_cmc_pressure_force_mat, MAT_FINAL_ASSEMBLY));
1784 LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1785 LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1786 // Matrix
1787#if !PETSC_VERSION_LESS_THAN(3, 15, 0)
1788 LibmeshPetscCall(
1789 MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_time_derivative_mat, UNKNOWN_NONZERO_PATTERN));
1790 LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1791 LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1792 LibmeshPetscCall(
1793 MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_advective_derivative_mat, UNKNOWN_NONZERO_PATTERN));
1794 LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1795 LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1796 LibmeshPetscCall(
1797 MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_friction_force_mat, UNKNOWN_NONZERO_PATTERN));
1798#else
1799 LibmeshPetscCall(
1800 MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_time_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1801 LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1802 LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1803 LibmeshPetscCall(
1804 MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_advective_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1805 LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1806 LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1807 LibmeshPetscCall(
1808 MatAXPY(_cmc_sys_Wij_mat, 1.0, _cmc_friction_force_mat, DIFFERENT_NONZERO_PATTERN));
1809#endif
1810 LibmeshPetscCall(MatAssemblyBegin(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1811 LibmeshPetscCall(MatAssemblyEnd(_cmc_sys_Wij_mat, MAT_FINAL_ASSEMBLY));
1812 // RHS
1813 LibmeshPetscCall(VecAXPY(_cmc_sys_Wij_rhs, 1.0, _cmc_time_derivative_rhs));
1814 LibmeshPetscCall(VecAXPY(_cmc_sys_Wij_rhs, 1.0, _cmc_advective_derivative_rhs));
1815 LibmeshPetscCall(VecAXPY(_cmc_sys_Wij_rhs, 1.0, _cmc_friction_force_rhs));
1816
1817 if (_segregated_bool)
1818 {
1819 // Assembly the matrix system
1820 Vec sol_holder_P;
1821 LibmeshPetscCall(createPetscVector(sol_holder_P, _block_size * _n_gaps));
1822 Vec sol_holder_W;
1823 LibmeshPetscCall(createPetscVector(sol_holder_W, _block_size * _n_gaps));
1824 LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
1825 _prodp, *_P_soln, first_node - 1, last_node - 1, _n_channels));
1827 _Wij_vec, _Wij, first_node, last_node, _n_gaps));
1828 LibmeshPetscCall(MatMult(_cmc_sys_Wij_mat, _Wij_vec, sol_holder_W));
1829 LibmeshPetscCall(VecAXPY(sol_holder_W, -1.0, _cmc_sys_Wij_rhs));
1830 LibmeshPetscCall(MatMult(_cmc_pressure_force_mat, _prodp, sol_holder_P));
1831 LibmeshPetscCall(VecAXPY(sol_holder_P, -1.0, _cmc_pressure_force_rhs));
1832 LibmeshPetscCall(VecAXPY(sol_holder_W, 1.0, sol_holder_P));
1833 PetscScalar * xx;
1834 LibmeshPetscCall(VecGetArray(sol_holder_W, &xx));
1835 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1836 {
1837 auto iz_ind = iz - first_node;
1838 for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
1839 {
1840 _Wij_residual_matrix(i_gap, iz - 1 - iblock * _block_size) = xx[iz_ind * _n_gaps + i_gap];
1841 }
1842 }
1843 LibmeshPetscCall(VecDestroy(&sol_holder_P));
1844 LibmeshPetscCall(VecDestroy(&sol_holder_W));
1845 }
1846 }
1847}
1848
1849void
1851{
1852 const unsigned int last_node = (iblock + 1) * _block_size;
1853 const unsigned int first_node = iblock * _block_size + 1;
1854 _mixing_closure->computeBlockMixingParameters(first_node, last_node);
1855 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1856 {
1857 auto dz = _z_grid[iz] - _z_grid[iz - 1];
1858 for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
1859 {
1860 auto chans = _subchannel_mesh.getGapChannels(i_gap);
1861 unsigned int i_ch = chans.first;
1862 unsigned int j_ch = chans.second;
1863 auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1864 auto * node_out_i = _subchannel_mesh.getChannelNode(i_ch, iz);
1865 auto * node_in_j = _subchannel_mesh.getChannelNode(j_ch, iz - 1);
1866 auto * node_out_j = _subchannel_mesh.getChannelNode(j_ch, iz);
1867 auto Si_in = (*_S_flow_soln)(node_in_i);
1868 auto Sj_in = (*_S_flow_soln)(node_in_j);
1869 auto Si_out = (*_S_flow_soln)(node_out_i);
1870 auto Sj_out = (*_S_flow_soln)(node_out_j);
1871 auto gap = _subchannel_mesh.getGapWidth(iz, i_gap);
1872 auto Sij = dz * gap;
1873 // Mass flux of the gap: total mass flow rate of the two subchannels over their total flow
1874 // area, averaged over the inlet and outlet of the cell. This is the area-weighted mean of rho
1875 // V of the two subchannels; Eq. (30) of Pacio et al. (2022) uses the arithmetic mean rho (V_i
1876 // + V_j) / 2, which differs when the two subchannels have different flow areas
1877 auto avg_massflux =
1878 0.5 * (((*_mdot_soln)(node_in_i) + (*_mdot_soln)(node_in_j)) / (Si_in + Sj_in) +
1879 ((*_mdot_soln)(node_out_i) + (*_mdot_soln)(node_out_j)) / (Si_out + Sj_out));
1880 auto beta = computeMixingParameter(i_gap, iz);
1881
1882 if (!_implicit_bool)
1883 {
1884 _WijPrime(i_gap, iz) = beta * avg_massflux * Sij;
1885 }
1886 else
1887 {
1888 auto iz_ind = iz - first_node;
1889 PetscScalar base_value = beta * 0.5 * Sij;
1890
1891 // Bottom values
1892 if (iz == first_node)
1893 {
1894 PetscScalar value_tl = -1.0 * base_value / (Si_in + Sj_in) *
1895 ((*_mdot_soln)(node_in_i) + (*_mdot_soln)(node_in_j));
1896 PetscInt row = i_gap + _n_gaps * iz_ind;
1897 LibmeshPetscCall(
1898 VecSetValues(_amc_turbulent_cross_flows_rhs, 1, &row, &value_tl, INSERT_VALUES));
1899 }
1900 else
1901 {
1902 PetscScalar value_tl = base_value / (Si_in + Sj_in);
1903 PetscInt row = i_gap + _n_gaps * iz_ind;
1904
1905 PetscInt col_ich = i_ch + _n_channels * (iz_ind - 1);
1906 LibmeshPetscCall(MatSetValues(
1907 _amc_turbulent_cross_flows_mat, 1, &row, 1, &col_ich, &value_tl, INSERT_VALUES));
1908
1909 PetscInt col_jch = j_ch + _n_channels * (iz_ind - 1);
1910 LibmeshPetscCall(MatSetValues(
1911 _amc_turbulent_cross_flows_mat, 1, &row, 1, &col_jch, &value_tl, INSERT_VALUES));
1912 }
1913
1914 // Top values
1915 PetscScalar value_bl = base_value / (Si_out + Sj_out);
1916 PetscInt row = i_gap + _n_gaps * iz_ind;
1917
1918 PetscInt col_ich = i_ch + _n_channels * iz_ind;
1919 LibmeshPetscCall(MatSetValues(
1920 _amc_turbulent_cross_flows_mat, 1, &row, 1, &col_ich, &value_bl, INSERT_VALUES));
1921
1922 PetscInt col_jch = j_ch + _n_channels * iz_ind;
1923 LibmeshPetscCall(MatSetValues(
1924 _amc_turbulent_cross_flows_mat, 1, &row, 1, &col_jch, &value_bl, INSERT_VALUES));
1925 }
1926 }
1927 }
1928
1929 if (_implicit_bool)
1930 {
1931 LibmeshPetscCall(MatAssemblyBegin(_amc_turbulent_cross_flows_mat, MAT_FINAL_ASSEMBLY));
1932 LibmeshPetscCall(MatAssemblyEnd(_amc_turbulent_cross_flows_mat, MAT_FINAL_ASSEMBLY));
1933
1935 Vec loc_prod;
1936 Vec loc_Wij;
1937 LibmeshPetscCall(VecDuplicate(_amc_sys_mdot_rhs, &loc_prod));
1938 LibmeshPetscCall(VecDuplicate(_Wij_vec, &loc_Wij));
1939 LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
1940 loc_prod, *_mdot_soln, first_node, last_node, _n_channels));
1941 LibmeshPetscCall(MatMult(_amc_turbulent_cross_flows_mat, loc_prod, loc_Wij));
1942 LibmeshPetscCall(VecAXPY(loc_Wij, -1.0, _amc_turbulent_cross_flows_rhs));
1944 loc_Wij, _WijPrime, first_node, last_node, _n_gaps));
1945 LibmeshPetscCall(VecDestroy(&loc_prod));
1946 LibmeshPetscCall(VecDestroy(&loc_Wij));
1947 }
1948}
1949
1950Real
1951SubChannel1PhaseProblem::computeMixingParameter(unsigned int i_gap, unsigned int iz) const
1952{
1953 auto beta = _mixing_closure->computeMixingParameter(i_gap, iz);
1954 if (!std::isfinite(beta) || beta < 0.0)
1955 mooseError(name(),
1956 ": Mixing closure returned invalid beta = ",
1957 beta,
1958 " for gap ",
1959 i_gap,
1960 " at axial index ",
1961 iz,
1962 ". Beta must be finite and non-negative.");
1963
1964 return beta;
1965}
1966
1967Real
1968SubChannel1PhaseProblem::computeSweepFlowMixingParameter(unsigned int i_gap, unsigned int iz) const
1969{
1970 auto beta = _mixing_closure->computeSweepFlowMixingParameter(i_gap, iz);
1971 if (!std::isfinite(beta) || beta < 0.0)
1972 mooseError(name(),
1973 ": Mixing closure returned invalid sweep-flow coefficient = ",
1974 beta,
1975 " for gap ",
1976 i_gap,
1977 " at axial index ",
1978 iz,
1979 ". sweep-flow coefficient must be finite and non-negative.");
1980
1981 return beta;
1982}
1983
1986{
1987 const unsigned int last_node = (iblock + 1) * _block_size;
1988 const unsigned int first_node = iblock * _block_size + 1;
1989 libMesh::DenseVector<Real> Wij_residual_vector(_n_gaps * _block_size, 0.0);
1990 // Assign the solution to the cross-flow matrix
1991 int i = 0;
1992 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1993 {
1994 for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
1995 {
1996 _Wij(i_gap, iz) = solution(i);
1997 i++;
1998 }
1999 }
2000
2001 // Calculating sum of crossflows
2002 computeSumWij(iblock);
2003 // Solving axial flux
2004 computeMdot(iblock);
2005 // Calculation of turbulent Crossflow
2006 computeWijPrime(iblock);
2007 // Solving for Pressure Drop
2008 computeDP(iblock);
2009 // Solving for pressure
2010 computeP(iblock);
2011 // Populating lateral crossflow residual matrix
2012 computeWijResidual(iblock);
2013
2014 // Turn the residual matrix into a residual vector
2015 for (unsigned int iz = 0; iz < _block_size; iz++)
2016 {
2017 for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
2018 {
2019 int i = _n_gaps * iz + i_gap; // column wise transfer
2020 Wij_residual_vector(i) = _Wij_residual_matrix(i_gap, iz);
2021 }
2022 }
2023 return Wij_residual_vector;
2024}
2025
2026PetscErrorCode
2028 const libMesh::DenseVector<Real> & solution,
2030{
2031 SNES snes;
2032 KSP ksp;
2033 PC pc;
2034 Vec x, r;
2035 PetscScalar * xx;
2036
2038 LibmeshPetscCall(SNESCreate(PETSC_COMM_SELF, &snes));
2039 LibmeshPetscCall(VecCreate(PETSC_COMM_SELF, &x));
2040 LibmeshPetscCall(VecSetSizes(x, PETSC_DECIDE, _block_size * _n_gaps));
2041 LibmeshPetscCall(VecSetFromOptions(x));
2042 LibmeshPetscCall(VecDuplicate(x, &r));
2043
2044#if PETSC_VERSION_LESS_THAN(3, 13, 0)
2045 LibmeshPetscCall(PetscOptionsSetValue(PETSC_NULL, "-snes_mf", PETSC_NULL));
2046#else
2047 LibmeshPetscCall(SNESSetUseMatrixFree(snes, PETSC_FALSE, PETSC_TRUE));
2048#endif
2049 Ctx ctx;
2050 ctx.iblock = iblock;
2051 ctx.schp = this;
2052 LibmeshPetscCall(SNESSetFunction(snes, r, formFunction, &ctx));
2053 LibmeshPetscCall(SNESGetKSP(snes, &ksp));
2054 LibmeshPetscCall(KSPGetPC(ksp, &pc));
2055 LibmeshPetscCall(PCSetType(pc, PCNONE));
2056 LibmeshPetscCall(KSPSetTolerances(ksp, _rtol, _atol, _dtol, _maxit));
2057 LibmeshPetscCall(SNESSetFromOptions(snes));
2058 LibmeshPetscCall(VecGetArray(x, &xx));
2059 for (unsigned int i = 0; i < _block_size * _n_gaps; i++)
2060 {
2061 xx[i] = solution(i);
2062 }
2063 LibmeshPetscCall(VecRestoreArray(x, &xx));
2064
2065 LibmeshPetscCall(SNESSolve(snes, NULL, x));
2066 // A diverged solve leaves x at its initial guess, which is then used as the crossflow of this
2067 // iteration. Report it rather than stopping, since the unpreconditioned linear solve can break
2068 // down near convergence
2069 SNESConvergedReason reason;
2070 LibmeshPetscCall(SNESGetConvergedReason(snes, &reason));
2071 if (reason < 0)
2072 {
2073 PetscInt iterations;
2074 PetscReal residual_norm;
2075 LibmeshPetscCall(SNESGetIterationNumber(snes, &iterations));
2076 LibmeshPetscCall(SNESGetFunctionNorm(snes, &residual_norm));
2077 mooseWarning("Crossflow nonlinear solve did not converge: ",
2078 SNESConvergedReasons[reason],
2079 " (",
2080 static_cast<int>(reason),
2081 ") after ",
2082 iterations,
2083 " iterations; residual norm = ",
2084 residual_norm,
2085 ". The crossflow of this iteration is not updated.");
2086 }
2087 LibmeshPetscCall(VecGetArray(x, &xx));
2088 for (unsigned int i = 0; i < _block_size * _n_gaps; i++)
2089 root(i) = xx[i];
2090
2091 LibmeshPetscCall(VecRestoreArray(x, &xx));
2092 LibmeshPetscCall(VecDestroy(&x));
2093 LibmeshPetscCall(VecDestroy(&r));
2094 LibmeshPetscCall(SNESDestroy(&snes));
2095 PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
2096}
2097
2098PetscErrorCode
2100 Mat A, Vec rhs, unsigned int first_node, unsigned int last_node, const char * ksp_prefix)
2101{
2103
2104 // Create solution vector with rhs layout
2105 Vec x = nullptr;
2106 LibmeshPetscCall(VecDuplicate(rhs, &x));
2107
2108 // KSP setup
2109 KSP ksp = nullptr;
2110 PC pc = nullptr;
2111 LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksp));
2112 LibmeshPetscCall(KSPSetOperators(ksp, A, A));
2113 LibmeshPetscCall(KSPGetPC(ksp, &pc));
2114 LibmeshPetscCall(PCSetType(pc, PCJACOBI));
2115 LibmeshPetscCall(KSPSetTolerances(ksp, _rtol, _atol, _dtol, _maxit));
2116 if (ksp_prefix && *ksp_prefix)
2117 LibmeshPetscCall(KSPSetOptionsPrefix(ksp, ksp_prefix));
2118 LibmeshPetscCall(KSPSetFromOptions(ksp));
2119
2120 // Solve
2121 LibmeshPetscCall(KSPSolve(ksp, rhs, x));
2122 KSPConvergedReason reason;
2123 LibmeshPetscCall(KSPGetConvergedReason(ksp, &reason));
2124 if (reason < 0)
2125 {
2126 PetscInt iterations;
2127 PetscReal residual_norm;
2128 LibmeshPetscCall(KSPGetIterationNumber(ksp, &iterations));
2129 LibmeshPetscCall(KSPGetResidualNorm(ksp, &residual_norm));
2130 mooseError(name(),
2131 ": enthalpy linear solve failed: ",
2132 KSPConvergedReasons[reason],
2133 " (",
2134 static_cast<int>(reason),
2135 ") after ",
2136 iterations,
2137 " iterations; residual norm = ",
2138 residual_norm,
2139 ".");
2140 }
2141
2142 // Scatter to _h_soln with sanity checks
2143 PetscScalar * xx = nullptr;
2144 LibmeshPetscCall(VecGetArray(x, &xx));
2145 for (unsigned int iz = first_node; iz <= last_node; ++iz)
2146 {
2147 const unsigned int iz_ind = iz - first_node;
2148 for (unsigned int i_ch = 0; i_ch < _n_channels; ++i_ch)
2149 {
2150 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
2151 const PetscScalar h_out = xx[iz_ind * _n_channels + i_ch];
2152 if (h_out < 0.0)
2153 mooseError(
2154 name(), " : Calculation of negative Enthalpy h_out = ", h_out, " Axial Level = ", iz);
2155 _h_soln->set(node_out, h_out);
2156 }
2157 }
2158 LibmeshPetscCall(VecRestoreArray(x, &xx));
2159
2160 // Cleanup
2161 LibmeshPetscCall(KSPDestroy(&ksp));
2162 LibmeshPetscCall(VecDestroy(&x));
2163
2164 PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
2165}
2166
2167Real
2168SubChannel1PhaseProblem::computeAddedHeatDuct(unsigned int i_ch, unsigned int iz) const
2169{
2170 mooseAssert(iz > 0, "Trapezoidal rule requires starting at index 1 at least");
2171 if (_duct_mesh_exist)
2172 {
2173 auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
2174 if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
2175 {
2176 auto dz = _z_grid[iz] - _z_grid[iz - 1];
2177 auto * node_in_chan = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
2178 auto * node_out_chan = _subchannel_mesh.getChannelNode(i_ch, iz);
2179 auto * node_in_duct = _subchannel_mesh.getDuctNodeFromChannel(node_in_chan);
2180 auto * node_out_duct = _subchannel_mesh.getDuctNodeFromChannel(node_out_chan);
2181 auto heat_rate_in = (*_duct_heat_flux_soln)(node_in_duct);
2182 auto heat_rate_out = (*_duct_heat_flux_soln)(node_out_duct);
2183 auto width = getSubChannelPeripheralDuctWidth(i_ch);
2184 return 0.5 * (heat_rate_in + heat_rate_out) * dz * width;
2185 }
2186 else
2187 {
2188 return 0.0;
2189 }
2190 }
2191 else
2192 {
2193 return 0.0;
2194 }
2195}
2196
2197PetscErrorCode
2199{
2201 // ---------- helper functions -----------------------------
2202 auto V = [&](const std::string & s)
2203 {
2205 _console << s << std::endl;
2206 };
2207
2208 auto DupMatAssembled = [&](Mat src, Mat * dst)
2209 {
2210 if (src)
2211 {
2212 LibmeshPetscCall(MatDuplicate(src, MAT_COPY_VALUES, dst));
2213 LibmeshPetscCall(MatAssemblyBegin(*dst, MAT_FINAL_ASSEMBLY));
2214 LibmeshPetscCall(MatAssemblyEnd(*dst, MAT_FINAL_ASSEMBLY));
2215 }
2216 else
2217 *dst = NULL;
2218 };
2219
2220 auto DupVecCopy = [&](Vec src, Vec * dst)
2221 {
2222 LibmeshPetscCall(VecDuplicate(src, dst));
2223 LibmeshPetscCall(VecCopy(src, *dst));
2224 };
2225
2226 const PetscInt Q = 3; // [mass conservation, axial momentum, cross momentum]
2227
2228 // small indexer
2229 auto Idx = [&](PetscInt r, PetscInt c) { return Q * r + c; };
2230
2231 // arrays that MUST be declared before lambdas use them
2232 std::vector<Mat> mat_array(Q * Q, NULL);
2233 std::vector<Vec> vec_array(Q, NULL);
2234
2235 // generic assembler for one governing equation row in the nested matrix
2236 auto AssembleEquation = [&](PetscInt f,
2237 Mat A0,
2238 Mat A1,
2239 Mat A2, // three blocks in row f (can be nullptr)
2240 Vec rhs, // base RHS for equation f
2241 Vec rhs_add, // optional extra RHS to add (can be nullptr)
2242 const char * label) // e.g. "Mass", "Lin mom", "Cross mom"
2243 {
2244 DupMatAssembled(A0, &mat_array[Idx(f, 0)]);
2245 DupMatAssembled(A1, &mat_array[Idx(f, 1)]);
2246 DupMatAssembled(A2, &mat_array[Idx(f, 2)]);
2247 DupVecCopy(rhs, &vec_array[f]);
2248 if (rhs_add)
2249 LibmeshPetscCall(VecAXPY(vec_array[f], 1.0, rhs_add));
2250 V(std::string(label) + " system assembled");
2251 };
2252
2253 // -----------------------------------------------------------------------------
2254 // Helper lambda that applies per-equation under-relaxation by modifying BOTH the
2255 // matrix block and the RHS for that equation.
2256 //
2257 // Specifically, with A_ff for the equation and D = diag(A_ff):
2258 // 1) Matrix diagonal scaling: D <- D / alpha, then A_ff's diagonal is replaced
2259 // with D. For alpha < 1 this increases diagonal dominance (more damping).
2260 // 2) RHS blending with the previous solution x_old:
2261 // rhs_f <- rhs_f + (1 - alpha) * (D / alpha) * x_old
2262 // where x_old is provided by the caller via `populate(work)`.
2263 //
2264 // Net effect: the solved x satisfies
2265 // A_ff x = rhs_f_original + ((1 - alpha)/alpha) * D * (x_old - x),
2266 // which damps updates toward x_old without changing the converged solution.
2267 // This equation relaxation is independent of the optional post-solve solution relaxation.
2268 // -----------------------------------------------------------------------------
2269 auto relaxEquation =
2270 [&](Mat diagonal_block, Vec rhs, Vec work, const Real relaxation, auto && populate)
2271 {
2272 if (relaxation == 1.0)
2273 return;
2274
2275 Vec diagonal = nullptr;
2276 LibmeshPetscCall(VecDuplicate(rhs, &diagonal));
2277
2278 // 1) diagonal_block: diag <- diag / relaxation
2279 LibmeshPetscCall(MatGetDiagonal(diagonal_block, diagonal));
2280 LibmeshPetscCall(VecScale(diagonal, 1.0 / relaxation));
2281 LibmeshPetscCall(MatDiagonalSet(diagonal_block, diagonal, INSERT_VALUES));
2282
2283 // 2) work <- x_old (caller-provided populator)
2284 LibmeshPetscCall(populate(work));
2285
2286 // 3) rhs += (1 - relaxation) * (diag .* work)
2287 LibmeshPetscCall(VecScale(diagonal, 1.0 - relaxation));
2288 LibmeshPetscCall(VecPointwiseMult(work, work, diagonal));
2289 LibmeshPetscCall(VecAXPY(rhs, 1.0, work));
2290
2291 LibmeshPetscCall(VecDestroy(&diagonal));
2292 };
2293
2294 // indices
2295 const unsigned int first_node = iblock * _block_size + 1;
2296 const unsigned int last_node = (iblock + 1) * _block_size;
2297
2298 // ---------- assemble per-block operators -----------------
2299 computeSumWij(iblock);
2300 computeMdot(iblock);
2301 computeWijPrime(iblock);
2302 computeDP(iblock);
2303 computeP(iblock);
2304 computeWijResidual(iblock);
2305
2306 V("Starting nested system.");
2307
2308 // Populate nested matrix with the individual physics
2309 // equation 0: Mass conservation
2310 AssembleEquation(/*f=*/0,
2313 /*A2=*/_mc_sumWij_mat,
2315 /*rhs_add=*/nullptr,
2316 /*label=*/"Mass");
2317
2318 // equation 1: Axial momentum conservation
2319 AssembleEquation(/*f=*/1,
2320 /*A0=*/_amc_sys_mdot_mat,
2322 /*A2=*/nullptr,
2324 /*rhs_add=*/_amc_sys_mdot_rhs,
2325 /*label=*/"Lin mom");
2326
2327 // equation 2: Cross momentum conservation
2328 AssembleEquation(/*f=*/2,
2329 /*A0=*/nullptr,
2331 /*A2=*/_cmc_sys_Wij_mat,
2332 /*rhs=*/_cmc_sys_Wij_rhs,
2333 /*rhs_add=*/_cmc_pressure_force_rhs,
2334 /*label=*/"Cross mom");
2335
2336 // ========================== Relaxation ====================
2337 if (true)
2338 {
2339 LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
2340 _prod, *_mdot_soln, first_node, last_node, _n_channels));
2341
2342 Vec mdot_estimate;
2343 LibmeshPetscCall(createPetscVector(mdot_estimate, _block_size * _n_channels));
2344 Vec pmat_diag;
2345 LibmeshPetscCall(createPetscVector(pmat_diag, _block_size * _n_channels));
2346 Vec p_estimate;
2347 LibmeshPetscCall(createPetscVector(p_estimate, _block_size * _n_channels));
2348 Vec unity_vec;
2349 LibmeshPetscCall(createPetscVector(unity_vec, _block_size * _n_channels));
2350 LibmeshPetscCall(VecSet(unity_vec, 1.0));
2351 Vec sol_holder_P;
2352 LibmeshPetscCall(createPetscVector(sol_holder_P, _block_size * _n_gaps));
2353 Vec unity_vec_Wij;
2354 LibmeshPetscCall(createPetscVector(unity_vec_Wij, _block_size * _n_gaps));
2355 LibmeshPetscCall(VecSet(unity_vec_Wij, 1.0));
2356 Vec _Wij_loc_vec;
2357 LibmeshPetscCall(createPetscVector(_Wij_loc_vec, _block_size * _n_gaps));
2358 Vec _Wij_old_loc_vec;
2359 LibmeshPetscCall(createPetscVector(_Wij_old_loc_vec, _block_size * _n_gaps));
2360
2361 // ---- scale estimates ----
2362 // mdot_estimate = A(1,0) * mdot
2363 LibmeshPetscCall(MatMult(mat_array[Q /* (1,0) */], _prod, mdot_estimate));
2364
2365 // p_estimate = mdot_est / (diag(A(1,1)) + eps)
2366 LibmeshPetscCall(MatGetDiagonal(mat_array[Q + 1], pmat_diag));
2367 LibmeshPetscCall(VecAXPY(pmat_diag, 1e-10, unity_vec));
2368 LibmeshPetscCall(VecPointwiseDivide(p_estimate, mdot_estimate, pmat_diag));
2369
2370 // sol_holder_P = A(2,1) * p_estimate - rhs_cmc_pressure
2371 LibmeshPetscCall(MatMult(mat_array[2 * Q + 1], p_estimate, sol_holder_P));
2372 LibmeshPetscCall(VecAXPY(sol_holder_P, -1.0, _cmc_pressure_force_rhs));
2373
2374 // sumWij_loc from sol_holder_P (accumulate)
2375 Vec sumWij_loc;
2376 LibmeshPetscCall(createPetscVector(sumWij_loc, _block_size * _n_channels));
2377 for (unsigned int iz = first_node; iz <= last_node; ++iz)
2378 {
2379 const auto iz_ind = iz - first_node;
2380 for (unsigned int i_ch = 0; i_ch < _n_channels; ++i_ch)
2381 {
2382 PetscScalar sumWij = 0.0;
2383 unsigned int counter = 0;
2384 for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
2385 {
2386 PetscInt row_vec = i_gap + _n_gaps * iz_ind;
2387 PetscScalar loc_Wij_value;
2388 LibmeshPetscCall(VecGetValues(sol_holder_P, 1, &row_vec, &loc_Wij_value));
2389 sumWij += _subchannel_mesh.getCrossflowSign(i_ch, counter) * loc_Wij_value;
2390 counter++;
2391 }
2392 PetscInt row_vec = i_ch + _n_channels * iz_ind;
2393 LibmeshPetscCall(VecSetValues(sumWij_loc, 1, &row_vec, &sumWij, INSERT_VALUES));
2394 }
2395 }
2396 LibmeshPetscCall(VecAssemblyBegin(sumWij_loc));
2397 LibmeshPetscCall(VecAssemblyEnd(sumWij_loc));
2398
2399 // ---- robust scale measurements ----
2400 PetscScalar min_mdot, sum_mdot, avg_mdot;
2401 PetscInt n;
2402 LibmeshPetscCall(VecAbs(_prod));
2403 LibmeshPetscCall(VecMin(_prod, NULL, &min_mdot));
2404 LibmeshPetscCall(VecSum(_prod, &sum_mdot)); // Sum all entries
2405 LibmeshPetscCall(VecGetSize(_prod, &n)); // Number of entries
2406 avg_mdot = sum_mdot / static_cast<PetscScalar>(n);
2407
2408 V("Average estimated mdot: " + std::to_string(avg_mdot));
2409 V("Minimum estimated mdot: " + std::to_string(min_mdot));
2410
2411 LibmeshPetscCall(VecAbs(sumWij_loc));
2412 LibmeshPetscCall(VecMax(sumWij_loc, NULL, &_max_sumWij));
2413 _max_sumWij = std::max(1e-10, _max_sumWij);
2414 V("Maximum estimated Wij: " + std::to_string(_max_sumWij));
2415
2417 _Wij_loc_vec, _Wij, first_node, last_node, _n_gaps));
2418 LibmeshPetscCall(VecAbs(_Wij_loc_vec));
2420 _Wij_old_loc_vec, _Wij_old, first_node, last_node, _n_gaps));
2421 LibmeshPetscCall(VecAbs(_Wij_old_loc_vec));
2422 LibmeshPetscCall(VecAXPY(_Wij_loc_vec, -1.0, _Wij_old_loc_vec));
2423
2424 PetscScalar relax_factor;
2425 LibmeshPetscCall(VecAbs(_Wij_loc_vec));
2426#if !PETSC_VERSION_LESS_THAN(3, 16, 0)
2427 LibmeshPetscCall(VecMean(_Wij_loc_vec, &relax_factor));
2428#else
2429 VecSum(_Wij_loc_vec, &relax_factor);
2430 relax_factor /= _block_size * _n_gaps;
2431#endif
2432 relax_factor = relax_factor / _max_sumWij + 0.5;
2433 V("Relax base value: " + std::to_string(relax_factor));
2434
2435 // ---- crossflow resistance inflation ----
2436 const PetscScalar resistance_relaxation = 0.9;
2437 _added_K = _max_sumWij / min_mdot;
2438 V("New cross resistance: " + std::to_string(_added_K));
2439 _added_K = (_added_K * resistance_relaxation + (1.0 - resistance_relaxation) * _added_K_old) *
2440 relax_factor;
2441 V("Relaxed cross resistance: " + std::to_string(_added_K));
2442
2443 // Snap-up lower-bounding
2444 if (_added_K < 10 && _added_K >= 1.0)
2445 _added_K = 1.0;
2446 if (_added_K < 1.0 && _added_K >= 0.1)
2447 _added_K = 0.5;
2448 if (_added_K < 0.1 && _added_K >= 0.01)
2449 _added_K = 1. / 3.;
2450 if (_added_K < 1e-2 && _added_K >= 1e-3)
2451 _added_K = 0.1;
2452 V("Actual added cross resistance: " + std::to_string(_added_K));
2453 LibmeshPetscCall(VecScale(unity_vec_Wij, _added_K));
2455
2456 LibmeshPetscCall(MatDiagonalSet(mat_array[2 * Q + 2], unity_vec_Wij, ADD_VALUES));
2457
2458 // ---- cleanup temp vectors used above ----
2459 LibmeshPetscCall(VecDestroy(&mdot_estimate));
2460 LibmeshPetscCall(VecDestroy(&pmat_diag));
2461 LibmeshPetscCall(VecDestroy(&unity_vec));
2462 LibmeshPetscCall(VecDestroy(&p_estimate));
2463 LibmeshPetscCall(VecDestroy(&sol_holder_P));
2464 LibmeshPetscCall(VecDestroy(&unity_vec_Wij));
2465 LibmeshPetscCall(VecDestroy(&sumWij_loc));
2466 LibmeshPetscCall(VecDestroy(&_Wij_loc_vec));
2467 LibmeshPetscCall(VecDestroy(&_Wij_old_loc_vec));
2468 }
2469
2470 V("Relax mdot: " + std::to_string(_mass_flow_equation_relaxation));
2471 V("Relax P: " + std::to_string(_pressure_equation_relaxation));
2472 V("Relax Wij: " + std::to_string(_crossflow_equation_relaxation));
2473
2474 relaxEquation(mat_array[Idx(0, 0)],
2475 vec_array[0],
2476 _prod,
2478 [&](Vec work)
2479 {
2480 return populateVectorFromHandle<SolutionHandle>(
2481 work, *_mdot_soln, first_node, last_node, _n_channels);
2482 });
2483 V("mdot relaxed");
2484
2485 relaxEquation(mat_array[Idx(1, 1)],
2486 vec_array[1],
2487 _prodp,
2489 [&](Vec work)
2490 {
2491 // The pressure block represents the node below each axial momentum cell because
2492 // the outlet pressure is prescribed.
2493 return populateVectorFromHandle<SolutionHandle>(
2494 work, *_P_soln, first_node - 1, last_node - 1, _n_channels);
2495 });
2496 V("P relaxed");
2497
2498 relaxEquation(mat_array[Idx(2, 2)],
2499 vec_array[2],
2500 _Wij_vec,
2502 [&](Vec work)
2503 {
2504 return populateVectorFromDense<libMesh::DenseMatrix<Real>>(
2505 work, _Wij, first_node, last_node, _n_gaps);
2506 });
2507 V("Wij relaxed");
2508 V("Linear solver relaxed");
2509
2510 // ======================== Create and configure KSP =========================
2511 Mat A_nest;
2512 Vec b_nest;
2513 Vec x_nest;
2514 LibmeshPetscCall(MatCreateNest(PETSC_COMM_SELF, Q, NULL, Q, NULL, mat_array.data(), &A_nest));
2515 LibmeshPetscCall(VecCreateNest(PETSC_COMM_SELF, Q, NULL, vec_array.data(), &b_nest));
2516 V("Nested system created");
2517
2518 KSP ksp;
2519 PC pc;
2520 LibmeshPetscCall(KSPCreate(PETSC_COMM_SELF, &ksp));
2521 LibmeshPetscCall(KSPSetOptionsPrefix(ksp, "scm_coupled_"));
2522 LibmeshPetscCall(KSPSetType(ksp, KSPFGMRES));
2523 LibmeshPetscCall(KSPSetOperators(ksp, A_nest, A_nest));
2524 LibmeshPetscCall(KSPGetPC(ksp, &pc));
2525 LibmeshPetscCall(PCSetType(pc, PCFIELDSPLIT));
2526 LibmeshPetscCall(KSPSetTolerances(ksp, _rtol, _atol, _dtol, _maxit));
2527
2528 // split equations
2529 std::vector<IS> rows(Q);
2530 LibmeshPetscCall(MatNestGetISs(A_nest, rows.data(), NULL));
2531 for (PetscInt j = 0; j < Q; ++j)
2532 {
2533 IS part;
2534 LibmeshPetscCall(ISDuplicate(rows[j], &part));
2535 LibmeshPetscCall(PCFieldSplitSetIS(pc, NULL, part));
2536 LibmeshPetscCall(ISDestroy(&part));
2537 }
2538 LibmeshPetscCall(KSPSetFromOptions(ksp));
2539 V("Linear solver assembled");
2540
2541 // ============================== Solve =====================================
2542 LibmeshPetscCall(VecDuplicate(b_nest, &x_nest));
2543 LibmeshPetscCall(VecSet(x_nest, 0.0));
2544 LibmeshPetscCall(KSPSolve(ksp, b_nest, x_nest));
2545 KSPConvergedReason reason;
2546 LibmeshPetscCall(KSPGetConvergedReason(ksp, &reason));
2547 if (reason < 0)
2548 {
2549 PetscInt iterations;
2550 PetscReal residual_norm;
2551 LibmeshPetscCall(KSPGetIterationNumber(ksp, &iterations));
2552 LibmeshPetscCall(KSPGetResidualNorm(ksp, &residual_norm));
2553 mooseError(name(),
2554 ": coupled mass/momentum linear solve failed: ",
2555 KSPConvergedReasons[reason],
2556 " (",
2557 static_cast<int>(reason),
2558 ") after ",
2559 iterations,
2560 " iterations; residual norm = ",
2561 residual_norm,
2562 ".");
2563 }
2564
2565 // destroy solver containers first
2566 LibmeshPetscCall(VecDestroy(&b_nest));
2567 LibmeshPetscCall(MatDestroy(&A_nest));
2568 LibmeshPetscCall(KSPDestroy(&ksp));
2569 for (PetscInt i = 0; i < Q * Q; i++)
2570 LibmeshPetscCall(MatDestroy(&mat_array[i]));
2571 for (PetscInt i = 0; i < Q; i++)
2572 LibmeshPetscCall(VecDestroy(&vec_array[i]));
2573 V("Solver elements destroyed");
2574
2575 // ====================== Extract & scatter the solution =====================
2576 Vec sol_mdot, sol_p, sol_Wij;
2577 V("Vectors to hold solution created");
2578 PetscInt num_vecs;
2579 Vec * loc_vecs;
2580 LibmeshPetscCall(VecNestGetSubVecs(x_nest, &num_vecs, &loc_vecs));
2581 LibmeshPetscCall(VecDuplicate(_mc_axial_convection_rhs, &sol_mdot));
2582 LibmeshPetscCall(VecCopy(loc_vecs[0], sol_mdot));
2583 LibmeshPetscCall(VecDuplicate(_amc_sys_mdot_rhs, &sol_p));
2584 LibmeshPetscCall(VecCopy(loc_vecs[1], sol_p));
2585 LibmeshPetscCall(VecDuplicate(_cmc_sys_Wij_rhs, &sol_Wij));
2586 LibmeshPetscCall(VecCopy(loc_vecs[2], sol_Wij));
2587 V("Solution from coupled solver copied to solution vectors");
2588
2589 // Apply independent post-solve relaxation to the fixed-point solution update.
2590 auto relaxSolution = [&](Vec solution, Vec old_solution, const Real relaxation)
2591 {
2592 LibmeshPetscCall(VecScale(solution, relaxation));
2593 LibmeshPetscCall(VecAXPY(solution, 1.0 - relaxation, old_solution));
2594 };
2595 LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
2596 _prod, *_mdot_soln, first_node, last_node, _n_channels));
2597 LibmeshPetscCall(populateVectorFromHandle<SolutionHandle>(
2598 _prodp, *_P_soln, first_node - 1, last_node - 1, _n_channels));
2599
2600 // Measure the pressure update before post-solve relaxation so changing that relaxation does
2601 // not change the meaning of the outer pressure tolerance.
2602 Vec pressure_residual;
2603 LibmeshPetscCall(VecDuplicate(sol_p, &pressure_residual));
2604 LibmeshPetscCall(VecCopy(sol_p, pressure_residual));
2605 LibmeshPetscCall(VecAXPY(pressure_residual, -1.0, _prodp));
2606
2607 const PetscScalar * residual_array;
2608 const PetscScalar * old_pressure_array;
2609 PetscInt pressure_size;
2610 LibmeshPetscCall(VecGetSize(pressure_residual, &pressure_size));
2611 LibmeshPetscCall(VecGetArrayRead(pressure_residual, &residual_array));
2612 LibmeshPetscCall(VecGetArrayRead(_prodp, &old_pressure_array));
2613 Real residual_norm_sq = 0.0;
2614 Real pressure_norm_sq = 0.0;
2615 for (PetscInt i = 0; i < pressure_size; ++i)
2616 {
2617 residual_norm_sq += Utility::pow<2>(residual_array[i]);
2618 pressure_norm_sq += Utility::pow<2>(old_pressure_array[i] + _P_out);
2619 }
2620 LibmeshPetscCall(VecRestoreArrayRead(pressure_residual, &residual_array));
2621 LibmeshPetscCall(VecRestoreArrayRead(_prodp, &old_pressure_array));
2622 LibmeshPetscCall(VecDestroy(&pressure_residual));
2625 std::sqrt(residual_norm_sq) / (std::sqrt(pressure_norm_sq) + 1e-14));
2626
2627 relaxSolution(sol_mdot, _prod, _mass_flow_relaxation);
2628 relaxSolution(sol_p, _prodp, _pressure_relaxation);
2630 _Wij_vec, _Wij, first_node, last_node, _n_gaps));
2631 relaxSolution(sol_Wij, _Wij_vec, _crossflow_relaxation);
2632
2633 // mass flow
2634 LibmeshPetscCall(populateSolutionChan<SolutionHandle>(
2635 sol_mdot, *_mdot_soln, first_node, last_node, _n_channels));
2636
2637 // pressure
2638 {
2639 PetscScalar * sol_p_array;
2640 LibmeshPetscCall(VecGetArray(sol_p, &sol_p_array));
2641 for (unsigned int iz = last_node; iz > first_node - 1; iz--)
2642 {
2643 const auto iz_ind = iz - first_node;
2644 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
2645 {
2646 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
2647 PetscScalar value = sol_p_array[iz_ind * _n_channels + i_ch];
2648 _P_soln->set(node_in, value);
2649 }
2650 }
2651 LibmeshPetscCall(VecRestoreArray(sol_p, &sol_p_array));
2652 }
2653
2654 // crossflow dense + sumWij + correction factor
2656 sol_Wij, _Wij, first_node, last_node, _n_gaps));
2657
2658 LibmeshPetscCall(MatMult(_mc_sumWij_mat, sol_Wij, _prod));
2659 LibmeshPetscCall(populateSolutionChan<SolutionHandle>(
2660 _prod, *_SumWij_soln, first_node, last_node, _n_channels));
2661
2662 LibmeshPetscCall(VecAbs(_prod));
2663 LibmeshPetscCall(VecMax(_prod, NULL, &_max_sumWij_new));
2664 V("Maximum estimated Wij new: " + std::to_string(_max_sumWij_new));
2666 V("Correction factor: " + std::to_string(_correction_factor));
2667 V("Solutions assigned to MOOSE variables.");
2668
2669 // cleanup solution objects
2670 LibmeshPetscCall(VecDestroy(&x_nest));
2671 LibmeshPetscCall(VecDestroy(&sol_mdot));
2672 LibmeshPetscCall(VecDestroy(&sol_p));
2673 LibmeshPetscCall(VecDestroy(&sol_Wij));
2674 V("Solutions destroyed.");
2675
2676 PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
2677}
2678
2679void
2681{
2682 _console << "Executing subchannel solver\n";
2683 _dt = (isTransient() ? dt() : _one);
2684 _TR = isTransient();
2685
2686 // The subchannel solver hardcodes a first-order backward (implicit) Euler time discretization, so
2687 // any other time integrator a user selects is silently ignored. Warn once if one is requested.
2689 {
2691 if (isTransient())
2692 if (auto * transient = dynamic_cast<TransientBase *>(_app.getExecutioner()))
2693 for (const auto * ti : transient->getTimeIntegrators())
2694 if (!dynamic_cast<const ImplicitEuler *>(ti))
2695 mooseWarning("The subchannel solver always uses implicit (backward) Euler time "
2696 "integration; the requested '",
2697 ti->type(),
2698 "' time integrator is ignored.");
2699 }
2700
2703 // Small helper functions to reduce repetition
2704 // Verbose print helper (no-op unless _verbose_subchannel is true)
2705 auto V = [&](const std::string & s)
2706 {
2708 _console << s << std::endl;
2709 };
2710 auto saveValues = [&](const SolutionHandle & solution,
2711 const unsigned int first_node,
2712 const unsigned int last_node)
2713 {
2714 std::vector<Real> values;
2715 values.reserve((last_node - first_node + 1) * _n_channels);
2716 for (unsigned int iz = first_node; iz <= last_node; ++iz)
2717 for (unsigned int i_ch = 0; i_ch < _n_channels; ++i_ch)
2718 values.push_back(solution(_subchannel_mesh.getChannelNode(i_ch, iz)));
2719 return values;
2720 };
2721 auto relativeChange = [&](const SolutionHandle & solution,
2722 const std::vector<Real> & old_values,
2723 const unsigned int first_node,
2724 const unsigned int last_node,
2725 const Real reference_offset)
2726 {
2727 Real difference_norm_sq = 0.0;
2728 Real reference_norm_sq = 0.0;
2729 std::size_t i = 0;
2730 for (unsigned int iz = first_node; iz <= last_node; ++iz)
2731 for (unsigned int i_ch = 0; i_ch < _n_channels; ++i_ch)
2732 {
2733 const Real value = solution(_subchannel_mesh.getChannelNode(i_ch, iz));
2734 difference_norm_sq += Utility::pow<2>(value - old_values[i]);
2735 reference_norm_sq += Utility::pow<2>(old_values[i] + reference_offset);
2736 ++i;
2737 }
2738 return std::sqrt(difference_norm_sq) / (std::sqrt(reference_norm_sq) + 1e-14);
2739 };
2740 V("Solution initialized");
2741 Real P_error = 1.0;
2742 unsigned int P_it = 0;
2743 unsigned int P_it_max;
2744 bool temperature_converged = true;
2745
2746 if (_segregated_bool)
2747 P_it_max = 20 * _n_blocks;
2748 else
2749 P_it_max = 100;
2750
2751 if ((_n_blocks == 1) && (_segregated_bool))
2752 P_it_max = 5;
2753
2754 if (_P_maxit > 0)
2755 P_it_max = _P_maxit;
2756
2757 while ((P_error > _P_tol && P_it < P_it_max))
2758 {
2759 P_it += 1;
2760 temperature_converged = true;
2762 _console << "Solving Outer Iteration : " << P_it << std::endl;
2763 const auto P_old = saveValues(*_P_soln, 0, _n_cells);
2764 for (unsigned int iblock = 0; iblock < _n_blocks; iblock++)
2765 {
2766 int last_level = (iblock + 1) * _block_size;
2767 int first_level = iblock * _block_size + 1;
2768 Real T_block_error = 1.0;
2769 auto T_it = 0;
2770 _console << "Solving Block: " << iblock << " From first level: " << first_level
2771 << " to last level: " << last_level << std::endl;
2772
2773 while (T_block_error > _T_tol && T_it < _T_maxit)
2774 {
2775 if (processor_id() == 0)
2776 {
2777 if (_segregated_bool)
2778 computeWijFromSolve(iblock);
2779 else
2780 {
2781 LibmeshPetscCall(implicitPetscSolve(iblock));
2782 computeWijPrime(iblock);
2783 V("Done with main solve.");
2784 }
2785 }
2786
2787 // A value of one preserves the original ordering by refreshing the flow solution before
2788 // every thermal update. Larger values opt into lagging flow during thermal subcycles.
2789 for (const auto enthalpy_subcycle : make_range(_enthalpy_subcycles))
2790 {
2791 if (T_block_error <= _T_tol || T_it >= _T_maxit)
2792 break;
2793
2794 V("Enthalpy subcycle: " + std::to_string(enthalpy_subcycle + 1));
2795 T_it += 1;
2796 const auto T_old = saveValues(*_T_soln, first_level, last_level);
2797 // We are only computing quantities on rank 0
2798 if (processor_id() > 0)
2799 {
2800 // Rank zero supplies the error through the maximum reduction below.
2801 T_block_error = 0.0;
2802 goto aux_close;
2803 }
2804
2805 if (_compute_power)
2806 {
2807 computeh(iblock);
2808 T_block_error = computeT(iblock);
2809 V("Done with thermal solve.");
2810 }
2811
2812 V("Start updating thermophysical properties.");
2813 if (_compute_density)
2814 computeRho(iblock);
2816 computeMu(iblock);
2817 V("Done updating thermophysical properties.");
2818
2819 // We must do a global assembly to make sure data is parallel consistent before we do
2820 // things like compute L2 norms
2821 aux_close:
2822 _aux->solution().close();
2823
2824 if (!_compute_power)
2825 T_block_error =
2826 relativeChange(*_T_soln, T_old, first_level, last_level, /*reference_offset=*/0.0);
2827 _console << "T_block_error: " << T_block_error << std::endl;
2828
2829 // All processes must have the same iteration count
2830 comm().max(T_block_error);
2831 }
2832 }
2833 const bool block_converged = T_block_error <= _T_tol;
2834 temperature_converged &= block_converged;
2835 if (!block_converged)
2836 {
2837 _console << "Reached maximum number of temperature iterations for block: " << iblock
2838 << std::endl;
2839 }
2840 }
2841 P_error = _segregated_bool ? relativeChange(*_P_soln, P_old, 0, _n_cells, _P_out)
2843 comm().max(P_error);
2844 _console << "P_error :" << P_error << std::endl;
2845 V("Iteration: " + std::to_string(P_it));
2846 V("Maximum iterations: " + std::to_string(P_it_max));
2847 }
2848 // Cache only the final iteration status. Earlier outer iterations may fail their thermal
2849 // tolerance and subsequently recover.
2850 const bool pressure_converged = P_error <= _P_tol;
2851 if (!pressure_converged)
2852 {
2853 _console << "Reached maximum number of axial pressure iterations" << std::endl;
2854 }
2855 _converged = pressure_converged && temperature_converged;
2856 // update old crossflow matrix
2857 _Wij_old = _Wij;
2858 _console << "Finished executing subchannel solver\n";
2859
2860 // set SumWij at the inlet equal to the one on the first axial level (for visualization purposes)
2861 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
2862 {
2863 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, 0);
2864 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, 1);
2865 _SumWij_soln->set(node_in, (*_SumWij_soln)(node_out)); // kg/sec
2866 }
2867
2868 if (_pin_mesh_exist)
2869 {
2870 // Assign average HTC to subchannels. This is exact if all pins have the same diameter
2871 for (unsigned int iz = 0; iz < _n_cells + 1; ++iz)
2872 {
2873 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
2874 {
2875 const auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
2876 auto mu = (*_mu_soln)(node);
2877 auto S = (*_S_flow_soln)(node);
2878 auto w_perim = (*_w_perim_soln)(node);
2879 auto Dh_i = 4.0 * S / w_perim;
2880 auto Re = ((std::abs((*_mdot_soln)(node)) / S) * Dh_i / mu);
2881 auto k = _fp->k_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
2882 auto cp = _fp->cp_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
2883 auto Pr = (*_mu_soln)(node)*cp / k;
2884 // Create Friction structure
2885 _friction_args = FrictionStruct(i_ch, Re, S, w_perim);
2886
2887 Real sumhw = 0.0;
2888 for (auto i_pin : _subchannel_mesh.getChannelPins(i_ch))
2889 {
2890 // Create nusselt number structure
2891 _nusselt_args = NusseltStruct(Re, Pr, i_pin, iz, i_ch);
2892
2893 // Compute HTC
2895 }
2896
2897 // Set HTC
2898 _HTC_soln->set(node, sumhw / _subchannel_mesh.getChannelPins(i_ch).size());
2899 }
2900 }
2901 _HTC_soln->close();
2902
2903 _console << "Commencing calculation of Pin surface temperature \n";
2904 for (unsigned int i_pin = 0; i_pin < _n_pins; i_pin++)
2905 {
2906 for (unsigned int iz = 0; iz < _n_cells + 1; ++iz)
2907 {
2908 const auto * pin_node = _subchannel_mesh.getPinNode(i_pin, iz);
2909 Real sumTemp = 0.0;
2910 // Calculate sum of pin surface temperatures that the channels around the pin see
2911 for (auto i_ch : _subchannel_mesh.getPinChannels(i_pin))
2912 {
2913 const auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
2914 auto mu = (*_mu_soln)(node);
2915 auto S = (*_S_flow_soln)(node);
2916 auto w_perim = (*_w_perim_soln)(node);
2917 auto Dh_i = 4.0 * S / w_perim;
2918 auto Re = ((std::abs((*_mdot_soln)(node)) / S) * Dh_i / mu);
2919 auto k = _fp->k_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
2920 auto cp = _fp->cp_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
2921 auto Pr = (*_mu_soln)(node)*cp / k;
2922 // Create Friction structure
2923 _friction_args = FrictionStruct(i_ch, Re, S, w_perim);
2924 // Create nusselt number structure
2925 _nusselt_args = NusseltStruct(Re, Pr, i_pin, iz, i_ch);
2926 // Compute HTC
2928 // Compute surface temperature contribution from subchannel side
2929 sumTemp +=
2930 (*_q_prime_soln)(pin_node) / ((*_Dpin_soln)(pin_node)*M_PI * hw) + (*_T_soln)(node);
2931 }
2932 if (_subchannel_mesh.getPinChannels(i_pin).size() > 0)
2933 _Tpin_soln->set(pin_node, sumTemp / _subchannel_mesh.getPinChannels(i_pin).size());
2934 else
2935 mooseError("Pin was not found for pin index: " + std::to_string(i_pin));
2936 }
2937 }
2938 }
2939
2941 if (_duct_mesh_exist && processor_id() == 0)
2942 {
2943 _console << "Commencing calculation of duct surface temperature " << std::endl;
2944 auto duct_nodes = _subchannel_mesh.getDuctNodes();
2945 for (Node * dn : duct_nodes)
2946 {
2947 auto * node_chan = _subchannel_mesh.getChannelNodeFromDuct(dn);
2948 auto mu = (*_mu_soln)(node_chan);
2949 auto S = (*_S_flow_soln)(node_chan);
2950 auto w_perim = (*_w_perim_soln)(node_chan);
2951 auto Dh_i = 4.0 * S / w_perim;
2952 auto Re = ((std::abs((*_mdot_soln)(node_chan)) / S) * Dh_i / mu);
2953 auto k = _fp->k_from_p_T((*_P_soln)(node_chan) + _P_out, (*_T_soln)(node_chan));
2954 auto cp = _fp->cp_from_p_T((*_P_soln)(node_chan) + _P_out, (*_T_soln)(node_chan));
2955 auto Pr = (*_mu_soln)(node_chan)*cp / k;
2956
2957 // Create nusselt number structure (consistent with pin case)
2958 const libMesh::Point & node_point = *_subchannel_mesh.getChannelNodeFromDuct(dn);
2959 const unsigned int iz = _subchannel_mesh.getZIndex(node_point);
2960 const unsigned int i_ch = _subchannel_mesh.channelIndex(node_point);
2961
2962 // Create nusselt number structure
2963 _nusselt_args = NusseltStruct(Re, Pr, std::numeric_limits<unsigned int>::max(), iz, i_ch);
2964
2965 // Create Friction structure
2966 _friction_args = FrictionStruct(i_ch, Re, S, w_perim);
2967
2968 // Compute HTC
2970
2971 // Compute Channel Temperature
2972 auto T_chan = (*_duct_heat_flux_soln)(dn) / hw + (*_T_soln)(node_chan);
2973 _Tduct_soln->set(dn, T_chan);
2974 }
2975 }
2976 _aux->solution().close();
2977 _aux->update();
2978
2979 // Called before the rank-0 early return since it broadcasts to all ranks
2981
2982 if (processor_id() != 0)
2983 return;
2984 Real power_in = 0.0;
2985 Real power_out = 0.0;
2986 Real mass_flow_in = 0.0;
2987 Real mass_flow_out = 0.0;
2988 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
2989 {
2990 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, 0);
2991 auto * node_out = _subchannel_mesh.getChannelNode(i_ch, _n_cells);
2992 const Real mdot_in = (*_mdot_soln)(node_in);
2993 power_in += mdot_in * (*_h_soln)(node_in);
2994 power_out += (*_mdot_soln)(node_out) * (*_h_soln)(node_out);
2995 mass_flow_in += mdot_in;
2996 mass_flow_out += (*_mdot_soln)(node_out);
2997 }
2998 auto h_bulk_out = power_out / mass_flow_out;
2999 auto T_bulk_out = _fp->T_from_p_h(_P_out, h_bulk_out);
3000
3001 const Real bulk_Dh = _subchannel_mesh.getAssemblyHydraulicDiameter();
3003 {
3004 _console << " ======================================= " << std::endl;
3005 _console << " ======== Subchannel Print Outs ======== " << std::endl;
3006 _console << " ======================================= " << std::endl;
3007 _console << "Total flow area :" << _subchannel_mesh.getAssemblyFlowArea() << " m^2"
3008 << std::endl;
3009 _console << "Assembly hydraulic diameter :" << bulk_Dh << " m" << std::endl;
3010 _console << "Assembly Re number :" << _bulk_Re << " [-]" << std::endl;
3011 _console << "Bulk coolant temperature at outlet :" << T_bulk_out << " K" << std::endl;
3012 _console << "Power added to coolant is : " << power_out - power_in << " Watt" << std::endl;
3013 _console << "Mass flow rate in is : " << mass_flow_in << " kg/sec" << std::endl;
3014 _console << "Mass balance is : " << mass_flow_out - mass_flow_in << " kg/sec" << std::endl;
3015 _console << "User defined outlet pressure is : " << _P_out << " Pa" << std::endl;
3016 _console << " ======================================= " << std::endl;
3017 }
3018
3019 if (MooseUtils::absoluteFuzzyLessEqual((power_out - power_in), -1.0))
3021 "Energy conservation equation might not be solved correctly, Power added to coolant: " +
3022 std::to_string(power_out - power_in) + " Watt ");
3023}
3024
3025void
Real f(Real x)
Test function for Brents method.
const double tol
const std::vector< double > x
const double mu
const double Re
const double rho
const double T
std::array< Real, 2 > values
PetscFunctionBegin
PetscErrorCode formFunction(SNES, Vec x, Vec f, void *ctx)
void ErrorVector unsigned int
const ConsoleStream _console
static InputParameters validParams()
std::shared_ptr< AuxiliarySystem > _aux
virtual Real & dt() const
virtual const MooseVariableFieldBase & getVariable(const THREAD_ID tid, const std::string &var_name, Moose::VarKindType expected_var_type=Moose::VarKindType::VAR_ANY, Moose::VarFieldType expected_var_field_type=Moose::VarFieldType::VAR_FIELD_ANY) const override
virtual void transient(bool trans)
virtual bool isTransient() const override
void initialSetup() override
void addParamNamesToGroup(const std::string &space_delim_names, const std::string group_name)
bool isParamSetByUser(const std::string &name) const
void addRequiredParam(const std::string &name, const std::string &doc_string)
void addDeprecatedParam(const std::string &name, const T &value, const std::string &doc_string, const std::string &deprecation_message)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
void addRangeCheckedParam(const std::string &name, const T &value, const std::string &parsed_function, const std::string &doc_string)
bool isRestarting() const
Executioner * getExecutioner() const
bool isRecovering() const
const std::string & name() const
void paramError(const std::string &param, Args... args) const
void mooseError(Args &&... args) const
void mooseWarning(Args &&... args) const
bool isParamValid(const std::string &name) const
MooseApp & _app
static InputParameters validParams()
virtual Real computeFrictionFactor(const FrictionStruct &friction_info) const =0
Computes the friction factor for the local conditions.
Real computeHTC(const FrictionStruct &friction_info, const NusseltStruct &nusselt_info, const Real conduction_k) const
Computes the convective heat transfer coefficient for the local conditions.
virtual void computeBulkMixingParameters() const
Precomputes the mixing coefficients that depend only on the bulk flow.
virtual Real computeSweepFlowMixingParameter(const unsigned int i_gap, const unsigned int iz) const
Computes the wire-wrap sweep-flow coefficient for peripheral gaps.
virtual void computeBlockMixingParameters(const unsigned int, const unsigned int) const
Precomputes the mixing coefficients that depend on the flow solution of the axial levels first_node t...
virtual Real getCT() const
Return the Turbulent modeling parameter.
virtual Real computeMixingParameter(const unsigned int i_gap, const unsigned int iz) const =0
Computes the turbulent mixing coefficient for the local conditions around gap(i_gap) and axial level(...
Provide a simple RAII interface for linear lagrange solution variables.
Base class for the 1-phase steady-state/transient subchannel solver.
libMesh::DenseMatrix< Real > _DP
void computeP(int iblock)
Computes Pressure per channel for block iblock.
const Real & _mass_flow_equation_relaxation
Equation relaxation factor for mass flow rate in the coupled implicit solve.
const Real & _pressure_equation_relaxation
Equation relaxation factor for pressure in the coupled implicit solve.
PetscErrorCode createPetscVector(Vec &v, PetscInt n)
Petsc Functions.
virtual void syncSolutions(Direction direction) override
const bool _segregated_bool
Segregated solve.
PetscScalar computeInterpolatedValue(PetscScalar topValue, PetscScalar botValue, PetscScalar Peclet=0.0)
const SCMMixingClosureBase * _mixing_closure
Turbulent Mixing closure object.
Real computeSweepFlowMixingParameter(unsigned int i_gap, unsigned int iz) const
Computes and validates the sweep-flow mixing parameter.
const SCMHTCClosureBase * _pin_HTC_closure
HTC closure objects.
const SCMHTCClosureBase * _duct_HTC_closure
SubChannel1PhaseProblem(const InputParameters &params)
Mat _cmc_friction_force_mat
Cross momentum conservation - friction force.
std::unique_ptr< SolutionHandle > _w_perim_soln
PetscErrorCode petscSnesSolver(int iblock, const libMesh::DenseVector< Real > &solution, libMesh::DenseVector< Real > &root)
Computes solution of nonlinear equation using snes and provided a residual in a formFunction.
const PetscReal & _dtol
The divergence tolerance for the ksp linear solver.
PetscErrorCode createPetscMatrix(Mat &M, PetscInt n, PetscInt m)
virtual void initializeSolution()=0
Function to initialize the solution & geometry fields.
std::unique_ptr< SolutionHandle > _displacement_soln
Mat _hc_time_derivative_mat
Enthalpy Enthalpy conservation - time derivative.
Mat _cmc_time_derivative_mat
Cross momentum Cross momentum conservation - time derivative.
const SCMFrictionClosureBase * _friction_closure
Friction closure object.
Mat _amc_turbulent_cross_flows_mat
Axial momentum Axial momentum conservation - compute turbulent cross fluxes.
static InputParameters validParams()
PetscErrorCode solveAndPopulateEnthalpy(Mat A, Vec rhs, unsigned int first_node, unsigned int last_node, const char *ksp_prefix)
Solve a linear system (A * x = rhs) with a simple PCJACOBI KSP and populate the enthalpy solution int...
const SinglePhaseFluidProperties * _fp
Non-owning pointer to fluid properties user object.
Mat _amc_sys_mdot_mat
Axial momentum system matrix.
Mat _cmc_sys_Wij_mat
Lateral momentum system matrix.
libMesh::DenseMatrix< Real > _WijPrime
void computeRho(int iblock)
Computes Density per channel for block iblock.
virtual Real getSubChannelPeripheralDuctWidth(unsigned int i_ch) const =0
Function that computes the width of the duct cell that the peripheral subchannel i_ch sees.
std::unique_ptr< SolutionHandle > _DP_soln
const MooseEnum _interpolation_scheme
The interpolation method used in constructing the systems.
std::unique_ptr< SolutionHandle > _rho_soln
Mat _mc_sumWij_mat
Matrices and vectors to be used in implicit assembly Mass conservation Mass conservation - sum of cro...
std::unique_ptr< SolutionHandle > _duct_heat_flux_soln
struct SubChannel1PhaseProblem::FrictionStruct _friction_args
void computeDP(int iblock)
Computes Pressure Drop per channel for block iblock.
const Real & _T_tol
Convergence tolerance for the temperature loop in internal solve.
const bool _duct_mesh_exist
Flag that informs if there is a duct mesh or not.
std::unique_ptr< SolutionHandle > _S_flow_soln
void computeWijResidual(int iblock)
Computes Residual Matrix based on the lateral momentum conservation equation for block iblock.
const bool _compute_power
Flag that informs if we need to solve the Enthalpy/Temperature equations or not.
const PostprocessorValue & _P_out
Outlet pressure postprocessor value.
virtual void initialSetup() override
std::unique_ptr< SolutionHandle > _Dpin_soln
Mat _cmc_pressure_force_mat
Cross momentum conservation - pressure force.
std::unique_ptr< SolutionHandle > _P_soln
Real _bulk_Re
Assembly bulk Reynolds number.
unsigned int _n_blocks
number of axial blocks
void detectDeformation()
Detects whether pin diameter or duct displacement fields require geometry recalculation.
Mat _amc_time_derivative_mat
Axial momentum conservation - time derivative.
libMesh::DenseMatrix< Real > & _Wij
std::unique_ptr< SolutionHandle > _Tduct_soln
bool _deformation
Flag that activates the effect of deformation (pin/duct) based on the auxvalues for displacement,...
libMesh::DenseMatrix< Real > _Wij_residual_matrix
friend PetscErrorCode formFunction(SNES snes, Vec x, Vec f, void *ctx)
This is the residual Vector function in a form compatible with the SNES PETC solvers.
std::unique_ptr< SolutionHandle > _h_soln
std::unique_ptr< SolutionHandle > _T_soln
virtual void computeh(int iblock)=0
Computes Enthalpy per channel for block iblock.
std::unique_ptr< SolutionHandle > _mu_soln
Real _TR
Flag that activates or deactivates the transient parts of the equations we solve by multiplication.
virtual void externalSolve() override
PetscScalar computeInterpolationCoefficients(PetscScalar Peclet=0.0)
Functions that computes the interpolation scheme given the Peclet number.
Mat _cmc_advective_derivative_mat
Cross momentum conservation - advective (Eulerian) derivative.
bool _time_integrator_checked
Whether the time integrator has been checked for consistency with the implementation.
Vec _hc_added_heat_rhs
Enthalpy conservation - source and sink.
const Real & _P_tol
Convergence tolerance for the pressure loop in external solve.
Mat _amc_friction_force_mat
Axial momentum conservation - friction force.
Real _CT
Turbulent modeling parameter used in axial momentum equation.
PetscErrorCode populateDenseFromVector(const Vec &x, T &solution, const unsigned int first_axial_level, const unsigned int last_axial_level, const unsigned int cross_dimension)
Real _pressure_fixed_point_error
Maximum pressure fixed-point update before solution relaxation over the blocks.
Mat _mc_density_pressure_mat
Mass conservation - pressure derivative of transient density.
const bool _compute_viscosity
Flag that activates or deactivates the calculation of viscosity.
Mat _hc_advective_derivative_mat
Enthalpy conservation - advective (Eulerian) derivative;.
const PetscInt & _maxit
The maximum number of iterations to use for the ksp linear solver.
PetscErrorCode implicitPetscSolve(int iblock)
Computes implicit solve using PetSc.
const bool _compute_density
Flag that activates or deactivates the calculation of density.
libMesh::DenseVector< Real > residualFunction(int iblock, libMesh::DenseVector< Real > solution)
Computes Residual Vector based on the lateral momentum conservation equation for block iblock & updat...
bool _converged
Variable that informs whether we exited external solve with a converged solution or not.
void computeSumWij(int iblock)
Computes net diversion crossflow per channel for block iblock.
const int & _T_maxit
Maximum iterations for the inner temperature loop.
const Real & _T_relaxation
Relaxation factor for temperature updates in the inner thermal-hydraulic iteration.
const Real & _crossflow_relaxation
Relaxation factor for crossflow updates in the coupled implicit solve.
std::unique_ptr< SolutionHandle > _Tpin_soln
const bool _pin_mesh_exist
Flag that informs if there is a pin mesh or not.
Mat _amc_pressure_force_mat
Axial momentum conservation - pressure force.
void computeMdot(int iblock)
Computes mass flow per channel for block iblock.
Real _bulk_V
Assembly bulk velocity.
std::unique_ptr< SolutionHandle > _SumWij_soln
const bool _implicit_bool
Flag to define the usage of a implicit or explicit solution.
const int & _P_maxit
Maximum number of pressure iterations; zero selects the solver's existing automatic limit.
std::vector< Real > _z_grid
axial location of nodes
virtual bool solverSystemConverged(const unsigned int) override
const Real & _mass_flow_relaxation
Relaxation factor for mass flow rate updates in the coupled implicit solve.
Mat _amc_cross_derivative_mat
Axial momentum conservation - cross flux derivative.
void computeWijFromSolve(int iblock)
Computes diversion crossflow per gap for block iblock.
const unsigned int & _enthalpy_subcycles
Number of enthalpy, temperature, and property updates performed per flow solve.
void computeMu(int iblock)
Computes Viscosity per channel for block iblock.
PetscErrorCode populateVectorFromDense(Vec &x, const T &solution, const unsigned int first_axial_level, const unsigned int last_axial_level, const unsigned int cross_dimension)
libMesh::DenseMatrix< Real > & _Wij_old
struct SubChannel1PhaseProblem::NusseltStruct _nusselt_args
std::unique_ptr< SolutionHandle > _mdot_soln
Solutions handles and link to TH tables properties.
std::unique_ptr< SolutionHandle > _q_prime_soln
virtual Real computeAddedHeatDuct(unsigned int i_ch, unsigned int iz) const
Non-pure: implemented in the base (or override in a child if needed)
const PetscReal & _rtol
The relative convergence tolerance, (relative decrease) for the ksp linear solver.
const Real & _crossflow_equation_relaxation
Equation relaxation factor for crossflow in the coupled implicit solve.
Vec _amc_gravity_rhs
Axial momentum conservation - buoyancy force No implicit matrix.
Mat _mc_axial_convection_mat
Mass conservation - axial convection.
PetscScalar _added_K
Added resistances for monolithic convergence.
Mat _amc_advective_derivative_mat
Axial momentum conservation - advective (Eulerian) derivative.
void computeBulkReynoldsNumber()
Computes the assembly bulk Reynolds number from inlet flow conditions.
Real computeT(int iblock)
Computes and relaxes Temperature per channel for block iblock.
void computeWijPrime(int iblock)
Computes turbulent crossflow per gap for block iblock.
Real computeMixingParameter(unsigned int i_gap, unsigned int iz) const
Computes and validates the turbulent mixing parameter.
std::unique_ptr< SolutionHandle > _ff_soln
const bool _verbose_subchannel
Boolean to printout information related to subchannel solve.
Mat _hc_cross_derivative_mat
Enthalpy conservation - cross flux derivative.
const PetscReal & _atol
The absolute convergence tolerance for the ksp linear solver.
const Real & _pressure_relaxation
Relaxation factor for pressure updates in the coupled implicit solve.
std::unique_ptr< SolutionHandle > _HTC_soln
static const std::string ENTHALPY
static const std::string DISPLACEMENT
static const std::string PRESSURE_DROP
static const std::string WETTED_PERIMETER
static const std::string PRESSURE
static const std::string DUCT_TEMPERATURE
static const std::string FRICTION_FACTOR
static const std::string MASS_FLOW_RATE
static const std::string SURFACE_AREA
static const std::string VISCOSITY
static const std::string DENSITY
static const std::string PIN_DIAMETER
static const std::string HEAT_TRANSFER_COEFFICIENT
static const std::string LINEAR_HEAT_RATE
static const std::string DUCT_HEAT_FLUX
static const std::string SUM_CROSSFLOW
static const std::string PIN_TEMPERATURE
static const std::string TEMPERATURE
Base class for subchannel meshes.
virtual unsigned int getNumOfChannels() const =0
Return the number of channels per layer.
virtual const std::vector< unsigned int > & getChannelPins(unsigned int i_chan) const =0
Return a vector of pin indices for a given channel index.
virtual unsigned int channelIndex(const Point &point) const =0
Real getAssemblyFlowArea() const
Return undeformed bundle inlet flow area.
Real getAssemblyHydraulicDiameter() const
Return undeformed bundle-average hydraulic diameter.
virtual const std::vector< Real > & getZGrid() const
Get axial location of layers.
Node * getChannelNodeFromDuct(Node *duct_node) const
Function that gets the channel node from the duct node.
virtual Real getGapWidth(unsigned int axial_index, unsigned int gap_index) const =0
Return gap width for a given gap index.
virtual const Real & getPitch() const
Return the undeformed pitch between 2 subchannels.
virtual const std::pair< unsigned int, unsigned int > & getGapChannels(unsigned int i_gap) const =0
Return a pair of subchannel indices for a given gap index.
const std::vector< Node * > & getDuctNodes() const
Function that returns the vector with the duct nodes.
Node * getDuctNodeFromChannel(Node *channel_node) const
Function that gets the duct node from the channel node.
virtual unsigned int getNumOfPins() const =0
Return the number of pins.
virtual EChannelType getSubchannelType(unsigned int index) const =0
Return the type of the subchannel for given subchannel index.
virtual const Real & getCrossflowSign(unsigned int i_chan, unsigned int i_local) const =0
Return a sign for the crossflow given a subchannel index and local neighbor index.
virtual const std::vector< std::vector< Real > > & getKGrid() const
Get axial cell location and value of loss coefficient.
virtual unsigned int getZIndex(const Point &point) const
Get axial index of point.
virtual Node * getChannelNode(unsigned int i_chan, unsigned int iz) const =0
Get the subchannel mesh node for a given channel index and elevation index.
virtual unsigned int getNumOfGapsPerLayer() const =0
Return the number of gaps per layer.
virtual const std::vector< unsigned int > & getChannelGaps(unsigned int i_chan) const =0
Return a vector of gap indices for a given channel index.
virtual unsigned int getNumOfAxialCells() const
Return the number of axial cells.
virtual const Real & getPinDiameter() const
Return undeformed Pin diameter.
virtual Node * getPinNode(unsigned int i_pin, unsigned int iz) const =0
Get the pin mesh node for a given pin index and elevation index.
virtual const std::vector< unsigned int > & getPinChannels(unsigned int i_pin) const =0
Return a vector of channel indices for a given Pin index.
void max(const T &r, T &o, Request &req) const
void broadcast(T &data, const unsigned int root_id=0, const bool identical_sizes=false) const
void resize(const unsigned int new_m, const unsigned int new_n)
virtual void zero() override final
const Parallel::Communicator & _communicator
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
Definition SCM.h:17
The following methods are specializations for using the Parallel::packed_range_* routines for a vecto...
static constexpr Real TOLERANCE
SubChannel1PhaseProblem * schp
structure with the needed information to compute the friction factor at a specific subchannel cell
structure with the needed information to compute the Nusselt number at a specific subchannel cell and...