Source code for reV.SAM.windbos

# -*- coding: utf-8 -*-
"""SAM Wind Balance of System Cost Model"""

from copy import deepcopy

import numpy as np
from PySAM.PySSC import ssc_sim_from_dict

from reV.utilities.exceptions import SAMInputError


[docs]class WindBos: """Wind Balance of System Cost Model.""" MODULE = "windbos" # keys for the windbos input data dictionary. # Some keys may not be found explicitly in the SAM input. KEYS = ( "tech_model", "financial_model", "machine_rating", "rotor_diameter", "hub_height", "number_of_turbines", "interconnect_voltage", "distance_to_interconnect", "site_terrain", "turbine_layout", "soil_condition", "construction_time", "om_building_size", "quantity_test_met_towers", "quantity_permanent_met_towers", "weather_delay_days", "crane_breakdowns", "access_road_entrances", "turbine_capital_cost", "turbine_cost_per_kw", "tower_top_mass", "delivery_assist_required", "pad_mount_transformer_required", "new_switchyard_required", "rock_trenching_required", "mv_thermal_backfill", "mv_overhead_collector", "performance_bond", "contingency", "warranty_management", "sales_and_use_tax", "overhead", "profit_margin", "development_fee", "turbine_transportation", ) def __init__(self, inputs): """ Parameters ---------- inputs : dict SAM key value pair inputs. """ self._turbine_capital_cost = 0.0 self._datadict = {} self._inputs = inputs self._special = { "tech_model": "windbos", "financial_model": "none", "machine_rating": self.machine_rating, "hub_height": self.hub_height, "rotor_diameter": self.rotor_diameter, "number_of_turbines": self.number_of_turbines, "turbine_capital_cost": self.turbine_capital_cost, } self._parse_inputs() self._out = ssc_sim_from_dict(self._datadict) def _parse_inputs(self): """Parse SAM inputs into a windbos input dict and perform any required special operations.""" for k in self.KEYS: if k in self._special: self._datadict[k] = self._special[k] elif k not in self._inputs: raise SAMInputError( 'Windbos requires input key: "{}"'.format(k) ) else: self._datadict[k] = self._inputs[k] @property def machine_rating(self): """Single turbine machine rating either from input or power curve.""" if "machine_rating" in self._inputs: return self._inputs["machine_rating"] else: return np.max(self._inputs["wind_turbine_powercurve_powerout"]) @property def hub_height(self): """Turbine hub height.""" if "wind_turbine_hub_ht" in self._inputs: return self._inputs["wind_turbine_hub_ht"] else: return self._inputs["hub_height"] @property def rotor_diameter(self): """Turbine rotor diameter.""" if "wind_turbine_rotor_diameter" in self._inputs: return self._inputs["wind_turbine_rotor_diameter"] else: return self._inputs["rotor_diameter"] @property def number_of_turbines(self): """Number of turbines either based on input or system (farm) capacity and machine rating""" if "number_of_turbines" in self._inputs: return self._inputs["number_of_turbines"] else: return ( self._inputs['system_capacity'] / self.machine_rating ) @property def turbine_capital_cost(self): """Returns zero (no turbine capital cost for WindBOS input, and assigns any input turbine_capital_cost to an attr""" if "turbine_capital_cost" in self._inputs: self._turbine_capital_cost = self._inputs["turbine_capital_cost"] else: self._turbine_capital_cost = 0.0 return 0.0 @property def bos_cost(self): """Get the balance of system cost ($).""" return self._out["project_total_budgeted_cost"] @property def turbine_cost(self): """Get the turbine cost ($).""" tcost = ( self._inputs["turbine_cost_per_kw"] * self.machine_rating * self.number_of_turbines ) + (self._turbine_capital_cost * self.number_of_turbines) return tcost @property def sales_tax_mult(self): """Get a sales tax multiplier (frac of the total installed cost).""" basis = self._inputs.get("sales_tax_basis", 0) / 100 tax = self._datadict.get("sales_and_use_tax", 0) / 100 return basis * tax @property def sales_tax_cost(self): """Get the cost of sales tax ($).""" return (self.bos_cost + self.turbine_cost) * self.sales_tax_mult @property def total_installed_cost(self): """Get the total installed cost ($) (bos + turbine).""" return self.bos_cost + self.turbine_cost + self.sales_tax_cost @property def output(self): """Get a dictionary containing the cost breakdown.""" output = { "total_installed_cost": self.total_installed_cost, "turbine_cost": self.turbine_cost, "sales_tax_cost": self.sales_tax_cost, "bos_cost": self.bos_cost, } return output # pylint: disable-msg=W0613
[docs] @classmethod def reV_run( cls, points_control, site_df, output_request=("total_installed_cost",), **kwargs, ): """Execute SAM SingleOwner simulations based on reV points control. Parameters ---------- points_control : config.PointsControl PointsControl instance containing project points site and SAM config info. site_df : pd.DataFrame Dataframe of site-specific input variables. Row index corresponds to site number/gid (via df.loc not df.iloc), column labels are the variable keys that will be passed forward as SAM parameters. output_request : list | tuple | str Output(s) to retrieve from SAM. kwargs : dict Not used but maintained for polymorphic calls with other SAM econ reV_run() methods (lcoe and single owner). Breaks pylint error W0613: unused argument. Returns ------- out : dict Nested dictionaries where the top level key is the site index, the second level key is the variable name, second level value is the output variable value. """ out = {} for site in points_control.sites: # get SAM inputs from project_points based on the current site _, inputs = points_control.project_points[site] # ensure that site-specific data is not persisted to other sites site_inputs = deepcopy(inputs) site_inputs.update(dict(site_df.loc[site, :])) wb = cls(site_inputs) out[site] = { k: v for k, v in wb.output.items() if k in output_request } return out