def m(): m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.thermo_params = thermo_props.SaponificationParameterBlock() m.fs.reaction_params = rxn_props.SaponificationReactionParameterBlock( default={"property_package": m.fs.thermo_params}) m.fs.tank1 = CSTR(default={"property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params}) m.fs.tank2 = CSTR(default={"property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params}) m.fs.tank_array = CSTR( range(3), default={"property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params}) m.fs.stream = Arc(source=m.fs.tank1.outlet, destination=m.fs.tank2.inlet) def stream_array_rule(b, i): return (b.tank_array[i].outlet, b.tank_array[i+1].inlet) m.fs.stream_array = Arc(range(2), rule=stream_array_rule) TransformationFactory("network.expand_arcs").apply_to(m) return m
def test_create_stream_table_dataframe_from_StateBlock_time(): m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False, "time_set": [3]}) m.fs.thermo_params = thermo_props.SaponificationParameterBlock() m.fs.reaction_params = rxn_props.SaponificationReactionParameterBlock( default={"property_package": m.fs.thermo_params}) m.fs.tank1 = CSTR(default={"property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params}) m.fs.tank2 = CSTR(default={"property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params}) m.fs.stream = Arc(source=m.fs.tank1.outlet, destination=m.fs.tank2.inlet) TransformationFactory("network.expand_arcs").apply_to(m) df = create_stream_table_dataframe({ "state": m.fs.tank1.control_volume.properties_out}, time_point=3) assert df.loc["Pressure"]["state"] == 101325 assert df.loc["Temperature"]["state"] == 298.15 assert df.loc["Volumetric Flowrate"]["state"] == 1.0 assert df.loc["Molar Concentration H2O"]["state"] == 100.0 assert df.loc["Molar Concentration NaOH"]["state"] == 100.0 assert df.loc["Molar Concentration EthylAcetate"]["state"] == 100.0 assert df.loc["Molar Concentration SodiumAcetate"]["state"] == 100.0 assert df.loc["Molar Concentration Ethanol"]["state"] == 100.0
def model(self): m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.props = ASM1ParameterBlock() m.fs.rxn_props = ASM1ReactionParameterBlock( default={"property_package": m.fs.props}) m.fs.R1 = CSTR(default={ "property_package": m.fs.props, "reaction_package": m.fs.rxn_props, }) # Feed conditions based on manual mass balance of inlet and recycle streams m.fs.R1.inlet.flow_vol.fix(92230 * units.m**3 / units.day) m.fs.R1.inlet.temperature.fix(298.15 * units.K) m.fs.R1.inlet.pressure.fix(1 * units.atm) m.fs.R1.inlet.conc_mass_comp[0, "S_I"].fix(30 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_S"].fix(14.6112 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_I"].fix(1149 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_S"].fix(89.324 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_BH"].fix(2542.03 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_BA"].fix(148.6 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_P"].fix(448 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_O"].fix(0.3928 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_NO"].fix(8.32 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_NH"].fix(7.696 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_ND"].fix(1.9404 * units.g / units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_ND"].fix(5.616 * units.g / units.m**3) m.fs.R1.inlet.alkalinity.fix(4.704 * units.mol / units.m**3) m.fs.R1.volume.fix(1000 * units.m**3) return m
def model(): m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": True}) m.fs.params = PropertyInterrogatorBlock() m.fs.rxn_params = ReactionInterrogatorBlock( default={"property_package": m.fs.params}) m.fs.R01 = CSTR( default={ "property_package": m.fs.params, "reaction_package": m.fs.rxn_params, "has_heat_of_reaction": True }) m.fs.R02 = PFR(default={ "property_package": m.fs.params, "reaction_package": m.fs.rxn_params }) return m
def test_initialize_by_time_element(): horizon = 6 time_set = [0, horizon] ntfe = 60 # For a finite element every six seconds ntcp = 2 m = ConcreteModel(name='CSTR model for testing') m.fs = FlowsheetBlock(default={ 'dynamic': True, 'time_set': time_set, 'time_units': pyunits.minute }) m.fs.properties = AqueousEnzymeParameterBlock() m.fs.reactions = EnzymeReactionParameterBlock( default={'property_package': m.fs.properties}) m.fs.cstr = CSTR( default={ "property_package": m.fs.properties, "reaction_package": m.fs.reactions, "material_balance_type": MaterialBalanceType.componentTotal, "energy_balance_type": EnergyBalanceType.enthalpyTotal, "momentum_balance_type": MomentumBalanceType.none, "has_heat_of_reaction": True }) # Time discretization disc = TransformationFactory('dae.collocation') disc.apply_to(m, wrt=m.fs.time, nfe=ntfe, ncp=ntcp, scheme='LAGRANGE-RADAU') # Fix geometry variables m.fs.cstr.volume[0].fix(1.0) # Fix initial conditions: for p, j in m.fs.properties.phase_list * m.fs.properties.component_list: if j == 'Solvent': continue m.fs.cstr.control_volume.material_holdup[0, p, j].fix(0) # Fix inlet conditions # This is a huge hack because I didn't know that the proper way to # have multiple inlets to a CSTR was to use a mixer. # I 'combine' both my inlet streams before sending them to the CSTR. for t, j in m.fs.time * m.fs.properties.component_list: if t <= 2: if j == 'E': m.fs.cstr.inlet.conc_mol[t, j].fix(11.91 * 0.1 / 2.2) elif j == 'S': m.fs.cstr.inlet.conc_mol[t, j].fix(12.92 * 2.1 / 2.2) elif j != 'Solvent': m.fs.cstr.inlet.conc_mol[t, j].fix(0) elif t <= 4: if j == 'E': m.fs.cstr.inlet.conc_mol[t, j].fix(5.95 * 0.1 / 2.2) elif j == 'S': m.fs.cstr.inlet.conc_mol[t, j].fix(12.92 * 2.1 / 2.2) elif j != 'Solvent': m.fs.cstr.inlet.conc_mol[t, j].fix(0) else: if j == 'E': m.fs.cstr.inlet.conc_mol[t, j].fix(8.95 * 0.1 / 2.2) elif j == 'S': m.fs.cstr.inlet.conc_mol[t, j].fix(16.75 * 2.1 / 2.2) elif j != 'Solvent': m.fs.cstr.inlet.conc_mol[t, j].fix(0) m.fs.cstr.inlet.conc_mol[:, 'Solvent'].fix(1.) m.fs.cstr.inlet.flow_vol.fix(2.2) m.fs.cstr.inlet.temperature.fix(300) # Fix outlet conditions m.fs.cstr.outlet.flow_vol.fix(2.2) m.fs.cstr.outlet.temperature[m.fs.time.first()].fix(300) assert degrees_of_freedom(m) == 0 initialize_by_time_element(m.fs, m.fs.time, solver=solver) assert degrees_of_freedom(m) == 0 # Assert that the result looks how we expect assert m.fs.cstr.outlet.conc_mol[0, 'S'].value == 0 assert abs(m.fs.cstr.outlet.conc_mol[2, 'S'].value - 11.389) < 1e-2 assert abs(m.fs.cstr.outlet.conc_mol[4, 'P'].value - 0.2191) < 1e-3 assert abs(m.fs.cstr.outlet.conc_mol[6, 'E'].value - 0.0327) < 1e-3 assert abs(m.fs.cstr.outlet.temperature[6].value - 289.7) < 1 # Assert that model is still fixed and deactivated as expected assert (m.fs.cstr.control_volume.material_holdup[m.fs.time.first(), 'aq', 'S'].fixed) for t in m.fs.time: if t != m.fs.time.first(): assert (not m.fs.cstr.control_volume.material_holdup[t, 'aq', 'S'].fixed) assert not m.fs.cstr.outlet.temperature[t].fixed assert ( m.fs.cstr.control_volume.material_holdup_calculation[t, 'aq', 'C'].active) assert m.fs.cstr.control_volume.properties_out[t].active assert not m.fs.cstr.outlet.conc_mol[t, 'S'].fixed assert m.fs.cstr.inlet.conc_mol[t, 'S'].fixed # Assert that constraints are feasible after initialization for con in m.fs.component_data_objects(Constraint, active=True): assert value(con.body) - value(con.upper) < 1e-5 assert value(con.lower) - value(con.body) < 1e-5 results = solver.solve(m.fs) assert check_optimal_termination(results)
def test_ASM1_reactor(): m = pyo.ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.props = ASM1ParameterBlock() m.fs.rxn_props = ASM1ReactionParameterBlock( default={"property_package": m.fs.props}) m.fs.R1 = CSTR(default={ "property_package": m.fs.props, "reaction_package": m.fs.rxn_props, }) m.fs.R2 = CSTR(default={ "property_package": m.fs.props, "reaction_package": m.fs.rxn_props, }) m.fs.R3 = CSTR(default={ "property_package": m.fs.props, "reaction_package": m.fs.rxn_props, }) m.fs.R4 = CSTR(default={ "property_package": m.fs.props, "reaction_package": m.fs.rxn_props, }) m.fs.R5 = CSTR(default={ "property_package": m.fs.props, "reaction_package": m.fs.rxn_props, }) # Link units m.fs.stream1 = Arc(source=m.fs.R1.outlet, destination=m.fs.R2.inlet) m.fs.stream2 = Arc(source=m.fs.R2.outlet, destination=m.fs.R3.inlet) m.fs.stream3 = Arc(source=m.fs.R3.outlet, destination=m.fs.R4.inlet) m.fs.stream4 = Arc(source=m.fs.R4.outlet, destination=m.fs.R5.inlet) pyo.TransformationFactory("network.expand_arcs").apply_to(m) assert_units_consistent(m) # Feed conditions based on manual mass balance of inlet and recycle streams m.fs.R1.inlet.flow_vol.fix(92230 * pyo.units.m**3 / pyo.units.day) m.fs.R1.inlet.temperature.fix(298.15 * pyo.units.K) m.fs.R1.inlet.pressure.fix(1 * pyo.units.atm) m.fs.R1.inlet.conc_mass_comp[0, "S_I"].fix(30 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_S"].fix(14.6112 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_I"].fix(1149 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_S"].fix(89.324 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_BH"].fix(2542.03 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_BA"].fix(148.6 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_P"].fix(448 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_O"].fix(0.3928 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_NO"].fix(8.32 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_NH"].fix(7.696 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "S_ND"].fix(1.9404 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.conc_mass_comp[0, "X_ND"].fix(5.616 * pyo.units.g / pyo.units.m**3) m.fs.R1.inlet.alkalinity.fix(4.704 * pyo.units.mol / pyo.units.m**3) m.fs.R1.volume.fix(1000 * pyo.units.m**3) m.fs.R2.volume.fix(1000 * pyo.units.m**3) m.fs.R3.volume.fix(1333 * pyo.units.m**3) m.fs.R4.volume.fix(1333 * pyo.units.m**3) m.fs.R5.volume.fix(1333 * pyo.units.m**3) assert degrees_of_freedom(m) == 0 # Initialize flowsheet m.fs.R1.initialize() propagate_state(m.fs.stream1) m.fs.R2.initialize() propagate_state(m.fs.stream2) m.fs.R3.initialize() propagate_state(m.fs.stream3) m.fs.R4.initialize() propagate_state(m.fs.stream4) m.fs.R5.initialize() # For aerobic reactors, need to fix the oxygen concentration in outlet # To do this, we also need to deactivate the constraint linking O2 from # the previous unit # Doing this before initialization will cause issues with DoF however m.fs.R3.outlet.conc_mass_comp[0, "S_O"].fix(1.72 * pyo.units.g / pyo.units.m**3) m.fs.stream2.expanded_block.conc_mass_comp_equality[0, "S_O"].deactivate() m.fs.R4.outlet.conc_mass_comp[0, "S_O"].fix(2.43 * pyo.units.g / pyo.units.m**3) m.fs.stream3.expanded_block.conc_mass_comp_equality[0, "S_O"].deactivate() m.fs.R5.outlet.conc_mass_comp[0, "S_O"].fix(0.491 * pyo.units.g / pyo.units.m**3) m.fs.stream4.expanded_block.conc_mass_comp_equality[0, "S_O"].deactivate() solver = get_solver() results = solver.solve(m, tee=True) assert pyo.check_optimal_termination(results) # Verify results against reference # First reactor (anoxic) assert pyo.value(m.fs.R1.outlet.flow_vol[0]) == pytest.approx(1.0675, rel=1e-4) assert pyo.value(m.fs.R1.outlet.temperature[0]) == pytest.approx(298.15, rel=1e-4) assert pyo.value(m.fs.R1.outlet.pressure[0]) == pytest.approx(101325, rel=1e-4) assert pyo.value(m.fs.R1.outlet.conc_mass_comp[0, "S_I"]) == pytest.approx( 30e-3, rel=1e-5) assert pyo.value(m.fs.R1.outlet.conc_mass_comp[0, "S_S"]) == pytest.approx( 2.81e-3, rel=1e-2) assert pyo.value(m.fs.R1.outlet.conc_mass_comp[0, "X_I"]) == pytest.approx( 1149e-3, rel=1e-3) assert pyo.value(m.fs.R1.outlet.conc_mass_comp[0, "X_S"]) == pytest.approx( 82.1e-3, rel=1e-2) assert pyo.value( m.fs.R1.outlet.conc_mass_comp[0, "X_BH"]) == pytest.approx(2552e-3, rel=1e-3) assert pyo.value( m.fs.R1.outlet.conc_mass_comp[0, "X_BA"]) == pytest.approx(149e-3, rel=1e-2) assert pyo.value(m.fs.R1.outlet.conc_mass_comp[0, "X_P"]) == pytest.approx( 449e-3, rel=1e-2) assert pyo.value(m.fs.R1.outlet.conc_mass_comp[0, "S_O"]) == pytest.approx( 4.3e-6, rel=1e-2) assert pyo.value( m.fs.R1.outlet.conc_mass_comp[0, "S_NO"]) == pytest.approx(5.36e-3, rel=1e-2) assert pyo.value( m.fs.R1.outlet.conc_mass_comp[0, "S_NH"]) == pytest.approx(7.92e-3, rel=1e-2) assert pyo.value( m.fs.R1.outlet.conc_mass_comp[0, "S_ND"]) == pytest.approx(1.22e-3, rel=1e-2) assert pyo.value( m.fs.R1.outlet.conc_mass_comp[0, "X_ND"]) == pytest.approx(5.29e-3, rel=1e-2) assert pyo.value(m.fs.R1.outlet.alkalinity[0]) == pytest.approx(4.93e-3, rel=1e-2) # Second reactor (anoixic) assert pyo.value(m.fs.R2.outlet.flow_vol[0]) == pytest.approx(1.0675, rel=1e-4) assert pyo.value(m.fs.R2.outlet.temperature[0]) == pytest.approx(298.15, rel=1e-4) assert pyo.value(m.fs.R2.outlet.pressure[0]) == pytest.approx(101325, rel=1e-4) assert pyo.value(m.fs.R2.outlet.conc_mass_comp[0, "S_I"]) == pytest.approx( 30e-3, rel=1e-5) assert pyo.value(m.fs.R2.outlet.conc_mass_comp[0, "S_S"]) == pytest.approx( 1.46e-3, rel=1e-2) assert pyo.value(m.fs.R2.outlet.conc_mass_comp[0, "X_I"]) == pytest.approx( 1149e-3, rel=1e-3) assert pyo.value(m.fs.R2.outlet.conc_mass_comp[0, "X_S"]) == pytest.approx( 76.4e-3, rel=1e-2) assert pyo.value( m.fs.R2.outlet.conc_mass_comp[0, "X_BH"]) == pytest.approx(2553e-3, rel=1e-3) assert pyo.value( m.fs.R2.outlet.conc_mass_comp[0, "X_BA"]) == pytest.approx(148e-3, rel=1e-2) assert pyo.value(m.fs.R2.outlet.conc_mass_comp[0, "X_P"]) == pytest.approx( 449e-3, rel=1e-2) assert pyo.value(m.fs.R2.outlet.conc_mass_comp[0, "S_O"]) == pytest.approx( 6.31e-8, rel=1e-2) assert pyo.value( m.fs.R2.outlet.conc_mass_comp[0, "S_NO"]) == pytest.approx(3.65e-3, rel=1e-2) assert pyo.value( m.fs.R2.outlet.conc_mass_comp[0, "S_NH"]) == pytest.approx(8.34e-3, rel=1e-2) assert pyo.value( m.fs.R2.outlet.conc_mass_comp[0, "S_ND"]) == pytest.approx(0.882e-3, rel=1e-2) assert pyo.value( m.fs.R2.outlet.conc_mass_comp[0, "X_ND"]) == pytest.approx(5.03e-3, rel=1e-2) assert pyo.value(m.fs.R2.outlet.alkalinity[0]) == pytest.approx(5.08e-3, rel=1e-2) # Third reactor (aerobic) assert pyo.value(m.fs.R3.outlet.flow_vol[0]) == pytest.approx(1.0675, rel=1e-4) assert pyo.value(m.fs.R3.outlet.temperature[0]) == pytest.approx(298.15, rel=1e-4) assert pyo.value(m.fs.R3.outlet.pressure[0]) == pytest.approx(101325, rel=1e-4) assert pyo.value(m.fs.R3.outlet.conc_mass_comp[0, "S_I"]) == pytest.approx( 30e-3, rel=1e-5) assert pyo.value(m.fs.R3.outlet.conc_mass_comp[0, "S_S"]) == pytest.approx( 1.15e-3, rel=1e-2) assert pyo.value(m.fs.R3.outlet.conc_mass_comp[0, "X_I"]) == pytest.approx( 1149e-3, rel=1e-3) assert pyo.value(m.fs.R3.outlet.conc_mass_comp[0, "X_S"]) == pytest.approx( 64.9e-3, rel=1e-2) assert pyo.value( m.fs.R3.outlet.conc_mass_comp[0, "X_BH"]) == pytest.approx(2557e-3, rel=1e-3) assert pyo.value( m.fs.R3.outlet.conc_mass_comp[0, "X_BA"]) == pytest.approx(149e-3, rel=1e-2) assert pyo.value(m.fs.R3.outlet.conc_mass_comp[0, "X_P"]) == pytest.approx( 450e-3, rel=1e-2) assert pyo.value(m.fs.R3.outlet.conc_mass_comp[0, "S_O"]) == pytest.approx( 1.72e-3, rel=1e-2) assert pyo.value( m.fs.R3.outlet.conc_mass_comp[0, "S_NO"]) == pytest.approx(6.54e-3, rel=1e-2) assert pyo.value( m.fs.R3.outlet.conc_mass_comp[0, "S_NH"]) == pytest.approx(5.55e-3, rel=1e-2) assert pyo.value( m.fs.R3.outlet.conc_mass_comp[0, "S_ND"]) == pytest.approx(0.829e-3, rel=1e-2) assert pyo.value( m.fs.R3.outlet.conc_mass_comp[0, "X_ND"]) == pytest.approx(4.39e-3, rel=1e-2) assert pyo.value(m.fs.R3.outlet.alkalinity[0]) == pytest.approx(4.67e-3, rel=1e-2) # Fourth reactor (aerobic) assert pyo.value(m.fs.R4.outlet.flow_vol[0]) == pytest.approx(1.0675, rel=1e-4) assert pyo.value(m.fs.R4.outlet.temperature[0]) == pytest.approx(298.15, rel=1e-4) assert pyo.value(m.fs.R4.outlet.pressure[0]) == pytest.approx(101325, rel=1e-4) assert pyo.value(m.fs.R4.outlet.conc_mass_comp[0, "S_I"]) == pytest.approx( 30e-3, rel=1e-5) assert pyo.value(m.fs.R4.outlet.conc_mass_comp[0, "S_S"]) == pytest.approx( 0.995e-3, rel=1e-2) assert pyo.value(m.fs.R4.outlet.conc_mass_comp[0, "X_I"]) == pytest.approx( 1149e-3, rel=1e-3) assert pyo.value(m.fs.R4.outlet.conc_mass_comp[0, "X_S"]) == pytest.approx( 55.7e-3, rel=1e-2) assert pyo.value( m.fs.R4.outlet.conc_mass_comp[0, "X_BH"]) == pytest.approx(2559e-3, rel=1e-3) assert pyo.value( m.fs.R4.outlet.conc_mass_comp[0, "X_BA"]) == pytest.approx(150e-3, rel=1e-2) assert pyo.value(m.fs.R4.outlet.conc_mass_comp[0, "X_P"]) == pytest.approx( 451e-3, rel=1e-2) assert pyo.value(m.fs.R4.outlet.conc_mass_comp[0, "S_O"]) == pytest.approx( 2.43e-3, rel=1e-2) assert pyo.value( m.fs.R4.outlet.conc_mass_comp[0, "S_NO"]) == pytest.approx(9.30e-3, rel=1e-2) assert pyo.value( m.fs.R4.outlet.conc_mass_comp[0, "S_NH"]) == pytest.approx(2.97e-3, rel=1e-2) assert pyo.value( m.fs.R4.outlet.conc_mass_comp[0, "S_ND"]) == pytest.approx(0.767e-3, rel=1e-2) assert pyo.value( m.fs.R4.outlet.conc_mass_comp[0, "X_ND"]) == pytest.approx(3.88e-3, rel=1e-2) assert pyo.value(m.fs.R4.outlet.alkalinity[0]) == pytest.approx(4.29e-3, rel=1e-2) # Fifth reactor (aerobic) assert pyo.value(m.fs.R5.outlet.flow_vol[0]) == pytest.approx(1.0675, rel=1e-4) assert pyo.value(m.fs.R5.outlet.temperature[0]) == pytest.approx(298.15, rel=1e-4) assert pyo.value(m.fs.R5.outlet.pressure[0]) == pytest.approx(101325, rel=1e-4) assert pyo.value(m.fs.R5.outlet.conc_mass_comp[0, "S_I"]) == pytest.approx( 30e-3, rel=1e-5) assert pyo.value(m.fs.R5.outlet.conc_mass_comp[0, "S_S"]) == pytest.approx( 0.889e-3, rel=1e-2) assert pyo.value(m.fs.R5.outlet.conc_mass_comp[0, "X_I"]) == pytest.approx( 1149e-3, rel=1e-3) assert pyo.value(m.fs.R5.outlet.conc_mass_comp[0, "X_S"]) == pytest.approx( 49.3e-3, rel=1e-2) assert pyo.value( m.fs.R5.outlet.conc_mass_comp[0, "X_BH"]) == pytest.approx(2559e-3, rel=1e-3) assert pyo.value( m.fs.R5.outlet.conc_mass_comp[0, "X_BA"]) == pytest.approx(150e-3, rel=1e-2) assert pyo.value(m.fs.R5.outlet.conc_mass_comp[0, "X_P"]) == pytest.approx( 452e-3, rel=1e-2) assert pyo.value(m.fs.R5.outlet.conc_mass_comp[0, "S_O"]) == pytest.approx( 0.491e-3, rel=1e-2) assert pyo.value( m.fs.R5.outlet.conc_mass_comp[0, "S_NO"]) == pytest.approx(10.4e-3, rel=1e-2) assert pyo.value( m.fs.R5.outlet.conc_mass_comp[0, "S_NH"]) == pytest.approx(1.73e-3, rel=1e-2) assert pyo.value( m.fs.R5.outlet.conc_mass_comp[0, "S_ND"]) == pytest.approx(0.688e-3, rel=1e-2) assert pyo.value( m.fs.R5.outlet.conc_mass_comp[0, "X_ND"]) == pytest.approx(3.53e-3, rel=1e-2) assert pyo.value(m.fs.R5.outlet.alkalinity[0]) == pytest.approx(4.13e-3, rel=1e-2)
def make_model(horizon=6, ntfe=60, ntcp=2, inlet_E=11.91, inlet_S=12.92): time_set = [0, horizon] m = ConcreteModel(name='CSTR with level control') m.fs = FlowsheetBlock(default={'dynamic': True, 'time_set': time_set}) m.fs.properties = AqueousEnzymeParameterBlock() m.fs.reactions = EnzymeReactionParameterBlock( default={'property_package': m.fs.properties}) m.fs.cstr = CSTR(default={'has_holdup': True, 'property_package': m.fs.properties, 'reaction_package': m.fs.reactions, 'material_balance_type': MaterialBalanceType.componentTotal, 'energy_balance_type': EnergyBalanceType.enthalpyTotal, 'momentum_balance_type': MomentumBalanceType.none, 'has_heat_of_reaction': True}) # MomentumBalanceType.none used because the property package doesn't # include pressure. m.fs.mixer = Mixer(default={ 'property_package': m.fs.properties, 'material_balance_type': MaterialBalanceType.componentTotal, 'momentum_mixing_type': MomentumMixingType.none, # MomentumMixingType.none used because the property package doesn't # include pressure. 'num_inlets': 2, 'inlet_list': ['S_inlet', 'E_inlet']}) # Allegedly the proper energy balance is being used... # Time discretization disc = TransformationFactory('dae.collocation') disc.apply_to(m, wrt=m.fs.time, nfe=ntfe, ncp=ntcp, scheme='LAGRANGE-RADAU') m.fs.pid = PIDBlock(default={'pv': m.fs.cstr.volume, 'output': m.fs.cstr.outlet.flow_vol, 'upper': 5.0, 'lower': 0.5, 'calculate_initial_integral': True, # ^ Why would initial integral be calculated # to be nonzero? 'pid_form': PIDForm.velocity}) m.fs.pid.gain.fix(-1.0) m.fs.pid.time_i.fix(0.1) m.fs.pid.time_d.fix(0.0) m.fs.pid.setpoint.fix(1.0) # Fix initial condition for volume: m.fs.cstr.volume.unfix() m.fs.cstr.volume[m.fs.time.first()].fix(1.0) # Fix initial conditions for other variables: for p, j in m.fs.properties.phase_list*m.fs.properties.component_list: if j == 'Solvent': continue m.fs.cstr.control_volume.material_holdup[0, p, j].fix(0.001) # Note: Model does not solve when initial conditions are empty tank m.fs.cstr.control_volume.energy_holdup[m.fs.time.first(), 'aq'].fix(300) m.fs.mixer.E_inlet.conc_mol.fix(0) m.fs.mixer.S_inlet.conc_mol.fix(0) m.fs.mixer.E_inlet.conc_mol[:,'Solvent'].fix(1.) m.fs.mixer.S_inlet.conc_mol[:,'Solvent'].fix(1.) for t, j in m.fs.time*m.fs.properties.component_list: if j == 'E': m.fs.mixer.E_inlet.conc_mol[t, j].fix(inlet_E) elif j == 'S': m.fs.mixer.S_inlet.conc_mol[t, j].fix(inlet_S) m.fs.mixer.E_inlet.flow_vol.fix(0.1) m.fs.mixer.S_inlet.flow_vol.fix(2.1) # Specify a perturbation to substrate flow rate: for t in m.fs.time: if t < horizon/4: continue else: m.fs.mixer.S_inlet.flow_vol[t].fix(3.0) m.fs.mixer.E_inlet.temperature.fix(290) m.fs.mixer.S_inlet.temperature.fix(310) m.fs.inlet = Arc(source=m.fs.mixer.outlet, destination=m.fs.cstr.inlet) # Fix "initial condition" for outlet flow rate, as here it cannot be # specified by the PID controller m.fs.cstr.outlet.flow_vol[m.fs.time.first()].fix(2.2) TransformationFactory('network.expand_arcs').apply_to(m.fs) return m
def make_model(horizon=6, ntfe=60, ntcp=2, inlet_E=11.91, inlet_S=12.92, steady=False, bounds=False): time_set = [0, horizon] m = ConcreteModel(name='CSTR model for testing') if steady: m.fs = FlowsheetBlock(default={'dynamic': False}) else: m.fs = FlowsheetBlock(default={'dynamic': True, 'time_set': time_set}) m.fs.properties = AqueousEnzymeParameterBlock() m.fs.reactions = EnzymeReactionParameterBlock( default={'property_package': m.fs.properties}) m.fs.cstr = CSTR( default={ 'has_holdup': True, 'property_package': m.fs.properties, 'reaction_package': m.fs.reactions, 'material_balance_type': MaterialBalanceType.componentTotal, 'energy_balance_type': EnergyBalanceType.enthalpyTotal, 'momentum_balance_type': MomentumBalanceType.none, 'has_heat_of_reaction': True }) m.fs.mixer = Mixer( default={ 'property_package': m.fs.properties, 'material_balance_type': MaterialBalanceType.componentTotal, 'momentum_mixing_type': MomentumMixingType.none, 'num_inlets': 2, 'inlet_list': ['S_inlet', 'E_inlet'] }) # Allegedly the proper energy balance is being used... # Time discretization if not steady: disc = TransformationFactory('dae.collocation') disc.apply_to(m, wrt=m.fs.time, nfe=ntfe, ncp=ntcp, scheme='LAGRANGE-RADAU') # Fix geometry variables m.fs.cstr.volume[0].fix(1.0) # Fix initial conditions: if not steady: for p, j in m.fs.properties.phase_list * m.fs.properties.component_list: if j == 'Solvent': continue m.fs.cstr.control_volume.material_holdup[0, p, j].fix(0.001) # Note: Model does not solve when initial conditions are empty tank m.fs.mixer.E_inlet.conc_mol.fix(0) m.fs.mixer.S_inlet.conc_mol.fix(0) for t, j in m.fs.time * m.fs.properties.component_list: if j == 'E': m.fs.mixer.E_inlet.conc_mol[t, j].fix(inlet_E) elif j == 'S': m.fs.mixer.S_inlet.conc_mol[t, j].fix(inlet_S) m.fs.mixer.E_inlet.flow_vol.fix(0.1) m.fs.mixer.S_inlet.flow_vol.fix(2.1) m.fs.mixer.E_inlet.conc_mol[:, 'Solvent'].fix(1.) m.fs.mixer.S_inlet.conc_mol[:, 'Solvent'].fix(1.) m.fs.mixer.E_inlet.temperature.fix(290) m.fs.mixer.S_inlet.temperature.fix(310) m.fs.inlet = Arc(source=m.fs.mixer.outlet, destination=m.fs.cstr.inlet) # This constraint is in lieu of tracking the CSTR's level and allowing # the outlet flow rate to be another degree of freedom. # ^ Not sure how to do this in IDAES. @m.fs.cstr.Constraint(m.fs.time, doc='Total flow rate balance') def total_flow_balance(cstr, t): return (cstr.inlet.flow_vol[t] == cstr.outlet.flow_vol[t]) # Specify initial condition for energy if not steady: m.fs.cstr.control_volume.energy_holdup[m.fs.time.first(), 'aq'].fix(300) TransformationFactory('network.expand_arcs').apply_to(m.fs) if bounds: m.fs.mixer.E_inlet.flow_vol.setlb(0.01) m.fs.mixer.E_inlet.flow_vol.setub(1.0) m.fs.mixer.S_inlet.flow_vol.setlb(0.5) m.fs.mixer.S_inlet.flow_vol.setub(5.0) m.fs.cstr.control_volume.material_holdup.setlb(0) holdup = m.fs.cstr.control_volume.material_holdup for t in m.fs.time: holdup[t, 'aq', 'S'].setub(20) holdup[t, 'aq', 'E'].setub(1) holdup[t, 'aq', 'P'].setub(5) holdup[t, 'aq', 'C'].setub(5) return m
def main(): """ Make the flowsheet object, fix some variables, and solve the problem """ # Create a Concrete Model as the top level object m = ConcreteModel() # Add a flowsheet object to the model m.fs = FlowsheetBlock(default={"dynamic": False}) # Add property packages to flowsheet library m.fs.thermo_params = thermo_props.SaponificationParameterBlock() m.fs.reaction_params = reaction_props.SaponificationReactionParameterBlock( default={"property_package": m.fs.thermo_params}) # Create unit models m.fs.Tank1 = CSTR( default={ "property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params, "has_equilibrium_reactions": False, "has_heat_of_reaction": True, "has_heat_transfer": True, "has_pressure_change": False }) m.fs.Tank2 = CSTR( default={ "property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params, "has_equilibrium_reactions": False, "has_heat_of_reaction": True, "has_heat_transfer": True, "has_pressure_change": False }) # Make Streams to connect units m.fs.stream = Arc(source=m.fs.Tank1.outlet, destination=m.fs.Tank2.inlet) TransformationFactory("network.expand_arcs").apply_to(m) # Set inlet and operating conditions, and some initial conditions. m.fs.Tank1.inlet.flow_vol[0].fix(1.0) m.fs.Tank1.inlet.conc_mol_comp[0, "H2O"].fix(55388.0) m.fs.Tank1.inlet.conc_mol_comp[0, "NaOH"].fix(100.0) m.fs.Tank1.inlet.conc_mol_comp[0, "EthylAcetate"].fix(100.0) m.fs.Tank1.inlet.conc_mol_comp[0, "SodiumAcetate"].fix(0.0) m.fs.Tank1.inlet.conc_mol_comp[0, "Ethanol"].fix(0.0) m.fs.Tank1.inlet.temperature[0].fix(303.15) m.fs.Tank1.inlet.pressure[0].fix(101325.0) m.fs.Tank1.volume.fix(1.0) m.fs.Tank1.heat_duty.fix(0.0) m.fs.Tank2.volume.fix(1.0) m.fs.Tank2.heat_duty.fix(0.0) # Initialize Units m.fs.Tank1.initialize() m.fs.Tank2.initialize( state_args={ "flow_vol": 1.0, "conc_mol_comp": { "H2O": 55388.0, "NaOH": 100.0, "EthylAcetate": 100.0, "SodiumAcetate": 0.0, "Ethanol": 0.0 }, "temperature": 303.15, "pressure": 101325.0 }) # ========================================================================= # Adding Optimization m.fs.obj = Objective(expr=m.fs.Tank2.outlet.conc_mol_comp[0, "SodiumAcetate"], sense=maximize) # Add additional constraint m.fs.volume_constraint = Constraint(expr=m.fs.Tank1.volume[0] + m.fs.Tank2.volume[0] == 3.0) # Set bounds for variables m.fs.Tank1.volume[0].setlb(0.5) m.fs.Tank1.volume[0].setub(5.0) m.fs.Tank2.volume[0].setlb(0.5) m.fs.Tank2.volume[0].setub(5.0) # Unfix heat inputs m.fs.Tank1.volume.unfix() m.fs.Tank2.volume.unfix() # ========================================================================= # Create a solver solver = SolverFactory('ipopt') results = solver.solve(m, tee=True) # Print results print(results) print() print("Results") print() print("Tank 1 Volume") m.fs.Tank1.volume.display() print() print("Tank 2 Volume") m.fs.Tank2.volume.display() print() print("Tank 1 Outlet") m.fs.Tank1.outlet.display() print() print("Tank 2 Outlet") m.fs.Tank2.outlet.display() # For testing purposes return (m, results)
def main(): """ Make the flowsheet object, fix some variables, and solve the problem """ # Create a Concrete Model as the top level object m = ConcreteModel() # Add a flowsheet object to the model # time_set has points at 0 and 20 as the start and end of the domain, # and a point at t=1 to allow for a step-change at this time m.fs = FlowsheetBlock(default={"dynamic": True, "time_set": [0, 1, 20]}) # Add property packages to flowsheet library m.fs.thermo_params = thermo_props.SaponificationParameterBlock() m.fs.reaction_params = reaction_props.SaponificationReactionParameterBlock( default={"property_package": m.fs.thermo_params}) # Create unit models m.fs.mix = Mixer(default={ "dynamic": False, "property_package": m.fs.thermo_params }) m.fs.Tank1 = CSTR( default={ "property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params, "has_holdup": True, "has_equilibrium_reactions": False, "has_heat_of_reaction": True, "has_heat_transfer": True, "has_pressure_change": False }) m.fs.Tank2 = CSTR( default={ "property_package": m.fs.thermo_params, "reaction_package": m.fs.reaction_params, "has_holdup": True, "has_equilibrium_reactions": False, "has_heat_of_reaction": True, "has_heat_transfer": True, "has_pressure_change": False }) # Add pressure-flow constraints to Tank 1 m.fs.Tank1.height = Var(m.fs.time, initialize=1.0, doc="Depth of fluid in tank [m]") m.fs.Tank1.area = Var(initialize=1.0, doc="Cross-sectional area of tank [m^2]") m.fs.Tank1.flow_coeff = Var(m.fs.time, initialize=5e-5, doc="Tank outlet flow coefficient") def geometry(b, t): return b.volume[t] == b.area * b.height[t] m.fs.Tank1.geometry = Constraint(m.fs.time, rule=geometry) def outlet_flowrate(b, t): return b.control_volume.properties_out[t].flow_vol == \ b.flow_coeff[t]*b.height[t]**0.5 m.fs.Tank1.outlet_flowrate = Constraint(m.fs.time, rule=outlet_flowrate) # Add pressure-flow constraints to tank 2 m.fs.Tank2.height = Var(m.fs.time, initialize=1.0, doc="Depth of fluid in tank [m]") m.fs.Tank2.area = Var(initialize=1.0, doc="Cross-sectional area of tank [m^2]") m.fs.Tank2.flow_coeff = Var(m.fs.time, initialize=5e-5, doc="Tank outlet flow coefficient") m.fs.Tank2.geometry = Constraint(m.fs.time, rule=geometry) m.fs.Tank2.outlet_flowrate = Constraint(m.fs.time, rule=outlet_flowrate) # Make Streams to connect units m.fs.stream1 = Arc(source=m.fs.mix.outlet, destination=m.fs.Tank1.inlet) m.fs.stream2 = Arc(source=m.fs.Tank1.outlet, destination=m.fs.Tank2.inlet) # Discretize time domain m.discretizer = TransformationFactory('dae.finite_difference') m.discretizer.apply_to(m, nfe=50, wrt=m.fs.time, scheme="BACKWARD") TransformationFactory("network.expand_arcs").apply_to(m) # Set inlet and operating conditions, and some initial conditions. m.fs.mix.inlet_1.flow_vol.fix(0.5) m.fs.mix.inlet_1.conc_mol_comp[:, "H2O"].fix(55388.0) m.fs.mix.inlet_1.conc_mol_comp[:, "NaOH"].fix(100.0) m.fs.mix.inlet_1.conc_mol_comp[:, "EthylAcetate"].fix(0.0) m.fs.mix.inlet_1.conc_mol_comp[:, "SodiumAcetate"].fix(0.0) m.fs.mix.inlet_1.conc_mol_comp[:, "Ethanol"].fix(0.0) m.fs.mix.inlet_1.temperature.fix(303.15) m.fs.mix.inlet_1.pressure.fix(101325.0) m.fs.mix.inlet_2.flow_vol.fix(0.5) m.fs.mix.inlet_2.conc_mol_comp[:, "H2O"].fix(55388.0) m.fs.mix.inlet_2.conc_mol_comp[:, "NaOH"].fix(0.0) m.fs.mix.inlet_2.conc_mol_comp[:, "EthylAcetate"].fix(100.0) m.fs.mix.inlet_2.conc_mol_comp[:, "SodiumAcetate"].fix(0.0) m.fs.mix.inlet_2.conc_mol_comp[:, "Ethanol"].fix(0.0) m.fs.mix.inlet_2.temperature.fix(303.15) m.fs.mix.inlet_2.pressure.fix(101325.0) m.fs.Tank1.area.fix(1.0) m.fs.Tank1.flow_coeff.fix(0.5) m.fs.Tank1.heat_duty.fix(0.0) m.fs.Tank2.area.fix(1.0) m.fs.Tank2.flow_coeff.fix(0.5) m.fs.Tank2.heat_duty.fix(0.0) # Set initial conditions - accumulation = 0 at time = 0 m.fs.fix_initial_conditions(state="steady-state") # Initialize Units m.fs.mix.initialize() m.fs.Tank1.initialize( state_args={ "flow_vol": 1.0, "conc_mol_comp": { "H2O": 55388.0, "NaOH": 100.0, "EthylAcetate": 100.0, "SodiumAcetate": 0.0, "Ethanol": 0.0 }, "temperature": 303.15, "pressure": 101325.0 }) m.fs.Tank2.initialize( state_args={ "flow_vol": 1.0, "conc_mol_comp": { "H2O": 55388.0, "NaOH": 100.0, "EthylAcetate": 100.0, "SodiumAcetate": 0.0, "Ethanol": 0.0 }, "temperature": 303.15, "pressure": 101325.0 }) # Create a solver solver = SolverFactory('ipopt') results = solver.solve(m.fs) # Make a step disturbance in feed and solve again for t in m.fs.time: if t >= 1.0: m.fs.mix.inlet_2.conc_mol_comp[t, "EthylAcetate"].fix(90.0) results = solver.solve(m.fs) # Print results print(results) # For testing purposes return (m, results)