From 09b46887dda366e21ad73421875d6cacb45365ce Mon Sep 17 00:00:00 2001 From: Matthew Falcone Date: Thu, 19 Feb 2026 13:46:01 +0000 Subject: [PATCH 1/7] Add initial ambient convection objects to coaxial pipe --- include/components/CoaxialPipe1Phase.h | 10 +- src/components/CoaxialPipe1Phase.C | 142 +++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/include/components/CoaxialPipe1Phase.h b/include/components/CoaxialPipe1Phase.h index bd1b644..01ef039 100644 --- a/include/components/CoaxialPipe1Phase.h +++ b/include/components/CoaxialPipe1Phase.h @@ -27,4 +27,12 @@ class CoaxialPipe1Phase : public Coaxial1PhaseBase { void AddHeatTransferConnection(const std::string &flow_channel, const std::string &hs, const std::string &hs_side, const Real radius); -}; \ No newline at end of file + + // Add ambient convection to shell surface + void AddAmbientConvection(const Real T_ambient, const Real p_ambient, + MooseEnum ambient_properties); + + // Create constant function based on scalar value + FunctionName CreateFunctionFromValue(const std::string &suffix, + const Real value); +}; diff --git a/src/components/CoaxialPipe1Phase.C b/src/components/CoaxialPipe1Phase.C index b694d80..4fbed33 100644 --- a/src/components/CoaxialPipe1Phase.C +++ b/src/components/CoaxialPipe1Phase.C @@ -2,11 +2,19 @@ #include "Component1D.h" #include "FEProblemBase.h" #include "Factory.h" +#include "FlowModel.h" +#include "IdealGasFluidProperties.h" #include "InputParameters.h" +#include "MooseEnum.h" +#include "MooseError.h" #include "MooseTypes.h" #include "Registry.h" +#include "SinglePhaseFluidProperties.h" #include "THMProblem.h" +#include "libmesh/libmesh_common.h" +#include #include +#include registerMooseObject("ProteusApp", CoaxialPipe1Phase); @@ -101,6 +109,17 @@ InputParameters CoaxialPipe1Phase::validParams() { params.addParam( "outer_shell_Hw", "Manually specified HTC for annular pipe to shell."); + // Parameters for ambient convection + params.addParam("use_ambient_convection", false, + "Whether to apply ambient convection to the external " + "surface of the shell"); + params.addParam("T_ambient", 298, "Ambient temperature [K]."); + params.addParam("p_ambient", 101325, "Ambient pressure [Pa]."); + + MooseEnum ambient_properties("air", "air"); + params.addParam("ambient_properties", ambient_properties, + "Ambient fluid properties"); + // Add global parameter options params.addParam( "fp", "Global fluid properties. Overriden by inner_fp and outer_fp."); @@ -140,6 +159,12 @@ CoaxialPipe1Phase::CoaxialPipe1Phase(const InputParameters ¶ms) AddHeatTransferConnection("outer", "tube", "OUTER", outer_radius); AddHeatTransferConnection("outer", "shell", "INNER", params.get("shell_inner_radius")); + + if (getParam("use_ambient_convection")) { + AddAmbientConvection(getParam("T_ambient"), + getParam("p_ambient"), + getParam("ambient_properties")); + } } void CoaxialPipe1Phase::AddInnerPipe(const InputParameters ¶ms) { @@ -323,3 +348,120 @@ void CoaxialPipe1Phase::AddHeatTransferConnection( getTHMProblem().addComponent( class_name, name() + "_" + flow_channel + "_" + hs, ht_params); } + +void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, + const Real p_ambient, + MooseEnum ambient_properties) { + + std::unique_ptr fluid_props; + if (ambient_properties == "air") { + const std::string class_name = "IdealGasFluidProperties"; + auto params = _factory.getValidParams(class_name); + + fluid_props = std::make_unique(params); + } + + const Real mu = fluid_props->mu_from_p_T(p_ambient, T_ambient); + const Real cp = fluid_props->cp_from_p_T(p_ambient, T_ambient); + const Real k = fluid_props->k_from_p_T(p_ambient, T_ambient); + + auto widths = getParam>("shell_widths"); + const Real Dshell = std::accumulate(widths.begin(), widths.end(), 0.) + + getParam("shell_inner_radius"); + + // Create Rayleigh number property + { + const std::string class_name = "ADParsedMaterial"; + auto params = _factory.getValidParams(class_name); + params.set("property_name") = "Ra"; + params.set("expression") = + "abs(rho*beta*(Th-Ta)*D*D*D*g)/(mu*k/(rho*cp))"; + params.set>("functor_symbols") = {"rho", "beta" + "mu" + "k" + "cp"}; + + Real rho, drho_dt, drho_dp; + fluid_props->rho_from_p_T(p_ambient, T_ambient, rho, drho_dt, drho_dp); + const Real beta = -drho_dt / rho; + + std::vector functor_names{ + CreateFunctionFromValue("rho_conv", rho), + CreateFunctionFromValue("beta_conv", beta), + CreateFunctionFromValue("mu_conv", mu), + CreateFunctionFromValue("k_conv", k), + CreateFunctionFromValue("cp_conv", cp), + "T_solid"}; + + params.set>("functor_names") = functor_names; + params.set>("constant_names") = {"T_a", "D", "g"}; + + params.set>("constant_expressions") = { + std::to_string(T_ambient), std::to_string(Dshell), "-9.81"}; + + params.set>("boundary") = {name() + + "/shell:outer"}; + getTHMProblem().addMaterial(class_name, name() + "/Ra_conv", params); + } + + // Nusselt number + { + const std::string class_name = "ADParsedMaterial"; + auto params = _factory.getValidParams(class_name); + params.set("property_name") = "Nu"; + + params.set("expression") = + "pow(0.6 + (0.387*pow(Ra,1./6.))/pow(1 + pow(0.559/Pr,9./16.),8/27), " + "2)"; + + params.set>("constant_names") = {"Pr"}; + params.set>("constant_expressions") = { + std::to_string(mu * cp / k)}; + params.set>("material_property_names") = {"Ra"}; + params.set>("boundary") = {name() + + "/shell:outer"}; + getTHMProblem().addMaterial(class_name, name() + "/Nu_conv", params); + } + + // HTC + { + const std::string class_name = "ADParsedMaterial"; + auto params = _factory.getValidParams(class_name); + params.set("property_name") = "Hw"; + params.set>("boundary") = {name() + + "/shell:outer"}; + + params.set("expression") = "Nu*k/L"; + params.set>("constant_names") = {"k", "L"}; + params.set>("constant_expressions") = { + std::to_string(k), std::to_string(Dshell)}; + params.set>("material_property_names") = {"Nu"}; + getTHMProblem().addMaterial(class_name, name() + "/Hw_conv", params); + } + + // Ambient convection + { + const std::string class_name = "HSBoundaryAmbientConvection"; + auto params = _factory.getValidParams(class_name); + + params.set("T_ambient") = + CreateFunctionFromValue("T_ambient", T_ambient); + params.set>("boundary") = {name() + + "/shell:outer"}; + params.set("boundary") = {name() + "/shell"}; + params.set("htc_ambient") = "Hw"; + + getTHMProblem().addComponent(class_name, name() + "/conv_ambient", params); + } +} + +FunctionName +CoaxialPipe1Phase::CreateFunctionFromValue(const std::string &suffix, + const Real value) { + auto func_params = _factory.getValidParams("ConstantFunction"); + func_params.set("value") = value; + + auto func_name = name() + "_" + suffix; + getTHMProblem().addFunction("ConstantFunction", func_name, func_params); + return func_name; +} From 7b6a95d4fc882f9c8e81321c17af3e95e3e6af0b Mon Sep 17 00:00:00 2001 From: Matthew Falcone Date: Thu, 19 Feb 2026 13:54:21 +0000 Subject: [PATCH 2/7] Add thm_problem to conection components --- src/components/CoaxialPipe1Phase.C | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/CoaxialPipe1Phase.C b/src/components/CoaxialPipe1Phase.C index 4fbed33..07d4eda 100644 --- a/src/components/CoaxialPipe1Phase.C +++ b/src/components/CoaxialPipe1Phase.C @@ -373,6 +373,7 @@ void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, { const std::string class_name = "ADParsedMaterial"; auto params = _factory.getValidParams(class_name); + params.set("_thm_problem") = &getTHMProblem(); params.set("property_name") = "Ra"; params.set("expression") = "abs(rho*beta*(Th-Ta)*D*D*D*g)/(mu*k/(rho*cp))"; @@ -408,6 +409,7 @@ void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, { const std::string class_name = "ADParsedMaterial"; auto params = _factory.getValidParams(class_name); + params.set("_thm_problem") = &getTHMProblem(); params.set("property_name") = "Nu"; params.set("expression") = @@ -427,6 +429,7 @@ void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, { const std::string class_name = "ADParsedMaterial"; auto params = _factory.getValidParams(class_name); + params.set("_thm_problem") = &getTHMProblem(); params.set("property_name") = "Hw"; params.set>("boundary") = {name() + "/shell:outer"}; @@ -443,6 +446,7 @@ void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, { const std::string class_name = "HSBoundaryAmbientConvection"; auto params = _factory.getValidParams(class_name); + params.set("_thm_problem") = &getTHMProblem(); params.set("T_ambient") = CreateFunctionFromValue("T_ambient", T_ambient); From 5c9d70440628baceb36a511d34c0b97b2c2b06d6 Mon Sep 17 00:00:00 2001 From: Matthew Falcone Date: Thu, 19 Feb 2026 14:00:30 +0000 Subject: [PATCH 3/7] Set fe_problem_base parameter --- src/components/CoaxialPipe1Phase.C | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/CoaxialPipe1Phase.C b/src/components/CoaxialPipe1Phase.C index 07d4eda..c5b5c54 100644 --- a/src/components/CoaxialPipe1Phase.C +++ b/src/components/CoaxialPipe1Phase.C @@ -373,7 +373,7 @@ void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, { const std::string class_name = "ADParsedMaterial"; auto params = _factory.getValidParams(class_name); - params.set("_thm_problem") = &getTHMProblem(); + params.set("_fe_problem_base") = &getTHMProblem(); params.set("property_name") = "Ra"; params.set("expression") = "abs(rho*beta*(Th-Ta)*D*D*D*g)/(mu*k/(rho*cp))"; @@ -409,7 +409,7 @@ void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, { const std::string class_name = "ADParsedMaterial"; auto params = _factory.getValidParams(class_name); - params.set("_thm_problem") = &getTHMProblem(); + params.set("_fe_problem_base") = &getTHMProblem(); params.set("property_name") = "Nu"; params.set("expression") = @@ -429,7 +429,7 @@ void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, { const std::string class_name = "ADParsedMaterial"; auto params = _factory.getValidParams(class_name); - params.set("_thm_problem") = &getTHMProblem(); + params.set("_fe_problem_base") = &getTHMProblem(); params.set("property_name") = "Hw"; params.set>("boundary") = {name() + "/shell:outer"}; From 7d3d973a08ce209279239046c3ddbcf3f3786a4c Mon Sep 17 00:00:00 2001 From: Matthew Falcone Date: Fri, 20 Feb 2026 11:37:42 +0000 Subject: [PATCH 4/7] Make ambient convection use Functor Materials --- include/components/CoaxialPipe1Phase.h | 7 +- src/components/CoaxialPipe1Phase.C | 142 +++++++++++-------------- 2 files changed, 70 insertions(+), 79 deletions(-) diff --git a/include/components/CoaxialPipe1Phase.h b/include/components/CoaxialPipe1Phase.h index 01ef039..863fa4f 100644 --- a/include/components/CoaxialPipe1Phase.h +++ b/include/components/CoaxialPipe1Phase.h @@ -23,16 +23,19 @@ class CoaxialPipe1Phase : public Coaxial1PhaseBase { // Add solid shell around annulus void AddSolidShell(const InputParameters ¶ms); + void addMooseObjects() override; + // Add solid-fluid connection based on component names void AddHeatTransferConnection(const std::string &flow_channel, const std::string &hs, const std::string &hs_side, const Real radius); // Add ambient convection to shell surface - void AddAmbientConvection(const Real T_ambient, const Real p_ambient, - MooseEnum ambient_properties); + void AddAmbientConvection(); // Create constant function based on scalar value FunctionName CreateFunctionFromValue(const std::string &suffix, const Real value); + + const Real _T_ambient, _p_ambient; }; diff --git a/src/components/CoaxialPipe1Phase.C b/src/components/CoaxialPipe1Phase.C index c5b5c54..7a7c026 100644 --- a/src/components/CoaxialPipe1Phase.C +++ b/src/components/CoaxialPipe1Phase.C @@ -3,16 +3,12 @@ #include "FEProblemBase.h" #include "Factory.h" #include "FlowModel.h" -#include "IdealGasFluidProperties.h" #include "InputParameters.h" #include "MooseEnum.h" -#include "MooseError.h" #include "MooseTypes.h" #include "Registry.h" -#include "SinglePhaseFluidProperties.h" +#include "SubProblem.h" #include "THMProblem.h" -#include "libmesh/libmesh_common.h" -#include #include #include @@ -141,7 +137,8 @@ InputParameters CoaxialPipe1Phase::validParams() { } CoaxialPipe1Phase::CoaxialPipe1Phase(const InputParameters ¶ms) - : Coaxial1PhaseBase(params) { + : Coaxial1PhaseBase(params), _T_ambient(getParam("T_ambient")), + _p_ambient(getParam("p_ambient")) { // Add components AddInnerPipe(params); AddOuterAnnulus(params); @@ -161,9 +158,7 @@ CoaxialPipe1Phase::CoaxialPipe1Phase(const InputParameters ¶ms) params.get("shell_inner_radius")); if (getParam("use_ambient_convection")) { - AddAmbientConvection(getParam("T_ambient"), - getParam("p_ambient"), - getParam("ambient_properties")); + AddAmbientConvection(); } } @@ -349,21 +344,42 @@ void CoaxialPipe1Phase::AddHeatTransferConnection( class_name, name() + "_" + flow_channel + "_" + hs, ht_params); } -void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, - const Real p_ambient, - MooseEnum ambient_properties) { +void CoaxialPipe1Phase::AddAmbientConvection() { - std::unique_ptr fluid_props; - if (ambient_properties == "air") { - const std::string class_name = "IdealGasFluidProperties"; + // Ambient convection + { + const std::string class_name = "HSBoundaryAmbientConvection"; auto params = _factory.getValidParams(class_name); + params.set("_thm_problem") = &getTHMProblem(); + + params.set("T_ambient") = + CreateFunctionFromValue("T_ambient", _T_ambient); + params.set>("boundary") = {name() + + "/shell:outer"}; + params.set("hs") = {name() + "/shell"}; + params.set("htc_ambient") = "Hw"; - fluid_props = std::make_unique(params); + getTHMProblem().addComponent(class_name, name() + "/conv_ambient", params); + } +} + +void CoaxialPipe1Phase::addMooseObjects() { + if (!getParam("use_ambient_convection")) + return; + + Real mu, k, cp, rho, R, gamma, beta; + if (getParam("ambient_properties") == "air") { + mu = 1.823e-05; + k = 0.02568; + R = 8.31446261815324 / 0.0289647; + gamma = 1.4; + cp = gamma * R / (gamma - 1); + rho = _p_ambient / (R * _T_ambient); + beta = 1 / _T_ambient; } - const Real mu = fluid_props->mu_from_p_T(p_ambient, T_ambient); - const Real cp = fluid_props->cp_from_p_T(p_ambient, T_ambient); - const Real k = fluid_props->k_from_p_T(p_ambient, T_ambient); + mooseInfo("Ambient properties\n", "\tk: ", k, "\n\tmu: ", mu, "\n\tcp: ", cp, + "\n\tbeta: ", beta, "\n\trho: ", rho, "\n"); auto widths = getParam>("shell_widths"); const Real Dshell = std::accumulate(widths.begin(), widths.end(), 0.) + @@ -371,92 +387,64 @@ void CoaxialPipe1Phase::AddAmbientConvection(const Real T_ambient, // Create Rayleigh number property { - const std::string class_name = "ADParsedMaterial"; + const std::string class_name = "ADParsedFunctorMaterial"; auto params = _factory.getValidParams(class_name); params.set("_fe_problem_base") = &getTHMProblem(); params.set("property_name") = "Ra"; params.set("expression") = - "abs(rho*beta*(Th-Ta)*D*D*D*g)/(mu*k/(rho*cp))"; - params.set>("functor_symbols") = {"rho", "beta" - "mu" - "k" - "cp"}; - - Real rho, drho_dt, drho_dp; - fluid_props->rho_from_p_T(p_ambient, T_ambient, rho, drho_dt, drho_dp); - const Real beta = -drho_dt / rho; - - std::vector functor_names{ - CreateFunctionFromValue("rho_conv", rho), - CreateFunctionFromValue("beta_conv", beta), - CreateFunctionFromValue("mu_conv", mu), - CreateFunctionFromValue("k_conv", k), - CreateFunctionFromValue("cp_conv", cp), - "T_solid"}; - - params.set>("functor_names") = functor_names; - params.set>("constant_names") = {"T_a", "D", "g"}; - - params.set>("constant_expressions") = { - std::to_string(T_ambient), std::to_string(Dshell), "-9.81"}; - - params.set>("boundary") = {name() + - "/shell:outer"}; + "rho*beta*(T_solid-T_a)*D*D*D*g/(mu*k/(rho*cp))"; + params.set>("functor_symbols") = { + "rho", "beta", "mu", "k", "cp", "T_solid", "T_a", "D", "g"}; + + std::vector functor_names{ + std::to_string(rho), std::to_string(beta), std::to_string(mu), + std::to_string(k), std::to_string(cp), "T_solid", + std::to_string(_T_ambient), std::to_string(Dshell), "9.81"}; + params.set>("functor_names") = functor_names; + params.set>("block") = { + name() + + "/shell:" + getParam>("shell_names").back()}; + getTHMProblem().addMaterial(class_name, name() + "/Ra_conv", params); } // Nusselt number { - const std::string class_name = "ADParsedMaterial"; + const std::string class_name = "ADParsedFunctorMaterial"; auto params = _factory.getValidParams(class_name); params.set("_fe_problem_base") = &getTHMProblem(); params.set("property_name") = "Nu"; params.set("expression") = - "pow(0.6 + (0.387*pow(Ra,1./6.))/pow(1 + pow(0.559/Pr,9./16.),8/27), " + "pow(0.825 + (0.387*pow(Ra,1./6.))/pow(1 + pow(0.492/Pr,9./16.),8/27), " "2)"; - params.set>("constant_names") = {"Pr"}; - params.set>("constant_expressions") = { - std::to_string(mu * cp / k)}; - params.set>("material_property_names") = {"Ra"}; - params.set>("boundary") = {name() + - "/shell:outer"}; + params.set>("functor_symbols") = {"Pr", "Ra"}; + params.set>("functor_names") = { + std::to_string(mu * cp / k), "Ra"}; + + params.set>("block") = { + name() + + "/shell:" + getParam>("shell_names").back()}; getTHMProblem().addMaterial(class_name, name() + "/Nu_conv", params); } // HTC { - const std::string class_name = "ADParsedMaterial"; + const std::string class_name = "ADParsedFunctorMaterial"; auto params = _factory.getValidParams(class_name); params.set("_fe_problem_base") = &getTHMProblem(); params.set("property_name") = "Hw"; - params.set>("boundary") = {name() + - "/shell:outer"}; + params.set>("block") = { + name() + + "/shell:" + getParam>("shell_names").back()}; params.set("expression") = "Nu*k/L"; - params.set>("constant_names") = {"k", "L"}; - params.set>("constant_expressions") = { - std::to_string(k), std::to_string(Dshell)}; - params.set>("material_property_names") = {"Nu"}; + params.set>("functor_symbols") = {"k", "L", "Nu"}; + params.set>("functor_names") = { + std::to_string(k), std::to_string(Dshell), "Nu"}; getTHMProblem().addMaterial(class_name, name() + "/Hw_conv", params); } - - // Ambient convection - { - const std::string class_name = "HSBoundaryAmbientConvection"; - auto params = _factory.getValidParams(class_name); - params.set("_thm_problem") = &getTHMProblem(); - - params.set("T_ambient") = - CreateFunctionFromValue("T_ambient", T_ambient); - params.set>("boundary") = {name() + - "/shell:outer"}; - params.set("boundary") = {name() + "/shell"}; - params.set("htc_ambient") = "Hw"; - - getTHMProblem().addComponent(class_name, name() + "/conv_ambient", params); - } } FunctionName From edded81d01b7a9eb585785cbbaea9ef3dcf7cfb7 Mon Sep 17 00:00:00 2001 From: Matthew Falcone Date: Fri, 4 Sep 2026 11:55:45 +0100 Subject: [PATCH 5/7] Add vertical and horizontal comvection --- src/components/CoaxialPipe1Phase.C | 50 ++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/src/components/CoaxialPipe1Phase.C b/src/components/CoaxialPipe1Phase.C index 7a7c026..99733b6 100644 --- a/src/components/CoaxialPipe1Phase.C +++ b/src/components/CoaxialPipe1Phase.C @@ -111,6 +111,9 @@ InputParameters CoaxialPipe1Phase::validParams() { "surface of the shell"); params.addParam("T_ambient", 298, "Ambient temperature [K]."); params.addParam("p_ambient", 101325, "Ambient pressure [Pa]."); + params.addParam( + "vertical_vector", RealVectorValue{0., 1., 0.}, + "Vertical vector for determining external convection correlation."); MooseEnum ambient_properties("air", "air"); params.addParam("ambient_properties", ambient_properties, @@ -367,6 +370,33 @@ void CoaxialPipe1Phase::addMooseObjects() { if (!getParam("use_ambient_convection")) return; + auto vertical_vect = getParam("vertical_vector"); + if (vertical_vect.norm() < 1e-8) + mooseError("The vertical vector must have magnitude greater than 0"); + + auto v = getParam("orientation"); + Real l{0.}; + Real dot_prod{fabs(v * vertical_vect / (v.norm() * vertical_vect.norm()))}; + std::string expression; + + if (dot_prod < 1e-8) { // horizontal pipe + auto widths = getParam>("shell_widths"); + l = 2. * std::accumulate(widths.begin(), widths.end(), + getParam("shell_inner_radius")); + expression = "pow(0.6 + (0.387*pow(Ra,1./6.))/pow(1 + " + "pow(0.559/Pr,9./16.),8./27.), 2)"; + + } else if (fabs(dot_prod - 1) < 1e-8) { // vertical pipe + auto lengths = getParam>("length"); + l = std::accumulate(lengths.begin(), lengths.end(), 0.); + expression = "pow(0.825 + (0.387*pow(Ra,1./6.))/pow(1 + " + "pow(0.492/Pr,9./16.),8/27), 2)"; + } else { + mooseError("Ambient convection only usable for vertical " + "and horizontal pipes."); + return; + } + Real mu, k, cp, rho, R, gamma, beta; if (getParam("ambient_properties") == "air") { mu = 1.823e-05; @@ -381,10 +411,6 @@ void CoaxialPipe1Phase::addMooseObjects() { mooseInfo("Ambient properties\n", "\tk: ", k, "\n\tmu: ", mu, "\n\tcp: ", cp, "\n\tbeta: ", beta, "\n\trho: ", rho, "\n"); - auto widths = getParam>("shell_widths"); - const Real Dshell = std::accumulate(widths.begin(), widths.end(), 0.) + - getParam("shell_inner_radius"); - // Create Rayleigh number property { const std::string class_name = "ADParsedFunctorMaterial"; @@ -392,14 +418,14 @@ void CoaxialPipe1Phase::addMooseObjects() { params.set("_fe_problem_base") = &getTHMProblem(); params.set("property_name") = "Ra"; params.set("expression") = - "rho*beta*(T_solid-T_a)*D*D*D*g/(mu*k/(rho*cp))"; + "rho*beta*abs(T_solid-T_a)*L*L*L*g/(mu*k/(rho*cp))"; params.set>("functor_symbols") = { - "rho", "beta", "mu", "k", "cp", "T_solid", "T_a", "D", "g"}; + "rho", "beta", "mu", "k", "cp", "T_solid", "T_a", "L", "g"}; std::vector functor_names{ - std::to_string(rho), std::to_string(beta), std::to_string(mu), - std::to_string(k), std::to_string(cp), "T_solid", - std::to_string(_T_ambient), std::to_string(Dshell), "9.81"}; + std::to_string(rho), std::to_string(beta), std::to_string(mu), + std::to_string(k), std::to_string(cp), "T_solid", + std::to_string(_T_ambient), std::to_string(l), "9.81"}; params.set>("functor_names") = functor_names; params.set>("block") = { name() + @@ -415,9 +441,7 @@ void CoaxialPipe1Phase::addMooseObjects() { params.set("_fe_problem_base") = &getTHMProblem(); params.set("property_name") = "Nu"; - params.set("expression") = - "pow(0.825 + (0.387*pow(Ra,1./6.))/pow(1 + pow(0.492/Pr,9./16.),8/27), " - "2)"; + params.set("expression") = expression; params.set>("functor_symbols") = {"Pr", "Ra"}; params.set>("functor_names") = { @@ -442,7 +466,7 @@ void CoaxialPipe1Phase::addMooseObjects() { params.set("expression") = "Nu*k/L"; params.set>("functor_symbols") = {"k", "L", "Nu"}; params.set>("functor_names") = { - std::to_string(k), std::to_string(Dshell), "Nu"}; + std::to_string(k), std::to_string(l), "Nu"}; getTHMProblem().addMaterial(class_name, name() + "/Hw_conv", params); } } From 1f50d0cea6b1b7ccaf1ec2a2fa967c40d61d0b62 Mon Sep 17 00:00:00 2001 From: Matthew Falcone Date: Fri, 4 Sep 2026 12:05:41 +0100 Subject: [PATCH 6/7] Use gravity vector to determine orientation --- src/components/CoaxialPipe1Phase.C | 22 ++++++++++++---------- test/tests/components/coaxial_pipe/tests | 2 +- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/CoaxialPipe1Phase.C b/src/components/CoaxialPipe1Phase.C index 99733b6..821bbd0 100644 --- a/src/components/CoaxialPipe1Phase.C +++ b/src/components/CoaxialPipe1Phase.C @@ -111,9 +111,6 @@ InputParameters CoaxialPipe1Phase::validParams() { "surface of the shell"); params.addParam("T_ambient", 298, "Ambient temperature [K]."); params.addParam("p_ambient", 101325, "Ambient pressure [Pa]."); - params.addParam( - "vertical_vector", RealVectorValue{0., 1., 0.}, - "Vertical vector for determining external convection correlation."); MooseEnum ambient_properties("air", "air"); params.addParam("ambient_properties", ambient_properties, @@ -370,13 +367,13 @@ void CoaxialPipe1Phase::addMooseObjects() { if (!getParam("use_ambient_convection")) return; - auto vertical_vect = getParam("vertical_vector"); - if (vertical_vect.norm() < 1e-8) + auto gravity = getParam("gravity_vector"); + if (gravity.norm() < 1e-8) mooseError("The vertical vector must have magnitude greater than 0"); auto v = getParam("orientation"); Real l{0.}; - Real dot_prod{fabs(v * vertical_vect / (v.norm() * vertical_vect.norm()))}; + Real dot_prod{fabs(v * gravity / (v.norm() * gravity.norm()))}; std::string expression; if (dot_prod < 1e-8) { // horizontal pipe @@ -422,10 +419,15 @@ void CoaxialPipe1Phase::addMooseObjects() { params.set>("functor_symbols") = { "rho", "beta", "mu", "k", "cp", "T_solid", "T_a", "L", "g"}; - std::vector functor_names{ - std::to_string(rho), std::to_string(beta), std::to_string(mu), - std::to_string(k), std::to_string(cp), "T_solid", - std::to_string(_T_ambient), std::to_string(l), "9.81"}; + std::vector functor_names{std::to_string(rho), + std::to_string(beta), + std::to_string(mu), + std::to_string(k), + std::to_string(cp), + "T_solid", + std::to_string(_T_ambient), + std::to_string(l), + std::to_string(gravity.norm())}; params.set>("functor_names") = functor_names; params.set>("block") = { name() + diff --git a/test/tests/components/coaxial_pipe/tests b/test/tests/components/coaxial_pipe/tests index f6b8225..f60abb9 100644 --- a/test/tests/components/coaxial_pipe/tests +++ b/test/tests/components/coaxial_pipe/tests @@ -31,7 +31,7 @@ [test] type= PythonUnitTest input = test.py - test_case = TestCoaxialPipe.test_energy_balance_outer + test_case = TestCoaxialPipe.test_energy_balance_inner heavy=true prereq = energy_balance_inner/run [] From fd15e78a75d7f0ab4c8d2c7ffbfbcf02ee6f2dc5 Mon Sep 17 00:00:00 2001 From: Matthew Falcone Date: Fri, 4 Sep 2026 12:43:55 +0100 Subject: [PATCH 7/7] Add tests for ambient convection --- src/components/CoaxialPipe1Phase.C | 24 ++-- .../coaxial_pipe/ambient_convection.i | 119 ++++++++++++++++++ test/tests/components/coaxial_pipe/test.py | 68 ++++++++++ test/tests/components/coaxial_pipe/tests | 86 +++++++++++++ 4 files changed, 286 insertions(+), 11 deletions(-) create mode 100644 test/tests/components/coaxial_pipe/ambient_convection.i diff --git a/src/components/CoaxialPipe1Phase.C b/src/components/CoaxialPipe1Phase.C index 821bbd0..8286b43 100644 --- a/src/components/CoaxialPipe1Phase.C +++ b/src/components/CoaxialPipe1Phase.C @@ -1,5 +1,6 @@ #include "CoaxialPipe1Phase.h" #include "Component1D.h" +#include "Conversion.h" #include "FEProblemBase.h" #include "Factory.h" #include "FlowModel.h" @@ -419,15 +420,16 @@ void CoaxialPipe1Phase::addMooseObjects() { params.set>("functor_symbols") = { "rho", "beta", "mu", "k", "cp", "T_solid", "T_a", "L", "g"}; - std::vector functor_names{std::to_string(rho), - std::to_string(beta), - std::to_string(mu), - std::to_string(k), - std::to_string(cp), - "T_solid", - std::to_string(_T_ambient), - std::to_string(l), - std::to_string(gravity.norm())}; + std::vector functor_names{ + Moose::stringifyExact(rho), + Moose::stringifyExact(beta), + Moose::stringifyExact(mu), + Moose::stringifyExact(k), + Moose::stringifyExact(cp), + "T_solid", + Moose::stringifyExact(_T_ambient), + Moose::stringifyExact(l), + Moose::stringifyExact(gravity.norm())}; params.set>("functor_names") = functor_names; params.set>("block") = { name() + @@ -447,7 +449,7 @@ void CoaxialPipe1Phase::addMooseObjects() { params.set>("functor_symbols") = {"Pr", "Ra"}; params.set>("functor_names") = { - std::to_string(mu * cp / k), "Ra"}; + Moose::stringifyExact(mu * cp / k), "Ra"}; params.set>("block") = { name() + @@ -468,7 +470,7 @@ void CoaxialPipe1Phase::addMooseObjects() { params.set("expression") = "Nu*k/L"; params.set>("functor_symbols") = {"k", "L", "Nu"}; params.set>("functor_names") = { - std::to_string(k), std::to_string(l), "Nu"}; + Moose::stringifyExact(k), Moose::stringifyExact(l), "Nu"}; getTHMProblem().addMaterial(class_name, name() + "/Hw_conv", params); } } diff --git a/test/tests/components/coaxial_pipe/ambient_convection.i b/test/tests/components/coaxial_pipe/ambient_convection.i new file mode 100644 index 0000000..50a5668 --- /dev/null +++ b/test/tests/components/coaxial_pipe/ambient_convection.i @@ -0,0 +1,119 @@ +# Ambient convection correlation test + +T_solid = 350 +T_ambient = 300 + +[GlobalParams] + initial_p = 1e5 + initial_T = ${T_solid} + initial_vel = 0.1 + closures = thm_closures + fp = fluid +[] + +[FluidProperties] + [fluid] + type = SimpleFluidProperties + cv = 4000 + [] +[] + +[SolidProperties] + [solid] + type = ThermalFunctionSolidProperties + cp = 500 + k = 10 + rho = 1000 + [] +[] + +[Closures] + [thm_closures] + type = Closures1PhaseTHM + [] +[] + +[Components] + [inlet_inner] + type = InletMassFlowRateTemperature1Phase + T = ${T_solid} + m_dot = 0.1 + input = coaxial/inner:in + [] + [inlet_outer] + type = InletMassFlowRateTemperature1Phase + T = ${T_solid} + m_dot = 0.1 + input = coaxial/outer:in + [] + [coaxial] + type = CoaxialPipe1Phase + position = '0 0 0' + orientation = '1 0 0' + length = '0.4 0.6' + n_elems = '1 1' + axial_region_names = 'section_1 section_2' + + tube_inner_radius = 0.025 + tube_names = tube + tube_widths = 0.025 + tube_materials = solid + tube_n_elems = 1 + tube_T_ref = ${T_solid} + + shell_inner_radius = 0.075 + shell_names = shell + shell_widths = 0.025 + shell_materials = solid + shell_n_elems = 1 + shell_T_ref = ${T_solid} + + use_ambient_convection = true + T_ambient = ${T_ambient} + [] + [outlet_inner] + type = Outlet1Phase + input = coaxial/inner:out + p = 1e5 + [] + [outlet_outer] + type = Outlet1Phase + input = coaxial/outer:out + p = 1e5 + [] +[] + +[Postprocessors] + [Ra] + type = ADElementExtremeFunctorValue + functor = Ra + block = coaxial/shell:shell + execute_on = INITIAL + [] + [Nu] + type = ADElementExtremeFunctorValue + functor = Nu + block = coaxial/shell:shell + execute_on = INITIAL + [] + [Hw] + type = ADElementExtremeFunctorValue + functor = Hw + block = coaxial/shell:shell + execute_on = INITIAL + [] +[] + +[Problem] + solve = false +[] + +[Executioner] + type = Steady +[] + +[Outputs] + csv = true + execute_on = INITIAL + show = 'Ra Nu Hw' +[] diff --git a/test/tests/components/coaxial_pipe/test.py b/test/tests/components/coaxial_pipe/test.py index c21470a..4ca3675 100644 --- a/test/tests/components/coaxial_pipe/test.py +++ b/test/tests/components/coaxial_pipe/test.py @@ -3,6 +3,34 @@ import unittest import numpy as np + +def ambient_convection_values(T_solid, T_ambient, length, gravity, vertical): + """Independently evaluate the ambient convection correlations.""" + mu = 1.823e-5 + k = 0.02568 + gas_constant = 8.31446261815324 / 0.0289647 + gamma = 1.4 + cp = gamma * gas_constant / (gamma - 1) + rho = 101325 / (gas_constant * T_ambient) + beta = 1 / T_ambient + + prandtl = mu * cp / k + thermal_diffusivity = k / (rho * cp) + rayleigh = (rho * beta * abs(T_solid - T_ambient) * length**3 * gravity + / (mu * thermal_diffusivity)) + + base, prandtl_coefficient = (0.825, 0.492) if vertical else (0.6, 0.559) + nusselt = (base + 0.387 * rayleigh ** (1 / 6) + / (1 + (prandtl_coefficient / prandtl) ** (9 / 16)) ** (8 / 27)) ** 2 + + return np.array([nusselt * k / length, nusselt, rayleigh]) + + +def read_ambient_convection_output(file_name): + """Read Hw, Nu, and Ra from an ambient convection CSV output.""" + data = np.genfromtxt(file_name, delimiter=",", names=True) + return np.array([data["Hw"], data["Nu"], data["Ra"]]) + class TestCoaxialPipe(unittest.TestCase): """Test class for the coaxial pipe component.""" def test_energy_balance(self): @@ -74,3 +102,43 @@ def test_energy_balance_outer(self): rel_diff = abs(total_energy - q)/q assert rel_diff < 0.00048, f"Rel. energy difference greater than 0.00046: {rel_diff}" + + def assert_ambient_convection_values( + self, file_name, T_solid, T_ambient, length, gravity, vertical + ): + """Compare computed correlation values against an independent evaluation.""" + actual = read_ambient_convection_output(file_name) + expected = ambient_convection_values( + T_solid, T_ambient, length, gravity, vertical + ) + np.testing.assert_allclose(actual, expected, rtol=1e-12) + + def test_ambient_convection_horizontal(self): + """Checks the diameter-based horizontal cylinder correlation.""" + self.assert_ambient_convection_values( + "ambient_convection_horizontal.csv", 350, 300, 0.2, 9.81, False + ) + + def test_ambient_convection_vertical(self): + """Checks the total-length-based vertical correlation.""" + self.assert_ambient_convection_values( + "ambient_convection_vertical.csv", 350, 300, 1.0, 9.81, True + ) + + def test_ambient_convection_vertical_reversed(self): + """Checks that reversing the vertical direction does not change the result.""" + self.assert_ambient_convection_values( + "ambient_convection_vertical_reversed.csv", 350, 300, 1.0, 9.81, True + ) + + def test_ambient_convection_cold_surface(self): + """Checks a pipe colder than its ambient environment.""" + self.assert_ambient_convection_values( + "ambient_convection_cold_surface.csv", 250, 300, 0.2, 9.81, False + ) + + def test_ambient_convection_gravity_magnitude(self): + """Checks that the configured gravity magnitude is used.""" + self.assert_ambient_convection_values( + "ambient_convection_gravity_magnitude.csv", 350, 300, 0.2, 4.905, False + ) diff --git a/test/tests/components/coaxial_pipe/tests b/test/tests/components/coaxial_pipe/tests index f60abb9..35f945c 100644 --- a/test/tests/components/coaxial_pipe/tests +++ b/test/tests/components/coaxial_pipe/tests @@ -50,4 +50,90 @@ [] requirement = "Checks that outer pipe conserved energy in isolation" [] + [ambient_convection] + [horizontal] + [run] + type = RunApp + input = ambient_convection.i + cli_args = "Outputs/file_base=ambient_convection_horizontal" + [] + [test] + type = PythonUnitTest + input = test.py + test_case = TestCoaxialPipe.test_ambient_convection_horizontal + prereq = ambient_convection/horizontal/run + [] + requirement = "The system shall compute ambient natural convection for a horizontal coaxial pipe using its outer diameter." + [] + [vertical] + [run] + type = RunApp + input = ambient_convection.i + cli_args = "Components/coaxial/orientation='0 0 1' Outputs/file_base=ambient_convection_vertical" + [] + [test] + type = PythonUnitTest + input = test.py + test_case = TestCoaxialPipe.test_ambient_convection_vertical + prereq = ambient_convection/vertical/run + [] + requirement = "The system shall compute ambient natural convection for a vertical coaxial pipe using its total axial length." + [] + [vertical_reversed] + [run] + type = RunApp + input = ambient_convection.i + cli_args = "Components/coaxial/orientation='0 0 -1' Outputs/file_base=ambient_convection_vertical_reversed" + [] + [test] + type = PythonUnitTest + input = test.py + test_case = TestCoaxialPipe.test_ambient_convection_vertical_reversed + prereq = ambient_convection/vertical_reversed/run + [] + requirement = "The system shall compute identical ambient convection for either vertical pipe direction." + [] + [cold_surface] + [run] + type = RunApp + input = ambient_convection.i + cli_args = "T_solid=250 Outputs/file_base=ambient_convection_cold_surface" + [] + [test] + type = PythonUnitTest + input = test.py + test_case = TestCoaxialPipe.test_ambient_convection_cold_surface + prereq = ambient_convection/cold_surface/run + [] + requirement = "The system shall compute finite ambient convection properties when the pipe is colder than its environment." + [] + [gravity_magnitude] + [run] + type = RunApp + input = ambient_convection.i + cli_args = "Components/coaxial/gravity_vector='0 0 -4.905' Outputs/file_base=ambient_convection_gravity_magnitude" + [] + [test] + type = PythonUnitTest + input = test.py + test_case = TestCoaxialPipe.test_ambient_convection_gravity_magnitude + prereq = ambient_convection/gravity_magnitude/run + [] + requirement = "The system shall use the configured gravity magnitude in the ambient convection correlation." + [] + [oblique] + type = RunException + input = ambient_convection.i + cli_args = "Components/coaxial/orientation='1 0 1'" + expect_err = "Ambient convection only usable for vertical and horizontal pipes." + requirement = "The system shall reject ambient convection for an oblique coaxial pipe." + [] + [zero_gravity] + type = RunException + input = ambient_convection.i + cli_args = "Components/coaxial/gravity_vector='0 0 0'" + expect_err = "magnitude greater than 0" + requirement = "The system shall reject ambient convection when gravity has zero magnitude." + [] + [] []