def scal_prod_triple(self, left_terms, right_terms, coefs): used_coefs = None checker = self._model._checker if is_iterable(coefs, accept_string=False): used_coefs = coefs elif is_number(coefs): if coefs: used_coefs = generate_constant(coefs, count_max=None) else: return self.new_zero_expr() else: self._model.fatal( "scal_prod_triple expects iterable or number as coefficients, got: {0!r}", coefs) if is_iterable(left_terms): used_left = checker.typecheck_var_seq(left_terms) else: checker.typecheck_var(left_terms) used_left = generate_constant(left_terms, count_max=None) if is_iterable(right_terms): used_right = checker.typecheck_var_seq(right_terms) else: checker.typecheck_var(right_terms) used_right = generate_constant(right_terms, count_max=None) if used_coefs is not coefs and used_left is not left_terms and used_right is not right_terms: # LOOK return left_terms * right_terms * coefs return self._scal_prod_triple(coefs=used_coefs, left_terms=used_left, right_terms=used_right)
def equals_solution(self, other, check_models=False, check_explicit=False, obj_precision=1e-3, var_precision=1e-6): 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 math.fabs(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 math.fabs(self.objective_value - other.objective_value) >= obj_precision: return False else: # One solution is for multi-objective, and not the other return False for dvar, val in self.iter_var_values(): if check_explicit and not other.contains(dvar): return False this_val = self._get_var_value(dvar) other_val = other._get_var_value(dvar) if math.fabs(this_val - other_val) >= var_precision: return False for other_dvar, other_val in other.iter_var_values(): if check_explicit and not self.contains(other_dvar): return False this_val = self._get_var_value(other_dvar) other_val = other._get_var_value(other_dvar) if math.fabs(this_val - other_val) >= var_precision: return False return True
def scal_prod_triple_vars(self, left_terms, right_terms, coefs): """ Creates a quadratic expression from two lists of variables and a sequence of coefficients. This method sums all quadratic terms built by multiplying the i_th coefficient by the product of the i_th expression in `left_terms` and the i_th expression in `right_terms` This method is faster than the standard generic scalar quadratic product method due to the fact that it takes only variables and does not take expressions as arguments. Example: `Model.scal_prod_vars_triple([x, y], [z, t], [2, 3])` returns the expression `2xz + 3yt`. :param left_terms: A list or an iterator on variables. :param right_terms: A list or an iterator on variables. :param coefs: A list or an iterator on numbers or a number. :returns: An instance of :class:`docplex.mp.quad.QuadExpr` or 0. Note: If either list or iterator is empty, this method returns zero. """ used_coefs = None checker = self._checker nb_non_iterables = 0 if is_iterable(coefs, accept_string=False): used_coefs = coefs elif is_number(coefs): if coefs: used_coefs = generate_constant(coefs, count_max=None) nb_non_iterables += 1 else: return self._aggregator.new_zero_expr() else: self.fatal( "scal_prod_triple expects iterable or number as coefficients, got: {0!r}", coefs) if is_iterable(left_terms): used_left = checker.typecheck_var_seq(left_terms) else: nb_non_iterables += 1 checker.typecheck_var(left_terms) used_left = generate_constant(left_terms, count_max=None) if is_iterable(right_terms): used_right = checker.typecheck_var_seq(right_terms) else: nb_non_iterables += 1 checker.typecheck_var(right_terms) used_right = generate_constant(right_terms, count_max=None) if nb_non_iterables >= 3: return left_terms * right_terms * coefs else: return self._aggregator._scal_prod_triple_vars( left_terms=used_left, right_terms=used_right, coefs=used_coefs)
def check_list_pair_slope_breakx(logger, arg, anchor): if arg is None: logger.fatal("argument 'slopebreaksx' must be defined") if not is_iterable(arg): logger.fatal("not an iterable: {0!s}".format(arg)) if len(arg) == 0: return if isinstance(arg, tuple): # Encapsulate tuple argument into a list: this allows defining a PWL with a tuple if there is only # one element in its definition arg = [arg] prev_pair = None pprev_pair = None for pair in arg: if isinstance(pair, tuple): if len(pair) != 2: logger.fatal("invalid tuple in 'slopebreaksx': {0!s}. Each tuple must have 2 items.". format(pair)) PwlFunction.check_number(logger, pair[0]) PwlFunction.check_number(logger, pair[1]) else: logger.fatal("invalid item in 'slopebreaksx': {0!s}. Each item must be a (x, y) tuple.".format(pair)) if prev_pair is not None: if pair[1] < prev_pair[1]: logger.fatal("X coordinate in: {0!s} cannot be smaller than previous break abscisse: {1!s}.". format(pair, prev_pair)) if pprev_pair is not None and pair[1] == prev_pair[1] and prev_pair[1] == pprev_pair[1]: logger.fatal( "invalid break: {0!s}. There cannot be more than 2 consecutive breaks with same abscisse.". format(pair)) if pair[1] == prev_pair[1] and anchor[0] == pair[1]: logger.fatal("anchor {0!s} cannot be defined at discontinuity point: {1!s}". format(anchor, pair)) pprev_pair = prev_pair prev_pair = pair
def make_key_seq(self, keys, name): # INTERNAL Takes as input a candidate keys input and returns a valid key sequence used_name = name check_keys = True used_keys = [] if is_iterable(keys): if is_pandas_dataframe(keys): used_keys = keys.index.values elif has_len(keys): used_keys = keys elif is_iterator(keys): used_keys = list(keys) else: # TODO: make a test for this case. self.fatal( "Cannot handle iterable var keys: {0!s} : no len() and not an iterator", keys) # pragma: no cover elif is_int(keys) and keys >= 0: # if name is str and we have a size, disable automatic names used_name = None if name is str else name used_keys = range(keys) check_keys = False else: self.fatal( "Unexpected var keys: {0!s}, expecting iterable or integer", keys) # pragma: no cover if check_keys and len( used_keys ): # do not check truth value of used_keys: can be a Series! self._checker.typecheck_key_seq(used_keys) return used_name, used_keys
def _lazy_compute_name_string(self): if self._name_str is not None: return self._name_str else: raw_name = self._name if is_string(raw_name): # drop opl-style formats s_name = raw_name.replace("({%s})", "") # purge fields pos_pct = raw_name.find('%') if pos_pct >= 0: s_name = raw_name[:pos_pct - 1] elif raw_name.find('{') > 0: pos = raw_name.find('{') s_name = raw_name[:pos - 1] elif is_iterable(raw_name): from os.path import commonprefix s_name = commonprefix(raw_name) else: # try a function from os.path import commonprefix namefn = raw_name try: all_names = [namefn(k) for k in self.iter_keys()] s_name = commonprefix(all_names) except TypeError: s_name = '' self._name_str = s_name return s_name
def _print_to_stream2(cls, out, solutions, write_level, use_lp_names, effort_level=None): # no kwargs at this stage. # solutions can be either a plain solution or a sequence or an iterator if not is_iterable(solutions): cls.print_one_solution(solutions, out, use_lp_names=use_lp_names, write_level=write_level, effort_level=effort_level) else: sol_seq = list(solutions) nb_solutions = len(sol_seq) assert nb_solutions > 0 if 1 == nb_solutions: cls.print_one_solution(sol_seq[0], out, use_lp_names, write_level=write_level, effort_level=effort_level[0]) else: cls.print_many_solutions(sol_seq, out, use_lp_names, write_level, effort_level)
def print_many_solutions(cls, sol_seq, out, use_lp_names, write_level, effort_level=None): osa = OutputStreamAdapter(out) osa.write(cls.mst_header) cls.print_signature(out, write_level) # <CPLEXSolutions version="1.0"> osa.write(cls.many_solution_start_tag) osa.write("\n") efforts = [EffortLevel.Auto] if effort_level is None: pass elif is_iterable(effort_level): efforts = effort_level else: efforts = [effort_level] for sol, effort in izip_longest(sol_seq, efforts, fillvalue=EffortLevel.Auto): cls.print_one_solution(sol, out, print_header=False, use_lp_names=use_lp_names, write_level=write_level, effort_level=effort) # <CPLEXSolutions version="1.0"> osa.write(cls.many_solution_end_tag) osa.write("\n")
def check_list_pair_breaksxy(logger, arg): if not is_iterable(arg): logger.fatal("argument 'breaksxy' expects iterable, {0!r} was passed".format(arg)) if isinstance(arg, tuple): # Encapsulate tuple argument into a list: this allows defining a PWL with a tuple if there is only # one element in its definition arg = [arg] if len(arg) == 0: logger.fatal("argument 'breaksxy' must be a non-empty list of (x, y) tuples.") prev_pair = None pprev_pair = None for pair in arg: if isinstance(pair, tuple): if len(pair) != 2: logger.fatal("invalid tuple in 'breaksxy': {0!s}. Each tuple must have 2 items.".format(pair)) PwlFunction.check_number(logger, pair[0]) PwlFunction.check_number(logger, pair[1]) else: logger.fatal("invalid item in 'breaksxy': {0!s}. Each item must be a (x, y) tuple.".format(pair)) if prev_pair is not None: if pair[0] < prev_pair[0]: logger.fatal("X coordinate in: {0!s} cannot be smaller than previous break abscisse: {1!s}.". format(pair, prev_pair)) if pprev_pair is not None and pair[0] == prev_pair[0] and prev_pair[0] == pprev_pair[0]: logger.fatal( "invalid break: {0!s}. There cannot be more than 2 consecutive breaks with same abscisse.". format(pair)) pprev_pair = prev_pair prev_pair = pair
def typecheck_optional_num_seq(cls, mdl, nums, accept_none=True, expected_size=None, caller=None): # INTERNAL # accepting either a num an iterable, or None (if accept_none if nums is None and accept_none: return nums elif is_iterable(nums, accept_string=False): nums_ = mdl._checker.typecheck_num_seq(nums, caller) lnums = list(nums_) if expected_size is not None: if len(lnums) != expected_size: caller_string = '' if not caller else '%s: ' % caller mdl.fatal( '{0}Expecting a sequence of numbers of size {1}, actual size is {2}', caller_string, expected_size, len(lnums)) return lnums elif is_number(nums): if expected_size is not None: return [nums] * expected_size else: return nums else: caller_string = '' if not caller else '%s: ' % caller msg = "{0}Expecting a number, a sequence of numbers{1}, {2!r} was passed" none_msg = ' or None' if accept_none else '' mdl.fatal(msg, caller_string, none_msg, nums)
def __init__(self, model, exprs, name=None): _IAdvancedExpr.__init__(self, model, name) if is_iterable(exprs) or is_iterator(exprs): self._exprs = exprs else: self._exprs = [model._lfactory._to_linear_operand(exprs)] # allocate xvars iff necessary self._xvars = [self._allocate_arg_var_if_necessary(e) for e in self._exprs]
def add_new_pattern(self, item_usages): """ makes a new pattern from a sequence of usages (one per item)""" assert is_iterable(item_usages) new_pattern_id = self.max_pattern_id + 1 new_pattern = TPattern(new_pattern_id, 1) self.patterns.append(new_pattern) self.max_pattern_id = new_pattern_id for used, item in zip(item_usages, self.items): self.pattern_item_filled[new_pattern, item] = used
def get_attribute(self, mobjs, attr, default_attr_value=0): assert is_iterable(mobjs) if attr not in self._attribute_map: # warn return [0] * len(mobjs) else: attr_map = self._attribute_map[attr] return [attr_map.get(mobj, default_attr_value) for mobj in mobjs]
def add_new_pattern(self, item_usages): """ makes a new pattern from a sequence of usages (one per item)""" assert is_iterable(item_usages) new_pattern_id = self.max_pattern_id + 1 new_pattern = TPattern(new_pattern_id, 1) self.patterns.append(new_pattern) self.max_pattern_id = new_pattern_id for used, item in zip(item_usages, self.items): self.pattern_item_filled[new_pattern, item] = used
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 add_new_pattern(self, item_usages): """ makes a new pattern from a sequence of usages (one per item)""" assert is_iterable(item_usages) new_pattern_id = self.max_pattern_id + 1 new_pattern = TPattern(new_pattern_id, 1) self.patterns.append(new_pattern) self.max_pattern_id = new_pattern_id for i in range(len(item_usages)): used = item_usages[i] item = self.items[i] self.pattern_item_filled[new_pattern, item] = used
def __init__(self, model, exprs, name=None): _FunctionalExpr.__init__(self, model, name) if is_iterable(exprs) or is_iterator(exprs): self._exprs = exprs else: self._exprs = [model._lfactory._to_linear_operand(exprs)] # allocate xvars iff necessary self._xvars = [ self._allocate_arg_var_if_necessary(expr, pos=e) for e, expr in enumerate(self._exprs, start=1) ]
def print(cls, out, solutions): # solutions can be either a plain solution or a sequence or an iterator if not is_iterable(solutions): cls.print_one_solution(solutions, out) else: sol_seq = list(solutions) nb_solutions = len(sol_seq) assert nb_solutions > 0 if 1 == nb_solutions: cls.print_one_solution(sol_seq[0], out) else: cls.print_many_solutions(sol_seq, out)
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 print_to_stream2(cls, out, solutions, indent=None, **kwargs): # solutions can be either a plain solution or a sequence or an iterator sol_to_print = list(solutions) if is_iterable(solutions) else [solutions] # encode all solutions in dict ready for json output encoder = SolutionJSONEncoder(**kwargs) solutions_as_dict = [encoder.default(sol) for sol in sol_to_print] # use an output stream adapter for py2/py3 and str/unicode compatibility osa = OutputStreamAdapter(out) if len(sol_to_print) == 1: # if only one solution, use at root node osa.write(json.dumps(solutions_as_dict[0], indent=indent)) else: # for multiple solutions, we want a "CPLEXSolutions" root osa.write(json.dumps({"CPLEXSolutions": solutions_as_dict}, indent=indent))
def docplex_sum(x_seq): if is_iterable(x_seq): if not x_seq: return 0 elif isinstance(x_seq, dict): return _docplex_sum_with_seq(x_seq.values()) elif is_indexable(x_seq): return _docplex_sum_with_seq(x_seq) else: return _docplex_sum_with_seq(list(x_seq)) elif x_seq: mdl = _docplex_extract_model(x_seq, do_raise=False) return mdl.to_linear_expr(x_seq) if mdl else x_seq else: return 0
def _sum_vars(self, dvars): if is_numpy_ndarray(dvars): return self._sum_vars(dvars.flat) elif is_pandas_series(dvars): return self.sum(dvars.values) elif isinstance(dvars, dict): # handle dict: sum all values return self._sum_vars(itervalues(dvars)) elif is_iterable(dvars): checked_dvars = self._checker.typecheck_var_seq(dvars, caller='Model.sumvars()') sumvars_terms = self._varlist_to_terms(checked_dvars) return self._to_expr(qcc=None, lcc=sumvars_terms) else: self._model.fatal('Model.sumvars() expects an iterable returning variables, {0!r} was passed', dvars)
def sumsq(self, sum_args): if is_iterable(sum_args): if is_iterator(sum_args): return self._sumsq(sum_args) elif isinstance(sum_args, dict): return self._sumsq(sum_args.values()) elif is_numpy_ndarray(sum_args): return self._sumsq(sum_args.flat) elif is_pandas_series(sum_args): return self._sumsq(sum_args.values) else: return self._sumsq(sum_args) elif is_number(sum_args): return sum_args ** 2 else: self._model.fatal("Model.sumsq() expects number/iterable/expression, got: {0!s}", sum_args)
def sum(self, sum_args): if is_iterator(sum_args): return self._sum_with_iter(sum_args) elif is_numpy_ndarray(sum_args): return self._sum_with_iter(sum_args.flat) elif is_pandas_series(sum_args): return self.sum(sum_args.values) elif isinstance(sum_args, dict): # handle dict: sum all values return self._sum_with_iter(itervalues(sum_args)) elif is_iterable(sum_args): return self._sum_with_seq(sum_args) elif is_number(sum_args): return sum_args else: return self._linear_factory._to_linear_expr(sum_args)
def scal_prod(self, terms, coefs=1.0): # Testing anumpy array for its logical value will not work: # ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() # we would have to trap the test for ValueError then call any() # if is_iterable(coefs): pass # ok elif is_number(coefs): if 0 == coefs: return self.new_zero_expr() else: sum_expr = self.sum(terms) return sum_expr * coefs else: self._model.fatal("scal_prod expects iterable or number, {0!s} was passed", coefs) # model has checked terms is an ordered sequence return self._scal_prod(terms, coefs)
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 __init__(self, model, exprs, name=None): _FunctionalExpr.__init__(self, model, name) assert is_iterable(exprs) or is_iterator(exprs) self._exprs = exprs # never allocate vars: arguments --are-- binary variables. self._xvars = exprs
def typecheck_iterable(self, arg): # INTERNAL: checks for an iterable if not is_iterable(arg): self.fatal("Expecting iterable, got: {0!s}", arg)