Source code for reV.supply_curve.competitive_wind_farms

# -*- coding: utf-8 -*-
"""
Competitive Wind Farms exclusion handler
"""
import logging

import numpy as np
from rex.utilities.utilities import parse_table

from reV.utilities import SupplyCurveField

logger = logging.getLogger(__name__)


[docs]class CompetitiveWindFarms: """ Handle competitive wind farm exclusion during supply curve sorting """ def __init__(self, wind_dirs, sc_points, n_dirs=2, offshore=False): """ Parameters ---------- wind_dirs : pandas.DataFrame | str path to .csv or reVX.wind_dirs.wind_dirs.WindDirs output with the neighboring supply curve point gids and power-rose value at each cardinal direction sc_points : pandas.DataFrame | str Supply curve point summary table n_dirs : int, optional Number of prominent directions to use, by default 2 offshore : bool Flag as to whether offshore farms should be included during CompetitiveWindFarms """ self._wind_dirs = self._parse_wind_dirs(wind_dirs) self._sc_gids, self._sc_point_gids, self._mask = self._parse_sc_points( sc_points, offshore=offshore ) self._offshore = offshore valid = np.isin(self.sc_point_gids, self._wind_dirs.index) if not np.all(valid): msg = ( "'sc_points contains sc_point_gid values that do not " "correspond to valid 'wind_dirs' sc_point_gids:\n{}".format( self.sc_point_gids[~valid] ) ) logger.error(msg) raise RuntimeError(msg) mask = self._wind_dirs.index.isin(self._sc_point_gids.keys()) self._wind_dirs = self._wind_dirs.loc[mask] self._upwind, self._downwind = self._get_neighbors( self._wind_dirs, n_dirs=n_dirs ) def __repr__(self): gids = len(self._upwind) # pylint: disable=unsubscriptable-object neighbors = len(self._upwind.values[0]) msg = "{} with {} sc_point_gids and {} prominent directions".format( self.__class__.__name__, gids, neighbors ) return msg def __getitem__(self, keys): """ Map gid for given mapping Parameters ---------- keys : tuple (gid(s) to extract, gid) pair Returns ------- gid(s) : int | list Mapped gid(s) for given mapping """ if not isinstance(keys, tuple): msg = ("{} must be a tuple of form (source, gid) where source is: " "{}, '{}', or 'upwind', 'downwind'" .format(keys, SupplyCurveField.SC_GID, SupplyCurveField.SC_POINT_GID)) logger.error(msg) raise ValueError(msg) source, gid = keys if source == SupplyCurveField.SC_POINT_GID: out = self.map_sc_gid_to_sc_point_gid(gid) elif source == SupplyCurveField.SC_GID: out = self.map_sc_point_gid_to_sc_gid(gid) elif source == "upwind": out = self.map_upwind(gid) elif source == "downwind": out = self.map_downwind(gid) else: msg = ("{} must be: {}, {}, or 'upwind', " "'downwind'".format(source, SupplyCurveField.SC_GID, SupplyCurveField.SC_POINT_GID)) logger.error(msg) raise ValueError(msg) return out @property def mask(self): """ Supply curve point boolean mask, used for efficient exclusion False == excluded sc_point_gid Returns ------- ndarray """ return self._mask @property def sc_point_gids(self): """ Un-masked sc_point_gids Returns ------- ndarray """ sc_point_gids = np.array(list(self._sc_point_gids.keys()), dtype=int) mask = self.mask[sc_point_gids] return sc_point_gids[mask] @property def sc_gids(self): """ Un-masked sc_gids Returns ------- ndarray """ sc_gids = np.concatenate( [self._sc_point_gids[gid] for gid in self.sc_point_gids] ) return sc_gids @staticmethod def _parse_table(table): """ Extract features and their capacity from supply curve transmission mapping table Parameters ---------- table : str | pd.DataFrame Path to .csv or .json or DataFrame to parse Returns ------- table : pandas.DataFrame DataFrame extracted from file path """ try: table = parse_table(table) except ValueError as ex: logger.error(ex) raise return table @classmethod def _parse_wind_dirs(cls, wind_dirs): """ Parse prominent direction neighbors Parameters ---------- wind_dirs : pandas.DataFrame | str Neighboring supply curve point gids and power-rose value at each cardinal direction Returns ------- wind_dirs : pandas.DataFrame Neighboring supply curve point gids and power-rose value at each cardinal direction for each sc point gid """ wind_dirs = cls._parse_table(wind_dirs) wind_dirs = wind_dirs.rename( columns=SupplyCurveField.map_from_legacy()) wind_dirs = wind_dirs.set_index(SupplyCurveField.SC_POINT_GID) columns = [c for c in wind_dirs if c.endswith(('_gid', '_pr'))] wind_dirs = wind_dirs[columns] return wind_dirs @classmethod def _parse_sc_points(cls, sc_points, offshore=False): """ Parse supply curve point summary table into sc_gid to sc_point_gid mapping and vis-versa. Parameters ---------- sc_points : pandas.DataFrame | str Supply curve point summary table offshore : bool Flag as to whether offshore farms should be included during CompetitiveWindFarms Returns ------- sc_gids : pandas.DataFrame sc_gid to sc_point_gid mapping sc_point_gids : pandas.DataFrame sc_point_gid to sc_gid mapping mask : ndarray Mask array to mask excluded sc_point_gids """ sc_points = cls._parse_table(sc_points) sc_points = sc_points.rename( columns=SupplyCurveField.map_from_legacy()) if SupplyCurveField.OFFSHORE in sc_points and not offshore: logger.debug('Not including offshore supply curve points in ' 'CompetitiveWindFarm') mask = sc_points[SupplyCurveField.OFFSHORE] == 0 sc_points = sc_points.loc[mask] mask = np.ones(int(1 + sc_points[SupplyCurveField.SC_POINT_GID].max()), dtype=bool) sc_points = sc_points[[SupplyCurveField.SC_GID, SupplyCurveField.SC_POINT_GID]] sc_gids = sc_points.set_index(SupplyCurveField.SC_GID) sc_gids = {k: int(v[0]) for k, v in sc_gids.iterrows()} groups = sc_points.groupby(SupplyCurveField.SC_POINT_GID) sc_point_gids = groups[SupplyCurveField.SC_GID].unique().to_frame() sc_point_gids = {int(k): v[SupplyCurveField.SC_GID] for k, v in sc_point_gids.iterrows()} return sc_gids, sc_point_gids, mask @staticmethod def _get_neighbors(wind_dirs, n_dirs=2): """ Parse prominent direction neighbors Parameters ---------- wind_dirs : pandas.DataFrame | str Neighboring supply curve point gids and power-rose value at each cardinal direction for each available sc point gid n_dirs : int, optional Number of prominent directions to use, by default 2 Returns ------- upwind : pandas.DataFrame Upwind neighbor gids for n prominent wind directions downwind : pandas.DataFrame Downwind neighbor gids for n prominent wind directions """ cols = [ c for c in wind_dirs if (c.endswith("_gid") and not c.startswith("sc")) ] directions = [c.split("_")[0] for c in cols] upwind_gids = wind_dirs[cols].values cols = ["{}_pr".format(d) for d in directions] neighbor_pr = wind_dirs[cols].values neighbors = np.argsort(neighbor_pr)[:, :n_dirs] upwind_gids = np.take_along_axis(upwind_gids, neighbors, axis=1) downwind_map = { "N": "S", "NE": "SW", "E": "W", "SE": "NW", "S": "N", "SW": "NE", "W": "E", "NW": "SE", } cols = ["{}_gid".format(downwind_map[d]) for d in directions] downwind_gids = wind_dirs[cols].values downwind_gids = np.take_along_axis(downwind_gids, neighbors, axis=1) downwind = {} upwind = {} for i, gid in enumerate(wind_dirs.index.values): downwind[gid] = downwind_gids[i] upwind[gid] = upwind_gids[i] return upwind, downwind
[docs] def map_sc_point_gid_to_sc_gid(self, sc_point_gid): """ Map given sc_point_gid to equivalent sc_gid(s) Parameters ---------- sc_point_gid : int Supply curve point gid to map to equivalent supply curve gid(s) Returns ------- int | list Equivalent supply curve gid(s) """ return self._sc_point_gids[sc_point_gid]
[docs] def map_sc_gid_to_sc_point_gid(self, sc_gid): """ Map given sc_gid to equivalent sc_point_gid Parameters ---------- sc_gid : int Supply curve gid to map to equivalent supply point curve gid Returns ------- int Equivalent supply point curve gid """ return self._sc_gids[sc_gid]
[docs] def check_sc_gid(self, sc_gid): """ Check to see if sc_gid is valid, if so return associated sc_point_gids Parameters ---------- sc_gid : int Supply curve gid to map to equivalent supply point curve gid Returns ------- int | None Equivalent supply point curve gid or None if sc_gid is invalid (offshore) """ sc_point_gid = None if sc_gid in self._sc_gids: sc_point_gid = self._sc_gids[sc_gid] return sc_point_gid
[docs] def map_upwind(self, sc_point_gid): """ Map given sc_point_gid to upwind neighbors Parameters ---------- sc_point_gid : int Supply point curve gid to get upwind neighbors Returns ------- int | list upwind neighborings """ return self._upwind[sc_point_gid]
[docs] def map_downwind(self, sc_point_gid): """ Map given sc_point_gid to downwind neighbors Parameters ---------- sc_point_gid : int Supply point curve gid to get downwind neighbors Returns ------- int | list downwind neighborings """ return self._downwind[sc_point_gid]
[docs] def exclude_sc_point_gid(self, sc_point_gid): """ Exclude supply curve point gid, return False if gid is not present in list of available gids to avoid key errors elsewhere Parameters ---------- sc_point_gid : int supply curve point gid to mask Returns ------- bool Flag if gid is valid and was masked """ if sc_point_gid in self._sc_point_gids: self._mask[sc_point_gid] = False out = True else: out = False return out
[docs] def remove_noncompetitive_farm( self, sc_points, sort_on="total_lcoe", downwind=False ): """ Remove neighboring sc points for given number of prominent wind directions Parameters ---------- sc_points : pandas.DataFrame | str Supply curve point summary table sort_on : str, optional column to sort on before excluding neighbors, by default 'total_lcoe' downwind : bool, optional Flag to remove downwind neighbors as well as upwind neighbors, by default False Returns ------- sc_points : pandas.DataFrame Updated supply curve points after removing non-competative wind farms """ sc_points = self._parse_table(sc_points) sc_points = sc_points.rename( columns=SupplyCurveField.map_from_legacy()) if SupplyCurveField.OFFSHORE in sc_points and not self._offshore: mask = sc_points[SupplyCurveField.OFFSHORE] == 0 sc_points = sc_points.loc[mask] sc_points = sc_points.sort_values(sort_on) sc_point_gids = sc_points[SupplyCurveField.SC_POINT_GID].values sc_point_gids = sc_point_gids.astype(int) for i in range(len(sc_points)): gid = sc_point_gids[i] if self.mask[gid]: upwind_gids = self["upwind", gid] for n in upwind_gids: self.exclude_sc_point_gid(n) if downwind: downwind_gids = self["downwind", gid] for n in downwind_gids: self.exclude_sc_point_gid(n) sc_gids = self.sc_gids mask = sc_points[SupplyCurveField.SC_GID].isin(sc_gids) return sc_points.loc[mask].reset_index(drop=True)
[docs] @classmethod def run( cls, wind_dirs, sc_points, n_dirs=2, offshore=False, sort_on="total_lcoe", downwind=False, out_fpath=None, ): """ Exclude given number of neighboring Supply Point gids based on most prominent wind directions Parameters ---------- wind_dirs : pandas.DataFrame | str path to .csv or reVX.wind_dirs.wind_dirs.WindDirs output with the neighboring supply curve point gids and power-rose value at each cardinal direction sc_points : pandas.DataFrame | str Supply curve point summary table n_dirs : int, optional Number of prominent directions to use, by default 2 offshore : bool Flag as to whether offshore farms should be included during CompetitiveWindFarms sort_on : str, optional column to sort on before excluding neighbors, by default 'total_lcoe' downwind : bool, optional Flag to remove downwind neighbors as well as upwind neighbors, by default False out_fpath : str, optional Path to .csv file to save updated sc_points to, by default None Returns ------- sc_points : pandas.DataFrame Updated supply curve points after removing non-competative wind farms """ cwf = cls(wind_dirs, sc_points, n_dirs=n_dirs, offshore=offshore) sc_points = cwf.remove_noncompetitive_farm( sc_points, sort_on=sort_on, downwind=downwind ) if out_fpath is not None: sc_points.to_csv(out_fpath, index=False) return sc_points