Source code for pytes.submodels.energy

#
# Device-level power and energy
#
"""Device-level power, energies and initial state (DESIGN D12 to D14)."""

import pybamm

from .base_submodel import BaseSubModel
from .storage import INITIAL_TEMPERATURE


[docs] class DeviceEnergy(BaseSubModel): """ Power and energies of the whole device, and its initial state. - Power: ``mdot (h_out - h_in)``, positive on discharge (DESIGN D8, D12). - Discharged energy: the time integral of the power, solved for by the solver so that it can be checked against the change in total thermal energy. Charged energy is its opposite, the net energy put in. - Throughputs (issue #14): the energy delivered while discharging, the time integral of ``max(P, 0)``, and the energy put in while charging, of ``max(-P, 0)``. Both only grow, and their difference is the discharged energy; their ratio over a cycle is its round-trip efficiency. - Energies: the storage thermal energy is the sum of the storage components' energies, and the total thermal energy adds the fluid. Every energy is zero at the uniform state at the reference temperature (DESIGN D14). - Heat loss power: zero (there is no casing yet), positive for loss. - Storage temperatures (issue #14): the average over all the storage components, weighted by volume, over the whole device ("Storage average temperature [K]") and over each cross-section of the flow path ("Storage cross-section average temperature [K]"). With one storage they equal its own averages; with several (the fins and the PCM of the mush model) they compare with a model that homogenises them. - Initial state: uniform at "Initial temperature [K]" (DESIGN D14). Each component starts at its equilibrium state at that temperature: for a sharp melting point, solid at the melting temperature; with a melting range, partly liquid inside it. There is no state of charge (DESIGN D13). The energy between two uniform states is post-processing: see :func:`pytes.energy_between`, which uses :meth:`uniform_energy`. Parameters ---------- param : object The model's parameters. Uses ``T_init``. fluid_component : :class:`pytes.submodels.AdvectedFluid` The fluid. storage_components : list The storage submodels. Each has a ``label``, a ``storage_geometry``, an ``energy_variable`` and a ``uniform_energy(T)`` method, and provides "<label> average temperature [K]" and "<label> cross-section average temperature [K]". """ assumptions = ("There are no heat losses to the surroundings.",) def __init__( self, param, fluid_component, storage_components, name="Device energy" ): self.fluid_component = fluid_component self.storage_components = list(storage_components) super().__init__(param, name) @property def components(self): """All the parts that hold heat, the storage components and then the fluid""" return [*self.storage_components, self.fluid_component]
[docs] def uniform_energy(self, T): """ Total thermal energy [J] of the uniform equilibrium state at ``T`` [K]. It contains integrals over the storage, so it must be discretised before it is evaluated. """ E = self.components[0].uniform_energy(T) for component in self.components[1:]: E = E + component.uniform_energy(T) return E
[docs] def build(self): param = self.param power = self.coupled("Mass flow rate [kg.s-1]") * ( self.coupled("Outlet specific enthalpy [J.kg-1]") - self.coupled("Inlet specific enthalpy [J.kg-1]") ) E_out = pybamm.Variable("Discharged energy [J]", scale=1e6) E_discharge = pybamm.Variable("Discharged throughput [J]", scale=1e6) E_charge = pybamm.Variable("Charged throughput [J]", scale=1e6) self.rhs = { E_out: power, E_discharge: pybamm.maximum(power, 0), E_charge: pybamm.maximum(-power, 0), } self.initial_conditions = { E: pybamm.Scalar(0) for E in (E_out, E_discharge, E_charge) } E_storage = self.coupled(self.storage_components[0].energy_variable) for component in self.storage_components[1:]: E_storage = E_storage + self.coupled(component.energy_variable) E_total = E_storage + self.coupled(self.fluid_component.energy_variable) def volume_weighted(name, volume): """The average of a temperature of every storage, weighted by volume""" total = weighted = 0 for component in self.storage_components: V = volume(component.storage_geometry) # On the volume's domain, so that PyBaMM does not broadcast it T = self.coupled(name.format(component.label), V.domain or None) weighted = weighted + V * T total = total + V return weighted / total variables = { "Power [W]": power, "Discharged energy [J]": E_out, "Charged energy [J]": -E_out, "Discharged throughput [J]": E_discharge, "Charged throughput [J]": E_charge, "Storage thermal energy [J]": E_storage, "Total thermal energy [J]": E_total, "Heat loss power [W]": pybamm.Scalar(0), "Storage average temperature [K]": volume_weighted( "{} average temperature [K]", lambda geometry: geometry.volume ), "Storage cross-section average temperature [K]": volume_weighted( "{} cross-section average temperature [K]", lambda geometry: geometry.cross_section_volume, ), INITIAL_TEMPERATURE: param.T_init, } self.variables = variables
[docs] class PerAreaTotals(BaseSubModel): """ Device totals per unit of a reference area, as family-specific twins of the shared whole-device outputs (DESIGN D4): for each total ``"<name> [<unit>]"``, the variable ``"<name> per unit area [<unit>.m-2]"``. Parameters ---------- param : object The model's parameters. area : pybamm.Symbol The reference area [m2], e.g. a bed cross-section. totals : iterable of str, optional Names of the totals, each ending in "[W]" or "[J]" (default: power, discharged energy, storage and total thermal energy). """ def __init__( self, param, area, totals=( "Power [W]", "Discharged energy [J]", "Storage thermal energy [J]", "Total thermal energy [J]", ), name="Per-area totals", ): self.area = area self.totals = tuple(totals) super().__init__(param, name)
[docs] def build(self): variables = {} for total in self.totals: stem, unit = total[: total.rindex("[")].strip(), total[total.rindex("[") :] per_area = f"{stem} per unit area {unit[:-1]}.m-2]" variables[per_area] = self.coupled(total) / self.area self.variables = variables