Tutorial 4: changing parameter values#

The parameter values of a model are a pybamm.ParameterValues: a dictionary of names and values. Each name ends with its units, and the values are always in SI units: temperatures in kelvin, not degrees Celsius.

There are two ways to change them: by hand, one value at a time, or by composing a set from layers (a PCM, a fluid, a geometry, …), which swaps a whole material or component at once.

import numpy as np
import pybamm

import pytes
from pytes import parameter_layers as layers

model = pytes.SharpFrontModel()
parameter_values = model.default_parameter_values
parameter_values
{'Fin pitch [m]': 0.003,
 'Fin thickness [m]': 0.0002,
 'Heat transfer coefficient [W.m-2.K-1]': 1000,
 'Heat transfer fluid conductivity [W.m-1.K-1]': 0.6,
 'Heat transfer fluid density [kg.m-3]': 998.21,
 'Heat transfer fluid specific heat capacity [J.kg-1.K-1]': 4184.1,
 'Heat transfer fluid viscosity [Pa.s]': 0.001,
 'Initial temperature [K]': 343.15,
 'Inlet temperature [K]': 283.15,
 'Mass flow rate [kg.s-1]': 0.02,
 'Metal conductivity [W.m-1.K-1]': 237,
 'Metal density [kg.m-3]': 2700,
 'Metal specific heat capacity [J.kg-1.K-1]': 897,
 'Number of tubes': 1,
 'PCM latent heat [J.kg-1]': 250000.0,
 'PCM liquid phase conductivity [W.m-1.K-1]': 0.5,
 'PCM liquid phase density [kg.m-3]': 1300,
 'PCM liquid phase specific heat capacity [J.kg-1.K-1]': 3000,
 'PCM melting range [K]': 2.0,
 'PCM melting temperature [K]': 333.15,
 'PCM smoothing width [K]': 0.01,
 'PCM solid phase conductivity [W.m-1.K-1]': 0.6,
 'PCM solid phase density [kg.m-3]': 1400,
 'PCM solid phase specific heat capacity [J.kg-1.K-1]': 2500,
 'Reference temperature [K]': 283.15,
 'Storage depth [m]': 0.02,
 'Tube length [m]': 10,
 'Tube radius [m]': 0.003,
 'citations': ['CRC']}

search finds parameters by name:

parameter_values.search("PCM")
Results for 'PCM': ['PCM solid phase conductivity [W.m-1.K-1]', 'PCM liquid phase conductivity [W.m-1.K-1]', 'PCM solid phase density [kg.m-3]', 'PCM liquid phase density [kg.m-3]', 'PCM solid phase specific heat capacity [J.kg-1.K-1]', 'PCM liquid phase specific heat capacity [J.kg-1.K-1]', 'PCM latent heat [J.kg-1]', 'PCM melting temperature [K]', 'PCM melting range [K]', 'PCM smoothing width [K]']
PCM solid phase conductivity [W.m-1.K-1] -> 0.6
PCM liquid phase conductivity [W.m-1.K-1] -> 0.5
PCM solid phase density [kg.m-3] -> 1400
PCM liquid phase density [kg.m-3] -> 1300
PCM solid phase specific heat capacity [J.kg-1.K-1] -> 2500
PCM liquid phase specific heat capacity [J.kg-1.K-1] -> 3000
PCM latent heat [J.kg-1] -> 250000.0
PCM melting temperature [K] -> 333.15
PCM melting range [K] -> 2.0
PCM smoothing width [K] -> 0.01

Changing values by hand#

Change a value like a dictionary entry, or several at once with update, then pass the parameter values to the simulation. model.default_parameter_values returns a new copy each time, so the defaults are never changed.

Here the battery is discharged with twice the default flow (which is the total flow into the device), and with twice the flow but now spread over four tubes instead of one.

t_eval = np.linspace(0, 3600, 121)
default = pybamm.Simulation(model).solve([0, 3600], t_interp=t_eval)

faster = model.default_parameter_values
faster["Mass flow rate [kg.s-1]"] = 0.04
faster_solution = pybamm.Simulation(model, parameter_values=faster).solve(
    [0, 3600], t_interp=t_eval
)

bigger = model.default_parameter_values
bigger.update({"Mass flow rate [kg.s-1]": 0.04, "Number of tubes": 4})
bigger_solution = pybamm.Simulation(model, parameter_values=bigger).solve(
    [0, 3600], t_interp=t_eval
)

pybamm.dynamic_plot(
    [default, faster_solution, bigger_solution],
    ["Outlet temperature [degC]", "Power [kW]", "Discharged energy [kWh]"],
    labels=["default", "twice the flow", "twice the flow, four tubes"],
);

With twice the flow, the battery delivers its energy faster, but the outlet is colder and falls sooner. With four tubes as well, each tube carries half the default flow and the battery holds four times the energy, so it delivers more power for longer.

Many runs: input parameters#

Each pybamm.Simulation builds and discretises the model before solving it. To solve the same model for many values of a parameter, set the parameter to "[input]" and give its value when solving: the model is built once, and each solve is fast.

Here the flow is swept to find how much energy the battery delivers while its outlet is still above 40 °C, hot enough for a shower.

sweep = model.default_parameter_values
sweep["Mass flow rate [kg.s-1]"] = "[input]"
simulation = pybamm.Simulation(model, parameter_values=sweep)

t_long = np.linspace(0, 7200, 721)
for mass_flow_rate in [0.01, 0.015, 0.02, 0.03, 0.04]:
    solution = simulation.solve(
        [0, 7200], t_interp=t_long, inputs={"Mass flow rate [kg.s-1]": mass_flow_rate}
    )
    outlet = solution["Outlet temperature [degC]"].entries
    t_hot = t_long[outlet >= 40][-1]
    useful = solution["Discharged energy [kWh]"](t_hot)
    print(
        f"{mass_flow_rate:.3f} kg/s: outlet above 40 degC for {t_hot / 60:3.0f} min, "
        f"delivering {useful:.2f} kWh"
    )
0.010 kg/s: outlet above 40 degC for  68 min, delivering 2.17 kWh
0.015 kg/s: outlet above 40 degC for  45 min, delivering 2.04 kWh
0.020 kg/s: outlet above 40 degC for  34 min, delivering 1.90 kWh
0.030 kg/s: outlet above 40 degC for  22 min, delivering 1.61 kWh
0.040 kg/s: outlet above 40 degC for   7 min, delivering 0.63 kWh

The faster the flow, the less of the stored energy comes out hot: the water spends less time in the tube, so the PCM around it cannot keep it warm.

Composing parameter values from layers#

A parameter set is made of independent layers: the operation (initial state, inlet temperature and flow), the coupling (the heat transfer coefficient), the heat transfer fluid, the PCM, the metal of the fins, and the geometry of the flow path and of the storage element. Each layer holds its own names, so a layer can be swapped without touching the rest. These are the layers available:

for kind in layers.KINDS:
    print(f"{kind}: {', '.join(layers.available(kind))}")
operation: Finned tube discharge, Nallusamy2007, Raul2018
coupling: Finned tube default, Packed bed default
heat_transfer_fluid: Water, Hytherm 600
pcm: Paraffin (Nallusamy 2007), A164 (Raul 2018), PCM 10N, PCM 30, PCM 60
metal: Aluminium, Steel, Copper
finned_tube_flow_path: Finned tube default
finned_tube_storage_element: Finned tube default
packed_bed_flow_path: Nallusamy2007, Raul2018
packed_bed_storage_element: Nallusamy2007, Raul2018, Default capsule

Each family’s default is a named set of layers:

layers.SETS["Finned tube default"]
{'operation': 'Finned tube discharge',
 'coupling': 'Finned tube default',
 'heat_transfer_fluid': 'Water',
 'pcm': 'PCM 60',
 'metal': 'Aluminium',
 'finned_tube_flow_path': 'Finned tube default',
 'finned_tube_storage_element': 'Finned tube default'}

swap replaces layers of a set. Here the same battery is built with copper fins instead of aluminium, and with a paraffin instead of the default PCM. pytes.energy_between gives the energy each version stores between 10 °C and 70 °C, the temperatures of the discharge.

variants = {
    "default (PCM 60, aluminium)": {},
    "copper fins": {"metal": "Copper"},
    "paraffin": {"pcm": "Paraffin (Nallusamy 2007)"},
}
solutions = []
for label, swapped in variants.items():
    values = layers.compose_set("Finned tube default")
    layers.swap(values, **swapped)
    capacity = pytes.energy_between(model, 273.15 + 10, 273.15 + 70, values)
    print(f"{label}: stores {capacity / 3.6e6:.2f} kWh between 10 and 70 degC")
    solutions.append(
        pybamm.Simulation(model, parameter_values=values).solve(
            [0, 3600], t_interp=t_eval
        )
    )

pybamm.dynamic_plot(
    solutions,
    ["Outlet temperature [degC]", "Power [kW]", "Average liquid fraction"],
    labels=list(variants),
);
default (PCM 60, aluminium): stores 2.45 kWh between 10 and 70 degC
copper fins: stores 2.47 kWh between 10 and 70 degC
paraffin: stores 1.26 kWh between 10 and 70 degC

Copper fins change this battery little: its outlet is slightly warmer, and copper’s higher heat capacity per unit volume stores slightly more energy. The paraffin stores about half the energy in the same volume, as it is less dense and has a lower latent heat, so the battery is spent in less than half an hour.

A layer is a plain dictionary, so you can start from a registered one and change it, or fill in an empty one from layers.template. layers.check tells you what is missing.

my_pcm = layers.get("pcm", "PCM 60")
my_pcm["PCM latent heat [J.kg-1]"] = 300e3
my_pcm["citations"] = []
layers.check("pcm", my_pcm)

values = layers.compose_set("Finned tube default")
layers.swap(values, pcm=my_pcm)
capacity = pytes.energy_between(model, 273.15 + 10, 273.15 + 70, values)
print(f"with a latent heat of 300 kJ/kg: {capacity / 3.6e6:.2f} kWh")
with a latent heat of 300 kJ/kg: 2.75 kWh

Layers also build an operation that suits a PCM, compose sets for other families and record the sources of every value: see the parameter layers example.

Next: Tutorial 5: plotting.