https://mooseframework.inl.gov
Loading...
Searching...
No Matches
SCMMixingChenTodreas.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
14 SCMMixingChengTodreas,
15 "09/30/2027 24:00",
17
20{
22 params.addClassDescription("Class that models the turbulent mixing coefficient for wire-wrapped "
23 "triangular assemblies using the Chen Todreas correlations.");
24 MooseEnum mixing_model("1986 Pacio", "1986");
25 params.addParam<MooseEnum>(
26 "mixing_model",
27 mixing_model,
28 "Mixing-model parameterization for triangular wire-wrapped Chen-Todreas correlations.");
29 return params;
30}
31
33 : SCMMixingClosureBase(parameters),
34 _is_tri_lattice(dynamic_cast<const TriSubChannelMesh *>(&_subchannel_mesh) != nullptr),
35 _tri_sch_mesh(dynamic_cast<const TriSubChannelMesh *>(&_subchannel_mesh)),
36 _mixing_model(getParam<MooseEnum>("mixing_model")),
37 _S_soln(_subproblem.getVariable(0, "S")),
38 _mdot_soln(_subproblem.getVariable(0, "mdot")),
39 _rho_soln(_subproblem.getVariable(0, "rho")),
40 // Not a number until computeBulkMixingParameters, so that the problem rejects any use before
41 _beta_1986(std::numeric_limits<Real>::quiet_NaN()),
42 _beta_sweep(std::numeric_limits<Real>::quiet_NaN())
43{
44 if (!_is_tri_lattice)
45 mooseError("This correlation applies only for triangular assemblies");
46
48 mooseError("This correlation applies only for wire-wrapped assemblies");
49
50 const auto pitch = _subchannel_mesh.getPitch();
51 const auto pin_diameter = _subchannel_mesh.getPinDiameter();
52 const auto p_over_d = pitch / pin_diameter;
53 const auto wire_lead_to_diameter = _tri_sch_mesh->getWireLeadLength() / pin_diameter;
54 const unsigned int Nr = _tri_sch_mesh->getNumOfRings();
55 const unsigned int num_pins = 1 + 3 * Nr * (Nr - 1);
56
57 // Cheng and Todreas (1986), Table 3, reports the mixing parameter data range as
58 // 1.07 <= P/D <= 1.30, 4 <= H/D <= 52, and 400 <= Reb <= 1.0e6. Their Tables 1 and 2 list
59 // the mixing experiments, which span 7 <= Npin <= 217.
60 // Pacio et al. (2022), Table 1, reports the PCTD range of validity as
61 // 19 <= Npin <= 217, 1.02 <= P/D <= 1.42, 7.5 <= H/D <= 54, and 10 <= Reb <= 3.0e5.
62 if (_mixing_model == "1986")
63 {
64 if (p_over_d < 1.07 || p_over_d > 1.30)
65 flagSolutionWarning("Pitch-over-pin diameter ratio (P/D) outside the 1986 "
66 "Cheng-Todreas mixing correlation data range.");
67 if (wire_lead_to_diameter < 4.0 || wire_lead_to_diameter > 52.0)
68 flagSolutionWarning("Wire lead length-over-pin diameter ratio (H/D) outside the 1986 "
69 "Cheng-Todreas mixing correlation data range.");
70 if (num_pins < 7 || num_pins > 217)
71 flagSolutionWarning("Number of pins outside the 1986 Cheng-Todreas mixing correlation "
72 "data range.");
73 }
74 else
75 {
76 if (p_over_d < 1.02 || p_over_d > 1.42)
77 flagSolutionWarning("Pitch-over-pin diameter ratio (P/D) outside the "
78 "Pacio-Chen-Todreas mixing correlation data range.");
79 if (wire_lead_to_diameter < 7.5 || wire_lead_to_diameter > 54.0)
80 flagSolutionWarning("Wire lead length-over-pin diameter ratio (H/D) outside the "
81 "Pacio-Chen-Todreas mixing correlation data range.");
82 if (num_pins < 19 || num_pins > 217)
83 flagSolutionWarning("Number of pins outside the Pacio-Chen-Todreas mixing correlation "
84 "data range.");
85 }
86}
87
88void
90{
91 const Real pitch = _subchannel_mesh.getPitch();
92 const Real pin_diameter = _subchannel_mesh.getPinDiameter();
93 const Real p_over_d = pitch / pin_diameter;
94
95 const Real wire_lead_length = _tri_sch_mesh->getWireLeadLength();
96 const Real wire_diameter = _tri_sch_mesh->getWireDiameter();
97 const unsigned int Nr = _tri_sch_mesh->getNumOfRings();
98
99 const Real bulk_Re = _scm_problem.getBulkReynoldsNumber();
100
101 // The bulk Reynolds number is computed from the solution during the solve, so its
102 // applicability range (see constructor) can only be checked here rather than at construction
103 if (_mixing_model == "1986")
104 {
105 if (bulk_Re < 400.0 || bulk_Re > 1.0e6)
106 flagSolutionWarning("Bulk Reynolds number (Reb) outside the 1986 Cheng-Todreas mixing "
107 "correlation data range.");
108 }
109 else if (bulk_Re < 10.0 || bulk_Re > 3.0e5)
110 flagSolutionWarning("Bulk Reynolds number (Reb) outside the Pacio-Chen-Todreas mixing "
111 "correlation data range.");
112
113 const Real theta = std::acos(
114 wire_lead_length / std::sqrt(Utility::pow<2>(wire_lead_length) +
115 Utility::pow<2>(libMesh::pi * (pin_diameter + wire_diameter))));
116
117 const Real ReL = 320.0 * std::pow(10.0, p_over_d - 1.0);
118
119 const Real ReT = 10000.0 * std::pow(10.0, 0.7 * (p_over_d - 1.0));
120
121 //
122 // Original Cheng-Todreas (1986) mixing correlation.
123 //
124 // This remains active for center-center gaps even when Pacio is selected,
125 // while center-edge gaps and edge-corner gaps are replaced by the Pacio treatment.
126 // When Pacio is not selected: beta is defined for center-center, center-edge gaps.
127 // In edge-edge, edge-corner beta = zero.
128 {
129 // projected area of wire on center subchannel
130 const Real Ar1 = libMesh::pi * (pin_diameter + wire_diameter) * wire_diameter / 6.0;
131
132 // bare center-subchannel flow area
133 const Real A1prime = (std::sqrt(3.0) / 4.0) * Utility::pow<2>(pitch) -
134 libMesh::pi * Utility::pow<2>(pin_diameter) / 8.0;
135
136 Real CmL_constant;
137 Real CmT_constant;
138
139 if (Nr == 1)
140 {
141 CmT_constant = 0.1;
142 CmL_constant = 0.055;
143 }
144 else
145 {
146 CmT_constant = 0.14;
147 CmL_constant = 0.077;
148 }
149
150 const Real CmT = CmT_constant * std::pow((pitch - pin_diameter) / pin_diameter, -0.5);
151
152 const Real CmL = CmL_constant * std::pow((pitch - pin_diameter) / pin_diameter, -0.5);
153
154 Real Cm;
155
156 if (bulk_Re < ReL)
157 Cm = CmL;
158 else if (bulk_Re > ReT)
159 Cm = CmT;
160 else
161 {
162 // Intermittency factor from the bulk Reynolds number. Cheng and Todreas (1986) Eqs.
163 // (10a)-(10c) and (11) use one per subchannel type, with the bounds Re_bL and Re_bT scaled by
164 // the laminar and turbulent flow splits; see SCMMixingChenTodreas.md
165 const Real psi = std::log(bulk_Re / ReL) / std::log(ReT / ReL);
166
167 const Real gamma = 2.0 / 3.0;
168
169 Cm = CmL + (CmT - CmL) * std::pow(psi, gamma);
170 }
171
172 _beta_1986 = Cm * std::sqrt(Ar1 / A1prime) * std::tan(theta);
173 }
174
175 // Sweep flow always uses the original Cheng-Todreas (1986) correlation.
176 {
177 // distance from pin surface to duct
178 const Real dpgap = _tri_sch_mesh->getDuctToPinGap();
179
180 // Edge pitch parameter defined as pin diameter plus distance to duct wall
181 const Real w = pin_diameter + dpgap;
182
183 const Real Ar2 = libMesh::pi * (pin_diameter + wire_diameter) * wire_diameter / 4.0;
184
185 const Real A2prime =
186 pitch * (w - pin_diameter / 2.0) - libMesh::pi * Utility::pow<2>(pin_diameter) / 8.0;
187
188 Real CsL_constant;
189 Real CsT_constant;
190
191 if (Nr == 1)
192 {
193 CsT_constant = 0.6;
194 CsL_constant = 0.33;
195 }
196 else
197 {
198 CsT_constant = 0.75;
199 CsL_constant = 0.413;
200 }
201
202 const Real CsL = CsL_constant * std::pow(wire_lead_length / pin_diameter, 0.3);
203 const Real CsT = CsT_constant * std::pow(wire_lead_length / pin_diameter, 0.3);
204
205 Real Cs;
206 if (bulk_Re < ReL)
207 Cs = CsL;
208 else if (bulk_Re > ReT)
209 Cs = CsT;
210 else
211 {
212 // Intermittency factor from the bulk Reynolds number. Cheng and Todreas (1986) Eqs.
213 // (10a)-(10c) and (11) use one per subchannel type, with the bounds Re_bL and Re_bT scaled by
214 // the laminar and turbulent flow splits; see SCMMixingChenTodreas.md
215 const Real psi = std::log(bulk_Re / ReL) / std::log(ReT / ReL);
216 const Real gamma = 2.0 / 3.0;
217
218 Cs = CsL + (CsT - CsL) * std::pow(psi, gamma);
219 }
220
221 // Sweep-flow coefficient used only by the peripheral enthalpy calculation.
222 _beta_sweep = Cs * std::sqrt(Ar2 / A2prime) * std::tan(theta);
223 }
224}
225
226void
228 const unsigned int last_node) const
229{
230 if (_mixing_model != "Pacio")
231 return;
232
233 if (_beta_pacio_center_edge.size() < last_node + 1)
234 {
235 _beta_pacio_center_edge.resize(last_node + 1);
236 _beta_pacio_edge_corner.resize(last_node + 1);
237 }
238
239 const Real pitch = _subchannel_mesh.getPitch();
240 const Real pin_diameter = _subchannel_mesh.getPinDiameter();
241 const Real wire_diameter = _tri_sch_mesh->getWireDiameter();
242 const Real dpgap = _tri_sch_mesh->getDuctToPinGap();
243 const Real bulk_V = _scm_problem.getBulkVelocity();
244
245 // Pacio et al. (2022) Eq. (30) multiplies beta by the contact perimeter between all subchannels
246 // of two types, Eqs. (A.24) and (A.25), while SCM multiplies beta by the width of each gap.
247 // Scale beta by the ratio of the contact perimeter per gap to the gap width, so that the sum
248 // over the gaps of SCM gives the crossflow of Pacio:
249 // - center-edge: Pi12 = 6 n (P - D - Dw / 6) over the 6 n pin-pin gaps of width P - D;
250 // - edge-corner: Pi23 = [6 (W - D) 2 - Dw / 6] / 2 over the 12 pin-duct gaps of width W - D.
251 // Eq. (A.25) subtracts Dw / 6 once from the whole edge-corner perimeter, while Eq. (A.24)
252 // subtracts it from every center-edge gap. The corrigendum of Pacio et al., Nucl. Eng. Des. 395
253 // (2022) 111846, corrects their implementation to be consistent with Eq. (A.25) as printed.
254 const Real perimeter_ratio_center_edge =
255 (pitch - pin_diameter - wire_diameter / 6.0) / (pitch - pin_diameter);
256 const Real perimeter_ratio_edge_corner = (0.5 * dpgap - wire_diameter / 144.0) / dpgap;
257
258 for (unsigned int iz = first_node; iz < last_node + 1; iz++)
259 {
260 // Flow split of each subchannel type lumped over the axial cell, as in the PCTD model,
261 // where the flow split is defined per subchannel type: sum of the mass flow rates over sum of
262 // the density times flow area, averaged over the inlet and outlet of the cell. In the
263 // transition regime, Eq. (36) of Pacio et al. (2022) uses the turbulent flow split instead;
264 // the flow-split term varies as about X^(-0.18), so this changes the mixing by less than 1%.
265 Real mdot_sum[3] = {0.0, 0.0, 0.0};
266 Real rhoS_sum[3] = {0.0, 0.0, 0.0};
267 Real S_sum[3] = {0.0, 0.0, 0.0};
268 for (const auto i_ch : make_range(_subchannel_mesh.getNumOfChannels()))
269 {
270 const Node * const node_in = _subchannel_mesh.getChannelNode(i_ch, iz - 1);
271 const Node * const node_out = _subchannel_mesh.getChannelNode(i_ch, iz);
272 const auto type = _subchannel_mesh.getSubchannelType(i_ch);
273 const unsigned int i_type =
274 type == EChannelType::CENTER ? 0 : (type == EChannelType::EDGE ? 1 : 2);
275 mdot_sum[i_type] += 0.5 * (_mdot_soln(node_in) + _mdot_soln(node_out));
276 rhoS_sum[i_type] += 0.25 * (_rho_soln(node_in) + _rho_soln(node_out)) *
277 (_S_soln(node_in) + _S_soln(node_out));
278 S_sum[i_type] += 0.5 * (_S_soln(node_in) + _S_soln(node_out));
279 }
280 // Velocity of each type relative to the inlet bulk velocity. The density drops along a heated
281 // assembly, so the local bulk velocity rises above bulk_V; normalize so that the area-weighted
282 // flow splits sum to one, Pacio et al. (2022) Eq. (11), with S the fraction of the flow area
283 const Real V_center = mdot_sum[0] / rhoS_sum[0] / bulk_V;
284 const Real V_edge = mdot_sum[1] / rhoS_sum[1] / bulk_V;
285 const Real V_corner = mdot_sum[2] / rhoS_sum[2] / bulk_V;
286 const Real S_total = S_sum[0] + S_sum[1] + S_sum[2];
287 const Real norm = (S_sum[0] * V_center + S_sum[1] * V_edge + S_sum[2] * V_corner) / S_total;
288 const Real X_center = V_center / norm;
289 const Real X_edge = V_edge / norm;
290 const Real X_corner = V_corner / norm;
291
293 computePacioMixingParameter(X_center, X_edge, perimeter_ratio_center_edge);
295 computePacioMixingParameter(X_edge, X_corner, perimeter_ratio_edge_corner);
296 }
297}
298
299Real
301 const Real Xj,
302 const Real perimeter_ratio) const
303{
304 mooseAssert(_mixing_model == "Pacio", "The Pacio mixing parameter requires mixing_model = Pacio");
305
306 const Real pitch = _subchannel_mesh.getPitch();
307 const Real pin_diameter = _subchannel_mesh.getPinDiameter();
308 const Real wire_lead_length = _tri_sch_mesh->getWireLeadLength();
309 const Real wire_diameter = _tri_sch_mesh->getWireDiameter();
310 const Real bulk_Re = _scm_problem.getBulkReynoldsNumber();
311
312 const Real theta = std::acos(
313 wire_lead_length / std::sqrt(Utility::pow<2>(wire_lead_length) +
314 Utility::pow<2>(libMesh::pi * (pin_diameter + wire_diameter))));
315
316 const Real ReL = 700.0;
317 const Real ReT = 10000.0;
318
319 constexpr Real flow_split_exponent = 2.0 - 0.18;
320
321 // Flow-split term of the mixing parameter, Pacio et al. (2022) Eq. (31).
322 Real fraction;
323 if (MooseUtils::absoluteFuzzyEqual(Xi, Xj))
324 {
325 const Real Xavg = 0.5 * (Xi + Xj);
326
327 if (Xavg < 0.0)
328 mooseError("The Pacio mixing correlation does not support negative flow splits "
329 "when evaluating the fractional flow-split exponent.");
330
331 // The flow-split term diverges as X^(-m) at zero flow; the crossflow is zero there because
332 // SCM multiplies beta by the average mass flux of the gap, so any finite value works
333 if (MooseUtils::absoluteFuzzyEqual(Xavg, 0.0))
334 fraction = 0.0;
335 else
336 fraction = 0.5 * flow_split_exponent * std::pow(Xavg, flow_split_exponent - 2.0);
337 }
338 else
339 {
340 if (Xi < 0.0 || Xj < 0.0)
341 mooseError("The Pacio mixing correlation does not support negative flow splits "
342 "when evaluating the fractional flow-split exponent.");
343
344 fraction = (std::pow(Xi, flow_split_exponent) - std::pow(Xj, flow_split_exponent)) /
345 (Utility::pow<2>(Xi) - Utility::pow<2>(Xj));
346 }
347
348 // Laminar and turbulent wire mixing coefficients, Pacio et al. (2022) Table 5
349 const Real WmL = 0.0;
350 const Real WmT = 8.8;
351
352 // Mixing parameter without the geometric factors, Pacio et al. (2022) Eqs. (31) and (33):
353 // WmT fraction / Reb^m in the turbulent regime and WmL / Reb in the laminar regime
354 const Real Re_eff = std::max(bulk_Re, 1.0);
355 const Real Cm_L = WmL / Re_eff;
356 const Real Cm_T = WmT * fraction / std::pow(Re_eff, 0.18);
357
358 Real Cm;
359
360 if (bulk_Re < ReL)
361 Cm = Cm_L;
362 else if (bulk_Re > ReT)
363 Cm = Cm_T;
364 else
365 {
366 const Real psi = std::log(bulk_Re / ReL) / std::log(ReT / ReL);
367
368 // Same transition interpolation as the Pacio-Chen-Todreas friction factor, Pacio et al. (2022)
369 // Eq. (35), with gamma and lambda of Table 5
370 const Real gamma = 0.362;
371 const Real lambda = 6.7;
372
373 Cm = Cm_L * std::pow(1.0 - psi, gamma) * (1.0 - std::pow(psi, lambda)) +
374 Cm_T * std::pow(psi, gamma);
375 }
376
377 // Pacio uses the edge-subchannel projected wire area and bare flow area.
378 const Real dpgap = _tri_sch_mesh->getDuctToPinGap();
379 const Real w = pin_diameter + dpgap;
380
381 const Real Ar2 = libMesh::pi * (pin_diameter + wire_diameter) * wire_diameter / 4.0;
382
383 const Real A2prime =
384 pitch * (w - pin_diameter / 2.0) - libMesh::pi * Utility::pow<2>(pin_diameter) / 8.0;
385
386 return Cm * std::sqrt(Ar2 / A2prime) * std::tan(theta) * perimeter_ratio;
387}
388
389Real
390SCMMixingChenTodreas::computeMixingParameter(const unsigned int i_gap, const unsigned int iz) const
391{
392 const auto chans = _subchannel_mesh.getGapChannels(i_gap);
393 const auto subch_type_i = _subchannel_mesh.getSubchannelType(chans.first);
394 const auto subch_type_j = _subchannel_mesh.getSubchannelType(chans.second);
395
396 // Pacio applicability
397 const bool center_edge =
398 (subch_type_i == EChannelType::CENTER && subch_type_j == EChannelType::EDGE) ||
399 (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CENTER);
400
401 const bool edge_corner =
402 (subch_type_i == EChannelType::EDGE && subch_type_j == EChannelType::CORNER) ||
403 (subch_type_i == EChannelType::CORNER && subch_type_j == EChannelType::EDGE);
404
405 // Pacio defines the mixing treatment across center-edge and edge-corner gaps.
406 // For all other applicable gaps, we retain the original Cheng-Todreas (1986) model.
407 if (_mixing_model == "Pacio" && center_edge)
408 return _beta_pacio_center_edge[iz];
409 else if (_mixing_model == "Pacio" && edge_corner)
410 return _beta_pacio_edge_corner[iz];
411 else if (subch_type_i == EChannelType::CENTER || subch_type_j == EChannelType::CENTER)
412 return _beta_1986;
413 else
414 return 0.0;
415}
416
417Real
419 const unsigned int /* iz */) const
420{
421 const auto chans = _subchannel_mesh.getGapChannels(i_gap);
422 const auto subch_type_i = _subchannel_mesh.getSubchannelType(chans.first);
423 const auto subch_type_j = _subchannel_mesh.getSubchannelType(chans.second);
424
425 if ((subch_type_i == EChannelType::CORNER || subch_type_i == EChannelType::EDGE) &&
426 (subch_type_j == EChannelType::CORNER || subch_type_j == EChannelType::EDGE))
427 return _beta_sweep;
428 else
429 return 0.0;
430}
registerMooseObjectRenamed("SubChannelApp", SCMMixingChengTodreas, "09/30/2027 24:00", SCMMixingChenTodreas)
registerMooseObject("SubChannelApp", SCMMixingChenTodreas)
void addParam(const std::string &name, const std::initializer_list< typename T::value_type > &value, const std::string &doc_string)
void addClassDescription(const std::string &doc_string)
const std::string & type() const
void mooseError(Args &&... args) const
const SubChannel1PhaseProblem & _scm_problem
Reference to the subchannel problem.
const SubChannelMesh & _subchannel_mesh
Reference to the subchannel mesh.
Class that calculates turbulent mixing and sweep-flow coefficients for wire-wrapped triangular lattic...
std::vector< Real > _beta_pacio_center_edge
Pacio mixing parameter of the center-edge and edge-corner gaps per axial level, from the flow split o...
Real computeMixingParameter(const unsigned int i_gap, const unsigned int iz) const override
Computes the turbulent mixing coefficient for the local conditions around gap(i_gap) and axial level(...
void computeBulkMixingParameters() const override
Precomputes the mixing coefficients that depend only on the bulk flow.
const MooseEnum & _mixing_model
Chen-Todreas mixing-model parameterization.
Real _beta_1986
Cheng-Todreas (1986) mixing parameter of the gaps next to a center subchannel, which depends only on ...
Real computeSweepFlowMixingParameter(const unsigned int i_gap, const unsigned int iz) const override
Computes the wire-wrap sweep-flow coefficient for peripheral gaps.
SCMMixingChenTodreas(const InputParameters &parameters)
std::vector< Real > _beta_pacio_edge_corner
Real _beta_sweep
Cheng-Todreas (1986) sweep-flow coefficient of the peripheral gaps, which depends only on the geometr...
static InputParameters validParams()
bool _is_tri_lattice
Keep track of the lattice type.
void computeBlockMixingParameters(const unsigned int first_node, const unsigned int last_node) const override
Precomputes the mixing coefficients that depend on the flow solution of the axial levels first_node t...
const TriSubChannelMesh *const _tri_sch_mesh
Pointer to the triangular lattice mesh.
Real computePacioMixingParameter(const Real Xi, const Real Xj, const Real perimeter_ratio) const
Pacio mixing parameter of a gap between subchannels with flow splits Xi and Xj, including the ratio o...
Base class for turbulent mixing closures used in SCM.
static InputParameters validParams()
virtual unsigned int getNumOfChannels() const =0
Return the number of channels per layer.
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 EChannelType getSubchannelType(unsigned int index) const =0
Return the type of the subchannel for given subchannel 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 Real & getPinDiameter() const
Return undeformed Pin diameter.
Mesh class for triangular, edge and corner subchannels for hexagonal lattice fuel assemblies.
const Real & getWireDiameter() const
Return wire diameter.
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 unsigned int & getNumOfRings() const
Return the number of fuel-pin rings, counting the center pin as the first ring.
const Real pi