diff --git a/RELEASE.md b/RELEASE.md index a21404b49..15ecdbd05 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,5 +1,9 @@ # Release Notes ## Unreleased +* Added excel file with coordinates for 50 locations in west Africa. This excel file is used in example 12, but can also be broadly useful in testing other HOPP scripts. + - `examples/inputs/west_africa_coordinates.xlsx` +* Added an example that allows HOPP simulations to be run sequentially for multiple locations anywhere in the world. + - `examples/12-worldwide-multi-location-example.ipynb` * Add `overwrite_fin_values` from H2Integrate to sync cost input methods * Bump minimum NREL-PySAM version to 7.0.0 * Clarify that the `nominal_discount_rate` method of the `CustomFinancialModel` uses the Fisher equation diff --git a/examples/12-worldwide-multi-location-example.ipynb b/examples/12-worldwide-multi-location-example.ipynb new file mode 100644 index 000000000..19887dd94 --- /dev/null +++ b/examples/12-worldwide-multi-location-example.ipynb @@ -0,0 +1,622 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c5b2637c", + "metadata": {}, + "source": [ + "## Worldwide multi-location HOPP example\n", + "---\n", + "This example shows how to simulate a hybrid renewable energy plant using HOPP for any set of (onshore) locations in the world.\n", + "\n", + "We download solar resource data from NSRDB (through NREL API) and wind resource data from the Open-Meteo database. \n", + "\n", + "Unlike the NREL Wind Toolkit database, the Open-Meteo database provides worldwide coverage, though the resulting data requires some modification to be usable in HOPP." + ] + }, + { + "cell_type": "markdown", + "id": "ea3793e9", + "metadata": {}, + "source": [ + "### Import required modules\n", + "We start by importing the necessary HOPP modules and other packages that will be needed later." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51129d62", + "metadata": {}, + "outputs": [], + "source": [ + "# Import HOPP modules\n", + "from hopp.simulation.hopp_interface import HoppInterface\n", + "from hopp.tools.dispatch.plot_tools import plot_battery_output, plot_generation_profile\n", + "# Import other packages\n", + "import pandas as pd\n", + "import numpy as np\n", + "import os\n", + "import datetime\n", + "import time" + ] + }, + { + "cell_type": "markdown", + "id": "f9190a9a", + "metadata": {}, + "source": [ + "### Set NREL API key\n", + "To access the NSRDB (solar resource) data, we need to set an NREL API key. You can obtain an API key from the [NREL developer website](https://developer.nrel.gov/signup/).\n", + "\n", + "**IMPORTANT!**\n", + "To set up the NREL_API_KEY required for resource downloads, you can create an Environment Variable called NREL_API_KEY. Otherwise, you can keep the key in a new file called \".env\" in the root directory of this project. The \".env\" file must contain the line: NREL_API_KEY=key" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "635bfe32", + "metadata": {}, + "outputs": [], + "source": [ + "from hopp.utilities.keys import set_nrel_key_dot_env\n", + "from hopp.utilities.keys import get_developer_nrel_gov_key, get_developer_nrel_gov_email\n", + "\n", + "# Set the API key\n", + "set_nrel_key_dot_env()\n", + "\n", + "# Get API key and associated email address\n", + "email_address = get_developer_nrel_gov_email()\n", + "api_key = get_developer_nrel_gov_key()" + ] + }, + { + "cell_type": "markdown", + "id": "e014d32a", + "metadata": {}, + "source": [ + "### Define the supporting functions" + ] + }, + { + "cell_type": "markdown", + "id": "48182af5", + "metadata": {}, + "source": [ + "(1) A function that uses the NREL API to request NSRDB (solar resource) data for a set of user-specified coordinates and saves it as a CSV file in a user-specified directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0c855f5f", + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "\n", + "# Function to get solar data via NREL API for one location\n", + "# lat = latitude, lon = longitude, name = location name (e.g. city, village)\n", + "def nrel_query(lat, lon, name, year, solar_output_dir):\n", + " \n", + " # URL for the NREL Meteosat Prime Meridian dataset API\n", + " NSRDB_URL = f\"https://developer.nrel.gov/api/nsrdb/v2/solar/nsrdb-msg-v1-0-0-tmy-download.csv?api_key={api_key}&wkt=POINT({lat} {lon})&names={year}&utc=false&leap_day=false&interval=60&email={email_address}\"\n", + "\n", + " # Make sure 'year' variable is valid\n", + " if year not in ['tmy','tmy-2014','tmy-2022']:\n", + " raise ValueError(f\"{year} is not valid TMY year for solar resource API. Please set year as either 'tmy','tmy-2014', or 'tmy-2022'\")\n", + "\n", + " # Send request to the NREL API and save response\n", + " print(f\"\\nSending request to NREL API for location with lat {lat} and lon {lon}...\")\n", + " response = requests.get(NSRDB_URL)\n", + "\n", + " # Raise an HTTPError for bad responses (4XX or 5XX client/server errors)\n", + " response.raise_for_status()\n", + " print(f\"Received HTTP {response.status_code} response from API.\")\n", + "\n", + " response_text = response.text\n", + "\n", + " # Check for API-specific errors within the CSV content even if status is 200\n", + " first_few_lines = response_text.splitlines()\n", + " is_api_error = False\n", + " if first_few_lines:\n", + " # Check if the first line (potential header of an error CSV) contains \"error message\"\n", + " if \"error message\" in first_few_lines[0].lower():\n", + " is_api_error = True\n", + "\n", + " if is_api_error:\n", + " print(f\"API Error: Received an error message from NREL API within the data response:\")\n", + " print(\"-------------------- API ERROR RESPONSE (first 5 lines) --------------------\")\n", + " print(\"\\n\".join(first_few_lines[:5])) # Print first 5 lines of the error\n", + " print(\"--------------------------------------------------------------------------\")\n", + "\n", + " # Set up output directory\n", + " os.makedirs(solar_output_dir, exist_ok=True)\n", + "\n", + " # Define output filename\n", + " filename = f\"{lat}_{lon}_{name}_NSRDB.csv\"\n", + " filepath = os.path.join(solar_output_dir, filename)\n", + "\n", + " # Save the data\n", + " print(f\"INFO: Saving data to {filepath}...\")\n", + " with open(filepath, 'w', newline='', encoding='utf-8') as f:\n", + " f.write(response_text)\n", + "\n", + " print(f\"SUCCESS! Downloaded and saved solar data to: {filepath}\")\n", + "\n", + " # Delay for 1 second to prevent API rate limiting\n", + " time.sleep(1)\n", + "\n", + " return filepath" + ] + }, + { + "cell_type": "markdown", + "id": "afb96a08", + "metadata": {}, + "source": [ + "(2) A function that queries the Open-Meteo database for wind data for a set of user-specified coordinates and saves it as a CSV file in a user-specified directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c1f116a", + "metadata": {}, + "outputs": [], + "source": [ + "import openmeteo_requests\n", + "import requests_cache\n", + "from retry_requests import retry\n", + "\n", + "# Function to get wind data via OpenMeteo API for one location\n", + "# lat = latitude, lon = longitude, name = location name (e.g. city, village)\n", + "def openmeteo_query(lat, lon, name, year, wind_output_dir):\n", + " \n", + " # Set up the OpenMeteo API client with cache and retry on error\n", + " cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)\n", + " retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)\n", + " openmeteo = openmeteo_requests.Client(session = retry_session)\n", + "\n", + " # Make sure all required weather variables are listed here (modify as needed)\n", + " # The order of variables in hourly or daily is important to assign them correctly below\n", + " url = \"https://historical-forecast-api.open-meteo.com/v1/forecast\"\n", + " date1 = year + \"-01-01\"\n", + " date2 = year + \"-12-31\"\n", + " params = {\n", + " \"latitude\": lat, \n", + " \"longitude\": lon,\n", + " \"start_date\": date1,\n", + " \"end_date\": date2,\n", + " \"hourly\": [\"temperature_80m\", \"wind_speed_80m\", \"wind_direction_80m\", \"temperature_120m\", \"wind_speed_120m\", \"wind_direction_120m\"],\n", + " \"timezone\": \"auto\",\n", + " \"wind_speed_unit\": \"ms\"\n", + " }\n", + " # Open-Meteo API query\n", + " print(f\"\\nSending request to Open-Meteo API for location with lat {lat} and lon {lon}...\")\n", + " responses = openmeteo.weather_api(url, params=params)\n", + "\n", + " # Output directory setup\n", + " os.makedirs(wind_output_dir, exist_ok=True)\n", + "\n", + " # Print data from API query\n", + " response = responses[0]\n", + " print(f\"Coordinates {response.Latitude()}°N {response.Longitude()}°E\")\n", + " print(f\"Elevation {response.Elevation()} m asl\")\n", + " print(f\"Timezone {response.Timezone()}{response.TimezoneAbbreviation()}\")\n", + " print(f\"Timezone difference to GMT+0 {response.UtcOffsetSeconds()} s\")\n", + "\n", + " # Process hourly data. The order of variables needs to be the same as requested.\n", + " hourly = response.Hourly()\n", + " hourly_temperature_80m = hourly.Variables(0).ValuesAsNumpy()\n", + " hourly_wind_speed_80m = hourly.Variables(1).ValuesAsNumpy()\n", + " hourly_wind_direction_80m = hourly.Variables(2).ValuesAsNumpy()\n", + " hourly_wind_speed_120m = hourly.Variables(3).ValuesAsNumpy()\n", + " hourly_wind_direction_120m = hourly.Variables(4).ValuesAsNumpy()\n", + " hourly_temperature_120m = hourly.Variables(5).ValuesAsNumpy()\n", + "\n", + " hourly_data = {\"date\": pd.date_range(\n", + " start = pd.to_datetime(hourly.Time(), unit = \"s\", utc = True),\n", + " end = pd.to_datetime(hourly.TimeEnd(), unit = \"s\", utc = True),\n", + " freq = pd.Timedelta(seconds = hourly.Interval()),\n", + " inclusive = \"left\"\n", + " )}\n", + "\n", + " hourly_data[\"temperature_80m\"] = hourly_temperature_80m\n", + " hourly_data[\"wind_speed_80m\"] = hourly_wind_speed_80m\n", + " hourly_data[\"wind_direction_80m\"] = hourly_wind_direction_80m\n", + " hourly_data[\"wind_speed_120m\"] = hourly_wind_speed_120m\n", + " hourly_data[\"wind_direction_120m\"] = hourly_wind_direction_120m\n", + " hourly_data[\"temperature_120m\"] = hourly_temperature_120m\n", + "\n", + " hourly_dataframe = pd.DataFrame(data = hourly_data)\n", + " print(hourly_dataframe.head())\n", + " printable_data = hourly_dataframe.to_string()\n", + "\n", + " # Define output filename\n", + " filename = f\"{response.Latitude()}_{response.Longitude()}_{response.Elevation()}_{name}_openmeteo_wind.csv\"\n", + " filepath = os.path.join(wind_output_dir, filename)\n", + "\n", + " # Save the data\n", + " print(f\"INFO: Saving data to {filepath}...\")\n", + " with open(filepath, 'w', newline='', encoding='utf-8') as f:\n", + " f.write(printable_data)\n", + "\n", + " print(f\"SUCCESS! Downloaded and saved wind data to: {filepath}\")\n", + "\n", + " # Delay for 1 second to prevent API rate limiting\n", + " time.sleep(1)\n", + "\n", + " return filepath" + ] + }, + { + "cell_type": "markdown", + "id": "54304ea9", + "metadata": {}, + "source": [ + "(3) A function that converts the wind data obtained through Open-Meteo to a HOPP-friendly format." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "8623e244", + "metadata": {}, + "outputs": [], + "source": [ + "# Function converts a OpenMeteo-formatted CSV file to a WTK-formatted SRW file.\n", + "def convert_to_srw(input_filepath, output_dir):\n", + " \n", + " try:\n", + " # --- 1. Data Import and Initial Manipulation ---\n", + " print(\"\\nConverting OpenMeteo-formatted CSV file to WTK-formatted SRW file...\")\n", + " print(f\"Processing file: {input_filepath}\")\n", + " df = pd.read_csv(input_filepath, sep='\\s+')\n", + "\n", + " # --- 2. Column Modification ---\n", + " # Reordering the columns (if needed).\n", + " df = df[[\n", + " 'date', 'temperature_80m', 'wind_speed_80m','wind_direction_80m', \n", + " 'wind_speed_120m', 'wind_direction_120m', 'temperature_120m'\n", + " ]]\n", + " # Delete the 'date' column.\n", + " df = df.drop(columns=['date'])\n", + " # Insert a column of zeros at position 2\n", + " df.insert(1, 'pressure_80m', 1.0)\n", + " # Insert another column of zeros at position 6\n", + " df.insert(5, 'pressure_120m', 1.0)\n", + " # Rounding to 3 decimals\n", + " df = df.round(4)\n", + " # Convert to numpy array\n", + " data = df.to_numpy()\n", + "\n", + " # --- 3. File Output ---\n", + " # Create the full path for the new .srw file.\n", + " base_filename = os.path.basename(input_filepath)\n", + " filename_without_ext = os.path.splitext(base_filename)[0]\n", + " split_filename = filename_without_ext.split(\"_\")\n", + " latitude = round(float(split_filename[0]), 4)\n", + " longitude = round(float(split_filename[1]), 4)\n", + " altitude = round(float(split_filename[2]), 1)\n", + " name = str(split_filename[3])\n", + " output_filepath = os.path.join(output_dir, f\"{latitude}_{longitude}_{altitude}_{name}_openmeteo_wind_converted.srw\")\n", + "\n", + " # Define the 5-line header similar to the wtk_file format.\n", + " header_lines = [\n", + " f\"123456,{name},state??,Country,2022,{latitude},{longitude},Not Available,1,8760\\n\",\n", + " \"WIND Toolkit data converted from OpenMeteo data\\n\",\n", + " \"Temperature,Pressure,Speed,Direction,Temperature,Pressure,Speed,Direction\\n\",\n", + " \"C,atm,m/s,Degrees,C,atm,m/s,Degrees\\n\",\n", + " \"80,80,80,80,120,120,120,120\\n\"\n", + " ]\n", + "\n", + " # Write the header and the modified data to the new file.\n", + " with open(output_filepath, 'w') as fi:\n", + " # Write the custom 5-line header\n", + " fi.writelines(header_lines)\n", + " # Append the DataFrame to the file as comma-delimited, without its own header.\n", + " #df.to_csv(fi, index=False, header=False, sep=',')\n", + " np.savetxt(fi, data, fmt='%.4f', delimiter=',')\n", + "\n", + " print(f\"Successfully converted to: {output_filepath}\\n\")\n", + "\n", + " except FileNotFoundError:\n", + " print(f\"Error: Input file not found at {input_filepath}\\n\")\n", + "\n", + " except Exception as e:\n", + " print(f\"An error occurred while processing {input_filepath}: {e}\\n\")\n", + "\n", + " return output_filepath" + ] + }, + { + "cell_type": "markdown", + "id": "6ec96246", + "metadata": {}, + "source": [ + "(4) A function that modifies the HOPP configuration YAML file to enable sequential HOPP simulations for multiple locations." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d9c64000", + "metadata": {}, + "outputs": [], + "source": [ + "import ruamel.yaml\n", + "\n", + "def yaml_modifier(file_path, new_lat, new_lon, solar_file, wind_file):\n", + "\n", + " # YAML parser\n", + " yaml = ruamel.yaml.YAML()\n", + " yaml.preserve_quotes = True \n", + "\n", + " # Load the YAML file\n", + " with open(file_path, 'r') as file:\n", + " data = yaml.load(file)\n", + "\n", + " # Locate and modify the data\n", + " data['site']['solar_resource_file'] = solar_file\n", + " data['site']['wind_resource_file'] = wind_file\n", + " data['site']['data']['lat'] = new_lat\n", + " data['site']['data']['lon'] = new_lon\n", + "\n", + " # Write the changes back to the file\n", + " with open(file_path, 'w') as file:\n", + " yaml.dump(data, file)\n", + "\n", + " print(f\"Successfully updated '{file_path}' with data for ({new_lat},{new_lon})\")" + ] + }, + { + "cell_type": "markdown", + "id": "be3ac9cc", + "metadata": {}, + "source": [ + "(5) A function that runs the HOPP simulation for all the locations provided in an excel file. \n", + "\n", + "To run for one location, set **RANGE = [location_index]** where location_index = 0, 1, 2, ...\n", + "\n", + "The HOPP results are saved in a separate txt file for each location. A single txt file with key results from all locations is also saved at the end. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "032861c5", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def hopp_simulation(solar_year, wind_year, coord_file, yaml_file, solar_dir, wind_dir, hopp_dir):\n", + " '''\n", + " coord_file: Excel file containing locations and coordinates\n", + " yaml_file: Yaml file containing HOPP configuration\n", + " solar_dir: Directory containing solar data files\n", + " wind_dir: Directory containing wind data files\n", + " hopp_dir: Directory for HOPP output\n", + " '''\n", + " # Get today's date\n", + " time_now = datetime.date.today()\n", + "\n", + " # Import locations and coordinates\n", + " df = pd.read_csv(coord_file)\n", + " lat = df['latitude'].to_numpy()\n", + " lon = df['longitude'].to_numpy()\n", + " name = df['location'].to_numpy()\n", + "\n", + " # Identify locations to simulate\n", + " # For first location: RANGE = [0], for third location: RANGE = [2], or for all locations: RANGE = range(len(name))\n", + " RANGE = range(len(name))\n", + "\n", + " # Initialize arrays for saving key results\n", + " lcoe_data = np.zeros(len(RANGE))\n", + " energy_data = np.zeros(len(RANGE))\n", + " cf_data = np.zeros(len(RANGE))\n", + " k = 0 #index for lcoe array\n", + "\n", + " # Iterate for all locations specified in RANGE\n", + " for idx in RANGE:\n", + " latitude = np.round(lat[idx], 2)\n", + " longitude = np.round(lon[idx], 2)\n", + " LAT = latitude.item() #convert np.array to float\n", + " LON = longitude.item()\n", + " LOCATION = name[idx]\n", + " S_YEAR = solar_year \n", + " W_YEAR = wind_year\n", + "\n", + " # Get solar data from NREL API\n", + " SOLAR_FILE = nrel_query(LAT, LON, LOCATION, S_YEAR, solar_dir)\n", + "\n", + " # Get wind data from OpenMeteo API\n", + " WIND_FILE = openmeteo_query(LAT, LON, LOCATION, W_YEAR, wind_dir)\n", + "\n", + " # Convert wind data to SRW format\n", + " CONVERTED_WIND_FILE = convert_to_srw(WIND_FILE, wind_dir)\n", + "\n", + " # Modify YAML file for desired location\n", + " yaml_modifier(yaml_file, LAT, LON, SOLAR_FILE, CONVERTED_WIND_FILE)\n", + "\n", + " #HOPP simulation\n", + " hi = HoppInterface(yaml_file)\n", + " print(\"\\nHOPP simulation in progress...\")\n", + " hi.simulate(project_life=20) #specify the project lifetime in years\n", + "\n", + " hybrid_plant = hi.system\n", + " print(\"\\nPrinting and saving HOPP output...\")\n", + "\n", + " #HOPP Output\n", + " if not os.path.exists(hopp_dir):\n", + " os.makedirs(hopp_dir)\n", + " output_file = os.path.join(hopp_dir, f\"hopp_output_{LAT}_{LON}_{LOCATION}_{time_now}.txt\")\n", + "\n", + " Wind_eff = hybrid_plant.wind.value(\"annual_energy\") / hybrid_plant.wind.value(\"annual_gross_energy\")\n", + " energy = hybrid_plant.annual_energies\n", + " cf = hybrid_plant.capacity_factors\n", + " lcoe_n = hybrid_plant.lcoe_nom\n", + " lcoe_r = hybrid_plant.lcoe_real\n", + "\n", + " # Print to file\n", + " with open(output_file, \"w\") as log:\n", + " print(f\"Wind output efficiency:\\n {Wind_eff} \\nAnnual Energies (kWh):\\n {energy} \\nCapacity factors (%):\\n {cf} \\nNominal LCOE (c/kWh):\\n {lcoe_n} \\nReal LCOE (c/kWh):\\n {lcoe_r}\"\n", + " , file=log)\n", + " print(f\"HOPP output saved in '{output_file}'\\n\")\n", + "\n", + " # Save key results for this location\n", + " lcoe_data[k] = lcoe_r[\"hybrid\"]\n", + " energy_data[k] = energy[\"hybrid\"]\n", + " cf_data[k] = cf[\"hybrid\"]\n", + " k += 1\n", + " \n", + " # Print to terminal\n", + " print(\"Wind output efficieny:\", Wind_eff)\n", + " print(\"\\nAnnual Energies (kWh):\")\n", + " print(energy)\n", + " print(\"\\nCapacity factors (%):\")\n", + " print(cf)\n", + " print(\"\\nNominal LCOE (cents/kWh):\")\n", + " print(lcoe_n)\n", + " print(\"\\nInflation-adjusted LCOE (cents/kWh):\")\n", + " print(lcoe_r)\n", + "\n", + " # Plot generation and battery dispatch profiles (We advise to comment these out if running multiple locations)\n", + " #plot_generation_profile(hybrid_plant)\n", + " #plot_battery_output(hybrid_plant)\n", + "\n", + " # Save key results\n", + " data_stack = np.column_stack([name[RANGE], energy_data, cf_data, lcoe_data])\n", + " lcoe_file = os.path.join(hopp_dir, f\"LCOE_output_{time_now}.txt\")\n", + " with open(lcoe_file, \"w\") as log:\n", + " print(f\"Location, Annual generation (kWh), Capacity factor (%), Real LCOE (cents/kWh)\\n {data_stack}\", file=log)\n", + " print(f\"\\nKey HOPP results saved in '{lcoe_file}'\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "3511583e", + "metadata": {}, + "source": [ + "### Run the main code\n", + "Specify the excel file containing the locations' coordinates and names, the HOPP configuration YAML file, the directories containig solar and wind resource data files, and the output directory. To assist the user, an excel file is provided with coordinates for locations in western Africa.\n", + "\n", + "Then, run the code below to simulate a hybrid renewable energy plant in all desired locations. \n", + "\n", + "Check the results in the output directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b5ddfea", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Sending request to NREL API for location with lat 34.92 and lon -6.18...\n", + "Received HTTP 200 response from API.\n", + "INFO: Saving data to ./inputs/solar_data/34.92_-6.18_Larache_NSRDB.csv...\n", + "SUCCESS! Downloaded and saved solar data to: ./inputs/solar_data/34.92_-6.18_Larache_NSRDB.csv\n", + "\n", + "Sending request to Open-Meteo API for location with lat 34.92 and lon -6.18...\n", + "Coordinates 34.9375°N -6.1875°E\n", + "Elevation 85.0 m asl\n", + "Timezone b'Africa/Casablanca'b'GMT+1'\n", + "Timezone difference to GMT+0 3600 s\n", + " date temperature_80m wind_speed_80m \\\n", + "0 2021-12-31 23:00:00+00:00 18.973000 1.780449 \n", + "1 2022-01-01 00:00:00+00:00 18.622999 1.920937 \n", + "2 2022-01-01 01:00:00+00:00 18.172998 3.220248 \n", + "3 2022-01-01 02:00:00+00:00 17.823000 3.764306 \n", + "4 2022-01-01 03:00:00+00:00 17.372999 3.360060 \n", + "\n", + " wind_direction_80m wind_speed_120m wind_direction_120m temperature_120m \n", + "0 128.157272 19.172998 1.817277 128.157272 \n", + "1 141.340164 18.772999 2.041369 143.130020 \n", + "2 143.841721 18.473000 3.369805 144.865723 \n", + "3 140.389343 18.223000 4.064836 141.115494 \n", + "4 143.471054 17.823000 3.595649 145.407654 \n", + "INFO: Saving data to ./inputs/wind_data/34.9375_-6.1875_85.0_Larache_openmeteo_wind.csv...\n", + "SUCCESS! Downloaded and saved wind data to: ./inputs/wind_data/34.9375_-6.1875_85.0_Larache_openmeteo_wind.csv\n", + "\n", + "Converting OpenMeteo-formatted CSV file to WTK-formatted SRW file...\n", + "Processing file: ./inputs/wind_data/34.9375_-6.1875_85.0_Larache_openmeteo_wind.csv\n", + "Successfully converted to: ./inputs/wind_data/34.9375_-6.1875_85.0_Larache_openmeteo_wind_converted.srw\n", + "\n", + "Successfully updated './inputs/03-wind-solar-battery.yaml' with data for (34.92,-6.18)\n", + "\n", + "HOPP simulation in progress...\n", + "\n", + "Printing and saving HOPP output...\n", + "HOPP output saved in './hopp_output/hopp_output_34.92_-6.18_Larache_2025-09-11.txt'\n", + "\n", + "Wind output efficieny: 0.8273167449154486\n", + "\n", + "Annual Energies (kWh):\n", + "{\"pv\": 758505765.6473475, \"wind\": 173379028.88429186, \"battery\": 8503.958886887514, \"hybrid\": 931893298.490529}\n", + "\n", + "Capacity factors (%):\n", + "{\"pv\": 28.14091025518127, \"wind\": 10.995625880536014, \"battery\": 0, \"hybrid\": 13.505338631270558}\n", + "\n", + "Nominal LCOE (cents/kWh):\n", + "{\"pv\": 4.735263428696596, \"wind\": 13.977866407691577, \"battery\": 290685.3032543002, \"hybrid\": 9.107426314617603}\n", + "\n", + "Inflation-adjusted LCOE (cents/kWh):\n", + "{\"pv\": 3.8753528517482616, \"wind\": 11.4395250148341, \"battery\": 237897.66628420443, \"hybrid\": 7.45354320238008}\n", + "\n", + "Key HOPP results saved in './hopp_output/LCOE_output_2025-09-11.txt'\n", + "\n", + "SUCCESS! HOPP simulation completed for all locations and results saved.\n" + ] + } + ], + "source": [ + "# Weather data years\n", + "SOLAR_YEAR = 'tmy-2022' #either 'tmy','tmy-2014', or 'tmy-2022'\n", + "WIND_YEAR = '2022' #any year from '1940' onwards, but more recent years are strongly recommended\n", + "\n", + "# INPUT FILES (MODIFY FILE PATHS AS NEEDED)\n", + "# Excel file with at least three columns (latitude, longitude, and location)\n", + "COORD_FILE = './inputs/west_africa_coordinates.csv'\n", + "# YAML file for HOPP configuration\n", + "YAML_FILE = \"./inputs/03-wind-solar-battery.yaml\"\n", + "\n", + "# Directories for weather data and HOPP output (modify as needed)\n", + "SOLAR_DIR = './inputs/solar_data/'\n", + "WIND_DIR = './inputs/wind_data/'\n", + "HOPP_DIR = './hopp_output/'\n", + "\n", + "# Run HOPP simulation for all locations\n", + "hopp_simulation(SOLAR_YEAR, WIND_YEAR, COORD_FILE, YAML_FILE, SOLAR_DIR, WIND_DIR, HOPP_DIR)\n", + "\n", + "print(\"\\nSUCCESS! HOPP simulation completed for all locations and results saved.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "h2integrate", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/inputs/west_africa_coordinates.csv b/examples/inputs/west_africa_coordinates.csv new file mode 100644 index 000000000..49fd88e10 --- /dev/null +++ b/examples/inputs/west_africa_coordinates.csv @@ -0,0 +1,51 @@ +latitude,longitude,location +35.7775,-5.8000,Tangier +35.5767,-5.3667,Tétouan +35.4750,-6.0033,Asilah +34.9239,-6.1814,Larache +34.2575,-6.5800,Kénitra +34.0209,-6.8416,Rabat +34.0388,-6.7888,Salé +33.6833,-7.3833,Mohammedia +33.5731,-7.5898,Casablanca +33.2453,-8.4900,El Jadida +32.2939,-9.2333,Safi +31.5126,-9.7701,Essaouira +30.4209,-9.5981,Agadir +29.6800,-9.7700,Tiznit +28.8833,-11.0833,Sidi Ifni +28.4344,-11.1308,Tan-Tan +27.1533,-13.1922,Laayoune +23.6964,-15.9500,Dakhla +21.0167,-15.9500,Nouadhibou +18.0731,-15.9582,Nouakchott +16.5333,-15.8833,Rosso +16.0228,-16.4783,Saint-Louis +14.7167,-17.4677,Dakar +14.7833,-16.9333,Thiès +14.4000,-16.9833,M'Bour +14.1500,-16.0833,Kaolack +14.3333,-16.3833,Fatick +12.5667,-16.2667,Ziguinchor +13.4433,-16.5974,Banjul +11.8744,-15.5877,Bissau +12.2500,-14.5833,Bafatá +9.5092,-13.7122,Conakry +8.4833,-13.2333,Freetown +7.5167,-12.5000,Bonthe +6.3000,-10.8000,Monrovia +5.9167,-10.2333,Buchanan +5.3167,-3.9167,Grand-Bassam +5.3167,-4.0333,Abidjan +6.8167,-5.2833,Yamoussoukro +5.1500,-4.2000,Dabou +4.6667,-5.1167,Sassandra +4.7500,-6.7500,San-Pédro +4.9167,-4.9000,Grand-Lahou +5.5167,-3.0833,Aboisso +5.9833,1.1833,Aflao +6.1333,1.2167,Lomé +6.3667,2.4000,Cotonou +6.4833,2.6167,Porto-Novo +6.4167,2.8833,Badagry +6.5244,3.3792,Lagos