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 Real
678 {
679  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
680  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
681  {
682  auto width = _subchannel_mesh.getPitch();
683  if (subch_type == EChannelType::CORNER)
684  width = 2.0 / std::sqrt(3.0) *
686  return width;
687  }
688  else
689  mooseError("Channel is not a perimetric subchannel ");
690 }
691 
692 void
694 {
695  unsigned int last_node = (iblock + 1) * _block_size;
696  unsigned int first_node = iblock * _block_size + 1;
697  const Real & wire_lead_length = _tri_sch_mesh.getWireLeadLength();
698  const Real & wire_diameter = _tri_sch_mesh.getWireDiameter();
699  const Real & pitch = _subchannel_mesh.getPitch();
700  const Real & pin_diameter = _subchannel_mesh.getPinDiameter();
701 
702  if (iblock == 0)
703  {
704  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
705  {
706  auto * node = _subchannel_mesh.getChannelNode(i_ch, 0);
707  auto h_out = _fp->h_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
708  if (h_out < 0)
709  {
710  mooseError(
711  name(), " : Calculation of negative Enthalpy h_out = : ", h_out, " Axial Level= : ", 0);
712  }
713  _h_soln->set(node, h_out);
714  }
715  }
716 
717  if (!_implicit_bool)
718  {
719  for (unsigned int iz = first_node; iz < last_node + 1; iz++)
720  {
721  auto z_grid = _subchannel_mesh.getZGrid();
722  auto dz = z_grid[iz] - z_grid[iz - 1];
723  // Calculation of average mass flux of all periphery subchannels
724  Real edge_flux_ave = 0.0;
725  Real mdot_sum = 0.0;
726  Real si_sum = 0.0;
727  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
728  {
729  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
730  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
731  {
732  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
733  auto Si = (*_S_flow_soln)(node_in);
734  auto mdot_in = (*_mdot_soln)(node_in);
735  mdot_sum = mdot_sum + mdot_in;
736  si_sum = si_sum + Si;
737  }
738  }
739  edge_flux_ave = mdot_sum / si_sum;
740 
741  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
742  {
743  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
744  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
745  auto mdot_in = (*_mdot_soln)(node_in);
746  auto h_in = (*_h_soln)(node_in); // J/kg
747  auto volume = dz * (*_S_flow_soln)(node_in);
748  auto mdot_out = (*_mdot_soln)(node_out);
749  auto h_out = 0.0;
750  Real sumWijh = 0.0;
751  Real sumWijPrimeDhij = 0.0;
752  Real sweep_enthalpy = 0.0;
753  Real e_cond = 0.0;
754 
755  // Calculate added enthalpy from heatflux (Pin, Duct)
756  Real added_enthalpy = computeAddedHeatPin(i_ch, iz);
757  added_enthalpy += computeAddedHeatDuct(i_ch, iz);
758 
759  // Calculate net sum of enthalpy into/out-of channel i from channels j around i
760  // (Turbulent diffusion, Diversion Crossflow, Sweep flow Enthalpy, Radial heat conduction)
761  unsigned int counter = 0;
762  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
763  {
764  auto chans = _subchannel_mesh.getGapChannels(i_gap);
765  auto gap = _subchannel_mesh.getGapWidth(iz, i_gap);
766  auto Sij = dz * gap;
767  unsigned int ii_ch = chans.first; // the first subchannel next to gap i_gap
768  unsigned int jj_ch = chans.second; // the second subchannel next to gap i_gap
769  auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
770  auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
771  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
772  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
773  // Define donor enthalpy
774  auto h_star = 0.0;
775  if (_Wij(i_gap, iz) > 0.0)
776  h_star = (*_h_soln)(node_in_i);
777  else if (_Wij(i_gap, iz) < 0.0)
778  h_star = (*_h_soln)(node_in_j);
779  // Diversion crossflow
780  // take care of the sign by applying the map, use donor cell
781  sumWijh += _subchannel_mesh.getCrossflowSign(i_ch, counter) * _Wij(i_gap, iz) * h_star;
782  counter++;
783  // SWEEP FLOW is calculated if i_gap is located in the periphery
784  // and we have a wire-wrap (if i_gap is in the periphery then i_ch is in the periphery)
785  // There are two gaps per periphery subchannel that this is true.
786  if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
787  (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE) &&
788  (wire_lead_length != 0) && (wire_diameter != 0))
789  {
790  // donor subchannel and node of sweep flow. The donor subchannel is the subchannel next
791  // to i_ch that sweep flow, flows from and into i_ch
792  auto sweep_donor = _tri_sch_mesh.getSweepFlowChans(i_ch).first;
793  auto * node_sweep_donor = _subchannel_mesh.getChannelNode(sweep_donor, iz - 1);
794  // if one of the neighbor subchannels of the periphery gap is the donor subchannel
795  //(the other would be the i_ch) sweep enthalpy flows into i_ch
796  if ((ii_ch == sweep_donor) || (jj_ch == sweep_donor))
797  {
798  sweep_enthalpy +=
799  computeBeta(i_gap, iz, true) * edge_flux_ave * Sij * (*_h_soln)(node_sweep_donor);
800  }
801  // else sweep enthalpy flows out of i_ch
802  else
803  {
804  sweep_enthalpy -=
805  computeBeta(i_gap, iz, true) * edge_flux_ave * Sij * (*_h_soln)(node_in);
806  }
807  }
808  // Inner gap
809  // Turbulent Diffusion
810  else
811  {
812  sumWijPrimeDhij +=
813  _WijPrime(i_gap, iz) * (2 * h_in - (*_h_soln)(node_in_j) - (*_h_soln)(node_in_i));
814  }
815 
816  // compute the radial heat conduction through the gaps
817  Real dist_ij = pitch;
818 
819  if (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::EDGE)
820  {
821  dist_ij = pitch;
822  }
823  else if ((subch_type_i == EChannelType::CORNER && subch_type_j == EChannelType::EDGE) ||
824  (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CORNER))
825  {
826  dist_ij = pitch;
827  }
828  else
829  {
830  dist_ij = pitch / std::sqrt(3);
831  }
832 
833  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
834  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
835  auto shape_factor =
836  0.66 * (pitch / pin_diameter) *
837  std::pow((_subchannel_mesh.getGapWidth(iz, i_gap) / pin_diameter), -0.3);
838  if (ii_ch == i_ch)
839  {
840  e_cond += 0.5 * (thcon_i + thcon_j) * Sij * shape_factor *
841  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
842  }
843  else
844  {
845  e_cond += -0.5 * (thcon_i + thcon_j) * Sij * shape_factor *
846  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
847  }
848  }
849 
850  // compute the axial heat conduction between current and lower axial node
851  auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz);
852  auto * node_in_j = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
853  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
854  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
855  auto Si = (*_S_flow_soln)(node_in_i);
856  auto dist_ij = z_grid[iz] - z_grid[iz - 1];
857 
858  e_cond += 0.5 * (thcon_i + thcon_j) * Si * ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) /
859  dist_ij;
860 
861  unsigned int nz = _subchannel_mesh.getNumOfAxialCells();
862  // compute the axial heat conduction between current and upper axial node
863  if (iz < nz)
864  {
865  auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz);
866  auto * node_in_j = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
867  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
868  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
869  auto Si = (*_S_flow_soln)(node_in_i);
870  auto dist_ij = z_grid[iz + 1] - z_grid[iz];
871  e_cond += 0.5 * (thcon_i + thcon_j) * Si *
872  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
873  }
874 
875  // end of radial heat conduction calc.
876  h_out =
877  (mdot_in * h_in - sumWijh - sumWijPrimeDhij + added_enthalpy + e_cond + sweep_enthalpy +
878  _TR * _rho_soln->old(node_out) * _h_soln->old(node_out) * volume / _dt) /
879  (mdot_out + _TR * (*_rho_soln)(node_out)*volume / _dt);
880  if (h_out < 0)
881  {
882  mooseError(name(),
883  " : Calculation of negative Enthalpy h_out = : ",
884  h_out,
885  " Axial Level= : ",
886  iz);
887  }
888  _h_soln->set(node_out, h_out); // J/kg
889  }
890  }
891  }
892  else
893  {
894  LibmeshPetscCall(MatZeroEntries(_hc_time_derivative_mat));
895  LibmeshPetscCall(MatZeroEntries(_hc_advective_derivative_mat));
896  LibmeshPetscCall(MatZeroEntries(_hc_cross_derivative_mat));
897  LibmeshPetscCall(MatZeroEntries(_hc_axial_heat_conduction_mat));
898  LibmeshPetscCall(MatZeroEntries(_hc_radial_heat_conduction_mat));
899  LibmeshPetscCall(MatZeroEntries(_hc_sweep_enthalpy_mat));
900 
901  LibmeshPetscCall(VecZeroEntries(_hc_time_derivative_rhs));
902  LibmeshPetscCall(VecZeroEntries(_hc_advective_derivative_rhs));
903  LibmeshPetscCall(VecZeroEntries(_hc_cross_derivative_rhs));
904  LibmeshPetscCall(VecZeroEntries(_hc_added_heat_rhs));
905  LibmeshPetscCall(VecZeroEntries(_hc_axial_heat_conduction_rhs));
906  LibmeshPetscCall(VecZeroEntries(_hc_radial_heat_conduction_rhs));
907  LibmeshPetscCall(VecZeroEntries(_hc_sweep_enthalpy_rhs));
908 
909  LibmeshPetscCall(MatZeroEntries(_hc_sys_h_mat));
910  LibmeshPetscCall(VecZeroEntries(_hc_sys_h_rhs));
911 
912  for (unsigned int iz = first_node; iz < last_node + 1; iz++)
913  {
914  auto dz = _z_grid[iz] - _z_grid[iz - 1];
916  auto pin_diameter = _subchannel_mesh.getPinDiameter();
917  auto iz_ind = iz - first_node;
918 
919  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
920  {
921  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
922  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
923  auto S_in = (*_S_flow_soln)(node_in);
924  auto S_out = (*_S_flow_soln)(node_out);
925  auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
926  auto volume = dz * S_interp;
927 
928  PetscScalar Pe = 0.5;
929  if (_interpolation_scheme == 3)
930  {
931  // Compute the Peclet number
932  auto w_perim_in = (*_w_perim_soln)(node_in);
933  auto w_perim_out = (*_w_perim_soln)(node_out);
934  auto w_perim_interp = this->computeInterpolatedValue(w_perim_out, w_perim_in, 0.5);
935  auto K_in = _fp->k_from_p_T((*_P_soln)(node_in) + _P_out, (*_T_soln)(node_in));
936  auto K_out = _fp->k_from_p_T((*_P_soln)(node_out) + _P_out, (*_T_soln)(node_out));
937  auto K = this->computeInterpolatedValue(K_out, K_in, 0.5);
938  auto cp_in = _fp->cp_from_p_T((*_P_soln)(node_in) + _P_out, (*_T_soln)(node_in));
939  auto cp_out = _fp->cp_from_p_T((*_P_soln)(node_out) + _P_out, (*_T_soln)(node_out));
940  auto cp = this->computeInterpolatedValue(cp_out, cp_in, 0.5);
941  auto mdot_loc =
942  this->computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), 0.5);
943  // hydraulic diameter in the i direction
944  auto Dh_i = 4.0 * S_interp / w_perim_interp;
945  Pe = mdot_loc * Dh_i * cp / (K * S_interp) * (mdot_loc / std::abs(mdot_loc));
946  }
948 
949  // Time derivative term
950  if (iz == first_node)
951  {
952  PetscScalar value_vec_tt =
953  -1.0 * _TR * alpha * (*_rho_soln)(node_in) * (*_h_soln)(node_in)*volume / _dt;
954  PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
955  LibmeshPetscCall(
956  VecSetValues(_hc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
957  }
958  else
959  {
960  PetscInt row_tt = i_ch + _n_channels * iz_ind;
961  PetscInt col_tt = i_ch + _n_channels * (iz_ind - 1);
962  PetscScalar value_tt = _TR * alpha * (*_rho_soln)(node_in)*volume / _dt;
963  LibmeshPetscCall(MatSetValues(
964  _hc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
965  }
966 
967  // Adding diagonal elements
968  PetscInt row_tt = i_ch + _n_channels * iz_ind;
969  PetscInt col_tt = i_ch + _n_channels * iz_ind;
970  PetscScalar value_tt = _TR * (1.0 - alpha) * (*_rho_soln)(node_out)*volume / _dt;
971  LibmeshPetscCall(MatSetValues(
972  _hc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
973 
974  // Adding RHS elements
975  PetscScalar rho_old_interp =
976  computeInterpolatedValue(_rho_soln->old(node_out), _rho_soln->old(node_in), Pe);
977  PetscScalar h_old_interp =
978  computeInterpolatedValue(_h_soln->old(node_out), _h_soln->old(node_in), Pe);
979  PetscScalar value_vec_tt = _TR * rho_old_interp * h_old_interp * volume / _dt;
980  PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
981  LibmeshPetscCall(
982  VecSetValues(_hc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
983 
984  // Advective derivative term
985  if (iz == first_node)
986  {
987  PetscInt row_at = i_ch + _n_channels * iz_ind;
988  PetscScalar value_at = alpha * (*_mdot_soln)(node_in) * (*_h_soln)(node_in);
989  LibmeshPetscCall(
990  VecSetValues(_hc_advective_derivative_rhs, 1, &row_at, &value_at, ADD_VALUES));
991 
992  value_at = alpha * (*_mdot_soln)(node_out) - (1 - alpha) * (*_mdot_soln)(node_in);
993  PetscInt col_at = i_ch + _n_channels * iz_ind;
994  LibmeshPetscCall(MatSetValues(
995  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
996 
997  value_at = (1 - alpha) * (*_mdot_soln)(node_out);
998  col_at = i_ch + _n_channels * (iz_ind + 1);
999  LibmeshPetscCall(MatSetValues(
1000  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1001  }
1002  else if (iz == last_node)
1003  {
1004  PetscInt row_at = i_ch + _n_channels * iz_ind;
1005  PetscScalar value_at = 1.0 * (*_mdot_soln)(node_out);
1006  PetscInt col_at = i_ch + _n_channels * iz_ind;
1007  LibmeshPetscCall(MatSetValues(
1008  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1009 
1010  value_at = -1.0 * (*_mdot_soln)(node_in);
1011  col_at = i_ch + _n_channels * (iz_ind - 1);
1012  LibmeshPetscCall(MatSetValues(
1013  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1014  }
1015  else
1016  {
1017  PetscInt row_at = i_ch + _n_channels * iz_ind;
1018  PetscInt col_at;
1019 
1020  PetscScalar value_at = -alpha * (*_mdot_soln)(node_in);
1021  col_at = i_ch + _n_channels * (iz_ind - 1);
1022  LibmeshPetscCall(MatSetValues(
1023  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1024 
1025  value_at = alpha * (*_mdot_soln)(node_out) - (1 - alpha) * (*_mdot_soln)(node_in);
1026  col_at = i_ch + _n_channels * iz_ind;
1027  LibmeshPetscCall(MatSetValues(
1028  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1029 
1030  value_at = (1 - alpha) * (*_mdot_soln)(node_out);
1031  col_at = i_ch + _n_channels * (iz_ind + 1);
1032  LibmeshPetscCall(MatSetValues(
1033  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
1034  }
1035 
1036  // Axial heat conduction
1037  auto * node_center = _subchannel_mesh.getChannelNode(i_ch, iz);
1038  auto K_center = _fp->k_from_p_T((*_P_soln)(node_center) + _P_out, (*_T_soln)(node_center));
1039  auto cp_center =
1040  _fp->cp_from_p_T((*_P_soln)(node_center) + _P_out, (*_T_soln)(node_center));
1041  auto diff_center = K_center / (cp_center + 1e-15);
1042 
1043  if (iz == first_node)
1044  {
1045  auto * node_top = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
1046  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1047  auto K_bottom =
1048  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1049  auto K_top = _fp->k_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
1050  auto cp_bottom =
1051  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1052  auto cp_top = _fp->cp_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
1053  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
1054  auto diff_top = K_top / (cp_top + 1e-15);
1055 
1056  auto dz_up = _z_grid[iz + 1] - _z_grid[iz];
1057  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
1058  auto S_up =
1059  computeInterpolatedValue((*_S_flow_soln)(node_top), (*_S_flow_soln)(node_center));
1060  auto S_down =
1061  computeInterpolatedValue((*_S_flow_soln)(node_center), (*_S_flow_soln)(node_bottom));
1062  auto diff_up = computeInterpolatedValue(diff_top, diff_center);
1063  auto diff_down = computeInterpolatedValue(diff_center, diff_bottom);
1064 
1065  // Diagonal value
1066  PetscInt row_at = i_ch + _n_channels * iz_ind;
1067  PetscInt col_at = i_ch + _n_channels * iz_ind;
1068  PetscScalar value_at = diff_up * S_up / dz_up + diff_down * S_down / dz_down;
1069  LibmeshPetscCall(MatSetValues(
1070  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1071 
1072  // Bottom value
1073  value_at = 1.0 * diff_down * S_down / dz_down * (*_h_soln)(node_bottom);
1074  LibmeshPetscCall(
1075  VecSetValues(_hc_axial_heat_conduction_rhs, 1, &row_at, &value_at, ADD_VALUES));
1076 
1077  // Top value
1078  col_at = i_ch + _n_channels * (iz_ind + 1);
1079  value_at = -diff_up * S_up / dz_up;
1080  LibmeshPetscCall(MatSetValues(
1081  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1082  }
1083  else if (iz == last_node)
1084  {
1085  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1086  auto K_bottom =
1087  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1088  auto cp_bottom =
1089  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1090  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
1091 
1092  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
1093  auto S_down = 0.5 * ((*_S_flow_soln)(node_center) + (*_S_flow_soln)(node_bottom));
1094  auto diff_down = 0.5 * (diff_center + diff_bottom);
1095 
1096  // Diagonal value
1097  PetscInt row_at = i_ch + _n_channels * iz_ind;
1098  PetscInt col_at = i_ch + _n_channels * iz_ind;
1099  PetscScalar value_at = diff_down * S_down / dz_down;
1100  LibmeshPetscCall(MatSetValues(
1101  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1102 
1103  // Bottom value
1104  col_at = i_ch + _n_channels * (iz_ind - 1);
1105  value_at = -diff_down * S_down / dz_down;
1106  LibmeshPetscCall(MatSetValues(
1107  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1108 
1109  // Outflow derivative
1111  // value_at = -1.0 * (*_mdot_soln)(node_center) * (*_h_soln)(node_center);
1112  // VecSetValues(_hc_axial_heat_conduction_rhs, 1, &row_at, &value_at, ADD_VALUES);
1113  }
1114  else
1115  {
1116  auto * node_top = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
1117  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1118  auto K_bottom =
1119  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1120  auto K_top = _fp->k_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
1121  auto cp_bottom =
1122  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
1123  auto cp_top = _fp->cp_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
1124  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
1125  auto diff_top = K_top / (cp_top + 1e-15);
1126 
1127  auto dz_up = _z_grid[iz + 1] - _z_grid[iz];
1128  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
1129  auto S_up =
1130  computeInterpolatedValue((*_S_flow_soln)(node_top), (*_S_flow_soln)(node_center));
1131  auto S_down =
1132  computeInterpolatedValue((*_S_flow_soln)(node_center), (*_S_flow_soln)(node_bottom));
1133  auto diff_up = computeInterpolatedValue(diff_top, diff_center);
1134  auto diff_down = computeInterpolatedValue(diff_center, diff_bottom);
1135 
1136  // Diagonal value
1137  PetscInt row_at = i_ch + _n_channels * iz_ind;
1138  PetscInt col_at = i_ch + _n_channels * iz_ind;
1139  PetscScalar value_at = diff_up * S_up / dz_up + diff_down * S_down / dz_down;
1140  LibmeshPetscCall(MatSetValues(
1141  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1142 
1143  // Bottom value
1144  col_at = i_ch + _n_channels * (iz_ind - 1);
1145  value_at = -diff_down * S_down / dz_down;
1146  LibmeshPetscCall(MatSetValues(
1147  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1148 
1149  // Top value
1150  col_at = i_ch + _n_channels * (iz_ind + 1);
1151  value_at = -diff_up * S_up / dz_up;
1152  LibmeshPetscCall(MatSetValues(
1153  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
1154  }
1155 
1156  // Radial Terms
1157  unsigned int counter = 0;
1158  unsigned int cross_index = iz;
1159  // Real radial_heat_conduction(0.0);
1160  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
1161  {
1162  auto chans = _subchannel_mesh.getGapChannels(i_gap);
1163  unsigned int ii_ch = chans.first;
1164  unsigned int jj_ch = chans.second;
1165  auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
1166  auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
1167  PetscScalar h_star;
1168  // figure out donor axial velocity
1169  if (_Wij(i_gap, cross_index) > 0.0)
1170  {
1171  if (iz == first_node)
1172  {
1173  h_star = (*_h_soln)(node_in_i);
1174  PetscScalar value_vec_ct = -1.0 * alpha *
1175  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1176  _Wij(i_gap, cross_index) * h_star;
1177  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1178  LibmeshPetscCall(VecSetValues(
1179  _hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1180  }
1181  else
1182  {
1183  PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1184  _Wij(i_gap, cross_index);
1185  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1186  PetscInt col_ct = ii_ch + _n_channels * (iz_ind - 1);
1187  LibmeshPetscCall(MatSetValues(
1188  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1189  }
1190  PetscScalar value_ct = (1.0 - alpha) *
1191  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1192  _Wij(i_gap, cross_index);
1193  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1194  PetscInt col_ct = ii_ch + _n_channels * iz_ind;
1195  LibmeshPetscCall(MatSetValues(
1196  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1197  }
1198  else if (_Wij(i_gap, cross_index) < 0.0) // _Wij=0 operations not necessary
1199  {
1200  if (iz == first_node)
1201  {
1202  h_star = (*_h_soln)(node_in_j);
1203  PetscScalar value_vec_ct = -1.0 * alpha *
1204  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1205  _Wij(i_gap, cross_index) * h_star;
1206  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1207  LibmeshPetscCall(VecSetValues(
1208  _hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1209  }
1210  else
1211  {
1212  PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1213  _Wij(i_gap, cross_index);
1214  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1215  PetscInt col_ct = jj_ch + _n_channels * (iz_ind - 1);
1216  LibmeshPetscCall(MatSetValues(
1217  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1218  }
1219  PetscScalar value_ct = (1.0 - alpha) *
1220  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
1221  _Wij(i_gap, cross_index);
1222  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1223  PetscInt col_ct = jj_ch + _n_channels * iz_ind;
1224  LibmeshPetscCall(MatSetValues(
1225  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
1226  }
1227 
1228  // Turbulent cross flows
1229  if (iz == first_node)
1230  {
1231  PetscScalar value_vec_ct =
1232  -2.0 * alpha * (*_h_soln)(node_in)*_WijPrime(i_gap, cross_index);
1233  value_vec_ct += alpha * (*_h_soln)(node_in_j)*_WijPrime(i_gap, cross_index);
1234  value_vec_ct += alpha * (*_h_soln)(node_in_i)*_WijPrime(i_gap, cross_index);
1235  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
1236  LibmeshPetscCall(
1237  VecSetValues(_hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
1238  }
1239  else
1240  {
1241  PetscScalar value_center_ct = 2.0 * alpha * _WijPrime(i_gap, cross_index);
1242  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1243  PetscInt col_ct = i_ch + _n_channels * (iz_ind - 1);
1244  LibmeshPetscCall(MatSetValues(
1245  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
1246 
1247  PetscScalar value_left_ct = -1.0 * alpha * _WijPrime(i_gap, cross_index);
1248  row_ct = i_ch + _n_channels * iz_ind;
1249  col_ct = jj_ch + _n_channels * (iz_ind - 1);
1250  LibmeshPetscCall(MatSetValues(
1251  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
1252 
1253  PetscScalar value_right_ct = -1.0 * alpha * _WijPrime(i_gap, cross_index);
1254  row_ct = i_ch + _n_channels * iz_ind;
1255  col_ct = ii_ch + _n_channels * (iz_ind - 1);
1256  LibmeshPetscCall(MatSetValues(
1257  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
1258  }
1259  PetscScalar value_center_ct = 2.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
1260  PetscInt row_ct = i_ch + _n_channels * iz_ind;
1261  PetscInt col_ct = i_ch + _n_channels * iz_ind;
1262  LibmeshPetscCall(MatSetValues(
1263  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
1264 
1265  PetscScalar value_left_ct = -1.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
1266  row_ct = i_ch + _n_channels * iz_ind;
1267  col_ct = jj_ch + _n_channels * iz_ind;
1268  LibmeshPetscCall(MatSetValues(
1269  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
1270 
1271  PetscScalar value_right_ct = -1.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
1272  row_ct = i_ch + _n_channels * iz_ind;
1273  col_ct = ii_ch + _n_channels * iz_ind;
1274  LibmeshPetscCall(MatSetValues(
1275  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
1276 
1277  // Radial heat conduction
1278  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
1279  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
1280  Real dist_ij = pitch;
1281 
1282  if (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::EDGE)
1283  {
1284  dist_ij = pitch;
1285  }
1286  else if ((subch_type_i == EChannelType::CORNER && subch_type_j == EChannelType::EDGE) ||
1287  (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CORNER))
1288  {
1289  dist_ij = pitch;
1290  }
1291  else
1292  {
1293  dist_ij = pitch / std::sqrt(3);
1294  }
1295 
1296  auto Sij = dz * _subchannel_mesh.getGapWidth(iz, i_gap);
1297  auto K_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
1298  auto K_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
1299  auto cp_i = _fp->cp_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
1300  auto cp_j = _fp->cp_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
1301  auto A_i = K_i / cp_i;
1302  auto A_j = K_j / cp_j;
1303  auto harm_A = 2.0 * A_i * A_j / (A_i + A_j);
1304  auto shape_factor =
1305  0.66 * (pitch / pin_diameter) *
1306  std::pow((_subchannel_mesh.getGapWidth(iz, i_gap) / pin_diameter), -0.3);
1307  // auto base_value = 0.5 * (A_i + A_j) * Sij * shape_factor / dist_ij;
1308  auto base_value = harm_A * shape_factor * Sij / dist_ij;
1309  auto neg_base_value = -1.0 * base_value;
1310 
1311  row_ct = ii_ch + _n_channels * iz_ind;
1312  col_ct = ii_ch + _n_channels * iz_ind;
1313  LibmeshPetscCall(MatSetValues(
1314  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &base_value, ADD_VALUES));
1315 
1316  row_ct = jj_ch + _n_channels * iz_ind;
1317  col_ct = jj_ch + _n_channels * iz_ind;
1318  LibmeshPetscCall(MatSetValues(
1319  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &base_value, ADD_VALUES));
1320 
1321  row_ct = ii_ch + _n_channels * iz_ind;
1322  col_ct = jj_ch + _n_channels * iz_ind;
1323  LibmeshPetscCall(MatSetValues(
1324  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &neg_base_value, ADD_VALUES));
1325 
1326  row_ct = jj_ch + _n_channels * iz_ind;
1327  col_ct = ii_ch + _n_channels * iz_ind;
1328  LibmeshPetscCall(MatSetValues(
1329  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &neg_base_value, ADD_VALUES));
1330  counter++;
1331  }
1332 
1333  // Compute the sweep flow enthalpy change
1334  // Calculation of average mass flux of all periphery subchannels
1335  Real edge_flux_ave = 0.0;
1336  Real mdot_sum = 0.0;
1337  Real si_sum = 0.0;
1338  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1339  {
1340  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
1341  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
1342  {
1343  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
1344  auto Si = (*_S_flow_soln)(node_in);
1345  auto mdot_in = (*_mdot_soln)(node_in);
1346  mdot_sum = mdot_sum + mdot_in;
1347  si_sum = si_sum + Si;
1348  }
1349  }
1350  edge_flux_ave = mdot_sum / si_sum;
1351  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
1352  PetscScalar sweep_enthalpy = 0.0;
1353  if ((subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER) &&
1354  (wire_diameter != 0.0) && (wire_lead_length != 0.0))
1355  {
1356  auto beta_in = std::numeric_limits<double>::quiet_NaN();
1357  auto beta_out = std::numeric_limits<double>::quiet_NaN();
1358  // donor sweep channel for i_ch
1359  auto sweep_donor = _tri_sch_mesh.getSweepFlowChans(i_ch).first;
1360  auto * node_sweep_donor = _subchannel_mesh.getChannelNode(sweep_donor, iz - 1);
1361  // Calculation of turbulent mixing parameter
1362  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
1363  {
1364  auto chans = _subchannel_mesh.getGapChannels(i_gap);
1365  unsigned int ii_ch = chans.first;
1366  unsigned int jj_ch = chans.second;
1367  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
1368  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
1369  if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
1370  (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE))
1371  {
1372  if ((ii_ch == sweep_donor) || (jj_ch == sweep_donor))
1373  {
1374  beta_in = computeBeta(i_gap, iz, true);
1375  }
1376  else
1377  {
1378  beta_out = computeBeta(i_gap, iz, true);
1379  }
1380  }
1381  }
1382  // Abort execution if required values are unset
1383  mooseAssert(!std::isnan(beta_in),
1384  "beta_in was not set. Check gap logic for i_ch = " + std::to_string(i_ch) +
1385  ", iz = " + std::to_string(iz));
1386  mooseAssert(!std::isnan(beta_out),
1387  "beta_out was not set. Check gap logic for i_ch = " + std::to_string(i_ch) +
1388  ", iz = " + std::to_string(iz));
1389 
1390  auto gap = _tri_sch_mesh.getDuctToPinGap();
1391  auto Sij = dz * gap;
1392  auto wsweep_in = edge_flux_ave * beta_in * Sij;
1393  auto wsweep_out = edge_flux_ave * beta_out * Sij;
1394  auto sweep_hin = (*_h_soln)(node_sweep_donor);
1395  auto sweep_hout = (*_h_soln)(node_in);
1396  sweep_enthalpy = (wsweep_in * sweep_hin - wsweep_out * sweep_hout);
1397 
1398  if (iz == first_node)
1399  {
1400  PetscInt row_sh = i_ch + _n_channels * iz_ind;
1401  PetscScalar value_hs = -sweep_enthalpy;
1402  LibmeshPetscCall(
1403  VecSetValues(_hc_sweep_enthalpy_rhs, 1, &row_sh, &value_hs, ADD_VALUES));
1404  }
1405  else
1406  {
1407  // coefficient of sweep_hin
1408  PetscInt row_sh = i_ch + _n_channels * (iz_ind - 1);
1409  PetscInt col_sh = i_ch + _n_channels * (iz_ind - 1);
1410  LibmeshPetscCall(MatSetValues(
1411  _hc_sweep_enthalpy_mat, 1, &row_sh, 1, &col_sh, &wsweep_out, ADD_VALUES));
1412  PetscInt col_sh_l = sweep_donor + _n_channels * (iz_ind - 1);
1413  PetscScalar neg_sweep_in = -1.0 * wsweep_in;
1414  // coefficient of sweep_hout
1415  LibmeshPetscCall(MatSetValues(
1416  _hc_sweep_enthalpy_mat, 1, &row_sh, 1, &col_sh_l, &(neg_sweep_in), ADD_VALUES));
1417  }
1418  }
1419 
1420  // Add heat enthalpy from pin and/or duct
1421  PetscScalar added_enthalpy = computeAddedHeatPin(i_ch, iz);
1422  added_enthalpy += computeAddedHeatDuct(i_ch, iz);
1423  PetscInt row_vec_ht = i_ch + _n_channels * iz_ind;
1424  LibmeshPetscCall(
1425  VecSetValues(_hc_added_heat_rhs, 1, &row_vec_ht, &added_enthalpy, ADD_VALUES));
1426  }
1427  }
1428  // Assembling system
1429  LibmeshPetscCall(MatAssemblyBegin(_hc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1430  LibmeshPetscCall(MatAssemblyEnd(_hc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1431  LibmeshPetscCall(MatAssemblyBegin(_hc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1432  LibmeshPetscCall(MatAssemblyEnd(_hc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1433  LibmeshPetscCall(MatAssemblyBegin(_hc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1434  LibmeshPetscCall(MatAssemblyEnd(_hc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1435  LibmeshPetscCall(MatAssemblyBegin(_hc_axial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1436  LibmeshPetscCall(MatAssemblyEnd(_hc_axial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1437  LibmeshPetscCall(MatAssemblyBegin(_hc_radial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1438  LibmeshPetscCall(MatAssemblyEnd(_hc_radial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1439  LibmeshPetscCall(MatAssemblyBegin(_hc_sweep_enthalpy_mat, MAT_FINAL_ASSEMBLY));
1440  LibmeshPetscCall(MatAssemblyEnd(_hc_sweep_enthalpy_mat, MAT_FINAL_ASSEMBLY));
1441  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1442  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1443  // Add all matrices together
1444  LibmeshPetscCall(
1445  MatAXPY(_hc_sys_h_mat, 1.0, _hc_time_derivative_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_advective_derivative_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  LibmeshPetscCall(
1453  MatAXPY(_hc_sys_h_mat, 1.0, _hc_cross_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1454  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1455  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1456  LibmeshPetscCall(
1457  MatAXPY(_hc_sys_h_mat, 1.0, _hc_axial_heat_conduction_mat, DIFFERENT_NONZERO_PATTERN));
1458  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1459  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1460  LibmeshPetscCall(
1461  MatAXPY(_hc_sys_h_mat, 1.0, _hc_radial_heat_conduction_mat, DIFFERENT_NONZERO_PATTERN));
1462  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1463  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1464  LibmeshPetscCall(
1465  MatAXPY(_hc_sys_h_mat, 1.0, _hc_sweep_enthalpy_mat, DIFFERENT_NONZERO_PATTERN));
1466  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1467  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1468  if (_verbose_subchannel)
1469  _console << "Block: " << iblock << " - Enthalpy conservation matrix assembled" << std::endl;
1470  // RHS
1471  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_time_derivative_rhs));
1472  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_advective_derivative_rhs));
1473  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_cross_derivative_rhs));
1474  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_added_heat_rhs));
1475  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_axial_heat_conduction_rhs));
1476  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_radial_heat_conduction_rhs));
1477  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_sweep_enthalpy_rhs));
1478 
1480  {
1481  // Assembly the matrix system
1482  KSP ksploc;
1483  PC pc;
1484  Vec sol;
1485  LibmeshPetscCall(VecDuplicate(_hc_sys_h_rhs, &sol));
1486  LibmeshPetscCall(KSPCreate(PETSC_COMM_WORLD, &ksploc));
1487  LibmeshPetscCall(KSPSetOperators(ksploc, _hc_sys_h_mat, _hc_sys_h_mat));
1488  LibmeshPetscCall(KSPGetPC(ksploc, &pc));
1489  LibmeshPetscCall(PCSetType(pc, PCJACOBI));
1490  LibmeshPetscCall(KSPSetTolerances(ksploc, _rtol, _atol, _dtol, _maxit));
1491  LibmeshPetscCall(KSPSetOptionsPrefix(ksploc, "h_sys_"));
1492  LibmeshPetscCall(KSPSetFromOptions(ksploc));
1493  LibmeshPetscCall(KSPSolve(ksploc, _hc_sys_h_rhs, sol));
1494  // VecView(sol, PETSC_VIEWER_STDOUT_WORLD);
1495  PetscScalar * xx;
1496  LibmeshPetscCall(VecGetArray(sol, &xx));
1497  for (unsigned int iz = first_node; iz < last_node + 1; iz++)
1498  {
1499  auto iz_ind = iz - first_node;
1500  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
1501  {
1502  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
1503  auto h_out = xx[iz_ind * _n_channels + i_ch];
1504  if (h_out < 0)
1505  {
1506  mooseError(name(),
1507  " : Calculation of negative Enthalpy h_out = : ",
1508  h_out,
1509  " Axial Level= : ",
1510  iz);
1511  }
1512  _h_soln->set(node_out, h_out);
1513  }
1514  }
1515  LibmeshPetscCall(KSPDestroy(&ksploc));
1516  LibmeshPetscCall(VecDestroy(&sol));
1517  }
1518  }
1519 }
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
Function that computes the added heat coming from the fuel pins, 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 added by the duct, for channel i_ch and cell iz.
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)
virtual Real getSubChannelPeripheralDuctWidth(unsigned int i_ch) override
Function that computes the width of the duct cell that the peripheral subchannel i_ch sees...
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.