Skip to content

Commit fec1d81

Browse files
committed
more work on PO
1 parent 9bdbdf0 commit fec1d81

10 files changed

+160
-38
lines changed
11.4 KB
Loading
-41.3 KB
Loading
-15.9 KB
Loading
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"acetic_acid_simple": 13, "acetic_acid_complex_decoupled": 21, "acetic_acid_complex": 44, "butanol_purification": 4, "ethanol_purification": 2, "haber_bosch_process": 4}

benchmark/profile.py

Lines changed: 156 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
import thermosteam as tmo
2323
from scipy.integrate import romb
2424
from scipy.optimize import minimize
25+
from warnings import warn
26+
from biosteam import report
27+
import openpyxl
28+
import json
2529

2630
__all__ = (
2731
'BenchmarkModel',
@@ -32,6 +36,8 @@
3236
'register',
3337
'all_systems',
3438
'test_convergence',
39+
'save_stream_tables_and_specifications',
40+
'save_simulation_errors',
3541
'Tracker'
3642
)
3743

@@ -229,10 +235,17 @@ def plot_monte_carlo(system='alcohol_wide_flash'):
229235
system_tickmarks = {}
230236
system_labels = {}
231237
system_yticks = {}
238+
try:
239+
with open('system_stages.json') as f: system_stages = json.load(f)
240+
except:
241+
system_stages = {}
232242

233243
def register(name, title, time, tickmarks, label, yticks=None):
234244
f = getattr(systems, 'create_system_' + name, None) or getattr(systems, 'create_' + name + '_system')
235245
if yticks is None: yticks = [(-10, -5, 0, 5), (-10, -5, 0, 5)]
246+
if name not in system_stages:
247+
sys = f(None)
248+
system_stages[name] = len(sys.stages)
236249
all_systems[name] = f
237250
system_titles[name] = title
238251
system_convergence_times[name] = time
@@ -247,7 +260,8 @@ def register(name, title, time, tickmarks, label, yticks=None):
247260
register(
248261
'acetic_acid_simple', 'Subsystem',
249262
40, [0, 8, 16, 24, 32], 'AcOH\npartial\ndewatering',
250-
[(-15, -10, -5, 0, 5, 10), (-15, -10, -5, 0, 5, 10)],
263+
[(-15, -10, -5, 0, 5), (-15, -10, -5, 0, 5)],
264+
# [(-15, -10, -5, 0, 5, 10), (-15, -10, -5, 0, 5, 10)],
251265
)
252266
register(
253267
'acetic_acid_complex_decoupled', 'Shortcut system',
@@ -256,8 +270,9 @@ def register(name, title, time, tickmarks, label, yticks=None):
256270
)
257271
register(
258272
'acetic_acid_complex', 'Rigorous system',
259-
500, [0, 60, 120, 180, 240], 'AcOH\nindustrial\ndewatering',
260-
[(-5, -2.5, 0, 2.5, 5), (-8, -5, -2, 1, 4)],
273+
240, [0, 60, 120, 180, 240], 'AcOH\nindustrial\ndewatering',
274+
[(-15, -10, -5, 0, 5), (-15, -10, -5, 0, 5)],
275+
# [(-5, -2.5, 0, 2.5, 5), (-8, -5, -2, 1, 4)],
261276
)
262277
# register(
263278
# 'alcohol_narrow_flash', 'Alcohol flash narrow',
@@ -269,12 +284,12 @@ def register(name, title, time, tickmarks, label, yticks=None):
269284
# )
270285
register(
271286
'butanol_purification', 'Butanol purification',
272-
1, [0, 0.2, 0.4, 0.6, 0.8, 1], 'BtOH\nseparation',
287+
1, [0, 0.1, 0.2, 0.3, 0.4, 0.5], 'BtOH\nseparation',
273288
[(-15, -10, -5, 0, 5), (-15, -10, -5, 0, 5)],
274289
)
275290
register(
276291
'ethanol_purification', 'Ethanol purification',
277-
0.2, [0, 0.05, 0.1, 0.15, 0.2], 'EtOH\nseparation',
292+
0.2, [0, 0.03, 0.06, 0.09, 0.12], 'EtOH\nseparation',
278293
[(-15, -10, -5, 0, 5), (-15, -10, -5, 0, 5)],
279294
)
280295
# register(
@@ -291,10 +306,12 @@ def register(name, title, time, tickmarks, label, yticks=None):
291306
# )
292307
register(
293308
'haber_bosch_process', 'Haber-Bosch',
294-
0.03, [0, 0.007, 0.014, 0.021, 0.028], 'Haber-Bosch\nammonia\nproduction',
309+
0.03, [0, 0.004, 0.010, 0.015, 0.020], 'Haber-Bosch\nammonia\nproduction',
295310
[[-10, -7.5, -5, -2.5, 0], [-10, -7.5, -5, -2.5, 0]],
296311
)
297312

313+
with open('system_stages.json', 'w') as file: json.dump(system_stages, file)
314+
298315
# %% Testing
299316

300317
def test_convergence(systems=None, alg=None, maxiter=None):
@@ -339,26 +356,86 @@ def test_convergence(systems=None, alg=None, maxiter=None):
339356
print(f'- Multiple steady stages for {s_sm}, {s_sm.sink.ins.index(s_sm)}-{s_sm.sink}: sm-{actual} po-{value}')
340357
return outs
341358

359+
# %% Stream tables and specifications
360+
361+
def save_stream_tables_and_specifications(systems=None):
362+
if systems is None: systems = list(all_systems)
363+
for sys in systems:
364+
po = Tracker(sys, 'phenomena oriented')
365+
for i in range(50): po.run()
366+
with po.system.stage_configuration() as conf:
367+
for i in conf.streams:
368+
if i.ID == '': i.ID = ''
369+
name = system_labels[sys].replace('\n', ' ')
370+
file_name = f'{name}.xlsx'
371+
file = os.path.join(simulations_folder, file_name)
372+
po.system.save_report(
373+
file=file,
374+
sheets={
375+
'Flowsheet',
376+
'Stream table',
377+
'Specifications',
378+
'Reactions',
379+
},
380+
stage=True,
381+
)
382+
383+
# %% Simulation errors
384+
385+
def save_simulation_errors(systems=None):
386+
if systems is None: systems = list(all_systems)
387+
values = []
388+
for sys in systems:
389+
file_name = f"{sys}_steady_state"
390+
file = os.path.join(simulations_folder, file_name)
391+
values.append([
392+
Convergence(
393+
all_systems[sys](i), file
394+
).benchmark
395+
for i in ('phenomena-oriented', 'sequential-modular')
396+
])
397+
df = pd.DataFrame(
398+
values,
399+
[system_labels[i].replace('\n', ' ') for i in systems],
400+
('Phenomena-based', 'Sequential modular')
401+
)
402+
file_name = 'Simulation times.xlsx'
403+
file = os.path.join(simulations_folder, file_name)
404+
df.to_excel(file)
405+
return df
406+
407+
408+
# %% Convergence time
409+
410+
def convergence_time(sm, po):
411+
ysm = np.array(sm)
412+
ypo = np.array(po)
413+
cutoff = ysm.min() + 1
414+
sm_index = sum(ysm > cutoff)
415+
po_index = sum(ypo > cutoff)
416+
return sm['Time'][sm_index], po['Time'][po_index]
417+
418+
342419
# %% Profiling and benchmarking utilities
343420

344421
class ErrorPoint(NamedTuple):
345422
time: float; error: float
346423

347-
default_steady_state_cutoff = 2
424+
default_steady_state_cutoff = 1
348425

349426
def steady_state_error(profile, steady_state_cutoff=None):
350427
if steady_state_cutoff is None: steady_state_cutoff = default_steady_state_cutoff
351-
minimum_error = np.log10(10 ** profile['Component flow rate error'][-1] + 10 ** profile['Temperature error'][-1])
428+
minimum_error = np.log10(10 ** profile['Component flow rate error'][-1] + 10 ** profile['Temperature error'][-1])
352429
return minimum_error + steady_state_cutoff
353430

354431
def benchmark(profile, steady_state_error=None):
355432
if steady_state_error is None: steady_state_error = steady_state_error(profile)
356433
time = profile['Time']
357434
error = np.log10(10 ** profile['Component flow rate error'] + 10 ** profile['Temperature error'])
358435
if np.isnan(error).any(): breakpoint()
359-
error = interpolate.interp1d(error, time, bounds_error=False)(steady_state_error)
436+
time = interpolate.interp1d(error, time, bounds_error=False)(steady_state_error)
360437
if np.isnan(error).any(): breakpoint()
361-
return error
438+
return time
362439

363440
class Convergence:
364441

@@ -381,6 +458,8 @@ def __init__(self, system, file):
381458
flows = np.array(sum([i._imol._parent.data.rows for i in streams], []))
382459
Ts = np.array([i.T for i in streams])
383460
results = flows, Ts
461+
# cfe, te = zip(*[i._simulation_error() for i in all_stages])
462+
system.run()
384463
cfe, te = zip(*[i._simulation_error() for i in all_stages])
385464
benchmark = sum(cfe) + sum(te)
386465
with open(file, 'wb') as f: pickle.dump((*results, benchmark), f)
@@ -733,12 +812,15 @@ def division_mean_std(xdx, ydy):
733812

734813
# %% Benchmark plot
735814

736-
def plot_benchmark(systems=None, exclude=None, N=5, load=True, save=True):
815+
def plot_benchmark(systems=None, exclude=None, N=5, load=True, save=True, sort_by_stage=True, label_stages=True):
737816
if systems is None: systems = list(all_systems)
738817
if exclude is not None: systems = [i for i in systems if i not in exclude]
818+
if sort_by_stage: systems = sorted(systems, key=lambda x: system_stages[x])
739819
n_systems = len(systems)
740820
results = np.zeros([n_systems, 3])
741821
Ns = N * np.ones(n_systems, int)
822+
index = []
823+
values = []
742824
for m, sys in enumerate(systems):
743825
N = Ns[m]
744826
time = system_convergence_times[sys]
@@ -804,11 +886,18 @@ def plot_benchmark(systems=None, exclude=None, N=5, load=True, save=True):
804886
po_name = f'po_{time}_{sys}_profile.npy'
805887
file = os.path.join(simulations_folder, po_name)
806888
with open(file, 'wb') as f: pickle.dump(pos, f)
807-
pos = pos[1:]
808-
sms = sms[1:]
809889
cutoff = max([steady_state_error(i) for i in sms + pos])
810890
sms = np.array([benchmark(i, cutoff) for i in sms])
811891
pos = np.array([benchmark(i, cutoff) for i in pos])
892+
values.append(pos)
893+
name = system_labels[sys].replace('\n', ' ')
894+
index.append(
895+
(name, 'Phen.')
896+
)
897+
values.append(sms)
898+
index.append(
899+
(name, 'Seq. mod.')
900+
)
812901
pos_mean_std = [np.mean(pos), np.std(pos)]
813902
sms_mean_std = [np.mean(sms), np.std(sms)]
814903
mean, std = division_mean_std(pos_mean_std, sms_mean_std)
@@ -826,9 +915,18 @@ def plot_benchmark(systems=None, exclude=None, N=5, load=True, save=True):
826915
# Assume only time matters from here on
827916
# for i, (sm, po) in enumerate(system_results):
828917
# results[i] = uncertainty_percent(po['Time'], sm['Time'])
918+
df = pd.DataFrame(
919+
values,
920+
index=pd.MultiIndex.from_tuples(index),
921+
columns=[str(i) for i in range(N)],
922+
)
923+
file_name = 'Simulation times.xlsx'
924+
file = os.path.join(simulations_folder, file_name)
925+
df.to_excel(file)
926+
829927
n_rows = 1
830928
n_cols = 1
831-
fs = 10
929+
fs = 8
832930
bst.set_font(fs)
833931
bst.set_figure_size()
834932
fig, ax = plt.subplots(n_rows, n_cols)
@@ -842,7 +940,10 @@ def plot_benchmark(systems=None, exclude=None, N=5, load=True, save=True):
842940
yticks = (0, 25, 50, 75, 100, 125)
843941
yticklabels = [f'{i}%' for i in yticks]
844942
xticks = list(range(n_systems))
845-
xticklabels = [system_labels[sys] for sys in systems]
943+
if label_stages:
944+
xticklabels = [system_labels[sys] + f'\n[{system_stages[sys]} stages]' for sys in systems]
945+
else:
946+
xticklabels = [system_labels[sys] for sys in systems]
846947
sm_index, = np.where(results[:, -1])
847948
po_index, = np.where(~results[:, -1].astype(bool))
848949
plt.errorbar([xticks[i] for i in sm_index], results[sm_index, 0],
@@ -851,7 +952,7 @@ def plot_benchmark(systems=None, exclude=None, N=5, load=True, save=True):
851952
plt.errorbar([xticks[i] for i in po_index], results[po_index, 0], yerr=results[po_index, 1], color=cpo,
852953
marker='s', linestyle='', capsize=5, capthick=1.5, ecolor=black)
853954
plt.axhline(y=100, color='grey', ls='--', zorder=-1)
854-
plt.ylabel('Simulation time [%]')
955+
plt.ylabel('Relative simulation time [%]')
855956
bst.utils.style_axis(
856957
ax, xticks=xticks, yticks=yticks,
857958
xticklabels=xticklabels,
@@ -867,11 +968,12 @@ def plot_benchmark(systems=None, exclude=None, N=5, load=True, save=True):
867968

868969
# %% Profile plot
869970

870-
def dct_mean_profile(dcts: list[dict], keys: list[str], ub: float, n=50):
871-
mean = {i: np.zeros(n) for i in keys}
872-
tmin = np.min([np.min(i['Time']) for i in dcts])
873-
t = np.linspace(0, ub, n)
874-
goods = [np.zeros(n) for i in range(len(keys))]
971+
def dct_mean_profile(dcts: list[dict], keys: list[str], ub: float):
972+
tmin = np.mean([np.min(i['Time']) for i in dcts])
973+
size = np.min([len(i['Time']) for i in dcts])
974+
t = np.array([i['Time'][:size] for i in dcts]).mean(axis=0)
975+
mean = {i: np.zeros(size) for i in keys}
976+
goods = [np.zeros(size) for i in range(len(keys))]
875977
for dct in dcts:
876978
for i, good in zip(keys, goods):
877979
x = dct['Time']
@@ -893,29 +995,30 @@ def dct_mean_std(dcts: list[dict], keys: list[str]):
893995
return {i: (values[i].mean(), values[i].std()) for i in keys}
894996

895997
def plot_profile(
896-
systems=None, N=1, load=True, save=True
998+
systems=None, N=1, load=True, save=True,
999+
T=False,
8971000
):
8981001
if systems is None: systems = list(all_systems)
8991002
fs = 9
9001003
bst.set_font(fs)
9011004
keys = (
902-
'Component flow rate error',
903-
# 'Temperature error',
904-
# 'Component flow rate',
905-
# 'Stream temperature',
906-
# 'Stripping factor',
907-
# 'Material balance',
908-
# 'Energy balance',
1005+
'Temperature error' if T else 'Component flow rate error' ,
9091006
)
1007+
# 'Temperature error',
1008+
# 'Component flow rate',
1009+
# 'Stream temperature',
1010+
# 'Stripping factor',
1011+
# 'Material balance',
1012+
# 'Energy balance',
9101013
units = (
911-
r'$[\mathrm{mol} \cdot \mathrm{hr}^{\mathrm{-1}}]$',
912-
# r'$[\mathrm{K}]$',
913-
# r'$[\mathrm{mol} \cdot \mathrm{hr}^{\mathrm{-1}}]$',
914-
# r'$[\mathrm{K}]$',
915-
# r'$[-]$',
916-
# r'$[\mathrm{mol} \cdot \mathrm{hr}^{\mathrm{-1}}]$',
917-
# r'$[\mathrm{K}]$',
1014+
r'$[\mathrm{K}]$' if T else r'$[\mathrm{mol} \cdot \mathrm{hr}^{\mathrm{-1}}]$',
9181015
)
1016+
# r'$[\mathrm{K}]$',
1017+
# r'$[\mathrm{mol} \cdot \mathrm{hr}^{\mathrm{-1}}]$',
1018+
# r'$[\mathrm{K}]$',
1019+
# r'$[-]$',
1020+
# r'$[\mathrm{mol} \cdot \mathrm{hr}^{\mathrm{-1}}]$',
1021+
# r'$[\mathrm{K}]$',
9191022
n_rows = len(units)
9201023
n_cols = len(systems)
9211024
if n_cols >= 2:
@@ -985,8 +1088,16 @@ def plot_profile(
9851088
sms = sms[1:]
9861089
tub = system_tickmarks[sys][-1]
9871090
tub = min(tub, min([dct['Time'][-1] for dct in sms]), min([dct['Time'][-1] for dct in pos]))
1091+
for dct in sms + pos:
1092+
for key in dct:
1093+
if key == 'Time': continue
1094+
dct[key] = 10 ** np.array(dct[key])
9881095
sm = dct_mean_profile(sms, keys, tub)
9891096
po = dct_mean_profile(pos, keys, tub)
1097+
for dct in (sm, po):
1098+
for key in dct:
1099+
if key == 'Time': continue
1100+
dct[key] = np.log10(dct[key])
9901101
csm = Color(fg='#33BBEE').RGBn
9911102
cpo = Color(fg='#EE7733').RGBn
9921103
labels = {
@@ -1014,6 +1125,9 @@ def plot_profile(
10141125
ypo = np.array(po[i])
10151126
ysm = gaussian_filter(ysm, 0.2)
10161127
ypo = gaussian_filter(ypo, 0.2)
1128+
cutoff = ysm.min() + 1
1129+
sm_index = sum(ysm > cutoff)
1130+
po_index = sum(ypo > cutoff)
10171131
tsm = np.array(sm['Time'])
10181132
tpo = np.array(po['Time'])
10191133
if ms:
@@ -1024,6 +1138,11 @@ def plot_profile(
10241138
plt.plot(tsm, ysm, lw=0, marker='.', color=csm, markersize=2.5)
10251139
plt.plot(tpo, ypo, lw=0, marker='.', color=cpo, markersize=2.5)
10261140
plt.plot(tpo, ypo, '-', color=cpo, lw=1.5, alpha=0.5)
1141+
plt.plot(tsm[sm_index], ysm[sm_index], lw=0, marker='x', color=csm, markersize=5)
1142+
plt.plot(tpo[po_index], ypo[po_index], lw=0, marker='x', color=cpo, markersize=5)
1143+
print(sm_index, po_index)
1144+
# plt.annotate(str(sm_index), (tsm[sm_index], ysm[sm_index]), (tsm[sm_index], ysm[sm_index] - 1.5), color=csm)
1145+
# plt.annotate(str(po_index), (tpo[po_index], ypo[po_index]), (tpo[po_index], ypo[po_index] + 0.5), color=cpo)
10271146
yticklabels = m == 0
10281147
yticks = yticks_list[n]
10291148
if yticklabels:
@@ -1094,4 +1213,4 @@ def plot_profile(
10941213
name = f'PO_SM_{system_names}_profile.{i}'
10951214
file = os.path.join(images_folder, name)
10961215
plt.savefig(file, dpi=900, transparent=True)
1097-
return fig, all_axes, sms, pos
1216+
# return fig, all_axes, sms, pos
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"acetic_acid_simple": 13, "acetic_acid_complex_decoupled": 21, "acetic_acid_complex": 44, "butanol_purification": 4, "ethanol_purification": 2, "haber_bosch_process": 4}

benchmark/system_stages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"acetic_acid_simple": 13, "acetic_acid_complex_decoupled": 21, "acetic_acid_complex": 44, "butanol_purification": 4, "ethanol_purification": 2, "haber_bosch_process": 4}

0 commit comments

Comments
 (0)