Flow-dependent heat transfer coefficients#

By default the heat transfer coefficient between the fluid and the storage is a constant parameter, "Heat transfer coefficient [W.m-2.K-1]". In practice it depends on the flow, so a value chosen at one flow rate does not hold at another. The option "heat transfer coefficient" computes it instead from a Nusselt number correlation, with the fluid properties from the fluid-property object and the geometry from the family:

  • "Gnielinski" in the finned-tube models, for the flow inside the tubes;

  • "Wakao-Kaguei" in the packed bed, for the flow around the capsules.

Every model outputs "Heat transfer coefficient [W.m-2.K-1]"; with a correlation it also outputs the surface Reynolds, Prandtl and Nusselt numbers.

import matplotlib.pyplot as plt
import numpy as np
import pybamm

import pytes

The Gnielinski law uses the laminar Nusselt number of a tube at a uniform wall temperature, 3.66, below a Reynolds number of 2300, the Gnielinski correlation above \(10^4\), and a linear interpolation in between.

Re = np.logspace(2.5, 5, 200)
fig, ax = plt.subplots(figsize=(6, 4))
for Pr in (1, 7, 50):
    Nu = pytes.submodels.Gnielinski(1).nusselt(pybamm.Vector(Re), pybamm.Scalar(Pr))
    ax.loglog(Re, Nu.evaluate(), label=f"Pr = {Pr}")
ax.set_xlabel("Reynolds number")
ax.set_ylabel("Nusselt number")
ax.legend()
plt.show()
../_images/b849b9d9ac1469d2f2a1f301799eb2770037cfb2c8bbc6fc483fa9a10e7c3241.png

The default finned-tube battery runs at a Reynolds number of about 4200, in the transition, where the correlation gives a coefficient about twice the default constant. Here the battery is discharged at the default flow rate and at a quarter of it, which is laminar.

t = np.linspace(0, 3600, 121)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
for option, style in [("constant", "--"), ("Gnielinski", "-")]:
    model = pytes.SharpFrontModel(options={"heat transfer coefficient": option})
    for factor in (1, 0.25):
        parameter_values = model.default_parameter_values
        parameter_values["Mass flow rate [kg.s-1]"] *= factor
        solution = pybamm.Simulation(model, parameter_values=parameter_values).solve(
            [0, t[-1]], t_interp=t
        )
        h = solution["Heat transfer coefficient [W.m-2.K-1]"].entries[0]
        label = f"{option}, {factor:g} x flow (h = {h:.0f} W/m2/K)"
        time = solution["Time [min]"].entries
        axes[0].plot(
            time, solution["Outlet temperature [degC]"].entries, style, label=label
        )
        axes[1].plot(time, solution["Power [kW]"].entries, style, label=label)
axes[0].set_ylabel("Outlet temperature [°C]")
axes[1].set_ylabel("Power [kW]")
for ax in axes:
    ax.set_xlabel("Time [min]")
axes[0].legend(fontsize=8)
plt.tight_layout()
plt.show()
../_images/0d6777492b259eb1efb7355af8cc1112636ea7ddab439716867e789a3e0c64c0.png

In the packed bed of Nallusamy et al. (2007), the Wakao and Kaguei correlation gives about 150 W.m-2.K-1, against the constant 100 of the default set, and fits the measured fluid temperature slightly better.

t_end = 160 * 60
t = np.linspace(0, t_end, 321)
t_data, T_data = np.loadtxt(
    "data/Nallusamy2007_HTF_1L.csv", delimiter=",", skiprows=1, unpack=True
)
fig, ax = plt.subplots(figsize=(6, 4))
for option in ("constant", "Wakao-Kaguei"):
    model = pytes.PackedBedModel(options={"heat transfer coefficient": option})
    solution = pybamm.Simulation(model).solve([0, t_end], t_interp=t)
    h = solution["Heat transfer coefficient [W.m-2.K-1]"].entries[0]
    ax.plot(
        solution["Time [min]"].entries,
        solution["Outlet temperature [degC]"].entries,
        label=f"{option} (h = {h:.0f} W/m2/K)",
    )
ax.plot(t_data, T_data, "ko", markersize=3, label="measured")
ax.set_xlabel("Time [min]")
ax.set_ylabel("Outlet temperature [°C]")
ax.legend()
plt.show()
../_images/c28b2adcf4cf35deb13e89d1809d59a87437d43c7033395d9824aaffbf3385f0.png

Under an experiment that adjusts the flow rate, such as a constant power discharge, the coefficient follows the flow.

model = pytes.SharpFrontModel(options={"heat transfer coefficient": "Gnielinski"})
experiment = pytes.experiments.Experiment(
    [pytes.experiments.step.Power(4000, inlet_temperature=283.15, duration=1800)]
)
solution = pytes.experiments.run_experiment(model, experiment)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
time = solution["Time [min]"].entries
axes[0].plot(time, solution["Mass flow rate [kg.s-1]"].entries)
axes[0].set_ylabel("Mass flow rate [kg/s]")
axes[1].plot(time, solution["Heat transfer coefficient [W.m-2.K-1]"].entries)
axes[1].set_ylabel("Heat transfer coefficient [W/m2/K]")
for ax in axes:
    ax.set_xlabel("Time [min]")
plt.tight_layout()
plt.show()
../_images/74919c1fadfff0fefac4bea58ef612f7e37f7b8d8f4e2cea6961390a10ff8c0d.png

A correlation of your own#

A correlation is only its Nusselt number: the family supplies the geometry (here the tube diameter, and the mean velocity in a tube) and the fluid-property object the properties. register_correlation adds a function of the Reynolds and Prandtl numbers, as PyBaMM symbols, as a value of the option for the models of its flow geometry, "tube" or "packed bed". PyTES adds its own correlations the same way. replace=True lets the cell run again.

Here the Dittus-Boelter correlation, for a fluid being heated, is compared with Gnielinski at four times the default flow, where both apply (\(Re > 10^4\)). conformance.check_model (in pytes.conformance) runs the contract on the model, energy conservation included, and returns its solution.

from pytes import conformance


def dittus_boelter(Re, Pr):
    return 0.023 * Re**0.8 * Pr**0.4


pytes.submodels.register_correlation(
    "Dittus-Boelter", dittus_boelter, flow="tube", replace=True
)
print(pytes.SharpFrontModel.allowed_options["heat transfer coefficient"])

for option in ("Gnielinski", "Dittus-Boelter"):
    model = pytes.SharpFrontModel(options={"heat transfer coefficient": option})
    parameter_values = model.default_parameter_values
    parameter_values["Mass flow rate [kg.s-1]"] *= 4
    solution = conformance.check_model(model, parameter_values)
    Re = solution["Surface Reynolds number"].entries[0]
    h = solution["Heat transfer coefficient [W.m-2.K-1]"].entries[0]
    print(f"{option}: Re = {Re:.0f}, h = {h:.0f} W/m2/K")
HeatTransferCoefficientOptions('tube'): ['constant', 'Gnielinski', 'Dittus-Boelter']
Gnielinski: Re = 16977, h = 12813 W/m2/K
Dittus-Boelter: Re = 16977, h = 12106 W/m2/K

A law that is not a Nusselt correlation, such as a wall resistance in series or a nonlinear law, goes to the coupling through the composition API instead (see composing models).