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 generate_rows(cls, coef_mat): if is_pandas_dataframe(coef_mat): row_gen = cls.generate_df_rows(coef_mat) elif is_numpy_matrix(coef_mat): row_gen = cls.generate_np_matrix_rows(coef_mat) else: row_gen = iter(coef_mat) return row_gen
def matrix_ranges(self, coef_mat, dvars, lbs, ubs): """ Creates a list of range constraints from a matrix of coefficients, a sequence of variables, and two sequence of numbers. This method returns the list of range constraints built from L <= Ax <= U where A is the coefficient matrix (of size (M,N)), X is the variable sequence (size N), L and B are sequence of numbers (resp. the lower and upper bounds of the ranges) both with size M. :param coef_mat: A matrix of coefficients with M rows and N columns. This argument accepts either a list of lists of numbers, a `numpy` array with size (M,N), or a `scipy` sparse matrix. :param dvars: An ordered sequence of decision variables: accepts a Python list, `numpy` array, or a `pandas` series. The size of the sequence must match the number of columns in the matrix. :param lbs: A sequence of numbers: accepts a Python list, a `numpy` array, or a `pandas` series. The size of the sequence must match the number of rows in the matrix. :param ubs: A sequence of numbers: accepts a Python list, a `numpy` array, or a `pandas` series. The size of the sequence must match the number of rows in the matrix. :returns: A list of range constraints. Example:: If A is a matrix of coefficients with 2 rows and 3 columns: A = [[1, 2, 3], [4, 5, 6]], X = [x, y, z] where x, y, and z are decision variables (size 3), and L = [101. 102], a sequence of numbers (size 2), U = [201, 202] then:: `mdl.range_constraints(A, X, L, U)` returns a list of two ranges [(101 <= x + 2y+3z <= 102), (201 <= 4x + 5y +6z <= 202)]. Note: If the dimensions of the matrix and variables or of the matrix and number sequence do not match, an error is raised. """ checker = self._checker if is_pandas_dataframe(coef_mat) or is_numpy_matrix( coef_mat) or is_scipy_sparse(coef_mat): nb_rows, nb_cols = coef_mat.shape else: # a sequence of sequences a_mat = list(coef_mat) nb_rows = len(a_mat) nb_cols = None try: shared_len = None for r in a_mat: checker.check_ordered_sequence(r, 'matrix_constraints') r_len = len(r) if shared_len is None: shared_len = r_len elif r_len != shared_len: self.fatal( 'All columns should have same length found {0} != {1}' .format(shared_len, r_len)) nb_cols = shared_len if shared_len is not None else 0 except AttributeError: self.fatal('All columns should have a len()') s_dvars = self._to_list(dvars, caller='Model.range_constraints()') s_lbs = self._to_list(lbs, caller='Model.range_constraints()') s_ubs = self._to_list(ubs, caller='Model.range_constraints()') # check checker.typecheck_var_seq(s_dvars) checker.typecheck_num_seq(s_lbs) checker.typecheck_num_seq(s_ubs) # --- # check dimensions and whether to transpose or not. # --- nb_vars = len(s_dvars) nb_lbs = len(s_lbs) nb_ubs = len(s_ubs) if nb_lbs != nb_rows: self.fatal( 'Incorrect size for range lower bounds, expecting: {1}, got: {0},' .format(nb_lbs, nb_rows)) if nb_ubs != nb_rows: self.fatal( 'Incorrect size for range upper bounds, expecting: {1}, got: {0}' .format(nb_ubs, nb_rows)) if nb_cols != nb_vars: self.fatal( 'Incorrect number of variables, expecting: {1}, got: {0}, matrix is ({0},{1})' .format(nb_vars, nb_cols, nb_rows, nb_cols)) if is_scipy_sparse(coef_mat): return self._aggregator._sparse_matrix_ranges( coef_mat, s_dvars, s_lbs, s_ubs) else: return self._aggregator._matrix_ranges(coef_mat, s_dvars, s_lbs, s_ubs)
def matrix_constraints(self, coef_mat, dvars, rhs, sense='le'): """ Creates a list of linear constraints from a matrix of coefficients, a sequence of variables, and a sequence of numbers. This method returns the list of constraints built from A.X <op> B where A is the coefficient matrix (of size (M,N)), X is the variable sequence (size N), and B is the sequence of right-hand side values (of size M). <op> is the comparison operator that defines the sense of the constraint. By default, this generates a 'less-than-or-equal' constraint. Example: `Model.scal_prod_vars_triple([x, y], [z, t], [2, 3])` returns the expression `2xz + 3yt`. :param coef_mat: A matrix of coefficients with M rows and N columns. This argument accepts either a list of lists of numbers, a `numpy` array with size (M,N), or a `scipy` sparse matrix. :param dvars: An ordered sequence of decision variables: accepts a Python list, `numpy` array, or a `pandas` series. The size of the sequence must match the number of columns in the matrix. :param rhs: A sequence of numbers: accepts a Python list, a `numpy` array, or a `pandas` series. The size of the sequence must match the number of rows in the matrix. :param sense: A constraint sense \; accepts either a value of type `ComparisonType` or a string (e.g 'le', 'eq', 'ge'). :returns: A list of linear constraints. Example: If A is a matrix of coefficients with 2 rows and 3 columns:: A = [[1, 2, 3], [4, 5, 6]], X = [x, y, z] where x, y, and z are decision variables (size 3), and B = [100, 200], a sequence of numbers (size 2), then:: `mdl.matrix_constraint(A, X, B, 'GE')` returns a list of two constraints [(x + 2y+3z <= 100), (4x + 5y +6z <= 200)]. Note: If the dimensions of the matrix and variables or of the matrix and number sequence do not match, an error is raised. """ checker = self._checker if is_pandas_dataframe(coef_mat) or is_numpy_matrix( coef_mat) or is_scipy_sparse(coef_mat): nb_rows, nb_cols = coef_mat.shape else: # a sequence of sequences a_mat = list(coef_mat) nb_rows = len(a_mat) nb_cols = None try: shared_len = None for r in a_mat: checker.check_ordered_sequence(r, 'matrix_constraints') r_len = len(r) if shared_len is None: shared_len = r_len elif r_len != shared_len: self.fatal( 'All columns should have same length found {0} != {1}' .format(shared_len, r_len)) nb_cols = shared_len if shared_len is not None else 0 except AttributeError: self.fatal('All columns should have a len()') s_dvars = self._to_list(dvars, caller='Model.matrix-constraints()') s_rhs = self._to_list(rhs, caller='Model.matrix-constraints()') # check checker.typecheck_var_seq(s_dvars) for k in s_rhs: checker.typecheck_num(k) op = ComparisonType.parse(sense) # --- # check dimensions and whether to transpose or not. # --- nb_rhs = len(s_rhs) nb_vars = len(s_dvars) if (nb_rows, nb_cols) != (nb_rhs, nb_vars): self.fatal( 'Dimension error, matrix is ({0},{1}), expecting ({3}, {2})'. format(nb_rows, nb_cols, nb_vars, nb_rhs)) if is_scipy_sparse(coef_mat): return self._aggregator._sparse_matrix_constraints( coef_mat, s_dvars, s_rhs, op) else: return self._aggregator._matrix_constraints( coef_mat, s_dvars, s_rhs, op)