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