- move models to separate repository
This commit is contained in:
commit
56c1c3cc1f
69 files changed
+49903
No files matched your search
Executable
Whitespace-only changes.
Executable
+504
@@ -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
|
||||
Reference in new issue
Block a user