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