Full Code of emadehsan/csp for AI

master ece925f81e3a cached
26 files
121.5 KB
32.9k tokens
55 symbols
1 requests
Download .txt
Repository: emadehsan/csp
Branch: master
Commit: ece925f81e3a
Files: 26
Total size: 121.5 KB

Directory structure:
gitextract_vyf0nlkm/

├── .gitignore
├── LICENSE
├── Pipfile
├── README.md
├── csp/
│   ├── __init__.py
│   ├── read_lengths.py
│   └── stock_cutter_1d.py
├── deployment/
│   ├── .editorconfig
│   ├── .gitignore
│   ├── Procfile
│   ├── csp.py
│   ├── frontend/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── babel.config.js
│   │   ├── package.json
│   │   ├── public/
│   │   │   └── index.html
│   │   └── src/
│   │       ├── App.vue
│   │       ├── assets/
│   │       │   └── style.scss
│   │       ├── components/
│   │       │   └── CspTool.vue
│   │       └── main.js
│   ├── requirements.txt
│   ├── server.py
│   ├── stock_cutter.py
│   └── stock_cutter_1d.py
├── infile.txt
└── tests/
    └── basic_test.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
__pycache__
.idea

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Emad Ehsan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Pipfile
================================================
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
ortools = "*"
pytest = "*"
matplotlib = "*"
typer = "*"

[dev-packages]

[requires]
python_version = "3.9"


================================================
FILE: README.md
================================================
# Cutting Stock Problem
Cutting Stock Problem (CSP) deals with planning the cutting of items (rods / sheets) from given stock items (which are usually of fixed size).

## New to Cutting Stock Problem? Understand Visually
<a href="https://www.youtube.com/watch?v=4WXtfO9JB20">
	<img src="./github/video-thumb.jpg" alt="Video Tutorial on Cutting Stock Problem">
</a>


This implementation of CSP tries to answer
> How to minimize number of stock items used while cutting customer order


while doing so, it also caters
> How to cut the stock for customer orders so that waste is minimum


The OR Tools also helps us in calculating the number of possible solutions for your problem. So in addition, we can also compute
> In how many ways can we cut given order from fixed size Stock?


## Quick Usage
This is how CSP Tools looks in action. Click [CSP Tool](https://emadehsan.com/csp/) to use it
<a href="https://emadehsan.com/csp/">
	<img src="./github/CSP-Tool.PNG" alt="CSP Tool">
</a>

## Libraries
* [Google OR-Tools](https://developers.google.com/optimization)

## Quick Start
Install [Pipenv](https://pipenv.pypa.io/en/latest/), if not already installed
```sh
$ pip3 install --user pipenv
```

Clone this project and install packages
```sh
$ git clone https://github.com/emadehsan/csp
$ cd csp
$ pipenv install

# activate env
$ pipenv shell
```

## Run
If 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`.
```sh
(csp) $ python csp/stock_cutter_1d.py
```

Output:

```sh
numRollsUsed 5
Status: OPTIMAL
Roll #0: [0.0, [33, 33, 18, 18, 18]]
Roll #1: [2.9999999999999925, [33, 30, 18, 18, 18]]
Roll #2: [5.999999999999993, [30, 30, 18, 18, 18]]
Roll #3: [2.9999999999999987, [33, 33, 33, 18]]
Roll #4: [21.0, [33, 33, 33]]```
```

![Graph of Output](./github/graph-1d-b.PNG)


### Using input file
If you want to describe your inputs in a file, [infile.txt](./infile.txt) describes the expected format

```sh
(csp) $ python3 csp/stock_cutter_1d.py infile.txt
```


## Thinks to keep in mind
* 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).
* 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.


## CSP 2D
Code 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.

## Resources
The whole code for this project is taken from Serge Kruk's
* [Practical Python AI Projects: Mathematical Models of Optimization Problems with Google OR-Tools](https://amzn.to/3iPceJD)
* [Repository of the code in Serge's book](https://github.com/sgkruk/Apress-AI/)


================================================
FILE: csp/__init__.py
================================================


================================================
FILE: csp/read_lengths.py
================================================
import pathlib
from typing import List
import re
from math import ceil
def get_data(infile:str)->List[float]:
    """ Reads a file of numbers and returns a list of (count, number) pairs."""
    _p = pathlib.Path(infile)
    input_text = _p.read_text()
    numbers = [ceil(float(n)) for n in re.findall(r'[0-9.]+', _p.read_text())]
    quan = []
    nr = []
    for n in numbers:
        if n not in nr and n != 0:
            quan.append(numbers.count(n))
            nr.append(n)
    return list(zip(quan,nr))



================================================
FILE: csp/stock_cutter_1d.py
================================================
'''
Original Author: Serge Kruk
Original Version: https://github.com/sgkruk/Apress-AI/blob/master/cutting_stock.py

Updated by: Emad Ehsan
'''
from ortools.linear_solver import pywraplp
from math import ceil
from random import randint
import json
from read_lengths import get_data
import typer
from typing import Optional

def newSolver(name,integer=False):
  return pywraplp.Solver(name,\
                         pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING \
                         if integer else \
                         pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)

'''
return a printable value
'''
def SolVal(x):
  if type(x) is not list:
    return 0 if x is None \
      else x if isinstance(x,(int,float)) \
           else x.SolutionValue() if x.Integer() is False \
                else int(x.SolutionValue())
  elif type(x) is list:
    return [SolVal(e) for e in x]

def ObjVal(x):
  return x.Objective().Value()


def gen_data(num_orders):
    R=[] # small rolls
    # S=0 # seed?
    for i in range(num_orders):
        R.append([randint(1,12), randint(5,40)])
    return R


def solve_model(demands, parent_width=100):
  '''
      demands = [
          [1, 3], # [quantity, width]
          [3, 5],
          ...
      ]

      parent_width = integer
  '''
  num_orders = len(demands)
  solver = newSolver('Cutting Stock', True)
  k,b  = bounds(demands, parent_width)

  # array of boolean declared as int, if y[i] is 1, 
  # then y[i] Big roll is used, else it was not used
  y = [ solver.IntVar(0, 1, f'y_{i}') for i in range(k[1]) ] 

  # x[i][j] = 3 means that small-roll width specified by i-th order
  # must be cut from j-th order, 3 tmies 
  x = [[solver.IntVar(0, b[i], f'x_{i}_{j}') for j in range(k[1])] \
      for i in range(num_orders)]
  
  unused_widths = [ solver.NumVar(0, parent_width, f'w_{j}') \
      for j in range(k[1]) ] 
  
  # will contain the number of big rolls used
  nb = solver.IntVar(k[0], k[1], 'nb')

  # consntraint: demand fullfilment
  for i in range(num_orders):  
    # small rolls from i-th order must be at least as many in quantity
    # as specified by the i-th order
    solver.Add(sum(x[i][j] for j in range(k[1])) >= demands[i][0]) 

  # constraint: max size limit
  for j in range(k[1]):
    # total width of small rolls cut from j-th big roll, 
    # must not exceed big rolls width
    solver.Add( \
        sum(demands[i][1]*x[i][j] for i in range(num_orders)) \
        <= parent_width*y[j] \
      ) 

    # width of j-th big roll - total width of all orders cut from j-th roll
    # must be equal to unused_widths[j]
    # So, we are saying that assign unused_widths[j] the remaining width of j'th big roll
    solver.Add(parent_width*y[j] - sum(demands[i][1]*x[i][j] for i in range(num_orders)) == unused_widths[j])

    '''
    Book Author's note from page 201:
    [the following constraint]  breaks the symmetry of multiple solutions that are equivalent 
    for our purposes: any permutation of the rolls. These permutations, and there are K! of 
    them, cause most solvers to spend an exorbitant time solving. With this constraint, we 
    tell the solver to prefer those permutations with more cuts in roll j than in roll j + 1. 
    The reader is encouraged to solve a medium-sized problem with and without this 
    symmetry-breaking constraint. I have seen problems take 48 hours to solve without the 
    constraint and 48 minutes with. Of course, for problems that are solved in seconds, the 
    constraint will not help; it may even hinder. But who cares if a cutting stock instance 
    solves in two or in three seconds? We care much more about the difference between two 
    minutes and three hours, which is what this constraint is meant to address
    '''
    if j < k[1]-1: # k1 = total big rolls
      # total small rolls of i-th order cut from j-th big roll must be >=
      # totall small rolls of i-th order cut from j+1-th big roll
      solver.Add(sum(x[i][j] for i in range(num_orders)) >= sum(x[i][j+1] for i in range(num_orders)))

  # find & assign to nb, the number of big rolls used
  solver.Add(nb == solver.Sum(y[j] for j in range(k[1])))

  ''' 
    minimize total big rolls used
    let's say we have y = [1, 0, 1]
    here, total big rolls used are 2. 0-th and 2nd. 1st one is not used. So we want our model to use the 
    earlier rolls first. i.e. y = [1, 1, 0]. 
    The trick to do this is to define the cost of using each next roll to be higher. So the model would be
    forced to used the initial rolls, when available, instead of the next rolls.

    So instead of Minimize ( Sum of y ) or Minimize( Sum([1,1,0]) )
    we Minimize( Sum([1*1, 1*2, 1*3]) )
  ''' 

  '''
  Book Author's note from page 201:

  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
  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
  '''

  Cost = solver.Sum((j+1)*y[j] for j in range(k[1]))

  solver.Minimize(Cost)

  status = solver.Solve()
  numRollsUsed = SolVal(nb)

  return status, \
    numRollsUsed, \
    rolls(numRollsUsed, SolVal(x), SolVal(unused_widths), demands), \
    SolVal(unused_widths), \
    solver.WallTime()

def bounds(demands, parent_width=100):
  '''
  b = [sum of widths of individual small rolls of each order]
  T = local var. stores sum of widths of adjecent small-rolls. When the width reaches 100%, T is set to 0 again.
  k = [k0, k1], k0 = minimum big-rolls requierd, k1: number of big rolls that can be consumed / cut from
  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
  '''
  num_orders = len(demands)
  b = []
  T = 0
  k = [0,1]
  TT = 0

  for i in range(num_orders):
    # q = quantity, w = width; of i-th order
    quantity, width = demands[i][0], demands[i][1]
    # TODO Verify: why min of quantity, parent_width/width?
    # assumes widths to be entered as percentage
    # 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)
    # b.append( min(demands[i][0], int(round(parent_width / demands[i][1]))) )
    b.append( min(quantity, int(round(parent_width / width))) )

    # if total width of this i-th order + previous order's leftover (T) is less than parent_width
    # it's fine. Cut it.
    if T + quantity*width <= parent_width:
      T, TT = T + quantity*width, TT + quantity*width
    # else, the width exceeds, so we have to cut only as much as we can cut from parent_width width of the big roll
    else:
      while quantity:
        if T + width <= parent_width:
          T, TT, quantity = T + width, TT + width, quantity-1
        else:
          k[1],T = k[1]+1, 0 # use next roll (k[1] += 1)
  k[0] = int(round(TT/parent_width+0.5))

  print('k', k)
  print('b', b)

  return k, b

'''
  nb: array of number of rolls to cut, of each order
  
  w: 
  demands: [
    [quantity, width],
    [quantity, width],
    [quantity, width],
  ]
'''
def rolls(nb, x, w, demands):
  consumed_big_rolls = []
  num_orders = len(x) 
  # go over first row (1st order)
  # this row contains the list of all the big rolls available, and if this 1st (0-th) order
  # is cut from any big roll, that big roll's index would contain a number > 0
  for j in range(len(x[0])):
    # w[j]: width of j-th big roll 
    # 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 
    RR = [ abs(w[j])] + [ int(x[i][j])*[demands[i][1]] for i in range(num_orders) \
                    if x[i][j] > 0 ] # if i-th order has some cuts from j-th order, x[i][j] would be > 0
    consumed_big_rolls.append(RR)

  return consumed_big_rolls



'''
this model starts with some patterns and then optimizes those patterns
'''
def solve_large_model(demands, parent_width=100):
  num_orders = len(demands)
  iter = 0
  patterns = get_initial_patterns(demands)
  # print('method#solve_large_model, patterns', patterns)

  # list quantities of orders
  quantities = [demands[i][0] for i in range(num_orders)]
  print('quantities', quantities)

  while iter < 20:
    status, y, l = solve_master(patterns, quantities, parent_width=parent_width)
    iter += 1

    # list widths of orders
    widths = [demands[i][1] for i in range(num_orders)]
    new_pattern, objectiveValue = get_new_pattern(l, widths, parent_width=parent_width)

    # print('method#solve_large_model, new_pattern', new_pattern)
    # print('method#solve_large_model, objectiveValue', objectiveValue)

    for i in range(num_orders):
      # add i-th cut of new pattern to i-thp pattern
      patterns[i].append(new_pattern[i])

  status, y, l = solve_master(patterns, quantities, parent_width=parent_width, integer=True)  

  return status, \
          patterns, \
          y, \
          rolls_patterns(patterns, y, demands, parent_width=parent_width)


'''
Dantzig-Wolfe decomposition splits the problem into a Master Problem MP and a sub-problem SP.

The Master Problem: provided a set of patterns, find the best combination satisfying the demand

C: patterns
b: demand
'''
def solve_master(patterns, quantities, parent_width=100, integer=False):
  title = 'Cutting stock master problem'
  num_patterns = len(patterns)
  n = len(patterns[0])
  # print('**num_patterns x n: ', num_patterns, 'x', n)
  # print('**patterns recived:')
  # for p in patterns:
  #   print(p)

  constraints = []

  solver = newSolver(title, integer)

  # y is not boolean, it's an integer now (as compared to y in approach used by solve_model)
  y = [ solver.IntVar(0, 1000, '') for j in range(n) ] # right bound?
  # minimize total big rolls (y) used
  Cost = sum(y[j] for j in range(n)) 
  solver.Minimize(Cost)

  # for every pattern
  for i in range(num_patterns):
    # add constraint that this pattern (demand) must be met
    # there are m such constraints, for each pattern
    constraints.append(solver.Add( sum(patterns[i][j]*y[j] for j in range(n)) >= quantities[i]) ) 

  status = solver.Solve()
  y = [int(ceil(e.SolutionValue())) for e in y]

  l =  [0 if integer else constraints[i].DualValue() for i in range(num_patterns)]
  # sl =  [0 if integer else constraints[i].name() for i in range(num_patterns)]
  # print('sl: ', sl)

  # l =  [0 if integer else u[i].Ub() for i in range(m)]
  toreturn = status, y, l
  # l_to_print = [round(dd, 2) for dd in toreturn[2]]
  # print('l: ', len(l_to_print), '->', l_to_print)
  # print('l: ', toreturn[2])
  return toreturn

def get_new_pattern(l, w, parent_width=100):
  solver = newSolver('Cutting stock sub-problem', True)
  n = len(l)
  new_pattern = [ solver.IntVar(0, parent_width, '') for i in range(n) ]

  # maximizes the sum of the values times the number of occurrence of that roll in a pattern
  Cost = sum( l[i] * new_pattern[i] for i in range(n))
  solver.Maximize(Cost)

  # ensuring that the pattern stays within the total width of the large roll 
  solver.Add( sum( w[i] * new_pattern[i] for i in range(n)) <= parent_width ) 

  status = solver.Solve()
  return SolVal(new_pattern), ObjVal(solver)


'''
the initial patterns must be such that they will allow a feasible solution, 
one that satisfies all demands. 
Considering the already complex model, let’s keep it simple. 
Our initial patterns have exactly one roll per pattern, as obviously feasible as inefficient.
'''
def get_initial_patterns(demands):
  num_orders = len(demands)
  return [[0 if j != i else 1 for j in range(num_orders)]\
          for i in range(num_orders)]

def rolls_patterns(patterns, y, demands, parent_width=100):
  R, m, n = [], len(patterns), len(y)

  for j in range(n):
    for _ in range(y[j]):
      RR = []
      for i in range(m):
        if patterns[i][j] > 0:
          RR.extend( [demands[i][1]] * int(patterns[i][j]) )
      used_width = sum(RR)
      R.append([parent_width - used_width, RR])

  return R


'''
checks if all small roll widths (demands) smaller than parent roll's width
'''
def checkWidths(demands, parent_width):
  for quantity, width in demands:
    if width > parent_width:
      print(f'Small roll width {width} is greater than parent rolls width {parent_width}. Exiting')
      return False
  return True


'''
    params
        child_rolls: 
            list of lists, each containing quantity & width of rod / roll to be cut
            e.g.: [ [quantity, width], [quantity, width], ...]
        parent_rolls: 
            list of lists, each containing quantity & width of rod / roll to cut from
            e.g.: [ [quantity, width], [quantity, width], ...]
'''
def StockCutter1D(child_rolls, parent_rolls, output_json=True, large_model=True):

  # at the moment, only parent one width of parent rolls is supported
  # quantity of parent rolls is calculated by algorithm, so user supplied quantity doesn't matter?
  # TODO: or we can check and tell the user the user when parent roll quantity is insufficient
  parent_width = parent_rolls[0][1]

  if not checkWidths(demands=child_rolls, parent_width=parent_width):
    return []


  print('child_rolls', child_rolls)
  print('parent_rolls', parent_rolls)

  if not large_model:
    print('Running Small Model...')
    status, numRollsUsed, consumed_big_rolls, unused_roll_widths, wall_time = \
              solve_model(demands=child_rolls, parent_width=parent_width)

    # convert the format of output of solve_model to be exactly same as solve_large_model
    print('consumed_big_rolls before adjustment: ', consumed_big_rolls)
    new_consumed_big_rolls = []
    for big_roll in consumed_big_rolls:
      if len(big_roll) < 2:
        # sometimes the solve_model return a solution that contanis an extra [0.0] entry for big roll
        consumed_big_rolls.remove(big_roll)
        continue
      unused_width = big_roll[0]
      subrolls = []
      for subitem in big_roll[1:]:
        if isinstance(subitem, list):
          # if it's a list, concatenate with the other lists, to make a single list for this big_roll
          subrolls = subrolls + subitem
        else:
          # if it's an integer, add it to the list
          subrolls.append(subitem)
      new_consumed_big_rolls.append([unused_width, subrolls])
    print('consumed_big_rolls after adjustment: ', new_consumed_big_rolls)
    consumed_big_rolls = new_consumed_big_rolls
  
  else:
    print('Running Large Model...');
    status, A, y, consumed_big_rolls = solve_large_model(demands=child_rolls, parent_width=parent_width)

  numRollsUsed = len(consumed_big_rolls)
  # print('A:', A, '\n')
  # print('y:', y, '\n')


  STATUS_NAME = ['OPTIMAL',
    'FEASIBLE',
    'INFEASIBLE',
    'UNBOUNDED',
    'ABNORMAL',
    'NOT_SOLVED'
    ]

  output = {
      "statusName": STATUS_NAME[status],
      "numSolutions": '1',
      "numUniqueSolutions": '1',
      "numRollsUsed": numRollsUsed,
      "solutions": consumed_big_rolls # unique solutions
  }


  # print('Wall Time:', wall_time)
  print('numRollsUsed', numRollsUsed)
  print('Status:', output['statusName'])
  print('Solutions found :', output['numSolutions'])
  print('Unique solutions: ', output['numUniqueSolutions'])

  if output_json:
    return json.dumps(output)        
  else:
    return consumed_big_rolls


'''
Draws the big rolls on the graph. Each horizontal colored line represents one big roll.
In each big roll (multi-colored horizontal line), each color represents small roll to be cut from it.
If the big roll ends with a black color, that part of the big roll is unused width.

TODO: Assign each child roll a unique color
'''
def drawGraph(consumed_big_rolls, child_rolls, parent_width):
    import matplotlib.pyplot as plt
    import matplotlib.patches as patches

    # TODO: to add support for multiple different parent rolls, update here
    xSize = parent_width # width of big roll
    ySize = 10 * len(consumed_big_rolls) # one big roll will take 10 units vertical space

    # draw rectangle
    fig,ax = plt.subplots(1)
    plt.xlim(0, xSize)
    plt.ylim(0, ySize)
    plt.gca().set_aspect('equal', adjustable='box')
    
    # print coords
    coords = []
    colors = ['r', 'g', 'b', 'y', 'brown', 'violet', 'pink', 'gray', 'orange', 'b', 'y']
    colorDict = {}
    i = 0
    for quantity, width in child_rolls:
      colorDict[width] = colors[i % 11]
      i+= 1

    # start plotting each big roll horizontly, from the bottom
    y1 = 0
    for i, big_roll in enumerate(consumed_big_rolls):
      '''
        big_roll = [leftover_width, [small_roll_1_1, small_roll_1_2, other_small_roll_2_1]]
      '''
      unused_width = big_roll[0]
      small_rolls = big_roll[1]

      x1 = 0
      x2 = 0
      y2 = y1 + 8 # the height of each big roll will be 8 
      for j, small_roll in enumerate(small_rolls):
        x2 = x2 + small_roll
        print(f"{x1}, {y1} -> {x2}, {y2}")
        width = abs(x1-x2)
        height = abs(y1-y2)
        # print(f"Rect#{idx}: {width}x{height}")
        # Create a Rectangle patch
        rect_shape = patches.Rectangle((x1,y1), width, height, facecolor=colorDict[small_roll], label=f'{small_roll}')
        ax.add_patch(rect_shape) # Add the patch to the Axes
        x1 = x2 # x1 for next small roll in same big roll will be x2 of current roll 

      # now that all small rolls have been plotted, check if a there is unused width in this big roll
      # set the unused width at the end as black colored rectangle
      if unused_width > 0:
        width = unused_width
        rect_shape = patches.Rectangle((x1,y1), width, height, facecolor='black', label='Unused')
        ax.add_patch(rect_shape) # Add the patch to the Axes

      y1 += 10 # next big roll will be plotted on top of current, a roll height is 8, so 2 will be margin between rolls

    plt.show()


if __name__ == '__main__':

  # child_rolls = [
  #    [quantity, width],
  # ]
  app = typer.Typer()


  def main(infile_name: Optional[str] = typer.Argument(None)):

    if infile_name:
      child_rolls = get_data(infile_name)
    else:
      child_rolls = gen_data(3)
    parent_rolls = [[10, 120]] # 10 doesn't matter, itls not used at the moment

    consumed_big_rolls = StockCutter1D(child_rolls, parent_rolls, output_json=False, large_model=False)
    typer.echo(f"{consumed_big_rolls}")


    for idx, roll in enumerate(consumed_big_rolls):
      typer.echo(f"Roll #{idx}:{roll}")

    drawGraph(consumed_big_rolls, child_rolls, parent_width=parent_rolls[0][1])

if __name__ == "__main__":
  typer.run(main)


================================================
FILE: deployment/.editorconfig
================================================
# Editor configuration, see http://editorconfig.org
# source: https://stackoverflow.com/a/51398290/3578289
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
max_line_length = off
trim_trailing_whitespace = false


================================================
FILE: deployment/.gitignore
================================================
node_modules/
__pycache__
server/__pycache__/
*.csv

.DS_Store
frontend/dist/

# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*


================================================
FILE: deployment/Procfile
================================================
web: gunicorn server:app

================================================
FILE: deployment/csp.py
================================================
from __future__ import print_function
import collections, json
from ortools.sat.python import cp_model

# to draw rectangles
import matplotlib.pyplot as plt
import matplotlib.patches as patches

"""
    Cutting Stock problem
    params
        child_rects: 
            lists of multiple rectangles' coords
            e.g.: [ [w, h], [w, h], ...]
        parent_rects: rectangle coords
            lists of multiple rectangles' coords
            e.g.: [ [w, h], [w, h], ...]
"""
def StockCutter(child_rects, parent_rects):
    
    # Create the model
    model = cp_model.CpModel()

    # parent rect (to cut from). horizon = [ width, height ] of parent sheet
    # for now, parent rectangle is just one
    # TODO: to add functionality of cutting from multiple parent sheets, start here:
    horizon = parent_rects[0] 

    # Named Tuple to store information about created variables
    sheet_type = collections.namedtuple('sheet_type', 'x1 y1 x2 y2 x_interval y_interval')

    # Store for all model variables
    all_vars = {}

    # sum of to save area of all small rects, to cut from parent rect
    total_child_area = 0 

    # hold the widths (x) and heights (y) interval vars of each sheet
    x_intervals = []
    y_intervals = []

    # create model vars and intervals
    for rect_id, rect in enumerate(child_rects):
        width = rect[0]
        height = rect[1]
        area = width * height
        total_child_area += area
        # print(f"Rect: {width}x{height}, Area: {area}")

        suffix = '_%i_%i' % (width, height)

        # interval to represent width. max value can be the width of parent rect
        x1_var = model.NewIntVar(0, horizon[0], 'x1' + suffix)
        x2_var = model.NewIntVar(0, horizon[0], 'x2' + suffix)
        x_interval_var = model.NewIntervalVar(x1_var, width, x2_var, 'x_interval' + suffix)

        # interval to represent height. max value can be the height of parent rect
        y1_var = model.NewIntVar(0, horizon[1], 'y1' + suffix)
        y2_var = model.NewIntVar(0, horizon[1], 'y2' + suffix)
        y_interval_var = model.NewIntervalVar(y1_var, height, y2_var, 'y_interval' + suffix)
        
        x_intervals.append(x_interval_var)
        y_intervals.append(y_interval_var)

        # store the variables for later use
        all_vars[rect_id] = sheet_type(
            x1=x1_var, 
            y1=y1_var, 
            x2=x2_var, 
            y2=y2_var, 
            x_interval=x_interval_var,
            y_interval=y_interval_var
        )

    # add constraint: no over lap of rectangles allowed
    model.AddNoOverlap2D(x_intervals, y_intervals)

    # Solve model
    solver = cp_model.CpSolver()

    solution_printer = VarArraySolutionPrinter(all_vars)

    # Search for all solutions is only defined on satisfiability problems
    status = solver.SearchForAllSolutions(model, solution_printer) # use for satisfiability problem
    # status = solver.Solve(model) # use for Optimization Problem

    print('Status:', solver.StatusName(status))
    print('Solutions found :', solution_printer.solution_count())

    solutions = solution_printer.get_unique_solutions()    

    # call draw methods here, if want to draw with matplotlib

    int_solutions = solutions_to_int(solutions)

    statusName = solver.StatusName(status)
    numSolutions = solution_printer.solution_count()
    numUniqueSolutions = len(solutions)

    output = {
        "statusName": statusName,
        "numSolutions": numSolutions,
        "numUniqueSolutions": numUniqueSolutions,
        "solutions": int_solutions # unique solutions
    }

    # return json.dumps(output)

    # draw
    for idx, sol in enumerate(solutions):
        # sol is string of coordinates of all rectangles in this solution
        # format: x1,y1,x2,y2-x1,y1,x2,y2
        print('Sol#', idx)
        rect_strs = sol.split('-')
        rect_coords = [
            #    [x1,y1,x2,y2],
            #    [x1,y1,x2,y2],
        ]
        for rect_str in rect_strs:
            coords_str = rect_str.split(',')
            coords = [int(c) for c in coords_str]
            rect_coords.append(coords)
        print('rect_coords')
        # print(rect_coords)
        drawRectsFromCoords(rect_coords)

def drawRectsFromCoords(rect_coords):
    # draw rectangle
    fig,ax = plt.subplots(1)
    plt.xlim(0,6) # todo 7
    plt.ylim(0,6)
    plt.gca().set_aspect('equal', adjustable='box')
    
    # print coords
    coords = []
    colors = ['r', 'g', 'b', 'y', 'brown', 'black', 'violet', 'pink', 'gray', 'orange', 'b', 'y']
    for idx, coords in enumerate(rect_coords):
        x1=coords[0]
        y1=coords[1]
        x2=coords[2]
        y2=coords[3]
        # print(f"{x1}, {y1} -> {x2}, {y2}")

        width = abs(x1-x2)
        height = abs(y1-y2)
        # print(f"Rect#{idx}: {width}x{height}")

        # Create a Rectangle patch
        rect_shape = patches.Rectangle((x1,y1), width, height,facecolor=colors[idx])
        # Add the patch to the Axes
        ax.add_patch(rect_shape)
    plt.show()





"""
    TODO complete this, add to git
    params:
        str_solutions: list of strings. 1 string contains is solution
"""
def solutions_to_int(str_solutions):

    # list of solutions, each solution is a list of rectangle coords that look like [x1,y1,x2,y2]
    int_solutions = []

    # go over all solutions and convert them to int>list>json
    for idx, sol in enumerate(str_solutions):
        # sol is string of coordinates of all rectangles in this solution
        # format: x1,y1,x2,y2-x1,y1,x2,y2
    
        rect_strs = sol.split('-')
        rect_coords = [
            # [x1,y1,x2,y2],
            # [x1,y1,x2,y2],
            # ...
        ]

        # convert each rectangle's coords to int
        for rect_str in rect_strs:
            coords_str = rect_str.split(',')
            coords = [int(c) for c in coords_str]
            rect_coords.append(coords)

        # print('rect_coords', rect_coords)

        # call draw methods here, if want to draw individual solutions with matplotlib

        int_solutions.append(rect_coords)

    return int_solutions


"""
    To get all the solutions of the problem, as they come up. 
    https://developers.google.com/optimization/cp/cp_solver#all_solutions

    The solutions are all unique. But for the child rectangles that have same dimensions, 
    some solution will be repetitive. Because for the algorithm, they are different solutions, 
    but because of same size, they are merely permutations of the similar child rectangles - 
    having other rectangles' positions fixed.

    We want to remove repetitive extra solutions. One way to do this is
        1. Stringify every rectangle coords in a solution
            (1,2)->(2,3) becomes "1,2,2,3"

            # here the rectangles are stored as a string: "1,2,2,3" where x1=1, y1=2, x2=2, y2=3
        
        2. Put all these string coords into a sorted list. This sorting is important. 
            Because the rectangles (1,2)->(2,3) and (3,3)->(4,4) are actually same size (1x1) rectangles. 
            And they can appear in 1st solution as 
            [(1,2)->(2,3)   ,   (3,3)->(4,4)]
            and in the 2nd solution as
            [(3,3)->(4,4)   ,   (1,2)->(2,3)]

            but this sorted list of strings will ensure both solutions are represented as

            [..., "1,2,2,3", "3,3,4,4", ...]

        3. Join the Set of "strings (rectangles)" in to one big string seperated by '-'. For every solution.
            So in resulting big strings (solutions), we will have two similar strings (solutions) 
            that be similar and also contain

            "....1,2,2,3-3,3,4,4-...."

        4. Now add all these "strings (solutions)" into a Set. this adding to the set 
        will remove similar strings. And hence duplicate solutions will be removed.

"""
class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):

    def __init__(self, variables):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self.__variables = variables
        self.__solution_count = 0

        # hold the calculated solutions
        self.__solutions = []
        self.__unique_solutions = set()

    def on_solution_callback(self):
        self.__solution_count += 1
        # print('Sol#: ', self.__solution_count)

        # using list to hold the coordinate strings of rectangles
        rect_strs = []

        # extra coordinates of all rectangles for this solution 
        for rect_id in self.__variables:
            rect = self.__variables[rect_id]
            x1 = self.Value(rect.x1)
            x2 = self.Value(rect.x2)
            y1 = self.Value(rect.y1)
            y2 = self.Value(rect.y2)

            rect_str = f"{x1},{y1},{x2},{y2}"
            # print(rect_str)

            rect_strs.append(rect_str)
            # print(f'Rect #{rect_id}: {x1},{y1} -> {x2},{y2}')

        # print(rect_strs)

        # sort the rectangles
        rect_strs = sorted(rect_strs)

        # single solution as a string
        solution_str = '-'.join(rect_strs)
        # print(solution_str)

        # store the solutions
        self.__solutions.append(solution_str)
        self.__unique_solutions.add(solution_str) # __unique_solutions is a set, so duplicates will get removed


    def solution_count(self):
        return self.__solution_count

    # returns all solutions  
    def get_solutions(self):
        return self.__solutions

    """
    returns unique solutions
    returns the permutation free list of solution strings  
    """
    def get_unique_solutions(self):
        return list(self.__unique_solutions) # __unique_solutions is a Set, convert to list



# for testing
if __name__ == '__main__':

    child_rects = [
        # [2, 2],
        # [1, 3],
        # [4, 3],
        # [1, 1],
        # [2, 4],

        [3, 3],
        [3, 3],
        [3, 3],
        [3, 3],
    ]

    parent_rects = [[6,6]]

    StockCutter(child_rects, parent_rects)

================================================
FILE: deployment/frontend/.gitignore
================================================
.DS_Store
node_modules
/dist


# local env files
.env.local
.env.*.local

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: deployment/frontend/README.md
================================================
# frontend

## Project setup
```
npm install
```

### Compiles and hot-reloads for development
```
npm run serve
```

### Compiles and minifies for production
```
npm run build
```

### Lints and fixes files
```
npm run lint
```

### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).


================================================
FILE: deployment/frontend/babel.config.js
================================================
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ]
}


================================================
FILE: deployment/frontend/package.json
================================================
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "axios": "^0.21.1",
    "bootstrap": "^5.1.0",
    "bootstrap-vue": "^2.21.2",
    "core-js": "^3.6.5",
    "d3": "^7.0.1",
    "vue": "^3.2.6"
  },
  "devDependencies": {
    "@types/d3": "^7.0.0",
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^7.0.0"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/vue3-essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}


================================================
FILE: deployment/frontend/public/index.html
================================================
<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>


================================================
FILE: deployment/frontend/src/App.vue
================================================
<template>
  <CspTool msg="Let's Go"/>
</template>

<script>
import CspTool from './components/CspTool.vue'

export default {
  name: 'App',
  components: {
    CspTool
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>


================================================
FILE: deployment/frontend/src/assets/style.scss
================================================


body {
  /* font-size: 0.9em; */
  background-color: #ecf0f1;
}

.btn {
  padding: 2px 8px;
}

input {
  border: 0px solid #000;
  margin: 0;
  background: transparent;
  width: 100%;
}
table tr td {
  border-right: 1px solid #000;
  border-bottom: 1px solid #000;
}
table {
  background: #fff none repeat scroll 0 0;
  border-left: 1px solid #000;
  border-top: 1px solid #000;
}
table tr:nth-child(even) {
  background: #95a5a6;
}
table tr:nth-child(odd) {
  background: #bdc3c7;
}
.active {
  font-weight: bold;
}

.information {
  border-radius: 100%;
  background: #444;
  color: white;
  padding: 2px 5px;
  font-size: 80%;
  font-weight: bold;
}

================================================
FILE: deployment/frontend/src/components/CspTool.vue
================================================
<template>
  <span>
    <!-- As a heading -->
    <nav class="navbar navbar-light bg-light">
      <div class="container-fluid">
        <span class="navbar-brand mb-0 h1">Navbar</span>
      </div>
    </nav>

    <div class="container text-start">
      <!-- header / intro section -->
      <div class="row">
        <div class="col">
          <h1 class="text-center mb-0">Stock Cuts Planner</h1>

          <div class="row">
            <div class="col float-start">
              <a href="/cutting-stock-problem">Learn about Cuts Planner</a>
            </div>

            <div class="col-8">
              <h5 class="text-center">
                Plan how to cut your stock in a way to minimize waste
              </h5>
            </div>

            <div class="col float-end">
              <a
                class="float-end"
                href="https://github.com/emadehsan/csp"
                target="_blank"
                >Code</a
              >
            </div>
          </div>

          <ul class="nav nav-tabs">
            <li class="nav-item">
              <!-- hide the border around button onclick, using onmousedown -->
              <button
                class="nav-link active"
                id="1d-tab"
                v-on:click="setMode('1d')"
              >
                Rods, Rolls, 1-D Sheets
              </button>
            </li>

            <li class="nav-item">
              <button class="nav-link" id="2d-tab" v-on:click="setMode('2d')">
                Rectangular Sheets (2-D)
              </button>
            </li>
          </ul>
        </div>
      </div>

      <div class="row tab-content">
        <!-- input sections | left -->
        <div class="col-6">
          <!-- input small rects | left+top -->
          <div class="row">
            <div class="col">
              <h3 class="mt-2">{{ mode_data.childTitle }}</h3>
              <p class="text-secondary mt-0 mb-2">
                {{ mode_data.childMessage }}
              </p>

              <div class="row">
                <div class="col">
                  <!-- adds a row to Sheets to Cut -->
                  <button
                    class="my-1 btn btn-outline-success btn-sm float-start"
                    v-on:click="addRowToChilds"
                  >
                    <b>+ Add Another</b>
                  </button>
                </div>

                <div class="col">
                  <button
                    class="my-1 btn btn-outline-danger btn-sm float-end"
                    v-on:click="clearChildData"
                  >
                    <b>x Clear All</b>
                  </button>
                </div>
              </div>

              <p class="text-danger mb-1">{{ mode_data.childErrors }}</p>

              <table cellpadding="0" cellspacing="0" class="w-100 border-0">
                <thead>
                  <tr class="border">
                    <td class="px-1">#</td>
                    <td class="px-1">Width</td>

                    <!-- display height if mode is 2d -->
                    <td v-if="mode === '2d'" class="px-1">Height</td>

                    <td class="px-1">Quantity</td>
                    <!-- <td>Label</td> -->
                    <!-- <td>Color</td> -->
                    <td
                      v-if="mode === '1d' && mode_data.result"
                      class="px-3"
                    ></td>
                    <td class="px-1 border-0"></td>
                  </tr>
                </thead>
                <tbody>
                  <tr
                    class="border"
                    v-for="(child, index) in mode_data.childs"
                    v-bind:key="index"
                  >
                    <td class="px-1 text-secondary">
                      {{ index + 1 }}
                    </td>
                    <td>
                      <input class="px-1" type="text" v-model="child.width" />
                    </td>

                    <td v-if="mode === '2d'">
                      <input class="px-1" type="text" v-model="child.height" />
                    </td>

                    <td>
                      <input
                        class="px-1"
                        type="text"
                        v-model="child.quantity"
                      />
                    </td>

                    <td
                      v-if="mode === '1d' && mode_data.result"
                      class="px-1"
                      :style="getColor(child.width)"
                    ></td>

                    <td class="px-1 border-0">
                      <div
                        v-on:click="removeRow(index, false)"
                        class="btn btn-outline-danger btn-sm m-0 py-0 px-1"
                      >
                        x
                      </div>
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>

          <hr class="mb-2" />

          <!-- input sheets | left+bottom -->
          <div class="row">
            <div class="col">
              <h3 class="my-0">{{ mode_data.parentTitle }}</h3>
              <p class="text-secondary mb-2">{{ mode_data.parentMessage }}</p>

              <div class="row">
                <!-- <div class="col">
                                <button
                                    class="my-1 btn btn-outline-success btn-sm"
                                    v-on:click="addRowToParents">+ Add Another
                                </button>
                            </div> -->

                <div class="col">
                  <button
                    class="my-1 btn btn-outline-danger btn-sm float-end"
                    v-on:click="clearParentData"
                  >
                    <b>x Clear All</b>
                  </button>
                </div>
              </div>

              <p class="text-danger mb-1">{{ mode_data.parentErrors }}</p>

              <table cellpadding="0" cellspacing="0" class="w-100 border-0">
                <thead>
                  <tr class="border">
                    <td class="px-1">#</td>
                    <td class="px-1">Width</td>

                    <td v-if="mode === '2d'" class="px-1">Height</td>

                    <td class="px-1">Quantity</td>

                    <td class="px-1 border-0"></td>
                  </tr>
                </thead>
                <tbody>
                  <tr
                    class="border"
                    v-for="(parent, index) in mode_data.parents"
                    v-bind:key="index"
                  >
                    <td class="px-1 text-secondary">
                      {{ index + 1 }}
                    </td>
                    <td>
                      <input class="px-1" type="text" v-model="parent.width" />
                    </td>

                    <td v-if="mode === '2d'">
                      <input class="px-1" type="text" v-model="parent.height" />
                    </td>

                    <td>
                      <input
                        disabled="true"
                        class="px-1"
                        type="text"
                        v-model="parent.quantity"
                      />
                    </td>

                    <td class="px-1 border-0">
                      <div
                        v-on:click="removeRow(index, true)"
                        class="btn btn-outline-danger btn-sm m-0 py-0 px-1"
                      >
                        x
                      </div>
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>

          <!-- only one sheet allowed for now. so keep add more button hidden -->
          <!--
                <div class="row">
                    <div class="col">
                        <button class="mt-1" v-on:click="addRowToParents"><b>+ Add</b></button>
                    </div>
                </div>
                 -->
        </div>

        <!-- output+diagram section | right -->
        <div class="col-6">
          <div class="row mt-1">
            <div class="row form-check">
              <div class="col">
                <input
                  type="radio"
                  id="exactCutsRadio"
                  name="exactCutsRadio"
                  class="form-check-input"
                  value="exactCuts"
                  v-model="cutStyle"
                />

                <label class="custom-control-label" for="exactCutsRadio">
                  Exact Cuts

                  <span
                    class="information"
                    data-toggle="tooltip"
                    data-placement="top"
                    title="Cut the exact number of  items from stock, as specified"
                    >?</span
                  >
                </label>
              </div>

              <div class="col">
                <input
                  type="radio"
                  id="minWasteRadio"
                  name="minWasteRadio"
                  class="form-check-input"
                  value="minWaste"
                  v-model="cutStyle"
                />

                <label class="custom-control-label" for="minWasteRadio">
                  Minimize Waste

                  <span
                    class="information"
                    data-toggle="tooltip"
                    data-placement="top"
                    title="Cut some extra items than specified, to avoid wastage of leftover"
                    >?</span
                  >
                </label>
              </div>
            </div>
          </div>

          <div class="row">
            <div class="col">
              <button
                :disabled="cutButtonDisabled"
                class="my-2 btn btn-primary btn-sm"
                @click="cutSheets()"
              >
                <b>✓ Cut</b>
              </button>
            </div>

            <div class="col">
              <button
                :disabled="cutButtonDisabled"
                class="my-2 btn btn-outline-danger btn-sm float-end"
                @click="reset()"
              >
                <b>x Reset</b>
              </button>
            </div>
          </div>

          <h4 v-if="mode_data.result" class="text-capitalize">
            Solution: {{ mode_data.result.statusName }}
          </h4>

          <div id="d3_area">
            <svg class="w-100"></svg>
          </div>

          <span v-if="mode_data.result">
            <!-- <p>Total Solutions: {{ mode_data.result.numSolutions }}</p> -->
            <!-- <p>Helpful Solutions: {{ mode_data.result.numUniqueSolutions }}</p> -->

            <div v-if="mode === '1d'" class="row my-2">
              <div class="col">
                <h4>Cut Details</h4>

                <div class="row">
                  <div class="col">
                    <p class="m-0">
                      Stock required = {{ mode_data.result.solutions.length }}
                    </p>
                  </div>

                  <div class="col">
                    <button
                      v-on:click="downloadCsv()"
                      class="p-0 btn btn-link float-end"
                    >
                      Download CSV
                    </button>
                  </div>
                </div>

                <table cellpadding="0" cellspacing="0" class="w-100 border-0">
                  <thead>
                    <tr class="border">
                      <td class="px-1">Stock</td>
                      <td class="px-1">Usage</td>
                      <!-- <td class="px-1">Unused Width</td> -->
                      <td class="px-1">Width of Cuts</td>
                    </tr>
                  </thead>
                  <tbody>
                    <tr
                      class="border"
                      v-for="(bigRoll, index) in mode_data.result.solutions"
                      v-bind:key="index"
                    >
                      <td class="px-1 text-secondary">
                        {{ index + 1 }}
                      </td>

                      <td class="px-1">
                        {{ getPercentageUtilization(bigRoll[0]) }} %
                      </td>

                      <!-- 										<td class="px-1">
                                            {{ bigRoll[0] }}
                                        </td>

 -->
                      <td class="px-1">
                        <!-- join without space, so paste in excel would not change it to date -->
                        {{ bigRoll[1].join(",") }}
                      </td>
                    </tr>
                  </tbody>
                </table>

                <!--
                            <div class="row">
                                <h6 class="col-3">Stock</h6>
                                <h6 class="col-6">Cut Widths</h6>
                                <h6 class="col-3">Leftover</h6>
                            </div>
                            <div class="row" v-for="(bigRoll, index) in mode_data.result.solutions">
                                <div class="col-3">
                                    {{ index + 1}}
                                </div>

                                <div class="col-6">
                                    <span>
                                        {{ bigRoll[1].join(",   ") }}
                                    </span>
                                </div>

                                <div class="col-3">
                                    {{bigRoll[0]}}
                                </div>
                            </div> -->
              </div>
            </div>

            <!-- dimentions of rects of solutions -->
            <div v-if="mode === '2d'">
              <div
                v-for="(sol, index) in mode_data.result.solutions"
                v-bind:key="index"
              >
                <div v-for="(rect, index) in sol" v-bind:key="index">
                  {{ rect[0] }},{{ rect[1] }}-{{ rect[2] }},{{ rect[3] }}
                </div>
                <hr />
              </div>
            </div>
          </span>
        </div>
      </div>

      <hr />

      <footer class="footer mb-3">© {{ getCurrentYear() }}</footer>
    </div>
  </span>
</template>

<script>
import * as d3 from "d3";
import * as axios from "axios";

export default {
  name: "CspTool",
  props: {
    msg: String,
  },
  data: function () {
    return {
      // by default, 1d mode will open. But onclick 1d / 2d buttons, mode can be switched
      mode: "1d",

      message: "hello",

      /*
        it specifies how to cut?
        1. exactCuts: cut exact number of sheets as specified by user
        2. minWaste: cut more than specified number of times, the items specified. to minimize waste
        */
      cutStyle: "exactCuts",

      // remembers the state of Cut button
      cutButtonDisabled: false,

      mode1d: {
        childs: [
          { width: "", quantity: "" }, // 1d mode doesn't have height
        ],
        parents: [{ width: "", quantity: "Comming soon" }],
        childErrors: null,
        parentErrors: null,
        // data from server, to display
        result: null,

        // information displayed on each mode
        childTitle: "Small Sheets / Rolls",
        childMessage:
          "Details of small rods, rolls or sheets to cut from respective stock",
        parentTitle: "Stock",
        parentMessage:
          "The 1-D algorithm tells the number of stock sheets required",
      },

      mode2d: {
        childs: [{ width: "", height: "", quantity: "" }],
        parents: [{ width: "", height: "", quantity: "Comming soon" }],
        childErrors: null,
        parentErrors: null,
        // data from server, to display
        result: null,

        // information displayed on each mode
        childTitle: "Small Sheets",
        childMessage:
          "Details of small rectangular (▯) sheets to cut from big ▯ sheet",
        parentTitle: "Stock Sheets",
        parentMessage: "Only 1 stock sheet is allowed for now",
      },

      // currently displayed mode's data (mode1d or mode2d from above)
      mode_data: null,

      // from https://flatuicolors.com/palette/defo
      colors: [
        "#1abc9c", // Torquise
        "#16a085", // Green Sea
        "#f1c40f", // Sun Flower
        "#f39c12", // Orange
        "#2ecc71", // Emerald
        "#27ae60", // Nephritis
        "#e67e22", // Carrot
        "#d35400", // Pumpkin
        "#3498db", // Peter River
        "#2980b9", // Belize Hole
        "#e74c3c", // Alizarin
        "#c0392b", // Pomegranate
        "#9b59b6", // Amethyst
        "#8e44ad", // Wisteria
        "#ecf0f1", // Clouds
        // '#bdc3c7', // Silver
        // '#95a5a6', // Concrete <- Clouds & Silver are close
        // '#34495e', // West Asphalt <- don't use because it is very close to Midnight blue
        // '#2c3e50', // Midnight Blue <- use for wasted part
      ],

      wasteColor: "#7f8c8d", // Asbestos
    };
  },

  beforeMount() {
    // console.log('#beforeMount');

    // set mode before mount
    this.setMode("1d");
  },

  mounted() {
    this.hideButtonClickBorder();
  },

  methods: {
    /**
     * bootstrap buttons stay highlighted with a border after a click
     * this method hides that
     */
    hideButtonClickBorder: function () {
      let buttons = document.getElementsByTagName("button");
      for (let i = 0; i < buttons.length; i++) {
        buttons[i].addEventListener("mousedown", function (event) {
          return event.preventDefault();
        });
      }
    },

    /**
     * this method is called when the mode is switched between 1d and 2d
     * this method saves the data of currently displayed mode and displays the data
     * that was previously entered in the other mode because that mode just become the current.
     *
     * it also draws the results of that mode, if they were retrieved
     */
    setMode: function (newMode) {
      this.mode = newMode;

      if (newMode === "1d") {
        // hide 2d mode's options
        if (this.mode_data != null) this.mode2d = this.mode_data;

        this.mode_data = this.mode1d;

        // make 1D tab active
        this.removeActiveClass("2d-tab");
        this.addActiveClass("1d-tab");

        // if there is anything to draw, draw it
        this.draw1d();
      } else if (newMode === "2d") {
        // hide 1d mode's options
        if (this.mode_data != null) this.mode1d = this.mode_data;

        this.mode_data = this.mode2d;

        this.removeActiveClass("1d-tab");
        this.addActiveClass("2d-tab");

        this.draw2d();
      }

      console.log("Mode: ", this.mode);
      console.log("Mode_data: ", this.mode_data);
    },

    /**
        add active Bootstrap class to make the Tab look as selected
        */
    addActiveClass: function (id) {
      let element = document.getElementById(id);
      if (!element) return;

      element.classList.add("active");
    },

    removeActiveClass: function (id) {
      let element = document.getElementById(id);
      if (!element) return;

      element.classList.remove("active");
    },

    /**
     * checks if the number entered at current row & column is a valid digit
     * since input field has type text (for styling reason), this function enforces
     * that input is positive number
     */
    validNum: function (row, key, is_parent) {
      // is this from parent table?
      let item = null;
      if (is_parent) item = this.mode_data.parents[row][key];
      else item = this.mode_data.childs[row][key];

      let validChars = "";
      for (let i = 0; i < item.length; i++) {
        const c = item[i];

        if ("0123456789".includes(c)) {
          // c is a digit? in string type
          validChars += c;
        }
      }

      const num = validChars === "" ? 0 : parseInt(validChars);

      if (is_parent) this.mode_data.parents[row][key] = num;
      else this.mode_data.childs[row][key] = num;
    },

    addRowToChilds: function () {
      this.mode_data.childs.push(["", "", ""]); // add an empty row
    },

    addRowToParents: function () {
      this.mode_data.parents.push(["", "", ""]); // add an empty row
    },

    hideResult: function () {
      this.mode_data.result = null;
    },

    /**
     * called when the Cut button is pressed.
     * Validates the inputs, show errors
     * Send request to the server if input is valid
     */
    cutSheets: function () {
      console.log(`Cut Style: ${this.cutStyle}`);

      // hide the result from previous request
      this.hideResult();
      // clear the drawing
      this.clearTheDrawing();

      // TODO: disable the cut button & remember which mode the button is disabled for

      const isValid = this.validate();

      if (!isValid) {
        console.log("NOT Valid");
        return;
      }

      console.log("request is valid");

      // TODO send to server
      this.sendReq();
    },

    /**
     * Hides the previous error msgs if any,
     * validates childs and parents array
     * in parents array, only validates Width and Height
     */
    validate: function () {
      // empties the previous errors
      this.hideErrorMsgs();

      /*
            all the rows in childs must contain numbers > 0
            */
      const labels =
        this.mode === "2d"
          ? ["width", "height", "quantity"] // for 2d mode, height is required
          : ["width", "quantity"]; // for 1d mode, height is not required

      // console.log('Labels:', labels)

      // compute child errors
      for (let i = 0; i < this.mode_data.childs.length; i++) {
        const child = this.mode_data.childs[i];

        console.log("Validating: ", child);

        for (let j = 0; j < labels.length; j++) {
          let val = child[labels[j]]; /// width, height, quantity
          val = parseInt(val);

          if (!Number.isInteger(val) || val < 1) {
            this.mode_data.childErrors = `> Row #${i + 1}: "${
              labels[j]
            }" must be 1 units or more\n`;
            // return on first error. To only show the one (first) error at a time
            return false;
          }
        }

        // go over width, height, quantity of each row
        // for (let j = 0; j < row.length; j++)
        // 	if (!Number.isInteger(row[j]) || row[j] < 1) {
        // 		this.childErrors = `> ${labels[j]} in Row #${i+1} must be 1 units or more\n`;
        // 		// return on first error. To only show the one (first) error at a time
        // 		return false;
        // 	}
      }

      // compute parent errors
      for (let i = 0; i < this.mode_data.parents.length; i++) {
        const parent = this.mode_data.parents[i];

        // go over width, height, quantity of each row
        // [FOR NOW: don't go over quantity. Because we don't support quantity for parent sheets]
        for (let j = 0; j < labels.length - 1; j++) {
          let val = parent[labels[j]]; // value is currently a string
          val = parseInt(val);
          if (!Number.isInteger(val) || val < 1) {
            this.mode_data.parentErrors = `> Row #${i + 1}: "${
              labels[j]
            }" must be 1 units or more\n`;

            console.log("Valdiation Error: ", this.mode_data.parentErrors);
            console.log("Attr name: ", labels[j]);
            console.log("Value: ", val);
            console.log("Row #: ", j);
            // return on first error. To only show the one (first) error at a time
            return false;
          }
        }
      }

      return !this.mode_data.childErrors && !this.mode_data.parentErrors;
    },

    hideErrorMsgs: function () {
      this.mode_data.childErrors = null;
      this.mode_data.parentErrors = null;
    },

    prepareDataToSend1D: function () {
      /*
            For 1D algorithm the server expects data in the format:
            child_rolls:
                array of arrays. E.g [ [quantity, width], [quantity, width], ... ]

            parent_rolls:
                array of arrays. E.g [ [quantity, width], [quantity, width], ... ]

            IMPORTANT: convert inputs from string to int
            */

      let newChilds = [];

      this.mode_data.childs.forEach((child) => {
        newChilds.push([parseInt(child.quantity), parseInt(child.width)]);
      });

      let newParents = [];
      this.mode_data.parents.forEach((parent) => {
        // TODO: fix from here. At the moment, parent's quantity is not really being used
        newParents.push([parseInt(parent.quantity), parseInt(parent.width)]);
      });

      return {
        child_rolls: newChilds,
        parent_rolls: newParents,
        cutStyle: this.cutStyle, // exactCuts or minWaste
      };
    },

    sendReq: function () {
      let url = null;

      // set local url for local requests
      //   const { href } = window.location;
      // if (href && (href.includes('localhost') || href.includes('127.0.0.1')) ) {
      // 	url = this.mode === '1d' ?
      // 		'http://localhost:5000/stocks_1d'
      // 		: 'http://localhost:5000/stocks_2d';

      // 	console.log('Requesting from local server:', url);
      // } else {
      // 	url = this.mode === '1d' ?
      // 		'https://chromeless.herokuapp.com/stocks_1d'
      // 		: 'https://chromeless.herokuapp.com/stocks_2d';
      // }

      // TODO: uncomment above, comment below
      url =
        this.mode === "1d"
          ? "https://chromeless.herokuapp.com/stocks_1d"
          : "https://chromeless.herokuapp.com/stocks_2d";

      this.disableCutButton(true);

      // process the data as server's requirements
      const dataToSend =
        this.mode === "1d"
          ? this.prepareDataToSend1D()
          : this.prepareDataToSend2D();

      console.log("dataToSend", dataToSend);

      axios
        .post(url, dataToSend)
        .then((response) => {
          console.log(response);

          this.disableCutButton(false);
          this.displayResult(response);
        })
        .catch((error) => {
          this.disableCutButton(false);
          console.log("Network/Server error");
          console.error(error);
        });

      // TODO catch exceptions and show display errors
    },

    // disable / enable cut button between requests
    disableCutButton: function (disabled) {
      this.cutButtonDisabled = disabled;
    },

    /**
     * called before sending a validated input to the server.
     * Adds multiple quantity sheets as multiple sheets and removes the quantity value.
     */
    prepareDataToSend2D: function () {
      /*
            E.g.
            currently:
                this.mode_data.childs = [ {width: 3, height: 3, quantity: 4} ] // array of objects

            after this method:
                child_rects = [[3,3],[3,3],[3,3],[3,3]] // array of arrays

            doing this on client side so server has to do less work

            IMPORTANT: convert inputs from string to int
            */

      let newChilds = [];

      this.mode_data.childs.forEach((child) => {
        const quantity = child.quantity;

        // const newChild = { width: child.width, height: child.height }
        // at the moment, server's acceptable format is
        // [ [width, height], [width, height], ...]
        const newChild = [parseInt(child.width), parseInt(child.height)];
        // add as many childs as there are quantities of that child
        for (let q = 0; q < quantity; q++) newChilds.push(newChild);
      });

      let newParents = [];
      this.mode_data.parents.forEach((parent) => {
        // At the moment, parent's quantity is not allowed, so keep it 1
        const quantity = 1;
        // const quantity = parent[2] // 0: width, 1: height, 2: quantity

        const newParent = [parseInt(parent.width), parseInt(parent.height)];
        // add as many childs as there are quantities of that child
        for (let q = 0; q < quantity; q++) newParents.push(newParent);
      });

      // the server expects data in this format
      return { child_rects: newChilds, parent_rects: newParents };
    },

    // response: from server
    displayResult: function (response) {
      console.log("data > ", response.data);

      this.mode_data.result = response.data;

      if (this.mode_data.result && this.mode_data.result.statusName) {
        // make it lower case
        this.mode_data.result.statusName =
          this.mode_data.result.statusName.toLowerCase();
      }

      if (this.mode === "1d") {
        this.checkValidity1D();
        this.draw1d();
      } else this.draw2d();
    },

    /**
        checks the validity of the output of 1D algo

        IMPORTANT: convert all local inputs to integers before comparing
        */
    checkValidity1D: function () {
      let isValid = true;

      // input sheets
      const childRolls = this.mode_data.childs;
      const parentRolls = this.mode_data.parents;
      let parentWidth = parentRolls[0].width; // at the moment, there is only one parent. TODO update here
      parentWidth = parseInt(parentWidth);

      // algorithm output from server
      const bigRolls = this.mode_data.result.solutions;

      /*
            check 1: sum of smallRolls & unusedWidth in a bigRoll must be equal to parentWidth i.e. len of bigRoll.
            for each bigRoll
            */

      // go over all the bigRolls and count the number of each small width
      let outputQuantities = {};

      for (let i = 0; i < bigRolls.length; i++) {
        const unusedWidth = bigRolls[i][0];
        const bRoll = bigRolls[i][1];

        // totalWidth will contain sum of all widths in current bRoll. Must be equal to parentWidth
        let totalWidth = Math.round(unusedWidth);

        for (let j = 0; j < bRoll.length; j++) {
          const smallRoll = bRoll[j];
          totalWidth += smallRoll;

          // count the number of rolls of each width. this is for check 2 below.
          // if this width has already been added, update the count (quantity)
          if (Object.prototype.hasOwnProperty.call(outputQuantities, smallRoll))
            outputQuantities[smallRoll] += 1;
          else outputQuantities[smallRoll] = 1;
        }

        if (totalWidth !== parentWidth) {
          console.error(
            `#checkValidity1D: bigRolls[${i}] totalWidth != parentWidth`,
            totalWidth,
            "!=",
            parentWidth
          );
          isValid = false;
        }
      }

      console.log("outputQuantities: ", outputQuantities);

      /*
            check 2: number of smallRolls of specific width must be exactly as many as the quantity specified for this width in childRolls

            create a dict/object
            {
                3: 6, <- 3 is width, 6 is quantity
            }
            */

      // go over all childs and count quantity of specific width
      let inputQuantities = {};
      for (let i = 0; i < childRolls.length; i++) {
        let width = childRolls[i].width;
        width = parseInt(width);
        let quantity = childRolls[i].quantity;
        quantity = parseInt(quantity);

        // if this width has already been added, update the count
        if (Object.prototype.hasOwnProperty.call(inputQuantities, width)) {
          // let alreadyAddedQuantity = inputQuantities[width];
          // inputQuantities[width] = alreadyAddedQuantity + quantity;

          inputQuantities[width] += quantity;
        } else {
          inputQuantities[width] = quantity;
        }
      }

      console.log("inputQuantities: ", inputQuantities);

      const inputWidths = Object.keys(inputQuantities);

      // for every width in inputQuantities, the quantity must be equal to quantity of
      // corresponding width in outputQuantities
      for (let i = 0; i < inputWidths.length; i++) {
        const width = inputWidths[i];

        const inQuantity = inputQuantities[width];
        const outQuantity = outputQuantities[width];

        if (inQuantity !== outQuantity) {
          console.error(
            `#checkValidity1D: for input width: ${width} inQuantity != outQuantity`,
            inQuantity,
            "!=",
            outQuantity
          );
          isValid = false;
        } else {
          // remove this width from outputQuantities
          delete outputQuantities[width];
        }
      }

      /*
            Check 3: is there extra non intended width that user didn't input?

            by now, all the widths in outputQuanitites must have been deleted & it must be empty
            */
      const outputWidths = Object.keys(outputQuantities);
      if (outputWidths.length > 0) {
        console.error(
          `#checkValidity1D: unintended buggy widths in output:`,
          outputWidths,
          outputQuantities
        );
        isValid = false;
      }

      if (!isValid) {
        alert("Alert! Results contains extra cuts/items. Use with caution");
      }

      return isValid;
    },

    /**
        for output of 1D algo:
            1. sorts the bigRolls in ascending order of waste (unused width)
            2. sort the cuts to each bigRoll (small rolls in nested array to bigRoll) in descending order
        */
    sortBigRolls: function (bigRolls) {
      /*
            sorts the bigRolls in ascending order of width.
            E.g,
            [
                [0, [21, 21, 21, 21, 21, 15]],
                [2, [25, 25, 34, 34]],
                [0, [21, 33, 33, 33]],
            ]

            becomes:
            [
                [0, [21, 21, 21, 21, 21, 15]],
                [0, [21, 33, 33, 33]],
                [2, [25, 25, 34, 34]],
            ]
            */

      // sort 2-D array based on 0-th element of each nested array
      // https://stackoverflow.com/a/16096900/3578289
      bigRolls = bigRolls.sort(function (a, b) {
        return a[0] - b[0];
      });

      /*
            sorts the smallRolls inside each bigRoll in descending order of width
            E.g,
            [
                [0, [21, 21, 21, 21, 21, 15]],
                [0, [21, 33, 33, 33]],
                [2, [25, 25, 34, 34]],
            ]

            becomes:
            [
                [0, [15, 21, 21, 21, 21, 21]],
                [0, [21, 33, 33, 33]],
                [2, [25, 25, 34, 34]],
            ]
            */
      for (let i = 0; i < bigRolls.length; i++) {
        let smallRolls = bigRolls[i][1];
        smallRolls = smallRolls.sort(function (a, b) {
          return a - b;
        });
        bigRolls[i][1] = smallRolls;
      }

      return bigRolls;
    },

    /**
        draws the shapes according to data and requirements of 1d algorithm
        */
    draw1d: function () {
      // clear old drawing
      this.clearTheDrawing();

      if (!this.mode_data.result) {
        console.log(
          `Cannot draw anything. "result" is: ${this.mode_data.result} for mode: ${this.mode}`
        );
        return;
      }

      /**
            Result format:
            [
                [unused_width, [width_of_small_roll, width_of_small_roll, ...]], // single roll format
            ]

            E.g,
            [
                [0, [21, 21, 21, 21, 21, 15]],
                [2, [25, 25, 34, 34]],
                [0, [21, 33, 33, 33]],
            ]
            */

      const unSortedBigRolls = this.mode_data.result.solutions;
      console.log("bigRolls before sorting", unSortedBigRolls);

      const bigRolls = this.sortBigRolls(unSortedBigRolls);
      console.log("bigRolls after sorting", bigRolls);

      this.mode_data.result.solutions = bigRolls;

      const colorDict = this.getColorDict();

      const parentWidth = this.mode_data.parents[0].width;

      // get the current width alloted to #d3_area, it's dynamic
      const graphWidth = document.getElementById("d3_area").clientWidth;
      let xScale = d3
        .scaleLinear()
        .domain([0, parentWidth])
        .range([0, graphWidth]);

      // let yScale = d3.scaleOrdinal()
      // 	.domain(d3.range(0, dataLen))// <-num big rolls
      // 	.rangeBands(0, 300);
      let yScale = d3
        .scaleBand()
        .domain(d3.range(bigRolls.length))
        // .range([0, 20 * bigRolls.length])
        .range([0, 300]);

      // create svg element:
      let svg = d3.select("#d3_area svg");

      var margin = { top: 20, right: 20, bottom: 20, left: 20 };

      let svgWidth = 300 - margin.left - margin.right;
      let svgHeight = 300 - margin.top - margin.bottom;

      svg
        .attr("width", svgWidth + margin.left + margin.right)
        .attr("height", svgHeight + margin.top + margin.bottom)
        .style("border", "1px solid #34495e");
      // .append("g")
      // .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      // .attr('fill', 'blue')

      let x1 = 0;
      let x2 = 0;
      let y1 = 0;
      for (let i = 0; i < bigRolls.length; i++) {
        const unusedWidth = bigRolls[i][0];
        const smallRolls = bigRolls[i][1];

        /*
                each bigRoll has it's own row. All of it's cuts have same Y
                */
        x1 = 0;
        y1 = yScale(i);
        for (let j = 0; j < smallRolls.length; j++) {
          const smallRoll = smallRolls[j];

          const width = xScale(smallRoll);
          x2 = x1 + width;

          // add the rectangular strip / bar
          let g = svg.append("g").attr("transform", `translate(${x1},${y1})`); // one vertical bar

          g.append("rect")
            .attr("fill", colorDict[smallRoll]) // <- apply color associated with this width
            .attr("width", width - 1)
            .attr("height", yScale.bandwidth() - 1);

          // add text
          g.append("text")
            .attr("fill", "white")
            .attr("x", 3) // this x is relative to the parent g
            .attr("y", yScale.bandwidth() / 2)
            .attr("dy", "0.35em")
            .text(smallRoll);

          // for next rect, x1 will update to x2 of current rect
          x1 = x2;
        }

        if (unusedWidth > 0) {
          // add unusedWith as rectangular bar
          x2 = x1 + xScale(unusedWidth);
          let g = svg.append("g").attr("transform", `translate(${x1},${y1})`); // one vertical bar

          g.append("rect")
            .attr("fill", this.wasteColor)
            .attr("width", xScale(unusedWidth) - 1)
            .attr("height", yScale.bandwidth() - 1);

          // add text
          g.append("text")
            .attr("fill", "white")
            .attr("x", 3)
            .attr("y", yScale.bandwidth() / 2)
            .attr("dy", "0.35em")
            .text(Math.round(unusedWidth));
        }
      }

      return svg.node();
    },

    /**
        draws the shapes according to data and requirements of 1d algorithm
        */
    draw2d: function () {
      // clear old drawing
      this.clearTheDrawing();

      if (!this.mode_data.result) {
        console.log(
          `Cannot draw anything. "result" is: ${this.mode_data.result} for mode: ${this.mode}`
        );
        return;
      }

      const solutions = this.mode_data.result.solutions;

      for (let i = 0; i < solutions.length; i++) {
        const sol = solutions[i];

        for (let j = 0; j < sol.length; j++) {
          const rect = sol[j];

          let x1 = rect[0];
          let y1 = rect[1];
          let x2 = rect[2];
          let y2 = rect[3];

          const color = this.colors[j % this.colors.length];
          this.drawRect(x1, y1, x2, y2, color);
        }

        // only draw first solution for now
        // TODO: FIXME: Does drawing all solution help?
        break;
      }
    },

    // before drawing new result, clear old canvas drawing
    clearTheDrawing: function () {
      d3.selectAll("#d3_area svg > *").remove();
      // also hide the border of svg
      d3.select("#d3_area svg").style("border", "");
    },

    drawRect: function (x1, y1, x2, y2, color) {
      console.log("Drawing Rect... Color: ", color);
      console.log("Draw rect", x1, y1, x2, y2);

      const width = Math.abs(x2 - x1);
      const height = Math.abs(y2 - y1);
      // const coords = [{ x1, y1, width, height, color }];

      // const rectTitle = `${width} x ${height}`;

      const parentWidth = this.mode_data.parents[0].width;
      const parentHeight = this.mode_data.parents[0].height;
      const dataLen = this.mode_data.parents.length;

      // let maxDimOfParent = Math.max(parentWidth, parentHeight);

      //   let maxDimOfParent = null;
      //   if (this.mode === "1d") {
      //     maxDimOfParent = parentWidth;
      //   }

      let xScale = d3.scaleLinear().domain([0, parentWidth]).range([0, 300]); // <- TODO here put the dynamic width of chart

      let yScale;

      if (this.mode === "1d") {
        yScale = d3
          .scaleOrdinal()
          .domain(d3.range(0, dataLen)) // <-num big rolls
          .rangeBands(0, 300);
      } else if (this.mode === "2d") {
        yScale = d3
          .scaleLinear()
          .domain([0, parentHeight]) // sum of all stock's heights
          .range([0, 300]); // display in 300 pixels height?
      }

      // const scaleFactor =
      // this.mode === '1d'?
      // Math.floor(300 / maxDimOfParent)
      // 1:
      // Math.floor(300 / maxDimOfParent);

      // const rectX = x1 * scaleFactor;
      // const rectY = this.mode == '1d'? y1: y1 * scaleFactor;
      // const rectW = width * scaleFactor;
      // const rectH = this.mode == '1d'? height: height * scaleFactor;

      // console.log('Scaled rect', rectX, rectY, rectX+rectW, rectY+rectH, scaleFactor);
      console.log(
        "D3 Scaled rect",
        xScale(x1),
        yScale(y1),
        xScale(width),
        yScale(height)
      );

      // create svg element:
      let svg = d3.select("#d3_area svg");

      var margin = { top: 20, right: 20, bottom: 20, left: 20 };

      let svgWidth = 300 - margin.left - margin.right;
      let svgHeight = 300 - margin.top - margin.bottom;

      let g = svg
        .attr("width", svgWidth + margin.left + margin.right)
        .attr("height", svgHeight + margin.top + margin.bottom)
        .style("border", "1px solid #34495e")
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      // Add the path using this helper function
      g
        // .data(coords)
        .append("rect")
        // .style('fill', function (coords) { return coords.color })
        .style("fill", color)
        // .style("background-color", "black")
        // .attr('x', rectX)
        .attr("x", xScale(x1)) //function (coords) { return xScale(coords.x1) })
        // .attr('y', rectY)
        .attr("y", yScale(y1)) //function (coords) {return yScale(coords.y1) })
        // .attr('width', rectW)
        .attr("width", xScale(width)) //function (coords) { return xScale(coords.width) })
        // .attr('height', rectH)
        .attr("height", yScale(height)) //function (coords) { return yScale(coords.height) })
        .attr("stroke", "#34495e")
        .text(`${width} x ${height}`);

      // add the label to the shape
      // const labelX = rectX + Math.abs( rectW / 2 - 15);
      // const labelY = rectY + Math.abs( rectH / 2 + 5);
      const labelX = xScale(x1) + Math.abs(xScale(width) / 2); // - 15);
      const labelY = yScale(y1) + Math.abs(yScale(height) / 2); // + 5);

      //   let label =
      g.append("text")
        .attr("x", labelX)
        .attr("y", labelY)
        .attr("stroke", "#34495e");
      // .attr('stroke', 'black')
      // .style("font-size", 15);

      return;
    },

    /**
     * clears the data in the child table and returns it to it's initial state
     * i.e. 1 empty row
     */
    clearChildData: function (askConfirm) {
      if (askConfirm) {
        const answer = confirm("Are you sure you want to empty this Table?");
        if (!answer) return;
      }

      const emptyChildData =
        this.mode === "1d"
          ? [{ width: "", quantity: "" }]
          : [{ width: "", height: "", quantity: "" }];

      this.mode_data.childs = emptyChildData;

      // also clear errors displayed in Child table (if any)
      this.mode_data.childErrors = null;

      // also clear the result and drawing
      this.mode_data.result = null;
      this.clearTheDrawing();
    },

    clearParentData: function (askConfirm) {
      if (askConfirm) {
        const answer = confirm("Are you sure you want to empty this Table?");
        if (!answer) return;
      }

      const emptyParentData =
        this.mode === "1d"
          ? [{ width: "", quantity: "Comming soon" }]
          : [{ width: "", height: "", quantity: "Comming soon" }];

      this.mode_data.parents = emptyParentData;

      // also clear errors displayed in Parent table (if any)
      this.mode_data.parentErrors = null;

      // also clear the result and drawing
      this.mode_data.result = null;
      this.clearTheDrawing();
    },

    /**
            Removes the row at the index specified. if it's only row, add an empty row after removing
        */
    removeRow: function (idx, is_parent) {
      if (is_parent) {
        // parent only has 1 row. take help from clearParentData. The remove row button
        // in parent table is added for symmetry
        this.clearParentData(false);
        return;
      }

      // in case of child
      if (this.mode_data.childs.length > 1) {
        this.mode_data.childs.splice(idx, 1);
      } else {
        // there is only one item in the child table,
        // calling clearChildData() will both clear the table and add back an empty row
        this.clearChildData(false);
      }
    },

    /**
        to each of the widths of small rolls in the result of 1d
        assign a unique color
        */
    getColorDict: function () {
      const bigRolls = this.mode_data.result.solutions;

      // assign same color to equal sized small rolls
      let uniqueSmallRollsSet = new Set([]);
      for (let i = 0; i < bigRolls.length; i++) {
        const smallRolls = bigRolls[i][1];

        smallRolls.forEach((roll) => {
          uniqueSmallRollsSet.add(roll);
        });
      }

      let uniqueSmallRolls = Array.from(uniqueSmallRollsSet);
      let colorDict = {};

      for (let i = 0; i < uniqueSmallRolls.length; i++) {
        // colorDict[ width_of_roll ] = '#color'
        colorDict[uniqueSmallRolls[i]] = this.colors[i % this.colors.length];
      }

      // console.log('colorDict:', colorDict)
      return colorDict;
    },

    /**
        returns the color assigned to this width in colorDict
        this is for 1d mode at the moment
        */
    getColor: function (width) {
      const colorDict = this.getColorDict();
      // return colorDict[width];
      return { backgroundColor: `${colorDict[width]}` };
    },

    /**
        clear all the inputs and drawings
        */
    reset: function () {
      const answer = confirm(
        "Are you sure you want to delete all inputs? Cannot be undone"
      );
      if (!answer) return;

      this.clearChildData(false); // it will also clear the drawing
      this.clearParentData(false);
    },

    getPercentageUtilization: function (unusedWidth) {
      let usedWidth = Math.abs(this.mode_data.parents[0].width - unusedWidth);
      let percentage = (usedWidth * 100) / this.mode_data.parents[0].width;

      percentage *= 100; // preserve 2 digits after decimal
      percentage = Math.round(percentage); // remove the decimal part
      percentage /= 100; // back to original percentage

      return percentage;
    },

    // to display in footer
    getCurrentYear: function () {
      return new Date().getFullYear();
    },

    /**
        implemented only for 1D, download the details of cut in CSV
        TODO: implement for 2D
        */
    downloadCsv: function () {
      if (!this.mode_data.result || !this.mode_data.result.solutions) {
        console.log("downloadCsv: bigRolls are empty..");
        return;
      }

      // prepare data
      let dataForCsv = [["Stock", "Usage", "Width of Cuts"]];
      let numSmallRolls = 0;

      const bigRolls = this.mode_data.result.solutions;
      for (let i = 0; i < bigRolls.length; i++) {
        const unusedWidth = bigRolls[i][0];
        const smallRolls = bigRolls[i][1];

        numSmallRolls += smallRolls.length;

        // ['Stock #', 'Usage', 'Width of Cuts']
        const nextRow = [
          i + 1,
          this.getPercentageUtilization(unusedWidth) + "%",
          smallRolls.join(","),
        ];
        dataForCsv.push(nextRow);
      }

      // convert to CSV format
      // source: https://stackoverflow.com/a/14966131/3578289
      const csvContent =
        "data:text/csv;charset=utf-8," +
        dataForCsv.map((e) => e.join(",")).join("\n");
      // console.log('csvContent: ', csvContent);

      // download the file
      let encodedUri = encodeURI(csvContent);
      // console.log('encodedUri: ', encodedUri);
      let link = document.createElement("a");
      link.setAttribute("href", encodedUri);

      // unique and identifiable filename
      const stockWidth = this.mode_data.parents[0].width;
      const d = new Date();
      const dateString = `${d.getFullYear()}-${
        d.getMonth() + 1
      }-${d.getUTCDate()}-${d.getHours()}${d.getMinutes()}-${d.getSeconds()}`;

      const filename = `CSP_stock_${stockWidth}_cuts_${numSmallRolls}_${dateString}.csv`;
      link.setAttribute("download", filename);

      document.body.appendChild(link); // Required for FF
      link.click(); // This will download the data file named "my_data.csv".
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
@import "../assets/style.scss";
</style>


================================================
FILE: deployment/frontend/src/main.js
================================================
import { createApp } from 'vue'

// import Vue from 'vue'
// import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
// Import Bootstrap an BootstrapVue CSS files (order is important)
import 'bootstrap/dist/css/bootstrap.css'
// import 'bootstrap-vue/dist/bootstrap-vue.css'

// Make BootstrapVue available throughout your project
// Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
// Vue.use(IconsPlugin)


import App from './App.vue'

createApp(App).mount('#app')


================================================
FILE: deployment/requirements.txt
================================================
gunicorn
Flask
Flask-Cors
ortools


================================================
FILE: deployment/server.py
================================================
from flask import Flask, json, request
from flask_cors import CORS, cross_origin

import stock_cutter # local module

app = Flask(__name__)
cors = CORS(app)
app.config['CORS_HEADERS'] = 'Content-Type'

@app.route('/', methods=['GET'])
@cross_origin()
def get_csp():
	return 'Cutting Stock Problem'

'''
route for receving data for 1D problem 
'''
@app.route('/stocks_1d', methods=['POST'])
@cross_origin()
def post_stocks_1d():
	'''
	expects two params to be present
	child_rolls:
		array of arrays. E.g [ [quantity, width], [quantity, width], ... ]

	parent_rolls:
		array of arrays. E.g [ [quantity, width], [quantity, width], ... ]
	'''
	import stock_cutter_1d

	data = request.json
	print('data: ', data)

	child_rolls = data['child_rolls']
	parent_rolls = data['parent_rolls']

	'''
	it can be
	exactCuts: cut exactly as many as specified by user
	minWaste: cut some items, more than specified, to avoid waste
	'''
	cutStyle = data['cutStyle']

	# output = stock_cutter_1d.StockCutter1D(child_rolls, parent_rolls, cutStyle=cutStyle)
	output = stock_cutter_1d.StockCutter1D(child_rolls, parent_rolls, large_model=False, cutStyle=cutStyle)

	return output



'''
route for 2D
'''
@app.route('/stocks_2d', methods=['POST'])
@cross_origin()
def post_stocks():
	'''
	expects two params to be present
	child_rects:
		array of arrays. Each inner array is like [w, h] i.e. width & height of rectangle

	parent_rects:
		array of arrays. Each inner array is like [w, h] i.e. width & height of rectangle
	'''
	data = request.json
	print('data: ', data)

	child_rects = data['child_rects']
	parent_rects = data['parent_rects']

	output = stock_cutter.StockCutter(child_rects, parent_rects)

	return output



if __name__ == '__main__':
    # app.run()
	app.run(threaded=True, port=5000)


================================================
FILE: deployment/stock_cutter.py
================================================
'''
@Author Emad Ehsan
Cutting Stock problem 2D
Not complete.
What's remaining: Finding Optimized solution that minimizes the waste.
'''

import collections, json
from ortools.sat.python import cp_model

"""
    params
        child_rects: 
            lists of multiple rectangles' coords
            e.g.: [ [w, h], [w, h], ...]
        parent_rects: rectangle coords
            lists of multiple rectangles' coords
            e.g.: [ [w, h], [w, h], ...]
"""
def StockCutter(child_rects, parent_rects, output_json=True):
    
    # Create the model
    model = cp_model.CpModel()

    # parent rect (to cut from). horizon = [ width, height ] of parent sheet
    # for now, parent rectangle is just one
    # TODO: to add functionality of cutting from multiple parent sheets, start here:
    horizon = parent_rects[0] 
    total_parent_area = horizon[0] * horizon[1] # width x height

    # Named Tuple to store information about created variables
    sheet_type = collections.namedtuple('sheet_type', 'x1 y1 x2 y2 x_interval y_interval is_extra')

    # Store for all model variables
    all_vars = {}

    # sum of to save area of all small rects, to cut from parent rect
    total_child_area = 0 

    # hold the widths (x) and heights (y) interval vars of each sheet
    x_intervals = []
    y_intervals = []

    # create model vars and intervals
    for rect_id, rect in enumerate(child_rects):
        width = rect[0]
        height = rect[1]
        area = width * height
        total_child_area += area
        # print(f"Rect: {width}x{height}, Area: {area}")

        suffix = '_%i_%i' % (width, height)

        # interval to represent width. max value can be the width of parent rect
        x1_var = model.NewIntVar(0, horizon[0], 'x1' + suffix)
        x2_var = model.NewIntVar(0, horizon[0], 'x2' + suffix)
        x_interval_var = model.NewIntervalVar(x1_var, width, x2_var, 'x_interval' + suffix)

        # interval to represent height. max value can be the height of parent rect
        y1_var = model.NewIntVar(0, horizon[1], 'y1' + suffix)
        y2_var = model.NewIntVar(0, horizon[1], 'y2' + suffix)
        y_interval_var = model.NewIntervalVar(y1_var, height, y2_var, 'y_interval' + suffix)
        
        x_intervals.append(x_interval_var)
        y_intervals.append(y_interval_var)

        # store the variables for later use
        all_vars[rect_id] = sheet_type(
            x1=x1_var, 
            y1=y1_var, 
            x2=x2_var, 
            y2=y2_var, 
            x_interval=x_interval_var,
            y_interval=y_interval_var,
            is_extra=False # to keep track of 1x1 custom rects added in next step
        )

        # model.Minimize(x1_var)
        # model.Minimize(y1_var)


    # TODO: Minimize (x1,y1) values. So that rectangles are placed at the start
    # this reduced the areas wasted by place rectangles in the middle / at the end
    # even though the space at the start is available.
    # >
    # for rect_id in range(len(child_rects)):
    #     model.Minimize(all_vars[rect_id].x1 + all_vars[rect_id].y1)
    #     model.Minimize(all_vars[rect_id].x2 + all_vars[rect_id].y2)
    #     model.Minimize(all_vars[rect_id].x1)
    #     model.Minimize(all_vars[rect_id].x2)
    #     model.Minimize(all_vars[rect_id].y1)
    #     model.Minimize(all_vars[rect_id].y2)


    '''
    FIXME: experiment
    Experment: treat the remaining area as small units of 1x1 rectangles. Push these rects to higher x,y.
    '''
    # leftover_area = total_parent_area - total_child_area
    # if leftover_area >= 0:
    #     '''
    #     each unit of leftover_area can be represented by 1x1 rectangles. 
    #     For leftover_area = 4 (e.g. 2x2 originally), we can use 4 rects of 1x1. Why? Because
    #     1. leftover_area would not always be continous. It is possible it is in the form of two 
    #     separate 2x1 rects or one 2x2 or four rects of 1x1. So we need the simplest version, 
    #     that can cover all types of rects. And it is 1x1
    #     2. 1x1 can represent non-adjecent weirdly shaped locations in the parent area that were leftover.
    #     '''
    #     num_1x1rects = leftover_area

    #     for i in range(num_1x1rects):
    #         print(f'{i}-th 1x1')
    #         suffix = '_%i_%i' % (1, 1)

    #         # interval to represent width. max value can be the width of parent rect
    #         x1_var = model.NewIntVar(0, horizon[0], 'x1' + suffix)
    #         x2_var = model.NewIntVar(0, horizon[0], 'x2' + suffix)
    #         x_interval_var = model.NewIntervalVar(x1_var, 1, x2_var, 'x_interval' + suffix)

    #         # interval to represent height. max value can be the height of parent rect
    #         y1_var = model.NewIntVar(0, horizon[1], 'y1' + suffix)
    #         y2_var = model.NewIntVar(0, horizon[1], 'y2' + suffix)
    #         y_interval_var = model.NewIntervalVar(y1_var, 1, y2_var, 'y_interval' + suffix)
            
    #         x_intervals.append(x_interval_var)
    #         y_intervals.append(y_interval_var)

    #         # store the variables for later use
    #         all_vars[rect_id] = sheet_type(
    #             x1=x1_var, 
    #             y1=y1_var, 
    #             x2=x2_var, 
    #             y2=y2_var, 
    #             x_interval=x_interval_var,
    #             y_interval=y_interval_var,
    #             is_extra=True
    #         )
    #         model.Maximize(x1_var)
    #         model.Maximize(y1_var)
    # else:
    #     print(f'Problem identified: Area of small rects is larger than parent rect by {leftover_area}')

    


    # add constraint: no over lap of rectangles allowed
    model.AddNoOverlap2D(x_intervals, y_intervals)

    # Solve model
    solver = cp_model.CpSolver()

    '''
    Search for all solutions is only defined on satisfiability problems
    '''
    # solution_printer = VarArraySolutionPrinter(all_vars)
    # status = solver.SearchForAllSolutions(model, solution_printer) # use for satisfiability problem
    # solutions = solution_printer.get_unique_solutions()
    # int_solutions = str_solutions_to_int(solutions)
    # output = {
    #     "statusName": solver.StatusName(status),
    #     "numSolutions": solution_printer.solution_count(),
    #     "numUniqueSolutions": len(solutions),
    #     "solutions": int_solutions # unique solutions
    # }

    '''
    for single solution
    '''
    status = solver.Solve(model) # use for Optimization Problem
    singleSolution = getSingleSolution(solver, all_vars)
    int_solutions = [singleSolution] # convert to array
    output = {
        "statusName": solver.StatusName(status),
        "numSolutions": '1',
        "numUniqueSolutions": '1',
        "solutions": int_solutions # unique solutions
    }


    print('Time:', solver.WallTime())
    print('Status:', output['statusName'])
    print('Solutions found :', output['numSolutions'])
    print('Unique solutions: ', output['numUniqueSolutions'])

    if output_json:
        return json.dumps(output)        
    else:
        return int_solutions # integer representation of solutions

'''
    This method is used to extract the single solution from the solver.
    Because in the case where VarArraySolutionPrinter is not used, the answers are not 
    yet extracted from the solver. Use this method to extract the solver.
'''
def getSingleSolution(solver, all_vars):
    solution = []
    # extra coordinates of all rectangles for this solution 
    for rect_id in all_vars:
        rect = all_vars[rect_id]
        x1 = solver.Value(rect.x1)
        x2 = solver.Value(rect.x2)
        y1 = solver.Value(rect.y1)
        y2 = solver.Value(rect.y2)

        # rect_str = f"{x1},{y1},{x2},{y2}"
        coords = [x1, y1, x2, y2];
        # print(rect_str)

        solution.append(coords)
        # print(f'Rect #{rect_id}: {x1},{y1} -> {x2},{y2}')

    # print(rect_strs)
    # sort the rectangles
    # rect_strs = sorted(rect_strs)
    # single solution as a string
    # solution_str = '-'.join(rect_strs)
    return solution


"""
    converts from string format to integer values. String format, in previous step, was used 
    to exclude duplicates.
    params:
        str_solutions: list of strings. 1 string contains is solution
"""
def str_solutions_to_int(str_solutions):

    # list of solutions, each solution is a list of rectangle coords that look like [x1,y1,x2,y2]
    int_solutions = []

    # go over all solutions and convert them to int>list>json
    for idx, sol in enumerate(str_solutions):
        # sol is string of coordinates of all rectangles in this solution
        # format: x1,y1,x2,y2-x1,y1,x2,y2
    
        rect_strs = sol.split('-')
        rect_coords = [
            # [x1,y1,x2,y2],
            # [x1,y1,x2,y2],
            # ...
        ]

        # convert each rectangle's coords to int
        for rect_str in rect_strs:
            coords_str = rect_str.split(',')
            coords = [int(c) for c in coords_str]
            rect_coords.append(coords)

        # print('rect_coords', rect_coords)

        int_solutions.append(rect_coords)

    return int_solutions


"""
    To get all the solutions of the problem, as they come up. 
    https://developers.google.com/optimization/cp/cp_solver#all_solutions

    The solutions are all unique. But for the child rectangles that have same dimensions, 
    some solution will be repetitive. Because for the algorithm, they are different solutions, 
    but because of same size, they are merely permutations of the similar child rectangles - 
    having other rectangles' positions fixed.

    We want to remove repetitive extra solutions. One way to do this is
        1. Stringify every rectangle coords in a solution
            (1,2)->(2,3) becomes "1,2,2,3"

            # here the rectangles are stored as a string: "1,2,2,3" where x1=1, y1=2, x2=2, y2=3
        
        2. Put all these string coords into a sorted list. This sorting is important. 
            Because the rectangles (1,2)->(2,3) and (3,3)->(4,4) are actually same size (1x1) rectangles. 
            And they can appear in 1st solution as 
            [(1,2)->(2,3)   ,   (3,3)->(4,4)]
            and in the 2nd solution as
            [(3,3)->(4,4)   ,   (1,2)->(2,3)]

            but this sorted list of strings will ensure both solutions are represented as

            [..., "1,2,2,3", "3,3,4,4", ...]

        3. Join the Set of "strings (rectangles)" in to one big string seperated by '-'. For every solution.
            So in resulting big strings (solutions), we will have two similar strings (solutions) 
            that be similar and also contain

            "....1,2,2,3-3,3,4,4-...."

        4. Now add all these "strings (solutions)" into a Set. this adding to the set 
        will remove similar strings. And hence duplicate solutions will be removed.

"""
class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):

    def __init__(self, variables):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self.__variables = variables
        self.__solution_count = 0

        # hold the calculated solutions
        self.__solutions = []
        self.__unique_solutions = set()

    def on_solution_callback(self):
        self.__solution_count += 1
        # print('Sol#: ', self.__solution_count)

        # using list to hold the coordinate strings of rectangles
        rect_strs = []

        # extra coordinates of all rectangles for this solution 
        for rect_id in self.__variables:
            rect = self.__variables[rect_id]
            x1 = self.Value(rect.x1)
            x2 = self.Value(rect.x2)
            y1 = self.Value(rect.y1)
            y2 = self.Value(rect.y2)

            rect_str = f"{x1},{y1},{x2},{y2}"
            # print(rect_str)

            rect_strs.append(rect_str)
            # print(f'Rect #{rect_id}: {x1},{y1} -> {x2},{y2}')

        # print(rect_strs)

        # sort the rectangles
        rect_strs = sorted(rect_strs)

        # single solution as a string
        solution_str = '-'.join(rect_strs)
        # print(solution_str)

        # store the solutions
        self.__solutions.append(solution_str)
        self.__unique_solutions.add(solution_str) # __unique_solutions is a set, so duplicates will get removed


    def solution_count(self):
        return self.__solution_count

    # returns all solutions  
    def get_solutions(self):
        return self.__solutions

    """
    returns unique solutions
    returns the permutation free list of solution strings  
    """
    def get_unique_solutions(self):
        return list(self.__unique_solutions) # __unique_solutions is a Set, convert to list


'''
non-API method. Used for testing and running locally / in a Notebook.
Draws the rectangles
'''

def drawRectsFromCoords(rect_coords, parent_rects):
    import matplotlib.pyplot as plt
    import matplotlib.patches as patches

    # TODO: to add support for multiple parent rects, update here
    xSize = parent_rects[0][0]
    ySize = parent_rects[0][1]

    # draw rectangle
    fig,ax = plt.subplots(1)
    plt.xlim(0,xSize)
    plt.ylim(0,ySize)
    plt.gca().set_aspect('equal', adjustable='box')
    
    # print coords
    coords = []
    colors = ['r', 'g', 'b', 'y', 'brown', 'black', 'violet', 'pink', 'gray', 'orange', 'b', 'y']
    for idx, coords in enumerate(rect_coords):
        x1=coords[0]
        y1=coords[1]
        x2=coords[2]
        y2=coords[3]
        # print(f"{x1}, {y1} -> {x2}, {y2}")

        width = abs(x1-x2)
        height = abs(y1-y2)
        # print(f"Rect#{idx}: {width}x{height}")

        # Create a Rectangle patch
        rect_shape = patches.Rectangle((x1,y1), width, height,facecolor=colors[idx])
        # Add the patch to the Axes
        ax.add_patch(rect_shape)
    plt.show()




# for testing
if __name__ == '__main__':

    child_rects = [
        # [1, 1],
        # [2, 2],
        # [1, 3],
        # [4, 3],
        # [2, 4],
        # [2, 2],

        [27, 17],
        [27, 17],
        [18, 56],

        # [3, 3],
        # [3, 3],
        # [3, 3],
        # [3, 3],
    ]

    # parent_rects = [[6,6]]
    parent_rects = [[84,72]]

    solutions = StockCutter(child_rects, parent_rects, output_json=False) # get the integer solution

    for sol in solutions:
        print(sol)
        drawRectsFromCoords(sol, parent_rects)

================================================
FILE: deployment/stock_cutter_1d.py
================================================
'''
Original Author: Serge Kruk
Original Version: https://github.com/sgkruk/Apress-AI/blob/master/cutting_stock.py

Updated by: Emad Ehsan
V2: https://github.com/emadehsan/Apress-AI/blob/master/my-models/custom_cutting_stock.py

V3 is following:
'''
from ortools.linear_solver import pywraplp
from math import ceil
from random import randint
import json

def newSolver(name,integer=False):
  return pywraplp.Solver(name,\
                         pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING \
                         if integer else \
                         pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)

'''
return a printable value
'''
def SolVal(x):
  if type(x) is not list:
    return 0 if x is None \
      else x if isinstance(x,(int,float)) \
           else x.SolutionValue() if x.Integer() is False \
                else int(x.SolutionValue())
  elif type(x) is list:
    return [SolVal(e) for e in x]

def ObjVal(x):
  return x.Objective().Value()


def gen_data(num_orders):
    R=[] # small rolls
    # S=0 # seed?
    for i in range(num_orders):
        R.append([randint(1,12), randint(5,40)])
    return R


def solve_model(demands, parent_width=100, cutStyle='exactCuts'):
  ''' demands = [
          [1, 3], # [quantity, width]
          [3, 5],
          ...
      ]

      parent_width = integer
  '''
  num_orders = len(demands)
  solver = newSolver('Cutting Stock', True)
  k,b  = bounds(demands, parent_width)

  # array of boolean declared as int, if y[i] is 1, 
  # then y[i] Big roll is used, else it was not used
  y = [ solver.IntVar(0, 1, f'y_{i}') for i in range(k[1]) ] 

  # x[i][j] = 3 means that small-roll width specified by i-th order
  # must be cut from j-th order, 3 tmies 
  x = [[solver.IntVar(0, b[i], f'x_{i}_{j}') for j in range(k[1])] \
      for i in range(num_orders)]
  
  unused_widths = [ solver.NumVar(0, parent_width, f'w_{j}') \
      for j in range(k[1]) ] 
  
  # will contain the number of big rolls used
  nb = solver.IntVar(k[0], k[1], 'nb')

  # consntraint: demand fullfilment
  for i in range(num_orders):  
    # small rolls from i-th order must be at least as many in quantity
    # as specified by the i-th order
    if cutStyle == 'minWaste':
      solver.Add(sum(x[i][j] for j in range(k[1])) >= demands[i][0]) 
    else:
      # probably cutStyle == exactCuts
      solver.Add(sum(x[i][j] for j in range(k[1])) == demands[i][0]) 

  # constraint: max size limit
  for j in range(k[1]):
    # total width of small rolls cut from j-th big roll, 
    # must not exceed big rolls width
    solver.Add( \
        sum(demands[i][1]*x[i][j] for i in range(num_orders)) \
        <= parent_width*y[j] \
      ) 

    # width of j-th big roll - total width of all orders cut from j-th roll
    # must be equal to unused_widths[j]
    # So, we are saying that assign unused_widths[j] the remaining width of j'th big roll
    solver.Add(parent_width*y[j] - sum(demands[i][1]*x[i][j] for i in range(num_orders)) == unused_widths[j])

    '''
    Book Author's note from page 201:
    [the following constraint]  breaks the symmetry of multiple solutions that are equivalent 
    for our purposes: any permutation of the rolls. These permutations, and there are K! of 
    them, cause most solvers to spend an exorbitant time solving. With this constraint, we 
    tell the solver to prefer those permutations with more cuts in roll j than in roll j + 1. 
    The reader is encouraged to solve a medium-sized problem with and without this 
    symmetry-breaking constraint. I have seen problems take 48 hours to solve without the 
    constraint and 48 minutes with. Of course, for problems that are solved in seconds, the 
    constraint will not help; it may even hinder. But who cares if a cutting stock instance 
    solves in two or in three seconds? We care much more about the difference between two 
    minutes and three hours, which is what this constraint is meant to address
    '''
    if j < k[1]-1: # k1 = total big rolls
      # total small rolls of i-th order cut from j-th big roll must be >=
      # totall small rolls of i-th order cut from j+1-th big roll
      solver.Add(sum(x[i][j] for i in range(num_orders)) >= sum(x[i][j+1] for i in range(num_orders)))

  # find & assign to nb, the number of big rolls used
  solver.Add(nb == solver.Sum(y[j] for j in range(k[1])))

  ''' 
    minimize total big rolls used
    let's say we have y = [1, 0, 1]
    here, total big rolls used are 2. 0-th and 2nd. 1st one is not used. So we want our model to use the 
    earlier rolls first. i.e. y = [1, 1, 0]. 
    The trick to do this is to define the cost of using each next roll to be higher. So the model would be
    forced to used the initial rolls, when available, instead of the next rolls.

    So instead of Minimize ( Sum of y ) or Minimize( Sum([1,1,0]) )
    we Minimize( Sum([1*1, 1*2, 1*3]) )
  ''' 

  '''
  Book Author's note from page 201:

  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
  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
  '''

  Cost = solver.Sum((j+1)*y[j] for j in range(k[1]))

  solver.Minimize(Cost)

  status = solver.Solve()
  numRollsUsed = SolVal(nb)

  return status, \
    numRollsUsed, \
    rolls(numRollsUsed, SolVal(x), SolVal(unused_widths), demands), \
    SolVal(unused_widths), \
    solver.WallTime()

def bounds(demands, parent_width=100):
  '''
  b = [sum of widths of individual small rolls of each order]
  T = local var. stores sum of widths of adjecent small-rolls. When the width reaches 100%, T is set to 0 again.
  k = [k0, k1], k0 = minimum big-rolls requierd, k1: number of big rolls that can be consumed / cut from
  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
  '''
  num_orders = len(demands)
  b = []
  T = 0
  k = [0,1]
  TT = 0

  for i in range(num_orders):
    # q = quantity, w = width; of i-th order
    quantity, width = demands[i][0], demands[i][1]
    # TODO Verify: why min of quantity, parent_width/width?
    # assumes widths to be entered as percentage
    # 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)
    # b.append( min(demands[i][0], int(round(parent_width / demands[i][1]))) )
    b.append( min(quantity, int(round(parent_width / width))) )

    # if total width of this i-th order + previous order's leftover (T) is less than parent_width
    # it's fine. Cut it.
    if T + quantity*width <= parent_width:
      T, TT = T + quantity*width, TT + quantity*width
    # else, the width exceeds, so we have to cut only as much as we can cut from parent_width width of the big roll
    else:
      while quantity:
        if T + width <= parent_width:
          T, TT, quantity = T + width, TT + width, quantity-1
        else:
          k[1],T = k[1]+1, 0 # use next roll (k[1] += 1)
  k[0] = int(round(TT/parent_width+0.5))

  print('k', k)
  print('b', b)

  return k, b

'''
  nb: array of number of rolls to cut, of each order
  
  w: 
  demands: [
    [quantity, width],
    [quantity, width],
    [quantity, width],
  ]
'''
def rolls(nb, x, w, demands):
  consumed_big_rolls = []
  num_orders = len(x) 
  # go over first row (1st order)
  # this row contains the list of all the big rolls available, and if this 1st (0-th) order
  # is cut from any big roll, that big roll's index would contain a number > 0
  for j in range(len(x[0])):
    # w[j]: width of j-th big roll 
    # 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 
    RR = [ abs(w[j])] + [ int(x[i][j])*[demands[i][1]] for i in range(num_orders) \
                    if x[i][j] > 0 ] # if i-th order has some cuts from j-th order, x[i][j] would be > 0
    consumed_big_rolls.append(RR)

  return consumed_big_rolls



'''
this model starts with some patterns and then optimizes those patterns
'''
def solve_large_model(demands, parent_width=100, cutStyle='exactCuts'):
  num_orders = len(demands)
  iter = 0
  patterns = get_initial_patterns(demands)
  # print('method#solve_large_model, patterns', patterns)

  # list quantities of orders
  quantities = [demands[i][0] for i in range(num_orders)]
  print('quantities', quantities)

  while iter < 20:
    status, y, l = solve_master(patterns, quantities, parent_width=parent_width, cutStyle=cutStyle)
    iter += 1

    # list widths of orders
    widths = [demands[i][1] for i in range(num_orders)]
    new_pattern, objectiveValue = get_new_pattern(l, widths, parent_width=parent_width)

    # print('method#solve_large_model, new_pattern', new_pattern)
    # print('method#solve_large_model, objectiveValue', objectiveValue)

    for i in range(num_orders):
      # add i-th cut of new pattern to i-thp pattern
      patterns[i].append(new_pattern[i])

  status, y, l = solve_master(patterns, quantities, parent_width=parent_width, integer=True, cutStyle=cutStyle)  

  return status, \
          patterns, \
          y, \
          rolls_patterns(patterns, y, demands, parent_width=parent_width)


'''
Dantzig-Wolfe decomposition splits the problem into a Master Problem MP and a sub-problem SP.

The Master Problem: provided a set of patterns, find the best combination satisfying the demand

C: patterns
b: demand
'''
def solve_master(patterns, quantities, parent_width=100, integer=False, cutStyle='exactCuts'):
  title = 'Cutting stock master problem'
  num_patterns = len(patterns)
  n = len(patterns[0])
  # print('**num_patterns x n: ', num_patterns, 'x', n)
  # print('**patterns recived:')
  # for p in patterns:
  #   print(p)

  constraints = []

  solver = newSolver(title, integer)

  # y is not boolean, it's an integer now (as compared to y in approach used by solve_model)
  y = [ solver.IntVar(0, 1000, '') for j in range(n) ] # right bound?
  # minimize total big rolls (y) used
  Cost = sum(y[j] for j in range(n)) 
  solver.Minimize(Cost)

  # for every pattern
  for i in range(num_patterns):
    # add constraint that this pattern (demand) must be met
    # there are m such constraints, for each pattern

    if cutStyle == 'minWaste':
      constraints.append(solver.Add( sum(patterns[i][j]*y[j] for j in range(n)) >= quantities[i]) ) 
    else:
      # probably cutStyle == exactCuts
      constraints.append(solver.Add( sum(patterns[i][j]*y[j] for j in range(n)) == quantities[i]) ) 


  status = solver.Solve()
  y = [int(ceil(e.SolutionValue())) for e in y]

  l =  [0 if integer else constraints[i].DualValue() for i in range(num_patterns)]
  # sl =  [0 if integer else constraints[i].name() for i in range(num_patterns)]
  # print('sl: ', sl)

  # l =  [0 if integer else u[i].Ub() for i in range(m)]
  toreturn = status, y, l
  # l_to_print = [round(dd, 2) for dd in toreturn[2]]
  # print('l: ', len(l_to_print), '->', l_to_print)
  # print('l: ', toreturn[2])
  return toreturn


'''
TODO Make sense of this:
'''
def get_new_pattern(l, w, parent_width=100):
  solver = newSolver('Cutting stock sub-problem', True)
  n = len(l)
  new_pattern = [ solver.IntVar(0, parent_width, '') for i in range(n) ]

  # maximizes the sum of the values times the number of occurrence of that roll in a pattern
  Cost = sum( l[i] * new_pattern[i] for i in range(n))
  solver.Maximize(Cost)

  # ensuring that the pattern stays within the total width of the large roll 
  solver.Add( sum( w[i] * new_pattern[i] for i in range(n)) <= parent_width ) 

  status = solver.Solve()
  return SolVal(new_pattern), ObjVal(solver)


'''
the initial patterns must be such that they will allow a feasible solution, 
one that satisfies all demands. 
Considering the already complex model, let’s keep it simple. 
Our initial patterns have exactly one roll per pattern, as obviously feasible as inefficient.
'''
def get_initial_patterns(demands):
  num_orders = len(demands)
  return [[0 if j != i else 1 for j in range(num_orders)]\
          for i in range(num_orders)]

def rolls_patterns(patterns, y, demands, parent_width=100):
  R, m, n = [], len(patterns), len(y)

  for j in range(n):
    for _ in range(y[j]):
      RR = []
      for i in range(m):
        if patterns[i][j] > 0:
          RR.extend( [demands[i][1]] * int(patterns[i][j]) )
      used_width = sum(RR)
      R.append([parent_width - used_width, RR])

  return R


'''
checks if all small roll widths (demands) smaller than parent roll's width
'''
def checkWidths(demands, parent_width):
  for quantity, width in demands:
    if width > parent_width:
      print(f'Small roll width {width} is greater than parent rolls width {parent_width}. Exiting')
      return False
  return True


'''
    params
        child_rolls: 
            list of lists, each containing quantity & width of rod / roll to be cut
            e.g.: [ [quantity, width], [quantity, width], ...]
        parent_rolls: 
            list of lists, each containing quantity & width of rod / roll to cut from
            e.g.: [ [quantity, width], [quantity, width], ...]
        cutStyle:
          there are two types of cutting style
          1. cut exactly as many items as specified: exactCuts
          2. cut some items more than specified to minimize waste: minWaste
'''
def StockCutter1D(child_rolls, parent_rolls, output_json=True, large_model=True, cutStyle='exactCuts'):

  # at the moment, only parent one width of parent rolls is supported
  # quantity of parent rolls is calculated by algorithm, so user supplied quantity doesn't matter?
  # TODO: or we can check and tell the user the user when parent roll quantity is insufficient
  parent_width = parent_rolls[0][1]

  if not checkWidths(demands=child_rolls, parent_width=parent_width):
    return []


  print('child_rolls', child_rolls)
  print('parent_rolls', parent_rolls)

  if not large_model:
    print('Running Small Model...')
    status, numRollsUsed, consumed_big_rolls, unused_roll_widths, wall_time = \
              solve_model(demands=child_rolls, parent_width=parent_width, cutStyle=cutStyle)

    # convert the format of output of solve_model to be exactly same as solve_large_model
    print('consumed_big_rolls before adjustment: ', consumed_big_rolls)
    new_consumed_big_rolls = []
    for big_roll in consumed_big_rolls:
      if len(big_roll) < 2:
        # sometimes the solve_model return a solution that contanis an extra [0.0] entry for big roll
        consumed_big_rolls.remove(big_roll)
        continue
      unused_width = big_roll[0]
      subrolls = []
      for subitem in big_roll[1:]:
        if isinstance(subitem, list):
          # if it's a list, concatenate with the other lists, to make a single list for this big_roll
          subrolls = subrolls + subitem
        else:
          # if it's an integer, add it to the list
          subrolls.append(subitem)
      new_consumed_big_rolls.append([unused_width, subrolls])
    print('consumed_big_rolls after adjustment: ', new_consumed_big_rolls)
    consumed_big_rolls = new_consumed_big_rolls
  
  else:
    print('Running Large Model...');
    status, A, y, consumed_big_rolls = solve_large_model(demands=child_rolls, parent_width=parent_width, cutStyle=cutStyle)

  numRollsUsed = len(consumed_big_rolls)
  # print('A:', A, '\n')
  # print('y:', y, '\n')


  STATUS_NAME = ['OPTIMAL',
    'FEASIBLE',
    'INFEASIBLE',
    'UNBOUNDED',
    'ABNORMAL',
    'NOT_SOLVED'
    ]

  output = {
      "statusName": STATUS_NAME[status],
      "numSolutions": '1',
      "numUniqueSolutions": '1',
      "numRollsUsed": numRollsUsed,
      "solutions": consumed_big_rolls # unique solutions
  }


  # print('Wall Time:', wall_time)
  print('numRollsUsed', numRollsUsed)
  print('Status:', output['statusName'])
  print('Solutions found :', output['numSolutions'])
  print('Unique solutions: ', output['numUniqueSolutions'])

  if output_json:
    return json.dumps(output)        
  else:
    return consumed_big_rolls


'''
Draws the big rolls on the graph. Each horizontal colored line represents one big roll.
In each big roll (multi-colored horizontal line), each color represents small roll to be cut from it.
If the big roll ends with a black color, that part of the big roll is unused width.

TODO: Assign each child roll a unique color
'''
def drawGraph(consumed_big_rolls, child_rolls, parent_width):
    import matplotlib.pyplot as plt
    import matplotlib.patches as patches

    # TODO: to add support for multiple different parent rolls, update here
    xSize = parent_width # width of big roll
    ySize = 10 * len(consumed_big_rolls) # one big roll will take 10 units vertical space

    # draw rectangle
    fig,ax = plt.subplots(1)
    plt.xlim(0, xSize)
    plt.ylim(0, ySize)
    plt.gca().set_aspect('equal', adjustable='box')
    
    # print coords
    coords = []
    colors = ['r', 'g', 'b', 'y', 'brown', 'violet', 'pink', 'gray', 'orange', 'b', 'y']
    colorDict = {}
    i = 0
    for quantity, width in child_rolls:
      colorDict[width] = colors[i % 11]
      i+= 1

    # start plotting each big roll horizontly, from the bottom
    y1 = 0
    for i, big_roll in enumerate(consumed_big_rolls):
      '''
        big_roll = [leftover_width, [small_roll_1_1, small_roll_1_2, other_small_roll_2_1]]
      '''
      unused_width = big_roll[0]
      small_rolls = big_roll[1]

      x1 = 0
      x2 = 0
      y2 = y1 + 8 # the height of each big roll will be 8 
      for j, small_roll in enumerate(small_rolls):
        x2 = x2 + small_roll
        print(f"{x1}, {y1} -> {x2}, {y2}")
        width = abs(x1-x2)
        height = abs(y1-y2)
        # print(f"Rect#{idx}: {width}x{height}")
        # Create a Rectangle patch
        rect_shape = patches.Rectangle((x1,y1), width, height, facecolor=colorDict[small_roll], label=f'{small_roll}')
        ax.add_patch(rect_shape) # Add the patch to the Axes
        x1 = x2 # x1 for next small roll in same big roll will be x2 of current roll 

      # now that all small rolls have been plotted, check if a there is unused width in this big roll
      # set the unused width at the end as black colored rectangle
      if unused_width > 0:
        width = unused_width
        rect_shape = patches.Rectangle((x1,y1), width, height, facecolor='black', label='Unused')
        ax.add_patch(rect_shape) # Add the patch to the Axes

      y1 += 10 # next big roll will be plotted on top of current, a roll height is 8, so 2 will be margin between rolls

    plt.show()


if __name__ == '__main__':

  child_rolls = [
    # [quantity, width],
    # [6, 25],
    # [12, 21],
    # [7, 26],
    # [3, 23],
    # [8, 33],
    # [2, 15],
    # [2, 34],

    # [3, 3],
    # [3, 4],

    [3,3],
    [3,1],
    [2,4],
    [2,2]

    # [3,30],
    # [2,72],
    # [5,50]
  ]

  # child_rolls = gen_data(3)
  # parent_rolls = [[10, 120]]
  # parent_rolls = [[10, 8]]
  parent_rolls = [[10, 6]]
  # parent_rolls = [[10, 144]]


  consumed_big_rolls = StockCutter1D(child_rolls, parent_rolls, output_json=False, large_model=False)
  print (consumed_big_rolls)

  for idx, roll in enumerate(consumed_big_rolls):
    print(f'Roll #{idx}:', roll)


  drawGraph(consumed_big_rolls, child_rolls, parent_width=parent_rolls[0][1])

================================================
FILE: infile.txt
================================================
58	58	38
58	58	47.25
58	58	47.25
58	58	47
58	58	22.75
58	58	58.25
58.25	58.25	58.25
58.25	58.25	58.5
58	58	71
58	58	28.5
		
		
		
34.5	34.5	64.5
16.5	16.5	70
46.5	46.5	47
23	23	75

================================================
FILE: tests/basic_test.py
================================================
import pytest
from csp.read_lengths import get_data

def test_get_data():
    infile = "infile.txt"
    nrs = get_data(infile)
    print(nrs)
    assert nrs[0][1] == 38
Download .txt
gitextract_vyf0nlkm/

├── .gitignore
├── LICENSE
├── Pipfile
├── README.md
├── csp/
│   ├── __init__.py
│   ├── read_lengths.py
│   └── stock_cutter_1d.py
├── deployment/
│   ├── .editorconfig
│   ├── .gitignore
│   ├── Procfile
│   ├── csp.py
│   ├── frontend/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── babel.config.js
│   │   ├── package.json
│   │   ├── public/
│   │   │   └── index.html
│   │   └── src/
│   │       ├── App.vue
│   │       ├── assets/
│   │       │   └── style.scss
│   │       ├── components/
│   │       │   └── CspTool.vue
│   │       └── main.js
│   ├── requirements.txt
│   ├── server.py
│   ├── stock_cutter.py
│   └── stock_cutter_1d.py
├── infile.txt
└── tests/
    └── basic_test.py
Download .txt
SYMBOL INDEX (55 symbols across 7 files)

FILE: csp/read_lengths.py
  function get_data (line 5) | def get_data(infile:str)->List[float]:

FILE: csp/stock_cutter_1d.py
  function newSolver (line 15) | def newSolver(name,integer=False):
  function SolVal (line 24) | def SolVal(x):
  function ObjVal (line 33) | def ObjVal(x):
  function gen_data (line 37) | def gen_data(num_orders):
  function solve_model (line 45) | def solve_model(demands, parent_width=100):
  function bounds (line 147) | def bounds(demands, parent_width=100):
  function rolls (line 197) | def rolls(nb, x, w, demands):
  function solve_large_model (line 217) | def solve_large_model(demands, parent_width=100):
  function solve_master (line 258) | def solve_master(patterns, quantities, parent_width=100, integer=False):
  function get_new_pattern (line 297) | def get_new_pattern(l, w, parent_width=100):
  function get_initial_patterns (line 319) | def get_initial_patterns(demands):
  function rolls_patterns (line 324) | def rolls_patterns(patterns, y, demands, parent_width=100):
  function checkWidths (line 342) | def checkWidths(demands, parent_width):
  function StockCutter1D (line 359) | def StockCutter1D(child_rolls, parent_rolls, output_json=True, large_mod...
  function drawGraph (line 444) | def drawGraph(consumed_big_rolls, child_rolls, parent_width):
  function main (line 510) | def main(infile_name: Optional[str] = typer.Argument(None)):

FILE: deployment/csp.py
  function StockCutter (line 19) | def StockCutter(child_rects, parent_rects):
  function drawRectsFromCoords (line 127) | def drawRectsFromCoords(rect_coords):
  function solutions_to_int (line 163) | def solutions_to_int(str_solutions):
  class VarArraySolutionPrinter (line 231) | class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):
    method __init__ (line 233) | def __init__(self, variables):
    method on_solution_callback (line 242) | def on_solution_callback(self):
    method solution_count (line 277) | def solution_count(self):
    method get_solutions (line 281) | def get_solutions(self):
    method get_unique_solutions (line 288) | def get_unique_solutions(self):

FILE: deployment/server.py
  function get_csp (line 12) | def get_csp():
  function post_stocks_1d (line 20) | def post_stocks_1d():
  function post_stocks (line 56) | def post_stocks():

FILE: deployment/stock_cutter.py
  function StockCutter (line 20) | def StockCutter(child_rects, parent_rects, output_json=True):
  function getSingleSolution (line 195) | def getSingleSolution(solver, all_vars):
  function str_solutions_to_int (line 226) | def str_solutions_to_int(str_solutions):
  class VarArraySolutionPrinter (line 292) | class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):
    method __init__ (line 294) | def __init__(self, variables):
    method on_solution_callback (line 303) | def on_solution_callback(self):
    method solution_count (line 338) | def solution_count(self):
    method get_solutions (line 342) | def get_solutions(self):
    method get_unique_solutions (line 349) | def get_unique_solutions(self):
  function drawRectsFromCoords (line 358) | def drawRectsFromCoords(rect_coords, parent_rects):

FILE: deployment/stock_cutter_1d.py
  function newSolver (line 15) | def newSolver(name,integer=False):
  function SolVal (line 24) | def SolVal(x):
  function ObjVal (line 33) | def ObjVal(x):
  function gen_data (line 37) | def gen_data(num_orders):
  function solve_model (line 45) | def solve_model(demands, parent_width=100, cutStyle='exactCuts'):
  function bounds (line 150) | def bounds(demands, parent_width=100):
  function rolls (line 200) | def rolls(nb, x, w, demands):
  function solve_large_model (line 220) | def solve_large_model(demands, parent_width=100, cutStyle='exactCuts'):
  function solve_master (line 261) | def solve_master(patterns, quantities, parent_width=100, integer=False, ...
  function get_new_pattern (line 310) | def get_new_pattern(l, w, parent_width=100):
  function get_initial_patterns (line 332) | def get_initial_patterns(demands):
  function rolls_patterns (line 337) | def rolls_patterns(patterns, y, demands, parent_width=100):
  function checkWidths (line 355) | def checkWidths(demands, parent_width):
  function StockCutter1D (line 376) | def StockCutter1D(child_rolls, parent_rolls, output_json=True, large_mod...
  function drawGraph (line 461) | def drawGraph(consumed_big_rolls, child_rolls, parent_width):

FILE: tests/basic_test.py
  function test_get_data (line 4) | def test_get_data():
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (131K chars).
[
  {
    "path": ".gitignore",
    "chars": 17,
    "preview": "__pycache__\n.idea"
  },
  {
    "path": "LICENSE",
    "chars": 1067,
    "preview": "MIT License\n\nCopyright (c) 2020 Emad Ehsan\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
  },
  {
    "path": "Pipfile",
    "chars": 194,
    "preview": "[[source]]\nurl = \"https://pypi.org/simple\"\nverify_ssl = true\nname = \"pypi\"\n\n[packages]\nortools = \"*\"\npytest = \"*\"\nmatplo"
  },
  {
    "path": "README.md",
    "chars": 3026,
    "preview": "# Cutting Stock Problem\nCutting Stock Problem (CSP) deals with planning the cutting of items (rods / sheets) from given "
  },
  {
    "path": "csp/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "csp/read_lengths.py",
    "chars": 512,
    "preview": "import pathlib\nfrom typing import List\nimport re\nfrom math import ceil\ndef get_data(infile:str)->List[float]:\n    \"\"\" Re"
  },
  {
    "path": "csp/stock_cutter_1d.py",
    "chars": 18876,
    "preview": "'''\nOriginal Author: Serge Kruk\nOriginal Version: https://github.com/sgkruk/Apress-AI/blob/master/cutting_stock.py\n\nUpda"
  },
  {
    "path": "deployment/.editorconfig",
    "chars": 300,
    "preview": "# Editor configuration, see http://editorconfig.org\n# source: https://stackoverflow.com/a/51398290/3578289\nroot = true\n\n"
  },
  {
    "path": "deployment/.gitignore",
    "chars": 263,
    "preview": "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"
  },
  {
    "path": "deployment/Procfile",
    "chars": 24,
    "preview": "web: gunicorn server:app"
  },
  {
    "path": "deployment/csp.py",
    "chars": 9978,
    "preview": "from __future__ import print_function\nimport collections, json\nfrom ortools.sat.python import cp_model\n\n# to draw rectan"
  },
  {
    "path": "deployment/frontend/.gitignore",
    "chars": 231,
    "preview": ".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*\nyar"
  },
  {
    "path": "deployment/frontend/README.md",
    "chars": 320,
    "preview": "# frontend\n\n## Project setup\n```\nnpm install\n```\n\n### Compiles and hot-reloads for development\n```\nnpm run serve\n```\n\n##"
  },
  {
    "path": "deployment/frontend/babel.config.js",
    "chars": 73,
    "preview": "module.exports = {\n  presets: [\n    '@vue/cli-plugin-babel/preset'\n  ]\n}\n"
  },
  {
    "path": "deployment/frontend/package.json",
    "chars": 982,
    "preview": "{\n  \"name\": \"frontend\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n  "
  },
  {
    "path": "deployment/frontend/public/index.html",
    "chars": 611,
    "preview": "<!DOCTYPE html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=ed"
  },
  {
    "path": "deployment/frontend/src/App.vue",
    "chars": 402,
    "preview": "<template>\n  <CspTool msg=\"Let's Go\"/>\n</template>\n\n<script>\nimport CspTool from './components/CspTool.vue'\n\nexport defa"
  },
  {
    "path": "deployment/frontend/src/assets/style.scss",
    "chars": 654,
    "preview": "\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 s"
  },
  {
    "path": "deployment/frontend/src/components/CspTool.vue",
    "chars": 50118,
    "preview": "<template>\n  <span>\n    <!-- As a heading -->\n    <nav class=\"navbar navbar-light bg-light\">\n      <div class=\"container"
  },
  {
    "path": "deployment/frontend/src/main.js",
    "chars": 504,
    "preview": "import { createApp } from 'vue'\n\n// import Vue from 'vue'\n// import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'\n/"
  },
  {
    "path": "deployment/requirements.txt",
    "chars": 34,
    "preview": "gunicorn\nFlask\nFlask-Cors\nortools\n"
  },
  {
    "path": "deployment/server.py",
    "chars": 1780,
    "preview": "from flask import Flask, json, request\nfrom flask_cors import CORS, cross_origin\n\nimport stock_cutter # local module\n\nap"
  },
  {
    "path": "deployment/stock_cutter.py",
    "chars": 14388,
    "preview": "'''\n@Author Emad Ehsan\nCutting Stock problem 2D\nNot complete.\nWhat's remaining: Finding Optimized solution that minimize"
  },
  {
    "path": "deployment/stock_cutter_1d.py",
    "chars": 19664,
    "preview": "'''\nOriginal Author: Serge Kruk\nOriginal Version: https://github.com/sgkruk/Apress-AI/blob/master/cutting_stock.py\n\nUpda"
  },
  {
    "path": "infile.txt",
    "chars": 179,
    "preview": "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"
  },
  {
    "path": "tests/basic_test.py",
    "chars": 169,
    "preview": "import pytest\nfrom csp.read_lengths import get_data\n\ndef test_get_data():\n    infile = \"infile.txt\"\n    nrs = get_data(i"
  }
]

About this extraction

This page contains the full source code of the emadehsan/csp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (121.5 KB), approximately 32.9k tokens, and a symbol index with 55 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!