# --------------------------------------------------------------------------
# Source file provided under Apache License, Version 2.0, January 2004,
# http://www.apache.org/licenses/
# (c) Copyright IBM Corp. 2015, 2016
# --------------------------------------------------------------------------

"""
The problem is to build steel coils from slabs that are available in a
work-in-process inventory of semi-finished products.
There is no limitation in the number of slabs that can be requested,
but only a finite number of slab sizes is available
(sizes 11, 13, 16, 17, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 33, 34, 40, 43, 45).
The problem is to select a number of slabs to build the coil orders,
and to satisfy the following constraints:

    * A coil order can be built from only one slab.
    * Each coil order requires a specific process to build it from a
      slab. This process is encoded by a "color".
    * Several coil orders can be built from the same slab, but a slab can
      be used to produce at most two different "colors" of coils.
    * The sum of the sizes of each coil order built from a slab must not
      exceed the slab size.

Finally, the production plan should minimize the unused capacity of the
selected slabs.

This problem is based on "prob038: Steel mill slab design problem" from
CSPLib (www.csplib.org). It is a simplification of an industrial problem
described in J. R. Kalagnanam, M. W. Dawande, M. Trumbo, H. S. Lee.
"Inventory Matching Problems in the Steel Industry," IBM Research
Report RC 21171, 1998.

Please refer to documentation for appropriate setup of solving configuration.
"""

from docplex.cp.model import CpoModel
from collections import namedtuple


#-----------------------------------------------------------------------------
# Initialize the problem data
#-----------------------------------------------------------------------------

# List of coils to produce (orders)
Order = namedtuple("Order", ['id', 'weight', 'color'])
ORDERS = (
            Order( 1, 22, 5),
            Order( 2,  9, 3),
            Order( 3,  9, 4),
            Order( 4,  8, 5),
            Order( 5,  8, 7),
            Order( 6,  6, 3),
            Order( 7,  5, 6),
            Order( 8,  3, 0),
            Order( 9,  3, 2),
            Order(10,  3, 3),
            Order(11,  2, 1),
            Order(12,  2, 5)
         )

# Max number of different colors of coils produced by a single slab
MAX_COLOR_PER_SLAB = 2

# List of available slab weights.
AVAILABLE_SLAB_WEIGHTS = [11, 13, 16, 17, 19, 20, 23, 24, 25,
                          26, 27, 28, 29, 30, 33, 34, 40, 43, 45]


#-----------------------------------------------------------------------------
# Prepare the data for modeling
#-----------------------------------------------------------------------------

# Upper bound for the number of slabs to use
MAX_SLABS = len(ORDERS)

# Build a set of all colors
allcolors = set(o.color for o in ORDERS)

# The heaviest slab
max_slab_weight = max(AVAILABLE_SLAB_WEIGHTS)

# Minimum loss incurred for a given slab usage.
# loss[v] = loss when smallest slab is used to produce a total weight of v
loss = [0] + [min([sw - use for sw in AVAILABLE_SLAB_WEIGHTS if sw >= use]) for use in range(1, max_slab_weight + 1)]


#-----------------------------------------------------------------------------
# Build the model
#-----------------------------------------------------------------------------

# Create model 
mdl = CpoModel()

# Index of the slab used to produce each coil order
production_slab = mdl.integer_var_list(len(ORDERS), 0, MAX_SLABS - 1, "production_slab")

# Usage of each slab
slab_use = mdl.integer_var_list(MAX_SLABS, 0, max_slab_weight, "slab_use")

# The orders are allocated to the slabs with capacity
mdl.add(mdl.pack(slab_use, production_slab, [o.weight for o in ORDERS]))

# Constrain max number of colors produced by each slab
for s in range(MAX_SLABS):
   su = 0
   for c in allcolors:
       lo = False
       for i, o in enumerate(ORDERS):
           if o.color == c:
               lo |= (production_slab[i] == s)
       su += lo
   mdl.add(su <= MAX_COLOR_PER_SLAB)

# Minimize the total loss
total_loss = sum([mdl.element(slab_use[s], loss) for s in range(MAX_SLABS)])
mdl.add(mdl.minimize(total_loss))

# Set search strategy
mdl.set_search_phases([mdl.search_phase(production_slab)])


#-----------------------------------------------------------------------------
# Solve the model and display the result
#-----------------------------------------------------------------------------

# Solve model
print("Solving model....")
msol = mdl.solve(FailLimit=100000, TimeLimit=10)

# Print solution
if msol:
    print("Solution: ")
    for s in set(msol[ps] for ps in production_slab):
        # Determine orders using this slab
        lordrs = [o for i, o in enumerate(ORDERS) if msol[production_slab[i]] == s]
        # Compute display attributes
        used_weight = msol[slab_use[s]]          # Weight used in the slab
        loss_weight = loss[used_weight]          # Loss weight
        colors = set(o.color for o in lordrs)    # List of colors
        loids = [o.id for o in lordrs]           # List of order irs
        print("Slab weight={}, used={}, loss={}, colors={}, orders={}"
              .format(used_weight + loss_weight, used_weight, loss_weight, colors, loids))
else:
    print("No solution found")




    
    



