text: # -*- coding: utf-8 -*-
  """
  Created on Thu Jun 27 23:12:28 2019
  
  @author: yoelr
  """
  
  from biosteam import System
  import biosteam as bst
  import thermosteam as tmo
  from thermosteam import Stream
  from biorefineries import BST222
  from biorefineries.sugarcane import create_ethanol_purification_system
  from biorefineries.cornstover._process_settings import price
  from biorefineries.cornstover._chemicals import chemical_groups
  from biorefineries.cornstover import units
  import thermosteam.reaction as rxn
  import numpy as np
  
  
  __all__ = (
      'create_system',
      'create_facilities',
      'create_hot_water_pretreatment_system',
      'create_dilute_acid_pretreatment_system',
      'create_saccharification_system',
      'create_cofermentation_system',
      'create_simultaneous_saccharification_and_cofermentation_system',
      'create_integrated_bioprocess_saccharification_and_cofermentation_system',
      'create_cellulosic_fermentation_system',
  )
  
  default_nonsolids = ['Water', 'Ethanol', 'AceticAcid', 
                       'Furfural', 'H2SO4', 'NH3', 'HMF']
  
  default_insoluble_solids = ['Glucan', 'Mannan', 'Xylan', 
                              'Arabinan', 'Galactan', 'Lignin']
  
  default_ignored = ['TAG', 'DAG', 'MAG', 'FFA', 'PL']
  
  @bst.SystemFactory(
      ID='hot_water_pretreatment_sys',
      ins=[dict(ID='cornstover', # Cornstover composition by default
                Glucan=0.28,
                Xylan=0.1562,
                Galactan=0.001144,
                Arabinan=0.01904,
                Mannan=0.0048,
                Lignin=0.12608,
                Acetate=0.01448,
                Protein=0.0248,
                Extract=0.1172,
                Ash=0.03944,
                Sucrose=0.00616,
                Water=0.2,
                total_flow=104229.16,
                units='kg/hr',
                price=price['Feedstock'])],
      outs=[dict(ID='hydrolyzate'),
            dict(ID='pretreatment_wastewater')],
  )
  def create_hot_water_pretreatment_system(
          ins, outs,
          pretreatment_area=200,
          include_feedstock_handling=True,
          solids_loading=0.305,
          nonsolids=['Water'],
          milling=False,
          T_pretreatment_reactor=273.15 + 130.
      ):
      
      feedstock, = ins
      hydrolyzate, pretreatment_wastewater = outs
      
      warm_process_water = Stream('warm_process_water',
                                T=368.15,
                                P=4.7*101325,
                                Water=1)
      pretreatment_steam = Stream('pretreatment_steam',
                      phase='g',
                      T=268+273.15,
                      P=13*101325,
                      Water=24534+3490,
                      units='kg/hr')
      
      ### Pretreatment system
      n = pretreatment_area
      P = pretreatment_steam.chemicals['H2O'].Psat(T_pretreatment_reactor + 25)
      M203 = bst.SteamMixer(f'M{n+2}', (feedstock, pretreatment_steam, warm_process_water), P=P)
      R201 = units.PretreatmentReactorSystem(f'R{n+1}', M203-0, T=T_pretreatment_reactor)
      P201 = units.BlowdownDischargePump(f'P{n+1}', R201-1)
      F201 = units.PretreatmentFlash(f'F{n+1}', P201-0, P=101325, Q=0)
      M204 = bst.Mixer(f'M{n+3}', (R201-0, F201-0))
      H201 = units.WasteVaporCondenser(f'H{n+1}', M204-0, pretreatment_wastewater, V=0)
      P202 = units.HydrolyzatePump(f'P{n+2}', F201-1, None)
      if milling:
          U201 = bst.HammerMill(f'U{n+1}', P202-0, hydrolyzate)
          P202.outs[0].materialize_connection
      else:
          P202.outs[0] = hydrolyzate
      M203.solids_loading = solids_loading
      @M203.add_specification(run=True)
      def update_pretreatment_process_water():
          solids_loading = M203.solids_loading
          chemicals = M203.chemicals
          indices = chemicals.available_indices(nonsolids)
          *other_feeds, warm_process_water = M203.ins
          F_mass_feed = sum([i.F_mass for i in other_feeds if i])
          available_water = (chemicals.MW[indices] * sum([i.mol[indices] for i in other_feeds if i])).sum()
          required_water = (F_mass_feed - available_water) * (1. - solids_loading) / solids_loading
          warm_process_water.imass['Water'] = max(required_water - available_water, 0.)
  
  @bst.SystemFactory(
      ID='dilute_acid_pretreatment_sys',
      ins=[dict(ID='cornstover', # Cornstover composition by default
                Glucan=0.28,
                Xylan=0.1562,
                Galactan=0.001144,
                Arabinan=0.01904,
                Mannan=0.0048,
                Lignin=0.12608,
                Acetate=0.01448,
                Protein=0.0248,
                Extract=0.1172,
                Ash=0.03944,
                Sucrose=0.00616,
                Water=0.2,
                total_flow=104229.16,
                units='kg/hr',
                price=price['Feedstock'])],
      outs=[dict(ID='hydrolyzate'),
            dict(ID='pretreatment_wastewater')],
  )
  def create_dilute_acid_pretreatment_system(
          ins, outs,
          pretreatment_area=200,
          include_feedstock_handling=True,
          solids_loading=0.3,
          nonsolids=default_nonsolids,
      ):
      
      feedstock, = ins
      hydrolyzate, pretreatment_wastewater = outs
      
      warm_process_water_1 = Stream('warm_process_water_1',
                                T=368.15,
                                P=4.7*101325,
                                Water=1)
      ammonia_process_water = Stream('ammonia_process_water',
                                T=368.15,
                                P=4.7*101325,
                                Water=1)
      sulfuric_acid = Stream('sulfuric_acid',
                              P=5.4*101325,
                              T=294.15,
                              Water=130,
                              SulfuricAcid=1800,
                              units='kg/hr',
                              price=price['Sulfuric acid'])
      pretreatment_steam = Stream('pretreatment_steam',
                      phase='g',
                      T=268+273.15,
                      P=13*101325,
                      Water=24534+3490,
                      units='kg/hr')
      ammonia = Stream('ammonia',
                        units='kg/hr',
                        phase='l',
                        price=price['Ammonia'])
      warm_process_water_2 = warm_process_water_1.copy('warm_process_water_2')
      
      ### Pretreatment system
      n = pretreatment_area
      H2SO4_storage = units.SulfuricAcidStorageTank('H2SO4_storage', sulfuric_acid)
      T201 = units.SulfuricAcidTank(f'T{n+1}', H2SO4_storage-0)
      M201 = units.SulfuricAcidMixer(f'M{n+1}', (warm_process_water_1, T201-0))
      M202 = bst.Mixer(f'M{n+2}', (M201-0, warm_process_water_2, feedstock))
      M203 = bst.SteamMixer(f'M{n+3}', (M202-0, pretreatment_steam), P=5.5*101325)
      R201 = units.PretreatmentReactorSystem(f'R{n+1}', M203-0)
      P201 = units.BlowdownDischargePump(f'P{n+1}', R201-1)
      T202 = units.OligomerConversionTank(f'T{n+2}', P201-0)
      F201 = units.PretreatmentFlash(f'F{n+1}', T202-0, P=101325, Q=0)
      M204 = bst.Mixer(f'M{n+4}', (R201-0, F201-0))
      H201 = units.WasteVaporCondenser(f'H{n+1}', M204-0, pretreatment_wastewater, T=373.15, V=0)
      Ammonia_storage = units.AmmoniaStorageTank('Ammonia_storage', ammonia)
      M205 = units.AmmoniaMixer(f'M{n+5}', (Ammonia_storage-0, ammonia_process_water))
      T203 = units.AmmoniaAdditionTank(f'T{n+3}', (F201-1, M205-0))
      P202 = units.HydrolyzatePump(f'P{n+2}', T203-0, hydrolyzate)
      
      M202.solids_loading = solids_loading
      @M202.add_specification(run=True)
      def update_pretreatment_process_water():
          chemicals  = M202.chemicals 
          indices = chemicals.available_indices(default_nonsolids)
          sulfuric_acid, warm_process_water, feed = M202.ins
          available_water = (chemicals.MW[indices] * (feed.mol[indices] + sulfuric_acid.mol[indices])).sum()
          solids_loading = M202.solids_loading
          required_water = (feed.F_mass + sulfuric_acid.F_mass - available_water) * (1. - solids_loading) / solids_loading
          warm_process_water.imass['Water'] = max(required_water - available_water, 0.)
      
      @T201.add_specification(run=True)
      def update_sulfuric_acid_loading():
          F_mass_dry_feedstock = feedstock.F_mass - feedstock.imass['water']
          sulfuric_acid, = H2SO4_storage.ins
          warm_water, _ = M201.ins
          sulfuric_acid.F_mass = 0.02316 * F_mass_dry_feedstock
          warm_water.F_mass = 0.282 * F_mass_dry_feedstock
      
      neutralization_rxn = tmo.Reaction('2 NH4OH + H2SO4 -> (NH4)2SO4 + 2 H2O', 'H2SO4', 1)
      @M205.add_specification(run=True)
      def update_ammonia_loading():
          ammonia, ammonia_process_water = M205.ins
          hydrolyzate = F201.outs[1]
          ammonia.imol['NH4OH'] = 2. * hydrolyzate.imol['H2SO4']
          ammonia_process_water.imass['Water'] = 2435.6 * ammonia.imol['NH4OH']
      
      def neutralization():
          T203._run(); neutralization_rxn.adiabatic_reaction(T203.outs[0])
      T203.specification = neutralization
  
  @bst.SystemFactory(
      ID='saccharification_sys',
      ins=[dict(ID='hydrolyzate'),
           dict(ID='cellulase',
                units='kg/hr',
                price=price['Enzyme']),
           dict(ID='saccharification_water')],
      outs=[dict(ID='slurry')],
  )
  def create_saccharification_system(
          ins, outs, solids_loading=None, 
          nonsolids=None,
          insoluble_solids=None,
          insoluble_solids_loading=None,
          ignored=None,
          Saccharification=None,
      ):
      hydrolyzate, cellulase, saccharification_water = ins
      slurry, = outs
      if nonsolids is None: nonsolids = default_nonsolids
      if insoluble_solids is None: insoluble_solids = default_insoluble_solids
      if insoluble_solids_loading is None: insoluble_solids_loading = 10.3
      if solids_loading is None: solids_loading = 0.2
      if ignored is None: ignored = default_ignored
      M301 = units.EnzymeHydrolysateMixer('M301', (hydrolyzate, cellulase, saccharification_water))
      H301 = units.HydrolysateHeatExchanger('H301', M301-0, T=48+273.15)
      M301.solids_loading = solids_loading
      M301.insoluble_solids_loading = insoluble_solids_loading
      M301.enzyme_loading = 0.02 # (20 g enzyme / 1000 g cellulose mixture) 
      M301.enzyme_concentration = 0.05 # (50 g cellulase / 1000g enzyme) 
      
      @M301.add_specification
      def update_cellulase_loading():
          hydrolyzate, cellulase, water = M301.ins
          # Note: An additional 10% is produced to produce sophorose
          # Humbird (2011) pg. 37 
          enzyme_concentration = M301.enzyme_concentration
          enzyme_over_cellulose = M301.enzyme_loading / enzyme_concentration
          z_mass_cellulase_mixture = np.array([1 - enzyme_concentration , enzyme_concentration], float)
          cellulase.imass['Water', 'Cellulase'] = (
              enzyme_over_cellulose
              * z_mass_cellulase_mixture
              * 1.2 * (hydrolyzate.imass['Glucan', 'GlucoseOligomer'].sum())
          )
      
      
      @M301.add_specification(run=True)
      def update_moisture_content():
          hydrolyzate, cellulase, saccharification_water, *other = M301.ins
          chemicals = M301.chemicals
          s_mix = Stream.sum([hydrolyzate, cellulase], None, M301.outs[0].thermo, energy_balance=False)
          mass = s_mix.mol * chemicals.MW
          solids_loading = M301.solids_loading
          insoluble_solids_loading = M301.insoluble_solids_loading
          indices = chemicals.available_indices(nonsolids)
          mass_moisture = mass[indices].sum()
          indices = chemicals.available_indices(insoluble_solids)
          mass_insoluble = mass[indices].sum()
          indices = chemicals.available_indices(ignored)
          if indices:
              mass_ignored = mass[indices].sum()
          else:
              mass_ignored = 0.
          total_mass = mass.sum() - mass_ignored
          water_over_solids = (1 - solids_loading) / solids_loading
          water_over_insoluble_solids = (1 - insoluble_solids_loading) / insoluble_solids_loading
          M301.required_saccharification_water = max(
              water_over_solids * (total_mass - mass_moisture) - mass_moisture,
              water_over_insoluble_solids * mass_insoluble - (total_mass - mass_insoluble)
          )
          saccharification_water.imass['Water'] = max(M301.required_saccharification_water, 0.)
      
      if Saccharification is None:
          Saccharification = units.ContinuousPresaccharification
      R301 = Saccharification('R301', H301-0, slurry)
  
  @bst.SystemFactory(
      ID='cofermentation_sys',
      ins=[dict(ID='slurry'),
           dict(ID='DAP',
                price=price['DAP']),
           dict(ID='CSL',
                price=price['CSL'])],
      outs=[dict(ID='vent'),
            dict(ID='beer')],
  )
  def create_simultaneous_saccharification_and_cofermentation_system(
          ins, outs, SimultaneousSaccharificationAndCoFermentation=None, SeedTrain=None,
          include_scrubber=True
      ):
      slurry, DAP, CSL = ins
      vent, beer = outs
      
      DAP1 = Stream('DAP1',
                      DAP=26,
                      units='kg/hr',
                      price=price['DAP'])
      DAP2 = Stream('DAP2',
                      DAP=116,
                      units='kg/hr',
                      price=price['DAP'])
      CSL1 = Stream('CSL1',
                      CSL=211,
                      units='kg/hr',
                      price=price['CSL'])
      CSL2 = Stream('CSL2',
                      CSL=948,
                      units='kg/hr',
                      price=price['CSL'])
      
      DAP_storage = units.DAPStorageTank('DAP_storage', DAP)
      S301 = bst.FakeSplitter('S301', DAP_storage-0, outs=(DAP1, DAP2))
      CSL_storage = units.CSLStorageTank('CSL_storage', CSL)
      S302 = bst.FakeSplitter('S302', CSL_storage-0, outs=(CSL1, CSL2))
      S303 = bst.Splitter('S303', slurry, split=0.1)
      if not SimultaneousSaccharificationAndCoFermentation:
          SimultaneousSaccharificationAndCoFermentation = units.SimultaneousSaccharificationAndCoFermentation
      if not SeedTrain: SeedTrain = units.SeedTrain
      R302 = SeedTrain('R302', (S303-0, CSL1, DAP1), saccharification=True)
      
      def adjust_CSL_and_DAP_feed_to_seed_train():
          feed, CSL1, DAP1 = R302.ins
          CSL1.imass['CSL'] = 0.0050 * feed.F_mass
          DAP1.imass['DAP'] = 0.33 * feed.F_vol
          R302._run()
      
      R302.specification = adjust_CSL_and_DAP_feed_to_seed_train
      T301 = units.SeedHoldTank('T301', R302-1)
      R303 = SimultaneousSaccharificationAndCoFermentation(
          'R303', (S303-1, T301-0, CSL2, DAP2),
      )
      
      def adjust_CSL_and_DAP_feed_to_fermentation():
          feed, seed, CSL2, DAP2 = R303.ins
          CSL2.imass['CSL'] = 0.0025 * feed.F_mass
          DAP2.imass['DAP'] = 0.33 * feed.F_vol
          S301.ins[0].mix_from(S301.outs)
          S302.ins[0].mix_from(S302.outs)
          DAP_storage.ins[0].copy_like(DAP_storage.outs[0])
          CSL_storage.ins[0].copy_like(CSL_storage.outs[0])
          R303._run()
      
      R303.specification = adjust_CSL_and_DAP_feed_to_fermentation
      M304 = bst.Mixer('M304', (R302-0, R303-0))
      T302 = units.BeerTank('T302', outs=beer)
      
      if include_scrubber:
          stripping_water = Stream('stripping_water',
                                   Water=26836,
                                   units='kg/hr')
          M401 = bst.Mixer('M401', (R303-1, None))
          D401 = bst.VentScrubber('D401', (stripping_water, M304-0), (vent, ''),
                                  gas=('CO2', 'NH3', 'O2'))
          D401-1-1-M401-0-T302
      
          stripping_water_over_vent = stripping_water.mol / 21202.490455845436
          def update_stripping_water():
              stripping_water, vent = D401.ins
              stripping_water.mol[:] = stripping_water_over_vent * vent.F_mass
              D401._run()
          D401.specification = update_stripping_water
      else:
          M304.outs[0] = vent
          R303-1-T302
  
  @bst.SystemFactory(
      ID='cofermentation_sys',
      ins=[dict(ID='slurry'),
           dict(ID='DAP',
                price=price['DAP']),
           dict(ID='CSL',
                price=price['CSL'])],
      outs=[dict(ID='vent'),
            dict(ID='beer')],
  )
  def create_integrated_bioprocess_saccharification_and_cofermentation_system(
          ins, outs, SaccharificationAndCoFermentation=None, SeedTrain=None,
          include_scrubber=True
      ):
      slurry, DAP, CSL = ins
      vent, beer = outs
      
      DAP1 = Stream('DAP1',
                      DAP=26,
                      units='kg/hr',
                      price=price['DAP'])
      DAP2 = Stream('DAP2',
                      DAP=116,
                      units='kg/hr',
                      price=price['DAP'])
      CSL1 = Stream('CSL1',
                      CSL=211,
                      units='kg/hr',
                      price=price['CSL'])
      CSL2 = Stream('CSL2',
                      CSL=948,
                      units='kg/hr',
                      price=price['CSL'])
      
      DAP_storage = units.DAPStorageTank('DAP_storage', DAP)
      S301 = bst.FakeSplitter('S301', DAP_storage-0, outs=(DAP1, DAP2))
      CSL_storage = units.CSLStorageTank('CSL_storage', CSL)
      S302 = bst.FakeSplitter('S302', CSL_storage-0, outs=(CSL1, CSL2))
      if not SaccharificationAndCoFermentation:
          SaccharificationAndCoFermentation = units.SaccharificationAndCoFermentation
      if not SeedTrain: SeedTrain = units.SeedTrain
      recycle = bst.Stream()
      R302 = SeedTrain('R302', (recycle, CSL1, DAP1))
      
      def adjust_CSL_and_DAP_feed_to_seed_train():
          feed, CSL1, DAP1 = R302.ins
          CSL1.imass['CSL'] = 0.0050 * feed.F_mass
          DAP1.imass['DAP'] = 0.33 * feed.F_vol
          R302._run()
      
      R302.specification = adjust_CSL_and_DAP_feed_to_seed_train
      T301 = units.SeedHoldTank('T301', R302-1)
      R303 = SaccharificationAndCoFermentation('R303', (slurry, T301-0, CSL2, DAP2),
                                               outs=('', '', recycle))
      
      def adjust_CSL_and_DAP_feed_to_fermentation():
          feed, seed, CSL2, DAP2 = R303.ins
          CSL2.imass['CSL'] = 0.0025 * feed.F_mass
          DAP2.imass['DAP'] = 0.33 * feed.F_vol
          S301.ins[0].mix_from(S301.outs)
          S302.ins[0].mix_from(S302.outs)
          DAP_storage.ins[0].copy_like(DAP_storage.outs[0])
          CSL_storage.ins[0].copy_like(CSL_storage.outs[0])
          R303._run()
      
      R303.specification = adjust_CSL_and_DAP_feed_to_fermentation
      M304 = bst.Mixer('M304', (R302-0, R303-0))
      T302 = units.BeerTank('T302', outs=beer)
      
      if include_scrubber:
          stripping_water = Stream('stripping_water',
                                   Water=26836,
                                   units='kg/hr')
          M401 = bst.Mixer('M401', (R303-1, None))
          D401 = bst.VentScrubber('D401', (stripping_water, M304-0), (vent, ''),
                                  gas=('CO2', 'NH3', 'O2'))
          D401-1-1-M401-0-T302
      
          stripping_water_over_vent = stripping_water.mol / 21202.490455845436
          def update_stripping_water():
              stripping_water, vent = D401.ins
              stripping_water.mol[:] = stripping_water_over_vent * vent.F_mass
              D401._run()
          D401.specification = update_stripping_water
      else:
          M304.outs[0] = vent
          R303-1-T302
   
  @bst.SystemFactory(
      ID='cofermentation_sys',
      ins=[dict(ID='slurry'),
           dict(ID='DAP',
                price=price['DAP']),
           dict(ID='CSL',
                price=price['CSL'])],
      outs=[dict(ID='vent'),
            dict(ID='beer'),
            dict(ID='lignin')],
  )
  def create_cofermentation_system(
          ins, outs, CoFermentation=None, SeedTrain=None,
          include_scrubber=True
      ):
      slurry, DAP, CSL = ins
      vent, beer, lignin = outs
      
      DAP1 = Stream('DAP1',
                      DAP=26,
                      units='kg/hr',
                      price=price['DAP'])
      DAP2 = Stream('DAP2',
                      DAP=116,
                      units='kg/hr',
                      price=price['DAP'])
      CSL1 = Stream('CSL1',
                      CSL=211,
                      units='kg/hr',
                      price=price['CSL'])
      CSL2 = Stream('CSL2',
                      CSL=948,
                      units='kg/hr',
                      price=price['CSL'])
      
      DAP_storage = units.DAPStorageTank('DAP_storage', DAP)
      S301 = bst.FakeSplitter('S301', DAP_storage-0, outs=(DAP1, DAP2))
      CSL_storage = units.CSLStorageTank('CSL_storage', CSL)
      S302 = bst.FakeSplitter('S302', CSL_storage-0, outs=(CSL1, CSL2))
      S303 = bst.PressureFilter('S303', slurry, (lignin, ''))
      S304 = bst.Splitter('S304', S303-1, split=0.1)
      if not SeedTrain: SeedTrain = units.SeedTrain
      R302 = SeedTrain('R302', (S304-0, CSL1, DAP1))
      
      @R302.add_specification(run=True)
      def adjust_CSL_and_DAP_feed_to_seed_train():
          feed, CSL1, DAP1 = R302.ins
          CSL1.imass['CSL'] = 0.0050 * feed.F_mass
          DAP1.imass['DAP'] = 0.33 * feed.F_vol
      
      T301 = units.SeedHoldTank('T301', R302-1)
      if not CoFermentation: CoFermentation = units.CoFermentation
      R303 = CoFermentation('R303', (S304-1, T301-0, CSL2, DAP2),
                            outs=('', ''))
      
      @R303.add_specification(run=True)
      def adjust_CSL_and_DAP_feed_to_fermentation():
          feed, seed, CSL2, DAP2, *other = R303.ins
          CSL2.imass['CSL'] = 0.0025 * feed.F_mass
          DAP2.imass['DAP'] = 0.33 * feed.F_vol
          S301.ins[0].mix_from(S301.outs)
          S302.ins[0].mix_from(S302.outs)
          DAP_storage.ins[0].copy_like(DAP_storage.outs[0])
          CSL_storage.ins[0].copy_like(CSL_storage.outs[0])
      
      M304 = bst.Mixer('M304', (R302-0, R303-0))
      T302 = units.BeerTank('T302', outs=beer)
      
      if include_scrubber:
          stripping_water = Stream('stripping_water',
                                   Water=26836,
                                   units='kg/hr')
          M401 = bst.Mixer('M401', (R303-1, None))
          D401 = bst.VentScrubber('D401', (stripping_water, M304-0), (vent, ''),
                                  gas=('CO2', 'NH3', 'O2'))
          D401-1-1-M401-0-T302
      
          stripping_water_over_vent = stripping_water.imol['Water'] / 21202.490455845436
          def update_stripping_water():
              stripping_water, vent = D401.ins
              stripping_water.imol['Water'] = stripping_water_over_vent * vent.F_mass
              D401._run()
          D401.specification = update_stripping_water
      else:
          M304.outs[0] = vent
          R303-1-T302   
   
  @bst.SystemFactory(
      ID='cellulosic_fermentation_sys',
      ins=[dict(ID='hydrolyzate'),
           dict(ID='cellulase',
                units='kg/hr',
                price=price['Enzyme']),
           dict(ID='saccharification_water'),
           dict(ID='DAP',
                price=price['DAP']),
           dict(ID='CSL',
                price=price['CSL'])],
      outs=[dict(ID='vent'),
            dict(ID='beer'),
            dict(ID='lignin')],
      optional_outs_index=[2],
  )
  def create_cellulosic_fermentation_system(
          ins, outs,
          include_scrubber=True,
          solids_loading=None,
          insoluble_solids_loading=None,
          nonsolids=None,
          insoluble_solids=None,
          kind=0, 
          # 0 for Integrated Bioprocess(IB)
          # 1 for Simultaneous Saccharification and Co-fermentation (SSCF) 
      ):
      vent, beer, lignin = outs
      hydrolyzate, cellulase, saccharification_water, DAP, CSL = ins
      
      saccharification_sys = create_saccharification_system(
          ins=[hydrolyzate, cellulase, saccharification_water],
          mockup=True,
          solids_loading=solids_loading,
          insoluble_solids_loading=insoluble_solids_loading,
          nonsolids=nonsolids,
          insoluble_solids=insoluble_solids,
          Saccharification=(units.Saccharification if kind == 2 else units.ContinuousPresaccharification),
      )
      if kind == 0:
          cofermentation_sys = create_integrated_bioprocess_saccharification_and_cofermentation_system(
              ins=[saccharification_sys-0, DAP, CSL],
              outs=[vent, beer],
              mockup=True
          )
      elif kind == 1:
          cofermentation_sys = create_simultaneous_saccharification_and_cofermentation_system(
              ins=[saccharification_sys-0, DAP, CSL],
              outs=[vent, beer],
              mockup=True
          )
      elif kind == 2:
          T303 = bst.StorageTank('T303', saccharification_sys-0, tau=4)
          cofermentation_sys = create_cofermentation_system(
              ins=[T303-0, DAP, CSL],
              outs=[vent, beer, lignin],
              mockup=True
          )
      else:
          raise ValueError("invalid 'kind'")
  
  def create_facilities(
          solids_to_boiler, 
          gas_to_boiler,
          process_water_streams,
          feedstock,
          RO_water='',
          recycle_process_water='',
          blowdown_to_wastewater=None,
          BT_area=None,
          area=None,
      ):
      
      BT = bst.facilities.BoilerTurbogenerator(BT_area or area or 'BT',
                                               ins=(solids_to_boiler,
                                                    gas_to_boiler, 
                                                    'boiler_makeup_water',
                                                    'natural_gas',
                                                    'FGD_lime',
                                                    'boilerchems'))
      
      CWP = bst.facilities.ChilledWaterPackage(area or 'CWP')
      CT = bst.facilities.CoolingTower(area or 'CT')
      
      process_water_streams = (*process_water_streams,
                                BT-1, CT-1)
              
      makeup_water = Stream('makeup_water', price=price['Makeup water'])
      
      PWC = bst.facilities.ProcessWaterCenter(area or 'PWC',
                                              (RO_water, makeup_water, recycle_process_water),
                                              (),
                                              None,
                                              (BT-1, CT-1),
                                              process_water_streams)
      
      CIP = Stream('CIP', Water=126, units='kg/hr')
      CIP_package = bst.facilities.CIPpackage(area or 'CIP_package', CIP)
      plant_air = Stream('plant_air', N2=83333, units='kg/hr')
      def adjust_plant_air():
          plant_air.imass['N2'] = 0.8 * feedstock.F_mass
          ADP._run()
          
      ADP = bst.facilities.AirDistributionPackage(area or 'ADP', plant_air)
      ADP.specification = adjust_plant_air
      fire_water = Stream('fire_water', Water=8343, units='kg/hr')
      FT = units.FireWaterStorageTank(area or 'FT', fire_water)
      
      ### Complete system
      if blowdown_to_wastewater:
          blowdown_mixer = bst.BlowdownMixer(area or 'blowdown_mixer', (BT-1, CT-1), blowdown_to_wastewater)
  
  @bst.SystemFactory(
      ID='cornstover_sys',
      ins=[*create_dilute_acid_pretreatment_system.ins,
            dict(ID='denaturant',
                Octane=1,
                price=price['Denaturant'])],
      outs=[dict(ID='ethanol',
                  price=price['Ethanol'])],
  )
  def create_system(ins, outs, include_blowdown_recycle=False):
      feedstock, denaturant = ins
      ethanol, = outs
      f = bst.main_flowsheet
      s = f.stream
      u = f.unit
      U101 = units.FeedStockHandling('U101', feedstock)
      U101.cost_items['System'].cost = 0.
      pretreatment_sys = create_dilute_acid_pretreatment_system(
          ins=U101-0,
          mockup=True
      )
      fermentation_sys = create_cellulosic_fermentation_system(
          ins=pretreatment_sys-0,
          mockup=True,
      )
      ethanol_purification_sys = create_ethanol_purification_system(
          ins=[fermentation_sys-1, denaturant],
          outs=[ethanol],
          IDs={'Beer pump': 'P401',
               'Beer column heat exchange': 'H401',
               'Beer column': 'D402',
               'Beer column bottoms product pump': 'P402',
               'Distillation': 'D403',
               'Distillation bottoms product pump': 'P403',
               'Ethanol-denaturant mixer': 'M701',
               'Recycle mixer': 'M402',
               'Heat exchanger to superheat vapor to molecular sieves': 'H402',
               'Molecular sieves': 'U401',
               'Ethanol condenser': 'H403',
               'Ethanol day tank': 'T701', 
               'Ethanol day tank pump': 'P701',
               'Denaturant storage': 'T702', 
               'Denaturant pump': 'P702', 
               'Product tank': 'T703'},
          mockup=True,
      )
      ethanol, stillage, stripper_bottoms_product = ethanol_purification_sys.outs
      recycled_water = tmo.Stream(Water=1,
                                  T=47+273.15,
                                  P=3.9*101325,
                                  units='kg/hr')
      S401 = bst.PressureFilter('S401', (stillage, recycled_water))
      if include_blowdown_recycle:
          blowdown_to_wastewater = Stream('blowdown_to_wastewater')
      else:
          blowdown_to_wastewater = None
      wastewater_treatment_sys = bst.create_wastewater_treatment_system(
          ins=[S401-1, pretreatment_sys-1, blowdown_to_wastewater],
          mockup=True,
      )
      M501 = bst.Mixer('M501', (u.S603-1, S401-0))
      create_facilities(
          solids_to_boiler=M501-0,
          gas_to_boiler=u.R601-0,
          process_water_streams=(s.caustic, s.stripping_water, 
                                  s.warm_process_water_1, 
                                  s.warm_process_water_2,
                                  s.pretreatment_steam,
                                  s.saccharification_water),
          feedstock=feedstock,
          RO_water=u.S604-0,
          recycle_process_water=stripper_bottoms_product,
          blowdown_to_wastewater=blowdown_to_wastewater,
      )
  