From 004eee026a16f887ea212435b8e6c4065b862959 Mon Sep 17 00:00:00 2001 From: phschiele Date: Fri, 21 Jul 2023 00:35:33 +0200 Subject: [PATCH 01/14] Draft --- .gitignore | 1 + cvxpygen/cpg.py | 863 +++++++++++++++++-------------------------- cvxpygen/mappings.py | 68 ++++ cvxpygen/solvers.py | 416 +++++++++++++++++++++ 4 files changed, 824 insertions(+), 524 deletions(-) create mode 100644 cvxpygen/mappings.py create mode 100644 cvxpygen/solvers.py diff --git a/.gitignore b/.gitignore index dd13ba1..aab2b12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *code/ tests/*/ +.python-version *.so __pycache__ .idea diff --git a/cvxpygen/cpg.py b/cvxpygen/cpg.py index 7e2a591..ff32142 100644 --- a/cvxpygen/cpg.py +++ b/cvxpygen/cpg.py @@ -16,14 +16,16 @@ import shutil import pickle import warnings -import osqp + from cvxpygen import utils +from cvxpygen.mappings import PrimalVariableInfo, DualVariableInfo, ConstraintInfo, ParameterCanon, \ + ParameterInfo +from cvxpygen.solvers import get_interface_class from cvxpygen.utils import C import cvxpy as cp import numpy as np from scipy import sparse from subprocess import call -from platform import system from cvxpy.problems.objective import Maximize from cvxpy.cvxcore.python import canonInterface as cI from cvxpy.expressions.variable import upper_tri_to_full @@ -38,28 +40,7 @@ def generate_code(problem, code_dir='CPG_code', solver=None, unroll=False, prefi sys.stdout.write('Generating code with CVXPYgen ...\n') - cvxpygen_directory = os.path.dirname(os.path.realpath(__file__)) - solver_code_dir = os.path.join(code_dir, 'c', 'solver_code') - - # adjust problem_name - if prefix != '': - if not prefix[0].isalpha(): - prefix = '_' + prefix - prefix = prefix + '_' - - # create code directory and copy template files - if os.path.isdir(code_dir): - shutil.rmtree(code_dir) - os.mkdir(code_dir) - os.mkdir(os.path.join(code_dir, 'c')) - for d in ['src', 'include', 'build']: - os.mkdir(os.path.join(code_dir, 'c', d)) - os.mkdir(os.path.join(code_dir, 'cpp')) - for d in ['src', 'include']: - os.mkdir(os.path.join(code_dir, 'cpp', d)) - shutil.copy(os.path.join(cvxpygen_directory, 'template', 'CMakeLists.txt'), os.path.join(code_dir, 'c')) - for file in ['setup.py', 'README.html', '__init__.py']: - shutil.copy(os.path.join(cvxpygen_directory, 'template', file), code_dir) + create_folder_structure(code_dir) # problem data solver_opts = {} @@ -73,45 +54,183 @@ def generate_code(problem, code_dir='CPG_code', solver=None, unroll=False, prefi verbose=False, solver_opts=solver_opts, ) + param_prob = data['param_prob'] - # catch non-supported cone types solver_name = solving_chain.solver.name() - p_prob = data['param_prob'] - if solver_name == 'ECOS': - if p_prob.cone_dims.exp > 0: - raise ValueError('Code generation with ECOS and exponential cones is not supported yet.') - elif solver_name == 'SCS': - if p_prob.cone_dims.exp > 0 or len(p_prob.cone_dims.psd) > 0 or len(p_prob.cone_dims.p3d) > 0: - raise ValueError('Code generation with SCS and exponential, positive semidefinite, or power cones ' - 'is not supported yet.') + interface_class = get_interface_class(solver_name) + + # for cone problems, check if all cones are supported + if hasattr(param_prob, 'cone_dims'): + cone_dims = param_prob.cone_dims + interface_class.check_unsupported_cones(cone_dims) # checks in sparsity - for p in p_prob.parameters: - if p.attributes['sparsity'] is not None: - if p.size == 1: - warnings.warn('Ignoring sparsity pattern for scalar parameter %s!' % p.name()) - p.attributes['sparsity'] = None - elif max(p.shape) == p.size: - warnings.warn('Ignoring sparsity pattern for vector parameter %s!' % p.name()) - p.attributes['sparsity'] = None - else: - for coord in p.attributes['sparsity']: - if coord[0] < 0 or coord[1] < 0 or coord[0] >= p.shape[0] or coord[1] >= p.shape[1]: - warnings.warn('Invalid sparsity pattern for parameter %s - out of range! ' - 'Ignoring sparsity pattern.' % p.name()) - p.attributes['sparsity'] = None - break - p.attributes['sparsity'] = list(set(p.attributes['sparsity'])) - elif p.attributes['diag']: - p.attributes['sparsity'] = [(i, i) for i in range(p.shape[0])] - if p.attributes['sparsity'] is not None and p.value is not None: - for i in range(p.shape[0]): - for j in range(p.shape[1]): - if (i, j) not in p.attributes['sparsity'] and p.value[i, j] != 0: - warnings.warn('Ignoring nonzero value outside of sparsity pattern for parameter %s!' % p.name()) - p.value[i, j] = 0 + handle_sparsity(param_prob) # variable information + variable_info = get_variable_info(problem, inverse_data) + + # dual variable information + dual_variable_info = get_dual_variable_info(inverse_data, solver_name) + + # user parameters + parameter_info = get_parameter_info(param_prob) + + # dimensions and information specific to solver + solver_interface = interface_class(data, param_prob) # noqa + + constraint_info = get_constraint_info(solver_interface) + + adjacency, parameter_canon = process_canonical_parameters(constraint_info, param_prob, + parameter_info, solver_interface, + solver_name) + + cvxpygen_directory = os.path.dirname(os.path.realpath(__file__)) + solver_code_dir = os.path.join(code_dir, 'c', 'solver_code') + solver_interface.generate_code(code_dir, solver_code_dir, cvxpygen_directory, parameter_canon) + + param_name_to_canon_outdated = { + user_p_name: [solver_interface.canon_p_ids[j] for j in np.nonzero(adjacency[:, i])[0]] + for i, user_p_name in enumerate(parameter_info.names)} + + # avoid non-alphanumeric first character in problem name + prefix = adjust_problem_name(prefix) + + # summarize information on options, codegen, user parameters / variables, + # canonicalization in dictionaries + info_opt = {C.CODE_DIR: code_dir, + C.SOLVER_NAME: solver_name, + C.UNROLL: unroll, + C.PREFIX: prefix} + + info_cg = {C.RET_PRIM_FUNC_EXISTS: solver_interface.ret_prim_func_exists(variable_info), + C.RET_DUAL_FUNC_EXISTS: solver_interface.ret_dual_func_exists(dual_variable_info), + C.NONZERO_D: parameter_canon.nonzero_d, + C.IS_MAXIMIZATION: type(problem.objective) == Maximize} + + info_usr = {C.P_WRITABLE: parameter_info.writable, + C.P_FLAT_USP: parameter_info.flat_usp, + C.P_COL_TO_NAME_USP: parameter_info.col_to_name_usp, + C.P_NAME_TO_SHAPE: parameter_info.name_to_shape, + C.P_NAME_TO_SIZE: parameter_info.name_to_size_usp, + C.P_NAME_TO_CANON_OUTDATED: param_name_to_canon_outdated, + C.P_NAME_TO_SPARSITY: parameter_info.name_to_sparsity, + C.P_NAME_TO_SPARSITY_TYPE: parameter_info.name_to_sparsity_type, + C.V_NAME_TO_INDICES: variable_info.name_to_indices, + C.V_NAME_TO_SIZE: variable_info.name_to_size, + C.V_NAME_TO_SHAPE: variable_info.name_to_shape, + C.V_NAME_TO_INIT: variable_info.name_to_init, + C.V_NAME_TO_SYM: variable_info.name_to_sym, + C.V_NAME_TO_OFFSET: variable_info.name_to_offset, + C.D_NAME_TO_INIT: dual_variable_info.name_to_init, + C.D_NAME_TO_VEC: dual_variable_info.name_to_vec, + C.D_NAME_TO_OFFSET: dual_variable_info.name_to_offset, + C.D_NAME_TO_SIZE: dual_variable_info.name_to_size, + C.D_NAME_TO_SHAPE: dual_variable_info.name_to_shape, + C.D_NAME_TO_INDICES: dual_variable_info.name_to_indices} + + info_can = {C.P: parameter_canon.p, + C.P_ID_TO_SIZE: parameter_canon.p_id_to_size, + C.P_ID_TO_CHANGES: parameter_canon.p_id_to_changes, + C.P_ID_TO_MAPPING: parameter_canon.p_id_to_mapping, + C.CONSTANTS: solver_interface.canon_constants, + C.SETTINGS_NAMES_TO_TYPE: solver_interface.settings_names_to_type, + C.SETTINGS_NAMES_TO_DEFAULT: solver_interface.settings_names_to_default, + C.SETTINGS_LIST: solver_interface.settings_names} + + write_c_code(code_dir, info_opt, info_cg, info_can, info_usr, problem) + + sys.stdout.write('CVXPYgen finished generating code.\n') + + if wrapper: + compile_python_module(code_dir) + + +def process_canonical_parameters(constraint_info, param_prob, parameter_info, solver_interface, solver_name): + adjacency = np.zeros(shape=(len(solver_interface.canon_p_ids), parameter_info.num), dtype=bool) + parameter_canon = ParameterCanon() + # compute affine mapping for each canonical parameter + for i, p_id in enumerate(solver_interface.canon_p_ids): + + affine_map = solver_interface.get_affine_map(p_id, param_prob, constraint_info) + + if p_id in solver_interface.canon_p_ids_constr_vec: + affine_map = update_to_dense_mapping(affine_map, param_prob, solver_interface) + + if p_id == 'd': + parameter_canon.nonzero_d = affine_map.mapping.nnz > 0 + + adjacency = update_adjacency_matrix(adjacency, i, parameter_info, affine_map.mapping) + + # take sparsity into account + affine_map.mapping = affine_map.mapping[:, parameter_info.sparsity_mask] + + # compute default values of canonical parameters + affine_map, parameter_canon = set_default_values(affine_map, p_id, parameter_canon, parameter_info, solver_interface, solver_name) + + parameter_canon.p_id_to_mapping[p_id] = affine_map.mapping.tocsr() + parameter_canon.p_id_to_changes[p_id] = affine_map.mapping[:, :-1].nnz > 0 + parameter_canon.p_id_to_size[p_id] = affine_map.mapping.shape[0] + return adjacency, parameter_canon + + +def update_to_dense_mapping(affine_map, param_prob, solver_interface): + mapping_to_sparse = solver_interface.sign_constr_vec * param_prob.reduced_A.reduced_mat[ + affine_map.mapping_rows] + mapping_to_dense = sparse.lil_matrix( + np.zeros((affine_map.shape[0], mapping_to_sparse.shape[1]))) + for i_data in range(mapping_to_sparse.shape[0]): + mapping_to_dense[affine_map.indices[i_data], :] = mapping_to_sparse[i_data, :] + affine_map.mapping = sparse.csc_matrix(mapping_to_dense) + return affine_map + + +def set_default_values(affine_map, p_id, parameter_canon, parameter_info, solver_interface, + solver_name): + if p_id.isupper(): + rows_nonzero, _ = affine_map.mapping.nonzero() + canon_p_data_nonzero = np.sort(np.unique(rows_nonzero)) + affine_map.mapping = affine_map.mapping[canon_p_data_nonzero, :] + canon_p_data = affine_map.mapping @ parameter_info.flat_usp + # compute 'indptr' to construct sparse matrix from 'canon_p_data' and 'indices' + if solver_name in ['OSQP', 'SCS']: + if p_id == 'P': + affine_map.indptr = solver_interface.indptr_obj + elif p_id == 'A': + affine_map.indptr = solver_interface.indptr_constr[:-1] + elif solver_name == 'ECOS': + indptr_original = solver_interface.indptr_constr[:-1] + affine_map.indptr = 0 * indptr_original + for r in affine_map.mapping_rows: + for c in range(affine_map.shape[1]): + if indptr_original[c] <= r < indptr_original[c + 1]: + affine_map.indptr[c + 1:] += 1 + break + # compute 'indices_usp' and 'indptr_usp' + indices_usp = affine_map.indices[canon_p_data_nonzero] + indptr_usp = 0 * affine_map.indptr + for r in canon_p_data_nonzero: + for c in range(affine_map.shape[1]): + if affine_map.indptr[c] <= r < affine_map.indptr[c + 1]: + indptr_usp[c + 1:] += 1 + break + csc_mat = sparse.csc_matrix((canon_p_data, indices_usp, indptr_usp), + shape=affine_map.shape) + if solver_name == 'OSQP': + parameter_canon.p_csc[p_id] = csc_mat + parameter_canon.p[p_id] = utils.csc_to_dict(csc_mat) + else: + canon_p_data = affine_map.mapping @ parameter_info.flat_usp + if solver_name == 'OSQP' and p_id == 'l': + parameter_canon.p[p_id] = np.concatenate( + (canon_p_data, -np.inf * np.ones(solver_interface.n_ineq)), axis=0) + else: + parameter_canon.p[p_id] = canon_p_data + + return affine_map, parameter_canon + + +def get_variable_info(problem, inverse_data) -> PrimalVariableInfo: variables = problem.variables() var_names = [var.name() for var in variables] var_ids = [var.id for var in variables] @@ -119,7 +238,8 @@ def generate_code(problem, code_dir='CPG_code', solver=None, unroll=False, prefi var_name_to_offset = {n: o for n, o in zip(var_names, var_offsets)} var_shapes = [var.shape for var in variables] var_sizes = [var.size for var in variables] - var_sym = [var.attributes['symmetric'] or var.attributes['PSD'] or var.attributes['NSD'] for var in variables] + var_sym = [var.attributes['symmetric'] or var.attributes['PSD'] or var.attributes['NSD'] for var + in variables] var_name_to_sym = {n: s for n, s in zip(var_names, var_sym)} var_name_to_indices = {} for var_name, offset, shape, sym in zip(var_names, var_offsets, var_shapes, var_sym): @@ -128,7 +248,7 @@ def generate_code(problem, code_dir='CPG_code', solver=None, unroll=False, prefi (_, col) = fill_coefficient.nonzero() var_name_to_indices[var_name] = offset + col else: - var_name_to_indices[var_name] = np.arange(offset, offset+np.prod(shape)) + var_name_to_indices[var_name] = np.arange(offset, offset + np.prod(shape)) var_name_to_size = {var.name(): var.size for var in variables} var_name_to_shape = {var.name(): var.shape for var in variables} var_name_to_init = dict() @@ -138,7 +258,13 @@ def generate_code(problem, code_dir='CPG_code', solver=None, unroll=False, prefi else: var_name_to_init[var.name()] = np.zeros(shape=var.shape) - # dual variable information + variable_info = PrimalVariableInfo(var_name_to_offset, var_name_to_indices, var_name_to_size, + var_sizes, var_name_to_shape, var_name_to_init, + var_name_to_sym, var_sym) + return variable_info + + +def get_dual_variable_info(inverse_data, solver_name) -> DualVariableInfo: # get chain of constraint id maps for 'CvxAttr2Constr' and 'Canonicalization' objects dual_id_maps = [] if solver_name == 'OSQP': @@ -166,10 +292,10 @@ def generate_code(problem, code_dir='CPG_code', solver=None, unroll=False, prefi con_canon_dict = {c.id: c for c in con_canon} d_canon_offsets = np.cumsum([0] + [c.args[0].size for c in con_canon[:-1]]) if solver_name in ['OSQP', 'SCS']: - d_vectors = ['y']*len(d_canon_offsets) + d_vectors = ['y'] * len(d_canon_offsets) else: n_split_yz = len(inverse_data[-1][ECOS.EQ_CONSTR]) - d_vectors = ['y']*n_split_yz + ['z']*(len(d_canon_offsets)-n_split_yz) + d_vectors = ['y'] * n_split_yz + ['z'] * (len(d_canon_offsets) - n_split_yz) d_canon_offsets[n_split_yz:] -= d_canon_offsets[n_split_yz] d_canon_offsets_dict = {c.id: off for c, off in zip(con_canon, d_canon_offsets)} # select for user-defined constraints @@ -192,510 +318,76 @@ def generate_code(problem, code_dir='CPG_code', solver=None, unroll=False, prefi else: d_name_to_init[name] = np.zeros(shape=shape) - # user parameters - user_p_num = len(p_prob.parameters) - user_p_names = [par.name() for par in p_prob.parameters] - user_p_ids = list(p_prob.param_id_to_col.keys()) - user_p_id_to_col = p_prob.param_id_to_col - user_p_id_to_size = p_prob.param_id_to_size - user_p_id_to_param = p_prob.id_to_param - user_p_total_size = p_prob.total_param_size - user_p_name_to_shape = {user_p_id_to_param[p_id].name(): user_p_id_to_param[p_id].shape - for p_id in user_p_id_to_size.keys()} - user_p_name_to_size_usp = {user_p_id_to_param[p_id].name(): size for p_id, size in user_p_id_to_size.items()} - user_p_name_to_sparsity = {} - user_p_name_to_sparsity_type = {} - user_p_sparsity_mask = np.ones(user_p_total_size + 1, dtype=bool) - for p in p_prob.parameters: - if p.attributes['sparsity'] is not None: - user_p_name_to_size_usp[p.name()] = len(p.attributes['sparsity']) - user_p_name_to_sparsity[p.name()] = np.sort([coord[0]+p.shape[0]*coord[1] - for coord in p.attributes['sparsity']]) - if p.attributes['diag']: - user_p_name_to_sparsity_type[p.name()] = 'diag' - else: - user_p_name_to_sparsity_type[p.name()] = 'general' - user_p_sparsity_mask[user_p_id_to_col[p.id]:user_p_id_to_col[p.id]+user_p_id_to_size[p.id]] = False - user_p_sparsity_mask[user_p_id_to_col[p.id] + user_p_name_to_sparsity[p.name()]] = True - user_p_col_to_name_usp = {} - cum_sum = 0 - for name, size in user_p_name_to_size_usp.items(): - user_p_col_to_name_usp[cum_sum] = name - cum_sum += size - user_p_writable = dict() - for p_name, p in zip(user_p_names, p_prob.parameters): - if p.value is None: - p.project_and_assign(np.random.randn(*p.shape)) - if type(p.value) is sparse.dia_matrix: - p.value = p.value.toarray() - if len(p.shape) < 2: - # dealing with scalar or vector - user_p_writable[p_name] = p.value - else: - # dealing with matrix - if p_name in user_p_name_to_sparsity.keys(): - dense_value = p.value.flatten(order='F') - sparse_value = np.zeros(len(user_p_name_to_sparsity[p_name])) - for i, idx in enumerate(user_p_name_to_sparsity[p_name]): - sparse_value[i] = dense_value[idx] - user_p_writable[p_name] = sparse_value - else: - user_p_writable[p_name] = p.value.flatten(order='F') - - def user_p_value(user_p_id): - return np.array(user_p_id_to_param[user_p_id].value) - user_p_flat = cI.get_parameter_vector(user_p_total_size, user_p_id_to_col, user_p_id_to_size, user_p_value) - user_p_flat_usp = user_p_flat[user_p_sparsity_mask] - - canon_p = {} - canon_p_csc = {} - canon_p_id_to_mapping = {} - canon_p_id_to_changes = {} - canon_p_id_to_size = {} - nonzero_d = True - - # dimensions and information specific to solver - - if solver_name == 'OSQP': - - canon_p_ids = ['P', 'q', 'd', 'A', 'l', 'u'] - canon_p_ids_constr_vec = ['l', 'u'] - sign_constr_vec = -1 - n_var = data['n_var'] - n_eq = data['n_eq'] - n_ineq = data['n_ineq'] - - indices_obj, indptr_obj, shape_obj = p_prob.reduced_P.problem_data_index - indices_constr, indptr_constr, shape_constr = p_prob.reduced_A.problem_data_index - - canon_constants = {} - - elif solver_name == 'SCS': - - canon_p_ids = ['c', 'd', 'A', 'b'] - canon_p_ids_constr_vec = ['b'] - sign_constr_vec = 1 - n_var = p_prob.x.size - n_eq = data['A'].shape[0] - n_ineq = 0 - - indices_obj, indptr_obj, shape_obj = None, None, None - indices_constr, indptr_constr, shape_constr = p_prob.reduced_A.problem_data_index - - canon_constants = {'n': n_var, 'm': n_eq, 'z': p_prob.cone_dims.zero, 'l': p_prob.cone_dims.nonneg, - 'q': np.array(p_prob.cone_dims.soc), 'qsize': len(p_prob.cone_dims.soc)} - - elif solver_name == 'ECOS': - - canon_p_ids = ['c', 'd', 'A', 'b', 'G', 'h'] - canon_p_ids_constr_vec = ['b', 'h'] - sign_constr_vec = 1 - n_var = p_prob.x.size - n_eq = p_prob.cone_dims.zero - n_ineq = data['G'].shape[0] - - indices_obj, indptr_obj, shape_obj = None, None, None - indices_constr, indptr_constr, shape_constr = p_prob.reduced_A.problem_data_index + dual_variable_info = DualVariableInfo(d_name_to_offset, d_name_to_indices, d_name_to_size, + d_sizes, d_name_to_shape, d_name_to_init, d_name_to_vec) + return dual_variable_info - canon_constants = {'n': n_var, 'm': n_ineq, 'p': n_eq, 'l': p_prob.cone_dims.nonneg, - 'n_cones': len(p_prob.cone_dims.soc), 'q': np.array(p_prob.cone_dims.soc), - 'e': p_prob.cone_dims.exp} - else: - raise ValueError("Problem class cannot be addressed by the OSQP, SCS, or ECOS solver!") - - n_data_constr = len(indices_constr) - n_data_constr_vec = indptr_constr[-1] - indptr_constr[-2] +def get_constraint_info(solver_interface) -> ConstraintInfo: + n_data_constr = len(solver_interface.indices_constr) + n_data_constr_vec = solver_interface.indptr_constr[-1] - solver_interface.indptr_constr[-2] n_data_constr_mat = n_data_constr - n_data_constr_vec - mapping_rows_eq = np.nonzero(indices_constr < n_eq)[0] - mapping_rows_ineq = np.nonzero(indices_constr >= n_eq)[0] - - adjacency = np.zeros(shape=(len(canon_p_ids), user_p_num), dtype=bool) - - for i, p_id in enumerate(canon_p_ids): - - # compute affine mapping for each canonical parameter - mapping_rows = [] - mapping = [] - indices = [] - indptr = [] - shape = () - - if solver_name == 'OSQP': - - if p_id == 'P': - mapping = p_prob.reduced_P.reduced_mat - indices = indices_obj - shape = (n_var, n_var) - elif p_id == 'q': - mapping = p_prob.q[:-1] - elif p_id == 'd': - mapping = p_prob.q[-1] - elif p_id == 'A': - mapping = p_prob.reduced_A.reduced_mat[:n_data_constr_mat] - indices = indices_constr[:n_data_constr_mat] - shape = (n_eq+n_ineq, n_var) - elif p_id == 'l': - mapping_rows_eq = np.nonzero(indices_constr < n_eq)[0] - mapping_rows = mapping_rows_eq[mapping_rows_eq >= n_data_constr_mat] # mapping to the finite part of l - indices = indices_constr[mapping_rows] - shape = (n_eq, 1) - elif p_id == 'u': - mapping_rows = np.arange(n_data_constr_mat, n_data_constr) - indices = indices_constr[mapping_rows] - shape = (n_eq+n_ineq, 1) - else: - raise ValueError('Unknown OSQP parameter name: "%s"' % p_id) - - elif solver_name in ['SCS', 'ECOS']: - - if p_id == 'c': - mapping = p_prob.c[:-1] - elif p_id == 'd': - mapping = p_prob.c[-1] - elif p_id == 'A': - mapping_rows = mapping_rows_eq[mapping_rows_eq < n_data_constr_mat] - shape = (n_eq, n_var) - elif p_id == 'G': - mapping_rows = mapping_rows_ineq[mapping_rows_ineq < n_data_constr_mat] - shape = (n_ineq, n_var) - elif p_id == 'b': - mapping_rows = mapping_rows_eq[mapping_rows_eq >= n_data_constr_mat] - shape = (n_eq, 1) - elif p_id == 'h': - mapping_rows = mapping_rows_ineq[mapping_rows_ineq >= n_data_constr_mat] - shape = (n_ineq, 1) - else: - raise ValueError('Unknown %s parameter name: "%s"' % (solver_name, p_id)) - - if p_id in ['A', 'b']: - indices = indices_constr[mapping_rows] - elif p_id in ['G', 'h']: - indices = indices_constr[mapping_rows] - n_eq - - if p_id.isupper(): - mapping = -p_prob.reduced_A.reduced_mat[mapping_rows] - - if p_id in canon_p_ids_constr_vec: - mapping_to_sparse = sign_constr_vec*p_prob.reduced_A.reduced_mat[mapping_rows] - mapping_to_dense = sparse.lil_matrix(np.zeros((shape[0], mapping_to_sparse.shape[1]))) - for i_data in range(mapping_to_sparse.shape[0]): - mapping_to_dense[indices[i_data], :] = mapping_to_sparse[i_data, :] - mapping = sparse.csc_matrix(mapping_to_dense) - - if p_id == 'd': - nonzero_d = mapping.nnz > 0 - - # compute adjacency matrix - for j in range(user_p_num): - column_slice = slice(user_p_id_to_col[user_p_ids[j]], user_p_id_to_col[user_p_ids[j + 1]]) - if mapping[:, column_slice].nnz > 0: - adjacency[i, j] = True - - # take sparsity into account - mapping = mapping[:, user_p_sparsity_mask] - - # compute default values of canonical parameters - if p_id.isupper(): - rows_nonzero, _ = mapping.nonzero() - canon_p_data_nonzero = np.sort(np.unique(rows_nonzero)) - mapping = mapping[canon_p_data_nonzero, :] - canon_p_data = mapping @ user_p_flat_usp - # compute 'indptr' to construct sparse matrix from 'canon_p_data' and 'indices' - if solver_name in ['OSQP', 'SCS']: - if p_id == 'P': - indptr = indptr_obj - elif p_id == 'A': - indptr = indptr_constr[:-1] - elif solver_name == 'ECOS': - indptr_original = indptr_constr[:-1] - indptr = 0 * indptr_original - for r in mapping_rows: - for c in range(shape[1]): - if indptr_original[c] <= r < indptr_original[c + 1]: - indptr[c + 1:] += 1 - break - # compute 'indices_usp' and 'indptr_usp' - indices_usp = indices[canon_p_data_nonzero] - indptr_usp = 0 * indptr - for r in canon_p_data_nonzero: - for c in range(shape[1]): - if indptr[c] <= r < indptr[c + 1]: - indptr_usp[c + 1:] += 1 - break - csc_mat = sparse.csc_matrix((canon_p_data, indices_usp, indptr_usp), shape=shape) - if solver_name == 'OSQP': - canon_p_csc[p_id] = csc_mat - canon_p[p_id] = utils.csc_to_dict(csc_mat) - else: - canon_p_data = mapping @ user_p_flat_usp - if solver_name == 'OSQP' and p_id == 'l': - canon_p[p_id] = np.concatenate((canon_p_data, -np.inf * np.ones(n_ineq)), axis=0) - else: - canon_p[p_id] = canon_p_data - - canon_p_id_to_mapping[p_id] = mapping.tocsr() - canon_p_id_to_changes[p_id] = mapping[:, :-1].nnz > 0 - canon_p_id_to_size[p_id] = mapping.shape[0] - - if solver_name == 'OSQP': - - # solver settings - settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', 'alpha', - 'scaled_termination', 'check_termination', 'warm_start'] - settings_types = ['c_float', 'c_int', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_int', 'c_int', - 'c_int'] - settings_defaults = [] - - # OSQP codegen - osqp_obj = osqp.OSQP() - osqp_obj.setup(P=canon_p_csc['P'], q=canon_p['q'], A=canon_p_csc['A'], l=canon_p['l'], u=canon_p['u']) - if system() == 'Windows': - cmake_generator = 'MinGW Makefiles' - elif system() == 'Linux' or system() == 'Darwin': - cmake_generator = 'Unix Makefiles' - else: - raise OSError('Unknown operating system!') - osqp_obj.codegen(os.path.join(code_dir, 'c', 'solver_code'), project_type=cmake_generator, - parameters='matrices', force_rewrite=True) - - # copy license files - shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'osqp-python', 'LICENSE'), - os.path.join(solver_code_dir, 'LICENSE')) - shutil.copy(os.path.join(cvxpygen_directory, 'template', 'LICENSE'), code_dir) - - elif solver_name == 'SCS': - - # solver settings - settings_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', 'eps_rel', - 'eps_infeas', 'alpha', 'time_limit_secs', 'verbose', 'warm_start', 'acceleration_lookback', - 'acceleration_interval', 'write_data_filename', 'log_csv_filename'] - settings_types = ['c_int', 'c_float', 'c_int', 'c_float', 'c_int', 'c_float', 'c_float', 'c_float', 'c_float', - 'c_float', 'c_int', 'c_int', 'c_int', 'c_int', 'const char*', 'const char*'] - settings_defaults = ['1', '0.1', '1', '1e-6', '1e5', '1e-4', '1e-4', '1e-7', '1.5', '0', '0', '0', '0', '1', - 'SCS_NULL', 'SCS_NULL'] - - # copy sources - if os.path.isdir(solver_code_dir): - shutil.rmtree(solver_code_dir) - os.mkdir(solver_code_dir) - dirs_to_copy = ['src', 'include', 'linsys', 'cmake'] - for dtc in dirs_to_copy: - shutil.copytree(os.path.join(cvxpygen_directory, 'solvers', 'scs', dtc), os.path.join(solver_code_dir, dtc)) - files_to_copy = ['scs.mk', 'CMakeLists.txt', 'LICENSE.txt'] - for fl in files_to_copy: - shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'scs', fl), - os.path.join(solver_code_dir, fl)) - shutil.copy(os.path.join(cvxpygen_directory, 'template', 'LICENSE'), code_dir) - - # disable BLAS and LAPACK - with open(os.path.join(code_dir, 'c', 'solver_code', 'scs.mk'), 'r') as f: - scs_mk_data = f.read() - scs_mk_data = scs_mk_data.replace('USE_LAPACK = 1', 'USE_LAPACK = 0') - with open(os.path.join(code_dir, 'c', 'solver_code', 'scs.mk'), 'w') as f: - f.write(scs_mk_data) - - # modify CMakeLists.txt - with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'r') as f: - cmake_data = f.read() - cmake_data = cmake_data.replace(' include/', ' ${CMAKE_CURRENT_SOURCE_DIR}/include/') - cmake_data = cmake_data.replace(' src/', ' ${CMAKE_CURRENT_SOURCE_DIR}/src/') - cmake_data = cmake_data.replace(' ${LINSYS}/', ' ${CMAKE_CURRENT_SOURCE_DIR}/${LINSYS}/') - with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'w') as f: - f.write(cmake_data) - - # adjust top-level CMakeLists.txt - with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'r') as f: - cmake_data = f.read() - indent = ' ' * 6 - sdir = '${CMAKE_CURRENT_SOURCE_DIR}/solver_code/' - cmake_data = cmake_data.replace(sdir + 'include', - sdir + 'include\n' + - indent + sdir + 'linsys') - with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'w') as f: - f.write(cmake_data) - - # adjust setup.py - with open(os.path.join(code_dir, 'setup.py'), 'r') as f: - setup_text = f.read() - indent = ' ' * 30 - setup_text = setup_text.replace("os.path.join('c', 'solver_code', 'include'),", - "os.path.join('c', 'solver_code', 'include'),\n" + - indent + "os.path.join('c', 'solver_code', 'linsys'),") - with open(os.path.join(code_dir, 'setup.py'), 'w') as f: - f.write(setup_text) - - elif solver_name == 'ECOS': - - # solver settings - settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', 'reltol_inacc', 'maxit'] - settings_types = ['c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_int'] - settings_defaults = ['1e-8', '1e-8', '1e-8', '1e-4', '5e-5', '5e-5', '100'] - - # copy sources - if os.path.isdir(solver_code_dir): - shutil.rmtree(solver_code_dir) - os.mkdir(solver_code_dir) - dirs_to_copy = ['src', 'include', 'external', 'ecos_bb'] - for dtc in dirs_to_copy: - shutil.copytree(os.path.join(cvxpygen_directory, 'solvers', 'ecos', dtc), os.path.join(solver_code_dir, dtc)) - shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'ecos', 'CMakeLists.txt'), - os.path.join(solver_code_dir, 'CMakeLists.txt')) - shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'ecos', 'COPYING'), - os.path.join(solver_code_dir, 'COPYING')) - shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'ecos', 'COPYING'), - os.path.join(code_dir, 'COPYING')) - - # adjust print level - with open(os.path.join(code_dir, 'c', 'solver_code', 'include', 'glblopts.h'), 'r') as f: - glbl_opts_data = f.read() - glbl_opts_data = glbl_opts_data.replace('#define PRINTLEVEL (2)', '#define PRINTLEVEL (0)') - with open(os.path.join(code_dir, 'c', 'solver_code', 'include', 'glblopts.h'), 'w') as f: - f.write(glbl_opts_data) - - # adjust top-level CMakeLists.txt - with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'r') as f: - cmake_data = f.read() - indent = ' ' * 6 - sdir = '${CMAKE_CURRENT_SOURCE_DIR}/solver_code/' - cmake_data = cmake_data.replace(sdir + 'include', - sdir + 'include\n' + - indent + sdir + 'external/SuiteSparse_config\n' + - indent + sdir + 'external/amd/include\n' + - indent + sdir + 'external/ldl/include') - with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'w') as f: - f.write(cmake_data) - - # remove library target from ECOS CMakeLists.txt - with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'r') as f: - lines = f.readlines() - with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'w') as f: - for line in lines: - if '# ECOS library' in line: - break - f.write(line) - - # adjust setup.py - with open(os.path.join(code_dir, 'setup.py'), 'r') as f: - setup_text = f.read() - indent = ' ' * 30 - setup_text = setup_text.replace("os.path.join('c', 'solver_code', 'include'),", - "os.path.join('c', 'solver_code', 'include'),\n" + - indent+"os.path.join('c', 'solver_code', 'external', 'SuiteSparse_config'),\n" + - indent+"os.path.join('c', 'solver_code', 'external', 'amd', 'include'),\n" + - indent+"os.path.join('c', 'solver_code', 'external', 'ldl', 'include'),") - setup_text = setup_text.replace("license='Apache 2.0'", "license='GPL 3.0'") - with open(os.path.join(code_dir, 'setup.py'), 'w') as f: - f.write(setup_text) - - else: - raise ValueError("Problem class cannot be addressed by the OSQP, SCS, or ECOS solver!") - - user_p_name_to_canon_outdated = {user_p_name: [canon_p_ids[j] for j in np.nonzero(adjacency[:, i])[0]] - for i, user_p_name in enumerate(user_p_names)} - settings_names_to_type = {name: typ for name, typ in zip(settings_names, settings_types)} - settings_names_to_default = {name: typ for name, typ in zip(settings_names, settings_defaults)} - - ret_prim_func_exists = any(var_sym) or any([s == 1 for s in var_sizes]) or solver_name == 'ECOS' - ret_dual_func_exists = any([s == 1 for s in d_sizes]) or solver_name == 'ECOS' + mapping_rows_eq = np.nonzero(solver_interface.indices_constr < solver_interface.n_eq)[0] + mapping_rows_ineq = np.nonzero(solver_interface.indices_constr >= solver_interface.n_eq)[0] - # summarize information on options, codegen, user parameters / variables, canonicalization in dictionaries + return ConstraintInfo(n_data_constr, n_data_constr_mat, mapping_rows_eq, mapping_rows_ineq) - info_opt = {C.CODE_DIR: code_dir, - C.SOLVER_NAME: solver_name, - C.UNROLL: unroll, - C.PREFIX: prefix} - info_cg = {C.RET_PRIM_FUNC_EXISTS: ret_prim_func_exists, - C.RET_DUAL_FUNC_EXISTS: ret_dual_func_exists, - C.NONZERO_D: nonzero_d, - C.IS_MAXIMIZATION: type(problem.objective) == Maximize} +def update_adjacency_matrix(adjacency, i, parameter_info, mapping) -> np.ndarray: + # compute adjacency matrix + for j in range(parameter_info.num): + column_slice = slice(parameter_info.id_to_col[parameter_info.ids[j]], + parameter_info.id_to_col[parameter_info.ids[j + 1]]) + if mapping[:, column_slice].nnz > 0: + adjacency[i, j] = True + return adjacency - info_usr = {C.P_WRITABLE: user_p_writable, - C.P_FLAT_USP: user_p_flat_usp, - C.P_COL_TO_NAME_USP: user_p_col_to_name_usp, - C.P_NAME_TO_SHAPE: user_p_name_to_shape, - C.P_NAME_TO_SIZE: user_p_name_to_size_usp, - C.P_NAME_TO_CANON_OUTDATED: user_p_name_to_canon_outdated, - C.P_NAME_TO_SPARSITY: user_p_name_to_sparsity, - C.P_NAME_TO_SPARSITY_TYPE: user_p_name_to_sparsity_type, - C.V_NAME_TO_INDICES: var_name_to_indices, - C.V_NAME_TO_SIZE: var_name_to_size, - C.V_NAME_TO_SHAPE: var_name_to_shape, - C.V_NAME_TO_INIT: var_name_to_init, - C.V_NAME_TO_SYM: var_name_to_sym, - C.V_NAME_TO_OFFSET: var_name_to_offset, - C.D_NAME_TO_INIT: d_name_to_init, - C.D_NAME_TO_VEC: d_name_to_vec, - C.D_NAME_TO_OFFSET: d_name_to_offset, - C.D_NAME_TO_SIZE: d_name_to_size, - C.D_NAME_TO_SHAPE: d_name_to_shape, - C.D_NAME_TO_INDICES: d_name_to_indices} - - info_can = {C.P: canon_p, - C.P_ID_TO_SIZE: canon_p_id_to_size, - C.P_ID_TO_CHANGES: canon_p_id_to_changes, - C.P_ID_TO_MAPPING: canon_p_id_to_mapping, - C.CONSTANTS: canon_constants, - C.SETTINGS_NAMES_TO_TYPE: settings_names_to_type, - C.SETTINGS_NAMES_TO_DEFAULT: settings_names_to_default, - C.SETTINGS_LIST: settings_names} +def write_c_code(code_dir: str, info_opt: dict, info_cg: dict, info_can: dict, info_usr: dict, + problem: cp.Problem) -> None: # 'workspace' prototypes with open(os.path.join(code_dir, 'c', 'include', 'cpg_workspace.h'), 'w') as f: utils.write_workspace_prot(f, info_opt, info_usr, info_can) - # 'workspace' definitions with open(os.path.join(code_dir, 'c', 'src', 'cpg_workspace.c'), 'w') as f: utils.write_workspace_def(f, info_opt, info_usr, info_can) - # 'solve' prototypes with open(os.path.join(code_dir, 'c', 'include', 'cpg_solve.h'), 'w') as f: utils.write_solve_prot(f, info_opt, info_cg, info_usr, info_can) - # 'solve' definitions with open(os.path.join(code_dir, 'c', 'src', 'cpg_solve.c'), 'w') as f: utils.write_solve_def(f, info_opt, info_cg, info_usr, info_can) - # 'example' definitions with open(os.path.join(code_dir, 'c', 'src', 'cpg_example.c'), 'w') as f: utils.write_example_def(f, info_opt, info_usr) - # adapt top-level CMakeLists.txt with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'r') as f: cmake_data = f.read() cmake_data = utils.replace_cmake_data(cmake_data, info_opt) with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'w') as f: f.write(cmake_data) - # adapt solver CMakeLists.txt with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'a') as f: utils.write_canon_cmake(f, info_opt) - # binding module prototypes with open(os.path.join(code_dir, 'cpp', 'include', 'cpg_module.hpp'), 'w') as f: utils.write_module_prot(f, info_opt, info_usr) - # binding module definition with open(os.path.join(code_dir, 'cpp', 'src', 'cpg_module.cpp'), 'w') as f: utils.write_module_def(f, info_opt, info_usr, info_can) - # adapt setup.py with open(os.path.join(code_dir, 'setup.py'), 'r') as f: setup_data = f.read() setup_data = utils.replace_setup_data(setup_data) with open(os.path.join(code_dir, 'setup.py'), 'w') as f: f.write(setup_data) - # custom CVXPY solve method with open(os.path.join(code_dir, 'cpg_solver.py'), 'w') as f: utils.write_method(f, info_opt, info_usr) - # serialize problem formulation with open(os.path.join(code_dir, 'problem.pickle'), 'wb') as f: pickle.dump(cp.Problem(problem.objective, problem.constraints), f) - # html documentation file with open(os.path.join(code_dir, 'README.html'), 'r') as f: html_data = f.read() @@ -703,13 +395,136 @@ def user_p_value(user_p_id): with open(os.path.join(code_dir, 'README.html'), 'w') as f: f.write(html_data) - sys.stdout.write('CVXPYgen finished generating code.\n') - # compile python module - if wrapper: - sys.stdout.write('Compiling python wrapper with CVXPYgen ... \n') - p_dir = os.getcwd() - os.chdir(code_dir) - call([sys.executable, 'setup.py', '--quiet', 'build_ext', '--inplace']) - os.chdir(p_dir) - sys.stdout.write("CVXPYgen finished compiling python wrapper.\n") +def adjust_problem_name(prefix): + if prefix != '': + if not prefix[0].isalpha(): + prefix = '_' + prefix + prefix = prefix + '_' + return prefix + + +def get_parameter_info(p_prob) -> ParameterInfo: + user_p_num = len(p_prob.parameters) + user_p_names = [par.name() for par in p_prob.parameters] + user_p_ids = list(p_prob.param_id_to_col.keys()) + user_p_id_to_col = p_prob.param_id_to_col + user_p_id_to_size = p_prob.param_id_to_size + user_p_id_to_param = p_prob.id_to_param + user_p_total_size = p_prob.total_param_size + user_p_name_to_shape = {user_p_id_to_param[p_id].name(): user_p_id_to_param[p_id].shape + for p_id in user_p_id_to_size.keys()} + user_p_name_to_size_usp = {user_p_id_to_param[p_id].name(): size for p_id, size in + user_p_id_to_size.items()} + user_p_name_to_sparsity = {} + user_p_name_to_sparsity_type = {} + user_p_sparsity_mask = np.ones(user_p_total_size + 1, dtype=bool) + for p in p_prob.parameters: + if p.attributes['sparsity'] is not None: + user_p_name_to_size_usp[p.name()] = len(p.attributes['sparsity']) + user_p_name_to_sparsity[p.name()] = np.sort([coord[0] + p.shape[0] * coord[1] + for coord in p.attributes['sparsity']]) + if p.attributes['diag']: + user_p_name_to_sparsity_type[p.name()] = 'diag' + else: + user_p_name_to_sparsity_type[p.name()] = 'general' + user_p_sparsity_mask[ + user_p_id_to_col[p.id]:user_p_id_to_col[p.id] + user_p_id_to_size[p.id]] = False + user_p_sparsity_mask[user_p_id_to_col[p.id] + user_p_name_to_sparsity[p.name()]] = True + user_p_col_to_name_usp = {} + cum_sum = 0 + for name, size in user_p_name_to_size_usp.items(): + user_p_col_to_name_usp[cum_sum] = name + cum_sum += size + user_p_writable = dict() + for p_name, p in zip(user_p_names, p_prob.parameters): + if p.value is None: + p.project_and_assign(np.random.randn(*p.shape)) + if type(p.value) is sparse.dia_matrix: + p.value = p.value.toarray() + if len(p.shape) < 2: + # dealing with scalar or vector + user_p_writable[p_name] = p.value + else: + # dealing with matrix + if p_name in user_p_name_to_sparsity.keys(): + dense_value = p.value.flatten(order='F') + sparse_value = np.zeros(len(user_p_name_to_sparsity[p_name])) + for i, idx in enumerate(user_p_name_to_sparsity[p_name]): + sparse_value[i] = dense_value[idx] + user_p_writable[p_name] = sparse_value + else: + user_p_writable[p_name] = p.value.flatten(order='F') + + def user_p_value(user_p_id): + """ + Returns the value of the parameter with the given ID. + """ + return np.array(user_p_id_to_param[user_p_id].value) + + user_p_flat = cI.get_parameter_vector(user_p_total_size, user_p_id_to_col, user_p_id_to_size, + user_p_value) + user_p_flat_usp = user_p_flat[user_p_sparsity_mask] + parameter_info = ParameterInfo(user_p_col_to_name_usp, user_p_flat_usp, user_p_id_to_col, + user_p_ids, user_p_name_to_shape, user_p_name_to_size_usp, + user_p_name_to_sparsity, user_p_name_to_sparsity_type, + user_p_names, user_p_num, user_p_sparsity_mask, user_p_writable) + return parameter_info + + +def handle_sparsity(p_prob: cp.Problem) -> None: + for p in p_prob.parameters: + if p.attributes['sparsity'] is not None: + if p.size == 1: + warnings.warn('Ignoring sparsity pattern for scalar parameter %s!' % p.name()) + p.attributes['sparsity'] = None + elif max(p.shape) == p.size: + warnings.warn('Ignoring sparsity pattern for vector parameter %s!' % p.name()) + p.attributes['sparsity'] = None + else: + for coord in p.attributes['sparsity']: + if coord[0] < 0 or coord[1] < 0 or coord[0] >= p.shape[0] or coord[1] >= \ + p.shape[1]: + warnings.warn('Invalid sparsity pattern for parameter %s - out of range! ' + 'Ignoring sparsity pattern.' % p.name()) + p.attributes['sparsity'] = None + break + p.attributes['sparsity'] = list(set(p.attributes['sparsity'])) + elif p.attributes['diag']: + p.attributes['sparsity'] = [(i, i) for i in range(p.shape[0])] + if p.attributes['sparsity'] is not None and p.value is not None: + for i in range(p.shape[0]): + for j in range(p.shape[1]): + if (i, j) not in p.attributes['sparsity'] and p.value[i, j] != 0: + warnings.warn( + 'Ignoring nonzero value outside of sparsity pattern for parameter %s!' % p.name()) + p.value[i, j] = 0 + + +def compile_python_module(code_dir: str): + sys.stdout.write('Compiling python wrapper with CVXPYgen ... \n') + p_dir = os.getcwd() + os.chdir(code_dir) + call([sys.executable, 'setup.py', '--quiet', 'build_ext', '--inplace']) + os.chdir(p_dir) + sys.stdout.write("CVXPYgen finished compiling python wrapper.\n") + + +def create_folder_structure(code_dir: str): + cvxpygen_directory = os.path.dirname(os.path.realpath(__file__)) + + # create code directory and copy template files + if os.path.isdir(code_dir): + shutil.rmtree(code_dir) + os.mkdir(code_dir) + os.mkdir(os.path.join(code_dir, 'c')) + for d in ['src', 'include', 'build']: + os.mkdir(os.path.join(code_dir, 'c', d)) + os.mkdir(os.path.join(code_dir, 'cpp')) + for d in ['src', 'include']: + os.mkdir(os.path.join(code_dir, 'cpp', d)) + shutil.copy(os.path.join(cvxpygen_directory, 'template', 'CMakeLists.txt'), + os.path.join(code_dir, 'c')) + for file in ['setup.py', 'README.html', '__init__.py']: + shutil.copy(os.path.join(cvxpygen_directory, 'template', file), code_dir) + return cvxpygen_directory diff --git a/cvxpygen/mappings.py b/cvxpygen/mappings.py new file mode 100644 index 0000000..91b6238 --- /dev/null +++ b/cvxpygen/mappings.py @@ -0,0 +1,68 @@ +from dataclasses import dataclass, field + +import scipy.sparse as sp +import numpy as np + + +@dataclass +class AffineMap: + mapping_rows: list = field(default_factory=list) + mapping: list = field(default_factory=list) + indices: list = field(default_factory=list) + indptr: list = field(default_factory=list) + shape = () + + +@dataclass +class ParameterCanon: + p: dict = field(default_factory=dict) + p_csc: dict[str, sp.csc_matrix] = field(default_factory=dict) + p_id_to_mapping: dict[str, sp.csr_matrix] = field(default_factory=dict) + p_id_to_changes: dict[str, bool] = field(default_factory=dict) + p_id_to_size: dict[str, int] = field(default_factory=dict) + nonzero_d = True + + +@dataclass +class ParameterInfo: + col_to_name_usp: dict[int, str] + flat_usp: np.ndarray + id_to_col: dict[int, int] + ids: list[int] + name_to_shape: dict[str, tuple] + name_to_size_usp: dict[str, int] + name_to_sparsity: dict[str, np.ndarray] + name_to_sparsity_type: dict[str, str] + names: list[str] + num: int + sparsity_mask: np.ndarray + writable: dict[str, np.ndarray] + + +@dataclass +class VariableInfo: + name_to_offset: dict[str, int] + name_to_indices: dict[str, np.ndarray] + name_to_size: dict[str, int] + sizes: list[int] + name_to_shape: dict[str, tuple] + name_to_init: dict[str, np.ndarray] + + +@dataclass +class PrimalVariableInfo(VariableInfo): + name_to_sym: dict[str, bool] + sym: list[bool] + + +@dataclass +class DualVariableInfo(VariableInfo): + name_to_vec: dict[str, str] + + +@dataclass +class ConstraintInfo: + n_data_constr: int + n_data_constr_mat: int + mapping_rows_eq: np.ndarray + mapping_rows_ineq: np.ndarray diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py new file mode 100644 index 0000000..8fe3236 --- /dev/null +++ b/cvxpygen/solvers.py @@ -0,0 +1,416 @@ +import os +import shutil +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from platform import system + +import numpy as np + +from cvxpygen.mappings import PrimalVariableInfo, DualVariableInfo, ConstraintInfo, AffineMap, \ + ParameterCanon + + +def get_interface_class(solver_name: str) -> "SolverInterface": + mapping = { + 'OSQP': OSQPInterface, + 'SCS': SCSInterface, + 'ECOS': ECOSInterface, + } + interface = mapping.get(solver_name, None) + if interface is None: + raise ValueError(f'Unsupported solver: {solver_name}.') + return interface + + +class SolverInterface(ABC): + + def __init__(self, n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, indices_constr, + indptr_constr, shape_constr, canon_constants): + self.n_var = n_var + self.n_eq = n_eq + self.n_ineq = n_ineq + self.indices_obj = indices_obj + self.indptr_obj = indptr_obj + self.shape_obj = shape_obj + self.indices_constr = indices_constr + self.indptr_constr = indptr_constr + self.shape_constr = shape_constr + self.canon_constants = canon_constants + + @property + @abstractmethod + def canon_p_ids(self): + pass + + @property + @abstractmethod + def canon_p_ids_constr_vec(self): + pass + + @property + @abstractmethod + def sign_constr_vec(self): + pass + + @property + @abstractmethod + def settings_names(self): + pass + + @property + @abstractmethod + def settings_types(self): + pass + + @property + @abstractmethod + def settings_defaults(self): + pass + + @staticmethod + def ret_prim_func_exists(variable_info: PrimalVariableInfo) -> bool: + return any(variable_info.sym) or any([s == 1 for s in variable_info.sizes]) + + @staticmethod + def ret_dual_func_exists(dual_variable_info: DualVariableInfo) -> bool: + return any([s == 1 for s in dual_variable_info.sizes]) + + def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> AffineMap: + affine_map = AffineMap() + + if p_id == 'c': + affine_map.mapping = param_prob.c[:-1] + elif p_id == 'd': + affine_map.mapping = param_prob.c[-1] + elif p_id == 'A': + affine_map.mapping_rows = constraint_info.mapping_rows_eq[ + constraint_info.mapping_rows_eq < constraint_info.n_data_constr_mat] + affine_map.shape = (self.n_eq, self.n_var) + elif p_id == 'G': + affine_map.mapping_rows = constraint_info.mapping_rows_ineq[ + constraint_info.mapping_rows_ineq < constraint_info.n_data_constr_mat] + affine_map.shape = (self.n_ineq, self.n_var) + elif p_id == 'b': + affine_map.mapping_rows = constraint_info.mapping_rows_eq[ + constraint_info.mapping_rows_eq >= constraint_info.n_data_constr_mat] + affine_map.shape = (self.n_eq, 1) + elif p_id == 'h': + affine_map.mapping_rows = constraint_info.mapping_rows_ineq[ + constraint_info.mapping_rows_ineq >= constraint_info.n_data_constr_mat] + affine_map.shape = (self.n_ineq, 1) + else: + raise ValueError(f'Unknown parameter name: {p_id}.') + + if p_id in ['A', 'b']: + affine_map.indices = self.indices_constr[affine_map.mapping_rows] + elif p_id in ['G', 'h']: + affine_map.indices = self.indices_constr[affine_map.mapping_rows] - self.n_eq + + if p_id.isupper(): + affine_map.mapping = -param_prob.reduced_A.reduced_mat[affine_map.mapping_rows] + + return affine_map + + @property + def settings_names_to_type(self): + return {name: typ for name, typ in zip(self.settings_names, self.settings_types)} + + @property + def settings_names_to_default(self): + return {name: typ for name, typ in zip(self.settings_names, self.settings_defaults)} + + @staticmethod + def check_unsupported_cones(cone_dims: "ConeDims") -> None: + pass + + @abstractmethod + def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, + parameter_canon: ParameterCanon) -> None: + pass + + +class OSQPInterface(SolverInterface): + canon_p_ids = ['P', 'q', 'd', 'A', 'l', 'u'] + canon_p_ids_constr_vec = ['l', 'u'] + sign_constr_vec = -1 + + # solver settings + settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', + 'alpha', + 'scaled_termination', 'check_termination', 'warm_start'] + settings_types = ['c_float', 'c_int', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', + 'c_int', 'c_int', + 'c_int'] + settings_defaults = [] + + def __init__(self, data, p_prob): + n_var = data['n_var'] + n_eq = data['n_eq'] + n_ineq = data['n_ineq'] + + indices_obj, indptr_obj, shape_obj = p_prob.reduced_P.problem_data_index + indices_constr, indptr_constr, shape_constr = p_prob.reduced_A.problem_data_index + + canon_constants = {} + + super().__init__(n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, + indices_constr, + indptr_constr, shape_constr, canon_constants) + + def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, + parameter_canon: ParameterCanon) -> None: + import osqp + + # OSQP codegen + osqp_obj = osqp.OSQP() + osqp_obj.setup(P=parameter_canon.p_csc['P'], q=parameter_canon.p['q'], + A=parameter_canon.p_csc['A'], l=parameter_canon.p['l'], + u=parameter_canon.p['u']) + if system() == 'Windows': + cmake_generator = 'MinGW Makefiles' + elif system() == 'Linux' or system() == 'Darwin': + cmake_generator = 'Unix Makefiles' + else: + raise ValueError(f'Unsupported OS {system()}.') + + osqp_obj.codegen(os.path.join(code_dir, 'c', 'solver_code'), project_type=cmake_generator, + parameters='matrices', force_rewrite=True) + + # copy license files + shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'osqp-python', 'LICENSE'), + os.path.join(solver_code_dir, 'LICENSE')) + shutil.copy(os.path.join(cvxpygen_directory, 'template', 'LICENSE'), code_dir) + + def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> AffineMap: + affine_map = AffineMap() + + if p_id == 'P': + affine_map.mapping = param_prob.reduced_P.reduced_mat + affine_map.indices = self.indices_obj + affine_map.shape = (self.n_var, self.n_var) + elif p_id == 'q': + affine_map.mapping = param_prob.q[:-1] + elif p_id == 'd': + affine_map.mapping = param_prob.q[-1] + elif p_id == 'A': + affine_map.mapping = param_prob.reduced_A.reduced_mat[ + :constraint_info.n_data_constr_mat] + affine_map.indices = self.indices_constr[:constraint_info.n_data_constr_mat] + affine_map.shape = (self.n_eq + self.n_ineq, self.n_var) + elif p_id == 'l': + mapping_rows_eq = np.nonzero(self.indices_constr < self.n_eq)[0] + affine_map.mapping_rows = mapping_rows_eq[ + mapping_rows_eq >= constraint_info.n_data_constr_mat] # mapping to the finite part of l + affine_map.indices = self.indices_constr[affine_map.mapping_rows] + affine_map.shape = (self.n_eq, 1) + elif p_id == 'u': + affine_map.mapping_rows = np.arange(constraint_info.n_data_constr_mat, + constraint_info.n_data_constr) + affine_map.indices = self.indices_constr[affine_map.mapping_rows] + affine_map.shape = (self.n_eq + self.n_ineq, 1) + else: + raise ValueError(f'Unknown OSQP parameter name {p_id}.') + + return affine_map + + +class SCSInterface(SolverInterface): + canon_p_ids = ['c', 'd', 'A', 'b'] + canon_p_ids_constr_vec = ['b'] + sign_constr_vec = 1 + + # solver settings + settings_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', + 'eps_rel', + 'eps_infeas', 'alpha', 'time_limit_secs', 'verbose', 'warm_start', + 'acceleration_lookback', + 'acceleration_interval', 'write_data_filename', 'log_csv_filename'] + settings_types = ['c_int', 'c_float', 'c_int', 'c_float', 'c_int', 'c_float', 'c_float', + 'c_float', 'c_float', + 'c_float', 'c_int', 'c_int', 'c_int', 'c_int', 'const char*', 'const char*'] + settings_defaults = ['1', '0.1', '1', '1e-6', '1e5', '1e-4', '1e-4', '1e-7', '1.5', '0', '0', + '0', '0', '1', + 'SCS_NULL', 'SCS_NULL'] + + def __init__(self, data, p_prob): + n_var = p_prob.x.size + n_eq = data['A'].shape[0] + n_ineq = 0 + + indices_obj, indptr_obj, shape_obj = None, None, None + + indices_constr, indptr_constr, shape_constr = p_prob.reduced_A.problem_data_index + + canon_constants = {'n': n_var, 'm': n_eq, 'z': p_prob.cone_dims.zero, + 'l': p_prob.cone_dims.nonneg, + 'q': np.array(p_prob.cone_dims.soc), + 'qsize': len(p_prob.cone_dims.soc)} + + super().__init__(n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, + indices_constr, + indptr_constr, shape_constr, canon_constants) + + @staticmethod + def check_unsupported_cones(cone_dims: "ConeDims") -> None: + if cone_dims.exp > 0 or len(cone_dims.psd) > 0 or len(cone_dims.p3d) > 0: + raise ValueError( + 'Code generation with SCS and exponential, positive semidefinite, or power cones ' + 'is not supported yet.') + + def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, + parameter_canon: ParameterCanon) -> None: + + # copy sources + if os.path.isdir(solver_code_dir): + shutil.rmtree(solver_code_dir) + os.mkdir(solver_code_dir) + dirs_to_copy = ['src', 'include', 'linsys', 'cmake'] + for dtc in dirs_to_copy: + shutil.copytree(os.path.join(cvxpygen_directory, 'solvers', 'scs', dtc), + os.path.join(solver_code_dir, dtc)) + files_to_copy = ['scs.mk', 'CMakeLists.txt', 'LICENSE.txt'] + for fl in files_to_copy: + shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'scs', fl), + os.path.join(solver_code_dir, fl)) + shutil.copy(os.path.join(cvxpygen_directory, 'template', 'LICENSE'), code_dir) + + # disable BLAS and LAPACK + with open(os.path.join(code_dir, 'c', 'solver_code', 'scs.mk'), 'r') as f: + scs_mk_data = f.read() + scs_mk_data = scs_mk_data.replace('USE_LAPACK = 1', 'USE_LAPACK = 0') + with open(os.path.join(code_dir, 'c', 'solver_code', 'scs.mk'), 'w') as f: + f.write(scs_mk_data) + + # modify CMakeLists.txt + with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'r') as f: + cmake_data = f.read() + cmake_data = cmake_data.replace(' include/', ' ${CMAKE_CURRENT_SOURCE_DIR}/include/') + cmake_data = cmake_data.replace(' src/', ' ${CMAKE_CURRENT_SOURCE_DIR}/src/') + cmake_data = cmake_data.replace(' ${LINSYS}/', ' ${CMAKE_CURRENT_SOURCE_DIR}/${LINSYS}/') + with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'w') as f: + f.write(cmake_data) + + # adjust top-level CMakeLists.txt + with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'r') as f: + cmake_data = f.read() + indent = ' ' * 6 + sdir = '${CMAKE_CURRENT_SOURCE_DIR}/solver_code/' + cmake_data = cmake_data.replace(sdir + 'include', + sdir + 'include\n' + + indent + sdir + 'linsys') + with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'w') as f: + f.write(cmake_data) + + # adjust setup.py + with open(os.path.join(code_dir, 'setup.py'), 'r') as f: + setup_text = f.read() + indent = ' ' * 30 + setup_text = setup_text.replace("os.path.join('c', 'solver_code', 'include'),", + "os.path.join('c', 'solver_code', 'include'),\n" + + indent + "os.path.join('c', 'solver_code', 'linsys'),") + with open(os.path.join(code_dir, 'setup.py'), 'w') as f: + f.write(setup_text) + + +class ECOSInterface(SolverInterface): + canon_p_ids = ['c', 'd', 'A', 'b', 'G', 'h'] + canon_p_ids_constr_vec = ['b', 'h'] + sign_constr_vec = 1 + + # solver settings + settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', + 'reltol_inacc', 'maxit'] + settings_types = ['c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_int'] + settings_defaults = ['1e-8', '1e-8', '1e-8', '1e-4', '5e-5', '5e-5', '100'] + + def __init__(self, data, p_prob): + n_var = p_prob.x.size + n_eq = p_prob.cone_dims.zero + n_ineq = data['G'].shape[0] + + indices_obj, indptr_obj, shape_obj = None, None, None + + indices_constr, indptr_constr, shape_constr = p_prob.reduced_A.problem_data_index + + canon_constants = {'n': n_var, 'm': n_ineq, 'p': n_eq, + 'l': p_prob.cone_dims.nonneg, + 'n_cones': len(p_prob.cone_dims.soc), + 'q': np.array(p_prob.cone_dims.soc), + 'e': p_prob.cone_dims.exp} + + super().__init__(n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, + indices_constr, indptr_constr, shape_constr, canon_constants) + + @staticmethod + def check_unsupported_cones(cone_dims: "ConeDims") -> None: + if cone_dims.exp > 0: + raise ValueError( + 'Code generation with ECOS and exponential cones is not supported yet.') + + @staticmethod + def ret_prim_func_exists(variable_info: PrimalVariableInfo) -> bool: + return True + + @staticmethod + def ret_dual_func_exists(dual_variable_info: DualVariableInfo) -> bool: + return True + + def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, + parameter_canon: ParameterCanon) -> None: + + # copy sources + if os.path.isdir(solver_code_dir): + shutil.rmtree(solver_code_dir) + os.mkdir(solver_code_dir) + dirs_to_copy = ['src', 'include', 'external', 'ecos_bb'] + for dtc in dirs_to_copy: + shutil.copytree(os.path.join(cvxpygen_directory, 'solvers', 'ecos', dtc), + os.path.join(solver_code_dir, dtc)) + shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'ecos', 'CMakeLists.txt'), + os.path.join(solver_code_dir, 'CMakeLists.txt')) + shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'ecos', 'COPYING'), + os.path.join(solver_code_dir, 'COPYING')) + shutil.copyfile(os.path.join(cvxpygen_directory, 'solvers', 'ecos', 'COPYING'), + os.path.join(code_dir, 'COPYING')) + + # adjust print level + with open(os.path.join(code_dir, 'c', 'solver_code', 'include', 'glblopts.h'), 'r') as f: + glbl_opts_data = f.read() + glbl_opts_data = glbl_opts_data.replace('#define PRINTLEVEL (2)', '#define PRINTLEVEL (0)') + with open(os.path.join(code_dir, 'c', 'solver_code', 'include', 'glblopts.h'), 'w') as f: + f.write(glbl_opts_data) + + # adjust top-level CMakeLists.txt + with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'r') as f: + cmake_data = f.read() + indent = ' ' * 6 + sdir = '${CMAKE_CURRENT_SOURCE_DIR}/solver_code/' + cmake_data = cmake_data.replace(sdir + 'include', + sdir + 'include\n' + + indent + sdir + 'external/SuiteSparse_config\n' + + indent + sdir + 'external/amd/include\n' + + indent + sdir + 'external/ldl/include') + with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'w') as f: + f.write(cmake_data) + + # remove library target from ECOS CMakeLists.txt + with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'r') as f: + lines = f.readlines() + with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'w') as f: + for line in lines: + if '# ECOS library' in line: + break + f.write(line) + + # adjust setup.py + with open(os.path.join(code_dir, 'setup.py'), 'r') as f: + setup_text = f.read() + indent = ' ' * 30 + setup_text = setup_text.replace("os.path.join('c', 'solver_code', 'include'),", + "os.path.join('c', 'solver_code', 'include'),\n" + + indent + "os.path.join('c', 'solver_code', 'external', 'SuiteSparse_config'),\n" + + indent + "os.path.join('c', 'solver_code', 'external', 'amd', 'include'),\n" + + indent + "os.path.join('c', 'solver_code', 'external', 'ldl', 'include'),") + setup_text = setup_text.replace("license='Apache 2.0'", "license='GPL 3.0'") + with open(os.path.join(code_dir, 'setup.py'), 'w') as f: + f.write(setup_text) From 3eb3f0e5b7ed9a86c6429ca5a5954413d0880c22 Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Sun, 3 Sep 2023 19:01:13 -0500 Subject: [PATCH 02/14] Refactor --- cvxpygen/cpg.py | 130 ++---- cvxpygen/mappings.py | 12 +- cvxpygen/solvers.py | 83 +++- cvxpygen/utils.py | 989 +++++++++++++++++++++---------------------- 4 files changed, 596 insertions(+), 618 deletions(-) diff --git a/cvxpygen/cpg.py b/cvxpygen/cpg.py index 2ffe0ca..ca8e98a 100644 --- a/cvxpygen/cpg.py +++ b/cvxpygen/cpg.py @@ -18,10 +18,9 @@ import warnings from cvxpygen import utils -from cvxpygen.mappings import PrimalVariableInfo, DualVariableInfo, ConstraintInfo, ParameterCanon, \ - ParameterInfo +from cvxpygen.mappings import Configuration, PrimalVariableInfo, DualVariableInfo, ConstraintInfo, \ + ParameterCanon, ParameterInfo from cvxpygen.solvers import get_interface_class -from cvxpygen.utils import C import cvxpy as cp import numpy as np from scipy import sparse @@ -77,76 +76,34 @@ def generate_code(problem, code_dir='CPG_code', solver=None, enable_settings=[], parameter_info = get_parameter_info(param_prob) # dimensions and information specific to solver - solver_interface = interface_class(data, param_prob) # noqa + solver_interface = interface_class(data, param_prob, enable_settings) # noqa constraint_info = get_constraint_info(solver_interface) adjacency, parameter_canon = process_canonical_parameters(constraint_info, param_prob, parameter_info, solver_interface, - solver_name) + solver_name, problem) cvxpygen_directory = os.path.dirname(os.path.realpath(__file__)) solver_code_dir = os.path.join(code_dir, 'c', 'solver_code') solver_interface.generate_code(code_dir, solver_code_dir, cvxpygen_directory, parameter_canon) - param_name_to_canon_outdated = { + parameter_canon.user_p_name_to_canon_outdated = { user_p_name: [solver_interface.canon_p_ids[j] for j in np.nonzero(adjacency[:, i])[0]] for i, user_p_name in enumerate(parameter_info.names)} - # avoid non-alphanumeric first character in problem name - prefix = adjust_problem_name(prefix) - - # summarize information on options, codegen, user parameters / variables, - # canonicalization in dictionaries - info_opt = {C.CODE_DIR: code_dir, - C.SOLVER_NAME: solver_name, - C.UNROLL: unroll, - C.PREFIX: prefix} - - info_cg = {C.RET_PRIM_FUNC_EXISTS: solver_interface.ret_prim_func_exists(variable_info), - C.RET_DUAL_FUNC_EXISTS: solver_interface.ret_dual_func_exists(dual_variable_info), - C.NONZERO_D: parameter_canon.nonzero_d, - C.IS_MAXIMIZATION: type(problem.objective) == Maximize} - - info_usr = {C.P_WRITABLE: parameter_info.writable, - C.P_FLAT_USP: parameter_info.flat_usp, - C.P_COL_TO_NAME_USP: parameter_info.col_to_name_usp, - C.P_NAME_TO_SHAPE: parameter_info.name_to_shape, - C.P_NAME_TO_SIZE: parameter_info.name_to_size_usp, - C.P_NAME_TO_CANON_OUTDATED: param_name_to_canon_outdated, - C.P_NAME_TO_SPARSITY: parameter_info.name_to_sparsity, - C.P_NAME_TO_SPARSITY_TYPE: parameter_info.name_to_sparsity_type, - C.V_NAME_TO_INDICES: variable_info.name_to_indices, - C.V_NAME_TO_SIZE: variable_info.name_to_size, - C.V_NAME_TO_SHAPE: variable_info.name_to_shape, - C.V_NAME_TO_INIT: variable_info.name_to_init, - C.V_NAME_TO_SYM: variable_info.name_to_sym, - C.V_NAME_TO_OFFSET: variable_info.name_to_offset, - C.D_NAME_TO_INIT: dual_variable_info.name_to_init, - C.D_NAME_TO_VEC: dual_variable_info.name_to_vec, - C.D_NAME_TO_OFFSET: dual_variable_info.name_to_offset, - C.D_NAME_TO_SIZE: dual_variable_info.name_to_size, - C.D_NAME_TO_SHAPE: dual_variable_info.name_to_shape, - C.D_NAME_TO_INDICES: dual_variable_info.name_to_indices} - - info_can = {C.P: parameter_canon.p, - C.P_ID_TO_SIZE: parameter_canon.p_id_to_size, - C.P_ID_TO_CHANGES: parameter_canon.p_id_to_changes, - C.P_ID_TO_MAPPING: parameter_canon.p_id_to_mapping, - C.CONSTANTS: solver_interface.canon_constants, - C.SETTINGS_NAMES_TO_TYPE: solver_interface.settings_names_to_type, - C.SETTINGS_NAMES_TO_DEFAULT: solver_interface.settings_names_to_default, - C.SETTINGS_LIST: solver_interface.settings_names} - - write_c_code(code_dir, info_opt, info_cg, info_can, info_usr, problem) + # configuration + configuration = get_configuration(code_dir, solver, unroll, prefix) + + write_c_code(problem, configuration, variable_info, dual_variable_info, parameter_info, + parameter_canon, solver_interface) sys.stdout.write('CVXPYgen finished generating code.\n') if wrapper: compile_python_module(code_dir) - -def process_canonical_parameters(constraint_info, param_prob, parameter_info, solver_interface, solver_name): +def process_canonical_parameters(constraint_info, param_prob, parameter_info, solver_interface, solver_name, problem): adjacency = np.zeros(shape=(len(solver_interface.canon_p_ids), parameter_info.num), dtype=bool) parameter_canon = ParameterCanon() # compute affine mapping for each canonical parameter @@ -171,6 +128,7 @@ def process_canonical_parameters(constraint_info, param_prob, parameter_info, so parameter_canon.p_id_to_mapping[p_id] = affine_map.mapping.tocsr() parameter_canon.p_id_to_changes[p_id] = affine_map.mapping[:, :-1].nnz > 0 parameter_canon.p_id_to_size[p_id] = affine_map.mapping.shape[0] + parameter_canon.is_maximization = type(problem.objective) == Maximize return adjacency, parameter_canon @@ -344,59 +302,59 @@ def update_adjacency_matrix(adjacency, i, parameter_info, mapping) -> np.ndarray return adjacency -def write_c_code(code_dir: str, info_opt: dict, info_cg: dict, info_can: dict, info_usr: dict, - problem: cp.Problem) -> None: +def write_c_code(problem: cp.Problem, configuration: dict, variable_info: dict, dual_variable_info: dict, + parameter_info: dict, parameter_canon: dict, solver_interface: dict) -> None: # 'workspace' prototypes - with open(os.path.join(code_dir, 'c', 'include', 'cpg_workspace.h'), 'w') as f: - utils.write_workspace_prot(f, info_opt, info_usr, info_can) + with open(os.path.join(configuration.code_dir, 'c', 'include', 'cpg_workspace.h'), 'w') as f: + utils.write_workspace_prot(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface) # 'workspace' definitions - with open(os.path.join(code_dir, 'c', 'src', 'cpg_workspace.c'), 'w') as f: - utils.write_workspace_def(f, info_opt, info_usr, info_can) + with open(os.path.join(configuration.code_dir, 'c', 'src', 'cpg_workspace.c'), 'w') as f: + utils.write_workspace_def(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface) # 'solve' prototypes - with open(os.path.join(code_dir, 'c', 'include', 'cpg_solve.h'), 'w') as f: - utils.write_solve_prot(f, info_opt, info_cg, info_usr, info_can) + with open(os.path.join(configuration.code_dir, 'c', 'include', 'cpg_solve.h'), 'w') as f: + utils.write_solve_prot(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface) # 'solve' definitions - with open(os.path.join(code_dir, 'c', 'src', 'cpg_solve.c'), 'w') as f: - utils.write_solve_def(f, info_opt, info_cg, info_usr, info_can) + with open(os.path.join(configuration.code_dir, 'c', 'src', 'cpg_solve.c'), 'w') as f: + utils.write_solve_def(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface) # 'example' definitions - with open(os.path.join(code_dir, 'c', 'src', 'cpg_example.c'), 'w') as f: - utils.write_example_def(f, info_opt, info_usr) + with open(os.path.join(configuration.code_dir, 'c', 'src', 'cpg_example.c'), 'w') as f: + utils.write_example_def(f, configuration, variable_info, dual_variable_info, parameter_info) # adapt top-level CMakeLists.txt - with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'r') as f: + with open(os.path.join(configuration.code_dir, 'c', 'CMakeLists.txt'), 'r') as f: cmake_data = f.read() - cmake_data = utils.replace_cmake_data(cmake_data, info_opt) - with open(os.path.join(code_dir, 'c', 'CMakeLists.txt'), 'w') as f: + cmake_data = utils.replace_cmake_data(cmake_data, configuration) + with open(os.path.join(configuration.code_dir, 'c', 'CMakeLists.txt'), 'w') as f: f.write(cmake_data) # adapt solver CMakeLists.txt - with open(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'a') as f: - utils.write_canon_cmake(f, info_opt) + with open(os.path.join(configuration.code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'a') as f: + utils.write_canon_cmake(f, configuration) # binding module prototypes - with open(os.path.join(code_dir, 'cpp', 'include', 'cpg_module.hpp'), 'w') as f: - utils.write_module_prot(f, info_opt, info_usr) + with open(os.path.join(configuration.code_dir, 'cpp', 'include', 'cpg_module.hpp'), 'w') as f: + utils.write_module_prot(f, configuration, parameter_info, variable_info, dual_variable_info) # binding module definition - with open(os.path.join(code_dir, 'cpp', 'src', 'cpg_module.cpp'), 'w') as f: - utils.write_module_def(f, info_opt, info_usr, info_can) + with open(os.path.join(configuration.code_dir, 'cpp', 'src', 'cpg_module.cpp'), 'w') as f: + utils.write_module_def(f, configuration, variable_info, dual_variable_info, parameter_info, solver_interface) # adapt setup.py - with open(os.path.join(code_dir, 'setup.py'), 'r') as f: + with open(os.path.join(configuration.code_dir, 'setup.py'), 'r') as f: setup_data = f.read() setup_data = utils.replace_setup_data(setup_data) - with open(os.path.join(code_dir, 'setup.py'), 'w') as f: + with open(os.path.join(configuration.code_dir, 'setup.py'), 'w') as f: f.write(setup_data) # custom CVXPY solve method - with open(os.path.join(code_dir, 'cpg_solver.py'), 'w') as f: - utils.write_method(f, info_opt, info_usr) + with open(os.path.join(configuration.code_dir, 'cpg_solver.py'), 'w') as f: + utils.write_method(f, configuration, variable_info, dual_variable_info, parameter_info) # serialize problem formulation - with open(os.path.join(code_dir, 'problem.pickle'), 'wb') as f: + with open(os.path.join(configuration.code_dir, 'problem.pickle'), 'wb') as f: pickle.dump(cp.Problem(problem.objective, problem.constraints), f) # html documentation file - with open(os.path.join(code_dir, 'README.html'), 'r') as f: + with open(os.path.join(configuration.code_dir, 'README.html'), 'r') as f: html_data = f.read() - html_data = utils.replace_html_data(html_data, info_opt, info_usr, info_can) - with open(os.path.join(code_dir, 'README.html'), 'w') as f: + html_data = utils.replace_html_data(html_data, configuration, variable_info, dual_variable_info, parameter_info, solver_interface) + with open(os.path.join(configuration.code_dir, 'README.html'), 'w') as f: f.write(html_data) -def adjust_problem_name(prefix): +def adjust_prefix(prefix): if prefix != '': if not prefix[0].isalpha(): prefix = '_' + prefix @@ -404,6 +362,10 @@ def adjust_problem_name(prefix): return prefix +def get_configuration(code_dir, solver_name, unroll, prefix) -> Configuration: + return Configuration(code_dir, solver_name, unroll, adjust_prefix(prefix)) + + def get_parameter_info(p_prob) -> ParameterInfo: user_p_num = len(p_prob.parameters) user_p_names = [par.name() for par in p_prob.parameters] diff --git a/cvxpygen/mappings.py b/cvxpygen/mappings.py index 91b6238..e6fb3a3 100644 --- a/cvxpygen/mappings.py +++ b/cvxpygen/mappings.py @@ -4,6 +4,14 @@ import numpy as np +@dataclass +class Configuration: + code_dir: str + solver_name: str + unroll: bool + prefix: str + + @dataclass class AffineMap: mapping_rows: list = field(default_factory=list) @@ -20,7 +28,9 @@ class ParameterCanon: p_id_to_mapping: dict[str, sp.csr_matrix] = field(default_factory=dict) p_id_to_changes: dict[str, bool] = field(default_factory=dict) p_id_to_size: dict[str, int] = field(default_factory=dict) - nonzero_d = True + nonzero_d: bool = True + is_maximization: bool = False + user_p_name_to_canon_outdated: dict[str, list[str]] = field(default_factory=dict) @dataclass diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 8fe3236..20e0756 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -1,11 +1,13 @@ import os import shutil +import warnings from abc import ABC, abstractmethod from dataclasses import dataclass, field from platform import system import numpy as np +from cvxpygen import utils from cvxpygen.mappings import PrimalVariableInfo, DualVariableInfo, ConstraintInfo, AffineMap, \ ParameterCanon @@ -24,8 +26,9 @@ def get_interface_class(solver_name: str) -> "SolverInterface": class SolverInterface(ABC): - def __init__(self, n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, indices_constr, - indptr_constr, shape_constr, canon_constants): + def __init__(self, solver_name, n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, + indices_constr, indptr_constr, shape_constr, canon_constants, enable_settings): + self.solver_name = solver_name self.n_var = n_var self.n_eq = n_eq self.n_ineq = n_ineq @@ -36,6 +39,9 @@ def __init__(self, n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, indi self.indptr_constr = indptr_constr self.shape_constr = shape_constr self.canon_constants = canon_constants + self.enable_settings = enable_settings + + self.configure_settings() @property @abstractmethod @@ -75,6 +81,13 @@ def ret_prim_func_exists(variable_info: PrimalVariableInfo) -> bool: def ret_dual_func_exists(dual_variable_info: DualVariableInfo) -> bool: return any([s == 1 for s in dual_variable_info.sizes]) + def configure_settings(self) -> None: + for i, s in enumerate(self.settings_names): + if s in self.enable_settings: + self.settings_enabled[i] = True + for s in set(self.enable_settings)-set(self.settings_names): + warnings.warn('Cannot enable setting %s for solver %s' % (s, self.solver_name)) + def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> AffineMap: affine_map = AffineMap() @@ -111,13 +124,19 @@ def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> A return affine_map + @property + def settings_names_enabled(self): + return [name for name, enabled in zip(self.settings_names, self.settings_enabled) if enabled] + @property def settings_names_to_type(self): - return {name: typ for name, typ in zip(self.settings_names, self.settings_types)} + return {name: typ for name, typ, enabled in zip(self.settings_names, self.settings_types, self.settings_enabled) + if enabled} @property def settings_names_to_default(self): - return {name: typ for name, typ in zip(self.settings_names, self.settings_defaults)} + return {name: typ for name, typ, enabled in zip(self.settings_names, self.settings_defaults, self.settings_enabled) + if enabled} @staticmethod def check_unsupported_cones(cone_dims: "ConeDims") -> None: @@ -130,20 +149,22 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, class OSQPInterface(SolverInterface): + solver_name = 'OSQP' canon_p_ids = ['P', 'q', 'd', 'A', 'l', 'u'] canon_p_ids_constr_vec = ['l', 'u'] sign_constr_vec = -1 # solver settings settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', - 'alpha', - 'scaled_termination', 'check_termination', 'warm_start'] + 'alpha', 'scaled_termination', 'check_termination', 'warm_start', + 'verbose', 'polish', 'polish_refine_iter', 'delta'] settings_types = ['c_float', 'c_int', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', - 'c_int', 'c_int', - 'c_int'] + 'c_int', 'c_int', 'c_int', 'c_int', 'c_int', 'c_int', 'c_float'] + settings_enabled = [True, True, True, True, True, True, True, True, True, True, + False, False, False, False] settings_defaults = [] - def __init__(self, data, p_prob): + def __init__(self, data, p_prob, enable_settings): n_var = data['n_var'] n_eq = data['n_eq'] n_ineq = data['n_ineq'] @@ -153,9 +174,8 @@ def __init__(self, data, p_prob): canon_constants = {} - super().__init__(n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, - indices_constr, - indptr_constr, shape_constr, canon_constants) + super().__init__(self.solver_name, n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, + indices_constr, indptr_constr, shape_constr, canon_constants, enable_settings) def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, parameter_canon: ParameterCanon) -> None: @@ -181,6 +201,27 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, os.path.join(solver_code_dir, 'LICENSE')) shutil.copy(os.path.join(cvxpygen_directory, 'template', 'LICENSE'), code_dir) + # modify for extra settings + if 'verbose' in self.enable_settings: + utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), + [('message(STATUS "Disabling printing for embedded")', 'message(STATUS "Not disabling printing for embedded by user request")'), + ('set(PRINTING OFF)', '')]) + utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'constants.h'), + [('# ifdef __cplusplus\n}', '# define VERBOSE (1)\n\n# ifdef __cplusplus\n}')]) + utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'types.h'), + [('} OSQPInfo;', ' c_int status_polish;\n} OSQPInfo;'), + ('} OSQPSettings;', ' c_int polish;\n c_int verbose;\n} OSQPSettings;'), + ('# ifndef EMBEDDED\n c_int nthreads; ///< number of threads active\n# endif // ifndef EMBEDDED', ' c_int nthreads;')]) + utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'osqp.h'), + [('# ifdef __cplusplus\n}', 'c_int osqp_update_verbose(OSQPWorkspace *work, c_int verbose_new);\n\n# ifdef __cplusplus\n}')]) + utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'src', 'osqp', 'util.c'), + [('// Print Settings', '/* Print Settings'), + ('LINSYS_SOLVER_NAME[settings->linsys_solver]);', 'LINSYS_SOLVER_NAME[settings->linsys_solver]);*/')]) + utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'src', 'osqp', 'osqp.c'), + [('void osqp_set_default_settings(OSQPSettings *settings) {', 'void osqp_set_default_settings(OSQPSettings *settings) {\n settings->verbose = VERBOSE;'), + ('c_int osqp_update_verbose', '#endif // EMBEDDED\n\nc_int osqp_update_verbose'), + ('verbose = verbose_new;\n\n return 0;\n}\n\n#endif // EMBEDDED', 'verbose = verbose_new;\n\n return 0;\n}')]) + def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> AffineMap: affine_map = AffineMap() @@ -215,6 +256,7 @@ def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> A class SCSInterface(SolverInterface): + solver_name = 'SCS' canon_p_ids = ['c', 'd', 'A', 'b'] canon_p_ids_constr_vec = ['b'] sign_constr_vec = 1 @@ -228,11 +270,13 @@ class SCSInterface(SolverInterface): settings_types = ['c_int', 'c_float', 'c_int', 'c_float', 'c_int', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_int', 'c_int', 'c_int', 'c_int', 'const char*', 'const char*'] + settings_enabled = [True, True, True, True, True, True, True, True, True, True, True, True, + True, True, True, True] settings_defaults = ['1', '0.1', '1', '1e-6', '1e5', '1e-4', '1e-4', '1e-7', '1.5', '0', '0', '0', '0', '1', 'SCS_NULL', 'SCS_NULL'] - def __init__(self, data, p_prob): + def __init__(self, data, p_prob, enable_settings): n_var = p_prob.x.size n_eq = data['A'].shape[0] n_ineq = 0 @@ -246,9 +290,8 @@ def __init__(self, data, p_prob): 'q': np.array(p_prob.cone_dims.soc), 'qsize': len(p_prob.cone_dims.soc)} - super().__init__(n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, - indices_constr, - indptr_constr, shape_constr, canon_constants) + super().__init__(self.solver_name, n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, + indices_constr, indptr_constr, shape_constr, canon_constants, enable_settings) @staticmethod def check_unsupported_cones(cone_dims: "ConeDims") -> None: @@ -313,6 +356,7 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, class ECOSInterface(SolverInterface): + solver_name = 'ECOS' canon_p_ids = ['c', 'd', 'A', 'b', 'G', 'h'] canon_p_ids_constr_vec = ['b', 'h'] sign_constr_vec = 1 @@ -321,9 +365,10 @@ class ECOSInterface(SolverInterface): settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', 'reltol_inacc', 'maxit'] settings_types = ['c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_int'] + settings_enabled = [True, True, True, True, True, True, True] settings_defaults = ['1e-8', '1e-8', '1e-8', '1e-4', '5e-5', '5e-5', '100'] - def __init__(self, data, p_prob): + def __init__(self, data, p_prob, enable_settings): n_var = p_prob.x.size n_eq = p_prob.cone_dims.zero n_ineq = data['G'].shape[0] @@ -338,8 +383,8 @@ def __init__(self, data, p_prob): 'q': np.array(p_prob.cone_dims.soc), 'e': p_prob.cone_dims.exp} - super().__init__(n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, - indices_constr, indptr_constr, shape_constr, canon_constants) + super().__init__(self.solver_name, n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, + indices_constr, indptr_constr, shape_constr, canon_constants, enable_settings) @staticmethod def check_unsupported_cones(cone_dims: "ConeDims") -> None: diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index 9bad7e4..ffc8c45 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -16,45 +16,6 @@ from osqp.codegen import utils as osqp_utils -class C: - CODE_DIR = 'code_dir' - SOLVER_NAME = 'solver_name' - UNROLL = 'unroll' - PREFIX = 'prefix' - RET_PRIM_FUNC_EXISTS = 'ret_prim_func_exists' - RET_DUAL_FUNC_EXISTS = 'ret_dual_func_exists' - NONZERO_D = 'nonzero_d' - IS_MAXIMIZATION = 'is_maximization' - P_WRITABLE = 'p_writable' - P_FLAT_USP = 'p_flat_usp' - P_COL_TO_NAME_USP = 'p_col_to_name_usp' - P_NAME_TO_SHAPE = 'p_name_to_shape' - P_NAME_TO_SIZE = 'p_name_to_size_usp' - P_NAME_TO_CANON_OUTDATED = 'p_name_to_canon_outdated' - P_NAME_TO_SPARSITY = 'p_name_to_sparsity' - P_NAME_TO_SPARSITY_TYPE = 'p_name_to_sparsity_type' - V_NAME_TO_INDICES = 'v_name_to_indices' - V_NAME_TO_SIZE = 'v_name_to_size' - V_NAME_TO_SHAPE = 'v_name_to_shape' - V_NAME_TO_INIT = 'v_name_to_init' - V_NAME_TO_SYM = 'v_name_to_sym' - V_NAME_TO_OFFSET = 'v_name_to_offset' - D_NAME_TO_INIT = 'd_name_to_init' - D_NAME_TO_VEC = 'd_name_to_vec' - D_NAME_TO_OFFSET = 'd_name_to_offset' - D_NAME_TO_SIZE = 'd_name_to_size' - D_NAME_TO_SHAPE = 'd_name_to_shape' - D_NAME_TO_INDICES = 'd_name_to_indices' - P = 'p' - P_ID_TO_SIZE = 'p_id_to_size' - P_ID_TO_CHANGES = 'p_id_to_changes' - P_ID_TO_MAPPING = 'p_id_to_mapping' - CONSTANTS = 'constants' - SETTINGS_NAMES_TO_TYPE = 'settings_names_to_type' - SETTINGS_NAMES_TO_DEFAULT = 'settings_names_to_default' - SETTINGS_LIST = 'settings_list' - - def write_description(f, file_type, content): """ Timestamp and file content to beginning of file @@ -109,7 +70,7 @@ def csc_to_dict(m): d = dict() d['i'] = m.indices - d[C.P] = m.indptr + d['p'] = m.indptr d['x'] = m.data d['nzmax'] = m.nnz (d['m'], d['n']) = m.shape @@ -286,7 +247,7 @@ def write_ecos_setup_update(f, canon_constants, prefix): """ n = canon_constants['n'] m = canon_constants['m'] - p = canon_constants[C.P] + p = canon_constants['p'] ell = canon_constants['l'] n_cones = canon_constants['n_cones'] e = canon_constants['e'] @@ -330,51 +291,51 @@ def write_ecos_setup_update(f, canon_constants, prefix): f.write(' }\n') -def write_workspace_def(f, info_opt, info_usr, info_can): +def write_workspace_def(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): write_description(f, 'c', 'Variable definitions') f.write('#include "cpg_workspace.h"\n') - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': f.write('#include "workspace.h"\n') - if info_opt[C.UNROLL]: + if configuration.unroll: f.write('\n// User-defined parameters\n') user_casts = [] user_values = [] - names = list(info_usr[C.P_WRITABLE].keys()) + names = list(parameter_info.writable.keys()) for name in names: - value = info_usr[C.P_WRITABLE][name] + value = parameter_info.writable[name] if is_mathematical_scalar(value): user_casts.append('') user_values.append('%.20f' % value) else: - osqp_utils.write_vec(f, value, info_opt[C.PREFIX] + 'cpg_' + name, 'c_float') + osqp_utils.write_vec(f, value, configuration.prefix + 'cpg_' + name, 'c_float') f.write('\n') user_casts.append('(c_float *) ') - user_values.append('&' + info_opt[C.PREFIX] + 'cpg_' + name) + user_values.append('&' + configuration.prefix + 'cpg_' + name) f.write('// Struct containing all user-defined parameters\n') - write_struct_def(f, names, user_casts, user_values, '%sCPG_Params' % info_opt[C.PREFIX], 'CPG_Params_t') + write_struct_def(f, names, user_casts, user_values, '%sCPG_Params' % configuration.prefix, 'CPG_Params_t') f.write('\n') else: f.write('\n// Vector containing flattened user-defined parameters\n') - osqp_utils.write_vec(f, info_usr[C.P_FLAT_USP], '%scpg_params_vec' % info_opt[C.PREFIX], 'c_float') + osqp_utils.write_vec(f, parameter_info.flat_usp, '%scpg_params_vec' % configuration.prefix, 'c_float') f.write('\n// Sparse mappings from user-defined to canonical parameters\n') - for p_id, mapping in info_can[C.P_ID_TO_MAPPING].items(): - if info_can[C.P_ID_TO_CHANGES][p_id]: - osqp_utils.write_mat(f, csc_to_dict(mapping), '%scanon_%s_map' % (info_opt[C.PREFIX], p_id)) + for p_id, mapping in parameter_canon.p_id_to_mapping.items(): + if parameter_canon.p_id_to_changes[p_id]: + osqp_utils.write_mat(f, csc_to_dict(mapping), '%scanon_%s_map' % (configuration.prefix, p_id)) f.write('\n') - p_ids = list(info_can[C.P].keys()) + p_ids = list(parameter_canon.p.keys()) canon_casts = [] f.write('// Canonical parameters\n') for p_id in p_ids: - p = info_can[C.P][p_id] + p = parameter_canon.p[p_id] if p_id == 'd': canon_casts.append('') else: - write_param_def(f, replace_inf(p), p_id, info_opt[C.PREFIX], '') - if info_opt[C.SOLVER_NAME] == 'ECOS': - write_param_def(f, replace_inf(p), p_id, info_opt[C.PREFIX], '_ECOS') + write_param_def(f, replace_inf(p), p_id, configuration.prefix, '') + if configuration.solver_name == 'ECOS': + write_param_def(f, replace_inf(p), p_id, configuration.prefix, '_ECOS') if p_id.isupper(): canon_casts.append('') else: @@ -385,144 +346,144 @@ def write_workspace_def(f, info_opt, info_usr, info_can): struct_values = [] struct_values_ECOS = [] for i, p_id in enumerate(p_ids): - p = info_can[C.P][p_id] + p = parameter_canon.p[p_id] if type(p) == dict: length = len(p['x']) else: length = len(p) if length == 0: struct_values.append('0') - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': struct_values_ECOS.append('0') elif p_id=='d': struct_values.append('%.20f' % p) - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': struct_values_ECOS.append('%.20f' % p) else: - struct_values.append('&%scanon_%s' % (info_opt[C.PREFIX], p_id)) - if info_opt[C.SOLVER_NAME] == 'ECOS': - struct_values_ECOS.append('&%scanon_%s_ECOS' % (info_opt[C.PREFIX], p_id)) + struct_values.append('&%scanon_%s' % (configuration.prefix, p_id)) + if configuration.solver_name == 'ECOS': + struct_values_ECOS.append('&%scanon_%s_ECOS' % (configuration.prefix, p_id)) - write_struct_def(f, p_ids, canon_casts, struct_values, '%sCanon_Params' % info_opt[C.PREFIX], 'Canon_Params_t') + write_struct_def(f, p_ids, canon_casts, struct_values, '%sCanon_Params' % configuration.prefix, 'Canon_Params_t') f.write('\n') - if info_opt[C.SOLVER_NAME] == 'ECOS': - write_struct_def(f, p_ids, canon_casts, struct_values_ECOS, '%sCanon_Params_ECOS' % info_opt[C.PREFIX], + if configuration.solver_name == 'ECOS': + write_struct_def(f, p_ids, canon_casts, struct_values_ECOS, '%sCanon_Params_ECOS' % configuration.prefix, 'Canon_Params_t') f.write('\n') # Boolean struct for outdated parameter flags f.write('// Struct containing flags for outdated canonical parameters\n') - f.write('Canon_Outdated_t %sCanon_Outdated = {\n' % info_opt[C.PREFIX]) - for p_id in info_can[C.P].keys(): + f.write('Canon_Outdated_t %sCanon_Outdated = {\n' % configuration.prefix) + for p_id in parameter_canon.p.keys(): f.write('.%s = 0,\n' % p_id) f.write('};\n\n') prim_cast = [] - if any(info_usr[C.V_NAME_TO_SYM]) or info_opt[C.SOLVER_NAME] == 'ECOS': + if any(variable_info.name_to_sym) or configuration.solver_name == 'ECOS': f.write('// User-defined variables\n') - for name, value in info_usr[C.V_NAME_TO_INIT].items(): + for name, value in variable_info.name_to_init.items(): if is_mathematical_scalar(value): prim_cast.append('') else: prim_cast.append('(c_float *) ') - if info_usr[C.V_NAME_TO_SYM][name] or info_opt[C.SOLVER_NAME] == 'ECOS': - osqp_utils.write_vec(f, value.flatten(order='F'), info_opt[C.PREFIX] + name, 'c_float') + if variable_info.name_to_sym[name] or configuration.solver_name == 'ECOS': + osqp_utils.write_vec(f, value.flatten(order='F'), configuration.prefix + name, 'c_float') f.write('\n') f.write('// Struct containing primal solution\n') - CPG_Prim_fields = list(info_usr[C.V_NAME_TO_INIT].keys()) + CPG_Prim_fields = list(variable_info.name_to_init.keys()) CPG_Prim_values = [] - for name, var in info_usr[C.V_NAME_TO_INIT].items(): - offset = info_usr[C.V_NAME_TO_OFFSET][name] + for name, var in variable_info.name_to_init.items(): + offset = variable_info.name_to_offset[name] if is_mathematical_scalar(var): CPG_Prim_values.append('0') else: - if info_usr[C.V_NAME_TO_SYM][name] or info_opt[C.SOLVER_NAME] == 'ECOS': - CPG_Prim_values.append('&' + info_opt[C.PREFIX] + name) + if variable_info.name_to_sym[name] or configuration.solver_name == 'ECOS': + CPG_Prim_values.append('&' + configuration.prefix + name) else: - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': CPG_Prim_values.append('&xsolution + %d' % offset) - elif info_opt[C.SOLVER_NAME] == 'SCS': - CPG_Prim_values.append('&%sscs_x + %d' % (info_opt[C.PREFIX], offset)) - write_struct_def(f, CPG_Prim_fields, prim_cast, CPG_Prim_values, '%sCPG_Prim' % info_opt[C.PREFIX], 'CPG_Prim_t') + elif configuration.solver_name == 'SCS': + CPG_Prim_values.append('&%sscs_x + %d' % (configuration.prefix, offset)) + write_struct_def(f, CPG_Prim_fields, prim_cast, CPG_Prim_values, '%sCPG_Prim' % configuration.prefix, 'CPG_Prim_t') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: dual_cast = [] - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': f.write('\n// Dual variables associated with user-defined constraints\n') - for name, value in info_usr[C.D_NAME_TO_INIT].items(): + for name, value in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(value): dual_cast.append('') else: dual_cast.append('(c_float *) ') - if info_opt[C.SOLVER_NAME] == 'ECOS': - osqp_utils.write_vec(f, value.flatten(order='F'), info_opt[C.PREFIX] + name, 'c_float') + if configuration.solver_name == 'ECOS': + osqp_utils.write_vec(f, value.flatten(order='F'), configuration.prefix + name, 'c_float') f.write('\n') f.write('// Struct containing dual solution\n') - CPG_Dual_fields = info_usr[C.D_NAME_TO_INIT].keys() + CPG_Dual_fields = dual_variable_info.name_to_init.keys() CPG_Dual_values = [] - for name, var in info_usr[C.D_NAME_TO_INIT].items(): - vec = info_usr[C.D_NAME_TO_VEC][name] - offset = info_usr[C.D_NAME_TO_OFFSET][name] + for name, var in dual_variable_info.name_to_init.items(): + vec = dual_variable_info.name_to_vec[name] + offset = dual_variable_info.name_to_offset[name] if is_mathematical_scalar(var): CPG_Dual_values.append('0') else: - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': CPG_Dual_values.append('&%ssolution + %d' % (vec, offset)) - elif info_opt[C.SOLVER_NAME] == 'SCS': - CPG_Dual_values.append('&%sscs_%s + %d' % (info_opt[C.PREFIX], vec, offset)) + elif configuration.solver_name == 'SCS': + CPG_Dual_values.append('&%sscs_%s + %d' % (configuration.prefix, vec, offset)) else: - CPG_Dual_values.append('&' + info_opt[C.PREFIX] + name) - write_struct_def(f, CPG_Dual_fields, dual_cast, CPG_Dual_values, '%sCPG_Dual' % info_opt[C.PREFIX], + CPG_Dual_values.append('&' + configuration.prefix + name) + write_struct_def(f, CPG_Dual_fields, dual_cast, CPG_Dual_values, '%sCPG_Dual' % configuration.prefix, 'CPG_Dual_t') f.write('\n// Struct containing solver info\n') CPG_Info_fields = ['obj_val', 'iter', 'status', 'pri_res', 'dua_res'] - if info_opt[C.SOLVER_NAME] in ['OSQP', 'SCS']: + if configuration.solver_name in ['OSQP', 'SCS']: CPG_Info_values = ['0', '0', '"unknown"', '0', '0'] - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': CPG_Info_values = ['0', '0', '0', '0', '0'] else: raise ValueError("Problem class cannot be addressed by the OSQP or ECOS solver!") info_cast = ['', '', '', '', ''] - write_struct_def(f, CPG_Info_fields, info_cast, CPG_Info_values, '%sCPG_Info' % info_opt[C.PREFIX], 'CPG_Info_t') + write_struct_def(f, CPG_Info_fields, info_cast, CPG_Info_values, '%sCPG_Info' % configuration.prefix, 'CPG_Info_t') f.write('\n// Struct containing solution and info\n') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: CPG_Result_fields = ['prim', 'dual', 'info'] result_cast = ['', '', ''] - CPG_Result_values = ['&%sCPG_Prim' % info_opt[C.PREFIX], '&%sCPG_Dual' % info_opt[C.PREFIX], - '&%sCPG_Info' % info_opt[C.PREFIX]] + CPG_Result_values = ['&%sCPG_Prim' % configuration.prefix, '&%sCPG_Dual' % configuration.prefix, + '&%sCPG_Info' % configuration.prefix] else: CPG_Result_fields = ['prim', 'info'] result_cast = ['', ''] - CPG_Result_values = ['&%sCPG_Prim' % info_opt[C.PREFIX], '&%sCPG_Info' % info_opt[C.PREFIX]] - write_struct_def(f, CPG_Result_fields, result_cast, CPG_Result_values, '%sCPG_Result' % info_opt[C.PREFIX], + CPG_Result_values = ['&%sCPG_Prim' % configuration.prefix, '&%sCPG_Info' % configuration.prefix] + write_struct_def(f, CPG_Result_fields, result_cast, CPG_Result_values, '%sCPG_Result' % configuration.prefix, 'CPG_Result_t') - if info_opt[C.SOLVER_NAME] == 'SCS': + if configuration.solver_name == 'SCS': f.write('\n// SCS matrix A\n') scs_A_fiels = ['x', 'i', 'p', 'm', 'n'] scs_A_casts = ['(c_float *) ', '(c_int *) ', '(c_int *) ', '', ''] - scs_A_values = ['&%scanon_A_x' % info_opt[C.PREFIX], '&%scanon_A_i' % info_opt[C.PREFIX], - '&%scanon_A_p' % info_opt[C.PREFIX], str(info_can[C.CONSTANTS]['m']), - str(info_can[C.CONSTANTS]['n'])] - write_struct_def(f, scs_A_fiels, scs_A_casts, scs_A_values, '%sScs_A' % info_opt[C.PREFIX], 'ScsMatrix') + scs_A_values = ['&%scanon_A_x' % configuration.prefix, '&%scanon_A_i' % configuration.prefix, + '&%scanon_A_p' % configuration.prefix, str(solver_interface.canon_constants['m']), + str(solver_interface.canon_constants['n'])] + write_struct_def(f, scs_A_fiels, scs_A_casts, scs_A_values, '%sScs_A' % configuration.prefix, 'ScsMatrix') f.write('\n// Struct containing SCS data\n') scs_d_fiels = ['m', 'n', 'A', 'P', 'b', 'c'] scs_d_casts = ['', '', '', '', '(c_float *) ', '(c_float *) '] - scs_d_values = [str(info_can[C.CONSTANTS]['m']), str(info_can[C.CONSTANTS]['n']), '&%sScs_A' - % info_opt[C.PREFIX], 'SCS_NULL', '&%scanon_b' % info_opt[C.PREFIX], '&%scanon_c' - % info_opt[C.PREFIX]] - write_struct_def(f, scs_d_fiels, scs_d_casts, scs_d_values, '%sScs_D' % info_opt[C.PREFIX], 'ScsData') + scs_d_values = [str(solver_interface.canon_constants['m']), str(solver_interface.canon_constants['n']), '&%sScs_A' + % configuration.prefix, 'SCS_NULL', '&%scanon_b' % configuration.prefix, '&%scanon_c' + % configuration.prefix] + write_struct_def(f, scs_d_fiels, scs_d_casts, scs_d_values, '%sScs_D' % configuration.prefix, 'ScsData') - if info_can[C.CONSTANTS]['qsize'] > 0: + if solver_interface.canon_constants['qsize'] > 0: f.write('\n// SCS array of SOC dimensions\n') - osqp_utils.write_vec(f, info_can[C.CONSTANTS]['q'], '%sscs_q' % info_opt[C.PREFIX], 'c_int') - k_field_q_str = '&%sscs_q' % info_opt[C.PREFIX] + osqp_utils.write_vec(f, solver_interface.canon_constants['q'], '%sscs_q' % configuration.prefix, 'c_int') + k_field_q_str = '&%sscs_q' % configuration.prefix else: k_field_q_str = 'SCS_NULL' @@ -530,29 +491,29 @@ def write_workspace_def(f, info_opt, info_usr, info_can): scs_k_fields = ['z', 'l', 'bu', 'bl', 'bsize', 'q', 'qsize', 's', 'ssize', 'ep', 'ed', 'p', 'psize'] scs_k_casts = ['', '', '(c_float *) ', '(c_float *) ', '', '(c_int *) ', '', '(c_int *) ', '', '', '', '(c_float *) ', ''] - scs_k_values = [str(info_can[C.CONSTANTS]['z']), str(info_can[C.CONSTANTS]['l']), 'SCS_NULL', 'SCS_NULL', '0', - k_field_q_str, str(info_can[C.CONSTANTS]['qsize']), 'SCS_NULL', '0', '0', '0', 'SCS_NULL', '0'] - write_struct_def(f, scs_k_fields, scs_k_casts, scs_k_values, '%sScs_K' % info_opt[C.PREFIX], 'ScsCone') + scs_k_values = [str(solver_interface.canon_constants['z']), str(solver_interface.canon_constants['l']), 'SCS_NULL', 'SCS_NULL', '0', + k_field_q_str, str(solver_interface.canon_constants['qsize']), 'SCS_NULL', '0', '0', '0', 'SCS_NULL', '0'] + write_struct_def(f, scs_k_fields, scs_k_casts, scs_k_values, '%sScs_K' % configuration.prefix, 'ScsCone') f.write('\n// Struct containing SCS settings\n') - scs_stgs_fields = list(info_can[C.SETTINGS_NAMES_TO_DEFAULT].keys()) + scs_stgs_fields = list(solver_interface.settings_names_to_default.keys()) scs_stgs_casts = ['']*len(scs_stgs_fields) - scs_stgs_values = list(info_can[C.SETTINGS_NAMES_TO_DEFAULT].values()) + scs_stgs_values = list(solver_interface.settings_names_to_default.values()) write_struct_def(f, scs_stgs_fields, scs_stgs_casts, scs_stgs_values, '%sScs_Stgs' - % info_opt[C.PREFIX], 'ScsSettings') + % configuration.prefix, 'ScsSettings') f.write('\n// SCS solution\n') - osqp_utils.write_vec(f, np.zeros(info_can[C.CONSTANTS]['n']), '%sscs_x' % info_opt[C.PREFIX], 'c_float') - osqp_utils.write_vec(f, np.zeros(info_can[C.CONSTANTS]['m']), '%sscs_y' % info_opt[C.PREFIX], 'c_float') - osqp_utils.write_vec(f, np.zeros(info_can[C.CONSTANTS]['m']), '%sscs_s' % info_opt[C.PREFIX], 'c_float') + osqp_utils.write_vec(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, 'c_float') + osqp_utils.write_vec(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_y' % configuration.prefix, 'c_float') + osqp_utils.write_vec(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_s' % configuration.prefix, 'c_float') f.write('\n// Struct containing SCS solution\n') scs_sol_fields = ['x', 'y', 's'] scs_sol_casts = ['(c_float *) ', '(c_float *) ', '(c_float *) '] - scs_sol_values = ['&%sscs_x' % info_opt[C.PREFIX], '&%sscs_y' % info_opt[C.PREFIX], '&%sscs_s' - % info_opt[C.PREFIX]] + scs_sol_values = ['&%sscs_x' % configuration.prefix, '&%sscs_y' % configuration.prefix, '&%sscs_s' + % configuration.prefix] write_struct_def(f, scs_sol_fields, scs_sol_casts, scs_sol_values, '%sScs_Sol' - % info_opt[C.PREFIX], 'ScsSolution') + % configuration.prefix, 'ScsSolution') f.write('\n// Struct containing SCS information\n') scs_info_fields = ['iter', 'status', 'status_val', 'scale_updates', 'pobj', 'dobj', 'res_pri', 'res_dual', @@ -563,53 +524,53 @@ def write_workspace_def(f, info_opt, info_usr, info_can): scs_info_values = ['0', '"unknown"', '0', '0', '0', '0', '99', '99', '99', '99', '99', '99', '99', '0', '0', '1', '0', '0', '0', '0', '0'] write_struct_def(f, scs_info_fields, scs_info_casts, scs_info_values, '%sScs_Info' - % info_opt[C.PREFIX], 'ScsInfo') + % configuration.prefix, 'ScsInfo') f.write('\n// Pointer to struct containing SCS workspace\n') - f.write('ScsWork* %sScs_Work = 0;\n' % info_opt[C.PREFIX]) + f.write('ScsWork* %sScs_Work = 0;\n' % configuration.prefix) - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': f.write('\n// Struct containing solver settings\n') - f.write('Canon_Settings_t %sCanon_Settings = {\n' % info_opt[C.PREFIX]) - for name, default in info_can[C.SETTINGS_NAMES_TO_DEFAULT].items(): + f.write('Canon_Settings_t %sCanon_Settings = {\n' % configuration.prefix) + for name, default in solver_interface.settings_names_to_default.items(): f.write('.%s = %s,\n' % (name, default)) f.write('};\n') - if info_can[C.CONSTANTS]['n_cones'] > 0: + if solver_interface.canon_constants['n_cones'] > 0: f.write('\n// ECOS array of SOC dimensions\n') - osqp_utils.write_vec(f, info_can[C.CONSTANTS]['q'], '%secos_q' % info_opt[C.PREFIX], 'c_int') + osqp_utils.write_vec(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'c_int') f.write('\n// ECOS workspace\n') - f.write('pwork* %secos_workspace = 0;\n' % info_opt[C.PREFIX]) + f.write('pwork* %secos_workspace = 0;\n' % configuration.prefix) f.write('\n// ECOS exit flag\n') - f.write('c_int %secos_flag = -99;\n' % info_opt[C.PREFIX]) + f.write('c_int %secos_flag = -99;\n' % configuration.prefix) -def write_workspace_prot(f, info_opt, info_usr, info_can): +def write_workspace_prot(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): """" Write workspace initialization to file """ write_description(f, 'c', 'Type definitions and variable declarations') - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': f.write('#include "types.h"\n\n') - elif info_opt[C.SOLVER_NAME] == 'SCS': + elif configuration.solver_name == 'SCS': f.write('#include "scs.h"\n\n') - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': f.write('#include "ecos.h"\n\n') # definition safeguard f.write('#ifndef CPG_TYPES_H\n') f.write('# define CPG_TYPES_H\n\n') - if info_opt[C.SOLVER_NAME] == 'SCS': + if configuration.solver_name == 'SCS': f.write('typedef scs_float c_float;\n') f.write('typedef scs_int c_int;\n\n') - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': f.write('typedef double c_float;\n') f.write('typedef int c_int;\n\n') # struct definitions - if info_opt[C.SOLVER_NAME] in ['SCS', 'ECOS']: + if configuration.solver_name in ['SCS', 'ECOS']: f.write('// Compressed sparse column (csc) matrix\n') f.write('typedef struct {\n') f.write(' c_int nzmax;\n') @@ -621,11 +582,11 @@ def write_workspace_prot(f, info_opt, info_usr, info_can): f.write(' c_int nz;\n') f.write('} csc;\n\n') - if info_opt[C.UNROLL]: + if configuration.unroll: f.write('// User-defined parameters\n') f.write('typedef struct {\n') # single user parameters - for name, size in info_usr[C.P_NAME_TO_SIZE].items(): + for name, size in parameter_info.name_to_size_usp.items(): if size == 1: s = '' else: @@ -635,7 +596,7 @@ def write_workspace_prot(f, info_opt, info_usr, info_can): f.write('// Canonical parameters\n') f.write('typedef struct {\n') - for p_id in info_can[C.P].keys(): + for p_id in parameter_canon.p.keys(): if p_id.isupper(): f.write(' csc *%s // Canonical parameter %s\n' % ((p_id+';').ljust(8), p_id)) else: @@ -648,13 +609,13 @@ def write_workspace_prot(f, info_opt, info_usr, info_can): f.write('// Flags indicating outdated canonical parameters\n') f.write('typedef struct {\n') - for p_id in info_can[C.P].keys(): + for p_id in parameter_canon.p.keys(): f.write(' int %s // Bool, if canonical parameter %s outdated\n' % ((p_id + ';').ljust(8), p_id)) f.write('} Canon_Outdated_t;\n\n') f.write('// Primal solution\n') f.write('typedef struct {\n') - for name, var in info_usr[C.V_NAME_TO_INIT].items(): + for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): s = '' else: @@ -662,10 +623,10 @@ def write_workspace_prot(f, info_opt, info_usr, info_can): f.write(' c_float %s // Your variable %s\n' % ((s + name + ';').ljust(9), name)) f.write('} CPG_Prim_t;\n\n') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: f.write('// Dual solution\n') f.write('typedef struct {\n') - for name, var in info_usr[C.D_NAME_TO_INIT].items(): + for name, var in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(var): s = '' else: @@ -677,9 +638,9 @@ def write_workspace_prot(f, info_opt, info_usr, info_can): f.write('typedef struct {\n') f.write(' c_float obj_val; // Objective function value\n') f.write(' c_int iter; // Number of iterations\n') - if info_opt[C.SOLVER_NAME] in ['OSQP', 'SCS']: + if configuration.solver_name in ['OSQP', 'SCS']: f.write(' char *status; // Solver status\n') - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': f.write(' c_int status; // Solver status\n') f.write(' c_float pri_res; // Primal residual\n') f.write(' c_float dua_res; // Dual residual\n') @@ -688,116 +649,116 @@ def write_workspace_prot(f, info_opt, info_usr, info_can): f.write('// Solution and solver information\n') f.write('typedef struct {\n') f.write(' CPG_Prim_t *prim; // Primal solution\n') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: f.write(' CPG_Dual_t *dual; // Dual solution\n') f.write(' CPG_Info_t *info; // Solver info\n') f.write('} CPG_Result_t;\n\n') - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': f.write('// Solver settings\n') f.write('typedef struct {\n') - for name, typ in info_can[C.SETTINGS_NAMES_TO_TYPE].items(): + for name, typ in solver_interface.settings_names_to_type.items(): f.write(' %s%s;\n' % (typ.ljust(11), name)) f.write('} Canon_Settings_t;\n\n') f.write('#endif // ifndef CPG_TYPES_H\n') - if info_opt[C.UNROLL]: + if configuration.unroll: f.write('\n// User-defined parameters\n') - for name, value in info_usr[C.P_WRITABLE].items(): + for name, value in parameter_info.writable.items(): if not is_mathematical_scalar(value): - osqp_utils.write_vec_extern(f, value, info_opt[C.PREFIX]+'cpg_'+name, 'c_float') + osqp_utils.write_vec_extern(f, value, configuration.prefix+'cpg_'+name, 'c_float') f.write('\n// Struct containing all user-defined parameters\n') - write_struct_prot(f, '%sCPG_Params' % info_opt[C.PREFIX], 'CPG_Params_t') + write_struct_prot(f, '%sCPG_Params' % configuration.prefix, 'CPG_Params_t') else: f.write('\n// Vector containing flattened user-defined parameters\n') - osqp_utils.write_vec_extern(f, info_usr[C.P_FLAT_USP], '%scpg_params_vec' % info_opt[C.PREFIX], 'c_float') + osqp_utils.write_vec_extern(f, parameter_info.flat_usp, '%scpg_params_vec' % configuration.prefix, 'c_float') f.write('\n// Sparse mappings from user-defined to canonical parameters\n') - for p_id, mapping in info_can[C.P_ID_TO_MAPPING].items(): - if info_can[C.P_ID_TO_CHANGES][p_id]: - osqp_utils.write_mat_extern(f, csc_to_dict(mapping), '%scanon_%s_map' % (info_opt[C.PREFIX], p_id)) + for p_id, mapping in parameter_canon.p_id_to_mapping.items(): + if parameter_canon.p_id_to_changes[p_id]: + osqp_utils.write_mat_extern(f, csc_to_dict(mapping), '%scanon_%s_map' % (configuration.prefix, p_id)) f.write('\n// Canonical parameters\n') - for p_id, p in info_can[C.P].items(): + for p_id, p in parameter_canon.p.items(): if p_id != 'd': - write_param_prot(f, p, p_id, info_opt[C.PREFIX], '') - if info_opt[C.SOLVER_NAME] == 'ECOS': - write_param_prot(f, p, p_id, info_opt[C.PREFIX], '_ECOS') + write_param_prot(f, p, p_id, configuration.prefix, '') + if configuration.solver_name == 'ECOS': + write_param_prot(f, p, p_id, configuration.prefix, '_ECOS') f.write('\n// Struct containing canonical parameters\n') - write_struct_prot(f, '%sCanon_Params' % info_opt[C.PREFIX], 'Canon_Params_t') - if info_opt[C.SOLVER_NAME] == 'ECOS': - write_struct_prot(f, '%sCanon_Params_ECOS' % info_opt[C.PREFIX], 'Canon_Params_t') + write_struct_prot(f, '%sCanon_Params' % configuration.prefix, 'Canon_Params_t') + if configuration.solver_name == 'ECOS': + write_struct_prot(f, '%sCanon_Params_ECOS' % configuration.prefix, 'Canon_Params_t') f.write('\n// Struct containing flags for outdated canonical parameters\n') - f.write('extern Canon_Outdated_t %sCanon_Outdated;\n' % info_opt[C.PREFIX]) + f.write('extern Canon_Outdated_t %sCanon_Outdated;\n' % configuration.prefix) - if any(info_usr[C.V_NAME_TO_SYM].values()) or info_opt[C.SOLVER_NAME] == 'ECOS': + if any(variable_info.name_to_sym.values()) or configuration.solver_name == 'ECOS': f.write('\n// User-defined variables\n') - for name, value in info_usr[C.V_NAME_TO_INIT].items(): - if info_usr[C.V_NAME_TO_SYM][name] or info_opt[C.SOLVER_NAME] == 'ECOS': + for name, value in variable_info.name_to_init.items(): + if variable_info.name_to_sym[name] or configuration.solver_name == 'ECOS': if not is_mathematical_scalar(value): - osqp_utils.write_vec_extern(f, value.flatten(order='F'), info_opt[C.PREFIX]+'cpg_'+name, + osqp_utils.write_vec_extern(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, 'c_float') - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': f.write('\n// Dual variables associated with user-defined constraints\n') - for name, value in info_usr[C.D_NAME_TO_INIT].items(): + for name, value in dual_variable_info.name_to_init.items(): if not is_mathematical_scalar(value): - osqp_utils.write_vec_extern(f, value.flatten(order='F'), info_opt[C.PREFIX]+'cpg_'+name, 'c_float') + osqp_utils.write_vec_extern(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, 'c_float') f.write('\n// Struct containing primal solution\n') - write_struct_prot(f, '%sCPG_Prim' % info_opt[C.PREFIX], 'CPG_Prim_t') + write_struct_prot(f, '%sCPG_Prim' % configuration.prefix, 'CPG_Prim_t') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: f.write('\n// Struct containing dual solution\n') - write_struct_prot(f, '%sCPG_Dual' % info_opt[C.PREFIX], 'CPG_Dual_t') + write_struct_prot(f, '%sCPG_Dual' % configuration.prefix, 'CPG_Dual_t') f.write('\n// Struct containing solver info\n') - write_struct_prot(f, '%sCPG_Info' % info_opt[C.PREFIX], 'CPG_Info_t') + write_struct_prot(f, '%sCPG_Info' % configuration.prefix, 'CPG_Info_t') f.write('\n// Struct containing solution and info\n') - write_struct_prot(f, '%sCPG_Result' % info_opt[C.PREFIX], 'CPG_Result_t') + write_struct_prot(f, '%sCPG_Result' % configuration.prefix, 'CPG_Result_t') - if info_opt[C.SOLVER_NAME] == 'SCS': + if configuration.solver_name == 'SCS': f.write('\n// SCS matrix A\n') - write_struct_prot(f, '%sscs_A' % info_opt[C.PREFIX], 'ScsMatrix') + write_struct_prot(f, '%sscs_A' % configuration.prefix, 'ScsMatrix') f.write('\n// Struct containing SCS data\n') - write_struct_prot(f, '%sScs_D' % info_opt[C.PREFIX], 'ScsData') - if info_can[C.CONSTANTS]['qsize'] > 0: + write_struct_prot(f, '%sScs_D' % configuration.prefix, 'ScsData') + if solver_interface.canon_constants['qsize'] > 0: f.write('\n// SCS array of SOC dimensions\n') - osqp_utils.write_vec_extern(f, info_can[C.CONSTANTS]['q'], '%sscs_q' % info_opt[C.PREFIX], 'c_int') + osqp_utils.write_vec_extern(f, solver_interface.canon_constants['q'], '%sscs_q' % configuration.prefix, 'c_int') f.write('\n// Struct containing SCS cone data\n') - write_struct_prot(f, '%sScs_K' % info_opt[C.PREFIX], 'ScsCone') + write_struct_prot(f, '%sScs_K' % configuration.prefix, 'ScsCone') f.write('\n// Struct containing SCS settings\n') - write_struct_prot(f, '%sScs_Stgs' % info_opt[C.PREFIX], 'ScsSettings') + write_struct_prot(f, '%sScs_Stgs' % configuration.prefix, 'ScsSettings') f.write('\n// SCS solution\n') - osqp_utils.write_vec_extern(f, np.zeros(info_can[C.CONSTANTS]['n']), '%sscs_x' % info_opt[C.PREFIX], + osqp_utils.write_vec_extern(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, 'c_float') - osqp_utils.write_vec_extern(f, np.zeros(info_can[C.CONSTANTS]['m']), '%sscs_y' % info_opt[C.PREFIX], + osqp_utils.write_vec_extern(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_y' % configuration.prefix, 'c_float') - osqp_utils.write_vec_extern(f, np.zeros(info_can[C.CONSTANTS]['m']), '%sscs_s' % info_opt[C.PREFIX], + osqp_utils.write_vec_extern(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_s' % configuration.prefix, 'c_float') f.write('\n// Struct containing SCS solution\n') - write_struct_prot(f, '%sScs_Sol' % info_opt[C.PREFIX], 'ScsSolution') + write_struct_prot(f, '%sScs_Sol' % configuration.prefix, 'ScsSolution') f.write('\n// Struct containing SCS information\n') - write_struct_prot(f, '%sScs_Info' % info_opt[C.PREFIX], 'ScsInfo') + write_struct_prot(f, '%sScs_Info' % configuration.prefix, 'ScsInfo') f.write('\n// Pointer to struct containing SCS workspace\n') - write_struct_prot(f, '%sScs_Work' % info_opt[C.PREFIX], 'ScsWork*') + write_struct_prot(f, '%sScs_Work' % configuration.prefix, 'ScsWork*') - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': f.write('\n// Struct containing solver settings\n') - write_struct_prot(f, '%sCanon_Settings' % info_opt[C.PREFIX], 'Canon_Settings_t') - if info_can[C.CONSTANTS]['n_cones'] > 0: + write_struct_prot(f, '%sCanon_Settings' % configuration.prefix, 'Canon_Settings_t') + if solver_interface.canon_constants['n_cones'] > 0: f.write('\n// ECOS array of SOC dimensions\n') - osqp_utils.write_vec_extern(f, info_can[C.CONSTANTS]['q'], '%secos_q' % info_opt[C.PREFIX], 'c_int') + osqp_utils.write_vec_extern(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'c_int') f.write('\n// ECOS workspace\n') - f.write('extern pwork* %secos_workspace;\n' % info_opt[C.PREFIX]) + f.write('extern pwork* %secos_workspace;\n' % configuration.prefix) f.write('\n// ECOS exit flag\n') - f.write('extern c_int %secos_flag;\n' % info_opt[C.PREFIX]) + f.write('extern c_int %secos_flag;\n' % configuration.prefix) -def write_solve_def(f, info_opt, info_cg, info_usr, info_can): +def write_solve_def(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): """ Write parameter initialization function to file """ @@ -805,327 +766,327 @@ def write_solve_def(f, info_opt, info_cg, info_usr, info_can): write_description(f, 'c', 'Function definitions') f.write('#include "cpg_solve.h"\n') f.write('#include "cpg_workspace.h"\n') - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': f.write('#include "workspace.h"\n') f.write('#include "osqp.h"\n\n') else: f.write('\n') - if not info_opt[C.UNROLL]: + if not configuration.unroll: f.write('static c_int i;\n') f.write('static c_int j;\n') - if info_opt[C.UNROLL] and info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.unroll and configuration.solver_name == 'ECOS': f.write('static c_int i;\n') - if info_opt[C.SOLVER_NAME] in ['SCS', 'ECOS']: + if configuration.solver_name in ['SCS', 'ECOS']: f.write('static c_int initialized = 0;\n') f.write('\n// Update user-defined parameters\n') - if info_opt[C.UNROLL]: - for user_p_name, Canon_outdated_names in info_usr[C.P_NAME_TO_CANON_OUTDATED].items(): - if info_usr[C.P_NAME_TO_SIZE][user_p_name] == 1: - f.write('void %scpg_update_%s(c_float val){\n' % (info_opt[C.PREFIX], user_p_name)) - f.write(' %sCPG_Params.%s = val;\n' % (info_opt[C.PREFIX], user_p_name)) + if configuration.unroll: + for user_p_name, Canon_outdated_names in parameter_canon.user_p_name_to_canon_outdated.items(): + if parameter_info.name_to_size_usp[user_p_name] == 1: + f.write('void %scpg_update_%s(c_float val){\n' % (configuration.prefix, user_p_name)) + f.write(' %sCPG_Params.%s = val;\n' % (configuration.prefix, user_p_name)) else: - f.write('void %scpg_update_%s(c_int idx, c_float val){\n' % (info_opt[C.PREFIX], user_p_name)) - f.write(' %sCPG_Params.%s[idx] = val;\n' % (info_opt[C.PREFIX], user_p_name)) + f.write('void %scpg_update_%s(c_int idx, c_float val){\n' % (configuration.prefix, user_p_name)) + f.write(' %sCPG_Params.%s[idx] = val;\n' % (configuration.prefix, user_p_name)) for Canon_outdated_name in Canon_outdated_names: - f.write(' %sCanon_Outdated.%s = 1;\n' % (info_opt[C.PREFIX], Canon_outdated_name)) + f.write(' %sCanon_Outdated.%s = 1;\n' % (configuration.prefix, Canon_outdated_name)) f.write('}\n\n') else: - for base_col, name in info_usr[C.P_COL_TO_NAME_USP].items(): - Canon_outdated_names = info_usr[C.P_NAME_TO_CANON_OUTDATED][name] - if info_usr[C.P_NAME_TO_SIZE][name] == 1: - f.write('void %scpg_update_%s(c_float val){\n' % (info_opt[C.PREFIX], name)) - f.write(' %scpg_params_vec[%d] = val;\n' % (info_opt[C.PREFIX], base_col)) + for base_col, name in parameter_info.col_to_name_usp.items(): + Canon_outdated_names = parameter_canon.user_p_name_to_canon_outdated[name] + if parameter_info.name_to_size_usp[name] == 1: + f.write('void %scpg_update_%s(c_float val){\n' % (configuration.prefix, name)) + f.write(' %scpg_params_vec[%d] = val;\n' % (configuration.prefix, base_col)) else: - f.write('void %scpg_update_%s(c_int idx, c_float val){\n' % (info_opt[C.PREFIX], name)) - f.write(' %scpg_params_vec[idx+%d] = val;\n' % (info_opt[C.PREFIX], base_col)) + f.write('void %scpg_update_%s(c_int idx, c_float val){\n' % (configuration.prefix, name)) + f.write(' %scpg_params_vec[idx+%d] = val;\n' % (configuration.prefix, base_col)) for Canon_outdated_name in Canon_outdated_names: - f.write(' %sCanon_Outdated.%s = 1;\n' % (info_opt[C.PREFIX], Canon_outdated_name)) + f.write(' %sCanon_Outdated.%s = 1;\n' % (configuration.prefix, Canon_outdated_name)) f.write('}\n\n') f.write('// Map user-defined to canonical parameters\n') - for p_id, mapping in info_can[C.P_ID_TO_MAPPING].items(): - if info_can[C.P_ID_TO_CHANGES][p_id]: - f.write('void %scpg_canonicalize_%s(){\n' % (info_opt[C.PREFIX], p_id)) + for p_id, mapping in parameter_canon.p_id_to_mapping.items(): + if parameter_canon.p_id_to_changes[p_id]: + f.write('void %scpg_canonicalize_%s(){\n' % (configuration.prefix, p_id)) if p_id.isupper(): s = '->x' else: s = '' - if info_opt[C.UNROLL]: - write_canonicalize_explicit(f, p_id, s, mapping, info_usr[C.P_COL_TO_NAME_USP], - info_usr[C.P_NAME_TO_SIZE], info_opt[C.PREFIX]) + if configuration.unroll: + write_canonicalize_explicit(f, p_id, s, mapping, parameter_info.col_to_name_usp, + parameter_info.name_to_size_usp, configuration.prefix) else: - write_canonicalize(f, p_id, s, mapping, info_opt[C.PREFIX]) + write_canonicalize(f, p_id, s, mapping, configuration.prefix) f.write('}\n\n') - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': obj_str = 'workspace.info->obj_val' prim_str = 'workspace.solution->x' dual_str = 'workspace.solution->' - elif info_opt[C.SOLVER_NAME] == 'SCS': - obj_str = '%sScs_Info.pobj' % info_opt[C.PREFIX] - prim_str = '%sscs_x' % info_opt[C.PREFIX] - dual_str = '%sscs_' % info_opt[C.PREFIX] - elif info_opt[C.SOLVER_NAME] == 'ECOS': - obj_str = '%secos_workspace->info->pcost' % info_opt[C.PREFIX] - prim_str = '%secos_workspace->x' % info_opt[C.PREFIX] - dual_str = '%secos_workspace->' % info_opt[C.PREFIX] + elif configuration.solver_name == 'SCS': + obj_str = '%sScs_Info.pobj' % configuration.prefix + prim_str = '%sscs_x' % configuration.prefix + dual_str = '%sscs_' % configuration.prefix + elif configuration.solver_name == 'ECOS': + obj_str = '%secos_workspace->info->pcost' % configuration.prefix + prim_str = '%secos_workspace->x' % configuration.prefix + dual_str = '%secos_workspace->' % configuration.prefix else: raise ValueError("Only OSQP and ECOS are supported!") - if info_cg[C.RET_PRIM_FUNC_EXISTS]: + if solver_interface.ret_prim_func_exists(variable_info): f.write('// Retrieve primal solution in terms of user-defined variables\n') - f.write('void %scpg_retrieve_prim(){\n' % info_opt[C.PREFIX]) - for var_name, indices in info_usr[C.V_NAME_TO_INDICES].items(): + f.write('void %scpg_retrieve_prim(){\n' % configuration.prefix) + for var_name, indices in variable_info.name_to_indices.items(): if len(indices) == 1: - f.write(' %sCPG_Prim.%s = %s[%d];\n' % (info_opt[C.PREFIX], var_name, prim_str, indices)) - elif info_usr[C.V_NAME_TO_SYM][var_name] or info_opt[C.SOLVER_NAME] == 'ECOS': + f.write(' %sCPG_Prim.%s = %s[%d];\n' % (configuration.prefix, var_name, prim_str, indices)) + elif variable_info.name_to_sym[var_name] or configuration.solver_name == 'ECOS': for i, idx in enumerate(indices): - f.write(' %sCPG_Prim.%s[%d] = %s[%d];\n' % (info_opt[C.PREFIX], var_name, i, prim_str, idx)) + f.write(' %sCPG_Prim.%s[%d] = %s[%d];\n' % (configuration.prefix, var_name, i, prim_str, idx)) f.write('}\n\n') - if info_cg[C.RET_DUAL_FUNC_EXISTS]: + if solver_interface.ret_dual_func_exists(dual_variable_info): f.write('// Retrieve dual solution in terms of user-defined constraints\n') - f.write('void %scpg_retrieve_dual(){\n' % info_opt[C.PREFIX]) - for var_name, (vector, indices) in info_usr[C.D_NAME_TO_INDICES].items(): + f.write('void %scpg_retrieve_dual(){\n' % configuration.prefix) + for var_name, (vector, indices) in dual_variable_info.name_to_indices.items(): if len(indices) == 1: - f.write(' %sCPG_Dual.%s = %s%s[%d];\n' % (info_opt[C.PREFIX], var_name, dual_str, vector, indices)) - elif info_opt[C.SOLVER_NAME] == 'ECOS': + f.write(' %sCPG_Dual.%s = %s%s[%d];\n' % (configuration.prefix, var_name, dual_str, vector, indices)) + elif configuration.solver_name == 'ECOS': for i, idx in enumerate(indices): f.write(' %sCPG_Dual.%s[%d] = %s%s[%d];\n' - % (info_opt[C.PREFIX], var_name, i, dual_str, vector, idx)) + % (configuration.prefix, var_name, i, dual_str, vector, idx)) f.write('}\n\n') f.write('// Retrieve solver info\n') - f.write('void %scpg_retrieve_info(){\n' % info_opt[C.PREFIX]) - if info_cg[C.NONZERO_D]: - d_str = ' + %sCanon_Params.d' % info_opt[C.PREFIX] + f.write('void %scpg_retrieve_info(){\n' % configuration.prefix) + if parameter_canon.nonzero_d: + d_str = ' + %sCanon_Params.d' % configuration.prefix else: d_str = '' - if info_cg[C.IS_MAXIMIZATION]: - f.write(' %sCPG_Info.obj_val = -(%s%s);\n' % (info_opt[C.PREFIX], obj_str, d_str)) + if parameter_canon.is_maximization: + f.write(' %sCPG_Info.obj_val = -(%s%s);\n' % (configuration.prefix, obj_str, d_str)) else: - f.write(' %sCPG_Info.obj_val = %s%s;\n' % (info_opt[C.PREFIX], obj_str, d_str)) - if info_opt[C.SOLVER_NAME] == 'OSQP': - f.write(' %sCPG_Info.iter = workspace.info->iter;\n' % info_opt[C.PREFIX]) - f.write(' %sCPG_Info.status = workspace.info->status;\n' % info_opt[C.PREFIX]) - f.write(' %sCPG_Info.pri_res = workspace.info->pri_res;\n' % info_opt[C.PREFIX]) - f.write(' %sCPG_Info.dua_res = workspace.info->dua_res;\n' % info_opt[C.PREFIX]) - elif info_opt[C.SOLVER_NAME] == 'SCS': - f.write(' %sCPG_Info.iter = %sScs_Info.iter;\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' %sCPG_Info.status = %sScs_Info.status;\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' %sCPG_Info.pri_res = %sScs_Info.res_pri;\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' %sCPG_Info.dua_res = %sScs_Info.res_dual;\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - elif info_opt[C.SOLVER_NAME] == 'ECOS': - f.write(' %sCPG_Info.iter = %secos_workspace->info->iter;\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' %sCPG_Info.status = %secos_flag;\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + f.write(' %sCPG_Info.obj_val = %s%s;\n' % (configuration.prefix, obj_str, d_str)) + if configuration.solver_name == 'OSQP': + f.write(' %sCPG_Info.iter = workspace.info->iter;\n' % configuration.prefix) + f.write(' %sCPG_Info.status = workspace.info->status;\n' % configuration.prefix) + f.write(' %sCPG_Info.pri_res = workspace.info->pri_res;\n' % configuration.prefix) + f.write(' %sCPG_Info.dua_res = workspace.info->dua_res;\n' % configuration.prefix) + elif configuration.solver_name == 'SCS': + f.write(' %sCPG_Info.iter = %sScs_Info.iter;\n' % (configuration.prefix, configuration.prefix)) + f.write(' %sCPG_Info.status = %sScs_Info.status;\n' % (configuration.prefix, configuration.prefix)) + f.write(' %sCPG_Info.pri_res = %sScs_Info.res_pri;\n' % (configuration.prefix, configuration.prefix)) + f.write(' %sCPG_Info.dua_res = %sScs_Info.res_dual;\n' % (configuration.prefix, configuration.prefix)) + elif configuration.solver_name == 'ECOS': + f.write(' %sCPG_Info.iter = %secos_workspace->info->iter;\n' % (configuration.prefix, configuration.prefix)) + f.write(' %sCPG_Info.status = %secos_flag;\n' % (configuration.prefix, configuration.prefix)) f.write(' %sCPG_Info.pri_res = %secos_workspace->info->pres;\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + % (configuration.prefix, configuration.prefix)) f.write(' %sCPG_Info.dua_res = %secos_workspace->info->dres;\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + % (configuration.prefix, configuration.prefix)) f.write('}\n\n') f.write('// Solve via canonicalization, canonical solve, retrieval\n') - f.write('void %scpg_solve(){\n' % info_opt[C.PREFIX]) + f.write('void %scpg_solve(){\n' % configuration.prefix) f.write(' // Canonicalize if necessary\n') - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': - if info_can[C.P_ID_TO_CHANGES]['P'] and info_can[C.P_ID_TO_CHANGES]['A']: + if parameter_canon.p_id_to_changes['P'] and parameter_canon.p_id_to_changes['A']: f.write(' if (%sCanon_Outdated.P && %sCanon_Outdated.A) {\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' %scpg_canonicalize_P();\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_A();\n' % info_opt[C.PREFIX]) + % (configuration.prefix, configuration.prefix)) + f.write(' %scpg_canonicalize_P();\n' % configuration.prefix) + f.write(' %scpg_canonicalize_A();\n' % configuration.prefix) f.write(' osqp_update_P_A(&workspace, %sCanon_Params.P->x, 0, 0, %sCanon_Params.A->x, 0, 0);\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' } else if (%sCanon_Outdated.P) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_P();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_P(&workspace, %sCanon_Params.P->x, 0, 0);\n' % info_opt[C.PREFIX]) - f.write(' } else if (%sCanon_Outdated.A) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_A();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_A(&workspace, %sCanon_Params.A->x, 0, 0);\n' % info_opt[C.PREFIX]) + % (configuration.prefix, configuration.prefix)) + f.write(' } else if (%sCanon_Outdated.P) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_P();\n' % configuration.prefix) + f.write(' osqp_update_P(&workspace, %sCanon_Params.P->x, 0, 0);\n' % configuration.prefix) + f.write(' } else if (%sCanon_Outdated.A) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_A();\n' % configuration.prefix) + f.write(' osqp_update_A(&workspace, %sCanon_Params.A->x, 0, 0);\n' % configuration.prefix) f.write(' }\n') else: - if info_can[C.P_ID_TO_CHANGES]['P']: - f.write(' if (%sCanon_Outdated.P) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_P();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_P(&workspace, %sCanon_Params.P->x, 0, 0);\n' % info_opt[C.PREFIX]) + if parameter_canon.p_id_to_changes['P']: + f.write(' if (%sCanon_Outdated.P) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_P();\n' % configuration.prefix) + f.write(' osqp_update_P(&workspace, %sCanon_Params.P->x, 0, 0);\n' % configuration.prefix) f.write(' }\n') - if info_can[C.P_ID_TO_CHANGES]['A']: - f.write(' if (%sCanon_Outdated.A) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_A();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_A(&workspace, %sCanon_Params.A->x, 0, 0);\n' % info_opt[C.PREFIX]) + if parameter_canon.p_id_to_changes['A']: + f.write(' if (%sCanon_Outdated.A) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_A();\n' % configuration.prefix) + f.write(' osqp_update_A(&workspace, %sCanon_Params.A->x, 0, 0);\n' % configuration.prefix) f.write(' }\n') - if info_can[C.P_ID_TO_CHANGES]['q']: - f.write(' if (%sCanon_Outdated.q) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_q();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_lin_cost(&workspace, %sCanon_Params.q);\n' % info_opt[C.PREFIX]) + if parameter_canon.p_id_to_changes['q']: + f.write(' if (%sCanon_Outdated.q) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_q();\n' % configuration.prefix) + f.write(' osqp_update_lin_cost(&workspace, %sCanon_Params.q);\n' % configuration.prefix) f.write(' }\n') - if info_can[C.P_ID_TO_CHANGES]['d']: - f.write(' if (%sCanon_Outdated.d) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_d();\n' % info_opt[C.PREFIX]) + if parameter_canon.p_id_to_changes['d']: + f.write(' if (%sCanon_Outdated.d) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_d();\n' % configuration.prefix) f.write(' }\n') - if info_can[C.P_ID_TO_CHANGES]['l'] and info_can[C.P_ID_TO_CHANGES]['u']: + if parameter_canon.p_id_to_changes['l'] and parameter_canon.p_id_to_changes['u']: f.write(' if (%sCanon_Outdated.l && %sCanon_Outdated.u) {\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' %scpg_canonicalize_l();\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_u();\n' % info_opt[C.PREFIX]) + % (configuration.prefix, configuration.prefix)) + f.write(' %scpg_canonicalize_l();\n' % configuration.prefix) + f.write(' %scpg_canonicalize_u();\n' % configuration.prefix) f.write(' osqp_update_bounds(&workspace, %sCanon_Params.l, %sCanon_Params.u);\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' } else if (%sCanon_Outdated.l) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_l();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_lower_bound(&workspace, %sCanon_Params.l);\n' % info_opt[C.PREFIX]) - f.write(' } else if (%sCanon_Outdated.u) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_u();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_upper_bound(&workspace, %sCanon_Params.u);\n' % info_opt[C.PREFIX]) + % (configuration.prefix, configuration.prefix)) + f.write(' } else if (%sCanon_Outdated.l) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_l();\n' % configuration.prefix) + f.write(' osqp_update_lower_bound(&workspace, %sCanon_Params.l);\n' % configuration.prefix) + f.write(' } else if (%sCanon_Outdated.u) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_u();\n' % configuration.prefix) + f.write(' osqp_update_upper_bound(&workspace, %sCanon_Params.u);\n' % configuration.prefix) f.write(' }\n') else: - if info_can[C.P_ID_TO_CHANGES]['l']: - f.write(' if (%sCanon_Outdated.l) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_l();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_lower_bound(&workspace, %sCanon_Params.l);\n' % info_opt[C.PREFIX]) + if parameter_canon.p_id_to_changes['l']: + f.write(' if (%sCanon_Outdated.l) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_l();\n' % configuration.prefix) + f.write(' osqp_update_lower_bound(&workspace, %sCanon_Params.l);\n' % configuration.prefix) f.write(' }\n') - if info_can[C.P_ID_TO_CHANGES]['u']: - f.write(' if (%sCanon_Outdated.u) {\n' % info_opt[C.PREFIX]) - f.write(' %scpg_canonicalize_u();\n' % info_opt[C.PREFIX]) - f.write(' osqp_update_upper_bound(&workspace, %sCanon_Params.u);\n' % info_opt[C.PREFIX]) + if parameter_canon.p_id_to_changes['u']: + f.write(' if (%sCanon_Outdated.u) {\n' % configuration.prefix) + f.write(' %scpg_canonicalize_u();\n' % configuration.prefix) + f.write(' osqp_update_upper_bound(&workspace, %sCanon_Params.u);\n' % configuration.prefix) f.write(' }\n') - elif info_opt[C.SOLVER_NAME] in ['SCS', 'ECOS']: + elif configuration.solver_name in ['SCS', 'ECOS']: - for p_id, changes in info_can[C.P_ID_TO_CHANGES].items(): + for p_id, changes in parameter_canon.p_id_to_changes.items(): if changes: - f.write(' if (%sCanon_Outdated.%s) {\n' % (info_opt[C.PREFIX], p_id)) - f.write(' %scpg_canonicalize_%s();\n' % (info_opt[C.PREFIX], p_id)) + f.write(' if (%sCanon_Outdated.%s) {\n' % (configuration.prefix, p_id)) + f.write(' %scpg_canonicalize_%s();\n' % (configuration.prefix, p_id)) f.write(' }\n') - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': f.write(' // Solve with OSQP\n') f.write(' osqp_solve(&workspace);\n') - elif info_opt[C.SOLVER_NAME] == 'SCS': + elif configuration.solver_name == 'SCS': f.write(' // Solve with SCS\n') - f.write(' if (initialized == 0 || %sCanon_Outdated.A) {\n' % info_opt[C.PREFIX]) + f.write(' if (initialized == 0 || %sCanon_Outdated.A) {\n' % configuration.prefix) f.write(' %sScs_Work = scs_init(&%sScs_D, &%sScs_K, &%sScs_Stgs);\n' % - (info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX])) + (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) f.write(' initialized = 1;\n') - f.write(' } else if (%sCanon_Outdated.b && %sCanon_Outdated.c) {\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + f.write(' } else if (%sCanon_Outdated.b && %sCanon_Outdated.c) {\n' % (configuration.prefix, configuration.prefix)) f.write(' scs_update(%sScs_Work, %sCanon_Params.b, %sCanon_Params.c);\n' % - (info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' } else if (%sCanon_Outdated.b) {\n' % info_opt[C.PREFIX]) + (configuration.prefix, configuration.prefix, configuration.prefix)) + f.write(' } else if (%sCanon_Outdated.b) {\n' % configuration.prefix) f.write(' scs_update(%sScs_Work, %sCanon_Params.b, SCS_NULL);\n' % - (info_opt[C.PREFIX], info_opt[C.PREFIX])) - f.write(' } else if (%sCanon_Outdated.c) {\n' % info_opt[C.PREFIX]) + (configuration.prefix, configuration.prefix)) + f.write(' } else if (%sCanon_Outdated.c) {\n' % configuration.prefix) f.write(' scs_update(%sScs_Work, SCS_NULL, %sCanon_Params.c);\n' % - (info_opt[C.PREFIX], info_opt[C.PREFIX])) + (configuration.prefix, configuration.prefix)) f.write(' }\n') f.write(' scs_solve(%sScs_Work, &%sScs_Sol, &%sScs_Info, (initialized && %sScs_Stgs.warm_start));\n' % - (info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX])) - elif info_opt[C.SOLVER_NAME] == 'ECOS': - for p_id, size in info_can[C.P_ID_TO_SIZE].items(): + (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) + elif configuration.solver_name == 'ECOS': + for p_id, size in parameter_canon.p_id_to_size.items(): if size == 1: f.write(' %sCanon_Params_ECOS.%s = %sCanon_Params.%s;\n' - % (info_opt[C.PREFIX], p_id, info_opt[C.PREFIX], p_id)) + % (configuration.prefix, p_id, configuration.prefix, p_id)) elif size > 1: f.write(' for (i=0; i<%d; i++){\n' % size) if p_id.isupper(): f.write(' %sCanon_Params_ECOS.%s->x[i] = %sCanon_Params.%s->x[i];\n' - % (info_opt[C.PREFIX], p_id, info_opt[C.PREFIX], p_id)) + % (configuration.prefix, p_id, configuration.prefix, p_id)) else: f.write(' %sCanon_Params_ECOS.%s[i] = %sCanon_Params.%s[i];\n' - % (info_opt[C.PREFIX], p_id, info_opt[C.PREFIX], p_id)) + % (configuration.prefix, p_id, configuration.prefix, p_id)) f.write(' }\n') f.write(' // Initialize / update ECOS workspace and settings\n') - write_ecos_setup_update(f, info_can[C.CONSTANTS], info_opt[C.PREFIX]) - for name in info_can[C.SETTINGS_NAMES_TO_TYPE].keys(): + write_ecos_setup_update(f, solver_interface.canon_constants, configuration.prefix) + for name in solver_interface.settings_names_to_type.keys(): f.write(' %secos_workspace->stgs->%s = %sCanon_Settings.%s;\n' - % (info_opt[C.PREFIX], name, info_opt[C.PREFIX], name)) + % (configuration.prefix, name, configuration.prefix, name)) f.write(' // Solve with ECOS\n') - f.write(' %secos_flag = ECOS_solve(%secos_workspace);\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + f.write(' %secos_flag = ECOS_solve(%secos_workspace);\n' % (configuration.prefix, configuration.prefix)) f.write(' // Retrieve results\n') - if info_cg[C.RET_PRIM_FUNC_EXISTS]: - f.write(' %scpg_retrieve_prim();\n' % info_opt[C.PREFIX]) - if info_cg[C.RET_DUAL_FUNC_EXISTS]: - f.write(' %scpg_retrieve_dual();\n' % info_opt[C.PREFIX]) - f.write(' %scpg_retrieve_info();\n' % info_opt[C.PREFIX]) + if solver_interface.ret_prim_func_exists(variable_info): + f.write(' %scpg_retrieve_prim();\n' % configuration.prefix) + if solver_interface.ret_dual_func_exists(dual_variable_info): + f.write(' %scpg_retrieve_dual();\n' % configuration.prefix) + f.write(' %scpg_retrieve_info();\n' % configuration.prefix) f.write(' // Reset flags for outdated canonical parameters\n') - for p_id in info_can[C.P_ID_TO_SIZE].keys(): - f.write(' %sCanon_Outdated.%s = 0;\n' % (info_opt[C.PREFIX], p_id)) + for p_id in parameter_canon.p_id_to_size.keys(): + f.write(' %sCanon_Outdated.%s = 0;\n' % (configuration.prefix, p_id)) f.write('}\n\n') f.write('// Update solver settings\n') - f.write('void %scpg_set_solver_default_settings(){\n' % info_opt[C.PREFIX]) - if info_opt[C.SOLVER_NAME] == 'OSQP': + f.write('void %scpg_set_solver_default_settings(){\n' % configuration.prefix) + if configuration.solver_name == 'OSQP': f.write(' osqp_set_default_settings(&settings);\n') - elif info_opt[C.SOLVER_NAME] == 'SCS': - f.write(' scs_set_default_settings(&%sScs_Stgs);\n' % info_opt[C.PREFIX]) - elif info_opt[C.SOLVER_NAME] == 'ECOS': - for name, value in info_can[C.SETTINGS_NAMES_TO_DEFAULT].items(): - f.write(' %sCanon_Settings.%s = %s;\n' % (info_opt[C.PREFIX], name, value)) + elif configuration.solver_name == 'SCS': + f.write(' scs_set_default_settings(&%sScs_Stgs);\n' % configuration.prefix) + elif configuration.solver_name == 'ECOS': + for name, value in solver_interface.settings_names_to_default.items(): + f.write(' %sCanon_Settings.%s = %s;\n' % (configuration.prefix, name, value)) f.write('}\n') - for name, typ in info_can[C.SETTINGS_NAMES_TO_TYPE].items(): - f.write('\nvoid %scpg_set_solver_%s(%s %s_new){\n' % (info_opt[C.PREFIX], name, typ, name)) - if info_opt[C.SOLVER_NAME] == 'OSQP': + for name, typ in solver_interface.settings_names_to_type.items(): + f.write('\nvoid %scpg_set_solver_%s(%s %s_new){\n' % (configuration.prefix, name, typ, name)) + if configuration.solver_name == 'OSQP': f.write(' osqp_update_%s(&workspace, %s_new);\n' % (name, name)) - elif info_opt[C.SOLVER_NAME] == 'SCS': - f.write(' %sScs_Stgs.%s = %s_new;\n' % (info_opt[C.PREFIX], name, name)) - elif info_opt[C.SOLVER_NAME] == 'ECOS': - f.write(' %sCanon_Settings.%s = %s_new;\n' % (info_opt[C.PREFIX], name, name)) + elif configuration.solver_name == 'SCS': + f.write(' %sScs_Stgs.%s = %s_new;\n' % (configuration.prefix, name, name)) + elif configuration.solver_name == 'ECOS': + f.write(' %sCanon_Settings.%s = %s_new;\n' % (configuration.prefix, name, name)) f.write('}\n') -def write_solve_prot(f, info_opt, info_cg, info_usr, info_can): +def write_solve_prot(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): """ Write function declarations to file """ write_description(f, 'c', 'Function declarations') - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': f.write('#include "types.h"\n') - elif info_opt[C.SOLVER_NAME] in ['SCS', 'ECOS']: + elif configuration.solver_name in ['SCS', 'ECOS']: f.write('#include "cpg_workspace.h"\n') f.write('\n// Update user-defined parameter values\n') - for name, size in info_usr[C.P_NAME_TO_SIZE].items(): + for name, size in parameter_info.name_to_size_usp.items(): if size == 1: - f.write('extern void %scpg_update_%s(c_float val);\n' % (info_opt[C.PREFIX], name)) + f.write('extern void %scpg_update_%s(c_float val);\n' % (configuration.prefix, name)) else: - f.write('extern void %scpg_update_%s(c_int idx, c_float val);\n' % (info_opt[C.PREFIX], name)) + f.write('extern void %scpg_update_%s(c_int idx, c_float val);\n' % (configuration.prefix, name)) f.write('\n// Map user-defined to canonical parameters\n') - for p_id, changes in info_can[C.P_ID_TO_CHANGES].items(): + for p_id, changes in parameter_canon.p_id_to_changes.items(): if changes: - f.write('extern void %scpg_canonicalize_%s();\n' % (info_opt[C.PREFIX], p_id)) + f.write('extern void %scpg_canonicalize_%s();\n' % (configuration.prefix, p_id)) - if info_cg[C.RET_PRIM_FUNC_EXISTS]: + if solver_interface.ret_prim_func_exists(variable_info): f.write('\n// Retrieve primal solution in terms of user-defined variables\n') - f.write('extern void %scpg_retrieve_prim();\n' % info_opt[C.PREFIX]) + f.write('extern void %scpg_retrieve_prim();\n' % configuration.prefix) - if info_cg[C.RET_DUAL_FUNC_EXISTS]: + if solver_interface.ret_dual_func_exists(dual_variable_info): f.write('\n// Retrieve dual solution in terms of user-defined constraints\n') - f.write('extern void %scpg_retrieve_dual();\n' % info_opt[C.PREFIX]) + f.write('extern void %scpg_retrieve_dual();\n' % configuration.prefix) f.write('\n// Retrieve solver information\n') - f.write('extern void %scpg_retrieve_info();\n' % info_opt[C.PREFIX]) + f.write('extern void %scpg_retrieve_info();\n' % configuration.prefix) f.write('\n// Solve via canonicalization, canonical solve, retrieval\n') - f.write('extern void %scpg_solve();\n' % info_opt[C.PREFIX]) + f.write('extern void %scpg_solve();\n' % configuration.prefix) f.write('\n// Update solver settings\n') - f.write('extern void %scpg_set_solver_default_settings();\n' % info_opt[C.PREFIX]) - for name, typ in info_can[C.SETTINGS_NAMES_TO_TYPE].items(): - f.write('extern void %scpg_set_solver_%s(%s %s_new);\n' % (info_opt[C.PREFIX], name, typ, name)) + f.write('extern void %scpg_set_solver_default_settings();\n' % configuration.prefix) + for name, typ in solver_interface.settings_names_to_type.items(): + f.write('extern void %scpg_set_solver_%s(%s %s_new);\n' % (configuration.prefix, name, typ, name)) -def write_example_def(f, info_opt, info_usr): +def write_example_def(f, configuration, variable_info, dual_variable_info, parameter_info): """ Write main function to file """ @@ -1139,70 +1100,70 @@ def write_example_def(f, info_opt, info_usr): f.write('int main(int argc, char *argv[]){\n\n') f.write(' // Update first entry of every user-defined parameter\n') - for name, value in info_usr[C.P_WRITABLE].items(): + for name, value in parameter_info.writable.items(): if is_mathematical_scalar(value): - f.write(' %scpg_update_%s(%.20f);\n' % (info_opt[C.PREFIX], name, value)) + f.write(' %scpg_update_%s(%.20f);\n' % (configuration.prefix, name, value)) else: - f.write(' %scpg_update_%s(0, %.20f);\n' % (info_opt[C.PREFIX], name, value[0])) + f.write(' %scpg_update_%s(0, %.20f);\n' % (configuration.prefix, name, value[0])) f.write('\n // Solve the problem instance\n') - f.write(' %scpg_solve();\n\n' % info_opt[C.PREFIX]) + f.write(' %scpg_solve();\n\n' % configuration.prefix) f.write(' // Print objective function value\n') - f.write(' printf("obj = %%f\\n", %sCPG_Result.info->obj_val);\n\n' % info_opt[C.PREFIX]) + f.write(' printf("obj = %%f\\n", %sCPG_Result.info->obj_val);\n\n' % configuration.prefix) f.write(' // Print primal solution\n') - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': int_format_str = 'lld' else: int_format_str = 'd' - for name, var in info_usr[C.V_NAME_TO_INIT].items(): + for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' printf("%s = %%f\\n", %sCPG_Result.prim->%s);\n' % (name, info_opt[C.PREFIX], name)) + f.write(' printf("%s = %%f\\n", %sCPG_Result.prim->%s);\n' % (name, configuration.prefix, name)) else: f.write(' for(i=0; i<%d; i++) {\n' % var.size) f.write(' printf("%s[%%%s] = %%f\\n", i, %sCPG_Result.prim->%s[i]);\n' - % (name, int_format_str, info_opt[C.PREFIX], name)) + % (name, int_format_str, configuration.prefix, name)) f.write(' }\n') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: f.write('\n // Print dual solution\n') - for name, var in info_usr[C.D_NAME_TO_INIT].items(): + for name, var in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' printf("%s = %%f\\n", %sCPG_Result.dual->%s);\n' % (name, info_opt[C.PREFIX], name)) + f.write(' printf("%s = %%f\\n", %sCPG_Result.dual->%s);\n' % (name, configuration.prefix, name)) else: f.write(' for(i=0; i<%d; i++) {\n' % var.size) f.write(' printf("%s[%%%s] = %%f\\n", i, %sCPG_Result.dual->%s[i]);\n' - % (name, int_format_str, info_opt[C.PREFIX], name)) + % (name, int_format_str, configuration.prefix, name)) f.write(' }\n') f.write('\n return 0;\n\n') f.write('}\n') -def replace_cmake_data(cmake_data, info_opt): +def replace_cmake_data(cmake_data, configuration): """ - Add C.PREFIX prefix to directory/file lists in top-level CMakeLists.txt + Add configuration.prefix to directory/file lists in top-level CMakeLists.txt """ now = datetime.now() cmake_data = cmake_data.replace('%DATE', now.strftime("on %B %d, %Y at %H:%M:%S")) - cmake_data = cmake_data.replace('cpg_include', info_opt[C.PREFIX]+'cpg_include') - cmake_data = cmake_data.replace('cpg_head', info_opt[C.PREFIX] + 'cpg_head') - return cmake_data.replace('cpg_src', info_opt[C.PREFIX] + 'cpg_src') + cmake_data = cmake_data.replace('cpg_include', configuration.prefix+'cpg_include') + cmake_data = cmake_data.replace('cpg_head', configuration.prefix + 'cpg_head') + return cmake_data.replace('cpg_src', configuration.prefix + 'cpg_src') -def write_canon_cmake(f, info_opt): +def write_canon_cmake(f, configuration): """ Pass sources to parent scope in {OSQP/ECOS}_code/CMakeLists.txt """ - if info_opt[C.SOLVER_NAME] == 'OSQP': + if configuration.solver_name == 'OSQP': f.write('\nset(solver_head "${osqp_headers}" PARENT_SCOPE)') f.write('\nset(solver_src "${osqp_src}" PARENT_SCOPE)') - elif info_opt[C.SOLVER_NAME] == 'SCS': + elif configuration.solver_name == 'SCS': f.write('\nset(solver_head') f.write('\n ${${PROJECT_NAME}_HDR}') f.write('\n ${DIRSRC}/private.h') @@ -1215,12 +1176,12 @@ def write_canon_cmake(f, info_opt): f.write('\n ${${PROJECT_NAME}_AMD_EXTERNAL_SRC})') f.write('\n\nset(solver_head "${solver_head}" PARENT_SCOPE)') f.write('\nset(solver_src "${solver_src}" PARENT_SCOPE)') - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': f.write('\nset(solver_head "${ecos_headers}" PARENT_SCOPE)') f.write('\nset(solver_src "${ecos_sources}" PARENT_SCOPE)') -def write_module_def(f, info_opt, info_usr, info_can): +def write_module_def(f, configuration, variable_info, dual_variable_info, parameter_info, solver_interface): """ Write c++ file for pbind11 wrapper """ @@ -1236,65 +1197,65 @@ def write_module_def(f, info_opt, info_usr, info_can): f.write('}\n\n') f.write('namespace py = pybind11;\n\n') if max( - max(info_usr[C.P_NAME_TO_SIZE].values(), default=0), - max(info_usr[C.V_NAME_TO_SIZE].values(), default=0), - max(info_usr[C.D_NAME_TO_SIZE].values(), default=0) + max(parameter_info.name_to_size_usp.values(), default=0), + max(variable_info.name_to_size.values(), default=0), + max(dual_variable_info.name_to_size.values(), default=0) ) > 1: f.write('static int i;\n\n') # cpp function that maps parameters to results f.write('%sCPG_Result_cpp_t %ssolve_cpp(struct %sCPG_Updated_cpp_t& CPG_Updated_cpp, ' 'struct %sCPG_Params_cpp_t& CPG_Params_cpp){\n\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX])) + % (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) f.write(' // Pass changed user-defined parameter values to the solver\n') - for name, size in info_usr[C.P_NAME_TO_SIZE].items(): + for name, size in parameter_info.name_to_size_usp.items(): f.write(' if (CPG_Updated_cpp.%s) {\n' % name) if size == 1: - f.write(' %scpg_update_%s(CPG_Params_cpp.%s);\n' % (info_opt[C.PREFIX], name, name)) + f.write(' %scpg_update_%s(CPG_Params_cpp.%s);\n' % (configuration.prefix, name, name)) else: f.write(' for(i=0; i<%d; i++) {\n' % size) - f.write(' %scpg_update_%s(i, CPG_Params_cpp.%s[i]);\n' % (info_opt[C.PREFIX], name, name)) + f.write(' %scpg_update_%s(i, CPG_Params_cpp.%s[i]);\n' % (configuration.prefix, name, name)) f.write(' }\n') f.write(' }\n') # perform ASA procedure f.write('\n // Solve\n') f.write(' std::clock_t ASA_start = std::clock();\n') - f.write(' %scpg_solve();\n' % info_opt[C.PREFIX]) + f.write(' %scpg_solve();\n' % configuration.prefix) f.write(' std::clock_t ASA_end = std::clock();\n\n') # arrange and return results f.write(' // Arrange and return results\n') - f.write(' %sCPG_Prim_cpp_t CPG_Prim_cpp {};\n' % info_opt[C.PREFIX]) - for name, var in info_usr[C.V_NAME_TO_INIT].items(): + f.write(' %sCPG_Prim_cpp_t CPG_Prim_cpp {};\n' % configuration.prefix) + for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' CPG_Prim_cpp.%s = %sCPG_Prim.%s;\n' % (name, info_opt[C.PREFIX], name)) + f.write(' CPG_Prim_cpp.%s = %sCPG_Prim.%s;\n' % (name, configuration.prefix, name)) else: f.write(' for(i=0; i<%d; i++) {\n' % var.size) f.write(' CPG_Prim_cpp.%s[i] = %sCPG_Prim.%s[i];\n' - % (name, info_opt[C.PREFIX], name)) + % (name, configuration.prefix, name)) f.write(' }\n') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: - f.write(' %sCPG_Dual_cpp_t CPG_Dual_cpp {};\n' % info_opt[C.PREFIX]) - for name, var in info_usr[C.D_NAME_TO_INIT].items(): + if len(dual_variable_info.name_to_init) > 0: + f.write(' %sCPG_Dual_cpp_t CPG_Dual_cpp {};\n' % configuration.prefix) + for name, var in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' CPG_Dual_cpp.%s = %sCPG_Dual.%s;\n' % (name, info_opt[C.PREFIX], name)) + f.write(' CPG_Dual_cpp.%s = %sCPG_Dual.%s;\n' % (name, configuration.prefix, name)) else: f.write(' for(i=0; i<%d; i++) {\n' % var.size) - f.write(' CPG_Dual_cpp.%s[i] = %sCPG_Dual.%s[i];\n' % (name, info_opt[C.PREFIX], name)) + f.write(' CPG_Dual_cpp.%s[i] = %sCPG_Dual.%s[i];\n' % (name, configuration.prefix, name)) f.write(' }\n') - f.write(' %sCPG_Info_cpp_t CPG_Info_cpp {};\n' % info_opt[C.PREFIX]) + f.write(' %sCPG_Info_cpp_t CPG_Info_cpp {};\n' % configuration.prefix) for field in ['obj_val', 'iter', 'status', 'pri_res', 'dua_res']: - f.write(' CPG_Info_cpp.%s = %sCPG_Info.%s;\n' % (field, info_opt[C.PREFIX], field)) + f.write(' CPG_Info_cpp.%s = %sCPG_Info.%s;\n' % (field, configuration.prefix, field)) f.write(' CPG_Info_cpp.time = 1.0*(ASA_end-ASA_start) / CLOCKS_PER_SEC;\n') - f.write(' %sCPG_Result_cpp_t CPG_Result_cpp {};\n' % info_opt[C.PREFIX]) + f.write(' %sCPG_Result_cpp_t CPG_Result_cpp {};\n' % configuration.prefix) f.write(' CPG_Result_cpp.prim = CPG_Prim_cpp;\n') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: f.write(' CPG_Result_cpp.dual = CPG_Dual_cpp;\n') f.write(' CPG_Result_cpp.info = CPG_Info_cpp;\n') @@ -1305,60 +1266,60 @@ def write_module_def(f, info_opt, info_usr, info_can): # module f.write('PYBIND11_MODULE(cpg_module, m) {\n\n') - f.write(' py::class_<%sCPG_Params_cpp_t>(m, "%scpg_params")\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + f.write(' py::class_<%sCPG_Params_cpp_t>(m, "%scpg_params")\n' % (configuration.prefix, configuration.prefix)) f.write(' .def(py::init<>())\n') - for name in info_usr[C.P_NAME_TO_SIZE].keys(): - f.write(' .def_readwrite("%s", &%sCPG_Params_cpp_t::%s)\n' % (name, info_opt[C.PREFIX], name)) + for name in parameter_info.name_to_size_usp.keys(): + f.write(' .def_readwrite("%s", &%sCPG_Params_cpp_t::%s)\n' % (name, configuration.prefix, name)) f.write(' ;\n\n') f.write(' py::class_<%sCPG_Updated_cpp_t>(m, "%scpg_updated")\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + % (configuration.prefix, configuration.prefix)) f.write(' .def(py::init<>())\n') - for name in info_usr[C.P_NAME_TO_SIZE].keys(): - f.write(' .def_readwrite("%s", &%sCPG_Updated_cpp_t::%s)\n' % (name, info_opt[C.PREFIX], name)) + for name in parameter_info.name_to_size_usp.keys(): + f.write(' .def_readwrite("%s", &%sCPG_Updated_cpp_t::%s)\n' % (name, configuration.prefix, name)) f.write(' ;\n\n') - f.write(' py::class_<%sCPG_Prim_cpp_t>(m, "%scpg_prim")\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + f.write(' py::class_<%sCPG_Prim_cpp_t>(m, "%scpg_prim")\n' % (configuration.prefix, configuration.prefix)) f.write(' .def(py::init<>())\n') - for name in info_usr[C.V_NAME_TO_INIT].keys(): - f.write(' .def_readwrite("%s", &%sCPG_Prim_cpp_t::%s)\n' % (name, info_opt[C.PREFIX], name)) + for name in variable_info.name_to_init.keys(): + f.write(' .def_readwrite("%s", &%sCPG_Prim_cpp_t::%s)\n' % (name, configuration.prefix, name)) f.write(' ;\n\n') - if len(info_usr[C.D_NAME_TO_INIT]) > 0: - f.write(' py::class_<%sCPG_Dual_cpp_t>(m, "%scpg_dual")\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + if len(dual_variable_info.name_to_init) > 0: + f.write(' py::class_<%sCPG_Dual_cpp_t>(m, "%scpg_dual")\n' % (configuration.prefix, configuration.prefix)) f.write(' .def(py::init<>())\n') - for name in info_usr[C.D_NAME_TO_INIT].keys(): - f.write(' .def_readwrite("%s", &%sCPG_Dual_cpp_t::%s)\n' % (name, info_opt[C.PREFIX], name)) + for name in dual_variable_info.name_to_init.keys(): + f.write(' .def_readwrite("%s", &%sCPG_Dual_cpp_t::%s)\n' % (name, configuration.prefix, name)) f.write(' ;\n\n') - f.write(' py::class_<%sCPG_Info_cpp_t>(m, "%scpg_info")\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + f.write(' py::class_<%sCPG_Info_cpp_t>(m, "%scpg_info")\n' % (configuration.prefix, configuration.prefix)) f.write(' .def(py::init<>())\n') - f.write(' .def_readwrite("obj_val", &%sCPG_Info_cpp_t::obj_val)\n' % info_opt[C.PREFIX]) - f.write(' .def_readwrite("iter", &%sCPG_Info_cpp_t::iter)\n' % info_opt[C.PREFIX]) - f.write(' .def_readwrite("status", &%sCPG_Info_cpp_t::status)\n' % info_opt[C.PREFIX]) - f.write(' .def_readwrite("pri_res", &%sCPG_Info_cpp_t::pri_res)\n' % info_opt[C.PREFIX]) - f.write(' .def_readwrite("dua_res", &%sCPG_Info_cpp_t::dua_res)\n' % info_opt[C.PREFIX]) - f.write(' .def_readwrite("time", &%sCPG_Info_cpp_t::time)\n' % info_opt[C.PREFIX]) + f.write(' .def_readwrite("obj_val", &%sCPG_Info_cpp_t::obj_val)\n' % configuration.prefix) + f.write(' .def_readwrite("iter", &%sCPG_Info_cpp_t::iter)\n' % configuration.prefix) + f.write(' .def_readwrite("status", &%sCPG_Info_cpp_t::status)\n' % configuration.prefix) + f.write(' .def_readwrite("pri_res", &%sCPG_Info_cpp_t::pri_res)\n' % configuration.prefix) + f.write(' .def_readwrite("dua_res", &%sCPG_Info_cpp_t::dua_res)\n' % configuration.prefix) + f.write(' .def_readwrite("time", &%sCPG_Info_cpp_t::time)\n' % configuration.prefix) f.write(' ;\n\n') - f.write(' py::class_<%sCPG_Result_cpp_t>(m, "%scpg_result")\n' % (info_opt[C.PREFIX], info_opt[C.PREFIX])) + f.write(' py::class_<%sCPG_Result_cpp_t>(m, "%scpg_result")\n' % (configuration.prefix, configuration.prefix)) f.write(' .def(py::init<>())\n') - f.write(' .def_readwrite("cpg_prim", &%sCPG_Result_cpp_t::prim)\n' % info_opt[C.PREFIX]) - if len(info_usr[C.D_NAME_TO_INIT]) > 0: - f.write(' .def_readwrite("cpg_dual", &%sCPG_Result_cpp_t::dual)\n' % info_opt[C.PREFIX]) - f.write(' .def_readwrite("cpg_info", &%sCPG_Result_cpp_t::info)\n' % info_opt[C.PREFIX]) + f.write(' .def_readwrite("cpg_prim", &%sCPG_Result_cpp_t::prim)\n' % configuration.prefix) + if len(dual_variable_info.name_to_init) > 0: + f.write(' .def_readwrite("cpg_dual", &%sCPG_Result_cpp_t::dual)\n' % configuration.prefix) + f.write(' .def_readwrite("cpg_info", &%sCPG_Result_cpp_t::info)\n' % configuration.prefix) f.write(' ;\n\n') - f.write(' m.def("solve", &%ssolve_cpp);\n\n' % info_opt[C.PREFIX]) + f.write(' m.def("solve", &%ssolve_cpp);\n\n' % configuration.prefix) - f.write(' m.def("set_solver_default_settings", &%scpg_set_solver_default_settings);\n' % info_opt[C.PREFIX]) - for name in info_can[C.SETTINGS_NAMES_TO_TYPE].keys(): - f.write(' m.def("set_solver_%s", &%scpg_set_solver_%s);\n' % (name, info_opt[C.PREFIX], name)) + f.write(' m.def("set_solver_default_settings", &%scpg_set_solver_default_settings);\n' % configuration.prefix) + for name in solver_interface.settings_names_to_type.keys(): + f.write(' m.def("set_solver_%s", &%scpg_set_solver_%s);\n' % (name, configuration.prefix, name)) f.write('\n}\n') -def write_module_prot(f, info_opt, info_usr): +def write_module_prot(f, configuration, parameter_info, variable_info, dual_variable_info): """ Write c++ file for pbind11 wrapper """ @@ -1367,8 +1328,8 @@ def write_module_prot(f, info_opt, info_usr): # cpp struct containing user-defined parameters f.write('// User-defined parameters\n') - f.write('struct %sCPG_Params_cpp_t {\n' % info_opt[C.PREFIX]) - for name, size in info_usr[C.P_NAME_TO_SIZE].items(): + f.write('struct %sCPG_Params_cpp_t {\n' % configuration.prefix) + for name, size in parameter_info.name_to_size_usp.items(): if size == 1: f.write(' double %s;\n' % name) else: @@ -1377,15 +1338,15 @@ def write_module_prot(f, info_opt, info_usr): # cpp struct containing update flags for user-defined parameters f.write('// Flags for updated user-defined parameters\n') - f.write('struct %sCPG_Updated_cpp_t {\n' % info_opt[C.PREFIX]) - for name in info_usr[C.P_NAME_TO_SIZE].keys(): + f.write('struct %sCPG_Updated_cpp_t {\n' % configuration.prefix) + for name in parameter_info.name_to_size_usp.keys(): f.write(' bool %s;\n' % name) f.write('};\n\n') # cpp struct containing primal variables f.write('// Primal solution\n') - f.write('struct %sCPG_Prim_cpp_t {\n' % info_opt[C.PREFIX]) - for name, var in info_usr[C.V_NAME_TO_INIT].items(): + f.write('struct %sCPG_Prim_cpp_t {\n' % configuration.prefix) + for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): f.write(' double %s;\n' % name) else: @@ -1393,10 +1354,10 @@ def write_module_prot(f, info_opt, info_usr): f.write('};\n\n') # cpp struct containing dual variables - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: f.write('// Dual solution\n') - f.write('struct %sCPG_Dual_cpp_t {\n' % info_opt[C.PREFIX]) - for name, var in info_usr[C.D_NAME_TO_INIT].items(): + f.write('struct %sCPG_Dual_cpp_t {\n' % configuration.prefix) + for name, var in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(var): f.write(' double %s;\n' % name) else: @@ -1405,12 +1366,12 @@ def write_module_prot(f, info_opt, info_usr): # cpp struct containing info on results f.write('// Solver information\n') - f.write('struct %sCPG_Info_cpp_t {\n' % info_opt[C.PREFIX]) + f.write('struct %sCPG_Info_cpp_t {\n' % configuration.prefix) f.write(' double obj_val;\n') f.write(' int iter;\n') - if info_opt[C.SOLVER_NAME] in ['OSQP', 'SCS']: + if configuration.solver_name in ['OSQP', 'SCS']: f.write(' char* status;\n') - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': f.write(' int status;\n') f.write(' double pri_res;\n') f.write(' double dua_res;\n') @@ -1419,18 +1380,18 @@ def write_module_prot(f, info_opt, info_usr): # cpp struct containing objective value and user-defined variables f.write('// Solution and solver information\n') - f.write('struct %sCPG_Result_cpp_t {\n' % info_opt[C.PREFIX]) - f.write(' %sCPG_Prim_cpp_t prim;\n' % info_opt[C.PREFIX]) - if len(info_usr[C.D_NAME_TO_INIT]) > 0: - f.write(' %sCPG_Dual_cpp_t dual;\n' % info_opt[C.PREFIX]) - f.write(' %sCPG_Info_cpp_t info;\n' % info_opt[C.PREFIX]) + f.write('struct %sCPG_Result_cpp_t {\n' % configuration.prefix) + f.write(' %sCPG_Prim_cpp_t prim;\n' % configuration.prefix) + if len(dual_variable_info.name_to_init) > 0: + f.write(' %sCPG_Dual_cpp_t dual;\n' % configuration.prefix) + f.write(' %sCPG_Info_cpp_t info;\n' % configuration.prefix) f.write('};\n\n') # cpp function that maps parameters to results f.write('// Main solve function\n') f.write('%sCPG_Result_cpp_t %ssolve_cpp(struct %sCPG_Updated_cpp_t& CPG_Updated_cpp, ' 'struct %sCPG_Params_cpp_t& CPG_Params_cpp);\n' - % (info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX], info_opt[C.PREFIX])) + % (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) def replace_setup_data(text): @@ -1443,7 +1404,7 @@ def replace_setup_data(text): return text.replace('%DATE', now.strftime("on %B %d, %Y at %H:%M:%S")) -def write_method(f, info_opt, info_usr): +def write_method(f, configuration, variable_info, dual_variable_info, parameter_info): """ Write function to be registered as custom CVXPY solve method """ @@ -1454,9 +1415,9 @@ def write_method(f, info_opt, info_usr): f.write('import numpy as np\n') f.write('from cvxpy.reductions import Solution\n') f.write('from cvxpy.problems.problem import SolverStats\n') - f.write('from %s import cpg_module\n\n\n' % info_opt[C.CODE_DIR].replace('/', '.').replace('\\', '.')) + f.write('from %s import cpg_module\n\n\n' % configuration.code_dir.replace('/', '.').replace('\\', '.')) - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': indent = ' ' * 24 f.write('status_int_to_string = {0: "Optimal solution found", \n' + indent + '1: "Certificate of primal infeasibility found", \n' + @@ -1473,10 +1434,10 @@ def write_method(f, info_opt, info_usr): f.write('def cpg_solve(prob, updated_params=None, **kwargs):\n\n') f.write(' # set flags for updated parameters\n') - f.write(' upd = cpg_module.%scpg_updated()\n' % info_opt[C.PREFIX]) + f.write(' upd = cpg_module.%scpg_updated()\n' % configuration.prefix) f.write(' if updated_params is None:\n') p_list_string = '' - for name in info_usr[C.P_NAME_TO_SIZE].keys(): + for name in parameter_info.name_to_size_usp.keys(): p_list_string += '"%s", ' % name f.write(' updated_params = [%s]\n' % p_list_string[:-2]) f.write(' for p in updated_params:\n') @@ -1488,7 +1449,7 @@ def write_method(f, info_opt, info_usr): f.write(' # set solver settings\n') f.write(' cpg_module.set_solver_default_settings()\n') f.write(' for key, value in kwargs.items():\n') - if info_opt[C.SOLVER_NAME] == 'ECOS': + if configuration.solver_name == 'ECOS': f.write(' if key == "max_iters":\n') f.write(' key = "maxit"\n') f.write(' try:\n') @@ -1497,13 +1458,13 @@ def write_method(f, info_opt, info_usr): f.write(' raise(AttributeError(\'Solver setting "%s" not available.\' % key))\n\n') f.write(' # set parameter values\n') - f.write(' par = cpg_module.%scpg_params()\n' % info_opt[C.PREFIX]) + f.write(' par = cpg_module.%scpg_params()\n' % configuration.prefix) # prob.param_dict is a computed property. Avoid repeated calls in the loop. f.write(' param_dict = prob.param_dict\n') - for name, size in info_usr[C.P_NAME_TO_SIZE].items(): - if name in info_usr[C.P_NAME_TO_SPARSITY].keys(): + for name, size in parameter_info.name_to_size_usp.items(): + if name in parameter_info.name_to_sparsity.keys(): f.write(' n = param_dict[\'%s\'].shape[0]\n' % name) - if info_usr[C.P_NAME_TO_SPARSITY_TYPE][name] == 'diag': + if parameter_info.name_to_sparsity_type[name] == 'diag': f.write(' %s_coordinates = np.arange(0, n**2, n+1)\n' % name) else: f.write(' %s_coordinates = np.unique([coord[0]+coord[1]*n for coord in ' @@ -1533,7 +1494,7 @@ def write_method(f, info_opt, info_usr): f.write(' # store solution in problem object\n') f.write(' prob._clear_solution()\n') - for name, shape in info_usr[C.V_NAME_TO_SHAPE].items(): + for name, shape in variable_info.name_to_shape.items(): if len(shape) == 2: f.write(' prob.var_dict[\'%s\'].save_value(np.array(res.cpg_prim.%s).reshape((%d, %d), order=\'F\'))\n' % (name, name, shape[0], shape[1])) @@ -1542,7 +1503,7 @@ def write_method(f, info_opt, info_usr): % (name, name, shape[0])) else: f.write(' prob.var_dict[\'%s\'].save_value(np.array(res.cpg_prim.%s))\n' % (name, name)) - for i, (name, shape) in enumerate(info_usr[C.D_NAME_TO_SHAPE].items()): + for i, (name, shape) in enumerate(dual_variable_info.name_to_shape.items()): if len(shape) == 2: f.write(' prob.constraints[%d].save_dual_value(' 'np.array(res.cpg_dual.%s).reshape((%d, %d), order=\'F\'))\n' % (i, name, shape[0], shape[1])) @@ -1553,9 +1514,9 @@ def write_method(f, info_opt, info_usr): f.write(' prob.constraints[%d].save_dual_value(np.array(res.cpg_dual.%s))\n' % (i, name)) f.write('\n # store additional solver information in problem object\n') - if info_opt[C.SOLVER_NAME] in ['OSQP', 'SCS']: + if configuration.solver_name in ['OSQP', 'SCS']: f.write(' prob._status = res.cpg_info.status\n') - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': f.write(' prob._status = status_int_to_string[res.cpg_info.status]\n') f.write(' if abs(res.cpg_info.obj_val) == 1e30:\n') f.write(' prob._value = np.sign(res.cpg_info.obj_val)*np.inf\n') @@ -1575,12 +1536,12 @@ def write_method(f, info_opt, info_usr): f.write(' results_dict = {\'solver_specific_stats\': solver_specific_stats,\n') f.write(' \'num_iters\': res.cpg_info.iter,\n') f.write(' \'solve_time\': t1-t0}\n') - f.write(' prob._solver_stats = SolverStats(results_dict, \'%s\')\n\n' % info_opt[C.SOLVER_NAME]) + f.write(' prob._solver_stats = SolverStats(results_dict, \'%s\')\n\n' % configuration.solver_name) f.write(' return prob.value\n') -def replace_html_data(text, info_opt, info_usr, info_can): +def replace_html_data(text, configuration, variable_info, dual_variable_info, parameter_info, solver_interface): """ Replace placeholder strings in html documentation file """ @@ -1590,34 +1551,34 @@ def replace_html_data(text, info_opt, info_usr, info_can): text = text.replace('$DATE', now.strftime("on %B %d, %Y at %H:%M:%S")) # param summary - text = text.replace('$PARAMS', write_problem_summary(info_usr[C.P_NAME_TO_SHAPE], info_usr[C.P_NAME_TO_SIZE])) + text = text.replace('$PARAMS', write_problem_summary(parameter_info.name_to_shape, parameter_info.name_to_size_usp)) # primal variable summary - text = text.replace('$PRIMALS', write_problem_summary(info_usr[C.V_NAME_TO_SHAPE], info_usr[C.V_NAME_TO_SIZE])) + text = text.replace('$PRIMALS', write_problem_summary(variable_info.name_to_shape, variable_info.name_to_size)) # dual variable summary - text = text.replace('$DUALS', write_problem_summary(info_usr[C.D_NAME_TO_SHAPE], info_usr[C.D_NAME_TO_SIZE])) + text = text.replace('$DUALS', write_problem_summary(dual_variable_info.name_to_shape, dual_variable_info.name_to_size)) # code_dir - text = text.replace('$CODEDIR', info_opt[C.CODE_DIR]) - text = text.replace('$CDPYTHON', info_opt[C.CODE_DIR].replace('/', '.').replace('\\', '.')) + text = text.replace('$CODEDIR', configuration.code_dir) + text = text.replace('$CDPYTHON', configuration.code_dir.replace('/', '.').replace('\\', '.')) # solver name and docu - text = text.replace('$CPGSOLVERNAME', info_opt[C.SOLVER_NAME]) - if info_opt[C.SOLVER_NAME] == 'OSQP': + text = text.replace('$CPGSOLVERNAME', configuration.solver_name) + if configuration.solver_name == 'OSQP': text = text.replace('$CPGSOLVERDOCUURL', 'https://osqp.org/docs/codegen/python.html') - elif info_opt[C.SOLVER_NAME] == 'SCS': + elif configuration.solver_name == 'SCS': text = text.replace('$CPGSOLVERDOCUURL', 'https://www.cvxgrp.org/scs/api/c.html') - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': text = text.replace('$CPGSOLVERDOCUURL', 'https://github.com/embotech/ecos/wiki/Usage-from-C') # CMake prefix - text = text.replace('$CPGCMAKELISTS', info_opt[C.PREFIX]+'cpg') + text = text.replace('$CPGCMAKELISTS', configuration.prefix+'cpg') # type definition of CPG_Prim_t CPGPRIMTYPEDEF = '\n// Struct type with primal solution\n' CPGPRIMTYPEDEF += 'typedef struct {\n' - for name, var in info_usr[C.V_NAME_TO_INIT].items(): + for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): s = '' else: @@ -1627,10 +1588,10 @@ def replace_html_data(text, info_opt, info_usr, info_can): text = text.replace('$CPGPRIMTYPEDEF', CPGPRIMTYPEDEF) # type definition of CPG_Dual_t - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: CPGDUALTYPEDEF = '\n// Struct type with dual solution\n' CPGDUALTYPEDEF += 'typedef struct {\n' - for name, var in info_usr[C.D_NAME_TO_INIT].items(): + for name, var in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(var): s = '' else: @@ -1647,9 +1608,9 @@ def replace_html_data(text, info_opt, info_usr, info_can): CPGINFOTYPEDEF += 'typedef struct {\n' CPGINFOTYPEDEF += ' c_float obj_val; // Objective function value\n' CPGINFOTYPEDEF += ' c_int iter; // Number of iterations\n' - if info_opt[C.SOLVER_NAME] in ['OSQP', 'SCS']: + if configuration.solver_name in ['OSQP', 'SCS']: CPGINFOTYPEDEF += ' char *status; // Solver status\n' - elif info_opt[C.SOLVER_NAME] == 'ECOS': + elif configuration.solver_name == 'ECOS': CPGINFOTYPEDEF += ' c_int status; // Solver status\n' CPGINFOTYPEDEF += ' c_float pri_res; // Primal residual\n' CPGINFOTYPEDEF += ' c_float dua_res; // Dual residual\n' @@ -1660,7 +1621,7 @@ def replace_html_data(text, info_opt, info_usr, info_can): CPGRESULTTYPEDEF = '\n// Struct type with user-defined objective value and solution as fields\n' CPGRESULTTYPEDEF += 'typedef struct {\n' CPGRESULTTYPEDEF += ' CPG_Prim_t *prim; // Primal solution\n' - if len(info_usr[C.D_NAME_TO_INIT]) > 0: + if len(dual_variable_info.name_to_init) > 0: CPGRESULTTYPEDEF += ' CPG_Dual_t *dual; // Dual solution\n' CPGRESULTTYPEDEF += ' CPG_Info_t *info; // Solver information\n' CPGRESULTTYPEDEF += '} CPG_Result_t;\n' @@ -1668,27 +1629,27 @@ def replace_html_data(text, info_opt, info_usr, info_can): # update declarations CPGUPDATEDECLARATIONS = '\n// Update user-defined parameter values\n' - for name, size in info_usr[C.P_NAME_TO_SIZE].items(): + for name, size in parameter_info.name_to_size_usp.items(): if size == 1: - CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(c_float value);\n' % (info_opt[C.PREFIX], name) + CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(c_float value);\n' % (configuration.prefix, name) else: - CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(c_int idx, c_float value);\n' % (info_opt[C.PREFIX], name) + CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(c_int idx, c_float value);\n' % (configuration.prefix, name) text = text.replace('$CPGUPDATEDECLARATIONS', CPGUPDATEDECLARATIONS) # solve declarations CPGSOLVEDECLARATIONS = '\n// Solve via canonicalization, canonical solve, retrieval\n' - CPGSOLVEDECLARATIONS += 'void %scpg_solve();\n' % info_opt[C.PREFIX] + CPGSOLVEDECLARATIONS += 'void %scpg_solve();\n' % configuration.prefix text = text.replace('$CPGSOLVEDECLARATIONS', CPGSOLVEDECLARATIONS) # settings declarations CPGSETTINGSDECLARATIONS = '\n// Update solver settings\n' - CPGSETTINGSDECLARATIONS += 'void %scpg_set_solver_default_settings();\n' % info_opt[C.PREFIX] + CPGSETTINGSDECLARATIONS += 'void %scpg_set_solver_default_settings();\n' % configuration.prefix CPGSETTINGSDECLARATIONS += 'void %scpg_set_solver_<setting_name>' \ - '(<setting_type> <setting_name>_new);\n' % info_opt[C.PREFIX] + '(<setting_type> <setting_name>_new);\n' % configuration.prefix CPGSETTINGSDECLARATIONS += '...\n' text = text.replace('$CPGSETTINGSDECLARATIONS', CPGSETTINGSDECLARATIONS) # settings list - CPGSETTINGSLIST = ', '.join([('%s' % s) for s in info_can[C.SETTINGS_LIST]]) + CPGSETTINGSLIST = ', '.join([('%s' % s) for s in solver_interface.settings_names_enabled]) return text.replace('$CPGSETTINGSLIST', CPGSETTINGSLIST) From 56fcfeab83179731b42491fb2f72e531e1c9cff3 Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Tue, 5 Sep 2023 16:45:35 -0500 Subject: [PATCH 03/14] Solver includes to solver classes --- cvxpygen/solvers.py | 9 +++++++++ cvxpygen/utils.py | 24 +++++------------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 20e0756..5ce6937 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -154,6 +154,9 @@ class OSQPInterface(SolverInterface): canon_p_ids_constr_vec = ['l', 'u'] sign_constr_vec = -1 + # header files + header_files = ['osqp.h', 'types.h', 'workspace.h'] + # solver settings settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', 'alpha', 'scaled_termination', 'check_termination', 'warm_start', @@ -261,6 +264,9 @@ class SCSInterface(SolverInterface): canon_p_ids_constr_vec = ['b'] sign_constr_vec = 1 + # header files + header_files = ['scs.h'] + # solver settings settings_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', 'eps_rel', @@ -361,6 +367,9 @@ class ECOSInterface(SolverInterface): canon_p_ids_constr_vec = ['b', 'h'] sign_constr_vec = 1 + # header files + header_files = ['ecos.h'] + # solver settings settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', 'reltol_inacc', 'maxit'] diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index ffc8c45..94981e5 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -295,8 +295,6 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par write_description(f, 'c', 'Variable definitions') f.write('#include "cpg_workspace.h"\n') - if configuration.solver_name == 'OSQP': - f.write('#include "workspace.h"\n') if configuration.unroll: f.write('\n// User-defined parameters\n') @@ -551,15 +549,11 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa """ write_description(f, 'c', 'Type definitions and variable declarations') - if configuration.solver_name == 'OSQP': - f.write('#include "types.h"\n\n') - elif configuration.solver_name == 'SCS': - f.write('#include "scs.h"\n\n') - elif configuration.solver_name == 'ECOS': - f.write('#include "ecos.h"\n\n') + for header_file in solver_interface.header_files: + f.write('#include "%s"\n' % header_file) # definition safeguard - f.write('#ifndef CPG_TYPES_H\n') + f.write('\n#ifndef CPG_TYPES_H\n') f.write('# define CPG_TYPES_H\n\n') if configuration.solver_name == 'SCS': @@ -765,12 +759,7 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet write_description(f, 'c', 'Function definitions') f.write('#include "cpg_solve.h"\n') - f.write('#include "cpg_workspace.h"\n') - if configuration.solver_name == 'OSQP': - f.write('#include "workspace.h"\n') - f.write('#include "osqp.h"\n\n') - else: - f.write('\n') + f.write('#include "cpg_workspace.h"\n\n') if not configuration.unroll: f.write('static c_int i;\n') @@ -1049,10 +1038,7 @@ def write_solve_prot(f, configuration, variable_info, dual_variable_info, parame """ write_description(f, 'c', 'Function declarations') - if configuration.solver_name == 'OSQP': - f.write('#include "types.h"\n') - elif configuration.solver_name in ['SCS', 'ECOS']: - f.write('#include "cpg_workspace.h"\n') + f.write('#include "cpg_workspace.h"\n') f.write('\n// Update user-defined parameter values\n') for name, size in parameter_info.name_to_size_usp.items(): From 9e3c6250a0c88158aad2cdf9ff49cf21e1f69f69 Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Tue, 5 Sep 2023 20:38:39 -0500 Subject: [PATCH 04/14] Extra param structs for in memory pre conditioning --- cvxpygen/solvers.py | 9 ++++++ cvxpygen/utils.py | 78 +++++++++++++++++++++++---------------------- 2 files changed, 49 insertions(+), 38 deletions(-) diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 5ce6937..9d2c7a7 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -157,6 +157,9 @@ class OSQPInterface(SolverInterface): # header files header_files = ['osqp.h', 'types.h', 'workspace.h'] + # preconditioning of problem data happening in-memory + inmemory_preconditioning = False + # solver settings settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', 'alpha', 'scaled_termination', 'check_termination', 'warm_start', @@ -267,6 +270,9 @@ class SCSInterface(SolverInterface): # header files header_files = ['scs.h'] + # preconditioning of problem data happening in-memory + inmemory_preconditioning = False + # solver settings settings_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', 'eps_rel', @@ -370,6 +376,9 @@ class ECOSInterface(SolverInterface): # header files header_files = ['ecos.h'] + # preconditioning of problem data happening in-memory + inmemory_preconditioning = True + # solver settings settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', 'reltol_inacc', 'maxit'] diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index 94981e5..d159b72 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -255,10 +255,10 @@ def write_ecos_setup_update(f, canon_constants, prefix): if p == 0: Ax_str = Ap_str = Ai_str = b_str = '0' else: - Ax_str = '%sCanon_Params_ECOS.A->x' % prefix - Ap_str = '%sCanon_Params_ECOS.A->p' % prefix - Ai_str = '%sCanon_Params_ECOS.A->i' % prefix - b_str = '%sCanon_Params_ECOS.b' % prefix + Ax_str = '%sCanon_Params_conditioning.A->x' % prefix + Ap_str = '%sCanon_Params_conditioning.A->p' % prefix + Ai_str = '%sCanon_Params_conditioning.A->i' % prefix + b_str = '%sCanon_Params_conditioning.b' % prefix if n_cones == 0: ecos_q_str = '0' @@ -267,24 +267,24 @@ def write_ecos_setup_update(f, canon_constants, prefix): f.write(' if (!initialized) {\n') f.write(' %secos_workspace = ECOS_setup(%d, %d, %d, %d, %d, %s, %d, ' - '%sCanon_Params_ECOS.G->x, %sCanon_Params_ECOS.G->p, %sCanon_Params_ECOS.G->i, ' - '%s, %s, %s, %sCanon_Params_ECOS.c, %sCanon_Params_ECOS.h, %s);\n' % + '%sCanon_Params_conditioning.G->x, %sCanon_Params_conditioning.G->p, %sCanon_Params_conditioning.G->i, ' + '%s, %s, %s, %sCanon_Params_conditioning.c, %sCanon_Params_conditioning.h, %s);\n' % (prefix, n, m, p, ell, n_cones, ecos_q_str, e, prefix, prefix, prefix, Ax_str, Ap_str, Ai_str, prefix, prefix, b_str)) f.write(' initialized = 1;\n') f.write(' } else {\n') f.write(' if (%sCanon_Outdated.G || %sCanon_Outdated.A || %sCanon_Outdated.b) {\n' % (prefix, prefix, prefix)) - f.write(' ECOS_updateData(%secos_workspace, %sCanon_Params_ECOS.G->x, %s, %sCanon_Params_ECOS.c, ' - '%sCanon_Params_ECOS.h, %s);\n' % (prefix, prefix, Ax_str, prefix, prefix, b_str)) + f.write(' ECOS_updateData(%secos_workspace, %sCanon_Params_conditioning.G->x, %s, %sCanon_Params_conditioning.c, ' + '%sCanon_Params_conditioning.h, %s);\n' % (prefix, prefix, Ax_str, prefix, prefix, b_str)) f.write(' } else {\n') f.write(' if (%sCanon_Outdated.h) {\n' % prefix) f.write(' for (i=0; i<%d; i++){\n' % m) - f.write(' ecos_updateDataEntry_h(%secos_workspace, i, %sCanon_Params_ECOS.h[i]);\n' % (prefix, prefix)) + f.write(' ecos_updateDataEntry_h(%secos_workspace, i, %sCanon_Params_conditioning.h[i]);\n' % (prefix, prefix)) f.write(' }\n') f.write(' }\n') f.write(' if (%sCanon_Outdated.c) {\n' % prefix) f.write(' for (i=0; i<%d; i++){\n' % n) - f.write(' ecos_updateDataEntry_c(%secos_workspace, i, %sCanon_Params_ECOS.c[i]);\n' % (prefix, prefix)) + f.write(' ecos_updateDataEntry_c(%secos_workspace, i, %sCanon_Params_conditioning.c[i]);\n' % (prefix, prefix)) f.write(' }\n') f.write(' }\n') f.write(' }\n') @@ -332,8 +332,8 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par canon_casts.append('') else: write_param_def(f, replace_inf(p), p_id, configuration.prefix, '') - if configuration.solver_name == 'ECOS': - write_param_def(f, replace_inf(p), p_id, configuration.prefix, '_ECOS') + if solver_interface.inmemory_preconditioning: + write_param_def(f, replace_inf(p), p_id, configuration.prefix, '_conditioning') if p_id.isupper(): canon_casts.append('') else: @@ -342,7 +342,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par f.write('// Struct containing canonical parameters\n') struct_values = [] - struct_values_ECOS = [] + struct_values_conditioning = [] for i, p_id in enumerate(p_ids): p = parameter_canon.p[p_id] if type(p) == dict: @@ -351,21 +351,21 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par length = len(p) if length == 0: struct_values.append('0') - if configuration.solver_name == 'ECOS': - struct_values_ECOS.append('0') + if solver_interface.inmemory_preconditioning: + struct_values_conditioning.append('0') elif p_id=='d': struct_values.append('%.20f' % p) - if configuration.solver_name == 'ECOS': - struct_values_ECOS.append('%.20f' % p) + if solver_interface.inmemory_preconditioning: + struct_values_conditioning.append('%.20f' % p) else: struct_values.append('&%scanon_%s' % (configuration.prefix, p_id)) - if configuration.solver_name == 'ECOS': - struct_values_ECOS.append('&%scanon_%s_ECOS' % (configuration.prefix, p_id)) + if solver_interface.inmemory_preconditioning: + struct_values_conditioning.append('&%scanon_%s_conditioning' % (configuration.prefix, p_id)) write_struct_def(f, p_ids, canon_casts, struct_values, '%sCanon_Params' % configuration.prefix, 'Canon_Params_t') f.write('\n') - if configuration.solver_name == 'ECOS': - write_struct_def(f, p_ids, canon_casts, struct_values_ECOS, '%sCanon_Params_ECOS' % configuration.prefix, + if solver_interface.inmemory_preconditioning: + write_struct_def(f, p_ids, canon_casts, struct_values_conditioning, '%sCanon_Params_conditioning' % configuration.prefix, 'Canon_Params_t') f.write('\n') @@ -676,13 +676,13 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa for p_id, p in parameter_canon.p.items(): if p_id != 'd': write_param_prot(f, p, p_id, configuration.prefix, '') - if configuration.solver_name == 'ECOS': - write_param_prot(f, p, p_id, configuration.prefix, '_ECOS') + if solver_interface.inmemory_preconditioning: + write_param_prot(f, p, p_id, configuration.prefix, '_conditioning') f.write('\n// Struct containing canonical parameters\n') write_struct_prot(f, '%sCanon_Params' % configuration.prefix, 'Canon_Params_t') - if configuration.solver_name == 'ECOS': - write_struct_prot(f, '%sCanon_Params_ECOS' % configuration.prefix, 'Canon_Params_t') + if solver_interface.inmemory_preconditioning: + write_struct_prot(f, '%sCanon_Params_conditioning' % configuration.prefix, 'Canon_Params_t') f.write('\n// Struct containing flags for outdated canonical parameters\n') f.write('extern Canon_Outdated_t %sCanon_Outdated;\n' % configuration.prefix) @@ -955,6 +955,21 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet f.write(' %scpg_canonicalize_%s();\n' % (configuration.prefix, p_id)) f.write(' }\n') + if solver_interface.inmemory_preconditioning: + for p_id, size in parameter_canon.p_id_to_size.items(): + if size == 1: + f.write(' %sCanon_Params_conditioning.%s = %sCanon_Params.%s;\n' + % (configuration.prefix, p_id, configuration.prefix, p_id)) + elif size > 1: + f.write(' for (i=0; i<%d; i++){\n' % size) + if p_id.isupper(): + f.write(' %sCanon_Params_conditioning.%s->x[i] = %sCanon_Params.%s->x[i];\n' + % (configuration.prefix, p_id, configuration.prefix, p_id)) + else: + f.write(' %sCanon_Params_conditioning.%s[i] = %sCanon_Params.%s[i];\n' + % (configuration.prefix, p_id, configuration.prefix, p_id)) + f.write(' }\n') + if configuration.solver_name == 'OSQP': f.write(' // Solve with OSQP\n') f.write(' osqp_solve(&workspace);\n') @@ -977,19 +992,6 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet f.write(' scs_solve(%sScs_Work, &%sScs_Sol, &%sScs_Info, (initialized && %sScs_Stgs.warm_start));\n' % (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) elif configuration.solver_name == 'ECOS': - for p_id, size in parameter_canon.p_id_to_size.items(): - if size == 1: - f.write(' %sCanon_Params_ECOS.%s = %sCanon_Params.%s;\n' - % (configuration.prefix, p_id, configuration.prefix, p_id)) - elif size > 1: - f.write(' for (i=0; i<%d; i++){\n' % size) - if p_id.isupper(): - f.write(' %sCanon_Params_ECOS.%s->x[i] = %sCanon_Params.%s->x[i];\n' - % (configuration.prefix, p_id, configuration.prefix, p_id)) - else: - f.write(' %sCanon_Params_ECOS.%s[i] = %sCanon_Params.%s[i];\n' - % (configuration.prefix, p_id, configuration.prefix, p_id)) - f.write(' }\n') f.write(' // Initialize / update ECOS workspace and settings\n') write_ecos_setup_update(f, solver_interface.canon_constants, configuration.prefix) for name in solver_interface.settings_names_to_type.keys(): From 3e6012c194b13dedcec927090cffbb7521642bec Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Tue, 5 Sep 2023 21:00:03 -0500 Subject: [PATCH 05/14] Allocate solution if not done in solver code --- cvxpygen/solvers.py | 9 +++++++++ cvxpygen/utils.py | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 9d2c7a7..cde0b93 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -160,6 +160,9 @@ class OSQPInterface(SolverInterface): # preconditioning of problem data happening in-memory inmemory_preconditioning = False + # solution vectors statically allocated + sol_statically_allocated = True + # solver settings settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', 'alpha', 'scaled_termination', 'check_termination', 'warm_start', @@ -273,6 +276,9 @@ class SCSInterface(SolverInterface): # preconditioning of problem data happening in-memory inmemory_preconditioning = False + # solution vectors statically allocated + sol_statically_allocated = True + # solver settings settings_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', 'eps_rel', @@ -379,6 +385,9 @@ class ECOSInterface(SolverInterface): # preconditioning of problem data happening in-memory inmemory_preconditioning = True + # solution vectors statically allocated + sol_statically_allocated = False + # solver settings settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', 'reltol_inacc', 'maxit'] diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index d159b72..01a8ab3 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -377,14 +377,14 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par f.write('};\n\n') prim_cast = [] - if any(variable_info.name_to_sym) or configuration.solver_name == 'ECOS': + if any(variable_info.name_to_sym) or not solver_interface.sol_statically_allocated: f.write('// User-defined variables\n') for name, value in variable_info.name_to_init.items(): if is_mathematical_scalar(value): prim_cast.append('') else: prim_cast.append('(c_float *) ') - if variable_info.name_to_sym[name] or configuration.solver_name == 'ECOS': + if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: osqp_utils.write_vec(f, value.flatten(order='F'), configuration.prefix + name, 'c_float') f.write('\n') @@ -396,7 +396,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if is_mathematical_scalar(var): CPG_Prim_values.append('0') else: - if variable_info.name_to_sym[name] or configuration.solver_name == 'ECOS': + if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: CPG_Prim_values.append('&' + configuration.prefix + name) else: if configuration.solver_name == 'OSQP': @@ -407,14 +407,14 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if len(dual_variable_info.name_to_init) > 0: dual_cast = [] - if configuration.solver_name == 'ECOS': + if not solver_interface.sol_statically_allocated: f.write('\n// Dual variables associated with user-defined constraints\n') for name, value in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(value): dual_cast.append('') else: dual_cast.append('(c_float *) ') - if configuration.solver_name == 'ECOS': + if not solver_interface.sol_statically_allocated: osqp_utils.write_vec(f, value.flatten(order='F'), configuration.prefix + name, 'c_float') f.write('\n') @@ -687,15 +687,15 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('\n// Struct containing flags for outdated canonical parameters\n') f.write('extern Canon_Outdated_t %sCanon_Outdated;\n' % configuration.prefix) - if any(variable_info.name_to_sym.values()) or configuration.solver_name == 'ECOS': + if any(variable_info.name_to_sym.values()) or not solver_interface.sol_statically_allocated: f.write('\n// User-defined variables\n') for name, value in variable_info.name_to_init.items(): - if variable_info.name_to_sym[name] or configuration.solver_name == 'ECOS': + if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: if not is_mathematical_scalar(value): osqp_utils.write_vec_extern(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, 'c_float') - if configuration.solver_name == 'ECOS': + if not solver_interface.sol_statically_allocated: f.write('\n// Dual variables associated with user-defined constraints\n') for name, value in dual_variable_info.name_to_init.items(): if not is_mathematical_scalar(value): @@ -833,7 +833,7 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet for var_name, indices in variable_info.name_to_indices.items(): if len(indices) == 1: f.write(' %sCPG_Prim.%s = %s[%d];\n' % (configuration.prefix, var_name, prim_str, indices)) - elif variable_info.name_to_sym[var_name] or configuration.solver_name == 'ECOS': + elif variable_info.name_to_sym[var_name] or not solver_interface.sol_statically_allocated: for i, idx in enumerate(indices): f.write(' %sCPG_Prim.%s[%d] = %s[%d];\n' % (configuration.prefix, var_name, i, prim_str, idx)) f.write('}\n\n') @@ -844,7 +844,7 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet for var_name, (vector, indices) in dual_variable_info.name_to_indices.items(): if len(indices) == 1: f.write(' %sCPG_Dual.%s = %s%s[%d];\n' % (configuration.prefix, var_name, dual_str, vector, indices)) - elif configuration.solver_name == 'ECOS': + elif not solver_interface.sol_statically_allocated: for i, idx in enumerate(indices): f.write(' %sCPG_Dual.%s[%d] = %s%s[%d];\n' % (configuration.prefix, var_name, i, dual_str, vector, idx)) From 6dbd7f87e8f4035c1251ca44f89bb10273686c36 Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Tue, 5 Sep 2023 21:39:51 -0500 Subject: [PATCH 06/14] Eliminate initialized flag --- cvxpygen/utils.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index 01a8ab3..a381d6b 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -265,13 +265,12 @@ def write_ecos_setup_update(f, canon_constants, prefix): else: ecos_q_str = '(int *) &%secos_q' % prefix - f.write(' if (!initialized) {\n') + f.write(' if (!%secos_workspace) {\n' % prefix) f.write(' %secos_workspace = ECOS_setup(%d, %d, %d, %d, %d, %s, %d, ' '%sCanon_Params_conditioning.G->x, %sCanon_Params_conditioning.G->p, %sCanon_Params_conditioning.G->i, ' '%s, %s, %s, %sCanon_Params_conditioning.c, %sCanon_Params_conditioning.h, %s);\n' % (prefix, n, m, p, ell, n_cones, ecos_q_str, e, prefix, prefix, prefix, Ax_str, Ap_str, Ai_str, prefix, prefix, b_str)) - f.write(' initialized = 1;\n') f.write(' } else {\n') f.write(' if (%sCanon_Outdated.G || %sCanon_Outdated.A || %sCanon_Outdated.b) {\n' % (prefix, prefix, prefix)) f.write(' ECOS_updateData(%secos_workspace, %sCanon_Params_conditioning.G->x, %s, %sCanon_Params_conditioning.c, ' @@ -765,12 +764,9 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet f.write('static c_int i;\n') f.write('static c_int j;\n') - if configuration.unroll and configuration.solver_name == 'ECOS': + if configuration.unroll and solver_interface.inmemory_preconditioning: f.write('static c_int i;\n') - if configuration.solver_name in ['SCS', 'ECOS']: - f.write('static c_int initialized = 0;\n') - f.write('\n// Update user-defined parameters\n') if configuration.unroll: for user_p_name, Canon_outdated_names in parameter_canon.user_p_name_to_canon_outdated.items(): @@ -975,10 +971,9 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet f.write(' osqp_solve(&workspace);\n') elif configuration.solver_name == 'SCS': f.write(' // Solve with SCS\n') - f.write(' if (initialized == 0 || %sCanon_Outdated.A) {\n' % configuration.prefix) + f.write(' if (!%sScs_Work || %sCanon_Outdated.A) {\n' % (configuration.prefix, configuration.prefix)) f.write(' %sScs_Work = scs_init(&%sScs_D, &%sScs_K, &%sScs_Stgs);\n' % (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) - f.write(' initialized = 1;\n') f.write(' } else if (%sCanon_Outdated.b && %sCanon_Outdated.c) {\n' % (configuration.prefix, configuration.prefix)) f.write(' scs_update(%sScs_Work, %sCanon_Params.b, %sCanon_Params.c);\n' % (configuration.prefix, configuration.prefix, configuration.prefix)) @@ -989,8 +984,8 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet f.write(' scs_update(%sScs_Work, SCS_NULL, %sCanon_Params.c);\n' % (configuration.prefix, configuration.prefix)) f.write(' }\n') - f.write(' scs_solve(%sScs_Work, &%sScs_Sol, &%sScs_Info, (initialized && %sScs_Stgs.warm_start));\n' % - (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) + f.write(' scs_solve(%sScs_Work, &%sScs_Sol, &%sScs_Info, (%sScs_Work && %sScs_Stgs.warm_start));\n' % + (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) elif configuration.solver_name == 'ECOS': f.write(' // Initialize / update ECOS workspace and settings\n') write_ecos_setup_update(f, solver_interface.canon_constants, configuration.prefix) From e6ebd1716785591f6e452d42a61b2861a364eda6 Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:29:32 -0500 Subject: [PATCH 07/14] Handle status str vs int and refer to docu url --- cvxpygen/cpg.py | 4 ++-- cvxpygen/solvers.py | 18 +++++++++++++++ cvxpygen/utils.py | 55 +++++++++------------------------------------ 3 files changed, 30 insertions(+), 47 deletions(-) diff --git a/cvxpygen/cpg.py b/cvxpygen/cpg.py index ca8e98a..1e3a9af 100644 --- a/cvxpygen/cpg.py +++ b/cvxpygen/cpg.py @@ -330,7 +330,7 @@ def write_c_code(problem: cp.Problem, configuration: dict, variable_info: dict, utils.write_canon_cmake(f, configuration) # binding module prototypes with open(os.path.join(configuration.code_dir, 'cpp', 'include', 'cpg_module.hpp'), 'w') as f: - utils.write_module_prot(f, configuration, parameter_info, variable_info, dual_variable_info) + utils.write_module_prot(f, configuration, parameter_info, variable_info, dual_variable_info, solver_interface) # binding module definition with open(os.path.join(configuration.code_dir, 'cpp', 'src', 'cpg_module.cpp'), 'w') as f: utils.write_module_def(f, configuration, variable_info, dual_variable_info, parameter_info, solver_interface) @@ -342,7 +342,7 @@ def write_c_code(problem: cp.Problem, configuration: dict, variable_info: dict, f.write(setup_data) # custom CVXPY solve method with open(os.path.join(configuration.code_dir, 'cpg_solver.py'), 'w') as f: - utils.write_method(f, configuration, variable_info, dual_variable_info, parameter_info) + utils.write_method(f, configuration, variable_info, dual_variable_info, parameter_info, solver_interface) # serialize problem formulation with open(os.path.join(configuration.code_dir, 'problem.pickle'), 'wb') as f: pickle.dump(cp.Problem(problem.objective, problem.constraints), f) diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index cde0b93..f9059fe 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -163,6 +163,9 @@ class OSQPInterface(SolverInterface): # solution vectors statically allocated sol_statically_allocated = True + # solver status as integer vs. string + status_is_int = False + # solver settings settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', 'alpha', 'scaled_termination', 'check_termination', 'warm_start', @@ -173,6 +176,9 @@ class OSQPInterface(SolverInterface): False, False, False, False] settings_defaults = [] + # docu + docu = 'https://osqp.org/docs/codegen/python.html' + def __init__(self, data, p_prob, enable_settings): n_var = data['n_var'] n_eq = data['n_eq'] @@ -279,6 +285,9 @@ class SCSInterface(SolverInterface): # solution vectors statically allocated sol_statically_allocated = True + # solver status as integer vs. string + status_is_int = False + # solver settings settings_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', 'eps_rel', @@ -294,6 +303,9 @@ class SCSInterface(SolverInterface): '0', '0', '1', 'SCS_NULL', 'SCS_NULL'] + # docu + docu = 'https://www.cvxgrp.org/scs/api/c.html' + def __init__(self, data, p_prob, enable_settings): n_var = p_prob.x.size n_eq = data['A'].shape[0] @@ -388,6 +400,9 @@ class ECOSInterface(SolverInterface): # solution vectors statically allocated sol_statically_allocated = False + # solver status as integer vs. string + status_is_int = True + # solver settings settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', 'reltol_inacc', 'maxit'] @@ -395,6 +410,9 @@ class ECOSInterface(SolverInterface): settings_enabled = [True, True, True, True, True, True, True] settings_defaults = ['1e-8', '1e-8', '1e-8', '1e-4', '5e-5', '5e-5', '100'] + # docu + docu = 'https://github.com/embotech/ecos/wiki/Usage-from-C' + def __init__(self, data, p_prob, enable_settings): n_var = p_prob.x.size n_eq = p_prob.cone_dims.zero diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index a381d6b..36ad99f 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -437,12 +437,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par f.write('\n// Struct containing solver info\n') CPG_Info_fields = ['obj_val', 'iter', 'status', 'pri_res', 'dua_res'] - if configuration.solver_name in ['OSQP', 'SCS']: - CPG_Info_values = ['0', '0', '"unknown"', '0', '0'] - elif configuration.solver_name == 'ECOS': - CPG_Info_values = ['0', '0', '0', '0', '0'] - else: - raise ValueError("Problem class cannot be addressed by the OSQP or ECOS solver!") + CPG_Info_values = ['0', '0', ('0' if solver_interface.status_is_int else '"unknown"'), '0', '0'] info_cast = ['', '', '', '', ''] write_struct_def(f, CPG_Info_fields, info_cast, CPG_Info_values, '%sCPG_Info' % configuration.prefix, 'CPG_Info_t') @@ -631,10 +626,8 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('typedef struct {\n') f.write(' c_float obj_val; // Objective function value\n') f.write(' c_int iter; // Number of iterations\n') - if configuration.solver_name in ['OSQP', 'SCS']: - f.write(' char *status; // Solver status\n') - elif configuration.solver_name == 'ECOS': - f.write(' c_int status; // Solver status\n') + f.write(' %sstatus; // Solver status\n' % + ('c_int ' if solver_interface.status_is_int else 'char *')) f.write(' c_float pri_res; // Primal residual\n') f.write(' c_float dua_res; // Dual residual\n') f.write('} CPG_Info_t;\n\n') @@ -1302,7 +1295,7 @@ def write_module_def(f, configuration, variable_info, dual_variable_info, parame f.write('\n}\n') -def write_module_prot(f, configuration, parameter_info, variable_info, dual_variable_info): +def write_module_prot(f, configuration, parameter_info, variable_info, dual_variable_info, solver_interface): """ Write c++ file for pbind11 wrapper """ @@ -1352,10 +1345,7 @@ def write_module_prot(f, configuration, parameter_info, variable_info, dual_vari f.write('struct %sCPG_Info_cpp_t {\n' % configuration.prefix) f.write(' double obj_val;\n') f.write(' int iter;\n') - if configuration.solver_name in ['OSQP', 'SCS']: - f.write(' char* status;\n') - elif configuration.solver_name == 'ECOS': - f.write(' int status;\n') + f.write(' %s status;\n' % ('int' if solver_interface.status_is_int else 'char*')) f.write(' double pri_res;\n') f.write(' double dua_res;\n') f.write(' double time;\n') @@ -1387,7 +1377,7 @@ def replace_setup_data(text): return text.replace('%DATE', now.strftime("on %B %d, %Y at %H:%M:%S")) -def write_method(f, configuration, variable_info, dual_variable_info, parameter_info): +def write_method(f, configuration, variable_info, dual_variable_info, parameter_info, solver_interface): """ Write function to be registered as custom CVXPY solve method """ @@ -1400,21 +1390,6 @@ def write_method(f, configuration, variable_info, dual_variable_info, parameter_ f.write('from cvxpy.problems.problem import SolverStats\n') f.write('from %s import cpg_module\n\n\n' % configuration.code_dir.replace('/', '.').replace('\\', '.')) - if configuration.solver_name == 'ECOS': - indent = ' ' * 24 - f.write('status_int_to_string = {0: "Optimal solution found", \n' + - indent + '1: "Certificate of primal infeasibility found", \n' + - indent + '2: "Certificate of dual infeasibility found", \n' + - indent + '10: "Optimal solution found subject to reduced tolerances", \n' + - indent + '11: "Certificate of primal infeasibility found subject to reduced tolerances", \n' + - indent + '12: "Certificate of dual infeasibility found subject to reduced tolerances", \n' + - indent + '-1: "Maximum number of iterations reached", \n' + - indent + '-2: "Numerical problems (unreliable search direction)", \n' + - indent + '-3: "Numerical problems (slacks or multipliers outside cone)", \n' + - indent + '-4: "Interrupted by signal or CTRL-C", \n' + - indent + '-7: "Unknown problem in solver", \n' + - indent + '-99: "Unknown problem before solving"}\n\n\n') - f.write('def cpg_solve(prob, updated_params=None, **kwargs):\n\n') f.write(' # set flags for updated parameters\n') f.write(' upd = cpg_module.%scpg_updated()\n' % configuration.prefix) @@ -1497,10 +1472,8 @@ def write_method(f, configuration, variable_info, dual_variable_info, parameter_ f.write(' prob.constraints[%d].save_dual_value(np.array(res.cpg_dual.%s))\n' % (i, name)) f.write('\n # store additional solver information in problem object\n') - if configuration.solver_name in ['OSQP', 'SCS']: - f.write(' prob._status = res.cpg_info.status\n') - elif configuration.solver_name == 'ECOS': - f.write(' prob._status = status_int_to_string[res.cpg_info.status]\n') + f.write(' prob._status = %sres.cpg_info.status\n' % + (('"%%d (for description visit %s)" %% ' % solver_interface.docu) if solver_interface.status_is_int else '')) f.write(' if abs(res.cpg_info.obj_val) == 1e30:\n') f.write(' prob._value = np.sign(res.cpg_info.obj_val)*np.inf\n') f.write(' else:\n') @@ -1548,12 +1521,7 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa # solver name and docu text = text.replace('$CPGSOLVERNAME', configuration.solver_name) - if configuration.solver_name == 'OSQP': - text = text.replace('$CPGSOLVERDOCUURL', 'https://osqp.org/docs/codegen/python.html') - elif configuration.solver_name == 'SCS': - text = text.replace('$CPGSOLVERDOCUURL', 'https://www.cvxgrp.org/scs/api/c.html') - elif configuration.solver_name == 'ECOS': - text = text.replace('$CPGSOLVERDOCUURL', 'https://github.com/embotech/ecos/wiki/Usage-from-C') + text = text.replace('$CPGSOLVERDOCUURL', solver_interface.docu) # CMake prefix text = text.replace('$CPGCMAKELISTS', configuration.prefix+'cpg') @@ -1591,10 +1559,7 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa CPGINFOTYPEDEF += 'typedef struct {\n' CPGINFOTYPEDEF += ' c_float obj_val; // Objective function value\n' CPGINFOTYPEDEF += ' c_int iter; // Number of iterations\n' - if configuration.solver_name in ['OSQP', 'SCS']: - CPGINFOTYPEDEF += ' char *status; // Solver status\n' - elif configuration.solver_name == 'ECOS': - CPGINFOTYPEDEF += ' c_int status; // Solver status\n' + CPGINFOTYPEDEF += (' %sstatus; // Solver status\n' % ('c_int ' if solver_interface.status_is_int else 'char *')) CPGINFOTYPEDEF += ' c_float pri_res; // Primal residual\n' CPGINFOTYPEDEF += ' c_float dua_res; // Dual residual\n' CPGINFOTYPEDEF += '} CPG_Info_t;\n' From b48930e2a2eaaef23b65ab38540d8803f464d758 Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Wed, 6 Sep 2023 11:04:45 -0500 Subject: [PATCH 08/14] Introduce types cpg_int, cpg_float, cpg_csc --- cvxpygen/solvers.py | 21 +++- cvxpygen/utils.py | 270 +++++++++++++++++++++++++------------------- 2 files changed, 171 insertions(+), 120 deletions(-) diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index f9059fe..7ac01cb 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -166,12 +166,15 @@ class OSQPInterface(SolverInterface): # solver status as integer vs. string status_is_int = False + # float and integer types + numeric_types = {'float': 'c_float', 'int': 'c_int'} + # solver settings settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', 'alpha', 'scaled_termination', 'check_termination', 'warm_start', 'verbose', 'polish', 'polish_refine_iter', 'delta'] - settings_types = ['c_float', 'c_int', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', - 'c_int', 'c_int', 'c_int', 'c_int', 'c_int', 'c_int', 'c_float'] + settings_types = ['cpg_float', 'cpg_int', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', + 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_float'] settings_enabled = [True, True, True, True, True, True, True, True, True, True, False, False, False, False] settings_defaults = [] @@ -288,15 +291,18 @@ class SCSInterface(SolverInterface): # solver status as integer vs. string status_is_int = False + # float and integer types + numeric_types = {'float': 'scs_float', 'int': 'scs_int'} + # solver settings settings_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', 'eps_rel', 'eps_infeas', 'alpha', 'time_limit_secs', 'verbose', 'warm_start', 'acceleration_lookback', 'acceleration_interval', 'write_data_filename', 'log_csv_filename'] - settings_types = ['c_int', 'c_float', 'c_int', 'c_float', 'c_int', 'c_float', 'c_float', - 'c_float', 'c_float', - 'c_float', 'c_int', 'c_int', 'c_int', 'c_int', 'const char*', 'const char*'] + settings_types = ['cpg_int', 'cpg_float', 'cpg_int', 'cpg_float', 'cpg_int', 'cpg_float', 'cpg_float', + 'cpg_float', 'cpg_float', + 'cpg_float', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_int', 'const char*', 'const char*'] settings_enabled = [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True] settings_defaults = ['1', '0.1', '1', '1e-6', '1e5', '1e-4', '1e-4', '1e-7', '1.5', '0', '0', @@ -403,10 +409,13 @@ class ECOSInterface(SolverInterface): # solver status as integer vs. string status_is_int = True + # float and integer types + numeric_types = {'float': 'double', 'int': 'int'} + # solver settings settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', 'reltol_inacc', 'maxit'] - settings_types = ['c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_float', 'c_int'] + settings_types = ['cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_int'] settings_enabled = [True, True, True, True, True, True, True] settings_defaults = ['1e-8', '1e-8', '1e-8', '1e-4', '5e-5', '5e-5', '100'] diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index 36ad99f..0570392 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -13,7 +13,77 @@ import numpy as np from datetime import datetime -from osqp.codegen import utils as osqp_utils + + +def write_vec_def(f, vec, name, typ): + """ + Write vector to file + """ + f.write('%s %s[%d] = {\n' % (typ, name, len(vec))) + + # Write vector components + for i in range(len(vec)): + if typ == 'cpg_float': + f.write('(cpg_float)%.20f,\n' % vec[i]) + else: + f.write('%i,\n' % vec[i]) + + f.write('};\n') + + +def write_vec_prot(f, vec, name, typ): + """ + Write vector to file + """ + f.write('extern %s %s[%d];\n' % (typ, name, len(vec))) + + +def write_mat_def(f, mat, name): + """ + Write sparse matrix (scipy compressed sparse column) to file + """ + write_vec_def(f, mat['i'], name + '_i', 'cpg_int') + write_vec_def(f, mat['p'], name + '_p', 'cpg_int') + write_vec_def(f, mat['x'], name + '_x', 'cpg_float') + + f.write('cpg_csc %s = {' % name) + f.write('%d, ' % mat['nzmax']) + f.write('%d, ' % mat['m']) + f.write('%d, ' % mat['n']) + f.write('%s_p, ' % name) + f.write('%s_i, ' % name) + f.write('%s_x, ' % name) + f.write('%d};\n' % mat['nz']) + + +def write_mat_prot(f, mat, name): + """ + Write sparse matrix (scipy compressed sparse column) to file + """ + f.write('extern cpg_csc %s;\n' % name) + + +def write_dense_mat_def(f, mat, name): + """ + Write dense matrix to file + """ + + f.write('cpg_float %s[%d] = {\n' % (name, mat.size)) + + # represent matrix as vector (Fortran style) + for j in range(mat.shape[1]): + for i in range(mat.shape[0]): + f.write('(cpg_float)%.20f,\n' % mat[i, j]) + + f.write('};\n') + + +def write_dense_mat_prot(f, mat, name): + """ + Write dense matrix to file + """ + + f.write("extern cpg_float cpg_%s[%d];\n" % (name, mat.size)) def write_description(f, file_type, content): @@ -65,7 +135,7 @@ def replace_inf(v): def csc_to_dict(m): """ - Convert scipy csc matrix to dict that can be passed to osqp_utils.write_mat() + Convert scipy csc matrix to dict that can be passed to write_mat_def() """ d = dict() @@ -169,52 +239,29 @@ def write_canonicalize(f, canon_name, s, mapping, prefix): def write_param_def(f, param, name, prefix, suffix): """ - Use osqp.codegen.utils for writing vectors and matrices + Write vectors and matrices """ if not param_is_empty(param): if name.isupper(): - osqp_utils.write_mat(f, param, '%scanon_%s%s' % (prefix, name, suffix)) + write_mat_def(f, param, '%scanon_%s%s' % (prefix, name, suffix)) elif name == 'd': - f.write('c_float %scanon_d%s = %.20f;\n' % (prefix, suffix, param[0])) + f.write('cpg_float %scanon_d%s = %.20f;\n' % (prefix, suffix, param[0])) else: - osqp_utils.write_vec(f, param, '%scanon_%s%s' % (prefix, name, suffix), 'c_float') + write_vec_def(f, param, '%scanon_%s%s' % (prefix, name, suffix), 'cpg_float') f.write('\n') def write_param_prot(f, param, name, prefix, suffix): """ - Use osqp.codegen.utils for writing vectors and matrices + Write vectors and matrices """ if not param_is_empty(param): if name.isupper(): - osqp_utils.write_mat_extern(f, param, '%scanon_%s%s' % (prefix, name, suffix)) + write_mat_prot(f, param, '%scanon_%s%s' % (prefix, name, suffix)) elif name == 'd': - f.write('extern c_float %scanon_d%s;\n' % (prefix, suffix)) + f.write('extern cpg_float %scanon_d%s;\n' % (prefix, suffix)) else: - osqp_utils.write_vec_extern(f, param, '%scanon_%s%s' % (prefix, name, suffix), 'c_float') - - -def write_dense_mat_def(f, mat, name): - """ - Write dense matrix to file - """ - - f.write('c_float %s[%d] = {\n' % (name, mat.size)) - - # represent matrix as vector (Fortran style) - for j in range(mat.shape[1]): - for i in range(mat.shape[0]): - f.write('(c_float)%.20f,\n' % mat[i, j]) - - f.write('};\n') - - -def write_dense_mat_prot(f, mat, name): - """ - Write dense matrix to file - """ - - f.write("extern c_float cpg_%s[%d];\n" % (name, mat.size)) + write_vec_prot(f, param, '%scanon_%s%s' % (prefix, name, suffix), 'cpg_float') def write_struct_def(f, fields, casts, values, name, typ): @@ -306,20 +353,20 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par user_casts.append('') user_values.append('%.20f' % value) else: - osqp_utils.write_vec(f, value, configuration.prefix + 'cpg_' + name, 'c_float') + write_vec_def(f, value, configuration.prefix + 'cpg_' + name, 'cpg_float') f.write('\n') - user_casts.append('(c_float *) ') + user_casts.append('(cpg_float *) ') user_values.append('&' + configuration.prefix + 'cpg_' + name) f.write('// Struct containing all user-defined parameters\n') write_struct_def(f, names, user_casts, user_values, '%sCPG_Params' % configuration.prefix, 'CPG_Params_t') f.write('\n') else: f.write('\n// Vector containing flattened user-defined parameters\n') - osqp_utils.write_vec(f, parameter_info.flat_usp, '%scpg_params_vec' % configuration.prefix, 'c_float') + write_vec_def(f, parameter_info.flat_usp, '%scpg_params_vec' % configuration.prefix, 'cpg_float') f.write('\n// Sparse mappings from user-defined to canonical parameters\n') for p_id, mapping in parameter_canon.p_id_to_mapping.items(): if parameter_canon.p_id_to_changes[p_id]: - osqp_utils.write_mat(f, csc_to_dict(mapping), '%scanon_%s_map' % (configuration.prefix, p_id)) + write_mat_def(f, csc_to_dict(mapping), '%scanon_%s_map' % (configuration.prefix, p_id)) f.write('\n') p_ids = list(parameter_canon.p.keys()) @@ -336,7 +383,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if p_id.isupper(): canon_casts.append('') else: - canon_casts.append('(c_float *) ') + canon_casts.append('(cpg_float *) ') f.write('// Struct containing canonical parameters\n') @@ -382,9 +429,9 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if is_mathematical_scalar(value): prim_cast.append('') else: - prim_cast.append('(c_float *) ') + prim_cast.append('(cpg_float *) ') if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: - osqp_utils.write_vec(f, value.flatten(order='F'), configuration.prefix + name, 'c_float') + write_vec_def(f, value.flatten(order='F'), configuration.prefix + name, 'cpg_float') f.write('\n') f.write('// Struct containing primal solution\n') @@ -412,9 +459,9 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if is_mathematical_scalar(value): dual_cast.append('') else: - dual_cast.append('(c_float *) ') + dual_cast.append('(cpg_float *) ') if not solver_interface.sol_statically_allocated: - osqp_utils.write_vec(f, value.flatten(order='F'), configuration.prefix + name, 'c_float') + write_vec_def(f, value.flatten(order='F'), configuration.prefix + name, 'cpg_float') f.write('\n') f.write('// Struct containing dual solution\n') @@ -458,7 +505,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par f.write('\n// SCS matrix A\n') scs_A_fiels = ['x', 'i', 'p', 'm', 'n'] - scs_A_casts = ['(c_float *) ', '(c_int *) ', '(c_int *) ', '', ''] + scs_A_casts = ['(cpg_float *) ', '(cpg_int *) ', '(cpg_int *) ', '', ''] scs_A_values = ['&%scanon_A_x' % configuration.prefix, '&%scanon_A_i' % configuration.prefix, '&%scanon_A_p' % configuration.prefix, str(solver_interface.canon_constants['m']), str(solver_interface.canon_constants['n'])] @@ -466,7 +513,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par f.write('\n// Struct containing SCS data\n') scs_d_fiels = ['m', 'n', 'A', 'P', 'b', 'c'] - scs_d_casts = ['', '', '', '', '(c_float *) ', '(c_float *) '] + scs_d_casts = ['', '', '', '', '(cpg_float *) ', '(cpg_float *) '] scs_d_values = [str(solver_interface.canon_constants['m']), str(solver_interface.canon_constants['n']), '&%sScs_A' % configuration.prefix, 'SCS_NULL', '&%scanon_b' % configuration.prefix, '&%scanon_c' % configuration.prefix] @@ -474,15 +521,15 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if solver_interface.canon_constants['qsize'] > 0: f.write('\n// SCS array of SOC dimensions\n') - osqp_utils.write_vec(f, solver_interface.canon_constants['q'], '%sscs_q' % configuration.prefix, 'c_int') + write_vec_def(f, solver_interface.canon_constants['q'], '%sscs_q' % configuration.prefix, 'cpg_int') k_field_q_str = '&%sscs_q' % configuration.prefix else: k_field_q_str = 'SCS_NULL' f.write('\n// Struct containing SCS cone data\n') scs_k_fields = ['z', 'l', 'bu', 'bl', 'bsize', 'q', 'qsize', 's', 'ssize', 'ep', 'ed', 'p', 'psize'] - scs_k_casts = ['', '', '(c_float *) ', '(c_float *) ', '', '(c_int *) ', '', '(c_int *) ', '', '', '', - '(c_float *) ', ''] + scs_k_casts = ['', '', '(cpg_float *) ', '(cpg_float *) ', '', '(cpg_int *) ', '', '(cpg_int *) ', '', '', '', + '(cpg_float *) ', ''] scs_k_values = [str(solver_interface.canon_constants['z']), str(solver_interface.canon_constants['l']), 'SCS_NULL', 'SCS_NULL', '0', k_field_q_str, str(solver_interface.canon_constants['qsize']), 'SCS_NULL', '0', '0', '0', 'SCS_NULL', '0'] write_struct_def(f, scs_k_fields, scs_k_casts, scs_k_values, '%sScs_K' % configuration.prefix, 'ScsCone') @@ -495,13 +542,13 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par % configuration.prefix, 'ScsSettings') f.write('\n// SCS solution\n') - osqp_utils.write_vec(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, 'c_float') - osqp_utils.write_vec(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_y' % configuration.prefix, 'c_float') - osqp_utils.write_vec(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_s' % configuration.prefix, 'c_float') + write_vec_def(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, 'cpg_float') + write_vec_def(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_y' % configuration.prefix, 'cpg_float') + write_vec_def(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_s' % configuration.prefix, 'cpg_float') f.write('\n// Struct containing SCS solution\n') scs_sol_fields = ['x', 'y', 's'] - scs_sol_casts = ['(c_float *) ', '(c_float *) ', '(c_float *) '] + scs_sol_casts = ['(cpg_float *) ', '(cpg_float *) ', '(cpg_float *) '] scs_sol_values = ['&%sscs_x' % configuration.prefix, '&%sscs_y' % configuration.prefix, '&%sscs_s' % configuration.prefix] write_struct_def(f, scs_sol_fields, scs_sol_casts, scs_sol_values, '%sScs_Sol' @@ -530,11 +577,11 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par f.write('};\n') if solver_interface.canon_constants['n_cones'] > 0: f.write('\n// ECOS array of SOC dimensions\n') - osqp_utils.write_vec(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'c_int') + write_vec_def(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'cpg_int') f.write('\n// ECOS workspace\n') f.write('pwork* %secos_workspace = 0;\n' % configuration.prefix) f.write('\n// ECOS exit flag\n') - f.write('c_int %secos_flag = -99;\n' % configuration.prefix) + f.write('cpg_int %secos_flag = -99;\n' % configuration.prefix) def write_workspace_prot(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): @@ -550,25 +597,20 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('\n#ifndef CPG_TYPES_H\n') f.write('# define CPG_TYPES_H\n\n') - if configuration.solver_name == 'SCS': - f.write('typedef scs_float c_float;\n') - f.write('typedef scs_int c_int;\n\n') - elif configuration.solver_name == 'ECOS': - f.write('typedef double c_float;\n') - f.write('typedef int c_int;\n\n') + f.write('typedef %s cpg_float;\n' % solver_interface.numeric_types['float']) + f.write('typedef %s cpg_int;\n\n' % solver_interface.numeric_types['int']) # struct definitions - if configuration.solver_name in ['SCS', 'ECOS']: - f.write('// Compressed sparse column (csc) matrix\n') - f.write('typedef struct {\n') - f.write(' c_int nzmax;\n') - f.write(' c_int n;\n') - f.write(' c_int m;\n') - f.write(' c_int *p;\n') - f.write(' c_int *i;\n') - f.write(' c_float *x;\n') - f.write(' c_int nz;\n') - f.write('} csc;\n\n') + f.write('// Compressed sparse column matrix\n') + f.write('typedef struct {\n') + f.write(' cpg_int nzmax;\n') + f.write(' cpg_int n;\n') + f.write(' cpg_int m;\n') + f.write(' cpg_int *p;\n') + f.write(' cpg_int *i;\n') + f.write(' cpg_float *x;\n') + f.write(' cpg_int nz;\n') + f.write('} cpg_csc;\n\n') if configuration.unroll: f.write('// User-defined parameters\n') @@ -579,20 +621,20 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - f.write(' c_float %s // Your parameter %s\n' % ((s+name+';').ljust(9), name)) + f.write(' cpg_float %s // Your parameter %s\n' % ((s+name+';').ljust(9), name)) f.write('} CPG_Params_t;\n\n') f.write('// Canonical parameters\n') f.write('typedef struct {\n') for p_id in parameter_canon.p.keys(): if p_id.isupper(): - f.write(' csc *%s // Canonical parameter %s\n' % ((p_id+';').ljust(8), p_id)) + f.write(' cpg_csc *%s // Canonical parameter %s\n' % ((p_id+';').ljust(8), p_id)) else: if p_id == 'd': s = '' else: s = '*' - f.write(' c_float %s // Canonical parameter %s\n' % ((s+p_id+';').ljust(9), p_id)) + f.write(' cpg_float %s // Canonical parameter %s\n' % ((s+p_id+';').ljust(9), p_id)) f.write('} Canon_Params_t;\n\n') f.write('// Flags indicating outdated canonical parameters\n') @@ -608,7 +650,7 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - f.write(' c_float %s // Your variable %s\n' % ((s + name + ';').ljust(9), name)) + f.write(' cpg_float %s // Your variable %s\n' % ((s + name + ';').ljust(9), name)) f.write('} CPG_Prim_t;\n\n') if len(dual_variable_info.name_to_init) > 0: @@ -619,17 +661,17 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - f.write(' c_float %s // Your dual variable for constraint %s\n' % ((s + name + ';').ljust(9), name)) + f.write(' cpg_float %s // Your dual variable for constraint %s\n' % ((s + name + ';').ljust(9), name)) f.write('} CPG_Dual_t;\n\n') f.write('// Solver information\n') f.write('typedef struct {\n') - f.write(' c_float obj_val; // Objective function value\n') - f.write(' c_int iter; // Number of iterations\n') + f.write(' cpg_float obj_val; // Objective function value\n') + f.write(' cpg_int iter; // Number of iterations\n') f.write(' %sstatus; // Solver status\n' % - ('c_int ' if solver_interface.status_is_int else 'char *')) - f.write(' c_float pri_res; // Primal residual\n') - f.write(' c_float dua_res; // Dual residual\n') + ('cpg_int ' if solver_interface.status_is_int else 'char *')) + f.write(' cpg_float pri_res; // Primal residual\n') + f.write(' cpg_float dua_res; // Dual residual\n') f.write('} CPG_Info_t;\n\n') f.write('// Solution and solver information\n') @@ -653,16 +695,16 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('\n// User-defined parameters\n') for name, value in parameter_info.writable.items(): if not is_mathematical_scalar(value): - osqp_utils.write_vec_extern(f, value, configuration.prefix+'cpg_'+name, 'c_float') + write_vec_prot(f, value, configuration.prefix+'cpg_'+name, 'cpg_float') f.write('\n// Struct containing all user-defined parameters\n') write_struct_prot(f, '%sCPG_Params' % configuration.prefix, 'CPG_Params_t') else: f.write('\n// Vector containing flattened user-defined parameters\n') - osqp_utils.write_vec_extern(f, parameter_info.flat_usp, '%scpg_params_vec' % configuration.prefix, 'c_float') + write_vec_prot(f, parameter_info.flat_usp, '%scpg_params_vec' % configuration.prefix, 'cpg_float') f.write('\n// Sparse mappings from user-defined to canonical parameters\n') for p_id, mapping in parameter_canon.p_id_to_mapping.items(): if parameter_canon.p_id_to_changes[p_id]: - osqp_utils.write_mat_extern(f, csc_to_dict(mapping), '%scanon_%s_map' % (configuration.prefix, p_id)) + write_mat_prot(f, csc_to_dict(mapping), '%scanon_%s_map' % (configuration.prefix, p_id)) f.write('\n// Canonical parameters\n') for p_id, p in parameter_canon.p.items(): @@ -684,14 +726,14 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa for name, value in variable_info.name_to_init.items(): if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: if not is_mathematical_scalar(value): - osqp_utils.write_vec_extern(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, - 'c_float') + write_vec_prot(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, + 'cpg_float') if not solver_interface.sol_statically_allocated: f.write('\n// Dual variables associated with user-defined constraints\n') for name, value in dual_variable_info.name_to_init.items(): if not is_mathematical_scalar(value): - osqp_utils.write_vec_extern(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, 'c_float') + write_vec_prot(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, 'cpg_float') f.write('\n// Struct containing primal solution\n') write_struct_prot(f, '%sCPG_Prim' % configuration.prefix, 'CPG_Prim_t') @@ -713,18 +755,18 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa write_struct_prot(f, '%sScs_D' % configuration.prefix, 'ScsData') if solver_interface.canon_constants['qsize'] > 0: f.write('\n// SCS array of SOC dimensions\n') - osqp_utils.write_vec_extern(f, solver_interface.canon_constants['q'], '%sscs_q' % configuration.prefix, 'c_int') + write_vec_prot(f, solver_interface.canon_constants['q'], '%sscs_q' % configuration.prefix, 'cpg_int') f.write('\n// Struct containing SCS cone data\n') write_struct_prot(f, '%sScs_K' % configuration.prefix, 'ScsCone') f.write('\n// Struct containing SCS settings\n') write_struct_prot(f, '%sScs_Stgs' % configuration.prefix, 'ScsSettings') f.write('\n// SCS solution\n') - osqp_utils.write_vec_extern(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, - 'c_float') - osqp_utils.write_vec_extern(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_y' % configuration.prefix, - 'c_float') - osqp_utils.write_vec_extern(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_s' % configuration.prefix, - 'c_float') + write_vec_prot(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, + 'cpg_float') + write_vec_prot(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_y' % configuration.prefix, + 'cpg_float') + write_vec_prot(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_s' % configuration.prefix, + 'cpg_float') f.write('\n// Struct containing SCS solution\n') write_struct_prot(f, '%sScs_Sol' % configuration.prefix, 'ScsSolution') f.write('\n// Struct containing SCS information\n') @@ -737,11 +779,11 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa write_struct_prot(f, '%sCanon_Settings' % configuration.prefix, 'Canon_Settings_t') if solver_interface.canon_constants['n_cones'] > 0: f.write('\n// ECOS array of SOC dimensions\n') - osqp_utils.write_vec_extern(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'c_int') + write_vec_prot(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'cpg_int') f.write('\n// ECOS workspace\n') f.write('extern pwork* %secos_workspace;\n' % configuration.prefix) f.write('\n// ECOS exit flag\n') - f.write('extern c_int %secos_flag;\n' % configuration.prefix) + f.write('extern cpg_int %secos_flag;\n' % configuration.prefix) def write_solve_def(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): @@ -754,20 +796,20 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet f.write('#include "cpg_workspace.h"\n\n') if not configuration.unroll: - f.write('static c_int i;\n') - f.write('static c_int j;\n') + f.write('static cpg_int i;\n') + f.write('static cpg_int j;\n') if configuration.unroll and solver_interface.inmemory_preconditioning: - f.write('static c_int i;\n') + f.write('static cpg_int i;\n') f.write('\n// Update user-defined parameters\n') if configuration.unroll: for user_p_name, Canon_outdated_names in parameter_canon.user_p_name_to_canon_outdated.items(): if parameter_info.name_to_size_usp[user_p_name] == 1: - f.write('void %scpg_update_%s(c_float val){\n' % (configuration.prefix, user_p_name)) + f.write('void %scpg_update_%s(cpg_float val){\n' % (configuration.prefix, user_p_name)) f.write(' %sCPG_Params.%s = val;\n' % (configuration.prefix, user_p_name)) else: - f.write('void %scpg_update_%s(c_int idx, c_float val){\n' % (configuration.prefix, user_p_name)) + f.write('void %scpg_update_%s(cpg_int idx, cpg_float val){\n' % (configuration.prefix, user_p_name)) f.write(' %sCPG_Params.%s[idx] = val;\n' % (configuration.prefix, user_p_name)) for Canon_outdated_name in Canon_outdated_names: f.write(' %sCanon_Outdated.%s = 1;\n' % (configuration.prefix, Canon_outdated_name)) @@ -776,10 +818,10 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet for base_col, name in parameter_info.col_to_name_usp.items(): Canon_outdated_names = parameter_canon.user_p_name_to_canon_outdated[name] if parameter_info.name_to_size_usp[name] == 1: - f.write('void %scpg_update_%s(c_float val){\n' % (configuration.prefix, name)) + f.write('void %scpg_update_%s(cpg_float val){\n' % (configuration.prefix, name)) f.write(' %scpg_params_vec[%d] = val;\n' % (configuration.prefix, base_col)) else: - f.write('void %scpg_update_%s(c_int idx, c_float val){\n' % (configuration.prefix, name)) + f.write('void %scpg_update_%s(cpg_int idx, cpg_float val){\n' % (configuration.prefix, name)) f.write(' %scpg_params_vec[idx+%d] = val;\n' % (configuration.prefix, base_col)) for Canon_outdated_name in Canon_outdated_names: f.write(' %sCanon_Outdated.%s = 1;\n' % (configuration.prefix, Canon_outdated_name)) @@ -1033,9 +1075,9 @@ def write_solve_prot(f, configuration, variable_info, dual_variable_info, parame f.write('\n// Update user-defined parameter values\n') for name, size in parameter_info.name_to_size_usp.items(): if size == 1: - f.write('extern void %scpg_update_%s(c_float val);\n' % (configuration.prefix, name)) + f.write('extern void %scpg_update_%s(cpg_float val);\n' % (configuration.prefix, name)) else: - f.write('extern void %scpg_update_%s(c_int idx, c_float val);\n' % (configuration.prefix, name)) + f.write('extern void %scpg_update_%s(cpg_int idx, cpg_float val);\n' % (configuration.prefix, name)) f.write('\n// Map user-defined to canonical parameters\n') for p_id, changes in parameter_canon.p_id_to_changes.items(): @@ -1071,7 +1113,7 @@ def write_example_def(f, configuration, variable_info, dual_variable_info, param f.write('#include \n') f.write('#include "cpg_workspace.h"\n') f.write('#include "cpg_solve.h"\n\n') - f.write('static c_int i;\n\n') + f.write('static cpg_int i;\n\n') f.write('int main(int argc, char *argv[]){\n\n') @@ -1534,7 +1576,7 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - CPGPRIMTYPEDEF += (' c_float %s // Your variable %s\n' % ((s + name + ';').ljust(9), name)) + CPGPRIMTYPEDEF += (' cpg_float %s // Your variable %s\n' % ((s + name + ';').ljust(9), name)) CPGPRIMTYPEDEF += '} CPG_Prim_t;\n' text = text.replace('$CPGPRIMTYPEDEF', CPGPRIMTYPEDEF) @@ -1547,7 +1589,7 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - CPGDUALTYPEDEF += (' c_float %s // Your dual variable for constraint %s\n' + CPGDUALTYPEDEF += (' cpg_float %s // Your dual variable for constraint %s\n' % ((s + name + ';').ljust(9), name)) CPGDUALTYPEDEF += '} CPG_Dual_t;\n\n' else: @@ -1557,11 +1599,11 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa # type definition of CPG_Info_t CPGINFOTYPEDEF = '// Struct type with canonical solver information\n' CPGINFOTYPEDEF += 'typedef struct {\n' - CPGINFOTYPEDEF += ' c_float obj_val; // Objective function value\n' - CPGINFOTYPEDEF += ' c_int iter; // Number of iterations\n' - CPGINFOTYPEDEF += (' %sstatus; // Solver status\n' % ('c_int ' if solver_interface.status_is_int else 'char *')) - CPGINFOTYPEDEF += ' c_float pri_res; // Primal residual\n' - CPGINFOTYPEDEF += ' c_float dua_res; // Dual residual\n' + CPGINFOTYPEDEF += ' cpg_float obj_val; // Objective function value\n' + CPGINFOTYPEDEF += ' cpg_int iter; // Number of iterations\n' + CPGINFOTYPEDEF += (' %sstatus; // Solver status\n' % ('cpg_int ' if solver_interface.status_is_int else 'char *')) + CPGINFOTYPEDEF += ' cpg_float pri_res; // Primal residual\n' + CPGINFOTYPEDEF += ' cpg_float dua_res; // Dual residual\n' CPGINFOTYPEDEF += '} CPG_Info_t;\n' text = text.replace('$CPGINFOTYPEDEF', CPGINFOTYPEDEF) @@ -1579,9 +1621,9 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa CPGUPDATEDECLARATIONS = '\n// Update user-defined parameter values\n' for name, size in parameter_info.name_to_size_usp.items(): if size == 1: - CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(c_float value);\n' % (configuration.prefix, name) + CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(cpg_float value);\n' % (configuration.prefix, name) else: - CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(c_int idx, c_float value);\n' % (configuration.prefix, name) + CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(cpg_int idx, cpg_float value);\n' % (configuration.prefix, name) text = text.replace('$CPGUPDATEDECLARATIONS', CPGUPDATEDECLARATIONS) # solve declarations From 18a73923326af06d7f5d15edcb012160b63a463f Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Wed, 6 Sep 2023 12:53:27 -0500 Subject: [PATCH 09/14] Generalized settings update --- cvxpygen/solvers.py | 57 +++++++++++++++++++++++++------------------- cvxpygen/utils.py | 58 ++++++++++++++++++++------------------------- 2 files changed, 59 insertions(+), 56 deletions(-) diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 7ac01cb..ad5a9e0 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -60,17 +60,17 @@ def sign_constr_vec(self): @property @abstractmethod - def settings_names(self): + def stgs_names(self): pass @property @abstractmethod - def settings_types(self): + def stgs_types(self): pass @property @abstractmethod - def settings_defaults(self): + def stgs_defaults(self): pass @staticmethod @@ -82,10 +82,10 @@ def ret_dual_func_exists(dual_variable_info: DualVariableInfo) -> bool: return any([s == 1 for s in dual_variable_info.sizes]) def configure_settings(self) -> None: - for i, s in enumerate(self.settings_names): + for i, s in enumerate(self.stgs_names): if s in self.enable_settings: - self.settings_enabled[i] = True - for s in set(self.enable_settings)-set(self.settings_names): + self.stgs_enabled[i] = True + for s in set(self.enable_settings)-set(self.stgs_names): warnings.warn('Cannot enable setting %s for solver %s' % (s, self.solver_name)) def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> AffineMap: @@ -125,17 +125,17 @@ def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> A return affine_map @property - def settings_names_enabled(self): - return [name for name, enabled in zip(self.settings_names, self.settings_enabled) if enabled] + def stgs_names_enabled(self): + return [name for name, enabled in zip(self.stgs_names, self.stgs_enabled) if enabled] @property - def settings_names_to_type(self): - return {name: typ for name, typ, enabled in zip(self.settings_names, self.settings_types, self.settings_enabled) + def stgs_names_to_type(self): + return {name: typ for name, typ, enabled in zip(self.stgs_names, self.stgs_types, self.stgs_enabled) if enabled} @property - def settings_names_to_default(self): - return {name: typ for name, typ, enabled in zip(self.settings_names, self.settings_defaults, self.settings_enabled) + def stgs_names_to_default(self): + return {name: typ for name, typ, enabled in zip(self.stgs_names, self.stgs_defaults, self.stgs_enabled) if enabled} @staticmethod @@ -170,14 +170,17 @@ class OSQPInterface(SolverInterface): numeric_types = {'float': 'c_float', 'int': 'c_int'} # solver settings - settings_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', + stgs_statically_allocated = True + stgs_set_function = {'name': 'osqp_update_%s', 'ptr_name': 'workspace'} + stgs_reset_function = {'name': 'osqp_set_default_settings', 'ptr_name': 'settings'} + stgs_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', 'alpha', 'scaled_termination', 'check_termination', 'warm_start', 'verbose', 'polish', 'polish_refine_iter', 'delta'] - settings_types = ['cpg_float', 'cpg_int', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', + stgs_types = ['cpg_float', 'cpg_int', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_float'] - settings_enabled = [True, True, True, True, True, True, True, True, True, True, + stgs_enabled = [True, True, True, True, True, True, True, True, True, True, False, False, False, False] - settings_defaults = [] + stgs_defaults = [] # docu docu = 'https://osqp.org/docs/codegen/python.html' @@ -295,17 +298,20 @@ class SCSInterface(SolverInterface): numeric_types = {'float': 'scs_float', 'int': 'scs_int'} # solver settings - settings_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', + stgs_statically_allocated = False + stgs_set_function = None + stgs_reset_function = {'name': 'scs_set_default_settings', 'ptr_name': None} # set 'ptr_name' to None if stgs not statically allocated in solver code + stgs_names = ['normalize', 'scale', 'adaptive_scale', 'rho_x', 'max_iters', 'eps_abs', 'eps_rel', 'eps_infeas', 'alpha', 'time_limit_secs', 'verbose', 'warm_start', 'acceleration_lookback', 'acceleration_interval', 'write_data_filename', 'log_csv_filename'] - settings_types = ['cpg_int', 'cpg_float', 'cpg_int', 'cpg_float', 'cpg_int', 'cpg_float', 'cpg_float', + stgs_types = ['cpg_int', 'cpg_float', 'cpg_int', 'cpg_float', 'cpg_int', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_int', 'cpg_int', 'cpg_int', 'cpg_int', 'const char*', 'const char*'] - settings_enabled = [True, True, True, True, True, True, True, True, True, True, True, True, + stgs_enabled = [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True] - settings_defaults = ['1', '0.1', '1', '1e-6', '1e5', '1e-4', '1e-4', '1e-7', '1.5', '0', '0', + stgs_defaults = ['1', '0.1', '1', '1e-6', '1e5', '1e-4', '1e-4', '1e-7', '1.5', '0', '0', '0', '0', '1', 'SCS_NULL', 'SCS_NULL'] @@ -413,11 +419,14 @@ class ECOSInterface(SolverInterface): numeric_types = {'float': 'double', 'int': 'int'} # solver settings - settings_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', + stgs_statically_allocated = False + stgs_set_function = None + stgs_reset_function = None + stgs_names = ['feastol', 'abstol', 'reltol', 'feastol_inacc', 'abstol_inacc', 'reltol_inacc', 'maxit'] - settings_types = ['cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_int'] - settings_enabled = [True, True, True, True, True, True, True] - settings_defaults = ['1e-8', '1e-8', '1e-8', '1e-4', '5e-5', '5e-5', '100'] + stgs_types = ['cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_float', 'cpg_int'] + stgs_enabled = [True, True, True, True, True, True, True] + stgs_defaults = ['1e-8', '1e-8', '1e-8', '1e-4', '5e-5', '5e-5', '100'] # docu docu = 'https://github.com/embotech/ecos/wiki/Usage-from-C' diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index 0570392..edc12d8 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -535,11 +535,10 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par write_struct_def(f, scs_k_fields, scs_k_casts, scs_k_values, '%sScs_K' % configuration.prefix, 'ScsCone') f.write('\n// Struct containing SCS settings\n') - scs_stgs_fields = list(solver_interface.settings_names_to_default.keys()) + scs_stgs_fields = list(solver_interface.stgs_names_to_default.keys()) scs_stgs_casts = ['']*len(scs_stgs_fields) - scs_stgs_values = list(solver_interface.settings_names_to_default.values()) - write_struct_def(f, scs_stgs_fields, scs_stgs_casts, scs_stgs_values, '%sScs_Stgs' - % configuration.prefix, 'ScsSettings') + scs_stgs_values = list(solver_interface.stgs_names_to_default.values()) + write_struct_def(f, scs_stgs_fields, scs_stgs_casts, scs_stgs_values, configuration.prefix + 'Canon_Settings', 'ScsSettings') f.write('\n// SCS solution\n') write_vec_def(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, 'cpg_float') @@ -572,7 +571,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par f.write('\n// Struct containing solver settings\n') f.write('Canon_Settings_t %sCanon_Settings = {\n' % configuration.prefix) - for name, default in solver_interface.settings_names_to_default.items(): + for name, default in solver_interface.stgs_names_to_default.items(): f.write('.%s = %s,\n' % (name, default)) f.write('};\n') if solver_interface.canon_constants['n_cones'] > 0: @@ -685,7 +684,7 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa if configuration.solver_name == 'ECOS': f.write('// Solver settings\n') f.write('typedef struct {\n') - for name, typ in solver_interface.settings_names_to_type.items(): + for name, typ in solver_interface.stgs_names_to_type.items(): f.write(' %s%s;\n' % (typ.ljust(11), name)) f.write('} Canon_Settings_t;\n\n') @@ -759,7 +758,7 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('\n// Struct containing SCS cone data\n') write_struct_prot(f, '%sScs_K' % configuration.prefix, 'ScsCone') f.write('\n// Struct containing SCS settings\n') - write_struct_prot(f, '%sScs_Stgs' % configuration.prefix, 'ScsSettings') + write_struct_prot(f, configuration.prefix + 'Canon_Settings', 'ScsSettings') f.write('\n// SCS solution\n') write_vec_prot(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, 'cpg_float') @@ -776,7 +775,7 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa if configuration.solver_name == 'ECOS': f.write('\n// Struct containing solver settings\n') - write_struct_prot(f, '%sCanon_Settings' % configuration.prefix, 'Canon_Settings_t') + write_struct_prot(f, configuration.prefix + 'Canon_Settings', 'Canon_Settings_t') if solver_interface.canon_constants['n_cones'] > 0: f.write('\n// ECOS array of SOC dimensions\n') write_vec_prot(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'cpg_int') @@ -1007,7 +1006,7 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet elif configuration.solver_name == 'SCS': f.write(' // Solve with SCS\n') f.write(' if (!%sScs_Work || %sCanon_Outdated.A) {\n' % (configuration.prefix, configuration.prefix)) - f.write(' %sScs_Work = scs_init(&%sScs_D, &%sScs_K, &%sScs_Stgs);\n' % + f.write(' %sScs_Work = scs_init(&%sScs_D, &%sScs_K, &%sCanon_Settings);\n' % (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) f.write(' } else if (%sCanon_Outdated.b && %sCanon_Outdated.c) {\n' % (configuration.prefix, configuration.prefix)) f.write(' scs_update(%sScs_Work, %sCanon_Params.b, %sCanon_Params.c);\n' % @@ -1019,12 +1018,12 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet f.write(' scs_update(%sScs_Work, SCS_NULL, %sCanon_Params.c);\n' % (configuration.prefix, configuration.prefix)) f.write(' }\n') - f.write(' scs_solve(%sScs_Work, &%sScs_Sol, &%sScs_Info, (%sScs_Work && %sScs_Stgs.warm_start));\n' % + f.write(' scs_solve(%sScs_Work, &%sScs_Sol, &%sScs_Info, (%sScs_Work && %sCanon_Settings.warm_start));\n' % (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) elif configuration.solver_name == 'ECOS': f.write(' // Initialize / update ECOS workspace and settings\n') write_ecos_setup_update(f, solver_interface.canon_constants, configuration.prefix) - for name in solver_interface.settings_names_to_type.keys(): + for name in solver_interface.stgs_names_to_type.keys(): f.write(' %secos_workspace->stgs->%s = %sCanon_Settings.%s;\n' % (configuration.prefix, name, configuration.prefix, name)) f.write(' // Solve with ECOS\n') @@ -1045,21 +1044,21 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet f.write('// Update solver settings\n') f.write('void %scpg_set_solver_default_settings(){\n' % configuration.prefix) - if configuration.solver_name == 'OSQP': - f.write(' osqp_set_default_settings(&settings);\n') - elif configuration.solver_name == 'SCS': - f.write(' scs_set_default_settings(&%sScs_Stgs);\n' % configuration.prefix) - elif configuration.solver_name == 'ECOS': - for name, value in solver_interface.settings_names_to_default.items(): + if solver_interface.stgs_reset_function is not None: + f.write(' %s(&%s);\n' % ( + solver_interface.stgs_reset_function['name'], + solver_interface.stgs_reset_function['ptr_name'] if solver_interface.stgs_reset_function['ptr_name'] is not None else configuration.prefix + 'Canon_Settings')) + else: + for name, value in solver_interface.stgs_names_to_default.items(): f.write(' %sCanon_Settings.%s = %s;\n' % (configuration.prefix, name, value)) f.write('}\n') - for name, typ in solver_interface.settings_names_to_type.items(): + for name, typ in solver_interface.stgs_names_to_type.items(): f.write('\nvoid %scpg_set_solver_%s(%s %s_new){\n' % (configuration.prefix, name, typ, name)) - if configuration.solver_name == 'OSQP': - f.write(' osqp_update_%s(&workspace, %s_new);\n' % (name, name)) - elif configuration.solver_name == 'SCS': - f.write(' %sScs_Stgs.%s = %s_new;\n' % (configuration.prefix, name, name)) - elif configuration.solver_name == 'ECOS': + if solver_interface.stgs_set_function is not None: + f.write(' %s(&%s, %%s_new);\n' % ( + solver_interface.stgs_set_function['name'], + solver_interface.stgs_set_function['ptr_name']) % (name, name)) + else: f.write(' %sCanon_Settings.%s = %s_new;\n' % (configuration.prefix, name, name)) f.write('}\n') @@ -1100,7 +1099,7 @@ def write_solve_prot(f, configuration, variable_info, dual_variable_info, parame f.write('\n// Update solver settings\n') f.write('extern void %scpg_set_solver_default_settings();\n' % configuration.prefix) - for name, typ in solver_interface.settings_names_to_type.items(): + for name, typ in solver_interface.stgs_names_to_type.items(): f.write('extern void %scpg_set_solver_%s(%s %s_new);\n' % (configuration.prefix, name, typ, name)) @@ -1113,7 +1112,7 @@ def write_example_def(f, configuration, variable_info, dual_variable_info, param f.write('#include \n') f.write('#include "cpg_workspace.h"\n') f.write('#include "cpg_solve.h"\n\n') - f.write('static cpg_int i;\n\n') + f.write('static int i;\n\n') f.write('int main(int argc, char *argv[]){\n\n') @@ -1132,11 +1131,6 @@ def write_example_def(f, configuration, variable_info, dual_variable_info, param f.write(' // Print primal solution\n') - if configuration.solver_name == 'OSQP': - int_format_str = 'lld' - else: - int_format_str = 'd' - for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): f.write(' printf("%s = %%f\\n", %sCPG_Result.prim->%s);\n' % (name, configuration.prefix, name)) @@ -1331,7 +1325,7 @@ def write_module_def(f, configuration, variable_info, dual_variable_info, parame f.write(' m.def("solve", &%ssolve_cpp);\n\n' % configuration.prefix) f.write(' m.def("set_solver_default_settings", &%scpg_set_solver_default_settings);\n' % configuration.prefix) - for name in solver_interface.settings_names_to_type.keys(): + for name in solver_interface.stgs_names_to_type.keys(): f.write(' m.def("set_solver_%s", &%scpg_set_solver_%s);\n' % (name, configuration.prefix, name)) f.write('\n}\n') @@ -1640,6 +1634,6 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa text = text.replace('$CPGSETTINGSDECLARATIONS', CPGSETTINGSDECLARATIONS) # settings list - CPGSETTINGSLIST = ', '.join([('%s' % s) for s in solver_interface.settings_names_enabled]) + CPGSETTINGSLIST = ', '.join([('%s' % s) for s in solver_interface.stgs_names_enabled]) return text.replace('$CPGSETTINGSLIST', CPGSETTINGSLIST) From 278bfe574af3d4e50aa32e53ddd14f4f140dc9fd Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Wed, 6 Sep 2023 14:35:31 -0500 Subject: [PATCH 10/14] Introduce dataclass ResultPointerInfo --- cvxpygen/mappings.py | 11 ++++++ cvxpygen/solvers.py | 38 +++++++++++++++++++- cvxpygen/utils.py | 83 ++++++++++++++------------------------------ 3 files changed, 75 insertions(+), 57 deletions(-) diff --git a/cvxpygen/mappings.py b/cvxpygen/mappings.py index e6fb3a3..1d78813 100644 --- a/cvxpygen/mappings.py +++ b/cvxpygen/mappings.py @@ -76,3 +76,14 @@ class ConstraintInfo: n_data_constr_mat: int mapping_rows_eq: np.ndarray mapping_rows_ineq: np.ndarray + + +@dataclass +class ResultPointerInfo: + objective_value: str + iterations: str + status: str + primal_residual: str + dual_residual: str + primal_solution: str + dual_solution: str diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index ad5a9e0..43830fd 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -9,7 +9,7 @@ from cvxpygen import utils from cvxpygen.mappings import PrimalVariableInfo, DualVariableInfo, ConstraintInfo, AffineMap, \ - ParameterCanon + ParameterCanon, ResultPointerInfo def get_interface_class(solver_name: str) -> "SolverInterface": @@ -160,6 +160,18 @@ class OSQPInterface(SolverInterface): # preconditioning of problem data happening in-memory inmemory_preconditioning = False + # workspace + ws_allocated_in_solver_code = True + result_ptrs = ResultPointerInfo( + objective_value = 'workspace.info->obj_val', + iterations = 'workspace.info->iter', + status = 'workspace.info->status', + primal_residual = 'workspace.info->pri_res', + dual_residual = 'workspace.info->dua_res', + primal_solution = 'xsolution', #'workspace.solution->x', + dual_solution = '%ssolution' #'workspace.solution->%s' + ) + # solution vectors statically allocated sol_statically_allocated = True @@ -288,6 +300,18 @@ class SCSInterface(SolverInterface): # preconditioning of problem data happening in-memory inmemory_preconditioning = False + # workspace + ws_allocated_in_solver_code = False + result_ptrs = ResultPointerInfo( + objective_value = 'Scs_Info.pobj', + iterations = 'Scs_Info.iter', + status = 'Scs_Info.status', + primal_residual = 'Scs_Info.res_pri', + dual_residual = 'Scs_Info.res_dual', + primal_solution = 'scs_x', + dual_solution = 'scs_%s' + ) + # solution vectors statically allocated sol_statically_allocated = True @@ -409,6 +433,18 @@ class ECOSInterface(SolverInterface): # preconditioning of problem data happening in-memory inmemory_preconditioning = True + # workspace + ws_allocated_in_solver_code = False + result_ptrs = ResultPointerInfo( + objective_value = 'ecos_workspace->info->pcost', + iterations = 'ecos_workspace->info->iter', + status = 'ecos_flag', + primal_residual = 'ecos_workspace->info->pres', + dual_residual = 'ecos_workspace->info->dres', + primal_solution = 'ecos_workspace->x', + dual_solution = 'ecos_workspace->%s' + ) + # solution vectors statically allocated sol_statically_allocated = False diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index edc12d8..2081623 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -434,6 +434,8 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par write_vec_def(f, value.flatten(order='F'), configuration.prefix + name, 'cpg_float') f.write('\n') + result_prefix = configuration.prefix if not solver_interface.ws_allocated_in_solver_code else '' + f.write('// Struct containing primal solution\n') CPG_Prim_fields = list(variable_info.name_to_init.keys()) CPG_Prim_values = [] @@ -445,10 +447,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: CPG_Prim_values.append('&' + configuration.prefix + name) else: - if configuration.solver_name == 'OSQP': - CPG_Prim_values.append('&xsolution + %d' % offset) - elif configuration.solver_name == 'SCS': - CPG_Prim_values.append('&%sscs_x + %d' % (configuration.prefix, offset)) + CPG_Prim_values.append('&%s%s + %d' % (result_prefix, solver_interface.result_ptrs.primal_solution, offset)) write_struct_def(f, CPG_Prim_fields, prim_cast, CPG_Prim_values, '%sCPG_Prim' % configuration.prefix, 'CPG_Prim_t') if len(dual_variable_info.name_to_init) > 0: @@ -473,12 +472,10 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if is_mathematical_scalar(var): CPG_Dual_values.append('0') else: - if configuration.solver_name == 'OSQP': - CPG_Dual_values.append('&%ssolution + %d' % (vec, offset)) - elif configuration.solver_name == 'SCS': - CPG_Dual_values.append('&%sscs_%s + %d' % (configuration.prefix, vec, offset)) - else: + if not solver_interface.sol_statically_allocated: CPG_Dual_values.append('&' + configuration.prefix + name) + else: + CPG_Dual_values.append('&%s%s + %d' % (result_prefix, solver_interface.result_ptrs.dual_solution, offset) % vec) write_struct_def(f, CPG_Dual_fields, dual_cast, CPG_Dual_values, '%sCPG_Dual' % configuration.prefix, 'CPG_Dual_t') @@ -842,20 +839,9 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet write_canonicalize(f, p_id, s, mapping, configuration.prefix) f.write('}\n\n') - if configuration.solver_name == 'OSQP': - obj_str = 'workspace.info->obj_val' - prim_str = 'workspace.solution->x' - dual_str = 'workspace.solution->' - elif configuration.solver_name == 'SCS': - obj_str = '%sScs_Info.pobj' % configuration.prefix - prim_str = '%sscs_x' % configuration.prefix - dual_str = '%sscs_' % configuration.prefix - elif configuration.solver_name == 'ECOS': - obj_str = '%secos_workspace->info->pcost' % configuration.prefix - prim_str = '%secos_workspace->x' % configuration.prefix - dual_str = '%secos_workspace->' % configuration.prefix - else: - raise ValueError("Only OSQP and ECOS are supported!") + result_prefix = configuration.prefix if not solver_interface.ws_allocated_in_solver_code else '' + prim_str = result_prefix + solver_interface.result_ptrs.primal_solution + dual_str = result_prefix + solver_interface.result_ptrs.dual_solution if solver_interface.ret_prim_func_exists(variable_info): f.write('// Retrieve primal solution in terms of user-defined variables\n') @@ -871,42 +857,27 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet if solver_interface.ret_dual_func_exists(dual_variable_info): f.write('// Retrieve dual solution in terms of user-defined constraints\n') f.write('void %scpg_retrieve_dual(){\n' % configuration.prefix) - for var_name, (vector, indices) in dual_variable_info.name_to_indices.items(): + for var_name, (canonical_var_name, indices) in dual_variable_info.name_to_indices.items(): if len(indices) == 1: - f.write(' %sCPG_Dual.%s = %s%s[%d];\n' % (configuration.prefix, var_name, dual_str, vector, indices)) + f.write(' %sCPG_Dual.%s = %s[%d];\n' % (configuration.prefix, var_name, dual_str, indices) % canonical_var_name) elif not solver_interface.sol_statically_allocated: for i, idx in enumerate(indices): - f.write(' %sCPG_Dual.%s[%d] = %s%s[%d];\n' - % (configuration.prefix, var_name, i, dual_str, vector, idx)) + f.write(' %sCPG_Dual.%s[%d] = %s[%d];\n' + % (configuration.prefix, var_name, i, dual_str, idx) % canonical_var_name) f.write('}\n\n') f.write('// Retrieve solver info\n') f.write('void %scpg_retrieve_info(){\n' % configuration.prefix) - if parameter_canon.nonzero_d: - d_str = ' + %sCanon_Params.d' % configuration.prefix - else: - d_str = '' - if parameter_canon.is_maximization: - f.write(' %sCPG_Info.obj_val = -(%s%s);\n' % (configuration.prefix, obj_str, d_str)) - else: - f.write(' %sCPG_Info.obj_val = %s%s;\n' % (configuration.prefix, obj_str, d_str)) - if configuration.solver_name == 'OSQP': - f.write(' %sCPG_Info.iter = workspace.info->iter;\n' % configuration.prefix) - f.write(' %sCPG_Info.status = workspace.info->status;\n' % configuration.prefix) - f.write(' %sCPG_Info.pri_res = workspace.info->pri_res;\n' % configuration.prefix) - f.write(' %sCPG_Info.dua_res = workspace.info->dua_res;\n' % configuration.prefix) - elif configuration.solver_name == 'SCS': - f.write(' %sCPG_Info.iter = %sScs_Info.iter;\n' % (configuration.prefix, configuration.prefix)) - f.write(' %sCPG_Info.status = %sScs_Info.status;\n' % (configuration.prefix, configuration.prefix)) - f.write(' %sCPG_Info.pri_res = %sScs_Info.res_pri;\n' % (configuration.prefix, configuration.prefix)) - f.write(' %sCPG_Info.dua_res = %sScs_Info.res_dual;\n' % (configuration.prefix, configuration.prefix)) - elif configuration.solver_name == 'ECOS': - f.write(' %sCPG_Info.iter = %secos_workspace->info->iter;\n' % (configuration.prefix, configuration.prefix)) - f.write(' %sCPG_Info.status = %secos_flag;\n' % (configuration.prefix, configuration.prefix)) - f.write(' %sCPG_Info.pri_res = %secos_workspace->info->pres;\n' - % (configuration.prefix, configuration.prefix)) - f.write(' %sCPG_Info.dua_res = %secos_workspace->info->dres;\n' - % (configuration.prefix, configuration.prefix)) + f.write(' %sCPG_Info.obj_val = %s(%s%s%s);\n' % ( + configuration.prefix, + '-' if parameter_canon.is_maximization else '', + result_prefix, + solver_interface.result_ptrs.objective_value, + ' + %sCanon_Params.d' % configuration.prefix if parameter_canon.nonzero_d else '')) + f.write(' %sCPG_Info.iter = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.result_ptrs.iterations)) + f.write(' %sCPG_Info.status = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.result_ptrs.status)) + f.write(' %sCPG_Info.pri_res = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.result_ptrs.primal_residual)) + f.write(' %sCPG_Info.dua_res = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.result_ptrs.dual_residual)) f.write('}\n\n') f.write('// Solve via canonicalization, canonical solve, retrieval\n') @@ -1136,8 +1107,8 @@ def write_example_def(f, configuration, variable_info, dual_variable_info, param f.write(' printf("%s = %%f\\n", %sCPG_Result.prim->%s);\n' % (name, configuration.prefix, name)) else: f.write(' for(i=0; i<%d; i++) {\n' % var.size) - f.write(' printf("%s[%%%s] = %%f\\n", i, %sCPG_Result.prim->%s[i]);\n' - % (name, int_format_str, configuration.prefix, name)) + f.write(' printf("%s[%%d] = %%f\\n", i, %sCPG_Result.prim->%s[i]);\n' + % (name, configuration.prefix, name)) f.write(' }\n') if len(dual_variable_info.name_to_init) > 0: @@ -1147,8 +1118,8 @@ def write_example_def(f, configuration, variable_info, dual_variable_info, param f.write(' printf("%s = %%f\\n", %sCPG_Result.dual->%s);\n' % (name, configuration.prefix, name)) else: f.write(' for(i=0; i<%d; i++) {\n' % var.size) - f.write(' printf("%s[%%%s] = %%f\\n", i, %sCPG_Result.dual->%s[i]);\n' - % (name, int_format_str, configuration.prefix, name)) + f.write(' printf("%s[%%d] = %%f\\n", i, %sCPG_Result.dual->%s[i]);\n' + % (name, configuration.prefix, name)) f.write(' }\n') f.write('\n return 0;\n\n') From dd542aefdd393b65e577bb3c3ef96be982024d66 Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Wed, 6 Sep 2023 15:04:25 -0500 Subject: [PATCH 11/14] Cmake headers and sources to solver classes --- cvxpygen/cpg.py | 2 +- cvxpygen/solvers.py | 18 +++++++++++++++++- cvxpygen/utils.py | 29 +++++++++-------------------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/cvxpygen/cpg.py b/cvxpygen/cpg.py index 1e3a9af..0d45164 100644 --- a/cvxpygen/cpg.py +++ b/cvxpygen/cpg.py @@ -327,7 +327,7 @@ def write_c_code(problem: cp.Problem, configuration: dict, variable_info: dict, f.write(cmake_data) # adapt solver CMakeLists.txt with open(os.path.join(configuration.code_dir, 'c', 'solver_code', 'CMakeLists.txt'), 'a') as f: - utils.write_canon_cmake(f, configuration) + utils.write_canon_cmake(f, configuration, solver_interface) # binding module prototypes with open(os.path.join(configuration.code_dir, 'cpp', 'include', 'cpg_module.hpp'), 'w') as f: utils.write_module_prot(f, configuration, parameter_info, variable_info, dual_variable_info, solver_interface) diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 43830fd..4d17e6c 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -154,8 +154,10 @@ class OSQPInterface(SolverInterface): canon_p_ids_constr_vec = ['l', 'u'] sign_constr_vec = -1 - # header files + # header and source files header_files = ['osqp.h', 'types.h', 'workspace.h'] + cmake_headers = ['${osqp_headers}'] + cmake_sources = ['${osqp_src}'] # preconditioning of problem data happening in-memory inmemory_preconditioning = False @@ -296,6 +298,18 @@ class SCSInterface(SolverInterface): # header files header_files = ['scs.h'] + cmake_headers = [ + '${${PROJECT_NAME}_HDR}', + '${DIRSRC}/private.h', + '${${PROJECT_NAME}_LDL_EXTERNAL_HDR}', + '${${PROJECT_NAME}_AMD_EXTERNAL_HDR}', + ] + cmake_sources = [ + '${${PROJECT_NAME}_SRC}', + '${DIRSRC}/private.c', + '${EXTERNAL}/qdldl/qdldl.c', + '${${PROJECT_NAME}_AMD_EXTERNAL_SRC}' + ] # preconditioning of problem data happening in-memory inmemory_preconditioning = False @@ -429,6 +443,8 @@ class ECOSInterface(SolverInterface): # header files header_files = ['ecos.h'] + cmake_headers = ['${ecos_headers}'] + cmake_sources = ['${ecos_sources}'] # preconditioning of problem data happening in-memory inmemory_preconditioning = True diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index 2081623..b228b1a 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -1138,30 +1138,19 @@ def replace_cmake_data(cmake_data, configuration): return cmake_data.replace('cpg_src', configuration.prefix + 'cpg_src') -def write_canon_cmake(f, configuration): +def write_canon_cmake(f, configuration, solver_interface): """ Pass sources to parent scope in {OSQP/ECOS}_code/CMakeLists.txt """ - if configuration.solver_name == 'OSQP': - f.write('\nset(solver_head "${osqp_headers}" PARENT_SCOPE)') - f.write('\nset(solver_src "${osqp_src}" PARENT_SCOPE)') - elif configuration.solver_name == 'SCS': - f.write('\nset(solver_head') - f.write('\n ${${PROJECT_NAME}_HDR}') - f.write('\n ${DIRSRC}/private.h') - f.write('\n ${${PROJECT_NAME}_LDL_EXTERNAL_HDR}') - f.write('\n ${${PROJECT_NAME}_AMD_EXTERNAL_HDR})') - f.write('\nset(solver_src') - f.write('\n ${${PROJECT_NAME}_SRC}') - f.write('\n ${DIRSRC}/private.c') - f.write('\n ${EXTERNAL}/qdldl/qdldl.c') - f.write('\n ${${PROJECT_NAME}_AMD_EXTERNAL_SRC})') - f.write('\n\nset(solver_head "${solver_head}" PARENT_SCOPE)') - f.write('\nset(solver_src "${solver_src}" PARENT_SCOPE)') - elif configuration.solver_name == 'ECOS': - f.write('\nset(solver_head "${ecos_headers}" PARENT_SCOPE)') - f.write('\nset(solver_src "${ecos_sources}" PARENT_SCOPE)') + f.write('\nset(solver_head') + for h in solver_interface.cmake_headers: + f.write('\n ' + h) + f.write('\n PARENT_SCOPE)') + f.write('\n\nset(solver_src') + for s in solver_interface.cmake_sources: + f.write('\n ' + s) + f.write('\n PARENT_SCOPE)') def write_module_def(f, configuration, variable_info, dual_variable_info, parameter_info, solver_interface): From b2ef443381afe4134951c66aaa85f660275f9a0e Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Wed, 6 Sep 2023 17:26:05 -0500 Subject: [PATCH 12/14] Workspace gen to solver classes --- cvxpygen/solvers.py | 131 ++++++++++++++++++++++++++++++++++++++--- cvxpygen/utils.py | 138 ++++---------------------------------------- 2 files changed, 136 insertions(+), 133 deletions(-) diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 4d17e6c..177b541 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -7,7 +7,7 @@ import numpy as np -from cvxpygen import utils +from cvxpygen.utils import replace_in_file, write_struct_prot, write_struct_def, write_vec_prot, write_vec_def from cvxpygen.mappings import PrimalVariableInfo, DualVariableInfo, ConstraintInfo, AffineMap, \ ParameterCanon, ResultPointerInfo @@ -147,6 +147,14 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, parameter_canon: ParameterCanon) -> None: pass + @staticmethod + def declare_workspace(self, f, prefix) -> None: + pass + + @staticmethod + def define_workspace(self, f, prefix) -> None: + pass + class OSQPInterface(SolverInterface): solver_name = 'OSQP' @@ -238,21 +246,21 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, # modify for extra settings if 'verbose' in self.enable_settings: - utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), + replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'CMakeLists.txt'), [('message(STATUS "Disabling printing for embedded")', 'message(STATUS "Not disabling printing for embedded by user request")'), ('set(PRINTING OFF)', '')]) - utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'constants.h'), + replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'constants.h'), [('# ifdef __cplusplus\n}', '# define VERBOSE (1)\n\n# ifdef __cplusplus\n}')]) - utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'types.h'), + replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'types.h'), [('} OSQPInfo;', ' c_int status_polish;\n} OSQPInfo;'), ('} OSQPSettings;', ' c_int polish;\n c_int verbose;\n} OSQPSettings;'), ('# ifndef EMBEDDED\n c_int nthreads; ///< number of threads active\n# endif // ifndef EMBEDDED', ' c_int nthreads;')]) - utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'osqp.h'), + replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'include', 'osqp.h'), [('# ifdef __cplusplus\n}', 'c_int osqp_update_verbose(OSQPWorkspace *work, c_int verbose_new);\n\n# ifdef __cplusplus\n}')]) - utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'src', 'osqp', 'util.c'), + replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'src', 'osqp', 'util.c'), [('// Print Settings', '/* Print Settings'), ('LINSYS_SOLVER_NAME[settings->linsys_solver]);', 'LINSYS_SOLVER_NAME[settings->linsys_solver]);*/')]) - utils.replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'src', 'osqp', 'osqp.c'), + replace_in_file(os.path.join(code_dir, 'c', 'solver_code', 'src', 'osqp', 'osqp.c'), [('void osqp_set_default_settings(OSQPSettings *settings) {', 'void osqp_set_default_settings(OSQPSettings *settings) {\n settings->verbose = VERBOSE;'), ('c_int osqp_update_verbose', '#endif // EMBEDDED\n\nc_int osqp_update_verbose'), ('verbose = verbose_new;\n\n return 0;\n}\n\n#endif // EMBEDDED', 'verbose = verbose_new;\n\n return 0;\n}')]) @@ -434,6 +442,90 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, with open(os.path.join(code_dir, 'setup.py'), 'w') as f: f.write(setup_text) + def declare_workspace(self, f, prefix) -> None: + f.write('\n// SCS matrix A\n') + write_struct_prot(f, '%sscs_A' % prefix, 'ScsMatrix') + f.write('\n// Struct containing SCS data\n') + write_struct_prot(f, '%sScs_D' % prefix, 'ScsData') + if self.canon_constants['qsize'] > 0: + f.write('\n// SCS array of SOC dimensions\n') + write_vec_prot(f, self.canon_constants['q'], '%sscs_q' % prefix, 'cpg_int') + f.write('\n// Struct containing SCS cone data\n') + write_struct_prot(f, '%sScs_K' % prefix, 'ScsCone') + f.write('\n// Struct containing SCS settings\n') + write_struct_prot(f, prefix + 'Canon_Settings', 'ScsSettings') + f.write('\n// SCS solution\n') + write_vec_prot(f, np.zeros(self.canon_constants['n']), '%sscs_x' % prefix, 'cpg_float') + write_vec_prot(f, np.zeros(self.canon_constants['m']), '%sscs_y' % prefix, 'cpg_float') + write_vec_prot(f, np.zeros(self.canon_constants['m']), '%sscs_s' % prefix, 'cpg_float') + f.write('\n// Struct containing SCS solution\n') + write_struct_prot(f, '%sScs_Sol' % prefix, 'ScsSolution') + f.write('\n// Struct containing SCS information\n') + write_struct_prot(f, '%sScs_Info' % prefix, 'ScsInfo') + f.write('\n// Pointer to struct containing SCS workspace\n') + write_struct_prot(f, '%sScs_Work' % prefix, 'ScsWork*') + + def define_workspace(self, f, prefix) -> None: + f.write('\n// SCS matrix A\n') + scs_A_fields = ['x', 'i', 'p', 'm', 'n'] + scs_A_casts = ['(cpg_float *) ', '(cpg_int *) ', '(cpg_int *) ', '', ''] + scs_A_values = ['&%scanon_A_x' % prefix, '&%scanon_A_i' % prefix, + '&%scanon_A_p' % prefix, str(self.canon_constants['m']), + str(self.canon_constants['n'])] + write_struct_def(f, scs_A_fields, scs_A_casts, scs_A_values, '%sScs_A' % prefix, 'ScsMatrix') + + f.write('\n// Struct containing SCS data\n') + scs_d_fields = ['m', 'n', 'A', 'P', 'b', 'c'] + scs_d_casts = ['', '', '', '', '(cpg_float *) ', '(cpg_float *) '] + scs_d_values = [str(self.canon_constants['m']), str(self.canon_constants['n']), + '&%sScs_A' % prefix, 'SCS_NULL', '&%scanon_b' % prefix, '&%scanon_c' % prefix] + write_struct_def(f, scs_d_fields, scs_d_casts, scs_d_values, '%sScs_D' % prefix, 'ScsData') + + if self.canon_constants['qsize'] > 0: + f.write('\n// SCS array of SOC dimensions\n') + write_vec_def(f, self.canon_constants['q'], '%sscs_q' % prefix, 'cpg_int') + k_field_q_str = '&%sscs_q' % prefix + else: + k_field_q_str = 'SCS_NULL' + + f.write('\n// Struct containing SCS cone data\n') + scs_k_fields = ['z', 'l', 'bu', 'bl', 'bsize', 'q', 'qsize', 's', 'ssize', 'ep', 'ed', 'p', 'psize'] + scs_k_casts = ['', '', '(cpg_float *) ', '(cpg_float *) ', '', '(cpg_int *) ', '', '(cpg_int *) ', '', '', '', + '(cpg_float *) ', ''] + scs_k_values = [str(self.canon_constants['z']), str(self.canon_constants['l']), 'SCS_NULL', 'SCS_NULL', '0', + k_field_q_str, str(self.canon_constants['qsize']), 'SCS_NULL', '0', '0', '0', 'SCS_NULL', '0'] + write_struct_def(f, scs_k_fields, scs_k_casts, scs_k_values, '%sScs_K' % prefix, 'ScsCone') + + f.write('\n// Struct containing SCS settings\n') + scs_stgs_fields = list(self.stgs_names_to_default.keys()) + scs_stgs_casts = ['']*len(scs_stgs_fields) + scs_stgs_values = list(self.stgs_names_to_default.values()) + write_struct_def(f, scs_stgs_fields, scs_stgs_casts, scs_stgs_values, prefix + 'Canon_Settings', 'ScsSettings') + + f.write('\n// SCS solution\n') + write_vec_def(f, np.zeros(self.canon_constants['n']), '%sscs_x' % prefix, 'cpg_float') + write_vec_def(f, np.zeros(self.canon_constants['m']), '%sscs_y' % prefix, 'cpg_float') + write_vec_def(f, np.zeros(self.canon_constants['m']), '%sscs_s' % prefix, 'cpg_float') + + f.write('\n// Struct containing SCS solution\n') + scs_sol_fields = ['x', 'y', 's'] + scs_sol_casts = ['(cpg_float *) ', '(cpg_float *) ', '(cpg_float *) '] + scs_sol_values = ['&%sscs_x' % prefix, '&%sscs_y' % prefix, '&%sscs_s' % prefix] + write_struct_def(f, scs_sol_fields, scs_sol_casts, scs_sol_values, '%sScs_Sol' % prefix, 'ScsSolution') + + f.write('\n// Struct containing SCS information\n') + scs_info_fields = ['iter', 'status', 'status_val', 'scale_updates', 'pobj', 'dobj', 'res_pri', 'res_dual', + 'gap', 'res_infeas', 'res_unbdd_a', 'res_unbdd_p', 'comp_slack', 'setup_time', 'solve_time', + 'scale', 'rejected_accel_steps', 'accepted_accel_steps', 'lin_sys_time', 'cone_time', + 'accel_time'] + scs_info_casts = ['']*len(scs_info_fields) + scs_info_values = ['0', '"unknown"', '0', '0', '0', '0', '99', '99', '99', '99', '99', '99', '99', '0', '0', + '1', '0', '0', '0', '0', '0'] + write_struct_def(f, scs_info_fields, scs_info_casts, scs_info_values, '%sScs_Info' % prefix, 'ScsInfo') + + f.write('\n// Pointer to struct containing SCS workspace\n') + f.write('ScsWork* %sScs_Work = 0;\n' % prefix) + class ECOSInterface(SolverInterface): solver_name = 'ECOS' @@ -574,3 +666,28 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, setup_text = setup_text.replace("license='Apache 2.0'", "license='GPL 3.0'") with open(os.path.join(code_dir, 'setup.py'), 'w') as f: f.write(setup_text) + + def declare_workspace(self, f, prefix) -> None: + f.write('\n// Struct containing solver settings\n') + write_struct_prot(f, prefix + 'Canon_Settings', 'Canon_Settings_t') + if self.canon_constants['n_cones'] > 0: + f.write('\n// ECOS array of SOC dimensions\n') + write_vec_prot(f, self.canon_constants['q'], '%secos_q' % prefix, 'cpg_int') + f.write('\n// ECOS workspace\n') + f.write('extern pwork* %secos_workspace;\n' % prefix) + f.write('\n// ECOS exit flag\n') + f.write('extern cpg_int %secos_flag;\n' % prefix) + + def define_workspace(self, f, prefix) -> None: + f.write('\n// Struct containing solver settings\n') + f.write('Canon_Settings_t %sCanon_Settings = {\n' % prefix) + for name, default in self.stgs_names_to_default.items(): + f.write('.%s = %s,\n' % (name, default)) + f.write('};\n') + if self.canon_constants['n_cones'] > 0: + f.write('\n// ECOS array of SOC dimensions\n') + write_vec_def(f, self.canon_constants['q'], '%secos_q' % prefix, 'cpg_int') + f.write('\n// ECOS workspace\n') + f.write('pwork* %secos_workspace = 0;\n' % prefix) + f.write('\n// ECOS exit flag\n') + f.write('cpg_int %secos_flag = -99;\n' % prefix) diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index b228b1a..318751b 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -498,86 +498,8 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par write_struct_def(f, CPG_Result_fields, result_cast, CPG_Result_values, '%sCPG_Result' % configuration.prefix, 'CPG_Result_t') - if configuration.solver_name == 'SCS': - - f.write('\n// SCS matrix A\n') - scs_A_fiels = ['x', 'i', 'p', 'm', 'n'] - scs_A_casts = ['(cpg_float *) ', '(cpg_int *) ', '(cpg_int *) ', '', ''] - scs_A_values = ['&%scanon_A_x' % configuration.prefix, '&%scanon_A_i' % configuration.prefix, - '&%scanon_A_p' % configuration.prefix, str(solver_interface.canon_constants['m']), - str(solver_interface.canon_constants['n'])] - write_struct_def(f, scs_A_fiels, scs_A_casts, scs_A_values, '%sScs_A' % configuration.prefix, 'ScsMatrix') - - f.write('\n// Struct containing SCS data\n') - scs_d_fiels = ['m', 'n', 'A', 'P', 'b', 'c'] - scs_d_casts = ['', '', '', '', '(cpg_float *) ', '(cpg_float *) '] - scs_d_values = [str(solver_interface.canon_constants['m']), str(solver_interface.canon_constants['n']), '&%sScs_A' - % configuration.prefix, 'SCS_NULL', '&%scanon_b' % configuration.prefix, '&%scanon_c' - % configuration.prefix] - write_struct_def(f, scs_d_fiels, scs_d_casts, scs_d_values, '%sScs_D' % configuration.prefix, 'ScsData') - - if solver_interface.canon_constants['qsize'] > 0: - f.write('\n// SCS array of SOC dimensions\n') - write_vec_def(f, solver_interface.canon_constants['q'], '%sscs_q' % configuration.prefix, 'cpg_int') - k_field_q_str = '&%sscs_q' % configuration.prefix - else: - k_field_q_str = 'SCS_NULL' - - f.write('\n// Struct containing SCS cone data\n') - scs_k_fields = ['z', 'l', 'bu', 'bl', 'bsize', 'q', 'qsize', 's', 'ssize', 'ep', 'ed', 'p', 'psize'] - scs_k_casts = ['', '', '(cpg_float *) ', '(cpg_float *) ', '', '(cpg_int *) ', '', '(cpg_int *) ', '', '', '', - '(cpg_float *) ', ''] - scs_k_values = [str(solver_interface.canon_constants['z']), str(solver_interface.canon_constants['l']), 'SCS_NULL', 'SCS_NULL', '0', - k_field_q_str, str(solver_interface.canon_constants['qsize']), 'SCS_NULL', '0', '0', '0', 'SCS_NULL', '0'] - write_struct_def(f, scs_k_fields, scs_k_casts, scs_k_values, '%sScs_K' % configuration.prefix, 'ScsCone') - - f.write('\n// Struct containing SCS settings\n') - scs_stgs_fields = list(solver_interface.stgs_names_to_default.keys()) - scs_stgs_casts = ['']*len(scs_stgs_fields) - scs_stgs_values = list(solver_interface.stgs_names_to_default.values()) - write_struct_def(f, scs_stgs_fields, scs_stgs_casts, scs_stgs_values, configuration.prefix + 'Canon_Settings', 'ScsSettings') - - f.write('\n// SCS solution\n') - write_vec_def(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, 'cpg_float') - write_vec_def(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_y' % configuration.prefix, 'cpg_float') - write_vec_def(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_s' % configuration.prefix, 'cpg_float') - - f.write('\n// Struct containing SCS solution\n') - scs_sol_fields = ['x', 'y', 's'] - scs_sol_casts = ['(cpg_float *) ', '(cpg_float *) ', '(cpg_float *) '] - scs_sol_values = ['&%sscs_x' % configuration.prefix, '&%sscs_y' % configuration.prefix, '&%sscs_s' - % configuration.prefix] - write_struct_def(f, scs_sol_fields, scs_sol_casts, scs_sol_values, '%sScs_Sol' - % configuration.prefix, 'ScsSolution') - - f.write('\n// Struct containing SCS information\n') - scs_info_fields = ['iter', 'status', 'status_val', 'scale_updates', 'pobj', 'dobj', 'res_pri', 'res_dual', - 'gap', 'res_infeas', 'res_unbdd_a', 'res_unbdd_p', 'comp_slack', 'setup_time', 'solve_time', - 'scale', 'rejected_accel_steps', 'accepted_accel_steps', 'lin_sys_time', 'cone_time', - 'accel_time'] - scs_info_casts = ['']*len(scs_info_fields) - scs_info_values = ['0', '"unknown"', '0', '0', '0', '0', '99', '99', '99', '99', '99', '99', '99', '0', '0', - '1', '0', '0', '0', '0', '0'] - write_struct_def(f, scs_info_fields, scs_info_casts, scs_info_values, '%sScs_Info' - % configuration.prefix, 'ScsInfo') - - f.write('\n// Pointer to struct containing SCS workspace\n') - f.write('ScsWork* %sScs_Work = 0;\n' % configuration.prefix) - - if configuration.solver_name == 'ECOS': - - f.write('\n// Struct containing solver settings\n') - f.write('Canon_Settings_t %sCanon_Settings = {\n' % configuration.prefix) - for name, default in solver_interface.stgs_names_to_default.items(): - f.write('.%s = %s,\n' % (name, default)) - f.write('};\n') - if solver_interface.canon_constants['n_cones'] > 0: - f.write('\n// ECOS array of SOC dimensions\n') - write_vec_def(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'cpg_int') - f.write('\n// ECOS workspace\n') - f.write('pwork* %secos_workspace = 0;\n' % configuration.prefix) - f.write('\n// ECOS exit flag\n') - f.write('cpg_int %secos_flag = -99;\n' % configuration.prefix) + if not solver_interface.ws_allocated_in_solver_code: + solver_interface.define_workspace(f, configuration.prefix) def write_workspace_prot(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): @@ -678,12 +600,11 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write(' CPG_Info_t *info; // Solver info\n') f.write('} CPG_Result_t;\n\n') - if configuration.solver_name == 'ECOS': - f.write('// Solver settings\n') - f.write('typedef struct {\n') - for name, typ in solver_interface.stgs_names_to_type.items(): - f.write(' %s%s;\n' % (typ.ljust(11), name)) - f.write('} Canon_Settings_t;\n\n') + f.write('// Solver settings\n') + f.write('typedef struct {\n') + for name, typ in solver_interface.stgs_names_to_type.items(): + f.write(' %s%s;\n' % (typ.ljust(11), name)) + f.write('} Canon_Settings_t;\n\n') f.write('#endif // ifndef CPG_TYPES_H\n') @@ -744,42 +665,8 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('\n// Struct containing solution and info\n') write_struct_prot(f, '%sCPG_Result' % configuration.prefix, 'CPG_Result_t') - if configuration.solver_name == 'SCS': - f.write('\n// SCS matrix A\n') - write_struct_prot(f, '%sscs_A' % configuration.prefix, 'ScsMatrix') - f.write('\n// Struct containing SCS data\n') - write_struct_prot(f, '%sScs_D' % configuration.prefix, 'ScsData') - if solver_interface.canon_constants['qsize'] > 0: - f.write('\n// SCS array of SOC dimensions\n') - write_vec_prot(f, solver_interface.canon_constants['q'], '%sscs_q' % configuration.prefix, 'cpg_int') - f.write('\n// Struct containing SCS cone data\n') - write_struct_prot(f, '%sScs_K' % configuration.prefix, 'ScsCone') - f.write('\n// Struct containing SCS settings\n') - write_struct_prot(f, configuration.prefix + 'Canon_Settings', 'ScsSettings') - f.write('\n// SCS solution\n') - write_vec_prot(f, np.zeros(solver_interface.canon_constants['n']), '%sscs_x' % configuration.prefix, - 'cpg_float') - write_vec_prot(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_y' % configuration.prefix, - 'cpg_float') - write_vec_prot(f, np.zeros(solver_interface.canon_constants['m']), '%sscs_s' % configuration.prefix, - 'cpg_float') - f.write('\n// Struct containing SCS solution\n') - write_struct_prot(f, '%sScs_Sol' % configuration.prefix, 'ScsSolution') - f.write('\n// Struct containing SCS information\n') - write_struct_prot(f, '%sScs_Info' % configuration.prefix, 'ScsInfo') - f.write('\n// Pointer to struct containing SCS workspace\n') - write_struct_prot(f, '%sScs_Work' % configuration.prefix, 'ScsWork*') - - if configuration.solver_name == 'ECOS': - f.write('\n// Struct containing solver settings\n') - write_struct_prot(f, configuration.prefix + 'Canon_Settings', 'Canon_Settings_t') - if solver_interface.canon_constants['n_cones'] > 0: - f.write('\n// ECOS array of SOC dimensions\n') - write_vec_prot(f, solver_interface.canon_constants['q'], '%secos_q' % configuration.prefix, 'cpg_int') - f.write('\n// ECOS workspace\n') - f.write('extern pwork* %secos_workspace;\n' % configuration.prefix) - f.write('\n// ECOS exit flag\n') - f.write('extern cpg_int %secos_flag;\n' % configuration.prefix) + if not solver_interface.ws_allocated_in_solver_code: + solver_interface.declare_workspace(f, configuration.prefix) def write_solve_def(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): @@ -1386,6 +1273,8 @@ def write_method(f, configuration, variable_info, dual_variable_info, parameter_ f.write('from cvxpy.problems.problem import SolverStats\n') f.write('from %s import cpg_module\n\n\n' % configuration.code_dir.replace('/', '.').replace('\\', '.')) + f.write('standard_settings_names = {"max_iters": "maxit"}\n\n\n') + f.write('def cpg_solve(prob, updated_params=None, **kwargs):\n\n') f.write(' # set flags for updated parameters\n') f.write(' upd = cpg_module.%scpg_updated()\n' % configuration.prefix) @@ -1403,11 +1292,8 @@ def write_method(f, configuration, variable_info, dual_variable_info, parameter_ f.write(' # set solver settings\n') f.write(' cpg_module.set_solver_default_settings()\n') f.write(' for key, value in kwargs.items():\n') - if configuration.solver_name == 'ECOS': - f.write(' if key == "max_iters":\n') - f.write(' key = "maxit"\n') f.write(' try:\n') - f.write(' eval(\'cpg_module.set_solver_%s(value)\' % key)\n') + f.write(' eval(\'cpg_module.set_solver_%s(value)\' % standard_settings_names.get(key, key))\n') f.write(' except AttributeError:\n') f.write(' raise(AttributeError(\'Solver setting "%s" not available.\' % key))\n\n') From d3360e76b5e3c4f20a313ca01aa338e5062c94a2 Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:47:57 -0500 Subject: [PATCH 13/14] Systematic logical parameter updates --- cvxpygen/cpg.py | 6 +- cvxpygen/mappings.py | 18 ++- cvxpygen/solvers.py | 116 +++++++++++++++-- cvxpygen/utils.py | 292 ++++++++++++++++++++----------------------- 4 files changed, 260 insertions(+), 172 deletions(-) diff --git a/cvxpygen/cpg.py b/cvxpygen/cpg.py index 0d45164..8d14176 100644 --- a/cvxpygen/cpg.py +++ b/cvxpygen/cpg.py @@ -58,6 +58,9 @@ def generate_code(problem, code_dir='CPG_code', solver=None, enable_settings=[], solver_name = solving_chain.solver.name() interface_class = get_interface_class(solver_name) + # configuration + configuration = get_configuration(code_dir, solver, unroll, prefix) + # for cone problems, check if all cones are supported if hasattr(param_prob, 'cone_dims'): cone_dims = param_prob.cone_dims @@ -92,9 +95,6 @@ def generate_code(problem, code_dir='CPG_code', solver=None, enable_settings=[], user_p_name: [solver_interface.canon_p_ids[j] for j in np.nonzero(adjacency[:, i])[0]] for i, user_p_name in enumerate(parameter_info.names)} - # configuration - configuration = get_configuration(code_dir, solver, unroll, prefix) - write_c_code(problem, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface) diff --git a/cvxpygen/mappings.py b/cvxpygen/mappings.py index 1d78813..bc6c410 100644 --- a/cvxpygen/mappings.py +++ b/cvxpygen/mappings.py @@ -79,7 +79,7 @@ class ConstraintInfo: @dataclass -class ResultPointerInfo: +class WorkspacePointerInfo: objective_value: str iterations: str status: str @@ -87,3 +87,19 @@ class ResultPointerInfo: dual_residual: str primal_solution: str dual_solution: str + settings: str = None + + +@dataclass +class UpdatePendingLogic: + parameters_outdated: list[str] + operator: str = None + functions_if_false: list[str] = None + extra_condition: str = None + extra_condition_operator: str = None + + +@dataclass +class ParameterUpdateLogic: + update_pending_logic: UpdatePendingLogic + function_call: str diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 177b541..5bdde1d 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -9,7 +9,7 @@ from cvxpygen.utils import replace_in_file, write_struct_prot, write_struct_def, write_vec_prot, write_vec_def from cvxpygen.mappings import PrimalVariableInfo, DualVariableInfo, ConstraintInfo, AffineMap, \ - ParameterCanon, ResultPointerInfo + ParameterCanon, WorkspacePointerInfo, UpdatePendingLogic, ParameterUpdateLogic def get_interface_class(solver_name: str) -> "SolverInterface": @@ -147,11 +147,9 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, parameter_canon: ParameterCanon) -> None: pass - @staticmethod def declare_workspace(self, f, prefix) -> None: pass - @staticmethod def define_workspace(self, f, prefix) -> None: pass @@ -161,6 +159,37 @@ class OSQPInterface(SolverInterface): canon_p_ids = ['P', 'q', 'd', 'A', 'l', 'u'] canon_p_ids_constr_vec = ['l', 'u'] sign_constr_vec = -1 + parameter_update_structure = { + 'PA': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['P', 'A'], '&&', ['P', 'A']), + function_call = 'osqp_update_P_A(&workspace, {prefix}Canon_Params.P->x, 0, 0, {prefix}Canon_Params.A->x, 0, 0)', + ), + 'P': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['P']), + function_call = 'osqp_update_P(&workspace, {prefix}Canon_Params.P->x, 0, 0)' + ), + 'A': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['A']), + function_call = 'osqp_update_A(&workspace, {prefix}Canon_Params.A->x, 0, 0)' + ), + 'q': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['q']), + function_call = 'osqp_update_lin_cost(&workspace, {prefix}Canon_Params.q)' + ), + 'lu': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['l', 'u'], '&&', ['l', 'u']), + function_call = 'osqp_update_bounds(&workspace, {prefix}Canon_Params.l, {prefix}Canon_Params.u)', + ), + 'l': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['l']), + function_call = 'osqp_update_lower_bound(&workspace, {prefix}Canon_Params.l)' + ), + 'u': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['u']), + function_call = 'osqp_update_upper_bound(&workspace, {prefix}Canon_Params.u)' + ) + } + solve_function_call = 'osqp_solve(&workspace)' # header and source files header_files = ['osqp.h', 'types.h', 'workspace.h'] @@ -171,15 +200,16 @@ class OSQPInterface(SolverInterface): inmemory_preconditioning = False # workspace - ws_allocated_in_solver_code = True - result_ptrs = ResultPointerInfo( + ws_statically_allocated_in_solver_code = True + ws_dynamically_allocated_in_solver_code = False + ws_ptrs = WorkspacePointerInfo( objective_value = 'workspace.info->obj_val', iterations = 'workspace.info->iter', status = 'workspace.info->status', primal_residual = 'workspace.info->pri_res', dual_residual = 'workspace.info->dua_res', - primal_solution = 'xsolution', #'workspace.solution->x', - dual_solution = '%ssolution' #'workspace.solution->%s' + primal_solution = 'xsolution', + dual_solution = '%ssolution' ) # solution vectors statically allocated @@ -303,6 +333,27 @@ class SCSInterface(SolverInterface): canon_p_ids = ['c', 'd', 'A', 'b'] canon_p_ids_constr_vec = ['b'] sign_constr_vec = 1 + parameter_update_structure = { + 'init': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic( + ['A'], extra_condition = '!{prefix}Scs_Work', extra_condition_operator = '||', functions_if_false = ['bc'] + ), + function_call = '{prefix}Scs_Work = scs_init(&{prefix}Scs_D, &{prefix}Scs_K, &{prefix}Canon_Settings)', + ), + 'bc': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['b', 'c'], '&&', ['b', 'c']), + function_call = 'scs_update({prefix}Scs_Work, {prefix}Canon_Params.b, {prefix}Canon_Params.c)' + ), + 'b': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['b']), + function_call = 'scs_update({prefix}Scs_Work, {prefix}Canon_Params.b, SCS_NULL)' + ), + 'c': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['c']), + function_call = 'scs_update({prefix}Scs_Work, SCS_NULL, {prefix}Canon_Params.c)' + ) + } + solve_function_call = 'scs_solve({prefix}Scs_Work, &{prefix}Scs_Sol, &{prefix}Scs_Info, ({prefix}Scs_Work && {prefix}Canon_Settings.warm_start))' # header files header_files = ['scs.h'] @@ -323,8 +374,9 @@ class SCSInterface(SolverInterface): inmemory_preconditioning = False # workspace - ws_allocated_in_solver_code = False - result_ptrs = ResultPointerInfo( + ws_statically_allocated_in_solver_code = False + ws_dynamically_allocated_in_solver_code = False + ws_ptrs = WorkspacePointerInfo( objective_value = 'Scs_Info.pobj', iterations = 'Scs_Info.iter', status = 'Scs_Info.status', @@ -532,6 +584,7 @@ class ECOSInterface(SolverInterface): canon_p_ids = ['c', 'd', 'A', 'b', 'G', 'h'] canon_p_ids_constr_vec = ['b', 'h'] sign_constr_vec = 1 + solve_function_call = '{prefix}ecos_flag = ECOS_solve({prefix}ecos_workspace)' # header files header_files = ['ecos.h'] @@ -542,15 +595,17 @@ class ECOSInterface(SolverInterface): inmemory_preconditioning = True # workspace - ws_allocated_in_solver_code = False - result_ptrs = ResultPointerInfo( + ws_statically_allocated_in_solver_code = False + ws_dynamically_allocated_in_solver_code = True + ws_ptrs = WorkspacePointerInfo( objective_value = 'ecos_workspace->info->pcost', iterations = 'ecos_workspace->info->iter', status = 'ecos_flag', primal_residual = 'ecos_workspace->info->pres', dual_residual = 'ecos_workspace->info->dres', primal_solution = 'ecos_workspace->x', - dual_solution = 'ecos_workspace->%s' + dual_solution = 'ecos_workspace->%s', + settings = 'ecos_workspace->stgs->%s' ) # solution vectors statically allocated @@ -590,6 +645,43 @@ def __init__(self, data, p_prob, enable_settings): 'q': np.array(p_prob.cone_dims.soc), 'e': p_prob.cone_dims.exp} + self.parameter_update_structure = { + 'init': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic([], extra_condition = '!{prefix}ecos_workspace', functions_if_false = ['AbcGh']), + function_call = '{prefix}ecos_workspace = ECOS_setup(%d, %d, %d, %d, %d, %s, %d, ' + '{prefix}Canon_Params_conditioning.G->x, {prefix}Canon_Params_conditioning.G->p, {prefix}Canon_Params_conditioning.G->i, ' + '%s, %s, %s, {prefix}Canon_Params_conditioning.c, {prefix}Canon_Params_conditioning.h, %s)' % ( + canon_constants['n'], + canon_constants['m'], + canon_constants['p'], + canon_constants['l'], + canon_constants['n_cones'], + '0' if canon_constants['n_cones'] == 0 else '(int *) &{prefix}ecos_q', + canon_constants['e'], + '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.A->x', + '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.A->p', + '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.A->i', + '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.b' + ), + ), + 'AbcGh': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['A', 'b', 'G'], '||', ['c', 'h']), + function_call = 'ECOS_updateData({prefix}ecos_workspace, {prefix}Canon_Params_conditioning.G->x, %s, ' + '{prefix}Canon_Params_conditioning.c, {prefix}Canon_Params_conditioning.h, %s)' % ( + '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.A->x', + '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.b' + ) + ), + 'c': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['c']), + function_call = 'for (i=0; i<%d; i++) {{ ecos_updateDataEntry_c({prefix}ecos_workspace, i, {prefix}Canon_Params_conditioning.c[i]); }}' % canon_constants['n'] + ), + 'h': ParameterUpdateLogic( + update_pending_logic = UpdatePendingLogic(['h']), + function_call = 'for (i=0; i<%d; i++) {{ ecos_updateDataEntry_h({prefix}ecos_workspace, i, {prefix}Canon_Params_conditioning.h[i]); }}' % canon_constants['m'] + ) + } + super().__init__(self.solver_name, n_var, n_eq, n_ineq, indices_obj, indptr_obj, shape_obj, indices_constr, indptr_constr, shape_constr, canon_constants, enable_settings) diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index 318751b..10ca3bf 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -288,53 +288,118 @@ def write_struct_prot(f, name, typ): f.write("extern %s %s;\n" % (typ, name)) -def write_ecos_setup_update(f, canon_constants, prefix): +def extend_functions_if_false(pus, functions_if_false): """ - Write ECOS setup function to file + Recursively extends functions_if_false in the parameter update structure """ - n = canon_constants['n'] - m = canon_constants['m'] - p = canon_constants['p'] - ell = canon_constants['l'] - n_cones = canon_constants['n_cones'] - e = canon_constants['e'] - - if p == 0: - Ax_str = Ap_str = Ai_str = b_str = '0' - else: - Ax_str = '%sCanon_Params_conditioning.A->x' % prefix - Ap_str = '%sCanon_Params_conditioning.A->p' % prefix - Ai_str = '%sCanon_Params_conditioning.A->i' % prefix - b_str = '%sCanon_Params_conditioning.b' % prefix - if n_cones == 0: - ecos_q_str = '0' - else: - ecos_q_str = '(int *) &%secos_q' % prefix - - f.write(' if (!%secos_workspace) {\n' % prefix) - f.write(' %secos_workspace = ECOS_setup(%d, %d, %d, %d, %d, %s, %d, ' - '%sCanon_Params_conditioning.G->x, %sCanon_Params_conditioning.G->p, %sCanon_Params_conditioning.G->i, ' - '%s, %s, %s, %sCanon_Params_conditioning.c, %sCanon_Params_conditioning.h, %s);\n' % - (prefix, n, m, p, ell, n_cones, ecos_q_str, e, prefix, prefix, prefix, Ax_str, Ap_str, Ai_str, - prefix, prefix, b_str)) - f.write(' } else {\n') - f.write(' if (%sCanon_Outdated.G || %sCanon_Outdated.A || %sCanon_Outdated.b) {\n' % (prefix, prefix, prefix)) - f.write(' ECOS_updateData(%secos_workspace, %sCanon_Params_conditioning.G->x, %s, %sCanon_Params_conditioning.c, ' - '%sCanon_Params_conditioning.h, %s);\n' % (prefix, prefix, Ax_str, prefix, prefix, b_str)) - f.write(' } else {\n') - f.write(' if (%sCanon_Outdated.h) {\n' % prefix) - f.write(' for (i=0; i<%d; i++){\n' % m) - f.write(' ecos_updateDataEntry_h(%secos_workspace, i, %sCanon_Params_conditioning.h[i]);\n' % (prefix, prefix)) - f.write(' }\n') - f.write(' }\n') - f.write(' if (%sCanon_Outdated.c) {\n' % prefix) - f.write(' for (i=0; i<%d; i++){\n' % n) - f.write(' ecos_updateDataEntry_c(%secos_workspace, i, %sCanon_Params_conditioning.c[i]);\n' % (prefix, prefix)) - f.write(' }\n') - f.write(' }\n') - f.write(' }\n') - f.write(' }\n') + if functions_if_false is None: + return [] + + extended_functions_if_false = [] + + for function in functions_if_false: + extended_functions_if_false.append(function) + extended_functions_if_false.extend(extend_functions_if_false(pus, pus[function].update_pending_logic.functions_if_false)) + + return extended_functions_if_false + + +def analyze_pus(pus, p_id_to_changes): + ''' + Analyze parameter update structure (pus) to return set of canonical update functions + that are to be written to code without logical inter-connections and functions that + are never called + ''' + + functions = set(pus.keys()) + functions_called = set(pus.keys()) + functions_secondary = [] + + for function, logic in pus.items(): + + up_logic = logic.update_pending_logic + operator = up_logic.operator + functions_if_false = extend_functions_if_false(pus, up_logic.functions_if_false) + + if operator in ['&&', '&', 'and', 'AND']: + skip = False + for p in up_logic.parameters_outdated: + if not p_id_to_changes[p]: + functions_called.remove(function) + skip = True + if skip: + continue + elif operator in ['||', '|', 'or', 'OR']: + skip = True + for p in up_logic.parameters_outdated: + if p_id_to_changes[p]: + skip = False + if skip: + functions_called.remove(function) + continue + elif operator is None: + if up_logic.extra_condition_operator is None and len(up_logic.parameters_outdated) == 1 and not p_id_to_changes[function]: + functions_called.remove(function) + continue + else: + raise ValueError('Operator "%s" not implemented.' % operator) + + if functions_if_false is None: + continue + + for f in functions_if_false: + if f not in functions: + raise ValueError('"%s" is not part of parameter update structure.' % f) + functions_secondary.extend(functions_if_false) + + return functions_called-set(functions_secondary), functions-functions_called + + +operator_map = {'&&': '&&', '&': '&&', 'and': '&&', 'AND': '&&', + '||': '||', '|': '||', 'or': '||', 'OR': '||'} + + +def write_update_structure(f, configuration, pus, functions, functions_never_called, depth=0): + """ + Recursively write logical parameter update structure to file + """ + + if functions is None: + return + + write_else = depth > 0 and len(set(functions)-set(functions_never_called)) > 0 + + if write_else: + f.write(' else {\n') + + for function in functions: + + logic = pus[function] + up_logic = logic.update_pending_logic + + if function not in functions_never_called: + f.write('%sif (%s%s%s) {\n' % ( + ' '*(depth+1), + '%s ' % up_logic.extra_condition.format(prefix=configuration.prefix) if up_logic.extra_condition is not None else '', + '%s ' % operator_map.get(up_logic.extra_condition_operator, '||') if up_logic.extra_condition is not None and len(up_logic.parameters_outdated) > 0 else '', + (' %s ' % operator_map.get(up_logic.operator, '&&')).join([ + '%sCanon_Outdated.%s' % (configuration.prefix, p) for p in up_logic.parameters_outdated + ]) + )) + f.write('%s%s;\n' % (' '*(depth+2), logic.function_call.format(prefix=configuration.prefix))) + f.write('%s}' % (' '*(depth+1))) + new_depth = depth + 1 + else: + new_depth = depth * 1 + + write_update_structure(f, configuration, pus, up_logic.functions_if_false, functions_never_called, new_depth) + + if function not in functions_never_called: + f.write('\n') + + if write_else: + f.write('%s}' % (' '*depth)) def write_workspace_def(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): @@ -434,7 +499,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par write_vec_def(f, value.flatten(order='F'), configuration.prefix + name, 'cpg_float') f.write('\n') - result_prefix = configuration.prefix if not solver_interface.ws_allocated_in_solver_code else '' + result_prefix = configuration.prefix if not solver_interface.ws_statically_allocated_in_solver_code else '' f.write('// Struct containing primal solution\n') CPG_Prim_fields = list(variable_info.name_to_init.keys()) @@ -447,7 +512,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: CPG_Prim_values.append('&' + configuration.prefix + name) else: - CPG_Prim_values.append('&%s%s + %d' % (result_prefix, solver_interface.result_ptrs.primal_solution, offset)) + CPG_Prim_values.append('&%s%s + %d' % (result_prefix, solver_interface.ws_ptrs.primal_solution, offset)) write_struct_def(f, CPG_Prim_fields, prim_cast, CPG_Prim_values, '%sCPG_Prim' % configuration.prefix, 'CPG_Prim_t') if len(dual_variable_info.name_to_init) > 0: @@ -475,7 +540,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if not solver_interface.sol_statically_allocated: CPG_Dual_values.append('&' + configuration.prefix + name) else: - CPG_Dual_values.append('&%s%s + %d' % (result_prefix, solver_interface.result_ptrs.dual_solution, offset) % vec) + CPG_Dual_values.append('&%s%s + %d' % (result_prefix, solver_interface.ws_ptrs.dual_solution, offset) % vec) write_struct_def(f, CPG_Dual_fields, dual_cast, CPG_Dual_values, '%sCPG_Dual' % configuration.prefix, 'CPG_Dual_t') @@ -498,7 +563,7 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par write_struct_def(f, CPG_Result_fields, result_cast, CPG_Result_values, '%sCPG_Result' % configuration.prefix, 'CPG_Result_t') - if not solver_interface.ws_allocated_in_solver_code: + if not solver_interface.ws_statically_allocated_in_solver_code: solver_interface.define_workspace(f, configuration.prefix) @@ -665,7 +730,7 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('\n// Struct containing solution and info\n') write_struct_prot(f, '%sCPG_Result' % configuration.prefix, 'CPG_Result_t') - if not solver_interface.ws_allocated_in_solver_code: + if not solver_interface.ws_statically_allocated_in_solver_code: solver_interface.declare_workspace(f, configuration.prefix) @@ -726,9 +791,9 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet write_canonicalize(f, p_id, s, mapping, configuration.prefix) f.write('}\n\n') - result_prefix = configuration.prefix if not solver_interface.ws_allocated_in_solver_code else '' - prim_str = result_prefix + solver_interface.result_ptrs.primal_solution - dual_str = result_prefix + solver_interface.result_ptrs.dual_solution + result_prefix = configuration.prefix if not solver_interface.ws_statically_allocated_in_solver_code else '' + prim_str = result_prefix + solver_interface.ws_ptrs.primal_solution + dual_str = result_prefix + solver_interface.ws_ptrs.dual_solution if solver_interface.ret_prim_func_exists(variable_info): f.write('// Retrieve primal solution in terms of user-defined variables\n') @@ -759,90 +824,24 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet configuration.prefix, '-' if parameter_canon.is_maximization else '', result_prefix, - solver_interface.result_ptrs.objective_value, + solver_interface.ws_ptrs.objective_value, ' + %sCanon_Params.d' % configuration.prefix if parameter_canon.nonzero_d else '')) - f.write(' %sCPG_Info.iter = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.result_ptrs.iterations)) - f.write(' %sCPG_Info.status = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.result_ptrs.status)) - f.write(' %sCPG_Info.pri_res = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.result_ptrs.primal_residual)) - f.write(' %sCPG_Info.dua_res = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.result_ptrs.dual_residual)) + f.write(' %sCPG_Info.iter = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.ws_ptrs.iterations)) + f.write(' %sCPG_Info.status = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.ws_ptrs.status)) + f.write(' %sCPG_Info.pri_res = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.ws_ptrs.primal_residual)) + f.write(' %sCPG_Info.dua_res = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.ws_ptrs.dual_residual)) f.write('}\n\n') f.write('// Solve via canonicalization, canonical solve, retrieval\n') f.write('void %scpg_solve(){\n' % configuration.prefix) f.write(' // Canonicalize if necessary\n') - if configuration.solver_name == 'OSQP': - - if parameter_canon.p_id_to_changes['P'] and parameter_canon.p_id_to_changes['A']: - f.write(' if (%sCanon_Outdated.P && %sCanon_Outdated.A) {\n' - % (configuration.prefix, configuration.prefix)) - f.write(' %scpg_canonicalize_P();\n' % configuration.prefix) - f.write(' %scpg_canonicalize_A();\n' % configuration.prefix) - f.write(' osqp_update_P_A(&workspace, %sCanon_Params.P->x, 0, 0, %sCanon_Params.A->x, 0, 0);\n' - % (configuration.prefix, configuration.prefix)) - f.write(' } else if (%sCanon_Outdated.P) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_P();\n' % configuration.prefix) - f.write(' osqp_update_P(&workspace, %sCanon_Params.P->x, 0, 0);\n' % configuration.prefix) - f.write(' } else if (%sCanon_Outdated.A) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_A();\n' % configuration.prefix) - f.write(' osqp_update_A(&workspace, %sCanon_Params.A->x, 0, 0);\n' % configuration.prefix) - f.write(' }\n') - else: - if parameter_canon.p_id_to_changes['P']: - f.write(' if (%sCanon_Outdated.P) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_P();\n' % configuration.prefix) - f.write(' osqp_update_P(&workspace, %sCanon_Params.P->x, 0, 0);\n' % configuration.prefix) - f.write(' }\n') - if parameter_canon.p_id_to_changes['A']: - f.write(' if (%sCanon_Outdated.A) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_A();\n' % configuration.prefix) - f.write(' osqp_update_A(&workspace, %sCanon_Params.A->x, 0, 0);\n' % configuration.prefix) - f.write(' }\n') - - if parameter_canon.p_id_to_changes['q']: - f.write(' if (%sCanon_Outdated.q) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_q();\n' % configuration.prefix) - f.write(' osqp_update_lin_cost(&workspace, %sCanon_Params.q);\n' % configuration.prefix) - f.write(' }\n') - if parameter_canon.p_id_to_changes['d']: - f.write(' if (%sCanon_Outdated.d) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_d();\n' % configuration.prefix) - f.write(' }\n') - - if parameter_canon.p_id_to_changes['l'] and parameter_canon.p_id_to_changes['u']: - f.write(' if (%sCanon_Outdated.l && %sCanon_Outdated.u) {\n' - % (configuration.prefix, configuration.prefix)) - f.write(' %scpg_canonicalize_l();\n' % configuration.prefix) - f.write(' %scpg_canonicalize_u();\n' % configuration.prefix) - f.write(' osqp_update_bounds(&workspace, %sCanon_Params.l, %sCanon_Params.u);\n' - % (configuration.prefix, configuration.prefix)) - f.write(' } else if (%sCanon_Outdated.l) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_l();\n' % configuration.prefix) - f.write(' osqp_update_lower_bound(&workspace, %sCanon_Params.l);\n' % configuration.prefix) - f.write(' } else if (%sCanon_Outdated.u) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_u();\n' % configuration.prefix) - f.write(' osqp_update_upper_bound(&workspace, %sCanon_Params.u);\n' % configuration.prefix) + for p_id, changes in parameter_canon.p_id_to_changes.items(): + if changes: + f.write(' if (%sCanon_Outdated.%s) {\n' % (configuration.prefix, p_id)) + f.write(' %scpg_canonicalize_%s();\n' % (configuration.prefix, p_id)) f.write(' }\n') - else: - if parameter_canon.p_id_to_changes['l']: - f.write(' if (%sCanon_Outdated.l) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_l();\n' % configuration.prefix) - f.write(' osqp_update_lower_bound(&workspace, %sCanon_Params.l);\n' % configuration.prefix) - f.write(' }\n') - if parameter_canon.p_id_to_changes['u']: - f.write(' if (%sCanon_Outdated.u) {\n' % configuration.prefix) - f.write(' %scpg_canonicalize_u();\n' % configuration.prefix) - f.write(' osqp_update_upper_bound(&workspace, %sCanon_Params.u);\n' % configuration.prefix) - f.write(' }\n') - - elif configuration.solver_name in ['SCS', 'ECOS']: - - for p_id, changes in parameter_canon.p_id_to_changes.items(): - if changes: - f.write(' if (%sCanon_Outdated.%s) {\n' % (configuration.prefix, p_id)) - f.write(' %scpg_canonicalize_%s();\n' % (configuration.prefix, p_id)) - f.write(' }\n') - + if solver_interface.inmemory_preconditioning: for p_id, size in parameter_canon.p_id_to_size.items(): if size == 1: @@ -858,34 +857,15 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet % (configuration.prefix, p_id, configuration.prefix, p_id)) f.write(' }\n') - if configuration.solver_name == 'OSQP': - f.write(' // Solve with OSQP\n') - f.write(' osqp_solve(&workspace);\n') - elif configuration.solver_name == 'SCS': - f.write(' // Solve with SCS\n') - f.write(' if (!%sScs_Work || %sCanon_Outdated.A) {\n' % (configuration.prefix, configuration.prefix)) - f.write(' %sScs_Work = scs_init(&%sScs_D, &%sScs_K, &%sCanon_Settings);\n' % - (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) - f.write(' } else if (%sCanon_Outdated.b && %sCanon_Outdated.c) {\n' % (configuration.prefix, configuration.prefix)) - f.write(' scs_update(%sScs_Work, %sCanon_Params.b, %sCanon_Params.c);\n' % - (configuration.prefix, configuration.prefix, configuration.prefix)) - f.write(' } else if (%sCanon_Outdated.b) {\n' % configuration.prefix) - f.write(' scs_update(%sScs_Work, %sCanon_Params.b, SCS_NULL);\n' % - (configuration.prefix, configuration.prefix)) - f.write(' } else if (%sCanon_Outdated.c) {\n' % configuration.prefix) - f.write(' scs_update(%sScs_Work, SCS_NULL, %sCanon_Params.c);\n' % - (configuration.prefix, configuration.prefix)) - f.write(' }\n') - f.write(' scs_solve(%sScs_Work, &%sScs_Sol, &%sScs_Info, (%sScs_Work && %sCanon_Settings.warm_start));\n' % - (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) - elif configuration.solver_name == 'ECOS': - f.write(' // Initialize / update ECOS workspace and settings\n') - write_ecos_setup_update(f, solver_interface.canon_constants, configuration.prefix) + pus = solver_interface.parameter_update_structure + write_update_structure(f, configuration, pus, *analyze_pus(pus, parameter_canon.p_id_to_changes)) + + if solver_interface.ws_dynamically_allocated_in_solver_code: for name in solver_interface.stgs_names_to_type.keys(): - f.write(' %secos_workspace->stgs->%s = %sCanon_Settings.%s;\n' - % (configuration.prefix, name, configuration.prefix, name)) - f.write(' // Solve with ECOS\n') - f.write(' %secos_flag = ECOS_solve(%secos_workspace);\n' % (configuration.prefix, configuration.prefix)) + f.write(' %s%s = %sCanon_Settings.%s;\n' % (configuration.prefix, solver_interface.ws_ptrs.settings, configuration.prefix, name) % name) + + f.write(' // Solve with %s\n' % configuration.solver_name) + f.write(' %s;\n' % solver_interface.solve_function_call.format(prefix=configuration.prefix)) f.write(' // Retrieve results\n') if solver_interface.ret_prim_func_exists(variable_info): @@ -1027,7 +1007,7 @@ def replace_cmake_data(cmake_data, configuration): def write_canon_cmake(f, configuration, solver_interface): """ - Pass sources to parent scope in {OSQP/ECOS}_code/CMakeLists.txt + Pass sources to parent scope in /CMakeLists.txt """ f.write('\nset(solver_head') From c5c1e6e7c71e2f09d6425d3226bc9b97472f12dd Mon Sep 17 00:00:00 2001 From: maximilianschaller <79449026+maxschaller@users.noreply.github.com> Date: Mon, 11 Sep 2023 21:41:43 -0500 Subject: [PATCH 14/14] f strings --- cvxpygen/cpg.py | 14 +- cvxpygen/solvers.py | 178 +++++++------ cvxpygen/utils.py | 608 +++++++++++++++++++++----------------------- 3 files changed, 376 insertions(+), 424 deletions(-) diff --git a/cvxpygen/cpg.py b/cvxpygen/cpg.py index 8d14176..3ef497b 100644 --- a/cvxpygen/cpg.py +++ b/cvxpygen/cpg.py @@ -260,8 +260,8 @@ def get_dual_variable_info(inverse_data, solver_name) -> DualVariableInfo: d_offsets = [d_canon_offsets_dict[i] for i in dual_ids] d_sizes = [con_canon_dict[i].size for i in dual_ids] d_shapes = [con_canon_dict[i].shape for i in dual_ids] - d_names = ['d%d' % i for i in range(len(dual_ids))] - d_i_to_name = {i: 'd%d' % i for i in range(len(dual_ids))} + d_names = [f'd{i}' for i in range(len(dual_ids))] + d_i_to_name = {i: f'd{i}' for i in range(len(dual_ids))} d_name_to_shape = {n: d_shapes[i] for i, n in d_i_to_name.items()} d_name_to_indices = {n: (v, o + np.arange(np.prod(d_name_to_shape[n]))) for n, v, o in zip(d_names, d_vectors, d_offsets)} @@ -438,17 +438,17 @@ def handle_sparsity(p_prob: cp.Problem) -> None: for p in p_prob.parameters: if p.attributes['sparsity'] is not None: if p.size == 1: - warnings.warn('Ignoring sparsity pattern for scalar parameter %s!' % p.name()) + warnings.warn(f'Ignoring sparsity pattern for scalar parameter {p.name()}!') p.attributes['sparsity'] = None elif max(p.shape) == p.size: - warnings.warn('Ignoring sparsity pattern for vector parameter %s!' % p.name()) + warnings.warn(f'Ignoring sparsity pattern for vector parameter {p.name()}!') p.attributes['sparsity'] = None else: for coord in p.attributes['sparsity']: if coord[0] < 0 or coord[1] < 0 or coord[0] >= p.shape[0] or coord[1] >= \ p.shape[1]: - warnings.warn('Invalid sparsity pattern for parameter %s - out of range! ' - 'Ignoring sparsity pattern.' % p.name()) + warnings.warn(f'Invalid sparsity pattern for parameter {p.name()} - out of range! ' + 'Ignoring sparsity pattern.') p.attributes['sparsity'] = None break p.attributes['sparsity'] = list(set(p.attributes['sparsity'])) @@ -459,7 +459,7 @@ def handle_sparsity(p_prob: cp.Problem) -> None: for j in range(p.shape[1]): if (i, j) not in p.attributes['sparsity'] and p.value[i, j] != 0: warnings.warn( - 'Ignoring nonzero value outside of sparsity pattern for parameter %s!' % p.name()) + f'Ignoring nonzero value outside of sparsity pattern for parameter {p.name()}!') p.value[i, j] = 0 diff --git a/cvxpygen/solvers.py b/cvxpygen/solvers.py index 5bdde1d..653feb9 100644 --- a/cvxpygen/solvers.py +++ b/cvxpygen/solvers.py @@ -86,7 +86,7 @@ def configure_settings(self) -> None: if s in self.enable_settings: self.stgs_enabled[i] = True for s in set(self.enable_settings)-set(self.stgs_names): - warnings.warn('Cannot enable setting %s for solver %s' % (s, self.solver_name)) + warnings.warn(f'Cannot enable setting {s} for solver {self.solver_name}') def get_affine_map(self, p_id, param_prob, constraint_info: ConstraintInfo) -> AffineMap: affine_map = AffineMap() @@ -209,7 +209,7 @@ class OSQPInterface(SolverInterface): primal_residual = 'workspace.info->pri_res', dual_residual = 'workspace.info->dua_res', primal_solution = 'xsolution', - dual_solution = '%ssolution' + dual_solution = '{dual_var_name}solution' ) # solution vectors statically allocated @@ -223,7 +223,7 @@ class OSQPInterface(SolverInterface): # solver settings stgs_statically_allocated = True - stgs_set_function = {'name': 'osqp_update_%s', 'ptr_name': 'workspace'} + stgs_set_function = {'name': 'osqp_update_{setting_name}', 'ptr_name': 'workspace'} stgs_reset_function = {'name': 'osqp_set_default_settings', 'ptr_name': 'settings'} stgs_names = ['rho', 'max_iter', 'eps_abs', 'eps_rel', 'eps_prim_inf', 'eps_dual_inf', 'alpha', 'scaled_termination', 'check_termination', 'warm_start', @@ -383,7 +383,7 @@ class SCSInterface(SolverInterface): primal_residual = 'Scs_Info.res_pri', dual_residual = 'Scs_Info.res_dual', primal_solution = 'scs_x', - dual_solution = 'scs_%s' + dual_solution = 'scs_{dual_var_name}' ) # solution vectors statically allocated @@ -495,88 +495,89 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, f.write(setup_text) def declare_workspace(self, f, prefix) -> None: - f.write('\n// SCS matrix A\n') - write_struct_prot(f, '%sscs_A' % prefix, 'ScsMatrix') - f.write('\n// Struct containing SCS data\n') - write_struct_prot(f, '%sScs_D' % prefix, 'ScsData') + f.write(f'\n// SCS matrix A\n') + write_struct_prot(f, f'{prefix}scs_A', 'ScsMatrix') + f.write(f'\n// Struct containing SCS data\n') + write_struct_prot(f, f'{prefix}Scs_D', 'ScsData') if self.canon_constants['qsize'] > 0: - f.write('\n// SCS array of SOC dimensions\n') - write_vec_prot(f, self.canon_constants['q'], '%sscs_q' % prefix, 'cpg_int') - f.write('\n// Struct containing SCS cone data\n') - write_struct_prot(f, '%sScs_K' % prefix, 'ScsCone') - f.write('\n// Struct containing SCS settings\n') - write_struct_prot(f, prefix + 'Canon_Settings', 'ScsSettings') - f.write('\n// SCS solution\n') - write_vec_prot(f, np.zeros(self.canon_constants['n']), '%sscs_x' % prefix, 'cpg_float') - write_vec_prot(f, np.zeros(self.canon_constants['m']), '%sscs_y' % prefix, 'cpg_float') - write_vec_prot(f, np.zeros(self.canon_constants['m']), '%sscs_s' % prefix, 'cpg_float') - f.write('\n// Struct containing SCS solution\n') - write_struct_prot(f, '%sScs_Sol' % prefix, 'ScsSolution') - f.write('\n// Struct containing SCS information\n') - write_struct_prot(f, '%sScs_Info' % prefix, 'ScsInfo') - f.write('\n// Pointer to struct containing SCS workspace\n') - write_struct_prot(f, '%sScs_Work' % prefix, 'ScsWork*') + f.write(f'\n// SCS array of SOC dimensions\n') + write_vec_prot(f, self.canon_constants['q'], f'{prefix}scs_q', 'cpg_int') + f.write(f'\n// Struct containing SCS cone data\n') + write_struct_prot(f, f'{prefix}Scs_K', 'ScsCone') + f.write(f'\n// Struct containing SCS settings\n') + write_struct_prot(f, f'{prefix}Canon_Settings', 'ScsSettings') + f.write(f'\n// SCS solution\n') + write_vec_prot(f, np.zeros(self.canon_constants['n']), f'{prefix}scs_x', 'cpg_float') + write_vec_prot(f, np.zeros(self.canon_constants['m']), f'{prefix}scs_y', 'cpg_float') + write_vec_prot(f, np.zeros(self.canon_constants['m']), f'{prefix}scs_s', 'cpg_float') + f.write(f'\n// Struct containing SCS solution\n') + write_struct_prot(f, f'{prefix}Scs_Sol', 'ScsSolution') + f.write(f'\n// Struct containing SCS information\n') + write_struct_prot(f, f'{prefix}Scs_Info', 'ScsInfo') + f.write(f'\n// Pointer to struct containing SCS workspace\n') + write_struct_prot(f, f'{prefix}Scs_Work', 'ScsWork*') + def define_workspace(self, f, prefix) -> None: - f.write('\n// SCS matrix A\n') + f.write(f'\n// SCS matrix A\n') scs_A_fields = ['x', 'i', 'p', 'm', 'n'] scs_A_casts = ['(cpg_float *) ', '(cpg_int *) ', '(cpg_int *) ', '', ''] - scs_A_values = ['&%scanon_A_x' % prefix, '&%scanon_A_i' % prefix, - '&%scanon_A_p' % prefix, str(self.canon_constants['m']), + scs_A_values = [f'&{prefix}canon_A_x', f'&{prefix}canon_A_i', + f'&{prefix}canon_A_p', str(self.canon_constants['m']), str(self.canon_constants['n'])] - write_struct_def(f, scs_A_fields, scs_A_casts, scs_A_values, '%sScs_A' % prefix, 'ScsMatrix') + write_struct_def(f, scs_A_fields, scs_A_casts, scs_A_values, f'{prefix}Scs_A', 'ScsMatrix') - f.write('\n// Struct containing SCS data\n') + f.write(f'\n// Struct containing SCS data\n') scs_d_fields = ['m', 'n', 'A', 'P', 'b', 'c'] scs_d_casts = ['', '', '', '', '(cpg_float *) ', '(cpg_float *) '] scs_d_values = [str(self.canon_constants['m']), str(self.canon_constants['n']), - '&%sScs_A' % prefix, 'SCS_NULL', '&%scanon_b' % prefix, '&%scanon_c' % prefix] - write_struct_def(f, scs_d_fields, scs_d_casts, scs_d_values, '%sScs_D' % prefix, 'ScsData') + f'&{prefix}Scs_A', 'SCS_NULL', f'&{prefix}canon_b', f'&{prefix}canon_c'] + write_struct_def(f, scs_d_fields, scs_d_casts, scs_d_values, f'{prefix}Scs_D', 'ScsData') if self.canon_constants['qsize'] > 0: - f.write('\n// SCS array of SOC dimensions\n') - write_vec_def(f, self.canon_constants['q'], '%sscs_q' % prefix, 'cpg_int') - k_field_q_str = '&%sscs_q' % prefix + f.write(f'\n// SCS array of SOC dimensions\n') + write_vec_def(f, self.canon_constants['q'], f'{prefix}scs_q', 'cpg_int') + k_field_q_str = f'&{prefix}scs_q' else: k_field_q_str = 'SCS_NULL' - f.write('\n// Struct containing SCS cone data\n') + f.write(f'\n// Struct containing SCS cone data\n') scs_k_fields = ['z', 'l', 'bu', 'bl', 'bsize', 'q', 'qsize', 's', 'ssize', 'ep', 'ed', 'p', 'psize'] scs_k_casts = ['', '', '(cpg_float *) ', '(cpg_float *) ', '', '(cpg_int *) ', '', '(cpg_int *) ', '', '', '', - '(cpg_float *) ', ''] + '(cpg_float *) ', ''] scs_k_values = [str(self.canon_constants['z']), str(self.canon_constants['l']), 'SCS_NULL', 'SCS_NULL', '0', k_field_q_str, str(self.canon_constants['qsize']), 'SCS_NULL', '0', '0', '0', 'SCS_NULL', '0'] - write_struct_def(f, scs_k_fields, scs_k_casts, scs_k_values, '%sScs_K' % prefix, 'ScsCone') + write_struct_def(f, scs_k_fields, scs_k_casts, scs_k_values, f'{prefix}Scs_K', 'ScsCone') - f.write('\n// Struct containing SCS settings\n') + f.write(f'\n// Struct containing SCS settings\n') scs_stgs_fields = list(self.stgs_names_to_default.keys()) - scs_stgs_casts = ['']*len(scs_stgs_fields) + scs_stgs_casts = [''] * len(scs_stgs_fields) scs_stgs_values = list(self.stgs_names_to_default.values()) - write_struct_def(f, scs_stgs_fields, scs_stgs_casts, scs_stgs_values, prefix + 'Canon_Settings', 'ScsSettings') + write_struct_def(f, scs_stgs_fields, scs_stgs_casts, scs_stgs_values, f'{prefix}Canon_Settings', 'ScsSettings') - f.write('\n// SCS solution\n') - write_vec_def(f, np.zeros(self.canon_constants['n']), '%sscs_x' % prefix, 'cpg_float') - write_vec_def(f, np.zeros(self.canon_constants['m']), '%sscs_y' % prefix, 'cpg_float') - write_vec_def(f, np.zeros(self.canon_constants['m']), '%sscs_s' % prefix, 'cpg_float') + f.write(f'\n// SCS solution\n') + write_vec_def(f, np.zeros(self.canon_constants['n']), f'{prefix}scs_x', 'cpg_float') + write_vec_def(f, np.zeros(self.canon_constants['m']), f'{prefix}scs_y', 'cpg_float') + write_vec_def(f, np.zeros(self.canon_constants['m']), f'{prefix}scs_s', 'cpg_float') - f.write('\n// Struct containing SCS solution\n') + f.write(f'\n// Struct containing SCS solution\n') scs_sol_fields = ['x', 'y', 's'] scs_sol_casts = ['(cpg_float *) ', '(cpg_float *) ', '(cpg_float *) '] - scs_sol_values = ['&%sscs_x' % prefix, '&%sscs_y' % prefix, '&%sscs_s' % prefix] - write_struct_def(f, scs_sol_fields, scs_sol_casts, scs_sol_values, '%sScs_Sol' % prefix, 'ScsSolution') + scs_sol_values = [f'&{prefix}scs_x', f'&{prefix}scs_y', f'&{prefix}scs_s'] + write_struct_def(f, scs_sol_fields, scs_sol_casts, scs_sol_values, f'{prefix}Scs_Sol', 'ScsSolution') - f.write('\n// Struct containing SCS information\n') + f.write(f'\n// Struct containing SCS information\n') scs_info_fields = ['iter', 'status', 'status_val', 'scale_updates', 'pobj', 'dobj', 'res_pri', 'res_dual', - 'gap', 'res_infeas', 'res_unbdd_a', 'res_unbdd_p', 'comp_slack', 'setup_time', 'solve_time', - 'scale', 'rejected_accel_steps', 'accepted_accel_steps', 'lin_sys_time', 'cone_time', - 'accel_time'] - scs_info_casts = ['']*len(scs_info_fields) + 'gap', 'res_infeas', 'res_unbdd_a', 'res_unbdd_p', 'comp_slack', 'setup_time', 'solve_time', + 'scale', 'rejected_accel_steps', 'accepted_accel_steps', 'lin_sys_time', 'cone_time', + 'accel_time'] + scs_info_casts = [''] * len(scs_info_fields) scs_info_values = ['0', '"unknown"', '0', '0', '0', '0', '99', '99', '99', '99', '99', '99', '99', '0', '0', - '1', '0', '0', '0', '0', '0'] - write_struct_def(f, scs_info_fields, scs_info_casts, scs_info_values, '%sScs_Info' % prefix, 'ScsInfo') + '1', '0', '0', '0', '0', '0'] + write_struct_def(f, scs_info_fields, scs_info_casts, scs_info_values, f'{prefix}Scs_Info', 'ScsInfo') - f.write('\n// Pointer to struct containing SCS workspace\n') - f.write('ScsWork* %sScs_Work = 0;\n' % prefix) + f.write(f'\n// Pointer to struct containing SCS workspace\n') + f.write(f'ScsWork* {prefix}Scs_Work = 0;\n') class ECOSInterface(SolverInterface): @@ -604,8 +605,8 @@ class ECOSInterface(SolverInterface): primal_residual = 'ecos_workspace->info->pres', dual_residual = 'ecos_workspace->info->dres', primal_solution = 'ecos_workspace->x', - dual_solution = 'ecos_workspace->%s', - settings = 'ecos_workspace->stgs->%s' + dual_solution = 'ecos_workspace->{dual_var_name}', + settings = 'ecos_workspace->stgs->{setting_name}' ) # solution vectors statically allocated @@ -647,38 +648,28 @@ def __init__(self, data, p_prob, enable_settings): self.parameter_update_structure = { 'init': ParameterUpdateLogic( - update_pending_logic = UpdatePendingLogic([], extra_condition = '!{prefix}ecos_workspace', functions_if_false = ['AbcGh']), - function_call = '{prefix}ecos_workspace = ECOS_setup(%d, %d, %d, %d, %d, %s, %d, ' - '{prefix}Canon_Params_conditioning.G->x, {prefix}Canon_Params_conditioning.G->p, {prefix}Canon_Params_conditioning.G->i, ' - '%s, %s, %s, {prefix}Canon_Params_conditioning.c, {prefix}Canon_Params_conditioning.h, %s)' % ( - canon_constants['n'], - canon_constants['m'], - canon_constants['p'], - canon_constants['l'], - canon_constants['n_cones'], - '0' if canon_constants['n_cones'] == 0 else '(int *) &{prefix}ecos_q', - canon_constants['e'], - '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.A->x', - '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.A->p', - '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.A->i', - '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.b' - ), + update_pending_logic=UpdatePendingLogic([], extra_condition='!{prefix}ecos_workspace', functions_if_false=['AbcGh']), + function_call=f'{{prefix}}ecos_workspace = ECOS_setup({canon_constants["n"]}, {canon_constants["m"]}, {canon_constants["p"]}, {canon_constants["l"]}, {canon_constants["n_cones"]}' + f', {"0" if canon_constants["n_cones"] == 0 else "(int *) &{prefix}ecos_q"}, {canon_constants["e"]}' + f', {{prefix}}Canon_Params_conditioning.G->x, {{prefix}}Canon_Params_conditioning.G->p, {{prefix}}Canon_Params_conditioning.G->i' + f', {"0" if canon_constants["p"] == 0 else "{prefix}Canon_Params_conditioning.A->x"}' + f', {"0" if canon_constants["p"] == 0 else "{prefix}Canon_Params_conditioning.A->p"}' + f', {"0" if canon_constants["p"] == 0 else "{prefix}Canon_Params_conditioning.A->i"}' + f', {{prefix}}Canon_Params_conditioning.c, {{prefix}}Canon_Params_conditioning.h' + f', {"0" if canon_constants["p"] == 0 else "{prefix}Canon_Params_conditioning.b"})' ), 'AbcGh': ParameterUpdateLogic( - update_pending_logic = UpdatePendingLogic(['A', 'b', 'G'], '||', ['c', 'h']), - function_call = 'ECOS_updateData({prefix}ecos_workspace, {prefix}Canon_Params_conditioning.G->x, %s, ' - '{prefix}Canon_Params_conditioning.c, {prefix}Canon_Params_conditioning.h, %s)' % ( - '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.A->x', - '0' if canon_constants['p'] == 0 else '{prefix}Canon_Params_conditioning.b' - ) + update_pending_logic=UpdatePendingLogic(['A', 'b', 'G'], '||', ['c', 'h']), + function_call=f'ECOS_updateData({{prefix}}ecos_workspace, {{prefix}}Canon_Params_conditioning.G->x, {"0" if canon_constants["p"] == 0 else "{prefix}Canon_Params_conditioning.A->x"}' + f', {{prefix}}Canon_Params_conditioning.c, {{prefix}}Canon_Params_conditioning.h, {"0" if canon_constants["p"] == 0 else "{prefix}Canon_Params_conditioning.b"})' ), 'c': ParameterUpdateLogic( - update_pending_logic = UpdatePendingLogic(['c']), - function_call = 'for (i=0; i<%d; i++) {{ ecos_updateDataEntry_c({prefix}ecos_workspace, i, {prefix}Canon_Params_conditioning.c[i]); }}' % canon_constants['n'] + update_pending_logic=UpdatePendingLogic(['c']), + function_call=f'for (i=0; i<{canon_constants["n"]}; i++) {{{{ ecos_updateDataEntry_c({{prefix}}ecos_workspace, i, {{prefix}}Canon_Params_conditioning.c[i]); }}}}' ), 'h': ParameterUpdateLogic( - update_pending_logic = UpdatePendingLogic(['h']), - function_call = 'for (i=0; i<%d; i++) {{ ecos_updateDataEntry_h({prefix}ecos_workspace, i, {prefix}Canon_Params_conditioning.h[i]); }}' % canon_constants['m'] + update_pending_logic=UpdatePendingLogic(['h']), + function_call=f'for (i=0; i<{canon_constants["m"]}; i++) {{{{ ecos_updateDataEntry_h({{prefix}}ecos_workspace, i, {{prefix}}Canon_Params_conditioning.h[i]); }}}}' ) } @@ -761,25 +752,26 @@ def generate_code(self, code_dir, solver_code_dir, cvxpygen_directory, def declare_workspace(self, f, prefix) -> None: f.write('\n// Struct containing solver settings\n') - write_struct_prot(f, prefix + 'Canon_Settings', 'Canon_Settings_t') + write_struct_prot(f, f'{prefix}Canon_Settings', 'Canon_Settings_t') if self.canon_constants['n_cones'] > 0: f.write('\n// ECOS array of SOC dimensions\n') - write_vec_prot(f, self.canon_constants['q'], '%secos_q' % prefix, 'cpg_int') + write_vec_prot(f, self.canon_constants['q'], f'{prefix}ecos_q', 'cpg_int') f.write('\n// ECOS workspace\n') - f.write('extern pwork* %secos_workspace;\n' % prefix) + f.write(f'extern pwork* {prefix}ecos_workspace;\n') f.write('\n// ECOS exit flag\n') - f.write('extern cpg_int %secos_flag;\n' % prefix) + f.write(f'extern cpg_int {prefix}ecos_flag;\n') def define_workspace(self, f, prefix) -> None: f.write('\n// Struct containing solver settings\n') - f.write('Canon_Settings_t %sCanon_Settings = {\n' % prefix) + f.write(f'Canon_Settings_t {prefix}Canon_Settings = {{\n') for name, default in self.stgs_names_to_default.items(): - f.write('.%s = %s,\n' % (name, default)) + f.write(f'.{name} = {default},\n') f.write('};\n') if self.canon_constants['n_cones'] > 0: f.write('\n// ECOS array of SOC dimensions\n') - write_vec_def(f, self.canon_constants['q'], '%secos_q' % prefix, 'cpg_int') + write_vec_def(f, self.canon_constants['q'], f'{prefix}ecos_q', 'cpg_int') f.write('\n// ECOS workspace\n') - f.write('pwork* %secos_workspace = 0;\n' % prefix) + f.write(f'pwork* {prefix}ecos_workspace = 0;\n') f.write('\n// ECOS exit flag\n') - f.write('cpg_int %secos_flag = -99;\n' % prefix) + f.write(f'cpg_int {prefix}ecos_flag = -99;\n') + diff --git a/cvxpygen/utils.py b/cvxpygen/utils.py index 10ca3bf..d13f045 100644 --- a/cvxpygen/utils.py +++ b/cvxpygen/utils.py @@ -19,14 +19,14 @@ def write_vec_def(f, vec, name, typ): """ Write vector to file """ - f.write('%s %s[%d] = {\n' % (typ, name, len(vec))) + f.write(f'{typ} {name}[{len(vec)}] = {{\n') # Write vector components for i in range(len(vec)): if typ == 'cpg_float': f.write('(cpg_float)%.20f,\n' % vec[i]) else: - f.write('%i,\n' % vec[i]) + f.write(f'{vec[i]},\n') f.write('};\n') @@ -35,7 +35,7 @@ def write_vec_prot(f, vec, name, typ): """ Write vector to file """ - f.write('extern %s %s[%d];\n' % (typ, name, len(vec))) + f.write(f'extern {typ} {name}[{len(vec)}];\n') def write_mat_def(f, mat, name): @@ -46,21 +46,21 @@ def write_mat_def(f, mat, name): write_vec_def(f, mat['p'], name + '_p', 'cpg_int') write_vec_def(f, mat['x'], name + '_x', 'cpg_float') - f.write('cpg_csc %s = {' % name) - f.write('%d, ' % mat['nzmax']) - f.write('%d, ' % mat['m']) - f.write('%d, ' % mat['n']) - f.write('%s_p, ' % name) - f.write('%s_i, ' % name) - f.write('%s_x, ' % name) - f.write('%d};\n' % mat['nz']) + f.write(f'cpg_csc {name} = {{') + f.write(f'{mat["nzmax"]}, ') + f.write(f'{mat["m"]}, ') + f.write(f'{mat["n"]}, ') + f.write(f'{name}_p, ') + f.write(f'{name}_i, ') + f.write(f'{name}_x, ') + f.write(f'{mat["nz"]}}};\n') def write_mat_prot(f, mat, name): """ Write sparse matrix (scipy compressed sparse column) to file """ - f.write('extern cpg_csc %s;\n' % name) + f.write(f'extern cpg_csc {name};\n') def write_dense_mat_def(f, mat, name): @@ -68,7 +68,7 @@ def write_dense_mat_def(f, mat, name): Write dense matrix to file """ - f.write('cpg_float %s[%d] = {\n' % (name, mat.size)) + f.write(f'cpg_float {name}[{mat.size}] = {{\n') # represent matrix as vector (Fortran style) for j in range(mat.shape[1]): @@ -83,7 +83,7 @@ def write_dense_mat_prot(f, mat, name): Write dense matrix to file """ - f.write("extern cpg_float cpg_%s[%d];\n" % (name, mat.size)) + f.write(f'extern cpg_float cpg_{name}[{mat.size}];\n') def write_description(f, file_type, content): @@ -96,10 +96,11 @@ def write_description(f, file_type, content): else: comment_str = '/*' now = datetime.now() - f.write('\n%s\n' % comment_str) - f.write('Auto-generated by CVXPYgen %s.\n' % now.strftime("on %B %d, %Y at %H:%M:%S")) - f.write('Content: %s.\n' % content) - f.write('%s\n\n' % comment_str[::-1]) + f.write(f'\n{comment_str}\n') + f.write(f'Auto-generated by CVXPYgen {now.strftime("on %B %d, %Y at %H:%M:%S")}.\n') + f.write(f'Content: {content}.\n') + f.write(f'{comment_str[::-1]}\n\n') + def replace_in_file(filepath, replacements): @@ -180,8 +181,8 @@ def write_problem_summary(name_to_shape, name_to_size): elif len(sh) == 1: shape_str = str(sh[0]) else: - shape_str = '%d by %d (%d)' % (sh[0], sh[1], name_to_size[n]) - string += ' \n %s\n %s\n \n' % (n, shape_str) + shape_str = f'{sh[0]} by {sh[1]} ({name_to_size[n]})' + string += f' \n {n}\n {shape_str}\n \n' return string @@ -192,7 +193,7 @@ def write_canonicalize_explicit(f, p_id, s, mapping, user_p_col_to_name_usp, use sign_to_str = {1: '', -1: '-'} - for row in range(len(mapping.indptr)-1): + for row in range(len(mapping.indptr) - 1): expr = '' expr_is_const = True data = mapping.data[mapping.indptr[row]:mapping.indptr[row + 1]] @@ -204,22 +205,22 @@ def write_canonicalize_explicit(f, p_id, s, mapping, user_p_col_to_name_usp, use expr_is_const = False if user_p_name_to_size_usp[user_name] == 1: if abs(datum) == 1: - ex = '(%s%sCPG_Params.%s)+' % (sign_to_str[datum], prefix, user_name) + ex = f'({sign_to_str[datum]}{prefix}CPG_Params.{user_name})+' else: - ex = '(%.20f*%sCPG_Params.%s)+' % (datum, prefix, user_name) + ex = f'(%.20f*{prefix}CPG_Params.{user_name})+' % datum else: if abs(datum) == 1: - ex = '(%s%sCPG_Params.%s[%d])+' % (sign_to_str[datum], prefix, user_name, col - user_p_col) + ex = f'({sign_to_str[datum]}{prefix}CPG_Params.{user_name}[%d])+' % (col - user_p_col) else: - ex = '(%.20f*%sCPG_Params.%s[%d])+' % (datum, prefix, user_name, col - user_p_col) + ex = f'(%.20f*{prefix}CPG_Params.{user_name}[%d])+' % (datum, col - user_p_col) break expr += ex expr = expr[:-1] - if data.size > 0 and expr_is_const is False: + if data.size > 0 and not expr_is_const: if p_id == 'd': - f.write(' %sCanon_Params.d = %s;\n' % (prefix, expr)) + f.write(f' {prefix}Canon_Params.d = {expr};\n') else: - f.write(' %sCanon_Params.%s%s[%d] = %s;\n' % (prefix, p_id, s, row, expr)) + f.write(f' {prefix}Canon_Params.{p_id}{s}[%d] = {expr};\n' % row) def write_canonicalize(f, canon_name, s, mapping, prefix): @@ -227,14 +228,13 @@ def write_canonicalize(f, canon_name, s, mapping, prefix): Write function to compute canonical parameter value """ - f.write(' for(i=0; i<%d; i++){\n' % mapping.shape[0]) - f.write(' %sCanon_Params.%s%s[i] = 0;\n' % (prefix, canon_name, s)) - f.write(' for(j=%scanon_%s_map.p[i]; j<%scanon_%s_map.p[i+1]; j++){\n' % - (prefix, canon_name, prefix, canon_name)) - f.write(' %sCanon_Params.%s%s[i] += %scanon_%s_map.x[j]*%scpg_params_vec[%scanon_%s_map.i[j]];\n' % - (prefix, canon_name, s, prefix, canon_name, prefix, prefix, canon_name)) - f.write(' }\n') - f.write(' }\n') + f.write(f' for(i=0; i<{mapping.shape[0]}; i++){{\n') + f.write(f' {prefix}Canon_Params.{canon_name}{s}[i] = 0;\n') + f.write(f' for(j={prefix}canon_{canon_name}_map.p[i]; j<{prefix}canon_{canon_name}_map.p[i+1]; j++){{\n') + f.write(f' {prefix}Canon_Params.{canon_name}{s}[i] += {prefix}canon_{canon_name}_map.x[j]*{prefix}cpg_params_vec[{prefix}canon_{canon_name}_map.i[j]];\n') + f.write(f' }}\n') + f.write(f' }}\n') + def write_param_def(f, param, name, prefix, suffix): @@ -243,11 +243,11 @@ def write_param_def(f, param, name, prefix, suffix): """ if not param_is_empty(param): if name.isupper(): - write_mat_def(f, param, '%scanon_%s%s' % (prefix, name, suffix)) + write_mat_def(f, param, f'{prefix}canon_{name}{suffix}') elif name == 'd': - f.write('cpg_float %scanon_d%s = %.20f;\n' % (prefix, suffix, param[0])) + f.write(f'cpg_float {prefix}canon_d{suffix} = %.20f;\n' % param[0]) else: - write_vec_def(f, param, '%scanon_%s%s' % (prefix, name, suffix), 'cpg_float') + write_vec_def(f, param, f'{prefix}canon_{name}{suffix}', 'cpg_float') f.write('\n') @@ -257,11 +257,11 @@ def write_param_prot(f, param, name, prefix, suffix): """ if not param_is_empty(param): if name.isupper(): - write_mat_prot(f, param, '%scanon_%s%s' % (prefix, name, suffix)) + write_mat_prot(f, param, f'{prefix}canon_{name}{suffix}') elif name == 'd': - f.write('extern cpg_float %scanon_d%s;\n' % (prefix, suffix)) + f.write(f'extern cpg_float {prefix}canon_d{suffix};\n') else: - write_vec_prot(f, param, '%scanon_%s%s' % (prefix, name, suffix), 'cpg_float') + write_vec_prot(f, param, f'{prefix}canon_{name}{suffix}', 'cpg_float') def write_struct_def(f, fields, casts, values, name, typ): @@ -269,13 +269,13 @@ def write_struct_def(f, fields, casts, values, name, typ): Write structure to file """ - f.write('%s %s = {\n' % (typ, name)) + f.write(f'{typ} {name} = {{\n') # write structure fields for field, cast, value in zip(fields, casts, values): if value in ['0', 'SCS_NULL']: cast = '' - f.write('.%s = %s%s,\n' % (field, cast, value)) + f.write(f'.{field} = {cast}{value},\n') f.write('};\n') @@ -285,7 +285,7 @@ def write_struct_prot(f, name, typ): Write structure to file """ - f.write("extern %s %s;\n" % (typ, name)) + f.write(f'extern {typ} {name};\n') def extend_functions_if_false(pus, functions_if_false): @@ -343,14 +343,14 @@ def analyze_pus(pus, p_id_to_changes): functions_called.remove(function) continue else: - raise ValueError('Operator "%s" not implemented.' % operator) + raise ValueError(f'Operator "{operator}" not implemented.') if functions_if_false is None: continue for f in functions_if_false: if f not in functions: - raise ValueError('"%s" is not part of parameter update structure.' % f) + raise ValueError(f'"{f}" is not part of parameter update structure.') functions_secondary.extend(functions_if_false) return functions_called-set(functions_secondary), functions-functions_called @@ -368,7 +368,7 @@ def write_update_structure(f, configuration, pus, functions, functions_never_cal if functions is None: return - write_else = depth > 0 and len(set(functions)-set(functions_never_called)) > 0 + write_else = depth > 0 and len(set(functions) - set(functions_never_called)) > 0 if write_else: f.write(' else {\n') @@ -379,27 +379,25 @@ def write_update_structure(f, configuration, pus, functions, functions_never_cal up_logic = logic.update_pending_logic if function not in functions_never_called: - f.write('%sif (%s%s%s) {\n' % ( - ' '*(depth+1), - '%s ' % up_logic.extra_condition.format(prefix=configuration.prefix) if up_logic.extra_condition is not None else '', - '%s ' % operator_map.get(up_logic.extra_condition_operator, '||') if up_logic.extra_condition is not None and len(up_logic.parameters_outdated) > 0 else '', - (' %s ' % operator_map.get(up_logic.operator, '&&')).join([ - '%sCanon_Outdated.%s' % (configuration.prefix, p) for p in up_logic.parameters_outdated - ]) - )) - f.write('%s%s;\n' % (' '*(depth+2), logic.function_call.format(prefix=configuration.prefix))) - f.write('%s}' % (' '*(depth+1))) + extra_condition = f'{up_logic.extra_condition.format(prefix=configuration.prefix)} ' if up_logic.extra_condition is not None else '' + extra_condition_operator = f'{operator_map.get(up_logic.extra_condition_operator, "||")} ' if up_logic.extra_condition is not None and len(up_logic.parameters_outdated) > 0 else '' + parameters_outdated = f' {operator_map.get(up_logic.operator, "&&").join([f"{configuration.prefix}Canon_Outdated.{p}" for p in up_logic.parameters_outdated])}' + + f.write(f'{" "*(depth+1)}if ({extra_condition}{extra_condition_operator}{parameters_outdated}) {{\n') + f.write(f'{" "*(depth+2)}{logic.function_call.format(prefix=configuration.prefix)};\n') + f.write(f'{" "*(depth+1)}}}') new_depth = depth + 1 else: new_depth = depth * 1 write_update_structure(f, configuration, pus, up_logic.functions_if_false, functions_never_called, new_depth) - + if function not in functions_never_called: f.write('\n') if write_else: - f.write('%s}' % (' '*depth)) + f.write(f'{" " * depth}}}') + def write_workspace_def(f, configuration, variable_info, dual_variable_info, parameter_info, parameter_canon, solver_interface): @@ -418,20 +416,20 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par user_casts.append('') user_values.append('%.20f' % value) else: - write_vec_def(f, value, configuration.prefix + 'cpg_' + name, 'cpg_float') + write_vec_def(f, value, f'{configuration.prefix}cpg_{name}', 'cpg_float') f.write('\n') user_casts.append('(cpg_float *) ') - user_values.append('&' + configuration.prefix + 'cpg_' + name) + user_values.append(f'&{configuration.prefix}cpg_{name}') f.write('// Struct containing all user-defined parameters\n') - write_struct_def(f, names, user_casts, user_values, '%sCPG_Params' % configuration.prefix, 'CPG_Params_t') + write_struct_def(f, names, user_casts, user_values, f'{configuration.prefix}CPG_Params', 'CPG_Params_t') f.write('\n') else: f.write('\n// Vector containing flattened user-defined parameters\n') - write_vec_def(f, parameter_info.flat_usp, '%scpg_params_vec' % configuration.prefix, 'cpg_float') + write_vec_def(f, parameter_info.flat_usp, f'{configuration.prefix}cpg_params_vec', 'cpg_float') f.write('\n// Sparse mappings from user-defined to canonical parameters\n') for p_id, mapping in parameter_canon.p_id_to_mapping.items(): if parameter_canon.p_id_to_changes[p_id]: - write_mat_def(f, csc_to_dict(mapping), '%scanon_%s_map' % (configuration.prefix, p_id)) + write_mat_def(f, csc_to_dict(mapping), f'{configuration.prefix}canon_{p_id}_map') f.write('\n') p_ids = list(parameter_canon.p.keys()) @@ -469,22 +467,21 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if solver_interface.inmemory_preconditioning: struct_values_conditioning.append('%.20f' % p) else: - struct_values.append('&%scanon_%s' % (configuration.prefix, p_id)) + struct_values.append(f'&{configuration.prefix}canon_{p_id}') if solver_interface.inmemory_preconditioning: - struct_values_conditioning.append('&%scanon_%s_conditioning' % (configuration.prefix, p_id)) + struct_values_conditioning.append(f'&{configuration.prefix}canon_{p_id}_conditioning') - write_struct_def(f, p_ids, canon_casts, struct_values, '%sCanon_Params' % configuration.prefix, 'Canon_Params_t') + write_struct_def(f, p_ids, canon_casts, struct_values, f'{configuration.prefix}Canon_Params', 'Canon_Params_t') f.write('\n') if solver_interface.inmemory_preconditioning: - write_struct_def(f, p_ids, canon_casts, struct_values_conditioning, '%sCanon_Params_conditioning' % configuration.prefix, - 'Canon_Params_t') + write_struct_def(f, p_ids, canon_casts, struct_values_conditioning, f'{configuration.prefix}Canon_Params_conditioning', 'Canon_Params_t') f.write('\n') # Boolean struct for outdated parameter flags f.write('// Struct containing flags for outdated canonical parameters\n') - f.write('Canon_Outdated_t %sCanon_Outdated = {\n' % configuration.prefix) + f.write(f'Canon_Outdated_t {configuration.prefix}Canon_Outdated = {{\n') for p_id in parameter_canon.p.keys(): - f.write('.%s = 0,\n' % p_id) + f.write(f'.{p_id} = 0,\n') f.write('};\n\n') prim_cast = [] @@ -512,8 +509,8 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: CPG_Prim_values.append('&' + configuration.prefix + name) else: - CPG_Prim_values.append('&%s%s + %d' % (result_prefix, solver_interface.ws_ptrs.primal_solution, offset)) - write_struct_def(f, CPG_Prim_fields, prim_cast, CPG_Prim_values, '%sCPG_Prim' % configuration.prefix, 'CPG_Prim_t') + CPG_Prim_values.append(f'&{result_prefix}{solver_interface.ws_ptrs.primal_solution} + {offset}') + write_struct_def(f, CPG_Prim_fields, prim_cast, CPG_Prim_values, f'{configuration.prefix}CPG_Prim', 'CPG_Prim_t') if len(dual_variable_info.name_to_init) > 0: dual_cast = [] @@ -540,28 +537,26 @@ def write_workspace_def(f, configuration, variable_info, dual_variable_info, par if not solver_interface.sol_statically_allocated: CPG_Dual_values.append('&' + configuration.prefix + name) else: - CPG_Dual_values.append('&%s%s + %d' % (result_prefix, solver_interface.ws_ptrs.dual_solution, offset) % vec) - write_struct_def(f, CPG_Dual_fields, dual_cast, CPG_Dual_values, '%sCPG_Dual' % configuration.prefix, - 'CPG_Dual_t') + CPG_Dual_values.append(f'&{result_prefix}{solver_interface.ws_ptrs.dual_solution.format(dual_var_name=vec)} + {offset}') + write_struct_def(f, CPG_Dual_fields, dual_cast, CPG_Dual_values, f'{configuration.prefix}CPG_Dual', 'CPG_Dual_t') f.write('\n// Struct containing solver info\n') CPG_Info_fields = ['obj_val', 'iter', 'status', 'pri_res', 'dua_res'] CPG_Info_values = ['0', '0', ('0' if solver_interface.status_is_int else '"unknown"'), '0', '0'] info_cast = ['', '', '', '', ''] - write_struct_def(f, CPG_Info_fields, info_cast, CPG_Info_values, '%sCPG_Info' % configuration.prefix, 'CPG_Info_t') + write_struct_def(f, CPG_Info_fields, info_cast, CPG_Info_values, f'{configuration.prefix}CPG_Info', 'CPG_Info_t') f.write('\n// Struct containing solution and info\n') if len(dual_variable_info.name_to_init) > 0: CPG_Result_fields = ['prim', 'dual', 'info'] result_cast = ['', '', ''] - CPG_Result_values = ['&%sCPG_Prim' % configuration.prefix, '&%sCPG_Dual' % configuration.prefix, - '&%sCPG_Info' % configuration.prefix] + CPG_Result_values = [f'&{configuration.prefix}CPG_Prim', f'&{configuration.prefix}CPG_Dual', + f'&{configuration.prefix}CPG_Info'] else: CPG_Result_fields = ['prim', 'info'] result_cast = ['', ''] - CPG_Result_values = ['&%sCPG_Prim' % configuration.prefix, '&%sCPG_Info' % configuration.prefix] - write_struct_def(f, CPG_Result_fields, result_cast, CPG_Result_values, '%sCPG_Result' % configuration.prefix, - 'CPG_Result_t') + CPG_Result_values = [f'&{configuration.prefix}CPG_Prim', f'&{configuration.prefix}CPG_Info'] + write_struct_def(f, CPG_Result_fields, result_cast, CPG_Result_values, f'{configuration.prefix}CPG_Result', 'CPG_Result_t') if not solver_interface.ws_statically_allocated_in_solver_code: solver_interface.define_workspace(f, configuration.prefix) @@ -574,14 +569,14 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa write_description(f, 'c', 'Type definitions and variable declarations') for header_file in solver_interface.header_files: - f.write('#include "%s"\n' % header_file) + f.write(f'#include "{header_file}"\n') # definition safeguard f.write('\n#ifndef CPG_TYPES_H\n') f.write('# define CPG_TYPES_H\n\n') - f.write('typedef %s cpg_float;\n' % solver_interface.numeric_types['float']) - f.write('typedef %s cpg_int;\n\n' % solver_interface.numeric_types['int']) + f.write(f'typedef {solver_interface.numeric_types["float"]} cpg_float;\n') + f.write(f'typedef {solver_interface.numeric_types["int"]} cpg_int;\n\n') # struct definitions f.write('// Compressed sparse column matrix\n') @@ -604,26 +599,26 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - f.write(' cpg_float %s // Your parameter %s\n' % ((s+name+';').ljust(9), name)) + f.write(f' cpg_float {(s+name+";").ljust(9)} // Your parameter {name}\n') f.write('} CPG_Params_t;\n\n') f.write('// Canonical parameters\n') f.write('typedef struct {\n') for p_id in parameter_canon.p.keys(): if p_id.isupper(): - f.write(' cpg_csc *%s // Canonical parameter %s\n' % ((p_id+';').ljust(8), p_id)) + f.write(f' cpg_csc *{(p_id+";").ljust(8)} // Canonical parameter {p_id}\n') else: if p_id == 'd': s = '' else: s = '*' - f.write(' cpg_float %s // Canonical parameter %s\n' % ((s+p_id+';').ljust(9), p_id)) + f.write(f' cpg_float {(s+p_id+";").ljust(9)} // Canonical parameter {p_id}\n') f.write('} Canon_Params_t;\n\n') f.write('// Flags indicating outdated canonical parameters\n') f.write('typedef struct {\n') for p_id in parameter_canon.p.keys(): - f.write(' int %s // Bool, if canonical parameter %s outdated\n' % ((p_id + ';').ljust(8), p_id)) + f.write(f' int {(p_id + ";").ljust(8)} // Bool, if canonical parameter {p_id} outdated\n') f.write('} Canon_Outdated_t;\n\n') f.write('// Primal solution\n') @@ -633,7 +628,7 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - f.write(' cpg_float %s // Your variable %s\n' % ((s + name + ';').ljust(9), name)) + f.write(f' cpg_float {(s + name + ";").ljust(9)} // Your variable {name}\n') f.write('} CPG_Prim_t;\n\n') if len(dual_variable_info.name_to_init) > 0: @@ -644,15 +639,14 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - f.write(' cpg_float %s // Your dual variable for constraint %s\n' % ((s + name + ';').ljust(9), name)) + f.write(f' cpg_float {(s + name + ";").ljust(9)} // Your dual variable for constraint {name}\n') f.write('} CPG_Dual_t;\n\n') f.write('// Solver information\n') f.write('typedef struct {\n') f.write(' cpg_float obj_val; // Objective function value\n') f.write(' cpg_int iter; // Number of iterations\n') - f.write(' %sstatus; // Solver status\n' % - ('cpg_int ' if solver_interface.status_is_int else 'char *')) + f.write(f' {"cpg_int " if solver_interface.status_is_int else "char *"}status; // Solver status\n') f.write(' cpg_float pri_res; // Primal residual\n') f.write(' cpg_float dua_res; // Dual residual\n') f.write('} CPG_Info_t;\n\n') @@ -668,7 +662,7 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('// Solver settings\n') f.write('typedef struct {\n') for name, typ in solver_interface.stgs_names_to_type.items(): - f.write(' %s%s;\n' % (typ.ljust(11), name)) + f.write(f' {typ.ljust(11)}{name};\n') f.write('} Canon_Settings_t;\n\n') f.write('#endif // ifndef CPG_TYPES_H\n') @@ -677,58 +671,57 @@ def write_workspace_prot(f, configuration, variable_info, dual_variable_info, pa f.write('\n// User-defined parameters\n') for name, value in parameter_info.writable.items(): if not is_mathematical_scalar(value): - write_vec_prot(f, value, configuration.prefix+'cpg_'+name, 'cpg_float') + write_vec_prot(f, value, f'{configuration.prefix}cpg_{name}', 'cpg_float') f.write('\n// Struct containing all user-defined parameters\n') - write_struct_prot(f, '%sCPG_Params' % configuration.prefix, 'CPG_Params_t') + write_struct_prot(f, f'{configuration.prefix}CPG_Params', 'CPG_Params_t') else: f.write('\n// Vector containing flattened user-defined parameters\n') - write_vec_prot(f, parameter_info.flat_usp, '%scpg_params_vec' % configuration.prefix, 'cpg_float') + write_vec_prot(f, parameter_info.flat_usp, f'{configuration.prefix}cpg_params_vec', 'cpg_float') f.write('\n// Sparse mappings from user-defined to canonical parameters\n') for p_id, mapping in parameter_canon.p_id_to_mapping.items(): if parameter_canon.p_id_to_changes[p_id]: - write_mat_prot(f, csc_to_dict(mapping), '%scanon_%s_map' % (configuration.prefix, p_id)) + write_mat_prot(f, csc_to_dict(mapping), f'{configuration.prefix}canon_{p_id}_map') f.write('\n// Canonical parameters\n') for p_id, p in parameter_canon.p.items(): if p_id != 'd': - write_param_prot(f, p, p_id, configuration.prefix, '') + write_param_prot(f, p, p_id, f'{configuration.prefix}', '') if solver_interface.inmemory_preconditioning: - write_param_prot(f, p, p_id, configuration.prefix, '_conditioning') + write_param_prot(f, p, p_id, f'{configuration.prefix}', '_conditioning') f.write('\n// Struct containing canonical parameters\n') - write_struct_prot(f, '%sCanon_Params' % configuration.prefix, 'Canon_Params_t') + write_struct_prot(f, f'{configuration.prefix}Canon_Params', 'Canon_Params_t') if solver_interface.inmemory_preconditioning: - write_struct_prot(f, '%sCanon_Params_conditioning' % configuration.prefix, 'Canon_Params_t') + write_struct_prot(f, f'{configuration.prefix}Canon_Params_conditioning', 'Canon_Params_t') f.write('\n// Struct containing flags for outdated canonical parameters\n') - f.write('extern Canon_Outdated_t %sCanon_Outdated;\n' % configuration.prefix) + f.write(f'extern Canon_Outdated_t {configuration.prefix}Canon_Outdated;\n') if any(variable_info.name_to_sym.values()) or not solver_interface.sol_statically_allocated: f.write('\n// User-defined variables\n') for name, value in variable_info.name_to_init.items(): if variable_info.name_to_sym[name] or not solver_interface.sol_statically_allocated: if not is_mathematical_scalar(value): - write_vec_prot(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, - 'cpg_float') + write_vec_prot(f, value.flatten(order='F'), f'{configuration.prefix}cpg_{name}', 'cpg_float') if not solver_interface.sol_statically_allocated: f.write('\n// Dual variables associated with user-defined constraints\n') for name, value in dual_variable_info.name_to_init.items(): if not is_mathematical_scalar(value): - write_vec_prot(f, value.flatten(order='F'), configuration.prefix+'cpg_'+name, 'cpg_float') + write_vec_prot(f, value.flatten(order='F'), f'{configuration.prefix}cpg_{name}', 'cpg_float') f.write('\n// Struct containing primal solution\n') - write_struct_prot(f, '%sCPG_Prim' % configuration.prefix, 'CPG_Prim_t') + write_struct_prot(f, f'{configuration.prefix}CPG_Prim', 'CPG_Prim_t') if len(dual_variable_info.name_to_init) > 0: f.write('\n// Struct containing dual solution\n') - write_struct_prot(f, '%sCPG_Dual' % configuration.prefix, 'CPG_Dual_t') + write_struct_prot(f, f'{configuration.prefix}CPG_Dual', 'CPG_Dual_t') f.write('\n// Struct containing solver info\n') - write_struct_prot(f, '%sCPG_Info' % configuration.prefix, 'CPG_Info_t') + write_struct_prot(f, f'{configuration.prefix}CPG_Info', 'CPG_Info_t') f.write('\n// Struct containing solution and info\n') - write_struct_prot(f, '%sCPG_Result' % configuration.prefix, 'CPG_Result_t') + write_struct_prot(f, f'{configuration.prefix}CPG_Result', 'CPG_Result_t') if not solver_interface.ws_statically_allocated_in_solver_code: solver_interface.declare_workspace(f, configuration.prefix) @@ -754,36 +747,33 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet if configuration.unroll: for user_p_name, Canon_outdated_names in parameter_canon.user_p_name_to_canon_outdated.items(): if parameter_info.name_to_size_usp[user_p_name] == 1: - f.write('void %scpg_update_%s(cpg_float val){\n' % (configuration.prefix, user_p_name)) - f.write(' %sCPG_Params.%s = val;\n' % (configuration.prefix, user_p_name)) + f.write(f'void {configuration.prefix}cpg_update_{user_p_name}(cpg_float val){{\n') + f.write(f' {configuration.prefix}CPG_Params.{user_p_name} = val;\n') else: - f.write('void %scpg_update_%s(cpg_int idx, cpg_float val){\n' % (configuration.prefix, user_p_name)) - f.write(' %sCPG_Params.%s[idx] = val;\n' % (configuration.prefix, user_p_name)) + f.write(f'void {configuration.prefix}cpg_update_{user_p_name}(cpg_int idx, cpg_float val){{\n') + f.write(f' {configuration.prefix}CPG_Params.{user_p_name}[idx] = val;\n') for Canon_outdated_name in Canon_outdated_names: - f.write(' %sCanon_Outdated.%s = 1;\n' % (configuration.prefix, Canon_outdated_name)) + f.write(f' {configuration.prefix}Canon_Outdated.{Canon_outdated_name} = 1;\n') f.write('}\n\n') else: for base_col, name in parameter_info.col_to_name_usp.items(): Canon_outdated_names = parameter_canon.user_p_name_to_canon_outdated[name] if parameter_info.name_to_size_usp[name] == 1: - f.write('void %scpg_update_%s(cpg_float val){\n' % (configuration.prefix, name)) - f.write(' %scpg_params_vec[%d] = val;\n' % (configuration.prefix, base_col)) + f.write(f'void {configuration.prefix}cpg_update_{name}(cpg_float val){{\n') + f.write(f' {configuration.prefix}cpg_params_vec[%d] = val;\n' % base_col) else: - f.write('void %scpg_update_%s(cpg_int idx, cpg_float val){\n' % (configuration.prefix, name)) - f.write(' %scpg_params_vec[idx+%d] = val;\n' % (configuration.prefix, base_col)) + f.write(f'void {configuration.prefix}cpg_update_{name}(cpg_int idx, cpg_float val){{\n') + f.write(f' {configuration.prefix}cpg_params_vec[idx+{base_col}] = val;\n') for Canon_outdated_name in Canon_outdated_names: - f.write(' %sCanon_Outdated.%s = 1;\n' % (configuration.prefix, Canon_outdated_name)) + f.write(f' {configuration.prefix}Canon_Outdated.{Canon_outdated_name} = 1;\n') f.write('}\n\n') f.write('// Map user-defined to canonical parameters\n') for p_id, mapping in parameter_canon.p_id_to_mapping.items(): if parameter_canon.p_id_to_changes[p_id]: - f.write('void %scpg_canonicalize_%s(){\n' % (configuration.prefix, p_id)) - if p_id.isupper(): - s = '->x' - else: - s = '' + f.write(f'void {configuration.prefix}cpg_canonicalize_{p_id}(){{\n') + s = '->x' if p_id.isupper() else '' if configuration.unroll: write_canonicalize_explicit(f, p_id, s, mapping, parameter_info.col_to_name_usp, parameter_info.name_to_size_usp, configuration.prefix) @@ -797,107 +787,94 @@ def write_solve_def(f, configuration, variable_info, dual_variable_info, paramet if solver_interface.ret_prim_func_exists(variable_info): f.write('// Retrieve primal solution in terms of user-defined variables\n') - f.write('void %scpg_retrieve_prim(){\n' % configuration.prefix) + f.write(f'void {configuration.prefix}cpg_retrieve_prim(){{\n') for var_name, indices in variable_info.name_to_indices.items(): if len(indices) == 1: - f.write(' %sCPG_Prim.%s = %s[%d];\n' % (configuration.prefix, var_name, prim_str, indices)) + f.write(f' {configuration.prefix}CPG_Prim.{var_name} = {prim_str}[%d];\n' % indices[0]) elif variable_info.name_to_sym[var_name] or not solver_interface.sol_statically_allocated: for i, idx in enumerate(indices): - f.write(' %sCPG_Prim.%s[%d] = %s[%d];\n' % (configuration.prefix, var_name, i, prim_str, idx)) + f.write(f' {configuration.prefix}CPG_Prim.{var_name}[%d] = {prim_str}[%d];\n' % (i, idx)) f.write('}\n\n') if solver_interface.ret_dual_func_exists(dual_variable_info): f.write('// Retrieve dual solution in terms of user-defined constraints\n') - f.write('void %scpg_retrieve_dual(){\n' % configuration.prefix) + f.write(f'void {configuration.prefix}cpg_retrieve_dual(){{\n') for var_name, (canonical_var_name, indices) in dual_variable_info.name_to_indices.items(): if len(indices) == 1: - f.write(' %sCPG_Dual.%s = %s[%d];\n' % (configuration.prefix, var_name, dual_str, indices) % canonical_var_name) + f.write(f' {configuration.prefix}CPG_Dual.{var_name} = {dual_str.format(dual_var_name=canonical_var_name)}[%d];\n' % indices[0]) elif not solver_interface.sol_statically_allocated: for i, idx in enumerate(indices): - f.write(' %sCPG_Dual.%s[%d] = %s[%d];\n' - % (configuration.prefix, var_name, i, dual_str, idx) % canonical_var_name) + f.write(f' {configuration.prefix}CPG_Dual.{var_name}[%d] = {dual_str.format(dual_var_name=canonical_var_name)}[%d];\n' % (i, idx)) f.write('}\n\n') f.write('// Retrieve solver info\n') - f.write('void %scpg_retrieve_info(){\n' % configuration.prefix) - f.write(' %sCPG_Info.obj_val = %s(%s%s%s);\n' % ( - configuration.prefix, - '-' if parameter_canon.is_maximization else '', - result_prefix, - solver_interface.ws_ptrs.objective_value, - ' + %sCanon_Params.d' % configuration.prefix if parameter_canon.nonzero_d else '')) - f.write(' %sCPG_Info.iter = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.ws_ptrs.iterations)) - f.write(' %sCPG_Info.status = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.ws_ptrs.status)) - f.write(' %sCPG_Info.pri_res = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.ws_ptrs.primal_residual)) - f.write(' %sCPG_Info.dua_res = %s%s;\n' % (configuration.prefix, result_prefix, solver_interface.ws_ptrs.dual_residual)) + f.write(f'void {configuration.prefix}cpg_retrieve_info(){{\n') + f.write(f' {configuration.prefix}CPG_Info.obj_val = {"-" if parameter_canon.is_maximization else ""}({result_prefix}{solver_interface.ws_ptrs.objective_value}{" + " + configuration.prefix + "Canon_Params.d" if parameter_canon.nonzero_d else ""});\n') + f.write(f' {configuration.prefix}CPG_Info.iter = {result_prefix}{solver_interface.ws_ptrs.iterations};\n') + f.write(f' {configuration.prefix}CPG_Info.status = {result_prefix}{solver_interface.ws_ptrs.status};\n') + f.write(f' {configuration.prefix}CPG_Info.pri_res = {result_prefix}{solver_interface.ws_ptrs.primal_residual};\n') + f.write(f' {configuration.prefix}CPG_Info.dua_res = {result_prefix}{solver_interface.ws_ptrs.dual_residual};\n') f.write('}\n\n') f.write('// Solve via canonicalization, canonical solve, retrieval\n') - f.write('void %scpg_solve(){\n' % configuration.prefix) - f.write(' // Canonicalize if necessary\n') + f.write(f'void {configuration.prefix}cpg_solve(){{\n') + f.write('// Canonicalize if necessary\n') for p_id, changes in parameter_canon.p_id_to_changes.items(): if changes: - f.write(' if (%sCanon_Outdated.%s) {\n' % (configuration.prefix, p_id)) - f.write(' %scpg_canonicalize_%s();\n' % (configuration.prefix, p_id)) + f.write(f' if ({configuration.prefix}Canon_Outdated.{p_id}) {{\n') + f.write(f' {configuration.prefix}cpg_canonicalize_{p_id}();\n') f.write(' }\n') - + if solver_interface.inmemory_preconditioning: for p_id, size in parameter_canon.p_id_to_size.items(): - if size == 1: - f.write(' %sCanon_Params_conditioning.%s = %sCanon_Params.%s;\n' - % (configuration.prefix, p_id, configuration.prefix, p_id)) - elif size > 1: - f.write(' for (i=0; i<%d; i++){\n' % size) - if p_id.isupper(): - f.write(' %sCanon_Params_conditioning.%s->x[i] = %sCanon_Params.%s->x[i];\n' - % (configuration.prefix, p_id, configuration.prefix, p_id)) - else: - f.write(' %sCanon_Params_conditioning.%s[i] = %sCanon_Params.%s[i];\n' - % (configuration.prefix, p_id, configuration.prefix, p_id)) - f.write(' }\n') + if size == 1: + f.write(f' {configuration.prefix}Canon_Params_conditioning.{p_id} = {configuration.prefix}Canon_Params.{p_id};\n') + elif size > 1: + f.write(f' for (i=0; i<{size}; i++){{\n') + if p_id.isupper(): + f.write(f' {configuration.prefix}Canon_Params_conditioning.{p_id}->x[i] = {configuration.prefix}Canon_Params.{p_id}->x[i];\n') + else: + f.write(f' {configuration.prefix}Canon_Params_conditioning.{p_id}[i] = {configuration.prefix}Canon_Params.{p_id}[i];\n') + f.write(' }\n') pus = solver_interface.parameter_update_structure write_update_structure(f, configuration, pus, *analyze_pus(pus, parameter_canon.p_id_to_changes)) if solver_interface.ws_dynamically_allocated_in_solver_code: for name in solver_interface.stgs_names_to_type.keys(): - f.write(' %s%s = %sCanon_Settings.%s;\n' % (configuration.prefix, solver_interface.ws_ptrs.settings, configuration.prefix, name) % name) + f.write(f' {configuration.prefix}{solver_interface.ws_ptrs.settings.format(setting_name=name)} = {configuration.prefix}Canon_Settings.{name};\n') - f.write(' // Solve with %s\n' % configuration.solver_name) - f.write(' %s;\n' % solver_interface.solve_function_call.format(prefix=configuration.prefix)) + f.write(f' // Solve with {configuration.solver_name}\n') + f.write(f' {solver_interface.solve_function_call.format(prefix=configuration.prefix)};\n') f.write(' // Retrieve results\n') if solver_interface.ret_prim_func_exists(variable_info): - f.write(' %scpg_retrieve_prim();\n' % configuration.prefix) + f.write(f' {configuration.prefix}cpg_retrieve_prim();\n') if solver_interface.ret_dual_func_exists(dual_variable_info): - f.write(' %scpg_retrieve_dual();\n' % configuration.prefix) - f.write(' %scpg_retrieve_info();\n' % configuration.prefix) + f.write(f' {configuration.prefix}cpg_retrieve_dual();\n') + f.write(f' {configuration.prefix}cpg_retrieve_info();\n') f.write(' // Reset flags for outdated canonical parameters\n') for p_id in parameter_canon.p_id_to_size.keys(): - f.write(' %sCanon_Outdated.%s = 0;\n' % (configuration.prefix, p_id)) + f.write(f' {configuration.prefix}Canon_Outdated.{p_id} = 0;\n') f.write('}\n\n') f.write('// Update solver settings\n') - f.write('void %scpg_set_solver_default_settings(){\n' % configuration.prefix) + f.write(f'void {configuration.prefix}cpg_set_solver_default_settings(){{\n') if solver_interface.stgs_reset_function is not None: - f.write(' %s(&%s);\n' % ( - solver_interface.stgs_reset_function['name'], - solver_interface.stgs_reset_function['ptr_name'] if solver_interface.stgs_reset_function['ptr_name'] is not None else configuration.prefix + 'Canon_Settings')) + f.write(f' {solver_interface.stgs_reset_function["name"]}(&{solver_interface.stgs_reset_function["ptr_name"] if solver_interface.stgs_reset_function["ptr_name"] is not None else configuration.prefix + "Canon_Settings"});\n') else: for name, value in solver_interface.stgs_names_to_default.items(): - f.write(' %sCanon_Settings.%s = %s;\n' % (configuration.prefix, name, value)) + f.write(f' {configuration.prefix}Canon_Settings.{name} = {value};\n') f.write('}\n') for name, typ in solver_interface.stgs_names_to_type.items(): - f.write('\nvoid %scpg_set_solver_%s(%s %s_new){\n' % (configuration.prefix, name, typ, name)) + f.write(f'\nvoid {configuration.prefix}cpg_set_solver_{name}({typ} {name}_new){{\n') if solver_interface.stgs_set_function is not None: - f.write(' %s(&%s, %%s_new);\n' % ( - solver_interface.stgs_set_function['name'], - solver_interface.stgs_set_function['ptr_name']) % (name, name)) + f.write(f' {solver_interface.stgs_set_function["name"].format(setting_name=name)}(&{solver_interface.stgs_set_function["ptr_name"]}, {name}_new);\n') else: - f.write(' %sCanon_Settings.%s = %s_new;\n' % (configuration.prefix, name, name)) + f.write(f' {configuration.prefix}Canon_Settings.{name} = {name}_new;\n') f.write('}\n') @@ -912,33 +889,33 @@ def write_solve_prot(f, configuration, variable_info, dual_variable_info, parame f.write('\n// Update user-defined parameter values\n') for name, size in parameter_info.name_to_size_usp.items(): if size == 1: - f.write('extern void %scpg_update_%s(cpg_float val);\n' % (configuration.prefix, name)) + f.write(f'extern void {configuration.prefix}cpg_update_{name}(cpg_float val);\n') else: - f.write('extern void %scpg_update_%s(cpg_int idx, cpg_float val);\n' % (configuration.prefix, name)) + f.write(f'extern void {configuration.prefix}cpg_update_{name}(cpg_int idx, cpg_float val);\n') f.write('\n// Map user-defined to canonical parameters\n') for p_id, changes in parameter_canon.p_id_to_changes.items(): if changes: - f.write('extern void %scpg_canonicalize_%s();\n' % (configuration.prefix, p_id)) + f.write(f'extern void {configuration.prefix}cpg_canonicalize_{p_id}();\n') if solver_interface.ret_prim_func_exists(variable_info): f.write('\n// Retrieve primal solution in terms of user-defined variables\n') - f.write('extern void %scpg_retrieve_prim();\n' % configuration.prefix) + f.write(f'extern void {configuration.prefix}cpg_retrieve_prim();\n') if solver_interface.ret_dual_func_exists(dual_variable_info): f.write('\n// Retrieve dual solution in terms of user-defined constraints\n') - f.write('extern void %scpg_retrieve_dual();\n' % configuration.prefix) + f.write(f'extern void {configuration.prefix}cpg_retrieve_dual();\n') f.write('\n// Retrieve solver information\n') - f.write('extern void %scpg_retrieve_info();\n' % configuration.prefix) + f.write(f'extern void {configuration.prefix}cpg_retrieve_info();\n') f.write('\n// Solve via canonicalization, canonical solve, retrieval\n') - f.write('extern void %scpg_solve();\n' % configuration.prefix) + f.write(f'extern void {configuration.prefix}cpg_solve();\n') f.write('\n// Update solver settings\n') - f.write('extern void %scpg_set_solver_default_settings();\n' % configuration.prefix) + f.write(f'extern void {configuration.prefix}cpg_set_solver_default_settings();\n') for name, typ in solver_interface.stgs_names_to_type.items(): - f.write('extern void %scpg_set_solver_%s(%s %s_new);\n' % (configuration.prefix, name, typ, name)) + f.write(f'extern void {configuration.prefix}cpg_set_solver_{name}({typ} {name}_new);\n') def write_example_def(f, configuration, variable_info, dual_variable_info, parameter_info): @@ -957,36 +934,34 @@ def write_example_def(f, configuration, variable_info, dual_variable_info, param f.write(' // Update first entry of every user-defined parameter\n') for name, value in parameter_info.writable.items(): if is_mathematical_scalar(value): - f.write(' %scpg_update_%s(%.20f);\n' % (configuration.prefix, name, value)) + f.write(f' {configuration.prefix}cpg_update_{name}(%.20f);\n' % value) else: - f.write(' %scpg_update_%s(0, %.20f);\n' % (configuration.prefix, name, value[0])) + f.write(f' {configuration.prefix}cpg_update_{name}(0, %.20f);\n' % value[0]) f.write('\n // Solve the problem instance\n') - f.write(' %scpg_solve();\n\n' % configuration.prefix) + f.write(f' {configuration.prefix}cpg_solve();\n\n') f.write(' // Print objective function value\n') - f.write(' printf("obj = %%f\\n", %sCPG_Result.info->obj_val);\n\n' % configuration.prefix) + f.write(f' printf("obj = %f\\n", {configuration.prefix}CPG_Result.info->obj_val);\n\n') f.write(' // Print primal solution\n') for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' printf("%s = %%f\\n", %sCPG_Result.prim->%s);\n' % (name, configuration.prefix, name)) + f.write(f' printf("{name} = %f\\n", {configuration.prefix}CPG_Result.prim->{name});\n') else: - f.write(' for(i=0; i<%d; i++) {\n' % var.size) - f.write(' printf("%s[%%d] = %%f\\n", i, %sCPG_Result.prim->%s[i]);\n' - % (name, configuration.prefix, name)) - f.write(' }\n') + f.write(f' for(i=0; i<{var.size}; i++) {{\n') + f.write(f' printf("{name}[%d] = %f\\n", i, {configuration.prefix}CPG_Result.prim->{name}[i]);\n') + f.write(f' }}\n') if len(dual_variable_info.name_to_init) > 0: f.write('\n // Print dual solution\n') for name, var in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' printf("%s = %%f\\n", %sCPG_Result.dual->%s);\n' % (name, configuration.prefix, name)) + f.write(f' printf("{name} = %f\\n", {configuration.prefix}CPG_Result.dual->{name});\n') else: - f.write(' for(i=0; i<%d; i++) {\n' % var.size) - f.write(' printf("%s[%%d] = %%f\\n", i, %sCPG_Result.dual->%s[i]);\n' - % (name, configuration.prefix, name)) + f.write(f' for(i=0; i<{var.size}; i++) {{\n') + f.write(f' printf("{name}[%d] = %f\\n", i, {configuration.prefix}CPG_Result.dual->{name}[i]);\n') f.write(' }\n') f.write('\n return 0;\n\n') @@ -1043,56 +1018,54 @@ def write_module_def(f, configuration, variable_info, dual_variable_info, parame f.write('static int i;\n\n') # cpp function that maps parameters to results - f.write('%sCPG_Result_cpp_t %ssolve_cpp(struct %sCPG_Updated_cpp_t& CPG_Updated_cpp, ' - 'struct %sCPG_Params_cpp_t& CPG_Params_cpp){\n\n' - % (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) + f.write(f'{configuration.prefix}CPG_Result_cpp_t {configuration.prefix}solve_cpp(struct {configuration.prefix}CPG_Updated_cpp_t& CPG_Updated_cpp, ' + f'struct {configuration.prefix}CPG_Params_cpp_t& CPG_Params_cpp){{\n\n') f.write(' // Pass changed user-defined parameter values to the solver\n') for name, size in parameter_info.name_to_size_usp.items(): - f.write(' if (CPG_Updated_cpp.%s) {\n' % name) + f.write(f' if (CPG_Updated_cpp.{name}) {{\n') if size == 1: - f.write(' %scpg_update_%s(CPG_Params_cpp.%s);\n' % (configuration.prefix, name, name)) + f.write(f' {configuration.prefix}cpg_update_{name}(CPG_Params_cpp.{name});\n') else: - f.write(' for(i=0; i<%d; i++) {\n' % size) - f.write(' %scpg_update_%s(i, CPG_Params_cpp.%s[i]);\n' % (configuration.prefix, name, name)) - f.write(' }\n') + f.write(f' for(i=0; i<{size}; i++) {{\n') + f.write(f' {configuration.prefix}cpg_update_{name}(i, CPG_Params_cpp.{name}[i]);\n') + f.write(f' }}\n') f.write(' }\n') # perform ASA procedure f.write('\n // Solve\n') f.write(' std::clock_t ASA_start = std::clock();\n') - f.write(' %scpg_solve();\n' % configuration.prefix) + f.write(f' {configuration.prefix}cpg_solve();\n') f.write(' std::clock_t ASA_end = std::clock();\n\n') # arrange and return results f.write(' // Arrange and return results\n') - f.write(' %sCPG_Prim_cpp_t CPG_Prim_cpp {};\n' % configuration.prefix) + f.write(f' {configuration.prefix}CPG_Prim_cpp_t CPG_Prim_cpp {{}};\n') for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' CPG_Prim_cpp.%s = %sCPG_Prim.%s;\n' % (name, configuration.prefix, name)) + f.write(f' CPG_Prim_cpp.{name} = {configuration.prefix}CPG_Prim.{name};\n') else: - f.write(' for(i=0; i<%d; i++) {\n' % var.size) - f.write(' CPG_Prim_cpp.%s[i] = %sCPG_Prim.%s[i];\n' - % (name, configuration.prefix, name)) + f.write(f' for(i=0; i<{var.size}; i++) {{\n') + f.write(f' CPG_Prim_cpp.{name}[i] = {configuration.prefix}CPG_Prim.{name}[i];\n') f.write(' }\n') if len(dual_variable_info.name_to_init) > 0: - f.write(' %sCPG_Dual_cpp_t CPG_Dual_cpp {};\n' % configuration.prefix) + f.write(f' {configuration.prefix}CPG_Dual_cpp_t CPG_Dual_cpp {{}};\n') for name, var in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' CPG_Dual_cpp.%s = %sCPG_Dual.%s;\n' % (name, configuration.prefix, name)) + f.write(f' CPG_Dual_cpp.{name} = {configuration.prefix}CPG_Dual.{name};\n') else: - f.write(' for(i=0; i<%d; i++) {\n' % var.size) - f.write(' CPG_Dual_cpp.%s[i] = %sCPG_Dual.%s[i];\n' % (name, configuration.prefix, name)) + f.write(f' for(i=0; i<{var.size}; i++) {{\n') + f.write(f' CPG_Dual_cpp.{name}[i] = {configuration.prefix}CPG_Dual.{name}[i];\n') f.write(' }\n') - f.write(' %sCPG_Info_cpp_t CPG_Info_cpp {};\n' % configuration.prefix) + f.write(f' {configuration.prefix}CPG_Info_cpp_t CPG_Info_cpp {{}};\n') for field in ['obj_val', 'iter', 'status', 'pri_res', 'dua_res']: - f.write(' CPG_Info_cpp.%s = %sCPG_Info.%s;\n' % (field, configuration.prefix, field)) - f.write(' CPG_Info_cpp.time = 1.0*(ASA_end-ASA_start) / CLOCKS_PER_SEC;\n') + f.write(f' CPG_Info_cpp.{field} = {configuration.prefix}CPG_Info.{field};\n') + f.write(' CPG_Info_cpp.time = 1.0 * (ASA_end - ASA_start) / CLOCKS_PER_SEC;\n') - f.write(' %sCPG_Result_cpp_t CPG_Result_cpp {};\n' % configuration.prefix) + f.write(f' {configuration.prefix}CPG_Result_cpp_t CPG_Result_cpp {{}};\n') f.write(' CPG_Result_cpp.prim = CPG_Prim_cpp;\n') if len(dual_variable_info.name_to_init) > 0: f.write(' CPG_Result_cpp.dual = CPG_Dual_cpp;\n') @@ -1105,110 +1078,107 @@ def write_module_def(f, configuration, variable_info, dual_variable_info, parame # module f.write('PYBIND11_MODULE(cpg_module, m) {\n\n') - f.write(' py::class_<%sCPG_Params_cpp_t>(m, "%scpg_params")\n' % (configuration.prefix, configuration.prefix)) + f.write(f' py::class_<{configuration.prefix}CPG_Params_cpp_t>(m, "{configuration.prefix}cpg_params")\n') f.write(' .def(py::init<>())\n') for name in parameter_info.name_to_size_usp.keys(): - f.write(' .def_readwrite("%s", &%sCPG_Params_cpp_t::%s)\n' % (name, configuration.prefix, name)) + f.write(f' .def_readwrite("{name}", &{configuration.prefix}CPG_Params_cpp_t::{name})\n') f.write(' ;\n\n') - f.write(' py::class_<%sCPG_Updated_cpp_t>(m, "%scpg_updated")\n' - % (configuration.prefix, configuration.prefix)) + f.write(f' py::class_<{configuration.prefix}CPG_Updated_cpp_t>(m, "{configuration.prefix}cpg_updated")\n') f.write(' .def(py::init<>())\n') for name in parameter_info.name_to_size_usp.keys(): - f.write(' .def_readwrite("%s", &%sCPG_Updated_cpp_t::%s)\n' % (name, configuration.prefix, name)) + f.write(f' .def_readwrite("{name}", &{configuration.prefix}CPG_Updated_cpp_t::{name})\n') f.write(' ;\n\n') - f.write(' py::class_<%sCPG_Prim_cpp_t>(m, "%scpg_prim")\n' % (configuration.prefix, configuration.prefix)) + f.write(f' py::class_<{configuration.prefix}CPG_Prim_cpp_t>(m, "{configuration.prefix}cpg_prim")\n') f.write(' .def(py::init<>())\n') for name in variable_info.name_to_init.keys(): - f.write(' .def_readwrite("%s", &%sCPG_Prim_cpp_t::%s)\n' % (name, configuration.prefix, name)) + f.write(f' .def_readwrite("{name}", &{configuration.prefix}CPG_Prim_cpp_t::{name})\n') f.write(' ;\n\n') if len(dual_variable_info.name_to_init) > 0: - f.write(' py::class_<%sCPG_Dual_cpp_t>(m, "%scpg_dual")\n' % (configuration.prefix, configuration.prefix)) + f.write(f' py::class_<{configuration.prefix}CPG_Dual_cpp_t>(m, "{configuration.prefix}cpg_dual")\n') f.write(' .def(py::init<>())\n') for name in dual_variable_info.name_to_init.keys(): - f.write(' .def_readwrite("%s", &%sCPG_Dual_cpp_t::%s)\n' % (name, configuration.prefix, name)) + f.write(f' .def_readwrite("{name}", &{configuration.prefix}CPG_Dual_cpp_t::{name})\n') f.write(' ;\n\n') - f.write(' py::class_<%sCPG_Info_cpp_t>(m, "%scpg_info")\n' % (configuration.prefix, configuration.prefix)) + f.write(f' py::class_<{configuration.prefix}CPG_Info_cpp_t>(m, "{configuration.prefix}cpg_info")\n') f.write(' .def(py::init<>())\n') - f.write(' .def_readwrite("obj_val", &%sCPG_Info_cpp_t::obj_val)\n' % configuration.prefix) - f.write(' .def_readwrite("iter", &%sCPG_Info_cpp_t::iter)\n' % configuration.prefix) - f.write(' .def_readwrite("status", &%sCPG_Info_cpp_t::status)\n' % configuration.prefix) - f.write(' .def_readwrite("pri_res", &%sCPG_Info_cpp_t::pri_res)\n' % configuration.prefix) - f.write(' .def_readwrite("dua_res", &%sCPG_Info_cpp_t::dua_res)\n' % configuration.prefix) - f.write(' .def_readwrite("time", &%sCPG_Info_cpp_t::time)\n' % configuration.prefix) + f.write(f' .def_readwrite("obj_val", &{configuration.prefix}CPG_Info_cpp_t::obj_val)\n') + f.write(f' .def_readwrite("iter", &{configuration.prefix}CPG_Info_cpp_t::iter)\n') + f.write(f' .def_readwrite("status", &{configuration.prefix}CPG_Info_cpp_t::status)\n') + f.write(f' .def_readwrite("pri_res", &{configuration.prefix}CPG_Info_cpp_t::pri_res)\n') + f.write(f' .def_readwrite("dua_res", &{configuration.prefix}CPG_Info_cpp_t::dua_res)\n') + f.write(f' .def_readwrite("time", &{configuration.prefix}CPG_Info_cpp_t::time)\n') f.write(' ;\n\n') - f.write(' py::class_<%sCPG_Result_cpp_t>(m, "%scpg_result")\n' % (configuration.prefix, configuration.prefix)) + f.write(f' py::class_<{configuration.prefix}CPG_Result_cpp_t>(m, "{configuration.prefix}cpg_result")\n') f.write(' .def(py::init<>())\n') - f.write(' .def_readwrite("cpg_prim", &%sCPG_Result_cpp_t::prim)\n' % configuration.prefix) + f.write(f' .def_readwrite("cpg_prim", &{configuration.prefix}CPG_Result_cpp_t::prim)\n') if len(dual_variable_info.name_to_init) > 0: - f.write(' .def_readwrite("cpg_dual", &%sCPG_Result_cpp_t::dual)\n' % configuration.prefix) - f.write(' .def_readwrite("cpg_info", &%sCPG_Result_cpp_t::info)\n' % configuration.prefix) + f.write(f' .def_readwrite("cpg_dual", &{configuration.prefix}CPG_Result_cpp_t::dual)\n') + f.write(f' .def_readwrite("cpg_info", &{configuration.prefix}CPG_Result_cpp_t::info)\n') f.write(' ;\n\n') - f.write(' m.def("solve", &%ssolve_cpp);\n\n' % configuration.prefix) + f.write(f' m.def("solve", &{configuration.prefix}solve_cpp);\n\n') - f.write(' m.def("set_solver_default_settings", &%scpg_set_solver_default_settings);\n' % configuration.prefix) + f.write(f' m.def("set_solver_default_settings", &{configuration.prefix}cpg_set_solver_default_settings);\n') for name in solver_interface.stgs_names_to_type.keys(): - f.write(' m.def("set_solver_%s", &%scpg_set_solver_%s);\n' % (name, configuration.prefix, name)) + f.write(f' m.def("set_solver_{name}", &{configuration.prefix}cpg_set_solver_{name});\n') f.write('\n}\n') def write_module_prot(f, configuration, parameter_info, variable_info, dual_variable_info, solver_interface): """ - Write c++ file for pbind11 wrapper + Write c++ file for pybind11 wrapper """ write_description(f, 'cpp', 'Declarations for Python binding with pybind11') # cpp struct containing user-defined parameters f.write('// User-defined parameters\n') - f.write('struct %sCPG_Params_cpp_t {\n' % configuration.prefix) + f.write(f'struct {configuration.prefix}CPG_Params_cpp_t {{\n') for name, size in parameter_info.name_to_size_usp.items(): - if size == 1: - f.write(' double %s;\n' % name) - else: - f.write(' std::array %s;\n' % (size, name)) + array_decl = f'std::array' if size > 1 else 'double' + f.write(f' {array_decl} {name};\n') f.write('};\n\n') # cpp struct containing update flags for user-defined parameters f.write('// Flags for updated user-defined parameters\n') - f.write('struct %sCPG_Updated_cpp_t {\n' % configuration.prefix) + f.write(f'struct {configuration.prefix}CPG_Updated_cpp_t {{\n') for name in parameter_info.name_to_size_usp.keys(): - f.write(' bool %s;\n' % name) + f.write(f' bool {name};\n') f.write('};\n\n') # cpp struct containing primal variables - f.write('// Primal solution\n') - f.write('struct %sCPG_Prim_cpp_t {\n' % configuration.prefix) + f.write(f'// Primal solution\n') + f.write(f'struct {configuration.prefix}CPG_Prim_cpp_t {{\n') for name, var in variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' double %s;\n' % name) + f.write(f' double {name};\n') else: - f.write(' std::array %s;\n' % (var.size, name)) + f.write(f' std::array {name};\n') f.write('};\n\n') # cpp struct containing dual variables if len(dual_variable_info.name_to_init) > 0: f.write('// Dual solution\n') - f.write('struct %sCPG_Dual_cpp_t {\n' % configuration.prefix) + f.write(f'struct {configuration.prefix}CPG_Dual_cpp_t {{\n') for name, var in dual_variable_info.name_to_init.items(): if is_mathematical_scalar(var): - f.write(' double %s;\n' % name) + f.write(f' double {name};\n') else: - f.write(' std::array %s;\n' % (var.size, name)) + f.write(f' std::array {name};\n') f.write('};\n\n') # cpp struct containing info on results f.write('// Solver information\n') - f.write('struct %sCPG_Info_cpp_t {\n' % configuration.prefix) + f.write(f'struct {configuration.prefix}CPG_Info_cpp_t {{\n') f.write(' double obj_val;\n') f.write(' int iter;\n') - f.write(' %s status;\n' % ('int' if solver_interface.status_is_int else 'char*')) + f.write(f' { "int" if solver_interface.status_is_int else "char*"} status;\n') f.write(' double pri_res;\n') f.write(' double dua_res;\n') f.write(' double time;\n') @@ -1216,18 +1186,17 @@ def write_module_prot(f, configuration, parameter_info, variable_info, dual_vari # cpp struct containing objective value and user-defined variables f.write('// Solution and solver information\n') - f.write('struct %sCPG_Result_cpp_t {\n' % configuration.prefix) - f.write(' %sCPG_Prim_cpp_t prim;\n' % configuration.prefix) + f.write(f'struct {configuration.prefix}CPG_Result_cpp_t {{\n') + f.write(f' {configuration.prefix}CPG_Prim_cpp_t prim;\n') if len(dual_variable_info.name_to_init) > 0: - f.write(' %sCPG_Dual_cpp_t dual;\n' % configuration.prefix) - f.write(' %sCPG_Info_cpp_t info;\n' % configuration.prefix) + f.write(f' {configuration.prefix}CPG_Dual_cpp_t dual;\n') + f.write(f' {configuration.prefix}CPG_Info_cpp_t info;\n') f.write('};\n\n') # cpp function that maps parameters to results - f.write('// Main solve function\n') - f.write('%sCPG_Result_cpp_t %ssolve_cpp(struct %sCPG_Updated_cpp_t& CPG_Updated_cpp, ' - 'struct %sCPG_Params_cpp_t& CPG_Params_cpp);\n' - % (configuration.prefix, configuration.prefix, configuration.prefix, configuration.prefix)) + f.write(f'// Main solve function\n') + f.write(f'{configuration.prefix}CPG_Result_cpp_t {configuration.prefix}solve_cpp(struct {configuration.prefix}CPG_Updated_cpp_t& CPG_Updated_cpp, ' + f'struct {configuration.prefix}CPG_Params_cpp_t& CPG_Params_cpp);\n') def replace_setup_data(text): @@ -1257,55 +1226,52 @@ def write_method(f, configuration, variable_info, dual_variable_info, parameter_ f.write('def cpg_solve(prob, updated_params=None, **kwargs):\n\n') f.write(' # set flags for updated parameters\n') - f.write(' upd = cpg_module.%scpg_updated()\n' % configuration.prefix) + f.write(f' upd = cpg_module.{configuration.prefix}cpg_updated()\n') f.write(' if updated_params is None:\n') - p_list_string = '' - for name in parameter_info.name_to_size_usp.keys(): - p_list_string += '"%s", ' % name - f.write(' updated_params = [%s]\n' % p_list_string[:-2]) + p_list_string = ', '.join([f'"{name}"' for name in parameter_info.name_to_size_usp.keys()]) + f.write(f' updated_params = [{p_list_string}]\n') f.write(' for p in updated_params:\n') f.write(' try:\n') f.write(' setattr(upd, p, True)\n') f.write(' except AttributeError:\n') - f.write(' raise(AttributeError("%s is not a parameter." % p))\n\n') + f.write(' raise AttributeError(f"{p} is not a parameter.")\n\n') f.write(' # set solver settings\n') f.write(' cpg_module.set_solver_default_settings()\n') f.write(' for key, value in kwargs.items():\n') f.write(' try:\n') - f.write(' eval(\'cpg_module.set_solver_%s(value)\' % standard_settings_names.get(key, key))\n') + f.write(' eval(f\'cpg_module.set_solver_{standard_settings_names.get(key, key)}(value)\')\n') f.write(' except AttributeError:\n') - f.write(' raise(AttributeError(\'Solver setting "%s" not available.\' % key))\n\n') + f.write(' raise AttributeError(f\'Solver setting "{key}" not available.\')\n\n') f.write(' # set parameter values\n') - f.write(' par = cpg_module.%scpg_params()\n' % configuration.prefix) - # prob.param_dict is a computed property. Avoid repeated calls in the loop. + f.write(f' par = cpg_module.{configuration.prefix}cpg_params()\n') f.write(' param_dict = prob.param_dict\n') for name, size in parameter_info.name_to_size_usp.items(): if name in parameter_info.name_to_sparsity.keys(): - f.write(' n = param_dict[\'%s\'].shape[0]\n' % name) + f.write(f' n = param_dict["{name}"].shape[0]\n') if parameter_info.name_to_sparsity_type[name] == 'diag': - f.write(' %s_coordinates = np.arange(0, n**2, n+1)\n' % name) + f.write(f' {name}_coordinates = np.arange(0, n**2, n+1)\n') else: - f.write(' %s_coordinates = np.unique([coord[0]+coord[1]*n for coord in ' - 'param_dict[\'%s\'].attributes[\'sparsity\']])\n' % (name, name)) + f.write(f' {name}_coordinates = np.unique([coord[0]+coord[1]*n for coord in ' + f'param_dict["{name}"].attributes["sparsity"]])\n') if size == 1: - f.write(' par.%s = param_dict[\'%s\'].value[coordinates]\n' % (name, name)) + f.write(f' par.{name} = param_dict["{name}"].value[{name}_coordinates]\n') else: - f.write(' %s_value = []\n' % name) - f.write(' %s_flat = param_dict[\'%s\'].value.flatten(order=\'F\')\n' % (name, name)) - f.write(' for coord in %s_coordinates:\n' % name) - f.write(' %s_value.append(%s_flat[coord])\n' % (name, name)) - f.write(' %s_flat[coord] = 0\n' % name) - f.write(' if np.sum(np.abs(%s_flat)) > 0:\n' % name) - f.write(' warnings.warn(\'Ignoring nonzero value outside of sparsity pattern for ' - 'parameter %s!\')\n' % name) - f.write(' par.%s = list(%s_value)\n' % (name, name)) + f.write(f' {name}_value = []\n') + f.write(f' {name}_flat = param_dict["{name}"].value.flatten(order="F")\n') + f.write(f' for coord in {name}_coordinates:\n') + f.write(f' {name}_value.append({name}_flat[coord])\n') + f.write(f' {name}_flat[coord] = 0\n') + f.write(f' if np.sum(np.abs({name}_flat)) > 0:\n') + f.write(f' warnings.warn(\'Ignoring nonzero value outside of sparsity pattern for ' + f'parameter {name}!\')\n') + f.write(f' par.{name} = list({name}_value)\n') else: if size == 1: - f.write(' par.%s = param_dict[\'%s\'].value\n' % (name, name)) + f.write(f' par.{name} = param_dict["{name}"].value\n') else: - f.write(' par.%s = list(param_dict[\'%s\'].value.flatten(order=\'F\'))\n' % (name, name)) + f.write(f' par.{name} = list(param_dict["{name}"].value.flatten(order="F"))\n') f.write('\n # solve\n') f.write(' t0 = time.time()\n') @@ -1316,28 +1282,25 @@ def write_method(f, configuration, variable_info, dual_variable_info, parameter_ f.write(' prob._clear_solution()\n') for name, shape in variable_info.name_to_shape.items(): if len(shape) == 2: - f.write(' prob.var_dict[\'%s\'].save_value(np.array(res.cpg_prim.%s).reshape((%d, %d), order=\'F\'))\n' % - (name, name, shape[0], shape[1])) + f.write(f' prob.var_dict[\'{name}\'].save_value(np.array(res.cpg_prim.{name}).reshape(({shape[0]}, {shape[1]}), order=\'F\'))\n') elif len(shape) == 1: - f.write(' prob.var_dict[\'%s\'].save_value(np.array(res.cpg_prim.%s).reshape(%d))\n' - % (name, name, shape[0])) + f.write(f' prob.var_dict[\'{name}\'].save_value(np.array(res.cpg_prim.{name}).reshape({shape[0]}))\n') else: - f.write(' prob.var_dict[\'%s\'].save_value(np.array(res.cpg_prim.%s))\n' % (name, name)) + f.write(f' prob.var_dict[\'{name}\'].save_value(np.array(res.cpg_prim.{name}))\n') + for i, (name, shape) in enumerate(dual_variable_info.name_to_shape.items()): if len(shape) == 2: - f.write(' prob.constraints[%d].save_dual_value(' - 'np.array(res.cpg_dual.%s).reshape((%d, %d), order=\'F\'))\n' % (i, name, shape[0], shape[1])) + f.write(f' prob.constraints[{i}].save_dual_value(np.array(res.cpg_dual.{name}).reshape(({shape[0]}, {shape[1]}), order=\'F\'))\n') elif len(shape) == 1: - f.write(' prob.constraints[%d].save_dual_value(np.array(res.cpg_dual.%s).reshape(%d))\n' - % (i, name, shape[0])) + f.write(f' prob.constraints[{i}].save_dual_value(np.array(res.cpg_dual.{name}).reshape({shape[0]}))\n') else: - f.write(' prob.constraints[%d].save_dual_value(np.array(res.cpg_dual.%s))\n' % (i, name)) + f.write(f' prob.constraints[{i}].save_dual_value(np.array(res.cpg_dual.{name}))\n') f.write('\n # store additional solver information in problem object\n') - f.write(' prob._status = %sres.cpg_info.status\n' % - (('"%%d (for description visit %s)" %% ' % solver_interface.docu) if solver_interface.status_is_int else '')) + f.write(' prob._status = %s\n' % + (f'"%d (for description visit {solver_interface.docu})" % res.cpg_info.status' if solver_interface.status_is_int else 'res.cpg_info.status')) f.write(' if abs(res.cpg_info.obj_val) == 1e30:\n') - f.write(' prob._value = np.sign(res.cpg_info.obj_val)*np.inf\n') + f.write(' prob._value = np.sign(res.cpg_info.obj_val) * np.inf\n') f.write(' else:\n') f.write(' prob._value = res.cpg_info.obj_val\n') f.write(' primal_vars = {var.id: var.value for var in prob.variables()}\n') @@ -1348,14 +1311,13 @@ def write_method(f, configuration, variable_info, dual_variable_info, parameter_ f.write(' \'pri_res\': res.cpg_info.pri_res,\n') f.write(' \'dua_res\': res.cpg_info.dua_res,\n') f.write(' \'time\': res.cpg_info.time}\n') - f.write(' attr = {\'solve_time\': t1-t0, \'solver_specific_stats\': solver_specific_stats, ' + f.write(' attr = {\'solve_time\': t1 - t0, \'solver_specific_stats\': solver_specific_stats, ' '\'num_iters\': res.cpg_info.iter}\n') f.write(' prob._solution = Solution(prob.status, prob.value, primal_vars, dual_vars, attr)\n') f.write(' results_dict = {\'solver_specific_stats\': solver_specific_stats,\n') f.write(' \'num_iters\': res.cpg_info.iter,\n') - f.write(' \'solve_time\': t1-t0}\n') - f.write(' prob._solver_stats = SolverStats(results_dict, \'%s\')\n\n' % configuration.solver_name) - + f.write(' \'solve_time\': t1 - t0}\n') + f.write(f' prob._solver_stats = SolverStats(results_dict, \'{configuration.solver_name}\')\n\n') f.write(' return prob.value\n') @@ -1396,7 +1358,7 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - CPGPRIMTYPEDEF += (' cpg_float %s // Your variable %s\n' % ((s + name + ';').ljust(9), name)) + CPGPRIMTYPEDEF += (f' cpg_float {(s + name + ";").ljust(9)} // Your variable {name}\n') CPGPRIMTYPEDEF += '} CPG_Prim_t;\n' text = text.replace('$CPGPRIMTYPEDEF', CPGPRIMTYPEDEF) @@ -1409,8 +1371,7 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa s = '' else: s = '*' - CPGDUALTYPEDEF += (' cpg_float %s // Your dual variable for constraint %s\n' - % ((s + name + ';').ljust(9), name)) + CPGDUALTYPEDEF += (f' cpg_float {(s + name + ";").ljust(9)} // Your dual variable for constraint {name}\n') CPGDUALTYPEDEF += '} CPG_Dual_t;\n\n' else: CPGDUALTYPEDEF = '' @@ -1421,7 +1382,7 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa CPGINFOTYPEDEF += 'typedef struct {\n' CPGINFOTYPEDEF += ' cpg_float obj_val; // Objective function value\n' CPGINFOTYPEDEF += ' cpg_int iter; // Number of iterations\n' - CPGINFOTYPEDEF += (' %sstatus; // Solver status\n' % ('cpg_int ' if solver_interface.status_is_int else 'char *')) + CPGINFOTYPEDEF += (f' {"cpg_int " if solver_interface.status_is_int else "char *"}status; // Solver status\n') CPGINFOTYPEDEF += ' cpg_float pri_res; // Primal residual\n' CPGINFOTYPEDEF += ' cpg_float dua_res; // Dual residual\n' CPGINFOTYPEDEF += '} CPG_Info_t;\n' @@ -1441,25 +1402,24 @@ def replace_html_data(text, configuration, variable_info, dual_variable_info, pa CPGUPDATEDECLARATIONS = '\n// Update user-defined parameter values\n' for name, size in parameter_info.name_to_size_usp.items(): if size == 1: - CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(cpg_float value);\n' % (configuration.prefix, name) + CPGUPDATEDECLARATIONS += f'void {configuration.prefix}cpg_update_{name}(cpg_float value);\n' else: - CPGUPDATEDECLARATIONS += 'void %scpg_update_%s(cpg_int idx, cpg_float value);\n' % (configuration.prefix, name) + CPGUPDATEDECLARATIONS += f'void {configuration.prefix}cpg_update_{name}(cpg_int idx, cpg_float value);\n' text = text.replace('$CPGUPDATEDECLARATIONS', CPGUPDATEDECLARATIONS) # solve declarations CPGSOLVEDECLARATIONS = '\n// Solve via canonicalization, canonical solve, retrieval\n' - CPGSOLVEDECLARATIONS += 'void %scpg_solve();\n' % configuration.prefix + CPGSOLVEDECLARATIONS += f'void {configuration.prefix}cpg_solve();\n' text = text.replace('$CPGSOLVEDECLARATIONS', CPGSOLVEDECLARATIONS) # settings declarations CPGSETTINGSDECLARATIONS = '\n// Update solver settings\n' - CPGSETTINGSDECLARATIONS += 'void %scpg_set_solver_default_settings();\n' % configuration.prefix - CPGSETTINGSDECLARATIONS += 'void %scpg_set_solver_<setting_name>' \ - '(<setting_type> <setting_name>_new);\n' % configuration.prefix + CPGSETTINGSDECLARATIONS += f'void {configuration.prefix}cpg_set_solver_default_settings();\n' + CPGSETTINGSDECLARATIONS += f'void {configuration.prefix}cpg_set_solver_<setting_name>(<setting_type> <setting_name>_new);\n' CPGSETTINGSDECLARATIONS += '...\n' text = text.replace('$CPGSETTINGSDECLARATIONS', CPGSETTINGSDECLARATIONS) # settings list - CPGSETTINGSLIST = ', '.join([('%s' % s) for s in solver_interface.stgs_names_enabled]) + CPGSETTINGSLIST = ', '.join([f'{s}' for s in solver_interface.stgs_names_enabled]) return text.replace('$CPGSETTINGSLIST', CPGSETTINGSLIST)