def run(default_config: Dict) -> None: config, output_path, run_name = setup_run(default_config) recorder = DataRecorder.make_data_recorder(output_path) max_evaluations = config['max_evaluations'] optimizer_config = config['optimizer_config'] site_info = SiteInfo(flatirons_site) inner_problem = WindOptimizationProblem(site_info, config['num_turbines']) problem = WindParametrization(inner_problem) optimizer = ParametrizedOptimizationDriver(problem, recorder=recorder, **optimizer_config) figure = plt.figure(1) ax = figure.add_subplot(111) plt.grid() plt.tick_params(which='both', labelsize=15) plt.xlabel('x (m)', fontsize=15) plt.ylabel('y (m)', fontsize=15) site_info.plot() score, evaluation, best_solution = optimizer.central_solution() score, evaluation = problem.objective(best_solution) if score is None else score print(-1, ' ', score) optimizer.problem.plot_candidate(best_solution, (1.0, 0, 0), .2) prev = optimizer.best_solution()[1] try: while optimizer.num_evaluations() < max_evaluations: print('step start') optimizer.step() print('step end') proportion = min(1.0, optimizer.num_evaluations() / max_evaluations) g = 1.0 * proportion b = 1.0 - g a = .5 color = (b, g, b) score, eval, best = optimizer.best_solution() score = problem.objective(best) if score is None else score problem.plot_candidate(best, color, .3) prev = best print(optimizer.num_iterations(), ' ', optimizer.num_evaluations(), score) except: raise RuntimeError("Optimizer error encountered. Try modifying the config to use larger generation_size if" " encountering singular matrix errors.") print('best: ', optimizer.best_solution().__repr__()) optimizer.problem.plot_candidate(optimizer.best_solution()[2], (0, 0, 0), 1.0) # Create the figure legend_elements = [Line2D([0], [0], marker='o', color='w', markerfacecolor=(0, 0, 0), label='Optimal')] plt.legend(handles=legend_elements) plt.show() optimizer.close()
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 site(): solar_resource_file = Path(__file__).absolute( ).parent.parent.parent / "resource_files" / "solar" / "35.2018863_-101.945027_psmv3_60_2012.csv" wind_resource_file = Path(__file__).absolute( ).parent.parent.parent / "resource_files" / "wind" / "35.2018863_-101.945027_windtoolkit_2012_60min_80m_100m.srw" return SiteInfo(flatirons_site, solar_resource_file=solar_resource_file, wind_resource_file=wind_resource_file)
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 test_changing_turbine_rating(): # powercurve scaling model = WindPlant(SiteInfo(flatirons_site), { 'num_turbines': 24, "turbine_rating_kw": 2000 }) n_turbs = model.num_turbines for n in range(1000, 3000, 150): model.turb_rating = n assert model.system_capacity_kw == model.turb_rating * n_turbs, "system size error when rating is " + str( n)
def test_changing_system_capacity(): # adjust number of turbines, system capacity won't be exactly as requested model = WindPlant(SiteInfo(flatirons_site), { 'num_turbines': 20, "turbine_rating_kw": 1000 }) rating = model.turb_rating for n in range(1000, 20000, 1000): model.system_capacity_by_num_turbines(n) assert model.turb_rating == rating, str(n) assert model.system_capacity_kw == rating * round(n / rating) # adjust turbine rating first, system capacity will be exact model = WindPlant(SiteInfo(flatirons_site), { 'num_turbines': 20, "turbine_rating_kw": 1000 }) for n in range(40000, 60000, 1000): model.system_capacity_by_rating(n) assert model.system_capacity_kw == pytest.approx(n)
def test_changing_n_turbines(): # test with gridded layout model = WindPlant(SiteInfo(flatirons_site), { 'num_turbines': 10, "turbine_rating_kw": 2000 }) assert (model.system_capacity_kw == 20000) for n in range(1, 20): model.num_turbines = n assert model.num_turbines == n, "n turbs should be " + str(n) assert model.system_capacity_kw == pytest.approx( 20000, 1), "system capacity different when n turbs " + str(n)
def test_changing_rotor_diam_recalc(): model = WindPlant(SiteInfo(flatirons_site), { 'num_turbines': 10, "turbine_rating_kw": 2000 }) assert model.system_capacity_kw == 20000 diams = range(50, 70, 140) for d in diams: model.rotor_diameter = d assert model.rotor_diameter == d, "rotor diameter should be " + str(d) assert model.turb_rating == 2000, "new rating different when rotor diameter is " + str( d)
def test_create_grid(): site_info = SiteInfo(flatirons_site) bounding_shape = site_info.polygon.buffer(-200) site_info.plot() turbine_positions = create_grid(bounding_shape, site_info.polygon.centroid, np.pi / 4, 200, 200, .5) expected_positions = [[242., 497.], [383., 355.], [312., 709.], [454., 568.], [595., 426.], [525., 780.], [666., 638.], [737., 850.], [878., 709.]] for n, t in enumerate(turbine_positions): assert (t.x == pytest.approx(expected_positions[n][0], 1e-1)) assert (t.y == pytest.approx(expected_positions[n][1], 1e-1))
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_changing_powercurve(): # with power curve recalculation requires diameter changes model = WindPlant(SiteInfo(flatirons_site), { 'num_turbines': 24, "turbine_rating_kw": 2000 }) n_turbs = model.num_turbines d_to_r = model.rotor_diameter / model.turb_rating for n in range(1000, 3001, 500): d = math.ceil(n * d_to_r * 1) model.modify_powercurve(d, n) assert model.turb_rating == pytest.approx( n, 0.1), "turbine rating should be " + str(n) assert model.system_capacity_kw == pytest.approx( model.turb_rating * n_turbs, 0.1), "size error when rating is " + str(n)
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))
wind_size_mw = 50 interconnection_size_mw = 50 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
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 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 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
def h2_main(): """ Runs a Hydrogen Levelized Cost (HLC/LCOH) analysis for scenarios contained in "default_h2_scenarios.csv" """ # Step 1: Establish output structure and special inputs # save_all_runs = pd.DataFrame() save_outputs_dict = establish_save_output_dict() year = 2013 sample_site['year'] = year useful_life = 30 critical_load_factor_list = [1] run_reopt_flag = False custom_powercurve = True storage_used = True battery_can_grid_charge = False grid_connected_hopp = False interconnection_size_mw = 100 electrolyzer_sizes = [50] # which plots to show plot_power_production = False plot_battery = False plot_grid = False plot_h2 = False plot_reopt = False # Step 2: Load scenarios from .csv and enumerate # scenarios_df = pd.read_csv('H2 Baseline Future Scenarios Test Refactor.csv') parent_path = os.path.abspath(os.path.dirname(__file__)) scenarios_df = pd.read_csv(os.path.join(parent_path,'default_h2_scenarios.csv')) for electrolyzer_size in electrolyzer_sizes: for critical_load_factor in critical_load_factor_list: for i, scenario in scenarios_df.iterrows(): # TODO: Make scenario_choice, lookup all other values from dataframe from csv. # TODO: # -Pass through rotor diameter to pySAM # -Add wind, solar, storage installed costs # -Fix "H2 H2 xxx" text # print(scenario) kw_continuous = electrolyzer_size*1000 load = [kw_continuous for x in range(0, 8760)] # * (sin(x) + pi) Set desired/required load profile for plant scenario_choice = scenario['Scenario Name'] site_name = scenario['Site Name'] sample_site['lat'] = scenario['Lat'] sample_site['lon'] = scenario['Long'] lat = scenario['Lat'] lon = scenario['Long'] atb_year = scenario['ATB Year'] ptc_avail = scenario['PTC Available'] itc_avail = scenario['ITC Available'] forced_sizes = scenario['Force Plant Size'] force_electrolyzer_cost = scenario['Force Electrolyzer Cost'] if forced_sizes: forced_wind_size = scenario['Wind Size MW'] forced_solar_size = scenario['Solar Size MW'] forced_storage_size_mw = scenario['Storage Size MW'] forced_storage_size_mwh = scenario['Storage Size MWh'] else: print("Using ReOPT for sizing. REopt will be turned on and may not find a solution") run_reopt_flag = True if force_electrolyzer_cost: forced_electrolyzer_cost = scenario['Electrolyzer Cost KW'] tower_height = scenario['Tower Height'] rotor_diameter = scenario['Rotor Diameter'] turbine_rating = scenario['Turbine Rating'] wind_cost_kw = scenario['Wind Cost KW'] custom_powercurve_path = scenario['Powercurve File'] solar_cost_kw = scenario['Solar Cost KW'] storage_cost_kw = scenario['Storage Cost KW'] storage_cost_kwh = scenario['Storage Cost KWh'] debt_equity_split = scenario['Debt Equity'] buy_price = scenario['Buy From Grid ($/kWh)'] sell_price = scenario['Sell To Grid ($/kWh)'] #Todo: Add useful life to .csv scenario input instead scenario['Useful Life'] = useful_life site = SiteInfo(sample_site, hub_height=tower_height) # Step 3: Set up REopt run # ------------------------- # wind_size_mw, solar_size_mw, storage_size_mw,\ storage_size_mwh, storage_hours, reopt_results, REoptResultsDF = run_reopt(site, scenario, load, interconnection_size_mw*1000, critical_load_factor, useful_life, battery_can_grid_charge, storage_used, run_reopt_flag) # Step 4: Set up HOPP run # ------------------------- # if forced_sizes: solar_size_mw = forced_solar_size wind_size_mw = forced_wind_size storage_size_mw = forced_storage_size_mw storage_size_mwh = forced_storage_size_mwh # TODO: Replace electrolyzer size with interconnection size after testing # technologies = {'solar': solar_size_mw, # mw system capacity # 'wind': wind_size_mw, # mw system capacity # 'collection_system': True} technologies = {'pv': {'system_capacity_kw': solar_size_mw * 1000}, 'wind': {'num_turbines': np.floor(scenario['Wind Size MW'] / scenario['Turbine Rating']), 'turbine_rating_kw': scenario['Turbine Rating']*1000, 'hub_height': scenario['Tower Height'], 'rotor_diameter': scenario['Rotor Diameter']}, 'battery': { 'system_capacity_kwh': storage_size_mwh * 1000, 'system_capacity_kw': storage_size_mw * 1000 } } hybrid_plant, combined_pv_wind_power_production_hopp, combined_pv_wind_curtailment_hopp,\ energy_shortfall_hopp, annual_energies, wind_plus_solar_npv, npvs, lcoe = \ hopp_for_h2(site, scenario, technologies, wind_size_mw, solar_size_mw, storage_size_mw, storage_size_mwh, storage_hours, wind_cost_kw, solar_cost_kw, storage_cost_kw, storage_cost_kwh, kw_continuous, load, custom_powercurve, electrolyzer_size, grid_connected_hopp=True) 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 if plot_power_production: plt.figure(figsize=(4,4)) plt.title("HOPP power production") plt.plot(combined_pv_wind_power_production_hopp[200:300],label="wind + pv") plt.plot(energy_shortfall_hopp[200:300],label="shortfall") plt.plot(combined_pv_wind_curtailment_hopp[200:300],label="curtailment") plt.plot(load[200:300],label="electrolyzer rating") plt.xlabel("time (hour)") plt.ylabel("power production") # plt.ylim(0,250000) plt.legend() plt.tight_layout() plt.show() # Step 5: Run Simple Dispatch Model # ------------------------- # bat_model = SimpleDispatch() bat_model.Nt = len(energy_shortfall_hopp) bat_model.curtailment = combined_pv_wind_curtailment_hopp bat_model.shortfall = energy_shortfall_hopp bat_model.battery_storage = storage_size_mwh * 1000 bat_model.charge_rate = storage_size_mw * 1000 bat_model.discharge_rate = storage_size_mw * 1000 battery_used, excess_energy, battery_SOC = bat_model.run() combined_pv_wind_storage_power_production_hopp = combined_pv_wind_power_production_hopp + battery_used if plot_battery: plt.figure(figsize=(7,4)) plt.subplot(121) plt.plot(combined_pv_wind_curtailment_hopp[200:300],label="curtailment") plt.plot(energy_shortfall_hopp[200:300],label="shortfall") plt.plot(battery_SOC[200:300],label="state of charge") # plt.ylim(0,350000) # plt.plot(excess_energy[200:300],label="excess") plt.plot(battery_used[200:300],"--",label="battery used") plt.legend() plt.subplot(122) plt.plot(combined_pv_wind_storage_power_production_hopp[200:300],label="wind+pv+storage") plt.plot(combined_pv_wind_power_production_hopp[200:300],"--",label="wind+pv") plt.plot(load[200:300],"--",label="electrolyzer rating") # plt.ylim(0,225000) plt.legend() plt.suptitle("battery dispatch") plt.tight_layout() plt.show() if plot_grid: plt.plot(combined_pv_wind_storage_power_production_hopp[200:300],label="before buy from grid") sell_price = 0.01 buy_price = 0.05 # sell_price = False # buy_price = False if sell_price: profit_from_selling_to_grid = np.sum(excess_energy)*sell_price else: profit_from_selling_to_grid = 0.0 # buy_price = False # if you want to force no buy from grid if buy_price: cost_to_buy_from_grid = 0.0 for i in range(len(combined_pv_wind_storage_power_production_hopp)): if combined_pv_wind_storage_power_production_hopp[i] < kw_continuous: cost_to_buy_from_grid += (kw_continuous-combined_pv_wind_storage_power_production_hopp[i])*buy_price combined_pv_wind_storage_power_production_hopp[i] = kw_continuous else: cost_to_buy_from_grid = 0.0 energy_to_electrolyzer = [x if x < kw_continuous else kw_continuous for x in combined_pv_wind_storage_power_production_hopp] if plot_grid: plt.plot(combined_pv_wind_storage_power_production_hopp[200:300],"--",label="after buy from grid") plt.plot(energy_to_electrolyzer[200:300],"--",label="energy to electrolyzer") plt.legend() plt.show() # Step 6: Run the Python H2A model # ------------------------- # #TODO: Refactor H2A model call # Should take as input (electrolyzer size, cost, electrical timeseries, total system electrical usage (kwh/kg), # Should give as ouptut (h2 costs by net cap cost, levelized, total_unit_cost of hydrogen etc) ) # electrical_generation_timeseries = combined_pv_wind_storage_power_production_hopp electrical_generation_timeseries = np.zeros_like(energy_to_electrolyzer) electrical_generation_timeseries[:] = energy_to_electrolyzer[:] # Parangat model adjusted_installed_cost = hybrid_plant.grid._financial_model.Outputs.adjusted_installed_cost #NB: adjusted_installed_cost does NOT include the electrolyzer cost useful_life = scenario['Useful Life'] net_capital_costs = reopt_results['outputs']['Scenario']['Site'] \ ['Financial']['net_capital_costs'] # intalled costs: # hybrid_plant.grid._financial_model.costs # system_rating = electrolyzer_size system_rating = wind_size_mw + solar_size_mw H2_Results, H2A_Results = run_h2_PEM.run_h2_PEM(electrical_generation_timeseries,electrolyzer_size, kw_continuous,forced_electrolyzer_cost,lcoe,adjusted_installed_cost,useful_life, net_capital_costs) if plot_h2: hydrogen_hourly_production = H2_Results['hydrogen_hourly_production'] plt.figure(figsize=(6,3)) plt.subplot(121) plt.plot(electrical_generation_timeseries[200:300]) plt.ylim(0,max(electrical_generation_timeseries[200:300])*1.2) plt.plot(load[200:300],label="electrolyzer rating") plt.title("energy to electrolyzer") plt.subplot(122) plt.plot(hydrogen_hourly_production[200:300]) plt.ylim(0,max(hydrogen_hourly_production[200:300])*1.2) plt.title("hydrogen production") plt.tight_layout() plt.show() # TEMPORARY CORRECTION FOR PEM EFFICIENCY. # # Convert H2 production from ~72.55kWh eff to 55.5kWh/kg H2_Results['hydrogen_annual_output'] = H2_Results['hydrogen_annual_output'] * 72.55/55.5 # Step 6.5: Intermediate financial calculation total_elec_production = np.sum(electrical_generation_timeseries) #REMOVE total_hopp_installed_cost = hybrid_plant.grid._financial_model.SystemCosts.total_installed_cost total_electrolyzer_cost = H2A_Results['scaled_total_installed_cost'] total_system_installed_cost = total_hopp_installed_cost + total_electrolyzer_cost annual_operating_cost_hopp = (wind_size_mw * 1000 * 42) + (solar_size_mw * 1000 * 13) annual_operating_cost_h2 = H2A_Results['Fixed O&M'] * H2_Results['hydrogen_annual_output'] total_annual_operating_costs = annual_operating_cost_hopp + annual_operating_cost_h2 + cost_to_buy_from_grid - profit_from_selling_to_grid # h_lcoe_no_op_cost = lcoe_calc((H2_Results['hydrogen_annual_output']), total_system_installed_cost, # 0, 0.07, useful_life) h_lcoe = lcoe_calc((H2_Results['hydrogen_annual_output']), total_system_installed_cost, total_annual_operating_costs, 0.07, useful_life) # Cashflow Financial Calculation (Not sure that this includes electrical prices) discount_rate = scenario['Discount Rate'] cf_wind_annuals = hybrid_plant.wind._financial_model.Outputs.cf_annual_costs cf_solar_annuals = hybrid_plant.pv._financial_model.Outputs.cf_annual_costs cf_h2_annuals = H2A_Results['expenses_annual_cashflow'] # This might be unreliable. cf_df = pd.DataFrame([cf_wind_annuals, cf_solar_annuals, cf_h2_annuals[:len(cf_wind_annuals)]],['Wind', 'Solar', 'H2']) results_dir = Path(__file__).parent / 'results/' cf_df.to_csv(os.path.join(results_dir, "Annual Cashflows_{}_{}_{}_discount_{}.csv".format(site_name, scenario_choice, atb_year, discount_rate))) #NPVs of wind, solar, H2 npv_wind_costs = npf.npv(discount_rate, cf_wind_annuals) npv_solar_costs = npf.npv(discount_rate, cf_solar_annuals) npv_h2_costs = npf.npv(discount_rate, cf_h2_annuals) npv_total_costs = npv_wind_costs+npv_solar_costs+npv_h2_costs LCOH_cf_method = -npv_total_costs / (H2_Results['hydrogen_annual_output'] * useful_life) financial_summary_df = pd.DataFrame([scenario['Useful Life'], scenario['Wind Cost KW'], scenario['Solar Cost KW'], forced_electrolyzer_cost, scenario['Debt Equity'], atb_year, ptc_avail, itc_avail, discount_rate, npv_wind_costs, npv_solar_costs, npv_h2_costs, LCOH_cf_method], ['Useful Life', 'Wind Cost KW', 'Solar Cost KW', 'Electrolyzer Cost KW', 'Debt Equity', 'ATB Year', 'PTC available', 'ITC available', 'Discount Rate', 'NPV Wind Expenses', 'NPV Solar Expenses', 'NPV H2 Expenses', 'LCOH cf method']) financial_summary_df.to_csv(os.path.join(results_dir, 'Financial Summary.csv')) # Gut Check H2 calculation (non-levelized) total_installed_and_operational_lifetime_cost = total_system_installed_cost + (30 * total_annual_operating_costs) lifetime_h2_production = 30 * H2_Results['hydrogen_annual_output'] gut_check_h2_cost_kg = total_installed_and_operational_lifetime_cost / lifetime_h2_production # Step 7: Print results print_reults = False print_h2_results = True if print_reults: # ------------------------- # #TODO: Tidy up these print statements print("Future Scenario: {}".format(scenario['Scenario Name'])) print("Wind Cost per KW: {}".format(scenario['Wind Cost KW'])) print("PV Cost per KW: {}".format(scenario['Solar Cost KW'])) print("Storage Cost per KW: {}".format(scenario['Storage Cost kW'])) print("Storage Cost per KWh: {}".format(scenario['Storage Cost kWh'])) print("Wind Size built: {}".format(wind_size_mw)) print("PV Size built: {}".format(solar_size_mw)) print("Storage Size built: {}".format(storage_size_mw)) print("Storage Size built: {}".format(storage_size_mwh)) print("Levelized cost of Electricity (HOPP): {}".format(lcoe)) print("Total Yearly Electrical Output: {}".format(total_elec_production)) print("Total Yearly Hydrogen Production: {}".format(H2_Results['hydrogen_annual_output'])) print("Levelized Cost H2/kg (new method - no operational costs)".format(h_lcoe_no_op_cost)) print("Capacity Factor of Electrolyzer: {}".format(H2_Results['cap_factor'])) if print_h2_results: print('Total Lifetime H2(kg) produced: {}'.format(lifetime_h2_production)) print("Gut-check H2 cost/kg: {}".format(gut_check_h2_cost_kg)) print("h_lcoe: ", h_lcoe) print("LCOH CF Method (doesn't include elec)", LCOH_cf_method) # print("Levelized cost of H2 (electricity feedstock) (HOPP): {}".format( # H2_Results['feedstock_cost_h2_levelized_hopp'])) # print("Levelized cost of H2 (excl. electricity) (H2A): {}".format(H2A_Results['Total Hydrogen Cost ($/kgH2)'])) # print("Total unit cost of H2 ($/kg) : {}".format(H2_Results['total_unit_cost_of_hydrogen'])) # print("kg H2 cost from net cap cost/lifetime h2 production (HOPP): {}".format( # H2_Results['feedstock_cost_h2_via_net_cap_cost_lifetime_h2_hopp'])) # Step 8: Plot REopt results if plot_reopt: plot_reopt_results(REoptResultsDF, site_name, atb_year, critical_load_factor, useful_life, tower_height, wind_size_mw, solar_size_mw, storage_size_mw, storage_size_mwh, lcoe, H2_Results['feedstock_cost_h2_via_net_cap_cost_lifetime_h2_hopp'], H2_Results['feedstock_cost_h2_levelized_hopp'], hybrid_installed_cost, H2A_Results['Total Hydrogen Cost ($/kgH2)'], H2_Results['total_unit_cost_of_hydrogen'], output_dir='results/', monthly_separation=False, reopt_was_run=run_reopt_flag) # Step 9: Plot HOPP Production, Curtailment, and Hydrogen Production Profiles #TODO: Add this # Step 10: Save outputs # ------------------------- # #TODO: Place in function save_outputs_dict['Site Name'].append(site_name) save_outputs_dict['Scenario Choice'].append(scenario_choice) save_outputs_dict['Site Lat'].append(lat) save_outputs_dict['Site Lon'].append(lon) save_outputs_dict['ATB Year'].append(atb_year) save_outputs_dict['Resource Year'].append(year) save_outputs_dict['Critical Load Factor'].append(critical_load_factor) save_outputs_dict['kW continuous load'].append(kw_continuous) save_outputs_dict['Useful Life'].append(useful_life) save_outputs_dict['PTC'].append(ptc_avail) save_outputs_dict['ITC'].append(itc_avail) save_outputs_dict['Discount Rate'].append(discount_rate) save_outputs_dict['Debt Equity'].append(debt_equity_split) save_outputs_dict['Hub Height (m)'].append(tower_height) save_outputs_dict['Storage Enabled'].append(storage_used) save_outputs_dict['Wind Cost kW'].append(wind_cost_kw) save_outputs_dict['Solar Cost kW'].append(solar_cost_kw) save_outputs_dict['Storage Cost kW'].append(storage_cost_kw) save_outputs_dict['Storage Cost kWh'].append(storage_cost_kwh) save_outputs_dict['Storage Hours'].append(storage_hours) save_outputs_dict['Wind MW built'].append(wind_size_mw) save_outputs_dict['Solar MW built'].append(solar_size_mw) save_outputs_dict['Storage MW built'].append(storage_size_mw) save_outputs_dict['Storage MWh built'].append(storage_size_mwh) save_outputs_dict['Battery Can Grid Charge'].append(battery_can_grid_charge) save_outputs_dict['Built Interconnection Size'].append(hybrid_plant.interconnect_kw) save_outputs_dict['REOpt Interconnection Size'].append(interconnection_size_mw*1000) save_outputs_dict['Total Installed Cost $(HOPP)'].append(total_hopp_installed_cost) save_outputs_dict['Total Yearly Electrical Output'].append(total_elec_production) save_outputs_dict['LCOE'].append(lcoe) save_outputs_dict['Total Annual H2 production (kg)'].append(H2_Results['hydrogen_annual_output']) save_outputs_dict['Gut-Check Cost/kg H2 (non-levelized, includes elec if used)'].append(gut_check_h2_cost_kg) save_outputs_dict['Levelized Cost/kg H2 (lcoe using installed and operation costs)'].append(h_lcoe) save_outputs_dict['Levelized Cost/kg H2 (CF Method - using annual cashflows per technology)'].append(LCOH_cf_method) # save_outputs_dict['Levelized Cost H2/kg (new method - no operational costs)'].append(h_lcoe_no_op_cost) # save_outputs_dict['Levelized H2 Elec Feedstock Cost/kg (HOPP)'].append(H2_Results['feedstock_cost_h2_levelized_hopp']) # save_outputs_dict['Levelized cost of H2 (excl. electricity) (H2A)'].append(H2A_Results['Total Hydrogen Cost ($/kgH2)']) # save_outputs_dict['H2 Elec Feedstock Cost/kg (HOPP) Net Cap Cost Method'].append(H2_Results['feedstock_cost_h2_via_net_cap_cost_lifetime_h2_hopp']) # save_outputs_dict['Total H2 cost/kg (H2A)'].append(H2_Results['total_unit_cost_of_hydrogen']) save_outputs_dict['REOpt Energy Shortfall'].append(np.sum(REoptResultsDF['energy_shortfall'])) save_outputs_dict['REOpt Curtailment'].append(np.sum(REoptResultsDF['combined_pv_wind_curtailment'])) save_outputs_dict['Grid Connected HOPP'].append(grid_connected_hopp) save_outputs_dict['HOPP Total Generation'].append(np.sum(hybrid_plant.grid.generation_profile[0:8759])) save_outputs_dict['Wind Capacity Factor'].append(hybrid_plant.wind._system_model.Outputs.capacity_factor) save_outputs_dict['HOPP Energy Shortfall'].append(np.sum(energy_shortfall_hopp)) save_outputs_dict['HOPP Curtailment'].append(np.sum(combined_pv_wind_curtailment_hopp)) save_outputs_dict['Battery Generation'].append(np.sum(battery_used)) save_outputs_dict['Electricity to Grid'].append(np.sum(excess_energy)) save_outputs_dict['Electrolyzer Size'].append(H2A_Results['electrolyzer_size']) save_outputs_dict['Electrolyzer Total System Size'].append(H2A_Results['total_plant_size']) save_outputs_dict['H2A scaled total install cost'].append(H2A_Results['scaled_total_installed_cost']) save_outputs_dict['H2A scaled total install cost per kw'].append(H2A_Results['scaled_total_installed_cost_kw']) # save_all_runs = save_all_runs.append(save_outputs_dict, sort=False) # Create dataframe from outputs and save save_outputs = True if save_outputs: save_outputs_dict_df = pd.DataFrame(save_outputs_dict) save_outputs_dict_df.to_csv(os.path.join(results_dir, "H2_Analysis_{}.csv".format('Main')))
def site(): return SiteInfo(flatirons_site, solar_resource_file=solar_resource_file, wind_resource_file=wind_resource_file)
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 # 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
def test_hopp_for_h2(self): scenario = {} scenario['Useful Life'] = 30 scenario['Powercurve File'] = self.examples_dir / "powercurve_2018_COE" scenario['Tower Height'] = 90 scenario['Rotor Diameter'] = 116 scenario['Debt Equity'] = 90 scenario['ITC Available'] = 'no' scenario['PTC Available'] = 'yes' interconnection_size_mw = 150 wind_size_mw = 150 solar_size_mw = 50 electrolyzer_size = 5 storage_size_mw = 50 storage_size_mwh = 100 storage_hours = storage_size_mwh / storage_size_mw wind_cost_kw = 1454 solar_cost_kw = 1080 storage_cost_kw = 101 storage_cost_kwh = 116 kw_continuous = 1000 * electrolyzer_size load = [kw_continuous for x in range(0, 8760)] custom_powercurve = True if os.path.exists(scenario['Powercurve File']): with open(scenario['Powercurve File'], 'r') as f: powercurve_data = json.load(f) turb_size = max( powercurve_data['turbine_powercurve_specification'] ['turbine_power_output']) technologies = { 'pv': { 'system_capacity_kw': solar_size_mw * 1e3 }, 'wind': { 'num_turbines': int(wind_size_mw / turb_size), 'turbine_rating_kw': turb_size, 'hub_height': scenario['Tower Height'], 'rotor_diameter': scenario['Rotor Diameter'] }, 'battery': { 'system_capacity_kwh': storage_size_mwh * 1000, 'system_capacity_kw': storage_size_mw * 1000 } } solar_resource_file = Path(__file__).absolute( ).parent.parent.parent / "resource_files" / "solar" / "35.2018863_-101.945027_psmv3_60_2012.csv" wind_resource_file = Path(__file__).absolute( ).parent.parent.parent / "resource_files" / "wind" / "35.2018863_-101.945027_windtoolkit_2012_60min_80m_100m.srw" sample_site['site_num'] = 1 Site = SiteInfo(sample_site, solar_resource_file=solar_resource_file, wind_resource_file=wind_resource_file) hybrid_plant, combined_pv_wind_power_production_hopp, combined_pv_wind_curtailment_hopp, \ energy_shortfall_hopp, annual_energies, wind_plus_solar_npv, npvs, lcoe = hopp_for_h2( Site, scenario, technologies, wind_size_mw, solar_size_mw, storage_size_mw, storage_size_mwh, storage_hours, wind_cost_kw, solar_cost_kw, storage_cost_kw, storage_cost_kwh, kw_continuous, load, custom_powercurve, interconnection_size_mw, grid_connected_hopp=True ) df_produced = pd.DataFrame() df_produced['combined_pv_wind_power_production_hopp'] = [ sum(combined_pv_wind_power_production_hopp) ] df_produced['combined_pv_wind_curtailment_hopp'] = [ sum(combined_pv_wind_curtailment_hopp) ] df_produced['energy_shortfall_hopp'] = [sum(energy_shortfall_hopp)] df_produced['annual_energies'] = annual_energies df_produced['wind_plus_solar_npv'] = wind_plus_solar_npv df_produced['npvs'] = npvs df_produced['lcoe'] = lcoe results_path = os.path.join(self.test_dir, 'results') if not os.path.exists(results_path): os.mkdir(results_path) df_produced.to_csv(os.path.join( results_path, 'hopp_for_h2_test_results_produced.csv'), index=False) df_produced = pd.read_csv( os.path.join(results_path, 'hopp_for_h2_test_results_produced.csv')) df_expected = pd.read_csv( os.path.join(self.test_dir, 'expected_hopp_for_h2_test_results.csv')) assert df_produced[ 'combined_pv_wind_power_production_hopp'].values == approx( df_expected['combined_pv_wind_power_production_hopp'].values, 1e-4) assert df_produced[ 'combined_pv_wind_curtailment_hopp'].values == approx( df_expected['combined_pv_wind_curtailment_hopp'].values, 1e-4) assert df_produced['energy_shortfall_hopp'].values == approx( df_expected['energy_shortfall_hopp'].values, 1e-3) assert df_produced['wind_plus_solar_npv'].values == approx( df_expected['wind_plus_solar_npv'].values, 1e-2) shutil.rmtree(results_path)
site_data = None 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 + "'") g_file = Path(__file__).absolute( ).parent.parent.parent / "resource_files" / "grid" / "pricing-data-2015-IronMtn-002_factors.csv" site_info = SiteInfo(site_data, grid_resource_file=g_file) # set up hybrid simulation with all the required parameters solar_size_mw = 100 wind_size_mw = 100 interconnection_size_mw = 150 technologies = { 'pv': { 'system_capacity_kw': solar_size_mw * 1000, 'layout_params': PVGridParameters(x_position=0.5, y_position=0.5, aspect_power=0, gcr=0.5,
def run(default_config: Dict) -> None: config, output_path, run_name = setup_run(default_config) recorder = DataRecorder.make_data_recorder(output_path) max_evaluations = config['max_evaluations'] location_index = config['location'] location = locations[location_index] site = config['site'] site_data = None 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 + "'") site_info = SiteInfo(site_data) inner_problem = HybridOptimizationProblem(site_info, config['num_turbines'], config['solar_capacity']) problem = HybridParametrization(inner_problem) optimizer = ParametrizedOptimizationDriver(problem, recorder=recorder, **config['optimizer_config']) figure = plt.figure(1) axes = figure.add_subplot(111) axes.set_aspect('equal') plt.grid() plt.tick_params(which='both', labelsize=15) plt.xlabel('x (m)', fontsize=15) plt.ylabel('y (m)', fontsize=15) site_info.plot() score, evaluation, best_solution = optimizer.central_solution() score, evaluation = problem.objective(best_solution) if score is None else score print(-1, ' ', score, evaluation) print('setup 1') num_substeps = 1 figure, axes = plt.subplots(dpi=200) axes.set_aspect(1) animation_writer = PillowWriter(2 * num_substeps) animation_writer.setup(figure, os.path.join(output_path, 'trajectory.gif'), dpi=200) print('setup 2') _, _, central_solution = optimizer.central_solution() print('setup 3') bounds = problem.inner_problem.site_info.polygon.bounds site_sw_bound = np.array([bounds[0], bounds[1]]) site_ne_bound = np.array([bounds[2], bounds[3]]) site_center = .5 * (site_sw_bound + site_ne_bound) max_delta = max(bounds[2] - bounds[0], bounds[3] - bounds[1]) reach = (max_delta / 2) * 1.3 min_plot_bound = site_center - reach max_plot_bound = site_center + reach print('setup 4') best_score, best_evaluation, best_solution = 0.0, 0.0, None def plot_candidate(candidate): nonlocal best_score, best_evaluation, best_solution axes.cla() axes.set(xlim=(min_plot_bound[0], max_plot_bound[0]), ylim=(min_plot_bound[1], max_plot_bound[1])) wind_color = (153 / 255, 142 / 255, 195 / 255) solar_color = (241 / 255, 163 / 255, 64 / 255) central_color = (.5, .5, .5) conforming_candidate, _, __ = problem.make_conforming_candidate_and_get_penalty(candidate) problem.plot_candidate(conforming_candidate, figure, axes, central_color, central_color, alpha=.7) if best_solution is not None: conforming_best, _, __ = problem.make_conforming_candidate_and_get_penalty(best_solution) problem.plot_candidate(conforming_best, figure, axes, wind_color, solar_color, alpha=1.0) axes.set_xlabel('Best Solution AEP: {}'.format(best_evaluation)) else: axes.set_xlabel('') axes.legend([ Line2D([0], [0], color=wind_color, lw=8), Line2D([0], [0], color=solar_color, lw=8), Line2D([0], [0], color=central_color, lw=8), ], ['Wind Layout', 'Solar Layout', 'Mean Search Vector'], loc='lower left') animation_writer.grab_frame() print('plot candidate') plot_candidate(central_solution) central_prev = central_solution # TODO: make a smooth transition between points # TODO: plot exclusion zones print('begin') try: while optimizer.num_evaluations() < max_evaluations: print('step start') logger.info("Starting step, num evals {}".format(optimizer.num_evaluations())) optimizer.step() print('step end') proportion = min(1.0, optimizer.num_evaluations() / max_evaluations) g = 1.0 * proportion b = 1.0 - g a = .5 color = (b, g, b) best_score, best_evaluation, best_solution = optimizer.best_solution() central_score, central_evaluation, central_solution = optimizer.central_solution() a1 = optimizer.converter.convert_from(central_prev) b1 = optimizer.converter.convert_from(central_solution) a = np.array(a1, dtype=np.float64) b = np.array(b1, dtype=np.float64) for i in range(num_substeps): p = (i + 1) / num_substeps c = (1 - p) * a + p * b candidate = optimizer.converter.convert_to(c) plot_candidate(candidate) central_prev = central_solution print(optimizer.num_iterations(), ' ', optimizer.num_evaluations(), best_score, best_evaluation) except: raise RuntimeError("Optimizer error encountered. Try modifying the config to use larger generation_size if" " encountering singular matrix errors.") animation_writer.finish() optimizer.close() print("Results and animation written to " + os.path.abspath(output_path))
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
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
def init_simulation(): """ Create the simulation object needed to calculate the objective of the problem TODO: make this representative of the design variables, is there currently a tradeoff in objectives? :return: The HOPP simulation as defined for this problem """ site = 'irregular' location = locations[1] 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 + "'") solar_file = examples_dir.parent / "resource_files" / "solar" / "Beni_Miha" / "659265_32.69_10.90_2019.csv" grid_file = examples_dir.parent / "resource_files" / "grid" / "tunisia_est_grid_prices.csv" 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 tower_cycle_mw = 125 # battery_capacity_mwh = 15 interconnection_size_mw = 400 technologies = { 'tower': { 'cycle_capacity_kw': tower_cycle_mw * 1000, 'solar_multiple': 2.0, 'tes_hours': 12.0, 'optimize_field_before_sim': False }, # TODO: turn on 'pv': { 'system_capacity_kw': solar_size_mw * 1000 }, # 'battery': {'system_capacity_kwh': battery_capacity_mwh * 1000, # 'system_capacity_kw': battery_capacity_mwh * 1000 / 10}, 'grid': interconnection_size_mw * 1000 } # Create model # TODO: turn these off to run full year simulation dispatch_options = {'is_test_start_year': True, 'is_test_end_year': False} # 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) return hybrid_plant