def __init__(self, site: SiteInfo, trough_config: dict): """ Parabolic trough concentrating solar power class based on SSC’s Parabolic trough (physical model) :param site: Power source site information :param trough_config: CSP configuration with the following keys: #. ``cycle_capacity_kw``: float, Power cycle design turbine gross output [kWe] #. ``solar_multiple``: float, Solar multiple [-] #. ``tes_hours``: float, Full load hours of thermal energy storage [hrs] """ financial_model = Singleowner.default('PhysicalTroughSingleOwner') # set-up param file paths # TODO: Site should have dispatch factors consistent across all models self.param_files = {'tech_model_params_path': 'tech_model_defaults.json', 'cf_params_path': 'construction_financing_defaults.json', 'wlim_series_path': 'wlim_series.csv'} rel_path_to_param_files = os.path.join('pySSC_daotk', 'trough_data') self.param_file_paths(rel_path_to_param_files) super().__init__("TroughPlant", 'trough_physical', site, financial_model, trough_config) self._dispatch: TroughDispatch = None
def default(): """Get the default PySAM object""" pv = DefaultPvwattsv5.default() obj = PySamSingleOwner.default('PVWattsSingleOwner') obj.SystemOutput.gen = pv.Outputs.ac obj.execute() return obj
def test_ReOPT(): lat = 39.7555 lon = -105.2211 # get resource and create model site = SiteInfo(flatirons_site) load = [1000 * (sin(x) + pi) for x in range(0, 8760)] urdb_label = "5ca4d1175457a39b23b3d45e" # https://openei.org/apps/IURDB/rate/view/5ca3d45ab718b30e03405898 solar_model = PVPlant(site, {'system_capacity_kw': 20000}) wind_model = WindPlant(site, { 'num_turbines': 10, "turbine_rating_kw": 2000 }) wind_model._system_model.Resource.wind_resource_filename = os.path.join( "data", "39.7555_-105.2211_windtoolkit_2012_60min_60m.srw") fin_model = so.default("GenericSystemSingleOwner") fileout = os.path.join(filepath, "REoptResultsNoExportAboveLoad.json") reopt = REopt(lat=lat, lon=lon, load_profile=load, urdb_label=urdb_label, solar_model=solar_model, wind_model=wind_model, fin_model=fin_model, interconnection_limit_kw=20000, fileout=fileout) reopt.set_rate_path(os.path.join(filepath, 'data')) reopt_site = reopt.post['Scenario']['Site'] pv = reopt_site['PV'] assert (pv['dc_ac_ratio'] == pytest.approx(1.3, 0.01)) wind = reopt_site['Wind'] assert (wind['pbi_us_dollars_per_kwh'] == pytest.approx(0.015)) results = reopt.get_reopt_results() assert (isinstance(results, dict)) print(results["outputs"]["Scenario"]["Site"]["Wind"] ['year_one_to_grid_series_kw']) if 'error' in results['outputs']['Scenario']["status"]: if 'error' in results["messages"].keys(): if 'Optimization exceeded timeout' in results["messages"]['error']: assert True else: print(results["messages"]['error']) elif 'warning' in results["messages"].keys(): print(results["messages"]['warnings']) assert True else: assert (results["outputs"]["Scenario"]["Site"]["Wind"]["size_kw"] >= 0) os.remove(fileout)
def __init__(self, site: SiteInfo, tower_config: dict): """ Tower concentrating solar power class based on SSC’s MSPT (molten salt power tower) model :param site: Power source site information :param tower_config: CSP configuration with the following keys: #. ``cycle_capacity_kw``: float, Power cycle design turbine gross output [kWe] #. ``solar_multiple``: float, Solar multiple [-] #. ``tes_hours``: float, Full load hours of thermal energy storage [hrs] #. ``optimize_field_before_sim``: (optional, default = True) bool, If True, SolarPilot's field and tower height optimization will before system simulation, o.w., SolarPilot will just generate field based on inputs. #. ``scale_input_params``: (optional, default = True) bool, If True, HOPP will run :py:func:`hybrid.tower_source.scale_params` before system simulation. """ financial_model = Singleowner.default('MSPTSingleOwner') # set-up param file paths self.param_files = { 'tech_model_params_path': 'tech_model_defaults.json', 'cf_params_path': 'construction_financing_defaults.json', 'wlim_series_path': 'wlim_series.csv', 'helio_positions_path': 'helio_positions.csv' } rel_path_to_param_files = os.path.join('pySSC_daotk', 'tower_data') self.param_file_paths(rel_path_to_param_files) super().__init__("TowerPlant", 'tcsmolten_salt', site, financial_model, tower_config) self.optimize_field_before_sim = True if 'optimize_field_before_sim' in tower_config: self.optimize_field_before_sim = tower_config[ 'optimize_field_before_sim'] # (optionally) adjust ssc input parameters based on tower capacity self.is_scale_params = False if 'scale_input_params' in tower_config and tower_config[ 'scale_input_params']: self.is_scale_params = True # Parameters to be scaled before receiver size optimization self.scale_params(params_names=[ 'helio_size', 'helio_parasitics', 'tank_heaters', 'tank_height' ]) self._dispatch: TowerDispatch = None
def test_ReOPT(): lat = 39.7555 lon = -105.2211 # get resource and create model site = SiteInfo(flatirons_site) load = [1000*(sin(x) + pi)for x in range(0, 8760)] urdb_label = "5ca4d1175457a39b23b3d45e" # https://openei.org/apps/IURDB/rate/view/5ca3d45ab718b30e03405898 solar_model = SolarPlant(site, 20000) wind_model = WindPlant(site, 20000) wind_model.system_model.Resource.wind_resource_filename = os.path.join( "data", "39.7555_-105.2211_windtoolkit_2012_60min_60m.srw") fin_model = so.default("GenericSystemSingleOwner") reopt = REopt(lat=lat, lon=lon, load_profile=load, urdb_label=urdb_label, solar_model=solar_model, wind_model=wind_model, fin_model=fin_model, interconnection_limit_kw=20000, fileout=os.path.join(filepath, "data", "REoptResultsNoExportAboveLoad.json")) reopt.set_rate_path(os.path.join(filepath, 'data')) reopt_site = reopt.post['Scenario']['Site'] pv = reopt_site['PV'] assert(pv['dc_ac_ratio'] == pytest.approx(1.2, 0.01)) wind = reopt_site['Wind'] assert(wind['pbi_us_dollars_per_kwh'] == pytest.approx(0.022)) results = reopt.get_reopt_results(force_download=True) assert(isinstance(results, dict)) print(results["outputs"]["Scenario"]["Site"]["Wind"]['year_one_to_grid_series_kw']) assert (results["outputs"]["Scenario"]["Site"]["Wind"]["size_kw"] == pytest.approx(20000, 1)) assert(results["outputs"]["Scenario"]["Site"]["Financial"]["lcc_us_dollars"] == pytest.approx(17008573.0, 1)) assert(results["outputs"]["Scenario"]["Site"]["Financial"]["lcc_bau_us_dollars"] == pytest.approx(15511546.0, 1)) assert(results["outputs"]["Scenario"]["Site"]["ElectricTariff"]["year_one_export_benefit_us_dollars"] == pytest.approx(-15158711.0, 1))
def gcr_func(x, y): # set up base a = Pvsamv1.default("FlatPlatePVSingleowner") a.SolarResource.solar_resource_file = "../sam/deploy/solar_resource/tucson_az_32.116521_-110.933042_psmv3_60_tmy.csv" b = Singleowner.default("FlatPlatePVSingleowner") # set up shading a.Shading.subarray1_shade_mode = 1 a.Layout.subarray1_nmodx = 12 a.Layout.subarray1_nmody = 2 a.SystemDesign.subarray1_gcr = float(x) land_area = a.CECPerformanceModelWithModuleDatabase.cec_area * ( a.SystemDesign.subarray1_nstrings * a.SystemDesign.subarray1_modules_per_string) / x * 0.0002471 a.execute() # total_installed_cost = total_direct_cost + permitting_total + engr_total + grid_total + landprep_total + sales_tax_total + land_total b.SystemCosts.total_installed_cost += y * land_area * 1000 b.SystemOutput.gen = a.Outputs.gen b.execute() return b.Outputs.analysis_period_irr
def __init__( self, site: SiteInfo, farm_config: dict, rating_range_kw: tuple = (1000, 3000), ): """ Set up a WindPlant :param farm_config: dict, with keys ('num_turbines', 'turbine_rating_kw', 'rotor_diameter', 'hub_height', 'layout_mode', 'layout_params') where layout_mode can be selected from the following: - 'boundarygrid': regular grid with boundary turbines, requires WindBoundaryGridParameters as 'params' - 'grid': regular grid with dx, dy distance, 0 angle; does not require 'params' :param rating_range_kw: allowable kw range of turbines, default is 1000 - 3000 kW """ self._rating_range_kw = rating_range_kw if 'model_name' in farm_config.keys(): if farm_config['model_name'] == 'floris': print('FLORIS is the system model...') system_model = Floris(farm_config, site, timestep=farm_config['timestep']) financial_model = Singleowner.default("WindPowerSingleOwner") else: raise NotImplementedError else: system_model = Windpower.default("WindPowerSingleOwner") financial_model = Singleowner.from_existing( system_model, "WindPowerSingleOwner") super().__init__("WindPlant", site, system_model, financial_model) self._system_model.value("wind_resource_data", self.site.wind_resource.data) if 'layout_mode' not in farm_config.keys(): layout_mode = 'grid' else: layout_mode = farm_config['layout_mode'] params: Optional[WindBoundaryGridParameters] = None if layout_mode == 'boundarygrid': if 'layout_params' not in farm_config.keys(): raise ValueError( "Parameters of WindBoundaryGridParameters required for boundarygrid layout mode" ) else: params: WindBoundaryGridParameters = farm_config[ 'layout_params'] self._layout = WindLayout(site, system_model, layout_mode, params) self._dispatch: WindDispatch = None if 'turbine_rating_kw' not in farm_config.keys(): raise ValueError("Turbine rating required for WindPlant") if 'num_turbines' not in farm_config.keys(): raise ValueError("Num Turbines required for WindPlant") self.turb_rating = farm_config['turbine_rating_kw'] self.num_turbines = farm_config['num_turbines'] if 'hub_height' in farm_config.keys(): self._system_model.Turbine.wind_turbine_hub_ht = farm_config[ 'hub_height'] if 'rotor_diameter' in farm_config.keys(): self.rotor_diameter = farm_config['rotor_diameter']
def run_reopt(site, scenario, load, interconnection_limit_kw, critical_load_factor, useful_life, battery_can_grid_charge, storage_used, run_reopt_flag): # kw_continuous = forced_system_size # 5 MW continuous load - equivalent to 909kg H2 per hr at 55 kWh/kg electrical intensity urdb_label = "5ca4d1175457a39b23b3d45e" # https://openei.org/apps/IURDB/rate/view/5ca3d45ab718b30e03405898 pv_config = {'system_capacity_kw': 20000} solar_model = PVPlant(site, pv_config) # ('num_turbines', 'turbine_rating_kw', 'rotor_diameter', 'hub_height', 'layout_mode', 'layout_params') wind_config = {'num_turbines': np.floor(scenario['Wind Size MW'] / scenario['Turbine Rating']), 'rotor_dimeter': scenario['Rotor Diameter'], 'hub_height': scenario['Tower Height'], 'turbine_rating_kw': scenario['Turbine Rating']} wind_model = WindPlant(site, wind_config) fin_model = so.default("GenericSystemSingleOwner") filepath = os.path.dirname(os.path.abspath(__file__)) fileout = 'reopt_result_test_intergration.json' # site = SiteInfo(sample_site, hub_height=tower_height) count = 1 reopt = REopt(lat=scenario['Lat'], lon=scenario['Long'], load_profile=load, urdb_label=urdb_label, solar_model=solar_model, wind_model=wind_model, fin_model=fin_model, interconnection_limit_kw=interconnection_limit_kw, off_grid=True, fileout=fileout) reopt.set_rate_path(os.path.join(filepath, '../data')) reopt.post['Scenario']['Site']['Wind']['installed_cost_us_dollars_per_kw'] = scenario['Wind Cost KW'] # ATB reopt.post['Scenario']['Site']['PV']['installed_cost_us_dollars_per_kw'] = scenario['Solar Cost KW'] reopt.post['Scenario']['Site']['Storage'] = {'min_kw': 0.0, 'max_kw': 0.99e9, 'min_kwh': 0.0, 'max_kwh': 0.99e9, 'internal_efficiency_pct': 0.975, 'inverter_efficiency_pct': 0.96, 'rectifier_efficiency_pct': 0.96, 'soc_min_pct': 0.2, 'soc_init_pct': 0.5, 'canGridCharge': battery_can_grid_charge, 'installed_cost_us_dollars_per_kw': scenario['Storage Cost KW'], 'installed_cost_us_dollars_per_kwh': scenario['Storage Cost KWh'], 'replace_cost_us_dollars_per_kw': scenario['Storage Cost KW'], 'replace_cost_us_dollars_per_kwh': scenario['Storage Cost KWh'], 'inverter_replacement_year': 10, 'battery_replacement_year': 10, 'macrs_option_years': 7, 'macrs_bonus_pct': 1.0, 'macrs_itc_reduction': 0.5, 'total_itc_pct': 0.0, 'total_rebate_us_dollars_per_kw': 0, 'total_rebate_us_dollars_per_kwh': 0} reopt.post['Scenario']['Site']['Financial']['analysis_years'] = useful_life if not storage_used: reopt.post['Scenario']['Site']['Storage']['max_kw'] = 0 if scenario['PTC Available']: reopt.post['Scenario']['Site']['Wind']['pbi_us_dollars_per_kwh'] = 0.022 else: reopt.post['Scenario']['Site']['Wind']['pbi_us_dollars_per_kwh'] = 0.0 if scenario['ITC Available']: reopt.post['Scenario']['Site']['PV']['federal_itc_pct'] = 0.26 else: reopt.post['Scenario']['Site']['PV']['federal_itc_pct'] = 0.0 # reopt.post['Scenario']['Site']['LoadProfile']['doe_reference_name'] = "FlatLoad" # reopt.post['Scenario']['Site']['LoadProfile']['annual_kwh'] = load #8760 * kw_continuous reopt.post['Scenario']['Site']['LoadProfile']['loads_kw'] = load reopt.post['Scenario']['Site']['LoadProfile']['critical_load_pct'] = critical_load_factor off_grid = False reopt.post['Scenario']['optimality_tolerance_techs'] = 0.05 if off_grid == True: # reopt.post['Scenario']['Site'].pop('Wind') # reopt.post['Scenario']['Site']['Wind']['min_kw'] = 10000 dictforstuff = {"off_grid_flag": True} reopt.post['Scenario'].update(dictforstuff) reopt.post['Scenario']['optimality_tolerance_techs'] = 0.05 reopt.post['Scenario']["timeout_seconds"] = 3600 # reopt.post['Scenario']['Site']['LoadProfile'].pop('annual kwh') reopt.post['Scenario']['Site'].pop('ElectricTariff') reopt.post['Scenario']['Site']['LoadProfile']['critical_load_pct'] = 1.0 f = open('massproducer_offgrid (1).json') data_for_post = json.load(f) reopt.post['Scenario']['Site']['Financial'] = data_for_post['Scenario']['Site']['Financial'] else: reopt.post['Scenario']['Site']['ElectricTariff']['wholesale_rate_us_dollars_per_kwh'] = 0.01 reopt.post['Scenario']['Site']['ElectricTariff']['wholesale_rate_above_site_load_us_dollars_per_kwh'] = 0.01 reopt.post['Scenario']['Site']['LoadProfile']['outage_start_hour'] = 10 reopt.post['Scenario']['Site']['LoadProfile']['outage_end_hour'] = 11 from pathlib import Path post_path = 'results/reopt_precomputes/reopt_post' post_path_abs = Path(__file__).parent / post_path if not os.path.exists(post_path_abs.parent): os.mkdir(post_path_abs.parent) with open(post_path_abs, 'w') as outfile: json.dump(reopt.post, outfile) # mass_producer_dict = { # "mass_units": "kg", # "time_units": "hr", # "min_mass_per_time": 10.0, # "max_mass_per_time": 10.0, # "electric_consumed_to_mass_produced_ratio_kwh_per_mass": 71.7, # "thermal_consumed_to_mass_produced_ratio_kwh_per_mass": 0.0, # "feedstock_consumed_to_mass_produced_ratio": 0.0, # "installed_cost_us_dollars_per_mass_per_time": 10.0, # "om_cost_us_dollars_per_mass_per_time": 1.5, # "om_cost_us_dollars_per_mass": 0.0, # "mass_value_us_dollars_per_mass": 5.0, # "feedstock_cost_us_dollars_per_mass": 0.0, # "macrs_option_years": 0, # "macrs_bonus_pct": 0 # } # reopt.post['Scenario']['Site']['MassProducer'] = mass_producer_dict if run_reopt_flag: #NEW METHOD load_dotenv() result = reopt.get_reopt_results() #BASIC INITIAL TEST FOR NEW METHOD # result = post_and_poll.get_api_results(data_for_post, NREL_API_KEY, 'https://offgrid-electrolyzer-reopt-dev-api.its.nrel.gov/v1', # 'reopt_result_test_intergration.json') # f = open('massproducer_offgrid (1).json') # data_for_post = json.load(f) #OLD METHOD # result = reopt.get_reopt_results(force_download=True) pickle.dump(result, open("results/reopt_precomputes/results_{}_{}_{}.p".format( scenario['Site Name'], scenario['Scenario Name'], critical_load_factor), "wb")) else: print("Not running reopt. Loading Dummy data") precompute_path = 'results/reopt_precomputes/' precompute_path_abs = Path(__file__).parent / precompute_path result = pickle.load( open(os.path.join(precompute_path_abs, "results_ATB_moderate_2020_IOWA_0.9.p"), "rb")) if result['outputs']['Scenario']['Site']['PV']['size_kw']: solar_size_mw = result['outputs']['Scenario']['Site']['PV']['size_kw'] / 1000 if result['outputs']['Scenario']['Site']['Wind']['size_kw']: wind_size_mw = result['outputs']['Scenario']['Site']['Wind']['size_kw'] / 1000 if result['outputs']['Scenario']['Site']['Storage']['size_kw']: storage_size_mw = result['outputs']['Scenario']['Site']['Storage']['size_kw'] / 1000 storage_size_mwh = result['outputs']['Scenario']['Site']['Storage']['size_kwh'] / 1000 storage_hours = storage_size_mwh / storage_size_mw reopt_site_result = result['outputs']['Scenario']['Site'] generated_date = pd.date_range(start='1/1/2018 00:00:00', end='12/31/2018 23:00:00', periods=8760) if reopt_site_result['Wind']['size_kw'] == 0: reopt_site_result['Wind']['year_one_power_production_series_kw'] = np.zeros(8760) reopt_site_result['Wind']['year_one_to_grid_series_kw'] = np.zeros(8760) reopt_site_result['Wind']['year_one_to_load_series_kw'] = np.zeros(8760) reopt_site_result['Wind']['year_one_to_battery_series_kw'] = np.zeros(8760) reopt_site_result['Wind']['year_one_curtailed_production_series_kw'] = np.zeros(8760) wind_size_mw = 0 if reopt_site_result['PV']['size_kw'] == 0: reopt_site_result['PV']['year_one_power_production_series_kw'] = np.zeros(8760) reopt_site_result['PV']['year_one_to_grid_series_kw'] = np.zeros(8760) reopt_site_result['PV']['year_one_to_load_series_kw'] = np.zeros(8760) reopt_site_result['PV']['year_one_to_battery_series_kw'] = np.zeros(8760) reopt_site_result['PV']['year_one_curtailed_production_series_kw'] = np.zeros(8760) solar_size_mw = 0 if reopt_site_result['Storage']['size_kw'] == 0: reopt_site_result['Storage']['year_one_soc_series_pct'] = np.zeros(8760) reopt_site_result['Storage']['year_one_to_massproducer_series_kw'] = np.zeros(8760) storage_size_mw = 0 storage_size_mwh = 0 storage_hours = 0 combined_pv_wind_power_production = [x + y for x, y in zip(reopt_site_result['PV']['year_one_power_production_series_kw'] , reopt_site_result['Wind']['year_one_power_production_series_kw'])] combined_pv_wind_storage_power_production = [x + y for x, y in zip(combined_pv_wind_power_production, reopt_site_result['Storage'][ 'year_one_to_load_series_kw'])] energy_shortfall = [y - x for x, y in zip(combined_pv_wind_storage_power_production, load)] energy_shortfall = [x if x > 0 else 0 for x in energy_shortfall] combined_pv_wind_curtailment = [x + y for x, y in zip(reopt_site_result['PV']['year_one_curtailed_production_series_kw'] , reopt_site_result['Wind']['year_one_curtailed_production_series_kw'])] reopt_result_dict = {'Date': generated_date, 'pv_power_production': reopt_site_result['PV'] ['year_one_power_production_series_kw'], 'pv_power_to_grid': reopt_site_result['PV'] ['year_one_to_grid_series_kw'], 'pv_power_to_load': reopt_site_result['PV']['year_one_to_load_series_kw'], 'pv_power_to_battery': reopt_site_result['PV']['year_one_to_battery_series_kw'], 'pv_power_curtailed': reopt_site_result['PV']['year_one_curtailed_production_series_kw'], 'wind_power_production': reopt_site_result['Wind'] ['year_one_power_production_series_kw'], 'wind_power_to_grid': reopt_site_result['Wind'] ['year_one_to_grid_series_kw'], 'wind_power_to_load': reopt_site_result['Wind']['year_one_to_load_series_kw'], 'wind_power_to_battery': reopt_site_result['Wind']['year_one_to_battery_series_kw'], 'wind_power_curtailed': reopt_site_result['Wind']['year_one_curtailed_production_series_kw'], 'combined_pv_wind_power_production': combined_pv_wind_power_production, 'combined_pv_wind_storage_power_production': combined_pv_wind_storage_power_production, 'storage_power_to_load': reopt_site_result['Storage']['year_one_to_load_series_kw'], 'storage_power_to_grid': reopt_site_result['Storage']['year_one_to_grid_series_kw'], 'battery_soc_pct': reopt_site_result['Storage']['year_one_soc_series_pct'], 'energy_shortfall': energy_shortfall, 'combined_pv_wind_curtailment': combined_pv_wind_curtailment } REoptResultsDF = pd.DataFrame(reopt_result_dict) return wind_size_mw, solar_size_mw, storage_size_mw, storage_size_mwh, storage_hours, result, REoptResultsDF