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