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