def check_vars_domain(self, lbs, ubs, names): l_ubs = len(ubs) l_lbs = len(lbs) if l_lbs and l_ubs: names = names or generate_constant(None, max(l_lbs, l_ubs)) for lb, ub, varname in izip(lbs, ubs, names): self.check_var_domain(lb, ub, varname)
def _scal_prod_triple(self, coefs, left_terms, right_terms): # INTERNAL accumulated_ct = 0 qcc = self.counter_type() lcc = self.counter_type() number_validation_fn = self._checker.get_number_validation_fn() for coef, lterm, rterm in izip(coefs, left_terms, right_terms): if coef: safe_coef = number_validation_fn( coef) if number_validation_fn else coef lcst = lterm.get_constant() rcst = rterm.get_constant() accumulated_ct += safe_coef * lcst * rcst for lv, lk in lterm.iter_terms(): for rv, rk in rterm.iter_terms(): coef3 = safe_coef * lk * rk update_dict_from_item_value(qcc, VarPair(lv, rv), coef3) if rcst: for lv, lk in lterm.iter_terms(): update_dict_from_item_value(lcc, lv, safe_coef * lk * rcst) if lcst: for rv, rk in rterm.iter_terms(): update_dict_from_item_value(lcc, rv, safe_coef * rk * lcst) return self._to_expr(qcc, lcc, constant=accumulated_ct)
def _make_expr_from_vars_coefs(cls, mdl, dvars, coefs, offset=0): terms_dict = mdl._lfactory._new_term_dict() for dv, k in izip(dvars, coefs): if k: terms_dict[dv] = k return mdl._lfactory.linear_expr(arg=terms_dict, constant=offset, safe=True)
def _sparse_make_exprs(self, sp_mat, dvars, nb_exprs): lfactory = self._linear_factory exprs = [lfactory.linear_expr() for _ in range(nb_exprs)] coo_mat = sp_mat.tocoo() for coef, row, col in izip(coo_mat.data, coo_mat.row, coo_mat.col): exprs[row]._add_term(dvars[col], coef) return exprs
def _scal_prod_triple_vars(self, coefs, left_terms, right_terms): # INTERNAL # assuming all arguments are iterable. dcc = self.counter_type qcc = dcc() number_validation_fn = self._checker.get_number_validation_fn() if number_validation_fn: for coef, lterm, rterm in izip(coefs, left_terms, right_terms): safe_coef = number_validation_fn( coef) if number_validation_fn else coef update_dict_from_item_value(qcc, VarPair(lterm, rterm), safe_coef) else: for coef, lterm, rterm in izip(coefs, left_terms, right_terms): update_dict_from_item_value(qcc, VarPair(lterm, rterm), coef) return self._to_expr(qcc=qcc)
def __add__(self, arg): if isinstance(arg, PwlFunction._PwlAsBreaks): all_x_coord = sorted({br[0] for br in self.breaksxy + arg.breaksxy}) all_breaks_left = self._get_all_breaks(all_x_coord) all_breaks_right = arg._get_all_breaks(all_x_coord) result_breaksxy = [] # Both lists have same size, with same x-coord for breaks ==> perform the addition on each break for br_l, br_r in izip(all_breaks_left, all_breaks_right): if isinstance(br_l, tuple) and isinstance(br_r, tuple): result_breaksxy.append((br_l[0], br_l[1] + br_r[1])) else: if isinstance(br_l, tuple): # br_r is a list containing 2 tuple pairs result_breaksxy.append((br_l[0], br_l[1] + br_r[0][1])) result_breaksxy.append((br_l[0], br_l[1] + br_r[1][1])) elif isinstance(br_r, tuple): # br_l is a list containing 2 tuple pairs result_breaksxy.append((br_r[0], br_l[0][1] + br_r[1])) result_breaksxy.append((br_r[0], br_l[1][1] + br_r[1])) else: # br_l and br_r are two lists, each containing 2 tuple pairs result_breaksxy.append((br_l[0][0], br_l[0][1] + br_r[0][1])) result_breaksxy.append((br_l[0][0], br_l[1][1] + br_r[1][1])) result_preslope = self.preslope + arg.preslope result_postslope = self.postslope + arg.postslope return PwlFunction._PwlAsBreaks(*self._remove_useless_intermediate_breaks( result_preslope, result_breaksxy, result_postslope)) elif is_number(arg): return PwlFunction._PwlAsBreaks( self.preslope, [(br[0], br[1] + arg) for br in self.breaksxy], self.postslope) else: raise DOcplexException("Invalid type for right hand side operand: {0!s}.".format(arg))
def notify_obj_indices(self, objs, indices): # take the last one?? if indices: idxmap = self._index_map if idxmap is not None: for obj, idx in izip(objs, indices): idxmap[idx] = obj
def build_matrix_range_model_and_solve(cls, var_count, var_lbs, var_ubs, var_types, var_names, cts_mat, range_mins, range_maxs, objsense, costs, cast_to_float, solution_maker=make_solution, **transform_params): cpx = cls.create_cplex() # varlist = mdl.continuous_var_list(var_count, lb=var_lbs, ub=var_ubs, name=var_names) cls.create_column_vars(cpx, var_count, var_lbs, var_ubs, var_types, var_names) var_indices = list(range(var_count)) gen_rows = ModelAggregator.generate_rows(cts_mat) cpx_rows = [] for row in gen_rows: # need this step as cplex may crash with np types. frow = [float(k) for k in row] if cast_to_float else row cpx_rows.append([var_indices, frow]) nb_rows = len(cpx_rows) if nb_rows: #ctsense = ComparisonType.parse(transform_params.get('sense', 'le')) cpx_senses = 'R' * nb_rows cpx_ranges = [float(rmin - rmax) for rmin, rmax in izip(range_mins, range_maxs)] # rhs is UB cpx_rhss = [float(rmax) for rmax in range_maxs] add_linear(cpx, cpx_rows, cpx_senses, rhs=cpx_rhss, names=[], ranges=cpx_ranges) if costs is not None: # set linear objective for all variables. fcosts = [float(k) for k in costs] static_fast_set_linear_obj(cpx, var_indices, fcosts) cpx.objective.set_sense(objsense.cplex_coef) # here we go to solve... return cls._solve(cpx, var_names, solution_maker=solution_maker, **transform_params)
def notify_obj_indices(self, objs, indices): # take the last one?? if indices: _AutomaticSymbolGenerator.notify_new_index(self, max(indices)) idxmap = self._index_map if idxmap is not None: for obj, idx in izip(objs, indices): idxmap[idx] = obj
def check_vars_domain(self, lbs, ubs, names): l_ubs = len(ubs) l_lbs = len(lbs) if l_lbs and l_ubs: names = names or generate_constant(None, max(l_lbs, l_ubs)) # noinspection PyArgumentList,PyArgumentList for lb, ub, varname in izip(lbs, ubs, names): self.check_var_domain(lb, ub, varname)
def _make_expr_from_varmap_coefs(cls, lfactory, varmap, dvarxs, coefs, offset=0): terms_dict = lfactory._new_term_dict() for dvx, k in izip(dvarxs, coefs): dv = varmap.get(dvx) if dv is not None and k: terms_dict[dv] = k return lfactory.linear_expr(arg=terms_dict, constant=offset, safe=True)
def vector_compare(self, left_exprs, right_exprs, sense): lfactory = self._linear_factory assert len(left_exprs) == len(right_exprs) cts = [ lfactory._new_binary_constraint(left, sense, right) for left, right in izip(left_exprs, right_exprs) ] return cts
def _make_expr_from_varmap_coefs(cls, lfactory, varmap, var_indices, coefs, offset=0): if Environment.env_is_python36: terms_dict = { varmap.get(dvx): k for dvx, k in izip(var_indices, coefs) } else: terms_dict = lfactory._new_term_dict() for dvx, k in izip(var_indices, coefs): dv = varmap.get(dvx) if dv is not None: terms_dict[dv] = k return lfactory.linear_expr(arg=terms_dict, constant=offset, safe=True)
def make_solution_from_vars(self, dvars): # build a solution object from array of solution values # noinspection PyUnresolvedReferences if dvars: indices = [v._index for v in dvars] var_values = super(ModelCallbackMixin, self).get_values(indices) var_value_dict = {v: val for v, val in izip(dvars, var_values)} else: var_value_dict = {} return self.model.new_solution(var_value_dict)
def _matrix_ranges(self, coef_mat, svars, lbs, ubs): row_gen = self.generate_rows(coef_mat) lfactory = self._linear_factory return [ lfactory.new_range_constraint(expr=self._scal_prod(svars, row), lb=lb, ub=ub) for row, lb, ub in izip(row_gen, lbs, ubs) ]
def _scal_prod_vars_all_different(self, terms, coefs): checker = self._checker if not is_iterable(coefs, accept_string=False): checker.typecheck_num(coefs) return coefs * self._sum_vars_all_different(terms) else: # coefs is iterable lcc_type = self.counter_type lcc = lcc_type() lcc_setitem = lcc_type.__setitem__ number_validation_fn = checker.get_number_validation_fn() if number_validation_fn: for dvar, coef in izip(terms, coefs): lcc_setitem(lcc, dvar, number_validation_fn(coef)) else: for dvar, coef in izip(terms, coefs): lcc_setitem(lcc, dvar, coef) return self._to_expr(qcc=None, lcc=lcc)
def _matrix_constraints(self, coef_mat, svars, srhs, op): row_gen = self.generate_rows(coef_mat) lfactory = self._linear_factory return [ lfactory._new_binary_constraint(lhs=self._scal_prod(svars, row), sense=op, rhs=rhs) for row, rhs in izip(row_gen, srhs) ]
def compile_naming_function(keys, user_name, dimension=1, key_format=None, _default_key_format='_%s', stringifier=str_flatten_tuple): # INTERNAL # builds a naming rule from an input , a dimension, and an optional meta-format # Makes sure the format string does contain the right number of format slots assert user_name is not None if is_string(user_name): if key_format is None: used_key_format = _default_key_format elif is_string(key_format): # -- make sure some %s is inside, otherwise add it if '%s' in key_format: used_key_format = key_format else: used_key_format = key_format + '%s' else: # pragma: no cover raise DOcplexException( "key format expects string format or None, got: {0!r}".format( key_format)) fixed_format_string = fix_format_string(user_name, dimension, used_key_format) if 1 == dimension: return lambda k: fixed_format_string % stringifier(k) else: # here keys are tuples of size >= 2 return lambda key_tuple: fixed_format_string % key_tuple elif is_function(user_name): return user_name elif is_iterable(user_name): # check that the iterable has same len as keys, # otherwise thereis no more default naming and None cannot appear in CPLEX name arrays list_names = list(user_name) if len(list_names) < len(keys): raise DOcplexException( "An array of names should have same len as keys, expecting: {0}, go: {1}" .format(len(keys), len(list_names))) key_to_names_dict = {k: nm for k, nm in izip(keys, list_names)} # use a closure return lambda k: key_to_names_dict[ k] # if k in key_to_names_dict else default_fn() else: raise DOcplexException( 'Cannot use this for naming variables: {0!r} - expecting string, function or iterable' .format(user_name))
def make_solution_from_vars(self, dvars): # build a solution object from array of solution values # noinspection PyUnresolvedReferences m = self.model if dvars: indices = [v._index for v in dvars] # this calls the Cplex callback method get_values, which crashes if called with empty list var_values = super(ModelCallbackMixin, self).get_values(indices) var_value_dict = {v: val for v, val in izip(dvars, var_values)} else: var_value_dict = {} return m.new_solution(var_value_dict)
def _print_sos_block(self, wrapper, mdl): if mdl.number_of_sos > 0: wrapper.write('SOS') wrapper.flush(print_newline=True) name_fn = self._var_print_name for s, sos in enumerate(mdl.iter_sos(), start=1): # always print a name string, s1, s2, ... if no name sos_name = sos.get_name() or 's%d' % s wrapper.write('%s:' % sos_name) wrapper.write('S%d ::' % sos.sos_type.value) # 1 or 2 ranks = sos.weights # noinspection PyArgumentList for rank, sos_var in izip(ranks, sos._variables): wrapper.write('%s : %d' % (name_fn(sos_var), rank)) wrapper.flush(print_newline=True)
def make_solution_from_vars(self, dvars): """ Creates an intermediate solution from a list of variables. :param dvars: a list of DOcplex variables. :return: a :class:`docplex.mp.solution.SolveSolution` object. """ if dvars: indices = [v._index for v in dvars] # this calls the Cplex callback method get_values, which crashes if called with empty list # noinspection PyUnresolvedReferences var_values = super(ModelCallbackMixin, self).get_values(indices) # noinspection PyArgumentList var_value_dict = {v: val for v, val in izip(dvars, var_values)} else: # pragma: no cover var_value_dict = {} return self.model.new_solution(var_value_dict)
def _scal_prod(self, terms, coefs): # INTERNAL checker = self._checker total_num = 0 lcc = self.counter_type() qcc = None number_validation_fn = checker.get_number_validation_fn() for item, coef in izip(terms, coefs): if not coef: continue safe_coef = number_validation_fn( coef) if number_validation_fn else coef if isinstance(item, Var): update_dict_from_item_value(lcc, item, safe_coef) elif isinstance(item, AbstractLinearExpr): total_num += safe_coef * item.get_constant() for lv, lk in item.iter_terms(): update_dict_from_item_value(lcc, lv, lk * safe_coef) elif isinstance(item, QuadExpr): if qcc is None: qcc = self.counter_type() for qv, qk in item.iter_quads(): update_dict_from_item_value(qcc, qv, qk * safe_coef) qlin = item.get_linear_part() for v, k in qlin.iter_terms(): update_dict_from_item_value(lcc, v, k * safe_coef) total_num += safe_coef * qlin.constant # --- try conversion --- else: try: e = item.to_linear_expr() total_num += e.get_constant() for dv, k, in e.iter_terms(): update_dict_from_item_value(lcc, dv, k * safe_coef) except AttributeError: self._model.fatal( "scal_prod accepts variables, expressions, numbers, not: {0!s}", item) return self._to_expr(qcc, lcc, total_num)
def __add__(self, arg): if isinstance(arg, PwlFunction._PwlAsSlopes): all_x_coord = sorted({sbr[1] for sbr in self.slopebreaksx + arg.slopebreaksx}) all_slopebreaks_left = self._get_all_slopebreaks(all_x_coord) all_slopebreaks_right = arg._get_all_slopebreaks(all_x_coord) result_slopebreaksxy = [] # Both lists have same size, with same x-coord for slopebreaks # ==> perform the addition of slopes on each break for sbr_l, sbr_r in izip(all_slopebreaks_left, all_slopebreaks_right): if isinstance(sbr_l, tuple) and isinstance(sbr_r, tuple): result_slopebreaksxy.append((sbr_l[0] + sbr_r[0], sbr_l[1])) else: if isinstance(sbr_l, tuple): # sbr_r is a list containing 2 tuple pairs result_slopebreaksxy.append((sbr_l[0] + sbr_r[0][0], sbr_l[1])) result_slopebreaksxy.append((sbr_r[1][0], sbr_l[1])) elif isinstance(sbr_r, tuple): # sbr_l is a list containing 2 tuple pairs result_slopebreaksxy.append((sbr_l[0][0] + sbr_r[0], sbr_r[1])) result_slopebreaksxy.append((sbr_l[1][0], sbr_r[1])) else: # sbr_l and sbr_r are two lists, each containing 2 tuple pairs result_slopebreaksxy.append((sbr_l[0][0] + sbr_r[0][0], sbr_l[0][1])) result_slopebreaksxy.append((sbr_l[1][0] + sbr_r[1][0], sbr_l[0][1])) result_lastslope = self.lastslope + arg.lastslope if self.anchor[0] == arg.anchor[0]: result_anchor = (self.anchor[0], self.anchor[1] + arg.anchor[1]) else: # Compute a new anchor based on the last x-coord in the slopebreakx list + anchor point anchor_l = self._get_safe_xy_anchor() anchor_r = arg._get_safe_xy_anchor() delta = anchor_r[0] - anchor_l[0] if anchor_l[0] < anchor_r[0]: result_anchor = (anchor_r[0], anchor_l[1] + anchor_r[1] + delta * self.lastslope) else: result_anchor = (anchor_l[0], anchor_l[1] + anchor_r[1] - delta * arg.lastslope) return PwlFunction._PwlAsSlopes(*self._remove_useless_intermediate_slopes( result_slopebreaksxy, result_lastslope, result_anchor)) elif is_number(arg): return PwlFunction._PwlAsSlopes(copy.deepcopy(self.slopebreaksx), self.lastslope, (self.anchor[0], self.anchor[1] + arg)) else: raise DOcplexException("Invalid type for right hand side operand: {0!s}.".format(arg))
def equals(self, other, check_models=False, obj_precision=1e-3, var_precision=1e-6): from itertools import dropwhile if check_models and (self.model != other.model): return False if is_iterable(self.objective_value) and is_iterable( other.objective_value): if len(self.objective_value) == len(other.objective_value): for self_obj_val, other_obj_val in zip(self.objective_value, other.objective_value): if abs(self_obj_val - other_obj_val) >= obj_precision: return False else: # Different number of objectives return False elif not is_iterable(self.objective_value) and not is_iterable( other.objective_value): if abs(self.objective_value - other.objective_value) >= obj_precision: return False else: # One solution is for multi-objective, and not the other return False # noinspection PyPep8 this_triplets = [(dv.index, dv.name, svalue) for dv, svalue in dropwhile(lambda dvv: not dvv[1], self.iter_var_values())] other_triplets = [(dv.index, dv.name, svalue) for dv, svalue in dropwhile(lambda dvv: not dvv[1], other.iter_var_values())] # noinspection PyArgumentList res = True for this_triple, other_triple in izip(this_triplets, other_triplets): this_index, this_name, this_val = this_triple other_index, other_name, other_val = other_triple if other_index != this_index or this_name != other_name or \ abs(this_val - other_val) >= var_precision: res = False break return res
def new_range_block(self, lbs, exprs, ubs, names): try: n_exprs = len(exprs) if n_exprs != len(lbs): # pragma: no cover self.fatal( 'incorrect number of expressions: expecting {0}, got: {1}'. format(len(lbs), n_exprs)) except TypeError: pass # no len available. if not names: names = generate_constant(None, None) ranges = [ self.new_range_constraint(lb, exp, ub, name) for lb, exp, ub, name in izip(lbs, exprs, ubs, names) ] # else: # ranges = [self.new_range_constraint(lb, exp, ub) for lb, exp, ub in izip(lbs, exprs, ubs)] self._post_constraint_block(ranges) return ranges
def new_var_df(self, keys1, keys2, vartype, lb=None, ub=None, name=None): # pragma: no cover try: from pandas import DataFrame except ImportError: DataFrame = None self.fatal('make_var_df() requires pandas module - not found') _, row_keys = self.make_key_seq(keys1, name) _, col_keys = self.make_key_seq(keys2, name) matrix_keys = [(k1, k2) for k1 in row_keys for k2 in col_keys] ctn = self._new_var_container(vartype, key_list=matrix_keys, lb=lb, ub=ub, name=name) lvars = self.new_var_list(ctn, matrix_keys, vartype, lb, ub, name, dimension=2, key_format=None) # TODO: see how to do without this temp dict dd = dict(izip(matrix_keys, lvars)) # row-oriented dict rowd = { row_k: [dd[row_k, col_k] for col_k in col_keys] for row_k in row_keys } vdtf = DataFrame.from_dict(rowd, orient='index', dtype='object') # convert to string or not? vdtf.columns = col_keys return vdtf
def new_batch_indicator_constraints(self, bvars, linear_cts, active_values, names): return [ self.new_indicator_constraint(bv, lct, active, name) for bv, lct, active, name in izip(bvars, linear_cts, active_values, names) ]
def _build_linear_expr_from_sparse_pair(lfactory, var_map, cpx_sparsepair): expr = lfactory.linear_expr(arg=0, safe=True) for ix, k in izip(cpx_sparsepair.ind, cpx_sparsepair.val): dv = var_map[ix] expr._add_term(dv, k) return expr
def read(cls, filename, model_name=None, verbose=False, model_class=None, **kwargs): """ Reads a model from a CPLEX export file. Accepts all formats exported by CPLEX: LP, SAV, MPS. If an error occurs while reading the file, the message of the exception is printed and the function returns None. Args: filename: The file to read. model_name: An optional name for the newly created model. If None, the model name will be the path basename. verbose: An optional flag to print informative messages, default is False. model_class: An optional class type; must be a subclass of Model. The returned model is built using this model_class and the keyword arguments kwargs, if any. By default, the model is class is `Model` (see kwargs: A dict of keyword-based arguments that are used when creating the model instance. Example: `m = read_model("c:/temp/foo.mps", model_name="docplex_foo", solver_agent="docloud", output_level=100)` Returns: An instance of Model, or None if an exception is raised. See Also: :class:`docplex.mp.model.Model` """ if not os.path.exists(filename): raise IOError("* file not found: {0}".format(filename)) # extract basename if model_name: name_to_use = model_name else: basename = os.path.basename(filename) if '.' not in filename: raise RuntimeError( 'ModelReader.read_model(): path has no extension: {}'. format(filename)) dotpos = basename.find(".") if dotpos > 0: name_to_use = basename[:dotpos] else: # pragma: no cover name_to_use = basename model_class = model_class or Model if 0 == os.stat(filename).st_size: print("* file is empty: {0} - exiting".format(filename)) return model_class(name=name_to_use, **kwargs) if verbose: print("-> CPLEX starts reading file: {0}".format(filename)) cpx_adapter = cls._read_cplex(filename) cpx = cpx_adapter.cpx if verbose: print("<- CPLEX finished reading file: {0}".format(filename)) if not cpx: # pragma: no cover return None final_output_level = kwargs.get("output_level", "info") debug_read = kwargs.get("debug", False) try: # force no tck if 'checker' in kwargs: final_checker = kwargs['checker'] else: final_checker = 'default' # build the model with no checker, then restore final_checker in the end. kwargs['checker'] = 'off' ignore_names = kwargs.get('ignore_names', False) # ------------- mdl = model_class(name=name_to_use, **kwargs) lfactory = mdl._lfactory qfactory = mdl._qfactory mdl.set_quiet() # output level set to ERROR vartype_cont = mdl.continuous_vartype vartype_map = { 'B': mdl.binary_vartype, 'I': mdl.integer_vartype, 'C': mdl.continuous_vartype, 'S': mdl.semicontinuous_vartype } # 1 upload variables cpx_nb_vars = cpx.variables.get_num() def make_constant_expr(k): if k: return lfactory._new_safe_constant_expr(k) else: return lfactory.new_zero_expr() if verbose: print("-- uploading {0} variables...".format(cpx_nb_vars)) cpx_var_names = [] if ignore_names else cls._safe_call_get_names( cpx_adapter, cpx.variables.get_names) if cpx._is_MIP(): cpx_vartypes = [ vartype_map.get(cpxt, vartype_cont) for cpxt in cpx.variables.get_types() ] else: cpx_vartypes = [vartype_cont] * cpx_nb_vars cpx_var_lbs = cpx.variables.get_lower_bounds() cpx_var_ubs = cpx.variables.get_upper_bounds() # map from cplex variable indices to docplex's # use to skip range vars # cplex : [x, Rg1, y] -> {0:0, 2: 1} if cpx_var_names: model_varnames = cpx_var_names else: model_varnames = [None] * cpx_nb_vars model_lbs = cpx_var_lbs model_ubs = cpx_var_ubs model_types = cpx_vartypes # vars model_vars = lfactory.new_multitype_var_list( cpx_nb_vars, model_types, model_lbs, model_ubs, model_varnames) # inverse map from indices to docplex vars cpx_var_index_to_docplex = { v: model_vars[v] for v in range(cpx_nb_vars) } # 2. upload linear constraints and ranges (mixed in cplex) cpx_linearcts = cpx.linear_constraints nb_linear_cts = cpx_linearcts.get_num() # all_rows1 = cpx_linearcts.get_rows() all_rows = cpx_adapter.fast_get_rows(cpx) all_rhs = cpx_linearcts.get_rhs() all_senses = cpx_linearcts.get_senses() all_range_values = cpx_linearcts.get_range_values() cpx_ctnames = [] if ignore_names else cls._safe_call_get_names( cpx_adapter, cpx_linearcts.get_names) deferred_cts = [] if verbose: print("-- uploading {0} linear constraints...".format( nb_linear_cts)) for c in range(nb_linear_cts): row = all_rows[c] sense = all_senses[c] rhs = all_rhs[c] ctname = cpx_ctnames[c] if cpx_ctnames else None range_val = all_range_values[c] indices, coefs = row expr = cls._make_expr_from_varmap_coefs( lfactory, cpx_var_index_to_docplex, indices, coefs) if sense == 'R': # rangeval can be negative !!! issue 52 if range_val >= 0: range_lb = rhs range_ub = rhs + range_val else: range_ub = rhs range_lb = rhs + range_val rgct = mdl.range_constraint(lb=range_lb, ub=range_ub, expr=expr, rng_name=ctname) deferred_cts.append(rgct) else: op = cls.parse_sense(sense) rhs_expr = make_constant_expr(rhs) ct = LinearConstraint(mdl, expr, op, rhs_expr, ctname) deferred_cts.append(ct) if deferred_cts: # add constraint as a block lfactory._post_constraint_block(posted_cts=deferred_cts) # 3. upload Quadratic constraints cpx_quadraticcts = cpx.quadratic_constraints nb_quadratic_cts = cpx_quadraticcts.get_num() if nb_quadratic_cts: all_rhs = cpx_quadraticcts.get_rhs() all_linear_nb_non_zeros = cpx_quadraticcts.get_linear_num_nonzeros( ) all_linear_components = cpx_quadraticcts.get_linear_components( ) all_quadratic_nb_non_zeros = cpx_quadraticcts.get_quad_num_nonzeros( ) all_quadratic_components = cpx_quadraticcts.get_quadratic_components( ) all_senses = cpx_quadraticcts.get_senses() cpx_ctnames = [] if ignore_names else cls._safe_call_get_names( cpx_adapter, cpx_quadraticcts.get_names) for c in range(nb_quadratic_cts): rhs = all_rhs[c] linear_nb_non_zeros = all_linear_nb_non_zeros[c] linear_component = all_linear_components[c] quadratic_nb_non_zeros = all_quadratic_nb_non_zeros[c] quadratic_component = all_quadratic_components[c] sense = all_senses[c] ctname = cpx_ctnames[c] if cpx_ctnames else None if linear_nb_non_zeros > 0: indices, coefs = linear_component.unpack() # linexpr = mdl._aggregator._scal_prod((cpx_var_index_to_docplex[idx] for idx in indices), coefs) linexpr = cls._make_expr_from_varmap_coefs( lfactory, cpx_var_index_to_docplex, indices, coefs) else: linexpr = None if quadratic_nb_non_zeros > 0: qfactory = mdl._qfactory ind1, ind2, coefs = quadratic_component.unpack() quads = qfactory.term_dict_type() for idx1, idx2, coef in izip(ind1, ind2, coefs): quads[VarPair( cpx_var_index_to_docplex[idx1], cpx_var_index_to_docplex[idx2])] = coef else: # pragma: no cover # should not happen, but who knows quads = None quad_expr = mdl._aggregator._quad_factory.new_quad( quads=quads, linexpr=linexpr, safe=True) op = ComparisonType.cplex_ctsense_to_python_op(sense) ct = op(quad_expr, rhs) mdl.add_constraint(ct, ctname) # 4. upload indicators cpx_indicators = cpx.indicator_constraints nb_indicators = cpx_indicators.get_num() if nb_indicators: all_ind_names = [] if ignore_names else cls._safe_call_get_names( cpx_adapter, cpx_indicators.get_names) all_ind_bvars = cpx_indicators.get_indicator_variables() all_ind_rhs = cpx_indicators.get_rhs() all_ind_linearcts = cpx_indicators.get_linear_components() all_ind_senses = cpx_indicators.get_senses() all_ind_complemented = cpx_indicators.get_complemented() all_ind_types = cpx_indicators.get_types() ind_equiv_type = 3 for i in range(nb_indicators): ind_bvar = all_ind_bvars[i] ind_name = all_ind_names[i] if all_ind_names else None ind_rhs = all_ind_rhs[i] ind_linear = all_ind_linearcts[i] # SparsePair(ind, val) ind_sense = all_ind_senses[i] ind_complemented = all_ind_complemented[i] ind_type = all_ind_types[i] # 1 . check the bvar is ok ind_bvar = cpx_var_index_to_docplex[ind_bvar] # each var appears once ind_linexpr = cls._build_linear_expr_from_sparse_pair( lfactory, cpx_var_index_to_docplex, ind_linear) op = ComparisonType.cplex_ctsense_to_python_op(ind_sense) ind_lct = op(ind_linexpr, ind_rhs) if ind_type == ind_equiv_type: logct = lfactory.new_equivalence_constraint( ind_bvar, ind_lct, true_value=1 - ind_complemented, name=ind_name) else: logct = lfactory.new_indicator_constraint( ind_bvar, ind_lct, true_value=1 - ind_complemented, name=ind_name) mdl.add(logct) # 5. upload Piecewise linear constraints try: cpx_pwl = cpx.pwl_constraints cpx_pwl_defs = cpx_pwl.get_definitions() pwl_fallback_names = [""] * cpx_pwl.get_num() cpx_pwl_names = pwl_fallback_names if ignore_names else cls._safe_call_get_names( cpx_adapter, cpx_pwl.get_names, pwl_fallback_names) for (vary_idx, varx_idx, preslope, postslope, breakx, breaky), pwl_name in izip(cpx_pwl_defs, cpx_pwl_names): varx = cpx_var_index_to_docplex.get(varx_idx, None) vary = cpx_var_index_to_docplex.get(vary_idx, None) breakxy = [(brkx, brky) for brkx, brky in zip(breakx, breaky)] pwl_func = mdl.piecewise(preslope, breakxy, postslope, name=pwl_name) pwl_expr = mdl._lfactory.new_pwl_expr( pwl_func, varx, 0, add_counter_suffix=False, resolve=False) pwl_expr._f_var = vary pwl_expr._ensure_resolved() except AttributeError: # pragma: no cover pass # Do not check for PWLs if Cplex version does not support them # 6. upload objective # noinspection PyPep8 try: cpx_multiobj = cpx.multiobj except AttributeError: # pragma: no cover # pre-12.9 version cpx_multiobj = None if cpx_multiobj is None or cpx_multiobj.get_num() <= 1: cpx_obj = cpx.objective cpx_sense = cpx_obj.get_sense() cpx_all_lin_obj_coeffs = cpx_obj.get_linear() all_obj_vars = [] all_obj_coefs = [] for v in range(cpx_nb_vars): if v in cpx_var_index_to_docplex: obj_coeff = cpx_all_lin_obj_coeffs[v] all_obj_coefs.append(obj_coeff) all_obj_vars.append(cpx_var_index_to_docplex[v]) # obj_expr = mdl._aggregator._scal_prod(all_obj_vars, all_obj_coefs) obj_expr = cls._make_expr_from_vars_coefs( mdl, all_obj_vars, all_obj_coefs) if cpx_obj.get_num_quadratic_variables() > 0: cpx_all_quad_cols_coeffs = cpx_obj.get_quadratic() quads = qfactory.term_dict_type() for v, col_coefs in izip(cpx_var_index_to_docplex, cpx_all_quad_cols_coeffs): var1 = cpx_var_index_to_docplex[v] indices, coefs = col_coefs.unpack() for idx, coef in izip(indices, coefs): vp = VarPair(var1, cpx_var_index_to_docplex[idx]) quads[vp] = quads.get(vp, 0) + coef / 2 obj_expr += qfactory.new_quad(quads=quads, linexpr=None) obj_expr += cpx.objective.get_offset() is_maximize = cpx_sense == cpx_adapter.cplex_module._internal._subinterfaces.ObjSense.maximize if is_maximize: mdl.maximize(obj_expr) else: mdl.minimize(obj_expr) else: # we have multiple objective nb_multiobjs = cpx_multiobj.get_num() exprs = [0] * nb_multiobjs priorities = [1] * nb_multiobjs weights = [1] * nb_multiobjs abstols = [0] * nb_multiobjs reltols = [0] * nb_multiobjs names = cpx_multiobj.get_names() for m in range(nb_multiobjs): (obj_coeffs, obj_offset, weight, prio, abstol, reltol) = cpx_multiobj.get_definition(m) obj_expr = cls._make_expr_from_coef_vector( mdl, cpx_var_index_to_docplex, obj_coeffs, obj_offset) exprs[m] = obj_expr priorities[m] = prio weights[m] = weight abstols[m] = abstol reltols[m] = reltol sense = cpx_multiobj.get_sense() mdl.set_multi_objective(sense, exprs, priorities, weights, abstols, reltols, names) # upload sos cpx_sos = cpx.SOS cpx_sos_num = cpx_sos.get_num() if cpx_sos_num > 0: cpx_sos_types = cpx_sos.get_types() cpx_sos_indices = cpx_sos.get_sets() cpx_sos_names = cpx_sos.get_names() if not cpx_sos_names: cpx_sos_names = [None] * cpx_sos_num for sostype, sos_sparse, sos_name in izip( cpx_sos_types, cpx_sos_indices, cpx_sos_names): sos_var_indices = sos_sparse.ind sos_weights = sos_sparse.val isostype = int(sostype) sos_vars = [ cpx_var_index_to_docplex[var_ix] for var_ix in sos_var_indices ] mdl.add_sos(dvars=sos_vars, sos_arg=isostype, name=sos_name, weights=sos_weights) # upload lazy constraints cpx_linear_advanced = cpx.linear_constraints.advanced cpx_lazyct_num = cpx_linear_advanced.get_num_lazy_constraints() if cpx_lazyct_num: print( "WARNING: found {0} lazy constraints that cannot be uploaded to DOcplex" .format(cpx_lazyct_num)) mdl.output_level = final_output_level if final_checker: # need to restore checker mdl.set_checker(final_checker) except cpx_adapter.CplexError as cpx_e: # pragma: no cover print("* CPLEX error: {0!s} reading file {1}".format( cpx_e, filename)) mdl = None if debug_read: raise except ModelReaderError as mre: # pragma: no cover print("! Model reader error: {0!s} while reading file {1}".format( mre, filename)) mdl = None if debug_read: raise except DOcplexException as doe: # pragma: no cover print("! Internal DOcplex error: {0!s} while reading file {1}". format(doe, filename)) mdl = None if debug_read: raise # except Exception as any_e: # pragma: no cover # print("Internal exception raised: {0} msg={1!s} while reading file '{2}'".format(type(any_e), any_e, filename)) # mdl = None # if debug_read: # raise finally: # clean up CPLEX instance... cpx.end() return mdl
def read_model(self, filename, model_name=None, verbose=False, model_class=None, **kwargs): """ Reads a model from a CPLEX export file. Accepts all formats exported by CPLEX: LP, SAV, MPS. If an error occurs while reading the file, the message of the exception is printed and the function returns None. Args: filename: The file to read. model_name: An optional name for the newly created model. If None, the model name will be the path basename. verbose: An optional flag to print informative messages, default is False. model_class: An optional class type; must be a subclass of Model. The returned model is built using this model_class and the keyword arguments kwargs, if any. By default, the model is class is `Model` (see kwargs: A dict of keyword-based arguments that are used when creating the model instance. Example: `m = read_model("c:/temp/foo.mps", model_name="docplex_foo", solver_agent="docloud", output_level=100)` Returns: An instance of Model, or None if an exception is raised. See Also: :class:`docplex.mp.model.Model` """ if not Cplex: # pragma: no cover raise RuntimeError( "ModelReader.read_model() requires CPLEX runtime.") if not os.path.exists(filename): raise IOError("* file not found: {0}".format(filename)) # extract basename if model_name: name_to_use = model_name else: basename = os.path.basename(filename) dotpos = basename.find(".") if dotpos > 0: name_to_use = basename[:dotpos] else: name_to_use = basename model_class = model_class or Model if 0 == os.stat(filename).st_size: print("* file is empty: {0} - exiting".format(filename)) return model_class(name=name_to_use, **kwargs) # print("-> start reading file: {0}".format(filename)) cpx = self._cplex_read(filename, verbose=verbose) if not cpx: # pragma: no cover return None range_map = {} final_output_level = kwargs.get("output_level", "info") debug_read = kwargs.get("debug", False) try: # force no tck if 'checker' in kwargs: final_checker = kwargs['checker'] else: final_checker = 'default' # build the model with no checker, then restore final_checker in the end. kwargs['checker'] = 'off' # ------------- mdl = model_class(name=name_to_use, **kwargs) lfactory = mdl._lfactory qfactory = mdl._qfactory mdl.set_quiet() # output level set to ERROR vartype_cont = mdl.continuous_vartype vartype_map = { 'B': mdl.binary_vartype, 'I': mdl.integer_vartype, 'C': mdl.continuous_vartype, 'S': mdl.semicontinuous_vartype } # 1 upload variables cpx_nb_vars = cpx.variables.get_num() cpx_var_names = self._safe_call_get_names(cpx.variables) if cpx._is_MIP(): cpx_vartypes = [ vartype_map.get(cpxt, vartype_cont) for cpxt in cpx.variables.get_types() ] else: cpx_vartypes = [vartype_cont] * cpx_nb_vars cpx_var_lbs = cpx.variables.get_lower_bounds() cpx_var_ubs = cpx.variables.get_upper_bounds() # map from cplex variable indices to docplex's # use to skip range vars # cplex : [x, Rg1, y] -> {0:0, 2: 1} var_index_map = {} d = 0 model_varnames = [] model_lbs = [] model_ubs = [] model_types = [] for v in range(cpx_nb_vars): varname = cpx_var_names[v] if cpx_var_names else None if varname and varname.startswith("Rg"): # generated var for ranges range_map[v] = self._RangeData(var_index=v, var_name=varname, ub=cpx_var_ubs[v]) else: # docplex_var = lfactory.new_var(vartype, lb, ub, varname) var_index_map[v] = d model_varnames.append(varname) model_types.append(cpx_vartypes[v]) model_lbs.append(cpx_var_lbs[v]) model_ubs.append(cpx_var_ubs[v]) d += 1 # vars model_vars = lfactory.new_multitype_var_list( d, model_types, model_lbs, model_ubs, model_varnames) cpx_var_index_to_docplex = { v: model_vars[var_index_map[v]] for v in var_index_map.keys() } # 2. upload linear constraints and ranges (mixed in cplex) cpx_linearcts = cpx.linear_constraints nb_linear_cts = cpx_linearcts.get_num() all_rows = cpx_linearcts.get_rows() all_rhs = cpx_linearcts.get_rhs() all_senses = cpx_linearcts.get_senses() all_range_values = cpx_linearcts.get_range_values() cpx_ctnames = self._safe_call_get_names(cpx_linearcts) has_range = range_map or any(s == "R" for s in all_senses) deferred_cts = [] for c in range(nb_linear_cts): row = all_rows[c] sense = all_senses[c] rhs = all_rhs[c] ctname = cpx_ctnames[c] if cpx_ctnames else None range_val = all_range_values[c] indices = row.ind coefs = row.val range_data = None if not has_range: expr = mdl._aggregator._scal_prod( (cpx_var_index_to_docplex[idx] for idx in indices), coefs) op = ComparisonType.parse(sense) ct = lfactory._new_binary_constraint(lhs=expr, rhs=rhs, sense=op) ct.name = ctname deferred_cts.append(ct) else: expr = lfactory.linear_expr() rcoef = 1 for idx, koef in izip(indices, coefs): var = cpx_var_index_to_docplex.get(idx, None) if var: expr._add_term(var, koef) elif idx in range_map: # this is a range: coeff must be 1 or -1 abscoef = koef if koef >= 0 else -koef rcoef = koef assert abscoef == 1, "range var has coef different from 1: {}".format( koef) assert range_data is None, "range_data is not None: {0!s}".format( range_data) # cannot use two range vars range_data = range_map[idx] else: # pragma: no cover # this is an internal error. raise ModelReaderError( "ERROR: index not in var map or range map: {0}" .format(idx)) if range_data: label = ctname or 'c#%d' % (c + 1) if sense not in "EL": # pragma: no cover raise ModelReaderError( "{0} range sense is not E: {1!s}".format( label, sense)) if rcoef < 0: # -1 actually rng_lb = rhs rng_ub = rhs + range_data.ub elif rcoef > 0: # koef is 1 here rng_lb = rhs - range_data.ub rng_ub = rhs else: # pragma: no cover raise ModelReaderError( "unexpected range coef: {}".format(rcoef)) mdl.add_range(lb=rng_lb, expr=expr, ub=rng_ub, rng_name=ctname) else: if sense == 'R': # range min is rangeval range_lb = rhs range_ub = rhs + range_val mdl.add_range(lb=range_lb, ub=range_ub, expr=expr, rng_name=ctname) else: op = ComparisonType.cplex_ctsense_to_python_op( sense) ct = op(expr, rhs) mdl.add_constraint(ct, ctname) if deferred_cts: # add constraint as a block lfactory._post_constraint_block(posted_cts=deferred_cts) # 3. upload Quadratic constraints cpx_quadraticcts = cpx.quadratic_constraints nb_quadratic_cts = cpx_quadraticcts.get_num() all_rhs = cpx_quadraticcts.get_rhs() all_linear_nb_non_zeros = cpx_quadraticcts.get_linear_num_nonzeros( ) all_linear_components = cpx_quadraticcts.get_linear_components() all_quadratic_nb_non_zeros = cpx_quadraticcts.get_quad_num_nonzeros( ) all_quadratic_components = cpx_quadraticcts.get_quadratic_components( ) all_senses = cpx_quadraticcts.get_senses() cpx_ctnames = self._safe_call_get_names(cpx_quadraticcts) for c in range(nb_quadratic_cts): rhs = all_rhs[c] linear_nb_non_zeros = all_linear_nb_non_zeros[c] linear_component = all_linear_components[c] quadratic_nb_non_zeros = all_quadratic_nb_non_zeros[c] quadratic_component = all_quadratic_components[c] sense = all_senses[c] ctname = cpx_ctnames[c] if cpx_ctnames else None if linear_nb_non_zeros > 0: indices, coefs = linear_component.unpack() linexpr = mdl._aggregator._scal_prod( (cpx_var_index_to_docplex[idx] for idx in indices), coefs) else: linexpr = None if quadratic_nb_non_zeros > 0: qfactory = mdl._qfactory ind1, ind2, coefs = quadratic_component.unpack() quads = qfactory.term_dict_type() for idx1, idx2, coef in izip(ind1, ind2, coefs): quads[VarPair(cpx_var_index_to_docplex[idx1], cpx_var_index_to_docplex[idx2])] = coef else: # pragma: no cover # should not happen, but who knows quads = None quad_expr = mdl._aggregator._quad_factory.new_quad( quads=quads, linexpr=linexpr, safe=True) op = ComparisonType.cplex_ctsense_to_python_op(sense) ct = op(quad_expr, rhs) mdl.add_constraint(ct, ctname) # 4. upload indicators cpx_indicators = cpx.indicator_constraints nb_indicators = cpx_indicators.get_num() all_ind_names = self._safe_call_get_names(cpx_indicators) all_ind_bvars = cpx_indicators.get_indicator_variables() all_ind_rhs = cpx_indicators.get_rhs() all_ind_linearcts = cpx_indicators.get_linear_components() all_ind_senses = cpx_indicators.get_senses() all_ind_complemented = cpx_indicators.get_complemented() lfactory = mdl._lfactory for i in range(nb_indicators): ind_bvar = all_ind_bvars[i] ind_name = all_ind_names[i] if all_ind_names else None ind_rhs = all_ind_rhs[i] ind_linear = all_ind_linearcts[i] # SparsePair(ind, val) ind_sense = all_ind_senses[i] ind_complemented = all_ind_complemented[i] # 1 . check the bvar is ok ind_bvar = cpx_var_index_to_docplex[ind_bvar] # each var appears once ind_linexpr = self._build_linear_expr_from_sparse_pair( lfactory, cpx_var_index_to_docplex, ind_linear) op = ComparisonType.cplex_ctsense_to_python_op(ind_sense) ind_ct = op(ind_linexpr, ind_rhs) indct = lfactory.new_indicator_constraint(ind_bvar, ind_ct, active_value=1 - ind_complemented, name=ind_name) mdl.add(indct) # 5. upload Piecewise linear constraints try: cpx_pwl = cpx.pwl_constraints cpx_pwl_defs = cpx_pwl.get_definitions() pwl_fallback_names = [""] * cpx_pwl.get_num() cpx_pwl_names = self._safe_call_get_names( cpx_pwl, pwl_fallback_names) for (vary_idx, varx_idx, preslope, postslope, breakx, breaky), pwl_name in izip(cpx_pwl_defs, cpx_pwl_names): varx = cpx_var_index_to_docplex.get(varx_idx, None) vary = cpx_var_index_to_docplex.get(vary_idx, None) breakxy = [(brkx, brky) for brkx, brky in zip(breakx, breaky)] pwl_func = mdl.piecewise(preslope, breakxy, postslope, name=pwl_name) pwl_expr = mdl._lfactory.new_pwl_expr( pwl_func, varx, 0, add_counter_suffix=False, resolve=False) pwl_expr._f_var = vary pwl_expr._ensure_resolved() except AttributeError: # pragma: no cover pass # Do not check for PWLs if Cplex version does not support them # 6. upload objective cpx_obj = cpx.objective cpx_sense = cpx_obj.get_sense() cpx_all_lin_obj_coeffs = cpx_obj.get_linear() # noinspection PyPep8 all_obj_vars = [] all_obj_coefs = [] for v in range(cpx_nb_vars): if v in cpx_var_index_to_docplex: obj_coeff = cpx_all_lin_obj_coeffs[v] all_obj_coefs.append(obj_coeff) all_obj_vars.append(cpx_var_index_to_docplex[v]) # obj_expr._add_term(idx_to_var_map[v], cpx_all_obj_coeffs[v]) obj_expr = mdl._aggregator._scal_prod(all_obj_vars, all_obj_coefs) if cpx_obj.get_num_quadratic_variables() > 0: cpx_all_quad_cols_coeffs = cpx_obj.get_quadratic() quads = qfactory.term_dict_type() for v, col_coefs in izip(cpx_var_index_to_docplex, cpx_all_quad_cols_coeffs): var1 = cpx_var_index_to_docplex[v] indices, coefs = col_coefs.unpack() for idx, coef in izip(indices, coefs): vp = VarPair(var1, cpx_var_index_to_docplex[idx]) quads[vp] = quads.get(vp, 0) + coef / 2 obj_expr += qfactory.new_quad(quads=quads, linexpr=None) obj_expr += cpx.objective.get_offset() is_maximize = cpx_sense == ObjSense.maximize if is_maximize: mdl.maximize(obj_expr) else: mdl.minimize(obj_expr) # upload sos cpx_sos = cpx.SOS cpx_sos_num = cpx_sos.get_num() if cpx_sos_num > 0: cpx_sos_types = cpx_sos.get_types() cpx_sos_indices = cpx_sos.get_sets() cpx_sos_names = cpx_sos.get_names() if not cpx_sos_names: cpx_sos_names = [None] * cpx_sos_num for sostype, sos_sparse, sos_name in izip( cpx_sos_types, cpx_sos_indices, cpx_sos_names): sos_var_indices = sos_sparse.ind isostype = int(sostype) sos_vars = [ cpx_var_index_to_docplex[var_ix] for var_ix in sos_var_indices ] mdl.add_sos(dvars=sos_vars, sos_arg=isostype, name=sos_name) # upload lazy constraints cpx_linear_advanced = cpx.linear_constraints.advanced cpx_lazyct_num = cpx_linear_advanced.get_num_lazy_constraints() if cpx_lazyct_num: print( "WARNING: found {0} lazy constraints that cannot be uploaded to DOcplex" .format(cpx_lazyct_num)) mdl.output_level = final_output_level if final_checker: # need to restore checker mdl.set_checker(final_checker) except CplexError as cpx_e: # pragma: no cover print("* CPLEX error: {0!s} reading file {1}".format( cpx_e, filename)) mdl = None if debug_read: raise except ModelReaderError as mre: # pragma: no cover print("! Model reader error: {0!s} while reading file {1}".format( mre, filename)) mdl = None if debug_read: raise except DOcplexException as doe: # pragma: no cover print("! Internal DOcplex error: {0!s} while reading file {1}". format(doe, filename)) mdl = None if debug_read: raise except Exception as any_e: # pragma: no cover print("Internal exception raised: {0!s} while reading file {1}". format(any_e, filename)) mdl = None if debug_read: raise finally: # clean up CPLEX instance... del cpx return mdl