Widgetco is about to introduce a new product. One unit of this product is produced by assembling subassembly 1 and subassembly 2. Before production begins on either subassembly, raw materials must be purchased and workers must be trained. Before the subassemblies can be assembled into the final product, the finished subassembly 2 must be inspected. The GAMS code that sets up this data is given below.
import numpy as np
import pandas as pd
import math
from gamspy import (
Container,Set,Alias,Parameter,Variable,Equation,Model,Problem,Sense,Options,
Domain,Number,Sum,Product,Smax,Smin,Ord,Card,SpecialValues,
)
options = Options(variable_listing_limit=0, equation_listing_limit=20)
m = Container(options=options)
activity = Set(m,'activity',records=[('A',"Train Workers"),('B',"Purchase Raw Materials"),
('C',"Make Subassembly 1"),('D',"Make Subassembly 2"),
('E',"Inspect Subassembly 2"),('F',"Assemble Subassemblies")])
I = Alias(m,'I',activity)
J = Alias(m,'J',activity)
pred = Set(m,'pred',domain=[I,J],description="I preceeds J", records=[('A','C'), ('B','C'),
('A','D'), ('B','D'), ('D','E'), ('C','F'), ('E','F')])
duration = Parameter(m,'duration',domain=I,records=np.array([6, 9, 8, 7, 10, 12]))
/Users/mas/anaconda3/lib/python3.11/site-packages/pandas/core/arrays/masked.py:60: UserWarning: Pandas requires version '1.3.6' or newer of 'bottleneck' (version '1.3.5' currently installed). from pandas.core import (
display(activity.records,pred.toList(),duration.records)
uni | element_text | |
---|---|---|
0 | A | Train Workers |
1 | B | Purchase Raw Materials |
2 | C | Make Subassembly 1 |
3 | D | Make Subassembly 2 |
4 | E | Inspect Subassembly 2 |
5 | F | Assemble Subassemblies |
[('A', 'C'), ('B', 'C'), ('A', 'D'), ('B', 'D'), ('D', 'E'), ('C', 'F'), ('E', 'F')]
I | value | |
---|---|---|
0 | A | 6.0 |
1 | B | 9.0 |
2 | C | 8.0 |
3 | D | 7.0 |
4 | E | 10.0 |
5 | F | 12.0 |
t = Variable(m,'t','positive',domain=I,description='time activity starts')
projDur = Variable(m,'projDur','free')
incidence = Equation(m,'incidence',domain=[I,J])
incidence[pred[I,J]]= t[J] >= t[I] + duration[I]
endTime = Equation(m,'endTime',domain=I)
endTime[I] = projDur >= t[I] + duration[I]
cpm = Model(m,"cpm",
equations=m.getEquations(),
problem=Problem.LP,
sense=Sense.MIN,
objective=projDur,
)
cpm.solve()
Solver Status | Model Status | Objective | Num of Equations | Num of Variables | Model Type | Solver | Solver Time | |
---|---|---|---|---|---|---|---|---|
0 | Normal | OptimalGlobal | 38 | 13 | 7 | LP | CPLEX | 0.005 |
critical = Set(m,'critical',domain=I,description="critical activities")
critical[I] = Number(1).where[
(Smax(J.where[pred[J,I]],incidence.m[J,I]) >= 1) |
(Smax(J.where[pred[I,J]],incidence.m[I,J]) >= 1)]
display(critical.toList(),incidence.pivot(fill_value=0,value='marginal'))
['B', 'D', 'E', 'F']
C | D | E | F | |
---|---|---|---|---|
A | 0.0 | 0.0 | 0.0 | 0.0 |
B | -0.0 | 1.0 | 0.0 | 0.0 |
C | 0.0 | 0.0 | 0.0 | 0.0 |
D | 0.0 | 0.0 | 1.0 | 0.0 |
E | 0.0 | 0.0 | 0.0 | 1.0 |
Now let's set up the dual
lamda = Variable(m,'lamda','positive',domain=[I,J],description='Dual var for incidence (critical arc)')
pi = Variable(m,'pi','positive',domain=I)
teq = Equation(m,'teq',domain=I,description='Equations for primal t vars')
teq[I]= ( -pi[I] - Sum(pred[I,J], lamda[I,J])
+ Sum(pred[J,I], lamda[J,I]) <= 0 )
projDur_dual_eq = Equation(m,'projDur_dual_eq',description='Dual equation for projDur')
projDur_dual_eq[:]= Sum(I, pi[I]) == 1
dual_cpm = Model(m,"dual_cpm",
equations=[teq, projDur_dual_eq],
problem=Problem.LP,
sense=Sense.MAX,
objective= (Sum(pred[I,J], lamda[I,J] * duration[I])
+ Sum(I,duration[I]*pi[I])),
)
dual_cpm.solve()
Solver Status | Model Status | Objective | Num of Equations | Num of Variables | Model Type | Solver | Solver Time | |
---|---|---|---|---|---|---|---|---|
0 | Normal | OptimalGlobal | 38 | 8 | 14 | LP | CPLEX | 0.005 |
critical[I] = Number(1).where[
(Smax(J.where[pred[J,I]],lamda.l[J,I]) >= 1) |
(Smax(J.where[pred[I,J]],lamda.l[I,J]) >= 1)]
display(lamda.pivot(fill_value=0),critical.toList())
C | D | E | F | |
---|---|---|---|---|
A | 0.0 | 0.0 | 0.0 | 0.0 |
B | 0.0 | 1.0 | 0.0 | 0.0 |
C | 0.0 | 0.0 | 0.0 | 0.0 |
D | 0.0 | 0.0 | 1.0 | 0.0 |
E | 0.0 | 0.0 | 0.0 | 1.0 |
['B', 'D', 'E', 'F']
Now try to find early and late event times
projDur.fx[:] = projDur.l
eventtimes = m.addModel("eventtimes",
equations=[incidence,endTime],
problem=Problem.LP,
sense=Sense.MAX,
objective=Sum(I,t[I])
)
eventtimes.solve()
results = pd.DataFrame(
t.toDense(),
columns=['leTime'],
index=I.records.uni)
eventtimes = Model(m,"eventtimes",
equations=[incidence,endTime],
problem=Problem.LP,
sense=Sense.MIN,
objective=Sum(I,t[I])
)
eventtimes.solve()
results['eeTime'] = t.toDense()
results['critical'] = results['eeTime'] >= results['leTime']
display(results)
leTime | eeTime | critical | |
---|---|---|---|
uni | |||
A | 3.0 | 0.0 | False |
B | 0.0 | 0.0 | True |
C | 18.0 | 9.0 | False |
D | 9.0 | 9.0 | True |
E | 16.0 | 16.0 | True |
F | 26.0 | 26.0 | True |