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