def __ms_nlp_vars(self, options, model, V, P): """Rearrange decision variables to dae-compatible form, allowing for parallel function evaluations @param model awebox model @param V nlp decision variables @param P nlp parameters """ # interval parameters param_at_time = model.parameters(cas.vertcat(P['theta0'], V['phi'])) ms_params = cas.repmat(param_at_time, 1, self.__n_k) if options['parallelization']['include']: # use function map for rootfinder parallellization G_map = self.__dae.rootfinder.map( 'G_map', options['parallelization']['type'], self.__n_k, [], []) x_root = [] z_root = [] p_root = [] else: # compute implicit vars in for loop z_implicit = [] # compute explicit values of implicit variables ms_vars0 = [] for kdx in range(self.__n_k): # get vars at time var_at_time = struct_op.get_variables_at_time( options, V, None, model.variables, kdx) ms_vars0 += [var_at_time] # get dae vars at time x, z, p = self.__dae.fill_in_dae_variables(var_at_time, param_at_time) if not options['parallelization']['include']: # compute implicit vars in for loop z_at_time = self.__dae.z(self.__dae.rootfinder(z, x, p)) z_implicit = cas.horzcat(z_implicit, z_at_time) else: # store vars for parallelization x_root = cas.horzcat(x_root, x) z_root = cas.horzcat(z_root, z) p_root = cas.horzcat(p_root, p) if options['parallelization']['include']: # compute implicit vars in parallel fashion z_implicit = G_map(z_root, x_root, p_root) # construct list of all interval variables ms_vars = [] ms_x = [] ms_z = [] ms_p = [] for kdx in range(self.__n_k): # fill in non-lifted vars var_at_time = self.__set_implicit_variables( options, ms_vars0[kdx], param_at_time, self.__dae.z(z_implicit[:, kdx])) # update dae vars at time x, z, p = self.__dae.fill_in_dae_variables(var_at_time, param_at_time) # store result ms_vars = cas.horzcat(ms_vars, var_at_time) ms_x = cas.horzcat(ms_x, x) ms_z = cas.horzcat(ms_z, z) ms_p = cas.horzcat(ms_p, p) self.__ms_params = ms_params self.__ms_vars = ms_vars self.__ms_x = ms_x self.__ms_z = ms_z self.__ms_z0 = z_implicit self.__ms_p = ms_p return None
def collocate_constraints(self, options, model, formulation, V, P, Xdot): """ Generate collocation and path constraints on all nodes, provide integral outputs and integral constraints on all nodes """ # extract discretization information N_coll = self.__n_k * self.__d # collocation points # extract model information variables = model.variables parameters = model.parameters # construct list of all collocation node variables and parameters coll_vars = [] for kdx in range(self.__n_k): for ddx in range(self.__d): var_at_time = struct_op.get_variables_at_time( options, V, Xdot, model, kdx, ddx) coll_vars = cas.horzcat(coll_vars, var_at_time) coll_params = cas.repmat( parameters(cas.vertcat(P['theta0'], V['phi'])), 1, N_coll) # evaluate dynamics and constraint functions on all intervals if options['parallelization']['include']: # use function map for parallellization parallellization = options['parallelization']['type'] dynamics = model.dynamics.map('dynamics_map', parallellization, N_coll, [], []) path_constraints_fun = model.constraints_fun.map( 'constraints_map', parallellization, N_coll, [], []) integral_outputs_fun = model.integral_outputs_fun.map( 'integral_outputs_map', parallellization, N_coll, [], []) outputs_fun = model.outputs_fun.map('outputs_fun', parallellization, N_coll, [], []) # extract formulation information constraints_fun_ineq = formulation.constraints_fun['integral'][ 'inequality'].map('integral_constraints_map_ineq', 'serial', N_coll, [], []) constraints_fun_eq = formulation.constraints_fun['integral'][ 'equality'].map('integral_constraints_map_eq', 'serial', N_coll, [], []) # evaluate functions coll_dynamics = dynamics(coll_vars, coll_params) coll_constraints = path_constraints_fun(coll_vars, coll_params) coll_outputs = outputs_fun(coll_vars, coll_params) integral_outputs_deriv = integral_outputs_fun( coll_vars, coll_params) integral_constraints = OrderedDict() integral_constraints['inequality'] = constraints_fun_ineq( coll_vars, coll_params) integral_constraints['equality'] = constraints_fun_eq( coll_vars, coll_params) else: # initialize function evaluations coll_dynamics = [] coll_constraints = [] coll_outputs = [] integral_outputs_deriv = [] integral_constraints = OrderedDict() integral_constraints['inequality'] = [] integral_constraints['equality'] = [] # evaluate functions in for loop for i in range(N_coll): coll_dynamics = cas.horzcat( coll_dynamics, model.dynamics(coll_vars[:, i], coll_params[:, i])) coll_constraints = cas.horzcat( coll_constraints, model.constraints_fun(coll_vars[:, i], coll_params[:, i])) coll_outputs = cas.horzcat( coll_outputs, model.outputs_fun(coll_vars[:, i], coll_params[:, i])) integral_outputs_deriv = cas.horzcat( integral_outputs_deriv, model.integral_outputs_fun(coll_vars[:, i], coll_params[:, i])) integral_constraints['inequality'] = cas.horzcat( integral_constraints['inequality'], formulation.constraints_fun['integral']['inequality']( coll_vars[:, i], coll_params[:, i])) integral_constraints['equality'] = cas.horzcat( integral_constraints['equality'], formulation.constraints_fun['integral']['equality']( coll_vars[:, i], coll_params[:, i])) # integrate integral outputs Integral_outputs_list = [np.zeros(model.integral_outputs.cat.shape[0])] Integral_constraints_list = [] for kdx in range(self.__n_k): tf = struct_op.calculate_tf(options, V, kdx) Integral_outputs_list = self.__integrate_integral_outputs( Integral_outputs_list, integral_outputs_deriv[:, kdx * self.__d:(kdx + 1) * self.__d], model, tf) Integral_constraints_list += [ self.__integrate_integral_constraints(integral_constraints, kdx, tf) ] return coll_dynamics, coll_constraints, coll_outputs, Integral_outputs_list, Integral_constraints_list
def discretize(nlp_numerics_options, model, formulation): # ----------------------------------------------------------------------------- # discretization setup # ----------------------------------------------------------------------------- nk = nlp_numerics_options['n_k'] if nlp_numerics_options['discretization'] == 'direct_collocation': direct_collocation = True ms = False d = nlp_numerics_options['collocation']['d'] scheme = nlp_numerics_options['collocation']['scheme'] Collocation = collocation.Collocation(nk, d, scheme) Multiple_shooting = None elif nlp_numerics_options['discretization'] == 'multiple_shooting': direct_collocation = False ms = True Collocation = None dae = model.get_dae() Multiple_shooting = multiple_shooting.Multiple_shooting( nk, dae, nlp_numerics_options['integrator']) slacks = None # -------------------------------------- # prepare model variables and dynamics # -------------------------------------- variables = model.variables parameters = model.parameters variables_dict = model.variables_dict #--------------------------------------- # prepare constraints structure #--------------------------------------- form_constraints = formulation.constraints constraints_fun = formulation.constraints_fun # initial, terminal and periodicity constraints path_constraints = model.constraints path_constraints_fun = model.constraints_fun g_struct = constraints.setup_constraint_structure( nlp_numerics_options, model, formulation) # empty struct for collocated constraints #------------------------------------------- # DISCRETIZE VARIABLES, CREATE NLP PARAMETERS #------------------------------------------- V = setup_nlp_v(nlp_numerics_options, model, formulation, Collocation) P = setup_nlp_p(V, model) if direct_collocation: Xdot = Collocation.get_xdot(nlp_numerics_options, V, model) # construct time grids for this nlp time_grids = construct_time_grids(nlp_numerics_options) # --------------------------------------- # prepare outputs structure # --------------------------------------- outputs = model.outputs outputs_fun = model.outputs_fun [form_outputs, form_outputs_dict ] = performance.collect_performance_outputs(nlp_numerics_options, model, V) form_outputs_fun = cas.Function('form_outputs_fun', [V, P], [form_outputs.cat]) Outputs_struct = setup_output_structure(nlp_numerics_options, outputs, form_outputs) #------------------------------------------- # COLLOCATE CONSTRAINTS, OUTPUTS #------------------------------------------- # prepare listing of outputs and constraints Outputs_list = [] g_list = [] g_bounds = {'lb': [], 'ub': []} # extract model.parameters from V param_at_time = parameters(cas.vertcat(P['theta0'], V['phi'])) xi = V['xi'] #TODO: don't hard code! # parallellize constraints on collocation nodes if direct_collocation: [ coll_dynamics, coll_constraints, coll_outputs, Integral_outputs_list, Integral_constraint_list ] = Collocation.collocate_constraints(nlp_numerics_options, model, formulation, V, P, Xdot) # parallellize constraints on interval nodes if ms: [ ms_xf, ms_z0, Xdot, ms_constraints, ms_outputs, Integral_outputs_list, Integral_constraint_list ] = Multiple_shooting.discretize_constraints(nlp_numerics_options, model, formulation, V, P) # Construct list of constraints (+ bounds) and outputs for kdx in range(nk): # time constant of the following interval tf = struct_op.calculate_tf(nlp_numerics_options, V, kdx) if kdx == 0: # extract initial (reference) variables var_initial = struct_op.get_variables_at_time( nlp_numerics_options, V, Xdot, model, 0) var_ref_initial = struct_op.get_var_ref_at_time( nlp_numerics_options, P, V, Xdot, model, 0) # add initial constraints [g_list, g_bounds] = constraints.append_initial_constraints( g_list, g_bounds, form_constraints, constraints_fun, var_initial, var_ref_initial, xi) if (ms) or (direct_collocation and scheme != 'radau'): # at each interval node, algebraic constraints should be satisfied [g_list, g_bounds] = constraints.append_algebraic_constraints( g_list, g_bounds, dae.z(ms_z0[:, kdx]), V, kdx) # at each interval node, path constraints should be satisfied if 'us' in list(V.keys()): # slack path constraints slacks = V['us', kdx] [g_list, g_bounds] = constraints.append_path_constraints( g_list, g_bounds, path_constraints, ms_constraints[:, kdx], slacks) # compute outputs for this time interval Outputs_list.append(ms_outputs[:, kdx]) if direct_collocation: # add constraints and outputs on collocation nodes for ddx in range(d): # at each (except for first node) collocation point dynamics should meet [g_list, g_bounds] = constraints.append_collocation_constraints( g_list, g_bounds, coll_dynamics[:, kdx * d + ddx]) # at each (except for first node) collocation node, path constraints should be satisfied [g_list, g_bounds] = constraints.append_path_constraints( g_list, g_bounds, path_constraints, coll_constraints[:, kdx * d + ddx]) # compute outputs for this time interval Outputs_list.append(coll_outputs[:, kdx * d + ddx]) # endpoint should match next start point [g_list, g_bounds] = Collocation.append_continuity_constraint( g_list, g_bounds, V, kdx) elif ms: # endpoint should match next start point [g_list, g_bounds] = Multiple_shooting.append_continuity_constraint( g_list, g_bounds, ms_xf, V, kdx) # extract terminal (reference) variables var_terminal = struct_op.get_variables_at_final_time( nlp_numerics_options, V, Xdot, model) var_ref_terminal = struct_op.get_var_ref_at_final_time( nlp_numerics_options, P, Xdot, model) # add terminal and periodicity constraints [g_list, g_bounds] = constraints.append_terminal_constraints( g_list, g_bounds, form_constraints, constraints_fun, var_terminal, var_ref_terminal, xi) [g_list, g_bounds] = constraints.append_periodic_constraints( g_list, g_bounds, form_constraints, constraints_fun, var_initial, var_terminal) if direct_collocation: [g_list, g_bounds] = constraints.append_integral_constraints( nlp_numerics_options, g_list, g_bounds, Integral_constraint_list, form_constraints, constraints_fun, V, Xdot, model, formulation.integral_constants) Outputs_list.append(form_outputs_fun(V, P)) # Create Outputs struct and function Outputs = Outputs_struct(cas.vertcat(*Outputs_list)) Outputs_fun = cas.Function('Outputs_fun', [V, P], [Outputs.cat]) # Create Integral outputs struct and function Integral_outputs_struct = setup_integral_output_structure( nlp_numerics_options, model.integral_outputs) Integral_outputs = Integral_outputs_struct( cas.vertcat(*Integral_outputs_list)) Integral_outputs_fun = cas.Function('Integral_outputs_fun', [V, P], [Integral_outputs.cat]) # Create g struct and functions and g_bounds vectors [g, g_fun, g_jacobian_fun, g_bounds] = constraints.create_constraint_outputs(g_list, g_bounds, g_struct, V, P) Xdot_struct = struct_op.construct_Xdot_struct(nlp_numerics_options, model) Xdot_fun = cas.Function('Xdot_fun', [V], [Xdot]) return V, P, Xdot_struct, Xdot_fun, g_struct, g_fun, g_jacobian_fun, g_bounds, Outputs_struct, Outputs_fun, Integral_outputs_struct, Integral_outputs_fun, time_grids, Collocation, Multiple_shooting
def test_integrators(): # =========================================== # SET-UP DIRECT COLLOCATION PROBLEM AND SOLVE # =========================================== # make default options object base_options = awe.Options(True) # True refers to internal access switch # choose simplest model base_options['user_options']['system_model']['architecture'] = {1:0} base_options['user_options']['system_model']['kite_dof'] = 3 base_options['user_options']['kite_standard'] = awe.ampyx_data.data_dict() base_options['user_options']['tether_drag_model'] = 'split' base_options['user_options']['induction_model'] = 'not_in_use' # specify direct collocation options base_options['nlp']['n_k'] = 40 base_options['nlp']['discretization'] = 'direct_collocation' base_options['nlp']['collocation']['u_param'] = 'zoh' base_options['nlp']['collocation']['scheme'] = 'radau' base_options['nlp']['collocation']['d'] = 4 base_options['model']['tether']['control_var'] = 'dddl_t' # homotopy tuning base_options['solver']['mu_hippo'] = 1e-4 base_options['solver']['tol_hippo'] = 1e-4 # make trial, build and run trial = awe.Trial(name = 'test', seed = base_options) trial.build() trial.optimize() # extract solution data V_final = trial.optimization.V_opt P = trial.optimization.p_fix_num Int_outputs = trial.optimization.integral_output_vals[1] model = trial.model dae = model.get_dae() # build dae variables for t = 0 within first shooting interval variables0 = struct_op.get_variables_at_time(base_options['nlp'], V_final, None, model.variables, 0) parameters = model.parameters(vertcat(P['theta0'], V_final['phi'])) x0, z0, p = dae.fill_in_dae_variables(variables0, parameters) # =================================== # TEST COLLOCATION INTEGRATOR # =================================== # set discretization to multiple shooting base_options['nlp']['discretization'] = 'multiple_shooting' base_options['nlp']['integrator']['type'] = 'collocation' base_options['nlp']['integrator']['collocation_scheme'] = base_options['nlp']['collocation']['scheme'] base_options['nlp']['integrator']['interpolation_order'] = base_options['nlp']['collocation']['d'] base_options['nlp']['integrator']['num_steps'] = 1 # switch off expand to allow for use of integrator in NLP base_options['solver']['expand_overwrite'] = False # build MS trial trialColl = awe.Trial(name = 'testColl', seed = base_options) trialColl.build() # multiple shooting dae integrator F = trialColl.nlp.Multiple_shooting.F # integrate over one interval Ff = F(x0 = x0, z0 = z0, p = p) xf = Ff['xf'] zf = Ff['zf'] qf = Ff['qf'] # evaluate integration error err_coll_x = np.max(np.abs(np.divide((xf - V_final['xd',1]), V_final['xd',1]).full())) xa = dae.z(zf)['xa'] err_coll_z = np.max(np.abs(np.divide(dae.z(zf)['xa'] - V_final['coll_var',0, -1, 'xa'], V_final['coll_var',0, -1, 'xa']).full())) err_coll_q = np.max(np.abs(np.divide((qf - Int_outputs['int_out',1]), Int_outputs['int_out',1]).full())) tolerance = 1e-8 # values should match up to nlp solver accuracy assert(err_coll_x < tolerance) assert(err_coll_z < tolerance) assert(err_coll_q < tolerance) # =================================== # TEST RK4-ROOT INTEGRATOR # =================================== # set discretization to multiple shooting base_options['nlp']['integrator']['type'] = 'rk4root' base_options['nlp']['integrator']['num_steps'] = 20 # build MS trial trialRK = awe.Trial(name = 'testRK', seed = base_options) trialRK.build() # multiple shooting dae integrator F = trialRK.nlp.Multiple_shooting.F # integrate over one interval Ff = F(x0 = x0, z0 = z0, p = p) xf = Ff['xf'] zf = Ff['zf'] qf = Ff['qf'] # evaluate err_rk_x = np.max(np.abs(np.divide((xf - V_final['xd',1]), V_final['xd',1]).full())) xa = dae.z(zf)['xa'] err_rk_z = np.max(np.abs(np.divide(dae.z(zf)['xa'] - V_final['coll_var',0, -1, 'xa'], V_final['coll_var',0, -1, 'xa']).full())) err_rk_q = np.max(np.abs(np.divide((qf - Int_outputs['int_out',1]), Int_outputs['int_out',1]).full())) # error should be below 1% assert(err_rk_x < 1e-2) assert(err_rk_z < 1e-2) assert(err_rk_q < 2e-2)
def get_strength_constraint(options, V, Outputs, model): n_k = options['n_k'] d = options['collocation']['d'] comparison_labels = options['induction']['comparison_labels'] wake_nodes = options['induction']['vortex_wake_nodes'] rings = wake_nodes - 1 kite_nodes = model.architecture.kite_nodes strength_scale = tools.get_strength_scale(options) Xdot = struct_op.construct_Xdot_struct(options, model.variables_dict)(0.) cstr_list = cstr_op.ConstraintList() any_vor = any(label[:3] == 'vor' for label in comparison_labels) if any_vor: for kite in kite_nodes: for ring in range(rings): wake_node = ring for ndx in range(n_k): for ddx in range(d): local_name = 'vortex_strength_' + str(kite) + '_' + str(ring) + '_' + str(ndx) + '_' + str(ddx) variables = struct_op.get_variables_at_time(options, V, Xdot, model.variables, ndx, ddx) wg_local = tools.get_ring_strength(variables, kite, ring) ndx_shed = n_k - 1 - wake_node ddx_shed = d - 1 # working out: # n_k = 3 # if ndx = 0 and ddx = 0 -> shed: wn >= n_k # wn: 0 sheds at ndx = 2, ddx = -1 : unshed, period = 0 # wn: 1 sheds at ndx = 1, ddx = -1 : unshed, period = 0 # wn: 2 sheds at ndx = 0, ddx = -1 : unshed, period = 0 # wn: 3 sheds at ndx = -1, ddx = -1 : SHED period = 1 # wn: 4 sheds at ndx = -2, period = 1 # wn: 5 sheds at ndx = -3 period = 1 # wn: 6 sheds at ndx = -4 period = 2 # if ndx = 1 and ddx = 0 -> shed: wn >= n_k - ndx # wn: 0 sheds at ndx = 2, ddx = -1 : unshed, # wn: 1 sheds at ndx = 1, ddx = -1 : unshed, # wn: 2 sheds at ndx = 0, ddx = -1 : SHED, # wn: 3 sheds at ndx = -1, ddx = -1 : SHED # if ndx = 0 and ddx = -1 -> shed: # wn: 0 sheds at ndx = 2, ddx = -1 : unshed, # wn: 1 sheds at ndx = 1, ddx = -1 : unshed, # wn: 2 sheds at ndx = 0, ddx = -1 : SHED, # wn: 3 sheds at ndx = -1, ddx = -1 : SHED already_shed = False if (ndx > ndx_shed): already_shed = True elif ((ndx == ndx_shed) and (ddx == ddx_shed)): already_shed = True if already_shed: # working out: # n_k = 3 # period_0 -> wn 0, wn 1, wn 2 -> floor(ndx_shed / n_k) # period_1 -> wn 3, wn 4, wn 5 period_number = int(np.floor(float(ndx_shed)/float(n_k))) ndx_shed_w_periodicity = ndx_shed - period_number * n_k gamma_val = Outputs['coll_outputs', ndx_shed_w_periodicity, ddx_shed, 'aerodynamics', 'circulation' + str(kite)] wg_ref = 1. * gamma_val / strength_scale else: wg_ref = 0. local_resi = (wg_local - wg_ref) local_cstr = cstr_op.Constraint(expr = local_resi, name = local_name, cstr_type='eq') cstr_list.append(local_cstr) return cstr_list
def get_fixing_constraint(options, V, Outputs, model): n_k = options['n_k'] comparison_labels = options['induction']['comparison_labels'] wake_nodes = options['induction']['vortex_wake_nodes'] kite_nodes = model.architecture.kite_nodes wingtips = ['ext', 'int'] Xdot = struct_op.construct_Xdot_struct(options, model.variables_dict)(0.) cstr_list = cstr_op.ConstraintList() any_vor = any(label[:3] == 'vor' for label in comparison_labels) if any_vor: for kite in kite_nodes: for tip in wingtips: for wake_node in range(wake_nodes): local_name = 'wake_fixing_' + str(kite) + '_' + str(tip) + '_' + str(wake_node) if wake_node < n_k: # working out: # n_k = 3 # wn:0, n_k-1=2 # wn:1, n_k-2=1 # wn:2=n_k-1, n_k-3=0 # ... switch to periodic fixing reverse_index = n_k - 1 - wake_node variables_at_shed = struct_op.get_variables_at_time(options, V, Xdot, model.variables, reverse_index, -1) wx_local = tools.get_wake_node_position_si(options, variables_at_shed, kite, tip, wake_node) wingtip_pos = Outputs[ 'coll_outputs', reverse_index, -1, 'aerodynamics', 'wingtip_' + tip + str(kite)] local_resi = wx_local - wingtip_pos local_cstr = cstr_op.Constraint(expr = local_resi, name = local_name, cstr_type='eq') cstr_list.append(local_cstr) else: # working out: # n_k = 3 # wn:0, n_k-1=2 # wn:1, n_k-2=1 # wn:2=n_k-1, n_k-3=0 # ... switch to periodic fixing # wn:3 at ndx = 0 must be equal to -> wn:0 at ndx = -1, ddx = -1 # wn:4 at ndx = 0 must be equal to -> wn:1 at ndx = -1, ddx = -1 variables_at_initial = struct_op.get_variables_at_time(options, V, Xdot, model.variables, 0) variables_at_final = struct_op.get_variables_at_time(options, V, Xdot, model.variables, -1, -1) upstream_node = wake_node - n_k wx_local = tools.get_wake_node_position_si(options, variables_at_initial, kite, tip, wake_node) wx_upstream = tools.get_wake_node_position_si(options, variables_at_final, kite, tip, upstream_node) local_resi = wx_local - wx_upstream local_cstr = cstr_op.Constraint(expr = local_resi, name = local_name, cstr_type='eq') cstr_list.append(local_cstr) return cstr_list