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