def _setup_MPC_solver(self, file_path, opt_name, dt, horizon, n_e, par_values, constr_viol_costs={}, mpc_options={}): op = transfer_optimization_problem( opt_name, file_path, compiler_options={'state_initial_equations': True}) op.set(par_values.keys(), par_values.values()) opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['n_cp'] = 2 opt_opts['IPOPT_options']['tol'] = 1e-10 opt_opts['IPOPT_options']['print_time'] = False if 'IPOPT_options' in mpc_options: opt_opts['IPOPT_options'].update(mpc_options['IPOPT_options']) for key in mpc_options: if key != 'IPOPT_options': opt_opts[key] = mpc_options[key] self.solver = MPC(op, opt_opts, dt, horizon, constr_viol_costs=constr_viol_costs)
def test_get_results_this_sample(self): """ Test that get_results_this_sample returns the optimization result for this optimization. """ op = transfer_to_casadi_interface( "CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={"state_initial_equations": True}) op.set('_start_c', float(self.c_0_A)) op.set('_start_T', float(self.T_0_A)) # Set options collocation n_e = 50 opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['IPOPT_options']['print_level'] = 0 # Define some MPC-options sample_period = 3 horizon = 50 seed = 7 cvc = {'T': 1e6} # Define blocking factors bl_list = [1] * horizon factors = {'Tc': bl_list} bf = BlockingFactors(factors=factors) opt_opts['blocking_factors'] = bf # Create MPC-object MPC_object = MPC(op, opt_opts, sample_period, horizon, constr_viol_costs=cvc, noise_seed=seed) MPC_object.update_state() u_k1 = MPC_object.sample() result1 = MPC_object.get_results_this_sample() N.testing.assert_equal(0, result1['time'][0]) N.testing.assert_equal(sample_period * horizon, result1['time'][-1]) MPC_object.update_state() u_k2 = MPC_object.sample() result2 = MPC_object.get_results_this_sample() N.testing.assert_equal(sample_period, result2['time'][0]) N.testing.assert_equal(sample_period * (horizon + 1), result2['time'][-1])
def test_infeasible_start(self): """ Test that the MPC class throws an exception if the first optimization is unsuccessful. """ op = transfer_to_casadi_interface( "CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={"state_initial_equations": True}) # Set options collocation n_e = 50 opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['IPOPT_options']['print_level'] = 0 # Define some MPC-options sample_period = 3 horizon = 50 cvc = {'T': 1e6} # Define blocking factors bl_list = [1] * horizon factors = {'Tc': bl_list} bf = BlockingFactors(factors=factors) opt_opts['blocking_factors'] = bf # Create MPC-object MPC_object = MPC(op, opt_opts, sample_period, horizon, constr_viol_costs=cvc) # Test with infeasible problem MPC_object.update_state({'_start_c': 900, '_start_T': 700}) N.testing.assert_raises(Exception, MPC_object.sample)
def test_warm_start_options(self): """ Test that the warm start options are activated. """ op = transfer_to_casadi_interface( "CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={"state_initial_equations": True}) op.set('_start_c', float(self.c_0_A)) op.set('_start_T', float(self.T_0_A)) # Set options collocation n_e = 50 opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['IPOPT_options']['print_level'] = 0 # Define some MPC-options sample_period = 3 horizon = 50 cvc = {'T': 1e6} # Create MPC-object MPC_object = MPC(op, opt_opts, sample_period, horizon, initial_guess='trajectory', constr_viol_costs=cvc) MPC_object.update_state({ '_start_c': 587.47543496, '_start_T': 345.64619542 }) u_k1 = MPC_object.sample() MPC_object.update_state() u_k2 = MPC_object.sample() N.testing.assert_(MPC_object.collocator.warm_start) wsip =\ MPC_object.collocator.solver_object.getOption('warm_start_init_point') mu_init = MPC_object.collocator.solver_object.getOption('mu_init') prl = MPC_object.collocator.solver_object.getOption('print_level') N.testing.assert_(wsip == 'yes') N.testing.assert_equal(mu_init, 1e-3) N.testing.assert_equal(prl, 0)
def test_du_bounds(self): """ Test with blocking_factor du_bounds. """ op = transfer_to_casadi_interface( "CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={"state_initial_equations": True}) op.set('_start_c', float(self.c_0_A)) op.set('_start_T', float(self.T_0_A)) # Set options collocation n_e = 50 opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['IPOPT_options']['print_level'] = 0 # Define some MPC-options sample_period = 3 horizon = 50 seed = 7 # Define blocking factors bl_list = [1] * horizon factors = {'Tc': bl_list} du_bounds = {'Tc': 5} bf = BlockingFactors(factors=factors, du_bounds=du_bounds) opt_opts['blocking_factors'] = bf # Create MPC-object MPC_object = MPC(op, opt_opts, sample_period, horizon, noise_seed=seed, initial_guess='trajectory') MPC_object.update_state() u_k1 = MPC_object.sample() res = MPC_object.get_results_this_sample() Tc = res['Tc'] prev_value = Tc[0] largest_delta = 0 for value in Tc: delta = value - prev_value if delta > largest_delta: largest_delta = delta prev_value = value assert largest_delta < 5, "Value {} is not less than {}".format( largest_delta, 5)
def test_softening_bounds(self): """ Test the automatic softening of hard variable bounds. """ op = transfer_to_casadi_interface( "CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={"state_initial_equations": True}) # Set options collocation n_e = 50 opt_opts = op.optimize_options() opt_opts['n_e'] = n_e # Define some MPC-options sample_period = 3 horizon = 50 seed = 7 # Define blocking factors bl_list = [1] * horizon factors = {'Tc': bl_list} bf = BlockingFactors(factors=factors) opt_opts['blocking_factors'] = bf opt_opts['IPOPT_options']['print_level'] = 0 cvc = {'T': 1e6} originalPathConstraints = op.getPathConstraints() # Create MPC-object MPC_object = MPC(op, opt_opts, sample_period, horizon, constr_viol_costs=cvc, noise_seed=seed) # Assert that an optimization with an initial value outside of bounds # succeeds MPC_object.update_state({'_start_c': self.c_0_A, '_start_T': 355}) MPC_object.sample() N.testing.assert_( 'Solve_Succeeded', MPC_object.collocator.solver_object.getStat('return_status'))
def run_demo(with_plots=True): """ This example is based on the Hicks-Ray Continuously Stirred Tank Reactors (CSTR) system. The system has two states, the concentration and the temperature. The control input to the system is the temperature of the cooling flow in the reactor jacket. The chemical reaction in the reactor is exothermic, and also temperature dependent; high temperature results in high reaction rate. The problem is solved using the CasADi-based collocation algorithm through the MPC-class. FMI is used for initialization and simulation purposes. The following steps are demonstrated in this example: 1. How to generate an initial guess for a direct collocation method by means of simulation with a constant input. The trajectories resulting from the simulation are used to initialize the variables in the transcribed NLP, in the first sample(optimization). 2. An optimal control problem is defined where the objective is to transfer the state of the system from stationary point A to point B. An MPC object for the optimization problem is created. After each sample the NLP is updated with an estimate of the states in the next sample. The estimate is done by simulating the model for one sample period with the optimal input calculated in the optimization as input. To each estimate a normally distributed noise, with the mean 0 and standard deviation 0.5% of the nominal value of each state, is added. The MPC object uses the result from the previous optimization as initial guess for the next optimization (for all but the first optimization, where the simulation result from #1 is used instead). (3.) If with_plots is True we compile the same optimization problem again and define the options so that the op has the same options and resolution as the op we solved through the MPC-class. By same resolution we mean that both op should have the same mesh and blocking factors. This allows us to compare the MPC-results to an open loop optimization. Note that the MPC-results contains noise while the open loop optimization does not. """ ### 1. Compute initial guess trajectories by means of simulation # Locate the Modelica and Optimica code file_path = os.path.join(get_files_path(), "CSTR.mop") # Compile and load the model used for simulation sim_fmu = compile_fmu("CSTR.CSTR_MPC_Model", file_path, compiler_options={"state_initial_equations":True}) sim_model = load_fmu(sim_fmu) # Define stationary point A and set initial values and inputs c_0_A = 956.271352 T_0_A = 250.051971 sim_model.set('_start_c', c_0_A) sim_model.set('_start_T', T_0_A) sim_model.set('Tc', 280) opts = sim_model.simulate_options() opts["CVode_options"]["maxh"] = 0.0 opts["ncp"] = 0 init_res = sim_model.simulate(start_time=0., final_time=150, options=opts) ### 2. Define the optimal control problem and solve it using the MPC class # Compile and load optimization problem op = transfer_optimization_problem("CSTR.CSTR_MPC", file_path, compiler_options={"state_initial_equations":True}) # Define MPC options sample_period = 3 # s horizon = 33 # Samples on the horizon n_e_per_sample = 1 # Collocation elements / sample n_e = n_e_per_sample*horizon # Total collocation elements finalTime = 150 # s number_samp_tot = int(finalTime/sample_period) # Total number of samples to do # Create blocking factors with quadratic penalty and bound on 'Tc' bf_list = [n_e_per_sample]*(horizon/n_e_per_sample) factors = {'Tc': bf_list} du_quad_pen = {'Tc': 500} du_bounds = {'Tc': 30} bf = BlockingFactors(factors, du_bounds, du_quad_pen) # Set collocation options opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['n_cp'] = 2 opt_opts['init_traj'] = init_res opt_opts['blocking_factors'] = bf if with_plots: # Compile and load a new instance of the op to compare the MPC results # with an open loop optimization op_open_loop = transfer_optimization_problem( "CSTR.CSTR_MPC", file_path, compiler_options={"state_initial_equations":True}) op_open_loop.set('_start_c', float(c_0_A)) op_open_loop.set('_start_T', float(T_0_A)) # Copy options from MPC optimization open_loop_opts = copy.deepcopy(opt_opts) # Change n_e and blocking_factors so op_open_loop gets the same # resolution as op open_loop_opts['n_e'] = number_samp_tot bf_list_ol = [n_e_per_sample]*(number_samp_tot/n_e_per_sample) factors_ol = {'Tc': bf_list_ol} bf_ol = BlockingFactors(factors_ol, du_bounds, du_quad_pen) open_loop_opts['blocking_factors'] = bf_ol open_loop_opts['IPOPT_options']['print_level'] = 0 constr_viol_costs = {'T': 1e6} # Create the MPC object MPC_object = MPC(op, opt_opts, sample_period, horizon, constr_viol_costs=constr_viol_costs, noise_seed=1) # Set initial state x_k = {'_start_c': c_0_A, '_start_T': T_0_A } # Update the state and optimize number_samp_tot times for k in range(number_samp_tot): # Update the state and compute the optimal input for next sample period MPC_object.update_state(x_k) u_k = MPC_object.sample() # Reset the model and set the new initial states before simulating # the next sample period with the optimal input u_k sim_model.reset() sim_model.set(list(x_k.keys()), list(x_k.values())) sim_res = sim_model.simulate(start_time=k*sample_period, final_time=(k+1)*sample_period, input=u_k, options=opts) # Extract state at end of sample_period from sim_res and add Gaussian # noise with mean 0 and standard deviation 0.005*(state_current_value) x_k = MPC_object.extract_states(sim_res, mean=0, st_dev=0.005) # Extract variable profiles MPC_object.print_solver_stats() complete_result = MPC_object.get_complete_results() c_res_comp = complete_result['c'] T_res_comp = complete_result['T'] Tc_res_comp = complete_result['Tc'] time_res_comp = complete_result['time'] # Verify solution for testing purposes try: import casadi except: pass else: Tc_norm = N.linalg.norm(Tc_res_comp) / N.sqrt(len(Tc_res_comp)) assert(N.abs(Tc_norm - 311.7362) < 1e-3) c_norm = N.linalg.norm(c_res_comp) / N.sqrt(len(c_res_comp)) assert(N.abs(c_norm - 653.5369) < 1e-3) T_norm = N.linalg.norm(T_res_comp) / N.sqrt(len(T_res_comp)) assert(N.abs(T_norm - 328.0852) < 1e-3) # Plot the results if with_plots: ### 3. Solve the original optimal control problem without MPC res = op_open_loop.optimize(options=open_loop_opts) c_res = res['c'] T_res = res['T'] Tc_res = res['Tc'] time_res = res['time'] # Get reference values Tc_ref = op.get('Tc_ref') T_ref = op.get('T_ref') c_ref = op.get('c_ref') # Plot plt.close('MPC') plt.figure('MPC') plt.subplot(3, 1, 1) plt.plot(time_res_comp, c_res_comp) plt.plot(time_res, c_res ) plt.plot([time_res[0],time_res[-1]],[c_ref,c_ref],'--') plt.legend(('MPC with noise', 'Open-loop without noise', 'Reference value')) plt.grid() plt.ylabel('Concentration') plt.title('Simulated trajectories') plt.subplot(3, 1, 2) plt.plot(time_res_comp, T_res_comp) plt.plot(time_res, T_res) plt.plot([time_res[0],time_res[-1]],[T_ref,T_ref], '--') plt.grid() plt.ylabel('Temperature [C]') plt.subplot(3, 1, 3) plt.step(time_res_comp, Tc_res_comp) plt.step(time_res, Tc_res) plt.plot([time_res[0],time_res[-1]],[Tc_ref,Tc_ref], '--') plt.grid() plt.ylabel('Cooling temperature [C]') plt.xlabel('time') plt.show()
def test_eliminated_variables(self): """ Test that the results when using eliminated variables are the same as when not using them. """ # Compile and load the model used for simulation sim_fmu = compile_fmu( "CSTR.CSTR_MPC_Model", self.cstr_file_path, compiler_options={"state_initial_equations": True}) sim_model = load_fmu(sim_fmu) # Compile and load the model with eliminated variables used for simulation sim_fmu_elim = compile_fmu("CSTR.CSTR_elim_vars_MPC_Model", self.cstr_file_path, compiler_options={ "state_initial_equations": True, 'equation_sorting': True, 'automatic_tearing': False }) sim_model_elim = load_fmu(sim_fmu_elim) # Define stationary point A and set initial values and inputs c_0_A = 956.271352 T_0_A = 250.051971 sim_model.set('_start_c', c_0_A) sim_model.set('_start_T', T_0_A) sim_model.set('Tc', 280) init_res = sim_model.simulate(start_time=0., final_time=150) # Compile and load optimization problems op = transfer_to_casadi_interface("CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={ "state_initial_equations": True, "common_subexp_elim": False }) op_elim = transfer_to_casadi_interface("CSTR.CSTR_elim_vars_MPC", self.cstr_file_path, compiler_options={ "state_initial_equations": True, 'equation_sorting': True, 'automatic_tearing': False, "common_subexp_elim": False }) # Define MPC options sample_period = 5 # s horizon = 10 # Samples on the horizon n_e_per_sample = 1 # Collocation elements / sample n_e = n_e_per_sample * horizon # Total collocation elements finalTime = 50 # s number_samp_tot = 5 # Total number of samples to do # Create blocking factors with quadratic penalty and bound on 'Tc' bf_list = [n_e_per_sample] * (horizon / n_e_per_sample) factors = {'Tc': bf_list} du_quad_pen = {'Tc': 50} du_bounds = {'Tc': 30} bf = BlockingFactors(factors, du_bounds, du_quad_pen) # Set collocation options opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['n_cp'] = 2 opt_opts['init_traj'] = init_res constr_viol_costs = {'T': 1e6} # Create the MPC object MPC_object = MPC(op, opt_opts, sample_period, horizon, constr_viol_costs=constr_viol_costs, noise_seed=1) # Set initial state x_k = {'_start_c': c_0_A, '_start_T': T_0_A} # Update the state and optimize number_samp_tot times for k in range(number_samp_tot): # Update the state and compute the optimal input for next sample period MPC_object.update_state(x_k) u_k = MPC_object.sample() # Reset the model and set the new initial states before simulating # the next sample period with the optimal input u_k sim_model.reset() sim_model.set(list(x_k.keys()), list(x_k.values())) sim_res = sim_model.simulate(start_time=k * sample_period, final_time=(k + 1) * sample_period, input=u_k) # Extract state at end of sample_period from sim_res and add Gaussian # noise with mean 0 and standard deviation 0.005*(state_current_value) x_k = MPC_object.extract_states(sim_res, mean=0, st_dev=0.005) # Extract variable profiles complete_result = MPC_object.get_complete_results() op_elim.eliminateAlgebraics() assert (len(op_elim.getEliminatedVariables()) == 2) opt_opts_elim = op_elim.optimize_options() opt_opts_elim['n_e'] = n_e opt_opts_elim['n_cp'] = 2 opt_opts_elim['init_traj'] = init_res # Create the MPC object with eliminated variables MPC_object_elim = MPC(op_elim, opt_opts_elim, sample_period, horizon, constr_viol_costs=constr_viol_costs, noise_seed=1) # Set initial state x_k = {'_start_c': c_0_A, '_start_T': T_0_A} # Update the state and optimize number_samp_tot times for k in range(number_samp_tot): # Update the state and compute the optimal input for next sample period MPC_object_elim.update_state(x_k) u_k = MPC_object_elim.sample() # Reset the model and set the new initial states before simulating # the next sample period with the optimal input u_k sim_model_elim.reset() sim_model_elim.set(list(x_k.keys()), list(x_k.values())) sim_res = sim_model_elim.simulate(start_time=k * sample_period, final_time=(k + 1) * sample_period, input=u_k) # Extract state at end of sample_period from sim_res and add Gaussian # noise with mean 0 and standard deviation 0.005*(state_current_value) x_k = MPC_object_elim.extract_states(sim_res, mean=0, st_dev=0.005) # Extract variable profiles complete_result_elim = MPC_object_elim.get_complete_results() N.testing.assert_array_almost_equal(complete_result['c'], complete_result_elim['c']) N.testing.assert_array_almost_equal(complete_result['T'], complete_result_elim['T']) N.testing.assert_array_almost_equal(complete_result['Tc'], complete_result_elim['Tc'])
def test_auto_bl_factors(self): """ Test blocking factors generated in the mpc-class. """ op = transfer_to_casadi_interface( "CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={"state_initial_equations": True}) # Set options collocation n_e = 50 opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['IPOPT_options']['print_level'] = 0 # Define some MPC-options sample_period = 3 horizon = 50 # Create MPC-object MPC_object_auto = MPC(op, opt_opts, sample_period, horizon) MPC_object_auto.update_state() MPC_object_auto.sample() res_auto = MPC_object_auto.get_results_this_sample() opt_opts_auto = op.optimize_options() opt_opts_auto['n_e'] = n_e opt_opts_auto['IPOPT_options']['print_level'] = 0 bf_list = [1] * horizon factors = {'Tc': bf_list} bf = BlockingFactors(factors) opt_opts_auto['blocking_factors'] = bf MPC_object = MPC(op, opt_opts_auto, sample_period, horizon) MPC_object.update_state() MPC_object.sample() res = MPC_object.get_results_this_sample() # Assert that res_auto['Tc'] and res['Tc'] are equal N.testing.assert_array_equal(res_auto['Tc'], res['Tc'])
def test_infeasible_return_input(self): """ Test that the input returned from an unsuccessful optimization is the next input in the last successful optimization. """ op = transfer_to_casadi_interface( "CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={"state_initial_equations": True}) # Set options collocation n_e = 50 opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['IPOPT_options']['print_level'] = 0 # Define some MPC-options sample_period = 3 horizon = 50 seed = 7 cvc = {'T': 1e6} # Define blocking factors bl_list = [1] * horizon factors = {'Tc': bl_list} bf = BlockingFactors(factors=factors) opt_opts['blocking_factors'] = bf # Create MPC-object MPC_object = MPC(op, opt_opts, sample_period, horizon, constr_viol_costs=cvc, noise_seed=seed, create_comp_result=False, initial_guess='trajectory') # NOTE: THIS NOT WORKING WITH initial_guess='shift'!! MPC_object.update_state({ '_start_c': 587.47543496, '_start_T': 345.64619542 }) u_k1 = MPC_object.sample() result1 = MPC_object.get_results_this_sample() # Optimize with infeasible problem MPC_object.update_state({'_start_c': 900, '_start_T': 400}) u_k2 = MPC_object.sample() # Assert that problem was infeasible and that the returned input is # the next input from the last succesful optimization N.testing.assert_( 'Infeasible_Problem_Detected', MPC_object.collocator.solver_object.getStat('return_status')) N.testing.assert_almost_equal(u_k2[1](0)[0], result1['Tc'][4], decimal=10) # Assert that the returned resultfile is that of the last succesful # optimization result2 = MPC_object.get_results_this_sample() assert result1 == result2, "UNEQUAL VALUES. result1={}\nresult2={}".format( result1, result2) # Assert that problem was infeasible yet again and that the returned # input is the next (third) input from the last succesful optimization MPC_object.update_state({'_start_c': 900, '_start_T': 400}) u_k3 = MPC_object.sample() N.testing.assert_( 'Infeasible_Problem_Detected', MPC_object.collocator.solver_object.getStat('return_status')) N.testing.assert_almost_equal(u_k3[1](0)[0], result1['Tc'][7], decimal=10)
class RealTimeMPCBase(RealTimeBase): """ A base class for performing real time MPC on a process. Note that this is an abstract class; to use it, you need to extend it with the functions send_control_signal and get_values. """ def __init__(self, file_path, opt_name, dt, t_hor, t_final, start_values, par_values, output_names, input_names, par_changes=ParameterChanges(), mpc_options={}, constr_viol_costs={}, noise=0): """ Create a real time MPC object. Parameters:: file_path -- The path of the .mop file containing the model to be used for the MPC solver. opt_name -- The name of the optimization in the file specified by file_path to be used by the MPC solver. dt -- The time to wait in between each sample. t_hor -- The horizon time for the MPC solver. Must be an even multiple of dt. t_final -- The total time to run the real time MPC. start_values -- A dictionary containing the initial state values for the process. par_values -- A dictionary containing parameter values to be set in the model. output_names -- A list of the names of all of the output variables used in the model. input_names -- A list of the names of all of the input variables used in the model. par_changes -- A ParameterChanges object containing parameter changes and the times they should be applied. Default: An empty ParameterChanges object mpc_options -- A dictionary of options to be used for the MPC solver. constr_viol_costs -- Constraint violation costs used by the MPC solver. See the documentation of the MPC class for more information. noise -- Standard deviation of the noise to add to the input signals. Default: 0 """ super(RealTimeMPCBase, self).__init__(dt, t_final, start_values, output_names, input_names, par_changes, noise) horizon = int(t_hor / dt) n_e = horizon self._setup_MPC_solver(file_path, opt_name, dt, horizon, n_e, par_values, constr_viol_costs, mpc_options) self.range_ = [] for i, name in enumerate(self.inputs): var = self.solver.op.getVariable(name) self.range_.append( (var.getMin().getValue(), var.getMax().getValue())) def _setup_MPC_solver(self, file_path, opt_name, dt, horizon, n_e, par_values, constr_viol_costs={}, mpc_options={}): op = transfer_optimization_problem( opt_name, file_path, compiler_options={'state_initial_equations': True}) op.set(par_values.keys(), par_values.values()) opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['n_cp'] = 2 opt_opts['IPOPT_options']['tol'] = 1e-10 opt_opts['IPOPT_options']['print_time'] = False if 'IPOPT_options' in mpc_options: opt_opts['IPOPT_options'].update(mpc_options['IPOPT_options']) for key in mpc_options: if key != 'IPOPT_options': opt_opts[key] = mpc_options[key] self.solver = MPC(op, opt_opts, dt, horizon, constr_viol_costs=constr_viol_costs) def enable_codegen(self, name=None): """ Enables use of generated C code for the MPC solver. Generates and compiles code for the NLP, gradient of f, Jacobian of g, and Hessian of the Lagrangian of g Function objects, and then replaces the solver object in the solver's collocator with a new one that makes use of the compiled functions as ExternalFunction objects. Parameters:: name -- A string that if it is not None, loads existing files nlp_[name].so, grad_f_[name].so, jac_g_[name].so and hess_lag_[name].so as ExternalFunction objects to be used by the solver rather than generating new code. Default: None """ self.solver.collocator.enable_codegen(name) def enable_integral_action(self, mu, M, error_names=None, u_e=None): """ Enables integral action for the inputs. If integral action is enabled, in each step the input error is calculated and used to update the estimation of the error. By default, the input error is calculated as the matrix M times the difference between the current state vector as predicted by the solver in the last time step and as measured from the process in the current time step; however, this can be changed by overriding the estimate_input_error method. The low-pass filter used to update the estimate is [new estimate] = mu*[old estimate] + (1-mu)*[current estimate] Parameters:: mu -- Controls the convergence rate of the error estimate. See above. M -- A matrix used for calculating the input error from the state error. See above. error_names -- A list containing the names of the model variables for the input errors. If set to None, it is assumed be the same as the list of input variables with the prefix '_e' appended to each one. Default: None u_e -- A list containing a set of values to be applied as stationary errors to the input signals. Used for simulating a stationary error where there otherwise wouldn't be one. If set to None, no stationary error is applied. Default: None. """ self._ia = True self.mu = mu self.M = M if error_names == None: self.errors = [name + '_e' for name in self.inputs] else: self.errors = error_names if u_e == None: self.u_e = N.zeros(len(self.inputs)) else: self.u_e = N.array(u_e) self.u_e_e = N.zeros(len(self.inputs)) def run(self, save=False): """ Run the real time MPC controller defined by the object. Parameters:: save -- Determines whether or not to save data after running. If set to False, the same data can still be saved manually by using the save_results function. Default: False Returns:: The results and statistics from the run. """ if self._already_run: raise RuntimeError('run can only be called once') self.e_e = [] n_outputs = len(self.outputs) n_inputs = len(self.inputs) x_k = self.start_values.copy() x_k_last = x_k.copy() time1 = time.clock() time2 = time.time() time3 = 0 for k in range(self.n_steps): new_pars = self.par_changes.get_new_pars(k * self.dt) if new_pars != None: self.solver.op.set(new_pars.keys(), new_pars.values()) self.solver.update_state(x_k) u_k = self.solver.sample() u_k = self._apply_noise(u_k, std_dev=self.noise) if self._ia: u_k_e = self._apply_error(u_k) if time3 != 0: solve_time = time.time() - time3 self.solve_times.append(solve_time) if solve_time > self.dt * 0.2: print 'WARNING: Control signal late by', solve_time, 's' if self._ia: self.send_control_signal(u_k_e) else: self.send_control_signal(u_k) if k == 0: next_time = time.time() + self.dt m_k = self.wait_and_get_measurements(next_time) next_time = time.time() + self.dt time3 = time.time() x_k = self.estimate_states(m_k, x_k_last) x_k_last = x_k.copy() if self._ia: self._update_error_estimate(x_k) for i in range(n_outputs): self.results[self.outputs[i]].append(x_k['_start_' + self.outputs[i]]) for i in range(n_inputs): self.results[self.inputs[i]].append(u_k[1](0)[i]) self.stats.append(self.solver.collocator.solver_object.getStats()) self.ptime = time.clock() - time1 self.rtime = time.time() - time2 print 'Processor time:', self.ptime, 's' print 'Real time:', self.rtime, 's' if save: self.save_results() self._already_run = True return self.results, self.stats def _apply_noise(self, u_k, std_dev=0.0): if std_dev == 0: return u_k inputs = u_k[1](0) for n in range(len(inputs)): noise = N.random.normal(0.0, std_dev) inputs[n] += noise inputs[n] = max(self.range_[n][0], min(self.range_[n][1], inputs[n])) return (u_k[0], lambda t: inputs) def _apply_error(self, u_k): u_k_e = [] for i in range(len(self.errors)): u_k_e.append( max(self.range_[i][0], min(self.range_[i][1], u_k[1](0)[i] + self.u_e[i]))) return (u_k[0], lambda t: N.array(u_k_e)) def _update_error_estimate(self, x_k): e_k = self._calculate_error(x_k) u_e_e_next = self.estimate_input_error(e_k) self.u_e_e = (1 - self.mu) * self.u_e_e + self.mu * (u_e_e_next + self.u_e_e) for i in range(len(self.errors)): self.solver.set(self.errors[i], self.u_e_e[i]) self.e_e.append(self.u_e_e) def estimate_input_error(self, e_k): """ Estimates the input error given the state error. Parameters:: e_k -- The state error vector. Returns:: The input error vector. """ return self.M.dot(e_k) def _calculate_error(self, x_k): x_k_a = N.array([x_k['_start_' + name] for name in self.outputs]) res = self.solver.get_results_this_sample() x_k_e = N.array( [res[name][self.solver.options['n_cp']] for name in self.outputs]) return x_k_a - x_k_e def print_stats(self): """ Print statistics from the run. The times printed are the sums of the corresponding statistics from the NLP solver over all time steps. """ if not self._already_run: raise RuntimeError( 'Stats can only be printed after run() has been called') stat_names = [ 't_callback_fun', 't_callback_prepare', 't_eval_f', 't_eval_g', 't_eval_grad_f', 't_eval_h', 't_eval_jac_g', 't_mainloop' ] total_times = {} for name in stat_names: total_times[name] = 0. for stat in self.stats: for name in stat_names: total_times[name] += stat[name] t_total = total_times['t_mainloop'] print 'Total times:' for name in stat_names: print("%19s: %6.4f s (%7.3f%%)" % (name, total_times[name], total_times[name] / t_total * 100)) def save_results(self, filename=None): """ Pickle and save data from the run to a file name either passed as an argument or entered by the user. If an empty string is entered as the file name, no data is saved. Parameters:: filename -- The file name to save as. If it is not provided, the user will be prompted to input it. Default: None """ if not self._already_run: raise RuntimeError( 'Results can only be saved after run() has been called') result_dict = {} result_dict['results'] = self.results result_dict['stats'] = self.stats result_dict['ptime'] = self.ptime result_dict['rtime'] = self.rtime result_dict['noise'] = self.noise result_dict['late_times'] = self.late_times result_dict['wait_times'] = self.wait_times result_dict['solve_times'] = self.solve_times save_to_file(result_dict, filename)
def test_du_quad_pen(self): """ Test with blocking_factor du_quad_pen. """ op = transfer_to_casadi_interface( "CSTR.CSTR_MPC", self.cstr_file_path, compiler_options={"state_initial_equations": True}) op.set('_start_c', float(self.c_0_A)) op.set('_start_T', float(self.T_0_A)) # Set options collocation n_e = 50 opt_opts = op.optimize_options() opt_opts['n_e'] = n_e opt_opts['IPOPT_options']['print_level'] = 0 # Define some MPC-options sample_period = 3 horizon = 50 seed = 7 # Define blocking factors bl_list = [1] * horizon factors = {'Tc': bl_list} bf = BlockingFactors(factors=factors) opt_opts['blocking_factors'] = bf # Create MPC-object without du_quad_pen MPC_object = MPC(op, opt_opts, sample_period, horizon, noise_seed=seed, initial_guess='trajectory') MPC_object.update_state() MPC_object.sample() res = MPC_object.get_results_this_sample() # Create MPC-object with du_quad_pen opt_opts_quad = op.optimize_options() opt_opts_quad['n_e'] = n_e opt_opts_quad['IPOPT_options']['print_level'] = 0 bf_list = [1] * horizon factors = {'Tc': bf_list} du_quad_pen = {'Tc': 100} bf = BlockingFactors(factors, du_quad_pen=du_quad_pen) opt_opts_quad['blocking_factors'] = bf MPC_object_quad = MPC(op, opt_opts_quad, sample_period, horizon, noise_seed=seed, initial_guess='trajectory') MPC_object_quad.update_state() MPC_object_quad.sample() res_quad = MPC_object_quad.get_results_this_sample() Tc = res['Tc'] prev_value = Tc[0] largest_delta = 0 for value in Tc: delta = value - prev_value if delta > largest_delta: largest_delta = delta prev_value = value Tc = res_quad['Tc'] prev_value = Tc[0] largest_delta_quad = 0 for value in Tc: delta = value - prev_value if delta > largest_delta_quad: largest_delta_quad = delta prev_value = value N.testing.assert_(largest_delta_quad < largest_delta)