#
# 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 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