class LPFormat(ExchangeFormat): __raw = " -+/\\<>" __cooked = "_mp____" from docplex.mp.compat23 import mktrans _str_translate_table = mktrans(__raw, __cooked) _unicode_translate_table = {} for c in range(len(__raw)): _unicode_translate_table[ord(__raw[c])] = ord(__cooked[c]) @classmethod def _translate_chars_py2(cls, raw_name): # noinspection PyUnresolvedReferences if isinstance(raw_name, unicode): char_mapping = cls._unicode_translate_table else: char_mapping = cls._str_translate_table return raw_name.translate(char_mapping) @classmethod def _translate_chars_py3(cls, raw_name): return raw_name.translate(cls._unicode_translate_table) # which translate_method to use if six.PY2: _translate_chars = _translate_chars_py2 else: # pragma: no cover _translate_chars = _translate_chars_py3 def __init__(self): ExchangeFormat.__init__(self, "LP", "lp", requires_cplex=False) lp_re = re.compile( r"[a-df-zA-DF-Z!#$%&()/,;?@_`'{}|\"][a-zA-Z0-9!#$%&()/.,;?@_`'{}|\"]*") @classmethod def is_lp_compliant(cls, name, lp_re=lp_re): if name: lp_match = lp_re.match(name) return lp_match and lp_match.start() == 0 and lp_match.end( ) == len(name) else: return False @classmethod def make_prefix_name(cls, prefix, local_index, offset=1): prefixed_name = "{0:s}{1:d}".format(prefix, local_index + offset) return prefixed_name @classmethod def lp_var_name(cls, dvar): return cls.lp_name(dvar.get_name(), "x", dvar.index) # @classmethod # def lp_ct_name(cls, linct): # return cls.lp_name(linct.get_name(), "c", linct.get_index()) @classmethod def is_truth_value_name(cls, name): return name.startswith('_bool{') @classmethod def lp_name(cls, raw_name, prefix, local_index, hide_names=False, noncompliant_hook=None): # anonymous constraints must be named in a LP (we follow CPLEX here) if hide_names or not raw_name: return cls.make_prefix_name(prefix, local_index, offset=1) # # swap blanks with underscores ws_name = cls._translate_chars(raw_name) if not cls.is_lp_compliant(ws_name): if ws_name[0] in 'eE': # fixing eE non-LP names fixed_name = '_' + ws_name if cls.is_lp_compliant(fixed_name): return fixed_name # -- stats # TODO: map truth values to LP names if not cls.is_truth_value_name(raw_name) and noncompliant_hook: noncompliant_hook(raw_name) # -- return cls.make_prefix_name(prefix, local_index, offset=1) else: # truncate if necessary, again this does nothing if name is too short return ws_name[:255]
class TextModelPrinter(ModelPrinter): DEFAULT_ENCODING = "ENCODING=ISO-8859-1" def __init__(self, comment_start, indent=1, hide_user_names=False, nb_digits_for_floats=3, encoding=DEFAULT_ENCODING, sort_variable_names=False): ModelPrinter.__init__(self) # should be elsewhere self.true_infinity = float('inf') self.line_width = 79 # noinspection PyArgumentEqualDefault self._comment_start = comment_start self._mangle_names = hide_user_names self._encoding = encoding # None is a valid value, in which case no encoding is printed # ----------------------- # TODO: refactor these maps as scope objects... self._var_name_map = {} self._linct_name_map = {} # linear constraints self._lc_name_map = {} # indicators have a seperate index space. self._qc_name_map = {} self._pwl_name_map = {} # ------------------------ self._rangeData = {} self._num_printer = _NumPrinter(nb_digits_for_floats) self._indent_level = indent self._indent_space = ' ' * indent self._indent_map = {1: ' '} self.sort_variable_names = sort_variable_names # which translate_method to use if six.PY2: self._translate_chars = self._translate_chars2 else: # pragma: no cover self._translate_chars = self._translate_chars3 def _get_indent_from_level(self, level): cached_indent = self._indent_map.get(level) if cached_indent is None: indent = ' ' * level self._indent_map[level] = indent return indent else: return cached_indent @property def nb_digits_for_floats(self): # pragma: no cover return self._num_printer.precision def mangle_names(self): """ Actually used to decide whether to encryupt or noyt :return: """ return self._mangle_names def set_mangle_names(self, mangled): self._mangle_names = mangled def is_mangling_names(self): return self._mangle_names def _print_line_comment(self, out, comment_text): out.write("%s %s\n" % (self._comment_start, comment_text)) def _print_encoding(self, out): """ prints the file encoding :return: """ if self._encoding: self._print_line_comment(out, self._encoding) def _print_model_name(self, out, mdl): """ Redefine this method to print the model name, if necessary :param mdl: the model to be printed :return: """ raise NotImplementedError # pragma: no cover def _print_signature(self, out): """ Prints a signature message denoting this file comes from Python Modeling Layer :return: """ self._print_line_comment(out, "This file has been generated by DOcplex") def _newline(self, out, nb_lines=1): for _ in range(nb_lines): out.write("\n") disambiguate_try_max = 1000 def _disambiguate(self, candidate_name, names, mobj, prefix, local_idx, try_max=disambiguate_try_max): # candidate_name is already in names # we coin successive names with index until the suffixed name is no longer in names k = 1 disambiguate_fmt = '%s##%d' cur_name = candidate_name while cur_name in names: if k >= try_max: # giving up return self._make_prefix_name(mobj, prefix, local_idx) cur_name = disambiguate_fmt % (candidate_name, k) k += 1 # -- return cur_name def _precompute_name_dict(self, mobj_seq, prefix): fixed_name_dir = {} all_names = set() hide_names = self.mangle_names() for local_index, mobj in enumerate(mobj_seq): fixed_name = self.fix_name(mobj, prefix, local_index, hide_names) if fixed_name: if fixed_name in all_names: # to disambiguate, start with name#index, then add ##1,2,3, seed_name = '%s#%d' % (fixed_name, mobj._index) fixed_name = self._disambiguate(seed_name, all_names, mobj, prefix, local_index) fixed_name_dir[mobj._index] = fixed_name all_names.add(fixed_name) return fixed_name_dir def _num_to_string(self, num): # INTERNAL return self._num_printer.to_string(num) def prepare(self, model): self._var_name_map = self._precompute_name_dict(model.iter_variables(), prefix='x') self._linct_name_map = self._precompute_name_dict( model.iter_linear_constraints(), prefix='c') self._lc_name_map = self._precompute_name_dict( model.iter_logical_constraints(), prefix='lc') self._qc_name_map = self._precompute_name_dict( model.iter_quadratic_constraints(), prefix='qc') self._pwl_name_map = self._precompute_name_dict( model.iter_pwl_constraints(), prefix='pwl') self._rangeData = {} for rng in model.iter_range_constraints(): # precompute data for ranges # 1 name ? # 2 rhs is lb - constant # 3 bounds are (0, ub-lb) varname = 'Rg%s' % self.linearct_print_name(rng) rhs = rng.cplex_num_rhs() rngval = rng.cplex_range_value(do_raise=False) self._rangeData[rng] = (varname, rhs, rngval) @staticmethod def fix_whitespace(name): """ Swaps white spaces by underscores. Names with no blanks are not copied. :param name: :return: """ return name.replace(" ", "_") def _var_print_name(self, dvar): # INTERNAL return self._var_name_map[dvar._index] def get_name_to_var_map(self, model): # INTERNAL name_to_var_map = {} for v in model.iter_variables(): name_to_var_map[self._var_name_map[v._index]] = v return name_to_var_map def print_name(self, obj, name_dict): return name_dict.get(obj._index) # default is None def linearct_print_name(self, ct): return self.print_name(ct, self._linct_name_map) def logicalct_print_name(self, indicator): return self.print_name(indicator, self._lc_name_map) def qc_print_name(self, quad_constraint): return self.print_name(quad_constraint, self._qc_name_map) @staticmethod def _make_prefix_name(mobj, prefix, local_index, offset=1): prefixed_name = "{0:s}{1:d}".format(prefix, local_index + offset) return prefixed_name from docplex.mp.compat23 import mktrans __raw = " -+/\\<>" __cooked = "_mp____" _str_translate_table = mktrans(__raw, __cooked) _unicode_translate_table = {} for c in range(len(__raw)): _unicode_translate_table[ord(__raw[c])] = ord(__cooked[c]) @staticmethod def _translate_chars2(raw_name): # noinspection PyUnresolvedReferences if isinstance(raw_name, unicode): char_mapping = TextModelPrinter._unicode_translate_table else: char_mapping = TextModelPrinter._str_translate_table return raw_name.translate(char_mapping) @staticmethod def _translate_chars3(raw_name): return raw_name.translate(TextModelPrinter._unicode_translate_table) def fix_name(self, mobj, prefix, local_index, hide_names): # INTERNAL raw_name = mobj.name if hide_names or mobj.is_generated() or not raw_name: return self._make_prefix_name(mobj, prefix, local_index, offset=1) else: return self._translate_chars(raw_name) @staticmethod def _generate_linear_obj_coefs(model, linear_obj_expr): # INTERNAL: print all variables and their coef in the linear part of the objective for v in model.iter_variables(): yield v, linear_obj_expr.unchecked_get_coef(v) @staticmethod def _generate_linear_obj_coefs_smart(model, linear_obj_expr, selected_variables): # INTERNAL: used to print unreferenced variables in sos and pwl constraints and their coef in the linear part # of the objective, in addition to variables in the objective for v in selected_variables: yield v, linear_obj_expr.unchecked_get_coef(v) def _print_lexpr(self, wrapper, num_printer, var_name_map, expr, print_constant=False, allow_empty=False, force_first_plus=False): # prints an expr to a stream term_iter = expr.iter_sorted_terms() k = expr.get_constant() if print_constant else None self._print_expr_iter(wrapper, num_printer, var_name_map, term_iter, constant=k, allow_empty=allow_empty, force_first_plus=force_first_plus) def _print_expr_iter(self, wrapper, num_printer, var_name_map, expr_iter, allow_empty=False, force_first_plus=False, constant=None, accept_zero=False): num2string_fn = num_printer.to_string c = 0 if self.sort_variable_names: def varname( x ): # use function, since auto tuple unpacking not supported in py3 v, _ = x return var_name_map[v._index] expr_iter = sorted(list(expr_iter), key=varname) for (v, coeff) in expr_iter: curr_token = '' if not accept_zero and not coeff: continue # pragma: no cover if coeff < 0: curr_token += '-' wrote_sign = True coeff = -coeff elif c > 0 or force_first_plus: # here coeff is positive, we write the '+' only if term is non-first curr_token += '+' wrote_sign = True else: wrote_sign = False if 1 != coeff: if wrote_sign: curr_token += ' ' curr_token += num2string_fn(coeff) if wrote_sign or 1 != coeff: curr_token += ' ' curr_token += var_name_map[v._index] wrapper.write(curr_token) c += 1 printed_k = True if constant is not None: # here constant is a number if constant: if constant > 0: if c > 0 or force_first_plus: wrapper.write('+') wrapper.write(num2string_fn(constant)) elif 0 == c and not allow_empty: wrapper.write('0') else: printed_k = False else: # constant is none here if not c and not allow_empty: # expr is empty, if we must print something, print 0 wrapper.write('0') else: printed_k = False return c or printed_k def _print_qexpr_obj(self, wrapper, num_printer, var_name_map, quad_expr, force_initial_plus, use_double=True): # writes a quadratic expression # in the form [ 2a_ij a_i.a_j ] / 2 # Note that all coefficients must be doubled due to the tQXQ formulation if force_initial_plus: wrapper.write('+') return self._print_qexpr_iter(wrapper, num_printer, var_name_map, quad_expr.iter_sorted_quads(), use_double=use_double) def _print_qexpr_iter(self, wrapper, num_printer, var_name_map, iter_quads, use_double=False): q = 0 varname_getter = self._var_print_name for qvp, qk in iter_quads: curr_token = '' if 0 == qk: continue # pragma: no cover if q == 0: wrapper.write('[') # only once abs_qk = qk if qk < 0: curr_token += '-' abs_qk = -qk wrote_sign = True elif q > 0: curr_token += '+' wrote_sign = True else: wrote_sign = False if wrote_sign: curr_token += ' ' # all coefficients must be doubled because of the []/2 pattern. abs_qk2 = 2 * abs_qk if use_double else abs_qk if abs_qk2 != 1: curr_token += num_printer.to_string(abs_qk2) curr_token += ' ' if qvp.is_square(): qv_name = varname_getter(qvp[0]) curr_token += "%s^2" % qv_name else: qv1 = qvp[0] qv2 = qvp[1] curr_token += "%s*%s" % (varname_getter(qv1), varname_getter(qv2)) wrapper.write(curr_token) q += 1 if q: closer = ']/2' if use_double else ']' wrapper.write(closer) return q