diff --git a/.DS_Store b/.DS_Store index 6992d9a..39ebc78 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/README.md b/README.md index 8393f4b..004f836 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + # Robust Stochastic Optimization Made Easy @@ -13,7 +13,7 @@ ![GitHub issues](https://img.shields.io/github/issues-raw/XiongPengNUS/rsome) - Website: [RSOME for Python](https://xiongpengnus.github.io/rsome/) -- PyPI: [RSOME 1.2.5](https://pypi.org/project/rsome/) +- PyPI: [RSOME 1.2.6](https://pypi.org/project/rsome/) RSOME (Robust Stochastic Optimization Made Easy) is an open-source Python package for generic modeling of optimization problems (subject to uncertainty). Models in RSOME are constructed by variables, constraints, and expressions that are formatted as N-dimensional arrays. These arrays are consistent with the NumPy library in terms of syntax and operations, including broadcasting, indexing, slicing, element-wise operations, and matrix calculation rules, among others. In short, RSOME provides a convenient platform to facilitate developments of robust optimization models and their applications. diff --git a/rsome/cpt_solver.py b/rsome/cpt_solver.py index 22571e9..760ca06 100644 --- a/rsome/cpt_solver.py +++ b/rsome/cpt_solver.py @@ -34,9 +34,7 @@ def solve(form, display=True, log=False, params={}): try: if xmat: - warnings.warn('The SOCP solver ignores exponential cone constraints. ') - # if lmi: - # warnings.warn('The SOCP solver ignores semidefinite cone constraints. ') + warnings.warn('The conic solver ignores exponential cone constraints. ') except AttributeError: pass @@ -52,14 +50,15 @@ def solve(form, display=True, log=False, params={}): linear_ineq = form.linear[~is_eq] linear_eq = form.linear[is_eq] num_ineq = linear_ineq.shape[0] - # num_eq = linear_eq.shape[0] const_ineq = form.const[~is_eq] const_eq = form.const[is_eq] num_constr, num_var = form.linear.shape - env = cp.Envr() + envconfig = cp.EnvrConfig() + envconfig.set('nobanner', '1') + env = cp.Envr(envconfig) m = env.createModel() m.setParam(cp.COPT.Param.Logging, log) m.setParam(cp.COPT.Param.LogToConsole, False) @@ -149,7 +148,14 @@ def solve(form, display=True, log=False, params={}): else: y = None - solution = Solution('COPT', objval, x_sol, status, stime, y=y) + xx_sol = np.zeros(len(form.vtype)) + xx_sol[idx_cont] = x_sol[:len(idx_cont)] + if idx_bin: + xx_sol[idx_bin] = x_sol[len(idx_cont): len(idx_cont)+len(idx_bin)] + if idx_int: + xx_sol[idx_int] = x_sol[len(idx_cont)+len(idx_bin):] + + solution = Solution('COPT', objval, xx_sol, status, stime, y=y) except cp.CoptError: warnings.warn('Fail to find the optimal solution.') # solution = None diff --git a/rsome/gcp.py b/rsome/gcp.py index b811a9e..feb6439 100644 --- a/rsome/gcp.py +++ b/rsome/gcp.py @@ -543,6 +543,7 @@ def to_socp(self, degree=4, cuts=(-30, 60)): left_width = self.linear.shape[1] qmat = self.qmat + lmi = self.lmi linear = self.linear const = self.const @@ -570,4 +571,5 @@ def to_socp(self, degree=4, cuts=(-30, 60)): qmat += [list(left_width + num_vars + np.array([2, 1, 0]) + q*3) for q in range(3+degree)] - return SOCProg(linear, const, sense, vtype, ub, lb, qmat, obj) + # return SOCProg(linear, const, sense, vtype, ub, lb, qmat, obj) + return GCProg(linear, const, sense, vtype, ub, lb, qmat, [], lmi, obj) diff --git a/rsome/lp.py b/rsome/lp.py index 0c40cc5..54a65a6 100644 --- a/rsome/lp.py +++ b/rsome/lp.py @@ -137,20 +137,20 @@ def concat(iters, axis=0): """ Join a sequence of arrays of affine expressions along an existing axis. - Parameters - ---------- - iters : array_like. - A sequence of RSOME variables or affine expressions. The arrays must - have the same shape, except in the dimension corresponding to `axis` - (the first, by default). + Parameters + ---------- + iters : array_like. + A sequence of RSOME variables or affine expressions. The arrays must + have the same shape, except in the dimension corresponding to `axis` + (the first, by default). - axis : int, optional - The axis along which the arrays will be joined. Default is 0. + axis : int, optional + The axis along which the arrays will be joined. Default is 0. - Returns - ------- - out : Affine - The concatenated array of affine expressions. + Returns + ------- + out : Affine + The concatenated array of affine expressions. """ linear_each = [] @@ -226,24 +226,24 @@ def rstack(*args): """ Stack a sequence of rows of affine expressions vertically (row wise). - Parameters - ---------- - arg : {list, Affine}. - Each arg represents an array of affine expressions. If arg is a list - of affine expressions, they will be concatenated horizontally (column - wise) first. + Parameters + ---------- + arg : {list, Affine}. + Each arg represents an array of affine expressions. If arg is a list + of affine expressions, they will be concatenated horizontally (column + wise) first. - Returns - ------- - out : Affine - The vertically stacked array of affine expressions. + Returns + ------- + out : Affine + The vertically stacked array of affine expressions. - Notes - ----- - The rstack function is different from the vstack function from the numpy - package in 1) the arrays to be stacked together are presented as separate - arguments, instead of elements in an array-like sequence; and 2) a list of - arrays can be stacked horizontally first before being vertically stacked. + Notes + ----- + The rstack function is different from the vstack function from the numpy + package in 1) the arrays to be stacked together are presented as separate + arguments, instead of elements in an array-like sequence; and 2) a list of + arrays can be stacked horizontally first before being vertically stacked. """ rows = [] @@ -260,24 +260,23 @@ def cstack(*args): """ Stack a sequence of rows of affine expressions horizontally (column wise). - Parameters - ---------- - arg : {list, Affine}. - Each arg represents an array of affine expressions. If arg is a list - of affine expressions, they will be concatenated vertically (row wise) - first. + Parameters + ---------- + arg : {list, Affine}. + Each arg represents an array of affine expressions. If arg is a list of + affine expressions, they will be concatenated vertically (row wise) first. - Returns - ------- - out : Affine - The horizontally stacked array of affine expressions. + Returns + ------- + out : Affine + The horizontally stacked array of affine expressions. - Notes - ----- - The cstack function is different from the hstack function from the numpy - package in 1) the arrays to be stacked together are presented as separate - arguments, instead of elements in an array-like sequence; and 2) a list of - arrays can be stacked vertically first before being horizontally stacked. + Notes + ----- + The cstack function is different from the hstack function from the numpy + package in 1) the arrays to be stacked together are presented as separate + arguments, instead of elements in an array-like sequence; and 2) a list of + arrays can be stacked vertically first before being horizontally stacked. """ cols = [] @@ -847,22 +846,72 @@ def get_ind(self): return np.array(range(self.first, self.first + self.size)) def reshape(self, shape): + """ + Returns an array containing the same variables with a new shape. + + Parameters + ---------- + shape : tuple + The new shape of the returned array. + + Returns + ------- + out : Affine + An array of the specified shape containing the given variables. + """ return self.to_affine().reshape(shape) def flatten(self): + """ + Returns a 1D array containing the same variables. + + Returns + ------- + out : Affine + A 1D array of the given variables. + """ return self.to_affine().flatten() def diag(self, k=0, fill=False): + """ + Return the diagonal elements of a 2-D array. + + Refer to `rsome.math.diag` for full documentation. + + See Also + -------- + rsome.math.diag : equivalent function + """ return self.to_affine().diag(k, fill) def tril(self, k=0): + """ + Return the lower triangular elements of a 2-D array. The remaining + elements are filled with zeros. + + Refer to `rsome.math.tril` for full documentation. + + See Also + -------- + rsome.math.tril : equivalent function + """ return self.to_affine().tril(k) def triu(self, k=0): + """ + Return the upper triangular elements of a 2-D array. The remaining + elements are filled with zeros. + + Refer to `rsome.math.triu` for full documentation. + + See Also + -------- + rsome.math.triu : equivalent function + """ return self.to_affine().triu(k) @@ -874,11 +923,11 @@ def norm(self, degree): """ Return the first, second, or infinity norm of a 1-D array. - Refer to `rsome.norm` for full documentation. + Refer to `rsome.math.norm` for full documentation. See Also -------- - rso.norm : equivalent function + rsome.math.norm : equivalent function """ return self.to_affine().norm(degree) @@ -887,11 +936,11 @@ def square(self): """ Return the element-wise square of an array. - Refer to `rsome.square` for full documentation + Refer to `rsome.math.square` for full documentation See Also -------- - rso.square : equivalent function + rsome.lp.square : equivalent function """ return self.to_affine().square() @@ -900,11 +949,11 @@ def sumsqr(self): """ Return the sum of squares of a 1-D array. - Refer to `rsome.sumsqr` for full documentation. + Refer to `rsome.math.sumsqr` for full documentation. See Also -------- - rso.sumsqr : equivalent function + rsome.math.sumsqr : equivalent function """ return self.to_affine().sumsqr() @@ -913,11 +962,11 @@ def quad(self, qmat): """ Return the quadratic expression var @ qmat @ var. - Refer to `rsome.quad` for full documentation. + Refer to `rsome.math.quad` for full documentation. See Also -------- - rso.quad : equivalent function + rsome.math.quad : equivalent function """ return self.to_affine().quad(qmat) @@ -926,11 +975,11 @@ def rsocone(self, y, z): """ Return the rotated second-order cone constraint. - Refer to `rsome.rsocone` for full documentation. + Refer to `rsome.math.rsocone` for full documentation. See Also -------- - rso.rsocone : equivalent function + rsome.math.rsocone : equivalent function """ return self.to_affine().rsocone(y, z) @@ -939,11 +988,11 @@ def expcone(self, x, z): """ Return the exponential cone constraint z*exp(x/z) <= var. - Refer to `rsome.expcone` for full documentation. + Refer to `rsome.math.expcone` for full documentation. See Also -------- - rso.expcone : equivalent function + rsome.math.expcone : equivalent function """ return self.to_affine().expcone(x, z) @@ -952,11 +1001,11 @@ def exp(self): """ Return the natural exponential function exp(var). - Refer to `rsome.exp` for full documentation. + Refer to `rsome.math.exp` for full documentation. See Also -------- - rso.exp : equivalent function + rsome.math.exp : equivalent function """ return self.to_affine().exp() @@ -966,11 +1015,11 @@ def pexp(self, scale): Return the perspective natural exponential function scale * exp(var/scale). - Refer to `rsome.pexp` for full documentation. + Refer to `rsome.math.pexp` for full documentation. See Also -------- - rso.pexp : equivalent function + rsome.math.pexp : equivalent function """ return self.to_affine().pexp(scale) @@ -979,11 +1028,11 @@ def log(self): """ Return the natural logarithm function log(var). - Refer to `rsome.log` for full documentation. + Refer to `rsome.math.log` for full documentation. See Also -------- - rso.log : equivalent function + rsome.math.log : equivalent function """ return self.to_affine().log() @@ -993,11 +1042,11 @@ def plog(self, scale): Return the perspective of natural logarithm function scale * log(var/scale). - Refer to `rsome.plog` for full documentation. + Refer to `rsome.math.plog` for full documentation. See Also -------- - rso.plog : equivalent function + rsome.math.plog : equivalent function """ return self.to_affine().plog(scale) @@ -1006,11 +1055,11 @@ def entropy(self): """ Return the natural exponential function -sum(var*log(var)). - Refer to `rsome.entropy` for full documentation. + Refer to `rsome.math.entropy` for full documentation. See Also -------- - rso.entropy : equivalent function + rsome.math.entropy : equivalent function """ return self.to_affine().entropy() @@ -1019,11 +1068,11 @@ def softplus(self): """ Return the softplus function log(1 + exp(var)). - Refer to `rsome.softplus` for full documentation. + Refer to `rsome.math.softplus` for full documentation. See Also -------- - rso.softplus : equivalent function + rsome.math.softplus : equivalent function """ return self.to_affine().softplus() @@ -1032,16 +1081,25 @@ def kldiv(self, q, r): """ Return the KL divergence constraints sum(var*log(var/q)) <= r. - Refer to `rsome.kldiv` for full documentation. + Refer to `rsome.math.kldiv` for full documentation. See Also -------- - rso.kldiv : equivalent function + rsome.math.kldiv : equivalent function """ return self.to_affine().kldiv(q, r) def trace(self): + """ + Return the trace of a 2D array. + + Refer to `rsome.lp.trace` for full documentation. + + See Also + -------- + rsome.lp.rsocone : equivalent function + """ return self.to_affine().trace() @@ -1384,6 +1442,21 @@ def T(self): return Affine(self.model, linear, const) def reshape(self, shape): + """ + Returns an array containing the same affine expressions with a + new shape. + + Parameters + ---------- + shape : tuple + The new shape of the returned array. + + Returns + ------- + out : Affine + An array of the specified shape containing the given affine + expressions. + """ if isinstance(self.const, np.ndarray): new_const = self.const.reshape(shape) @@ -1392,10 +1465,27 @@ def reshape(self, shape): return Affine(self.model, self.linear, new_const) def flatten(self): + """ + Returns a 1D array containing the same affine expressions. + + Returns + ------- + out : Affine + A 1D array of the given affine expressions. + """ return self.reshape((self.size, )) def diag(self, k=0, fill=False): + """ + Return the diagonal elements of a 2-D array. + + Refer to `rsome.math.diag` for full documentation. + + See Also + -------- + rsome.math.diag : equivalent function + """ if len(self.shape) != 2: raise ValueError('The diag function can only be applied to 2D arrays.') @@ -1424,6 +1514,16 @@ def diag(self, k=0, fill=False): return self[idx_row, idx_col] def tril(self, k=0): + """ + Return the lower triangular elements of a 2-D array. The remaining + elements are filled with zeros. + + Refer to `rsome.math.tril` for full documentation. + + See Also + -------- + rsome.math.tril : equivalent function + """ if len(self.shape) != 2: raise ValueError('The tril function can only be applied to 2D arrays.') @@ -1439,6 +1539,16 @@ def tril(self, k=0): return affine def triu(self, k=0): + """ + Return the upper triangular elements of a 2-D array. The remaining + elements are filled with zeros. + + Refer to `rsome.math.triu` for full documentation. + + See Also + -------- + rsome.math.triu : equivalent function + """ if len(self.shape) != 2: raise ValueError('The tril function can only be applied to 2D arrays.') @@ -1466,6 +1576,15 @@ def sum(self, axis=None): return Affine(self.model, linear, const) def trace(self): + """ + Return the trace of a 2D array. + + Refer to `rsome.math.trace` for full documentation. + + See Also + -------- + rsome.math.rsocone : equivalent function + """ if len(self.shape) != 2: raise ValueError('The trace function only applies to two-dimensional arrays') @@ -1487,11 +1606,11 @@ def norm(self, degree): """ Return the first, second, or infinity norm of a 1-D array. - Refer to `rsome.norm` for full documentation + Refer to `rsome.math.norm` for full documentation See Also -------- - rso.norm : equivalent function + rsome.math.norm : equivalent function """ if len(self.shape) != 1: @@ -1513,11 +1632,11 @@ def square(self): """ Return the element-wise square of an array. - Refer to `rsome.square` for full documentation + Refer to `rsome.math.square` for full documentation See Also -------- - rso.square : equivalent function + rsome.math.square : equivalent function """ size = self.size @@ -1529,11 +1648,11 @@ def sumsqr(self): """ Return the sum of squares of a 1-D array. - Refer to `rsome.sumsqr` for full documentation. + Refer to `rsome.math.sumsqr` for full documentation. See Also -------- - rso.sumsqr : equivalent function + rsome.math.sumsqr : equivalent function """ shape = self.shape @@ -1549,11 +1668,11 @@ def quad(self, qmat): """ Return the quadratic expression var @ qmat @ var. - Refer to `rsome.quad` for full documentation. + Refer to `rsome.math.quad` for full documentation. See Also -------- - rso.quad : equivalent function + rsome.math.quad : equivalent function """ if len(self.shape) != 1: @@ -1581,11 +1700,11 @@ def rsocone(self, y, z): """ Return the rotated second-order cone constraint. - Refer to `rsome.rsocone` for full documentation. + Refer to `rsome.math.rsocone` for full documentation. See Also -------- - rso.rsocone : equivalent function + rsome.math.rsocone : equivalent function """ if self.size > 1: @@ -1623,11 +1742,11 @@ def expcone(self, x, z): """ Return the exponential cone constraint z*exp(x/z) <= affine. - Refer to `rsome.expcone` for full documentation. + Refer to `rsome.math.expcone` for full documentation. See Also -------- - rso.expcone : equivalent function + rsome.math.expcone : equivalent function """ if isinstance(x, (Vars, VarSub)): @@ -1656,11 +1775,11 @@ def exp(self): """ Return the natural exponential function exp(affine). - Refer to `rsome.exp` for full documentation. + Refer to `rsome.math.exp` for full documentation. See Also -------- - rso.exp : equivalent function + rsome.math.exp : equivalent function """ return Convex(self, np.zeros(self.shape), 'X', 1) @@ -1670,11 +1789,11 @@ def pexp(self, scale): Return the perspective natural exponential function scale * exp(affine/scale). - Refer to `rsome.pexp` for full documentation. + Refer to `rsome.math.pexp` for full documentation. See Also -------- - rso.pexp : equivalent function + rsome.math.pexp : equivalent function """ return PerspConvex(self, scale, np.zeros(self.shape), 'X', 1) @@ -1683,11 +1802,11 @@ def log(self): """ Return the natural logarithm function log(affine). - Refer to `rsome.log` for full documentation. + Refer to `rsome.math.log` for full documentation. See Also -------- - rso.log : equivalent function + rsome.math.log : equivalent function """ return Convex(self, np.zeros(self.shape), 'L', -1) @@ -1697,11 +1816,11 @@ def plog(self, scale): Return the perspective of natural logarithm function scale * log(affine/scale). - Refer to `rsome.plog` for full documentation. + Refer to `rsome.math.plog` for full documentation. See Also -------- - rso.plog : equivalent function + rsome.math.plog : equivalent function """ return PerspConvex(self, scale, np.zeros(self.shape), 'L', -1) @@ -1710,11 +1829,11 @@ def entropy(self): """ Return the natural exponential function -sum(affine*log(affine)). - Refer to `rsome.entropy` for full documentation. + Refer to `rsome.math.entropy` for full documentation. See Also -------- - rso.entropy : equivalent function + rsome.math.entropy : equivalent function """ if self.shape != (): @@ -1727,11 +1846,11 @@ def softplus(self): """ Return the softplus function log(1 + exp(var)). - Refer to `rsome.softplus` for full documentation. + Refer to `rsome.math.softplus` for full documentation. See Also -------- - rso.softplus : equivalent function + rsome.math.softplus : equivalent function """ return Convex(self, np.zeros(self.shape), 'F', 1) @@ -1740,11 +1859,11 @@ def kldiv(self, q, r): """ Return the KL divergence constraints sum(affine*log(affine/q)) <= r. - Refer to `rsome.kldiv` for full documentation. + Refer to `rsome.math.kldiv` for full documentation. See Also -------- - rso.kldiv : equivalent function + rsome.math.kldiv : equivalent function """ affine = self.to_affine().reshape((self.size, )) @@ -3609,6 +3728,15 @@ def sum(self, axis=None): return DecAffine(self.dro_model, expr, self.event_adapt, self.fixed) def trace(self): + """ + Return the trace of a 2D array. + + Refer to `rsome.lp.trace` for full documentation. + + See Also + -------- + rsome.lp.rsocone : equivalent function + """ expr = super().trace() @@ -4817,25 +4945,14 @@ def lp_export(self): each_line = each_line[2:] string += ' c{}: '.format(i+1) + each_line - string += ' <= ' if self.sense[i] == 0 else ' == ' + string += ' <= ' if self.sense[i] == 0 else ' = ' string += '{}\n'.format(self.const[i]) ub, lb = self.ub, self.lb nvar = len(ub) - ub_string = '\n'.join([' x{} <= {}'.format(i+1, ub[i]) - for i in range(nvar) if ub[i] < np.inf]) - lb_string = '\n'.join([' {} <= x{}'.format(lb[i], i+1) - for i in range(nvar) if lb[i] > -np.inf]) - free_string = '\n'.join([' x{} free'.format(i+1) - for i in range(nvar) - if lb[i] == -np.inf and ub[i] == np.inf]) string += 'Bounds\n' - if len(ub_string) > 0: - string += ub_string + '\n' - if len(lb_string) > 0: - string += lb_string + '\n' - if len(free_string) > 0: - string += free_string + '\n' + for i in range(nvar): + string += '{} <= x{} <= {}\n'.format(lb[i], i+1, ub[i]) ind_int, = np.where(self.vtype == 'I') int_string = '\n'.join(['x{}'.format(i+1) for i in ind_int]) diff --git a/rsome/math.py b/rsome/math.py index 7f6c174..7436b9a 100644 --- a/rsome/math.py +++ b/rsome/math.py @@ -109,6 +109,7 @@ def quad(x, qmat): x : an array of variables or affine expressions Input array. The array must be 1-D. qmat : a positive or negative semidefinite matrix. + A matrix representing the quadratic coefficients. Returns ------- @@ -432,10 +433,10 @@ def diag(affine, k=0, fill=False): filled with zeros. Otherwise, return the diagonal elements as a 1-D array - Returns - ------- - out : Affine - The diagonal elements of a given 2-D array. + Returns + ------- + out : Affine + The diagonal elements of a given 2-D array. """ return affine.diag(k, fill) @@ -446,20 +447,19 @@ def tril(affine, k=0): Return the lower triangular elements of a 2-D array. The remaining elements are filled with zeros. - Parameters - ---------- - affine : an array of variables or affine expressions. - Input array. It must be a 2-D array. + Parameters + ---------- + affine : an array of variables or affine expressions. + Input array. It must be a 2-D array. - k : int, optional - Diagonal above which to zero elements. `k = 0` (the - default) is the main diagonal, `k < 0` is below it and - `k > 0` is above. + k : int, optional + Diagonal above which to zero elements. `k = 0` (the default) is + the main diagonal, `k < 0` is below it and `k > 0` is above. - Returns - ------- - out : Affine - The lower triangular elements of the given 2-D array. + Returns + ------- + out : Affine + The lower triangular elements of the given 2-D array. """ return affine.tril(k) @@ -481,7 +481,7 @@ def triu(affine, k): Returns ------- - out : Affine + out : Affine The upper triangular elements of the given 2-D array. """ diff --git a/setup.cfg b/setup.cfg index 802d98b..a469d4f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = rsome -version = 1.2.5 +version = 1.2.6 description = Robust Stochastic Optimization Made Easy long_description = file: README.rst author = Peng Xiong