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"
15 #include "SCM.h"
16 #include <limits> // for std::numeric_limits
17 #include <cmath> // for std::isnan
18 #include "SCMMixingClosureBase.h"
19 
21 
24 {
26  params.addClassDescription("Solver class for subchannels in a triangular lattice assembly and "
27  "bare/wire-wrapped fuel pins");
28  return params;
29 }
30 
32  : SubChannel1PhaseProblem(params),
33  _tri_sch_mesh(SCM::getMesh<TriSubChannelMesh>(_subchannel_mesh))
34 {
35  // Initializing heat conduction system
36  LibmeshPetscCall(createPetscMatrix(
39  LibmeshPetscCall(createPetscMatrix(
42  LibmeshPetscCall(createPetscMatrix(
45 }
46 
48 {
49  PetscErrorCode ierr = cleanUp();
50  if (ierr)
51  mooseError(name(), ": Error in memory cleanup");
52 }
53 
54 PetscErrorCode
56 {
58  // Clean up heat conduction system
59  LibmeshPetscCall(MatDestroy(&_hc_axial_heat_conduction_mat));
60  LibmeshPetscCall(VecDestroy(&_hc_axial_heat_conduction_rhs));
61  LibmeshPetscCall(MatDestroy(&_hc_radial_heat_conduction_mat));
62  LibmeshPetscCall(VecDestroy(&_hc_radial_heat_conduction_rhs));
63  LibmeshPetscCall(MatDestroy(&_hc_sweep_enthalpy_mat));
64  LibmeshPetscCall(VecDestroy(&_hc_sweep_enthalpy_rhs));
65  PetscFunctionReturn(LIBMESH_PETSC_SUCCESS);
66 }
67 
68 void
70 {
72 
73  if (_deformation)
74  {
75  // update surface area, wetted perimeter based on: Dpin, displacement
76  Real standard_area, wire_area, additional_area, wetted_perimeter, displaced_area;
77  auto flat_to_flat = _tri_sch_mesh.getFlatToFlat();
78  auto n_rings = _tri_sch_mesh.getNumOfRings();
80  auto pin_diameter = _subchannel_mesh.getPinDiameter();
81  auto wire_diameter = _tri_sch_mesh.getWireDiameter();
82  auto wire_lead_length = _tri_sch_mesh.getWireLeadLength();
83  auto gap = _tri_sch_mesh.getDuctToPinGap();
84  auto z_blockage = _subchannel_mesh.getZBlockage();
85  auto index_blockage = _subchannel_mesh.getIndexBlockage();
86  auto reduction_blockage = _subchannel_mesh.getReductionBlockage();
87  auto theta =
88  std::acos(wire_lead_length /
89  std::sqrt(Utility::pow<2>(wire_lead_length) +
90  Utility::pow<2>(libMesh::pi * (pin_diameter + wire_diameter))));
91  for (unsigned int iz = 0; iz < _n_cells + 1; iz++)
92  {
93  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
94  {
95  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
96  auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
97  auto Z = _z_grid[iz];
98  Real rod_area = 0.0;
99  Real rod_perimeter = 0.0;
100  for (auto i_pin : _subchannel_mesh.getChannelPins(i_ch))
101  {
102  auto * pin_node = _subchannel_mesh.getPinNode(i_pin, iz);
103  if (subch_type == EChannelType::CENTER || subch_type == EChannelType::CORNER)
104  {
105  rod_area +=
106  (1.0 / 6.0) * 0.25 * M_PI * (*_Dpin_soln)(pin_node) * (*_Dpin_soln)(pin_node);
107  rod_perimeter += (1.0 / 6.0) * M_PI * (*_Dpin_soln)(pin_node);
108  }
109  else
110  {
111  rod_area +=
112  (1.0 / 4.0) * 0.25 * M_PI * (*_Dpin_soln)(pin_node) * (*_Dpin_soln)(pin_node);
113  rod_perimeter += (1.0 / 4.0) * M_PI * (*_Dpin_soln)(pin_node);
114  }
115  }
116 
117  if (subch_type == EChannelType::CENTER)
118  {
119  standard_area = Utility::pow<2>(pitch) * std::sqrt(3.0) / 4.0;
120  additional_area = 0.0;
121  displaced_area = 0.0;
122  wire_area = libMesh::pi * Utility::pow<2>(wire_diameter) / 8.0 / std::cos(theta);
123  wetted_perimeter = rod_perimeter + 0.5 * libMesh::pi * wire_diameter / std::cos(theta);
124  }
125  else if (subch_type == EChannelType::EDGE)
126  {
127  standard_area = pitch * (pin_diameter / 2.0 + gap);
128  additional_area = 0.0;
129  displaced_area = (*_displacement_soln)(node)*pitch;
130  wire_area = libMesh::pi * Utility::pow<2>(wire_diameter) / 8.0 / std::cos(theta);
131  wetted_perimeter =
132  rod_perimeter + 0.5 * libMesh::pi * wire_diameter / std::cos(theta) + pitch;
133  }
134  else
135  {
136  standard_area = 1.0 / std::sqrt(3.0) * Utility::pow<2>(pin_diameter / 2.0 + gap);
137  additional_area = 0.0;
138  displaced_area = 1.0 / std::sqrt(3.0) *
139  (pin_diameter + 2.0 * gap + (*_displacement_soln)(node)) *
140  (*_displacement_soln)(node);
141  wire_area = libMesh::pi / 24.0 * Utility::pow<2>(wire_diameter) / std::cos(theta);
142  wetted_perimeter =
143  rod_perimeter + libMesh::pi * wire_diameter / std::cos(theta) / 6.0 +
144  2.0 / std::sqrt(3.0) * (pin_diameter / 2.0 + gap + (*_displacement_soln)(node));
145  }
146 
147  // Calculate subchannel area
148  auto subchannel_area =
149  standard_area + additional_area + displaced_area - rod_area - wire_area;
150 
151  // Correct subchannel area and wetted perimeter in case of overlapping pins
152  auto overlapping_pin_area = 0.0;
153  auto overlapping_wetted_perimeter = 0.0;
154  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
155  {
156  auto gap_pins = _subchannel_mesh.getGapPins(i_gap);
157  auto pin_1 = gap_pins.first;
158  auto pin_2 = gap_pins.second;
159  auto * pin_node_1 = _subchannel_mesh.getPinNode(pin_1, iz);
160  auto * pin_node_2 = _subchannel_mesh.getPinNode(pin_2, iz);
161  auto Diameter1 = (*_Dpin_soln)(pin_node_1);
162  auto Radius1 = Diameter1 / 2.0;
163  auto Diameter2 = (*_Dpin_soln)(pin_node_2);
164  auto Radius2 = Diameter2 / 2.0;
166 
167  if (pitch < (Radius1 + Radius2)) // overlapping pins
168  {
169  mooseWarning(" The gap of index : '", i_gap, " at axial cell ", iz, " ' is blocked.");
170  auto cos1 =
171  (pitch * pitch + Radius1 * Radius1 - Radius2 * Radius2) / (2 * pitch * Radius1);
172  auto cos2 =
173  (pitch * pitch + Radius2 * Radius2 - Radius1 * Radius1) / (2 * pitch * Radius2);
174  auto angle1 = 2.0 * acos(cos1);
175  auto angle2 = 2.0 * acos(cos2);
176  // half of the intersecting arc-length
177  overlapping_wetted_perimeter += 0.5 * angle1 * Radius1 + 0.5 * angle2 * Radius2;
178  // Half of the overlapping area
179  overlapping_pin_area +=
180  0.5 * Radius1 * Radius1 * acos(cos1) + 0.5 * Radius2 * Radius2 * acos(cos2) -
181  0.25 * sqrt((-pitch + Radius1 + Radius2) * (pitch + Radius1 - Radius2) *
182  (pitch - Radius1 + Radius2) * (pitch + Radius1 + Radius2));
183  }
184  }
185  subchannel_area += overlapping_pin_area; // correct surface area
186  wetted_perimeter += -overlapping_wetted_perimeter; // correct wetted perimeter
187 
188  // Apply area reduction on subchannels affected by blockage
189  auto index = 0;
190  for (const auto & i_blockage : index_blockage)
191  {
192  if (i_ch == i_blockage && (Z >= z_blockage.front() && Z <= z_blockage.back()))
193  {
194  subchannel_area *= reduction_blockage[index];
195  }
196  index++;
197  }
198  _S_flow_soln->set(node, subchannel_area);
199  _w_perim_soln->set(node, wetted_perimeter);
200  }
201  }
202  // update map of gap between pins (gij) based on: Dpin, displacement
203  for (unsigned int iz = 0; iz < _n_cells + 1; iz++)
204  {
205  for (unsigned int i_gap = 0; i_gap < _n_gaps; i_gap++)
206  {
207  auto gap_pins = _subchannel_mesh.getGapPins(i_gap);
208  auto pin_1 = gap_pins.first;
209  auto pin_2 = gap_pins.second;
210  auto * pin_node_1 = _subchannel_mesh.getPinNode(pin_1, iz);
211  auto * pin_node_2 = _subchannel_mesh.getPinNode(pin_2, iz);
212 
213  if (pin_1 == pin_2) // Corner or edge gap
214  {
215  auto displacement = 0.0;
216  auto counter = 0.0;
217  for (auto i_ch : _subchannel_mesh.getPinChannels(pin_1))
218  {
219  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
220  auto * node = _subchannel_mesh.getChannelNode(i_ch, iz);
221  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
222  {
223  displacement += (*_displacement_soln)(node);
224  counter += 1.0;
225  }
226  }
227  displacement = displacement / counter;
229  i_gap,
230  0.5 * (flat_to_flat - (n_rings - 1) * pitch * std::sqrt(3.0) -
231  (*_Dpin_soln)(pin_node_1)) +
232  displacement);
233  }
234  else // center gap
235  {
237  iz, i_gap, pitch - (*_Dpin_soln)(pin_node_1) / 2.0 - (*_Dpin_soln)(pin_node_2) / 2.0);
238  }
239  // if pins come in contact, the gap is zero
240  if (_tri_sch_mesh.getGapWidth(iz, i_gap) <= 0.0)
241  _tri_sch_mesh.setGapWidth(iz, i_gap, 0.0);
242  }
243  }
244  }
245 
246  for (unsigned int iz = 1; iz < _n_cells + 1; iz++)
247  {
248  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
249  {
250  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
251  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
252  _mdot_soln->set(node_out, (*_mdot_soln)(node_in));
253  }
254  }
255 
256  // We must do a global assembly to make sure data is parallel consistent before we do things
257  // like compute L2 norms
258  _aux->solution().close();
259 }
260 
261 Real
262 TriSubChannel1PhaseProblem::computeAddedHeatPin(unsigned int i_ch, unsigned int iz) const
263 {
264  if (!_pin_mesh_exist)
265  return 0.0;
266 
267  // Compute axial location of nodes.
268  auto z2 = _z_grid[iz];
269  auto z1 = _z_grid[iz - 1];
270  auto heated_length = _subchannel_mesh.getHeatedLength();
271  auto unheated_length_entry = _subchannel_mesh.getHeatedLengthEntry();
272  if (MooseUtils::absoluteFuzzyGreaterThan(z2, unheated_length_entry) &&
273  MooseUtils::absoluteFuzzyLessThan(z1, unheated_length_entry + heated_length))
274  {
275  // Compute the height of this element.
276  auto dz = z2 - z1;
277  Real factor;
278  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
279  switch (subch_type)
280  {
282  factor = 1.0 / 6.0;
283  break;
284  case EChannelType::EDGE:
285  factor = 1.0 / 4.0;
286  break;
288  factor = 1.0 / 6.0;
289  break;
290  default:
291  return 0.0; // handle invalid subch_type if needed
292  }
293  double heat_rate_in = 0.0;
294  double heat_rate_out = 0.0;
295  for (auto i_pin : _subchannel_mesh.getChannelPins(i_ch))
296  {
297  auto * node_in = _subchannel_mesh.getPinNode(i_pin, iz - 1);
298  auto * node_out = _subchannel_mesh.getPinNode(i_pin, iz);
299  heat_rate_out += factor * (*_q_prime_soln)(node_out);
300  heat_rate_in += factor * (*_q_prime_soln)(node_in);
301  }
302  return (heat_rate_in + heat_rate_out) * dz / 2.0;
303  }
304  else
305  return 0.0;
306 }
307 
308 Real
310 {
311  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
312  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
313  {
314  auto width = _subchannel_mesh.getPitch();
315  if (subch_type == EChannelType::CORNER)
316  width = 2.0 / std::sqrt(3.0) *
318  return width;
319  }
320  else
321  mooseError("Channel is not a perimetric subchannel ");
322 }
323 
324 void
326 {
327  unsigned int last_node = (iblock + 1) * _block_size;
328  unsigned int first_node = iblock * _block_size + 1;
329  const Real & wire_lead_length = _tri_sch_mesh.getWireLeadLength();
330  const Real & wire_diameter = _tri_sch_mesh.getWireDiameter();
331  const Real & pitch = _subchannel_mesh.getPitch();
332  const Real & pin_diameter = _subchannel_mesh.getPinDiameter();
333 
334  if (iblock == 0)
335  {
336  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
337  {
338  auto * node = _subchannel_mesh.getChannelNode(i_ch, 0);
339  auto h_out = _fp->h_from_p_T((*_P_soln)(node) + _P_out, (*_T_soln)(node));
340  if (h_out < 0)
341  {
342  mooseError(
343  name(), " : Calculation of negative Enthalpy h_out = : ", h_out, " Axial Level= : ", 0);
344  }
345  _h_soln->set(node, h_out);
346  }
347  }
348 
349  if (!_implicit_bool)
350  {
351  for (unsigned int iz = first_node; iz < last_node + 1; iz++)
352  {
353  auto z_grid = _subchannel_mesh.getZGrid();
354  auto dz = z_grid[iz] - z_grid[iz - 1];
355  // Calculation of average mass flux of all periphery subchannels
356  Real edge_flux_ave = 0.0;
357  Real mdot_sum = 0.0;
358  Real si_sum = 0.0;
359  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
360  {
361  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
362  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
363  {
364  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
365  auto Si = (*_S_flow_soln)(node_in);
366  auto mdot_in = (*_mdot_soln)(node_in);
367  mdot_sum = mdot_sum + mdot_in;
368  si_sum = si_sum + Si;
369  }
370  }
371  edge_flux_ave = mdot_sum / si_sum;
372 
373  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
374  {
375  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
376  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
377  auto mdot_in = (*_mdot_soln)(node_in);
378  auto h_in = (*_h_soln)(node_in); // J/kg
379  auto volume = dz * (*_S_flow_soln)(node_in);
380  auto mdot_out = (*_mdot_soln)(node_out);
381  auto h_out = 0.0;
382  Real sumWijh = 0.0;
383  Real sumWijPrimeDhij = 0.0;
384  Real sweep_enthalpy = 0.0;
385  Real e_cond = 0.0;
386 
387  // Calculate added enthalpy from heatflux (Pin, Duct)
388  Real added_enthalpy = computeAddedHeatPin(i_ch, iz);
389  added_enthalpy += computeAddedHeatDuct(i_ch, iz);
390 
391  // Calculate net sum of enthalpy into/out-of channel i from channels j around i
392  // (Turbulent diffusion, Diversion Crossflow, Sweep flow Enthalpy, Radial heat conduction)
393  unsigned int counter = 0;
394  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
395  {
396  auto chans = _subchannel_mesh.getGapChannels(i_gap);
397  auto gap = _subchannel_mesh.getGapWidth(iz, i_gap);
398  auto Sij = dz * gap;
399  unsigned int ii_ch = chans.first; // the first subchannel next to gap i_gap
400  unsigned int jj_ch = chans.second; // the second subchannel next to gap i_gap
401  auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
402  auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
403  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
404  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
405  // Define donor enthalpy
406  auto h_star = 0.0;
407  if (_Wij(i_gap, iz) > 0.0)
408  h_star = (*_h_soln)(node_in_i);
409  else if (_Wij(i_gap, iz) < 0.0)
410  h_star = (*_h_soln)(node_in_j);
411  // Diversion crossflow
412  // take care of the sign by applying the map, use donor cell
413  sumWijh += _subchannel_mesh.getCrossflowSign(i_ch, counter) * _Wij(i_gap, iz) * h_star;
414  counter++;
415  // SWEEP FLOW is calculated if i_gap is located in the periphery
416  // and we have a wire-wrap (if i_gap is in the periphery then i_ch is in the periphery)
417  // There are two gaps per periphery subchannel that this is true.
418  if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
419  (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE) &&
420  (wire_lead_length != 0) && (wire_diameter != 0))
421  {
422  // donor subchannel and node of sweep flow. The donor subchannel is the subchannel next
423  // to i_ch that sweep flow, flows from and into i_ch
424  auto sweep_donor = _tri_sch_mesh.getSweepFlowChans(i_ch).first;
425  auto * node_sweep_donor = _subchannel_mesh.getChannelNode(sweep_donor, iz - 1);
426  // if one of the neighbor subchannels of the periphery gap is the donor subchannel
427  //(the other would be the i_ch) sweep enthalpy flows into i_ch
428  if ((ii_ch == sweep_donor) || (jj_ch == sweep_donor))
429  {
430  sweep_enthalpy += computeSweepFlowMixingParameter(i_gap, iz) * edge_flux_ave * Sij *
431  (*_h_soln)(node_sweep_donor);
432  }
433  // else sweep enthalpy flows out of i_ch
434  else
435  {
436  sweep_enthalpy -= computeSweepFlowMixingParameter(i_gap, iz) * edge_flux_ave * Sij *
437  (*_h_soln)(node_in);
438  }
439  }
440  // Inner gap
441  // Turbulent Diffusion
442  else
443  {
444  sumWijPrimeDhij +=
445  _WijPrime(i_gap, iz) * (2 * h_in - (*_h_soln)(node_in_j) - (*_h_soln)(node_in_i));
446  }
447 
448  // compute the radial heat conduction through the gaps
449  Real dist_ij = pitch;
450 
451  if (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::EDGE)
452  {
453  dist_ij = pitch;
454  }
455  else if ((subch_type_i == EChannelType::CORNER && subch_type_j == EChannelType::EDGE) ||
456  (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CORNER))
457  {
458  dist_ij = pitch;
459  }
460  else
461  {
462  dist_ij = pitch / std::sqrt(3);
463  }
464 
465  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
466  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
467  auto shape_factor =
468  0.66 * (pitch / pin_diameter) *
469  std::pow((_subchannel_mesh.getGapWidth(iz, i_gap) / pin_diameter), -0.3);
470  if (ii_ch == i_ch)
471  {
472  e_cond += 0.5 * (thcon_i + thcon_j) * Sij * shape_factor *
473  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
474  }
475  else
476  {
477  e_cond += -0.5 * (thcon_i + thcon_j) * Sij * shape_factor *
478  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
479  }
480  }
481 
482  // compute the axial heat conduction between current and lower axial node
483  auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz);
484  auto * node_in_j = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
485  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
486  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
487  auto Si = (*_S_flow_soln)(node_in_i);
488  auto dist_ij = z_grid[iz] - z_grid[iz - 1];
489 
490  e_cond += 0.5 * (thcon_i + thcon_j) * Si * ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) /
491  dist_ij;
492 
493  unsigned int nz = _subchannel_mesh.getNumOfAxialCells();
494  // compute the axial heat conduction between current and upper axial node
495  if (iz < nz)
496  {
497  auto * node_in_i = _subchannel_mesh.getChannelNode(i_ch, iz);
498  auto * node_in_j = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
499  auto thcon_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
500  auto thcon_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
501  auto Si = (*_S_flow_soln)(node_in_i);
502  auto dist_ij = z_grid[iz + 1] - z_grid[iz];
503  e_cond += 0.5 * (thcon_i + thcon_j) * Si *
504  ((*_T_soln)(node_in_j) - (*_T_soln)(node_in_i)) / dist_ij;
505  }
506 
507  // end of radial heat conduction calc.
508  h_out =
509  (mdot_in * h_in - sumWijh - sumWijPrimeDhij + added_enthalpy + e_cond + sweep_enthalpy +
510  _TR * _rho_soln->old(node_out) * _h_soln->old(node_out) * volume / _dt) /
511  (mdot_out + _TR * (*_rho_soln)(node_out)*volume / _dt);
512  if (h_out < 0)
513  {
514  mooseError(name(),
515  " : Calculation of negative Enthalpy h_out = : ",
516  h_out,
517  " Axial Level= : ",
518  iz);
519  }
520  _h_soln->set(node_out, h_out); // J/kg
521  }
522  }
523  }
524  else
525  {
526  LibmeshPetscCall(MatZeroEntries(_hc_time_derivative_mat));
527  LibmeshPetscCall(MatZeroEntries(_hc_advective_derivative_mat));
528  LibmeshPetscCall(MatZeroEntries(_hc_cross_derivative_mat));
529  LibmeshPetscCall(MatZeroEntries(_hc_axial_heat_conduction_mat));
530  LibmeshPetscCall(MatZeroEntries(_hc_radial_heat_conduction_mat));
531  LibmeshPetscCall(MatZeroEntries(_hc_sweep_enthalpy_mat));
532 
533  LibmeshPetscCall(VecZeroEntries(_hc_time_derivative_rhs));
534  LibmeshPetscCall(VecZeroEntries(_hc_advective_derivative_rhs));
535  LibmeshPetscCall(VecZeroEntries(_hc_cross_derivative_rhs));
536  LibmeshPetscCall(VecZeroEntries(_hc_added_heat_rhs));
537  LibmeshPetscCall(VecZeroEntries(_hc_axial_heat_conduction_rhs));
538  LibmeshPetscCall(VecZeroEntries(_hc_radial_heat_conduction_rhs));
539  LibmeshPetscCall(VecZeroEntries(_hc_sweep_enthalpy_rhs));
540 
541  LibmeshPetscCall(MatZeroEntries(_hc_sys_h_mat));
542  LibmeshPetscCall(VecZeroEntries(_hc_sys_h_rhs));
543 
544  for (unsigned int iz = first_node; iz < last_node + 1; iz++)
545  {
546  auto dz = _z_grid[iz] - _z_grid[iz - 1];
548  auto pin_diameter = _subchannel_mesh.getPinDiameter();
549  auto iz_ind = iz - first_node;
550 
551  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
552  {
553  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
554  auto * node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
555  auto S_in = (*_S_flow_soln)(node_in);
556  auto S_out = (*_S_flow_soln)(node_out);
557  auto S_interp = computeInterpolatedValue(S_out, S_in, 0.5);
558  auto volume = dz * S_interp;
559 
560  PetscScalar Pe = 0.5;
561  if (_interpolation_scheme == 3)
562  {
563  // Compute the Peclet number
564  auto w_perim_in = (*_w_perim_soln)(node_in);
565  auto w_perim_out = (*_w_perim_soln)(node_out);
566  auto w_perim_interp = this->computeInterpolatedValue(w_perim_out, w_perim_in, 0.5);
567  auto K_in = _fp->k_from_p_T((*_P_soln)(node_in) + _P_out, (*_T_soln)(node_in));
568  auto K_out = _fp->k_from_p_T((*_P_soln)(node_out) + _P_out, (*_T_soln)(node_out));
569  auto K = this->computeInterpolatedValue(K_out, K_in, 0.5);
570  auto cp_in = _fp->cp_from_p_T((*_P_soln)(node_in) + _P_out, (*_T_soln)(node_in));
571  auto cp_out = _fp->cp_from_p_T((*_P_soln)(node_out) + _P_out, (*_T_soln)(node_out));
572  auto cp = this->computeInterpolatedValue(cp_out, cp_in, 0.5);
573  auto mdot_loc =
574  this->computeInterpolatedValue((*_mdot_soln)(node_out), (*_mdot_soln)(node_in), 0.5);
575  // hydraulic diameter in the i direction
576  auto Dh_i = 4.0 * S_interp / w_perim_interp;
577  Pe = mdot_loc * Dh_i * cp / (K * S_interp) * (mdot_loc / std::abs(mdot_loc));
578  }
580 
581  // Time derivative term
582  if (iz == first_node)
583  {
584  PetscScalar value_vec_tt =
585  -1.0 * _TR * alpha * (*_rho_soln)(node_in) * (*_h_soln)(node_in)*volume / _dt;
586  PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
587  LibmeshPetscCall(
588  VecSetValues(_hc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
589  }
590  else
591  {
592  PetscInt row_tt = i_ch + _n_channels * iz_ind;
593  PetscInt col_tt = i_ch + _n_channels * (iz_ind - 1);
594  PetscScalar value_tt = _TR * alpha * (*_rho_soln)(node_in)*volume / _dt;
595  LibmeshPetscCall(MatSetValues(
596  _hc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
597  }
598 
599  // Adding diagonal elements
600  PetscInt row_tt = i_ch + _n_channels * iz_ind;
601  PetscInt col_tt = i_ch + _n_channels * iz_ind;
602  PetscScalar value_tt = _TR * (1.0 - alpha) * (*_rho_soln)(node_out)*volume / _dt;
603  LibmeshPetscCall(MatSetValues(
604  _hc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
605 
606  // Adding RHS elements
607  PetscScalar rho_old_interp =
608  computeInterpolatedValue(_rho_soln->old(node_out), _rho_soln->old(node_in), Pe);
609  PetscScalar h_old_interp =
610  computeInterpolatedValue(_h_soln->old(node_out), _h_soln->old(node_in), Pe);
611  PetscScalar value_vec_tt = _TR * rho_old_interp * h_old_interp * volume / _dt;
612  PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
613  LibmeshPetscCall(
614  VecSetValues(_hc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
615 
616  // Advective derivative term
617  if (iz == first_node)
618  {
619  PetscInt row_at = i_ch + _n_channels * iz_ind;
620  PetscScalar value_at = alpha * (*_mdot_soln)(node_in) * (*_h_soln)(node_in);
621  LibmeshPetscCall(
622  VecSetValues(_hc_advective_derivative_rhs, 1, &row_at, &value_at, ADD_VALUES));
623 
624  value_at = alpha * (*_mdot_soln)(node_out) - (1 - alpha) * (*_mdot_soln)(node_in);
625  PetscInt col_at = i_ch + _n_channels * iz_ind;
626  LibmeshPetscCall(MatSetValues(
627  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
628 
629  value_at = (1 - alpha) * (*_mdot_soln)(node_out);
630  col_at = i_ch + _n_channels * (iz_ind + 1);
631  LibmeshPetscCall(MatSetValues(
632  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
633  }
634  else if (iz == last_node)
635  {
636  PetscInt row_at = i_ch + _n_channels * iz_ind;
637  PetscScalar value_at = 1.0 * (*_mdot_soln)(node_out);
638  PetscInt col_at = i_ch + _n_channels * iz_ind;
639  LibmeshPetscCall(MatSetValues(
640  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
641 
642  value_at = -1.0 * (*_mdot_soln)(node_in);
643  col_at = i_ch + _n_channels * (iz_ind - 1);
644  LibmeshPetscCall(MatSetValues(
645  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
646  }
647  else
648  {
649  PetscInt row_at = i_ch + _n_channels * iz_ind;
650  PetscInt col_at;
651 
652  PetscScalar value_at = -alpha * (*_mdot_soln)(node_in);
653  col_at = i_ch + _n_channels * (iz_ind - 1);
654  LibmeshPetscCall(MatSetValues(
655  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
656 
657  value_at = alpha * (*_mdot_soln)(node_out) - (1 - alpha) * (*_mdot_soln)(node_in);
658  col_at = i_ch + _n_channels * iz_ind;
659  LibmeshPetscCall(MatSetValues(
660  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
661 
662  value_at = (1 - alpha) * (*_mdot_soln)(node_out);
663  col_at = i_ch + _n_channels * (iz_ind + 1);
664  LibmeshPetscCall(MatSetValues(
665  _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
666  }
667 
668  // Axial heat conduction
669  auto * node_center = _subchannel_mesh.getChannelNode(i_ch, iz);
670  auto K_center = _fp->k_from_p_T((*_P_soln)(node_center) + _P_out, (*_T_soln)(node_center));
671  auto cp_center =
672  _fp->cp_from_p_T((*_P_soln)(node_center) + _P_out, (*_T_soln)(node_center));
673  auto diff_center = K_center / (cp_center + 1e-15);
674 
675  if (iz == first_node)
676  {
677  auto * node_top = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
678  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
679  auto K_bottom =
680  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
681  auto K_top = _fp->k_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
682  auto cp_bottom =
683  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
684  auto cp_top = _fp->cp_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
685  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
686  auto diff_top = K_top / (cp_top + 1e-15);
687 
688  auto dz_up = _z_grid[iz + 1] - _z_grid[iz];
689  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
690  auto S_up =
691  computeInterpolatedValue((*_S_flow_soln)(node_top), (*_S_flow_soln)(node_center));
692  auto S_down =
693  computeInterpolatedValue((*_S_flow_soln)(node_center), (*_S_flow_soln)(node_bottom));
694  auto diff_up = computeInterpolatedValue(diff_top, diff_center);
695  auto diff_down = computeInterpolatedValue(diff_center, diff_bottom);
696 
697  // Diagonal value
698  PetscInt row_at = i_ch + _n_channels * iz_ind;
699  PetscInt col_at = i_ch + _n_channels * iz_ind;
700  PetscScalar value_at = diff_up * S_up / dz_up + diff_down * S_down / dz_down;
701  LibmeshPetscCall(MatSetValues(
702  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
703 
704  // Bottom value
705  value_at = 1.0 * diff_down * S_down / dz_down * (*_h_soln)(node_bottom);
706  LibmeshPetscCall(
707  VecSetValues(_hc_axial_heat_conduction_rhs, 1, &row_at, &value_at, ADD_VALUES));
708 
709  // Top value
710  col_at = i_ch + _n_channels * (iz_ind + 1);
711  value_at = -diff_up * S_up / dz_up;
712  LibmeshPetscCall(MatSetValues(
713  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
714  }
715  else if (iz == last_node)
716  {
717  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
718  auto K_bottom =
719  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
720  auto cp_bottom =
721  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
722  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
723 
724  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
725  auto S_down = 0.5 * ((*_S_flow_soln)(node_center) + (*_S_flow_soln)(node_bottom));
726  auto diff_down = 0.5 * (diff_center + diff_bottom);
727 
728  // Diagonal value
729  PetscInt row_at = i_ch + _n_channels * iz_ind;
730  PetscInt col_at = i_ch + _n_channels * iz_ind;
731  PetscScalar value_at = diff_down * S_down / dz_down;
732  LibmeshPetscCall(MatSetValues(
733  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
734 
735  // Bottom value
736  col_at = i_ch + _n_channels * (iz_ind - 1);
737  value_at = -diff_down * S_down / dz_down;
738  LibmeshPetscCall(MatSetValues(
739  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
740 
741  // Outflow derivative
743  // value_at = -1.0 * (*_mdot_soln)(node_center) * (*_h_soln)(node_center);
744  // VecSetValues(_hc_axial_heat_conduction_rhs, 1, &row_at, &value_at, ADD_VALUES);
745  }
746  else
747  {
748  auto * node_top = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
749  auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
750  auto K_bottom =
751  _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
752  auto K_top = _fp->k_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
753  auto cp_bottom =
754  _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
755  auto cp_top = _fp->cp_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
756  auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
757  auto diff_top = K_top / (cp_top + 1e-15);
758 
759  auto dz_up = _z_grid[iz + 1] - _z_grid[iz];
760  auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
761  auto S_up =
762  computeInterpolatedValue((*_S_flow_soln)(node_top), (*_S_flow_soln)(node_center));
763  auto S_down =
764  computeInterpolatedValue((*_S_flow_soln)(node_center), (*_S_flow_soln)(node_bottom));
765  auto diff_up = computeInterpolatedValue(diff_top, diff_center);
766  auto diff_down = computeInterpolatedValue(diff_center, diff_bottom);
767 
768  // Diagonal value
769  PetscInt row_at = i_ch + _n_channels * iz_ind;
770  PetscInt col_at = i_ch + _n_channels * iz_ind;
771  PetscScalar value_at = diff_up * S_up / dz_up + diff_down * S_down / dz_down;
772  LibmeshPetscCall(MatSetValues(
773  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
774 
775  // Bottom value
776  col_at = i_ch + _n_channels * (iz_ind - 1);
777  value_at = -diff_down * S_down / dz_down;
778  LibmeshPetscCall(MatSetValues(
779  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
780 
781  // Top value
782  col_at = i_ch + _n_channels * (iz_ind + 1);
783  value_at = -diff_up * S_up / dz_up;
784  LibmeshPetscCall(MatSetValues(
785  _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
786  }
787 
788  // Radial Terms
789  unsigned int counter = 0;
790  unsigned int cross_index = iz;
791  // Real radial_heat_conduction(0.0);
792  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
793  {
794  auto chans = _subchannel_mesh.getGapChannels(i_gap);
795  unsigned int ii_ch = chans.first;
796  unsigned int jj_ch = chans.second;
797  auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
798  auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
799  PetscScalar h_star;
800  // figure out donor axial velocity
801  if (_Wij(i_gap, cross_index) > 0.0)
802  {
803  if (iz == first_node)
804  {
805  h_star = (*_h_soln)(node_in_i);
806  PetscScalar value_vec_ct = -1.0 * alpha *
807  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
808  _Wij(i_gap, cross_index) * h_star;
809  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
810  LibmeshPetscCall(VecSetValues(
811  _hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
812  }
813  else
814  {
815  PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
816  _Wij(i_gap, cross_index);
817  PetscInt row_ct = i_ch + _n_channels * iz_ind;
818  PetscInt col_ct = ii_ch + _n_channels * (iz_ind - 1);
819  LibmeshPetscCall(MatSetValues(
820  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
821  }
822  PetscScalar value_ct = (1.0 - alpha) *
823  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
824  _Wij(i_gap, cross_index);
825  PetscInt row_ct = i_ch + _n_channels * iz_ind;
826  PetscInt col_ct = ii_ch + _n_channels * iz_ind;
827  LibmeshPetscCall(MatSetValues(
828  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
829  }
830  else if (_Wij(i_gap, cross_index) < 0.0) // _Wij=0 operations not necessary
831  {
832  if (iz == first_node)
833  {
834  h_star = (*_h_soln)(node_in_j);
835  PetscScalar value_vec_ct = -1.0 * alpha *
836  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
837  _Wij(i_gap, cross_index) * h_star;
838  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
839  LibmeshPetscCall(VecSetValues(
840  _hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
841  }
842  else
843  {
844  PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
845  _Wij(i_gap, cross_index);
846  PetscInt row_ct = i_ch + _n_channels * iz_ind;
847  PetscInt col_ct = jj_ch + _n_channels * (iz_ind - 1);
848  LibmeshPetscCall(MatSetValues(
849  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
850  }
851  PetscScalar value_ct = (1.0 - alpha) *
852  _subchannel_mesh.getCrossflowSign(i_ch, counter) *
853  _Wij(i_gap, cross_index);
854  PetscInt row_ct = i_ch + _n_channels * iz_ind;
855  PetscInt col_ct = jj_ch + _n_channels * iz_ind;
856  LibmeshPetscCall(MatSetValues(
857  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
858  }
859 
860  // Turbulent cross flows
861  if (iz == first_node)
862  {
863  PetscScalar value_vec_ct =
864  -2.0 * alpha * (*_h_soln)(node_in)*_WijPrime(i_gap, cross_index);
865  value_vec_ct += alpha * (*_h_soln)(node_in_j)*_WijPrime(i_gap, cross_index);
866  value_vec_ct += alpha * (*_h_soln)(node_in_i)*_WijPrime(i_gap, cross_index);
867  PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
868  LibmeshPetscCall(
869  VecSetValues(_hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
870  }
871  else
872  {
873  PetscScalar value_center_ct = 2.0 * alpha * _WijPrime(i_gap, cross_index);
874  PetscInt row_ct = i_ch + _n_channels * iz_ind;
875  PetscInt col_ct = i_ch + _n_channels * (iz_ind - 1);
876  LibmeshPetscCall(MatSetValues(
877  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
878 
879  PetscScalar value_left_ct = -1.0 * alpha * _WijPrime(i_gap, cross_index);
880  row_ct = i_ch + _n_channels * iz_ind;
881  col_ct = jj_ch + _n_channels * (iz_ind - 1);
882  LibmeshPetscCall(MatSetValues(
883  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
884 
885  PetscScalar value_right_ct = -1.0 * alpha * _WijPrime(i_gap, cross_index);
886  row_ct = i_ch + _n_channels * iz_ind;
887  col_ct = ii_ch + _n_channels * (iz_ind - 1);
888  LibmeshPetscCall(MatSetValues(
889  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
890  }
891  PetscScalar value_center_ct = 2.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
892  PetscInt row_ct = i_ch + _n_channels * iz_ind;
893  PetscInt col_ct = i_ch + _n_channels * iz_ind;
894  LibmeshPetscCall(MatSetValues(
895  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
896 
897  PetscScalar value_left_ct = -1.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
898  row_ct = i_ch + _n_channels * iz_ind;
899  col_ct = jj_ch + _n_channels * iz_ind;
900  LibmeshPetscCall(MatSetValues(
901  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
902 
903  PetscScalar value_right_ct = -1.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
904  row_ct = i_ch + _n_channels * iz_ind;
905  col_ct = ii_ch + _n_channels * iz_ind;
906  LibmeshPetscCall(MatSetValues(
907  _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
908 
909  // Radial heat conduction
910  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
911  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
912  Real dist_ij = pitch;
913 
914  if (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::EDGE)
915  {
916  dist_ij = pitch;
917  }
918  else if ((subch_type_i == EChannelType::CORNER && subch_type_j == EChannelType::EDGE) ||
919  (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CORNER))
920  {
921  dist_ij = pitch;
922  }
923  else
924  {
925  dist_ij = pitch / std::sqrt(3);
926  }
927 
928  auto Sij = dz * _subchannel_mesh.getGapWidth(iz, i_gap);
929  auto K_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
930  auto K_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
931  auto cp_i = _fp->cp_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
932  auto cp_j = _fp->cp_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
933  auto A_i = K_i / cp_i;
934  auto A_j = K_j / cp_j;
935  auto harm_A = 2.0 * A_i * A_j / (A_i + A_j);
936  auto shape_factor =
937  0.66 * (pitch / pin_diameter) *
938  std::pow((_subchannel_mesh.getGapWidth(iz, i_gap) / pin_diameter), -0.3);
939  // auto base_value = 0.5 * (A_i + A_j) * Sij * shape_factor / dist_ij;
940  auto base_value = harm_A * shape_factor * Sij / dist_ij;
941  auto neg_base_value = -1.0 * base_value;
942 
943  row_ct = ii_ch + _n_channels * iz_ind;
944  col_ct = ii_ch + _n_channels * iz_ind;
945  LibmeshPetscCall(MatSetValues(
946  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &base_value, ADD_VALUES));
947 
948  row_ct = jj_ch + _n_channels * iz_ind;
949  col_ct = jj_ch + _n_channels * iz_ind;
950  LibmeshPetscCall(MatSetValues(
951  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &base_value, ADD_VALUES));
952 
953  row_ct = ii_ch + _n_channels * iz_ind;
954  col_ct = jj_ch + _n_channels * iz_ind;
955  LibmeshPetscCall(MatSetValues(
956  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &neg_base_value, ADD_VALUES));
957 
958  row_ct = jj_ch + _n_channels * iz_ind;
959  col_ct = ii_ch + _n_channels * iz_ind;
960  LibmeshPetscCall(MatSetValues(
961  _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &neg_base_value, ADD_VALUES));
962  counter++;
963  }
964 
965  // Compute the sweep flow enthalpy change
966  // Calculation of average mass flux of all periphery subchannels
967  Real edge_flux_ave = 0.0;
968  Real mdot_sum = 0.0;
969  Real si_sum = 0.0;
970  for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
971  {
972  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
973  if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
974  {
975  auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
976  auto Si = (*_S_flow_soln)(node_in);
977  auto mdot_in = (*_mdot_soln)(node_in);
978  mdot_sum = mdot_sum + mdot_in;
979  si_sum = si_sum + Si;
980  }
981  }
982  edge_flux_ave = mdot_sum / si_sum;
983  auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
984  PetscScalar sweep_enthalpy = 0.0;
985  if ((subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER) &&
986  (wire_diameter != 0.0) && (wire_lead_length != 0.0))
987  {
988  auto beta_in = std::numeric_limits<double>::quiet_NaN();
989  auto beta_out = std::numeric_limits<double>::quiet_NaN();
990  // donor sweep channel for i_ch
991  auto sweep_donor = _tri_sch_mesh.getSweepFlowChans(i_ch).first;
992  auto * node_sweep_donor = _subchannel_mesh.getChannelNode(sweep_donor, iz - 1);
993  // Calculation of turbulent mixing parameter
994  for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
995  {
996  auto chans = _subchannel_mesh.getGapChannels(i_gap);
997  unsigned int ii_ch = chans.first;
998  unsigned int jj_ch = chans.second;
999  auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
1000  auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
1001  if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
1002  (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE))
1003  {
1004  if ((ii_ch == sweep_donor) || (jj_ch == sweep_donor))
1005  {
1006  beta_in = computeSweepFlowMixingParameter(i_gap, iz);
1007  }
1008  else
1009  {
1010  beta_out = computeSweepFlowMixingParameter(i_gap, iz);
1011  }
1012  }
1013  }
1014  // Abort execution if required values are unset
1015  mooseAssert(!std::isnan(beta_in),
1016  "beta_in was not set. Check gap logic for i_ch = " + std::to_string(i_ch) +
1017  ", iz = " + std::to_string(iz));
1018  mooseAssert(!std::isnan(beta_out),
1019  "beta_out was not set. Check gap logic for i_ch = " + std::to_string(i_ch) +
1020  ", iz = " + std::to_string(iz));
1021 
1022  auto gap = _tri_sch_mesh.getDuctToPinGap();
1023  auto Sij = dz * gap;
1024  auto wsweep_in = edge_flux_ave * beta_in * Sij;
1025  auto wsweep_out = edge_flux_ave * beta_out * Sij;
1026  auto sweep_hin = (*_h_soln)(node_sweep_donor);
1027  auto sweep_hout = (*_h_soln)(node_in);
1028  sweep_enthalpy = (wsweep_in * sweep_hin - wsweep_out * sweep_hout);
1029 
1030  if (iz == first_node)
1031  {
1032  PetscInt row_sh = i_ch + _n_channels * iz_ind;
1033  PetscScalar value_hs = -sweep_enthalpy;
1034  LibmeshPetscCall(
1035  VecSetValues(_hc_sweep_enthalpy_rhs, 1, &row_sh, &value_hs, ADD_VALUES));
1036  }
1037  else
1038  {
1039  // coefficient of sweep_hin
1040  PetscInt row_sh = i_ch + _n_channels * (iz_ind - 1);
1041  PetscInt col_sh = i_ch + _n_channels * (iz_ind - 1);
1042  LibmeshPetscCall(MatSetValues(
1043  _hc_sweep_enthalpy_mat, 1, &row_sh, 1, &col_sh, &wsweep_out, ADD_VALUES));
1044  PetscInt col_sh_l = sweep_donor + _n_channels * (iz_ind - 1);
1045  PetscScalar neg_sweep_in = -1.0 * wsweep_in;
1046  // coefficient of sweep_hout
1047  LibmeshPetscCall(MatSetValues(
1048  _hc_sweep_enthalpy_mat, 1, &row_sh, 1, &col_sh_l, &(neg_sweep_in), ADD_VALUES));
1049  }
1050  }
1051 
1052  // Add heat enthalpy from pin and/or duct
1053  PetscScalar added_enthalpy = computeAddedHeatPin(i_ch, iz);
1054  added_enthalpy += computeAddedHeatDuct(i_ch, iz);
1055  PetscInt row_vec_ht = i_ch + _n_channels * iz_ind;
1056  LibmeshPetscCall(
1057  VecSetValues(_hc_added_heat_rhs, 1, &row_vec_ht, &added_enthalpy, ADD_VALUES));
1058  }
1059  }
1060  // Assembling system
1061  LibmeshPetscCall(MatAssemblyBegin(_hc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1062  LibmeshPetscCall(MatAssemblyEnd(_hc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1063  LibmeshPetscCall(MatAssemblyBegin(_hc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1064  LibmeshPetscCall(MatAssemblyEnd(_hc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1065  LibmeshPetscCall(MatAssemblyBegin(_hc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1066  LibmeshPetscCall(MatAssemblyEnd(_hc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1067  LibmeshPetscCall(MatAssemblyBegin(_hc_axial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1068  LibmeshPetscCall(MatAssemblyEnd(_hc_axial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1069  LibmeshPetscCall(MatAssemblyBegin(_hc_radial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1070  LibmeshPetscCall(MatAssemblyEnd(_hc_radial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1071  LibmeshPetscCall(MatAssemblyBegin(_hc_sweep_enthalpy_mat, MAT_FINAL_ASSEMBLY));
1072  LibmeshPetscCall(MatAssemblyEnd(_hc_sweep_enthalpy_mat, MAT_FINAL_ASSEMBLY));
1073  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1074  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1075  // Add all matrices together
1076  LibmeshPetscCall(
1077  MatAXPY(_hc_sys_h_mat, 1.0, _hc_time_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1078  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1079  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1080  LibmeshPetscCall(
1081  MatAXPY(_hc_sys_h_mat, 1.0, _hc_advective_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1082  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1083  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1084  LibmeshPetscCall(
1085  MatAXPY(_hc_sys_h_mat, 1.0, _hc_cross_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1086  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1087  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1088  LibmeshPetscCall(
1089  MatAXPY(_hc_sys_h_mat, 1.0, _hc_axial_heat_conduction_mat, DIFFERENT_NONZERO_PATTERN));
1090  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1091  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1092  LibmeshPetscCall(
1093  MatAXPY(_hc_sys_h_mat, 1.0, _hc_radial_heat_conduction_mat, DIFFERENT_NONZERO_PATTERN));
1094  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1095  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1096  LibmeshPetscCall(
1097  MatAXPY(_hc_sys_h_mat, 1.0, _hc_sweep_enthalpy_mat, DIFFERENT_NONZERO_PATTERN));
1098  LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1099  LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1100  if (_verbose_subchannel)
1101  _console << "Block: " << iblock << " - Enthalpy conservation matrix assembled" << std::endl;
1102  // RHS
1103  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_time_derivative_rhs));
1104  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_advective_derivative_rhs));
1105  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_cross_derivative_rhs));
1106  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_added_heat_rhs));
1107  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_axial_heat_conduction_rhs));
1108  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_radial_heat_conduction_rhs));
1109  LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_sweep_enthalpy_rhs));
1110 
1111  // Use system to solve for and populate enthalpy
1112  LibmeshPetscCall(this->solveAndPopulateEnthalpy(
1113  _hc_sys_h_mat, _hc_sys_h_rhs, first_node, last_node, "h_sys_"));
1114  }
1115 }
PetscErrorCode solveAndPopulateEnthalpy(Mat A, Vec rhs, unsigned int first_node, unsigned int last_node, const char *ksp_prefix)
Solve a linear system (A * x = rhs) with a simple PCJACOBI KSP and populate the enthalpy solution int...
const bool _pin_mesh_exist
Flag that informs if there is a pin mesh or not.
const Real & getWireDiameter() const
Return wire diameter.
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 undeformed Pin diameter.
std::unique_ptr< SolutionHandle > _T_soln
std::unique_ptr< SolutionHandle > _h_soln
virtual const std::vector< unsigned int > & getPinChannels(unsigned int i_pin) const =0
Return a vector of channel indices for a given Pin index.
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...
void setGapWidth(unsigned int axial_index, unsigned int gap_index, Real gap_width)
Set the gap width for a given axial cell and gap index.
const PostprocessorValue & _P_out
Outlet pressure postprocessor value.
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:174
virtual const Real & getPitch() const
Return the undeformed pitch between 2 subchannels.
virtual EChannelType getSubchannelType(unsigned int index) const =0
Return the type of the subchannel for given subchannel index.
std::unique_ptr< SolutionHandle > _S_flow_soln
virtual void computeh(int iblock) override
Computes Enthalpy per channel for block iblock.
static InputParameters validParams()
virtual Node * getPinNode(unsigned int i_pin, unsigned int iz) const =0
Get the pin mesh node for a given pin index and elevation index.
PetscErrorCode createPetscVector(Vec &v, PetscInt n)
Petsc Functions.
PetscFunctionBegin
virtual Real computeAddedHeatPin(unsigned int i_ch, unsigned int iz) const override
Pure virtual: daughters provide different implementations.
static InputParameters validParams()
PetscScalar computeInterpolatedValue(PetscScalar topValue, PetscScalar botValue, PetscScalar Peclet=0.0)
virtual const std::vector< unsigned int > & getChannelGaps(unsigned int i_chan) const =0
Return a vector of gap indices for a given channel index.
const unsigned int & getNumOfRings() const
Return the number of fuel-pin rings, counting the center pin as the first ring.
const Real & getDuctToPinGap() const
Return the the gap thickness between the duct and peripheral fuel pins.
std::unique_ptr< SolutionHandle > _rho_soln
static const std::string cp
Definition: NS.h:125
Vec _hc_added_heat_rhs
Enthalpy conservation - source and sink.
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 pitch
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.
PetscErrorCode createPetscMatrix(Mat &M, PetscInt n, PetscInt m)
Real computeSweepFlowMixingParameter(unsigned int i_gap, unsigned int iz) const
Computes and validates the sweep-flow mixing parameter.
virtual Node * getChannelNode(unsigned int i_chan, unsigned int iz) const =0
Get the subchannel mesh node for a given channel index and elevation index.
const MooseEnum _interpolation_scheme
The interpolation method used in constructing the systems.
std::unique_ptr< SolutionHandle > _Dpin_soln
bool _deformation
Flag that activates the effect of deformation (pin/duct) based on the auxvalues for displacement...
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...
virtual unsigned int getNumOfAxialCells() const
Return the number of axial cells.
Base class for the 1-phase steady-state/transient subchannel solver.
std::unique_ptr< SolutionHandle > _mdot_soln
Solutions handles and link to TH tables properties.
virtual Real getGapWidth(unsigned int axial_index, unsigned int gap_index) const =0
Return gap width for a given gap index.
const Real & getFlatToFlat() const
Return flat to flat [m].
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:138
virtual const Real & getCrossflowSign(unsigned int i_chan, unsigned int i_local) const =0
Return a sign for the crossflow given a subchannel index and local neighbor index.
void detectDeformation()
Detects whether pin diameter or duct displacement fields require geometry recalculation.
Real getGapWidth(unsigned int axial_index, unsigned int gap_index) const override
Return gap width for a given gap index.
virtual Real getSubChannelPeripheralDuctWidth(unsigned int i_ch) const override
Function that computes the width of the duct cell that the peripheral subchannel i_ch sees...
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
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 std::pair< unsigned int, unsigned int > & getSweepFlowChans(unsigned int i_chan) const
const ConsoleStream _console
registerMooseObject("SubChannelApp", TriSubChannel1PhaseProblem)
Mat _hc_time_derivative_mat
Enthalpy Enthalpy conservation - time derivative.
libMesh::DenseMatrix< Real > _WijPrime
PetscFunctionReturn(LIBMESH_PETSC_SUCCESS)
const Real & getWireLeadLength() const
Return the wire lead length.
virtual Real computeAddedHeatDuct(unsigned int i_ch, unsigned int iz) const
Non-pure: implemented in the base (or override in a child if needed)
TriSubChannel1PhaseProblem(const InputParameters &params)
MooseUnits pow(const MooseUnits &, int)
std::unique_ptr< SolutionHandle > _P_soln
std::unique_ptr< SolutionHandle > _w_perim_soln
Definition: SCM.h:16
virtual const Real & getHeatedLengthEntry() const
Return unheated length at entry.
const SinglePhaseFluidProperties * _fp
Non-owning pointer to fluid properties user object.
const Real pi
Mat _hc_sys_h_mat
System matrices.
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.