Spaces:
Runtime error
Runtime error
| # Utils to create simulation - from params to config, fron config to simu, from simu to config, ... | |
| import os | |
| import random | |
| import string | |
| import urllib | |
| from datetime import date, datetime, timedelta | |
| from typing import Literal | |
| from awswrangler import s3 | |
| from WattFieldsCommon.constants import PATH_S3_FOLDER | |
| def create_simu_config(simulation=None, **kwargs): | |
| """Creates a simulation configuration fron either the simu itself or the | |
| kwarg parameters. | |
| Args: | |
| simulation: A simulation object containing the data. | |
| **kwargs: Individual parameters for the configuration if the simulation object is not provided. | |
| Returns: | |
| A dictionary representing the simulation configuration. | |
| """ | |
| config = {} | |
| # WeatherData | |
| config["WeatherData"] = { | |
| "lat": simulation.weather_data.lat if simulation else kwargs.get("lat"), | |
| "lon": simulation.weather_data.lon if simulation else kwargs.get("lon"), | |
| "alt": simulation.weather_data.alt if simulation else kwargs.get("alt"), | |
| "planting_date": ( | |
| simulation.weather_data.planting_date if simulation else kwargs.get("planting_date") | |
| ), | |
| "use_tmy": (simulation.weather_data.use_tmy if simulation else kwargs.get("use_tmy")), | |
| "max_simulation_duration": ( | |
| simulation.weather_data.max_simulation_duration | |
| if simulation | |
| else kwargs.get("max_simulation_duration") | |
| ), | |
| } | |
| # SolarFarmBifacial | |
| config["SolarFarmBifacial"] = { | |
| "rotation_axis_height": ( | |
| simulation.solar_farm.rotation_axis_height | |
| if simulation | |
| else kwargs.get("rotation_axis_height") | |
| ), | |
| "panel_layout": ( | |
| simulation.solar_farm.panel_layout if simulation else kwargs.get("panel_layout") | |
| ), | |
| "max_angle": (simulation.solar_farm.max_angle if simulation else kwargs.get("max_angle")), | |
| "distance_between_panels": ( | |
| simulation.solar_farm.distance_between_panels | |
| if simulation | |
| else kwargs.get("distance_between_panels") | |
| ), | |
| "axis_azimuth": ( | |
| simulation.solar_farm.axis_azimuth if simulation else kwargs.get("axis_azimuth") | |
| ), | |
| "pv_module_config_name": ( | |
| simulation.solar_farm.pv_module_config_name | |
| if simulation | |
| else kwargs.get("pv_module_config_name") | |
| ), | |
| "pv_inverter_config_name": ( | |
| simulation.solar_farm.pv_inverter_config_name | |
| if simulation | |
| else kwargs.get("pv_inverter_config_name") | |
| ), | |
| "post_inverter_elec_efficiency": ( | |
| simulation.solar_farm.post_inverter_elec_efficiency | |
| if simulation | |
| else kwargs.get("post_inverter_elec_efficiency") | |
| ), | |
| "is_bifacial": ( | |
| simulation.solar_farm.is_bifacial if simulation else kwargs.get("is_bifacial") | |
| ), | |
| # "panel_peak_power": simulation.solar_farm.panel_peak_power if simulation else kwargs.get("panel_peak_power"), | |
| # "panel_surface": simulation.solar_farm.panel_surface if simulation else kwargs.get("panel_surface") | |
| } | |
| # AgridRunner | |
| config["AgridRunner"] = { | |
| "soil_type": ( | |
| simulation.agrid_runner.soil_type if simulation else kwargs.get("soil_type") | |
| ), | |
| "soil_depth": ( | |
| simulation.agrid_runner.soil_depth if simulation else kwargs.get("soil_depth") | |
| ), | |
| "soil_RU": ( | |
| simulation.agrid_runner.soil_RU if simulation else kwargs.get("soil_RU") | |
| ), | |
| "soil_percent_available_water_start_simu": ( | |
| simulation.agrid_runner.soil_percent_available_water_start_simu if simulation else kwargs.get("soil_percent_available_water_start_simu") | |
| ), | |
| "crop_specie": ( | |
| simulation.agrid_runner.crop_specie if simulation else kwargs.get("crop_specie") | |
| ), | |
| "mean_crop_height": ( | |
| simulation.solar_farm.mean_crop_height if simulation else kwargs.get("mean_crop_height") | |
| ), | |
| "crop_cultivar": ( | |
| simulation.agrid_runner.crop_cultivar if simulation else kwargs.get("crop_cultivar") | |
| ), | |
| "nb_rows_per_dssat_simu": ( | |
| simulation.agrid_runner.nb_rows_per_dssat_simu | |
| if simulation | |
| else kwargs.get("nb_rows_per_dssat_simu") | |
| ), | |
| "agri_row_spacing": ( | |
| simulation.agrid_runner.agri_row_spacing | |
| if simulation | |
| else kwargs.get("agri_row_spacing") | |
| ), | |
| "agri_row_spacing_intra_row": ( | |
| simulation.agrid_runner.agri_row_spacing_intra_row | |
| if simulation | |
| else kwargs.get("agri_row_spacing_intra_row") | |
| ), | |
| "symetric_hypothesis": ( | |
| simulation.agrid_runner.symetric_hypothesis | |
| if simulation | |
| else kwargs.get("symetric_hypothesis") | |
| ), | |
| "distance_between_panels": ( | |
| simulation.agrid_runner.distance_between_panels | |
| if simulation | |
| else kwargs.get("distance_between_panels") | |
| ), | |
| "upper_bound_mid_band": ( | |
| simulation.agrid_runner.upper_bound_mid_band | |
| if simulation | |
| else kwargs.get("upper_bound_mid_band") | |
| ), | |
| "location": ( | |
| simulation.agrid_runner.location | |
| if simulation | |
| else (kwargs.get("lat"), kwargs.get("lon"), kwargs.get("alt")) | |
| ), | |
| } | |
| # Simulation | |
| config["Simulation"] = { | |
| "simu_id": simulation.id if simulation else kwargs.get("simu_id"), | |
| "simu_creation_date": ( | |
| simulation.simu_creation_date if simulation else kwargs.get("simu_creation_date") | |
| ), | |
| "nb_max_generations_in_optim": ( | |
| simulation.nb_max_generations_in_optim | |
| if simulation | |
| else kwargs.get("nb_max_generations_in_optim") | |
| ), | |
| "max_agri_loss": (simulation.max_agri_loss if simulation else kwargs.get("max_agri_loss")), | |
| "info": simulation.info if simulation else kwargs.get("info"), | |
| "nb_days_per_block": ( | |
| simulation.nb_days_per_block | |
| if simulation | |
| else kwargs.get("nb_days_per_block") | |
| ), | |
| } | |
| config["General"] = { | |
| "site": simulation.site if simulation else kwargs.get("site"), | |
| "company_id": simulation.company_id if simulation else kwargs.get("company_id"), | |
| "user_name": simulation.user_name if simulation else kwargs.get("user_name"), | |
| } | |
| return config | |
| def check_if_valid_dssat_params(crop_specie: str, crop_cultivar: str): | |
| # TODO | |
| return True, None | |
| """# Random weather data DATES = pd.date_range('2008-01-01', '2010-12-31') | |
| N = len(DATES) df = pd.DataFrame( { 'tn': np.random.gamma(10, 1, N), 'rad': | |
| np.random.gamma(10, 1.5, N), | |
| 'prec': [0.0]* N, | |
| 'rh': 100 * np.random.beta(1.5, 1.15, N), | |
| }, | |
| index=DATES, | |
| ) | |
| df['TMAX'] = df.tn + np.random.gamma(5., .5, N) | |
| wth = Weather( | |
| df, | |
| {'tn': 'TMIN', 'TMAX': 'TMAX', 'prec': 'RAIN', | |
| 'rad': 'SRAD', 'rh': 'RHUM'}, | |
| 4.3434237,-74.3606715, 1800 | |
| ) | |
| return True, None | |
| """ | |
| def check_if_valid_simu_params( | |
| rotation_axis_height: float, | |
| pitch: float, | |
| max_angle: float, | |
| mean_crop_height: float, | |
| max_simulation_duration: float, | |
| planting_date: datetime, | |
| ) -> (bool, str): | |
| """Check parameters validity before creating simu config. | |
| Args: | |
| rotation_axis_height (float): height of the rotation axis (from ground) | |
| pitch (float): distance between panels | |
| max_angle (float): max angle of rotation (comapred to horizontal, 30 means total deflection of 60 degrees) | |
| mean_crop_height (float): mean height of crop : will be used to caclulate the irradience | |
| max_simulation_duration (float): in days | |
| planting_date (datetime): planting date | |
| Returns: | |
| bool: True if valid params, False otherwise | |
| str: error message if invalid | |
| """ | |
| # TODO : add additional parameter checks | |
| if rotation_axis_height < 0 or pitch < 0 or max_angle < 0 or mean_crop_height < 0: | |
| return ( | |
| False, | |
| "Tous les champs hauteur, largeur, distance entre panneaux, angle doivent etre superieurs a 0.", | |
| ) | |
| if max_simulation_duration < 30: | |
| return False, "La duree de la simulation doit au moins etre de 30 jours" | |
| elif mean_crop_height >= rotation_axis_height: | |
| return ( | |
| False, | |
| "La hauteur des cultures ne peut pas etre superieure a la hauteur du panneau", | |
| ) | |
| elif planting_date + timedelta(days=max_simulation_duration) > date(2024, 11, 1): | |
| return False, "La date de fin de simulation depasse le 01/11/2024" | |
| else: | |
| return True, None | |
| def get_all_simulations_metadata_from_s3( | |
| company_id: str, site_id: str, status: Literal["ALL", "COMPLETED", "PENDING", 'FAILED'] | |
| ): | |
| """Gathers all the simulations stored in s3 (both pending and completed) | |
| for a given company and site id Pretty nice as the name itself or the simu | |
| contains all the metadata ! Means that single s3 query gives you all the | |
| information instead of having to open some files... | |
| Args: | |
| company_id (str): company_id | |
| site_id (str): site_id | |
| status (str): status | |
| Returns: | |
| list: list of dict containing all the metadata associated to simulations | |
| """ | |
| assert status in ["ALL", "COMPLETED", "PENDING", 'FAILED'] | |
| list_simu_files = [] | |
| if status == "COMPLETED" or status == "ALL" or status == "FAILED": | |
| s3_dir = os.path.join(PATH_S3_FOLDER, "simulations", f"{company_id}", f"{site_id}") | |
| try: | |
| list_objects = s3.list_objects(s3_dir) | |
| if status == 'COMPLETED': | |
| list_objects = [obj for obj in list_objects if 'COMPLETED' in obj] | |
| if status == 'FAILED': | |
| list_objects = [obj for obj in list_objects if 'FAILED' in obj] | |
| list_simu_files.extend(list_objects) | |
| except Exception as e: | |
| print(e) | |
| if status == "PENDING" or status == "ALL": | |
| s3_dir = os.path.join(PATH_S3_FOLDER, "simulations", "pending_simu_configs") | |
| try: | |
| list_simu_files.extend(s3.list_objects(s3_dir)) | |
| except Exception as e: | |
| print(e) | |
| list_simus_metadata = [file_path_to_simu_metadata(file) for file in list_simu_files] | |
| list_simus_metadata = [ | |
| simu_meta | |
| for simu_meta in list_simus_metadata | |
| if ((simu_meta["site_id"] == site_id) and (simu_meta["company_id"] == company_id)) | |
| ] | |
| return list_simus_metadata | |
| def decode_string(s): | |
| """Decode a previously encoded string.""" | |
| return urllib.parse.unquote(s) | |
| def encode_string(s): | |
| """Encode a string to make it safe for file paths.""" | |
| return urllib.parse.quote(s, safe="") | |
| def simu_to_file_name( | |
| company_id: str, | |
| site_id: str, | |
| simu_status: str, | |
| simu_id: str, | |
| user_name: str, | |
| simu_creation_date: str, | |
| simu_info: str, | |
| ) -> str: | |
| """Function to create the filename that will contained all the metadata | |
| information. | |
| Args: | |
| company_id (str): straightfoward | |
| site_id (str): straightfoward | |
| simu_status (str): status of the simulation (pending if in queue) | |
| simu_id (str): straightfoward | |
| user_name (str): straightfoward | |
| simu_creation_date (str): straightfoward | |
| simu_info (str): additional information about the simulation | |
| Returns: | |
| str: filename for both the input config and the output pkl file | |
| Extension could help discriminating these two | |
| """ | |
| assert simu_status in ["PENDING", "FAILED", "COMPLETED"] | |
| if not isinstance(simu_creation_date, str): | |
| simu_creation_date = simu_creation_date = simu_creation_date.isoformat().replace("/", "-") | |
| encoded_site_id = encode_string(site_id) | |
| encoded_user_name = encode_string(user_name) | |
| encoded_info = encode_string(simu_info) | |
| return f"{company_id}_{encoded_site_id}_{simu_status}_{simu_id}_{encoded_user_name}_{simu_creation_date.replace('/','-')}_{encoded_info}" | |
| def file_path_to_simu_metadata(file_path) -> dict: | |
| """Function to get metadata from filename. | |
| Args: | |
| file_path (_type_): ok | |
| Returns: | |
| dict: metadat dict | |
| """ | |
| # file_path can be either a json file (config file) or a pickle file(output file) | |
| file_name = file_path.split("/")[-1] | |
| file_name = file_name.split(".")[0] | |
| parts = file_name.split("_") | |
| if len(parts) != 7: | |
| raise ValueError("Invalid file path format") | |
| return { | |
| "path": file_path, | |
| "company_id": parts[0], | |
| "site_id": parts[1], | |
| "simu_id": parts[3], | |
| "simu_status": parts[2], | |
| "user_name": decode_string(parts[4]), | |
| "simu_creation_date": parts[5], | |
| "file_name": file_name, | |
| "info": decode_string(parts[6]), | |
| } | |
| def generate_random_simu_id(length=8): | |
| """Generate a random alphanumeric string of specified length. | |
| Args: | |
| length (int, optional): Length of the random string. Defaults to 8. | |
| """ | |
| # Combine uppercase letters, lowercase letters, and digits | |
| characters = string.ascii_letters + string.digits | |
| # Use random.choices to generate a list of random characters of the specified length | |
| random_id = "".join(random.choices(characters, k=length)) | |
| return random_id | |