[
  {
    "path": ".gitignore",
    "content": "__pycache__\n.idea"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Emad Ehsan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Pipfile",
    "content": "[[source]]\nurl = \"https://pypi.org/simple\"\nverify_ssl = true\nname = \"pypi\"\n\n[packages]\nortools = \"*\"\npytest = \"*\"\nmatplotlib = \"*\"\ntyper = \"*\"\n\n[dev-packages]\n\n[requires]\npython_version = \"3.9\"\n"
  },
  {
    "path": "README.md",
    "content": "# Cutting Stock Problem\nCutting Stock Problem (CSP) deals with planning the cutting of items (rods / sheets) from given stock items (which are usually of fixed size).\n\n## New to Cutting Stock Problem? Understand Visually\n<a href=\"https://www.youtube.com/watch?v=4WXtfO9JB20\">\n\t<img src=\"./github/video-thumb.jpg\" alt=\"Video Tutorial on Cutting Stock Problem\">\n</a>\n\n\nThis implementation of CSP tries to answer\n> How to minimize number of stock items used while cutting customer order\n\n\nwhile doing so, it also caters\n> How to cut the stock for customer orders so that waste is minimum\n\n\nThe OR Tools also helps us in calculating the number of possible solutions for your problem. So in addition, we can also compute\n> In how many ways can we cut given order from fixed size Stock?\n\n\n## Quick Usage\nThis is how CSP Tools looks in action. Click [CSP Tool](https://emadehsan.com/csp/) to use it\n<a href=\"https://emadehsan.com/csp/\">\n\t<img src=\"./github/CSP-Tool.PNG\" alt=\"CSP Tool\">\n</a>\n\n## Libraries\n* [Google OR-Tools](https://developers.google.com/optimization)\n\n## Quick Start\nInstall [Pipenv](https://pipenv.pypa.io/en/latest/), if not already installed\n```sh\n$ pip3 install --user pipenv\n```\n\nClone this project and install packages\n```sh\n$ git clone https://github.com/emadehsan/csp\n$ cd csp\n$ pipenv install\n\n# activate env\n$ pipenv shell\n```\n\n## Run\nIf you run the `stock_cutter_1d.py` file directly, it runs the example which uses 120 as length of stock Rod and generates some customer rods to cut. You can update these at the end of `stock_cutter_1d.py`.\n```sh\n(csp) $ python csp/stock_cutter_1d.py\n```\n\nOutput:\n\n```sh\nnumRollsUsed 5\nStatus: OPTIMAL\nRoll #0: [0.0, [33, 33, 18, 18, 18]]\nRoll #1: [2.9999999999999925, [33, 30, 18, 18, 18]]\nRoll #2: [5.999999999999993, [30, 30, 18, 18, 18]]\nRoll #3: [2.9999999999999987, [33, 33, 33, 18]]\nRoll #4: [21.0, [33, 33, 33]]```\n```\n\n![Graph of Output](./github/graph-1d-b.PNG)\n\n\n### Using input file\nIf you want to describe your inputs in a file, [infile.txt](./infile.txt) describes the expected format\n\n```sh\n(csp) $ python3 csp/stock_cutter_1d.py infile.txt\n```\n\n\n## Thinks to keep in mind\n* Works with integers only: IP (Integer Programming) problems working with integers only. If you have some values that have decimal part, you can multiply all of your inputs with some number that will make them integers (or close estimation).\n* You cannot specify units: Whether your input is in Inches or Meters, you have to keep a record of that yourself and conversions if any.\n\n\n## CSP 2D\nCode for 2-dimensional Cutting Stock Problem is in [`deployment/stock_cutter.py`](deployment/stock_cutter.py) file. The `deployment` directory also contains code for the API server and deploying it on Heroku.\n\n## Resources\nThe whole code for this project is taken from Serge Kruk's\n* [Practical Python AI Projects: Mathematical Models of Optimization Problems with Google OR-Tools](https://amzn.to/3iPceJD)\n* [Repository of the code in Serge's book](https://github.com/sgkruk/Apress-AI/)\n"
  },
  {
    "path": "csp/__init__.py",
    "content": ""
  },
  {
    "path": "csp/read_lengths.py",
    "content": "import pathlib\nfrom typing import List\nimport re\nfrom math import ceil\ndef get_data(infile:str)->List[float]:\n    \"\"\" Reads a file of numbers and returns a list of (count, number) pairs.\"\"\"\n    _p = pathlib.Path(infile)\n    input_text = _p.read_text()\n    numbers = [ceil(float(n)) for n in re.findall(r'[0-9.]+', _p.read_text())]\n    quan = []\n    nr = []\n    for n in numbers:\n        if n not in nr and n != 0:\n            quan.append(numbers.count(n))\n            nr.append(n)\n    return list(zip(quan,nr))\n\n"
  },
  {
    "path": "csp/stock_cutter_1d.py",
    "content": "'''\nOriginal Author: Serge Kruk\nOriginal Version: https://github.com/sgkruk/Apress-AI/blob/master/cutting_stock.py\n\nUpdated by: Emad Ehsan\n'''\nfrom ortools.linear_solver import pywraplp\nfrom math import ceil\nfrom random import randint\nimport json\nfrom read_lengths import get_data\nimport typer\nfrom typing import Optional\n\ndef newSolver(name,integer=False):\n  return pywraplp.Solver(name,\\\n                         pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING \\\n                         if integer else \\\n                         pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)\n\n'''\nreturn a printable value\n'''\ndef SolVal(x):\n  if type(x) is not list:\n    return 0 if x is None \\\n      else x if isinstance(x,(int,float)) \\\n           else x.SolutionValue() if x.Integer() is False \\\n                else int(x.SolutionValue())\n  elif type(x) is list:\n    return [SolVal(e) for e in x]\n\ndef ObjVal(x):\n  return x.Objective().Value()\n\n\ndef gen_data(num_orders):\n    R=[] # small rolls\n    # S=0 # seed?\n    for i in range(num_orders):\n        R.append([randint(1,12), randint(5,40)])\n    return R\n\n\ndef solve_model(demands, parent_width=100):\n  '''\n      demands = [\n          [1, 3], # [quantity, width]\n          [3, 5],\n          ...\n      ]\n\n      parent_width = integer\n  '''\n  num_orders = len(demands)\n  solver = newSolver('Cutting Stock', True)\n  k,b  = bounds(demands, parent_width)\n\n  # array of boolean declared as int, if y[i] is 1, \n  # then y[i] Big roll is used, else it was not used\n  y = [ solver.IntVar(0, 1, f'y_{i}') for i in range(k[1]) ] \n\n  # x[i][j] = 3 means that small-roll width specified by i-th order\n  # must be cut from j-th order, 3 tmies \n  x = [[solver.IntVar(0, b[i], f'x_{i}_{j}') for j in range(k[1])] \\\n      for i in range(num_orders)]\n  \n  unused_widths = [ solver.NumVar(0, parent_width, f'w_{j}') \\\n      for j in range(k[1]) ] \n  \n  # will contain the number of big rolls used\n  nb = solver.IntVar(k[0], k[1], 'nb')\n\n  # consntraint: demand fullfilment\n  for i in range(num_orders):  \n    # small rolls from i-th order must be at least as many in quantity\n    # as specified by the i-th order\n    solver.Add(sum(x[i][j] for j in range(k[1])) >= demands[i][0]) \n\n  # constraint: max size limit\n  for j in range(k[1]):\n    # total width of small rolls cut from j-th big roll, \n    # must not exceed big rolls width\n    solver.Add( \\\n        sum(demands[i][1]*x[i][j] for i in range(num_orders)) \\\n        <= parent_width*y[j] \\\n      ) \n\n    # width of j-th big roll - total width of all orders cut from j-th roll\n    # must be equal to unused_widths[j]\n    # So, we are saying that assign unused_widths[j] the remaining width of j'th big roll\n    solver.Add(parent_width*y[j] - sum(demands[i][1]*x[i][j] for i in range(num_orders)) == unused_widths[j])\n\n    '''\n    Book Author's note from page 201:\n    [the following constraint]  breaks the symmetry of multiple solutions that are equivalent \n    for our purposes: any permutation of the rolls. These permutations, and there are K! of \n    them, cause most solvers to spend an exorbitant time solving. With this constraint, we \n    tell the solver to prefer those permutations with more cuts in roll j than in roll j + 1. \n    The reader is encouraged to solve a medium-sized problem with and without this \n    symmetry-breaking constraint. I have seen problems take 48 hours to solve without the \n    constraint and 48 minutes with. Of course, for problems that are solved in seconds, the \n    constraint will not help; it may even hinder. But who cares if a cutting stock instance \n    solves in two or in three seconds? We care much more about the difference between two \n    minutes and three hours, which is what this constraint is meant to address\n    '''\n    if j < k[1]-1: # k1 = total big rolls\n      # total small rolls of i-th order cut from j-th big roll must be >=\n      # totall small rolls of i-th order cut from j+1-th big roll\n      solver.Add(sum(x[i][j] for i in range(num_orders)) >= sum(x[i][j+1] for i in range(num_orders)))\n\n  # find & assign to nb, the number of big rolls used\n  solver.Add(nb == solver.Sum(y[j] for j in range(k[1])))\n\n  ''' \n    minimize total big rolls used\n    let's say we have y = [1, 0, 1]\n    here, total big rolls used are 2. 0-th and 2nd. 1st one is not used. So we want our model to use the \n    earlier rolls first. i.e. y = [1, 1, 0]. \n    The trick to do this is to define the cost of using each next roll to be higher. So the model would be\n    forced to used the initial rolls, when available, instead of the next rolls.\n\n    So instead of Minimize ( Sum of y ) or Minimize( Sum([1,1,0]) )\n    we Minimize( Sum([1*1, 1*2, 1*3]) )\n  ''' \n\n  '''\n  Book Author's note from page 201:\n\n  There are alternative objective functions. For example, we could have minimized the sum of the waste. This makes sense, especially if the demand constraint is formulated as an inequality. Then minimizing the sum of waste Chapter 7  advanCed teChniques\n  will spend more CPU cycles trying to find more efficient patterns that over-satisfy demand. This is especially good if the demand widths recur regularly and storing cut rolls in inventory to satisfy future demand is possible. Note that the running time will grow quickly with such an objective function\n  '''\n\n  Cost = solver.Sum((j+1)*y[j] for j in range(k[1]))\n\n  solver.Minimize(Cost)\n\n  status = solver.Solve()\n  numRollsUsed = SolVal(nb)\n\n  return status, \\\n    numRollsUsed, \\\n    rolls(numRollsUsed, SolVal(x), SolVal(unused_widths), demands), \\\n    SolVal(unused_widths), \\\n    solver.WallTime()\n\ndef bounds(demands, parent_width=100):\n  '''\n  b = [sum of widths of individual small rolls of each order]\n  T = local var. stores sum of widths of adjecent small-rolls. When the width reaches 100%, T is set to 0 again.\n  k = [k0, k1], k0 = minimum big-rolls requierd, k1: number of big rolls that can be consumed / cut from\n  TT = local var. stores sum of widths of of all small-rolls. At the end, will be used to estimate lower bound of big-rolls\n  '''\n  num_orders = len(demands)\n  b = []\n  T = 0\n  k = [0,1]\n  TT = 0\n\n  for i in range(num_orders):\n    # q = quantity, w = width; of i-th order\n    quantity, width = demands[i][0], demands[i][1]\n    # TODO Verify: why min of quantity, parent_width/width?\n    # assumes widths to be entered as percentage\n    # int(round(parent_width/demands[i][1])) will always be >= 1, because widths of small rolls can't exceed parent_width (which is width of big roll)\n    # b.append( min(demands[i][0], int(round(parent_width / demands[i][1]))) )\n    b.append( min(quantity, int(round(parent_width / width))) )\n\n    # if total width of this i-th order + previous order's leftover (T) is less than parent_width\n    # it's fine. Cut it.\n    if T + quantity*width <= parent_width:\n      T, TT = T + quantity*width, TT + quantity*width\n    # else, the width exceeds, so we have to cut only as much as we can cut from parent_width width of the big roll\n    else:\n      while quantity:\n        if T + width <= parent_width:\n          T, TT, quantity = T + width, TT + width, quantity-1\n        else:\n          k[1],T = k[1]+1, 0 # use next roll (k[1] += 1)\n  k[0] = int(round(TT/parent_width+0.5))\n\n  print('k', k)\n  print('b', b)\n\n  return k, b\n\n'''\n  nb: array of number of rolls to cut, of each order\n  \n  w: \n  demands: [\n    [quantity, width],\n    [quantity, width],\n    [quantity, width],\n  ]\n'''\ndef rolls(nb, x, w, demands):\n  consumed_big_rolls = []\n  num_orders = len(x) \n  # go over first row (1st order)\n  # this row contains the list of all the big rolls available, and if this 1st (0-th) order\n  # is cut from any big roll, that big roll's index would contain a number > 0\n  for j in range(len(x[0])):\n    # w[j]: width of j-th big roll \n    # int(x[i][j]) * [demands[i][1]] width of all i-th order's small rolls that are to be cut from j-th big roll \n    RR = [ abs(w[j])] + [ int(x[i][j])*[demands[i][1]] for i in range(num_orders) \\\n                    if x[i][j] > 0 ] # if i-th order has some cuts from j-th order, x[i][j] would be > 0\n    consumed_big_rolls.append(RR)\n\n  return consumed_big_rolls\n\n\n\n'''\nthis model starts with some patterns and then optimizes those patterns\n'''\ndef solve_large_model(demands, parent_width=100):\n  num_orders = len(demands)\n  iter = 0\n  patterns = get_initial_patterns(demands)\n  # print('method#solve_large_model, patterns', patterns)\n\n  # list quantities of orders\n  quantities = [demands[i][0] for i in range(num_orders)]\n  print('quantities', quantities)\n\n  while iter < 20:\n    status, y, l = solve_master(patterns, quantities, parent_width=parent_width)\n    iter += 1\n\n    # list widths of orders\n    widths = [demands[i][1] for i in range(num_orders)]\n    new_pattern, objectiveValue = get_new_pattern(l, widths, parent_width=parent_width)\n\n    # print('method#solve_large_model, new_pattern', new_pattern)\n    # print('method#solve_large_model, objectiveValue', objectiveValue)\n\n    for i in range(num_orders):\n      # add i-th cut of new pattern to i-thp pattern\n      patterns[i].append(new_pattern[i])\n\n  status, y, l = solve_master(patterns, quantities, parent_width=parent_width, integer=True)  \n\n  return status, \\\n          patterns, \\\n          y, \\\n          rolls_patterns(patterns, y, demands, parent_width=parent_width)\n\n\n'''\nDantzig-Wolfe decomposition splits the problem into a Master Problem MP and a sub-problem SP.\n\nThe Master Problem: provided a set of patterns, find the best combination satisfying the demand\n\nC: patterns\nb: demand\n'''\ndef solve_master(patterns, quantities, parent_width=100, integer=False):\n  title = 'Cutting stock master problem'\n  num_patterns = len(patterns)\n  n = len(patterns[0])\n  # print('**num_patterns x n: ', num_patterns, 'x', n)\n  # print('**patterns recived:')\n  # for p in patterns:\n  #   print(p)\n\n  constraints = []\n\n  solver = newSolver(title, integer)\n\n  # y is not boolean, it's an integer now (as compared to y in approach used by solve_model)\n  y = [ solver.IntVar(0, 1000, '') for j in range(n) ] # right bound?\n  # minimize total big rolls (y) used\n  Cost = sum(y[j] for j in range(n)) \n  solver.Minimize(Cost)\n\n  # for every pattern\n  for i in range(num_patterns):\n    # add constraint that this pattern (demand) must be met\n    # there are m such constraints, for each pattern\n    constraints.append(solver.Add( sum(patterns[i][j]*y[j] for j in range(n)) >= quantities[i]) ) \n\n  status = solver.Solve()\n  y = [int(ceil(e.SolutionValue())) for e in y]\n\n  l =  [0 if integer else constraints[i].DualValue() for i in range(num_patterns)]\n  # sl =  [0 if integer else constraints[i].name() for i in range(num_patterns)]\n  # print('sl: ', sl)\n\n  # l =  [0 if integer else u[i].Ub() for i in range(m)]\n  toreturn = status, y, l\n  # l_to_print = [round(dd, 2) for dd in toreturn[2]]\n  # print('l: ', len(l_to_print), '->', l_to_print)\n  # print('l: ', toreturn[2])\n  return toreturn\n\ndef get_new_pattern(l, w, parent_width=100):\n  solver = newSolver('Cutting stock sub-problem', True)\n  n = len(l)\n  new_pattern = [ solver.IntVar(0, parent_width, '') for i in range(n) ]\n\n  # maximizes the sum of the values times the number of occurrence of that roll in a pattern\n  Cost = sum( l[i] * new_pattern[i] for i in range(n))\n  solver.Maximize(Cost)\n\n  # ensuring that the pattern stays within the total width of the large roll \n  solver.Add( sum( w[i] * new_pattern[i] for i in range(n)) <= parent_width ) \n\n  status = solver.Solve()\n  return SolVal(new_pattern), ObjVal(solver)\n\n\n'''\nthe initial patterns must be such that they will allow a feasible solution, \none that satisfies all demands. \nConsidering the already complex model, let’s keep it simple. \nOur initial patterns have exactly one roll per pattern, as obviously feasible as inefficient.\n'''\ndef get_initial_patterns(demands):\n  num_orders = len(demands)\n  return [[0 if j != i else 1 for j in range(num_orders)]\\\n          for i in range(num_orders)]\n\ndef rolls_patterns(patterns, y, demands, parent_width=100):\n  R, m, n = [], len(patterns), len(y)\n\n  for j in range(n):\n    for _ in range(y[j]):\n      RR = []\n      for i in range(m):\n        if patterns[i][j] > 0:\n          RR.extend( [demands[i][1]] * int(patterns[i][j]) )\n      used_width = sum(RR)\n      R.append([parent_width - used_width, RR])\n\n  return R\n\n\n'''\nchecks if all small roll widths (demands) smaller than parent roll's width\n'''\ndef checkWidths(demands, parent_width):\n  for quantity, width in demands:\n    if width > parent_width:\n      print(f'Small roll width {width} is greater than parent rolls width {parent_width}. Exiting')\n      return False\n  return True\n\n\n'''\n    params\n        child_rolls: \n            list of lists, each containing quantity & width of rod / roll to be cut\n            e.g.: [ [quantity, width], [quantity, width], ...]\n        parent_rolls: \n            list of lists, each containing quantity & width of rod / roll to cut from\n            e.g.: [ [quantity, width], [quantity, width], ...]\n'''\ndef StockCutter1D(child_rolls, parent_rolls, output_json=True, large_model=True):\n\n  # at the moment, only parent one width of parent rolls is supported\n  # quantity of parent rolls is calculated by algorithm, so user supplied quantity doesn't matter?\n  # TODO: or we can check and tell the user the user when parent roll quantity is insufficient\n  parent_width = parent_rolls[0][1]\n\n  if not checkWidths(demands=child_rolls, parent_width=parent_width):\n    return []\n\n\n  print('child_rolls', child_rolls)\n  print('parent_rolls', parent_rolls)\n\n  if not large_model:\n    print('Running Small Model...')\n    status, numRollsUsed, consumed_big_rolls, unused_roll_widths, wall_time = \\\n              solve_model(demands=child_rolls, parent_width=parent_width)\n\n    # convert the format of output of solve_model to be exactly same as solve_large_model\n    print('consumed_big_rolls before adjustment: ', consumed_big_rolls)\n    new_consumed_big_rolls = []\n    for big_roll in consumed_big_rolls:\n      if len(big_roll) < 2:\n        # sometimes the solve_model return a solution that contanis an extra [0.0] entry for big roll\n        consumed_big_rolls.remove(big_roll)\n        continue\n      unused_width = big_roll[0]\n      subrolls = []\n      for subitem in big_roll[1:]:\n        if isinstance(subitem, list):\n          # if it's a list, concatenate with the other lists, to make a single list for this big_roll\n          subrolls = subrolls + subitem\n        else:\n          # if it's an integer, add it to the list\n          subrolls.append(subitem)\n      new_consumed_big_rolls.append([unused_width, subrolls])\n    print('consumed_big_rolls after adjustment: ', new_consumed_big_rolls)\n    consumed_big_rolls = new_consumed_big_rolls\n  \n  else:\n    print('Running Large Model...');\n    status, A, y, consumed_big_rolls = solve_large_model(demands=child_rolls, parent_width=parent_width)\n\n  numRollsUsed = len(consumed_big_rolls)\n  # print('A:', A, '\\n')\n  # print('y:', y, '\\n')\n\n\n  STATUS_NAME = ['OPTIMAL',\n    'FEASIBLE',\n    'INFEASIBLE',\n    'UNBOUNDED',\n    'ABNORMAL',\n    'NOT_SOLVED'\n    ]\n\n  output = {\n      \"statusName\": STATUS_NAME[status],\n      \"numSolutions\": '1',\n      \"numUniqueSolutions\": '1',\n      \"numRollsUsed\": numRollsUsed,\n      \"solutions\": consumed_big_rolls # unique solutions\n  }\n\n\n  # print('Wall Time:', wall_time)\n  print('numRollsUsed', numRollsUsed)\n  print('Status:', output['statusName'])\n  print('Solutions found :', output['numSolutions'])\n  print('Unique solutions: ', output['numUniqueSolutions'])\n\n  if output_json:\n    return json.dumps(output)        \n  else:\n    return consumed_big_rolls\n\n\n'''\nDraws the big rolls on the graph. Each horizontal colored line represents one big roll.\nIn each big roll (multi-colored horizontal line), each color represents small roll to be cut from it.\nIf the big roll ends with a black color, that part of the big roll is unused width.\n\nTODO: Assign each child roll a unique color\n'''\ndef drawGraph(consumed_big_rolls, child_rolls, parent_width):\n    import matplotlib.pyplot as plt\n    import matplotlib.patches as patches\n\n    # TODO: to add support for multiple different parent rolls, update here\n    xSize = parent_width # width of big roll\n    ySize = 10 * len(consumed_big_rolls) # one big roll will take 10 units vertical space\n\n    # draw rectangle\n    fig,ax = plt.subplots(1)\n    plt.xlim(0, xSize)\n    plt.ylim(0, ySize)\n    plt.gca().set_aspect('equal', adjustable='box')\n    \n    # print coords\n    coords = []\n    colors = ['r', 'g', 'b', 'y', 'brown', 'violet', 'pink', 'gray', 'orange', 'b', 'y']\n    colorDict = {}\n    i = 0\n    for quantity, width in child_rolls:\n      colorDict[width] = colors[i % 11]\n      i+= 1\n\n    # start plotting each big roll horizontly, from the bottom\n    y1 = 0\n    for i, big_roll in enumerate(consumed_big_rolls):\n      '''\n        big_roll = [leftover_width, [small_roll_1_1, small_roll_1_2, other_small_roll_2_1]]\n      '''\n      unused_width = big_roll[0]\n      small_rolls = big_roll[1]\n\n      x1 = 0\n      x2 = 0\n      y2 = y1 + 8 # the height of each big roll will be 8 \n      for j, small_roll in enumerate(small_rolls):\n        x2 = x2 + small_roll\n        print(f\"{x1}, {y1} -> {x2}, {y2}\")\n        width = abs(x1-x2)\n        height = abs(y1-y2)\n        # print(f\"Rect#{idx}: {width}x{height}\")\n        # Create a Rectangle patch\n        rect_shape = patches.Rectangle((x1,y1), width, height, facecolor=colorDict[small_roll], label=f'{small_roll}')\n        ax.add_patch(rect_shape) # Add the patch to the Axes\n        x1 = x2 # x1 for next small roll in same big roll will be x2 of current roll \n\n      # now that all small rolls have been plotted, check if a there is unused width in this big roll\n      # set the unused width at the end as black colored rectangle\n      if unused_width > 0:\n        width = unused_width\n        rect_shape = patches.Rectangle((x1,y1), width, height, facecolor='black', label='Unused')\n        ax.add_patch(rect_shape) # Add the patch to the Axes\n\n      y1 += 10 # next big roll will be plotted on top of current, a roll height is 8, so 2 will be margin between rolls\n\n    plt.show()\n\n\nif __name__ == '__main__':\n\n  # child_rolls = [\n  #    [quantity, width],\n  # ]\n  app = typer.Typer()\n\n\n  def main(infile_name: Optional[str] = typer.Argument(None)):\n\n    if infile_name:\n      child_rolls = get_data(infile_name)\n    else:\n      child_rolls = gen_data(3)\n    parent_rolls = [[10, 120]] # 10 doesn't matter, itls not used at the moment\n\n    consumed_big_rolls = StockCutter1D(child_rolls, parent_rolls, output_json=False, large_model=False)\n    typer.echo(f\"{consumed_big_rolls}\")\n\n\n    for idx, roll in enumerate(consumed_big_rolls):\n      typer.echo(f\"Roll #{idx}:{roll}\")\n\n    drawGraph(consumed_big_rolls, child_rolls, parent_width=parent_rolls[0][1])\n\nif __name__ == \"__main__\":\n  typer.run(main)\n"
  },
  {
    "path": "deployment/.editorconfig",
    "content": "# Editor configuration, see http://editorconfig.org\n# source: https://stackoverflow.com/a/51398290/3578289\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_line_length = off\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": "deployment/.gitignore",
    "content": "node_modules/\n__pycache__\nserver/__pycache__/\n*.csv\n\n.DS_Store\nfrontend/dist/\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw*\n"
  },
  {
    "path": "deployment/Procfile",
    "content": "web: gunicorn server:app"
  },
  {
    "path": "deployment/csp.py",
    "content": "from __future__ import print_function\nimport collections, json\nfrom ortools.sat.python import cp_model\n\n# to draw rectangles\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\n\n\"\"\"\n    Cutting Stock problem\n    params\n        child_rects: \n            lists of multiple rectangles' coords\n            e.g.: [ [w, h], [w, h], ...]\n        parent_rects: rectangle coords\n            lists of multiple rectangles' coords\n            e.g.: [ [w, h], [w, h], ...]\n\"\"\"\ndef StockCutter(child_rects, parent_rects):\n    \n    # Create the model\n    model = cp_model.CpModel()\n\n    # parent rect (to cut from). horizon = [ width, height ] of parent sheet\n    # for now, parent rectangle is just one\n    # TODO: to add functionality of cutting from multiple parent sheets, start here:\n    horizon = parent_rects[0] \n\n    # Named Tuple to store information about created variables\n    sheet_type = collections.namedtuple('sheet_type', 'x1 y1 x2 y2 x_interval y_interval')\n\n    # Store for all model variables\n    all_vars = {}\n\n    # sum of to save area of all small rects, to cut from parent rect\n    total_child_area = 0 \n\n    # hold the widths (x) and heights (y) interval vars of each sheet\n    x_intervals = []\n    y_intervals = []\n\n    # create model vars and intervals\n    for rect_id, rect in enumerate(child_rects):\n        width = rect[0]\n        height = rect[1]\n        area = width * height\n        total_child_area += area\n        # print(f\"Rect: {width}x{height}, Area: {area}\")\n\n        suffix = '_%i_%i' % (width, height)\n\n        # interval to represent width. max value can be the width of parent rect\n        x1_var = model.NewIntVar(0, horizon[0], 'x1' + suffix)\n        x2_var = model.NewIntVar(0, horizon[0], 'x2' + suffix)\n        x_interval_var = model.NewIntervalVar(x1_var, width, x2_var, 'x_interval' + suffix)\n\n        # interval to represent height. max value can be the height of parent rect\n        y1_var = model.NewIntVar(0, horizon[1], 'y1' + suffix)\n        y2_var = model.NewIntVar(0, horizon[1], 'y2' + suffix)\n        y_interval_var = model.NewIntervalVar(y1_var, height, y2_var, 'y_interval' + suffix)\n        \n        x_intervals.append(x_interval_var)\n        y_intervals.append(y_interval_var)\n\n        # store the variables for later use\n        all_vars[rect_id] = sheet_type(\n            x1=x1_var, \n            y1=y1_var, \n            x2=x2_var, \n            y2=y2_var, \n            x_interval=x_interval_var,\n            y_interval=y_interval_var\n        )\n\n    # add constraint: no over lap of rectangles allowed\n    model.AddNoOverlap2D(x_intervals, y_intervals)\n\n    # Solve model\n    solver = cp_model.CpSolver()\n\n    solution_printer = VarArraySolutionPrinter(all_vars)\n\n    # Search for all solutions is only defined on satisfiability problems\n    status = solver.SearchForAllSolutions(model, solution_printer) # use for satisfiability problem\n    # status = solver.Solve(model) # use for Optimization Problem\n\n    print('Status:', solver.StatusName(status))\n    print('Solutions found :', solution_printer.solution_count())\n\n    solutions = solution_printer.get_unique_solutions()    \n\n    # call draw methods here, if want to draw with matplotlib\n\n    int_solutions = solutions_to_int(solutions)\n\n    statusName = solver.StatusName(status)\n    numSolutions = solution_printer.solution_count()\n    numUniqueSolutions = len(solutions)\n\n    output = {\n        \"statusName\": statusName,\n        \"numSolutions\": numSolutions,\n        \"numUniqueSolutions\": numUniqueSolutions,\n        \"solutions\": int_solutions # unique solutions\n    }\n\n    # return json.dumps(output)\n\n    # draw\n    for idx, sol in enumerate(solutions):\n        # sol is string of coordinates of all rectangles in this solution\n        # format: x1,y1,x2,y2-x1,y1,x2,y2\n        print('Sol#', idx)\n        rect_strs = sol.split('-')\n        rect_coords = [\n            #    [x1,y1,x2,y2],\n            #    [x1,y1,x2,y2],\n        ]\n        for rect_str in rect_strs:\n            coords_str = rect_str.split(',')\n            coords = [int(c) for c in coords_str]\n            rect_coords.append(coords)\n        print('rect_coords')\n        # print(rect_coords)\n        drawRectsFromCoords(rect_coords)\n\ndef drawRectsFromCoords(rect_coords):\n    # draw rectangle\n    fig,ax = plt.subplots(1)\n    plt.xlim(0,6) # todo 7\n    plt.ylim(0,6)\n    plt.gca().set_aspect('equal', adjustable='box')\n    \n    # print coords\n    coords = []\n    colors = ['r', 'g', 'b', 'y', 'brown', 'black', 'violet', 'pink', 'gray', 'orange', 'b', 'y']\n    for idx, coords in enumerate(rect_coords):\n        x1=coords[0]\n        y1=coords[1]\n        x2=coords[2]\n        y2=coords[3]\n        # print(f\"{x1}, {y1} -> {x2}, {y2}\")\n\n        width = abs(x1-x2)\n        height = abs(y1-y2)\n        # print(f\"Rect#{idx}: {width}x{height}\")\n\n        # Create a Rectangle patch\n        rect_shape = patches.Rectangle((x1,y1), width, height,facecolor=colors[idx])\n        # Add the patch to the Axes\n        ax.add_patch(rect_shape)\n    plt.show()\n\n\n\n\n\n\"\"\"\n    TODO complete this, add to git\n    params:\n        str_solutions: list of strings. 1 string contains is solution\n\"\"\"\ndef solutions_to_int(str_solutions):\n\n    # list of solutions, each solution is a list of rectangle coords that look like [x1,y1,x2,y2]\n    int_solutions = []\n\n    # go over all solutions and convert them to int>list>json\n    for idx, sol in enumerate(str_solutions):\n        # sol is string of coordinates of all rectangles in this solution\n        # format: x1,y1,x2,y2-x1,y1,x2,y2\n    \n        rect_strs = sol.split('-')\n        rect_coords = [\n            # [x1,y1,x2,y2],\n            # [x1,y1,x2,y2],\n            # ...\n        ]\n\n        # convert each rectangle's coords to int\n        for rect_str in rect_strs:\n            coords_str = rect_str.split(',')\n            coords = [int(c) for c in coords_str]\n            rect_coords.append(coords)\n\n        # print('rect_coords', rect_coords)\n\n        # call draw methods here, if want to draw individual solutions with matplotlib\n\n        int_solutions.append(rect_coords)\n\n    return int_solutions\n\n\n\"\"\"\n    To get all the solutions of the problem, as they come up. \n    https://developers.google.com/optimization/cp/cp_solver#all_solutions\n\n    The solutions are all unique. But for the child rectangles that have same dimensions, \n    some solution will be repetitive. Because for the algorithm, they are different solutions, \n    but because of same size, they are merely permutations of the similar child rectangles - \n    having other rectangles' positions fixed.\n\n    We want to remove repetitive extra solutions. One way to do this is\n        1. Stringify every rectangle coords in a solution\n            (1,2)->(2,3) becomes \"1,2,2,3\"\n\n            # here the rectangles are stored as a string: \"1,2,2,3\" where x1=1, y1=2, x2=2, y2=3\n        \n        2. Put all these string coords into a sorted list. This sorting is important. \n            Because the rectangles (1,2)->(2,3) and (3,3)->(4,4) are actually same size (1x1) rectangles. \n            And they can appear in 1st solution as \n            [(1,2)->(2,3)   ,   (3,3)->(4,4)]\n            and in the 2nd solution as\n            [(3,3)->(4,4)   ,   (1,2)->(2,3)]\n\n            but this sorted list of strings will ensure both solutions are represented as\n\n            [..., \"1,2,2,3\", \"3,3,4,4\", ...]\n\n        3. Join the Set of \"strings (rectangles)\" in to one big string seperated by '-'. For every solution.\n            So in resulting big strings (solutions), we will have two similar strings (solutions) \n            that be similar and also contain\n\n            \"....1,2,2,3-3,3,4,4-....\"\n\n        4. Now add all these \"strings (solutions)\" into a Set. this adding to the set \n        will remove similar strings. And hence duplicate solutions will be removed.\n\n\"\"\"\nclass VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n\n    def __init__(self, variables):\n        cp_model.CpSolverSolutionCallback.__init__(self)\n        self.__variables = variables\n        self.__solution_count = 0\n\n        # hold the calculated solutions\n        self.__solutions = []\n        self.__unique_solutions = set()\n\n    def on_solution_callback(self):\n        self.__solution_count += 1\n        # print('Sol#: ', self.__solution_count)\n\n        # using list to hold the coordinate strings of rectangles\n        rect_strs = []\n\n        # extra coordinates of all rectangles for this solution \n        for rect_id in self.__variables:\n            rect = self.__variables[rect_id]\n            x1 = self.Value(rect.x1)\n            x2 = self.Value(rect.x2)\n            y1 = self.Value(rect.y1)\n            y2 = self.Value(rect.y2)\n\n            rect_str = f\"{x1},{y1},{x2},{y2}\"\n            # print(rect_str)\n\n            rect_strs.append(rect_str)\n            # print(f'Rect #{rect_id}: {x1},{y1} -> {x2},{y2}')\n\n        # print(rect_strs)\n\n        # sort the rectangles\n        rect_strs = sorted(rect_strs)\n\n        # single solution as a string\n        solution_str = '-'.join(rect_strs)\n        # print(solution_str)\n\n        # store the solutions\n        self.__solutions.append(solution_str)\n        self.__unique_solutions.add(solution_str) # __unique_solutions is a set, so duplicates will get removed\n\n\n    def solution_count(self):\n        return self.__solution_count\n\n    # returns all solutions  \n    def get_solutions(self):\n        return self.__solutions\n\n    \"\"\"\n    returns unique solutions\n    returns the permutation free list of solution strings  \n    \"\"\"\n    def get_unique_solutions(self):\n        return list(self.__unique_solutions) # __unique_solutions is a Set, convert to list\n\n\n\n# for testing\nif __name__ == '__main__':\n\n    child_rects = [\n        # [2, 2],\n        # [1, 3],\n        # [4, 3],\n        # [1, 1],\n        # [2, 4],\n\n        [3, 3],\n        [3, 3],\n        [3, 3],\n        [3, 3],\n    ]\n\n    parent_rects = [[6,6]]\n\n    StockCutter(child_rects, parent_rects)"
  },
  {
    "path": "deployment/frontend/.gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "deployment/frontend/README.md",
    "content": "# frontend\n\n## Project setup\n```\nnpm install\n```\n\n### Compiles and hot-reloads for development\n```\nnpm run serve\n```\n\n### Compiles and minifies for production\n```\nnpm run build\n```\n\n### Lints and fixes files\n```\nnpm run lint\n```\n\n### Customize configuration\nSee [Configuration Reference](https://cli.vuejs.org/config/).\n"
  },
  {
    "path": "deployment/frontend/babel.config.js",
    "content": "module.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset'\n  ]\n}\n"
  },
  {
    "path": "deployment/frontend/package.json",
    "content": "{\n  \"name\": \"frontend\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\",\n    \"lint\": \"vue-cli-service lint\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^0.21.1\",\n    \"bootstrap\": \"^5.1.0\",\n    \"bootstrap-vue\": \"^2.21.2\",\n    \"core-js\": \"^3.6.5\",\n    \"d3\": \"^7.0.1\",\n    \"vue\": \"^3.2.6\"\n  },\n  \"devDependencies\": {\n    \"@types/d3\": \"^7.0.0\",\n    \"@vue/cli-plugin-babel\": \"~4.5.0\",\n    \"@vue/cli-plugin-eslint\": \"~4.5.0\",\n    \"@vue/cli-service\": \"~4.5.0\",\n    \"@vue/compiler-sfc\": \"^3.0.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"eslint\": \"^6.7.2\",\n    \"eslint-plugin-vue\": \"^7.0.0\"\n  },\n  \"eslintConfig\": {\n    \"root\": true,\n    \"env\": {\n      \"node\": true\n    },\n    \"extends\": [\n      \"plugin:vue/vue3-essential\",\n      \"eslint:recommended\"\n    ],\n    \"parserOptions\": {\n      \"parser\": \"babel-eslint\"\n    },\n    \"rules\": {}\n  },\n  \"browserslist\": [\n    \"> 1%\",\n    \"last 2 versions\",\n    \"not dead\"\n  ]\n}\n"
  },
  {
    "path": "deployment/frontend/public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n    <link rel=\"icon\" href=\"<%= BASE_URL %>favicon.ico\">\n    <title><%= htmlWebpackPlugin.options.title %></title>\n  </head>\n  <body>\n    <noscript>\n      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>\n    </noscript>\n    <div id=\"app\"></div>\n    <!-- built files will be auto injected -->\n  </body>\n</html>\n"
  },
  {
    "path": "deployment/frontend/src/App.vue",
    "content": "<template>\n  <CspTool msg=\"Let's Go\"/>\n</template>\n\n<script>\nimport CspTool from './components/CspTool.vue'\n\nexport default {\n  name: 'App',\n  components: {\n    CspTool\n  }\n}\n</script>\n\n<style>\n#app {\n  font-family: Avenir, Helvetica, Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  text-align: center;\n  color: #2c3e50;\n  margin-top: 60px;\n}\n</style>\n"
  },
  {
    "path": "deployment/frontend/src/assets/style.scss",
    "content": "\n\nbody {\n  /* font-size: 0.9em; */\n  background-color: #ecf0f1;\n}\n\n.btn {\n  padding: 2px 8px;\n}\n\ninput {\n  border: 0px solid #000;\n  margin: 0;\n  background: transparent;\n  width: 100%;\n}\ntable tr td {\n  border-right: 1px solid #000;\n  border-bottom: 1px solid #000;\n}\ntable {\n  background: #fff none repeat scroll 0 0;\n  border-left: 1px solid #000;\n  border-top: 1px solid #000;\n}\ntable tr:nth-child(even) {\n  background: #95a5a6;\n}\ntable tr:nth-child(odd) {\n  background: #bdc3c7;\n}\n.active {\n  font-weight: bold;\n}\n\n.information {\n  border-radius: 100%;\n  background: #444;\n  color: white;\n  padding: 2px 5px;\n  font-size: 80%;\n  font-weight: bold;\n}"
  },
  {
    "path": "deployment/frontend/src/components/CspTool.vue",
    "content": "<template>\n  <span>\n    <!-- As a heading -->\n    <nav class=\"navbar navbar-light bg-light\">\n      <div class=\"container-fluid\">\n        <span class=\"navbar-brand mb-0 h1\">Navbar</span>\n      </div>\n    </nav>\n\n    <div class=\"container text-start\">\n      <!-- header / intro section -->\n      <div class=\"row\">\n        <div class=\"col\">\n          <h1 class=\"text-center mb-0\">Stock Cuts Planner</h1>\n\n          <div class=\"row\">\n            <div class=\"col float-start\">\n              <a href=\"/cutting-stock-problem\">Learn about Cuts Planner</a>\n            </div>\n\n            <div class=\"col-8\">\n              <h5 class=\"text-center\">\n                Plan how to cut your stock in a way to minimize waste\n              </h5>\n            </div>\n\n            <div class=\"col float-end\">\n              <a\n                class=\"float-end\"\n                href=\"https://github.com/emadehsan/csp\"\n                target=\"_blank\"\n                >Code</a\n              >\n            </div>\n          </div>\n\n          <ul class=\"nav nav-tabs\">\n            <li class=\"nav-item\">\n              <!-- hide the border around button onclick, using onmousedown -->\n              <button\n                class=\"nav-link active\"\n                id=\"1d-tab\"\n                v-on:click=\"setMode('1d')\"\n              >\n                Rods, Rolls, 1-D Sheets\n              </button>\n            </li>\n\n            <li class=\"nav-item\">\n              <button class=\"nav-link\" id=\"2d-tab\" v-on:click=\"setMode('2d')\">\n                Rectangular Sheets (2-D)\n              </button>\n            </li>\n          </ul>\n        </div>\n      </div>\n\n      <div class=\"row tab-content\">\n        <!-- input sections | left -->\n        <div class=\"col-6\">\n          <!-- input small rects | left+top -->\n          <div class=\"row\">\n            <div class=\"col\">\n              <h3 class=\"mt-2\">{{ mode_data.childTitle }}</h3>\n              <p class=\"text-secondary mt-0 mb-2\">\n                {{ mode_data.childMessage }}\n              </p>\n\n              <div class=\"row\">\n                <div class=\"col\">\n                  <!-- adds a row to Sheets to Cut -->\n                  <button\n                    class=\"my-1 btn btn-outline-success btn-sm float-start\"\n                    v-on:click=\"addRowToChilds\"\n                  >\n                    <b>+ Add Another</b>\n                  </button>\n                </div>\n\n                <div class=\"col\">\n                  <button\n                    class=\"my-1 btn btn-outline-danger btn-sm float-end\"\n                    v-on:click=\"clearChildData\"\n                  >\n                    <b>x Clear All</b>\n                  </button>\n                </div>\n              </div>\n\n              <p class=\"text-danger mb-1\">{{ mode_data.childErrors }}</p>\n\n              <table cellpadding=\"0\" cellspacing=\"0\" class=\"w-100 border-0\">\n                <thead>\n                  <tr class=\"border\">\n                    <td class=\"px-1\">#</td>\n                    <td class=\"px-1\">Width</td>\n\n                    <!-- display height if mode is 2d -->\n                    <td v-if=\"mode === '2d'\" class=\"px-1\">Height</td>\n\n                    <td class=\"px-1\">Quantity</td>\n                    <!-- <td>Label</td> -->\n                    <!-- <td>Color</td> -->\n                    <td\n                      v-if=\"mode === '1d' && mode_data.result\"\n                      class=\"px-3\"\n                    ></td>\n                    <td class=\"px-1 border-0\"></td>\n                  </tr>\n                </thead>\n                <tbody>\n                  <tr\n                    class=\"border\"\n                    v-for=\"(child, index) in mode_data.childs\"\n                    v-bind:key=\"index\"\n                  >\n                    <td class=\"px-1 text-secondary\">\n                      {{ index + 1 }}\n                    </td>\n                    <td>\n                      <input class=\"px-1\" type=\"text\" v-model=\"child.width\" />\n                    </td>\n\n                    <td v-if=\"mode === '2d'\">\n                      <input class=\"px-1\" type=\"text\" v-model=\"child.height\" />\n                    </td>\n\n                    <td>\n                      <input\n                        class=\"px-1\"\n                        type=\"text\"\n                        v-model=\"child.quantity\"\n                      />\n                    </td>\n\n                    <td\n                      v-if=\"mode === '1d' && mode_data.result\"\n                      class=\"px-1\"\n                      :style=\"getColor(child.width)\"\n                    ></td>\n\n                    <td class=\"px-1 border-0\">\n                      <div\n                        v-on:click=\"removeRow(index, false)\"\n                        class=\"btn btn-outline-danger btn-sm m-0 py-0 px-1\"\n                      >\n                        x\n                      </div>\n                    </td>\n                  </tr>\n                </tbody>\n              </table>\n            </div>\n          </div>\n\n          <hr class=\"mb-2\" />\n\n          <!-- input sheets | left+bottom -->\n          <div class=\"row\">\n            <div class=\"col\">\n              <h3 class=\"my-0\">{{ mode_data.parentTitle }}</h3>\n              <p class=\"text-secondary mb-2\">{{ mode_data.parentMessage }}</p>\n\n              <div class=\"row\">\n                <!-- <div class=\"col\">\n                                <button\n                                    class=\"my-1 btn btn-outline-success btn-sm\"\n                                    v-on:click=\"addRowToParents\">+ Add Another\n                                </button>\n                            </div> -->\n\n                <div class=\"col\">\n                  <button\n                    class=\"my-1 btn btn-outline-danger btn-sm float-end\"\n                    v-on:click=\"clearParentData\"\n                  >\n                    <b>x Clear All</b>\n                  </button>\n                </div>\n              </div>\n\n              <p class=\"text-danger mb-1\">{{ mode_data.parentErrors }}</p>\n\n              <table cellpadding=\"0\" cellspacing=\"0\" class=\"w-100 border-0\">\n                <thead>\n                  <tr class=\"border\">\n                    <td class=\"px-1\">#</td>\n                    <td class=\"px-1\">Width</td>\n\n                    <td v-if=\"mode === '2d'\" class=\"px-1\">Height</td>\n\n                    <td class=\"px-1\">Quantity</td>\n\n                    <td class=\"px-1 border-0\"></td>\n                  </tr>\n                </thead>\n                <tbody>\n                  <tr\n                    class=\"border\"\n                    v-for=\"(parent, index) in mode_data.parents\"\n                    v-bind:key=\"index\"\n                  >\n                    <td class=\"px-1 text-secondary\">\n                      {{ index + 1 }}\n                    </td>\n                    <td>\n                      <input class=\"px-1\" type=\"text\" v-model=\"parent.width\" />\n                    </td>\n\n                    <td v-if=\"mode === '2d'\">\n                      <input class=\"px-1\" type=\"text\" v-model=\"parent.height\" />\n                    </td>\n\n                    <td>\n                      <input\n                        disabled=\"true\"\n                        class=\"px-1\"\n                        type=\"text\"\n                        v-model=\"parent.quantity\"\n                      />\n                    </td>\n\n                    <td class=\"px-1 border-0\">\n                      <div\n                        v-on:click=\"removeRow(index, true)\"\n                        class=\"btn btn-outline-danger btn-sm m-0 py-0 px-1\"\n                      >\n                        x\n                      </div>\n                    </td>\n                  </tr>\n                </tbody>\n              </table>\n            </div>\n          </div>\n\n          <!-- only one sheet allowed for now. so keep add more button hidden -->\n          <!--\n                <div class=\"row\">\n                    <div class=\"col\">\n                        <button class=\"mt-1\" v-on:click=\"addRowToParents\"><b>+ Add</b></button>\n                    </div>\n                </div>\n                 -->\n        </div>\n\n        <!-- output+diagram section | right -->\n        <div class=\"col-6\">\n          <div class=\"row mt-1\">\n            <div class=\"row form-check\">\n              <div class=\"col\">\n                <input\n                  type=\"radio\"\n                  id=\"exactCutsRadio\"\n                  name=\"exactCutsRadio\"\n                  class=\"form-check-input\"\n                  value=\"exactCuts\"\n                  v-model=\"cutStyle\"\n                />\n\n                <label class=\"custom-control-label\" for=\"exactCutsRadio\">\n                  Exact Cuts\n\n                  <span\n                    class=\"information\"\n                    data-toggle=\"tooltip\"\n                    data-placement=\"top\"\n                    title=\"Cut the exact number of  items from stock, as specified\"\n                    >?</span\n                  >\n                </label>\n              </div>\n\n              <div class=\"col\">\n                <input\n                  type=\"radio\"\n                  id=\"minWasteRadio\"\n                  name=\"minWasteRadio\"\n                  class=\"form-check-input\"\n                  value=\"minWaste\"\n                  v-model=\"cutStyle\"\n                />\n\n                <label class=\"custom-control-label\" for=\"minWasteRadio\">\n                  Minimize Waste\n\n                  <span\n                    class=\"information\"\n                    data-toggle=\"tooltip\"\n                    data-placement=\"top\"\n                    title=\"Cut some extra items than specified, to avoid wastage of leftover\"\n                    >?</span\n                  >\n                </label>\n              </div>\n            </div>\n          </div>\n\n          <div class=\"row\">\n            <div class=\"col\">\n              <button\n                :disabled=\"cutButtonDisabled\"\n                class=\"my-2 btn btn-primary btn-sm\"\n                @click=\"cutSheets()\"\n              >\n                <b>✓ Cut</b>\n              </button>\n            </div>\n\n            <div class=\"col\">\n              <button\n                :disabled=\"cutButtonDisabled\"\n                class=\"my-2 btn btn-outline-danger btn-sm float-end\"\n                @click=\"reset()\"\n              >\n                <b>x Reset</b>\n              </button>\n            </div>\n          </div>\n\n          <h4 v-if=\"mode_data.result\" class=\"text-capitalize\">\n            Solution: {{ mode_data.result.statusName }}\n          </h4>\n\n          <div id=\"d3_area\">\n            <svg class=\"w-100\"></svg>\n          </div>\n\n          <span v-if=\"mode_data.result\">\n            <!-- <p>Total Solutions: {{ mode_data.result.numSolutions }}</p> -->\n            <!-- <p>Helpful Solutions: {{ mode_data.result.numUniqueSolutions }}</p> -->\n\n            <div v-if=\"mode === '1d'\" class=\"row my-2\">\n              <div class=\"col\">\n                <h4>Cut Details</h4>\n\n                <div class=\"row\">\n                  <div class=\"col\">\n                    <p class=\"m-0\">\n                      Stock required = {{ mode_data.result.solutions.length }}\n                    </p>\n                  </div>\n\n                  <div class=\"col\">\n                    <button\n                      v-on:click=\"downloadCsv()\"\n                      class=\"p-0 btn btn-link float-end\"\n                    >\n                      Download CSV\n                    </button>\n                  </div>\n                </div>\n\n                <table cellpadding=\"0\" cellspacing=\"0\" class=\"w-100 border-0\">\n                  <thead>\n                    <tr class=\"border\">\n                      <td class=\"px-1\">Stock</td>\n                      <td class=\"px-1\">Usage</td>\n                      <!-- <td class=\"px-1\">Unused Width</td> -->\n                      <td class=\"px-1\">Width of Cuts</td>\n                    </tr>\n                  </thead>\n                  <tbody>\n                    <tr\n                      class=\"border\"\n                      v-for=\"(bigRoll, index) in mode_data.result.solutions\"\n                      v-bind:key=\"index\"\n                    >\n                      <td class=\"px-1 text-secondary\">\n                        {{ index + 1 }}\n                      </td>\n\n                      <td class=\"px-1\">\n                        {{ getPercentageUtilization(bigRoll[0]) }} %\n                      </td>\n\n                      <!-- \t\t\t\t\t\t\t\t\t\t<td class=\"px-1\">\n                                            {{ bigRoll[0] }}\n                                        </td>\n\n -->\n                      <td class=\"px-1\">\n                        <!-- join without space, so paste in excel would not change it to date -->\n                        {{ bigRoll[1].join(\",\") }}\n                      </td>\n                    </tr>\n                  </tbody>\n                </table>\n\n                <!--\n                            <div class=\"row\">\n                                <h6 class=\"col-3\">Stock</h6>\n                                <h6 class=\"col-6\">Cut Widths</h6>\n                                <h6 class=\"col-3\">Leftover</h6>\n                            </div>\n                            <div class=\"row\" v-for=\"(bigRoll, index) in mode_data.result.solutions\">\n                                <div class=\"col-3\">\n                                    {{ index + 1}}\n                                </div>\n\n                                <div class=\"col-6\">\n                                    <span>\n                                        {{ bigRoll[1].join(\",   \") }}\n                                    </span>\n                                </div>\n\n                                <div class=\"col-3\">\n                                    {{bigRoll[0]}}\n                                </div>\n                            </div> -->\n              </div>\n            </div>\n\n            <!-- dimentions of rects of solutions -->\n            <div v-if=\"mode === '2d'\">\n              <div\n                v-for=\"(sol, index) in mode_data.result.solutions\"\n                v-bind:key=\"index\"\n              >\n                <div v-for=\"(rect, index) in sol\" v-bind:key=\"index\">\n                  {{ rect[0] }},{{ rect[1] }}-{{ rect[2] }},{{ rect[3] }}\n                </div>\n                <hr />\n              </div>\n            </div>\n          </span>\n        </div>\n      </div>\n\n      <hr />\n\n      <footer class=\"footer mb-3\">© {{ getCurrentYear() }}</footer>\n    </div>\n  </span>\n</template>\n\n<script>\nimport * as d3 from \"d3\";\nimport * as axios from \"axios\";\n\nexport default {\n  name: \"CspTool\",\n  props: {\n    msg: String,\n  },\n  data: function () {\n    return {\n      // by default, 1d mode will open. But onclick 1d / 2d buttons, mode can be switched\n      mode: \"1d\",\n\n      message: \"hello\",\n\n      /*\n        it specifies how to cut?\n        1. exactCuts: cut exact number of sheets as specified by user\n        2. minWaste: cut more than specified number of times, the items specified. to minimize waste\n        */\n      cutStyle: \"exactCuts\",\n\n      // remembers the state of Cut button\n      cutButtonDisabled: false,\n\n      mode1d: {\n        childs: [\n          { width: \"\", quantity: \"\" }, // 1d mode doesn't have height\n        ],\n        parents: [{ width: \"\", quantity: \"Comming soon\" }],\n        childErrors: null,\n        parentErrors: null,\n        // data from server, to display\n        result: null,\n\n        // information displayed on each mode\n        childTitle: \"Small Sheets / Rolls\",\n        childMessage:\n          \"Details of small rods, rolls or sheets to cut from respective stock\",\n        parentTitle: \"Stock\",\n        parentMessage:\n          \"The 1-D algorithm tells the number of stock sheets required\",\n      },\n\n      mode2d: {\n        childs: [{ width: \"\", height: \"\", quantity: \"\" }],\n        parents: [{ width: \"\", height: \"\", quantity: \"Comming soon\" }],\n        childErrors: null,\n        parentErrors: null,\n        // data from server, to display\n        result: null,\n\n        // information displayed on each mode\n        childTitle: \"Small Sheets\",\n        childMessage:\n          \"Details of small rectangular (▯) sheets to cut from big ▯ sheet\",\n        parentTitle: \"Stock Sheets\",\n        parentMessage: \"Only 1 stock sheet is allowed for now\",\n      },\n\n      // currently displayed mode's data (mode1d or mode2d from above)\n      mode_data: null,\n\n      // from https://flatuicolors.com/palette/defo\n      colors: [\n        \"#1abc9c\", // Torquise\n        \"#16a085\", // Green Sea\n        \"#f1c40f\", // Sun Flower\n        \"#f39c12\", // Orange\n        \"#2ecc71\", // Emerald\n        \"#27ae60\", // Nephritis\n        \"#e67e22\", // Carrot\n        \"#d35400\", // Pumpkin\n        \"#3498db\", // Peter River\n        \"#2980b9\", // Belize Hole\n        \"#e74c3c\", // Alizarin\n        \"#c0392b\", // Pomegranate\n        \"#9b59b6\", // Amethyst\n        \"#8e44ad\", // Wisteria\n        \"#ecf0f1\", // Clouds\n        // '#bdc3c7', // Silver\n        // '#95a5a6', // Concrete <- Clouds & Silver are close\n        // '#34495e', // West Asphalt <- don't use because it is very close to Midnight blue\n        // '#2c3e50', // Midnight Blue <- use for wasted part\n      ],\n\n      wasteColor: \"#7f8c8d\", // Asbestos\n    };\n  },\n\n  beforeMount() {\n    // console.log('#beforeMount');\n\n    // set mode before mount\n    this.setMode(\"1d\");\n  },\n\n  mounted() {\n    this.hideButtonClickBorder();\n  },\n\n  methods: {\n    /**\n     * bootstrap buttons stay highlighted with a border after a click\n     * this method hides that\n     */\n    hideButtonClickBorder: function () {\n      let buttons = document.getElementsByTagName(\"button\");\n      for (let i = 0; i < buttons.length; i++) {\n        buttons[i].addEventListener(\"mousedown\", function (event) {\n          return event.preventDefault();\n        });\n      }\n    },\n\n    /**\n     * this method is called when the mode is switched between 1d and 2d\n     * this method saves the data of currently displayed mode and displays the data\n     * that was previously entered in the other mode because that mode just become the current.\n     *\n     * it also draws the results of that mode, if they were retrieved\n     */\n    setMode: function (newMode) {\n      this.mode = newMode;\n\n      if (newMode === \"1d\") {\n        // hide 2d mode's options\n        if (this.mode_data != null) this.mode2d = this.mode_data;\n\n        this.mode_data = this.mode1d;\n\n        // make 1D tab active\n        this.removeActiveClass(\"2d-tab\");\n        this.addActiveClass(\"1d-tab\");\n\n        // if there is anything to draw, draw it\n        this.draw1d();\n      } else if (newMode === \"2d\") {\n        // hide 1d mode's options\n        if (this.mode_data != null) this.mode1d = this.mode_data;\n\n        this.mode_data = this.mode2d;\n\n        this.removeActiveClass(\"1d-tab\");\n        this.addActiveClass(\"2d-tab\");\n\n        this.draw2d();\n      }\n\n      console.log(\"Mode: \", this.mode);\n      console.log(\"Mode_data: \", this.mode_data);\n    },\n\n    /**\n        add active Bootstrap class to make the Tab look as selected\n        */\n    addActiveClass: function (id) {\n      let element = document.getElementById(id);\n      if (!element) return;\n\n      element.classList.add(\"active\");\n    },\n\n    removeActiveClass: function (id) {\n      let element = document.getElementById(id);\n      if (!element) return;\n\n      element.classList.remove(\"active\");\n    },\n\n    /**\n     * checks if the number entered at current row & column is a valid digit\n     * since input field has type text (for styling reason), this function enforces\n     * that input is positive number\n     */\n    validNum: function (row, key, is_parent) {\n      // is this from parent table?\n      let item = null;\n      if (is_parent) item = this.mode_data.parents[row][key];\n      else item = this.mode_data.childs[row][key];\n\n      let validChars = \"\";\n      for (let i = 0; i < item.length; i++) {\n        const c = item[i];\n\n        if (\"0123456789\".includes(c)) {\n          // c is a digit? in string type\n          validChars += c;\n        }\n      }\n\n      const num = validChars === \"\" ? 0 : parseInt(validChars);\n\n      if (is_parent) this.mode_data.parents[row][key] = num;\n      else this.mode_data.childs[row][key] = num;\n    },\n\n    addRowToChilds: function () {\n      this.mode_data.childs.push([\"\", \"\", \"\"]); // add an empty row\n    },\n\n    addRowToParents: function () {\n      this.mode_data.parents.push([\"\", \"\", \"\"]); // add an empty row\n    },\n\n    hideResult: function () {\n      this.mode_data.result = null;\n    },\n\n    /**\n     * called when the Cut button is pressed.\n     * Validates the inputs, show errors\n     * Send request to the server if input is valid\n     */\n    cutSheets: function () {\n      console.log(`Cut Style: ${this.cutStyle}`);\n\n      // hide the result from previous request\n      this.hideResult();\n      // clear the drawing\n      this.clearTheDrawing();\n\n      // TODO: disable the cut button & remember which mode the button is disabled for\n\n      const isValid = this.validate();\n\n      if (!isValid) {\n        console.log(\"NOT Valid\");\n        return;\n      }\n\n      console.log(\"request is valid\");\n\n      // TODO send to server\n      this.sendReq();\n    },\n\n    /**\n     * Hides the previous error msgs if any,\n     * validates childs and parents array\n     * in parents array, only validates Width and Height\n     */\n    validate: function () {\n      // empties the previous errors\n      this.hideErrorMsgs();\n\n      /*\n            all the rows in childs must contain numbers > 0\n            */\n      const labels =\n        this.mode === \"2d\"\n          ? [\"width\", \"height\", \"quantity\"] // for 2d mode, height is required\n          : [\"width\", \"quantity\"]; // for 1d mode, height is not required\n\n      // console.log('Labels:', labels)\n\n      // compute child errors\n      for (let i = 0; i < this.mode_data.childs.length; i++) {\n        const child = this.mode_data.childs[i];\n\n        console.log(\"Validating: \", child);\n\n        for (let j = 0; j < labels.length; j++) {\n          let val = child[labels[j]]; /// width, height, quantity\n          val = parseInt(val);\n\n          if (!Number.isInteger(val) || val < 1) {\n            this.mode_data.childErrors = `> Row #${i + 1}: \"${\n              labels[j]\n            }\" must be 1 units or more\\n`;\n            // return on first error. To only show the one (first) error at a time\n            return false;\n          }\n        }\n\n        // go over width, height, quantity of each row\n        // for (let j = 0; j < row.length; j++)\n        // \tif (!Number.isInteger(row[j]) || row[j] < 1) {\n        // \t\tthis.childErrors = `> ${labels[j]} in Row #${i+1} must be 1 units or more\\n`;\n        // \t\t// return on first error. To only show the one (first) error at a time\n        // \t\treturn false;\n        // \t}\n      }\n\n      // compute parent errors\n      for (let i = 0; i < this.mode_data.parents.length; i++) {\n        const parent = this.mode_data.parents[i];\n\n        // go over width, height, quantity of each row\n        // [FOR NOW: don't go over quantity. Because we don't support quantity for parent sheets]\n        for (let j = 0; j < labels.length - 1; j++) {\n          let val = parent[labels[j]]; // value is currently a string\n          val = parseInt(val);\n          if (!Number.isInteger(val) || val < 1) {\n            this.mode_data.parentErrors = `> Row #${i + 1}: \"${\n              labels[j]\n            }\" must be 1 units or more\\n`;\n\n            console.log(\"Valdiation Error: \", this.mode_data.parentErrors);\n            console.log(\"Attr name: \", labels[j]);\n            console.log(\"Value: \", val);\n            console.log(\"Row #: \", j);\n            // return on first error. To only show the one (first) error at a time\n            return false;\n          }\n        }\n      }\n\n      return !this.mode_data.childErrors && !this.mode_data.parentErrors;\n    },\n\n    hideErrorMsgs: function () {\n      this.mode_data.childErrors = null;\n      this.mode_data.parentErrors = null;\n    },\n\n    prepareDataToSend1D: function () {\n      /*\n            For 1D algorithm the server expects data in the format:\n            child_rolls:\n                array of arrays. E.g [ [quantity, width], [quantity, width], ... ]\n\n            parent_rolls:\n                array of arrays. E.g [ [quantity, width], [quantity, width], ... ]\n\n            IMPORTANT: convert inputs from string to int\n            */\n\n      let newChilds = [];\n\n      this.mode_data.childs.forEach((child) => {\n        newChilds.push([parseInt(child.quantity), parseInt(child.width)]);\n      });\n\n      let newParents = [];\n      this.mode_data.parents.forEach((parent) => {\n        // TODO: fix from here. At the moment, parent's quantity is not really being used\n        newParents.push([parseInt(parent.quantity), parseInt(parent.width)]);\n      });\n\n      return {\n        child_rolls: newChilds,\n        parent_rolls: newParents,\n        cutStyle: this.cutStyle, // exactCuts or minWaste\n      };\n    },\n\n    sendReq: function () {\n      let url = null;\n\n      // set local url for local requests\n      //   const { href } = window.location;\n      // if (href && (href.includes('localhost') || href.includes('127.0.0.1')) ) {\n      // \turl = this.mode === '1d' ?\n      // \t\t'http://localhost:5000/stocks_1d'\n      // \t\t: 'http://localhost:5000/stocks_2d';\n\n      // \tconsole.log('Requesting from local server:', url);\n      // } else {\n      // \turl = this.mode === '1d' ?\n      // \t\t'https://chromeless.herokuapp.com/stocks_1d'\n      // \t\t: 'https://chromeless.herokuapp.com/stocks_2d';\n      // }\n\n      // TODO: uncomment above, comment below\n      url =\n        this.mode === \"1d\"\n          ? \"https://chromeless.herokuapp.com/stocks_1d\"\n          : \"https://chromeless.herokuapp.com/stocks_2d\";\n\n      this.disableCutButton(true);\n\n      // process the data as server's requirements\n      const dataToSend =\n        this.mode === \"1d\"\n          ? this.prepareDataToSend1D()\n          : this.prepareDataToSend2D();\n\n      console.log(\"dataToSend\", dataToSend);\n\n      axios\n        .post(url, dataToSend)\n        .then((response) => {\n          console.log(response);\n\n          this.disableCutButton(false);\n          this.displayResult(response);\n        })\n        .catch((error) => {\n          this.disableCutButton(false);\n          console.log(\"Network/Server error\");\n          console.error(error);\n        });\n\n      // TODO catch exceptions and show display errors\n    },\n\n    // disable / enable cut button between requests\n    disableCutButton: function (disabled) {\n      this.cutButtonDisabled = disabled;\n    },\n\n    /**\n     * called before sending a validated input to the server.\n     * Adds multiple quantity sheets as multiple sheets and removes the quantity value.\n     */\n    prepareDataToSend2D: function () {\n      /*\n            E.g.\n            currently:\n                this.mode_data.childs = [ {width: 3, height: 3, quantity: 4} ] // array of objects\n\n            after this method:\n                child_rects = [[3,3],[3,3],[3,3],[3,3]] // array of arrays\n\n            doing this on client side so server has to do less work\n\n            IMPORTANT: convert inputs from string to int\n            */\n\n      let newChilds = [];\n\n      this.mode_data.childs.forEach((child) => {\n        const quantity = child.quantity;\n\n        // const newChild = { width: child.width, height: child.height }\n        // at the moment, server's acceptable format is\n        // [ [width, height], [width, height], ...]\n        const newChild = [parseInt(child.width), parseInt(child.height)];\n        // add as many childs as there are quantities of that child\n        for (let q = 0; q < quantity; q++) newChilds.push(newChild);\n      });\n\n      let newParents = [];\n      this.mode_data.parents.forEach((parent) => {\n        // At the moment, parent's quantity is not allowed, so keep it 1\n        const quantity = 1;\n        // const quantity = parent[2] // 0: width, 1: height, 2: quantity\n\n        const newParent = [parseInt(parent.width), parseInt(parent.height)];\n        // add as many childs as there are quantities of that child\n        for (let q = 0; q < quantity; q++) newParents.push(newParent);\n      });\n\n      // the server expects data in this format\n      return { child_rects: newChilds, parent_rects: newParents };\n    },\n\n    // response: from server\n    displayResult: function (response) {\n      console.log(\"data > \", response.data);\n\n      this.mode_data.result = response.data;\n\n      if (this.mode_data.result && this.mode_data.result.statusName) {\n        // make it lower case\n        this.mode_data.result.statusName =\n          this.mode_data.result.statusName.toLowerCase();\n      }\n\n      if (this.mode === \"1d\") {\n        this.checkValidity1D();\n        this.draw1d();\n      } else this.draw2d();\n    },\n\n    /**\n        checks the validity of the output of 1D algo\n\n        IMPORTANT: convert all local inputs to integers before comparing\n        */\n    checkValidity1D: function () {\n      let isValid = true;\n\n      // input sheets\n      const childRolls = this.mode_data.childs;\n      const parentRolls = this.mode_data.parents;\n      let parentWidth = parentRolls[0].width; // at the moment, there is only one parent. TODO update here\n      parentWidth = parseInt(parentWidth);\n\n      // algorithm output from server\n      const bigRolls = this.mode_data.result.solutions;\n\n      /*\n            check 1: sum of smallRolls & unusedWidth in a bigRoll must be equal to parentWidth i.e. len of bigRoll.\n            for each bigRoll\n            */\n\n      // go over all the bigRolls and count the number of each small width\n      let outputQuantities = {};\n\n      for (let i = 0; i < bigRolls.length; i++) {\n        const unusedWidth = bigRolls[i][0];\n        const bRoll = bigRolls[i][1];\n\n        // totalWidth will contain sum of all widths in current bRoll. Must be equal to parentWidth\n        let totalWidth = Math.round(unusedWidth);\n\n        for (let j = 0; j < bRoll.length; j++) {\n          const smallRoll = bRoll[j];\n          totalWidth += smallRoll;\n\n          // count the number of rolls of each width. this is for check 2 below.\n          // if this width has already been added, update the count (quantity)\n          if (Object.prototype.hasOwnProperty.call(outputQuantities, smallRoll))\n            outputQuantities[smallRoll] += 1;\n          else outputQuantities[smallRoll] = 1;\n        }\n\n        if (totalWidth !== parentWidth) {\n          console.error(\n            `#checkValidity1D: bigRolls[${i}] totalWidth != parentWidth`,\n            totalWidth,\n            \"!=\",\n            parentWidth\n          );\n          isValid = false;\n        }\n      }\n\n      console.log(\"outputQuantities: \", outputQuantities);\n\n      /*\n            check 2: number of smallRolls of specific width must be exactly as many as the quantity specified for this width in childRolls\n\n            create a dict/object\n            {\n                3: 6, <- 3 is width, 6 is quantity\n            }\n            */\n\n      // go over all childs and count quantity of specific width\n      let inputQuantities = {};\n      for (let i = 0; i < childRolls.length; i++) {\n        let width = childRolls[i].width;\n        width = parseInt(width);\n        let quantity = childRolls[i].quantity;\n        quantity = parseInt(quantity);\n\n        // if this width has already been added, update the count\n        if (Object.prototype.hasOwnProperty.call(inputQuantities, width)) {\n          // let alreadyAddedQuantity = inputQuantities[width];\n          // inputQuantities[width] = alreadyAddedQuantity + quantity;\n\n          inputQuantities[width] += quantity;\n        } else {\n          inputQuantities[width] = quantity;\n        }\n      }\n\n      console.log(\"inputQuantities: \", inputQuantities);\n\n      const inputWidths = Object.keys(inputQuantities);\n\n      // for every width in inputQuantities, the quantity must be equal to quantity of\n      // corresponding width in outputQuantities\n      for (let i = 0; i < inputWidths.length; i++) {\n        const width = inputWidths[i];\n\n        const inQuantity = inputQuantities[width];\n        const outQuantity = outputQuantities[width];\n\n        if (inQuantity !== outQuantity) {\n          console.error(\n            `#checkValidity1D: for input width: ${width} inQuantity != outQuantity`,\n            inQuantity,\n            \"!=\",\n            outQuantity\n          );\n          isValid = false;\n        } else {\n          // remove this width from outputQuantities\n          delete outputQuantities[width];\n        }\n      }\n\n      /*\n            Check 3: is there extra non intended width that user didn't input?\n\n            by now, all the widths in outputQuanitites must have been deleted & it must be empty\n            */\n      const outputWidths = Object.keys(outputQuantities);\n      if (outputWidths.length > 0) {\n        console.error(\n          `#checkValidity1D: unintended buggy widths in output:`,\n          outputWidths,\n          outputQuantities\n        );\n        isValid = false;\n      }\n\n      if (!isValid) {\n        alert(\"Alert! Results contains extra cuts/items. Use with caution\");\n      }\n\n      return isValid;\n    },\n\n    /**\n        for output of 1D algo:\n            1. sorts the bigRolls in ascending order of waste (unused width)\n            2. sort the cuts to each bigRoll (small rolls in nested array to bigRoll) in descending order\n        */\n    sortBigRolls: function (bigRolls) {\n      /*\n            sorts the bigRolls in ascending order of width.\n            E.g,\n            [\n                [0, [21, 21, 21, 21, 21, 15]],\n                [2, [25, 25, 34, 34]],\n                [0, [21, 33, 33, 33]],\n            ]\n\n            becomes:\n            [\n                [0, [21, 21, 21, 21, 21, 15]],\n                [0, [21, 33, 33, 33]],\n                [2, [25, 25, 34, 34]],\n            ]\n            */\n\n      // sort 2-D array based on 0-th element of each nested array\n      // https://stackoverflow.com/a/16096900/3578289\n      bigRolls = bigRolls.sort(function (a, b) {\n        return a[0] - b[0];\n      });\n\n      /*\n            sorts the smallRolls inside each bigRoll in descending order of width\n            E.g,\n            [\n                [0, [21, 21, 21, 21, 21, 15]],\n                [0, [21, 33, 33, 33]],\n                [2, [25, 25, 34, 34]],\n            ]\n\n            becomes:\n            [\n                [0, [15, 21, 21, 21, 21, 21]],\n                [0, [21, 33, 33, 33]],\n                [2, [25, 25, 34, 34]],\n            ]\n            */\n      for (let i = 0; i < bigRolls.length; i++) {\n        let smallRolls = bigRolls[i][1];\n        smallRolls = smallRolls.sort(function (a, b) {\n          return a - b;\n        });\n        bigRolls[i][1] = smallRolls;\n      }\n\n      return bigRolls;\n    },\n\n    /**\n        draws the shapes according to data and requirements of 1d algorithm\n        */\n    draw1d: function () {\n      // clear old drawing\n      this.clearTheDrawing();\n\n      if (!this.mode_data.result) {\n        console.log(\n          `Cannot draw anything. \"result\" is: ${this.mode_data.result} for mode: ${this.mode}`\n        );\n        return;\n      }\n\n      /**\n            Result format:\n            [\n                [unused_width, [width_of_small_roll, width_of_small_roll, ...]], // single roll format\n            ]\n\n            E.g,\n            [\n                [0, [21, 21, 21, 21, 21, 15]],\n                [2, [25, 25, 34, 34]],\n                [0, [21, 33, 33, 33]],\n            ]\n            */\n\n      const unSortedBigRolls = this.mode_data.result.solutions;\n      console.log(\"bigRolls before sorting\", unSortedBigRolls);\n\n      const bigRolls = this.sortBigRolls(unSortedBigRolls);\n      console.log(\"bigRolls after sorting\", bigRolls);\n\n      this.mode_data.result.solutions = bigRolls;\n\n      const colorDict = this.getColorDict();\n\n      const parentWidth = this.mode_data.parents[0].width;\n\n      // get the current width alloted to #d3_area, it's dynamic\n      const graphWidth = document.getElementById(\"d3_area\").clientWidth;\n      let xScale = d3\n        .scaleLinear()\n        .domain([0, parentWidth])\n        .range([0, graphWidth]);\n\n      // let yScale = d3.scaleOrdinal()\n      // \t.domain(d3.range(0, dataLen))// <-num big rolls\n      // \t.rangeBands(0, 300);\n      let yScale = d3\n        .scaleBand()\n        .domain(d3.range(bigRolls.length))\n        // .range([0, 20 * bigRolls.length])\n        .range([0, 300]);\n\n      // create svg element:\n      let svg = d3.select(\"#d3_area svg\");\n\n      var margin = { top: 20, right: 20, bottom: 20, left: 20 };\n\n      let svgWidth = 300 - margin.left - margin.right;\n      let svgHeight = 300 - margin.top - margin.bottom;\n\n      svg\n        .attr(\"width\", svgWidth + margin.left + margin.right)\n        .attr(\"height\", svgHeight + margin.top + margin.bottom)\n        .style(\"border\", \"1px solid #34495e\");\n      // .append(\"g\")\n      // .attr(\"transform\", \"translate(\" + margin.left + \",\" + margin.top + \")\")\n      // .attr('fill', 'blue')\n\n      let x1 = 0;\n      let x2 = 0;\n      let y1 = 0;\n      for (let i = 0; i < bigRolls.length; i++) {\n        const unusedWidth = bigRolls[i][0];\n        const smallRolls = bigRolls[i][1];\n\n        /*\n                each bigRoll has it's own row. All of it's cuts have same Y\n                */\n        x1 = 0;\n        y1 = yScale(i);\n        for (let j = 0; j < smallRolls.length; j++) {\n          const smallRoll = smallRolls[j];\n\n          const width = xScale(smallRoll);\n          x2 = x1 + width;\n\n          // add the rectangular strip / bar\n          let g = svg.append(\"g\").attr(\"transform\", `translate(${x1},${y1})`); // one vertical bar\n\n          g.append(\"rect\")\n            .attr(\"fill\", colorDict[smallRoll]) // <- apply color associated with this width\n            .attr(\"width\", width - 1)\n            .attr(\"height\", yScale.bandwidth() - 1);\n\n          // add text\n          g.append(\"text\")\n            .attr(\"fill\", \"white\")\n            .attr(\"x\", 3) // this x is relative to the parent g\n            .attr(\"y\", yScale.bandwidth() / 2)\n            .attr(\"dy\", \"0.35em\")\n            .text(smallRoll);\n\n          // for next rect, x1 will update to x2 of current rect\n          x1 = x2;\n        }\n\n        if (unusedWidth > 0) {\n          // add unusedWith as rectangular bar\n          x2 = x1 + xScale(unusedWidth);\n          let g = svg.append(\"g\").attr(\"transform\", `translate(${x1},${y1})`); // one vertical bar\n\n          g.append(\"rect\")\n            .attr(\"fill\", this.wasteColor)\n            .attr(\"width\", xScale(unusedWidth) - 1)\n            .attr(\"height\", yScale.bandwidth() - 1);\n\n          // add text\n          g.append(\"text\")\n            .attr(\"fill\", \"white\")\n            .attr(\"x\", 3)\n            .attr(\"y\", yScale.bandwidth() / 2)\n            .attr(\"dy\", \"0.35em\")\n            .text(Math.round(unusedWidth));\n        }\n      }\n\n      return svg.node();\n    },\n\n    /**\n        draws the shapes according to data and requirements of 1d algorithm\n        */\n    draw2d: function () {\n      // clear old drawing\n      this.clearTheDrawing();\n\n      if (!this.mode_data.result) {\n        console.log(\n          `Cannot draw anything. \"result\" is: ${this.mode_data.result} for mode: ${this.mode}`\n        );\n        return;\n      }\n\n      const solutions = this.mode_data.result.solutions;\n\n      for (let i = 0; i < solutions.length; i++) {\n        const sol = solutions[i];\n\n        for (let j = 0; j < sol.length; j++) {\n          const rect = sol[j];\n\n          let x1 = rect[0];\n          let y1 = rect[1];\n          let x2 = rect[2];\n          let y2 = rect[3];\n\n          const color = this.colors[j % this.colors.length];\n          this.drawRect(x1, y1, x2, y2, color);\n        }\n\n        // only draw first solution for now\n        // TODO: FIXME: Does drawing all solution help?\n        break;\n      }\n    },\n\n    // before drawing new result, clear old canvas drawing\n    clearTheDrawing: function () {\n      d3.selectAll(\"#d3_area svg > *\").remove();\n      // also hide the border of svg\n      d3.select(\"#d3_area svg\").style(\"border\", \"\");\n    },\n\n    drawRect: function (x1, y1, x2, y2, color) {\n      console.log(\"Drawing Rect... Color: \", color);\n      console.log(\"Draw rect\", x1, y1, x2, y2);\n\n      const width = Math.abs(x2 - x1);\n      const height = Math.abs(y2 - y1);\n      // const coords = [{ x1, y1, width, height, color }];\n\n      // const rectTitle = `${width} x ${height}`;\n\n      const parentWidth = this.mode_data.parents[0].width;\n      const parentHeight = this.mode_data.parents[0].height;\n      const dataLen = this.mode_data.parents.length;\n\n      // let maxDimOfParent = Math.max(parentWidth, parentHeight);\n\n      //   let maxDimOfParent = null;\n      //   if (this.mode === \"1d\") {\n      //     maxDimOfParent = parentWidth;\n      //   }\n\n      let xScale = d3.scaleLinear().domain([0, parentWidth]).range([0, 300]); // <- TODO here put the dynamic width of chart\n\n      let yScale;\n\n      if (this.mode === \"1d\") {\n        yScale = d3\n          .scaleOrdinal()\n          .domain(d3.range(0, dataLen)) // <-num big rolls\n          .rangeBands(0, 300);\n      } else if (this.mode === \"2d\") {\n        yScale = d3\n          .scaleLinear()\n          .domain([0, parentHeight]) // sum of all stock's heights\n          .range([0, 300]); // display in 300 pixels height?\n      }\n\n      // const scaleFactor =\n      // this.mode === '1d'?\n      // Math.floor(300 / maxDimOfParent)\n      // 1:\n      // Math.floor(300 / maxDimOfParent);\n\n      // const rectX = x1 * scaleFactor;\n      // const rectY = this.mode == '1d'? y1: y1 * scaleFactor;\n      // const rectW = width * scaleFactor;\n      // const rectH = this.mode == '1d'? height: height * scaleFactor;\n\n      // console.log('Scaled rect', rectX, rectY, rectX+rectW, rectY+rectH, scaleFactor);\n      console.log(\n        \"D3 Scaled rect\",\n        xScale(x1),\n        yScale(y1),\n        xScale(width),\n        yScale(height)\n      );\n\n      // create svg element:\n      let svg = d3.select(\"#d3_area svg\");\n\n      var margin = { top: 20, right: 20, bottom: 20, left: 20 };\n\n      let svgWidth = 300 - margin.left - margin.right;\n      let svgHeight = 300 - margin.top - margin.bottom;\n\n      let g = svg\n        .attr(\"width\", svgWidth + margin.left + margin.right)\n        .attr(\"height\", svgHeight + margin.top + margin.bottom)\n        .style(\"border\", \"1px solid #34495e\")\n        .append(\"g\")\n        .attr(\"transform\", \"translate(\" + margin.left + \",\" + margin.top + \")\");\n\n      // Add the path using this helper function\n      g\n        // .data(coords)\n        .append(\"rect\")\n        // .style('fill', function (coords) { return coords.color })\n        .style(\"fill\", color)\n        // .style(\"background-color\", \"black\")\n        // .attr('x', rectX)\n        .attr(\"x\", xScale(x1)) //function (coords) { return xScale(coords.x1) })\n        // .attr('y', rectY)\n        .attr(\"y\", yScale(y1)) //function (coords) {return yScale(coords.y1) })\n        // .attr('width', rectW)\n        .attr(\"width\", xScale(width)) //function (coords) { return xScale(coords.width) })\n        // .attr('height', rectH)\n        .attr(\"height\", yScale(height)) //function (coords) { return yScale(coords.height) })\n        .attr(\"stroke\", \"#34495e\")\n        .text(`${width} x ${height}`);\n\n      // add the label to the shape\n      // const labelX = rectX + Math.abs( rectW / 2 - 15);\n      // const labelY = rectY + Math.abs( rectH / 2 + 5);\n      const labelX = xScale(x1) + Math.abs(xScale(width) / 2); // - 15);\n      const labelY = yScale(y1) + Math.abs(yScale(height) / 2); // + 5);\n\n      //   let label =\n      g.append(\"text\")\n        .attr(\"x\", labelX)\n        .attr(\"y\", labelY)\n        .attr(\"stroke\", \"#34495e\");\n      // .attr('stroke', 'black')\n      // .style(\"font-size\", 15);\n\n      return;\n    },\n\n    /**\n     * clears the data in the child table and returns it to it's initial state\n     * i.e. 1 empty row\n     */\n    clearChildData: function (askConfirm) {\n      if (askConfirm) {\n        const answer = confirm(\"Are you sure you want to empty this Table?\");\n        if (!answer) return;\n      }\n\n      const emptyChildData =\n        this.mode === \"1d\"\n          ? [{ width: \"\", quantity: \"\" }]\n          : [{ width: \"\", height: \"\", quantity: \"\" }];\n\n      this.mode_data.childs = emptyChildData;\n\n      // also clear errors displayed in Child table (if any)\n      this.mode_data.childErrors = null;\n\n      // also clear the result and drawing\n      this.mode_data.result = null;\n      this.clearTheDrawing();\n    },\n\n    clearParentData: function (askConfirm) {\n      if (askConfirm) {\n        const answer = confirm(\"Are you sure you want to empty this Table?\");\n        if (!answer) return;\n      }\n\n      const emptyParentData =\n        this.mode === \"1d\"\n          ? [{ width: \"\", quantity: \"Comming soon\" }]\n          : [{ width: \"\", height: \"\", quantity: \"Comming soon\" }];\n\n      this.mode_data.parents = emptyParentData;\n\n      // also clear errors displayed in Parent table (if any)\n      this.mode_data.parentErrors = null;\n\n      // also clear the result and drawing\n      this.mode_data.result = null;\n      this.clearTheDrawing();\n    },\n\n    /**\n            Removes the row at the index specified. if it's only row, add an empty row after removing\n        */\n    removeRow: function (idx, is_parent) {\n      if (is_parent) {\n        // parent only has 1 row. take help from clearParentData. The remove row button\n        // in parent table is added for symmetry\n        this.clearParentData(false);\n        return;\n      }\n\n      // in case of child\n      if (this.mode_data.childs.length > 1) {\n        this.mode_data.childs.splice(idx, 1);\n      } else {\n        // there is only one item in the child table,\n        // calling clearChildData() will both clear the table and add back an empty row\n        this.clearChildData(false);\n      }\n    },\n\n    /**\n        to each of the widths of small rolls in the result of 1d\n        assign a unique color\n        */\n    getColorDict: function () {\n      const bigRolls = this.mode_data.result.solutions;\n\n      // assign same color to equal sized small rolls\n      let uniqueSmallRollsSet = new Set([]);\n      for (let i = 0; i < bigRolls.length; i++) {\n        const smallRolls = bigRolls[i][1];\n\n        smallRolls.forEach((roll) => {\n          uniqueSmallRollsSet.add(roll);\n        });\n      }\n\n      let uniqueSmallRolls = Array.from(uniqueSmallRollsSet);\n      let colorDict = {};\n\n      for (let i = 0; i < uniqueSmallRolls.length; i++) {\n        // colorDict[ width_of_roll ] = '#color'\n        colorDict[uniqueSmallRolls[i]] = this.colors[i % this.colors.length];\n      }\n\n      // console.log('colorDict:', colorDict)\n      return colorDict;\n    },\n\n    /**\n        returns the color assigned to this width in colorDict\n        this is for 1d mode at the moment\n        */\n    getColor: function (width) {\n      const colorDict = this.getColorDict();\n      // return colorDict[width];\n      return { backgroundColor: `${colorDict[width]}` };\n    },\n\n    /**\n        clear all the inputs and drawings\n        */\n    reset: function () {\n      const answer = confirm(\n        \"Are you sure you want to delete all inputs? Cannot be undone\"\n      );\n      if (!answer) return;\n\n      this.clearChildData(false); // it will also clear the drawing\n      this.clearParentData(false);\n    },\n\n    getPercentageUtilization: function (unusedWidth) {\n      let usedWidth = Math.abs(this.mode_data.parents[0].width - unusedWidth);\n      let percentage = (usedWidth * 100) / this.mode_data.parents[0].width;\n\n      percentage *= 100; // preserve 2 digits after decimal\n      percentage = Math.round(percentage); // remove the decimal part\n      percentage /= 100; // back to original percentage\n\n      return percentage;\n    },\n\n    // to display in footer\n    getCurrentYear: function () {\n      return new Date().getFullYear();\n    },\n\n    /**\n        implemented only for 1D, download the details of cut in CSV\n        TODO: implement for 2D\n        */\n    downloadCsv: function () {\n      if (!this.mode_data.result || !this.mode_data.result.solutions) {\n        console.log(\"downloadCsv: bigRolls are empty..\");\n        return;\n      }\n\n      // prepare data\n      let dataForCsv = [[\"Stock\", \"Usage\", \"Width of Cuts\"]];\n      let numSmallRolls = 0;\n\n      const bigRolls = this.mode_data.result.solutions;\n      for (let i = 0; i < bigRolls.length; i++) {\n        const unusedWidth = bigRolls[i][0];\n        const smallRolls = bigRolls[i][1];\n\n        numSmallRolls += smallRolls.length;\n\n        // ['Stock #', 'Usage', 'Width of Cuts']\n        const nextRow = [\n          i + 1,\n          this.getPercentageUtilization(unusedWidth) + \"%\",\n          smallRolls.join(\",\"),\n        ];\n        dataForCsv.push(nextRow);\n      }\n\n      // convert to CSV format\n      // source: https://stackoverflow.com/a/14966131/3578289\n      const csvContent =\n        \"data:text/csv;charset=utf-8,\" +\n        dataForCsv.map((e) => e.join(\",\")).join(\"\\n\");\n      // console.log('csvContent: ', csvContent);\n\n      // download the file\n      let encodedUri = encodeURI(csvContent);\n      // console.log('encodedUri: ', encodedUri);\n      let link = document.createElement(\"a\");\n      link.setAttribute(\"href\", encodedUri);\n\n      // unique and identifiable filename\n      const stockWidth = this.mode_data.parents[0].width;\n      const d = new Date();\n      const dateString = `${d.getFullYear()}-${\n        d.getMonth() + 1\n      }-${d.getUTCDate()}-${d.getHours()}${d.getMinutes()}-${d.getSeconds()}`;\n\n      const filename = `CSP_stock_${stockWidth}_cuts_${numSmallRolls}_${dateString}.csv`;\n      link.setAttribute(\"download\", filename);\n\n      document.body.appendChild(link); // Required for FF\n      link.click(); // This will download the data file named \"my_data.csv\".\n    },\n  },\n};\n</script>\n\n<!-- Add \"scoped\" attribute to limit CSS to this component only -->\n<style scoped>\n@import \"../assets/style.scss\";\n</style>\n"
  },
  {
    "path": "deployment/frontend/src/main.js",
    "content": "import { createApp } from 'vue'\n\n// import Vue from 'vue'\n// import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'\n// Import Bootstrap an BootstrapVue CSS files (order is important)\nimport 'bootstrap/dist/css/bootstrap.css'\n// import 'bootstrap-vue/dist/bootstrap-vue.css'\n\n// Make BootstrapVue available throughout your project\n// Vue.use(BootstrapVue)\n// Optionally install the BootstrapVue icon components plugin\n// Vue.use(IconsPlugin)\n\n\nimport App from './App.vue'\n\ncreateApp(App).mount('#app')\n"
  },
  {
    "path": "deployment/requirements.txt",
    "content": "gunicorn\nFlask\nFlask-Cors\nortools\n"
  },
  {
    "path": "deployment/server.py",
    "content": "from flask import Flask, json, request\nfrom flask_cors import CORS, cross_origin\n\nimport stock_cutter # local module\n\napp = Flask(__name__)\ncors = CORS(app)\napp.config['CORS_HEADERS'] = 'Content-Type'\n\n@app.route('/', methods=['GET'])\n@cross_origin()\ndef get_csp():\n\treturn 'Cutting Stock Problem'\n\n'''\nroute for receving data for 1D problem \n'''\n@app.route('/stocks_1d', methods=['POST'])\n@cross_origin()\ndef post_stocks_1d():\n\t'''\n\texpects two params to be present\n\tchild_rolls:\n\t\tarray of arrays. E.g [ [quantity, width], [quantity, width], ... ]\n\n\tparent_rolls:\n\t\tarray of arrays. E.g [ [quantity, width], [quantity, width], ... ]\n\t'''\n\timport stock_cutter_1d\n\n\tdata = request.json\n\tprint('data: ', data)\n\n\tchild_rolls = data['child_rolls']\n\tparent_rolls = data['parent_rolls']\n\n\t'''\n\tit can be\n\texactCuts: cut exactly as many as specified by user\n\tminWaste: cut some items, more than specified, to avoid waste\n\t'''\n\tcutStyle = data['cutStyle']\n\n\t# output = stock_cutter_1d.StockCutter1D(child_rolls, parent_rolls, cutStyle=cutStyle)\n\toutput = stock_cutter_1d.StockCutter1D(child_rolls, parent_rolls, large_model=False, cutStyle=cutStyle)\n\n\treturn output\n\n\n\n'''\nroute for 2D\n'''\n@app.route('/stocks_2d', methods=['POST'])\n@cross_origin()\ndef post_stocks():\n\t'''\n\texpects two params to be present\n\tchild_rects:\n\t\tarray of arrays. Each inner array is like [w, h] i.e. width & height of rectangle\n\n\tparent_rects:\n\t\tarray of arrays. Each inner array is like [w, h] i.e. width & height of rectangle\n\t'''\n\tdata = request.json\n\tprint('data: ', data)\n\n\tchild_rects = data['child_rects']\n\tparent_rects = data['parent_rects']\n\n\toutput = stock_cutter.StockCutter(child_rects, parent_rects)\n\n\treturn output\n\n\n\nif __name__ == '__main__':\n    # app.run()\n\tapp.run(threaded=True, port=5000)\n"
  },
  {
    "path": "deployment/stock_cutter.py",
    "content": "'''\n@Author Emad Ehsan\nCutting Stock problem 2D\nNot complete.\nWhat's remaining: Finding Optimized solution that minimizes the waste.\n'''\n\nimport collections, json\nfrom ortools.sat.python import cp_model\n\n\"\"\"\n    params\n        child_rects: \n            lists of multiple rectangles' coords\n            e.g.: [ [w, h], [w, h], ...]\n        parent_rects: rectangle coords\n            lists of multiple rectangles' coords\n            e.g.: [ [w, h], [w, h], ...]\n\"\"\"\ndef StockCutter(child_rects, parent_rects, output_json=True):\n    \n    # Create the model\n    model = cp_model.CpModel()\n\n    # parent rect (to cut from). horizon = [ width, height ] of parent sheet\n    # for now, parent rectangle is just one\n    # TODO: to add functionality of cutting from multiple parent sheets, start here:\n    horizon = parent_rects[0] \n    total_parent_area = horizon[0] * horizon[1] # width x height\n\n    # Named Tuple to store information about created variables\n    sheet_type = collections.namedtuple('sheet_type', 'x1 y1 x2 y2 x_interval y_interval is_extra')\n\n    # Store for all model variables\n    all_vars = {}\n\n    # sum of to save area of all small rects, to cut from parent rect\n    total_child_area = 0 \n\n    # hold the widths (x) and heights (y) interval vars of each sheet\n    x_intervals = []\n    y_intervals = []\n\n    # create model vars and intervals\n    for rect_id, rect in enumerate(child_rects):\n        width = rect[0]\n        height = rect[1]\n        area = width * height\n        total_child_area += area\n        # print(f\"Rect: {width}x{height}, Area: {area}\")\n\n        suffix = '_%i_%i' % (width, height)\n\n        # interval to represent width. max value can be the width of parent rect\n        x1_var = model.NewIntVar(0, horizon[0], 'x1' + suffix)\n        x2_var = model.NewIntVar(0, horizon[0], 'x2' + suffix)\n        x_interval_var = model.NewIntervalVar(x1_var, width, x2_var, 'x_interval' + suffix)\n\n        # interval to represent height. max value can be the height of parent rect\n        y1_var = model.NewIntVar(0, horizon[1], 'y1' + suffix)\n        y2_var = model.NewIntVar(0, horizon[1], 'y2' + suffix)\n        y_interval_var = model.NewIntervalVar(y1_var, height, y2_var, 'y_interval' + suffix)\n        \n        x_intervals.append(x_interval_var)\n        y_intervals.append(y_interval_var)\n\n        # store the variables for later use\n        all_vars[rect_id] = sheet_type(\n            x1=x1_var, \n            y1=y1_var, \n            x2=x2_var, \n            y2=y2_var, \n            x_interval=x_interval_var,\n            y_interval=y_interval_var,\n            is_extra=False # to keep track of 1x1 custom rects added in next step\n        )\n\n        # model.Minimize(x1_var)\n        # model.Minimize(y1_var)\n\n\n    # TODO: Minimize (x1,y1) values. So that rectangles are placed at the start\n    # this reduced the areas wasted by place rectangles in the middle / at the end\n    # even though the space at the start is available.\n    # >\n    # for rect_id in range(len(child_rects)):\n    #     model.Minimize(all_vars[rect_id].x1 + all_vars[rect_id].y1)\n    #     model.Minimize(all_vars[rect_id].x2 + all_vars[rect_id].y2)\n    #     model.Minimize(all_vars[rect_id].x1)\n    #     model.Minimize(all_vars[rect_id].x2)\n    #     model.Minimize(all_vars[rect_id].y1)\n    #     model.Minimize(all_vars[rect_id].y2)\n\n\n    '''\n    FIXME: experiment\n    Experment: treat the remaining area as small units of 1x1 rectangles. Push these rects to higher x,y.\n    '''\n    # leftover_area = total_parent_area - total_child_area\n    # if leftover_area >= 0:\n    #     '''\n    #     each unit of leftover_area can be represented by 1x1 rectangles. \n    #     For leftover_area = 4 (e.g. 2x2 originally), we can use 4 rects of 1x1. Why? Because\n    #     1. leftover_area would not always be continous. It is possible it is in the form of two \n    #     separate 2x1 rects or one 2x2 or four rects of 1x1. So we need the simplest version, \n    #     that can cover all types of rects. And it is 1x1\n    #     2. 1x1 can represent non-adjecent weirdly shaped locations in the parent area that were leftover.\n    #     '''\n    #     num_1x1rects = leftover_area\n\n    #     for i in range(num_1x1rects):\n    #         print(f'{i}-th 1x1')\n    #         suffix = '_%i_%i' % (1, 1)\n\n    #         # interval to represent width. max value can be the width of parent rect\n    #         x1_var = model.NewIntVar(0, horizon[0], 'x1' + suffix)\n    #         x2_var = model.NewIntVar(0, horizon[0], 'x2' + suffix)\n    #         x_interval_var = model.NewIntervalVar(x1_var, 1, x2_var, 'x_interval' + suffix)\n\n    #         # interval to represent height. max value can be the height of parent rect\n    #         y1_var = model.NewIntVar(0, horizon[1], 'y1' + suffix)\n    #         y2_var = model.NewIntVar(0, horizon[1], 'y2' + suffix)\n    #         y_interval_var = model.NewIntervalVar(y1_var, 1, y2_var, 'y_interval' + suffix)\n            \n    #         x_intervals.append(x_interval_var)\n    #         y_intervals.append(y_interval_var)\n\n    #         # store the variables for later use\n    #         all_vars[rect_id] = sheet_type(\n    #             x1=x1_var, \n    #             y1=y1_var, \n    #             x2=x2_var, \n    #             y2=y2_var, \n    #             x_interval=x_interval_var,\n    #             y_interval=y_interval_var,\n    #             is_extra=True\n    #         )\n    #         model.Maximize(x1_var)\n    #         model.Maximize(y1_var)\n    # else:\n    #     print(f'Problem identified: Area of small rects is larger than parent rect by {leftover_area}')\n\n    \n\n\n    # add constraint: no over lap of rectangles allowed\n    model.AddNoOverlap2D(x_intervals, y_intervals)\n\n    # Solve model\n    solver = cp_model.CpSolver()\n\n    '''\n    Search for all solutions is only defined on satisfiability problems\n    '''\n    # solution_printer = VarArraySolutionPrinter(all_vars)\n    # status = solver.SearchForAllSolutions(model, solution_printer) # use for satisfiability problem\n    # solutions = solution_printer.get_unique_solutions()\n    # int_solutions = str_solutions_to_int(solutions)\n    # output = {\n    #     \"statusName\": solver.StatusName(status),\n    #     \"numSolutions\": solution_printer.solution_count(),\n    #     \"numUniqueSolutions\": len(solutions),\n    #     \"solutions\": int_solutions # unique solutions\n    # }\n\n    '''\n    for single solution\n    '''\n    status = solver.Solve(model) # use for Optimization Problem\n    singleSolution = getSingleSolution(solver, all_vars)\n    int_solutions = [singleSolution] # convert to array\n    output = {\n        \"statusName\": solver.StatusName(status),\n        \"numSolutions\": '1',\n        \"numUniqueSolutions\": '1',\n        \"solutions\": int_solutions # unique solutions\n    }\n\n\n    print('Time:', solver.WallTime())\n    print('Status:', output['statusName'])\n    print('Solutions found :', output['numSolutions'])\n    print('Unique solutions: ', output['numUniqueSolutions'])\n\n    if output_json:\n        return json.dumps(output)        \n    else:\n        return int_solutions # integer representation of solutions\n\n'''\n    This method is used to extract the single solution from the solver.\n    Because in the case where VarArraySolutionPrinter is not used, the answers are not \n    yet extracted from the solver. Use this method to extract the solver.\n'''\ndef getSingleSolution(solver, all_vars):\n    solution = []\n    # extra coordinates of all rectangles for this solution \n    for rect_id in all_vars:\n        rect = all_vars[rect_id]\n        x1 = solver.Value(rect.x1)\n        x2 = solver.Value(rect.x2)\n        y1 = solver.Value(rect.y1)\n        y2 = solver.Value(rect.y2)\n\n        # rect_str = f\"{x1},{y1},{x2},{y2}\"\n        coords = [x1, y1, x2, y2];\n        # print(rect_str)\n\n        solution.append(coords)\n        # print(f'Rect #{rect_id}: {x1},{y1} -> {x2},{y2}')\n\n    # print(rect_strs)\n    # sort the rectangles\n    # rect_strs = sorted(rect_strs)\n    # single solution as a string\n    # solution_str = '-'.join(rect_strs)\n    return solution\n\n\n\"\"\"\n    converts from string format to integer values. String format, in previous step, was used \n    to exclude duplicates.\n    params:\n        str_solutions: list of strings. 1 string contains is solution\n\"\"\"\ndef str_solutions_to_int(str_solutions):\n\n    # list of solutions, each solution is a list of rectangle coords that look like [x1,y1,x2,y2]\n    int_solutions = []\n\n    # go over all solutions and convert them to int>list>json\n    for idx, sol in enumerate(str_solutions):\n        # sol is string of coordinates of all rectangles in this solution\n        # format: x1,y1,x2,y2-x1,y1,x2,y2\n    \n        rect_strs = sol.split('-')\n        rect_coords = [\n            # [x1,y1,x2,y2],\n            # [x1,y1,x2,y2],\n            # ...\n        ]\n\n        # convert each rectangle's coords to int\n        for rect_str in rect_strs:\n            coords_str = rect_str.split(',')\n            coords = [int(c) for c in coords_str]\n            rect_coords.append(coords)\n\n        # print('rect_coords', rect_coords)\n\n        int_solutions.append(rect_coords)\n\n    return int_solutions\n\n\n\"\"\"\n    To get all the solutions of the problem, as they come up. \n    https://developers.google.com/optimization/cp/cp_solver#all_solutions\n\n    The solutions are all unique. But for the child rectangles that have same dimensions, \n    some solution will be repetitive. Because for the algorithm, they are different solutions, \n    but because of same size, they are merely permutations of the similar child rectangles - \n    having other rectangles' positions fixed.\n\n    We want to remove repetitive extra solutions. One way to do this is\n        1. Stringify every rectangle coords in a solution\n            (1,2)->(2,3) becomes \"1,2,2,3\"\n\n            # here the rectangles are stored as a string: \"1,2,2,3\" where x1=1, y1=2, x2=2, y2=3\n        \n        2. Put all these string coords into a sorted list. This sorting is important. \n            Because the rectangles (1,2)->(2,3) and (3,3)->(4,4) are actually same size (1x1) rectangles. \n            And they can appear in 1st solution as \n            [(1,2)->(2,3)   ,   (3,3)->(4,4)]\n            and in the 2nd solution as\n            [(3,3)->(4,4)   ,   (1,2)->(2,3)]\n\n            but this sorted list of strings will ensure both solutions are represented as\n\n            [..., \"1,2,2,3\", \"3,3,4,4\", ...]\n\n        3. Join the Set of \"strings (rectangles)\" in to one big string seperated by '-'. For every solution.\n            So in resulting big strings (solutions), we will have two similar strings (solutions) \n            that be similar and also contain\n\n            \"....1,2,2,3-3,3,4,4-....\"\n\n        4. Now add all these \"strings (solutions)\" into a Set. this adding to the set \n        will remove similar strings. And hence duplicate solutions will be removed.\n\n\"\"\"\nclass VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):\n\n    def __init__(self, variables):\n        cp_model.CpSolverSolutionCallback.__init__(self)\n        self.__variables = variables\n        self.__solution_count = 0\n\n        # hold the calculated solutions\n        self.__solutions = []\n        self.__unique_solutions = set()\n\n    def on_solution_callback(self):\n        self.__solution_count += 1\n        # print('Sol#: ', self.__solution_count)\n\n        # using list to hold the coordinate strings of rectangles\n        rect_strs = []\n\n        # extra coordinates of all rectangles for this solution \n        for rect_id in self.__variables:\n            rect = self.__variables[rect_id]\n            x1 = self.Value(rect.x1)\n            x2 = self.Value(rect.x2)\n            y1 = self.Value(rect.y1)\n            y2 = self.Value(rect.y2)\n\n            rect_str = f\"{x1},{y1},{x2},{y2}\"\n            # print(rect_str)\n\n            rect_strs.append(rect_str)\n            # print(f'Rect #{rect_id}: {x1},{y1} -> {x2},{y2}')\n\n        # print(rect_strs)\n\n        # sort the rectangles\n        rect_strs = sorted(rect_strs)\n\n        # single solution as a string\n        solution_str = '-'.join(rect_strs)\n        # print(solution_str)\n\n        # store the solutions\n        self.__solutions.append(solution_str)\n        self.__unique_solutions.add(solution_str) # __unique_solutions is a set, so duplicates will get removed\n\n\n    def solution_count(self):\n        return self.__solution_count\n\n    # returns all solutions  \n    def get_solutions(self):\n        return self.__solutions\n\n    \"\"\"\n    returns unique solutions\n    returns the permutation free list of solution strings  \n    \"\"\"\n    def get_unique_solutions(self):\n        return list(self.__unique_solutions) # __unique_solutions is a Set, convert to list\n\n\n'''\nnon-API method. Used for testing and running locally / in a Notebook.\nDraws the rectangles\n'''\n\ndef drawRectsFromCoords(rect_coords, parent_rects):\n    import matplotlib.pyplot as plt\n    import matplotlib.patches as patches\n\n    # TODO: to add support for multiple parent rects, update here\n    xSize = parent_rects[0][0]\n    ySize = parent_rects[0][1]\n\n    # draw rectangle\n    fig,ax = plt.subplots(1)\n    plt.xlim(0,xSize)\n    plt.ylim(0,ySize)\n    plt.gca().set_aspect('equal', adjustable='box')\n    \n    # print coords\n    coords = []\n    colors = ['r', 'g', 'b', 'y', 'brown', 'black', 'violet', 'pink', 'gray', 'orange', 'b', 'y']\n    for idx, coords in enumerate(rect_coords):\n        x1=coords[0]\n        y1=coords[1]\n        x2=coords[2]\n        y2=coords[3]\n        # print(f\"{x1}, {y1} -> {x2}, {y2}\")\n\n        width = abs(x1-x2)\n        height = abs(y1-y2)\n        # print(f\"Rect#{idx}: {width}x{height}\")\n\n        # Create a Rectangle patch\n        rect_shape = patches.Rectangle((x1,y1), width, height,facecolor=colors[idx])\n        # Add the patch to the Axes\n        ax.add_patch(rect_shape)\n    plt.show()\n\n\n\n\n# for testing\nif __name__ == '__main__':\n\n    child_rects = [\n        # [1, 1],\n        # [2, 2],\n        # [1, 3],\n        # [4, 3],\n        # [2, 4],\n        # [2, 2],\n\n        [27, 17],\n        [27, 17],\n        [18, 56],\n\n        # [3, 3],\n        # [3, 3],\n        # [3, 3],\n        # [3, 3],\n    ]\n\n    # parent_rects = [[6,6]]\n    parent_rects = [[84,72]]\n\n    solutions = StockCutter(child_rects, parent_rects, output_json=False) # get the integer solution\n\n    for sol in solutions:\n        print(sol)\n        drawRectsFromCoords(sol, parent_rects)"
  },
  {
    "path": "deployment/stock_cutter_1d.py",
    "content": "'''\nOriginal Author: Serge Kruk\nOriginal Version: https://github.com/sgkruk/Apress-AI/blob/master/cutting_stock.py\n\nUpdated by: Emad Ehsan\nV2: https://github.com/emadehsan/Apress-AI/blob/master/my-models/custom_cutting_stock.py\n\nV3 is following:\n'''\nfrom ortools.linear_solver import pywraplp\nfrom math import ceil\nfrom random import randint\nimport json\n\ndef newSolver(name,integer=False):\n  return pywraplp.Solver(name,\\\n                         pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING \\\n                         if integer else \\\n                         pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)\n\n'''\nreturn a printable value\n'''\ndef SolVal(x):\n  if type(x) is not list:\n    return 0 if x is None \\\n      else x if isinstance(x,(int,float)) \\\n           else x.SolutionValue() if x.Integer() is False \\\n                else int(x.SolutionValue())\n  elif type(x) is list:\n    return [SolVal(e) for e in x]\n\ndef ObjVal(x):\n  return x.Objective().Value()\n\n\ndef gen_data(num_orders):\n    R=[] # small rolls\n    # S=0 # seed?\n    for i in range(num_orders):\n        R.append([randint(1,12), randint(5,40)])\n    return R\n\n\ndef solve_model(demands, parent_width=100, cutStyle='exactCuts'):\n  ''' demands = [\n          [1, 3], # [quantity, width]\n          [3, 5],\n          ...\n      ]\n\n      parent_width = integer\n  '''\n  num_orders = len(demands)\n  solver = newSolver('Cutting Stock', True)\n  k,b  = bounds(demands, parent_width)\n\n  # array of boolean declared as int, if y[i] is 1, \n  # then y[i] Big roll is used, else it was not used\n  y = [ solver.IntVar(0, 1, f'y_{i}') for i in range(k[1]) ] \n\n  # x[i][j] = 3 means that small-roll width specified by i-th order\n  # must be cut from j-th order, 3 tmies \n  x = [[solver.IntVar(0, b[i], f'x_{i}_{j}') for j in range(k[1])] \\\n      for i in range(num_orders)]\n  \n  unused_widths = [ solver.NumVar(0, parent_width, f'w_{j}') \\\n      for j in range(k[1]) ] \n  \n  # will contain the number of big rolls used\n  nb = solver.IntVar(k[0], k[1], 'nb')\n\n  # consntraint: demand fullfilment\n  for i in range(num_orders):  \n    # small rolls from i-th order must be at least as many in quantity\n    # as specified by the i-th order\n    if cutStyle == 'minWaste':\n      solver.Add(sum(x[i][j] for j in range(k[1])) >= demands[i][0]) \n    else:\n      # probably cutStyle == exactCuts\n      solver.Add(sum(x[i][j] for j in range(k[1])) == demands[i][0]) \n\n  # constraint: max size limit\n  for j in range(k[1]):\n    # total width of small rolls cut from j-th big roll, \n    # must not exceed big rolls width\n    solver.Add( \\\n        sum(demands[i][1]*x[i][j] for i in range(num_orders)) \\\n        <= parent_width*y[j] \\\n      ) \n\n    # width of j-th big roll - total width of all orders cut from j-th roll\n    # must be equal to unused_widths[j]\n    # So, we are saying that assign unused_widths[j] the remaining width of j'th big roll\n    solver.Add(parent_width*y[j] - sum(demands[i][1]*x[i][j] for i in range(num_orders)) == unused_widths[j])\n\n    '''\n    Book Author's note from page 201:\n    [the following constraint]  breaks the symmetry of multiple solutions that are equivalent \n    for our purposes: any permutation of the rolls. These permutations, and there are K! of \n    them, cause most solvers to spend an exorbitant time solving. With this constraint, we \n    tell the solver to prefer those permutations with more cuts in roll j than in roll j + 1. \n    The reader is encouraged to solve a medium-sized problem with and without this \n    symmetry-breaking constraint. I have seen problems take 48 hours to solve without the \n    constraint and 48 minutes with. Of course, for problems that are solved in seconds, the \n    constraint will not help; it may even hinder. But who cares if a cutting stock instance \n    solves in two or in three seconds? We care much more about the difference between two \n    minutes and three hours, which is what this constraint is meant to address\n    '''\n    if j < k[1]-1: # k1 = total big rolls\n      # total small rolls of i-th order cut from j-th big roll must be >=\n      # totall small rolls of i-th order cut from j+1-th big roll\n      solver.Add(sum(x[i][j] for i in range(num_orders)) >= sum(x[i][j+1] for i in range(num_orders)))\n\n  # find & assign to nb, the number of big rolls used\n  solver.Add(nb == solver.Sum(y[j] for j in range(k[1])))\n\n  ''' \n    minimize total big rolls used\n    let's say we have y = [1, 0, 1]\n    here, total big rolls used are 2. 0-th and 2nd. 1st one is not used. So we want our model to use the \n    earlier rolls first. i.e. y = [1, 1, 0]. \n    The trick to do this is to define the cost of using each next roll to be higher. So the model would be\n    forced to used the initial rolls, when available, instead of the next rolls.\n\n    So instead of Minimize ( Sum of y ) or Minimize( Sum([1,1,0]) )\n    we Minimize( Sum([1*1, 1*2, 1*3]) )\n  ''' \n\n  '''\n  Book Author's note from page 201:\n\n  There are alternative objective functions. For example, we could have minimized the sum of the waste. This makes sense, especially if the demand constraint is formulated as an inequality. Then minimizing the sum of waste Chapter 7  advanCed teChniques\n  will spend more CPU cycles trying to find more efficient patterns that over-satisfy demand. This is especially good if the demand widths recur regularly and storing cut rolls in inventory to satisfy future demand is possible. Note that the running time will grow quickly with such an objective function\n  '''\n\n  Cost = solver.Sum((j+1)*y[j] for j in range(k[1]))\n\n  solver.Minimize(Cost)\n\n  status = solver.Solve()\n  numRollsUsed = SolVal(nb)\n\n  return status, \\\n    numRollsUsed, \\\n    rolls(numRollsUsed, SolVal(x), SolVal(unused_widths), demands), \\\n    SolVal(unused_widths), \\\n    solver.WallTime()\n\ndef bounds(demands, parent_width=100):\n  '''\n  b = [sum of widths of individual small rolls of each order]\n  T = local var. stores sum of widths of adjecent small-rolls. When the width reaches 100%, T is set to 0 again.\n  k = [k0, k1], k0 = minimum big-rolls requierd, k1: number of big rolls that can be consumed / cut from\n  TT = local var. stores sum of widths of of all small-rolls. At the end, will be used to estimate lower bound of big-rolls\n  '''\n  num_orders = len(demands)\n  b = []\n  T = 0\n  k = [0,1]\n  TT = 0\n\n  for i in range(num_orders):\n    # q = quantity, w = width; of i-th order\n    quantity, width = demands[i][0], demands[i][1]\n    # TODO Verify: why min of quantity, parent_width/width?\n    # assumes widths to be entered as percentage\n    # int(round(parent_width/demands[i][1])) will always be >= 1, because widths of small rolls can't exceed parent_width (which is width of big roll)\n    # b.append( min(demands[i][0], int(round(parent_width / demands[i][1]))) )\n    b.append( min(quantity, int(round(parent_width / width))) )\n\n    # if total width of this i-th order + previous order's leftover (T) is less than parent_width\n    # it's fine. Cut it.\n    if T + quantity*width <= parent_width:\n      T, TT = T + quantity*width, TT + quantity*width\n    # else, the width exceeds, so we have to cut only as much as we can cut from parent_width width of the big roll\n    else:\n      while quantity:\n        if T + width <= parent_width:\n          T, TT, quantity = T + width, TT + width, quantity-1\n        else:\n          k[1],T = k[1]+1, 0 # use next roll (k[1] += 1)\n  k[0] = int(round(TT/parent_width+0.5))\n\n  print('k', k)\n  print('b', b)\n\n  return k, b\n\n'''\n  nb: array of number of rolls to cut, of each order\n  \n  w: \n  demands: [\n    [quantity, width],\n    [quantity, width],\n    [quantity, width],\n  ]\n'''\ndef rolls(nb, x, w, demands):\n  consumed_big_rolls = []\n  num_orders = len(x) \n  # go over first row (1st order)\n  # this row contains the list of all the big rolls available, and if this 1st (0-th) order\n  # is cut from any big roll, that big roll's index would contain a number > 0\n  for j in range(len(x[0])):\n    # w[j]: width of j-th big roll \n    # int(x[i][j]) * [demands[i][1]] width of all i-th order's small rolls that are to be cut from j-th big roll \n    RR = [ abs(w[j])] + [ int(x[i][j])*[demands[i][1]] for i in range(num_orders) \\\n                    if x[i][j] > 0 ] # if i-th order has some cuts from j-th order, x[i][j] would be > 0\n    consumed_big_rolls.append(RR)\n\n  return consumed_big_rolls\n\n\n\n'''\nthis model starts with some patterns and then optimizes those patterns\n'''\ndef solve_large_model(demands, parent_width=100, cutStyle='exactCuts'):\n  num_orders = len(demands)\n  iter = 0\n  patterns = get_initial_patterns(demands)\n  # print('method#solve_large_model, patterns', patterns)\n\n  # list quantities of orders\n  quantities = [demands[i][0] for i in range(num_orders)]\n  print('quantities', quantities)\n\n  while iter < 20:\n    status, y, l = solve_master(patterns, quantities, parent_width=parent_width, cutStyle=cutStyle)\n    iter += 1\n\n    # list widths of orders\n    widths = [demands[i][1] for i in range(num_orders)]\n    new_pattern, objectiveValue = get_new_pattern(l, widths, parent_width=parent_width)\n\n    # print('method#solve_large_model, new_pattern', new_pattern)\n    # print('method#solve_large_model, objectiveValue', objectiveValue)\n\n    for i in range(num_orders):\n      # add i-th cut of new pattern to i-thp pattern\n      patterns[i].append(new_pattern[i])\n\n  status, y, l = solve_master(patterns, quantities, parent_width=parent_width, integer=True, cutStyle=cutStyle)  \n\n  return status, \\\n          patterns, \\\n          y, \\\n          rolls_patterns(patterns, y, demands, parent_width=parent_width)\n\n\n'''\nDantzig-Wolfe decomposition splits the problem into a Master Problem MP and a sub-problem SP.\n\nThe Master Problem: provided a set of patterns, find the best combination satisfying the demand\n\nC: patterns\nb: demand\n'''\ndef solve_master(patterns, quantities, parent_width=100, integer=False, cutStyle='exactCuts'):\n  title = 'Cutting stock master problem'\n  num_patterns = len(patterns)\n  n = len(patterns[0])\n  # print('**num_patterns x n: ', num_patterns, 'x', n)\n  # print('**patterns recived:')\n  # for p in patterns:\n  #   print(p)\n\n  constraints = []\n\n  solver = newSolver(title, integer)\n\n  # y is not boolean, it's an integer now (as compared to y in approach used by solve_model)\n  y = [ solver.IntVar(0, 1000, '') for j in range(n) ] # right bound?\n  # minimize total big rolls (y) used\n  Cost = sum(y[j] for j in range(n)) \n  solver.Minimize(Cost)\n\n  # for every pattern\n  for i in range(num_patterns):\n    # add constraint that this pattern (demand) must be met\n    # there are m such constraints, for each pattern\n\n    if cutStyle == 'minWaste':\n      constraints.append(solver.Add( sum(patterns[i][j]*y[j] for j in range(n)) >= quantities[i]) ) \n    else:\n      # probably cutStyle == exactCuts\n      constraints.append(solver.Add( sum(patterns[i][j]*y[j] for j in range(n)) == quantities[i]) ) \n\n\n  status = solver.Solve()\n  y = [int(ceil(e.SolutionValue())) for e in y]\n\n  l =  [0 if integer else constraints[i].DualValue() for i in range(num_patterns)]\n  # sl =  [0 if integer else constraints[i].name() for i in range(num_patterns)]\n  # print('sl: ', sl)\n\n  # l =  [0 if integer else u[i].Ub() for i in range(m)]\n  toreturn = status, y, l\n  # l_to_print = [round(dd, 2) for dd in toreturn[2]]\n  # print('l: ', len(l_to_print), '->', l_to_print)\n  # print('l: ', toreturn[2])\n  return toreturn\n\n\n'''\nTODO Make sense of this:\n'''\ndef get_new_pattern(l, w, parent_width=100):\n  solver = newSolver('Cutting stock sub-problem', True)\n  n = len(l)\n  new_pattern = [ solver.IntVar(0, parent_width, '') for i in range(n) ]\n\n  # maximizes the sum of the values times the number of occurrence of that roll in a pattern\n  Cost = sum( l[i] * new_pattern[i] for i in range(n))\n  solver.Maximize(Cost)\n\n  # ensuring that the pattern stays within the total width of the large roll \n  solver.Add( sum( w[i] * new_pattern[i] for i in range(n)) <= parent_width ) \n\n  status = solver.Solve()\n  return SolVal(new_pattern), ObjVal(solver)\n\n\n'''\nthe initial patterns must be such that they will allow a feasible solution, \none that satisfies all demands. \nConsidering the already complex model, let’s keep it simple. \nOur initial patterns have exactly one roll per pattern, as obviously feasible as inefficient.\n'''\ndef get_initial_patterns(demands):\n  num_orders = len(demands)\n  return [[0 if j != i else 1 for j in range(num_orders)]\\\n          for i in range(num_orders)]\n\ndef rolls_patterns(patterns, y, demands, parent_width=100):\n  R, m, n = [], len(patterns), len(y)\n\n  for j in range(n):\n    for _ in range(y[j]):\n      RR = []\n      for i in range(m):\n        if patterns[i][j] > 0:\n          RR.extend( [demands[i][1]] * int(patterns[i][j]) )\n      used_width = sum(RR)\n      R.append([parent_width - used_width, RR])\n\n  return R\n\n\n'''\nchecks if all small roll widths (demands) smaller than parent roll's width\n'''\ndef checkWidths(demands, parent_width):\n  for quantity, width in demands:\n    if width > parent_width:\n      print(f'Small roll width {width} is greater than parent rolls width {parent_width}. Exiting')\n      return False\n  return True\n\n\n'''\n    params\n        child_rolls: \n            list of lists, each containing quantity & width of rod / roll to be cut\n            e.g.: [ [quantity, width], [quantity, width], ...]\n        parent_rolls: \n            list of lists, each containing quantity & width of rod / roll to cut from\n            e.g.: [ [quantity, width], [quantity, width], ...]\n        cutStyle:\n          there are two types of cutting style\n          1. cut exactly as many items as specified: exactCuts\n          2. cut some items more than specified to minimize waste: minWaste\n'''\ndef StockCutter1D(child_rolls, parent_rolls, output_json=True, large_model=True, cutStyle='exactCuts'):\n\n  # at the moment, only parent one width of parent rolls is supported\n  # quantity of parent rolls is calculated by algorithm, so user supplied quantity doesn't matter?\n  # TODO: or we can check and tell the user the user when parent roll quantity is insufficient\n  parent_width = parent_rolls[0][1]\n\n  if not checkWidths(demands=child_rolls, parent_width=parent_width):\n    return []\n\n\n  print('child_rolls', child_rolls)\n  print('parent_rolls', parent_rolls)\n\n  if not large_model:\n    print('Running Small Model...')\n    status, numRollsUsed, consumed_big_rolls, unused_roll_widths, wall_time = \\\n              solve_model(demands=child_rolls, parent_width=parent_width, cutStyle=cutStyle)\n\n    # convert the format of output of solve_model to be exactly same as solve_large_model\n    print('consumed_big_rolls before adjustment: ', consumed_big_rolls)\n    new_consumed_big_rolls = []\n    for big_roll in consumed_big_rolls:\n      if len(big_roll) < 2:\n        # sometimes the solve_model return a solution that contanis an extra [0.0] entry for big roll\n        consumed_big_rolls.remove(big_roll)\n        continue\n      unused_width = big_roll[0]\n      subrolls = []\n      for subitem in big_roll[1:]:\n        if isinstance(subitem, list):\n          # if it's a list, concatenate with the other lists, to make a single list for this big_roll\n          subrolls = subrolls + subitem\n        else:\n          # if it's an integer, add it to the list\n          subrolls.append(subitem)\n      new_consumed_big_rolls.append([unused_width, subrolls])\n    print('consumed_big_rolls after adjustment: ', new_consumed_big_rolls)\n    consumed_big_rolls = new_consumed_big_rolls\n  \n  else:\n    print('Running Large Model...');\n    status, A, y, consumed_big_rolls = solve_large_model(demands=child_rolls, parent_width=parent_width, cutStyle=cutStyle)\n\n  numRollsUsed = len(consumed_big_rolls)\n  # print('A:', A, '\\n')\n  # print('y:', y, '\\n')\n\n\n  STATUS_NAME = ['OPTIMAL',\n    'FEASIBLE',\n    'INFEASIBLE',\n    'UNBOUNDED',\n    'ABNORMAL',\n    'NOT_SOLVED'\n    ]\n\n  output = {\n      \"statusName\": STATUS_NAME[status],\n      \"numSolutions\": '1',\n      \"numUniqueSolutions\": '1',\n      \"numRollsUsed\": numRollsUsed,\n      \"solutions\": consumed_big_rolls # unique solutions\n  }\n\n\n  # print('Wall Time:', wall_time)\n  print('numRollsUsed', numRollsUsed)\n  print('Status:', output['statusName'])\n  print('Solutions found :', output['numSolutions'])\n  print('Unique solutions: ', output['numUniqueSolutions'])\n\n  if output_json:\n    return json.dumps(output)        \n  else:\n    return consumed_big_rolls\n\n\n'''\nDraws the big rolls on the graph. Each horizontal colored line represents one big roll.\nIn each big roll (multi-colored horizontal line), each color represents small roll to be cut from it.\nIf the big roll ends with a black color, that part of the big roll is unused width.\n\nTODO: Assign each child roll a unique color\n'''\ndef drawGraph(consumed_big_rolls, child_rolls, parent_width):\n    import matplotlib.pyplot as plt\n    import matplotlib.patches as patches\n\n    # TODO: to add support for multiple different parent rolls, update here\n    xSize = parent_width # width of big roll\n    ySize = 10 * len(consumed_big_rolls) # one big roll will take 10 units vertical space\n\n    # draw rectangle\n    fig,ax = plt.subplots(1)\n    plt.xlim(0, xSize)\n    plt.ylim(0, ySize)\n    plt.gca().set_aspect('equal', adjustable='box')\n    \n    # print coords\n    coords = []\n    colors = ['r', 'g', 'b', 'y', 'brown', 'violet', 'pink', 'gray', 'orange', 'b', 'y']\n    colorDict = {}\n    i = 0\n    for quantity, width in child_rolls:\n      colorDict[width] = colors[i % 11]\n      i+= 1\n\n    # start plotting each big roll horizontly, from the bottom\n    y1 = 0\n    for i, big_roll in enumerate(consumed_big_rolls):\n      '''\n        big_roll = [leftover_width, [small_roll_1_1, small_roll_1_2, other_small_roll_2_1]]\n      '''\n      unused_width = big_roll[0]\n      small_rolls = big_roll[1]\n\n      x1 = 0\n      x2 = 0\n      y2 = y1 + 8 # the height of each big roll will be 8 \n      for j, small_roll in enumerate(small_rolls):\n        x2 = x2 + small_roll\n        print(f\"{x1}, {y1} -> {x2}, {y2}\")\n        width = abs(x1-x2)\n        height = abs(y1-y2)\n        # print(f\"Rect#{idx}: {width}x{height}\")\n        # Create a Rectangle patch\n        rect_shape = patches.Rectangle((x1,y1), width, height, facecolor=colorDict[small_roll], label=f'{small_roll}')\n        ax.add_patch(rect_shape) # Add the patch to the Axes\n        x1 = x2 # x1 for next small roll in same big roll will be x2 of current roll \n\n      # now that all small rolls have been plotted, check if a there is unused width in this big roll\n      # set the unused width at the end as black colored rectangle\n      if unused_width > 0:\n        width = unused_width\n        rect_shape = patches.Rectangle((x1,y1), width, height, facecolor='black', label='Unused')\n        ax.add_patch(rect_shape) # Add the patch to the Axes\n\n      y1 += 10 # next big roll will be plotted on top of current, a roll height is 8, so 2 will be margin between rolls\n\n    plt.show()\n\n\nif __name__ == '__main__':\n\n  child_rolls = [\n    # [quantity, width],\n    # [6, 25],\n    # [12, 21],\n    # [7, 26],\n    # [3, 23],\n    # [8, 33],\n    # [2, 15],\n    # [2, 34],\n\n    # [3, 3],\n    # [3, 4],\n\n    [3,3],\n    [3,1],\n    [2,4],\n    [2,2]\n\n    # [3,30],\n    # [2,72],\n    # [5,50]\n  ]\n\n  # child_rolls = gen_data(3)\n  # parent_rolls = [[10, 120]]\n  # parent_rolls = [[10, 8]]\n  parent_rolls = [[10, 6]]\n  # parent_rolls = [[10, 144]]\n\n\n  consumed_big_rolls = StockCutter1D(child_rolls, parent_rolls, output_json=False, large_model=False)\n  print (consumed_big_rolls)\n\n  for idx, roll in enumerate(consumed_big_rolls):\n    print(f'Roll #{idx}:', roll)\n\n\n  drawGraph(consumed_big_rolls, child_rolls, parent_width=parent_rolls[0][1])"
  },
  {
    "path": "infile.txt",
    "content": "58\t58\t38\n58\t58\t47.25\n58\t58\t47.25\n58\t58\t47\n58\t58\t22.75\n58\t58\t58.25\n58.25\t58.25\t58.25\n58.25\t58.25\t58.5\n58\t58\t71\n58\t58\t28.5\n\t\t\n\t\t\n\t\t\n34.5\t34.5\t64.5\n16.5\t16.5\t70\n46.5\t46.5\t47\n23\t23\t75"
  },
  {
    "path": "tests/basic_test.py",
    "content": "import pytest\nfrom csp.read_lengths import get_data\n\ndef test_get_data():\n    infile = \"infile.txt\"\n    nrs = get_data(infile)\n    print(nrs)\n    assert nrs[0][1] == 38\n"
  }
]