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