def test_tower_with_dispatch_model(site): """Testing pySSC tower model using HOPP built-in dispatch model""" expected_energy = 3842225.688 interconnection_size_kw = 50000 technologies = { 'tower': { 'cycle_capacity_kw': 50 * 1000, 'solar_multiple': 2.0, 'tes_hours': 6.0, 'optimize_field_before_sim': False } } system = HybridSimulation(technologies, site, interconnect_kw=interconnection_size_kw, dispatch_options={ 'is_test_start_year': True, 'is_test_end_year': True }) system.tower.value('helio_width', 10.0) system.tower.value('helio_height', 10.0) system.tower.value('h_tower', 117.7) system.tower.value('rec_height', 11.3) system.tower.value('D_rec', 11.12) system.ppa_price = (0.12, ) system.simulate() assert system.tower.annual_energy_kwh == pytest.approx( expected_energy, 1e-2) # Check dispatch targets disp_outputs = system.tower.outputs.dispatch ssc_outputs = system.tower.outputs.ssc_time_series for i in range(len(ssc_outputs['gen'])): # cycle start-up allowed target = 1 if (disp_outputs['is_cycle_generating'][i] + disp_outputs['is_cycle_starting'][i]) > 0.01 else 0 assert target == pytest.approx(ssc_outputs['is_pc_su_allowed'][i], 1e-5) # receiver start-up allowed target = 1 if (disp_outputs['is_field_generating'][i] + disp_outputs['is_field_starting'][i]) > 0.01 else 0 assert target == pytest.approx(ssc_outputs['is_rec_su_allowed'][i], 1e-5) # cycle thermal power start_power = system.tower.dispatch.allowable_cycle_startup_power if disp_outputs[ 'is_cycle_starting'][i] else 0 target = disp_outputs['cycle_thermal_power'][i] + start_power assert target == pytest.approx(ssc_outputs['q_dot_pc_target_on'][i], 1e-3) # thermal energy storage state-of-charge if i % system.dispatch_builder.options.n_roll_periods == 0: tes_estimate = disp_outputs['thermal_energy_storage'][i] tes_actual = ssc_outputs['e_ch_tes'][i] assert tes_estimate == pytest.approx(tes_actual, 0.01)
def test_trough_with_dispatch_model(site): """Testing pySSC tower model using HOPP built-in dispatch model""" expected_energy = 1873589.560 interconnection_size_kw = 50000 technologies = { 'trough': { 'cycle_capacity_kw': 50 * 1000, 'solar_multiple': 2.0, 'tes_hours': 6.0 } } system = HybridSimulation(technologies, site, interconnect_kw=interconnection_size_kw, dispatch_options={ 'is_test_start_year': True, 'is_test_end_year': True }) system.ppa_price = (0.12, ) system.simulate() assert system.trough.annual_energy_kwh == pytest.approx( expected_energy, 1e-2)
def test_hybrid_dispatch_financials(site): wind_solar_battery = {key: technologies[key] for key in ('pv', 'wind', 'battery')} hybrid_plant = HybridSimulation(wind_solar_battery, site, interconnect_mw * 1000, dispatch_options={'grid_charging': True}) hybrid_plant.ppa_price = (0.06,) hybrid_plant.simulate(1) assert sum(hybrid_plant.battery.Outputs.P) < 0.0
def test_hybrid_dispatch_one_cycle_heuristic(site): dispatch_options = {'battery_dispatch': 'one_cycle_heuristic', 'grid_charging': False} wind_solar_battery = {key: technologies[key] for key in ('pv', 'wind', 'battery')} hybrid_plant = HybridSimulation(wind_solar_battery, site, interconnect_mw * 1000, dispatch_options=dispatch_options) hybrid_plant.simulate(1) assert sum(hybrid_plant.battery.Outputs.P) < 0.0
def test_hybrid_tax_incentives(site): wind_pv_battery = { key: technologies[key] for key in ('pv', 'wind', 'battery') } hybrid_plant = HybridSimulation( wind_pv_battery, site, interconnect_kw=interconnection_size_kw, dispatch_options={'battery_dispatch': 'one_cycle_heuristic'}) hybrid_plant.ppa_price = (0.03, ) hybrid_plant.pv.dc_degradation = [0] * 25 hybrid_plant.pv._financial_model.TaxCreditIncentives.itc_fed_percent = 0.0 hybrid_plant.wind._financial_model.TaxCreditIncentives.ptc_fed_amount = ( 1, ) hybrid_plant.pv._financial_model.TaxCreditIncentives.ptc_fed_amount = (2, ) hybrid_plant.battery._financial_model.TaxCreditIncentives.ptc_fed_amount = ( 3, ) hybrid_plant.wind._financial_model.TaxCreditIncentives.ptc_fed_escal = 0 hybrid_plant.pv._financial_model.TaxCreditIncentives.ptc_fed_escal = 0 hybrid_plant.battery._financial_model.TaxCreditIncentives.ptc_fed_escal = 0 hybrid_plant.simulate() ptc_wind = hybrid_plant.wind._financial_model.value("cf_ptc_fed")[1] assert ptc_wind == approx( hybrid_plant.wind._financial_model.value("ptc_fed_amount")[0] * hybrid_plant.wind.annual_energy_kwh, rel=1e-3) ptc_pv = hybrid_plant.pv._financial_model.value("cf_ptc_fed")[1] assert ptc_pv == approx( hybrid_plant.pv._financial_model.value("ptc_fed_amount")[0] * hybrid_plant.pv.annual_energy_kwh, rel=1e-3) ptc_batt = hybrid_plant.battery._financial_model.value("cf_ptc_fed")[1] assert ptc_batt == approx( hybrid_plant.battery._financial_model.value("ptc_fed_amount")[0] * hybrid_plant.battery._financial_model.LCOS. batt_annual_discharge_energy[1], rel=1e-3) ptc_hybrid = hybrid_plant.grid._financial_model.value("cf_ptc_fed")[1] ptc_fed_amount = hybrid_plant.grid._financial_model.value( "ptc_fed_amount")[0] assert ptc_fed_amount == approx(1.229, rel=1e-2) assert ptc_hybrid == approx( ptc_fed_amount * hybrid_plant.grid._financial_model.Outputs.cf_energy_net[1], rel=1e-3)
def test_hybrid_wind_only(site): wind_only = {'wind': technologies['wind']} hybrid_plant = HybridSimulation(wind_only, site, interconnect_kw=interconnection_size_kw) hybrid_plant.layout.plot() hybrid_plant.ppa_price = (0.01, ) hybrid_plant.simulate(25) aeps = hybrid_plant.annual_energies npvs = hybrid_plant.net_present_values assert aeps.wind == approx(33615479, 1e3) assert aeps.hybrid == approx(33615479, 1e3) assert npvs.wind == approx(-13692784, 1e3) assert npvs.hybrid == approx(-13692784, 1e3)
def test_pv_wind_battery_hybrid_dispatch(site): expected_objective = 39460.698 wind_solar_battery = {key: technologies[key] for key in ('pv', 'wind', 'battery')} hybrid_plant = HybridSimulation(wind_solar_battery, site, interconnect_mw * 1000, dispatch_options={'grid_charging': False, 'include_lifecycle_count': False}) hybrid_plant.grid.value("federal_tax_rate", (0., )) hybrid_plant.grid.value("state_tax_rate", (0., )) hybrid_plant.ppa_price = (0.06, ) hybrid_plant.pv.dc_degradation = [0.5] * 1 hybrid_plant.pv.simulate(1) hybrid_plant.wind.simulate(1) hybrid_plant.dispatch_builder.dispatch.initialize_parameters() hybrid_plant.dispatch_builder.dispatch.update_time_series_parameters(0) hybrid_plant.battery.dispatch.initial_SOC = hybrid_plant.battery.dispatch.minimum_soc # Set to min SOC results = HybridDispatchBuilderSolver.glpk_solve_call(hybrid_plant.dispatch_builder.pyomo_model) assert results.solver.termination_condition == TerminationCondition.optimal gross_profit_objective = pyomo.value(hybrid_plant.dispatch_builder.dispatch.objective_value) assert gross_profit_objective == pytest.approx(expected_objective, 1e-3) n_look_ahead_periods = hybrid_plant.dispatch_builder.options.n_look_ahead_periods available_resource = hybrid_plant.pv.generation_profile[0:n_look_ahead_periods] dispatch_generation = hybrid_plant.pv.dispatch.generation for t in hybrid_plant.dispatch_builder.pyomo_model.forecast_horizon: assert dispatch_generation[t] * 1e3 == pytest.approx(available_resource[t], 1e-3) available_resource = hybrid_plant.wind.generation_profile[0:n_look_ahead_periods] dispatch_generation = hybrid_plant.wind.dispatch.generation for t in hybrid_plant.dispatch_builder.pyomo_model.forecast_horizon: assert dispatch_generation[t] * 1e3 == pytest.approx(available_resource[t], 1e-3) assert sum(hybrid_plant.battery.dispatch.charge_power) > 0.0 assert sum(hybrid_plant.battery.dispatch.discharge_power) > 0.0 assert (sum(hybrid_plant.battery.dispatch.charge_power) * hybrid_plant.battery.dispatch.round_trip_efficiency / 100.0 == pytest.approx(sum(hybrid_plant.battery.dispatch.discharge_power))) transmission_limit = hybrid_plant.grid.value('grid_interconnection_limit_kwac') system_generation = hybrid_plant.grid.dispatch.system_generation for t in hybrid_plant.dispatch_builder.pyomo_model.forecast_horizon: assert system_generation[t] * 1e3 <= transmission_limit + 1e-3 assert system_generation[t] * 1e3 >= 0.0
def init_simulation_pv(): """ Create the simulation object needed to calculate the objective of the problem :return: The HOPP simulation as defined for this problem """ # Create the site for the design evaluation site = 'irregular' location = locations[3] if site == 'circular': site_data = make_circular_site(lat=location[0], lon=location[1], elev=location[2]) elif site == 'irregular': site_data = make_irregular_site(lat=location[0], lon=location[1], elev=location[2]) else: raise Exception("Unknown site '" + site + "'") # Load in weather and price data files solar_file = Path( __file__).parent.parent / "resource_files" / "solar" / WEATHER_FILE #"Beni_Miha" / "659265_32.69_10.90_2019.csv" grid_file = Path(__file__).parent.parent / "resource_files" / "grid" / PRICE_FILE #"tunisia_est_grid_prices.csv" # Combine the data into a site definition site_info = SiteInfo(site_data, solar_resource_file=solar_file, grid_resource_file=grid_file) # set up hybrid simulation with all the required parameters solar_size_mw = 200 battery_capacity_mwh = 15 battery_capacity_mw = 100 interconnection_size_mw = 100 technologies = {'pv': {'system_capacity_kw': solar_size_mw * 1000, 'array_type': 2, 'dc_ac_ratio': 1.1}, 'battery': {'system_capacity_kwh': battery_capacity_mwh * 1000, 'system_capacity_kw': battery_capacity_mw * 1000}, 'grid': interconnection_size_mw * 1000} # Create the hybrid plant simulation # TODO: turn these off to run full year simulation dispatch_options = {'is_test_start_year': False, 'is_test_end_year': False, 'solver': 'gurobi_ampl', 'grid_charging': False, 'pv_charging_only': True} # TODO: turn-on receiver and field optimization before... initial simulation hybrid_plant = HybridSimulation(technologies, site_info, interconnect_kw=interconnection_size_mw * 1000, dispatch_options=dispatch_options) # Customize the hybrid plant assumptions here... hybrid_plant.pv.value('inv_eff', 95.0) hybrid_plant.pv.value('array_type', 0) hybrid_plant.pv.dc_degradation = [0] * 25 return hybrid_plant
def test_hybrid_om_costs_error(site): wind_pv_battery = { key: technologies[key] for key in ('pv', 'wind', 'battery') } hybrid_plant = HybridSimulation( wind_pv_battery, site, interconnect_kw=interconnection_size_kw, dispatch_options={'battery_dispatch': 'one_cycle_heuristic'}) hybrid_plant.ppa_price = (0.03, ) hybrid_plant.pv.dc_degradation = [0] * 25 hybrid_plant.battery._financial_model.SystemCosts.om_production = (1, ) try: hybrid_plant.simulate() except ValueError as e: assert e
def test_tower_pv_battery_hybrid(site): interconnection_size_kw_test = 50000 technologies_test = { 'tower': { 'cycle_capacity_kw': 50 * 1000, 'solar_multiple': 2.0, 'tes_hours': 12.0 }, 'pv': { 'system_capacity_kw': 50 * 1000 }, 'battery': { 'system_capacity_kwh': 40 * 1000, 'system_capacity_kw': 20 * 1000 } } solar_hybrid = { key: technologies_test[key] for key in ('tower', 'pv', 'battery') } hybrid_plant = HybridSimulation( solar_hybrid, site, interconnect_kw=interconnection_size_kw_test, dispatch_options={ 'is_test_start_year': True, 'is_test_end_year': True }) hybrid_plant.ppa_price = (0.12, ) # $/kWh hybrid_plant.pv.dc_degradation = [0] * 25 hybrid_plant.tower.value('helio_width', 10.0) hybrid_plant.tower.value('helio_height', 10.0) hybrid_plant.simulate() aeps = hybrid_plant.annual_energies npvs = hybrid_plant.net_present_values assert aeps.pv == approx(104053614.17, 1e-3) assert aeps.tower == approx(3769716.50, 5e-2) assert aeps.battery == approx(-9449.70, 2e-1) assert aeps.hybrid == approx(107882747.80, 1e-2) assert npvs.pv == approx(45233832.23, 1e3)
def test_hybrid_pv_only(site): solar_only = {'pv': technologies['pv']} hybrid_plant = HybridSimulation(solar_only, site, interconnect_kw=interconnection_size_kw) hybrid_plant.layout.plot() hybrid_plant.ppa_price = (0.01, ) hybrid_plant.pv.dc_degradation = [0] * 25 hybrid_plant.simulate() aeps = hybrid_plant.annual_energies npvs = hybrid_plant.net_present_values assert aeps.pv == approx(9884106.55, 1e-3) assert aeps.hybrid == approx(9884106.55, 1e-3) assert npvs.pv == approx(-5121293, 1e3) assert npvs.hybrid == approx(-5121293, 1e3)
def test_hybrid_dispatch_heuristic(site): dispatch_options = {'battery_dispatch': 'heuristic', 'grid_charging': False} wind_solar_battery = {key: technologies[key] for key in ('pv', 'wind', 'battery')} hybrid_plant = HybridSimulation(wind_solar_battery, site, interconnect_mw * 1000, dispatch_options=dispatch_options) fixed_dispatch = [0.0]*6 fixed_dispatch.extend([-1.0]*6) fixed_dispatch.extend([1.0]*6) fixed_dispatch.extend([0.0]*6) hybrid_plant.battery.dispatch.user_fixed_dispatch = fixed_dispatch hybrid_plant.simulate(1) assert sum(hybrid_plant.battery.dispatch.charge_power) > 0.0 assert sum(hybrid_plant.battery.dispatch.discharge_power) > 0.0
def test_tower_field_optimize_before_sim(site): """Testing pySSC tower model using HOPP built-in dispatch model""" interconnection_size_kw = 50000 technologies = { 'tower': { 'cycle_capacity_kw': 50 * 1000, 'solar_multiple': 2.0, 'tes_hours': 6.0, 'optimize_field_before_sim': True }, 'grid': 50000 } system = {key: technologies[key] for key in ('tower', 'grid')} system = HybridSimulation(system, site, interconnect_kw=interconnection_size_kw, dispatch_options={'is_test_start_year': True}) system.ppa_price = (0.12, ) system.tower.value('helio_width', 10.0) system.tower.value('helio_height', 10.0) system.tower.generate_field() # Get old field: field_parameters = [ 'N_hel', 'D_rec', 'rec_height', 'h_tower', 'land_area_base', 'A_sf_in' ] old_values = {} for k in field_parameters: old_values[k] = system.tower.value(k) system.simulate() new_values = {} for k in field_parameters: new_values[k] = system.tower.value(k) assert system.tower.optimize_field_before_sim for k in field_parameters: assert old_values[k] != new_values[k]
def test_hybrid(site): """ Performance from Wind is slightly different from wind-only case because the solar presence modified the wind layout """ solar_wind_hybrid = {key: technologies[key] for key in ('pv', 'wind')} hybrid_plant = HybridSimulation(solar_wind_hybrid, site, interconnect_kw=interconnection_size_kw) hybrid_plant.layout.plot() hybrid_plant.ppa_price = (0.01, ) hybrid_plant.pv.dc_degradation = [0] * 25 hybrid_plant.simulate() # plt.show() aeps = hybrid_plant.annual_energies npvs = hybrid_plant.net_present_values assert aeps.pv == approx(8703525.94, 13) assert aeps.wind == approx(33615479.57, 1e3) assert aeps.hybrid == approx(41681662.63, 1e3) assert npvs.pv == approx(-5121293, 1e3) assert npvs.wind == approx(-13909363, 1e3) assert npvs.hybrid == approx(-19216589, 1e3)
def test_hybrid_solar_battery_dispatch(site): expected_objective = 20819.456 solar_battery_technologies = {k: technologies[k] for k in ('pv', 'battery')} hybrid_plant = HybridSimulation(solar_battery_technologies, site, interconnect_mw * 1000, dispatch_options={'grid_charging': False}) hybrid_plant.grid.value("federal_tax_rate", (0., )) hybrid_plant.grid.value("state_tax_rate", (0., )) hybrid_plant.pv.dc_degradation = [0.5] * 1 hybrid_plant.pv.simulate(1) hybrid_plant.dispatch_builder.dispatch.initialize_parameters() hybrid_plant.dispatch_builder.dispatch.update_time_series_parameters(0) hybrid_plant.battery.dispatch.initial_SOC = hybrid_plant.battery.dispatch.minimum_soc # Set to min SOC n_look_ahead_periods = hybrid_plant.dispatch_builder.options.n_look_ahead_periods # This was done because the default peak prices coincide with solar production... available_resource = hybrid_plant.pv.generation_profile[0:n_look_ahead_periods] prices = [0.] * len(available_resource) for t in hybrid_plant.dispatch_builder.pyomo_model.forecast_horizon: if available_resource[t] > 0.0: prices[t] = 30.0 else: prices[t] = 110.0 hybrid_plant.grid.dispatch.electricity_sell_price = prices hybrid_plant.grid.dispatch.electricity_purchase_price = prices results = HybridDispatchBuilderSolver.glpk_solve_call(hybrid_plant.dispatch_builder.pyomo_model) assert results.solver.termination_condition == TerminationCondition.optimal gross_profit_objective = pyomo.value(hybrid_plant.dispatch_builder.dispatch.objective_value) assert gross_profit_objective == pytest.approx(expected_objective, 1e-3) available_resource = hybrid_plant.pv.generation_profile[0:n_look_ahead_periods] dispatch_generation = hybrid_plant.pv.dispatch.generation for t in hybrid_plant.dispatch_builder.pyomo_model.forecast_horizon: assert dispatch_generation[t] * 1e3 == pytest.approx(available_resource[t], 1e-3) assert sum(hybrid_plant.battery.dispatch.charge_power) > 0.0 assert sum(hybrid_plant.battery.dispatch.discharge_power) > 0.0 assert (sum(hybrid_plant.battery.dispatch.charge_power) * hybrid_plant.battery.dispatch.round_trip_efficiency / 100.0 == pytest.approx(sum(hybrid_plant.battery.dispatch.discharge_power))) transmission_limit = hybrid_plant.grid.value('grid_interconnection_limit_kwac') system_generation = hybrid_plant.grid.dispatch.system_generation for t in hybrid_plant.dispatch_builder.pyomo_model.forecast_horizon: assert system_generation[t] * 1e3 <= transmission_limit assert system_generation[t] * 1e3 >= 0.0
def init_simulation_csp(): """ Create the simulation object needed to calculate the objective of the problem :return: The HOPP simulation as defined for this problem """ # Create the site for the design evaluation site = 'irregular' location = locations[3] if site == 'circular': site_data = make_circular_site(lat=location[0], lon=location[1], elev=location[2]) elif site == 'irregular': site_data = make_irregular_site(lat=location[0], lon=location[1], elev=location[2]) else: raise Exception("Unknown site '" + site + "'") # Load in weather and price data files solar_file = Path( __file__).parent.parent / "resource_files" / "solar" / WEATHER_FILE #"Beni_Miha" / "659265_32.69_10.90_2019.csv" grid_file = Path(__file__).parent.parent / "resource_files" / "grid" / PRICE_FILE #"tunisia_est_grid_prices.csv" # Combine the data into a site definition site_info = SiteInfo(site_data, solar_resource_file=solar_file, grid_resource_file=grid_file) # set up hybrid simulation with all the required parameters tower_cycle_mw = 100 interconnection_size_mw = 100 technologies = {'tower': {'cycle_capacity_kw': tower_cycle_mw * 1000, 'solar_multiple': 2.0, 'tes_hours': 12.0, 'optimize_field_before_sim': True}, # TODO: turn on 'grid': interconnection_size_mw * 1000} # Create the hybrid plant simulation # TODO: turn these off to run full year simulation dispatch_options = {'is_test_start_year': False, 'is_test_end_year': False, 'solver': 'gurobi_ampl'} # TODO: turn-on receiver and field optimization before... initial simulation hybrid_plant = HybridSimulation(technologies, site_info, interconnect_kw=interconnection_size_mw * 1000, dispatch_options=dispatch_options) return hybrid_plant
def test_trough_pv_hybrid(site): interconnection_size_kw_test = 50000 technologies_test = { 'trough': { 'cycle_capacity_kw': 50 * 1000, 'solar_multiple': 2.0, 'tes_hours': 12.0 }, 'pv': { 'system_capacity_kw': 50 * 1000 } } solar_hybrid = {key: technologies_test[key] for key in ('trough', 'pv')} hybrid_plant = HybridSimulation( solar_hybrid, site, interconnect_kw=interconnection_size_kw_test, dispatch_options={ 'is_test_start_year': True, 'is_test_end_year': True }) hybrid_plant.ppa_price = (0.12, ) # $/kWh hybrid_plant.pv.dc_degradation = [0] * 25 hybrid_plant.simulate() aeps = hybrid_plant.annual_energies npvs = hybrid_plant.net_present_values assert aeps.pv == approx(104053614.17, 1e-3) assert aeps.trough == approx(1871471.58, 2e-2) assert aeps.hybrid == approx(105926003.55, 1e-3) assert npvs.pv == approx(45233832.23, 1e3)
def init_hybrid_plant(techs_in_sim: list, is_test: bool = False, ud_techs: dict = {}): """ Initialize hybrid simulation object using specific project inputs :param techs_in_sim: List of technologies to include in the simulation :param is_test: if True, runs dispatch for the first and last 5 days of the year and turns off tower and receiver optimization :param ud_techs: Dictionary containing technology initialization parameters required by HybridSimulation :return: HybridSimulation as defined for this problem """ schedule_scale = 100 # MWe grid_interconnect_mw = 100 # MWe example_root = get_example_path_root() # Set plant location site_data = { "lat": 34.8653, "lon": -116.7830, "elev": 561, "tz": 1, "no_wind": True } solar_file = example_root + "02_weather_data/daggett_ca_34.865371_-116.783023_psmv3_60_tmy.csv" prices_file = example_root + "03_cost_load_price_data/constant_norm_prices.csv" desired_schedule_file = example_root + "03_cost_load_price_data/desired_schedule_normalized.csv" # Reading in desired schedule with open(desired_schedule_file) as f: csvreader = csv.reader(f) desired_schedule = [] for row in csvreader: desired_schedule.append(float(row[0]) * schedule_scale) # If normalized pricing is used, then PPA price must be adjusted after HybridSimulation is initialized site = SiteInfo(site_data, solar_resource_file=solar_file, grid_resource_file=prices_file, desired_schedule=desired_schedule) # Load in system costs with open(example_root + "03_cost_load_price_data/system_costs_SAM.json") as f: cost_info = json.load(f) # Initializing technologies if ud_techs: technologies = ud_techs else: technologies = { 'tower': { 'cycle_capacity_kw': 200 * 1000, #100 'solar_multiple': 4.0, #2.0 'tes_hours': 20.0, #14 'optimize_field_before_sim': not is_test, 'scale_input_params': True, }, 'trough': { 'cycle_capacity_kw': 200 * 1000, 'solar_multiple': 6.0, 'tes_hours': 28.0 }, 'pv': { 'system_capacity_kw': 120 * 1000 }, 'battery': { 'system_capacity_kwh': 200 * 1000, 'system_capacity_kw': 100 * 1000 }, 'grid': grid_interconnect_mw * 1000 } # Create hybrid simulation class based on the technologies needed in the simulation sim_techs = {key: technologies[key] for key in techs_in_sim} sim_techs['grid'] = technologies['grid'] hybrid_plant = HybridSimulation(sim_techs, site, interconnect_kw=technologies['grid'], dispatch_options={ 'is_test_start_year': is_test, 'is_test_end_year': is_test, 'solver': 'cbc', 'grid_charging': False, 'pv_charging_only': True }, cost_info=cost_info['cost_info']) csp_dispatch_obj_costs = { 'cost_per_field_generation': 0.5, 'cost_per_field_start_rel': 0.0, 'cost_per_cycle_generation': 2.0, 'cost_per_cycle_start_rel': 0.0, 'cost_per_change_thermal_input': 0.5 } # Set CSP costs if hybrid_plant.tower: hybrid_plant.tower.ssc.set(cost_info['tower_costs']) hybrid_plant.tower.dispatch.objective_cost_terms = csp_dispatch_obj_costs if hybrid_plant.trough: hybrid_plant.trough.ssc.set(cost_info['trough_costs']) hybrid_plant.trough.dispatch.objective_cost_terms = csp_dispatch_obj_costs # Set O&M costs for all technologies for tech in ['tower', 'trough', 'pv', 'battery']: if not tech in techs_in_sim: cost_info["SystemCosts"].pop(tech) hybrid_plant.assign(cost_info["SystemCosts"]) # Set financial parameters for singleowner model with open(example_root + '03_cost_load_price_data/financial_parameters_SAM.json') as f: fin_info = json.load(f) hybrid_plant.assign(fin_info["FinancialParameters"]) hybrid_plant.assign(fin_info["TaxCreditIncentives"]) hybrid_plant.assign(fin_info["Revenue"]) hybrid_plant.assign(fin_info["Depreciation"]) hybrid_plant.assign(fin_info["PaymentIncentives"]) # Set specific technology assumptions here if hybrid_plant.pv: hybrid_plant.pv.dc_degradation = [0.5] * 25 hybrid_plant.pv.value('array_type', 2) # 1-axis tracking hybrid_plant.pv.value('tilt', 0) # Tilt for 1-axis # This is required if normalized prices are provided hybrid_plant.ppa_price = (0.10, ) # $/kWh return hybrid_plant
def test_wind_pv_with_storage_dispatch(site): wind_pv_battery = { key: technologies[key] for key in ('pv', 'wind', 'battery') } hybrid_plant = HybridSimulation(wind_pv_battery, site, interconnect_kw=interconnection_size_kw) hybrid_plant.battery.dispatch.lifecycle_cost_per_kWh_cycle = 0.01 hybrid_plant.ppa_price = (0.03, ) hybrid_plant.pv.dc_degradation = [0] * 25 hybrid_plant.simulate() aeps = hybrid_plant.annual_energies npvs = hybrid_plant.net_present_values taxes = hybrid_plant.federal_taxes apv = hybrid_plant.energy_purchases_values debt = hybrid_plant.debt_payment esv = hybrid_plant.energy_sales_values depr = hybrid_plant.federal_depreciation_totals insr = hybrid_plant.insurance_expenses om = hybrid_plant.om_total_expenses rev = hybrid_plant.total_revenues tc = hybrid_plant.tax_incentives assert aeps.pv == approx(9882421, rel=0.05) assert aeps.wind == approx(33637983, rel=0.05) assert aeps.battery == approx(-31287, rel=0.05) assert aeps.hybrid == approx(43489117, rel=0.05) assert npvs.pv == approx(-853226, rel=5e-2) assert npvs.wind == approx(-4380277, rel=5e-2) assert npvs.battery == approx(-6889961, rel=5e-2) assert npvs.hybrid == approx(-11861790, rel=5e-2) assert taxes.pv[1] == approx(94661, rel=5e-2) assert taxes.wind[1] == approx(413068, rel=5e-2) assert taxes.battery[1] == approx(297174, rel=5e-2) assert taxes.hybrid[1] == approx(804904, rel=5e-2) assert apv.pv[1] == approx(0, rel=5e-2) assert apv.wind[1] == approx(0, rel=5e-2) assert apv.battery[1] == approx(40158, rel=5e-2) assert apv.hybrid[1] == approx(3050, rel=5e-2) assert debt.pv[1] == approx(0, rel=5e-2) assert debt.wind[1] == approx(0, rel=5e-2) assert debt.battery[1] == approx(0, rel=5e-2) assert debt.hybrid[1] == approx(0, rel=5e-2) assert esv.pv[1] == approx(353105, rel=5e-2) assert esv.wind[1] == approx(956067, rel=5e-2) assert esv.battery[1] == approx(80449, rel=5e-2) assert esv.hybrid[1] == approx(1352445, rel=5e-2) assert depr.pv[1] == approx(762811, rel=5e-2) assert depr.wind[1] == approx(2651114, rel=5e-2) assert depr.battery[1] == approx(1486921, rel=5e-2) assert depr.hybrid[1] == approx(4900847, rel=5e-2) assert insr.pv[0] == approx(0, rel=5e-2) assert insr.wind[0] == approx(0, rel=5e-2) assert insr.battery[0] == approx(0, rel=5e-2) assert insr.hybrid[0] == approx(0, rel=5e-2) assert om.pv[1] == approx(74993, rel=5e-2) assert om.wind[1] == approx(420000, rel=5e-2) assert om.battery[1] == approx(75000, rel=5e-2) assert om.hybrid[1] == approx(569993, rel=5e-2) assert rev.pv[1] == approx(353105, rel=5e-2) assert rev.wind[1] == approx(956067, rel=5e-2) assert rev.battery[1] == approx(80449, rel=5e-2) assert rev.hybrid[1] == approx(1352445, rel=5e-2) assert tc.pv[1] == approx(1123104, rel=5e-2) assert tc.wind[1] == approx(504569, rel=5e-2) assert tc.battery[1] == approx(0, rel=5e-2) assert tc.hybrid[1] == approx(1646170, rel=5e-2)
'layout_mode': 'boundarygrid', 'layout_params': WindBoundaryGridParameters(border_spacing=2, border_offset=0.5, grid_angle=0.5, grid_aspect_power=0.5, row_phase_offset=0.5) } } # Get resource # Create model hybrid_plant = HybridSimulation(technologies, site_info, interconnect_kw=interconnection_size_mw * 1000) # hybrid_plant.plot_layout() # plt.show() hybrid_plant.simulate(1) # Setup Optimization Candidate class HybridLayoutProblem(OptimizationProblem): """ Optimize the layout of the wind and solar plant """ def __init__(self, simulation: HybridSimulation) -> None: """ Optimize layout with fixed number of turbines and solar capacity
def init_hybrid_plant(): """ Initialize hybrid simulation object using specific project inputs :return: HybridSimulation as defined for this problem """ is_test = False # Turns off full year dispatch and optimize tower and receiver techs_in_sim = ['tower', 'pv', 'battery', 'grid'] site_data = { "lat": 34.85, "lon": -116.9, "elev": 641, "year": 2012, "tz": -8, "no_wind": True } root = "C:/Users/WHamilt2/Documents/Projects/HOPP/CSP_PV_battery_dispatch_plots/" solar_file = root + "34.865371_-116.783023_psmv3_60_tmy.csv" prices_file = root + "constant_nom_prices.csv" schedule_scale = 100 # MWe desired_schedule_file = root + "sample_load_profile_normalized.csv" # Reading in desired schedule with open(desired_schedule_file) as f: csvreader = csv.reader(f) desired_schedule = [] for row in csvreader: desired_schedule.append(float(row[0])*schedule_scale) # If normalized pricing is used, then PPA price must be adjusted after HybridSimulation is initialized site = SiteInfo(site_data, solar_resource_file=solar_file, grid_resource_file=prices_file, desired_schedule=desired_schedule ) technologies = {'tower': { 'cycle_capacity_kw': 100 * 1000, #100 * 1000, 'solar_multiple': 3.0, #2.0, 'tes_hours': 16.0, #16.0, 'optimize_field_before_sim': not is_test, 'scale_input_params': True, }, 'trough': { 'cycle_capacity_kw': 100 * 1000, 'solar_multiple': 4.0, 'tes_hours': 20.0 }, 'pv': { 'system_capacity_kw': 50 * 1000 }, 'battery': { 'system_capacity_kwh': 300 * 1000, 'system_capacity_kw': 100 * 1000 }, 'grid': 150 * 1000} # Create model hybrid_plant = HybridSimulation({key: technologies[key] for key in techs_in_sim}, site, interconnect_kw=technologies['grid'], dispatch_options={ 'is_test_start_year': is_test, 'is_test_end_year': is_test, 'solver': 'xpress', 'grid_charging': False, 'pv_charging_only': True, }, simulation_options={ 'storage_capacity_credit': False, } ) # Defaults: # {'cost_per_field_generation': 0.5, # 'cost_per_field_start_rel': 1.5, # 'cost_per_cycle_generation': 2.0, # 'cost_per_cycle_start_rel': 40.0, # 'cost_per_change_thermal_input': 0.5} csp_dispatch_obj_costs = dict() csp_dispatch_obj_costs = { 'cost_per_field_generation': 0.0, #0.5, # 'cost_per_field_start_rel': 0.0, # 'cost_per_cycle_generation': 2.0, 'cost_per_cycle_start_rel': 0.0, 'cost_per_change_thermal_input': 0.5} # Set CSP costs if hybrid_plant.tower: hybrid_plant.tower.dispatch.objective_cost_terms.update(csp_dispatch_obj_costs) hybrid_plant.tower.value('cycle_max_frac', 1.0) if hybrid_plant.trough: hybrid_plant.trough.dispatch.objective_cost_terms.update(csp_dispatch_obj_costs) hybrid_plant.trough.value('cycle_max_frac', 1.0) # if hybrid_plant.battery: # hybrid_plant.battery.dispatch.lifecycle_cost_per_kWh_cycle = 0.0265 / 100 # # hybrid_plant.battery.dispatch.lifecycle_cost_per_kWh_cycle = 1e-6 if hybrid_plant.pv: hybrid_plant.pv.dc_degradation = [0.5] * 25 hybrid_plant.pv.value('array_type', 2) # 1-axis tracking hybrid_plant.pv.value('tilt', 0) # Tilt for 1-axis # This is required if normalized prices are provided hybrid_plant.ppa_price = (0.12,) # $/kWh return hybrid_plant
technologies = {'pv': { 'system_capacity_kw': solar_size_mw * 1000 }, 'wind': { 'num_turbines': 10, 'turbine_rating_kw': 2000 }} # Get resource lat = flatirons_site['lat'] lon = flatirons_site['lon'] prices_file = examples_dir.parent / "resource_files" / "grid" / "pricing-data-2015-IronMtn-002_factors.csv" site = SiteInfo(flatirons_site, grid_resource_file=prices_file) # Create model hybrid_plant = HybridSimulation(technologies, site, interconnect_kw=interconnection_size_mw * 1000) hybrid_plant.pv.system_capacity_kw = solar_size_mw * 1000 hybrid_plant.wind.system_capacity_by_num_turbines(wind_size_mw * 1000) hybrid_plant.ppa_price = 0.1 hybrid_plant.pv.dc_degradation = [0] * 25 hybrid_plant.simulate(25) # Save the outputs annual_energies = hybrid_plant.annual_energies wind_plus_solar_npv = hybrid_plant.net_present_values.wind + hybrid_plant.net_present_values.pv npvs = hybrid_plant.net_present_values wind_installed_cost = hybrid_plant.wind.total_installed_cost solar_installed_cost = hybrid_plant.pv.total_installed_cost hybrid_installed_cost = hybrid_plant.grid.total_installed_cost
def run_hopp_calc(Site, scenario_description, bos_details, total_hybrid_plant_capacity_mw, solar_size_mw, wind_size_mw, nameplate_mw, interconnection_size_mw, load_resource_from_file, ppa_price, results_dir): """ run_hopp_calc Establishes sizing models, creates a wind or solar farm based on the desired sizes, and runs SAM model calculations for the specified inputs. save_outputs contains a dictionary of all results for the hopp calculation. :param scenario_description: Project scenario - 'greenfield' or 'solar addition'. :param bos_details: contains bos details including type of analysis to conduct (cost/mw, json lookup, HybridBOSSE). :param total_hybrid_plant_capacity_mw: capacity in MW of hybrid plant. :param solar_size_mw: capacity in MW of solar component of plant. :param wind_size_mw: capacity in MW of wind component of plant. :param nameplate_mw: nameplate capacity of total plant. :param interconnection_size_mw: interconnection size in MW. :param load_resource_from_file: flag determining whether resource is loaded directly from file or through interpolation routine. :param ppa_price: PPA price in USD($) :return: collection of outputs from SAM and hybrid-specific calculations (includes e.g. AEP, IRR, LCOE), plus wind and solar filenames used (save_outputs) """ # Get resource data if load_resource_from_file: pass else: Site[ 'resource_filename_solar'] = "" # Unsetting resource filename to force API download of wind resource Site[ 'resource_filename_wind'] = "" # Unsetting resource filename to force API download of solar resource site = SiteInfo(Site, solar_resource_file=sample_site['resource_filename_solar'], wind_resource_file=sample_site['resource_filename_wind']) #TODO: Incorporate this in SiteInfo # if 'roll_tz' in Site.keys(): # site.solar_resource.roll_timezone(Site['roll_tz'], Site['roll_tz']) # Set up technology and cost model info technologies = { 'solar': solar_size_mw, # mw system capacity 'wind': wind_size_mw, # mw system capacity 'grid': interconnection_size_mw } # mw interconnect # Create model hybrid_plant = HybridSimulation(technologies, site, interconnect_kw=interconnection_size_mw * 1000) hybrid_plant.setup_cost_calculator( create_cost_calculator(interconnection_size_mw, bos_details['BOSSource'], scenario_description)) hybrid_plant.ppa_price = ppa_price hybrid_plant.discount_rate = 6.4 hybrid_plant.solar.system_capacity_kw = solar_size_mw * 1000 hybrid_plant.wind.system_capacity_by_num_turbines(wind_size_mw * 1000) actual_solar_pct = hybrid_plant.solar.system_capacity_kw / \ (hybrid_plant.solar.system_capacity_kw + hybrid_plant.wind.system_capacity_kw) logger.info("Run with solar percent {}".format(actual_solar_pct)) hybrid_plant.simulate() outputs = hybrid_plant.hybrid_outputs() for k, v in outputs.items(): outputs[k] = [v] return outputs, site.wind_resource.filename, site.solar_resource.filename
def setup_power_calcs(scenario, solar_size_mw, storage_size_mwh, storage_size_mw, interconnection_size_mw): """ A function to facilitate plant setup for POWER calculations, assuming one wind turbine. INPUT VARIABLES scenario: dict, the H2 scenario of interest solar_size_mw: float, the amount of solar capacity in MW storage_size_mwh: float, the amount of battery storate capacity in MWh storage_size_mw: float, the amount of battery storate capacity in MW interconnection_size_mw: float, the interconnection size in MW OUTPUTS hybrid_plant: the hybrid plant object from HOPP for power calculations """ # Set API key load_dotenv() NREL_API_KEY = os.getenv("NREL_API_KEY") set_developer_nrel_gov_key( NREL_API_KEY ) # Set this key manually here if you are not setting it using the .env # Step 1: Establish output structure and special inputs year = 2013 sample_site['year'] = year useful_life = 30 custom_powercurve = True electrolyzer_size = 50000 sample_site['lat'] = scenario['Lat'] sample_site['lon'] = scenario['Long'] tower_height = scenario['Tower Height'] scenario['Useful Life'] = useful_life site = SiteInfo(sample_site, hub_height=tower_height) technologies = { 'pv': { 'system_capacity_kw': solar_size_mw * 1000 }, 'wind': { 'num_turbines': 1, 'turbine_rating_kw': scenario['Turbine Rating'] * 1000, 'hub_height': scenario['Tower Height'], 'rotor_diameter': scenario['Rotor Diameter'] }, 'grid': electrolyzer_size, 'battery': { 'system_capacity_kwh': storage_size_mwh * 1000, 'system_capacity_kw': storage_size_mw * 1000 } } dispatch_options = {'battery_dispatch': 'heuristic'} hybrid_plant = HybridSimulation(technologies, site, interconnect_kw=electrolyzer_size, dispatch_options=dispatch_options) hybrid_plant.wind._system_model.Turbine.wind_resource_shear = 0.33 if custom_powercurve: powercurve_file = open(scenario['Powercurve File']) powercurve_data = json.load(powercurve_file) powercurve_file.close() hybrid_plant.wind._system_model.Turbine.wind_turbine_powercurve_windspeeds = \ powercurve_data['turbine_powercurve_specification']['wind_speed_ms'] hybrid_plant.wind._system_model.Turbine.wind_turbine_powercurve_powerout = \ powercurve_data['turbine_powercurve_specification']['turbine_power_output'] hybrid_plant.pv.system_capacity_kw = solar_size_mw * 1000 return hybrid_plant
def run_hopp_calc(Site, scenario_description, bos_details, total_hybrid_plant_capacity_mw, solar_size_mw, wind_size_mw, nameplate_mw, interconnection_size_mw, load_resource_from_file, ppa_price, results_dir): """ run_hopp_calc Establishes sizing models, creates a wind or solar farm based on the desired sizes, and runs SAM model calculations for the specified inputs. save_outputs contains a dictionary of all results for the hopp calculation. :param scenario_description: Project scenario - 'greenfield' or 'solar addition'. :param bos_details: contains bos details including type of analysis to conduct (cost/mw, json lookup, HybridBOSSE). :param total_hybrid_plant_capacity_mw: capacity in MW of hybrid plant. :param solar_size_mw: capacity in MW of solar component of plant. :param wind_size_mw: capacity in MW of wind component of plant. :param nameplate_mw: nameplate capacity of total plant. :param interconnection_size_mw: interconnection size in MW. :param load_resource_from_file: flag determining whether resource is loaded directly from file or through interpolation routine. :param ppa_price: PPA price in USD($) :return: collection of outputs from SAM and hybrid-specific calculations (includes e.g. AEP, IRR, LCOE), plus wind and solar filenames used (save_outputs) """ # Get resource data # sample_site['lat'] = Site['Lat'] # sample_site['lon'] = Site['Lon'] # # site = SiteInfo(sample_site, solar_resource_file=Site['resource_filename_solar'], # # wind_resource_file=Site['resource_filename_wind']) if load_resource_from_file: pass else: Site['resource_filename_solar'] = "" # Unsetting resource filename to force API download of wind resource Site['resource_filename_wind'] = "" # Unsetting resource filename to force API download of solar resource site = SiteInfo(Site) if 'roll_tz' in Site.keys(): site.solar_resource.roll_timezone(Site['roll_tz'], Site['roll_tz']) # Set up technology and cost model info technologies = {'solar': solar_size_mw, # mw system capacity 'wind': wind_size_mw # mw system capacity } # Create model hybrid_plant = HybridSimulation(technologies, site, interconnect_kw=interconnection_size_mw * 1000) # hybrid_plant.setup_cost_calculator(create_cost_calculator(bos_cost_source='boslookup', interconnection_mw=interconnection_size_mw)) hybrid_plant.setup_cost_calculator(create_cost_calculator(bos_cost_source=bos_details['BOSSource'], interconnection_mw=interconnection_size_mw, modify_costs=bos_details['Modify Costs'], cost_reductions=bos_details, wind_installed_cost_mw=1696000, solar_installed_cost_mw=1088600, storage_installed_cost_mw=0, storage_installed_cost_mwh=0, )) hybrid_plant.ppa_price = ppa_price hybrid_plant.discount_rate = 6.4 hybrid_plant.solar.system_capacity_kw = solar_size_mw * 1000 hybrid_plant.wind.rotor_diameter = 100 # Modify Wind Turbine Coordinates Nx = 8 Ny = 5 spacing = hybrid_plant.wind.row_spacing turbine_x = np.linspace(0, (Nx * spacing) - spacing, Nx) turbine_y = np.linspace(0, (Ny * spacing) - spacing, Ny) turbX = np.zeros(Nx * Ny) turbY = np.zeros(Nx * Ny) count = 0 for i in range(Nx): for j in range(Ny): turbX[count] = turbine_x[i] turbY[count] = turbine_y[j] count = count + 1 hybrid_plant.wind.modify_coordinates(list(turbX), list(turbY)) hybrid_plant.wind.system_capacity_by_num_turbines(wind_size_mw * 1000) actual_solar_pct = hybrid_plant.solar.system_capacity_kw / \ (hybrid_plant.solar.system_capacity_kw + hybrid_plant.wind.system_capacity_kw) logger.info("Run with solar percent {}".format(actual_solar_pct)) hybrid_plant.simulate() outputs = hybrid_plant.hybrid_outputs() for k, v in outputs.items(): outputs[k] = [v] return outputs, site.wind_resource.filename, site.solar_resource.filename
# Set wind, solar, and interconnection capacities (in MW) solar_size_mw = 20 wind_size_mw = 20 interconnection_size_mw = 20 technologies = {'solar': solar_size_mw, # mw system capacity 'wind': wind_size_mw, # mw system capacity 'grid': interconnection_size_mw} # Get resource lat = flatirons_site['lat'] lon = flatirons_site['lon'] site = SiteInfo(flatirons_site) # Create model hybrid_plant = HybridSimulation(technologies, site, interconnect_kw=interconnection_size_mw * 1000) # Setup cost model hybrid_plant.setup_cost_calculator(create_cost_calculator(interconnection_size_mw)) hybrid_plant.solar.system_capacity_kw = solar_size_mw * 1000 hybrid_plant.wind.system_capacity_by_num_turbines(wind_size_mw * 1000) hybrid_plant.ppa_price = 0.1 hybrid_plant.simulate(25) # Save the outputs annual_energies = hybrid_plant.annual_energies wind_plus_solar_npv = hybrid_plant.net_present_values.wind + hybrid_plant.net_present_values.solar npvs = hybrid_plant.net_present_values wind_installed_cost = hybrid_plant.wind.financial_model.SystemCosts.total_installed_cost solar_installed_cost = hybrid_plant.solar.financial_model.SystemCosts.total_installed_cost
def test_hybrid_om_costs(site): wind_pv_battery = { key: technologies[key] for key in ('pv', 'wind', 'battery') } hybrid_plant = HybridSimulation( wind_pv_battery, site, interconnect_kw=interconnection_size_kw, dispatch_options={'battery_dispatch': 'one_cycle_heuristic'}) hybrid_plant.ppa_price = (0.03, ) hybrid_plant.pv.dc_degradation = [0] * 25 # set all O&M costs to 0 to start hybrid_plant.wind.om_fixed = 0 hybrid_plant.wind.om_capacity = 0 hybrid_plant.wind.om_variable = 0 hybrid_plant.pv.om_fixed = 0 hybrid_plant.pv.om_capacity = 0 hybrid_plant.pv.om_variable = 0 hybrid_plant.battery.om_fixed = 0 hybrid_plant.battery.om_capacity = 0 hybrid_plant.battery.om_variable = 0 # test variable costs hybrid_plant.wind.om_variable = 5 hybrid_plant.pv.om_variable = 2 hybrid_plant.battery.om_variable = 3 hybrid_plant.simulate() var_om_costs = hybrid_plant.om_variable_expenses total_om_costs = hybrid_plant.om_total_expenses for i in range(len(var_om_costs.hybrid)): assert var_om_costs.pv[i] + var_om_costs.wind[ i] + var_om_costs.battery[i] == approx(var_om_costs.hybrid[i], rel=1e-1) assert total_om_costs.pv[i] == approx(var_om_costs.pv[i]) assert total_om_costs.wind[i] == approx(var_om_costs.wind[i]) assert total_om_costs.battery[i] == approx(var_om_costs.battery[i]) assert total_om_costs.hybrid[i] == approx(var_om_costs.hybrid[i]) hybrid_plant.wind.om_variable = 0 hybrid_plant.pv.om_variable = 0 hybrid_plant.battery.om_variable = 0 # test fixed costs hybrid_plant.wind.om_fixed = 5 hybrid_plant.pv.om_fixed = 2 hybrid_plant.battery.om_fixed = 3 hybrid_plant.simulate() fixed_om_costs = hybrid_plant.om_fixed_expenses total_om_costs = hybrid_plant.om_total_expenses for i in range(len(fixed_om_costs.hybrid)): assert fixed_om_costs.pv[i] + fixed_om_costs.wind[i] + fixed_om_costs.battery[i] \ == approx(fixed_om_costs.hybrid[i]) assert total_om_costs.pv[i] == approx(fixed_om_costs.pv[i]) assert total_om_costs.wind[i] == approx(fixed_om_costs.wind[i]) assert total_om_costs.battery[i] == approx(fixed_om_costs.battery[i]) assert total_om_costs.hybrid[i] == approx(fixed_om_costs.hybrid[i]) hybrid_plant.wind.om_fixed = 0 hybrid_plant.pv.om_fixed = 0 hybrid_plant.battery.om_fixed = 0 # test capacity costs hybrid_plant.wind.om_capacity = 5 hybrid_plant.pv.om_capacity = 2 hybrid_plant.battery.om_capacity = 3 hybrid_plant.simulate() cap_om_costs = hybrid_plant.om_capacity_expenses total_om_costs = hybrid_plant.om_total_expenses for i in range(len(cap_om_costs.hybrid)): assert cap_om_costs.pv[i] + cap_om_costs.wind[i] + cap_om_costs.battery[i] \ == approx(cap_om_costs.hybrid[i]) assert total_om_costs.pv[i] == approx(cap_om_costs.pv[i]) assert total_om_costs.wind[i] == approx(cap_om_costs.wind[i]) assert total_om_costs.battery[i] == approx(cap_om_costs.battery[i]) assert total_om_costs.hybrid[i] == approx(cap_om_costs.hybrid[i]) hybrid_plant.wind.om_capacity = 0 hybrid_plant.pv.om_capacity = 0 hybrid_plant.battery.om_capacity = 0
def test_capacity_credit(site): site = SiteInfo(data=flatirons_site, solar_resource_file=solar_resource_file, wind_resource_file=wind_resource_file, capacity_hours=capacity_credit_hours) wind_pv_battery = { key: technologies[key] for key in ('pv', 'wind', 'battery') } hybrid_plant = HybridSimulation(wind_pv_battery, site, interconnect_kw=interconnection_size_kw) hybrid_plant.battery.dispatch.lifecycle_cost_per_kWh_cycle = 0.01 hybrid_plant.ppa_price = (0.03, ) hybrid_plant.pv.dc_degradation = [0] * 25 # Backup values for resetting before tests gen_max_feasible_orig = hybrid_plant.battery.gen_max_feasible capacity_hours_orig = hybrid_plant.site.capacity_hours interconnect_kw_orig = hybrid_plant.interconnect_kw def reinstate_orig_values(): hybrid_plant.battery.gen_max_feasible = gen_max_feasible_orig hybrid_plant.site.capacity_hours = capacity_hours_orig hybrid_plant.interconnect_kw = interconnect_kw_orig # Test when 0 gen_max_feasible reinstate_orig_values() hybrid_plant.battery.gen_max_feasible = [0] * 8760 capacity_credit_battery = hybrid_plant.battery.calc_capacity_credit_percent( hybrid_plant.interconnect_kw) assert capacity_credit_battery == approx(0, rel=0.05) # Test when representative gen_max_feasible reinstate_orig_values() hybrid_plant.battery.gen_max_feasible = [2500] * 8760 capacity_credit_battery = hybrid_plant.battery.calc_capacity_credit_percent( hybrid_plant.interconnect_kw) assert capacity_credit_battery == approx(50, rel=0.05) # Test when no capacity hours reinstate_orig_values() hybrid_plant.battery.gen_max_feasible = [2500] * 8760 hybrid_plant.site.capacity_hours = [False] * 8760 capacity_credit_battery = hybrid_plant.battery.calc_capacity_credit_percent( hybrid_plant.interconnect_kw) assert capacity_credit_battery == approx(0, rel=0.05) # Test when no interconnect capacity reinstate_orig_values() hybrid_plant.battery.gen_max_feasible = [2500] * 8760 hybrid_plant.interconnect_kw = 0 capacity_credit_battery = hybrid_plant.battery.calc_capacity_credit_percent( hybrid_plant.interconnect_kw) assert capacity_credit_battery == approx(0, rel=0.05) # Test integration with system simulation reinstate_orig_values() cap_payment_mw = 100000 hybrid_plant.assign({"cp_capacity_payment_amount": [cap_payment_mw]}) hybrid_plant.simulate() total_gen_max_feasible = np.array(hybrid_plant.pv.gen_max_feasible) \ + np.array(hybrid_plant.wind.gen_max_feasible) \ + np.array(hybrid_plant.battery.gen_max_feasible) assert sum(hybrid_plant.grid.gen_max_feasible) == approx(sum(np.minimum(hybrid_plant.grid.interconnect_kw * hybrid_plant.site.interval / 60, \ total_gen_max_feasible)), rel=0.01) total_nominal_capacity = hybrid_plant.pv.calc_nominal_capacity(hybrid_plant.interconnect_kw) \ + hybrid_plant.wind.calc_nominal_capacity(hybrid_plant.interconnect_kw) \ + hybrid_plant.battery.calc_nominal_capacity(hybrid_plant.interconnect_kw) assert total_nominal_capacity == approx(18845.8, rel=0.01) assert total_nominal_capacity == approx( hybrid_plant.grid.hybrid_nominal_capacity, rel=0.01) capcred = hybrid_plant.capacity_credit_percent assert capcred['pv'] == approx(8.03, rel=0.05) assert capcred['wind'] == approx(33.25, rel=0.10) assert capcred['battery'] == approx(58.95, rel=0.05) assert capcred['hybrid'] == approx(43.88, rel=0.05) cp_pay = hybrid_plant.capacity_payments np_cap = hybrid_plant.system_nameplate_mw # This is not the same as nominal capacity... assert cp_pay['pv'][1] / (np_cap['pv']) / (capcred['pv'] / 100) == approx( cap_payment_mw, 0.05) assert cp_pay['wind'][1] / (np_cap['wind']) / (capcred['wind'] / 100) == approx( cap_payment_mw, 0.05) assert cp_pay['battery'][1] / (np_cap['battery']) / (capcred['battery'] / 100) == approx( cap_payment_mw, 0.05) assert cp_pay['hybrid'][1] / (np_cap['hybrid']) / (capcred['hybrid'] / 100) == approx( cap_payment_mw, 0.05) aeps = hybrid_plant.annual_energies assert aeps.pv == approx(9882421, rel=0.05) assert aeps.wind == approx(33637983, rel=0.05) assert aeps.battery == approx(-31287, rel=0.05) assert aeps.hybrid == approx(43489117, rel=0.05) npvs = hybrid_plant.net_present_values assert npvs.pv == approx(-565098, rel=5e-2) assert npvs.wind == approx(-1992106, rel=5e-2) assert npvs.battery == approx(-4773045, rel=5e-2) assert npvs.hybrid == approx(-5849767, rel=5e-2) taxes = hybrid_plant.federal_taxes assert taxes.pv[1] == approx(86826, rel=5e-2) assert taxes.wind[1] == approx(348124, rel=5e-2) assert taxes.battery[1] == approx(239607, rel=5e-2) assert taxes.hybrid[1] == approx(633523, rel=5e-2) apv = hybrid_plant.energy_purchases_values assert apv.pv[1] == approx(0, rel=5e-2) assert apv.wind[1] == approx(0, rel=5e-2) assert apv.battery[1] == approx(40158, rel=5e-2) assert apv.hybrid[1] == approx(2980, rel=5e-2) debt = hybrid_plant.debt_payment assert debt.pv[1] == approx(0, rel=5e-2) assert debt.wind[1] == approx(0, rel=5e-2) assert debt.battery[1] == approx(0, rel=5e-2) assert debt.hybrid[1] == approx(0, rel=5e-2) esv = hybrid_plant.energy_sales_values assert esv.pv[1] == approx(353105, rel=5e-2) assert esv.wind[1] == approx(956067, rel=5e-2) assert esv.battery[1] == approx(80449, rel=5e-2) assert esv.hybrid[1] == approx(1352445, rel=5e-2) depr = hybrid_plant.federal_depreciation_totals assert depr.pv[1] == approx(762811, rel=5e-2) assert depr.wind[1] == approx(2651114, rel=5e-2) assert depr.battery[1] == approx(1486921, rel=5e-2) assert depr.hybrid[1] == approx(4900847, rel=5e-2) insr = hybrid_plant.insurance_expenses assert insr.pv[0] == approx(0, rel=5e-2) assert insr.wind[0] == approx(0, rel=5e-2) assert insr.battery[0] == approx(0, rel=5e-2) assert insr.hybrid[0] == approx(0, rel=5e-2) om = hybrid_plant.om_total_expenses assert om.pv[1] == approx(74993, rel=5e-2) assert om.wind[1] == approx(420000, rel=5e-2) assert om.battery[1] == approx(75000, rel=5e-2) assert om.hybrid[1] == approx(569993, rel=5e-2) rev = hybrid_plant.total_revenues assert rev.pv[1] == approx(393226, rel=5e-2) assert rev.wind[1] == approx(1288603, rel=5e-2) assert rev.battery[1] == approx(375215, rel=5e-2) assert rev.hybrid[1] == approx(2229976, rel=5e-2) tc = hybrid_plant.tax_incentives assert tc.pv[1] == approx(1123104, rel=5e-2) assert tc.wind[1] == approx(504569, rel=5e-2) assert tc.battery[1] == approx(0, rel=5e-2) assert tc.hybrid[1] == approx(1646170, rel=5e-2)
def test_desired_schedule_dispatch(): # Creating a contrived schedule daily_schedule = [interconnect_mw]*10 daily_schedule.extend([20] * 8) daily_schedule.append(interconnect_mw + 5) daily_schedule.extend([0] * 5) desired_schedule = daily_schedule*365 desired_schedule_site = SiteInfo(flatirons_site, desired_schedule=desired_schedule) tower_pv_battery = {key: technologies[key] for key in ('pv', 'tower', 'battery')} # Default case doesn't leave enough head room for battery operations tower_pv_battery['tower'] = {'cycle_capacity_kw': 35 * 1000, 'solar_multiple': 2.0, 'tes_hours': 10.0} tower_pv_battery['pv'] = {'system_capacity_kw': 80 * 1000} hybrid_plant = HybridSimulation(tower_pv_battery, desired_schedule_site, interconnect_mw * 1000, dispatch_options={'is_test_start_year': True, 'is_test_end_year': False, 'grid_charging': False, 'pv_charging_only': True, 'include_lifecycle_count': False }) hybrid_plant.ppa_price = (0.06, ) # Constant price # hybrid_plant.site.elec_prices = [100] * hybrid_plant.site.n_timesteps hybrid_plant.simulate(1) system_generation = hybrid_plant.dispatch_builder.dispatch.system_generation system_load = hybrid_plant.dispatch_builder.dispatch.system_load electricity_sold = hybrid_plant.grid.dispatch.electricity_sold electricity_purchased = hybrid_plant.grid.dispatch.electricity_purchased gen_limit = hybrid_plant.grid.dispatch.generation_transmission_limit transmission_limit = hybrid_plant.grid.value('grid_interconnection_limit_kwac') schedule = daily_schedule*2 # System generation does not exceed schedule limits for t in hybrid_plant.dispatch_builder.pyomo_model.forecast_horizon: assert gen_limit[t] * 1e3 <= transmission_limit assert system_generation[t] - system_load[t] <= schedule[t] + 1e-3 if system_generation[t] > system_load[t]: assert electricity_sold[t] == pytest.approx(system_generation[t] - system_load[t], 1e-3) assert electricity_purchased[t] == pytest.approx(0.0, 1e-3) else: assert electricity_purchased[t] == pytest.approx(system_load[t] - system_generation[t], 1e-3) assert electricity_sold[t] == pytest.approx(0.0, 1e-3) # Battery charges and discharges assert sum(hybrid_plant.battery.dispatch.charge_power) > 0.0 assert sum(hybrid_plant.battery.dispatch.discharge_power) > 0.0 # PV can be curtailed assert sum(hybrid_plant.pv.dispatch.generation) <= sum(hybrid_plant.pv.dispatch.available_generation) # CSP can run assert sum(hybrid_plant.tower.dispatch.cycle_generation) > 0.0 assert sum(hybrid_plant.tower.dispatch.receiver_thermal_power) > 0.0
}, 'battery': { 'system_capacity_kwh': battery_capacity_mw * 1000, 'system_capacity_kw': battery_capacity_mw * 4 * 1000 } } # Get resource lat = flatirons_site['lat'] lon = flatirons_site['lon'] prices_file = examples_dir.parent / "resource_files" / "grid" / "pricing-data-2015-IronMtn-002_factors.csv" site = SiteInfo(flatirons_site, grid_resource_file=prices_file) # Create base model hybrid_plant = HybridSimulation(technologies, site, interconnect_kw=interconnection_size_mw * 1000) hybrid_plant.pv.dc_degradation = (0,) # year over year degradation hybrid_plant.wind.wake_model = 3 # constant wake loss, layout-independent hybrid_plant.wind.value("wake_int_loss", 1) # percent wake loss hybrid_plant.pv.system_capacity_kw = solar_size_mw * 1000 hybrid_plant.wind.system_capacity_by_num_turbines(wind_size_mw * 1000) # prices_file are unitless dispatch factors, so add $/kwh here hybrid_plant.ppa_price = 0.04 # use single year for now, multiple years with battery not implemented yet hybrid_plant.simulate(project_life=20) print("output after losses over gross output",