https://mooseframework.inl.gov
Loading...
Searching...
No Matches
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
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
46
48{
49 PetscErrorCode ierr = cleanUp();
50 if (ierr)
51 mooseError(name(), ": Error in memory cleanup");
52}
53
54PetscErrorCode
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
68void
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();
79 auto pitch = _subchannel_mesh.getPitch();
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;
165 auto pitch = _subchannel_mesh.getPitch();
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
261Real
262TriSubChannel1PhaseProblem::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;
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
308Real
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
324void
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 {
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 {
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];
547 auto pitch = _subchannel_mesh.getPitch();
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 }
579 auto alpha = computeInterpolationCoefficients(Pe);
580
581 // Keep temporal storage independent of the spatial interpolation scheme. Applying the
582 // upwind coefficient to this term puts the storage on the upstream node and makes the
583 // small-timestep matrix singular in the upwind limit.
584 PetscInt row_tt = i_ch + _n_channels * iz_ind;
585 PetscInt col_tt = i_ch + _n_channels * iz_ind;
586 PetscScalar value_tt = _TR * (*_rho_soln)(node_out)*volume / _dt;
587 LibmeshPetscCall(MatSetValues(
588 _hc_time_derivative_mat, 1, &row_tt, 1, &col_tt, &value_tt, INSERT_VALUES));
589
590 PetscScalar value_vec_tt =
591 _TR * _rho_soln->old(node_out) * _h_soln->old(node_out) * volume / _dt;
592 PetscInt row_vec_tt = i_ch + _n_channels * iz_ind;
593 LibmeshPetscCall(
594 VecSetValues(_hc_time_derivative_rhs, 1, &row_vec_tt, &value_vec_tt, ADD_VALUES));
595
596 // Advective derivative term
597 if (iz == first_node)
598 {
599 PetscInt row_at = i_ch + _n_channels * iz_ind;
600 PetscScalar value_at = alpha * (*_mdot_soln)(node_in) * (*_h_soln)(node_in);
601 LibmeshPetscCall(
602 VecSetValues(_hc_advective_derivative_rhs, 1, &row_at, &value_at, ADD_VALUES));
603
604 value_at = alpha * (*_mdot_soln)(node_out) - (1 - alpha) * (*_mdot_soln)(node_in);
605 PetscInt col_at = i_ch + _n_channels * iz_ind;
606 LibmeshPetscCall(MatSetValues(
607 _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
608
609 value_at = (1 - alpha) * (*_mdot_soln)(node_out);
610 col_at = i_ch + _n_channels * (iz_ind + 1);
611 LibmeshPetscCall(MatSetValues(
612 _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
613 }
614 else if (iz == last_node)
615 {
616 PetscInt row_at = i_ch + _n_channels * iz_ind;
617 PetscScalar value_at = 1.0 * (*_mdot_soln)(node_out);
618 PetscInt col_at = i_ch + _n_channels * iz_ind;
619 LibmeshPetscCall(MatSetValues(
620 _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
621
622 value_at = -1.0 * (*_mdot_soln)(node_in);
623 col_at = i_ch + _n_channels * (iz_ind - 1);
624 LibmeshPetscCall(MatSetValues(
625 _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
626 }
627 else
628 {
629 PetscInt row_at = i_ch + _n_channels * iz_ind;
630 PetscInt col_at;
631
632 PetscScalar value_at = -alpha * (*_mdot_soln)(node_in);
633 col_at = i_ch + _n_channels * (iz_ind - 1);
634 LibmeshPetscCall(MatSetValues(
635 _hc_advective_derivative_mat, 1, &row_at, 1, &col_at, &value_at, ADD_VALUES));
636
637 value_at = alpha * (*_mdot_soln)(node_out) - (1 - alpha) * (*_mdot_soln)(node_in);
638 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 - alpha) * (*_mdot_soln)(node_out);
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
648 // Axial heat conduction
649 auto * node_center = _subchannel_mesh.getChannelNode(i_ch, iz);
650 auto K_center = _fp->k_from_p_T((*_P_soln)(node_center) + _P_out, (*_T_soln)(node_center));
651 auto cp_center =
652 _fp->cp_from_p_T((*_P_soln)(node_center) + _P_out, (*_T_soln)(node_center));
653 auto diff_center = K_center / (cp_center + 1e-15);
654
655 if (iz == first_node)
656 {
657 auto * node_top = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
658 auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
659 auto K_bottom =
660 _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
661 auto K_top = _fp->k_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
662 auto cp_bottom =
663 _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
664 auto cp_top = _fp->cp_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
665 auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
666 auto diff_top = K_top / (cp_top + 1e-15);
667
668 auto dz_up = _z_grid[iz + 1] - _z_grid[iz];
669 auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
670 auto S_up =
671 computeInterpolatedValue((*_S_flow_soln)(node_top), (*_S_flow_soln)(node_center));
672 auto S_down =
673 computeInterpolatedValue((*_S_flow_soln)(node_center), (*_S_flow_soln)(node_bottom));
674 auto diff_up = computeInterpolatedValue(diff_top, diff_center);
675 auto diff_down = computeInterpolatedValue(diff_center, diff_bottom);
676
677 // Diagonal value
678 PetscInt row_at = i_ch + _n_channels * iz_ind;
679 PetscInt col_at = i_ch + _n_channels * iz_ind;
680 PetscScalar value_at = diff_up * S_up / dz_up + diff_down * S_down / dz_down;
681 LibmeshPetscCall(MatSetValues(
682 _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
683
684 // Bottom value
685 value_at = 1.0 * diff_down * S_down / dz_down * (*_h_soln)(node_bottom);
686 LibmeshPetscCall(
687 VecSetValues(_hc_axial_heat_conduction_rhs, 1, &row_at, &value_at, ADD_VALUES));
688
689 // Top value
690 col_at = i_ch + _n_channels * (iz_ind + 1);
691 value_at = -diff_up * S_up / dz_up;
692 LibmeshPetscCall(MatSetValues(
693 _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
694 }
695 else if (iz == last_node)
696 {
697 auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
698 auto K_bottom =
699 _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
700 auto cp_bottom =
701 _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
702 auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
703
704 auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
705 auto S_down = 0.5 * ((*_S_flow_soln)(node_center) + (*_S_flow_soln)(node_bottom));
706 auto diff_down = 0.5 * (diff_center + diff_bottom);
707
708 // Diagonal value
709 PetscInt row_at = i_ch + _n_channels * iz_ind;
710 PetscInt col_at = i_ch + _n_channels * iz_ind;
711 PetscScalar value_at = diff_down * S_down / dz_down;
712 LibmeshPetscCall(MatSetValues(
713 _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
714
715 // Bottom value
716 col_at = i_ch + _n_channels * (iz_ind - 1);
717 value_at = -diff_down * S_down / dz_down;
718 LibmeshPetscCall(MatSetValues(
719 _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
720
721 // Outflow derivative
723 // value_at = -1.0 * (*_mdot_soln)(node_center) * (*_h_soln)(node_center);
724 // VecSetValues(_hc_axial_heat_conduction_rhs, 1, &row_at, &value_at, ADD_VALUES);
725 }
726 else
727 {
728 auto * node_top = _subchannel_mesh.getChannelNode(i_ch, iz + 1);
729 auto * node_bottom = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
730 auto K_bottom =
731 _fp->k_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
732 auto K_top = _fp->k_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
733 auto cp_bottom =
734 _fp->cp_from_p_T((*_P_soln)(node_bottom) + _P_out, (*_T_soln)(node_bottom));
735 auto cp_top = _fp->cp_from_p_T((*_P_soln)(node_top) + _P_out, (*_T_soln)(node_top));
736 auto diff_bottom = K_bottom / (cp_bottom + 1e-15);
737 auto diff_top = K_top / (cp_top + 1e-15);
738
739 auto dz_up = _z_grid[iz + 1] - _z_grid[iz];
740 auto dz_down = _z_grid[iz] - _z_grid[iz - 1];
741 auto S_up =
742 computeInterpolatedValue((*_S_flow_soln)(node_top), (*_S_flow_soln)(node_center));
743 auto S_down =
744 computeInterpolatedValue((*_S_flow_soln)(node_center), (*_S_flow_soln)(node_bottom));
745 auto diff_up = computeInterpolatedValue(diff_top, diff_center);
746 auto diff_down = computeInterpolatedValue(diff_center, diff_bottom);
747
748 // Diagonal value
749 PetscInt row_at = i_ch + _n_channels * iz_ind;
750 PetscInt col_at = i_ch + _n_channels * iz_ind;
751 PetscScalar value_at = diff_up * S_up / dz_up + diff_down * S_down / dz_down;
752 LibmeshPetscCall(MatSetValues(
753 _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
754
755 // Bottom value
756 col_at = i_ch + _n_channels * (iz_ind - 1);
757 value_at = -diff_down * S_down / dz_down;
758 LibmeshPetscCall(MatSetValues(
759 _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
760
761 // Top value
762 col_at = i_ch + _n_channels * (iz_ind + 1);
763 value_at = -diff_up * S_up / dz_up;
764 LibmeshPetscCall(MatSetValues(
765 _hc_axial_heat_conduction_mat, 1, &row_at, 1, &col_at, &value_at, INSERT_VALUES));
766 }
767
768 // Radial Terms
769 unsigned int counter = 0;
770 unsigned int cross_index = iz;
771 // Real radial_heat_conduction(0.0);
772 for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
773 {
774 auto chans = _subchannel_mesh.getGapChannels(i_gap);
775 unsigned int ii_ch = chans.first;
776 unsigned int jj_ch = chans.second;
777 auto * node_in_i = _subchannel_mesh.getChannelNode(ii_ch, iz - 1);
778 auto * node_in_j = _subchannel_mesh.getChannelNode(jj_ch, iz - 1);
779 PetscScalar h_star;
780 // figure out donor axial velocity
781 if (_Wij(i_gap, cross_index) > 0.0)
782 {
783 if (iz == first_node)
784 {
785 h_star = (*_h_soln)(node_in_i);
786 PetscScalar value_vec_ct = -1.0 * alpha *
787 _subchannel_mesh.getCrossflowSign(i_ch, counter) *
788 _Wij(i_gap, cross_index) * h_star;
789 PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
790 LibmeshPetscCall(VecSetValues(
791 _hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
792 }
793 else
794 {
795 PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
796 _Wij(i_gap, cross_index);
797 PetscInt row_ct = i_ch + _n_channels * iz_ind;
798 PetscInt col_ct = ii_ch + _n_channels * (iz_ind - 1);
799 LibmeshPetscCall(MatSetValues(
800 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
801 }
802 PetscScalar value_ct = (1.0 - alpha) *
803 _subchannel_mesh.getCrossflowSign(i_ch, counter) *
804 _Wij(i_gap, cross_index);
805 PetscInt row_ct = i_ch + _n_channels * iz_ind;
806 PetscInt col_ct = ii_ch + _n_channels * iz_ind;
807 LibmeshPetscCall(MatSetValues(
808 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
809 }
810 else if (_Wij(i_gap, cross_index) < 0.0) // _Wij=0 operations not necessary
811 {
812 if (iz == first_node)
813 {
814 h_star = (*_h_soln)(node_in_j);
815 PetscScalar value_vec_ct = -1.0 * alpha *
816 _subchannel_mesh.getCrossflowSign(i_ch, counter) *
817 _Wij(i_gap, cross_index) * h_star;
818 PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
819 LibmeshPetscCall(VecSetValues(
820 _hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
821 }
822 else
823 {
824 PetscScalar value_ct = alpha * _subchannel_mesh.getCrossflowSign(i_ch, counter) *
825 _Wij(i_gap, cross_index);
826 PetscInt row_ct = i_ch + _n_channels * iz_ind;
827 PetscInt col_ct = jj_ch + _n_channels * (iz_ind - 1);
828 LibmeshPetscCall(MatSetValues(
829 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
830 }
831 PetscScalar value_ct = (1.0 - alpha) *
832 _subchannel_mesh.getCrossflowSign(i_ch, counter) *
833 _Wij(i_gap, cross_index);
834 PetscInt row_ct = i_ch + _n_channels * iz_ind;
835 PetscInt col_ct = jj_ch + _n_channels * iz_ind;
836 LibmeshPetscCall(MatSetValues(
837 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_ct, ADD_VALUES));
838 }
839
840 // Turbulent cross flows
841 if (iz == first_node)
842 {
843 PetscScalar value_vec_ct =
844 -2.0 * alpha * (*_h_soln)(node_in)*_WijPrime(i_gap, cross_index);
845 value_vec_ct += alpha * (*_h_soln)(node_in_j)*_WijPrime(i_gap, cross_index);
846 value_vec_ct += alpha * (*_h_soln)(node_in_i)*_WijPrime(i_gap, cross_index);
847 PetscInt row_vec_ct = i_ch + _n_channels * iz_ind;
848 LibmeshPetscCall(
849 VecSetValues(_hc_cross_derivative_rhs, 1, &row_vec_ct, &value_vec_ct, ADD_VALUES));
850 }
851 else
852 {
853 PetscScalar value_center_ct = 2.0 * alpha * _WijPrime(i_gap, cross_index);
854 PetscInt row_ct = i_ch + _n_channels * iz_ind;
855 PetscInt col_ct = i_ch + _n_channels * (iz_ind - 1);
856 LibmeshPetscCall(MatSetValues(
857 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
858
859 PetscScalar value_left_ct = -1.0 * alpha * _WijPrime(i_gap, cross_index);
860 row_ct = i_ch + _n_channels * iz_ind;
861 col_ct = jj_ch + _n_channels * (iz_ind - 1);
862 LibmeshPetscCall(MatSetValues(
863 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
864
865 PetscScalar value_right_ct = -1.0 * alpha * _WijPrime(i_gap, cross_index);
866 row_ct = i_ch + _n_channels * iz_ind;
867 col_ct = ii_ch + _n_channels * (iz_ind - 1);
868 LibmeshPetscCall(MatSetValues(
869 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
870 }
871 PetscScalar value_center_ct = 2.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
872 PetscInt row_ct = i_ch + _n_channels * iz_ind;
873 PetscInt col_ct = i_ch + _n_channels * iz_ind;
874 LibmeshPetscCall(MatSetValues(
875 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_center_ct, ADD_VALUES));
876
877 PetscScalar value_left_ct = -1.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
878 row_ct = i_ch + _n_channels * iz_ind;
879 col_ct = jj_ch + _n_channels * iz_ind;
880 LibmeshPetscCall(MatSetValues(
881 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_left_ct, ADD_VALUES));
882
883 PetscScalar value_right_ct = -1.0 * (1.0 - alpha) * _WijPrime(i_gap, cross_index);
884 row_ct = i_ch + _n_channels * iz_ind;
885 col_ct = ii_ch + _n_channels * iz_ind;
886 LibmeshPetscCall(MatSetValues(
887 _hc_cross_derivative_mat, 1, &row_ct, 1, &col_ct, &value_right_ct, ADD_VALUES));
888
889 // Radial heat conduction
890 auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
891 auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
892 Real dist_ij = pitch;
893
894 if (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::EDGE)
895 {
896 dist_ij = pitch;
897 }
898 else if ((subch_type_i == EChannelType::CORNER && subch_type_j == EChannelType::EDGE) ||
899 (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CORNER))
900 {
901 dist_ij = pitch;
902 }
903 else
904 {
905 dist_ij = pitch / std::sqrt(3);
906 }
907
908 auto Sij = dz * _subchannel_mesh.getGapWidth(iz, i_gap);
909 auto K_i = _fp->k_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
910 auto K_j = _fp->k_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
911 auto cp_i = _fp->cp_from_p_T((*_P_soln)(node_in_i) + _P_out, (*_T_soln)(node_in_i));
912 auto cp_j = _fp->cp_from_p_T((*_P_soln)(node_in_j) + _P_out, (*_T_soln)(node_in_j));
913 auto A_i = K_i / cp_i;
914 auto A_j = K_j / cp_j;
915 auto harm_A = 2.0 * A_i * A_j / (A_i + A_j);
916 auto shape_factor =
917 0.66 * (pitch / pin_diameter) *
918 std::pow((_subchannel_mesh.getGapWidth(iz, i_gap) / pin_diameter), -0.3);
919 // auto base_value = 0.5 * (A_i + A_j) * Sij * shape_factor / dist_ij;
920 auto base_value = harm_A * shape_factor * Sij / dist_ij;
921 auto neg_base_value = -1.0 * base_value;
922
923 row_ct = ii_ch + _n_channels * iz_ind;
924 col_ct = ii_ch + _n_channels * iz_ind;
925 LibmeshPetscCall(MatSetValues(
926 _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &base_value, ADD_VALUES));
927
928 row_ct = jj_ch + _n_channels * iz_ind;
929 col_ct = jj_ch + _n_channels * iz_ind;
930 LibmeshPetscCall(MatSetValues(
931 _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &base_value, ADD_VALUES));
932
933 row_ct = ii_ch + _n_channels * iz_ind;
934 col_ct = jj_ch + _n_channels * iz_ind;
935 LibmeshPetscCall(MatSetValues(
936 _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &neg_base_value, ADD_VALUES));
937
938 row_ct = jj_ch + _n_channels * iz_ind;
939 col_ct = ii_ch + _n_channels * iz_ind;
940 LibmeshPetscCall(MatSetValues(
941 _hc_radial_heat_conduction_mat, 1, &row_ct, 1, &col_ct, &neg_base_value, ADD_VALUES));
942 counter++;
943 }
944
945 // Compute the sweep flow enthalpy change
946 // Calculation of average mass flux of all periphery subchannels
947 Real edge_flux_ave = 0.0;
948 Real mdot_sum = 0.0;
949 Real si_sum = 0.0;
950 for (unsigned int i_ch = 0; i_ch < _n_channels; i_ch++)
951 {
952 auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
953 if (subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER)
954 {
955 auto * node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
956 auto Si = (*_S_flow_soln)(node_in);
957 auto mdot_in = (*_mdot_soln)(node_in);
958 mdot_sum = mdot_sum + mdot_in;
959 si_sum = si_sum + Si;
960 }
961 }
962 edge_flux_ave = mdot_sum / si_sum;
963 auto subch_type = _subchannel_mesh.getSubchannelType(i_ch);
964 PetscScalar sweep_enthalpy = 0.0;
965 if ((subch_type == EChannelType::EDGE || subch_type == EChannelType::CORNER) &&
966 (wire_diameter != 0.0) && (wire_lead_length != 0.0))
967 {
968 auto beta_in = std::numeric_limits<double>::quiet_NaN();
969 auto beta_out = std::numeric_limits<double>::quiet_NaN();
970 // donor sweep channel for i_ch
971 auto sweep_donor = _tri_sch_mesh.getSweepFlowChans(i_ch).first;
972 auto * node_sweep_donor = _subchannel_mesh.getChannelNode(sweep_donor, iz - 1);
973 // Calculation of turbulent mixing parameter
974 for (auto i_gap : _subchannel_mesh.getChannelGaps(i_ch))
975 {
976 auto chans = _subchannel_mesh.getGapChannels(i_gap);
977 unsigned int ii_ch = chans.first;
978 unsigned int jj_ch = chans.second;
979 auto subch_type_i = _subchannel_mesh.getSubchannelType(ii_ch);
980 auto subch_type_j = _subchannel_mesh.getSubchannelType(jj_ch);
981 if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
982 (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE))
983 {
984 if ((ii_ch == sweep_donor) || (jj_ch == sweep_donor))
985 {
986 beta_in = computeSweepFlowMixingParameter(i_gap, iz);
987 }
988 else
989 {
990 beta_out = computeSweepFlowMixingParameter(i_gap, iz);
991 }
992 }
993 }
994 // Abort execution if required values are unset
995 mooseAssert(!std::isnan(beta_in),
996 "beta_in was not set. Check gap logic for i_ch = " + std::to_string(i_ch) +
997 ", iz = " + std::to_string(iz));
998 mooseAssert(!std::isnan(beta_out),
999 "beta_out was not set. Check gap logic for i_ch = " + std::to_string(i_ch) +
1000 ", iz = " + std::to_string(iz));
1001
1002 auto gap = _tri_sch_mesh.getDuctToPinGap();
1003 auto Sij = dz * gap;
1004 auto wsweep_in = edge_flux_ave * beta_in * Sij;
1005 auto wsweep_out = edge_flux_ave * beta_out * Sij;
1006 auto sweep_hin = (*_h_soln)(node_sweep_donor);
1007 auto sweep_hout = (*_h_soln)(node_in);
1008 sweep_enthalpy = (wsweep_in * sweep_hin - wsweep_out * sweep_hout);
1009
1010 if (iz == first_node)
1011 {
1012 PetscInt row_sh = i_ch + _n_channels * iz_ind;
1013 PetscScalar value_hs = -sweep_enthalpy;
1014 LibmeshPetscCall(
1015 VecSetValues(_hc_sweep_enthalpy_rhs, 1, &row_sh, &value_hs, ADD_VALUES));
1016 }
1017 else
1018 {
1019 // coefficient of sweep_hin
1020 PetscInt row_sh = i_ch + _n_channels * (iz_ind - 1);
1021 PetscInt col_sh = i_ch + _n_channels * (iz_ind - 1);
1022 LibmeshPetscCall(MatSetValues(
1023 _hc_sweep_enthalpy_mat, 1, &row_sh, 1, &col_sh, &wsweep_out, ADD_VALUES));
1024 PetscInt col_sh_l = sweep_donor + _n_channels * (iz_ind - 1);
1025 PetscScalar neg_sweep_in = -1.0 * wsweep_in;
1026 // coefficient of sweep_hout
1027 LibmeshPetscCall(MatSetValues(
1028 _hc_sweep_enthalpy_mat, 1, &row_sh, 1, &col_sh_l, &(neg_sweep_in), ADD_VALUES));
1029 }
1030 }
1031
1032 // Add heat enthalpy from pin and/or duct
1033 PetscScalar added_enthalpy = computeAddedHeatPin(i_ch, iz);
1034 added_enthalpy += computeAddedHeatDuct(i_ch, iz);
1035 PetscInt row_vec_ht = i_ch + _n_channels * iz_ind;
1036 LibmeshPetscCall(
1037 VecSetValues(_hc_added_heat_rhs, 1, &row_vec_ht, &added_enthalpy, ADD_VALUES));
1038 }
1039 }
1040 // Assembling system
1041 LibmeshPetscCall(MatAssemblyBegin(_hc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1042 LibmeshPetscCall(MatAssemblyEnd(_hc_time_derivative_mat, MAT_FINAL_ASSEMBLY));
1043 LibmeshPetscCall(MatAssemblyBegin(_hc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1044 LibmeshPetscCall(MatAssemblyEnd(_hc_advective_derivative_mat, MAT_FINAL_ASSEMBLY));
1045 LibmeshPetscCall(MatAssemblyBegin(_hc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1046 LibmeshPetscCall(MatAssemblyEnd(_hc_cross_derivative_mat, MAT_FINAL_ASSEMBLY));
1047 LibmeshPetscCall(MatAssemblyBegin(_hc_axial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1048 LibmeshPetscCall(MatAssemblyEnd(_hc_axial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1049 LibmeshPetscCall(MatAssemblyBegin(_hc_radial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1050 LibmeshPetscCall(MatAssemblyEnd(_hc_radial_heat_conduction_mat, MAT_FINAL_ASSEMBLY));
1051 LibmeshPetscCall(MatAssemblyBegin(_hc_sweep_enthalpy_mat, MAT_FINAL_ASSEMBLY));
1052 LibmeshPetscCall(MatAssemblyEnd(_hc_sweep_enthalpy_mat, MAT_FINAL_ASSEMBLY));
1053 LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1054 LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1055 // Add all matrices together
1056 LibmeshPetscCall(
1057 MatAXPY(_hc_sys_h_mat, 1.0, _hc_time_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1058 LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1059 LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1060 LibmeshPetscCall(
1061 MatAXPY(_hc_sys_h_mat, 1.0, _hc_advective_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1062 LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1063 LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1064 LibmeshPetscCall(
1065 MatAXPY(_hc_sys_h_mat, 1.0, _hc_cross_derivative_mat, DIFFERENT_NONZERO_PATTERN));
1066 LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1067 LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1068 LibmeshPetscCall(
1069 MatAXPY(_hc_sys_h_mat, 1.0, _hc_axial_heat_conduction_mat, DIFFERENT_NONZERO_PATTERN));
1070 LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1071 LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1072 LibmeshPetscCall(
1073 MatAXPY(_hc_sys_h_mat, 1.0, _hc_radial_heat_conduction_mat, DIFFERENT_NONZERO_PATTERN));
1074 LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1075 LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1076 LibmeshPetscCall(
1077 MatAXPY(_hc_sys_h_mat, 1.0, _hc_sweep_enthalpy_mat, DIFFERENT_NONZERO_PATTERN));
1078 LibmeshPetscCall(MatAssemblyBegin(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1079 LibmeshPetscCall(MatAssemblyEnd(_hc_sys_h_mat, MAT_FINAL_ASSEMBLY));
1081 _console << "Block: " << iblock << " - Enthalpy conservation matrix assembled" << std::endl;
1082 // RHS
1083 LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_time_derivative_rhs));
1084 LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_advective_derivative_rhs));
1085 LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_cross_derivative_rhs));
1086 LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_added_heat_rhs));
1087 LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_axial_heat_conduction_rhs));
1088 LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_radial_heat_conduction_rhs));
1089 LibmeshPetscCall(VecAXPY(_hc_sys_h_rhs, 1.0, _hc_sweep_enthalpy_rhs));
1090
1091 // Use system to solve for and populate enthalpy
1092 LibmeshPetscCall(this->solveAndPopulateEnthalpy(
1093 _hc_sys_h_mat, _hc_sys_h_rhs, first_node, last_node, "h_sys_"));
1094 }
1095}
PetscFunctionBegin
registerMooseObject("SubChannelApp", TriSubChannel1PhaseProblem)
const ConsoleStream _console
std::shared_ptr< AuxiliarySystem > _aux
void addClassDescription(const std::string &doc_string)
const std::string & name() const
void mooseError(Args &&... args) const
void mooseWarning(Args &&... args) const
Base class for the 1-phase steady-state/transient subchannel solver.
PetscErrorCode createPetscVector(Vec &v, PetscInt n)
Petsc Functions.
PetscScalar computeInterpolatedValue(PetscScalar topValue, PetscScalar botValue, PetscScalar Peclet=0.0)
Real computeSweepFlowMixingParameter(unsigned int i_gap, unsigned int iz) const
Computes and validates the sweep-flow mixing parameter.
std::unique_ptr< SolutionHandle > _w_perim_soln
PetscErrorCode createPetscMatrix(Mat &M, PetscInt n, PetscInt m)
Mat _hc_time_derivative_mat
Enthalpy Enthalpy conservation - time derivative.
static InputParameters validParams()
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 SinglePhaseFluidProperties * _fp
Non-owning pointer to fluid properties user object.
libMesh::DenseMatrix< Real > _WijPrime
const MooseEnum _interpolation_scheme
The interpolation method used in constructing the systems.
std::unique_ptr< SolutionHandle > _rho_soln
std::unique_ptr< SolutionHandle > _S_flow_soln
const PostprocessorValue & _P_out
Outlet pressure postprocessor value.
std::unique_ptr< SolutionHandle > _Dpin_soln
std::unique_ptr< SolutionHandle > _P_soln
void detectDeformation()
Detects whether pin diameter or duct displacement fields require geometry recalculation.
libMesh::DenseMatrix< Real > & _Wij
bool _deformation
Flag that activates the effect of deformation (pin/duct) based on the auxvalues for displacement,...
std::unique_ptr< SolutionHandle > _h_soln
std::unique_ptr< SolutionHandle > _T_soln
Real _TR
Flag that activates or deactivates the transient parts of the equations we solve by multiplication.
PetscScalar computeInterpolationCoefficients(PetscScalar Peclet=0.0)
Functions that computes the interpolation scheme given the Peclet number.
Vec _hc_added_heat_rhs
Enthalpy conservation - source and sink.
Mat _hc_advective_derivative_mat
Enthalpy conservation - advective (Eulerian) derivative;.
const bool _pin_mesh_exist
Flag that informs if there is a pin mesh or not.
const bool _implicit_bool
Flag to define the usage of a implicit or explicit solution.
std::vector< Real > _z_grid
axial location of nodes
std::unique_ptr< SolutionHandle > _mdot_soln
Solutions handles and link to TH tables properties.
virtual Real computeAddedHeatDuct(unsigned int i_ch, unsigned int iz) const
Non-pure: implemented in the base (or override in a child if needed)
const bool _verbose_subchannel
Boolean to printout information related to subchannel solve.
Mat _hc_cross_derivative_mat
Enthalpy conservation - cross flux derivative.
virtual const Real & getHeatedLength() const
Return heated length.
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 > & getZBlockage() const
Get axial location of blockage (in,out) [m].
virtual const Real & getHeatedLengthEntry() const
Return unheated length at entry.
virtual const std::vector< Real > & getZGrid() const
Get axial location of layers.
virtual Real getGapWidth(unsigned int axial_index, unsigned int gap_index) const =0
Return gap width for a given gap index.
virtual const Real & getPitch() const
Return the undeformed pitch between 2 subchannels.
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 std::vector< Real > & getReductionBlockage() const
Get area reduction of blocked subchannels.
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 EChannelType getSubchannelType(unsigned int index) const =0
Return the type of the subchannel for given subchannel index.
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 Node * getChannelNode(unsigned int i_chan, unsigned int iz) const =0
Get the subchannel mesh node for a given channel index and elevation index.
virtual const std::vector< unsigned int > & getIndexBlockage() const
Get index of blocked subchannels.
virtual const std::vector< unsigned int > & getChannelGaps(unsigned int i_chan) const =0
Return a vector of gap indices for a given channel index.
virtual unsigned int getNumOfAxialCells() const
Return the number of axial cells.
virtual const Real & getPinDiameter() const
Return undeformed Pin diameter.
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.
virtual const std::vector< unsigned int > & getPinChannels(unsigned int i_pin) const =0
Return a vector of channel indices for a given Pin index.
Triangular subchannel solver.
static InputParameters validParams()
virtual Real computeAddedHeatPin(unsigned int i_ch, unsigned int iz) const override
Pure virtual: daughters provide different implementations.
virtual void computeh(int iblock) override
Computes Enthalpy per channel for block iblock.
virtual void initializeSolution() override
Function to initialize the solution & geometry fields.
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.
TriSubChannel1PhaseProblem(const InputParameters &params)
Mesh class for triangular, edge and corner subchannels for hexagonal lattice fuel assemblies.
const Real & getFlatToFlat() const
Return flat to flat [m].
const Real & getWireDiameter() const
Return wire diameter.
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.
Real getGapWidth(unsigned int axial_index, unsigned int gap_index) const override
Return gap width for a given gap index.
const Real & getDuctToPinGap() const
Return the the gap thickness between the duct and peripheral fuel pins.
const Real & getWireLeadLength() const
Return the wire lead length.
const std::pair< unsigned int, unsigned int > & getSweepFlowChans(unsigned int i_chan) const
const unsigned int & getNumOfRings() const
Return the number of fuel-pin rings, counting the center pin as the first ring.
Definition SCM.h:17
const Real pi