-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathopex.py
202 lines (160 loc) · 7.91 KB
/
opex.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import numpy as np
import capex
import time
from typing import List, Tuple, Any
runtime = 8000 # Operational runtime (h/yr)
shiftdur = 8 # Duration per workshift (h/shift)
shiftperwk = 5 # Number of workshifts per week
yearww = 49 # Number of work weeks per year
SF = runtime / (365*24) # Stream factor
# Utility Costs (per GJ basis)
# From "Analysis, Synthesis & Design of Chemical Processes, 5th Ed. by Turton et al.", year=2016
utilprice = {
'LPS': 4.54, # Utility cost for LPS (5 barg, 160 degC) ($/GJ)
'MPS': 4.77, # Utility cost for MPS (10 barg, 184 degC) ($/GJ)
'HPS': 5.66, # Utility cost for HPS (41 barg, 254 degC) ($/GJ)
'CW': 0.378, # Utility cost for cooling water (30-45 degC) ($/GJ)
'ChW': 4.77, # Utility cost for chilled water (5 degC) ($/GJ)
'LTR': 8.49, # Utility cost for low temperature refrigerant (-20 degC) ($/GJ)
'VLTR': 14.12, # Utility cost for very low temperature refrigerant (-50 degC) ($/GJ)
'elec': 18.72 # Utility cost for electricity (110-440 V) ($/GJ)
}
def operatorspershift(P: int=0, Nnp: int=0):
"""
Calculate number of operators per shift
:param P: number of processing steps involving particulate solids (P=0 for fluid-processing plants)
:param Nnp: number of non-particulate/fluid handling equipment/steps (include compressors, towers, reactors,
heaters and exchangers; exclude pumps, vessels and tanks)
:return: NOL: number of operators required per shift
"""
NOL = round(np.sqrt(6.29 + 31.7 * P ** 2. + 0.23 * Nnp))
return NOL
def labourcost(P: int=None, Nnp: int=None, wage: float=42750., eqptlist: List[Any]=None) -> (float, float):
"""
Calculate annualised labour cost
Two methods of calculation:
Method 1 - Specify P, Nnp and wage manually:
:param P: number of processing steps involving particulate solids (P=0 for fluid-processing plants)
:param Nnp: number of non-particulate/fluid handling equipment/steps (include compressors, towers, reactors,
heaters and exchangers; exclude pumps, vessels and tanks)
:param wage: annualised per-operator wage ($/yr, using the desired currency and year)
Method 2 - Specify the list of equipment objects directly, then specify wage:
:param eqptlist: list of equipment objects as generated by the dsg.design(...) or dsg.size(...) functions
:param wage: annualised per-operator wage ($/yr, using the desired currency and year)
:return: COL: annualised labour cost ($/yr)
:return: NOL: total number of operators required
"""
if eqptlist is None:
pass
elif P is None and Nnp is None:
P = 0
Nnp = 0
for eqpt in eqptlist:
P += 1 if eqpt.category in ['crystallizer'] else 0
Nnp += 1 if eqpt.category in ['compressor', 'vessel', 'heatexc'] else 0
else:
raise ValueError('Specify either (P + Nnp) or eqptlist!')
NOL = operatorspershift(P, Nnp)
shiftperyr = runtime / shiftdur
shiftperopperyr = yearww * shiftperwk
Nop = round(shiftperyr / shiftperopperyr * NOL)
COL = Nop * wage
return COL, Nop
def costofutil(utiltuple: Tuple[float]=None, HPS: float=0., MPS: float=0., LPS: float=0.,
CW: float=0., ChW: float=0., LTR: float=0., VLTR: float=0., elec: float=0.,
year: int=2019, currency: str='SGD'):
"""
Calculates the annualised cost of utilities
Either key in utilities as a tuple, or as separate numerical inputs (see param):
:param utiltuple: tuple containing annual consumption of each type of utility (GJ/yr), in the following order:
HPS, MPS, LPS, CW, ChW, LTR, VLTR, elec
:param HPS: annual consumption of high-pressure steam (GJ/yr)
:param MPS: annual consumption of medium-pressure steam (GJ/yr)
:param LPS: annual consumption of low-pressure steam (GJ/yr)
:param CW: annual consumption of cooling water (GJ/yr)
:param ChW: annual consumption of chilled water (GJ/yr)
:param LTR: annual consumption of low-temperature refrigerant (GJ/yr)
:param VLTR: annual consumption of very low-temperature refrigerant (GJ/yr)
:param elec: annual consumption of electricity (GJ/yr)
:param year: Year for CPI updating (integer, either 2001, 2018 or 2019 [default])
:param currency: Currency (string, either 'USD' or 'SGD' [default])
:return: CUT: annualised cost of utilities ($/yr)
"""
a = np.array([utilprice['HPS'], utilprice['MPS'], utilprice['LPS'],
utilprice['CW'], utilprice['ChW'],
utilprice['LTR'], utilprice['VLTR'], utilprice['elec']])
if utiltuple is not None:
b = np.array(utiltuple)
else:
b = np.array([HPS, MPS, LPS, CW, ChW, LTR, VLTR, elec])
yearcurrfac = capex.CPI['US'][year] / capex.CPI['US'][2016] * (capex.USSG[year] if currency is 'SGD' else 1.)
CUT = (a @ b.T) * yearcurrfac
return CUT
def costofraw(rawmaterialtuple: Tuple[float]=(0.,), unitpricetuple: Tuple[float]=(0.,)) -> float:
"""
Calculates the annualised cost of raw materials
:param rawmaterialtuple: annual flow of raw materials, in a tuple (flow/yr)
:param unitpricetuple: per-flow unit cost price of raw materials, in a tuple in the same order as rawmaterials
($/flow, using the desired currency and year)
:return: CRM: annualised cost of raw materials ($/yr)
"""
if type(rawmaterialtuple) is tuple:
a = np.array(rawmaterialtuple)
else:
raise TypeError('rawmaterials should be of type tuple')
if type(unitpricetuple) is tuple:
b = np.array(unitpricetuple)
else:
raise TypeError('rawmaterials should be of type tuple')
if a.size != b.size:
raise ValueError('Number of unit prices do not match number of raw materials!')
CRM = a @ b.T
return CRM
def costofwaste():
# TODO User has to create this function on his/her own if necessary,
# because there are many possible sources of waste treatment
raise NotImplementedError
def costofmanfc(FCI: float=0., COL: float=0., CRM: float=0., CWT: float=0., CUT: float=0.,
verbose: bool=False) -> (float, float, float, float, float, float, dict):
"""
Estimate annualised cost of manufacture.
:param FCI: fixed capital investment (= CTM or total module cost for brownfield projects, or =CGR or grassroots cost
for greenfield projects) ($)
:param COL: annualised labour cost ($/yr)
:param CRM: annualised cost of raw materials ($/yr)
:param CWT: annualised cost of waste treatment ($/yr)
:param CUT: annualised cost of utilities ($/yr)
:return: COMd: annualised cost of manufacturing without depreciation ($/yr)
:return: d: annualised depreciation ($/yr)
:return: COM: annualised cost of manufacturing with depreciation ($/yr)
:return: DMC: annualised direct manufacturing costs ($/yr)
:return: FMC: annualised fixed manufacturing costs ($/yr)
:return: GE: annualised general expenses ($/yr)
:param verbose: True to print economic capex report, False to print nothing [default]
:return: report: The opex report
"""
report_dict = dict()
COMd = 0.18 * FCI + 2.73 * COL + 1.23 * (CRM + CWT + CUT)
d = 0.1 * FCI
COM = COMd + d
DMC = CRM + CWT + CUT + 1.33 * COL + 0.069 * FCI + 0.03 * COM
FMC = 0.708 * COL + 0.068 * FCI
GE = 0.177 * COL + 0.009 * FCI + 0.16 * COM
report_dict['COMd'] = round(COMd, 2)
report_dict['d'] = round(d, 2)
report_dict['COM'] = round(COM, 2)
report_dict['DMC'] = round(DMC, 2)
report_dict['FMC'] = round(FMC, 2)
report_dict['GE'] = round(GE, 2)
report_dict['FCI'] = round(FCI, 2)
report_dict['COL'] = round(COL, 2)
report_dict['CRM'] = round(CRM, 2)
report_dict['CWT'] = round(CWT, 2)
report_dict['CUT'] = round(CUT, 2)
if verbose:
time.sleep(0.1)
print('----------------------------')
print('OPEX REPORT:')
print(str(report_dict))
print('----------------------------')
return COMd, d, COM, DMC, FMC, GE, report_dict