- move models to separate repository

This commit is contained in:
giuseppenuc committed 2024-12-10 23:29:54 +01:00
commit 56c1c3cc1f
69 files changed
+49903

No files matched your search

View File
Whitespace-only changes.
+504
View File
@@ -0,0 +1,504 @@
import psutil
import multiprocessing
import re
import pandas as pd
import numpy as np
from typing import List, Dict
import os
import joblib
def get_optimal_workers() -> int:
"""
Calcola il numero ottimale di workers basandosi sulle risorse del sistema.
Returns
-------
int
Numero ottimale di workers
"""
# Ottiene il numero di CPU logiche (inclusi i thread virtuali)
cpu_count = multiprocessing.cpu_count()
# Ottiene la memoria totale e disponibile in GB
memory = psutil.virtual_memory()
total_memory_gb = memory.total / (1024 ** 3)
available_memory_gb = memory.available / (1024 ** 3)
# Stima della memoria necessaria per worker (esempio: 2GB per worker)
memory_per_worker_gb = 2
# Calcola il numero massimo di workers basato sulla memoria disponibile
max_workers_by_memory = int(available_memory_gb / memory_per_worker_gb)
# Usa il minimo tra:
# - numero di CPU disponibili - 1 (lascia una CPU libera per il sistema)
# - numero massimo di workers basato sulla memoria
# - un limite massimo arbitrario (es. 32) per evitare troppo overhead
optimal_workers = min(
cpu_count - 1,
max_workers_by_memory,
32 # limite massimo arbitrario
)
# Assicura almeno 1 worker
return max(1, optimal_workers)
def clean_column_name(name: str) -> str:
"""
Rimuove caratteri speciali e spazi, converte in snake_case e abbrevia.
Parameters
----------
name : str
Nome della colonna da pulire
Returns
-------
str
Nome della colonna pulito
"""
# Rimuove caratteri speciali
name = re.sub(r'[^a-zA-Z0-9\s]', '', name)
# Converte in snake_case
name = name.lower().replace(' ', '_')
# Abbreviazioni comuni
abbreviations = {
'production': 'prod',
'percentage': 'pct',
'hectare': 'ha',
'tonnes': 't',
'litres': 'l',
'minimum': 'min',
'maximum': 'max',
'average': 'avg'
}
for full, abbr in abbreviations.items():
name = name.replace(full, abbr)
return name
def clean_column_names(df: pd.DataFrame) -> List[str]:
"""
Pulisce tutti i nomi delle colonne in un DataFrame.
Parameters
----------
df : pd.DataFrame
DataFrame con le colonne da pulire
Returns
-------
list
Lista dei nuovi nomi delle colonne puliti
"""
new_columns = []
for col in df.columns:
# Usa regex per separare le varietà
varieties = re.findall(r'([a-z]+)_([a-z_]+)', col)
if varieties:
new_columns.append(f"{varieties[0][0]}_{varieties[0][1]}")
else:
new_columns.append(col)
return new_columns
def to_camel_case(text: str) -> str:
"""
Converte una stringa in camelCase.
Gestisce stringhe con spazi, trattini o underscore.
Se è una sola parola, la restituisce in minuscolo.
Parameters
----------
text : str
Testo da convertire
Returns
-------
str
Testo convertito in camelCase
"""
# Rimuove eventuali spazi iniziali e finali
text = text.strip()
# Se la stringa è vuota, ritorna stringa vuota
if not text:
return ""
# Sostituisce trattini e underscore con spazi
text = text.replace('-', ' ').replace('_', ' ')
# Divide la stringa in parole
words = text.split()
# Se non ci sono parole dopo lo split, ritorna stringa vuota
if not words:
return ""
# Se c'è una sola parola, ritorna in minuscolo
if len(words) == 1:
return words[0].lower()
# Altrimenti procedi con il camelCase
result = words[0].lower()
for word in words[1:]:
result += word.capitalize()
return result
def get_full_data(simulated_data: pd.DataFrame,
olive_varieties: pd.DataFrame) -> pd.DataFrame:
"""
Ottiene il dataset completo combinando dati simulati e varietà di olive.
Parameters
----------
simulated_data : pd.DataFrame
DataFrame con i dati simulati
olive_varieties : pd.DataFrame
DataFrame con le informazioni sulle varietà
Returns
-------
pd.DataFrame
DataFrame completo con tutte le informazioni
"""
# Colonne base rilevanti
relevant_columns = [
'year', 'temp_mean', 'precip_sum', 'solar_energy_sum',
'ha', 'zone', 'olive_prod'
]
# Aggiungi colonne specifiche per varietà
all_varieties = olive_varieties['Varietà di Olive'].unique()
varieties = [clean_column_name(variety) for variety in all_varieties]
for variety in varieties:
relevant_columns.extend([
f'{variety}_olive_prod',
f'{variety}_tech'
])
# Seleziona solo le colonne rilevanti
full_data = simulated_data[relevant_columns].copy()
# Aggiungi feature calcolate
for variety in varieties:
# Calcola efficienza produttiva
if f'{variety}_olive_prod' in full_data.columns:
full_data[f'{variety}_efficiency'] = (
full_data[f'{variety}_olive_prod'] / full_data['ha']
)
# Aggiungi indicatori tecnici
if f'{variety}_tech' in full_data.columns:
technique_dummies = pd.get_dummies(
full_data[f'{variety}_tech'],
prefix=f'{variety}_technique'
)
full_data = pd.concat([full_data, technique_dummies], axis=1)
# Aggiungi feature temporali
full_data['month'] = 1 # Assumiamo dati annuali
full_data['day'] = 1 # Assumiamo dati annuali
# Calcola medie mobili
for col in ['temp_mean', 'precip_sum', 'solar_energy_sum']:
full_data[f'{col}_ma3'] = full_data[col].rolling(window=3, min_periods=1).mean()
full_data[f'{col}_ma5'] = full_data[col].rolling(window=5, min_periods=1).mean()
return full_data
def prepare_static_features_multiple(varieties_info: List[Dict],
percentages: List[float],
hectares: float,
all_varieties: List[str]) -> np.ndarray:
"""
Prepara le feature statiche per multiple varietà.
Parameters
----------
varieties_info : List[Dict]
Lista di dizionari contenenti le informazioni sulle varietà selezionate
percentages : List[float]
Lista delle percentuali corrispondenti a ciascuna varietà selezionata
hectares : float
Numero di ettari totali
all_varieties : List[str]
Lista di tutte le possibili varietà nel dataset originale
Returns
-------
np.ndarray
Array numpy contenente tutte le feature statiche
"""
# Inizializza un dizionario per tutte le varietà possibili
variety_data = {variety.lower(): {
'pct': 0,
'prod_t_ha': 0,
'tech': '',
'oil_prod_t_ha': 0,
'oil_prod_l_ha': 0,
'min_yield_pct': 0,
'max_yield_pct': 0,
'min_oil_prod_l_ha': 0,
'max_oil_prod_l_ha': 0,
'avg_oil_prod_l_ha': 0,
'l_per_t': 0,
'min_l_per_t': 0,
'max_l_per_t': 0,
'avg_l_per_t': 0,
'water_need_spring': 0,
'water_need_summer': 0,
'water_need_autumn': 0,
'water_need_winter': 0,
'annual_water_need': 0,
'optimal_temp': 0,
'drought_resistance': 0
} for variety in all_varieties}
# Aggiorna i dati per le varietà selezionate
for variety_info, percentage in zip(varieties_info, percentages):
variety_name = clean_column_name(variety_info['variet_di_olive']).lower()
technique = clean_column_name(variety_info['tecnica_di_coltivazione']).lower()
if variety_name not in variety_data:
print(f"Attenzione: La varietà '{variety_name}' non è presente nella lista delle varietà conosciute.")
continue
variety_data[variety_name].update({
'pct': percentage / 100,
'prod_t_ha': variety_info['produzione_tonnellateettaro'],
'tech': technique,
'oil_prod_t_ha': variety_info['produzione_olio_tonnellateettaro'],
'oil_prod_l_ha': variety_info['produzione_olio_litriettaro'],
'min_yield_pct': variety_info['min__resa'],
'max_yield_pct': variety_info['max__resa'],
'min_oil_prod_l_ha': variety_info['min_produzione_olio_litriettaro'],
'max_oil_prod_l_ha': variety_info['max_produzione_olio_litriettaro'],
'avg_oil_prod_l_ha': variety_info['media_produzione_olio_litriettaro'],
'l_per_t': variety_info['litri_per_tonnellata'],
'min_l_per_t': variety_info['min_litri_per_tonnellata'],
'max_l_per_t': variety_info['max_litri_per_tonnellata'],
'avg_l_per_t': variety_info['media_litri_per_tonnellata'],
'water_need_spring': variety_info['fabbisogno_acqua_primavera_mettaro'],
'water_need_summer': variety_info['fabbisogno_acqua_estate_mettaro'],
'water_need_autumn': variety_info['fabbisogno_acqua_autunno_mettaro'],
'water_need_winter': variety_info['fabbisogno_acqua_inverno_mettaro'],
'annual_water_need': variety_info['fabbisogno_idrico_annuale_mettaro'],
'optimal_temp': variety_info['temperatura_ottimale'],
'drought_resistance': variety_info['resistenza_alla_siccit']
})
# Crea il vettore delle feature
static_features = [hectares]
# Lista delle feature per ogni varietà
variety_features = ['pct', 'prod_t_ha', 'oil_prod_t_ha', 'oil_prod_l_ha',
'min_yield_pct', 'max_yield_pct', 'min_oil_prod_l_ha',
'max_oil_prod_l_ha', 'avg_oil_prod_l_ha', 'l_per_t',
'min_l_per_t', 'max_l_per_t', 'avg_l_per_t',
'water_need_spring', 'water_need_summer', 'water_need_autumn',
'water_need_winter', 'annual_water_need', 'optimal_temp',
'drought_resistance']
# Appiattisci i dati delle varietà
for variety in all_varieties:
variety_lower = variety.lower()
# Feature esistenti
for feature in variety_features:
static_features.append(variety_data[variety_lower][feature])
# Feature binarie per le tecniche
for technique in ['tradizionale', 'intensiva', 'superintensiva']:
static_features.append(1 if variety_data[variety_lower]['tech'] == technique else 0)
return np.array(static_features).reshape(1, -1)
def get_feature_names(all_varieties: List[str]) -> List[str]:
"""
Genera i nomi delle feature nell'ordine corretto.
Parameters
----------
all_varieties : List[str]
Lista di tutte le varietà possibili
Returns
-------
List[str]
Lista dei nomi delle feature
"""
feature_names = ['hectares']
variety_features = ['pct', 'prod_t_ha', 'oil_prod_t_ha', 'oil_prod_l_ha',
'min_yield_pct', 'max_yield_pct', 'min_oil_prod_l_ha',
'max_oil_prod_l_ha', 'avg_oil_prod_l_ha', 'l_per_t',
'min_l_per_t', 'max_l_per_t', 'avg_l_per_t']
techniques = ['tradizionale', 'intensiva', 'superintensiva']
for variety in all_varieties:
for feature in variety_features:
feature_names.append(f"{variety}_{feature}")
for technique in techniques:
feature_names.append(f"{variety}_tech_{technique}")
return feature_names
def add_controlled_variation(base_value: float, max_variation_pct: float = 0.20) -> float:
"""
Aggiunge una variazione controllata a un valore base.
Parameters
----------
base_value : float
Valore base da modificare
max_variation_pct : float
Percentuale massima di variazione (default 20%)
Returns
-------
float
Valore con variazione applicata
"""
variation = np.random.uniform(-max_variation_pct, max_variation_pct)
return base_value * (1 + variation)
def get_growth_phase(month):
if month in [12, 1, 2]:
return 'dormancy'
elif month in [3, 4, 5]:
return 'flowering'
elif month in [6, 7, 8]:
return 'fruit_set'
else:
return 'ripening'
def calculate_weather_effect(row, optimal_temp):
# Effetti base
temp_effect = -0.1 * (row['temp_mean'] - optimal_temp) ** 2
rain_effect = -0.05 * (row['precip_sum'] - 600) ** 2 / 10000
sun_effect = 0.1 * row['solarenergy_sum'] / 1000
# Fattori di scala basati sulla fase di crescita
if row['growth_phase'] == 'dormancy':
temp_scale = 0.5
rain_scale = 0.2
sun_scale = 0.1
elif row['growth_phase'] == 'flowering':
temp_scale = 2.0
rain_scale = 1.5
sun_scale = 1.0
elif row['growth_phase'] == 'fruit_set':
temp_scale = 1.5
rain_scale = 1.0
sun_scale = 0.8
else: # ripening
temp_scale = 1.0
rain_scale = 0.5
sun_scale = 1.2
# Calcolo dell'effetto combinato
combined_effect = (
temp_scale * temp_effect +
rain_scale * rain_effect +
sun_scale * sun_effect
)
# Aggiustamenti specifici per fase
if row['growth_phase'] == 'flowering':
combined_effect -= 0.5 * max(0, row['precip_sum'] - 50) # Penalità per pioggia eccessiva durante la fioritura
elif row['growth_phase'] == 'fruit_set':
combined_effect += 0.3 * max(0, row['temp_mean'] - (optimal_temp + 5)) # Bonus per temperature più alte durante la formazione dei frutti
return combined_effect
def calculate_water_need(weather_data, base_need, optimal_temp):
# Calcola il fabbisogno idrico basato su temperatura e precipitazioni
temp_factor = 1 + 0.05 * (weather_data['temp_mean'] - optimal_temp) # Aumenta del 5% per ogni grado sopra l'ottimale
rain_factor = 1 - 0.001 * weather_data['precip_sum'] # Diminuisce leggermente con l'aumentare delle precipitazioni
return base_need * temp_factor * rain_factor
def create_technique_mapping(olive_varieties, mapping_path='./sources/technique_mapping.joblib'):
# Estrai tutte le tecniche uniche dal dataset e convertile in lowercase
all_techniques = olive_varieties['Tecnica di Coltivazione'].str.lower().unique()
# Crea il mapping partendo da 1
technique_mapping = {tech: i + 1 for i, tech in enumerate(sorted(all_techniques))}
# Salva il mapping
os.makedirs(os.path.dirname(mapping_path), exist_ok=True)
joblib.dump(technique_mapping, mapping_path)
return technique_mapping
def encode_techniques(df, mapping_path='./sources/technique_mapping.joblib'):
if not os.path.exists(mapping_path):
raise FileNotFoundError(f"Mapping not found at {mapping_path}. Run create_technique_mapping first.")
technique_mapping = joblib.load(mapping_path)
# Trova tutte le colonne delle tecniche
tech_columns = [col for col in df.columns if col.endswith('_tech')]
# Applica il mapping a tutte le colonne delle tecniche
for col in tech_columns:
df[col] = df[col].str.lower().map(technique_mapping).fillna(0).astype(int)
return df
def decode_techniques(df, mapping_path='./sources/technique_mapping.joblib'):
if not os.path.exists(mapping_path):
raise FileNotFoundError(f"Mapping not found at {mapping_path}")
technique_mapping = joblib.load(mapping_path)
reverse_mapping = {v: k for k, v in technique_mapping.items()}
reverse_mapping[0] = '' # Aggiungi un mapping per 0 a stringa vuota
# Trova tutte le colonne delle tecniche
tech_columns = [col for col in df.columns if col.endswith('_tech')]
# Applica il reverse mapping a tutte le colonne delle tecniche
for col in tech_columns:
df[col] = df[col].map(reverse_mapping)
return df
def decode_single_technique(technique_value, mapping_path='./sources/technique_mapping.joblib'):
if not os.path.exists(mapping_path):
raise FileNotFoundError(f"Mapping not found at {mapping_path}")
technique_mapping = joblib.load(mapping_path)
reverse_mapping = {v: k for k, v in technique_mapping.items()}
reverse_mapping[0] = ''
return reverse_mapping.get(technique_value, '')
def preprocess_weather_data(weather_df):
# Calcola statistiche mensili per ogni anno
monthly_weather = weather_df.groupby(['year', 'month']).agg({
'temp': ['mean', 'min', 'max'],
'humidity': 'mean',
'precip': 'sum',
'windspeed': 'mean',
'cloudcover': 'mean',
'solarradiation': 'sum',
'solarenergy': 'sum',
'uvindex': 'max'
}).reset_index()
monthly_weather.columns = ['year', 'month'] + [f'{col[0]}_{col[1]}' for col in monthly_weather.columns[2:]]
return monthly_weather