Source code for pytes.models.packed_bed.packed_bed

#
# Packed-bed model
#
"""Model of a packed bed of PCM capsules, with the capsules resolved along the bed."""

import pybamm

from ... import compat
from ...submodels import (
    AdvectedFluid,
    DeviceEnergy,
    EnthalpyStorage,
    FlowPath,
    HeatTransferCoefficientOptions,
    Jeffreson,
    LumpedGeometry,
    LumpedStorage,
    PerAreaTotals,
    StorageGeometry,
    WallCoupling,
)
from ..base_model import BaseModel
from .parameter_values import get_parameter_values
from .parameters import PackedBedParameters


[docs] class PackedBedModel(BaseModel): """ Packed bed of spherical PCM capsules, with the fluid flowing through the voids. At each point along the bed, one capsule stands for all those at that position: heat flows by conduction across it ("capsule", radially from its centre) and into the fluid at its surface. See :mod:`pytes.models.packed_bed.parameters` for the geometry and the assumptions. Submodels: - "fluid": :class:`pytes.submodels.AdvectedFluid`, through the voids (flow area ``eps A``, a single flow path); - "storage": :class:`pytes.submodels.EnthalpyStorage` of the PCM in a capsule (spherical), exchanging heat at its surface; or, for a lumped capsule, :class:`pytes.submodels.LumpedStorage`; - "coupling": :class:`pytes.submodels.WallCoupling`, the fluid and the capsule surfaces (``3 (1 - eps) / R`` of surface per unit bed volume); - "device energy": :class:`pytes.submodels.DeviceEnergy`; - "per-area totals": :class:`pytes.submodels.PerAreaTotals`, the device totals per unit bed cross-sectional area (e.g. "Power per unit area [W.m-2]"). In this family the cross-section averages along the bed (e.g. "Storage cross-section average temperature [K]") are the capsules' volume averages: every capsule at a given height is the same. Options: - "storage along flow path": "resolved" (default), a capsule at every point along the bed; or "averaged", one capsule standing for the average along the bed, exchanging heat with the fluid averaged along it (DESIGN D3, D4; the "reduced model" of the `encapsulated-LTES <https://github.com/mmsg-warwick/encapsulated-LTES>`_ repository). - "storage element": "resolved" (default), the temperature resolved across each capsule; "lumped", a uniform temperature in each capsule (issue #5), much cheaper, and accurate when the capsule's Biot number is small; or "lumped with Jeffreson", a lumped capsule whose heat transfer coefficient is corrected for the conduction inside it (:class:`pytes.submodels.Jeffreson`), whichever coefficient the "heat transfer coefficient" option gives. - "heat transfer coefficient": "constant" (default), "Heat transfer coefficient [W.m-2.K-1]" (:class:`pytes.submodels.NewtonCooling`); or a Nusselt correlation for the flow through the bed, by name: "Wakao-Kaguei" (:class:`pytes.submodels.WakaoKaguei`, issue #6), or any correlation registered for the flow ``"packed bed"`` with :func:`pytes.submodels.register_correlation`. Parameters ---------- name : str, optional The name of the model. options : dict, optional The model's options. Attributes ---------- r : pybamm.SpatialVariable Distance from the capsule centre [m], from 0 to the capsule radius, in the "capsule" domain (spherical), with the "flow path" as its secondary domain unless the storage is averaged. Not set for a lumped capsule. z : pybamm.SpatialVariable Distance along the bed from the inlet [m], in the "flow path" domain. """ allowed_options = { "storage along flow path": ("resolved", "averaged"), "storage element": ("resolved", "lumped", "lumped with Jeffreson"), "heat transfer coefficient": HeatTransferCoefficientOptions("packed bed"), } def __init__(self, name="Packed bed model", options=None): super().__init__(name=name, options=options) @property def averaged(self): """Whether the capsules are averaged along the bed""" return self.options["storage along flow path"] == "averaged" @property def lumped(self): """Whether each capsule has a uniform temperature""" return self.options["storage element"].startswith("lumped")
[docs] def set_spatial_variables(self): if not self.lumped: domains = {"primary": "capsule"} if not self.averaged: domains["secondary"] = "flow path" self.r = pybamm.SpatialVariable( "r", domains=domains, coord_sys="spherical polar" ) self.z = pybamm.SpatialVariable( "z", domains={"primary": "flow path"}, coord_sys="cartesian" )
[docs] def get_parameters(self): return PackedBedParameters()
[docs] def set_submodels(self): param = self.param z = self.z if self.lumped: # The capsules take (1 - eps) A of bed volume per unit bed length def cross_section_integral(symbol): return param.A * (1 - param.eps) * symbol else: # The integral over r of a spherical symbol is over the capsule volume (it # includes 4 pi r**2); there are capsule_density capsules per unit bed volume def cross_section_integral(symbol): return param.A * param.capsule_density * pybamm.Integral(symbol, self.r) # An averaged capsule is the same at every z def integrate(symbol): per_length = cross_section_integral(symbol) if self.averaged: return param.Z * per_length return pybamm.Integral(per_length, z) flow_path = FlowPath(z, param.Z, param.flow_area, 1) fluid = AdvectedFluid(param, param.fluid, flow_path) if self.lumped: geometry = LumpedGeometry( integrate, cross_section_integral, 3 / param.R, flow_path, averaged=self.averaged, ) storage = LumpedStorage(param, param.pcm, geometry) else: geometry = StorageGeometry( self.r, integrate, cross_section_integral, inner="right", flow_path=flow_path if self.averaged else None, ) storage = EnthalpyStorage(param, param.pcm, geometry) self.submodels["fluid"] = fluid self.submodels["storage"] = storage self.submodels["coupling"] = WallCoupling( param, param.fluid, storage.label, geometry, flux_law=self.flux_law(storage) ) self.submodels["device energy"] = DeviceEnergy(param, fluid, [storage]) self.submodels["per-area totals"] = PerAreaTotals(param, param.A)
[docs] def flux_law(self, storage): """ The surface flux law at the capsules, from the "heat transfer coefficient" option, corrected for the conduction inside the capsule with "storage element": "lumped with Jeffreson" """ values = self.allowed_options["heat transfer coefficient"] law = values.flux_law( self.options["heat transfer coefficient"], self.param.R, self.param.A ) if self.options["storage element"] == "lumped with Jeffreson": law = Jeffreson( law, self.param.R, f"{storage.label} thermal conductivity [W.m-1.K-1]", storage.storage_geometry.boundary_domain, ) return law
@property def default_geometry(self): """ A capsule (``r`` from 0 to the capsule radius), unless it is lumped, and the bed (``z``) """ geometry = { "flow path": {self.z: {"min": pybamm.Scalar(0), "max": self.param.Z}} } if not self.lumped: geometry["capsule"] = { self.r: {"min": pybamm.Scalar(0), "max": self.param.R} } return pybamm.Geometry(geometry) @property def default_submesh_types(self): """Uniform 1D meshes in every domain""" return { domain: pybamm.MeshGenerator(pybamm.Uniform1DSubMesh) for domain in self.default_geometry } @property def default_var_pts(self): """ Number of mesh points in each spatial variable (F-052). The error comes mostly from ``r``, and falls slowly with it because of the melting front inside the capsules. For the default charge, against 160 points in ``r`` (40 in ``z``), 40 give 0.27 K on average in the outlet temperature and 1.8 % in the stored energy; against 640 points in ``z`` (40 in ``r``), 40 give 0.063 K and 0.33 %, and 5.4 K at most, in a short spike as the front leaves the bed. It solves in about 1.5 s. A lumped capsule has no ``r``. """ if self.lumped: return {self.z: 40} return {self.r: 40, self.z: 40} @property def default_spatial_methods(self): """Finite volumes in every domain; the bed uses the fluid's spatial method""" methods = {"flow path": compat.fluid_spatial_method()} if not self.lumped: methods["capsule"] = pybamm.FiniteVolume() return methods @property def default_quick_plot_variables(self): """Variables shown by :class:`pybamm.QuickPlot` by default (DESIGN D15)""" return [ ["Inlet temperature [degC]", "Outlet temperature [degC]"], "Heat transfer fluid temperature [degC]", "Power [kW]", "Average liquid fraction", ] @property def default_solver(self): """IDAKLU with ``rtol=1e-5`` and ``atol=1e-8``, as for the finned-tube family""" return pybamm.IDAKLUSolver(rtol=1e-5, atol=1e-8) @property def default_parameter_values(self): """See :func:`pytes.models.packed_bed.get_parameter_values`""" return get_parameter_values()