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