diff --git a/.gitignore b/.gitignore index c35f777..21d5aa9 100644 --- a/.gitignore +++ b/.gitignore @@ -125,8 +125,8 @@ dmypy.json # Datafiles *.xlsx *.csv - - +!onsset/data/*.csv +!test/*/*.csv # VSCode .vscode diff --git a/onsset/data/.gitkeep b/onsset/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/onsset/onsset.py b/onsset/onsset.py index 69a3616..dcfa6aa 100644 --- a/onsset/onsset.py +++ b/onsset/onsset.py @@ -1,10 +1,10 @@ import logging from math import exp, log, pi from typing import Dict -import scipy.spatial import numpy as np import pandas as pd +import scipy.spatial logging.basicConfig(format='%(asctime)s\t\t%(message)s', level=logging.DEBUG) logger = logging.getLogger(__name__) @@ -1566,7 +1566,7 @@ def do_kdtree(combined_x_y_arrays, points): dist, indexes = mytree.query(points) return indexes - def calculate_new_connections(self, year, time_step, start_year): + def calculate_new_connections(self, year: int, time_step: int, start_year: int): """this method defines new connections for grid related purposes Arguments @@ -1719,7 +1719,8 @@ def calculate_total_demand_per_settlement(self, year): self.df.loc[self.df[SET_URBAN] == 2, SET_TOTAL_ENERGY_PER_CELL] = \ self.df[SET_CAPITA_DEMAND] * self.df[SET_POP + "{}".format(year)] - def set_scenario_variables(self, year, num_people_per_hh_rural, num_people_per_hh_urban, time_step, start_year, + def set_scenario_variables(self, year: int, num_people_per_hh_rural, num_people_per_hh_urban, + time_step: int, start_year: int, urban_tier, rural_tier, end_year_pop, productive_demand): """ this method determines some basic parameters required in LCOE calculation @@ -1751,7 +1752,7 @@ def set_scenario_variables(self, year, num_people_per_hh_rural, num_people_per_h self.calculate_total_demand_per_settlement(year) def calculate_off_grid_lcoes(self, mg_hydro_calc, mg_wind_calc, mg_pv_calc, sa_pv_calc, mg_diesel_calc, - sa_diesel_calc, year, end_year, time_step, diesel_techs=0): + sa_diesel_calc, year: int, end_year: int, time_step: int, diesel_techs=0): """ Calculate the LCOEs for all off-grid technologies diff --git a/onsset/runner.py b/onsset/runner.py index 859c7c1..63e5776 100644 --- a/onsset/runner.py +++ b/onsset/runner.py @@ -2,35 +2,50 @@ import logging import os +from csv import DictReader, DictWriter +from typing import Any, Dict import pandas as pd -from onsset import (SET_ELEC_ORDER, SET_LCOE_GRID, SET_MIN_GRID_DIST, SET_GRID_PENALTY, - SET_MV_CONNECT_DIST, SET_WINDCF, SettlementProcessor, Technology) - -try: - from onsset.specs import (SPE_COUNTRY, SPE_ELEC, SPE_ELEC_MODELLED, - SPE_ELEC_RURAL, SPE_ELEC_URBAN, SPE_END_YEAR, - SPE_GRID_CAPACITY_INVESTMENT, SPE_GRID_LOSSES, - SPE_MAX_GRID_EXTENSION_DIST, - SPE_NUM_PEOPLE_PER_HH_RURAL, - SPE_NUM_PEOPLE_PER_HH_URBAN, SPE_POP, SPE_POP_FUTURE, - SPE_START_YEAR, SPE_URBAN, SPE_URBAN_FUTURE, - SPE_URBAN_MODELLED) -except ImportError: - from specs import (SPE_COUNTRY, SPE_ELEC, SPE_ELEC_MODELLED, - SPE_ELEC_RURAL, SPE_ELEC_URBAN, SPE_END_YEAR, - SPE_GRID_CAPACITY_INVESTMENT, SPE_GRID_LOSSES, - SPE_MAX_GRID_EXTENSION_DIST, - SPE_NUM_PEOPLE_PER_HH_RURAL, - SPE_NUM_PEOPLE_PER_HH_URBAN, SPE_POP, SPE_POP_FUTURE, - SPE_START_YEAR, SPE_URBAN, SPE_URBAN_FUTURE, - SPE_URBAN_MODELLED) -from openpyxl import load_workbook +from onsset import (SET_ELEC_ORDER, SET_GRID_PENALTY, SET_LCOE_GRID, + SET_MIN_GRID_DIST, SET_MV_CONNECT_DIST, SET_WINDCF, + SettlementProcessor, Technology) +from onsset.specs import (SPE_GRID_CAPACITY_INVESTMENT, SPE_GRID_LOSSES, + SPE_MAX_GRID_EXTENSION_DIST, + SPE_NUM_PEOPLE_PER_HH_RURAL, + SPE_NUM_PEOPLE_PER_HH_URBAN) logging.basicConfig(format='%(asctime)s\t\t%(message)s', level=logging.DEBUG) -def calibration(specs_path, csv_path, specs_path_calib, calibrated_csv_path): +def read_scenario_data(path_to_config: str) -> Dict[str, Any]: + """Reads the scenario data into a dictionary + """ + config = {} # typing: Dict[str, Any] + with open(path_to_config, 'r') as csvfile: + reader = DictReader(csvfile) + config_file = list(reader) + for row in config_file: + if row['value']: + if row['data_type'] == 'int': + config[row['parameter']] = int(row['value']) + elif row['data_type'] == 'float': + config[row['parameter']] = float(row['value']) + elif row['data_type'] == 'str': + config[row['parameter']] = str(row['value']) + else: + raise ValueError("Config file data type not recognised.") + + return config + + +def write_data_to_csv(calibration_data: Dict, calibration_path: str): + with open(calibration_path, 'w') as csv_file: + writer = DictWriter(csv_file, fieldnames=['parameter', 'value']) + for key, value in calibration_data.items(): + writer.writerow({'parameter': key, 'value': value}) + + +def calibration(specs_path: str, csv_path, specs_path_calib, calibrated_csv_path): """ Arguments @@ -40,14 +55,12 @@ def calibration(specs_path, csv_path, specs_path_calib, calibrated_csv_path): specs_path_calib calibrated_csv_path """ - specs_data = pd.read_excel(specs_path, sheet_name='SpecsData') + specs_data = read_scenario_data(specs_path) settlements_in_csv = csv_path settlements_out_csv = calibrated_csv_path - onsseter = SettlementProcessor(settlements_in_csv) - - num_people_per_hh_rural = float(specs_data.iloc[0][SPE_NUM_PEOPLE_PER_HH_RURAL]) - num_people_per_hh_urban = float(specs_data.iloc[0][SPE_NUM_PEOPLE_PER_HH_URBAN]) + num_people_per_hh_rural = specs_data['NumPeoplePerHHRural'] + num_people_per_hh_urban = specs_data['NumPeoplePerHHUrban'] # RUN_PARAM: these are the annual household electricity targets tier_1 = 38.7 # 38.7 refers to kWh/household/year. It is the mean value between Tier 1 and Tier 2 @@ -56,6 +69,8 @@ def calibration(specs_path, csv_path, specs_path_calib, calibrated_csv_path): tier_4 = 2117 tier_5 = 2993 + onsseter = SettlementProcessor(settlements_in_csv) + onsseter.prepare_wtf_tier_columns(num_people_per_hh_rural, num_people_per_hh_urban, tier_1, tier_2, tier_3, tier_4, tier_5) onsseter.condition_df() @@ -63,50 +78,44 @@ def calibration(specs_path, csv_path, specs_path_calib, calibrated_csv_path): onsseter.df[SET_WINDCF] = onsseter.calc_wind_cfs() - pop_actual = specs_data.loc[0, SPE_POP] - pop_future_high = specs_data.loc[0, SPE_POP_FUTURE + 'High'] - pop_future_low = specs_data.loc[0, SPE_POP_FUTURE + 'Low'] - urban_current = specs_data.loc[0, SPE_URBAN] - urban_future = specs_data.loc[0, SPE_URBAN_FUTURE] - start_year = int(specs_data.loc[0, SPE_START_YEAR]) - end_year = int(specs_data.loc[0, SPE_END_YEAR]) - intermediate_year = 2025 - elec_actual = specs_data.loc[0, SPE_ELEC] - elec_actual_urban = specs_data.loc[0, SPE_ELEC_URBAN] - elec_actual_rural = specs_data.loc[0, SPE_ELEC_RURAL] + elec_actual = specs_data['ElecActual'] + elec_actual_urban = specs_data['Urban_elec_ratio'] + elec_actual_rural = specs_data['Rural_elec_ratio'] - pop_modelled, urban_modelled = onsseter.calibrate_current_pop_and_urban(pop_actual, urban_current) + pop_modelled, urban_modelled = onsseter.calibrate_current_pop_and_urban(specs_data['PopStartYear'], + specs_data['UrbanRatioStartYear']) - onsseter.project_pop_and_urban(pop_modelled, pop_future_high, pop_future_low, urban_modelled, - urban_future, start_year, end_year, intermediate_year) + onsseter.project_pop_and_urban(pop_modelled, specs_data['PopEndYearHigh'], + specs_data['PopEndYearLow'], + urban_modelled, + specs_data['UrbanRatioEndYear'], + specs_data['StartYear'], + specs_data['EndYear'], + intermediate_year) elec_modelled, rural_elec_ratio, urban_elec_ratio = \ - onsseter.elec_current_and_future(elec_actual, elec_actual_urban, elec_actual_rural, start_year) + onsseter.elec_current_and_future(elec_actual, + elec_actual_urban, + elec_actual_rural, + specs_data['StartYear']) - # In case there are limitations in the way grid expansion is moving in a country, + # In case there are limitations in the way grid expansion is moving in a country, # this can be reflected through gridspeed. # In this case the parameter is set to a very high value therefore is not taken into account. - specs_data.loc[0, SPE_URBAN_MODELLED] = urban_modelled - specs_data.loc[0, SPE_ELEC_MODELLED] = elec_modelled - specs_data.loc[0, 'rural_elec_ratio_modelled'] = rural_elec_ratio - specs_data.loc[0, 'urban_elec_ratio_modelled'] = urban_elec_ratio + specs_data['UrbanRatioModelled'] = urban_modelled + specs_data['ElecModelled'] = elec_modelled + specs_data['rural_elec_ratio_modelled'] = rural_elec_ratio + specs_data['urban_elec_ratio_modelled'] = urban_elec_ratio - book = load_workbook(specs_path) - writer = pd.ExcelWriter(specs_path_calib, engine='openpyxl') - writer.book = book - # RUN_PARAM: Here the calibrated "specs" data are copied to a new tab called "SpecsDataCalib". - # This is what will later on be used to feed the model - specs_data.to_excel(writer, sheet_name='SpecsDataCalib', index=False) - writer.save() - writer.close() + write_data_to_csv(specs_data, specs_path_calib) logging.info('Calibration finished. Results are transferred to the csv file') onsseter.df.to_csv(settlements_out_csv, index=False) -def scenario(specs_path, calibrated_csv_path, results_folder, summary_folder): +def scenario(specs_path, scenario_path, calibrated_csv_path, results_folder, summary_folder): """ Arguments @@ -118,15 +127,15 @@ def scenario(specs_path, calibrated_csv_path, results_folder, summary_folder): """ - scenario_info = pd.read_excel(specs_path, sheet_name='ScenarioInfo') + scenario_info = pd.read_excel(scenario_path, sheet_name='ScenarioInfo') scenarios = scenario_info['Scenario'] - scenario_parameters = pd.read_excel(specs_path, sheet_name='ScenarioParameters') - specs_data = pd.read_excel(specs_path, sheet_name='SpecsDataCalib') - print(specs_data.loc[0, SPE_COUNTRY]) + scenario_parameters = pd.read_excel(scenario_path, sheet_name='ScenarioParameters') + specs_data = read_scenario_data(specs_path) + print(specs_data['Country']) for scenario in scenarios: print('Scenario: ' + str(scenario + 1)) - country_id = specs_data.iloc[0]['CountryCode'] + country_id = specs_data['CountryCode'] pop_index = scenario_info.iloc[scenario]['Population_Growth'] tier_index = scenario_info.iloc[scenario]['Target_electricity_consumption_level'] @@ -150,7 +159,23 @@ def scenario(specs_path, calibrated_csv_path, results_folder, summary_folder): prioritization = scenario_parameters.iloc[prio_index]['PrioritizationAlgorithm'] auto_intensification = scenario_parameters.iloc[prio_index]['AutoIntensificationKM'] - settlements_in_csv = calibrated_csv_path + start_year = int(specs_data['StartYear']) + end_year = int(specs_data['EndYear']) + + num_people_per_hh_rural = float(specs_data[SPE_NUM_PEOPLE_PER_HH_RURAL]) + num_people_per_hh_urban = float(specs_data[SPE_NUM_PEOPLE_PER_HH_URBAN]) + max_grid_extension_dist = float(specs_data[SPE_MAX_GRID_EXTENSION_DIST]) + annual_grid_cap_gen_limit = specs_data['NewGridGenerationCapacityAnnualLimitMW'] * 1000 + + df_summary, sumtechs, df = perform_run(calibrated_csv_path, start_year, end_year, + specs_data, + grid_price, pv_capital_cost_adjust, diesel_price, + five_year_target, annual_grid_cap_gen_limit, + annual_new_grid_connections_limit, num_people_per_hh_rural, + num_people_per_hh_urban, urban_tier, rural_tier, + end_year_pop, productive_demand, max_grid_extension_dist, + auto_intensification, prioritization) + settlements_out_csv = os.path.join(results_folder, '{}-1-{}_{}_{}_{}_{}_{}.csv'.format(country_id, pop_index, tier_index, five_year_index, grid_index, pv_index, @@ -160,176 +185,176 @@ def scenario(specs_path, calibrated_csv_path, results_folder, summary_folder): five_year_index, grid_index, pv_index, prio_index)) - onsseter = SettlementProcessor(settlements_in_csv) - - start_year = specs_data.iloc[0][SPE_START_YEAR] - end_year = specs_data.iloc[0][SPE_END_YEAR] + df_summary.to_csv(summary_csv, index=sumtechs) + df.to_csv(settlements_out_csv, index=False) - num_people_per_hh_rural = float(specs_data.iloc[0][SPE_NUM_PEOPLE_PER_HH_RURAL]) - num_people_per_hh_urban = float(specs_data.iloc[0][SPE_NUM_PEOPLE_PER_HH_URBAN]) - max_grid_extension_dist = float(specs_data.iloc[0][SPE_MAX_GRID_EXTENSION_DIST]) - annual_grid_cap_gen_limit = specs_data.loc[0, 'NewGridGenerationCapacityAnnualLimitMW'] * 1000 + logging.info('Finished') - # RUN_PARAM: Fill in general and technology specific parameters (e.g. discount rate, losses etc.) - Technology.set_default_values(base_year=start_year, - start_year=start_year, - end_year=end_year, - discount_rate=0.08) - grid_calc = Technology(om_of_td_lines=0.02, - distribution_losses=float(specs_data.iloc[0][SPE_GRID_LOSSES]), - connection_cost_per_hh=125, - base_to_peak_load_ratio=0.8, - capacity_factor=1, +def perform_run(calibrated_csv_path, start_year, end_year, specs_data, grid_price, pv_capital_cost_adjust, + diesel_price, five_year_target, annual_grid_cap_gen_limit, annual_new_grid_connections_limit, + num_people_per_hh_rural, num_people_per_hh_urban, urban_tier, rural_tier, end_year_pop, + productive_demand, max_grid_extension_dist, auto_intensification, prioritization): + """Performs an OnSSET run + """ + onsseter = SettlementProcessor(calibrated_csv_path) + # RUN_PARAM: Fill in general and technology specific parameters (e.g. discount rate, losses etc.) + Technology.set_default_values(base_year=start_year, + start_year=start_year, + end_year=end_year, + discount_rate=0.08) + + grid_calc = Technology(om_of_td_lines=0.02, + distribution_losses=float(specs_data[SPE_GRID_LOSSES]), + connection_cost_per_hh=125, + base_to_peak_load_ratio=0.8, + capacity_factor=1, + tech_life=30, + grid_capacity_investment=float(specs_data[SPE_GRID_CAPACITY_INVESTMENT]), + grid_penalty_ratio=1, + grid_price=grid_price) + + mg_hydro_calc = Technology(om_of_td_lines=0.02, + distribution_losses=0.05, + connection_cost_per_hh=100, + base_to_peak_load_ratio=0.85, + capacity_factor=0.5, tech_life=30, - grid_capacity_investment=float(specs_data.iloc[0][SPE_GRID_CAPACITY_INVESTMENT]), - grid_penalty_ratio=1, - grid_price=grid_price) - - mg_hydro_calc = Technology(om_of_td_lines=0.02, - distribution_losses=0.05, - connection_cost_per_hh=100, - base_to_peak_load_ratio=0.85, - capacity_factor=0.5, - tech_life=30, - capital_cost={float("inf"): 3000}, - om_costs=0.03, - mini_grid=True) - - mg_wind_calc = Technology(om_of_td_lines=0.02, - distribution_losses=0.05, - connection_cost_per_hh=100, - base_to_peak_load_ratio=0.85, - capital_cost={float("inf"): 3750}, - om_costs=0.02, - tech_life=20, - mini_grid=True) - - mg_pv_calc = Technology(om_of_td_lines=0.02, + capital_cost={float("inf"): 3000}, + om_costs=0.03, + mini_grid=True) + + mg_wind_calc = Technology(om_of_td_lines=0.02, + distribution_losses=0.05, + connection_cost_per_hh=100, + base_to_peak_load_ratio=0.85, + capital_cost={float("inf"): 3750}, + om_costs=0.02, + tech_life=20, + mini_grid=True) + + mg_pv_calc = Technology(om_of_td_lines=0.02, + distribution_losses=0.05, + connection_cost_per_hh=100, + base_to_peak_load_ratio=0.85, + tech_life=20, + om_costs=0.015, + capital_cost={float("inf"): 2950 * pv_capital_cost_adjust}, + mini_grid=True) + + sa_pv_calc = Technology(base_to_peak_load_ratio=0.9, + tech_life=15, + om_costs=0.02, + capital_cost={float("inf"): 6950 * pv_capital_cost_adjust, + 1: 4470 * pv_capital_cost_adjust, + 0.100: 6380 * pv_capital_cost_adjust, + 0.050: 8780 * pv_capital_cost_adjust, + 0.020: 9620 * pv_capital_cost_adjust + }, + standalone=True) + + mg_diesel_calc = Technology(om_of_td_lines=0.02, distribution_losses=0.05, connection_cost_per_hh=100, base_to_peak_load_ratio=0.85, - tech_life=20, - om_costs=0.015, - capital_cost={float("inf"): 2950 * pv_capital_cost_adjust}, + capacity_factor=0.7, + tech_life=15, + om_costs=0.1, + capital_cost={float("inf"): 721}, mini_grid=True) - sa_pv_calc = Technology(base_to_peak_load_ratio=0.9, - tech_life=15, - om_costs=0.02, - capital_cost={float("inf"): 6950 * pv_capital_cost_adjust, - 1: 4470 * pv_capital_cost_adjust, - 0.100: 6380 * pv_capital_cost_adjust, - 0.050: 8780 * pv_capital_cost_adjust, - 0.020: 9620 * pv_capital_cost_adjust - }, + sa_diesel_calc = Technology(base_to_peak_load_ratio=0.9, + capacity_factor=0.5, + tech_life=10, + om_costs=0.1, + capital_cost={float("inf"): 938}, standalone=True) - mg_diesel_calc = Technology(om_of_td_lines=0.02, - distribution_losses=0.05, - connection_cost_per_hh=100, - base_to_peak_load_ratio=0.85, - capacity_factor=0.7, - tech_life=15, - om_costs=0.1, - capital_cost={float("inf"): 721}, - mini_grid=True) - - sa_diesel_calc = Technology(base_to_peak_load_ratio=0.9, - capacity_factor=0.5, - tech_life=10, - om_costs=0.1, - capital_cost={float("inf"): 938}, - standalone=True) - - sa_diesel_cost = {'diesel_price': diesel_price, - 'efficiency': 0.28, - 'diesel_truck_consumption': 14, - 'diesel_truck_volume': 300} - - mg_diesel_cost = {'diesel_price': diesel_price, - 'efficiency': 0.33, - 'diesel_truck_consumption': 33.7, - 'diesel_truck_volume': 15000} - - # RUN_PARAM: One shall define here the years of analysis (excluding start year), - # together with access targets per interval and timestep duration - yearsofanalysis = [2025, 2030] - eleclimits = {2025: five_year_target, 2030: 1} - time_steps = {2025: 7, 2030: 5} - - elements = ["1.Population", "2.New_Connections", "3.Capacity", "4.Investment"] - techs = ["Grid", "SA_Diesel", "SA_PV", "MG_Diesel", "MG_PV", "MG_Wind", "MG_Hydro", "MG_Hybrid"] - sumtechs = [] - for element in elements: - for tech in techs: - sumtechs.append(element + "_" + tech) - total_rows = len(sumtechs) - df_summary = pd.DataFrame(columns=yearsofanalysis) - for row in range(0, total_rows): - df_summary.loc[sumtechs[row]] = "Nan" - - onsseter.current_mv_line_dist() - - for year in yearsofanalysis: - eleclimit = eleclimits[year] - time_step = time_steps[year] - - if year - time_step == start_year: - grid_cap_gen_limit = time_step * annual_grid_cap_gen_limit - grid_connect_limit = time_step * annual_new_grid_connections_limit - else: - grid_cap_gen_limit = 9999999999 - grid_connect_limit = 9999999999 - - onsseter.set_scenario_variables(year, num_people_per_hh_rural, num_people_per_hh_urban, time_step, - start_year, urban_tier, rural_tier, end_year_pop, productive_demand) - - onsseter.diesel_cost_columns(sa_diesel_cost, mg_diesel_cost, year) - - sa_diesel_investment, sa_pv_investment, mg_diesel_investment, mg_pv_investment, mg_wind_investment, \ - mg_hydro_investment = onsseter.calculate_off_grid_lcoes(mg_hydro_calc, mg_wind_calc, mg_pv_calc, - sa_pv_calc, mg_diesel_calc, - sa_diesel_calc, year, end_year, time_step) - - grid_investment, grid_cap_gen_limit, grid_connect_limit = \ - onsseter.pre_electrification(grid_price, year, time_step, end_year, grid_calc, grid_cap_gen_limit, - grid_connect_limit) - - onsseter.df[SET_LCOE_GRID + "{}".format(year)], onsseter.df[SET_MIN_GRID_DIST + "{}".format(year)], \ + sa_diesel_cost = {'diesel_price': diesel_price, + 'efficiency': 0.28, + 'diesel_truck_consumption': 14, + 'diesel_truck_volume': 300} + + mg_diesel_cost = {'diesel_price': diesel_price, + 'efficiency': 0.33, + 'diesel_truck_consumption': 33.7, + 'diesel_truck_volume': 15000} + + # RUN_PARAM: One shall define here the years of analysis (excluding start year), + # together with access targets per interval and timestep duration + yearsofanalysis = [2025, 2030] + eleclimits = {2025: five_year_target, 2030: 1} + time_steps = {2025: 7, 2030: 5} + + elements = ["1.Population", "2.New_Connections", "3.Capacity", "4.Investment"] + techs = ["Grid", "SA_Diesel", "SA_PV", "MG_Diesel", "MG_PV", "MG_Wind", "MG_Hydro", "MG_Hybrid"] + sumtechs = [] + for element in elements: + for tech in techs: + sumtechs.append(element + "_" + tech) + total_rows = len(sumtechs) + df_summary = pd.DataFrame(columns=yearsofanalysis) + for row in range(0, total_rows): + df_summary.loc[sumtechs[row]] = "Nan" + + onsseter.current_mv_line_dist() + + for year in yearsofanalysis: + eleclimit = eleclimits[year] + time_step = time_steps[year] + + if year - time_step == start_year: + grid_cap_gen_limit = time_step * annual_grid_cap_gen_limit + grid_connect_limit = time_step * annual_new_grid_connections_limit + else: + grid_cap_gen_limit = 9999999999 + grid_connect_limit = 9999999999 + + onsseter.set_scenario_variables(year, num_people_per_hh_rural, num_people_per_hh_urban, time_step, + start_year, urban_tier, rural_tier, end_year_pop, productive_demand) + + onsseter.diesel_cost_columns(sa_diesel_cost, mg_diesel_cost, year) + + sa_diesel_investment, sa_pv_investment, mg_diesel_investment, mg_pv_investment, mg_wind_investment, \ + mg_hydro_investment = onsseter.calculate_off_grid_lcoes(mg_hydro_calc, mg_wind_calc, mg_pv_calc, + sa_pv_calc, mg_diesel_calc, + sa_diesel_calc, year, end_year, time_step) + + grid_investment, grid_cap_gen_limit, grid_connect_limit = \ + onsseter.pre_electrification(grid_price, year, time_step, end_year, grid_calc, grid_cap_gen_limit, + grid_connect_limit) + + onsseter.df[SET_LCOE_GRID + "{}".format(year)], onsseter.df[SET_MIN_GRID_DIST + "{}".format(year)], \ onsseter.df[SET_ELEC_ORDER + "{}".format(year)], onsseter.df[SET_MV_CONNECT_DIST], grid_investment = \ - onsseter.elec_extension(grid_calc, - max_grid_extension_dist, - year, - start_year, - end_year, - time_step, - grid_cap_gen_limit, - grid_connect_limit, - auto_intensification=auto_intensification, - prioritization=prioritization, - new_investment=grid_investment) - - onsseter.results_columns(year, time_step, prioritization, auto_intensification) - - onsseter.calculate_investments(sa_diesel_investment, sa_pv_investment, mg_diesel_investment, - mg_pv_investment, mg_wind_investment, - mg_hydro_investment, grid_investment, year) - - onsseter.apply_limitations(eleclimit, year, time_step, prioritization, auto_intensification) - - onsseter.calculate_new_capacity(mg_hydro_calc, mg_wind_calc, mg_pv_calc, sa_pv_calc, mg_diesel_calc, - sa_diesel_calc, grid_calc, year) - - onsseter.calc_summaries(df_summary, sumtechs, year) - - for i in range(len(onsseter.df.columns)): - if onsseter.df.iloc[:, i].dtype == 'float64': - onsseter.df.iloc[:, i] = pd.to_numeric(onsseter.df.iloc[:, i], downcast='float') - elif onsseter.df.iloc[:, i].dtype == 'int64': - onsseter.df.iloc[:, i] = pd.to_numeric(onsseter.df.iloc[:, i], downcast='signed') - - df_summary.to_csv(summary_csv, index=sumtechs) - onsseter.df.to_csv(settlements_out_csv, index=False) - - logging.info('Finished') + onsseter.elec_extension(grid_calc, + max_grid_extension_dist, + year, + start_year, + end_year, + time_step, + grid_cap_gen_limit, + grid_connect_limit, + auto_intensification=auto_intensification, + prioritization=prioritization, + new_investment=grid_investment) + + onsseter.results_columns(year, time_step, prioritization, auto_intensification) + + onsseter.calculate_investments(sa_diesel_investment, sa_pv_investment, mg_diesel_investment, + mg_pv_investment, mg_wind_investment, + mg_hydro_investment, grid_investment, year) + + onsseter.apply_limitations(eleclimit, year, time_step, prioritization, auto_intensification) + + onsseter.calculate_new_capacity(mg_hydro_calc, mg_wind_calc, mg_pv_calc, sa_pv_calc, mg_diesel_calc, + sa_diesel_calc, grid_calc, year) + + onsseter.calc_summaries(df_summary, sumtechs, year) + + for i in range(len(onsseter.df.columns)): + if onsseter.df.iloc[:, i].dtype == 'float64': + onsseter.df.iloc[:, i] = pd.to_numeric(onsseter.df.iloc[:, i], downcast='float') + elif onsseter.df.iloc[:, i].dtype == 'int64': + onsseter.df.iloc[:, i] = pd.to_numeric(onsseter.df.iloc[:, i], downcast='signed') + + return df_summary, sumtechs, onsseter.df diff --git a/test/test_config.py b/test/test_config.py new file mode 100644 index 0000000..afe69bf --- /dev/null +++ b/test/test_config.py @@ -0,0 +1,37 @@ +import os + +from onsset.runner import read_scenario_data + + +def test_read_scenario_data(): + """Store the configuration data as a dictionary + """ + + path = os.path.join('test', 'test_data', 'config.csv') + + data = [ + ['Country', 'Djibouti'], + ['CountryCode', 'dj'], + ['StartYear', 2018], + ['EndYear', 2030], + ['PopStartYear', 971000], + ['UrbanRatioStartYear', 0.778], + ['PopEndYearHigh', 1179150], + ['PopEndYearLow', 1132710], + ['UrbanRatioEndYear', 0.8], + ['NumPeoplePerHHRural', 7.7], + ['NumPeoplePerHHUrban', 6.5], + ['GridCapacityInvestmentCost', 4426], + ['GridLosses', 0.083], + ['BaseToPeak', 0.8], + ['ExistingGridCostRatio', 0.1], + ['MaxGridExtensionDist', 50], + ['NewGridGenerationCapacityAnnualLimitMW', 19], + ['ElecActual', 0.6], + ['Rural_elec_ratio', 0.26], + ['Urban_elec_ratio', 0.7], + ] + actual = read_scenario_data(path) + expected = dict(data) + + assert actual == expected diff --git a/test/test_data/config.csv b/test/test_data/config.csv new file mode 100644 index 0000000..8059b3b --- /dev/null +++ b/test/test_data/config.csv @@ -0,0 +1,26 @@ +parameter,value,description,data_type +Country,Djibouti,,str +CountryCode,dj,,str +StartYear,2018,,int +EndYear,2030,,int +PopStartYear,971000,,int +UrbanRatioStartYear,0.778,,float +PopEndYearHigh,1179150,,int +PopEndYearLow,1132710,,int +UrbanRatioEndYear,0.8,,float +NumPeoplePerHHRural,7.7,,float +NumPeoplePerHHUrban,6.5,,float +GridCapacityInvestmentCost,4426,,float +GridLosses,0.083,,float +BaseToPeak,0.8,,float +ExistingGridCostRatio,0.1,,float +MaxGridExtensionDist,50,,float +NewGridGenerationCapacityAnnualLimitMW,19,,float +ElecActual,0.6,,float +Rural_elec_ratio,0.26,,float +Urban_elec_ratio,0.7,,float +ElecModelled,,,float +urban_elec_ratio_modelled,,,float +rural_elec_ratio_modelled,,,float +UrbanCutOff,,,float +UrbanRatioModelled,,,float diff --git a/test/test_data/template_config.csv b/test/test_data/template_config.csv new file mode 100644 index 0000000..64a9013 --- /dev/null +++ b/test/test_data/template_config.csv @@ -0,0 +1,37 @@ +parameter;value;description +Country;Benin;Name of country +CountryCode;bj;ISO-2 code of country +StartYear;2020;Start year of analysis +EndYEar;2030;End year of analysis +PopStartYear;12123000;Population in start year +UrbanRatioStartYear;0.484;Urban ratio in start year (0 = all rural, 1 = all urban) +UrbanCutOff;;Cut-off value for urban settlements, all settlements with higher population are urban, else rural. +UrbanRatioModelled;;Urban ratio modelled based on input +PopEndYearHigh;15672000;Population in 2030 (high variant) +PopEndYearLow;15672000;Population in 2030 (low variant) +UrbanRatioEndYear;0.541;Urban ratio in end year (0 = all rural, 1 = all urban) +NumPeoplePerHHRural;3.6;Number of people per housheold in rural setttlements +NumPeoplePerHHUrban;3.1;Number of people per housheold in urban setttlements +GridCapacityInvestmentCost;1982;grid capacity investments costs USD/kW. Weighted average of additions to the grid +GridLosses;0.143;Losses in transmission and distrbution network (%) +DiscountRate;0.08;Discount rate (%) +BaseToPeak;0.8;Base to peak ratio (%) +ExistingGridCostRatio;0.1; +MaxGridExtensionDist;50;Max limit for how far the grid can extend +NewGridGenerationCapacityAnnualLimitMW;70;Annual added capacity limit (MW/year) between start year and the intermediate year (no limit between intermediate and end yaer) +ElecActual;0.42;National electrification rate as reported by country +Rural_elec_ratio;0.18;Rural electrification rate as repoterd by country +Urban_elec_ratio;0.67;Urban electrification rate as repoterd by country +ElecModelled;;National electrification rate modelled by OnSSET +urban_elec_ratio_modelled;;Rural electrification rate modelled by OnSSET +rural_elec_ratio_modelled;;Urban electrification rate modelled by OnSSET +UrbanTargetTier;5;Electricity demand in urban settlements in the end year (1-5 are MTF tiers, 6 is custom demand) +RuralTargetTier;4;Electricity demand in rural settlements in the end year (1-5 are MTF tiers, 6 is custom demand) +5YearTarget;0.71;Electrification rate 5 years from start year of analysis +GridConnectionsLimitThousands;9999;Limit for how many people that can be connected to the grid +GridGenerationCost;0.16;Cost of generating electricity for the centralised grid +PV_Cost_adjust;1.25;Development of PV price +DieselPrice;9999;Diesel price +ProductiveDemand;0; +PrioritizationAlgorithm;2;Determines which settlements to prioritize first (for electrification in the intermediate year). +AutoIntensificationKM;0; diff --git a/test/test_runner.py b/test/test_runner.py index 41d0da2..a41a52a 100644 --- a/test/test_runner.py +++ b/test/test_runner.py @@ -31,15 +31,15 @@ def run_analysis(tmpdir): Returns a tuple of bool for whether the summary or full files match """ - - specs_path = os.path.join('test', 'test_data', 'dj-specs-test.xlsx') + specs_path = os.path.join('test', 'test_data', 'config.csv') + scenario_path = os.path.join('test', 'test_data', 'dj-specs-test.xlsx') csv_path = os.path.join('test', 'test_data', 'dj-test.csv') calibrated_csv_path = os.path.join(tmpdir, 'dj-calibrated.csv') specs_path_calib = os.path.join(tmpdir, 'dj-specs-test-calib.xlsx') calibration(specs_path, csv_path, specs_path_calib, calibrated_csv_path) - scenario(specs_path_calib, calibrated_csv_path, tmpdir, tmpdir) + scenario(specs_path, scenario_path, calibrated_csv_path, tmpdir, tmpdir) actual = os.path.join(tmpdir, 'dj-1-1_1_1_1_0_0_summary.csv') expected = os.path.join('test', 'test_results', 'expected_summary.csv')