# The Nurses Model¶

This tutorial includes everything you need to set up IBM Decision Optimization CPLEX Modeling for Python (DOcplex), build a Mathematical Programming model, and get its solution by solving the model on the cloud with IBM ILOG CPLEX Optimizer.

When you finish this tutorial, you’ll have a foundational knowledge of
*Prescriptive Analytics*.

This notebook is part of Prescriptive Analytics for Python

It requires either an installation of CPLEX Optimizers or it can be run on IBM Watson Studio Cloud (Sign up for a free IBM Cloud account and you can start using Watson Studio Cloud right away).

Table of contents:

## Describe the business problem¶

This model deals with nurse scheduling. Nurses must be assigned to hospital shifts in accordance with various skill and staffing constraints.

The goal of the model is to find an efficient balance between the different objectives:

- minimize the overall cost of the plan and
- assign shifts as fairly as possible.

## How decision optimization can help¶

Prescriptive analytics (decision optimization) technology recommends actions that are based on desired outcomes. It takes into account specific scenarios, resources, and knowledge of past and current events. With this insight, your organization can make better decisions and have greater control of business outcomes.

Prescriptive analytics is the next step on the path to insight-based actions. It creates value through synergy with predictive analytics, which analyzes data to predict future outcomes.

- Prescriptive analytics takes that insight to the next level by suggesting the optimal way to handle that future situation. Organizations that can act fast in dynamic conditions and make superior decisions in uncertain environments gain a strong competitive advantage.

With prescriptive analytics, you can:

- Automate the complex decisions and trade-offs to better manage your limited resources.
- Take advantage of a future opportunity or mitigate a future risk.
- Proactively update recommendations based on changing events.
- Meet operational goals, increase customer loyalty, prevent threats and fraud, and optimize business processes.

## Use decision optimization¶

### Step 1: Import the library¶

Run the following code to import Decision Optimization CPLEX Modeling
library. The *DOcplex* library contains the two modeling packages,
Mathematical Programming and Constraint Programming.

### Step 2: Model the data¶

Input data consists of several tables:

- The Departments table lists all departments in the scope of the assignment.
- The Skills table list all skills.
- The Shifts table lists all shifts to be staffed. A shift contains a department, a day in the week, plus the start and end times.
- The Nurses table lists all nurses, identified by their names.
- The NurseSkills table gives the skills of each nurse.
- The SkillRequirements table lists the minimum number of persons required for a given department and skill.
- The NurseVacations table lists days off for each nurse.
- The NurseAssociations table lists pairs of nurses who wish to work together.
- The NurseIncompatibilities table lists pairs of nurses who do not want to work together . In addition, the plan has to satisfy a maximum worktime for all nurses, for example 40 hours a week.

### Step 3: Prepare the data¶

Now we need some basic data structures to store information.

#### Loading data from Excel with pandas¶

We load the data from an Excel file using *pandas*. Each sheet is read
into a separate *pandas* DataFrame.

name | seniority | qualification | pay_rate | |
---|---|---|---|---|

0 | Anne | 11 | 1 | 25 |

1 | Bethanie | 4 | 5 | 28 |

2 | Betsy | 2 | 2 | 17 |

3 | Cathy | 2 | 2 | 17 |

4 | Cecilia | 9 | 5 | 38 |

5 | Chris | 11 | 4 | 38 |

6 | Cindy | 5 | 2 | 21 |

7 | David | 1 | 2 | 15 |

8 | Debbie | 7 | 2 | 24 |

9 | Dee | 3 | 3 | 21 |

10 | Gloria | 8 | 2 | 25 |

11 | Isabelle | 3 | 1 | 16 |

12 | Jane | 3 | 4 | 23 |

13 | Janelle | 4 | 3 | 22 |

14 | Janice | 2 | 2 | 17 |

15 | Jemma | 2 | 4 | 22 |

16 | Joan | 5 | 3 | 24 |

17 | Joyce | 8 | 3 | 29 |

18 | Jude | 4 | 3 | 22 |

19 | Julie | 6 | 2 | 22 |

20 | Juliet | 7 | 4 | 31 |

21 | Kate | 5 | 3 | 24 |

22 | Nancy | 8 | 4 | 32 |

23 | Nathalie | 9 | 5 | 38 |

24 | Nicole | 0 | 2 | 14 |

25 | Patricia | 1 | 1 | 13 |

26 | Patrick | 6 | 1 | 19 |

27 | Roberta | 3 | 5 | 26 |

28 | Suzanne | 5 | 1 | 18 |

29 | Vickie | 7 | 1 | 20 |

30 | Wendie | 5 | 2 | 21 |

31 | Zoe | 8 | 3 | 29 |

Now, we create some additional data structures to be used for building
the prescriptive model. The goal is to not depend on *pandas* when
defining decision variables and constraints. The ‘nurses_pandas’
notebook illustrates how to benefit from pandas to build the
prescriptive model.

### Step 4: Set up the prescriptive model¶

* system is: Windows 64bit * Python version 3.7.3, located at: c:\local\python373\python.exe * docplex is present, version is (2, 11, 0) * pandas is present, version is 0.25.1

#### Create the DOcplex model¶

This model contains all the business constraints and defines the objective.

#### Define the decision variables¶

The basic decisions are “which nurse works which shift”, which is modeled by binary variables for each (nurse, shift) pair.

The output of the model is, for each shift, the list of nurses that work the shift.

#### Express the business constraints¶

##### First constraint: define average work time¶

The average work time over all nurses will be used in particular to
calculate the over/under average work time for each nurse, and to
formulate a *fairness* rule.

```
docplex.mp.LinearConstraint[average](18AverageWorkTime,EQ,NurseWorkTime_Betsy+NurseWorkTime_Cathy+NurseWorkTime_Cindy+NurseWorkTime_Debbie+NurseWorkTime_Dee+NurseWorkTime_Isabelle+NurseWorkTime_Jane+NurseWorkTime_Janelle+NurseWorkTime_Janice+NurseWorkTime_Jemma+NurseWorkTime_Joan+NurseWorkTime_Jude+NurseWorkTime_Julie+NurseWorkTime_Kate+NurseWorkTime_Patrick+NurseWorkTime_Suzanne+NurseWorkTime_Vickie+NurseWorkTime_Wendie)
```

##### Second constraint: compute nurse work time, average and under/over time¶

##### Third constraint: vacations¶

When a nurse is on vacation, he or she cannot be assigned to any shift starting that day.

##### Fourth constraint: a nurse cannot be assigned overlapping shifts¶

Some shifts overlap in time and thus cannot be assigned to the same nurse.

```
# overlapping shifts: 20
```

##### Fifth constraint: enforce minimum and maximum requirements for shifts¶

Each shift requires a minimum and a maximum number of nurses. For each shift, the sum over all nurses of assignments to this shift must be greater than or equal to the minimum requirement and lesser than or equal to the maximum requirement.

##### Sixth constraint: enforce skill requirements for selected shifts¶

Some shifts require at least *x* nurses with a specified skill.

##### Seventh constraint: associations¶

Some pairs of nurses get along particularly well, so we wish to assign them together as a team. In other words, for every such pair and for each shift, both assignment variables should always be equal. Either both nurses work the shift, or both do not.

##### Eighth constraint: incompatibilities¶

Similarly, certain pairs of nurses do not get along well, and we want to avoid having them together on a shift. In other words, for each shift, both nurses of an incompatible pair cannot be assigned together to the shift.

#### Express the objective¶

The objective mixes different (and contradictory) KPIs.

The first KPI is the total salary cost, computed as the sum of work times over all nurses, weighted by pay rate. The second KPI is the total number of assignments (nurse, shift). The third KPI is the average total work time over all nurses. The fourth KPI represents the total number of hours that is above the average work time (summed over all nurses), while the fifth KPI represents the total number of hours that is below this average. Finally, the last KPI is a measure of fairness, which is evaluated as the total deviation from the average work time.

```
Model: nurses
- number of variables: 793
- binary=738, integer=0, continuous=55
- number of constraints: 961
- linear=961
- parameters: defaults
- problem type is: MILP
```

##### Minimizing objective¶

The goal is to minimize the non-weighted sum of the *total salary cost*,
*fairness* and *total number of assignment*. This is accomplished using
the `Model.minimize()`

method.

This definition is arbitrary and could be revised. For instance, one could emphasize minimizing salary cost by adding a weight on this term in the objective.

#### Solve with Decision Optimization¶

Now we have everything we need to solve the model, using
`Model.solve()`

. The following cell solves using your local CPLEX (if
any, and provided you have added it to your `PYTHONPATH`

variable).

```
CPXPARAM_Read_DataCheck 1
CPXPARAM_MIP_Tolerances_MIPGap 1.0000000000000001e-05
Bound infeasibility column 'NurseWorkTime_Betsy'.
Presolve time = 0.00 sec. (0.29 ticks)
Root node processing (before b&c):
Real time = 0.00 sec. (0.45 ticks)
Parallel b&c, 12 threads:
Real time = 0.00 sec. (0.00 ticks)
Sync time (average) = 0.00 sec.
Wait time (average) = 0.00 sec.
------------
Total (root+branch&cut) = 0.00 sec. (0.45 ticks)
Warning: 55 constraint(s) will not be relaxed (e.g.: average: 18AverageWorkTime == NurseWorkTime_Betsy+NurseWorkTime_Cathy+NurseWorkTime_Cindy+NurseWorkTime_Debbie+NurseWorkTime_Dee+NurseWorkTime_Isabelle+NurseWorkTime_Jane+NurseWorkTime_Janelle+NurseWorkTime_Janice+NurseWorkTime_Jemma+NurseWorkTime_Joan+NurseWorkTime_Jude+NurseWorkTime_Julie+NurseWorkTime_Kate+NurseWorkTime_Patrick+NurseWorkTime_Suzanne+NurseWorkTime_Vickie+NurseWorkTime_Wendie)
* number of relaxations: 35
- relaxed: high_req_min_EMER_Mon_18_3, with relaxation: -3.0
- relaxed: high_req_min_CONS_Mon_08_10, with relaxation: -5.0
- relaxed: high_req_min_CONS_Mon_12_8, with relaxation: -3.0
- relaxed: high_req_min_CARD_Mon_08_10, with relaxation: -6.0
- relaxed: high_req_min_CARD_Mon_12_8, with relaxation: -6.0
- relaxed: high_req_min_EMER_Tue_08_4, with relaxation: -2.0
- relaxed: high_req_min_EMER_Tue_18_3, with relaxation: -3.0
- relaxed: high_req_min_CONS_Tue_08_10, with relaxation: -4.0
- relaxed: high_req_min_CONS_Tue_12_8, with relaxation: -5.0
- relaxed: high_req_min_CARD_Tue_08_4, with relaxation: -1.0
- relaxed: high_req_min_CARD_Tue_18_3, with relaxation: -3.0
- relaxed: high_req_min_EMER_Wed_18_3, with relaxation: -1.0
- relaxed: high_req_min_EMER_Thu_02_3, with relaxation: -1.0
- relaxed: high_req_min_EMER_Thu_18_3, with relaxation: -3.0
- relaxed: high_req_min_CONS_Thu_08_10, with relaxation: -2.0
- relaxed: high_req_min_CONS_Thu_12_8, with relaxation: -1.0
- relaxed: high_req_min_EMER_Fri_18_3, with relaxation: -3.0
- relaxed: high_req_min_CONS_Fri_08_10, with relaxation: -3.0
- relaxed: high_req_min_CONS_Fri_12_8, with relaxation: -3.0
- relaxed: high_req_min_EMER_Sat_02_5, with relaxation: -5.0
- relaxed: high_req_min_EMER_Sat_12_7, with relaxation: -7.0
- relaxed: high_req_min_EMER_Sat_20_12, with relaxation: -4.0
- relaxed: high_req_min_EMER_Sun_02_5, with relaxation: -5.0
- relaxed: high_req_min_EMER_Sun_12_7, with relaxation: -7.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Mon_02, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Mon_18, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Tue_18, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Wed_12, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Wed_18, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Thu_18, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Fri_18, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sat_02, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sat_12, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sun_02, with relaxation: -1.0
- relaxed: high_required_Emergency_Cardiac Care_1_EMER_Sun_12, with relaxation: -1.0
* total absolute relaxation: 97.0
* model nurses solved with objective = 14097.333
* KPI: Total salary cost = 13940.000
* KPI: Total number of assignments = 134.000
* KPI: AverageWorkTime = 37.667
* KPI: Total over-average worktime = 11.667
* KPI: Total under-average worktime = 11.667
* KPI: Total fairness = 23.333
```

### Step 5: Investigate the solution and then run an example analysis¶

Let’s display some charts to visualize the results: a Gantt chart displaying the assignment of nurses to shifts in a Gantt chart, and another chart showing the number of assigned nurses to each department over time.

```
-24
```

## Summary¶

You learned how to set up and use IBM Decision Optimization CPLEX Modeling for Python to formulate a Mathematical Programming model and solve it with IBM Decision Optimization on Cloud.

## References¶

- CPLEX Modeling for Python documentation
- Decision Optimization on Cloud
- Need help with DOcplex or to report a bug? Please go here.
- Contact us at dofeedback@wwpdl.vnet.ibm.com.

Copyright � 2017-2019 IBM. IPLA licensed Sample Materials.