Exemple #1
0
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
    
Exemple #2
0
    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 __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 _register(self, ID):
     replace_ticket_number = isinstance(ID, int)
     if replace_ticket_number: 
         Stream.ticket_numbers[Stream.ticket_name] = ID
     if ID == "" or replace_ticket_number: 
         registry = Stream.registry
         data = registry.data
         ID = Stream._take_ticket()
         while ID in data: ID = Stream._take_ticket()
         registry.register(ID, self)
     elif ID:
         Stream.registry.register_safely(ID, self) 
     else:
         self._ID = Stream._take_unregistered_ticket()
    def to_stream(self, ID=None):
        """
        Return a copy as a :class:`~thermosteam.Stream` object.

        Examples
        --------
        >>> import biosteam as bst
        >>> bst.settings.set_thermo(['Water', 'Ethanol']) 
        >>> cooling_water = bst.HeatUtility.get_agent('cooling_water')
        >>> cooling_water_copy = cooling_water.to_stream('cooling_water_copy')
        >>> cooling_water_copy.show(flow='kg/hr')
        Stream: cooling_water_copy
         phase: 'l', T: 305.37 K, P: 101325 Pa
         flow (kg/hr): Water  18
        
        """
        new = Stream.__new__(Stream)
        new._sink = new._source = None
        new._thermo = self._thermo
        new._imol = self._imol.copy()
        new._thermal_condition = self._thermal_condition.copy()
        new._init_cache()
        new._price = 0.
        new.ID = ID
        return new
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)
Exemple #7
0
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'
        )
Exemple #8
0
 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
Exemple #11
0
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)
Exemple #12
0
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 ID(self, ID):
     Stream._register(self, ID)
         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)
Exemple #15
0
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
Exemple #16
0
         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',
Exemple #17
0
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
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')
Exemple #19
0
    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