Source code for sdom.parametric.mutations
"""Data mutation helpers for SDOM parametric analysis.
Each helper applies one parameter change to the provided data dict.
These helpers mutate the given data structure in-place; callers who need to
preserve the original data are responsible for deep-copying before passing it
here.
"""
import logging
from typing import Any
import pandas as pd
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Lookup table: data-dict key → numeric column name to scale
# ---------------------------------------------------------------------------
#: Maps every supported ``ts_key`` to the column that holds numeric values
#: in the corresponding DataFrame. Used by :func:`_apply_ts_mutation`.
TS_KEY_TO_COLUMN: dict[str, str] = {
"load_data": "Load",
"large_hydro_data": "LargeHydro",
"large_hydro_max": "LargeHydro_max",
"large_hydro_min": "LargeHydro_min",
"cap_imports": "Imports",
"price_imports": "Imports_price",
"cap_exports": "Exports",
"price_exports": "Exports_price",
"nuclear_data": "Nuclear",
"other_renewables_data": "OtherRenewables",
}
# ---------------------------------------------------------------------------
# Mutation helpers
# ---------------------------------------------------------------------------
[docs]
def _apply_scalar_mutation(data: dict, data_key: str, param_name: str, value: Any) -> None:
"""Replace a scalar value in a DataFrame row-indexed by parameter name.
Sets ``data[data_key].loc[param_name, "Value"] = value``.
Parameters
----------
data : dict
SDOM data dictionary (already deep-copied; will be mutated in-place).
data_key : str
Key identifying the DataFrame in *data* (e.g. ``"scalars"``).
param_name : str
Row label of the parameter to modify (e.g. ``"GenMix_Target"``).
value : float or int
New value to assign.
Raises
------
ValueError
If *data_key* is not in *data*, or *param_name* is not a valid row
label in ``data[data_key]``.
"""
if data_key not in data:
raise ValueError(
f"_apply_scalar_mutation: data key '{data_key}' not found in data dict. "
f"Available keys: {list(data.keys())}"
)
df: pd.DataFrame = data[data_key]
if param_name not in df.index:
raise ValueError(
f"_apply_scalar_mutation: parameter '{param_name}' not found in "
f"data['{data_key}'].index. Available: {list(df.index)}"
)
logger.debug(
"_apply_scalar_mutation: data['%s'].loc['%s', 'Value'] = %s",
data_key, param_name, value,
)
data[data_key].loc[param_name, "Value"] = value
[docs]
def _apply_storage_factor_mutation(data: dict, param_name: str, factor: float) -> None:
"""Scale an entire row of ``data["storage_data"]`` by a multiplicative factor.
Multiplies ``data["storage_data"].loc[param_name]`` (all technology
columns) by *factor*. This uniformly scales the parameter across all
storage technologies.
Parameters
----------
data : dict
SDOM data dictionary (already deep-copied; will be mutated in-place).
param_name : str
Row label in ``data["storage_data"]`` (e.g. ``"P_Capex"``).
factor : float
Multiplicative factor. ``1.0`` leaves the row unchanged.
Raises
------
ValueError
If ``"storage_data"`` is absent from *data* or *param_name* is not
a valid row label.
"""
data_key = "storage_data"
if data_key not in data:
raise ValueError(
f"_apply_storage_factor_mutation: '{data_key}' not found in data dict."
)
df: pd.DataFrame = data[data_key]
if param_name not in df.index:
raise ValueError(
f"_apply_storage_factor_mutation: parameter '{param_name}' not found in "
f"data['storage_data'].index. Available: {list(df.index)}"
)
logger.debug(
"_apply_storage_factor_mutation: data['storage_data'].loc['%s'] *= %s",
param_name, factor,
)
data[data_key].loc[param_name] = data[data_key].loc[param_name] * factor
[docs]
def _apply_ts_mutation(data: dict, ts_key: str, factor: float) -> None:
"""Scale the numeric column of a time-series DataFrame by a multiplicative factor.
Looks up the target column name in :data:`TS_KEY_TO_COLUMN` and multiplies
``data[ts_key][column] *= factor``.
Parameters
----------
data : dict
SDOM data dictionary (already deep-copied; will be mutated in-place).
ts_key : str
Key identifying the time-series DataFrame in *data*
(e.g. ``"load_data"``). Must be present in :data:`TS_KEY_TO_COLUMN`.
factor : float
Multiplicative scaling factor. ``1.0`` leaves the series unchanged.
Raises
------
ValueError
If *ts_key* is not in :data:`TS_KEY_TO_COLUMN`, if *ts_key* is not
present in *data*, or if the resolved column is absent from the
DataFrame.
"""
if ts_key not in TS_KEY_TO_COLUMN:
raise ValueError(
f"_apply_ts_mutation: ts_key '{ts_key}' is not supported. "
f"Supported keys: {list(TS_KEY_TO_COLUMN.keys())}"
)
if ts_key not in data:
raise ValueError(
f"_apply_ts_mutation: ts_key '{ts_key}' not found in data dict. "
f"(The corresponding formulation may not include it.)"
)
column = TS_KEY_TO_COLUMN[ts_key]
df: pd.DataFrame = data[ts_key]
if column not in df.columns:
raise ValueError(
f"_apply_ts_mutation: expected column '{column}' not found in "
f"data['{ts_key}'].columns. Available: {list(df.columns)}"
)
logger.debug(
"_apply_ts_mutation: data['%s']['%s'] *= %s",
ts_key, column, factor,
)
data[ts_key][column] = data[ts_key][column] * factor