Knowledge Science at House: Fixing the Nanny Schedule Puzzle with Monte Carlo and Genetic Algorithms | by Courtney Perigo | Sep, 2024
Armed with simulation of all of the doable methods our schedule can throw curveballs at us, I knew it was time to herald some heavy-hitting optimization methods. Enter genetic algorithms — a pure selection-inspired optimization technique that finds the most effective answer by iteratively evolving a inhabitants of candidate options.
On this case, every “candidate” was a possible set of nanny traits, reminiscent of their availability and adaptability. The algorithm evaluates totally different nanny traits, and iteratively improves these traits to search out the one that matches our household’s wants. The end result? A extremely optimized nanny with scheduling preferences that steadiness our parental protection gaps with the nanny’s availability.
On the coronary heart of this strategy is what I prefer to name the “nanny chromosome.” In genetic algorithm phrases, a chromosome is just a solution to symbolize potential options — in our case, totally different nanny traits. Every “nanny chromosome” had a set of options that outlined their schedule: the variety of days per week the nanny might work, the utmost hours she might cowl in a day, and their flexibility to regulate to various begin occasions. These options had been the constructing blocks of each potential nanny schedule the algorithm would contemplate.
Defining the Nanny Chromosome
In genetic algorithms, a “chromosome” represents a doable answer, and on this case, it’s a set of options defining a nanny’s schedule. Right here’s how we outline a nanny’s traits:
# Perform to generate nanny traits
def generate_nanny_characteristics():
return {
'versatile': np.random.selection([True, False]), # Nanny's flexibility
'days_per_week': np.random.selection([3, 4, 5]), # Days accessible per week
'hours_per_day': np.random.selection([6, 7, 8, 9, 10, 11, 12]) # Hours accessible per day
}
Every nanny’s schedule is outlined by their flexibility (whether or not they can alter begin occasions), the variety of days they’re accessible per week, and the utmost hours they’ll work per day. This offers the algorithm the flexibleness to guage all kinds of potential schedules.
Constructing the Schedule for Every Nanny
As soon as the nanny’s traits are outlined, we have to generate a weekly schedule that matches these constraints:
# Perform to calculate a weekly schedule based mostly on nanny's traits
def calculate_nanny_schedule(traits, num_days=5):
shifts = []
for _ in vary(num_days):
start_hour = np.random.randint(6, 12) if traits['flexible'] else 9 # Versatile nannies have various begin occasions
end_hour = start_hour + traits['hours_per_day'] # Calculate finish hour based mostly on hours per day
shifts.append((start_hour, end_hour))
return shifts # Return the generated weekly schedule
This operate builds a nanny’s schedule based mostly on their outlined flexibility and dealing hours. Versatile nannies can begin between 6 AM and 12 PM, whereas others have fastened schedules that begin and finish at set occasions. This enables the algorithm to guage a variety of doable weekly schedules.
Choosing the Finest Candidates
As soon as we’ve generated an preliminary inhabitants of nanny schedules, we use a health operate to guage which of them greatest meet our childcare wants. Essentially the most match schedules are chosen to maneuver on to the following technology:
# Perform for choice in genetic algorithm
def choice(inhabitants, fitness_scores, num_parents):
# Normalize health scores and choose mother and father based mostly on likelihood
min_fitness = np.min(fitness_scores)
if min_fitness < 0:
fitness_scores = fitness_scores - min_fitnessfitness_scores_sum = np.sum(fitness_scores)
possibilities = fitness_scores / fitness_scores_sum if fitness_scores_sum != 0 else np.ones(len(fitness_scores)) / len(fitness_scores)
# Choose mother and father based mostly on their health scores
selected_parents = np.random.selection(inhabitants, dimension=num_parents, p=possibilities)
return selected_parents
Within the choice step, the algorithm evaluates the inhabitants of nanny schedules utilizing a health operate that measures how properly the nanny’s availability aligns with the household’s wants. Essentially the most match schedules, people who greatest cowl the required hours, are chosen to change into “mother and father” for the following technology.
Including Mutation to Preserve Issues Attention-grabbing
To keep away from getting caught in suboptimal options, we add a little bit of randomness by mutation. This enables the algorithm to discover new potentialities by often tweaking the nanny’s schedule:
# Perform to mutate nanny traits
def mutate_characteristics(traits, mutation_rate=0.1):
if np.random.rand() < mutation_rate:
traits['flexible'] = not traits['flexible']
if np.random.rand() < mutation_rate:
traits['days_per_week'] = np.random.selection([3, 4, 5])
if np.random.rand() < mutation_rate:
traits['hours_per_day'] = np.random.selection([6, 7, 8, 9, 10, 11, 12])
return traits
By introducing small mutations, the algorithm is ready to discover new schedules that may not have been thought of in any other case. This range is essential for avoiding native optima and enhancing the answer over a number of generations.
Evolving Towards the Excellent Schedule
The ultimate step was evolution. With choice and mutation in place, the genetic algorithm iterates over a number of generations, evolving higher nanny schedules with every spherical. Right here’s how we implement the evolution course of:
# Perform to evolve nanny traits over a number of generations
def evolve_nanny_characteristics(all_childcare_weeks, population_size=1000, num_generations=10):
inhabitants = [generate_nanny_characteristics() for _ in range(population_size)] # Initialize the inhabitantsfor technology in vary(num_generations):
print(f"n--- Era {technology + 1} ---")
fitness_scores = []
hours_worked_collection = []
for traits in inhabitants:
fitness_score, yearly_hours_worked = fitness_function_yearly(traits, all_childcare_weeks)
fitness_scores.append(fitness_score)
hours_worked_collection.append(yearly_hours_worked)
fitness_scores = np.array(fitness_scores)
# Discover and retailer the most effective particular person of this technology
max_fitness_idx = np.argmax(fitness_scores)
best_nanny = inhabitants[max_fitness_idx]
best_nanny['actual_hours_worked'] = hours_worked_collection[max_fitness_idx]
# Choose mother and father and generate a brand new inhabitants
mother and father = choice(inhabitants, fitness_scores, num_parents=population_size // 2)
new_population = []
for i in vary(0, len(mother and father), 2):
parent_1, parent_2 = mother and father[i], mother and father[i + 1]
little one = {
'versatile': np.random.selection([parent_1['flexible'], parent_2['flexible']]),
'days_per_week': np.random.selection([parent_1['days_per_week'], parent_2['days_per_week']]),
'hours_per_day': np.random.selection([parent_1['hours_per_day'], parent_2['hours_per_day']])
}
little one = mutate_characteristics(little one)
new_population.append(little one)
inhabitants = new_population # Substitute the inhabitants with the brand new technology
return best_nanny # Return the most effective nanny in spite of everything generations
Right here, the algorithm evolves over a number of generations, selecting the right nanny schedules based mostly on their health scores and permitting new options to emerge by mutation. After a number of generations, the algorithm converges on the very best nanny schedule, optimizing protection for our household.
Ultimate Ideas
With this strategy, we utilized genetic algorithms to iteratively enhance nanny schedules, making certain that the chosen schedule might deal with the chaos of Dad or mum 2’s unpredictable work shifts whereas balancing our household’s wants. Genetic algorithms could have been overkill for the duty, however they allowed us to discover varied potentialities and optimize the answer over time.
The pictures beneath describe the evolution of nanny health scores over time. The algorithm was in a position to rapidly converge on the most effective nanny chromosome after only a few generations.