Skip to content

Commit 548c8e9

Browse files
committed
0.52.16 release; add tutorial on distillation
1 parent 9d30ad0 commit 548c8e9

File tree

7 files changed

+897
-34
lines changed

7 files changed

+897
-34
lines changed

biosteam/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
1414
"""
1515
from __future__ import annotations
16-
__version__ = '2.52.15'
16+
__version__ = '2.52.16'
1717

1818
#: Chemical engineering plant cost index (defaults to 567.5 at 2017).
1919
CE: float = 567.5

biosteam/units/distillation.py

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,55 @@ def _load_components(self, partial_condenser, condenser_thermo, reboiler_thermo)
431431
self.reboiler-0, ('boilup', self-1), thermo=reboiler_thermo,
432432
)
433433

434+
def to_rigorous_column(self, dP=None, kwargs_only=False, print_code=False, **kwargs):
435+
design = self.design_results
436+
feed_stage = int(design['Theoretical feed stage'])
437+
N_stages = int(design['Theoretical stages'])
438+
R = design['Reflux']
439+
flow_split = self.outs[1].F_mol / self.ins[0].F_mol
440+
if dP is None:
441+
dP = 690.0 # Heuristically, pressure drops are between 0.35 to 1.03 kPa
442+
if dP:
443+
P_reboiler = self.outs[1].P
444+
P = [P_reboiler + i * dP for i in range(N_stages)]
445+
else:
446+
P = self.P
447+
kwargs = dict(
448+
N_stages=N_stages,
449+
feed_stages=[feed_stage],
450+
LHK=self.LHK,
451+
stage_specifications={
452+
0: ('Reflux', R),
453+
-1: ('Flow', flow_split),
454+
},
455+
P=P,
456+
**kwargs,
457+
)
458+
if print_code:
459+
kwargs_code = kwargs.copy()
460+
kwargs_code['P'] = f'[{self.P} + i*{dP} for i in range({N_stages})]'
461+
kwargs_code['stage_specifications'] = {
462+
0: ('Reflux', round(R, 3)),
463+
-1: ('Flow', round(flow_split, 5)),
464+
}
465+
code = (
466+
f'MESHDistillation(\n'
467+
f" ins=[{', '.join([i.ID for i in self.ins])}],\n"
468+
f" outs=[{', '.join([i.ID for i in self.outs])}]"
469+
)
470+
code += bst.repr_kwargs(kwargs_code, dlim=',\n ')
471+
code += '\n)'
472+
print(code)
473+
if kwargs_only:
474+
return kwargs
475+
return MESHDistillation(
476+
None,
477+
ins=self.ins,
478+
outs=self.outs,
479+
thermo=self.thermo,
480+
**kwargs
481+
)
482+
434483
@property
435484
def distillate(self):
436485
return self.outs[0]
@@ -866,36 +915,33 @@ def _run_condenser_and_reboiler(self):
866915
liq = reboiler.ins[0]
867916
liq.mix_from([bottoms_product, boilup], energy_balance=False)
868917
liq.phase = 'l'
869-
liq_T = liq.bubble_point_at_P().T
870-
if liq_T > bp.T: liq_T = bp.T - 0.1
871-
liq.T = liq_T
872-
self.pump.ins[0].copy_like(liq)
873-
self.pump.simulate()
918+
liq.T = bp.T
874919
self.bottoms_split.simulate()
875920
if self._partial_condenser: self.reflux_drum.simulate()
876921

877922
def _simulate_components(self):
878923
reboiler = self.reboiler
879924
condenser = self.condenser
880925
Q_condenser = condenser.outs[0].H - condenser.ins[0].H
926+
condenser_kwargs = dict(duty=Q_condenser)
881927
H_out = self.H_out
882928
H_in = self.H_in
883929
Q_overall_boiler = H_out - H_in - Q_condenser
884-
Q_boiler = reboiler.outs[0].H - reboiler.ins[0].H
885-
if Q_boiler < Q_overall_boiler:
886-
liquid = reboiler.ins[0]
887-
H_out_boiler = reboiler.outs[0].H
888-
try:
889-
liquid.H = H_out_boiler - Q_overall_boiler
890-
except:
891-
liquid.phase = 'l'
892-
boiler_kwargs = dict(duty=Q_boiler)
893-
else:
894-
boiler_kwargs = dict(duty=Q_overall_boiler)
895-
condenser_kwargs = dict(duty=Q_condenser)
930+
H_out_boiler = reboiler.outs[0].H
931+
liquid_in = reboiler.ins[0]
932+
vapor = reboiler.outs[0]
933+
try:
934+
liquid_in.H = H_out_boiler - Q_overall_boiler
935+
except:
936+
liq_T = liquid_in.bubble_point_at_P().T
937+
if liq_T > vapor.T: liq_T = vapor.T - 0.1
938+
vapor.T = liq_T
939+
Q_boiler = reboiler.outs[0].H - reboiler.ins[0].H
940+
boiler_kwargs = dict(duty=Q_boiler)
896941
else:
897-
boiler_kwargs = dict(duty=Q_boiler)
898-
condenser_kwargs = dict(duty=Q_condenser)
942+
boiler_kwargs = dict(duty=Q_overall_boiler)
943+
self.pump.ins[0].copy_like(liquid_in)
944+
self.pump.simulate()
899945
reboiler.simulate(
900946
run=False,
901947
design_kwargs=boiler_kwargs,

biosteam/units/stage.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,12 +2071,14 @@ class MultiStageEquilibrium(Unit):
20712071
damping = 0 # Damping factor; defined as x_(i+1) = damping * x_i + (1 - damping) * f(x_i)
20722072
minimum_residual_reduction = 0.25 # Minimum fractional reduction in residual for simulation.
20732073
iteration_memory = 10 # Length of recorded iterations.
2074+
preconditioning_tolerance = 1e-3
2075+
preconditioning_relative_tolerance = 1e-3
20742076
inside_maxiter = 100
20752077
default_max_attempts = 5
20762078
default_maxiter = 100
2077-
default_optimize_result = True
2078-
default_tolerance = 1e-5
2079-
default_relative_tolerance = 1e-4
2079+
default_optimize_result = False
2080+
default_tolerance = 1e-6
2081+
default_relative_tolerance = 1e-6
20802082
default_algorithms = ('inside out', 'phenomena', 'simultaneous correction', 'sequential modular')
20812083
default_inside_loop_algorithm = 'simultaneous correction'
20822084
decomposition_algorithms = {
@@ -2258,7 +2260,7 @@ def _init(self,
22582260
N_given = len(outs)
22592261
N_outs = 2 + N_side_draws
22602262
top_mark = 2 + len(top_side_draws)
2261-
if stage_specifications[0] == ('Reflux', inf) and N_given < N_outs:
2263+
if stage_specifications.get(0) == ('Reflux', inf) and N_given < N_outs:
22622264
# Empty vapor is not given (for full condenser scenario)
22632265
tsd_iter = iter(outs[2:top_mark])
22642266
bsd_iter = iter([outs[0], *outs[top_mark:]])
@@ -2386,6 +2388,9 @@ def _init(self,
23862388

23872389
# %% Optimization
23882390

2391+
def residual(self):
2392+
return self._objective(self._get_point())
2393+
23892394
def _get_point(self, x=None):
23902395
if x is None: x = np.zeros(self._point_shape)
23912396
for i, stage in enumerate(self.stages):
@@ -2520,12 +2525,12 @@ def _run(self):
25202525
x = self.hot_start()
25212526
algorithms = self.algorithms
25222527
methods = self.methods
2523-
optimize_result = self.optimize_result
2528+
optimize_result = self.optimize_result and 'K' not in self.partition_data
25242529
f = self._iter
25252530
analysis_mode = self._convergence_analysis_mode
25262531
maxiter = self.maxiter
2527-
xtol = 100 * self.tolerance if optimize_result else self.tolerance
2528-
rtol = 100 * self.relative_tolerance if optimize_result else self.relative_tolerance
2532+
xtol = self.preconditioning_tolerance if optimize_result else self.tolerance
2533+
rtol = self.preconditioning_relative_tolerance if optimize_result else self.relative_tolerance
25292534
self.iter = 0
25302535
for n in range(self.max_attempts):
25312536
self.attempt = n
@@ -2559,7 +2564,7 @@ def _run(self):
25592564
break
25602565
else: continue
25612566
break
2562-
if optimize_result and 'K' not in self.partition_data: x = self._simultaneous_correction(x, 'hybr')
2567+
if optimize_result: x = self._simultaneous_correction(x, 'hybr')
25632568
self._set_point(x)
25642569
# Last simulation to force mass balance
25652570
self.update_mass_balance()

docs/tutorial/Distillation.ipynb

Lines changed: 813 additions & 0 deletions
Large diffs are not rendered by default.

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
name='biosteam',
1212
packages=['biosteam'],
1313
license='MIT',
14-
version='2.52.15',
14+
version='2.52.16',
1515
description='The Biorefinery Simulation and Techno-Economic Analysis Modules',
1616
long_description=open('README.rst', encoding='utf-8').read(),
1717
author='Yoel Cortes-Pena',
1818
install_requires=['IPython>=7.9.0',
19-
'thermosteam>=0.52.12',
19+
'thermosteam>=0.52.16',
2020
'graphviz>=0.17',
2121
'numpoly==1.2.13',
2222
'chaospy==4.3.15',

tests/test_equilibrium_stages.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def test_distillation():
293293
N_stages=10,
294294
ins=[hot_extract],
295295
feed_stages=[5],
296-
outs=['', 'bottoms_product', 'distillate'],
296+
outs=['distillate', 'bottoms_product'],
297297
full_condenser=True,
298298
reflux=1.0,
299299
boilup=3.5,
@@ -302,9 +302,8 @@ def test_distillation():
302302
)
303303
distillation.simulate()
304304
flows = [
305-
[0.0, 0.0, 0.0],
305+
[22.055922550575424, 0.1438997998268773, 87.15249997005776],
306306
[0.11207744942457795, 4.350500200173122, 22.35850002994225],
307-
[22.055922550575424, 0.1438997998268773, 87.15249997005776]
308307
]
309308
for i, j in zip(distillation.outs, flows):
310309
assert_allclose(i.mol, j, rtol=1e-3, atol=1e-3)

0 commit comments

Comments
 (0)