Example #1
0
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
Example #2
0
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
Example #5
0
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)
Example #6
0
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
Example #8
0
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
Example #9
0
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)