File size: 2,776 Bytes
9e62f55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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