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_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_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_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_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_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_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)
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 border_spacing: spacing along border = (1 + border_spacing) * min spacing (0, 100) border_offset: turbine border spacing offset as ratio of border spacing (0, 1) grid_angle: turbine inner grid rotation (0, pi) [radians]
}} # 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 print("Wind Installed Cost: {}".format(wind_installed_cost)) print("Solar Installed Cost: {}".format(solar_installed_cost)) print("Hybrid Installed Cost: {}".format(hybrid_installed_cost)) print("Wind NPV: {}".format(hybrid_plant.net_present_values.wind)) print("Solar NPV: {}".format(hybrid_plant.net_present_values.pv))
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 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
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)
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_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_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
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", hybrid_plant.wind.value("annual_energy") / hybrid_plant.wind.value("annual_gross_energy")) # Save the outputs annual_energies = hybrid_plant.annual_energies npvs = hybrid_plant.net_present_values revs = hybrid_plant.total_revenues print(annual_energies) print(npvs) print(revs) file = 'figures/' tag = 'simple2_'