Source code for biosteam.wastewater.conventional

# -*- coding: utf-8 -*-
# BioSTEAM: The Biorefinery Simulation and Techno-Economic Analysis Modules
# Copyright (C) 2020-2024, Yoel Cortes-Pena <yoelcortes@gmail.com>
#               2023-2024, Yalin Li <mailto.yalin.li@gmail.com>
# 
# This module is under the UIUC open-source license. See 
# github.com/BioSTEAMDevelopmentGroup/biosteam/blob/master/LICENSE.txt
# for license details.
"""
This module contains unit operations for wastewater treatment of a 
cellulosic ethanol biorefinery as in [1]_.

.. contents:: :local:

Data
----
.. autodata:: biosteam.wastewater.conventional.non_digestables
    
Unit operations
---------------
.. autoclass:: biosteam.wastewater.conventional.AnaerobicDigestion 
.. autoclass:: biosteam.wastewater.conventional.AerobicDigestion
.. autoclass:: biosteam.wastewater.conventional.ReverseOsmosis
.. autoclass:: biosteam.wastewater.conventional.WastewaterSystemCost

Utilities
---------
.. autofunction:: biosteam.wastewater.conventional.get_digestable_organic_chemicals

System factories
----------------
.. autofunction:: biosteam.wastewater.conventional.create_conventional_wastewater_treatment_system

References
----------
.. [1] Humbird, D., Davis, R., Tao, L., Kinchin, C., Hsu, D., Aden, A.,
    Dudgeon, D. (2011). Process Design and Economics for Biochemical 
    Conversion of Lignocellulosic Biomass to Ethanol: Dilute-Acid 
    Pretreatment and Enzymatic Hydrolysis of Corn Stover
    (No. NREL/TP-5100-47764, 1013269). https://doi.org/10.2172/1013269

"""
from biosteam.utils import remove_undefined_chemicals, default_chemical_dict
import biosteam as bst
import thermosteam as tmo
from thermosteam import (
    utils,
    settings,
    separations,
    Reaction as Rxn,
    ParallelReaction as PRxn,
)
cost = bst.decorators.cost

__all__ = (
    'AnaerobicDigestion', 
    'AerobicDigestion', 
    'ReverseOsmosis',
    'WastewaterSystemCost',
    'SludgeCentrifuge',
    'get_digestable_organic_chemicals',
    'create_conventional_wastewater_treatment_system',
)


# %% Functional utilities

#: list[str] IDs for non-digestible components in wastewater
non_digestables = ['WWTsludge', 'Cellulose', 'Xylan', 'CellulaseNutrients',
                   'Mannan', 'Lignin', 'Galactan', 'Glucan', 'Acetate',
                   'Biomass', 'Arabinan', 'Tar', 'CO', 'CO2', 'CH4']

[docs] def get_digestable_organic_chemicals(chemicals): """ Return a list of digestible organic chemical IDs. Parameters ---------- chemicals : :class:`~thermosteam.Chemicals` Digestible organic chemicals will be retrieve from this parameter. """ non_digestable_chemicals = set([chemicals[i] for i in non_digestables if i in chemicals]) digestables = [i for i in chemicals if i not in non_digestable_chemicals] return [i for i in digestables if i.locked_state != 'g' and 'C' in i.atoms]
def anaerobic_digestion_reactions( chemicals, MW_sludge, biogas_CH4_fraction=0.51, # mol-CH4 / mol-biogas organics_to_biogas=0.86, # g-biogas / g-reactant organics_to_biomass=0.05, # g-biomass / g-reactant thermo=None, ): # Defaults are based on P49 in Humbird et al., 91% of organic components is destroyed, # of which 86% is converted to biogas and 5% is converted to sludge, # and the biogas is assumed to be 51% CH4 and 49% CO2 on a dry molar basis MW_CH4 = 16.04246 MW_CO2 = 44.0095 x_CH4 = biogas_CH4_fraction x_CO2 = (1. - x_CH4) MW_biogas = x_CH4 * MW_CH4 + x_CO2 * MW_CO2 # g-biogas / mol-biogas conversion = organics_to_biogas + organics_to_biomass # g-reacted / g-reactant f_biogas = organics_to_biogas / (conversion * MW_biogas) # mol-biogas / g-reacted f_sludge = organics_to_biomass / (conversion * MW_sludge) # mol-biomass / g-reacted f_CH4 = x_CH4 * f_biogas # mol-CH4 / g-reacted f_CO2 = x_CO2 * f_biogas # mol-CO2 / g-reacted thermo = settings.get_default_thermo(thermo) parsable_name = thermo.chemicals.get_parsable_synonym isvalid = utils.is_valid_ID def anaerobic_rxn(chemical): reactant = chemical.ID if not isvalid(reactant): reactant = parsable_name(reactant) if reactant == 'H2SO4': return Rxn("H2SO4 -> H2S + 2O2", 'H2SO4', 1.) else: MW_inv = 1. / chemical.MW return Rxn(f'{MW_inv}{reactant} -> {f_CH4}CH4 + {f_CO2}CO2 + {f_sludge}WWTsludge', reactant, 0.91, chemicals=thermo.chemicals) return PRxn([anaerobic_rxn(i) for i in chemicals]) def aerobic_digestion_reactions(chemicals, MW_sludge, X_combustion=0.74, X_growth=0.22, thermo=None): # Based on P49 in Humbird et al. Defaults assume 96% of remaining soluble # organic matter is removed after aerobic digestion, of which 74% is # converted to water and CO2 and 22% to cell mass isvalid = utils.is_valid_ID thermo = settings.get_default_thermo(thermo) parsable_name = thermo.chemicals.get_parsable_synonym def growth(chemical): f = MW_sludge / chemical.MW reactant = chemical.ID if not isvalid(reactant): reactant = parsable_name(reactant) return Rxn(f"{f}{reactant} -> WWTsludge", reactant, X_growth, chemicals=thermo.chemicals) return PRxn([i.get_combustion_reaction(conversion=X_combustion) + growth(i) for i in chemicals]) # %% Unit operations
[docs] @cost('Flow rate', 'Wastewater system', units='kg/hr', CE=551, cost=50280080., n=0.6, BM=1, kW=7139/1.05, S=393100) class WastewaterSystemCost(bst.Unit): """ Create a unit that estimates the capital cost and electricity demand of a wastewater treatment system. Parameters ---------- ins : Wastewater. """
[docs] class AnaerobicDigestion(bst.Unit): """ Create an anaerobic digestion unit operation. The model is based on stoichiometric reactions and a specified fraction of water evaporated. Parameters ---------- reactions : ReactionSet, optional Anaerobic digestion reactions. Default assumes 91% of organic components is destroyed, of which 86% is converted to biogas and 5% is converted to sludge. The biogas is assumed to be 51% CH4 and 49% CO2 on a dry molar basis. sludge_split : Array, optional Split between wastewater and sludge. ins : * [0] Wastewater outs : * [0] Biogas * [1] Wastewater * [2] Sludge """ purchase_cost = installation_cost = 0 _N_ins = 1 _N_outs = 3 def _init(self, reactions=None, sludge_split=None): chemicals = self.chemicals if not reactions: digestables = get_digestable_organic_chemicals(chemicals) reactions = anaerobic_digestion_reactions(digestables, chemicals.WWTsludge.MW, thermo=self.thermo) self.reactions = reactions if sludge_split is None: sludge_split = dict( Water=0.07087, Ethanol=0.0625, Furfural=0.06667, Glycerol=0.07377, LacticAcid=0.07084, SuccinicAcid=0.07377, HNO3=0.0678, Denaturant=0.07377, DAP=0.0678, AmmoniumAcetate=0.07084, AmmoniumSulfate=0.0678, H2SO4=0.0678, NaNO3=0.0678, Oil=0.07377, HMF=0.06667, NH3=0.07048, Glucose=0.06667, Xylose=0.07609, Sucrose=0.06915, Mannose=0.06915, Galactose=0.06915, Arabinose=0.06915, Extract=0.07084, Tar=0.7473, CaO=0.7473, Ash=0.7473, NaOH=0.0678, Lignin=0.744, SolubleLignin=0.07084, GlucoseOligomer=0.07143, GalactoseOligomer=0.07143, MannoseOligomer=0.07143, XyloseOligomer=0.07143, ArabinoseOligomer=0.07143, Z_mobilis=0.7438, T_reesei=0.7438, Cellulose=0.76, Protein=0.7391, Enzyme=0.7391, Xylan=0.75, Xylitol=0.07377, Cellobiose=0.06915, DenaturedEnzyme=0.7391, Arabinan=1, Mannan=1, Galactan=1, WWTsludge=0.7438, Cellulase=0.07084 ) remove_undefined_chemicals(sludge_split, chemicals) default_chemical_dict(sludge_split, chemicals, 0.07087, 0.07087, 0.744) self.sludge_split = chemicals.isplit(sludge_split) self._load_components() def _load_components(self): self.multi_stream = tmo.MultiStream(thermo=self.thermo) def _run(self): feed, = self.ins biogas, waste, sludge = self.outs biogas.phase = 'g' biogas.T = waste.T = sludge.T = 35+273.15 sludge.copy_flow(feed) self.reactions(sludge) self.multi_stream.empty() self.multi_stream.copy_flow(sludge) self.multi_stream['g'].receive_vent(self.multi_stream['l'], energy_balance=False) biogas.mol[:] = self.multi_stream.imol['g'] liquid_mol = self.multi_stream.imol['l'] sludge.mol[:] = liquid_mol * self.sludge_split.data waste.mol[:] = liquid_mol - sludge.mol biogas.receive_vent(waste)
[docs] class AerobicDigestion(bst.Unit): """ Create an aerobic digestion unit operation. Model is based on stoichiometric reactions and a specified fraction of water evaporated. Parameters ---------- ins : * [0] Wastewater * [1] Air * [2] Caustic outs : * [0] Vent * [1] Treated wastewater reactions : ReactionSet, optional Aerobic digestion reactions. Defaults assume 96% of remaining soluble organic matter is removed after aerobic digestion, of which 74% is converted to water and CO2 and 22% to cell mass. evaporation : float, optional Fraction of water evaporated. Defaults to 0.0113. """ _N_ins = 3 _N_outs = 2 purchase_cost = installation_cost = 0 def _init(self, reactions=None, evaporation=0.0113): if not reactions: chemicals = self.chemicals digestables = get_digestable_organic_chemicals(self.chemicals) reactions = aerobic_digestion_reactions(digestables, chemicals.WWTsludge.MW, thermo=self.thermo) self.reactions = reactions self.evaporation = evaporation def _run(self): waste, air, caustic = self._ins vent, water = self.outs vent.phase = 'g' water.copy_like(waste) water.mol[:] += caustic.mol self.reactions.force_reaction(water) O2 = - water.imass['O2'] N2 = 0.78 / 0.22 * O2 air.empty() air.imass['O2', 'N2'] += [1.5 * O2, 1.5 * N2] water.imol['O2'] = 0. water.imass['N2'] = N2 vent.copy_flow(water, ('CO2', 'O2', 'N2')) water_index = self.chemicals.index('7732-18-5') vent.mol[water_index] = water.mol[water_index] * self.evaporation water.mol[:] -= vent.mol
class SludgeCentrifuge(bst.SolidsSeparator): """ Create a centrifuge to separate sludge. The model is based on component splits. Parameters ---------- ins : Inlet fluid to be split. outs : * [0] Liquid * [1] Sludge split : Defaults to Should be one of the following * [float] The fraction of net feed in the 0th outlet stream * [array_like] Componentwise split of feed to 0th outlet stream * [dict] ID-split pairs of feed to 0th outlet stream order=None : Iterable[str], defaults to biosteam.settings.chemicals.IDs Chemical order of split. moisture_content : float, optional Moisture content of sludge. Defaults to 0.79 based on stream 623 in [1]_ (or 20% for insolubles). """ purchase_cost = installation_cost = 0 def _init(self, split=None, order=None, moisture_content=None, strict_moisture_content=None): chemicals = self.chemicals ID_water = chemicals['7732-18-5'].ID # Water must be defined if split is None: split = dict( Furfural=1, Glycerol=0.8889, LacticAcid=0.935, SuccinicAcid=0.8889, HNO3=0.9344, Denaturant=0.8889, DAP=0.9344, AmmoniumAcetate=0.935, AmmoniumSulfate=0.9344, H2SO4=0.935, NaNO3=0.9344, Oil=0.8889, HMF=1, NH3=0.9388, H2S=0.9394, SO2=0.9394, CO2=0.9333, NO2=0.9394, NO=0.9394, CO=0.9394, Glucose=1, Xylose=1, Sucrose=0.9286, Mannose=0.9286, Galactose=0.9286, Arabinose=0.9286, Extract=0.935, Tar=0.05155, CaO=0.05155, Ash=0.05155, NaOH=0.9344, Lignin=0.04943, SolubleLignin=0.935, GlucoseOligomer=0.9, GalactoseOligomer=0.9, MannoseOligomer=0.9, XyloseOligomer=0.9, ArabinoseOligomer=0.9, Z_mobilis=0.04991, T_reesei=0.04991, Cellulose=0.03846, Protein=0.05455, Enzyme=0.05455, Xylitol=0.8889, Cellobiose=0.9286, DenaturedEnzyme=0.05455, WWTsludge=0.04991, Cellulase=0.935 ) remove_undefined_chemicals(split, chemicals) default_chemical_dict(split, chemicals, 0.9394, 0.9286, 0.04991) split.pop(ID_water) # Remove water from split if ID_water not in split and moisture_content is None: # Only set moisture content if water split is not given moisture_content = 0.79 bst.SolidsSeparator._init( self, split=split, order=order, moisture_content=moisture_content, strict_moisture_content=strict_moisture_content ) def _run(self): ins = self.ins retentate, permeate = self.outs # Filtrate, solids separations.mix_and_split(ins, retentate, permeate, self.split) separations.adjust_moisture_content(permeate, retentate, self.moisture_content, None, self.strict_moisture_content) # TODO: Split values seem arbitrary in NREL 2011 model, perhaps work on a better model class MembraneBioreactor(bst.Splitter): """ Create a membrane bioreactor to clarify sludge. The model is based on component splits. Parameters ---------- ins : Inlet fluid to be split. outs : * [0] Liquid * [1] Sludge split : Defaults to Should be one of the following * [float] The fraction of net feed in the 0th outlet stream * [array_like] Componentwise split of feed to 0th outlet stream * [dict] ID-split pairs of feed to 0th outlet stream order=None : Iterable[str], defaults to biosteam.settings.chemicals.IDs Chemical order of split. """ purchase_cost = installation_cost = 0 def _init(self, split=None, order=None): if split is None: chemicals = self.chemicals split = dict( Water=0.1454, Glycerol=0.125, LacticAcid=0.145, SuccinicAcid=0.125, HNO3=0.1454, Denaturant=0.125, DAP=0.1454, AmmoniumAcetate=0.145, AmmoniumSulfate=0.1454, H2SO4=0.1454, NaNO3=0.1454, Oil=0.125, N2=0.1351, NH3=0.1579, O2=0.15, CO2=0.1364, Xylose=0.25, Sucrose=0.125, Mannose=0.125, Galactose=0.125, Arabinose=0.125, Extract=0.145, NaOH=0.1454, SolubleLignin=0.145, GlucoseOligomer=0.1429, GalactoseOligomer=0.1429, MannoseOligomer=0.1429, XyloseOligomer=0.1429, ArabinoseOligomer=0.1429, Xylitol=0.125, Cellobiose=0.125, Cellulase=0.145 ) remove_undefined_chemicals(split, chemicals) default_chemical_dict(split, chemicals, 0.15, 0.125, 0.145) bst.Splitter._init(self, split=split, order=order)
[docs] class ReverseOsmosis(bst.Unit): """ Create a reverse osmosis unit operation for recovering water from brine. The model is based on a fraction of water recovered. Parameters ---------- ins : Inlet fluid to be split. outs : * [0] Filtered water * [1] Brine water_recovery : float, optional Water recovered to 0th stream. Defaults to 0.987 """ _N_ins = 1 _N_outs = 2 def _init(self, water_recovery=0.987): self.water_recovery = water_recovery @property def RO_treated_water(self): return self.outs[0] def _run(self): feed, = self.ins water, brine = self.outs water.copy_thermal_condition(feed) brine.copy_like(feed) water_index = self.chemicals.index('7732-18-5') water_flow = brine.mol[water_index] water_recovered = self.water_recovery * water_flow water.mol[water_index] = water_recovered brine.mol[water_index] = water_flow - water_recovered
def _create_cellulosic_ethanol_chemicals(): from biorefineries.cellulosic import create_cellulosic_ethanol_chemicals return create_cellulosic_ethanol_chemicals() @bst.SystemFactory( ID='wastewater_treatment_sys', outs=[dict(ID='biogas'), dict(ID='sludge'), dict(ID='RO_treated_water'), dict(ID='brine')], fixed_ins_size=False, fthermo=_create_cellulosic_ethanol_chemicals, ) def create_conventional_wastewater_treatment_system(ins, outs, NaOH_price=None, autopopulate=None ): """ Return a system for wastewater treatment as described in Humbird et al. [1]_ The system includes anaerobic and aerobic digestion reactors, a membrane bioreactor, a sludge centrifuge, and a reverse osmosis unit. Parameters ---------- ins : Wastewater streams (without solids). Defaults to all product streams at run time that are not sold and cannot generate energy through combustion (i.e. streams that have no sink, no price, and a LHV less that 1 kJ / g). outs : * [0] biogas * [1] sludge * [2] RO_treated_water * [3] brine NaOH_price : float, optional Price of NaOH in USD/kg. The default is 0.07476. autopopulate : bool, optional Whether to automatically add wastewater streams. Examples -------- >>> from biosteam import Stream, create_conventional_wastewater_treatment_system, settings >>> settings.set_thermo(create_conventional_wastewater_treatment_system.fthermo()) >>> feed = Stream( ... ID='wastewater', ... Water=2.634e+04, ... Ethanol=0.07225, ... AceticAcid=24.67, ... Furfural=6.206, ... Glycerol=1.784, ... LacticAcid=17.7, ... SuccinicAcid=3.472, ... DAP=1.001, ... AmmoniumSulfate=17.63, ... HMF=2.366, ... Glucose=2.816, ... Xylose=6.953, ... Arabinose=12.78, ... Extract=65.98, ... Ash=83.52, ... Lignin=1.659, ... SolubleLignin=4.202, ... GlucoseOligomer=6.796, ... GalactoseOligomer=0.01718, ... MannoseOligomer=0.009008, ... XyloseOligomer=2.878, ... ArabinoseOligomer=0.3508, ... Z_mobilis=0.6668, ... Protein=2.569, ... Glucan=0.1555, ... Xylan=0.06121, ... Xylitol=4.88, ... Cellobiose=0.9419, ... Arabinan=0.02242, ... Mannan=0.06448, ... Galactan=0.01504, ... Cellulase=25.4, ... units='kmol/hr' ... ) >>> wwt_sys = create_conventional_wastewater_treatment_system(ins=feed) >>> wwt_sys.simulate() >>> wwt_sys.show('cwt100') System: wastewater_treatment_sys Highest convergence error among components in recycle stream M604-0 after 16 loops: - flow rate 1.13e+03 kmol/hr (0.84%) - temperature 6.49e-05 K (2.1e-05%) ins... [0] wastewater phase: 'l', T: 298.15 K, P: 101325 Pa composition (%): Water 94.6 Ethanol 0.000664 AceticAcid 0.295 Furfural 0.119 Glycerol 0.0328 LacticAcid 0.318 SuccinicAcid 0.0818 DAP 0.0264 AmmoniumSulfate 0.465 HMF 0.0595 Glucose 0.101 Xylose 0.208 Arabinose 0.383 Extract 2.37 Ash 0.0167 Lignin 0.0503 SolubleLignin 0.128 GlucoseOligomer 0.244 GalactoseOligomer 0.000617 MannoseOligomer 0.000324 XyloseOligomer 0.0862 ArabinoseOligomer 0.0105 Z_mobilis 0.00328 Protein 0.0117 Glucan 0.00503 Xylan 0.00161 Xylitol 0.148 Cellobiose 0.0643 Arabinan 0.000591 Mannan 0.00209 Galactan 0.000486 Cellulase 0.122 ----------------- 5.01e+05 kg/hr outs... [0] biogas phase: 'g', T: 307.76 K, P: 101325 Pa composition (%): Water 3.18 Ethanol 2.46e-05 AceticAcid 0.0019 Furfural 0.00296 Glycerol 1.43e-08 LacticAcid 8.25e-07 SuccinicAcid 2.88e-09 CH4 26.6 CO2 70.2 ------------ 2.13e+04 kg/hr [1] sludge phase: 'l', T: 307.88 K, P: 101325 Pa composition (%): Water 46 Ethanol 5.26e-05 AceticAcid 0.0267 Glycerol 0.0048 LacticAcid 0.0262 SuccinicAcid 0.012 DAP 0.0976 AmmoniumSulfate 1.72 SO2 0.000601 Arabinose 0.0338 Extract 0.195 Ash 2.56 NaOH 1.58 Lignin 7.54 SolubleLignin 0.0105 GlucoseOligomer 0.0312 GalactoseOligomer 7.88e-05 MannoseOligomer 4.13e-05 XyloseOligomer 0.011 ArabinoseOligomer 0.00134 Z_mobilis 0.0406 Protein 0.143 Glucan 0.0229 Xylan 0.0226 Xylitol 0.0217 Cellobiose 0.00568 Arabinan 0.0103 Mannan 0.0363 Galactan 0.00847 WWTsludge 39.8 Cellulase 0.01 ----------------- 2.57e+03 kg/hr [2] RO_treated_water phase: 'l', T: 307.79 K, P: 101325 Pa composition (%): Water 100 ----- 4.16e+05 kg/hr [3] brine phase: 'l', T: 307.79 K, P: 101325 Pa composition (%): Water 48.7 Ethanol 1.35e-05 AceticAcid 0.00609 Furfural 0.00244 Glycerol 0.000675 LacticAcid 0.00763 SuccinicAcid 0.00169 DAP 1.11 AmmoniumSulfate 19.6 HMF 0.00124 SO2 0.01 Glucose 0.00244 Xylose 0.00861 Arabinose 0.00791 Extract 0.0568 Ash 0.267 NaOH 23.5 Lignin 0.519 SolubleLignin 0.00306 GlucoseOligomer 0.00576 GalactoseOligomer 1.46e-05 MannoseOligomer 7.63e-06 XyloseOligomer 0.00203 ArabinoseOligomer 0.000248 Z_mobilis 1.99e-05 Protein 7.32e-05 Glucan 0.206 Xylan 0.0657 Xylitol 0.00305 Cellobiose 0.00133 Arabinan 0.0237 Mannan 0.0837 Galactan 0.0195 WWTsludge 5.9 Cellulase 0.00292 ----------------- 1.12e+04 kg/hr """ biogas, sludge, RO_treated_water, brine = outs RO_treated_water.register_alias('recycled_water') if NaOH_price is None: NaOH_price = 0.07476 air = bst.Stream('air_lagoon', O2=51061, N2=168162, phase='g', units='kg/hr') caustic = bst.Stream('caustic', Water=2252, NaOH=2252, units='kg/hr', price=NaOH_price) wastewater_mixer = bst.Mixer('M601', ins or []) wastewater_mixer.autopopulate = False if autopopulate is None else autopopulate @wastewater_mixer.add_specification(run=True) def autopopulate_waste_streams(): if wastewater_mixer.autopopulate and not wastewater_mixer.ins: sys = wastewater_mixer.system streams = bst.FreeProductStreams(sys.streams) wastewater_mixer.ins.extend(streams.noncombustible_slurries) for i in sys.facilities: if isinstance(i, bst.BlowdownMixer): wastewater_mixer.ins.append(i.outs[0]) WWTC = WastewaterSystemCost('WWTC', wastewater_mixer-0) anaerobic_digestion = AnaerobicDigestion('R601', WWTC-0, (biogas, '', '')) recycled_sludge_mixer = bst.Mixer('M602', ('', anaerobic_digestion-1)) caustic_over_waste = caustic.imol['Water', 'NaOH'] / 2544301 air_over_waste = air.imol['O2', 'N2'] / 2544301 air.mol[:] = 0. waste = recycled_sludge_mixer-0 aerobic_digestion = AerobicDigestion('R602', (waste, air, caustic), outs=('evaporated_water', '')) @aerobic_digestion.add_specification def update_aerobic_input_streams(): waste, air, caustic = aerobic_digestion.ins F_mass_waste = waste.F_mass caustic.imol['Water', 'NaOH'] = F_mass_waste * caustic_over_waste air.imol['O2', 'N2'] = F_mass_waste * air_over_waste aerobic_digestion._run() membrane_bioreactor = MembraneBioreactor('S601', aerobic_digestion-1) sludge_splitter = bst.Splitter('S602', membrane_bioreactor-1, split=0.96) fresh_sludge_mixer = bst.Mixer('M603', (anaerobic_digestion-2, sludge_splitter-1)) sludge_centrifuge = SludgeCentrifuge('S603', fresh_sludge_mixer-0, outs=('', sludge)) bst.Mixer('M604', [sludge_splitter-0, sludge_centrifuge-0], 0-recycled_sludge_mixer) ReverseOsmosis('S604', membrane_bioreactor-0, outs=(RO_treated_water, brine)) create_wastewater_treatment_system = create_conventional_wastewater_treatment_system