def test_component(): import thermosteam as tmo from qsdsan import Component, Components from chemicals.elements import molecular_weight from math import isclose S_NH4 = Component('S_NH4', formula='NH4+', measured_as='N', f_BOD5_COD=0, f_uBOD_COD=0, f_Vmass_Totmass=0, description="Ammonium", particle_size="Soluble", degradability="Undegradable", organic=False) assert S_NH4.i_N == 1 assert S_NH4.i_NOD == molecular_weight({'O':4})/molecular_weight({'N':1}) S_NH4.measured_as = None assert S_NH4.i_mass == 1 S_Ac = Component('S_Ac', formula='CH3COO-', measured_as='COD', f_BOD5_COD=0.717, f_uBOD_COD=0.863, f_Vmass_Totmass=1, description="Acetate", particle_size="Soluble", degradability="Readily", organic=True) assert S_Ac.i_COD == 1 S_Ac.measured_as = None assert S_Ac.i_mass == 1 assert S_Ac.i_COD == molecular_weight({'O':4})/molecular_weight({'C':2, 'H':3, 'O':2}) S_HS = Component.from_chemical('S_HS', tmo.Chemical('Hydrosulfide'), particle_size="Soluble", degradability="Undegradable", organic=False) assert S_HS.i_charge < 0 S_HS.measured_as = 'S' assert S_HS.i_mass > 1 components = Components.load_default(default_compile=False) #!!! Should we allow None for particle_size, degradability, and organic? # with pytest.raises(AssertionError): # H2O = Component.from_chemical('H2O', tmo.Chemical('H2O')) H2O = Component.from_chemical('H2O', tmo.Chemical('H2O'), particle_size='Soluble', degradability='Undegradable', organic=False) with pytest.raises(ValueError): components.append(H2O) components = Components.load_default() assert components.S_H2.measured_as == 'COD' assert components.S_H2.i_COD == 1 assert isclose(components.S_N2.i_COD, - molecular_weight({'O':1.5})/molecular_weight({'N':1}), rel_tol=1e-3) assert isclose(components.S_NO3.i_COD, - molecular_weight({'O':4})/molecular_weight({'N':1}), rel_tol=1e-3) tmo.settings.set_thermo(components)
def test_waste_stream(): import thermosteam as tmo from qsdsan import Components, WasteStream components = Components.load_default() tmo.settings.set_thermo(components) ws1 = WasteStream.codstates_inf_model('ws1', 1e5) ws2 = WasteStream.codstates_inf_model('ws2', 1e5 * 24 / 1e3, units=('m3/d', 'g/m3')) assert isclose(ws1.COD, 430, rel_tol=1e-3) assert isclose(ws1.TKN, 40, rel_tol=1e-3) assert isclose(ws1.TP, 10, rel_tol=1e-3) assert isclose(ws1.F_vol, ws2.F_vol) ws3 = WasteStream(S_Ac=5, H2O=1000, units='kg/hr') ws4 = WasteStream(X_NOO=10, H2O=1000, units='kg/hr') ws5 = WasteStream() ws5.mix_from((ws3, ws4)) assert_allclose(ws5.F_mass, 2015.0) # TODO: After updating the default component properties, # add in tests here to make sure COD, etc. are calculated correctly assert_allclose(ws5.COD, 7424.606289711915, rtol=1e-3) # Make sure below attributes are calculated based on flow info, cannot be set with pytest.raises(AttributeError): ws5.COD = 5
def _create_asm2d_cmps(pickle=False): cmps = Components.load_default() S_A = cmps.S_Ac.copy('S_A') S_ALK = cmps.S_CO3.copy('S_ALK') # measured as g C S_F = cmps.S_F.copy('S_F') S_I = cmps.S_U_E.copy('S_I') S_N2 = cmps.S_N2.copy('S_N2') S_NH4 = cmps.S_NH4.copy('S_NH4') S_NO3 = cmps.S_NO3.copy('S_NO3') S_O2 = cmps.S_O2.copy('S_O2') S_PO4 = cmps.S_PO4.copy('S_PO4') X_AUT = cmps.X_AOO.copy('X_AUT') X_H = cmps.X_OHO.copy('X_H') X_I = cmps.X_U_OHO_E.copy('X_I') X_MeOH = cmps.X_FeOH.copy('X_MeOH') X_MeP = cmps.X_FePO4.copy('X_MeP') X_PAO = cmps.X_PAO.copy('X_PAO') X_PHA = cmps.X_PAO_PHA.copy('X_PHA') X_PP = cmps.X_PAO_PP_Lo.copy('X_PP') X_S = cmps.X_B_Subst.copy('X_S') S_I.i_N = 0.01 S_F.i_N = 0.03 X_I.i_N = 0.02 X_S.i_N = 0.04 X_H.i_N = X_PAO.i_N = X_AUT.i_N = 0.07 S_I.i_P = 0.00 S_F.i_P = 0.01 X_I.i_P = 0.01 X_S.i_P = 0.01 X_H.i_P = X_PAO.i_P = X_AUT.i_P = 0.02 X_I.i_mass = 0.75 X_S.i_mass = 0.75 X_H.i_mass = X_PAO.i_mass = X_AUT.i_mass = 0.9 cmps_asm2d = Components([S_O2, S_F, S_A, S_NH4, S_NO3, S_PO4, S_I, S_ALK, S_N2, X_I, X_S, X_H, X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP, cmps.H2O]) cmps_asm2d.compile() if pickle: save_pickle(cmps_asm2d, _path_cmps) return cmps_asm2d
def test_sanunit(): import biosteam as bst from qsdsan import Components, WasteStream, sanunits components = Components.load_default() bst.settings.set_thermo(components) ws1 = WasteStream(S_Ac=5, H2O=1000, units='kg/hr') ws2 = WasteStream(X_NOO=10, H2O=1000, units='kg/hr') M1 = sanunits.Mixer('M1', ins=(ws1, ws2, ''), outs='mixture') M1.show() assert type(M1.ins[0]).__name__ == 'WasteStream' S1 = sanunits.Splitter('S1', ins=M1-0, outs=('', ''), split=0.2) ins3 = WasteStream(S_CH3OH=7, H2O=1000, units='kg/hr') P1 = sanunits.Pump('P1', ins=ins3) M2 = sanunits.MixTank('M2', ins=(S1-0, P1-0), tau=2) M2-0-2-M1 System = bst.System('System', path=(M1, S1, P1, M2), recycle=M2-0) System.simulate() assert_allclose(M2.installed_cost, 65519.00446342958, rtol=1e-3)
def test_waste_stream(): import pytest, numpy as np from numpy.testing import assert_allclose from math import isclose from qsdsan import set_thermo, Components, WasteStream components = Components.load_default() set_thermo(components) ws1 = WasteStream.codstates_inf_model('ws1', 1e5) ws2 = WasteStream.codstates_inf_model('ws2', 1e5*24/1e3, units=('m3/d', 'g/m3')) assert isclose(ws1.COD, 430, rel_tol=1e-2) assert isclose(ws1.TKN, 40, rel_tol=1e-2) assert isclose(ws1.TP, 10, rel_tol=1e-2) assert isclose(ws1.F_vol, ws2.F_vol) ws3 = WasteStream(S_Ac=5, H2O=1000, units='kg/hr') ws4 = WasteStream(X_NOO=10, H2O=1000, units='kg/hr') ws5 = WasteStream() ws5.mix_from((ws3, ws4)) assert_allclose(ws5.F_mass, 2015.0) # TODO: After updating the default component properties, # add in tests here to make sure COD, etc. are calculated correctly assert_allclose(ws5.COD, 7414.267796, rtol=1e-2) # Make sure below attributes are calculated based on flow info, cannot be set with pytest.raises(AttributeError): ws5.COD = 5 # Concentration calclation ws6 = WasteStream(X_CaCO3=1, H2O=1000, units='kg/hr') assert_allclose(np.abs(ws6.conc.value-ws6.mass/ws6.F_vol*1e3).sum(), 0, atol=1e-6) ws6.imass['X_B_Subst', 'X_GAO_PHA'] = (100, 1) ws7 = WasteStream(X_CaCO3=1, X_B_Subst=100, X_GAO_PHA=1, H2O=1000, units='kg/hr') assert_allclose(np.abs(ws6.conc.value-ws7.mass/ws7.F_vol*1e3).sum(), 0, atol=1e-6) ws6.mass[:] = 1e-3 ws6.imass['H2O'] = 1e3 diff = ws6.conc.value - np.ones_like(ws6.conc.value) diff[components.index('H2O')] = 0 assert_allclose(np.max(np.abs(diff)), 0, atol=1e-2)
def test_process(): from qsdsan import Components, Process, Processes, CompiledProcesses import thermosteam as tmo cmps = Components.load_default() S_A = cmps.S_Ac.copy('S_A') S_ALK = cmps.S_CO3.copy('S_ALK') # measured as g C S_F = cmps.S_F.copy('S_F') S_I = cmps.S_U_E.copy('S_I') S_N2 = cmps.S_N2.copy('S_N2') S_NH4 = cmps.S_NH4.copy('S_NH4') S_NO3 = cmps.S_NO3.copy('S_NO3') S_O2 = cmps.S_O2.copy('S_O2') S_PO4 = cmps.S_PO4.copy('S_PO4') X_AUT = cmps.X_AOO.copy('X_AUT') X_H = cmps.X_OHO.copy('X_H') X_I = cmps.X_U_OHO_E.copy('X_I') X_MeOH = cmps.X_FeOH.copy('X_MeOH') X_MeP = cmps.X_FePO4.copy('X_MeP') X_PAO = cmps.X_PAO.copy('X_PAO') X_PHA = cmps.X_PAO_PHA.copy('X_PHA') X_PP = cmps.X_PAO_PP_Lo.copy('X_PP') X_S = cmps.X_B_Subst.copy('X_S') S_I.i_N = 0.01 S_F.i_N = 0.03 X_I.i_N = 0.02 X_S.i_N = 0.04 X_H.i_N = X_PAO.i_N = X_AUT.i_N = 0.07 S_I.i_P = 0.00 S_F.i_P = 0.01 X_I.i_P = 0.01 X_S.i_P = 0.01 X_H.i_P = X_PAO.i_P = X_AUT.i_P = 0.02 cmps_asm2d = Components([S_O2, S_F, S_A, S_NH4, S_NO3, S_PO4, S_I, S_ALK, S_N2, X_I, X_S, X_H, X_PAO, X_PP, X_PHA, X_AUT, X_MeOH, X_MeP]) cmps_asm2d.compile() tmo.settings.set_thermo(cmps_asm2d) p1 = Process('aero_hydrolysis', 'X_S -> [1-f_SI]S_F + [f_SI]S_I + [?]S_NH4 + [?]S_PO4 + [?]S_ALK', ref_component='X_S', rate_equation='K_h * S_O2/(K_O2+S_O2) * X_S/(K_X*X_H+X_S) * X_H', parameters=('f_SI', 'K_h', 'K_O2', 'K_X')) f_SI = symbols('f_SI') assert abs(sum(p1._stoichiometry * p1._components.i_N).subs({'f_SI':1})) < 1e-8 assert abs(sum(p1._stoichiometry * p1._components.i_N).subs({'f_SI':0})) < 1e-8 assert abs(sum(p1._stoichiometry * p1._components.i_P).subs({'f_SI':1})) < 1e-8 assert abs(sum(p1._stoichiometry * p1._components.i_charge).subs({'f_SI':1})) < 1e-8 p1.set_parameters(f_SI = 0.0) assert p1.parameters['f_SI'] == 0.0 assert Eq(p1._stoichiometry[p1._components._index['S_I']], parse_expr('1*f_SI')) p12 = Process('anox_storage_PP', 'S_PO4 + [Y_PHA]X_PHA + [?]S_NO3 -> X_PP + [?]S_N2 + [?]S_NH4 + [?]S_ALK', ref_component='X_PP', rate_equation='q_PP * S_O2/(K_O2+S_O2) * S_PO4/(K_PS+S_PO4) * S_ALK/(K_ALK+S_ALK) * (X_PHA/X_PAO)/(K_PHA+X_PHA/X_PAO) * (K_MAX-X_PP/X_PAO)/(K_PP+K_MAX-X_PP/X_PAO) * X_PAO * eta_NO3 * K_O2/S_O2 * S_NO3/(K_NO3+S_NO3)', parameters=('Y_PHA', 'q_PP', 'K_O2', 'K_PS', 'K_ALK', 'K_PHA', 'eta_NO3', 'K_PP', 'K_NO3'), conserved_for=('COD', 'N', 'P', 'NOD', 'charge')) p14 = Process('PAO_anox_growth', '[1/Y_H]X_PHA + [?]S_NO3 + [?]S_PO4 -> X_PAO + [?]S_N2 + [?]S_NH4 + [?]S_ALK', ref_component='X_PAO', rate_equation='mu_PAO * S_O2/(K_O2 + S_O2) * S_NH4/(K_NH4 + S_NH4) * S_PO4/(K_P + S_PO4) * S_CO3/(K_ALK + S_ALK) * (X_PHA/X_PAO)/(K_PHA + X_PHA/X_PAO) * X_PAO * eta_NO3 * K_O2/S_O2 * S_NO3/(K_NO3 + S_NO3)', parameters=('Y_H', 'mu_PAO', 'K_O2', 'K_NH4', 'K_P', 'K_ALK', 'K_PHA', 'eta_NO3', 'K_NO3'), conserved_for=('COD', 'N', 'P', 'NOD', 'charge')) PAO_anox_processes = Processes([p12, p14]) assert PAO_anox_processes.PAO_anox_growth.ref_component == X_PAO with pytest.raises(AttributeError): print(PAO_anox_processes.production_rates) params = ('f_SI', 'Y_H', 'f_XI', 'Y_PO4', 'Y_PHA', 'Y_A', 'K_h', 'eta_NO3', 'eta_fe', 'K_O2', 'K_NO3', 'K_X', 'mu_H', 'q_fe', 'eta_NO3_deni', 'b_H', 'K_F', 'K_fe', 'K_A', 'K_NH4', 'K_P', 'K_ALK', 'q_PHA', 'q_PP', 'mu_PAO', 'b_PAO', 'b_PP', 'b_PHA', 'K_PS', 'K_PP', 'K_MAX', 'K_IPP', 'K_PHA', 'mu_AUT', 'b_AUT', 'K_O2_AUT', 'K_NH4_AUT', 'K_ALK_2', 'k_PRE', 'k_RED') path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ASM2d_original.tsv') asm2d = Processes.load_from_file(path, conserved_for=('COD', 'N', 'P', 'charge'), parameters=params, compile=False) asm2d.extend(PAO_anox_processes) asm2d.compile() assert isinstance(asm2d, CompiledProcesses) assert p12 in asm2d assert set(asm2d.parameters.keys()) == set(params)
def test_component(): import pytest from qsdsan import Chemical, Component, Components, set_thermo, \ _waste_stream as ws_module from chemicals.elements import molecular_weight from math import isclose S_NH4 = Component('S_NH4', formula='NH4+', measured_as='N', f_BOD5_COD=0, f_uBOD_COD=0, f_Vmass_Totmass=0, description="Ammonium", particle_size="Soluble", degradability="Undegradable", organic=False) assert S_NH4.i_N == 1 assert S_NH4.i_NOD == molecular_weight({'O': 4}) / molecular_weight( {'N': 1}) S_NH4.measured_as = None assert S_NH4.i_mass == 1 S_Ac = Component('S_Ac', formula='CH3COO-', measured_as='COD', f_BOD5_COD=0.717, f_uBOD_COD=0.863, f_Vmass_Totmass=1, description="Acetate", particle_size="Soluble", degradability="Readily", organic=True) assert S_Ac.i_COD == 1 S_Ac.measured_as = None assert S_Ac.i_mass == 1 assert S_Ac.i_COD == molecular_weight({'O': 4}) / molecular_weight({ 'C': 2, 'H': 3, 'O': 2 }) S_HS = Component.from_chemical('S_HS', Chemical('Hydrosulfide'), particle_size="Soluble", degradability="Undegradable", organic=False) assert S_HS.i_charge < 0 S_HS.measured_as = 'S' assert S_HS.i_mass > 1 # Check default components cmps1 = Components.load_default(default_compile=False) H2O_chemical = Chemical('H2O') H2O = Component.from_chemical('H2O', H2O_chemical) with pytest.raises(ValueError): # H2O already in default components cmps1.append(H2O) with pytest.raises( RuntimeError): # key chemical-related properties missing cmps1.compile() # Can compile with default-filling those missing properties cmps1.default_compile(lock_state_at='', particulate_ref='NaCl') cmps2 = Components((cmp for cmp in cmps1 if cmp.ID != 'H2O')) H2O = Component.from_chemical('H2O', Chemical('H2O'), particle_size='Soluble', degradability='Undegradable', organic=False) cmps2.append(H2O) cmps2.default_compile(lock_state_at='', particulate_ref='NaCl') cmps3 = Components.load_default() assert cmps3.S_H2.measured_as == 'COD' assert cmps3.S_H2.i_COD == 1 assert isclose(cmps3.S_N2.i_COD, -molecular_weight({'O': 1.5}) / molecular_weight({'N': 1}), rel_tol=1e-3) assert isclose(cmps3.S_NO3.i_COD, -molecular_weight({'O': 4}) / molecular_weight({'N': 1}), rel_tol=1e-3) set_thermo(cmps3) # Check if the default groups are up-to-date cached_cmp_IDs = ws_module._default_cmp_IDs cached_cmp_groups = ws_module._specific_groups assert set(cmps3.IDs) == cached_cmp_IDs get_IDs = lambda attr: {cmp.ID for cmp in getattr(cmps3, attr)} for attr, IDs in cached_cmp_groups.items(): assert IDs == get_IDs(attr)
def create_components(): NH3 = Component('NH3', measured_as='N', phase='l', particle_size='Soluble', degradability='Undegradable', organic=False) NonNH3 = Component('NonNH3', search_ID='N', measured_as='N', phase='l', particle_size='Soluble', degradability='Undegradable', organic=False, description='Non-NH3 nitrogen') P = Component('P', phase='l', particle_size='Soluble', degradability='Undegradable', organic=False) K = Component('K', phase='l', particle_size='Soluble', degradability='Undegradable', organic=False) Mg = Component('Mg', phase='l', particle_size='Soluble', degradability='Undegradable', organic=False) Ca = Component('Ca', phase='l', particle_size='Soluble', degradability='Undegradable', organic=False) H2O = Component('H2O', phase='l', particle_size='Soluble', degradability='Undegradable', organic=False) OtherSS = Component('OtherSS', phase='l', particle_size='Soluble', degradability='Undegradable', organic=False, description='Unspecified soluble solids') N2O = Component('N2O', phase='g', particle_size='Dissolved gas', degradability='Undegradable', organic=False) CH4 = Component('CH4', phase='g', particle_size='Dissolved gas', degradability='Slowly', organic=True) # Below three are for combustion reactions O2 = Component('O2', phase='g', particle_size='Dissolved gas', degradability='Undegradable', organic=False) N2 = Component('N2', phase='g', particle_size='Dissolved gas', degradability='Undegradable', organic=False) CO2 = Component('CO2', phase='g', particle_size='Dissolved gas', degradability='Undegradable', organic=False) P4O10 = Component.from_chemical('P4O10', Chemical('P4O10'), phase='s', particle_size='Particulate', degradability='Undegradable', organic=False) # The following will lead to an error as it won't be able to copy the V model # P4O10 = Component('P4O10', phase='s', particle_size='Particulate', # degradability='Undegradable', organic=False) def add_V_from_rho(cmp, rho): V_model = rho_to_V(rho, cmp.MW) try: cmp.V.add_model(V_model) except: handle = getattr(cmp.V, cmp.locked_state) handle.add_model(V_model) Tissue = Component('Tissue', MW=1, phase='s', particle_size='Particulate', degradability='Undegradable', organic=False, description='Tissue for toilet paper') # 375 kg/m3 is the average of 250-500 for tissue from # https://paperonweb.com/density.htm (accessed 2020-11-12) add_V_from_rho(Tissue, 375) WoodAsh = Component('WoodAsh', MW=1, phase='s', i_Mg=0.0224, i_Ca=0.3034, particle_size='Particulate', degradability='Undegradable', organic=False, description='Wood ash for desiccant') add_V_from_rho(WoodAsh, 760) for i in (Tissue, WoodAsh): i.copy_models_from(Chemical('Glucose'), ('Cn', 'mu')) Struvite = Component('Struvite', search_ID='MagnesiumAmmoniumPhosphate', formula='NH4MgPO4·H12O6', phase='s', particle_size='Particulate', degradability='Undegradable', organic=False) # http://www.chemspider.com/Chemical-Structure.8396003.html (accessed 2020-11-19) add_V_from_rho(Struvite, 1711) HAP = Component('HAP', search_ID='Hydroxyapatite', phase='s', particle_size='Particulate', degradability='Undegradable', organic=False) # Taking the average of 3.1-3.2 g/cm3 from # https://pubchem.ncbi.nlm.nih.gov/compound/Hydroxyapatite (accessed 2020-11-19) add_V_from_rho(HAP, 3150) for cmp in (NonNH3, P, K, Mg, Ca, OtherSS): cmp.default() cmp.copy_models_from(H2O, ('sigma', 'epsilon', 'kappa', 'Cn', 'mu')) add_V_from_rho(cmp, 1e3) # assume the same density as water cmps = Components((NH3, NonNH3, P, K, Mg, Ca, H2O, OtherSS, N2O, CH4, O2, N2, CO2, P4O10, Tissue, WoodAsh, Struvite, HAP)) for i in cmps: if i.HHV is None: i.HHV = 0 if i.LHV is None: i.LHV = 0 if i.Hf is None: i.Hf = 0 cmps.compile() cmps.set_synonym('H2O', 'Water') return cmps
def _create_asm1_cmps(pickle=False): cmps = Components.load_default() S_I = cmps.S_U_Inf.copy('S_I') S_I.description = 'Soluble inert organic matter' X_I = cmps.X_U_Inf.copy('X_I') X_I.description = 'Particulate inert organic matter' S_S = cmps.S_F.copy('S_S') S_S.description = 'Readily biodegradable substrate' S_S.i_N = S_I.i_N = 0 X_S = cmps.X_B_Subst.copy('X_S') X_S.description = 'Slowly biodegradable substrate' X_S.i_N = 0 X_BH = cmps.X_OHO.copy('X_BH') X_BH.description = 'Active heterotrophic biomass' X_BA = cmps.X_AOO.copy('X_BA') X_BA.description = 'Active autotrophic biomass' X_BH.i_N = X_BA.i_N = 0.086 # i_XB X_P = cmps.X_U_OHO_E.copy('X_P') X_P.description = 'Particulate products arising from biomass decay' X_P.i_N = X_I.i_N = 0.06 # i_XP # X_I.i_mass = X_S.i_mass = X_BH.i_mass = X_BA.i_mass = X_P.i_mass = .75 # fr_COD_SS S_O = cmps.S_O2.copy('S_O') S_NO = cmps.S_NO3.copy('S_NO') S_NO.description = 'Nitrate and nitrite nitrogen' S_NH = cmps.S_NH4.copy('S_NH') S_ND = cmps.S_F.copy('S_ND') S_ND.description = 'Soluble biodegradable organic nitrogen' S_ND.measured_as = 'N' S_ND.i_COD = S_ND.i_C = S_ND.i_P = S_ND.i_mass = 0 X_ND = cmps.X_B_Subst.copy('X_ND') X_ND.description = 'Particulate biodegradable organic nitrogen' X_ND.measured_as = 'N' X_ND.i_COD = X_ND.i_C = X_ND.i_P = X_ND.i_mass = 0 S_ALK = cmps.S_CO3.copy('S_ALK') # measured as g C S_ALK.description = 'Alkalinity, assumed to be HCO3-' # add S_N2 to close mass balance # add water for the creation of WasteStream objects cmps_asm1 = Components([S_I, S_S, X_I, X_S, X_BH, X_BA, X_P, S_O, S_NO, S_NH, S_ND, X_ND, S_ALK, cmps.S_N2, cmps.H2O]) cmps_asm1.compile() if pickle: save_pickle(cmps_asm1, _path_cmps) return cmps_asm1
__all__ = ('cmps', 'sys',) # ============================================================================= # Components and streams # ============================================================================= X = Component('X', phase='s', measured_as='COD', i_COD=0, description='Biomass', organic=True, particle_size='Particulate', degradability='Readily') X_inert = Component('X_inert', phase='s', description='Inert biomass', i_COD=0, organic=True, particle_size='Particulate', degradability='Undegradable') Substrate = Component('Substrate', phase='l', i_COD=1, organic=True, particle_size='Soluble', degradability='Readily') CH4 = Component('CH4', phase='g', organic=True, particle_size='Dissolved gas', degradability='Readily') cmps = Components([X, X_inert, Substrate, CH4]) # Default-adding components needed for combustion cmps = Components.append_combustion_components(cmps, lock_state_at='') # Define component groups cmps.define_group('active_biomass', IDs=('X',)) cmps.define_group('inert_biomass', IDs=('X_inert',)) cmps.define_group('substrates', IDs=('Substrate',)) set_thermo(cmps) inf = WasteStream('inf') inf.set_flow_by_concentration(flow_tot=20, concentrations={