def create_streams(defaults, user_streams, kind, fixed_size): if user_streams is None: return [Stream(**kwargs) for kwargs in defaults] isa = isinstance if isa(user_streams, Stream): user_streams = [user_streams] N_defaults = len(defaults) N_streams = len(user_streams) if fixed_size and N_streams > N_defaults: raise ValueError(f'too many {kind} ({N_streams} given); ' f'number of {kind} must be {N_defaults} or less') streams = [] index = 0 for kwargs, stream in zip(defaults, user_streams): if not isa(stream, Stream): if isa(stream, str): kwargs = kwargs.copy() kwargs['ID'] = stream stream = Stream(**kwargs) elif stream: raise TypeError( f"{kind} must be streams, strings, or None; " f"invalid type '{type(stream).__name__}' at index {index}" ) else: stream = Stream(**kwargs) streams.append(stream) index += 1 if N_streams < N_defaults: streams += [Stream(**kwargs) for kwargs in defaults[index:]] elif N_streams > N_defaults: streams += [as_stream(i) for i in user_streams[N_defaults:]] return streams
def __init__(self, size, streams, thermo, fixed_size): self._size = size self._fixed_size = fixed_size dock = self._dock redock = self._redock if streams == (): self._streams = [dock(Stream(thermo=thermo)) for i in range(size)] else: if fixed_size: self._initialize_missing_streams() if streams: if isa(streams, str): self._streams[0] = dock(Stream(streams, thermo=thermo)) elif isa(streams, (Stream, MultiStream)): self._streams[0] = redock(streams) else: N = len(streams) n_missing(size, N) # Assert size is not too big self._streams[:N] = [ redock(i) if isa(i, Stream) else dock( Stream(i, thermo=thermo)) for i in streams ] else: if streams: if isa(streams, str): self._streams = [dock(Stream(streams, thermo=thermo))] elif isa(streams, (Stream, MultiStream)): self._streams = [redock(streams)] else: self._streams = [ redock(i) if isa(i, Stream) else dock( Stream(i, thermo=thermo)) for i in streams ] else: self._initialize_missing_streams()
def __init__(self, ID='', ins=None, outs=(), thermo=None, *, P, V): Unit.__init__(self, ID, ins, outs, thermo) # Unpack out_wt_solids, liq = self.outs self.V = V #: [float] Overall molar fraction of component evaporated. self._V1 = V / 2. # Create components self._N_evap = n = len(P) # Number of evaporators first_evaporator = Evaporator_PV(None, outs=(None, None), P=P[0]) # Put liquid first, then vapor side stream evaporators = [first_evaporator] for i in range(1, n): evap = Evaporator_PQ(None, outs=(None, None, None), P=P[i], Q=0) evaporators.append(evap) condenser = HXutility(None, outs=Stream(None), V=0) self.heat_utilities = (first_evaporator.heat_utilities[0], condenser.heat_utilities[0]) mixer = Mixer(None, outs=Stream(None)) self.components = { 'evaporators': evaporators, 'condenser': condenser, 'mixer': mixer }
def set_lipid_fraction(lipid_fraction, stream=None, data={}): """Adjust composition of lipid cane to achieve desired oil fraction (dry weight).""" if not stream: stream = lc.lipidcane if not data: carbs_IDs = ('Glucose', 'Sucrose') fiber_IDs = ('Lignin', 'Cellulose', 'Hemicellulose') lipid_IDs = ('Lipid', ) carbs = Stream('Carbs', thermo=stream.thermo) fiber = Stream('Fiber', thermo=stream.thermo) lipid = Stream('Lipid', thermo=stream.thermo) carbs.imol[carbs_IDs] = stream.imol[carbs_IDs] fiber.imol[fiber_IDs] = stream.imol[fiber_IDs] lipid.imol[lipid_IDs] = stream.imol[lipid_IDs] # Mass property arrays carbs_mass = stream.imass[carbs_IDs] fiber_mass = stream.imass[fiber_IDs] lipid_mass = stream.imass[lipid_IDs] # Net weight carbs_massnet = carbs_mass.sum() fiber_massnet = fiber_mass.sum() lipid_massnet = lipid_mass.sum() # Heats of combustion per kg LHV_carbs_kg = carbs.LHV / carbs_massnet LHV_lipid_kg = lipid.LHV / lipid_massnet data['LHV_lipid_over_carbs'] = LHV_lipid_kg / LHV_carbs_kg # Relative composition data['r_mass_carbs'] = array(carbs_mass / carbs_massnet) data['r_mass_fiber'] = array(fiber_mass / fiber_massnet) z_mass_lipid = lipid_fraction F_mass = stream.F_mass z_dry = 0.3 z_mass_lipid = z_mass_lipid * z_dry z_mass_carbs = 0.149 - z_mass_lipid * data['LHV_lipid_over_carbs'] z_mass_fiber = z_dry - z_mass_carbs - z_mass_lipid - 0.006 - 0.015 imass = stream.imass imass['Water'] = F_mass * 0.7 imass['Ash'] = F_mass * 0.006 imass['Solids'] = F_mass * 0.015 imass['Lipid'] = z_mass_lipid * F_mass imass['Glucose', 'Sucrose'] = data['r_mass_carbs'] * z_mass_carbs * F_mass imass['Lignin', 'Cellulose', 'Hemicellulose'] = data['r_mass_fiber'] * z_mass_fiber * F_mass if any(stream.mol < 0): raise ValueError( f'lipid cane oil composition of {z_mass_lipid*100:.0f}% dry weight is infeasible' )
def minimal_digraph(ID, units, streams, **graph_attrs): ins, outs = get_section_inlets_and_outlets(units, streams) product = Stream(None) product._ID = '' feed = Stream(None) feed._ID = '' feed_box = bst.units.DiagramOnlyStreamUnit('\n'.join([i.ID for i in ins]), None, feed) product_box = bst.units.DiagramOnlyStreamUnit( '\n'.join([i.ID for i in outs]), product, None) system_box = bst.units.DiagramOnlySystemUnit(ID, feed, product) return digraph_from_units((feed_box, system_box, product_box), **graph_attrs)
def extend_surface_units(ID, streams, units, surface_units, old_unit_connections): outs = [] ins = [] feeds = [] products = [] StreamUnit = bst.units.DiagramOnlyStreamUnit SystemUnit = bst.units.DiagramOnlySystemUnit for s in streams: source = s._source sink = s._sink if source in units and sink not in units: if sink: outs.append(s) else: products.append(s) u_io = (source, tuple(source.ins), tuple(source.outs)) old_unit_connections.add(u_io) elif sink in units and source not in units: if source: ins.append(s) else: feeds.append(s) u_io = (sink, tuple(sink.ins), tuple(sink.outs)) old_unit_connections.add(u_io) if len(feeds) > 1: feed = Stream(None) feed._ID = '' feeds = sort_streams(feeds) feed_box = StreamUnit('\n'.join([i.ID for i in feeds]) or '-', None, feed) ins.append(feed) else: feed_box = None ins += feeds if len(products) > 1: product = Stream(None) product._ID = '' products = sort_streams(products) product_box = StreamUnit('\n'.join([i.ID for i in products]) or '-', product, None) outs.append(product) else: product_box = None outs += products subsystem_unit = SystemUnit(ID, ins, outs) for i in (feed_box, subsystem_unit, product_box): if i: surface_units.append(i)
def as_stream(stream): if isa(stream, Stream): return stream elif isa(stream, str): return Stream(stream) elif stream is None: return MissingStream(None, None)
def __init__(self, size, streams, thermo, fixed_size, stacklevel): self._size = size self._fixed_size = fixed_size dock = self._dock redock = self._redock if streams == (): self._streams = [dock(Stream(thermo=thermo)) for i in range(size)] else: isa = isinstance stream_types = (Stream, MissingStream) if fixed_size: self._initialize_missing_streams() if streams: if isa(streams, str): self._streams[0] = dock(Stream(streams, thermo=thermo)) elif isa(streams, stream_types): self._streams[0] = redock(streams, stacklevel) else: N = len(streams) n_missing(size, N) # Make sure size is not too big self._streams[:N] = [redock(i, stacklevel+1) if isa(i, stream_types) else dock(Stream(i, thermo=thermo)) for i in streams] else: if streams: if isa(streams, str): self._streams = [dock(Stream(streams, thermo=thermo))] elif isa(streams, stream_types): self._streams = [redock(streams, stacklevel)] else: self._streams = loaded_streams = [] for i in streams: if isa(i, stream_types): s = redock(i, stacklevel) elif i is None: s = self._create_missing_stream() else: s = Stream(i, thermo=thermo) dock(s) loaded_streams.append(s) else: self._initialize_missing_streams()
def materialize_connection(self, ID=""): """ Disconnect this missing stream from any unit operations and replace it with a material stream. """ source = self._source sink = self._sink assert source and sink, ( "both a source and a sink is required to materialize connection") material_stream = Stream(ID, thermo=source.thermo) source._outs.replace(self, material_stream) sink._ins.replace(self, material_stream)
def materialize_connection(self, ID=""): """ Disconnect this missing stream from any unit operations and replace it with a material stream. """ source = self._source sink = self._sink if not (source or sink): raise RuntimeError("either a source or a sink is required to " "materialize connection") material_stream = Stream(ID, thermo=(source or sink).thermo) if source: source._outs.replace(self, material_stream) if sink: sink._ins.replace(self, material_stream) return material_stream
def _design(self): feed_solids, feed_gases, lime, ammonia, boiler_chems, bag, natural_gas, \ makeup_water = self.ins emission, ash, blowdown_water = self.outs side_streams_to_heat = self.side_streams_to_heat side_streams_lps = self.side_streams_lps system_heating_utilities = self.system_heating_utilities = {} lps = HeatUtility.get_heating_agent('low_pressure_steam') hps = HeatUtility.get_heating_agent('high_pressure_steam') # Use combustion reactions to create outs combustion_rxns = self.chemicals.get_combustion_reactions() combustible_feeds = Stream(None) emission.mol = feed_solids.mol + feed_gases.mol combustible_feeds.copy_flow(emission, tuple(self.combustibles), remove=True) combustion_rxns.force_reaction(combustible_feeds.mol) emission.mol += (combustible_feeds.mol + ammonia.mol) self.emission_rxns.force_reaction(emission.mol) # FGD lime scaled based on SO2 generated, # 20% stoichiometetric excess based on P52 of ref [1] lime.imol['Lime'] = max(0, -emission.imol['Lime'] * 1.2) emission.mol += lime.mol # Air/O2 usage not rigorously modeled emission.imol['O2'] = 0 ash.empty() for chemical in emission.chemicals: if chemical.ID not in ('Water', 'H2O') and chemical.locked_state != 'g': ash.imol[chemical.ID] = emission.imol[chemical.ID] emission.imol[chemical.ID] = 0 emission.imol[ 'Water'] = feed_solids.imol['Water'] + feed_gases.imol['Water'] ash.mol += boiler_chems.mol emission.phase = 'g' ash.phase = 's' # Assume T of emission and ash are the same as hps, which # has highest T amont all heating agents emission.T = ash.T = hps.T # Total heat generated by the boiler (kJ/hr) H_in = feed_solids.H + feed_gases.H H_out = emission.H + ash.H # Water evaporation energy is already accounted for in emission enthalpy heat_from_combustion = -(feed_solids.HHV + feed_gases.HHV) heat_generated = self.heat_generated = \ (H_in+heat_from_combustion)*self.B_eff - H_out for u in self.system.units: if u is self: continue if hasattr(u, 'heat_utilities'): for hu in u.heat_utilities: # Including low/medium/high_pressure_steam if hu.flow * hu.duty > 0: system_heating_utilities[f'{u.ID} - {hu.ID}'] = hu # Use lps to account for the energy needed for the side steam if side_streams_to_heat: if not side_streams_lps: side_streams_lps = self.side_streams_lps = HeatUtility() side_streams_lps.load_agent(lps) side_streams_lps(duty=sum([i.H for i in side_streams_to_heat]), T_in=298.15) system_heating_utilities[ 'CHP - side_streams_lps'] = side_streams_lps system_heating_demand = self.system_heating_demand = \ sum([i.duty for i in system_heating_utilities.values()]) CHP_heat_surplus = self.CHP_heat_surplus = heat_generated - system_heating_demand self.system_steam_demand = sum( [i.flow for i in system_heating_utilities.values()]) hu_cooling = HeatUtility() # CHP can meet system heating/steam demand if CHP_heat_surplus > 0: # 3600 is conversion of kJ/hr to kW (kJ/s) electricity_generated = self.electricity_generated = \ CHP_heat_surplus * self.TG_eff / 3600 # Take the opposite for cooling duty (i.e., cooling duty should be negative) # this is to condense the unused steam cooling_need = self.cooling_need = -(CHP_heat_surplus - electricity_generated) hu_cooling(duty=cooling_need, T_in=lps.T) natural_gas.empty() # CHP cannot meet system heating/steam demand, supplement with natural gas else: CH4_LHV = natural_gas.chemicals.CH4.LHV natural_gas.imol['CH4'] = CHP_heat_surplus / (CH4_LHV * self.B_eff) emission.imol['CO2'] += natural_gas.imol['CH4'] emission.imol['H2O'] += 2 * natural_gas.imol['CH4'] electricity_generated = self.electricity_generated = 0 heating_utilities = HeatUtility.sum_by_agent( system_heating_utilities.values()) for i in heating_utilities: i.reverse() if hu_cooling.duty != 0: self.heat_utilities = tuple([hu_cooling, *heating_utilities]) else: self.heat_utilities = tuple(heating_utilities) total_steam_mol = sum( [i.flow for i in system_heating_utilities.values()]) total_steam = total_steam_mol * self.chemicals.H2O.MW blowdown_water.imass['H2O'] = total_steam * self.blowdown blowdown_water.T = 373.15 # Additional need for making lime slurry makeup_water.imol[ 'H2O'] = blowdown_water.imol['H2O'] + lime.F_mol / 0.2 * 0.8 # 1.23 is $2007/hour and 2.2661 is 2007$/lb from Table 30 in ref [1], # 144.629 is the total duty from Page 131 in ref [1], # 2.20462 is kg to lb, 4.184 is kcal to kJ, ratio = system_heating_demand / (144.629 * 1e6 * 4.184) boiler_chems.imass['BoilerChems'] = 1.23 / 2.2661 / 2.20462 * ratio bag.imass['BaghouseBag'] = ratio self.design_results['Flow rate'] = total_steam self.design_results['Work'] = electricity_generated
bst.CE = 541.7 # year 2016 System.maxiter = 400 System.converge_method = 'fixed-point' System.molar_tolerance = 0.01 tmo.settings.set_thermo(chems) # %% # ============================================================================= # Feedstock preprocessing # ============================================================================= feedstock = Stream('feedstock', baseline_feedflow.copy(), units='kg/hr', price=price['Feedstock']) U101 = units.FeedstockPreprocessing('U101', ins=feedstock) # Handling costs/utilities included in feedstock cost thus not considered here U101.cost_items['System'].cost = 0 U101.cost_items['System'].kW = 0 # %% # ============================================================================= # Pretreatment streams # ============================================================================= # For pretreatment, 93% purity sulfuric_acid_T201 = Stream('sulfuric_acid_T201', units='kg/hr')
Xylan=0.2330, Arabinan=0.0420, Lignin=0.2260, Extract=0.1330, Ash=0.0180, Acetate=0.0130)) TS = 0.95 moisture_content = pretreatment_chemicals.kwarray(dict(Water=1 - TS)) dryflow = 83333.0 netflow = dryflow / TS feedflow = netflow * (drycomposition * TS + moisture_content) process_water_over_dryflow = 19.96 sulfuric_acid_over_dryflow = 0.04 wheatstraw = Stream('wheatstraw', feedflow, units='kg/hr', price=price['Feedstock'] * TS) process_water1 = Stream('process_water1', T=25 + 273.15, P=1 * 101325, Water=process_water_over_dryflow * dryflow, units='kg/hr') sulfuric_acid = Stream('sulfuric_acid', P=1 * 101325, T=25 + 273.15, Water=0.05 * sulfuric_acid_over_dryflow * dryflow, SulfuricAcid=0.95 * sulfuric_acid_over_dryflow * dryflow, units='kg/hr', price=price['Sulfuric acid'] * 0.95)
def create_system(ID='cornstover_sys'): System.maxiter = 400 System.converge_method = 'Aitken' System.molar_tolerance = 0.01 ### Streams ### chemicals = bst.settings.get_chemicals() # feed flow dry_composition = chemicals.kwarray( dict(Glucan=0.3505, Xylan=0.1953, Lignin=0.1576, Ash=0.0493, Acetate=0.0181, Protein=0.0310, Extract=0.1465, Arabinan=0.0238, Galactan=0.0143, Mannan=0.0060, Sucrose=0.0077)) moisture_content = chemicals.kwarray(dict(Water=0.20)) netflow = 104167.0 feedflow = netflow * (dry_composition * 0.8 + moisture_content) cornstover = Stream('cornstover', feedflow, units='kg/hr', price=price['Feedstock']) warm_process_water = Stream('warm_process_water', T=368.15, P=4.7 * 101325, Water=140000, units='kg/hr') ammonia_process_water = Stream('ammonia_process_water', T=368.15, P=4.7 * 101325, Water=150310, units='kg/hr') rectifier_bottoms_product = Stream('', T=100 + 273.15, P=101325, Ethanol=18, Water=36629, Furfural=72, HMF=100, units='kg/hr') sulfuric_acid = Stream('sulfuric_acid', P=5.4 * 101325, T=294.15, Water=139, SulfuricAcid=1842, units='kg/hr', price=price['Sulfuric acid']) steam = Stream('steam', phase='g', T=268 + 273.15, P=13 * 101325, Water=24534 + 3490, units='kg/hr') ammonia = Stream('ammonia', Ammonia=1051, units='kg/hr', phase='l', price=price['Ammonia']) cellulase = Stream('cellulase', units='kg/hr', price=price['Enzyme']) ### Pretreatment system U101 = units.FeedStockHandling('U101', cornstover) U101.cost_items['System'].cost = 0 T201 = units.SulfuricAcidTank('T201', sulfuric_acid) M201 = units.SulfuricAcidMixer('M201', (rectifier_bottoms_product, T201 - 0)) M202 = bst.Mixer('M202', (M201 - 0, warm_process_water, U101 - 0)) M203 = units.SteamMixer('M203', (M202 - 0, steam), P=5.5 * 101325) R201 = units.PretreatmentReactorSystem('R201', M203 - 0) P201 = units.BlowdownDischargePump('P201', R201 - 1) T202 = units.OligomerConversionTank('T202', P201 - 0) F201 = units.PretreatmentFlash('F201', T202 - 0, P=101325, Q=0) M204 = bst.Mixer('M204', (R201 - 0, F201 - 0)) H201 = units.WasteVaporCondenser('H201', M204 - 0, T=99 + 273.15, V=0) M210 = units.AmmoniaMixer('M210', (ammonia, ammonia_process_water)) M205 = bst.Mixer('M205', (F201 - 1, M210 - 0)) T203 = units.AmmoniaAdditionTank('T203', M205 - 0) P209 = units.HydrolyzatePump('P209', T203 - 0) H301 = units.HydrolysateCooler('H301', P209 - 0, T=48 + 273.15) M301 = units.EnzymeHydrolysateMixer('M301', (H301 - 0, cellulase)) def update_pretreatment_process_water(): warm_process_water.imass[ 'Water'] = 140000.0 * M202.ins[2].F_mass / 104175.33336 sulfuric_acid_over_feed = sulfuric_acid.mol / cornstover.F_mass def update_sulfuric_acid_loading(): # Also plant air F_mass_feed = cornstover.F_mass plant_air.mol[0] = 0.8 * F_mass_feed sulfuric_acid.mol[:] = sulfuric_acid_over_feed * F_mass_feed hydrolyzate = F201.outs[1] ammonia_over_hydrolyzate = ammonia.mol / 310026.22446428984 def update_ammonia_loading(): ammonia.mol[:] = ammonia_over_hydrolyzate * hydrolyzate.F_mass ammonia_process_water.imass[ 'Water'] = ammonia.imass['Ammonia'] * 150310 / 1051 cooled_hydrolyzate = H301.outs[0] enzyme_over_cellulose = 20 / 1000 * 20 # (20 g enzyme / cellulose) / (50 g cellulase / 1 L enzyme) water_cellulase_mass = cellulase.imass['Water', 'Cellulase'] water_cellulase_to_cellulase = np.array([0.95, 0.05]) def update_cellulase_and_nutrient_loading(): F_mass_cooled_hydrolyzate = cooled_hydrolyzate.F_mass cellulose = cornstover.imass['Glucan'] # Note: An additional 10% is produced for the media glucose/sophorose mixture # Humbird (2011) pg. 37 water_cellulase_mass[:] = (enzyme_over_cellulose * water_cellulase_to_cellulase * cellulose * 1.1) DAP1.mol[:] = DAP1_over_hydrolyzate * F_mass_cooled_hydrolyzate DAP2.mol[:] = DAP2_over_hydrolyzate * F_mass_cooled_hydrolyzate CSL1.mol[:] = CSL1_over_hydrolyzate * F_mass_cooled_hydrolyzate CSL2.mol[:] = CSL2_over_hydrolyzate * F_mass_cooled_hydrolyzate pretreatment_sys = System('pretreatment_sys', path=(U101, T201, M201, update_pretreatment_process_water, M202, M203, R201, P201, T202, F201, M204, H201, M210, M205, T203, P209, T203, H301, M301)) ### Fermentation system ### DAP1 = Stream('DAP1', DAP=26, units='kg/hr', price=price['DAP']) DAP2 = Stream('DAP2', DAP=116, units='kg/hr', price=price['DAP']) DAP_storage = units.DAPTank('DAP_storage', Stream('DAP_fresh'), outs='DAP') S301 = bst.ReversedSplitter('S301', DAP_storage - 0, outs=(DAP1, DAP2)) CSL1 = Stream('CSL1', CSL=211, units='kg/hr', price=price['CSL']) CSL2 = Stream('CSL2', CSL=948, units='kg/hr', price=price['CSL']) CSL_storage = units.CSLTank('CSL_storage', Stream('CSL_fresh'), outs='CSL') S302 = bst.ReversedSplitter('S302', CSL_storage - 0, outs=(CSL1, CSL2)) denaturant = Stream('denaturant', Octane=230.69, units='kg/hr', price=price['Denaturant']) stripping_water = Stream('stripping_water', Water=26836, units='kg/hr') DAP1_over_hydrolyzate = DAP1.mol / 451077.22446428984 DAP2_over_hydrolyzate = DAP2.mol / 451077.22446428984 CSL1_over_hydrolyzate = CSL1.mol / 451077.22446428984 CSL2_over_hydrolyzate = CSL2.mol / 451077.22446428984 M302 = bst.Mixer('M302', (M301 - 0, None)) R301 = units.SaccharificationAndCoFermentation('R301', (M302 - 0, CSL2, DAP2)) M303 = bst.Mixer('M303', (R301 - 2, CSL1, DAP1)) R302 = units.SeedTrain('R302', M303 - 0) T301 = units.SeedHoldTank('T301', R302 - 1) T301 - 0 - 1 - M302 fermentation_sys = System('fermentation_sys', path=(update_cellulase_and_nutrient_loading, M302, R301, M303, R302, T301), recycle=M302 - 0) ### Ethanol purification ### M304 = bst.Mixer('M304', (R302 - 0, R301 - 0)) T302 = units.BeerTank('T302') M401 = bst.Mixer('M401', (R301 - 1, None)) M401 - 0 - T302 D401 = bst.VentScrubber('D401', (stripping_water, M304 - 0), gas=('CO2', 'NH3', 'O2')) D401 - 1 - 1 - M401 # Heat up before beer column # Exchange heat with stillage H401 = bst.HXprocess('H401', (T302 - 0, None), phase0='l', phase1='l', U=1.28) Ethanol_MW = chemicals.Ethanol.MW Water_MW = chemicals.Water.MW def Ethanol_molfrac(e): """Return ethanol mol fraction in a ethanol water mixture""" return e / Ethanol_MW / (e / Ethanol_MW + (1 - e) / Water_MW) # Beer column xbot = Ethanol_molfrac(0.00001) ytop = Ethanol_molfrac(0.50) D402 = bst.BinaryDistillation('D402', H401 - 0, k=1.25, Rmin=0.3, P=101325, y_top=ytop, x_bot=xbot, LHK=('Ethanol', 'Water')) D402.tray_material = 'Stainless steel 304' D402.vessel_material = 'Stainless steel 304' D402.boiler.U = 1.85 P401 = bst.Pump('P401', D402 - 1) P401 - 0 - 1 - H401 # Mix ethanol Recycle (Set-up) M402 = bst.Mixer('M402', (D402 - 0, None)) ytop = Ethanol_molfrac(0.915) D403 = bst.BinaryDistillation('D403', M402 - 0, Rmin=0.3, P=101325, y_top=ytop, x_bot=xbot, k=1.25, LHK=('Ethanol', 'Water')) D403.tray_material = 'Stainless steel 304' D403.vessel_material = 'Stainless steel 304' D403.is_divided = True D403.boiler.U = 1.85 P402 = bst.Pump('P402', D403 - 1) P402 - 0 - 0 - M201 # Superheat vapor for mol sieve H402 = bst.HXutility('H402', D403 - 0, T=115 + 273.15, V=1) # Molecular sieve U401 = bst.MolecularSieve('U401', H402 - 0, split=(2165.14 / 13356.04, 1280.06 / 1383.85), order=('Ethanol', 'Water')) U401 - 0 - 1 - M402 ethanol_recycle_sys = System('ethanol_recycle_sys', path=(M402, D403, H402, U401), recycle=M402 - 0) # Condense ethanol product H403 = bst.HXutility('H403', U401 - 1, V=0, T=350.) T701 = bst.StorageTank('T701', H403 - 0, tau=7 * 24, vessel_type='Floating roof', vessel_material='Carbon steel') P701 = bst.Pump('P701', T701 - 0) # Storage for gasoline T702 = bst.StorageTank('T702', denaturant, tau=7 * 24, vessel_type='Floating roof', vessel_material='Carbon steel') P702 = bst.Pump('P702', T702 - 0) # Mix in denaturant ethanol = Stream('ethanol', price=price['Ethanol']) M701 = bst.MixTank('M701', (P702 - 0, P701 - 0), outs=ethanol) M701.line = 'Mixer' M701.tau = 0.05 def adjust_denaturant(): denaturant.imol['Octane'] = 0.022 * P701.outs[0].F_mass / 114.232 P401.BM = P402.BM = P701.BM = P702.BM = 3.1 T701.BM = T702.BM = 1.7 vent_stream = M304 - 0 stripping_water_over_vent = stripping_water.mol / 21202.490455845436 def update_stripping_water(): stripping_water.mol[:] = stripping_water_over_vent * vent_stream.F_mass puresys = System('purification', path=(M304, update_stripping_water, D401, M401, T302, H401, D402, H401, P401, H401, ethanol_recycle_sys, P402, H403, T701, P701, adjust_denaturant, T702, P702, M701)) ### Lignin Separation recycled_water = tmo.Stream(Water=1, T=47 + 273.15, P=3.9 * 101325, units='kg/hr') splits = [('Glucose', 19, 502), ('Xylose', 40, 1022), ('OtherSugars', 81, 2175), ('SugarOligomers', 60, 1552), ('OrganicSolubleSolids', 612, 15808), ('InorganicSolubleSolids', 97, 2513), ('Furfurals', 19, 513), ('OtherOrganics', 52, 1348), ('Glucan', 1230, 25), ('Xylan', 415, 8), ('OtherStructuralCarbohydrates', 94, 2), ('Lignin', 12226, 250), ('Protein', 3376, 69), ('CellMass', 925, 19), ('OtherInsolubleSolids', 4489, 92)] S401 = units.PressureFilter('S401', ('', recycled_water), moisture_content=0.35, split=find_split(*zip(*splits))) H401 - 1 - 0 - S401 ### Waste water treatment ### combustion = chemicals.get_combustion_reactions() def growth(reactant): f = chemicals.WWTsludge.MW / getattr(chemicals, reactant).MW return rxn.Reaction(f"{f}{reactant} -> WWTsludge", reactant, 1.) organic_groups = [ 'OtherSugars', 'SugarOligomers', 'OrganicSolubleSolids', 'Furfurals', 'OtherOrganics', 'Protein', 'CellMass' ] organics = list( sum([chemical_groups[i] for i in organic_groups], ('Ethanol', 'AceticAcid', 'Xylose', 'Glucose'))) organics.remove('WWTsludge') P_sludge = 0.05 / 0.91 / chemicals.WWTsludge.MW MW = np.array([chemicals.CH4.MW, chemicals.CO2.MW]) mass = np.array([0.51, 0.49]) * MW mass /= mass.sum() mass *= 0.86 / (0.91) P_ch4, P_co2 = mass / MW def anaerobic_rxn(reactant): MW = getattr(chemicals, reactant).MW return rxn.Reaction( f"{1/MW}{reactant} -> {P_ch4}CH4 + {P_co2}CO2 + {P_sludge}WWTsludge", reactant, 0.91) # TODO: Revise this with Jeremy anaerobic_digestion = rxn.ParallelReaction( [anaerobic_rxn(i) for i in organics] + [rxn.Reaction("H2SO4 -> H2S + 2O2", 'H2SO4', 1.)]) # Note, nitogenous species included here, but most of it removed in R601 digester # TODO: Add ammonium to reaction, make sure it can be a liquid, possibly add Henry's constant aerobic_digestion = rxn.ParallelReaction([ i * 0.74 + 0.22 * growth(i.reactant) for i in combustion if (i.reactant in organics) ]) aerobic_digestion.X[:] = 0.96 splits = [('Ethanol', 1, 15), ('Water', 27158, 356069), ('Glucose', 3, 42), ('Xylose', 7, 85), ('OtherSugars', 13, 175), ('SugarOligomers', 10, 130), ('OrganicSolubleSolids', 182, 2387), ('InorganicSolubleSolids', 8, 110), ('Ammonia', 48, 633), ('AceticAcid', 0, 5), ('Furfurals', 5, 70), ('OtherOrganics', 9, 113), ('Cellulose', 19, 6), ('Xylan', 6, 2), ('OtherStructuralCarbohydrates', 1, 0), ('Lignin', 186, 64), ('Protein', 51, 18), ('CellMass', 813, 280), ('OtherInsolubleSolids', 68, 23)] well_water = Stream('well_water', Water=1, T=15 + 273.15) M601 = bst.Mixer('M601', (S401 - 1, '', '')) H201 - 0 - 1 - M601 WWTC = units.WasteWaterSystemCost('WWTC', M601 - 0) R601 = units.AnaerobicDigestion('R601', (WWTC - 0, well_water), reactions=anaerobic_digestion, sludge_split=find_split(*zip(*splits))) air = Stream('air_lagoon', O2=51061, N2=168162, phase='g', units='kg/hr') caustic = Stream('WWT_caustic', Water=2252, NaOH=2252, units='kg/hr', price=price['Caustic'] * 0.5) # polymer = Stream('WWT polymer') # Empty in humbird report :-/ M602 = bst.Mixer('M602', (R601 - 1, None)) caustic_over_waste = caustic.mol / 2544300.6261793654 air_over_waste = air.mol / 2544300.6261793654 waste = M602 - 0 def update_aerobic_input_streams(): F_mass_waste = waste.F_mass caustic.mol[:] = F_mass_waste * caustic_over_waste air.mol[:] = F_mass_waste * air_over_waste R602 = units.AerobicDigestion('R602', (waste, air, caustic), outs=('evaporated_water', ''), reactions=aerobic_digestion) splits = [('Ethanol', 0, 1), ('Water', 381300, 2241169), ('Glucose', 0, 2), ('Xylose', 1, 3), ('OtherSugars', 1, 7), ('SugarOligomers', 1, 6), ('OrganicSolubleSolids', 79, 466), ('InorganicSolubleSolids', 4828, 28378), ('Ammonia', 3, 16), ('Furfurals', 0, 3), ('OtherOrganics', 1, 7), ('CarbonDioxide', 6, 38), ('O2', 3, 17), ('N2', 5, 32), ('Cellulose', 0, 194), ('Xylan', 0, 65), ('OtherStructuralCarbohydrates', 0, 15), ('Lignin', 0, 1925), ('Protein', 0, 90), ('CellMass', 0, 19778), ('OtherInsolubleSolids', 0, 707)] S601 = bst.Splitter('S601', R602 - 1, split=find_split(*zip(*splits))) S602 = bst.Splitter('S602', S601 - 1, split=0.96) M603 = bst.Mixer('M603', (S602 - 0, None)) M603 - 0 - 1 - M602 M604 = bst.Mixer('M604', (R601 - 2, S602 - 1)) centrifuge_species = ('Water', 'Glucose', 'Xylose', 'OtherSugars', 'SugarOligomers', 'OrganicSolubleSolids', 'InorganicSolubleSolids', 'Ammonia', 'Furfurals', 'OtherOrganics', 'CO2', 'COxSOxNOxH2S', 'Cellulose', 'Xylan', 'OtherStructuralCarbohydrates', 'Lignin', 'Protein', 'CellMass', 'OtherInsolubleSolids') S623_flow = np.array( [7708, 0, 0, 1, 1, 13, 75, 3, 0, 1, 1, 2, 25, 8, 2, 250, 52, 1523, 92]) S616_flow = np.array([ 109098, 3, 6, 13, 9, 187, 1068, 46, 5, 8, 14, 31, 1, 0, 0, 13, 3, 80, 5 ]) S603 = bst.Splitter('S603', M604 - 0, outs=('', 'sludge'), split=find_split(centrifuge_species, S616_flow, S623_flow)) S603 - 0 - 1 - M603 S604 = bst.Splitter('S604', S601 - 0, outs=('treated_water', 'waste_brine'), split={'Water': 0.987}) aerobic_digestion_sys = System('aerobic_digestion_sys', path=(M602, update_aerobic_input_streams, R602, S601, S602, M604, S603, M603), recycle=M602 - 0) ### Facilities M501 = bst.Mixer('M501', (S603 - 1, S401 - 0)) BT = bst.facilities.BoilerTurbogenerator( 'BT', ins=(M501 - 0, R601 - 0, 'boiler_makeup_water', 'natural_gas', 'lime', 'boilerchems'), turbogenerator_efficiency=0.85) CWP = bst.facilities.ChilledWaterPackage('CWP') CT = bst.facilities.CoolingTower('CT') CT.outs[1].T = 273.15 + 28 process_water_streams = (caustic, stripping_water, warm_process_water, steam, BT - 1, CT - 1) makeup_water = Stream('makeup_water', price=price['Makeup water']) PWC = bst.facilities.ProcessWaterCenter('PWC', (S604 - 0, makeup_water), (), None, (BT - 1, CT - 1), process_water_streams) blowdown_mixer = bst.BlowdownMixer('blowdown_mixer', (BT - 1, CT - 1), 2**M601) CIP = Stream('CIP', Water=126, units='kg/hr') CIP_package = units.CIPpackage('CIP_package', CIP) plant_air = Stream('plant_air', N2=83333, units='kg/hr') ADP = bst.facilities.AirDistributionPackage('ADP', plant_air) fire_water = Stream('fire_water', Water=8343, units='kg/hr') FT = units.FireWaterTank('FT', fire_water) ### Complete system cornstover_sys = System(ID, path=(pretreatment_sys, fermentation_sys, puresys, S401, M601, WWTC, R601, aerobic_digestion_sys, S604), facilities=(M501, CWP, BT, CT, PWC, ADP, CIP_package, S301, S302, DAP_storage, CSL_storage, FT, blowdown_mixer), facility_recycle=blowdown_mixer - 0) return cornstover_sys
Xylan=0.1953, Lignin=0.1576, Ash=0.0493, Acetate=0.0181, Protein=0.0310, Extract=0.1465, Arabinan=0.0238, Galactan=0.0143, Mannan=0.0060, Sucrose=0.0077)) moisture_content = pretreatment_chemicals.kwarray(dict(Water=0.20)) netflow = 104167.0 feedflow = netflow * (dry_composition * 0.8 + moisture_content) cornstover = Stream('cornstover', feedflow, units='kg/hr', price=price['Feedstock']) warm_process_water = Stream('warm_process_water', T=368.15, P=4.7 * 101325, Water=140000, units='kg/hr') rectifier_bottoms_product = Stream('', T=114 + 273.15, P=6.1 * 101325, Ethanol=18, Water=36629, Furfural=72, HMF=100, units='kg/hr') sulfuric_acid = Stream('sulfuric_acid',
def create_system(ID='wheatstraw_sys'): System.maxiter = 400 System.converge_method = 'Aitken' System.molar_tolerance = 0.01 ### Streams ### chemicals = bst.settings.get_chemicals() non_soluble = [ 'Xylan', 'Glucan', 'Arabinan', 'Lignin', 'Extract', 'Ash', 'Mannan', 'Galactan', 'Acetate' ] # feed flow drycomposition = chemicals.kwarray( dict(Glucan=0.3342, Xylan=0.2330, Arabinan=0.0420, Lignin=0.2260, Extract=0.1330, Ash=0.0180, Acetate=0.0130)) TS = 0.95 moisture_content = chemicals.kwarray(dict(Water=1 - TS)) dryflow = 83333.0 netflow = dryflow / TS feedflow = netflow * (drycomposition * TS + moisture_content) process_water_over_dryflow = 19.96 sulfuric_acid_over_dryflow = 0.04 wheatstraw = Stream('wheatstraw', feedflow, units='kg/hr', price=price['Feedstock'] * TS) # %% Pretreatment system process_water1 = Stream( 'process_water1', T=25 + 273.15, P=1 * 101325, Water=process_water_over_dryflow * dryflow, #only an initialization units='kg/hr') sulfuric_acid = Stream('sulfuric_acid', P=1 * 101325, T=25 + 273.15, Water=0.05 * sulfuric_acid_over_dryflow * dryflow, SulfuricAcid=0.95 * sulfuric_acid_over_dryflow * dryflow, units='kg/hr', price=price['Sulfuric acid'] * 0.95) steam = Stream( 'steam', phase='g', T=212 + 273.15, P=20 * 101325, Water=dryflow * 0.5, #This is just a guess units='kg/hr') U101 = units.FeedStockHandling('U101', ins=wheatstraw) U101.cost_items['System'].cost = 0 T201 = units.SulfuricAcidTank('T201', ins=sulfuric_acid) M201 = bst.Mixer('M201', ins=(process_water1, T201 - 0, Stream())) M202 = units.WashingTank('M202', ins=(M201 - 0, U101 - 0)) S200 = units.SieveFilter('S200', ins=(M202 - 0), outs=(Stream('feed_20TS'), Stream('recycled_water1')), moisture_content=1 - 0.20, split=find_split_solids(M202 - 0, non_soluble)) S201 = units.PressureFilter('S201', ins=(S200 - 0), outs=(Stream('feed_50TS'), Stream('recycled_water2')), moisture_content=0.5, split=find_split_solids(S200 - 0, non_soluble)) M200 = bst.Mixer('M200', ins=(S200 - 1, S201 - 1), outs='recycled_water') M200 - 0 - 2 - M201 recycled_water = M200 - 0 def update_process_water1(): process_water1.imass[ 'Water'] = process_water_over_dryflow * dryflow - recycled_water.imass[ 'Water'] sulfuric_acid.imass[ 'SulfuricAcid'] = 0.95 * sulfuric_acid_over_dryflow * dryflow - recycled_water.imass[ 'SulfuricAcid'] sulfuric_acid.imass[ 'Water'] = 0.05 / 0.95 * sulfuric_acid.imass['SulfuricAcid'] water_recycle_sys = System('water_recycle_sys', path=(U101, T201, M201, M202, S200, S201, M200, update_process_water1), recycle=M201 - 0) M205 = bst.Mixer('M205', ins=(S201 - 0, None)) M203 = units.SteamMixer('M203', ins=(M205 - 0, steam), P=steam.chemicals.Water.Psat(190.0 + 273.15)) R201 = units.PretreatmentReactorSystem( 'R201', ins=M203 - 0, outs=(Stream('pretreatment_steam'), Stream('pretreatment_effluent'))) P201 = units.BlowdownDischargePump('P201', ins=R201 - 1) T202 = units.OligomerConversionTank('T202', ins=P201 - 0) F201 = units.PretreatmentFlash('F201', ins=T202 - 0, outs=(Stream('flash_steam'), Stream('flash_effluent')), P=101325, Q=0) M204 = bst.Mixer('M204', ins=(R201 - 0, F201 - 0)) S202 = units.PressureFilter('S202', ins=(F201 - 1), outs=(Stream('pretreated_stream'), Stream('pretreated_liquid')), moisture_content=0.5, split=find_split_solids(F201 - 1, non_soluble)) S203 = bst.Splitter('S203', ins=M204 - 0, outs=(Stream('steam_back'), Stream('residual_steam')), split=0.25) H201 = units.WasteVaporCondenser('H201', ins=S203 - 1, outs=Stream('condensed_steam'), T=99 + 273.15, V=0) S203 - 0 - 1 - M205 steam_out1 = S203 - 1 steam_inS203 = 0 - S203 steam_out0 = S203 - 0 def update_split(): steam_out1.mol[:] = steam_inS203.mol[:] - steam_out0.mol[:] pretreatment_sys = System( 'pretreatment_sys', path=( water_recycle_sys, M205, M203, R201, P201, T202, F201, M204, S202, S203, update_split, H201), # TODO: H201 moved to the end, no need to resimulate system recycle=M204 - 0) ### TODO: There is a bug in original code; for now spec is constant # S203.split[:] = 0.5 T90 = 90 + 273.15 def f_DSpret(split): S203.split[:] = split for i in range(3): pretreatment_sys.simulate() sobj = M205 - 0 return sobj.T - T90 pretreatment_sys.specification = BoundedNumericalSpecification( f_DSpret, 0.10, 0.70) ### Fermentation system ### cellulase_conc = 0.05 cellulase = Stream('cellulase', units='kg/hr', price=price['Enzyme']) ammonia = Stream( 'ammonia', Ammonia=1051 / 1000 * dryflow, #This is just a initialization units='kg/hr', phase='l', price=price['Ammonia']) process_water2 = Stream( 'process_water2', T=10 + 273.15, P=1 * 101325, Water=1664.8 / 1000 * dryflow, #This is just a guess units='kg/hr') ammonia1 = Stream( 'ammonia1', Ammonia=26 / 1000 * dryflow, #This is just a initialization units='kg/hr', price=price['Ammonia']) ammonia2 = Stream( 'ammonia2', Ammonia=116 / 1000 * dryflow, #This is just a initialization units='kg/hr', price=price['Ammonia']) ammonia_fresh = Stream('ammonia_fresh', units='kg/hr', price=price['Ammonia']) ammonia_storage = units.DAPTank('Ammonia_storage', ins=ammonia_fresh, outs='Ammonia_fermentation') S301 = bst.ReversedSplitter('S301', ins=ammonia_storage - 0, outs=(ammonia, ammonia1, ammonia2)) air1 = Stream('air_lagoon1', O2=51061, N2=168162, phase='g', units='kg/hr') air2 = Stream('air_lagoon2', O2=51061, N2=168162, phase='g', units='kg/hr') J1 = bst.Junction('J1', upstream=S202 - 0, downstream=Stream()) sacch_split = 0.05 #This is just a initialization ammonia_zmass = 0.0052 M301 = bst.Mixer('M301', ins=(ammonia, process_water2)) M302 = bst.Mixer('M302', ins=(J1 - 0, M301 - 0)) S303 = units.PressureFilter('S303', ins=(M302 - 0), outs=(Stream('cooled_hydrolysate'), Stream('residual_water')), moisture_content=0.4, split=find_split_solids(M302 - 0, non_soluble)) WIS_prehyd = 0.20 cooled_hydrolyzate_pre = M302.outs[0] S303_out0 = S303.outs[0] S303_out1 = S303.outs[1] def update_moisture_content(): F_non_sol_S303in = find_WIS( cooled_hydrolyzate_pre, non_soluble) * cooled_hydrolyzate_pre.F_mass F_sol_S303in = cooled_hydrolyzate_pre.F_mass - F_non_sol_S303in F_sol_S303out = F_non_sol_S303in / WIS_prehyd - cellulase.F_mass - F_non_sol_S303in split_soluble = F_sol_S303out / F_sol_S303in new_split = find_split_solids(cooled_hydrolyzate_pre, non_soluble) new_split[new_split == 0] = split_soluble S303_out0.mass[:] = cooled_hydrolyzate_pre.mass[:] * new_split S303_out1.mass[:] = cooled_hydrolyzate_pre.mass[:] * (1 - new_split) T203 = units.AmmoniaAdditionTank('T203', ins=S303 - 0) M303 = units.EnzymeHydrolysateMixer('M303', ins=(T203 - 0, cellulase)) cellulase_over_WIS = 0.05 * cellulase_conc water_over_WIS = 0.05 * (1 - cellulase_conc) def update_cellulase_and_nutrient_loading(): WIS_premixer = cooled_hydrolyzate_pre.F_mass * find_WIS( cooled_hydrolyzate_pre, non_soluble) cellulase_mass = cellulase_over_WIS * WIS_premixer water_mass = water_over_WIS * WIS_premixer cellulase.imass['Cellulase'] = cellulase_mass * 1.1 cellulase.imass['Water'] = water_mass * 1.1 # Note: An additional 10% is produced for the media glucose/sophorose mixture # Humbird (2011) p[g. 37 def update_ammonia_loading(): water_cooled_hydrolyzate = cooled_hydrolyzate_pre.imass['Water'] ammonia.F_mass = water_cooled_hydrolyzate * ammonia_zmass M304 = bst.Mixer('M304', ins=(M303 - 0, None)) R301 = units.SaccharificationAndCoFermentation( 'R301', ins=(M304 - 0, ammonia1, air1), outs=(Stream('CO2_1'), Stream('fermentation_slurry'), Stream('saccharified_to_seed')), saccharified_slurry_split=sacch_split) M305 = bst.Mixer('M305', ins=(R301 - 2, ammonia2, air2)) R302 = units.SeedTrain('R302', ins=M305 - 0, outs=(Stream('CO2_2'), Stream('effluent'))) T301 = units.SeedHoldTank('T301', ins=R302 - 1) T301 - 0 - 1 - M304 air2_over_glucose = (R302.reactions.X[1] * 2.17 + R302.reactions.X[3] * 1.5 / 2 - R302.reactions.X[2]) * 1.1 ammonia2_over_glucose = R302.reactions.X[1] * 0.62 * 1.1 preseed = M305 - 0 def update_nutrient_loading2(): glucose_preseed = preseed.imol['Glucose'] air2.imol['O2'] = air2_over_glucose * glucose_preseed air2.imol['N2'] = (air2_over_glucose * glucose_preseed) / 0.21 * 0.79 ammonia2_mol = ammonia2_over_glucose * glucose_preseed - preseed.imol[ 'Ammonia'] if ammonia2_mol < 0: ammonia2.imol['NH3'] = 0 else: ammonia2.imol['NH3'] = ammonia2_mol air1_over_glucose = (R301.cofermentation.X[1] * 2.17 + R301.cofermentation.X[3] * 1.5 / 2 - R301.cofermentation.X[2]) * 1.2 ammonia1_over_glucose = R301.cofermentation.X[1] * 0.62 * 1.1 preferm = M304 - 0 glucose_over_glucan = R301.saccharification.X[ 0] + R301.saccharification.X[1] * 0.5 + R301.saccharification.X[2] def update_nutrient_loading1(): glucose_preferm = preferm.imol['Glucan'] * glucose_over_glucan * ( 1 - R301.saccharified_slurry_split) air1.imol['O2'] = air1_over_glucose * glucose_preferm air1.imol['N2'] = (air1_over_glucose * glucose_preferm) / 0.21 * 0.79 ammonia1_mol = ammonia1_over_glucose * glucose_preferm - preferm.imol[ 'Ammonia'] * (1 - R301.saccharified_slurry_split) if ammonia1_mol < 0: ammonia1.imol['NH3'] = 0 else: ammonia1.imol['NH3'] = ammonia1_mol # TODO: Bug in update nutrient loading (not enough O2 to run R301 and R302 seed train) # TODO: so just ignore negative flow in the meanwhile # def ignore_negative_O2_flow(): # for i in (R301.outs + R302.outs): i.imol['O2'] = 0 seed_recycle_sys = System('seed_recycle_sys', path=(M304, update_nutrient_loading1, R301, M305, update_nutrient_loading2, R302, T301), recycle=M304 - 0) conc_yeast = 3.0 def f_DSferm1(x): sacch_split = x R301.saccharified_slurry_split = sacch_split for i in range(3): seed_recycle_sys.simulate() s_obj2 = R301 - 1 light_ind = s_obj2.chemicals._light_indices l = [a for a in s_obj2.vol[light_ind] if not a == 0] v_0 = s_obj2.F_vol - sum(l) conc_yeast_obtained = s_obj2.imass['S_cerevisiae'] / v_0 return ((conc_yeast_obtained - conc_yeast) / conc_yeast) seed_recycle_sys.specification = BoundedNumericalSpecification( f_DSferm1, 0.01, 0.35) fermentation_sys = System( 'fermentation_sys', path=(J1, M301, M302, S303, update_ammonia_loading, T203, update_cellulase_and_nutrient_loading, update_moisture_content, M303, seed_recycle_sys)) #update_moisture_content, T_solid_cool = 50.0 + 273.15 def f_DSferm2(x): mass_water = x process_water2.F_mass = mass_water for i in range(3): fermentation_sys.simulate() s_obj1 = M302 - 0 return ((s_obj1.T - T_solid_cool) / T_solid_cool) fermentation_sys.specification = BoundedNumericalSpecification( f_DSferm2, process_water2.F_mass / 2, process_water2.F_mass * 2) ### Ethanol purification ### stripping_water = Stream( 'stripping_water', Water=26836, #This is just a initialization units='kg/hr') M306 = bst.Mixer('M306', ins=(R302 - 0, R301 - 0)) T302 = units.BeerTank('T302', outs=Stream('cool_feed')) # tmo.Stream.default_ID_number = 400 M401 = bst.Mixer('M401', ins=(R301 - 1, None)) M401 - 0 - T302 D401 = bst.VentScrubber('D401', ins=(stripping_water, M306 - 0), outs=(Stream('CO2_purified'), Stream('bottom_liquid')), gas=('CO2', 'NH3', 'O2', 'N2')) D401 - 1 - 1 - M401 # Heat up before beer column # Exchange heat with stillage mid_eth_massfrac = 0.50 high_eth_massfrac = 0.915 bott_eth_massfrac = 0.00001 dist_high_pres = 2 * 101325 high_dist_stream = Stream('high_eth_stream', Ethanol=high_eth_massfrac, Water=1 - high_eth_massfrac, units='kg/hr') mid_dist_stream = Stream('mid_eth_stream', Ethanol=mid_eth_massfrac, Water=1 - mid_eth_massfrac, units='kg/hr') bottom_stream = Stream( 'bottom_stream', Ethanol= bott_eth_massfrac, #only an initialization. Later it gets updated with the real composition Water=1 - bott_eth_massfrac, units='kg/hr') dist_high_dp = high_dist_stream.dew_point_at_P(dist_high_pres) bott_mid_dp = bottom_stream.dew_point_at_T(dist_high_dp.T - 5) dist_mid_dp = mid_dist_stream.dew_point_at_P(bott_mid_dp.P) bott_low_dp = bottom_stream.dew_point_at_T(dist_mid_dp.T - 5) dist_low_dp = mid_dist_stream.dew_point_at_P(bott_low_dp.P) S401 = bst.Splitter('S401', ins=(T302 - 0), outs=(Stream('feed_low_pressure', P=bott_low_dp.P), Stream('feed_mid_pressure', P=bott_mid_dp.P)), split=0.5) H402 = bst.HXprocess('H402', ins=(S401 - 0, None), outs=(Stream('warmed_feed_lp'), Stream('cooled_bottom_water_lp')), U=1.28) H403 = bst.HXprocess('H403', ins=(S401 - 1, None), outs=(Stream('warmed_feed_mp'), Stream('cooled_bottom_water_mp')), U=1.28) # Beer column Ethanol_MW = chemicals.Ethanol.MW Water_MW = chemicals.Water.MW def Ethanol_molfrac(e): """Return ethanol mol fraction in a ethanol water mixture""" return e / Ethanol_MW / (e / Ethanol_MW + (1 - e) / Water_MW) xbot = Ethanol_molfrac(bott_eth_massfrac) ytop = Ethanol_molfrac(mid_eth_massfrac) D402 = units.DistillationColumn('D402', ins=H402 - 0, P=bott_low_dp.P, y_top=ytop, x_bot=xbot, k=1.5, LHK=('Ethanol', 'Water'), energy_integration=True) D402.tray_material = 'Stainless steel 304' D402.vessel_material = 'Stainless steel 304' D402.BM = 2.4 D402.boiler.U = 1.85 # Condense distillate H402_dist = bst.HXutility('H402_dist', ins=D402 - 0, V=0, T=dist_low_dp.T - 1) P402_2 = bst.Pump('P402_2', ins=H402_dist - 0, P=bott_mid_dp.P) P402_2.BM = 3.1 D402 - 1 - 1 - H402 LP_dist_sys = System('LP_dist_sys', path=(H402, D402, H402_dist), recycle=H402 - 0) D403 = units.DistillationColumn('D403', ins=H403 - 0, P=bott_mid_dp.P, y_top=ytop, x_bot=xbot, k=1.5, LHK=('Ethanol', 'Water'), energy_integration=True) D403.tray_material = 'Stainless steel 304' D403.vessel_material = 'Stainless steel 304' D403.BM = 2.4 D403.boiler.U = 1.85 # Condense distillate H403_dist = bst.HXutility('H403_dist', ins=D403 - 0, V=0, T=dist_mid_dp.T - 1) D403 - 1 - 1 - H403 MP_dist_sys = System('MP_dist_sys', path=(H403, D403, H403_dist), recycle=H403 - 0) M402 = bst.Mixer('M402', ins=(P402_2 - 0, H403_dist - 0), outs=Stream(P=bott_mid_dp.P)) P404 = bst.Pump('P404', ins=M402 - 0, P=dist_high_pres) M403 = bst.Mixer('M403', ins=(H402 - 1, H403 - 1), outs=Stream('bottom_water')) S402 = units.PressureFilter('S402', ins=(M403 - 0), outs=(Stream('Lignin'), Stream('Thin_spillage')), flux=1220.6 * 0.8, moisture_content=0.35, split=find_split_solids(M403 - 0, non_soluble)) # Mix ethanol Recycle (Set-up) M404 = bst.Mixer('M404', ins=(P404 - 0, None), outs=Stream(P=dist_high_pres)) ytop = Ethanol_molfrac(high_eth_massfrac) D404 = units.DistillationColumn('D404', ins=M404 - 0, P=dist_high_pres, y_top=ytop, x_bot=xbot, k=1.5, LHK=('Ethanol', 'Water'), energy_integration=True) D404.tray_material = 'Stainless steel 304' D404.vessel_material = 'Stainless steel 304' D404.BM = 2.4 D404.boiler.U = 1.85 P405 = bst.Pump('P405', ins=D404 - 1, outs=Stream('bottom_water')) # Superheat vapor for mol sieve H404 = bst.HXutility('H404', ins=D404 - 0, T=dist_high_dp.T + 37.0, V=1) # Molecular sieve U401 = bst.MolecularSieve('U401', ins=H404 - 0, split=(2165.14 / 13356.04, 1280.06 / 1383.85), order=('Ethanol', 'Water')) U401 - 0 - 1 - M404 ethanol_recycle_sys = System('ethanol_recycle_sys', path=(M404, D404, H404, U401), recycle=M404 - 0) # Condense ethanol product H405 = bst.HXutility('H405', ins=U401 - 1, V=0, T=dist_high_dp.T - 1) T701 = bst.StorageTank('T701', ins=H405 - 0, tau=7 * 24, vessel_type='Floating roof', vessel_material='Carbon steel') ethanol = Stream('ethanol', price=price['Ethanol']) P701 = bst.Pump('P701', ins=T701 - 0, outs=ethanol) P701.BM = 3.1 T701.BM = 1.7 vent_stream = M306 - 0 stripping_water_over_vent = stripping_water.mol / 21202.490455845436 def update_stripping_water(): stripping_water.mol[:] = stripping_water_over_vent * vent_stream.F_mass purification_sys = System( 'purification_sys', path=(M306, update_stripping_water, D401, M401, T302, S401, MP_dist_sys, LP_dist_sys, P402_2, M402, P404, M403, S402, ethanol_recycle_sys, P405, H405, T701, P701)) def f_DSpur(split): S401.split[:] = split for i in range(3): purification_sys.simulate() heat_cond = D403.condenser.Q + H403_dist.Q heat_boil = D402.boiler.Q return heat_boil + heat_cond #heat_boil and heat_cond have different signs purification_sys.specification = BoundedNumericalSpecification( f_DSpur, 0.10, 0.70) ### Biogas production organic_groups = [ 'OtherSugars', 'SugarOligomers', 'OrganicSolubleSolids', 'Furfurals', 'OtherOrganics', 'Protein', 'CellMass' ] organics = list( sum([chemical_groups[i] for i in organic_groups], ('Ethanol', 'AceticAcid', 'Xylose', 'Glucose', 'ExtractVol', 'ExtractNonVol'))) organics.remove('WWTsludge') P_sludge = 0.05 / 0.91 / chemicals.WWTsludge.MW MW = np.array([chemicals.CH4.MW, chemicals.CO2.MW]) CH4_molcomp = 0.60 mass = np.array([CH4_molcomp, 1 - CH4_molcomp]) * MW mass /= mass.sum() mass *= 0.381 / (0.91) P_ch4, P_co2 = mass / MW def anaerobic_rxn(reactant): MW = getattr(chemicals, reactant).MW return rxn.Reaction( f"{1/MW}{reactant} -> {P_ch4}CH4 + {P_co2}CO2 + {P_sludge}WWTsludge", reactant, 0.91) anaerobic_digestion = rxn.ParallelReaction( [anaerobic_rxn(i) for i in organics] + [rxn.Reaction(f"H2SO4 -> H2S + 2O2", 'H2SO4', 1.)]) well_water1 = Stream('well_water1', Water=1, T=15 + 273.15) J5_1 = bst.Junction('J5_1', upstream=S303 - 1, downstream=Stream()) J5_2 = bst.Junction('J5_2', upstream=S402 - 1, downstream=Stream()) J5_3 = bst.Junction('J5_3', upstream=S202 - 1, downstream=Stream()) J5_4 = bst.Junction('J5_4', upstream=H201 - 0, downstream=Stream()) J5_5 = bst.Junction('J5_5', upstream=P405 - 0, downstream=Stream()) M501 = bst.Mixer('M501', ins=(J5_1 - 0, J5_2 - 0, J5_3 - 0, J5_4 - 0, J5_5 - 0)) splits = [('Ethanol', 1, 15), ('Water', 27158, 356069), ('Glucose', 3, 42), ('Xylose', 7, 85), ('OtherSugars', 13, 175), ('SugarOligomers', 10, 130), ('OrganicSolubleSolids', 182, 2387), ('InorganicSolubleSolids', 8, 110), ('Ammonia', 48, 633), ('AceticAcid', 0, 5), ('Furfurals', 5, 70), ('OtherOrganics', 9, 113), ('Cellulose', 19, 6), ('Xylan', 6, 2), ('OtherStructuralCarbohydrates', 1, 0), ('Lignin', 186, 64), ('Protein', 51, 18), ('CellMass', 813, 280), ('OtherInsolubleSolids', 68, 23)] raw_biogas = Stream('raw_biogas', price=price['Pure biogas'] * 0.33) Tin_digestor = 37 + 273.15 R501 = units.AnaerobicDigestion('R501', ins=(M501 - 0, well_water1), outs=(raw_biogas, 'waste_effluent', 'sludge_effluent', ''), reactions=anaerobic_digestion, sludge_split=find_split(*zip(*splits)), T=Tin_digestor) digestor_sys = System('digestor_sys', path=(J5_1, J5_2, J5_3, J5_4, J5_5, M501, R501)) ### Waste water treatment combustion = chemicals.get_combustion_reactions() def growth(reactant): f = chemicals.WWTsludge.MW / getattr(chemicals, reactant).MW return rxn.Reaction(f"{f}{reactant} -> WWTsludge", reactant, 1.) # Note, nitrogenous species included here, but most of it removed in R601 digester aerobic_digestion = rxn.ParallelReaction([ i * 0.74 + 0.22 * growth(i.reactant) for i in combustion if (i.reactant in organics) ]) aerobic_digestion.X[:] = 0.96 # tmo.Stream.default_ID_number = 600 well_water = Stream('well_water', Water=1, T=15 + 273.15) raw_biogas2 = Stream('raw_biogas2', price=price['Pure biogas'] * 0.33) WWTC = units.WasteWaterSystemCost('WWTC', ins=R501 - 1) R601 = units.AnaerobicDigestionWWT('R601', ins=(WWTC - 0, well_water), outs=(raw_biogas2, '', '', ''), reactions=anaerobic_digestion, sludge_split=find_split(*zip(*splits)), T=Tin_digestor - 2) air = Stream('air_lagoon', O2=51061, N2=168162, phase='g', units='kg/hr') caustic = Stream('WWT_caustic', Water=2252, NaOH=2252, units='kg/hr', price=price['Caustic'] * 0.5) # polymer = Stream('WWT polymer') # Empty in humbird report :-/ M602 = bst.Mixer('M602', ins=(R601 - 1, None)) caustic_over_waste = caustic.mol / 2544300.6261793654 air_over_waste = air.mol / 2544300.6261793654 waste = M602 - 0 def update_aerobic_input_streams(): F_mass_waste = waste.F_mass caustic.mol[:] = F_mass_waste * caustic_over_waste air.mol[:] = F_mass_waste * air_over_waste R602 = units.AerobicDigestionWWT('R602', ins=(waste, air, caustic), outs=('evaporated_water', ''), reactions=aerobic_digestion) splits = [('Ethanol', 0, 1), ('Water', 381300, 2241169), ('Glucose', 0, 2), ('Xylose', 1, 3), ('OtherSugars', 1, 7), ('SugarOligomers', 1, 6), ('OrganicSolubleSolids', 79, 466), ('InorganicSolubleSolids', 4828, 28378), ('Ammonia', 3, 16), ('Furfurals', 0, 3), ('OtherOrganics', 1, 7), ('CarbonDioxide', 6, 38), ('O2', 3, 17), ('N2', 5, 32), ('Cellulose', 0, 194), ('Xylan', 0, 65), ('OtherStructuralCarbohydrates', 0, 15), ('Lignin', 0, 1925), ('Protein', 0, 90), ('CellMass', 0, 19778), ('OtherInsolubleSolids', 0, 707)] S601 = bst.Splitter('S601', ins=R602 - 1, split=find_split(*zip(*splits))) S602 = bst.Splitter('S602', ins=S601 - 1, split=0.96) M603 = bst.Mixer('M603', ins=(S602 - 0, None)) M603 - 0 - 1 - M602 M604 = bst.Mixer('M604', ins=(R601 - 2, S602 - 1)) centrifuge_species = ('Water', 'Glucose', 'Xylose', 'OtherSugars', 'SugarOligomers', 'OrganicSolubleSolids', 'InorganicSolubleSolids', 'Ammonia', 'Furfurals', 'OtherOrganics', 'CO2', 'COxSOxNOxH2S', 'Cellulose', 'Xylan', 'OtherStructuralCarbohydrates', 'Lignin', 'Protein', 'CellMass', 'OtherInsolubleSolids') S623_flow = np.array( [7708, 0, 0, 1, 1, 13, 75, 3, 0, 1, 1, 2, 25, 8, 2, 250, 52, 1523, 92]) S616_flow = np.array([ 109098, 3, 6, 13, 9, 187, 1068, 46, 5, 8, 14, 31, 1, 0, 0, 13, 3, 80, 5 ]) S603 = bst.Splitter('S603', ins=M604 - 0, outs=('', 'sludge'), split=find_split(centrifuge_species, S616_flow, S623_flow)) S603 - 0 - 1 - M603 S604 = bst.Splitter('S604', ins=S601 - 0, outs=('treated_water', 'waste_brine'), split={'Water': 0.987}) aerobic_recycle_sys = System('aerobic_recycle_sys', path=(M602, update_aerobic_input_streams, R602, S601, S602, M604, S603, M603), recycle=M602 - 0) WWT_sys = System('WWT_sys', path=(WWTC, R601, aerobic_recycle_sys, S604)) ### Facilities # %% Facilities # TODO: Double check that I did is right. # TODO: The BoilerTurbogenerator burns both biogas and lignin # Note that lime and boilerchems cost is taking into account in the # unit operation now # M605 = bst.Mixer('M605', ins=(R501-0, R601-0)) BT = bst.facilities.BoilerTurbogenerator( 'BT', ins=(S402 - 0, '', 'boiler_makeup_water', 'natural_gas', 'lime', 'boilerchems'), turbogenerator_efficiency=0.85) # tmo.Stream.default_ID_number = 700 CWP = bst.facilities.ChilledWaterPackage('CWP') CT = bst.facilities.CoolingTower('CT') CT.outs[1].T = 273.15 + 28 water_thermo = tmo.Thermo(tmo.Chemicals(['Water'])) process_water = tmo.Stream(ID='process_water', thermo=water_thermo) process_water_streams = (caustic, stripping_water, process_water1, process_water2, steam, BT - 1, CT - 1) def update_water_loss(): process_water.imol['Water'] = sum( [i.imol['Water'] for i in process_water_streams]) makeup_water = Stream('makeup_water', thermo=water_thermo, price=price['Makeup water']) PWC = bst.facilities.ProcessWaterCenter( 'PWC', ins=(S604 - 0, makeup_water), outs=(process_water, ''), makeup_water_streams=(makeup_water, ), process_water_streams=process_water_streams) Substance = tmo.Chemical.blank('Substance') Substance.at_state(phase='l') Substance.default() substance_thermo = tmo.Thermo(tmo.Chemicals([Substance])) CIP = Stream('CIP', thermo=substance_thermo, flow=(126 / 83333 * dryflow, )) CIP_package = units.CIPpackage('CIP_package', ins=CIP, thermo=substance_thermo) plant_air = Stream('plant_air', flow=(83333 / 83333 * dryflow, ), thermo=substance_thermo) ADP = bst.facilities.AirDistributionPackage('ADP', ins=plant_air, thermo=substance_thermo) FT = units.FireWaterTank('FT', ins=Stream('fire_water', flow=(8343 / 83333 * dryflow, ), thermo=substance_thermo), thermo=substance_thermo) ### Complete system wheatstraw_sys = System('wheatstraw_sys', path=(pretreatment_sys, fermentation_sys, ammonia_storage, S301, purification_sys, digestor_sys, WWT_sys), facilities=(CWP, BT, CT, update_water_loss, PWC, ADP, CIP_package, S301, ammonia_storage, FT)) return wheatstraw_sys