GeneticWFM / src /engine /mutation.py
GaetanoParente's picture
first commit
9e62f55
import numpy as np
import random
from src.config import cfg
CODE_OFF = -1
def mutate(individual, slots_cache, daily_deficit_probs):
"""
Applica operatore di mutazione con logica ibrida (Guided/Random).
"""
mut_rate = cfg.genetic_params.get('mutation_rate', 0.4)
split_rate = cfg.genetic_params.get('guided_mutation_split', 0.4)
# Early exit se non triggeriamo la mutazione (risparmio computazionale)
if random.random() > mut_rate:
return individual
mutated = individual.copy()
num_emps, num_days = mutated.shape
emp_idx = random.randint(0, num_emps - 1)
if random.random() < split_rate:
# --- MUTATION STRATEGY 1: Day Swap (Guided) ---
# Sposta un turno da un giorno all'altro cercando di coprire i deficit operativi
row = mutated[emp_idx]
days_worked = np.where(row >= 0)[0]
days_off = np.where(row == CODE_OFF)[0]
if len(days_worked) > 0 and len(days_off) > 0:
# 1. Selezione del giorno da riempire (pesata sul deficit)
try:
probs_off = daily_deficit_probs[days_off]
if np.sum(probs_off) > 0:
probs_off = probs_off / np.sum(probs_off)
day_to_fill = np.random.choice(days_off, p=probs_off)
else:
day_to_fill = np.random.choice(days_off)
except:
# Safe check in caso di anomalie nei tensori
day_to_fill = np.random.choice(days_off)
# 2. Selezione del giorno da svuotare (inversamente proporzionale al deficit)
try:
probs_work = 1.0 - daily_deficit_probs[days_worked]
probs_work = probs_work + 0.01 # Smoothing per evitare probabilità nulle
probs_work = probs_work / np.sum(probs_work)
day_to_empty = np.random.choice(days_worked, p=probs_work)
except:
day_to_empty = np.random.choice(days_worked)
# Esegue lo swap solo se esistono slot validi nella cache pre-calcolata
valid_slots = slots_cache[emp_idx][day_to_fill]
if len(valid_slots) > 0:
mutated[emp_idx, day_to_fill] = np.random.choice(valid_slots)
mutated[emp_idx, day_to_empty] = CODE_OFF
else:
# --- MUTATION STRATEGY 2: Time Shift ---
# Perturba randomicamente l'orario di inizio turno sullo stesso giorno
day_idx = random.randint(0, 6)
if mutated[emp_idx, day_idx] >= 0:
valid = slots_cache[emp_idx][day_idx]
if len(valid) > 0:
mutated[emp_idx, day_idx] = np.random.choice(valid)
return mutated