{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "Va6a1JliHzHO" }, "source": [ "```{index} single: AMPL; sets\n", "```\n", "```{index} single: AMPL; parameters\n", "```\n", "```{index} single: solver; highs\n", "```\n", "```{index} single: application; scheduling\n", "```\n", "```{index} Gantt charts\n", "```\n", "# Workforce shift scheduling" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "iTmwYjZcHx3X", "outputId": "f5cec05b-4c7f-4403-fb5f-555205ac9893" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Using default Community Edition License for Colab. Get yours at: https://ampl.com/ce\n", "Licensed to AMPL Community Edition License for the AMPL Model Colaboratory (https://colab.ampl.com).\n" ] } ], "source": [ "# install dependencies and select solver\n", "%pip install -q amplpy matplotlib\n", "\n", "SOLVER = \"highs\"\n", "\n", "from amplpy import AMPL, ampl_notebook\n", "\n", "ampl = ampl_notebook(\n", " modules=[\"highs\"], # modules to install\n", " license_uuid=\"default\", # license to use\n", ") # instantiate AMPL object and register magics" ] }, { "cell_type": "markdown", "metadata": { "id": "e1gXyjsjuJjg" }, "source": [ "## Problem Statement\n", "\n", "An article entitled [\"Modeling and optimization of a weekly workforce with Python and Pyomo\"](https://towardsdatascience.com/modeling-and-optimization-of-a-weekly-workforce-with-python-and-pyomo-29484ba065bb) by [Christian Carballo Lozano](https://medium.com/@ccarballolozano) posted on the [Towards Data Science](https://towardsdatascience.com/) blog showed how to build a Pyomo model to schedule weekly shifts for a small campus food store.\n", "\n", "From the original article:\n", "\n", "> A new food store has been opened at the University Campus which will be open 24 hours a day, 7 days a week. Each day, there are three eight-hour shifts. Morning shift is from 6:00 to 14:00, evening shift is from 14:00 to 22:00 and night shift is from 22:00 to 6:00 of the next day.\n", "> During the night there is only one worker while during the day there are two, except on Sunday that there is only one for each shift. Each worker will not exceed a maximum of 40 hours per week and have to rest for 12 hours between two shifts.\n", "> As for the weekly rest days, an employee who rests one Sunday will also prefer to do the same that Saturday.\n", "In principle, there are available ten employees, which is clearly over-sized. The less the workers are needed, the more the resources for other stores.\n", "\n", "Here we revisit the example with a new model demonstrating how to use indexed sets in AMPL, and how to use the model solution to create useful visualizations and reports for workers and managers." ] }, { "cell_type": "markdown", "metadata": { "id": "h2Z4Ll__vSyF", "tags": [] }, "source": [ "## Model formulation" ] }, { "cell_type": "markdown", "metadata": { "tags": [], "id": "8lcRNRLyTt29" }, "source": [ "### Model sets\n", "\n", "This problem requires assignment of an unspecified number of workers to a predetermined set of shifts. There are three shifts per day, seven days per week. These observations suggest the need for three ordered sets:\n", "\n", "* `WORKERS` with $N$ elements representing workers. $N$ is as input to a function creating an instance of the model.\n", "\n", "* `DAYS` with labeling the days of the week.\n", "\n", "* `SHIFTS` labeling the shifts each day.\n", "\n", "The problem describes additional considerations that suggest the utility of several additional sets.\n", "\n", "* `SLOTS` is an ordered set of (day, shift) pairs describing all of the available shifts during the week.\n", "\n", "* `BLOCKS` is an ordered set of all overlapping 24 hour periods in the week. An element of the set contains the (day, shift) period in the corresponding period. This set will be used to limit worker assignments to no more than one for each 24 hour period.\n", "\n", "* `WEEKENDS` is a the set of all (day, shift) pairs on a weekend. This set will be used to implement worker preferences on weekend scheduling.\n", "\n", "These additional sets improve the readability of the model.\n", "\n", "$$\n", "\\begin{align*}\n", "\\text{WORKERS} & = \\{w_1, w_2, \\ldots, w_1\\} \\text{ set of all workers} \\\\\n", "\\text{DAYS} & = \\{\\text{Mon}, \\text{Tues}, \\ldots, \\text{Sun}\\} \\text{ days of the week} \\\\\n", "\\text{SHIFTS} & = \\{\\text{morning}, \\text{evening}, \\text{night}\\} \\text{ 8 hour daily shifts} \\\\\n", "\\text{SLOTS} & = \\text{DAYS} \\times \\text{SHIFTS} \\text{ ordered set of all (day, shift) pairs}\\\\\n", "\\text{BLOCKS} & \\subset \\text{SLOTS} \\times \\text{SLOTS} \\times \\text{SLOTS} \\text{ all 24 blocks of consecutive slots} \\\\\n", "\\text{WEEKENDS} & \\subset \\text{SLOTS} \\text{ subset of slots corresponding to weekends} \\\\\n", "\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": { "tags": [], "id": "o7xWuuXpTt2_" }, "source": [ "### Model parameters\n", "\n", "$$\n", "\\begin{align*}\n", "N & = \\text{ number of workers} \\\\\n", "\\text{WorkersRequired}_{d, s} & = \\text{ number of workers required for each day, shift pair } (d, s) \\\\\n", "\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": { "tags": [], "id": "RU7dDW2kTt3A" }, "source": [ "### Model decision variables\n", "\n", "$$\n", "\\begin{align*}\n", "\\text{assign}_{w, d, s} & = \\begin{cases}1\\quad\\text{if worker } w \\text{ is assigned to day, shift pair } (d,s)\\in \\text{SLOTS} \\\\ 0\\quad \\text{otherwise} \\end{cases} \\\\\n", "\\text{weekend}_{w} & = \\begin{cases}1\\quad\\text{if worker } w \\text{ is assigned to a weekend day, shift pair } (d,s)\\in\\text{WEEKENDS} \\\\ 0\\quad \\text{otherwise} \\end{cases} \\\\\n", "\\text{needed}_{w} & = \\begin{cases}1\\quad\\text{if worker } w \\text{ is needed during the week} \\\\ 0\\quad \\text{otherwise} \\end{cases} \\\\\n", "\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": { "tags": [], "id": "72bmC74WTt3C" }, "source": [ "### Model constraints\n", "\n", "Assign workers to each shift to meet staffing requirement.\n", "\n", "$$\\begin{align*}\n", "\\\\\n", "\\sum_{w\\in\\text{ WORKERS}} \\text{assign}_{w, d, s} & \\geq \\text{WorkersRequired}_{d, s} & \\forall (d, s) \\in \\text{SLOTS} \\\\\n", "\\end{align*}$$\n", "\n", "Assign no more than 40 hours per week to each worker.\n", "\n", "$$\\begin{align*}\n", "\\\\\n", "8\\sum_{d,s\\in\\text{ SLOTS}} \\text{assign}_{w, d, s} & \\leq 40 & \\forall w \\in \\text{WORKERS} \\\\\n", "\\\\\n", "\\end{align*}$$\n", "\n", "Assign no more than one shift in each 24 hour period.\n", "\n", "$$\\begin{align*}\n", "\\\\\n", "\\text{assign}_{w, d_1,s_1} + \\text{assign}_{w, d_2, s_2} + \\text{assign}_{w, d_3, s_3} & \\leq 1 & \\forall w \\in \\text{WORKERS} \\\\ & & \\forall ((d_1, s_1), (d_2, s_2), (d_3, s_3))\\in \\text{BLOCKS} \\\\\n", "\\\\\n", "\\end{align*}$$\n", "\n", "Do not assign any shift to a worker that is not needed during the week.\n", "\n", "$$\\begin{align*}\n", "\\\\\n", "\\sum_{d,s\\in\\text{ SLOTS}} \\text{assign}_{w,d,s} & \\leq M_{\\text{SLOTS}}\\cdot\\text{needed}_w & \\forall w\\in \\text{WORKERS} \\\\\n", "\\\\\n", "\\end{align*}$$\n", "\n", "Do not assign any weekend shift to a worker that is not needed during the weekend.\n", "\n", "$$\\begin{align*}\n", "\\\\\n", "\\sum_{d,s\\in\\text{ WEEKENDS}} \\text{assign}_{w,d,s} & \\leq M_{\\text{WEEKENDS}}\\cdot\\text{weekend}_w & \\forall w\\in \\text{WORKERS} \\\\\n", "\\\\\n", "\\end{align*}$$" ] }, { "cell_type": "markdown", "metadata": { "tags": [], "id": "ePCgmPoITt3E" }, "source": [ "### Model objective\n", "\n", "The model objective is to minimize the overall number of workers needed to fill the shift and work requirements while also attempting to meet worker preferences regarding weekend shift assignments. This is formulated here as an objective for minimizing a weighted sum of the number of workers needed to meet all shift requirements and the number of workers assigned to weekend shifts. The positive weight $\\gamma$ determines the relative importance of these two measures of a desirable shift schedule.\n", "\n", "$$\n", "\\begin{align*}\n", "\\min \\left(\\sum_{w\\in\\text{ WORKERS}} \\text{needed}_w + \\gamma\\sum_{w\\in\\text{ WORKERS}} \\text{weekend}_w)\n", "\\right)\\end{align*}\n", "$$" ] }, { "cell_type": "markdown", "metadata": { "id": "JbhSiHcox4Ef" }, "source": [ "## AMPL implementation" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "id": "agrRjS5rTt3G", "outputId": "677579e6-ce93-4037-e63b-180aff3160d4", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "Overwriting shift_schedule.mod\n" ] } ], "source": [ "%%writefile shift_schedule.mod\n", "\n", "# sets\n", "# ordered set of avaiable workers\n", "set WORKERS ordered;\n", "# ordered sets of days and shifts\n", "set DAYS ordered;\n", "set SHIFTS ordered;\n", "# set of day, shift time slots\n", "set SLOTS within {DAYS, SHIFTS};\n", "# set of 24 hour time blocks\n", "set BLOCKS within {SLOTS, SLOTS, SLOTS};\n", "# set of weekend shifts\n", "set WEEKENDS within SLOTS;\n", "\n", "# parameters\n", "# number of workers required for each slot\n", "param WorkersRequired{SLOTS};\n", "# max hours per week per worker\n", "param Hours;\n", "# weight of the weekend component in the objective function\n", "param gamma default 0.1;\n", "\n", "# variables\n", "# assign[worker, day, shift] = 1 assigns worker to a time slot\n", "var assign{WORKERS, SLOTS} binary;\n", "# weekend[worker] = 1 worker is assigned weekend shift\n", "var weekend{WORKERS} binary;\n", "# needed[worker] = 1\n", "var needed{WORKERS} binary;\n", "\n", "# constraints\n", "# assign a sufficient number of workers for each time slot\n", "s.t. required_workers {(day, shift) in SLOTS}:\n", " WorkersRequired[day, shift] == sum{worker in WORKERS} assign[worker, day, shift];\n", "# workers limited to forty hours per week assuming 8 hours per shift\n", "s.t. forty_hour_limit {worker in WORKERS}:\n", " 8 * sum{(day, shift) in SLOTS} assign[worker, day, shift] <= Hours;\n", "# workers are assigned no more than one time slot per 24 time block\n", "s.t. required_rest {worker in WORKERS, (d1, s1, d2, s2, d3, s3) in BLOCKS}:\n", " assign[worker, d1, s1] + assign[worker, d2, s2] + assign[worker, d3, s3] <= 1;\n", "# determine if a worker is assigned to any shift\n", "s.t. is_needed {worker in WORKERS}:\n", " sum{(day, shift) in SLOTS} assign[worker, day, shift] <= card(SLOTS) * needed[worker];\n", "# determine if a worker is assigned to a weekend shift\n", "s.t. is_weekend {worker in WORKERS}:\n", " 6 * weekend[worker] >= sum{(day, shift) in WEEKENDS} assign[worker, day, shift];\n", "\n", "# choose(uncomment) one of the following objective functions\n", "\n", "# minimize a blended objective of needed workers and needed weekend workers\n", "#minimize minimize_workers:\n", " #sum{worker in WORKERS} needed[worker] + gamma * sum{worker in WORKERS} weekend[worker];\n", "\n", "# weighted version: since we are minimizing the objective function a smaller weight will give higher\n", "# priority to a given worker\n", "# weight is obtained with the ord function, that returns the index of a given worker in the weights SET\n", "minimize minimize_workers:\n", " sum{worker in WORKERS} ord(worker) * needed[worker] +\n", " gamma * sum{worker in WORKERS} ord(worker) * weekend[worker];" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "id": "4Vvt0azzH0J7", "outputId": "23605dba-1dfc-42c8-eae7-979884232cc5", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "HiGHS 1.5.3: \b\b\b\b\b\b\b\b\b\b\b\b\bHiGHS 1.5.3: optimal solution; objective 29.5\n", "12934 simplex iterations\n", "7 branching nodes\n", " \n" ] } ], "source": [ "def shift_schedule(N=10, hours=40):\n", " \"\"\"return a solved model assigning N workers to shifts\"\"\"\n", "\n", " workers = [f\"W{i:02d}\" for i in range(1, N + 1)]\n", " days = [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]\n", " shifts = [\"morning\", \"evening\", \"night\"]\n", " slots = [(d, s) for d in days for s in shifts]\n", " blocks = [(slots[i] + slots[i + 1] + slots[i + 2]) for i in range(len(slots) - 2)]\n", " weekends = [(d, s) for (d, s) in slots if d in [\"Sat\", \"Sun\"]]\n", " workers_required = {\n", " (d, s): (1 if s in [\"night\"] or d in [\"Sun\"] else 2)\n", " for d in days\n", " for s in shifts\n", " }\n", "\n", " m = AMPL()\n", " m.read(\"shift_schedule.mod\")\n", "\n", " m.set[\"WORKERS\"] = workers\n", " m.set[\"DAYS\"] = days\n", " m.set[\"SHIFTS\"] = shifts\n", " m.set[\"SLOTS\"] = slots\n", " m.set[\"BLOCKS\"] = blocks\n", " m.set[\"WEEKENDS\"] = weekends\n", " m.param[\"WorkersRequired\"] = workers_required\n", " m.param[\"Hours\"] = hours\n", "\n", " m.option[\"solver\"] = SOLVER\n", " m.solve()\n", "\n", " return m\n", "\n", "\n", "m = shift_schedule(10, 40)" ] }, { "cell_type": "markdown", "metadata": { "id": "s9A7zkSAhC8-" }, "source": [ "## Visualizing the solution\n", "\n", "Scheduling applications generate a considerable amount of data to be used by the participants. The following cells demonstrate the preparation of charts and reports that can be used to communicate scheduling information to the store management and shift workers." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 410 }, "id": "Gk8SAHcPPbXo", "outputId": "d8a400a4-37b4-4232-e0f9-6c805efba8d5" }, "outputs": [ { "output_type": "display_data", "data": { "text/plain": [ "
" ], "image/png": "iVBORw0KGgoAAAANSUhEUgAAA/IAAAGJCAYAAAApGAgTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB4lElEQVR4nO3de3yT9d3/8VfSpOn5SGkp9ECpnGQgoLPVSRqUwa2bbODt9J6bB6a3DtyUezq5FRV14nSDnXD83BjqnJvzcHscTsUEkZZyEHCgIpQChUKh56ZN06bN7w8kmnJocU3TC97PxyMP6LffXPlc8O736idXcsXk9/v9iIiIiIiIiIghmMNdgIiIiIiIiIj0nBp5EREREREREQNRIy8iIiIiIiJiIGrkRURERERERAxEjbyIiIiIiIiIgaiRFxERERERETEQNfIiIiIiIiIiBqJGXkRERERERMRA1MiLiIiIiIiIGIgaeRERkX7IZDIxZ86cbuc9+eSTmEwmdu/eHTT+2GOPkZeXR0REBOecc05oijwFubm5fOMb3wj54+zevRuTycSTTz75pe5vMpm4//77e7UmERGR3qZGXkREpA/961//4oorriAnJ4eoqCgGDx7MlClT+O1vf9trj/HWW29x5513cuGFF7J8+XIefvhhKisruf/++9m8eXO/qlVEREROnSXcBYiIiJwpiouLcTgcZGdnc+ONN5KRkUFFRQVr167l17/+Nbfeeuspb/N73/seV111FTabLTD27rvvYjabWbZsGZGRkQBs2LCBBQsWkJub26Mz9KGoVURERHqHGnkREZE+8rOf/YzExETWr19PUlJS0PcOHTr0pbYZERFBRETEMduKjo4ONPFfRihqFRERkd6hl9aLiIj0kbKyMs4+++xjGmOAgQMHHvc+L7/8MmPGjMFms3H22Wfz5ptvBn2/63vkTSYTy5cvp7m5GZPJFHi/+HnnnQfA9ddfHzTeW7U+88wzfPWrXyUmJobk5GQmTZrEW2+9dcy8999/n69+9atERUWRl5fH008/fcyc+vp6brvtNrKysrDZbOTn5/Pzn/+czs7OY+Zdd911JCYmkpSUxLXXXkt9ff0x2ysqKqKoqOiY8euuu47c3NwT/hsctX//fm644QbS09MD/w9/+tOfur2fiIhIqKiRFxER6SM5OTls3LiRrVu39mj++++/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6DgwR4S7nB75oNnMq42x/OegatJjOsJdTreUidAyWh5AmQg1ZSL0lInQMloeQJkINWUi9JSJ0DJaHgDeP+hm+eIfB/rRE1EjfwJHX06fkJDIwIEpYa6me96mDjp9fgYMsGExW8NdTrdaIk2An7SERLAYI4YJtZ2YvRaSk1MYmNj/326hTISW0fIAykSoKROhp0yEltHyAMpEqCkToadMhJbR8gAQ03LkCZLu3t7d/59GEREREREREZEANfIiIiIiIiIiBqJGXkRERERERMRA1MiLiIiIiIiIGIgaeREREREREREDUSMvIiIiIiIiYiBq5EVEREREREQMRI28iIiIiIiIiIGErZFfunQp8fHx+Hy+wJjb7cZqtVJUVBQ01+VyYTKZKCsr44knnqCoqIiEhARMJhP19fXHbLu2tpbvfve7JCQkkJSUxKxZs3C73SHeIxEREREREZHQC1sj73A4cLvdbNiwITC2evVqMjIyKC0tpbW1NTDudDrJzs5m2LBhtLS0MG3aNP73f//3hNv+7ne/y7Zt23j77bd5/fXXee+997jppptCuj8iIiIiIiIifSFsjfyIESMYNGgQLpcrMOZyuZg+fTpDhw5l7dq1QeMOhwOA2267jbvuuouCgoLjbvfjjz/mzTff5I9//CPnn38+X/va1/jtb3/L3/72NyorK0O6TyIiIiIiIiKhFtb3yDscDpxOZ+Brp9NJUVERdrs9MO7xeCgtLQ008t0pKSkhKSmJc889NzB2ySWXYDabKS0tPeH9vF4vjY2NQTcRERERERGR/ibsjfyaNWvw+Xw0NTWxadMm7HY7kyZNCpypLykpwev19riRP3jwIAMHDgwas1gspKSkcPDgwRPeb+HChSQmJgZuWVlZX3q/REREREREREIlrI18UVERzc3NrF+/ntWrVzN8+HDS0tKw2+2B98m7XC7y8vLIzs4OaS3z5s2joaEhcKuoqAjp44mIiIiIiIh8GZZwPnh+fj5DhgzB6XRSV1eH3W4HIDMzk6ysLIqLi3E6nUyePLnH28zIyODQoUNBYz6fj9raWjIyMk54P5vNhs1m+3I7IiIiIiIiItJHwv458g6HA5fLhcvlCvrYuUmTJrFixQrWrVvX45fVAxQWFlJfX8/GjRsDY++++y6dnZ2cf/75vVm6iIiIiIiISJ8L6xl5ONLIz549m/b29sAZeQC73c6cOXNoa2sLauQPHjzIwYMH2blzJwD/+te/iI+PJzs7m5SUFEaNGsW0adO48cYbWbp0Ke3t7cyZM4errrqKzMzMPt8/ERERERERkd7UL87Iezwe8vPzSU9PD4zb7XaampoCH1N31NKlSxk/fjw33ngjcOTM/fjx43n11VcDc/7yl78wcuRILr74Yi699FK+9rWv8cQTT/TdTomIiIiIiIiESNjPyOfm5uL3+48Zz8nJOe74/fffz/3333/SbaakpPDss8/2VokiIiIiIiIi/UbYz8iLiIiIiIiISM+pkRcRERERERExEDXyIiIiIiIiIgaiRl5ERERERETEQNTIi4iIiIiIiBiIGnkRERERERERA1EjLyIiIiIiImIgauRFREREREREDMQS7gL6u4p6L2ZzQ7jL6Nahtgjo9IG3E7OpPdzldOuwJwKaoM7TABZjPJ9U0WIFEiivbqapqSPc5XRLmQgto+UBlIlQUyZCT5kILaPlAZSJUFMmQk+ZCC2j5QHgoLutR/PUyHdjd3k5Nbtbw11Gt1osCeD3cqizCbMBXmjRYkkEL8S0e8Dc/+sFqIkZBOnnsP2T7ezt6P9P7igToWW0PIAyEWrKROgpE6FltDyAMhFqykToKROhZbQ8AFRZkns0T418N845azBnDYwNdxndqvSY8XfUMTJhEBazNdzldGt/ixXq/AyOiQaLMWK4qTmSyhYo/EoeGTH9/xlTZSK0jJYHUCZCTZkIPWUitIyWB1AmQk2ZCD1lIrSMlgeA96taWd+DecbYmzCKj4sndUBKuMvoVktTB50+H2kDbIb4oWppMEGnn7SERMP8UMWbOqEFEhOTSE00hbucbikToWW0PIAyEWrKROgpE6FltDyAMhFqykToKROhZbQ8AMQ01fRonjFeXyAiIiIiIiIigBp5EREREREREUNRIy8iIiIiIiJiIGrkRURERERERAxEjbyIiIiIiIiIgaiRFxERERERETEQNfIiIiIiIiIiBqJGXkRERERERMRAwtbIL126lPj4eHw+X2DM7XZjtVopKioKmutyuTCZTJSVldHa2srs2bNJTU0lLi6OmTNnUlVVFTR/5cqVXHDBBcTHx5ORkcFPf/rToMcRERERERERMaqwNfIOhwO3282GDRsCY6tXryYjI4PS0lJaW1sD406nk+zsbIYNG8btt9/Oa6+9xvPPP8+qVauorKxkxowZgblbtmzh0ksvZdq0aWzatInnnnuOV199lbvuuqtP909EREREREQkFMLWyI8YMYJBgwbhcrkCYy6Xi+nTpzN06FDWrl0bNO5wOGhoaGDZsmUsWrSIyZMnM3HiRJYvX05xcXFg/nPPPcfYsWO59957yc/Px2638+ijj7JkyRKampr6ejdFREREREREelVY3yPvcDhwOp2Br51OJ0VFRdjt9sC4x+OhtLQUh8PBxo0baW9v55JLLgncZ+TIkWRnZ1NSUgKA1+slKioq6HGio6NpbW1l48aNJ6zF6/XS2NgYdBMRERERERHpb8LeyK9Zswafz0dTUxObNm3CbrczadKkwJn6kpISvF4vDoeDgwcPEhkZSVJSUtB20tPTOXjwIABTp06luLiYv/71r3R0dLB//34eeOABAA4cOHDCWhYuXEhiYmLglpWVFZJ9FhEREREREfl3hLWRLyoqorm5mfXr17N69WqGDx9OWloadrs98D55l8tFXl4e2dnZPdrm17/+dR577DFuvvlmbDYbw4cP59JLLwXAbD7x7s6bN4+GhobAraKiolf2UURERERERKQ3hbWRz8/PZ8iQITidTpxOJ3a7HYDMzEyysrIoLi7G6XQyefJkADIyMmhra6O+vj5oO1VVVWRkZAS+njt3LvX19ezdu5fq6mqmT58OQF5e3glrsdlsJCQkBN1ERERERERE+puwf468w+HA5XLhcrmCPnZu0qRJrFixgnXr1uFwOACYOHEiVquVlStXBuZt376dvXv3UlhYGLRdk8lEZmYm0dHR/PWvfyUrK4sJEyb0yT6JiIiIiIiIhIol3AU4HA5mz55Ne3t74Iw8gN1uZ86cObS1tQUa+cTERGbNmsXcuXNJSUkhISGBW2+9lcLCQgoKCgL3feyxx5g2bRpms5mXXnqJRx55hL///e9ERET0+f6JiIiIiIiI9KZ+0ch7PB5GjhxJenp6YNxut9PU1BT4mLqjFi9ejNlsZubMmXi9XqZOncrjjz8etM0VK1bws5/9DK/Xy7hx43jllVf4j//4jz7bJxEREREREZFQCXsjn5ubi9/vP2Y8JyfnuONRUVEsWbKEJUuWnHCb7777bq/WKCIiIiIiItJfhP098iIiIiIiIiLSc2rkRURERERERAxEjbyIiIiIiIiIgaiRFxERERERETEQ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AY888ghDhw7l0KFD3HPPPeEu2zB0Rv4Ml5CQENSQfdEjjzzCzJkz+d73vseECRPYuXMn//znP0lOTu7jKiWcRo0axeOPP86SJUsYN24c69atO+bqsT/4wQ/44x//yPLly/nKV76C3W7nySef1Bl5g3I4HHg8HvLz80lPTw+M2+12mpqaAh9TB/Dggw8yf/58Fi5cyKhRo5g2bRpvvPFG4P8+OzubF198kZdffplx48axdOlSHn744bDsl4Tf1KlTmT9/PnfeeSfnnXceTU1NfP/73w93WSLSi+Li4jj//PNZvHgxkyZNYsyYMcyfP58bb7yR3/3ud4wbN45Fixbx85//nDFjxvCXv/yFhQsXBm3jggsu4Oabb+Y73/kOaWlpPProo2HaG/l3dJcFOPLJWT6fj4kTJ3Lbbbfx0EMPhblq4zD5u75ZTURERERERET6LZ2RFxERERERETEQNfIiIiIiIiIiBqJGXkRERERERMRA1MiLiIiIiIiIGIgaeREREREREREDUSMvIiIiIiIiYiBq5EVEREREREQMRI28iIiIiIiIiIGokRcREZEvzWQy8fLLL5/w+y6XC5PJRH19fWDs5ZdfJj8/n4iICG677baQ1ygiInK6USMvIiIiJ3T48GFuueUWsrOzsdlsZGRkMHXqVNasWdOj+19wwQUcOHCAxMTEwNh///d/c8UVV1BRUcGDDz7Iddddx7e+9a0Q7YGIiMjpxxLuAkRERKT/mjlzJm1tbTz11FPk5eVRVVXFypUrqamp6dH9IyMjycjICHztdrs5dOgQU6dOJTMzM1Rli4iInNZMfr/fH+4iREREpP+pr68nOTkZl8uF3W4/7hyTycQf/vAH3njjDf75z38yePBgfvnLX3L55ZcDR15a73A4qKurY/PmzTgcjqD72+12Vq1aFTTmdDopKioKyT6JiIicDvTSehERETmuuLg44uLiePnll/F6vSect2DBAq688ko+/PBDLr30Ur773e9SW1t7zLwLLriA7du3A/Diiy9y4MABXn31Va688kqmTZvGgQMHOHDgABdccEHI9klEROR0oEZeREREjstisfDkk0/y1FNPkZSUxIUXXsj//u//8uGHHwbNu+6667j66qvJz8/n4Ycfxu12s27dumO2FxkZycCBAwFISUkhIyODhIQEoqOjA++/z8jIIDIysk/2T0RExKjUyIuIiMgJzZw5k8rKSl599VWmTZuGy+ViwoQJPPnkk4E5Y8eODfw9NjaWhIQEDh06FIZqRUREzgxq5EVEROSkoqKimDJlCvPnz6e4uJjrrruO++67L/B9q9UaNN9kMtHZ2dnXZYqIiJwx1MiLiIjIKRk9ejTNzc29tr3IyEg6Ojp6bXsiIiKnOzXyIiIiclw1NTVMnjyZZ555hg8//JDy8nKef/55Hn30UaZPn95rj5Obm8uHH37I9u3bqa6upr29vde2LSIicjrS58iLiIjIccXFxXH++eezePFiysrKaG9vJysrixtvvJH//d//7bXHufHGG3G5XJx77rm43W59/JyIiEg39DnyIiIiIiIiIgail9aLiIiIiIiIGIgaeREREREREREDUSMvIiIiIiIiYiBq5EVEREREREQMRI28iIiIiIiIiIGokRcRERERERExEDXyIiIiIiIiIgaiRl5ERERERETEQNTIi4iIiIiIiBiIGnkRERERERERA1EjLyIiIiIiImIg/x93DbWPqBTAPgAAAABJRU5ErkJggg==\n" }, "metadata": {} } ], "source": [ "import matplotlib.pyplot as plt\n", "from matplotlib.patches import Rectangle\n", "\n", "\n", "def visualize(m):\n", " workers = m.set[\"WORKERS\"].to_list()\n", " slots = m.set[\"SLOTS\"].to_list()\n", " days = m.set[\"DAYS\"].to_list()\n", "\n", " assign = m.var[\"assign\"].to_dict()\n", " needed = m.var[\"needed\"].to_dict()\n", " weekend = m.var[\"weekend\"].to_dict()\n", "\n", " bw = 1.0\n", " fig, ax = plt.subplots(1, 1, figsize=(12, 1 + 0.3 * len(workers)))\n", " ax.set_title(\"Shift Schedule\")\n", "\n", " # x axis styling\n", " ax.set_xlim(0, len(slots))\n", " colors = [\"teal\", \"gold\", \"magenta\"]\n", " for i in range(len(slots) + 1):\n", " ax.axvline(i, lw=0.3)\n", " ax.fill_between(\n", " [i, i + 1], [0] * 2, [len(workers)] * 2, alpha=0.1, color=colors[i % 3]\n", " )\n", " for i in range(len(days) + 1):\n", " ax.axvline(3 * i, lw=1)\n", " ax.set_xticks([3 * i + 1.5 for i in range(len(days))])\n", " ax.set_xticklabels(days)\n", " ax.set_xlabel(\"Shift\")\n", "\n", " # y axis styling\n", " ax.set_ylim(0, len(workers))\n", " for j in range(len(workers) + 1):\n", " ax.axhline(j, lw=0.3)\n", " ax.set_yticks([j + 0.5 for j in range(len(workers))])\n", " ax.set_yticklabels(workers)\n", " ax.set_ylabel(\"Worker\")\n", "\n", " # show shift assignments\n", " for i, slot in enumerate(slots):\n", " day, shift = slot\n", " for j, worker in enumerate(workers):\n", " if round(assign[worker, day, shift]):\n", " ax.add_patch(Rectangle((i, j + (1 - bw) / 2), 1, bw, edgecolor=\"b\"))\n", " ax.text(\n", " i + 1 / 2, j + 1 / 2, worker, ha=\"center\", va=\"center\", color=\"w\"\n", " )\n", "\n", " # display needed and weekend data\n", " for j, worker in enumerate(workers):\n", " if not needed[worker]:\n", " ax.fill_between(\n", " [0, len(slots)], [j, j], [j + 1, j + 1], color=\"k\", alpha=0.3\n", " )\n", " if needed[worker] and not weekend[worker]:\n", " ax.fill_between(\n", " [15, len(slots)], [j, j], [j + 1, j + 1], color=\"k\", alpha=0.3\n", " )\n", "\n", "\n", "visualize(m)" ] }, { "cell_type": "markdown", "metadata": { "id": "msPIb5ziTt3M" }, "source": [ "## Implementing the Schedule with Reports\n", "\n", "Optimal planning models can generate large amounts of data that need to be summarized and communicated to individuals for implementation.\n", "\n", "### Creating a master schedule with categorical data\n", "\n", "The following cell creates a pandas DataFrame comprising all active assignments from the solved model. The data consists of all (worker, day, shift) tuples for which the binary decision variable m.assign equals one. \n", "\n", "The data is categorical consisting of a unique id for each worker, a day of the week, or the name of a shift. Each of the categories has a natural ordering that should be used in creating reports. This is implemented using the `CategoricalDtype` class." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "id": "67ayTwsSTt3N", "outputId": "007807b7-f45e-4fbf-9a8a-5b153dde1bc1", "colab": { "base_uri": "https://localhost:8080/", "height": 1000 } }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ " worker day shift\n", "1 W01 Mon morning\n", "8 W02 Mon morning\n", "42 W06 Mon morning\n", "49 W07 Mon morning\n", "7 W02 Mon evening\n", "16 W03 Mon evening\n", "31 W05 Mon evening\n", "41 W06 Mon evening\n", "24 W04 Mon night\n", "32 W05 Mon night\n", "28 W04 Tue morning\n", "46 W06 Tue morning\n", "51 W07 Tue morning\n", "5 W01 Tue evening\n", "12 W02 Tue evening\n", "38 W05 Tue night\n", "22 W03 Wed morning\n", "47 W06 Wed morning\n", "52 W07 Wed morning\n", "6 W01 Wed evening\n", "13 W02 Wed evening\n", "23 W03 Wed night\n", "29 W04 Wed night\n", "37 W05 Thu morning\n", "45 W06 Thu morning\n", "4 W01 Thu evening\n", "11 W02 Thu evening\n", "50 W07 Thu evening\n", "21 W03 Thu night\n", "27 W04 Thu night\n", "14 W03 Fri morning\n", "30 W05 Fri morning\n", "0 W01 Fri evening\n", "39 W06 Fri evening\n", "48 W07 Fri evening\n", "15 W03 Fri night\n", "40 W06 Fri night\n", "17 W03 Sat morning\n", "25 W04 Sat morning\n", "34 W05 Sat morning\n", "43 W06 Sat morning\n", "2 W01 Sat evening\n", "9 W02 Sat evening\n", "33 W05 Sat evening\n", "35 W05 Sat night\n", "19 W03 Sun morning\n", "26 W04 Sun morning\n", "3 W01 Sun evening\n", "18 W03 Sun evening\n", "44 W06 Sun evening\n", "10 W02 Sun night\n", "20 W03 Sun night\n", "36 W05 Sun night" ], "text/html": [ "\n", "
\n", "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
workerdayshift
1W01Monmorning
8W02Monmorning
42W06Monmorning
49W07Monmorning
7W02Monevening
16W03Monevening
31W05Monevening
41W06Monevening
24W04Monnight
32W05Monnight
28W04Tuemorning
46W06Tuemorning
51W07Tuemorning
5W01Tueevening
12W02Tueevening
38W05Tuenight
22W03Wedmorning
47W06Wedmorning
52W07Wedmorning
6W01Wedevening
13W02Wedevening
23W03Wednight
29W04Wednight
37W05Thumorning
45W06Thumorning
4W01Thuevening
11W02Thuevening
50W07Thuevening
21W03Thunight
27W04Thunight
14W03Frimorning
30W05Frimorning
0W01Frievening
39W06Frievening
48W07Frievening
15W03Frinight
40W06Frinight
17W03Satmorning
25W04Satmorning
34W05Satmorning
43W06Satmorning
2W01Satevening
9W02Satevening
33W05Satevening
35W05Satnight
19W03Sunmorning
26W04Sunmorning
3W01Sunevening
18W03Sunevening
44W06Sunevening
10W02Sunnight
20W03Sunnight
36W05Sunnight
\n", "
\n", "
\n", "\n", "
\n", " \n", "\n", " \n", "\n", " \n", "
\n", "\n", "\n", "
\n", " \n", "\n", "\n", "\n", " \n", "
\n", "
\n", "
\n" ] }, "metadata": {}, "execution_count": 5 } ], "source": [ "import pandas as pd\n", "\n", "# get the schedule from AMPL and convert to pandas DataFrame with defined columns\n", "schedule = m.var[\"assign\"].to_list()\n", "schedule = pd.DataFrame(\n", " [[s[0], s[1], s[2]] for s in schedule if s[3]], columns=[\"worker\", \"day\", \"shift\"]\n", ")\n", "\n", "# create and assign a worker category type\n", "worker_type = pd.CategoricalDtype(\n", " categories=m.set[\"WORKERS\"].to_list(), ordered=True\n", ")\n", "schedule[\"worker\"] = schedule[\"worker\"].astype(worker_type)\n", "\n", "# create and assign a day category type\n", "day_type = pd.CategoricalDtype(\n", " categories=m.set[\"DAYS\"].to_list(), ordered=True\n", ")\n", "schedule[\"day\"] = schedule[\"day\"].astype(day_type)\n", "\n", "# create and assign a shift category type\n", "shift_type = pd.CategoricalDtype(\n", " categories=m.set[\"SHIFTS\"].to_list(), ordered=True\n", ")\n", "schedule[\"shift\"] = schedule[\"shift\"].astype(shift_type)\n", "\n", "# demonstrate sorting and display of the master schedule\n", "schedule.sort_values(by=[\"day\", \"shift\", \"worker\"])" ] }, { "cell_type": "markdown", "metadata": { "id": "mRyjL5-ATt3O" }, "source": [ "### Reports for workers\n", "\n", "Each worker should receive a report detailing their shift assignments. The reports are created by sorting the master schedule by worker, day, and shift, then grouping by worker." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "id": "Nu8SaY_RTt3O", "outputId": "9c098cbe-ba9a-4a19-ce21-cf42a5388d11", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "\n", " Work schedule for W01\n", "worker day shift\n", " W01 Mon morning\n", " W01 Tue evening\n", " W01 Wed evening\n", " W01 Thu evening\n", " W01 Fri evening\n", " W01 Sat evening\n", " W01 Sun evening\n", "\n", " Work schedule for W02\n", "worker day shift\n", " W02 Mon morning\n", " W02 Mon evening\n", " W02 Tue evening\n", " W02 Wed evening\n", " W02 Thu evening\n", " W02 Sat evening\n", " W02 Sun night\n", "\n", " Work schedule for W03\n", "worker day shift\n", " W03 Mon evening\n", " W03 Wed morning\n", " W03 Wed night\n", " W03 Thu night\n", " W03 Fri morning\n", " W03 Fri night\n", " W03 Sat morning\n", " W03 Sun morning\n", " W03 Sun evening\n", " W03 Sun night\n", "\n", " Work schedule for W04\n", "worker day shift\n", " W04 Mon night\n", " W04 Tue morning\n", " W04 Wed night\n", " W04 Thu night\n", " W04 Sat morning\n", " W04 Sun morning\n", "\n", " Work schedule for W05\n", "worker day shift\n", " W05 Mon evening\n", " W05 Mon night\n", " W05 Tue night\n", " W05 Thu morning\n", " W05 Fri morning\n", " W05 Sat morning\n", " W05 Sat evening\n", " W05 Sat night\n", " W05 Sun night\n", "\n", " Work schedule for W06\n", "worker day shift\n", " W06 Mon morning\n", " W06 Mon evening\n", " W06 Tue morning\n", " W06 Wed morning\n", " W06 Thu morning\n", " W06 Fri evening\n", " W06 Fri night\n", " W06 Sat morning\n", " W06 Sun evening\n", "\n", " Work schedule for W07\n", "worker day shift\n", " W07 Mon morning\n", " W07 Tue morning\n", " W07 Wed morning\n", " W07 Thu evening\n", " W07 Fri evening\n", "\n", " Work schedule for W08\n", " no assigned shifts\n", "\n", " Work schedule for W09\n", " no assigned shifts\n", "\n", " Work schedule for W10\n", " no assigned shifts\n" ] } ], "source": [ "# sort schedule by worker\n", "schedule = schedule.sort_values(by=[\"worker\", \"day\", \"shift\"])\n", "\n", "# print worker schedules\n", "for worker, worker_schedule in schedule.groupby(\"worker\"):\n", " print(f\"\\n Work schedule for {worker}\")\n", " if len(worker_schedule) > 0:\n", " for s in worker_schedule.to_string(index=False).split(\"\\n\"):\n", " print(s)\n", " else:\n", " print(\" no assigned shifts\")" ] }, { "cell_type": "markdown", "metadata": { "id": "8wXotzxwTt3P" }, "source": [ "### Reports for store managers\n", "\n", "The store managers need reports listing workers by assigned day and shift." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "id": "S709cNyYTt3P", "outputId": "e223ef4f-1b3d-4e2d-9d44-47b8b5289c79", "colab": { "base_uri": "https://localhost:8080/" } }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "\n", "Shift schedule for Mon\n", " morning shift: W01, W02, W06, W07\n", " evening shift: W02, W03, W05, W06\n", " night shift: W04, W05\n", "\n", "Shift schedule for Tue\n", " morning shift: W04, W06, W07\n", " evening shift: W01, W02\n", " night shift: W05\n", "\n", "Shift schedule for Wed\n", " morning shift: W03, W06, W07\n", " evening shift: W01, W02\n", " night shift: W03, W04\n", "\n", "Shift schedule for Thu\n", " morning shift: W05, W06\n", " evening shift: W01, W02, W07\n", " night shift: W03, W04\n", "\n", "Shift schedule for Fri\n", " morning shift: W03, W05\n", " evening shift: W01, W06, W07\n", " night shift: W03, W06\n", "\n", "Shift schedule for Sat\n", " morning shift: W03, W04, W05, W06\n", " evening shift: W01, W02, W05\n", " night shift: W05\n", "\n", "Shift schedule for Sun\n", " morning shift: W03, W04\n", " evening shift: W01, W03, W06\n", " night shift: W02, W03, W05\n" ] } ], "source": [ "# sort by day, shift, worker\n", "schedule = schedule.sort_values(by=[\"day\", \"shift\", \"worker\"])\n", "\n", "for day, day_schedule in schedule.groupby(\"day\"):\n", " print(f\"\\nShift schedule for {day}\")\n", " for shift, shift_schedule in day_schedule.groupby(\"shift\"):\n", " print(f\" {shift} shift: \", end=\"\")\n", " print(\", \".join([worker for worker in shift_schedule[\"worker\"].values]))" ] }, { "cell_type": "markdown", "metadata": { "id": "s4nX2GyHnyX6" }, "source": [ "## Suggested Exercises\n", "\n", "1. How many workers will be required to operate the food store if all workers are limited to four shifts per week, i.e., 32 hours? How about 3 shifts or 24 hours per week?\n", "\n", "2. Add a second class of workers called \"manager\". There needs to be one manager on duty for every shift, that morning shifts require 3 staff on duty, evening shifts 2,\n", "and night shifts 1.\n", "\n", "3. Add a third class of workers called \"part_time\". Part time workers are limited to no more than 30 hours per week.\n", "\n", "4. Modify the problem formulation and objective to spread the shifts out amongst all workers, attempting to equalize the total number of assigned shifts, and similar numbers of day, evening, night, and weekend shifts.\n", "\n", "5. Find the minimum cost staffing plan assuming managers cost 30 euros per hour + 100 euros per week in fixed benefits, regular workers cost 20 euros per hour plus 80 euros per week in fixed benefits, and part time workers cost 15 euros per week with no benefits." ] } ], "metadata": { "colab": { "name": "Shift-Scheduling.ipynb", "provenance": [] }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.6" } }, "nbformat": 4, "nbformat_minor": 0 }