Source code for powersimdata.input.changes.storage
import copy
from powersimdata.input.changes import ordinal
[docs]def add_storage_capacity(obj, info):
"""Sets storage parameters in change table.
:param powersimdata.input.change_table.ChangeTable obj: change table
:param list info: each entry is a dictionary. The dictionary gathers
the information needed to create a new storage device.
Required keys: "bus_id", "capacity".
"capacity" denotes the symmetric input and output power limits (MW).
Optional keys: "duration", "min_stor", "max_stor", "energy_value", "InEff",
"OutEff", "LossFactor", "terminal_min", "terminal_max".
"duration" denotes the energy to power ratio (hours).
"min_stor" denotes the minimum energy limit (unitless), e.g. 0.05 = 5%.
"max_stor" denotes the maximum energy limit (unitless), e.g. 0.95 = 95%.
"energy_value" denotes the value of stored energy at interval end ($/MWh).
"InEff" denotes the input efficiency (unitless), e.g. 0.95 = 95%.
"OutEff" denotes the output efficiency (unitless), e.g. 0.95 = 95%.
"LossFactor" denotes the per-hour relative losses,
e.g. 0.01 means that 1% of the current state of charge is lost per hour).
"terminal_min" denotes the minimum state of charge at interval end,
e.g. 0.5 means that the storage must end the interval with at least 50%.
"terminal_max" denotes the maximum state of charge at interval end,
e.g. 0.9 means that the storage must end the interval with no more than 90%.
:raises TypeError: if ``info`` is not a list.
:raises ValueError: if any of the new storages to be added have bad values.
"""
if not isinstance(info, list):
raise TypeError("Argument enclosing new storage(s) must be a list")
info = copy.deepcopy(info)
new_storages = []
required = {"bus_id", "capacity"}
optional = {
"duration",
"min_stor",
"max_stor",
"energy_value",
"InEff",
"OutEff",
"LossFactor",
"terminal_min",
"terminal_max",
}
anticipated_bus = obj._get_transformed_df("bus")
for i, storage in enumerate(info):
obj._check_entry_keys(storage, i, "storage", required, None, optional)
if storage["bus_id"] not in anticipated_bus.index:
raise ValueError(
f"No bus id {storage['bus_id']} available for {ordinal(i)} storage"
)
for o in optional:
if o not in storage:
storage[o] = obj.grid.storage[o]
for k, v in storage.items():
if not isinstance(v, (int, float)):
err_msg = f"values must be numeric, bad type for {ordinal(i)} {k}"
raise ValueError(err_msg)
if v < 0:
raise ValueError(
f"values must be non-negative, bad value for {ordinal(i)} {k}"
)
for k in {"min_stor", "max_stor", "InEff", "OutEff", "LossFactor"}:
if storage[k] > 1:
raise ValueError(
f"value for {k} must be <=1, bad value for {ordinal(i)} storage"
)
new_storages.append(storage)
if "storage" not in obj.ct:
obj.ct["storage"] = []
obj.ct["storage"] += new_storages