Source code for powersimdata.input.changes.electrification

import copy
from dataclasses import dataclass
from typing import Dict


def _check_scale_factors(scale_factors):
    """Validate schema of scale factors dict

    :param dict scale_factors: see :func:`add_electrification`
    """
    if not isinstance(scale_factors, dict):
        raise ValueError("scale factors must be a dict")
    if not all(isinstance(k, str) for k in scale_factors):
        raise ValueError("profile name must be str")
    if not all(isinstance(d, (int, float)) for d in scale_factors.values()):
        raise ValueError("scale factors must be numeric")
    vals = list(scale_factors.values())
    if any(v < 0 for v in vals):
        raise ValueError("scaling factor must be non negative")
    if sum(vals) > 1:
        raise ValueError("scaling factors must sum to between 0 and 1")


[docs]@dataclass class ScaleFactors: """Map technology to adoption rate :param dict sf: a dictionary mapping tech to adoption rate """ sf: Dict[str, float] def __init__(self, sf): _check_scale_factors(sf) self.sf = sf
[docs] def value(self): return self.sf
[docs]@dataclass class AreaScaling: """Map end uses to adoption rates of each technology :param dict info: a mapping from end use (*str*) to scale factors (*dict*) :raises ValueError: if info is not a dict, or any keys are not strings """ end_uses: Dict[str, ScaleFactors] def __init__(self, info): if not isinstance(info, dict): raise ValueError("zone/grid scaling must be a dict") if not all(isinstance(k, str) for k in info): raise ValueError("end use must be str") self.end_uses = {k: ScaleFactors(v) for k, v in info.items()}
[docs] def value(self): return {k: v.value() for k, v in self.end_uses.items()}
[docs]@dataclass class ElectrifiedDemand: """Container object for specifying zone or grid level adoption of any technologies for a given class of electrification :param powersimdata.input.change_table.ChangeTable obj: change table :param dict info: see :func:`add_electrification` """ grid_info: AreaScaling zone_info: Dict[str, AreaScaling] def __init__(self, obj, info): if not isinstance(info, dict): raise ValueError("info must be a dict") grid = info.get("grid", {}) grid_info = AreaScaling(grid) zone = info.get("zone", {}) if not all(isinstance(k, str) for k in zone): raise ValueError("zone name must be str") obj._check_zone(list(zone.keys())) zone_info = {k: AreaScaling(v) for k, v in zone.items()} self.grid_info = grid_info self.zone_info = zone_info
[docs] def value(self): zones = {k: v.value() for k, v in self.zone_info.items()} return {"grid": self.grid_info.value(), "zone": zones}
[docs]def add_electrification(obj, kind, info): """Add electrification profiles :param powersimdata.input.change_table.ChangeTable obj: change table :param str kind: the kind of demand, e.g. building :param dict info: Keys are *'grid'* and *'zone'*, to specify the scale factors in the given area. For grid scaling, the value is a *dict*, which maps a *str* representing the end use to a *dict*, which maps a *str* to *float*. This dict is referred to as *scale_factors* here. The values in *scale_factors* must be nonnegative and sum to at most 1. For zone scaling, the value is also a *dict*, mapping zone names (*str*) to a *dict* which mirrors the structure used for grid scaling """ allowed = ["building", "transportation"] if kind not in allowed: raise ValueError(f"unrecognized class of electrification: {kind}") info = copy.deepcopy(info) if not set(info) <= {"zone", "grid"}: raise ValueError("unrecognized scaling key") result = ElectrifiedDemand(obj, info).value() curr = obj.ct.get(kind) if curr is None: obj.ct[kind] = {"grid": {}, "zone": {}} obj.ct[kind]["grid"].update(result["grid"]) obj.ct[kind]["zone"].update(result["zone"])