Skip to content

Commit adceea7

Browse files
committed
solve merge conflict
2 parents 559be07 + aa88fd1 commit adceea7

9 files changed

Lines changed: 188 additions & 60 deletions

biosteam/_settings.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,22 +165,23 @@ def define_allocation_property(
165165
self, name: str, basis: float,
166166
stream: Callable=None,
167167
power_utility: Callable=None,
168-
heat_utility: Callable=None
168+
heat_utility: Callable=None,
169+
safe: bool=True,
169170
):
170171
"""Define a new allocation property by property getters."""
171172
allocation_name = name + '-allocation'
172173
units = basis + '/hr'
173174
if stream is not None:
174175
bst.Stream.define_property(
175-
allocation_name, units, stream,
176+
allocation_name, units, stream, safe=safe
176177
)
177178
if power_utility is not None:
178179
bst.PowerUtility.define_property(
179-
allocation_name, units, power_utility,
180+
allocation_name, units, power_utility, safe=safe
180181
)
181182
if heat_utility is not None:
182183
bst.HeatUtility.define_property(
183-
allocation_name, units, heat_utility,
184+
allocation_name, units, heat_utility, safe=safe
184185
)
185186
bst.allocation_properties[name] = basis
186187

@@ -255,16 +256,19 @@ def revenue_allocation(stream):
255256
stream=lambda self: max(self.LHV, 0),
256257
power_utility=lambda self: max(-self.rate * 3600, 0.),
257258
heat_utility=lambda self: max(self.duty, 0),
259+
safe=False,
258260
)
259261
settings.define_allocation_property(
260262
'revenue', 'USD',
261263
stream=revenue_allocation,
262264
power_utility=lambda self: max(-self.cost, 0.),
263265
heat_utility=lambda self: max(self.cost, 0),
266+
safe=False,
264267
)
265268
settings.define_allocation_property(
266-
'mass', 'kg', stream=lambda self: np.dot(self.chemicals.MW, self.mol)
269+
'mass', 'kg', stream=lambda self: np.dot(self.chemicals.MW, self.mol),
270+
safe=False,
267271
)
268272
settings.define_impact_indicator(
269-
'WU', 'L' # Water usage is a special indicator native to BioSTEAM
273+
'WU', 'L', # Water usage is a special indicator native to BioSTEAM
270274
)

biosteam/_system.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2682,9 +2682,8 @@ def track_convergence(
26822682
'energy': [],
26832683
'phenomenode': [],
26842684
}
2685-
if not self.recycle:
2686-
self.timer = Timer()
2687-
self.timer.start()
2685+
self.timer = Timer()
2686+
self.timer.start()
26882687

26892688
else:
26902689
for i in self.variable_profiles: i.getter = None
@@ -2696,6 +2695,7 @@ def track_convergence(
26962695
self.edge_profiles = None
26972696
self.grouped_variables = None
26982697
for i in self.units: i.tracking = False
2698+
self.timer = None
26992699

27002700
def get_profiles(self, equation_profiles, edge_profiles):
27012701
time, variable_profiles = self.get_variable_profiles()
@@ -2738,9 +2738,9 @@ def get_phenomegraph(self, bipartite=False, **kwargs):
27382738
def get_monopartite_phenomegraph(self, decomposition=None, dotfile=None):
27392739
if decomposition is None: decomposition = self.algorithm.lower()
27402740
subgraph_units = [i.ID for i in self.path]
2741-
if 'phenomenode' in decomposition:
2741+
if 'phenomena' in decomposition:
27422742
if 'modular' in decomposition:
2743-
criteria = [*all_subgraphs, *[(i, 'phenomenode') for i in subgraph_units]]
2743+
criteria = [*all_subgraphs, *[(i, 'phenomena') for i in subgraph_units]]
27442744
else:
27452745
criteria = all_subgraphs
27462746
elif 'modular' in decomposition:
@@ -2804,7 +2804,7 @@ def get_bipartite_phenomegraph(
28042804
raise ValueError('unknown decompostion')
28052805

28062806
def equation_match(equation, subgraph):
2807-
name = equation.name.replace('phenomenode', 'phenomena')
2807+
name = equation.name
28082808
if isinstance(subgraph, tuple):
28092809
return all([i in name for i in subgraph])
28102810
else:

biosteam/process_tools/system_factory.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,6 @@ def __call__(self, ID=None, ins=None, outs=None,
237237
bst.settings.set_thermo(fthermo(chemicals=chemicals))
238238
elif not hasattr(bst.settings, '_thermo'):
239239
bst.settings.set_thermo(fthermo())
240-
if autorename is not None:
241-
original_autorename = tmo.utils.Registry.AUTORENAME
242-
tmo.utils.Registry.AUTORENAME = autorename
243240
ins = create_streams(self.ins, ins, 'inlets', self.fixed_ins_size)
244241
outs = create_streams(self.outs, outs, 'outlets', self.fixed_outs_size)
245242
rename = area is not None
@@ -275,7 +272,13 @@ def __call__(self, ID=None, ins=None, outs=None,
275272
ins = tuple([module.auxin(i) for i in module.ins])
276273
outs = tuple([module.auxout(i) for i in module.outs])
277274
with bst.Flowsheet(ID), bst.System(**options) as system:
278-
self.f(ins, outs, **kwargs)
275+
if autorename is None:
276+
self.f(ins, outs, **kwargs)
277+
else:
278+
original_autorename = tmo.utils.Registry.AUTORENAME
279+
tmo.utils.Registry.AUTORENAME = autorename
280+
try: self.f(ins, outs, **kwargs)
281+
finally: tmo.utils.Registry.AUTORENAME = original_autorename
279282
module._init(system=system)
280283
else: # Create system
281284
with (bst.MockSystem() if mockup else bst.System(**options)) as system:
@@ -286,10 +289,15 @@ def __call__(self, ID=None, ins=None, outs=None,
286289
elif udct:
287290
unit_registry = system.flowsheet.unit
288291
irrelevant_units = set(unit_registry)
289-
self.f(ins, outs, **kwargs)
292+
if autorename is None:
293+
self.f(ins, outs, **kwargs)
294+
else:
295+
original_autorename = tmo.utils.Registry.AUTORENAME
296+
tmo.utils.Registry.AUTORENAME = autorename
297+
try: self.f(ins, outs, **kwargs)
298+
finally: tmo.utils.Registry.AUTORENAME = original_autorename
290299
system.load_inlet_ports(ins, {k: i for i, j in enumerate(self.ins) if (k:=get_name(j)) is not None})
291300
system.load_outlet_ports(outs, {k: i for i, j in enumerate(self.outs) if (k:=get_name(j)) is not None})
292-
if autorename is not None: tmo.utils.Registry.AUTORENAME = original_autorename
293301
if udct:
294302
unit_dct = {}
295303
def add(key, unit):

biosteam/units/abstract_stirred_tank_reactor.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ class AbstractStirredTankReactor(PressureVessel, Unit, isabstract=True):
269269
#: Phases accounted for in residence time
270270
tau_phases_default: str = 'l'
271271

272+
#: Basis of residence time. Defaults to inlet flow rates
273+
tau_basis: str = 'ins' # Alternatively, 'outs'
274+
272275
@property
273276
def effluent(self):
274277
return self.outs[-1]
@@ -426,12 +429,14 @@ def _get_duty(self): # User can replace
426429

427430
def _design(self, size_only=False):
428431
Design = self.design_results
429-
ins_F_vol = sum([i.F_vol for i in self.ins if i.phase in self.tau_phases])
432+
tau_basis = self.tau_basis
433+
streams = getattr(self, tau_basis)
434+
F_vol = sum([i.F_vol for i in streams if i.phase in self.tau_phases])
430435
P_pascal = (self.P if self.P else self.outs[0].P)
431436
P_psi = P_pascal * 0.000145038 # Pa to psi
432437
length_to_diameter = self.length_to_diameter
433438
if self.batch:
434-
v_0 = ins_F_vol
439+
v_0 = F_vol
435440
tau = self.tau
436441
tau_0 = self.tau_0
437442
V_wf = self.V_wf
@@ -446,14 +451,14 @@ def _design(self, size_only=False):
446451
N = Design['Number of reactors']
447452
else:
448453
if self.N is None:
449-
V_total = ins_F_vol * self.tau / self.V_wf
454+
V_total = F_vol * self.tau / self.V_wf
450455
N = ceil(V_total/self.V_max)
451456
if N == 0:
452457
V_reactor = 0
453458
else:
454459
V_reactor = V_total / N
455460
else:
456-
V_total = ins_F_vol * self.tau / self.V_wf
461+
V_total = F_vol * self.tau / self.V_wf
457462
V_reactor = V_total / self.N
458463
Design['Reactor volume'] = V_reactor
459464
Design['Number of reactors'] = N

biosteam/units/design_tools/Gibbs_equilibrium_reaction.py

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,54 @@ def Gibbs_equilibrium_objective(
1616
product, # Product stream (constant T and P)
1717
main_phase, # Initial guess phase
1818
phase_equilibrium, # Name of phase equilibrium. Valid inputs include None, 'vle', 'lle', and 'vlle'
19+
phase_hook,
1920
):
2021
mask = mass < 0
2122
penalty = mask.any()
2223
if penalty: mass[mask] = 0
2324
product.empty()
2425
product[main_phase].imol[IDs] = mass / MWs
25-
if phase_equilibrium is not None:
26-
eq = getattr(product, phase_equilibrium)
27-
eq(T=eq.T, P=eq.P)
26+
if phase_hook is None:
27+
if phase_equilibrium is not None:
28+
eq = getattr(product, phase_equilibrium)
29+
eq(T=eq.T, P=eq.P)
30+
else:
31+
phase_hook(product)
2832
return product.G
2933

3034
def minimize_Gibbs_free_energy(
3135
product,
3236
IDs=None, # Names of potential products
3337
method=None,
38+
phase_hook=None,
3439
):
3540
if method is None: method = 'differential evolution'
41+
42+
# Normalize to 1 kg/hr
3643
F_mass = product.F_mass
37-
mol_norm = product.mol / F_mass # Normalize
44+
mol_norm = product.mol / F_mass
45+
46+
# Get atomic flows
3847
chemicals = product.chemicals
3948
atoms = chemicals.formula_array @ mol_norm
4049
index, = np.where(atoms)
4150
atoms = atoms[index]
4251
formula_array = chemicals.formula_array[index]
52+
53+
# Reduce to only possible products (such that all atoms exist in feed)
4354
if IDs is None:
4455
IDs = chemicals.IDs
4556
MWs = chemicals.MW
4657
else:
4758
index = chemicals.get_index(IDs)
4859
formula_array = formula_array[:, index]
4960
MWs = chemicals.MW[index]
61+
index, = np.where(formula_array.any(axis=0))
62+
IDs = [IDs[i] for i in index]
63+
formula_array = formula_array[:, index]
64+
MWs = MWs[index]
65+
66+
# Specify atomic and mass constraints
5067
N = len(IDs)
5168
lb = np.zeros(N)
5269
ub = np.ones(N)
@@ -59,24 +76,41 @@ def minimize_Gibbs_free_energy(
5976
atomic_balance = LinearConstraint(
6077
formula_array / MWs, atoms, atoms
6178
)
62-
match product.phases:
63-
case ('g', 'l'):
64-
phase_equilibrium = 'vle'
79+
80+
# Choose main phase and phase equilibrium algorithm
81+
if phase_hook:
82+
phases = product.phases
83+
if 'g' in phases:
6584
main_phase = 'g'
66-
case ('L', 'l'):
67-
phase_equilibrium = 'lle'
68-
main_phase = 'l'
69-
case ('L', 'g', 'l'):
70-
phase_equilibrium = 'vlle'
85+
elif 'l' in phases:
7186
main_phase = 'l'
72-
case ('l', 's'):
73-
phase_equilibrium = 'sle'
74-
main_phase = 'l'
75-
case [main_phase]:
76-
phase_equilibrium = None
77-
case _:
78-
raise RuntimeError(f'phase equilibrium for {product.phases!r} not supported')
79-
f_args = (MWs, IDs, product, main_phase, phase_equilibrium)
87+
elif 'L' in phases:
88+
main_phase = 'L'
89+
elif 's' in phases:
90+
main_phase = 's'
91+
else:
92+
raise RuntimeError('main phase could not be found')
93+
else:
94+
match product.phases:
95+
case ('g', 'l'):
96+
phase_equilibrium = 'vle'
97+
main_phase = 'g'
98+
case ('L', 'l'):
99+
phase_equilibrium = 'lle'
100+
main_phase = 'l'
101+
case ('L', 'g', 'l'):
102+
phase_equilibrium = 'vlle'
103+
main_phase = 'l'
104+
case ('l', 's'):
105+
phase_equilibrium = 'sle'
106+
main_phase = 'l'
107+
case [main_phase]:
108+
phase_equilibrium = None
109+
case _:
110+
raise RuntimeError(f'phase equilibrium for {product.phases!r} not supported')
111+
112+
# Solve
113+
f_args = (MWs, IDs, product, main_phase, phase_equilibrium, phase_hook)
80114
if method == 'differential evolution':
81115
polish = lambda *args, **kwargs: minimize(
82116
*args, **kwargs,
@@ -89,7 +123,7 @@ def minimize_Gibbs_free_energy(
89123
bounds=bounds,
90124
constraints=[atomic_balance],
91125
polish=polish,
92-
tol=1e-5,
126+
tol=1e-6,
93127
seed=0,
94128
)
95129
elif method == 'COBYLA':
@@ -107,9 +141,13 @@ def minimize_Gibbs_free_energy(
107141
"only 'SHGO' and 'differential evolution' "
108142
"are valid methods"
109143
)
144+
145+
# Set solution
110146
product.empty()
111147
product[main_phase].imol[IDs] = solution.x / MWs
112-
if phase_equilibrium is not None:
148+
if phase_hook:
149+
phase_hook(product)
150+
elif phase_equilibrium is not None:
113151
eq = getattr(product, phase_equilibrium)
114152
eq(T=eq.T, P=eq.P)
115153
product.F_mass = F_mass

biosteam/units/equilibrium_reactor.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import biosteam as bst
99
from .abstract_stirred_tank_reactor import AbstractStirredTankReactor
1010
from .design_tools.Gibbs_equilibrium_reaction import minimize_Gibbs_free_energy
11-
from typing import Optional, Iterable
11+
from typing import Optional, Iterable, Callable
1212

1313
__all__ = (
1414
'EquilibriumReactor',
@@ -153,18 +153,21 @@ class EquilibriumReactor(AbstractStirredTankReactor):
153153
kW_per_m3_default = 0
154154
batch_default = False
155155
tau_phases_default = 'lg'
156+
tau_basis = 'outs' # Alternatively, 'ins'
156157
method_default = 'differential evolution' # Alternatively 'COBYLA'
157158

158159
def _init(self,
159160
phases: Optional[str]=None,
160161
products: Optional[Iterable[str]]=None,
161162
method: Optional[str]=None,
163+
phase_hook: Optional[Callable]=None,
162164
**kwargs
163165
):
164166
AbstractStirredTankReactor._init(self, **kwargs)
165167
self.phases = phases
166168
self.products = products
167169
self.method = self.method_default if method is None else method
170+
self.phase_hook = phase_hook
168171

169172
def _get_duty(self): return self.Hnet
170173

@@ -186,5 +189,5 @@ def _run(self):
186189
product.P = self.P
187190
product.empty()
188191
outs[0].mix_flows(ins)
189-
minimize_Gibbs_free_energy(product, self.products, method=self.method)
192+
minimize_Gibbs_free_energy(product, self.products, method=self.method, phase_hook=self.phase_hook)
190193

0 commit comments

Comments
 (0)