From 0ac1990cfd51746925a0c096430bedbef2537a6f Mon Sep 17 00:00:00 2001 From: Ross Date: Sat, 9 May 2020 23:48:46 +0100 Subject: [PATCH] Start rewriting nights --- .gitignore | 2 + .vscode/.ropeproject/config.py | 114 ++ rota.ipynb | 2442 ++------------------------------ run.py | 294 ++++ shifts.py | 881 ++++++++---- test.py | 198 +++ workers.py | 43 +- 7 files changed, 1358 insertions(+), 2616 deletions(-) create mode 100644 .vscode/.ropeproject/config.py create mode 100644 run.py create mode 100644 test.py diff --git a/.gitignore b/.gitignore index e9beb2d..2ef41a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ rota.csv trainees.csv sample.txt +*.csv +*.html diff --git a/.vscode/.ropeproject/config.py b/.vscode/.ropeproject/config.py new file mode 100644 index 0000000..dee2d1a --- /dev/null +++ b/.vscode/.ropeproject/config.py @@ -0,0 +1,114 @@ +# The default ``config.py`` +# flake8: noqa + + +def set_prefs(prefs): + """This function is called before opening the project""" + + # Specify which files and folders to ignore in the project. + # Changes to ignored resources are not added to the history and + # VCSs. Also they are not returned in `Project.get_files()`. + # Note that ``?`` and ``*`` match all characters but slashes. + # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc' + # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc' + # '.svn': matches 'pkg/.svn' and all of its children + # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' + # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' + prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject', + '.hg', '.svn', '_svn', '.git', '.tox'] + + # Specifies which files should be considered python files. It is + # useful when you have scripts inside your project. Only files + # ending with ``.py`` are considered to be python files by + # default. + # prefs['python_files'] = ['*.py'] + + # Custom source folders: By default rope searches the project + # for finding source folders (folders that should be searched + # for finding modules). You can add paths to that list. Note + # that rope guesses project source folders correctly most of the + # time; use this if you have any problems. + # The folders should be relative to project root and use '/' for + # separating folders regardless of the platform rope is running on. + # 'src/my_source_folder' for instance. + # prefs.add('source_folders', 'src') + + # You can extend python path for looking up modules + # prefs.add('python_path', '~/python/') + + # Should rope save object information or not. + prefs['save_objectdb'] = True + prefs['compress_objectdb'] = False + + # If `True`, rope analyzes each module when it is being saved. + prefs['automatic_soa'] = True + # The depth of calls to follow in static object analysis + prefs['soa_followed_calls'] = 0 + + # If `False` when running modules or unit tests "dynamic object + # analysis" is turned off. This makes them much faster. + prefs['perform_doa'] = True + + # Rope can check the validity of its object DB when running. + prefs['validate_objectdb'] = True + + # How many undos to hold? + prefs['max_history_items'] = 32 + + # Shows whether to save history across sessions. + prefs['save_history'] = True + prefs['compress_history'] = False + + # Set the number spaces used for indenting. According to + # :PEP:`8`, it is best to use 4 spaces. Since most of rope's + # unit-tests use 4 spaces it is more reliable, too. + prefs['indent_size'] = 4 + + # Builtin and c-extension modules that are allowed to be imported + # and inspected by rope. + prefs['extension_modules'] = [] + + # Add all standard c-extensions to extension_modules list. + prefs['import_dynload_stdmods'] = True + + # If `True` modules with syntax errors are considered to be empty. + # The default value is `False`; When `False` syntax errors raise + # `rope.base.exceptions.ModuleSyntaxError` exception. + prefs['ignore_syntax_errors'] = False + + # If `True`, rope ignores unresolvable imports. Otherwise, they + # appear in the importing namespace. + prefs['ignore_bad_imports'] = False + + # If `True`, rope will insert new module imports as + # `from import ` by default. + prefs['prefer_module_from_imports'] = False + + # If `True`, rope will transform a comma list of imports into + # multiple separate import statements when organizing + # imports. + prefs['split_imports'] = False + + # If `True`, rope will remove all top-level import statements and + # reinsert them at the top of the module when making changes. + prefs['pull_imports_to_top'] = True + + # If `True`, rope will sort imports alphabetically by module name instead + # of alphabetically by import statement, with from imports after normal + # imports. + prefs['sort_imports_alphabetically'] = False + + # Location of implementation of + # rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general + # case, you don't have to change this value, unless you're an rope expert. + # Change this value to inject you own implementations of interfaces + # listed in module rope.base.oi.type_hinting.providers.interfaces + # For example, you can add you own providers for Django Models, or disable + # the search type-hinting in a class hierarchy, etc. + prefs['type_hinting_factory'] = ( + 'rope.base.oi.type_hinting.factory.default_type_hinting_factory') + + +def project_opened(project): + """This function is called after opening the project""" + # Do whatever you like here! diff --git a/rota.ipynb b/rota.ipynb index 2e151bc..9c103af 100644 --- a/rota.ipynb +++ b/rota.ipynb @@ -1,27 +1,4 @@ { - "nbformat": 4, - "nbformat_minor": 2, - "metadata": { - "language_info": { - "name": "python", - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "version": "3.7.5-final" - }, - "orig_nbformat": 2, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "npconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": 3, - "kernelspec": { - "name": "python37564bit5ba0d1a6f89f489d9799b0f9b976a56e", - "display_name": "Python 3.7.5 64-bit" - } - }, "cells": [ { "cell_type": "code", @@ -31,24 +8,18 @@ "source": [ "from pyomo.environ import *\n", "from pyomo.opt import SolverFactory\n", - "from shifts import SingleShift, RotaBuilder, days, sites\n", + "from shifts import SingleShift, RotaBuilder, days\n", "from workers import Worker\n", "\n", "import math\n", "\n", "import scipy.stats as stats\n", "\n", - "import csv\n", - "\n", "import statistics\n", "\n", "import datetime\n", "import itertools\n", - "import operator\n", - "\n", - "from functools import reduce\n", - "def prod(iterable):\n", - " return reduce(operator.mul, iterable, 1)" + "import operator\n" ] }, { @@ -59,8 +30,8 @@ "source": [ "#night_balance_offset = 2\n", "#twilight_balance_offset = 1\n", - "weeks_to_rota = 16\n", - "use_test_workers = True" + "weeks_to_rota = 26\n", + "use_test_workers = False" ] }, { @@ -70,20 +41,29 @@ "outputs": [], "source": [ "\n", - "Rota = RotaBuilder(weeks_to_rota=weeks_to_rota)\n", + "sites = (\"truro\", \"exeter\", \"torquay\", \"barnstaple\", \"plymouth\")\n", + "\n", + "Rota = RotaBuilder(datetime.date(2020, 9, 7), weeks_to_rota=weeks_to_rota, balance_offset_modifier=1)\n", + "\n", + "rota_collections = {\n", + "\n", + " #\"current\" : RotaBuilder(datetime.date(2020, 9, 7), weeks_to_rota=weeks_to_rota, balance_offset_modifier=2)\n", + "}\n", "\n", "Rota.add_shifts(\n", " SingleShift((\"exeter\",), \"exeter_twilight\", 12.5, days[:5]),\n", " SingleShift((\"truro\",), \"truro_twilight\", 12.5, days[:5]),\n", " SingleShift((\"torquay\",), \"torquay_twilight\", 12.5, days[:5]),\n", " SingleShift((\"plymouth\",), \"plymouth_twilight\", 12.5, days[:5]), \n", - " SingleShift((\"exeter\",), \"weekend_exeter\", 12.5, days[5:]), \n", - " SingleShift((\"truro\",), \"weekend_truro\", 12.5, days[5:]), \n", - " SingleShift((\"torquay\",), \"weekend_torquay\", 12.5, days[5:]), \n", - " SingleShift((\"plymouth\",), \"weekend_plymouth\", 8, days[5:], workers_required=2), \n", - " SingleShift((sites), \"night\", 12.5, days, balance_offset=2, balance_weighting=2, workers_required=3, rota_on_nwds=True), \n", + " SingleShift((\"exeter\",), \"weekend_exeter\", 12.5, days[5:], assign_as_block=True), \n", + " SingleShift((\"truro\",), \"weekend_truro\", 12.5, days[5:], assign_as_block=True), \n", + " SingleShift((\"torquay\",), \"weekend_torquay\", 12.5, days[5:], assign_as_block=True), \n", + " SingleShift((\"plymouth\",), \"weekend_plymouth\", 8, days[5:], workers_required=2, assign_as_block=True), \n", + " #SingleShift((sites), \"night\", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), \n", + " SingleShift((sites), \"night\", 12.5, days, balance_offset=2, balance_weighting=1, workers_required=3, rota_on_nwds=True), \n", ")\n", "\n", + "\n", "#Rota2 = RotaBuilder(weeks_to_rota=weeks_to_rota)\n", "\n", "#Rota2.add_shifts(\n", @@ -94,8 +74,7 @@ "\n", "#Rota = Rota2\n", "\n", - "\n", - "" + "\n" ] }, { @@ -106,14 +85,15 @@ "source": [ "if use_test_workers:\n", " # Enter workers ids (name, number, ...)\n", - " #workers.extend([Worker(Rota, i, \"Test Person {}\".format(i), \"truro\", 3, nwd=['Fri', \"Sat\", \"Sun\"]) for i in range(1, 2)])\n", - " Rota.add_workers([Worker(Rota,i, \"Test Person {}\".format(i), \"truro\", 4, ) for i in range(1, 15)])\n", - " Rota.add_workers([Worker(Rota,i, \"Test Person {}\".format(i), \"plymouth\", 4) for i in range(15, 30)])\n", - " Rota.add_workers([Worker(Rota,i, \"Test Person {}\".format(i), \"exeter\", 4) for i in range(30, 45)])\n", - " Rota.add_workers([Worker(Rota,i, \"Test Person {}\".format(i), \"torquay\", 4) for i in range(45, 60)])\n", + " Rota.add_workers([Worker(Rota, i, \"Truro {}\".format(i), \"truro\", 3, nwd=['Fri', \"Sat\", \"Sun\"]) for i in range(1, 2)])\n", + " Rota.add_workers([Worker(Rota,i, \"Truro {}\".format(i), \"truro\", 4, pref_not_to_work=((\"2020/09/07\"),(\"2020/09/08\"),(\"2020/09/09\"),(\"2020/09/10\")) ) for i in range(2, 14)])\n", + " Rota.add_workers([Worker(Rota,i, \"Plym {}\".format(i), \"plymouth\", 4) for i in range(16, 30)])\n", + " Rota.add_workers([Worker(Rota,i, \"Exe {}\".format(i), \"exeter\", 4, pref_not_to_work=((\"2020/09/07\"),)) for i in range(30, 40)])\n", + " Rota.add_workers([Worker(Rota,i, \"Torq {}\".format(i), \"torquay\", 4, pref_not_to_work=((\"2020/09/07\"),)) for i in range(50, 60)])\n", " #workers.extend([workerClass(i, \"Test Person {}\".format(i), 4, 200, \"\") for i in range(48, 49)])\n", " \n", " #workers.extend([Worker(Rota,i, \"Test Person {}\".format(i), \"exeter\", 4) for i in range(34, 45)])\n", + " #Rota.add_worker([Worker(Rota,i, \"Test Person {}\".format(i), \"torquay\", 4, pref_not_to_work=(\"2020/09/10\",\"2020/09/7\",\"2020/09/14\",\"2020/09/23\")) for i in range(45, 46)])\n", "else:\n", " # Import trainee data\n", " import csv\n", @@ -138,17 +118,29 @@ " #print(nwds, end_date, oop)\n", " Rota.add_worker(Worker(Rota, n, name, site.lower(), int(grade[2]), int(fte), nwds, end_date, oop))\n", "\n", - "\n", - "" + "\n" ] }, { "cell_type": "code", "execution_count": 5, - "metadata": {}, - "outputs": [], + "metadata": { + "tags": [ + "outputPrepend", + "outputPrepend", + "outputPrepend" + ] + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "{'exeter', 'truro', 'plymouth', 'barnstaple', 'torquay'}\n" + } + ], "source": [ - "Rota.build_workers()\n", + "Rota.build_shifts_and_workers()\n", + "print(Rota.sites)\n", "Rota.build_model()" ] }, @@ -157,2135 +149,6 @@ "execution_count": 6, "metadata": { "tags": [ - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", - "outputPrepend", "outputPrepend", "outputPrepend", "outputPrepend", @@ -2320,14 +183,14 @@ { "output_type": "stream", "name": "stdout", - "text": "Welcome to the CBC MILP Solver \nVersion: 2.9.9 \nBuild Date: Aug 7 2019 \n\ncommand line - /usr/bin/cbc -seconds 600 -allow 25 -printingOptions all -import /tmp/tmpua24r1de.pyomo.lp -stat=1 -solve -solu /tmp/tmpua24r1de.pyomo.soln (default strategy 1)\nseconds was changed from 1e+100 to 600\nallowableGap was changed from 1e-10 to 25\nOption for printingOptions changed from normal to all\nPresolve 8525 (-7679) rows, 33158 (-28852) columns and 138886 (-135111) elements\nStatistics for presolved model\nOriginal problem has 61360 integers (61360 of which binary)\nPresolved problem has 33040 integers (33040 of which binary)\n==== 33040 zero objective 2 different\n33040 variables have objective of 0\n118 variables have objective of 1\n==== absolute objective values 2 different\n33040 variables have objective of 0\n118 variables have objective of 1\n==== for integers 33040 zero objective 1 different\n33040 variables have objective of 0\n==== for integers absolute objective values 1 different\n33040 variables have objective of 0\n===== end objective counts\n\n\nProblem has 8525 rows, 33158 columns (118 with objective) and 138886 elements\nThere are 118 singletons with objective \nColumn breakdown:\n118 of type 0.0->inf, 0 of type 0.0->up, 0 of type lo->inf, \n0 of type lo->up, 0 of type free, 0 of type fixed, \n0 of type -inf->0.0, 0 of type -inf->up, 33040 of type 0.0->1.0 \nRow breakdown:\n0 of type E 0.0, 416 of type E 1.0, 0 of type E -1.0, \n203 of type E other, 0 of type G 0.0, 0 of type G 1.0, \n177 of type G other, 0 of type L 0.0, 6608 of type L 1.0, \n1121 of type L other, 0 of type Range 0.0->1.0, 0 of type Range other, \n0 of type Free \nContinuous objective value is 0 - 0.33 seconds\nCgl0002I 26432 variables fixed\nCgl0004I processed model has 8348 rows, 21350 columns (21232 integer (21232 of which binary)) and 90246 elements\nCbc0038I Initial state - 159 integers unsatisfied sum - 34.8044\nCbc0038I Pass 1: (0.88 seconds) suminf. 9.38095 (36) obj. 40.5517 iterations 464\nCbc0038I Pass 2: (0.88 seconds) suminf. 9.38095 (36) obj. 40.5517 iterations 0\nCbc0038I Pass 3: (0.90 seconds) suminf. 5.00952 (20) obj. 48.6712 iterations 122\nCbc0038I Pass 4: (0.92 seconds) suminf. 4.34286 (19) obj. 49.0712 iterations 31\nCbc0038I Pass 5: (0.94 seconds) suminf. 2.60952 (10) obj. 51.7785 iterations 80\nCbc0038I Pass 6: (0.96 seconds) suminf. 2.60952 (10) obj. 51.7785 iterations 11\nCbc0038I Pass 7: (0.98 seconds) suminf. 2.60952 (10) obj. 52.9747 iterations 60\nCbc0038I Pass 8: (1.00 seconds) suminf. 2.60952 (10) obj. 51.1296 iterations 60\nCbc0038I Pass 9: (1.04 seconds) suminf. 27.94802 (95) obj. 192.581 iterations 537\nCbc0038I Pass 10: (1.07 seconds) suminf. 16.67684 (61) obj. 186.06 iterations 303\nCbc0038I Pass 11: (1.08 seconds) suminf. 15.67119 (61) obj. 186.941 iterations 34\nCbc0038I Pass 12: (1.10 seconds) suminf. 12.72768 (49) obj. 180.727 iterations 106\nCbc0038I Pass 13: (1.12 seconds) suminf. 12.33785 (48) obj. 179.814 iterations 48\nCbc0038I Pass 14: (1.14 seconds) suminf. 12.33785 (48) obj. 179.814 iterations 2\nCbc0038I Pass 15: (1.16 seconds) suminf. 9.84068 (39) obj. 180.049 iterations 116\nCbc0038I Pass 16: (1.18 seconds) suminf. 9.84068 (39) obj. 180.049 iterations 24\nCbc0038I Pass 17: (1.20 seconds) suminf. 7.43874 (31) obj. 180.126 iterations 58\nCbc0038I Pass 18: (1.22 seconds) suminf. 7.43874 (31) obj. 180.126 iterations 26\nCbc0038I Pass 19: (1.24 seconds) suminf. 5.72446 (25) obj. 179.71 iterations 70\nCbc0038I Pass 20: (1.26 seconds) suminf. 5.72446 (25) obj. 179.71 iterations 26\nCbc0038I Pass 21: (1.29 seconds) suminf. 5.85779 (21) obj. 179.251 iterations 110\nCbc0038I Pass 22: (1.30 seconds) suminf. 5.19112 (21) obj. 179.651 iterations 58\nCbc0038I Pass 23: (1.33 seconds) suminf. 5.59112 (21) obj. 175.843 iterations 122\nCbc0038I Pass 24: (1.34 seconds) suminf. 5.19112 (21) obj. 176.243 iterations 43\nCbc0038I Pass 25: (1.36 seconds) suminf. 5.59112 (21) obj. 176.051 iterations 85\nCbc0038I Pass 26: (1.38 seconds) suminf. 5.19112 (21) obj. 176.451 iterations 52\nCbc0038I Pass 27: (1.40 seconds) suminf. 5.59112 (21) obj. 175.843 iterations 75\nCbc0038I Pass 28: (1.42 seconds) suminf. 5.19112 (21) obj. 176.243 iterations 36\nCbc0038I Pass 29: (1.44 seconds) suminf. 5.59112 (21) obj. 177.518 iterations 118\nCbc0038I Pass 30: (1.46 seconds) suminf. 5.19112 (21) obj. 177.918 iterations 42\nCbc0038I No solution found this major pass\nCbc0038I Before mini branch and bound, 20552 integers at bound fixed and 25 continuous\nCbc0038I Full problem 8348 rows 21350 columns, reduced to 251 rows 531 columns\nCbc0038I Mini branch and bound did not improve solution (1.77 seconds)\nCbc0038I Full problem 8349 rows 21350 columns, reduced to 8349 rows 21350 columns - too large\nCbc0038I After 2.19 seconds - Feasibility pump exiting - took 1.40 seconds\nCbc0012I Integer solution of 19.715254 found by DiveCoefficient after 3604 iterations and 0 nodes (6.37 seconds)\nCbc0031I 124 added rows had average density of 258.29032\nCbc0013I At root node, 124 cuts changed objective from 0 to 10.135593 in 10 passes\nCbc0014I Cut generator 0 (Probing) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.389 seconds - new frequency is -100\nCbc0014I Cut generator 1 (Gomory) - 408 row cuts average 742.9 elements, 0 column cuts (0 active) in 0.874 seconds - new frequency is 1\nCbc0014I Cut generator 2 (Knapsack) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.049 seconds - new frequency is -100\nCbc0014I Cut generator 3 (Clique) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.007 seconds - new frequency is -100\nCbc0014I Cut generator 4 (MixedIntegerRounding2) - 322 row cuts average 223.6 elements, 0 column cuts (0 active) in 0.191 seconds - new frequency is 1\nCbc0014I Cut generator 5 (FlowCover) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.054 seconds - new frequency is -100\nCbc0014I Cut generator 6 (TwoMirCuts) - 274 row cuts average 277.7 elements, 0 column cuts (0 active) in 0.458 seconds - new frequency is 1\nCbc0011I Exiting as integer gap of 9.579661 less than 25 or 0%%\nCbc0001I Search completed - best objective 19.71525423728813, took 3604 iterations and 0 nodes (6.39 seconds)\nCbc0035I Maximum depth 0, 0 variables fixed on reduced cost\nCuts at root node changed objective from 0 to 10.1356\nProbing was tried 10 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.389 seconds)\nGomory was tried 10 times and created 408 cuts of which 0 were active after adding rounds of cuts (0.874 seconds)\nKnapsack was tried 10 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.049 seconds)\nClique was tried 10 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.007 seconds)\nMixedIntegerRounding2 was tried 10 times and created 322 cuts of which 0 were active after adding rounds of cuts (0.191 seconds)\nFlowCover was tried 10 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.054 seconds)\nTwoMirCuts was tried 10 times and created 274 cuts of which 0 were active after adding rounds of cuts (0.458 seconds)\n\nResult - Optimal solution found (within gap tolerance)\n\nObjective value: 19.71525424\nLower bound: 10.136\nGap: 0.95\nEnumerated nodes: 0\nTotal iterations: 3604\nTime (CPU seconds): 6.56\nTime (Wallclock seconds): 6.66\n\nTotal time (CPU seconds): 7.38 (Wallclock seconds): 7.51\n\n" + "text": "Welcome to the CBC MILP Solver \nVersion: 2.9.9 \nBuild Date: Aug 7 2019 \n\ncommand line - /usr/bin/cbc -seconds 3600 -allow 3500 -printingOptions all -import /tmp/tmpf5i54yvl.pyomo.lp -stat=1 -solve -solu /tmp/tmpf5i54yvl.pyomo.soln (default strategy 1)\nseconds was changed from 1e+100 to 3600\nallowableGap was changed from 1e-10 to 3500\nOption for printingOptions changed from normal to all\nPresolve 53080 (-28899) rows, 22855 (-73242) columns and 199897 (-492588) elements\nStatistics for presolved model\nOriginal problem has 95524 integers (94640 of which binary)\nPresolved problem has 22765 integers (22453 of which binary)\n==== 19020 zero objective 4 different\n19020 variables have objective of 0\n208 variables have objective of 20\n1271 variables have objective of 100\n2356 variables have objective of 1000\n==== absolute objective values 4 different\n19020 variables have objective of 0\n208 variables have objective of 20\n1271 variables have objective of 100\n2356 variables have objective of 1000\n==== for integers 19020 zero objective 4 different\n19020 variables have objective of 0\n208 variables have objective of 20\n1271 variables have objective of 100\n2266 variables have objective of 1000\n==== for integers absolute objective values 4 different\n19020 variables have objective of 0\n208 variables have objective of 20\n1271 variables have objective of 100\n2266 variables have objective of 1000\n===== end objective counts\n\n\nProblem has 53080 rows, 22855 columns (3835 with objective) and 199897 elements\nThere are 1569 singletons with objective 852 singletons with no objective \nColumn breakdown:\n402 of type 0.0->inf, 0 of type 0.0->up, 0 of type lo->inf, \n0 of type lo->up, 0 of type free, 0 of type fixed, \n0 of type -inf->0.0, 0 of type -inf->up, 22453 of type 0.0->1.0 \nRow breakdown:\n2124 of type E 0.0, 1528 of type E 1.0, 0 of type E -1.0, \n799 of type E other, 0 of type G 0.0, 52 of type G 1.0, \n334 of type G other, 1271 of type L 0.0, 45119 of type L 1.0, \n1853 of type L other, 0 of type Range 0.0->1.0, 0 of type Range other, \n0 of type Free \nContinuous objective value is 7026.09 - 11.42 seconds\nCgl0002I 67382 variables fixed\nCgl0003I 110 fixed, 208 tightened bounds, 41717 strengthened rows, 45229 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 1344 strengthened rows, 8755 substitutions\nCgl0003I 488 fixed, 0 tightened bounds, 3602 strengthened rows, 26 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 150 strengthened rows, 0 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 268 strengthened rows, 0 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 167 strengthened rows, 0 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 167 strengthened rows, 0 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 166 strengthened rows, 0 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 165 strengthened rows, 0 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 162 strengthened rows, 0 substitutions\nCgl0003I 0 fixed, 0 tightened bounds, 162 strengthened rows, 0 substitutions\nCgl0004I processed model has 13820 rows, 13726 columns (13636 integer (13428 of which binary)) and 85182 elements\nCbc0038I Initial state - 928 integers unsatisfied sum - 260.353\nCbc0038I Pass 1: (22.71 seconds) suminf. 130.17048 (361) obj. 54418.3 iterations 3281\nCbc0038I Pass 2: (22.78 seconds) suminf. 114.65515 (321) obj. 61548.2 iterations 714\nCbc0038I Pass 3: (22.82 seconds) suminf. 111.57450 (319) obj. 63556.3 iterations 146\nCbc0038I Pass 4: (22.85 seconds) suminf. 110.79486 (316) obj. 63527.3 iterations 76\nCbc0038I Pass 5: (22.87 seconds) suminf. 110.29486 (315) obj. 63577.3 iterations 4\nCbc0038I Pass 6: (22.90 seconds) suminf. 102.79486 (300) obj. 64327.3 iterations 21\nCbc0038I Pass 7: (22.92 seconds) suminf. 93.79486 (282) obj. 65227.3 iterations 24\nCbc0038I Pass 8: (22.95 seconds) suminf. 88.29486 (271) obj. 65777.3 iterations 11\nCbc0038I Pass 9: (22.97 seconds) suminf. 81.79486 (258) obj. 66427.3 iterations 18\nCbc0038I Pass 10: (23.00 seconds) suminf. 75.79486 (246) obj. 67027.3 iterations 19\nCbc0038I Pass 11: (23.03 seconds) suminf. 65.79486 (226) obj. 68027.3 iterations 38\nCbc0038I Pass 12: (23.05 seconds) suminf. 54.48204 (201) obj. 69116.3 iterations 125\nCbc0038I Pass 13: (23.08 seconds) suminf. 42.98204 (178) obj. 70266.3 iterations 29\nCbc0038I Pass 14: (23.15 seconds) suminf. 26.99681 (126) obj. 71658.8 iterations 545\nCbc0038I Pass 15: (23.18 seconds) suminf. 23.15673 (110) obj. 74088.5 iterations 62\nCbc0038I Pass 16: (23.23 seconds) suminf. 12.43064 (83) obj. 76945.6 iterations 459\nCbc0038I Pass 17: (23.27 seconds) suminf. 11.05741 (71) obj. 76158.1 iterations 220\nCbc0038I Pass 18: (23.31 seconds) suminf. 7.01292 (50) obj. 78595.2 iterations 378\nCbc0038I Pass 19: (23.34 seconds) suminf. 6.86964 (57) obj. 78751.1 iterations 89\nCbc0038I Pass 20: (23.37 seconds) suminf. 5.08719 (42) obj. 81358.4 iterations 288\nCbc0038I Pass 21: (23.40 seconds) suminf. 5.08719 (42) obj. 81358.4 iterations 4\nCbc0038I Pass 22: (23.43 seconds) suminf. 4.53125 (37) obj. 83406.5 iterations 160\nCbc0038I Pass 23: (23.46 seconds) suminf. 4.41533 (38) obj. 82267 iterations 71\nCbc0038I Pass 24: (23.50 seconds) suminf. 5.00933 (32) obj. 76370.5 iterations 406\nCbc0038I Pass 25: (23.53 seconds) suminf. 5.00933 (32) obj. 76370.5 iterations 90\nCbc0038I Pass 26: (23.58 seconds) suminf. 3.12866 (19) obj. 81141.9 iterations 521\nCbc0038I Pass 27: (23.60 seconds) suminf. 3.12866 (19) obj. 81141.9 iterations 2\nCbc0038I Pass 28: (23.65 seconds) suminf. 3.96820 (13) obj. 75644.8 iterations 302\nCbc0038I Pass 29: (23.68 seconds) suminf. 3.96820 (13) obj. 75644.8 iterations 7\nCbc0038I Pass 30: (23.71 seconds) suminf. 2.05382 (6) obj. 77470.6 iterations 129\nCbc0038I No solution found this major pass\nCbc0038I Before mini branch and bound, 12420 integers at bound fixed and 21 continuous\nCbc0038I Full problem 13820 rows 13726 columns, reduced to 770 rows 705 columns\nCbc0038I Mini branch and bound did not improve solution (24.79 seconds)\nCbc0038I After 24.79 seconds - Feasibility pump exiting - took 2.76 seconds\nCbc0031I 175 added rows had average density of 83.977143\nCbc0013I At root node, 175 cuts changed objective from 16776.086 to 28416.154 in 10 passes\nCbc0014I Cut generator 0 (Probing) - 1123 row cuts average 2.0 elements, 0 column cuts (37 active) in 4.086 seconds - new frequency is 1\nCbc0014I Cut generator 1 (Gomory) - 534 row cuts average 247.5 elements, 0 column cuts (0 active) in 2.320 seconds - new frequency is 1\nCbc0014I Cut generator 2 (Knapsack) - 606 row cuts average 2.2 elements, 0 column cuts (0 active) in 0.113 seconds - new frequency is 1\nCbc0014I Cut generator 3 (Clique) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.041 seconds - new frequency is -100\nCbc0014I Cut generator 4 (MixedIntegerRounding2) - 906 row cuts average 43.6 elements, 0 column cuts (0 active) in 0.246 seconds - new frequency is 1\nCbc0014I Cut generator 5 (FlowCover) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.050 seconds - new frequency is -100\nCbc0014I Cut generator 6 (TwoMirCuts) - 1048 row cuts average 73.2 elements, 0 column cuts (0 active) in 1.688 seconds - new frequency is 1\nCbc0010I After 0 nodes, 1 on tree, 1e+50 best solution, best possible 28416.154 (45.59 seconds)\nCbc0012I Integer solution of 37000.826 found by DiveCoefficient after 18584 iterations and 6 nodes (60.40 seconds)\nCbc0038I Full problem 13820 rows 13726 columns, reduced to 158 rows 189 columns\nCbc0012I Integer solution of 34382.245 found by RINS after 18584 iterations and 6 nodes (61.78 seconds)\nCbc0038I Full problem 13820 rows 13726 columns, reduced to 147 rows 212 columns\nCbc0012I Integer solution of 30728.276 found by RINS after 24677 iterations and 41 nodes (80.09 seconds)\nCbc0011I Exiting as integer gap of 2312.1218 less than 3500 or 0%%\nCbc0001I Search completed - best objective 30728.27563158896, took 24697 iterations and 42 nodes (80.23 seconds)\nCbc0032I Strong branching done 938 times (36727 iterations), fathomed 0 nodes and fixed 0 variables\nCbc0035I Maximum depth 22, 0 variables fixed on reduced cost\nCuts at root node changed objective from 16776.1 to 28416.2\nProbing was tried 57 times and created 2617 cuts of which 37 were active after adding rounds of cuts (4.375 seconds)\nGomory was tried 57 times and created 706 cuts of which 0 were active after adding rounds of cuts (3.023 seconds)\nKnapsack was tried 57 times and created 1360 cuts of which 0 were active after adding rounds of cuts (0.537 seconds)\nClique was tried 10 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.041 seconds)\nMixedIntegerRounding2 was tried 57 times and created 2057 cuts of which 0 were active after adding rounds of cuts (0.914 seconds)\nFlowCover was tried 10 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.050 seconds)\nTwoMirCuts was tried 57 times and created 1692 cuts of which 0 were active after adding rounds of cuts (2.318 seconds)\nImplicationCuts was tried 14 times and created 1011 cuts of which 0 were active after adding rounds of cuts (0.031 seconds)\n\nResult - Optimal solution found (within gap tolerance)\n\nObjective value: 30728.27563159\nLower bound: 28416.154\nGap: 0.08\nEnumerated nodes: 42\nTotal iterations: 24697\nTime (CPU seconds): 81.01\nTime (Wallclock seconds): 81.36\n\nTotal time (CPU seconds): 82.74 (Wallclock seconds): 83.24\n\n" } ], "source": [ "opt = SolverFactory('cbc') # choose a solver\n", "#opt = SolverFactory('ipopt') # choose a solver\n", "\n", - "results = opt.solve(Rota.model, tee=True, options={\"seconds\":600, \"allow\": 25}) # solve the model with the, options=\"seconds=60\" selected solver" + "results = opt.solve(Rota.model, tee=True, options={\"seconds\":3600, \"allow\": 3500}) # solve the model with the, options=\"seconds=60\" selected solver" ] }, { @@ -2338,26 +201,44 @@ { "output_type": "execute_result", "data": { - "text/plain": "EnumValue(, 0, 'ok')" + "text/plain": "EnumValue(, 0, 'ok')" }, "metadata": {}, "execution_count": 7 } ], "source": [ - "results.solver.status" + "results.solver.status\n", + "\n" ] }, { "cell_type": "code", - "execution_count": 83, + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from shifts import RotaResults\n", + "\n", + "ResultsHolder = RotaResults(Rota, results)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, "metadata": { "tags": [ "outputPrepend", "outputPrepend" ] }, - "outputs": [], + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": "exeter 1 weekend 1.0 0.0 0.0\nexeter 2 weekend 1.0 0.0 0.0\nexeter 3 weekend 0.0 0.0 1.0\nexeter 4 weekend 1.0 0.0 0.0\nexeter 5 weekend 0.0 0.0 1.0\nexeter 6 weekend 1.0 0.0 0.0\nexeter 7 weekend 1.0 0.0 0.0\nexeter 8 weekend 0.0 0.0 1.0\nexeter 9 weekend 1.0 0.0 0.0\nexeter 10 weekend 1.0 0.0 0.0\nexeter 11 weekend 1.0 0.0 0.0\nexeter 12 weekend 0.0 0.0 1.0\nexeter 13 weekend 1.0 0.0 0.0\nexeter 14 weekend 1.0 0.0 0.0\nexeter 15 weekend 1.0 0.0 0.0\nexeter 16 weekend 0.0 0.0 1.0\nexeter 17 weekend 1.0 0.0 0.0\nexeter 18 weekend 1.0 0.0 0.0\nexeter 19 weekend 1.0 0.0 0.0\nexeter 20 weekend 1.0 0.0 0.0\nexeter 21 weekend 1.0 0.0 0.0\nexeter 22 weekend 1.0 0.0 0.0\nexeter 23 weekend 1.0 0.0 0.0\nexeter 24 weekend 0.0 0.0 1.0\nexeter 25 weekend 1.0 0.0 0.0\nexeter 26 weekend 1.0 0.0 0.0\nexeter 1 weekday 0.0 0.0 1.0\nexeter 2 weekday 0.0 0.0 1.0\nexeter 3 weekday 0.0 0.0 1.0\nexeter 4 weekday 1.0 0.0 0.0\nexeter 5 weekday 1.0 0.0 0.0\nexeter 6 weekday 1.0 0.0 0.0\nexeter 7 weekday 0.0 0.0 1.0\nexeter 8 weekday 1.0 0.0 0.0\nexeter 9 weekday 1.0 0.0 0.0\nexeter 10 weekday 1.0 0.0 0.0\nexeter 11 weekday 1.0 0.0 0.0\nexeter 12 weekday 1.0 0.0 0.0\nexeter 13 weekday 1.0 0.0 0.0\nexeter 14 weekday 1.0 0.0 0.0\nexeter 15 weekday 0.0 0.0 1.0\nexeter 16 weekday 1.0 0.0 0.0\nexeter 17 weekday 0.0 0.0 1.0\nexeter 18 weekday 0.0 0.0 1.0\nexeter 19 weekday 0.0 0.0 1.0\nexeter 20 weekday 0.0 0.0 1.0\nexeter 21 weekday 1.0 0.0 0.0\nexeter 22 weekday 0.0 0.0 1.0\nexeter 23 weekday 0.0 0.0 1.0\nexeter 24 weekday 1.0 0.0 0.0\nexeter 25 weekday 0.0 0.0 1.0\nexeter 26 weekday 1.0 0.0 0.0\ntruro 1 weekend 0.0 0.0 1.0\ntruro 2 weekend 0.0 0.0 1.0\ntruro 3 weekend 1.0 0.0 0.0\ntruro 4 weekend 1.0 0.0 0.0\ntruro 5 weekend 1.0 0.0 0.0\ntruro 6 weekend 1.0 0.0 0.0\ntruro 7 weekend 1.0 0.0 0.0\ntruro 8 weekend 1.0 0.0 0.0\ntruro 9 weekend 1.0 0.0 0.0\ntruro 10 weekend 0.0 0.0 1.0\ntruro 11 weekend 0.0 0.0 1.0\ntruro 12 weekend 1.0 0.0 0.0\ntruro 13 weekend 0.0 0.0 1.0\ntruro 14 weekend 0.0 0.0 1.0\ntruro 15 weekend 0.0 0.0 1.0\ntruro 16 weekend 1.0 0.0 0.0\ntruro 17 weekend 1.0 0.0 0.0\ntruro 18 weekend 0.0 0.0 1.0\ntruro 19 weekend 1.0 0.0 0.0\ntruro 20 weekend 0.0 0.0 1.0\ntruro 21 weekend 0.0 0.0 1.0\ntruro 22 weekend 1.0 0.0 0.0\ntruro 23 weekend 1.0 0.0 0.0\ntruro 24 weekend 1.0 0.0 0.0\ntruro 25 weekend 0.0 0.0 1.0\ntruro 26 weekend 1.0 0.0 0.0\ntruro 1 weekday 1.0 0.0 0.0\ntruro 2 weekday 1.0 0.0 0.0\ntruro 3 weekday 1.0 0.0 0.0\ntruro 4 weekday 0.0 0.0 1.0\ntruro 5 weekday 1.0 0.0 0.0\ntruro 6 weekday 0.0 0.0 1.0\ntruro 7 weekday 1.0 0.0 0.0\ntruro 8 weekday 1.0 0.0 0.0\ntruro 9 weekday 0.0 0.0 1.0\ntruro 10 weekday 1.0 0.0 0.0\ntruro 11 weekday 0.0 0.0 1.0\ntruro 12 weekday 1.0 0.0 0.0\ntruro 13 weekday 1.0 0.0 0.0\ntruro 14 weekday 0.0 0.0 1.0\ntruro 15 weekday 1.0 0.0 0.0\ntruro 16 weekday 0.0 0.0 1.0\ntruro 17 weekday 1.0 0.0 0.0\ntruro 18 weekday 1.0 0.0 0.0\ntruro 19 weekday 1.0 0.0 0.0\ntruro 20 weekday 1.0 0.0 0.0\ntruro 21 weekday 1.0 0.0 0.0\ntruro 22 weekday 1.0 0.0 0.0\ntruro 23 weekday 1.0 0.0 0.0\ntruro 24 weekday 1.0 0.0 0.0\ntruro 25 weekday 1.0 0.0 0.0\ntruro 26 weekday 1.0 0.0 0.0\nplymouth 1 weekend 1.0 0.0 0.0\nplymouth 2 weekend 1.0 0.0 0.0\nplymouth 3 weekend 1.0 0.0 0.0\nplymouth 4 weekend 1.0 0.0 0.0\nplymouth 5 weekend 1.0 0.0 0.0\nplymouth 6 weekend 1.0 0.0 0.0\nplymouth 7 weekend 1.0 0.0 0.0\nplymouth 8 weekend 1.0 0.0 0.0\nplymouth 9 weekend 1.0 0.0 0.0\nplymouth 10 weekend 1.0 0.0 0.0\nplymouth 11 weekend 1.0 0.0 0.0\nplymouth 12 weekend 1.0 0.0 0.0\nplymouth 13 weekend 1.0 0.0 0.0\nplymouth 14 weekend 1.0 0.0 0.0\nplymouth 15 weekend 1.0 0.0 0.0\nplymouth 16 weekend 1.0 0.0 0.0\nplymouth 17 weekend 1.0 0.0 0.0\nplymouth 18 weekend 1.0 0.0 0.0\nplymouth 19 weekend 1.0 0.0 0.0\nplymouth 20 weekend 1.0 0.0 0.0\nplymouth 21 weekend 1.0 0.0 0.0\nplymouth 22 weekend 0.0 0.0 1.0\nplymouth 23 weekend 1.0 0.0 0.0\nplymouth 24 weekend 1.0 0.0 0.0\nplymouth 25 weekend 1.0 0.0 0.0\nplymouth 26 weekend 1.0 0.0 0.0\nplymouth 1 weekday 1.0 0.0 0.0\nplymouth 2 weekday 1.0 0.0 0.0\nplymouth 3 weekday 1.0 0.0 0.0\nplymouth 4 weekday 1.0 0.0 0.0\nplymouth 5 weekday 1.0 0.0 0.0\nplymouth 6 weekday 1.0 0.0 0.0\nplymouth 7 weekday 1.0 0.0 0.0\nplymouth 8 weekday 1.0 0.0 0.0\nplymouth 9 weekday 1.0 0.0 0.0\nplymouth 10 weekday 1.0 0.0 0.0\nplymouth 11 weekday 1.0 0.0 0.0\nplymouth 12 weekday 0.0 0.0 1.0\nplymouth 13 weekday 0.0 0.0 1.0\nplymouth 14 weekday 1.0 0.0 0.0\nplymouth 15 weekday 1.0 0.0 0.0\nplymouth 16 weekday 1.0 0.0 0.0\nplymouth 17 weekday 1.0 0.0 0.0\nplymouth 18 weekday 1.0 0.0 0.0\nplymouth 19 weekday 1.0 0.0 0.0\nplymouth 20 weekday 1.0 0.0 0.0\nplymouth 21 weekday 1.0 0.0 0.0\nplymouth 22 weekday 1.0 0.0 0.0\nplymouth 23 weekday 1.0 0.0 0.0\nplymouth 24 weekday 1.0 0.0 0.0\nplymouth 25 weekday 1.0 0.0 0.0\nplymouth 26 weekday 1.0 0.0 0.0\nbarnstaple 1 weekend 0.0 0.0 1.0\nbarnstaple 2 weekend 0.0 0.0 1.0\nbarnstaple 3 weekend 0.0 0.0 1.0\nbarnstaple 4 weekend 0.0 0.0 1.0\nbarnstaple 5 weekend 0.0 0.0 1.0\nbarnstaple 6 weekend 0.0 0.0 1.0\nbarnstaple 7 weekend 0.0 0.0 1.0\nbarnstaple 8 weekend 0.0 0.0 1.0\nbarnstaple 9 weekend 0.0 0.0 1.0\nbarnstaple 10 weekend 0.0 0.0 1.0\nbarnstaple 11 weekend 0.0 0.0 1.0\nbarnstaple 12 weekend 0.0 0.0 1.0\nbarnstaple 13 weekend 0.0 0.0 1.0\nbarnstaple 14 weekend 0.0 0.0 1.0\nbarnstaple 15 weekend 0.0 0.0 1.0\nbarnstaple 16 weekend 0.0 0.0 1.0\nbarnstaple 17 weekend 0.0 0.0 1.0\nbarnstaple 18 weekend 0.0 0.0 1.0\nbarnstaple 19 weekend 0.0 0.0 1.0\nbarnstaple 20 weekend 0.0 0.0 1.0\nbarnstaple 21 weekend 0.0 0.0 1.0\nbarnstaple 22 weekend 0.0 0.0 1.0\nbarnstaple 23 weekend 0.0 0.0 1.0\nbarnstaple 24 weekend 0.0 0.0 1.0\nbarnstaple 25 weekend 0.0 0.0 1.0\nbarnstaple 26 weekend 0.0 0.0 1.0\nbarnstaple 1 weekday 0.0 0.0 1.0\nbarnstaple 2 weekday 0.0 0.0 1.0\nbarnstaple 3 weekday 0.0 0.0 1.0\nbarnstaple 4 weekday 0.0 0.0 1.0\nbarnstaple 5 weekday 0.0 0.0 1.0\nbarnstaple 6 weekday 0.0 0.0 1.0\nbarnstaple 7 weekday 0.0 0.0 1.0\nbarnstaple 8 weekday 0.0 0.0 1.0\nbarnstaple 9 weekday 0.0 0.0 1.0\nbarnstaple 10 weekday 0.0 0.0 1.0\nbarnstaple 11 weekday 0.0 0.0 1.0\nbarnstaple 12 weekday 0.0 0.0 1.0\nbarnstaple 13 weekday 0.0 0.0 1.0\nbarnstaple 14 weekday 0.0 0.0 1.0\nbarnstaple 15 weekday 0.0 0.0 1.0\nbarnstaple 16 weekday 0.0 0.0 1.0\nbarnstaple 17 weekday 0.0 0.0 1.0\nbarnstaple 18 weekday 0.0 0.0 1.0\nbarnstaple 19 weekday 0.0 0.0 1.0\nbarnstaple 20 weekday 0.0 0.0 1.0\nbarnstaple 21 weekday 0.0 0.0 1.0\nbarnstaple 22 weekday 0.0 0.0 1.0\nbarnstaple 23 weekday 0.0 0.0 1.0\nbarnstaple 24 weekday 0.0 0.0 1.0\nbarnstaple 25 weekday 0.0 0.0 1.0\nbarnstaple 26 weekday 0.0 0.0 1.0\ntorquay 1 weekend 1.0 0.0 0.0\ntorquay 2 weekend 1.0 0.0 0.0\ntorquay 3 weekend 1.0 0.0 0.0\ntorquay 4 weekend 0.0 0.0 1.0\ntorquay 5 weekend 1.0 0.0 0.0\ntorquay 6 weekend 0.0 0.0 1.0\ntorquay 7 weekend 0.0 0.0 1.0\ntorquay 8 weekend 1.0 0.0 0.0\ntorquay 9 weekend 0.0 0.0 1.0\ntorquay 10 weekend 1.0 0.0 0.0\ntorquay 11 weekend 1.0 0.0 0.0\ntorquay 12 weekend 1.0 0.0 0.0\ntorquay 13 weekend 1.0 0.0 0.0\ntorquay 14 weekend 1.0 0.0 0.0\ntorquay 15 weekend 1.0 0.0 0.0\ntorquay 16 weekend 1.0 0.0 0.0\ntorquay 17 weekend 0.0 0.0 1.0\ntorquay 18 weekend 1.0 0.0 0.0\ntorquay 19 weekend 0.0 0.0 1.0\ntorquay 20 weekend 1.0 0.0 0.0\ntorquay 21 weekend 1.0 0.0 0.0\ntorquay 22 weekend 1.0 0.0 0.0\ntorquay 23 weekend 0.0 0.0 1.0\ntorquay 24 weekend 1.0 0.0 0.0\ntorquay 25 weekend 1.0 0.0 0.0\ntorquay 26 weekend 0.0 0.0 1.0\ntorquay 1 weekday 1.0 0.0 0.0\ntorquay 2 weekday 1.0 0.0 0.0\ntorquay 3 weekday 1.0 0.0 0.0\ntorquay 4 weekday 1.0 0.0 0.0\ntorquay 5 weekday 0.0 0.0 1.0\ntorquay 6 weekday 1.0 0.0 0.0\ntorquay 7 weekday 1.0 0.0 0.0\ntorquay 8 weekday0.0 0.0 1.0\ntorquay 9 weekday 1.0 0.0 0.0\ntorquay 10 weekday 0.0 0.0 1.0\ntorquay 11 weekday 1.0 0.0 0.0\ntorquay 12 weekday 1.0 0.0 0.0\ntorquay 13 weekday 1.0 0.0 0.0\ntorquay 14 weekday 1.0 0.0 0.0\ntorquay 15 weekday 1.0 0.0 0.0\ntorquay 16 weekday 1.0 0.0 0.0\ntorquay 17 weekday 1.0 0.0 0.0\ntorquay 18 weekday 1.0 0.0 0.0\ntorquay 19 weekday 1.0 0.0 0.0\ntorquay 20 weekday 1.0 0.0 0.0\ntorquay 21 weekday 0.0 0.0 1.0\ntorquay 22 weekday 1.0 0.0 0.0\ntorquay 23 weekday 1.0 0.0 0.0\ntorquay 24 weekday 0.0 0.0 1.0\ntorquay 25 weekday 1.0 0.0 0.0\ntorquay 26 weekday 0.0 0.0 1.0\n" + } + ], "source": [ "#def get_workers_needed(needed):\n", "# \"\"\"Extract to a list the needed workers for the optimal solution.\"\"\"\n", @@ -2368,131 +249,40 @@ "# return workers_needed\n", "\n", "\n", - "def get_work_table(works):\n", - " \"\"\"Build a timetable of the week as a dictionary from the model's optimal solution.\"\"\"\n", - " week_table = {week :{day: {shift: [] for shift in Rota.GetShiftNames()} for day in days} for week in Rota.weeks}\n", - " for week in Rota.weeks:\n", - " for worker in Rota.workers:\n", - " for day, shift in Rota.GetAllDayShiftAsNames():\n", - " if works[worker.id, week, day, shift].value == 1:\n", - " week_table[week][day][shift].append(worker.get_details())\n", - " return week_table\n", "\n", - "def get_worker_timetable(works):\n", - " timetable = { worker.name : { week : { day: \"\" for day in days } for week in Rota.weeks } for worker in Rota.workers }\n", - " for worker in Rota.workers:\n", - " for week in Rota.weeks:\n", - " for day, shift in Rota.GetAllDayShiftAsNames():\n", - " if works[worker.id, week, day, shift].value == 1:\n", - " timetable[worker.name][week][day] = shift\n", - " return timetable\n", - "\n", - "def get_worker_timetable_brief(works):\n", - " timetable = []\n", - " timetable.append(\"{:20}\".format(\"Worker\") + \"\".join([7*str(\"{}\".format(w))[-1:] for w in Rota.weeks]))\n", - " for worker in Rota.workers:\n", - " w = [\"{:20}\".format(worker.name)]\n", - " for week, day in Rota.GetAllWeeksDays():\n", - " a = \"-\"\n", - " for shift in Rota.GetShiftNamesByDay(day):\n", - " if works[worker.id, week, day, shift].value == 1:\n", - " a = shift[0]\n", - " w.append(a)\n", - " timetable.append(\"\".join(w))\n", - " \n", - " return \"\\n\".join(timetable)\n", - "\n", - "def get_shift_summary(works):\n", - " timetable = { worker.get_details() : { shift[0] : \"\" for shift in Rota.GetShiftNames() } for worker in Rota.workers }\n", - " #timetable = { worker.get_details() : { shift : \"\" for shift in [\"truro_twilight\"] } for worker in workers } \n", - " t = []\n", - " for worker in Rota.workers:\n", - " l = [worker.get_details()+\": \"]\n", - "\n", - " for shift in Rota.GetShiftNames():\n", - " #for shift in [\"truro_twilight\"]:\n", - " c = [works[worker.id, week, day, shift] for week in Rota.weeks for day in days].count(1)\n", - " if c > 0:\n", - " l.append(\"{} ({})\".format(shift,c))\n", - " #print(worker.id, shift)\n", - " timetable[worker.get_details()][shift[0]] = c\n", - " t.append(\",\".join(l))\n", - " return \"\\n\".join(t) \n", - "\n", - "#def get_no_preference(no_pref):\n", - "# \"\"\"Extract to a list the workers not satisfied with their weekend preference.\"\"\"\n", - "# return [worker.id for worker in workers if no_pref[worker.id].value == 1]\n", - "\n", - "def get_night_blocks(nights):\n", - " timetable = { worker.name : { week : { block: \"\" for block in Rota.night_blocks } for week in Rota.weeks } for worker in Rota.workers }\n", - " for worker in Rota.workers:\n", - " for week in Rota.weeks:\n", - " for block in Rota.night_blocks:\n", - " if nights[worker.id, week, block].value == 1:\n", - " timetable[worker.name][week][block] = \"true\"\n", - " return timetable\n", "\n", "\n", "\n", "#workers_needed = get_workers_needed(model.needed) # dict with the optimal timetable\n", - "week_table = get_work_table(Rota.model.works) # list with the required workers\n", - "worker_timetable = get_worker_timetable(Rota.model.works)\n", - "worker_timetable_brief = get_worker_timetable_brief(Rota.model.works)\n", - "#workers_no_pref = get_no_preference(Rota.model.no_pref) # list with the non-satisfied workers (work on Sat but not on Sun)\n", - "worker_night_block = get_night_blocks(Rota.model.nights)\n", - "worker_shift_summary = get_shift_summary(Rota.model.works)" + "week_table = ResultsHolder.get_work_table() # list with the required workers\n", + "worker_timetable = ResultsHolder.get_worker_timetable()\n", + "worker_timetable_brief = ResultsHolder.get_worker_timetable_brief(show_prefs=False)\n", + "#workers_no_pref = ResultsHolder.get_no_preference(Rota.model.no_pref) # list with the non-satisfied workers (work on Sat but not on Sun)\n", + "worker_night_block = ResultsHolder.get_night_blocks()\n", + "worker_shift_summary = ResultsHolder.get_shift_summary()\n", + "multi = ResultsHolder.get_multinight()" ] }, { "cell_type": "code", - "execution_count": 98, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "def ExportRotaToCSV(works):\n", - " with open(\"rota.csv\", 'w', newline='') as f:\n", - " wr = csv.writer(f, quoting=csv.QUOTE_ALL)\n", - " l = [\"Name\"]\n", - " l.extend([worker.name for worker in Rota.workers])\n", - " wr.writerow(l)\n", - "\n", - " l2 = [\"Site\"]\n", - " l2.extend([worker.site for worker in Rota.workers])\n", - " wr.writerow(l2)\n", - "\n", - " l3 = [\"Grade\"]\n", - " l3.extend([worker.grade for worker in Rota.workers])\n", - " wr.writerow(l3)\n", - "\n", - " l4 = [\"FTE\"]\n", - " l4.extend([worker.fte for worker in Rota.workers])\n", - " wr.writerow(l4)\n", "\n", "\n", - " for week, day in Rota.GetAllWeeksDays():\n", - " d = [\"Week {} Day {}\".format(week, day)]\n", - "\n", - " for worker in Rota.workers:\n", - " i = \"\"\n", - " for shift in Rota.GetShiftNamesByDay(day):\n", - " if works[worker.id, week, day, shift].value == 1:\n", - " i = shift\n", - "\n", - " d.append(i)\n", - " wr.writerow(d)\n", - "\n", - "ExportRotaToCSV(Rota.model.works)" + "ResultsHolder.export_rota_to_csv()" ] }, { "cell_type": "code", - "execution_count": 99, + "execution_count": 11, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", - "text": "Worker 1111111222222233333334444444555555566666667777777888888899999990000000111111122222223333333444444455555556666666\nTest Person 30 eee------n-------------------------n-e--w---e--------n-w------------w----ne------------------------n------------\nTest Person 31 ---ee------------------e--nn-n----------------n-------w---e-------------------e------n------------------w-----n-\nTest Person 32 -----------n--------ne----------e---------------w----------n----------e-----------w-n--ee--e--n------n----------\nTest Person 33 ------------w--------------------n-e-----------------------ee-------n-------nnn--------n--------------ew---e----\nTest Person 34 -----wwe-----------nn----------n-------e----------------n------n---------------------e-------n------e-------e---\nTest Person 35 -------n-------en--w----------e------n---w------ne--n---e------e-------------------------n----------------------\nTest Person 36 ------------------------e-w---n--------------ee---------------n----e------------------n--w---------nn--n--e-----\nTest Person 37 -n-n----e--------------------------n-------n----------------nn---------e---w----------------------e--e-------e-w\nTest Person 38 -----------e---------------w--n--------n---e--------e------------e---w-ne--n------------n------------n----------\nTest Person 39 --------------e---e---------nen---w---n----------------------ww-e-n------------e-------------------e---n--------\nTest Person 40 --------n---n---e-----------e---------------n-----e------e------n------------e--e-------------n--w------------w-\nTest Person 41 ---------e---w---e---n---n----------e------------------n------n------------------e--------w---en------------n---\nTest Person 42 ----------e-----------e-----n---------e---n----------e-------------------e--w---------n-----e--nw------------n--\nTest Person 43 -------------------------e-------w--------e----w----------------n-e-------n---------e-e---------n-----n----n-n--\nTest Person 44 --------------------w----n-----e-------n-----------e--------n--------n-------------w----nn---e-e---------e------\nTest Person 15 pnppn-w--------p---n------w------w----------------n----w--n----------w------------------------p------p----------\nTest Person 16 ----pwwp----w--------------w----------------------nn-------n----pn-----n--p-------------pw---p------------------\nTest Person 17 --n--w--p-p--w---------np-------n-------w----------------------n--p-------------p-n--------n------------w-------\nTest Person 18 --n--------pww-----------p---n------n--------------p-n------p-w------n-----w---------------p-------------n------\nTest Person 19 -pn-----------p----w-------------------p----------------nn----w---------p-----------n-----------n------w-----pwn\nTest Person 20 ---n------------pppwwp-----wp----------------------------n-----------------w-----n----------n-n-------------n---\nTest Person 21 ----------n----------------------n-------w---p--w------------------n---p-------n---w--------p--p-wp-----nn------\nTest Person 22 ------------nn---------------------------------wwp--n---p--------p-p-wp------------np-----w-------n-------n-----\nTest Person 23 ----------------------p---w--p----wp--nn-wn--n---np-------------------------w--------------------w-p--------p---\nTest Person 24 ------n---------------np-------p--w------n----------p--w-------p--------n-------n-w---------n---w---------p-----\nTest Person 25 ----n----p--------------------p----------------w-----nw------------n--n------np--p-n--------------------w--p--w-\nTest Person 26 ---------------------n----------pn----p----p---------p-------w---n--w-n-----w--------pp--nw-----w---------------\nTest Person 27 ---------------------------------wn-p-----n------n-n------p--w-n-n-------p---------w----------------p----p-----w\nTest Person 28 --------------------w----------------p--------p------------p-------------n--np----w------wnnn---------pw-------n\nTest Person 29 ------n-------------------------n-------w-p-pn--------w--p--n-----n-w---n------p-------p-----------------------w\nTest Person 45 t-t--n-------------w-----t------n-----------------------n----n-----------------------n-------t-t--n----w--------\nTest Person 46 ----t--n----------------n------n------------n---------------t-------------t-----t-------t-w----n------n-------w-\nTest Person 47 -----w---tt------------tn---t------------n--t-t-------n----------------n----w--------n----------n---------------\nTest Person 48 ------w-t--------t---------------------------------t-----n----w---------------------------nn--------n-tnn--t----\nTest Person 49 ------------w----n---t----w-------------n---------n-------------t------------t--nn-----t--n------------------t--\nTest Person 50 --------------nn-----n--t--w------------w-----n-n------n-------t------t-------t-------------------t-------------\nTest Person 51 -t--------------t--------------------------n------t-t-n-----------t-ww----------n-n--t-----------n--------------\nTest Person 52 -----n-t--------n---------------------t---t----w-n-n---------w-----t-------n----------n--------------t----------\nTest Person 53 ---------------n------------n-----------------n--------------n--nt---------------t-------w-ttnt-w---------------\nTest Person 54 --------n--t--------w---n------tt----------------------------------n---t---w--n--------------------t--n-n-------\nTest Person 55 -----------n-w-nn-------------------tn-t-------nw---------t------------------------wt---------------t----t------\nTest Person 56 ------------------nn---------t-------------------t---t-wt------------------nn---------t-n--------w------w---t---\nTest Person 57 ------------------t---t------nt----t-----w-----n------n--t---------------------------------------n--------nn---w\nTest Person 58 ---------n----tt-----------------ww--t-----n---------------t----------n--t----n-----n--n------------------t-----\nTest Person 59 ---t------n------n-----------------------n-t-t--------w----n------------t-n----t-nw-----------------------------\nTest Person 1 n---n--tt-n---t-tt---------w----------t-w---n----------------------------------n---------------------------n----\nTest Person 10 --------------n---------------------------ttt--------t-------w--------------------------t----------nt----n--nnww\nTest Person 11 n-tt-n---t---n---------------------------------n-------n--n-----t-----------------w------------t--------w-------\nTest Person 12 ------------------n--t---nnn--------------------w---------n---------------t---tt------t-------------n--w--t-----\nTest Person 13 -t--------t-----------n---w-------n----------------t----------w-----n--t-------------------t-nt------n----------\nTest Person 14 t---------------------nn-------------------------------w--t-------nt----n---w--n----t-------t--------------t----\nTest Person 2 nn--tww----twn-t--n----n--------------------------------t----------------t----------------------------t---------\nTest Person 3 ---n--------n----------------t--t------------tt-n---t----------t-----n-----w-------w-------------n--------------\nTest Person 4 ------nn-----w------n-t---nn---n----t--------------------------------wt---------t-----------------t-------------\nTest Person 5 --------n-----n--n----------t----w-t-------------t-------t--t-n--------------------------w-------------------tnn\nTest Person 6 -----------n------t------t--------w--n--n------w------w-----------t----------n---t---t----------------------t-n-\nTest Person 7 -----------------------tt---------n-n--------n----------------------w---t-n------------n--------w--t-----tn-----\nTest Person 8 ---------n---------ww---------tt---n-t-tn---------t-------------------------------nn------w--t------------------\nTest Person 9 ------------------------------------n-n--w----------n------t-----t--n----n---t---------t---------wn--t----------\n" + "text": "-Day- MTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSS\n-Week- 11111112222222333333344444445555555666666677777778888888999999900000001111111222222233333334444444555555566666667777777888888899999990000000111111122222223333333444444455555556666666\nAlex Wood --e----------------------------------------ee-----------ee--nnn--e-----------------------------nnn-----ww---e---e---nnn-----w----------e----e---ewwe-------ee--ww-e-------e-----e--nnn weekend_exeter: 7,night: 12,exeter_twilight: 17,\nMichal Tomek -----ww----nnn--e-------------e-----------e---ewwe--e--------ww----nnn-------nnnn----e-------------------eee-e------------e---------------ww----nnn------------------e--e---------e--- weekend_exeter: 8,night: 13,exeter_twilight: 15,\nWijhdan Abusrewil e---nnn--------------nnnn------e--------ww---------e--------------e-------nnn---------e----e-e--ww---------------e--e--ee---------eww-------nnnn-------e-----ee-----e----------------- weekend_exeter: 6,night: 14,exeter_twilight: 16,\nMadalina Drumea ---------------------e---nnn-----ww--e-e------nnn----e-----ee------e----e-e-------ww----nnn---e-------nnn---------------------e-------e-e--------------nnn---------e---------wwe------ weekend_exeter: 6,night: 15,exeter_twilight: 15,\nSalma Aslam -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \nSayed Alqarooni -e--------ee------e-------ww-e------e-e-----------e---ww-------nnnn--------wwe---------e---nnnn------e---------------------nnn--e--------------e-----------------e----------nnn--e-e-- weekend_exeter: 6,night: 14,exeter_twilight: 16,\nDelilah Trimmer -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \nRebecca Murphy -------e------e-------e-e---nnnn--------------------------e----e-------e-e-----e--------e---e-----e------nnnn------------e-e-w-e-----e---nnn------------------nnn-----ww----e-------ww weekend_exeter: 5,night: 14,exeter_twilight: 17,\nTom Welsh ----e---e-----------------------e--nnnn-------------------------e---wwe-------------e----ww-----------------------ee-ww-----------nnn----e---ee-----eee----------nnnn----e-----nnnn--- weekend_exeter: 6,night: 15,exeter_twilight: 15,\nJin Virdi ---e-----e---------ww--e-e--e------e---------e----------nnnn------------------------nnnn-------e---ee---------ww---------------------------------------------------------------------- weekend_exeter: 4,night: 8,exeter_twilight: 10,\nTania King Mohammed ------------ww-e-e---------------------nnn-------nnnn-----------------nnnn----e-ee--------------------e--------------------------e----------------------wwe----------------e---------- weekend_exeter: 4,night: 11,exeter_twilight: 9,\nTom Davies -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \nAlexander Sanchez ------------ww--p--------p--------------------p-----p----p-----p-----------ww-----ww-----------p------nnn-----w-------------ww----nnn--------------nnnn---p-------p-p---nnnn---------- weekend_plymouth: 9,night: 14,plymouth_twilight: 10,\nHannah Lewis ----p-------wwp-------------nnnn----p-ppww-------nnnn---p--p----p-----nnnn-------------------p-----p----------ww-----------------------------p---------------p-ww--------------------- weekend_plymouth: 8,night: 12,plymouth_twilight: 12,\nSeren Peters ---p-ww------------------------p-ww---------pp-------nnn-------------------------nnn-------nnnn-----------pp---w----------------p-----p-p--------------pww-------nnnn--------ww------- weekend_plymouth: 9,night: 14,plymouth_twilight: 10,\nAdeola Omotade -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \nAmina Odeh ---------p----nnnn--------ww--p----nnnn--------ww----------------------p----------------p-------ww---p---p-------p--nnn------------------nnn-----ww--p--------p----p--------------p--- weekend_plymouth: 8,night: 14,plymouth_twilight: 11,\nGeorge Edwards --------------------------ww------w--------p-----pp-----nnnn--------------------ppww-----ww-------p------nnnn-----p----p-------------nnnn----------p-----------------nnn--p-----p----- weekend_plymouth: 7,night: 15,plymouth_twilight: 11,\nJean Sukumar -----ww---p--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- weekend_plymouth: 2,plymouth_twilight: 1,\n-Week- 11111112222222333333344444445555555666666677777778888888999999900000001111111222222233333334444444555555566666667777777888888899999990000000111111122222223333333444444455555556666666\n-Day- MTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSS\nJenn Fong -p-----nnnn-------p----------------------------ww-----ww----------------------------p-------------nnnn------p--------ww----p--pp--pww-----------nnn-----------nnn-------p---p--p------ weekend_plymouth: 8,night: 14,plymouth_twilight: 11,\nSafi Rahman -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \nKhalil Madbak ---------------p---------nnn-----------nnn---------p--------nnn----p------nnn------------ww-p---ww--p--ww------------ww------------------------ppw----p--------------p-------------nnn weekend_plymouth: 9,night: 15,plymouth_twilight: 9,\nNatasha Hougham ----nnn------------------------------------------------------ww---------pp--------------------p------------------------nnnn----------p----ww---------------pp-----------------------ww plymouth_twilight: 6,night: 7,weekend_plymouth: 6,\nRichard Chaytor p-------p--p----------------pp-----p------------------ww-----------nnn-----ww--------------------------------p--nnnn----------nnnn------------p-----------nnnn--------ww-p-p--------ww weekend_plymouth: 8,night: 15,plymouth_twilight: 10,\nS Ghauri --p---------------------p-------nnn-------------------------------p-ww------------------nnn------------ww------------------nnn--------------------w-p----------------------------p---- weekend_plymouth: 5,night: 9,plymouth_twilight: 5,\nSophie McGlade -----------------p-ww-p--------------p--------nnn-----------p----p--wwp---p--------------------nnn------------------p----p--ww---------p----nnnn--------ww-------p----------nnn----p-- weekend_plymouth: 8,night: 13,plymouth_twilight: 12,\nHuub Witt -------p---nnn-------nnnn--------w--------nnnn-------p----p----nnnn------------------ppp---p--------------------p---------p------p--------wwp-------------------------ww-----ww------- weekend_plymouth: 7,night: 15,plymouth_twilight: 11,\nJon Stokes -------------------wwp---------------------------------------------------------------------------------------------------------------------------------------------------------------- weekend_plymouth: 2,plymouth_twilight: 1,\nKaris McFeely nnnn--------------nnn--p--------p-------wwp------------------ww--------------ppp----------------------p------nnn---p----p----------ww----p---------------------ww--------------nnnn--- weekend_plymouth: 8,night: 14,plymouth_twilight: 10,\nMirna Long -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \nChristian Greer -------nnnn----------t------------------------t-----------------------------------------------t-ww-----ww-t-----nnnn-----------t----------wwttt----t------nnnn----t--t------t---t----w torquay_twilight: 13,night: 12,weekend_torquay: 7,\nMax Ireland ---------ttt--t---t----------------nnnn-----------------nnnn------------------------nnnn----t------------t---tww-----------------t---t----------t---t---ww------------ww--t---------w- weekend_torquay: 7,torquay_twilight: 13,night: 12,\nAlan Eccles -----ww----------------t------------------nnnn-----t------t------------------------------wwt-t---------------nnn-------nnnn-----t--------------------------t-------tt----t------------ night: 11,torquay_twilight: 10,weekend_torquay: 4,\nCharles Finan tt-----------------ww-t---------tww-t--t-------------nnn----t---------nnnn------------------------tt---------------t----------nnnn----t-t----------------------------nnn-----ww------- night: 14,torquay_twilight: 12,weekend_torquay: 6,\nChong Ng ----nnn----------t-------tww--t----t--t--------ww---------------t-tt----t-t----t--------nnn----------t------t--------------nnn---------------------nnnn-------t-------------------t--- torquay_twilight: 15,night: 13,weekend_torquay: 4,\nMatthew Thorley nnnn--------------nnn-------t---------------t---------wwt------------------wwt---t----t--------nnn----t----------t------t--t-------------nnn----------t-----t-----------t------t------ torquay_twilight: 14,night: 13,weekend_torquay: 4,\nMo Babsail ---t--------------------t----t----------ww-----------------t-ww----nnn---t-------nnn---t------------t----nnnn-------t----t--------t----t---------ww----nnn-----------------------t-t-- night: 13,torquay_twilight: 13,weekend_torquay: 6,\nNeil McIntyre --------t---ww-------nnnn------t----------tt-t-----------------t----wwt---nnn---t----t----------------nnn---------------------t----------t-----------t----t------t------nnnn---------- night: 14,torquay_twilight: 14,weekend_torquay: 4,\n-Week- 11111112222222333333344444445555555666666677777778888888999999900000001111111222222233333334444444555555566666667777777888888899999990000000111111122222223333333444444455555556666666\n-Day- MTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSSMTWTFSS\nPaul Jenkins ---------------t----------------nnn--t------------t--------------t-----t-----nnnn--------------t--nnnn----------t----wwt--t--------ww-----------nnn----t-----t-ww----------t---------- weekend_torquay: 6,torquay_twilight: 12,night: 14,\nGana Kugathasan -----------nnn--t--------------------------------------------------------------------------------------------------------------------------------------------------------------------- torquay_twilight: 1,night: 3,\nHarriet Conley -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \nJon Skinner --t-t--t------nnnn-------------------------------t--tt---t--------------------t---wwt---t--nnnn------------t------t---------ww-------nnnn------t----------------------------nnn------- weekend_torquay: 4,torquay_twilight: 13,night: 15,\nNicolas Dziad nnnn-------t------nnn---t-ww-----------------------t-----------nnnn-----------------t-------------nnnn------------------t-t---------------ww---t-----ttt--t----ww-----------------tt-- weekend_truro: 6,truro_twilight: 13,night: 15,\nVilim Kalamar t--t------t-----------------ttt--ww-----wwt--t--------------------t---------------ww----------------------t--nnn--t----nnnn----------nnnn----------t----------nnn--------t------------ weekend_truro: 6,truro_twilight: 13,night: 14,\nAmoolya Mannava ------------wwtt----------------t---t------------nnnn------t-------------t------------t----------------ww-------nnnn----------ttt-t----t-t------t---t------------nnnn--------ww------- weekend_truro: 6,truro_twilight: 15,night: 12,\nChi Leung --------------nnnn----t---------------------------t--t------nnn--t-----------nnnn-------------t---t--------t----tt-------t------------------nnnn--------ww-tt-----t---ww---------t---- weekend_truro: 4,truro_twilight: 14,night: 15,\nJenna Millington --------------------------------nnn---------t-t-------------------------t-t---ttt---nnnn-------------------------------------------ww-------------------------------------t--------nnn weekend_truro: 2,truro_twilight: 8,night: 10,\nPaul Ward -t------t----------------nnn--------------nnnn-------------------------------t---------tt---t-------tt---t--ttww-----ww-----ww----nnn------------------------t------t---nnnn---------- weekend_truro: 6,truro_twilight: 13,night: 14,\nSarath Vennam ----------------t-twwt------nnnn-----t---------ww---------------------t-------------------wt---t------t------------t-------t-----t---t--t----tt-----------nnnn-----t-----------nnnn--- weekend_truro: 5,truro_twilight: 16,night: 12,\nRoss Kruger --t----nnnn------------t-t---------t-------t-----t---nnn--------t--t-------ww-------------------ww-t-------------------t--------------t------------nnnn--------------nnn---tt---t---ww weekend_truro: 6,truro_twilight: 14,night: 14,\nJack Looker -----wwt---------t--------------------tt------nnn---t-wwtt-----t----ww-t---------nnn---------t----------------------nnn--------------------------------nnn----t--t---t--t------t------ weekend_truro: 6,truro_twilight: 15,night: 12,\nPete Abernethy ----t----t---------------------t-------nnn----------------t-tww------------------t---t---w--------------------------t---------nnnn----------t----ww----------------------------------- weekend_truro: 5,truro_twilight: 9,night: 7,\n-Week- 11111112222222333333344444445555555666666677777778888888999999900000001111111222222233333334444444555555566666667777777888888899999990000000111111122222223333333444444455555556666666\n" } ], "source": [ @@ -2503,13 +293,13 @@ }, { "cell_type": "code", - "execution_count": 100, + "execution_count": 12, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", - "text": "30 e 4: ,exeter_twilight (6),weekend_exeter (3),night (5)\n31 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n32 e 4: ,exeter_twilight (6),weekend_exeter (2),night (6)\n33 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n34 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n35 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n36 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n37 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n38 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n39 e 4: ,exeter_twilight (6),weekend_exeter (3),night (5)\n40 e 4: ,exeter_twilight (6),weekend_exeter (2),night (5)\n41 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n42 e 4: ,exeter_twilight (6),weekend_exeter (2),night (5)\n43 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n44 e 4: ,exeter_twilight (5),weekend_exeter (2),night (6)\n15 p 4: ,plymouth_twilight (6),weekend_plymouth (5),night (5)\n16 p 4: ,plymouth_twilight (6),weekend_plymouth (5),night (5)\n17 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n18 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n19 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n20 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n21 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n22 p 4: ,plymouth_twilight (6),weekend_plymouth (4),night (6)\n23 p 4: ,plymouth_twilight (6),weekend_plymouth (5),night (5)\n24 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n25 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n26 p 4: ,plymouth_twilight (6),weekend_plymouth (5),night (5)\n27 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n28 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n29 p 4: ,plymouth_twilight (5),weekend_plymouth (4),night (6)\n45 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n46 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n47 t 4: ,torquay_twilight (6),weekend_torquay (2),night (6)\n48 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n49 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n50 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n51 t 4: ,torquay_twilight (6),weekend_torquay (2),night (5)\n52 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n53 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n54 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n55 t 4: ,torquay_twilight (6),weekend_torquay (3),night (5)\n56 t 4: ,torquay_twilight (6),weekend_torquay (3),night (5)\n57 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n58 t 4: ,torquay_twilight (6),weekend_torquay (2),night (6)\n59 t 4: ,torquay_twilight (5),weekend_torquay (2),night (6)\n1 t 4: ,truro_twilight (6),weekend_truro (2),night (6)\n10 t 4: ,truro_twilight (6),weekend_truro (3),night (5)\n11 t 4: ,truro_twilight (5),weekend_truro (2),night (6)\n12 t 4: ,truro_twilight (6),weekend_truro (2),night (6)\n13 t 4: ,truro_twilight (6),weekend_truro (2),night (5)\n14 t 4: ,truro_twilight (6),weekend_truro (2),night (5)\n2 t 4: ,truro_twilight (6),weekend_truro (3),night (5)\n3 t 4: ,truro_twilight (6),weekend_truro (2),night (5)\n4 t 4: ,truro_twilight (5),weekend_truro (2),night (6)\n5 t 4: ,truro_twilight (6),weekend_truro (2),night (6)\n6 t 4: ,truro_twilight (6),weekend_truro (3),night (5)\n7 t 4: ,truro_twilight (5),weekend_truro (2),night (6)\n8 t 4: ,truro_twilight (6),weekend_truro (3),night (5)\n9 t 4: ,truro_twilight (5),weekend_truro (2),night (6)\n" + "text": "7 e 2 [36]: exeter_twilight (17), weekend_exeter (7), night (12)\n6 e 2 [36]: exeter_twilight (15), weekend_exeter (8), night (13)\n1 e 2 [36]: exeter_twilight (16), weekend_exeter (6), night (14)\n18 e 3 [36]: exeter_twilight (15), weekend_exeter (6), night (15)\n11 e 3 [0]: \n19 e 3 [36]: exeter_twilight (16), weekend_exeter (6), night (14)\n28 e 4 [0]: \n27 e 4 [36]: exeter_twilight (17), weekend_exeter (5), night (14)\n26 e 4 [36]: exeter_twilight (15), weekend_exeter (6), night (15)\n43 e 5 [22]: exeter_twilight (10), weekend_exeter (4), night (8)\n42 e 5 [24]: exeter_twilight (9), weekend_exeter (4), night (11)\n41 e 5 [0]: \n8 p 2 [33]: plymouth_twilight (10), weekend_plymouth (9), night (14)\n3 p 2 [32]: plymouth_twilight (12), weekend_plymouth (8), night (12)\n2 p 2 [33]: plymouth_twilight (10), weekend_plymouth (9), night (14)\n12 p 3 [0]: \n13 p 3 [33]: plymouth_twilight (11), weekend_plymouth (8), night (14)\n35 p 3 [33]: plymouth_twilight (11), weekend_plymouth (7), night (15)\n20 p 3 [3]: plymouth_twilight (1), weekend_plymouth (2)\n21 p 3 [33]: plymouth_twilight (11), weekend_plymouth (8), night (14)\n34 p 3 [0]: \n31 p 4 [33]: plymouth_twilight (9), weekend_plymouth (9), night (15)\n32 p 4 [19]: plymouth_twilight (6), weekend_plymouth (6), night (7)\n33 p 4 [33]: plymouth_twilight (10), weekend_plymouth (8), night (15)\n30 p 4 [19]: plymouth_twilight (5), weekend_plymouth (5), night (9)\n29 p 4 [33]: plymouth_twilight (12), weekend_plymouth (8), night (13)\n44 p 5 [33]: plymouth_twilight (11), weekend_plymouth (7), night (15)\n46 p 5 [3]: plymouth_twilight (1), weekend_plymouth (2)\n47 p 5 [32]: plymouth_twilight (10), weekend_plymouth (8), night (14)\n45 p 5 [0]: \n4 t 2 [32]: torquay_twilight (13), weekend_torquay (7), night (12)\n9 t 2 [32]: torquay_twilight (13), weekend_torquay (7), night (12)\n15 t 3 [25]: torquay_twilight (10), weekend_torquay (4), night (11)\n14 t 3 [32]: torquay_twilight (12), weekend_torquay (6), night (14)\n22 t 3 [32]: torquay_twilight (15), weekend_torquay (4), night (13)\n23 t 3 [31]: torquay_twilight (14), weekend_torquay (4), night (13)\n37 t 3 [32]: torquay_twilight (13), weekend_torquay (6), night (13)\n36 t 4 [32]: torquay_twilight (14), weekend_torquay (4), night (14)\n38 t 4 [32]: torquay_twilight (12), weekend_torquay (6), night (14)\n50 t 5 [4]: torquay_twilight (1), night (3)\n48 t 5 [0]: \n49 t 5 [32]: torquay_twilight (13), weekend_torquay (4), night (15)\n5 t 2 [34]: truro_twilight (13), weekend_truro (6), night (15)\n10 t 2 [33]: truro_twilight (13), weekend_truro (6), night (14)\n40 t 3 [33]: truro_twilight (15), weekend_truro (6), night (12)\n25 t 3 [33]: truro_twilight (14), weekend_truro (4), night (15)\n24 t 3 [20]: truro_twilight (8), weekend_truro (2), night (10)\n17 t 3 [33]: truro_twilight (13), weekend_truro (6), night (14)\n16 t 3 [33]: truro_twilight (16), weekend_truro (5), night (12)\n39 t 4 [34]: truro_twilight (14), weekend_truro (6), night (14)\n51 t 5 [33]: truro_twilight (15), weekend_truro (6), night (12)\n52 t 5 [21]: truro_twilight (9), weekend_truro (5), night (7)\n" } ], "source": [ @@ -2520,25 +310,22 @@ }, { "cell_type": "code", - "execution_count": 101, + "execution_count": 13, "metadata": {}, "outputs": [ { - "output_type": "execute_result", - "data": { - "text/plain": "\n 'truro_twilight': ['10 t 4'],\n 'torquay_twilight': ['47 t 4'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['40 e 4', '46 t 4', '1 t 4']},\n 'Thu': {'exeter_twilight': ['36 e 4'],\n 'truro_twilight': ['3 t 4'],\n 'torquay_twilight': ['59 t 4'],\n 'plymouth_twilight': ['21 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['23 p 4', '29 p 4', '7 t 4']},\n 'Fri': {'exeter_twilight': ['36 e 4'],\n 'truro_twilight': ['3 t 4'],\n 'torquay_twilight': ['47 t 4'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['31 e 4', '50 t 4', '53 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['43 e 4'],\n 'weekend_truro': ['6 t 4'],\n 'weekend_torquay': ['52 t 4'],\n 'weekend_plymouth': ['22 p 4', '25 p 4'],\n 'night': ['55 t 4', '57 t 4', '11 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['32 e 4'],\n 'weekend_truro': ['12 t 4'],\n 'weekend_torquay': ['55 t 4'],\n 'weekend_plymouth': ['21 p 4', '22 p 4'],\n 'night': ['35 e 4', '50 t 4', '3 t 4']}},\n 8: {'Mon': {'exeter_twilight': ['35 e 4'],\n 'truro_twilight': ['5 t 4'],\n 'torquay_twilight': ['56 t 4'],\n 'plymouth_twilight': ['22 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['23 p 4', '27 p 4', '52 t 4']},\n 'Tue': {'exeter_twilight': ['40 e 4'],\n 'truro_twilight': ['8 t 4'],\n 'torquay_twilight': ['51 t 4'],\n 'plymouth_twilight': ['23 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['15 p 4', '16 p 4', '49 t 4']},\n 'Wed': {'exeter_twilight': ['44 e 4'],\n 'truro_twilight': ['13 t 4'],\n 'torquay_twilight': ['48 t 4'],\n 'plymouth_twilight': ['18 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['16 p 4', '27 p 4', '52 t 4']},\n 'Thu': {'exeter_twilight': ['38 e 4'],\n 'truro_twilight': ['3 t 4'],\n 'torquay_twilight': ['51 t 4'],\n 'plymouth_twilight': ['24 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['35 e 4', '22 p 4', '9 t 4']},\n 'Fri': {'exeter_twilight': ['42 e 4'],\n 'truro_twilight': ['10 t 4'],\n 'torquay_twilight': ['56 t 4'],\n 'plymouth_twilight': ['26 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['30 e 4', '18 p 4', '25 p 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['31 e 4'],\n 'weekend_truro': ['6 t 4'],\n 'weekend_torquay': ['59 t 4'],\n 'weekend_plymouth': ['25 p 4', '29 p 4'],\n 'night': ['47 t 4', '51 t 4', '57 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['30 e 4'],\n 'weekend_truro': ['14 t 4'],\n 'weekend_torquay': ['56 t 4'],\n 'weekend_plymouth': ['15 p 4', '24 p 4'],\n 'night': ['41 e 4', '50 t 4', '11 t 4']}},\n 9: {'Mon': {'exeter_twilight': ['35 e 4'],\n 'truro_twilight': ['2 t 4'],\n 'torquay_twilight': ['56 t 4'],\n 'plymouth_twilight': ['22 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['34 e 4', '19 p 4', '45 t 4']},\n 'Tue': {'exeter_twilight': ['40 e 4'],\n 'truro_twilight': ['5 t 4'],\n 'torquay_twilight': ['57 t 4'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['19 p 4', '20 p 4', '48 t 4']},\n 'Wed': {'exeter_twilight': ['31 e 4'],\n 'truro_twilight': ['14 t 4'],\n 'torquay_twilight': ['55 t 4'],\n 'plymouth_twilight': ['27 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['15 p 4', '11 t 4', '12 t 4']},\n 'Thu': {'exeter_twilight': ['33 e 4'],\n 'truro_twilight': ['9 t 4'],\n 'torquay_twilight': ['58 t 4'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['32 e 4', '16 p 4', '59 t 4']},\n 'Fri': {'exeter_twilight': ['33 e 4'],\n 'truro_twilight': ['5 t 4'],\n 'torquay_twilight': ['46 t 4'],\n 'plymouth_twilight': ['18 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['37 e 4', '44 e 4', '29 p 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['39 e 4'],\n 'weekend_truro': ['10 t 4'],\n 'weekend_torquay': ['52 t 4'],\n 'weekend_plymouth': ['26 p 4', '27 p 4'],\n 'night': ['37 e 4', '45 t 4', '53 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['39 e 4'],\n 'weekend_truro': ['13 t 4'],\n 'weekend_torquay': ['48 t 4'],\n 'weekend_plymouth': ['18 p 4', '19 p 4'],\n 'night': ['36 e 4', '41 e 4', '5 t 4']}},\n 10: {'Mon': {'exeter_twilight': ['35 e 4'],\n 'truro_twilight': ['3 t 4'],\n 'torquay_twilight': ['50 t 4'],\n 'plymouth_twilight': ['24 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['34 e 4', '17 p 4', '27 p 4']},\n 'Tue': {'exeter_twilight': ['39 e 4'],\n 'truro_twilight': ['11 t 4'],\n 'torquay_twilight': ['49 t 4'],\n 'plymouth_twilight': ['16 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['40 e 4', '43 e 4', '53 t 4']},\n 'Wed': {'exeter_twilight': ['38 e 4'],\n 'truro_twilight': ['9 t 4'],\n 'torquay_twilight': ['53 t 4'],\n 'plymouth_twilight': ['22 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['16 p 4', '26 p 4', '27 p 4']},\n 'Thu': {'exeter_twilight': ['43 e 4'],\n 'truro_twilight': ['6 t 4'],\n 'torquay_twilight': ['51 t 4'],\n 'plymouth_twilight': ['17 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['39 e 4', '29 p 4', '14 t 4']},\n 'Fri': {'exeter_twilight': ['36 e 4'],\n 'truro_twilight': ['14 t 4'],\n 'torquay_twilight': ['52 t 4'],\n 'plymouth_twilight': ['22 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['21 p 4', '25 p 4', '54 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['30 e 4'],\n 'weekend_truro': ['7 t 4'],\n 'weekend_torquay': ['51 t 4'],\n 'weekend_plymouth': ['26 p 4', '29 p 4'],\n 'night': ['33 e 4', '13 t 4', '9 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['38 e 4'],\n 'weekend_truro': ['4 t 4'],\n 'weekend_torquay': ['51 t 4'],\n 'weekend_plymouth': ['15 p 4', '22 p 4'],\n 'night': ['44 e 4', '18 p 4', '3 t 4']}},\n 11: {'Mon': {'exeter_twilight': ['32 e 4'],\n 'truro_twilight': ['4 t 4'],\n 'torquay_twilight': ['50 t 4'],\n 'plymouth_twilight': ['22 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['25 p 4', '26 p 4', '58 t 4']},\n 'Tue': {'exeter_twilight': ['37 e 4'],\n 'truro_twilight': ['13 t 4'],\n 'torquay_twilight': ['54 t 4'],\n 'plymouth_twilight': ['21 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['38 e 4', '16 p 4', '47 t 4']},\n 'Wed': {'exeter_twilight': ['38 e 4'],\n 'truro_twilight': ['7 t 4'],\n 'torquay_twilight': ['59 t 4'],\n 'plymouth_twilight': ['19 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['24 p 4', '29 p 4', '14 t 4']},\n 'Thu': {'exeter_twilight': ['42 e 4'],\n 'truro_twilight': ['2 t 4'],\n 'torquay_twilight': ['58 t 4'],\n 'plymouth_twilight': ['27 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['30 e 4', '28 p 4', '9 t 4']},\n 'Fri': {'exeter_twilight': ['30 e 4'],\n 'truro_twilight': ['12 t 4'],\n 'torquay_twilight': ['46 t 4'],\n 'plymouth_twilight': ['16 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['43 e 4', '59 t 4', '7 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['37 e 4'],\n 'weekend_truro': ['3 t 4'],\n 'weekend_torquay': ['54 t 4'],\n 'weekend_plymouth': ['18 p 4', '20 p 4'],\n 'night': ['38 e 4', '52 t 4', '56 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['42 e 4'],\n 'weekend_truro': ['14 t 4'],\n 'weekend_torquay': ['47 t 4'],\n 'weekend_plymouth': ['23 p 4', '26 p 4'],\n 'night': ['33 e 4', '28 p 4', '56 t 4']}},\n 12: {'Mon': {'exeter_twilight': ['40 e 4'],\n 'truro_twilight': ['9 t 4'],\n 'torquay_twilight': ['49 t 4'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['33 e 4', '25 p 4', '6 t 4']},\n 'Tue': {'exeter_twilight': ['31 e 4'],\n 'truro_twilight': ['12 t 4'],\n 'torquay_twilight': ['50 t 4'],\n 'plymouth_twilight': ['25 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['33 e 4', '54 t 4', '58 t 4']},\n 'Wed': {'exeter_twilight': ['39 e 4'],\n 'truro_twilight': ['12 t 4'],\n 'torquay_twilight': ['59 t 4'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['21 p 4', '1 t 4', '14 t 4']},\n 'Thu': {'exeter_twilight': ['40 e 4'],\n 'truro_twilight': ['4 t 4'],\n 'torquay_twilight': ['46 t 4'],\n 'plymouth_twilight': ['17 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['24 p 4', '49 t 4', '51 t 4']},\n 'Fri': {'exeter_twilight': ['41 e 4'],\n 'truro_twilight': ['6 t 4'],\n 'torquay_twilight': ['53 t 4'],\n 'plymouth_twilight': ['25 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['20 p 4', '49 t 4', '59 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['32 e 4'],\n 'weekend_truro': ['11 t 4'],\n 'weekend_torquay': ['59 t 4'],\n 'weekend_plymouth': ['24 p 4', '28 p 4'],\n 'night': ['17 p 4', '51 t 4', '8 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['44 e 4'],\n 'weekend_truro': ['3 t 4'],\n 'weekend_torquay': ['55 t 4'],\n 'weekend_plymouth': ['21 p 4', '27 p 4'],\n 'night': ['22 p 4', '25 p 4', '8 t 4']}},\n 13: {'Mon': {'exeter_twilight': ['43 e 4'],\n 'truro_twilight': ['14 t 4'],\n 'torquay_twilight': ['55 t 4'],\n 'plymouth_twilight': ['22 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['32 e 4', '19 p 4', '58 t 4']},\n 'Tue': {'exeter_twilight': ['34 e 4'],\n 'truro_twilight': ['6 t 4'],\n 'torquay_twilight': ['51 t 4'],\n 'plymouth_twilight': ['26 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['31 e 4', '45 t 4', '47 t 4']},\n 'Wed': {'exeter_twilight': ['43 e 4'],\n 'truro_twilight': ['12 t 4'],\n 'torquay_twilight': ['56 t 4'],\n 'plymouth_twilight': ['26 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['36 e 4', '42 e 4', '52 t 4']},\n 'Thu': {'exeter_twilight': ['32 e 4'],\n 'truro_twilight': ['9 t 4'],\n 'torquay_twilight': ['49 t 4'],\n 'plymouth_twilight': ['29 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['33 e 4', '58 t 4', '7 t 4']},\n 'Fri': {'exeter_twilight': ['32 e 4'],\n 'truro_twilight': ['10 t 4'],\n 'torquay_twilight': ['46 t 4'],\n 'plymouth_twilight': ['16 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['38 e 4', '44 e 4', '56 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['36 e 4'],\n 'weekend_truro': ['5 t 4'],\n 'weekend_torquay': ['53 t 4'],\n 'weekend_plymouth': ['16 p 4', '28 p 4'],\n 'night': ['35 e 4', '44 e 4', '26 p 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['41 e 4'],\n 'weekend_truro': ['8 t 4'],\n 'weekend_torquay': ['46 t 4'],\n 'weekend_plymouth': ['22 p 4', '26 p 4'],\n 'night': ['28 p 4', '48 t 4', '49 t 4']}},\n 14: {'Mon': {'exeter_twilight': ['32 e 4'],\n 'truro_twilight': ['13 t 4'],\n 'torquay_twilight': ['53 t 4'],\n 'plymouth_twilight': ['18 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['17 p 4', '28 p 4', '48 t 4']},\n 'Tue': {'exeter_twilight': ['42 e 4'],\n 'truro_twilight': ['14 t 4'],\n 'torquay_twilight': ['53 t 4'],\n 'plymouth_twilight': ['21 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['20 p 4', '24 p 4', '28 p 4']},\n 'Wed': {'exeter_twilight': ['44 e 4'],\n 'truro_twilight': ['8 t 4'],\n 'torquay_twilight': ['45 t 4'],\n 'plymouth_twilight': ['16 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['34 e 4', '53 t 4', '13 t 4']},\n 'Thu': {'exeter_twilight': ['41 e 4'],\n 'truro_twilight': ['13 t 4'],\n 'torquay_twilight': ['53 t 4'],\n 'plymouth_twilight': ['15 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['32 e 4', '40 e 4', '20 p 4']},\n 'Fri': {'exeter_twilight': ['44 e 4'],\n 'truro_twilight': ['11 t 4'],\n 'torquay_twilight': ['45 t 4'],\n 'plymouth_twilight': ['21 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['41 e 4', '42 e 4', '46 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['42 e 4'],\n 'weekend_truro': ['7 t 4'],\n 'weekend_torquay': ['53 t 4'],\n 'weekend_plymouth': ['24 p 4', '26 p 4'],\n 'night': ['43 e 4', '19 p 4', '47 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['40 e 4'],\n 'weekend_truro': ['9 t 4'],\n 'weekend_torquay': ['56 t 4'],\n 'weekend_plymouth': ['21 p 4', '23 p 4'],\n 'night': ['51 t 4', '57 t 4', '3 t 4']}},\n 15: {'Mon': {'exeter_twilight': ['37 e 4'],\n 'truro_twilight': ['4 t 4'],\n 'torquay_twilight': ['50 t 4'],\n 'plymouth_twilight': ['21 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['22 p 4', '45 t 4', '9 t 4']},\n 'Tue': {'exeter_twilight': ['39 e 4'],\n 'truro_twilight': ['7 t 4'],\n 'torquay_twilight': ['54 t 4'],\n 'plymouth_twilight': ['23 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['30 e 4', '36 e 4', '10 t 4']},\n 'Wed': {'exeter_twilight': ['34 e 4'],\n 'truro_twilight': ['10 t 4'],\n 'torquay_twilight': ['55 t 4'],\n 'plymouth_twilight': ['27 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['36 e 4', '48 t 4', '12 t 4']},\n 'Thu': {'exeter_twilight': ['37 e 4'],\n 'truro_twilight': ['9 t 4'],\n 'torquay_twilight': ['52 t 4'],\n 'plymouth_twilight': ['15 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['32 e 4', '38 e 4', '13 t 4']},\n 'Fri': {'exeter_twilight': ['33 e 4'],\n 'truro_twilight': ['2 t 4'],\n 'torquay_twilight': ['48 t 4'],\n 'plymouth_twilight': ['28 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['43 e 4', '46 t 4', '54 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['33 e 4'],\n 'weekend_truro': ['12 t 4'],\n 'weekend_torquay': ['45 t 4'],\n 'weekend_plymouth': ['19 p 4', '28 p 4'],\n 'night': ['36 e 4', '39 e 4', '48 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['31 e 4'],\n 'weekend_truro': ['11 t 4'],\n 'weekend_torquay': ['56 t 4'],\n 'weekend_plymouth': ['17 p 4', '25 p 4'],\n 'night': ['21 p 4', '48 t 4', '54 t 4']}},\n 16: {'Mon': {'exeter_twilight': ['44 e 4'],\n 'truro_twilight': ['7 t 4'],\n 'torquay_twilight': ['55 t 4'],\n 'plymouth_twilight': ['27 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['18 p 4', '21 p 4', '10 t 4']},\n 'Tue': {'exeter_twilight': ['36 e 4'],\n 'truro_twilight': ['12 t 4'],\n 'torquay_twilight': ['58 t 4'],\n 'plymouth_twilight': ['24 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['22 p 4', '57 t 4', '7 t 4']},\n 'Wed': {'exeter_twilight': ['33 e 4'],\n 'truro_twilight': ['14 t 4'],\n 'torquay_twilight': ['48 t 4'],\n 'plymouth_twilight': ['25 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['43 e 4', '57 t 4', '1 t 4']},\n 'Thu': {'exeter_twilight': ['34 e 4'],\n 'truro_twilight': ['6 t 4'],\n 'torquay_twilight': ['56 t 4'],\n 'plymouth_twilight': ['23 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['41 e 4', '20 p 4', '10 t 4']},\n 'Fri': {'exeter_twilight': ['37 e 4'],\n 'truro_twilight': ['5 t 4'],\n 'torquay_twilight': ['49 t 4'],\n 'plymouth_twilight': ['19 p 4'],\n 'weekend_exeter': [],\n 'weekend_truro': [],\n 'weekend_torquay': [],\n 'weekend_plymouth': [],\n 'night': ['42 e 4', '43 e 4', '10 t 4']},\n 'Sat': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['40 e 4'],\n 'weekend_truro': ['10 t 4'],\n 'weekend_torquay': ['46 t 4'],\n 'weekend_plymouth': ['19 p 4', '25 p 4'],\n 'night': ['31 e 4', '5 t 4', '6 t 4']},\n 'Sun': {'exeter_twilight': [],\n 'truro_twilight': [],\n 'torquay_twilight': [],\n 'plymouth_twilight': [],\n 'weekend_exeter': ['37 e 4'],\n 'weekend_truro': ['10 t 4'],\n 'weekend_torquay': ['57 t 4'],\n 'weekend_plymouth': ['27 p 4', '29 p 4'],\n 'night': ['19 p 4', '28 p 4', '5 t 4']}}}" - }, - "metadata": {}, - "execution_count": 101 + "output_type": "stream", + "name": "stdout", + "text": "None\n" } ], "source": [ - "week_table" + "print(multi)" ] }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -2547,7 +334,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -2556,7 +343,7 @@ }, { "cell_type": "code", - "execution_count": 104, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -2565,7 +352,7 @@ "text/plain": "1" }, "metadata": {}, - "execution_count": 104 + "execution_count": 16 } ], "source": [ @@ -2574,7 +361,7 @@ }, { "cell_type": "code", - "execution_count": 105, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -2583,7 +370,7 @@ "text/plain": "0" }, "metadata": {}, - "execution_count": 105 + "execution_count": 17 } ], "source": [ @@ -2592,7 +379,7 @@ }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -2603,7 +390,7 @@ }, { "cell_type": "code", - "execution_count": 107, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -2613,7 +400,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0ma\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'a' is not defined" ] } @@ -2624,7 +411,7 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -2633,7 +420,7 @@ "text/plain": "0" }, "metadata": {}, - "execution_count": 108 + "execution_count": 20 } ], "source": [ @@ -2642,7 +429,7 @@ }, { "cell_type": "code", - "execution_count": 109, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -2651,7 +438,7 @@ "text/plain": "6" }, "metadata": {}, - "execution_count": 109 + "execution_count": 21 } ], "source": [ @@ -2660,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": 110, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -2670,7 +457,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshift_count\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"night\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mmodel\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshift_count\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"night\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'model' is not defined" ] } @@ -2681,7 +468,7 @@ }, { "cell_type": "code", - "execution_count": 111, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -2691,7 +478,7 @@ "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m[\u001b[0m\u001b[0mw\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mid\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mw\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mworkers\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;34m[\u001b[0m\u001b[0mw\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mid\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mw\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mworkers\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mNameError\u001b[0m: name 'workers' is not defined" ] } @@ -2707,5 +494,28 @@ "outputs": [], "source": [] } - ] + ], + "metadata": { + "language_info": { + "name": "python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "version": "3.7.5-final" + }, + "orig_nbformat": 2, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "npconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": 3, + "kernelspec": { + "name": "python37564bit5ba0d1a6f89f489d9799b0f9b976a56e", + "display_name": "Python 3.7.5 64-bit" + } + }, + "nbformat": 4, + "nbformat_minor": 2 } \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000..2bf3648 --- /dev/null +++ b/run.py @@ -0,0 +1,294 @@ +from pyomo.environ import * +from pyomo.opt import SolverFactory +from shifts import SingleShift, RotaBuilder, RotaResults, days +from workers import Worker + +from copy import deepcopy + +import math + +import scipy.stats as stats + +import statistics + +import datetime +import itertools +import operator + +weeks_to_rota = 26 + +sites = ("truro", "exeter", "torquay", "barnstaple", "plymouth") + +Rota = RotaBuilder(datetime.date(2020, 9, 7), + weeks_to_rota=weeks_to_rota, + balance_offset_modifier=1) + +rota_collections = { + "current": deepcopy(Rota), + "extended proc nights only": deepcopy(Rota), + "no weekends": deepcopy(Rota), + "no twighlights": deepcopy(Rota), + "no nights": deepcopy(Rota), + "nights + proc twighlights + normal weekends": deepcopy(Rota), + "nights + proc twighlights + proc weekends": deepcopy(Rota), +} + +rota_collections['current'].add_shifts( + SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:5]), + SingleShift(("truro", ), "truro_twilight", 12.5, days[:5]), + SingleShift(("torquay", ), "torquay_twilight", 12.5, days[:5]), + SingleShift(("plymouth", ), "plymouth_twilight", 12.5, days[:5]), + SingleShift(("exeter", ), + "weekend_exeter", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("truro", ), + "weekend_truro", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("torquay", ), + "weekend_torquay", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("plymouth", ), + "weekend_plymouth", + 8, + days[5:], + workers_required=2, + assign_as_block=True), + #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), + SingleShift((sites), + "night", + 12.25, + days, + balance_offset=2, + balance_weighting=1, + workers_required=3, + rota_on_nwds=True), +) + +rota_collections['extended proc nights only'].add_shifts( + #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), + SingleShift((sites), + "night", + 13, + days, + balance_offset=2, + balance_weighting=1, + workers_required=3, + rota_on_nwds=True), ) + +rota_collections['no weekends'].add_shifts( + SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:5]), + SingleShift(("truro", ), "truro_twilight", 12.5, days[:5]), + SingleShift(("torquay", ), "torquay_twilight", 12.5, days[:5]), + SingleShift(("plymouth", ), "plymouth_twilight", 12.5, days[:5]), + #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), + SingleShift((sites), + "night", + 13, + days, + balance_offset=2, + balance_weighting=1, + workers_required=3, + rota_on_nwds=True), +) + +rota_collections['no twighlights'].add_shifts( + SingleShift(("exeter", ), + "weekend_exeter", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("truro", ), + "weekend_truro", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("torquay", ), + "weekend_torquay", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("plymouth", ), + "weekend_plymouth", + 8, + days[5:], + workers_required=2, + assign_as_block=True), + #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), + SingleShift((sites), + "night", + 13, + days, + balance_offset=2, + balance_weighting=1, + workers_required=3, + rota_on_nwds=True), +) + +rota_collections['no nights'].add_shifts( + SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:5]), + SingleShift(("truro", ), "truro_twilight", 12.5, days[:5]), + SingleShift(("torquay", ), "torquay_twilight", 12.5, days[:5]), + SingleShift(("plymouth", ), "plymouth_twilight", 12.5, days[:5]), + SingleShift(("exeter", ), + "weekend_exeter", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("truro", ), + "weekend_truro", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("torquay", ), + "weekend_torquay", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("plymouth", ), + "weekend_plymouth", + 8, + days[5:], + workers_required=2, + assign_as_block=True), + #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), +) + +rota_collections['nights + proc twighlights + normal weekends'].add_shifts( + SingleShift((sites), "proc_twilight", 12.5, days[:5], workers_required=4), + SingleShift(("exeter", ), + "weekend_exeter", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("truro", ), + "weekend_truro", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("torquay", ), + "weekend_torquay", + 12.5, + days[5:], + assign_as_block=True), + SingleShift(("plymouth", ), + "weekend_plymouth", + 8, + days[5:], + workers_required=2, + assign_as_block=True), + #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), + SingleShift((sites), + "night", + 12.25, + days, + balance_offset=2, + balance_weighting=1, + workers_required=3, + rota_on_nwds=True), +) + +rota_collections['nights + proc twighlights + proc weekends'].add_shifts( + SingleShift((sites), + "proc_twilight_and_weekends", + 12.5, + days, + workers_required=3), + #SingleShift((sites), "night", 12.5, days, balance_weighting=1, workers_required=3, rota_on_nwds=True), + SingleShift((sites), + "night", + 12.25, + days, + balance_offset=2, + balance_weighting=1, + workers_required=3, + rota_on_nwds=True), +) + +# Import trainee data +import csv + +with open('trainees.csv', newline='') as f: + reader = csv.reader(f, delimiter=',', quotechar='|') + next(reader) # skip header + + n = 0 + for row in reader: + n = n + 1 + #print(row) + name, site, grade, fte, nwd, end_date, oop, extra = row + + # Ignore trainees if fte == 0 (what are they doing here anyway) + if fte == 0: + continue + + nwds = nwd.split(",").title() if nwd else None + end_date = end_date if end_date else None + oop = oop.split("-") if oop else None + #print(nwds, end_date, oop) + for rot in rota_collections: + rota_collections[rot].add_worker( + Worker(Rota, n, name, site.lower(), int(grade[2]), int(fte), + nwds, end_date, oop)) + +with open("rotas.html", "w") as f: + f.write("") + +for rot in rota_collections: + print("Running rota: ", rot) + Rota = rota_collections[rot] + + Rota.build_shifts_and_workers() + + Rota.build_model() + + opt = SolverFactory('cbc') # choose a solver + #opt = SolverFactory('ipopt') # choose a solver + + results = opt.solve( + Rota.model, tee=True, options={ + "seconds": 1200, + "allow": 3500 + }) # solve the model with the, options="seconds=60" selected solver + + results.solver.status + + ResultsHolder = RotaResults(Rota, results) + + week_table = ResultsHolder.get_work_table( + ) # list with the required workers + worker_timetable = ResultsHolder.get_worker_timetable() + worker_timetable_brief = ResultsHolder.get_worker_timetable_brief( + show_prefs=False) + + worker_shift_summary = ResultsHolder.get_shift_summary() + + ResultsHolder.export_rota_to_csv("rotas/" + rot) + with open("rotas.html", "a") as f: + f.write("----------------------------") + f.write("

{}

".format(rot)) + f.write("----------------------------") + f.write("\n\n") + + for s in Rota.shifts: + f.write("
{}
".format(s.get_shift_summary())) + f.write("\n") + f.write("\n\n") + + f.write("
{}
".format(worker_timetable_brief)) + f.write("\n\n") + f.write("
{}
".format(worker_shift_summary)) + f.write("\n\n\n") + #workers_no_pref = ResultsHolder.get_no_preference(Rota.model.no_pref) # list with the non-satisfied workers (work on Sat but not on Sun) + worker_night_block = ResultsHolder.get_night_blocks() + multi = ResultsHolder.get_multinight() + + #break + +with open("rotas.html", "a") as f: + f.write("") \ No newline at end of file diff --git a/shifts.py b/shifts.py index a13a7da..016f572 100644 --- a/shifts.py +++ b/shifts.py @@ -2,11 +2,15 @@ import datetime import itertools from typing import List, Tuple -from workers import Worker - from pyomo.environ import * from pyomo.opt import SolverFactory +from workers import Worker + +import csv + +from collections import defaultdict + ShiftName = str DayStr = str WeekInt = int @@ -17,12 +21,9 @@ days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] # weeks_days_product = list(itertools.product(weeks, days)) -sites = ("truro", "exeter", "torquay", "barnstaple", "plymouth") - class SingleShift(object): """Class to hold all details for a shift""" - def __init__( self, sites, @@ -30,10 +31,13 @@ class SingleShift(object): length, shift_days, # balance_by_site=True, - balance_offset=1, # this could be generated dynamically - balance_weighting=1, # this could be generated dynamically + balance_offset=2, # this could be generated dynamically + balance_weighting=1, workers_required=1, rota_on_nwds=False, + assign_as_block=False, + force_as_block=False, + constraints=[], ): self.site = sites self.name = name @@ -50,30 +54,52 @@ class SingleShift(object): self.workers_required = workers_required self.rota_on_nwds = rota_on_nwds + self.assign_as_block = assign_as_block + self.force_as_block = force_as_block + self.constraints = constraints self.total_shifts = length * len(days) * len(shift_days) + def get_shift_summary(self): + """ + Returns a text summary of the shift + """ + + return "{}: {} workers for sites ({}) on days ({})".format( + self.name, self.workers_required, ", ".join(self.site), + ", ".join(self.shift_days)) + class RotaBuilder(object): """Class to hold and manipulate shifts""" - - def __init__(self, weeks_to_rota=26): + def __init__(self, + start_date, + weeks_to_rota=26, + balance_offset_modifier=1): self.shifts = [] # type List[SingleShift] + self.days = days + self.weeks = [i for i in range(1, weeks_to_rota + 1)] self.weeks_days_product = list(itertools.product(self.weeks, days)) - self.start_date = datetime.date(2020, 9, 7) + self.start_date = start_date self.rota_days_length = len(self.weeks) * 7 - self.rota_end_date = self.start_date + datetime.timedelta(self.rota_days_length) + self.rota_end_date = self.start_date + datetime.timedelta( + self.rota_days_length) self.unavailable_to_work = set() + self.pref_not_to_work = {} self.workers = [] self.night_blocks = ["weekday", "weekend", "none"] + self.sites = None + + self.balance_offset_modifier = balance_offset_modifier + def build_model(self): # Initialize model self.model = ConcreteModel() @@ -81,55 +107,95 @@ class RotaBuilder(object): # binary variables representing if a worker is scheduled somewhere # NOTE: this will assign to all possible shift combinations (which we probably don't want) self.model.works = Var( - ( - (worker.id, week, day, shift) - for worker in self.workers - for week, day in self.get_week_day_combinations() - for shift in self.get_shift_names() - ), + ((worker.id, week, day, shift) for worker in self.workers + for week, day in self.get_week_day_combinations() + for shift in self.get_shift_names()), within=Binary, initialize=0, ) # The nights self.model is used to ensure nights are assigned as a block (and limit the number of workers required) self.model.nights = Var( - ( - (worker.id, week, block) - for worker in self.workers - for week in self.weeks - for block in self.night_blocks - ), + ((worker.id, week, block) for worker in self.workers + for week in self.weeks for block in self.night_blocks), within=Binary, initialize=0, ) - self.model.shift_count = Var( - ( - (worker.id, shift) - for worker in self.workers - for shift in self.get_shift_names() - ), - domain=NonNegativeReals, + # The nights self.model is used to ensure nights are assigned as a block (and limit the number of workers required) + self.model.shift_week_worker_assigned = Var( + ((shift, week, worker.id) for worker in self.workers + for week in self.weeks for shift in self.get_shift_names()), + within=Binary, initialize=0, ) - # self.model.shift_count_t1 = Var(((worker.id, shift) for worker in self.workers for shift in self.get_shift_names()), domain=NonNegativeReals, initialize=0) - # self.model.shift_count_t2 = Var(((worker.id, shift) for worker in self.workers for shift in self.get_shift_names()), domain=NonNegativeReals, initialize=0) + self.model.worker_block_shift_counts = Var( + ((worker.id, week, shift) for week in self.weeks + for shift in self.shifts_to_assign_or_force_as_blocks() + for worker in self.workers), + within=NonNegativeIntegers, + initialize=0, + ) + + self.model.blocks_worker_shift_assigned = Var( + ((worker.id, week, shift) for worker in self.workers + for week in self.weeks + for shift in self.shifts_to_assign_or_force_as_blocks()), + within=Binary, + initialize=0, + ) + + self.model.blocks_assigned = Var( + ((week, shift) for week in self.weeks + for shift in self.shifts_to_assign_or_force_as_blocks()), + within=NonNegativeIntegers, + initialize=0, + ) + + # Used to limit number of workers on night shift per site + # if we force a binary it will in effect hard constrain to < 2 + # from a single site + self.model.night_per_site = Var( + ((week, block, site) for site in self.sites for week in self.weeks + for block in ["weekday", "weekend"]), + #within=Binary, + within=NonNegativeIntegers, + initialize=0, + ) + + self.model.night_per_site_t1 = Var( + ((week, block, site) for site in self.sites for week in self.weeks + for block in ["weekday", "weekend"]), + domain=NonNegativeIntegers, + initialize=0, + ) + self.model.night_per_site_t2 = Var( + ((week, block, site) for site in self.sites for week in self.weeks + for block in ["weekday", "weekend"]), + domain=NonNegativeIntegers, + initialize=0, + ) + + self.model.shift_count = Var( + ((worker.id, shift) for worker in self.workers + for shift in self.get_shift_names()), + domain=NonNegativeReals, + initialize=0, + ) self.model.shift_count_t1 = Var( ((worker.id) for worker in self.workers), domain=NonNegativeReals, initialize=0, ) + self.model.shift_count_t2 = Var( ((worker.id) for worker in self.workers), domain=NonNegativeReals, initialize=0, ) - self.model.shift_count_t3 = Var( - (worker.id for worker in self.workers), initialize=0 - ) # self.model.unavailable = Var(((worker, week, day) for worker in self.workers for week in weeks for day in days), # within=Binary, initialize=0) @@ -140,14 +206,25 @@ class RotaBuilder(object): return 1 self.model.available = Param( - ( - (worker.id, week, day) - for worker in self.workers - for week, day in self.get_week_day_combinations() - ), + ((worker.id, week, day) for worker in self.workers + for week, day in self.get_week_day_combinations()), initialize=availability_init, ) + def pref_init(model, wid, week, day): + if (wid, week, day) in self.pref_not_to_work: + return self.pref_not_to_work[(wid, week, day)] + return 0 + + # It would also be possible to assign sequential days as blocks + # which would allow more weight to be given if the whole block + # is not allocated + self.model.pref_not_to_work = Param( + ((worker.id, week, day) for worker in self.workers + for week, day in self.get_week_day_combinations()), + initialize=pref_init, + ) + # binary variables representing if a worker is necessary # self.model.needed = Var([worker.id for worker in self.workers], within=Binary, initialize=0) @@ -157,33 +234,33 @@ class RotaBuilder(object): self.build_model_constraints() def build_model_constraints(self): - self.model.constraints = ConstraintList() # Create a set of constraints + self.model.constraints = ConstraintList( + ) # Create a set of constraints + # Ensure each shift has the requisit number of workers assigned for ( - week, - day, - shift, - self.workers_required, - site_required, + week, + day, + shift, + workers_required, + site_required, ) in self.get_required_workers_and_site_combinations(): # print(week, day, shift, self.workers_required, site_required) - self.model.constraints.add( - self.workers_required - == sum( - self.model.works[worker.id, week, day, shift] - for worker in self.workers - if worker.site in site_required - ) - ) + self.model.constraints.add(workers_required == sum( + self.model.works[worker.id, week, day, shift] + for worker in self.workers if worker.site in site_required)) + # # Ensure shifts are only assigned by workers from the allowed sites + # self.model.constraints.add(0 == sum( + # self.model.works[worker.id, week, day, shift] + # for worker in self.workers if worker.site not in site_required)) + + # Ensure no workers are assigned to shifts that are not required for week, day, shift in self.get_not_required_shifts(): - self.model.constraints.add( - 0 - == sum( - self.model.works[worker.id, week, day, shift.name] - for worker in self.workers - ) - ) + self.model.constraints.add(0 == sum(self.model.works[worker.id, + week, day, + shift.name] + for worker in self.workers)) # Constraint: total hours worked hours worked # for worker in self.workers: @@ -197,69 +274,56 @@ class RotaBuilder(object): # 48 >= sum(shift_lengths[shift] * self.model.works[worker, week, day, shift] for day in days for shift in days_shifts[day]) # ) + # Define a maximum number hours that can be worked per week + # NOTE: this works on a calander week not a 7 day period def maxHoursPerWeekRule(model, wid, week): return 72 >= sum( shift.length * model.works[wid, week, day, shift.name] - for day, shift in self.get_day_shiftclass_products() - ) + for day, shift in self.get_day_shiftclass_combinations()) self.model.max_hours_per_week_constraint = Constraint( - [worker.id for worker in self.workers], self.weeks, rule=maxHoursPerWeekRule - ) + [worker.id for worker in self.workers], + self.weeks, + rule=maxHoursPerWeekRule) + # Define an upper limit for hours worked over the rota period def maxHoursRule(model, wid): return 8000 >= sum( shift.length * model.works[wid, week, day, shift.name] - for week, day, shift in self.get_all_shiftclass_combinations() - ) + for week, day, shift in self.get_all_shiftclass_combinations()) self.model.max_hours_constraint = Constraint( - [worker.id for worker in self.workers], rule=maxHoursRule - ) + [worker.id for worker in self.workers], rule=maxHoursRule) - # self should not be allocated on nwds (excepting night shifts) - def nwdRule(model, worker): - print(worker, worker.id, worker.nwd) - wid = worker.id - nwd = worker.nwd - return 0 == sum( - model.works[wid, week, day, shift.name] - for week, day, shift in self.get_all_shiftclass_combinations() - if (not shift.rota_on_nwds and day in nwd) - ) + # # Limit to 1 ST2 (or below) on a night shift + # def nightShiftMaxSTRule(model, week, block): + # """Limits to 1 ST2 (or below) per night""" + # single_workers = [w for w in self.workers if w.grade < 3] + # if not single_workers: + # return Constraint.Skip + # return sum(model.nights[w.id, week, block] + # for w in single_workers) <= 1 - # self.model.nwd_constraint = Constraint([(worker) for worker in self.workers if worker.nwd], rule=nwdRule) - - # Set a rule to even twilight shifts: - # total_twilight_shifts = len(weeks) * 5 * 1 # One person per site - - # def trurotwilightShiftNumberRule(self.model, wid, fte_adj, site, shift): - # #site, shift = site_and_shift - # max_shifts = total_twilight_shifts / self.full_time_equivalent_sites[site] * fte_adj + twilight_balance_offset - # min_shifts = total_twilight_shifts / self.full_time_equivalent_sites[site] * fte_adj - twilight_balance_offset - - # return inequality(min_shifts, sum(self.model.works[wid, week, day, shift.GetShift()] for week in weeks for day in days), max_shifts) - # self.model.twilight_shifts_truro_constraint = Constraint([(worker.id, worker.fte_adj, ss[0], ss[1]) for ss in site_and_shifts for worker in self.workers if worker.site == ss[0]], rule=trurotwilightShiftNumberRule) - - # def plymouthtwilightShiftNumberRule(self.model, wid, fte_adj): - # max_shifts = total_twilight_shifts / self.full_time_equivalent_sites["plymouth"] * fte_adj + twilight_balance_offset - # min_shifts = total_twilight_shifts / self.full_time_equivalent_sites["plymouth"] * fte_adj - twilight_balance_offset - # - # return inequality(min_shifts, sum(self.model.works[wid, week, day, "plymouth_twilight"] for week in weeks for day in days), max_shifts) - # self.model.twilight_shifts_plymouth_constraint = Constraint([(worker.id, worker.fte_adj) for worker in self.workers if worker.site == "plymouth"], #rule=plymouthtwilightShiftNumberRule) + # # self.model.night_shifts_max_st_constraint = Constraint( + # # [week for week in self.weeks], + # # [block for block in ["weekday", "weekend"]], + # # rule=nightShiftMaxSTRule, + # # ) # Limit to 1 ST2 (or below) on a night shift - def nightShiftMaxSTRule(model, week, block): + def nightShiftMaxSTRule_new(model, week, day, shift): """Limits to 1 ST2 (or below) per night""" single_workers = [w for w in self.workers if w.grade < 3] if not single_workers: return Constraint.Skip - return sum(model.nights[w.id, week, block] for w in single_workers) <= 1 + return sum(model.works[w.id, week, day, shift] + for w in single_workers) <= 1 self.model.night_shifts_max_st_constraint = Constraint( [week for week in self.weeks], - [block for block in ["weekday", "weekend"]], - rule=nightShiftMaxSTRule, + [day for day in self.days], + [shift.name for shift in self.get_shifts_with_constraint("night")], + rule=nightShiftMaxSTRule_new, ) # Enusre at least 1 ST4+ on a night shift @@ -267,7 +331,8 @@ class RotaBuilder(object): single_workers = [w for w in self.workers if w.grade > 3] if not single_workers: return Constraint.Skip - return sum(model.nights[w.id, week, block] for w in single_workers) >= 1 + return sum(model.nights[w.id, week, block] + for w in single_workers) >= 1 self.model.night_shifts_min_st4_constraint = Constraint( [week for week in self.weeks], @@ -275,169 +340,175 @@ class RotaBuilder(object): rule=nightShiftMinST4Rule, ) - # Constraint (def of self.model.needed) + # #Constraint (def of self.model.needed) # for worker in self.workers: - # self.model.constraints.add( - # 10000 * self.model.needed[worker.id] >= sum(self.model.works[worker.id, week, day, shift] for week in weeks for day in days for shift in days_shifts[day]) - # ) # if any self.model.works[worker, ·, ·] non-zero, self.model.needed[worker] must be one; else is zero to reduce the obj function - # 10000 is to remark, but 5 was enough since max of 40 hours yields max of 5 shifts, the maximum possible sum + # self.model.constraints.add( + # 10000 * self.model.needed[worker.id] >= sum( + # self.model.works[worker.id, week, day, shift] for week, + # day, shift in self.get_all_shiftname_combinations()) + # ) # if any self.model.works[worker, ·, ·] non-zero, self.model.needed[worker] must be one; else is zero to reduce the obj function + # #10000 is to remark, but 5 was enough since max of 40 hours yields max of 5 shifts, the maximum possible sum - # Constraint (def of self.model.no_pref) + # #Constraint (def of self.model.no_pref) # for worker in self.workers: - # for week in weeks: - # self.model.constraints.add( - # self.model.no_pref[worker.id] >= sum(self.model.works[worker.id, week, 'Sat', shift] for shift in days_shifts['Sat']) - # - sum(self.model.works[worker.id, week, 'Sun', shift] for shift in days_shifts['Sun']) - # ) # if not working on sunday but working saturday self.model.needed must be 1; else will be zero to reduce the obj function + # for week in weeks: + # self.model.constraints.add( + # self.model.no_pref[worker.id] >= + # sum(self.model.works[worker.id, week, 'Sat', shift] + # for shift in self.get_shift_names_by_day("Sat")) - + # sum(self.model.works[worker.id, week, 'Sun', shift] + # for shift in self.get_shift_names_by_day("Sun")) + # ) # if not working on sunday but working saturday self.model.needed must be 1; else will be zero to reduce the obj function - # def balanceSiteShiftNumberRule(model, wid, fte_adj, shiftClass): - # # TODO: make helper to retrieve - # total_shifts = len(self.weeks) * len(shiftClass.shift_days) * shiftClass.self.workers_required # One person per site + # Count the number of workers from each site on each night shift + # As 1 or 0 is optimum we can simply subtract 1 from the number + for site in self.sites: + for block in ("weekend", "weekday"): + for week in self.weeks: + self.model.constraints.add( + self.model.night_per_site[week, block, site] == sum( + self.model.nights[worker.id, week, block] + for worker in self.workers if worker.site == site) + #1 >= sum(self.model.nights[worker.id, week, block] for worker in self.workers if worker.site == site) + ) + self.model.constraints.add( + self.model.night_per_site_t1[week, block, site] - + self.model.night_per_site_t2[week, block, site] == + self.model.night_per_site[week, block, site] - 1) - # full_time_equivalent_joined = sum([self.full_time_equivalent_sites[i] for i in shiftClass.site]) + weeks_days = self.get_week_day_combinations() - # max_shifts = total_shifts / full_time_equivalent_joined * fte_adj + twilight_balance_offset - # min_shifts = total_shifts / full_time_equivalent_joined * fte_adj - twilight_balance_offset + for week in self.weeks: + for shift_name in self.shifts_to_assign_or_force_as_blocks(): + shift = self.get_shift_by_name(shift_name) + for worker in self.workers: + self.model.constraints.add( + 8 * self.model.blocks_worker_shift_assigned[ + worker.id, week, shift_name] >= sum( + self.model.works[worker.id, week, day, + shift_name] + for day in shift.shift_days)) + self.model.constraints.add( + self.model.blocks_assigned[week, shift_name] == sum( + self.model.blocks_worker_shift_assigned[worker.id, + week, + shift_name] + for worker in self.workers)) - # return inequality(min_shifts, sum(self.model.works[wid, week, day, shiftClass.name] for week, day in self.get_week_day_combinations()), max_shifts) + if shift.force_as_block: + # Force nights to be assigned in blocks + self.model.constraints.add( + self.model.worker_block_shift_counts[worker.id, week, + shift.name] == + sum(self.model.works[worker.id, week, day, shift.name] + for day in self.days)) - # #self.model.balance_site_shift_constraint = Constraint([(worker.id, worker.fte_adj, shift) for shift in self.GetShiftOptions() for worker in self.workers if worker.site in shift.site and shift.balance_by_site], rule=balanceSiteShiftNumberRule) + self.model.constraints.add( + sum(self.model.blocks_worker_shift_assigned[worker.id, + week, + shift.name] + for worker in self.workers) <= 1) - # # set a rule to even night shifts (could also be done in blocks, which may be quicker?) - # # takes into accound both LTFT, OOP and leaving the training scheme - # #total_night_shifts = self.rota_days_length * self.GetShiftByName("night").self.workers_required - - # def nightShiftNumberRule(model, wid, fte_adj): # NOTE no longer used - see generic function below - # target_shifts = total_night_shifts / self.full_time_equivalent * fte_adj - # max_shifts = target_shifts + night_balance_offset - # min_shifts = target_shifts - night_balance_offset - - # return inequality(min_shifts, sum(model.works[wid, week, day, "night"] for week, day in self.get_week_day_combinations()), max_shifts) - # #self.model.night_shifts_constraint = Constraint([(worker.id, worker.fte_adj) for worker in self.workers], rule=nightShiftNumberRule) - # twilight_constraints = {} - - # Balance shifts + # Most of our constraints apply per worker for worker in self.workers: - for shift in self.GetShiftOptions(): + + # Balance shifts within required limits (set by balance_offset) + # If balance_offset is too restrictive (for the rota length) + # no solution will be possible + for shift in self.get_shifts(): if ( - worker.site in shift.site + worker.site in shift.site ): # Each site specfies which sites self.workers can fullfill it - # total_shifts = self.rota_days_length * shift.workers_required # One person per site - total_shifts = ( - len(self.weeks) - * len(shift.shift_days) - * shift.workers_required - ) + # calaculate the total number of shifts that need assigning over the rota + total_shifts = (len(self.weeks) * len(shift.shift_days) * + shift.workers_required) - # if shift.balance_by_site: - full_time_equivalent_joined = sum( - [self.full_time_equivalent_sites[i] for i in shift.site] - ) + full_time_equivalent_joined = sum([ + self.full_time_equivalent_sites[i] for i in shift.site + ]) - target_shifts = ( - total_shifts / full_time_equivalent_joined * worker.fte_adj - ) + target_shifts = (total_shifts / + full_time_equivalent_joined * + worker.fte_adj) worker.shift_target_number[shift.name] = target_shifts - max_shifts = target_shifts + shift.balance_offset - min_shifts = target_shifts - shift.balance_offset - # print(max_shifts, min_shifts) + max_shifts = target_shifts + shift.balance_offset * self.balance_offset_modifier + min_shifts = target_shifts - shift.balance_offset * self.balance_offset_modifier self.model.constraints.add( inequality( min_shifts, - sum( - self.model.works[worker.id, week, day, shift.name] - for week, day in self.get_week_day_combinations() - ), + sum(self.model.works[worker.id, week, day, + shift.name] for week, day in + self.get_week_day_combinations()), max_shifts, - ) - ) - # return inequality(min_shifts, sum(self.model.works[wid, week, day, shiftClass.name] for week, day in self.get_week_day_combinations()), max_shifts) + )) + else: + # Make sure shifts aren't assigned to those from other sites + self.model.constraints.add(0 == sum( + self.model.works[worker.id, week, day, shift.name] + for week, day in self.get_week_day_combinations())) + + # model.shift_count stores the number of shifts that a worker is assigned to worker by shift + # this is used (by the objective) to balance the total number of shifts worked + self.model.constraints.add( + self.model.shift_count[worker.id, shift.name] == sum( + self.model.works[worker.id, week, day, shift.name] + for week, day in self.get_week_day_combinations())) - for worker in self.workers: # Define shift_count_t1 and shift_count_t2 constraints for the object - # This bypassing the need for a quadratic solver + # Thus bypassing the need for a quadratic solver # t1-t2 is the target # As the objective is to minimise t1+t2 and t1 and t2 are positive reals # t1+t2 approximates the absolute target (which otherwise requires a quadratic solver) self.model.constraints.add( - # self.model.shift_count_t3[worker.id] == abs(sum(self.model.shift_count[worker.id, shift.name] - worker.shift_target_number[shift.name] for shift in self.GetShiftOptions())) - self.model.shift_count_t1[worker.id] - - self.model.shift_count_t2[worker.id] - == sum( - ( - self.model.shift_count[worker.id, shift.name] - - worker.shift_target_number[shift.name] - ) - * shift.balance_weighting - for shift in self.GetShiftOptions() - ) - ) - for shift in self.GetShiftOptions(): - self.model.constraints.add( - self.model.shift_count[worker.id, shift.name] - == sum( - self.model.works[worker.id, week, day, shift.name] - for week, day in self.get_week_day_combinations() - ) - ) - # self.model.constraints.add( - # #self.model.shift_count_t1[worker.id, shift.name]-self.model.shift_count_t2[worker.id, shift.name] == self.model.shift_count[worker.id, shift.name] - # self.model.shift_count_t1[worker.id, shift.name]-self.model.shift_count_t2[worker.id, shift.name] == self.model.shift_count[worker.id, shift.name] - worker.shift_target_number[shift.name] - # ) + self.model.shift_count_t1[worker.id] - + self.model.shift_count_t2[worker.id] == sum( + (self.model.shift_count[worker.id, shift.name] - + worker.shift_target_number[shift.name]) * + shift.balance_weighting for shift in self.get_shifts())) + # Ensure worker is not allocated shifts on non working days if worker.nwd: for week, day, shift in self.get_all_shiftclass_combinations(): if not shift.rota_on_nwds and day in worker.nwd: self.model.constraints.add( - 0 == self.model.works[worker.id, week, day, shift.name] - ) - - # Constraint: rest between two shifts is of 12 hours (i.e., at least two shifts) - weeks_days = self.get_week_day_combinations() - for worker in self.workers: - if "nights" in self.get_shift_names(): + 0 == self.model.works[worker.id, week, day, + shift.name]) + if "night" in self.get_shift_names(): for week_blocks in self.get_week_block_iterator(2): # Prevent nights more than once every n weeks self.model.constraints.add( - 1 - >= sum( - self.model.nights[worker.id, week, block] - for week in week_blocks - for block in ["weekday", "weekend"] - ) - ) + 1 >= sum(self.model.nights[worker.id, week, block] + for week in week_blocks + for block in ["weekday", "weekend"])) for week in self.weeks: - if "nights" in self.get_shift_names(): + + # self.model.constraints.add(1 == sum( + # self.model.shift_week_worker_assigned[s.name, week, worker.id] + # for s in self.get_shifts_with_constraint("night"))) + + if "night" in self.get_shift_names(): # Force nights to be assigned in blocks - self.model.constraints.add( - 1 - == sum( - self.model.nights[worker.id, week, block] - for block in self.night_blocks - ) - ) + self.model.constraints.add(1 == sum( + self.model.nights[worker.id, week, block] + for block in self.night_blocks)) # if night block is weekday make sure Mon - Thurs is assigned as nights self.model.constraints.add( - self.model.nights[worker.id, week, "weekday"] * 4 - == sum( - self.model.works[worker.id, week, day, "night"] - for day in days[:4] - ) - ) + self.model.nights[worker.id, week, "weekday"] * + 4 == sum(self.model.works[worker.id, week, day, + "night"] + for day in days[:4])) self.model.constraints.add( - self.model.nights[worker.id, week, "weekend"] * 3 - == sum( - self.model.works[worker.id, week, day, "night"] - for day in days[4:] - ) - ) + self.model.nights[worker.id, week, "weekend"] * + 3 == sum(self.model.works[worker.id, week, day, + "night"] + for day in days[4:])) # @@ -446,96 +517,116 @@ class RotaBuilder(object): week, day = weeks_days[n] p1 = 1 + p2 = 1 if n > 0: pweek, pday = weeks_days[n - 1] else: p1 = 0 pweek, pday = weeks_days[n] + try: + p2week, p2day = weeks_days[n - 2] + except IndexError: + p2week, p2day = weeks_days[n] + n2 = 0 + n1 = 1 n2 = 1 try: nweek, nday = weeks_days[n + 1] except IndexError: - # print(worker.id, "shit") nweek, nday = weeks_days[n] n1 = 0 try: - n2week, n2day = weeks_days[n + 2] except IndexError: - # print(worker.id, "shit2") n2week, n2day = weeks_days[n] n2 = 0 - # print(week, day, nweek, nday, n2week, n2day, n1, n2) # Unable to work (hard constraint not preference) self.model.constraints.add( - self.model.available[worker.id, week, day] - >= sum( + self.model.available[worker.id, week, day] >= sum( self.model.works[worker.id, week, day, shift] - for shift in self.get_shift_namesByDay(day) - ) - ) - - if "nights" in self.get_shift_names(): - self.model.constraints.add( - self.model.available[worker.id, week, day] - >= self.model.works[worker.id, pweek, pday, "night"] - ) + for shift in self.get_shift_names_by_day(day))) # single shift per day self.model.constraints.add( - 1 - >= sum( - self.model.works[worker.id, week, day, shift] - for shift in self.get_shift_namesByDay(day) - ) - ) - ## if working in evening, until next evening (note that after sunday comes next monday) - # self.model.constraints.add( - # 1 >= sum(self.model.works[worker.id, week, days[j], shift] for shift in ['evening', 'night']) + - # self.model.works[worker.id, nweek, days[(j + 1) % 7], 'truro_twilight'] - # ) + 1 >= sum(self.model.works[worker.id, week, day, shift] + for shift in self.get_shift_names_by_day(day))) # if working a night ensure preceeding (1) or subsequent (2) shifts can only be nights - if "nights" in self.get_shift_names(): + if "night" in self.get_shift_names(): + # Ensure night prior to unavalibity is not assigned + self.model.constraints.add( + self.model.available[worker.id, week, day] >= + self.model.works[worker.id, pweek, pday, "night"]) self.model.constraints.add( - 1 - >= self.model.works[worker.id, week, day, "night"] - + sum( - n1 * self.model.works[worker.id, nweek, nday, shift] - for shift in self.get_shift_namesByDay(nday) - if shift != "night" - ) - + sum( - n2 * self.model.works[worker.id, n2week, n2day, shift] - for shift in self.get_shift_namesByDay(n2day) - if shift != "night" - ) - + sum( - p1 * self.model.works[worker.id, pweek, pday, shift] - for shift in self.get_shift_namesByDay(pday) - if shift != "night" - ) - ) + 1 >= self.model.works[worker.id, week, day, "night"] + + sum(n1 * + self.model.works[worker.id, nweek, nday, shift] + for shift in self.get_shift_names_by_day(nday) + if shift != "night")) + + self.model.constraints.add( + 1 >= self.model.works[worker.id, week, day, "night"] + + sum(n2 * + self.model.works[worker.id, n2week, n2day, shift] + for shift in self.get_shift_names_by_day(n2day) + if shift != "night")) + self.model.constraints.add( + 1 >= self.model.works[worker.id, week, day, "night"] + + sum(p1 * + self.model.works[worker.id, pweek, pday, shift] + for shift in self.get_shift_names_by_day(pday) + if shift != "night")) + self.model.constraints.add( + 1 >= self.model.works[worker.id, week, day, "night"] + + sum(p2 * + self.model.works[worker.id, p2week, p2day, shift] + for shift in self.get_shift_names_by_day(p2day) + if shift != "night")) self.define_objectives() def define_objectives(self): # Define an objective function with model as input, to pass later def obj_rule(m): - - #c = len(workers) - #return 1 - return sum(self.model.shift_count_t1[(worker.id)] + self.model.shift_count_t2[(worker.id)] for worker in self.workers) - #return sum(model.shift_count_t3[worker.id] for worker in workers) - + # c = len(workers) + balance_modifier_constant = 1000 + + shift_balancing = sum(balance_modifier_constant * + (self.model.shift_count_t1[(worker.id)] + + self.model.shift_count_t2[(worker.id)]) + for worker in self.workers) + + preference_constant = 10 + # Preferences + preferences = sum( + self.model.pref_not_to_work[worker.id, week, day] * + self.model.works[worker.id, week, day, shift] * + preference_constant for worker in self.workers + for week, day, shift in self.get_all_shiftname_combinations()) + + # # Spread nights + + nights_balancing = sum( + self.model.night_per_site_t1[week, block, site] * 20 + #self.model.night_per_site2[week, block, site] + for week in self.weeks for block in ("weekday", "weekend") + for site in self.sites) + #nights_balancing = 0 + + blocks_balancing = sum( + 100 * self.model.blocks_assigned[week, shift] + for week in self.weeks + for shift in self.shifts_to_assign_as_blocks()) + + return shift_balancing + preferences + nights_balancing + blocks_balancing # add objective function to the model. rule (pass function) or expr (pass expression directly) self.model.obj = Objective(rule=obj_rule, sense=minimize) @@ -556,20 +647,21 @@ class RotaBuilder(object): """ self.workers.extend(workers) - def build_workers(self): - """Process loaded workers + def build_shifts_and_workers(self): + """Process loaded shifts and workers Must be called prior to attempting to solve """ + self.build_shifts() + self.workers = sorted(self.workers) self.full_time_equivalent = sum([w.fte_adj for w in self.workers]) self.full_time_equivalent_sites = {} - for site in sites: + for site in self.sites: self.full_time_equivalent_sites[site] = sum( - [w.fte_adj for w in self.workers if w.site == site] - ) + [w.fte_adj for w in self.workers if w.site == site]) def add_shift(self, shift): """Add a shift to the collection @@ -578,7 +670,6 @@ class RotaBuilder(object): """ self.shifts.append(shift) - self.BuildShift() def add_shifts(self, *shifts: SingleShift): """Add multiple shifts @@ -587,9 +678,8 @@ class RotaBuilder(object): None """ self.shifts.extend(shifts) - self.BuildShift() - def get_shift_namesByDay(self, day: DayStr): + def get_shift_names_by_day(self, day: DayStr): """Returns the shifts required for a specific day Returns: @@ -598,7 +688,7 @@ class RotaBuilder(object): """ return self.day_shifts_dict[day] - def GetShiftByName(self, name): + def get_shift_by_name(self, name): """ :param name: @@ -606,7 +696,7 @@ class RotaBuilder(object): """ return self.shifts_by_name[name] - def GetShiftLengthByName(self, name): + def get_shift_length_by_name(self, name): """Get the length of a shift Args: @@ -617,13 +707,15 @@ class RotaBuilder(object): """ return self.shifts_by_name[name].length - def BuildShift(self): + def build_shifts(self): """ """ self.shifts_by_name = {} self.shift_names = [] # type: List[ShiftName] self.day_shifts_dict = {day: set() for day in days} + self.sites = set() + for s in self.shifts: self.shifts_by_name[s.name] = s self.shift_names.append(s.name) @@ -631,6 +723,9 @@ class RotaBuilder(object): for day in s.shift_days: self.day_shifts_dict[day].add(s.name) + for site in s.site: + self.sites.add(site) + self.week_day_shift_product = [] self.week_day_shiftclass_product = [] for week, day in self.weeks_days_product: @@ -647,7 +742,7 @@ class RotaBuilder(object): self.day_shift_product.append((day, s.name)) self.day_shiftclass_product.append((day, s)) - def GetAllDayShiftAsNames(self): + def get_day_shiftname_combinations(self): """Returns a list of all required day / shift combinations Returns: @@ -655,7 +750,8 @@ class RotaBuilder(object): """ return self.day_shift_product - def get_day_shiftclass_products(self) -> List[Tuple[DayStr, SingleShift]]: + def get_day_shiftclass_combinations( + self) -> List[Tuple[DayStr, SingleShift]]: """Returns a list of all required day / shift combinations Returns: @@ -663,7 +759,8 @@ class RotaBuilder(object): """ return self.day_shiftclass_product - def GetAllShiftAsNames(self) -> List[Tuple[WeekInt, DayStr, ShiftName]]: + def get_all_shiftname_combinations( + self) -> List[Tuple[WeekInt, DayStr, ShiftName]]: """Returns a list of all possible week / day / shift combinations Returns: @@ -671,7 +768,8 @@ class RotaBuilder(object): """ return self.week_day_shift_product - def get_all_shiftclass_combinations(self) -> List[Tuple[WeekInt, DayStr, SingleShift]]: + def get_all_shiftclass_combinations( + self, ) -> List[Tuple[WeekInt, DayStr, SingleShift]]: """Returns a list of all possible week / day / shift combinations Returns: @@ -688,7 +786,7 @@ class RotaBuilder(object): """ return self.weeks_days_product - def GetShiftOptions(self) -> List[SingleShift]: + def get_shifts(self) -> List[SingleShift]: """Returns a list of all the registered shifts Returns: @@ -696,6 +794,11 @@ class RotaBuilder(object): """ return self.shifts + def get_shifts_with_constraint(self, constraint) -> List[SingleShift]: + return [ + shift for shift in self.shifts if constraint in shift.constraints + ] + def get_shift_names(self) -> List[ShiftName]: """Returns a list of all the registered shift names @@ -722,7 +825,7 @@ class RotaBuilder(object): """ blocks = [] for i in range(len(self.weeks)): - blocks.append(self.weeks[i : i + block_length]) + blocks.append(self.weeks[i:i + block_length]) return blocks def get_required_workers_and_site_combinations(self): @@ -734,9 +837,8 @@ class RotaBuilder(object): l = [] for week, day, shift in self.week_day_shiftclass_product: if day in shift.shift_days: - l.append( - (week, day, shift.name, shift.workers_required, shift.site) - ) + l.append((week, day, shift.name, shift.workers_required, + shift.site)) return l @@ -756,3 +858,198 @@ class RotaBuilder(object): l.add((week, day, shift)) return l - set(self.get_all_shiftclass_combinations()) + + def shifts_to_assign_as_blocks(self): + return [ + shift.name for shift in self.get_shifts() if shift.assign_as_block + ] + + def shifts_to_force_as_blocks(self): + return [ + shift.name for shift in self.get_shifts() if shift.force_as_block + ] + + def shifts_to_assign_or_force_as_blocks(self): + s = self.shifts_to_assign_as_blocks() + s.extend(self.shifts_to_force_as_blocks()) + return s + + +class RotaResults(object): + def __init__(self, rota, results): + self.rota = rota + + def export_rota_to_csv(self, filename="rota"): + works = self.rota.model.works + with open("{}.csv".format(filename), 'w', newline='') as f: + wr = csv.writer(f, quoting=csv.QUOTE_ALL) + l = ["Name"] + l.extend([worker.name for worker in self.rota.workers]) + wr.writerow(l) + + l2 = ["Site"] + l2.extend([worker.site for worker in self.rota.workers]) + wr.writerow(l2) + + l3 = ["Grade"] + l3.extend([worker.grade for worker in self.rota.workers]) + wr.writerow(l3) + + l4 = ["FTE"] + l4.extend([worker.fte for worker in self.rota.workers]) + wr.writerow(l4) + + for week, day in self.rota.get_week_day_combinations(): + d = ["Week {} Day {}".format(week, day)] + + for worker in self.rota.workers: + i = "" + for shift in self.rota.get_shift_names_by_day(day): + if works[worker.id, week, day, shift].value == 1: + i = shift + + d.append(i) + wr.writerow(d) + + def get_work_table(self): + """Build a timetable of the week as a dictionary from the model's optimal solution.""" + works = self.rota.model.works + week_table = { + week: { + day: {shift: [] + for shift in self.rota.get_shift_names()} + for day in days + } + for week in self.rota.weeks + } + for week in self.rota.weeks: + for worker in self.rota.workers: + for day, shift in self.rota.get_day_shiftname_combinations(): + if works[worker.id, week, day, shift].value == 1: + week_table[week][day][shift].append( + worker.get_details()) + return week_table + + def get_worker_timetable(self): + works = self.rota.model.works + timetable = { + worker.name: + {week: {day: "" + for day in days} + for week in self.rota.weeks} + for worker in self.rota.workers + } + for worker in self.rota.workers: + for week in self.rota.weeks: + for day, shift in self.rota.get_day_shiftname_combinations(): + if works[worker.id, week, day, shift].value == 1: + timetable[worker.name][week][day] = shift + return timetable + + def get_worker_timetable_brief(self, show_prefs=True, marker_every=19): + model = self.rota.model + week_string = "{:20}".format("-Week-") + "".join( + [7 * str("{}".format(w))[-1:] for w in self.rota.weeks]) + days_string = "{:20}".format("-Day-") + "".join( + "MTWTFSS" * len(self.rota.weeks)) + timetable = [] + for worker in self.rota.workers: + shifts = [] + w = ["{:20}".format(worker.name)] + for week, day in self.rota.get_week_day_combinations(): + a = "-" + for shift in self.rota.get_shift_names_by_day(day): + if model.works[worker.id, week, day, shift].value == 1: + shifts.append(shift) + a = shift[0] + w.append(a) + + shift_count = "" + for s in set(shifts): + shift_count = shift_count + "{}: {},".format( + s, shifts.count(s)) + timetable.append("{} {}".format("".join(w), shift_count)) + + if show_prefs: + # prefs + w = ["{:20}".format("Preferences")] + for week, day in self.rota.get_week_day_combinations(): + if model.pref_not_to_work[worker.id, week, day] > 0: + w.append("Y") + else: + w.append("N") + timetable.append("".join(w)) + + i = marker_every + while i < len(timetable): + timetable.insert(i, week_string) + timetable.insert(i + 1, days_string) + i = i + marker_every + 2 + + timetable.append(week_string) + timetable.insert(0, week_string) + timetable.insert(0, days_string) + return "\n".join(timetable) + + def get_shift_summary(self): + works = self.rota.model.works + timetable = { + worker.get_details(): + {shift[0]: "" + for shift in self.rota.get_shift_names()} + for worker in self.rota.workers + } + #timetable = { worker.get_details() : { shift : "" for shift in ["truro_twilight"] } for worker in workers } + t = [] + for worker in self.rota.workers: + l = [] + + total_shifts = 0 + for shift in self.rota.get_shift_names(): + #for shift in ["truro_twilight"]: + c = [ + works[worker.id, week, day, shift] + for week in self.rota.weeks for day in days + ].count(1) + if c > 0: + l.append("{} ({})".format(shift, c)) + total_shifts = total_shifts + c + #print(worker.id, shift) + timetable[worker.get_details()][shift[0]] = c + t.append("{} [{}]: {}".format(worker.get_full_details(), + total_shifts, ", ".join(l))) + return "\n".join(t) + + #def get_no_preference(no_pref): + # """Extract to a list the workers not satisfied with their weekend preference.""" + # return [worker.id for worker in workers if no_pref[worker.id].value == 1] + + def get_night_blocks(self): + nights = self.rota.model.nights + timetable = { + worker.name: { + week: {block: "" + for block in self.rota.night_blocks} + for week in self.rota.weeks + } + for worker in self.rota.workers + } + for worker in self.rota.workers: + for week in self.rota.weeks: + for block in self.rota.night_blocks: + if nights[worker.id, week, block].value == 1: + timetable[worker.name][week][block] = "true" + return timetable + + def get_multinight(self): + for site in self.rota.sites: + for block in ("weekend", "weekday"): + for week in self.rota.weeks: + print( + site, week, block, + self.rota.model.night_per_site[week, block, + site].value, + self.rota.model.night_per_site_t1[week, block, + site].value, + self.rota.model.night_per_site_t2[week, block, + site].value) diff --git a/test.py b/test.py new file mode 100644 index 0000000..283eda7 --- /dev/null +++ b/test.py @@ -0,0 +1,198 @@ +from pyomo.environ import * +from pyomo.opt import SolverFactory +from shifts import SingleShift, RotaBuilder, RotaResults, days +from workers import Worker + +from copy import deepcopy + +import math + +import scipy.stats as stats + +import statistics + +import datetime +import itertools +import operator + +weeks_to_rota = 16 + +sites = ("truro", "exeter", "torquay", "barnstaple", "plymouth") + +Rota = RotaBuilder(datetime.date(2020, 9, 7), + weeks_to_rota=weeks_to_rota, + balance_offset_modifier=1) + +Rota.add_shifts( + SingleShift(("exeter", ), "exeter_twilight", 12.5, days[:5]), + SingleShift(("truro", ), + "truro_twilight", + 12.5, + days[:5], + assign_as_block=False), + #SingleShift(("torquay", ), "torquay_twilight", 12.5, days[:5]), + #SingleShift(("plymouth", ), "plymouth_twilight", 12.5, days[:5]), + SingleShift(("exeter", ), + "weekend_exeter", + 12.5, + days[5:], + force_as_block=True), + SingleShift(("truro", ), + "weekend_truro", + 12.5, + days[5:], + assign_as_block=True), + # SingleShift(("torquay", ), + # "weekend_torquay", + # 12.5, + # days[5:], + # assign_as_block=True), + # SingleShift(("plymouth", ), + # "weekend_plymouth", + # 8, + # days[5:], + # workers_required=2, + # assign_as_block=True), + SingleShift((sites), + "night", + 12.25, + days, + balance_offset=2, + balance_weighting=1, + workers_required=3, + rota_on_nwds=True, + constraints=["night"]), +) + +use_test_workers = True + +# Import trainee data +if use_test_workers: + # Rota.add_workers([ + # Worker(Rota, + # i, + # "Truro {}".format(i), + # "truro", + # 3, + # nwd=['Fri', "Sat", "Sun"]) for i in range(1, 2) + # ]) + Rota.add_workers([ + Worker( + Rota, + i, + "Truro {}".format(i), + "truro", + 4, + ) for i in range(2, 14) + ]) + # Rota.add_workers([ + # Worker(Rota, i, "Plym {}".format(i), "plymouth", 4) + # for i in range(16, 30) + # ]) + Rota.add_workers([ + Worker( + Rota, + i, + "Exe {}".format(i), + "exeter", + 4, + ) for i in range(30, 40) + ]) + Rota.add_workers([ + Worker(Rota, + i, + "Torq {}".format(i), + "torquay", + 2, + pref_not_to_work=(("2020/09/07"), )) for i in range(50, 60) + ]) +else: + import csv + with open('trainees.csv', newline='') as f: + reader = csv.reader(f, delimiter=',', quotechar='|') + next(reader) # skip header + + n = 0 + for row in reader: + n = n + 1 + #print(row) + name, site, grade, fte, nwd, end_date, oop, extra = row + + # Ignore trainees if fte == 0 (what are they doing here anyway) + if fte == 0: + continue + + nwds = nwd.split(",").title() if nwd else None + end_date = end_date if end_date else None + oop = oop.split("-") if oop else None + #print(nwds, end_date, oop) + Rota.add_worker( + Worker(Rota, n, name, site.lower(), int(grade[2]), int(fte), + nwds, end_date, oop)) + +print(0) +Rota.build_shifts_and_workers() + +Rota.build_model() + +print(1) +opt = SolverFactory('cbc') # choose a solver +#opt = SolverFactory('ipopt') # choose a solver + +print(2) +results = opt.solve( + Rota.model, tee=True, options={ + "seconds": 1200, + "allow": 70000 + }) # solve the model with the, options="seconds=60" selected solver + +print(results) +results.solver.status +if not results.solver.status: + sys.exit(0) + +ResultsHolder = RotaResults(Rota, results) + +week_table = ResultsHolder.get_work_table() # list with the required workers +worker_timetable = ResultsHolder.get_worker_timetable() +worker_timetable_brief = ResultsHolder.get_worker_timetable_brief( + show_prefs=False) + +print(worker_timetable_brief) + +# for i in ResultsHolder.rota.model.blocks_worker_shift_count: +# n = ResultsHolder.rota.model.blocks_worker_shift_count[i].value + +# if n > 0: +# print(i, n) +# #print(ResultsHolder.rota.model.blocks_assigned[i].value) +# else: +# pass +# #print("SHIT") +# for i in ResultsHolder.rota.model.blocks_assigned: +# n = ResultsHolder.rota.model.blocks_assigned[i].value +# if n: +# print(i, n) +# #print(ResultsHolder.rota.model.blocks_assigned[i].value) +# else: +# pass + +# ResultsHolder.export_rota_to_csv("rotas/"+rot) +# with open("cest", "a") as f: +# f.write(rot) +# f.write("\n\n") + +# for s in Rota.shifts: +# f.write(s.get_shift_summary()) +# f.write("\n") +# f.write("\n\n") + +# f.write(worker_timetable_brief) +# f.write("\n\n\n") +#workers_no_pref = ResultsHolder.get_no_preference(Rota.model.no_pref) # list with the non-satisfied workers (work on Sat but not on Sun) + +# worker_night_block = ResultsHolder.get_night_blocks() +# worker_shift_summary = ResultsHolder.get_shift_summary() +# multi = ResultsHolder.get_multinight() + +#break diff --git a/workers.py b/workers.py index a51bdeb..5282db9 100644 --- a/workers.py +++ b/workers.py @@ -5,12 +5,22 @@ from collections import defaultdict class Worker: - def __init__( - self, Rota, id, name, site, grade, fte=100, nwd=None, end_date=None, oop=None - ): + def __init__(self, + Rota, + id, + name, + site, + grade, + fte=100, + nwd=None, + end_date=None, + oop=None, + pref_not_to_work=None): self.id = id self.name = name self.site = site + # Grade are equivalent to roles, by keeping them integer defining model + # rules is easier. self.grade = grade self.fte = fte self.nwd = nwd @@ -23,7 +33,8 @@ class Worker: if end_date is None: self.end_date = None else: - self.end_date = datetime.datetime.strptime(end_date, "%Y/%m/%d").date() + self.end_date = datetime.datetime.strptime(end_date, + "%Y/%m/%d").date() if self.end_date > Rota.rota_end_date: self.end_date = Rota.rota_end_date @@ -37,8 +48,10 @@ class Worker: if oop is not None: start_oop, end_oop = oop - start_oop_date = datetime.datetime.strptime(start_oop, "%Y/%m/%d").date() - end_oop_date = datetime.datetime.strptime(end_oop, "%Y/%m/%d").date() + start_oop_date = datetime.datetime.strptime(start_oop, + "%Y/%m/%d").date() + end_oop_date = datetime.datetime.strptime(end_oop, + "%Y/%m/%d").date() if end_oop_date > Rota.rota_end_date: end_oop_date = Rota.rota_end_date @@ -49,11 +62,22 @@ class Worker: days_until_oop = (start_oop_date - Rota.start_date).days for weeks_days in Rota.weeks_days_product[ - days_until_oop : days_until_oop + oop_length - ]: + days_until_oop:days_until_oop + oop_length]: week, day = weeks_days Rota.unavailable_to_work.add((self.id, week, day)) + if pref_not_to_work is not None: + # loop throught dates converting to week / day combination + for date in pref_not_to_work: + days_from_start = (datetime.datetime.strptime(date, "%Y/%m/%d").date() - Rota.start_date).days + week = days_from_start // 7 + 1 + day = Rota.days[(days_from_start % 7)] + # Weight the value to take into account the number of preferences + # 1 is added to the total number of requests to ensure they do not outweight + # a single (or fewer) request(s) + Rota.pref_not_to_work[(self.id, week, day)] = 1 / (len(pref_not_to_work) + 1) + + self.proportion_rota_to_work = days_to_work / Rota.rota_days_length # We had to adjust the full time equivalent for people who CCT / leave the rota early @@ -68,3 +92,6 @@ class Worker: def get_details(self): return "{} {} {}".format(self.id, self.site[0], self.grade) + + def get_full_details(self): + return "{}/{}/{}".format(self.site, self.grade, self.name)