https://mooseframework.inl.gov
TriSubChannel1PhaseProblem.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 "AuxiliarySystem.h"
12 #include "TriSubChannelMesh.h"
13 #include "SCM.h"
14 #include <limits> // for std::numeric_limits
15 #include <cmath> // for std::isnan
16 
18 
21 {
23  params.addClassDescription("Solver class for subchannels in a triangular lattice assembly and "
24  "bare/wire-wrapped fuel pins");
25  return params;
26 }
27 
29  : SubChannel1PhaseProblem(params),
30  _tri_sch_mesh(SCM::getMesh<TriSubChannelMesh>(_subchannel_mesh))
31 {
32  // Initializing heat conduction system
33  LibmeshPetscCall(createPetscMatrix(
36  LibmeshPetscCall(createPetscMatrix(
39  LibmeshPetscCall(createPetscMatrix(
42 }
43 
45 {
46  PetscErrorCode ierr = cleanUp();
47  if (ierr)
48  mooseError(name(), ": Error in memory cleanup");
49 }
50 
51 PetscErrorCode
53 {
55  // Clean up heat conduction system
56  LibmeshPetscCall(MatDestroy(&_hc_axial_heat_conduction_mat));
57  LibmeshPetscCall(VecDestroy(&_hc_axial_heat_conduction_rhs));
58  LibmeshPetscCall(MatDestroy(&_hc_radial_heat_conduction_mat));
59  LibmeshPetscCall(VecDestroy(&_hc_radial_heat_conduction_rhs));
60  LibmeshPetscCall(MatDestroy(&_hc_sweep_enthalpy_mat));
61  LibmeshPetscCall(VecDestroy(&_hc_sweep_enthalpy_rhs));
62  PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
63 }
64 
65 void
67 {
68  if (_deformation)
69  {
70  // update surface area, wetted perimeter based on: Dpin, displacement
71  Real standard_area, wire_area, additional_area, wetted_perimeter, displaced_area;
72  auto flat_to_flat = _tri_sch_mesh.getFlatToFlat();
73  auto n_rings = _tri_sch_mesh.getNumOfRings();
75  auto pin_diameter = _subchannel_mesh.getPinDiameter();
76  auto wire_diameter = _tri_sch_mesh.getWireDiameter();
77  auto wire_lead_length = _tri_sch_mesh.getWireLeadLength();
78  auto gap = _tri_sch_mesh.getDuctToPinGap();
79  auto z_blockage = _subchannel_mesh.getZBlockage();
80  auto index_blockage = _subchannel_mesh.getIndexBlockage();
81  auto reduction_blockage = _subchannel_mesh.getReductionBlockage();
82  auto theta = std::acos(wire_lead_length /
83  std::sqrt(std::pow(wire_lead_length, 2) +
84  std::pow(libMesh::pi * (pin_diameter + wire_diameter), 2)));
85  for (unsigned int iz = 0; iz < _n_cells + 1; iz++)
86  {
87  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
88  {
89  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
90  auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
91  auto Z = _z_grid[iz];
92  Real rod_area = 0.0;
93  Real rod_perimeter = 0.0;
94  for (auto i_pin : _subchannel_mesh.getChannelPins(i_ch))
95  {
96  auto * pin_node = _subchannel_mesh.getPinNode(i_pin, iz);
97  if (subch_type == EChannelType::CENTER || subch_type == EChannelType::CORNER)
98  {
99  rod_area +=
100  (1.0 / 6.0) * 0.25 * M_PI * (*_Dpin_soln)(pin_node) * (*_Dpin_soln)(pin_node);
101  rod_perimeter += (1.0 / 6.0) * M_PI * (*_Dpin_soln)(pin_node);
102  }
103  else
104  {
105  rod_area +=
106  (1.0 / 4.0) * 0.25 * M_PI * (*_Dpin_soln)(pin_node) * (*_Dpin_soln)(pin_node);
107  rod_perimeter += (1.0 / 4.0) * M_PI * (*_Dpin_soln)(pin_node);
108  }
109  }
110 
111  if (subch_type == EChannelType::CENTER)
112  {
113  standard_area = std::pow(pitch, 2.0) * std::sqrt(3.0) / 4.0;
114  additional_area = 0.0;
115  displaced_area = 0.0;
116  wire_area = libMesh::pi * std::pow(wire_diameter, 2.0) / 8.0 / std::cos(theta);
117  wetted_perimeter = rod_perimeter + 0.5 * libMesh::pi * wire_diameter / std::cos(theta);
118  }
119  else if (subch_type == EChannelType::EDGE)
120  {
121  standard_area = pitch * (pin_diameter / 2.0 + gap);
122  additional_area = 0.0;
123  displaced_area = (*_displacement_soln)(node)*pitch;
124  wire_area = libMesh::pi * std::pow(wire_diameter, 2.0) / 8.0 / std::cos(theta);
125  wetted_perimeter =
126  rod_perimeter + 0.5 * libMesh::pi * wire_diameter / std::cos(theta) + pitch;
127  }
128  else
129  {
130  standard_area = 1.0 / std::sqrt(3.0) * std::pow((pin_diameter / 2.0 + gap), 2.0);
131  additional_area = 0.0;
132  displaced_area = 1.0 / std::sqrt(3.0) *
133  (pin_diameter + 2.0 * gap + (*_displacement_soln)(node)) *
134  (*_displacement_soln)(node);
135  wire_area = libMesh::pi / 24.0 * std::pow(wire_diameter, 2.0) / std::cos(theta);
136  wetted_perimeter =
137  rod_perimeter + libMesh::pi * wire_diameter / std::cos(theta) / 6.0 +
138  2.0 / std::sqrt(3.0) * (pin_diameter / 2.0 + gap + (*_displacement_soln)(node));
139  }
140 
141  // Calculate subchannel area
142  auto subchannel_area =
143  standard_area + additional_area + displaced_area - rod_area - wire_area;
144 
145  // Correct subchannel area and wetted perimeter in case of overlapping pins
146  auto overlapping_pin_area = 0.0;
147  auto overlapping_wetted_perimeter = 0.0;
148  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
149  {
150  auto gap_pins = _subchannel_mesh.getGapPins(i_gap);
151  auto pin_1 = gap_pins.first;
152  auto pin_2 = gap_pins.second;
153  auto * pin_node_1 = _subchannel_mesh.getPinNode(pin_1, iz);
154  auto * pin_node_2 = _subchannel_mesh.getPinNode(pin_2, iz);
155  auto Diameter1 = (*_Dpin_soln)(pin_node_1);
156  auto Radius1 = Diameter1 / 2.0;
157  auto Diameter2 = (*_Dpin_soln)(pin_node_2);
158  auto Radius2 = Diameter2 / 2.0;
160 
161  if (pitch < (Radius1 + Radius2)) // overlapping pins
162  {
163  mooseWarning(" The gap of index : '", i_gap, " at axial cell ", iz, " ' is blocked.");
164  auto cos1 =
165  (pitch * pitch + Radius1 * Radius1 - Radius2 * Radius2) / (2 * pitch * Radius1);
166  auto cos2 =
167  (pitch * pitch + Radius2 * Radius2 - Radius1 * Radius1) / (2 * pitch * Radius2);
168  auto angle1 = 2.0 * acos(cos1);
169  auto angle2 = 2.0 * acos(cos2);
170  // half of the intersecting arc-length
171  overlapping_wetted_perimeter += 0.5 * angle1 * Radius1 + 0.5 * angle2 * Radius2;
172  // Half of the overlapping area
173  overlapping_pin_area +=
174  0.5 * Radius1 * Radius1 * acos(cos1) + 0.5 * Radius2 * Radius2 * acos(cos2) -
175  0.25 * sqrt((-pitch + Radius1 + Radius2) * (pitch + Radius1 - Radius2) *
176  (pitch - Radius1 + Radius2) * (pitch + Radius1 + Radius2));
177  }
178  }
179  subchannel_area += overlapping_pin_area; // correct surface area
180  wetted_perimeter += -overlapping_wetted_perimeter; // correct wetted perimeter
181 
182  // Apply area reduction on subchannels affected by blockage
183  auto index = 0;
184  for (const auto & i_blockage : index_blockage)
185  {
186  if (i_ch == i_blockage && (Z >= z_blockage.front() && Z <= z_blockage.back()))
187  {
188  subchannel_area *= reduction_blockage[index];
189  }
190  index++;
191  }
192  _S_flow_soln->set(node, subchannel_area);
193  _w_perim_soln->set(node, wetted_perimeter);
194  }
195  }
196  // update map of gap between pins (gij) based on: Dpin, displacement
197  for (unsigned int iz = 0; iz < _n_cells + 1; iz++)
198  {
199  for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
200  {
201  auto gap_pins = _subchannel_mesh.getGapPins(i_gap);
202  auto pin_1 = gap_pins.first;
203  auto pin_2 = gap_pins.second;
204  auto * pin_node_1 = _subchannel_mesh.getPinNode(pin_1, iz);
205  auto * pin_node_2 = _subchannel_mesh.getPinNode(pin_2, iz);
206 
207  if (pin_1 == pin_2) // Corner or edge gap
208  {
209  auto displacement = 0.0;
210  auto counter = 0.0;
211  for (auto i_ch : _subchannel_mesh.getPinChannels(pin_1))
212  {
213  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
214  auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
215  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
216  {
217  displacement += (*_displacement_soln)(node);
218  counter += 1.0;
219  }
220  }
221  displacement = displacement / counter;
222  _tri_sch_mesh._gij_map[iz][i_gap] =
223  0.5 * (flat_to_flat - (n_rings - 1) * pitch * std::sqrt(3.0) -
224  (*_Dpin_soln)(pin_node_1)) +
225  displacement;
226  }
227  else // center gap
228  {
229  _tri_sch_mesh._gij_map[iz][i_gap] =
230  pitch - (*_Dpin_soln)(pin_node_1) / 2.0 - (*_Dpin_soln)(pin_node_2) / 2.0;
231  }
232  // if pins come in contact, the gap is zero
233  if (_tri_sch_mesh._gij_map[iz][i_gap] <= 0.0)
234  _tri_sch_mesh._gij_map[iz][i_gap] = 0.0;
235  }
236  }
237  }
238 
239  for (unsigned int iz = 1; iz < _n_cells + 1; iz++)
240  {
241  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
242  {
243  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
244  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
245  _mdot_soln->set(node_out, (*_mdot_soln)(node_in));
246  }
247  }
248 
249  // We must do a global assembly to make sure data is parallel consistent before we do things
250  // like compute L2 norms
251  _aux->solution().close();
252 }
253 
254 Real
256 {
257  // The upgraded Cheng and Todreas correlation for pressure drop in hexagonal wire-wrapped rod
258  // bundles
259  auto Re = friction_args.Re;
260  auto i_ch = friction_args.i_ch;
261  auto S = friction_args.S;
262  auto w_perim = friction_args.w_perim;
263  auto Dh_i = 4.0 * S / w_perim;
264  Real aL, b1L, b2L, cL;
265  Real aT, b1T, b2T, cT;
266  const Real & pitch = _subchannel_mesh.getPitch();
267  const Real & pin_diameter = _subchannel_mesh.getPinDiameter();
268  const Real & wire_lead_length = _tri_sch_mesh.getWireLeadLength();
269  const Real & wire_diameter = _tri_sch_mesh.getWireDiameter();
270  auto p_over_d = pitch / pin_diameter;
271  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
272  // This gap is a constant value for the whole assembly. Might want to make it
273  // subchannel specific in the future if we have duct deformation.
274  auto gap = _tri_sch_mesh.getDuctToPinGap();
275  auto w_over_d = (pin_diameter + gap) / pin_diameter;
276  auto ReL = std::pow(10, (p_over_d - 1)) * 320.0;
277  auto ReT = std::pow(10, 0.7 * (p_over_d - 1)) * 1.0E+4;
278  auto psi = std::log(Re / ReL) / std::log(ReT / ReL);
279  const Real lambda = 7.0;
280  auto theta = std::acos(wire_lead_length /
281  std::sqrt(std::pow(wire_lead_length, 2) +
282  std::pow(libMesh::pi * (pin_diameter + wire_diameter), 2)));
283  auto wd_t = (19.56 - 98.71 * (wire_diameter / pin_diameter) +
284  303.47 * std::pow((wire_diameter / pin_diameter), 2.0)) *
285  std::pow((wire_lead_length / pin_diameter), -0.541);
286  auto wd_l = 1.4 * wd_t;
287  auto ws_t = -11.0 * std::log(wire_lead_length / pin_diameter) + 19.0;
288  auto ws_l = ws_t;
289  Real pw_p = 0.0;
290  Real ar = 0.0;
291  Real a_p = 0.0;
292 
293  // Find the coefficients of bare Pin bundle friction factor
294  // correlations for turbulent and laminar flow regimes. Todreas & Kazimi, Nuclear Systems
295  // second edition, Volume 1, Chapter 9.6
296  if (subch_type == EChannelType::CENTER)
297  {
298  if (p_over_d < 1.1)
299  {
300  aL = 26.0;
301  b1L = 888.2;
302  b2L = -3334.0;
303  aT = 0.09378;
304  b1T = 1.398;
305  b2T = -8.664;
306  }
307  else
308  {
309  aL = 62.97;
310  b1L = 216.9;
311  b2L = -190.2;
312  aT = 0.1458;
313  b1T = 0.03632;
314  b2T = -0.03333;
315  }
316  // laminar flow friction factor for bare Pin bundle - Center subchannel
317  cL = aL + b1L * (p_over_d - 1) + b2L * std::pow((p_over_d - 1), 2.0);
318  // turbulent flow friction factor for bare Pin bundle - Center subchannel
319  cT = aT + b1T * (p_over_d - 1) + b2T * std::pow((p_over_d - 1), 2.0);
320  }
321  else if (subch_type == EChannelType::EDGE)
322  {
323  if (w_over_d < 1.1)
324  {
325  aL = 26.18;
326  b1L = 554.5;
327  b2L = -1480.0;
328  aT = 0.09377;
329  b1T = 0.8732;
330  b2T = -3.341;
331  }
332  else
333  {
334  aL = 44.4;
335  b1L = 256.7;
336  b2L = -267.6;
337  aT = 0.1430;
338  b1T = 0.04199;
339  b2T = -0.04428;
340  }
341  // laminar flow friction factor for bare Pin bundle - Edge subchannel
342  cL = aL + b1L * (w_over_d - 1) + b2L * std::pow((w_over_d - 1), 2.0);
343  // turbulent flow friction factor for bare Pin bundle - Edge subchannel
344  cT = aT + b1T * (w_over_d - 1) + b2T * std::pow((w_over_d - 1), 2.0);
345  }
346  else
347  {
348  if (w_over_d < 1.1)
349  {
350  aL = 26.98;
351  b1L = 1636.0;
352  b2L = -10050.0;
353  aT = 0.1004;
354  b1T = 1.625;
355  b2T = -11.85;
356  }
357  else
358  {
359  aL = 87.26;
360  b1L = 38.59;
361  b2L = -55.12;
362  aT = 0.1499;
363  b1T = 0.006706;
364  b2T = -0.009567;
365  }
366  // laminar flow friction factor for bare Pin bundle - Corner subchannel
367  cL = aL + b1L * (w_over_d - 1) + b2L * std::pow((w_over_d - 1), 2.0);
368  // turbulent flow friction factor for bare Pin bundle - Corner subchannel
369  cT = aT + b1T * (w_over_d - 1) + b2T * std::pow((w_over_d - 1), 2.0);
370  }
371 
372  // Find the coefficients of wire-wrapped Pin bundle friction factor
373  // correlations for turbulent and laminar flow regimes. Todreas & Kazimi, Nuclear Systems
374  // Volume 1 Chapter 9-6 also Chen and Todreas (2018).
375  if ((wire_diameter != 0.0) && (wire_lead_length != 0.0))
376  {
377  if (subch_type == EChannelType::CENTER)
378  {
379  // wetted perimeter for center subchannel and bare Pin bundle
380  pw_p = libMesh::pi * pin_diameter / 2.0;
381  // wire projected area - center subchannel wire-wrapped bundle
382  ar = libMesh::pi * (pin_diameter + wire_diameter) * wire_diameter / 6.0;
383  // bare Pin bundle center subchannel flow area (normal area + wire area)
384  a_p = S + libMesh::pi * std::pow(wire_diameter, 2.0) / 8.0 / std::cos(theta);
385  // turbulent friction factor equation constant - Center subchannel
386  cT *= (pw_p / w_perim);
387  cT += wd_t * (3.0 * ar / a_p) * (Dh_i / wire_lead_length) *
388  std::pow((Dh_i / wire_diameter), 0.18);
389  // laminar friction factor equation constant - Center subchannel
390  cL *= (pw_p / w_perim);
391  cL += wd_l * (3.0 * ar / a_p) * (Dh_i / wire_lead_length) * (Dh_i / wire_diameter);
392  }
393  else if (subch_type == EChannelType::EDGE)
394  {
395  // wire projected area - edge subchannel wire-wrapped bundle
396  ar = libMesh::pi * (pin_diameter + wire_diameter) * wire_diameter / 4.0;
397  // bare Pin bundle edge subchannel flow area (normal area + wire area)
398  a_p = S + libMesh::pi * std::pow(wire_diameter, 2.0) / 8.0 / std::cos(theta);
399  // turbulent friction factor equation constant - Edge subchannel
400  cT *= std::pow((1 + ws_t * (ar / a_p) * std::pow(std::tan(theta), 2.0)), 1.41);
401  // laminar friction factor equation constant - Edge subchannel
402  cL *= (1 + ws_l * (ar / a_p) * std::pow(std::tan(theta), 2.0));
403  }
404  else
405  {
406  // wire projected area - corner subchannel wire-wrapped bundle
407  ar = libMesh::pi * (pin_diameter + wire_diameter) * wire_diameter / 6.0;
408  // bare Pin bundle corner subchannel flow area (normal area + wire area)
409  a_p = S + libMesh::pi * std::pow(wire_diameter, 2.0) / 24.0 / std::cos(theta);
410  // turbulent friction factor equation constant - Corner subchannel
411  cT *= std::pow((1 + ws_t * (ar / a_p) * std::pow(std::tan(theta), 2.0)), 1.41);
412  // laminar friction factor equation constant - Corner subchannel
413  cL *= (1 + ws_l * (ar / a_p) * std::pow(std::tan(theta), 2.0));
414  }
415  }
416 
417  // laminar friction factor
418  auto fL = cL / Re;
419  // turbulent friction factor
420  auto fT = cT / std::pow(Re, 0.18);
421 
422  if (Re < ReL)
423  {
424  // laminar flow
425  return fL;
426  }
427  else if (Re > ReT)
428  {
429  // turbulent flow
430  return fT;
431  }
432  else
433  {
434  // transient flow: psi definition uses a Bulk ReT/ReL number, same for all channels
435  return fL * std::pow((1 - psi), 1.0 / 3.0) * (1 - std::pow(psi, lambda)) +
436  fT * std::pow(psi, 1.0 / 3.0);
437  }
438 }
439 
440 Real
441 TriSubChannel1PhaseProblem::computeBeta(unsigned int i_gap, unsigned int iz, bool enthalpy)
442 {
443  auto beta = std::numeric_limits<double>::quiet_NaN();
444  const Real & pitch = _subchannel_mesh.getPitch();
445  const Real & pin_diameter = _subchannel_mesh.getPinDiameter();
446  const Real & wire_lead_length = _tri_sch_mesh.getWireLeadLength();
447  const Real & wire_diameter = _tri_sch_mesh.getWireDiameter();
448  auto chans = _subchannel_mesh.getGapChannels(i_gap);
449  auto Nr = _tri_sch_mesh._n_rings;
450  unsigned int i_ch = chans.first;
451  unsigned int j_ch = chans.second;
452  auto subch_type_i = _subchannel_mesh.getSubchannelType(i_ch);
453  auto subch_type_j = _subchannel_mesh.getSubchannelType(j_ch);
454  auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
455  auto * node_out_i = _subchannel_mesh.getChannelNode(i_ch, iz);
456  auto * node_in_j = _subchannel_mesh.getChannelNode(j_ch, iz - 1);
457  auto * node_out_j = _subchannel_mesh.getChannelNode(j_ch, iz);
458  auto Si_in = (*_S_flow_soln)(node_in_i);
459  auto Sj_in = (*_S_flow_soln)(node_in_j);
460  auto Si_out = (*_S_flow_soln)(node_out_i);
461  auto Sj_out = (*_S_flow_soln)(node_out_j);
462  auto S_total = Si_in + Sj_in + Si_out + Sj_out;
463  auto Si = 0.5 * (Si_in + Si_out);
464  auto Sj = 0.5 * (Sj_in + Sj_out);
465  auto w_perim_i = 0.5 * ((*_w_perim_soln)(node_in_i) + (*_w_perim_soln)(node_out_i));
466  auto w_perim_j = 0.5 * ((*_w_perim_soln)(node_in_j) + (*_w_perim_soln)(node_out_j));
467  auto avg_mu = (1 / S_total) * ((*_mu_soln)(node_out_i)*Si_out + (*_mu_soln)(node_in_i)*Si_in +
468  (*_mu_soln)(node_out_j)*Sj_out + (*_mu_soln)(node_in_j)*Sj_in);
469  auto avg_hD = 4.0 * (Si + Sj) / (w_perim_i + w_perim_j);
470  auto avg_massflux =
471  0.5 * (((*_mdot_soln)(node_in_i) + (*_mdot_soln)(node_in_j)) / (Si_in + Sj_in) +
472  ((*_mdot_soln)(node_out_i) + (*_mdot_soln)(node_out_j)) / (Si_out + Sj_out));
473  auto Re = avg_massflux * avg_hD / avg_mu;
474  // crossflow area between channels i,j (dz*gap_width)
475  auto gap = _subchannel_mesh.getGapWidth(iz, i_gap);
476  // Calculation of flow regime
477  auto ReL = 320.0 * std::pow(10.0, pitch / pin_diameter - 1);
478  auto ReT = 10000.0 * std::pow(10.0, 0.7 * (pitch / pin_diameter - 1));
479  // Calculation of Turbulent Crossflow for wire-wrapped triangular assemblies. Cheng &
480  // Todreas (1986).
481  // INNER SUBCHANNELS
482  if ((subch_type_i == EChannelType::CENTER || subch_type_j == EChannelType::CENTER) &&
483  (wire_lead_length != 0) && (wire_diameter != 0))
484  {
485  // Calculation of geometric parameters
486  // wire angle
487  auto theta = std::acos(wire_lead_length /
488  std::sqrt(std::pow(wire_lead_length, 2) +
489  std::pow(libMesh::pi * (pin_diameter + wire_diameter), 2)));
490  // projected area of wire on subchannel
491  auto Ar1 = libMesh::pi * (pin_diameter + wire_diameter) * wire_diameter / 6.0;
492  // bare subchannel flow area
493  auto A1prime =
494  (std::sqrt(3.0) / 4.0) * std::pow(pitch, 2) - libMesh::pi * std::pow(pin_diameter, 2) / 8.0;
495  // wire-wrapped subchannel flow area
496  auto A1 = A1prime - libMesh::pi * std::pow(wire_diameter, 2) / 8.0 / std::cos(theta);
497  // empirical constant for mixing parameter
498  auto Cm = 0.0;
499  auto CmL_constant = 0.0;
500  auto CmT_constant = 0.0;
501 
502  if (Nr == 1)
503  {
504  CmT_constant = 0.1;
505  CmL_constant = 0.055;
506  }
507  else
508  {
509  CmT_constant = 0.14;
510  CmL_constant = 0.077;
511  }
512 
513  auto CmT = CmT_constant * std::pow((pitch - pin_diameter) / pin_diameter, -0.5);
514  auto CmL = CmL_constant * std::pow((pitch - pin_diameter) / pin_diameter, -0.5);
515 
516  if (Re < ReL)
517  {
518  Cm = CmL;
519  }
520  else if (Re > ReT)
521  {
522  Cm = CmT;
523  }
524  else
525  {
526  auto psi = (std::log(Re) - std::log(ReL)) / (std::log(ReT) - std::log(ReL));
527  auto gamma = 2.0 / 3.0;
528  Cm = CmL + (CmT - CmL) * std::pow(psi, gamma);
529  }
530  // mixing parameter
531  beta = Cm * std::pow(Ar1 / A1, 0.5) * std::tan(theta);
532  }
533  // EDGE OR CORNER SUBCHANNELS/ SWEEP FLOW
534  else if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
535  (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE) &&
536  (wire_lead_length != 0) && (wire_diameter != 0))
537  {
538  auto theta = std::acos(wire_lead_length /
539  std::sqrt(std::pow(wire_lead_length, 2) +
540  std::pow(libMesh::pi * (pin_diameter + wire_diameter), 2)));
541  // Calculation of geometric parameters
542  // distance from pin surface to duct
543  auto dpgap = _tri_sch_mesh.getDuctToPinGap();
544  // Edge pitch parameter defined as pin diameter plus distance to duct wall
545  auto w = pin_diameter + dpgap;
546  auto Ar2 = libMesh::pi * (pin_diameter + wire_diameter) * wire_diameter / 4.0;
547  auto A2prime = pitch * (w - pin_diameter / 2.0) - libMesh::pi * std::pow(pin_diameter, 2) / 8.0;
548  auto A2 = A2prime - libMesh::pi * std::pow(wire_diameter, 2) / 8.0 / std::cos(theta);
549  // empirical constant for mixing parameter
550  auto Cs = 0.0;
551  auto CsL_constant = 0.0;
552  auto CsT_constant = 0.0;
553  if (Nr == 1)
554  {
555  CsT_constant = 0.6;
556  CsL_constant = 0.33;
557  }
558  else
559  {
560  CsT_constant = 0.75;
561  CsL_constant = 0.413;
562  }
563  auto CsL = CsL_constant * std::pow(wire_lead_length / pin_diameter, 0.3);
564  auto CsT = CsT_constant * std::pow(wire_lead_length / pin_diameter, 0.3);
565 
566  if (Re < ReL)
567  {
568  Cs = CsL;
569  }
570  else if (Re > ReT)
571  {
572  Cs = CsT;
573  }
574  else
575  {
576  auto psi = (std::log(Re) - std::log(ReL)) / (std::log(ReT) - std::log(ReL));
577  auto gamma = 2.0 / 3.0;
578  Cs = CsL + (CsT - CsL) * std::pow(psi, gamma);
579  }
580  // Calculation of turbulent mixing parameter used for sweep flow only
581  if (enthalpy)
582  beta = Cs * std::pow(Ar2 / A2, 0.5) * std::tan(theta);
583  else
584  beta = 0.0;
585  }
586  // Calculation of Turbulent Crossflow for bare assemblies, from Kim and Chung (2001).
587  else if ((wire_lead_length == 0) && (wire_diameter == 0))
588  {
589  Real gamma = 20.0; // empirical constant
590  Real sf = 2.0 / 3.0; // shape factor
591  Real a = 0.18;
592  Real b = 0.2;
593  auto f = a * std::pow(Re, -b); // Rehme 1992 circular tube friction factor
594  auto k = (1 / S_total) *
595  (_fp->k_from_p_T((*_P_soln)(node_out_i) + _P_out, (*_T_soln)(node_out_i)) * Si_out +
596  _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i)) * Si_in +
597  _fp->k_from_p_T((*_P_soln)(node_out_j) + _P_out, (*_T_soln)(node_out_j)) * Sj_out +
598  _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j)) * Sj_in);
599  auto cp = (1 / S_total) *
600  (_fp->cp_from_p_T((*_P_soln)(node_out_i) + _P_out, (*_T_soln)(node_out_i)) * Si_out +
601  _fp->cp_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i)) * Si_in +
602  _fp->cp_from_p_T((*_P_soln)(node_out_j) + _P_out, (*_T_soln)(node_out_j)) * Sj_out +
603  _fp->cp_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j)) * Sj_in);
604  auto Pr = avg_mu * cp / k; // Prandtl number
605  auto Pr_t = Pr * (Re / gamma) * std::sqrt(f / 8.0); // Turbulent Prandtl number
606  auto delta = pitch / std::sqrt(3.0); // centroid to centroid distance
607  auto L_x = sf * delta; // axial length scale (gap is the lateral length scale)
608  auto lamda = gap / L_x; // aspect ratio
609  auto a_x = 1.0 - 2.0 * lamda * lamda / libMesh::pi; // velocity coefficient
610  auto z_FP_over_D = (2.0 * L_x / pin_diameter) *
611  (1 + (-0.5 * std::log(lamda) + 0.5 * std::log(4.0) - 0.25) * lamda * lamda);
612  auto Str = 1.0 / (0.822 * (gap / pin_diameter) + 0.144); // Strouhal number (Wu & Trupp 1994)
613  auto freq_factor = 2.0 / std::pow(gamma, 2) * std::sqrt(a / 8.0) * (avg_hD / gap);
614  auto rod_mixing = (1 / Pr_t) * lamda;
615  auto axial_mixing = a_x * z_FP_over_D * Str;
616  // Mixing Stanton number: Stg (eq 25,Kim and Chung (2001), eq 19 (Jeong et. al 2005)
617  beta = freq_factor * (rod_mixing + axial_mixing) * std::pow(Re, -b / 2.0);
618  }
619  mooseAssert(beta >= 0, "beta should be positive or zero.");
620  return beta;
621 }
622 
623 Real
624 TriSubChannel1PhaseProblem::computeAddedHeatPin(unsigned int i_ch, unsigned int iz)
625 {
626  // Compute axial location of nodes.
627  auto z2 = _z_grid[iz];
628  auto z1 = _z_grid[iz - 1];
629  auto heated_length = _subchannel_mesh.getHeatedLength();
630  auto unheated_length_entry = _subchannel_mesh.getHeatedLengthEntry();
631  if (MooseUtils::absoluteFuzzyGreaterThan(z2, unheated_length_entry) &&
632  MooseUtils::absoluteFuzzyLessThan(z1, unheated_length_entry + heated_length))
633  {
634  // Compute the height of this element.
635  auto dz = z2 - z1;
636  if (_pin_mesh_exist)
637  {
638  double factor;
639  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
640  switch (subch_type)
641  {
643  factor = 1.0 / 6.0;
644  break;
645  case EChannelType::EDGE:
646  factor = 1.0 / 4.0;
647  break;
649  factor = 1.0 / 6.0;
650  break;
651  default:
652  return 0.0; // handle invalid subch_type if needed
653  }
654  double heat_rate_in = 0.0;
655  double heat_rate_out = 0.0;
656  for (auto i_pin : _subchannel_mesh.getChannelPins(i_ch))
657  {
658  auto * node_in = _subchannel_mesh.getPinNode(i_pin, iz - 1);
659  auto * node_out = _subchannel_mesh.getPinNode(i_pin, iz);
660  heat_rate_out += factor * (*_q_prime_soln)(node_out);
661  heat_rate_in += factor * (*_q_prime_soln)(node_in);
662  }
663  return (heat_rate_in + heat_rate_out) * dz / 2.0;
664  }
665  else
666  {
667  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
668  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
669  return ((*_q_prime_soln)(node_out) + (*_q_prime_soln)(node_in)) * dz / 2.0;
670  }
671  }
672  else
673  return 0.0;
674 }
675 
676 void
678 {
679  unsigned int last_node = (iblock + 1) * _block_size;
680  unsigned int first_node = iblock * _block_size + 1;
681  const Real & wire_lead_length = _tri_sch_mesh.getWireLeadLength();
682  const Real & wire_diameter = _tri_sch_mesh.getWireDiameter();
683  const Real & pitch = _subchannel_mesh.getPitch();
684  const Real & pin_diameter = _subchannel_mesh.getPinDiameter();
685 
686  if (iblock == 0)
687  {
688  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
689  {
690  auto * node = _subchannel_mesh.getChannelNode(i_ch, 0);
691  auto h_out = _fp->h_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
692  if (h_out < 0)
693  {
694  mooseError(
695  name(), " : Calculation of negative Enthalpy h_out = : ", h_out, " Axial Level= : ", 0);
696  }
697  _h_soln->set(node, h_out);
698  }
699  }
700 
701  if (!_implicit_bool)
702  {
703  for (unsigned int iz = first_node; iz < last_node + 1; iz++)
704  {
705  auto z_grid = _subchannel_mesh.getZGrid();
706  auto dz = z_grid[iz] - z_grid[iz - 1];
707  // Calculation of average mass flux of all periphery subchannels
708  Real edge_flux_ave = 0.0;
709  Real mdot_sum = 0.0;
710  Real si_sum = 0.0;
711  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
712  {
713  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
714  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
715  {
716  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
717  auto Si = (*_S_flow_soln)(node_in);
718  auto mdot_in = (*_mdot_soln)(node_in);
719  mdot_sum = mdot_sum + mdot_in;
720  si_sum = si_sum + Si;
721  }
722  }
723  edge_flux_ave = mdot_sum / si_sum;
724 
725  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
726  {
727  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
728  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
729  auto mdot_in = (*_mdot_soln)(node_in);
730  auto h_in = (*_h_soln)(node_in); // J/kg
731  auto volume = dz * (*_S_flow_soln)(node_in);
732  auto mdot_out = (*_mdot_soln)(node_out);
733  auto h_out = 0.0;
734  Real sumWijh = 0.0;
735  Real sumWijPrimeDhij = 0.0;
736  Real sweep_enthalpy = 0.0;
737  Real e_cond = 0.0;
738 
739  // Calculate added enthalpy from heatflux (Pin, Duct)
740  Real added_enthalpy = computeAddedHeatPin(i_ch, iz);
741  added_enthalpy += computeAddedHeatDuct(i_ch, iz);
742 
743  // Calculate net sum of enthalpy into/out-of channel i from channels j around i
744  // (Turbulent diffusion, Diversion Crossflow, Sweep flow Enthalpy, Radial heat conduction)
745  unsigned int counter = 0;
746  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
747  {
748  auto chans = _subchannel_mesh.getGapChannels(i_gap);
749  auto gap = _subchannel_mesh.getGapWidth(iz, i_gap);
750  auto Sij = dz * gap;
751  unsigned int ii_ch = chans.first; // the first subchannel next to gap i_gap
752  unsigned int jj_ch = chans.second; // the second subchannel next to gap i_gap
753  auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
754  auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
755  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
756  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
757  // Define donor enthalpy
758  auto h_star = 0.0;
759  if (_Wij(i_gap, iz) > 0.0)
760  h_star = (*_h_soln)(node_in_i);
761  else if (_Wij(i_gap, iz) < 0.0)
762  h_star = (*_h_soln)(node_in_j);
763  // Diversion crossflow
764  // take care of the sign by applying the map, use donor cell
765  sumWijh += _subchannel_mesh.getCrossflowSign(i_ch, counter) * _Wij(i_gap, iz) * h_star;
766  counter++;
767  // SWEEP FLOW is calculated if i_gap is located in the periphery
768  // and we have a wire-wrap (if i_gap is in the periphery then i_ch is in the periphery)
769  // There are two gaps per periphery subchannel that this is true.
770  if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
771  (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE) &&
772  (wire_lead_length != 0) && (wire_diameter != 0))
773  {
774  // donor subchannel and node of sweep flow. The donor subchannel is the subchannel next
775  // to i_ch that sweep flow, flows from and into i_ch
776  auto sweep_donor = _tri_sch_mesh.getSweepFlowChans(i_ch).first;
777  auto * node_sweep_donor = _subchannel_mesh.getChannelNode(sweep_donor, iz - 1);
778  // if one of the neighbor subchannels of the periphery gap is the donor subchannel
779  //(the other would be the i_ch) sweep enthalpy flows into i_ch
780  if ((ii_ch == sweep_donor) || (jj_ch == sweep_donor))
781  {
782  sweep_enthalpy +=
783  computeBeta(i_gap, iz, true) * edge_flux_ave * Sij * (*_h_soln)(node_sweep_donor);
784  }
785  // else sweep enthalpy flows out of i_ch
786  else
787  {
788  sweep_enthalpy -=
789  computeBeta(i_gap, iz, true) * edge_flux_ave * Sij * (*_h_soln)(node_in);
790  }
791  }
792  // Inner gap
793  // Turbulent Diffusion
794  else
795  {
796  sumWijPrimeDhij +=
797  _WijPrime(i_gap, iz) * (2 * h_in - (*_h_soln)(node_in_j) - (*_h_soln)(node_in_i));
798  }
799 
800  // compute the radial heat conduction through the gaps
801  Real dist_ij = pitch;
802 
803  if (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::EDGE)
804  {
805  dist_ij = pitch;
806  }
807  else if ((subch_type_i == EChannelType::CORNER && subch_type_j == EChannelType::EDGE) ||
808  (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CORNER))
809  {
810  dist_ij = pitch;
811  }
812  else
813  {
814  dist_ij = pitch / std::sqrt(3);
815  }
816 
817  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
818  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
819  auto shape_factor =
820  0.66 * (pitch / pin_diameter) *
821  std::pow((_subchannel_mesh.getGapWidth(iz, i_gap) / pin_diameter), -0.3);
822  if (ii_ch == i_ch)
823  {
824  e_cond += 0.5 * (thcon_i + thcon_j) * Sij * shape_factor *
825  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
826  }
827  else
828  {
829  e_cond += -0.5 * (thcon_i + thcon_j) * Sij * shape_factor *
830  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
831  }
832  }
833 
834  // compute the axial heat conduction between current and lower axial node
835  auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz);
836  auto * node_in_j = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
837  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
838  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
839  auto Si = (*_S_flow_soln)(node_in_i);
840  auto dist_ij = z_grid[iz] - z_grid[iz - 1];
841 
842  e_cond += 0.5 * (thcon_i + thcon_j) * Si * ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) /
843  dist_ij;
844 
845  unsigned int nz = _subchannel_mesh.getNumOfAxialCells();
846  // compute the axial heat conduction between current and upper axial node
847  if (iz < nz)
848  {
849  auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz);
850  auto * node_in_j = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
851  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
852  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
853  auto Si = (*_S_flow_soln)(node_in_i);
854  auto dist_ij = z_grid[iz + 1] - z_grid[iz];
855  e_cond += 0.5 * (thcon_i + thcon_j) * Si *
856  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
857  }
858 
859  // end of radial heat conduction calc.
860  h_out =
861  (mdot_in * h_in - sumWijh - sumWijPrimeDhij + added_enthalpy + e_cond + sweep_enthalpy +
862  _TR * _rho_soln->old(node_out) * _h_soln->old(node_out) * volume / _dt) /
863  (mdot_out + _TR * (*_rho_soln)(node_out)*volume / _dt);
864  if (h_out < 0)
865  {
866  mooseError(name(),
867  " : Calculation of negative Enthalpy h_out = : ",
868  h_out,
869  " Axial Level= : ",
870  iz);
871  }
872  _h_soln->set(node_out, h_out); // J/kg
873  }
874  }
875  }
876  else
877  {
878  LibmeshPetscCall(MatZeroEntries(_hc_time_derivative_mat));
879  LibmeshPetscCall(MatZeroEntries(_hc_advective_derivative_mat));
880  LibmeshPetscCall(MatZeroEntries(_hc_cross_derivative_mat));
881  LibmeshPetscCall(MatZeroEntries(_hc_axial_heat_conduction_mat));
882  LibmeshPetscCall(MatZeroEntries(_hc_radial_heat_conduction_mat));
883  LibmeshPetscCall(MatZeroEntries(_hc_sweep_enthalpy_mat));
884 
885  LibmeshPetscCall(VecZeroEntries(_hc_time_derivative_rhs));
886  LibmeshPetscCall(VecZeroEntries(_hc_advective_derivative_rhs));
887  LibmeshPetscCall(VecZeroEntries(_hc_cross_derivative_rhs));
888  LibmeshPetscCall(VecZeroEntries(_hc_added_heat_rhs));
889  LibmeshPetscCall(VecZeroEntries(_hc_axial_heat_conduction_rhs));
890  LibmeshPetscCall(VecZeroEntries(_hc_radial_heat_conduction_rhs));
891  LibmeshPetscCall(VecZeroEntries(_hc_sweep_enthalpy_rhs));
892 
893  LibmeshPetscCall(MatZeroEntries(_hc_sys_h_mat));
894  LibmeshPetscCall(VecZeroEntries(_hc_sys_h_rhs));
895 
896  for (unsigned int iz = first_node; iz < last_node + 1; iz++)
897  {
898  auto dz = _z_grid[iz] - _z_grid[iz - 1];
900  auto pin_diameter = _subchannel_mesh.getPinDiameter();
901  auto iz_ind = iz - first_node;
902 
903  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
904  {
905  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
906  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
907  auto S_in = (*_S_flow_soln)(node_in);
908  auto S_out = (*_S_flow_soln)(node_out);
909  auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
910  auto volume = dz * S_interp;
911 
912  PetscScalar Pe = 0.5;
913  if (_interpolation_scheme == 3)
914  {
915  // Compute the Peclet number
916  auto w_perim_in = (*_w_perim_soln)(node_in);
917  auto w_perim_out = (*_w_perim_soln)(node_out);
918  auto w_perim_interp = this->computeInterpolatedValue(w_perim_out, w_perim_in, 0.5);
919  auto K_in = _fp->k_from_p_T((*_P_soln)(node_in) + _P_out, (*_T_soln)(node_in));
920  auto K_out = _fp->k_from_p_T((*_P_soln)(node_out) + _P_out, (*_T_soln)(node_out));
921  auto K = this->computeInterpolatedValue(K_out, K_in, 0.5);
922  auto cp_in = _fp->cp_from_p_T((*_P_soln)(node_in) + _P_out, (*_T_soln)(node_in));
923  auto cp_out = _fp->cp_from_p_T((*_P_soln)(node_out) + _P_out, (*_T_soln)(node_out));
924  auto cp = this->computeInterpolatedValue(cp_out, cp_in, 0.5);
925  auto mdot_loc =
926  this->computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), 0.5);
927  // hydraulic diameter in the i direction
928  auto Dh_i = 4.0 * S_interp / w_perim_interp;
929  Pe = mdot_loc * Dh_i * cp / (K * S_interp) * (mdot_loc / std::abs(mdot_loc));
930  }
932 
933  // Time derivative term
934  if (iz == first_node)
935  {
936  PetscScalar value_vec_tt =
937  -1.0 * _TR * alpha * (*_rho_soln)(node_in) * (*_h_soln)(node_in)*volume / _dt;
938  PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
939  LibmeshPetscCall(
940  VecSetValues(_hc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
941  }
942  else
943  {
944  PetscInt row_tt = i_ch + _n_channels * iz_ind;
945  PetscInt col_tt = i_ch + _n_channels * (iz_ind - 1);
946  PetscScalar value_tt = _TR * alpha * (*_rho_soln)(node_in)*volume / _dt;
947  LibmeshPetscCall(MatSetValues(
948  _hc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
949  }
950 
951  // Adding diagonal elements
952  PetscInt row_tt = i_ch + _n_channels * iz_ind;
953  PetscInt col_tt = i_ch + _n_channels * iz_ind;
954  PetscScalar value_tt = _TR * (1.0 - alpha) * (*_rho_soln)(node_out)*volume / _dt;
955  LibmeshPetscCall(MatSetValues(
956  _hc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
957 
958  // Adding RHS elements
959  PetscScalar rho_old_interp =
960  computeInterpolatedValue(_rho_soln->old(node_out), _rho_soln->old(node_in), Pe);
961  PetscScalar h_old_interp =
962  computeInterpolatedValue(_h_soln->old(node_out), _h_soln->old(node_in), Pe);
963  PetscScalar value_vec_tt = _TR * rho_old_interp * h_old_interp * volume / _dt;
964  PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
965  LibmeshPetscCall(
966  VecSetValues(_hc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
967 
968  // Advective derivative term
969  if (iz == first_node)
970  {
971  PetscInt row_at = i_ch + _n_channels * iz_ind;
972  PetscScalar value_at = alpha * (*_mdot_soln)(node_in) * (*_h_soln)(node_in);
973  LibmeshPetscCall(
974  VecSetValues(_hc_advective_derivative_rhs, 1, &row_at, &value_at, ADD_VALUES));
975 
976  value_at = alpha * (*_mdot_soln)(node_out) - (1 - alpha) * (*_mdot_soln)(node_in);
977  PetscInt col_at = i_ch + _n_channels * iz_ind;
978  LibmeshPetscCall(MatSetValues(
979  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
980 
981  value_at = (1 - alpha) * (*_mdot_soln)(node_out);
982  col_at = i_ch + _n_channels * (iz_ind + 1);
983  LibmeshPetscCall(MatSetValues(
984  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
985  }
986  else if (iz == last_node)
987  {
988  PetscInt row_at = i_ch + _n_channels * iz_ind;
989  PetscScalar value_at = 1.0 * (*_mdot_soln)(node_out);
990  PetscInt col_at = i_ch + _n_channels * iz_ind;
991  LibmeshPetscCall(MatSetValues(
992  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
993 
994  value_at = -1.0 * (*_mdot_soln)(node_in);
995  col_at = i_ch + _n_channels * (iz_ind - 1);
996  LibmeshPetscCall(MatSetValues(
997  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
998  }
999  else
1000  {
1001  PetscInt row_at = i_ch + _n_channels * iz_ind;
1002  PetscInt col_at;
1003 
1004  PetscScalar value_at = -alpha * (*_mdot_soln)(node_in);
1005  col_at = i_ch + _n_channels * (iz_ind - 1);
1006  LibmeshPetscCall(MatSetValues(
1007  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1008 
1009  value_at = alpha * (*_mdot_soln)(node_out) - (1 - alpha) * (*_mdot_soln)(node_in);
1010  col_at = i_ch + _n_channels * iz_ind;
1011  LibmeshPetscCall(MatSetValues(
1012  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1013 
1014  value_at = (1 - alpha) * (*_mdot_soln)(node_out);
1015  col_at = i_ch + _n_channels * (iz_ind + 1);
1016  LibmeshPetscCall(MatSetValues(
1017  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1018  }
1019 
1020  // Axial heat conduction
1021  auto * node_center = _subchannel_mesh.getChannelNode(i_ch, iz);
1022  auto K_center = _fp->k_from_p_T((*_P_soln)(node_center) + _P_out, (*_T_soln)(node_center));
1023  auto cp_center =
1024  _fp->cp_from_p_T((*_P_soln)(node_center) + _P_out, (*_T_soln)(node_center));
1025  auto diff_center = K_center / (cp_center + 1e-15);
1026 
1027  if (iz == first_node)
1028  {
1029  auto * node_top = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
1030  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1031  auto K_bottom =
1032  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1033  auto K_top = _fp->k_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
1034  auto cp_bottom =
1035  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1036  auto cp_top = _fp->cp_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
1037  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
1038  auto diff_top = K_top / (cp_top + 1e-15);
1039 
1040  auto dz_up = _z_grid[iz + 1] - _z_grid[iz];
1041  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
1042  auto S_up =
1043  computeInterpolatedValue((*_S_flow_soln)(node_top), (*_S_flow_soln)(node_center));
1044  auto S_down =
1045  computeInterpolatedValue((*_S_flow_soln)(node_center), (*_S_flow_soln)(node_bottom));
1046  auto diff_up = computeInterpolatedValue(diff_top, diff_center);
1047  auto diff_down = computeInterpolatedValue(diff_center, diff_bottom);
1048 
1049  // Diagonal value
1050  PetscInt row_at = i_ch + _n_channels * iz_ind;
1051  PetscInt col_at = i_ch + _n_channels * iz_ind;
1052  PetscScalar value_at = diff_up * S_up / dz_up + diff_down * S_down / dz_down;
1053  LibmeshPetscCall(MatSetValues(
1054  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1055 
1056  // Bottom value
1057  value_at = 1.0 * diff_down * S_down / dz_down * (*_h_soln)(node_bottom);
1058  LibmeshPetscCall(
1059  VecSetValues(_hc_axial_heat_conduction_rhs, 1, &row_at, &value_at, ADD_VALUES));
1060 
1061  // Top value
1062  col_at = i_ch + _n_channels * (iz_ind + 1);
1063  value_at = -diff_up * S_up / dz_up;
1064  LibmeshPetscCall(MatSetValues(
1065  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1066  }
1067  else if (iz == last_node)
1068  {
1069  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1070  auto K_bottom =
1071  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1072  auto cp_bottom =
1073  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1074  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
1075 
1076  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
1077  auto S_down = 0.5 * ((*_S_flow_soln)(node_center) + (*_S_flow_soln)(node_bottom));
1078  auto diff_down = 0.5 * (diff_center + diff_bottom);
1079 
1080  // Diagonal value
1081  PetscInt row_at = i_ch + _n_channels * iz_ind;
1082  PetscInt col_at = i_ch + _n_channels * iz_ind;
1083  PetscScalar value_at = diff_down * S_down / dz_down;
1084  LibmeshPetscCall(MatSetValues(
1085  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1086 
1087  // Bottom value
1088  col_at = i_ch + _n_channels * (iz_ind - 1);
1089  value_at = -diff_down * S_down / dz_down;
1090  LibmeshPetscCall(MatSetValues(
1091  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1092 
1093  // Outflow derivative
1095  // value_at = -1.0 * (*_mdot_soln)(node_center) * (*_h_soln)(node_center);
1096  // VecSetValues(_hc_axial_heat_conduction_rhs, 1, &row_at, &value_at, ADD_VALUES);
1097  }
1098  else
1099  {
1100  auto * node_top = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
1101  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1102  auto K_bottom =
1103  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1104  auto K_top = _fp->k_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
1105  auto cp_bottom =
1106  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1107  auto cp_top = _fp->cp_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
1108  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
1109  auto diff_top = K_top / (cp_top + 1e-15);
1110 
1111  auto dz_up = _z_grid[iz + 1] - _z_grid[iz];
1112  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
1113  auto S_up =
1114  computeInterpolatedValue((*_S_flow_soln)(node_top), (*_S_flow_soln)(node_center));
1115  auto S_down =
1116  computeInterpolatedValue((*_S_flow_soln)(node_center), (*_S_flow_soln)(node_bottom));
1117  auto diff_up = computeInterpolatedValue(diff_top, diff_center);
1118  auto diff_down = computeInterpolatedValue(diff_center, diff_bottom);
1119 
1120  // Diagonal value
1121  PetscInt row_at = i_ch + _n_channels * iz_ind;
1122  PetscInt col_at = i_ch + _n_channels * iz_ind;
1123  PetscScalar value_at = diff_up * S_up / dz_up + diff_down * S_down / dz_down;
1124  LibmeshPetscCall(MatSetValues(
1125  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1126 
1127  // Bottom value
1128  col_at = i_ch + _n_channels * (iz_ind - 1);
1129  value_at = -diff_down * S_down / dz_down;
1130  LibmeshPetscCall(MatSetValues(
1131  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1132 
1133  // Top value
1134  col_at = i_ch + _n_channels * (iz_ind + 1);
1135  value_at = -diff_up * S_up / dz_up;
1136  LibmeshPetscCall(MatSetValues(
1137  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1138  }
1139 
1140  // Radial Terms
1141  unsigned int counter = 0;
1142  unsigned int cross_index = iz;
1143  // Real radial_heat_conduction(0.0);
1144  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
1145  {
1146  auto chans = _subchannel_mesh.getGapChannels(i_gap);
1147  unsigned int ii_ch = chans.first;
1148  unsigned int jj_ch = chans.second;
1149  auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
1150  auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
1151  PetscScalar h_star;
1152  // figure out donor axial velocity
1153  if (_Wij(i_gap, cross_index) > 0.0)
1154  {
1155  if (iz == first_node)
1156  {
1157  h_star = (*_h_soln)(node_in_i);
1158  PetscScalar value_vec_ct = -1.0 * alpha *
1159  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1160  _Wij(i_gap, cross_index) * h_star;
1161  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1162  LibmeshPetscCall(VecSetValues(
1163  _hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1164  }
1165  else
1166  {
1167  PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1168  _Wij(i_gap, cross_index);
1169  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1170  PetscInt col_ct = ii_ch + _n_channels * (iz_ind - 1);
1171  LibmeshPetscCall(MatSetValues(
1172  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1173  }
1174  PetscScalar value_ct = (1.0 - alpha) *
1175  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1176  _Wij(i_gap, cross_index);
1177  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1178  PetscInt col_ct = ii_ch + _n_channels * iz_ind;
1179  LibmeshPetscCall(MatSetValues(
1180  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1181  }
1182  else if (_Wij(i_gap, cross_index) < 0.0) // _Wij=0 operations not necessary
1183  {
1184  if (iz == first_node)
1185  {
1186  h_star = (*_h_soln)(node_in_j);
1187  PetscScalar value_vec_ct = -1.0 * alpha *
1188  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1189  _Wij(i_gap, cross_index) * h_star;
1190  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1191  LibmeshPetscCall(VecSetValues(
1192  _hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1193  }
1194  else
1195  {
1196  PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1197  _Wij(i_gap, cross_index);
1198  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1199  PetscInt col_ct = jj_ch + _n_channels * (iz_ind - 1);
1200  LibmeshPetscCall(MatSetValues(
1201  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1202  }
1203  PetscScalar value_ct = (1.0 - alpha) *
1204  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1205  _Wij(i_gap, cross_index);
1206  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1207  PetscInt col_ct = jj_ch + _n_channels * iz_ind;
1208  LibmeshPetscCall(MatSetValues(
1209  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1210  }
1211 
1212  // Turbulent cross flows
1213  if (iz == first_node)
1214  {
1215  PetscScalar value_vec_ct =
1216  -2.0 * alpha * (*_h_soln)(node_in)*_WijPrime(i_gap, cross_index);
1217  value_vec_ct += alpha * (*_h_soln)(node_in_j)*_WijPrime(i_gap, cross_index);
1218  value_vec_ct += alpha * (*_h_soln)(node_in_i)*_WijPrime(i_gap, cross_index);
1219  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1220  LibmeshPetscCall(
1221  VecSetValues(_hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1222  }
1223  else
1224  {
1225  PetscScalar value_center_ct = 2.0 * alpha * _WijPrime(i_gap, cross_index);
1226  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1227  PetscInt col_ct = i_ch + _n_channels * (iz_ind - 1);
1228  LibmeshPetscCall(MatSetValues(
1229  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
1230 
1231  PetscScalar value_left_ct = -1.0 * alpha * _WijPrime(i_gap, cross_index);
1232  row_ct = i_ch + _n_channels * iz_ind;
1233  col_ct = jj_ch + _n_channels * (iz_ind - 1);
1234  LibmeshPetscCall(MatSetValues(
1235  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
1236 
1237  PetscScalar value_right_ct = -1.0 * alpha * _WijPrime(i_gap, cross_index);
1238  row_ct = i_ch + _n_channels * iz_ind;
1239  col_ct = ii_ch + _n_channels * (iz_ind - 1);
1240  LibmeshPetscCall(MatSetValues(
1241  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
1242  }
1243  PetscScalar value_center_ct = 2.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
1244  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1245  PetscInt col_ct = i_ch + _n_channels * iz_ind;
1246  LibmeshPetscCall(MatSetValues(
1247  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
1248 
1249  PetscScalar value_left_ct = -1.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
1250  row_ct = i_ch + _n_channels * iz_ind;
1251  col_ct = jj_ch + _n_channels * iz_ind;
1252  LibmeshPetscCall(MatSetValues(
1253  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
1254 
1255  PetscScalar value_right_ct = -1.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
1256  row_ct = i_ch + _n_channels * iz_ind;
1257  col_ct = ii_ch + _n_channels * iz_ind;
1258  LibmeshPetscCall(MatSetValues(
1259  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
1260 
1261  // Radial heat conduction
1262  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
1263  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
1264  Real dist_ij = pitch;
1265 
1266  if (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::EDGE)
1267  {
1268  dist_ij = pitch;
1269  }
1270  else if ((subch_type_i == EChannelType::CORNER && subch_type_j == EChannelType::EDGE) ||
1271  (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CORNER))
1272  {
1273  dist_ij = pitch;
1274  }
1275  else
1276  {
1277  dist_ij = pitch / std::sqrt(3);
1278  }
1279 
1280  auto Sij = dz * _subchannel_mesh.getGapWidth(iz, i_gap);
1281  auto K_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
1282  auto K_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
1283  auto cp_i = _fp->cp_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
1284  auto cp_j = _fp->cp_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
1285  auto A_i = K_i / cp_i;
1286  auto A_j = K_j / cp_j;
1287  auto harm_A = 2.0 * A_i * A_j / (A_i + A_j);
1288  auto shape_factor =
1289  0.66 * (pitch / pin_diameter) *
1290  std::pow((_subchannel_mesh.getGapWidth(iz, i_gap) / pin_diameter), -0.3);
1291  // auto base_value = 0.5 * (A_i + A_j) * Sij * shape_factor / dist_ij;
1292  auto base_value = harm_A * shape_factor * Sij / dist_ij;
1293  auto neg_base_value = -1.0 * base_value;
1294 
1295  row_ct = ii_ch + _n_channels * iz_ind;
1296  col_ct = ii_ch + _n_channels * iz_ind;
1297  LibmeshPetscCall(MatSetValues(
1298  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &base_value, ADD_VALUES));
1299 
1300  row_ct = jj_ch + _n_channels * iz_ind;
1301  col_ct = jj_ch + _n_channels * iz_ind;
1302  LibmeshPetscCall(MatSetValues(
1303  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &base_value, ADD_VALUES));
1304 
1305  row_ct = ii_ch + _n_channels * iz_ind;
1306  col_ct = jj_ch + _n_channels * iz_ind;
1307  LibmeshPetscCall(MatSetValues(
1308  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &neg_base_value, ADD_VALUES));
1309 
1310  row_ct = jj_ch + _n_channels * iz_ind;
1311  col_ct = ii_ch + _n_channels * iz_ind;
1312  LibmeshPetscCall(MatSetValues(
1313  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &neg_base_value, ADD_VALUES));
1314  counter++;
1315  }
1316 
1317  // Compute the sweep flow enthalpy change
1318  // Calculation of average mass flux of all periphery subchannels
1319  Real edge_flux_ave = 0.0;
1320  Real mdot_sum = 0.0;
1321  Real si_sum = 0.0;
1322  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1323  {
1324  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
1325  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
1326  {
1327  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1328  auto Si = (*_S_flow_soln)(node_in);
1329  auto mdot_in = (*_mdot_soln)(node_in);
1330  mdot_sum = mdot_sum + mdot_in;
1331  si_sum = si_sum + Si;
1332  }
1333  }
1334  edge_flux_ave = mdot_sum / si_sum;
1335  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
1336  PetscScalar sweep_enthalpy = 0.0;
1337  if ((subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER) &&
1338  (wire_diameter != 0.0) && (wire_lead_length != 0.0))
1339  {
1340  auto beta_in = std::numeric_limits<double>::quiet_NaN();
1341  auto beta_out = std::numeric_limits<double>::quiet_NaN();
1342  // donor sweep channel for i_ch
1343  auto sweep_donor = _tri_sch_mesh.getSweepFlowChans(i_ch).first;
1344  auto * node_sweep_donor = _subchannel_mesh.getChannelNode(sweep_donor, iz - 1);
1345  // Calculation of turbulent mixing parameter
1346  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
1347  {
1348  auto chans = _subchannel_mesh.getGapChannels(i_gap);
1349  unsigned int ii_ch = chans.first;
1350  unsigned int jj_ch = chans.second;
1351  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
1352  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
1353  if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
1354  (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE))
1355  {
1356  if ((ii_ch == sweep_donor) || (jj_ch == sweep_donor))
1357  {
1358  beta_in = computeBeta(i_gap, iz, true);
1359  }
1360  else
1361  {
1362  beta_out = computeBeta(i_gap, iz, true);
1363  }
1364  }
1365  }
1366  // Abort execution if required values are unset
1367  mooseAssert(!std::isnan(beta_in),
1368  "beta_in was not set. Check gap logic for i_ch = " + std::to_string(i_ch) +
1369  ", iz = " + std::to_string(iz));
1370  mooseAssert(!std::isnan(beta_out),
1371  "beta_out was not set. Check gap logic for i_ch = " + std::to_string(i_ch) +
1372  ", iz = " + std::to_string(iz));
1373 
1374  auto gap = _tri_sch_mesh.getDuctToPinGap();
1375  auto Sij = dz * gap;
1376  auto wsweep_in = edge_flux_ave * beta_in * Sij;
1377  auto wsweep_out = edge_flux_ave * beta_out * Sij;
1378  auto sweep_hin = (*_h_soln)(node_sweep_donor);
1379  auto sweep_hout = (*_h_soln)(node_in);
1380  sweep_enthalpy = (wsweep_in * sweep_hin - wsweep_out * sweep_hout);
1381 
1382  if (iz == first_node)
1383  {
1384  PetscInt row_sh = i_ch + _n_channels * iz_ind;
1385  PetscScalar value_hs = -sweep_enthalpy;
1386  LibmeshPetscCall(
1387  VecSetValues(_hc_sweep_enthalpy_rhs, 1, &row_sh, &value_hs, ADD_VALUES));
1388  }
1389  else
1390  {
1391  // coefficient of sweep_hin
1392  PetscInt row_sh = i_ch + _n_channels * (iz_ind - 1);
1393  PetscInt col_sh = i_ch + _n_channels * (iz_ind - 1);
1394  LibmeshPetscCall(MatSetValues(
1395  _hc_sweep_enthalpy_mat, 1, &row_sh, 1, &col_sh, &wsweep_out, ADD_VALUES));
1396  PetscInt col_sh_l = sweep_donor + _n_channels * (iz_ind - 1);
1397  PetscScalar neg_sweep_in = -1.0 * wsweep_in;
1398  // coefficient of sweep_hout
1399  LibmeshPetscCall(MatSetValues(
1400  _hc_sweep_enthalpy_mat, 1, &row_sh, 1, &col_sh_l, &(neg_sweep_in), ADD_VALUES));
1401  }
1402  }
1403 
1404  // Add heat enthalpy from pin and/or duct
1405  PetscScalar added_enthalpy = computeAddedHeatPin(i_ch, iz);
1406  added_enthalpy += computeAddedHeatDuct(i_ch, iz);
1407  PetscInt row_vec_ht = i_ch + _n_channels * iz_ind;
1408  LibmeshPetscCall(
1409  VecSetValues(_hc_added_heat_rhs, 1, &row_vec_ht, &added_enthalpy, ADD_VALUES));
1410  }
1411  }
1412  // Assembling system
1413  LibmeshPetscCall(MatAssemblyBegin(_hc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1414  LibmeshPetscCall(MatAssemblyEnd(_hc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1415  LibmeshPetscCall(MatAssemblyBegin(_hc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1416  LibmeshPetscCall(MatAssemblyEnd(_hc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1417  LibmeshPetscCall(MatAssemblyBegin(_hc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1418  LibmeshPetscCall(MatAssemblyEnd(_hc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1419  LibmeshPetscCall(MatAssemblyBegin(_hc_axial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1420  LibmeshPetscCall(MatAssemblyEnd(_hc_axial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1421  LibmeshPetscCall(MatAssemblyBegin(_hc_radial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1422  LibmeshPetscCall(MatAssemblyEnd(_hc_radial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1423  LibmeshPetscCall(MatAssemblyBegin(_hc_sweep_enthalpy_mat, MAT_FINAL_ASSEMBLY));
1424  LibmeshPetscCall(MatAssemblyEnd(_hc_sweep_enthalpy_mat, MAT_FINAL_ASSEMBLY));
1425  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1426  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1427  // Add all matrices together
1428  LibmeshPetscCall(
1429  MatAXPY(_hc_sys_h_mat, 1.0, _hc_time_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1430  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1431  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1432  LibmeshPetscCall(
1433  MatAXPY(_hc_sys_h_mat, 1.0, _hc_advective_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1434  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1435  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1436  LibmeshPetscCall(
1437  MatAXPY(_hc_sys_h_mat, 1.0, _hc_cross_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1438  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1439  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1440  LibmeshPetscCall(
1441  MatAXPY(_hc_sys_h_mat, 1.0, _hc_axial_heat_conduction_mat, DIFFERENT_NONZERO_PATTERN));
1442  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1443  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1444  LibmeshPetscCall(
1445  MatAXPY(_hc_sys_h_mat, 1.0, _hc_radial_heat_conduction_mat, DIFFERENT_NONZERO_PATTERN));
1446  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1447  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1448  LibmeshPetscCall(
1449  MatAXPY(_hc_sys_h_mat, 1.0, _hc_sweep_enthalpy_mat, DIFFERENT_NONZERO_PATTERN));
1450  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1451  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1452  if (_verbose_subchannel)
1453  _console << "Block: " << iblock << " - Enthalpy conservation matrix assembled" << std::endl;
1454  // RHS
1455  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_time_derivative_rhs));
1456  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_advective_derivative_rhs));
1457  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_cross_derivative_rhs));
1458  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_added_heat_rhs));
1459  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_axial_heat_conduction_rhs));
1460  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_radial_heat_conduction_rhs));
1461  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_sweep_enthalpy_rhs));
1462 
1464  {
1465  // Assembly the matrix system
1466  KSP ksploc;
1467  PC pc;
1468  Vec sol;
1469  LibmeshPetscCall(VecDuplicate(_hc_sys_h_rhs, &sol));
1470  LibmeshPetscCall(KSPCreate(PETSC_COMM_WORLD, &ksploc));
1471  LibmeshPetscCall(KSPSetOperators(ksploc, _hc_sys_h_mat, _hc_sys_h_mat));
1472  LibmeshPetscCall(KSPGetPC(ksploc, &pc));
1473  LibmeshPetscCall(PCSetType(pc, PCJACOBI));
1474  LibmeshPetscCall(KSPSetTolerances(ksploc, _rtol, _atol, _dtol, _maxit));
1475  LibmeshPetscCall(KSPSetOptionsPrefix(ksploc, "h_sys_"));
1476  LibmeshPetscCall(KSPSetFromOptions(ksploc));
1477  LibmeshPetscCall(KSPSolve(ksploc, _hc_sys_h_rhs, sol));
1478  // VecView(sol, PETSC_VIEWER_STDOUT_WORLD);
1479  PetscScalar * xx;
1480  LibmeshPetscCall(VecGetArray(sol, &xx));
1481  for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1482  {
1483  auto iz_ind = iz - first_node;
1484  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1485  {
1486  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
1487  auto h_out = xx[iz_ind * _n_channels + i_ch];
1488  if (h_out < 0)
1489  {
1490  mooseError(name(),
1491  " : Calculation of negative Enthalpy h_out = : ",
1492  h_out,
1493  " Axial Level= : ",
1494  iz);
1495  }
1496  _h_soln->set(node_out, h_out);
1497  }
1498  }
1499  LibmeshPetscCall(KSPDestroy(&ksploc));
1500  LibmeshPetscCall(VecDestroy(&sol));
1501  }
1502  }
1503 }
const bool _pin_mesh_exist
Flag that informs if there is a pin mesh or not.
virtual Real computeAddedHeatPin(unsigned int i_ch, unsigned int iz) override
Computes added heat for channel i_ch and cell iz.
virtual const std::pair< unsigned int, unsigned int > & getGapPins(unsigned int i_gap) const =0
Return a pair of pin indices for a given gap index.
virtual const Real & getPinDiameter() const
Return Pin diameter.
std::unique_ptr< SolutionHandle > _T_soln
std::unique_ptr< SolutionHandle > _h_soln
const PetscReal & _dtol
The divergence tolerance for the ksp linear solver.
const bool _monolithic_thermal_bool
Thermal monolithic bool.
virtual const std::vector< unsigned int > & getPinChannels(unsigned int i_pin) const =0
Return a vector of channel indices for a given Pin index.
T & getMesh(MooseMesh &mesh)
function to cast mesh
Definition: SCM.h:35
Real _TR
Flag that activates or deactivates the transient parts of the equations we solve by multiplication...
virtual const Real & getDuctToPinGap() const
Return the the gap thickness between the duct and peripheral fuel pins.
const PostprocessorValue & _P_out
Outlet Pressure.
virtual Real computeFrictionFactor(FrictionStruct friction_args) override
Computes the axial friction factor for the sodium coolant and for each subchannel.
virtual const std::vector< unsigned int > & getChannelPins(unsigned int i_chan) const =0
Return a vector of pin indices for a given channel index.
virtual const std::vector< Real > & getZGrid() const
Get axial location of layers.
static const std::string K
Definition: NS.h:170
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
virtual void computeh(int iblock) override
Computes Enthalpy per channel for block iblock.
static InputParameters validParams()
PetscErrorCode createPetscVector(Vec &v, PetscInt n)
Petsc Functions.
int delta(unsigned int i, unsigned int j)
Delta function, which returns zero if $i j$ and unity if $i=j$.
PetscFunctionBegin
static InputParameters validParams()
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)
const bool _segregated_bool
Segregated solve.
virtual const unsigned int & getNumOfRings() const
Return the number of rings.
virtual const std::vector< unsigned int > & getChannelGaps(unsigned int i_chan) const =0
Return a vector of gap indices for a given channel index.
Real computeAddedHeatDuct(unsigned int i_ch, unsigned int iz)
Function that computes the heat flux added by the duct.
virtual Node * getChannelNode(unsigned int i_chan, unsigned iz) const =0
Get the subchannel mesh node for a given channel index and elevation index.
std::unique_ptr< SolutionHandle > _rho_soln
static const std::string cp
Definition: NS.h:121
Vec _hc_added_heat_rhs
Enthalpy conservation - source and sink.
unsigned int _n_rings
number of rings of fuel pins
std::vector< Real > _z_grid
axial location of nodes
const std::string & name() const
Triangular subchannel solver.
virtual const Real & getHeatedLength() const
Return heated length.
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
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.
std::shared_ptr< AuxiliarySystem > _aux
Mat _hc_cross_derivative_mat
Enthalpy conservation - cross flux derivative.
bool absoluteFuzzyLessThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
const PetscInt & _maxit
The maximum number of iterations to use for the ksp linear solver.
PetscErrorCode createPetscMatrix(Mat &M, PetscInt n, PetscInt m)
std::vector< std::vector< Real > > _gij_map
gap size
const MooseEnum _interpolation_scheme
The interpolation method used in constructing the systems.
std::unique_ptr< SolutionHandle > _Dpin_soln
virtual const std::vector< Real > & getReductionBlockage() const
Get area reduction of blocked subchannels.
Real volume(const MeshBase &mesh, unsigned int dim=libMesh::invalid_uint)
Mesh class for triangular, edge and corner subchannels for hexagonal lattice fuel assemblies...
Base class for the 1-phase steady-state/transient subchannel solver.
virtual const Real & getFlatToFlat() const
Return flat to flat [m].
std::unique_ptr< SolutionHandle > _mdot_soln
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.
virtual const std::pair< unsigned int, unsigned int > & getSweepFlowChans(unsigned int i_chan) const
static const std::string Z
Definition: NS.h:169
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
virtual const std::vector< Real > & getZBlockage() const
Get axial location of blockage (in,out) [m].
libMesh::DenseMatrix< Real > & _Wij
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sqrt(_arg)) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tanh
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.
virtual void initializeSolution() override
Function to initialize the solution & geometry fields.
void mooseWarning(Args &&... args) const
const bool _verbose_subchannel
Boolean to printout information related to subchannel solve.
void mooseError(Args &&... args) const
virtual const Real & getWireLeadLength() const
Return the wire lead length.
void addClassDescription(const std::string &doc_string)
Mat _hc_advective_derivative_mat
Enthalpy conservation - advective (Eulerian) derivative;.
virtual const std::vector< unsigned int > & getIndexBlockage() const
Get index of blocked subchannels.
const bool _implicit_bool
Flag to define the usage of a implicit or explicit solution.
const ConsoleStream _console
registerMooseObject("SubChannelApp", TriSubChannel1PhaseProblem)
Mat _hc_time_derivative_mat
Enthalpy Enthalpy conservation - time derivative.
const bool _deformation
Flag that activates the effect of deformation (pin/duct) based on the auxvalues for displacement...
libMesh::DenseMatrix< Real > _WijPrime
PetscFunctionReturn(LIBMESH_PETSC_SUCCESS)
TriSubChannel1PhaseProblem(const InputParameters &params)
virtual Real computeBeta(unsigned int i_gap, unsigned int iz, bool enthalpy) override
Computes turbulent mixing coefficient.
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
static const std::string cL
Definition: NS.h:111
Definition: SCM.h:16
bool absoluteFuzzyGreaterThan(const T &var1, const T2 &var2, const T3 &tol=libMesh::TOLERANCE *libMesh::TOLERANCE)
virtual const Real & getHeatedLengthEntry() const
Return unheated length at entry.
const SinglePhaseFluidProperties * _fp
Solutions handles and link to TH tables properties.
const Real pi
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.
virtual const Real & getWireDiameter() const
Return wire diameter.