BioSTEAMDevelopmentGroup / Bioindustrial-Park

BioSTEAM's Premier Repository for Biorefinery Models and Results
MIT License
36 stars 17 forks source link

Consultation about loop convergence #104

Open zasddsgg opened 4 months ago

zasddsgg commented 4 months ago

Hello, when I run the following code, I have a problem with the loop not converging. I tried some ways, but it still hasn’t been solved. Could I ask you how to solve it? Thanks for your help.

The code is as follows:

import biosteam as bst
bst.nbtutorial()
from biosteam import settings
from biosteam import units
import thermosteam as tmo

Riboflavin = bst.Chemical('Riboflavin', Hf=-55700, Tc=650+273.15, Tb=240+273.15, Hvap=70000)
Riboflavin.mu.l.add_method(f=0.0003259750718128194)
Ethanol = bst.Chemical('Ethanol')
Water = bst.Chemical('Water')

bst.settings.set_thermo([Riboflavin, Ethanol, Water])

riboflavin = bst.Stream(
    ID='riboflavin',
    price=0.0916,  
    total_flow=1000,
    units='kg/hr',
    Riboflavin=300,
    Ethanol=350,
    T=100+273.15,
    P=101325,
)
Water = bst.Stream(
    ID='Water',
    price=0.0916,  
    total_flow=350,
    Water=1,
    units='kg/hr',
    T=100+273.15,
    P=101325,
)
recycle=bst.Stream('recycle')

M101=units.Mixer('M101', (riboflavin, Water))
P101=units.Pump('P101', M101-0, P=101325*1.1)
M102=units.Mixer('M102', (P101-0, recycle))
P102=units.Pump('P102', M102-0, P=101325*2)
d=M102-0
@P102.add_specification(run=True)
def adjust_pump_pressure_P102():
    P102.P = d.P + 10132.5
H101 = units.HXutility('H101', P102-0, T=370, rigorous=True)
D101 = units.BinaryDistillation('D101', H101-0, P=10132.5, Lr=0.9, Hr=0.9, k=1.2, Rmin=0.001, LHK=('Ethanol', 'Water'))
H102 = units.HXutility('H102', D101-0, T=300, rigorous=True)
F102 = units.Flash('F102', H102-0, outs=('F102_vapor', 'F102_liquid'), P=10132.5, Q=0)
P103=units.Pump('P103', F102-1, recycle, P=101325)
@P103.add_specification(run=True, impacted_units=[M101])
def adjust_ethanol_flow():                       
    F_mass_Ethanol = recycle.imass['Ethanol']
    F_mass_Water = recycle.imass['Water']
    riboflavin.imass['Ethanol'] = max(350 - F_mass_Ethanol, 0)
    Water.imass['Water'] = 350 - F_mass_Water
@P103.add_specification(run=True) 
def adjust_pump_pressure_P103():
    a = H102-0
    P103.P = a.P + 10132.5

Riboflavin_sys = bst.main_flowsheet.create_system('Riboflavin_sys')
Riboflavin_sys.simulate()
Riboflavin_sys.diagram('thorough', format='png')
riboflavin.imass

The error is as follows:

RuntimeError                              Traceback (most recent call last)
Cell In[1], line 71
     68     P103.P = a.P + 10132.5
     70 Riboflavin_sys = bst.main_flowsheet.create_system('Riboflavin_sys')
---> 71 Riboflavin_sys.simulate()
     72 Riboflavin_sys.diagram('thorough', format='png')
     73 riboflavin.imass

File D:\anaconda\envs\zddd\lib\site-packages\biosteam\_system.py:2601, in System.simulate(self, update_configuration, units, design_and_cost, **kwargs)
   2581 def simulate(self, update_configuration: Optional[bool]=None, units=None, 
   2582              design_and_cost=None, **kwargs):
   2583     """
   2584     If system is dynamic, run the system dynamically. Otherwise, converge 
   2585     the path of unit operations to steady state. After running/converging 
   (...)
   2599         
   2600     """
-> 2601     with self.flowsheet.temporary():
   2602         specifications = self._specifications
   2603         if specifications and not self._running_specifications:

File D:\anaconda\envs\zddd\lib\site-packages\biosteam\_flowsheet.py:38, in TemporaryFlowsheet.__exit__(self, type, exception, traceback)
     36 def __exit__(self, type, exception, traceback):
     37     main_flowsheet.set_flowsheet(self.original)
---> 38     if exception: raise exception

File D:\anaconda\envs\zddd\lib\site-packages\biosteam\_system.py:2657, in System.simulate(self, update_configuration, units, design_and_cost, **kwargs)
   2651         outputs = self.simulate(
   2652             update_configuration=True, 
   2653             design_and_cost=design_and_cost,
   2654             **kwargs
   2655         )
   2656     else:
-> 2657         raise error
   2658 else:
   2659     if (not update_configuration # Avoid infinite loop
   2660         and self._connections != [i.get_connection() for i in self.streams]):
   2661         # Connections has been updated within simulation.

File D:\anaconda\envs\zddd\lib\site-packages\biosteam\_system.py:2644, in System.simulate(self, update_configuration, units, design_and_cost, **kwargs)
   2642 else:
   2643     try:
-> 2644         outputs = self.converge(**kwargs)
   2645         if design_and_cost: self._summary()
   2646     except Exception as error:

File D:\anaconda\envs\zddd\lib\site-packages\biosteam\_system.py:2344, in System.converge(self, recycle_data, update_recycle_data)
   2342     for i in range(self._N_runs): method()
   2343 else:
-> 2344     method()
   2345 if update_recycle_data:
   2346     try: recycle_data.update()

File D:\anaconda\envs\zddd\lib\site-packages\biosteam\_system.py:2297, in System._solve(self)
   2295 data = self._get_recycle_data()
   2296 f = self._iter_run_conditional if conditional else self._iter_run
-> 2297 try: solver(f, data, **kwargs)
   2298 except (IndexError, ValueError) as error:
   2299     data = self._get_recycle_data()

File D:\anaconda\envs\zddd\lib\site-packages\flexsolve\iterative_solvers.py:48, in conditional_fixed_point(f, x)
     46 condition = True
     47 while condition:
---> 48     x1, condition = f(x0)
     49     x0 = x1
     50 return x1

File D:\anaconda\envs\zddd\lib\site-packages\biosteam\_system.py:2092, in System._iter_run_conditional(self, data)
   2084 not_converged = not (
   2085     (mol_error < self.molar_tolerance
   2086      or rmol_error < self.relative_molar_tolerance)
   (...)
   2089      or rT_error < self.relative_temperature_tolerance)
   2090 )
   2091 if not_converged and self._iter >= self.maxiter:
-> 2092     if self.strict_convergence: raise RuntimeError(f'{repr(self)} could not converge' + self._error_info())
   2093     else: not_converged = False
   2094 return self._get_recycle_data(), not_converged

RuntimeError: <System: Riboflavin_sys> could not converge
Highest convergence error among components in recycle
stream M101-0 after 200 loops:
- flow rate   5.74e+01 kmol/hr (1e+02%)
- temperature 0.00e+00 K (0%)
zasddsgg commented 3 months ago

Hello, could I trouble you take some time to look at this problem. Thanks for your help.

yoelcortes commented 3 months ago

@zasddsgg, sure thing. It looks like you discovered a special case that doesn't work in BioSTEAM yet. I'll try to make it work for this special case in the future. In the mean while, you can manually add M101.run_until(P103, inclusive=True):

import biosteam as bst
from biosteam import settings
from biosteam import units
import thermosteam as tmo

Riboflavin = bst.Chemical('Riboflavin', Hf=-55700, Tc=650+273.15, Tb=240+273.15, Hvap=70000)
Riboflavin.mu.l.add_method(f=0.0003259750718128194)
Ethanol = bst.Chemical('Ethanol')
Water = bst.Chemical('Water')

bst.settings.set_thermo([Riboflavin, Ethanol, Water])

riboflavin = bst.Stream(
    ID='riboflavin',
    price=0.0916,  
    total_flow=1000,
    units='kg/hr',
    Riboflavin=300,
    Ethanol=350,
    T=100+273.15,
    P=101325,
)
Water = bst.Stream(
    ID='Water',
    price=0.0916,  
    total_flow=350,
    Water=1,
    units='kg/hr',
    T=100+273.15,
    P=101325,
)
recycle=bst.Stream('recycle')

M101=units.Mixer('M101', (riboflavin, Water))
P101=units.Pump('P101', M101-0, P=101325*1.1)
M102=units.Mixer('M102', (P101-0, recycle))
P102=units.Pump('P102', M102-0, P=101325*2)
d=M102-0
@P102.add_specification(run=True)
def adjust_pump_pressure_P102():
    P102.P = d.P + 10132.5
H101 = units.HXutility('H101', P102-0, T=370, rigorous=True)
D101 = units.BinaryDistillation('D101', H101-0, P=10132.5, Lr=0.9, Hr=0.9, k=1.2, Rmin=0.001, LHK=('Ethanol', 'Water'))
H102 = units.HXutility('H102', D101-0, T=300, rigorous=True)
F102 = units.Flash('F102', H102-0, outs=('F102_vapor', 'F102_liquid'), P=10132.5, Q=0)
P103=units.Pump('P103', F102-1, recycle, P=101325)
@P103.add_specification # Using run=True adds P103.run() at the end of the specification!
def adjust_ethanol_flow():   
    P103.run() # You need to run P103 first to get the flow rates of recycle
    F_mass_Ethanol = recycle.imass['Ethanol']
    F_mass_Water = recycle.imass['Water']
    riboflavin.imass['Ethanol'] = max(350 - F_mass_Ethanol, 0)
    Water.imass['Water'] = 350 - F_mass_Water
    M101.run_until(P103, inclusive=True)
@P103.add_specification(run=True) 
def adjust_pump_pressure_P103():
    a = H102-0
    P103.P = a.P + 10132.5

Riboflavin_sys = bst.main_flowsheet.create_system('Riboflavin_sys')
Riboflavin_sys.set_tolerance(subsystems=True, mol=1e-5, rmol=1e-5) # Reduce tolerance for more exact convergence
Riboflavin_sys.simulate()
M102.outs[0].show('wt')

This prints out:

Stream: s3 from <Mixer: M102> to <Pump: P102>
phase: 'l', T: 350.55 K, P: 20265 Pa
flow (kg/hr): Riboflavin  462
              Ethanol     350
              Water       350

Note that it should have worked without M101.run_until(P103, inclusive=True), but there were some oscillations in convergence and I'll need to make some enhancements in BioSTEAM.

Thanks,

zasddsgg commented 3 months ago

Thank you for your suggestion. May I ask you the following questions.

a) Whether to add Riboflavin_sys.set_tolerance(subsystems=True, mol=1e-5, rmol=1e-5), will it affect the simulation results and the results of TEA and LCA? Because the reduction of tolerance may lead to process non-convergence

b) For the following code. Doesn't the following code need to add impact unit? If BioSTEAM is improved, I do not need to add code M101.run_until(P103, inclusive=True), is it right?

@P103.add_specification # Using run=True adds P103.run() at the end of the specification!
def adjust_ethanol_flow():   
    P103.run() # You need to run P103 first to get the flow rates of recycle
    F_mass_Ethanol = recycle.imass['Ethanol']
    F_mass_Water = recycle.imass['Water']
    riboflavin.imass['Ethanol'] = max(350 - F_mass_Ethanol, 0)
    Water.imass['Water'] = 350 - F_mass_Water
    M101.run_until(P103, inclusive=True)

c) If the ethanol at the top of the D101 tower is not recycled back to M102 (because recycle stream may cause the system not to converge), but is used as the outlet stream of the system, how can the cost of the riboflavin stream automatically be calculated according to the net consumption of ethanol when calculating TEA (i.e. the ethanol mass in the M101 inlet stream minus the ethanol mass recovered by D101, then times by ethanol price) (I can set up an M101 feed stream with only ethanol, and an stream with only Riboflavin, an stream with only Water), because after defining the stream by code Ethanol = bst.Stream(ID='Ethanol', price=0.0916, total_flow=1000, units='kg/hr', Ethanol=350, T=100+273.15, P=101325), it appears that TEA will be calculated based only on the ethanol mass in the M101 inlet stream multiplied by the ethanol price.

zasddsgg commented 3 months ago

Thanks again for your help. Could I trouble you take some time to look at this problem, especially question c. Thanks for your help again.

zasddsgg commented 3 months ago

Hello, could I trouble you take some time to look at above problems last posted, especially problem c (If the loop is easy to report errors, I plan to not use loop, but I don't know how to automatically calculate the net cost of ethanol). Thanks for your help again.