예제 #1
0
def create_model(
    steady_state=True,
    time_set=[0,3],
    nfe=5,
    calc_integ=True,
    form=PIDForm.standard
):
    """ Create a test model and solver

    Args:
        steady_state (bool): If True, create a steady state model, otherwise
            create a dynamic model
        time_set (list): The begining and end point of the time domain
        nfe (int): Number of finite elements argument for the DAE transformation
        calc_integ (bool): If Ture, calculate in the initial condition for
            the integral term, else use a fixed variable (fs.ctrl.err_i0), flase
            is the better option if you have a value from a previous time period
        form: whether the equations are written in the standard or velocity form

    Returns
        (tuple): (ConcreteModel, Solver)
    """
    if steady_state:
        fs_cfg = {"dynamic":False}
        model_name = "Steam Tank, Steady State"
    else:
        fs_cfg = {"dynamic":True, "time_set":time_set}
        model_name = "Steam Tank, Dynamic"

    m = pyo.ConcreteModel(name=model_name)
    m.fs = FlowsheetBlock(default=fs_cfg)
    # Create a property parameter block
    m.fs.prop_water = iapws95.Iapws95ParameterBlock(
        default={"phase_presentation":iapws95.PhaseType.LG})
    # Create the valve and tank models
    m.fs.valve_1 = SteamValve(default={
        "dynamic":False,
        "has_holdup":False,
        "material_balance_type":MaterialBalanceType.componentTotal,
        "property_package":m.fs.prop_water})
    m.fs.tank = Heater(default={
        "has_holdup":True,
        "material_balance_type":MaterialBalanceType.componentTotal,
        "property_package":m.fs.prop_water})
    m.fs.valve_2 = SteamValve(default={
        "dynamic":False,
        "has_holdup":False,
        "material_balance_type":MaterialBalanceType.componentTotal,
        "property_package":m.fs.prop_water})

    # Connect the models
    m.fs.v1_to_t = Arc(source=m.fs.valve_1.outlet, destination=m.fs.tank.inlet)
    m.fs.t_to_v2 = Arc(source=m.fs.tank.outlet, destination=m.fs.valve_2.inlet)

    # The control volume block doesn't assume the two phases are in equilibrium
    # by default, so I'll make that assumption here, I don't actually expect
    # liquid to form but who knows. The phase_fraction in the control volume is
    # volumetric phase fraction hence the densities.
    @m.fs.tank.Constraint(m.fs.time)
    def vol_frac_vap(b, t):
        return b.control_volume.properties_out[t].phase_frac["Vap"]\
            *b.control_volume.properties_out[t].dens_mol\
            /b.control_volume.properties_out[t].dens_mol_phase["Vap"]\
            == b.control_volume.phase_fraction[t, "Vap"]

    # Add the stream constraints and do the DAE transformation
    pyo.TransformationFactory('network.expand_arcs').apply_to(m.fs)
    if not steady_state:
        pyo.TransformationFactory('dae.finite_difference').apply_to(
            m.fs, nfe=nfe, wrt=m.fs.time, scheme='BACKWARD')

    # Fix the derivative variables to zero at time 0 (steady state assumption)
    m.fs.fix_initial_conditions()

    # A tank pressure reference that's directly time-indexed
    m.fs.tank_pressure = pyo.Reference(
        m.fs.tank.control_volume.properties_out[:].pressure)

    # Add a controller
    m.fs.ctrl = PIDBlock(default={"pv":m.fs.tank_pressure,
                                  "output":m.fs.valve_1.valve_opening,
                                  "upper":1.0,
                                  "lower":0.0,
                                  "calculate_initial_integral":calc_integ,
                                  "pid_form":form})
    m.fs.ctrl.deactivate() # Don't want controller turned on by default

    # Fix the input variables
    m.fs.valve_1.inlet.enth_mol.fix(50000)
    m.fs.valve_1.inlet.pressure.fix(5e5)
    m.fs.valve_2.outlet.pressure.fix(101325)
    m.fs.valve_1.Cv.fix(0.001)
    m.fs.valve_2.Cv.fix(0.001)
    m.fs.valve_1.valve_opening.fix(1)
    m.fs.valve_2.valve_opening.fix(1)
    m.fs.tank.heat_duty.fix(0)
    m.fs.tank.control_volume.volume.fix(2.0)
    m.fs.ctrl.gain.fix(1e-6)
    m.fs.ctrl.time_i.fix(0.1)
    m.fs.ctrl.time_d.fix(0.1)
    m.fs.ctrl.setpoint.fix(3e5)

    # Initialize the model
    solver = pyo.SolverFactory("ipopt")
    solver.options = {'tol': 1e-6,
                      'linear_solver': "ma27",
                      'max_iter': 100}
    for t in m.fs.time:
        m.fs.valve_1.inlet.flow_mol = 100 # initial guess on flow
    # simple initialize
    m.fs.valve_1.initialize(outlvl=1)
    _set_port(m.fs.tank.inlet, m.fs.valve_1.outlet)
    m.fs.tank.initialize(outlvl=1)
    _set_port(m.fs.valve_2.inlet, m.fs.tank.outlet)
    m.fs.valve_2.initialize(outlvl=1)
    solver.solve(m, tee=True)

    # Return the model and solver
    return m, solver
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