Download
"""
PyCSP3 Model (see pycsp.org)

Data can come:
 - either directly from a JSON file
 - or from an intermediate parser

Example:
  python Vellino.py -data=Vellino-example.json
"""

from pycsp3 import *

Unusable, Red, Blue, Green = BIN_COLORS = 0, 1, 2, 3  # 0 is a special color 'Unusable' for any empty bin
Glass, Plastic, Steel, Wood, Copper = MATERIALS = 0, 1, 2, 3, 4
nColors, nMaterials = len(BIN_COLORS), len(MATERIALS)

capacities, demands = data
capacities.insert(0, 0)  # unusable bins have capacity 0
maxCapacity, nBins = max(capacities), sum(demands)

# c[i] is the color of the ith bin
c = VarArray(size=nBins, dom=range(nColors))

# p[i][j] is the number of components of the jth material put in the ith bin
p = VarArray(size=[nBins, nMaterials], dom=lambda i, j: range(min(maxCapacity, demands[j]) + 1))

satisfy(
    # every bin with a real colour must contain something, and vice versa
    [(c[i] == Unusable) == (Sum(p[i]) == 0) for i in range(nBins)],

    # all components of each material are spread across all bins
    [Sum(p[:, j]) == demands[j] for j in range(nMaterials)],

    # the capacity of each bin is not exceeded
    [Sum(p[i]) <= capacities[c[i]] for i in range(nBins)],

    # red bins cannot contain plastic or steel
    [(c[i] != Red) | ((p[i][Plastic] == 0) & (p[i][Steel] == 0)) for i in range(nBins)],

    # blue bins cannot contain wood or plastic
    [(c[i] != Blue) | ((p[i][Wood] == 0) & (p[i][Plastic] == 0)) for i in range(nBins)],

    # green bins cannot contain steel or glass
    [(c[i] != Green) | ((p[i][Steel] == 0) & (p[i][Glass] == 0)) for i in range(nBins)],

    # red bins contain at most one wooden component
    [(c[i] != Red) | (p[i][Wood] <= 1) for i in range(nBins)],

    # green bins contain at most two wooden components
    [(c[i] != Green) | (p[i][Wood] <= 2) for i in range(nBins)],

    # wood requires plastic
    [(p[i][Wood] == 0) | (p[i][Plastic] > 0) for i in range(nBins)],

    # glass excludes copper
    [(p[i][Glass] == 0) | (p[i][Copper] == 0) for i in range(nBins)],

    # copper excludes plastic
    [(p[i][Copper] == 0) | (p[i][Plastic] == 0) for i in range(nBins)],

    # tag(symmetry-breaking)
    [LexIncreasing(p[i], p[i + 1]) for i in range(nBins - 1)]
)

minimize(
    # minimizing the number of used bins
    Sum(c[i] != Unusable for i in range(nBins))
)

""" Comments
1) writing capacities = [0] + capacities is not possible because the new built list 
   is there not from the specific type of list we need. One would need to call
   the function cp_array
"""