[PYTHON] Solving the nurse scheduling problem (shift optimization) with a genetic algorithm

This article

Premise

Nurse scheduling problem

This is a problem of determining the work schedule of nurses working in medical facilities such as hospitals, and is a typical example of the shift scheduling problem. Due to complicated shift work such as day shift, evening shift, night shift, and consideration of various restrictions, it is a difficult task that requires manual labor to actually obtain a schedule ...

Genetic algorithm

So how to apply

Day of the week Month Month Month fire fire fire water water water wood wood wood Money Money Money soil soil soil Day Day Day
Time zone Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night
Employee 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0
Employee 1 1 1 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0
Employee 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 0
[0,0,0,1,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,
 1,1,0,0,0,0,0,1,1,0,0,0,0,0,1,0,0,0,0,0,0,
 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,0]

Implemented in Python / deap

Let's proceed with the implementation concretely.

Use of deap

pip install deap

Sample specifications

The specifications of this sample are as follows.

Employees and shift wishes

Day of the week Month Month Month fire fire fire water water water wood wood wood Money Money Money soil soil soil Day Day Day
Time zone Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night
Required number of people 2 3 3 2 3 3 2 3 3 1 2 2 2 3 3 2 4 4 2 4 4
Employee 0
Employee 1
Employee 2
Employee 3
Employee 4
Employee 5
Employee 6
Employee 7
Employee 8
Employee 9

Constraint

Make sure your score goes up enough to meet the following constraints. The numbers in parentheses are weights and represent the priority of the constraint.

Code overview

point

Employee definition

class Employee(object):
  def __init__(self, no, name, age, manager, wills):
    self.no = no
    self.name = name
    self.age = age
    self.manager = manager

    #will is the day of the week_Time zone. 1 is morning, 2 is noon, 3 is night.
    #Example) mon_1 is monday morning
    self.wills = wills
...
#Only in the morning
e0 = Employee(0, "Yamada", 40, False, ['mon_1', 'tue_1', 'wed_1', 'thu_1', 'fri_1', 'sat_1', 'sun_1'])

#Mon / Wed / Fri
e1 = Employee(1, "Suzuki", 21, False, ['mon_1', 'mon_2', 'mon_3', 'wed_1', 'wed_2', 'wed_3','fri_1', 'fri_2', 'fri_3'])

#Only on weekends
e2 = Employee(2, "Sato", 18, False, ['sat_1', 'sat_2', 'sat_3', 'sun_1', 'sun_2', 'sun_3'])

#OK anywhere
e3 = Employee(3, "Tanaka", 35, True, ['mon_1', 'mon_2', 'mon_3',
                                     'tue_1', 'tue_2', 'tue_3',
                                     'wed_1', 'wed_2', 'wed_3',
                                     'thu_1', 'thu_2', 'thu_3',
                                     'fri_1', 'fri_2', 'fri_3',
                                     'sat_1', 'sat_2', 'sat_3',
                                     'sun_1', 'sun_2', 'sun_3'
                                    ])

...

employees = [e0, e1, e2, e3, e4, e5, e6, e7, e8, e9]

Definition of shift (frame)

#Class representing shift
#Internally 3(Morning day and night) *The 7th*10 people=Consists of 210-dimensional tuples
class Shift(object):
  #Definition of top
  SHIFT_BOXES = [
    'mon_1', 'mon_2', 'mon_3',
    'tue_1', 'tue_2', 'tue_3',
    'wed_1', 'wed_2', 'wed_3',
    'thu_1', 'thu_2', 'thu_3',
    'fri_1', 'fri_2', 'fri_3',
    'sat_1', 'sat_2', 'sat_3',
    'sun_1', 'sun_2', 'sun_3']

  #Estimated number of people in each frame
  NEED_PEOPLE = [
    2,3,3,
    2,3,3,
    2,3,3,
    1,2,2,
    2,3,3,
    2,4,4,
    2,4,4]

  def __init__(self, list):
    if list == None:
      self.make_sample()
    else:
      self.list = list
    self.employees = []

  #Generate random data
  def make_sample(self):
    sample_list = []
    for num in range(210):
      sample_list.append(random.randint(0, 1))
    self.list = tuple(sample_list)
...

Scoring and weighting

creator.create("FitnessPeopleCount", base.Fitness, weights=(-10.0, -100.0, -1.0, -100.0, -10.0))
creator.create("Individual", list, fitness=creator.FitnessPeopleCount)

...

def evalShift(individual):
  s = Shift(individual)
  s.employees = employees

  #Difference between expected number of people and assigned number of people
  people_count_sub_sum = sum(s.abs_people_between_need_and_actual()) / 210.0
  #Number of assignments to times when you are not applying
  not_applicated_count = s.not_applicated_assign() / 210.0
  #Number of employees with less than half the number of assignments
  few_work_user = len(s.few_work_user()) / 10.0
  #Number of frames with no administrator
  no_manager_box = len(s.no_manager_box()) / 21.0
  #Assigned to all morning, noon, and night
  three_box_per_day = len(s.three_box_per_day()) / 70.0
  return (not_applicated_count, people_count_sub_sum, few_work_user, no_manager_box, three_box_per_day)

toolbox.register("evaluate", evalShift)

Specify the number of evolutions, etc.

    pop = toolbox.population(n=300)
    CXPB, MUTPB, NGEN = 0.6, 0.5, 500 #Crossing probability, mutation probability, evolutionary computation loop count

Run

$ python nurse_scheduling_by_ga.py 
Evolution started
Evaluate 300 individuals
--0 generation--
Evaluate 245 individuals
*Parameter 1
  Min 0.242857142857
  Max 0.242857142857
  Avg 0.242857142857
  Std 2.2660056242e-08
*Parameter 2
  Min 0.247619047619
  Max 0.247619047619
  Avg 0.247619047619
  Std 9.49766396283e-09
*Parameter 3
  Min 0.0
  Max 0.0
  Avg 0.0
  Std 0.0
*Parameter 4
  Min 0.142857142857
  Max 0.142857142857
  Avg 0.142857142857
  Std 1.66600046866e-08
*Parameter 5
  Min 0.114285714286
  Max 0.114285714286
  Avg 0.114285714286
  Std 1.19267483008e-08
--1 generation--
Evaluate 235 individuals
*Parameter 1
  Min 0.238095238095
  Max 0.238095238095
  Avg 0.238095238095
  Std 2.45699769971e-08
*Parameter 2
  Min 0.228571428571
  Max 0.228571428571
  Avg 0.228571428571
  Std 2.38534966016e-08
*Parameter 3
  Min 0.1
  Max 0.1
  Avg 0.1
  Std 1.31048702444e-08
*Parameter 4
  Min 0.047619047619
  Max 0.047619047619
  Avg 0.047619047619
  Std 4.46646616171e-09
*Parameter 5
  Min 0.1
  Max 0.1
  Avg 0.1
  Std 1.31048702444e-08

(snip)

--499 generations--
Evaluate 219 individuals
*Parameter 1
  Min 0.0333333333333
  Max 0.0333333333333
  Avg 0.0333333333333
  Std 2.98168707519e-09
*Parameter 2
  Min 0.0714285714286
  Max 0.0714285714286
  Avg 0.0714285714286
  Std 8.33000234328e-09
*Parameter 3
  Min 0.1
  Max 0.1
  Avg 0.1
  Std 1.31048702444e-08
*Parameter 4
  Min 0.0952380952381
  Max 0.0952380952381
  Avg 0.0952380952381
  Std 8.93293232343e-09
*Parameter 5
  Min 0.0428571428571
  Max 0.0428571428571
  Avg 0.0428571428571
  Std 4.56253018749e-09
--End of evolution--
The best individual: [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1], (0.0, 0.0, 0.2, 0.047619047619047616, 0.1)
0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0
0,1,0,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1
1,0,1,0,1,1,0,0,1,0,1,0,1,0,1,0,1,1,0,0,1
0,0,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0
1,1,1,1,1,1,1,1,1,0,0,1,0,1,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1,1,1,1
0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,1,0,0,1,0
0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,1,0,1,1
0	0	0	1	0	0	0	0	0	0	0	0	0	0	0	1	0	0
0	1	0	0	0	0	1	1	0	0	0	0	1	1	1	0	0	0
0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0	1
1	0	1	0	1	1	0	0	1	0	1	0	1	0	1	0	1	1
0	0	0	0	0	1	0	0	1	0	0	1	0	0	1	0	0	0
1	1	1	1	1	1	1	1	1	0	0	1	0	1	0	0	0	0
0	0	0	0	0	0	0	0	0	0	0	0	0	1	0	0	1	1
0	1	0	0	1	0	0	1	0	0	1	0	0	0	0	0	1	0
0	0	1	0	0	0	0	0	0	0	0	0	0	0	0	0	0	0
0	0	0	0	0	0	0	0	0	1	0	0	0	0	0	1	1	1

result

Day of the week Month Month Month fire fire fire water water water wood wood wood Money Money Money soil soil soil Day Day Day Number of assignments Desired number Assignment rate
Time zone Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night Morning Noon Night
Required number of people 2 3 3 2 3 3 2 3 3 1 2 2 2 3 3 2 4 4 2 4 4
Approved number 2 3 3 2 3 3 2 3 3 1 2 2 2 3 3 2 4 4 2 4 4
Difference 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Employee 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 5 7 71.4%
Employee 1 0 1 1 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0 0 6 9 66.7%
Employee 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1 4 6 66.7%
[tube]Employee 3 0 1 0 0 1 0 0 1 0 1 1 0 1 0 0 0 0 0 0 1 0 7 21 33.3%
Employee 4 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 1 5 7 71.4%
[tube]Employee 5 1 0 1 1 1 1 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0 8 15 53.3%
Employee 6 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1 1 0 0 0 5 9 55.6%
Employee 7 0 1 0 0 1 0 0 1 0 0 1 0 0 0 0 0 1 0 0 1 0 6 7 85.7%
Employee 8 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 4 7 57.1%
[tube]Employee 9 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 1 1 1 1 1 7 12 58.3%
Number of administrators 1 1 1 1 2 1 1 1 0 1 1 2 1 1 1 0 1 1 1 2 1

At the end

Recommended Posts

Solving the nurse scheduling problem (shift optimization) with a genetic algorithm
Finding a solution to the N-Queen problem with a genetic algorithm (2)
Finding a solution to the N-Queen problem with a genetic algorithm (1)
Solving nurse scheduling problems with combinatorial optimization
Solving the N Queen problem with combinatorial optimization
Solving the N Queens problem with combinatorial optimization
Solving the Python knapsack problem with the greedy algorithm
Try to solve the traveling salesman problem with a genetic algorithm (Theory)
Try to solve the traveling salesman problem with a genetic algorithm (Python code)
Try to solve the traveling salesman problem with a genetic algorithm (execution result)
Solving the traveling salesman problem with the genetic algorithm (GA) and its library (vcopt)
Find the optimal value of a function with a genetic algorithm (Part 2)
Solving the paraboloid minimization problem with OpenMDAO
Search the maze with the python A * algorithm
We will implement the optimization algorithm (Problem)
How to fix the initial population with a genetic algorithm using DEAP
The first algorithm to learn with Python: FizzBuzz problem
Solving the iris problem with scikit-learn ver1.0 (logistic regression)
[Python] Try optimizing FX systole parameters with a genetic algorithm
I tried to solve a combination optimization problem with Qiskit
A story about how to deal with the CORS problem
A memo that solves the knapsack problem by the greedy algorithm
I tried "Implementing a genetic algorithm (GA) in python to solve the traveling salesman problem (TSP)"
Solving the Maze with Python-Supplement to Chapter 6 of the Algorithm Quick Reference-
I wanted to solve the ABC164 A ~ D problem with Python
Error with pip: There was a problem confirming the ssl certificate
Solve the Python knapsack problem with a branch and bound method
Solving Knapsack Problems with Google's OR-Tools-Practicing the Basics of Combinatorial Optimization Problems
I tried to solve the shift scheduling problem by various methods
Finding the optimum value of a function using a genetic algorithm (Part 1)
Solve the subset sum problem with a full search in Python
Solving 4-color problems with combinatorial optimization
Pave the road with combinatorial optimization
Solving game theory with combinatorial optimization
Calculate the shortest route of a graph with Dijkstra's algorithm and Python