Capacitated Lot-Sizing Problem (CLSP)#

clsp.py

"""
## LICENSETYPE: Demo
## MODELTYPE: MIP


Capacitated Lot-Sizing Problem (CLSP)
"""

from __future__ import annotations

import os
from itertools import product

import pandas as pd
from gamspy import (
    Alias,
    Container,
    Equation,
    Model,
    Parameter,
    Sense,
    Set,
    Sum,
    Variable,
)


def model_data():
    # Sets
    products = ["Product_A", "Product_B", "Product_C"]
    time_periods = [1, 2, 3, 4]
    resources = ["Resource_A", "Resource_B"]

    kj = pd.DataFrame(
        product(products, resources)
    )  # product-resource combinations -->  ("Product_A", "Resource_A"), ("Product_B", "Resource_A"), ...

    # Parameters
    demand_data = pd.DataFrame(
        {
            "Product_A": {1: 100, 2: 150, 3: 120, 4: 180},
            "Product_B": {1: 80, 2: 100, 3: 90, 4: 120},
            "Product_C": {1: 50, 2: 60, 3: 70, 4: 80},
        }
    ).unstack()

    setup_cost_data = pd.DataFrame(
        [("Product_A", 100), ("Product_B", 200), ("Product_C", 300)]
    )
    holding_cost_data = pd.DataFrame(
        [("Product_A", 0.2), ("Product_B", 0.1), ("Product_C", 0.6)]
    )

    capacity_data = pd.DataFrame(
        [
            ("Resource_A", 1, 340),
            ("Resource_B", 1, 340),
            ("Resource_A", 2, 330),
            ("Resource_B", 2, 330),
            ("Resource_A", 3, 300),
            ("Resource_B", 3, 300),
            ("Resource_A", 4, 380),
            ("Resource_B", 4, 380),
        ]
    )

    return {
        "products": products,
        "time_periods": time_periods,
        "resources": resources,
        "kj": kj,
        "demand_data": demand_data,
        "setup_cost_data": setup_cost_data,
        "holding_cost_data": holding_cost_data,
        "capacity_data": capacity_data,
    }


def main():
    m = Container(
        system_directory=os.getenv("SYSTEM_DIRECTORY", None),
    )
    data = model_data()

    # SETS
    k = Set(
        m,
        name="k",
        description="products",
        records=data["products"],
    )
    j = Set(m, name="j", description="resources", records=data["resources"])
    t = Set(
        m, name="t", description="time periods", records=data["time_periods"]
    )

    KJ = Set(
        m,
        name="KJ",
        domain=[k, j],
        description="products k that can be handled by resource j",
        records=data["kj"],
    )

    # ALIAS
    tau = Alias(m, name="tau", alias_with=t)

    # PARAMETERS
    d = Parameter(
        m,
        name="d",
        domain=[k, t],
        description="demand of product k in period t",
        records=data["demand_data"],
    )
    s = Parameter(
        m,
        name="s",
        domain=k,
        description="fixed setup cost for product k",
        records=data["setup_cost_data"],
    )
    h = Parameter(
        m,
        name="h",
        domain=k,
        description="holding cost for product k",
        records=data["holding_cost_data"],
    )
    c = Parameter(
        m,
        name="c",
        domain=[j, t],
        description="production capacity of resource j in period t",
        records=data["capacity_data"],
    )

    # VARIABLES
    X = Variable(
        m,
        name="X",
        domain=[k, t],
        type="positive",
        description="lot size of product k in period t",
    )
    Y = Variable(
        m,
        name="Y",
        domain=[k, t],
        type="binary",
        description="indicates if product k is manufactured in period t",
    )
    Z = Variable(
        m,
        name="Z",
        domain=[k, t],
        type="positive",
        description="stock of product k in period t",
    )

    # EQUATIONS
    objective = Sum((k, t), s[k] * Y[k, t] + h[k] * Z[k, t])

    stock = Equation(
        m, name="stock", domain=[k, t], description="Stock balance equation"
    )
    stock[...] = Z[k, t] == Z[k, t.lag(1)] + X[k, t] - d[k, t]

    production = Equation(
        m, name="production", domain=[k, t], description="Ensure production"
    )
    production[...] = X[k, t] <= Y[k, t] * Sum(tau, d[k, tau])

    capacity = Equation(
        m, name="capacity", domain=[j, t], description="Capacity restriction"
    )
    capacity[...] = Sum(KJ[k, j], X[k, t]) <= c[j, t]

    Z.fx[k, t].where[t.last] = 0

    # Model definition
    clsp = Model(
        m,
        name="CLSP",
        problem="MIP",
        equations=m.getEquations(),
        sense=Sense.MIN,
        objective=objective,
    )

    clsp.solve()

    import math

    assert math.isclose(clsp.objective_value, 1694, rel_tol=0.001)

    print("Objective function value:", clsp.objective_value)

    print("X: \n", X.pivot(index="t", columns="k"))

    print("Z: \n", Z.pivot(index="t", columns="k"))


if __name__ == "__main__":
    main()