def main(): m_ss = get_model(dynamic=False) m_dyn = get_model(dynamic=True) copy_non_time_indexed_values(m_dyn.fs, m_ss.fs, copy_fixed=True) for t in m_dyn.fs.config.time: copy_values_at_time(m_dyn.fs, m_ss.fs, t, 0.0, copy_fixed=True) m_dyn.fs.controller.mv_ref.value = m_dyn.fs.valve.valve_opening[0].value # calculate integral error assuming error is zero m_dyn.fs.controller.integral_of_error[:].value = 0 dof = degrees_of_freedom(m_dyn) assert dof == 0 run_dynamic(m_dyn) return m_dyn
def initialize_by_time_element(fs, time, **kwargs): """ Function to initialize Flowsheet fs element-by-element along ContinuousSet time. Assumes sufficient initialization/correct degrees of freedom such that the first finite element can be solved immediately and each subsequent finite element can be solved by fixing differential and derivative variables at the initial time point of that finite element. Args: fs : Flowsheet to initialize time : Set whose elements will be solved for individually solver : Pyomo solver object initialized with user's desired options outlvl : IDAES logger outlvl ignore_dof : Bool. If True, checks for square problems will be skipped. Returns: None """ if not isinstance(fs, FlowsheetBlock): raise TypeError('First arg must be a FlowsheetBlock') if not isinstance(time, ContinuousSet): raise TypeError('Second arg must be a ContinuousSet') if time.get_discretization_info() == {}: raise ValueError('ContinuousSet must be discretized') scheme = time.get_discretization_info()['scheme'] fep_list = time.get_finite_elements() nfe = time.get_discretization_info()['nfe'] if scheme == 'LAGRANGE-RADAU': ncp = time.get_discretization_info()['ncp'] elif scheme == 'LAGRANGE-LEGENDRE': msg = 'Initialization does not support collocation with Legendre roots' raise NotImplementedError(msg) elif scheme == 'BACKWARD Difference': ncp = 1 elif scheme == 'FORWARD Difference': ncp = 1 msg = 'Forward initialization (explicit Euler) has not yet been implemented' raise NotImplementedError(msg) elif scheme == 'CENTRAL Difference': msg = 'Initialization does not support central finite difference' raise NotImplementedError(msg) else: msg = 'Unrecognized discretization scheme. ' 'Has the model been discretized along the provided ContinuousSet?' raise ValueError(msg) # Disallow Central/Legendre discretizations. # Neither of these seem to be square by default for multi-finite element # initial value problems. # Create logger objects outlvl = kwargs.pop('outlvl', idaeslog.NOTSET) init_log = idaeslog.getInitLogger(__name__, level=outlvl) solver_log = idaeslog.getSolveLogger(__name__, level=outlvl) ignore_dof = kwargs.pop('ignore_dof', False) solver = kwargs.pop('solver', get_solver()) fix_diff_only = kwargs.pop('fix_diff_only', True) # This option makes the assumption that the only variables that # link constraints to previous points in time (which must be fixed) # are the derivatives and differential variables. Not true if a controller # is being present, but should be a good assumption otherwise, and is # significantly faster than searching each constraint for time-linking # variables. if not ignore_dof: if degrees_of_freedom(fs) != 0: msg = ('Original model has nonzero degrees of freedom. This was ' 'unexpected. Use keyword arg igore_dof=True to skip this ' 'check.') init_log.error(msg) raise ValueError('Nonzero degrees of freedom.') # Get dict telling which constraints/blocks are already inactive: # dict: id(compdata) -> bool (is active?) was_originally_active = get_activity_dict(fs) # Deactivate flowsheet except at t0, solve to ensure consistency # of initial conditions. non_initial_time = [t for t in time] non_initial_time.remove(time.first()) deactivated = deactivate_model_at(fs, time, non_initial_time, outlvl=idaeslog.ERROR) if not ignore_dof: if degrees_of_freedom(fs) != 0: msg = ( 'Model has nonzero degrees of freedom at initial conditions.' ' This was unexpected. Use keyword arg igore_dof=True to skip' ' this check.') init_log.error(msg) raise ValueError('Nonzero degrees of freedom.') init_log.info( 'Model is inactive except at t=0. Solving for consistent initial conditions.' ) with idaeslog.solver_log(solver_log, level=idaeslog.DEBUG) as slc: results = solver.solve(fs, tee=slc.tee) if results.solver.termination_condition == TerminationCondition.optimal: init_log.info('Successfully solved for consistent initial conditions') else: init_log.error('Failed to solve for consistent initial conditions') raise ValueError('Solver failed in initialization') deactivated[time.first()] = deactivate_model_at( fs, time, time.first(), outlvl=idaeslog.ERROR)[time.first()] # Here, deactivate non-time-indexed components. Do this after solve # for initial conditions in case these were used to specify initial # conditions con_unindexed_by_time = deactivate_constraints_unindexed_by(fs, time) var_unindexed_by_time = fix_vars_unindexed_by(fs, time) # Now model is completely inactive # For each timestep, we need to # 1. Activate model at points we're solving for # 2. Fix initial conditions (differential variables at previous timestep) # of finite element # 3. Solve the (now) square system # 4. Revert the model to its prior state # This will make use of the following dictionaries mapping # time points -> time derivatives and time-differential variables derivs_at_time = get_derivatives_at(fs, time, [t for t in time]) dvars_at_time = { t: [ d.parent_component().get_state_var()[d.index()] for d in derivs_at_time[t] ] for t in time } # Perform a solve for 1 -> nfe; i is the index of the finite element init_log.info( 'Flowsheet has been deactivated. Beginning element-wise initialization' ) for i in range(1, nfe + 1): t_prev = time[(i - 1) * ncp + 1] # Non-initial time points in the finite element: fe = [time[k] for k in range((i - 1) * ncp + 2, i * ncp + 2)] init_log.info(f'Entering step {i}/{nfe} of initialization') # Activate components of model that were active in the presumably # square original system for t in fe: for comp in deactivated[t]: if was_originally_active[id(comp)]: comp.activate() # Get lists of derivative and differential variables # at initial time point of finite element init_deriv_list = derivs_at_time[t_prev] init_dvar_list = dvars_at_time[t_prev] # Variables that were originally fixed fixed_vars = [] if fix_diff_only: for drv in init_deriv_list: # Cannot fix variables with value None. # Any variable with value None was not solved for # (either stale or not included in previous solve) # and we don't want to fix it. if not drv.fixed: fixed_vars.append(drv) if not drv.value is None: drv.fix() for dv in init_dvar_list: if not dv.fixed: fixed_vars.append(dv) if not dv.value is None: dv.fix() else: for con in fs.component_data_objects(Constraint, active=True): for var in identify_variables(con.expr, include_fixed=False): t_idx = get_implicit_index_of_set(var, time) if t_idx is None: continue if t_idx <= t_prev: fixed_vars.append(var) var.fix() # Initialize finite element from its initial conditions for t in fe: copy_values_at_time(fs, fs, t, t_prev, copy_fixed=False, outlvl=idaeslog.ERROR) # Log that we are solving finite element {i} init_log.info(f'Solving finite element {i}') if not ignore_dof: if degrees_of_freedom(fs) != 0: msg = ( f'Model has nonzero degrees of freedom at finite element' ' {i}. This was unexpected. ' 'Use keyword arg igore_dof=True to skip this check.') init_log.error(msg) raise ValueError('Nonzero degrees of freedom') with idaeslog.solver_log(solver_log, level=idaeslog.DEBUG) as slc: results = solver.solve(fs, tee=slc.tee) if results.solver.termination_condition == TerminationCondition.optimal: init_log.info(f'Successfully solved finite element {i}') else: init_log.error(f'Failed to solve finite element {i}') raise ValueError('Failure in initialization solve') # Deactivate components that may have been activated for t in fe: for comp in deactivated[t]: comp.deactivate() # Unfix variables that have been fixed for var in fixed_vars: var.unfix() # Log that initialization step {i} has been finished init_log.info(f'Initialization step {i} complete') # Reactivate components of the model that were originally active for t in time: for comp in deactivated[t]: if was_originally_active[id(comp)]: comp.activate() for con in con_unindexed_by_time: con.activate() for var in var_unindexed_by_time: var.unfix() # Logger message that initialization is finished init_log.info('Initialization completed. Model has been reactivated')
def main_dyn(): start_time = wall_clock.time() # declare dictionary for the data to plot plot_data = {} plot_data['time'] = [] plot_data['coal_flow'] = [] plot_data['bfpt_opening'] = [] plot_data['gross_power'] = [] plot_data['ww_heat'] = [] plot_data['fegt'] = [] plot_data['drum_level'] = [] plot_data['feed_water_flow_sp'] = [] plot_data['drum_master_ctrl_op'] = [] plot_data['feed_water_flow'] = [] plot_data['spray_flow'] = [] plot_data['main_steam_flow'] = [] plot_data['rh_steam_flow'] = [] plot_data['bfpt_flow'] = [] plot_data['main_steam_temp'] = [] plot_data['rh_steam_temp'] = [] plot_data['fw_pres'] = [] plot_data['drum_pres'] = [] plot_data['main_steam_pres'] = [] plot_data['rh_steam_pres'] = [] plot_data['hw_tank_level'] = [] plot_data['da_tank_level'] = [] plot_data['fwh2_level'] = [] plot_data['fwh3_level'] = [] plot_data['fwh5_level'] = [] plot_data['fwh6_level'] = [] plot_data['makeup_valve_opening'] = [] plot_data['cond_valve_opening'] = [] plot_data['fwh2_valve_opening'] = [] plot_data['fwh3_valve_opening'] = [] plot_data['fwh5_valve_opening'] = [] plot_data['fwh6_valve_opening'] = [] plot_data['spray_valve_opening'] = [] plot_data['tube_temp_rh2'] = [] plot_data['t_fg_econ_exit'] = [] plot_data['t_fg_aph_exit'] = [] plot_data['throttle_opening'] = [] plot_data['load_demand'] = [] plot_data['sliding_pressure'] = [] plot_data['steam_pressure_sp'] = [] plot_data['gross_heat_rate'] = [] plot_data['deaerator_pressure'] = [] plot_data['SR'] = [] plot_data['temp_econ_in'] = [] plot_data['temp_econ_out'] = [] plot_data['temp_econ_out_sat'] = [] #plot_data['boiler_efficiency_heat'] = [] #plot_data['boiler_efficiency_steam'] = [] # steady-state model m_ss = get_model(dynamic=False) num_step = [2, 2, 2] step_size = [30, 60, 180] # 1st dynamic model with smallest time step size m_dyn0 = get_model(dynamic=True, time_set=[0, num_step[0]*step_size[0]], nstep=num_step[0]) # 2nd dynamic model with intermediate time step size m_dyn1 = get_model(dynamic=True, time_set=[0, num_step[1]*step_size[1]], nstep=num_step[1]) # 3rd dynamic model with largest time size m_dyn2 = get_model(dynamic=True, time_set=[0, num_step[2]*step_size[2]], nstep=num_step[2]) # model type list, user input to specify the time duration itype_list = [] for i in range(31): # to 1860 sec itype_list.append(0) # number of periods for i in range(20): # to 4260 sec itype_list.append(1) for i in range(30): # to 6060 sec itype_list.append(0) nperiod = len(itype_list) tstart = [] model_list = [] t = 0 for i in range(nperiod): tstart.append(t) t += step_size[itype_list[i]]*num_step[itype_list[i]] if itype_list[i]==0: model_list.append(m_dyn0) elif itype_list[i]==1: model_list.append(m_dyn1) else: model_list.append(m_dyn2) # start first model m_dyn = model_list[0] copy_non_time_indexed_values(m_dyn.fs_main, m_ss.fs_main, copy_fixed=True, outlvl=idaeslog.ERROR) for t in m_dyn.fs_main.config.time: copy_values_at_time(m_dyn.fs_main, m_ss.fs_main, t, 0.0, copy_fixed=True, outlvl=idaeslog.ERROR) t0 = m_dyn.fs_main.config.time.first() # reset bias of controller to current steady-state value, this makes both error and integral error to zero m_dyn.fs_main.fs_stc.fwh2_ctrl.mv_ref.value = m_dyn.fs_main.fs_stc.fwh2_valve.valve_opening[t0].value m_dyn.fs_main.fs_stc.fwh3_ctrl.mv_ref.value = m_dyn.fs_main.fs_stc.fwh3_valve.valve_opening[t0].value m_dyn.fs_main.fs_stc.fwh5_ctrl.mv_ref.value = m_dyn.fs_main.fs_stc.fwh5_valve.valve_opening[t0].value m_dyn.fs_main.fs_stc.fwh6_ctrl.mv_ref.value = m_dyn.fs_main.fs_stc.fwh6_valve.valve_opening[t0].value m_dyn.fs_main.fs_stc.makeup_ctrl.mv_ref.value = m_dyn.fs_main.fs_stc.makeup_valve.valve_opening[t0].value m_dyn.fs_main.fs_stc.da_ctrl.mv_ref.value = m_dyn.fs_main.fs_stc.cond_valve.valve_opening[t0].value m_dyn.fs_main.fs_stc.spray_ctrl.mv_ref.value = m_dyn.fs_main.fs_stc.spray_valve.valve_opening[t0].value opening = m_dyn.fs_main.fs_stc.spray_valve.valve_opening[t0].value m_dyn.fs_main.fs_stc.spray_ctrl.integral_of_error[:].value = pyo.value(m_dyn.fs_main.fs_stc.spray_ctrl.integral_of_error_ref[t0]) opening = m_dyn.fs_main.fs_stc.makeup_valve.valve_opening[t0].value m_dyn.fs_main.fs_stc.makeup_ctrl.integral_of_error[:].value = pyo.value(m_dyn.fs_main.fs_stc.makeup_ctrl.integral_of_error_ref[t0]) m_dyn.fs_main.fs_blr.aDrum.level[t0].value = 0.889 m_dyn.fs_main.flow_level_ctrl_output[t0].value = m_dyn.fs_main.fs_stc.bfp.outlet.flow_mol[t0].value m_dyn.fs_main.drum_slave_ctrl.mv_ref.value = m_dyn.fs_main.fs_stc.bfp_turb_valve.valve_opening[t0].value m_dyn.fs_main.fs_blr.aBoiler.flowrate_coal_raw[:].value = m_ss.fs_main.fs_blr.aBoiler.flowrate_coal_raw[t0].value m_dyn.fs_main.main_steam_pressure[:].value = m_ss.fs_main.fs_blr.aPlaten.outlet.pressure[t0].value/1e6 m_dyn.fs_main.turbine_master_ctrl.mv_ref.value = m_ss.fs_main.fs_stc.turb.throttle_valve[1].valve_opening[t0].value m_dyn.fs_main.turbine_master_ctrl.setpoint[:].value = m_ss.fs_main.fs_stc.power_output[t0].value m_dyn.fs_main.boiler_master_ctrl.mv_ref.value = m_ss.fs_main.fs_blr.aBoiler.flowrate_coal_raw[t0].value m_dyn.fs_main.boiler_master_ctrl.setpoint[:].value = m_ss.fs_main.fs_blr.aPlaten.outlet.pressure[t0].value/1e6 print('boiler_master_setpoint=', m_dyn.fs_main.boiler_master_ctrl.setpoint[0].value) print('sliding pressure=', pyo.value(m_dyn.fs_main.sliding_pressure[0])) solver = pyo.SolverFactory("ipopt") solver.options = { "tol": 1e-7, "linear_solver": "ma27", "max_iter": 20, } # copy non-time-indexed variables to all dynamic models if itype_list[0]==0: copy_non_time_indexed_values(m_dyn1.fs_main, m_dyn.fs_main, copy_fixed=True, outlvl=idaeslog.ERROR) copy_non_time_indexed_values(m_dyn2.fs_main, m_dyn.fs_main, copy_fixed=True, outlvl=idaeslog.ERROR) elif itype_list[0]==1: copy_non_time_indexed_values(m_dyn0.fs_main, m_dyn.fs_main, copy_fixed=True, outlvl=idaeslog.ERROR) copy_non_time_indexed_values(m_dyn2.fs_main, m_dyn.fs_main, copy_fixed=True, outlvl=idaeslog.ERROR) else: copy_non_time_indexed_values(m_dyn0.fs_main, m_dyn.fs_main, copy_fixed=True, outlvl=idaeslog.ERROR) copy_non_time_indexed_values(m_dyn1.fs_main, m_dyn.fs_main, copy_fixed=True, outlvl=idaeslog.ERROR) dof = degrees_of_freedom(m_dyn) print('dof of full model', dof) # solving dynamic model at steady-state, this step could be skipped if all setup is good print('Solving dynamic model at steady-state...') results = solver.solve(m_dyn, tee=True) # start 1st simulation print("Solving for period number 0 from ", tstart[0], " sec.") ss_value = m_ss.fs_main.fs_stc.power_output[0].value m_dyn = run_dynamic(m_dyn, ss_value, tstart[0], plot_data, solver) # loop for remaining periods tlast = m_dyn.fs_main.config.time.last() m_prev = m_dyn for i in range(1,nperiod): m_dyn = model_list[i] for t in m_dyn.fs_main.config.time: if itype_list[i]!=itype_list[i-1] or t!=tlast: copy_values_at_time(m_dyn.fs_main, m_prev.fs_main, t, tlast, copy_fixed=True, outlvl=idaeslog.ERROR) print('windup=', m_dyn.fs_main.fs_stc.spray_ctrl.integral_of_error[t].value) p_only_with_ref = pyo.value(m_dyn.fs_main.fs_stc.spray_ctrl.mv_p_only_with_ref[t]) if p_only_with_ref<pyo.value(m_dyn.fs_main.fs_stc.spray_ctrl.mv_lb): m_dyn.fs_main.fs_stc.spray_ctrl.integral_of_error[t].value = 0 print('reachig lower bound. windup reset to 0') if p_only_with_ref>pyo.value(m_dyn.fs_main.fs_stc.spray_ctrl.mv_ub): m_dyn.fs_main.fs_stc.spray_ctrl.integral_of_error[t].value = 0 print('reachig upper bound. windup reset to 0') print("Solving for period number ", i, "from ", tstart[i], " sec.") m_dyn = run_dynamic(m_dyn, ss_value, tstart[i], plot_data, solver) tlast = m_dyn.fs_main.config.time.last() m_prev = m_dyn end_time = wall_clock.time() time_used = end_time - start_time print("simulation time=", time_used) write_data_to_txt_file(plot_data) plot_results(plot_data) return m_dyn