Flow Shop Scheduling - (FLOWSHOP)#

`flowshop.py`

```"""
## GAMSSOURCE: https://www.gams.com/latest/gamslib_ml/libhtml/gamslib_flowshop.html
## MODELTYPE: MIP
## KEYWORDS: mixed integer linear programming, relaxed mixed integer programming, scenario analysis, GUSS, flow shop scheduling, production planning

Flow Shop Scheduling - (FLOWSHOP)

A workshop that produces metal pipes on demand for automotive industry
has three machines for bending the pipes, soldering the fastenings,
and assembling the links. The workshop has to produce six items, for
which the durations of the processing steps are given below. Once
started, jobs must be carried out to completion, but the
workpieces(items) may wait between the machines.

Every machine only processes one item at a time. A workpiece(item) may
not overtake any other.

What is the sequence that minimizes the total time for completing all
items (makespan)?

Gueret, C, Prins, C, and Sevaux, M, Applications of Optimization with
Xpress-MP, Translated and revised by Susanne Heipcke. Dash
Optimization, 2002.
"""

from __future__ import annotations

import os

import pandas as pd

import gamspy as gap

def flow_shop(process_time_df, last_machine, last_item):
c = gap.Container(
system_directory=os.getenv("SYSTEM_DIRECTORY", None),
delayed_execution=int(os.getenv("DELAYED_EXECUTION", False)),
)

# Sets
name="i", description="item", records=process_time_df["item"].unique()
)
name="m",
description="machine",
records=process_time_df["machine"].unique(),
)

# Parameters
name="proctime",
domain=[m, i],
description="process time of item i on machine m",
records=process_time_df,
)

# Variables
name="rank",
type="binary",
domain=[i, k],
description="item i has position k",
)
name="start",
type="positive",
domain=[m, k],
description="start time for job in position k on m",
)
name="comp",
type="positive",
domain=[m, k],
description="completion time for job in position k on m",
)
name="totwait",
type="free",
description="before first job + times between jobs on last machine",
)

# Equations
name="oneInPosition",
domain=k,
description="every position gets a jobs",
)
name="oneRankPer",
domain=i,
description="every job is assigned a rank",
)
name="onMachRel",
domain=[m, k],
)
name="perMachRel",
domain=[m, k],
)
name="defComp",
domain=[m, k],
description="completion time based on start time and proctime",
)
name="defObj",
description="completion time of job rank last",
)

oneInPosition[k] = gap.Sum(i, rank[i, k]) == 1
oneRankPer[i] = gap.Sum(k, rank[i, k]) == 1
defComp[m, k] = comp[m, k] == start[m, k] + gap.Sum(
i, proctime[m, i] * rank[i, k]
)

defObj[...] = totwait >= comp[last_machine, last_item]

flowshop = gap.Model(
container=c,
name="flowshop",
equations=c.getEquations(),
problem="MIP",
sense=gap.Sense.MIN,
objective=totwait,
)

flowshop.solve(options=gap.Options(relative_optimality_gap=0))

import math

assert math.isclose(flowshop.objective_value, 35, rel_tol=0.001)

def prepare_data():
# Prepare data
process_time = pd.DataFrame(
[
["blending", "i1", 3],
["blending", "i2", 6],
["blending", "i3", 3],
["blending", "i4", 5],
["blending", "i5", 5],
["blending", "i6", 7],
["soldering", "i1", 5],
["soldering", "i2", 4],
["soldering", "i3", 2],
["soldering", "i4", 4],
["soldering", "i5", 4],
["soldering", "i6", 5],
["assembly", "i1", 5],
["assembly", "i2", 2],
["assembly", "i3", 4],
["assembly", "i4", 6],
["assembly", "i5", 3],
["assembly", "i6", 6],
],
columns=["machine", "item", "value"],
)

last_item = process_time["item"].unique()[-1]
last_machine = process_time["machine"].unique()[-1]

return process_time, last_machine, last_item

if __name__ == "__main__":
process_time, last_machine, last_item = prepare_data()
flow_shop(
process_time_df=process_time,
last_machine=last_machine,
last_item=last_item,
)
```