Source code for pytes.materials

#
# Storage materials
#
"""
Storage materials: the enthalpy-temperature relation, the conductivity and the liquid
fraction of the material a storage submodel holds.

A material maps its enthalpy per unit volume ``H`` and its internal states to a temperature.
The materials here have no internal states (``internal_states`` is empty), so the
temperature is a function of ``H`` alone. Materials with kinetics or hysteresis will add
internal states without changing the storage submodels (DESIGN D7).

Enthalpies are per unit volume and relative to 0 K. Energy variables subtract the enthalpy
at the reference temperature, so this reference never shows in the outputs.
"""

import pybamm


[docs] def smooth_min(a, b, width): """ Smooth approximation of min(a, b), ``(a + b - sqrt((a - b)**2 + width**2)) / 2``. It rounds the corner at ``a = b`` over a distance of order ``width``, is never larger than min(a, b), differs from it by at most ``width / 2`` (at the corner), and is exactly min(a, b) when ``width`` is 0. Parameters ---------- a, b : pybamm.Symbol or float The values to compare. width : pybamm.Symbol or float Smoothing width, in the units of ``a`` and ``b``. Returns ------- pybamm.Symbol The smoothed minimum. """ return (a + b - pybamm.sqrt((a - b) ** 2 + width**2)) / 2
[docs] def smooth_max(a, b, width): """ Smooth approximation of max(a, b), ``(a + b + sqrt((a - b)**2 + width**2)) / 2``. Never smaller than max(a, b); see :func:`smooth_min`. """ return (a + b + pybamm.sqrt((a - b) ** 2 + width**2)) / 2
[docs] class SensibleMaterial: """ A material that does not change phase, such as the metal of the fins. Parameters ---------- prefix : str, optional Prefix of the parameter names (default "Metal"): "<prefix> conductivity [W.m-1.K-1]", "<prefix> density [kg.m-3]" and "<prefix> specific heat capacity [J.kg-1.K-1]". Attributes ---------- k_param, rho, c_p : pybamm.Parameter Conductivity [W.m-1.K-1], density [kg.m-3] and specific heat capacity [J.kg-1.K-1]. rho_c_p : pybamm.Symbol Volumetric heat capacity [J.m-3.K-1]. internal_states : tuple Empty: the temperature is a function of the enthalpy alone. changes_phase : bool False: the material has no liquid fraction. H_scale, H_reference : pybamm.Symbol Typical enthalpy scale and reference [J.m-3], those of 100 K and 300 K. A storage submodel may override them (e.g. to scale like the model's temperatures). """ internal_states = () changes_phase = False def __init__(self, prefix="Metal"): self.prefix = prefix self.k_param = pybamm.Parameter(f"{prefix} conductivity [W.m-1.K-1]") self.rho = pybamm.Parameter(f"{prefix} density [kg.m-3]") self.c_p = pybamm.Parameter(f"{prefix} specific heat capacity [J.kg-1.K-1]") self.rho_c_p = self.rho * self.c_p self.H_scale = self.rho_c_p * 100 self.H_reference = self.rho_c_p * 300
[docs] def H2T(self, H, states=()): """Temperature [K] as a function of the enthalpy per unit volume [J.m-3]""" return H / self.rho_c_p
[docs] def T2H(self, T): """Enthalpy per unit volume [J.m-3], relative to 0 K, at temperature ``T`` [K]""" return self.rho_c_p * T
[docs] def k(self, H, states=()): """Conductivity [W.m-1.K-1] (constant)""" return self.k_param
[docs] class PhaseChangeMaterial: """ Thermal properties of a material that melts, and the enthalpy-temperature relations derived from them. The enthalpy is per unit volume and relative to 0 K. Subclasses set the solid and liquid volumetric heat capacities ``rho_c_p_s`` and ``rho_c_p_l`` [J.m-3.K-1], the volumetric latent heat ``rho_L`` [J.m-3], the solid and liquid conductivities ``k_s`` and ``k_l`` [W.m-1.K-1], the melting temperature ``T_m`` [K], the melting range ``melting_range`` [K] and the smoothing width ``smoothing_width`` [K], then call :meth:`_set_derived`. The material melts between the solidus ``T_m - melting_range / 2`` and the liquidus ``T_m + melting_range / 2`` (DESIGN D7). In between (the mushy zone) the enthalpy is linear in temperature: the latent heat spread evenly, plus the mean of the solid and liquid heat capacities. A melting range of 0 gives a sharp melting point, where the temperature stays at ``T_m`` while the material melts. Separately, the corners of the relations at the solidus and liquidus are smoothed over ``smoothing_width``, a numerical device that makes the models faster. A width of 0 gives exact corners. Both may be 0. Attributes ---------- internal_states : tuple Empty: the temperature is a function of the enthalpy alone. changes_phase : bool True: the material has a liquid fraction. T_solidus, T_liquidus : pybamm.Symbol Solidus and liquidus temperatures [K]. rho_c_p_mushy : pybamm.Symbol Sensible heat capacity in the mushy zone [J.m-3.K-1], the mean of the solid and liquid ones. H_solidus, H_liquidus : pybamm.Symbol Enthalpy at the solidus (fully solid) and at the liquidus (fully liquid) [J.m-3]. dT_smooth : pybamm.Symbol The smoothing width, floored at 1e-9 K so that derivatives stay defined where the arguments of :func:`smooth_min` and :func:`smooth_max` are equal (its effect is at most 5e-10 K). H_scale, H_reference : pybamm.Symbol Scale and reference for the enthalpy as a solver variable: the enthalpy of the mushy zone and its middle. The scaled enthalpy is then the liquid fraction minus 1/2 while melting, and of order one over the operating range, as the sensible heat is comparable to the latent heat. """ internal_states = () changes_phase = True def _set_derived(self): """Set the solidus, the liquidus, the smoothing width and the scaling""" self.T_solidus = self.T_m - self.melting_range / 2 self.T_liquidus = self.T_m + self.melting_range / 2 self.rho_c_p_mushy = (self.rho_c_p_s + self.rho_c_p_l) / 2 self.H_solidus = self.rho_c_p_s * self.T_solidus self.H_liquidus = ( self.H_solidus + self.rho_L + self.rho_c_p_mushy * self.melting_range ) # Floor the width at 1 nK so that derivatives stay defined (rather than 0 / 0) # where the arguments of smooth_min / smooth_max are equal, even when the # smoothing width is 0 self.dT_smooth = pybamm.maximum(self.smoothing_width, 1e-9) # The scaled enthalpy is the liquid fraction - 1/2 while melting self.H_scale = self.H_liquidus - self.H_solidus self.H_reference = (self.H_solidus + self.H_liquidus) / 2
[docs] def H2T(self, H, states=()): """ Temperature as a function of the enthalpy and the internal states (none here). Below the solidus the temperature is ``H / rho_c_p_s``; in the mushy zone it rises linearly from the solidus to the liquidus temperature with the liquid fraction; above the liquidus it is ``T_liquidus + (H - H_liquidus) / rho_c_p_l``. The corners are smoothed over ``dT_smooth``. Parameters ---------- H : pybamm.Symbol Enthalpy per unit volume, relative to 0 K [J.m-3]. states : tuple, optional Internal states, in the order of ``internal_states`` (none for this material). Returns ------- pybamm.Symbol Temperature [K]. """ # Each term is one piece, clipped: the corner widths in enthalpy match dT_smooth solid = smooth_min(H, self.H_solidus, self.rho_c_p_s * self.dT_smooth) liquid = smooth_max(H - self.H_liquidus, 0, self.rho_c_p_l * self.dT_smooth) return ( solid / self.rho_c_p_s + self.melting_range * self.liquid_fraction(H, states) + liquid / self.rho_c_p_l )
[docs] def T2H(self, T): """ Enthalpy as a function of the temperature. This is the exact (unsmoothed) relation. Away from the solidus and the liquidus it inverts :meth:`H2T` up to about ``dT_smooth**2 / (4 d)``, with ``d`` the distance to the nearer of them. For a sharp melting point (melting range 0), the material at exactly ``T = T_m`` is taken to be fully solid. Parameters ---------- T : pybamm.Symbol Temperature [K]. Returns ------- pybamm.Symbol Enthalpy per unit volume, relative to 0 K [J.m-3]. """ # The floor makes a melting range of 0 a step, solid at T_m width = pybamm.maximum(self.melting_range, 1e-12) phi = pybamm.minimum(pybamm.maximum((T - self.T_solidus) / width, 0), 1) return ( self.rho_c_p_s * pybamm.minimum(T, self.T_solidus) + (self.H_liquidus - self.H_solidus) * phi + self.rho_c_p_l * pybamm.maximum(T - self.T_liquidus, 0) )
[docs] def k(self, H, states=()): """ Conductivity [W.m-1.K-1] as a function of the enthalpy [J.m-3], interpolated linearly in the liquid fraction between ``k_s`` and ``k_l``. """ return self.k_s + (self.k_l - self.k_s) * self.liquid_fraction(H, states)
[docs] def liquid_fraction(self, H, states=()): """ Liquid fraction as a function of the enthalpy [J.m-3]: the fraction of the mushy zone's enthalpy, ``(H - H_solidus) / (H_liquidus - H_solidus)``, clipped to [0, 1] with the corners smoothed over the enthalpy widths matching ``dT_smooth`` (``rho_c_p_s * dT_smooth`` and ``rho_c_p_l * dT_smooth``). In the mushy zone it is also ``(T - T_solidus) / melting_range``. It is between 0 and 1 up to an overshoot of order ``(rho_c_p * dT_smooth / rho_L)**2`` when smoothed. """ dH = self.H_liquidus - self.H_solidus phi = (H - self.H_solidus) / dH phi = smooth_max(phi, 0, self.rho_c_p_s * self.dT_smooth / dH) return smooth_min(phi, 1, self.rho_c_p_l * self.dT_smooth / dH)
[docs] class PureMaterial(PhaseChangeMaterial): """ A phase-change material on its own: ``rho_c_p_s = rho_s c_p_s``, ``rho_c_p_l = rho_l c_p_l``, ``rho_L = rho_s L`` (the latent heat per unit volume uses the solid density, neglecting the volume change on melting). Parameters ---------- prefix : str, optional Prefix of the parameter names (default "PCM"), e.g. "<prefix> latent heat [J.kg-1]", "<prefix> melting range [K]" and "<prefix> smoothing width [K]". Attributes ---------- k_s, k_l, rho_s, rho_l, c_p_s, c_p_l, L, T_m, melting_range, smoothing_width : \ pybamm.Parameter "<prefix> solid phase conductivity [W.m-1.K-1]", "<prefix> liquid phase conductivity [W.m-1.K-1]", "<prefix> solid phase density [kg.m-3]", "<prefix> liquid phase density [kg.m-3]", "<prefix> solid phase specific heat capacity [J.kg-1.K-1]", "<prefix> liquid phase specific heat capacity [J.kg-1.K-1]", "<prefix> latent heat [J.kg-1]", "<prefix> melting temperature [K]" (the middle of the melting range), "<prefix> melting range [K]" (from solidus to liquidus; 0 for a sharp melting point) and "<prefix> smoothing width [K]" (numerical; 0 for exact corners). """ def __init__(self, prefix="PCM"): self.prefix = prefix self.k_s = pybamm.Parameter(f"{prefix} solid phase conductivity [W.m-1.K-1]") self.k_l = pybamm.Parameter(f"{prefix} liquid phase conductivity [W.m-1.K-1]") self.rho_s = pybamm.Parameter(f"{prefix} solid phase density [kg.m-3]") self.rho_l = pybamm.Parameter(f"{prefix} liquid phase density [kg.m-3]") self.c_p_s = pybamm.Parameter( f"{prefix} solid phase specific heat capacity [J.kg-1.K-1]" ) self.c_p_l = pybamm.Parameter( f"{prefix} liquid phase specific heat capacity [J.kg-1.K-1]" ) self.L = pybamm.Parameter(f"{prefix} latent heat [J.kg-1]") self.T_m = pybamm.Parameter(f"{prefix} melting temperature [K]") self.melting_range = pybamm.Parameter(f"{prefix} melting range [K]") self.smoothing_width = pybamm.Parameter(f"{prefix} smoothing width [K]") self.rho_c_p_s = self.rho_s * self.c_p_s self.rho_c_p_l = self.rho_l * self.c_p_l self.rho_L = self.rho_s * self.L self._set_derived()
[docs] class CompositeMaterial(PhaseChangeMaterial): """ A phase-change material and a sensible filler (such as metal fins) homogenised into one material, with the filler taking up a volume fraction ``delta``: - heat capacities: volume averages, ``rho_c_p_s = delta rho_c_p_filler + (1 - delta) rho_c_p_s,PCM`` (and similarly for the liquid); - latent heat: ``rho_L = (1 - delta) rho_L,PCM``, as only the PCM melts; - conductivities: volume averages, ``k_s = (1 - delta) k_s,PCM + delta k_filler`` (and similarly for the liquid), i.e. the parallel arrangement, for heat flowing along the filler. Parameters ---------- pcm : :class:`PureMaterial` The phase-change material. filler : :class:`SensibleMaterial` The filler. volume_fraction : pybamm.Symbol Volume fraction of the filler, ``delta``. """ def __init__(self, pcm, filler, volume_fraction): self.pcm = pcm self.filler = filler delta = volume_fraction self.T_m = pcm.T_m self.melting_range = pcm.melting_range self.smoothing_width = pcm.smoothing_width self.rho_c_p_s = filler.rho_c_p * delta + pcm.rho_c_p_s * (1 - delta) self.rho_c_p_l = filler.rho_c_p * delta + pcm.rho_c_p_l * (1 - delta) self.rho_L = pcm.rho_L * (1 - delta) self.k_s = pcm.k_s * (1 - delta) + filler.k_param * delta self.k_l = pcm.k_l * (1 - delta) + filler.k_param * delta self._set_derived()