{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1153f842",
   "metadata": {},
   "source": [
    "# **Bin packing problem**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "34635dc2",
   "metadata": {},
   "source": [
    "***bin_packing.ipynb***\n",
    "\n",
    "Pack $n$ items into $m$ bins minimizing the number of bins used. Showcases the use of the `xp.Dot` operator.\n",
    "\n",
    "&copy; Copyright 2025 Fair Isaac Corporation\n",
    "\n",
    "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.\n",
    " \n",
    "Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n",
    "\n",
    "This example uses FICO&reg; Xpress software. By running it, you agree to the Community License terms of the [Xpress Shrinkwrap License Agreement](https://www.fico.com/en/shrinkwrap-license-agreement-fico-xpress-optimization-suite-on-premises) with respect to the FICO&reg; Xpress software. See the [licensing options](https://www.fico.com/en/fico-xpress-trial-and-licensing-options) overview for additional details and information about obtaining a paid license."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28f7d116",
   "metadata": {},
   "source": [
    "## Problem description and formulation\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "243e0782",
   "metadata": {},
   "source": [
    "There are $n$ items, each with a positive integer size $s_i, \\forall i \\in \\mathcal{I}$, and $m$ bins, each with capacity $c_b, \\forall b \\in \\mathcal{B}$. Our goal is to pack every item into a bin so as to **minimize** the total number of bins in use. \n",
    "\n",
    "We introduce two families of binary variables:\n",
    "\n",
    "$$\n",
    "x_{b,i} =\n",
    "\\begin{cases}\n",
    "1, & \\text{if item }i\\text{ is placed into bin }b,\\\\\n",
    "0, & \\text{otherwise},\n",
    "\\end{cases}\n",
    "$$\n",
    "$$\n",
    "y_b =\n",
    "\\begin{cases}\n",
    "1, & \\text{if bin }b\\text{ is used},\\\\\n",
    "0, & \\text{otherwise}.\n",
    "\\end{cases}\n",
    "$$\n",
    "\n",
    "- $x_{b,i}$ ensures each item is assigned to exactly one bin.  \n",
    "- $y_b$ allows us to count how many bins are actually used.\n",
    "\n",
    "The objective is to minimize the total number of bins used:\n",
    "\n",
    "$$\n",
    "\\min \\sum_{b \\in \\mathcal{B}} y_b.\n",
    "$$\n",
    "\n",
    "Subject to the following constraints:\n",
    "\n",
    "* Each item must go into exactly one bin:\n",
    "   $$\n",
    "   \\sum_{b \\in \\mathcal{B}} x_{b,i} = 1,\n",
    "   \\quad \\forall i \\in \\mathcal{I}.\n",
    "   $$\n",
    "\n",
    "* Bin capacity linking:\n",
    "   $$\n",
    "   \\sum_{i \\in \\mathcal{I}} s_i\\,x_{b,i}\n",
    "   \\le C_b\\,y_b,\n",
    "   \\quad \\forall b \\in \\mathcal{B}.\n",
    "   $$\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3046c9c",
   "metadata": {},
   "source": [
    "## Data preparation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7672ce7",
   "metadata": {},
   "source": [
    "In this step, all the necessary packages are imported, the scalar parameters and ranges for data generation are defined. Then, item sizes and bin capacities are generated as `NumPy` arrays using `np.random.randint()` within the pre-defined ranges."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ff1f8f4b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import xpress as xp\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Parameters\n",
    "NITEMS = 20         # Number of items\n",
    "NBINS = 11          # Number of bins\n",
    "MinSize = 20        # Minimum size of an item\n",
    "MaxSize = 39        # Maximum size of an item\n",
    "MinCapacity = 40    # Minimum capacity of a bin\n",
    "MaxCapacity = 89    # Maximum capacity of a bin\n",
    "\n",
    "# Random data generation for item sizes and bin capacities\n",
    "np.random.seed(2)\n",
    "sizes = np.random.randint(MinSize, MaxSize + 1, size=NITEMS)\n",
    "capacities = np.random.randint(MinCapacity, MaxCapacity + 1, size=NBINS)\n",
    "total_size = sizes.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "518c97e2",
   "metadata": {},
   "source": [
    "## Model implementation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "392dfdf5",
   "metadata": {},
   "source": [
    "We create an Xpress problem named **Binpacking** and define two sets of binary variables (`contains` and `binused`$) by calling[`p.addVariables()`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.addVariables.html). When passing integer arguments to [p.addVariables()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.addVariables.html), a *NumPy* arrays of variables is created.\n",
    "\n",
    "The objective function is created and added to the problem by passing the summation over the array of variables `binused` to [`p.setObjective()`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.setObjective.html). The optimization sense is to minimize the function, by default.\n",
    "\n",
    "Similarly, constraints are created and added to the problem by passing the corresponding expressions using list comprehension to [`p.addConstraint()`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.addConstraint.html). \n",
    "\n",
    "The set of capacity constraints leverages the use of the [`xp.Dot()`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/xpress.Dot.html) operator to perform the  dot‑product between the `contains` variable matrix and the `sizes` array, generating `NBINS` constraints at once with a compact and more efficient expression. The right-hand-sides of those each constraints are mapped to the  array resulting from the multiplication between the `capacity` and the `binused` arrays.\n",
    "\n",
    "After building the model, we turn off solver logging using the `OUTPUTLOG` control and call [`p.optimize()`](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.optimize.html) to solve the problem, then extract the solutions and print both the number of bins used and the detailed item‑to‑bin assignments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3ba639b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total number of bins used: 9.0\n",
      "Bin 1 (cap 79): items 4(28) 10(22) 15(27)\n",
      "Bin 2 (cap 78): items 16(23) 18(24) 19(30)\n",
      "Bin 3 (cap 82): items 12(31) 14(25) 17(26)\n",
      "Bin 4 (cap 73): items 2(35) 7(31)\n",
      "Bin 5 (cap 43): items 6(38)\n",
      "Bin 7 (cap 64): items 3(33) 20(31)\n",
      "Bin 8 (cap 44): items 11(37)\n",
      "Bin 9 (cap 86): items 1(28) 8(28) 9(27)\n",
      "Bin 11 (cap 71): items 5(31) 13(35)\n"
     ]
    }
   ],
   "source": [
    "p = xp.problem(name=\"Binpacking\")\n",
    "\n",
    "# Decision variables:\n",
    "# Binary variables equal to 1 iff item i is placed in bin b, and 0 otherwise\n",
    "contains = p.addVariables(NBINS, NITEMS, vartype=xp.binary, name=\"c\")\n",
    "# Binary variables equal to 1 iff bin b is used, and 0 otherwise\n",
    "binused  = p.addVariables(NBINS, vartype=xp.binary, name=\"u\")\n",
    "\n",
    "# Objective function: minimize number of bins used\n",
    "p.setObjective(xp.Sum(binused), sense=xp.minimize)\n",
    "\n",
    "# Constraint 1: each item in exactly one bin\n",
    "p.addConstraint(xp.Sum(contains[b, i] for b in range(NBINS)) == 1 for i in range(NITEMS))\n",
    "\n",
    "# Constraint 2: Capacity constraints\n",
    "p.addConstraint(xp.Dot(contains, sizes) <= capacities * binused)\n",
    "\n",
    "# Solve the problem\n",
    "p.controls.outputlog = 0  # Turn off solver logging for cleaner output\n",
    "p.optimize()\n",
    "\n",
    "ct = p.getSolution(contains)\n",
    "used = p.getSolution(binused)\n",
    "\n",
    "# Print results\n",
    "print(\"Total number of bins used:\", used.sum())\n",
    "for b in range(NBINS):\n",
    "    if used[b]:\n",
    "        items = [i+1 for i in range(NITEMS) if ct[b, i] == 1]\n",
    "        sizes_str = \" \".join(f\"{i}({sizes[i-1]})\" for i in items)\n",
    "        print(f\"Bin {b+1} (cap {capacities[b]}): items {sizes_str}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "468c839f",
   "metadata": {},
   "source": [
    "## Visualization"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50400050",
   "metadata": {},
   "source": [
    "The code below allows to visualize each bin used with a stacked bar formed by the sizes of the items assigned to it, and a dashed line marking the bin capacity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "2d68f2bb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABKYAAAJOCAYAAACN2Q8zAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAATSZJREFUeJzt3QmcVWX9P/DvDDsioCiIIoI7bqS4oSSpKJprmpY/LUJN0yzRciHF3VArw1xwSVFzt9TUcsPMQhFLyiUJSTBME0RWWQZk5v96jv+ZZnQGx2FmztyZ9/v1us6ce55773PvYZx7P/N9vqeorKysLAAAAACgkRU39gMCAAAAQCKYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgCahKKiorjggguiUL311lvZc/jpT3+6ynF//OMfs3Hpa3PwpS99Kbs0xGt566231uv9AgBNj2AKAGgQKVRI4ULlS/fu3WPPPfeMxx57rMEf/1vf+laVx+7cuXP0798/fvazn0VJSUm0VK+++mp89atfjY022ijat28fG2ywQeyzzz5x9dVXN/pc7rrrrhgzZkyjPy4A0HS0znsCAEDzdtFFF0Xfvn2jrKwsZs2alQVWX/7yl+ORRx6JAw88sGLc0qVLo3Xr+n1r0q5du/jlL3+ZfT9//vz4zW9+Ez/84Q/jL3/5S9xzzz2Rhz322CN7rm3btm30x37++eezYLB3797x7W9/O9Zbb714++2344UXXoirrroqvve97zV6MPXaa6/FiBEjqlyfQrP0GrVp06ZR5wMAND7BFADQoPbff//YcccdK7aPO+646NGjR9x9991VgqlUvVPfUtB1zDHHVGyffPLJscsuu8S9994bV155Zay//vrR2IqLixvkudbGpZdeGl26dMmCua5du1bZN3v27GgqUoVbXq8RANC4LOUDABpVCkQ6dOjwqeqoT/aYSt+n6/71r39ly/LS7VKoMnz48FiyZEmdQ6Hyfkipj9HcuXOzCqptt902OnXqlC33S0Hayy+//KnbLlu2LJvT5ptvnoUmPXv2jMMOOyzefPPNGh8vVYmdcMIJWXXUAw88UGOPqTSnbbbZJl5//fWsoqljx47ZErsrrrjiU/f573//Ow4++OBYY401sqWRp512WjzxxBO16luV5rr11lt/KpRK0n1V9tFHH8XFF18cm2yySVZ51qdPn/jRj370mcsgy5dwpte3sk8+7/Scf/e732XPp3y5ZXqMVfWY+sMf/hBf/OIXs+eensMhhxwSU6ZMqTKmIf7dAAANR8UUANCgFixYEHPmzMlCmlSVk3oZffjhh1UqmVblyCOPzJYCjh49OiZPnpwtzUshyuWXX16n+ZQHSd26dYvp06fHQw89FEcccUT2GGmp4Q033BCDBw/OQqLyiqqVK1dm1V1PP/10fP3rX49TTz01Fi1aFE899VS2FC2FN5+UbnPsscdm1VkPPvhgHHDAAauc17x582K//fbLwq70nH/961/HWWedlYVmKSxLFi9eHHvttVf897//zeaQluKl5XDPPPNMrZ57WiI3ceLEbM4pCFuV448/Pm677basH9UPfvCDmDRpUnYMUhCUns/qOuecc7J/G//5z3/i5z//eXZdCgdrMn78+Ox12HjjjbPwKS31S/+Wdt999+zfRXmo1VD/bgCAhiGYAgAa1JAhQ6psp+qbW265JWu4XRvbb7993HzzzRXbH3zwQbZd24AhhWJJCkHuu+++LIjabrvtYosttsiqf954442skqrcN77xjdhyyy2zxxg1alR23e23356FUmn5X6pQKnf22WdngdsnpWqjFLw9/PDD2WXffff9zHm+++672eOkxy9f8piCpDSP8mAqhWblYVqqFkpOPPHE7DWqjVQdlu7rC1/4Quy8885Z9dHee++dVWlV7ueUKsZSKJXCqZtuuqliGWQKdtJZB1MQlm6zOtLxT1VhKZCrTUh5xhlnxNprr50Fa+lrcuihh2bP/fzzz8/mW5//bgCAxmEpHwDQoK699tqssihd7rjjjizQSIFH+dK2z/Kd73ynynYKU1LIsHDhws+8baowWnfddbPLpptumi1FGzhwYEXFTwrJykOpVOGU7jdV7aTQKlXZlEtN09dZZ51qm4OnZWOVLV++PKvAevTRR+P3v/99rUKpJD1u5YAmLf9L4VEKoso9/vjjWZiTlvKVS8sKUyPz2oZBKdhJt0/hU1oqOHTo0Ow+U4BWLs07Of3006vcPlVOJWkJXmNKFWJ///vfs6V55aFUkgLG9JzK51tf/24AgMajYgoAaFApXKnc/Pyoo47KqllOOeWUbHncZ52dLp1BrrK11lor+5oqbVJPqFVJoU06+195CJWWdvXq1atif2lpaXY2uuuuuy5mzJiRhVPl0lK/ysv/UlhVm7MGpqVjaaniY489VtHPqjbSvD4ZcqXn+sorr1Rsp35MadngJ8el0K22dtpppywUTAFaCqdSSJeW0qUleyn82WqrrbLHSYHdJ+83LR1MPZvS/sZU/njpGHxSv379sh5bKYRMvafq498NANB4VEwBAI0qBR6paipVwUybNu0zx7dq1ara66tbQlfdbdNSwnRJFTOVQ6nkxz/+cVYVtMcee2TVXCngSJVdqUF4Cq3qIlUgpYAkVSOlhum1tTrPsy5SIJhCqvQajB07NlasWBH3339/lTGfDMBqo6bbVA79GkNjv54AQN2omAIAGl3qwZSkyqI8pQbjKSSr3IsomT9/frZ0r1yqUkrNv1N4U7kXU3V23XXXbBlZqgZLS/pSRVJtKq1qI/WcSk3ZU7hSOQBKZ6BbHeUVbSksLH+cFMyl4DBVJJVLzeHTa5P216S8MimNq6y6KqvaBl/ljzd16tRP7fvnP/+ZHavK1VIAQOFQMQUANKoU7jz55JNZxU7l0CMPqarmkxU0qWronXfeqXLd4YcfnjVRv+aaa2pVgZMqtO65556sJ1RqZl7X6qvqqrHS3Cr3g0pVWeUNyj9Lalpe3XzLezSVL5X78pe/nH0dM2ZMlXGp+XuyqjMMlp+h8E9/+lOVaqkbb7zxU2NTmJSa0n+Wnj17Zg3bU4PzyoFXOrtg+rdUPl8AoPComAIAGlTqtZSqWpLZs2fHXXfdlVXipDPa5d3rJ1U1XXTRRTF8+PDYbbfd4tVXX40777wzNt544yrjvvnNb2ZnzEvL/l588cVsWWDqaTR+/PjsbHXlZ8irLJ0xbty4cdlt0/NMZ9RbXekMfCkcS326Tj311CywSfNNvbRqU4GUmrcvWbIkvvKVr2RnHkx9pp5//vm49957o0+fPtnrkPTv3z+GDRuWhUkpCBo8eHD2vFMwlJ7Xqs7Il5ZBpqqxkSNHxty5c7Nm5SmkK6+Sq2zAgAHZY6fXNS0rTA3gDzrooGrv9yc/+Ul2RsHUvD6dsXDp0qVx9dVXR5cuXeKCCy74nK8kANBUCKYAgAZ13nnnVXyfApQUiKSeRilkyVs6S18KmFJYlgKSHXbYITvjXArNPllZlaqKLr300mxsOktfao4+aNCg2HbbbWu8/3SWvUWLFmXhVQqnUriyOlJw84c//CELmFLT9rSdgq8UqqWqrvKAqiY//elPs4qw9FxS6JSCqdQkPM3v3HPPzRqbl/vlL3+ZBXS33nprthwxNT5PYdP555//mfNMYVk6vpdddll2nylISmFWOoNeZelxU8P1FOClBuxpyV5NwVSqQksVaOnx07+ptKQyBWaXX3551tQeAChMRWU6QAIAFLS05O60006L//znP7HBBhvkPR0AgFoTTAEAFJC0hK1Dhw5Vekxtv/32WR+nN954I9e5AQB8XpbyAQAUkMMOOyxbfpeagafG4XfccUfWwystnwMAKDSCKQCAApLOzJf6P6UgKlVJbbXVVllz8a997Wt5Tw0A4HOzlA8AAACAXBTn87AAAAAAtHSCKQAAAABy0eJ6TJWWlsa7774ba665ZhQVFeU9HQAAAIBmJXWNWrRoUay//vpRXLzqmqgWF0ylUGrDDTfMexoAAAAAzdrbb78dvXr1WuWYFhdMpUqp8henc+fOeU8HAAAAoFlZuHBhVhRUnsGsSosLpsqX76VQSjAFAAAA0DBq00JJ83MAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAAciGYAgAAACAXgikAAAAActE6n4cFAACgvixZ/lGN+4qLiqJ9m1YNPnbp8pVRFmXVji2KoujQtm5jl61YGaVl1Y9NOrZtXW9jK+8HGoefOgAAgAK31XlP1Lhvzy3WjXHDd67YHnDx+Fi6YmW1Y3fpu3bce+LAiu1Blz8Tcxcvr3bsdr26xMOnDKrYHnLls/HO/KXVjt2se6d46vTBFdsHXzMhps3+sNqxG3TtEM+dvVfF9pE3TIxX/rOg2rFrr9E2Jo/ap2J72C0vxqQZc6sd26FNq5hy8X4V2yfd8VI8M/X9KmPeuuyAam8LNBxL+QAAAADIRVFZ2SrqHJuhhQsXRpcuXWLBggXRuXPnvKcDAACw2izlq5+xlvJB42cvfuoAAAAK3OcJVBpqbOUwqT7HVg6/8hoLNBxL+QAAAADIhWAKAAAAgFwIpgAAAADIhWAKAAAAgFwIpgAAAADIhWAKAAAAgFwIpgAAAADIhWAKAAAAgFwIpgAAAADIhWAKAAAAgFwIpgAAAADIhWAKAAAAgFy0zudhAQAAqDczZ0bMmZP3LArfOutE9O6d9yygRRFMAQAAFLKZM6O0X78oXrIk75kUvNKOHaN4yhThFDQiwRQAAEAhmzMnC6UevmRsfNB3s7xnU7C6zZgWB5970seVZ4IpaDSCKQAAgGYghVKz+vXPexoAn4tgCmjyliz/qMZ9xUVF0b5NqwYfu3T5yiiLsmrHFkVRdGhbt7HLVqyM0rLqxyYd27aul7GV9wEAADQVPqkATd5W5z1R4749e7aLcV9cu2J7wG/ei6Urqw9vdlm3bdy7Z7eK7UG/nRVzS0qrHbvdWm3i4X3Wqdge8ujseGfJymrHbta5dTy137oV2wc//n5MW1h96LVBx1bx3IHdK7aPfGpOvDJvRbVj125XHJMP6VGxPeyZD2LS+8urHduhVVFMOXy9iu2T/jw3nvlvScX2WydvqyQdAABocorzngDAZ55hZlUmTIgYMOB/l6VLax770ktVx86bV/PY1/9Rdex/36157PQ3q45N2zVJ91N5bHqcmqT5VR6b5l+T9Lwrj02vSyWpIepnvpYAAACNrKisbBXrQpqhhQsXRpcuXWLBggXRuXPnvKcDfJbJk2PJrrvF7867Kub22aTadL11cVHF9vLSmv+Xlka1qePYFaU1Lc5ruLFJ2zqO/ai0LMprwdZ+6804YtR3Pg62dthhFfcAABSkyZOzP0yNu3O8HlOroceUl2P40UO8Z4JGzl4s5QOavI4rSuLDjTeNed5o1UnlwAoAAKApsZQPAAAAgFwIpgAAAADIhWAKAAAAgFw0qWBq5cqVMWrUqOjbt2906NAhNtlkk7j44oujcn/29P15550XPXv2zMYMGTIkpk2bluu8AQAAACjwYOryyy+PsWPHxjXXXBNTpkzJtq+44oq4+uqrK8ak7V/84hdx/fXXx6RJk2KNNdaIoUOHxrJly3KdOwAAAACfT5M6K9/zzz8fhxxySBxwwAHZdp8+feLuu++OF198saJaasyYMXHuuedm45Lbb789evToEQ899FB8/etfz3X+AAAAABRoxdRuu+0WTz/9dLzxxhvZ9ssvvxwTJkyI/fffP9ueMWNGvPfee9nyvXJdunSJXXbZJSZOnJjbvAEAAAAo8Iqps88+OxYuXBhbbrlltGrVKus5demll8bRRx+d7U+hVJIqpCpL2+X7PqmkpCS7lEv3DwAAAED+mlTF1H333Rd33nln3HXXXTF58uS47bbb4qc//Wn2ta5Gjx6dVVWVXzbccMN6nTMAAAAAzSCYOuOMM7KqqdQratttt41vfOMbcdppp2XhUrLeeutlX2fNmlXldmm7fN8njRw5MhYsWFBxefvttxvhmQAAAABQUMHUkiVLori46pTSkr7S0tLs+759+2YBVOpDVXlpXjo738CBA6u9z3bt2kXnzp2rXAAAAADIX5PqMXXQQQdlPaV69+4dW2+9dfztb3+LK6+8Mo499thsf1FRUYwYMSIuueSS2GyzzbKgatSoUbH++uvHoYceGs3JkuUf1bivuKgo2rdp1eBjly5fGWVRVu3YoiiKDm3rNnbZipVRWlb92KRj29b1MrbyPgAAAKDpaVKf3K+++uosaDr55JNj9uzZWeB04oknxnnnnVcx5swzz4zFixfHCSecEPPnz49BgwbF448/Hu3bt4/mZKvznqhx355brBvjhu9csT3g4vGxdMXKasfu0nftuPfE/1WTDbr8mZi7eHm1Y7fr1SUePmVQxfaQK5+Nd+YvrXbsZt07xVOnD67YPviaCTFt9ofVjt2ga4d47uy9KraPvGFivPKfBdWOXXuNtjF51D4V28NueTEmzZhb7dgObVrFlIv3q9g+6Y6X4pmp71dsv3XZAdXeDgAAAGgamlQwteaaa8aYMWOyS01S1dRFF12UXQAAAAAoXE0qmOJ/Xr9o6CqX3FX20qghtR474aw9az12/OmDV7k8r7JUaVXbsfedOHCVy/Mqu+3YnWs9duwxA2o9FgAAAMifYKqJ+jz9kRpqbOW+UPU5tnIfq7zGAgAAAPkTTMEqaEK/+mM1oQcAAKAmPjHCKmhC/zFN6AEAAGgIxQ1yrwAAAADwGVRMwSpoQv8xTegBAABoCIIpWAVN6Bt2LAAAAC2bpXwAAAAA5ELFVFM1c2bEnDl5z6KwrbNORO/eq3cfjkPTOA4AAAA0S4KppmjmzCjt1y+KlyzJeyYFrbRjxyieMqXuoYjj0DSOAwAAAM2WYKopmjMnC0MevmRsfNB3s7xnU5C6zZgWB5970sfVTnUNRByHpnEcAAAAaLYEU01YCkNm9euf9zRaPMcBAAAAGobm5wAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC5a5/OwABSaJcs/qnFfcVFRtG/TqsHHLl2+MsqirNqxRVEUHdrWbeyyFSujtKz6sUnHtq3rbWzl/QAA0NJ5dwxArWx13hM17ttzi3Vj3PCdK7YHXDw+lq5YWe3YXfquHfeeOLBie9Dlz8TcxcurHbtdry7x8CmDKraHXPlsvDN/abVjN+veKZ46fXDF9sHXTIhpsz+sduwGXTvEc2fvVbF95A0T45X/LKh27NprtI3Jo/ap2B52y4sxacbcasd2aNMqply8X8X2SXe8FM9Mfb/KmLcuO6Da2wIAQEtkKR8AAAAAuVAxBUCtvH7R0FUuuavspVFDaj12wll71nrs+NMHr3J5XmWp0qq2Y+87ceAql+dVdtuxO9d67NhjBtR6LAAAtESCKQBq5fP0RmqosZX7QtXn2Mp9rPIaCwAALZGlfAAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC4EUwAAAADkQjAFAAAAQC5qf45uAAAAgCZuyfKPatxXXFQU7du0avCxS5evjLIoq3ZsURRFh7Z1G7tsxcooLat+bNKxbet6G1t5f0MSTAEAAADNxlbnPVHjvj23WDfGDd+5YnvAxeNj6YqV1Y7dpe/ace+JAyu2B13+TMxdvLzasdv16hIPnzKoYnvIlc/GO/OXVjt2s+6d4qnTB1dsH3zNhJg2+8Nqx27QtUM8d/ZeFdtH3jAxXvnPgmrHrr1G25g8ap+K7WG3vBiTZsytdmyHNq1iysX7VWyfdMdL8czU96uMeeuyA6IxWMoHAAAAQC5UTAEAAADNxusXDV3lkrvKXho1pNZjJ5y1Z63Hjj998CqX51X28CmDaj32vhMHrnJ5XmW3HbtzrceOPWZArcfWN8EUAAAA0Gx8nt5IDTW2cl+o+hzbvk3+Y+ubpXwAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuBFMAAAAA5EIwBQAAAEAuWufzsAAUnJkzI+bMyXsWhW+ddSJ69857FgAAzZf3rQX1vlUwBcBnmzkzSvv1i+IlS/KeScEr7dgxiqdMEU4BADQE71sL7n2rYAqAzzZnTvbL/eFLxsYHfTfLezYFq9uMaXHwuSd9/Bc8wRQAQP3zvrXg3rcKpgCotfTLfVa//nlPAwAAVsn71sKh+TkAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuXBWPgAACtKS5R/VuK+4qCjat2nV4GOXLl8ZZVFW7diiKIoObes2dtmKlVFaVv3YpGPb1vU2tvJ+AGhsfgsBAFCQtjrviRr37dmzXYz74toV2wN+814sXVl9eLPLum3j3j27VWwP+u2smFtSWu3Y7dZqEw/vs07F9pBHZ8c7S1ZWO3azzq3jqf3Wrdg++PH3Y9rC6kOvDTq2iucO7F6xfeRTc+KVeSuqHbt2u+KYfEiPiu1hz3wQk95fXu3YDq2KYsrh61Vsn/TnufHMf0uqjHnr5G0jeveu9vYA0NAs5QMAoPDMnLnq/RMmRAwY8L/L0qU1j33ppapj582reezr/6g69r/v1jx2+ptVx6btmqT7qTw2PU5N0vwqj03zr0l63pXHptflE0r79fvs1xMAGoiKKQAACs+cOfH6lYfH7867Kub22eRTu4u3GRTjvjK+YntEac1L3Yq22THGHfi/sSeucuy2MW7o/8YOK61pcV5anrd5jNvrf2OPWOXY3jHui/8be2BpWRxQ4yy6x7g7/zd279Ky2KvGse2qjN2ttCx2rbR37bfejOIlS7LXU9UUAHkQTAEAUJA6riiJDzfeNOb165/3VApW2+KivKcAQAtnKR8AAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuWhywdQ777wTxxxzTHTr1i06dOgQ2267bfz1r3+t2F9WVhbnnXde9OzZM9s/ZMiQmDZtWq5zBgAAAKDAg6l58+bF7rvvHm3atInHHnssXn/99fjZz34Wa621VsWYK664In7xi1/E9ddfH5MmTYo11lgjhg4dGsuWLct17gAAAAB8Pq2jCbn88stjww03jHHjxlVc17dv3yrVUmPGjIlzzz03DjnkkOy622+/PXr06BEPPfRQfP3rX89l3gAAAAAUeMXUww8/HDvuuGMcccQR0b1799h+++3jpptuqtg/Y8aMeO+997Lle+W6dOkSu+yyS0ycODGnWQMAAABQ8MHU9OnTY+zYsbHZZpvFE088ESeddFJ8//vfj9tuuy3bn0KpJFVIVZa2y/d9UklJSSxcuLDKBQAAAID8NamlfKWlpVnF1I9//ONsO1VMvfbaa1k/qWHDhtXpPkePHh0XXnhhPc8UAAAAgGZVMZXOtLfVVltVua5fv34xc+bM7Pv11lsv+zpr1qwqY9J2+b5PGjlyZCxYsKDi8vbbbzfY/AEAAAAo0GAqnZFv6tSpVa574403YqONNqpohJ4CqKeffrpif1qal87ON3DgwGrvs127dtG5c+cqFwAAAAAKOJhauXJl3HPPPXHiiSfGV77ylXj11Vez61NV0gMPPPCpqqbaOO200+KFF17IlvL961//irvuuituvPHG+O53v5vtLyoqihEjRsQll1ySNUpPj/nNb34z1l9//Tj00EPr+lQAAAAAKJQeU/Pnz4/99tsvXnzxxejUqVMsXrw4vve972X70nZqWJ4Co/JeUbW10047xYMPPpgtv7vooouyCqkxY8bE0UcfXTHmzDPPzB7vhBNOyOYxaNCgePzxx6N9+/Z1eSoAAAAAFFLF1Nlnnx3/+Mc/sjPnpTPplZWVVexr1apVfPWrX43f//73dZrQgQcemFVCLVu2LKZMmRLf/va3q+xPVVMptEpn4Utjxo8fH5tvvnmdHgsAAACAAgumHnrooaxCap999smCok9KQdFbb71VH/MDAAAAoJmqUzCV+kilZXY1WbFiRXz00UerMy8AAAAAmrk6BVObbLJJTJ48ucb9Tz75ZGy11VarMy8AAAAAmrk6BVPHH3983HLLLXHvvfdW9JdKS/pKSkrinHPOyZqRp7P1AQAAAEC9npXv1FNPzZqfH3XUUdG1a9fsuv/7v/+LDz74IFvCl0Kp4447ri53DQAAAEALUadgKlVH3XTTTTFs2LD49a9/HdOmTYvS0tJsid+RRx4Ze+yxR/3PFAAAAIBmpU7BVLlBgwZlFwAAAABolB5Txx57bIwcOTKWLl1a7f4XXnghGwMAAAAA9RpM3XrrrXHFFVfEbrvtFm+99dan9r/55ptx22231cf8AAAAAGim6hRMlTdAnzdvXuy4447x1FNP1e+sAAAAAGj26hxMpUDqpZdeiu233z6+/OUvx2WXXVa/MwMAAACgWatzMJV069YtnnjiifjhD38Y55xzThxxxBGxePHi+psdAAAAAM1W8WrfQXFxjB49On7zm99kS/p23XXXeOONN+pndgAAAAA0W6sdTJU79NBD48UXX4zS0tK4+OKL6+tuAQAAAGimWtflRueff35st912n7p+8803z8KpCy64IObMmVMf8wPIdJsxLe8pFCyvHQC0DH7nrx6vHxRYMFWTNdZYI37yk5+szpwA/qekJEqLi+Pgc0/KeyYFLb2GxSUleU8DAGgI3i/VG++ZoIkGUzNnzsy+9u7du8r2ZykfD1Bn7dpFcWlpPHvyyFiwvv+n1EWXd2fG4OtGZ68lANAMeb9UL7xngiYcTPXp0yeKiopi6dKl0bZt24rtz7Jy5cr6mCNATN9975jVr3/e0yhIPaa8/PGbLACgWfN+afV4zwRNOJi65ZZbsiCqTZs2VbYBAAAAoEGDqW9961ur3AYAAACAz6s46klpaWnMnj07ysrK6usuAQAAAGjGah1MvfHGG3H77bfHvHnzqly/YMGC+OY3vxkdO3aMnj17xrrrrhvXXHNNQ8wVAAAAgJYYTP3sZz+LUaNGRdeuXatcf+KJJ8Ydd9wRG220URx22GHRrl27OPXUU+Ohhx5qiPkCAAAA0NKCqeeeey4OPPDAKk3P33777bjvvvti4MCB8Y9//CPuv//+7OvGG28c1157bUPNGQAAAICWFEy98847seWWW1a57tFHH82CqlQh1br1x33UU0VVWtr3t7/9rf5nCwAAAEDLC6ZSc/M2bdpUuW7ChAnZ18GDB1e5vlevXrFo0aL6miMAAAAALTmY2mSTTeKFF16o2F65cmX84Q9/yKqoevToUWXs3LlzsyboAAAAAFCTj9ff1cKwYcPijDPOiH79+sVuu+0Wd955Z8yePTu+//3vf2rsn//859h8881re9cAAAAAtEC1DqZOPvnkGD9+fIwcOTLrK1VWVpYt4fvhD39YZVxqiP7YY4/FJZdc0hDzBQAAAKClBVOpv9QjjzwSf/3rX+PNN9+MjTbaKHbddddPjSspKYm77ror9thjj/qeKwAAAAAtMZgqt+OOO2aXmmy66abZBQAAAADqpfk5AAAAANQnwRQAAAAAuRBMAQAAAJALwRQAAAAATTuYmj17dsPOBAAAAIAWpdbBVM+ePWOXXXaJiy66KF566aWGnRUAAAAAzV6tg6mHHnoodthhh7j55ptjp512yoKqY489Nh544IFYtGhRw84SAAAAgGandW0HHnTQQdklefXVV+P3v/99dvn6178eRUVFsfvuu8cBBxyQXbbccsuGnDMAAAAALbX5+bbbbhtnnXVWPPvss/H+++/H7bffHr17946f/OQnsfXWW8cmm2wS3/ve9+KJJ56IkpKS+p81AAAAAAVvtc/K16VLl/ja174Wt956a7z33nsxceLE+MY3vhGTJk3Kqqcuv/zy+pkpAAAAAC1zKV9t7bzzztnlggsuyM7kt2DBgvp+CAAAAACagXoPpirr3r17dgEAAACAel/KBwAAAAB1IZgCAAAAoPkt5WP1dJsxLe8pFCyvHQAAADR9gqmmqKQkSouL4+BzT8p7JgUtvYbFJSV5TwMAAACoz2Bq//33j2984xvxla98JTp06FCXu2BV2rWL4tLSePbkkbFg/d55z6YgdXl3Zgy+bnT2WgIAAADNKJiaPn16HHPMMdGpU6csnEoh1d577x1FRUX1P8MWbPrue8esfv3znkZB6jHl5Y+DKQAAAKB5NT+fOnVqTJo0KYYPHx5PPvlkDB06NHr16hVnnHFG/P3vf6//WQIAAADQ7NT5rHw77bRTXHXVVfHOO+/E73//+9hrr73ihhtuiAEDBsQ222wTV1xxRfznP/+p39kCAAAA0GwUr/YdFBdnFVO/+tWvYubMmfHVr341Xn/99Tj77LOjT58+MWTIkPjd735XP7MFAAAAoNlY7WAqmTBhQnznO9+JTTfdNO6///6Kiqmf/exn8f7778fBBx8c5513Xn08FAAAAAAtufl5kqqi7rjjjrj77ruzSqnu3bvHsGHDskboX/jCFyrGnXrqqXHCCSfEtddeGxdddFF9zRsAAACAlhhMpeDp1VdfjXbt2sUhhxwS1113XbacLy3rq86ee+4Zv/zlL1d3rgAAAAC09GCqa9euceONN8YRRxwRnTt3/szxKbyaMWNGXR4KAAAAgGaqTsHU7bffHuuuu2506NCh2v1Lly7Nekv17t072+7YsWNstNFGqzdTAAAAAJqVOjU/79u3bzz44IM17n/44YezMQAAAABQr8FUWVnZKvevWLGixn5TAAAAAPC5lvItXLgw5s+fX7H9wQcfZGfj+6Q05p577omePXt6hQEAAABY/WDq5z//eVx00UXZ90VFRTFixIjsUlNF1SWXXFLbuwYAAACgBap1MLXvvvtGp06dstDpzDPPjKOOOip22GGHKmNSYLXGGmvEgAEDYscdd2yI+UKj6zZjWt5TKFheOwAAAOolmBo4cGB2SRYvXhyHHXZYbLvttrW9ORSekpIoLS6Og889Ke+ZFLT0GhaXlOQ9DQAAAAo5mKrs/PPPr/+ZQFPTrl0Ul5bGsyePjAXr9857NgWpy7szY/B1o7PXEgAAAOoUTKXeUmmZ3jnnnJOdba+819SqpPGjRo2qzd1DkzZ9971jVr/+eU+jIPWY8vLHwRQAAADUNZi64IILsqDprLPOirZt22bbn0UwBQAAQEuyZPlHNe4rLiqK9m1aNfjYpctXRlmUVTu2KIqiQ9u6jV22YmWUllU/NunYtnW9ja28n+avVke7tLR0ldsAAADQ0m113hM17ttzi3Vj3PCdK7YHXDw+lq5YWe3YXfquHfee+HGP52TQ5c/E3MXLqx27Xa8u8fApgyq2h1z5bLwzf2m1Yzfr3imeOn1wxfbB10yIabM/rHbsBl07xHNn71WxfeQNE+OV/yyoduzaa7SNyaP2qdgedsuLMWnG3GrHdmjTKqZcvF/F9kl3vBTPTH2/ypi3Ljug2tvSPBXnPQEAAAAAWqY61cfNmDEjXnvttTjooIOq3f/II49kZ+zr06fP6s4PAAAACsLrFw1d5ZK7yl4aNaTWYyectWetx44/ffAql+dVliqtajv2vhMHrnJ5XmW3HbtzrceOPWZArcfSPNUpmPrhD38YCxcurDGYuvbaa6Nr165xzz33rO78AAAAoCB8nt5IDTW2cl+o+hxbuY9VXmNpnuoUTE2cODFGjBhR4/699947xowZszrzAgBosjS3rZ+xmtsCAHV6NzBv3rxYc801a9zfqVOn+OCDD1ZnXgAATZbmth/T3BYAyKX5ee/eveO5556rcf+f//zn6NWr1+rMCwAAAIBmrk4VU0cddVRcfPHFsfPOO8cpp5wSxcUf51srV66Ma665Ju69994455xz6nuuAABNgua2H9PcFgDIJZgaOXJkTJgwIeszdemll8YWW2yRXT916tR4//3340tf+pJgCgBotjS3bdixAEDLUadgql27dvHkk0/GbbfdFg888EC8+eab2fWpgurwww+Pb37zmxVVVAAAQPPlZAD1M9bJAICWqs7/90vB0/Dhw7MLAADQMjkZwMecDACgbpQ1AQAAAFBYFVPvvfde3HzzzTF58uRYsGBBlJaWVtlfVFQUTz/9dH3MEQAAaKKcDOBjTgYA0IjB1CuvvJI1OF+6dGnW+PzVV1+NrbbaKubPnx/vvPNObLLJJrHhhhvWcUoAAEChcDKAhh0L0NzVaSnf2WefHZ06dcrOwjd+/PgoKyuLq666Kt5+++249957Y968eXHZZZfV/2wBAAAAaNkVU88991yceeaZ0bt375g79+MGf+VL+Y444oiYMGFCnHHGGfHss8/W72wBoIVz9qv6GevsVwAATUOd3pWlEKpHjx7Z9127do1WrVpVBFTJtttum/WfAgDql7NffczZrwAAWvBSvr59+8aMGTM+voPi4mw7Lekr9/zzz2eBFQAAAADUa8XUvvvuG/fff39ceuml2fZJJ50UP/jBD2L69OlZv6k//vGP2TYAUL+c/epjzn4FANCCg6lzzjknjjrqqFixYkW0adMmRowYEYsXL47f/OY32bK+UaNGxY9+9KP6ny0AtHDOftWwYwEAKIBgaq211ooBAwZUbBcVFcW5556bXQAAAACgNlb7lDSzZ8+Ot956K/u+T58+0b1799W9SwAAAABagDo1P0+efvrp2HHHHaNnz54xcODA7JK+T9dVboQOAAAAAPVWMfXggw/GEUccET169IgzzzwzNt988+z6qVOnxq9+9avYf//947777ouvfOUrdbl7AAAAAFqAOgVTqZfUNttsE3/+859jzTXXrLIvNT0fNGhQNkYwBQAAAEC9LuWbPn16DB8+/FOhVNK5c+c47rjjYsaMGXW5awAAAABaiDoFU1tuuWXW9Lwms2bNqljeBwAAAAD1FkxdccUVcf3118dvf/vbavtP3XDDDfHTn/60LncNAAAAQAtRp2Dq6quvjnXXXTcOO+yw2HDDDWPPPffMLun7r371q9G9e/f4xS9+EQcffHDF5ZBDDvlcj3HZZZdFUVFRjBgxouK6ZcuWxXe/+93o1q1bdOrUKQ4//PCsOgsAAACAFtL8/JVXXslCo969e2fbb7311sd31rp1dl0KkF599dUqt0nja+svf/lLVnW13XbbVbn+tNNOi9/97ndx//33R5cuXeKUU07JwrHnnnuuLk8DAAAAgEILpsqDqIbw4YcfxtFHHx033XRTXHLJJRXXL1iwIG6++ea46667Yq+99squGzduXPTr1y9eeOGF2HXXXRtsTgAAAAA0kaV8DSkt1TvggANiyJAhVa5/6aWXYsWKFVWuT03YU4XWxIkTa7y/kpKSWLhwYZULAAAAAAVaMVXZokWLsmqm0tLST+0rX+pXW/fcc09Mnjw5W8r3Se+99160bds2unbtWuX6Hj16ZPtqMnr06Ljwwgs/1zwAAAAAaMLB1NixY+PKK6+M6dOn1zhm5cqVtb6/t99+O0499dR46qmnon379lFfRo4cGaeffnrFdqqYSk3aAQAAACjApXzXX399tuRu0003zfpAlZWVZWfPO/vss2O99daL/v37Z/2gPo+0VG/27Nmxww47ZE3U0+XZZ5/Nzu6Xvk+VUcuXL4/58+dXuV06K196zJq0a9cuOnfuXOUCAAAAQIEGU1dffXUMHTo0HnvssTjhhBOy61JfqEsvvTRef/31bHnfBx988Lnuc++9987O5Pf3v/+94rLjjjtmjdDLv2/Tpk08/fTTFbeZOnVqzJw5MwYOHFiXpwEAAABAoS3le/PNN7OKqSSFRUmqZkq6dOkSxx9/fFx33XXxgx/8oNb3ueaaa8Y222xT5bo11lgjunXrVnH9cccdly3LW3vttbPKp+9973tZKOWMfAAAAAAtJJhK4dNHH32UfZ8Coo4dO2Y9oiqHTKtqSF5XP//5z6O4uDgOP/zw7Gx7qWorBWAAAAAAtJBgKlUwvfzyyxXbqWIpNUP/8pe/nJ2d74YbbojNN998tSf3xz/+scp2aop+7bXXZhcAAAAAWmAwdcwxx2QN0FPVUmoufuGFF8aQIUOid+/eFcv7fvOb39T3XAEAAABo6cHU8OHDs0u53XffPf7xj3/EI488Eq1atYp99923XiqmAAAAAGi+6hRMVWfjjTeOU089tb7uDgAAAIBmrtbB1LJly2LEiBGx9dZbZ2fDq8kvfvGLmDJlSva1/Ix9AEA9mTkzYs6cvGdR+NZZJ+L/tyAAAKAAgqkbb7wxbr311nj99ddXOe6AAw6IM888M7bbbrs46aST6mOOAEAyc2aU9usXxUuW5D2TglfasWMUT5kinAIAKJRg6r777ovDDz88W7K3KptsskkcccQRcffddwumAKA+zZmThVIPXzI2Pui7Wd6zKVjdZkyLg8896ePKM8EUAEBhBFOvvvpqHH300bUau9tuu2WN0AGA+pdCqVn9+uc9DQAAWG3FtR24fPnyaNu2ba3GpnElJSWrMy8AAAAAmrlaV0ytv/768dprr9VqbBqXxgMAAM2ckzLUDydlAFqoWgdTQ4YMidtvvz1GjhwZ3bt3r3Hc7Nmzs3GpzxQAANCMOSlDvXFSBqClqnUwddZZZ8Udd9wRe+21V9x8882xyy67fGrMpEmT4vjjj49ly5bFGWecUd9zBQAAmhInZagXTsoAtGS1DqbS2fjSmfmOOuqorLl52t52221jzTXXjEWLFmXL9958883o2LFj3HPPPdnZ+QAAgObPSRkAaPBgKjnggAPilVdeicsvvzweffTReOihhyr2pZ5S3/72t+PMM8/MQisAAAAAqLdgKunTp0+MHTs2u6RKqYULF0bnzp2zyikAAAAAaLBgqrIURgmkAAAAAKiL4jrdCgAAAABWk2AKAAAAgFwIpgAAAAAovB5TALQs3WZMy3sKBc3rBwAAVQmmAPhsJSVRWlwcB597Ut4zKXjpdSwuKcl7GgAA0CQIpgD4bO3aRXFpaTx78shYsH7vvGdTsLq8OzMGXzc6ez0BAADBFACfw/Td945Z/frnPY2C1WPKyx8HUwAAQEbzcwAAAAByIZgCAAAAIBeCKQAAAAByIZgCAAAAIBeCKQAAAAByIZgCAAAAIBeCKQAAAABy0TqfhwUAgNXXbca0vKdQ0Lx+AORNMAUAQOEpKYnS4uI4+NyT8p5JwUuvY3FJSd7TAKCFEkwBAFB42rWL4tLSePbkkbFg/d55z6ZgdXl3Zgy+bnT2egJAHgRTAAAUrOm77x2z+vXPexoFq8eUlz8OpgAgJ5qfAwAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuWidz8MCfD7dZkzLewoFy2sHAEBL4z1w4bx+gimgaSspidLi4jj43JPynklBS69hcUlJ3tMAAICG5fNDwX2GEEwBTVu7dlFcWhrPnjwyFqzfO+/ZFKQu786MwdeNzl5LAABo1nx+KLjPEIIpoCBM333vmNWvf97TKEg9prz88S8VAABoIXx+KJzPEJqfAwAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuRBMAQAAAJALwRQAAAAAuWidz8NSG91mTMt7CgXLawcAQEvjPfDq8fpBPgRTTVFJSZQWF8fB556U90wKWnoNi0tK8p4GAAA0LJ8f6o3PEND4BFNNUbt2UVxaGs+ePDIWrN8779kUpC7vzozB143OXksAAGjWfH6oFz5DQD4EU03Y9N33jln9+uc9jYLUY8rLH/9SAQCAFsLnh9XjMwTkQ/NzAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF4IpAAAAAHIhmAIAAAAgF63zeVgoHN1mTMt7CgXLawcAAMCqCKagJiUlUVpcHAefe1LeMylo6TUsLinJexoAAAA0QYIpqEm7dlFcWhrPnjwyFqzfO+/ZFKQu786MwdeNzl5LAAAA+CTBFHyG6bvvHbP69c97GgWpx5SXPw6mAAAAoBqanwMAAACQC8EUAAAAALkQTAEAAACQC8EUAAAAALkQTAEAAACQC8EUAAAAALkQTAEAAACQC8EUAAAAALloUsHU6NGjY6eddoo111wzunfvHoceemhMnTq1yphly5bFd7/73ejWrVt06tQpDj/88Jg1a1ZucwYAAACgGQRTzz77bBY6vfDCC/HUU0/FihUrYt99943FixdXjDnttNPikUceifvvvz8b/+6778Zhhx2W67wBAAAA+PxaRxPy+OOPV9m+9dZbs8qpl156KfbYY49YsGBB3HzzzXHXXXfFXnvtlY0ZN25c9OvXLwuzdt1115xmDgAAAEBBV0x9UgqikrXXXjv7mgKqVEU1ZMiQijFbbrll9O7dOyZOnFjtfZSUlMTChQurXAAAAADIX5MNpkpLS2PEiBGx++67xzbbbJNd995770Xbtm2ja9euVcb26NEj21dT36ouXbpUXDbccMNGmT8AAAAABRpMpV5Tr732Wtxzzz2rdT8jR47MKq/KL2+//Xa9zREAAACAZtJjqtwpp5wSjz76aPzpT3+KXr16VVy/3nrrxfLly2P+/PlVqqbSWfnSvuq0a9cuuwAAAADQtDSpiqmysrIslHrwwQfjD3/4Q/Tt27fK/gEDBkSbNm3i6aefrrhu6tSpMXPmzBg4cGAOMwYAAACgWVRMpeV76Yx7v/3tb2PNNdes6BuVekN16NAh+3rcccfF6aefnjVE79y5c3zve9/LQiln5AMAAAAoLE0qmBo7dmz29Utf+lKV68eNGxff+ta3su9//vOfR3FxcRx++OHZGfeGDh0a1113XS7zBQAAAKCZBFNpKd9nad++fVx77bXZBQAAAIDC1aR6TAEAAADQcgimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXAimAAAAAMiFYAoAAACAXBRkMHXttddGnz59on379rHLLrvEiy++mPeUAAAAAGjuwdS9994bp59+epx//vkxefLk6N+/fwwdOjRmz56d99QAAAAAaM7B1JVXXhnf/va3Y/jw4bHVVlvF9ddfHx07doxbbrkl76kBAAAA0FyDqeXLl8dLL70UQ4YMqbiuuLg42544cWKucwMAAADg82kdBWTOnDmxcuXK6NGjR5Xr0/Y///nPam9TUlKSXcotWLAg+7pw4cJosj78MPvSYcor0XnJ4rxnU5A6/PvNWFj+Wtb1WDsOq81xaBoch2Z0LByHpvMzQf78PNQL/19qGhyHpsHvh2bCz0OT+Hkoz1zKyso+c2xRWW1GNRHvvvtubLDBBvH888/HwIEDK64/88wz49lnn41JkyZ96jYXXHBBXHjhhY08UwAAAICW7e23345evXo1n4qpddZZJ1q1ahWzZs2qcn3aXm+99aq9zciRI7Nm6eVKS0tj7ty50a1btygqKmrwOTdXKf3ccMMNs39knTt3zns6LZbj0DQ4Dk2D49A0OA5Ng+PQNDgOTYPj0DQ4Dk2D49A0tITjUFZWFosWLYr111//M8cWVDDVtm3bGDBgQDz99NNx6KGHVgRNafuUU06p9jbt2rXLLpV17dq1UebbEqQfoub6g1RIHIemwXFoGhyHpsFxaBoch6bBcWgaHIemwXFoGhyHpqG5H4cuXbrUalxBBVNJqn4aNmxY7LjjjrHzzjvHmDFjYvHixdlZ+gAAAAAoHAUXTH3ta1+L999/P84777x477334gtf+EI8/vjjn2qIDgAAAEDTVnDBVJKW7dW0dI/GkZZHnn/++Z9aJknjchyaBsehaXAcmgbHoWlwHJoGx6FpcByaBsehaXAcmgbHoYDPygcAAABA81Gc9wQAAAAAaJkEUwAAAADkQjAFAAAAQC4EU3wuf/rTn+Kggw6K9ddfP4qKiuKhhx7Ke0otzujRo2OnnXaKNddcM7p37x6HHnpoTJ06Ne9ptThjx46N7bbbLjp37pxdBg4cGI899lje02rxLrvssuz/TSNGjMh7Ki3KBRdckL3ulS9bbrll3tNqkd5555045phjolu3btGhQ4fYdttt469//Wve02pR+vTp86mfh3T57ne/m/fUWpSVK1fGqFGjom/fvtnPwiabbBIXX3xxaK/b+BYtWpT9Xt5oo42yY7HbbrvFX/7yl7ynFS39c1v6WUhnuu/Zs2d2XIYMGRLTpk3Lbb4t9Tg88MADse+++2a/t9P+v//979ESCab4XBYvXhz9+/ePa6+9Nu+ptFjPPvts9ub2hRdeiKeeeipWrFiR/c8sHRsaT69evbIQ5KWXXso+9O21115xyCGHxD/+8Y+8p9ZipTe5N9xwQxYY0vi23nrr+O9//1txmTBhQt5TanHmzZsXu+++e7Rp0yYLyl9//fX42c9+FmuttVbeU2tx/y+q/LOQflcnRxxxRN5Ta1Euv/zy7I9I11xzTUyZMiXbvuKKK+Lqq6/Oe2otzvHHH5/9HPzqV7+KV199NXvfmkKQFKST3+e29PPwi1/8Iq6//vqYNGlSrLHGGjF06NBYtmxZo8+1JR+HtH/QoEHZ/6NaMmflo85Sovvggw9mFTvk5/33388qp1Jgtccee+Q9nRZt7bXXjp/85Cdx3HHH5T2VFufDDz+MHXbYIa677rq45JJL4gtf+EKMGTMm72m1qIqp9BfAlvpXvqbi7LPPjueeey7+/Oc/5z0VKkmVIo8++mhWiZDeO9E4DjzwwOjRo0fcfPPNFdcdfvjhWWXIHXfckevcWpKlS5dmVf6//e1v44ADDqi4fsCAAbH//vtnv7Np/M9tKQJIFTw/+MEP4oc//GF23YIFC7KfmVtvvTW+/vWv5zzjlvf5+a233soqPP/2t79l72NbGhVTUODSL5HyUIT8lgvcc8892V880pI+Gl+qIkxveNNfYMlH+tCd3uRuvPHGcfTRR8fMmTPznlKL8/DDD8eOO+6YVeakP1hsv/32cdNNN+U9rRZt+fLlWQhy7LHHCqUaWVou9vTTT8cbb7yRbb/88stZJWcKQ2g8H330UfY+qX379lWuTwGhytr8zJgxI957770q75u6dOkSu+yyS0ycODHXudEytc57AkDdlZaWZn+JTUs3ttlmm7yn0+KkcvQURKWS506dOmV/Adlqq63ynlaLk0LByZMn61eRo/RGNv2FdYsttsiWLl144YXxxS9+MV577bXsL+U0junTp2dLl04//fT40Y9+lP1MfP/734+2bdvGsGHD8p5ei5QqCefPnx/f+ta38p5Ki6wgXLhwYdbvrlWrVlk4cumll2bBOY0n/Q5I75VSf69+/fplFTl33313Fn5suummeU+vxUqhVJKOR2Vpu3wfNCbBFBR4lUj64OcvTvlIH8LT0qVUtfbrX/86++CXllQKpxrP22+/HaeeemrWu+KTf42l8VSuQEg9vlJQlZrc3nfffZa2NvIfK1LF1I9//ONsO1VMpd8RqX+IYCofaRlZ+vlI1YQ0rvT/nzvvvDPuuuuurAde+n2d/piXjoWfh8aVekulqsENNtggCwnT0vujjjoq69MJkFjKBwXqlFNOyXpWPPPMM1kjbhpfqkJIf+1LfRLS2RJTY8Orrroq72m1KOlN7ezZs7M3ua1bt84uKRxMzTzT9+kv5DS+rl27xuabbx7/+te/8p5Ki5LOrPTJYDxVKFhWmY9///vfMX78+KzxM43vjDPOyKqmUq+cdHbKb3zjG3Haaadlv69pXOmMiOl3c+oHmf6g9OKLL2Yn70lLv8nHeuutl32dNWtWlevTdvk+aEyCKSgwqVlhCqXSsrE//OEPWZM8mk61QklJSd7TaFH23nvvbEll+kt4+SVVjKSlGun79JdZGl/68PHmm29mQQmNJy3rnjp1apXrUn+dVL1G4xs3blzW66tyw2caz5IlS6K4uOpHnfQ7If2uJh/prG/p90I6g+gTTzyRnc2YfKTPDymASn3YyqWlr+nsfPqlkgdL+fjcHzYq/wU8Nc5LH/5S4+3evXvnOreWtHwvlaWns5ukdfvl68BTw8LUSJLGMXLkyGx5Rvp3v2jRouyY/PGPf8zeaNF40s/AJ/urpTe+3bp103etEaUz+hx00EFZAPLuu+/G+eefn30ATEs1aDypGiQ1fE5L+Y488sisKuHGG2/MLjSuFH6kYCotGUvVmzS+9P+k1FMq/Z5OS/nSma6uvPLKbEkZjSu9N0p/WE0tENLniFTNlnp/DR8+PO+ptejPbWlpazor4mabbZYFVaNGjcqWujrjeuMeh7lz52aVzen9U1L+B6YUHLao6rUy+ByeeeaZsvTP5pOXYcOG5T21FqO61z9dxo0bl/fUWpRjjz22bKONNipr27Zt2brrrlu29957lz355JN5T4uysrLBgweXnXrqqXlPo0X52te+VtazZ8/s52GDDTbItv/1r3/lPa0W6ZFHHinbZpttytq1a1e25ZZblt144415T6lFeuKJJ7LfzVOnTs17Ki3WwoULs98FvXv3Lmvfvn3ZxhtvXHbOOeeUlZSU5D21Fufee+/NXv/0O2K99dYr++53v1s2f/78vKdV1tI/t5WWlpaNGjWqrEePHtnvjPRe1v+zGv84pM9wUc3+888/v6wlKUr/yTscAwAAAKDl0WMKAAAAgFwIpgAAAADIhWAKAAAAgFwIpgAAAADIhWAKAAAAgFwIpgAAAADIhWAKAAAAgFwIpgAAAADIhWAKAGA1FBUVxQUXXFBv9/etb30r+vTpU2/311D3CQBQHwRTAACV3HrrrVnYVPnSvXv32HPPPeOxxx7Le3oAAM1K67wnAADQFF100UXRt2/fKCsri1mzZmWB1Ze//OV45JFH4sADD6wYt3Tp0mjduv7eUt10001RWlpab/cHANCUCaYAAKqx//77x4477lixfdxxx0WPHj3i7rvvrhJMtW/fvl4ft02bNvV6fwAATZmlfAAAtdC1a9fo0KHDp6qjPtljKn2frvvXv/6V9XZKt+vSpUsMHz48lixZ8rn7Qb311lvZ/f30pz+NG2+8MTbZZJNo165d7LTTTvGXv/zlU7d/6KGHYptttskCs/T1wQcfrPZxUlXWmDFjYuutt87GptDtxBNPjHnz5lWMOf/886O4uDiefvrpKrc94YQTom3btvHyyy9/5vMBAFgVFVMAANVYsGBBzJkzJ1vKN3v27Lj66qvjww8/jGOOOaZWtz/yyCOzpYCjR4+OyZMnxy9/+cusV9Xll19ep/ncddddsWjRoiw8SkHVFVdcEYcddlhMnz69osrqySefjMMPPzy22mqr7HE/+OCDLBDr1avXp+4v3U9anpj2f//7348ZM2bENddcE3/729/iueeey+7z3HPPzZYupmqxV199NdZcc8144oknsuWGF198cfTv379OzwUAoJxgCgCgGkOGDKmynaqUbrnllthnn31qdfvtt98+br755ortFBKl7boGUzNnzoxp06bFWmutlW1vscUWccghh2RBUfnSwrPOOiurfJowYUJWpZUMHjw49t1339hoo40q7ivtT0HZnXfeGf/3f/9XcX1q8L7ffvvF/fffn12fwqnbb789BgwYEKeffnr85Cc/yUKqtMTx7LPPrtPzAACozFI+AIBqXHvttfHUU09llzvuuCMLbY4//vh44IEHanX773znO1W2v/jFL2bh1MKFC+s0n6997WsVoVT5/SWpYir573//G3//+99j2LBhFaFUkoK0VEFVWQqe0pi0L1WFlV9SANWpU6d45plnKsam5YAXXnhhFmQNHTo0G3fbbbfVa8N3AKDl8o4CAKAaO++8c5Xm50cddVRWBXXKKadkFUqpx9Kq9O7du8p2eaiUejh17tz5c89nVfeX/Pvf/86+brbZZp+6baquSssJy6XKq7RUMS0trE5auljZGWecEffcc0+8+OKL8eMf//hTQRcAQF0JpgAAaiE1AU9VU1dddVUW7KSm4avSqlWraq9PPavqoj7vLzU+T6FUWspXnXXXXbfKdqrKSs85Sb2mAADqi2AKAKCWPvroo+xraoLe1JT3kCoPkCqbOnVqle10Zr/x48fH7rvvnp1p8LNCrHSmwFTlNWLEiKxi6qtf/WrWeB0AYHXpMQUAUAsrVqzIznqXlvD169cvmpqePXvGF77whaz/U1qmVy71yHr99dc/dcbAlStXZmfWqy58mz9/fsX2lVdeGc8//3zceOON2fjddtstTjrppKzXFADA6lIxBQBQjcceeyz++c9/VvRcuuuuu7JqpHQ2urr0iGoMo0ePjgMOOCAGDRoUxx57bMydOzeuvvrqbNlh5SqvdKa+E088MRufGqans/alM/Cl55cao6fliqkqasqUKTFq1KisYuqggw7KbnvrrbdmAdjJJ58c9913X47PFgBoDgRTAADVOO+88yq+b9++fWy55ZYxduzYLNBpqvbbb78sWDr33HNj5MiR2ZK9cePGxW9/+9v44x//WGXs9ddfn52F74Ybbogf/ehH2Vn2+vTpE8ccc0y2xC9VVKUz/K2zzjoxZsyYitul5uop0Dr11FOzYCpVXwEA1FVRWV07cAIAAADAatBjCgAAAIBcCKYAAAAAyIVgCgAAAIBcCKYAAAAAyIVgCgAAAIBcCKYAAAAAyIVgCgAAAIBcCKYAAAAAyIVgCgAAAIBcCKYAAAAAyIVgCgAAAIBcCKYAAAAAyIVgCgAAAIDIw/8DEx+DSQIxif4AAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 1200x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualization:\n",
    "fig, ax = plt.subplots(figsize=(12, 6))\n",
    "for b in range(NBINS):\n",
    "    y_offset = 0\n",
    "    for i in range(NITEMS):\n",
    "        if ct[b, i]:\n",
    "            rect = plt.Rectangle((b*1.2, y_offset), 1, sizes[i], edgecolor='red', facecolor='skyblue')\n",
    "            ax.add_patch(rect)\n",
    "            y_offset += sizes[i]\n",
    "    ax.hlines(capacities[b], b*1.2, b*1.2 + 1, linestyle='dashed')\n",
    "\n",
    "ax.set_xlim(-0.5, NBINS*1.2)\n",
    "ax.set_ylim(0, capacities.max() + 10)\n",
    "ax.set_xlabel('Bin index', fontsize=12)\n",
    "ax.set_ylabel('Capacity / Size', fontsize=12)\n",
    "ax.set_title('Bin Packing Solution')\n",
    "ax.set_xticks([b*1.2 + 0.5 for b in range(NBINS)])\n",
    "ax.set_xticklabels([str(b+1) for b in range(NBINS)])\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
