The following is a Pandas-assisted PuLP implementation. First, the implementation with comments:
import pandas as pd
import pulp
def make_data() -> tuple[
pd.DataFrame, # options
pd.DataFrame, # cleaning
pd.Series, # item runtimes
int, # time buffer
]:
"""Create problem dataframes and characteristic parameters"""
item_idx = pd.Index(name='item', data=('A', 'B', 'C', 'D', 'E'))
options = pd.DataFrame(
data=((
(1, 'A', 1),
(1, 'A', 2),
(1, 'B', 1),
(1, 'B', 2),
(2, 'A', 1),
(2, 'A', 2),
(2, 'D', 1),
(2, 'D', 2),
(3, 'E', 1),
(3, 'E', 2),
)),
columns=('order', 'item', 'machine'),
)
item_runtimes = pd.Series(
name='runtime', data=(50, 60, 70, 80, 90),
index=item_idx,
)
# This should be much larger than the difference between any two times within an order
time_buffer = item_runtimes.max() * len(options)
cleaning = pd.DataFrame(
index=item_idx.rename('curr_item'),
columns=item_idx.rename('prev_item'),
data=((
(0, 0, 1, 1, 2),
(0, 0, 2, 1, 0),
(0, 0, 0, 1, 1),
(0, 0, 0, 0, 1),
(1, 0, 0, 0, 0),
)),
)
return options, cleaning, item_runtimes, time_buffer
def make_problem() -> tuple[
pulp.LpProblem,
pulp.LpVariable, # total runtime
]:
prob = pulp.LpProblem(name='order_machine_processing', sense=pulp.LpMinimize)
total_runtime = pulp.LpVariable(name='runtime', lowBound=0)
prob.objective = pulp.LpAffineExpression(total_runtime)
return prob, total_runtime
def make_option_vars(row: pd.Series, prob: pulp.LpProblem, item_runtimes: pd.Series, time_buffer: int) -> pd.Series:
"""Given a row from the options dataframe, create and constrain its LP variables"""
suffix = f'_o{row.order}_i{row["item"]}_m{row.machine}'
asn = pulp.LpVariable(name='asn' + suffix, cat=pulp.const.LpBinary)
start = pulp.LpVariable(name='start' + suffix, lowBound=0)
dur = pulp.LpVariable(name='dur' + suffix, lowBound=0, upBound=item_runtimes[row['item']])
# If non-assigned, the runtime is zero
prob.addConstraint(name='unused' + suffix, constraint=dur <= asn*time_buffer)
return pd.Series((asn, start, dur))
def make_item_vars(group: pd.DataFrame, prob: pulp.LpProblem, item_runtimes: pd.Series, time_buffer: int) -> pd.Series:
"""Given an order-item group dataframe from the options, create and constrain its LP variables"""
order, item = group.index[0]
suffix = f'_o{order}_i{item}'
# There must be at least one machine assigned to each order-item pair
total = pulp.lpSum(group.asn)
prob.addConstraint(name='assign' + suffix, constraint=total >= 1)
# The runtime of each machine in an order-item pair must sum to the runtime of the item
prob.addConstraint(
name='time' + suffix,
constraint=pulp.lpSum(group.dur) == item_runtimes[item]
)
start = pulp.LpVariable(name='start' + suffix, lowBound=0)
# Duration for an order-item pair is nonzero
dur = pulp.LpVariable(name='dur' + suffix, lowBound=1, upBound=item_runtimes[item])
# We assume that if multiple machines are working on one order item, they must run at the same time.
for _, row in group.iterrows():
tolerance = time_buffer*(1 - row.asn)
prefix = f'match{suffix}_m{row.machine}_'
start_diff = row.start - start
prob.addConstraint(name=prefix + 'startlo', constraint=start_diff <= tolerance)
prob.addConstraint(name=prefix + 'starthi', constraint=-start_diff <= tolerance)
dur_diff = row.dur - dur
prob.addConstraint(name=prefix + 'durlo', constraint=dur_diff <= tolerance)
prob.addConstraint(name=prefix + 'durhi', constraint=-dur_diff <= tolerance)
return pd.Series((start, dur))
def make_vars(prob: pulp.LpProblem, options: pd.DataFrame, item_runtimes: pd.Series, time_buffer: int) -> None:
"""Make all the remaining LP variables and add the constraints specific to them"""
options[['asn', 'start', 'dur']] = options.apply(
make_option_vars, axis=1, prob=prob, item_runtimes=item_runtimes, time_buffer=time_buffer)
(
options.set_index(keys=['order', 'item'])
.groupby(level=[0, 1])
.apply(make_item_vars, prob=prob, item_runtimes=item_runtimes, time_buffer=time_buffer)
)
def constrain_total_runtime(prob: pulp.LpProblem, options: pd.DataFrame, total_runtime: pulp.LpVariable) -> None:
"""Total runtime must be greater than all stops in the final order"""
for _, row in options[options.order == options.order.iloc[-1]].iterrows():
prob.addConstraint(
name=f'runtime_total_o{row.order}_i{row["item"]}_m{row.machine}',
constraint=total_runtime >= row.start + row.dur)
def constrain_order_machines(prob: pulp.LpProblem, options: pd.DataFrame) -> None:
"""There must be at most one item assigned to each order-machine pair"""
for (order, machine), group in options.groupby(['order', 'machine']):
total = pulp.lpSum(group['asn'])
prob.addConstraint(name=f'assign_o{order}_m{machine}', constraint=total <= 1)
def constrain_order_transitions(prob: pulp.LpProblem, options: pd.DataFrame, cleaning: pd.DataFrame) -> None:
"""Comparing the transition between every order and its next order, enforce minimum time bounds"""
for (prev_order, prev_group), (curr_order, curr_group) in zip(
options[options.order < options.order.iloc[-1]].groupby('order'),
options[options.order > options.order.iloc[0]].groupby('order'),
):
# Comparing every item-machine combination...
pairs = pd.merge(
left=prev_group, right=curr_group,
left_on='machine', right_on='machine',
suffixes=('_prev', '_curr'),
)
for _, combo in pairs.iterrows():
# always (disregarding clean): start_curr - stop_prev >= 0
suffix = f'_o{prev_order}{curr_order}_m{combo.machine}_i{combo.item_prev}{combo.item_curr}'
stop_prev = combo.start_prev + combo.dur_prev
prob.addConstraint(name='seq' + suffix, constraint=stop_prev <= combo.start_curr)
# also: if asn_prev and asn_curr, then start_curr - stop_prev >= cleaning[item_curr, item_prev]
# start_curr - stop_prev >= -cl + cl*asn_prev + cl*asn_curr
clean_time = cleaning.loc[combo.item_curr, combo.item_prev]
if clean_time > 0:
prob.addConstraint(
name='seq_clean' + suffix,
constraint=combo.start_curr - stop_prev >= clean_time*(combo.asn_curr + combo.asn_prev - 1)
)
def solve(prob: pulp.LpProblem, verbose: bool) -> None:
pulp.PULP_CBC_CMD(msg=verbose).solve(prob)
assert prob.status == pulp.const.LpStatusOptimal
def display(options: pd.DataFrame, item_runtimes: pd.Series) -> None:
used = options.asn.apply(pulp.LpVariable.value).astype(bool)
for _, row in options[used].iterrows():
stop = row.start.value() + row.dur.value()
durmax = item_runtimes[row["item"]]
print(
f'Order {row.order} item {row["item"]} processed by machine {row.machine} '
f'for {row.dur.value()}/{durmax} from {row.start.value()} to {stop}'
)
def main(verbose: bool = False) -> None:
options, cleaning, item_runtimes, time_buffer = make_data()
prob, total_runtime = make_problem()
make_vars(prob, options, item_runtimes, time_buffer)
constrain_total_runtime(prob, options, total_runtime)
constrain_order_machines(prob, options)
constrain_order_transitions(prob, options, cleaning)
if verbose:
print(prob)
solve(prob, verbose)
display(options, item_runtimes)
if __name__ == '__main__':
main(verbose=True)
Output with verbose=True
:
order_machine_processing:
MINIMIZE
1*runtime + 0
SUBJECT TO
unused_o1_iA_m1: - 900 asn_o1_iA_m1 + dur_o1_iA_m1 <= 0
unused_o1_iA_m2: - 900 asn_o1_iA_m2 + dur_o1_iA_m2 <= 0
unused_o1_iB_m1: - 900 asn_o1_iB_m1 + dur_o1_iB_m1 <= 0
unused_o1_iB_m2: - 900 asn_o1_iB_m2 + dur_o1_iB_m2 <= 0
unused_o2_iA_m1: - 900 asn_o2_iA_m1 + dur_o2_iA_m1 <= 0
unused_o2_iA_m2: - 900 asn_o2_iA_m2 + dur_o2_iA_m2 <= 0
unused_o2_iD_m1: - 900 asn_o2_iD_m1 + dur_o2_iD_m1 <= 0
unused_o2_iD_m2: - 900 asn_o2_iD_m2 + dur_o2_iD_m2 <= 0
unused_o3_iE_m1: - 900 asn_o3_iE_m1 + dur_o3_iE_m1 <= 0
unused_o3_iE_m2: - 900 asn_o3_iE_m2 + dur_o3_iE_m2 <= 0
assign_o1_iA: asn_o1_iA_m1 + asn_o1_iA_m2 >= 1
time_o1_iA: dur_o1_iA_m1 + dur_o1_iA_m2 = 50
match_o1_iA_m1_startlo: 900 asn_o1_iA_m1 - start_o1_iA + start_o1_iA_m1 <= 900
match_o1_iA_m1_starthi: 900 asn_o1_iA_m1 + start_o1_iA - start_o1_iA_m1 <= 900
match_o1_iA_m1_durlo: 900 asn_o1_iA_m1 - dur_o1_iA + dur_o1_iA_m1 <= 900
match_o1_iA_m1_durhi: 900 asn_o1_iA_m1 + dur_o1_iA - dur_o1_iA_m1 <= 900
match_o1_iA_m2_startlo: 900 asn_o1_iA_m2 - start_o1_iA + start_o1_iA_m2 <= 900
match_o1_iA_m2_starthi: 900 asn_o1_iA_m2 + start_o1_iA - start_o1_iA_m2 <= 900
match_o1_iA_m2_durlo: 900 asn_o1_iA_m2 - dur_o1_iA + dur_o1_iA_m2 <= 900
match_o1_iA_m2_durhi: 900 asn_o1_iA_m2 + dur_o1_iA - dur_o1_iA_m2 <= 900
assign_o1_iB: asn_o1_iB_m1 + asn_o1_iB_m2 >= 1
time_o1_iB: dur_o1_iB_m1 + dur_o1_iB_m2 = 60
match_o1_iB_m1_startlo: 900 asn_o1_iB_m1 - start_o1_iB + start_o1_iB_m1 <= 900
match_o1_iB_m1_starthi: 900 asn_o1_iB_m1 + start_o1_iB - start_o1_iB_m1 <= 900
match_o1_iB_m1_durlo: 900 asn_o1_iB_m1 - dur_o1_iB + dur_o1_iB_m1 <= 900
match_o1_iB_m1_durhi: 900 asn_o1_iB_m1 + dur_o1_iB - dur_o1_iB_m1 <= 900
match_o1_iB_m2_startlo: 900 asn_o1_iB_m2 - start_o1_iB + start_o1_iB_m2 <= 900
match_o1_iB_m2_starthi: 900 asn_o1_iB_m2 + start_o1_iB - start_o1_iB_m2 <= 900
match_o1_iB_m2_durlo: 900 asn_o1_iB_m2 - dur_o1_iB + dur_o1_iB_m2 <= 900
match_o1_iB_m2_durhi: 900 asn_o1_iB_m2 + dur_o1_iB - dur_o1_iB_m2 <= 900
assign_o2_iA: asn_o2_iA_m1 + asn_o2_iA_m2 >= 1
time_o2_iA: dur_o2_iA_m1 + dur_o2_iA_m2 = 50
match_o2_iA_m1_startlo: 900 asn_o2_iA_m1 - start_o2_iA + start_o2_iA_m1 <= 900
match_o2_iA_m1_starthi: 900 asn_o2_iA_m1 + start_o2_iA - start_o2_iA_m1 <= 900
match_o2_iA_m1_durlo: 900 asn_o2_iA_m1 - dur_o2_iA + dur_o2_iA_m1 <= 900
match_o2_iA_m1_durhi: 900 asn_o2_iA_m1 + dur_o2_iA - dur_o2_iA_m1 <= 900
match_o2_iA_m2_startlo: 900 asn_o2_iA_m2 - start_o2_iA + start_o2_iA_m2 <= 900
match_o2_iA_m2_starthi: 900 asn_o2_iA_m2 + start_o2_iA - start_o2_iA_m2 <= 900
match_o2_iA_m2_durlo: 900 asn_o2_iA_m2 - dur_o2_iA + dur_o2_iA_m2 <= 900
match_o2_iA_m2_durhi: 900 asn_o2_iA_m2 + dur_o2_iA - dur_o2_iA_m2 <= 900
assign_o2_iD: asn_o2_iD_m1 + asn_o2_iD_m2 >= 1
time_o2_iD: dur_o2_iD_m1 + dur_o2_iD_m2 = 80
match_o2_iD_m1_startlo: 900 asn_o2_iD_m1 - start_o2_iD + start_o2_iD_m1 <= 900
match_o2_iD_m1_starthi: 900 asn_o2_iD_m1 + start_o2_iD - start_o2_iD_m1 <= 900
match_o2_iD_m1_durlo: 900 asn_o2_iD_m1 - dur_o2_iD + dur_o2_iD_m1 <= 900
match_o2_iD_m1_durhi: 900 asn_o2_iD_m1 + dur_o2_iD - dur_o2_iD_m1 <= 900
match_o2_iD_m2_startlo: 900 asn_o2_iD_m2 - start_o2_iD + start_o2_iD_m2 <= 900
match_o2_iD_m2_starthi: 900 asn_o2_iD_m2 + start_o2_iD - start_o2_iD_m2 <= 900
match_o2_iD_m2_durlo: 900 asn_o2_iD_m2 - dur_o2_iD + dur_o2_iD_m2 <= 900
match_o2_iD_m2_durhi: 900 asn_o2_iD_m2 + dur_o2_iD - dur_o2_iD_m2 <= 900
assign_o3_iE: asn_o3_iE_m1 + asn_o3_iE_m2 >= 1
time_o3_iE: dur_o3_iE_m1 + dur_o3_iE_m2 = 90
match_o3_iE_m1_startlo: 900 asn_o3_iE_m1 - start_o3_iE + start_o3_iE_m1 <= 900
match_o3_iE_m1_starthi: 900 asn_o3_iE_m1 + start_o3_iE - start_o3_iE_m1 <= 900
match_o3_iE_m1_durlo: 900 asn_o3_iE_m1 - dur_o3_iE + dur_o3_iE_m1 <= 900
match_o3_iE_m1_durhi: 900 asn_o3_iE_m1 + dur_o3_iE - dur_o3_iE_m1 <= 900
match_o3_iE_m2_startlo: 900 asn_o3_iE_m2 - start_o3_iE + start_o3_iE_m2 <= 900
match_o3_iE_m2_starthi: 900 asn_o3_iE_m2 + start_o3_iE - start_o3_iE_m2 <= 900
match_o3_iE_m2_durlo: 900 asn_o3_iE_m2 - dur_o3_iE + dur_o3_iE_m2 <= 900
match_o3_iE_m2_durhi: 900 asn_o3_iE_m2 + dur_o3_iE - dur_o3_iE_m2 <= 900
runtime_total_o3_iE_m1: - dur_o3_iE_m1 + runtime - start_o3_iE_m1 >= 0
runtime_total_o3_iE_m2: - dur_o3_iE_m2 + runtime - start_o3_iE_m2 >= 0
assign_o1_m1: asn_o1_iA_m1 + asn_o1_iB_m1 <= 1
assign_o1_m2: asn_o1_iA_m2 + asn_o1_iB_m2 <= 1
assign_o2_m1: asn_o2_iA_m1 + asn_o2_iD_m1 <= 1
assign_o2_m2: asn_o2_iA_m2 + asn_o2_iD_m2 <= 1
assign_o3_m1: asn_o3_iE_m1 <= 1
assign_o3_m2: asn_o3_iE_m2 <= 1
seq_o12_m1_iAA: dur_o1_iA_m1 + start_o1_iA_m1 - start_o2_iA_m1 <= 0
seq_o12_m1_iAD: dur_o1_iA_m1 + start_o1_iA_m1 - start_o2_iD_m1 <= 0
seq_o12_m1_iBA: dur_o1_iB_m1 + start_o1_iB_m1 - start_o2_iA_m1 <= 0
seq_o12_m1_iBD: dur_o1_iB_m1 + start_o1_iB_m1 - start_o2_iD_m1 <= 0
seq_o12_m2_iAA: dur_o1_iA_m2 + start_o1_iA_m2 - start_o2_iA_m2 <= 0
seq_o12_m2_iAD: dur_o1_iA_m2 + start_o1_iA_m2 - start_o2_iD_m2 <= 0
seq_o12_m2_iBA: dur_o1_iB_m2 + start_o1_iB_m2 - start_o2_iA_m2 <= 0
seq_o12_m2_iBD: dur_o1_iB_m2 + start_o1_iB_m2 - start_o2_iD_m2 <= 0
seq_o23_m1_iAE: dur_o2_iA_m1 + start_o2_iA_m1 - start_o3_iE_m1 <= 0
seq_clean_o23_m1_iAE: - asn_o2_iA_m1 - asn_o3_iE_m1 - dur_o2_iA_m1
- start_o2_iA_m1 + start_o3_iE_m1 >= -1
seq_o23_m1_iDE: dur_o2_iD_m1 + start_o2_iD_m1 - start_o3_iE_m1 <= 0
seq_o23_m2_iAE: dur_o2_iA_m2 + start_o2_iA_m2 - start_o3_iE_m2 <= 0
seq_clean_o23_m2_iAE: - asn_o2_iA_m2 - asn_o3_iE_m2 - dur_o2_iA_m2
- start_o2_iA_m2 + start_o3_iE_m2 >= -1
seq_o23_m2_iDE: dur_o2_iD_m2 + start_o2_iD_m2 - start_o3_iE_m2 <= 0
VARIABLES
0 <= asn_o1_iA_m1 <= 1 Integer
0 <= asn_o1_iA_m2 <= 1 Integer
0 <= asn_o1_iB_m1 <= 1 Integer
0 <= asn_o1_iB_m2 <= 1 Integer
0 <= asn_o2_iA_m1 <= 1 Integer
0 <= asn_o2_iA_m2 <= 1 Integer
0 <= asn_o2_iD_m1 <= 1 Integer
0 <= asn_o2_iD_m2 <= 1 Integer
0 <= asn_o3_iE_m1 <= 1 Integer
0 <= asn_o3_iE_m2 <= 1 Integer
1 <= dur_o1_iA <= 50 Continuous
dur_o1_iA_m1 <= 50 Continuous
dur_o1_iA_m2 <= 50 Continuous
1 <= dur_o1_iB <= 60 Continuous
dur_o1_iB_m1 <= 60 Continuous
dur_o1_iB_m2 <= 60 Continuous
1 <= dur_o2_iA <= 50 Continuous
dur_o2_iA_m1 <= 50 Continuous
dur_o2_iA_m2 <= 50 Continuous
1 <= dur_o2_iD <= 80 Continuous
dur_o2_iD_m1 <= 80 Continuous
dur_o2_iD_m2 <= 80 Continuous
1 <= dur_o3_iE <= 90 Continuous
dur_o3_iE_m1 <= 90 Continuous
dur_o3_iE_m2 <= 90 Continuous
runtime Continuous
start_o1_iA Continuous
start_o1_iA_m1 Continuous
start_o1_iA_m2 Continuous
start_o1_iB Continuous
start_o1_iB_m1 Continuous
start_o1_iB_m2 Continuous
start_o2_iA Continuous
start_o2_iA_m1 Continuous
start_o2_iA_m2 Continuous
start_o2_iD Continuous
start_o2_iD_m1 Continuous
start_o2_iD_m2 Continuous
start_o3_iE Continuous
start_o3_iE_m1 Continuous
start_o3_iE_m2 Continuous
Welcome to the CBC MILP Solver
Version: 2.10.3
Build Date: Dec 15 2019
command line - .venv/lib/python3.10/site-packages/pulp/solverdir/cbc/linux/64/cbc /tmp/b35b0c4d0be94ca5bbd06f7e0e658e3e-pulp.mps timeMode elapsed branch printingOptions all solution /tmp/b35b0c4d0be94ca5bbd06f7e0e658e3e-pulp.sol (default strategy 1)
At line 2 NAME MODEL
At line 3 ROWS
At line 87 COLUMNS
At line 331 RHS
At line 414 BOUNDS
At line 445 ENDATA
Problem MODEL has 82 rows, 41 columns and 222 elements
Coin0008I MODEL read with 0 errors
Option for timeMode changed from cpu to elapsed
Continuous objective value is 115 - 0.00 seconds
Cgl0003I 0 fixed, 0 tightened bounds, 18 strengthened rows, 26 substitutions
Cgl0003I 0 fixed, 0 tightened bounds, 1 strengthened rows, 0 substitutions
Cgl0004I processed model has 43 rows, 22 columns (4 integer (4 of which binary)) and 128 elements
Cbc0038I Initial state - 4 integers unsatisfied sum - 1.07601
Cbc0038I Pass 1: suminf. 0.00000 (0) obj. 175 iterations 10
Cbc0038I Solution found of 175
Cbc0038I Relaxing continuous gives 175
Cbc0038I Before mini branch and bound, 0 integers at bound fixed and 3 continuous
Cbc0038I Full problem 43 rows 22 columns, reduced to 39 rows 19 columns
Cbc0038I Mini branch and bound did not improve solution (0.00 seconds)
Cbc0038I Round again with cutoff of 169
Cbc0038I Pass 2: suminf. 0.01333 (1) obj. 169 iterations 5
Cbc0038I Pass 3: suminf. 0.46667 (1) obj. 169 iterations 7
Cbc0038I Pass 4: suminf. 0.44000 (1) obj. 169 iterations 4
Cbc0038I Pass 5: suminf. 0.44000 (1) obj. 169 iterations 0
Cbc0038I Pass 6: suminf. 0.44000 (1) obj. 169 iterations 0
Cbc0038I Pass 7: suminf. 0.64444 (2) obj. 169 iterations 5
Cbc0038I Pass 8: suminf. 0.44000 (1) obj. 169 iterations 6
Cbc0038I Pass 9: suminf. 0.03556 (1) obj. 169 iterations 7
Cbc0038I Pass 10: suminf. 0.39000 (2) obj. 169 iterations 2
Cbc0038I Pass 11: suminf. 0.03556 (1) obj. 169 iterations 2
Cbc0038I Pass 12: suminf. 0.03556 (1) obj. 169 iterations 0
Cbc0038I Pass 13: suminf. 0.39000 (2) obj. 169 iterations 2
Cbc0038I Pass 14: suminf. 0.03556 (1) obj. 169 iterations 2
Cbc0038I Pass 15: suminf. 0.01333 (1) obj. 169 iterations 7
Cbc0038I Pass 16: suminf. 0.46667 (1) obj. 169 iterations 7
Cbc0038I Pass 17: suminf. 0.20000 (1) obj. 169 iterations 14
Cbc0038I Pass 18: suminf. 0.01333 (1) obj. 169 iterations 7
Cbc0038I Pass 19: suminf. 0.03556 (1) obj. 169 iterations 7
Cbc0038I Pass 20: suminf. 0.44000 (1) obj. 169 iterations 6
Cbc0038I Pass 21: suminf. 0.44000 (1) obj. 169 iterations 0
Cbc0038I Pass 22: suminf. 0.44000 (1) obj. 169 iterations 0
Cbc0038I Pass 23: suminf. 0.44000 (1) obj. 169 iterations 0
Cbc0038I Pass 24: suminf. 0.03556 (1) obj. 169 iterations 7
Cbc0038I Pass 25: suminf. 0.44000 (1) obj. 169 iterations 6
Cbc0038I Pass 26: suminf. 0.44000 (1) obj. 169 iterations 0
Cbc0038I Pass 27: suminf. 0.07500 (1) obj. 169 iterations 15
Cbc0038I Pass 28: suminf. 0.03556 (1) obj. 169 iterations 8
Cbc0038I Pass 29: suminf. 0.03556 (1) obj. 169 iterations 0
Cbc0038I Pass 30: suminf. 0.49000 (2) obj. 169 iterations 9
Cbc0038I Pass 31: suminf. 0.49000 (2) obj. 169 iterations 0
Cbc0038I No solution found this major pass
Cbc0038I Before mini branch and bound, 0 integers at bound fixed and 0 continuous
Cbc0038I Full problem 43 rows 22 columns, reduced to 43 rows 22 columns
Cbc0038I Mini branch and bound did not improve solution (0.01 seconds)
Cbc0038I After 0.01 seconds - Feasibility pump exiting with objective of 175 - took 0.01 seconds
Cbc0012I Integer solution of 175 found by feasibility pump after 0 iterations and 0 nodes (0.01 seconds)
Cbc0031I 7 added rows had average density of 2.1428571
Cbc0013I At root node, 17 cuts changed objective from 115 to 191 in 6 passes
Cbc0014I Cut generator 0 (Probing) - 19 row cuts average 2.7 elements, 1 column cuts (1 active) in 0.000 seconds - new frequency is 1
Cbc0014I Cut generator 1 (Gomory) - 16 row cuts average 5.8 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is 1
Cbc0014I Cut generator 2 (Knapsack) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100
Cbc0014I Cut generator 3 (Clique) - 0 row cuts average 0.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100
Cbc0014I Cut generator 4 (MixedIntegerRounding2) - 1 row cuts average 3.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100
Cbc0014I Cut generator 5 (FlowCover) - 3 row cuts average 3.0 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is 1
Cbc0014I Cut generator 6 (TwoMirCuts) - 21 row cuts average 5.4 elements, 0 column cuts (0 active) in 0.000 seconds - new frequency is -100
Cbc0001I Search completed - best objective 175, took 67 iterations and 0 nodes (0.01 seconds)
Cbc0035I Maximum depth 0, 0 variables fixed on reduced cost
Cuts at root node changed objective from 115 to 191
Probing was tried 6 times and created 20 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Gomory was tried 6 times and created 16 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Knapsack was tried 6 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Clique was tried 6 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
MixedIntegerRounding2 was tried 6 times and created 1 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
FlowCover was tried 6 times and created 3 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
TwoMirCuts was tried 6 times and created 21 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
ZeroHalf was tried 1 times and created 0 cuts of which 0 were active after adding rounds of cuts (0.000 seconds)
Result - Optimal solution found
Objective value: 175.00000000
Enumerated nodes: 0
Total iterations: 67
Time (CPU seconds): 0.01
Time (Wallclock seconds): 0.01
Option for printingOptions changed from normal to all
Total time (CPU seconds): 0.01 (Wallclock seconds): 0.01
Order 1 item A processed by machine 1 for 50.0/50 from 0.0 to 50.0
Order 1 item B processed by machine 2 for 60.0/60 from 0.0 to 60.0
Order 2 item A processed by machine 2 for 50.0/50 from 60.0 to 110.0
Order 2 item D processed by machine 1 for 80.0/80 from 50.0 to 130.0
Order 3 item E processed by machine 1 for 45.0/90 from 130.0 to 175.0
Order 3 item E processed by machine 2 for 45.0/90 from 130.0 to 175.0
Things to note in the output:
- Order 2 item A runtime is a normal runtime followed by a wait. This wait occurs so that the joint work on item E occurs simultaneously
- There is only one cleanup time paid, on machine 1 between orders 2,3 and items A,E
- The orders are indeed processed in sequence, but the optimizer takes advantage of the fact that machine 2 can start on order 2 while machine 1 is still working on order 1